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.
Lessons from standalone Spectre simulation (no ADE/Virtuoso), covering vsource syntax, noise analysis setup, PSF ASCII parsing, and PM calculation for inverting topologies.
mag= not ac=; type=dc suppresses ACTwo 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.
ac= is SPICE-compatibility syntax only; native Spectre uses for AC amplitudemag=mag=0 — must be set explicitlymag=1 for AC// ✗ 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
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.
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.
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.
// ✗ 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:
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.
Using a node name (e.g., oprobe=vout) causes error SFE-1997.
oprobe requires a circuit element (resistor, port, etc.), not a node name. Use the 1TΩ parallel resistor pattern from §2 above.
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_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_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 # ✓ CORRECT: V/√Hz → nV/√Hz
noise_nv = sqrt(in_value) * 1e9 # ✗ WRONG: treats as V²/Hz
Verify by checking the TYPE declaration section: "in" "V/sqrt(Hz)".
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°.
pm_raw = phase_at_gbw + 180.0 # standard formula
pm = pm_raw % 360.0 # normalize to [0, 360)
if pm > 180.0:
pm = 360.0 - pm # fold to [0, 180]
For a single-pole system: PM ≈ 90° (correct). The folding handles both
atan2 branch-cut cases (±180° at DC).
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).
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.
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
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.
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.
In the DC sweep PSF, each transistor's region field is saved (with
save M1:oppoint ...). BSIM region codes:
ICMR = range where all transistors remain in saturation (region=2):
# Parse "M1:region" ... "M5:region" from dc_sweep PSF
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 is determined by the output transistors' headroom:
Read vdsat from dc_op.dc oppoint data:
vov_m4 = abs(op["m4:vdsat"]) # NMOS load overdrive
vov_m2 = abs(op["m2:vdsat"]) # PMOS diff pair overdrive
vtail = op["vtail"]
output_swing = (vtail - vov_m2) - vov_m4
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.
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.
# Find the correct path (one level up from oa/):
# .../0.13um_1p3m_8k/oa/smic13mmrf_1233/../ → .../0.13um_1p3m_8k/oa/ (wrong)
# correct: .../0.13um_1p3m_8k/models/spectre/...
# Replace the two broken lines with a direct include + section:
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
Every vcli sim netlist --recreate regenerates input.scs with the broken ADE path.
Reapply the patch after each re-netlist when using standalone spectre.
vcli sim run Reports Success Even When Spectre Fails This Wayrun() 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
# ✓ "spectre completes with 0 errors"
# ✗ "spectre terminated prematurely due to fatal error"
ls <resultsDir>/psf/*.dc 2>/dev/null || echo "NO DATA FILES — sim failed"
spectre -h vsource # all vsource parameters (mag, dc, type, ...)
spectre -h noise # noise analysis options (oprobe, iprobe, ...)
spectre -h resistor # resistor element parameters
spectre file.scs), not ADE-driven simulationskill-shell-gotchas for SKILL/IPC integration issues