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:
  1. Instantiate a Mux2 component.

  2. Visualize its layout.

  3. Perform a corner analysis using Luceda Circuit Analyzer.

  4. 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
  1. 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)
plot mux variability example
  1. 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)
  1. 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
  1. 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()
  • Mux2 Corner Analysis for core_height_variation = nominal
  • Mux2 Corner Analysis for core_width_variation = nominal

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:
  1. 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]

  2. 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()
plot mux variability example
  1. 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()
Effective Index vs. Heater Current
  1. 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 Budget for Heated MUX
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