Repository navigation
Releases: mikroverk/esp32sim
Release list
esp32sim 0.3.0
esp32sim 0.3.0
A fourth chip, WiFi and Bluetooth on all of them, a much wider set of peripherals, and a browser
build that now runs some workloads faster than real time. 137 pull requests since 0.2.0.
Try it without installing anything: https://mikroverk.github.io/esp32sim/
The classic ESP32 (Xtensa LX6)
esp32sim now boots the original dual-core ESP32, from its own mask ROM through the real
bootloader to an unmodified app — selected with --chip esp32.
- The LX6 core, dual-core boot and a reproducible boot golden (#206)
- I2C, LEDC, SPI and RMT adapters (#207), AES, SHA and RSA register adapters (#208),
ADC, DAC and capacitive touch (#210), I2S receive into shared host audio and DMA (#212) - WiFi through the shared station link (#209), with
--wifion the classic CLI (#228), and
TX lengths independent of buffer capacity (#222) - Bluetooth glue for the shared VHCI controller (#211)
- One DRAM reach rule across the classic DMA engines (#225), cached GPIO interrupt status with
IRQs presented on events (#230), correct I2C pin routing at START (#226), an MSB-aligned
SPI1 user-command address (#227) - Boards: LilyGO TTGO T-Display and
esp32dev-loopback, with a MicroPython spot-price
demo on the T-Display (#233)
Status: early. It runs real firmware, including MicroPython, but fewer blocks are modelled than
on the S3.
WiFi on every chip
The station side of the air is now shared code, so the same virtual access point, DHCP, DNS, NTP
and NAT serve all four chips (#125, #193).
- ESP32-C6: end to end — scan, WPA2 join, DHCP lease, pings (#126), plus unstubbed
WiFi/TLS and SHA DMA (#203) - ESP32-C3: station support and a host Ethernet relay (#170)
- ESP32-S3: PHY bring-up completes without a virtual AP present (#223)
- Classic ESP32: joins through the same link (#209, #228)
- WiFi station goldens for S3, C3 and C6 run in CI (#191)
Bluetooth
- A virtual BLE controller for S3, C3 and C6 (#172)
- Opt-in C3 controller-driven advertising (#195) and controller-driven scan and connection
events (#219) — the real, unmodified controller firmware, not a stub
Peripherals
- PWM: LEDC and MCPWM outputs on S3, C3 and C6 (#167); LEDC also on the classic
- Audio: I2S RX microphone input on S3, C3 and C6 (#201), pin-routed microphone sources and
raw-count ADC streams (#202) - ADC: S3 SENS ADC1 one-shot conversions with host analog sources (#165), extended host input
and observation to S3, C3 and C6 (#177) - Camera: live host camera frames fed through the sensor configuration (#190), with host
settings preserved across a guest reboot (#198) - LEDs: GPIO WS2812 pulse decoding with opt-in instruction-sized scheduling (#204), mirrored
RMT frames and finite LED DMA output (#205),TX_STOPas a self-clearing command bit (#161) - GPIO: pin-aware bus attachment and timestamped output (#169), input feedback restored and
C6 SPI routed by pins (#200), digital I2C and timestamped GPIO on the C6 (#174), I2C, SPI2,
RMT and GPIO wired up on the C3 (#173), and a level-triggered interrupt now shows its pin in
STATUSandPCPU_INT(#247) - I2C: runtime addresses, aliases and transfer timing (#221), device removal (#175)
- UART:
RXFIFO_TOUTrises while bytes wait in the FIFO (#160); the S3 and C3 RC clock is
17.5 MHz (#182) - Crypto: C3/C6 SHA DMA (#203)
- Memory: quad PSRAM capacity reported correctly, short cycle limits honoured (#197);
C3/C6 debug areas preserved across a guest reboot (#213) - Interrupts: asserted S3 interrupts survive the APP core leaving reset (#231)
Speed
The browser build went through five rounds of measured optimisation (#85–#120, #129–#157), all
with identical guest work and console hashes between arms.
- fluidbox now runs at 1.52x real time in Chrome — 18.4% less wall time than the previous
round's base; Pocket Tank 12.4% and TinyDraw 5.4% less - The browser scheduler runs 256 instructions per busy core per round instead of 64; native
keeps 64, because Pocket Tank measured 5.4% slower at 256 there (#156, #157) - JIT work: regions that adopt the active hardware loop and follow ROM trampolines, guarded tail
copies with resume, closed leaf callees inlined with static window renaming, register-quad
window addressing, a flat dispatch record and a per-bus flash epoch - Native CLI screens are 2.75–4.64% faster than the previous round with equal work
- Correctness fixes found along the way: CPU instruction semantics and interrupt handling (#94),
a pre-existing TIMG autoreload defect, scheduler accounting (#100), AArch64 memory range
checks (#102), and retained JIT region ownership across cache reindexing (#242)
Full measurements, including everything tried and rejected, are in
docs/experiments.md.
Web and demos
- A landing page, with the emulator page as a workbench, in light and dark (#84)
- The Atech 14 workbench board is drawn with the real module artwork (#217)
- pocket-tank on the page and on Pages (#80, #81), demos from a checkout with per-demo page text
and panel rotation (#79) - A link-preview card, emulated time while loading, split escape sequences in the console (#181)
- Landing-page layout: hero caption, demo thumbnails, the classic ESP32 in the demo list (#237)
Docs and infrastructure
CONTRIBUTING.mdand the checks to run before a pull request (#176, #178)- The mask ROM ELFs are pinned by release and SHA-256, and cached, in CI (#184)
- The README is now a short overview (#249); the experiment history is one catalogue with
evidence (#115) - Release automation: pushing a
v*tag builds macOS arm64, Linux x86_64 and Linux arm64
tarballs, writesSHA256SUMS.txtand attaches them to the tag's release esp32sim --versionreports the release, from each of the three binaries
Install
Download the tarball for your platform below and verify it against
esp32sim-0.3.0-SHA256SUMS.txt. Each one holds esp32sim, esp32sim-c3, esp32sim-c6,
LICENSE and README.md. The classic ESP32 runs through the main binary with --chip esp32.
The mask ROMs are not redistributed; fetch them with tools/fetch-demo-assets.sh --no-linux.
Thanks
@aliceisjustplaying for the performance rounds,
@mfiumara for the classic ESP32, the peripheral models and the
BLE controller, @makoshan for the UART, RMT and ADC fixes, and
@Xorlev for the GPIO interrupt-status fix.
esp32sim 0.2.0
The second release of esp32sim, the instruction-level emulator that runs unmodified ESP-IDF, Arduino and Contiki-NG firmware from the real mask ROM through the bootloader into the application. 0.2.0 adds a third chip, the ESP32-C6, boots Linux 6.11 on the ESP32-S3, and gives the S3 a WebAssembly JIT in the browser.
Try it without installing: https://joakimeriksson.github.io/esp32sim/
New chip: ESP32-C6
- One RV32IMAC core, run with
esp32sim-c6. hello_world matches a real Waveshare ESP32-C6-LCD-1.47 in 203 of 204 console lines. - The Waveshare ESP32-C6-LCD-1.47 board: an ST7789 over SPI and DMA, and a WS2812.
- An 802.15.4 MAC with hardware ACKs in both directions, address filtering and CCA. Several motes share one radio medium, in one process or in one browser tab.
- Contiki-NG runs as an unmodified IDF app, up to an RPL/IPv6 root and client.
--coojaruns the C6 as a lock-stepped external mote of Cooja-NG.
Linux on the ESP32-S3
- svermigo/Linux-on-esp32-S3 boots to a shell natively and in the browser, with the esp-hosted network adapter on core 0 and Linux on core 1.
- UART0 has a receive FIFO, so you can type into the console. The page adds an xterm.js terminal tab.
Faster in the browser and on PIE
-
The browser build compiles hot Xtensa blocks and bounded regions to WebAssembly. That covers function entry, variable shifts, scalar floating point, integer operations, and PIE SIMD on WASM SIMD. Differential tests compare the generated blocks with the interpreter.
-
The PIE inference kernels pack their operands at decode and load 128 bits in one read. The dot product and division are compiled in the wasm JIT, and the native JIT calls PIE directly.
-
On pocket-tank, a 4-bit transformer running on PIE, the speed rose as follows:
0.1.0 code 0.2.0 Native (Apple silicon) 0.47× real time 0.72× Node 0.31× 0.63× Headless Chrome 0.32× 0.62×
Boards and peripherals
- The Waveshare ESP32-S3-Touch-AMOLED-1.8 V2: a CO5300 over QSPI and CST820 touch.
- The Atech board gains two NeoPixel Light Grids as a per-voice SID VU meter. Its firmware moved to its own project, atech-firmware.
- S3 boards now use shared device models, one file per board.
- GDMA memory-to-memory channel pairs, board-backed GP-SPI DMA transfers, output-enable toggles seen as GPIO edges, and one RNG model shared by the C3 and C6.
- A shared-time
CostModelhook, with a measured SRAM cost model and priced MMIO reads and LX7 opcodes for the S3.
The demo page
- Fifteen demos: Linux (plain, logged in, and in a terminal), hello_world, the Touch-LCD-4B panel and its SID player, pocket-tank, the C3 and C6 hello_world, the C6 energy scanner, three Contiki-NG setups, and the Atech synth and SID jukebox.
- Each demo has its own page text, and panels that draw rotated are shown upright.
tools/fetch-demo-assets.shfetches the mask ROMs, xterm.js and the Linux image, so the page runs from a checkout exactly as on the live site.
Benchmarks
tools/bench.pyruns interleaved A/B benchmarks. The pace report includes a real-time ratio.- A browser benchmark harness runs pinned workloads, TinyDraw and pocket-tank, and rejects invalid captures.
Fixes
- An S3 image built for 16 MB flash no longer reboot-loops after a software or panic reset. The reboot kept the wrong flash size.
- The
--webserver accepts WebSocket upgrades whatever the header's letter case. - Function stubs read their arguments from the frame the pending windowed call set up. Also fixed: the NTP transmit timestamp fraction, a duplicate AES mapping, C3 read bounds, CO5300 window coordinates split across SPI transfers, and a wasm abort when reading the environment.
Project
- A LICENSE file: MIT, copyright Joakim Eriksson and Alice.
- CI runs Clippy with warnings as errors, the wasm module under Node through the page's manifests, worker pacing tests, and differential tests for generated WebAssembly.
Regression bar
cargo test --release --workspace -- --include-ignored runs the committed demo firmware and pins console text, audio and instruction counts. CI runs it on every push, along with the wasm checks above.
Install
cargo build --release # target/release/esp32sim, esp32sim-c3, esp32sim-c6or use the macOS arm64 binaries attached below. The mask ROM ELFs come with ESP-IDF (~/.espressif/tools/esp-rom-elfs/), or pass --rom.
Thanks to Alice (@aliceisjustplaying) for the browser WebAssembly JIT, the benchmark harness, the cost model, the AMOLED board and the Clippy clean-up.
Not in this release
- Cycle-accurate timing. One instruction is one cycle by default; the cost model is a hook.
- Bluetooth on any chip. The Linux demo stubs out BLE provisioning.
- WiFi on the C3 and C6.
- A block cache or JIT for the RISC-V chips.
- pocket-tank at real time.
esp32sim 0.1.0
The first tagged release of esp32sim: an instruction-level emulator that runs unmodified ESP-IDF and Arduino firmware for the ESP32-S3 (two Xtensa LX7 cores) and the ESP32-C3 (RISC-V), from the real mask ROM through the second-stage bootloader into the application, with the SoC peripherals modelled at the register level and boards modelled as devices on the pins.
What runs today
- ESP32-S3: mask ROM → bootloader → FreeRTOS apps; SPI flash and octal PSRAM through the cache MMU; systimer, timer groups, interrupt matrix per core, GPIO, USB-Serial/JTAG, UARTs, I2C, GDMA with I2S/LCD_CAM/RMT, SHA/AES/RSA accelerators, WiFi MAC driven by Espressif's closed blobs against a virtual AP with WPA2 and a user-mode NAT to the real network.
- ESP32-C3: unmodified IDF firmware from the mask ROM to
app_main, verified line by line against a real module. - Boards: Atech 14-port Pocket Synth (ST7735, WS2812 ring, encoder, buttons, I2S audio), Waveshare ESP32-S3-CAM-OV5640, Waveshare ESP32-S3-Touch-LCD-4B; a bare module for stock projects.
- A basic-block interpreter and an AArch64 JIT (2× real time on the SID player), the interpreter being the bit-exact oracle (
--no-jit). - The same emulator in the browser (WebAssembly): https://joakimeriksson.github.io/esp32sim/
- Live board UI, action scripts, WAV/PNG capture, hardware differential testing over JTAG.
Architecture (this release)
One machine for every chip (esp-soc), the shared peripheral IP in esp-periph behind a Device trait and one device_set! table per chip, the CPU contract in emu-core. Adding a peripheral is one implementation and one table line; a board is one impl BoardModel; a chip plugs its cores, bus and interrupt routing into the machine. See docs/adding.md.
Analysis
--profile-blocks, --coverage, --irq-latency and --vcd run at full speed on the block path; --trace, --break, --watch, --regtrace, --profile, --regstat and --debug are there too.
Regression bar
cargo test --release --workspace -- --include-ignored runs the committed demo firmware and pins console text, audio (SHA-256) and instruction counts; CI runs it on every push. Every change in the refactor that produced this release was held to byte-identical output and a benchmark within noise.
Install
cargo build --release # target/release/esp32sim, esp32sim-c3or the macOS arm64 binaries attached below. The mask ROM ELFs come with ESP-IDF (~/.espressif/tools/esp-rom-elfs/), or pass --rom.
Not in this release: cycle-accurate timing (1 instruction = 1 cycle by design; a CostModel hook exists), C3 WiFi/BLE, --boot app on the C3.