feat: add RISC-V emulation documentation and update ESP32 emulation section

pull/47/head
David Montero Crespo 2026-03-15 23:15:31 -03:00
parent 74e7ec58c1
commit 5439dcf65f
3 changed files with 640 additions and 2 deletions

View File

@ -1,8 +1,11 @@
# ESP32 Emulation — Documentación Técnica
# ESP32 Emulation (Xtensa) — Documentación Técnica
> Estado: **Funcional** · Backend completo · Frontend completo
> Motor: **lcgamboa/qemu-8.1.3** · Plataforma: **arduino-esp32 2.0.17 (IDF 4.4.x)**
> Disponible en: **Windows** (`.dll`) · **Linux / Docker** (`.so`, incluido en imagen oficial)
> Aplica a: **ESP32, ESP32-S3** (arquitectura Xtensa LX6/LX7)
> **Nota sobre ESP32-C3:** Los boards ESP32-C3, XIAO ESP32-C3 y ESP32-C3 SuperMini usan la arquitectura **RISC-V RV32IMC** y tienen su propio emulador en el navegador. Ver → [RISCV_EMULATION.md](./RISCV_EMULATION.md)
---

450
docs/RISCV_EMULATION.md Normal file
View File

@ -0,0 +1,450 @@
# RISC-V Emulation (ESP32-C3 / XIAO-C3 / C3 SuperMini)
> Estado: **Funcional** · Emulación en el navegador · Sin dependencias de backend
> Motor: **RiscVCore (RV32IMC)** — implementado en TypeScript
> Plataforma: **ESP32-C3 @ 160 MHz** — arquitectura RISC-V de 32 bits
---
## Índice
1. [Visión general](#1-visión-general)
2. [Boards soportadas](#2-boards-soportadas)
3. [Arquitectura del emulador](#3-arquitectura-del-emulador)
4. [Memoria y periféricos emulados](#4-memoria-y-periféricos-emulados)
5. [Flujo completo: compilar y ejecutar](#5-flujo-completo-compilar-y-ejecutar)
6. [Formato de imagen ESP32](#6-formato-de-imagen-esp32)
7. [ISA soportada — RV32IMC](#7-isa-soportada--rv32imc)
8. [GPIO](#8-gpio)
9. [UART0 — Serial Monitor](#9-uart0--serial-monitor)
10. [Limitaciones](#10-limitaciones)
11. [Tests](#11-tests)
12. [Diferencias vs emulación Xtensa (ESP32 / ESP32-S3)](#12-diferencias-vs-emulación-xtensa-esp32--esp32-s3)
13. [Archivos clave](#13-archivos-clave)
---
## 1. Visión general
Los boards basados en **ESP32-C3** usan el procesador **ESP32-C3** de Espressif, que implementa la arquitectura **RISC-V RV32IMC** (32 bits, Multiply, Compressed instructions). A diferencia del ESP32 y ESP32-S3 (Xtensa LX6/LX7), el C3 **no requiere QEMU ni backend** para emularse.
### Comparación de motores de emulación
| Board | CPU | Motor |
|-------|-----|-------|
| ESP32, ESP32-S3 | Xtensa LX6/LX7 | QEMU lcgamboa (backend WebSocket) |
| **ESP32-C3, XIAO-C3, C3 SuperMini** | **RV32IMC @ 160 MHz** | **RiscVCore.ts (navegador, sin backend)** |
| Arduino Uno/Nano/Mega | AVR ATmega | avr8js (navegador) |
| Raspberry Pi Pico | RP2040 | rp2040js (navegador) |
### Ventajas del emulador JS
- **Sin dependencias de red** — funciona offline, sin conexión WebSocket al backend
- **Arranque instantáneo** — no hay proceso QEMU que arrancar (0 ms de latencia)
- **Testable con Vitest** — el mismo código TypeScript que se ejecuta en producción se puede probar en CI
- **Multiplataforma** — funciona igual en Windows, macOS, Linux y Docker
---
## 2. Boards soportadas
| Board | FQBN arduino-cli | LED built-in |
|-------|-----------------|--------------|
| ESP32-C3 DevKit | `esp32:esp32:esp32c3` | GPIO 8 |
| Seeed XIAO ESP32-C3 | `esp32:esp32:XIAO_ESP32C3` | GPIO 10 (active-low) |
| ESP32-C3 SuperMini | `esp32:esp32:esp32c3` | GPIO 8 |
---
## 3. Arquitectura del emulador
```
Arduino Sketch (.ino)
▼ arduino-cli (backend)
sketch.ino.bin ← ESP32 image format (segmentos IROM/DRAM/IRAM)
▼ base64 → frontend
compileBoardProgram(boardId, base64)
▼ Esp32C3Simulator.loadFlashImage(base64)
parseMergedFlashImage() ← lee segmentos de la imagen 4MB
├── IROM segment → flash buffer (0x42000000)
├── DROM segment → flash buffer (0x3C000000, alias)
├── DRAM segment → dram buffer (0x3FC80000)
└── IRAM segment → iram buffer (0x4037C000)
▼ core.reset(entryPoint)
RiscVCore.step() ← requestAnimationFrame @ 60 FPS
│ 2.666.667 ciclos/frame (160 MHz ÷ 60)
├── MMIO GPIO_W1TS/W1TC → onPinChangeWithTime → componentes visuales
└── MMIO UART0 FIFO → onSerialData → Serial Monitor
```
### Clases principales
| Clase | Archivo | Responsabilidad |
|-------|---------|----------------|
| `RiscVCore` | `simulation/RiscVCore.ts` | Decodificador/ejecutor RV32IMC, MMIO genérico |
| `Esp32C3Simulator` | `simulation/Esp32C3Simulator.ts` | Mapa de memoria ESP32-C3, GPIO, UART0, ciclo RAF |
| `parseMergedFlashImage` | `utils/esp32ImageParser.ts` | Parseo formato imagen ESP32 (segmentos, entry point) |
---
## 4. Memoria y periféricos emulados
### Mapa de memoria
| Región | Dirección base | Tamaño | Descripción |
|--------|---------------|--------|-------------|
| Flash IROM | `0x42000000` | 4 MB | Código ejecutable (buffer principal del core) |
| Flash DROM | `0x3C000000` | 4 MB | Datos de solo lectura (alias del mismo buffer) |
| DRAM | `0x3FC80000` | 384 KB | RAM de datos (stack, variables globales) |
| IRAM | `0x4037C000` | 384 KB | RAM de instrucciones (ISR, código time-critical) |
| UART0 | `0x60000000` | 1 KB | Serial port 0 |
| GPIO | `0x60004000` | 512 B | Registros GPIO |
### GPIO — registros implementados
| Registro | Offset | Función |
|----------|--------|---------|
| `GPIO_OUT_REG` | `+0x04` | Leer/escribir estado de salida de todos los pines |
| `GPIO_OUT_W1TS_REG` | `+0x08` | **Set bits** — poner pines a HIGH (write-only) |
| `GPIO_OUT_W1TC_REG` | `+0x0C` | **Clear bits** — poner pines a LOW (write-only) |
| `GPIO_IN_REG` | `+0x3C` | Leer estado de entrada de pines |
| `GPIO_ENABLE_REG` | `+0x20` | Dirección de pines (siempre devuelve `0xFF`) |
Cubre **GPIO 021** (todos los disponibles en ESP32-C3).
### UART0 — registros implementados
| Registro | Offset | Función |
|----------|--------|---------|
| `UART_FIFO_REG` | `+0x00` | Escribir byte TX / leer byte RX |
| `UART_STATUS_REG` | `+0x1C` | Estado FIFO (siempre devuelve `0` = listo) |
Lectura de byte de RX disponible para simular input desde Serial Monitor.
### Periféricos NO emulados (retornan 0 en lectura)
- Interrupt Matrix (`0x600C2000`)
- System / Clock (`0x600C0000`, `0x60008000`)
- Cache controller (`0x600C4000`)
- Timer Group 0/1
- SPI flash controller
- BLE / WiFi MAC
- ADC / DAC
> Estos periféricos retornan `0` por defecto. El código que los requiere puede no funcionar correctamente (ver [Limitaciones](#10-limitaciones)).
---
## 5. Flujo completo: compilar y ejecutar
### 5.1 Compilar el sketch
```bash
# arduino-cli compila para ESP32-C3:
arduino-cli compile \
--fqbn esp32:esp32:esp32c3 \
--output-dir build/ \
mi_sketch/
# El backend crea automáticamente la imagen fusionada (merged):
# build/mi_sketch.ino.bootloader.bin → 0x01000
# build/mi_sketch.ino.partitions.bin → 0x08000
# build/mi_sketch.ino.bin → 0x10000 (app)
# → merged: sketch.ino.merged.bin (4 MB)
```
El backend de Velxio produce esta imagen automáticamente y la envía al frontend como base64.
### 5.2 Sketch mínimo para ESP32-C3
```cpp
// LED en GPIO 8 (ESP32-C3 DevKit)
#define LED_PIN 8
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200);
Serial.println("ESP32-C3 iniciado");
}
void loop() {
digitalWrite(LED_PIN, HIGH);
Serial.println("LED ON");
delay(500);
digitalWrite(LED_PIN, LOW);
Serial.println("LED OFF");
delay(500);
}
```
### 5.3 Sketch bare-metal (para tests de emulación directos)
Para verificar la emulación sin el framework Arduino, se puede compilar con el toolchain RISC-V directamente:
```c
/* blink.c — bare-metal, sin ESP-IDF */
#define GPIO_W1TS (*(volatile unsigned int *)0x60004008u)
#define GPIO_W1TC (*(volatile unsigned int *)0x6000400Cu)
#define LED_BIT (1u << 8)
static void delay(int n) { for (volatile int i = 0; i < n; i++); }
void _start(void) {
while (1) {
GPIO_W1TS = LED_BIT; /* LED ON */
delay(500);
GPIO_W1TC = LED_BIT; /* LED OFF */
delay(500);
}
}
```
Compilar con el toolchain bundled en arduino-cli:
```bash
# Toolchain instalado con: arduino-cli core install esp32:esp32
TOOLCHAIN="$LOCALAPPDATA/Arduino15/packages/esp32/tools/riscv32-esp-elf-gcc/esp-2021r2-patch5-8.4.0/bin"
"$TOOLCHAIN/riscv32-esp-elf-gcc" \
-march=rv32imc -mabi=ilp32 -Os -nostdlib -nostartfiles \
-T link.ld -o blink.elf blink.c
"$TOOLCHAIN/riscv32-esp-elf-objcopy" -O binary blink.elf blink.bin
```
Ver script completo: `frontend/src/__tests__/fixtures/esp32c3-blink/build.sh`
---
## 6. Formato de imagen ESP32
El backend produce una imagen fusionada de **4 MB**:
```
Offset 0x00000: 0xFF (vacío)
Offset 0x01000: bootloader (imagen ESP32 format, magic 0xE9)
Offset 0x08000: partition table
Offset 0x10000: app binary (imagen ESP32 format, magic 0xE9) ← parseamos aquí
```
### Cabecera de imagen ESP32 (24 bytes)
```
+0x00 magic (0xE9)
+0x01 segment_count
+0x02 spi_mode
+0x03 spi_speed_size
+0x04 entry_addr ← uint32 LE — PC de entrada del firmware
+0x08 extended fields (16 bytes)
```
### Cabecera de segmento (8 bytes)
```
+0x00 load_addr ← dirección virtual destino (e.g. 0x42000000)
+0x04 data_len
+0x08 data[data_len]
```
El parser `parseMergedFlashImage()` en `utils/esp32ImageParser.ts` extrae todos los segmentos y el entry point, que se usa para el reset del core (`core.reset(entryPoint)`).
---
## 7. ISA soportada — RV32IMC
`RiscVCore.ts` implementa las tres extensiones necesarias para ejecutar código compilado para ESP32-C3:
### RV32I — Base integer (40 instrucciones)
Incluye: LUI, AUIPC, JAL, JALR, BEQ/BNE/BLT/BGE/BLTU/BGEU, LB/LH/LW/LBU/LHU, SB/SH/SW, ADDI/SLTI/SLTIU/XORI/ORI/ANDI/SLLI/SRLI/SRAI, ADD/SUB/SLL/SLT/SLTU/XOR/SRL/SRA/OR/AND, FENCE, ECALL/EBREAK, CSR (lectura devuelve 0)
### RV32M — Multiplicación y división (8 instrucciones)
| Instrucción | Operación |
|-------------|-----------|
| `MUL` | Producto entero (32 bits bajos) |
| `MULH` | Producto con signo (32 bits altos) |
| `MULHSU` | Producto mixto firmado×sin firma (altos) |
| `MULHU` | Producto sin firma (32 bits altos) |
| `DIV` | División entera con signo |
| `DIVU` | División entera sin firma |
| `REM` | Resto con signo |
| `REMU` | Resto sin firma |
### RV32C — Instrucciones comprimidas (16 bits)
Todas las instrucciones de 16 bits del estándar C son soportadas. Se detectan por `(halfword & 3) !== 3` y se descomprimen a su equivalente RV32I antes de ejecutar. Esto es crítico: el compilador GCC para ESP32-C3 genera intensamente instrucciones C (`c.addi`, `c.sw`, `c.lw`, `c.j`, `c.beqz`, `c.bnez`, etc.) que representan ~30-40% de todas las instrucciones en el binario final.
---
## 8. GPIO
El manejo de GPIO sigue el modelo de registros W1TS/W1TC del ESP32-C3:
```typescript
// Sketch Arduino:
digitalWrite(8, HIGH); // → internamente escribe 1<<8 a GPIO_OUT_W1TS_REG
// En el simulador:
// SW x10, 0(x12) donde x10=256 (1<<8), x12=0x60004008 (W1TS)
// → escribe 4 bytes a 0x60004008..0x6000400B
// → byteIdx=1 (offset 0x09): val=0x01, shift=8 → gpioOut |= 0x100
// → changed = prev ^ gpioOut ≠ 0 → dispara onPinChangeWithTime(8, true, timeMs)
```
El callback `onPinChangeWithTime(pin, state, timeMs)` es el punto de integración con los componentes visuales. `timeMs` es el tiempo simulado en milisegundos (calculado como `core.cycles / CPU_HZ * 1000`).
---
## 9. UART0 — Serial Monitor
Cualquier byte escrito a `UART0_FIFO_REG` (0x60000000) llama al callback `onSerialData(char)`:
```cpp
// Sketch Arduino:
Serial.println("Hola!");
// → Arduino framework escribe los bytes de "Hola!\r\n" a UART0_FIFO_REG
// → simulador llama onSerialData("H"), onSerialData("o"), ...
// → Serial Monitor muestra "Hola!"
```
Para enviar datos al sketch desde el Serial Monitor:
```typescript
sim.serialWrite("COMANDO\n");
// → bytes se añaden a rxFifo
// → lectura de UART0_FIFO_REG dequeue un byte del rxFifo
```
---
## 10. Limitaciones
### Framework ESP-IDF / Arduino
El framework Arduino para ESP32-C3 (basado en ESP-IDF 4.4.x) tiene una secuencia de inicialización compleja que accede a periféricos no emulados:
| Periférico | Por qué lo accede ESP-IDF | Efecto en emulador |
|------------|--------------------------|-------------------|
| Cache controller | Configura MMU para mapeo flash/DRAM | Lee 0, puede que no loop |
| Interrupt Matrix | Registra vectores ISR | Sin efecto (silenciado) |
| System registers | Configura PLLs y clocks | Lee 0 (asume velocidad por defecto) |
| FreeRTOS tick timer | Timer 0 → interrupción periódica | Sin interrupción = tareas no se planifican |
Como resultado, un sketch Arduino compilado con el framework completo puede ejecutarse parcialmente — el código anterior a la inicialización de FreeRTOS puede funcionar, pero `setup()` y `loop()` dependen de que FreeRTOS esté corriendo.
**Escenarios que SÍ funcionan:**
- Código bare-metal (sin framework, acceso directo a GPIO MMIO)
- Fragmentos de código que no usen FreeRTOS (`delay()`, `millis()`, `digitalWrite()` requieren FreeRTOS)
- Programas de prueba de ISA (operaciones aritméticas, branches, loads/stores a DRAM)
**Roadmap para soporte completo:**
1. Stub del cache controller (devolver valores que indiquen "cache ya configurada")
2. Stub del interrupt matrix (aceptar writes, ignorar)
3. Timer peripheral básico (generar tick FreeRTOS periódicamente)
4. Una vez activo FreeRTOS: sketches Arduino normales deberían funcionar
### Otras limitaciones
| Limitación | Detalle |
|------------|---------|
| Sin WiFi | El ESP32-C3 tiene radio BLE/WiFi; no emulada |
| Sin ADC | GPIO 0-5 como ADC no implementado |
| Sin SPI/I2C hardware | Los periféricos hardware SPI/I2C retornan 0 |
| Sin interrupciones | `attachInterrupt()` no funciona |
| Sin RTC | `esp_sleep_*`, `rtc_*` no implementados |
| Sin NVS/Flash writes | `Preferences`, `SPIFFS` no implementados |
---
## 11. Tests
Los tests de la emulación RISC-V están en `frontend/src/__tests__/`:
```bash
cd frontend
npm test -- esp32c3
```
### `esp32c3-simulation.test.ts` — 30 tests (ISA unit tests)
Verifica directamente el decodificador de instrucciones de `RiscVCore`:
| Grupo | Tests | Qué verifica |
|-------|-------|--------------|
| RV32M | 8 | MUL, MULH, MULHSU, MULHU, DIV, DIVU, REM, REMU |
| RV32C | 7 | C.ADDI, C.LI, C.LWSP, C.SWSP, C.MV, C.ADD, C.J, C.BEQZ |
| UART | 3 | Escritura a FIFO → onSerialData, lectura de RX, múltiples bytes |
| GPIO | 8 | W1TS set bit, W1TC clear bit, toggle, timestamp, múltiples pines |
| Lifecycle | 4 | reset(), start/stop, loadHex básico |
### `esp32c3-blink.test.ts` — 8 tests (integración end-to-end)
Compila `blink.c` con `riscv32-esp-elf-gcc` (el toolchain de arduino-cli) y verifica la ejecución en el simulador:
| Test | Qué verifica |
|------|-------------|
| `build.sh produces blink.bin` | El toolchain compila correctamente |
| `binary starts with valid RV32 instruction` | El entry point es código RISC-V válido |
| `loadBin() resets PC to 0x42000000` | Carga correcta en flash |
| `GPIO 8 goes HIGH after first SW` | Primer toggle correcto |
| `GPIO 8 toggles ON and OFF` | 7 toggles en 2000 pasos (4 ON, 3 OFF) |
| `PinManager.setPinState called` | Integración con el sistema de componentes |
| `timestamps increase monotonically` | El tiempo simulado es consistente |
| `reset() clears GPIO state` | Reset funcional |
**Resultado esperado:**
```
✓ esp32c3-simulation.test.ts (30 tests) ~500ms
✓ esp32c3-blink.test.ts (8 tests) ~300ms
```
### Binario de prueba bare-metal
```
frontend/src/__tests__/fixtures/esp32c3-blink/
├── blink.c ← código fuente bare-metal
├── link.ld ← linker script (IROM @ 0x42000000, DRAM @ 0x3FC80000)
├── build.sh ← script de compilación (usa toolchain de arduino-cli)
├── blink.elf ← (generado) ELF con debug info
├── blink.bin ← (generado) binario raw de 58 bytes
└── blink.dis ← (generado) desensamblado para inspección
```
---
## 12. Diferencias vs emulación Xtensa (ESP32 / ESP32-S3)
| Aspecto | ESP32-C3 (RISC-V) | ESP32 / ESP32-S3 (Xtensa) |
|---------|-------------------|--------------------------|
| Motor | `Esp32C3Simulator` (TypeScript, navegador) | `Esp32Bridge` + backend QEMU |
| Dependencia backend | **No** — 100% en el navegador | Sí — WebSocket a proceso QEMU |
| Arranque | Instantáneo | ~1-2 segundos |
| GPIO | Via MMIO W1TS/W1TC | Via QEMU callbacks → WebSocket |
| WiFi | No emulada | Emulada (SSIDs hardcoded) |
| I2C/SPI hardware | No emulado | Emulado (callbacks síncronos) |
| LEDC/PWM | No emulado | Emulado (poll periódico) |
| NeoPixel/RMT | No emulado | Emulado (decodificador RMT) |
| Arduino framework | Parcial (FreeRTOS no activo) | Completo |
| Tests CI | Sí (Vitest) | No (requiere lib nativa) |
---
## 13. Archivos clave
| Archivo | Descripción |
|---------|-------------|
| `frontend/src/simulation/RiscVCore.ts` | Núcleo del emulador RV32IMC (I + M + C extensions) |
| `frontend/src/simulation/Esp32C3Simulator.ts` | Mapa de memoria ESP32-C3, GPIO, UART0, ciclo RAF |
| `frontend/src/utils/esp32ImageParser.ts` | Parser del formato imagen ESP32 (merged flash → segmentos) |
| `frontend/src/store/useSimulatorStore.ts` | `ESP32_RISCV_KINDS`, `createSimulator()`, `compileBoardProgram()` |
| `frontend/src/__tests__/esp32c3-simulation.test.ts` | Unit tests ISA (30 tests) |
| `frontend/src/__tests__/esp32c3-blink.test.ts` | Integration test end-to-end (8 tests) |
| `frontend/src/__tests__/fixtures/esp32c3-blink/` | Firmware bare-metal de prueba + toolchain script |

View File

@ -27,6 +27,8 @@ type SectionId =
| 'intro'
| 'getting-started'
| 'emulator'
| 'riscv-emulation'
| 'esp32-emulation'
| 'components'
| 'roadmap'
| 'architecture'
@ -38,6 +40,8 @@ const VALID_SECTIONS: SectionId[] = [
'intro',
'getting-started',
'emulator',
'riscv-emulation',
'esp32-emulation',
'components',
'roadmap',
'architecture',
@ -55,6 +59,8 @@ const NAV_ITEMS: NavItem[] = [
{ id: 'intro', label: 'Introduction' },
{ id: 'getting-started', label: 'Getting Started' },
{ id: 'emulator', label: 'Emulator Architecture' },
{ id: 'riscv-emulation', label: 'RISC-V Emulation (ESP32-C3)' },
{ id: 'esp32-emulation', label: 'ESP32 Emulation (Xtensa)' },
{ id: 'components', label: 'Components Reference' },
{ id: 'architecture', label: 'Project Architecture' },
{ id: 'wokwi-libs', label: 'Wokwi Libraries' },
@ -76,7 +82,15 @@ const SECTION_META: Record<SectionId, SectionMeta> = {
},
'emulator': {
title: 'Emulator Architecture — Velxio Documentation',
description: 'How Velxio emulates AVR8 (ATmega328p) and RP2040 CPUs. Covers the execution loop, peripherals (GPIO, Timers, USART, ADC, SPI, I2C), and pin mapping.',
description: 'How Velxio emulates AVR8 (ATmega328p), RP2040, and RISC-V (ESP32-C3) CPUs. Covers execution loops, peripherals, and pin mapping for all supported boards.',
},
'riscv-emulation': {
title: 'RISC-V Emulation (ESP32-C3) — Velxio Documentation',
description: 'Browser-side RV32IMC emulator for ESP32-C3, XIAO ESP32-C3, and C3 SuperMini. Covers memory map, GPIO, UART0, the ESP32 image parser, RV32IMC ISA, and test suite.',
},
'esp32-emulation': {
title: 'ESP32 Emulation (Xtensa) — Velxio Documentation',
description: 'QEMU-based emulation for ESP32 and ESP32-S3 (Xtensa LX6/LX7). Covers the lcgamboa fork, libqemu-xtensa, GPIO, WiFi, I2C, SPI, RMT/NeoPixel, and LEDC/PWM.',
},
'components': {
title: 'Components Reference — Velxio Documentation',
@ -134,6 +148,8 @@ const IntroSection: React.FC = () => (
<tr><td>Arduino Nano</td><td>ATmega328p @ 16 MHz</td><td>avr8js</td></tr>
<tr><td>Arduino Mega</td><td>ATmega2560 @ 16 MHz</td><td>avr8js</td></tr>
<tr><td>Raspberry Pi Pico</td><td>RP2040 @ 133 MHz</td><td>rp2040js</td></tr>
<tr><td>ESP32-C3 / XIAO C3 / C3 SuperMini</td><td>RV32IMC @ 160 MHz</td><td>Esp32C3Simulator (browser)</td></tr>
<tr><td>ESP32 / ESP32-S3</td><td>Xtensa LX6/LX7 @ 240 MHz</td><td>QEMU (lcgamboa)</td></tr>
</tbody>
</table>
@ -1019,10 +1035,179 @@ const SetupSection: React.FC = () => (
</div>
);
const RiscVEmulationSection: React.FC = () => (
<div className="docs-section">
<span className="docs-label">// risc-v</span>
<h1>RISC-V Emulation (ESP32-C3)</h1>
<p>
ESP32-C3, XIAO ESP32-C3, and C3 SuperMini boards use a <strong>RISC-V RV32IMC</strong> core running at
160 MHz. Velxio emulates them entirely in the browser no backend, no QEMU, no WebAssembly pipeline.
The emulator is written in pure TypeScript and runs at real-time speeds.
</p>
<h2>Supported Boards</h2>
<table>
<thead>
<tr><th>Board</th><th>CPU</th><th>Flash</th><th>RAM</th></tr>
</thead>
<tbody>
<tr><td>ESP32-C3</td><td>RV32IMC @ 160 MHz</td><td>4 MB</td><td>384 KB DRAM</td></tr>
<tr><td>XIAO ESP32-C3</td><td>RV32IMC @ 160 MHz</td><td>4 MB</td><td>384 KB DRAM</td></tr>
<tr><td>C3 SuperMini</td><td>RV32IMC @ 160 MHz</td><td>4 MB</td><td>384 KB DRAM</td></tr>
</tbody>
</table>
<h2>Memory Map</h2>
<table>
<thead>
<tr><th>Region</th><th>Base Address</th><th>Size</th><th>Description</th></tr>
</thead>
<tbody>
<tr><td>IROM (Flash)</td><td><code>0x42000000</code></td><td>4 MB</td><td>Code stored in flash</td></tr>
<tr><td>DROM (Flash R/O)</td><td><code>0x3C000000</code></td><td>4 MB</td><td>Read-only data in flash</td></tr>
<tr><td>DRAM</td><td><code>0x3FC80000</code></td><td>384 KB</td><td>Data RAM (stack + heap)</td></tr>
<tr><td>IRAM</td><td><code>0x4037C000</code></td><td>384 KB</td><td>Instruction RAM (copied from flash)</td></tr>
<tr><td>UART0</td><td><code>0x60000000</code></td><td>1 KB</td><td>Serial port 0 (GPIO 20/21)</td></tr>
<tr><td>GPIO</td><td><code>0x60004000</code></td><td>512 B</td><td>GPIO output / input / enable</td></tr>
</tbody>
</table>
<h2>ISA Support</h2>
<ul>
<li><strong>RV32I</strong> Full base integer instruction set (ALU, load/store, branches, JAL/JALR)</li>
<li><strong>RV32M</strong> Multiply/divide: MUL, MULH, MULHSU, MULHU, DIV, DIVU, REM, REMU</li>
<li><strong>RV32C</strong> 16-bit compressed instructions: C.LI, C.ADDI, C.LUI, C.J, C.JAL, C.BEQZ,
C.BNEZ, C.MV, C.ADD, C.JR, C.JALR, C.LW, C.SW, C.LWSP, C.SWSP, C.SLLI, C.ADDI4SPN</li>
</ul>
<h2>Compilation Flow</h2>
<p>When you click <strong>Compile + Run</strong> for an ESP32-C3 board:</p>
<ol>
<li>The backend compiles your sketch with <code>arduino-cli</code> using the <code>esp32:esp32</code> core.</li>
<li>The resulting binary is a <strong>merged 4 MB flash image</strong>: bootloader at <code>0x1000</code>,
partition table at <code>0x8000</code>, application at <code>0x10000</code>.</li>
<li>The frontend's <code>esp32ImageParser.ts</code> finds the app at offset <code>0x10000</code>,
reads the 24-byte ESP32 image header (magic <code>0xE9</code>), and extracts all segments
(load address + data).</li>
<li>Each segment is written into the correct memory region (IROM, DROM, DRAM, or IRAM) of
the <code>Esp32C3Simulator</code>.</li>
<li>The CPU starts executing from the entry point specified in the image header.</li>
</ol>
<h2>GPIO Registers</h2>
<table>
<thead>
<tr><th>Register</th><th>Offset</th><th>Description</th></tr>
</thead>
<tbody>
<tr><td><code>GPIO_OUT_REG</code></td><td><code>+0x04</code></td><td>Current output value</td></tr>
<tr><td><code>GPIO_OUT_W1TS</code></td><td><code>+0x08</code></td><td>Set bits (write 1 to set)</td></tr>
<tr><td><code>GPIO_OUT_W1TC</code></td><td><code>+0x0C</code></td><td>Clear bits (write 1 to clear)</td></tr>
<tr><td><code>GPIO_ENABLE_REG</code></td><td><code>+0x20</code></td><td>Output enable</td></tr>
<tr><td><code>GPIO_IN_REG</code></td><td><code>+0x3C</code></td><td>Input pin states</td></tr>
</tbody>
</table>
<h2>UART0</h2>
<p>
Writing a byte to <code>0x60000000</code> (UART0 FIFO) triggers the <code>onSerialData</code> callback,
which streams characters to the Serial Monitor. Reading from the same address pops from the receive
FIFO (used by <code>Serial.read()</code>). The UART status register always returns 0 (TX ready).
</p>
<h2>Key Source Files</h2>
<table>
<thead>
<tr><th>File</th><th>Role</th></tr>
</thead>
<tbody>
<tr><td><code>simulation/RiscVCore.ts</code></td><td>RV32IMC interpreter (step, MMIO hooks)</td></tr>
<tr><td><code>simulation/Esp32C3Simulator.ts</code></td><td>ESP32-C3 peripherals, memory map, lifecycle</td></tr>
<tr><td><code>utils/esp32ImageParser.ts</code></td><td>Parses merged flash image, extracts segments</td></tr>
</tbody>
</table>
<div className="docs-callout">
<strong>Full details:</strong>{' '}
<a href={`${GITHUB_URL}/blob/master/docs/RISCV_EMULATION.md`} target="_blank" rel="noopener noreferrer">
docs/RISCV_EMULATION.md
</a>{' '}
in the repository.
</div>
</div>
);
const Esp32EmulationSection: React.FC = () => (
<div className="docs-section">
<span className="docs-label">// xtensa</span>
<h1>ESP32 Emulation (Xtensa)</h1>
<p>
ESP32 and ESP32-S3 boards use an <strong>Xtensa LX6 / LX7</strong> architecture. Because no
production-quality Xtensa emulator is available as pure JavaScript, Velxio uses a
<strong> QEMU-based backend</strong> for these boards the lcgamboa fork with
libqemu-xtensa, compiled to a native binary and served by the FastAPI backend.
</p>
<div className="docs-callout">
<strong>Note:</strong> This section applies only to <strong>ESP32</strong> and <strong>ESP32-S3</strong> (Xtensa).
For ESP32-C3, XIAO ESP32-C3, and C3 SuperMini (RISC-V), see{' '}
<strong>RISC-V Emulation (ESP32-C3)</strong> in the sidebar those boards run entirely in the browser.
</div>
<h2>How It Works</h2>
<ol>
<li>Arduino sketch is compiled by <code>arduino-cli</code> to an ESP32 <code>.bin</code> flash image.</li>
<li>Frontend sends the binary to the backend via WebSocket (<code>Esp32Bridge</code>).</li>
<li>Backend spawns a QEMU process with the lcgamboa Xtensa plugin, loads the image.</li>
<li>GPIO and UART events are forwarded over the WebSocket back to the browser.</li>
<li>Frontend updates component states (LEDs, display, etc.) in real time.</li>
</ol>
<h2>Supported Boards</h2>
<table>
<thead>
<tr><th>Board</th><th>CPU</th><th>Emulation</th></tr>
</thead>
<tbody>
<tr><td>ESP32</td><td>Xtensa LX6 dual-core @ 240 MHz</td><td>QEMU (lcgamboa)</td></tr>
<tr><td>ESP32-S3</td><td>Xtensa LX7 dual-core @ 240 MHz</td><td>QEMU (lcgamboa)</td></tr>
</tbody>
</table>
<h2>Peripheral Support</h2>
<ul>
<li><strong>GPIO</strong> digital output / input, LED control</li>
<li><strong>UART</strong> Serial Monitor via <code>Serial.print()</code></li>
<li><strong>I2C / SPI</strong> peripheral communication</li>
<li><strong>RMT / NeoPixel</strong> addressable LED strips</li>
<li><strong>LEDC / PWM</strong> hardware PWM channels</li>
<li><strong>WiFi</strong> partial (connection events forwarded)</li>
</ul>
<h2>Requirements</h2>
<p>
QEMU-based emulation requires the Velxio backend to be running. This means it works with
the <strong>hosted version</strong> at{' '}
<a href="https://velxio.dev" target="_blank" rel="noopener noreferrer">velxio.dev</a>{' '}
and with <strong>Docker self-hosting</strong>, but not in a pure static frontend deployment.
</p>
<div className="docs-callout">
<strong>Full details:</strong>{' '}
<a href={`${GITHUB_URL}/blob/master/docs/ESP32_EMULATION.md`} target="_blank" rel="noopener noreferrer">
docs/ESP32_EMULATION.md
</a>{' '}
in the repository.
</div>
</div>
);
const SECTION_MAP: Record<SectionId, React.FC> = {
intro: IntroSection,
'getting-started': GettingStartedSection,
emulator: EmulatorSection,
'riscv-emulation': RiscVEmulationSection,
'esp32-emulation': Esp32EmulationSection,
components: ComponentsSection,
roadmap: RoadmapSection,
architecture: ArchitectureSection,