diff --git a/src/core.rs b/src/core.rs index e03d830..5c0c534 100644 --- a/src/core.rs +++ b/src/core.rs @@ -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; diff --git a/src/core/bus.rs b/src/core/bus.rs new file mode 100644 index 0000000..0d518ba --- /dev/null +++ b/src/core/bus.rs @@ -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, +} + +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() + } +} diff --git a/src/core/isa.rs b/src/core/isa.rs new file mode 100644 index 0000000..9c182ea --- /dev/null +++ b/src/core/isa.rs @@ -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, RVException> { + let instruction = match instruction & 0x7f { + x if RV32I_OPCODES.contains(&x) => Ok(decode_rv32i_instruction(instruction)), + _ => Err(RVException::InstructionDecodeError), + }?; + + Ok(Box::new(instruction)) +} diff --git a/src/core/isa/hart.rs b/src/core/isa/hart.rs new file mode 100644 index 0000000..c33d644 --- /dev/null +++ b/src/core/isa/hart.rs @@ -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() + } +} diff --git a/src/core/isa/rv32i.rs b/src/core/isa/rv32i.rs new file mode 100644 index 0000000..55f722e --- /dev/null +++ b/src/core/isa/rv32i.rs @@ -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"), + } +} diff --git a/src/core/isa/rv32i/auipc.rs b/src/core/isa/rv32i/auipc.rs new file mode 100644 index 0000000..36a66cf --- /dev/null +++ b/src/core/isa/rv32i/auipc.rs @@ -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); + } +} diff --git a/src/core/isa/rv32i/branch.rs b/src/core/isa/rv32i/branch.rs new file mode 100644 index 0000000..1356ae7 --- /dev/null +++ b/src/core/isa/rv32i/branch.rs @@ -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); + } + } +} diff --git a/src/core/isa/rv32i/fence.rs b/src/core/isa/rv32i/fence.rs new file mode 100644 index 0000000..ea3cef7 --- /dev/null +++ b/src/core/isa/rv32i/fence.rs @@ -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. +} diff --git a/src/core/isa/rv32i/integer_register_immediate.rs b/src/core/isa/rv32i/integer_register_immediate.rs new file mode 100644 index 0000000..1562075 --- /dev/null +++ b/src/core/isa/rv32i/integer_register_immediate.rs @@ -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); + } + } +} diff --git a/src/core/isa/rv32i/integer_register_register.rs b/src/core/isa/rv32i/integer_register_register.rs new file mode 100644 index 0000000..f3f0f8b --- /dev/null +++ b/src/core/isa/rv32i/integer_register_register.rs @@ -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); + } + } +} diff --git a/src/core/isa/rv32i/jal.rs b/src/core/isa/rv32i/jal.rs new file mode 100644 index 0000000..3365750 --- /dev/null +++ b/src/core/isa/rv32i/jal.rs @@ -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); + } +} diff --git a/src/core/isa/rv32i/jalr.rs b/src/core/isa/rv32i/jalr.rs new file mode 100644 index 0000000..236d612 --- /dev/null +++ b/src/core/isa/rv32i/jalr.rs @@ -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); + } +} diff --git a/src/core/isa/rv32i/load.rs b/src/core/isa/rv32i/load.rs new file mode 100644 index 0000000..ae009cc --- /dev/null +++ b/src/core/isa/rv32i/load.rs @@ -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. +} diff --git a/src/core/isa/rv32i/lui.rs b/src/core/isa/rv32i/lui.rs new file mode 100644 index 0000000..0fe6fe5 --- /dev/null +++ b/src/core/isa/rv32i/lui.rs @@ -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) + } +} diff --git a/src/core/isa/rv32i/store.rs b/src/core/isa/rv32i/store.rs new file mode 100644 index 0000000..589b986 --- /dev/null +++ b/src/core/isa/rv32i/store.rs @@ -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. +} diff --git a/src/core/isa/rv32i/system.rs b/src/core/isa/rv32i/system.rs new file mode 100644 index 0000000..e4e7ee6 --- /dev/null +++ b/src/core/isa/rv32i/system.rs @@ -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. +} diff --git a/src/devices.rs b/src/devices.rs new file mode 100644 index 0000000..4d6cc53 --- /dev/null +++ b/src/devices.rs @@ -0,0 +1,2 @@ +//! Module containing each implementation of an emulation for some hardware. +pub mod memory_unit; diff --git a/src/devices/memory_unit.rs b/src/devices/memory_unit.rs new file mode 100644 index 0000000..98e2195 --- /dev/null +++ b/src/devices/memory_unit.rs @@ -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() + } +} diff --git a/src/main.rs b/src/main.rs index 3b35055..519d079 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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(); }