RV64GC RISC-V emulator written in Go capable of booting Linux, NetBSD, FreeBSD, and OpenBSD through OpenSBI, U-Boot, EFI, and GRUB, with SV39 virtual memory and VirtIO devices.
OpenSBI → BuildRoot Linux
OpenSBI → U-Boot → EFI → {NetBSD,FreeBSD,OpenBSD}
OpenSBI → U-Boot → EFI → GRUB → Ubuntu Linux
- RV64GC instruction set support
- Machine, Supervisor, and User privilege modes
- SV39 virtual memory and page tables with TLB fastpaths
- Custom Device Tree generation for hardware peripherals
- OpenSBI support
- VirtIO support (block, net, rng, gpu) with stable multi-hour async DMA handling
- Operating Systems:
- OpenSBI
- U-Boot
- EFI Boot
- Buildroot Linux
- Ubuntu 24.04 (tested with full package upgrade cycles)
- NetBSD 11.99
- FreeBSD 15.1
- OpenBSD 7.9 (tested with kernel compilation inside guest)
- Linux syscall emulation mode
- Device emulation:
- NS16550A UART
- PLIC interrupt controller
- ACLINT MSWI and MTIMER devices
- Syscon poweroff device
- Symbol-aware traces and debugging support
- Instruction-level execution tracing
- Compressed instruction support
The project is intended as a readable and hackable implementation of a modern RISC-V platform, suitable for learning emulator internals, operating systems, and the RISC-V privileged architecture.
- V extension
- Reimplement DTB creation
- FreeBSD
- virtio_input.c
- cursor keys (and others) in framebuffer console (scancodes)
- clean detach and shutdown
- Implement memory mapped framebuffer for
virtio_gpu.candgpu.go
- virtio_input.c
- riscv-arch-test
- Haiku boot
The project is divided into two tracks:
-
CPU conformance
- A clean, pure Go implementation of the RISC-V processor.
- Prioritize correctness, readability, and maintainability.
- Apply performance optimizations where they do not compromise code quality.
- This CPU implementation serves as the reference implementation and will later be reimplemented using MPC primitives to create a virtual MPC RISC-V CPU.
-
System emulation
- Emulate the system surrounding the CPU, including memory, interrupts, and devices.
- Use asynchronous device interfaces where appropriate.
- Optimize this layer for performance.
- In the MPC architecture, this layer remains outside the MPC computation and interacts with the virtual MPC CPU.
- VirtIO
- virtio-net
- HTTP proxy
- NTP server
- virtio-console
- virtio-net
- SMP support
- Parse Linux kernel PE32+ header: load address, symbols
- JIT experiments
- Full Linux userspace compatibility
Clone and run:
$ git clone https://github.com/markkurossi/riscv
$ cd riscv/cmd/goemu
$ go build
$ ./goemu buildroot.goemuExpected output:
OpenSBI v1.6
[ 0.000000] Booting Linux on hartid 0
[ 0.000000] Linux version 7.0.11 (root@5c73f8dabee1) (riscv64-linux-gnu-gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #9 SMP PREEMPT Wed Jun 3 08:23:43 UTC 2026
[ 0.000000] Machine model: goemu,riscv-emulator
...
[ 12.291753] Freeing initrd memory: 9468K
[ 18.570200] clk: Disabling unused clocks
[ 18.570327] PM: genpd: Disabling unused power domains
[ 18.570487] ALSA device list:
[ 18.570595] No soundcards found.
[ 18.582016] Freeing unused kernel image (initmem) memory: 2428K
[ 18.582810] Run /init as init process
...
Welcome to GoEMU RISC-V Linux
Kernel: 7.0.11
Machine: riscv64
buildroot login: root
login[79]: root login on 'console'
# ls -la
total 4
drwx------ 2 root root 60 Apr 19 05:25 .
drwxr-xr-x 18 root root 420 May 18 05:41 ..
-rw------- 1 root root 192 Apr 19 05:25 .ash_history
# uname -a
Linux goemu 7.0.11 #9 SMP PREEMPT Wed Jun 3 08:23:43 UTC 2026 riscv64 GNU/Linux
# date
Mon Jun 8 04:55:00 UTC 2026
# halt
...
The system is going down NOW!
Sent SIGTERM to all processes
Sent SIGKILL to all processes
Requesting system halt
[ 22.943205] reboot: System haltedThe buildroot.goemu file defines the emulator BIOS, kernel, and
rootfs arguments. Those arguments can also be specified (and
overwritten) with corresponding command line arguments:
$ ./goemu -bios opensbi/fw_jump.bin \
-kernel linux-7.0.11/Image \
-symbols linux-7.0.11/System.map \
-drive id=hd0,format=raw,file=linux-2026-04-08/rootfs.ext2 \
-device virtio-blk-device,drive=hd0 \
-append "earlycon=sbi console=ttyS0,115200 root=/dev/vda ro rootwait"The cmd/goemu directory contains *.goemu files for different
operation systems. The use OpenSBI and U-Boot for the boot process but
you must load the corresponding Operating System image to boot.
- BuildRoot - self-contained
- FreeBSD - download FreeBSD-15.1-RELEASE image
- NetBSD - download NetBSD 11.99 image
- Ubuntu-24.04 - download Ubuntu-24.04.4 image
The goemu provides also a primitive Linux userspace emulation:
$ cd cmd/goemu/
$ file examples/hello
examples/hello: ELF 64-bit LSB executable, UCB RISC-V, soft-float ABI, version 1 (SYSV), statically linked, not stripped
$ ./goemu -ktrace examples/hello
0 0 CALL write(1,10050,15)
Hello, RISC-V!
0 0 RET write 15
0 0 CALL exit(0)+--------------------+
| Linux / NetBSD / |
| FreeBSD Userspace |
+--------------------+
| Linux / NetBSD / |
| FreeBSD Kernel |
+--------------------+
| U-Boot / EFI |
+--------------------+
| OpenSBI |
+--------------------+
| RV64GC CPU |
| SV39 MMU |
| CSR subsystem |
| Interrupts |
+--------------------+
| VirtIO Block |
| UART |
| PLIC |
| ACLINT |
| Goldfish RTC |
| Syscon Poweroff |
+--------------------+
- Sustained long-term execution as a automated ports package builder
- Complete OS stability over 15+ days of in-guest uptime
- High-stress VirtIO DMA throughput validation (370+ GB written)
Uptime: 15d 3h 15m 26s
CPU:
instret : 129,725,158,326,644 instructions
runtime : 198h 19m 22s
MIPS : 181.70
I/O Telemetry:
virtio-blk : 1 GB read | 373 GB written
virtio-net : 492 MB rcvd | 51 MB sent
virtio-rng : 404 MB read
- Boot to console via OpenSBI → U-Boot → EFI
- Full native kernel compilation (
make build) inside the guest environment - Verified memory management stability under heavy parallel compiler runs
- Boot to login prompt
- Compile Hello World with GCC
- Run user binaries
- Clean shutdown
Example:
$ time cc -o hello hello.c
4.20 real 2.62 user 1.00 sys- Complete unattended system package upgrade (apt upgrade)
- Sustained high VirtIO disk and network I/O throughput over multi-hour runs
Performance improvements are tracked as the emulator evolves. The numbers below show the effect of individual optimizations. They are from running fibo.c on:
cpu: Intel(R) Core(TM) i5-8257U CPU @ 1.40GHz
| Optimization | fib 30 | fib 35 | fib 40 | MIPS | Total | Incr |
|---|---|---|---|---|---|---|
| Base | 2.969 | 32.510 | 19.75 | 1.00x | -- | |
| GC-less decode | 1.803 | 19.726 | 32.55 | 1.65x | +64.8% | |
| PTE access checks | 1.763 | 19.128 | 33.57 | 1.70x | +3.1% | |
| MMU Map fastpath | 1.709 | 18.507 | 34.70 | 1.76x | +3.4% | |
| DecodeCFast | 1.280 | 13.848 | 2m33.240s | 45.78 | 2.35x | +31.9% |
| Cached code page | 0.977 | 10.582 | 1m57.654s | 60.68 | 3.07x | +32.5% |
| Concrete memory | 0.915 | 9.835 | 1m48.920s | 65.29 | 3.31x | +7.6% |
| 32-bit decode cache | 0.684 | 7.327 | 1m20.509s | 87.64 | 4.44x | +34.2% |
| Ld/Sd TLB fastpath | 0.603 | 6.327 | 1m10.533s | 101.49 | 5.14x | +15.8% |
| Optimized Instr | 0.385 | 4.022 | 0m44.167s | 159.65 | 8.08x | +57.3% |
| Interrupts | 0.416 | 4.321 | 0m49.085s | 148.61 | 7.52x | -6.9% |
| unsafe.Pointer¹ | 0.363 | 3.668 | 0m40.373s | 175.06 | 8.86x | +17.8% |
| unsafe.Add | 0.358 | 3.582 | 0m39.659s | 179.27 | 9.08x | +2.4% |
| Interrupt opts | 0.351 | 3.542 | 0m39.144s | 181.29 | 9.18x | +1.1% |
- contains several optimizations:
- instr decode cache by
pc>>2instead ofraw>>2 - unsafe.Pointer PC load and uint64
sdandld
- Fix page table MMU code to run the existing samples
- Refactor the entire emulator stack
- CPU -> Kernel -> Process -> Emulator
- Kernel creates processes 1-n
- Virtual memory handled per process
- CPU has only {Load,Store}Uint{8,16,32,64}()
- Process has {Put,}Uint{8,16,32,64,String,Data}() functions
- Syscall can cause page fault on mmap'ed files. Syscall's traps should fill page table.
- sfence.vma must clear MMU's TLB entries
- Remove
Raw uint32fromInstr? - MMU with page tables
- Move pagetable to processes
- Refactor emulator source files
- Rethink FD handling.
- Run most Linux and Go binaries
- Proper Linux syscall support
+--------------------------------+
| 0xBFFF_FFFF |
~ ~
| 0x8000_0000 System RAM |
+--------------------------------+
| 0x1000_7FFF |
~ ~
| 0x1000_1000 VirtIO Devices |
+--------------------------------+
| 0x1000_00FF |
~ ~
| 0x1000_0000 UART (NS16550A) |
+--------------------------------+
| 0x0FFF_FFFF |
~ ~
| 0x0C00_0000 PLIC |
+--------------------------------+
| 0x0200_FFFF |
~ ~
| 0x0200_0000 CLINT |
+--------------------------------+
| 0x0000_2FFF |
~ ~
| 0x0000_1000 Boot ROM |
+--------------------------------+
+--------------------------------+
| |
~ ~
| 0x8800_0000 initrd |
+--------------------------------+
| |
~ ~
| 0x8400_0000 hw.dtb |
+--------------------------------+
| |
~ ~
| 0x8020_0000 Image |
+--------------------------------+
| |
~ ~
| 0x8000_0000 fw_boot.bin |
+--------------------------------+
On macOS host:
$ sudo sysctl -w net.inet.ip.forwarding=1Create pf.conf
nat on en0 from 192.168.42.0/24 to any -> (en0)Load conf:
$ sudo pfctl -f pf.conf
$ sudo pfctl -e$ ./goemu -cooked -htif -kernel ../../testdata/isa/rv64mi-p-illegalfails on
bad6:
# Make sure SFENCE.VMA and satp do trap when TVM=1.
sfence.vma
j fail
bad7:
csrr t0, satp
j fail
000000008000036c <bad6>:
8000036c: 12000073 sfence.vma
80000370: 0700006f j 800003e0 <fail>




