| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
A from-scratch emulator for Raspberry Pi RP2040 and RP2350 microcontrollers, supporting both ARM Cortex-M0+ (Thumb) and RISC-V Hazard3 (RV32IMAC) cores. Loads and executes UF2 and ELF firmware with accurate memory mapping and peripheral emulation.
319 tests passing (zero warnings). RP2040: Complete — boots MicroPython, CircuitPython, littleOS. RP2350 RISC-V: Complete Hazard3 emulation with Zba, Zbb, Zbs, Zcb, Zcmp, and Zbkb extensions. Boots MicroPython Pico 2 RISC-V and SagePico REPL with full semihosting I/O. RP2350 ARM: Cortex-M33 mode (-arch m33) with RP2350 ROM format and clock-domain peripheral address mapping. Boots to TinyUSB init. Tri-architecture: -arch m0+ / -arch m33 / -arch rv32 with automatic firmware detection via UF2 family ID and picobin IMAGE_DEF blocks. Networking: Virtual network bus with TAP bridge, multi-instance Ethernet mesh, W5500 live sockets, and software-defined devices.
| Area | Status | Details |
|---|---|---|
| RP2040 CPU | 65+ instructions | Full Thumb-1 + BL/MSR/MRS/DSB/DMB/ISB, O(1) dispatch, NZCV flags |
| RP2350 RV | Complete | Hazard3: 130+ instructions (RV32IMAC + Zba/Zbb/Zbs/Zcb/Zcmp), Hazard3 CSRs, CLINT, SDK bootrom, icache, GDB, semihosting |
| RP2350 ARM | Complete | Cortex-M33 (-arch m33): full Thumb-2 via existing engine, BASEPRI, M33 CPUID, UF2 auto-detect |
| RP2350 Peripherals | Complete | TICKS, POWMAN, QMI, OTP+data, BOOTRAM, TIMER1, PIO2, GLITCH, CORESIGHT, ACCESSCTRL, 48 GPIO, SIO |
| Dual-Core | Complete | RP2040: host-threaded, WFI, FIFO, spinlocks, auto-launch. RP2350: cooperative dual-hart with CLINT + SIO mailbox launch |
| Memory Map | 100% | RP2040: Flash + XIP + SRAM + ROM (16KB) + all peripherals. RP2350: 520KB SRAM + 32KB ROM + CLINT + all RP2350 peripherals |
| Boot | Complete | RP2040: vector table, boot2, ROM functions. RP2350: RISC-V bootrom (SP init, flash jump), picobin IMAGE_DEF parser |
| Exceptions | 100% | ARM: tail-chaining, late-arriving, PRIMASK + FAULTMASK. RISC-V: mtvec direct/vectored, MRET, MIE/MPIE, Hazard3 ext IRQ routing |
| Timing | Cycle-accurate | Configurable clock (-clock 125/-clock 150), ARMv6-M instruction costs, CLINT mtime, TIMER1 |
| Debugging | GDB RSP | Breakpoints, watchpoints, conditional breakpoints, dual-core threads (-gdb), architecture-aware registers |
| Flash | Write-through + FUSE | -flash <path> with sync; -mount <dir> for live host access (thread-safe) |
| Storage | SD card + eMMC | SPI-attached file-backed block devices |
| WiFi | CYW43 (Pico W) | gSPI-over-PIO, TAP bridge with auto IP/NAT (-wifi, -tap) |
| Virtual Network | VNet bus | Central Ethernet frame router, TAP/NAT bridge (-net), peer mesh (-net-peer), W5500 live sockets (-net-live) |
| Multi-Device | Wire + SDD | Wire UART/GPIO/Ethernet between instances, pluggable software-defined devices (-sdd) |
| Performance | ICache + JIT | 64K decoded cache by default, optional hot-block JIT (-jit) |
| Privilege | Auto-sudo | -tap, -net, -mount auto-escalate via sudo when needed |
| Dev Tools | 18 tools | Semihosting, coverage, hotspots, profile, trace, callgraph, VCD, IRQ latency, stack check, bus logging, watch, expect, script, fault injection, heatmap, symbols, exit codes, timeouts |
| Firmware Auto-Detect | UF2 + ELF | Auto-detects RP2040/RP2350-ARM/RP2350-RV from UF2 family ID or ELF machine type |
| RV Performance | ICache | 64K-entry decoded instruction cache for flash/ROM fetches |
| RV Semihosting | EBREAK | Full ARM semihosting protocol: SYS_WRITE0, SYS_WRITEC, SYS_WRITE, SYS_READC, SYS_EXIT, etc. via EBREAK |
| Tests | 319 | CTest integrated, 57+ categories (20 RV + 4 M33 + 19 networking tests) |
| Peripheral | Address | Emulation Level |
|---|---|---|
| GPIO | 0x40014000 / 0xD0000000 | Full (30 pins, SIO, IO_BANK0, PADS, edge/level interrupts) |
| UART | 0x40034000 / 0x40038000 | Full (dual PL011, Tx+Rx, 16-deep FIFO, active-console stdin routing) |
| SPI | 0x4003C000 / 0x40040000 | Full (dual PL022, 8-deep TX/RX FIFOs, device callbacks) |
| I2C | 0x40044000 / 0x40048000 | Full (dual DW_apb_i2c, 16-deep RX FIFO, device callbacks) |
| Timer | 0x40054000 | Full (64-bit counter, 4 alarms, interrupts) |
| PWM | 0x40050000 | Full (8 slices, CSR/DIV/CTR/CC/TOP, interrupts) |
| ADC | 0x4004C000 | Full (5 channels, temp sensor, FIFO, round-robin) |
| DMA | 0x50000000 | Full (12 channels, chaining, 4 alias layouts) |
| PIO | 0x50200000 / 0x50300000 | Full (2 blocks, all 9 opcodes, FIFOs, clock divider) |
| SysTick | 0xE000E010 | Full (CSR/RVR/CVR/CALIB, TICKINT, COUNTFLAG) |
| NVIC | 0xE000E100 | Full (priority preemption, 4 levels, SCB_SHPR) |
| Resets | 0x4000C000 | Full (reset/unreset, RESET_DONE tracking) |
| Clocks | 0x40008000 | Full (10 generators, FC0 dynamic freq, SELECTED) |
| XOSC/PLLs | 0x40024000 | Full (STATUS.STABLE, CS.LOCK) |
| Watchdog | 0x40058000 | Full (CTRL, TICK, SCRATCH[0-7], reboot with full multicore state reset) |
| SIO | 0xD0000000 | Full (GPIO, FIFO, spinlocks, hardware divider, interpolators) |
| ROM | 0x00000000 | Full (16KB, function table, soft-float/double, flash write) |
| USB | 0x50110000 | Full (host enumeration, CDC data bridge, stdio_usb, multi-packet IN) |
| SYSINFO | 0x40000000 | Stub (CHIP_ID=RP2040-B2, PLATFORM=ASIC) |
| SYSCFG | 0x40004000 | Full (NMI mask, proc config, debug force, mem power-down) |
| TBMAN | 0x4006C000 | Full (PLATFORM=ASIC, testbench manager) |
| VREG | 0x40064000 | Full (VREG EN/VSEL/ROK, BOD EN/VSEL, CHIP_RESET W1C flags) |
| IO_QSPI | 0x40018000 | Stub (6 QSPI GPIO pins, STATUS/CTRL) |
| PADS_QSPI | 0x40020000 | Stub (QSPI pad electrical control) |
| ROSC | 0x40060000 | Full (STATUS, RANDOMBIT LFSR, CTRL enable) |
| RTC | 0x4005C000 | Full (LOAD strobe, calendar rollover, leap year, ticking) |
| XIP Cache | 0x14000000 | Stub (always ready) + 16KB XIP SRAM |
| CYW43 | Pico W via PIO/SPI | Functional (scan/connect path, WLAN framing, TAP bridge) |
| Device | Interface | Details |
|---|---|---|
| SD Card (SDHC) | SPI (default SPI1) | Full SPI-mode protocol, CSD v2.0, single/multi-block R/W, file-backed |
| eMMC | SPI (default SPI0) | CMD1 init, EXT_CSD, sector addressing, file-backed |
Both devices attach via spi_attach_device() callbacks with periodic flush and flush-on-exit.
All peripherals support RP2040 atomic register aliases (SET/CLR/XOR).
./build.shThis builds the bramble executable in the project root.
You can also build explicitly with CMake:
cmake -S . -B build
cmake --build build -jBramble builds with dual-core support enabled by default. Select the active cores at runtime:
./bramble firmware.uf2 -cores 1
./bramble firmware.uf2 -cores 2
./bramble firmware.uf2 -cores autoHello World (prints "Hello from ASM!"):
cd test-firmware
chmod +x build.sh
./build.sh hello_worldGPIO Test (toggles LED on GPIO 25):
cd test-firmware
./build.sh gpioTimer Test (measures elapsed time):
cd test-firmware
./build.sh timerAlarm Test (tests timer alarms):
cd test-firmware
./build.sh alarmInteractive UART Prompt Test (reads host stdin via -stdin and prints a greeting):
cd test-firmware
./build.sh name_promptBuild All Tests:
cd test-firmware
./build.sh allUF2 Firmware:
./bramble hello_world.uf2
./bramble gpio_test.uf2
./bramble timer_test.uf2
./bramble alarm_test.uf2
./bramble name_prompt.uf2 -stdin
printf 'Ada\n' | ./bramble name_prompt.uf2 -stdinELF Firmware (auto-detected by extension):
./bramble firmware.elfctest --test-dir build --output-on-failureBramble now supports flexible debug output modes:
Single-Core CPU Step Tracing (verbose CPU and peripheral logging):
./bramble -debug timer_test.uf2Assembly Instruction Tracing (detailed POP/BX/branch operations):
./bramble -asm alarm_test.uf2Combined Debug + Assembly Tracing:
./bramble -debug -asm alarm_test.uf2No Debug Output:
./bramble hello_world.uf2Dual-Core Specific:
./bramble firmware.uf2 -debug # Core 0 debug output
./bramble firmware.uf2 -debug -debug1 # Both cores debug
./bramble firmware.uf2 -status # Periodic status updates
./bramble firmware.uf2 -debug -status # Debug + status combined
./bramble firmware.uf2 -stdin # Route stdin to USB CDC when active, else UART0
./bramble firmware.uf2 -gdb # Start GDB server on port 3333
./bramble firmware.uf2 -gdb 4444 # GDB server on custom port
./bramble firmware.uf2 -clock 125 # Real RP2040 timing (125 MHz)
./bramble firmware.uf2 -flash fs.bin # Persistent flash storage
./bramble firmware.uf2 -debug-mem # Log unmapped peripheral access
./bramble firmware.uf2 -jit # Enable JIT for hot flash/ROM loops
./bramble firmware.uf2 -cores 2 -thread-quantum 128 # Tune threaded timesliceRP2350 RISC-V Mode:
# Explicit architecture selection
./bramble firmware_rv.uf2 -arch rv32
# Auto-detected from UF2 family ID (0xE48BFF5A) or ELF machine type
./bramble pico2_rv_firmware.uf2
# With clock speed and flash persistence
./bramble firmware_rv.uf2 -arch rv32 -clock 150 -flash rv_flash.bin -stdinNetworking (UART-to-TCP bridge):
# Bridge UART0 to TCP port (connect with nc, minicom, etc.)
./bramble firmware.uf2 -net-uart0 9999 -stdin
# In another terminal: nc localhost 9999
# Connect UART0 to a remote host
./bramble firmware.uf2 -net-uart0-connect 192.168.1.10:9999Multi-Device Wiring (inter-instance communication):
# Terminal 1: Instance A with UART0 wired via Unix socket
./bramble fw_sensor.uf2 -wire-uart0 /tmp/uart_link.sock -stdin
# Terminal 2: Instance B with UART0 wired to the same socket
./bramble fw_controller.uf2 -wire-uart0 /tmp/uart_link.sock -stdin
# UART TX on either side arrives as UART RX on the other
# GPIO pins can also be wired: -wire-gpio /tmp/gpio_link.sockWiFi (Pico W / CYW43):
# Basic Pico W/CYW43 emulation
./bramble firmware.uf2 -wifi
# Bridge emulated WLAN frames to a host TAP interface
./bramble firmware.uf2 -wifi -tap tap0Virtual Network (Internet Bridge + Mesh):
# Single-command internet bridge (auto-creates TAP, NAT, sudo)
./bramble firmware.uf2 -net -stdin
# Mesh two Bramble instances via Ethernet-level peer link
./bramble fw1.uf2 -net-peer /tmp/vnet.sock -stdin # Terminal 1
./bramble fw2.uf2 -net-peer /tmp/vnet.sock -stdin # Terminal 2
# W5500 live networking (real host TCP/UDP sockets)
./bramble w5500_firmware.uf2 -net -net-live -stdin
# Wire Ethernet frames between instances
./bramble fw_sensor.uf2 -wire-eth /tmp/mesh.sock -stdin
./bramble fw_ctrl.uf2 -wire-eth /tmp/mesh.sock -stdinSoftware-Defined Devices (SDD):
# Attach a TMP102 thermometer on I2C0 at 0x48
./bramble firmware.uf2 -sdd thermometer
# Custom temperature, bus, and address
./bramble firmware.uf2 -sdd thermometer:temp=37.5,i2c=1,addr=0x49
# Combine with mesh networking
./bramble fw_sensor.uf2 -wire-eth /tmp/mesh.sock -sdd thermometer:temp=42Storage Devices (SD Card / eMMC):
# Attach a 32MB SD card image on SPI1 (default)
./bramble firmware.uf2 -sdcard sdcard.img -sdcard-size 32
# Attach SD card on SPI0 instead
./bramble firmware.uf2 -sdcard sdcard.img -sdcard-spi 0
# Attach a 64MB eMMC image on SPI0 (default)
./bramble firmware.uf2 -emmc emmc.img -emmc-size 64
# Attach eMMC on SPI1 instead
./bramble firmware.uf2 -emmc emmc.img -emmc-spi 1
# Combine with flash persistence and MicroPython
./bramble python/micropython.uf2 -stdin -clock 125 -flash mpy.bin -sdcard sd.imgMicroPython REPL:
./bramble python/micropython.uf2 -stdin -clock 125 -flash mpy.binOutput:
MicroPython v1.27.0 on 2025-12-09; Raspberry Pi Pico with RP2040 Type "help()" for more information. >>>
GDB Remote Debugging:
# Terminal 1: Start emulator with GDB server
./bramble firmware.uf2 -gdb
# Terminal 2: Connect GDB
arm-none-eabi-gdb firmware.elf -ex "target remote :3333"Expected output:
╔════════════════════════════════════════════════════════════╗ ║ Bramble RP2040 Emulator - Dual-Core Mode ║ ╚════════════════════════════════════════════════════════════╝ [Init] Initializing dual-core RP2040 emulator... [Init] Loading firmware: littleOS.uf2 [Init] Firmware loaded successfully [Boot] Starting Core 0 from flash... [Boot] Core 0 SP = 0x20020000 [Boot] Core 0 PC = 0x10000104 [Boot] Core 1 held in reset (waiting for Core 0 to start) ═══════════════════════════════════════════════════════════ Executing... ═══════════════════════════════════════════════════════════
Bramble/ ├── src/ │ ├── main.c # Unified entry point, boot, execution (single & dual) │ ├── cpu.c # Cortex-M0+ core: O(1) dispatch, dual-core, exceptions │ ├── instructions.c # 60+ Thumb instruction implementations │ ├── membus.c # Memory bus: pointer-based routing, peripheral stubs │ ├── elf.c # ELF32 ARM binary loader │ ├── uf2.c # UF2 file loader │ ├── gpio.c # GPIO peripheral emulation │ ├── timer.c # Hardware timer emulation │ ├── nvic.c # NVIC interrupt controller │ ├── clocks.c # Resets, Clocks, XOSC, PLLs, Watchdog │ ├── adc.c # ADC peripheral emulation │ ├── rom.c # ROM function table with Thumb code stubs │ ├── uart.c # Dual PL011 UART emulation │ ├── spi.c # Dual PL022 SPI emulation │ ├── i2c.c # Dual DW_apb_i2c emulation │ ├── pwm.c # 8-slice PWM emulation │ ├── dma.c # 12-channel DMA controller │ ├── pio.c # Dual PIO block emulation (full instruction execution) │ ├── usb.c # USB controller with host enumeration + CDC bridge │ ├── rtc.c # RTC peripheral (ticking, calendar, leap year) │ ├── gdb.c # GDB remote serial protocol stub │ ├── netbridge.c # UART-to-TCP bridge │ ├── wire.c # Multi-instance Unix socket wiring │ ├── vnet.c # Virtual network bus (TAP/peer/port routing) │ ├── sdd.c # Software-defined device framework │ ├── sdd_thermo.c # TMP102 I2C thermometer device model │ ├── storage.c # Flash write-through persistence │ ├── sdcard.c # SD card SPI emulation (SDHC, file-backed) │ ├── emmc.c # eMMC SPI emulation (file-backed) │ ├── fatfs.c # FAT16 helpers for flash/FUSE │ ├── fuse_mount.c # Optional FUSE mount integration │ ├── w5500.c # W5500 Ethernet device model │ ├── bme280.c # BME280 sensor model │ ├── corepool.c # Host-threaded execution + core allocation │ ├── cyw43.c # CYW43 WiFi emulation │ ├── tapif.c # TAP bridge for Pico W traffic │ ├── devtools.c # Developer tools (semihosting, coverage, etc.) │ └── rp2350_rv/ │ ├── rv_cpu.c # Hazard3 RV32IMAC CPU engine (93 instructions) │ ├── rv_clint.c # CLINT interrupt controller (mtime/mtimecmp/MSIP) │ ├── rv_membus.c # RP2350 memory bus (520KB SRAM, peripheral routing) │ └── rv_bootrom.c # Minimal RISC-V bootrom ├── include/ │ ├── emulator.h # Core definitions, CPU state, memory layout │ ├── instructions.h # Instruction handler prototypes │ ├── gpio.h # GPIO register definitions │ ├── timer.h # Timer register definitions │ ├── nvic.h # NVIC register definitions │ ├── clocks.h # Clock-domain peripheral definitions │ ├── adc.h # ADC register definitions │ ├── rom.h # ROM layout and function codes │ ├── uart.h # PL011 UART register definitions │ ├── spi.h # PL022 SPI register definitions │ ├── i2c.h # DW_apb_i2c register definitions │ ├── pwm.h # PWM register definitions │ ├── dma.h # DMA controller register definitions │ ├── pio.h # PIO register definitions │ ├── usb.h # USB controller register definitions │ ├── rtc.h # RTC register definitions │ ├── gdb.h # GDB RSP stub definitions │ ├── netbridge.h # UART network bridge definitions │ ├── wire.h # Multi-instance wire protocol definitions │ ├── vnet.h # Virtual network bus definitions │ ├── sdd.h # Software-defined device definitions │ ├── storage.h # Flash write-through definitions │ ├── sdcard.h # SD card SPI definitions │ ├── emmc.h # eMMC SPI definitions │ ├── fatfs.h # FAT filesystem helpers │ ├── fuse_mount.h # FUSE mount definitions │ ├── w5500.h # W5500 device definitions │ ├── bme280.h # BME280 device definitions │ ├── corepool.h # Core pool definitions │ ├── cyw43.h # CYW43 WiFi definitions │ ├── tapif.h # TAP bridge definitions │ ├── devtools.h # Developer tools definitions │ ├── rp2350_rv/ │ │ ├── rv_cpu.h # RISC-V CPU state, CSR defs, instruction decode │ │ ├── rv_clint.h # CLINT interrupt controller definitions │ │ ├── rv_membus.h # RP2350 memory bus definitions │ │ ├── rv_bootrom.h # Bootrom generator definitions │ │ └── rp2350_memmap.h # RP2350 memory map constants │ └── rp2350_arm/ │ └── m33_cpu.h # Cortex-M33 placeholder ├── tests/ │ └── test_suite.c # Unit test suite (319 tests, verbose, CTest integrated) ├── test-firmware/ │ ├── hello_world.S # Assembly UART test │ ├── gpio_test.S # Assembly GPIO test │ ├── timer_test.S # Assembly timer test │ ├── alarm_test.S # Assembly alarm test │ ├── interrupt_test.S # Assembly interrupt test │ ├── linker.ld # Memory layout definition │ ├── uf2conv.py # UF2 conversion utility │ └── build.sh # Firmware build script ├── docs/ │ ├── GPIO.md # GPIO peripheral documentation │ ├── NVIC_audit_report.md # NVIC audit findings and recommendations │ └── ROADMAP.md # Development roadmap and feature status ├── CMakeLists.txt # Build configuration ├── build.sh # Top-level build script ├── CHANGELOG.md # Version history and changes └── README.md # This file
/* Read current time */
ldr r0, =0x4005400C /* TIMER_TIMELR */
ldr r1, [r0] /* R1 = current time in microseconds */
/* Set alarm for 1000us in future */
ldr r2, =1000
add r1, r2 /* R1 = target time */
ldr r0, =0x40054010 /* TIMER_ALARM0 */
str r1, [r0] /* Alarm armed automatically */
/* Wait for alarm (polling) */
poll:
ldr r0, =0x40054034 /* TIMER_INTR */
ldr r1, [r0]
movs r2, #1
tst r1, r2 /* Check bit 0 */
beq poll
/* Clear interrupt */
movs r1, #1
ldr r0, =0x40054034 /* TIMER_INTR */
str r1, [r0] /* Write 1 to clear *//* Configure GPIO 25 as output (LED on Pico) */
ldr r0, =0x400140CC /* GPIO25_CTRL */
movs r1, #5 /* Function 5 = SIO */
str r1, [r0]
/* Enable output */
ldr r0, =0xD0000024 /* SIO_GPIO_OE_SET */
ldr r1, =(1 << 25) /* Bit 25 */
str r1, [r0]
/* Turn LED on */
ldr r0, =0xD0000014 /* SIO_GPIO_OUT_SET */
str r1, [r0]See docs/GPIO.md for complete documentation.
Independent Core Execution: Both cores run independently with their own:
Memory Sharing:
Inter-Core Communication:
Flash: 0x10000000 - 0x10200000 (2 MB, shared) Core 0 RAM: 0x20000000 - 0x20020000 (128 KB, core-local) Core 1 RAM: 0x20020000 - 0x20040000 (128 KB, core-local) Shared RAM: 0x20040000 - 0x20050000 (64 KB, shared) Total RAM: 320 KB usable, 264 KB available
// In C firmware code for dual-core operation:
// Core 0: Send message to Core 1
fifo_push(CORE0, 0x12345678);
// Core 1: Receive message
uint32_t msg = fifo_pop(CORE1);
// Both cores: Synchronized access to shared memory
spinlock_acquire(0);
shared_counter++;
spinlock_release(0);Most firmware builds naturally for dual-core:
# In your firmware makefile:
make CORES=2 # Compiles with dual-core definitionsThen run with:
./bramble firmware.uf2 -status # Show status for both coresThe emulator accurately models the RP2040 address space:
All accesses respect alignment requirements and return appropriate values for unimplemented regions.
Peripherals are integrated into the memory bus (membus.c):
The timer uses a cycle-accurate timing model with configurable clock frequency:
Alarms trigger when:
if (timer_low_32bits >= alarm_value) {
set_interrupt_bit();
disarm_alarm();
}Instructions are dispatched via a 256-entry O(1) lookup table indexed by instr >> 8:
The emulator implements full APSR flag semantics:
Helper functions update_add_flags() and update_sub_flags() ensure consistency across all arithmetic instructions.
The UF2 loader validates:
Multi-block firmware images are supported with sequential loading, and malformed or out-of-range blocks are rejected without modifying flash.
Core Synchronization:
FIFO Implementation:
typedef struct {
uint32_t buffer[FIFO_DEPTH]; // 8 entries per FIFO
uint16_t write_ptr;
uint16_t read_ptr;
uint16_t count;
} fifo_t;Spinlock Implementation:
uint32_t spinlock_acquire(uint32_t lock_id) {
if (spinlocks[lock_id] & SPINLOCK_LOCKED) {
return 0;
}
spinlocks[lock_id] = SPINLOCK_VALID | SPINLOCK_LOCKED;
return 1u << lock_id;
}Bramble now ships with a 64K decoded instruction cache enabled by default and optional JIT basic-block compilation via -jit.
For benchmarking details, see tests/benchmark.c.
The Bramble project is open for contributions! Areas that need help:
Run ctest --test-dir build --output-on-failure to verify changes don't break existing tests. See CHANGELOG.md for release history and docs/ for detailed technical documentation.
MIT License - See LICENSE file for details
For issues, questions, or contributions:
| Back | FazBrowse Home | New Git URL |