esp32sim — een ESP32-emulator in Rust: Xtensa (ESP32, S3) en RISC-V (C3, C6)

De klassieke ESP32 boot zijn ECO3 ROM op dual LX6 cores, inclusief GPIO, UART en timers. Met de vlag --board ttgo-tdisplay wordt daarnaast de ST7789 en de knoppen van de LilyGO TTGO T-Display toegevoegd.

Vergelijking van ondersteunde SoCs

KenmerkESP32-S3ESP32-C3ESP32-C6
CPU2 × Xtensa LX7 @ 240 MHz1 × RISC-V RV32IMC @ 160 MHz1 × RISC-V RV32IMAC @ 160 MHz
Commandoesp32simesp32sim-c3esp32sim-c6
Core cratextensa-lx7riscv-rv32riscv-rv32 (+ A extensie)
Decoder checks977k instructies, 0 mismatches161k instructies, 0 mismatches126k instructies, 0 mismatches
BootvolgordeROM → bootloader → FreeRTOS → appROM → bootloader → FreeRTOS → appROM → bootloader → FreeRTOS → app
Boards / displaysST7735, ST7701S 480×480 + touch, WS2812, camera, audioGeen — console onlyST7789 172×320 via SPI+DMA, WS2812, 802.15.4 energy-detect
WiFiVirtuele AP, WPA2, DHCP/DNS/NTP, NAT naar echt netwerk, HTTPSZelfde virtuele AP en netwerk: scan, WPA2, DHCP, NAT (één station)Zelfde virtuele AP en netwerk: scan, WPA2, DHCP, NAT (één station, legacy rates)
SnelheidBlock interpreter + AArch64 JIT, ~150–240 Minsn/sPlain interpreter (geen block cache of JIT) — nog steeds sneller dan real-time voor hello_worldZelfde interpreter
In de browserJa (WebAssembly)Ja (WebAssembly)Ja (WebAssembly)
Hardware vergelijkingJTAG trace, 8000 stappen na resynchronisatieConsole diff, 205/208 regels identiekConsole diff, 203/204 regels identiek
StatusVolwassen (mature)Concept (draft) — zie docs/esp32c3.mdConcept (draft) — zie docs/esp32c6.md

Opmerking: De genoemde decoder- en hardware-counts beschrijven eerdere handmatige runs. CI voert sampled decoder corpora en emulator golden outputs uit; externe full-listing tests zijn uitgesloten. De Xtensa objdump test sluit PIE-operandvergelijkingen uit. De hardware trace vergelijking normaliseert window-exceptions en resynchroniseert delay-loops, waardoor het geen ononderbroken cycle-by-cycle overeenstemming is.

Gedeelde SoC-componenten

Een groot deel van de SoC is gedeeld. De C3 hergebruikt de UART, USB-Serial/JTAG, systimer, timer groups, GPIO, SPI flash controller, GDMA en SHA/AES/RSA modellen van de S3, en voegt een eigen memory map, cache controller en interrupt matrix toe. De C6 hergebruikt dezelfde modellen opnieuw, met een eigen memory map, PLIC front-end, L1 cache, PCR en always-on LP blocks. Alleen de CPU-crates zijn echt gescheiden.

Projectstructuur

Kerncomponenten

  • emu-core/: Bevat de Bus en Core traits, Trap, ClockTree en de AArch64 encoder; gedeeld door beide cores.
  • esp-soc/: Bevat Machine<S: Soc>, inclusief scheduler, device time, console, scripts, web UI, loaders en boards. Hier bevindt zich ook het virtuele WiFi access point, het netwerk en de NAT voor elke chip.
  • esp-periph/: Bevat de gedeelde peripheral IP (UART, systimer, TIMG, GPIO, SPIMEM, GDMA, crypto, I2S, RMT, I2C, …), de Device trait en de deviceset! tabel voor montage.

ESP32-S3 (Xtensa LX7, dual core)

  • xtensa-lx7/: Decoder (sampled objdump checks), interpreter (windowed regs, loops, XEA2 exceptions/interrupts, FPU, MAC16, booleans, PIE SIMD), basic-block interpreter, AArch64 JIT en een objdump-compatibele disassembler.
  • esp32s3/: SoC + boards: memory map, cache MMU, SPI flash/PSRAM, SHA/AES/RSA, RNG, systimer, timer groups, interrupt matrix (per core), GPIO, USB-CDC, UARTs, I2C, GDMA + I2S/LCD_CAM, RMT TX, regi2c, RTC WDT, WiFi MAC.
  • board/: Ondersteuning voor atech14, waveshare-cam, waveshare-lcd4b, waveshare-amoled18-v2 en 'none'.
  • cli/: De esp32sim command line interface.

ESP32-C3 en ESP32-C6 (RISC-V, single core)

  • riscv-rv32/: RV32IMAC decoder, interpreter en disassembler.
  • esp32c3/: De C3 SoC: memory map, interrupt matrix, cache controller, RNG. Perifere modellen worden gedeeld met de S3 waar de IP identiek is.
  • esp32c6/: De C6 SoC: unified memory map en MMU, interrupt matrix + PLIC/INTPRI, L1 cache, PCR, LP blocks (reset cause, software reset, RTC timer), ASSIST_DEBUG, en de 802.15.4 en WiFi MACs.

Overige mappen

  • tests/: Golden-output regressietests en fixtures.
  • wasm/: C-ABI crate die alle drie de Machines bundelt voor gebruik in de browser.
  • web/: Browser UI (board tekeningen, console, audio, camera) en emu.js/worker.js voor WASM.
  • hw/: JTAG differentieel-test scripts tegen een echt board en opgevangen C3/C6 consoles.
  • examples/: Voorbeelden zoals hello_world, waveshare-cam, wifi-station en c6-radio-dashboard.
  • tools/: PIE table generator, bench.py (A/B benchmark) en wasm-build.sh.

Ondersteunde Boards (ESP32-S3)

Het SoC-model genereert pin-level events (GPIO edges, RMT symbol streams, I2S samples), die door een BoardModel worden geïnterpreteerd.

  • --board atech14 (standaard): De Atech 14-poorts board met ST7735, WS2812 ring, encoder en knoppen.
  • --board none: Een kaal module; console-only output.
  • --board waveshare-lcd4b: Waveshare ESP32-S3-Touch-LCD-4B (ST7701S 480×480 via LCD_CAM RGB bus, GT911 touch, TCA9554, codecs).
  • --board waveshare-amoled18-v2: Waveshare ESP32-S3-Touch-AMOLED-1.8 V2 (CO5300 368x448 via QSPI, CST820 touch).
  • --board waveshare-cam: Waveshare ESP32-S3-CAM-OV5640 (CH32V003 IO expander, OV5640 via SCCB, ES8311/ES7210 codecs op I2C0, speaker op I2S1, OV5640 op de LCD_CAM DVP poort).

Gebruiksaanwijzing

Run — ESP32-S3

Compileer eerst de release:

cargo build --release

Voorbeeld van het draaien van een demo-image:

FW=web/wasm/fw/public
./target/release/esp32sim --boot rom \
--bootloader $FW/atech-bootloader.bin --ptable $FW/atech-ptable.bin --app $FW/atech-firmware.bin \
--script $FW/atech-script1.txt --wav out.wav --tft-png tft.png --max-seconds 5

Voor een standaard ESP-IDF project:

H=examples/hello_world/build
./target/release/esp32sim --board none --boot rom --console uart0 \
--bootloader $H/bootloader/bootloader.bin --ptable $H/partition_table/partition-table.bin \
--app $H/hello_world.bin --elf $H/hello_world.elf --elf $H/bootloader/bootloader.elf --max-seconds 26

Run — ESP32-C3

esp32sim-c3 draait ongewijzigde ESP-IDF firmware op de geëmuleerde RISC-V core.

H=examples/hello_world-c3/build
./target/release/esp32sim-c3 --boot rom --flash-mb 4 \
--bootloader $H/bootloader/bootloader.bin --ptable $H/partition_table/partition-table.bin \
--app $H/hello_world.bin --elf $H/hello_world.elf --max-seconds 26

Run — ESP32-C6

esp32sim-c6 gebruikt dezelfde front-end voor de ESP32-C6 (RV32IMAC, PLIC, unified memory map).

H=examples/hello_world-c6/build
./target/release/esp32sim-c6 --boot rom --flash-mb 4 \
--bootloader $H/bootloader/bootloader.bin --ptable $H/partition_table/partition-table.bin \
--app $H/hello_world.bin --elf $H/hello_world.elf --max-seconds 27

Scripts (Host-acties op geëmuleerde tijd)

Je kunt acties plannen op specifieke tijdstippen:

1.5  press btn1 150                       # GPIO17 laag voor 150 ms
2.2  knob cw 2                            # twee quadrature detents op CLK5/DT4
3.4  press btn2 120
4.0  serial {"action":"set_note","value":"9"}   # naar USB-CDC RX
5.0  stop

WiFi (ESP32-S3, C3, C6 en klassieke ESP32)

Met de vlag --wifi ssid=NAME[,psk=PASS,chan=N,bssid=..] wordt een virtueel access point gekoppeld. De ongewijzigde Espressif WiFi blob kan hiermee associëren (scan, authenticatie, associatie en WPA2-PSK four-way handshake).

  • --net nat (standaard): Stuurt TCP en UDP van de guest door via host sockets naar het echte netwerk.
  • --net none: Weigert netwerktoegang.

Er zijn geen firmware-wijzigingen, root-rechten of tun-devices nodig.

Gebruik in de browser (WebAssembly)

De emulator is gecompileerd naar WebAssembly en draait in een Web Worker. Gebruikers kunnen een board kiezen, ROM ELF en firmware laden en op "Boot" drukken.

  • Live demo: https://mikroverk.github.io/esp32sim/
  • De S3-versie beschikt over een eigen WebAssembly JIT (hot blocks en regions, PIE op WASM SIMD).
  • Er is geen NAT beschikbaar in de browser omdat de browser geen sockets ondersteunt.

Debugging

Beschikbare vlaggen voor analyse:

  • --trace [--trace-from N], --break ADDR, --watch ADDR (stop bij wijziging van een word).
  • --peek ADDR[,N], --profile (pc histogram), --log-periph (eerste touch van onbekende registers).
  • --stop-after-exceptions N, --gram-png (raw ST7735 GRAM), --no-jit (gebruik interpreter).

Omgevingsvariabelen voor debuggen: ESPEMUDEBUG, ESPEMUDEBUGSPI, ESPEMUDEBUGUSB, ESPEMUDEBUGWIFI[FRAMES], ESPEMUDEBUGNET, ESPEMUDEBUGAES, ESPEMUDEBUGSHA, ESPEMUDEBUGRSA.

Differentiële testen tegen echte hardware

Elke chip wordt gecontroleerd tegen fysieke hardware:

  • ESP32-S3: Wordt getest in JTAG lock-step. Scripts lezen efuses en strapping pins van de chip en voeren vervolgens stappen uit in zowel de chip als de emulator, waarbij de PC's worden vergeleken.
  • ESP32-C3 en C6: Worden getest door de console-output van de emulator te vergelijken met een opgevangen console van een echt board.

Deze methode heeft geleid tot het vinden van vijf bugs per chip, waaronder een device-command race en een cycle counter die niet resette.

Prestaties en Real-time weergave (ESP32-S3)

Instructieblokken worden eenmaal gedecodeerd, gecached en vervolgens gecompileerd naar native AArch64 code. Om snelheid te optimaliseren:

  • Idle cores (waiti) worden overgeslagen.
  • Device time wordt lazy geleverd tegen berekende timer deadlines.
  • Interrupt-lijnen worden alleen opnieuw berekend als een bron verandert.

De Pocket Synth firmware draait in real-time, behalve bij volledige display-redraws (bit-banging SPI), wat resulteert in een lichte lag die door de adaptive audio buffer wordt opgevangen.

Licenties en Bijdragen

esp32sim is uitgebracht onder de MIT License, copyright Joakim Eriksson, Alice en Mattia Fiumara.

Derde-partij code en data behouden hun eigen licenties:

  • pocket-tank: MIT.
  • MicroPython: MIT.
  • Mask ROM ELFs: Apache-2.0.
  • xterm.js: MIT.
  • Linux image: GPL-3.0.
  • ESP-IDF ADC calibration data: Apache-2.0.