velxio/docs/wiki/circuit-emulation-tests.md

229 lines
8.9 KiB
Markdown
Raw Normal View History

feat: electrical simulation via ngspice-WASM (eecircuit-engine) Adds full SPICE-accurate electrical simulation to Velxio, behind a lazy- loaded ⚡ toolbar toggle. Arduino / ESP32 / RP2040 sketches now co-simulate with real analog behaviour: correct voltages on wires, real I–V curves on LEDs, working potentiometers, NTC thermistors read by analogRead(), PWM driving RC filters, transistors, op-amps, diodes, MOSFETs, etc. Engine: eecircuit-engine (ngspice compiled to WebAssembly). Main bundle stays at 2.4 MB; the 20 MB SPICE chunk only loads when the user activates electrical mode. Disabled at build time via VITE_ELECTRICAL_SIM=false. Frontend additions: - simulation/spice/: SpiceEngine wrapper + lazy entry, NetlistBuilder with UnionFind over wires, componentToSpice mapping (24 metadataIds incl. real part numbers: 2N2222, 2N3055, BC547, IRF540, 2N7000, 1N4148, 1N4007, 1N4733, LEDs, NTC, op-amp ideal), CircuitScheduler with debounced coalescing, AVRSpiceBridge for quasi-static co-simulation. - store/useElectricalStore: Zustand slice, feature-flag aware. - components/analog-ui/: ⚡ toolbar toggle + SVG voltage overlay. - components/components-instruments/: Voltmeter, Ammeter probes. - 62 tests (spice-*, netlist-builder, component-to-spice, instruments). Sandbox (test/test_circuit/): 47-test validation sandbox that proved the approach (hand-rolled MNA baseline + ngspice pipeline) before porting to the app. Kept as reference. Docs: docs/wiki/circuit-emulation-*.md (13 engineering pages covering architecture, solvers, components, AVR bridge, gotchas, performance, integration plan, API reference, appendix) + electrical-simulation- user-guide.md (end-user facing). Reference plan: test/test_circuit/plan/phase_8_velxio_implementation.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:11:54 +07:00
# Test Catalog — All 47 Tests
Every test in [`test/test_circuit/test/`](../../test/test_circuit/test/) enumerated: the circuit under test, what physics it validates, and the numerical expectation.
## Baseline — hand-rolled MNA solver (25 tests)
### `passive.test.js` — 11 tests
Tests the linear DC solver on textbook passives.
| # | Test | Circuit | Expected | Tolerance |
|---|---|---|---|---|
| 1 | Voltage divider | 9V → 1k + 2k | V(out) = 6.0 V | 3 decimals |
| 2 | Equal-R divider | 5V → 1k + 1k | V(b) = 2.5 V | 3 decimals |
| 3 | Ohm's law | 5V, 220Ω | I = 22.7 mA | 5 decimals |
| 4 | Three 3kΩ parallel | series with 1k | V(p) = 1.5 V | 3 decimals |
| 5 | Current source + R | 1 mA into 2.2kΩ | V = 2.2 V | 3 decimals |
| 6 | Pot wiper at 50% | 10k pot, 5V | V = 2.5 V | 2 decimals |
| 7 | Pot sweep 0→1 | 5 steps | monotonic increase | ≥ 10 mV slack |
| 8 | NTC resistance @ 25°C | β=3950 | R = 10 000 Ω | within 1 Ω |
| 9 | NTC temperature monotonicity | T ∈ {0, 25, 50} | R(0) > R(25) > R(50) | textbook ranges |
| 10 | NTC + 10k divider | T ∈ {0, 25, 50} | V_A0 matches expected per table | per-point tolerance |
| 11 | Switch open/closed | 5V → 1k → switch → GND | open: 4.95.0 V, closed: < 50 mV | |
### `transient_rc.test.js` — 3 tests
| # | Test | Circuit | Expected | Tolerance |
|---|---|---|---|---|
| 1 | RC charging @ τ | 10k, 100µF, 5V | V(τ) ≈ 3.16 V | ±5% |
| 2 | 5τ settling | 1k, 1µF, 3.3V | V(5τ) > 99% V | hard bound |
| 3 | Capacitor discharge | pre-charged 5V → 10k | V(τ) in [1.5, 2.2] V | manual bounds |
### `diodes.test.js` — 7 tests
| # | Test | Circuit | Expected |
|---|---|---|---|
| 1 | Forward diode + 1k + 5V | Shockley default | V_d ∈ [0.55, 0.75] V, I ∈ [4, 5] mA |
| 2 | Reverse diode | diode blocks | V_a < 10 mV |
| 3 | Red LED + 220Ω + 5V | | V_f ∈ [1.8, 2.3] V, I ∈ [10, 16] mA, brightness > 0.5 |
| 4 | Blue LED higher Vf than red | | V_blue > V_red, V_blue ∈ [2.8, 3.6] V |
| 5 | LED brightness with supply | V ∈ {2.0, 3.0, 5.0} | monotonic, dim at 2V, bright at 5V |
| 6 | BJT switch ON | V_b = 5V, R_b = 10k | V_CE < 0.8 V |
| 7 | BJT switch OFF | V_b = 0 | V_CE > 4.9 V |
### `avr_blink.test.js` — 2 tests
Uses the real `fixtures/blink.hex` (copied from Velxio's test fixtures).
| # | Test | What it validates |
|---|---|---|
| 1 | Toggles pin 13 | After 2 s of AVR time, observed ≥ 3 edge transitions on D13; both 0 and 1 states visible |
| 2 | Drives simulated LED | Pin-13 voltage fed into a `VoltageSource`, 220 Ω + red LED; LED brightness switches between < 1% and > 50% |
### `e2e_pot_pwm_led.test.js` — 1 test
Uses `potToPwmProgram()` (hand-assembled: reads A0, writes to OCR0A for PWM on D6).
Circuit: `+5V → pot (10k) → A0 / GND` plus `D6 → 220Ω → red LED → GND`.
Sweep wiper 0.0 → 0.25 → 0.50 → 0.75 → 1.00:
| Wiper | V_A0 | PWM duty | I_LED | Brightness |
|---|---|---|---|---|
| 0.00 | 0.000 V | 0.0 % | 0.00 mA | 0.000 |
| 0.25 | 1.250 V | 24.7 % | 0.00 mA | 0.000 |
| 0.50 | 2.500 V | 49.8 % | 3.26 mA | 0.163 |
| 0.75 | 3.750 V | 74.9 % | 8.77 mA | 0.439 |
| 1.00 | 5.000 V | 100.0 % | 14.38 mA | 0.719 |
Assertions: duty at 0% < 5%, duty at 100% > 90%, brightness monotonic, final > 0.5, initial < 0.05.
### `e2e_thermistor.test.js` — 1 test
Uses `adcReadProgram()` + NTC + pullup divider.
Circuit: `+5V → 10kΩ → A0 → NTC → GND`.
Per temperature T ∈ {0, 15, 25, 35, 50} °C:
| T_set | V_A0 | ADC raw | R_ntc (true) | R_meas (recovered) | T_meas |
|---|---|---|---|---|---|
| 0 °C | 3.854 V | 789 | 33 621 Ω | 33 718 Ω | 0.05 °C |
| 15 °C | 3.065 V | 627 | 15 837 Ω | 15 833 Ω | 15.01 °C |
| 25 °C | 2.500 V | 511 | 10 000 Ω | 9 980 Ω | 25.04 °C |
| 35 °C | 1.971 V | 403 | 6 506 Ω | 6 500 Ω | 35.02 °C |
| 50 °C | 1.320 V | 270 | 3 588 Ω | 3 586 Ω | 50.02 °C |
Assertion: `|T_meas T_set| < 1 °C` at every point. Achieved: < 0.05 °C.
## ngspice — `spice_*.test.js` (22 tests)
### `ngspice_smoke.test.js` — 1 test
The minimal possible smoke test.
| # | Test | Netlist | Expected |
|---|---|---|---|
| 1 | Boot + voltage divider | `V 9 → R 1k → R 2k → GND, .op` | `V(out) = 6.0` |
### `spice_passive.test.js` — 3 tests
All run with `.op`:
| # | Circuit | Expected |
|---|---|---|
| 1 | 9V → 1k + 2k | V(out) = 6 V |
| 2 | Three 3kΩ parallel + 1k series from 3V | V(p) = 1.5 V |
| 3 | 1 mA current source into 2.2kΩ | V(a) = 2.2 V |
### `spice_transient.test.js` — 2 tests
| # | Test | `.tran` | Expected |
|---|---|---|---|
| 1 | RC charging | 10m 3 | V(τ) ≈ 3.16 V, V(5τ) > 90% |
| 2 | RLC underdamped oscillation | 10u 30m, R=1Ω, L=10mH, C=10µF | zero-crossings imply f ≈ 503 Hz, ±15 % |
### `spice_ac.test.js` — 2 tests
| # | Test | `.ac` | Expected |
|---|---|---|---|
| 1 | RC low-pass Bode | 20 ppd, 10 Hz 1 MHz | f₋₃dB ∈ [900, 1100] Hz; low-freq gain ≈ 0 dB; 20 dB/decade rolloff; phase @ cutoff ≈ 45° |
| 2 | Parallel-tank RLC bandpass | 30 ppd, 10 Hz 1 MHz | peak near 5033 Hz (±20%), passive so max gain ≈ 0 dB; 10×f₀ rejection > 15 dB below peak |
### `spice_active.test.js` — 6 tests
| # | Test | Analysis | Expected |
|---|---|---|---|
| 1 | Diode forward (1N4148 model) | `.op` | V_a ∈ [0.55, 0.80] V |
| 2 | Bridge rectifier (4 diodes + sine 6V/50Hz) | `.tran 0.1m 40m` | peak output 4.25.4 V, always positive |
| 3 | Common-emitter amp (2N2222, 12V, ~1kHz input, bypassed R_E) | `.tran 10u 6m` | voltage gain > 30 |
| 4 | N-MOS Level-1 switch, V_gate=5V | `.op` | V(drain) < 1.0 V |
| 5 | N-MOS Level-1 off, V_gate=0 | `.op` | V(drain) > 4.9 V |
| 6 | Op-amp inverting amp (E-source, R_in=1k, R_f=10k) | `.op` | V(out) ≈ 2.0 V |
### `spice_digital.test.js` — 4 tests
All gates implemented with B-sources using `u()` step function.
| # | Gate | Rows tested |
|---|---|---|
| 1 | AND | 4 (truth table) |
| 2 | NAND | 4 |
| 3 | XOR | 4 |
| 4 | AND → RC low-pass | `.tran 10u 20m` — verifies filter ripple < 1.5 V, raw AND swings 05 V, filtered mean in (0, 5) |
### `spice_555_astable.test.js` — 1 test
RC relaxation oscillator built from a Schmitt-switch (S-element with hysteresis).
- R = 10 kΩ, C = 10 nF
- `SMOD: Vt=2.5, Vh=0.833, Ron=100, Roff=1G`
- `.tran 0.5u 2m`
Expected: output toggles, ≥ 3 rising edges detected in the steady-state window; measured frequency between 500 Hz and 50 kHz (wide range because behavioral model differs from ideal 555 math).
Typical measured: ~7 kHz.
### `spice_avr_mixed.test.js` — 3 tests
The flagship mixed-signal demonstrator.
#### Test 1 — NTC → ngspice → ADC → sketch
For each T ∈ {0, 25, 50} °C:
1. ngspice `.op` solves `10k pullup + R_ntc(T)`
2. `avr.setAnalogVoltage(0, V(a0))`
3. `avr.runCycles(500_000)` runs `adcReadProgram()`
4. Read `ADCH/ADCL` registers directly → reconstruct 10-bit result
Expected results match textbook within ±5 LSB:
| T | V(a0) from ngspice | ADC raw from avr8js |
|---|---|---|
| 0 °C | 3.854 V | 789 |
| 25 °C | 2.500 V | 512 |
| 50 °C | 1.320 V | 270 |
#### Test 2 — Sketch → PWM → ngspice RC → filtered DC
For V_A0 ∈ {1.0, 2.5, 4.0} V:
1. Run `potToPwmProgram()` with the given analog voltage
2. Read PWM duty (must match `V_A0 / 5` ± 0.05)
3. Feed `duty × 5` as DC source into `R=10k, R_load=10MΩ` in ngspice
4. `.op` gives filtered DC output — should match duty × 5 within 0.1 V
Measured:
| V_A0 | duty | V_filt | expected |
|---|---|---|---|
| 1.0 V | 20.0 % | 0.999 V | 1.00 V |
| 2.5 V | 50.2 % | 2.507 V | 2.51 V |
| 4.0 V | 80.0 % | 3.996 V | 4.00 V |
#### Test 3 — Co-simulation loop (AVRSpiceBridge)
The bridge runs the AVR in 1 ms slices, re-solving ngspice between each. Pot wiper is moved mid-test.
- wiper = 0.25 → 5 slices run → expected ADC ≈ 256 (0.25 × 1023). Measured: **256**. Tolerance ±20 LSB.
- wiper moves to 0.75 → 5 more slices → expected ADC ≈ 767. Measured: **768**.
Demonstrates that ngspice + avr8js can be cleanly co-simulated quasi-statically.
## Pass/fail history
All 47 tests passed as of 2026-04-15. Running `npm test` from `test/test_circuit/` is the canonical verification. Expected wall-clock: ~5 seconds on an i7.
Full command output:
```
✓ test/ngspice_smoke.test.js (1 test) 372ms
✓ test/spice_passive.test.js (3 tests) 358ms
✓ test/spice_transient.test.js (2 tests) 629ms
✓ test/spice_ac.test.js (2 tests) 340ms
✓ test/spice_active.test.js (6 tests) 600ms
✓ test/spice_digital.test.js (4 tests) 420ms
✓ test/spice_555_astable.test.js (1 test) 609ms
✓ test/spice_avr_mixed.test.js (3 tests) 800ms
✓ test/passive.test.js (11 tests) 8ms
✓ test/transient_rc.test.js (3 tests) 22ms
✓ test/diodes.test.js (7 tests) 15ms
✓ test/avr_blink.test.js (2 tests) 2400ms
✓ test/e2e_pot_pwm_led.test.js (1 test) 90ms
✓ test/e2e_thermistor.test.js (1 test) 121ms
Test Files 14 passed (14)
Tests 47 passed (47)
Duration ~5 s
```