Simple RISC-V Emulator in WebAssembly

Wenxuan Shi /
January 22, 2022
4 min read

This is an exciting project I've been thinking about. A RISC-V Emulator in WASM that runs in your browser, perhaps on this very page!

My friend @MstMoonshine wrote an emulator of RISC-V architecture in Rust inspired by the video from the "Low Level Javascript" channel on YouTube1, where they made a RISC-V CPU on an FPGA in TypeScript. The live coding thing is great there.

Wait wait wait, how can you possibly do this with JavaScript?

Well, first it's TypeScript rather than JavaScript. And second, there are tools translating TS/JS into HDL(hardware description language). So they can run and test it on FPGA.

I have a soft spot for TypeScript (basically with which I wrote this entire website), but I love Rust even more. When Mr.Moonshine told me his idea, I was very excited. After fighting the Rust compiler with some whining and cursing, we got a simple prototype that has registers, memories, ALU, with a five level pipeline.

Last week I got the idea to turn this babe into a runnable package on browser so everyone can try RISC-V without leaving the blog or downloading any file. The best option would be to turn it into a WebAssembly package. This means we can run a performance-oriented program at a near-native binary speed in a website.

Rust to WASM

The translation from Rust to WASM went very smooth. Our emulator has one only main entry that takes a 'ROM' file in hex format. So we just use the tool wasm_bindgen to hook the entry function, generating a JavaScript function to invoke it. Currently the emulator will print out the memory and register dump, so we wrap it in a String value, then return it to JavaScript.

The only problem I encountered was the limitation from the browser. By default, we allocated 4GB memory for RAM in the emulator. However, browsers don't allow that much memory in a single web page, so I cut it down to 256MB until further research. Interestingly, Safari on iOS can also run WASM, but it doesn't even allow 256MB. Now I cut it down to 4MB to make it run.

At this stage, we don't have to worry about running out of memory. But this thing does show that there is still a lot of work to be done afterwards.

Next Step: More functionalities

It's too early to show off this project. We still don't support calling convention (e.g. stack memory), error handling, interrupts, device I/O, privilege mode, etc. We don't even have branch instructions. Some of them are easy to implement, but some of them need further discussion.

The final step is to make this emulator a minimal virtual machine on which we can run Operating Systems. Think of running an Linux virtual machine in your browser tab (or maybe in your mobile browser tab)!

Related Work

Some efforts has been made on other architectures to run virtual machines (emulators) with WASM. Here I put some brilliant projects.

For example, on X86:

On Arm:

And on RISC-V:

Live Demo

But, isn't it too early to present the project?

How can I make you read the entire blog without giving you anything fun? I made this little toy that actually runs a little piece of RISC-V assembly with WebAssembly. Just click the "Run" button and see how the machine works! If you don't believe me, feel free to press F12 and check the rv_emu_rs.wasm file (it won't load until you click the button)!

An update to the demo example is on Rv32_emu (, but it is still only a preliminary demonstration. I'll keep posting to keep up if we implement some big features.


  1. YouTube video: Building The Worlds First CPU in TypeScript? (No Really!)


Subscribe to the blog via RSS