Sample circuit layout with ltoi300 PDK#

Here we provide an example of PIC layout with the ltoi300 PDK. We start by choosing a die floorplan compatible with a submission for an LXT MPW run, then place some edge couplers for I/O at the right locations on the chip frame. We create a circuit cell with an evanescently-coupled ring resonator and connect it with the input and output edge couplers. We also create travelling wave modulators and route them optically to I/O edge couplers and electrically to wirebond pads.

from functools import partial
from pathlib import Path

import gdsfactory as gf
import numpy as np
from gdsfactory.routing import route_bundle

import ltoi300  # noqa: F401  – activates the PDK
from ltoi300.tech import LAYER

Choose the chip format and display the outline#

@gf.cell
def chip_frame():
    c = gf.get_component("chip_frame", size=(10_000, 5000), center=(0, 0))
    return c

chip_layout = chip_frame()
chip_layout
../_images/0458c77a1ecf65684711dfa80d12180fa172c38e7e00f8baa0052337b8d6ea46.png

Get the O-band ring resonator and edge couplers#

ring_resonator_oband = gf.get_component("ring_resonator_single_mode_point_coupler_oband")
ring_resonator_oband
../_images/23757323dae8f7e341c44b1681d29a2a606a224b9cc1d21556d7ead73f6bdd38.png

Note that we place the edge couplers such that the input port is slightly sticking outside of the chip footprint on the CHIP_EXCLUSION_ZONE layer. This is the correct placement for die singulation.

x_in = chip_layout.dxmin + 100.0
in_loc = np.array([x_in, chip_layout.dymax])
out_loc = np.array(
    [x_in + 2.5 * ring_resonator_oband.settings["ring_radius"], chip_layout.dymin]
)

ec_in = gf.Component()
ec_oband = gf.get_component("edge_coupler_oband")
ec_ref = ec_in << ec_oband
ec_ref.drotate(-90.0)
ec_ref.dmove(
    ec_ref.ports["o1"].dcenter, in_loc + [0.0, 0.5 * ec_oband.settings["input_ext"]]
)
ec_in.add_ports(ec_ref.ports)

ec_out = gf.Component()
ec_ref = ec_out << ec_oband
ec_ref.drotate(90.0)
ec_ref.dmove(
    ec_ref.ports["o1"].dcenter, out_loc - [0.0, 0.5 * ec_oband.settings["input_ext"]]
)
ec_out.add_ports(ec_ref.ports)

ecs_oband = {
    "in": ec_in,
    "out": ec_out,
}

Get the C-band ring resonator and edge couplers#

ring_resonator_cband = gf.get_component("ring_resonator_single_mode_point_coupler_cband")
ring_resonator_cband
../_images/a186913a7029dd7d33efcf0eb1e0b489d3b3fb8b8054b4cb81e245d6c566177d.png
x_in = chip_layout.dxmin + 200.0  # 100 um pitch from O-band ring EC (dxmin + 100)
in_loc = np.array([x_in, chip_layout.dymax])
out_loc = np.array(
    [x_in + 2.5 * ring_resonator_oband.settings["ring_radius"], chip_layout.dymin]
)

ec_in = gf.Component()
ec_cband = gf.get_component("edge_coupler_cband")
ec_ref = ec_in << ec_cband
ec_ref.drotate(-90.0)
ec_ref.dmove(
    ec_ref.ports["o1"].dcenter, in_loc + [0.0, 0.5 * ec_cband.settings["input_ext"]]
)
ec_in.add_ports(ec_ref.ports)

ec_out = gf.Component()
ec_ref = ec_out << ec_cband
ec_ref.drotate(90.0)
ec_ref.dmove(
    ec_ref.ports["o1"].dcenter, out_loc - [0.0, 0.5 * ec_cband.settings["input_ext"]]
)
ec_out.add_ports(ec_ref.ports)

ecs_cband = {
    "in": ec_in,
    "out": ec_out,
}
routing_roc = 60.0


@gf.cell
def ring_pass_circuit(
    communication_band="oband",
) -> gf.Component:
    if communication_band == "oband":
        ecs = ecs_oband
        coupled_ring = ring_resonator_oband
    elif communication_band == "cband":
        ecs = ecs_cband
        coupled_ring = ring_resonator_cband
    else:
        raise ValueError("communication_band must be either 'oband' or 'cband'")

    c = gf.Component()
    ring_ref = c << coupled_ring
    ring_ref.dmovex(
        -ring_ref.ports["o1"].dcenter[0] + ecs["out"].ports["o1"].dcenter[0]
    )
    if communication_band == "cband":
        ring_ref.dmovey(550.0)  # shift C-band ring 550 um higher

    # Bend spec

    routing_bend = partial(
        gf.components.bend_euler,
        radius=routing_roc,
        with_arc_floorplan=True,
    )

    # Routing to I/O

    [c << ec for ec in ecs.values()]

    route_bundle(
        c,
        ports1=[ring_ref.ports["o2"]],
        ports2=[ecs["in"].ports["o2"]],
        start_straight_length=5.0,
        end_straight_length=5.0,
        cross_section="xs_rwg700" if communication_band == "oband" else "xs_rwg900",
        bend=routing_bend,
        radius=routing_roc,
        straight="straight_rwg700_oband"
        if communication_band == "oband"
        else "straight_rwg900_cband",
    )
    route_bundle(
        c,
        ports1=[ring_ref.ports["o1"]],
        ports2=[ecs["out"].ports["o2"]],
        start_straight_length=5.0,
        end_straight_length=5.0,
        cross_section="xs_rwg700" if communication_band == "oband" else "xs_rwg900",
        bend=routing_bend,
        radius=routing_roc,
        straight="straight_rwg700_oband"
        if communication_band == "oband"
        else "straight_rwg900_cband",
    )
    text_ref = c << gf.components.text(
        f"{communication_band}" + "_ring", layer=LAYER.LABELS, size=13
    )
    text_ref.dmove(
        (
            ring_ref.dxmin + coupled_ring.settings["ring_radius"],
            (ring_ref.dymin + ring_ref.dymax) / 2,
        )
    )

    c.flatten()
    c.add_port(name="o1", port=ecs["in"].ports["o1"])
    c.add_port(name="o2", port=ecs["out"].ports["o1"])

    return c

Assemble the O-band circuit#

circuit_oband = ring_pass_circuit(communication_band="oband")
circuit_oband
../_images/bbf5eb74be57e8a0351abcea5236ff0fb754b087234fcc69614ed6961b0d062a.png

Assemble the C-band circuit#

circuit_cband = ring_pass_circuit(communication_band="cband")
circuit_cband
../_images/d794c92a9b1b6963b99550e16853670b2021cd95d739ef606a74b72f93485897.png

Get the O-band terminated EO modulator and edge couplers#

modulator_oband = gf.get_component(
    "terminated_mzm_1x2mmi_oband", modulation_length=6800.0, length_imbalance=120.0
)
modulator_oband
../_images/bb5231b96accf8bbddc59bf870441262be0d157754abf9eacdffe724d030d723.png
x_in = chip_layout.dxmin + 800.0
x_out = chip_layout.dxmax - 100.0
in_loc = np.array([x_in, chip_layout.dymax])
out_loc = np.array([x_out, chip_layout.dymin])

ec_in = gf.Component()
ec_oband = gf.get_component("edge_coupler_oband")
ec_ref = ec_in << ec_oband
ec_ref.drotate(-90.0)
ec_ref.dmove(
    ec_ref.ports["o1"].dcenter, in_loc + [0.0, 0.5 * ec_oband.settings["input_ext"]]
)
ec_in.add_ports(ec_ref.ports)

ec_out = gf.Component()
ec_ref = ec_out << ec_oband
ec_ref.drotate(90.0)
ec_ref.dmove(
    ec_ref.ports["o1"].dcenter, out_loc - [0.0, 0.5 * ec_oband.settings["input_ext"]]
)
ec_out.add_ports(ec_ref.ports)

ecs_oband_modulator = {
    "in": ec_in,
    "out": ec_out,
}

Wirebonding pads#

We add some metal pads close to the top edge of the chip. We then guide some electrical routing traces in the top metal layer to connect with the thermo-optical shifters of the modulators. This will facilitate electrical packaging and driving the heaters from an external circuit board.

@gf.cell
def bondpad_array(
    pad_size: tuple = (170, 170),
    pitch: float = 300,
    N: int = 4,
    chip_frame: gf.Component = chip_layout,
    dx: float = 1400.0,
    dy: float = 4800.0,
):
    c = gf.Component()

    pad = gf.components.pad(
        size=pad_size,
        layer=LAYER.M2,
    )
    bp = c << gf.components.pad_array(
        pad=pad,
        columns=N,
        column_pitch=pitch,
        layer=LAYER.M2,
        auto_rename_ports=True,
        centered_ports=True,
        port_orientation=-90.0,
    )

    bp.dmove(
        origin=(bp.dxmin, bp.dymin),
        destination=(chip_frame.dxmin + dx, chip_frame.dymin + dy),
    )


    c.add_ports(bp.ports)

    return c


bp_array = bondpad_array()
bp_array
../_images/92de2132c0071ee6d4ab61c170d640d8ab4d4b8c151d703080a7071f4e39863c.png

Get the C-band terminated EO modulator and edge couplers#

modulator_cband = gf.get_component(
    "terminated_mzm_1x2mmi_cband", modulation_length=6800.0, length_imbalance=120.0
)
modulator_cband
/opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/kfactory/decorators.py:406: UserWarning: via_array: 'signal' conductor cannot fit via rows (num_rows=0); using single solid opening.
  cell = f(**params)  # type: ignore[call-arg]
../_images/554f98a9b46f4a61533ee5324552c7c222c257062706c2010eb9526eedc71b34.png
x_in = chip_layout.dxmin + 750.0
x_out = chip_layout.dxmax - 200.0
in_loc = np.array([x_in, chip_layout.dymax])
out_loc = np.array([x_out, chip_layout.dymin])

ec_in = gf.Component()
ec_cband = gf.get_component("edge_coupler_cband")
ec_ref = ec_in << ec_cband
ec_ref.drotate(-90.0)
ec_ref.dmove(
    ec_ref.ports["o1"].dcenter, in_loc + [0.0, 0.5 * ec_cband.settings["input_ext"]]
)
ec_in.add_ports(ec_ref.ports)

ec_out = gf.Component()
ec_ref = ec_out << ec_cband
ec_ref.drotate(90.0)
ec_ref.dmove(
    ec_ref.ports["o1"].dcenter, out_loc - [0.0, 0.5 * ec_cband.settings["input_ext"]]
)
ec_out.add_ports(ec_ref.ports)

ecs_cband_modulator = {
    "in": ec_in,
    "out": ec_out,
}

Assembly of the modulator#

routing_roc = 60.0

@gf.cell
def modulator_circuit(
    communication_band="oband",
    modulator_x_pos=-4000.0,
    modulator_y_pos=2000.0,
    bp_port_names: tuple = ("e3", "e4"),  # hashable port name strings
    waypoints_heater_routing: list = None,
) -> gf.Component:
    if communication_band == "oband":
        ecs = ecs_oband_modulator
        modulator = modulator_oband
    elif communication_band == "cband":
        ecs = ecs_cband_modulator
        modulator = modulator_cband
    else:
        raise ValueError("communication_band must be either 'oband' or 'cband'")

    c = gf.Component()
    mod_ref = c << modulator
    mod_ref.dmove(mod_ref.ports["o1"].dcenter, [modulator_x_pos, modulator_y_pos])
    # Bend spec

    routing_bend = partial(
        gf.components.bend_euler,
        radius=routing_roc,
        with_arc_floorplan=True,
    )

    # Routing to I/O

    [c << ec for ec in ecs.values()]

    route_bundle(
        c,
        ports1=[mod_ref.ports["o1"]],
        ports2=[ecs["in"].ports["o2"]],
        start_straight_length=5.0,
        end_straight_length=5.0,
        cross_section="xs_rwg700" if communication_band == "oband" else "xs_rwg900",
        bend=routing_bend,
        radius=routing_roc,
        straight="straight_rwg700_oband"
        if communication_band == "oband"
        else "straight_rwg900_cband",
    )

    route_bundle(
        c,
        ports1=[mod_ref.ports["o2"]],
        ports2=[ecs["out"].ports["o2"]],
        start_straight_length=5.0,
        end_straight_length=5.0,
        cross_section="xs_rwg700" if communication_band == "oband" else "xs_rwg900",
        bend=routing_bend,
        radius=routing_roc,
        straight="straight_rwg700_oband"
        if communication_band == "oband"
        else "straight_rwg900_cband",
    )

    # Routing to bondpad
    heater_routing_width = 40.0
    route_bundle(
        c,
        ports1=[mod_ref.ports["e3"], mod_ref.ports["e4"]],
        ports2=[bp_array.ports[bp_port_names[0]], bp_array.ports[bp_port_names[1]]],
        bend=gf.components.bend_circular,
        straight=gf.components.straight,
        auto_taper=False,
        allow_width_mismatch=True,
        layer=LAYER.M2,
        route_width=heater_routing_width,
        waypoints=waypoints_heater_routing,
        separation=10.0,
    )



    text_ref = c << gf.components.text(
        f"{communication_band}" + "_modulator", layer=LAYER.LABELS, size=13
    )
    text_ref.dmove(
        ((mod_ref.dxmin + mod_ref.dxmax) / 2, (mod_ref.dymin + mod_ref.dymax) / 2 + 100)
    )

    c.flatten()
    c.add_port(name="o1", port=ecs["in"].ports["o1"])
    c.add_port(name="o2", port=ecs["out"].ports["o1"])
    c.add_port(name="ht1", port=bp_array.ports[bp_port_names[0]])
    c.add_port(name="ht2", port=bp_array.ports[bp_port_names[1]])

    return c
modulator_circuit_oband = modulator_circuit(
    "oband", modulator_x_pos=-4000.0, modulator_y_pos=1650.0, bp_port_names=("e2", "e1")
)
modulator_circuit_oband
../_images/487816e9a817291d426777156d9d0c681997af9652e1502b9422ea4fc26971b9.png
modulator_circuit_cband = modulator_circuit(
    "cband", modulator_x_pos=-4100.0, modulator_y_pos=650.0, bp_port_names=("e4", "e3"), waypoints_heater_routing=[(-4100, 1400), (-4100, 2200), (-3600, 2200)]
)
modulator_circuit_cband
../_images/e936482e02ba012ed628abb4bfd359f7e3d227e35aac9e6a6084d2b7cc323412.png

Assemble on the die outline

@gf.cell
def die_assembled(
    chip_layout=chip_layout,
) -> gf.Component:
    c = gf.Component()
    c << chip_layout
    c << circuit_oband
    c << circuit_cband
    c << modulator_circuit_oband
    c << modulator_circuit_cband
    c << bp_array

    c.add_ports(circuit_oband.ports, prefix="oband_ring_")
    c.add_ports(circuit_cband.ports, prefix="cband_ring_")
    c.add_ports(modulator_circuit_oband.ports, prefix="oband_modulator_")
    c.add_ports(modulator_circuit_cband.ports, prefix="cband_modulator_")

    return c


die = die_assembled()
die.plot()
die.show()
_ = die.write_gds(gdsdir=Path.cwd())
../_images/97852e4464e0ecf076a1e0dd23b3cb300f23d71d8828f583b3610c791e9ab4a1.png

Recap the port positions for testing

die.pprint_ports()
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ name                 width  orientation  layer           center              port_type  ┃
┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ oband_ring_o1       │ 0.35  │ 90.0        │ LT_SLAB (3/10) │ (-4950.0, 2530.0)  │ optical    │
│ oband_ring_o2       │ 0.35  │ 270.0       │ LT_SLAB (3/10) │ (-4450.0, -2530.0) │ optical    │
│ cband_ring_o1       │ 0.5   │ 90.0        │ LT_SLAB (3/10) │ (-4850.0, 2530.0)  │ optical    │
│ cband_ring_o2       │ 0.5   │ 270.0       │ LT_SLAB (3/10) │ (-4350.0, -2530.0) │ optical    │
│ oband_modulator_o1  │ 0.35  │ 90.0        │ LT_SLAB (3/10) │ (-4250.0, 2530.0)  │ optical    │
│ oband_modulator_o2  │ 0.35  │ 270.0       │ LT_SLAB (3/10) │ (4950.0, -2530.0)  │ optical    │
│ oband_modulator_ht1 │ 170.0 │ 270.0       │ M2 (22/0)      │ (-2965.0, 2360.0)  │ electrical │
│ oband_modulator_ht2 │ 170.0 │ 270.0       │ M2 (22/0)      │ (-2665.0, 2360.0)  │ electrical │
│ cband_modulator_o1  │ 0.5   │ 90.0        │ LT_SLAB (3/10) │ (-4300.0, 2530.0)  │ optical    │
│ cband_modulator_o2  │ 0.5   │ 270.0       │ LT_SLAB (3/10) │ (4850.0, -2530.0)  │ optical    │
│ cband_modulator_ht1 │ 170.0 │ 270.0       │ M2 (22/0)      │ (-3565.0, 2360.0)  │ electrical │
│ cband_modulator_ht2 │ 170.0 │ 270.0       │ M2 (22/0)      │ (-3265.0, 2360.0)  │ electrical │
└─────────────────────┴───────┴─────────────┴────────────────┴────────────────────┴────────────┘

Clear the gdsfactory cache#

gf.clear_cache()