Search › A-Z › R › RISC-V (Linux)
RISC-V (Linux)
A RISC-V machine that runs entirely in the browser. It is Fabrice Bellard's TinyEMU (the engine behind JSLinux) compiled to WebAssembly, booting a Buildroot Linux (a real rv32 kernel and busybox userland) to a shell over a serial console. RISC-V is the open, royalty-free RISC instruction set; this default machine is an RV32IMAC core.
The online version plugs into the shared in-frame debugger: live RISC-V registers (x0-x31, PC, CSRs), side-effect-free physical memory with RV32IMAC disassembly, true single-instruction step, execution breakpoints and write watchpoints, plus a physical and on-screen console keyboard.
Runs on: Web browser
RISC-V (Linux) Online Emulator
Play RISC-V (Linux) using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| RISC-V Linux (busybox) | RISC-V (Linux) | RISC-V (Linux) | open | Open ⛶ |
Machines
Chips
Notes
Embedding
The runtime is TinyEMU (Fabrice Bellard's riscvemu, the engine behind JSLinux) compiled to WebAssembly with emscripten. Rather than Bellard's networked JS front-end, we built a small self-contained front-end (emu.c) that loads the boot loader, kernel and root filesystem from a preloaded in-memory filesystem — so nothing is fetched from a CDN. We self-host riscvemu.js (the glue), riscvemu.wasm (the CPU + machine) and riscvemu.data (bbl + Linux kernel + ext2 rootfs, ~7 MB).
var mod = await createRiscvEmu({
locateFile: p => "/emulator/riscv/src/" + p, // find the .wasm / .data
onConsole: bytes => term.write(bytes) // VirtIO console output -> VT100 terminal
});
mod.ccall("emu_start", "number", [], []); // build the machine, load Linux
We own the run-loop. The wasm build does not use emscripten's main loop; instead it exports slice functions and JavaScript drives them, so the debugger can pause / step / breakpoint the CPU:
| Export | What it does |
|---|---|
emu_run_frame(cycles) | Feed queued keystrokes, run a slice of instructions, honour the RTC sleep. The normal "running" path. |
emu_step(n) | Run exactly n RISC-V instructions (true single step — see the debugger notes). |
emu_service() | Service devices once (console + timer) so a WFI-idle guest can wake via a real interrupt between single steps. |
emu_console_input(ch) | Queue one byte from the keyboard into the VirtIO console. |
The screen is a terminal, not a framebuffer. A RISC-V Linux console is a byte stream, so onConsole feeds a compact VT100 parser (terminal.js) that keeps an 80×30 character grid and paints it onto a <canvas>. The keyboard (physical keydown and the on-screen keys) writes ASCII bytes straight into the console FIFO.
Debugger integration
This shows a full-system WebAssembly emulator getting the same debugger as the pure-JS cores. TinyEMU keeps its whole RISCVCPUState (PC, reg[32], the CSRs) inside the wasm linear memory, and the machine keeps guest RAM there too. The state is not exposed by default, so we extended the core: we added EMSCRIPTEN_KEEPALIVE accessors to riscv_cpu.c and riscv_machine.c and published ccall wrappers on window.EMU_BOOT. None of them run per-cycle — only on a debugger refresh or a step.
| What the debugger needs | The hook we added to the core |
|---|---|
| PC + x0-x31 (read & write) | dbg_cpu_get_pc/reg, dbg_cpu_set_pc/reg — read/write the live RISCVCPUState (x0 stays hard-wired to 0). |
| CSRs | dbg_cpu_get_csr — mstatus, mtvec, mepc, mcause, satp, sepc … |
| Side-effect-free memory | dbg_machine_read_phys/write_phys — go straight to the RAM backing store via phys_mem_get_ram_ptr, so no MMIO device is touched. |
| Disasm "follow PC" under paging | dbg_cpu_va2pa — a read-only Sv32 page-table walk that maps the (virtual) PC to a RAM offset so the disassembly highlights the executing instruction. |
True single-instruction step. TinyEMU's interpreter only tests its cycle budget at basic-block boundaries (a branch or a page edge), so asking it to run "1 cycle" actually runs a whole block. We added a gated per-instruction budget check to the interpreter loop (a global riscv_dbg_single_step that is off during normal runs, so full-speed boot is unaffected); with it set, emu_step(1) advances the PC by exactly one instruction. Between steps the debugger calls emu_service() so a guest sitting in wfi (the idle shell) still wakes on a real timer/console interrupt and single-stepping stays exact.
Breakpoints & watchpoints. Because we own the loop, breakpoints are exact: when any breakpoint is set the loop single-steps and compares the PC's RAM offset against the set. Write watchpoints are implemented by snapshotting the watched bytes and re-reading them after each step, pausing when one changes. The memory window is a 1 MB view of physical RAM at the 0x8000_0000 boot address, disassembled with the shared riscv decoder.
The new decoder. debugger/src/cpus/riscv.js registers riscv: it decodes RV32I plus the M (mul/div), A (atomics) and Zicsr/fence ops in the R/I/S/B/U/J formats, and the 16-bit compressed C.* forms, using the ABI register names (zero ra sp gp tp t0.. a0.. s0..). Anything unknown becomes a .word / .2byte so the view never desynchronises.
Architecture
RISC-V is the open, royalty-free load/store RISC ISA (Berkeley, 2010). This machine is a 32-bit RV32IMAC core — the base integer set (I) plus integer multiply/divide (M), atomics (A) and the compressed 16-bit encodings (C) — running a real Linux.
riscvemu.wasm— TinyEMU's RISC-V CPU interpreter and the surrounding machine: RAM at0x8000_0000, the CLINT (timer/soft-IRQ), a PLIC, an HTIF console and VirtIO (console + block).bbl(the Berkeley Boot Loader / riscv-pk) sits at the reset vector, sets up machine mode and hands off to…- the Linux kernel (rv32, GPLv2), which enables Sv32 paging (hence the virtual PCs like
0xc00…) and mounts… - an ext2 root filesystem built with Buildroot, giving a busybox userland and the
~ #shell.
The default machine boots straight to that shell over the serial console — no disk to choose, no display adapter. Everything runs client-side in the browser tab.