Die assembly#

With gdsfactory you can easily go from a simple Component, to a Component with many components inside.

In the same way that you need to Layout for DRC (Design Rule Check) clean devices, you have to layout obeying the Design for Test (DFT) and Design for Packaging rules.

Design for test#

To measure your chips after fabrication you need to decide your test configurations. This includes Design For Testing Rules like:

  • Individual input and output fibers versus fiber array. You can use add_fiber_array for easier testing and higher throughput, or add_fiber_single for the flexibility of single fibers.

  • Fiber array pitch (127um or 250um) if using a fiber array.

  • Pad pitch for DC and RF high speed probes (100, 125, 150, 200um). Probe configuration (GSG, GS …)

  • Test layout for DC, RF and optical fibers.

from functools import partial

import json
import gdsfactory as gf
from gdsfactory.generic_tech import get_generic_pdk
from gdsfactory.labels.ehva import add_label_ehva
from gdsfactory.labels.add_label_yaml import add_label_json

Pack#

Lets start with a resistance sweep, where you change the resistance width to measure sheet resistance.

sweep = [gf.components.resistance_sheet(width=width) for width in [1, 10, 50]]
m = gf.pack(sweep)
c = m[0]
c.plot()
../_images/fc3d37ddeed4bd448cf3359a706f5a2ea504d0ca4a46ae042411bfc3842a4b9e.png

Then we add spirals with different lengths to measure waveguide propagation loss. You can use both fiber array or single fiber.

from toolz import compose
from functools import partial
import gdsfactory as gf

gf.config.rich_output()

c = gf.components.spiral(length=100)
c.info["measurement"] = "optical_loopback2"
c = add_label_json(c)
c.plot()

../_images/57ec77d44e4f953a1d9a8e02875f72191e6b9b9864238e4a5e438609fe3eb2cd.png
c.info

Info(length=1200, measurement='optical_loopback2')
spiral = gf.components.spiral()
spiral.plot()

../_images/57ec77d44e4f953a1d9a8e02875f72191e6b9b9864238e4a5e438609fe3eb2cd.png
spiral_te = gf.routing.add_fiber_array(gf.components.spiral)
spiral_te.plot()

../_images/9d4166b8b8c820812f53e78459b66f1c3f91d695f1c0dd1f7cdaeff66184e203.png
# which is equivalent to
spiral_te = gf.compose(
    gf.routing.add_fiber_array,
    gf.components.spiral,
)
c = spiral_te(length=100)
c.plot()

../_images/9d4166b8b8c820812f53e78459b66f1c3f91d695f1c0dd1f7cdaeff66184e203.png
spiral_te = gf.compose(
    gf.routing.add_fiber_array,
    gf.components.spiral,
)
sweep = [spiral_te(length=length) for length in [100, 200, 300]]
m = gf.pack(sweep)
c = m[0]
c.plot()

../_images/b0b4cc829ff4284c10ec461830fb03d017bf8fd45db51b46c39e53ee013d458c.png
from toolz import compose
from functools import partial
import gdsfactory as gf

c = gf.components.spiral(length=200)
c.info["measurement"] = "optical_loopback2"
c = gf.labels.add_label_json(c)
c.show()
c.plot()

../_images/0b1b22378ac494cf565e2188d278fcc1e652f1abb13304a9b332e17e8b60a7f0.png
def add_label_json(component):
    """Add label json and component.info)"""
    component.info["measurement"] = "optical_loopback2"
    component = gf.labels.add_label_json(component)
    return component
sweep = [
    add_label_json(gf.components.spiral(length=length)) for length in [100, 200, 300]
]
m = gf.pack(sweep)
c = m[0]
c.show()
c.plot()

../_images/89cf3d73da5ba4e9afa936485568752d4dbe2fca8348f6e7dca32cfbb6b84848.png

You can also add some physical labels that will be fabricated. For example you can add prefix S at the north-center of each spiral using text_rectangular which is DRC clean and anchored on nc (north-center)

text_metal = partial(gf.components.text_rectangular_multi_layer, layers=("M1",))

m = gf.pack(sweep, text=text_metal, text_anchors=("cw",), text_prefix="s")
c = m[0]
c.show()
c.plot()

../_images/f4e1e8b60083efa5f85567f74b73162fa8fafb5c0470aaeff55efe2162462e80.png

Grid#

You can also pack components with a constant spacing.

g = gf.grid(sweep)
g.plot()

../_images/4d11f19c7640c88478e1c3049181992795c2dd674847ff46685247683573b49e.png
gh = gf.grid(sweep, shape=(1, len(sweep)))
gh.plot()

../_images/f612ebd2ba202ec2978849c947a2529903735ff16bfcf0ab5265690c9503940c.png
gh_ymin = gf.grid(sweep, shape=(len(sweep), 1), align_x="xmin")
gh_ymin.plot()

../_images/481f0437c61bb8a32148ec487076994c064fda24bb819999af75ad1ef3c78eb4.png

You can also add text labels to each element of the sweep

gh_ymin = gf.grid_with_text(
    sweep, shape=(len(sweep), 1), align_x="xmax", text=text_metal
)
gh_ymin.plot()

../_images/a4ff257d4e6005a1c9d74326aabe2f5293d9fbf0e9b6e03515d0faa72b95e514.png

You have 2 ways of defining a mask:

  1. in python

  2. in YAML

1. Component in python#

You can define a Component top cell reticle or die using grid and pack python functions.

text_metal3 = partial(gf.components.text_rectangular_multi_layer, layers=((49, 0),))
grid = partial(gf.grid_with_text, text=text_metal3)
pack = partial(gf.pack, text=text_metal3)

gratings_sweep = [
    gf.components.grating_coupler_elliptical(taper_angle=taper_angle)
    for taper_angle in [20, 30, 40]
]
gratings = grid(gratings_sweep, text=None)
gratings.plot()

../_images/952a26e92f24c58e56eb644503b28c0c7ce08fdebec1637274382cdaf325d438.png
gratings_sweep = [
    gf.components.grating_coupler_elliptical(taper_angle=taper_angle)
    for taper_angle in [20, 30, 40]
]
gratings_loss_sweep = [
    gf.components.grating_coupler_loss_fiber_array(grating_coupler=grating)
    for grating in gratings_sweep
]
gratings = grid(
    gratings_loss_sweep, shape=(1, len(gratings_loss_sweep)), spacing=(40, 0)
)
gratings.plot()

../_images/9b3cfcf06b7cee96c8d93ab7ba490cf252c2e3c7e64f8982191174ab6992e57b.png
sweep_resistance = [
    gf.components.resistance_sheet(width=width) for width in [1, 10, 50]
]
resistance = gf.pack(sweep_resistance)[0]
resistance.plot()

../_images/fc3d37ddeed4bd448cf3359a706f5a2ea504d0ca4a46ae042411bfc3842a4b9e.png
spiral_te = gf.compose(
    gf.routing.add_fiber_array,
    gf.components.spiral,
)
sweep_spirals = [spiral_te(length=length) for length in [100, 200, 300]]
spirals = gf.pack(sweep_spirals)[0]
spirals.plot()

../_images/8baadc5983fa202e0656435644222fb021fce22f736aefaffd5446d64bc3b5e0.png
mask = gf.pack([spirals, resistance, gratings])[0]
mask.plot()

../_images/c2e5b408860a6d2590cce0a2efd8aefa6daf3a4a27d8ee1e8acdb2ea09b24069.png

As you can see you can define your mask in a single line.

For more complex mask, you can also create a new cell to build up more complexity

@gf.cell
def mask():
    c = gf.Component()
    c << gf.pack([spirals, resistance, gratings])[0]
    c << gf.components.seal_ring(c)
    return c


c = mask()
c.plot()

../_images/eab654ae0e32b45a7e44ac3386dc78055cc4371e9fcb9b3f86495ac1d2bce411.png

2. Component in YAML#

You can also define your component in YAML format thanks to gdsfactory.read.from_yaml

You need to define:

  • instances

  • placements

  • routes (optional)

and you can leverage:

  1. pack_doe

  2. pack_doe_grid

2.1 pack_doe#

pack_doe places components as compact as possible.

c = gf.read.from_yaml(
    """
name: mask_grid

instances:
  rings:
    component: pack_doe
    settings:
      doe: ring_single
      settings:
        radius: [30, 50, 20, 40]
        length_x: [1, 2, 3]
      do_permutations: True
      function:
        function: add_fiber_array
        settings:
            fanout_length: 200

  mzis:
    component: pack_doe
    settings:
      doe: mzi
      settings:
        delta_length: [10, 100]
      function: add_fiber_array

placements:
  rings:
    xmin: 50

  mzis:
    xmin: rings,east
"""
)

c.plot()

../_images/1c7ecd8c0e2fd8fd8ce9f22c99bf2526cc956c1795a6c8f2b3d0e86a58c6d927.png

2.2 pack_doe_grid#

pack_doe_grid places each component on a regular grid

c = gf.read.from_yaml(
    """
name: mask_compact

instances:
  rings:
    component: pack_doe
    settings:
      doe: ring_single
      settings:
        radius: [30, 50, 20, 40]
        length_x: [1, 2, 3]
      do_permutations: True
      function:
        function: add_fiber_array
        settings:
            fanout_length: 200


  mzis:
    component: pack_doe_grid
    settings:
      doe: mzi
      settings:
        delta_length: [10, 100]
      do_permutations: True
      spacing: [10, 10]
      function: add_fiber_array

placements:
  rings:
    xmin: 50

  mzis:
    xmin: rings,east
"""
)
c.plot()

../_images/29142156ccb06c0250adbbea2d5a3d441eddcfce3ec6c25a6f4b0cd86bf73d55.png

Automated testing exposing all ports#

You can promote all the ports that need to be tested to the top level component and then write a CSV test manifest.

This is the recommended way for measuring components that have electrical and optical port.

from gdsfactory.generic_tech.cells import add_fiber_array_optical_south_electrical_north

test_info_spirals = dict(
    doe="spirals_sc",
    measurement="optical_loopback4",
    analysis="optical_loopback4_spirals",
)
test_info_mzi_heaters = dict(
    doe="mzis_heaters",
    analysis="mzi_heater",
    measurement="optical_loopback4_heater_sweep",
)
test_info_ring_heaters = dict(
    doe="ring_heaters",
    analysis="ring_heater",
    measurement="optical_loopback2_heater_sweep",
)


def sample_reticle() -> gf.Component:
    """Returns MZI with TE grating couplers."""

    mzis = [
        gf.components.mzi2x2_2x2_phase_shifter(length_x=length, auto_rename_ports=False)
        for length in [100, 200, 300]
    ]
    rings = [
        gf.components.ring_single_heater(length_x=length_x) for length_x in [10, 20, 30]
    ]

    spirals_te = [
        gf.routing.add_fiber_array(
            gf.components.spiral(
                length=length,
            )
        )
        for length in [0, 100, 200]
    ]
    mzis_te = [
        add_fiber_array_optical_south_electrical_north(
            component=mzi,
            electrical_port_names=("top_l_e2", "top_r_e2"),
        )
        for mzi in mzis
    ]
    rings_te = [
        add_fiber_array_optical_south_electrical_north(
            component=ring,
            electrical_port_names=("l_e2", "r_e2"),
        )
        for ring in rings
    ]

    for component in mzis_te:
        component.info.update(test_info_mzi_heaters)

    for component in rings_te:
        component.info.update(test_info_ring_heaters)

    for component in spirals_te:
        component.info.update(test_info_spirals)

    components = mzis_te + rings_te + spirals_te
    return gf.pack(components)[0]


c = sample_reticle()
c.show()
c.plot()

../_images/5bccc99284fa1f316ee9b284afef0140bb4e92c89f4fda518d4c96a6553bcca1.png
c.pprint_ports()
┏━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ name         width  orientation  layer  center                       port_type   ┃
┡━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│ 0_e4_1_1    │ 80.0  │ 270.0       │ M3    │ (285.52, 255.05)            │ electrical  │
│ 0_e4_1_2    │ 80.0  │ 270.0       │ M3    │ (385.52, 255.05)            │ electrical  │
│ 1_e4_1_1    │ 80.0  │ 270.0       │ M3    │ (285.52, 595.0500000000001) │ electrical  │
│ 1_e4_1_2    │ 80.0  │ 270.0       │ M3    │ (385.52, 595.0500000000001) │ electrical  │
│ 2_e4_1_1    │ 80.0  │ 270.0       │ M3    │ (285.52, 935.0500000000001) │ electrical  │
│ 2_e4_1_2    │ 80.0  │ 270.0       │ M3    │ (385.52, 935.0500000000001) │ electrical  │
│ 3_e4_1_1    │ 80.0  │ 270.0       │ M3    │ (158.52, 1275.05)           │ electrical  │
│ 3_e4_1_2    │ 80.0  │ 270.0       │ M3    │ (258.52, 1275.05)           │ electrical  │
│ 4_e4_1_1    │ 80.0  │ 270.0       │ M3    │ (575.46, 1275.05)           │ electrical  │
│ 4_e4_1_2    │ 80.0  │ 270.0       │ M3    │ (675.46, 1275.05)           │ electrical  │
│ 5_e4_1_1    │ 80.0  │ 270.0       │ M3    │ (829.46, 255.05)            │ electrical  │
│ 5_e4_1_2    │ 80.0  │ 270.0       │ M3    │ (929.46, 255.05)            │ electrical  │
│ 6_v1        │ 10.0  │ 270.0       │ WG    │ (880.239, 374.391)          │ vertical_te │
│ 6_v2        │ 10.0  │ 270.0       │ WG    │ (1007.239, 374.391)         │ vertical_te │
│ 6_loopback1 │ 10.0  │ 270.0       │ WG    │ (753.239, 374.112)          │ vertical_te │
│ 6_loopback2 │ 10.0  │ 270.0       │ WG    │ (1134.239, 374.112)         │ vertical_te │
│ 7_v1        │ 10.0  │ 270.0       │ WG    │ (815.96, 547.97)            │ vertical_te │
│ 7_v2        │ 10.0  │ 270.0       │ WG    │ (942.96, 547.97)            │ vertical_te │
│ 7_loopback1 │ 10.0  │ 270.0       │ WG    │ (688.96, 547.691)           │ vertical_te │
│ 7_loopback2 │ 10.0  │ 270.0       │ WG    │ (1069.96, 547.691)          │ vertical_te │
│ 8_v1        │ 10.0  │ 270.0       │ WG    │ (815.96, 720.801)           │ vertical_te │
│ 8_v2        │ 10.0  │ 270.0       │ WG    │ (942.96, 720.801)           │ vertical_te │
│ 8_loopback1 │ 10.0  │ 270.0       │ WG    │ (688.96, 721.272)           │ vertical_te │
│ 8_loopback2 │ 10.0  │ 270.0       │ WG    │ (1069.96, 721.272)          │ vertical_te │
└─────────────┴───────┴─────────────┴───────┴─────────────────────────────┴─────────────┘
df = gf.labels.get_test_manifest(c)
df

cell measurement measurement_settings analysis analysis_settings doe
0 0_e4_1_1 None None None None None
1 0_e4_1_2 None None None None None
2 1_e4_1_1 None None None None None
3 1_e4_1_2 None None None None None
4 2_e4_1_1 None None None None None
5 2_e4_1_2 None None None None None
6 3_e4_1_1 None None None None None
7 3_e4_1_2 None None None None None
8 4_e4_1_1 None None None None None
9 4_e4_1_2 None None None None None
10 5_e4_1_1 None None None None None
11 5_e4_1_2 None None None None None
12 6_v1 None None None None None
13 6_v2 None None None None None
14 6_loopback1 None None None None None
15 6_loopback2 None None None None None
16 7_v1 None None None None None
17 7_v2 None None None None None
18 7_loopback1 None None None None None
19 7_loopback2 None None None None None
20 8_v1 None None None None None
21 8_v2 None None None None None
22 8_loopback1 None None None None None
23 8_loopback2 None None None None None
df.to_csv("test_manifest.csv")
def sample_reticle_grid() -> gf.Component:
    """Returns sample reticle with grid packer."""
    from gdsfactory.generic_tech.cells import (
        add_fiber_array_optical_south_electrical_north,
    )

    mzis = [
        gf.components.mzi2x2_2x2_phase_shifter(length_x=length, auto_rename_ports=False)
        for length in [100, 200, 300]
    ]
    rings = [
        gf.components.ring_single_heater(length_x=length_x) for length_x in [10, 20, 30]
    ]

    spirals_te = [
        gf.routing.add_fiber_array(
            gf.components.spiral(
                length=length,
            )
        )
        for length in [0, 100, 200]
    ]
    mzis_te = [
        add_fiber_array_optical_south_electrical_north(
            component=mzi,
            electrical_port_names=["top_l_e2", "top_r_e2"],
        )
        for mzi in mzis
    ]
    rings_te = [
        add_fiber_array_optical_south_electrical_north(
            component=ring,
            electrical_port_names=["l_e2", "r_e2"],
        )
        for ring in rings
    ]

    for component in mzis_te:
        component.info.update(test_info_mzi_heaters)

    for component in rings_te:
        component.info.update(test_info_ring_heaters)

    for component in spirals_te:
        component.info.update(test_info_spirals)

    components = mzis_te + rings_te + spirals_te
    return gf.grid(components)


c = sample_reticle_grid()
c.plot()

../_images/7b5836135024a8cef14c88add28ee2b5647bd7558eba234d7ed7d92010f3908c.png
df = gf.labels.get_test_manifest(c)
df

cell measurement measurement_settings analysis analysis_settings doe
0 0_0_e4_1_1 None None None None None
1 0_0_e4_1_2 None None None None None
2 1_0_e4_1_1 None None None None None
3 1_0_e4_1_2 None None None None None
4 2_0_e4_1_1 None None None None None
5 2_0_e4_1_2 None None None None None
6 3_0_e4_1_1 None None None None None
7 3_0_e4_1_2 None None None None None
8 4_0_e4_1_1 None None None None None
9 4_0_e4_1_2 None None None None None
10 5_0_e4_1_1 None None None None None
11 5_0_e4_1_2 None None None None None
12 6_0_v1 None None None None None
13 6_0_v2 None None None None None
14 6_0_loopback1 None None None None None
15 6_0_loopback2 None None None None None
16 7_0_v1 None None None None None
17 7_0_v2 None None None None None
18 7_0_loopback1 None None None None None
19 7_0_loopback2 None None None None None
20 8_0_v1 None None None None None
21 8_0_v2 None None None None None
22 8_0_loopback1 None None None None None
23 8_0_loopback2 None None None None None
df.to_csv("test_manifest.csv")

You can see a test manifest example here