Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/src/paths.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ capture an initial and final angle, a radius, and an origin. All circular turns
parameterized with these variables.

Another useful `Segment` subtype is [`Paths.BSpline`](@ref), which interpolates between two
or more points with specified start and end tangents.
or more points with specified start and end tangents (and curvature, optionally) using a [cubic B-spline](https://en.wikipedia.org/wiki/B-spline#Cubic_B-Splines).
These have the property that curvature is continuous along the spline, and
can be automatically optimized further to avoid sharp changes in curvature.

## Styles

Expand Down
132 changes: 131 additions & 1 deletion docs/src/routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ To draw a `Path` from a `Route`, you can use [`Path(r::Route, sty)`](@ref).

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.

## Route API
## Reference

```@docs
Paths.Route
Expand All @@ -24,6 +24,8 @@ More often, you won't work directly with the `Route` type but will instead use [
Paths.StraightAnd90
Paths.StraightAnd45
Paths.CompoundRouteRule
Paths.SingleChannelRouting
Paths.RouteChannel
```

### Route drawing
Expand Down Expand Up @@ -53,3 +55,131 @@ A `Route` supports endpoint inspection much like a `Path` does:
Paths.p1(::Paths.Route)
Paths.α1(::Paths.Route)
```

## Examples

### Channel routing

`RouteChannels` offer a way to run routes in parallel, with routes joining or leaving a channel at different points. Using [`Paths.SingleChannelRouting`](@ref), we can set the "track" (a curve offset from the channel centerline) for each route to follow 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:

```@example 1
using DeviceLayout, .PreferredUnits, FileIO
import DeviceLayout.Graphics: inch
# Define start and end points for various routes
p0s = [
Point(100.0, 200.0)μm, # Enter and exit from top
Point(50.0, 150)μm, # Enter from top, exit from right
Point(-100.0, -100.0)μm, # Enter from lower left, exit from right
Point(600.0, -150)μm, # Enter from bottom, exit from right
Point(100.0, -200.0)μm # Enter and exit from bottom
]

p1s = [
Point(900.0, 200.0)μm,
Point(1100.0, 150.0)μm,
Point(1200.0, 50.0)μm,
Point(1100.0, -150.0)μm,
Point(400.0, -200.0)μm
]

# Create channel
channel_path = Path()
straight!(channel_path, 1mm, Paths.Trace(0.1mm))
channel = Paths.RouteChannel(channel_path)
# Initialize paths
paths = [Path(p) for p in p0s]
# Define route rule
transition_rule = Paths.StraightAnd90(25μm) # Manhattan with 25μm bend radius
margin = 50.0μm # Room for bends between endpoints and channel
rule = Paths.SingleChannelRouting(channel, transition_rule, margin)
# Set tracks
tracks = [1, 2, 3, 4, 4] # Last two share a track
setindex!.(Ref(rule.segment_tracks), tracks, paths)
# Draw routes
for (pa, p1) in zip(paths, p1s)
route!(pa, p1, 0.0°, rule, Paths.Trace(2μm))
end
c = Cell("test")
render!.(c, paths, GDSMeta())
render!(c, channel_path, GDSMeta(1))
save("straight_channel.svg", flatten(c); width=6inch, height=2inch);
nothing; # hide
```

```@raw html
<img src="../straight_channel.svg" style="width:6in;"/>
```

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.

```@example 1
# Create BSpline channel
channel_path = Path()
bspline!(
channel_path,
[Point(0.5, 0.5)mm, Point(1.0mm, 0.0μm)],
0°,
Paths.Trace(0.1mm),
auto_speed=true,
auto_curvature=true
)
channel = Paths.RouteChannel(channel_path)
# Initialize paths
paths = [Path(p) for p in p0s]
# Define route rule
transition_rule = Paths.BSplineRouting(auto_speed=true, auto_curvature=true)
margin = 50.0μm
rule = Paths.SingleChannelRouting(channel, transition_rule, margin)
# Set tracks
tracks = [1, 2, 3, 4, 4] # Last two share a track
setindex!.(Ref(rule.segment_tracks), tracks, paths)
# Draw routes
for (pa, p1) in zip(paths, p1s)
route!(pa, p1, 0.0°, rule, Paths.Trace(2μm))
end
c = Cell("test")
render!.(c, paths, GDSMeta())
render!(c, channel_path, GDSMeta(1))
save("bspline_channel.svg", flatten(c); width=6inch, height=4inch);
nothing; # hide
```

```@raw html
<img src="../bspline_channel.svg" style="width:6in;"/>
```

Channels can also have variable width, like the example below using the `TaperTrace` style on a compound segment consisting of four turns.

```@example 1
# Create tapered, composite channel
channel_path = Path()
turn!(channel_path, 90°, 0.25mm, Paths.Trace(0.1mm))
turn!(channel_path, -90°, 0.25mm)
turn!(channel_path, -90°, 0.25mm)
turn!(channel_path, 90°, 0.25mm)
simplify!(channel_path)
setstyle!(channel_path[1], Paths.TaperTrace(0.1mm, 0.05mm))
channel = Paths.RouteChannel(channel_path)
# Initialize paths
paths = [Path(p) for p in p0s]
# Define route rule
transition_rule = Paths.BSplineRouting(auto_speed=true, auto_curvature=true)
margin = 50.0μm
rule = Paths.SingleChannelRouting(channel, transition_rule, margin)
# Set tracks
tracks = [1, 2, 3, 4, 4] # Last two share a track
setindex!.(Ref(rule.segment_tracks), tracks, paths)
# Draw routes
for (pa, p1) in zip(paths, p1s)
route!(pa, p1, 0.0°, rule, Paths.Trace(2μm))
end
c = Cell("test")
render!.(c, paths, GDSMeta())
render!(c, channel_path, GDSMeta(1))
save("compound_channel.svg", flatten(c); width=6inch, height=4inch);
nothing; # hide
```

```@raw html
<img src="../compound_channel.svg" style="width:6in;"/>
```
74 changes: 45 additions & 29 deletions docs/src/schematicdriven/schematics.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,61 @@ attach!(::SchematicDrivenLayout.SchematicGraph,

A [`Paths.Route`](@ref) is like a `Path`, but defined implicitly in terms of its endpoints and rules (like "use only straight sections and 90 degree turns") for getting from one end to another. We can add `Route`s between components in a schematic using [`route!`](@ref), creating flexible connections that are only resolved after floorplanning has determined the positions of the components to be connected.

In more detail: Typically, each `fuse!` operation fully determines the position and rotation of the new node. The exception is that the first node added to each connected component of the `SchematicGraph` is positioned at the origin.

!!! info "Terminology note: connected component"

There is a graph-theory term "connected component" unrelated to our `AbstractComponent`, indicating a subgraph whose nodes are all connected by edges and which isn't part of a larger such subgraph. For example, a fully connected graph has one connected component, the entire graph. Sometimes you may even hear these called "components", but below we use "connected component" for the graph-theory sense and "component" for `AbstractComponent`.

In more detail: Typically, each `fuse!` operation fully determines the position and rotation of the new node. For each connected component, the first node added to the `SchematicGraph` is positioned at the origin. A typical workflow could start by creating a node with a "chip template" component containing port [`hooks`](@ref SchematicDrivenLayout.hooks) and other boilerplate. We then `fuse!` any CPW launchers to the template. The devices in the middle of the chip are added and fused to one another without yet fixing their position relative to the chip template. Next, one connection is made between this connected component of devices and the connected component containing the template, fixing their relative positions. Since the chip template was added first, it will be centered at the origin.
For example, a workflow might start by creating a node with a "chip template" component containing port [`hooks`](@ref SchematicDrivenLayout.hooks) and other boilerplate. We then `fuse!` any CPW launchers to the template. The devices in the middle of the chip are added and fused to one another without yet fixing their position relative to the chip template. Next, one connection is made between this connected component of devices and the connected component containing the template and launchers, fixing their relative positions. Since the chip template was added first, it will be centered at the origin.

At this point, further connections still need to be made between various device ports and CPW launchers positioned on the chip template, all of which are now fully constrained. Another constraint like those created by `fuse!` so far would overconstrain the layout, causing floorplanning with `plan` to fail. The remaining connections are instead made with [`route!`](@ref), which creates a `RouteNode` with edges to the `ComponentNode`s at its start and end points. Then, during `plan`, after the initial floorplanning phase has determined position of all fixed `Component`s, the route node finds a path between its start and end points.
At this point, further connections still need to be made between various device ports and CPW launchers positioned on the chip template, all of which are now fully constrained. Using `fuse!` to connect a `Path` to both ports would overconstrain the layout, causing floorplanning with `plan` to fail unless the `Path` is drawn precisely to agree with the existing constraints. But the designer may want to vary parameters that change the port positions without redefining that `Path`, or they may simply not want to have to calculate the precise path themselves. So the remaining connections are instead defined with the desired flexibility using [`route!`](@ref). This creates a `RouteNode` with edges to the `ComponentNode`s at its start and end points. Then, after `plan`, the initial floorplanning phase has determined the position of all fixed `Component`s, so the route node can find a path between its start and end points.

!!! tip "Schematic connections without geometric constraints"

You can add edges to the schematic graph that will be ignored during `plan` using the keyword `plan_skips_edge=true` in `fuse!`.

### Differences between schematic and geometry-level routing

In geometry-level layout, we can extend a `Path` using `route!(path, p1, α1, rule, style; waypoints=[], waydirs=[])`. The schematic-level call looks a bit different: `route_node = route!(graph, rule, node1=>hook1, node2=>hook2, style, metadata; waypoints=[], waydirs=[], global_waypoints=false, kwargs...)`. In this case, the start and end points and directions are not known until after `plan`, and no path is actually calculated until until we `build!` or `render!` the schematic, or we call `path(route_node.component)`.

By default, `global_waypoints=false`, meaning that waypoints and directions are viewed as relative the the route start, with the positive x axis oriented along the route's initial start direction. Often `global_waypoints=true` is more useful, especially for a simple interactive routing workflow: When you view your final layout
built from the schematic, you may find that a route bends too sharply or goes too close to a
component. You can write down the points it needs to go to in the schematic's global coordinate system,
and add them as waypoints to the route. That is, if you go back to your layout script,
you can modify the `route!` call:

```julia
route_node = route!(
g,
rule,
node1 => hook1,
node2 => hook2,
sty,
meta; # Original route command
# Add waypoint information to to `route!` call
global_waypoints=true, # Waypoints are relative to global schematic coordsys
# If global_waypoints=false (default), waypoints are relative to the route start
# with the initial route direction as the +x axis
waypoints=[Point(600.0μm, -3000.0μm)],
waydirs=[90°]
)
```

Now the route in `route_node` is guaranteed to pass through the point (600.0μm, -3000.0μm)
on its way to its destination. If the `RouteRule`'s implementation uses `waydirs`, then it
will also have a direction of 90° at that point.

Channel routing at the schematic level also gets some special handling. When using
the [`Paths.SingleChannelRouting`](@ref) rule, the router will look for the rule's [`Paths.RouteChannel`](@ref)
in the schematic to get its global coordinates for routing. Additionally, paths are not assigned tracks in the rule using `Paths.set_track!` before `route!`. Instead, a route's track is set using the `track` keyword in `route!`,
defaulting to a new track at the bottom of the channel so far (`track=num_tracks(channel)+1`).
Because the routes are not drawn until later, the track offsets are still calculated using a
number of tracks given by the maximum track number of all routes that are eventually added to
the channel with the same rule. (Each route in the channel should still use the same instance of the `SingleChannelRouting` rule.)

Note that routes through a channel are no different from other routes as far as the schematic graph is concerned. That is, they are still just routes from one component's hook to another component's hook; they just happen to have a RouteRule that references the channel between them. One way to think about it is that the channel acts as a kind of extended waypoint. In particular, routes are not fused to the channel, and the channel component doesn't contain any individual route geometries in its own geometry (which is just empty).

## Schematics

```@docs
Expand Down Expand Up @@ -96,33 +139,6 @@ position in schematic-global or wafer-global coordinates as an argument:
SchematicDrivenLayout.position_dependent_replace!
```

One other useful trick allows a kind of interactive routing. When you view your final layout
built from the schematic, you may find that a route bends too sharply or goes too close to a
component. You can write down the points it needs to go to in the schematic's global coordinate system,
and add them as waypoints to the route. That is, if you go back to your layout script,
before you `render!` the layout, you can do something like

```julia
### original script
g = SchematicGraph("example")
...
route_node = route!(g, args...)
...
floorplan = plan(g)
check!(floorplan)
### modifications
# waypoints are global coordinates, not relative to the route's origin
route_node.component.global_waypoints = true
route_node.component.r.waypoints = [Point(600.0μm, -3000.0μm)]
route_node.component.r.waydirs = [90°]
### render to `cell` with options from `target`
render!(cell, floorplan, target)
```

Now the route in `route_node` is guaranteed to pass through the point (600.0μm, -3000.0μm)
on its way to its destination. If the `RouteRule`'s implementation uses `waydirs`, then it
will also have a direction of 90° at that point.

### Automatic crossover generation

You can automatically generate crossovers between `Path`s and `RouteComponent`s, including those nested within composite components. This is based on [`Path` intersection functionality](../paths.md#Intersections).
Expand Down
2 changes: 1 addition & 1 deletion src/DeviceLayout.jl
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ Return the coordinate type of the geometry.
coordinatetype(::Type{S}) where {T, S <: AbstractGeometry{T}} = T
coordinatetype(::S) where {T, S <: AbstractGeometry{T}} = T
coordinatetype(::AbstractArray{S}) where {T, S <: AbstractGeometry{T}} = T
coordinatetype(iterable) = promote_type(coordinatetype.(iterable))
coordinatetype(iterable) = promote_type(coordinatetype.(iterable)...)

# Entity interface
include("entities.jl")
Expand Down
1 change: 1 addition & 0 deletions src/hooks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Contains information describing how one component can attach to others.
"""
abstract type Hook{T} <: AbstractGeometry{T} end
getp(h::Hook) = h.p

"""
PointHook(p::Point, in_direction)
Expand Down
Loading