ngspice Circuit Simulation Guide
SPICE Fundamentals
Netlist Structure
* Title line (first line, always a comment)
<components>
<directives>
.END
.ENDmust be last line. No statements after it.+at start of line continues previous statement.- Comments:
*(full line) or$(inline). Note:;is not the inline comment character in ngspice (that's LTspice).
Component Syntax
<ref> <node+> <node-> <value>
R1 in out 10k
C1 out 0 100n
V1 in 0 AC 1 PULSE(0 5 0 1n 1n 0.5m 1m)
Value Notation — CRITICAL
| Suffix | Meaning | Value |
|---|---|---|
| f | femto | 1e-15 |
| p | pico | 1e-12 |
| n | nano | 1e-9 |
| u | micro | 1e-6 |
| m | milli | 1e-3 |
| k | kilo | 1e3 |
| MEG | mega | 1e6 |
| G | giga | 1e9 |
| T | tera | 1e12 |
M means milli, not mega. Use MEG for 1e6.
1M = 0.001, not 1000000. Unrecognized suffix letters are silently ignored:
no error, just a wrong value.
Waveform Sources
PULSE(Vinitial Vpulse Tdelay Trise Tfall Ton Tperiod Ncycles)
SINE(Voffset Vamp Freq Td Theta Phi Ncycles)
EXP(V1 V2 Td1 Tau1 Td2 Tau2)
SFFM(Voff Vamp Fcar MDI Fsig)
PWL(t1 v1 t2 v2 ...)
Directives
.tran 5m $ transient, 5ms stop
.tran 0 5m 0 10u $ tstep, tstop, tstart, tmaxstep
.ac dec 200 10 100k $ AC sweep, 200pts/decade, 10Hz-100kHz
.dc V1 0 5 0.01 $ DC sweep V1, 0-5V, 10mV step
.op $ DC operating point
.noise V(out) V1 dec 200 10 100k $ noise analysis
.tf V(out) V1 $ DC transfer function
.include /path/to/model.lib $ include library
.ic V(node)=1.5 $ initial conditions (used with UIC)
.nodeset V(node)=1.5 $ hint for DC operating point solver
.ic forces node voltages at t=0 (use with .tran ... UIC). .nodeset is a solver hint only.
.MEAS Syntax
.meas TRAN vmax MAX V(out)
.meas TRAN vpp PP V(out)
.meas TRAN trise TRIG V(out) VAL=0.1 RISE=1 TARG V(out) VAL=0.9 RISE=1
.meas AC fc WHEN mag(V(out)/V(in))=0.707
.meas AC gain_1k FIND V(out) AT=1k
.meas TRAN avg_out AVG V(out) FROM=1m TO=5m
.meas TRAN energy INTEG V(out)*I(R1)
.meas TRAN slope DERIV V(out) AT=2m
.meas TRAN check param='tdiff < vout_diff ? 1 : 0'
Additional measurement types vs LTspice:
MIN_AT,MAX_AT— return the time/freq of the min/max, not the value.DERIV— derivative at a point or when a condition is met.param='expression'— evaluate an expression using.paramvalues and prior.measresults.par('expression')— inline algebraic expression on any output variable (uses B source syntax internally).SPanalysis type for spectrum (fft) measurements (viameascommand, not.measline).
Important behavior:
- RISE/FALL/CROSS numbering starts at 1, not 0.
- If TRIG event never occurs, measurement silently fails.
.measis refused when batch mode (-b) is combined with-r rawfile, which is howrun_experimentsinvokes ngspice. ngspice printsNo .measure possible in batch mode (-b) with -r rawfile set!and computes nothing. Recovery: move the measurement into a.control ... run ... .endcblock as the dot-lessmeascommand, e.g.meas tran vmax MAX V(out). A dotted.measinside.controlis not a valid command and silently does nothing. The result prints to the run's log.paramandparare not available inside.controlblocks — useletinstead.
General notes
- Node "0" is ground. Using
GNDwithout.global GNDor tying it to 0 creates a floating node — no error, wrong results. - MOSFET requires 4 terminals:
M1 d g s b— ngspice does not auto-connect bulk to source (LTspice does). - Impedance ratios: Beyond ~1e16 cause numerical issues (64-bit doubles).
- Parameter sweep: ngspice has no native
.step(that is LTspice syntax). The MCP runs parametric sweeps throughrun_experimentsvariations(anassigngrid or arandomrule), which stage and simulate one deck per case. For a hand-written deck outside the MCP, use a.controlblock with analter/loop. A.stepline in a deck handed torun_experimentsis rejected with a pointer tovariations.
ngspice-Specific
Parameters and Expressions
.param Rval=10k
.param fc={1/(2*pi*Rval*Cval)}
.param combined='Rval + 10'
.func myfn(x) {x*2}
- Expressions in braces
{expr}or single quotes'expr'— both work. - Expressions without delimiters work only when spaces are absent:
.param c=a+123OK,.param c = a + 123fails silently (assigns only first token). - Self-referential params fail silently:
.param x = {x+3}does not work. - Parameter names: must start with alpha; may contain
! # $ % [ ] _. Cannot use reserved words:time,temper,hertz,not,and,or,div,mod,sqr,sqrt,sin,cos,exp,ln,log,log10,arctan,abs,pwr,defined. - String-valued params supported with limited concatenation.
Three separate expression parsers exist in ngspice — this is a known source of confusion:
- Front-end (
.param, brace expressions) — evaluated at netlist expansion time - B source / behavioral — evaluated during simulation (no braces)
.controlblock — operates on its own vectors/variables
These have slightly different function sets and precedence rules. Braces {...} are "compile-time"; bare expressions in B sources are "run-time".
Operator precedence (.param expressions):
| Op | Prec | Description |
|---|---|---|
! |
1 | unary NOT |
**, ^ |
2 | power |
* |
3 | multiply |
/, %, \ |
3 | divide, modulo, integer divide |
+, - |
4 | add, subtract |
==, !=/<> |
5 | equality |
<=, >=, <, > |
5 | comparison |
&& |
6 | boolean AND |
|| |
7 | boolean OR |
c ? x : y |
8 | ternary |
^ behavior depends on compatibility mode:
- Default (
hscompat):x^y=pow(fabs(x), y)for x>0; rounds y for x<0; 0 for x=0 - LTspice compat (
lt):x^y=pow(x, y)if y is close to integer; else 0 for x<0
Built-in functions (.param):
- Trig:
sin,cos,tan,asin,acos,atan,arctan - Hyperbolic:
sinh,cosh,tanh,asinh,acosh,atanh - Exp/log:
exp,ln,log(base e),log10 - Power:
sqrt,pow(x,y),pwr(x,y)(=pow(fabs(x),y)) - Rounding:
nint(nearest, half to even),int(toward 0),floor,ceil - Selection:
min,max,sgn - Conditional:
ternary_fcn(x,y,z)(=x ? y : z) - Statistical:
gauss(nom,rvar,sigma),agauss(nom,avar,sigma),unif(nom,rvar),aunif(nom,avar),limit(nom,avar) - Special:
var(name)(interpreter variable),vec(name)(vector value)
Behavioral Sources (B sources)
B1 out 0 V=<expression>
B2 out 0 I=<expression> [tc1=x] [tc2=x] [temp=x]
Conditional: uses ternary cond ? true : false, not IF() (that's LTspice). Put a space before ? so the parser doesn't confuse it with other tokens. Nested ternaries need explicit parentheses.
Available functions (B source context): cos, sin, tan, acos, asin, atan, cosh, sinh, acosh, asinh, atanh, exp, ln, log, log10, abs, sqrt, u (unit step), u2 (ramp 0-1), uramp, floor, ceil, min, max, pow, **, pwr, ^, i(device)
Special variables: time (transient), temper (circuit temp in C), hertz (AC frequency). time is zero during AC; hertz is zero during transient.
Piecewise linear in B source:
Bdio 1 0 I = pwl(v(A), 0,0, 33,10m, 100,33m, 200,50m)
Blimit b 0 V = pwl(v(1), -4,0, -2,2, 2,4, 4,5, 6,5)
x values must be monotonically increasing — non-monotonic stops execution. Can use time or expressions as the independent variable.
Important behavior:
exp()is internally capped at argument=14 — beyond that it becomes linear (for convergence).log/ln/sqrtof negative values usefabs()automatically — no error, may give unexpected results.- Division by zero or
log(0)causes an error. - B source
par('expression')can be used in.plot/.printoutput lines. - Non-linear R/L/C can be synthesized from B sources using the subcircuit template pattern (see ngspice manual 5.1).
Subcircuits
.subckt myfilter in out rval=100k cval=100nF
R1 in p1 {2*rval}
C1 p1 0 {cval}
.ends myfilter
X1 input output myfilter rval=1k cval=1n
Key differences from LTspice:
- Parameters on
.subcktline do not needparams:keyword — justname=valueafter nodes. .libbehavior depends on the compatibility mode, and no single.libform works in every mode. For unconditional whole-file inclusion use.include <file>, which works in every ngspice mode (verified on ngspice-42). If you use.lib:- ngspice-native modes (
hsa, plain default):.lib <file> <section>loads the named.lib section … .endlblock; a bare.lib <file>with no section does not load the file's models. - This server's default
kiltpsa(a PSPICE-family mode) is the reverse: a bare.lib <file>loads an unsectioned file, but a sectioned.lib <file> <section>(the PDK corner-select form) is split by thelt/pstokens into two plain includes that drop the section, and the run fails withcould not find include file. Set[simulator] ngbehavior = "hsa"inltspice-mcp.toml(orLTSPICE_MCP_NGBEHAVIOR=hsa) and restart the server to parse the section;run_experimentsemits this hint when a failed run matches the pattern.
- ngspice-native modes (
.paraminside subcircuits is local scope (masks globals). Nesting up to 10 levels.- Subcircuit and model names are global — must be unique across the entire netlist.
.save Directive
.save V(out) I(Vin) $ save only these signals
.save @m1[id] @m1[gm] $ save internal device parameters
.save all @m2[vdsat] $ save defaults plus extras
- Without
.save, all node voltages and source currents are saved (can create huge files). - Adding even one
.saveline drops all defaults — only listed signals are saved. - To keep defaults plus extras:
.save all @m2[vdsat] .save @r1[i]for resistor current (not available viaI()syntax).- Read internals back as named numbers (no rawfile parsing, no
.control): on a.dc/.transweep, ananalyze_resultswaveformrecipe over['m1.gm','m1.id']gives the gm/ID table in one call; avaluerecipe (expr='m1.gm', at=...) reads one point;operating_point(device='M1')gives the bias snapshot of one device. Address an internal by them1.gmshorthand or the literal@m1[gm](the tools resolve thev()/i()wrapping and subcircuit paths). This.dc+.save+ read sequence is how to build a gm/ID characterization table; see thespice://guideresource.
.control / .endc Blocks
ngspice has a built-in scripting language for post-simulation analysis:
.control
run $ execute the simulation
plot V(out) $ plot results
print V(out) $ print to stdout
let vmax = maximum(V(out)) $ create vector
set filename = "results.csv" $ create string variable
write $filename V(out) I(Vin) $ save to rawfile
wrdata output.txt V(out) $ save as CSV-like text
.endc
Scripts without write/wrdata. A .control block replaces ngspice's
default raw output: the script runs instead of the -r rawfile write, so a
script with no write/wrdata produces no rawfile for the analysis tools
even though the run completes. When the deck has exactly one .control block
and no write/wrdata anywhere, run_experiments inserts a write <rawpath>
just before .endc, so results still reach the waveform/signal_stats
recipes. That inserted line is a bare write and captures only the
current/last plot. A script that runs several analyses, or writes per
iteration inside a Monte Carlo loop (see below), needs its own write/wrdata
for each. If the script already contains any write/wrdata, nothing is
inserted.
Variables vs vectors — a critical distinction:
setcreates string/shell variables:set myvar = "hello"— access with$myvarletcreates numeric vectors:let x = 2*pi— access with$&xto convert to number- Mixing them up causes silent failures.
$¶mdereferences a circuit.paraminto a control variable.
Control structures:
$ While loop
while condition
...
end
$ Repeat
repeat 10
...
end
$ Foreach
foreach val 1 2 3 4 5
echo value is $val
end
$ If/else
if condition
...
else
...
end
$ Also: dowhile, break [n], continue [n], label, goto
foreach values are space-separated (no commas). Can use variable expansion: foreach var $myvariable.
Key commands: run, plot, print, let, set, write, wrdata, alter, altermod, echo, meas, linearize, fft, define, source
Monte Carlo
ngspice has no .mc directive. Monte Carlo is done via .control block loops:
.control
define unif(nom, var) (nom + nom*var * sunif(0))
define gauss(nom, var, sig) (nom + nom*var/sig * sgauss(0))
let mc_runs = 100
let run = 1
dowhile run <= mc_runs
alter c1 = unif(1e-09, 0.1)
alter r1 = gauss(10k, 0.05, 3)
tran 1u 1m
$ ... store/process results ...
let run = run + 1
end
.endc
Random functions:
sunif(0)— single uniform random number in [-1, 1]sgauss(0)— single Gaussian random number (mean=0, stddev=1).options seed=<value>orseed=random— set random seed
Statistical functions (.param context):
gauss(nom, rvar, sigma)— Gaussian, relative variationagauss(nom, avar, sigma)— Gaussian, absolute variationunif(nom, rvar)— uniform, relativeaunif(nom, avar)— uniform, absolutelimit(nom, avar)— binary: nom+avar or nom-avar
Using .param statistical functions requires multiple ngspice runs (each run re-evaluates). Using .control loops with alter varies within a single run.
.options Flags
General:
| Flag | Effect |
|---|---|
SEED=val|random |
Random number seed |
TEMP=x |
Operating temperature (default 27C) |
TNOM=x |
Nominal temperature (default 27C) |
SAVECURRENTS |
Auto-save all device terminal currents |
KLU |
Use KLU matrix solver (faster for large MOS circuits) |
INTERP |
Interpolate output to fixed TSTEP grid |
Convergence:
| Flag | Default | Effect |
|---|---|---|
RELTOL |
0.001 | Relative tolerance |
ABSTOL |
1e-12 | Absolute current tolerance |
VNTOL |
1e-6 | Absolute voltage tolerance |
GMIN |
1e-12 | Minimum junction conductance |
ITL1 |
100 | DC iteration limit |
ITL4 |
10 | Transient iteration limit |
METHOD |
trap | Integration method: trap or gear |
MAXORD |
2 | Max integration order for Gear (2-6) |
TRTOL |
7 | Transient error tolerance factor |
XMU |
0.5 | Trapezoidal damping (reduce slightly to suppress ringing) |
Matrix conditioning:
.options rshunt=1e12 $ resistor from every node to ground
.options rseries=1e-4 $ series resistor on every inductor
.options cshunt=1e-13 $ capacitor from every node to ground
Use rshunt for "no DC path to ground" errors. Use rseries when inductors parallel voltage sources cause OP failure. Use cshunt for oscillation/noise issues.
AUTOSTOP — stops transient analysis after all .meas conditions are satisfied.
XSPICE
Mixed-signal simulation with code models. A-devices (the A prefix) are the XSPICE code-model primitives, and XSPICE is enabled by default in the official/stock ngspice builds — no custom build needed. Only the experimental XSPICE_EXP extras (e.g. the capacitor/inductor code models) require a custom build.
A1 [in] [out] lut1
.model lut1 d_lut(rise_delay=1n fall_delay=2n input_load=0.5p
+ table_values="0110")
Digital device types: d_and, d_or, d_nand, d_nor, d_xor, d_inverter, d_buffer, d_flop, d_latch, d_lut, etc.
Digital nodes use [name] bracket syntax for buses.
Key Differences from LTspice
- Inline comments:
$not; - Conditional in B source: ternary
?:notIF() - MOSFET bulk terminal: required (4 pins), not auto-connected
- GND node: must explicitly tie to 0 or use
.global - No
startupkeyword in.tran - A-devices are the XSPICE code-model primitives (the
Aprefix) — available in stock/official builds (XSPICE enabled by default); onlyXSPICE_EXPextras need a custom build - No Unicode mu issue — ngspice preserves
uas-is .funcdefinitions cannot be recursive — causes hang, not error- Different
.rawfile format (all doubles vs LTspice mixed precision) .backannois LTspice-only — ngspice rejects it (unimplemented dot command '.backanno') and aborts the run. Probe currents with.options savecurrents(or.probe) instead