Add first basic implementation of a RISC-V hart.

This commit add the main components to run a simple RISC-V hart.
This hart implement all the RV32I instructions (excepted FENCE and
System instruction ones).
Multicore is not implemented as it would need first to implement the
RISC-V Weak Memory Ordering model into our harts.
This commit is contained in:
Victor Mignot 2024-05-10 14:53:37 +02:00
parent f28056da82
commit a22e0a7738
Signed by: dala
GPG key ID: 5E7F2CE1BEAFED3D
19 changed files with 3029 additions and 2 deletions

View file

@ -1,2 +1,5 @@
//! The core module contains each software component that is used to run the main features of a RISC-V hart and common devices.
pub mod address_space;
pub mod bus;
pub mod isa;

126
src/core/bus.rs Normal file
View file

@ -0,0 +1,126 @@
//! Basic implementation for a data bus.
#![allow(unused)]
use crate::core::address_space::{AddressSpace, Addressable};
/// A trait that should be implemented for a memory-mapped device to give its behaviour during a memory read.
pub trait Readable: Addressable {
/// Function called when a request is done to read a byte at the given `offset` of the device memory range.
fn read_byte(&self, offset: u32) -> u8;
/// Function called when a request is done to read a halfword (2 bytes) at the given `offset` of the device memory range.
fn read_halfword(&self, offset: u32) -> u16;
/// Function called when a request is done to read a word (4 bytes) at the given `offset` of the device memory range.
fn read_word(&self, offset: u32) -> u32;
}
/// A trait that should be implemented for a memory-mapped device to specify its behaviour during a memory write.
pub trait Writable: Addressable {
/// Function called when a request is done to write a byte at the given `offset` of the device memory range.
fn write_byte(&mut self, offset: u32, value: u8);
/// Function called when a request is done to write a halfword (2 bytes) at the given `offset` of the device memory range.
fn write_halfword(&mut self, offset: u32, value: u16);
/// Function called when a request is done to write a word (4 bytes) at the given `offset` of the device memory range.
fn write_word(&mut self, offset: u32, value: u32);
}
/// A device that can be mapped to a bus, and handle memory read and write (mainly I/O devices).
pub trait BusDevice: Readable + Writable {}
/// A bus to which different object such as I/O devices can be mapped to specific address ranges.
pub struct Bus {
/// The inner address space of the bus, to which object implementing the `BusDevice` trait will be mapped.
address_space: AddressSpace<dyn BusDevice>,
}
impl Bus {
/// Instantiate a new `Bus` struct.
pub fn new() -> Self {
Bus {
address_space: AddressSpace::new(),
}
}
/// Map the given `device` at the specific `address` on the bus.
pub fn map(&mut self, device: impl BusDevice + 'static, address: u32) {
self.address_space.map_object(Box::new(device), address);
}
/// Make a write request of the byte `value` at the given `address` on the bus.
pub fn write_byte(&self, address: u32, value: u8) {
match self.address_space.get_mapped_object_to_address(address) {
Some((device, device_addr)) => device
.write()
.unwrap()
.write_byte(address - device_addr, value),
None => eprintln!("Tried to write to the unmapped address: {address}"),
}
}
/// Make a write request at the given `address` for the halfword `value` on the bus.
pub fn write_halfword(&self, address: u32, value: u16) {
match self.address_space.get_mapped_object_to_address(address) {
Some((device, device_addr)) => device
.write()
.unwrap()
.write_halfword(address - device_addr, value),
None => eprintln!("Tried to write to the unmapped address: {address}"),
}
}
/// Make a write request at the given `address` for the word `value` on the bus.
pub fn write_word(&self, address: u32, value: u32) {
match self.address_space.get_mapped_object_to_address(address) {
Some((device, device_addr)) => device
.write()
.unwrap()
.write_word(address - device_addr, value),
None => eprintln!("Tried to write to the unmapped address: {address}"),
}
}
/// Make a read request at the given `address` for a byte on the bus.
pub fn read_byte(&self, address: u32) -> u8 {
match self.address_space.get_mapped_object_to_address(address) {
Some((device, device_addr)) => device.read().unwrap().read_byte(address - device_addr),
None => {
eprintln!("Tried to read to the unmapped address: {address}");
0
}
}
}
/// Make a read request at the given `address` for a halfword on the bus.
pub fn read_halfword(&self, address: u32) -> u16 {
match self.address_space.get_mapped_object_to_address(address) {
Some((device, device_addr)) => {
device.read().unwrap().read_halfword(address - device_addr)
}
None => {
eprintln!("Tried to read to the unmapped address: {address}");
0
}
}
}
/// Make a read request at the given `address` for a word on the bus.
pub fn read_word(&self, address: u32) -> u32 {
match self.address_space.get_mapped_object_to_address(address) {
Some((device, device_addr)) => device.read().unwrap().read_word(address - device_addr),
None => {
eprintln!("Tried to read to the unmapped address: {address}");
0
}
}
}
}
impl Default for Bus {
fn default() -> Self {
Bus::new()
}
}

31
src/core/isa.rs Normal file
View file

@ -0,0 +1,31 @@
//! Module to deal with the RISC-V ISA (base and extensions).
//! This is where is implemented any software component that make our harts RISC-V ISA compliant.
pub use hart::RVHart;
pub use rv32i::{decode_rv32i_instruction, RV32I_OPCODES};
pub mod hart;
mod rv32i;
/// All the possible exception issued by a RISC-V hart.
#[derive(Debug)]
pub enum RVException {
/// Error while decoding a RISC-V instruction.
InstructionDecodeError,
}
/// The trait to be implemented for each instruction.
pub trait Instruction {
/// Execute the instruction on the given `core`.
fn execute(&self, core: &mut RVHart) -> Result<(), RVException>;
}
/// Return the instruction implementation to be executed from a given raw `instruction`.
pub fn decode_instruction(instruction: u32) -> Result<Box<dyn Instruction>, RVException> {
let instruction = match instruction & 0x7f {
x if RV32I_OPCODES.contains(&x) => Ok(decode_rv32i_instruction(instruction)),
_ => Err(RVException::InstructionDecodeError),
}?;
Ok(Box::new(instruction))
}

91
src/core/isa/hart.rs Normal file
View file

@ -0,0 +1,91 @@
//! The dremu implementation for the RISC-V hart (hardware thread) concept.
//! In real implementations, Harts are oftenly implemented as CPU cores.
#![allow(unused)]
use crate::core::{
bus::{Bus, BusDevice},
isa::decode_instruction,
};
/// A RISC-V compliant RISC-V 32 bits hart.
pub struct RVHart {
/// The Program Counter register.
pub pc: u32,
/// The X register array.
pub x: [u32; 32],
/// The hart main bus, which each I/O devices and avaible memory space will be mapped to.
system_bus: Bus,
}
impl RVHart {
/// Create a new RISC-V hart and initialize the inner registers with 0.
pub fn new() -> Self {
RVHart {
pc: 0,
x: [0; 32],
system_bus: Bus::new(),
}
}
/// The hart main loop, fetching and decoding instructions located on the PC address, then executing it and incrementing the PC to the next instruction.
pub fn start_main_loop(&mut self) -> ! {
loop {
let previous_pc = self.pc;
let instruction = decode_instruction(self.system_bus.read_word(self.pc)).unwrap();
instruction.execute(self);
// We dont want to increase the program counter if the previous instruction was a branch.
if self.pc == previous_pc {
self.pc += 4;
};
// The x0 register is hardwired to 0 value.
// We set it back to 0 if it was modified by the executed instruction.
self.x[0] = 0x0;
}
}
/// Map a `BusDevice` object into the hart system bus at the given `address`.
pub fn map_device(&mut self, device: impl BusDevice + 'static, address: u32) {
self.system_bus.map(device, address % u32::MAX)
}
/// Write a word (4 bytes) to the core system bus at the given `address`.
pub fn store_word(&self, address: u32, value: u32) {
self.system_bus.write_word(address % u32::MAX, value)
}
/// Write a halfword (2 bytes) to the core system bus at the given `address`.
pub fn store_halfword(&self, address: u32, value: u16) {
self.system_bus.write_halfword(address % u32::MAX, value)
}
/// Write a byte into the core system bus as the given `address`.
pub fn store_byte(&self, address: u32, value: u8) {
self.system_bus.write_byte(address % u32::MAX, value)
}
/// Read a word (4 bytes) from the given `address` from the core system bus.
pub fn load_word(&self, address: u32) -> u32 {
self.system_bus.read_word(address % u32::MAX)
}
/// Read a halfword (2 bytes) from the given `address` from the core system bus.
pub fn load_halfword(&self, address: u32) -> u16 {
self.system_bus.read_halfword(address % u32::MAX)
}
/// Read a byte from the given `address` from the core system bus.
pub fn load_byte(&self, address: u32) -> u8 {
self.system_bus.read_byte(address % u32::MAX)
}
}
impl Default for RVHart {
fn default() -> Self {
RVHart::new()
}
}

110
src/core/isa/rv32i.rs Normal file
View file

@ -0,0 +1,110 @@
//! The RV32I base instruction set for 32-bits RISC-V processor.
//! This module define and implement all the instructions from the set.
#![allow(clippy::upper_case_acronyms)]
use self::{
auipc::{AUIPC, AUIPC_OPCODE},
branch::{BranchInstruction, BRANCH_OPCODE},
fence::{FENCE, FENCE_OPCODE},
integer_register_immediate::{
IntegerRegisterImmediateInstruction, INTEGER_REGISTER_IMMEDIATE_OPCODE,
},
integer_register_register::{
IntegerRegisterRegisterInstruction, INTEGER_REGISTER_REGISTER_OPCODE,
},
jal::{JAL, JAL_OPCODE},
jalr::{JALR, JALR_OPCODE},
load::{LoadInstruction, LOAD_OPCODE},
lui::{LUI, LUI_OPCODE},
store::{StoreInstruction, STORE_OPCODE},
system::{SystemInstruction, SYSTEM_OPCODE},
};
use crate::core::{
isa::RVHart,
isa::{Instruction, RVException},
};
mod auipc;
mod branch;
mod fence;
mod integer_register_immediate;
mod integer_register_register;
mod jal;
mod jalr;
mod load;
mod lui;
mod store;
mod system;
/// All the RV32I opcodes.
pub const RV32I_OPCODES: [u32; 11] = [
INTEGER_REGISTER_IMMEDIATE_OPCODE,
INTEGER_REGISTER_REGISTER_OPCODE,
LUI_OPCODE,
AUIPC_OPCODE,
JAL_OPCODE,
JALR_OPCODE,
BRANCH_OPCODE,
LOAD_OPCODE,
STORE_OPCODE,
FENCE_OPCODE,
SYSTEM_OPCODE,
];
/// An enumeration of all the available RV32I instruction implementation.
#[allow(clippy::missing_docs_in_private_items)]
pub enum RV32IInstruction {
IntegerRegisterImmediate(IntegerRegisterImmediateInstruction),
IntegerRegisterRegister(IntegerRegisterRegisterInstruction),
LUI(LUI),
AUIPC(AUIPC),
JAL(JAL),
JALR(JALR),
Branch(BranchInstruction),
Load(LoadInstruction),
Store(StoreInstruction),
FENCE(FENCE),
System(SystemInstruction),
}
impl Instruction for RV32IInstruction {
fn execute(&self, core: &mut RVHart) -> Result<(), RVException> {
match self {
RV32IInstruction::IntegerRegisterImmediate(instruction) => instruction.execute(core),
RV32IInstruction::IntegerRegisterRegister(instruction) => instruction.execute(core),
RV32IInstruction::LUI(instruction) => instruction.execute(core),
RV32IInstruction::AUIPC(instruction) => instruction.execute(core),
RV32IInstruction::JAL(instruction) => instruction.execute(core),
RV32IInstruction::JALR(instruction) => instruction.execute(core),
RV32IInstruction::Branch(instruction) => instruction.execute(core),
RV32IInstruction::Load(instruction) => instruction.execute(core),
RV32IInstruction::Store(instruction) => instruction.execute(core),
RV32IInstruction::FENCE(instruction) => instruction.execute(core),
RV32IInstruction::System(instruction) => instruction.execute(core),
}
}
}
/// Return the matching implementation of a RV32I instruction passed by its raw form through the `instruction` parameter.
pub fn decode_rv32i_instruction(instruction: u32) -> RV32IInstruction {
match instruction & 0x7f {
INTEGER_REGISTER_IMMEDIATE_OPCODE => RV32IInstruction::IntegerRegisterImmediate(
IntegerRegisterImmediateInstruction::new(instruction),
),
INTEGER_REGISTER_REGISTER_OPCODE => RV32IInstruction::IntegerRegisterRegister(
IntegerRegisterRegisterInstruction::new(instruction),
),
LUI_OPCODE => RV32IInstruction::LUI(LUI::new(instruction)),
AUIPC_OPCODE => RV32IInstruction::AUIPC(AUIPC::new(instruction)),
JAL_OPCODE => RV32IInstruction::JAL(JAL::new(instruction)),
JALR_OPCODE => RV32IInstruction::JALR(JALR::new(instruction)),
BRANCH_OPCODE => RV32IInstruction::Branch(BranchInstruction::new(instruction)),
LOAD_OPCODE => RV32IInstruction::Load(LoadInstruction::new(instruction)),
STORE_OPCODE => RV32IInstruction::Store(StoreInstruction::new(instruction)),
FENCE_OPCODE => RV32IInstruction::FENCE(FENCE::new(instruction)),
SYSTEM_OPCODE => RV32IInstruction::System(SystemInstruction::new(instruction)),
_ => unimplemented!("Exceptions not handled"),
}
}

View file

@ -0,0 +1,80 @@
//! The RV32I AUIPC instruction.
//! This instruction add the signed 20 bits immediate to the PC register and store it in the
//! given destination register.
use crate::core::isa::{Instruction, RVException, RVHart};
/// The opcode for the AUIPC instruction.
pub const AUIPC_OPCODE: u32 = 0x17;
/// The start bit of the `rd` register on the 32-bits instrction field.
const RD_START_BIT: usize = 7;
/// Mask to apply on top of the 32-bits instruction to get a register field after having shifted it to the field start.
const REGISTER_FIELD_MASK: u32 = 0x1f;
/// Mask to apply on top of the 32-bits instruction to get the `imm` value.
const IMM_FIELD_MASK: u32 = 0xfffff000;
/// The AUIPC operation representation, ready to be executed.
pub struct AUIPC {
/// The destination register for the `AUIPC` command.
rd: usize,
/// The 20 most significant bytes of the value that will be added to the `pc` register value to be stored into the `rd` register.
imm: u32,
}
impl AUIPC {
/// Create and load the AUIPC instruction with its decoded operands.
pub fn new(instruction: u32) -> Self {
let rd = ((instruction >> RD_START_BIT) & REGISTER_FIELD_MASK) as usize;
let imm = instruction & IMM_FIELD_MASK;
AUIPC { rd, imm }
}
}
impl Instruction for AUIPC {
fn execute(&self, core: &mut RVHart) -> Result<(), RVException> {
let signed_pc = core.pc as i32;
let signed_offset = self.imm as i32;
core.x[self.rd] = signed_pc.wrapping_add(signed_offset) as u32;
Ok(())
}
}
#[cfg(test)]
mod test {
use crate::core::isa::{Instruction as _, RVHart};
use super::AUIPC;
#[test]
fn auipc_positive() {
// auipc x1, 0x10
let instruction: u32 = 0b00000000000000010000000010010111;
let mut core = RVHart::new();
core.pc = 0x00010000;
AUIPC::new(instruction).execute(&mut core).unwrap();
assert_eq!(core.pc, 0x00010000);
assert_eq!(core.x[1], 0x00020000);
}
#[test]
fn auipc_negative() {
// auipc x1, -0x10
let instruction: u32 = 0b11111111111111110000000010010111;
let mut core = RVHart::new();
core.pc = 0x00010000;
AUIPC::new(instruction).execute(&mut core).unwrap();
assert_eq!(core.pc, 0x00010000);
assert_eq!(core.x[1], 0);
}
}

View file

@ -0,0 +1,762 @@
//! All the branch instructions sharing the same opcode.
//! Each instruction branch according to a specific comparison between the two given register.
//! When branching, they add the given immediate to the Program Counter register.
//! They all use the B-type instruction format.
use crate::core::isa::{Instruction, RVException, RVHart};
/// The opcode for the branch instructions.
pub const BRANCH_OPCODE: u32 = 0x63;
/// The start bit of the `rs1` field in the 32-bits encoded instruction.
const RS1_START_BIT: usize = 15;
/// The start bit of the `rs2` field in the 32-bits encoded instruction.
const RS2_START_BIT: usize = 20;
/// The start bit of the `imm[4:1]` field in the 32-bits encoded instruction.
const IMM_4_1_START_BIT: usize = 8;
/// The start bit of the `imm[10:5]` field in the 32-bits encoded instruction.
const IMM_10_5_START_BIT: usize = 25;
/// The start bit of the `imm[11]` field in the 32-bits encoded instruction.
const IMM_11_START_BIT: usize = 7;
/// The start bit of the `imm[12]` field in the 32-bits encoded instruction.
const IMM_12_START_BIT: usize = 31;
/// The start bit of the `funct3` field in the 32-bits encoded instruction.
const FUNCT3_START_BIT: usize = 12;
/// The mask to apply on top of the 32-bits instruction after having shifted it to a register field start to extract it.
const REGISTER_FIELD_MASK: u32 = 0x1f;
/// The mask to apply on top of the 32-bits instruction after having shifted to `IMM_4_1_START_BIT`.
const IMM_4_1_FIELD_MASK: u32 = 0xf;
/// The mask to apply on top of the 32-bits instruction after having shifted to `IMM_10_5_START_BIT`.
const IMM_10_5_FIELD_MASK: u32 = 0x3f;
/// The mask to apply on top of the 32-bits instruction after having shifted to `IMM_11_START_BIT`.
const IMM_11_FIELD_MASK: u32 = 0x1;
/// The mask to apply on top of the 32-bits instruction after having shifted to `FUNCT3_START_BIT`.
const FUNCT3_FIELD_MASK: u32 = 0b111;
/// The `funct3` field value for the BEQ operation.
const FUNCT3_BEQ_CODE: u32 = 0b000;
/// The `funct3` field value for the BNE operation.
const FUNCT3_BNE_CODE: u32 = 0b001;
/// The `funct3` field value for the BLT operation.
const FUNCT3_BLT_CODE: u32 = 0b100;
/// The `funct3` field value for the BGE operation.
const FUNCT3_BGE_CODE: u32 = 0b101;
/// The `funct3` field value for the BLTU operation.
const FUNCT3_BLTU_CODE: u32 = 0b110;
/// The `funct3` field value for the BGEU operation.
const FUNCT3_BGEU_CODE: u32 = 0b111;
/// All the available branch operations.
enum BranchOperation {
/// Branch if EQual.
/// Will trigger the branch if the `rs1` and `rs2` register values are equal.
BEQ,
/// Branch if Not Equal.
/// Will trigger the branch if the `rs1` and `rs2` register values are not equal.
BNE,
/// Branch if Lesser Than.
/// Will trigger the branch if the `rs1` register value is lesser than the `rs2` one if both are considered as signed integers.
BLT,
/// Branch if Lesser Than Unsigned.
/// Will branch if the `rs1` register value is lesser than the `rs2` one if both are considered as unsigned integers.
BLTU,
/// Branch if Greater or Equal.
/// Will branch if the `rs1` register value is greater or equal than the `rs2` register value if both are considered as signed integers.
BGE,
/// Branch if Greater or Equal Unsigned.
/// Will branch if the `rs1` register value is greater or equal than the `rs2` register value if both are considered as unsigned integers.
BGEU,
/// An unknown operation.
/// Will be used if the `funct3` of the Branch instruction does not match with any of the codes of the above operations.
Unknown,
}
/// A decoded branch instruction, ready to be executed.
pub struct BranchInstruction {
/// The index of the first hart's X source register.
rs1: usize,
/// The index of the second hart's X source register to which `rs1` will be compared with.
rs2: usize,
/// The branch condition.
funct3: BranchOperation,
/// The sign-extended 12-bit value that the hart's pc register will be incremented if the `funct3` condition is met.
imm: u32,
}
impl BranchInstruction {
/// Decode the given raw `instruction` into a `BranchInstruction` ready to be executed.
pub fn new(instruction: u32) -> Self {
let rs1 = ((instruction >> RS1_START_BIT) & REGISTER_FIELD_MASK) as usize;
let rs2 = ((instruction >> RS2_START_BIT) & REGISTER_FIELD_MASK) as usize;
let imm_4_1 = (instruction >> IMM_4_1_START_BIT) & IMM_4_1_FIELD_MASK;
let imm_10_5 = (instruction >> IMM_10_5_START_BIT) & IMM_10_5_FIELD_MASK;
let imm_11 = (instruction >> IMM_11_START_BIT) & IMM_11_FIELD_MASK;
let imm_12 = (instruction as i32 >> IMM_12_START_BIT) as u32;
let imm = (imm_4_1 << 1) + (imm_10_5 << 5) + (imm_11 << 11) + (imm_12 << 12);
let funct3 = match (instruction >> FUNCT3_START_BIT) & FUNCT3_FIELD_MASK {
FUNCT3_BEQ_CODE => BranchOperation::BEQ,
FUNCT3_BNE_CODE => BranchOperation::BNE,
FUNCT3_BLT_CODE => BranchOperation::BLT,
FUNCT3_BGE_CODE => BranchOperation::BGE,
FUNCT3_BLTU_CODE => BranchOperation::BLTU,
FUNCT3_BGEU_CODE => BranchOperation::BGEU,
_ => BranchOperation::Unknown,
};
BranchInstruction {
rs1,
rs2,
funct3,
imm,
}
}
}
impl Instruction for BranchInstruction {
fn execute(&self, core: &mut RVHart) -> Result<(), RVException> {
let take_branch = match self.funct3 {
BranchOperation::BEQ => Ok(core.x[self.rs1] == core.x[self.rs2]),
BranchOperation::BNE => Ok(core.x[self.rs1] != core.x[self.rs2]),
BranchOperation::BLT => Ok((core.x[self.rs1] as i32) < (core.x[self.rs2] as i32)),
BranchOperation::BLTU => Ok(core.x[self.rs1] < core.x[self.rs2]),
BranchOperation::BGE => Ok((core.x[self.rs1] as i32) >= (core.x[self.rs2] as i32)),
BranchOperation::BGEU => Ok(core.x[self.rs1] >= core.x[self.rs2]),
BranchOperation::Unknown => Err(RVException::InstructionDecodeError),
}?;
if take_branch {
core.pc = (core.pc as i32).wrapping_add(self.imm as i32) as u32;
}
Ok(())
}
}
#[cfg(test)]
mod test {
mod beq {
use crate::core::isa::{rv32i::branch::BranchInstruction, Instruction as _, RVHart};
#[test]
fn beq_equal() {
// beq x1, x2, 0x10
let instruction = 0x00208863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x1;
core.x[2] = 0x1;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x60);
assert_eq!(core.x[1], 0x1);
assert_eq!(core.x[2], 0x1);
}
#[test]
fn beq_not_equal() {
// beq x1, x2, 0x10
let instruction = 0x00208863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x1;
core.x[2] = 0x0;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x50);
assert_eq!(core.x[1], 0x1);
assert_eq!(core.x[2], 0x0);
}
#[test]
fn beq_negative_offset() {
// beq x1, x2, -0x10
let instruction = 0xfe2088e3;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x1;
core.x[2] = 0x1;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x40);
assert_eq!(core.x[1], 0x1);
assert_eq!(core.x[2], 0x1);
}
}
mod bne {
use crate::core::isa::{rv32i::branch::BranchInstruction, Instruction as _, RVHart};
#[test]
fn bne_equal() {
// bne x1, x2, 0x10
let instruction = 0x00209863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x1;
core.x[2] = 0x1;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x50);
assert_eq!(core.x[1], 0x1);
assert_eq!(core.x[2], 0x1);
}
#[test]
fn bne_not_equal() {
// bne x1, x2, 0x10
let instruction = 0x00209863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x1;
core.x[2] = 0x0;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x60);
assert_eq!(core.x[1], 0x1);
assert_eq!(core.x[2], 0x0);
}
#[test]
fn bne_negative_offset() {
// bne x1, x2, -0x10
let instruction = 0xfe2098e3;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x1;
core.x[2] = 0x0;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x40);
assert_eq!(core.x[1], 0x1);
assert_eq!(core.x[2], 0x0);
}
}
mod blt {
use crate::core::isa::{rv32i::branch::BranchInstruction, Instruction as _, RVHart};
#[test]
fn blt_positive_positive_inferior() {
// blt x1, x2, 0x10
let instruction = 0x0020c863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x5;
core.x[2] = 0x10;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x60);
assert_eq!(core.x[1], 0x5);
assert_eq!(core.x[2], 0x10);
}
#[test]
fn blt_positive_positive_equal() {
// blt x1, x2, 0x10
let instruction = 0x0020c863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x5;
core.x[2] = 0x5;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x50);
assert_eq!(core.x[1], 0x5);
assert_eq!(core.x[2], 0x5);
}
#[test]
fn blt_positive_positive_superior() {
// blt x1, x2, 0x10
let instruction = 0x0020c863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x10;
core.x[2] = 0x5;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x50);
assert_eq!(core.x[1], 0x10);
assert_eq!(core.x[2], 0x5);
}
#[test]
fn blt_positive_negative() {
// blt x1, x2, 0x10
let instruction = 0x0020c863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x10i32 as u32;
core.x[2] = -0x5i32 as u32;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x50);
assert_eq!(core.x[1] as u32, 0x10);
assert_eq!(core.x[2] as i32, -0x5);
}
#[test]
fn blt_negative_positive() {
// blt x1, x2, 0x10
let instruction = 0x0020c863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = -0x10i32 as u32;
core.x[2] = 0x5i32 as u32;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x60);
assert_eq!(core.x[1] as i32, -0x10);
assert_eq!(core.x[2] as i32, 0x5);
}
#[test]
fn blt_negative_negative_superior() {
// blt x1, x2, 0x10
let instruction = 0x0020c863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = -0x5i32 as u32;
core.x[2] = -0x10i32 as u32;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x50);
assert_eq!(core.x[1] as i32, -0x5);
assert_eq!(core.x[2] as i32, -0x10);
}
#[test]
fn blt_negative_negative_equal() {
// blt x1, x2, 0x10
let instruction = 0x0020c863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = -0x5i32 as u32;
core.x[2] = -0x5i32 as u32;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x50);
assert_eq!(core.x[1] as i32, -0x5);
assert_eq!(core.x[2] as i32, -0x5);
}
#[test]
fn blt_negative_offset() {
// blt x1, x2, -0x10
let instruction = 0xfe20c8e3;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x5;
core.x[2] = 0x10;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x40);
assert_eq!(core.x[1], 0x5);
assert_eq!(core.x[2], 0x10);
}
}
mod bltu {
use crate::core::isa::{rv32i::branch::BranchInstruction, Instruction as _, RVHart};
#[test]
fn bltu_inferior() {
// bltu x1, x2, 0x10
let instruction = 0x0020e863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x5;
core.x[2] = 0x10;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x60);
assert_eq!(core.x[1], 0x5);
assert_eq!(core.x[2], 0x10);
}
#[test]
fn bltu_equal() {
// bltu x1, x2, 0x10
let instruction = 0x0020e863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x10;
core.x[2] = 0x10;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x50);
assert_eq!(core.x[1], 0x10);
assert_eq!(core.x[2], 0x10);
}
#[test]
fn bltu_superior() {
// bltu x1, x2, 0x10
let instruction = 0x0020e863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x10;
core.x[2] = 0x5;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x50);
assert_eq!(core.x[1], 0x10);
assert_eq!(core.x[2], 0x5);
}
#[test]
fn bltu_negative_offset() {
// bltu x1, x2, -0x10
let instruction = 0xfe20e8e3;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x5;
core.x[2] = 0x10;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x40);
assert_eq!(core.x[1], 0x5);
assert_eq!(core.x[2], 0x10);
}
}
mod bge {
use crate::core::isa::{rv32i::branch::BranchInstruction, Instruction as _, RVHart};
#[test]
fn bge_positive_positive_superior() {
// bge x1, x2, 0x10
let instruction = 0x0020d863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x10;
core.x[2] = 0x5;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x60);
assert_eq!(core.x[1], 0x10);
assert_eq!(core.x[2], 0x5);
}
#[test]
fn bge_positive_positive_equal() {
// bge x1, x2, 0x10
let instruction = 0x0020d863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x10;
core.x[2] = 0x10;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x60);
assert_eq!(core.x[1], 0x10);
assert_eq!(core.x[2], 0x10);
}
#[test]
fn bge_positive_positive_inferior() {
// bge x1, x2, 0x10
let instruction = 0x0020d863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x5;
core.x[2] = 0x10;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x50);
assert_eq!(core.x[1], 0x5);
assert_eq!(core.x[2], 0x10);
}
#[test]
fn bge_positive_negative() {
// bge x1, x2, 0x10
let instruction = 0x0020d863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x5i32 as u32;
core.x[2] = -0x10i32 as u32;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x60);
assert_eq!(core.x[1] as i32, 0x5);
assert_eq!(core.x[2] as i32, -0x10);
}
#[test]
fn bge_negative_positive() {
// bge x1, x2, 0x10
let instruction = 0x0020d863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = -0x5i32 as u32;
core.x[2] = 0x10i32 as u32;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x50);
assert_eq!(core.x[1] as i32, -0x5);
assert_eq!(core.x[2] as i32, 0x10);
}
#[test]
fn bge_negative_negative_superior() {
// bge x1, x2, 0x10
let instruction = 0x0020d863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = -0x5i32 as u32;
core.x[2] = -0x10i32 as u32;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x60);
assert_eq!(core.x[1] as i32, -0x5);
assert_eq!(core.x[2] as i32, -0x10);
}
#[test]
fn bge_negative_negative_equal() {
// bge x1, x2, 0x10
let instruction = 0x0020d863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = -0x5i32 as u32;
core.x[2] = -0x5i32 as u32;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x60);
assert_eq!(core.x[1] as i32, -0x5);
assert_eq!(core.x[2] as i32, -0x5);
}
#[test]
fn bge_negative_negative_inferior() {
// bge x1, x2, 0x10
let instruction = 0x0020d863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = -0x10i32 as u32;
core.x[2] = -0x5i32 as u32;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x50);
assert_eq!(core.x[1] as i32, -0x10);
assert_eq!(core.x[2] as i32, -0x5);
}
#[test]
fn bge_negative_offset() {
// bge x1, x2, -0x10
let instruction = 0xfe20d8e3;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x10;
core.x[2] = 0x10;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x40);
assert_eq!(core.x[1], 0x10);
assert_eq!(core.x[2], 0x10);
}
}
mod bgeu {
use crate::core::isa::{rv32i::branch::BranchInstruction, Instruction as _, RVHart};
#[test]
fn bgeu_superior() {
// bgeu x1, x2, 0x10
let instruction = 0x0020f863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x15;
core.x[2] = 0x10;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x60);
assert_eq!(core.x[1], 0x15);
assert_eq!(core.x[2], 0x10);
}
#[test]
fn bgeu_equal() {
// bgeu x1, x2, 0x10
let instruction = 0x0020f863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x10;
core.x[2] = 0x10;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x60);
assert_eq!(core.x[1], 0x10);
assert_eq!(core.x[2], 0x10);
}
#[test]
fn bgeu_inferior() {
// bgeu x1, x2, 0x10
let instruction = 0x0020f863;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x5;
core.x[2] = 0x10;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x50);
assert_eq!(core.x[1], 0x5);
assert_eq!(core.x[2], 0x10);
}
#[test]
fn bgeu_negative_offset() {
// bgeu x1, x2, -0x10
let instruction = 0xfe20f8e3;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x15;
core.x[2] = 0x10;
BranchInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.pc, 0x40);
assert_eq!(core.x[1], 0x15);
assert_eq!(core.x[2], 0x10);
}
}
}

View file

@ -0,0 +1,99 @@
//! The RV32I FENCE Instruction.
//! The RISC-V does not guarantee that a hart will see the instruction executed by the other harts in the right order.
//! the FENCE operation is here to be used as a synchronisation point that will guarantee that for any other hart observing the issuer instruction order,
//! all the instruction issued before the FENCE was in fact executed before any operation executed after the FENCE.
//! It's only useful when the implementation use multiple hart (which is currently not the case for dremu).
//! This instruction is currently a NOOP.
use crate::core::isa::{Instruction, RVException, RVHart};
/// The FENCE instruction opcode.
pub const FENCE_OPCODE: u32 = 0x0f;
/// The encoded instruction's `fm` field start bit.
const FM_START_BIT: usize = 28;
/// The encoded instruction's `PI` field bit.
const PI_BIT: usize = 27;
/// The encoded instruction's `PO` field bit.
const PO_BIT: usize = 26;
/// The encoded instruction's `PR` field bit.
const PR_BIT: usize = 25;
/// The encoded instruction's `PW` field bit.
const PW_BIT: usize = 24;
/// The encoded instruction's `SI` field bit.
const SI_BIT: usize = 23;
/// The encoded instruction's `SO` field bit.
const SO_BIT: usize = 22;
/// The encoded instruction's `SR` field bit.
const SR_BIT: usize = 21;
/// The encoded instruction's `SW` field bit.
const SW_BIT: usize = 20;
/// A decoded FENCE instruction ready to be executed.
#[allow(dead_code)]
pub struct FENCE {
/// The fence mode.
fm: u32,
/// The predecessor device input flag.
pi: bool,
/// The predecessor device output flag.
po: bool,
/// The predecessor memory reads flag.
pr: bool,
/// The predecessor memory writes flag.
pw: bool,
/// The sucessor device input flag.
si: bool,
/// The successor device output flag.
so: bool,
/// The successor memory reads flag.
sr: bool,
/// The successor memory writes flag.
sw: bool,
}
impl FENCE {
/// Create and decode a `FENCE` instruction from a raw `instruction`, and make it ready to be executed.
pub fn new(instruction: u32) -> Self {
let fm = instruction >> FM_START_BIT;
let pi = ((instruction >> PI_BIT) & 0x1) != 0;
let po = ((instruction >> PO_BIT) & 0x1) != 0;
let pr = ((instruction >> PR_BIT) & 0x1) != 0;
let pw = ((instruction >> PW_BIT) & 0x1) != 0;
let si = ((instruction >> SI_BIT) & 0x1) != 0;
let so = ((instruction >> SO_BIT) & 0x1) != 0;
let sr = ((instruction >> SR_BIT) & 0x1) != 0;
let sw = ((instruction >> SW_BIT) & 0x1) != 0;
FENCE {
fm,
pi,
po,
pr,
pw,
si,
so,
sr,
sw,
}
}
}
impl Instruction for FENCE {
fn execute(&self, _core: &mut RVHart) -> Result<(), RVException> {
Ok(())
}
}
#[cfg(test)]
mod test {
// TODO: Add test when multi-hart emulation will be available.
}

View file

@ -0,0 +1,483 @@
//! Define executions of all the RV32I's Integer Register-Immediate instructions.
//! In RV32I, they share all the same opcode (0xb0010011) and they are executed
//! according to the funct3 field (instructions[12:14] bits) of the I instruction format.
//! In RV32I, a signed immediate is represented with a two's complement, and Rust do the same.
//! It thus allows to easily manipulate signed values simply using Rust's casts to the matchig format.
use crate::core::isa::{Instruction, RVException, RVHart};
/// The opcode shared between all the Integer Register-Immediate instructions.
pub const INTEGER_REGISTER_IMMEDIATE_OPCODE: u32 = 0x13;
/// Mask to apply on the instruction after having shifted it to the `funct3` start bit.
const SHIFTED_FUNCT3_MASK: u32 = 0b111;
/// Mask to apply on the 32-bit instruction after having shifted it to a register field start bit.
const REGISTER_INDEX_MASK: usize = 0x1f;
/// The start bit of the `funct3` field on the 32-bits instruction.
const FUNCT3_START_BIT: u32 = 12;
/// The start bit of the `rd` field on the 32-bits instruction.
const RD_START_BIT: u32 = 7;
/// The start bit of the `rs1` field on the 32-bits instruction.
const RS1_START_BIT: u32 = 15;
/// The start bit of the `imm` field on the 32-bits instruction.
const IMM_START_BIT: u32 = 20;
/// The `funct3` value for the ADDI operation.
const FUNCT3_ADDI_VALUE: u32 = 0b000;
/// The `funct3` value for the SLTI operation.
const FUNCT3_SLTI_VALUE: u32 = 0b010;
/// The `funct3` value for the SLTIU operation.
const FUNCT3_SLTIU_VALUE: u32 = 0b011;
/// The `funct3` value for the ANDI operation.
const FUNCT3_ANDI_VALUE: u32 = 0b111;
/// The `funct3` value for the ORI operation.
const FUNCT3_ORI_VALUE: u32 = 0b110;
/// The `funct3` value for the XORI operation.
const FUNCT3_XORI_VALUE: u32 = 0b100;
/// The `funct3` value for the SLLI operation.
const FUNCT3_SLLI_VALUE: u32 = 0b001;
/// The `funct3` value for the SRLI and SRAI operations.
const FUNCT3_SRLI_VALUE: u32 = 0b101;
/// The position of the shift operation code into the `imm` field.
const IMM_SHIFT_OP_START_BIT: u32 = 5;
/// The code for the SRLI instruction into the SRLI `imm` field.
const IMM_SRLI_CODE: u32 = 0b0000000;
/// The code for the SRAI instruction into the SRLI `imm` field.
const IMM_SRAI_CODE: u32 = 0b0100000;
/// Mask to apply on the `imm` field to get the shift amount
const SHIFT_AMOUNT_MASK: u32 = 0x1f;
/// All the available Integer Register-Immediate instructions, sharing the `INTEGER_REGISTER_IMMEDIATE_OPCODE` opcode.
enum IntegerRegisterImmediateOperation {
/// ADD Immediate.
/// Add the given sign-extended 12 bits immediate to the `rs1` register and store the result in the `rd` register.
ADDI,
/// Set Lesser Than Immediate.
/// Set the `rd` register to 1 if the given `rs1` signed register value is lesser than the sign-extended 12-bits immediate, 0 else.
SLTI,
/// Set Lesser Than Immediate Unsigned.
/// Set the `rd` register the 1 if the given `rs1` unsigned register value is lesser than the unsigned 12-bits immediate, 0 else.
SLTIU,
/// AND Immediate.
/// Set the `rd` register value as the result of the logical AND operation between the `rs1` register value and the sign-extended 12-bits immediate.
ANDI,
/// OR Immediate.
/// Set the `rd` register value as the result of the logical OR operation between the `rs1` register value and the sign-extended 12-bits immediate.
ORI,
/// XOR Immediate.
/// Set the `rd` register value as the result of the logical XOR operation between the `rs1` register value and the sign-extended 12-bits immediate.
XORI,
/// Shift Left Logical Immediate.
/// Set the `rd` register value as the result of a left logical switch of the `rs1` register by a number of bits defined by the 5 least significant bits of the immediate.
SLLI,
/// Shift Right Logical Immediate.i
/// Set the `rd` register value as the result of a right logical or arithmetic shift (depending of the 7 most significant bytes of the immediate) by a number of bits defined by the 5 least significant bits of the immediate.
SRLI,
/// An unknown operation.
/// Used is the `funct3` field code does not match with any of the previous operations ones.
Unknown,
}
/// Represent an Integer Register-Immediate instruction.
/// This struct implement the Instruction trait, allowing the CPU to decode and execute it.
pub struct IntegerRegisterImmediateInstruction {
/// The Integer operation to be applied between the given `rs1` register and the immediate.
funct3: IntegerRegisterImmediateOperation,
/// The hart's X index that will serve as the source register.
rs1: usize,
/// The hart's X register index into which the operation result will be stored.
rd: usize,
/// The given immediate that will be used with the `rs1` register.
imm: u32,
}
impl IntegerRegisterImmediateInstruction {
/// Create and decode a new Integer Register-Immediate instruction to be executed afterwards from the binary instruction.
pub fn new(instruction: u32) -> Self {
let funct3 = match (instruction >> FUNCT3_START_BIT) & SHIFTED_FUNCT3_MASK {
FUNCT3_ADDI_VALUE => IntegerRegisterImmediateOperation::ADDI,
FUNCT3_SLTI_VALUE => IntegerRegisterImmediateOperation::SLTI,
FUNCT3_SLTIU_VALUE => IntegerRegisterImmediateOperation::SLTIU,
FUNCT3_ANDI_VALUE => IntegerRegisterImmediateOperation::ANDI,
FUNCT3_ORI_VALUE => IntegerRegisterImmediateOperation::ORI,
FUNCT3_XORI_VALUE => IntegerRegisterImmediateOperation::XORI,
FUNCT3_SLLI_VALUE => IntegerRegisterImmediateOperation::SLLI,
FUNCT3_SRLI_VALUE => IntegerRegisterImmediateOperation::SRLI,
_ => IntegerRegisterImmediateOperation::Unknown,
};
let rd = (instruction >> RD_START_BIT) as usize & REGISTER_INDEX_MASK;
let rs1 = (instruction >> RS1_START_BIT) as usize & REGISTER_INDEX_MASK;
// When using Right shift on a signed integer, it performs a Artithmetic shift (thus, it automatically expands the sign bit).
let imm = (instruction as i32 >> IMM_START_BIT) as u32;
IntegerRegisterImmediateInstruction {
funct3,
rs1,
rd,
imm,
}
}
}
impl Instruction for IntegerRegisterImmediateInstruction {
fn execute(&self, core: &mut RVHart) -> Result<(), RVException> {
let src = core.x[self.rs1];
let signed_src = src as i32;
let dest = &mut core.x[self.rd];
let signed_immediate = self.imm as i32;
*dest = match self.funct3 {
IntegerRegisterImmediateOperation::ADDI => {
Ok(signed_src.wrapping_add(signed_immediate) as u32)
}
IntegerRegisterImmediateOperation::SLTI => Ok((signed_src < signed_immediate) as u32),
IntegerRegisterImmediateOperation::SLTIU => Ok((src < self.imm) as u32),
IntegerRegisterImmediateOperation::ANDI => Ok((signed_src & signed_immediate) as u32),
IntegerRegisterImmediateOperation::ORI => Ok((signed_src | signed_immediate) as u32),
IntegerRegisterImmediateOperation::XORI => Ok((signed_src ^ signed_immediate) as u32),
IntegerRegisterImmediateOperation::SLLI => Ok(src << (self.imm & SHIFT_AMOUNT_MASK)),
IntegerRegisterImmediateOperation::SRLI => match self.imm >> IMM_SHIFT_OP_START_BIT {
// SRAI and SLLI use both the same opcode and function code, to know which one should be used, we have to check the instruction[25, 31] bit range.
IMM_SRLI_CODE => Ok(src >> (self.imm & SHIFT_AMOUNT_MASK)),
IMM_SRAI_CODE => Ok((signed_src >> (self.imm & SHIFT_AMOUNT_MASK)) as u32),
_ => Err(RVException::InstructionDecodeError),
},
IntegerRegisterImmediateOperation::Unknown => Err(RVException::InstructionDecodeError),
}?;
Ok(())
}
}
#[cfg(test)]
mod test {
// TODO: Add edge cases with i32::MIN, i32::MAX, u32::MIN, u32::MAX
mod addi {
use crate::core::isa::{
rv32i::IntegerRegisterImmediateInstruction, Instruction as _, RVHart,
};
#[test]
fn addi_unsigned() {
// addi x1, x2, 30
let instruction: u32 = 0b00000001111000010000000010010011;
let mut core = RVHart::new();
core.x[1] = 20i32 as u32;
core.x[2] = 30i32 as u32;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 60);
assert_eq!(core.x[2] as i32, 30);
}
#[test]
fn addi_signed_to_unsigned() {
// addi x1, x2, -30
let instruction: u32 = 0b11111110001000010000000010010011;
let mut core = RVHart::new();
core.x[1] = 10i32 as u32;
core.x[2] = 20i32 as u32;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, -10);
assert_eq!(core.x[2] as i32, 20);
}
#[test]
fn addi_signed_to_signed() {
// addi x1, x2, -30
let instruction: u32 = 0b11111110001000010000000010010011;
let mut core = RVHart::new();
core.x[1] = 10i32 as u32;
core.x[2] = -20i32 as u32;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, -50);
assert_eq!(core.x[2] as i32, -20);
}
}
mod slti {
use crate::core::isa::{
rv32i::IntegerRegisterImmediateInstruction, Instruction as _, RVHart,
};
#[test]
fn slti_positive_with_positive() {
// slti x1, x2, 20
let instruction: u32 = 0b00000001010000010010000010010011;
let mut core = RVHart::new();
core.x[1] = 10i32 as u32;
core.x[2] = 10i32 as u32;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 1);
assert_eq!(core.x[2] as i32, 10);
core.x[2] = 30i32 as u32;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 0);
assert_eq!(core.x[2] as i32, 30);
}
#[test]
fn slti_negative_with_positive() {
// slti x1, x2, 20
let instruction: u32 = 0b00000001010000010010000010010011;
let mut core = RVHart::new();
core.x[1] = 10i32 as u32;
core.x[2] = -20i32 as u32;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 1);
assert_eq!(core.x[2] as i32, -20);
}
#[test]
fn slti_positive_with_negative() {
// slti x1, x2, -20
let instruction: u32 = 0b11111110110000010010000010010011;
let mut core = RVHart::new();
core.x[1] = 10i32 as u32;
core.x[2] = 20i32 as u32;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 0);
assert_eq!(core.x[2] as i32, 20);
}
#[test]
fn slti_negative_with_negative() {
// slti x1, x2, -20
let instruction: u32 = 0b11111110110000010010000010010011;
let mut core = RVHart::new();
core.x[1] = 10i32 as u32;
core.x[2] = -30i32 as u32;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 1);
assert_eq!(core.x[2] as i32, -30);
core.x[2] = -10i32 as u32;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 0);
assert_eq!(core.x[2] as i32, -10);
}
}
mod sltiu {
use crate::core::isa::{
rv32i::IntegerRegisterImmediateInstruction, Instruction as _, RVHart,
};
#[test]
fn sltiu() {
// sltiu x1, x2, 25
let instruction: u32 = 0b00000001100100010011000010010011;
let mut core = RVHart::new();
core.x[1] = 100;
core.x[2] = 20;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1], 1);
assert_eq!(core.x[2], 20);
core.x[2] = 30;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1], 0);
}
}
mod andi {
use crate::core::isa::{
rv32i::IntegerRegisterImmediateInstruction, Instruction as _, RVHart,
};
#[test]
fn andi() {
// andi x1, x2, 699
let instruction: u32 = 0b00101011101100010111000010010011;
let mut core = RVHart::new();
core.x[1] = 100i32 as u32;
core.x[2] = -123i32 as u32;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 641);
assert_eq!(core.x[2] as i32, -123);
}
}
mod ori {
use crate::core::isa::{
rv32i::IntegerRegisterImmediateInstruction, Instruction as _, RVHart,
};
#[test]
fn ori() {
// ori x1, x2, -1
let instruction: u32 = 0b11111111111100010110000010010011;
let mut core = RVHart::new();
core.x[1] = 10i32 as u32;
core.x[2] = 0i32 as u32;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, -1);
assert_eq!(core.x[2] as i32, 0);
}
}
mod xori {
use crate::core::isa::{
rv32i::IntegerRegisterImmediateInstruction, Instruction as _, RVHart,
};
#[test]
fn xori() {
// xori x1, x2, -1
let instruction: u32 = 0b11111111111100010100000010010011;
let mut core = RVHart::new();
core.x[1] = 10i32 as u32;
core.x[2] = 404i32 as u32;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, -405);
assert_eq!(core.x[2] as i32, 404);
}
}
mod slli {
use crate::core::isa::{
rv32i::IntegerRegisterImmediateInstruction, Instruction as _, RVHart,
};
#[test]
fn slli() {
// slli x1, x2, 5
let instruction: u32 = 0b00000000010100010001000010010011;
let mut core = RVHart::new();
core.x[1] = 10i32 as u32;
core.x[2] = -1i32 as u32;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, -32);
assert_eq!(core.x[2] as i32, -1);
}
}
mod srli {
use crate::core::isa::{
rv32i::IntegerRegisterImmediateInstruction, Instruction as _, RVHart,
};
#[test]
fn srli() {
// srli x1, x2, 5
let instruction: u32 = 0b00000000010100010101000010010011;
let mut core = RVHart::new();
core.x[1] = 10i32 as u32;
core.x[2] = 32i32 as u32;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 1);
assert_eq!(core.x[2] as i32, 32);
}
#[test]
fn srai() {
// srai x1, x2, 5
let instruction: u32 = 0b01000000010100010101000010010011;
let mut core = RVHart::new();
core.x[1] = 10i32 as u32;
core.x[2] = -2i32 as u32;
IntegerRegisterImmediateInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, -1);
assert_eq!(core.x[2] as i32, -2);
}
}
}

View file

@ -0,0 +1,618 @@
//! The RV32Ii's Integer Register-Register instructions.
//! In RV32I, all these instructions share the same opcode, and are executed respectively according to the funct3 field of the encoded instruction (instruction[12:14] bits).
//! This instructions use the R instruction format.
use crate::core::isa::{Instruction, RVException, RVHart};
/// The opcode shared between all the Integer Register-Register instructions.
pub const INTEGER_REGISTER_REGISTER_OPCODE: u32 = 0x33;
/// Start bit for the `funct3` field in the 32-bits instruction.
const FUNCT3_START_BIT: u32 = 12;
/// Start bit for the `rs1` field in the 32-bits instruction.
const RS1_START_BIT: usize = 15;
/// Start bit for the `rs2` field in the 32-bits instruction.
const RS2_START_BIT: usize = 20;
/// Start bit for the `rd` field in the 32-bits instruction.
const RD_START_BIT: usize = 7;
/// Start bit for the `funct7` field in the 32-bits instruction.
const FUNCT7_START_BIT: usize = 25;
/// Mask to apply on the 32-bits instruction after having shifted it to the `funct3` field.
const SHIFTED_FUNCT3_MASK: u32 = 0b111;
/// Mask to apply on the 32-bits instruction after having shifted it to a register field.
const REGISTER_FIELD_MASK: u32 = 0x1f;
/// Mask to apply on the `rs2` register to get the shift amount on the `rs1` register.
const SHIFT_AMOUNT_MASK: u32 = 0x1f;
/// Code for the ADD/SUB instruction in the `funct3` field.
const FUNCT3_ADD_CODE: u32 = 0b000;
/// Code for the SLL instruction in the `funct3` field.
const FUNCT3_SLL_CODE: u32 = 0b001;
/// Code for the SLT instruction in the `funct3` field.
const FUNCT3_SLT_CODE: u32 = 0b010;
/// Code for the SLTU instruction in the `funct3` field.
const FUNCT3_SLTU_CODE: u32 = 0b011;
/// Code for the XOR instruction in the `funct3` field.
const FUNCT3_XOR_CODE: u32 = 0b100;
/// Code for the SRL/SRA instruction in the `funct3` field.
const FUNCT3_SRL_CODE: u32 = 0b101;
/// Code for the OR instruction in the `funct3` field.
const FUNCT3_OR_CODE: u32 = 0b110;
/// Code for the AND instruction in the `funct3` field.
const FUNCT3_AND_CODE: u32 = 0b111;
/// Code for the SUB instruction in the `funct7` field when using ADD in the `funct3` field.
const FUNCT7_SUB_CODE: u32 = 0b0100000;
/// Code for the ADD instruction in the `funct7` field when using ADD in the `funct3` field.
const FUNCT7_ADD_CODE: u32 = 0b0000000;
/// Code for the SRL instruction in the `funct7` field when using SRL in the `funct3` field.
const FUNCT7_SRL_CODE: u32 = 0b0000000;
/// Code for the SRA instruction in the `funct7` field when using SRL in the `funct3` field.
const FUNCT7_SRA_CODE: u32 = 0b0100000;
/// All the instructions available under the Integer Register-Register instructions opcode.
enum IntegerRegisterRegisterOperation {
/// ADD
/// Add (or sub, depending on the `funct7` field) the two signed integer in the `rs1` and `rs2` registers and store the result (ignoring overflow) into the `rd` register.
ADD,
/// Set Lesser Than.
/// Set the `rd` register value to 1 if the signed integer value in the `rs1` register is lesser than the `rs2` one, 0 else.
SLT,
/// Set Lesser Than Unsigned
/// Set the `rd` register value to 1 if the unsigned integer value in the `rs1` register is lesser than the `rs2` one, 0 else.
SLTU,
/// AND.
/// Set the `rd` register value to the result of the logicial AND operation between the values in the register `rs1` and `rs2`.
AND,
/// OR
/// Set the `rd` register value to the result of the logical OR operation between the values in the register `rs1` and `rs2`.
OR,
/// XOR
/// Set the `rd` register value to the result of the logical XOR operation between `rs1` and `rs2`.
XOR,
/// Shift Left Logical.
/// Set the `rd` register value to the result of the logical left shift operation on `rs1` by the number of bits specified by the 5 least significant bits of the `rs2` register.
SLL,
/// Shift Right Logical
/// Set the `rd` register value to the result of the logical (or arithmetic, according to the `funct7` field) right shift on `rs1` by the number of bits specified by the 5 least significant bits of the `rs2` register.
SRL,
/// Unknown.
/// Represent an operation code that did not match the available codes for the previous operations.
Unknown,
}
/// Represent an Integer Register-Register instruction.
/// This struct implement the Instruction trait, allowing a CPU core to decode and execute it.
pub struct IntegerRegisterRegisterInstruction {
/// The operation to be executed with the given registers.
funct3: IntegerRegisterRegisterOperation,
/// The hart's X first source register index.
rs1: usize,
/// The hart's X second source register index.
rs2: usize,
/// The hart's X destination register index.
rd: usize,
/// Allow to discriminate operations between ADD/SUB and SRL/SRA.
funct7: u32,
}
impl IntegerRegisterRegisterInstruction {
/// Create a new Integer Register-Register instruction decoded from the encoded in 32-bit `instruction`.
pub fn new(instruction: u32) -> Self {
let funct3 = match (instruction >> FUNCT3_START_BIT) & SHIFTED_FUNCT3_MASK {
FUNCT3_ADD_CODE => IntegerRegisterRegisterOperation::ADD,
FUNCT3_SLL_CODE => IntegerRegisterRegisterOperation::SLL,
FUNCT3_SLT_CODE => IntegerRegisterRegisterOperation::SLT,
FUNCT3_SLTU_CODE => IntegerRegisterRegisterOperation::SLTU,
FUNCT3_XOR_CODE => IntegerRegisterRegisterOperation::XOR,
FUNCT3_SRL_CODE => IntegerRegisterRegisterOperation::SRL,
FUNCT3_OR_CODE => IntegerRegisterRegisterOperation::OR,
FUNCT3_AND_CODE => IntegerRegisterRegisterOperation::AND,
_ => IntegerRegisterRegisterOperation::Unknown,
};
let rs1 = ((instruction >> RS1_START_BIT) & REGISTER_FIELD_MASK) as usize;
let rs2 = ((instruction >> RS2_START_BIT) & REGISTER_FIELD_MASK) as usize;
let rd = ((instruction >> RD_START_BIT) & REGISTER_FIELD_MASK) as usize;
let funct7 = instruction >> FUNCT7_START_BIT;
IntegerRegisterRegisterInstruction {
funct3,
rs1,
rs2,
rd,
funct7,
}
}
}
impl Instruction for IntegerRegisterRegisterInstruction {
fn execute(&self, core: &mut RVHart) -> Result<(), RVException> {
let src1 = core.x[self.rs1];
let src2 = core.x[self.rs2];
let dest = &mut core.x[self.rd];
let signed_src1 = src1 as i32;
let signed_src2 = src2 as i32;
*dest = match self.funct3 {
IntegerRegisterRegisterOperation::ADD => match self.funct7 {
FUNCT7_SUB_CODE => Ok(signed_src1.wrapping_sub(signed_src2) as u32),
FUNCT7_ADD_CODE => Ok(signed_src1.wrapping_add(signed_src2) as u32),
_ => Err(RVException::InstructionDecodeError),
},
IntegerRegisterRegisterOperation::SLT => Ok((signed_src1 < signed_src2) as u32),
IntegerRegisterRegisterOperation::SLTU => Ok((src1 < src2) as u32),
IntegerRegisterRegisterOperation::AND => Ok(src1 & src2),
IntegerRegisterRegisterOperation::OR => Ok(src1 | src2),
IntegerRegisterRegisterOperation::XOR => Ok(src1 ^ src2),
IntegerRegisterRegisterOperation::SLL => Ok(src1 << (src2 & SHIFT_AMOUNT_MASK)),
IntegerRegisterRegisterOperation::SRL => match self.funct7 {
FUNCT7_SRA_CODE => Ok((signed_src1 >> (src2 & SHIFT_AMOUNT_MASK)) as u32),
FUNCT7_SRL_CODE => Ok(src1 >> (src2 & SHIFT_AMOUNT_MASK)),
_ => Err(RVException::InstructionDecodeError),
},
IntegerRegisterRegisterOperation::Unknown => Err(RVException::InstructionDecodeError),
}?;
Ok(())
}
}
#[cfg(test)]
mod test {
mod add {
use crate::core::isa::{
rv32i::IntegerRegisterRegisterInstruction, Instruction as _, RVHart,
};
#[test]
fn add_positive_to_positive() {
// add x1, x2, x3
let instruction = 0x003100b3;
let mut core = RVHart::new();
core.x[1] = 0i32 as u32;
core.x[2] = 20i32 as u32;
core.x[3] = 30i32 as u32;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 50);
assert_eq!(core.x[2] as i32, 20);
assert_eq!(core.x[3] as i32, 30);
}
#[test]
fn add_negative_to_positive() {
// add x1, x2, x3
let instruction = 0x003100b3;
let mut core = RVHart::new();
core.x[1] = 0i32 as u32;
core.x[2] = 20i32 as u32;
core.x[3] = -30i32 as u32;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, -10);
assert_eq!(core.x[2] as i32, 20);
assert_eq!(core.x[3] as i32, -30);
}
#[test]
fn add_positive_to_negative() {
// add x1, x2, x3
let instruction = 0x003100b3;
let mut core = RVHart::new();
core.x[1] = 0i32 as u32;
core.x[2] = -10i32 as u32;
core.x[3] = 40i32 as u32;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 30);
assert_eq!(core.x[2] as i32, -10);
assert_eq!(core.x[3] as i32, 40);
}
#[test]
fn add_negative_to_negative() {
// add x1, x2, x3
let instruction = 0x003100b3;
let mut core = RVHart::new();
core.x[1] = 0i32 as u32;
core.x[2] = -10i32 as u32;
core.x[3] = -40i32 as u32;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, -50);
assert_eq!(core.x[2] as i32, -10);
assert_eq!(core.x[3] as i32, -40);
}
#[test]
fn sub_positive_to_positive() {
// sub x1, x2, x3
let instruction = 0x403100b3;
let mut core = RVHart::new();
core.x[1] = 0i32 as u32;
core.x[2] = 10i32 as u32;
core.x[3] = 40i32 as u32;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, -30);
assert_eq!(core.x[2] as i32, 10);
assert_eq!(core.x[3] as i32, 40);
}
#[test]
fn sub_negative_to_positive() {
// sub x1, x2, x3
let instruction = 0x403100b3;
let mut core = RVHart::new();
core.x[1] = 0i32 as u32;
core.x[2] = 10i32 as u32;
core.x[3] = -40i32 as u32;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 50);
assert_eq!(core.x[2] as i32, 10);
assert_eq!(core.x[3] as i32, -40);
}
#[test]
fn sub_positive_to_negative() {
// sub x1, x2, x3
let instruction = 0x403100b3;
let mut core = RVHart::new();
core.x[1] = 0i32 as u32;
core.x[2] = -10i32 as u32;
core.x[3] = 40i32 as u32;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, -50);
assert_eq!(core.x[2] as i32, -10);
assert_eq!(core.x[3] as i32, 40);
}
#[test]
fn sub_negative_to_negative() {
// sub x1, x2, x3
let instruction = 0x403100b3;
let mut core = RVHart::new();
core.x[1] = 0i32 as u32;
core.x[2] = -10i32 as u32;
core.x[3] = -40i32 as u32;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 30);
assert_eq!(core.x[2] as i32, -10);
assert_eq!(core.x[3] as i32, -40);
}
}
mod slt {
use crate::core::isa::{
rv32i::IntegerRegisterRegisterInstruction, Instruction as _, RVHart,
};
#[test]
fn slt_positive_positive() {
// slt x1, x2, x3
let instruction = 0x003120b3;
let mut core = RVHart::new();
core.x[1] = 0i32 as u32;
core.x[2] = 10i32 as u32;
core.x[3] = 40i32 as u32;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 1);
assert_eq!(core.x[2] as i32, 10);
assert_eq!(core.x[3] as i32, 40);
}
#[test]
fn slt_positive_negative() {
// slt x1, x2, x3
let instruction = 0x003120b3;
let mut core = RVHart::new();
core.x[1] = 0i32 as u32;
core.x[2] = 10i32 as u32;
core.x[3] = -40i32 as u32;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 0);
assert_eq!(core.x[2] as i32, 10);
assert_eq!(core.x[3] as i32, -40);
}
#[test]
fn slt_negative_positive() {
// slt x1, x2, x3
let instruction = 0x003120b3;
let mut core = RVHart::new();
core.x[1] = 0i32 as u32;
core.x[2] = -10i32 as u32;
core.x[3] = 40i32 as u32;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 1);
assert_eq!(core.x[2] as i32, -10);
assert_eq!(core.x[3] as i32, 40);
}
#[test]
fn slt_negative_negative() {
// slt x1, x2, x3
let instruction = 0x003120b3;
let mut core = RVHart::new();
core.x[1] = 0i32 as u32;
core.x[2] = -10i32 as u32;
core.x[3] = -40i32 as u32;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 0);
assert_eq!(core.x[2] as i32, -10);
assert_eq!(core.x[3] as i32, -40);
}
}
mod sltu {
use crate::core::isa::{
rv32i::IntegerRegisterRegisterInstruction, Instruction as _, RVHart,
};
#[test]
fn sltu() {
// sltu x1, x2, x3
let instruction = 0x003130b3;
let mut core = RVHart::new();
core.x[1] = 0;
core.x[2] = 10;
core.x[3] = 40;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1], 1);
assert_eq!(core.x[2], 10);
assert_eq!(core.x[3], 40);
}
}
mod and {
use crate::core::isa::{
rv32i::IntegerRegisterRegisterInstruction, Instruction as _, RVHart,
};
#[test]
fn and() {
// and x1, x2, x3
let instruction = 0x003170b3;
let mut core = RVHart::new();
core.x[1] = 0;
core.x[2] = -2i32 as u32;
core.x[3] = 1;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, 0);
assert_eq!(core.x[2] as i32, -2);
assert_eq!(core.x[3], 1);
}
}
mod or {
use crate::core::isa::{
rv32i::IntegerRegisterRegisterInstruction, Instruction as _, RVHart,
};
#[test]
fn or() {
// or x1, x2, x3
let instruction = 0x003160b3;
let mut core = RVHart::new();
core.x[1] = 0;
core.x[2] = -2i32 as u32;
core.x[3] = 1;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, -1);
assert_eq!(core.x[2] as i32, -2);
assert_eq!(core.x[3], 1);
}
}
mod xor {
use crate::core::isa::{
rv32i::IntegerRegisterRegisterInstruction, Instruction as _, RVHart,
};
#[test]
fn xor() {
// xor x1, x2, x3
let instruction = 0x003140b3;
let mut core = RVHart::new();
core.x[1] = 0;
core.x[2] = -4i32 as u32;
core.x[3] = 5;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, -7);
assert_eq!(core.x[2] as i32, -4);
assert_eq!(core.x[3], 5);
}
}
mod sll {
use crate::core::isa::{
rv32i::IntegerRegisterRegisterInstruction, Instruction as _, RVHart,
};
#[test]
fn sll() {
// sll x1, x2, x3
let instruction = 0x003110b3;
let mut core = RVHart::new();
core.x[1] = 0;
core.x[2] = -1i32 as u32;
core.x[3] = 5;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, -32);
assert_eq!(core.x[2] as i32, -1);
assert_eq!(core.x[3], 5);
}
#[test]
fn sll_with_too_big_shift() {
// sll x1, x2, x3
let instruction = 0x003110b3;
let mut core = RVHart::new();
core.x[1] = 0;
core.x[2] = -1i32 as u32;
core.x[3] = 33;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, -2);
assert_eq!(core.x[2] as i32, -1);
assert_eq!(core.x[3], 33);
}
}
mod srl {
use crate::core::isa::{
rv32i::IntegerRegisterRegisterInstruction, Instruction as _, RVHart,
};
#[test]
fn srl() {
// srl x1, x2, x3
let instruction = 0x003150b3;
let mut core = RVHart::new();
core.x[1] = 0;
core.x[2] = 8;
core.x[3] = 3;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1], 1);
assert_eq!(core.x[2], 8);
assert_eq!(core.x[3], 3);
}
#[test]
fn srl_with_too_big_shift() {
// srl x1, x2, x3
let instruction = 0x003150b3;
let mut core = RVHart::new();
core.x[1] = 0;
core.x[2] = 8;
core.x[3] = 35;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1], 1);
assert_eq!(core.x[2], 8);
assert_eq!(core.x[3], 35);
}
#[test]
fn sra() {
// sra x1, x2, x3
let instruction = 0x403150b3;
let mut core = RVHart::new();
core.x[1] = 0;
core.x[2] = -5i32 as u32;
core.x[3] = 5;
IntegerRegisterRegisterInstruction::new(instruction)
.execute(&mut core)
.unwrap();
assert_eq!(core.x[1] as i32, -1);
assert_eq!(core.x[2] as i32, -5);
assert_eq!(core.x[3], 5);
}
}
}

97
src/core/isa/rv32i/jal.rs Normal file
View file

@ -0,0 +1,97 @@
//! The RV32I JAL instruction.
//! Add the given signed 21-bits immediate as an offset to the Program Counter register and store the return address (first instruction after the JAL) in the given rd register.
//! This instruction use the J format.
//! It has to be noted that the least signicant bit is always 0, and the instruction only provides the 20 most significant one, allowing to meet the aligned-address constraint.
use crate::core::isa::{Instruction, RVException, RVHart};
/// The JAL instruction opcode.
pub const JAL_OPCODE: u32 = 0x6f;
/// The start bit of the `rd` field in the 32-bits encoded instruction.
const RD_START_BIT: usize = 7;
/// The start bit of the `imm[1:10]` field in the 32-bits encoded instruction.
const IMM_1_10_START_BIT: usize = 21;
/// The start bit of the `imm[11]` field in the 32-bits encoded instruction.
const IMM_11_START_BIT: usize = 20;
/// The start bit of the `imm[12:19]` field in the 32-bits encoded instruction.
const IMM_12_19_START_BIT: usize = 12;
/// The start bit of the `imm[20]` field in the 32-bits encoded instruction.
const IMM_20_START_BIT: usize = 31;
/// The mask to apply on top of the 32-bits instruction after having shifted it to a register field start.
const REGISTER_FIELD_MASK: u32 = 0x1f;
/// The mask to apply on top of the 32-bits instruction after having shifted it to `IMM_1_10_START_BIT`.
const IMM_1_10_FIELD_MASK: u32 = 0x3ff;
/// The mask to apply on top of the 32-bits instruction after having shifted it to `IMM_11_START_BIT`.
const IMM_11_FIELD_MASK: u32 = 0x1;
/// The mask to apply on top of the 32-bits instruction after having shifted it to `IMM_12_19_START_BIT`.
const IMM_12_19_FIELD_MASK: u32 = 0xff;
/// A decoded JAL instruction ready to be executed on the CPU register.
pub struct JAL {
/// The hart's X register index to which the return address of the jump will be written to.
rd: usize,
/// The sign-extended 21-bit immediate to add the the hart's PC register to compute the jump address.
imm: u32,
}
impl JAL {
/// Decode the given `instruction` into a JAL instruction ready to be implemented.
pub fn new(instruction: u32) -> Self {
let rd = ((instruction >> RD_START_BIT) & REGISTER_FIELD_MASK) as usize;
let imm_1_10 = (instruction >> IMM_1_10_START_BIT) & IMM_1_10_FIELD_MASK;
let imm_11 = (instruction >> IMM_11_START_BIT) & IMM_11_FIELD_MASK;
let imm_12_19 = (instruction >> IMM_12_19_START_BIT) & IMM_12_19_FIELD_MASK;
// We here use a cast s i32 to perform an arithmetic shift.
let imm_20 = (instruction as i32 >> IMM_20_START_BIT) as u32;
let imm = (imm_1_10 << 1) + (imm_11 << 11) + (imm_12_19 << 12) + (imm_20 << 20);
JAL { rd, imm }
}
}
impl Instruction for JAL {
fn execute(&self, core: &mut RVHart) -> Result<(), RVException> {
core.x[self.rd] = core.pc.wrapping_add(4);
core.pc = (core.pc as i32).wrapping_add(self.imm as i32) as u32;
Ok(())
}
}
#[cfg(test)]
mod test {
use crate::core::isa::{rv32i::JAL, Instruction as _, RVHart};
#[test]
fn jal_with_positive_offset() {
// jal x1, 0x10
let instruction = 0x010000ef;
let mut core = RVHart::new();
core.pc = 0x10;
core.x[1] = 0x0;
JAL::new(instruction).execute(&mut core).unwrap();
assert_eq!(core.pc, 0x20);
assert_eq!(core.x[1], 0x14);
}
#[test]
fn jal_with_negative_offset() {
// jal x1, -0x10
let instruction = 0xff1ff0ef;
let mut core = RVHart::new();
core.pc = 0x30;
core.x[1] = 0x0;
JAL::new(instruction).execute(&mut core).unwrap();
assert_eq!(core.pc, 0x20);
assert_eq!(core.x[1], 0x34);
}
}

106
src/core/isa/rv32i/jalr.rs Normal file
View file

@ -0,0 +1,106 @@
//! The RV32I JALR instruction.
//! It is used to set the Program Counter register as the addition of a given register value with a given offset.
//! It will also store the return address (Program Counter + 4) within another given register.
//! It has to be noted that the result of the addition will have its least significant byte clear to prevent an alignement constraint violation.
use crate::core::isa::{Instruction, RVException, RVHart};
/// The JALR instruction opcode.
pub const JALR_OPCODE: u32 = 0x67;
/// The start bit of the `rd` field in the 32-bits encoded instruction.
const RD_START_BIT: usize = 7;
/// The start bit of the `rs1` field in the 32-bits encoded instruction.
const RS1_START_BIT: usize = 15;
/// The start bit of the `imm` field in the 32-bits encoded instruction.
const IMM_START_BIT: usize = 20;
/// The mask to apply on top of a register field after having shift the instruction to the field start.
const REGISTER_FIELD_MASK: u32 = 0x1f;
/// Represent a decoded JALR instruction, ready to be executed by a CPU core.
pub struct JALR {
/// The destination register to which the return address of the jump will be written.
rd: usize,
/// The hart's X source register index to which will be added the given immediate to compute the jump address.
rs1: usize,
/// The sign-extended 12-bits immediate that will be added to the `rs1` register content to compute the jump address.
imm: u32,
}
impl JALR {
/// Decode the given raw `instruction` into a JALR instruction ready tobe executed.
pub fn new(instruction: u32) -> Self {
let rd = ((instruction >> RD_START_BIT) & REGISTER_FIELD_MASK) as usize;
let rs1 = ((instruction >> RS1_START_BIT) & REGISTER_FIELD_MASK) as usize;
let imm = ((instruction as i32) >> IMM_START_BIT) as u32;
Self { rd, rs1, imm }
}
}
impl Instruction for JALR {
fn execute(&self, core: &mut RVHart) -> Result<(), RVException> {
let old_pc = core.pc;
core.pc = (core.x[self.rs1] as i32).wrapping_add(self.imm as i32) as u32 & !0x1;
core.x[self.rd] = old_pc.wrapping_add(4);
Ok(())
}
}
#[cfg(test)]
mod test {
use crate::core::isa::{rv32i::JALR, Instruction as _, RVHart};
#[test]
fn jalr() {
// jalr x1, 0x10(x2)
let instruction = 0x010100e7;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x0;
core.x[2] = 0x30;
JALR::new(instruction).execute(&mut core).unwrap();
assert_eq!(core.pc, 0x40);
assert_eq!(core.x[1], 0x54);
assert_eq!(core.x[2], 0x30);
}
#[test]
fn jalr_with_negative_offset() {
// jalr x1, -0x10(x2)
let instruction = 0xff0100e7;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x0;
core.x[2] = 0x30;
JALR::new(instruction).execute(&mut core).unwrap();
assert_eq!(core.pc, 0x20);
assert_eq!(core.x[1], 0x54);
assert_eq!(core.x[2], 0x30);
}
#[test]
fn jalr_with_odd_offset() {
// jalr x1, -0xf(x2)
let instruction = 0xff1100e7;
let mut core = RVHart::new();
core.pc = 0x50;
core.x[1] = 0x0;
core.x[2] = 0x30;
JALR::new(instruction).execute(&mut core).unwrap();
assert_eq!(core.pc, 0x20);
assert_eq!(core.x[1], 0x54);
assert_eq!(core.x[2], 0x30);
}
}

124
src/core/isa/rv32i/load.rs Normal file
View file

@ -0,0 +1,124 @@
//! The RV32I Load instructions.
//! The various Load instructions share the same opcode.
//! These instructions allows to load data from the given address on the System bus, to the given register.
use crate::core::isa::{Instruction, RVException, RVHart};
/// The Load instructions opcode.
pub const LOAD_OPCODE: u32 = 0x03;
/// The start bit of the `rd` field in the 32-bits encoded instruction.
const RD_START_BIT: usize = 7;
/// The start bit of the `rs1` field in the 32-bits encoded instruction.
const RS1_START_BIT: usize = 15;
/// The start bit of the `funct3` field in the 32-bits encoded instruction.
const FUNCT3_START_BIT: usize = 12;
/// The start bit of the `imm` field in the 32-bits encoded instruction.
const IMM_START_BIT: usize = 20;
/// The mask to apply on top of the shifted 32-bits instruction to extract a register field.
const REGISTER_FIELD_MASK: u32 = 0x1f;
/// The mask to apply on top of the shifted to `FUNCT3_START_BIT` instruction to extract the `funct3` field.
const FUNCT3_FIELD_MASK: u32 = 0b111;
/// The value in the `funct3` field to apply the LB operation.
const FUNCT3_LB_CODE: u32 = 0b000;
/// The value in the `funct3` field to apply the LH operation.
const FUNCT3_LH_CODE: u32 = 0b001;
/// The value in the `funct3` field to apply the LW operation.
const FUNCT3_LW_CODE: u32 = 0b010;
/// The value in the `funct3` field to apply the LBU operation.
const FUNCT3_LBU_CODE: u32 = 0b100;
/// The value in the `funct3` field to apply the LHU operation.
const FUNCT3_LHU_CODE: u32 = 0b101;
/// All the available Load operation.
enum LoadOperation {
/// Load Byte.
/// Will load the byte from the given address in memory, sign-extend it, and store it into the `rd` register.
LB,
/// Load Byte Unsigned.
/// Will load the byte from the given address in memory as an unsigned integer (thus, without sign extension), and store it into the `rd` register.
LBU,
/// Load Halfword.
/// Will load the halfword starting from the given address in memory, sign extend it, and store it into the `rd` register.
LH,
/// Load Halfword Unsigned.
/// Will load the halfword starting from the given address in memory as an unsigned integer (thus, without sign extension), and store it into the `rd` register.
LHU,
/// Load Word.
/// Load the word starting from the given address in memory and store it into the `rd` register.
LW,
/// An unknown operation that does not match any of the specified code for the above ones.
Unknown,
}
/// The decoded Load instruction, ready to be executed.
pub struct LoadInstruction {
/// The hart's X destination register index for the loaded value.
rd: usize,
/// The hart's X source register index to be added with the given immediate to compute the address of the load instruction.
rs1: usize,
/// The load operation type to execute.
funct3: LoadOperation,
/// The immediate to add to the `rs1` register value to compute the address from which the value should be loaded.
imm: u32,
}
impl LoadInstruction {
/// Decode the given `raw` instruction into a `LoadInstruction` ready to be executed.
pub fn new(instruction: u32) -> Self {
let rd = ((instruction >> RD_START_BIT) & REGISTER_FIELD_MASK) as usize;
let rs1 = ((instruction >> RS1_START_BIT) & REGISTER_FIELD_MASK) as usize;
let funct3 = match (instruction >> FUNCT3_START_BIT) & FUNCT3_FIELD_MASK {
FUNCT3_LB_CODE => LoadOperation::LB,
FUNCT3_LH_CODE => LoadOperation::LH,
FUNCT3_LW_CODE => LoadOperation::LW,
FUNCT3_LBU_CODE => LoadOperation::LBU,
FUNCT3_LHU_CODE => LoadOperation::LHU,
_ => LoadOperation::Unknown,
};
let imm = ((instruction as i32) >> IMM_START_BIT) as u32;
LoadInstruction {
rd,
rs1,
funct3,
imm,
}
}
}
impl Instruction for LoadInstruction {
fn execute(&self, core: &mut RVHart) -> Result<(), RVException> {
let addr = (core.x[self.rs1] as i32).wrapping_add(self.imm as i32) as u32;
let data = match self.funct3 {
LoadOperation::LB | LoadOperation::LBU => core.load_byte(addr) as u32,
LoadOperation::LH | LoadOperation::LHU => core.load_halfword(addr) as u32,
LoadOperation::LW => core.load_word(addr),
LoadOperation::Unknown => 0,
};
core.x[self.rd] = match self.funct3 {
LoadOperation::LB => Ok(((data as i8) as i32) as u32),
LoadOperation::LBU => Ok(data & 0xff),
LoadOperation::LH => Ok(((data as i16) as i32) as u32),
LoadOperation::LHU => Ok(data & 0xffff),
LoadOperation::LW => Ok(data),
LoadOperation::Unknown => Err(RVException::InstructionDecodeError),
}?;
Ok(())
}
}
#[cfg(test)]
mod test {
// TODO: Add test when Bus and MemoryUnit will be properly implemented.
}

59
src/core/isa/rv32i/lui.rs Normal file
View file

@ -0,0 +1,59 @@
//! The RV32I's LUI (load upper immediate) instruction.
//! It is used to set the 20 upper bits of the given destination register with the 20 bits given immediate.
use crate::core::isa::{Instruction, RVException, RVHart};
/// The LUI instruction opcode.
pub const LUI_OPCODE: u32 = 0x37;
/// The start bit of the `rd` field in the 32-bit encoded instruction.
const RD_START_BIT: usize = 7;
/// Mask to apply on the 32-bit instruction after having shifted it to the `rd` field start to get its value.
const REGISTER_FIELD_MASK: u32 = 0x1f;
/// Mask to apply on the 32-bit instruction to get the `imm` value.
const IMM_FIELD_MASK: u32 = 0xfffff000;
/// Represent a decoded LUI instruction ready to be executed.
pub struct LUI {
/// The hart's x destination register index.
rd: usize,
/// The 20 bits upper immediate to load into the `rd` 20 most significant bits.
imm: u32,
}
impl LUI {
/// Create and load a new LUI instruction with its decoded operand.
pub fn new(instruction: u32) -> Self {
let rd = ((instruction >> RD_START_BIT) & REGISTER_FIELD_MASK) as usize;
let imm = instruction & IMM_FIELD_MASK;
LUI { rd, imm }
}
}
impl Instruction for LUI {
fn execute(&self, core: &mut RVHart) -> Result<(), RVException> {
core.x[self.rd] = self.imm;
Ok(())
}
}
#[cfg(test)]
mod test {
use crate::core::isa::{rv32i::LUI, Instruction as _, RVHart};
#[test]
fn lui() {
// lui, x1, -1
let instruction: u32 = 0b11111111111111111111000010110111;
let mut core = RVHart::new();
core.x[1] = 0i32 as u32;
LUI::new(instruction).execute(&mut core).unwrap();
assert_eq!(core.x[1] as i32, -4096)
}
}

115
src/core/isa/rv32i/store.rs Normal file
View file

@ -0,0 +1,115 @@
//! The RV32I Store instructions.
//! These instructions allow to write data on the system bus.
//! All these instructions use the same opcode and use the S-type instruction format.
use crate::core::isa::{Instruction, RVException, RVHart};
/// The Store instructions opcode.
pub const STORE_OPCODE: u32 = 0x23;
/// The start bit for the `rs1` field on the 32-bits encoded instruction.
const RS1_START_BIT: usize = 15;
/// The start bit for the `rs2` field on the 32-bits encoded instruction.
const RS2_START_BIT: usize = 20;
/// The start bit for the `funct3` field on the 32-bits encoded instruction.
const FUNCT3_START_BIT: usize = 12;
/// The start bit for the `imm[4:0]` field on the 32-bits encoded instruction.
const IMM_4_0_START_BIT: usize = 7;
/// The start bit for the `imm[11:5]` field on the 32-bits encoded instruction.
const IMM_11_5_START_BIT: usize = 25;
/// The mask to apply on top of the encoded instruction shifted to `FUNCT3_START_BIT` to extract the `funct3` value.
const FUNCT3_MASK: u32 = 0b111;
/// The mask to apply on top of the encoded instruction shifted to a register field to extract the register index value.
const REGISTER_INDEX_MASK: u32 = 0x1f;
/// The mask to apply on top of the encoded instruction shifted to `IMM_4_0_START_BIT` to extract the `imm[4:0]` value.
const IMM_4_0_MASK: u32 = 0x1f;
/// The `funct3` field code for the SB operation.
const FUNCT3_SB_CODE: u32 = 0b000;
/// The `funct3` field code for the SH operation.
const FUNCT3_SH_CODE: u32 = 0b001;
/// The `funct3` field code for the SW operation.
const FUNCT3_SW_CODE: u32 = 0b010;
/// All the available Store instruction.
enum StoreOperation {
/// Store Byte
/// Store the `rs2` 8 least significant bits at the address resulting from the addition of the `rs1` register and the signed-extended 12-bits immediate.
SB,
/// Store Halfword
/// Store the `rs2` 16 least significant bits at the address resulting from the addition of the `rs1` register and the signed-extended 12-bits immediate.
SH,
/// Store Word
/// Store the `rs2` register value at the address resulting from the addition of the `rs1` register and the signed-extended 12-bits immediate.
SW,
/// Unknown operation.
/// This is the result of having a `funct3` instruction field that does not match any of the previous operations.
Unknown,
}
/// A decoded Store instruction, ready to be executed.
pub struct StoreInstruction {
/// The hart's X register index holding the base address to which should be added the signed immediate to get the effective address where data should be written.
rs1: usize,
/// The hart's X register index holding the data to be stored at the given address.
rs2: usize,
/// The store operation to be applied.
funct3: StoreOperation,
/// The sign-extended 12-bits immediate that should be added to the `rs1` register value to get the store effective address.
imm: u32,
}
impl StoreInstruction {
/// Decode the given raw `instruction` into a new `StoreInstruction` ready to be executed.
pub fn new(instruction: u32) -> Self {
let rs1 = ((instruction >> RS1_START_BIT) & REGISTER_INDEX_MASK) as usize;
let rs2 = ((instruction >> RS2_START_BIT) & REGISTER_INDEX_MASK) as usize;
let funct3 = match (instruction >> FUNCT3_START_BIT) & FUNCT3_MASK {
FUNCT3_SB_CODE => StoreOperation::SB,
FUNCT3_SH_CODE => StoreOperation::SH,
FUNCT3_SW_CODE => StoreOperation::SW,
_ => StoreOperation::Unknown,
};
let imm_4_0 = (instruction >> IMM_4_0_START_BIT) & IMM_4_0_MASK;
let imm_11_5 = (instruction as i32 >> IMM_11_5_START_BIT) as u32;
let imm = (imm_11_5 << 5) | imm_4_0;
StoreInstruction {
rs1,
rs2,
funct3,
imm,
}
}
}
impl Instruction for StoreInstruction {
fn execute(&self, core: &mut RVHart) -> Result<(), RVException> {
let dest = (core.x[self.rs1] as i32).wrapping_add(self.imm as i32) as u32;
match self.funct3 {
StoreOperation::SB => {
core.store_byte(dest, core.x[self.rs2] as u8);
Ok(())
}
StoreOperation::SH => {
core.store_halfword(dest, core.x[self.rs2] as u16);
Ok(())
}
StoreOperation::SW => {
core.store_word(dest, core.x[self.rs2]);
Ok(())
}
StoreOperation::Unknown => Err(RVException::InstructionDecodeError),
}
}
}
#[cfg(test)]
mod test {
// TODO: Add test when proper bus and memory unit implementation.
}

View file

@ -0,0 +1,29 @@
//! The RV32I System instructions.
//! Used to access system functionnality that might required a certain privilege level.
//! Currently unimplemented.
use crate::core::isa::{Instruction, RVException, RVHart};
/// The System instructions shared opcode.
pub const SYSTEM_OPCODE: u32 = 0x73;
/// A decoded system instruction ready to be executed.
pub struct SystemInstruction {}
impl SystemInstruction {
/// Decode the given raw `instruction` to issue a new `SystemInstruction` ready to be executed.
pub fn new(_instruction: u32) -> Self {
SystemInstruction {}
}
}
impl Instruction for SystemInstruction {
fn execute(&self, _core: &mut RVHart) -> Result<(), RVException> {
Ok(())
}
}
#[cfg(test)]
mod test {
// TODO: Add test when a proper implementation will be done.
}

2
src/devices.rs Normal file
View file

@ -0,0 +1,2 @@
//! Module containing each implementation of an emulation for some hardware.
pub mod memory_unit;

View file

@ -0,0 +1,78 @@
//! A simple Memory Unit to load raw data.
use crate::core::{
address_space::Addressable,
bus::{BusDevice, Readable, Writable},
};
/// The size in bytes of the memory unit.
pub const MAX_MEMORY_SIZE_IN_BYTES: usize = 4096;
/// A simple memory unit in which data can be written and read from a system bus.
pub struct MemoryUnit {
/// The raw data stored by the memory unit.
data: [u8; MAX_MEMORY_SIZE_IN_BYTES],
}
impl MemoryUnit {
/// Create a new Memory unit.
pub fn new() -> Self {
MemoryUnit {
data: [0; MAX_MEMORY_SIZE_IN_BYTES],
}
}
/// Load raw data in bytes into the memory unit.
pub fn load_raw(&mut self, data: &[u8]) {
let data_len = data.iter().len().min(MAX_MEMORY_SIZE_IN_BYTES);
self.data[0..data_len].copy_from_slice(&data[0..data_len]);
}
}
impl Addressable for MemoryUnit {
fn memory_range_size(&self) -> u32 {
MAX_MEMORY_SIZE_IN_BYTES as u32
}
}
impl Readable for MemoryUnit {
fn read_word(&self, offset: u32) -> u32 {
let addr = offset as usize;
u32::from_le_bytes(self.data[addr..addr + 4].try_into().unwrap())
}
fn read_byte(&self, offset: u32) -> u8 {
let addr = offset as usize;
self.data[addr]
}
fn read_halfword(&self, offset: u32) -> u16 {
let addr = offset as usize;
u16::from_le_bytes(self.data[addr..addr + 2].try_into().unwrap())
}
}
impl Writable for MemoryUnit {
fn write_word(&mut self, offset: u32, value: u32) {
let addr = offset as usize;
self.data[addr..addr + 4].clone_from_slice(&value.to_le_bytes());
}
fn write_byte(&mut self, offset: u32, value: u8) {
let addr = offset as usize;
self.data[addr] = value;
}
fn write_halfword(&mut self, offset: u32, value: u16) {
let addr = offset as usize;
self.data[addr..addr + 2].clone_from_slice(&value.to_le_bytes());
}
}
impl BusDevice for MemoryUnit {}
impl Default for MemoryUnit {
fn default() -> Self {
MemoryUnit::new()
}
}

View file

@ -8,8 +8,22 @@
rustdoc::missing_crate_level_docs
)]
mod address_space;
use core::isa::hart::RVHart;
use devices::memory_unit::MemoryUnit;
pub mod core;
pub mod devices;
fn main() {
println!("Hello, world!");
let program_data: [u8; 8] = [
0x93, 0x80, 0x00, 0x03, // addi x1, x1, 0x30
0x67, 0x00, 0x00, 0x00, // jalr x0, 0(x0)
];
let mut mem = MemoryUnit::new();
mem.load_raw(&program_data);
let mut cpu = RVHart::new();
cpu.map_device(mem, 0x0);
cpu.start_main_loop();
}