Ring filter#

Calculations#

For a ring resonator we need to define:

Optical parameters:

  • coupling coefficient: will define resonance extinction ratio for a particular ring loss.

  • Free spectral range.

Electrical parameters:

  • VpiL

  • Resistance

import gdsfactory as gf
import numpy as np


def ring(
    wl: np.ndarray,
    wl0: float,
    neff: float,
    ng: float,
    ring_length: float,
    coupling: float,
    loss: float,
) -> np.ndarray:
    """Returns Frequency Domain Response of an all pass filter.

    Args:
        wl: wavelength in  um.
        wl0: center wavelength at which neff and ng are defined.
        neff: effective index.
        ng: group index.
        ring_length: in um.
        coupling: coupling coefficient.
        loss: dB/um.
    """
    transmission = 1 - coupling
    neff_wl = (
        neff + (wl0 - wl) * (ng - neff) / wl0
    )  # we expect a linear behavior with respect to wavelength
    out = np.sqrt(transmission) - 10 ** (-loss * ring_length / 20.0) * np.exp(
        2j * np.pi * neff_wl * ring_length / wl
    )
    out /= 1 - np.sqrt(transmission) * 10 ** (-loss * ring_length / 20.0) * np.exp(
        2j * np.pi * neff_wl * ring_length / wl
    )
    return abs(out) ** 2


if __name__ == "__main__":
    import matplotlib.pyplot as plt

    loss = 0.03  # [dB/μm] (alpha) waveguide loss
    neff = 2.46  # Effective index of the waveguides
    wl0 = 1.55  # [μm] the wavelength at which neff and ng are defined
    radius = 5
    ring_length = 2 * np.pi * radius  # [μm] Length of the ring
    coupling = 0.5  # [] coupling of the coupler
    wl = np.linspace(1.5, 1.6, 1000)  # [μm] Wavelengths to sweep over
    wl = np.linspace(1.55, 1.60, 1000)  # [μm] Wavelengths to sweep over
    ngs = [4.182551, 4.169563, 4.172917]
    thicknesses = [210, 220, 230]

    # widths = np.array([0.4, 0.45, 0.5, 0.55, 0.6])
    # ngs = np.array([4.38215238, 4.27254985, 4.16956338, 4.13283219, 4.05791982])

    widths = np.array([0.495, 0.5, 0.505])
    neffs = np.array([2.40197253, 2.46586378, 2.46731758])
    ng = 4.2  # Group index of the waveguides

    for width, neff in zip(widths, neffs):
        p = ring(
            wl=wl,
            wl0=wl0,
            neff=neff,
            ng=ng,
            ring_length=ring_length,
            coupling=coupling,
            loss=loss,
        )
        plt.plot(wl, p, label=f"{int(width*1e3)}nm")

    plt.title("ring resonator vs waveguide width")
    plt.xlabel("wavelength (um)")
    plt.ylabel("Power Transmission")
    plt.grid()
    plt.legend()
    plt.show()
../_images/b39d441ebcacfdf1e9dc1418aed916b93777e3b3b398663dc99b0c678ea35399.png

Layout#

gdsfactory easily enables you to layout Component with as many levels of hierarchy as you need.

A Component is a canvas where we can add polygons, references to other components or ports.

Lets add two references in a component.

import toolz

c = gf.components.ring_single_heater(gap=0.2, radius=10, length_x=4)
c.plot()
../_images/a7d965f88df9c380e8d2c538ac5b9cbf740abbb7137796d1c4aee4b5ff336ec4.png
scene = c.to_3d()
scene.show()

Lets define a ring function that also accepts other component specs for the subcomponents (straight, coupler, bend)

ring = gf.components.ring_single_heater(
    gap=0.2, radius=10, length_x=4, via_stack_offset=(2, 0)
)
ring_with_grating_couplers = gf.routing.add_fiber_array(ring)
ring_with_grating_couplers.plot()
../_images/445d2b75727a601b27d3a2b1f018e3b679087d463e4e3dff9750e832d3131156.png
port_names = ["l_e1", "r_e3"]
port_names = ["l_e4", "r_e4"]
c = gf.routing.add_pads_top(
    ring,
    port_names=port_names,
)
c = gf.routing.add_fiber_array(c)
c
../_images/4a1900a81f63760245679da226f4371767fb6a20bc0560d1c1d3e354881eb680.png

Top reticle assembly#

Once you have your components and circuits defined, you can add them into a top reticle Component for fabrication.

You need to consider:

  • what design variations do you want to include in the mask? You need to define your Design Of Experiment or DOE

  • obey DRC (Design rule checking) foundry rules for manufacturability. Foundry usually provides those rules for each layer (min width, min space, min density, max density)

  • make sure you will be able to test te devices after fabrication. Obey DFT (design for testing) rules. For example, if your test setup works only for fiber array, what is the fiber array spacing (127 or 250um?)

  • if you plan to package your device, make sure you follow your packaging guidelines from your packaging house (min pad size, min pad pitch, max number of rows for wire bonding …)

nm = 1e-3
ring_te = toolz.compose(gf.routing.add_fiber_array, gf.components.ring_single)
rings_te = []

for radius in [10, 20, 50]:
    ring = gf.c.ring_single(radius=radius)
    ring_te = gf.routing.add_fiber_array(ring)
    ring_te.name = f"ring_{radius}"
    rings_te.append(ring_te)

rings = gf.grid(rings_te)


@gf.cell
def reticle(size=(1000, 1000)):
    c = gf.Component()
    r = c << rings
    m = c << gf.components.pack_doe(
        gf.components.mzi,
        settings=dict(delta_length=[100, 200]),
        function=gf.routing.add_fiber_array,
    )
    m.dxmin = r.dxmax + 10
    m.dymin = r.dymin
    c << gf.components.seal_ring(c)
    return c


m = reticle()
m
../_images/f03320311f460728f49ff4b148027ab0b803f4831bf4c87210ed6212919cb79f.png
nm = 1e-3
ring_te = toolz.compose(gf.routing.add_fiber_array, gf.components.ring_single)
gaps = [210 * nm, 220 * nm, 230 * nm]
rings = []
port_names = ["l_e4", "r_e4"]

for gap in gaps:
    ring = gf.c.ring_single_heater(gap=gap)
    ring_pads = gf.routing.add_pads_top(ring, port_names=port_names)
    ring_te = gf.routing.add_fiber_array(ring_pads)
    ring_te.name = f"ring_{gap}"
    rings.append(ring_te)


def reticle(size=(1000, 1000)):
    c = gf.Component()
    p = c << gf.pack(rings)[0]
    c.add_ports(p.ports)
    c << gf.components.seal_ring(c)
    return c


m = reticle()
m
../_images/778a13140f764ebcfd07407ab2c0dbc9e0f231c5f97017eac4dc72b4bbebb95e.png
gdspath = m.write_gds(gdspath="mask.gds")
m.pprint_ports()
┏━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ name         width  orientation  layer        center                        port_type   ┃
┡━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│ 0_o1        │ 10.0  │ 270.0       │ WG (1/0)    │ (145.02, 34.111000000000004) │ vertical_te │
│ 0_o2        │ 10.0  │ 270.0       │ WG (1/0)    │ (272.02, 34.111000000000004) │ vertical_te │
│ 0_l_e4      │ 80.0  │ 270.0       │ MTOP (49/0) │ (158.52, 179.101)            │ electrical  │
│ 0_r_e4      │ 80.0  │ 270.0       │ MTOP (49/0) │ (258.52, 179.101)            │ electrical  │
│ 0_l_e1      │ 4.0   │ 180.0       │ MTOP (49/0) │ (203.02, 151.101)            │ electrical  │
│ 0_l_e2      │ 4.0   │ 270.0       │ MTOP (49/0) │ (205.02, 149.101)            │ electrical  │
│ 0_l_e3      │ 4.0   │ 0.0         │ MTOP (49/0) │ (207.02, 151.101)            │ electrical  │
│ 0_r_e1      │ 4.0   │ 180.0       │ MTOP (49/0) │ (210.02, 151.101)            │ electrical  │
│ 0_r_e2      │ 4.0   │ 270.0       │ MTOP (49/0) │ (212.02, 149.101)            │ electrical  │
│ 0_r_e3      │ 4.0   │ 0.0         │ MTOP (49/0) │ (214.02, 151.101)            │ electrical  │
│ 0_loopback1 │ 10.0  │ 270.0       │ WG (1/0)    │ (18.02, 34.111000000000004)  │ vertical_te │
│ 0_loopback2 │ 10.0  │ 270.0       │ WG (1/0)    │ (399.02, 34.111000000000004) │ vertical_te │
│ 1_o1        │ 10.0  │ 270.0       │ WG (1/0)    │ (145.02, 298.161)            │ vertical_te │
│ 1_o2        │ 10.0  │ 270.0       │ WG (1/0)    │ (272.02, 298.161)            │ vertical_te │
│ 1_l_e4      │ 80.0  │ 270.0       │ MTOP (49/0) │ (158.52, 443.151)            │ electrical  │
│ 1_r_e4      │ 80.0  │ 270.0       │ MTOP (49/0) │ (258.52, 443.151)            │ electrical  │
│ 1_l_e1      │ 4.0   │ 180.0       │ MTOP (49/0) │ (203.02, 415.151)            │ electrical  │
│ 1_l_e2      │ 4.0   │ 270.0       │ MTOP (49/0) │ (205.02, 413.151)            │ electrical  │
│ 1_l_e3      │ 4.0   │ 0.0         │ MTOP (49/0) │ (207.02, 415.151)            │ electrical  │
│ 1_r_e1      │ 4.0   │ 180.0       │ MTOP (49/0) │ (210.02, 415.151)            │ electrical  │
│ 1_r_e2      │ 4.0   │ 270.0       │ MTOP (49/0) │ (212.02, 413.151)            │ electrical  │
│ 1_r_e3      │ 4.0   │ 0.0         │ MTOP (49/0) │ (214.02, 415.151)            │ electrical  │
│ 1_loopback1 │ 10.0  │ 270.0       │ WG (1/0)    │ (18.02, 298.161)             │ vertical_te │
│ 1_loopback2 │ 10.0  │ 270.0       │ WG (1/0)    │ (399.02, 298.161)            │ vertical_te │
│ 2_o1        │ 10.0  │ 270.0       │ WG (1/0)    │ (145.02, 562.21)             │ vertical_te │
│ 2_o2        │ 10.0  │ 270.0       │ WG (1/0)    │ (272.02, 562.21)             │ vertical_te │
│ 2_l_e4      │ 80.0  │ 270.0       │ MTOP (49/0) │ (158.52, 707.2)              │ electrical  │
│ 2_r_e4      │ 80.0  │ 270.0       │ MTOP (49/0) │ (258.52, 707.2)              │ electrical  │
│ 2_l_e1      │ 4.0   │ 180.0       │ MTOP (49/0) │ (203.02, 679.2)              │ electrical  │
│ 2_l_e2      │ 4.0   │ 270.0       │ MTOP (49/0) │ (205.02, 677.2)              │ electrical  │
│ 2_l_e3      │ 4.0   │ 0.0         │ MTOP (49/0) │ (207.02, 679.2)              │ electrical  │
│ 2_r_e1      │ 4.0   │ 180.0       │ MTOP (49/0) │ (210.02, 679.2)              │ electrical  │
│ 2_r_e2      │ 4.0   │ 270.0       │ MTOP (49/0) │ (212.02, 677.2)              │ electrical  │
│ 2_r_e3      │ 4.0   │ 0.0         │ MTOP (49/0) │ (214.02, 679.2)              │ electrical  │
│ 2_loopback1 │ 10.0  │ 270.0       │ WG (1/0)    │ (18.02, 562.21)              │ vertical_te │
│ 2_loopback2 │ 10.0  │ 270.0       │ WG (1/0)    │ (399.02, 562.21)             │ vertical_te │
└─────────────┴───────┴─────────────┴─────────────┴──────────────────────────────┴─────────────┘

You can also write the test sequence in CSV from the component

df = gf.labels.get_test_manifest(m)
df
cell measurement measurement_settings analysis analysis_settings doe
0 0_o1 None None None None None
1 0_o2 None None None None None
2 0_l_e4 None None None None None
3 0_r_e4 None None None None None
4 0_l_e1 None None None None None
5 0_l_e2 None None None None None
6 0_l_e3 None None None None None
7 0_r_e1 None None None None None
8 0_r_e2 None None None None None
9 0_r_e3 None None None None None
10 0_loopback1 None None None None None
11 0_loopback2 None None None None None
12 1_o1 None None None None None
13 1_o2 None None None None None
14 1_l_e4 None None None None None
15 1_r_e4 None None None None None
16 1_l_e1 None None None None None
17 1_l_e2 None None None None None
18 1_l_e3 None None None None None
19 1_r_e1 None None None None None
20 1_r_e2 None None None None None
21 1_r_e3 None None None None None
22 1_loopback1 None None None None None
23 1_loopback2 None None None None None
24 2_o1 None None None None None
25 2_o2 None None None None None
26 2_l_e4 None None None None None
27 2_r_e4 None None None None None
28 2_l_e1 None None None None None
29 2_l_e2 None None None None None
30 2_l_e3 None None None None None
31 2_r_e1 None None None None None
32 2_r_e2 None None None None None
33 2_r_e3 None None None None None
34 2_loopback1 None None None None None
35 2_loopback2 None None None None None

As you can see there are 6 devices with optical and electrical ports.

You can turn each label into a test manifest CSV file to interface with your lab instrumentation functions.

Each measurement will use a different measurement procedure and settings measurement_settings

The default measurement settings for each functions can also be defined in a separate CSV file and easily editable with Excel or LibreOffice.

df.to_csv("test_manifest.csv")
from functools import partial

add_fiber_array_optical_south_electrical_north = partial(
    gf.components.add_fiber_array_optical_south_electrical_north,
    pad=gf.c.pad,
    grating_coupler=gf.c.grating_coupler_te,
    cross_section_metal="metal_routing",
)


def sample_reticle(grid: bool = False, **kwargs) -> gf.Component:
    """Returns MZI with TE grating couplers.

    Args:
        grid: if True returns components on a regular grid. False packs them as close as possible.
        kwargs: passed to pack or grid.
    """
    test_info_mzi_heaters = dict(
        doe="mzis_heaters",
        analysis="mzi_heater",
        measurement="optical_loopback4_heater_sweep",
    )
    test_info_ring_heaters = dict(
        doe="ring_heaters",
        analysis="ring_heater",
        measurement="optical_loopback2_heater_sweep",
    )
    spiral_info = dict(
        doe="spirals_sc",
        measurement="optical_loopback4",
        analysis="optical_loopback4_spirals",
    )

    mzis = []
    rings = []
    spirals = []

    for length in [100, 200, 300]:
        mzi = gf.components.mzi2x2_2x2_phase_shifter(
            length_x=length, auto_rename_ports=False
        )
        mzi = add_fiber_array_optical_south_electrical_north(
            mzi,
            electrical_port_names=["top_l_e2", "top_r_e2"],
        )
        mzi.name = f"mzi_heater_{length}"
        mzi.info.update(test_info_mzi_heaters)
        mzis.append(mzi)

    for length_x in [10, 20, 30]:
        ring = gf.components.ring_single_heater(length_x=length_x)
        ring = add_fiber_array_optical_south_electrical_north(
            ring,
            electrical_port_names=["l_e2", "r_e2"],
        )
        ring.info.update(test_info_ring_heaters)
        ring.name = f"ring_single_heater_{length_x}"
        rings.append(ring)

    for length in [0, 100, 200]:
        spiral = gf.c.spiral(length=length)
        spiral = gf.routing.add_fiber_array(spiral)
        spiral.name = f"spiral_{length}"
        spiral.info.update(spiral_info)
        spirals.append(spiral)

    components = mzis + rings + spirals

    if grid:
        return gf.grid(components, name_ports_with_component_name=True, **kwargs)
    c = gf.pack(components, **kwargs)
    if len(c) > 1:
        raise ValueError(f"failed to pack into single group. Made {len(c)} groups.")
    return c[0]


m = sample_reticle()
m
../_images/16b3932b836427c4a956a13d7a5f05f42428868c9e9ee330d904008448ec540b.png
m.pprint_ports()
┏━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ name         width  orientation  layer        center                         port_type   ┃
┡━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│ 0_e4_1_1    │ 100.0 │ 270.0       │ MTOP (49/0) │ (285.52, 255.05)              │ electrical  │
│ 0_e4_1_2    │ 100.0 │ 270.0       │ MTOP (49/0) │ (385.52, 255.05)              │ electrical  │
│ 1_e4_1_1    │ 100.0 │ 270.0       │ MTOP (49/0) │ (285.52, 615.0500000000001)   │ electrical  │
│ 1_e4_1_2    │ 100.0 │ 270.0       │ MTOP (49/0) │ (385.52, 615.0500000000001)   │ electrical  │
│ 2_e4_1_1    │ 100.0 │ 270.0       │ MTOP (49/0) │ (285.52, 975.0500000000001)   │ electrical  │
│ 2_e4_1_2    │ 100.0 │ 270.0       │ MTOP (49/0) │ (385.52, 975.0500000000001)   │ electrical  │
│ 3_e4_1_1    │ 100.0 │ 270.0       │ MTOP (49/0) │ (829.46, 255.05)              │ electrical  │
│ 3_e4_1_2    │ 100.0 │ 270.0       │ MTOP (49/0) │ (929.46, 255.05)              │ electrical  │
│ 4_e4_1_1    │ 100.0 │ 270.0       │ MTOP (49/0) │ (829.46, 615.0500000000001)   │ electrical  │
│ 4_e4_1_2    │ 100.0 │ 270.0       │ MTOP (49/0) │ (929.46, 615.0500000000001)   │ electrical  │
│ 5_e4_1_1    │ 100.0 │ 270.0       │ MTOP (49/0) │ (829.46, 975.0500000000001)   │ electrical  │
│ 5_e4_1_2    │ 100.0 │ 270.0       │ MTOP (49/0) │ (929.46, 975.0500000000001)   │ electrical  │
│ 6_o1        │ 10.0  │ 270.0       │ WG (1/0)    │ (209.298, 1114.111)           │ vertical_te │
│ 6_o2        │ 10.0  │ 270.0       │ WG (1/0)    │ (336.298, 1114.111)           │ vertical_te │
│ 6_loopback1 │ 10.0  │ 270.0       │ WG (1/0)    │ (82.298, 1114.111)            │ vertical_te │
│ 6_loopback2 │ 10.0  │ 270.0       │ WG (1/0)    │ (463.298, 1114.111)           │ vertical_te │
│ 7_o1        │ 10.0  │ 270.0       │ WG (1/0)    │ (626.24, 1114.111)            │ vertical_te │
│ 7_o2        │ 10.0  │ 270.0       │ WG (1/0)    │ (753.24, 1114.111)            │ vertical_te │
│ 7_loopback1 │ 10.0  │ 270.0       │ WG (1/0)    │ (499.24, 1114.111)            │ vertical_te │
│ 7_loopback2 │ 10.0  │ 270.0       │ WG (1/0)    │ (880.24, 1114.111)            │ vertical_te │
│ 8_o1        │ 10.0  │ 270.0       │ WG (1/0)    │ (1043.18, 1114.111)           │ vertical_te │
│ 8_o2        │ 10.0  │ 270.0       │ WG (1/0)    │ (1170.18, 1114.111)           │ vertical_te │
│ 8_loopback1 │ 10.0  │ 270.0       │ WG (1/0)    │ (916.1800000000001, 1114.111) │ vertical_te │
│ 8_loopback2 │ 10.0  │ 270.0       │ WG (1/0)    │ (1297.18, 1114.111)           │ vertical_te │
└─────────────┴───────┴─────────────┴─────────────┴───────────────────────────────┴─────────────┘
df = gf.labels.get_test_manifest(m)
df
cell measurement measurement_settings analysis analysis_settings doe
0 0_e4_1_1 None None None None None
1 0_e4_1_2 None None None None None
2 1_e4_1_1 None None None None None
3 1_e4_1_2 None None None None None
4 2_e4_1_1 None None None None None
5 2_e4_1_2 None None None None None
6 3_e4_1_1 None None None None None
7 3_e4_1_2 None None None None None
8 4_e4_1_1 None None None None None
9 4_e4_1_2 None None None None None
10 5_e4_1_1 None None None None None
11 5_e4_1_2 None None None None None
12 6_o1 None None None None None
13 6_o2 None None None None None
14 6_loopback1 None None None None None
15 6_loopback2 None None None None None
16 7_o1 None None None None None
17 7_o2 None None None None None
18 7_loopback1 None None None None None
19 7_loopback2 None None None None None
20 8_o1 None None None None None
21 8_o2 None None None None None
22 8_loopback1 None None None None None
23 8_loopback2 None None None None None