[examples] fixup examples and add port_pather example
This commit is contained in:
parent
d8702af5b9
commit
37418d2137
4 changed files with 176 additions and 67 deletions
|
|
@ -1,10 +1,10 @@
|
|||
"""
|
||||
Manual wire routing tutorial: Pather and BasicTool
|
||||
Manual wire routing tutorial: Pather and AutoTool
|
||||
"""
|
||||
from collections.abc import Callable
|
||||
from numpy import pi
|
||||
from masque import Pather, RenderPather, Library, Pattern, Port, layer_t, map_layers
|
||||
from masque.builder.tools import BasicTool, PathTool
|
||||
from masque.builder.tools import AutoTool, PathTool
|
||||
from masque.file.gdsii import writefile
|
||||
|
||||
from basic_shapes import GDS_OPTS
|
||||
|
|
@ -110,14 +110,10 @@ def map_layer(layer: layer_t) -> layer_t:
|
|||
return layer_mapping.get(layer, layer)
|
||||
|
||||
|
||||
#
|
||||
# Now we can start building up our library (collection of static cells) and pathing tools.
|
||||
#
|
||||
# If any of the operations below are confusing, you can cross-reference against the `RenderPather`
|
||||
# tutorial, which handles some things more explicitly (e.g. via placement) and simplifies others
|
||||
# (e.g. geometry definition).
|
||||
#
|
||||
def main() -> None:
|
||||
def prepare_tools() -> tuple[Library, Tool, Tool]:
|
||||
"""
|
||||
Create some basic library elements and tools for drawing M1 and M2
|
||||
"""
|
||||
# Build some patterns (static cells) using the above functions and store them in a library
|
||||
library = Library()
|
||||
library['pad'] = make_pad()
|
||||
|
|
@ -140,53 +136,79 @@ def main() -> None:
|
|||
# M2_tool will route on M2, using wires with M2_WIDTH
|
||||
# Both tools are able to automatically transition from the other wire type (with a via)
|
||||
#
|
||||
# Note that while we use BasicTool for this tutorial, you can define your own `Tool`
|
||||
# Note that while we use AutoTool for this tutorial, you can define your own `Tool`
|
||||
# with arbitrary logic inside -- e.g. with single-use bends, complex transition rules,
|
||||
# transmission line geometry, or other features.
|
||||
#
|
||||
M1_tool = BasicTool(
|
||||
straight = (
|
||||
M1_tool = AutoTool(
|
||||
# First, we need a function which takes in a length and spits out an M1 wire
|
||||
lambda length: make_straight_wire(layer='M1', ptype='m1wire', width=M1_WIDTH, length=length),
|
||||
'input', # When we get a pattern from make_straight_wire, use the port named 'input' as the input
|
||||
'output', # and use the port named 'output' as the output
|
||||
straights = [
|
||||
AutoTool.Straight(
|
||||
ptype = 'm1wire',
|
||||
fn = lambda length: make_straight_wire(layer='M1', ptype='m1wire', width=M1_WIDTH, length=length),
|
||||
in_port_name = 'input', # When we get a pattern from make_straight_wire, use the port named 'input' as the input
|
||||
out_port_name = 'output', # and use the port named 'output' as the output
|
||||
),
|
||||
bend = (
|
||||
library.abstract('m1_bend'), # When we need a bend, we'll reference the pattern we generated earlier
|
||||
'input', # To orient it clockwise, use the port named 'input' as the input
|
||||
'output', # and 'output' as the output
|
||||
],
|
||||
bends = [
|
||||
AutoTool.Bend(
|
||||
abstract = library.abstract('m1_bend'), # When we need a bend, we'll reference the pattern we generated earlier
|
||||
in_port_name = 'input',
|
||||
out_port_name = 'output',
|
||||
clockwise = True,
|
||||
),
|
||||
],
|
||||
transitions = { # We can automate transitions for different (normally incompatible) port types
|
||||
'm2wire': ( # For example, when we're attaching to a port with type 'm2wire'
|
||||
('m2wire', 'm1wire'): AutoTool.Transition( # For example, when we're attaching to a port with type 'm2wire'
|
||||
library.abstract('v1_via'), # we can place a V1 via
|
||||
'top', # using the port named 'top' as the input (i.e. the M2 side of the via)
|
||||
'bottom', # and using the port named 'bottom' as the output
|
||||
),
|
||||
},
|
||||
sbends = [],
|
||||
default_out_ptype = 'm1wire', # Unless otherwise requested, we'll default to trying to stay on M1
|
||||
)
|
||||
|
||||
M2_tool = BasicTool(
|
||||
straight = (
|
||||
M2_tool = AutoTool(
|
||||
straights = [
|
||||
# Again, we use make_straight_wire, but this time we set parameters for M2
|
||||
lambda length: make_straight_wire(layer='M2', ptype='m2wire', width=M2_WIDTH, length=length),
|
||||
'input',
|
||||
'output',
|
||||
AutoTool.Straight(
|
||||
ptype = 'm2wire',
|
||||
fn = lambda length: make_straight_wire(layer='M2', ptype='m2wire', width=M2_WIDTH, length=length),
|
||||
in_port_name = 'input',
|
||||
out_port_name = 'output',
|
||||
),
|
||||
bend = (
|
||||
library.abstract('m2_bend'), # and we use an M2 bend
|
||||
'input',
|
||||
'output',
|
||||
],
|
||||
bends = [
|
||||
# and we use an M2 bend
|
||||
AutoTool.Bend(
|
||||
abstract = library.abstract('m2_bend'),
|
||||
in_port_name = 'input',
|
||||
out_port_name = 'output',
|
||||
),
|
||||
],
|
||||
transitions = {
|
||||
'm1wire': (
|
||||
('m1wire', 'm2wire'): AutoTool.Transition(
|
||||
library.abstract('v1_via'), # We still use the same via,
|
||||
'bottom', # but the input port is now 'bottom'
|
||||
'top', # and the output port is now 'top'
|
||||
),
|
||||
},
|
||||
sbends = [],
|
||||
default_out_ptype = 'm2wire', # We default to trying to stay on M2
|
||||
)
|
||||
return library, M1_tool, M2_tool
|
||||
|
||||
|
||||
#
|
||||
# Now we can start building up our library (collection of static cells) and pathing tools.
|
||||
#
|
||||
# If any of the operations below are confusing, you can cross-reference against the `RenderPather`
|
||||
# tutorial, which handles some things more explicitly (e.g. via placement) and simplifies others
|
||||
# (e.g. geometry definition).
|
||||
#
|
||||
def main() -> None:
|
||||
library, M1_tool, M2_tool = prepare_tools()
|
||||
|
||||
#
|
||||
# Create a new pather which writes to `library` and uses `M2_tool` as its default tool.
|
||||
|
|
@ -272,7 +294,7 @@ def main() -> None:
|
|||
pather.path_to('GND', None, -50_000)
|
||||
|
||||
# Save the pather's pattern into our library
|
||||
library['Pather_and_BasicTool'] = pather.pattern
|
||||
library['Pather_and_AutoTool'] = pather.pattern
|
||||
|
||||
# Convert from text-based layers to numeric layers for GDS, and output the file
|
||||
library.map_layers(map_layer)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
Routines for creating normalized 2D lattices and common photonic crystal
|
||||
cavity designs.
|
||||
"""
|
||||
from collection.abc import Sequence
|
||||
from collections.abc import Sequence
|
||||
|
||||
import numpy
|
||||
from numpy.typing import ArrayLike, NDArray
|
||||
|
|
@ -198,11 +198,11 @@ def ln_defect(
|
|||
"""
|
||||
if defect_length % 2 != 1:
|
||||
raise Exception('defect_length must be odd!')
|
||||
p = triangular_lattice([2 * d + 1 for d in mirror_dims])
|
||||
pp = triangular_lattice([2 * dd + 1 for dd in mirror_dims])
|
||||
half_length = numpy.floor(defect_length / 2)
|
||||
hole_nums = numpy.arange(-half_length, half_length + 1)
|
||||
holes_to_keep = numpy.in1d(p[:, 0], hole_nums, invert=True)
|
||||
return p[numpy.logical_or(holes_to_keep, p[:, 1] != 0), ]
|
||||
holes_to_keep = numpy.isin(pp[:, 0], hole_nums, invert=True)
|
||||
return pp[numpy.logical_or(holes_to_keep, pp[:, 1] != 0), :]
|
||||
|
||||
|
||||
def ln_shift_defect(
|
||||
|
|
@ -248,7 +248,7 @@ def ln_shift_defect(
|
|||
for sign in (-1, 1):
|
||||
x_val = sign * (x_removed + ind + 1)
|
||||
which = numpy.logical_and(xyr[:, 0] == x_val, xyr[:, 1] == 0)
|
||||
xyr[which, ] = (x_val + numpy.sign(x_val) * shifts_a[ind], 0, shifts_r[ind])
|
||||
xyr[which, :] = (x_val + numpy.sign(x_val) * shifts_a[ind], 0, shifts_r[ind])
|
||||
|
||||
return xyr
|
||||
|
||||
|
|
@ -309,7 +309,7 @@ def l3_shift_perturbed_defect(
|
|||
|
||||
# which holes should be perturbed? (xs[[3, 7]], ys[1]) and (xs[[2, 6]], ys[2])
|
||||
perturbed_holes = ((xs[a], ys[b]) for a, b in ((3, 1), (7, 1), (2, 2), (6, 2)))
|
||||
for row in xyr:
|
||||
if numpy.fabs(row) in perturbed_holes:
|
||||
row[2] = perturbed_radius
|
||||
for xy in perturbed_holes:
|
||||
which = (numpy.fabs(xyr[:, :2]) == xy).all(axis=1)
|
||||
xyr[which, 2] = perturbed_radius
|
||||
return xyr
|
||||
|
|
|
|||
86
examples/tutorial/port_pather.py
Normal file
86
examples/tutorial/port_pather.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
"""
|
||||
PortPather tutorial: Using .at() syntax for fluent port manipulation
|
||||
"""
|
||||
from numpy import pi
|
||||
from masque import Pather, Library, Pattern, Port
|
||||
from masque.builder.tools import AutoTool
|
||||
from masque.file.gdsii import writefile
|
||||
|
||||
from basic_shapes import GDS_OPTS
|
||||
# Reuse helper functions and constants from the basic pather tutorial
|
||||
from pather import (
|
||||
M1_WIDTH, V1_WIDTH, M2_WIDTH,
|
||||
make_pad, make_via, make_bend, make_straight_wire, map_layer
|
||||
|
||||
)
|
||||
|
||||
def main() -> None:
|
||||
# Reuse the same patterns (pads, bends, vias) and tools as in pather.py
|
||||
library, M1_tool, M2_tool = prepare_tools()
|
||||
library['pad'] = make_pad()
|
||||
library['m1_bend'] = make_bend(layer='M1', ptype='m1wire', width=M1_WIDTH)
|
||||
library['m2_bend'] = make_bend(layer='M2', ptype='m2wire', width=M2_WIDTH)
|
||||
library['v1_via'] = make_via(
|
||||
layer_top='M2', layer_via='V1', layer_bot='M1',
|
||||
width_top=M2_WIDTH, width_via=V1_WIDTH, width_bot=M1_WIDTH,
|
||||
ptype_bot='m1wire', ptype_top='m2wire',
|
||||
)
|
||||
|
||||
# Create a RenderPather and place some initial pads (same as Pather tutorial)
|
||||
rpather = RenderPather(library, tools=M2_tool)
|
||||
|
||||
rpather.place('pad', offset=(18_000, 30_000), port_map={'wire_port': 'VCC'})
|
||||
rpather.place('pad', offset=(18_000, 60_000), port_map={'wire_port': 'GND'})
|
||||
rpather.pattern.label(layer='M2', string='VCC', offset=(18e3, 30e3))
|
||||
rpather.pattern.label(layer='M2', string='GND', offset=(18e3, 60e3))
|
||||
|
||||
#
|
||||
# Routing with .at()
|
||||
#
|
||||
# The .at(port_name) method returns a PortPather object which wraps the Pather
|
||||
# and remembers the selected port(s). This allows method chaining.
|
||||
|
||||
# Then we can route just like in the other pather tutorials:
|
||||
(rpather.at('VCC')
|
||||
.path(ccw=False, length=6_000)
|
||||
.path_to(ccw=None, x=0)
|
||||
)
|
||||
|
||||
rpather.at('GND').path(0, 5_000).path_to(None, x=rpather['VCC'].x)
|
||||
|
||||
|
||||
# We're using AutoTool so we could retool directly to M1_ptool like in the Pather
|
||||
# tutorial, but let's manually plug to demonstrate what it looks like:
|
||||
(rpather.at('GND')
|
||||
.plug('v1_via', 'top')
|
||||
.retool(M1_tool) # this only retools the 'GND' port
|
||||
)
|
||||
|
||||
# We can also pass multiple ports to .at(), and then use .mpath() on them:
|
||||
(rpather.at(['GND', 'VCC'])
|
||||
.mpath(ccw=True, xmax=-10_000, spacing=5_000)
|
||||
.retool(M1_tool) # this retools both ports
|
||||
.mpath(ccw=True, emax=50_000, spacing=1_200) # ...causing an automatic via on VCC here
|
||||
.mpath(ccw=False, emin=1_000, spacing=1_200)
|
||||
.mpath(ccw=False, emin=2_000, spacing=4_500)
|
||||
)
|
||||
|
||||
# Now we can finish up the equivalent to the other tutorials..
|
||||
rpather.at('VCC').retool(M2_tool)
|
||||
rpather.at(['GND', 'VCC']).mpath(None, xmin=-28_000)
|
||||
|
||||
rpather.at('VCC').path_to(None, x=-50_000, out_ptype='m1wire')
|
||||
with pather.toolctx(M2_tool, keys=['GND']):
|
||||
pather.at('GND').path_to(None, x=-40_000)
|
||||
pather.at('GND').path_to(None, x=-50_000)
|
||||
|
||||
#
|
||||
# Save result
|
||||
#
|
||||
library['PortPather_Tutorial'] = pather.pattern
|
||||
library.map_layers(map_layer)
|
||||
writefile(library, 'port_pather.gds', **GDS_OPTS)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -42,7 +42,7 @@ def main() -> None:
|
|||
M2_ptool = PathTool(layer='M2', width=M2_WIDTH, ptype='m2wire')
|
||||
rpather = RenderPather(tools=M2_ptool, library=library)
|
||||
|
||||
# As in the pather tutorial, we make soem pads and labels...
|
||||
# As in the pather tutorial, we make some pads and labels...
|
||||
rpather.place('pad', offset=(18_000, 30_000), port_map={'wire_port': 'VCC'})
|
||||
rpather.place('pad', offset=(18_000, 60_000), port_map={'wire_port': 'GND'})
|
||||
rpather.pattern.label(layer='M2', string='VCC', offset=(18e3, 30e3))
|
||||
|
|
@ -52,7 +52,7 @@ def main() -> None:
|
|||
rpather.path('VCC', ccw=False, length=6_000)
|
||||
rpather.path_to('VCC', ccw=None, x=0)
|
||||
rpather.path('GND', 0, 5_000)
|
||||
rpather.path_to('GND', None, x=rpather['VCC'].offset[0])
|
||||
rpather.path_to('GND', None, x=rpather['VCC'].x)
|
||||
|
||||
# `PathTool` doesn't know how to transition betwen metal layers, so we have to
|
||||
# `plug` the via into the GND wire ourselves.
|
||||
|
|
@ -76,13 +76,14 @@ def main() -> None:
|
|||
# just ask it to transition to an 'm1wire' port at the end of the final VCC segment.
|
||||
# Instead, we have to calculate the via size ourselves, and adjust the final position
|
||||
# to account for it.
|
||||
via_size = abs(
|
||||
library['v1_via'].ports['top'].offset[0]
|
||||
- library['v1_via'].ports['bottom'].offset[0]
|
||||
)
|
||||
v1pat = library['v1_via']
|
||||
via_size = abs(v1pat.ports['top'].x - v1pat.ports['bottom'].x)
|
||||
# alternatively, via_size = v1pat.ports['top'].measure_travel(v1pat.ports['bottom'])[0][0]
|
||||
# would take into account the port orientations if we didn't already know they're along x
|
||||
rpather.path_to('VCC', None, -50_000 + via_size)
|
||||
rpather.plug('v1_via', {'VCC': 'top'})
|
||||
|
||||
# Render the path we defined
|
||||
rpather.render()
|
||||
library['RenderPather_and_PathTool'] = rpather.pattern
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue