Skip to content

Electrical Verification (SPICE)

Offline, deterministic circuit verification for the classes of circuits the workflow tools generate — closing the gap between "manufacturable" and "works."

Scope and limits — read this first

  • Typed input only, never a raw SPICE deck. ngspice's interactive .control block supports a shell command that runs arbitrary OS commands. Accepting free-form deck text from a caller would be equivalent to unsandboxed code execution, so no tool or module in src/simulation/ accepts one. Every deck is generated by buildSpiceDeck() from typed, validated component data (src/simulation/types.ts), with net/ref names restricted to a safe identifier pattern before being embedded in deck text.
  • Component library is small and explicitly approximate. Resistors, capacitors, inductors, DC/pulse voltage and current sources, a small diode/LED model library (src/simulation/models.ts), and one deliberately simplified linear regulator model (ldo-behavioral). None of the diode/LED parameters are manufacturer-certified — see each model's source/caveat fields.
  • The LDO model has no dynamics. It clamps output to min(Vin - dropout, target) minus loadCurrent * outputResistance — a purely algebraic load-regulation approximation. It does not model thermal behavior, transient response, noise, soft-start, or protection features. Do not use it to verify anything beyond a rough steady-state sanity check.
  • The parser has not been validated against a live ngspice installation. No ngspice binary is available in this development environment. src/simulation/parser.ts targets ngspice's well-documented print v(node) output format (a name = value line per vector for .op, an Index/time/vector table for .tran), and is deliberately lenient — unrecognized output degrades to an empty/partial result rather than throwing. If you hit a parsing mismatch against your installed ngspice version, please file an issue with the actual output.
  • Nothing here reads your live EasyEDA schematic. These tools verify a circuit description you provide; they do not extract a netlist from the currently-open project. That translation (component values, package-derived parasitics, etc.) is out of scope.

Modules

src/simulation/
├── types.ts     — typed circuit description (SimComponent, SimCircuit, SimAnalysis, RailSpec)
├── models.ts    — diode/LED SPICE model library, with provenance
├── netlist.ts   — buildSpiceDeck(): typed circuit → SPICE deck text, with identifier validation
├── runner.ts    — detectNgspice(), runNgspiceDeck() — execFile-based, sandboxed, timeout-bound
├── parser.ts    — parseOperatingPointOutput(), parseTransientOutput()
├── verify.ts    — verifyRailAgainstSpec(): tolerance check against a RailSpec
└── index.ts     — barrel exports

runNgspiceDeck() always uses execFile (never a shell), writes the deck to a freshly-created temp directory that is removed in a finally block regardless of outcome, and enforces a timeout (default 15s).

MCP Tools

  • easyeda_simulate_operating_point (profile pro, read-only) — runs a .op analysis and returns node voltages, optionally checked against one or more RailSpec entries.
  • easyeda_simulate_transient (profile pro, read-only) — runs a .tran analysis and returns a time series (truncated to 200 samples, with truncated: true if more were produced), with rail specs checked against the final sample.

Both tools call detectNgspice() first and return available: false with an explanation rather than failing when ngspice is not installed — this is the expected, common case in most environments, not an error condition.

Wired into the power-rail workflow

easyeda_workflow_power_rail (src/tools/L2_workflows.ts) accepts an optional verifyRail field:

json
{
  "verifyRail": {
    "inputVoltage": 5,
    "outputVoltage": 3.3,
    "loadCurrentA": 0.5,
    "dropoutVoltage": 0.3,
    "outputResistanceOhms": 0.1,
    "tolerancePercent": 5
  }
}

When present, the tool builds a standalone 3-node model (input source → ldo-behavioral → load current sink) using those values — not a simulation of the literal placed components/pins — runs an operating-point analysis, and attaches a verification field to the response (available, observed_voltage, within_tolerance, caveat, and error when applicable). This runs in both preview and apply mode, so you get electrical feedback before committing to a placement.

Golden tests

tests/unit/simulation/golden.test.ts asserts two scenarios against hand-computed analytic expectations, using a mocked ngspice runner (stdout text in the exact format the parser targets):

  • RC charge transient: V(t) = Vfinal * (1 - e^(-t/RC)) at one and five time constants.
  • LDO under load: Vout = min(Vin - dropout, target) - Iload * Rout at a fixed load current.

Opt-in live check

pnpm smoke:ngspice (mirroring pnpm smoke:easyeda) runs the RC-charge scenario against a real ngspice binary when NGSPICE_LIVE_TESTS=true is set, comparing the result against the same analytic expectation. This is the one place the parser's format assumptions get checked against real ngspice output — skipped by default, and reports unavailable (not a failure) when no ngspice binary is found. Never part of the standard pnpm test/pnpm verify suite.

Released under the MIT License.