irv 0.0.2

A library with a very open interface to an interpreting RISC-V emulator.
Documentation
use std::num::{NonZeroU32, NonZeroU64};

pub use irv_traits::*;

mod instruction;
mod memory;

pub use memory::Memory;

/// Exceptions that can be encountered during the execution of an instruction
/// by a [BaseHart].
///
/// These mostly line up with the list of exceptions defined by the RISC-V
/// privlieged specification, though with some minor differences and omissions.
#[derive(Debug)]
pub enum Exception {
    InstructionAddressMisaligned { address: Option<NonZeroU64> },
    InstructionAccessFault { address: Option<NonZeroU64> },
    IllegalInstruction { instruction: Option<NonZeroU32> },
    Breakpoint { address: Option<NonZeroU64> },
    LoadAddressMisaligned { address: Option<NonZeroU64> },
    LoadAccessFault { address: Option<NonZeroU64> },
    StoreAmoAddressMisaligned { address: Option<NonZeroU64> },
    StoreAmoAccessFault { address: Option<NonZeroU64> },
    EnvironmentCall,
}

/// A simple implementation of a processor that implements only machine mode.
pub struct BaseHart<B, C> {
    /// The system bus for reading and writing physical memory addresses.
    pub bus: B,
    /// The address of the currently-executing instruction, if one is being executed.
    pub pc: u64,
    /// The address of the next instruction.
    pub next: u64,
    /// The general-purpose registers x0 through x31.
    pub gpr: [u64; 32],
    /// The state of all control and status registers.
    pub csr: C,
    /// The result that will be returned after the current instruction finishes.
    result: Result<(), Exception>,
}

impl<B, C> BaseHart<B, C> {
    /// Creates a new `Hart` that connects to `bus` for memory accesses.
    pub fn new(bus: B, csr: C) -> BaseHart<B, C> {
        BaseHart {
            bus,
            pc: 0,
            next: 0,
            gpr: [0; 32],
            csr,
            result: Ok(()),
        }
    }

    /// Executes one instruction at the given address, returning the offset
    /// that should be applied to the PC to execute the next instruction.
    ///
    /// The address may have any alignment; only PC offsets are checked for
    /// alignment.
    pub fn execute(&mut self) -> Result<(), Exception>
    where
        B: Bus<u64, u64> + Bus<u64, u32> + Bus<u64, u16> + Bus<u64, u8>,
        C: Csr,
    {
        // Fetch
        let raw: u32 = match self.bus.load(self.pc) {
            Ok(raw) => raw,
            Err(BusError::AccessFault) => {
                return Err(Exception::InstructionAccessFault {
                    address: NonZeroU64::new(self.pc),
                })
            }
            Err(BusError::AddressMisaligned) => {
                return Err(Exception::InstructionAddressMisaligned {
                    address: NonZeroU64::new(self.pc),
                })
            }
        };

        // Decode the part that will be matched on
        let funct3_opcode = raw & 0b1111111 | raw >> 5 & 0b111 << 7;

        // Set x0 back to zero in case it was set by a previous instruction
        self.gpr[0] = 0;

        // Calculate the address of the next instruction.
        self.next = self.pc.wrapping_add(4);

        // Match on the opcode (and funct3) to decode the rest of the
        // instruction and execute it
        let instruction = match funct3_opcode {
            0b000_0110111 | 0b001_0110111 | 0b010_0110111 | 0b011_0110111 | 0b100_0110111
            | 0b101_0110111 | 0b110_0110111 | 0b111_0110111 => instruction::lui,
            0b000_0010111 | 0b001_0010111 | 0b010_0010111 | 0b011_0010111 | 0b100_0010111
            | 0b101_0010111 | 0b110_0010111 | 0b111_0010111 => instruction::auipc,
            0b000_1101111 | 0b001_1101111 | 0b010_1101111 | 0b011_1101111 | 0b100_1101111
            | 0b101_1101111 | 0b110_1101111 | 0b111_1101111 => instruction::jal,
            0b000_1100111 => instruction::jalr,
            0b000_1100011 => instruction::beq,
            0b001_1100011 => instruction::bne,
            0b100_1100011 => instruction::blt,
            0b101_1100011 => instruction::bge,
            0b110_1100011 => instruction::bltu,
            0b111_1100011 => instruction::bgeu,
            0b000_0000011 => instruction::lb,
            0b001_0000011 => instruction::lh,
            0b010_0000011 => instruction::lw,
            0b011_0000011 => instruction::ld,
            0b100_0000011 => instruction::lbu,
            0b101_0000011 => instruction::lhu,
            0b110_0000011 => instruction::lwu,
            0b000_0100011 => instruction::sb,
            0b001_0100011 => instruction::sh,
            0b010_0100011 => instruction::sw,
            0b011_0100011 => instruction::sd,
            0b000_0010011 => instruction::addi,
            0b000_0011011 => instruction::addiw,
            0b010_0010011 => instruction::slti,
            0b011_0010011 => instruction::sltiu,
            0b100_0010011 => instruction::xori,
            0b110_0010011 => instruction::ori,
            0b111_0010011 => instruction::andi,
            0b001_0010011 => instruction::slli,
            0b101_0010011 => instruction::srxi,
            0b001_0011011 => instruction::slliw,
            0b101_0011011 => instruction::srxiw,
            0b000_0110011 => instruction::add_sub,
            0b000_0111011 => instruction::addw_subw,
            0b001_0110011 => instruction::sll,
            0b010_0110011 => instruction::slt,
            0b011_0110011 => instruction::sltu,
            0b100_0110011 => instruction::xor,
            0b101_0110011 => instruction::srx,
            0b110_0110011 => instruction::or,
            0b111_0110011 => instruction::and,
            0b001_0111011 => instruction::sllw,
            0b101_0111011 => instruction::srxw,
            0b000_0001111 => instruction::fence,
            0b001_0001111 => instruction::fence_i,
            0b000_1110011 => instruction::ecall_ebreak,
            0b001_1110011 => instruction::csrrw,
            0b010_1110011 => instruction::csrrs,
            0b011_1110011 => instruction::csrrc,
            0b101_1110011 => instruction::csrrwi,
            0b110_1110011 => instruction::csrrsi,
            0b111_1110011 => instruction::csrrci,
            _ => |hart: &mut BaseHart<B, C>, raw| {
                hart.raise(Exception::IllegalInstruction {
                    instruction: NonZeroU32::new(raw),
                })
            },
        };

        instruction(self, raw);

        self.pc = self.next;

        let mut result = Ok(());
        std::mem::swap(&mut self.result, &mut result);
        result
    }

    /// Sets up state for the given exception to be raised after execution is finished.
    fn raise(&mut self, exception: Exception) {
        self.result = Err(exception);
    }
}