Repository navigation
Releases
v0.5.0 — downloadable binaries, the cscript arity contract, and on crates.io
The release to use. Same code as v0.4.0, which was tagged minutes earlier — the version number moved to 0.5.0 so that the download and the crates.io packages agree, and crates.io never reuses a version. Until now the only way to get this simulator was to build it, which meant a Rust toolchain and three sibling checkouts first.
Download
| Platform | File |
|---|---|
| Windows x86-64 | general-simulator-windows-x86_64.zip |
| Linux x86-64 | general-simulator-linux-x86_64.tar.gz |
| Linux arm64 | general-simulator-linux-aarch64.tar.gz |
| macOS Apple silicon | general-simulator-macos-aarch64.tar.gz |
Unpack and run it — there is nothing to install. On Windows:
> general-simulator.exe circuit.cir --mode transient --tfinal 1e-3 --dt 1e-6Every binary was made to solve a resistive divider and checked to give 0.5 before it was packaged.
What is already inside it
Waveform measurements (kind=measure — RMS, THD, peak, TRIG-TARG, Fourier, error norms over non-uniform samples) are compiled in. There is no separate measurement program to install.
C blocks (kind=cscript) work too: the binary loads your .so/.dll at run time, so nothing was needed at build time.
What is not
kind=pyblock and kind=pyfunc are absent from these builds, deliberately. Embedding Python links the binary against one exact libpython, so a download built here would refuse to start against the Python you have. (abi3, the usual fix, applies to extension modules and not to an embedding binary — measured: the linked soname is identical with and without it.) Build locally with --features python for those two block kinds.
kind=octfunc / kind=octblock do work from the download: Octave is spawned as a subprocess rather than linked, so it only has to be installed on your machine, not on the build machine.
Since v0.3.0
A library can now declare its own arity. in_len/out_len used to be whatever the netlist said, and a .c file written for a different number indexed past the end of a buffer — a wrong answer at best, memory corruption at worst, and never a message naming the block. An optional int cscript_arity(int *in_len, int *out_len) is now checked against the netlist before the first step, and a disagreement is refused with both counts in the error. Absent, or returning non-zero, keeps the old behaviour exactly, so every existing .c file is unaffected.
Checkpoint and resume. --checkpoint-out writes a run's complete state at --tfinal, and --resume continues it bit-identically to a run that never stopped. cscript and octblock have their own state contracts so a block's internals survive the round trip.
Initial conditions on signal-domain blocks (ic=, y0=), alongside the existing ic= on capacitors and inductors.
The documentation is now verified, and gated. 152 tests assert that every ## Example and ## Errors claim in the Component Reference was actually run against this CLI, and CI now runs them — before, the suite existed and nothing executed it.
The netlist grammar chapter documents number suffixes, which it never did. Most importantly that 1M is one milliohm, not one megohm — SPICE's rule, case-insensitive, and the most expensive thing in the grammar to get wrong.
Both books (user guide and developer guide), the PDFs and the API reference are published at the repository's GitHub Pages site.
Related
- general-simulator-gui — draw schematics in VS Code instead of writing netlists
- gsim-tools — build them in Python
On crates.io
Every crate is published at this version, so the library side is usable too:
$ cargo install general-simulator-cli # the CLI, from the registry| crate | version |
|---|---|
general-simulator-cli |
0.5.0 |
dae-runtime, continuous-blocks, lcp-solver, pwl-devices |
0.5.0 |
cscript-ffi, pyblock-ffi, octave-ffi |
0.5.0 |
general-mna |
0.3.0 |
0.5.0 rather than 0.4.1 because it is breaking: DaeError gains
CScriptArityMismatch and is not #[non_exhaustive], so an exhaustive match downstream
stops compiling.
v0.4.0 — downloadable binaries, the cscript arity contract, checkpoint and resume
The first release with downloadable binaries. Until now the only way to get this simulator was to build it, which meant a Rust toolchain and three sibling checkouts first.
Download
| Platform | File |
|---|---|
| Windows x86-64 | general-simulator-windows-x86_64.zip |
| Linux x86-64 | general-simulator-linux-x86_64.tar.gz |
| Linux arm64 | general-simulator-linux-aarch64.tar.gz |
| macOS Apple silicon | general-simulator-macos-aarch64.tar.gz |
Unpack and run it — there is nothing to install. On Windows:
> general-simulator.exe circuit.cir --mode transient --tfinal 1e-3 --dt 1e-6Every binary was made to solve a resistive divider and checked to give 0.5 before it was packaged.
What is already inside it
Waveform measurements (kind=measure — RMS, THD, peak, TRIG-TARG, Fourier, error norms over non-uniform samples) are compiled in. There is no separate measurement program to install.
C blocks (kind=cscript) work too: the binary loads your .so/.dll at run time, so nothing was needed at build time.
What is not
kind=pyblock and kind=pyfunc are absent from these builds, deliberately. Embedding Python links the binary against one exact libpython, so a download built here would refuse to start against the Python you have. (abi3, the usual fix, applies to extension modules and not to an embedding binary — measured: the linked soname is identical with and without it.) Build locally with --features python for those two block kinds.
kind=octfunc / kind=octblock do work from the download: Octave is spawned as a subprocess rather than linked, so it only has to be installed on your machine, not on the build machine.
Since v0.3.0
A library can now declare its own arity. in_len/out_len used to be whatever the netlist said, and a .c file written for a different number indexed past the end of a buffer — a wrong answer at best, memory corruption at worst, and never a message naming the block. An optional int cscript_arity(int *in_len, int *out_len) is now checked against the netlist before the first step, and a disagreement is refused with both counts in the error. Absent, or returning non-zero, keeps the old behaviour exactly, so every existing .c file is unaffected.
Checkpoint and resume. --checkpoint-out writes a run's complete state at --tfinal, and --resume continues it bit-identically to a run that never stopped. cscript and octblock have their own state contracts so a block's internals survive the round trip.
Initial conditions on signal-domain blocks (ic=, y0=), alongside the existing ic= on capacitors and inductors.
The documentation is now verified, and gated. 152 tests assert that every ## Example and ## Errors claim in the Component Reference was actually run against this CLI, and CI now runs them — before, the suite existed and nothing executed it.
The netlist grammar chapter documents number suffixes, which it never did. Most importantly that 1M is one milliohm, not one megohm — SPICE's rule, case-insensitive, and the most expensive thing in the grammar to get wrong.
Both books (user guide and developer guide), the PDFs and the API reference are published at the repository's GitHub Pages site.
Related
- general-simulator-gui — draw schematics in VS Code instead of writing netlists
- gsim-tools — build them in Python
v0.3.0 — ic= initial conditions, --out-every, a real silent-wrong-answer fix
Third checkpoint release — 6 commits since v0.2.0, one real silent-wrong-answer bug closed and
two new capabilities. See README.md for the architecture rationale and
book/dev-guide/src/design-decisions.md/docs/journal/ for the reasoning behind each of these.
Highlights
- Fixed a real silent-wrong-answer bug:
kind=sig2physconverters have no terminals and
stamp nothing into the MNA system — they're named values consumed by reference (a source's
own value field, a switch'sgate=/ctrl=), never a circuit node. Because block names and
circuit node names are separate namespaces, wiring a converter's name onto an element line
(R1 VDRV 0 1k) built a perfectly ordinary undriven node — KCL solved it fine to 0, so the
run silently reportedV(VDRV) = 0right next to the block's own correct, non-zero output
column, in both--mode dcand--mode transient.DaeError::Sig2PhysUsedAsCircuitNode
(reject_sig2phys_wired_into_circuit) now catches this before any system is built, in both
modes, naming the converter, the offending element, and the node token as written. ic=initial conditions are now honored end to end:general-mnav0.1.0 gained real
ic=support onC/L(see its own release notes); this release is the seam that actually
uses it —resolve_initial_statecentralizes the decision across all four transient entry
points (simulate_transient,simulate_transient_with_ideal_switches, the closed-loop runner,
the streamed block-graph runner), so a run given no explicitx_initialnow starts from
whateveric=the netlist declares instead of always from rest, falling back to rest only when
the netlist declares none. An explicitx_initialfrom the caller still always wins.--out-every Ndecimates CSV/raw output without coarsening the timestep: every step is
still taken and the solution is bit-identical, only 1-in-N resolved points are written — for a
case like verifying zero-voltage switching, where the integration step has to resolve a sub-
picosecond hard-discharge time but a multi-microsecond commutation window doesn't need every
point written to disk.kind=measurestill sees every point regardless of--out-every(only
the file, not the measurement, is thinned — decimating a measurement's own input would change
its answer, not just its size). Tested by asserting kept rows are byte-for-byte the full run's
rows at those indices, not just that the output shrank.
Fixed since v0.2.0: the two v0.2.0-era doc pages (resolve_initial_state's own doc comment,
DaeError::InitialCondition) that described the old solved-operating-point ic= semantics —
now backwards, since general-mna moved to a true assignment — were corrected alongside the
seam that consumes it.
v0.2.0 — signal-domain enforcement, scripting blocks, measurements, streaming output
Second checkpoint release — 47 commits since v0.1.0. Educational Rust simulator for
piecewise-linear circuits and mixed circuit/block-diagram systems continues to grow its
signal-domain block vocabulary and output tooling, while fixing a real memory-exhaustion crash
found and closed out this same cycle. See README.md for the architecture rationale and
book/dev-guide/src/design-decisions.md for the reasoning behind each of these.
Highlights
- Enforced physical/signal-domain converters, the breaking change
v0.1.0was tagged
immediately before:kind=sig2phys domain=voltage|current(unified from separate
sig2voltage/sig2currentkinds) is the only legal way a signal-domain block drives a
source's magnitude or an ideal switch's gate;kind=phys2sig node=<n>|branch=<n>(renamed
fromprobe) is the only way a circuit quantity re-enters the signal domain — a real,
netlist-level physical/signal boundary, not just a naming convention. - Two new scripting block families:
kind=pyfunc/kind=pyblock(embedded Python via
PyO3, gated behind an optionalpythonfeature) andkind=octfunc/kind=octblock
(GNU Octave via subprocess — deliberately never linked, since Octave is GPLv3 and this
project is MIT — "mere aggregation" only, empirically validated for performance: a
persistent session vs. a fresh process per call is ~4500x faster). kind=cscriptgained a real state contract: optional continuous state (xc), an
optionalupdate()/next_sample_hit()split for block-controlled discrete sample times, a
to=phase offset — the same contractpyblock/octblockshare.- Vector-signal support (
SignalValue::Vector) —kind=statespaceis now genuinely MIMO,
not scalar-only. - New block kinds: discrete-time state-space/transfer-function/PID, logic gates, SR
latches, flip-flops, a counter, and signal-domainSIN/PULSE/EXP/SFFMsources. kind=measure: ngspice/Xyce-style post-processing measurements (MAX/MIN/RMS/
FOUR/TRIG-TARG/ERROR/...) over a completed transient trace, backed by the
newly-extractedgs-waveform-measurementscrate (now its own sibling repo, independent
release cadence). Results write to a<netlist>.logfile, matching real SPICE tools' own
.measureconvention — not stdout, not stderr.--format raw: writes a genuine binary SPICE rawfile, cross-validated against two real
Python readers (PySpice, spicelib) reading a file this CLI actually wrote.- Adaptive stepping now lands exactly on discrete/PWM edges instead of silently missing
them oncedt_maxapproaches a switching period (earliest_next_event_dt) — closes a real
bug demonstrated on a genuine MATLAB-ported phase-shift-PWM modulator (0/100 → 100/100 edges
correctly resolved at the previously-failing step sizes). - Streaming CSV/raw output: the transient engine no longer buffers an entire run in memory
before printing/writing anything — fixes a real incident where a large, stiff circuit under
adaptive stepping drove a development machine to ~11GB RSS/24GB swap before being killed; the
same run now holds ~23MB RSS regardless of length. A new--max-stepsflag caps runaway
adaptive runs as a hard safety net. - Generated Component Reference: 36+
kind=/BlockKindcomponents fully documented, each
backed by a committeddoc-verify/proof folder (a real.cirfixture + a Python test that
actually runs the CLI and checks the documented claim). - Renamed
Mosfet/DiodePWL models toIdealSwitch/IdealDiode;pyblock-ffi/pyo3gated
behind an optionalpythonCargo feature for a python-free build to stay possible.
Breaking changes since v0.1.0: kind=sig2voltage/kind=sig2current/kind=probe/
kind=sig2gate/kind=mosfet/kind=diode are all gone, no compatibility aliases — see the
items above for their replacements.
v0.1.0 — first checkpoint
First checkpoint release. Educational Rust simulator for piecewise-linear circuits and mixed circuit/block-diagram systems — no Newton-Raphson, no voltage limiting anywhere. See README.md for the full architecture rationale and docs/journal/2026-08.md for the development history.
This tag is a stable point immediately before a breaking netlist-grammar change: enforced physical/signal-domain converter blocks at every circuit-control boundary (tracked in book/dev-guide/src/open-questions.md).
Highlights
- LCP-resolved PWL diode/MOSFET-body-diode segments (Lemke's algorithm), backward-Euler/trapezoidal DAE integration with automatic fallback on mode changes and trapezoidal ringing.
- Full continuous-block library: PID (fixed and dynamic anti-windup bounds), state-space, transfer function, VCO, hysteresis, Clarke/Park coordinate transforms + PLL, PMSM (RK4).
- Block-graph execution order derived automatically from the
Signal::Blockdependency graph, with genuine algebraic-loop detection (exact closing path reported). - Complementary half-bridge PWM switching (
gate=dutyctrl/dutyctrlcomplement). elspice-pwl-cli: single-file netlist-plus-controller convention, fixed and adaptive time stepping.