SAX Simulation of a Resonator Test Chip#

This notebook demonstrates how to run a circuit-level SAX simulation of the resonator_test_chip_yaml component, which is defined via a .pic.yml netlist file and a corresponding gdsfactory+ schematic.

The workflow is:

  1. Load the component from the YAML netlist with gdsfactory.

  2. Extract the netlist for circuit simulation.

  3. Build a SAX circuit using the QPDK model library.

  4. Evaluate the S-parameters over a frequency range.

  5. Plot the transmission to observe resonator dips.

Hide code cell source

import warnings

import gdsfactory as gf
import jax.numpy as jnp
import matplotlib.pyplot as plt
import sax

from qpdk import PATH, PDK
from qpdk.models import models

PDK.activate()

Load the component#

The resonator test chip is defined in a .pic.yml file that lives alongside the QPDK sample scripts. It contains 16 quarter-wave coupled resonators (8 per probeline), four launchers, and CPW routing between all elements.

yaml_path = PATH.samples / "resonator_test_chip_yaml.pic.yml"
chip = gf.read.from_yaml(yaml_path)
chip.plot()
../_images/f654cc702f3cb1d69bd9d4d12a418770583eba3aeb3e291406da8563e55865a1.svg

Extract the netlist#

Component.get_netlist() returns a dictionary with instances, nets, ports, and placements. SAX understands this format directly.

with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    netlist = chip.get_netlist()

print("Instances:")
for name, inst in netlist["instances"].items():
    print(f"  {name}: {inst['component']}")
Instances:
  bend_s: bend_s
  bend_s2: bend_s
  bend_s3: bend_s
  bend_s4: bend_s
  launcher: launcher
  launcher2: launcher
  launcher3: launcher
  launcher4: launcher
  resonator_bot_1: quarter_wave_resonator_coupled
  resonator_bot_2: quarter_wave_resonator_coupled
  resonator_bot_3: quarter_wave_resonator_coupled
  resonator_bot_4: quarter_wave_resonator_coupled
  resonator_bot_5: quarter_wave_resonator_coupled
  resonator_bot_6: quarter_wave_resonator_coupled
  resonator_bot_7: quarter_wave_resonator_coupled
  resonator_bot_8: quarter_wave_resonator_coupled
  resonator_top_1: quarter_wave_resonator_coupled
  resonator_top_2: quarter_wave_resonator_coupled
  resonator_top_3: quarter_wave_resonator_coupled
  resonator_top_4: quarter_wave_resonator_coupled
  resonator_top_5: quarter_wave_resonator_coupled
  resonator_top_6: quarter_wave_resonator_coupled
  resonator_top_7: quarter_wave_resonator_coupled
  resonator_top_8: quarter_wave_resonator_coupled
  straight: straight
  straight2: straight
  straight3: straight
  straight4: straight
  straight5: straight
  straight6: straight
  straight7: straight
  straight8: straight
  straight9: straight
  straight10: straight
  straight11: straight
  straight12: straight
  straight13: straight
  straight14: straight

Build the SAX circuit#

QPDK ships a models dictionary that maps every component name to a SAX model function. We pass the netlist and models to sax.circuit.

circuit_fn, circuit_info = sax.circuit(
    netlist=netlist,
    models=models,
    on_internal_port="ignore",
)

Simulate#

Evaluate the circuit over the 5–9 GHz band. The four external ports correspond to the four launchers:

Port

Launcher

Probeline

o1

West top

Top

o2

East top

Top

o3

West bot

Bottom

o4

East bot

Bottom

The top probeline has eight resonators with varying coupling gaps (12–26 µm) and the bottom probeline has eight resonators with a fixed coupling gap of 16 µm.

freq = jnp.linspace(5e9, 9e9, 5001)
s_params = circuit_fn(f=freq)

freq_ghz = freq / 1e9

Results#

Top probeline – variable coupling gap#

:math:S_{21} (port o1 → o2) shows eight notches, one per resonator.

s21 = s_params[("o1", "o2")]
s11 = s_params[("o1", "o1")]

fig, ax = plt.subplots()
ax.plot(freq_ghz, 20 * jnp.log10(jnp.abs(s21)), label="$S_{21}$")
ax.plot(freq_ghz, 20 * jnp.log10(jnp.abs(s11)), label="$S_{11}$", alpha=0.5)
ax.set_xlabel("Frequency [GHz]")
ax.set_ylabel("Magnitude [dB]")
ax.set_title("Top probeline (variable coupling gap)")
ax.legend()
ax.grid(True)
plt.tight_layout()
plt.show()
../_images/0d6214f781479b36bb62bd3d68902ca88d911906809b0036690ce074bea40ca4.svg

Bottom probeline – fixed coupling gap#

:math:S_{43} (port o3 → o4) shows eight notches with uniform coupling depth because all resonators share the same 16 µm coupling gap.

s43 = s_params[("o3", "o4")]
s33 = s_params[("o3", "o3")]

fig, ax = plt.subplots()
ax.plot(freq_ghz, 20 * jnp.log10(jnp.abs(s43)), label="$S_{43}$")
ax.plot(freq_ghz, 20 * jnp.log10(jnp.abs(s33)), label="$S_{33}$", alpha=0.5)
ax.set_xlabel("Frequency [GHz]")
ax.set_ylabel("Magnitude [dB]")
ax.set_title("Bottom probeline (fixed coupling gap)")
ax.legend()
ax.grid(True)
plt.tight_layout()
plt.show()
../_images/00ef5ab0faf96052899599ef8a0c769ac2ac2152b1870f3c6dae359e67a63bae.svg

Both probelines#

Overlay both transmission traces to compare the two probelines.

fig, ax = plt.subplots()
ax.plot(freq_ghz, 20 * jnp.log10(jnp.abs(s21)), label="Top ($S_{21}$)")
ax.plot(freq_ghz, 20 * jnp.log10(jnp.abs(s43)), label="Bottom ($S_{43}$)")
ax.set_xlabel("Frequency [GHz]")
ax.set_ylabel("Magnitude [dB]")
ax.set_title("Resonator test chip – transmission comparison")
ax.legend()
ax.grid(True)
plt.tight_layout()
plt.show()
../_images/564578b4f49fd1e5fb1bd23cb32b80fb4705a9e3db8155b8785599eecdd30317.svg