This notebook shows how to wire arrayed instancesadd_ref(rows=N, columns=M, ...) placements that show up whenever you have grids of pads, probe arrays, or fan-outs. The routing APIs are the same ones covered in the single-routing and bundle-routing tutorials; the unique piece here is the port indexing that lets you address individual elements of an array.

gdsfactory.Component.add_ref(columns=N, rows=M, ...) places a component as a 2-D array. Ports on individual elements are addressed with instance.ports[port_name, col, row] (kfactory's array port indexing). Promoting those array ports onto the parent component lets the standard bundle routers consume them as flat lists.

We use the same two-row pad layout to demonstrate three bundle routing approaches in turn — A*, corners, and steps — so you can see how to feed array-indexed ports into each.

Imports

from functools import partial

import gdsfactory as gf
from gdsfactory.gpdk import PDK

import doroutes as dr

PDK.activate()

Two Rows of Pads

Two arrays of three pads, separated vertically by 500 µm. The top row exposes its south-facing ports as e1..e3; the bottom row exposes its north-facing ports as e4..e6. Promoting the array ports onto the parent component lets the bundle routers consume them as flat lists.

def pad_arrays() -> gf.Component:
    """Two rows of 3 pads, 200 µm pitch, 500 µm vertical separation."""
    c = gf.Component()
    pad = PDK.get_component("pad")

    top = c.add_ref(pad, name="top", columns=3, rows=1, column_pitch=200, row_pitch=0)
    top.dmove((0, 500))
    bot = c.add_ref(pad, name="bot", columns=3, rows=1, column_pitch=200, row_pitch=0)

    # Promote each array element's port onto the parent component using
    # gdsfactory's array-port indexing: ports[name, col, row].
    for i in range(3):
        c.add_port(f"e{i + 1}", port=top.ports["e4", i, 0])  # top row, south face
        c.add_port(f"e{i + 4}", port=bot.ports["e2", i, 0])  # bot row, north face

    return c

A* Bundle Route

We let add_bundle_astar find a path that avoids the pad layer. For how the algorithm picks routes (and how grid_unit and layers control the search), see A* Bundle Routing.

Here we route on the metal-routing cross-section using wire_corner for the bends — the natural choice for electrical traces, where bend losses aren't a concern and a sharp corner is the most compact turn possible.

wire_corner has a hard 90° body, and on a 200 µm pad pitch a corner landing right at the pad face would clip into the neighbouring pads. We push the comb turn out with fan_in={"type": "manhattan", "start_straight": 30.0} (30 µm of extra leading straight per wire, applied to both fan-ins) so the bends sit clear of the pad array.

c = pad_arrays()
ports1 = [p for p in c.ports if p.name in ["e1", "e2", "e3"]]
ports2 = [p for p in c.ports if p.name in ["e4", "e5", "e6"]]

dr.add_bundle_astar(
    component=c,
    ports1=ports1,
    ports2=ports2,
    straight=partial(PDK.get_component, "straight", cross_section="metal_routing"),
    bend=partial(PDK.get_component, "wire_corner", cross_section="metal_routing"),
    layers=[(49, 0)],  # pad metal layer — treated as an obstacle by A*
    grid_unit=2500,
    spacing=20,
    fan_in={"type": "manhattan", "start_straight": 30.0},
)

dr.util.show_cell(c)
API key for organization 'DoPlayDo' found.

Corners Bundle Route

Same array layout, now using [add_bundle_manual][doroutes.add_bundle_manual] to specify the exact path through four absolute corner points. For the full corners / steps API and when to choose each, see Bundle From Corners.

The four corners below produce a Z-shaped detour around the center of the layout: south leg from the top pads, east leg at y=350, south leg at x=400, west leg at y=150, then a final south leg into the bottom pads.

We pass fan_in={"type": "manhattan", "start_straight": 30.0} for the same reason as the A* route above — a 30 µm leading straight on each wire keeps the comb turn clear of the pad faces.

c = pad_arrays()
ports1 = [p for p in c.ports if p.name in ["e1", "e2", "e3"]]
ports2 = [p for p in c.ports if p.name in ["e4", "e5", "e6"]]

dr.add_bundle_manual(
    component=c,
    ports1=ports1,
    ports2=ports2,
    corners=[
        (200, 350),  # turn east at y=350
        (400, 350),  # turn south at x=400
        (400, 150),  # turn west at y=150
        (200, 150),  # turn south at x=200
    ],
    spacing=20,
    straight=partial(gf.c.straight, cross_section="metal_routing"),
    bend=partial(gf.c.wire_corner),
    fan_in={"type": "manhattan", "start_straight": 30.0},
)

dr.util.show_cell(c)

Steps Bundle Route

[add_bundle_manual][doroutes.add_bundle_manual] with steps= is the relative-coordinate sibling of [add_bundle_manual][doroutes.add_bundle_manual] with corners= — the shape is given as relative steps (dx/dy) anchored at the bundle exit (post-fan-in convergence point). Use this form when you want a recipe that doesn't depend on absolute coordinates — handy if you reuse the same routing pattern across different placements.

The first and last steps may be diagonal (both dx and dy); the router splits the diagonal into an axis-aligned L by going parallel to the port direction first. Below the first step is {dx: 200, dy: -40} — the diagonal gets split into a 40 µm south leg followed by a 200 µm east leg, putting the bundle on the y=350 rail without requiring an explicit south corner.

Same fan_in={"type": "manhattan", "start_straight": 30.0} is forwarded so the fan-in clears the pads on both ends.

c = pad_arrays()
ports1 = [p for p in c.ports if p.name in ["e1", "e2", "e3"]]
ports2 = [p for p in c.ports if p.name in ["e4", "e5", "e6"]]

dr.add_bundle_manual(
    component=c,
    ports1=ports1,
    ports2=ports2,
    steps=[
        # diagonal first step: 200 µm east + 40 µm south of the bundle exit
        # (the diagonal is split into south-then-east by the router)
        {"dx": 200, "dy": -40},
        {"dy": -200},  # south leg at x=400
        {"dx": -200},  # west leg at y=150 back to x=200
    ],
    spacing=20,
    straight=partial(gf.c.straight, cross_section="metal_routing"),
    bend=partial(gf.c.wire_corner, cross_section="metal_routing"),
    fan_in={"type": "manhattan", "start_straight": 30.0},
)

dr.util.show_cell(c)

Next steps