Skip to content

Repository files navigation

RISC-V in Go

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

Features

  • 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.

FreeBSD Linux Boot NetBSD X11

TODO

Release 1.0

  • 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.c and gpu.go
  • riscv-arch-test
  • Haiku boot

MPC RISC-V

The project is divided into two tracks:

  1. 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.
  2. 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.

System emulation and supervisor mode

  • VirtIO
    • virtio-net
      • HTTP proxy
      • NTP server
    • virtio-console
  • SMP support
  • Parse Linux kernel PE32+ header: load address, symbols
  • JIT experiments

Userspace emulation

  • Full Linux userspace compatibility

Quick start

Clone and run:

$ git clone https://github.com/markkurossi/riscv
$ cd riscv/cmd/goemu
$ go build
$ ./goemu buildroot.goemu

Expected 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 halted

The 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.

Emulator Example

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)

Architecture

+--------------------+
| 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    |
+--------------------+

Benchmarks

Real Workloads

FreeBSD 15.1 (Port Builder Strain Test)

  • 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

OpenBSD 7.9

  • 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

NetBSD 11.99

  • 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

Ubuntu 24.04 LTS

  • Complete unattended system package upgrade (apt upgrade)
  • Sustained high VirtIO disk and network I/O throughput over multi-hour runs

Fibonacci

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%
  1. contains several optimizations:
  • instr decode cache by pc>>2 instead of raw>>2
  • unsafe.Pointer PC load and uint64 sd and ld

Appendix

Userspace Emulation - MMU Refactoring

  • 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 uint32 from Instr?
  • 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

Kernel memory map

+--------------------------------+
| 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        |
+--------------------------------+

System RAM

+--------------------------------+
|                                |
~                                ~
| 0x8800_0000    initrd          |
+--------------------------------+
|                                |
~                                ~
| 0x8400_0000    hw.dtb          |
+--------------------------------+
|                                |
~                                ~
| 0x8020_0000    Image           |
+--------------------------------+
|                                |
~                                ~
| 0x8000_0000    fw_boot.bin     |
+--------------------------------+

Network routing

On macOS host:

$ sudo sysctl -w net.inet.ip.forwarding=1

Create pf.conf

nat on en0 from 192.168.42.0/24 to any -> (en0)

Load conf:

$ sudo pfctl -f pf.conf
$ sudo pfctl -e

Conformance tests

$ ./goemu -cooked -htif -kernel ../../testdata/isa/rv64mi-p-illegal

fails 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>

About

RV64GC RISC-V emulator written in Go capable of booting Linux, NetBSD, FreeBSD, and OpenBSD with OpenSBI, U-Boot, SV39 virtual memory, and VirtIO devices.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages