feat: update RISCV_EMULATION documentation with English translations and improvements
parent
8e38bd2fa1
commit
a40471fa38
|
|
@ -1,74 +1,74 @@
|
||||||
# RISC-V Emulation (ESP32-C3 / XIAO-C3 / C3 SuperMini)
|
# RISC-V Emulation (ESP32-C3 / XIAO-C3 / C3 SuperMini)
|
||||||
|
|
||||||
> Estado: **Funcional** · Emulación en el navegador · Sin dependencias de backend
|
> Status: **Functional** · In-browser emulation · No backend dependencies
|
||||||
> Motor: **RiscVCore (RV32IMC)** — implementado en TypeScript
|
> Engine: **RiscVCore (RV32IMC)** — implemented in TypeScript
|
||||||
> Plataforma: **ESP32-C3 @ 160 MHz** — arquitectura RISC-V de 32 bits
|
> Platform: **ESP32-C3 @ 160 MHz** — 32-bit RISC-V architecture
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Índice
|
## Table of Contents
|
||||||
|
|
||||||
1. [Visión general](#1-visión-general)
|
1. [Overview](#1-overview)
|
||||||
2. [Boards soportadas](#2-boards-soportadas)
|
2. [Supported Boards](#2-supported-boards)
|
||||||
3. [Arquitectura del emulador](#3-arquitectura-del-emulador)
|
3. [Emulator Architecture](#3-emulator-architecture)
|
||||||
4. [Memoria y periféricos emulados](#4-memoria-y-periféricos-emulados)
|
4. [Emulated Memory and Peripherals](#4-emulated-memory-and-peripherals)
|
||||||
5. [Flujo completo: compilar y ejecutar](#5-flujo-completo-compilar-y-ejecutar)
|
5. [Full Flow: Compile and Run](#5-full-flow-compile-and-run)
|
||||||
6. [Formato de imagen ESP32](#6-formato-de-imagen-esp32)
|
6. [ESP32 Image Format](#6-esp32-image-format)
|
||||||
7. [ISA soportada — RV32IMC](#7-isa-soportada--rv32imc)
|
7. [Supported ISA — RV32IMC](#7-supported-isa--rv32imc)
|
||||||
8. [GPIO](#8-gpio)
|
8. [GPIO](#8-gpio)
|
||||||
9. [UART0 — Serial Monitor](#9-uart0--serial-monitor)
|
9. [UART0 — Serial Monitor](#9-uart0--serial-monitor)
|
||||||
10. [Limitaciones](#10-limitaciones)
|
10. [Limitations](#10-limitations)
|
||||||
11. [Tests](#11-tests)
|
11. [Tests](#11-tests)
|
||||||
12. [Diferencias vs emulación Xtensa (ESP32 / ESP32-S3)](#12-diferencias-vs-emulación-xtensa-esp32--esp32-s3)
|
12. [Differences vs Xtensa Emulation (ESP32 / ESP32-S3)](#12-differences-vs-xtensa-emulation-esp32--esp32-s3)
|
||||||
13. [Archivos clave](#13-archivos-clave)
|
13. [Key Files](#13-key-files)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Visión general
|
## 1. Overview
|
||||||
|
|
||||||
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.
|
Boards based on **ESP32-C3** use Espressif's **ESP32-C3** processor, which implements the **RISC-V RV32IMC** architecture (32-bit, Multiply, Compressed instructions). Unlike the ESP32 and ESP32-S3 (Xtensa LX6/LX7), the C3 **does not require QEMU or a backend** to be emulated.
|
||||||
|
|
||||||
### Comparación de motores de emulación
|
### Emulation Engine Comparison
|
||||||
|
|
||||||
| Board | CPU | Motor |
|
| Board | CPU | Engine |
|
||||||
|-------|-----|-------|
|
| ----- | --- | ------ |
|
||||||
| ESP32, ESP32-S3 | Xtensa LX6/LX7 | QEMU lcgamboa (backend WebSocket) |
|
| ESP32, ESP32-S3 | Xtensa LX6/LX7 | QEMU lcgamboa (backend WebSocket) |
|
||||||
| **ESP32-C3, XIAO-C3, C3 SuperMini** | **RV32IMC @ 160 MHz** | **RiscVCore.ts (navegador, sin backend)** |
|
| **ESP32-C3, XIAO-C3, C3 SuperMini** | **RV32IMC @ 160 MHz** | **RiscVCore.ts (browser, no backend)** |
|
||||||
| Arduino Uno/Nano/Mega | AVR ATmega | avr8js (navegador) |
|
| Arduino Uno/Nano/Mega | AVR ATmega | avr8js (browser) |
|
||||||
| Raspberry Pi Pico | RP2040 | rp2040js (navegador) |
|
| Raspberry Pi Pico | RP2040 | rp2040js (browser) |
|
||||||
|
|
||||||
### Ventajas del emulador JS
|
### Advantages of the JS Emulator
|
||||||
|
|
||||||
- **Sin dependencias de red** — funciona offline, sin conexión WebSocket al backend
|
- **No network dependencies** — works offline, no WebSocket connection to the backend
|
||||||
- **Arranque instantáneo** — no hay proceso QEMU que arrancar (0 ms de latencia)
|
- **Instant startup** — no QEMU process to launch (0 ms latency)
|
||||||
- **Testable con Vitest** — el mismo código TypeScript que se ejecuta en producción se puede probar en CI
|
- **Testable with Vitest** — the same TypeScript code that runs in production can be tested in CI
|
||||||
- **Multiplataforma** — funciona igual en Windows, macOS, Linux y Docker
|
- **Cross-platform** — works the same on Windows, macOS, Linux, and Docker
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Boards soportadas
|
## 2. Supported Boards
|
||||||
|
|
||||||
| Board | FQBN arduino-cli | LED built-in |
|
| Board | arduino-cli FQBN | Built-in LED |
|
||||||
|-------|-----------------|--------------|
|
| ----- | ---------------- | ------------ |
|
||||||
| ESP32-C3 DevKit | `esp32:esp32:esp32c3` | GPIO 8 |
|
| ESP32-C3 DevKit | `esp32:esp32:esp32c3` | GPIO 8 |
|
||||||
| Seeed XIAO ESP32-C3 | `esp32:esp32:XIAO_ESP32C3` | GPIO 10 (active-low) |
|
| Seeed XIAO ESP32-C3 | `esp32:esp32:XIAO_ESP32C3` | GPIO 10 (active-low) |
|
||||||
| ESP32-C3 SuperMini | `esp32:esp32:esp32c3` | GPIO 8 |
|
| ESP32-C3 SuperMini | `esp32:esp32:esp32c3` | GPIO 8 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Arquitectura del emulador
|
## 3. Emulator Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
Arduino Sketch (.ino)
|
Arduino Sketch (.ino)
|
||||||
│
|
│
|
||||||
▼ arduino-cli (backend)
|
▼ arduino-cli (backend)
|
||||||
sketch.ino.bin ← ESP32 image format (segmentos IROM/DRAM/IRAM)
|
sketch.ino.bin ← ESP32 image format (IROM/DRAM/IRAM segments)
|
||||||
│
|
│
|
||||||
▼ base64 → frontend
|
▼ base64 → frontend
|
||||||
compileBoardProgram(boardId, base64)
|
compileBoardProgram(boardId, base64)
|
||||||
│
|
│
|
||||||
▼ Esp32C3Simulator.loadFlashImage(base64)
|
▼ Esp32C3Simulator.loadFlashImage(base64)
|
||||||
parseMergedFlashImage() ← lee segmentos de la imagen 4MB
|
parseMergedFlashImage() ← reads segments from the 4MB image
|
||||||
│
|
│
|
||||||
├── IROM segment → flash buffer (0x42000000)
|
├── IROM segment → flash buffer (0x42000000)
|
||||||
├── DROM segment → flash buffer (0x3C000000, alias)
|
├── DROM segment → flash buffer (0x3C000000, alias)
|
||||||
|
|
@ -77,56 +77,56 @@ Arduino Sketch (.ino)
|
||||||
│
|
│
|
||||||
▼ core.reset(entryPoint)
|
▼ core.reset(entryPoint)
|
||||||
RiscVCore.step() ← requestAnimationFrame @ 60 FPS
|
RiscVCore.step() ← requestAnimationFrame @ 60 FPS
|
||||||
│ 2.666.667 ciclos/frame (160 MHz ÷ 60)
|
│ 2,666,667 cycles/frame (160 MHz ÷ 60)
|
||||||
├── MMIO GPIO_W1TS/W1TC → onPinChangeWithTime → componentes visuales
|
├── MMIO GPIO_W1TS/W1TC → onPinChangeWithTime → visual components
|
||||||
└── MMIO UART0 FIFO → onSerialData → Serial Monitor
|
└── MMIO UART0 FIFO → onSerialData → Serial Monitor
|
||||||
```
|
```
|
||||||
|
|
||||||
### Clases principales
|
### Main Classes
|
||||||
|
|
||||||
| Clase | Archivo | Responsabilidad |
|
| Class | File | Responsibility |
|
||||||
|-------|---------|----------------|
|
| ----- | ---- | -------------- |
|
||||||
| `RiscVCore` | `simulation/RiscVCore.ts` | Decodificador/ejecutor RV32IMC, MMIO genérico |
|
| `RiscVCore` | `simulation/RiscVCore.ts` | RV32IMC decoder/executor, generic MMIO |
|
||||||
| `Esp32C3Simulator` | `simulation/Esp32C3Simulator.ts` | Mapa de memoria ESP32-C3, GPIO, UART0, ciclo RAF |
|
| `Esp32C3Simulator` | `simulation/Esp32C3Simulator.ts` | ESP32-C3 memory map, GPIO, UART0, RAF loop |
|
||||||
| `parseMergedFlashImage` | `utils/esp32ImageParser.ts` | Parseo formato imagen ESP32 (segmentos, entry point) |
|
| `parseMergedFlashImage` | `utils/esp32ImageParser.ts` | ESP32 image format parsing (segments, entry point) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Memoria y periféricos emulados
|
## 4. Emulated Memory and Peripherals
|
||||||
|
|
||||||
### Mapa de memoria
|
### Memory Map
|
||||||
|
|
||||||
| Región | Dirección base | Tamaño | Descripción |
|
| Region | Base Address | Size | Description |
|
||||||
|--------|---------------|--------|-------------|
|
| ------ | ------------ | ---- | ----------- |
|
||||||
| Flash IROM | `0x42000000` | 4 MB | Código ejecutable (buffer principal del core) |
|
| Flash IROM | `0x42000000` | 4 MB | Executable code (core's main buffer) |
|
||||||
| Flash DROM | `0x3C000000` | 4 MB | Datos de solo lectura (alias del mismo buffer) |
|
| Flash DROM | `0x3C000000` | 4 MB | Read-only data (alias of the same buffer) |
|
||||||
| DRAM | `0x3FC80000` | 384 KB | RAM de datos (stack, variables globales) |
|
| DRAM | `0x3FC80000` | 384 KB | Data RAM (stack, global variables) |
|
||||||
| IRAM | `0x4037C000` | 384 KB | RAM de instrucciones (ISR, código time-critical) |
|
| IRAM | `0x4037C000` | 384 KB | Instruction RAM (ISR, time-critical code) |
|
||||||
| UART0 | `0x60000000` | 1 KB | Serial port 0 |
|
| UART0 | `0x60000000` | 1 KB | Serial port 0 |
|
||||||
| GPIO | `0x60004000` | 512 B | Registros GPIO |
|
| GPIO | `0x60004000` | 512 B | GPIO registers |
|
||||||
|
|
||||||
### GPIO — registros implementados
|
### GPIO — Implemented Registers
|
||||||
|
|
||||||
| Registro | Offset | Función |
|
| Register | Offset | Function |
|
||||||
|----------|--------|---------|
|
| -------- | ------ | -------- |
|
||||||
| `GPIO_OUT_REG` | `+0x04` | Leer/escribir estado de salida de todos los pines |
|
| `GPIO_OUT_REG` | `+0x04` | Read/write output state of all pins |
|
||||||
| `GPIO_OUT_W1TS_REG` | `+0x08` | **Set bits** — poner pines a HIGH (write-only) |
|
| `GPIO_OUT_W1TS_REG` | `+0x08` | **Set bits** — drive pins HIGH (write-only) |
|
||||||
| `GPIO_OUT_W1TC_REG` | `+0x0C` | **Clear bits** — poner pines a LOW (write-only) |
|
| `GPIO_OUT_W1TC_REG` | `+0x0C` | **Clear bits** — drive pins LOW (write-only) |
|
||||||
| `GPIO_IN_REG` | `+0x3C` | Leer estado de entrada de pines |
|
| `GPIO_IN_REG` | `+0x3C` | Read input state of pins |
|
||||||
| `GPIO_ENABLE_REG` | `+0x20` | Dirección de pines (siempre devuelve `0xFF`) |
|
| `GPIO_ENABLE_REG` | `+0x20` | Pin direction (always returns `0xFF`) |
|
||||||
|
|
||||||
Cubre **GPIO 0–21** (todos los disponibles en ESP32-C3).
|
Covers **GPIO 0–21** (all available on ESP32-C3).
|
||||||
|
|
||||||
### UART0 — registros implementados
|
### UART0 — Implemented Registers
|
||||||
|
|
||||||
| Registro | Offset | Función |
|
| Register | Offset | Function |
|
||||||
|----------|--------|---------|
|
| -------- | ------ | -------- |
|
||||||
| `UART_FIFO_REG` | `+0x00` | Escribir byte TX / leer byte RX |
|
| `UART_FIFO_REG` | `+0x00` | Write TX byte / read RX byte |
|
||||||
| `UART_STATUS_REG` | `+0x1C` | Estado FIFO (siempre devuelve `0` = listo) |
|
| `UART_STATUS_REG` | `+0x1C` | FIFO status (always returns `0` = ready) |
|
||||||
|
|
||||||
Lectura de byte de RX disponible para simular input desde Serial Monitor.
|
RX byte reading is available to simulate input from the Serial Monitor.
|
||||||
|
|
||||||
### Periféricos NO emulados (retornan 0 en lectura)
|
### Peripherals NOT Emulated (return 0 on read)
|
||||||
|
|
||||||
- Interrupt Matrix (`0x600C2000`)
|
- Interrupt Matrix (`0x600C2000`)
|
||||||
- System / Clock (`0x600C0000`, `0x60008000`)
|
- System / Clock (`0x600C0000`, `0x60008000`)
|
||||||
|
|
@ -136,40 +136,40 @@ Lectura de byte de RX disponible para simular input desde Serial Monitor.
|
||||||
- BLE / WiFi MAC
|
- BLE / WiFi MAC
|
||||||
- ADC / DAC
|
- ADC / DAC
|
||||||
|
|
||||||
> Estos periféricos retornan `0` por defecto. El código que los requiere puede no funcionar correctamente (ver [Limitaciones](#10-limitaciones)).
|
> These peripherals return `0` by default. Code that depends on them may not function correctly (see [Limitations](#10-limitations)).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Flujo completo: compilar y ejecutar
|
## 5. Full Flow: Compile and Run
|
||||||
|
|
||||||
### 5.1 Compilar el sketch
|
### 5.1 Compile the Sketch
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# arduino-cli compila para ESP32-C3:
|
# arduino-cli compiles for ESP32-C3:
|
||||||
arduino-cli compile \
|
arduino-cli compile \
|
||||||
--fqbn esp32:esp32:esp32c3 \
|
--fqbn esp32:esp32:esp32c3 \
|
||||||
--output-dir build/ \
|
--output-dir build/ \
|
||||||
mi_sketch/
|
mi_sketch/
|
||||||
|
|
||||||
# El backend crea automáticamente la imagen fusionada (merged):
|
# The backend automatically creates the merged image:
|
||||||
# build/mi_sketch.ino.bootloader.bin → 0x01000
|
# build/mi_sketch.ino.bootloader.bin → 0x01000
|
||||||
# build/mi_sketch.ino.partitions.bin → 0x08000
|
# build/mi_sketch.ino.partitions.bin → 0x08000
|
||||||
# build/mi_sketch.ino.bin → 0x10000 (app)
|
# build/mi_sketch.ino.bin → 0x10000 (app)
|
||||||
# → merged: sketch.ino.merged.bin (4 MB)
|
# → merged: sketch.ino.merged.bin (4 MB)
|
||||||
```
|
```
|
||||||
|
|
||||||
El backend de Velxio produce esta imagen automáticamente y la envía al frontend como base64.
|
The Velxio backend produces this image automatically and sends it to the frontend as base64.
|
||||||
|
|
||||||
### 5.2 Sketch mínimo para ESP32-C3
|
### 5.2 Minimal Sketch for ESP32-C3
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// LED en GPIO 8 (ESP32-C3 DevKit)
|
// LED on GPIO 8 (ESP32-C3 DevKit)
|
||||||
#define LED_PIN 8
|
#define LED_PIN 8
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
pinMode(LED_PIN, OUTPUT);
|
pinMode(LED_PIN, OUTPUT);
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
Serial.println("ESP32-C3 iniciado");
|
Serial.println("ESP32-C3 started");
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
|
@ -182,12 +182,12 @@ void loop() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5.3 Sketch bare-metal (para tests de emulación directos)
|
### 5.3 Bare-Metal Sketch (for direct emulation tests)
|
||||||
|
|
||||||
Para verificar la emulación sin el framework Arduino, se puede compilar con el toolchain RISC-V directamente:
|
To verify the emulation without the Arduino framework, you can compile directly with the RISC-V toolchain:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
/* blink.c — bare-metal, sin ESP-IDF */
|
/* blink.c — bare-metal, no ESP-IDF */
|
||||||
#define GPIO_W1TS (*(volatile unsigned int *)0x60004008u)
|
#define GPIO_W1TS (*(volatile unsigned int *)0x60004008u)
|
||||||
#define GPIO_W1TC (*(volatile unsigned int *)0x6000400Cu)
|
#define GPIO_W1TC (*(volatile unsigned int *)0x6000400Cu)
|
||||||
#define LED_BIT (1u << 8)
|
#define LED_BIT (1u << 8)
|
||||||
|
|
@ -204,10 +204,10 @@ void _start(void) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Compilar con el toolchain bundled en arduino-cli:
|
Compile with the toolchain bundled in arduino-cli:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Toolchain instalado con: arduino-cli core install esp32:esp32
|
# Toolchain installed with: arduino-cli core install esp32:esp32
|
||||||
TOOLCHAIN="$LOCALAPPDATA/Arduino15/packages/esp32/tools/riscv32-esp-elf-gcc/esp-2021r2-patch5-8.4.0/bin"
|
TOOLCHAIN="$LOCALAPPDATA/Arduino15/packages/esp32/tools/riscv32-esp-elf-gcc/esp-2021r2-patch5-8.4.0/bin"
|
||||||
|
|
||||||
"$TOOLCHAIN/riscv32-esp-elf-gcc" \
|
"$TOOLCHAIN/riscv32-esp-elf-gcc" \
|
||||||
|
|
@ -217,156 +217,156 @@ TOOLCHAIN="$LOCALAPPDATA/Arduino15/packages/esp32/tools/riscv32-esp-elf-gcc/esp-
|
||||||
"$TOOLCHAIN/riscv32-esp-elf-objcopy" -O binary blink.elf blink.bin
|
"$TOOLCHAIN/riscv32-esp-elf-objcopy" -O binary blink.elf blink.bin
|
||||||
```
|
```
|
||||||
|
|
||||||
Ver script completo: `frontend/src/__tests__/fixtures/esp32c3-blink/build.sh`
|
See full script: `frontend/src/__tests__/fixtures/esp32c3-blink/build.sh`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Formato de imagen ESP32
|
## 6. ESP32 Image Format
|
||||||
|
|
||||||
El backend produce una imagen fusionada de **4 MB**:
|
The backend produces a merged **4 MB** image:
|
||||||
|
|
||||||
```
|
```
|
||||||
Offset 0x00000: 0xFF (vacío)
|
Offset 0x00000: 0xFF (empty)
|
||||||
Offset 0x01000: bootloader (imagen ESP32 format, magic 0xE9)
|
Offset 0x01000: bootloader (ESP32 format image, magic 0xE9)
|
||||||
Offset 0x08000: partition table
|
Offset 0x08000: partition table
|
||||||
Offset 0x10000: app binary (imagen ESP32 format, magic 0xE9) ← parseamos aquí
|
Offset 0x10000: app binary (ESP32 format image, magic 0xE9) ← parsed here
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cabecera de imagen ESP32 (24 bytes)
|
### ESP32 Image Header (24 bytes)
|
||||||
|
|
||||||
```
|
```
|
||||||
+0x00 magic (0xE9)
|
+0x00 magic (0xE9)
|
||||||
+0x01 segment_count
|
+0x01 segment_count
|
||||||
+0x02 spi_mode
|
+0x02 spi_mode
|
||||||
+0x03 spi_speed_size
|
+0x03 spi_speed_size
|
||||||
+0x04 entry_addr ← uint32 LE — PC de entrada del firmware
|
+0x04 entry_addr ← uint32 LE — firmware entry point PC
|
||||||
+0x08 extended fields (16 bytes)
|
+0x08 extended fields (16 bytes)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cabecera de segmento (8 bytes)
|
### Segment Header (8 bytes)
|
||||||
|
|
||||||
```
|
```
|
||||||
+0x00 load_addr ← dirección virtual destino (e.g. 0x42000000)
|
+0x00 load_addr ← destination virtual address (e.g. 0x42000000)
|
||||||
+0x04 data_len
|
+0x04 data_len
|
||||||
+0x08 data[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)`).
|
The `parseMergedFlashImage()` parser in `utils/esp32ImageParser.ts` extracts all segments and the entry point, which is used for the core reset (`core.reset(entryPoint)`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. ISA soportada — RV32IMC
|
## 7. Supported ISA — RV32IMC
|
||||||
|
|
||||||
`RiscVCore.ts` implementa las tres extensiones necesarias para ejecutar código compilado para ESP32-C3:
|
`RiscVCore.ts` implements the three extensions required to run code compiled for ESP32-C3:
|
||||||
|
|
||||||
### RV32I — Base integer (40 instrucciones)
|
### RV32I — Base Integer (40 instructions)
|
||||||
|
|
||||||
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)
|
Includes: 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 (reads return 0)
|
||||||
|
|
||||||
### RV32M — Multiplicación y división (8 instrucciones)
|
### RV32M — Multiply and Divide (8 instructions)
|
||||||
|
|
||||||
| Instrucción | Operación |
|
| Instruction | Operation |
|
||||||
|-------------|-----------|
|
| ----------- | --------- |
|
||||||
| `MUL` | Producto entero (32 bits bajos) |
|
| `MUL` | Integer product (low 32 bits) |
|
||||||
| `MULH` | Producto con signo (32 bits altos) |
|
| `MULH` | Signed product (high 32 bits) |
|
||||||
| `MULHSU` | Producto mixto firmado×sin firma (altos) |
|
| `MULHSU` | Mixed signed×unsigned product (high bits) |
|
||||||
| `MULHU` | Producto sin firma (32 bits altos) |
|
| `MULHU` | Unsigned product (high 32 bits) |
|
||||||
| `DIV` | División entera con signo |
|
| `DIV` | Signed integer division |
|
||||||
| `DIVU` | División entera sin firma |
|
| `DIVU` | Unsigned integer division |
|
||||||
| `REM` | Resto con signo |
|
| `REM` | Signed remainder |
|
||||||
| `REMU` | Resto sin firma |
|
| `REMU` | Unsigned remainder |
|
||||||
|
|
||||||
### RV32C — Instrucciones comprimidas (16 bits)
|
### RV32C — Compressed Instructions (16-bit)
|
||||||
|
|
||||||
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.
|
All 16-bit instructions from the standard C extension are supported. They are detected by `(halfword & 3) !== 3` and decompressed to their RV32I equivalent before execution. This is critical: the GCC compiler for ESP32-C3 heavily generates C instructions (`c.addi`, `c.sw`, `c.lw`, `c.j`, `c.beqz`, `c.bnez`, etc.) which represent ~30-40% of all instructions in the final binary.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. GPIO
|
## 8. GPIO
|
||||||
|
|
||||||
El manejo de GPIO sigue el modelo de registros W1TS/W1TC del ESP32-C3:
|
GPIO handling follows the W1TS/W1TC register model of the ESP32-C3:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Sketch Arduino:
|
// Arduino sketch:
|
||||||
digitalWrite(8, HIGH); // → internamente escribe 1<<8 a GPIO_OUT_W1TS_REG
|
digitalWrite(8, HIGH); // → internally writes 1<<8 to GPIO_OUT_W1TS_REG
|
||||||
|
|
||||||
// En el simulador:
|
// In the simulator:
|
||||||
// SW x10, 0(x12) donde x10=256 (1<<8), x12=0x60004008 (W1TS)
|
// SW x10, 0(x12) where x10=256 (1<<8), x12=0x60004008 (W1TS)
|
||||||
// → escribe 4 bytes a 0x60004008..0x6000400B
|
// → writes 4 bytes to 0x60004008..0x6000400B
|
||||||
// → byteIdx=1 (offset 0x09): val=0x01, shift=8 → gpioOut |= 0x100
|
// → byteIdx=1 (offset 0x09): val=0x01, shift=8 → gpioOut |= 0x100
|
||||||
// → changed = prev ^ gpioOut ≠ 0 → dispara onPinChangeWithTime(8, true, timeMs)
|
// → changed = prev ^ gpioOut ≠ 0 → fires 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`).
|
The callback `onPinChangeWithTime(pin, state, timeMs)` is the integration point with the visual components. `timeMs` is the simulated time in milliseconds (calculated as `core.cycles / CPU_HZ * 1000`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. UART0 — Serial Monitor
|
## 9. UART0 — Serial Monitor
|
||||||
|
|
||||||
Cualquier byte escrito a `UART0_FIFO_REG` (0x60000000) llama al callback `onSerialData(char)`:
|
Any byte written to `UART0_FIFO_REG` (0x60000000) calls the `onSerialData(char)` callback:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// Sketch Arduino:
|
// Arduino sketch:
|
||||||
Serial.println("Hola!");
|
Serial.println("Hello!");
|
||||||
// → Arduino framework escribe los bytes de "Hola!\r\n" a UART0_FIFO_REG
|
// → Arduino framework writes the bytes of "Hello!\r\n" to UART0_FIFO_REG
|
||||||
// → simulador llama onSerialData("H"), onSerialData("o"), ...
|
// → simulator calls onSerialData("H"), onSerialData("e"), ...
|
||||||
// → Serial Monitor muestra "Hola!"
|
// → Serial Monitor displays "Hello!"
|
||||||
```
|
```
|
||||||
|
|
||||||
Para enviar datos al sketch desde el Serial Monitor:
|
To send data to the sketch from the Serial Monitor:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
sim.serialWrite("COMANDO\n");
|
sim.serialWrite("COMMAND\n");
|
||||||
// → bytes se añaden a rxFifo
|
// → bytes are added to rxFifo
|
||||||
// → lectura de UART0_FIFO_REG dequeue un byte del rxFifo
|
// → reading UART0_FIFO_REG dequeues one byte from rxFifo
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. Limitaciones
|
## 10. Limitations
|
||||||
|
|
||||||
### Framework ESP-IDF / Arduino
|
### ESP-IDF / Arduino Framework
|
||||||
|
|
||||||
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:
|
The Arduino framework for ESP32-C3 (based on ESP-IDF 4.4.x) has a complex initialization sequence that accesses non-emulated peripherals:
|
||||||
|
|
||||||
| Periférico | Por qué lo accede ESP-IDF | Efecto en emulador |
|
| Peripheral | Why ESP-IDF accesses it | Effect in emulator |
|
||||||
|------------|--------------------------|-------------------|
|
| ---------- | ----------------------- | ------------------ |
|
||||||
| Cache controller | Configura MMU para mapeo flash/DRAM | Lee 0, puede que no loop |
|
| Cache controller | Configures MMU for flash/DRAM mapping | Reads 0, may not loop |
|
||||||
| Interrupt Matrix | Registra vectores ISR | Sin efecto (silenciado) |
|
| Interrupt Matrix | Registers ISR vectors | No effect (silenced) |
|
||||||
| System registers | Configura PLLs y clocks | Lee 0 (asume velocidad por defecto) |
|
| System registers | Configures PLLs and clocks | Reads 0 (assumes default speed) |
|
||||||
| FreeRTOS tick timer | Timer 0 → interrupción periódica | Sin interrupción = tareas no se planifican |
|
| FreeRTOS tick timer | Timer 0 → periodic interrupt | No interrupt = tasks not scheduled |
|
||||||
|
|
||||||
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.
|
As a result, an Arduino sketch compiled with the full framework may execute partially — code prior to FreeRTOS initialization may work, but `setup()` and `loop()` depend on FreeRTOS running.
|
||||||
|
|
||||||
**Escenarios que SÍ funcionan:**
|
**Scenarios that DO work:**
|
||||||
|
|
||||||
- Código bare-metal (sin framework, acceso directo a GPIO MMIO)
|
- Bare-metal code (no framework, direct GPIO MMIO access)
|
||||||
- Fragmentos de código que no usen FreeRTOS (`delay()`, `millis()`, `digitalWrite()` requieren FreeRTOS)
|
- Code fragments that do not use FreeRTOS (`delay()`, `millis()`, `digitalWrite()` require FreeRTOS)
|
||||||
- Programas de prueba de ISA (operaciones aritméticas, branches, loads/stores a DRAM)
|
- ISA test programs (arithmetic operations, branches, loads/stores to DRAM)
|
||||||
|
|
||||||
**Roadmap para soporte completo:**
|
**Roadmap for full support:**
|
||||||
|
|
||||||
1. Stub del cache controller (devolver valores que indiquen "cache ya configurada")
|
1. Cache controller stub (return values indicating "cache already configured")
|
||||||
2. Stub del interrupt matrix (aceptar writes, ignorar)
|
2. Interrupt matrix stub (accept writes, ignore)
|
||||||
3. Timer peripheral básico (generar tick FreeRTOS periódicamente)
|
3. Basic timer peripheral (generate FreeRTOS tick periodically)
|
||||||
4. Una vez activo FreeRTOS: sketches Arduino normales deberían funcionar
|
4. Once FreeRTOS is active: normal Arduino sketches should work
|
||||||
|
|
||||||
### Otras limitaciones
|
### Other Limitations
|
||||||
|
|
||||||
| Limitación | Detalle |
|
| Limitation | Detail |
|
||||||
|------------|---------|
|
| ---------- | ------ |
|
||||||
| Sin WiFi | El ESP32-C3 tiene radio BLE/WiFi; no emulada |
|
| No WiFi | ESP32-C3 has BLE/WiFi radio; not emulated |
|
||||||
| Sin ADC | GPIO 0-5 como ADC no implementado |
|
| No ADC | GPIO 0-5 as ADC not implemented |
|
||||||
| Sin SPI/I2C hardware | Los periféricos hardware SPI/I2C retornan 0 |
|
| No hardware SPI/I2C | Hardware SPI/I2C peripherals return 0 |
|
||||||
| Sin interrupciones | `attachInterrupt()` no funciona |
|
| No interrupts | `attachInterrupt()` does not work |
|
||||||
| Sin RTC | `esp_sleep_*`, `rtc_*` no implementados |
|
| No RTC | `esp_sleep_*`, `rtc_*` not implemented |
|
||||||
| Sin NVS/Flash writes | `Preferences`, `SPIFFS` no implementados |
|
| No NVS/Flash writes | `Preferences`, `SPIFFS` not implemented |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 11. Tests
|
## 11. Tests
|
||||||
|
|
||||||
Los tests de la emulación RISC-V están en `frontend/src/__tests__/`:
|
RISC-V emulation tests are in `frontend/src/__tests__/`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd frontend
|
cd frontend
|
||||||
|
|
@ -375,76 +375,76 @@ npm test -- esp32c3
|
||||||
|
|
||||||
### `esp32c3-simulation.test.ts` — 30 tests (ISA unit tests)
|
### `esp32c3-simulation.test.ts` — 30 tests (ISA unit tests)
|
||||||
|
|
||||||
Verifica directamente el decodificador de instrucciones de `RiscVCore`:
|
Directly verifies the instruction decoder in `RiscVCore`:
|
||||||
|
|
||||||
| Grupo | Tests | Qué verifica |
|
| Group | Tests | What it verifies |
|
||||||
|-------|-------|--------------|
|
| ----- | ----- | ---------------- |
|
||||||
| RV32M | 8 | MUL, MULH, MULHSU, MULHU, DIV, DIVU, REM, REMU |
|
| 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 |
|
| 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 |
|
| UART | 3 | Write to FIFO → onSerialData, RX read, multiple bytes |
|
||||||
| GPIO | 8 | W1TS set bit, W1TC clear bit, toggle, timestamp, múltiples pines |
|
| GPIO | 8 | W1TS set bit, W1TC clear bit, toggle, timestamp, multiple pins |
|
||||||
| Lifecycle | 4 | reset(), start/stop, loadHex básico |
|
| Lifecycle | 4 | reset(), start/stop, basic loadHex |
|
||||||
|
|
||||||
### `esp32c3-blink.test.ts` — 8 tests (integración end-to-end)
|
### `esp32c3-blink.test.ts` — 8 tests (end-to-end integration)
|
||||||
|
|
||||||
Compila `blink.c` con `riscv32-esp-elf-gcc` (el toolchain de arduino-cli) y verifica la ejecución en el simulador:
|
Compiles `blink.c` with `riscv32-esp-elf-gcc` (the arduino-cli toolchain) and verifies execution in the simulator:
|
||||||
|
|
||||||
| Test | Qué verifica |
|
| Test | What it verifies |
|
||||||
|------|-------------|
|
| ---- | ---------------- |
|
||||||
| `build.sh produces blink.bin` | El toolchain compila correctamente |
|
| `build.sh produces blink.bin` | Toolchain compiles correctly |
|
||||||
| `binary starts with valid RV32 instruction` | El entry point es código RISC-V válido |
|
| `binary starts with valid RV32 instruction` | Entry point is valid RISC-V code |
|
||||||
| `loadBin() resets PC to 0x42000000` | Carga correcta en flash |
|
| `loadBin() resets PC to 0x42000000` | Correct loading into flash |
|
||||||
| `GPIO 8 goes HIGH after first SW` | Primer toggle correcto |
|
| `GPIO 8 goes HIGH after first SW` | First toggle correct |
|
||||||
| `GPIO 8 toggles ON and OFF` | 7 toggles en 2000 pasos (4 ON, 3 OFF) |
|
| `GPIO 8 toggles ON and OFF` | 7 toggles in 2000 steps (4 ON, 3 OFF) |
|
||||||
| `PinManager.setPinState called` | Integración con el sistema de componentes |
|
| `PinManager.setPinState called` | Integration with the component system |
|
||||||
| `timestamps increase monotonically` | El tiempo simulado es consistente |
|
| `timestamps increase monotonically` | Simulated time is consistent |
|
||||||
| `reset() clears GPIO state` | Reset funcional |
|
| `reset() clears GPIO state` | Functional reset |
|
||||||
|
|
||||||
**Resultado esperado:**
|
**Expected result:**
|
||||||
```
|
```
|
||||||
✓ esp32c3-simulation.test.ts (30 tests) ~500ms
|
✓ esp32c3-simulation.test.ts (30 tests) ~500ms
|
||||||
✓ esp32c3-blink.test.ts (8 tests) ~300ms
|
✓ esp32c3-blink.test.ts (8 tests) ~300ms
|
||||||
```
|
```
|
||||||
|
|
||||||
### Binario de prueba bare-metal
|
### Bare-Metal Test Binary
|
||||||
|
|
||||||
```
|
```
|
||||||
frontend/src/__tests__/fixtures/esp32c3-blink/
|
frontend/src/__tests__/fixtures/esp32c3-blink/
|
||||||
├── blink.c ← código fuente bare-metal
|
├── blink.c ← bare-metal source code
|
||||||
├── link.ld ← linker script (IROM @ 0x42000000, DRAM @ 0x3FC80000)
|
├── link.ld ← linker script (IROM @ 0x42000000, DRAM @ 0x3FC80000)
|
||||||
├── build.sh ← script de compilación (usa toolchain de arduino-cli)
|
├── build.sh ← build script (uses arduino-cli toolchain)
|
||||||
├── blink.elf ← (generado) ELF con debug info
|
├── blink.elf ← (generated) ELF with debug info
|
||||||
├── blink.bin ← (generado) binario raw de 58 bytes
|
├── blink.bin ← (generated) raw 58-byte binary
|
||||||
└── blink.dis ← (generado) desensamblado para inspección
|
└── blink.dis ← (generated) disassembly for inspection
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 12. Diferencias vs emulación Xtensa (ESP32 / ESP32-S3)
|
## 12. Differences vs Xtensa Emulation (ESP32 / ESP32-S3)
|
||||||
|
|
||||||
| Aspecto | ESP32-C3 (RISC-V) | ESP32 / ESP32-S3 (Xtensa) |
|
| Aspect | ESP32-C3 (RISC-V) | ESP32 / ESP32-S3 (Xtensa) |
|
||||||
|---------|-------------------|--------------------------|
|
| ------ | ----------------- | ------------------------- |
|
||||||
| Motor | `Esp32C3Simulator` (TypeScript, navegador) | `Esp32Bridge` + backend QEMU |
|
| Engine | `Esp32C3Simulator` (TypeScript, browser) | `Esp32Bridge` + backend QEMU |
|
||||||
| Dependencia backend | **No** — 100% en el navegador | Sí — WebSocket a proceso QEMU |
|
| Backend dependency | **No** — 100% in the browser | Yes — WebSocket to QEMU process |
|
||||||
| Arranque | Instantáneo | ~1-2 segundos |
|
| Startup | Instant | ~1-2 seconds |
|
||||||
| GPIO | Via MMIO W1TS/W1TC | Via QEMU callbacks → WebSocket |
|
| GPIO | Via MMIO W1TS/W1TC | Via QEMU callbacks → WebSocket |
|
||||||
| WiFi | No emulada | Emulada (SSIDs hardcoded) |
|
| WiFi | Not emulated | Emulated (hardcoded SSIDs) |
|
||||||
| I2C/SPI hardware | No emulado | Emulado (callbacks síncronos) |
|
| Hardware I2C/SPI | Not emulated | Emulated (synchronous callbacks) |
|
||||||
| LEDC/PWM | No emulado | Emulado (poll periódico) |
|
| LEDC/PWM | Not emulated | Emulated (periodic polling) |
|
||||||
| NeoPixel/RMT | No emulado | Emulado (decodificador RMT) |
|
| NeoPixel/RMT | Not emulated | Emulated (RMT decoder) |
|
||||||
| Arduino framework | Parcial (FreeRTOS no activo) | Completo |
|
| Arduino framework | Partial (FreeRTOS not active) | Full |
|
||||||
| Tests CI | Sí (Vitest) | No (requiere lib nativa) |
|
| CI tests | Yes (Vitest) | No (requires native lib) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 13. Archivos clave
|
## 13. Key Files
|
||||||
|
|
||||||
| Archivo | Descripción |
|
| File | Description |
|
||||||
|---------|-------------|
|
| ---- | ----------- |
|
||||||
| `frontend/src/simulation/RiscVCore.ts` | Núcleo del emulador RV32IMC (I + M + C extensions) |
|
| `frontend/src/simulation/RiscVCore.ts` | RV32IMC emulator core (I + M + C extensions) |
|
||||||
| `frontend/src/simulation/Esp32C3Simulator.ts` | Mapa de memoria ESP32-C3, GPIO, UART0, ciclo RAF |
|
| `frontend/src/simulation/Esp32C3Simulator.ts` | ESP32-C3 memory map, GPIO, UART0, RAF loop |
|
||||||
| `frontend/src/utils/esp32ImageParser.ts` | Parser del formato imagen ESP32 (merged flash → segmentos) |
|
| `frontend/src/utils/esp32ImageParser.ts` | ESP32 image format parser (merged flash → segments) |
|
||||||
| `frontend/src/store/useSimulatorStore.ts` | `ESP32_RISCV_KINDS`, `createSimulator()`, `compileBoardProgram()` |
|
| `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-simulation.test.ts` | ISA unit tests (30 tests) |
|
||||||
| `frontend/src/__tests__/esp32c3-blink.test.ts` | Integration test end-to-end (8 tests) |
|
| `frontend/src/__tests__/esp32c3-blink.test.ts` | End-to-end integration test (8 tests) |
|
||||||
| `frontend/src/__tests__/fixtures/esp32c3-blink/` | Firmware bare-metal de prueba + toolchain script |
|
| `frontend/src/__tests__/fixtures/esp32c3-blink/` | Bare-metal test firmware + toolchain script |
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue