| name | spectre-netlist-gotchas |
| description | Critical Spectre netlist-mode gotchas for standalone simulation (no Virtuoso ADE).
Use when: (1) writing Spectre testbench .scs files with `simulator lang=spectre`,
(2) noise analysis shows absurd values (megavolts of noise) — oprobe topology is wrong,
(3) SFE-30 error on `ac=1` in vsource — use `mag=1` in native Spectre lang,
(4) SFE-1997 error on `oprobe=<node_name>` — must be a circuit element not a node,
(5) parsing PSF ASCII output from Spectre (dc_op.dc, ac_gain.ac, noise_an.noise),
(6) phase margin calculation for inverting amplifier topologies,
(7) slew rate measurement gives wrong result with small-signal step — need large signal,
(8) ICMR from DC sweep — use transistor region fields, not CM gain (which is ≈ 0),
(9) SFE-868 from ADE-generated netlist — oa/lib/../ model path only works in ADE interactive mode.
|
| author | Claude Code |
| version | 1.0.0 |
| date | "2026-04-07T00:00:00.000Z" |
| argument-hint | ["error or symptom","e.g. \"SFE-30\" or \"noise in megavolts\""] |
| allowed-tools | Bash(spectre *) Read Write |
Spectre Netlist-Mode Gotchas
Lessons from standalone Spectre simulation (no ADE/Virtuoso), covering vsource syntax,
noise analysis setup, PSF ASCII parsing, and PM calculation for inverting topologies.
1. vsource AC Stimulus: mag= not ac=; type=dc suppresses AC
Problem
Two failure modes, both produce zero AC response with no error:
A) Using ac=1 causes SFE-30 (invalid parameter in native Spectre lang).
B) Any vsource without mag= has zero AC response by default — this is true
whether or not type=dc is specified. The AC small-signal amplitude defaults to 0.
Verified: vsource dc=1.0 and vsource dc=1.0 type=dc both produce (0.0 0.0) at
every frequency. type=dc is not the cause; the absence of mag= is.
Root Cause
ac= is SPICE-compatibility syntax only; native Spectre uses mag= for AC amplitude
- Default AC magnitude for all vsource types is
mag=0 — must be set explicitly
- Typical trap: engineer sets up a DC supply for DC analysis, forgets
mag=1 for AC
Fix
// ✗ WRONG — SPICE-compat only, causes SFE-30
Vip (vip 0) vsource dc=0.9 ac=1
// ✗ WRONG — no mag= → zero AC response (true with OR without type=dc)
VVDDA (VDDA 0) vsource dc=3.3
VVDDA (VDDA 0) vsource dc=3.3 type=dc // same result — still zero AC
// ✓ CORRECT — pure AC stimulus
Vip (vip 0) vsource dc=0.9 mag=1
// ✓ CORRECT — DC supply with AC perturbation (PSRR measurement)
VVDDA (VDDA 0) vsource dc=3.3 type=dc mag=1 srcType=dc
Discovery
Run spectre -h vsource to see all valid parameters. The relevant parameter is:
mag=0 V (Small signal voltage).
Symptom: AC analysis runs without error, all node voltages show (0.0 0.0) complex
values → verify mag=1 is present on the excitation source.
2. Noise oprobe: Parallel Probe, Not Series
Problem
Noise analysis with a series resistor as oprobe gives wildly wrong results (e.g.,
millions of nV/√Hz instead of hundreds). The simulation completes with 0 errors —
no warning that the result is nonsensical.
Root Cause
When oprobe is a series resistor (e.g., 1Ω between vout and CL), Spectre measures
the voltage across that resistor, not the voltage at the output node.
At low frequencies, the CL capacitor is nearly open → almost no current flows through
the series resistor → voltage across it ≈ 0. The "gain" from input to oprobe becomes
tiny (e.g., 7.5e-9 V/V at 10 Hz instead of 118 V/V), and the input-referred noise
is inflated by 1/gain.
Fix: Use a Large Parallel Resistor
// ✗ WRONG — series probe, measures V across resistor (≈ 0 at low freq)
Rout_probe (vout vout_s) resistor r=1
CL_out (vout_s 0) capacitor c=CL
noise_an noise start=10 stop=100e3 dec=20 oprobe=Rout_probe iprobe=Vip
// ✓ CORRECT — parallel probe, negligible loading, measures full vout
CL_out (vout 0) capacitor c=CL
Rout_probe (vout 0) resistor r=1e12
noise_an noise start=10 stop=100e3 dec=20 oprobe=Rout_probe iprobe=Vip
The 1TΩ parallel resistor:
- Sees the full output voltage (vout) across it
- Negligibly loads the circuit (1TΩ ≫ Rout of OTA)
- Spectre correctly reports the output noise voltage spectral density
Verification
Check the "gain" field in noise PSF output at low frequency. It should match your
AC gain (e.g., ~118 V/V for a 41.5 dB amplifier). If gain ≪ 1, the oprobe is wrong.
3. oprobe Must Be a Circuit Element (SFE-1997)
Problem
Using a node name (e.g., oprobe=vout) causes error SFE-1997.
Fix
oprobe requires a circuit element (resistor, port, etc.), not a node name.
Use the 1TΩ parallel resistor pattern from §2 above.
4. PSF ASCII Format (Spectre 23.1)
DC (dc_op.dc)
VALUE
"M5:ids" "A" -9.966831545702541e-06 PROP(
"units" "A"
)
"vout" "V" 3.902670762490046e-01
Pattern: "KEY" "UNIT" <float> — key is lowercase m5:ids, not M5:id.
AC (ac_gain.ac)
VALUE
"freq" 1.000000000000000e+00
"vout" (-1.188805328346738e+02 1.182042857455499e-03)
Pattern: "freq" <float> then "vout" (<real> <imag>) on the next relevant line.
Noise (noise_an.noise)
VALUE
"freq" 1.000000000000000e+01
"M2" ( ... per-device noise contributions ... )
...
"out" 1.491e-03 ← output noise [V/√Hz]
"in" 1.254e-05 ← input-referred noise [V/√Hz]
"gain" 1.189e+02 ← transfer function [V/V]
Critical: The "in" field is in V/√Hz (not V²/Hz). To convert to nV/√Hz:
noise_nv = in_value * 1e9
noise_nv = sqrt(in_value) * 1e9
Verify by checking the TYPE declaration section: "in" "V/sqrt(Hz)".
5. Phase Margin for Inverting Amplifiers
Problem
A 5T OTA (PMOS diff pair + NMOS mirror load) is inverting: vip↑ → vout↓.
The atan2(im, re) phase at DC is ≈ ±180°, not 0°. Naively computing
PM = phase_at_GBW + 180° gives values like 268° or -92°.
Fix: Fold to (0°, 180°)
pm_raw = phase_at_gbw + 180.0
pm = pm_raw % 360.0
if pm > 180.0:
pm = 360.0 - pm
For a single-pole system: PM ≈ 90° (correct). The folding handles both
atan2 branch-cut cases (±180° at DC).
6. Slew Rate Measurement: Large-Signal Step Required
Problem
Using a small step (e.g., 1 mV) in an open-loop OTA transient analysis gives a
misleadingly low slew rate (e.g., 0.08 V/µs instead of expected 6+ V/µs).
Root Cause
Small-signal steps produce a bandwidth-limited response, not a slew-rate-limited
response. The output follows an exponential with time constant τ = 1/(2π·GBW), and
the max dV/dt is limited by bandwidth, not tail current.
For 1 mV input × 119 V/V gain = 119 mV output swing — the OTA never enters slew
limiting because the tail current can track the signal at all times.
Slew limiting occurs when the differential pair is fully steered to one side,
so the output current is clamped to I_tail.
Fix
Use a step large enough to fully steer the diff pair (≫ 2×Vov of input pair):
// ✗ WRONG — small signal, measures bandwidth not slew rate
Vip (vip 0) vsource dc=VICM mag=1 type=pulse \
val0=VICM val1=VICM+1m delay=500n rise=1n fall=1n width=1u period=3u
// ✓ CORRECT — 100mV step fully steers PMOS pair (Vov ≈ 120mV)
Vip (vip 0) vsource dc=VICM mag=1 type=pulse \
val0=VICM val1=VICM+100m delay=500n rise=1n fall=1n width=1u period=3u
Verification
- Theoretical SR = I_tail / CL (e.g., 10 µA / 1 pF = 10 V/µs)
- Measured SR should be within ~50-100% of theoretical
- If SR ≪ theoretical, step amplitude is too small
7. ICMR from DC Sweep: Use Transistor Region, Not CM Gain
Problem
Sweeping VICM (both inputs together) and computing dVout/dVICM gives
common-mode gain ≈ 0, not differential gain. This is correct behavior
for an open-loop OTA without CMFB, but useless for ICMR determination.
Root Cause
ICMR is about the range where the OTA maintains differential gain,
but a CM sweep only exercises CM rejection. Vout stays nearly constant
throughout the valid ICMR range.
Fix: Check Saturation Regions
In the DC sweep PSF, each transistor's region field is saved (with
save M1:oppoint ...). BSIM region codes:
- 0 = off, 1 = linear, 2 = saturation, 3 = subthreshold
ICMR = range where all transistors remain in saturation (region=2):
all_sat = [all(regions[f"M{j}"][i] == 2 for j in range(1, 6))
for i in range(len(vicm_vals))]
icmr_lo = next(v for v, s in zip(vicm_vals, all_sat) if s)
icmr_hi = next(v for v, s in zip(reversed(vicm_vals), reversed(all_sat)) if s)
Output Swing (from Same Data)
Output swing is determined by the output transistors' headroom:
- Vout_min = Vov_M4 (NMOS load enters linear)
- Vout_max = Vtail - |Vov_M2| (PMOS output enters linear)
Read vdsat from dc_op.dc oppoint data:
vov_m4 = abs(op["m4:vdsat"])
vov_m2 = abs(op["m2:vdsat"])
vtail = op["vtail"]
output_swing = (vtail - vov_m2) - vov_m4
8. ADE-Generated Netlist: Model Path Fails for Direct Invocation (SFE-868)
Problem
Running spectre input.scs directly on an ADE-generated netlist fails with SFE-868:
ERROR (SFE-868): Cannot open the input file
'.../oa/smic13mmrf_1233/../models/spectre/ms013_io33_v2p6_7p_spe.lib'
ERROR (SFE-868): Cannot open the input file 'tt'
spectre terminated prematurely due to fatal error.
Root Cause
ADE generates model include paths using an OA-relative pattern:
include ".../oa/smic13mmrf_1233//../models/spectre/ms013_io33_v2p6_7p_spe.lib"
include "tt"
The .. goes up one level from smic13mmrf_1233/ into oa/ — resolving to
oa/models/spectre/ which does not exist. When ADE runs spectre via runSimulation
with +adespetkn=adespe, it uses OA-aware path resolution that bypasses this
filesystem issue. Standalone spectre does plain filesystem .. traversal and fails.
Fix: Patch to Absolute Path Before Running
sed -i \
-e 's|include ".*/oa/smic13mmrf_1233//../models/spectre/ms013_io33_v2p6_7p_spe.lib"|include "/foundry/smic/013mmrf/pdk/20250911/cadence/0.13um_1p3m_8k/models/spectre/ms013_io33_v2p6_7p_spe.lib" section=tt|' \
-e '/^include "tt"$/d' \
input.scs
Or manually replace:
// ✗ ADE-generated (broken for standalone)
include ".../oa/smic13mmrf_1233//../models/spectre/ms013_io33_v2p6_7p_spe.lib"
include "tt"
// ✓ Direct path (works for standalone spectre)
include "/foundry/smic/013mmrf/pdk/20250911/cadence/0.13um_1p3m_8k/models/spectre/ms013_io33_v2p6_7p_spe.lib" section=tt
Warning: Patch Is Overwritten on Re-Netlist
Every vcli sim netlist --recreate regenerates input.scs with the broken ADE path.
Reapply the patch after each re-netlist when using standalone spectre.
Also: vcli sim run Reports Success Even When Spectre Fails This Way
run() returns the resultsDir (non-nil = "success" to Ocean) the moment spectre
is launched, before spectre's exit code is checked. Always verify:
tail -2 <resultsDir>/psf/spectre.out
ls <resultsDir>/psf/*.dc 2>/dev/null || echo "NO DATA FILES — sim failed"
Quick Reference: Spectre Help
spectre -h vsource
spectre -h noise
spectre -h resistor
Notes
- These gotchas apply to netlist mode (
spectre file.scs), not ADE-driven simulation
- In ADE/Virtuoso, noise setup is handled by the GUI and these issues don't arise
- The PSF ASCII format may vary slightly between Spectre versions; always verify with
a small test file
- See also:
skill-shell-gotchas for SKILL/IPC integration issues