velxio/test/test_circuit/autosearch/01_findings.md

6.6 KiB
Raw Blame History

Hallazgos — sesión 2026-04-15

Resumen ejecutivo

Se construyó un sandbox bajo test/test_circuit/ que valida end-to-end la emulación de circuitos analógicos + digitales + Arduino real vía avr8js. 25 tests pasan en ~2.9 s. La propuesta del plan1.md (ngspice WASM, 40 MB) puede reemplazarse por un solver MNA en JS puro de ~500 líneas con resultados correctos en los casos probados.

Lo que funciona

Solver DC/transitorio (src/solver/)

  • MNA (Modified Nodal Analysis) con eliminación gaussiana de pivote parcial.
  • Ground implícito (no aparece en la matriz).
  • Ruido numérico controlado con G_min = 1e-12 S en diagonal.
  • Newton-Raphson iterativo para no-lineales (diodo / LED / BJT), con:
    • límite SPICE-style pnjlim para evitar exp() overflow en la primera iteración.
    • damping adaptativo (max 0.5 V por paso por nodo).
    • convergencia < 1 µV en 38 iteraciones típicas.
  • Transitorio con backward Euler (método de companion para el capacitor). La clave: no ejecutar solveDC() inicial antes del primer paso, porque con capacitores abiertos en DC se calcula un estado falso que luego queda como V_prev. En su lugar, sembrar prev desde Vinit.

Componentes implementados

Categoría Componentes
Pasivos lineales Resistor, VoltageSource, CurrentSource, Potentiometer, Switch
Pasivos dinámicos Capacitor (backward Euler)
No-lineales Diode (Shockley), LED (5 colores), BJT_NPN (Ebers-Moll simplificado)
Paramétricos NTCThermistor (modelo β)

Integración avr8js ≡ Velxio

Se copió fielmente el patrón de Velxio:

  • new CPU(program, 8192), new AVRIOPort(cpu, portXConfig), new AVRADC(cpu, adcConfig), new AVRTimer(cpu, timerXConfig).
  • Listeners por puerto → mapeo (portName, bit) → Arduino pin.
  • adc.channelValues[ch] = volts para inyectar voltajes en tiempo real.
  • cpu.data[ocrAddr] para leer duty de PWM.

El test avr_blink.test.js carga la misma .hex que usa Velxio y dispara el mismo LED simulado con corriente real calculada por el solver. Funciona.

E2E: potenciómetro → PWM → LED

Wiper V_A0 PWM duty I_LED Brillo
0 % 0.00 V 0.0 % 0.00 mA 0.000
25 % 1.25 V 24.7 % 0.00 mA 0.000
50 % 2.50 V 49.8 % 3.26 mA 0.163
75 % 3.75 V 74.9 % 8.77 mA 0.439
100 % 5.00 V 100.0 % 14.38 mA 0.719

El PWM duty cycle rastrea el wiper con < 1 % de error. La no-linealidad IV del LED emerge naturalmente (por debajo de su knee no conduce).

E2E: termistor NTC

Error máximo de recuperación de temperatura: 0.05 °C en el rango 0-50 °C (β=3950). Prácticamente el error de redondeo del ADC de 10 bits.

Lo que no se probó / gaps

  • Inductores no se implementaron (no había casos de uso en el scope).
  • MOSFET queda para una futura iteración (BJT alcanzó).
  • Análisis AC/frecuencia (Bode, resonancia) — fuera del scope.
  • Co-simulación ciclo-a-ciclo entre AVR y solver. Los tests resuelven el circuito una vez después de cada cambio de pin, no en cada ciclo. Para PWM se usa duty · V_supply como fuente equivalente, no la conmutación real → precisión aceptable para brillo medio, pero no modela ripple.

Problemas encontrados (y solución)

1. Diodo explotaba en la primera iteración de Newton

  • Síntoma: Vd saltaba a 5 V (apertura circuital cuando gd ≈ 0 en iter 0). Siguiente iter: exp(5/0.026) ≈ 1e84 → NaN.
  • Fix: pnjlim dentro del stamp del diodo. Primera iteración: Vd = min(Vd, Vcrit) donde Vcrit = nVt · ln(nVt / (√2 · Is)). Iteraciones siguientes: paso logarítmico si |ΔVd| > 2nVt.

2. Capacitor no cargaba

  • Síntoma: V(τ) era 5 V en vez de 3.16 V. El solveDC() inicial veía el cap abierto → Vout=5V → este estado se usaba como V_prev.
  • Fix: runTransient() ahora siembra state.prev desde capacitor.Vinit directamente, sin llamar a solveDC() inicial.

3. ADC daba valores 0-3 en vez de 0-255

  • Síntoma: leer ADCH con ADMUX = 0x40 retornaba solo los top-2 bits del resultado 10-bit (derecha-ajustado por defecto).
  • Fix: activar ADLAR=1ADMUX = 0x60. Ahora ADCH contiene los 8 MSB.

4. Offsets RJMP incorrectos en programa hand-assembled

  • Síntoma: loop saltaba a dirección equivocada tras añadir más instrucciones.
  • Fix: cálculo manual cuidadoso. Cada STS/LDS ocupa 2 words. RJMP k salta a PC+1+k.

Mejoras sugeridas para Velxio (main app)

  1. Adoptar este solver para la simulación eléctrica en lugar de ngspice-wasm. Ventajas:

    • 0 KB de dependencia WASM (vs 40 MB)
    • Inicialización instantánea
    • Fácil extender con componentes custom de wokwi-elements
  2. LED brightness realista. Actualmente Velxio muestra LEDs como on/off booleanos. Con el solver:

    • led.brightness(state) ∈ [0, 1] basado en corriente directa
    • El ajuste del brillo del componente wokwi-led puede hacerse con --wokwi-led-brightness CSS var o similar.
  3. Potenciómetro funcional. El componente ya existe visualmente pero el wiper no afecta a nada. Con este solver:

    • pot.setWiper(x) → recalcular → inyectar V en adc.channelValues[ch]
    • Habilitar drag interactivo del wiper.
  4. Warnings al usuario:

    • Si la corriente por un LED > 25 mA → overlay rojo "quemaría el LED".
    • Si un diodo queda en reverse breakdown → overlay amarillo.
    • Si no hay resistencia en serie con un LED → advertencia pre-simulación.
  5. Corridas en Web Worker si el circuito supera ~50 componentes, para no bloquear la UI. El solver como está es ~3 ms por solveDC() con 10 componentes en Node → debería ser < 30 ms con 100 en browser.

Performance (measurements en WSL2, Node 22)

Suite Tests Duración
passive.test.js 11 8 ms
transient_rc.test.js 3 22 ms
diodes.test.js 7 13 ms
avr_blink.test.js 2 2.4 s (loop CPU real)
e2e_pot_pwm_led.test.js 1 90 ms
e2e_thermistor.test.js 1 121 ms
Total 25 ~2.9 s

Prácticamente todo el tiempo se va en avr8js ejecutando los 32 M de ciclos del blink. El solver de circuitos es irrelevante en el presupuesto.

Próximos pasos recomendados

  1. Integrar un wrapper del solver en frontend/src/simulation/ (nuevo archivo CircuitSolver.ts).
  2. Conectar con useSimulatorStore.wires y components para auto-construir el Circuit objeto.
  3. Hook en PinManager.onPinChange() con debounce de 50 ms → circuit.solveDC().
  4. Reutilizar las fixtures de HEX que ya tiene frontend/src/__tests__/fixtures/.
  5. Añadir MOSFET, Zener, op-amp ideal (GMIN stamping) cuando haya demanda.