ESP32-S3
ESP32-S3 is Espressif's popular dual-core WiFi and Bluetooth microcontroller, emulated in the browser on a from-scratch Tensilica Xtensa LX7 interpreter, part of the Xtensa family. Xtensa is a RISC-like ISA with a windowed register file (a0-a15 rotate over a larger physical AR file via WINDOWBASE/WINDOWSTART, driven by CALL4/8/12 + ENTRY + RETW), variable-length 24-bit and 16-bit (density) instructions, a shift-amount register with zero-overhead LOOP registers, and PS/EPC1/EXCCAUSE for exceptions. The core is wired to an ESP32-S3 SoC model at the real hardware addresses: internal SRAM, XIP flash, the GPIO block with the on-board LED on GPIO48, and UART0. It runs REAL bare-metal Xtensa firmware, including an interactive UART REPL you can type into. Because the whole chip is ordinary JavaScript it plugs into the site's shared debugger: a from-scratch Xtensa disassembler, live a0-a15 + PC + the special registers, side-effect-free memory, single-instruction step, execution breakpoints and write-watchpoints. WiFi and BLE have no radio here and are HLE stubs, only core0 is run, and the devkit is silent.
Runs on: Web browser
ESP32-S3 Online Emulator
Play ESP32-S3 using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| RGB LED colour cycle | ESP32-S3 | ESP32-S3 | open | Open ⛶ | |
| Mini-REPL (UART0) | ESP32-S3 | ESP32-S3 | open | Open ⛶ | |
| RGB-LED blink | ESP32-S3 | ESP32-S3 | open | Open ⛶ | |
| Hello UART (serial) | ESP32-S3 | ESP32-S3 | open | Open ⛶ |
Chips
Notes
Embedding
The CPU and SoC are written from scratch for this site (public domain / CC0) — nothing here wraps a third-party core. xtensa-esp32s3.js is adapted from the site's ESP32 (Xtensa LX6) core: the LX7 the ESP32-S3 uses shares the same windowed register file, the 24/16-bit density encodings, L32R, the SAR shifts, the 32-bit multiply and the special registers, so the CPU interpreter is reused unchanged and what differs is the SoC — the ESP32-S3 memory map and peripheral layout (including the bank-1 GPIO registers, because the S3 has GPIO0-48). It is all inspectable JavaScript state, which is exactly what lets the shared debugger drive it.
var sys = window.ESP32S3.createSystem(); // Xtensa LX7 core + ESP32-S3 SoC
sys.loadImage(bytes, 0x40370000); // flat app image into IRAM
sys.setEntry(0x40370000); // enter at the image start
sys.onUartTx = function(b){ /* UART0 TX -> console */ };
sys.feedRx(byte); // keyboard -> UART0 RX FIFO
sys.getPixel(); // decoded WS2812 RGB LED (GPIO48)
sys.step(); // execute exactly one instruction
We own the run loop so the debugger can drive it. Each frame runs a time-boxed budget of sys.step() (one Xtensa instruction each); breakpoints stop it by PC, write-watchpoints by bus address. Everything is plain JavaScript state:
| Member | Kind | What it does |
|---|---|---|
sys.step() | method | Fetch → decode width (16-bit density vs 24-bit) → execute one instruction, advancing pc and wrapping the zero-overhead LOOP. The single-step primitive. |
sys.ar(i) / sys.setAr(i,v) | method | The current window's a0-a15, resolved through WINDOWBASE over the 64-register physical AR file. sys.getPc()/setPc() and sys.sr (sar/ps/windowbase/windowstart/lbeg/lend/lcount/epc1/exccause) complete the programmer's model — all readable and writable. |
sys.mem.sramI / .sramD / .flash / .rtc | field | The real memories as typed arrays — read side-effect-free for the hex + disassembly views. |
sys.read8/16/32, sys.write8/16/32 | method | The bus, decoding the ESP32-S3 memory map. The write path notes watched addresses and decodes the WS2812 edge on GPIO48. |
sys.getPixel() | method | The on-board RGB LED — the 24-bit GRB colour decoded from the bit-banged WS2812 waveform on GPIO48, polled each frame to draw the LED. |
sys.feedRx(b) | method | Serial RX in — the keyboard drives the mini-REPL through UART0. |
Debugger integration
The plug-in (esp32-s3-debug.js) describes the ESP32-S3 to the shared debugger and nothing more — the core is not patched:
- Reused Xtensa decoder. The ESP32-S3's LX7 uses the same instruction encodings as the LX6, so the shared from-scratch disassembler at
/debugger/src/cpus/xtensa.js(registered asxtensa) is reused unmodified; the CPU is shown as "Xtensa LX7 (ESP32-S3)". It decodes the variable-length ISA (24-bit when the low nibble op0 is 0-7, 16-bit density forms when 8-13) — the ALU/shift/load-store/branch set, L32R, the windowed and CALL0 calls (CALL0/4/8/12, CALLX, ENTRY, RET, RETW), RSR/WSR/XSR and the narrow forms — and matches the interpreter's encodings exactly. - Registers are read live each refresh: the current window's a0-a15 (resolved through WINDOWBASE), the pc, and the special registers SAR, PS, WINDOWBASE, WINDOWSTART, the LBEG/LEND/LCOUNT loop registers and EPC1/EXCCAUSE. Each has a
set()that writes straight back through the core. Single-stepping the Hello firmware's windowed CALL4/ENTRY/RETW visibly rotates WINDOWBASE. - Memory is exposed as four chips, all read side-effect-free directly off the typed arrays, each with its real base so the disassembly lines up with PC: IRAM at
0x40370000(disassembled), DRAM at0x3FC88000, XIP flash at0x42000000(disassembled), and RTC memory at0x50000000. The UART/GPIO peripheral window is deliberately not a chip, because reading the UART FIFO pops a byte — memory reads stay side-effect-free. - Single step is one
sys.step(). Breakpoints are host-side checks of the PC (in the running code chip's offset space, so a click in the disasm gutter halts) run instruction-by-instruction when any are set. Write-watchpoints are a set the SoC's write path checks, pausing the loop the moment a watched bus address is written.
Architecture
The Espressif ESP32-S3 (2020) is a low-cost WiFi + Bluetooth LE microcontroller built on a dual-core 32-bit Tensilica Xtensa LX7:
- Xtensa LX7 — two 32-bit cores at up to 240 MHz. Like the ESP32's LX6, the LX7 is a configurable RISC-like ISA with a windowed register file (the visible a0-a15 rotate over a larger physical AR file via WINDOWBASE/WINDOWSTART, driven by CALL4/8/12 + ENTRY + RETW), variable-length 24-bit and 16-bit (density) instructions, a shift-amount register (SAR) with zero-overhead LOOP registers (LBEG/LEND/LCOUNT), and PS / EPC1 / EXCCAUSE for exceptions. Because the LX7 shares these with the LX6 for the code this firmware runs, the site's from-scratch Xtensa core is reused and only the SoC differs.
- Memory — internal SRAM reachable from the instruction bus at
0x40370000(IRAM) and the data bus at0x3FC88000(DRAM); code also executes in place (XIP) from external SPI flash mapped at0x42000000(and rodata at0x3C000000); a small RTC memory sits at0x50000000. All are at their real hardware addresses — note how they differ from the classic ESP32. - On-board RGB LED — an ESP32-S3-DevKitC carries a single addressable WS2812 RGB LED on GPIO48. GPIO48 lives in the GPIO block's bank-1 registers (GPIO_OUT1 / GPIO_ENABLE1 at
0x60004010/0x6000402Cand their W1TS/W1TC set/clear pairs). The firmware bit-bangs the one-wire 800 kHz waveform and the SoC decodes the HIGH-pulse widths back into a 24-bit GRB colour. - Serial — UART0 at
0x60000000is the console the ROM bootloader and apps log to; its TX FIFO drives the serial panel and its RX FIFO (read through UART_STATUS' RXFIFO_CNT) is fed by the keyboard.
The bundled firmware is real Xtensa LX7 machine code authored for this site (CC0), assembled to flat ESP32-S3 images and run on the from-scratch interpreter. The default is an RGB-LED colour cycle that bit-bangs the on-board WS2812; the picker also has an interactive mini-REPL that evaluates integer expressions over UART0 — because it is real machine code on the real memory map, every instruction single-steps in the debugger and led on visibly lights the on-board RGB LED.
Honest limits. This is a deliberate honest partial. The from-scratch core runs real Xtensa LX7 and drives the real GPIO / UART, but the full ESP-IDF / FreeRTOS + WiFi/BLE stack is out of scope: only core0 is run (core1 is not modelled), the WiFi and BLE radios have no RF — their register windows are RAM-backed HLE stubs so probing firmware does not hang, but no packets move — and the watchdog is fed so nothing resets. The content is therefore bare-metal, not the full MicroPython/ESP-IDF image; timing is instruction-approximate, not cycle-exact; and the bare devkit has no on-board audio, so this emulator is naturally silent.