Note
Go to the end to download the full example code
Corner and Power Budget Analysis of MUX Devices
This script showcases a corner analysis of multiplexer (MUX) devices using the Luceda Circuit Analyzer, followed by an estimation of heaters’ power budgeting.
Part 1: Corner Analysis of a Wavelength Demultiplexer
This part of the example demonstrates how to perform a corner analysis on a wavelength demultiplexer (Mux2) described in DOI:10.1109/group4.2015.7305928. Corner Analysis is a method to study the impact of process variations on the performance of a photonic integrated circuit.
- This example will:
Instantiate a Mux2 component.
Visualize its layout.
Perform a corner analysis using Luceda Circuit Analyzer.
Plot the transmission spectra for the ‘min’, ‘nominal’, and ‘max’ corners.
The analysis relies on a configuration folder (ca_config) which defines how process variations map to model parameters. This folder contains two key files:
- models.yaml:
This file defines the process parameters that are subject to variation. For each parameter, it specifies documentation, a default value, and corner values for corner analysis. It also links to a Python function for more complex parameter generation.
version: 1 libraries: si_fab: cells: GenericWaveguide: parameters: core_width_variation: doc: "width variation" default: 0 corners: [ -0.5, 0, 0.5 ] core_height_variation: doc: "height variation" default: 0 corners: [ -2, 0, 2 ] generate_parameters: "models.waveguide_parameters"
- models.py:
This file contains the Python functions referenced in the YAML file. The waveguide_parameters function, for example, calculates the effective refractive index (n_eff) based on the nominal waveguide dimensions and the sampled variations for core width and height.
import si_fab.all as pdk import numpy as np def waveguide_parameters(self: pdk.GenericWaveguide, **parameters): template = self.template width = template.cell.Layout().core_width + parameters["core_width_variation"] * 1e-3 height = template.core_height + parameters["core_height_variation"] * 1e-3 n_eff = template.get_n_eff_interp_for_design_parameters(width, height) # for si wire return { "n_eff": np.array(n_eff), }
import matplotlib.pyplot as plt
import numpy as np
import pteam_library_si_fab.all as pt_lib
import si_fab.all as pdk
import circuit_analyzer.all as ca
import ipkiss3.all as i3
from circuit_analyzer.fab_corner_variability import get_corners
Instantiate the Mux2 component and visualize its layout
mux = pt_lib.Mux2(name="my_mux2_for_corner_analysis")
mux_layout = mux.Layout()
mux_layout.visualize(annotate=True)

Set up the simulation
circuit_model = mux.get_default_view(i3.CircuitModelView)
# Define wavelength range for the simulation
start_wavelength: float = 1.5
end_wavelength: float = 1.55
n_wavelength_points: int = 1000
wavelengths = np.linspace(start_wavelength, end_wavelength, n_wavelength_points)
Perform corner analysis
corner_smatrices_nominal_height = {}
corner_smatrices_nominal_width = {}
# The 'with ca.setup(...)' block is required to configure the circuit analyzer
with ca.setup("ca_config"):
# Get the corner names defined for this model
corner_names = get_corners(circuit_model)
# Loop over the 'min', 'nominal', and 'max' corners
for corner in ["min", "nominal", "max"]:
# Create a dictionary of corner values for the analysis
kwargs_nominal_height = {"core_width_variation": corner, "core_height_variation": "nominal"}
kwargs_nominal_width = {"core_height_variation": corner, "core_width_variation": "nominal"}
# Run the corner analysis
smat_nominal_height = ca.corner_analysis(
circuit_model=circuit_model,
wavelengths=wavelengths,
parallel=False,
**kwargs_nominal_height,
)
smat_nominal_width = ca.corner_analysis(
circuit_model=circuit_model,
wavelengths=wavelengths,
parallel=False,
**kwargs_nominal_width,
)
corner_smatrices_nominal_height[corner] = smat_nominal_height
corner_smatrices_nominal_width[corner] = smat_nominal_width
Plot the results
line_styles = {"min": "dotted", "nominal": "solid", "max": "dashed"}
# core height variation = nominal, core_width_variation = [min,nominal,max]
plt.figure(figsize=(10, 6))
plt.grid(True, color="gray", linestyle="--", linewidth=0.5, alpha=0.5)
plt.title("Mux2 Corner Analysis for core_height_variation = nominal")
plt.xlabel(r"Wavelength [$\mu$m]", fontsize=12)
plt.ylabel("Transmission (S21)", fontsize=12)
# Plot the results for each corner
for corner, smat in corner_smatrices_nominal_height.items():
smat_values = np.abs(smat["in1", "out1"])
plt.plot(
smat.sweep_parameter_values,
smat_values,
label=f"out1: core_width_variation = {corner}",
linewidth=1.5,
linestyle=line_styles[corner],
)
plt.legend(loc="lower left")
plt.show()
# core height variation = [min,nominal,max], core_width_variation = nominal
plt.figure(figsize=(10, 6))
plt.grid(True, color="gray", linestyle="--", linewidth=0.5, alpha=0.5)
plt.title("Mux2 Corner Analysis for core_width_variation = nominal")
plt.xlabel(r"Wavelength [$\mu$m]", fontsize=12)
plt.ylabel("Transmission (S21)", fontsize=12)
# Plot the results for each corner
for corner, smat in corner_smatrices_nominal_width.items():
smat_values = np.abs(smat["in1", "out1"])
plt.plot(
smat.sweep_parameter_values,
smat_values,
label=f"out1: core_height_variation = {corner}",
linewidth=1.5,
linestyle=line_styles[corner],
)
plt.legend(loc="lower left")
plt.show()
Part 2: Power Budget Analysis of a Heated MUX
This part demonstrates power budgeting for a heated MUX using data obtained from thermal simulations. It calculates the relationship between power consumption and phase shift for a heated waveguide.
The example demonstrates that the phase shift is determined solely by the heater’s power consumption and does not depend on its length.
- This section will:
load the results of a thermo-optic simulation containing change in effective index (n_eff) as a function of the electrical current in the heater. [See femwell]
Calculate the required electrical power to achieve a \(\pi/2\) phase shift for different heater lengths.
# Get the heated waveguide from the PDK
mux_heater = pdk.HeatedWaveguide()
# Visualize the cross-section of heated waveguide
cross_section = mux_heater.Layout().cross_section(cross_section_path=i3.Shape(points=[(0, -2), (0, 2)]))
cross_section.visualize()

Load thermal simulation data The data are obtained from performing femwell thermal simulation on a cross section of si_fab.HeatedWaveguide.
thermal_sim_data = np.genfromtxt(
"Heater_thermal_simulation_current_vs_effective_index.csv", delimiter=",", skip_header=1
)
# Separate the columns back into their original arrays
currents = thermal_sim_data[:, 0]
neffs = thermal_sim_data[:, 1]
plt.figure()
plt.xlabel("Current [mA]")
plt.ylabel("Effective refractive index ($n_{eff}$)")
plt.plot(currents * 1e3, neffs)
plt.grid(True)
plt.title("Effective Index vs. Heater Current")
plt.show()

Calculate the required electrical power to achieve a \(\pi/2\) phase shift
TiN_conductivity = 2.36e6 # S/m
TiN_Area = 0.18 # um^2
# Find phase shift for different waveguide lengths
delay_lengths = np.linspace(40, 200, 5)
# Plot Power vs. Phase Shift
plt.figure()
for length in delay_lengths:
# Resistance = (resistivity * length) / area, where resistivity = 1 / conductivity
resistance = (1 / TiN_conductivity) * (length * 1e-6) / (TiN_Area * 1e-12)
power_mW = (currents**2) * resistance * 1e3 # P = I^2 * R, in mW
phase_shift = (2 * np.pi / 1.55) * (np.array(neffs) - neffs[0]) * length
plt.plot(power_mW, phase_shift, label=f"heater length = {length:.2f} um")
# Highlight the :math:`\pi/2` phase shift level
plt.axhline(y=0.5 * np.pi, color="purple", linestyle="--", linewidth=2, label=r"π/2-phase shift")
plt.xlabel("Power [mW]")
plt.ylabel("Phase shift [rad]")
plt.title("Power Budget for Heated MUX")
plt.legend()
plt.grid(True)
plt.show()
# power required for :math:`\pi/2` phase shift
idx = (np.abs(phase_shift - 0.5 * np.pi)).argmin()
power_at_half_pi = power_mW[idx]
print(f"power required for π/2-phase shift = {power_at_half_pi} mW")

power required for π/2-phase shift = 10.563876238321045 mW
- Estimating power consumption of MUX2
If we use heaters on both arms of MZI, then we need on average \(\pi/2\) phase shift for each arm. Considering Mux2 example, the circuit contains 4 MZIs which means 8 heaters should be used.
print(f"power estimation of MUX2 = {8 * power_at_half_pi} mW")
power estimation of MUX2 = 84.51100990656836 mW