This is the engineering reference for the model: what it implements, register by register, for people writing firmware against it. For using the board in the editor, start with The ESP32-C6-DevKitC-1 and the board page.

ESP32-C6 board contract (Phase 9)

The ESP32-C6-DevKitC-1 is the second RISC-V board here. One kernel component (esp32c6 in crates/parts) wraps an ESP32-C6 SoC model (crates/esp32c6) on the same hart as the C3 (crates/rv32-core). The core is shared; the chip around it is not. This document is the contract, and it wins over any comment in the code.

The catalog type is esp32c6. Where a section says "as docs/esp32c3.md", that document is the contract for that part.

Read section 7 first if you are taking a build from here to real silicon. This model is smaller than the C6, and more of it than usual is marked "believed, not verified": section 7 says which addresses those are and what is absent altogether. Nothing here is guessed at quietly. Section 3.6, the SAR ADC's register offsets, is the one block whose layout is on that list.

1. Board pins (catalog order)

Top view, the two USB-C sockets at the bottom, the ESP32-C6-WROOM-1 module and its PCB antenna at the top. The board is 52.5 x 25.4 mm and its two 15-pin headers are 22.86 mm (exactly 0.9 inch) apart, so it straddles a breadboard's center channel.

Left header, top to bottom (15 holes): 3V3, RST, 4, 5, 6, 7, 0, 1, 8, 10, 11, 2, 3, GND, 5V.

Right header, top to bottom (15 holes): GNDb, TX, RX, 9, 12, 13, 14, 21, 20, 19, 18, 15, 23, 22, GNDc.

30 pins. TX and RX are GPIO 16 and 17: the silkscreen names them for their function because they are the console UART. Duplicated names carry a letter because catalog pin names are unique.

The chip has GPIO 0 to 30. GPIO 24 upwards are the SPI flash on this package and are on neither header.

Two pins on the board are not on a header at all:

  • GPIO 8 has an addressable RGB LED soldered to it. A poke does not reach it and the board part does not decode it: a WS2812 is a protocol, not a level. Wire the ws2812 part to GPIO 8 and it lights with the colors the sketch is sending, which is what the rainbow example does.
  • GPIO 9 is the BOOT button, active low. It is on the right header as well, so it can be wired to; the poke grounds it on the board.

Electrical: as docs/esp32c3.md section 1. Every GPIO drives push-pull at 3.3 V through R_STRONG, sits high-Z, or pulls up or down through 45 k. An input reads high at 1.65 V; a floating or contended net reads 0. 3V3 drives 3.3 V at R_SUPPLY, 5V drives 5 V, the three grounds drive 0 V. RST carries a 10 k pull-up to 3.3 V, and pulling it below 1.65 V holds the core in reset.

The exact silkscreen ordering of the two headers is this model's reading of the DevKitC-1 pinout. The GPIO numbers are the chip's and are what firmware depends on; their position on the header is what a diagram depends on. If the real board's rows are ordered differently, a circuit drawn here would need rewiring on a desk, and nothing else would change.

2. Memory map

HP SRAM 512 KB at 0x4080_0000 to 0x4087_FFFF
stack top 0x4088_0000
UART0 0x6000_0000, section 3.3
LEDC 0x6000_7000, section 3.7
SYSTIMER 0x6000_A000, section 3.4
APB_SARADC 0x6000_E000, section 3.6 (offsets inside believed)
IO_MUX 0x6009_0000, section 3.2
GPIO 0x6009_1000, section 3.1

Every base address in this table is the ESP32-C6 Technical Reference Manual v1.2, table 5.3-2. An earlier version of this model put APB_SARADC at 0x6000_F000, which that table gives to the USB Serial/JTAG controller.

One window. This is the first thing that differs from the C3, where the same 400 KB RAM appears twice (at 0x4037_C000 for the instruction bus and 0x3FC7_C000 for the data bus), and every access has to try both. Here both buses reach the HP SRAM at the same addresses, so a load address is a run address, .data needs no copy at reset, and the bus is one range check rather than two.

Every PT_LOAD segment must land inside that range; anything else is refused with the offending address in the message, and the chip is left with no program.

3. Peripheral registers

Only the subset below is implemented. Anything else inside a mapped 4 KB block reads 0 and counts as a stray access; anything outside every block is an access fault.

3.1 GPIO (0x6009_1000)

offset register
0x04 GPIO_OUT_REG
0x08 GPIO_OUT_W1TS_REG
0x0C GPIO_OUT_W1TC_REG
0x20 GPIO_ENABLE_REG
0x24 GPIO_ENABLE_W1TS_REG
0x28 GPIO_ENABLE_W1TC_REG
0x3C GPIO_IN_REG
0x44 GPIO_STATUS_REG
0x4C GPIO_STATUS_W1TC_REG
0x74 + 4n GPIO_PINn_REG
0x554 + 4n GPIO_FUNCn_OUT_SEL_CFG_REG

Bits 0 to 30 exist; bit 31 is masked away on a write. GPIO_PINn_REG bit 2 PAD_DRIVER makes the pad open drain, and bits 7..9 INT_TYPE choose what raises the pin's interrupt: 0 off, 1 rising, 2 falling, 3 any edge, 4 low level, 5 high level. GPIO_FUNCn_OUT_SEL_CFG_REG at 128 makes the pad follow GPIO_OUT; 45 to 50 hand it to LEDC low-speed channels 0 to 5 (section 3.7, and those six indices are unverified); any other value selects a peripheral that is not modeled, and the model treats it as 128 rather than invent a level. A pad an LEDC channel is driving still needs its output driver on in GPIO_ENABLE_REG.

3.2 IO_MUX (0x6009_0000)

IO_MUX_GPIOn_REG at 0x04 + 4n. Bit 7 FUN_WPD, bit 8 FUN_WPU, bit 9 FUN_IE, bits 12..14 MCU_SEL of which 1 is plain GPIO. A pad only reaches GPIO_IN_REG with its input buffer enabled, which is what the hardware does. Every pin powers up at FUN_IE | (1 << 12), so a program that only ever writes GPIO_ENABLE and GPIO_OUT behaves the same on every pin.

3.3 UART0 (0x6000_0000)

The C3's offsets, as docs/esp32c3.md section 3.3 gives them: UART_FIFO_REG 0x00, UART_INT_RAW_REG 0x04, UART_INT_ST_REG 0x08, UART_INT_ENA_REG 0x0C, UART_INT_CLR_REG 0x10, UART_CLKDIV_REG 0x14, UART_STATUS_REG 0x1C, UART_CONF0_REG 0x20, UART_CONF1_REG 0x24 (the ESP32-C6 Technical Reference Manual v1.2, section 27 register summary). Two 128-byte FIFOs, and bytes leave the transmit FIFO at the baud rate rather than instantly.

Not the C3's bits, though. Checked against the same manual:

  • UART_CONF0_REG (the manual's UART_CONF0_SYNC_REG, register 27.9): bit 22 RXFIFO_RST and bit 23 TXFIFO_RST empty the FIFOs. An earlier version of this model used the C3's bits 17 and 18, which on the C6 are other fields. On silicon a write to a _SYNC register takes effect once UART_REG_UPDATE (0x98) is set; the model applies it at once.
  • UART_CONF1_REG (register 27.10): bits 0..7 RXFIFO_FULL_THRHD and bits 8..15 TXFIFO_EMPTY_THRHD, both resetting to 96. An earlier version of this model had nine-bit fields at 0 and 9.
  • UART_STATUS_REG (register 27.21): bits 0..7 RXFIFO_CNT and bits 16..23 TXFIFO_CNT. The runtime reads them with eight-bit masks, as the manual gives them. It once read ten, which returned the same numbers because the two bits above each field are reserved and read 0; the header now matches the manual.

The divisor divides the 40 MHz crystal, which is what a C6 UART comes out of reset using; UART_CLKDIV resets to 347, which is 115 274 baud.

3.4 SYSTIMER (0x6000_A000)

Unit 0 and comparator 0. SYSTIMER_UNIT0_OP_REG 0x04, SYSTIMER_UNIT0_VALUE_HI_REG 0x40, ..._LO_REG 0x44, SYSTIMER_TARGET0_HI_REG 0x1C, ..._LO_REG 0x20, SYSTIMER_TARGET0_CONF_REG 0x34, SYSTIMER_COMP0_LOAD_REG 0x50, SYSTIMER_INT_ENA_REG 0x64, ..._RAW_REG 0x68, ..._CLR_REG 0x6C, ..._ST_REG 0x70. Verified against the ESP32-C6 Technical Reference Manual v1.2, section 13 register summary and registers 13.14 to 13.17. An earlier version of this model put comparator 0 at 0x50 to 0x5C, which on the C6 are the load strobes; the runtime's images were rebuilt when that was corrected.

The counter is a 52-bit 16 MHz tick count derived from simulated time rather than counted, so it never drifts from the rest of the circuit. Reading it is latch-then-read: set UPDATE, wait for VALUE_VALID, read HI then LO. One-shot and period mode both work.

3.5 Interrupts

As docs/esp32c3.md section 3.5. The C6's interrupt matrix is not modeled: GPIO and UART0 share the machine external interrupt, mcause 11, and the handler reads the two status registers to find out which fired; the SYSTIMER comparator is the machine timer interrupt, mcause 7. mstatus.MIE, mie.MTIE and mie.MEIE behave; mtvec runs in direct mode.

3.6 APB_SARADC (0x6000_E000)

ADC1 in one-shot mode, register for register as docs/esp32c3.md section 3.5 describes it: the path ESP-IDF's adc_oneshot driver and the Arduino core's analogRead take, and nothing else. What differs here is the pinout (seven channels, 0 to 6, which on this package are GPIO 0 to GPIO 6) and the block's base address.

The base address is the manual's (table 5.3-2); an earlier version of this model had 0x6000_F000, which is the USB Serial/JTAG controller. Every register offset in this section is unverified: they are the C3's, which is the family's layout, not a page of the C6 technical reference manual quoted back. A program that has to run on a real DevKitC-1 should check them first.

offset name bits
0x00 APB_SARADC_CTRL_REG bits 7..14 SAR_CLK_DIV. Stored and read back; conversion timing here is fixed
0x04 APB_SARADC_CTRL2_REG stored, read back. The timer-triggered and DMA paths are not modeled
0x0C APB_SARADC_FSM_WAIT_REG stored, read back
0x38 APB_SARADC_ONETIME_SAMPLE_REG bits 23..24 ONETIME_ATTEN, bits 25..28 ONETIME_CHANNEL, bit 29 ONETIME_START, bit 30 SAR2_ONETIME_SAMPLE (stored, converts nothing), bit 31 SAR1_ONETIME_SAMPLE
0x44 APB_SARADC_1_DATA_STATUS_REG bits 0..16 ADC1_DATA: the last ADC1 conversion, twelve bits wide
0x64 APB_SARADC_INT_ENA_REG stored; no line reaches the core
0x68 APB_SARADC_INT_RAW_REG bit 31 ADC1_DONE
0x6C APB_SARADC_INT_ST_REG raw and enabled
0x70 APB_SARADC_INT_CLR_REG write 1 to clear, same bit
0x78 APB_SARADC_APB_ADC_CLKM_CONF_REG stored, read back

One conversion: write ONETIME_SAMPLE with SAR1_ONETIME_SAMPLE, the channel and the attenuation, write it again with ONETIME_START up, wait for ADC1_DONE in INT_RAW, read the data register, clear the flag and drop ONETIME_START. A conversion takes a fixed 5 us of simulated time, which firmware really does wait for.

The result is twelve bits, round(4095 x Vin / Vfull), clamped, of the net voltage at the moment the conversion started, and ONETIME_ATTEN picks the full scale at the data sheet's nominal figures: 0 dB is 1.1 V, 2.5 dB 1.5 V, 6 dB 2.2 V and 11 dB 3.3 V. A divider at half the 3.3 V rail therefore reads about 2048 at 11 dB. A channel with nothing holding its pin reads 0 rather than noise.

ADC2, the DMA pattern table, the digital controller, the filters and threshold monitors, the calibration eFuses and the internal temperature channel are all absent, and ADC1_DONE never reaches mip.

3.7 LEDC (0x6000_7000)

The LED PWM controller, low-speed group, which is the only group this chip has: four timers and six channels. This is the block behind analogWrite, ledcAttach and ledcWrite. The channel and timer blocks sit where the C3's do, but the C6's LEDC is not the C3's register for register: the timer's resolution field is five bits and goes to twenty, HPOINT and DUTY are wider, and the global registers moved.

A timer counts 2^DUTY_RES counts at 80 MHz divided by CLK_DIV, a Q10.8 number of 80 MHz clocks per count, so f = 80e6 / ((CLK_DIV / 256) * 2^DUTY_RES) and 5 kHz at eight bits is DUTY_RES 8 with CLK_DIV 16000, exactly. A channel picks a timer, a start count HPOINT and a duty, and its output is high from HPOINT for DUTY >> 4 counts, wrapping round the top of the period. A duty of 0 is a solid low and a duty of 2^DUTY_RES a solid high, with no edge at all.

Verified: the base address (table 5.3-2) and every offset and bit in the table below, against the ESP32-C6 Technical Reference Manual v1.2, section 35.4 (register summary) and registers 35.1, 35.2, 35.8 to 35.14 and 35.23. An earlier version of this model used the C3's LEDC_CONF_REG at 0xD0 and LEDC_DATE_REG at 0xFC, the C3's fourteen-bit HPOINT, nineteen-bit DUTY and fourteen-bit resolution limit, and wrote LEDC_SCLK_SEL = 1, which on the C6 selects RC_FAST_CLK rather than the 80 MHz PLL clock. The C6's event-task, compare, capture and gamma-RAM registers (0x100 to 0x1CC) are not modeled.

offset name bits
0x00 + 0x14n LEDC_CHn_CONF0_REG bits 0..1 TIMER_SEL, bit 2 SIG_OUT_EN, bit 3 IDLE_LV, bit 4 PARA_UP (stored; a duty written here takes effect at once)
0x04 + 0x14n LEDC_CHn_HPOINT_REG bits 0..19 HPOINT: the count the channel's pulse starts at
0x08 + 0x14n LEDC_CHn_DUTY_REG bits 0..24 DUTY: the pulse width in sixteenths of a count
0x0C + 0x14n LEDC_CHn_CONF1_REG bit 31 DUTY_START, the duty-fade trigger: stored, read back, and fades nothing
0x10 + 0x14n LEDC_CHn_DUTY_R_REG the duty as it stands, read only
0xA0 + 8n LEDC_TIMERn_CONF_REG bits 0..4 DUTY_RES (1 to 20; 0 is a timer nobody has configured and it counts nothing), bits 5..22 CLK_DIV, bit 23 PAUSE, bit 24 RST, bit 26 PARA_UP (stored); bit 25 is reserved
0xA4 + 8n LEDC_TIMERn_VALUE_REG the counter, read only
0xC0 LEDC_INT_RAW_REG bit n = timer n overflowed
0xC4 LEDC_INT_ST_REG raw and enabled
0xC8 LEDC_INT_ENA_REG stored; no line reaches the core
0xCC LEDC_INT_CLR_REG write 1 to clear
0x1F0 LEDC_CONF_REG bits 0..1 SCLK_SEL (0 = the 80 MHz PLL_F80M_CLK, the only source modeled; 1 RC_FAST_CLK, 2 XTAL_CLK; stored), bits 2..7 the gamma-RAM clock gates (stored), bit 31 CLK_EN (stored)
0x1FC LEDC_DATE_REG a version stamp, read only: 0x0211_1150

A channel reaches a pad through the GPIO matrix: set the pad's GPIO_ENABLE bit and point its GPIO_FUNCn_OUT_SEL_CFG_REG at 45 + channel. Those signal indices, 45 to 50 for ledc_ls_sig_out0 to ledc_ls_sig_out5, are unverified too. Section 3.1's OUT_SEL of 128 still means the pad follows GPIO_OUT, and any value that is neither 128 nor an LEDC channel is treated as 128 rather than invented.

The counter is not stepped: it is a closed-form function of simulated time and so is the time of its next edge, so a core parked in wfi costs nothing while a PWM runs and the board is woken for the edges and for nothing else.

Not modeled: the high-speed group (this chip has none), the duty-fade and gamma hardware, clock sources other than the 80 MHz PLL clock, and the LEDC interrupt line into the interrupt matrix: LEDC_INT_RAW_REG can be polled and cleared but never reaches mip.

3.8 Mokxi network mailbox (0x6FFF_E000)

This block is not the ESP32-C6's. Every other address in this document is the chip's, believed or quoted as section 7 sorts it, and an image built against it is an image for a DevKitC-1. This one is Mokxi's own: the simulated network of crates/net, mapped at an address the real part leaves unmapped (the C6's peripherals sit in the bottom of the 0x6000_xxxx window and in 0x6009_xxxx, and nothing answers at the top of it), so that an image using it faults on silicon rather than reading some other peripheral's register. That fault is the honest answer, because the thing behind this block does not exist on the chip. It is the same address the ESP32-C3 uses, because both parts leave the same place empty.

There is no radio, no 802.11 and no real internet here. Nothing in the model transmits, scans, associates, encrypts or opens a socket: a password is compared as a string, a name that is not under .mokxi does not resolve, and the three origins the network serves are functions in crates/net/src/http.rs. The mailbox is the shape of the real seam rather than a fiction about a MAC: on a real ESP32 the WiFi hardware is a closed binary blob with a command interface, and Arduino's WiFi.h ends up on the other side of one of these. docs/wifi.md is the contract for what is behind it; this section is only the registers.

A command register, a status register, a 4096-byte ring each way and an interrupt bit. Writing NET_CMD_REG runs the command; a command that takes simulated time raises BUSY and clears it, with DONE in NET_INT_RAW_REG, when it is finished.

offset name bits
0x00 NET_ID_REG reads 0x494B4F4D, and nothing else does. A sketch that wants to know whether it is on Mokxi reads this; on silicon the access faults
0x04 NET_CMD_REG write a command code (table below); the write runs it
0x08 NET_ARG_REG one argument word, read back: the port for LISTEN, the status for REPLY
0x0C NET_STATUS_REG bit 0 BUSY, bit 1 ERR, bit 2 RX_READY, bit 3 LINK, bit 4 SERVE, bits 8..11 STATE
0x10 NET_RESULT_REG what the last command answered
0x14 NET_INT_RAW_REG bit 0 DONE, bit 1 EVENT
0x18 NET_INT_ENA_REG the same two bits
0x1C NET_INT_CLR_REG write 1 to clear, the same two bits
0x20 NET_TX_LEN_REG bytes staged in the outbound ring
0x24 NET_TX_DATA_REG write: push one byte (bits 0..7) into the outbound ring
0x28 NET_RX_LEN_REG bytes waiting in the inbound ring
0x2C NET_RX_DATA_REG read: pop one byte from the inbound ring, 0 when it is empty
0x30 NET_RING_LEN_REG capacity of each ring in bytes, so firmware need not hard-code 4096

STATE is where the station is: 0 idle, 1 starting, 2 connecting, 3 connected, 4 failed, 5 no such network. NET_CMD_STATUS turns the same thing into the Arduino wl_status_t code a sketch reads from WiFi.status().

NET_CMD_REG name what it does
0 NOP nothing
1 RESET disconnect, drop both rings, drop any request
2 CONNECT the outbound ring holds ssid\0password; raises BUSY for one to three seconds
3 DISCONNECT drop the association and the address
4 STATUS NET_RESULT_REG = the wl_status_t code
5 IP NET_RESULT_REG = the leased address, first octet in the low byte
6 RSSI NET_RESULT_REG = the signal in dBm, sign extended
7 SSID the inbound ring gets the access point's name
8 MAC the inbound ring gets the board's six MAC bytes
9 REQUEST the outbound ring holds method\0url\0headers\0body; raises BUSY
10 POLL NET_RESULT_REG = 0 in flight, then the HTTP status, or a negative error with ERR up; the body lands in the inbound ring
11 LISTEN listen on the port in NET_ARG_REG
12 ACCEPT NET_RESULT_REG = 1 and the inbound ring holds method\0path\0query\0body, or 0 when nothing is waiting
13 REPLY answer the accepted request with the status in NET_ARG_REG; the outbound ring holds content-type\0body
14 STOP stop listening
15 WL_GPIO_SET drive the wireless part's own GPIOs from NET_ARG_REG. Not this board: this part's radio has no such pin, so it sets ERR and drives nothing. It is the path a Pico W's on-board LED takes (docs/picow.md section 3.8)
16 WL_GPIO_GET read them back, and the same ERR here

The negative POLL codes: -1 the name does not resolve, -2 the station is not associated, -3 https:// and TLS is not simulated, -4 a body longer than the ring, -5 a URL this network cannot parse, -6 a request is already in flight. firmware/include/HTTPClient.h turns them into the Arduino-shaped codes a sketch compares against, and docs/wifi.md lists the mapping.

Timing, all of it this model's own and all of it stated in docs/wifi.md: an association takes between one and three seconds, decided from the SSID so a lesson takes the same time twice; a wrong password becomes WL_CONNECT_FAILED after five seconds; a request to a simulated origin comes back 60 ms later; a request into the sketch's own server that the sketch never answers is closed by the network with a 504 after ten seconds.

4. Host channel

host_write puts bytes in UART0's receive FIFO; host_read takes the bytes that have left its transmit FIFO. That is Serial in a sketch and the serial monitor in the UI. Bytes are UTF-8 as far as the UI is concerned and opaque to the model.

A DevKitC-1 has two USB-C sockets: one to a USB-to-UART bridge, which is this channel, and one to the chip's own USB, which is not modeled at all.

5. Time model

As docs/esp32c3.md section 5. The HP core runs at 160 MHz, one instruction per cycle, so an instruction is exactly 6250 ps and a 10 us slice is exactly 1600 instructions. Inside a slice the instruction count is the clock: a SYSTIMER read, a UART byte and a pin change all land at the instruction that caused them.

probe is 1 while the firmware runs, 0.5 parked in wfi, 0 halted (no program, held in reset, or crashed).

poke: 1 holds BOOT (GPIO 9 low), 0 releases it; 2 holds RST, 3 releases it.

6. Firmware runtime (firmware/c6/)

An Arduino-shaped runtime derived from the C3's: setup() and loop(), pinMode, digitalWrite, digitalRead, analogRead (with analogReadResolution, analogSetAttenuation and analogSetPinAttenuation), analogWrite and the ledcAttach/ledcWrite pair, millis, micros, delay, delayMicroseconds, attachInterrupt, Serial. No Arduino core, no ESP-IDF, no libc.

analogRead is ADC1 one-shot on GPIO 0 to GPIO 6 (section 3.6), twelve bits at 11 dB by default, so firmware/lib/mokxi_joystick.h compiles and works here as it does on the C3. analogWrite(pin, 0..255) is eight bits at 1 kHz on any GPIO and attaches a free LEDC channel the first time it is called; ledcAttach(pin, frequency, bits) is what a sketch wants when the frequency matters, and channels asking for the same frequency and resolution share one of the four timers.

Built with clang --target=riscv32-unknown-elf -march=rv32imc -mabi=ilp32, linked against firmware/c6/link.ld. Three of the runtime's files are the C3's, compiled again for this board rather than copied: the number formatting, the math helpers and the freestanding floor, plus Print and main. This board's own are the register header, the pin count, the startup code, the linker script, and the GPIO, ADC, PWM, time, UART and interrupt drivers.

The catalog program string is elf32 riscv32imc esp32c6. It differs from the C3's because the ELF really does: same instruction set, different memory map, and an image linked for one will not run on the other.

LED_BUILTIN is 8, which on this board is the addressable RGB LED. Driving it high and low still does the useful thing on a breadboard; the rainbow example is what the pin is really for.

Six examples: blink, button (the BOOT button on GPIO 9), serial, fade (an LED on GPIO 10 breathing on hardware PWM), stick (a KY-023 on ADC1 channels 0 and 1, with the brightness of two LEDs going back out through LEDC) and rainbow (a hue wheel bit-banged onto the RGB LED and seven more on GPIO 8). An example is budgeted at 16 KB.

7. What this model is sure of, and what it is not

Three groups. This section exists because a board model that quietly guesses an address is worse than one that says it did not know.

Taken from the technical reference manual. Every base address in section 2 (the ESP32-C6 Technical Reference Manual v1.2, table 5.3-2): UART0_BASE = 0x6000_0000, LEDC_BASE = 0x6000_7000, SYSTIMER_BASE = 0x6000_A000, APB_SARADC_BASE = 0x6000_E000, IOMUX_BASE = 0x6009_0000 and GPIO_BASE = 0x6009_1000. Every register offset inside the UART, GPIO and IO_MUX blocks. The UART's FIFO-reset bits and CONF1 thresholds (registers 27.9 and 27.10), the SYSTIMER comparator (chapter 13) and every LEDC register in section 3.7 (chapter 35), all checked against that manual while the ESP32-S3 was being added; the C6 differs from the C3 in each of them, and each was wrong here before that check and has been corrected, with the runtime's images rebuilt. The GPIO range, 0 to 30. The HP SRAM's 512 KB at 0x4080_0000, and the fact that it is one window rather than two. The RGB LED on GPIO 8 and the BOOT button on GPIO 9. ADC1's channel map: channel n is GPIO n for n in 0 to 6.

Believed, not verified. The register offsets inside APB_SARADC, which are the C3's (section 3.6), and the GPIO matrix signal indices 45 to 50 for ledc_ls_sig_out0 to ledc_ls_sig_out5. Both are named in one place, firmware/c6/include/esp32c6.h, so an image being taken to real silicon has a short list of #defines to check. Firmware built here agrees with the model whether or not it agrees with silicon.

Deliberately absent. Each of these was left out rather than invented, because its register layout is not something this model can reproduce honestly:

  • The timer groups (TIMG0 and TIMG1). The C6 has two, with watchdogs. Time here comes from SYSTIMER, which is enough for millis, micros and a parked delay.
  • RMT, which is what would normally drive the RGB LED. The rainbow example bit-bangs it instead, and that is not a shortcut: it is the only way here.
  • The LP core and its peripherals, including LP_UART and LP_I2C. The C6's low-power domain is half of what makes the part interesting and none of it is here.
  • The interrupt matrix, the PLIC-shaped routing that decides which peripheral reaches which mcause. Section 3.5 says what stands in for it.
  • ADC2, the SAR ADC's DMA and digital controller, its filters and threshold monitors and its calibration eFuses. Section 3.6 is ADC1 one-shot and nothing else.
  • LEDC's duty-fade hardware and its interrupt line: section 3.7 is the timers, the channels and the output, and LEDC_INT_RAW_REG is polled rather than delivered.
  • The PMU and the clock tree, the cache, the ROM, flash, SPI, I2C, I2S, USB Serial/JTAG, WiFi 6, Bluetooth LE and 802.15.4.

That is why vendor ESP-IDF and Arduino core binaries do not run here, and firmware built for this document does.