Pole-Zero Analysis#

Compute poles and zeros of the small-signal transfer function.

import os
import sys
from pathlib import Path

# Set working directory to the PDK root if running from scripts/
if Path.cwd().name == "scripts":
    os.chdir(Path.cwd().parent)

# Ensure ngspice shared library can be found (macOS homebrew)
if sys.platform == "darwin" and "/opt/homebrew/lib" not in os.environ.get(
    "DYLD_LIBRARY_PATH", ""
):
    os.environ["DYLD_LIBRARY_PATH"] = "/opt/homebrew/lib:" + os.environ.get(
        "DYLD_LIBRARY_PATH", ""
    )

import numpy as np
from nyancad.watch import watch_project_dir, file_schematic
from nyancad.netlist import inspice_netlist
from InSpice import Simulator

Parameters#

SCHEMATIC = "inverter"  # stem of the .nyancir file
CORNER = "mos_tt"

# -- Pole-zero parameters --
INPUT_NODE1 = "W3"  # input positive node
INPUT_NODE2 = "GND"  # input negative node
OUTPUT_NODE1 = "W6"  # output positive node
OUTPUT_NODE2 = "GND"  # output negative node
TF_TYPE = "vol"  # transfer function type: "vol" or "cur"
PZ_TYPE = "pz"  # analysis type: "pol", "zer", or "pz"

Load schematic and build netlist#

project = watch_project_dir(".")
schem_data = await file_schematic(project, SCHEMATIC)
spice = await inspice_netlist(SCHEMATIC, schem_data, corner=CORNER)
print(spice)
Duplicated lib ('ihp/models/ngspice/models/cornerMOSlv.lib', 'mos_tt')
.title schematic
.lib /Users/pepijndevos/code/IHP/ihp/models/ngspice/models/cornerMOSlv.lib mos_tt
VV1 W2 GND DC 1.8
VV2 W3 GND DC 0.9 AC 1 sin(0.9 0.5 1k)
XM1 W6 W3 GND GND sg13_lv_nmos l={0.13 * 1e-6} m=1 ng=1 w={0.15 * 1e-6}
XM2 W2 W3 W6 W2 sg13_lv_pmos l={0.13 * 1e-6} m=1 ng=1 w={0.15 * 1e-6}
CC1 W6 GND 1u
print("Netlist nodes:", list(spice.node_names))
Netlist nodes: ['0', 'W2', 'GND', 'W3', 'W6']

Run pole-zero analysis#

simulator = Simulator.factory(simulator="ngspice-shared")

for osdi in [
    "ihp/models/ngspice/osdi/psp103.osdi",
    "ihp/models/ngspice/osdi/psp103_nqs.osdi",
    "ihp/models/ngspice/osdi/r3_cmc.osdi",
    "ihp/models/ngspice/osdi/mosvar.osdi",
]:
    simulator._ngspice_shared.exec_command(f"osdi {osdi}")

simulation = simulator.simulation(spice)
analysis = simulation.polezero(
    node1=INPUT_NODE1,
    node2=INPUT_NODE2,
    node3=OUTPUT_NODE1,
    node4=OUTPUT_NODE2,
    tf_type=TF_TYPE,
    pz_type=PZ_TYPE,
)
print("Pole-zero analysis complete.")
Warning: can't find the initialization file spinit.
Newer Ngspice version that could be unsupported 46
Pole-zero analysis complete.

Results#

print("Poles:")
for key in analysis.nodes.keys():
    if "pole" in key.lower():
        val = np.array(analysis[key]).item()
        print(f"  {key} = {val:.6g}")

print("\nZeros:")
for key in analysis.nodes.keys():
    if "zero" in key.lower():
        val = np.array(analysis[key]).item()
        print(f"  {key} = {val:.6g}")
Poles:
  pole(5) = -71.7487+0j
  pole(4) = -5.03165e+12+0j
  pole(3) = -1.23475e+13+0j
  pole(2) = -1.7598e+13+0j
  pole(1) = -1.53426e+14+0j

Zeros:
  zero(4) = 6.86471e+11+0j
  zero(3) = -1.23475e+13+0j
  zero(2) = -1.28779e+13+0j
  zero(1) = -1.7598e+13+0j