YAML Place and AutoRoute#
You have two options for working with gdsfactory:
python flow: you define your layout using python functions (Parametric Cells), and connect them with routing functions.
YAML Place and AutoRoute: you define your Component as Place and Route in YAML. From the netlist you can simulate the Component or generate the layout.
The YAML format contains the schematic together with placement information.
YAML is a human readable version of JSON that you can use to define placements and routes
to define a a YAML Component you need to define:
instances: with each instance setting
placements: with X and Y
And optionally:
routes: between instance ports
connections: to connect instance ports to other ports (without routes)
ports: define input and output ports for the top level Component.
gdsfactory VSCode extension has a filewatcher for *.pic.yml
files that will show them live in klayout as you edit them.
The extension provides you with useful code snippets and filewatcher extension to see live modifications of *pic.yml
or *.py
files. Look for the telescope button on the top right of VSCode ðŸ”.
import gdsfactory as gf
from IPython.display import Code
filepath = "yaml_pics/pads.pic.yml"
Code(filepath, language="yaml+jinja")
instances:
bl:
component: pad
tl:
component: pad
br:
component: pad
tr:
component: pad
placements:
tl:
x: -200
y: 500
br:
x: 400
y: 400
tr:
x: 400
y: 600
routes:
electrical:
settings:
separation: 20
width: 10
links:
tl,e3: tr,e1
bl,e3: br,e1
optical:
settings:
radius: 100
links:
bl,e4: br,e3
c = gf.read.from_yaml(filepath)
c.plot()
2024-10-31 18:40:07.424 | WARNING | gdsfactory.read.from_yaml:from_yaml:692 - UserWarning: prefix is deprecated and will be removed soon. _from_yaml
2024-10-31 18:40:07.803 | WARNING | gdsfactory.component:plot_klayout:1646 - UserWarning: Unnamed cells, 1 in 'Unnamed_a6480b42'
Lets start by defining the instances
and placements
section in YAML
Lets place an mmi_long
where you can place the o1
port at x=20, y=10
filepath = "yaml_pics/mmis.pic.yml"
Code(filepath, language="yaml+jinja")
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 10
mmi_short:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
placements:
mmi_long:
port: o1
x: 20
y: 10
mirror: False
c = gf.read.from_yaml(filepath)
c.plot()
2024-10-31 18:40:08.041 | WARNING | gdsfactory.read.from_yaml:from_yaml:692 - UserWarning: prefix is deprecated and will be removed soon. _from_yaml
2024-10-31 18:40:08.053 | WARNING | gdsfactory.component:plot_klayout:1646 - UserWarning: Unnamed cells, 1 in 'Unnamed_1d55b19a'
ports#
You can expose any ports of any instance to the new Component with a ports
section in YAML
Lets expose all the ports from mmi_long
into the new component.
Ports are exposed as new_port_name: instance_name, port_name
filepath = "yaml_pics/ports_demo.pic.yml"
Code(filepath, language="yaml+jinja")
name: ports_demo
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
placements:
mmi_long:
port: o1
x: 20
y: 10
mirror: True
ports:
o3: mmi_long,o3
o2: mmi_long,o2
o1: mmi_long,o1
c = gf.read.from_yaml(filepath)
c.plot()
You can also define a mirror placement using a port
Try mirroring with other ports o2
, o3
or with a number as well as with a rotation 90
, 180
, 270
filepath = "yaml_pics/mirror_demo.pic.yml"
Code(filepath, language="yaml+jinja")
name: mirror_demo
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
placements:
mmi_long:
x: 0
y: 0
mirror: o1
rotation: 0
c = gf.read.from_yaml(filepath)
c.plot()
connections#
You can connect any two instances by defining a connections
section in the YAML file.
it follows the syntax instance_source,port : instance_destination,port
filepath = "yaml_pics/connections_demo.pic.yml"
Code(filepath, language="yaml+jinja")
name: connections_demo
instances:
b:
component: bend_circular
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 10
mmi_short:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
placements:
mmi_short:
port: o1
x: 10
y: 20
connections:
b,o1 : mmi_short,o2
mmi_long,o1: b, o2
ports:
o1: mmi_short,o1
o2: mmi_long,o2
o3: mmi_long,o3
c = gf.read.from_yaml(filepath)
c.plot()
Relative port placing
You can also place a component with respect to another instance port
You can also define an x and y offset with dx
and dy
filepath = "yaml_pics/relative_port_placing.pic.yml"
Code(filepath, language="yaml+jinja")
name: relative_port_placing
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 10
mmi_short:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
placements:
mmi_short:
port: o1
x: 0
y: 0
mmi_long:
port: o1
x: mmi_short,o2
y: mmi_short,o2
dx : 10
dy: -10
c = gf.read.from_yaml(filepath)
c.plot()
routes#
You can define routes between two instances by defining a routes
section in YAML
it follows the syntax
routes:
route_name:
links:
instance_source,port: instance_destination,port
settings: # for the route (optional)
waveguide: strip
width: 1.2
filepath = "yaml_pics/routes.pic.yml"
Code(filepath, language="yaml+jinja")
name: with_routes
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 10
mmi_short:
component: mmi1x2
settings:
width_mmi: 4.5
length_mmi: 5
placements:
mmi_long:
x: 100
y: 100
routes:
optical:
links:
mmi_short,o2: mmi_long,o1
settings:
cross_section:
cross_section: xs_sc
c = gf.read.from_yaml(filepath)
c.plot()
instances, placements, connections, ports, routes#
Lets combine all you learned so far.
You can define the netlist connections of a component by a netlist in YAML format
Note that you define the connections as instance_source.port -> instance_destination.port
so the order is important and therefore you can only
change the position of the instance_destination
You can define several routes that will be connected using gf.routing.get_bundle
filepath = "yaml_pics/routes_mmi.pic.yml"
Code(filepath, language="yaml+jinja")
name: routes_mmi
instances:
mmi_bottom:
component: mmi2x2
mmi_top:
component: mmi2x2
placements:
mmi_top:
x: 100
y: 100
routes:
optical:
links:
mmi_bottom,o4: mmi_top,o1
mmi_bottom,o3: mmi_top,o2
c = gf.read.from_yaml(filepath)
c.plot()
You can also add custom component_factories to gf.read.from_yaml
@gf.cell
def pad_new(size=(100, 100), layer=(1, 0)):
c = gf.Component()
compass = c << gf.components.compass(size=size, layer=layer)
c.ports = compass.ports
return c
gf.get_active_pdk().register_cells(pad_new=pad_new)
c = pad_new()
c.plot()
filepath = "yaml_pics/new_factories.pic.yml"
Code(filepath, language="yaml+jinja")
name: new_factories
instances:
bot:
component: pad_new
top:
component: pad_new
placements:
top:
x: 0
y: 200
c = gf.read.from_yaml(filepath)
c.plot()
filepath = "yaml_pics/routes_custom.pic.yml"
Code(filepath, language="yaml+jinja")
name: routes_custom
instances:
t:
component: pad_array
settings:
orientation: 270
columns: 3
b:
component: pad_array
settings:
orientation: 90
columns: 3
placements:
t:
x: 200
y: 400
routes:
electrical:
settings:
width: 10.
end_straight_length: 150
links:
t,e11: b,e11
t,e13: b,e13
c = gf.read.from_yaml(filepath)
c.plot()
Also, you can define route bundles with different settings and specify the route factory
as a parameter as well as the settings
for that particular route alias.
filepath = "yaml_pics/pads_path_length_match.pic.yml"
Code(filepath, language="yaml+jinja")
name: pads_path_length_match
instances:
bl:
component: pad
tl:
component: pad
br:
component: pad
tr:
component: pad
placements:
tl:
x: -200
y: 500
br:
x: 400
y: 400
tr:
x: 400
y: 600
routes:
electrical:
settings:
separation: 20
width: 10
path_length_match_loops: 2
end_straight_length: 100
links:
tl,e3: tr,e1
bl,e3: br,e1
optical:
settings:
radius: 100
links:
bl,e4: br,e3
c = gf.read.from_yaml(filepath)
c.plot()
filepath = "yaml_pics/routes_path_length_match.pic.yml"
Code(filepath, language="yaml+jinja")
instances:
t:
component: pad_array
settings:
orientation: 270
columns: 3
b:
component: pad_array
settings:
orientation: 90
columns: 3
placements:
t:
x: 100
y: 1000
routes:
route1:
routing_strategy: get_bundle_path_length_match
settings:
extra_length: 500
width: 2
end_straight_length: 500
links:
t,e11: b,e11
t,e12: b,e12
c = gf.read.from_yaml(filepath)
c.plot()
2024-10-31 18:40:09.850 | WARNING | gdsfactory.component:plot_klayout:1646 - UserWarning: Unnamed cells, 1 in 'Unnamed_295dbf2b'
filepath = "yaml_pics/routes_waypoints.pic.yml"
Code(filepath, language="yaml+jinja")
instances:
t:
component: pad_array
settings:
orientation: 270
columns: 3
b:
component: pad_array
settings:
orientation: 90
columns: 3
placements:
t:
x: -250
y: 1000
routes:
route1:
routing_strategy: get_bundle_from_waypoints
settings:
waypoints:
- [0, 300]
- [400, 300]
- [400, 400]
- [-250, 400]
auto_widen: False
links:
b,e11: t,e11
b,e12: t,e12
c = gf.read.from_yaml(filepath)
c.plot()
2024-10-31 18:40:10.009 | WARNING | gdsfactory.component:plot_klayout:1646 - UserWarning: Unnamed cells, 1 in 'Unnamed_e96b0c79'
Jinja Pcells#
You use jinja templates in YAML cells to define Pcells.
from IPython.display import Code
from gdsfactory.read import cell_from_yaml_template
gf.clear_cache()
jinja_yaml = """
default_settings:
length_mmi:
value: 10
description: "The length of the long MMI"
width_mmi:
value: 5
description: "The width of both MMIs"
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: {{ width_mmi }}
length_mmi: {{ length_mmi }}
mmi_short:
component: mmi1x2
settings:
width_mmi: {{ width_mmi }}
length_mmi: 5
connections:
mmi_long,o2: mmi_short,o1
ports:
o1: mmi_long,o1
o2: mmi_short,o2
o3: mmi_short,o3
"""
pic_filename = "demo_jinja.pic.yml"
with open(pic_filename, mode="w") as f:
f.write(jinja_yaml)
pic_cell = cell_from_yaml_template(pic_filename, name="demo_jinja")
gf.get_active_pdk().register_cells(
demo_jinja=pic_cell
) # let's register this cell so we can use it later
Code(filename=pic_filename, language="yaml+jinja")
default_settings:
length_mmi:
value: 10
description: "The length of the long MMI"
width_mmi:
value: 5
description: "The width of both MMIs"
instances:
mmi_long:
component: mmi1x2
settings:
width_mmi: {{ width_mmi }}
length_mmi: {{ length_mmi }}
mmi_short:
component: mmi1x2
settings:
width_mmi: {{ width_mmi }}
length_mmi: 5
connections:
mmi_long,o2: mmi_short,o1
ports:
o1: mmi_long,o1
o2: mmi_short,o2
o3: mmi_short,o3
You’ll see that this generated a python function, with a real signature, default arguments, docstring and all!
help(pic_cell)
Help on function demo_jinja:
demo_jinja(*, length_mmi=10, width_mmi=5) -> gdsfactory.component.Component
demo_jinja: a templated yaml cell. This cell accepts keyword arguments only
Keyword Args:
length_mmi: The length of the long MMI
width_mmi: The width of both MMIs
You can invoke this cell without arguments to see the default implementation
c = pic_cell()
c.plot()
Or you can provide arguments explicitly, like a normal cell. Note however that yaml-based cells only accept keyword arguments, since yaml dictionaries are inherently unordered.
c = pic_cell(length_mmi=100)
c.plot()
The power of jinja-templated cells become more apparent with more complex cells, like the following.
gf.clear_cache()
jinja_yaml = """
default_settings:
length_mmis:
value: [10, 20, 30, 100]
description: "An array of mmi lengths for the DOE"
spacing_mmi:
value: 50
description: "The vertical spacing between adjacent MMIs"
mmi_component:
value: mmi1x2
description: "The mmi component to use"
instances:
{% for i in range(length_mmis|length)%}
mmi_{{ i }}:
component: {{ mmi_component }}
settings:
width_mmi: 4.5
length_mmi: {{ length_mmis[i] }}
{% endfor %}
placements:
{% for i in range(1, length_mmis|length)%}
mmi_{{ i }}:
port: o1
x: mmi_0,o1
y: mmi_0,o1
dy: {{ spacing_mmi * i }}
{% endfor %}
routes:
{% for i in range(1, length_mmis|length)%}
r{{ i }}:
routing_strategy: get_bundle_all_angle
links:
mmi_{{ i-1 }},o2: mmi_{{ i }},o1
{% endfor %}
ports:
{% for i in range(length_mmis|length)%}
o{{ i }}: mmi_{{ i }},o3
{% endfor %}
"""
pic_filename = "demo_jinja_loops.pic.yml"
with open(pic_filename, mode="w") as f:
f.write(jinja_yaml)
big_cell = cell_from_yaml_template(pic_filename, name="demo_jinja_loops")
Code(filename=pic_filename, language="yaml+jinja")
default_settings:
length_mmis:
value: [10, 20, 30, 100]
description: "An array of mmi lengths for the DOE"
spacing_mmi:
value: 50
description: "The vertical spacing between adjacent MMIs"
mmi_component:
value: mmi1x2
description: "The mmi component to use"
instances:
{% for i in range(length_mmis|length)%}
mmi_{{ i }}:
component: {{ mmi_component }}
settings:
width_mmi: 4.5
length_mmi: {{ length_mmis[i] }}
{% endfor %}
placements:
{% for i in range(1, length_mmis|length)%}
mmi_{{ i }}:
port: o1
x: mmi_0,o1
y: mmi_0,o1
dy: {{ spacing_mmi * i }}
{% endfor %}
routes:
{% for i in range(1, length_mmis|length)%}
r{{ i }}:
routing_strategy: get_bundle_all_angle
links:
mmi_{{ i-1 }},o2: mmi_{{ i }},o1
{% endfor %}
ports:
{% for i in range(length_mmis|length)%}
o{{ i }}: mmi_{{ i }},o3
{% endfor %}
bc = big_cell()
bc.plot()
2024-10-31 18:40:10.679 | WARNING | gdsfactory.component:_write_library:1934 - UserWarning: Component demo_jinja_loops has invalid transformations. Try component.flatten_offgrid_references() first.
bc2 = big_cell(
length_mmis=[10, 20, 40, 100, 200, 150, 10, 40],
spacing_mmi=60,
mmi_component="demo_jinja",
)
bc2.plot()
2024-10-31 18:40:11.383 | WARNING | gdsfactory.component:_write_library:1934 - UserWarning: Component demo_jinja_loops_6ca36734 has invalid transformations. Try component.flatten_offgrid_references() first.
In general, the jinja-yaml parser has a superset of the functionalities and syntax of the standard yaml parser. The one notable exception is with settings
. When reading any yaml files with settings
blocks, the default settings will be read and applied, but they will not be settable, as the jinja parser has a different mechanism for setting injection with the default_settings
block and jinja2.
filepath = "yaml_pics/mzi_lattice_filter.pic.yml"
mzi_lattice = cell_from_yaml_template(filepath, name="mzi_lattice_filter")
Code(filepath, language="yaml")
default_settings:
delta_length:
value: 20
description: "The delta length"
instances:
mzi1:
component: mzi
settings:
delta_length: {{ delta_length }}
mzi2:
component: mzi
settings:
delta_length: {{ delta_length }}
gc1:
component: grating_coupler_te
gc2:
component: grating_coupler_te
placements:
mzi2:
ymax: mzi1,north
dy: 100
xmin: mzi1,east
dx: 50
gc1:
xmax: mzi1,west
mirror: True
dx: -100
dy: -20
gc2:
xmin: mzi2,east
dx: 100
dy: 100
routes:
optical:
links:
mzi1,o2: mzi2,o1
settings:
auto_widen: True
gc1:
links:
gc1,o1: mzi1,o1
gc2:
links:
gc2,o1: mzi2,o2
c = mzi_lattice(delta_length=10)
c.plot()
c = mzi_lattice(delta_length=100)
c.plot()