Can I trust this SPICE result? Catches silent/wrong ngspice simulation results.
Project description
circuit / spiceguard
This repo started as CircuitCLI, an image/photo-to-SPICE-simulation pipeline (YOLO + OCR + graph → ngspice). That idea was dropped after research showed the problem it targeted ("redrawing schematics") isn't a pain users actually report, and the obvious adjacent gaps were already taken or funded.
It is now exploring a different, evidence-led direction: a SPICE result trust-guard.
What is spiceguard?
Modern ngspice recovers from many classic convergence problems on its own — and when it can't, it often returns exit code 0 with a plausible but wrong answer (a relaxed fallback estimate, or arbitrary voltages on an ungrounded node). Nothing in the standard flow warns you.
spiceguard answers one question about a SPICE run: can I trust this result?
It combines static netlist analysis, ngspice failure-log decoding (cryptic
internal names translated to the real component plus a specific fix), and
silent-failure detection (exit-0 runs that are still untrustworthy). It accepts
netlists from ngspice, KiCad, LTspice, and PSpice, and can convert LTspice .asc
schematics (experimental, built-in 2-pin symbols only).
Install
Requirements: Python 3.9+, ngspice
brew install ngspice # macOS; Linux: apt install ngspice
From PyPI:
pip install spiceguard
From the repo (development):
pip install .
# or, without installing:
PYTHONPATH=src python3 -m spiceguard FILE...
Docker (zero setup — ngspice bundled):
docker build -t spiceguard -f docker/Dockerfile .
docker run --rm -v "$PWD:/work" spiceguard mycircuit.cir
This registers a spiceguard command on your PATH.
ngspice path resolution (priority order)
spiceguard locates the ngspice binary in this order:
--ngspice PATHCLI flag — if given and not executable, raises an error immediately (no fallthrough)$NGSPICEenvironment variable — same hard-configured semantics; error if set but not usablewhich ngspice(PATH lookup)- Legacy fallback
/opt/homebrew/bin/ngspice
If none of the above resolves to a usable binary, spiceguard exits with code 3 and prints a clear message listing what was tried. Install ngspice, or point to it explicitly:
spiceguard --ngspice /usr/local/bin/ngspice mynetlist.cir
# or
export NGSPICE=/usr/local/bin/ngspice
CLI usage
spiceguard [--ngspice PATH] [--version] [--help] FILE...
spiceguard kicad [--ngspice PATH] FILE...
Pass one or more netlist (or schematic) files. When multiple files are given, spiceguard evaluates each in sequence and exits with the worst verdict across all.
Options
| Flag | Description |
|---|---|
FILE... |
One or more netlist or schematic files to check |
--ngspice PATH |
Explicit path to the ngspice binary |
--json |
Emit results as a JSON array (for editors, CI, tooling) |
--version |
Print version and exit |
--help |
Show usage |
Exit codes
| Code | Meaning |
|---|---|
| 0 | TRUSTWORTHY — ngspice exited 0 and no trust issues found |
| 1 | FAILED — ngspice exited non-zero |
| 2 | SUSPECT — ngspice exited 0 but trust issues were detected |
| 3 | ngspice not found |
| 64 | Usage error (bad arguments) |
Example
$ PYTHONPATH=src python3 -m spiceguard tests/netlists/n5_healthy_control.cir
======================================================================
n5_healthy_control.cir
======================================================================
✓ TRUSTWORTHY (ngspice exit 0)
No trust issues detected.
$ PYTHONPATH=src python3 -m spiceguard tests/netlists/n1_missing_ground.cir
======================================================================
n1_missing_ground.cir
======================================================================
⚠ SUSPECT (ngspice exit 0)
[FATAL] no_ground
→ No node '0' (ground). SPICE has no voltage reference, so it may float the circuit and return arbitrary WRONG voltages with no error. Fix: tie a reference node to '0'.
Input formats
| Extension | Handling |
|---|---|
.cir, .net, .sp, .spice, .ckt |
Passed directly to ngspice |
| Any other netlist | ngspice natively translates KiCad, LTspice, PSpice, HSpice dialects |
.asc |
Converted in-process (experimental — see below) |
LTspice .asc (experimental): spiceguard converts .asc schematics using
built-in 2-pin symbol geometry (resistor, capacitor, inductor, diode, voltage
source, current source). Net connectivity is recovered by union-find over
wire/pin/flag coordinates. For anything beyond these built-in symbols, export
the netlist from LTspice ("View > SPICE Netlist") and feed that instead — that
path is exact. When a .asc is evaluated, the generated netlist is printed at
the end of the report so you can compare it against LTspice's own export.
Subcircuit support (Feature B)
spiceguard's parser handles:
.subckt/.endsblock collection and extractionX-instance flattening with automatic node namespacing (internal nodes becomeinstancename:nodeto avoid collisions)+continuation lines rejoined before parsing.includefile resolution (local files only; URL.includelines are warned and skipped)
Errors detected during subcircuit processing:
undefined_subckt— X-instance references a subckt name not defined in the netlistport_mismatch— X-instance provides a different number of nodes than the subckt port listsubckt_recursion— self- or mutual-referencing subckts (skipped with a warning)
KiCad workflow (Feature C)
spiceguard kicad myboard.cir
The kicad subcommand runs the standard trust check plus a KiCad-specific
preflight that detects the classic export gotcha:
kicad_ground_not_zero — ngspice requires the circuit ground to be exactly
node 0. KiCad schematics commonly use a GND net (or GNDA, VSS, 0V,
AGND, DGND, PGND) that is NOT automatically mapped to node 0 on SPICE
export. When such a net is present and node 0 is absent, the simulation silently
floats the entire circuit, yielding wrong voltages with no error. The message
includes KiCad-specific fix instructions (set node mapping in Symbol Properties
or place a PWR_FLAG).
Pipe form — export and check in a single step without writing a file:
kicad-cli sch export netlist --format spice myboard.kicad_sch -o - \
| spiceguard kicad -
Important: spiceguard is a command-line workflow helper, not a native KiCad
plugin. KiCad has no post-simulation event API, so there is no in-GUI integration;
run spiceguard kicad from your terminal or CI pipeline.
Verdict model
Every run produces one of three verdicts:
| Verdict | Meaning |
|---|---|
| TRUSTWORTHY | ngspice exited 0 and no trust-breaking issues found |
| SUSPECT | ngspice exited 0 but at least one FATAL/SILENT/WARN issue detected |
| FAILED | ngspice exited non-zero |
FAILED outranks SUSPECT, which outranks TRUSTWORTHY (this is why exit code 1 < 2 numerically but FAILED is the worst outcome).
Detectors
| Code | Severity | Description |
|---|---|---|
no_ground |
FATAL | No node '0' — circuit has no voltage reference |
source_conflict |
FATAL | Multiple voltage sources forced across the same node pair |
no_dc_path |
FATAL | Node reachable only through capacitors/current sources — no DC reference |
timestep_collapse |
FATAL | Transient timestep collapsed; specific culprit identified from the log |
singular_node |
FATAL | Singular matrix at a node — no defined DC solution |
singular_branch |
FATAL | Singular matrix at a branch (current unconstrained) |
silent_fallback |
SILENT | ngspice exited 0 after gmin/source stepping failed; operating point is a relaxed estimate, not a true solution |
kicad_ground_not_zero |
WARN | KiCad export uses GND/GNDA/etc. but no node 0 (kicad subcommand only) |
dangling_node |
WARN | Node connects to only one pin — likely a wiring mistake |
Integrations
| Surface | Where | What it gives you |
|---|---|---|
| Docker | docker/ |
Zero-setup image with ngspice bundled |
| VS Code | vscode/ |
Inline trust diagnostics as you edit .cir/.net/.sp files (consumes --json) |
| KiCad | kicad/ |
kicad-cli netlist export → spiceguard kicad, as a one-liner, helper script, or CI step |
All three build on the same engine; the --json output makes spiceguard easy to
wire into editors, CI, and other tools.
Security
spiceguard runs ngspice in batch mode on the netlists you give it. Treat a netlist like a script you are about to run, because in two ways it is one:
- A SPICE netlist can contain
.control/shelldirectives that execute arbitrary shell commands. The captured log may include their output. .includereads whatever file path the netlist specifies (the same files ngspice itself would read), so a hostile netlist can point at files on your disk. The contents are parsed as a netlist, not printed.
Only run spiceguard on netlists you trust. It is a local dev/CI utility, not a sandbox.
Hardening that is in place:
- ngspice is invoked with
--no-spiceinit, so a.spiceinit/spice.rcconfig planted next to the netlist is not auto-executed. - The simulator is launched with a fixed argument list, never through a
shell (no
shell=True), so the path can't be shell-injected. - A 120s timeout terminates a hung/non-converging run (reported as FAILED, not
a crash); temp files use
mkstemp(0600) and are always cleaned up; subcircuit nesting is depth-bounded against stack exhaustion. - spiceguard has no third-party runtime dependencies, and the source passes
bandit -r src/with no findings (pip-auditreports no vulnerable deps).
Limitations (honest scope)
- Parser scope: The static netlist parser covers the element types listed in
netlist.py(NODE_COUNT). Exotic or simulator-specific elements not in that table are silently skipped (they do not affect the running netlist — ngspice still sees the full file). .ascis experimental and scoped to 6 built-in 2-pin symbol types. Any schematic with transistors, op-amps, subcircuit blocks, or custom symbols must be exported from LTspice first. Always verify the generated netlist against LTspice's own "View > SPICE Netlist".- Legacy fallback (
/opt/homebrew/bin/ngspice) is tier-4 last-resort only. It is not the preferred path; preferPATHor the$NGSPICEvariable. - KiCad integration is a CLI helper, not an in-application plugin.
Development
Run the test suite (no ngspice required for unit tests; integration tests auto-skip when ngspice is absent):
PYTHONPATH=src python3 -m pytest -q
Observed output on the current suite:
82 passed in 3.93s
How we got here
- Audited the original image-to-sim idea (market / engineering / business).
- Deep research across the full EDA/sim workflow + a competition cross-check.
- Ruled out taken/funded gaps (AR debugging = Cadence inspectAR; AI autorouting = Quilter et al.; SI/PI = heavy field-solver work).
- Landed on the SPICE result-trustworthiness wedge and built the tool.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file spiceguard-0.2.0.tar.gz.
File metadata
- Download URL: spiceguard-0.2.0.tar.gz
- Upload date:
- Size: 42.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e3943c4e8f4098d8197415656ddc2657ccfaebd2acae9278a840c444271d50b0
|
|
| MD5 |
52a5b087d1e9ef88bb68d0863231833d
|
|
| BLAKE2b-256 |
e24ffb82b095127259fbd92bb81f09428177ee4d8f5e437969d643ccc01538c3
|
Provenance
The following attestation bundles were made for spiceguard-0.2.0.tar.gz:
Publisher:
publish.yml on moiz-lakkadkutta/circuit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spiceguard-0.2.0.tar.gz -
Subject digest:
e3943c4e8f4098d8197415656ddc2657ccfaebd2acae9278a840c444271d50b0 - Sigstore transparency entry: 1895690955
- Sigstore integration time:
-
Permalink:
moiz-lakkadkutta/circuit@94d106e0b9fc31cbc741b4561fd73f5cbceeeb53 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/moiz-lakkadkutta
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@94d106e0b9fc31cbc741b4561fd73f5cbceeeb53 -
Trigger Event:
release
-
Statement type:
File details
Details for the file spiceguard-0.2.0-py3-none-any.whl.
File metadata
- Download URL: spiceguard-0.2.0-py3-none-any.whl
- Upload date:
- Size: 27.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a55b327a05dc1227b3f6f52b2748b561ce12377b8d6e2f7fcb57b2d9b7605ba6
|
|
| MD5 |
90ded9183a277f265859febdd28105a6
|
|
| BLAKE2b-256 |
de00d55419046a8093ecb8e3d25444d3f1e584b3f78e0c8a71b47e1761be5520
|
Provenance
The following attestation bundles were made for spiceguard-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on moiz-lakkadkutta/circuit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spiceguard-0.2.0-py3-none-any.whl -
Subject digest:
a55b327a05dc1227b3f6f52b2748b561ce12377b8d6e2f7fcb57b2d9b7605ba6 - Sigstore transparency entry: 1895691182
- Sigstore integration time:
-
Permalink:
moiz-lakkadkutta/circuit@94d106e0b9fc31cbc741b4561fd73f5cbceeeb53 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/moiz-lakkadkutta
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@94d106e0b9fc31cbc741b4561fd73f5cbceeeb53 -
Trigger Event:
release
-
Statement type: