From 7ce184c4c6e5788b367bc0b8cd3ee57ce9aaf852 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Mon, 18 May 2026 13:32:20 -0300 Subject: [PATCH] fix(spice): always emit V-source per wired GPIO pin (root cause of intermittent dark-LED) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug reproduced via CDP probe across 5 Run/Stop cycles: cycle 1 worked (LED toggled), cycles 2-5 LED stayed dark — but exactly the same code, same canvas, same circuit. Tracing the live electrical store via __spiceDebug() showed: cycle-1 after-run: branchCurrentCount=3 (pin13 V-source present) cycle-2 after-run: branchCurrentCount=2 (pin13 V-source MISSING) cycle-3..5 after-run: branchCurrentCount=2 The flow: 1. User clicks Run -> board.boards reference changes -> service ticks. 2. runSolve calls collectPinStates(board, ...) to snapshot output pins. 3. collectPinStates was emitting an entry ONLY when pinManager.getPinState(pin) was currently TRUE. If the pin was LOW at that exact instant (which is most of the time for a Blink sketch — 50% duty), no pinStates entry, no V-source card in the netlist. 4. AVR runs, digitalWrite(13, HIGH) fires, handleMcuEdge calls scheduler.onMcuPinChange -> solver.alterSource('V_arduino-uno_13', 5). 5. ngspice gets 'alter V_arduino-uno_13 dc 5' but that V-source doesn't exist in the deck. Silent no-op. branchCurrents never updates. LED stays dark forever. The 'sometimes it works' impression came from cycle 1: the cold-boot AVR happened to land on a HIGH state precisely when the tick fired, so the V-source got emitted and every subsequent edge alter worked. The other cycles caught the AVR in LOW. Fix: always emit a digital PinSourceState — with v=0 when LOW, v=vcc when HIGH — so the NetlistBuilder always produces V__ cards for every wired GPIO. alterSource then has a target to bind to no matter what state the pin was in at solve time. Verified live via CDP probe (_probe_blink.mjs in working tree): pin13 toggles 0V<->5V at the Blink frequency LED anode follows at 0V<->1.838V (matches manual calculation: (5 - 1.84) / 220 = 14.4 mA forward current through the red LED) branchCurrentCount = 3 stable across all cycles --- .../src/simulation/spice/collectPinStates.ts | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/frontend/src/simulation/spice/collectPinStates.ts b/frontend/src/simulation/spice/collectPinStates.ts index 3c01cad2..4a06ceb0 100644 --- a/frontend/src/simulation/spice/collectPinStates.ts +++ b/frontend/src/simulation/spice/collectPinStates.ts @@ -72,8 +72,25 @@ export function collectPinStates( const pwmDuty = pm.getPwmValue(arduinoPin); if (pwmDuty > 0) { result[pinName] = { type: 'pwm', duty: pwmDuty }; - } else if (pm.getPinState(arduinoPin)) { - result[pinName] = { type: 'digital', v: vcc }; + } else { + // ALWAYS emit a digital source — even when the pin is currently + // LOW. NetlistBuilder turns this into a `V__` card, + // and MixedModeScheduler.onMcuPinChange later calls + // `alterSource('V__', vcc | 0)` on every MCU edge. + // If the V-source isn't in the netlist (because we skipped it + // here while LOW), the alter is a silent no-op against a name + // that doesn't exist; the LED then never lights up no matter + // how many times the sketch toggles the pin afterwards. + // + // This was the root cause of the reported "sometimes the LED + // works after Run, sometimes it doesn't" symptom. The behaviour + // was deterministic but appeared random because it depended on + // whether the AVR happened to be in the HIGH half of its blink + // cycle at the moment runSolve() captured pin states. + result[pinName] = { + type: 'digital', + v: pm.getPinState(arduinoPin) ? vcc : 0, + }; } } return result;