Chip Layout¶
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.ipynbmust run successfully (it defines the PCells used here:edge_coupler,pm,pd,crossing,splitter,ps,heater,termination,wdm,bp, plusviewer,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
routeswith optionalwaypoints, 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 terminalsT0,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
originandrotation).routes: routed photonic connections between instance ports (optionally with
waypointsto 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
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%
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%
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%
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%
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%
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%
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%
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%
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%
Progress: 100%
Progress: 100%
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%
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%
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%
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_couplerreceives light from the fiber array.Power monitoring:
pmtaps a small fraction of optical power to apdfor calibration/feedback.Routing primitives: a
crossingandsplitterare used to route into the dual‑arm modulator.Modulation + trimming:
psis the dual‑arm EO phase shifter (the modulator core).two
heaterinstances 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]:
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]:
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]:
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 (defaultfiber_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 aspf.Pathgeometries).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]:
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_offsetor adjust theorigin=placement ofmod_ref*.Resolve optical route congestion:
adjust
arm_shift_x/waveguide_offset(small changes can remove parallel-waveguide conflicts),or add/edit
waypointsin the netlists.
Add/remove lanes: replicate
single_modulator_block(...)calls and add corresponding connections into the WDM ports.