Crate rvsim

source ·
Expand description

A RISC-V simulator implementing RV32G[C].

Usage

The primary workhorse in this crate is the Interp. It takes a CpuState, Memory and Clock, then simulates a virtual CPU using these resources. CpuState is a struct, while Memory and Clock are traits, allowing complete control over the structure of the rest of the virtual machine.

When using the crate feature serialize, a CpuState can be serialized (and deserialized) in order to suspend a virtual machine to persistent storage.

A very basic ELF parser is also provided in the elf module. Rvsim itself uses this parser to run the official RISC-V test suite.

Example

extern crate rvsim;

use std::io::Write;

/// A simple `Memory` implementation, that creates an address space with just some DRAM.
struct SimpleMemory {
    dram: Vec<u8>,
}

impl SimpleMemory {
    const DRAM_BASE: u32 = 0x1000_0000;
    const DRAM_SIZE: usize = 0x10_0000;

    fn new() -> Self {
        Self { dram: vec![0; Self::DRAM_SIZE] }
    }
}

/// Our implementation of `Memory` builds a simple memory map.
///
/// The `Memory` trait is also implemented for `[u8]`, so we can simple delegate to it, after
/// translating the address.
///
/// The condition here only checks the start address of DRAM, because the upper bound is
/// already checked by the `[u8]` implementation. This type of memory map can be easily
/// extended by adding more `else if` clauses, working through blocks of memory from highest
/// base address to lowest.
impl rvsim::Memory for SimpleMemory {
    fn access<T: Copy>(&mut self, addr: u32, access: rvsim::MemoryAccess<T>) -> bool {
        if addr >= Self::DRAM_BASE {
            rvsim::Memory::access(&mut self.dram[..], addr - Self::DRAM_BASE, access)
        } else {
            false
        }
    }
}

fn main() {
    // Create the `SimpleMemory` and load some code into it.
    // Writing to the start of DRAM will put the code at `DRAM_BASE` in the address space.
    let mut mem = SimpleMemory::new();
    (&mut mem.dram[..]).write_all(&[
        0x73, 0x00, 0x10, 0x00 // `EBREAK`
    ]).unwrap();

    // We can use the very basic `Clock` implementation that is provided.
    let mut clock = rvsim::SimpleClock::new();

    // Create the virtual CPU state, setting the PC to the start of our program.
    let mut state = rvsim::CpuState::new(SimpleMemory::DRAM_BASE);

    // Run until the program stops.
    let mut interp = rvsim::Interp::new(&mut state, &mut mem, &mut clock);
    let (err, op) = interp.run();

    // The program should've stopped at the `EBREAK` instruction.
    assert_eq!(err, rvsim::CpuError::Ebreak);
    assert_eq!(op, Some(rvsim::Op::Ebreak));
}

Current limitations

  • Supports only little-endian hosts.
  • Windows support needs work.

License

Rvsim uses the MIT license, but includes portions of Berkeley SoftFloat, which uses the BSD 3-clause license. For details, see the COPYING.md file.

Modules

A simple copy-free ELF parser.
Berkeley SoftFloat bindings.

Structs

Struct containing all virtual CPU state.
The interpeter.
A simple implementation of the Clock trait.

Enums

Statuses with which virtual CPU execution may stop.
Types of memory access used with the Memory trait.
A large enum holding a parsed instruction and its arguments.

Traits

A trait used by the interpreter to implement the clock CSRs.
A trait used by the interpreter to implement loads and stores.