| Pin | Note | Direction | OUT bit | IO_MUX | Level | Connected to |
|---|
| Address | Machine code | Basic | Line | Source |
|---|
Quick start
- Write RISC-V (RV32IM) assembly in the editor, or pick one of the Examples (top bar).
- Add parts with the add-part button in the top bar (a dialog lists every part); the button next to it removes all parts. Wire a part by clicking one of its pins and then a GPIO pin on the board (or choose the GPIO in the part's settings panel). Ground / 3V3 are connected automatically while Power supply is set to Automatic in the Settings dialog (gear button in the top bar; it shows an orange dot while Power supply is Manual). Part names in the same dialog shows or hides the names under the parts (led1, seg1, …). Wire style switches between curved wires and horizontal / vertical wires; with the latter, click while wiring to place points. If a point is diagonal to the previous one, the wire bends at a right angle, first along the direction you moved the mouse (move back near the previous point to choose again). Backspace removes the last point. To edit a wire afterwards, click it: drag a round handle to move a corner, drag a bar handle to move a segment (this adds corners where the segment meets a pin), and double-click a corner to remove it. Set Power supply to Manual to wire each part's supply pin to a GND or 3V3 pin yourself: a part without power doesn't work, and the rail you choose sets its wiring mode (for example, an LED's fixed leg on 3V3 makes it active-low).
- On the breadboard: drag a part to move it, drag the background to pan, and scroll to zoom (or use the fit / zoom in / zoom out buttons at the top right of the breadboard). Click a part to open its settings panel.
- Press the green ▶ (Run, F5); the wrench button next to it only assembles and lists errors. Use Step (the arrow over a dot, F10), breakpoints (click a line number) and
ebreakto debug. Choose a slow clock (1 kHz or less, such as 10 Hz) in the speed list to watch each instruction execute.
Memory map
| Address | Region |
|---|---|
| 0x40380000 | .text — your code (internal SRAM, instruction bus) |
| 0x3FCA0000 | .data / .bss / .rodata — your variables |
| 0x3FCDFFF0 | initial stack pointer sp (stack grows down); gp = start of .data |
| 0x60000000 | UART0 |
| 0x60004000 | GPIO |
| 0x60009000 | IO_MUX (MCU_SEL, FUN_IE, FUN_WPU, FUN_WPD are modelled) |
| 0x60023000 | SYSTIMER (16 MHz counter) |
| 0x60043000 | USB Serial/JTAG (EP1 writes go to the serial monitor) |
Execution starts at _start, else main, else the first instruction. Other peripheral addresses (0x60000000–0x600D0FFF) read back what was written, so watchdog and clock setup code does no harm.
GPIO registers (base 0x60004000)
| Offset | Name | Description |
|---|---|---|
| 0x04 | GPIO_OUT_REG | Output level of GPIO0–21 (bit n = GPIOn) |
| 0x08 | GPIO_OUT_W1TS_REG | Write 1s to set output bits |
| 0x0C | GPIO_OUT_W1TC_REG | Write 1s to clear output bits |
| 0x20 | GPIO_ENABLE_REG | Output enable (1 = output, 0 = input) |
| 0x24 | GPIO_ENABLE_W1TS_REG | Write 1s to make pins outputs |
| 0x28 | GPIO_ENABLE_W1TC_REG | Write 1s to make pins inputs |
| 0x3C | GPIO_IN_REG | Current level of every pin (read-only) |
IO_MUX (base 0x60009000)
IO_MUX_GPIOn_REG = 0x60009004 + 4·n. As on the real chip:
| Bit | Name | Effect |
|---|---|---|
| 14:12 | MCU_SEL | IO MUX function (0, 1 or 2). Function 1 is always GPIOn; only pins in a GPIO function are driven by GPIO_OUT / GPIO_ENABLE. |
| 9 | FUN_IE | Input enable. When 0, the pin always reads 0 in GPIO_IN_REG. |
| 8 | FUN_WPU | Internal pull-up |
| 7 | FUN_WPD | Internal pull-down |
A button only connects its pin to GND (or 3V3) while pressed. When released the pin is floating unless a pull resistor is enabled, so set FUN_IE | FUN_WPU for a button to GND (or FUN_IE | FUN_WPD for a button to 3V3). Reading a floating pin or a pin with FUN_IE = 0 prints a warning in the serial monitor. Reset values are the ones a real ESP32-C3 shows when the ROM has booted a program from flash: GPIO2–8 and 10 have IE = 1 and no pull; GPIO9, 12–17, 20 and 21 have IE = 1 and a pull-up; GPIO0/1, 11, 18, 19 have IE = 0. GPIO19 (USB D+) is also pulled up by the USB PHY.
After reset MCU_SEL = 0 (GPIO12/13: 1, set by the ROM's DIO flash setup), and function 0 is not GPIO on every pin:
| Pins | Function 0 | To use as GPIO output |
|---|---|---|
| GPIO0–3, 8–11, 18, 19 | GPIOn | works directly |
| GPIO4–7 | JTAG (MTMS, MTDI, MTCK, MTDO) | set MCU_SEL = 1 |
| GPIO12–17 | SPI flash (SPIHD … SPIQ) | |
| GPIO20, 21 | U0RXD, U0TXD (GPIO21 is driven high by the idle UART) |
Writing GPIO_OUT for an output pin that is not in a GPIO function prints a warning. Inputs (GPIO_IN_REG) work in any function as long as FUN_IE = 1. Other fields (FUN_DRV, …) read back what was written.
SYSTIMER (base 0x60023000)
| Offset | Name | Description |
|---|---|---|
| 0x04 | UNIT0_OP_REG | Write bit 30 (UPDATE) to latch the counter; bit 29 (VALUE_VALID) reads 1 |
| 0x40 | UNIT0_VALUE_HI_REG | Latched counter bits [51:32] |
| 0x44 | UNIT0_VALUE_LO_REG | Latched counter bits [31:0] — 16 000 ticks per ms |
UART0 (base 0x60000000)
| Offset | Name | Description |
|---|---|---|
| 0x00 | UART_FIFO_REG | Write: transmit a byte · Read: receive a byte |
| 0x1C | UART_STATUS_REG | bits [9:0] RXFIFO_CNT, bits [25:16] TXFIFO_CNT (always 0) |
USB-Serial-JTAG (base 0x60043000)
The chip's own USB serial port (the USB-C connector of the SuperMini and the XIAO ESP32C3). The serial monitor's input reaches both this port and UART0.
| Offset | Name | Description |
|---|---|---|
| 0x00 | EP1_REG | Write: queue a byte to send · Read: next received byte |
| 0x04 | EP1_CONF_REG | bit 0 WR_DONE (write 1: send the queued bytes now; needed on real hardware), bit 1 IN_EP_DATA_FREE (room to queue), bit 2 OUT_EP_DATA_AVAIL (a byte was received) |
ecall services
Conveniences like RARS/Venus. Put the service number in a7, the argument in a0. Real hardware has no OS to answer an ecall; the Hardware build adds a small runtime that implements these services over USB-Serial-JTAG and UART0.
| a7 | Service | a7 | Service |
|---|---|---|---|
| 1 | print int a0 | 32 | sleep a0 milliseconds |
| 4 | print string at a0 | 30 | a0/a1 = milliseconds since start |
| 11 | print char a0 | 12 | read char → a0 (−1 if none) |
| 34 | print hex a0 | 35 / 36 | print binary / unsigned |
| 10 | exit | 93 | exit with code a0 |
Assembler
Instructions: all of RV32I and the M extension, plus Zicsr (csrrw/s/c[i]), ecall, ebreak, fence, wfi, mret.
Pseudo-instructions: nop li la mv not neg seqz snez sltz sgtz beqz bnez blez bgez bltz bgtz bgt ble bgtu bleu j jr jal label ret call tail csrr csrw csrs csrc rdcycle rdtime rdinstret, lw rd, label and sw rs, label, rt.
Directives: .text .data .bss .rodata .section .equ/.eqv/.set (or NAME = value) .word .half .byte .ascii .asciz/.string .space/.zero .align .balign .globl.
Expressions: + - * / % << >> & | ^ ~, parentheses, 'c' characters, 0x/0b numbers, %hi(sym)/%lo(sym).
Labels: name: and numeric local labels (1: referenced as 1b / 1f). Comments start with #, // or ;, or use /* … */.
Boards
Pick the board in the list at the left of the top bar. Wires to GPIOs the new board doesn't have are removed.
| Board | GPIOs on the headers | On the board |
|---|---|---|
| ESP32-C3-DevKitM-1 | 0–10, 18–21 | BOOT button (GPIO9, active low), RST, USB-UART bridge on UART0. The RGB LED on GPIO8 is not modelled. |
| ESP32-C3 SuperMini | 0–10, 20, 21 (USB-C up: left IO5–IO10, IO20, IO21; right 5V, GND, 3V3, IO4–IO0) | Blue LED on GPIO8 (lit when the pin is LOW), BOOT (GPIO9), RST, USB-C = the chip's USB-Serial-JTAG (GPIO18/19) |
| Seeed Studio XIAO ESP32C3 | 2–10, 20, 21 (USB-C up: left D0–D6 = IO2–IO7, IO21; right 5V, GND, 3V3, D10–D7 = IO10, IO9, IO8, IO20). Pin tooltips show the D names. | BOOT (GPIO9), RST, USB-C = the chip's USB-Serial-JTAG (GPIO18/19). No user LED; UART0 is on D7 (RX) / D6 (TX). |
| ESP32-C3 Lab Board | Same as the SuperMini (the female headers J2 / J1 beside the module repeat its pins) | A SuperMini on a carrier board with: 8 LEDs LED1–LED8 on GPIO0–7 (pin → LED → resistor → GND, lit when HIGH); a common-cathode 7-segment digit with a–g, dp on GPIO0–7; push buttons PB1–PB4 on GPIO9, 10, 20, 21 (pin → GND while pressed; keys 1–4). Click a jumper to install / remove it: LED and 7S connect the LEDs' and the display's ground, PB the buttons' 10k pull-ups. GPIO4–7 start in the JTAG function and GPIO20/21 as U0RXD/U0TXD (U0TXD drives GPIO21 HIGH), so set MCU_SEL = 1 in their IO_MUX registers first. |
LED matrix (MAX7219)
An 8×8 LED matrix module with a MAX7219 driver, or 4 of them daisy-chained (32×8; choose Modules in the part's settings). Wire DIN, CS (LOAD) and CLK to three GPIO outputs and send 16-bit words by bit-banging, MSB first: the MAX7219 shifts DIN in on each rising edge of CLK and stores the word when CS goes from LOW to HIGH. Bits 11–8 are the register address, bits 7–0 the data. The simulator follows every pin change as it happens, so clock edges are never missed at any speed.
| Register | Address | Data |
|---|---|---|
| No-op | 0x0 | Used to skip a chip in a chain |
| Digit 0–7 | 0x1–0x8 | Row 0–7 (top to bottom); bit 7 is the leftmost LED |
| Decode mode | 0x9 | Bit n = 1: digit n shows a Code B character (0–9, -, E, H, L, P, blank) from data bits 3–0, bit 7 = DP |
| Intensity | 0xA | 0–15: brightness (duty cycle (2n+1)/32) |
| Scan limit | 0xB | 0–7: show digits 0..n only |
| Shutdown | 0xC | 0 = display off, 1 = normal operation |
| Display test | 0xF | 1 = all LEDs on (overrides everything else) |
At power-up (and whenever the simulation is reset) the chip is in shutdown with every register 0, so a program must at least set the scan limit and leave shutdown. In a chain, send one word per chip between CS LOW and CS HIGH: the first word ends up in the chip farthest from the board (the rightmost module); use no-ops for chips you don't want to change. The module is powered from 5V (or 3V3) and GND when Power supply is Manual. See the LED matrix (MAX7219) example.
RGB LED
Four legs: R, G, B and the common leg; the R, G and B legs are drawn red, green and blue. With a common cathode (to GND) a colour lights while its pin is HIGH; with a common anode (to 3V3) while its pin is LOW (and the chip drives it). The colour shown mixes the three pins by their duty cycle, so software PWM gives any colour.
4-digit display (TM1637)
A 4-digit 7-segment module with a TM1637 driver and two signal pins, CLK and DIO, each pulled up to VCC by 10k on the module (powered from 3V3 or 5V; on a real board use 3V3 so the pull-ups don't put 5V on the ESP32-C3 pins). The protocol looks like I2C but has no address and sends bytes LSB first:
- Start: DIO goes LOW while CLK is HIGH. Stop: DIO goes HIGH while CLK is HIGH. Otherwise change DIO only while CLK is LOW; the chip reads it on each rising edge of CLK.
- After 8 bits the chip pulls DIO LOW from the 8th falling edge of CLK to the 9th (ACK). Drive DIO open-drain: set
GPIO_ENABLEto pull it LOW (withGPIO_OUT= 0), clear it to release the line. A pin driven HIGH by the ESP32-C3 hides the ACK. To read the ACK, setFUN_IEin the pin's IO_MUX register.
| Command | Byte | Meaning |
|---|---|---|
| Data | 0x40 / 0x44 | Write the display registers with auto-increment / at a fixed address |
| Address | 0xC0 + n | The following bytes of the same transfer (up to the stop) go to digit n (0–3 from the left) and on |
| Display control | 0x80 / 0x88 + b | Display off / on with brightness b = 0–7 (pulse width 1/16, 2/16, 4/16, 10/16 … 14/16) |
A digit byte has segment a in bit 0 … g in bit 6; bit 7 is the colon (on digit 1, clock modules) or the digit's decimal point (choose Bit 7 in the part's settings). The simulated chip starts with the display off and every digit blank. Key scanning is not modelled (a read returns 0xFF). See the 4-digit clock (TM1637) example.
LCD1602 (HD44780)
A 16×2 character LCD with an HD44780-compatible controller, in two versions: LCD1602 wired in 4-bit mode with 7 wires (RS, RW, E, D4–D7; an unwired RW counts as tied to GND, D0–D3 and V0 are not used, the backlight is on while the LCD has power), and LCD I2C with a PCF8574 backpack (SDA, SCL, address 0x27 or 0x3F). Both are powered from 5V (or 3V3).
- The controller takes a nibble on the falling edge of E. After power-up it is in 8-bit mode: send the nibble 3 three times (wait > 4.1 ms after the first, > 100 µs after the second), then 2. From then on each byte is two nibbles, high first; RS = 0 for an instruction, 1 for a character.
- The controller is busy for 15 ms after power-up (here: after the program starts), 1.52 ms after clear / home and 37 µs after other bytes. A byte sent while it is busy is ignored and the Messages panel warns. Wait, or read the busy flag: with RS = 0 and RW = 1 the LCD drives D4–D7 while E is HIGH, D7 of the first nibble is the busy flag (turn the ESP32-C3 pins into inputs with
FUN_IEset while reading). - I2C backpack: each byte written to the PCF8574 sets P0 = RS, P1 = RW, P2 = E, P3 = backlight, P4–P7 = D4–D7, so one nibble is two writes (E = 1, then E = 0). I2C starts with SDA falling while SCL is HIGH and stops with SDA rising while SCL is HIGH; bytes go MSB first and the PCF8574 pulls SDA LOW on the 9th clock to acknowledge. Drive SDA and SCL open-drain as for the TM1637; the backpack has pull-ups. A read returns the port pins (pins written 1 read what the LCD drives).
| Instruction | Byte | Meaning |
|---|---|---|
| Clear display | 0x01 | All spaces, address 0, increment |
| Return home | 0x02 | Address 0, undo display shift |
| Entry mode | 0x04 + 2·I/D + S | I/D = 1: address increments; S = 1: the display shifts with each character |
| Display control | 0x08 + 4·D + 2·C + B | Display on, underline cursor, blinking block |
| Shift | 0x10 + 8·S/C + 4·R/L | Move the cursor (S/C = 0) or shift the display (S/C = 1) right (R/L = 1) or left |
| Function set | 0x20 + 16·DL + 8·N | DL = 0: 4-bit interface, N = 1: 2 lines (0x28 for this LCD) |
| Set CGRAM address | 0x40 + a | Following characters define custom characters 0–7 (8 rows each, bits 4–0) |
| Set DDRAM address | 0x80 + a | Line 1 starts at 0x00, line 2 at 0x40 |
Characters 0x20–0x7F use the HD44780 A00 font (0x5C is ¥, 0x7E / 0x7F are arrows), 0x00–0x07 are the custom characters, 0xDF is ° and 0xFF a full block; other codes (Japanese characters) show blank. See the LCD1602 examples.
Running on a real board
Connect the board over USB and use the ⚡ Hardware menu in the top bar (Chrome or Edge on a desktop, page opened from https:// or localhost; other browsers can only download the .bin):
- Run on board (RAM) resets the board into its ROM bootloader, loads the program into SRAM and starts it. Nothing is written to flash.
- Flash to board writes a firmware image at flash offset 0x0 and restarts the board, so the program runs at every power-up (it replaces whatever was in flash).
- Download firmware (.bin) saves the image for
esptool.py --chip esp32c3 write_flash 0x0 firmware.bin.
Serial port: the top of the menu lists the ports this page may use and marks the one in use (also shown next to the menu). Browsers don't tell web pages a port's COM / tty name, so ports are named after their USB chip (e.g. ESP32 USB-Serial-JTAG (303a:1001) for the SuperMini and the XIAO ESP32C3, CP210x USB-UART for the DevKitM-1). Choose another port… opens the browser's own list, which does show the COM names; Forget this port removes the permission.
The program keeps the simulator's addresses (code at 0x40380000, data at 0x3FCA0000). A runtime added in front of it turns off the watchdogs, sets sp (0x3FCDA000) and gp, answers the ecall services, and prints mcause / mepc / mtval if the program crashes. While it runs, the serial monitor shows the board's output (including the ROM boot messages) and sends what you type to the board. If the board doesn't answer, hold BOOT, press RST, release BOOT and try again.
Differences on the real chip: the CPU runs at 20 MHz (the simulator: 160 MHz), so time delays with SYSTIMER rather than instruction loops. The ESP32-C3 has no cycle / time / instret CSRs; the runtime emulates them with the chip's cycle counter (CSR 0x7E2, upper halves read 0). On the SuperMini and the XIAO ESP32C3, UART0 is on GPIO20/21, not on the USB-C port.
Keyboard
F5 run/pause · F10 step · Ctrl+Enter restart · Ctrl+S save in browser · Ctrl+/ comment / uncomment the selected lines · Ctrl+B show/hide the code editor · Ctrl+J show/hide the bottom panels · Del delete selected part/wire · Esc cancel wiring. Buttons can be given a keyboard key in their settings.