(Solo project)

I picked up the RP2350 datasheet as I wanted to know its inner workings for other projects and was surprised by how readable it was. That readability pulled me in, and I started thinking it could be fascinating to build an emulator and see how all the chip’s peripherals interact in software.

Choosing RISC-V

The RP2350 supports both an ARM Cortex-M33 and a RISC-V Hazard3 core. ARM was too complex for me to approach: the documentation felt dense and hard to follow. RISC-V was the opposite. Plenty of accessible materials exist, and I found a compliance test suite I could use to verify my implementation step by step. The choice was straightforward.

Getting to main()

GDB support turned out to be surprisingly easy to add. Lemur exposes a server on TCP port 3333 as a drop-in replacement for OpenOCD, so any standard GDB client connects without any special configuration.

With GDB in place, I spent a couple of months single-stepping through instructions, slowly working through the boot ROM until the emulator finally reached the user’s main() function. That milestone confirmed the whole approach was working.

Emulating Down to the Wire

For peripheral emulation I took the harder path. Rather than abstracting UART output as a string, I emulate at the signal level: the emulator modulates data into individual bits on the output pin and demodulates them back on the other end. When the chip outputs text over UART, I verify that the correct ones and zeros appear at the right timing on the wire. I even implemented a “logic analyzer” to dump the signal into a VCD file that can be opened in PulseView.

Development Approach

Throughout the project, Claude and other AI tools have been helping me. I treat them as “autocomplete on steroids”: they accelerate the implementation, but the architecture, the design decisions, and the hard debugging are mine.