Routing to IO#

Routing electrical#

For routing low speed DC electrical ports you can use sharp corners instead of smooth bends.

You can also define port.orientation = None to ignore the port orientation for low speed DC ports.

For single route between ports you can use route_single_electrical.

route_single_electrical#

route_single_electrical has bend = wire_corner with a 90deg bend corner.

import gdsfactory as gf
from gdsfactory.samples.big_device import big_device

gf.config.rich_output()

c = gf.Component()
pt = c << gf.components.pad_array(port_orientation=270, columns=3)
pb = c << gf.components.pad_array(port_orientation=90, columns=3)
pt.move((70, 200))
c.plot()

../_images/bc4d0e7f398d7140f296bb45bb1301f4e95c485b41b6f2cd090d8177027ed1bd.png
c = gf.Component()
pt = c << gf.components.pad_array(port_orientation=270, columns=3)
pb = c << gf.components.pad_array(port_orientation=90, columns=3)
pt.move((70, 200))
route = gf.routing.route_single_electrical(
    c,
    pt.ports["e11"],
    pb.ports["e11"],
    start_straight_length=20,
    cross_section="metal_routing",
)
c.plot()

../_images/ec7a80979fbf05745f160170e694a55d7bcc6ed756ddc1d8ef9cac4032985d8d.png

There is also bend = wire_corner45 for 45deg bend corner with parametrizable “radius”:

c = gf.Component()
pt = c << gf.components.pad_array(port_orientation=270, columns=1, centered_ports=False)
pb = c << gf.components.pad_array(port_orientation=90, columns=1, centered_ports=False)
pt.move((300, 300))
route = gf.routing.route_single(
    c,
    pt.ports["e11"],
    pb.ports["e11"],
    bend="wire_corner45", # This specifies that the bends should be 45 degrees instead of the standard 90 degree bends.
    port_type="electrical", # This informs the router that it is connecting electrical ports, which can influence how the connection is made.

    # This tells the router to use a pre-defined cross-section named "metal_routing,"
    # which specifies the physical properties of the trace, such as its width and GDSII layer.
    cross_section="metal_routing",
    allow_width_mismatch=True,
)
c.plot()
/home/runner/work/gdsfactory/gdsfactory/gdsfactory/components/waveguides/wire.py:133: UserWarning: {'width': 10.0} ignored for cross_section 'metal_routing'
  x = gf.get_cross_section(cross_section, width=width)

../_images/c531869aca66970000fc3b2298a18efb72ebb8fc2f2a25e32f91be5db1443a66.png
c = gf.Component()
pt = c << gf.components.pad_array(port_orientation=270, columns=1, centered_ports=False)
pb = c << gf.components.pad_array(port_orientation=90, columns=1, centered_ports=False)
pt.move((400, 400))
route = gf.routing.route_single(
    c,
    pt.ports["e11"],
    pb.ports["e11"],
    bend="wire_corner45", 
    radius=100,
    cross_section="metal_routing",
    port_type="electrical",
    allow_width_mismatch=True,
)
c.plot()

../_images/347d5edd64431530dda04e301a1e53b982ed35f5624d7debe6b8a8194d939958.png

route_quad#

c = gf.Component()
pt = c << gf.components.pad_array(port_orientation=270, columns=3, centered_ports=False)
pb = c << gf.components.pad_array(port_orientation=90, columns=3, centered_ports=False)
pt.move((100, 200))

# The route_quad function creates a U-shaped electrical trace.
# It connects port e11 of the top pad array to port e11 of the bottom pad array on the specified metal layer (49, 0).
gf.routing.route_quad(c, pt.ports["e11"], pb.ports["e11"], layer=(49, 0))
c.plot()

../_images/10a7f46f98de2ab8aaa76084c90e991c70ca2c188375dc1afc356c3340ae1c65.png

route_single#

c = gf.Component()
pt = c << gf.components.pad_array(port_orientation=270, columns=3, centered_ports=True)
pb = c << gf.components.pad_array(port_orientation=90, columns=3, centered_ports=True)
pt.move((100, 200))
route = gf.routing.route_single( # The route_single function is an auto-router that creates a single waveguide or electrical trace.
    c,
    pb.ports["e11"],
    pt.ports["e11"],
    steps=[
        {"y": 200},
    ],
    cross_section="metal_routing",
    bend=gf.components.wire_corner,
    port_type="electrical",
    allow_width_mismatch=True,
    auto_taper=False,
)
c.plot()
/home/runner/work/gdsfactory/gdsfactory/gdsfactory/components/waveguides/wire.py:43: UserWarning: {'width': 10.0} ignored for cross_section 'metal_routing'
  x = gf.get_cross_section(cross_section, width=width)

../_images/837005545dc5282de0a69ec2e7854fafd1ef63e67abfd5053646910bff3ef73c.png

route_bundle_electrical#

For routing groups of ports you can use route_bundle, which returns a bundle of routes using a bundle router (also known as bus or river router).

c = gf.Component()
pt = c << gf.components.pad_array(port_orientation=270, columns=3, centered_ports=False)
pb = c << gf.components.pad_array(port_orientation=90, columns=3, centered_ports=False)
pt.move((100, 300))

routes = gf.routing.route_bundle_electrical(
    c,
    pb.ports,
    pt.ports,
    start_straight_length=30,
    separation=30,
    cross_section="metal_routing",
)
c.plot()

../_images/933cd5e8b9ab4fdec5b810b1ee1a580d99bd9dfd244325fecb33914e0097a02a.png

Routing to pads#

You can also route to electrical pads.

c = gf.components.straight_heater_metal(length=100.0)

# The fanout_length parameter controls the length of the transitional section between waveguides.
# A longer fanout results in more gradual, lower-loss bends.
cc = gf.routing.add_pads_bot(component=c, port_names=("l_e4", "r_e4"), fanout_length=80)
cc.plot()

../_images/94f10ee09843af8dd7eca3b461ff4c3a91e173c14ac2d4ce6a6ea74cd4d998a8.png
c = gf.components.straight_heater_metal(length=100.0)
cc = gf.routing.add_pads_bot(component=c, port_names=("l_e4", "r_e4"), fanout_length=80)
cc.plot()

../_images/94f10ee09843af8dd7eca3b461ff4c3a91e173c14ac2d4ce6a6ea74cd4d998a8.png
c = gf.components.straight_heater_metal(length=110)
cc = gf.routing.add_pads_top(component=c, port_names=("l_e4", "r_e4"), fanout_length=80)
cc.plot()

../_images/e8a988da506c7f076824259d84e7a1b8df4572c578f3d604c9e31315bfdce9eb.png
c = gf.c.nxn(
    xsize=600,
    ysize=200,
    north=0,
    south=3,
    wg_width=10,
    layer="M3",
    port_type="electrical",
)
cc = gf.routing.add_pads_top(component=c, fanout_length=100)
cc.plot()

../_images/c17d9135f83dddf1a76bbb36b07386d093f3c36d348927bb5523ab488da4f7b0.png
n = west = north = south = east = 10
spacing = 20
c = gf.components.nxn(
    xsize=n * spacing,
    ysize=n * spacing,
    west=west,
    east=east,
    north=north,
    south=south,
    port_type="electrical",
    wg_width=10,
    layer="M3",
)
c.plot()

../_images/2ec67a7ef100f822610f6e485eb318b5c543c90af02ff8bf6ee20009c99bf8bd.png
cc = gf.routing.add_pads_top(component=c, fanout_length=-280)
cc.plot()

../_images/2ffccd9929095cfa6bb451e7fdff3d1811f2035757e9cf9e63af5fe8e3eff2c6.png

Routing to optical terminations#

Route to Fiber Array#

You can route to a fiber array.

component = big_device(nports=10)
c = gf.routing.add_fiber_array(component=component, radius=10.0, fanout_length=60.0)
c.plot()

../_images/74e0051ba3224daa1c6d81f442182e20982043e19101c9901d5548f4502a9a38.png

You can also mix and match TE and TM grating couplers. Notice that the TM polarization grating coupler is bigger.

import gdsfactory as gf

c = gf.components.mzi_phase_shifter()
gcte = gf.components.grating_coupler_te

cc = gf.routing.add_fiber_array(
    component=c,
    grating_coupler=gf.components.grating_coupler_te,
    radius=20,
)
cc.plot()

../_images/00fd89352ef20d410985f9966aaf8b6bc2d01c2546a0834f9d671c8c67991000.png

Route to edge couplers#

You can also route edge couplers to a fiber array or to both sides of the chip.

For routing to both sides you can follow different strategies:

  1. Place the edge couplers and route your components to the edge couplers.

  2. Extend your component ports to each side.

  3. Anything you imagine …

from functools import partial

import gdsfactory as gf
import gdsfactory.components as pc
from gdsfactory.generic_tech import LAYER


@gf.cell
def sample_reticle(
    size=(1500, 2000),
    ec="edge_coupler_silicon",
    bend_s=partial(gf.c.bend_s, size=(100, 100)),
) -> gf.Component:
    """Returns MZI with edge couplers.

    Args:
        size: size of the reticle.
        ec: edge coupler component name.
        bend_s: bend_s component.
    """
    mzis = [pc.mzi(length_x=lengths) for lengths in [100, 200, 300]]
    copies = 3  # Number of copies of each component.
    components = mzis * copies

    xsizes = [component.xsize for component in components]
    xsize_max = max(xsizes)
    ec = gf.get_component(ec)
    taper = pc.taper(width2=0.5)
    components_ec = []

    # xsize_max: The width of the main component itself.
    # + 2 * taper.xsize: Adds the width of the two tapers attached to each side of the main component.
    # + 2 * ec.xsize: Adds the width of the two edge couplers (ec) attached to the ends of the tapers.
    # > size[0]: Compares this total calculated width to the available width of the reticle (size[0]).
    # raise ValueError(...): If the component is too wide, this command halts the script and prints an error message explaining the problem.
    if xsize_max + 2 * taper.xsize + 2 * ec.xsize > size[0]:
        raise ValueError(
            f"Component xsize_max={xsize_max} is larger than reticle size[0]={size[0]}"
        )

    if bend_s:
        bend_s = gf.get_component(bend_s)

    for component in components:
        if bend_s:
            component = gf.components.extend_ports(
                component, extension=bend_s, port1="o1", port2="o2"
            )
            extension_length = (
                size[0]
                - 2 * taper.xsize # Subtracts the width of the two tapers.
                - 2 * ec.xsize # Subtracts the width of the two edge couplers (ec).
                - component.xsize # Subtracts the width of the main, central component.
                - 2 * bend_s.xsize # Subtracts the width of the two S-bends (bend_s).
            ) / 2 # The remaining distance is divided by two, because there will be one straight extension on each side of the component.
        else:
            extension_length = ( # is the precise length the straight extensions must have to make the structure fit perfectly within the target width size[0].
                size[0] - 2 * taper.xsize - 2 * ec.xsize - component.xsize
            ) / 2

        component_extended = gf.components.extend_ports(
            component,
            extension=pc.straight(extension_length),
            port2="o2",
            port1="o1",
        )

        component_tapered = gf.components.extend_ports(
            component_extended, extension=taper, port2="o2", port1="o1"
        )
        component_ec = gf.components.extend_ports(
            component_tapered, extension=ec, port1="o1", port2="o2"
        )
        components_ec.append(component_ec)

    c = gf.Component()
    fp = c << pc.rectangle(size=size, layer=LAYER.FLOORPLAN)

    text_offset_y = 10
    text_offset_x = 100

    grid = c << gf.grid_with_text(
        components_ec,
        shape=(len(components), 1),
        text=partial(gf.c.text_rectangular, layer=LAYER.M3),
        text_offsets=(
            (-size[0] / 2 + text_offset_x, text_offset_y),
            (+size[0] / 2 - text_offset_x - 160, text_offset_y),
        ),
    )
    fp.x = grid.x
    return c


c = sample_reticle(bend_s=None)
c.plot()

../_images/f49777c53d24e44295fad1686fdaac13c8a98863d50a80171867fc894ace43a4.png

To avoid straight light you can also include an S-bend.

c = sample_reticle()
c.plot()

../_images/6bded41b0498ad7e22ed8ceda8bf6b447dd6a478c2041f6781168329000dc6c2.png