AC Small-Signal Analysis#

Perform small-signal AC analysis with linearized devices.

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
import pandas as pd
import holoviews as hv
from nyancad.watch import watch_project_dir, file_schematic
from nyancad.netlist import inspice_netlist
from nyancad.plot import bodeplot
from InSpice import Simulator

hv.extension("bokeh")
scipy not available, fft plots will not work

Parameters#

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

# -- AC parameters --
VARIATION = "dec"  # point spacing: "dec", "oct", or "lin"
NUM_POINTS = 10  # number of points (per decade/octave, or total for lin)
START_FREQ = 1  # start frequency (Hz)
STOP_FREQ = 1e6  # stop frequency (Hz)

# -- Vectors to plot --
# Leave empty to auto-select all node voltages after simulation.
VECTORS = []

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))
print("Netlist elements:", list(spice.element_names))
Netlist nodes: ['0', 'W2', 'GND', 'W3', 'W6']
Netlist elements: ['VV1', 'VV2', 'XM1', 'XM2', 'CC1']

Run AC 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.ac(
    variation=VARIATION,
    number_of_points=NUM_POINTS,
    start_frequency=START_FREQ,
    stop_frequency=STOP_FREQ,
)
print("AC analysis complete.")
Warning: can't find the initialization file spinit.
Newer Ngspice version that could be unsupported 46
AC analysis complete.

Results#

available = [*analysis.nodes.keys(), *analysis.branches.keys()]
print("Available vectors:", available)

if not VECTORS:
    VECTORS = list(analysis.nodes.keys())
    print("Auto-selected:", VECTORS)

df = pd.DataFrame(index=np.array(analysis.frequency))
for vec in VECTORS:
    df[vec] = np.array(analysis[vec])
df
Available vectors: ['n.xm1.nsg13_lv_nmos#SI', 'n.xm1.nsg13_lv_nmos#GP', 'n.xm1.nsg13_lv_nmos#NOI', 'n.xm2.nsg13_lv_pmos#SI', 'n.xm2.nsg13_lv_pmos#GP', 'n.xm2.nsg13_lv_pmos#NOI', 'w6', 'w3', 'w2', 'vv1', 'vv2']
Auto-selected: ['n.xm1.nsg13_lv_nmos#SI', 'n.xm1.nsg13_lv_nmos#GP', 'n.xm1.nsg13_lv_nmos#NOI', 'n.xm2.nsg13_lv_pmos#SI', 'n.xm2.nsg13_lv_pmos#GP', 'n.xm2.nsg13_lv_pmos#NOI', 'w6', 'w3', 'w2']
n.xm1.nsg13_lv_nmos#SI n.xm1.nsg13_lv_nmos#GP n.xm1.nsg13_lv_nmos#NOI n.xm2.nsg13_lv_pmos#SI n.xm2.nsg13_lv_pmos#GP n.xm2.nsg13_lv_pmos#NOI w6 w3 w2
1.000000e+00 0.0+0.0j 1.000000+0.000000j 0.0+0.0j 0.0+0.0j 1.0+0.0j 0.0+0.0j -2.131095+0.186625j 1.0+0.0j 0.0+0.0j
1.258925e+00 0.0+0.0j 1.000000+0.000000j 0.0+0.0j 0.0+0.0j 1.0+0.0j 0.0+0.0j -2.121650+0.233905j 1.0+0.0j 0.0+0.0j
1.584893e+00 0.0+0.0j 1.000000+0.000000j 0.0+0.0j 0.0+0.0j 1.0+0.0j 0.0+0.0j -2.106853+0.292415j 1.0+0.0j 0.0+0.0j
1.995262e+00 0.0+0.0j 1.000000+0.000000j 0.0+0.0j 0.0+0.0j 1.0+0.0j 0.0+0.0j -2.083818+0.364104j 1.0+0.0j 0.0+0.0j
2.511886e+00 0.0+0.0j 1.000000+0.000000j 0.0+0.0j 0.0+0.0j 1.0+0.0j 0.0+0.0j -2.048325+0.450573j 1.0+0.0j 0.0+0.0j
... ... ... ... ... ... ... ... ... ...
3.981072e+05 0.0+0.0j 1.000000-0.000000j 0.0+0.0j 0.0+0.0j 1.0-0.0j 0.0+0.0j -0.000000+0.000062j 1.0+0.0j 0.0+0.0j
5.011872e+05 0.0+0.0j 1.000000-0.000001j 0.0+0.0j 0.0+0.0j 1.0-0.0j 0.0+0.0j -0.000000+0.000049j 1.0+0.0j 0.0+0.0j
6.309573e+05 0.0+0.0j 1.000000-0.000001j 0.0+0.0j 0.0+0.0j 1.0-0.0j 0.0+0.0j -0.000000+0.000039j 1.0+0.0j 0.0+0.0j
7.943282e+05 0.0+0.0j 1.000000-0.000001j 0.0+0.0j 0.0+0.0j 1.0-0.0j 0.0+0.0j -0.000000+0.000031j 1.0+0.0j 0.0+0.0j
1.000000e+06 0.0+0.0j 1.000000-0.000001j 0.0+0.0j 0.0+0.0j 1.0-0.0j 0.0+0.0j -0.000000+0.000025j 1.0+0.0j 0.0+0.0j

61 rows × 9 columns

bodeplot(df).opts(hv.opts.Curve(responsive=True, height=200))