spiceguard 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spiceguard-0.1.0/LICENSE +21 -0
- spiceguard-0.1.0/PKG-INFO +341 -0
- spiceguard-0.1.0/README.md +291 -0
- spiceguard-0.1.0/pyproject.toml +43 -0
- spiceguard-0.1.0/setup.cfg +4 -0
- spiceguard-0.1.0/src/spiceguard/__init__.py +22 -0
- spiceguard-0.1.0/src/spiceguard/__main__.py +4 -0
- spiceguard-0.1.0/src/spiceguard/checks.py +133 -0
- spiceguard-0.1.0/src/spiceguard/cli.py +176 -0
- spiceguard-0.1.0/src/spiceguard/core.py +84 -0
- spiceguard-0.1.0/src/spiceguard/formats.py +174 -0
- spiceguard-0.1.0/src/spiceguard/kicad.py +139 -0
- spiceguard-0.1.0/src/spiceguard/netlist.py +390 -0
- spiceguard-0.1.0/src/spiceguard/ngspice.py +121 -0
- spiceguard-0.1.0/src/spiceguard.egg-info/PKG-INFO +341 -0
- spiceguard-0.1.0/src/spiceguard.egg-info/SOURCES.txt +23 -0
- spiceguard-0.1.0/src/spiceguard.egg-info/dependency_links.txt +1 -0
- spiceguard-0.1.0/src/spiceguard.egg-info/entry_points.txt +2 -0
- spiceguard-0.1.0/src/spiceguard.egg-info/top_level.txt +1 -0
- spiceguard-0.1.0/tests/test_cli.py +413 -0
- spiceguard-0.1.0/tests/test_core.py +183 -0
- spiceguard-0.1.0/tests/test_kicad.py +326 -0
- spiceguard-0.1.0/tests/test_ngspice_path.py +306 -0
- spiceguard-0.1.0/tests/test_security.py +88 -0
- spiceguard-0.1.0/tests/test_subcircuits.py +406 -0
spiceguard-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Moiz Lakkadkutta
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spiceguard
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Can I trust this SPICE result? Catches silent/wrong ngspice simulation results.
|
|
5
|
+
Author-email: Moiz Lakkadkutta <moizlakkadkutta1@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Moiz Lakkadkutta
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/moiz-lakkadkutta/circuit
|
|
29
|
+
Project-URL: Repository, https://github.com/moiz-lakkadkutta/circuit
|
|
30
|
+
Project-URL: Issues, https://github.com/moiz-lakkadkutta/circuit/issues
|
|
31
|
+
Keywords: spice,ngspice,circuit,simulation,eda,electronics,kicad,ltspice,netlist,verification,convergence
|
|
32
|
+
Classifier: Development Status :: 4 - Beta
|
|
33
|
+
Classifier: Environment :: Console
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: Intended Audience :: Science/Research
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Operating System :: OS Independent
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
44
|
+
Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
|
|
45
|
+
Classifier: Topic :: Utilities
|
|
46
|
+
Requires-Python: >=3.9
|
|
47
|
+
Description-Content-Type: text/markdown
|
|
48
|
+
License-File: LICENSE
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# circuit / spiceguard
|
|
52
|
+
|
|
53
|
+
This repo started as **CircuitCLI**, an image/photo-to-SPICE-simulation pipeline
|
|
54
|
+
(YOLO + OCR + graph → ngspice). That idea was dropped after research showed the
|
|
55
|
+
problem it targeted ("redrawing schematics") isn't a pain users actually report,
|
|
56
|
+
and the obvious adjacent gaps were already taken or funded.
|
|
57
|
+
|
|
58
|
+
It is now exploring a different, evidence-led direction: a **SPICE result
|
|
59
|
+
trust-guard**.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## What is spiceguard?
|
|
64
|
+
|
|
65
|
+
Modern ngspice recovers from many classic convergence problems on its own — and
|
|
66
|
+
when it can't, it often returns **exit code 0 with a plausible but wrong answer**
|
|
67
|
+
(a relaxed fallback estimate, or arbitrary voltages on an ungrounded node).
|
|
68
|
+
Nothing in the standard flow warns you.
|
|
69
|
+
|
|
70
|
+
`spiceguard` answers one question about a SPICE run: **can I trust this result?**
|
|
71
|
+
|
|
72
|
+
It combines static netlist analysis, ngspice failure-log decoding (cryptic
|
|
73
|
+
internal names translated to the real component plus a specific fix), and
|
|
74
|
+
silent-failure detection (exit-0 runs that are still untrustworthy). It accepts
|
|
75
|
+
netlists from ngspice, KiCad, LTspice, and PSpice, and can convert LTspice `.asc`
|
|
76
|
+
schematics (experimental, built-in 2-pin symbols only).
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Install
|
|
81
|
+
|
|
82
|
+
**Requirements:** Python 3.9+, ngspice
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
brew install ngspice # macOS; Linux: apt install ngspice
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Install from the repo:**
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install .
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
This registers a `spiceguard` command on your PATH. Alternatively, run directly
|
|
95
|
+
without installing (useful during development):
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
PYTHONPATH=src python3 -m spiceguard FILE...
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## ngspice path resolution (priority order)
|
|
104
|
+
|
|
105
|
+
spiceguard locates the ngspice binary in this order:
|
|
106
|
+
|
|
107
|
+
1. `--ngspice PATH` CLI flag — if given and not executable, raises an error immediately (no fallthrough)
|
|
108
|
+
2. `$NGSPICE` environment variable — same hard-configured semantics; error if set but not usable
|
|
109
|
+
3. `which ngspice` (PATH lookup)
|
|
110
|
+
4. Legacy fallback `/opt/homebrew/bin/ngspice`
|
|
111
|
+
|
|
112
|
+
If none of the above resolves to a usable binary, spiceguard exits with code **3**
|
|
113
|
+
and prints a clear message listing what was tried. Install ngspice, or point to it
|
|
114
|
+
explicitly:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
spiceguard --ngspice /usr/local/bin/ngspice mynetlist.cir
|
|
118
|
+
# or
|
|
119
|
+
export NGSPICE=/usr/local/bin/ngspice
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## CLI usage
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
spiceguard [--ngspice PATH] [--version] [--help] FILE...
|
|
128
|
+
spiceguard kicad [--ngspice PATH] FILE...
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Pass one or more netlist (or schematic) files. When multiple files are given,
|
|
132
|
+
spiceguard evaluates each in sequence and exits with the worst verdict across all.
|
|
133
|
+
|
|
134
|
+
### Options
|
|
135
|
+
|
|
136
|
+
| Flag | Description |
|
|
137
|
+
|------|-------------|
|
|
138
|
+
| `FILE...` | One or more netlist or schematic files to check |
|
|
139
|
+
| `--ngspice PATH` | Explicit path to the ngspice binary |
|
|
140
|
+
| `--version` | Print version and exit |
|
|
141
|
+
| `--help` | Show usage |
|
|
142
|
+
|
|
143
|
+
### Exit codes
|
|
144
|
+
|
|
145
|
+
| Code | Meaning |
|
|
146
|
+
|------|---------|
|
|
147
|
+
| 0 | TRUSTWORTHY — ngspice exited 0 and no trust issues found |
|
|
148
|
+
| 1 | FAILED — ngspice exited non-zero |
|
|
149
|
+
| 2 | SUSPECT — ngspice exited 0 but trust issues were detected |
|
|
150
|
+
| 3 | ngspice not found |
|
|
151
|
+
| 64 | Usage error (bad arguments) |
|
|
152
|
+
|
|
153
|
+
### Example
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
$ PYTHONPATH=src python3 -m spiceguard tests/netlists/n5_healthy_control.cir
|
|
157
|
+
|
|
158
|
+
======================================================================
|
|
159
|
+
n5_healthy_control.cir
|
|
160
|
+
======================================================================
|
|
161
|
+
✓ TRUSTWORTHY (ngspice exit 0)
|
|
162
|
+
|
|
163
|
+
No trust issues detected.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
$ PYTHONPATH=src python3 -m spiceguard tests/netlists/n1_missing_ground.cir
|
|
168
|
+
|
|
169
|
+
======================================================================
|
|
170
|
+
n1_missing_ground.cir
|
|
171
|
+
======================================================================
|
|
172
|
+
⚠ SUSPECT (ngspice exit 0)
|
|
173
|
+
|
|
174
|
+
[FATAL] no_ground
|
|
175
|
+
→ 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'.
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Input formats
|
|
181
|
+
|
|
182
|
+
| Extension | Handling |
|
|
183
|
+
|-----------|----------|
|
|
184
|
+
| `.cir`, `.net`, `.sp`, `.spice`, `.ckt` | Passed directly to ngspice |
|
|
185
|
+
| Any other netlist | ngspice natively translates KiCad, LTspice, PSpice, HSpice dialects |
|
|
186
|
+
| `.asc` | Converted in-process (experimental — see below) |
|
|
187
|
+
|
|
188
|
+
**LTspice `.asc` (experimental):** spiceguard converts `.asc` schematics using
|
|
189
|
+
built-in 2-pin symbol geometry (resistor, capacitor, inductor, diode, voltage
|
|
190
|
+
source, current source). Net connectivity is recovered by union-find over
|
|
191
|
+
wire/pin/flag coordinates. For anything beyond these built-in symbols, export
|
|
192
|
+
the netlist from LTspice ("View > SPICE Netlist") and feed that instead — that
|
|
193
|
+
path is exact. When a `.asc` is evaluated, the generated netlist is printed at
|
|
194
|
+
the end of the report so you can compare it against LTspice's own export.
|
|
195
|
+
|
|
196
|
+
### Subcircuit support (Feature B)
|
|
197
|
+
|
|
198
|
+
spiceguard's parser handles:
|
|
199
|
+
|
|
200
|
+
- `.subckt` / `.ends` block collection and extraction
|
|
201
|
+
- `X`-instance flattening with automatic node namespacing (internal nodes become
|
|
202
|
+
`instancename:node` to avoid collisions)
|
|
203
|
+
- `+` continuation lines rejoined before parsing
|
|
204
|
+
- `.include` file resolution (local files only; URL `.include` lines are warned
|
|
205
|
+
and skipped)
|
|
206
|
+
|
|
207
|
+
Errors detected during subcircuit processing:
|
|
208
|
+
|
|
209
|
+
- `undefined_subckt` — X-instance references a subckt name not defined in the netlist
|
|
210
|
+
- `port_mismatch` — X-instance provides a different number of nodes than the subckt port list
|
|
211
|
+
- `subckt_recursion` — self- or mutual-referencing subckts (skipped with a warning)
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## KiCad workflow (Feature C)
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
spiceguard kicad myboard.cir
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The `kicad` subcommand runs the standard trust check **plus** a KiCad-specific
|
|
222
|
+
preflight that detects the classic export gotcha:
|
|
223
|
+
|
|
224
|
+
**`kicad_ground_not_zero`** — ngspice requires the circuit ground to be exactly
|
|
225
|
+
node `0`. KiCad schematics commonly use a `GND` net (or `GNDA`, `VSS`, `0V`,
|
|
226
|
+
`AGND`, `DGND`, `PGND`) that is NOT automatically mapped to node 0 on SPICE
|
|
227
|
+
export. When such a net is present and node 0 is absent, the simulation silently
|
|
228
|
+
floats the entire circuit, yielding wrong voltages with no error. The message
|
|
229
|
+
includes KiCad-specific fix instructions (set node mapping in Symbol Properties
|
|
230
|
+
or place a `PWR_FLAG`).
|
|
231
|
+
|
|
232
|
+
**Pipe form** — export and check in a single step without writing a file:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
kicad-cli sch export netlist --format spice myboard.kicad_sch -o - \
|
|
236
|
+
| spiceguard kicad -
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Important:** spiceguard is a command-line workflow helper, not a native KiCad
|
|
240
|
+
plugin. KiCad has no post-simulation event API, so there is no in-GUI integration;
|
|
241
|
+
run `spiceguard kicad` from your terminal or CI pipeline.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Verdict model
|
|
246
|
+
|
|
247
|
+
Every run produces one of three verdicts:
|
|
248
|
+
|
|
249
|
+
| Verdict | Meaning |
|
|
250
|
+
|---------|---------|
|
|
251
|
+
| **TRUSTWORTHY** | ngspice exited 0 and no trust-breaking issues found |
|
|
252
|
+
| **SUSPECT** | ngspice exited 0 but at least one FATAL/SILENT/WARN issue detected |
|
|
253
|
+
| **FAILED** | ngspice exited non-zero |
|
|
254
|
+
|
|
255
|
+
FAILED outranks SUSPECT, which outranks TRUSTWORTHY (this is why exit code 1 < 2
|
|
256
|
+
numerically but FAILED is the worst outcome).
|
|
257
|
+
|
|
258
|
+
### Detectors
|
|
259
|
+
|
|
260
|
+
| Code | Severity | Description |
|
|
261
|
+
|------|----------|-------------|
|
|
262
|
+
| `no_ground` | FATAL | No node '0' — circuit has no voltage reference |
|
|
263
|
+
| `source_conflict` | FATAL | Multiple voltage sources forced across the same node pair |
|
|
264
|
+
| `no_dc_path` | FATAL | Node reachable only through capacitors/current sources — no DC reference |
|
|
265
|
+
| `timestep_collapse` | FATAL | Transient timestep collapsed; specific culprit identified from the log |
|
|
266
|
+
| `singular_node` | FATAL | Singular matrix at a node — no defined DC solution |
|
|
267
|
+
| `singular_branch` | FATAL | Singular matrix at a branch (current unconstrained) |
|
|
268
|
+
| `silent_fallback` | SILENT | ngspice exited 0 after gmin/source stepping **failed**; operating point is a relaxed estimate, not a true solution |
|
|
269
|
+
| `kicad_ground_not_zero` | WARN | KiCad export uses GND/GNDA/etc. but no node 0 (kicad subcommand only) |
|
|
270
|
+
| `dangling_node` | WARN | Node connects to only one pin — likely a wiring mistake |
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Security
|
|
275
|
+
|
|
276
|
+
spiceguard runs ngspice in batch mode on the netlists you give it. **Treat a
|
|
277
|
+
netlist like a script you are about to run**, because in two ways it is one:
|
|
278
|
+
|
|
279
|
+
- A SPICE netlist can contain `.control`/`shell` directives that execute
|
|
280
|
+
arbitrary shell commands. The captured log may include their output.
|
|
281
|
+
- `.include` reads whatever file path the netlist specifies (the same files
|
|
282
|
+
ngspice itself would read), so a hostile netlist can point at files on your
|
|
283
|
+
disk. The contents are parsed as a netlist, not printed.
|
|
284
|
+
|
|
285
|
+
**Only run spiceguard on netlists you trust.** It is a local dev/CI utility,
|
|
286
|
+
not a sandbox.
|
|
287
|
+
|
|
288
|
+
Hardening that *is* in place:
|
|
289
|
+
|
|
290
|
+
- ngspice is invoked with `--no-spiceinit`, so a `.spiceinit`/`spice.rc` config
|
|
291
|
+
planted next to the netlist is **not** auto-executed.
|
|
292
|
+
- The simulator is launched with a fixed argument list, **never** through a
|
|
293
|
+
shell (no `shell=True`), so the path can't be shell-injected.
|
|
294
|
+
- A 120s timeout terminates a hung/non-converging run (reported as FAILED, not
|
|
295
|
+
a crash); temp files use `mkstemp` (0600) and are always cleaned up;
|
|
296
|
+
subcircuit nesting is depth-bounded against stack exhaustion.
|
|
297
|
+
- spiceguard has **no third-party runtime dependencies**, and the source passes
|
|
298
|
+
`bandit -r src/` with no findings (`pip-audit` reports no vulnerable deps).
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Limitations (honest scope)
|
|
303
|
+
|
|
304
|
+
- **Parser scope:** The static netlist parser covers the element types listed in
|
|
305
|
+
`netlist.py` (`NODE_COUNT`). Exotic or simulator-specific elements not in that
|
|
306
|
+
table are silently skipped (they do not affect the running netlist — ngspice
|
|
307
|
+
still sees the full file).
|
|
308
|
+
- **`.asc` is experimental** and scoped to 6 built-in 2-pin symbol types. Any
|
|
309
|
+
schematic with transistors, op-amps, subcircuit blocks, or custom symbols must
|
|
310
|
+
be exported from LTspice first. Always verify the generated netlist against
|
|
311
|
+
LTspice's own "View > SPICE Netlist".
|
|
312
|
+
- **Legacy fallback** (`/opt/homebrew/bin/ngspice`) is tier-4 last-resort only.
|
|
313
|
+
It is not the preferred path; prefer `PATH` or the `$NGSPICE` variable.
|
|
314
|
+
- **KiCad integration** is a CLI helper, not an in-application plugin.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Development
|
|
319
|
+
|
|
320
|
+
Run the test suite (no ngspice required for unit tests; integration tests
|
|
321
|
+
auto-skip when ngspice is absent):
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
PYTHONPATH=src python3 -m pytest -q
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Observed output on the current suite:
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
82 passed in 3.93s
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## How we got here
|
|
336
|
+
|
|
337
|
+
1. Audited the original image-to-sim idea (market / engineering / business).
|
|
338
|
+
2. Deep research across the full EDA/sim workflow + a competition cross-check.
|
|
339
|
+
3. Ruled out taken/funded gaps (AR debugging = Cadence inspectAR; AI autorouting
|
|
340
|
+
= Quilter et al.; SI/PI = heavy field-solver work).
|
|
341
|
+
4. Landed on the SPICE result-trustworthiness wedge and built the tool.
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# circuit / spiceguard
|
|
2
|
+
|
|
3
|
+
This repo started as **CircuitCLI**, an image/photo-to-SPICE-simulation pipeline
|
|
4
|
+
(YOLO + OCR + graph → ngspice). That idea was dropped after research showed the
|
|
5
|
+
problem it targeted ("redrawing schematics") isn't a pain users actually report,
|
|
6
|
+
and the obvious adjacent gaps were already taken or funded.
|
|
7
|
+
|
|
8
|
+
It is now exploring a different, evidence-led direction: a **SPICE result
|
|
9
|
+
trust-guard**.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## What is spiceguard?
|
|
14
|
+
|
|
15
|
+
Modern ngspice recovers from many classic convergence problems on its own — and
|
|
16
|
+
when it can't, it often returns **exit code 0 with a plausible but wrong answer**
|
|
17
|
+
(a relaxed fallback estimate, or arbitrary voltages on an ungrounded node).
|
|
18
|
+
Nothing in the standard flow warns you.
|
|
19
|
+
|
|
20
|
+
`spiceguard` answers one question about a SPICE run: **can I trust this result?**
|
|
21
|
+
|
|
22
|
+
It combines static netlist analysis, ngspice failure-log decoding (cryptic
|
|
23
|
+
internal names translated to the real component plus a specific fix), and
|
|
24
|
+
silent-failure detection (exit-0 runs that are still untrustworthy). It accepts
|
|
25
|
+
netlists from ngspice, KiCad, LTspice, and PSpice, and can convert LTspice `.asc`
|
|
26
|
+
schematics (experimental, built-in 2-pin symbols only).
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
**Requirements:** Python 3.9+, ngspice
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
brew install ngspice # macOS; Linux: apt install ngspice
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Install from the repo:**
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install .
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This registers a `spiceguard` command on your PATH. Alternatively, run directly
|
|
45
|
+
without installing (useful during development):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
PYTHONPATH=src python3 -m spiceguard FILE...
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## ngspice path resolution (priority order)
|
|
54
|
+
|
|
55
|
+
spiceguard locates the ngspice binary in this order:
|
|
56
|
+
|
|
57
|
+
1. `--ngspice PATH` CLI flag — if given and not executable, raises an error immediately (no fallthrough)
|
|
58
|
+
2. `$NGSPICE` environment variable — same hard-configured semantics; error if set but not usable
|
|
59
|
+
3. `which ngspice` (PATH lookup)
|
|
60
|
+
4. Legacy fallback `/opt/homebrew/bin/ngspice`
|
|
61
|
+
|
|
62
|
+
If none of the above resolves to a usable binary, spiceguard exits with code **3**
|
|
63
|
+
and prints a clear message listing what was tried. Install ngspice, or point to it
|
|
64
|
+
explicitly:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
spiceguard --ngspice /usr/local/bin/ngspice mynetlist.cir
|
|
68
|
+
# or
|
|
69
|
+
export NGSPICE=/usr/local/bin/ngspice
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## CLI usage
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
spiceguard [--ngspice PATH] [--version] [--help] FILE...
|
|
78
|
+
spiceguard kicad [--ngspice PATH] FILE...
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Pass one or more netlist (or schematic) files. When multiple files are given,
|
|
82
|
+
spiceguard evaluates each in sequence and exits with the worst verdict across all.
|
|
83
|
+
|
|
84
|
+
### Options
|
|
85
|
+
|
|
86
|
+
| Flag | Description |
|
|
87
|
+
|------|-------------|
|
|
88
|
+
| `FILE...` | One or more netlist or schematic files to check |
|
|
89
|
+
| `--ngspice PATH` | Explicit path to the ngspice binary |
|
|
90
|
+
| `--version` | Print version and exit |
|
|
91
|
+
| `--help` | Show usage |
|
|
92
|
+
|
|
93
|
+
### Exit codes
|
|
94
|
+
|
|
95
|
+
| Code | Meaning |
|
|
96
|
+
|------|---------|
|
|
97
|
+
| 0 | TRUSTWORTHY — ngspice exited 0 and no trust issues found |
|
|
98
|
+
| 1 | FAILED — ngspice exited non-zero |
|
|
99
|
+
| 2 | SUSPECT — ngspice exited 0 but trust issues were detected |
|
|
100
|
+
| 3 | ngspice not found |
|
|
101
|
+
| 64 | Usage error (bad arguments) |
|
|
102
|
+
|
|
103
|
+
### Example
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
$ PYTHONPATH=src python3 -m spiceguard tests/netlists/n5_healthy_control.cir
|
|
107
|
+
|
|
108
|
+
======================================================================
|
|
109
|
+
n5_healthy_control.cir
|
|
110
|
+
======================================================================
|
|
111
|
+
✓ TRUSTWORTHY (ngspice exit 0)
|
|
112
|
+
|
|
113
|
+
No trust issues detected.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
$ PYTHONPATH=src python3 -m spiceguard tests/netlists/n1_missing_ground.cir
|
|
118
|
+
|
|
119
|
+
======================================================================
|
|
120
|
+
n1_missing_ground.cir
|
|
121
|
+
======================================================================
|
|
122
|
+
⚠ SUSPECT (ngspice exit 0)
|
|
123
|
+
|
|
124
|
+
[FATAL] no_ground
|
|
125
|
+
→ 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'.
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Input formats
|
|
131
|
+
|
|
132
|
+
| Extension | Handling |
|
|
133
|
+
|-----------|----------|
|
|
134
|
+
| `.cir`, `.net`, `.sp`, `.spice`, `.ckt` | Passed directly to ngspice |
|
|
135
|
+
| Any other netlist | ngspice natively translates KiCad, LTspice, PSpice, HSpice dialects |
|
|
136
|
+
| `.asc` | Converted in-process (experimental — see below) |
|
|
137
|
+
|
|
138
|
+
**LTspice `.asc` (experimental):** spiceguard converts `.asc` schematics using
|
|
139
|
+
built-in 2-pin symbol geometry (resistor, capacitor, inductor, diode, voltage
|
|
140
|
+
source, current source). Net connectivity is recovered by union-find over
|
|
141
|
+
wire/pin/flag coordinates. For anything beyond these built-in symbols, export
|
|
142
|
+
the netlist from LTspice ("View > SPICE Netlist") and feed that instead — that
|
|
143
|
+
path is exact. When a `.asc` is evaluated, the generated netlist is printed at
|
|
144
|
+
the end of the report so you can compare it against LTspice's own export.
|
|
145
|
+
|
|
146
|
+
### Subcircuit support (Feature B)
|
|
147
|
+
|
|
148
|
+
spiceguard's parser handles:
|
|
149
|
+
|
|
150
|
+
- `.subckt` / `.ends` block collection and extraction
|
|
151
|
+
- `X`-instance flattening with automatic node namespacing (internal nodes become
|
|
152
|
+
`instancename:node` to avoid collisions)
|
|
153
|
+
- `+` continuation lines rejoined before parsing
|
|
154
|
+
- `.include` file resolution (local files only; URL `.include` lines are warned
|
|
155
|
+
and skipped)
|
|
156
|
+
|
|
157
|
+
Errors detected during subcircuit processing:
|
|
158
|
+
|
|
159
|
+
- `undefined_subckt` — X-instance references a subckt name not defined in the netlist
|
|
160
|
+
- `port_mismatch` — X-instance provides a different number of nodes than the subckt port list
|
|
161
|
+
- `subckt_recursion` — self- or mutual-referencing subckts (skipped with a warning)
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## KiCad workflow (Feature C)
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
spiceguard kicad myboard.cir
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The `kicad` subcommand runs the standard trust check **plus** a KiCad-specific
|
|
172
|
+
preflight that detects the classic export gotcha:
|
|
173
|
+
|
|
174
|
+
**`kicad_ground_not_zero`** — ngspice requires the circuit ground to be exactly
|
|
175
|
+
node `0`. KiCad schematics commonly use a `GND` net (or `GNDA`, `VSS`, `0V`,
|
|
176
|
+
`AGND`, `DGND`, `PGND`) that is NOT automatically mapped to node 0 on SPICE
|
|
177
|
+
export. When such a net is present and node 0 is absent, the simulation silently
|
|
178
|
+
floats the entire circuit, yielding wrong voltages with no error. The message
|
|
179
|
+
includes KiCad-specific fix instructions (set node mapping in Symbol Properties
|
|
180
|
+
or place a `PWR_FLAG`).
|
|
181
|
+
|
|
182
|
+
**Pipe form** — export and check in a single step without writing a file:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
kicad-cli sch export netlist --format spice myboard.kicad_sch -o - \
|
|
186
|
+
| spiceguard kicad -
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Important:** spiceguard is a command-line workflow helper, not a native KiCad
|
|
190
|
+
plugin. KiCad has no post-simulation event API, so there is no in-GUI integration;
|
|
191
|
+
run `spiceguard kicad` from your terminal or CI pipeline.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Verdict model
|
|
196
|
+
|
|
197
|
+
Every run produces one of three verdicts:
|
|
198
|
+
|
|
199
|
+
| Verdict | Meaning |
|
|
200
|
+
|---------|---------|
|
|
201
|
+
| **TRUSTWORTHY** | ngspice exited 0 and no trust-breaking issues found |
|
|
202
|
+
| **SUSPECT** | ngspice exited 0 but at least one FATAL/SILENT/WARN issue detected |
|
|
203
|
+
| **FAILED** | ngspice exited non-zero |
|
|
204
|
+
|
|
205
|
+
FAILED outranks SUSPECT, which outranks TRUSTWORTHY (this is why exit code 1 < 2
|
|
206
|
+
numerically but FAILED is the worst outcome).
|
|
207
|
+
|
|
208
|
+
### Detectors
|
|
209
|
+
|
|
210
|
+
| Code | Severity | Description |
|
|
211
|
+
|------|----------|-------------|
|
|
212
|
+
| `no_ground` | FATAL | No node '0' — circuit has no voltage reference |
|
|
213
|
+
| `source_conflict` | FATAL | Multiple voltage sources forced across the same node pair |
|
|
214
|
+
| `no_dc_path` | FATAL | Node reachable only through capacitors/current sources — no DC reference |
|
|
215
|
+
| `timestep_collapse` | FATAL | Transient timestep collapsed; specific culprit identified from the log |
|
|
216
|
+
| `singular_node` | FATAL | Singular matrix at a node — no defined DC solution |
|
|
217
|
+
| `singular_branch` | FATAL | Singular matrix at a branch (current unconstrained) |
|
|
218
|
+
| `silent_fallback` | SILENT | ngspice exited 0 after gmin/source stepping **failed**; operating point is a relaxed estimate, not a true solution |
|
|
219
|
+
| `kicad_ground_not_zero` | WARN | KiCad export uses GND/GNDA/etc. but no node 0 (kicad subcommand only) |
|
|
220
|
+
| `dangling_node` | WARN | Node connects to only one pin — likely a wiring mistake |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Security
|
|
225
|
+
|
|
226
|
+
spiceguard runs ngspice in batch mode on the netlists you give it. **Treat a
|
|
227
|
+
netlist like a script you are about to run**, because in two ways it is one:
|
|
228
|
+
|
|
229
|
+
- A SPICE netlist can contain `.control`/`shell` directives that execute
|
|
230
|
+
arbitrary shell commands. The captured log may include their output.
|
|
231
|
+
- `.include` reads whatever file path the netlist specifies (the same files
|
|
232
|
+
ngspice itself would read), so a hostile netlist can point at files on your
|
|
233
|
+
disk. The contents are parsed as a netlist, not printed.
|
|
234
|
+
|
|
235
|
+
**Only run spiceguard on netlists you trust.** It is a local dev/CI utility,
|
|
236
|
+
not a sandbox.
|
|
237
|
+
|
|
238
|
+
Hardening that *is* in place:
|
|
239
|
+
|
|
240
|
+
- ngspice is invoked with `--no-spiceinit`, so a `.spiceinit`/`spice.rc` config
|
|
241
|
+
planted next to the netlist is **not** auto-executed.
|
|
242
|
+
- The simulator is launched with a fixed argument list, **never** through a
|
|
243
|
+
shell (no `shell=True`), so the path can't be shell-injected.
|
|
244
|
+
- A 120s timeout terminates a hung/non-converging run (reported as FAILED, not
|
|
245
|
+
a crash); temp files use `mkstemp` (0600) and are always cleaned up;
|
|
246
|
+
subcircuit nesting is depth-bounded against stack exhaustion.
|
|
247
|
+
- spiceguard has **no third-party runtime dependencies**, and the source passes
|
|
248
|
+
`bandit -r src/` with no findings (`pip-audit` reports no vulnerable deps).
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Limitations (honest scope)
|
|
253
|
+
|
|
254
|
+
- **Parser scope:** The static netlist parser covers the element types listed in
|
|
255
|
+
`netlist.py` (`NODE_COUNT`). Exotic or simulator-specific elements not in that
|
|
256
|
+
table are silently skipped (they do not affect the running netlist — ngspice
|
|
257
|
+
still sees the full file).
|
|
258
|
+
- **`.asc` is experimental** and scoped to 6 built-in 2-pin symbol types. Any
|
|
259
|
+
schematic with transistors, op-amps, subcircuit blocks, or custom symbols must
|
|
260
|
+
be exported from LTspice first. Always verify the generated netlist against
|
|
261
|
+
LTspice's own "View > SPICE Netlist".
|
|
262
|
+
- **Legacy fallback** (`/opt/homebrew/bin/ngspice`) is tier-4 last-resort only.
|
|
263
|
+
It is not the preferred path; prefer `PATH` or the `$NGSPICE` variable.
|
|
264
|
+
- **KiCad integration** is a CLI helper, not an in-application plugin.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Development
|
|
269
|
+
|
|
270
|
+
Run the test suite (no ngspice required for unit tests; integration tests
|
|
271
|
+
auto-skip when ngspice is absent):
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
PYTHONPATH=src python3 -m pytest -q
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Observed output on the current suite:
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
82 passed in 3.93s
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## How we got here
|
|
286
|
+
|
|
287
|
+
1. Audited the original image-to-sim idea (market / engineering / business).
|
|
288
|
+
2. Deep research across the full EDA/sim workflow + a competition cross-check.
|
|
289
|
+
3. Ruled out taken/funded gaps (AR debugging = Cadence inspectAR; AI autorouting
|
|
290
|
+
= Quilter et al.; SI/PI = heavy field-solver work).
|
|
291
|
+
4. Landed on the SPICE result-trustworthiness wedge and built the tool.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "spiceguard"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Can I trust this SPICE result? Catches silent/wrong ngspice simulation results."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "Moiz Lakkadkutta", email = "moizlakkadkutta1@gmail.com" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"spice", "ngspice", "circuit", "simulation", "eda", "electronics",
|
|
15
|
+
"kicad", "ltspice", "netlist", "verification", "convergence",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Environment :: Console",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Intended Audience :: Science/Research",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.9",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
|
|
31
|
+
"Topic :: Utilities",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/moiz-lakkadkutta/circuit"
|
|
36
|
+
Repository = "https://github.com/moiz-lakkadkutta/circuit"
|
|
37
|
+
Issues = "https://github.com/moiz-lakkadkutta/circuit/issues"
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
spiceguard = "spiceguard.cli:main"
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.packages.find]
|
|
43
|
+
where = ["src"]
|