Chip Layout

a61c4014e19745fcbd0b21cfc675a79d

This notebook assembles the 800G transmitter PIC layout by composing the reusable building blocks defined in Component_Library.ipynb into hierarchical sub-modules:

  • Single TX lane: edge coupler → power monitor tap + monitor PD → crossing/splitter network → dual‑arm modulator + trims → output.

  • 4‑channel CWDM MUX block: optical monitor + WDM combiner with terminations.

  • Top-level chip: four TX lanes placed on a fiber-array pitch, routed into the MUX, with bond pads + simple metal routing, and chip/dicing outlines.

Prerequisites

  • Component_Library.ipynb must run successfully (it defines the PCells used here: edge_coupler, pm, pd, crossing, splitter, ps, heater, termination, wdm, bp, plus viewer, pf, etc.).

Conventions used in this notebook

  • Units: all coordinates and lengths are in µm.

  • Placement: each block is built as a netlist and converted to a Component via component_from_netlist.

  • Routing: optical routes use either explicit netlist routes with optional waypoints, or parametric.route at top level.

  • Ports/terminals: this notebook preserves the port naming used by the PDK/library cells (e.g., P0, P1, … and terminals T0, T1).

We now translate the individual building blocks into hierarchical sub-modules (TX lane, MUX block, top-level chip).

Design intent

  • Modularity: keep each functional block self-contained so it can be reused (e.g., one TX lane replicated 4×).

  • Testability: provide a standalone MUX test block so WDM routing/channelization can be verified without the modulators.

  • Placement knobs: expose small offsets (e.g., mod_y_offset, dc_offset, arm_shift_x) to quickly resolve routing/fabrication spacing conflicts without rewriting the netlist.

How to read the layouts

Each block is created from a netlist:

  • instances: placed references to components (with origin and rotation).

  • routes: routed photonic connections between instance ports (optionally with waypoints to steer the path).

  • connections: direct port-to-port connections. It displace the instance and connect it directly to the desired port of another instance.

  • ports / terminals: external optical/electrical interfaces exported by the block.

Before building any layout blocks, we run the component library notebook to load:

  • the technology/PDK setup,

  • reusable PCells and circuit models (edge couplers, taps + monitor PD, crossings, splitters, phase shifters, WDM, terminations, bond pads),

  • and the viewer(...) helper used to visualize components.

[1]:
# Load building blocks defined earlier.
# Expected symbols used below include (names as defined in `Component_Library.ipynb`):
# - `edge_coupler`: fiber-to-chip coupler PCell
# - `pm`: monitor tap / directional coupler used for power monitoring
# - `pd`: monitor photodiode
# - `crossing`, `splitter`: routing primitives
# - `ps`: dual-arm EO phase-shifter (modulator core)
# - `heater`: thermo-optic phase shifter (used here for trimming)
# - `termination`: low-reflection optical terminator
# - `wdm`: 4-channel WDM combiner
# - `bp`: bond pad
# - `viewer`, `pf`: layout + routing utilities
%run Component_Library.ipynb
../_images/examples_Chip_Layout_3_0.png
LiveViewer started at http://localhost:33695
Starting…
12:35:07 EST Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=mo-9a0b98e6-3d2d-
             4a3c-9a11-46abe51eb56f'.
Progress: 100%
../_images/examples_Chip_Layout_3_4.png
Starting…
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=mo-b5d30532-c7d6-
             4c54-916f-a5d1605180c0'.
Progress: 100%
../_images/examples_Chip_Layout_3_8.png
Taper angle: 10.282162084410936°
Starting…
12:35:08 EST Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-02994704-bf3
             a-49b8-9bd8-a9bf530dcb5a'.
Progress: 100%
../_images/examples_Chip_Layout_3_12.png
Starting…
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-4416b789-daf
             8-40fd-9b3f-77b3f8112954'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=mo-7a93bc0e-8fcd-
             46ee-b0ba-07ef7dba72b5'.
Progress: 100%
../_images/examples_Chip_Layout_3_17.png
Starting…
12:35:09 EST Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-db0aa056-0c8
             f-4842-9ee1-8b65d4fa75b4'.
Progress: 100%
../_images/examples_Chip_Layout_3_21.png
Starting…
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-c2e00209-8b3
             7-4d7e-80a9-2223bdba6f1d'.
Progress: 100%
../_images/examples_Chip_Layout_3_25.png
Starting…
12:35:10 EST Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-9087ee5f-1fb
             3-42aa-a69b-3e79deac6ca4'.
Progress: 100%
../_images/examples_Chip_Layout_3_29.png
Starting…
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-ab14119d-fd5
             f-415c-8574-fd8aecfc2722'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-60951774-616
             1-4f25-9994-23520ff39b80'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-7bd3df99-c65
             a-49fe-8356-ecf12b1bb614'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-f2d41e3f-aa7
             1-4d12-9f22-46c7372ee306'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=mo-e72c70aa-7cbd-
             4194-a6f9-71b8f876aede'.
12:35:11 EST Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=mo-95040211-0605-
             4563-a4c6-63b4aabd46b0'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=mo-cb502fbb-03f6-
             4365-96e0-1e0c6610618e'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=mo-5c2bee0e-39dc-
             402c-abae-7b8e1e7bbcd2'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-724dfe81-fb8
             6-491a-b04f-295fcb640020'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=mo-a0e415e1-7c0e-
             4c08-977e-22b60d2a10f6'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=mo-ab75d1e1-e7ef-
             4601-a600-18afc9c1167c'.
Progress: 100%
../_images/examples_Chip_Layout_3_43.png
Est. FSR: 11.6 nm
Required added_length: 79.7 nm
Starting…
12:35:13 EST Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-2aeb7f07-a43
             b-438a-ba1a-58b5ece95fd1'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-5d1ab163-1e6
             9-4e80-a242-042027302a20'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-48257fbb-288
             e-47bb-a07c-d2e7ca52deb7'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-54b6afdd-1d2
             8-45d5-923c-af0d49f0014d'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-a3e95bd2-28f
             5-4eec-aa83-43a82d560199'.
12:35:14 EST Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-83247d5d-5d3
             2-4c6f-b824-84ab026a7ad1'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-5f96ed3d-2df
             0-4f29-b376-6b746f9f5d9c'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-3fec96ba-8d4
             3-4b73-a27d-815534859fdc'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-dad62c4e-997
             3-46ec-8187-874b5ad0af83'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-1feb452f-47f
             a-4251-8a4f-1dc55e6a5aa3'.
12:35:15 EST Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-5c951698-621
             c-4849-8faa-d30cdafd819d'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-b20ac84a-35c
             b-4ce9-b50e-02d931a4ed0b'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-aeb4fcf6-e4e
             c-46df-8520-0f13b3d9c34f'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-72b733e3-112
             f-49fb-a99c-0b7a6d438783'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-ecfb07a8-b79
             1-4727-8296-95481003fb95'.
Progress: 100%
../_images/examples_Chip_Layout_3_61.png
Progress: 100%
Progress: 100%
../_images/examples_Chip_Layout_3_63.png
Starting…
12:35:16 EST Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-d7e74d68-dd4
             c-4665-81d3-9f67cf334b0c'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-9a3a6590-07a
             7-4972-9970-ed66fe588f6e'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-b753edcc-762
             b-4a36-976d-1e6fc3e2baa0'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=mo-74414175-7be9-
             4202-856f-e7e10e59a292'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=mo-07b35d0b-931c-
             4c79-97b1-68b2706456cf'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=mo-70c93c71-a487-
             436b-8822-fa672ed1537f'.
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=mo-811c642e-e56f-
             4753-8718-356e9038b13e'.
Progress: 100%
Progress: 100%
Progress: 100%
../_images/examples_Chip_Layout_3_73.png
Starting…
12:35:18 EST Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-c1fbeb99-0ec
             2-4906-be36-e61c641340d6'.
Progress: 100%
../_images/examples_Chip_Layout_3_77.png
Starting…
12:35:19 EST Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-f0e16397-71d
             4-4193-a89f-1e656420e01d'.
Progress: 100%
../_images/examples_Chip_Layout_3_81.png

Single TX lane

A single TX lane implements one ~200G optical transmitter path that can be replicated across a fiber array.

Functional blocks inside one lane

  • Input coupling: edge_coupler receives light from the fiber array.

  • Power monitoring: pm taps a small fraction of optical power to a pd for calibration/feedback.

  • Routing primitives: a crossing and splitter are used to route into the dual‑arm modulator.

  • Modulation + trimming:

    • ps is the dual‑arm EO phase shifter (the modulator core).

    • two heater instances provide thermo‑optic trim (one per arm) for bias/imbalance correction.

  • Back‑reflection control: unused/monitor ports are terminated using termination.

Interfaces

  • Optical ports: the block exports one input and one output (see netlist['ports']).

  • Electrical terminals: heater terminals are exported so they can be connected to bond pads/metal routing at top level.

The code below builds this lane as a netlist so it remains easy to replicate with controlled offsets.

[2]:
@pf.parametric_component(name_prefix="Single Modulator Block")
def single_modulator_block(
    mod_y_offset=0, dc_offset=0, arm_shift_x=0, edge_coupler_x=0, edge_coupler_y=0
):
    """Build one hierarchical TX lane (~200G).

    This function intentionally exposes a few *placement offsets* so the same lane can be
    replicated multiple times on a chip while avoiding optical route collisions.

    Parameters
    ----------
    mod_y_offset:
        Vertical shift applied to the modulator core + trimming heaters.
    dc_offset:
        Vertical shift applied to the monitor tap + monitor PD + associated routing.
    arm_shift_x:
        Small horizontal shift used to deconflict parallel waveguides/waypoints.
    edge_coupler_x, edge_coupler_y:
        Placement of the edge coupler for this lane.

    Returns
    -------
    pf.Component
        A reusable lane with exported optical ports and heater terminals.
    """

    # The positions below are “block floorplan” anchors used to keep routing predictable.
    ec_pos = (edge_coupler_x, edge_coupler_y)

    # Monitor tap + PD branch (shifted with `dc_offset` so it can follow the fiber pitch).
    dc_pos = (-500, -500 + dc_offset)
    pd_pos = (-500, -400 + dc_offset)
    waypoint_ec = (-300 + arm_shift_x, -450 + dc_offset)
    crossing_pos = (-675, -500 + dc_offset)

    # Modulator + trimming section (shifted with `mod_y_offset`).
    y_splt_pos_1 = (-4000, -550)
    ps_pos = (-1650, 0 + mod_y_offset)

    # Slightly staggered arm waypoints help maintain spacing between the two arms.
    way_point_arm1_1 = (y_splt_pos_1[0] - 700 - arm_shift_x, -200 + mod_y_offset)
    way_point_arm1_2 = (y_splt_pos_1[0] - 710 - arm_shift_x, -200 + mod_y_offset)

    # Thermo-optic phase shifters (one per arm) used for trimming/biasing.
    tops_pos_1 = (-2000, -350 + mod_y_offset)
    tops_pos_2 = (-2000, -450 + mod_y_offset)

    # Multi-waypoint routes steer around the heater block geometry.
    way_point_tops_arm1_1 = (tops_pos_1[0] + 400, tops_pos_1[1] + 300)
    way_point_tops_arm1_2 = (tops_pos_1[0] - 500, tops_pos_1[1] + 150)
    way_point_tops_arm2_1 = (tops_pos_1[0] + 375, tops_pos_1[1] + 300)
    way_point_tops_arm2_2 = (tops_pos_1[0] - 525, tops_pos_1[1] + 175)

    # Recombiner and termination.
    y_splt_pos_2 = (-1900, -400 + mod_y_offset)
    term_pos = (-450, -550 + dc_offset)

    # Netlist instance index map:
    # 0 edge coupler → 1 monitor tap → 3 crossing → 4 splitter → 5 EO phase shifter
    # 6/7 heaters (trim) → 8 splitter (recombine) → 9 terminator
    netlist = {
        "name": "Single_lambda_200G",
        "instances": [
            {"component": edge_coupler, "origin": ec_pos, "rotation": 180},  # 0
            {"component": pm, "origin": dc_pos, "rotation": 180},  # 1
            {"component": pd, "origin": pd_pos, "rotation": 0},  # 2
            {"component": crossing, "origin": crossing_pos, "rotation": 0},  # 3
            {"component": splitter, "origin": y_splt_pos_1, "rotation": 180},  # 4
            {"component": ps, "origin": ps_pos, "rotation": 180},  # 5
            {"component": heater, "origin": tops_pos_1, "rotation": 180},  # 6
            {"component": heater, "origin": tops_pos_2, "rotation": 180},  # 7
            {"component": splitter, "origin": y_splt_pos_2, "rotation": 180},  # 8
            {"component": termination, "origin": term_pos, "rotation": 90},  # 9
        ],
        "routes": [
            # Fiber input → monitor tap
            ((0, "P1"), (1, "P0"), {"waypoints": [waypoint_ec]}),
            # Tap/main-path routing into the crossing.
            ((1, "P2"), (3, "P1")),
            ((1, "P1"), (3, "P3")),
            # Monitor branch: crossing → photodiode.
            ((3, "P2"), (2, "P0")),
            # Main branch: crossing → splitter feeding the two modulator arms.
            ((3, "P0"), (4, "P0")),
            ((4, "P1"), (5, "P1"), {"waypoints": [way_point_arm1_1]}),
            ((4, "P2"), (5, "P3"), {"waypoints": [way_point_arm1_2]}),
            # Modulator outputs → trimming heaters (one per arm).
            (
                (5, "P2"),
                (6, "P1"),
                {"waypoints": [way_point_tops_arm1_1, way_point_tops_arm1_2]},
            ),
            (
                (5, "P0"),
                (7, "P1"),
                {"waypoints": [way_point_tops_arm2_1, way_point_tops_arm2_2]},
            ),
            # Heater outputs → recombiner splitter.
            ((6, "P0"), (8, "P1")),
            ((7, "P0"), (8, "P2")),
            # Terminate the unused tap port to suppress reflections.
            ((1, "P3"), (9, "P0")),
        ],
        # Export one optical input (edge coupler) and one optical output (recombiner).
        "ports": [(0, "P0"), (8, "P0")],
        # Export heater terminals so the top level can connect to bond pads / routing metal.
        "terminals": [(6, "T0"), (6, "T1"), (7, "T0"), (7, "T1")],
        "models": [pf.CircuitModel()],
    }

    return pf.component_from_netlist(netlist)


viewer(single_modulator_block())
/home/amin/venv/photonforge-release/lib/python3.12/site-packages/photonforge/parametric.py:2139: RuntimeWarning: Using bends with angles not multiples of 90° might lead to disconnected waveguides. Consider building a continuous path with grid-aligned ports instead of connecting sections with non grid-aligned ports.
  return _pf.extension._route(
[2]:
../_images/examples_Chip_Layout_5_1.svg

Multiplexer block

This block implements the 4‑channel CWDM combiner used to combine four modulated lanes onto a single output.

What’s inside

  • Input edge coupler (edge_coupler): lets the block be used standalone for probing/alignment.

  • Monitor tap + PD (pm + pd): samples optical power for calibration/bring‑up.

  • 4‑channel WDM (wdm): performs the wavelength combining.

  • Terminations (termination): absorb unused ports to reduce back‑reflections.

The block exports the edge‑coupler port plus four WDM ports (see netlist['ports']).

[3]:
@pf.parametric_component(name_prefix="CWDM combiner block")
def mux_block():
    """Build a 4-channel WDM combiner block.

    The intent is to have a standalone module that can be (a) connected to the four
    TX lanes at top level, and (b) probed on its own during bring-up using the
    edge coupler + monitor tap + PD.
    """

    # Floorplan anchors (µm)
    mux_pos = (-1100, -275)
    dc_pos = (-750, -250)
    pd_pos = (-500, -100)
    waypoint_ec = (-300, -200)

    # Instance index map:
    # 0 edge coupler → 1 monitor tap → 3 WDM; 2 is the monitor PD.
    # 4-9 are terminations tied off to unused ports.
    netlist = {
        "name": "4_channel_MUX",
        "instances": [
            {"component": edge_coupler, "origin": (0, 0), "rotation": 180},  # 0
            {"component": pm, "origin": dc_pos, "rotation": 0},  # 1
            {"component": pd, "origin": pd_pos, "rotation": 0},  # 2
            {"component": wdm, "origin": mux_pos, "rotation": 0},  # 3
            # 4 - 9: terminations (connected below)
            {"component": termination},
            {"component": termination},
            {"component": termination},
            {"component": termination},
            {"component": termination},
            {"component": termination},
        ],
        # Use routes when we want explicit waveguide routing control (e.g., a waypoint).
        "routes": [
            # Edge coupler → monitor tap (with waypoint to steer the route)
            ((0, "P1"), (1, "P1"), {"waypoints": [waypoint_ec]}),
            # Tap → monitor PD (power monitor branch)
            ((1, "P2"), (2, "P0")),
            # Tap/main path → WDM input
            ((1, "P0"), (3, "P9")),
        ],
        # Use direct connections for simple “tie-offs” where we just want ports connected.
        "connections": [
            # Terminate the unused tap port
            ((4, "P0"), (1, "P3")),
            # Terminate unused WDM ports to reduce reflections
            ((5, "P0"), (3, "P0")),
            ((6, "P0"), (3, "P2")),
            ((7, "P0"), (3, "P4")),
            ((8, "P0"), (3, "P6")),
            ((9, "P0"), (3, "P8")),
        ],
        # Export external ports:
        # - (0, P0) is the edge coupler external/fiber-facing port
        # - (3, P1/P3/P5/P7) are the four WDM outputs used to connect TX lanes
        "ports": [(0, "P0"), (3, "P1"), (3, "P3"), (3, "P5"), (3, "P7")],
        "terminals": [(3, t_name) for t_name in wdm.terminals.keys()],
        "models": [pf.CircuitModel()],
    }

    return pf.component_from_netlist(netlist)


viewer(mux_block())
[3]:
../_images/examples_Chip_Layout_7_0.svg

Reference MUX block

The reference MUX block is a self-test structure for validating the WDM routing.

  • It instantiates the same mux_block() used on the full chip.

  • Each WDM output is routed into a dedicated photodiode array.

This makes it easy to verify:

  • the port ordering of the WDM,

  • that each output can be reached and monitored electrically,

The pd_sep_x parameter lets you adjust the horizontal pitch of the PD array.

[4]:
@pf.parametric_component(name_prefix="Reference MUX block")
def mux_test_block(pd_sep_x=125):
    """Create a self-test structure for the 4-channel MUX.

    The MUX outputs are routed to a 4× photodiode array so each channel can be
    measured independently.

    Parameters
    ----------
    pd_sep_x:
        Horizontal spacing (µm) between adjacent photodiodes.
    """

    mux = mux_block()

    # Photodiode array anchor (µm)
    pd1_pos = (-1300, 50)

    # Fan-out waypoints:
    # We intentionally stagger the waypoint x/y coordinates so each route takes a
    # slightly different corridor, reducing overlaps and making the routing clearer.
    waypoint_ec_1 = (-1300, -100)
    waypoint_ec_2 = (-1300, -400)
    waypoint_ec_3 = (-1310, -100 + 10)
    waypoint_ec_4 = (-1310, -400 - 10)
    waypoint_ec_5 = (-1320, -100 + 20)
    waypoint_ec_6 = (-1320, -400 - 20)
    waypoint_ec_7 = (-1330, -100 + 30)
    waypoint_ec_8 = (-1330, -400 - 30)

    netlist = {
        "name": "4_channel_MUX_Test_Block",
        "instances": [
            {"component": mux, "origin": (0, 0), "rotation": 0},  # 0: MUX
            {"component": pd, "origin": pd1_pos, "rotation": 90},  # 1: PD
            {
                "component": pd,
                "origin": (pd1_pos[0] + pd_sep_x, pd1_pos[1]),
                "rotation": 90,
            },  # 2: PD
            {
                "component": pd,
                "origin": (pd1_pos[0] + 2 * pd_sep_x, pd1_pos[1]),
                "rotation": 90,
            },  # 3: PD
            {
                "component": pd,
                "origin": (pd1_pos[0] + 3 * pd_sep_x, pd1_pos[1]),
                "rotation": 90,
            },  # 4: PD
        ],
        "routes": [
            # Route each MUX output into a dedicated PD.
            ((0, "P1"), (4, "P0"), {"waypoints": [waypoint_ec_2, waypoint_ec_1]}),
            ((0, "P2"), (3, "P0"), {"waypoints": [waypoint_ec_4, waypoint_ec_3]}),
            ((0, "P3"), (2, "P0"), {"waypoints": [waypoint_ec_6, waypoint_ec_5]}),
            ((0, "P4"), (1, "P0"), {"waypoints": [waypoint_ec_8, waypoint_ec_7]}),
        ],
        # No external ports: this is intended as an on-chip test-only structure.
        "ports": [],
        "models": [pf.CircuitModel()],
    }

    return pf.component_from_netlist(netlist)


viewer(mux_test_block())
[4]:
../_images/examples_Chip_Layout_9_0.svg

Full Chip Layout

This section assembles the full chip top level.

What the top-level does

  • Replicates 4 TX lanes (single_modulator_block) and places them on a fiber-array pitch (default fiber_array_spacing=127 µm).

  • Places the CWDM MUX (mux_block) and routes each TX lane output into a specific WDM port.

  • Adds bond pads (bp) and simple metal routes for heater connections (routing is drawn as pf.Path geometries).

  • Draws chip outlines:

    • chip_area: the design boundary.

    • dicing_area: a perimeter ring used to reserve space for dicing/handling.

Notes on routing strategy

  • Optical routing at the top level uses pf.parametric.route(...) between referenced sub-block ports.

  • Electrical routing here is a Manhattan “bus” style route (pf.parametric.route_manhattan) that is replicated with small offsets to create multiple parallel traces.

  • We only need to actively drive the thermal phase shifter on one arm to fully control the relative phase difference required to bias the MZM. The identical, unconnected heater is kept on the second arm strictly to maintain physical and optical symmetry, ensuring both paths experience the exact same baseline insertion loss and parasitic effects.

If you want to change the floorplan, start by adjusting size, modulator_offset, and the origin= placements of the sub-block references.

[5]:
@pf.parametric_component(name_prefix="Main Chip")
def main_chip(
    size=(5500, 5500),
    dicing_size=200,
    fiber_array_spacing=127,
    modulator_offset=1000,
    waveguide_offset=10,
    trace_width=10,
    bp_spacing=150,
):
    """Assemble the full 800G TX PIC top level.

    High-level structure:
    - 4× `single_modulator_block` (fiber-pitch spaced)
    - 1× `mux_block` to combine channels
    - 1× `mux_test_block` for on-chip WDM validation
    - bond pad row + replicated electrical route templates
    - chip outline + dicing keepout
    """

    comp = pf.Component("800G_2xFR4")

    # ---------------------------------------------------------------------
    # 4 TX lane blocks
    # ---------------------------------------------------------------------
    mod_refs = []

    for i in reversed(range(4)):
        # Calculate linearly scaling offsets
        modulator = single_modulator_block(
            edge_coupler_x=500,
            edge_coupler_y=-100,
            mod_y_offset=i * modulator_offset,
            dc_offset=-100 - (i * fiber_array_spacing),
            arm_shift_x=i * 2 * waveguide_offset,
        )

        # Place the reference using the same index multiplier
        mod_ref = pf.Reference(
            modulator, origin=(size[0] - 500, size[1] / 3.0 - (i * fiber_array_spacing))
        )

        comp.add(mod_ref)
        mod_refs.append(mod_ref)

    # ---------------------------------------------------------------------
    # CWDM MUX + standalone MUX test structure
    # ---------------------------------------------------------------------
    mux = mux_block()
    mux_test = mux_test_block()
    mux_ref1 = pf.Reference(mux, origin=(size[0], size[1] / 2.0 + 1750))
    mux_ref2 = pf.Reference(mux_test, origin=(size[0], size[1] / 2.0 + 2500))
    comp.add(mux_ref1)
    comp.add(mux_ref2)

    # Connect each lane output (P1) into the intended WDM input port.
    for i, mod_ref in enumerate(mod_refs):
        # As the loop progresses (0, 1, 2, 3), the MUX port counts up (P1, P2, P3, P4)
        mux_port = f"P{i + 1}"

        comp.add(
            pf.parametric.route(
                port1=mod_ref.get_ports("P1")[0], port2=mux_ref1.get_ports(mux_port)[0]
            )
        )

    # ---------------------------------------------------------------------
    # Bond pads + replicated electrical route templates
    # ---------------------------------------------------------------------
    bp_ref_row1 = pf.Reference(
        bp, origin=(75, size[1] - 75), rows=1, columns=20, spacing=(bp_spacing, 0)
    )
    comp.add(bp_ref_row1)

    for i in range(8):
        # Fetch terminals (compacted logic)
        T0 = bp_ref_row1.get_terminals()["T0"][i]
        T1 = mod_refs[i // 2].get_terminals()["T1" if i % 2 == 0 else "T0"][0]

        # Calculate shifts based on loop index
        shift_1_x = i * bp_spacing
        wire_offset = i * 2 * trace_width
        shift_2_y = 830 * (i // 2)
        shift_2_x = 300 * (i // 2)

        # Pre-compute critical X and Y coordinate anchor points
        start_x = T0.center()[0]

        # Y-axis vertical levels (Top to Bottom)
        y_top = size[1] - 500 + wire_offset
        y_mid = size[1] - 1250 - wire_offset - shift_2_y
        y_bot = size[1] - 1400 - shift_2_y

        # X-axis horizontal boundaries (Right and Left)
        x_right = start_x + 3700 + wire_offset - shift_1_x
        x_left = start_x + 2800 - shift_2_x

        # The waypoints now clearly show a path going: Up -> Right -> Down -> Left -> Down
        h_ref = pf.parametric.route_manhattan(
            terminal1=T0,
            terminal2=T1,
            width=trace_width,
            waypoints=[
                (start_x, y_top),
                (x_right, y_top),
                (x_right, y_mid),
                (x_left, y_mid),
            ],
            direction1="y",
            direction2="y",
        )
        comp.add(h_ref)

    # Replicate MUX routes
    for i in range(8):
        # Fetch terminals (compacted logic)
        T0 = bp_ref_row1.get_terminals()["T0"][12 + i]
        T1 = mux_ref1.get_terminals()[f"T{i}"][0]

        # Calculate shifts based on loop index
        shift_1_x = i * bp_spacing
        wire_offset = i * 2 * trace_width

        # Pre-compute critical X and Y coordinate anchor points
        start_x = T0.center()[0]

        # Y-axis vertical levels (Top to Bottom)
        y_top = size[1] - 300 + wire_offset
        y_bot = y_top - 800

        # X-axis horizontal boundaries
        x_right_1 = start_x + 2100 + wire_offset - shift_1_x
        x_right_2 = x_right_1 + 380 + 20 * i

        waypoints = [(x_right_1, y_top), (x_right_1, y_bot), (x_right_2, y_bot)]

        if i % 2 == 0:
            waypoints.append((x_right_2, y_bot - 220 - 20 * i))

        # Waypoints tracing the path: Up/Right -> Down -> Right
        route_comp = pf.parametric.route_manhattan(
            terminal1=T0,
            terminal2=T1,
            width=trace_width,
            waypoints=waypoints,
            direction1="y",
            direction2="y",
        )
        comp.add(route_comp)

    # ---------------------------------------------------------------------
    # Chip boundary + dicing keepout
    # ---------------------------------------------------------------------
    chip_area = pf.Rectangle(corner1=(0, 0), corner2=(size[0], size[1]))
    dicing_area = pf.boolean(pf.offset(chip_area, dicing_size), chip_area, "-")

    comp.add("Chip design area", chip_area)
    comp.add("Dicing", *dicing_area)

    # Export top-level optical ports for IO/inspection.
    comp.add_port(mod_refs[3].get_ports("P0")[0])
    comp.add_port(mod_refs[2].get_ports("P0")[0])
    comp.add_port(mod_refs[1].get_ports("P0")[0])
    comp.add_port(mod_refs[0].get_ports("P0")[0])

    comp.add_port(mux_ref1.get_ports("P0")[0])

    return comp


pic = main_chip()
viewer(pic)
[5]:
../_images/examples_Chip_Layout_11_0.svg

Extending this layout

  • Change the chip size / margins: edit main_chip(size=..., dicing_size=...).

  • Change fiber-array pitch: edit fiber_array_spacing (default is 127 µm).

  • Spread lanes apart: increase modulator_offset or adjust the origin= placement of mod_ref*.

  • Resolve optical route congestion:

    • adjust arm_shift_x / waveguide_offset (small changes can remove parallel-waveguide conflicts),

    • or add/edit waypoints in the netlists.

  • Add/remove lanes: replicate single_modulator_block(...) calls and add corresponding connections into the WDM ports.