SPICE Simulation for Analog Design

Using circuit simulation effectively. Learn what SPICE does (modified nodal analysis, Newton iteration, numerical integration), analysis types (.op, .dc, .ac, .tran, .noise, .tf, .four), netlists and models (.model, subcircuits, op-amp macromodels, vendor models), parameter sweeps (.step), temperature and Monte Carlo analysis, loop-gain probing, convergence problems and fixes, time-step and accuracy settings, and the golden rule — simulation supplements hand analysis and measurement, never replaces them — with Python implementing a tiny SPICE-like backward-Euler transient of an RLC circuit and a Monte Carlo sweep of a divider.

SPICE (Simulation Program with Integrated Circuit Emphasis) solves circuit equations numerically: it finds DC operating points, frequency responses, transients and noise for circuits far too complex for hand analysis. LTspice, ngspice, PSpice and Spectre all descend from it.

Simulation is only as good as its models and your expectations: always predict the result roughly before running it, and investigate when they disagree.

An analogy

SPICE is like a flight simulator: excellent for trying manoeuvres safely and quickly, but only as realistic as its aircraft model — and pilots still need to understand aerodynamics to spot when the simulator is wrong.

Analysis types

Pick the right analysis:

Models

Results depend on models: ideal elements, SPICE primitives (diodes, BJTs Gummel–Poon, MOSFET BSIM), and vendor macromodels for op-amps, regulators and switching controllers. Macromodels may omit noise, temperature, input common-mode limits or phase reversal — read what the model supports. Add parasitics you know exist: capacitor ESR/ESL, PCB inductance, probe capacitance.

Convergence and accuracy

Convergence failures come from floating nodes (add large resistors to ground), ideal switches with infinite edge rates, discontinuous models and unrealistic element values. Fixes: realistic rise times, .ic/.nodeset hints, gmin/source stepping, relaxed tolerances as a last resort. For accuracy, set the maximum time step well below the fastest time constant and check that halving it doesn't change results.

Design verification

Sweep temperature and supply, run Monte Carlo with realistic tolerances and mismatch, and simulate worst-case corners. Measure loop gain with a Middlebrook/Tian probe, check stability with step responses and AC analyses, and use behavioural sources for quick system-level models. Keep simulation files under version control alongside the schematic.

Formulas

Worked example: Time step

Given: Smallest time constant 1 µs

Find: Max time step.

Answer: 10–20 ns

Worked example: Overshoot check

Given: ζ = 0.316

Find: Expected overshoot.

Answer: ≈ 35%

Worked example: Worst-case divider

Given: 5 V, 10 k/10 k, 1%

Find: Max output.

Answer: 2.525 V

Example netlist (run in LTspice/ngspice)

* RLC step response and AC sweep
V1 in 0 PULSE(0 1 0 1n 1n 1 2) AC 1
R1 in n1 2
L1 n1 out 10u
C1 out 0 1u
.tran 10n 60u
.ac dec 100 1k 1Meg
.step param Rval list 2 6.3 20      ; (replace R1 value with {Rval} to sweep damping)
.end

A netlist lists elements with their nodes and values, then analysis commands. .tran gives the step response, .ac the frequency response (using the AC 1 source), and .step sweeps a parameter. Run it in any SPICE to compare with the hand/Python results below — no simulator output is shown here.

A tiny SPICE-like transient and Monte Carlo in Python

import math, random
# A tiny SPICE-like transient (backward Euler) for an RLC step, compared with the analytic answer,
# plus a Monte Carlo of a divider with 1% resistors (what .step / .mc do in SPICE)
R, L, C, V = 2.0, 10e-6, 1e-6, 1.0
dt, T = 1e-8, 60e-6
i = vc = 0.0
peak = 0.0
t = 0.0
while t < T:
    # backward Euler: solve for i_new with vc_new = vc + dt*i_new/C
    i = (i + dt / L * (V - vc)) / (1 + dt * R / L + dt * dt / (L * C))
    vc = vc + dt * i / C
    peak = max(peak, vc)
    t += dt
zeta = R / 2 * math.sqrt(C / L)
os_analytic = math.exp(-math.pi * zeta / math.sqrt(1 - zeta**2))
print(f"zeta = {zeta:.3f}; simulated overshoot {(peak-1)*100:.1f}% vs analytic {os_analytic*100:.1f}%; final {vc:.4f} V")
random.seed(1)
vals = []
for _ in range(20000):
    r1 = 10e3 * (1 + random.gauss(0, 0.01 / 3))   # 1% = 3 sigma
    r2 = 10e3 * (1 + random.gauss(0, 0.01 / 3))
    vals.append(5.0 * r2 / (r1 + r2))
m = sum(vals) / len(vals)
s = math.sqrt(sum((x - m) ** 2 for x in vals) / len(vals))
print(f"Monte Carlo divider: mean {m:.4f} V, sigma {s*1e3:.2f} mV, min {min(vals):.4f}, max {max(vals):.4f}; worst-case bound {5*1.01/(1.01+0.99):.4f} V")
# zeta = 0.316; simulated overshoot 34.9% vs analytic 35.1%; final 0.9991 V
# Monte Carlo divider: mean 2.5000 V, sigma 5.91 mV, min 2.4773, max 2.5234; worst-case bound 2.5250 V

Discretising the RLC equations with backward Euler — the same integration method SPICE can use — reproduces the analytic overshoot (34.9% vs 35.1%; the small difference is backward Euler's numerical damping). The Monte Carlo run of 20 000 dividers with 1% resistors shows a 5.9 mV standard deviation and extremes well inside the 2.525 V worst-case bound — exactly what .mc/.step analyses reveal.

Common mistakes

Applications

Key takeaways

Going further