Velxio uses **real CPU emulation** on every supported board — never a simplified state machine. This document walks through each backend and how they share the same UI and bus abstractions.
| **QEMU upstream (ARM)** | ARM Cortex-A7/A53/A72/A76 | Raspberry Pi Zero / 1 / 2 / 3B / 4B / 5 | Backend (`qemu-system-arm` / `qemu-system-aarch64`) |
The browser backends (avr8js, rp2040js) execute in a Web Worker — no roundtrip to the server during simulation. The QEMU backends run as Python-managed subprocesses and stream events over WebSocket to the frontend.
The same `useSimulatorStore` + `PinManager` layer sits in front of every backend. Components don't know whether they're talking to avr8js, rp2040js, or QEMU — they just subscribe to `pin 13 went HIGH` events.
| Timing | WFI fast-forward — `delay()` advances simulation time instead of busy-waiting |
| Oscilloscope hook | GPIO transition timestamps at ~8 ns resolution (feeds the on-canvas oscilloscope) |
### Pico W extras
The Pico W ships a simulated **CYW43439** WiFi chip. See [Pico W WiFi Emulation](./PICO_W_WIFI_EMULATION.md) for the SPI handshake, RFC1483 framing, and SLIRP NAT bridge.
Compiled with the [earlephilhower arduino-pico](https://github.com/earlephilhower/arduino-pico) core. Serial redirect to UART0 is patched into `sketch.ino` only (other files left alone).
See [RP2040 Emulation](./RP2040_EMULATION.md) for the full peripheral model.
Backed by the **[lcgamboa QEMU fork](https://github.com/lcgamboa/qemu)** running as a `libqemu-xtensa.{dll,so,dylib}` shared library, embedded by the FastAPI backend. The frontend talks to it over a WebSocket bridge (`/ws/sim/{board_id}`).
Toolchain pinned to **arduino-esp32 2.0.17 (IDF 4.4.x)** — only version compatible with the lcgamboa WiFi shim. ESP-IDF projects (`idf.py`) supported via the [espidf_compiler](../backend/app/services/espidf_compiler.py) wrapper.
See [ESP32 Emulation](./ESP32_EMULATION.md) for setup and architectural details, [ESP32 WiFi/Bluetooth](./ESP32_WIFI_BLUETOOTH.md) for the radio stack.
Same QEMU backend pattern as Xtensa, different library (`libqemu-riscv32.{dll,so,dylib}`) and machine type (`esp32c3-picsimlab`).
| Feature | Notes |
|---------|-------|
| ISA | RV32IMC @ 160 MHz |
| GPIO | 0–21 via W1TS/W1TC MMIO registers |
| UART | UART0 to the Serial Monitor |
| ADC | 12-bit, ADC1 channels |
| WiFi / BLE | Same SLIRP path as Xtensa ESP32 |
A TypeScript ISA layer also lives at `frontend/src/simulation/RiscVCore.ts` + `Esp32C3Simulator.ts`. It exists for Vitest unit-test infrastructure — it does not implement the 150+ ROM functions ESP-IDF needs and is not the production emulation path. **All shipped ESP32-C3 simulation goes through QEMU.**
See [RISC-V Emulation](./RISCV_EMULATION.md) for details.
| Machine | `virt` for armhf, `raspi3b` for Pi 3B (full BCM2837), `virt` again for 4/5 |
| OS | Raspberry Pi OS (Trixie) — real Linux userland, runs Python 3 scripts directly |
| GPIO | 0–27 — output, input, event detection, PWM (binary state). Driven by an in-image **RPi.GPIO shim** that streams events over `ttyAMA1` |
| Serial | `ttyAMA0` for the user-facing Serial Monitor, `ttyAMA1` for the GPIO protocol |
| Storage | qcow2 overlay on top of the base SD image — base never mutates, session state is isolated |
| File system | UI-side **Virtual File System** (`useVfsStore`) — edit Python on the canvas, upload to the Pi at boot |
| Multi-board | UART bridge to AVR / RP2040 / ESP32 instances on the same canvas |
Boot kernels (`kernel8.img`), device trees (`bcm2710-rpi-3-b.dtb`), and base OS images live in `img/` and are bundled into the Docker image. To rebuild from scratch, see [BUILD-QEMU.md](./BUILD-QEMU.md) and [BOOT_IMAGES.md](./BOOT_IMAGES.md).
See [Raspberry Pi 3 Emulation](./RASPBERRYPI3_EMULATION.md) for the full bridge protocol.
---
## Languages
| Language | Boards | Toolchain |
|----------|--------|-----------|
| Arduino C++ | Every board | `arduino-cli` (AVR / RP2040 / ATtiny / ESP32) |
| ESP-IDF C | All ESP32 variants | `idf.py` (via [espidf_compiler.py](../backend/app/services/espidf_compiler.py)) |
| MicroPython | Pico, Pico W, all ESP32 / ESP32-S3 / ESP32-C3 | Pre-built MicroPython firmware booted under QEMU; user `.py` files mounted via VFS |
| Python 3 | All Raspberry Pi boards | Native — runs on the booted Pi OS rootfs with the `RPi.GPIO` shim pre-installed |
The `languageMode` field on each `BoardInstance` toggles between `arduino` and `micropython`. See [MicroPython Implementation](./MICROPYTHON_IMPLEMENTATION.md) for how `.py` files reach the running firmware on Pico and ESP32.
---
## Multi-board canvases
Multiple boards can sit on the same canvas, each with its own file group, its own running state, and its own Serial Monitor:
- A potentiometer wired to A0 of board A and A1 of board B is the **same** SPICE node — both ADCs read the same voltage.
- UART TX of board A wired to UART RX of board B forwards bytes through the backend message bus.
- Each board's pin state changes feed the same `PinManager`, so a NeoPixel ring wired to two boards reflects whichever wrote last.
Board instance IDs are deterministic — the first board of a kind uses the bare `boardKind` string (e.g. `arduino-uno`) as its ID; subsequent ones get a suffix. Example payloads in `frontend/src/data/examples.ts` (`boards: [...]` field) demonstrate the multi-board format.
---
## HEX / BIN / UF2 loading
- **AVR / RP2040** — Intel HEX produced by `arduino-cli`. Parser in `frontend/src/utils/hexParser.ts` reads `:`-prefixed lines, extracts addresses, returns a `Uint8Array`. AVRSimulator widens it to `Uint16Array` (16-bit words, little-endian).
- **ESP32 family** — ESP-IDF `.bin` files (bootloader + partition table + app). The backend writes them straight into the QEMU image at boot.
- **Pi family** — no firmware artifact; the user's Python files are uploaded to the running OS via the VFS at boot.
---
## Co-simulation with ngspice
When the **Electrical Sim** toggle is on, the simulator builds a SPICE netlist every frame:
1.**`NetlistBuilder`** (`frontend/src/simulation/spice/NetlistBuilder.ts`) runs Union-Find on `wires[]` to coalesce connected pins into electrical nodes.
2. Each component's `componentToSpice.ts` mapper emits SPICE cards (R, C, L, D, Q, M, V, I, op-amp subcircuits, …).
3. Each MCU digital output pin becomes a Thevenin source — voltage from the port driver, output impedance from the port driver model.
4.`useElectricalStore` calls into the lazy-loaded ngspice WASM (`SpiceEngine.lazy.ts`) and asks for a `.op` transient point.
5. Node voltages flow back: each MCU ADC reads its node's voltage on the next `analogRead()`, instruments (voltmeter, ammeter, oscilloscope) read their probe nodes directly.
See [Electrical Simulation User Guide](./wiki/electrical-simulation-user-guide.md) for the user-facing workflow and the [Circuit Emulation series](./wiki/circuit-emulation-overview.md) for engine internals.
Components are Web Components — either upstream [wokwi-elements](https://github.com/wokwi/wokwi-elements) (Lit) or Velxio-native (vanilla `HTMLElement` + Shadow DOM).
Wire positions update automatically when components move (`updateWirePositions(componentId)` after drag, with retries at 100/300/500 ms for board-internal pins that mount asynchronously).