Add RenderPather tutorial, tutorial README, and some minor doc updates
This commit is contained in:
parent
11c483cd46
commit
a36d3d397b
@ -1 +1,39 @@
|
|||||||
TODO write tutorial readme
|
masque Tutorial
|
||||||
|
===============
|
||||||
|
|
||||||
|
Contents
|
||||||
|
--------
|
||||||
|
|
||||||
|
- [basic_shapes](basic_shapes.py):
|
||||||
|
* Draw basic geometry
|
||||||
|
* Export to GDS
|
||||||
|
- [devices](devices.py)
|
||||||
|
* Reference other patterns
|
||||||
|
* Add ports to a pattern
|
||||||
|
* Snap ports together to build a circuit
|
||||||
|
* Check for dangling references
|
||||||
|
- [library](library.py)
|
||||||
|
* Create a `LazyLibrary`, which loads / generates patterns only when they are first used
|
||||||
|
* Explore alternate ways of specifying a pattern for `.plug()` and `.place()`
|
||||||
|
* Design a pattern which is meant to plug into an existing pattern (via `.interface()`)
|
||||||
|
- [pather](pather.py)
|
||||||
|
* Use `Pather` to route individual wires and wire bundles
|
||||||
|
* Use `BasicTool` to generate paths
|
||||||
|
* Use `BasicTool` to automatically transition between path types
|
||||||
|
- [renderpather](rendpather.py)
|
||||||
|
* Use `RenderPather` and `PathTool` to build a layout similar to the one in [pather](pather.py),
|
||||||
|
but using `Path` shapes instead of `Polygon`s.
|
||||||
|
|
||||||
|
|
||||||
|
Additionaly, [pcgen](pcgen.py) is a utility module for generating photonic crystal lattices.
|
||||||
|
|
||||||
|
|
||||||
|
Running
|
||||||
|
-------
|
||||||
|
|
||||||
|
Run from inside the examples directory:
|
||||||
|
```bash
|
||||||
|
cd examples/tutorial
|
||||||
|
python3 basic_shapes.py
|
||||||
|
klayout -e basic_shapes.gds
|
||||||
|
```
|
||||||
|
@ -31,7 +31,7 @@ def ports_to_data(pat: Pattern) -> Pattern:
|
|||||||
|
|
||||||
def data_to_ports(lib: Mapping[str, Pattern], name: str, pat: Pattern) -> Pattern:
|
def data_to_ports(lib: Mapping[str, Pattern], name: str, pat: Pattern) -> Pattern:
|
||||||
"""
|
"""
|
||||||
Scans the Pattern to determine port locations. Same port format as `ports_to_data`
|
Scan the Pattern to determine port locations. Same port format as `ports_to_data`
|
||||||
"""
|
"""
|
||||||
return ports2data.data_to_ports(layers=[(3, 0)], library=lib, pattern=pat, name=name)
|
return ports2data.data_to_ports(layers=[(3, 0)], library=lib, pattern=pat, name=name)
|
||||||
|
|
||||||
@ -246,13 +246,14 @@ def main(interactive: bool = True) -> None:
|
|||||||
devices['ysplit'] = y_splitter(lattice_constant=a, hole='hole', mirror_periods=5)
|
devices['ysplit'] = y_splitter(lattice_constant=a, hole='hole', mirror_periods=5)
|
||||||
devices['l3cav'] = perturbed_l3(lattice_constant=a, hole='smile', hole_lib=shape_lib, xy_size=(4, 10)) # uses smile :)
|
devices['l3cav'] = perturbed_l3(lattice_constant=a, hole='smile', hole_lib=shape_lib, xy_size=(4, 10)) # uses smile :)
|
||||||
|
|
||||||
# Turn our dict of devices into a Library -- useful for getting abstracts
|
# Turn our dict of devices into a Library.
|
||||||
|
# This provides some convenience functions in the future!
|
||||||
lib = Library(devices)
|
lib = Library(devices)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Build a circuit
|
# Build a circuit
|
||||||
#
|
#
|
||||||
# Create a builder, and add the circuit to our library as "my_circuit"
|
# Create a `Builder`, and add the circuit to our library as "my_circuit".
|
||||||
circ = Builder(library=lib, name='my_circuit')
|
circ = Builder(library=lib, name='my_circuit')
|
||||||
|
|
||||||
# Start by placing a waveguide. Call its ports "in" and "signal".
|
# Start by placing a waveguide. Call its ports "in" and "signal".
|
||||||
@ -263,6 +264,14 @@ def main(interactive: bool = True) -> None:
|
|||||||
# are attaching (wg10), it automatically inherits the name "signal".
|
# are attaching (wg10), it automatically inherits the name "signal".
|
||||||
circ.plug('wg10', {'signal': 'left'})
|
circ.plug('wg10', {'signal': 'left'})
|
||||||
|
|
||||||
|
# We could have done the following instead:
|
||||||
|
# circ_pat = Pattern()
|
||||||
|
# lib['my_circuit'] = circ_pat
|
||||||
|
# circ_pat.place(lib.abstract('wg10'), ...)
|
||||||
|
# circ_pat.plug(lib.abstract('wg10'), ...)
|
||||||
|
# but `Builder` lets us omit some of the repetition of `lib.abstract(...)`, and uses similar
|
||||||
|
# syntax to `Pather` and `RenderPather`, which add wire/waveguide routing functionality.
|
||||||
|
|
||||||
# Attach a y-splitter to the signal path.
|
# Attach a y-splitter to the signal path.
|
||||||
# Since the y-splitter has 3 ports total, we can't auto-inherit the
|
# Since the y-splitter has 3 ports total, we can't auto-inherit the
|
||||||
# port name, so we have to specify what we want to name the unattached
|
# port name, so we have to specify what we want to name the unattached
|
||||||
|
@ -60,14 +60,17 @@ def main() -> None:
|
|||||||
circ2 = Builder(library=lib, ports='tri_l3cav')
|
circ2 = Builder(library=lib, ports='tri_l3cav')
|
||||||
|
|
||||||
# First way to get abstracts is `lib.abstract(name)`
|
# First way to get abstracts is `lib.abstract(name)`
|
||||||
|
# We can use this syntax directly with `Pattern.plug()` and `Pattern.place()` as well as through `Builder`.
|
||||||
circ2.plug(lib.abstract('wg10'), {'input': 'right'})
|
circ2.plug(lib.abstract('wg10'), {'input': 'right'})
|
||||||
|
|
||||||
# Second way to get abstracts is to use an AbstractView
|
# Second way to get abstracts is to use an AbstractView
|
||||||
|
# This also works directly with `Pattern.plug()` / `Pattern.place()`.
|
||||||
abstracts = lib.abstract_view()
|
abstracts = lib.abstract_view()
|
||||||
circ2.plug(abstracts['wg10'], {'output': 'left'})
|
circ2.plug(abstracts['wg10'], {'output': 'left'})
|
||||||
|
|
||||||
# Third way to specify an abstract works by automatically getting
|
# Third way to specify an abstract works by automatically getting
|
||||||
# it from the library already within the Builder object:
|
# it from the library already within the Builder object.
|
||||||
|
# This wouldn't work if we only had a `Pattern` (not a `Builder`).
|
||||||
# Just pass the pattern name!
|
# Just pass the pattern name!
|
||||||
circ2.plug('tri_wg10', {'input': 'right'})
|
circ2.plug('tri_wg10', {'input': 'right'})
|
||||||
circ2.plug('tri_wg10', {'output': 'left'})
|
circ2.plug('tri_wg10', {'output': 'left'})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Manual wire routing tutorial
|
Manual wire routing tutorial: Pather and BasicTool
|
||||||
"""
|
"""
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
@ -7,6 +7,7 @@ from masque import Pather, RenderPather, Library, Pattern, Port, layer_t, map_la
|
|||||||
from masque.builder.tools import BasicTool, PathTool
|
from masque.builder.tools import BasicTool, PathTool
|
||||||
from masque.file.gdsii import writefile
|
from masque.file.gdsii import writefile
|
||||||
|
|
||||||
|
from basic_shapes import GDS_OPTS
|
||||||
|
|
||||||
#
|
#
|
||||||
# Define some basic wire widths, in nanometers
|
# Define some basic wire widths, in nanometers
|
||||||
@ -97,10 +98,25 @@ def make_straight_wire(layer: layer_t, width: float, ptype: str, length: float)
|
|||||||
return pat
|
return pat
|
||||||
|
|
||||||
|
|
||||||
|
def map_layer(layer: layer_t) -> layer_t:
|
||||||
|
"""
|
||||||
|
Map from a strings to GDS layer numbers
|
||||||
|
"""
|
||||||
|
layer_mapping = {
|
||||||
|
'M1': (10, 0),
|
||||||
|
'M2': (20, 0),
|
||||||
|
'V1': (30, 0),
|
||||||
|
}
|
||||||
|
return layer_mapping.get(layer, layer)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Now we can start building up our library (collection of static cells) and pathing tools.
|
# 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 main() -> None:
|
||||||
# Build some patterns (static cells) using the above functions and store them in a library
|
# Build some patterns (static cells) using the above functions and store them in a library
|
||||||
library = Library()
|
library = Library()
|
||||||
@ -181,6 +197,7 @@ def main() -> None:
|
|||||||
# Place two pads, and define their ports as 'VCC' and 'GND'
|
# Place two pads, and define their ports as 'VCC' and 'GND'
|
||||||
pather.place('pad', offset=(18_000, 30_000), port_map={'wire_port': 'VCC'})
|
pather.place('pad', offset=(18_000, 30_000), port_map={'wire_port': 'VCC'})
|
||||||
pather.place('pad', offset=(18_000, 60_000), port_map={'wire_port': 'GND'})
|
pather.place('pad', offset=(18_000, 60_000), port_map={'wire_port': 'GND'})
|
||||||
|
# Add some labels to make the pads easier to distinguish
|
||||||
pather.pattern.label(layer='M2', string='VCC', offset=(18e3, 30e3))
|
pather.pattern.label(layer='M2', string='VCC', offset=(18e3, 30e3))
|
||||||
pather.pattern.label(layer='M2', string='GND', offset=(18e3, 60e3))
|
pather.pattern.label(layer='M2', string='GND', offset=(18e3, 60e3))
|
||||||
|
|
||||||
@ -251,56 +268,9 @@ def main() -> None:
|
|||||||
# Save the pather's pattern into our library
|
# Save the pather's pattern into our library
|
||||||
library['Pather_and_BasicTool'] = pather.pattern
|
library['Pather_and_BasicTool'] = pather.pattern
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
M1_ptool = PathTool(layer='M1', width=M1_WIDTH, ptype='m1wire')
|
|
||||||
M2_ptool = PathTool(layer='M2', width=M2_WIDTH, ptype='m2wire')
|
|
||||||
rpather = RenderPather(tools=M2_ptool, library=library).add_port_pair()
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
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.plug('v1_via', {'GND': 'top'})
|
|
||||||
rpather.retool(M1_ptool, keys=['GND'])
|
|
||||||
rpather.mpath(['GND', 'VCC'], ccw=True, xmax=-10_000, spacing=5_000)
|
|
||||||
|
|
||||||
rpather.plug('v1_via', {'VCC': 'top'})
|
|
||||||
rpather.retool(M1_ptool)
|
|
||||||
rpather.mpath(['GND', 'VCC'], ccw=True, emax=50_000, spacing=1_200)
|
|
||||||
rpather.mpath(['GND', 'VCC'], ccw=False, emin=1_000, spacing=1_200)
|
|
||||||
rpather.mpath(['GND', 'VCC'], ccw=False, emin=2_000, spacing=4_500)
|
|
||||||
|
|
||||||
rpather.plug('v1_via', {'VCC': 'bottom'})
|
|
||||||
rpather.retool(M2_ptool)
|
|
||||||
rpather.mpath(['GND', 'VCC'], None, xmin=-28_000)
|
|
||||||
via_size = abs(
|
|
||||||
library['v1_via'].ports['top'].offset[0]
|
|
||||||
- library['v1_via'].ports['bottom'].offset[0]
|
|
||||||
)
|
|
||||||
rpather.path_to('VCC', None, -50_000 + via_size) #, out_ptype='m1wire')
|
|
||||||
rpather.plug('v1_via', {'VCC': 'top'})
|
|
||||||
|
|
||||||
rpather.render()
|
|
||||||
library['RenderPather_and_PathTool'] = rpather.pattern
|
|
||||||
|
|
||||||
|
|
||||||
# Convert from text-based layers to numeric layers for GDS, and output the file
|
# Convert from text-based layers to numeric layers for GDS, and output the file
|
||||||
def map_layer(layer: layer_t) -> layer_t:
|
|
||||||
layer_mapping = {
|
|
||||||
'M1': (10, 0),
|
|
||||||
'M2': (20, 0),
|
|
||||||
'V1': (30, 0),
|
|
||||||
}
|
|
||||||
return layer_mapping.get(layer, layer)
|
|
||||||
library.map_layers(map_layer)
|
library.map_layers(map_layer)
|
||||||
writefile(library, 'pathers.gds', 1e-9)
|
writefile(library, 'pather.gds', **GDS_OPTS)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
96
examples/tutorial/renderpather.py
Normal file
96
examples/tutorial/renderpather.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
"""
|
||||||
|
Manual wire routing tutorial: RenderPather an PathTool
|
||||||
|
"""
|
||||||
|
from typing import Callable
|
||||||
|
from masque import RenderPather, Library, Pattern, Port, layer_t, map_layers
|
||||||
|
from masque.builder.tools import PathTool
|
||||||
|
from masque.file.gdsii import writefile
|
||||||
|
|
||||||
|
from basic_shapes import GDS_OPTS
|
||||||
|
from pather import M1_WIDTH, V1_WIDTH, M2_WIDTH, map_layer, make_pad, make_via
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
#
|
||||||
|
# To illustrate the advantages of using `RenderPather`, we use `PathTool` instead
|
||||||
|
# of `BasicTool`. `PathTool` lacks some sophistication (e.g. no automatic transitions)
|
||||||
|
# but when used with `RenderPather`, it can consolidate multiple routing steps into
|
||||||
|
# a single `Path` shape.
|
||||||
|
#
|
||||||
|
# We'll try to nearly replicate the layout from the `Pather` tutorial; see `pather.py`
|
||||||
|
# for more detailed descriptions of the individual pathing steps.
|
||||||
|
#
|
||||||
|
|
||||||
|
# First, we make a library and generate some of the same patterns as in the pather tutorial
|
||||||
|
library = Library()
|
||||||
|
library['pad'] = make_pad()
|
||||||
|
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',
|
||||||
|
)
|
||||||
|
|
||||||
|
# `PathTool` is more limited than `BasicTool`. It only generates one type of shape
|
||||||
|
# (`Path`), so it only needs to know what layer to draw on, what width to draw with,
|
||||||
|
# and what port type to present.
|
||||||
|
M1_ptool = PathTool(layer='M1', width=M1_WIDTH, ptype='m1wire')
|
||||||
|
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...
|
||||||
|
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))
|
||||||
|
|
||||||
|
# ...and start routing the signals.
|
||||||
|
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])
|
||||||
|
|
||||||
|
# `PathTool` doesn't know how to transition betwen metal layers, so we have to
|
||||||
|
# `plug` the via into the GND wire ourselves.
|
||||||
|
rpather.plug('v1_via', {'GND': 'top'})
|
||||||
|
rpather.retool(M1_ptool, keys=['GND'])
|
||||||
|
rpather.mpath(['GND', 'VCC'], ccw=True, xmax=-10_000, spacing=5_000)
|
||||||
|
|
||||||
|
# Same thing on the VCC wire when it goes down to M1.
|
||||||
|
rpather.plug('v1_via', {'VCC': 'top'})
|
||||||
|
rpather.retool(M1_ptool)
|
||||||
|
rpather.mpath(['GND', 'VCC'], ccw=True, emax=50_000, spacing=1_200)
|
||||||
|
rpather.mpath(['GND', 'VCC'], ccw=False, emin=1_000, spacing=1_200)
|
||||||
|
rpather.mpath(['GND', 'VCC'], ccw=False, emin=2_000, spacing=4_500)
|
||||||
|
|
||||||
|
# And again when VCC goes back up to M2.
|
||||||
|
rpather.plug('v1_via', {'VCC': 'bottom'})
|
||||||
|
rpather.retool(M2_ptool)
|
||||||
|
rpather.mpath(['GND', 'VCC'], None, xmin=-28_000)
|
||||||
|
|
||||||
|
# Finally, since PathTool has no conception of transitions, we can't
|
||||||
|
# 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]
|
||||||
|
)
|
||||||
|
rpather.path_to('VCC', None, -50_000 + via_size)
|
||||||
|
rpather.plug('v1_via', {'VCC': 'top'})
|
||||||
|
|
||||||
|
rpather.render()
|
||||||
|
library['RenderPather_and_PathTool'] = rpather.pattern
|
||||||
|
|
||||||
|
|
||||||
|
# Convert from text-based layers to numeric layers for GDS, and output the file
|
||||||
|
library.map_layers(map_layer)
|
||||||
|
writefile(library, 'render_pather.gds', **GDS_OPTS)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
Loading…
Reference in New Issue
Block a user