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
versusfiber array
. You can useadd_fiber_array
for easier testing and higher throughput, oradd_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 import add_label_ehva, 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, 100]]
m = gf.pack(sweep)
c = m[0]
c.plot()
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_inner_io_fiber_array(length=20e3)
c.info["measurement"] = "optical_loopback2"
c = gf.labels.add_label_json(c)
c.plot()
c.info
Info(length=20000.009, polarization='te', wavelength=1.53, measurement='optical_loopback2')
spiral = gf.components.spiral_inner_io_fiber_single()
spiral.plot()
spiral_te = gf.routing.add_fiber_single(
gf.functions.rotate(gf.components.spiral_inner_io_fiber_single, 90)
)
spiral_te.plot()
2024-10-31 18:39:38.470 | WARNING | gdsfactory.component:_write_library:1934 - UserWarning: Component rotate_add_fiber_single_6f8349ab has invalid transformations. Try component.flatten_offgrid_references() first.
# which is equivalent to
spiral_te = gf.compose(
gf.routing.add_fiber_single,
gf.functions.rotate90,
gf.components.spiral_inner_io_fiber_single,
)
c = spiral_te(length=10e3)
c.plot()
2024-10-31 18:39:38.884 | WARNING | gdsfactory.component:_write_library:1934 - UserWarning: Component rotate_add_fiber_single_6774eb77 has invalid transformations. Try component.flatten_offgrid_references() first.
add_fiber_single_no_labels = partial(
gf.routing.add_fiber_single,
get_input_label_text_function=None,
)
spiral_te = gf.compose(
add_fiber_single_no_labels,
gf.functions.rotate90,
gf.components.spiral_inner_io_fiber_single,
)
sweep = [spiral_te(length=length) for length in [10e3, 20e3, 30e3]]
m = gf.pack(sweep)
c = m[0]
c.plot()
2024-10-31 18:39:39.219 | WARNING | gdsfactory.component:_write_library:1934 - UserWarning: Component pack_0$1 has invalid transformations. Try component.flatten_offgrid_references() first.
from toolz import compose
from functools import partial
import gdsfactory as gf
c = gf.components.spiral_inner_io_fiber_array(length=20e3)
c.info["measurement"] = "optical_loopback2"
c = gf.labels.add_label_json(c)
c.show()
c.plot()
2024-10-31 18:39:39.371 | WARNING | gdsfactory.klive:show:49 - UserWarning: Could not connect to klive server. Is klayout open and klive plugin installed?
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_inner_io_fiber_array(length=length))
for length in [20e3, 30e3, 40e3]
]
m = gf.pack(sweep)
c = m[0]
c.show()
c.plot()
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()
Grid#
You can also pack components with a constant spacing.
g = gf.grid_with_component_name(sweep)
g.plot()
gh = gf.grid_with_component_name(sweep, shape=(1, len(sweep)))
gh.plot()
gh_ymin = gf.grid_with_component_name(sweep, shape=(len(sweep), 1), align_x="xmin")
gh_ymin.plot()
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()
You have 2 ways of defining a mask:
in python
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()
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_single(grating_coupler=grating)
for grating in gratings_sweep
]
gratings = grid(
gratings_loss_sweep, shape=(1, len(gratings_loss_sweep)), spacing=(40, 0)
)
gratings.plot()
sweep_resistance = [
gf.components.resistance_sheet(width=width) for width in [1, 10, 100]
]
resistance = gf.pack(sweep_resistance)[0]
resistance.plot()
spiral_te = gf.compose(
gf.routing.add_fiber_single,
gf.functions.rotate90,
gf.components.spiral_inner_io_fiber_single,
)
sweep_spirals = [spiral_te(length=length) for length in [10e3, 20e3, 30e3]]
spirals = gf.pack(sweep_spirals)[0]
spirals.plot()
2024-10-31 18:39:41.459 | WARNING | gdsfactory.component:_write_library:1934 - UserWarning: Component pack_0$5 has invalid transformations. Try component.flatten_offgrid_references() first.
mask = gf.pack([spirals, resistance, gratings])[0]
mask.plot()
2024-10-31 18:39:41.623 | WARNING | gdsfactory.component:_write_library:1934 - UserWarning: Component pack_0$6 has invalid transformations. Try component.flatten_offgrid_references() first.
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.bbox)
return c
c = mask()
c.plot()
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:
pack_doe
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()
2024-10-31 18:39:41.986 | WARNING | gdsfactory.read.from_yaml:from_yaml:692 - UserWarning: prefix is deprecated and will be removed soon. _from_yaml
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()
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.
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)
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.components.spiral_inner_io_fiber_array(
length=length,
)
for length in [20e3, 40e3, 60e3]
]
mzis_te = [
gf.components.add_fiber_array_optical_south_electrical_north(
mzi,
electrical_port_names=["top_l_e2", "top_r_e2"],
)
for mzi in mzis
]
rings_te = [
gf.components.add_fiber_array_optical_south_electrical_north(
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.plot()
c.pprint_ports()
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━┓ ┃ name ┃ width ┃ center ┃ orientation ┃ layer ┃ port_type ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━┩ │ mzi_add_fiber_array_optical_south_electric… │ 80.0 │ [285.635, 1260.05] │ 270.0 │ [49, 0] │ electrical │ │ mzi_add_fiber_array_optical_south_electric… │ 80.0 │ [385.635, 1260.05] │ 270.0 │ [49, 0] │ electrical │ │ mzi_add_fiber_array_optical_south_electric… │ 0.5 │ [18.135, 1055.495] │ 90.0 │ [1, 0] │ optical │ │ mzi_add_fiber_array_optical_south_electric… │ 0.5 │ [653.135, 1055.495] │ 90.0 │ [1, 0] │ optical │ │ mzi_add_fiber_array_optical_south_electric… │ 80.0 │ [285.635, 920.05] │ 270.0 │ [49, 0] │ electrical │ │ mzi_add_fiber_array_optical_south_electric… │ 80.0 │ [385.635, 920.05] │ 270.0 │ [49, 0] │ electrical │ │ mzi_add_fiber_array_optical_south_electric… │ 0.5 │ [18.135, 715.495] │ 90.0 │ [1, 0] │ optical │ │ mzi_add_fiber_array_optical_south_electric… │ 0.5 │ [653.135, 715.495] │ 90.0 │ [1, 0] │ optical │ │ mzi_add_fiber_array_optical_south_electric… │ 80.0 │ [285.635, 580.05] │ 270.0 │ [49, 0] │ electrical │ │ mzi_add_fiber_array_optical_south_electric… │ 80.0 │ [385.635, 580.05] │ 270.0 │ [49, 0] │ electrical │ │ mzi_add_fiber_array_optical_south_electric… │ 0.5 │ [18.135, 375.495] │ 90.0 │ [1, 0] │ optical │ │ mzi_add_fiber_array_optical_south_electric… │ 0.5 │ [653.135, 375.495] │ 90.0 │ [1, 0] │ optical │ │ ring_single_heater_add_fiber_array_optical… │ 80.0 │ [829.805, 1260.05] │ 270.0 │ [49, 0] │ electrical │ │ ring_single_heater_add_fiber_array_optical… │ 80.0 │ [929.805, 1260.05] │ 270.0 │ [49, 0] │ electrical │ │ ring_single_heater_add_fiber_array_optical… │ 0.5 │ [689.305, 1055.494] │ 90.0 │ [1, 0] │ optical │ │ ring_single_heater_add_fiber_array_optical… │ 0.5 │ [1070.305, 1055.494] │ 90.0 │ [1, 0] │ optical │ │ ring_single_heater_add_fiber_array_optical… │ 80.0 │ [829.805, 580.05] │ 270.0 │ [49, 0] │ electrical │ │ ring_single_heater_add_fiber_array_optical… │ 80.0 │ [929.805, 580.05] │ 270.0 │ [49, 0] │ electrical │ │ ring_single_heater_add_fiber_array_optical… │ 0.5 │ [689.305, 375.494] │ 90.0 │ [1, 0] │ optical │ │ ring_single_heater_add_fiber_array_optical… │ 0.5 │ [1070.305, 375.494] │ 90.0 │ [1, 0] │ optical │ │ ring_single_heater_add_fiber_array_optical… │ 80.0 │ [829.805, 920.05] │ 270.0 │ [49, 0] │ electrical │ │ ring_single_heater_add_fiber_array_optical… │ 80.0 │ [929.805, 920.05] │ 270.0 │ [49, 0] │ electrical │ │ ring_single_heater_add_fiber_array_optical… │ 0.5 │ [689.305, 715.494] │ 90.0 │ [1, 0] │ optical │ │ ring_single_heater_add_fiber_array_optical… │ 0.5 │ [1070.305, 715.494] │ 90.0 │ [1, 0] │ optical │ │ spiral_inner_io_fiber_array-loopback1 │ 0.5 │ [360.824, 1455.3] │ 90.0 │ [1, 0] │ optical │ │ spiral_inner_io_fiber_array-loopback2 │ 0.5 │ [741.824, 1455.3] │ 90.0 │ [1, 0] │ optical │ │ spiral_inner_io_fiber_array-o1 │ 0.5 │ [487.824, 1459.3] │ 90.0 │ [1, 0] │ optical │ │ spiral_inner_io_fiber_array-o2 │ 0.5 │ [614.824, 1459.3] │ 90.0 │ [1, 0] │ optical │ │ spiral_inner_io_fiber_array_length40000p0-… │ 0.5 │ [1269.914, 272.8] │ 90.0 │ [1, 0] │ optical │ │ spiral_inner_io_fiber_array_length40000p0-… │ 0.5 │ [1650.914, 272.8] │ 90.0 │ [1, 0] │ optical │ │ spiral_inner_io_fiber_array_length40000p0-… │ 0.5 │ [1396.914, 276.8] │ 90.0 │ [1, 0] │ optical │ │ spiral_inner_io_fiber_array_length40000p0-… │ 0.5 │ [1523.914, 276.8] │ 90.0 │ [1, 0] │ optical │ │ spiral_inner_io_fiber_array_length60000p0-… │ 0.5 │ [2179.01, 110.3] │ 90.0 │ [1, 0] │ optical │ │ spiral_inner_io_fiber_array_length60000p0-… │ 0.5 │ [2560.01, 110.3] │ 90.0 │ [1, 0] │ optical │ │ spiral_inner_io_fiber_array_length60000p0-… │ 0.5 │ [2306.01, 114.3] │ 90.0 │ [1, 0] │ optical │ │ spiral_inner_io_fiber_array_length60000p0-… │ 0.5 │ [2433.01, 114.3] │ 90.0 │ [1, 0] │ optical │ └─────────────────────────────────────────────┴───────┴──────────────────────┴─────────────┴─────────┴────────────┘
df = gf.labels.get_test_manifest(c)
df
cell | measurement | measurement_settings | analysis | analysis_settings | doe | doe | wavelength | polarization | parent | analysis | measurement | length | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | mzi_add_fiber_array_optical_south_electrical_n... | optical_loopback4_heater_sweep | None | mzi_heater | None | mzis_heaters | mzis_heaters | NaN | None | mzi_add_fiber_array_optical_south_electrical_n... | mzi_heater | optical_loopback4_heater_sweep | NaN |
1 | mzi_add_fiber_array_optical_south_electrical_n... | optical_loopback4_heater_sweep | None | mzi_heater | None | mzis_heaters | mzis_heaters | NaN | None | mzi_add_fiber_array_optical_south_electrical_n... | mzi_heater | optical_loopback4_heater_sweep | NaN |
2 | mzi_add_fiber_array_optical_south_electrical_n... | optical_loopback4_heater_sweep | None | mzi_heater | None | mzis_heaters | mzis_heaters | NaN | None | mzi_add_fiber_array_optical_south_electrical_n... | mzi_heater | optical_loopback4_heater_sweep | NaN |
3 | ring_single_heater_add_fiber_array_optical_sou... | optical_loopback2_heater_sweep | None | ring_heater | None | ring_heaters | ring_heaters | NaN | None | ring_single_heater_add_fiber_array_optical_sou... | ring_heater | optical_loopback2_heater_sweep | NaN |
4 | ring_single_heater_add_fiber_array_optical_sou... | optical_loopback2_heater_sweep | None | ring_heater | None | ring_heaters | ring_heaters | NaN | None | ring_single_heater_add_fiber_array_optical_sou... | ring_heater | optical_loopback2_heater_sweep | NaN |
5 | ring_single_heater_add_fiber_array_optical_sou... | optical_loopback2_heater_sweep | None | ring_heater | None | ring_heaters | ring_heaters | NaN | None | ring_single_heater_add_fiber_array_optical_sou... | ring_heater | optical_loopback2_heater_sweep | NaN |
6 | spiral_inner_io_fiber_array | optical_loopback4 | None | optical_loopback4_spirals | None | spirals_sc | spirals_sc | 1.53 | te | spiral_inner_io_fiber_array | optical_loopback4_spirals | optical_loopback4 | 20000.009 |
7 | spiral_inner_io_fiber_array_length40000p0 | optical_loopback4 | None | optical_loopback4_spirals | None | spirals_sc | spirals_sc | 1.53 | te | spiral_inner_io_fiber_array_length40000p0 | optical_loopback4_spirals | optical_loopback4 | 39999.989 |
8 | spiral_inner_io_fiber_array_length60000p0 | optical_loopback4 | None | optical_loopback4_spirals | None | spirals_sc | spirals_sc | 1.53 | te | spiral_inner_io_fiber_array_length60000p0 | optical_loopback4_spirals | optical_loopback4 | 60000.013 |
df.to_csv("test_manifest.csv")
def sample_reticle_grid() -> gf.Component:
"""Returns sample reticle with grid packer."""
mzis = [
gf.components.mzi2x2_2x2_phase_shifter(length_x=length)
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.components.spiral_inner_io_fiber_array(
length=length,
)
for length in [20e3, 40e3, 60e3]
]
mzis_te = [
gf.components.add_fiber_array_optical_south_electrical_north(
mzi,
electrical_port_names=["top_l_e2", "top_r_e2"],
)
for mzi in mzis
]
rings_te = [
gf.components.add_fiber_array_optical_south_electrical_north(
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()
2024-10-31 18:39:43.788 | WARNING | gdsfactory.component:_write_library:1934 - UserWarning: Component grid_e413d6ec has invalid transformations. Try component.flatten_offgrid_references() first.
df = gf.labels.get_test_manifest(c)
df
cell | measurement | measurement_settings | analysis | analysis_settings | doe | doe | wavelength | polarization | parent | analysis | measurement | length | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | mzi_add_fiber_array_optical_south_electrical_n... | optical_loopback4_heater_sweep | None | mzi_heater | None | mzis_heaters | mzis_heaters | NaN | None | mzi_add_fiber_array_optical_south_electrical_n... | mzi_heater | optical_loopback4_heater_sweep | NaN |
1 | mzi_add_fiber_array_optical_south_electrical_n... | optical_loopback4_heater_sweep | None | mzi_heater | None | mzis_heaters | mzis_heaters | NaN | None | mzi_add_fiber_array_optical_south_electrical_n... | mzi_heater | optical_loopback4_heater_sweep | NaN |
2 | mzi_add_fiber_array_optical_south_electrical_n... | optical_loopback4_heater_sweep | None | mzi_heater | None | mzis_heaters | mzis_heaters | NaN | None | mzi_add_fiber_array_optical_south_electrical_n... | mzi_heater | optical_loopback4_heater_sweep | NaN |
3 | ring_single_heater_add_fiber_array_optical_sou... | optical_loopback2_heater_sweep | None | ring_heater | None | ring_heaters | ring_heaters | NaN | None | ring_single_heater_add_fiber_array_optical_sou... | ring_heater | optical_loopback2_heater_sweep | NaN |
4 | ring_single_heater_add_fiber_array_optical_sou... | optical_loopback2_heater_sweep | None | ring_heater | None | ring_heaters | ring_heaters | NaN | None | ring_single_heater_add_fiber_array_optical_sou... | ring_heater | optical_loopback2_heater_sweep | NaN |
5 | ring_single_heater_add_fiber_array_optical_sou... | optical_loopback2_heater_sweep | None | ring_heater | None | ring_heaters | ring_heaters | NaN | None | ring_single_heater_add_fiber_array_optical_sou... | ring_heater | optical_loopback2_heater_sweep | NaN |
6 | spiral_inner_io_fiber_array | optical_loopback4 | None | optical_loopback4_spirals | None | spirals_sc | spirals_sc | 1.53 | te | spiral_inner_io_fiber_array | optical_loopback4_spirals | optical_loopback4 | 20000.009 |
7 | spiral_inner_io_fiber_array_length40000p0 | optical_loopback4 | None | optical_loopback4_spirals | None | spirals_sc | spirals_sc | 1.53 | te | spiral_inner_io_fiber_array_length40000p0 | optical_loopback4_spirals | optical_loopback4 | 39999.989 |
8 | spiral_inner_io_fiber_array_length60000p0 | optical_loopback4 | None | optical_loopback4_spirals | None | spirals_sc | spirals_sc | 1.53 | te | spiral_inner_io_fiber_array_length60000p0 | optical_loopback4_spirals | optical_loopback4 | 60000.013 |
df.to_csv("test_manifest.csv")
You can see a test manifest example here