Live-coding an Open-Source Pico Emulator from Scratch
To make the experience fit your profile, pick a username and tell us what interests you.
The RP2040 datasheet, where we'll find all the specific details about the MCU core and peripherals.
Adobe Portable Document Format - 29.77 MB - 01/25/2021 at 18:44
ARM® v6-M Architecture Reference Manual. It'll be our main reference for implementing the emulator!
Adobe Portable Document Format - 2.16 MB - 01/25/2021 at 18:37
Pico Getting Started guide. Check out Chapter 2 for setting up the build environment. We'll use it extensively while working on the emulator.
Adobe Portable Document Format - 36.28 MB - 01/25/2021 at 18:36
Then, in a sharp turn, we went back to working on the code: refactoring the peripherals, implementing narrow I/O register writes, adding a missing variant of the CMP instruction, and even creating the Timer peripheral.
Our new goal is to get hello_serial to run. Unlike hello_uart, it uses a set of high-level APIs, including printf() and sleep_ms(). That's also the reason we needed the Timer:
In the next episode, we'll keep working on the Timer, and probably also implement the Nested Vectored Interrupt Controller (NVIC), to let us fire interrupts from our virtual peripherals.
This week we managed to get a Pi Pico program to run start to end in our emulator!
The happy moment happened just as the stream was about to conclude, about 2:29 hours into the stream. You can watch the full recording here:
Or, if you want to try running hello_uart yourself and replicate our success, you can clone the repo and run the code:
git clone https://github.com/wokwi/rp2040js npm install npm start 2>&1 | grep UART
The "grep" part takes care of hiding all the debug prints we have in our code... 😜
Alternatively, you can also run it in the Gitpod cloud. After launching the workspace simply copy the two npm commands above and paste them into the terminal. Enjoy!
Next week, we are going to tidy up the code and publish a first alpha release of rp2040js to npm. So if you are reading this after April 6th and still want to run the hello_uart example, make sure to git checkout 175f616.
So far we spent about 25 hours of coding to unlock our first milestone: running a basic program from start to end in the emulator! 🔓
The next live stream happens on Tuesday, and you'll be able to watch it here.
During our last live-hacking session, we discovered the compiler optimizations were severely limiting our ability to debug the code in the emulator. Function inlining, code rearrangement and tail call optimizations made debugging with GDB much more challenging.
After digging a bit in the Pico SDK's build system, we learned about a flag that disables all the compiler optimizations,
Here's the complete sequence of commands for building the Pico examples in debug mode and without any compiler optimizations:
cd ~/pico/pico-examples/ rm -rf build mkdir build cd build export PICO_SDK_PATH=../../pico-sdk cmake -DCMAKE_BUILD_TYPE=Debug -DPICO_DEOPTIMIZED_DEBUG=1 .. make -j4
It is also possible to build just a specific example, by running the
make -j4 command in a subdirectory of the build directory. e.g., to only build hello_uart, we'll first change the directory to
cd uart/hello_uart make -j4
In this week's episode we leveled-up our debugging capabilities. Continuing our last week effort to plug GDB into the emulator, we added single-stepping breakpoints, and memory/register writes.
This means we'll be harness the full power of GDB to debug the emulated code: step through the lines of source code, break at certain locations, travel through the stack trace and inspect/modify the values of local and global variables.
To sweeten the deal, we even managed to hook the Visual Studio Code debugger into the simulator, allowing us to use a modern and convenient user-interface for debugging.
In the episode recording you will see all the work that led into that magical moment: how our naïve implementation of the gdbserver "c" command failed to work with stepping and breakpoints, the intensive troubleshooting and debugging that followed, and how eventually comparing our implementation with the physicals Pi Pico's debugger revealed the answer.
The first 22 minutes of the episode show how I review a pull-request from Brain Popeck, adding 3 new instructions: sxtb, asrs, eors, and uncovering a sneaky bug in our signExtend implementation. Then we dive right into hacking on gdbserver!
The next episode will air on Tuesday. If you want the link right in your inbox, you can sign up for email updates.
How do you debug the code that is running in your emulator?
In the previous episodes, we printed a trace log of every single instruction that the emulator executes. This allowed us to find various bugs in our virtual RP2040 chip. This method worked, but was a very slow and inefficient: we had to go back and forth between the trace file, the assembly listing and the source code.
The last episode started as usual, hacking on a few more instructions. But midway it totally changed course: we tried to make it possible to connect GDB, the GNU debugger, into the emulator.
It took us about an hour of coding, but eventually we got it to work: GDB connected and was able to read the CPU state, registers, and even show us what line of code the emulated MCU was running!
You can watch the whole process here. GDB hacking starts at 1:24:12 👇
This week I'm planning to continue hacking on the GDB integration. We'll also take a look at a pull request some nice guy sent us, and hopefully also merge it. It adds the few missing instructions: sxtb, asrs, eors.
You can watch the stream on YouTube.
Last week, we implemented a bunch of missing instructions and completed the family of LDR/STR instructions, as well as LSLS and ADD (register).
chegewaras, who watched the stream, suggested that we also decode the 4-byte instructions DMB, MSR, and MRS to avoid a situation where the second pair of bytes is interpreted as a different instruction that might affect the execution of the code. So we did.
Finally, we tried running hello_usart, and ran into panic(). We started looking into it, and you can watch the complete debugging session in the live stream recording:
The next episode will be streamed today, you can watch it here. See you then!
This time, we deciphered the internals of the bootrom, while hunting for two subtle bugs that made our emulator go crazy. We thickened our virtual RP2040 with a bunch of new instructions: MOV, SBCS, ANDS, ADDS, SUBS, ORRS and ADD/SUB with the stack pointer (SP).
You can watch the complete stream here:
I personally find the bug-hunting process most interesting. The first part starts at 1:15:44, where you see how I close down on the bug until it has no choice but to reveal itself, while teaching me a painful lesson about writing good test cases.
The second hunt starts at 2:18:30, where we try to figure out why BL jumps to the wrong memory address.
So it feels like we made a good progress, but we're not quite there yet. There are a few more missing instructions, and then... 🤞
Hi Uri, just wanted to mention that I'd love to see micropython running in this emulator too (in any following episode, after the important stuff). Continue your awesome work mate, you're killing it!
I woke up this morning, and saw this message in the YouTube chat for the live stream today. Thank you Andreas!
I'm not sure if we'll actually get to try MicroPython today. The chances are slim, but I'm optimist, so better be prepared.
Here is how I compiled MicroPython for the RP2040:
git clone https://github.com/micropython/micropython cd micropython git submodule update --init lib/pico-sdk lib/tinyusb make -C mpy-cross cd ports/rp2 make
The compiled files can be found inside
ports/rp2/build, and you'll also find there a disassembly of the firmware (similar to the ones we looked at in the previous live streams). Enjoy!
This week's session was very fruitful: after fiddling with some of the XIP Flash registers, we eventually got boot_stage2 to execute and jump to the application's code. We also implemented a bunch of new instructions: BX (thanks to pyTony who sent us a PR), POP, STMIA, ADR, BLX, BICS and LDRH:
Overall, it feels like we are pretty close to hitting our goal of running a program from start to end. In the next live stream we'll implement the missing instructions: MOV, SBCS, ANDS, and then... who knows? join to see!
If you wish, you can also add the stream to your calendar ;-)
Last week, we spent some time exploring the Pi Pico's bootrom and stage-2 bootloader. We explored
__vector_entry, found some nice ASCII art buried deep in the Pico's SDK, and also implemented BL, LSRS and LDMIA:
The next live-stream will take place tomorrow, 7pm GMT (11am PST, 2pm EST, 8pm CET).
If you wish, add it to your Google calendar.
Become a member to follow this project and never miss any updates