Skip to main content

Peripheral implementation matrix

Every peripheral in the Rust model (stm32-periph-wasm/src/peripherals/), its register coverage, behavior level, and how it's exercised. Line counts from wc -l (all files, as of 2026-08-09).

Levels

  • Detailed — real register semantics and real behavior (data movement, state machines, interrupts, real algorithms).
  • Partial — registers modeled but with simplified or missing behavior (constants, canned data, no hardware path).

Legend — IRQ: generates NVIC interrupts; tick: implements fn tick(); ext: connects to external devices (SPI flash, EEPROM, display, ...).

Summary

Count
Modules33
Detailed28
Partial5
Stub / passthrough-only0

The matrix

PeripheralLocLevelWhat's implementedIRQtickext
ADC183DetailedSR/CR1/CR2/SMPR1-2/JOFR/HTR/LTR/SQR1-3/JSQR/JDR/DR; SWSTART-gated conversion, real sampling-time logic (SMPR lookup), channel values (16/17 = temp/Vref, 18 = Vbat, others LCG pseudo-random), EOC/OVR flagsY (EOC/OVR → 18/47)NN
CAN494DetailedMCR/MSR/TSR/RF0R/RF1R/IER/ESR/BTR; 3 TX + 2 RX mailboxes, filter banks (FMR/FM1R/FS1R/FFA1R/FA1R + 56 filter words), TX request → TXOK/RQCP, RX FIFO release decrements FMP, INIT/SLEEP transitions; two-node bus arbitration (lowest ID wins, ties by node/mailbox), winner broadcasts to every RX passing its filters (including itself), BTR LBKM loopback delivers only to the senderY (TX/RX/SCE → 19/63)NN
CRC39DetailedReal CRC-32 (poly 0x04C11DB7, MSB-first), accumulated on DR writes; DR/IDR/CR, RESET → 0xFFFFFFFFNNN
CRYP628DetailedFull crypto core via real aes/des crates: AES-128/192/256 ECB/CBC/CTR, DES + 3DES (EDE3), GCM (GHASH + GF(2¹²⁸) + CTR + tag), CCM (CBC-MAC + CTR); 64-byte FIFO with IFEM/IFNF/OFNE/OFFU/BUSY, datatype byte-swapY (79: OFNE/IFNF)NN
DAC135DetailedCR/SWTRIGR/all DHR regs/DOR1-2/SR; DOR update on trigger + writes, LFSR noise, triangle-waveform counter/direction, MAMP masksNNN
DBGMCU37PartialIDCODE constant 0x10006411; CR/APB1FZ/APB2FZ stored/maskedNNN
DCMI81PartialCR/SR/RIS/IER/ICR/ESCR/ESUR/CWSTRT/CWSIZ/DR; consumes a JS-fed camera frame with VSYNC/LINE/FRAME/OVR semantics and a 4-deep FIFO. Source: ext_devices.camera (pumped every step) or emu.camera.feed(w,h,pixels); guest-side coverage: dcmi_testY (78)NN
DMA212DetailedLISR/HISR/IFCR + 8 streams (CR/NDTR/PAR/M0AR/M1AR/FCR); EN queues a DmaTransfer (copy done by JS via dma_get_pending/dma_set_completed), TCIF/HTIF, stream IRQ, double-buffer M1AR, dir/mem2memY (11-18, 56-63)NN
ETH311DetailedAll 4 blocks (MAC/MMC/PTP/DMA) full register maps; PHY emulation (MIIAR read → fixed PHY regs), write-1-clear DMASR, DMAIER masking, AIS/NIS, TX/RX done → TS/RS bits, DMABMR soft-reset, DMAOMR ST/SR → eth_signal_tx/rx_poll atomicsY (61)YN
EXTI70DetailedIMR/EMR/RTSR/FTSR/SWIER/PR with correct line→IRQ mapping; GPIO edge-trigger path: scan_lines per tick compares GPIO line levels vs last_state, RTSR/FTSR gating, line→IRQ map (0-4→6-10, 5-9→23, 10-15→40)Y (6-10/23/40)NN
FLASH80DetailedACR, KEYR two-step unlock (0x45670123→0xCDEF89AB), OPTKEYR, SR write-1-clear, CR + LOCK/PSIZE, OPTCR1; program (PG) and sector-erase (SER) dispatch into the emulated flash backing buffer via flash_erase_applied (JS driver gated on syncFlashProtection)NNN
FSMC100Partial4 banks 0x60000000-0xA0001000; BCR/BTR/PCR stored per bank; data-space accesses forward to a JS device via the bank tap (fsmc_tap / ext_devices.fsmcDevices), which reports the ACCESS ADDRESS as well as the value so an 8080-mode display can decode its RS/DC line. An untapped bank reads 0; guest-side coverage: fsmc_testNNN
GPIO229DetailedMODER/OTYPER/OSPEEDR/PUPDR/IDR/ODR/BSRR/LCKR/AFRL/AFRH; ODR/BSRR drive output_state + write callbacks, MODER→input clears output, IDR = input_state + read callbacks, 11 ports (A-K)NNY
HASH164DetailedReal SHA-1/MD5/SHA-256 (sha1/md5/sha2 crates) with NBLW/length handling; CR/INIT, DIN FIFO, STR, HR + HASH_HR, IMR/SR, 54 CSR wordsY (80)NN
I2C204DetailedFull master state machine (Idle→Start→Addr→Active read/write), START/STOP, SWRST, address match vs attached EEPROMs (NACK/AF on miss), byte R/W, SR1/SR2 ordering semantics, event/buffer/error IRQ maskingY (31-34, 72-73)NY
I2S150DetailedCR1/CR2/SR/DR/CRC/I2SCFGR/I2SPR; DR reads consume WAV-backed PCM16 audio (audio_load_wav) with fallback to a synthetic generator, DR writes push into a capture FIFO (audio_take_capture), SR flagsY (35/36/51)NN
IWDG70DetailedKR keys (0x5555 write-enable, 0xAAAA reload, 0xCCCC start), PR prescaler, RLR; instruction-count driven; underflow → request_watchdog_reset(); SR PVU/RVUreset (not IRQ)N (self-timed)N
LTDC260DetailedGlobal regs + 2 layers (CR/whpcr/wvpcr/ckcr/pfcr/cacr/dccr/bfcr/cfbar/cfblr/cfblnr/clutwr); real scanline/frame advance from SSCR/BPCR/AWCR geometry (ltdc_get_scanline/ltdc_get_frame_count), LIF at LIPCR + frame-end F flag, IRQ 88; browser console renders layer0 (ARGB8888/RGB565) to a canvasY (88)NN
NVIC226DetailedISER/ICER/ISPR/ICPR/IABR/priority; u128 pending mask, enable/active arrays; set_intr_pending sets pending only (no auto-enable — pending is delivered only when ISER is set, and disabled pending stays set until taken or ICPR); get_and_clear_next_intr_pending delivers the highest-priority enabled IRQ, skips disabled ones without clearing; has_pending reflects deliverable-only; ICSR VECTPENDING returns the exception vector number; SysTick periodic pending; in_interruptcontrollervia System::tickN
PWR47PartialCR/CSR masked storage, WUF on valid wakeup write patternNNN
RCC221DetailedCR/PLLCFGR/CFGR/CIR/reset-enable/low-power/BDCR/CSR/SSCG/PLLI2SCFGR/PLLSAI/DCKCFGR/CKGATENR/DCKCFGR2; HSE/PLL ready after instruction-count delays, HSIRDY, SWS mirrors SW, real freq math (system/pll/ahb/apb1/apb2), enable-bit gating map, LSE/LSI ready timingNNN
RNG85DetailedCR/SR/DR; LCG pseudo-random 32-bit values regenerated every 40 inst, DRDY, error flags + IRQY (80)NN
RTC198DetailedTR/DR/CR/ISR/PRER/WUTR/CALIBR/ALRMAR/ALRMBR/WPR/SSR/SHIFTR/timestamp/CALR/TAFCR/ALRMASSR/ALRMBSSR + 20 backup regs; BCD time advances via PRER prescaler vs instruction count, alarm A/B matching with don't-care masksY (43)N (advances on access)N
SAI106DetailedGCR + 2 blocks (CR1/CR2/FRCR/SLOTR/IM/SR/CLRFR/DR); shares the WAV-backed audio path with I2S (block routes DR to audio when I2SMOD is set), SR flags on DR access, masked interruptY (87)NN
SCB130DetailedCPUID (Cortex-M4 r0p1), ICSR set/clear pending (PendSV/SysTick) + pending-vector report, AIRCR VECTKEY + SYSRESETREQ → request_watchdog_reset(), VTOR, SCR/CCR/SHPR/SHCSR/CFSR/HFSR/DFSR/MMFAR/BFAR/AFSR/CPACRindirect (PendSV/SysTick)NN
SDIO143DetailedFull register set + emulated SD card state machine (Idle→Ident→Stby→Tran), canned responses for CMD0/2/3/5/7/8/9/10/13/16/17/18/41/55, RCA matching, data-transfer simulation (DCOUNT/FIFOCNT, CMD17/18), status flags + ICR/MASKY (49)NN
SPI157DetailedCR1/CR2/SR/DR/RXCRC/TXCRC/I2SCFGR/I2SPR; full-duplex 8/16-bit transfers to attached device with CS selection via GPIO, I2S mode audio generation, TXE/RXNE toggling; CS edges delivered via GPIO write callbacks (register_cs_callbacks, sw_spi pattern) so attached-device CS deassert is observed immediatelyY (35/36/51)NY
SW_SPI105DetailedBit-banged SPI via GPIO callbacks (CS/CLK/MOSI/MISO): shift register, 8-bit framing, forwards bytes to attached device; CS edge resetsNNY
SYSCFG52PartialMEMRM/PMC/EXTICR[4]/CMPCR masked storage; CMPCR read toggles COMP bitNNN
SYSTICK55DetailedCSR/RVR/CVR/CALIB; enabling programs nvic.systick_period → periodic SYSTICK pending from System::tick; CVR write resets trigger pointindirect (SYSTICK)NN
TIM243DetailedCR1/CR2/SMCR/DIER/SR/EGR/CCMR1-3/CCER/CNT/PSC/ARR/CCR1-6/RCR/DCR/DMAR/OR; instruction-count counter with PSC prescaler, up/down/center-aligned, UIF+UIE IRQ on overflow, CC match IRQs, UG update event, PWM dutyY (per-timer)YN
USART126DetailedSR/DR/BRR/CR1-3/GTPR; TX pushes to global UART output buffer (drained by get_uart_output), RX via rx_byte (JS uart_rx_byte) with 64-byte RX buffer, RXNE/ORE, TXE/TC always set; IRQ on RXNEIE/TCIE/TXEIEY (37/38/39/52/53/71/82/83)NN
WWDG81DetailedCR/CFR/SR; countdown at 256×prescaler instructions, early-wakeup flag + IRQ (EWI), underflow + WDGA → request_watchdog_reset()Y (0)N (self-timed)N

External devices (stm32-periph-wasm/src/ext_devices/)

ext_devices/{lcd,touchscreen,usart_probe,display}.rs (protocol-specific device models) were removed in commit 13d7cdb in favor of two generic, protocol-agnostic bus taps — real device behavior now lives in JS on top of them (site/emulator.js's oled/tft/rtc blocks implement the SSD1306/ILI9341/DS3231 protocols client-side; see components.md for the public attachment API built on the same taps).

DeviceLinesBehavior
SpiFlash399SPI NOR flash: JEDEC ID (0x9F), device ID (0x90), status regs (0x05/0x35), ReadData (0x03)/FastRead (0x0B) streaming, WriteEnable (0x06), PageProgram (0x02), SectorErase4k (0x20) with CS-deassert commit (program ANDs bits, erase sets 0xFF, WEL auto-clears after program/erase like real W25Q). MISO timing: dummy_pending returns 0 while the command+address bytes are clocked in (one dummy per written byte), so the first real data byte appears only on the byte after the address phase
I2cEeprom78I²C EEPROM: 1/2-byte address phase, sequential byte read/write into RAM copy
I2cRegfile139Pointer-addressed I²C register file (DS3231 RTC): auto-increment pointer, JS seeds/reads registers via i2c_regfile_get/set
SpiTap54Generic protocol-agnostic SPI slave: CS/DC edges + bytes queued for JS (spi_take_events), JS answers reads via spi_push_miso — the oled/tft/custom-device bridge
I2cTap38Generic protocol-agnostic I²C slave: START/STOP + bytes queued for JS (i2c_take_events), JS answers reads via i2c_push_rx

How the matrix is exercised

  • UART + GPIO + CRC + HASH + CRYP: echo_test, blink_serial, rx_interrupt_test, rx_crypto_test, crypto_test, crypto_deep_test — all pass headless with real interrupt handlers (node site/test_rx_interrupt.mjs).
  • TIM + RCC + NVIC + IWDG: timer_test, hal_test, periph_test, deep_periph_test, new_periph_test.
  • SPI + FSMC + SDIO + DCMI + LTDC + I2C + CAN + ADC + DAC + RNG + RTC: spi_tft_test, comprehensive_test, edge_test, saturn/monox printer firmwares (upstream heritage).
  • ETH: eth_http, eth_dhcp, eth_test — full DHCP/TCP/HTTP flows (see benchmarks.md).

Probe all firmware binaries with node site/probe_firmwares.mjs; each prints a banner over UART when it boots.

Known gaps / model shortcuts

  • NVIC pending is set by peripherals regardless of ISER (hardware-accurate); the pump only ever runs enabled IRQs.
  • USART ignores its ext-device argument (uses the global UART buffer); the UsartProbe device is wired through the SPI lookup instead.
  • DMA peripheral-side accesses are chunked in Rust (dma_periph_read/ dma_periph_write — one WASM call per transfer instead of size/4); RAM-to-RAM copies still run in the JS driver because guest memory lives in Unicorn, not in the Rust model.
  • FLASH programs/erases emulated flash; DCMI consumes JS-fed camera frames (no pixel source). LTDC scanout, SAI/I2S WAV-backed audio, and CAN two-node bus arbitration are implemented (see rows above).
  • Timers/ADC/RNG/RTC/IWDG/WWDG are instruction-count driven, not wall-clock driven (deterministic across machines).