Skip to content

Commit fc59ffb

Browse files
committed
Test schematic version, add docs examples
1 parent 4f553e7 commit fc59ffb

File tree

10 files changed

+341
-54
lines changed

10 files changed

+341
-54
lines changed

docs/src/routes.md

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ To draw a `Path` from a `Route`, you can use [`Path(r::Route, sty)`](@ref).
1010

1111
More often, you won't work directly with the `Route` type but will instead use [`route!`](@ref) to extend an existing path to an endpoint according to the rules you specify.
1212

13-
## Route API
13+
## Reference
1414

1515
```@docs
1616
Paths.Route
@@ -24,6 +24,8 @@ More often, you won't work directly with the `Route` type but will instead use [
2424
Paths.StraightAnd90
2525
Paths.StraightAnd45
2626
Paths.CompoundRouteRule
27+
Paths.SingleChannelRouting
28+
Paths.RouteChannel
2729
```
2830

2931
### Route drawing
@@ -53,3 +55,128 @@ A `Route` supports endpoint inspection much like a `Path` does:
5355
Paths.p1(::Paths.Route)
5456
Paths.α1(::Paths.Route)
5557
```
58+
59+
## Examples
60+
61+
### Channel routing
62+
63+
`RouteChannels` offer a way to run routes in parallel, with routes joining or leaving a channel at different points. Using `SingleChannelRouting`, we can set the "track" (a path offset from the channel centerline) for each route through the channel, as well as some rules for joining and leaving the channel from route start and end points. Here's a basic example with a straight channel:
64+
65+
```@example 1
66+
using DeviceLayout, .PreferredUnits, FileIO
67+
import DeviceLayout.Graphics: inch
68+
# Define start and end points for various routes
69+
p0s = [
70+
Point(100.0, -200.0)μm, # Enter and exit from bottom
71+
Point(600.0, -150)μm, # Enter from bottom, exit from right
72+
Point(-100.0, -100.0)μm, # Enter from lower left, exit from right
73+
Point(50.0, 150)μm, # Enter from top, exit from right
74+
Point(100.0, 200.0)μm # Enter and exit from top
75+
]
76+
77+
p1s = [
78+
Point(400.0, -200.0)μm,
79+
Point(1100.0, -150.0)μm,
80+
Point(1200.0, 50.0)μm,
81+
Point(1100.0, 150.0)μm,
82+
Point(900.0, 200.0)μm
83+
]
84+
85+
# Create channel
86+
channel_path = Path()
87+
straight!(channel_path, 1mm, Paths.Trace(0.1mm))
88+
channel = Paths.RouteChannel(channel_path)
89+
# Initialize paths
90+
paths = [Path(p) for p in p0s]
91+
# Define route rule
92+
transition_rule = Paths.StraightAnd90(25μm) # Manhattan with 25μm bend radius
93+
margin = 50.0μm # Room for bends between endpoints and channel
94+
rule = Paths.SingleChannelRouting(channel, transition_rule, margin)
95+
# Set tracks
96+
tracks = [1, 1, 2, 3, 4] # First two share a track
97+
setindex!.(Ref(rule.segment_tracks), tracks, paths)
98+
# Draw routes
99+
for (pa, p1) in zip(paths, p1s)
100+
route!(pa, p1, 0.0°, rule, Paths.Trace(2μm))
101+
end
102+
c = Cell("test")
103+
render!.(c, paths, GDSMeta())
104+
render!(c, channel_path, GDSMeta(2))
105+
save("straight_channel.svg", flatten(c); width=6inch, height=2inch)
106+
```
107+
108+
```@raw html
109+
<img src="../straight_channel.svg" style="width:6in;"/>
110+
```
111+
112+
We can also have curved channels, like the `BSpline`-based example below. For the transition rule, `StraightAnd90` would no longer work for the paths that join the channel at an angle, so we use `BSplineRouting` instead. We also enable `auto_speed` and `auto_curvature` on that rule to help smooth out the B-splines and maintain continuous curvature.
113+
114+
```@example 1
115+
# Create BSpline channel
116+
channel_path = Path()
117+
bspline!(
118+
channel_path,
119+
[Point(0.5, 0.5)mm, Point(1.0mm, 0.0μm)],
120+
0°,
121+
Paths.Trace(0.1mm),
122+
auto_speed=true,
123+
auto_curvature=true
124+
)
125+
channel = Paths.RouteChannel(channel_path)
126+
# Initialize paths
127+
paths = [Path(p) for p in p0s]
128+
# Define route rule
129+
transition_rule = Paths.BSplineRouting(auto_speed=true, auto_curvature=true)
130+
margin = 50.0μm
131+
rule = Paths.SingleChannelRouting(channel, transition_rule, margin)
132+
# Set tracks
133+
tracks = [1, 1, 2, 3, 4] # First two share a track
134+
setindex!.(Ref(rule.segment_tracks), tracks, paths)
135+
# Draw routes
136+
for (pa, p1) in zip(paths, p1s)
137+
route!(pa, p1, 0.0°, rule, Paths.Trace(2μm))
138+
end
139+
c = Cell("test")
140+
render!.(c, paths, GDSMeta())
141+
render!(c, channel_path, GDSMeta(2))
142+
save("bspline_channel.svg", flatten(c); width=6inch, height=4inch)
143+
```
144+
145+
```@raw html
146+
<img src="../bspline_channel.svg" style="width:6in;"/>
147+
```
148+
149+
Channels can also have variable width, like the example below using the `TaperTrace` style on a compound segment consisting of four turns.
150+
151+
```@example 1
152+
# Create tapered, composite channel
153+
channel_path = Path()
154+
turn!(channel_path, 90°, 0.25mm, Paths.Trace(0.1mm))
155+
turn!(channel_path, -90°, 0.25mm)
156+
turn!(channel_path, -90°, 0.25mm)
157+
turn!(channel_path, 90°, 0.25mm)
158+
simplify!(channel_path)
159+
setstyle!(channel_path[1], Paths.TaperTrace(0.1mm, 0.05mm))
160+
channel = Paths.RouteChannel(channel_path)
161+
# Initialize paths
162+
paths = [Path(p) for p in p0s]
163+
# Define route rule
164+
transition_rule = Paths.BSplineRouting(auto_speed=true, auto_curvature=true)
165+
margin = 50.0μm
166+
rule = Paths.SingleChannelRouting(channel, transition_rule, margin)
167+
# Set tracks
168+
tracks = [1, 1, 2, 3, 4] # First two share a track
169+
setindex!.(Ref(rule.segment_tracks), tracks, paths)
170+
# Draw routes
171+
for (pa, p1) in zip(paths, p1s)
172+
route!(pa, p1, 0.0°, rule, Paths.Trace(2μm))
173+
end
174+
c = Cell("test")
175+
render!.(c, paths, GDSMeta())
176+
render!(c, channel_path, GDSMeta(2))
177+
save("compound_channel.svg", flatten(c); width=6inch, height=4inch)
178+
```
179+
180+
```@raw html
181+
<img src="../compound_channel.svg" style="width:6in;"/>
182+
```

src/paths/channels.jl

Lines changed: 91 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
11
"""
22
RouteChannel{T} <: AbstractComponent{T}
33
RouteChannel(pa::Path)
4+
5+
A channel that routes can be guided through along parallel tracks.
6+
7+
Used in `route!` with [`Paths.SingleChannelRouting`](@ref).
8+
9+
The `Path` used to construct a `RouteChannel` should use `Trace` styles only.
10+
11+
A `RouteChannel` is an `AbstractComponent` with the same hooks as its `Path` and
12+
an empty geometry.
413
"""
514
struct RouteChannel{T} <: AbstractComponent{T}
615
path::Path{T}
716
node::Node{T} # path as single node
8-
capacity::Int # Currently unused
917
end
18+
name(ch::RouteChannel) = name(ch.path)
19+
DeviceLayout.hooks(ch::RouteChannel) = DeviceLayout.hooks(ch.path)
1020

11-
function RouteChannel(pa::Path{T}, capacity=0) where {T}
12-
length(nodes(pa)) != 1 && return RouteChannel{T}(pa, simplify(pa), capacity)
13-
return RouteChannel{T}(pa, only(nodes(pa)), capacity)
21+
function RouteChannel(pa::Path{T}) where {T}
22+
length(nodes(pa)) != 1 && return RouteChannel{T}(pa, simplify(pa))
23+
return RouteChannel{T}(pa, only(nodes(pa)))
1424
end
1525

1626
# Return a node corresponding to the section of the channel that the segment actually runs through
@@ -24,14 +34,14 @@ function segment_channel_section(
2434
) where {T}
2535
d = wireseg_stop - wireseg_start
2636
# Adjust for margins and track vs channel direction to get the channel node section used by actual segment
27-
if abs(d) <= 2 * margin + prev_width + next_width
37+
if abs(d) <= 2 * margin + prev_width / 2 + next_width / 2
2838
# handle case where margin consumes entire segment
2939
# Just have a zero length Straight at the midpoint
3040
track_mid = (wireseg_start + wireseg_stop) / 2
3141
midpoint = ch.node.seg(track_mid)
3242
middir = direction(ch.node.seg, track_mid)
3343
channel_section = Node(
34-
Straight(zero(T), midpoint, middir),
44+
Straight(zero(T); p0=midpoint, α0=middir),
3545
SimpleTrace(width(ch.node.sty, track_mid))
3646
)
3747
elseif d > zero(d) # segment is along channel direction
@@ -56,13 +66,15 @@ function segment_channel_section(
5666
return channel_section
5767
end
5868

69+
# Actual routed path segment along a track offset from the channel path
5970
function track_path_segment(n_tracks, channel_section, track_idx; reversed=false)
6071
return offset(
6172
channel_section.seg,
6273
track_section_offset(n_tracks, width(channel_section.sty), track_idx; reversed)
6374
)
6475
end
6576

77+
# Offset coordinate or function for the section of track with given width
6678
function track_section_offset(
6779
n_tracks,
6880
section_width::Coordinate,
@@ -192,33 +204,97 @@ function _reconcile_curvature!(n::Node{T}, rule::BSplineRouting) where {T}
192204
end
193205
end
194206

195-
struct SingleChannelRouting{T} <: AbstractChannelRouting
207+
"""
208+
struct SingleChannelRouting{T <: Coordinate} <: AbstractChannelRouting
209+
SingleChannelRouting(ch::RouteChannel, transition_rule::RouteRule, margin::T)
210+
SingleChannelRouting(ch::RouteChannel, transition_rules, margins)
211+
212+
A `RouteRule` for guiding routed paths along tracks in a [`Paths.RouteChannel`](@ref).
213+
214+
## Tracks
215+
216+
"Tracks" are offsets of the channel's path, with equal spacing between each other
217+
and the extents of the channel's trace width. Tracks are ordered from right to left
218+
when looking along the channel. For example, track 1 is the bottom track
219+
(most negative offset) for a channel directed along the positive x axis, while
220+
the highest track index is the top track.
221+
222+
The user manually assigns tracks to paths that will be routed with
223+
`rule::SingleChannelRouting` using `set_track!(rule, path)` for each path,
224+
prior to calling `route!(path, ...)`.
225+
226+
If used for schematic routing, the track is supplied as a keyword argument,
227+
defaulting to a new track added at the top of the channel:
228+
`route!(g::SchematicGraph, rule, ...; track=num_tracks(rule)+1)`.
229+
230+
## Routing
231+
232+
A path routed from `p0` to `p1` using this rule will enter the channel
233+
at the channel's closest point to `p0` and exit at the closest point to `p1` if
234+
`margin` is zero. For nonzero `margin`, the entry and exit points are each shifted
235+
towards the other along the channel by `margin`, allowing more space for the
236+
transitions into and out of the channel.
237+
238+
The middle "tracked" section is offset from the channel's center line according to
239+
the path's track, the maximum track assigned to any path by the rule,
240+
and the channel width.
241+
242+
The path is routed from `p0` to the tracked section and from the tracked section
243+
to `p1` using `transition_rule`.
244+
245+
Transition rules and margins can also be supplied as tuples to the constructor
246+
to allow different parameters for entry and exit transitions.
247+
"""
248+
mutable struct SingleChannelRouting{T <: Coordinate} <: AbstractChannelRouting
196249
channel::RouteChannel{T}
197250
transition_rules::Tuple{<:RouteRule, <:RouteRule}
198251
transition_margins::Tuple{T, T}
199-
segment_tracks::Dict{Path{T}, Int}
252+
segment_tracks::Dict{Path, Int}
253+
global_channel::RouteChannel{T}
254+
function SingleChannelRouting(
255+
ch::RouteChannel{T},
256+
rules,
257+
margins,
258+
tracks=Dict{Path, Int}()
259+
) where {T}
260+
return new{T}(ch, rules, margins, tracks)
261+
end
262+
end
263+
function SingleChannelRouting(
264+
ch::RouteChannel{T},
265+
rule::RouteRule,
266+
margin,
267+
tracks...
268+
) where {T}
269+
return SingleChannelRouting(ch, (rule, rule), (margin, margin), tracks...)
200270
end
201-
function SingleChannelRouting(ch::RouteChannel{T}, rule::RouteRule, margin::T) where {T}
202-
return SingleChannelRouting{T}(ch, (rule, rule), (margin, margin), Dict{Path{T}, Int}())
271+
function channel(rule::SingleChannelRouting)
272+
isdefined(rule, :global_channel) && return rule.global_channel
273+
return rule.channel
203274
end
204275
entry_rules(scr::SingleChannelRouting) = [first(scr.transition_rules)]
205276
exit_rule(scr::SingleChannelRouting) = last(scr.transition_rules)
206277
entry_margin(scr::SingleChannelRouting) = first(scr.transition_margins)
207278
exit_margin(scr::SingleChannelRouting) = last(scr.transition_margins)
208-
num_tracks(scr::SingleChannelRouting) = maximum(values(scr.segment_tracks))
209-
track_idx(scr, pa) = scr.segment_tracks[pa]
279+
function num_tracks(scr::SingleChannelRouting)
280+
isempty(scr.segment_tracks) && return 0
281+
return maximum(values(scr.segment_tracks))
282+
end
283+
function track_idx(scr, pa)
284+
return scr.segment_tracks[pa]
285+
end
210286
function set_track!(scr, pa, track_idx)
211287
return scr.segment_tracks[pa] = track_idx
212288
end
213289

214290
function track_path_segments(rule::SingleChannelRouting, pa::Path, endpt)
215-
wireseg_start = pathlength_nearest(rule.channel.node.seg, p1(pa))
216-
wireseg_stop = pathlength_nearest(rule.channel.node.seg, endpt)
291+
wireseg_start = pathlength_nearest(channel(rule).node.seg, p1(pa))
292+
wireseg_stop = pathlength_nearest(channel(rule).node.seg, endpt)
217293
return [
218294
track_path_segment(
219295
num_tracks(rule),
220296
segment_channel_section(
221-
rule.channel,
297+
channel(rule),
222298
wireseg_start,
223299
wireseg_stop,
224300
2 * entry_margin(rule),
@@ -229,13 +305,3 @@ function track_path_segments(rule::SingleChannelRouting, pa::Path, endpt)
229305
)
230306
]
231307
end
232-
233-
function _update_with_graph!(
234-
rule::SingleChannelRouting,
235-
route_node,
236-
graph;
237-
track=num_tracks(rule) + 1,
238-
kwargs...
239-
)
240-
return set_track!(rule, route_node.component._path, track)
241-
end

src/paths/paths.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1319,7 +1319,7 @@ function split(seg::Segment, sty::Style, x)
13191319
end
13201320

13211321
function split(seg::ContinuousSegment, x)
1322-
if !(zero(x) < x < pathlength(seg))
1322+
if !(zero(x) <= x <= pathlength(seg))
13231323
throw(ArgumentError("x must be between 0 and pathlength(seg)"))
13241324
end
13251325
return _split(seg, x)

src/paths/routes.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ abstract type RouteRule end
3333
min_bend_radius = 200μm
3434
max_bend_radius = Inf*μm
3535
end
36+
StraightAnd90(r) = StraightAnd90(min_bend_radius=r, max_bend_radius=r)
3637
3738
Specifies rules for routing from one point to another using straight segments and 90° bends.
3839
@@ -46,12 +47,14 @@ Base.@kwdef struct StraightAnd90 <: RouteRule
4647
min_bend_radius = 200μm
4748
max_bend_radius = Inf * μm
4849
end
50+
StraightAnd90(r) = StraightAnd90(min_bend_radius=r, max_bend_radius=r)
4951

5052
"""
5153
Base.@kwdef struct StraightAnd45 <: RouteRule
5254
min_bend_radius = 200μm
5355
max_bend_radius = Inf*μm
5456
end
57+
StraightAnd45(r) = StraightAnd45(min_bend_radius=r, max_bend_radius=r)
5558
5659
Specifies rules for routing from one point to another using using straight segments and 45° bends.
5760
@@ -65,6 +68,7 @@ Base.@kwdef struct StraightAnd45 <: RouteRule
6568
min_bend_radius = 200μm
6669
max_bend_radius = Inf * μm
6770
end
71+
StraightAnd45(r) = StraightAnd45(min_bend_radius=r, max_bend_radius=r)
6872

6973
"""
7074
Base.@kwdef struct BSplineRouting <: RouteRule
@@ -610,6 +614,3 @@ function _route_leg!(
610614
turn!(p, 45° * sign(perp), bend_r, seg_sty[2])
611615
return d_diag > zero(dx) && straight!(p, d_diag, seg_sty[3])
612616
end
613-
614-
function _update_with_plan!(rule::RouteRule, route_node, schematic) end
615-
function _update_with_graph!(rule::RouteRule, route_node, graph; kwargs...) end

src/paths/segments/offset.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ struct GeneralOffset{T, S} <: OffsetSegment{T, S}
3131
offset
3232
end
3333

34+
convert(::Type{ConstantOffset{T}}, x::ConstantOffset) where {T} =
35+
ConstantOffset(convert(Segment{T}, x.seg), convert(T, x.offset))
36+
convert(::Type{ConstantOffset{T}}, x::ConstantOffset{T}) where {T} = x
37+
convert(::Type{Segment{T}}, x::ConstantOffset) where {T} = convert(ConstantOffset{T}, x)
38+
39+
convert(::Type{GeneralOffset{T}}, x::GeneralOffset) where {T} =
40+
OffsetSegment(convert(Segment{T}, x.seg), x.offset)
41+
convert(::Type{GeneralOffset{T}}, x::GeneralOffset{T}) where {T} = x
42+
convert(::Type{Segment{T}}, x::GeneralOffset) where {T} = convert(GeneralOffset{T}, x)
43+
3444
copy(s::OffsetSegment) = OffsetSegment(copy(s.seg), s.offset)
3545
getoffset(s::ConstantOffset, l...) = s.offset
3646
getoffset(s::GeneralOffset{T}) where {T} = l -> uconvert(unit(T), s.offset(l))

0 commit comments

Comments
 (0)