Validation

Every reference circuit is solved twice: against its closed-form answer, and again in ngspice. The numbers below come straight from the test harness, failures included.

4,169

checks passed

0

failures

13 of 14

circuits agree with ngspice

9

suites

Open issues

  • BJT emitter follower: differs from ngspice. DIVERGES from ngspice-45.2+, 16.1% on Ic(Q1) at 0.000 (spp 0.006219 vs ngspice 0.005217), treat neither number as verified

Reference circuits

Worst error against the closed-form answer, and whether ngspice agrees. Open a circuit for every measurement and its deck.

Resistive dividerDC network.op3.2e-7%Agrees

5 V across 10 kΩ + 4.7 kΩ. Reference: Vo = Vin·R2/(R1+R2). The most boring circuit in the report and the one that would catch a sign or stamping error instantly.

MeasurementClosed formSpice++Error
V(o)1.59864 V1.59864 V3.2e-7%

cross-checked vs ngspice-45.2+, max Δ 0% across 3 signals

.op: compared, largest difference 0%

.op: I(R1), I(R2) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information

55a37a474209ec91Download .op deck

RC low-passFirst-order filter.ac, .tran0.0024%Agrees

R = 1 kΩ, C = 1 µF (fc = 159.15 Hz). References: |H| = 1/√(1+(f/fc)²), ∠H = −atan(f/fc), and the step response 1−e^{−t/τ}.

MeasurementClosed formSpice++Error
|H| at 0.1·fc0.9950370.9950379.9e-8%
|H| at 1·fc0.7071180.7071185.0e-8%
|H| at 10·fc0.09950680.09950689.9e-10%
∠H at fc-44.9991 °-44.9991 °6.4e-8%
V(o) at 1τ0.632121 V0.632106 V0.0024%
V(o) at 5τ0.993262 V0.993262 V4.9e-6%

cross-checked vs ngspice-45.2+, max Δ 0.0043% across 6 signals across 2 analyses

.ac dec 40 1.5915→15915 Hz: compared, largest difference 0%

.tran 0.00001s/0.006s: compared, largest difference 0.0043%

.ac dec 40 1.5915→15915 Hz: I(R1) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information

.tran 0.00001s/0.006s: 1 TS signal(s) have no ngspice counterpart and were NOT cross-checked (I(C1))

.tran 0.00001s/0.006s: I(R1) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information

061610ec7af5647eDownload .ac dec 40 1.5915→15915 Hz deckDownload .tran 0.00001s/0.006s deck

RC high-passFirst-order filter.ac9.9e-8%Agrees

Same R and C, output taken across R. References: |H| = (f/fc)/√(1+(f/fc)²), ∠H = 90° − atan(f/fc).

MeasurementClosed formSpice++Error
|H| at 0.1·fc0.09950070.09950079.9e-8%
|H| at 1·fc0.7070960.7070965.0e-8%
|H| at 10·fc0.9950370.9950379.9e-10%
∠H at fc45.0009 °45.0009 °6.4e-8%

cross-checked vs ngspice-45.2+, max Δ 0% across 3 signals

.ac dec 40 1.5915→15915 Hz: compared, largest difference 0%

.ac dec 40 1.5915→15915 Hz: I(R1) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information

df6453fc4085145eDownload .ac dec 40 1.5915→15915 Hz deck

Series RLC stepSecond-order transient.tran0.00042%Agrees

L = 1 mH, C = 1 µF, R = 10 Ω (ζ = 0.158). References: peak overshoot exp(−πζ/√(1−ζ²)) and a final value of exactly 1 V. Overshoot is read off a discrete record, so a fraction of a percent of the residual is sampling, not solver error.

MeasurementClosed formSpice++Error
peak overshoot0.604679 V0.604677 V0.00042%
final value1.00000 V1.00000 V2.3e-5%

cross-checked vs ngspice-45.2+, max Δ 0.0018% across 5 signals

.tran 1e-7s/0.003s: compared, largest difference 0.0018%

.tran 1e-7s/0.003s: 1 TS signal(s) have no ngspice counterpart and were NOT cross-checked (I(C1))

.tran 1e-7s/0.003s: I(R1) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information

61fbcd5922c5c5a3Download .tran 1e-7s/0.003s deck

Parallel RLC tankResonance.ac1.0e-6%Agrees

1 A AC into R‖L‖C with L = 1 mH, C = 1 µF, R = 10 kΩ. At f0 = 1/(2π√LC) the reactances cancel exactly and |Z| = R; the swept grid puts a point on f0 so the peak is captured rather than straddled.

MeasurementClosed formSpice++Error
|Z| at f010000.0 Ω10000.0 Ω1.0e-6%

cross-checked vs ngspice-45.2+, max Δ 5.7e-6% across 2 signals

.ac lin 401 4832.921210448703→5232.921210448703 Hz: compared, largest difference 5.7e-6%

.ac lin 401 4832.921210448703→5232.921210448703 Hz: I(R1) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information

af8f93cac792c668Download .ac lin 401 4832.921210448703→5232.921210448703 Hz deck

RL first-order stepFirst-order transient.tran0.0018%Agrees

L = 10 mH into R = 100 Ω (τ = 100 µs). Reference: iL(t) = (V/R)(1−e^{−t/τ}). Exercises the inductor branch current, which is a solved unknown rather than a derived one.

MeasurementClosed formSpice++Error
iL at 1τ0.00632121 A0.00632109 A0.0018%
iL at 5τ0.00993262 A0.00993262 A1.3e-6%

cross-checked vs ngspice-45.2+, max Δ 0.0041% across 4 signals

.tran 0.000001s/0.001s: compared, largest difference 0.0041%

.tran 0.000001s/0.001s: I(R1) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information

29ba30956dc557c3Download .tran 0.000001s/0.001s deck

Coupled inductorsMagnetics.ac1.3e-11%Agrees

L1 = 1 H, L2 = 4 H, k = 0.999, secondary effectively open (1 GΩ). Reference: the open-circuit voltage ratio of a coupled pair is exactly k·√(L2/L1), the ideal-transformer turns ratio scaled by the coupling coefficient.

MeasurementClosed formSpice++Error
V(s)/V(p)1.998001.998001.3e-11%

cross-checked vs ngspice-45.2+, max Δ 0% across 6 signals

.ac dec 5 1000→10000 Hz: compared, largest difference 0%

.ac dec 5 1000→10000 Hz: I(RL), I(Rs) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information

6add9f1d2e9dfdabDownload .ac dec 5 1000→10000 Hz deck

Inverting amplifier ×10Op-amp.op0.0112%Agrees

R1 = 1 kΩ, R2 = 10 kΩ, Vin = 0.3 V. Reference is the IDEAL −R2/R1 result; the residual error is the real finite-gain term (1+R2/R1)/A0 ≈ 1.1e−4 with A0 = 1e5, so a near-zero error here would actually indicate a missing non-ideality.

MeasurementClosed formSpice++Error
V(o)-3.00000 V-2.99966 V0.0112%

cross-checked vs ngspice-45.2+, max Δ 0% across 4 signals

.op: compared, largest difference 0%

this circuit contains an ideal-ish OPAMP element, which deck.ts transcribes into ngspice as an equivalent B-source macromodel, agreement there validates the two SOLVERS on the same device equations, not two independent device models

.op: 6 TS signal(s) have no ngspice counterpart and were NOT cross-checked (@U1[gloc], @U1[region], @U1[vdiff], @U1[vsat], V(OPout:U1), V(OPx:U1))

.op: I(R1), I(R2) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information

12c464d3923529b9Download .op deck

Non-inverting amplifier ×10Op-amp.op0.0103%Agrees

R1 = 1 kΩ to ground, R2 = 9 kΩ feedback, Vin = 0.4 V. Reference: Vo = (1 + R2/R1)·Vin, again ideal, so the residual is the finite-gain term.

MeasurementClosed formSpice++Error
V(o)4.00000 V3.99959 V0.0103%

cross-checked vs ngspice-45.2+, max Δ 0% across 4 signals

.op: compared, largest difference 0%

this circuit contains an ideal-ish OPAMP element, which deck.ts transcribes into ngspice as an equivalent B-source macromodel, agreement there validates the two SOLVERS on the same device equations, not two independent device models

.op: 6 TS signal(s) have no ngspice counterpart and were NOT cross-checked (@U1[gloc], @U1[region], @U1[vdiff], @U1[vsat], V(OPout:U1), V(OPx:U1))

.op: I(R1), I(R2) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information

44890956ecfd2f70Download .op deck

Sallen–Key low-passActive filter.ac0.103%Agrees

Equal-R/C unity-gain Sallen-Key, R = 1 kΩ, C = 159.155 nF (fc ≈ 1 kHz, Q = 0.5). Reference is the exact transfer function ω0²/((ω0²−ω²) + j·2ω0ω) with ω0 = 1/RC, not a corner-frequency approximation.

MeasurementClosed formSpice++Error
|H| at 0.1·fc0.9900990.9900995.0e-5%
|H| at 1·fc0.5000000.4999920.0016%
|H| at 10·fc0.009900980.009890770.103%

cross-checked vs ngspice-45.2+, max Δ 0.745% across 5 signals

.ac dec 30 10→100000 Hz: compared, largest difference 0.745%

this circuit contains an ideal-ish OPAMP element, which deck.ts transcribes into ngspice as an equivalent B-source macromodel, agreement there validates the two SOLVERS on the same device equations, not two independent device models

.ac dec 30 10→100000 Hz: 2 TS signal(s) have no ngspice counterpart and were NOT cross-checked (V(OPout:U1), V(OPx:U1))

.ac dec 30 10→100000 Hz: I(R1), I(R2) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information

9a0c2b618a8fc253Download .ac dec 30 10→100000 Hz deck

Diode I–V sweepNonlinear device.dc0.00019%Agrees

1N4148-class diode in series with 100 Ω, swept 0→1 V. Reference: Newton solution of Vin = I·(R+Rs) + N·Vt·ln(I/Is + 1) using the model's own Is/N/Rs, the same physics solved by a scalar root-find instead of MNA.

MeasurementClosed formSpice++Error
V(diode) at Vin=0.40 V0.398345 V0.398345 V1.9e-8%
V(diode) at Vin=0.60 V0.551581 V0.551581 V9.7e-9%
V(diode) at Vin=0.80 V0.613479 V0.613479 V1.3e-7%
V(diode) at Vin=1.00 V0.643765 V0.643766 V0.00019%

cross-checked vs ngspice-45.2+, max Δ 0.026% across 3 signals

.dc V1 0→1: compared, largest difference 0.0258%

.dc V1 0→1: 7 TS signal(s) have no ngspice counterpart and were NOT cross-checked (@D1[gd], @D1[id], @D1[rd], @D1[region], @D1[vd], I(D1), V(Dint:D1))

.dc V1 0→1: I(R1) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information

50f6c1b2675ce604Download .dc V1 0→1 deck

BJT emitter followerNonlinear device.op0.0072%Diverges

NPN, Vb = 6 V, RE = 1 kΩ, Vcc = 12 V, Early voltage set to infinity so the closed form is exact. Reference: Newton solution of Ie = Is(e^{Vbe/NeVt}−1)(1+1/Bf) = (Vb−Vbe)/RE.

MeasurementClosed formSpice++Error
V(e)5.24260 V5.24223 V0.0072%
Ie0.00524260 A0.00524223 A0.0072%

DIVERGES from ngspice-45.2+, 16.1% on Ic(Q1) at 0.000 (spp 0.006219 vs ngspice 0.005217), treat neither number as verified

.op: compared, largest difference 16.1%

.op: 17 TS signal(s) have no ngspice counterpart and were NOT cross-checked (@Q1[beta], @Q1[gm], @Q1[gmu], @Q1[go], @Q1[gpi], @Q1[ib], @Q1[ic], @Q1[ie], @Q1[region], @Q1[ro], @Q1[rpi], @Q1[vbc], @Q1[vbe], @Q1[vce], I(Q1), Ib(Q1), Ie(Q1))

.op: I(RE) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information

6c913e221fb98100Download .op deck

NMOS common sourceNonlinear device.op5.0e-8%Agrees

Level-1 NMOS, Vto = 1 V, Kp = 1 mA/V², W/L = 10, λ = 0, Vgs = 2.5 V, RD = 500 Ω, Vdd = 10 V. Saturation is verified (Vds > Vov), so Id = ½·Kp·(W/L)·Vov² and Vd = Vdd − Id·RD are exact square-law.

MeasurementClosed formSpice++Error
Id0.0112500 A0.0112500 A0%
V(d)4.37500 V4.37500 V5.0e-8%

cross-checked vs ngspice-45.2+, max Δ 0% across 5 signals

.op: compared, largest difference 0%

.op: 11 TS signal(s) have no ngspice counterpart and were NOT cross-checked (@M1[gds], @M1[gm], @M1[gmb], @M1[id], @M1[region], @M1[ro], @M1[vbs], @M1[vds], @M1[vgs], @M1[vth], I(M1))

.op: I(RD) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information

fe209547a017ce11Download .op deck

VCVS gain blockControlled source.op0%Agrees

E-element with gain 4 driving 1 kΩ. Reference: Vo = 4·Vin exactly. A pure linear-algebra check on the extra branch equation a VCVS adds to the MNA matrix.

MeasurementClosed formSpice++Error
V(o)2.00000 V2.00000 V0%

cross-checked vs ngspice-45.2+, max Δ 0% across 3 signals

.op: compared, largest difference 0%

.op: I(RL) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information

7984be3e35157938Download .op deck

Suites

SuitePassedFailed
Tier-A ECE acceptance, every textbook class vs closed form AND vs ngspice109 / 1090
Independent numerical oracle, random networks vs a from-scratch MNA522 / 5220
Analytic golden, Sallen-Key/MFB H(jω), RLC regimes, R-2R, rectifier Fourier317 / 3170
Circuit-theory & device-physics laws23 / 230
Conservation laws, KCL / Tellegen / energy / charge / passivity1,200 / 1,2000
Metamorphic invariance, series/parallel/Y-Δ/source transform/relabel1,558 / 1,5580
Cross-method consistency, .op = .tran(∞), .ac = DFT(.tran), integration order338 / 3380
Loop gain & stability margins, Middlebrook injection vs closed form21 / 210
Second opinion, the cross-engine agreement machinery itself81 / 810

Known limitations

MOSFETs are Level 1 (Shichman-Hodges)
Square law with body effect, channel-length modulation, a subthreshold region and overlap capacitances. No BSIM, EKV, PSP or foundry PDKs. Right for board-level and coursework analog, wrong for on-chip design. Model cards that declare another level are rejected, not silently degraded.
BJTs are Ebers-Moll with Early voltage
Forward and reverse injection, emission coefficients, Vaf and Var, ohmic resistances, depletion capacitances and standard temperature scaling. Not Gummel-Poon: no high-injection knee and no quasi-saturation.
No RF or distributed elements
No S-parameters, Touchstone import, Smith charts or transmission lines. Lumped LC matching networks simulate correctly; anything where geometry matters is not modelled.
No harmonic balance or periodic steady state
Steady state is reached by running .tran until the transient decays. Fine for the circuits here, slow for a high-Q oscillator or a PLL.
Noise analysis is not cross-checked
The ngspice adapter does not map noise vectors back to Spice++ signal names, so .noise results are not compared rather than compared against an empty set.
Switching converters are not in this reference set
Buck, boost and buck-boost converters simulate with the switch and VDMOS models and have their own test suite, but none of them is among the reference circuits cross-checked against ngspice on this page.
THD below about 0.05% is a sampling limit
A .tran record is adaptively stepped and must be resampled before the FFT. The interpolation sets a floor near −66 dBc at 100 samples per cycle, so smaller THD figures describe the sampling, not the circuit.
Op-amps are the same macromodel on both sides
The OPAMP element is transcribed into the ngspice deck as an equivalent behavioural network, so agreement on an op-amp circuit validates the two solvers, not two independent op-amp models.
Parameter sweeps are compared at nominal values
The ngspice deck compiler emits no .step, so a swept netlist is cross-checked at its nominal values only.
Differences under 1 µV or 1 nA count as zero
Both engines add GMIN for conditioning, so a branch carrying no current reads 0 A in one and a few picoamps in the other. Comparing those would report a false divergence, so signals that live entirely below these levels are not cross-checked.
Resistor currents are excluded from agreement counts
This ngspice build does not expose resistor currents, so they are derived from node voltages on that side and left out of every count rather than inflating it.

Reproduce it

npm install
npx tsx scripts/gen_validation.ts --full

Generated 2026-07-28 05:33:06 UTC, commit 4b70d76 (uncommitted changes), engine spp-ts-1.0, oracle ngspice-45.2+.