Native toolchain (arm64 macOS): ngspice + a Python venv + sky130A PDK via volare.
Build the measuring instrument first; the DUT (ota.spice) drops in last.
# gm/ID device tables + design charts
.venv/bin/python gmid/sweep.py nfet # -> nfet.h5
.venv/bin/python gmid/sweep.py pfet # -> pfet.h5
.venv/bin/python gmid/plot.py # -> nfet_gmid.png, pfet_gmid.png
# any testbench standalone
ngspice -b tb/ac.sp # gain / UGB / PM
ngspice -b tb/{tran_sr,psrr,cmrr,noise,offset_mc}.sp
# 45-corner sweep (needs a real ota.spice) + report
.venv/bin/python run/run.py # --dummy to skip the PDK lib
.venv/bin/python report/report.py # corner_summary.md, bode.png, mc_offset.pngThe harness is validated against an ideal 2-pole dummy (tb/dummy_ota.spice).
To characterize the real amp:
- Port order (exact):
.subckt ota inp inn out vdd vss - Do NOT include the sky130 lib inside
ota.spice. The harness injects the process corner viatb/corner.inc(run/run.pywrites the.lib ... <corner>line). Ifota.spicepins its own corner, the process sweep silently won't vary.ota.spiceshould referencesky130_fd_pr__{nfet,pfet}_01v8devices only. - In each
tb/*.sp, change.include tb/dummy_ota.spice→.include ota.spice. run/run.py(no--dummy) then sweeps {tt,ss,ff,sf,fs}×{−40,27,125}C×{1.62,1.8,1.98}V.
- PSRR/CMRR/noise/offset numbers are meaningless on the dummy by construction (no supply path, perfect symmetry, no mismatch). They validate with real devices.
offset_mc.spneeds sky130 mismatch models active (mc_mm_switch=1, already set).