SearchA-ZM › Milk-V Duo (RISC-V Linux)

Milk-V Duo (RISC-V Linux)

2023 Open source Boots Linux Online

This is a RISC-V-Linux-in-the-browser showpiece — a sibling to the ARM "Pi-class" Linux exhibit, but on the open RISC-V ISA. A real RISC-V CPU boots a real Buildroot Linux to an interactive busybox shell over a serial console, entirely in the browser, via the vendored TinyEMU emulator (Fabrice Bellard's JSLinux engine, compiled to WebAssembly). There is no network at runtime — the boot loader, kernel and root filesystem are preloaded — and JavaScript owns the run loop, so the shared debugger can pause the core, single-step it, read and write x0-x31 / PC / CSRs, read physical RAM, disassemble RV32IMAC at the PC, and set breakpoints and write-watchpoints.

Honest scope. The physical Milk-V Duo is built around the Sophgo SG2002 (a.k.a. CV1800B), whose main application core is a 64-bit T-Head C906 (RV64GC) that boots Linux, plus a second small RISC-V/8051 co-processor, on-chip GPIO (the board's on-board LED), a camera/ISP and a small VPU. This exhibit stands in for that SoC with a generic 32-bit TinyEMU RISC-V machine (RV32IMAC) — the same open RISC-V ISA and the same boot-Linux-to-a-shell experience, but not true SG2002 silicon: no SG2002-specific peripherals, no GPIO LED, and a text serial console only (no display).

Milk-V ↗

Runs on: Web browser

Milk-V Duo (RISC-V Linux) Online Emulator

Play Milk-V Duo (RISC-V Linux) using WebAssembly directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
RISC-V Linux (busybox shell)Milk-V Duo (RISC-V Linux)Milk-V DuoopenOpen ⛶

Machines emulated

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 and the page is file://-safe. 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/milkv-duo/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:

ExportWhat 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 needsThe 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).
CSRsdbg_cpu_get_csr — mstatus, mtvec, mepc, mcause, satp, sepc …
Side-effect-free memorydbg_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 pagingdbg_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.

Architecture

RISC-V is the open, royalty-free load/store RISC ISA (Berkeley, 2010) — the architecture the Milk-V Duo's Sophgo SG2002 / CV1800B is built on. This exhibit runs a 32-bit RV32IMAC core — the base integer set (I) plus integer multiply/divide (M), atomics (A) and the compressed 16-bit encodings (C) — booting a real Linux:

  • riscvemu.wasm — TinyEMU's RISC-V CPU interpreter and the surrounding machine: RAM at 0x8000_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 (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.

What this is not: the real Milk-V Duo's SG2002 (CV1800B) has a 64-bit T-Head C906 (RV64GC) application core, a second small RISC-V/8051 co-processor, on-chip GPIO (the board's on-board LED), a camera/ISP and a small VPU. This exhibit is a generic 32-bit TinyEMU RISC-V machine standing in for that silicon — the same open RISC-V ISA and the same boot-Linux-to-a-shell experience, but no SG2002-specific peripherals, no GPIO LED, and a serial console only (no display adapter). It is a RISC-V sibling to the site's ARM "Pi-class" Linux exhibit.