YAML Place and AutoRoute#

You have two options for working with gdsfactory:

  1. python flow: you define your layout using python functions (Parametric Cells), and connect them with routing functions.

  2. 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.

extension

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 🔭. watcher-button

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'
../_images/835d5c2b7aada22259e104a499eff0f8f822a266441287b84e2847f7df1103a8.png

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'
../_images/1319fef6d64e2f1596a68b86cc42b6262d137e4f1bd5de7aa5f76928d5008742.png

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()
../_images/8c96974acf52eb0c05776e6508f2e71ffbe3eb4a30d2557e6bdc9598551617b3.png

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()
../_images/a24857f38696acc47d7f54cf49a9e85e1f37e3df493646e71415d687bcc88ce5.png

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()
../_images/5d65e69fd03913ff6591a567dedc56d2608453c5e82aca004f4026175472dd21.png

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()
../_images/da395dfca9bddcb0e025d29c9225c6fe62dcb4d6d655edefca8194f287e987d0.png

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()
../_images/f9248ed3d9d6cdafc1cebcf2b8897f3a9a8e491fb95916314b2b5cf9c9dcfffa.png

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()
../_images/e1ab7b2c94f4c21863a4d179caf5fe9de74b5b1952147e7f25205142311f7f22.png

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()
../_images/e6e65d8f6ad209b8ca59d22bec6acc7d9ffc6df5d467e212ec42ae705847fde3.png
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()
../_images/0371c98a51773c5f4dbfc0354abd78623612bf8e3678ca8efd47d177c1e59515.png
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()
../_images/9f780cc1f693158b77055879bcbc4c4cd7fe6526d6976c8ae704e59d423cb81a.png

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()
../_images/f13c382f31f69be1da7feef57738415603b86a0df368d36605c0a06cebc047e8.png
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'
../_images/e2256327f3b3203b6fd1f28deb4ad1ae5135e7982cc2c555d0ac505c5487d070.png
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'
../_images/b2e1947acaf86a0c6e4209ee0d8407d073ca857b06c6d25ed90b3a144fbf22b4.png

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()
../_images/eb8e6593aeb94aba172082b305eec7da1932a89fd38bebe09199011785980fd3.png

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()
../_images/6b32d158cf620aaff21c84e0b559a4d211c50a72c861d9e0f2750db8ae4240c7.png

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.
../_images/ba40b20f8e746f028f1ef63b606aec6aeb6688005039cbcd53795e7b19d7b445.png
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.
../_images/1a746f10bcd856c90cd48175fa61b1201fc93d86b61ae109b1a69ab4a75c7784.png

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()
../_images/c735a000364e8d6a184dee5f7b31af3d62c6fd7dba82f1ca795efdf4b29f1e7d.png
c = mzi_lattice(delta_length=100)
c.plot()
../_images/2eebee9cab1f66123f30142549c40c403a62394f6661490676d177a384026aaf.png