Reference manual for ngspice-jit, a fork of
ngspice (ngspice-46 base) that JIT-compiles behavioral
foundry R/C into native OSDI devices at parse time. A command-by-command reference with full
cp_coms catalog coverage; every example is executed against the ngspice-jit build and its
real output shown verbatim. Fork-specific features (OSDI codegen, regex device/vector selection, the
devices command) are flagged as build extensions where they appear.
Notation. Each entry carries a mode badge:
batch + ui the command works both in batch (ngspice -b)
and interactively; ui only it exists only inside
.control … .endc or the interactive prompt. The badge is taken directly from
the spiceonly field of the command table in the source, so it is authoritative.
This chapter assumes you know what a SPICE netlist is; it is the reference for ngspice’s specific syntax rules — the ones that bite. Individual commands are in §3.
A deck is a title line, a body of cards, and .end:
.save) on line 1 silently does nothing. Start every deck with a
comment or title line.* is a full-line comment; $ (with a
preceding space) starts an in-line comment to end of line.+ is appended to the previous card.VDD = vdd)..end terminates the deck; text after it is ignored.Every instance card begins with a letter that selects the device type, followed by the instance name, its terminal nodes, and a value or model plus parameters:
Rload out 0 1k $ R + name; nodes out,0; value 1k M1 d g s b nch w=1u l=0.18u $ M + name; 4 nodes; model; params X1 in out amp $ X = subcircuit call (see .subckt)
| letter | device | letter | device |
|---|---|---|---|
R C L | resistor, capacitor, inductor (K=mutual) | V I | independent voltage / current source |
D Q | diode, BJT | M J Z | MOSFET, JFET, MESFET |
E G | VCVS, VCCS (linear controlled) | F H | CCCS, CCVS |
B | arbitrary (nonlinear) source V=/I= | X | subcircuit call → .subckt |
S W | voltage / current switch | A | XSPICE code model |
Nodes are named or numbered; 0 (or gnd) is ground and is global.
Names are case-insensitive. A node reachable everywhere without wiring is declared with
.global; a node inside a subcircuit instance is addressed
hierarchically as Xinst.node (see .subckt).
A number may carry a scale suffix (case-insensitive); trailing letters after the suffix are ignored
(1kOhm = 1k). The classic trap: m is milli, not mega —
mega is meg.
T | G | Meg | k | m | u | n | p | f | a |
|---|---|---|---|---|---|---|---|---|---|
| 1e12 | 1e9 | 1e6 | 1e3 | 1e-3 | 1e-6 | 1e-9 | 1e-12 | 1e-15 | 1e-18 |
ngspice -b syntax.cir (meg vs m, and + continuation)* first line is the TITLE and is ignored .options savecurrents V1 in 0 DC 1 R1 in 0 1meg $ 1,000,000 ohm -> I = 1 uA R2 in 0 1k $ 1,000 ohm -> I = 1 mA R3 in 0 1m $ 0.001 ohm (m = MILLI!) -> I = 1000 A Rcont in 0 + 2k $ line continued with '+' -> I = 0.5 mA .op .control run print @R1[i] @R2[i] @R3[i] @Rcont[i] .endc .end
@r1[i] = 1.000000e-06 (1 V / 1 MΩ) @r2[i] = 1.000000e-03 (1 V / 1 kΩ) @r3[i] = 1.000000e+03 (1 V / 1 mΩ ← m is milli!) @rcont[i] = 5.000000e-04 (1 V / 2 kΩ, value read off the + line)
m vs meg. A “1 mΩ” typo for a 1 MΩ resistor
(1m instead of 1meg) is off by 10⁹ — the run above draws 1000 A instead
of 1 µA. This is the single most common SPICE numeric error.Expressions in a value are wrapped in { … } or ' … ' and are evaluated
at parse time (before the run) over .param/.func
values and constants — they cannot see simulation results. Run-time math on vectors is done instead with
let/define inside
.control (see §2).
A deck mixes two sub-languages: dot-cards (the netlist — .tran, .model,
.param, …) processed by the parser, and commands inside a
.control … .endc block (run, let, print, loops)
executed by the interpreter. Which one runs a given verb, and when, is the source of most surprises —
covered in §2 · .control vs. a pure dot-card deck (why ngspice
differs from Eldo/HSPICE, when you need .control, and what works without it).
Cross-cutting behaviors that trip people up; command entries link back here.
ngspice -b), an
analysis card only runs if the deck also produces output — a .control { run … }
block, or a .print/.plot/.four line. A deck with just
.pz (or .sens, .ac, …) and nothing else prints
“no .plot, .print, or .fourier lines in batch mode; no simulations run!” and exits without
computing anything. .op, .tf and .four are exceptions — they emit
their result directly. Interactively (or with -r) this restriction does not apply.Which nodes end up in the raw file, and whether a file is even produced, depends on
three interacting choices: the -r command-line flag, whether you used
.save/save, and whether a .control block calls write.
The table is the mental model; the examples verify it.
| command | mode | behavior |
|---|---|---|
.save node … | batch + ui | restricts the -r raw file to the listed vectors (plus the time scale) |
save node … | batch + ui | same restriction, issued as a command (e.g. inside .control) |
write file expr … | ui only | writes selected vectors to its own file; has no dot-card form |
.probe / probe | batch | adds device currents to the raw file; no-op without -r |
Inside .control, ngspice has two separate namespaces that look alike but are not:
vectors (numeric simulation data — nodes, currents, anything you compute with let)
and shell variables (text/flags set with set). The $ / $&
sigils and the echo/print split all come down to which namespace you mean.
echo is literal,
print evaluates. echo prints its words verbatim (expanding only
$shellvar); it will happily print v(out) or @R1[resistance] as
plain text. print evaluates them as vectors/expressions. If a value comes out as its own
name, you used echo where you needed print.These are control-mode constructs — they live inside a .control … .endc block
(or the interactive prompt), never as dot-cards. They turn a netlist into a small script: sweep a value,
retry until convergence, branch on a result. Each loop body is closed by end. They rely on
the shell variables and vectors covered in the Vectors chapter (let, set,
$var, $&vec).
foreach/while loop that re-runs an
analysis is not the same as .dc/.step: the loop runs a
full separate analysis each pass (you control saving/plotting), whereas a sweep produces one dataset
indexed by the swept variable. Use a loop when each pass needs different topology/parameters or
its own output file; use a sweep for one parametric curve.Commands that load, run, and modify the circuit between runs — the basis of interactive
what-if sweeps without re-parsing the deck. run starts the defined analysis;
resume continues a paused one; reset reloads the circuit to its initial
state; source file loads another deck. The two most useful for scripting are
alter and altermod.
These dot-cards give a netlist its structure: named parameters, reusable functions, hierarchical
subcircuits, and file inclusion. Parameter/function expressions are wrapped in { … } (or
' … ') and are evaluated at parse time, before the simulation runs.
.control … .endc vs. a pure dot-card deck — why ngspice has two
languages. A single ngspice file mixes two languages, and it helps to know which one you are
writing in. This is the biggest structural difference from Eldo and HSPICE, which use one unified deck
language and push scripting/plotting into dedicated dot-cards (.STEP, .ALTER,
.MEASURE) and an external waveform viewer.
ngspice descends from Berkeley SPICE3 + nutmeg: the simulator reads a declarative
netlist, while nutmeg is an interactive command shell (type let, plot,
foreach… at the ngspice -> prompt). The .control … .endc
block is simply “run these nutmeg commands automatically” embedded in the file — an imperative
script that runs after the circuit is parsed.
| netlist / dot-card language | control (nutmeg) language | |
|---|---|---|
| style | declarative — a circuit + what to analyze | imperative — a script, run top to bottom |
| examples | .tran .ac .dc .model .param .print .save | run let foreach while if alter meas fft write plot |
| runs | during the simulator pass | after parse, on demand, inside .control … .endc |
Can you do everything without .control? Basic simulation, yes; the full tool, no —
the whole scripting/post-processing half of ngspice lives only inside .control. ngspice
deliberately ships both forms of several features so HSPICE-style batch decks work unchanged
(.four card / fourier command, .plot ASCII card / graphical
plot, .meas card / meas command) — that duplication is the
compatibility bridge.
works as pure dot-cards (batch -b, no .control) | only available inside .control |
|---|---|
analyses .op .tran .ac .dc .noise .tf .disto .pz .sens .four; output .print .plot .save .meas .options .param .temp .model .subckt .include .ic; rawfile via -r, log via -o |
scripting let set foreach while if repeat; alter/altermod between runs; vector math fft spec psd linearize load; chosen-set write/wrdata; graphical plot/hardcopy/gnuplot; diff; sequenced multi-analysis; Monte Carlo (loop + mc_source) |
.control the moment you
need loops, logic, several analyses in sequence, or post-processing beyond .print/.plot.
Two traps at the boundary: a .param is not visible inside .control (promote
it with .csparam), and batch mode still needs an output directive (.print/.plot/.four
or -r) or nothing runs. To accept more HSPICE deck syntax overall, set
ngbehavior=hs in spinit/.spiceinit..spiceinit / spinit — the startup files. ngspice runs a
startup script at launch: a list of front-end commands executed as if typed at the
ngspice -> prompt. It is not a netlist — there is no circuit yet, so device
cards and analysis dot-cards do not belong here, and no .control … .endc wrapper is needed
(the file is already in command context; comment lines start with *). Two files share the
same syntax: spinit is system-wide (in the install/share dir — it loads code models
and sets machine defaults), and .spiceinit is yours. ngspice searches for
.spiceinit in the current directory first, then your home directory, so a project can
carry its own. Both are sourced on every start, batch or interactive.
It configures the tool; the deck describes the circuit. Anything valid as a front-end
command works — set/unset, option, alias,
codemodel/osdi/use, if/end:
| category | examples | what they do |
|---|---|---|
| dialect | set ngbehavior=hs (also ps / lt / hsa / psa / lta) | accept HSPICE / PSPICE / LTspice deck syntax |
| performance | set num_threads=8 · option klu | OpenMP thread count · pick the KLU solver as default |
| output defaults | set filetype=ascii · set hcopypscolor=1 · option noinit | ASCII rawfiles · colour hardcopy · suppress the OP dump |
| option defaults | option reltol=1e-4 · set nostepsizelimit · set ng_nomodcheck | tolerances / behavior applied to circuits loaded afterward |
| aliases | alias exit quit · alias acct rusage all | command shortcuts |
| load models / libs | codemodel …/analog.cm · osdi …/BSIMCMG.osdi · use lib | load XSPICE code models / OSDI compiled models / device libraries |
| enable / disable OSDI | set osdi_enabled · unset osdi_enabled | the switch the shipped spinit tests to load (or skip) the .osdi model libraries |
| front-end vars | set rndseed=12 · set askquit · set ngdebug · set sourcepath=( … ) | fix the RNG seed · confirm on quit · debug trace · source/.include search path |
| diagnostics | set topo_verbose · set iter_verbose | report what topology reduction pruned or tied · report every Newton iteration and its wall-clock cost — see iteration limits |
| control flow | if $?xspice_enabled … end · unset osdi_enabled | conditional setup (the shipped spinit guards code-model loading this way) |
Put the diagnostics in .spiceinit for a long run. A big extracted deck can spend
many minutes in setup, and the solver then prints nothing at all until the analysis either
converges or gives up — there is no way to distinguish a slow solve from a hang, and no way to learn what
an iteration actually costs. set iter_verbose makes every iteration report
itself (flushed immediately, so it survives a redirected -o log);
set topo_verbose names the nodes the reducer tied or removed and writes them to
float_nodes.txt.
.model,
.subckt, .param, and analysis cards (.tran/.ac/.dc)
— has no circuit to attach to at startup; keep it in the deck. Use the option … command
form here, not the .options dot-card. And don’t wrap the file in .control … .endc:
it is redundant. Verified: a set myflag=42 in ./.spiceinit is readable as
$myflag inside a later deck’s .control.spinit wraps its model loads in a
guard — if $?osdi_enabled … osdi lib1.osdi … end — so a single variable decides whether the
compiled .osdi libraries load. On this build osdi_enabled is not defined
by default ($?osdi_enabled = 0), so OSDI loading is opt-in: add set osdi_enabled
before the block to enable it, or unset osdi_enabled (the shipped default) to skip it.
The XSPICE code models work the same way via $?xspice_enabled. Two caveats: the variable is only
a spinit convention — a bare osdi foo.osdi line loads that library regardless — and
this switch governs loading external OSDI/OpenVAF model libraries, which is distinct from the
parse-time §4 behavioral-passive codegen (that one triggers on behavioral
R=/C= expressions in the netlist and has its own opt-out,
set osdi_res_bsource — also placed in .spiceinit because it acts at parse time).Every dot-card and control command in one alphabetical sequence (leading dot ignored for ordering). See the index above to jump.
.ac — small-signal AC (frequency) analysis batch + ui.ac dec|oct|lin n fstart fstop
Linearizes the circuit about its DC operating point and sweeps frequency, giving complex node
voltages (magnitude/phase). Stimulus comes from sources carrying an AC value.
| parameter | meaning |
|---|---|
dec / oct / lin | points spaced per decade / per octave / linearly |
n | points per decade/octave, or total points for lin |
fstart fstop | frequency range (Hz) |
ngspice -b ac.cir* RC low-pass AC magnitude, find -3 dB corner V1 in 0 AC 1 R1 in out 1k C1 out 0 100n .ac dec 50 10 1meg .control run meas ac fc WHEN vdb(out)=-3.0103 .endc .end
fc = 1.59155e+03
fc = 1/(2π·R·C) = 1/(2π·1 kΩ·100 nF) = 1591.55 Hz ✓. Note vdb() gives magnitude in dB;
-3.0103 dB is the half-power point.
ngspice -b ac_phase.cir* phase = -45 deg (= -pi/4 rad) exactly at the corner V1 in 0 AC 1 R1 in out 1k C1 out 0 100n .ac dec 50 10 1meg .control run meas ac f45 WHEN vp(out)=-0.7853982 $ vp() is in RADIANS .endc .end
f45 = 1.59169e+03
Same 1592 Hz corner, cross-checked through the phase instead of the magnitude ✓.
vp() returns radians, not degrees. A single-pole
low-pass is −45° at its corner, but meas … WHEN vp(out)=-45 fails with
out of interval because vp() ranges only −π/2…0. Use the radian value
-0.7853982 (= −π/4), or take cph() for continuous (unwrapped) phase.AC value, no stimulus. .ac only injects signal
through sources given an AC magnitude (e.g. V1 in 0 AC 1). A source with only a
DC or transient (SIN/PULSE) spec contributes nothing in AC — the
output is silently zero.A device / codemodel — XSPICE code models batch + uiAname in-ports … out-ports … modelname .model modelname codemodel_type ( params ) codemodel library.cm $ load an external code-model library
The A card instantiates an XSPICE code model — a compiled
behavioral block (gain, summer, limiter, s-domain transfer function, ADC/DAC, a file-driven source, …)
beyond the built-in analog primitives. The code models are not linked into ngspice; they live in
shared-library files (analog.cm, digital.cm, xtradev.cm, …) and are
loaded at run time by the codemodel command. In a normal install spinit already
issues those codemodel lines at start-up (guarded by $?xspice_enabled), so a deck
can use gain, filesource, etc. with no codemodel line of its own.
gain block; run: ngspice -b amodel.cirV1 in 0 DC 0.5 A1 in out gainblk .model gainblk gain(gain=3.0) .control op print v(out) $ 3.0 * 0.5 = 1.5 .endc .end
v(out) = 1.500000e+00
filesource driving a whole bus)
ngspice -b buspattern.cir* one A-device drives a 4-bit bus from a multi-column file: a binary counter a1 %v([b3 b2 b1 b0]) bus4 $ a VECTOR port -> four nodes at once .model bus4 filesource (file="pattern4.dat" + amploffset=[0 0 0 0] amplscale=[1.8 1.8 1.8 1.8] $ per-bit: logic 1 = 1.8 V + timeoffset=0 timescale=1 timerelative=false amplstep=TRUE) rb3 b3 0 1meg rb2 b2 0 1meg rb1 b1 0 1meg rb0 b0 0 1meg .tran 5n 1.5u .control run meas tran b3_7 FIND v(b3) AT=750n $ counter=7 (0111): b3=0, b0=1.8 meas tran b0_7 FIND v(b0) AT=750n meas tran b3_12 FIND v(b3) AT=1250n $ counter=12 (1100): b3=1.8, b0=0 meas tran b0_12 FIND v(b0) AT=1250n .endc .end
b3_7 = 0.00000e+00 b0_7 = 1.80000e+00 b3_12 = 1.80000e+00 b0_12 = 0.00000e+00
This is where code models earn their keep. A vector port %v([b3 b2 b1 b0]) lets one
filesource drive a whole bus: each column of pattern4.dat feeds one bit, scaled
independently by the per-port amplscale/amploffset vectors — here a 4-bit binary
counter with logic-1 = 1.8 V. amplstep=TRUE holds each level (clean digital edges);
false would linearly interpolate (analog PWL). At t = 750 ns the file’s row is
0 1 1 1 and t = 1250 ns is 1 1 0 0 — both read back exactly ✓. Ports carry a
type sigil: %v([n]) single-ended, %vd([p n]) differential, %i
current, %vnam named. A fuller version — differential sine/cosine plus an 8-bit bus — is at
/users/design/ngspice/code_model_pattern.sp.
b0 toggles every 100 ns,
each higher bit half as often). bus4.gp:
wrdata bus4.txt v(b3)…v(b0) then gnuplot bus4.gp.
ngspice -b sincos.cir* two quadrature channels from a 3-column file (time, cos, sin) a1 %vd([c 0 s 0]) sincos $ %vd pairs nodes -> differential ports .model sincos filesource (file="sincos.dat" + amploffset=[0 0] amplscale=[1 1] + timeoffset=0 timescale=1 timerelative=false amplstep=false) rc c 0 1meg rs s 0 1meg .tran 2u 2m .control run meas tran c_0 FIND v(c) AT=0 $ cos(0) = 1 meas tran s_0 FIND v(s) AT=0 $ sin(0) = 0 meas tran c_q FIND v(c) AT=250u $ cos(90d) = 0 meas tran s_q FIND v(s) AT=250u $ sin(90d) = 1 .endc .end
c_0 = 1.00000e+00 s_0 = 0.00000e+00 c_q = 1.73472e-18 (= 0) s_q = 9.99920e-01 (= 1)
Here %vd([c 0 s 0]) declares two differential output ports — one across
(c,0), one across (s,0) — fed by columns 2 and 3 of the file. With
amplstep=false the samples are linearly interpolated, giving smooth analog waveforms; the
two channels are 90° apart (cos and sin) ✓. Because each port’s reference here is ground, the pair acts
single-ended — give a non-zero second node to drive a genuinely floating differential output.
v(c) and v(s) in quadrature (90° apart), smoothly
interpolated from the file. sincos.gp:
wrdata sincos.txt v(c) v(s) then gnuplot sincos.gp.
ngspice -b bus8.cir* 8 bits, each parked at a distinct DC level so the traces don't overlap a1 %v([a7 a6 a5 a4 a3 a2 a1 a0]) bus8 .model bus8 filesource (file="pattern8.dat" + amploffset=[0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8] $ per-bit DC level + amplscale=[0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05] $ small swing + timeoffset=0 timescale=1 timerelative=false amplstep=TRUE) ra7 a7 0 1meg ra0 a0 0 1meg .tran 5n 850n .control run meas tran a0_hi FIND v(a0) AT=50n $ a0 hot: 0.8 + 0.05 = 0.85 meas tran a7_lo FIND v(a7) AT=50n $ a7 low: 0.1 + 0 = 0.10 meas tran a7_hi FIND v(a7) AT=750n $ a7 hot: 0.1 + 0.05 = 0.15 meas tran a0_lo FIND v(a0) AT=750n $ a0 low: 0.8 + 0 = 0.80 .endc .end
a0_hi = 8.50000e-01 a7_lo = 1.00000e-01 a7_hi = 1.50000e-01 a0_lo = 8.00000e-01
The eight-column file is a walking-1; the per-bit amploffset vector
(0.1…0.8 V) parks each bit at its own DC level while amplscale keeps the swing to 0.05 V, so
plotting the bus shows eight non-overlapping stacked traces — the usual way to eyeball a digital vector.
All four probes match their offset + scale·bit ✓.
1 climbs diagonally while each bit stays parked
at its own level. Generated with bus8.gp:
wrdata bus8.txt v(a7)…v(a0) then gnuplot bus8.gp.
filesource outputs 0 past the last sample. Beyond the final
row’s time the source drops to 0 (offsets included), not the last held value. Add a terminating row (here
at 800 ns) if you need the pattern to persist to the end of the .tran — without it the
a7/a0 probes at 750 ns read 0 instead of their levels.codemodel — loading a library
You only call codemodel path/lib.cm yourself to load a library that isn’t already in —
typically a custom, user-compiled .cm, placed in spinit/.spiceinit
(or a script sourced) before the circuit that uses it. That is why there is no standalone deck example
here: the standard libraries are loaded for you at start-up, and the model just works.
codemodel …/analog.cm to a deck when spinit already loaded it double-registers the
models and segfaults this build. Load each .cm exactly once (leave the standard set to
spinit); use an explicit codemodel only for a library not already present..model, .spiceinit (where codemodel normally lives), OSDI (compiled foundry passives, a different route).alias / unalias — command shortcuts ui onlyalias [ name [ definition ] ] unalias name
Defines a control-mode shortcut for a command and its arguments. alias with no arguments
lists all aliases; unalias removes one.
ngspice -b alias.cirR1 1 0 1k .control alias pp print let x = 6*7 pp x $ 'pp' now means 'print' .endc .end
x = 4.200000e+01
define.alter — change a device value between runs batch + uialter devname value alter devname param = value
Changes an instance parameter of an already-loaded device (a resistance, capacitance, source level, …)
without editing or re-parsing the netlist. Follow it with run to resimulate. Ideal inside a
foreach/while loop for parameter studies.
ngspice -b alter.cirV1 in 0 DC 1 R1 in out 1k R2 out 0 1k .tran 1u 5u .control run print v(out)[0] $ 0.5 (equal divider) alter R2 = 3k run print v(out)[0] $ 0.75 (now a 1k:3k divider) .endc .end
v(out)[0] = 5.000000e-01 v(out)[0] = 7.500000e-01
alterparam: change a .param, not a device
ngspice -b alterparam.cir.param rval=1k
V1 in 0 DC 1
R1 in 0 {rval}
.op
.control
run
print @r1[resistance]
alterparam rval=2k
mc_source $ rebuild the deck so the new .param takes effect
op
print @r1[resistance]
.endc
.end
@r1[resistance] = 1.000000e+03 @r1[resistance] = 2.000000e+03
Where alter/altermod poke a device or model directly, alterparam
edits a .param value. Because parameters are resolved when the netlist is expanded, it only
takes effect after a re-expansion — mc_source (or reset) — then the next
run/op sees the new value.
altermod — change a model parameter between runs batch + uialtermod modelname param = value
Like alter, but targets a .model parameter — the change affects every device
using that model. Useful for corner/temperature what-ifs.
ngspice -b altermod.cir.model DMOD D(IS=1e-14) V1 a 0 DC 0.7 R1 a d 100 D1 d 0 DMOD .tran 1u 5u .control run print v(d)[0] altermod DMOD IS=1e-11 run print v(d)[0] $ larger IS -> smaller forward drop .endc .end
v(d)[0] = 6.413270e-01 v(d)[0] = 4.950148e-01
Raising the diode saturation current by 1000× lowers its forward drop from 0.641 V to 0.495 V (≈ 60 mV/decade × 3 decades) ✓.
alter.@device[param] — read a device/model parameter ui only@devicename[parameter] $ e.g. @R1[resistance], @M1[gm], @Q1[ic]
The @ accessor reads a live device (or model) parameter or operating-point quantity into
an expression — resistance of a resistor, gm/vds of a MOSFET, ic of
a BJT, etc. It must be evaluated (via let/print), not
echoed. Which parameters exist for a device is listed by devhelp /
showmod.
print @R1[resistance] $ 1000
@r1[resistance] = 1.000000e+03
compose — build a vector ui onlycompose name values v1 v2 … compose name start=a stop=b step=d (or lin=n / log=n)
Creates a vector from an explicit list of values, or as a linear/log ramp. Handy for building a sweep
axis or a lookup table inside .control.
ngspice -b compose.cirR1 1 0 1k .control compose p values 1 2 4 8 print p compose ramp start=0 stop=1 step=0.25 print ramp .endc .end
p: 1 2 4 8 ramp: 0 0.25 0.5 0.75 1
let..csparam — expose a parameter to .control batch + ui.csparam name = { expression }Copies a netlist parameter into the .control namespace (as a vector), where plain
.param values are otherwise invisible. The bridge between parse-time parameters and the
control script.
ngspice -b csparam.cir.param vsup=3.3
.csparam vsupply={vsup}
.control
echo "supply is" $&vsupply $ 3.3
.endc
.end
supply is 3.3
.param is not visible in .control. Inside a
control block, $vsup / $&vsup do not see a netlist .param vsup.
Use .csparam to copy it across (or set/let a fresh value in the
control block)..dc — DC sweep batch + ui.dc srcname start stop step [ srcname2 start2 stop2 step2 ]
Sweeps a source (voltage or current) — or a nested pair of sources — and records the DC solution at each step, producing curves vs. the swept value. The classic use is a device I–V characteristic.
| parameter | meaning |
|---|---|
srcname | independent source to sweep (e.g. Vs) |
start stop step | sweep range and increment |
| second source | optional outer sweep → a family of curves |
ngspice -b dc.cir* Diode I–V curve via .dc source sweep Vs a 0 DC 0 R1 a d 100 D1 d 0 DMOD .model DMOD D(IS=1e-14 N=1) .dc Vs 0 1 0.05 .control run meas dc vd_1v FIND v(d) AT=1.0 meas dc id_1v FIND i(vs) AT=1.0 .endc .end
vd_1v = 6.84819e-01 id_1v = -3.15181e-03
At Vs = 1 V the diode drops 0.685 V, so I = (1 − 0.685)/100 Ω = 3.15 mA ✓.
ngspice -b dc_nested.cir.model NM NMOS(level=1 kp=200u vto=0.5) Vd d 0 DC 1 Vg g 0 DC 0 M1 d g 0 0 NM w=10u l=1u .dc Vg 0 2 0.5 Vd 1 2 1 $ inner Vg sweep, outer Vd -> a family .control run let id = i(vd) print id[3] $ Vgs=1.5 on the first Vds curve .endc .end
id[3] = -1.00000e-03
The outer Vd sweep repeats the inner Vg sweep for each drain voltage — the
MOSFET ID–VGS family. At VGS=1.5 V (saturation) ID =
½·kp·(W/L)·(VGS−Vto)² = 1.0 mA ✓ (i(vd) negative by source convention).
i(vs) is negative here (−3.15 mA):
a voltage source’s branch current is measured flowing into its + terminal, so current
delivered to the circuit reads negative. Negate it (-i(vs)) for the physical
current — but note meas … FIND -i(vs) fails to parse; measure i(vs) and negate
the result, or sense the current through a series element.define — user functions ui onlydefine name(args) expression
Defines a reusable function of one or more arguments, usable anywhere an expression is (in
let, print, plotting). Handy for repeated post-processing (dB, RMS, a custom
figure of merit).
define linesdefine sq(y) y*y print sq(4) $ 16
sq(4) = 1.600000e+01
let.devhelp / devices / show / showmod — device & model introspection batch + uidevhelp [ devtype [ param ] ] $ list a device type's parameters devices [ regexp … ] $ list device instance NAMES only show device|regexp … [ : param … ] $ instance operating-point values showmod model|regexp … [ : param … ] $ model parameter values
devhelp lists what parameters a device type accepts and reports;
show/showmod print the live values for instances/models after a run — the
readable companion to the @dev[param] accessor.
On a real design show dumps ~50 parameters per BSIM device × dozens of devices — pages of
scroll. Two features cut that down, and they combine:
show r.* = every
resistor, show m.* = every MOSFET, show m.x1.* = every device in subcircuit
X1. (Plain names, all, and the type:subckt:dev syntax still work; a name with a
bare . stays literal, so show m.x1.m7 is exact.):: everything past the colon names the parameters to
print — show m1 : vgs vds gm id shows only those four. (An invalid name for that model
prints ?????????.)Together: show m.x1.* : id vgs vds gm → those four values for every MOSFET in X1, one
tidy table instead of pages.
devices — just the names
When you only want to know which devices exist (not their values), devices lists
the instance names, one per line — the equivalent of piping show through
grep. No argument lists them all; each argument is a regex (grep-style):
devices $ every device instance devices m.* $ every MOSFET: m2 m1 m.x1.m3 … devices m.x1.* $ the devices inside subcircuit X1 devices ^r ^c $ every resistor and capacitor
Handy to discover names before a show … : params or an
@dev[param] query. (New command in this build.)
ngspice -b devhelp.cir.control devhelp resistor .endc
Resistor - Simple linear resistor 103 rsh inout Sheet resistance 106 narrow inout Narrowing of resistor 109 short inout Shortening of resistor …
ngspice -b show_regex.cir.model NM NMOS(LEVEL=1 VTO=0.7 KP=120u) V1 in 0 DC 1 R1 in a 1k R2 a b 2k M1 b in 0 0 NM W=10u L=1u M2 a in 0 0 NM W=4u L=1u .tran 1u 2u .control run show r.* $ every resistor (all default params) show m.* : vgs vds gm id $ every MOSFET, only these four params .endc .end
device r2 r1
resistance 2000 1000
device m2 m1
vgs 1 1
vds 0.9244 0.8164
gm 0.000144 0.00036
id 2.16e-05 5.4e-05
On the two_balance chip, show m.x1.* : id vgs vds gm reads back all
seven MOSFETs of instance X1 with four columns each. (Regex device selection is an
extension in this build; the : param selection is standard ngspice.)
diff — compare two plots ui onlydiff plot1 plot2 [ vec … ]
Compares two saved plots vector by vector and reports only the entries that differ by more than the
tolerances (set diff_abstol/diff_reltol/diff_vntol). The quick way
to see what a parameter change actually moved.
ngspice -b diff.cirV1 in 0 DC 1 R1 in out 1k R2 out 0 1k .op .control run $ -> plot op1 : v(out) = 0.5 alter r2 2k run $ -> plot op2 : v(out) = 0.667 diff op1 op2 .endc .end
op1.out[0] = 5.000000e-01 op2.out[0] = 6.666667e-01
Each run stores a plot (op1, op2); diff then prints
exactly the node that changed when R2 doubled — the divider output moved from 0.5 to 0.667 ✓.
display — list vectors in the current plot ui onlydisplay [ name | keyword | regexp … ]
Shows the status (not the values) of the vectors in the current plot:
each line is name : type, real/complex, length, with the scale tagged [default scale].
The quickest way to see what a run produced or what an analysis created (e.g. the sensitivity vectors of
.sens). For the numbers use print; for a
waveform, plot. It reports the current plot only — setplot first to inspect another.
| argument | lists |
|---|---|
| (none) | every vector in the current plot (alphabetical) |
v(out) i(v1) … | just the named vectors |
all · allv · alli · ally · alle | all vectors / voltages / currents / other / events |
<regexp> | every name matching a regular expression (see below) |
The variable set nosort lists in internal (creation) order instead of alphabetical.
ngspice -b display.cirV1 in 0 SIN(0 1 1k) Vdd vdd 0 DC 5 R1 in out 1k C1 out 0 100n Rl out vdd 2k .tran 20u 1m .control run display allv $ voltages only display .*branch $ regex: every branch current .endc .end
in : voltage, real, 59 long <- display allv
out : voltage, real, 59 long
vdd : voltage, real, 59 long
v1#branch : current, real, 59 long <- display .*branch
vdd#branch : current, real, 59 long
An argument that isn’t an exact name (or a v()/i() form or an all*
keyword) and contains regex metacharacters is treated as a POSIX extended, case-insensitive,
grep-style regular expression matched against vector names: display .*branch → all branch
currents, display ^v → everything starting with v, display o.*t →
out. Plain names still match exactly. (Regex selection is an extension
in this build; stock ngspice-46 accepts only names and the all* keywords.)
ngspice -b regex_hier.cir* two instances X1, X2 of a stage -> internal nodes x1.n1, x2.n1, ... Vin in 0 DC 3 X1 in mid stage X2 mid out stage RL out 0 1k .subckt stage a z R1 a n1 1k R2 n1 n2 1k R3 n2 z 1k .ends .op .control run display x1.* $ only X1's internal nodes print "x1.*" $ their voltages (print needs the quotes) unlet x2.* $ drop X2's internals display .*n1 $ x2.n1 is now gone .endc .end
x1.n1 : voltage, real, 1 long <- display x1.*
x1.n2 : voltage, real, 1 long
x1.n1 = 2.571429e+00 <- print "x1.*"
x1.n2 = 2.142857e+00
x1.n1 : voltage, real, 1 long <- display .*n1 (only x1.n1 left)
On a real design the subcircuit-internal nodes number in the dozens — the
two-stage balanced amplifier in user_examples/two_balance.cir exposes
x1.a, x1.b, x1.vp, x1.vcm, x2.… and the
per-device @m.x1.m7[…] currents. There, display x1.* lists just one instance’s
nodes and print "x1.*" dumps them — regex turns “what’s inside X1?” into one line.
.op prints an
operating-point device dump at the end of the run; that is the .op card’s own output,
not anything print/regex did. (2) print "x1.*" silently skips
zero-length vectors — e.g. the empty @r.x1.r1[i] placeholders that
.option savecurrents leaves in an AC plot — so you get the real node voltages
instead of a “vector not available” warning. Use ^x1\. to match only names starting
with x1. (excludes the @… device-current vectors entirely)..disto — small-signal distortion analysis batch + ui.disto dec|oct|lin n fstart fstop [ f2overf1 ]
Computes steady-state harmonic (and, with f2overf1, intermodulation) distortion from the
device small-signal series. Stimulus comes from sources carrying a DISTOF1/DISTOF2
spec. Results land in two plots: disto1 (2nd-order) and disto2 (3rd-order); the
node vectors are the distortion products.
ngspice -b disto.cir.model DMOD D(IS=1e-14) V1 in 0 DC 0.6 AC 1 DISTOF1 1 R1 in a 1k D1 a 0 DMOD .disto dec 5 1k 100k .control run setplot disto1 let hd2 = mag(a) print hd2[0] setplot disto2 let hd3 = mag(a) print hd3[0] .endc .end
hd2[0] = 1.044775e+00 (2nd-harmonic product at node a) hd3[0] = 2.018926e+00 (3rd-harmonic product)
.four.echo — print text ui onlyecho [ stuff … ]
Prints its arguments, expanding shell variables ($var). It does
not evaluate vectors or expressions — use $&vec for a vector’s value, or
print for expressions.
ngspice -b echo.cir.control set name = world let x = 6*7 echo "hello" $name $ shell variable echo "x =" $&x $ vector value .endc
hello world x = 42
fft — FFT of a transient vector ui onlyfft vector …
After a .tran run, computes the FFT of each vector into a new spectrum plot (frequency
resolution = 1/timespan). Use mag()/db() on the complex result. A windowed,
more controllable alternative to .four; window via
set specwindow=….
ngspice -b fftex.cirB1 s 0 V = sin(2*3.14159*1k*time) + 0.5*sin(2*3.14159*3k*time) R1 s 0 1k .tran 5u 10m .control run fft v(s) let m = mag(v(s)) meas sp a1k MAX m FROM=0.9k TO=1.1k meas sp a3k MAX m FROM=2.9k TO=3.1k .endc .end
FFT: Frequency resolution: 99.957 Hz, output length: 1005 a1k = 9.95417e-01 at= 9.99570e+02 a3k = 4.93659e-01 at= 2.99871e+03
Both tones are recovered at 1.0 / 3.0 kHz with a 2.01 : 1 amplitude ratio — matching the 1 : 0.5 input ✓.
meas needs a real vector. meas sp … mag(v(s)) fails
(“no such vector as mag(v(s))”): meas can’t take a function call inline. Materialize it
first with let m = mag(v(s)), then measure m.psd: power spectral density
ngspice -b psd.cirB1 s 0 V = sin(2*3.14159*1k*time) R1 s 0 1k .tran 5u 10m .control run psd 1 v(s) $ 1 = averaging length; result overwrites v(s) let p = db(v(s)) meas sp pk MAX p .endc .end
PSD: Frequency resolution: 100 Hz, output length: 1005 pk = -4.60992e+01 at= 1.00000e+03
psd len vec… is a close cousin of fft that returns the power spectral
density (magnitude squared, optionally smoothed over len bins). The peak lands on the
1 kHz tone ✓. Use fft when you want the complex spectrum, psd when you want power.
fopen / fread / fclose — read a file line by line ui onlyfopen handle file [mode] $ open; handle name in $handle-var fread handle var [len] $ read a line into $var; status in $len-var fclose handle
Line-oriented file reading for .control scripts (parameter
tables, external stimuli).
foreach — iterate over a word list ui onlyforeach var word1 word2 … … body (use $var) … end
Runs the body once per word, with var set to each word in turn (words are literal text —
values, node names, filenames). The most common way to repeat a run over a set of parameter values.
ngspice -b foreach.cir.control
foreach r 1k 2k 4k
echo "R = $r"
end
.endc
.end
R = 1k R = 2k R = 4k
set list variable; run: ngspice -b foreach_set.cir.control
set rvals = ( 1k 2k 4k )
foreach r $rvals $ walk the list held in a variable, not literal words
echo "R = $r"
end
.endc
.end
R = 1000 R = 2000 R = 4000
The word list can come from a set list variable ($rvals) rather than being
typed inline — handy when the values are built elsewhere. Note the suffixes are evaluated to plain
numbers (1k→1000) when stored in the variable.
.four / fourier — Fourier analysis of a transient batch + ui.four freq output_variable ...
After a .tran run, computes the DC component, fundamental (at freq), and
harmonics 2–9 of each output, plus total harmonic distortion (THD). Operates on the last period of the
transient waveform.
ngspice -b four.cir* Half-wave rectifier -> .four shows strong harmonics Vs in 0 SIN(0 1 1k) R1 in a 50 D1 a out DMOD Rout out 0 1k .model DMOD D(IS=1e-14 N=1) .tran 2u 20m .four 1k v(out) .end
Fourier analysis for v(out): No. Harmonics: 10, THD: 87.7379 %, Gridsize: 200, ... Harmonic Frequency Magnitude Norm. Mag 0 0 0.0703844 0 1 1000 0.128272 1 2 2000 0.0956183 0.745433 3 3000 0.0548493 0.427601 4 4000 0.0192777 0.150288
Half-wave rectification is strongly nonlinear → THD ≈ 88 %, with a large 2nd harmonic (74 % of the fundamental) — exactly the even-harmonic-rich spectrum expected.
.four resamples the last period
onto a fixed grid; too coarse a .tran tstep, or a tstop that isn’t
an integer number of periods of freq, inflates the reported THD with interpolation error.
Use a small tstep and a tstop that is a whole multiple of 1/freq.fourier: the on-demand .control form
ngspice -b four_ctrl.cirVs in 0 SIN(0 1 1k) R1 in a 50 D1 a out DMOD Rout out 0 1k .model DMOD D(IS=1e-14 N=1) .tran 2u 20m .control run fourier 1k v(out) $ same maths as .four, run when you choose .endc .end
No. Harmonics: 10, THD: 87.7379 %, Gridsize: 200, ...
The bare command fourier freq vec… is the interactive twin of the .four card:
identical result, but computed on demand inside .control (e.g. after an alter,
or on a vector you just built with let) rather than automatically at end of run.
.global — global nodes batch + ui.global node …
Declares nodes that are the same net everywhere — reachable inside any subcircuit without being
wired through its ports. The usual candidates are supplies and the substrate (vdd,
gnd, vss). Ground node 0 is always global.
ngspice -b global.cir.global vdd Vdd vdd 0 DC 5 Vin in 0 DC 1 X1 in out amp .subckt amp i o R1 i o 1k R2 o vdd 3k $ vdd here is the global node, not a port .ends .tran 1u 5u .control run print v(out)[0] $ 2.0 .endc .end
v(out)[0] = 2.000000e+00
The subckt reaches vdd without it being a port; v(out) settles where the two dividers
balance: (1/1k + 5/3k)/(1/1k + 1/3k) = 2.0 V ✓.
.subckt.goto / label — control-flow jump ui onlylabel name goto name
An unstructured jump within a .control block. Prefer the structured
loops (foreach/while/repeat);
goto is for the occasional escape or a small state machine.
ngspice -b goto.cir.control
let i = 0
label top
echo "i =" $&i
let i = i + 1
if i < 3
goto top
end
.endc
i = 0 i = 1 i = 2
.ic / .nodeset — initial node conditions batch + ui.ic v(node) = value … $ forced initial value (with .tran uic) .nodeset v(node) = value … $ hint for the DC operating-point solver
.ic forces a node to a value at the start of a transient run when the analysis uses
uic. .nodeset only seeds the DC solver (to pick between
multiple solutions or aid convergence) and is released once DC converges — it does not force a transient
start value.
ngspice -b ic.cirV1 in 0 DC 0 R1 in out 1k C1 out 0 1u .ic v(out)=1 .tran 10u 5m uic .control run print v(out)[0] $ 1.0 (the initial condition) meas tran vend FIND v(out) AT=5m $ decays ~ e^-5 .endc .end
v(out)[0] = 9.999000e-01 vend = 6.73767e-03
C starts at 1 V and discharges through R (τ=RC=1 ms); at 5 ms = 5τ it is 1·e−5 = 6.7 mV ✓.
.ic needs uic. Without uic on the
.tran line, ngspice computes a normal DC operating point and the .ic values are
ignored (they only bias the initial guess). .nodeset never forces a transient start..tran (uic), .op..if / .elseif / .else / .endif — conditional netlist batch + ui.if(expr) … cards … .elseif(expr) … cards … .else … cards … .endif
Includes netlist cards conditionally at parse time, based on a parameter expression — corners, options, or variant topologies selected from one deck.
ngspice -b ifelse.cir.param mode=2 .if (mode==1) R1 in 0 1k .else R1 in 0 4k .endif V1 in 0 DC 1 .op .control run print @R1[resistance] $ 4k (the else branch, since mode=2) .endc .end
@r1[resistance] = 4.000000e+03
.include / .lib — pull in other files batch + ui.include filename .lib filename section
.include textually inserts another file (parameters, subcircuits, models).
.lib file section pulls only a named section — the standard way PDKs ship process corners.
ngspice -b include.cir* params.inc: .param vval=5 rval=2k
.include params.inc
V1 in 0 DC {vval}
R1 in out {rval}
R2 out 0 {rval}
.tran 1u 5u
.control
run
print v(in)[0] $ 5 (from the included vval)
print v(out)[0] $ 2.5
.endc
.end
v(in)[0] = 5.000000e+00 v(out)[0] = 2.500000e+00
let / print — vectors and expressions ui onlylet name = expression $ create/assign a vector print expression … $ evaluate and show
let creates or overwrites a vector from an expression over existing vectors and constants;
print evaluates any expression (arithmetic, node voltages v(n), branch currents
i(vs), functions). Operators include + - * /, % (mod),
comparisons, and many functions (sqrt, abs, db, …).
ngspice -b vectors.cirV1 in 0 DC 2 R1 in out 1k R2 out 0 1k .op .control run let x = 2 + 3 print x $ 5 print v(out) $ 1 (2 V divided by two equal Rs) print @R1[resistance] $ 1000 (device parameter) .endc .end
x = 5.000000e+00 v(out) = 1.000000e+00 @r1[resistance] = 1.000000e+03
linearize — resample onto a uniform time grid ui onlylinearize [ vec … ]
A transient runs on an adaptive timestep, so its samples are unevenly spaced. linearize
interpolates the vectors onto a uniform grid (step = the .tran tstep). Required
before operations that assume even spacing — notably fft — and handy for
exporting clean columns with wrdata.
ngspice -b linearize.cirV1 in 0 SIN(0 1 1k) R1 in out 1k C1 out 0 100n .tran 10u 1m .control run let n1 = length(v(out)) echo "raw (adaptive) points:" $&n1 linearize let n2 = length(v(out)) echo "linearized points:" $&n2 .endc .end
raw (adaptive) points: 112 linearized points: 101
The adaptive run used 112 unevenly-spaced points; linearize resamples to 101 uniform points
(1 ms / 10 µs + 1) ✓.
listing — show the parsed circuit ui onlylisting [ logical | physical | deck | expand | runnable ]
Prints the current circuit as ngspice sees it after parsing — useful to confirm parameter substitution,
subcircuit expansion (expand), or the exact runnable deck (runnable).
ngspice -b listing.cirV1 in 0 DC 1 R1 in out 1k R2 out 0 1k .op .control listing .endc .end
3 : v1 in 0 dc 1
4 : r1 in out 1k
5 : r2 out 0 1k
source, edit.load — read a raw file back ui onlyload [ rawfile … ]
Reads a raw file (from -r or write)
into a new plot, so an earlier run’s data can be re-examined, compared, or post-processed without
re-simulating.
ngspice -b load.cir.control run write /tmp/saved.raw v(out) destroy all load /tmp/saved.raw $ data is back — no re-run print v(out)[0] .endc
v(out)[0] = 0.000000e+00
.meas / meas — extract figures of merit batch + ui.meas analysis result FIND expr AT=t | WHEN expr=val .meas analysis result MAX|MIN|AVG|RMS|PP|INTEG expr [FROM=t TO=t] .meas analysis result TRIG expr VAL=a RISE=n TARG expr VAL=b RISE=n .meas analysis result PARAM='expr'Control-mode: same, without the dot (
meas tran …) — except PARAM='expr', which
is dot-card only (see the table below). analysis is
tran, ac, dc, or sp.ngspice’s measurement command — the analog of SPICE .measure / Eldo .EXTRACT.
It reduces an analysis result to a named scalar you can print, reuse in a later .meas, or
feed to a script: peak/trough (MAX/MIN), average/RMS/peak-to-peak/integral over
a window, a value at a time or when a condition holds (FIND … AT/WHEN), and
delays/rise-times (TRIG … TARG).
.meas vs. meas — same engine, different role
The dot-card and the control-mode command share one measurement engine: the grammar above
(TRIG/TARG, FIND/WHEN, MAX/MIN/PP/AVG/RMS/INTEG) and the numbers
are identical either way. They are not fully interchangeable, though:
.meas (dot-card) | meas (in .control) | |
|---|---|---|
| when it runs | automatically at the end of its analysis | on demand, after you call run |
| result reusable? | prints only — not exposed as a vector (print name afterward fails) | stores a vector you can compute with (meas … vpk MAX … then let d = vpk*2) |
can measure derived vectors (from let/fft) | no — only the analysis output | yes (e.g. let m=mag(v(s)) then meas sp … MAX m) |
PARAM='expr' type | yes | no — rejected (“no such function as ‘param=’”); use let |
.options autostop (end run early once met) | yes — shortens the transient | no — the run has already finished |
needs .control? | no — works in a plain batch deck | yes |
Rule of thumb: if the number is the end product (a batch/regression figure of merit, or you want
autostop) use .meas; if the number is an input to more work — a
let expression, a second measurement, or measuring a vector you built yourself — use the
control-mode meas. The Q example below relies on exactly that reuse.
.meas→.meas via the PARAM= type
(.meas tran vpk2 param='vpk*2'), and control meas→meas because each
result is a stored vector (let scaled = vpk*2). But a .meas result cannot
be used by a control-mode meas — it is print-only and never enters the interactive namespace
(let/$var/meas … WHEN =vpk all fail with “vpk not available”).
If you need a measured value inside a script, compute it with the control meas after
run, not with .meas.ngspice -b meas.cirV1 in 0 PULSE(0 1 0 1n 1n 1 2) R1 in a 20 L1 a out 1m C1 out 0 1u .tran 1u 1m .control run meas tran vpeak MAX v(out) meas tran vfinal FIND v(out) AT=1m meas tran vavg AVG v(out) FROM=0.6m TO=1m meas tran vrms RMS v(out) FROM=0 TO=1m meas tran trise TRIG v(out) VAL=0.1 RISE=1 TARG v(out) VAL=0.9 RISE=1 .endc .end
vpeak = 1.35098e+00 at= 1.04838e-04 vfinal = 1.00001e+00 vavg = 9.99799e-01 from=6.0e-04 to=1.0e-03 vrms = 9.97501e-01 from=0.0 to=1.0e-03 trise = 4.24622e-05 targ=5.74e-05 trig=1.50e-05
Damping ζ = (R/2)·√(C/L) = 0.316 → overshoot e−πζ/√(1−ζ²) ≈ 0.35, so the peak is 1.35 V ✓, reached at 105 µs (≈ half the ringing period), and the output settles to 1.0 V.
ngspice -b q_resonator.cir* parallel RLC tank: Q = R*sqrt(C/L) ; here R=316.23, L=1u, C=1n -> Q=10 Iac in 0 AC 1 R1 in 0 316.23 L1 in 0 1u C1 in 0 1n .ac dec 2000 1e6 30e6 .control run meas ac zmax MAX vm(in) $ peak impedance = R, at fo let thr = zmax/sqrt(2) $ -3 dB level meas ac flo WHEN vm(in)=thr RISE=1 $ lower skirt meas ac fhi WHEN vm(in)=thr FALL=1 $ upper skirt let Q = sqrt(flo*fhi)/(fhi-flo) $ Q = fo / bandwidth print Q .endc .end
zmax = 3.16211e+02 at= 5.03567e+06 flo = 4.78753e+06 fhi = 5.29089e+06 q = 9.998790e+00
A current source drives the tank, so vm(in) is its impedance: it peaks at R at the
resonance fo, and the two frequencies where it falls to R/√2 bound the bandwidth. Combining
meas (peak + both skirts) with let yields Q = fo/BW = 10, matching
R·√(C/L) ✓. The RISE=1/FALL=1 qualifiers pick the lower and upper crossings.
.model — named device model batch + ui.model name type ( param=value … )
type is a device kind: D, NPN/PNP, NMOS/PMOS,
R, C, SW, …Defines a named parameter set shared by every instance that references it. The instance card names the
model in its model position (e.g. D1 a k MYDIODE).
ngspice -b model.cir.model MYDIODE D(IS=1e-14 N=1.2 RS=5) V1 a 0 DC 0.7 D1 a 0 MYDIODE .tran 1u 5u .control run print -i(v1)[0] $ 61.7 uA .endc .end
-i(v1)[0] = 6.171025e-05
ngspice -b model_bjt.cir* force 10uA base current, read back beta = Ic/Ib .model MYNPN NPN(IS=1e-16 BF=100 VAF=80) IB 0 b DC 10u Q1 c b 0 MYNPN VC c 0 DC 5 .op .control run let beta = -i(vc)/10u $ negate: current is into the VC source print beta .endc .end
beta = 1.052822e+02
β comes out just above the model’s BF=100: the Early effect
(VAF=80) lifts it to BF·(1+VCE/VAF) ≈ 106 at VCE=5 V ✓.
ngspice -b model_mos.cir* LEVEL-1 NMOS in saturation, drain current from KP/VTO .model NMOS1 NMOS(LEVEL=1 VTO=0.7 KP=120u LAMBDA=0) M1 d g 0 0 NMOS1 W=10u L=1u $ drain gate source bulk model VG g 0 DC 1.7 VD d 0 DC 3 .op .control run let id = -i(vd) $ negate: current is into VD print id .endc .end
id = 6.000000e-04
The M card names four nodes (drain, gate, source, bulk) then the model, with
W/L per-instance. With VGS=1.7, VDS=3 (saturated) and
LAMBDA=0, ID = ½·KP·(W/L)·(VGS−VTO)² =
½·120 µ·10·(1.0)² = 600 µA ✓.
.noise — noise analysis batch + ui.noise v(output [,ref]) input_source dec|oct|lin n fstart fstop [ pts_per_summary ]
Computes output-referred and input-referred noise spectral density vs. frequency
(vectors onoise_spectrum, inoise_spectrum), and their integrals over the band
(scalars onoise_total, inoise_total, in V and V/√Hz-integrated → V rms).
ngspice -b noise.cir* Output noise of RC low-pass -> integrates to sqrt(kT/C) V1 in 0 DC 0 AC 1 R1 in out 1k C1 out 0 100n .noise v(out) V1 dec 100 0.1 100meg .control run print onoise_total .endc .end
onoise_total = 2.035612e-07
The resistor’s thermal noise, low-pass filtered by C, integrates to the classic √(kT/C) = √(1.38e-23·300/100 nF) = 2.03e-7 V rms — independent of R ✓. A textbook result the tool reproduces to three digits.
ngspice -b noise_spec.cir* thermal floor of a 1k resistor = sqrt(4kTR) = 4.07 nV/rtHz, flat V1 in 0 DC 0 AC 1 R1 in out 1k C1 out 0 1p .noise v(out) V1 dec 10 1 1k .control run setplot noise1 $ spectra live in noise1, totals in noise2 print onoise_spectrum[0] .endc .end
onoise_spectrum[0] = 4.071372e-09
√(4kTR) = √(4·1.38e-23·300·1 kΩ) = 4.07 nV/√Hz ✓.
.noise makes noise1 (the *_spectrum vectors vs. frequency) and
noise2 (the scalar *_total). After run the current plot is noise2, so
setplot noise1 first to reach a spectrum. And onoise_spectrum is stored in
V/√Hz, not V²/Hz — don’t take a second square root..ac..op — DC operating point batch + ui.opControl-mode:
op.Solves the quiescent DC bias: all capacitors open, all inductors shorted, sources at their DC
value. Prints every node voltage and source current, and (in interactive mode) makes the small-signal
device operating-point parameters available. It is the starting point of .tran (unless
uic) and .ac.
ngspice -b op.cir* DC operating point of a resistive divider V1 in 0 DC 1 R1 in mid 1k R2 mid 0 1k .op .end
Node Voltage in 1.000000e+00 mid 5.000000e-01
v(mid) = 1 V · R2/(R1+R2) = 0.5 V ✓.
.option / .options — simulator options batch + ui.option name[=value] …
Sets tolerances, temperature, integration method, and output behavior. The most common:
| option | default | effect |
|---|---|---|
reltol | 1e-3 | relative convergence/LTE tolerance |
abstol / vntol | 1e-12 / 1e-6 | absolute current / voltage tolerance |
trtol / chgtol | 7 / 1e-14 | transient LTE / charge tolerance (see the accuracy note) |
temp / tnom | 27 / 27 | analysis / nominal temperature (°C) |
method | trap | integration: trap or gear |
bypass | 0 | device-eval bypass — leave off for precision analog |
savecurrents | off | store branch currents (readable as @dev[i]) |
numdgt | 6 | digits in printed output |
itl1 / itl2 | 100 / 50 | Newton iteration limit for the DC operating point / the DC transfer curve |
itl4 | 10 | Newton iteration limit at one transient timepoint; exceeding it cuts the timestep |
maxiterfloor | 100 | lower bound the solver forces on itl1/itl2/itl4 — set to 0 to have them obeyed (see below) |
minres | 0 (off) | parasitic reduction: short resistors below this value by merging their two nodes (ground and source terminals are protected) |
mincap | 0 (off) | parasitic reduction: delete capacitors below this value |
gmin | 1e-12 S | conductance added across every nonlinear device junction (the classic SPICE convergence aid) |
floatgmin | 1e-12 S | conductance tying floating (fewer than two terminals) nodes to ground; =0 disables |
rshunt | off | resistance from every node to ground — a blunt instrument; see the gotcha below |
pstran / dptran | off | last-resort pseudo-transient continuation for an operating point that Newton and the fallback methods cannot reach; see below |
ngspice -b option.cir.options savecurrents V1 in 0 DC 1 R1 in mid 1k R2 mid 0 1k .op .control run print @R1[i] $ (1 - 0.5)/1k = 0.5 mA print @R2[i] .endc .end
@r1[i] = 5.000000e-04 @r2[i] = 5.000000e-04
itl1, itl4, maxiterfloor
When Newton–Raphson fails to converge, ngspice does not simply give up: it escalates through a ladder of
progressively more robust (and more expensive) methods — dynamic gmin stepping, true gmin stepping, source
stepping, and finally optran/pseudo-transient. The itl
options decide how long it flails at Newton before moving on, and the whole value of a fallback
ladder is reaching it early.
The catch: for decades the solver silently raised any of these limits to a floor of 100. That
made itl4 (default 10) and itl2 (default 50) inoperative at their
own documented defaults, and made a user-supplied itl1 below 100 a no-op. On a small circuit
this is invisible: every timepoint converges in two or three iterations, so the limit is never reached
and it cannot matter — which is exactly why it went unnoticed, and why no regression test catches it:
every circuit in the suite converges. It becomes visible only on a deck that fails to converge,
where the solver then burns the full 100 iterations — ten times the budget itl4 asked
for — at every rejected timestep, before it will consider a method that might actually work.
maxiterfloor exposes that floor. It defaults to 100, so every existing deck behaves
exactly as before; set maxiterfloor=0 and itl1/itl2/itl4
mean what the table says.
itl1 real
ngspice -b iterlimit.cir.option itl1=3 $ ask Newton to give up after 3 iterations .option maxiterfloor=0 $ ... and actually mean it .control set iter_verbose op .endc
NIiter: iteration 1 of at most 3 (0.00 s) NIiter: iteration 2 of at most 3 (0.00 s) NIiter: iteration 3 of at most 3 (0.00 s) NIiter: iteration 4 of at most 3 (0.00 s) Note: Newton iteration failed, escalating to fallback OP methods Note: Starting dynamic gmin stepping NIiter: iteration 1 of at most 50 (0.00 s)
Newton is abandoned after 3 iterations and the ladder takes over. Delete the
maxiterfloor=0 line and the very same deck prints
iteration 1 of at most 100: itl1=3 is discarded.
set iter_verbose (a front-end variable, not an option) is what produces those lines. It
reports every Newton iteration with its wall-clock cost, flushed immediately — without it a long solve is
completely silent, and there is no way to tell a slow iteration from a hang, nor to measure what an
iteration actually costs on a big circuit.
itl4=10 may instead converge at, say, iteration 30 and be accepted. So
maxiterfloor=0 does not only change speed — it can change which timesteps are accepted, and
therefore the results. Use it when you want to fail fast and reach the fallback methods (large
extracted decks, a DC point that will clearly never converge); leave it alone for ordinary work. The
defect it fixes is not that the floor exists — it is that itl1/itl2/itl4
were silently untruthful, with no way to opt out.minres, mincap, floatgmin
These are ordinary options — use them on any deck when you want the behaviour they describe. They earn their keep most often on a post-layout (extracted) netlist, because extraction produces things a hand-written circuit rarely has — millions of parasitic elements, resistors of 0 Ω, capacitors of 0 F, and nodes that nothing actually connects to — but nothing about them is specific to extracted decks. The three answer three different problems.
| option | the problem it solves | when to reach for it |
|---|---|---|
minres | Size. Extraction emits near-zero resistors that carry no information
but each add a node and a matrix row. minres shorts them by merging their two nodes. |
The deck is bigger than it needs to be. Measure first: on many decks the count of sub-milliohm resistors is tiny and this changes nothing. |
mincap | Size. Same idea for negligible capacitors, which are simply deleted. | As above. Both are reduction levers, not convergence levers. |
floatgmin | Convergence — one specific cause. A node that fewer than two device
terminals reach cannot carry current, so at DC it has no reference: its matrix row is empty and the
matrix is rank-deficient. The solver reports a singular matrix — at an arbitrary node, not
necessarily the guilty one. floatgmin ties exactly those degree-<2 nodes to ground. |
On by default (1e-12 S). It handles the dangling-node case automatically; you touch it only to
disable it (=0) or to see what it caught. |
The mechanism, concretely. At DC a capacitor is an open circuit and a MOSFET gate draws no current, so a
floating gate — a gate wired to nothing else — has no resistive path to anywhere. Its row in the
conductance matrix is all zeros: nothing sets its voltage, the matrix loses rank, and the solver stops with
singular matrix. floatgmin attaches a single conductance of 1e-12 S (a 1 TΩ
resistor, the default) from that node to ground. That places one nonzero term on the node's diagonal, which
is all the matrix needs: the row is no longer empty, the node acquires a DC reference and simply sits at
0 V. Because 1 TΩ passes essentially no current (a picoamp per volt), it changes nothing else in the
circuit — it is a reference, not a load. The classic structures it rescues, both routine in extracted
layout, are exactly this dangling gate and the MOS with only its bulk connected (drain, gate and
source all going nowhere); commercial simulators prune such devices at elaboration, ngspice keeps them, so
their nodes stay unreferenced until floatgmin supplies the path.
floatgmin is not a cure for “singular matrix”. It fixes exactly one
cause — a node reachable by fewer than two device terminals — and on a deck whose only problem is dangling
nodes it fixes it completely (the example below). But a singular matrix has other causes it cannot touch:
an entire sub-network with no DC path to ground (for example a resistor island that only ever drives MOS
gates, or a device-internal node behind a series resistor), two voltage sources in a loop, or plain
ill-conditioning. Because the solver names an arbitrary node, you cannot tell which cause you have from the
message alone. Treat floatgmin as the cheap first thing to try when a matrix goes singular and
you do not yet know why — not as a guarantee. If it ties nodes (watch set topo_verbose) and the
matrix is still singular, the deficiency is elsewhere and tying more nodes will not help; the honest
next step is to find the sub-network that has no path to ground, or — if the circuit is solvable but merely
hard to reach — to escalate to pstran below.ngspice -b floatgmin.cir* Floating nodes: a dangling MOS gate and a bulk-only MOS .model nm nmos level=1 vto=0.7 kp=100u V1 d 0 2 R1 d dd 1k VG g 0 1.5 M1 dd g 0 0 nm w=10u l=1u $ working device, gate driven M2 dd floatg 0 0 nm w=10u l=1u $ gate dangles -> node floatg floats M3 dead1 dead2 dead3 0 nm w=10u l=1u $ only the bulk is connected .control set topo_verbose op print v(dd) .endc .end
floating node: floatg floating node: dead1 floating node: dead2 floating node: dead3 Topology reduction: 4 floating node(s) tied to ground through 1e-12 S (.option floatgmin). v(dd) = 1.680000e+00
The working device M1 solves normally. Add .option floatgmin=0 to the same deck and every
DC method fails in turn:
Warning: singular matrix: check node dead2 Note: Newton iteration failed, escalating to fallback OP methods Warning: Dynamic gmin stepping failed Warning: True gmin stepping failed Warning: source stepping failed
set topo_verbose (a front-end variable, not an option) prints the offending node names and
also writes them to float_nodes.txt — on a real extracted deck that file is how you find out
which nets your extraction left dangling. Without it the reduction is silent.
| option | where it is applied | can it change a correct result? |
|---|---|---|
gmin | across every nonlinear device junction | negligibly — it is what makes SPICE converge at all |
floatgmin | only on nodes that cannot carry current | no — no current can reach those nodes |
rshunt | every node in the circuit | yes — it loads every high-impedance node you have |
floatgmin is not rshunt. Both add a conductance to
ground, and it is tempting to reach for rshunt when a big deck will not converge. Do not:
rshunt shunts every node in the circuit, so it loads every genuinely high-impedance
node you have — a bandgap reference, a sample-and-hold storage node, a comparator input — and quietly
corrupts the answer. floatgmin is applied only to nodes that provably cannot carry
current, so it cannot perturb a working circuit. If a singular-matrix warning names a node and moves to a
different node when you change the shunt value, you have a structural problem, not a numerical one: find
and fix the topology (floatgmin already handles the dangling-node case), do not turn up
rshunt.pstran / dptran
Not every hard operating point is a broken one. A circuit can be perfectly well-posed — a unique DC
solution exists — and still defeat Newton–Raphson from a cold start: high-gain feedback loops (bandgaps,
comparators, ring oscillators, latches) make the Newton map oscillate, and gmin- and source-stepping lose
the path. For these, ngspice inherits the standard escalation ladder — Newton → gmin stepping → source
stepping → optran — and pstran adds one more, final rung.
pstran is pseudo-transient continuation: it finds the DC point by pretending to run a
transient and letting the circuit settle. It attaches an artificial capacitor Cp
(pstrancp, default 1 nF) from every node to ground and steps forward in a fictitious “pseudo
time” using backward Euler, with no truncation-error control — the trajectory is meaningless, only
the resting state matters.
The mechanics are the useful part. At each pseudo step of size dt, the backward-Euler
companion of each artificial capacitor puts a conductance g = Cp/dt on that node's matrix diagonal
and a current source g·vprev (its voltage at the previous step) on the right-hand side.
That added diagonal term is what buys the robustness: it makes each step's Jacobian diagonally dominant and
well-conditioned, so Newton converges at that step even on a circuit where a cold-start Newton would
oscillate or diverge. The step is solved, vprev is updated, and dt is
grown aggressively (×2, then ×10 on repeated success). As dt → ∞, g = Cp/dt → 0: the
artificial capacitor's conductance and its RHS current both vanish, the added terms disappear from the
system, and what remains is the original circuit at steady state — the exact DC operating point. So
it is a homotopy: it walks from an easy, heavily-damped problem (small dt, large g)
to the real one (g → 0) in continuous steps, and each intermediate step is one Newton solve
that actually converges. Physically it is the numerical form of bringing a bench supply up through a
soft-start instead of slamming the rail on.
dptran is the damped variant: on top of the pseudo capacitor it also clamps how far
any node voltage may move in a single Newton iteration (0.3 V), which stops a positive-feedback stage — a
latch, a bandgap, a comparator — from overshooting past its solution and re-diverging. It trades more
iterations for that safety; reach for it when plain pstran still will not settle.
| option | default | effect |
|---|---|---|
pstran | off | enable pseudo-transient continuation as the last-resort OP method |
dptran | off | damped pstran (implies pstran) — for positive-feedback circuits |
pstrancp | 1n | artificial capacitance placed on every node |
pstrandtinit | 1n | initial pseudo timestep |
pstrandtmax | auto | final pseudo timestep (0 = auto, pstrancp/gmin) |
pstranmaxsteps | 200 | maximum number of pseudo steps |
ngspice -b pstran.cir* pstran: a high-gain feedback ring that defeats Newton b1 n2 0 v = 0.9 - 0.89*tanh(200*(v(n1)-0.9)) b2 n3 0 v = 0.9 - 0.89*tanh(200*(v(n2)-0.9)) b3 n1 0 v = 0.9 - 0.89*tanh(200*(v(n3)-0.9)) r1 n1 0 1meg .option pstran .control op print v(n1) v(n2) v(n3) .endc .end
Note: Newton iteration failed, escalating to fallback OP methods Note: Starting dynamic gmin stepping ... (fails) Note: Starting source stepping ... (fails) Note: Starting pseudo-transient continuation (pstran) Note: Pseudo-transient continuation completed v(n1) = 9.000000e-01 v(n2) = 9.000000e-01 v(n3) = 9.000000e-01
Three high-gain tanh stages in a ring: Newton two-cycles on the steep map, gmin stepping
cannot damp voltage-forced nodes, and source stepping loses the path near full value — yet the circuit has
one honest solution (all nodes at 0.9 V), and pstran relaxes into it.
pstran is for hard, not for broken. It is a continuation method, so
it can only reach a solution that exists. It cannot repair a genuinely singular (rank-deficient)
matrix — a floating sub-network, a voltage-source loop — because there is no equilibrium to relax toward;
pstran will grind through its step budget and fail, the same as every other method. That is the dividing
line: floatgmin is for a matrix that is structurally broken
(a node with no DC reference); pstran is for a matrix that is fine but whose solution Newton
cannot find. Diagnosing which you have is the real work — the singular-matrix message alone will not
tell you.Other commands — less-common, alias & developer referenceThe remaining cp_coms commands, for completeness. Aliases point at their documented
twin; developer/debug dumps are grouped at the end.
| command | mode | what it does |
|---|---|---|
setcs | ui | like set but preserves the case of the value |
unset | ui | remove a variable set by set |
undefine | ui | remove a user function made with define |
newhelp / oldhelp | ui | alternate front-ends to help (same content) |
trace | batch+ui | print a node’s value at every timepoint (breakpoint family; remove with delete) |
iplot | batch+ui | plot nodes incrementally as the run progresses (interactive display) |
bltplot | ui | plot via the BLT widget toolkit (Tcl builds) |
use | ui | load a compiled device library (like codemodel/osdi) |
deftype | ui | redefine vector/plot type names (cf. settype) |
reshape-kin: shift | ui | shift argv or a list variable one place left (scripting) |
circbyline | ui | enter a netlist one line at a time (programmatic deck building) |
setcirc / remcirc / removecirc | batch+ui | switch to / delete the current circuit when several decks are loaded |
remzerovec | ui | drop zero-length vectors from the current plot |
inventory | batch+ui | print a count of devices by type in the circuit |
wrs2p | ui | write S-parameter data as a Touchstone .s2p file (needs .sp + P ports) |
aspice / rspice / jobs | ui | run a SPICE job asynchronously / remotely, and report on such jobs |
optran | batch+ui | configure the OP→TRAN ramp-up (the 6 optran flags) |
cdump | ui | dump the parsed .control structures (debug) |
mdump / mrdump | batch+ui | dump the solver matrix / right-hand-side to a file (debug) |
check_ifparm | batch+ui | validate device IFparm descriptors (developer) |
.param / .func — parameters and functions batch + ui.param name = value [ name2 = expr … ]
.func name(args) = { expression }.param defines named constants (usable in any value via {name});
.func defines a parameter-time function. Both are resolved during parsing — they cannot see
simulation results (for that, use let/define in .control).
ngspice -b structure.cir.param mult=3 rtop=2k
.func scaled(x)={x*mult}
.subckt divider a b rt=1k rb=1k
R1 a mid {rt}
R2 mid b {rb}
.ends
V1 in 0 DC {scaled(1)}
X1 in 0 divider rt={rtop} rb=1k
.tran 1u 5u
.control
run
print v(in)[0] $ scaled(1) = 3
print v(x1.mid)[0] $ 3 * 1k/(2k+1k) = 1
.endc
.end
v(in)[0] = 3.000000e+00 v(x1.mid)[0] = 1.000000e+00
agauss)
ngspice -b mc.cir.param rval=agauss(1k,100,3) $ gaussian: nominal 1k, ±100 at 3σ
V1 in 0 DC 1
R1 in 0 {rval}
.op
.control
setseed 7
run
print @R1[resistance] $ one random draw
.endc
.end
@r1[resistance] = 1.009399e+03
agauss/gauss/unif draw random parameter values (fixed with
setseed for reproducibility) — the basis of a Monte-Carlo loop with
foreach + alter.
.param/.func are evaluated once
at parse; they cannot reference node voltages or run results. Conversely let/define
(in .control) work on simulation vectors and are not visible to element cards. Mixing
the two is a common source of “parameter not found”..plot / plot — a curve, ASCII or graphical batch + ui.plot analysis expr ... $ batch: ASCII line plot plot expr ... [ vs xexpr ] $ control mode: graphical window plot "regexp" ... $ plot every matching vector
A double-quoted argument is a regular expression matched
over the current plot (like display): plot ".*#branch"
plots all branch currents. The quotes are required because bare metacharacters are expression operators;
unquoted vs/limit keywords and expressions are untouched. (Applies equally to
gnuplot and hardcopy, which share the plot engine.)
plot vs .plot trap. They are not the same
command. .plot (dot-card) renders an ASCII plot to the console and works in plain
batch. plot (no dot, control mode) opens a graphical window and needs a display —
in headless batch it produces no usable output. For a terminal/log or PDF pipeline, use
.plot or wrdata+gnuplot, not plot..plot ASCII output
ngspice -b dotplot.cirV1 in 0 SIN(0 1 1k) R1 in out 1k C1 out 0 100n .tran 20u 1m .plot tran v(out)
Legend: + = v(out) ----------------------------------------------------------------------- 2.000e-05 1.264e-02 . + . 4.000e-05 4.518e-02 . .+ . 6.000e-05 9.294e-02 . . + . 8.000e-05 1.532e-01 . . + . 1.000e-04 2.224e-01 . . + . 1.200e-04 2.974e-01 . . + .
asciiplot, hardcopy, gnuplot
Three siblings render the same vectors different ways: asciiplot vec… draws the text plot
shown above straight into the log (identical to a .plot card); gnuplot file vec…
hands off to gnuplot; and hardcopy file vec… writes a PostScript file — the only one that
works cleanly under -b for automated figure generation.
ngspice -b hardcopy.cirV1 in 0 SIN(0 1 1k) R1 in out 1k C1 out 0 100n .tran 20u 1m .control run set hcopypscolor=1 $ colour instead of monochrome hardcopy plot.ps v(out) .endc .end
plot.ps : %!PS-Adobe-3.0 EPSF-3.0 (a ~9 kB Encapsulated PostScript figure)
print / .print — values as text batch + uiprint expr ... $ control mode: dump values to stdout print "regexp" ... $ control mode: values of all matching vectors .print analysis expr ... $ batch: a formatted table
Because arguments are expressions (where *,
(, … are operators), a regular expression must be double-quoted: print
".*#branch" prints every branch current, print "v.*" every v-node — matched over the
current plot (same regex as display). Unquoted expressions like
v(out) or vdb(a)-vdb(b) are unaffected.
Control-mode print writes the value of vectors/expressions to the console (a scalar prints
on one line; a vector prints all points). The dot-card .print tran|ac|dc … produces a
numbered table of the named vectors after the run — and is one of the output directives that make an
analysis actually run in batch mode.
.print table
ngspice -b dotprint.cirV1 in 0 SIN(0 1 1k) R1 in out 1k C1 out 0 100n .tran 100u 500u .print tran v(in) v(out)
Index time v(in) v(out) 0 0.000000e+00 0.000000e+00 0.000000e+00 1 5.000000e-08 3.141593e-04 1.570011e-07 2 5.420140e-08 3.405574e-04 1.713021e-07 …
ngspice -b regex_hier.cir* two instances X1, X2 of a stage -> internal nodes x1.n1, x2.n1, ... Vin in 0 DC 3 X1 in mid stage X2 mid out stage RL out 0 1k .subckt stage a z R1 a n1 1k R2 n1 n2 1k R3 n2 z 1k .ends .op .control run print "x1.*" $ every node inside instance X1 (quotes required) .endc .end
x1.n1 = 2.571429e+00 x1.n2 = 2.142857e+00
One line dumps every node inside X1. On a real design this is the fast way to inspect a
subcircuit — the two-stage amplifier in user_examples/two_balance.cir (see
.subckt) exposes x1.a, x1.b,
x1.vp, x1.vcm, x2.…, so print "x1.*" reads back that
whole instance at once. Two gotchas — both covered in the display
entry — apply here: the quotes are required (bare */( are operators), and a
batch .op also prints a separate operating-point device dump that is not from
print. Empty @dev[i] vectors (AC savecurrents) are skipped
automatically; use "^x1\." to match only nodes and exclude device-current vectors.
.pz — pole–zero analysis batch + ui.pz node1 node2 node3 node4 cur|vol pz|pol|zer
Finds the poles and/or zeros of a small-signal transfer function about the DC operating point.
node1 node2 are the input port, node3 node4 the output port; vol
(voltage transfer) or cur (transimpedance); pz = both, pol = poles
only, zer = zeros only. Results appear as complex vectors pole(k),
zero(k) (rad/s).
ngspice -b pz.cir* Pole–zero of an RC low-pass: v(out)/v(in) V1 in 0 AC 1 R1 in out 1k C1 out 0 100n .pz in 0 out 0 vol pz .control run print pole(1) .endc .end
pole(1) = -1.00000e+04,0.000000e+00
A single real pole at −104 rad/s = −1/RC (= −1591 Hz), with no finite zero ✓. Poles are in rad/s; divide by 2π for Hz.
repeat / if / else — fixed count & branching ui onlyrepeat [ n ] if condition break / continue
… body … … body … (exit / skip a loop pass)
end else
… body …
end
repeat n runs the body n times (forever if n is omitted — use
break to leave).ngspice -b repeat.cir.control
let n = 0
repeat 4
let n = n + 1
if n % 2 = 0
echo $&n "is even"
else
echo $&n "is odd"
end
end
.endc
.end
1 is odd 2 is even 3 is odd 4 is even
reset / rehash / help / quit — session commands batch + uireset $ re-setup the circuit to its just-parsed state rehash $ rebuild the executable-command hash help [subject] quit bug tutorial
reset restores the circuit to its initial state, undoing
alters and prior runs; help browses the built-in docs;
quit exits; bug shows the bug-report address; tutorial points at the
tutorials.
ngspice -b trivial.cirV1 in 0 DC 1 R1 in 0 1k .op .control run print v(in) $ 1.0 reset run $ fresh setup, run again print v(in) $ 1.0 .endc .end
v(in) = 1.000000e+00 v(in) = 1.000000e+00
reshape / transpose / cutout / cross — vector surgery ui onlyreshape vec … [dim1,dim2,…] $ change a vector's dimensions transpose var … $ transpose a multi-dimensional vector cutout vec … $ keep only the current scale range cross newvec index vec … $ build a vector from one index across others
Manipulate the shape/contents of data vectors — reshape a flat vector into a matrix, transpose it, trim to a window, or gather a cross-section. Mostly for post-processing multi-dimensional (e.g. swept) results.
.save / save — choose what is stored batch + uiWithout any .save, a -r run stores the default set: every node voltage
and every voltage-source current. A .save (or control-mode save) replaces that
with exactly the vectors you name, always alongside the time/frequency scale. Use it to
shrink large raw files, or to store an expression such as a differential voltage.
save runs before the circuit, so a regex
argument matches the circuit node names (not plot vectors): save vd.* saves every node
starting with vd, save .*out.* every node containing out. The save
spec forms v(..)/i(..), @dev[param] and all are not
treated as patterns. Bare regex is fine here (unlike print) because
save’s arguments are names, not expressions.
.save
ngspice -b -r default.raw default.cirNo. Variables: 4 0 time time 1 v(in) voltage 2 v(out) voltage 3 i(v1) current
.save v(out) — same command* restrict the raw file .save v(out) V1 in 0 SIN(0 1 1k) R1 in out 1k C1 out 0 100n .tran 10u 2m .end
No. Variables: 2 0 time time 1 v(out) voltage
ngspice -b -r out.raw save_diff.cirV1 p 0 SIN(0 1 1k)
V2 n 0 SIN(0 0.7 1k)
R1 p 0 1k
R2 n 0 1k
.save v(p) v(n) vdiff=par('v(p)-v(n)')
.tran 50u 1m
.end
No. Variables: 4 1 v(p) voltage 2 v(n) voltage 3 v(vdiff) voltage ← the saved expression
.save name=par('expr') stores a computed vector straight into the raw file — the clean way
to capture a differential output without post-processing.
@device[param]
ngspice -b save_at.cir* @device[param] exposes a model's internal quantities .model NMOS1 NMOS(LEVEL=1 VTO=0.7 KP=120u LAMBDA=0) M1 d g 0 0 NMOS1 W=10u L=1u VG g 0 DC 1.7 VD d 0 DC 3 .options savecurrents $ makes @m1[id] @m1[is] ... available too .op .control run print @m1[gm] @m1[vdsat] @m1[id] .endc .end
@m1[gm] = 1.200000e-03 @m1[vdsat] = 1.000000e+00 @m1[id] = 6.000000e-04
Beyond node voltages and source currents, @name[param] reads a device's internal
values — here gm = KP·(W/L)·(VGS−VTO) = 1.2 mA/V and Vdsat = 1.0 V ✓.
Hierarchical instances use the full path, e.g. @m.x1.x2.m1[vgs]. Per-terminal branch
currents (@m1[id], @m1[is], …) need .options savecurrents;
you can save or plot any of these like an ordinary vector.
.save must not be the first line. The first line of a deck is
always the title and is ignored. A deck that opens with .save v(out) silently saves
everything because that line was treated as the title. (This exact mistake produced a wrong
result while writing this entry — hence the leading comment line in the example above.).sens — sensitivity analysis batch + ui.sens output_variable $ DC sensitivity .sens output_variable ac dec n fstart fstop $ AC sensitivity
Computes the sensitivity of an output to every circuit element and model parameter, at the DC operating
point (or over frequency with the ac form). Results land in a Sensitivity Analysis plot:
the bare element name (e.g. r1) is d(output)/d(element value), and elem:param
(e.g. r1:tc1) breaks it down per model parameter.
ngspice -b sens.cirV1 in 0 DC 1 R1 in out 1k R2 out 0 3k .sens v(out) .control run print r1 r2 v1 $ dV(out)/dR1, /dR2, /dV1 .endc .end
r1 = -1.87500e-04 r2 = 6.249994e-05 v1 = 7.500000e-01
For v(out) = V·R2/(R1+R2) = 0.75 V: dV/dR1 = −V·R2/(R1+R2)² = −1.875e-4, dV/dR2 = +V·R1/(R1+R2)² = +6.25e-5, and dV/dV1 = R2/(R1+R2) = 0.75 — all matched ✓.
set / $ / $& — shell variables vs. vector values ui onlyset name = value $ define a shell variable $name $ expand a shell variable (text) $&vecname $ expand the numeric value of a vector
set makes a shell variable (a string/flag, e.g. set appendwrite).
$name substitutes its text. To drop a vector’s numeric value into a command line
(e.g. an echo or a filename), use $&vecname — the & is what
says “take the value of the vector”, not “look up a shell variable”.
set/$ linesset foo = hello echo "foo is" $foo $ shell variable -> text let v = 7 echo "v ampersand:" $&v $ vector value -> text
foo is hello v ampersand: 7
$v vs $&v. $v looks for a
shell variable named v; if only the vector v exists, it expands to
nothing (or errors). Use $&v for the vector’s value. This is the single most common
.control scripting mistake.Some behaviour is controlled by set variables rather than .option, because it
belongs to the front end. The two most useful when a large deck misbehaves —
set iter_verbose (report every Newton iteration and its cost) and
set topo_verbose (report what topology reduction tied or removed) — are described under
.option, and are best placed in
.spiceinit so they are active before the deck is even read.
setplot / setscale / destroy — manage plots ui onlysetplot [ plotname ] $ list plots / switch the current one setscale [ vecname ] $ set the default scale of the current plot destroy [ plotname … ] $ free a plot's data (or 'all')
Each run/analysis makes a new plot (a named vector set:
tran1, ac1, sens1, …) — so the plot list is your list of
completed simulations. setplot with no argument lists them, marks the current, and
shows each one’s analysis type and title (and interactively prompts you to pick one); with a name
it switches. Cross-plot references use plot.vector (e.g. ac1.inp).
destroy reclaims memory.
| command | lists |
|---|---|
setplot (no arg) | the plots — one per analysis run — with type/title, current marked |
display (no arg) | the vectors in the current plot |
echo $plots | plot names only, as a string — for scripting, not a listing |
ngspice -b plotmgmt.cirV1 in 0 AC 1 SIN(0 1 1k) R1 in out 1k C1 out 0 100n .control op $ -> plot op1 ac dec 10 1 1meg $ -> plot ac1 tran 20u 1m $ -> plot tran1 setplot $ list all plots, marks the current setplot ac1 $ switch to the AC plot display $ vectors in ac1 .endc .end
List of plots available:
Current tran1 * … (Transient Analysis)
ac1 * … (AC Analysis)
op1 * … (Operating Point)
const Constant values (constants)
Three runs → three plots, each tagged with its analysis. A second tran would add
tran2, so the list doubles as a run history. echo $plots gives the same
names tersely (const op1 ac1 tran1) when you need them in a script.
setseed — seed the random generator ui onlysetseed integer
Fixes the seed of the random generator used by
agauss/gauss/unif and Monte-Carlo draws, so a randomized run is
exactly reproducible — set the same seed before each run to repeat a sequence.
mc_source, .option.settype — set a vector's type/units ui onlysettype type vec | regexp … $ type = voltage | current | time | frequency | impedance | …
Relabels a computed vector’s physical type so plots and printouts carry the
correct axis label and units (a derived impedance, power, gain, …). A vector argument containing regex
metacharacters retypes every current-plot vector whose name matches — e.g.
settype impedance .*#branch (same regex selection as display).
ngspice -b settype.cir.control run let z = v(out)/1m settype impedance z display z .endc
z : impedance, real, 59 long
shell / cd / getcwd — OS shell & directory ui onlyshell [ command ] cd [ dir ] getcwd
shell runs an OS command (or forks an interactive shell);
cd/getcwd change and print the working directory. Handy for pre/post-processing
around a run.
ngspice -b backtick.cirR1 1 0 1k V1 1 0 DC 5 .op .control run set nwords = `echo one two three four five | wc -w` $ capture OS stdout echo "the shell counted $nwords words" let total = $nwords * 2 $ feed it into a let print total .endc .end
the shell counted 5 words total = 1.000000e+01
Backticks `…` run a command and paste its standard output straight into the
.control line — the ngspice analogue of shell command substitution. Combined with
set/let it lets an external tool (sed, awk,
wc, a helper script) feed values back into the simulation.
shell can block a non-interactive -b run if the
command waits for input or a terminal; prefer running such steps outside ngspice in batch pipelines.snsave / snload — save/restore simulation state ui onlysnsave file snload file
Writes (or reloads) a full simulator snapshot — node values and internal state — so a long run can be checkpointed and continued later, or a hard-won operating point reused as a starting condition.
.ic.source / edit — load a deck interactively ui onlysource file edit [ file ]
source reads and loads another netlist file into the running
session; edit opens the current (or a named) deck in $EDITOR and reloads it on
exit. Interactive workflow aids; use .include for static composition.
.sp / .pss / .hb — RF & large-signal analyses batch + ui.sp dec|lin n fstart fstop $ S-parameters (needs P RF-port elements) .pss fund tstop points … $ periodic steady state (shooting method) .hb fund nharm $ harmonic balance
.sp computes scattering parameters and requires
P RF-port elements (each with a reference impedance) on the ports of interest.
.pss (periodic steady state) and .hb (harmonic balance) are large-signal RF
analyses for finding a circuit’s steady periodic response directly.
.sp is present (but errors
without RF ports: “No RF Port is present”), while pss and hb report
“no such command available” — they need a build compiled with those experimental analyses
enabled.spec — spectrum over a frequency range ui onlyspec fstart fstop fstep vector …
Like fft, but computes the spectrum at the exact frequencies you name
(fstart…fstop by fstep), rather than the FFT’s fixed
1/timespan bins. Run linearize first.
ngspice -b spec.cirB1 s 0 V = sin(2*3.14159*1k*time) + 0.5*sin(2*3.14159*3k*time) R1 s 0 1k .tran 5u 20m .control run linearize spec 0 5k 100 v(s) let m = mag(v(s)) meas sp a1k MAX m FROM=0.9k TO=1.1k meas sp a3k MAX m FROM=2.9k TO=3.1k .endc .end
a1k = 9.99901e-01 at= 1.00000e+03 a3k = 4.99552e-01 at= 3.00000e+03
Both tones land in exact 1.000 / 3.000 kHz bins with the right 2:1 amplitude ratio ✓.
.step — not supported.step. A deck with .step param … errors with
“unimplemented dot command '.step'” — it is an LTspice/PSpice extension. To sweep a parameter
across runs, use a control-mode loop with alter/altermod:.control
foreach rl 1k 2k 3k
alter R2 = $rl
run
meas tran vout FIND v(out) AT=...
end
.endc
stop / resume / delete — breakpoints batch + uistop [ after n ] [ when expr ] … $ set a breakpoint / condition resume $ continue a stopped run delete [ all | number … ] $ remove breakpoints
Pause a transient at a time, iteration, or condition to inspect state,
then resume. Most useful interactively (or when driven from a script that examines vectors at
each stop).
.tran.strcmp / strstr / strslice — string operations ui onlystrcmp var s1 s2 $ var = C strcmp(s1,s2): 0 if equal strstr var s1 s2 $ var = index of s2 in s1 (-1 if absent) strslice var s1 offset len $ var = s1[offset .. offset+len)
Each sets a shell variable from a string operation, for scripting file
names, labels, and conditionals in .control.
ngspice -b strings.cir.control strcmp c "abc" "abc" strcmp d "abc" "abd" strstr p "hello world" "world" strslice s "abcdef" 2 3 echo "equal=" $c "differ=" $d "pos=" $p "slice=" $s .endc
equal= 0 differ= -1 pos= 6 slice= cde
.subckt / .ends / X — subcircuits batch + ui.subckt name node1 node2 … [ param=default … ] … elements … .ends Xname net1 net2 … name [ param=value … ] $ instantiation
A subcircuit is a reusable block with external ports and optional parameters. Instantiate it with an
X card, wiring your nets to its ports and overriding parameters by name. Internal nodes are
addressed hierarchically as Xinstance.node (e.g. v(x1.mid) above).
ngspice -b subckt_params.cir* one parameterized RC low-pass, reused at three cutoffs
V1 in 0 AC 1
Xdef in def rclp $ takes the default c=1n -> ~159 kHz
Xmid in mid rclp c=10n $ override by name -> ~15.9 kHz
Xlo in lo rclp c=100n $ override by name -> ~1.59 kHz
.subckt rclp a b r=1k c=1n $ r,c are params with defaults
R1 a b {r} $ {..} evaluates the param
C1 b 0 {c}
.ends
.ac dec 100 100 1meg
.control
run
meas ac fcdef WHEN vdb(def)=-3.0103
meas ac fcmid WHEN vdb(mid)=-3.0103
meas ac fclo WHEN vdb(lo)=-3.0103
.endc
.end
fcdef = 1.59155e+05 fcmid = 1.59155e+04 fclo = 1.59155e+03
Parameters are declared as name=default on the .subckt line and used inside as
{name}. Each X card overrides by name (Xmid … c=10n) or, like
Xdef, inherits the default. The three cutoffs land exactly on
1/(2π·1 kΩ·C) for C = 1 n, 10 n, 100 n ✓ — one definition, three behaviours.
{…}. Writing
C1 b 0 c makes ngspice parse the bare c as a model name and warn
can't find model 'c'. The brace form {c} forces the token to be evaluated as
the parameter. A literal number needs no braces; a parameter reference always does. (The
.subckt name … params: c=1n keyword before the defaults is an accepted synonym.)user_examples/; run: ngspice -b two_balance.cir* two instances of the same subcircuit, chained xa vcasc inp inn ib0 op on vdd gnd balance_simple xb vcasc op on ib1 outp outn vdd gnd balance_simple .include ngspice_stimuli2 $ models + sources + .control .subckt balance_simple vcasc inp inn ib outn outp vdd gnd M3 a inp vcm gnd nch w=10u l=0.18u m=1000 $ input diff pair M1 b inn vcm gnd nch w=10u l=0.18u m=1000 M2 outn vcasc a gnd nch w=10u l=0.18u m=100 $ cascode devices M4 outp vcasc b gnd nch w=10u l=0.18u m=100 M5 vcm ib gnd gnd nch w=10u l=0.6u m=100 $ tail current sink M6 outn vp vdd vdd pch w=5u l=0.25u m=500 $ PMOS load M7 outp vp vdd vdd pch w=5u l=0.25u m=500 R1 vp outn 10meg $ R2 vp outp 10meg (common-mode feedback) .ends * --- ngspice_stimuli2 (excerpt): extract Cdiff from two AC runs --- .control ac dec 100 1k 11e6 $ ac1: common-mode drive meas ac vinp_cm find ac1.inp at=1e3 alter vinp acphase 0 alter vinn acphase 180 $ ac2: differential drive ac dec 100 1k 11e6 meas ac vinp_diff find ac2.inp at=1e3 let cinp = 10p*(1/ac2.vinp_diff - 1/ac1.vinp_cm)/2 echo "Cdiff = $&cinp" .endc
Warning: command 'plot' is not available during batch simulation, ignored! vinp_cm = 4.887917e-01 vinp_diff = 4.803305e-01 Cdiff = 1.80193E-13
The block is instantiated twice (xa, xb) and chained; the script drives it
common-mode then differentially and computes the differential input capacitance, Cdiff ≈ 0.18 pF.
Note the batch warning: the interactive plot is ignored under -b (use
.plot / gnuplot) — see § plot vs .plot.
(User-contributed example; generic LEVEL-3 models, no foundry data.)
vdb(outp,outn) vs frequency (rendered from the saved raw)
Passband differential gain ≈ 3.93 dB, flat to ~1 MHz, rolling off by ~10 MHz — the amplifier bandwidth.
(This is what the batch-ignored plot would have shown; here it is drawn from the saved data.)
.temp — simulation temperature batch + ui.temp value_in_celsius $ or, in .control: set temp = value
Sets the analysis temperature (°C). Every temperature-dependent model (diode/BJT/MOS saturation
currents, resistor tc1/tc2, …) is re-evaluated. In .control you can change
set temp between runs to sweep temperature.
ngspice -b temp.cir.model DMOD D(IS=1e-14) V1 a 0 DC 0.65 D1 a 0 DMOD .tran 1u 5u .control set temp = 27 run print -i(v1)[0] $ 0.82 mA set temp = 100 run print -i(v1)[0] $ 51 mA .endc .end
-i(v1)[0] = 8.204764e-04 -i(v1)[0] = 5.113547e-02
At a fixed 0.65 V the diode conducts 62× more at 100 °C — the strong temperature dependence of the saturation current ✓.
.tf — DC small-signal transfer function batch + ui.tf output_variable input_source
Computes, at the DC operating point, the small-signal ratio output/input, the resistance
seen at the input source, and the resistance seen at the output. One command for gain and both port
impedances.
ngspice -b tf.cir* DC small-signal transfer function V1 in 0 DC 1 R1 in out 1k R2 out 0 3k .tf v(out) V1 .end
transfer_function = 7.500000e-01 output_impedance_at_v(out) = 7.500000e+02 v1#input_impedance = 4.000000e+03
Gain = R2/(R1+R2) = 0.75; Zout = R1‖R2 = 750 Ω; Zin = R1+R2 = 4 kΩ ✓.
.tran — transient analysis batch + ui.tran tstep tstop [ tstart [ tmax ] ] [ uic ]As a control-mode command (inside
.control), drop the dot: tran tstep tstop ….Computes the circuit response in the time domain from tstart (default 0) to
tstop. tstep is the printing/initial step; the actual internal step is chosen
adaptively by local-truncation-error control and never exceeds tmax (default
(tstop−tstart)/50). Results are stored in the current plot as vectors indexed by the
time scale.
| parameter | meaning |
|---|---|
tstep | suggested/printing step size; also the initial step |
tstop | end time |
tstart | time at which output storage begins (analysis still starts at 0) |
tmax | maximum internal timestep (caps the adaptive step) |
uic | use initial conditions: skip the DC operating point; start from
.ic / device IC= values instead |
ngspice -b rc.cir* RC low-pass step response V1 in 0 PULSE(0 1 0 1n 1n 1 2) R1 in out 1k C1 out 0 1u .tran 0.1m 5m .control run meas tran v5ms FIND v(out) AT=5m meas tran trise TRIG v(out) VAL=0.1 RISE=1 TARG v(out) VAL=0.9 RISE=1 .endc .end
ngspice -b rc.cir
No. of Data Rows : 79 v5ms = 9.93289e-01 trise = 2.19596e-03 targ= 2.30186e-03 trig= 1.05894e-04
The output settles to 0.993 V at 5 ms (= 5τ, so 1 − e−5 ≈ 0.993 ✓), and the 10–90 % rise time is 2.196 ms ≈ 2.2τ, as expected for a single-pole RC.
uic: an LC tank rings from an initial condition
ngspice -b tran_uic.cirL1 n 0 1m C1 n 0 100n ic=1 $ start the cap at 1 V .tran 1u 400u uic $ no DC op; use the initial condition .control run meas tran period TRIG v(n) VAL=0 FALL=1 TARG v(n) VAL=0 FALL=2 .endc .end
period = 6.28842e-05
An LC tank has no DC operating point to settle to; uic starts it from C1’s
ic=1 and it oscillates at T = 2π√(LC) = 2π√(1 mH·100 nF) = 62.8 µs ✓.
uic skips the operating point. With uic, ngspice does
not solve the DC bias first; every node not pinned by an .ic or a device
IC= starts at 0 V. Use it for ring oscillators and switched circuits that have no valid
DC solution, not as a convergence shortcut.unlet — delete vectors ui onlyunlet vecname | regexp …
Removes vectors from the current plot (frees memory, or drops intermediates
before a write). The inverse of let.
An argument containing regex metacharacters deletes every current-plot vector whose name matches — e.g.
unlet temp.*, unlet .*#branch (same grep-style regex selection as
display; the scale vector is protected).
version / sysinfo / rusage / history — session info batch + uiversion [n] sysinfo rusage [resource] history [-r] [n]
version prints the ngspice version (and can assert a minimum);
sysinfo reports CPU/memory; rusage prints resource counters (time, memory,
iterations) — useful for benchmarking; history lists past commands.
ngspice -b info.cir** ngspice-46 : Circuit level simulation program [ngspice-jit fork: behavioral foundry R/C JIT-compiled to native OSDI]
.option.where / dump / state / status — diagnostics batch + uiwhere $ node/device with the largest residual (non-convergence) dump $ the circuit's node/branch matrix state $ current analysis state status $ analyses in progress and breakpoints
Convergence and state introspection. where names the node or
device carrying the largest residual after a failed Newton step — the first thing to check on a
“no convergence” error; dump prints the raw matrix; state/status
report the analysis and any breakpoints.
ngspice -b where.cirNo unconverged node found. v(out) = 5.000000e-01
V1 … on
line 1 — the title — so the source vanished and the divider’s resistors were (correctly) removed
as dangling, giving v(out)=0. Always start a deck with a comment/title (see §1).while / dowhile — condition loops ui onlywhile condition dowhile condition … body … … body … end end
while tests before the body (may run zero times); dowhile tests
after (runs at least once).ngspice -b while.cir.control
let i = 0
while i < 3
echo "step" $&i
let i = i + 1
end
.endc
.end
step 0 step 1 step 2
$&i prints the numeric value of the vector i (see the Vectors
chapter). A bare $i would look for a shell variable named i, which does
not exist here.
wrdata — ASCII column data ui onlywrdata file expr ...
Writes the scale (time/frequency) and each expression as whitespace-separated columns of plain text —
the easiest format to hand to gnuplot, a spreadsheet, or a script. Control mode only (no dot-card form).
A double-quoted argument is a regular expression over the current plot — wrdata f.txt
".*#branch" writes every branch current as columns (same rule as print).
ngspice -b wrdata.cirV1 in 0 SIN(0 1 1k) R1 in out 1k C1 out 0 100n .tran 50u 1m .control run wrdata out.txt v(out) .endc .end
# out.txt : two columns, time then value 0.00000000e+00 0.00000000e+00 1.00000000e-07 6.27690799e-07 1.08405602e-07 6.84886513e-07 …
ngspice -b wrdata_multi.cirV1 in 0 SIN(0 1 1k) R1 in out 1k C1 out 0 100n .tran 100u 1m .control run set wr_singlescale $ one time column, not one per expression set wr_vecnames $ prepend a header row of vector names wrdata multi.txt v(in) v(out) .endc .end
time v(in) v(out) 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e-07 6.28318489e-04 6.27690799e-07 …
By default wrdata repeats the scale column before every expression (so N exprs give
2N columns). set wr_singlescale collapses that to one scale + N value columns;
set wr_vecnames adds the name header — the two settings that make the file
gnuplot- and spreadsheet-ready.
wrnodev: snapshot node voltages as .ic lines
ngspice -b wrnodev.cirV1 in 0 SIN(0 1 1k) R1 in out 1k C1 out 0 100n .tran 20u 1m .control run wrnodev nodes.ic $ present node voltages -> .ic cards .endc .end
# nodes.ic * Recorded at simulation time: 0.001 .ic v(in) = -2.44929e-16 .ic v(out) = -0.450639
Unlike wrdata (which writes a whole waveform), wrnodev captures just the
present operating state of every node as ready-to-.include .ic cards —
a convenient way to seed another run from where this one stopped.
write vs. -r — two ways to make a raw file ui only-r file dumps the run’s default (or .save-restricted) set to file.
write file2 expr…, inside .control, writes a chosen set to file2. They
coexist only if the filenames differ. A double-quoted argument is a regular expression over the
current plot — write br.raw ".*#branch" writes every branch current (same rule as
print).
write to a different file than -r: both are produced
ngspice -b -r main.raw both.cir* write to a different file than -r V1 in 0 SIN(0 1 1k) R1 in out 1k C1 out 0 100n .tran 10u 2m .control run write sel.raw v(out) .endc .end
main.raw : No. Variables: 4 (time, v(in), v(out), i(v1) — the -r default set) sel.raw : No. Variables: 2 (time, v(out) — exactly what write named)
ngspice -b write_ascii.cirV1 in 0 SIN(0 1 1k) R1 in out 1k C1 out 0 100n .tran 100u 1m .control run set filetype=ascii $ default is binary write out_ascii.raw v(out) .endc .end
Title: * set filetype=ascii makes 'write' emit a human-readable rawfile. Plotname: Transient Analysis Flags: real No. Variables: 2 No. Points: 63 Variables: 0 time time 1 v(out) voltage Values: 0 0.000000000000000e+00 0.000000000000000e+00 …
Rawfiles are binary by default (compact, exact). set filetype=ascii before
write emits the same structure as text — readable, diff-able, and loadable back with
load. It applies to -r too.
appendwrite: many runs, one file
ngspice -b appendwrite.cirV1 in 0 SIN(0 1 1k)
R1 in out 1k
C1 out 0 100n
.tran 20u 1m
.control
let rk = 1k
while rk le 3k
alter r1 rk
run
write sweep.raw v(out)
set appendwrite $ 2nd run onward: append, don't overwrite
let rk = rk + 1k
end
destroy all $ clear the in-memory run plots first
load sweep.raw $ ...so echo shows only what the FILE holds
echo $plots
.endc
.end
const tran1 tran2 tran3
Normally each write overwrites; set appendwrite makes subsequent writes
append a new plot instead — the idiom for sweeping a parameter with alter and keeping
every run in one raw file. Reload it and each run reappears as tran1, tran2, …
(the destroy all is only so the echo isn’t cluttered by the plots still live from this session).
write to the same file as -r loses. If
write targets the very file passed to -r, the -r dump wins: the
file ends up with the full default set (4 variables), and write’s selection is discarded.
Give write a different filename from -r (or don’t use -r at
all).run needs an analysis card. run executes a
previously defined analysis (a .tran/.ac/… card, or a tran
issued earlier in .control). With no analysis defined, run does nothing and no
raw file is written.Foundry PDKs express precision passives as behavioral equations — a resistor or capacitor whose
value is a function of terminal voltage and temperature (high-field tanh, vc1/vc2
polynomials, temperature written into the expression). Stock SPICE can’t model those directly, and the
usual fallbacks hurt:
C=f(v) is realized as an auxiliary differentiator — numerically fragile, and
on large designs it aborts with “Timestep too small”.R=f(v) becomes a B-source I = V/R(V); the foundry’s
abs(V) puts a slope discontinuity right at the operating point, hurting the Newton Jacobian.This build adds a parse-time pass that instead compiles the foundry’s own equation into a native
OSDI device using the system gcc — no OpenVAF, no Verilog-A, no model edits. Resistors
become a native current device with an analytic Jacobian; capacitors become a charge-conserving
native charge device (so they are stable where the differentiator was not). Identical laws are compiled
once and shared (structural dedup); m (mfactor) is first-class; and electrically inert caps
(shorted n1==n2, or dangling on a floating node) are dropped, matching native topology
reduction. It is on by default; set osdi_res_bsource forces the old behavioral path.
osdi codegen — behavioral R/C → native batch + uiAutomatic: any behavioral resistor (R='…v(a,b)…') or capacitor (C='…v(a,b)…')
in the netlist is compiled at parse time. The run log reports what was generated, e.g.
osdi-res: N resistor(s) + M capacitor(s) -> K C-OSDI model(s).
set osdi_res_bsource
The codegen is the default; one boolean variable is the global opt-out. With
set osdi_res_bsource, every behavioral R=/C= keeps its original
B-source implementation instead — no compilation, no OSDI device. It governs resistors and
capacitors together.
* in .spiceinit (it is read before the deck is parsed): set osdi_res_bsource $ keep all behavioral R/C as B-sources, skip OSDI codegen
Put it in .spiceinit, not in the deck’s .control:
codegen runs at parse time, before an in-deck .control block executes, so setting it
there would be too late. Verified — with the flag the osdi-res: line disappears and the
circuit runs the behavioral network. Two more behaviors: even with codegen on, a device whose expression
uses an unsupported function, or a system with no gcc, falls back to B-source
automatically (warned once); and set osdi_res_include="/path" overrides the C header
include directory used when compiling the generated model.
ngspice -b osdi_selftest.cir.param r0=1000 k=0.1 c0=1e-12 vc=0.05 * voltage-dependent resistor R(v) = r0*(1 + k*v^2) vr a 0 dc 1 Rtest a 0 R='r0*(1 + k*v(a,0)*v(a,0))' * voltage-dependent capacitor C(v) = c0*(1 + vc*v) vx n 0 dc 0 pulse(0 1 1n 1n 1n 20n 40n) Rin n x 1k Ctest x 0 c='c0*(1 + vc*v(x,0))' .control op print i(vr) tran 0.5n 40n .endc .end
osdi-res: 1 resistor(s) + 1 capacitor(s) -> 2 C-OSDI model(s); 0 kept behavioral; 0 shorted/dangling cap(s) dropped i(vr) = -9.09091e-04
The two behavioral expressions are compiled into two native OSDI devices, and the DC check is exact:
at v(a)=1 V, R = r0(1 + k·1²) = 1100 Ω, so i(vr) = −1/1100 = −9.09e-4 A ✓ (negative by source
convention — see .dc).
.option bypass off. On very-high-gain
sample-and-hold / ADC nodes, .option bypass=1 lets a bounded ~1 mV error meander through the
hold — a property of the bypass approximation, not of OSDI (native devices show it too). Bypass is
off by default; keep it off for precision analog. Full analysis in
docs/osdi_bypass_wobble/ of the ngspice source tree.osdigen / osdigenc
Besides the automatic path, two .control commands emit a C-OSDI model on demand:
osdigen modelname n1 n2 expr $ resistor R(v) from a behavioral expression osdigenc modelname n1 n2 qexpr $ capacitor from a charge expression Q(v)
Use them to pre-generate and inspect a model (e.g. to check the emitted C, or build a library) rather than letting the parser compile netlist behaviorals silently. Same generator, same output devices.
docs/osdi_codegen_overview/, osdi_res_codegen/, osdi_cap_codegen/).All four sections populated (§1 Syntax, §2 Concepts, §3 Command reference A–Z, §4 OSDI);
every worked example verified against ngspice-46. Full cp_coms catalog coverage: all
118 interactive commands are documented — the common ones with tested examples, the niche/alias/developer
remainder in the Other commands table.