Charles Lohr's Linux-Capable Really Tiny RISC-V Emulator Exists in a Single 400-Line C Header File
Existing in a single 400-line header file, this RISC-V emulator can boot a usable Linux operating system and run executables.
Electrical engineer Charles Lohr has built a 32-bit RISC-V emulator with a difference: it exists as a single C header file, of around 400 lines of code — yet is capable of running Linux, despite a lack of memory management unit (MMU).
"I've been working really hard over the last few weeks on this little tiny RISC-V emulator. The really tiny part about is is that it doesn't have an MMU which is something that virtually all desktop modern processors have," Lohr explains. "The reason I wanted to do this was I wanted to see if I could run Linux on it. Something that was close to but not as simple as an ESP32-C3. And, well, the answer was yes. In fact, I was able to write a really tiny RISC-V emulator. The actual emulator part all exists in this one function in this header file and it was only around 350, 400 lines of code. And it's able to run Linux and I'm able to have executables and whatever on it."
The emulator, mini-rv32ima, implements the 32-bit RV32IMA variant of the RISC-V architecture, plus the Zifencei and Zicsr extensions. What it doesn't implement is a memory management unit (MMU), which is something the Linux kernel expects to see — meaning that the goal of booting Linux on the emulator would prove to be more of a challenge than it might otherwise have been.
"I barely knew about RISC-V and I had no idea about any of the more advanced features in it, or if it was even possible to build an RV32-NOMMU Linux image," Lohr explains. "So, the first thing I did was Google it, And I came across this tweet from August. So I reached out and RegyMM stated helping me."
"Once I got something that mostly ran, a system that was up and running, I was able to go then and convert the code," Lohr continues. "I was able to start changing things and cleaning things up to the more modern code that I have here. And because of test-driven-development I had a really good test and I could run the Linux kernel and run applications inside of the Linux kernel and I could make really small changes like that one there and see if the Kernel still works, and I was able to shave off a lot of size and complexity from my emulator over time just by being able to go edit it and and be able to go test the emulator and be able to see that it still worked, because I was using it with Linux."
The resulting emulator is tiny indeed: coming in at around 400 lines of code, it exists in a single C header file without a single external dependency — "not even libc," Lohr notes. It clocks in at around half the speed of the considerably larger QEMU emulator, can be easily embedded in other applications — and once compiled with its demo wrapper, which adds a further 250 lines of code to support a command-line interface, system control interface, UART serial bus, device tree, and kernel image loader, comes out as a single executable just 18kB in size or less when compressed.
"It's 'fully functional' now in that I can run Linux, apps, etc. Compile flat binaries and drop them in an image," Lohr writes. "[But it] is 'incomplete' in that it didn't implement the tons of the spec that Linux doesn't (and you shouldn't) use."
The source code for the project has been published to GitHub under the permissive MIT, BSD three-clause, and Creative Commons Zero licenses.