Owner: Dr. Meisam Bahadori
Using Claude Code AI to enhance the ngspice and openvaf frameworks.
Main goals:
- turn ngspice into the most powerful spice simulator (ngspice vs. Spectre gap analysis)
- turn openvaf-r into the most powerful verilog-a compiler (Verilog-A LRM compliance report)
Original OpenVAF git repository by Pascal Kuthe:
https://github.com/pascalkuthe/OpenVAF
OpenVAF-Reloaded git repository by Árpád Bűrmen:
https://github.com/arpadbuermen/OpenVAF
Ngspice Homepage:
https://ngspice.sourceforge.io/
Four hundred and ninety-four enhancements so far — language features, correctness fixes, systematic audits, and simulator-side workflow tooling, each verified end-to-end by a committed example suite and released with a detailed write-up.
🗂️ Browse them all in the live feature catalog — every enhancement grouped into 19 feature areas across the compiler and the simulator, searchable, with each entry linking to its write-up.
📖 Start with the User Handbook, which organizes everything by topic: getting started, the Verilog-A feature matrix, ngspice workflows, and the limitations & gotchas. The whole handbook plus the complete text of every enhancement write-up is also one linked PDF: docs/Ngspice-OpenVAF-Handbook.pdf.
🔧 Want to understand the compiler itself? OpenVAF Compiler Internals (PDF) is a ground-up, no-prior-knowledge walkthrough of how openvaf-r turns a Verilog-A model into a .osdi library — every stage of the pipeline (lexing → HIR → MIR → automatic differentiation → LLVM → OSDI), with real dumped IR traced end-to-end on a worked example.
⚙️ Want to understand the simulator itself? ngspice Simulator Internals (PDF) is the companion guide — a ground-up walkthrough of how ngspice-46 turns a netlist into a running circuit: the shell/engine split, the netlist parser, the CKTcircuit, the SPICEdev device interface, the sparse-matrix Newton loop, the analyses, and — crucially — how OpenVAF .osdi models plug in as first-class devices, traced end-to-end on a worked RC example.
🛡️ How robust is the compiler? OpenVAF Robustness Campaign (PDF) reports a deep robustness audit of openvaf-r — the full production-model corpus, ~50 adversarial inputs, and 4,000 mutation-fuzzing iterations — and the four crash/hang paths it found and fixed (Enhancement-147/-148).
⏱️ How fast does it compile? OpenVAF Compile-Time Analysis (PDF) profiles where openvaf-r's compilation time goes (≈70 % LLVM optimizing one eval function), why it's bound to a single core despite already parallelizing, and the compile-vs-simulation-speed trade-off of the -O level.
The index: Doc links each enhancement's detailed write-up, Examples links the folder whose verify script pins the behavior.
📖 Show the full enhancement table — 331 rows, click to expand
| # | What it delivered | Tool | Doc | Examples |
|---|---|---|---|---|
| 1 | absdelay() transport delay via synthetic-node DAE |
openvaf‑r | doc | absdelay |
| 2 | Indirect branch assignment (V(out): V(x)==0 — ideal op-amps) |
openvaf‑r | doc | indirect_assignment |
| 3 | Vectored (bus) nets and ports with bit-select access | openvaf‑r | doc | bus |
| 4 | laplace_* filter operators + array-variable declarations |
openvaf‑r | doc | laplace, bessel_filter |
| 5 | Module instantiation (hierarchy via compile-time flattening) | openvaf‑r | doc | instantiation |
| 6 | Directives, <<</>>>, slew/transition, zi_*, last_crossing |
openvaf‑r | doc | directive, shift, slew, zi, last_crossing |
| 7 | @(initial_step) gating + genuine variable persistence |
openvaf‑r | doc | initial_step, variable_persistence |
| 8 | generate for/genvar + cross()/above()/timer() events |
openvaf‑r | doc | generate, cross, timer |
| 9 | noise_table(_log), localparam, ground, strings, repeat/disable |
openvaf‑r | doc | noise, repeat, disable |
| 10 | $random + all $dist_*/$rdist_* distributions |
openvaf‑r | doc | rng |
| 11 | File I/O ($fopen…) + string formatting/parsing ($sscanf…) |
openvaf‑r | doc | fileio, stringio |
| 12 | $simprobe + alias/plusargs builtins |
openvaf‑r | doc | alias |
| 13 | limexp() kept stateless (documented decision); ddx() demo |
openvaf‑r | doc | ddx |
| 14 | Array literals/aggregates, array parameters, dynamic indexing | openvaf‑r | doc | array |
| 15 | Multi-dimensional arrays (N-D, per-element param override) | openvaf‑r | doc | mdarray |
| 16 | $table_model 1-D lookup tables (differentiable) |
openvaf‑r | doc | table_model |
| 17 | 2-D/3-D $table_model (multilinear, exact Jacobian partials) |
openvaf‑r | doc | mdtable |
| 18 | real x[0:n] declaration order + arrays in analog functions |
openvaf‑r | doc | funcarray |
| 19 | do … while loops |
openvaf‑r | doc | dowhile |
| 20 | Array output/inout function arguments |
openvaf‑r | doc | arrayout |
| 21 | paramset blocks |
openvaf‑r | doc | paramset |
| 22 | Natural cubic-spline $table_model (control "3") |
openvaf‑r | doc | cubic_table |
| 23 | Array return values from analog functions | openvaf‑r | doc | arrayret |
| 24 | $discontinuity(n) next-step clamp |
openvaf‑r | doc | discontinuity |
| 25 | $simparam$str (+ ngspice analysis_name/simulator params) |
openvaf‑r | doc | simparamstr |
| 26 | ac_stim crash fix + correct large-signal baseline |
openvaf‑r | doc | acstim |
| 27 | idtmod() modulo-integrator fix |
openvaf‑r | doc | idtmod |
| 28 | idt() initial condition survives into transient |
openvaf‑r | doc | idtic |
| 29 | Port-flow probes I(<port>) |
openvaf‑r | doc | portflow |
| 30 | Variadic analysis("ac","tran",…) |
openvaf‑r | doc | analysis |
| 31 | Complex poles/zeros in laplace_*/zi_* root forms |
openvaf‑r | doc | complexpole |
| 32 | Integer persistent/event-state variables (compiler crash fix) | openvaf‑r | doc | intstate |
| 33 | Array case + array-literal function arguments |
openvaf‑r | doc | arraycase |
| 34 | {…} concatenation and {n{…}} replication operators |
openvaf‑r | doc | concat |
| 35 | Lexer hang on // comment at EOF |
openvaf‑r | doc | comment |
| 36 | Probe-only branches (ideal ammeters, flow-only disciplines) | openvaf‑r | doc | signalflow |
| 37 | Operator-correctness audit (~, const-folded >>, string ?:) |
openvaf‑r | doc | operator |
| 38 | Precedence audit vs LRM Table 4-2 (% level fix) |
openvaf‑r | doc | precedence |
| 39 | Derived natures (nature X : Parent, : electrical.flow) |
openvaf‑r | doc | derivednature |
| 40 | $table_model in any number of dimensions |
openvaf‑r | doc | ndtable |
| 41 | Implicit nets in instance connections | openvaf‑r | doc | implicitnet |
| 42 | Correlated (same-named) noise sources sum coherently | openvaf‑r | doc | noisecorr |
| 43 | Variable declaration initializers, completed (N-D arrays) | openvaf‑r | doc | varinit |
| 44 | Paramset hidden system parameters (.$mfactor) |
openvaf‑r | doc | paramsethsp |
| 45 | Net initializers (nodesets) + nature-attribute access | openvaf‑r | doc | netinit |
| 46 | Escaped identifiers + based integer literals | openvaf‑r | doc | escid |
| 47 | `default_transition + transition() fixes |
openvaf‑r | doc | defaulttransition |
| 48 | String-literal escape sequences (single-pass unescaper) | openvaf‑r | doc | stresc |
| 49 | Hierarchical names + $root |
openvaf‑r | doc | hiername |
| 50 | Domain-binding validation (LRM 3.6.2.2) | openvaf‑r | doc | domainbind |
| 51 | Full ac_stim AC-RHS injection (OSDI ABI 0.6) |
both | doc | acstim |
| 52 | idt() assert/reset forms (relaxation oscillators) |
openvaf‑r | doc | idtassert |
| 53 | @(final_step) + analysis-phase lists on step events |
openvaf‑r | doc | finalstep |
| 54 | Correct, node-free noise factors (OSDI ABI 0.7) | openvaf‑r | doc | noisejw |
| 55 | $finish/$stop/$fatal honored + $discontinuity step rejection |
openvaf‑r | doc | simctrl |
| 56 | Corpus sweep: CMC default-range idiom + noise crash fix | openvaf‑r | doc | paramrange |
| 57 | Physics-accuracy validation suite | openvaf‑r | doc | physcheck |
| 58 | defparam hierarchical override |
openvaf‑r | doc | defparam |
| 59 | LRM corners: event OR lists, $realtime, port concat, recursion diags |
openvaf‑r | doc | lrmcorner |
| 60 | Multiple analog blocks — validation | openvaf‑r | doc | multianalog |
| 61 | Operator-argument audit — slew sign fix, $limit, $bound_step |
openvaf‑r | doc | opargs |
| 62 | .dc @inst[param] sweeps + .disto warning + analyses tutorial |
ngspice | doc | analyses |
| 63 | RF analyses: N-port .sp, trnoise, PSS + span.c NaN fix |
ngspice | doc | rfanalyses |
| 64 | Touchstone export: auto-Rbase, N-port wrsnp, 1-port .sp |
ngspice | doc | touchstone |
| 65 | Preprocessor audit — macro-recursion guard | openvaf‑r | doc | preproc |
| 66 | Monte Carlo with OSDI — validation (+ zero-warning build chore) | both | doc | montecarlo |
| 67 | Generate audit — genvar fix, nesting, generate if/case |
openvaf‑r | doc | generate |
| 68 | The compiler's own integration test suite, enabled (28 models) | openvaf‑r | doc | — |
| 69 | Operating-point variables end-to-end — validation | openvaf‑r | doc | opvar |
| 70 | Behavioral-loop audit — precise loop diagnostics | openvaf‑r | doc | analogloop |
| 71 | Display-task audit — full format surface + %b segfault fix |
openvaf‑r | doc | display |
| 72 | Touchstone round 2 — MA/DB, units, Y/Z, rdsnp reader |
ngspice | doc | touchstone |
| 73 | The user handbook, its PDF edition, and this README index | both | doc | docs/handbook |
| 74 | OSDI-vs-built-in benchmark (twins at parity) | both | doc | benchmark |
| 75 | dynamic-physics cross-checks (Cgg AC≡tran) | openvaf‑r | doc | dynphys |
| 76 | multi-module .osdi libraries |
ngspice | doc | multimod |
| 77 | ngspice build warnings → 0 (macOS/clang) | ngspice | doc | — |
| 78 | casex/casez don't-care masks |
openvaf‑r | doc | casexz |
| 79 | 1M-element benchmark round 2 (BSIM4) | both | doc | benchmark |
| 80 | temperature physics (dtemp alias fix) |
openvaf‑r | doc | tempphys |
| 81 | lifecycle: re-source/reset leak-free | ngspice | doc | lifecycle |
| 82 | provenance / compliance docs | both | doc | — |
| 83 | transistor-level opamp741 demo |
openvaf‑r | doc | opamp741 |
| 84 | LRM-2023 example sweep (231 examples) | openvaf‑r | doc | lrm |
| 85 | __FILE__/__LINE__ directives |
openvaf‑r | doc | filemacro, partselect |
| 86 | hierarchical branch probes | openvaf‑r | doc | hierbranch |
| 87 | block-scoped parameters | openvaf‑r | doc | blockparam |
| 88 | legacy generate syntax |
openvaf‑r | doc | legacygen |
| 89 | array ports + Annex E SPICE compat | openvaf‑r | doc | arrayport, annexe |
| 90 | multi-bit bus port ordering fix | openvaf‑r | doc | busport |
| 91 | param-dependent width + multi-name decls | openvaf‑r | doc | paramwidth |
| 92 | parameter freeze | openvaf‑r | doc | paramfreeze |
| 93 | unset-parameter warning | both | doc | paramnonset |
| 94 | pyplot (matplotlib backend) |
ngspice | doc | pyplot |
| 95 | pyplot default filename |
ngspice | doc | pyplot |
| 96 | bare generate blocks |
openvaf‑r | doc | baregenerate |
| 97 | contributing to all-ground branches |
openvaf‑r | doc | groundcontrib |
| 98 | pyplot subplots |
ngspice | doc | pyplotpanel |
| 99 | pyplot export formats (png/svg/pdf) |
ngspice | doc | pyplotexport |
| 100 | milestone audit | both | doc | — |
| 101 | $clog2 |
openvaf‑r | doc | clog2 |
| 102 | array parameters | openvaf‑r | doc | paramarray |
| 103 | ceil() non-const fix |
openvaf‑r | doc | ceil |
| 104 | $rtoi/$itor |
openvaf‑r | doc | convert |
| 105 | $sscanf format bases |
openvaf‑r | doc | sscanf |
| 106 | string relational operators | openvaf‑r | doc | stringcmp |
| 107 | $fgetc |
openvaf‑r | doc | fgetc |
| 108 | $ungetc one-char pushback |
openvaf‑r | doc | ungetc |
| 109 | noise_table interpolation |
openvaf‑r | doc | noisetable |
| 110 | .option errpreset (cons/mod/lib tolerance sets) |
ngspice | doc | errpreset |
| 111 | globalized-Newton line search | ngspice | doc | linesearch |
| 112 | KLU line search | ngspice | doc | linesearch |
| 113 | KLU noise + pole-zero | ngspice | doc | analyses, noisejw |
| 114 | KLU sensitivity | ngspice | doc | analyses |
| 115 | KLU distortion | ngspice | doc | analyses |
| 116 | KLU: decoupled-OSDI noise & pole-zero |
ngspice | doc | groundcontrib, hierbranch |
| 117 | PSS (shooting) productionized | ngspice | doc | rfpss |
| 118 | PSS under KLU | ngspice | doc | rfpss |
| 119 | PAC: retain PSS op-point | ngspice | doc | rfpss |
| 120 | PAC: Jacobian harmonics | ngspice | doc | rfpss |
| 121 | PAC: conversion matrix | ngspice | doc | rfpss |
| 122 | .pac command (periodic AC sweep) |
ngspice | doc | rfpss |
| 123 | .pac finish |
ngspice | doc | rfpss |
| 124 | .pnoise |
ngspice | doc | rfpss |
| 125 | .pxf |
ngspice | doc | rfpss |
| 126 | cyclostationary noise | ngspice | doc | rfpss |
| 127 | .option ptcont DC homotopy |
ngspice | doc | ptcont |
| 128 | .option dynorder (LTE-based Gear order) |
ngspice | doc | dynorder |
| 129 | sweep progress bar |
ngspice | doc | progressbar |
| 130 | optimize (Nelder-Mead) |
ngspice | doc | optimize |
| 131 | transient checkpoint/restart | ngspice | doc | checkpoint |
| 132 | .psp (PSS-based) |
ngspice | doc | psp |
| 133 | qpss two-tone (transient DFT) |
ngspice | doc | qpss |
| 134 | Harmonic Balance (hb) |
ngspice | doc | hb |
| 135 | HB source-stepping continuation | ngspice | doc | hb |
| 136 | two-tone QPSS via HB | ngspice | doc | qpss |
| 137 | qpac two-tone small-signal |
ngspice | doc | qpac |
| 138 | qpnoise |
ngspice | doc | qpnoise |
| 139 | cyclostationary qpnoise |
ngspice | doc | qpnoise |
| 140 | oscillator phase noise | ngspice | doc | phasenoise |
| 141 | qpxf |
ngspice | doc | qpxf |
| 142 | QP small-signal freq sweep (qpac/qpnoise/qpxf) |
ngspice | doc | sweep |
| 143 | least-squares optimize (Levenberg-Marquardt) |
ngspice | doc | fit |
| 144 | optimize -dparam (.param knobs) |
ngspice | doc | fit |
| 145 | optimize -mparam (.model knobs) |
ngspice | doc | fit |
| 146 | universal sweep command + .sweep card |
ngspice | doc | sweep |
| 147 | nested ?: compile time O(2^N)→O(N) |
openvaf‑r | doc | nested |
| 148 | compiler hardening (parser depth/include) | openvaf‑r | doc | robustness |
| 149 | Latin-Hypercube sampling | ngspice | doc | lhs |
| 150 | high-sigma analysis | ngspice | doc | highsigma |
| 151 | correlations + yield (Cholesky) | ngspice | doc | yield |
| 152 | KLU tuning (ordering/scale/BTF) | ngspice | doc | klu |
| 153 | trust-region optimizer | ngspice | doc | trustregion |
| 154 | envelope following | ngspice | doc | envelope |
| 155 | RC reduction (reduce/TICER) |
ngspice | doc | reduce |
| 156 | sparse RC reduction (scales to millions) | ngspice | doc | reduce |
| 157 | device aging (aging; static + dynamic) |
ngspice | doc | aging |
| 158 | power-grid EMIR (IR-drop + EM) | ngspice | doc | emir |
| 159 | real compact models (BSIM4/EKV) | both | doc | compactmodels |
| 160 | CMC coverage sweep (19 models) | both | doc | cmcsweep |
| 161 | dynamic C-V / fT vs built-in | both | doc | dynmodels |
| 162 | .hb dot-card |
ngspice | doc | hb |
| 163 | .qpss/.hbosc/.phasenoise dot-cards |
ngspice | doc | qpss · phasenoise |
| 164 | large-signal RF (P1dB/IP3) | both | doc | rfpa |
| 165 | model noise (flicker/thermal/shot) | both | doc | modelnoise |
| 166 | electro-thermal self-heating | both | doc | electrothermal |
| 167 | cross-model self-heating (4 classes) | both | doc | cmcselfheat |
| 168 | LNA noise figure (Friis / noise match) | both | doc | noisefigure |
| 169 | interactive syntax highlighting | ngspice | doc | syntaxhl |
| 170 | semantic highlighting (signals/exprs) | ngspice | doc | syntaxhl |
| 171 | KLU pole-zero (complex determinant) | ngspice | doc | klupz |
| 172 | KLU balanced PZ + full pivoting | ngspice | doc | klupz |
| 173 | eigenvalue pole-zero (pzeig) |
ngspice | doc | pzeig |
| 174 | help command crash fix |
ngspice | doc | helpcmd |
| 175 | conversion-matrix parametric-term fix | ngspice | doc | rfconv |
| 176 | driven-mode PSS (~1000x) | ngspice | doc | pssdriven |
| 177 | pnoise folding referee + flicker fix | ngspice | doc | pnoisefold |
| 178 | exact cyclostationary folding + HB DC fix | ngspice | doc | cyclofold |
| 179 | .tf/.sens/.meas audit + referees |
ngspice | doc | stdaudit |
| 180 | checkpoint under KLU (cross-solver) | ngspice | doc | checkpoint |
| 181 | integrator certified + ordfix |
ngspice | doc | corenum |
| 182 | pyplot autoscale by default |
ngspice | doc | pyplot |
| 183 | pyplot: distinct names, deck output, linewidth, backend |
ngspice | doc | pyplot |
| 184 | progress bar reaches 100% | ngspice | doc | progressbar |
| 185 | autodiff hypot/atan2 derivative fixes |
openvaf‑r | doc | vafautodiff |
| 186 | autodiff real-modulo derivative fix | openvaf‑r | doc | vafautodiff |
| 187 | simplifier inverse-function cancellation fixes | openvaf‑r | doc | mathident |
| 188 | warm-start Monte Carlo | ngspice | doc | warmstart |
| 189 | sweep -overlay waveform families |
ngspice | doc | sweepwave |
| 190 | sweep -vs nested multi-knob sweeps |
ngspice | doc | nestedsweep |
| 191 | .ac/.sp lin 2 off-by-one fix |
ngspice | doc | aclin2 |
| 192 | auto-checkpoint on interrupt (Ctrl-C) |
ngspice | doc | autosave |
| 193 | .pnoise honors sqrnoise (V/√Hz) |
ngspice | doc | pnoiseunits |
| 194 | optimize -method pso (global) |
ngspice | doc | psoopt |
| 195 | optimize -method de (global) |
ngspice | doc | deopt |
| 196 | optimize -method sa (global) |
ngspice | doc | saopt |
| 197 | 100-parameter curve-fit (raised caps) | ngspice | doc | opt100 |
| 198 | stb loop-gain + phase/gain margin |
ngspice | doc | stb |
| 199 | N-port Touchstone device (S-param, AC+tran) | both | doc | nport |
| 200 | pre_snp: built-in Touchstone→OSDI command |
both | doc | presnp |
| 201 | pre_snp scalability: fast vector fit (N→100) |
both | doc | presnp |
| 202 | .sp S-param inverse O(N!)→O(N³) |
ngspice | doc | spscale |
| 203 | .meas ac gain/phase margin + batch vdb fix |
ngspice | doc | acmargin |
| 204 | .option convhelp convergence ladder |
ngspice | doc | convhelp |
| 205 | pre_snp low-rank residue factorization |
both | doc | lowrank |
| 206 | optimize -center design centering (yield/Cpk) |
ngspice | doc | dcenter |
| 207 | eye diagram / jitter (SerDes) |
ngspice | doc | eye |
| 208 | pyplot -eye eye diagrams |
ngspice | doc | pyplot |
| 209 | hb publishes spectrum as nutmeg vectors |
ngspice | doc | hb |
| 210 | .pss dot-card + complex node vectors |
ngspice | doc | rfpss |
| 211 | static-analysis DC-op/import bug fixes | ngspice | doc | codeanalysis |
| 212 | crash hardening: 7 input-handling crashes | ngspice | doc | crashfix |
| 213 | openvaf crash hardening: 4 compiler panics | openvaf‑r | doc | vafcrash |
| 214 | openvaf whole-array coercion crash class (root fix) | openvaf‑r | doc | arraycast |
| 215 | $test/$value$plusargs |
both | doc | plusargs |
| 216 | optimize -method nsga2 Pareto front |
ngspice | doc | pareto |
| 217 | pyplot -hist histograms |
ngspice | doc | pyplothist |
| 218 | pyplot -contour 2-D maps |
ngspice | doc | pyplotcontour |
| 219 | openvaf preprocessor macro-arg hang + diag cap | openvaf‑r | doc | robustness |
| 220 | openvaf crash hardening r2 (10 panics) | openvaf‑r | doc | vafcrash2 |
| 221 | array/bus node ranges (a[0:1]) |
ngspice | doc | busnodes |
| 222 | parser fuzz hardening (7 crashes/hangs) | ngspice | doc | parserfuzz |
| 223 | XSPICE a-device model-type check (MIFgetMod) |
ngspice | doc | xspicemodel |
| 224 | array-node voltages in print/plot (v(a[0])) |
ngspice | doc | arraynodeprint |
| 225 | harden fft/deriv/fourier/meas/?: evaluator against fuzz crashes |
ngspice | doc | cmdfuzz |
| 226 | rawfile load crash hardening (fuzz: missing Flags: line → NULL deref) |
ngspice | doc | rawfuzz |
| 227 | Touchstone pre_snp crash hardening (fuzz: huge .sNp port count → heap corruption) |
ngspice | doc | snpfuzz |
| 228 | OSDI .osdi loader crash hardening (fuzz: reject implausible descriptor counts) |
ngspice | doc | osdifuzz |
| 229 | pre_osdi -f reloads a recompiled .osdi model in-session (no restart) |
ngspice | doc | osdireload |
| 230 | openvaf-r crash hardening round 3 (fuzz: 3 panics → clean errors) | openvaf‑r | doc | vafcrash3 |
| 231 | wrdata CSV output: set wr_csv + wrdata -csv flag (any position) | ngspice | doc | csv |
| 232 | harden KLU solver-glue (null-checks, collapse-map, bounds) | ngspice | doc | solverfix |
| 233 | fix KLU glue null-check order and collapse-map gaps | ngspice | doc | solverfix |
| 234 | loadpull PA load/source-pull contours on Smith chart |
ngspice | doc | loadpull |
| 235 | fix stb probe-lookup use-after-free; case-insensitive probes |
ngspice | doc | stbfix |
| 236 | fix .meas stack overflow on long measurement names |
ngspice | doc | measovf |
| 237 | fix .print/.plot/.four overflow on long node names |
ngspice | doc | nameovf |
| 238 | fix NULL-deref on malformed v(1, differential token |
ngspice | doc | malftoken |
| 239 | fix NULL-deref on 1-arg min/max/pow/pwr |
ngspice | doc | funcarity |
| 240 | fix XSPICE s_xfer OOB on static-gain transfer function |
ngspice | doc | sxfer |
| 241 | fix fft amplitude norm for non-power-of-2 records |
ngspice | doc | fftnorm |
| 242 | native N-port device via pre_snp -native (direct Y stamp, no OSDI) |
ngspice | doc | nport_native |
| 243 | pre_snp -osdi emits ref terminal for identical instance line |
ngspice | doc | presnp |
| 244 | fix nport unbound-node abort and pyplot -hist/-contour UAF |
ngspice | doc | crashfix2 |
| 245 | fix meas stray-= and altermod NULL-param derefs |
ngspice | doc | crashfix3 |
| 246 | fix OOB read in pwl/pwlts code models on mismatched arrays |
ngspice | doc | pwlfix |
| 247 | fix OOB/UB in table2d/table3d XSPICE models on degenerate tables |
ngspice | doc | tablefix |
| 248 | fix OOB in CPL coupled-line device on excess conductors |
ngspice | doc | cplfix |
| 249 | validate URC lump count and reject negative R/L/G/C in LTRA |
ngspice | doc | tlinefix |
| 250 | fix UB 1<<n shift in d_lut/d_genlut by capping input ports |
ngspice | doc | dlutfix |
| 251 | prove HB converges to exact steady state; tighten tolerance | ngspice | doc | hb |
| 252 | fix heap OOB writes in xfer/file_source file parsers |
ngspice | doc | filefix |
| 253 | rfstab two-port stability report (K, Delta, mu, MSG/MAG) |
ngspice | doc | rfstab |
| 254 | pyplot -smith Smith-chart view for S-params |
ngspice | doc | pyplotsmith |
| 255 | prove .disto exact vs HB; warn on B-source nonlinearities |
ngspice | doc | distoexact |
| 256 | fix DC false-convergence on singular-derivative B-sources | ngspice | doc | bsrcconv |
| 257 | extend DC false-convergence guard to .tran op point |
ngspice | doc | bsrcconv |
| 258 | extend false-convergence guard to .dc sweep cold-start |
ngspice | doc | bsrcconv |
| 259 | verify TRAP/Gear2/BE integration order and energy behavior | ngspice | doc | integaccuracy |
| 260 | verify LTE step-controller accuracy tracks reltol on stiff circuit |
ngspice | doc | integaccuracy |
| 261 | regularize sqrt() derivative singularity at V=0 in autodiff |
openvaf‑r | doc | vafsqrtguard |
| 262 | regularize fractional pow(V,Y) derivative singularity at V=0 |
openvaf‑r | doc | vafsqrtguard |
| 263 | harden 3 fuzz-found compiler panics (ddt/ddx/empty module) to clean errors |
openvaf‑r | doc | vafcrash4 |
| 264 | instance-array flatten O(N^2)->O(N) plus fix codegen stack overflow | openvaf‑r | doc | vafhang |
| 265 | fix laplace_*/zi_* bad-coefficient and empty-denominator crash |
openvaf‑r | doc | vaflaplace |
| 266 | announce linear solver once per multi-point run, not every analysis | ngspice | doc | solverannounce |
| 267 | sweep keeps bus-node names as ph[0] not ph_0_ |
ngspice | doc | sweepbus |
| 268 | wildcard model-param knob @*[param] sets every model in place |
ngspice | doc | sweepwild |
| 269 | @#*[param] instance-wildcard knob sets param on all instances |
ngspice | doc | sweepwild |
| 270 | sweep validates numeric bounds (reject non-numeric/inf/overflow) |
ngspice | doc | sweepbounds |
| 271 | fix let out-of-bounds read on empty left-hand side |
ngspice | doc | letoob |
| 272 | fix alter/sweep NULL-param SEGV on m-named device |
ngspice | doc | alternull |
| 273 | fix cmaths %/vector/unitvec double-to-int cast UB |
ngspice | doc | mathcast |
| 274 | fix vector index v[expr] non-finite cast UB |
ngspice | doc | idxcast |
| 275 | fix ifft() heap over-read on real-input vector |
ngspice | doc | ifftreal |
| 276 | fix rnd() non-finite operand cast UB |
ngspice | doc | rndcast |
| 277 | fix deriv() complex-vector over-read and wrong result |
ngspice | doc | derivcx |
| 278 | fix integ/deriv/ifft over-read when length != plot scale |
ngspice | doc | scaleguard |
| 279 | guard remaining (int)floor user-value casts (let/set/meas) |
ngspice | doc | castguard |
| 280 | fix OOB write on out-of-range single index in let assignment |
ngspice | doc | letidxoob |
| 281 | fix deriv() heap over-read on a partial last block |
ngspice | doc | derivgroup |
| 282 | fix asciiplot axis-label over-read on 3-digit exponent |
ngspice | doc | plotlabel |
| 283 | fix plot-coordinate UB casting non-finite doubles to int | ngspice | doc | plotcoord |
| 284 | @*[[p]] wildcard names the working instance-vs-model form |
ngspice | doc | wildparam |
| 285 | fix plot/wrdata/.meas OOB and complex-vector NULL deref |
ngspice | doc | veclenmix |
| 286 | fix const-fold int div-by-zero crash; wrapping arith matches codegen | openvaf‑r | doc | vafcodegen |
| 287 | fix const-fold branch leaving stale phi edge (broken SSA) | openvaf‑r | doc | vafcodegen |
| 288 | fix hypot declared unary but called binary (invalid IR) |
openvaf‑r | doc | vafcodegen |
| 289 | fix $clog2 invalid IR (llvm.ctlz missing type suffix) |
openvaf‑r | doc | vafcodegen |
| 290 | fix $temperature operator-arg wrong struct offset (SIGSEGV) |
openvaf‑r | doc | vafcodegen |
| 291 | fix max/min/abs in case default leaving block unsealed |
openvaf‑r | doc | vafcodegen |
| 292 | fix small-signal pruning crash on missing linear-contrib key | openvaf‑r | doc | vafcodegen |
| 293 | fix ddt(ddt(x)) directly-nested analog-operator crash |
openvaf‑r | doc | vafcodegen |
| 294 | fix Branch-to-Jump rewrite leaving stale condition use |
openvaf‑r | doc | vafcodegen |
| 295 | add regression guards for 4x4 matrices and param-slot readback | openvaf‑r | doc | vafcodegen |
| 296 | pyplot figure styling via 7 set vars (grid, legend, dpi) |
ngspice | doc | pyplotmore |
| 297 | pyplot -fft one-sided amplitude spectrum |
ngspice | doc | pyplotmore |
| 298 | pyplot -bode/-nyquist/-polar complex AC views |
ngspice | doc | pyplotmore |
| 299 | pyplot overlay sizes to longest run + pyplot_cursor crosshair |
ngspice | doc | pyplotmore |
| 300 | pyplot_mplcursors selects mplcursors hover-cursor backend |
ngspice | doc | pyplotmore |
| 301 | pyplot_cursor single master switch for interactive cursor |
ngspice | doc | pyplotmore |
| 302 | .meas avg clips window to [from,to] |
ngspice | doc | measwindow |
| 303 | .meas dc avg clips window to [from,to] |
ngspice | doc | measwindow |
| 304 | fix .meas dc integ/rms OOB on descending dc sweep |
ngspice | doc | measwindow |
| 305 | wcd worst-case-distance / MPFP high-sigma analysis |
ngspice | doc | wcd |
| 306 | fix fft() vector-expr amplitude scaled by padded size |
ngspice | doc | fftexpr |
| 307 | fix compiler crash on ddt reaching no contribution |
openvaf‑r | doc | vafdeadop |
| 308 | fix codegen crash on var read before its only-writer loop | openvaf‑r | doc | vafuninitloop |
| 309 | fix GVN crash re-queuing users in unreachable block | openvaf‑r | doc | vafgvnunreach |
| 310 | fix simplify_cfg const-fold leaving SSA-invalid phi |
openvaf‑r | doc | vafcfgphi |
| 311 | .control meas supports param/expr measurements |
ngspice | doc | measparam |
| 312 | fix XSPICE integrating code models to true O(h^2) transient | ngspice | doc | sxferorder |
| 313 | type-check $fwrite/$sformat format args, fix ddx(int) crash |
openvaf‑r | doc | vafargcoerce |
| 314 | fix const-fold int overflow abort and cap {N{...}} replication |
openvaf‑r | doc | vafconstlit |
| 315 | clean-error .tf/.pz/.disto crashes on degenerate circuits |
ngspice | doc | ngcrashanalysis |
| 316 | fix .meas avg dropping final timestep before to |
ngspice | doc | measavgwin |
| 317 | fix idt-IC codegen crash in statically-false branch |
openvaf‑r | doc | vafidtcfg |
| 318 | fix SFFM/AM voltage sources dropping DC offset before TD | ngspice | doc | sffmoffset |
| 319 | fix qpss transient-form spectral leakage into mixing bins |
ngspice | doc | qpssleak |
| 320 | sweep of a .param updates values in place, no full reset |
ngspice | doc | paramfastsweep |
| 321 | extend .param fast-sweep to subcircuit-internal device values |
ngspice | doc | paramfastsweep |
| 322 | optimize reuses .param fast-path, no per-eval reset |
ngspice | doc | optimize |
| 323 | arm optimize fast-path for small OSDI fits (weight OSDI 30x) |
ngspice | doc | optimize |
| 324 | fix $fatal stranding code in an unreachable block (2 shipped crashes) |
openvaf‑r | doc | vaffatalcfg |
| 325 | bound materialized size of {n{...}} (string arity hang, 2^40 u32 wrap) |
openvaf‑r | doc | vafconcatsize |
| 326 | fix shipped SIGSEGV: cross-namespace Value compare mis-typed init cache slots |
openvaf‑r | doc | vafinitcache |
| 327 | fix ddx crash on reverse-oriented or ground unknowns (now compile) |
openvaf‑r | doc | vafddxunknown |
| 328 | fix crash: dynamic array index used directly as a contribution RHS | openvaf‑r | doc | vafdynidx |
| 329 | fix crash: GRAVESTONE phi operand in the small-signal network builder | openvaf‑r | doc | vafssngravestone |
| 330 | fix compiler hang: ddx in a runtime loop now a clean LRM 4.5.1 error |
openvaf‑r | doc | vafddxloop |
| 331 | fix crash: BitSet::contains panicked outside its domain (dense rows) |
openvaf‑r | doc | vafbitsetdomain |
| 332 | fix wrong charge: summing 3+ ddt() terms dropped all but one |
openvaf‑r | doc | vafddtsum |
| 333 | fix crash: integer division by a literal zero SIGTRAPped the simulator | openvaf‑r | doc | vafdivzero |
| 334 | fix crash: INT_MIN/-1 and out-of-range shifts also SIGTRAPped (E-333 gap) |
openvaf‑r | doc | vafintub |
| 335 | fix wrong answers: != vs NaN, runtime shift masking, fast-math folds on doubles |
openvaf‑r | doc | vafieee |
| 336 | fix OSDI binding: param M taken as multiplier, case collisions, stale Jacobian count |
both | doc | osdiparam |
| 337 | keep x*0 fold: removing it shifted HiSIM2 drain current 10x (E-335 overreach) |
openvaf‑r | doc | vafmulzero |
| 338 | fix hang: 64-bit bus range overflowed the width guard (7.6 GB in 9 s) | ngspice | doc | busoverflow |
| 339 | fix crash: v() with 3+ node names double-freed (print/let/pyplot) |
ngspice | doc | vfuncarity |
| 340 | fix nondeterminism: implicit-net declaration order came from HashMap walk | openvaf‑r | doc | vafdeterminism |
| 341 | fix crash: sweep -analysis reset/remcirc freed the circuit mid-loop |
ngspice | doc | sweepanalysis |
| 342 | fix crash: rawfile Option: plots use-after-free; unset plots double free |
ngspice | doc | usrvarown |
| 343 | perf: sweep no longer O(N²) -- 26.6x at 16k points; cp_getvar built all 5 usrvars per call |
ngspice | doc | sweepscale |
| 344 | perf: .model params join the fast .param sweep's direct set -- now as cheap as instance params |
ngspice | doc | modelparamset |
| 345 | perf: sweep is now LINEAR -- plot naming no longer walks the plot list; 87x at 64k points | ngspice | doc | plotname |
| 346 | fix: fast .param path froze random draws reset re-drew; adds the Monte Carlo tier |
ngspice | doc | mcfastpath |
| 347 | fix: SSA re-builder no longer mints an Invalid phi operand (assertions build clean) | openvaf‑r | doc | ssavalid |
| 348 | fix crash: .pss segfaulted on a short argument list, and on harmonics 0 at full arity |
ngspice | doc | pssargs |
| 349 | fix crash: a mistyped node name on tf/pz/noise/sens/pss killed the process |
ngspice | doc | nodetypo |
| 350 | fix: a sweep now restores its .param; repeat sweeps no longer disarm the fast path |
ngspice | doc | sweeprestore |
| 351 | fix crash: sens killed ngspice on any OSDI model with an internal node |
ngspice | doc | osdisens |
| 352 | .disto for Verilog-A devices via OSDI 0.8 Taylor tensors; no variable-count limit |
both | doc | osdidisto |
| 353 | .disto now works for models using $limit, i.e. every production compact model |
openvaf‑r | doc | limitdisto |
| 359 | .disto rebuilt: tensors differenced from the analytic Jacobian in ngspice, so compile time and object size return to baseline |
ngspice | doc | osdidisto |
| 360 | fix: a second Verilog-A model no longer silences the first in .disto (per-model tensor cache) |
ngspice | doc | osdidisto |
| 361 | fix: ASan/UBSan in .disto — out-of-bounds read of the solution vector, and (int)NaN point count on degenerate sweeps |
ngspice | doc | osdidisto |
| 362 | fuzzing analysis-card sweep parameters: 7 fixes — counts cast to int reaching allocators, and an unbounded .dc sweep |
ngspice | doc | sweepguard |
| 363 | fix: two compiler crashes from cross-feature fuzzing — a block merged into itself (case in a do-while), and array parameters never instance-renamed |
openvaf‑r | doc | vafcfg |
| 364 | transient noise for OSDI devices — Verilog-A white_noise/flicker_noise injected into .tran, activating automatically when the deck has a trnoise source |
ngspice | doc | trnoise |
| 365 | fix: pz left device matrix bindings dangling, so a following hb returned a silently wrong result (and read freed memory) |
ngspice | doc | pzhb |
| 366 | fix: two more sites of the E-365 stale-binding class — pz then qpss, and a KLU NULL check that reported the NULL then dereferenced it |
ngspice | doc | pzklu |
| 367 | fix: sweep plots were named unknown<N> and the summary quoted a literal 'sweep' no plot answered to; eight plot types registered and the real name printed |
ngspice | doc | sweepname |
| 368 | fix: the periodic small-signal analyses named their plots wrong — pxf was unknown<N>, and pac/psp/pnoise/qpnoise/phasenoise/qpss each collided with an unrelated analysis |
ngspice | doc | periodicnames |
| 369 | fix: closes the E-365/366 stale-binding class — a KLU pole-zero binding was cleared only when re-established, so a later analysis dereferenced freed memory | ngspice | doc | klubind |
| 370 | fix: every .pz re-expanded a URC subcircuit, creating nodes past the allocated RHS — a heap-buffer-overflow hiding under a passing crash fixture |
ngspice | doc | urcpz |
| 371 | plot naming and dates: per-type numbering, so the first sweep is sweep1 not sweep500; every plot now carries a date (command-created plots printed (null)) |
ngspice | doc | plotname |
| 372 | fix: unset plots printed a spurious Internal Error: var 112 — a %d fed a dereferenced char *, on a branch valid input always reaches |
ngspice | doc | unsetvar |
| 373 | fix: a rawfile write/load round trip dropped the x-axis column from print (pl_ndims never restored) and renamed the .dc sweep axis to v(v-sweep) |
ngspice | doc | rawtrip |
| 374 | fix: setseed did not seed transient noise — the Wallace generator's pools were filled at startup from getpid() and never rebuilt |
ngspice | doc | setseed |
| 375 | fix: a loop that provably cannot finish is now a compile error — it used to emit a model that hung the simulator with no diagnostic; also closes 3 codegen crashes on disable |
openvaf‑r | doc | vafloop |
| 376 | fix: $dist_* returned real; the LRM makes it integer ($rdist_* is the real family) — needed a ficast in the lowering too, or the value read as 0 |
openvaf‑r | doc | distint |
| 377 | fix: OSDI diagnostics — name glued to its argument, no newline, no free, and LOG_LVL_MASK 8 made every severity report as OSDI(debug) on stdout |
both | doc | simparamdiag |
| 378 | fix: a Verilog-A $fatal during the operating point was read as non-convergence, so the gmin/source ladder retried it 373x and blamed timestep too small |
ngspice | doc | opfatal |
| 379 | fix: cargo test --workspace now builds and no longer overwrites checked-in source — verilogae drift, and three sourcegen generators that had fallen behind the files they generate |
openvaf‑r | doc | n/a |
| 380 | fix: a .dc sweep inherited stale integration coefficients from a preceding pss/tran/envelope, adding a spurious ag[0]*cap to every charge-storing device — 45% silent error |
ngspice | doc | dcstate |
| 381 | fix: stb handed its probe sources back with ac = 0 instead of their original values, so a following .ac returned all zeros |
ngspice | doc | stbrestore |
| 382 | fix: loadpull left the user's tuner R/L/C at the last swept grid point instead of restoring them, so a following analysis ran against the wrong network |
ngspice | doc | lprestore |
| 383 | fix: four unreachable plotabs[] entries named plots after a different analysis — envelope as op1, qpac as pac1, qpxf as pxf1, spectrum as sp1 |
ngspice | doc | plotorder |
| 384 | fix: a transient after sens returned every node zero — plus sens/sp aborting the process, a silent partial S-matrix at z0<=0, an OSDI DT alias, and two device-table flags |
ngspice | doc | sensstate |
| 385 | fix: sens ac zeroed VCCS/CCCS sources and sweep never restored an alter/altermod knob — found by a state-restoration audit that ships with it |
ngspice | doc | staterestore |
| 386 | fix: the six sensitivity queries returned the previous query's value on every device — first seen as denormal garbage from a reused static | ngspice | doc | senscplx |
| 387 | fix: an empty () expression crashed openvaf-r with an internal compiler error; -DNAME=VALUE defined a macro called NAME=VALUE; a bad TMPDIR aborted via an uncaught C++ exception |
openvaf‑r | doc | vafice |
| 388 | fix: -D macro values are now substituted (-DK=5.5 → 5.5, bare -DK → 1 as documented); the expression-depth guard says "nests too deeply" instead of a bogus token error |
openvaf‑r | doc | vafdefine |
| 389 | fix: a loop whose control variable never changes (k = k + 0) compiled and then hung the simulator; ANSI and combined analog-function argument declarations accepted; $table_model takes runtime array data |
openvaf‑r | doc | vafopenitems |
| 390 | fix: a case inside a do-while compiled into an infinite loop that hung the simulator; analog blocks inside generate were silently dropped; disable with an unknown label was a no-op; runtime $table_model arrays now sort and de-duplicate |
openvaf‑r | doc | vafcaseloop |
| 391 | fix: a repeated abscissa in a runtime $table_model gave a different cubic spline than the identical compile-time table; repeats are compacted out, boundary and end tangent following the live knots |
openvaf‑r | doc | vaftabledup |
| 392 | fix: module instantiation was unvalidated -- wrong port count, unknown port names and #(.param()) overrides naming nothing all compiled clean; plus a misbound $mfactor and a 64-knot $table_model sort limit |
openvaf‑r | doc | vafinstcheck |
| 393 | fix: a localparam could size a bus but not index one -- n[K] and n[2+1] were rejected as non-constant; all three places that resolve a bit-select now fold it, while a plain parameter is still refused (it binds at simulation time) |
openvaf‑r | doc | vafconstidx |
| 394 | fix: six ngspice/OSDI plumbing defects -- a subcircuit m= never reached an OSDI device and nested multipliers never compounded; instance temp= was read as Kelvin; .option scale and savecurrents never arrived |
both | doc | osdiplumb |
| 395 | fix: nine openvaf-r defects -- a seeded RNG is constant in a loop (a deliberate trade, now the rng_in_loop lint); laplace_np/zp/zd had the wrong DC gain; $table_model clamped instead of extrapolating |
both | doc | langguard |
| 396 | fix: ten openvaf-r defects -- $limit with a bad name or arity left a NULL pointer the model then CALLED (segfault, zero output); a collision warning fired on almost every industry model; @(timer) with period <= 0 fired every evaluation |
both | doc | limguard |
| 397 | fix: temp, dtemp and dt are readable on an OSDI device -- registered IF_SET without IF_ASK, so print @n1[temp] failed where every built-in answers; their ids had to move, dt's having collided with a terminal current |
ngspice | doc | instknobs |
| 398 | fix: four silent paramset defects -- it was the ONLY supply path bypassing parameter range validation; an override naming an undeclared parameter was dropped; a duplicate assignment let the FIRST win |
openvaf‑r | doc | paramsetguard |
| 399 | fix: thirteen silent round-13 defects -- {..} where the LRM wants '{..} made $table_model return 0.0 and noise_table contribute nothing; no analysis-name string was validated anywhere |
both | doc | VA_TEST corpus |
| 400 | fix: a contribution discarded by statement order (V and I on one branch) is reported | openvaf‑r | doc | VA_TEST corpus |
| 401 | fix: V(a,b) <+ 0 between two terminals was an open circuit, not a short |
both | doc | VA_TEST corpus |
| 402 | fix: an OSDI instance line with too few nodes was accepted silently | ngspice | doc | n/a |
| 403 | fix: an instance temp= inflated thermal noise by the nominal temperature |
both | doc | n/a |
| 404 | perf: a wide bus elaborated in time quadratic in its width — 31.5 s for [65535:0], now 0.62 s |
openvaf‑r | doc | n/a |
| 405 | fix: z-domain filters reciprocated every pole and zero; empty denominator hung the compiler | openvaf‑r | doc | filterforms |
| 406 | fix: a flow probe on a declared branch silently shorted the branch that was driven — new probe_only_branch_short lint |
openvaf‑r | doc | probeshort |
| 407 | feat: a genvar for-loop inside an analog block is unrolled at elaboration — three LRM examples now compile |
openvaf‑r | doc | genvarloop |
| 408 | fix: bracketed names missed their target -- a leading-zero node index, a parameter name containing brackets, a bus range on an output or IC card | ngspice | doc | busname |
| 409 | fix: a wildcard sweep knob was never put back, and printed a parser error that looked like a bad parameter name |
ngspice | doc | wildrestore |
| 410 | feat: @x1.r1[param] names a subcircuit device without its flattening type letter, in every accessor and in show |
ngspice | doc | hierdev |
| 411 | feat: a descending netlist bus range binds terminals in reverse and is now reported -- silent until now | ngspice | doc | busdir |
| 412 | fix: an OSDI operating-point variable read after .ac/.noise returned the small-signal solution, and changed with frequency |
ngspice | doc | opvarac |
| 413 | fix: .options savecurrents registered an empty vector for a multi-terminal OSDI device; now one waveform per terminal |
ngspice | doc | savecuroff |
| 414 | fix: a genvar loop body of any statement shape now unrolls, and a dangling else no longer re-binds to an enclosing if; a parameter named in another's from range stays settable |
openvaf‑r | doc | elabguard |
| 415 | fix: an OSDI @(timer) event is no longer stepped over and lost; the per-device noise summary total is the device's own, not the first source's input-referred figure |
both | doc | evtnoise |
| 425 | fix: a corrupt row in a $table_model/noise_table data file was silently dropped -- a tenfold wrong answer, and in the N-dimensional form the whole table contributed exactly zero |
openvaf‑r | doc | tabledata |
| 426 | fix: inputs that were never checked -- an invented analysis output node (an out-of-bounds read), sweeps running a different range than asked, meas on an unsaved @dev[param] answering 0.0, and m=-1 |
ngspice | doc | inputguard |
| 427 | fix: .dc @inst[param] applied values the model's from range forbids and published them at rc=0 while every other path refused; it also handed the device one value past stop, and a @(timer) event landing exactly on tstop never fired |
both | doc | sweepparam |
| 428 | fix: a Verilog-A device's INTERNAL node inside a subcircuit reads as v(x1.n1#mid) as well as v(n.x1.n1#mid) in every consumer; the type letter was a flattening artefact leaking into a node name, and .save failed destructively |
ngspice | doc | hiernode |
| 429 | fix: a .tf/.noise CARD naming a nonexistent node answered transfer_function = 0.0 with no diagnostic -- Enhancement-426's guard fires only on the .control path, since a card's invented node is created before the matrix is sized |
ngspice | doc | inputguard |
| 430 | fix: .probe's refusal now names what it accepts and points @device[param] at .save -- the refusal itself is correct, since .probe measures by inserting series sources; the warning also printed twice per token |
ngspice | doc | probeshort |
| 431 | fix: a sweep -output naming a vector that never resolves was recorded as a full column of zeros -- a plottable, entirely fictional flat line -- and is now named in an error, while genuine zeros and sibling outputs are kept |
ngspice | doc | sweepguard |
| 432 | fix: sweep -output takes every expression up to the next flag, not just the first token; also fixes an Enhancement-431 regression that dropped auto-collected curves |
ngspice | doc | sweepguard |
| 433 | fix: a double-quoted -analysis/-output kept its quotes in five commands (none called cp_unquote), and a subcircuit .model is now reachable as @x1.rmod[res] |
ngspice | doc | sweepguard, hierdev |
| 434 | fix: three silent wrong answers plus a truncation -- $simparam("temp") was never supplied, $simparam("abstime") returned the DC sweep voltage, -analysis was cut at 512 bytes, and .tf swallowed a model's $finish |
ngspice | doc | silentloss |
| 435 | fix: sweep accepts a subcircuit-local model parameter as @x1.rmod[res] -- it classifies knobs on its own path and missed Enhancement-433's fix, running on with a knob that never moved and drawing a plottable FLAT curve |
ngspice | doc | hierdev |
| 452 | fix: three -o destinations the driver never checked, each reaching the backend and failing there. openvaf-r m.va -o m.va wrote the compiled module straight over the SOURCE and reported success -- 111 bytes of Verilog-A became a 36 KB shared object, exit 0, source gone. An empty -o hit an .expect() in osdi::compile and an unwritable directory hit an assert_eq! on the object-emit result, so both PANICKED: exit 101, a crash banner and a request to file a GitHub issue, for a typo. The destination is now validated in the driver before any parsing, at the last point that knows both input and output; overwriting a previous OUTPUT is still allowed |
openvaf-r | doc | optpath |
| 453 | fix: five ways the compiler answered a request it could not satisfy. Batch mode keyed its cache on the source but not on the settings that decide the machine code, so -O 0 then -O 3 produced ONE entry and the second run handed back the debug build at exit 0; a --target request was answered from the HOST cache, so a Linux build came back an arm64 Mach-O. Cross-compiling then PANICKED (exit 101), since only the native LLVM target is registered -- fixing the key alone would have turned a wrong answer into a crash, so the target is now probed before any work is spawned and the error names what the binary can really emit. Separately the LRM null argument (two adjacent commas, the LRM's own spelling for a filter with no zeros) was rejected for all eight filters, and printing a comparison or an array literal crashed the compiler -- a Bool now prints 1/0 |
openvaf-r | doc | batchkey, nullarg |
| 454 | fix: .option autobus was decided in two places that disagreed, so the same card meant ON inside a subcircuit and off at the top level, or the reverse, depending only on the spelling. The subcircuit reader took = as a clean terminator and never read the VALUE, so autobus=0, =false and =no each switched the feature on silently; the top-level reader asked only for a BOOL, so autobus=1 -- not reported as unknown -- left a bus port unbound. Both now answer by one shared list of off-words, shared with savecurrents. A bus can also be passed down through nested subcircuits, which previously failed with too few nodes |
ngspice | doc | autobusopt |
| 455 | fix: seven ways a bad value was accepted, or crashed the compiler -- each a check that exists with a neighbouring spelling walking past it. Indexing a scalar (r[0] on a real, p[0] on a parameter) hit a panic! and exited 101 with a crash banner, for a typo. The value guards folded only a literal, so white_noise(-1e-18) was refused while white_noise(0-1e-18) was accepted and gave the positive power's noise silently. Reversed ranges spelled with inf were invisible -- from (inf:0) was accepted and enforced nothing. A constant outside a domain (sqrt(-1.0)) folded to NaN with no warning. And $rdist_uniform with reversed bounds, a zero-argument analog function and a discipline naming one nature for both potential and flow all compiled, the last producing a device that contributed nothing |
openvaf-r | doc | valguard |
| 456 | fix: analog initial ran on every evaluation instead of once (LRM 5.2). Its statements were concatenated into the front of the eval function, so they overwrote whatever the model had accumulated between timesteps -- destroying the one thing the construct exists for. A peak detector initialised there did not hold its peak, it followed the input back down (1.0 at the peak, then 0.4 and 0.1), and a counter never left zero; the identical models without the initialisation worked. Gated now on the same initial-step flag @(initial_step) uses, which fires once per analysis per instance -- once for an entire dc sweep however many points it has |
openvaf-r | doc | analoginit |
| 478 | fix: five defects of one shape from a bug hunt -- a check was performed and then something else was used. A count was validated with a float parser and consumed with atoi, which stops at the e: sweep lin 2e2 ran 2 points, montecarlo 2e2 drew 2 samples and printed a yield from them, and sweep lin 1e6 ran one point while the identical number written 1000000 was correctly refused as too many -- the float the validator computed was thrown away. The same block silently rewrote a count below 1, which for dec/oct changes the spacing and on a -vs knob collapses a sweep dimension, while every sibling (ac, dc, tran, montecarlo, highsigma, wcd, .for) names it. spec checked its step against the span but never against zero, so a negative step made a negative point count, a negative allocation and a NULL dereference -- a deterministic SIGSEGV in the shipped binary. Indexing a @dev[param] waveform returned the device's live value rather than the element, because the literal-index probe asks vec_get for a name that begins with @ and that is answered from the device, not the plot -- so every index gave the same number while length, maximum and wrdata were right all along. fourier guarded a fundamental below the time span but not one above the sample rate, printing THD: nan % with no diagnostic. And a loop command run as another's -analysis overwrote the shared progress state, so the outer bar vanished and the line read 100% with points still to run. Two lookalikes are deliberately left alone and pinned by checks. |
ngspice | doc | guardmatch |
| 479 | fix: every value guard only ever saw a literal. const_num had arms for literals, unary minus and the four binary operators -- and none for a path -- so naming a value made every check skip: white_noise(-1e-12) was rejected and white_noise(-1e-12*1.0) was too (folding is applied), but a localparam holding the same number was accepted in silence, though the LRM forbids overriding one so the compiler knows its value exactly. One missing arm, eleven guard sites -- $bound_step, @(timer), @(cross), transition, absdelay, zi_nd, last_crossing, white_noise, flicker_noise, the analysis/$simparam name checks and the parameter-range emptiness check. A negative transition time supplied that way drove a 0-to-1 signal to -2.5 V and made it respond before the input edge, rc=0, silent. The same assumption built the compile-time tables: a named entry became a zero entry, so one table written two ways gave $table_model 15 and 5 and cost noise_table its noise entirely. abs(-0.0) disagreed with the compiler's own folding (1/abs giving -inf generated against +inf folded). Adds the guards that never existed -- six $rdist_* shapes, $vt(T<=0), ddt/idt abstol, and a laplace_nd/laplace_zd denominator whose zero leading coefficient made the filter produce no output at all -- and makes a build that defines no module an error instead of a green Finished building over an empty .osdi. Pinned as deliberately unchanged: @(timer) period <= 0 fires once (LRM 5.10.3.3), noise_table_log's linear input, 0.0 * NaN (E-337), literal underflow, and a parameter default. |
openvaf-r | doc | constguard |
| 480 | fix: a check that could not fire where it mattered. Most of a bug-hunt round is not a missing check but a written one made unreachable. The duplicate-parameter test on a .model card was gated on its tracking list not being full, so once every distinct parameter had been seen once a repeat was never looked up -- a device with one model parameter could never report one; and it counted the model type token as a parameter, so .model rmod r(r=1k) was told parameter 'r' is set more than once ... remove one on the most ordinary card there is. .measure's edge count collided with the field's -1/-2 sentinels, so fall=-1 was read as no fall given and answered with a rising edge. % disagreed with itself: .param and a B-source call fmod while the control operator truncated both operands to integers and dropped the sign, so (0.5) % 3 was 0; E-273's range check is kept, so 1e30 % 5 still errors. An unterminated control block was checked per line, never at the end of the section, so an if with no end swallowed every command after it and exited 0. Adds the guards that were absent -- a transmission line's nl/f (f=0 printed a table of nan as an ordinary AC result), a switch's vh/ih, the code-model PWL's breakpoint order, a duplicate subcircuit parameter, a .dc step larger than its span -- and makes the limiter's reversed-limits message fire once at INIT instead of never in op/dc and 214 times in a transient. Pinned as deliberately unchanged: vector(-4), a negative pulse TR/PW, .dc start == stop, ac lin 1. One fix built, measured and reverted: protecting the constant plot by shadowing broke name resolution and hung lhs_examples, because run is itself a built-in constant. |
ngspice | doc | gatecheck |
| 481 | feature: .option silentports -- an opt-out for a netlist nobody typed. E-402 made an omitted OSDI terminal audible and that stays the default, for good reason: it looks exactly like a typo, and it dangles rather than grounding. What it did not account for is a netlist written by a tool -- a schematic front end emits the short form for every instance of a model with an optional thermal port, and the author cannot add the pin from the schematic, so a five-device sheet collects twenty-five lines about a choice nobody made (KiCad's exporter is the case that prompted it). The option suppresses that warning and nothing else; it is opt-in, and a front end that cannot edit netlists can ship set silentports in .spiceinit. It silences a warning, it does not repair a circuit: on the E-402 reproducer, omitting the thermal node still leaves all six singular matrix reports because BSIM-BULK pins that node with a potential tie-off -- the answer there is still to write 0 for the pin, and three checks pin the distinction. No openvaf-r change: the warning is raised in INP2N from the terminal count already in the .osdi descriptor, and the compiler prints nothing about an unused port. Registered in both the option name list and the type dispatch -- the first build worked while still printing unknown option. All nine on/off spellings tested. |
ngspice | doc | silentports |
| 482 | feature: .option silentports=ground -- ground the terminals a netlist leaves out, and give E-481's option a value table. E-481 turned the absent-terminal warning off; what it could not do was make the deck RUN. An omitted OSDI terminal is not grounded -- INP2N binds it to -1 and osdisetup.c gives it a private node <inst>#<term>, upstream behaviour that E-402 only started saying out loud -- and ten of the twelve corpus models with an optional pin tie it off with a potential contribution (Temp(t) <+ 0.0), which puts nothing into a node ngspice allocated itself, so the operating point dies on singular matrix. E-402's answer was always write 0 for the pin, and a schematic front end that hides the pin cannot write anything: on the E-402 reproducer, silencing removed five warning lines and left all six singular-matrix reports, quiet and still broken. =ground writes the 0 -- every omitted terminal bound to ground in the parser, so $port_connected() reports 1, the skipped branches are built, no private node is created, and the deck simulates: four.cir now returns i(vd) = -6.58515e-07, exactly what the hand-grounded deck returns, and the four BSIM-BULK decks ngspice itself ships go from 36/12/12 singular with every sweep aborting to zero of both. Three states, and the bare card keeps E-481's meaning: bare (also =dangle, =quiet) silences and leaves the terminal DANGLING -- held to the warned default value for value, and just as singular on the gated shape -- while =ground alone binds it, asked for BY NAME because it changes the circuit. The read order is load-bearing: silentports_mode() asks cp_getvar for the STRING first, since E-467's CP_BOOL coercion answers true for anything that is not an off-word, right for a two-state option and fatal for a three-state one; measured with the BOOL query first, =quiet and =bananna both GROUNDED the pin. An unrecognised word is named once per distinct spelling and falls back to the DEFAULT, not to either ON state. Verified groundports 59/59 (41/59 on the E-481 binary) while silentports still scores 24/24 unchanged. No openvaf-r change. |
ngspice | doc | groundports |
| 483 | feature: set qpss_tol / set qpss_maxiter, and harmonic-balance stall detection. QPSShb was called with its convergence bound and iteration cap COMPILED IN (0, 0, 60, 1e-10), and tol is an ABSOLUTE bound on the residual norm -- so what is reachable depends on the circuit: the diode two-tone deck settles at 1e-15, while an FET amplifier carrying tens of milliamps floors near 1e-8 and could never satisfy 1e-10. The way it failed was the real defect: the Newton iteration reached 9.4e-09 at iteration 4, a reduction of NINE ORDERS, then sat flat to seven digits for the remaining 55 iterations, after which the continuation ladder halved its step and walked all the way back to lambda=0 -- 1022 Newton iterations -- and reported a bare error 103. A good answer found in five iterations was discarded after minutes of work, six to seven of them at hb 4 4, which reads as a hang. Both numbers are now readable from a deck, asking BOTH published types (E-454: the spelling decides the type) and parsing with strtod not atoi so qpss_maxiter=2e2 means 200 and not 2 (E-478's trap); a present-but-unusable value is reported and the default kept. And four consecutive iterates that fail to improve the residual by 0.1% is a STALL, accepted as the answer only if it sits 1e6 below the residual the level opened with -- a stall that never came down is still a failure, it just reaches that verdict in a few iterations instead of the whole cap. Acceptance does not change the ANSWER: fundamentals BIT-IDENTICAL to a loosened-bound run and third-order products agreeing to 1.1e-05, about 0.0001 dB on OIP3. On the deck that prompted it, hb 2 2 goes from E_ITERLIM after 14 s to converging in 10 iterations under a second, yielding OIP3 = +34 dBm with both sidebands agreeing to 0.1 dB, while hb 4 4 -- a genuinely different fault the bound does not rescue -- now fails in 48 s instead of 6-7 minutes. Verified hbconv 23/23 (6/23 pre-fix); qpssleak, distoexact, plotorder and pzklu unchanged. No openvaf-r change. |
ngspice | doc | hbconv |
| 484 | fix: a converged flag is not a correct answer. E-483 made the harmonic-balance bound reachable and taught the Newton loop to spot a stalled residual, and left a hole: the STALL path reported what it settled for while the ORDINARY tolerance path reported nothing. So a deck that loosened the bound far enough had its solution accepted with a clean converged in 3 iterations and no hint anything was wrong -- on a two-tone FET amplifier, set qpss_tol=1e-1 at K=4 accepts a residual that came down by only ~100x and the third-order products land 6 dB out, silently. That is worse than the failure E-483 fixed: a refusal is honest, a wrong number is not, and E-483 had handed users a knob whose obvious misuse was silent corruption. The reduction behind an accepted solution is now reported on the ordinary path too, naming the residual, the reduction, the bound that permitted it and both remedies; the spectrum still prints, since the user may know exactly what they are doing. The threshold is CALIBRATED, not chosen -- QP_LOWRED_WARN is a deliberately different constant from QP_STALL_ACCEPT (one decides whether to accept, the other whether to believe) and its value comes from measurement: a 101x reduction puts OIP3 6.16 dB out and is warned, while a 55345x reduction agrees with the K=2 answer to 0.033 dB and is not. Two suite checks hold that bar from both sides, because a warning that fires on a good answer is one people learn to ignore. It does NOT fix hb 4 4, whose Newton step degrades as harmonics are added (5.3e8x reduction at K=2, 7.8e4x at K=3, 1.5e2x at K=4) for reasons ruled out by measurement one by one -- drive level, beta, is, alpha, cgs/cgd, the passive network, the topology, the DFT oversampling, the solve's pivoting and uninitialised memory -- leaving qp_build_matrix's harmonic-domain Jacobian as the open question. What it guarantees is narrower and worth having: when the solver cannot give a correct answer, it does not quietly give a wrong one. Also folds a const char * qualifier fix in com_qpss.c that E-483 introduced. Verified hbtrust 16/16 (10/16 pre-fix). No openvaf-r change. |
ngspice | doc | hbtrust |
| 485 | fix: eight guards that detected a fault and then used the bad value anyway. An hour-long hunt found the frontend and OSDI paths clean almost everywhere and the defects concentrated in the XSPICE code models, all of one shape. THE HEADLINE: the shared limiter helper cm_climit_fcn had its bail-out COMMENTED OUT -- it detected the crossed linear range, printed, and used the bad value regardless. It could not simply be uncommented: those lines assign the LOCALS and return, while the out-parameters are written at the end of the function, so restoring them verbatim would have left *out_final uninitialised -- very likely why they were disabled rather than repaired. Fixing the INPUT instead (clamping the smoothing half-width to half the limit span, which leaves hard limiting at the declared bounds) takes ilimit from 24.48 to 0.437 against rails of +/-1, its message count from 26 per op to 1, and its text from naming CLIMIT in a deck with no CLIMIT to naming nothing it is not. THE SAME SHAPE FOUR MORE TIMES: limit, int and d_dt never computed linear_range at all, so an oversized limit_range carried them to 24.5, 95.0 and 24.3 against limits of +/-1 -- and to 249999.75 at limit_range=1e6, unbounded and silent, while all three declare those limits as MANDATORY parameters; E-468's own comment says it added its checks as the CLIMIT sibling already does and ported two of them but not CLIMIT's actual one. pwl's monotonicity guard ended in break, leaving only the CHECKING loop, so the table was built from data just declared unusable and x=[0 2 1] answered 5.5 for an input of 0.5, above the table's whole y range; it could not return in place (x/y are STATIC_VAR-owned, so freeing double-frees and not freeing leaves a half-built table), so the test moved beside the size_error check it belongs with. hyst and slew had NO checks: an inverted in_low/in_high pair and an oversized hyst both left the block dead at 0.0, and a negative rise_slope drove the output to -2.0. AND THREE IN THE FRONTEND: sens ... ac validated nothing while .ac in the same file rejects the same arguments by name -- a reversed range silently swept a FABRICATED decade, 1e6 to 1e7 ascending; disto reported a DEVICE-parameter fault it does not have, identically for two different sweep faults; and .include <a directory> and source <a directory> succeeded in SILENCE (fopen() on a directory succeeds on macOS, the BSDs and glibc) so the deck solved a different circuit, v(out) 1.0 instead of 0.5, while .lib was already guarded; meas clamped a negative FROM correctly and then reported the window the user asked for rather than the one it used. THE REPAIR IS THE CODEBASE'S OWN PATTERN -- sine/square/triangle already do detect, report, substitute a safe value -- which is what makes these defects rather than design. THREE FINDINGS WITHDRAWN, one at fix time: sweep's negative step is corrected deliberately (com_sweep.c:2119, fix an obvious sign slip), XSPICE Limits: ARE enforced, and .nodeset on an OSDI internal node really is ignored -- the counter-evidence was a diagnostic ECHOING the card, matched by an unanchored value reader. Verified guardsweep 37/37 (24/37 pre-fix), every fix paired with the control that must not move. No openvaf-r change. |
ngspice | doc | guardsweep |
| 486 | fix: twelve guards that one sibling had and the other did not. An hour-long hunt found OSDI itself clean -- state restoration across all nine analyses including E-483/484's hb, array instantiation, instance-parameter ranges, round-34 still fixed -- and one shape repeated: the check exists somewhere in the tree and is simply absent next door. Nine of sixteen findings were that; often the sibling is in the same directory, once ten lines away in the same function, once in the same file's own history. THE HEADLINE: table2D's data-row loop is driven by the FILE and wrote table_data[lLineCount-1] unbounded -- while the x row, the y row, each row's WIDTH and even a premature EOF were all checked. A file declaring 3 y values and supplying 5 rows indexed past the allocation and SEGFAULTED (EXC_BAD_ACCESS at 0, rc 139, no diagnostic) from a twelve-line table file; too FEW rows left calloc's zeros in place, so a probe there returned 0.0 -- a plausible no current. E-247 had already fixed the OOB READ and the too-small case in this same file and left the WRITE, and the sibling table3D refuses the truncated file -- so the two disagreed about one input and only one crashed. THE SECOND: count_steps() is declared to return a POINT COUNT, and E-362's overflow guard signalled failure with return(E_PARMVAL) -- E_PARMVAL is 11, the caller never tested it, so an impossible sweep ran eleven points, with *stepsize never written because the return came first. Two silent repairs sat below it, rewriting a stated 0 Hz start to 1e-3 and a stated stop to a decade above the start -- in the LOCAL copy only, so the count used the repaired bounds while the sweep still ran from job->start_freq: .sens ac dec 5 0 1meg printed a table of all 0 Hz rows and .sens ac dec 5 1k 1k swept a full decade past the stop (6 rows where .ac gives 1). .ac, .noise, .disto and .sp all get both right, so the new rules are .ac's own. TEN MORE: d_state ran ROW 0 for a state its file never defines; file_source lacked the monotonicity check pwl has had since E-480; xfer's file path validated nothing while its table path ten lines above refuses the same data; hyst's unbounded input_domain drove the output to 250000 against declared limits of 0 and 1; core indexed B with H's size; mlin/cpline/cpmlin accepted impossible geometry (a NEGATIVE length returning a plausible 0.5011 against 0.4992); poly() reported each of its two faults with the OTHER's message; and four models were the only ones of twenty-plus with unbounded delays. DELIBERATELY UNCHANGED, with the built-in devices as the oracle: NEGATIVE capacitance and inductance are legitimate -- built-in C and L behave identically, so only ZERO is fixed (a real division by zero that had surfaced as Timestep too small; cause unrecorded); mlin's rho=0 looked like a perfect-conductor idealisation until it measured as NaN; and xfer duplicates stay legal because the table path allows them. Both headlines trace by git -S to 4f29ffad, the vanilla upstream import -- pre-existing, not regressions from this tree. Verified guardpair 65/65 (24/65 pre-fix, 41 checks discriminating). No openvaf-r change. |
ngspice | doc | guardpair |
| 487 | fix: every RF analysis leaves its results in a nutmeg plot. An RF result that is only printed cannot be plotted, printed again, wrdata'd, diffed against a reference or read by a script -- ngspice's post-processing is built on the plot, not the terminal. Six of the eight RF entry points published one; hbosc and phasenoise stored nothing, and since hbosc runs a transient internally to seed the oscillation, setplot after a successful run reported Current tran1 -- its own startup transient. THE CAUSE: E-209 added struct hbspectrum *out to HBanalyze so com_hb could publish; HBOSCanalyze computes the same (2K+1)*N spectrum in the same layout and never got the parameter -- one signature extended, its sibling not, which is the shape E-486 spent its length on. Both now hand their results back, and rather than let com_hbosc grow a second copy of hb_publish() the two share one parameterised publisher, so they cannot diverge again. hbosc also stores oscfreq -- the frequency an autonomous circuit solves for rather than is given. THE SUBTLE PART: ft_plotabbrev() matches by substring, so hbosc hit the hb pattern and came out named hb1, indistinguishable from a driven run in the same session, while phasenoise hit noise and collided with .noise. Both get entries ahead of the shadowing pattern per E-383's rule -- and plotorder_examples, which asserts that rule for every name in the tree, turned out to have a hand-maintained name list that was passing over these two vacuously; mis-ordering the entry now fails it with hbosc->hb. Two checks run each shadowing pair in one session, the only arrangement where the collision is visible. TYPES: loadpull's pout_dbm/gain_db are dB and now carry SV_DB, while gamma_re/gamma_im, pae, eff and stb's loopgain stay untyped -- dimensionless ratios with no enum member, where inventing one would be worse. NOT CHANGED: loadpull leaves 61 intermediate transient plots at -n 9 and 333 at -n 21; discarding plots a user may want to inspect is a behaviour change on a shipped command, so it is recorded, as are stb's printed-only margins. Verified rfplots 17/17 (7/17 pre-fix, 10 discriminating) with six checks passing before and after as the control that the working analyses did not move; hbosc 13/13 (6/13 pre-fix). No openvaf-r change. |
ngspice | doc | rfplots |
| 488 | fix: sweep temp swept nothing. sweep resolves a knob as a .model param, an instance/device param, or a deck .param -- the global circuit temperature is none of those, so a bare temp fell through to instance/device, alter temp= found no such device, and the sweep ran to completion over a knob that never moved: a full set of points, rc=0, and a plottable flat curve reading as temperature has no effect on this circuit -- a wrong conclusion rather than a missing one. E-431 removed that shape for an unresolved -output, and E-435's own comment in this file describes it for subckt-local model names; temp was a third instance, and the one users meet, since every other route already worked (.option temp=, set temp=, alter @dev[temp]=, sweep @#*[temp]). THE OBVIOUS FIX DOES NOT WORK: writing ckt->CKTtemp -- exactly what .dc temp does -- left the curve flat, because CKTdoJob opens with ckt->CKTtemp = task->TSKtemp. .dc survives that only because its whole sweep runs inside one CKTdoJob; sweep runs a fresh analysis per point, so the write was discarded before the next point solved. The task must move, so it issues option temp= -- how the frontend already moves it -- which also carries the value through the guarded OPT_TEMP funnel and so inherits E-426's absolute-zero refusal and E-440's sanity check instead of copying them. DELIBERATELY NOT IDENTICAL TO dc temp: over a temperature-driven node collapse dc temp returns 0.0 where a static op returns 0.5 -- it holds one setup for the whole sweep and never rebuilds (round-24, still open, not fixed here) -- while sweep rebuilds via E-471 and is right. The suite pins all three series against the static op and asserts dc temp disagrees, so a later pass that "fixes" sweep into agreement fails rather than adopting the wrong answer. STRICTLY ADDITIVE: a deck .param temp is tested first and still wins. Also matched to the oracle: an unphysical range is refused up front (left to the funnel, bad points are ignored one at a time while the axis still carries them, so a row labelled -600 C held the 27 C answer), and the axis carries SV_TEMP. Not fixed: sweep's other unresolvable knobs still warn and sweep flat -- only temp names something that exists. Verified sweeptemp 20/20 (10/20 pre-fix, 10 discriminating). No openvaf-r change. |
ngspice | doc | sweeptemp |
| 489 | fix: pow and ** join the constant-domain guard. E-455 refuses a constant argument outside a function's domain -- sqrt(-1.0), ln(0.0), asin(2.0) and four more -- because the fold yields a NaN that reaches the user not as a compiler message but as "Transient op failed, timestep too small", a convergence complaint for a NaN written literally in the source. pow was not in that list, and pow(-2.0, 0.5) IS sqrt(-2.0) -- a negative base with a fractional exponent has no real root. Before the fix it compiled clean and produced exactly that message; a zero base with a negative exponent (a division by zero) is refused too. TWO SPELLINGS, TWO CODE PATHS: pow(x,y) is BuiltIn::pow and judged in the call handler, while x ** y is BinaryOp::Power and never reaches it -- the first version guarded only the call and (-2.0)**0.5 still compiled, so a second arm judges the operator on the identical rule. Both inherit E-479's const_num, so a localparam base or exponent is seen as a literal is. THE TRAP: integer ** is a different operation -- IEEE 1364-2005 Table 5-6 via E-420 -- where a negative exponent is fully defined (2**-1=0, -1**-3=-1, base 0 is 'x=0). Those are correct answers, not NaNs; the first version judged them by the real rule and took vafdegen from 91/91 to ten failures, which is how the mistake was found. Unchanged by design: a run-time base/exponent and an overridable parameter, per E-455's convention. Two further findings from the same hunt were withdrawn on source evidence: real /0 is not the integer case (E-333's guard exists because integer sdiv x,0 is LLVM UB → poison → SIGTRAP; fdiv is well-defined IEEE), and an out-of-range dynamic array index stays the model's business -- seeding NaN was rejected because the same lowering serves variable indices, where a transient out-of-range value would poison the iteration; the fallback is now documented at the site. Verified powguard 18/18 (12/18 pre-fix, 6 discriminating), vafdegen 91/91, and the 754-file corpus recompiled byte-identically with zero new diagnostics. |
openvaf-r | doc | powguard |
| 490 | fix: mixing shorthand and written-out bits on a bus port silently miswired the device. E-444's .option autobus fires on a token count equal to the port count; positional binding covers a count equal to the terminal count. A line that leaves one port in shorthand while writing another port's bits out is neither, so N1 a b[0:2] for inout [0:4] a; inout [0:2] b fell through both and bound positionally against the flat terminal list -- a onto a[0], then b[0] onto a[1], b[1] onto a[2]. Measured on a model whose eight bits each carry a different conductance, every node sat one or more terminals off. The only thing said was E-402's warning about the terminals left over at the tail -- the symptom, not the cause -- so supplying the two nodes it asks for leaves the circuit wired entirely wrong and now silent. E-445's diagnostic exists for this exact mistake but sits inside the count check, one if from the line that needed it. Nothing here is ambiguous, so the fix does not guess: it walks the ports left to right and lets each token declare its form -- a bare name on a bus port is shorthand for its bits, a token already carrying an index (or ground, which E-445 established can never be indexed) means that port was written out, so take one token per bit. N1 a 0 0 0 reads correctly under the same rule. The rewrite is accepted only when the walk consumes exactly the tokens the line has, and the walk is bounded by that count because gettok_instance cannot tell where the nodes end. Where the counts do not reconcile no reading can repair the line, so it is refused there, naming the port, its width, the tokens written and the two counts that would work -- replacing a wrong answer, not a working deck. One rule, one reader: whether a token already carries a bit index is spelling-dependent (a[0] always, a_0_ only under autobus=kicad), and subckt.c already answered it, so the rule moved to INPbusTokenIndexed rather than become a second reader free to disagree -- the shape E-454 repaired in this same option. Two smaller fixes: .option autobus=1 reported a style that does not exist, the on-word list holding true/yes/on but not 1 because a deck card publishes it as a string where the comment assumed a number; and E-445's own message named a terminal where a port was meant, reporting a [0:2] port as 'b[0]'. Unchanged: autobus off, all-shorthand and all-explicit lines, and a short line with no shorthand token. Eighteen further shapes were measured and needed no fix. |
ngspice | doc | busmixed |
| 491 | fix: an unbounded number used as a length, and four wrong ones. Ten defects of one shape from a bug hunt -- a number the deck supplies, used without being measured against what it controls. THE CRASHES: set numdgt is the print precision and nothing bounded it, so printnum sprintf'd into BSIZE_SP(512) caller buffers and evtprint into a char[100] -- numdgt=510 with print ABORTED and numdgt=94 with eprint TRAPPED, from a plain batch deck, both exactly where %.*e's n+9 bytes say. printnum's own comment recorded the hazard and did not bound it, while the DSTRING sibling printnum_ds right below could not overflow -- which is why fourier, wrdata, write, display and diff were fine and only print crashed. It now takes the buffer size and clamps rather than truncates (a truncated number is not the number computed; past ~17 digits the rest is zero padding), and says so once. Both trace by git -S to 4f29ffad, the vanilla UPSTREAM import. THE WRONG NUMBERS: PTdivide added PTfudge_factor to every divisor, and that factor is gmin*1e-20 -- so 1/boltz was 42% low under .option gmin=1e-3 and 88% low at gmin=1e-2, and 1/0 returned 1e26/1e32/1e50 by gmin alone; the nudge now applies only to an EXACT zero, with a fixed epsilon. And B-source trig reduced with x-(int)(x/2pi)*2pi, UB above 2^31*2pi, so sin(1e20) gave +0.9993 where the answer is -0.6453 -- while numparam and Verilog-A both matched libm, making the B-source the sole outlier, the divergence E-399 forbids and ptfuncs.c's own comment quotes. THE SILENT REFUSALS: a sens filter matching nothing returned OK with no plot, leaving the PREVIOUS analysis current; meas enforced its analysis keyword for INTERVAL measurements (E-468) and ignored it for POINT ones, so meas tran ... FIND AT= read a DC sweep as a transient; s_xfer blamed the solver for a model error (a zero denominator went NaN and was reported as gmin stepping failed; num>den was announced 1238 times over 300 steps with rc=0 and no contribution); printf("oops ") reached users on stdout from two default: arms -- one of them the printer the internal check of parse tree failed message uses to show the expression; show printed ????????? where every sibling names the parameter; and a duplicate user .func was silently last-wins while shadowing a builtin warned. ONE COMMENT CORRECTED RATHER THAN OBEYED: E-440's claim that the singular routes clamp to HUGE so NIiter can reach for gmin is not what happens -- ifeval treats HUGE as an error flag and aborts; recorded, not unified, since changing it would move an answer a working deck gets today. Unchanged: ordinary precisions, 1/0 still finite, interval measurements, an unfiltered sens, and .func last-wins. |
ngspice | doc | numguard |
| 492 | fix: a node named only in a control position was silently created. E, G and S take a controlling node pair, bound exactly the way the output pair is (INPtermInsert, which creates the node) -- so a typo invented a node and the run continued against it. For E/G the invented node has no path to ground, the matrix goes singular, and the user is told check node nosuch -- a node they never wrote, reported as a fault in their circuit. S is worse: a switch only reads its control voltage and stamps nothing for it, so the matrix stays non-singular, the solve succeeds, and the answer is silently wrong -- S1 a b ctl 0 sw gives 0.999001 while S1 a b nosuch 0 sw gives 9.99999e-07, a factor of a million from one mistyped character, rc=0, no diagnostic. A phantom reference is dangerous exactly where it is READ but not STAMPED. Every other route already answered this: .ic/.nodeset report IC on non-existent node; F, H, W and B-source i() report unknown controlling source; all thirteen output constructs name a missing vector -- and warn_physics even validates the switch's own ron/roff/vh, while its control node was validated by nothing. The mechanism is E-429's, unchanged: devRef records whether a node was ever named by a device, and a control reference names without making real, so it no longer sets it. Marking stays with INPtermInsert everywhere else, so a control node connected somewhere -- including the source's own output -- is never reported; the check runs in pass 3, like .ic's, because only then is did anything connect to this? answerable. It refuses rather than warns, since a switch would otherwise answer from a node not in the circuit. Two diagnostics that named the wrong thing: CKTop blamed Verilog-A for faults that were not -- E-399 argued its E_PANIC test exact because E_PANIC and E_ITERLIM differ (true), but the invariant the message needs is E_PANIC ⟺ a VA $fatal, and E_PANIC has ~10 producers, so .option klu with a current-source-only matrix and no VA device at all claimed one had raised $fatal, through op/dc/ac/tran alike; and KLU printed nine lines for one condition, its own PreOrder comment saying an empty matrix is legitimate while three sites reported it per call and Solve added two NULL-object errors that are its consequences. Unchanged: control nodes connected anywhere else, F/H/W, a real $fatal, and either solver on an ordinary circuit. |
ngspice | doc | ctrlnode |
| 493 | fix: three names the simulator would not look up. One shape, three times: a name the user wrote was not looked up where it was written, and what came back described something else. showmod <MODEL> could not find the model -- the device generator reads a bare word as an instance name and only #name as a model, so with .model dm d used by D1, showmod d1 and showmod #dm both printed it while showmod dm said No matching instances or models. The command's help calls its argument models, and its write sibling altermod dm is= takes the model name directly -- so the one command dedicated to models could not be handed one; OSDI models were affected identically, the defect being in the name grammar. Retry, not reinterpret: the instance reading runs first and unchanged, and only when nothing matched is each bare word retried with #. A saved name that matched nothing was dropped in silence -- Pass 1 marks the .save/.probe items it places and left the rest unmarked, so .save v(n) v(nosuch) recorded v(n), discarded the typo and said nothing; .probe v(nosuch) shares that path, which is why a mistyped node was silent while a mistyped source in the same card is reported, and why E-418 already spoke for @dev[param]. It warns (an absent vector is not a wrong answer) and honours savesused first, so all/allv and other-analysis items are never flagged. A resistor model named r was unreachable -- INP2R excluded the token r outright, which R1 a b r=1k needs, but that also locked out a model called r: .model r r rsh=1k bound neither model nor value and the device came out 1 mΩ with resistance too low or not given, the symptom rather than the cause. Every other name works, including every other resistor keyword (rsh, l, w, tc1, temp, m, scale); the two spellings differ by what follows the token (r= vs not), so asking that keeps r=1k intact. Unchanged: showmod by device/#/bare, show, altermod, .save all/allv/.probe alli, @dev[param], and r=1k/R=2k/plain values/r=4k winning when a model r also exists. |
ngspice | doc | namelookup |
| 494 | fix: two ways a B source expression disagreed with every other value path. Round 54 compared the B source path against the paths that read the same number on the same deck. x/0 lost its sign -- E-491 replaced a gmin-derived fudge factor with a fixed epsilon and wrote the nudge as arg2 = (arg1 >= 0) ? EPS : -EPS, meaning to keep the sign of the numerator; a negative numerator was then divided by a negative epsilon, so every x/0 came out positive: v(p)/0 with v(p) = -3 returned +3e+32, and -1/0, (0-2)/0 and E0 ... vol='-1/0' were all positive too. Every case E-491 measured had a positive numerator, which is why its own suite did not see it. A divisor of exactly zero has no sign to recover, so the side zero is approached from is now chosen once -- the epsilon is unconditionally positive -- and the quotient keeps the numerator's sign, which is what lim(x/eps), eps to 0+ gives. Numeric literals were rounded to 11 significant digits -- every B line is rewritten by inp_modify_exp(), which re-emitted each literal with "%18.10e", so v=1.2345678901234567 reached the parser as 1.2345678901e+00 (relative error 1.9e-11) while the same literal on an R, C or V card, in a .param, or as an OSDI .model parameter kept all seventeen. It reached the arithmetic, not just the printout: tan(1.5707963267948966) returned -1.96e11 against libm's 1.633e16 -- the argument was wrong, not the tangent. More digits would not have fixed it: INPevaluate() reads the text back and accumulates the mantissa as mantis = 10*mantis + digit, losing a bit past 2^53, so a fixed "%.17e" put 0.7853981633974483 1 ulp out when the user's own sixteen digits would have survived -- the new inp_num_text() emits the shortest text that round-trips, trying 15, 16 then 17 digits. Deliberately not widened: INPevaluate's own scaling still costs 1-2 ulp, but a V source shows it identically, so the suite pins parity and a 2 ulp bound rather than exactness; and mkb()'s unguarded constant fold of / never produced an infinity from B, E or G in any form probed, so it was left alone rather than fixed past the evidence. Unchanged: E-491's magnitude and gmin independence, ordinary divisors, SPICE suffixes, pwl B sources, current B sources, and E/G expressions, which never passed through the rewrite at all. |
ngspice | doc | bexprvalue |
| 495 | fix: seven ways a decision made once was never revisited. Round 55 probed model binning, untouched by any earlier round. The tolerance was ABSOLUTE -- is_equal compared fabs(a-b) < 1e-9 on values in metres, a fixed one-nanometre slop, so a device up to 1 nm outside every declared bin was silently placed in one (l=31n bound to a bin reaching 30n; 31.1n refused). The magnitude is fixed, so as a fraction it grows without bound: 0.03% of a 3 um width but 5% of a 20 nm channel; it is now relative. ADJACENT BINS OVERLAPPED -- in_range's own comment states min <= value < max while the code also accepted is_equal(value,max), so a device on a shared boundary matched both neighbours and .model declaration order decided: reversing two cards moved l=1.999u -- unambiguously inside the lower bin -- into the upper one and changed i(V1) by 2.95x. Selection now runs the strict rule first and the closed one only where it matched nothing, so the top bin's lmax still binds and nothing that works today moves. AN OSDI MODEL COULD NOT BE BINNED -- the set was eleven hardcoded built-ins, so a Verilog-A model written as a PDK writes one died with Unable to find definition of model nv for a model defined twice; OSDI is now asked through E-323's predicate and the four bin limits are consumed like level. A DEGENERATE agauss/gauss WAS SILENTLY FLAT -- a zero or negative variation or sigma returns the nominal for every draw, so a montecarlo over a parameter that never moves reported 100% yield where the real spec gave 12%, one character apart and silent under warn_physics; now audible, behaviour unchanged, and unif/aunif/limit deliberately untouched. .dc NEVER REVISITED WHAT SETUP DECIDED -- E-471's own comment says .dc sets the circuit up once and walks its points inside the analysis and that a frozen topology makes the sweep quietly draw a flat line; it gave sweep the machinery to rebuild and .dc never got it, so a swept l/w leaving its bin kept the parse-time model (2.9x out) and a swept parameter changing an OSDI node collapse kept the old matrix (a flat line). alter and sweep both get these right, so .dc now refuses the point it cannot compute and names sweep -- the collapse read from a flag OSDItemp already sets and CKTdoJob already consumes (E-417), which .dc simply never asked. THE ASCII RAWFILE WAS ONE DIGIT SHORT -- DEFPREC 15 emits sixteen significant digits where a double needs seventeen, so write+load changed the last ulp while the binary format was exact (%.15e fails to round-trip 51390 of 200000 random doubles, %.16e none). Unchanged: bin selection that works today in either card order, alter, sweep, a genuine device refusal, source and m sweeps, an unbinned model's sweep, the binary rawfile and an explicit rawfileprec. |
ngspice | doc | binstale |
| 496 | fix: a plot keyword is not a signal name. Reported from use: with .option saveused, pyplot v(a[0]) xlabel 'something' printed save 'xlabel': nothing of that name is in this analysis -- xlabel is the plot command's own grammar and the author never asked for a vector of that name. E-469's bare-word scan took every argument of an output command that was not a number, a redirection or an expression, with no knowledge of those commands' keywords: all 22 plot keywords (xlabel, ylabel, title, vs, xlog, xlimit, samep ...), hardcopy likewise, and meas worst -- meas tran m1 FIND v(b) AT 50u offered tran, m1, find, at. The answer was never affected: E-469 over-collects deliberately (under-saving would cost the answer) and a save matching nothing produces nothing -- the vector asked for returns bit-identical with zero or three stray keywords. The names were collected in silence until E-493 added the unmatched-save warning, which exposed the flaw rather than causing it. Fixed in two parts, the obvious one second. (1) An INFERRED save is never reported -- E-493's warning exists to catch a name the author wrote and got wrong, so registration records which is which (db_auto -> save_info.autosaved) and the warning skips what saveused guessed; set only around ft_saveused's own com_save, so .save, save, .probe and savecurrents are untouched. This covers every keyword of every command, present and future. (2) The plot family now knows its own grammar and meas contributes no bare words (its vectors arrive as v(...)/i(...), already taken by the reference scan) -- but on its own that would be the E-487 trap, a hand-maintained list that rots invisibly, which is why (1) is what answers the report. Unchanged: what is SAVED -- every value matches the same run without the option; all still means everything; wrdata's file name is still not a vector; an explicit .save still makes saveused stand aside; a node genuinely named title or vs keeps its value; and .save/.probe of a name matching nothing still warns. |
ngspice | doc | savekw |
| 497 | fix: constraints the documentation states and the code did not check. Round 56 mined the manual for stated numeric constraints -- three of five findings came out of one `grep -E "must be | should be". **disto's f2overf1 ratio was unvalidated**: the manual says it *should be a real number between (and not equal to) 0.0 and 1.0*, yet 0, 1, 1.5, 2 and -0.5 were accepted in silence and **move the answer** (the 2F1-F2 product read 1.695 / 1.630 / 1.477 / 1.580 against 1.711 for a legal 0.5). At ratio 1, F2 = F1, so the plot still labelled *IM: f1-f2* holds a product at **DC**; at a negative ratio the second tone sits at a negative frequency. **Both neighbouring cases in the SAME switch** (D_START, D_STOP) already test their value and return E_PARMVAL. **Refused, not clamped** -- the ratio *is* the experiment, and any substitute would answer a different question silently. **The guard is NARROWER than the manual, deliberately**: the first version took the manual at its word and refused everything outside (0,1), and [E-255](enhancements_doc/Enhancement-255.md)'s suite caught it -- that suite measures IM3 at f1 = 1.0 GHz and **f2 = 1.3 GHz** and proves the result machine-exact against an independent QPSS harmonic-balance engine, so F2 above F1 is well posed and a verified working deck outranks the sentence in the manual. Only <=0(second tone at DC or negative) and==1 (F1-F2 would be DC) are refused. **setseedtruncated a fractional seed**:%dstops at the first unusable character, so2.5became **2**, while0, -3andabcare each named and the siblingrepeat names a fractional count outright. **s_xferindexedint_icby the WRONG array's size** --PARAM(int_ic[den_size - 2 - i])with nothing consultingPARAM_SIZE(int_ic), though the manual states the relationship: too short and the unreached initial conditions read zero, taking v(out)at 10 us from **7.00005 to 4.99995e-05**; too long and the surplus was never read. **The oscillator family went SILENTLY DEAD** --sine, square, triangle, oneshotannounced an array mismatch and returned **without setting an output, every evaluation**: rc=**0**, source held at zero, and **2025 message blocks over 2 ms** (about a million for a 1 s run). That is the shape [E-491](enhancements_doc/Enhancement-491.md) fixed ins_xferand named there -- *say it once and stop*;d_osc/d_pwmcheck insideINITand are deliberately untouched. **A duplicate.paramwas the only one of its family to pass in silence** --.funcwarns (E-491),.modelwarns,.subcktwarns -- and it resolves the **other way**, taking the LAST where.model/.subcktkeep the FIRST, so two included files that each setvddagree or disagree purely by **include ORDER** and a deck's own.paramis displaced by a library included after it. Which value wins is unchanged; it is only made audible, scoped to top-level cards so a subcircuit's own parameters are not mistaken for duplicates. Also fixed:D_STOP/N_STOPreset the **start** frequency field on a refused **stop** frequency (no observable consequence). **Unchanged**: every meaningful disto ratio incl. above 1, and single-tonedisto; setseedwith an integer or blanks and its existing messages;s_xferwith a correct or absentint_icand E-491's num>den guard; matching oscillator arrays; a single.param, two names on one card, subcircuit parameters, and the .func/.model/.subckt` messages. |
ngspice | doc |
| 498 | fix: per-run state left stale by the setup-reuse fast path. E-471 lets sweep, optimize and montecarlo -warm keep a circuit standing between points -- CKTdoJob skips CKTunsetup/CKTsetup and re-runs only CKTtemp. That reasoning was about topology, and for topology it is right; what was never asked is which state DEVsetup initialises that is not topology at all. Two devices keep transient run state on the instance. A VOLTAGE SOURCE STOPPED SCHEDULING BREAKPOINTS ENTIRELY -- VSRCbreak_time is the source's breakpoint cursor and VSRCaccept arms the next edge only when CKTtime >= VSRCbreak_time, seeded to -1.0 in vsrcset.c; reused, the instance carried the PREVIOUS run's break time, a value at or past that run's TSTOP, so the test was false at t=0 and stayed false, and a PULSE or PWL source armed no breakpoints at all (native timepoints 81 vs 103; PWL corners landed on exactly 0 of 5 vs 5 of 5). Against a standalone run of the identical circuit, maximum(v(n)) -- a grid-INDEPENDENT quantity, so a different ANSWER and not a different sampling -- was +16%, +44%, -10% across a 5-point sweep, 106% at other spacings, in silence; a 0.3 ppm change of the swept value sufficed, because the schedule is either armed or it is not. Only a sweep's FIRST point still had a correct schedule, so the same resistance gave two different answers depending on the direction of travel (R=1000: 0.40217511 up, 0.33963131 down). optimize RETURNED A WRONG FIT AND CALLED IT CONVERGED -- fitting R for maximum(v(n))=0.20 gave 2501.777336 with reuse off (hits 0.2000000003) against 2156.690117 with it on (gives 0.2264, 13% out), in 106 evaluations rather than 67. This contradicts E-471's own comment that reuse changes nothing a user can see except the time ... a few ulp, whose stated cause is refuted too: KLU and Sparse give bit-identical deviations. OSDI's last_crossing CACHE -- crossing_time[] is per-run state whose contract osdiaccept.c states outright, starts at 0.0 ... before any crossing has been observed; only OSDIsetup seeded it, so a reused point held the previous point's crossing and a 100 kHz sine over 40 us read 3e-05 at t=0 of points 2..5 instead of 0. Its sibling absdelay was already right, re-seeding on is_init_tran -- the first transient call of each run, not setup. Both re-armed in DEVtemperature, which CKTtemp runs once per job on BOTH paths and which does NOT run on a resume, so a continued transient keeps the schedule it paused with. Why the suites stayed green: reusesetup_examples and reuseloops_examples contain no PULSE or PWL source between them, and the two shipped examples that do combine sweep+tran+PULSE use -overlay, whose resampling damps the error below their tolerances -- both moved when this was fixed. Unchanged: the fast path itself (the suite asserts the setup is still reused at 4 of 5 points, 0 rebuilt), sources registering no breakpoints (SIN, EXP, dc-only), a PULSE current source (ISRC recomputes from CKTtime and keeps no cursor), and op/ac/tf/noise. Reported, not fixed: under KLU, sweep -analysis ac with reuse returns 0.0 for reused points and crashes in about one run in ten, non-deterministically -- a different defect in a different layer, reproducing on the shipped binary and needing its own investigation. |
ngspice | doc | reusestate |
| 499 | fix: loop-command arguments, verdicts, and a KLU refactor kind. Round 58 re-probed the sweep/optimize fast path after E-498; the reuse arithmetic was clean, but everything around it was not. A COMPLEX REFACTOR WAS HANDED A REAL FACTORISATION -- klu_z_refactor refills a factorisation in place, walking the L/U index arrays klu_z_factor built; given a REAL Numeric it walks half-sized arrays with complex strides, reads and writes past their ends, and a later klu_free_numeric frees what it scribbled (the malloc abort names object 0x3ff0000000000000 -- the bit pattern of the double 1.0, a matrix VALUE freed as a pointer). Reachable because every AC/SP/NOISE run follows a real operating point and E-471's reuse keeps the matrix standing: under .option klu, sweep -analysis ac/noise returned 0.0 at every reused point, optimize -analysis ac fitted a parameter 10x wrong saying converged, and sp SIGSEGV'd on 9 of 10 runs. SPARSE and every real analysis (op/dc/tran/tf/pz/disto) were always fine. Both refactor directions now check the kind. FOUR ARGUMENTS PARSED BY A WEAKER PARSER THAN THE ONE IN THE SAME FILE -- optnum() (SPICE suffixes + scientific notation) served the bounds, -target and -spec, while -maxiter/-samples/-swarmsize used bare atoi and -tol bare atof, so 1k meant 1000 in a bound and 1 in -maxiter on the same line: -maxiter 1k/2e2 ran 1-2 iterations and returned 1495 where 1000 returns exactly 1500, -samples 2e2 ran 2 Monte-Carlo samples while design centering still printed a yield and a Wilson interval, and abc was 0 everywhere in silence. sweep lin 2e2 (200 points) and montecarlo 2e2 (200 samples) were already right. -seed HAD THE MATCHING HOLE -- E-497 taught the setseed COMMAND to refuse 3.7, but the OPTION truncated it silently and -seed 0/-3 left the run unseeded and NOT reproducible; all four sites now apply the command's rule. CONVERGED WHEN NOTHING WAS OPTIMISED -- a parameter the objective cannot depend on, or one that does not resolve at all, hands the STARTING value back after 3 evaluations; -target x 0.4 0 printed sum-sq residual = 0, the most convincing number available, for a fit that never happened; and a result pinned to a bound was not distinguished from a minimum. Said now, as E-438 already says it for failed evaluations. A KNOB THAT NAMES NOTHING fell through sw_kind's SW_ALTER fallback to a full set of points, rc=0, and a plottable FLAT curve named after the typo -- the shape E-435, E-488 and E-431 each removed elsewhere; and list 1k inf 2k 3k ran ONE point while calling the valid 2k/3k unrecognized. -overlay DISCARDED THE SOLVER'S TIMEPOINTS -- resampling onto a UNIFORM grid of the same count kept the number and threw away the placement, reporting a peak 39% low (0.244 vs 0.402) from the same 121 points; it now uses the union of the runs' own timepoints, bounded, and says which grid it used. Its %g names collided (three vectors all vn_1000), now shortest-round-trip per E-494. TWO WARNINGS CONTRADICTED EACH OTHER on the same points: recorded as NaN (true) and those entries are zero (false). Unchanged: a real fit is not annotated; every legitimate knob spelling, a good list, and a two-knob sweep still parse; a transient sweep and the reuse tally are untouched; a legal -seed still pins the run. |
ngspice | doc | loopguard |
| 477 | feat: a progress line for the commands that run N analyses in a loop -- sweep, montecarlo, highsigma, wcd. All four silence per-point chatter (E-130's ft_optimizing), so they printed a banner and then nothing at all: a forty-point sweep of a slow transient looked hung for minutes. Letting the inner analysis's own bar through is the obvious fix and the wrong one -- it runs 0 to 100% for every point, so it resets N times and never says how far the sweep is, and it redraws the same line with a carriage return, so the two would overwrite each other. One line now carries both: sweep: point 7/40 [===== ] 17% (tran 63%), with the inner fraction also advancing the outer bar within the point so it moves smoothly instead of stepping once per analysis. Two drivers are needed and neither suffices alone -- while a point runs the command is blocked inside the analysis so only the output path can refresh, but the default analysis is op, which produces no swept data points and never reaches it, so the command also draws at each point boundary. wcd iterates to convergence and gets a counter rather than a bar against maxiter that would jump to done. Auto-enabled only on a terminal, since the line is redrawn in place; loopbar/noloopbar force it, all eight spellings tested. A standalone analysis keeps its own bar and optimize stays silent. |
ngspice | doc | loopbar |
| 476 | fix: four defects of one shape from a bug hunt -- the simulator's account of itself did not match what it does, and none of them raised an error. An OSDI operating-point variable answered with a number when nothing had computed one: the opvar storage is zeroed, so a read after an aborted analysis, or with none run at all, returned a clean 0.0 that is indistinguishable from a real current or conductance, while i(v1) in the same print said it had no value. ngspice already applied that rule to show, which is why it never displayed one; the direct @dev[opvar] read was the path without it. Every OSDI device's integrated noise total was named with a trailing space -- display padded it away and every read missed it, so per-device noise attribution was advertised and unreachable while its own per-source children and the grand total were fine. A model declaring dtemp or temperature at model scope had alter @n1[dtemp]= accepted, discarded and unreported, where every other model-scope parameter is refused honestly. And the compiler warned that $simparam("temp") is unserved and fatal, of a name ngspice has served since E-434 -- three copies of one list, two of them stale, now one that the diagnostic is built from. Three lookalikes are deliberately left alone and pinned by checks. |
both | doc | reportguard |
| 475 | fix: seven defects of one shape from a bug hunt -- a value the deck stated was discarded and something else quietly put in its place, or a refusal named the wrong fault. sin with an explicit freq=0 took its frequency from TSTOP, so lengthening the run changed the stimulus (a zero frequency is DC, unlike a zero rise time; omitting it still defaults to 1/TSTOP). An unknown parameter on a subcircuit call was silently dropped and the default used, while the same mistake on a .model warns and on a device instance is an error. A failed .measure left the previous answer under its name, undetectable since sim_status is not set by meas. tran TMAX had no validation and a negative one reported singular matrix against the circuit. Six options accepted nonsense in silence while their siblings refused it. And two E-474 refusals named the wrong fault -- every unevaluable {{ }} claimed to be outside a loop, and a shadowed loop index died with device already exists. Three lookalikes are deliberately left alone and pinned by checks. |
ngspice | doc | explicitvalue |
| 474 | feat: .for / .endfor in the netlist. A run of near-identical instance lines differing only by an index -- a ladder, a stack of periodic sections -- is long to write by hand and wrong in ways that are hard to see, since one node name out of step still parses and still simulates. .for i in range(1,4) around XP{{i}} P{{i}} P{{i+1}} hl_periodic n1={nL} expands to exactly those four lines. range(first,last) includes both bounds (four iterations, not Python's three), with optional step, or an explicit list [7,2,9]; {{i}} and {{expression}} (integer arithmetic) are substituted as text, so they build node, instance and model names alike; loops nest, and an inner bound may be an expression over an outer index. Double braces because numparam owns single ones -- a body carries both. Expanded before every other stage, so nothing downstream knows it exists; the bounds therefore cannot be .params and that is refused by name. 14 malformed forms refused, one message each. |
ngspice | doc | forloop |
| 473 | fix: the Monte Carlo fast path armed on a draw it could not push -- a silently wrong and unstable yield. E-346 pushes re-drawn random values into the live circuit instead of re-sourcing per sample, which is sound only if every use is pushed; but only a braced expression is captured and numparam decides the braces, so a quoted rd='rv' was captured while a bare v=rv in a B-source was walked past and the line called eligible. A B-source value is substituted textually at parse time, so every sample after the first saw the first draw: a 40-sample run reported 100% or 0%, differing between runs of the same deck and seed, where the correct sampled answer is ~45%. A random draw outside any braces is now ineligible, so such a deck disarms; the quoted and braced forms still arm, and the three spellings of the same B-source value now agree. This also unblocked giving montecarlo -- only under -warm, since keeping the circuit also carries the previous sample's solution and E-188 made that opt-in -- the setup reuse E-472 withheld (1.29x, yield unchanged). |
ngspice | doc | mcarming |
| 472 | perf: E-471 gave the setup reuse to sweep and nothing else. optimize never re-sources the deck for -param/-mparam knobs -- nor for -dparam once E-322's fast path arms -- yet still rebuilt a circuit it had not touched on every evaluation, hundreds of times in a fit. It now asks on the same terms, with every guard E-471's unchanged. The work was proving that holds when a search step moves a node collapse rather than a sweep point: a new cs_thresh model collapses below a threshold, and a search crossing it reports 1 rebuild in 17 analyses while returning the identical residual, evaluation count and parameter. A well-posed fit is unchanged under lm and nm; only a degenerate one already at its noise floor takes a different number of evaluations to the same answer. montecarlo was built, measured and taken back out -- its fast path can arm while a random .param has a use it cannot push, which already yields a wrong and run-to-run unstable yield with no reuse involved. |
ngspice | doc | reuseloops |
| 471 | perf: a sweep now keeps the circuit standing between points instead of tearing it down and building it again for each one -- .dc has never done that. After E-470 removed the quadratic teardown the rest of that per-point rebuild was still most of the run: 1001 points over a 2448-unknown stack, 7.24 s -> 0.57 s (12.8x) under SPARSE with byte-identical results, and 6.35 s -> 1.38 s (4.6x) under KLU, where reusing the matrix ordering moved one number of 5005 by 4.7e-09 relative. KLU was the faster solver on this deck before the change and is now the slower one. The obstacle is node collapse -- a device may merge two of its nodes at setup, so naive reuse freezes the topology and draws a flat line (a first version did exactly that and was reverted). A reused point still runs CKTtemp, which re-decides an OSDI device's collapse against the snapshot the matrix was built from, and any change forces a real rebuild; a built-in device, which decides its collapse only in DEVsetup, declines reuse for the whole circuit; a failed point never hands its state on. Default on, .option reusesetup=0 off, set ngdebug reports the decision. |
ngspice | doc | reusesetup |
| 470 | perf: tearing an OSDI circuit down was quadratic in its node count. CKTdltNNum() scans the node list from the head and OSDIunsetup() calls it once per internal node, so every repeated analysis paid O(k*N) -- a profile of a 1001-point sweep over a 2448-unknown circuit found 77% of the entire run in the teardown between points. The caller now marks every number it wants gone and one walk removes them all. Per sweep point 32.9 -> 7.6 ms at 25 stack periods, 4.0 -> 2.3 at 10, 1.7 -> 1.2 at 5 -- the speedup growing with size is the signature of removing a quadratic; the full deck 29.78 s -> 7.12 s, byte-identical. Scoped as "move sweep's loop into the analysis kernel" on the theory that the gap was per-point setup; the scope note put instrumentation first and the profile overturned it, so the large rewrite was not built. A second profile was needed because the first fix sized its mark array from CKTmaxEqNum, which shrinks as nodes are deleted, so later model types fell back to the slow path |
ngspice | doc | teardown |
| 469 | feat: .option saveused (or set saveused) keeps only the vectors the .control block actually reads. An analysis stores every node at every point unless a save says otherwise; on a 2448-unknown dielectric stack a 201-point parameter sweep costs 104.73 s that way and 7.22 s with a hand-written save, a factor of 14.5 from one line the author has to remember and keep in step with the wrdata beside it. With the option on and no save line the same deck runs in 7.08 s with byte-identical results. The scan is deliberately wider than the output command's arguments -- every v(...), i(...) and @dev[param] anywhere in the block -- because let r = v(out) - v(mid) then wrdata r names only r, and dropping the two vectors that build it would turn a performance option into a wrong answer. Stands aside entirely for an explicit save/.save, for all, and for a block with no output command. Not called autosave: E-192 owns that name for set autosave=<file>, and a filename is a string that is not an off-word, so reading it as a boolean would have switched filtering on for every deck using E-192's checkpointing |
ngspice | doc | saveused |
| 468 | fix: seven numbers that were wrong, two in code with no test coverage. psd reported a total power set by the WINDOW, not the signal -- a constant 1 V (true power 1 V^2) read 1.4999 under the default hanning window and 3.0 once zero-padded, because the window is scaled for unit coherent gain while a PSD sums squared bins; the normalisation is now N*sum(w^2), which leaves the already-correct rectangular case bit-for-bit unchanged. numparam's ** and ^ dropped the sign of a negative base, so .param {(-2)**1} returned +2 -- E-446 fixed exactly this in the OTHER evaluator, whose suite builds only B-source decks, so one simulator answered -8 for a B-source and +8 for a .param. Over a nested .dc, avg and integ integrated across the sweep restarts (0.25 and 0.5) while rms refused on the same plot; the integrating three now refuse and explain, and max/min still work. Undoes an E-467 regression that let meas dc measure a tran or ac plot. sens reported nan for a diode's ikf on every model leaving it at its default. Duplicate parameters were silent on built-in model cards and instance lines -- extending E-395's check needed the discovery that built-in parameter ids are enum tags, not dense indices. And the XSPICE limit block stopped limiting for a negative limit_range, which the climit sibling has always tested for. Two candidates withdrawn as my own measurement error |
ngspice | doc | mathguard |
| 467 | fix: eleven silent acceptances. Root of a class four enhancements had patched one site at a time -- an option's spelling decides its published type (bare a BOOL, =1 a NUMBER, =true a STRING) and cp_getvar had no CP_BOOL coercion, so all ~110 CP_BOOL readers saw only the bare word: set sqrnoise=1, interp=1 and autostop=1 each did nothing. Fixed once in cp_getvar; =0 still means off. The fix found its own hazard, a cascade leading with CP_BOOL that swallowed .option autoadapt=debug, caught by two existing suites. Also: .option defas= assigned the DRAIN field so the source-area default was unreachable; an instance temp/dtemp below absolute zero answered -0.998 V from a +1 V source; KiCad-spelled subcircuit formals never matched under autobus=kicad, leaving the device floating at 1.0 against 0.5238095; .func sqrt(x) silently replaced the built-in; .adapt validated the adapter model but not its node list, so one typo switched the feature off; meas lost max/min/avg/rms/integ over a .dc of a device parameter while find-when worked on the same plot; and alter @dm[is] blamed the parameter when the cause was that dm is a model. Four candidates were withdrawn rather than shipped -- three on re-verifying the measurement, and a negative-geometry guard when E-438's own suite caught it suppressing the warn_physics report it duplicated |
ngspice | doc | silentaccept |
| 466 | fix: .option autoadapt is quiet by default -- E-463 printed a line per adapter and per non-qualifying node, which buried the run's output; .option autoadapt=debug asks for it back, while errors are never silenced. Also fixes the value never being looked at, so autoadapt=0, =false, =no and =off ALL turned the feature on and a deck silently gained an adapter its author had just switched off -- the fourth appearance of that defect after E-450, E-451 and E-454, now using the word list they share |
ngspice | doc | adaptquiet |
| 465 | perf+fix: sweep no longer re-sources the deck once per point for subcircuit and derived .param knobs -- 1.58s -> 0.50s on a 3000-element deck at 201 points, 0.54s -> 0.07s on 800 subcircuit instances. Derived params and chains, X-line passing, nested calls, header defaults, derived params inside subcircuits and local shadows all stay on E-320's fast path: each captured expression is rewritten once at build time into global names by walking the instance's scope chain, since a subckt param is bound PER INSTANCE while the template is keyed by source line. Also fixes the reset path never restoring the knob into the live circuit -- E-385 closed that for the fast path only, so every analysis after a fallback sweep was quietly wrong |
ngspice | doc | sweepsubfast |
| 464 | fix: a bus FORMAL and a LOCAL bus on the same OSDI instance line gave a wrong answer -- inside .subckt s a[0] a[1] a[2] a[3], E-449 expanded a into the caller's four actuals while b stayed one token, leaving five node tokens where autobus needs two or eight, so nothing expanded and the tokens bound positionally with the top three bits dangling (1.0 against 0.5238095 for the same circuit flattened by hand). Every remaining bus port on such a line is now expanded too, using widths reached through INPtypelook and INP2N's own exported port grouping. A second defect surfaced during the fix: the autobus variable is not published at flattening time, so the kicad bit spelling was silently lost -- the style now travels with the flag |
ngspice | doc | busmix |
| 463 | feat: .option autoadapt adapter=<model> -- inject a two-bus-port adapter between two OSDI devices sharing a bus node, so N1 a b m1 / N2 b c m2 becomes N1 a b_f m1 / N2 b_r c m2 plus the adapter instance, instead of renaming the node on both lines by hand. Runs between INPpas1 and INPpas2 -- the only seam where the model table exists (so port structure is knowable), subcircuits are flattened, and INP2N has not run (so the rewrite is at token level and autobus expands all three lines). The forward side is chosen by port INDEX, not deck order, so reordering the netlist cannot change the circuit; every non-qualifying case is refused and reported. Optional .adapt n1, n2 restricts the node set by whole token |
ngspice | doc | autoadapt |
| 462 | feat: .option autobus=kicad -- the bit spelling a schematic can write. E-444 names an expanded bus port's terminals a[0]..a[4], but KiCad's SPICE exporter rewrites every [ and ] in a net name to _, so a sheet labelling a wire AA[0] emits /AA_0_ (multi-digit indices intact, ZA[10] -> /ZA_10_) while KiCad's internal name keeps the brackets -- so the bits of a bus port could not be labelled, wired to ordinary parts, or plotted, and the signal list offered /AA, which after expansion carries no device at all. The option changes only the generated spelling; indices still come from the model, so [4:1] still expands 1..4. An unknown style is reported rather than silently falling back to a deck that still solves with every bit dangling |
ngspice | doc | autobuskicad |
| 461 | fix: a string parameter's from '{"a","b"} set selection did not work -- only the FIRST member was ever enforced, because the parser reads the whole list but the AST accessor returned just the first child, so from rejected its own members, reordering the list changed which value was legal, and exclude silently ACCEPTED every forbidden value after the first; a set now becomes one constraint per member (numeric sets had the same bug). ngspice also corrupted the value before the model saw it -- ty="PMOS" arrived as pmos and ty="with space" as with -- so a quoted value on a model card or instance line now survives verbatim |
both | doc | strparam |
| 460 | fix: five defects from a one-hour hunt -- a.potential.access crashed the compiler (LRM Syntax 5-4 forbids reading access/ddt_nature/idt_nature this way; the attribute type never resolved, nothing was reported, and the lowering panicked); a multi-dimensional $table_model file whose axis was not ascending was interpolated to garbage in silence while every 1-D form already sorted its breakpoints; event control statements were accepted -- and their guarded statement silently dropped -- in the two places the LRM forbids them, analog initial and analog functions; and -D =1 names no macro. Two further LRM nature rules were implemented and withdrawn when the sweep failed E-39's and E-422's suites, which pin the opposite on purpose |
openvaf-r | doc | ctxguard |
| 459 | fix: a part select as an analog-filter coefficient vector -- de[0:1], the second of the three argument forms LRM Syntax 4-3 defines -- was rejected as "wrong number of array indices", since a part select and a multi-dimensional m[i][j] both reach inference as a two-index bit-select. They are distinguishable one layer up, where the parser keeps the colon and body lowering records every part select, so no new syntax was needed. The slice is built in the order written -- a reversed slice is a different filter, pinned by value -- and consuming it is what makes it legal, so a part select anywhere else stays refused |
openvaf-r | doc | filterslice |
| 458 | fix: every LRM function checked in every argument form the spec defines (117 builtins, 223 compiled-and-run checks) and seven defects fixed -- ln1p/expm1 did not exist in either spelling and are now their own opcodes on libm log1p/expm1, since the naive ln(1+x) is wrong in the first significant digit near zero; $abs/$min/$max were the only math functions whose $-spelling was unregistered; an array PARAMETER, the form the LRM lists first for a filter coefficient vector, was rejected by every Laplace/Z filter; a trailing null filter argument was a syntax error while the interior one worked; $simparam demanded a literal where the LRM allows a parameter or variable; $fatal alone refused its bare form; and a string-parameter noise_table file name panicked the compiler |
openvaf-r | doc | lrmfuncs |
| 457 | fix: replication inside an assignment pattern did not compile -- '{4{0}} failed at parse time, and so did the LRM's own initializer examples (real distort[0:2][0:2] = '{ 3{ '{3{0.0}}}};). The replication that always worked, {4{0}}, is the concatenation operator; '{4{0}} is an assignment pattern, a different construct one apostrophe away. The parser now builds the same node shape, and expansion happens in one shared walker instead of three, so the leaf count and the elements cannot disagree; an unfoldable, negative or oversized count is left unexpanded and reported |
openvaf-r | doc | patternrep |
| 451 | fix: two options were located by a bare substring search over the option line, so any option whose NAME MERELY ENDED in the watched text was taken as that option -- myseed=7, noseed=7 and xseed=7 all set the RNG seed, and the cshunt equivalents moved a node from 1.0 V to 6.92e-07, six orders of magnitude. Each also printed "unknown option", so the user was told the name was not recognised while it changed the answer. Now matched as whole tokens, seedinfo included. Separately, three options that demonstrably take effect were reported as unknown -- scale doubles @m1[w], rseries moves a transient, and autostop truncates one from 567 rows to 2 -- the case E-447 fixed for three other names; scalm is flagged too and deliberately left out, having no demonstrable effect |
ngspice | doc | optname |
| 450 | fix: .options savecurrents could be requested but never declined. Whether it was in force was decided by a bare strstr for the word over the option line, so EVERY line merely containing it switched it on whatever the line said -- savecurrents=0, savecurrents=false and nosavecurrents all turned it ON, the last being ngspice's own no<option> convention (noacct, noinit, nomod), and any identifier containing the word matched too. Silent, because a deck that quietly saves every terminal current still simulates correctly; it just carries vectors nobody asked for. The line is now read as tokens, a no prefix or a false value turns it off, and the later card wins. Which card is retained is unchanged, so the savecurrents_bsim3/_bsim4/_mos1 MOS variants still pick their own current sets |
ngspice | doc | savecuroff |
| 449 | fix: .option autobus did not reach inside a .subckt, and failed silently. A definition declaring a[0:4] has the formals a[0]..a[4], so a device line writing the bare a matched none of them, became the local node x1.a, and was then expanded into five FLOATING nodes with the device wired to those instead of the ports. Nothing was reported, because every terminal did receive a node -- so turning the option ON removed the under-connected warning the same deck gets with it OFF. Expansion now happens at subcircuit substitution, where the .subckt line alone is enough, ordered by ascending bit index because the model orders bus terminals that way whatever direction the Verilog-A declared. The other form, .subckt mysub a b with scalar formals passing the bus base through, already worked and is unchanged |
ngspice | doc | subbus |
| 448 | fix: a node named after one of the twelve built-in constants (c, e, i, pi, kelvin, boltz, echarge, planck, TRUE, FALSE, yes, no) was answered with the constant. v(c) on a renamed or mistyped node returned 2.9979e+08 instead of failing, which sweep drew as a flat curve and which walked through E-431's "-output never resolved" refusal; the other names are worse because they look like results, v(no) giving a flat 0.0. A bus bit c[0] -- what .option autobus builds for a bus called c -- was unreachable while print all printed it correctly, because E-224 preferred the literal name only when nothing called c existed. The same gate silently returned the wrong vector's element whenever the base was longer, so a circuit with both a node q and a bus bit q[0] read node q's first sample for print q[0] in tran |
ngspice | doc | constname |
| 447 | fix: eight degenerate inputs accepted in silence, each with a working guard a few lines away covering a different spelling — a negative gmin wrecked the operating point while every sibling tolerance was guarded; scale=0 shorted a resistor though a written 0 warned and was clamped; trrandom with a type outside 1–4 was a dead source; diode level=99 was accepted while level=2 was refused; cshunt from .control was ignored; show claimed a source had all eight transient waveforms; savecurrents/seed/numdgt were called unknown while working. m=0 and @r[resistance] were left alone — E-426 had settled both |
ngspice | doc | guardspell |
| 446 | fix: six things the netlist wrote that were quietly discarded — an explicit TD1=0 on an EXP source was read as "not supplied" and replaced by the timestep, so the waveform ran a step late and the answer depended on the step (~4% at 100µs); the same zero-as-sentinel test made PULSE PW=0/PER=0 differ between voltage and current sources; a PWL list with an odd token count had a value invented for the dangling time; a third .dc source and surplus .ac arguments were dropped in silence; (-2)**3 returned +8 while the same simulator's Verilog-A pow(-2,3) returns −8; @c[capacitance] folded in m= while @r[resistance] did not |
ngspice | doc | argdiscard |
| 445 | fix: nine silent failures — a bare .four card segfaulted; .four 1e400 overflowed to +INF and reported a 202% THD; R1 a b 1,5k silently became 5k because a trailing unlabeled number overwrote the value; an array instance past 8192 collapsed to ONE device; .option autobus indexed ground into five floating nodes, leaving the device disconnected; a failed sweep point republished the previous solution; a legal 20-deep hierarchy was refused as infinite recursion; an iteration limit below the solver floor was discarded unannounced |
ngspice | doc | guardgaps |
| 444 | opt-in .option autobus: a Verilog-A bus port can be connected by one name — N1 a b expands to a[0]…a[4] from the model's own terminal table, so the indices come from the model (a [4:1] port gives a[1]…a[4]), and the $port_connected idiom is untouched |
ngspice | doc | autobus |
| 443 | index lists: a[1,3,5] and R[1,3,5] alongside the existing ranges, and the two written together as a[0:1,7] — for node fields, instance names and wrapped .save v(a[1,3,5]) refs. Written order is kept; a lone a[2] is still a scalar bit, so existing decks are unaffected |
ngspice | doc | idxlist |
| 442 | listing tree draws the subcircuit hierarchy — each X labelled with the subcircuit it instantiates and drawn where it is used, plus a count of instances, devices and depth — instead of listing e's flat one-line-per-device wall. listing t works too |
ngspice | doc | listtree |
| 441 | array instances: R[0:3] a b r=1k is four resistors in parallel, and N[0:3] a[0:3] a[1:4] model indexes each node range in step with the instance. A range on the NAME selects this reading, so Enhancement-221's X1 bus[0:3] sub (one device, wide port) is untouched. Elements are addressable as @r[2][resistance] |
ngspice | doc | arrayinst |
| 440 | fix: sens left the circuit altered -- perturbing a parameter marks it given and nothing un-marks it, so a BJT fell into the ibe/ibc branch with both at 0, lost its junction saturation currents, and every later analysis was 12.8% wrong (101% for a diffpair), silently. Models are now snapshotted and restored. Also: pss argument checks (a negative stabtime ran forever), .meas window checks, pow(0,-1), a heap abort on set curplotname, and an unbounded sprintf in the expression parser |
ngspice | doc | sensrestore, argguard |
| 439 | fix: KLU accepted a singular refactor -- klu_refactor reuses the previous pivot order without pivoting or a singularity test, so it returned success on a zero-pivot LU and the solve produced NaN; an operating point SPARSE solves (a node with no DC path) failed after 33,911 iterations. A klu_rcond check routes it into the reorder path SPARSE already uses |
ngspice | doc | klusingular |
| 438 | fix: a failed simulation no longer becomes data -- montecarlo counted samples that never solved as PASSING (100% yield on 14-of-20 failures); sweep and optimize now report theirs. Adds opt-in .option warn_physics |
ngspice | doc | failacct, warnphysics |
| 437 | fix: a swept @*:model[param] is put back when the sweep ends -- Enhancement-436 gave the form a set path but not Enhancement-409's capture-and-replay -- and a valueless .temp card, which silently ran the circuit at 0 C, is refused |
ngspice | doc | modelwild, sweepguard |
| 436 | feat: @*:model[param] -- one model name, every instance path including the top-level card; a bare @rmod still means that card alone, but now reports the copies it left untouched |
ngspice | doc | modelwild |
| 424 | fix: a noise or ac_stim source inside a run-time loop registered no source at all and contributed nothing, silently -- while every other analog operator in that position was already an error |
openvaf‑r | doc | noiseloop |
| 423 | fix: a parenthesised comma list (a, b) kept only its first element and never checked the rest, so a , written for a + silently dropped a term and undeclared names hid in the discards |
openvaf‑r | doc | commaexpr |
| 422 | fix: one reference, three outcomes -- an unresolvable parent nature CRASHED the compiler even when unused, ddt_nature/idt_nature went silent, and a discipline's potential/flow blamed the model body |
openvaf‑r | doc | natureref |
| 421 | fix: a check that existed for one spelling and not its sibling -- an exclude swallowing its from range or written backwards, and $simparam names, the only such name whose typo is FATAL at run time |
openvaf‑r | doc | rangeguard |
| 420 | fix: six things accepted then silently degenerate -- integer ** with a negative exponent, a zero laplace denominator or zi_* period, an illegal last_crossing direction, an unmatched ac_stim name |
openvaf‑r | doc | vafdegen |
| 419 | feat: three integration methods — trbdf2 (L-stable composite, kills trapezoidal ringing), sdirk (order-3 L-stable Runge-Kutta), adams (variable-step Adams-Moulton); all opt-in, error constants derived not borrowed | ngspice | doc | integmethod |
| 418 | fix: four unchecked things — absdelay/last_crossing read 0 unless contributed, pz blamed the netlist for that same empty row, .save never validated a device name, meas when invented a time in the first interval | ngspice | doc | saveguard |
| 417 | fix: a node collapse re-decided after setup — sens printed roundoff, a .dc temp sweep ran one topology, savecurrents skipped two-terminal names | ngspice | doc | collapsestate |
| 416 | fix: a terminal collapsed onto an internal node reported exactly zero current; the whole collapse group is now summed | ngspice | doc | collapsecur |
VA_TEST/ holds the public VA-Models collection as a compile-regression corpus: the industry-standard compact models (BSIM4/6/BULK/CMG/IMG/SOI, PSP 102/103/104, PSP-HV, HiCUM L0/L2, MEXTRAM 504/505, VBIC, EKV 2.6/3, ASM-HEMT, EPFL-HEMT, Angelov, MVSG, diode_cmc, r2/r3_cmc, L-UTSOI, MOSVAR, IGBT, …) — 124 .va files in total. python3 VA_TEST/compile_all.py compiles every file with the committed openvaf-r and regenerates VA_TEST/compile_report.md; all 92 standalone models compile (the remaining 32 files are `include fragments — macro bodies and module-body pieces — reported separately since they aren't standalone modules).
examples/osdicampaign_examples/ is an oracle-based campaign over the OSDI device path — 83 checks across analyses (both solvers), sweeps, all seven optimizers, Monte Carlo, RF, and deliberate abuse. Every check computes its expected value independently, so a pass means the number is right, not that a run finished: .noise is checked against √(4kTR), .pz against −1/RC, .sp against exact S-parameter algebra, the optimizers against an analytic optimum, and Monte Carlo against both a hand-rolled reset-loop and the exact Gaussian probability. OSDI devices come out bit-identical to ngspice's built-in R/C/L/G through op/ac/tran/tf/pz/sens.
It is deliberately outside the regression suite — run_regression.py discovers verify_*.py, and this driver is named run_campaign.py, so the routine sweep never picks it up. The suite answers "did anything change?" on every fold; this answers "is the OSDI path correct?" and is worth running when the OSDI or analysis machinery is touched:
cd examples/osdicampaign_examples && python3 run_campaign.py # or: run_campaign.py A CLast run: 83/83, no ngspice or OSDI defect. Two differences are documented rather than tolerated — a 0.24 ppm thermal-voltage constant difference between OpenVAF's $vt (exactly the constants.vams value) and ngspice's built-in diode, and PSS discretisation error that the check verifies converges under grid refinement instead of pinning to a fixed tolerance. See the README there, which also collects the usage gotchas that each looked like a bug at first.
examples/solverdiff_examples/ compares the two linear solvers on circuits where the solve is actually hard — ill-conditioned networks and stiff nonlinear ones. A solver-vs-solver diff says they differ; it cannot say which is wrong, so phase 1 has numpy solve the same MNA system independently as a third opinion, reports each deck's condition number, and asserts against eps·cond; decks beyond what float64 can deliver are reported, never asserted. Phase 2 covers the nonlinear regime — where Sparse actually re-pivots — using solver-vs-solver at tight tolerance plus a tightened-reltol run that separates a linear-solve difference from convergence slack.
Like the OSDI campaign it is deliberately outside the regression suite: the driver is named run_solverdiff.py, so run_regression.py (which discovers verify_*.py) never picks it up.
cd examples/solverdiff_examples && python3 run_solverdiff.py # or: run_solverdiff.py 1Last run: 37/37, no solver defect. Worst Sparse-vs-KLU disagreement anywhere was 7.06e-06, on cond 2.4e12 where eps·cond is 5e-4. Both solvers track the exact solution in step with conditioning and trade places (Sparse better on ladder_ratio, KLU on star(9)), so neither shows systematic bias — Sparse is numerically sound where float64 allows; its weakness is cost, not accuracy. The README there also records the harness traps, each of which produced a false green or false red before being caught — including a first version that reported 25/25 and meant nothing.
Binaries are built by CI and committed to bin/:
| Platform | Directory | Binaries |
|---|---|---|
| Linux x86-64 | bin/linux/intel/ |
ngspice, openvaf-r |
| Linux ARM64 | bin/linux/arm/ |
ngspice, openvaf-r |
| macOS Apple Silicon (M1/M2/M3) | bin/macos/apple-silicon/ |
ngspice, openvaf-r |
| macOS Intel | bin/macos/intel/ |
ngspice, openvaf-r |
| Windows x86-64 | bin/windows/intel/ |
ngspice.exe, openvaf-r.exe |
Each platform directory also ships ngspice's XSPICE code models under
codemodels/*.cm with a portable scripts/spinit. Export
SPICE_LIB_DIR="$PWD/bin/<os>/<arch>" and ngspice loads them at startup so the
A-device code-model library (gain, summer, oscillators, ADC/DAC bridges,
…) is available — see handbook §3.8.
The example scripts set SPICE_LIB_DIR automatically.
The binaries are built on Ubuntu 22.04, so they run on any distro with glibc ≥ 2.35 (Ubuntu 22.04+, Debian 12+; an error like version `GLIBC_2.39' not found means the binary in your checkout predates this baseline — pull the latest). They are dynamically linked against standard system libraries. Install them with your package manager if missing:
Ubuntu / Debian:
sudo apt-get install libreadline8 libx11-6 libxaw7 libxft2 libxext6 # ngspice
sudo apt-get install libllvm18 # openvaf-r (Ubuntu 24.04+ / Debian 13+)On Ubuntu 22.04 / Debian 12, whose repos stop at older LLVM versions, get openvaf-r's LLVM 18 runtime from apt.llvm.org — which is also the exact build these binaries were linked against:
wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 18Fedora / RHEL:
sudo dnf install readline libX11 libXaw libXft libXext # ngspice
sudo dnf install llvm18-libs # openvaf-r
openvaf-rneeds one exact library filename, not merely the right LLVM version. It links LLVM dynamically, and the recorded dependency is the SONAMElibLLVM-18.so.18.1. The dynamic linker matches that string literally — there is no prefix or version-range matching — so a perfectly good LLVM 18.1 installed under a different name does not satisfy it:openvaf-r: error while loading shared libraries: libLLVM-18.so.18.1: cannot open shared object file: No such file or directoryDebian and apt.llvm.org use that spelling; several other packagings ship the same library as
libLLVM.so.18.1, without the-18infix. (The binaries carry noRUNPATH, so onlyld.so.cache, the standard directories andLD_LIBRARY_PATHare searched.) Check what you have, and if only the name differs, supply the name the loader asks for:ldd bin/linux/intel/openvaf-r | grep -i llvm # what is missing ls /usr/lib/*/libLLVM*18* /usr/lib/llvm-18/lib/libLLVM*.so* 2>/dev/null # what you have have=/usr/lib/$(uname -m)-linux-gnu/libLLVM.so.18.1 # ...whatever the line above found sudo ln -s "$have" "$(dirname "$have")/libLLVM-18.so.18.1" && sudo ldconfigUse a symlink, not a copy: libLLVM is ~120 MB, and a copy silently goes stale the next time LLVM is updated. Keep it pointing at an 18.1.x library — LLVM's C++ ABI is not stable across releases, so aliasing a different version turns a clean load error into a crash.
After that, mark the binaries executable and run:
chmod +x bin/linux/intel/ngspice bin/linux/intel/openvaf-r
./bin/linux/intel/ngspiceThe binaries are dynamically linked against XQuartz (X11) and Homebrew readline/ncurses. Both must be installed before running.
1. Install XQuartz (provides the X11 window system for ngspice plots):
Download and install from https://www.xquartz.org, then log out and log back in so the X11 libraries at /opt/X11 are on the dynamic linker path.
2. Install Homebrew dependencies:
brew install readline ncurses # for ngspice
brew install llvm@18 # for openvaf-r (runtime: libLLVM + libunwind)
openvaf-rlinks LLVM dynamically, so it needs Homebrew'sllvm@18at runtime — without it you'll seeLibrary not loaded: .../opt/llvm@18/lib/libunwind.1.dylib(Intel Macs resolve it under/usr/local/opt, Apple Silicon under/opt/homebrew/opt).
3. Mark binaries executable and run:
# Apple Silicon (M1/M2/M3)
chmod +x bin/macos/apple-silicon/ngspice bin/macos/apple-silicon/openvaf-r
./bin/macos/apple-silicon/ngspice
# Intel Mac
chmod +x bin/macos/intel/ngspice bin/macos/intel/openvaf-r
./bin/macos/intel/ngspiceNote: macOS may show a security warning ("cannot be opened because the developer cannot be verified"). Go to System Settings → Privacy & Security and click Allow Anyway, or run:
xattr -d com.apple.quarantine bin/macos/apple-silicon/ngspice xattr -d com.apple.quarantine bin/macos/apple-silicon/openvaf-r
The Windows binaries come bundled with all required MinGW runtime DLLs (libreadline8.dll, libtermcap-0.dll, libstdc++-6.dll, libwinpthread-1.dll, etc.) in the same directory. No MSYS2, MinGW, or other runtime installation is required — just keep all files in bin\windows\intel\ together.
Simply run from that directory:
bin\windows\intel\ngspice.exe
bin\windows\intel\openvaf-r.exe
Note: Windows may show a SmartScreen warning on first run ("Windows protected your PC"). Click More info → Run anyway.
Note:
openvaf-r.exeis a command-line tool. Run it from Command Prompt or PowerShell, not by double-clicking.
Builds run on push to main (source changes only; binary commits are skipped) or manually via Actions → Build binaries → Run workflow.
| Runner | Target | Notes |
|---|---|---|
ubuntu-latest |
bin/linux/intel/ |
LLVM 18 from apt |
ubuntu-24.04-arm |
bin/linux/arm/ |
LLVM 18 from apt |
macos-14 |
bin/macos/apple-silicon/ |
LLVM 18 via Homebrew, XQuartz |
macos-26-intel |
bin/macos/intel/ |
LLVM 18 via Homebrew, XQuartz; macOS 26 "Tahoe" image, currently in beta. GitHub has signaled Intel macOS runners will be retired entirely in 2027. |
windows-latest |
bin/windows/intel/ |
LLVM 18 official tarball, ngspice via MSYS2/MinGW (static) |
See .github/workflows/build-binaries.yml for the full workflow.
With the Verilog-A / OSDI device side now on par with commercial tools, the
remaining gaps are all on the simulator side. ngspice vs. Spectre — feature
gap analysis
(PDF) is a category-by-category
✅/
A companion note, KLU vs. Sparse 1.3 solver
notes
(PDF), documents this
build's two linear solvers: Sparse 1.3 is the default and runs everything,
while KLU (opt-in via .option klu) matches it on DC/AC/transient but rejects
noise and pole-zero and is less robust on stiff transient edges — verified by a
solver-by-solver sweep of the whole example suite.
For the statistical side, Statistical simulation in
ngspice
(PDF) is a complete,
plotted guide to the whole suite — the random .param functions, ordinary Monte
Carlo, Latin-Hypercube sampling (mcsample), high-sigma rare-event estimation
(highsigma), native process/mismatch correlations (mccorr/mvnorm), and the
packaged yield command (montecarlo) — with worked examples and figures
generated from real ngspice runs.
For time-domain noise, Transient noise in
ngspice
(PDF)
covers both paths — the built-in trnoise sources and the OSDI (Verilog-A)
devices made noisy in .tran by Enhancement-364 — deriving the amplitude law
from the generator's structure and checking it against closed forms: the
parameter-free kT/C identity across a 10x resistance sweep, shot noise tracking
a 45x current sweep on a nonlinear diode, a 1/f spectrum matched to .noise, and
a variance cross-check between the transient, .noise, and thermodynamics. It
also records which elements are not noise sources in .tran, and a measurement
trap that made a correct result look 38% wrong.
Temperature and the multiplier in
ngspice
(PDF) documents the
four instance knobs a compact model sits under — m, temp, dtemp, dt —
and how each reaches an OSDI (Verilog-A) device: that ngspice supplies all four
by default and declaring one in Verilog-A is what makes it step back; that m
and $mfactor are distinct parameters that multiply rather than alias; the
Celsius→Kelvin convention on temp; and why $vt must be checked by ratio
rather than against a textbook kT/q. Every figure is generated from a
simulation, each verified against a built-in device in the same deck — including
a resistor used as a thermometer.
Not every idea survives measurement. OSDI/Verilog-A device bypass — an
investigation
(PDF) records a full,
correct prototype of element bypass (latency exploitation) for OSDI devices that
was built, measured, and then reverted: it delivered no speedup (often a
slowdown), because the per-device Jacobian/residual extraction a safe bypass
test needs is not free for a monolithic OSDI eval(), and freezing device
linearizations inflated the Newton iteration count enough to erase the eval
savings — and on some circuits broke convergence outright. This mirrors ngspice's
own choice to ship .option bypass disabled by default.
This repository is a combined work, and most of the code in it is not original to this project.
The combination is distributed under GPL-3.0 (LICENSE) — a consequence rather than a choice: the bundled OpenVAF compiler is GPL-3.0, this project modifies and redistributes it, and GPL-3.0 is the strongest copyleft among the components. Every other component's license is compatible with it.
Each bundled component keeps its own license. GPL-3.0 governs the combination; it relicenses nothing. In particular:
| Component | License |
|---|---|
ngspice (ngspice-46/) |
Modified BSD, © Regents of the University of California and others — with its own exception list in ngspice-46/COPYING |
OpenVAF (OpenVAF-master-20260610/) |
GPL-3.0 (two utility crates are MIT OR Apache-2.0) |
the OSDI interface in ngspice (ngspice-46/src/osdi/) |
MPL-2.0, © 2022 SemiMod GmbH — OpenVAF's side of the same ABI is GPL-3.0 |
| KLU | LGPL-2.1-or-later |
parts of XSPICE, and ndev |
public domain |
The BSD, MIT and MPL-2.0 components carry attribution notices that survive redistribution. THIRD-PARTY-LICENSES.md is the component-by-component manifest that records them, and is the file to read before redistributing this work in any form.
The prebuilt binaries under bin/ are built from the
sources in this repository and are covered by GPL-3.0; the corresponding source
required by GPL-3.0 §6 is this repository itself, so source and binary always
travel together.
No separate copyright is asserted over this project's modifications to ngspice and OpenVAF. Those changes are contributed under the terms of the works they modify, and claim nothing beyond them. The files original to this project — the documentation, examples, verification harnesses and change reports — are © 2026 javaNoviceProgrammer under GPL-3.0.