Circuit layout#

Here we provide an example of PIC layout with the lnoi400 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. Finally we create a circuit cell with an evanescently-coupled ring resonator and connect it with the input and output edge couplers.

from functools import partial
from pathlib import Path
import numpy as np
import lnoi400
import gdsfactory as gf
2024-09-26 18:27:42.694 | WARNING  | kfactory.conf:_validate_layout_cache:254 - 'cell_layout_cache' has been set to True. This might cause when as any cell names generated automatically are loaded from the layout instead of created. This could happen e.g. after reading a gds file into the layout.

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/eb99b54ae8fbdead94ebffafd61310f416cab12d287ee9748ddfa85919007cd9.png

Get the circuit building blocks#

input_ext = 10.0
double_taper = gf.get_component("double_linear_inverse_taper",
                                input_ext=input_ext,
                                )

coupler_gap = 0.6
ring_radius = 100.0
ring_width = 2.0
wg_width = 1.0

ring = gf.components.ring(
    layer="LN_RIDGE",
    radius=ring_radius,
    width=ring_width,
    angle_resolution=0.15,
)

dc_wg = gf.components.straight(
    length = ring_radius * 2,
    cross_section="xs_rwg1000",
)

@gf.cell
def ring_with_coupler(
    ring=ring,
    bus=dc_wg,
    gap=coupler_gap,
) -> gf.Component:

    c = gf.Component()
    ring_ref = c << ring
    coupler_ref = c << bus
    coupler_ref.drotate(90)
    coupler_ref.dcenter = [
        ring_ref.dxmax + gap + 0.5 * wg_width, 0.0
        ]
    c.add_ports(coupler_ref.ports)
    c.flatten()
    return c

coupled_ring = ring_with_coupler()
coupled_ring
../_images/d4e9808b79ecafcbbf10f528b590d27af94b4c984e6e6f3ff6c966df3ad171e6.png

Circuit assembly#

Positioning of the I/O couplers

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

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

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

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

Connecting the ring with I/O

routing_roc = 75.0

@gf.cell
def ring_pass_circuit(
    coupled_ring = coupled_ring,
    ecs = ecs,
) -> gf.Component:

    c = gf.Component()
    ring_ref = c << coupled_ring
    ring_ref.dmovex(- ring_ref.ports["o1"].dcenter[0] + ecs["out"].ports["o1"].dcenter[0])

    # 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()]

    gf.routing.route_single(
        c,
        ring_ref.ports["o2"],
        ecs["in"].ports["o2"],
        start_straight_length=5.0,
        end_straight_length=5.0,
        cross_section="xs_rwg1000",
        bend=routing_bend,
    )

    gf.routing.route_single(
        c,
        ring_ref.ports["o1"],
        ecs["out"].ports["o2"],
        start_straight_length=5.0,
        end_straight_length=5.0,
        cross_section="xs_rwg1000",
        bend=routing_bend,
    )

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

    return c

circuit = ring_pass_circuit()
circuit
../_images/157d53b6ad291222c88b3d3f746dc89648ce007b124852aca77719def48d94ef.png

Assemble on the die outline

@gf.cell
def die_assembled(
    chip_layout = chip_layout,
    circuit = circuit,
) -> gf.Component:
    c = gf.Component()
    c << chip_layout
    c << circuit
    c.add_ports(circuit.ports)
    return c

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

Recap the port positions for testing

die.pprint_ports()
┏━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ name  width  orientation  layer    center              port_type ┃
┡━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ o1   │ 0.25  │ 90.0        │ LN_SLAB │ (-4050.0, 2530.0)  │ optical   │
│ o2   │ 0.25  │ 270.0       │ LN_SLAB │ (-3800.0, -2530.0) │ optical   │
└──────┴───────┴─────────────┴─────────┴────────────────────┴───────────┘

Clear the gdsfactory cache#

gf.clear_cache()