libriscv 1.0.0
libriscv: ^1.0.0 copied to clipboard
A RISC-V emulator in pure Dart supporting RV32/RV64 with M, A, F, D, C extensions, virtual memory, SBI runtime, and Linux/xv6 boot.
LibRISCV #
A RISC-V emulator written in pure Dart. Supports RV32 and RV64 with M, A, F, D, C, Zicsr, Zifencei, and Sstc extensions, SV32/SV39 virtual memory, a built-in SBI runtime, and a memory-mapped device model. Boots both 32-bit and 64-bit Linux to a BusyBox shell, and runs xv6.
Features #
Architecture #
- RV32I / RV64I base integer ISA
- M integer multiply/divide
- A atomics (LR/SC, AMO)
- F / D single- and double-precision IEEE 754 floating point
- C compressed instructions
- Zicsr CSR instructions
- Zifencei instruction-fetch fence
- Sstc supervisor timer (RV64)
System #
- Machine, Supervisor, and User privilege levels
- SV32 (RV32) and SV39 (RV64) page-based virtual memory with TLB
- Built-in SBI runtime — v0.1 legacy and v0.3 extensions
- Memory-mapped device model with IRQ delivery through the PLIC
- ELF binary loading via
dart_elf
Devices #
- UART 16550A — serial console with pluggable input/output callbacks
- CLINT — core-local interruptor (mtime, mtimecmp, MSIP)
- PLIC — platform-level interrupt controller
- VirtIO block — disk storage with legacy and modern MMIO layouts
- HTIF — Host–Target Interface for test harnesses
Conformance #
All 217 tests from the official RISC-V test suite pass (94 RV32 + 123 RV64), covering base ISA, M, A, F, D, C, supervisor, and machine-mode scenarios.
Install #
dependencies:
libriscv: ^1.0.0
Then:
dart pub get
Quick start #
A minimal RV32 run — allocate memory, load two instructions, step the CPU:
import 'dart:typed_data';
import 'package:libriscv/libriscv.dart';
void main() {
final emulator = EmulatorBuilder()
.xlen(XLEN.rv32)
.memorySize(4 * 1024 * 1024)
.enableRV32M(true)
.enableRV32A(true)
.enableZicsr(true)
.build();
final ram = Uint8List(4 * 1024 * 1024);
emulator.cpu.memory.add(
BasicMemoryDevice.from(ByteData.view(ram.buffer), MemoryProtections.rwx),
0x80000000,
);
final view = ByteData.view(ram.buffer);
view.setUint32(0, 0x02a00093, Endian.little); // addi x1, x0, 42
view.setUint32(4, 0x00100073, Endian.little); // ebreak
emulator.cpu.pc = 0x80000000;
emulator.step(2);
print('x1 = ${emulator.cpu.x[1]}'); // x1 = 42
}
The same example lives in example/example.dart.
Booting Linux #
LibRISCV ships with working direct-boot scripts for both RV32 and RV64 Linux kernels. They use the built-in SBI v0.1 runtime — no OpenSBI or U-Boot needed.
RV64 #
dart run example/linux/boot_rv64_direct.dart \
--kernel example/linux/Image-rv64 \
--initrd example/linux/rootfs-rv64-busybox.cpio \
--memory 512
Boots Linux 6.6 to a BusyBox shell in roughly 1.2 s of emulated time.
RV32 #
dart run example/linux/boot_rv32_direct.dart \
--kernel example/linux/Image-rv32 \
--initrd example/linux/rootfs-rv32-busybox.cpio \
--memory 256
Boots Linux 6.6 to a BusyBox shell in roughly 3.3 s of emulated time. BusyBox's
mount userspace issues one harmless SIGSEGV; the shell remains usable.
Building your own kernel and rootfs is covered in
example/linux/README.md.
Running xv6 #
The MIT teaching OS runs on RV64. You need to build xv6 yourself; the
boot script expects example/xv6/kernel and example/xv6/fs.img:
git clone https://github.com/mit-pdos/xv6-riscv
cd xv6-riscv && make
cp kernel fs.img ../example/xv6/
cd ..
dart run example/xv6/boot_xv6.dart
Other examples #
| Script | Description |
|---|---|
example/example.dart |
Minimal in-process RV32 run |
example/linux/boot_rv64_direct.dart |
Direct-boot Linux on RV64 |
example/linux/boot_rv32_direct.dart |
Direct-boot Linux on RV32 |
example/rv32/boot_rv32.dart |
Bare-metal RV32 kernel with UART + timer |
example/xv6/boot_xv6.dart |
xv6 on RV64 with VirtIO disk |
Architecture #
Emulator
├── HART (hardware thread)
│ ├── x / f register files
│ ├── CSR system
│ ├── MMU + TLB
│ └── per-extension emulators
├── Memory map
│ ├── Physical RAM
│ └── Memory-mapped devices
└── Instruction pipeline
├── Decoder (per extension)
├── Dispatcher
└── Exception handler + SBI runtime
Privilege invariants #
Two spec-critical rules are centralised in PrivilegePolicy
(lib/src/riscv/emulator/privilege_policy.dart) so that every exception
handler and every MMU shares one implementation:
- MPRV on xRET — MRET and SRET clear
MSTATUS.MPRVwhen the target privilege is below M. - S-mode access to U pages — S-mode may never execute a U=1 page; SUM only gates S-mode data loads/stores.
Regression tests live in
test/privilege_semantics_test.dart.
Development #
dart pub get # install deps
dart analyze # static analysis
dart format lib test # format
dart test # unit + compliance tests (217 RISC-V tests)
Adding a RISC-V extension #
- Write a decoder in
lib/src/riscv/decoder/. - Write the executor in
lib/src/riscv/emulator/. - Wire it into
Emulator/EmulatorBuilder. - Add compliance tests under
test/data/riscv-tests/.
Adding a device #
- Extend
MemoryMappedDevice. - Implement register read/write handlers.
- For IRQs, call into the PLIC.
- Attach to the emulator via
emulator.cpu.memory.add(device, address)or one of the fluentwithX(...)helpers.
Extension matrix #
| Extension | RV32 | RV64 |
|---|---|---|
| I (base) | ✅ | ✅ |
| M (mul/div) | ✅ | ✅ |
| A (atomic) | ✅ | ✅ |
| F (float) | ✅ | ✅ |
| D (double) | ✅ | ✅ |
| C (compressed) | ✅ | ✅ |
| Zicsr | ✅ | ✅ |
| Zifencei | ✅ | ✅ |
| Sstc (timer) | — | ✅ |
License #
MIT — see LICENSE.