diff --git a/src/agent.rs b/src/agent.rs index 1b50b1d01..16d19bb9c 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -159,6 +159,11 @@ pub async fn emulation_core_agent(scope: ReactorScope) state.updates.changed_stack, RiscStateUpdate::UpdateStack(datapath.stack.clone()) ); + send_update_riscv!( + state.scope, + state.updates.changed_coprocessor_registers, + RiscStateUpdate::UpdateCoprocessorRegisters(datapath.coprocessor.registers) + ); } } state.updates = Default::default(); diff --git a/src/agent/datapath_reducer.rs b/src/agent/datapath_reducer.rs index 2e32746e9..6c6160515 100644 --- a/src/agent/datapath_reducer.rs +++ b/src/agent/datapath_reducer.rs @@ -8,7 +8,9 @@ use crate::emulation_core::mips::gp_registers::{GpRegisterType, GpRegisters}; use crate::emulation_core::mips::memory::Memory; use crate::emulation_core::register::{RegisterType, Registers}; use crate::emulation_core::riscv::datapath::{RiscDatapathState, RiscStage}; -use crate::emulation_core::riscv::registers::{RiscGpRegisterType, RiscGpRegisters}; +use crate::emulation_core::riscv::registers::{ + RiscFpRegisters, RiscGpRegisterType, RiscGpRegisters, +}; use crate::emulation_core::stack::Stack; use gloo_console::log; use std::rc::Rc; @@ -40,6 +42,7 @@ pub struct MipsCoreState { pub struct RiscCoreState { pub state: RiscDatapathState, pub registers: RiscGpRegisters, + pub coprocessor_registers: RiscFpRegisters, pub memory: Memory, pub current_stage: RiscStage, pub stack: Stack, @@ -151,6 +154,12 @@ impl Reducible for DatapathReducer { stack, ..self.riscv.clone() }, + RiscStateUpdate::UpdateCoprocessorRegisters(coprocessor_registers) => { + RiscCoreState { + coprocessor_registers, + ..self.riscv.clone() + } + } }, ..(*self).clone() }, @@ -180,6 +189,13 @@ impl DatapathReducer { } } + pub fn get_dyn_fp_registers(&self) -> Vec<(Rc, u64)> { + match self.current_architecture { + MIPS => self.mips.coprocessor_registers.get_dyn_register_list(), + RISCV => self.riscv.coprocessor_registers.get_dyn_register_list(), + } + } + pub fn get_memory(&self) -> &Memory { match self.current_architecture { MIPS => &self.mips.memory, diff --git a/src/agent/messages.rs b/src/agent/messages.rs index 2943f4caa..51a8dbbfd 100644 --- a/src/agent/messages.rs +++ b/src/agent/messages.rs @@ -4,7 +4,7 @@ use crate::emulation_core::mips::fp_registers::FpRegisters; use crate::emulation_core::mips::gp_registers::GpRegisters; use crate::emulation_core::mips::memory::Memory; use crate::emulation_core::riscv::datapath::{RiscDatapathState, RiscStage}; -use crate::emulation_core::riscv::registers::RiscGpRegisters; +use crate::emulation_core::riscv::registers::{RiscFpRegisters, RiscGpRegisters}; use crate::emulation_core::stack::Stack; use crate::emulation_core::{architectures::AvailableDatapaths, mips::datapath::Stage}; use serde::{Deserialize, Serialize}; @@ -44,6 +44,7 @@ pub enum MipsStateUpdate { pub enum RiscStateUpdate { UpdateState(RiscDatapathState), UpdateRegisters(RiscGpRegisters), + UpdateCoprocessorRegisters(RiscFpRegisters), UpdateMemory(Memory), UpdateStage(RiscStage), UpdateStack(Stack), diff --git a/src/bin/main.rs b/src/bin/main.rs index e6f7d9c82..3f9a84992 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -32,7 +32,6 @@ use web_sys::HtmlInputElement; use yew::prelude::*; use yew::{html, Html, Properties}; -use swim::emulation_core::register::Registers; use swim::emulation_core::riscv::datapath::RiscStage; use yew_agent::Spawnable; @@ -657,7 +656,7 @@ fn app(props: &AppProps) -> Html { // Right column - + } diff --git a/src/emulation_core/riscv.rs b/src/emulation_core/riscv.rs index a937a8c28..8e83c2b45 100644 --- a/src/emulation_core/riscv.rs +++ b/src/emulation_core/riscv.rs @@ -3,6 +3,7 @@ pub mod constants; pub mod control_signals; +pub mod coprocessor; pub mod datapath; pub mod datapath_signals; pub mod instruction; diff --git a/src/emulation_core/riscv/constants.rs b/src/emulation_core/riscv/constants.rs index de2208061..022dfd788 100644 --- a/src/emulation_core/riscv/constants.rs +++ b/src/emulation_core/riscv/constants.rs @@ -44,6 +44,7 @@ pub const FUNCT_SOP37: u8 = 0b011111; /// Used for R-type instructions. pub const OPCODE_OP: u8 = 0b0110011; pub const OPCODE_OP_32: u8 = 0b0111011; +pub const OPCODE_OP_FP: u8 = 0b1010011; /// Used for I-type instructions. pub const OPCODE_IMM: u8 = 0b0010011; @@ -52,11 +53,13 @@ pub const OPCODE_IMM_32: u8 = 0b0011011; pub const OPCODE_JALR: u8 = 0b1100111; // LOAD pub const OPCODE_LOAD: u8 = 0b0000011; +pub const OPCODE_LOAD_FP: u8 = 0b0000111; // SYSTEM pub const OPCODE_SYSTEM: u8 = 0b1110011; /// Used for S-type instructions. pub const OPCODE_STORE: u8 = 0b0100011; +pub const OPCODE_STORE_FP: u8 = 0b0100111; /// Used for B-type instructions. pub const OPCODE_BRANCH: u8 = 0b1100011; @@ -70,6 +73,19 @@ pub const OPCODE_AUIPC: u8 = 0b0010111; /// Used for J-type instructions. pub const OPCODE_JAL: u8 = 0b1101111; +/// Used for R4-type instructions. +// FMADD.S +pub const OPCODE_MADD: u8 = 0b1000011; +// FMSUB.S +pub const OPCODE_MSUB: u8 = 0b1000111; +// FNMSUB.S +pub const OPCODE_NMSUB: u8 = 0b1001011; +// FNMADD.S +pub const OPCODE_NMADD: u8 = 0b1001111; + +/// Not a Number +pub const RISC_NAN: u32 = 0x7fc00000; + // "ENC" is short for encoding. There is no formal name for this field // in the MIPS64 specification, other than the "shamt"/"sa" field that it // replaces, so this was chosen as the mnemonic for this project. diff --git a/src/emulation_core/riscv/control_signals.rs b/src/emulation_core/riscv/control_signals.rs index fe0b494b1..f418667fb 100644 --- a/src/emulation_core/riscv/control_signals.rs +++ b/src/emulation_core/riscv/control_signals.rs @@ -211,3 +211,223 @@ pub enum RegWriteEn { NoWrite = 0, YesWrite = 1, } + +pub mod floating_point { + + use super::super::constants::*; + use serde::{Deserialize, Serialize}; + + #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] + pub struct FpuControlSignals { + pub round_mode: RoundingMode, + pub data_src: DataSrc, + pub data_write: DataWrite, + pub fpu_alu_op: FpuAluOp, + pub fpu_mem_to_reg: FpuMemToReg, + pub fpu_reg_dst: FpuRegDst, + pub fpu_reg_write: FpuRegWrite, + } + + /// Determines the source of the `Data` register in the floating-point unit. + /// + /// This is a special intermediary register that facilitates passing data between + /// the main processing unit and the floating-point unit. + #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] + pub enum DataSrc { + /// Use data from the main processing unit. Specifically, the data from register + /// `rs1` from a given instruction. This value can additionally be used in the cases + /// where this register is not written to. + MainProcessorUnit = 0, + + /// Use data from the floating-point unit. Specifically, the data from register `rs1` + /// from a given instruction. + #[default] + FloatingPointUnitRS1 = 1, + + /// Use data from the floating-point unit. Specifically, the data from the comparator. + FloatingPointUnitComp = 2, + + /// Use data from the floating-point unit. Specifically, the Classify Mask. + FloatingPointUnitMask = 3, + + /// Use the un-altered bits from the floating-point unit. + FloatingPointBits = 4, + + /// Use the un-altered bits from the main unit. + MainProcessorBits = 5, + } + + /// Determines whether to write to the `Data` register in the floating-point unit. + /// + /// This acts as a toggle for the source of data to the main processing unit register + /// file. Additionally, it acts as a toggle for a source to the floating-point unit + /// register file (this could be overridden by the [`FpuMemToReg`] control signal). + /// For the latter two functions, it is imperative to unset the [`RegWriteEn`](super::RegWriteEn) and + /// [`FpuRegWrite`] control signals in cases where registers should not be modified + /// with unintended data. + #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] + pub enum DataWrite { + /// - Do not write to the data register. + /// - Source data to write to the main processing unit register file from the main + /// processing unit. This implies either the ALU result or the data read from memory + /// - Source data to write to the floating-point register file from the floating-point + /// ALU. + #[default] + NoWrite = 0, + + /// - Write to the data register. + /// - Source data to write to the main processing unit register file from the + /// floating-point unit. Specifically, this is the data stored in the `Data` register + /// in the FPU, likely from register `rs1` from a given instruction. This data source + /// overrides the decision given by the [`MemToReg`](super::MemToReg) control signal. + /// - Source data to write to the floating-point register file from the `Data` register + /// in the FPU, likely from register `rs1` from a given instruction. + YesWrite = 1, + } + + /// This doubly determines the operations sent to the floating-point ALU and the + /// floating-point comparator. + /// + /// Only one of these units are effectively utilized in any given instruction. + /// + /// The fifth bit of the control signal represents either a single-precision + /// floating-point operation (0), or a double-precision floating-point operation (1). + /// This fifth bit is determined by [`FpuRegWidth`]. + /// + /// *Implementation note:* The bits set for the comparator are intended to match + /// the bits used in the `cond` field of a `c.cond.fmt` instruction. + #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] + pub enum FpuAluOp { + #[default] + /// `_00000` (0): + /// - ALU: Perform an addition. + Addition = 0, + + /// `_00001` (1): + /// - ALU: Perform a subtraction. + Subtraction = 1, + + /// `_00010` (2): + /// - ALU: Perform a multiplication. + /// - Comparator: Set if equal. + MultiplicationOrEqual = 2, + + /// `_00011` (3): + /// - ALU: Perform a division. + Division = 3, + + /// `_00100` (4): + /// - ALU: Perform a Square Root. + Sqrt = 4, + + /// `_00101` (5): + /// - ALU: Take the Minimum value. + Min = 5, + + /// `_00110` (6): + /// - ALU: Take the Maximum value. + Max = 6, + + /// `_00111` (7): + /// - ALU: Sign-Injection. + SGNJ = 7, + + /// `_01000` (8): + /// - ALU: Negative Sign-Injection. + SGNJN = 8, + + /// `_01001` (9): + /// - ALU: Xor Sign-Injection. + SGNJX = 9, + + /// `_01010` (10): + /// - ALU: Classification Mask. + Class = 10, + + /// `_01011` (11): + /// - ALU: Fused Multiplication-Addition. + MAdd = 11, + + /// `_01100` (12): + /// - ALU: Fused Multiplication-Subtraction. + MSub = 12, + + /// `_01101` (13): + /// - ALU: Fused Negated Multiplication-Subtraction. + NMSub = 13, + + /// `_01110` (14): + /// - ALU: Fused Negated Multiplication-Addition. + NMAdd = 14, + + /// `_10000` (16): + /// - Comparator: Set if less than. + Slt = 16, + + /// `_10001` (17): + /// - Comparator: Set if less than or equal. + Sle = 17, + } + + impl FpuAluOp { + /// Get the corresponding control signal given a function code. + pub fn from_function(function: u8) -> Result { + match function { + FUNCTION_C_EQ => Ok(Self::MultiplicationOrEqual), + FUNCTION_C_LT => Ok(Self::Slt), + FUNCTION_C_LE => Ok(Self::Sle), + _ => Err(format!("Unsupported function code `{function}`")), + } + } + } + #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] + pub enum RoundingMode { + RNE = 0, + RTZ = 1, + RDN = 2, + RUP = 3, + RMM = 4, + #[default] + DRM = 7, + } + + /// Determines, given that [`FpuRegWrite`] is set, what the source of a floating-point + /// register's new data will be. + /// + /// This decision, if set, overrides the decision from the [`DataWrite`] control signal. + #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] + pub enum FpuMemToReg { + /// Do not use data from memory. Use the result of the [`DataWrite`] control signal. + #[default] + UseDataWrite = 0, + + /// Use data from memory. + UseMemory = 1, + } + + /// Determines, given that [`FpuRegWrite`] is set, which destination register to write + /// to, which largely depends on the instruction format. + #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] + pub enum FpuRegDst { + /// Use register `rs1`. + Reg1 = 0, + + /// Use register `rs2`. + Reg2 = 1, + + /// Use register `rd`. + #[default] + Reg3 = 2, + } + + /// Determines if the floating-point register file should be written to. + #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] + pub enum FpuRegWrite { + /// Do not write to the floating-point register file. + #[default] + NoWrite = 0, + + /// Write to the floating-point register file. + YesWrite = 1, + } +} diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs new file mode 100644 index 000000000..c23ef4041 --- /dev/null +++ b/src/emulation_core/riscv/coprocessor.rs @@ -0,0 +1,628 @@ +//! Implementation of a RISC-V floating-point coprocessor. + +use std::ops::Neg; + +use super::constants::*; +use super::instruction::Instruction; +use super::registers::RiscFpRegisters; +use super::{constants::RISC_NAN, control_signals::floating_point::*}; +use serde::{Deserialize, Serialize}; + +/// An implementation of a floating-point coprocessor for the RISC-V ISA. +/// +/// Different from the main processor, much of the functionality of the coprocessor +/// is controlled remotely using its available API calls. +#[derive(Clone, PartialEq, Default, Debug, Serialize, Deserialize)] +pub struct RiscFpCoprocessor { + instruction: Instruction, + pub signals: FpuControlSignals, + pub state: RiscFpuState, + pub is_halted: bool, + pub registers: RiscFpRegisters, + pub condition_code: u64, + pub data: u64, +} + +#[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] +pub struct RiscFpuState { + pub instruction: u32, + pub rs1: u32, + pub rs2: u32, + pub rs3: u32, + pub rd: u32, + pub shamt: u32, + pub funct2: u32, + pub funct3: u32, + pub funct7: u32, + pub imm: u32, + pub imm1: u32, + pub imm2: u32, + pub branch_flag: bool, + + /// The line that comes out of the condition code register file. Should contain + /// 1 for true or 0 for false. + pub condition_code_bit: u8, + /// The inversion of `condition_code_bit`. + pub condition_code_bit_inverted: u8, + /// The result of the multiplexer with `condition_code_bit` and `condition_code_bit_inverted`. + pub condition_code_mux: u8, + + pub data_from_main_processor: u64, + pub data_writeback: u64, + pub destination: usize, + pub fp_register_data_from_main_processor: u64, + pub read_data_1: u64, + pub read_data_2: u64, + pub read_data_3: u64, + pub register_write_data: u64, + pub register_write_mux_to_mux: u64, + pub sign_extend_data: u64, + + /// Data line that goes from `Read Data 2` to the multiplexer in the main processor + /// controlled by [`MemWriteSrc`](super::control_signals::MemWriteSrc). + /// This variable in a way in just a copy of read_data_2 + pub fp_register_to_memory: u64, + + pub alu_result: u64, + pub comparator_result: u64, +} + +impl RiscFpCoprocessor { + // ========================== Stages ========================== + pub fn stage_instruction_decode(&mut self) { + self.instruction_decode(); + self.set_control_signals(); + self.read_registers(); + } + + pub fn stage_execute(&mut self) { + self.alu(); + self.comparator(); + self.write_fp_register_to_memory(); + } + + pub fn stage_memory(&mut self) { + self.write_data(); + self.set_data_writeback(); + } + + pub fn stage_writeback(&mut self) { + self.register_write(); + } + + // ===================== General Functions ===================== + /// Handle an otherwise irrecoverable error within the datapath. + pub fn error(&mut self, _message: &str) { + self.is_halted = true; + } + + // =================== API For Main Processor =================== + /// Set the internally-stored copy of the current instruction. This effectively + /// operates in lieu of any "instruction fetch" functionality since the coprocessor + /// does not fetch instructions. + pub fn set_instruction(&mut self, instruction_bits: u32) { + self.state.instruction = instruction_bits; + if let Ok(instruction) = Instruction::try_from(self.state.instruction) { + self.instruction = instruction; + } + } + + /// Sets the data line between the main processor and the `Data` register. This + /// is then used if deciding data from the main processor should go into the `Data` + /// register. + pub fn set_data_from_main_processor(&mut self, data: u64) { + self.state.data_from_main_processor = data; + } + + /// Gets the contents of the data line between the `Data` register and the multiplexer + /// in the main processor controlled by the [`DataWrite`] control signal. + pub fn get_data_writeback(&mut self) -> u64 { + self.state.data_writeback + } + + /// Sets the data line between the multiplexer controlled by [`MemToReg`](super::control_signals::MemToReg) + /// in the main processor and the multiplexer controlled by [`FpuMemToReg`] in the + /// floating-point coprocessor. + pub fn set_fp_register_data_from_main_processor(&mut self, data: u64) { + self.state.fp_register_data_from_main_processor = data; + } + + /// Gets the contents of the data line that goes from `Read Data 2` to the multiplexer + /// in the main processor controlled by [`MemWriteSrc`](super::control_signals::MemWriteSrc). + pub fn get_fp_register_to_memory(&mut self) -> u64 { + self.state.fp_register_to_memory + } + + pub fn set_register(&mut self, _register: usize, _data: u64) -> Result<(), String> { + if _register >= 32 { + return Err(format!("Register index out of bounds: {}", _register)); + } + + let register = &mut self.registers.fpr[_register]; + *register = _data; + + Ok(()) + } + + pub fn register_from_str(&self, _register: &str) -> Option { + // Check if register matches a register between f0 and f31 + if _register.len() >= 2 && &_register[0..1] == "f" && _register.len() <= 3 { + let register = &_register[1..]; + if let Ok(register) = register.parse::() { + if register < 32 { + return Some(register); + } + } + } + None + } + + // ================== Instruction Decode (ID) ================== + /// Decode an instruction into its individual fields. + fn instruction_decode(&mut self) { + // Set the data lines based on the contents of the instruction. + // Some lines will hold uninitialized values as a result. + match self.instruction { + Instruction::RType(r) => { + self.state.rs1 = r.rs1 as u32; + self.state.rs2 = r.rs2 as u32; + self.state.rd = r.rd as u32; + self.state.shamt = r.rs2 as u32; + self.state.funct3 = r.funct3 as u32; + self.state.funct7 = r.funct7 as u32; + } + Instruction::IType(i) => { + self.state.rs1 = i.rs1 as u32; + self.state.funct3 = i.funct3 as u32; + self.state.rd = i.rd as u32; + self.state.imm = i.imm as u32; + self.state.shamt = (i.imm & 0x003f) as u32; + } + Instruction::SType(s) => { + self.state.rs2 = s.rs2 as u32; + self.state.rs1 = s.rs1 as u32; + self.state.funct3 = s.funct3 as u32; + self.state.imm1 = s.imm1 as u32; + self.state.imm2 = s.imm2 as u32; + } + Instruction::R4Type(r4) => { + self.state.rs3 = r4.rs3 as u32; + self.state.funct2 = r4.funct2 as u32; + self.state.rs2 = r4.rs2 as u32; + self.state.rs1 = r4.rs1 as u32; + self.state.funct3 = r4.funct3 as u32; + self.state.rd = r4.rd as u32; + } + _ => (), + } + } + + /// Set the control signals of the processor based on the instruction opcode and function + /// control signals. + fn set_control_signals(&mut self) { + match self.instruction { + Instruction::RType(r) => { + self.signals = FpuControlSignals { + data_write: DataWrite::NoWrite, + fpu_mem_to_reg: FpuMemToReg::UseDataWrite, + fpu_reg_write: FpuRegWrite::YesWrite, + fpu_reg_dst: FpuRegDst::Reg3, + ..Default::default() + }; + + if r.op != OPCODE_OP_FP { + self.signals.fpu_reg_write = FpuRegWrite::NoWrite; + return; + } + + match r.funct7 >> 2 { + 0 => self.signals.fpu_alu_op = FpuAluOp::Addition, + 1 => self.signals.fpu_alu_op = FpuAluOp::Subtraction, + 2 => self.signals.fpu_alu_op = FpuAluOp::MultiplicationOrEqual, + 3 => self.signals.fpu_alu_op = FpuAluOp::Division, + 4 => match r.funct3 { + 0 => self.signals.fpu_alu_op = FpuAluOp::SGNJ, + 1 => self.signals.fpu_alu_op = FpuAluOp::SGNJN, + 2 => self.signals.fpu_alu_op = FpuAluOp::SGNJX, + _ => self.error("Unsupported Instruction!"), + }, + 5 => match r.funct3 { + 0 => self.signals.fpu_alu_op = FpuAluOp::Min, + 1 => self.signals.fpu_alu_op = FpuAluOp::Max, + _ => self.error("Unsupported Instruction!"), + }, + 11 => self.signals.fpu_alu_op = FpuAluOp::Sqrt, + 20 => { + self.signals.fpu_reg_write = FpuRegWrite::NoWrite; + self.signals.data_write = DataWrite::YesWrite; + self.signals.data_src = DataSrc::FloatingPointUnitComp; + match r.funct3 { + 0 => self.signals.fpu_alu_op = FpuAluOp::Sle, + 1 => self.signals.fpu_alu_op = FpuAluOp::Slt, + 2 => self.signals.fpu_alu_op = FpuAluOp::MultiplicationOrEqual, + _ => self.error("Unsupported Instruction!"), + } + } + 24 => { + self.signals.data_write = DataWrite::YesWrite; + self.signals.fpu_reg_write = FpuRegWrite::NoWrite; + } + 26 => { + self.signals.data_write = DataWrite::YesWrite; + self.signals.data_src = DataSrc::MainProcessorUnit; + } + 28 => { + self.signals.data_write = DataWrite::YesWrite; + self.signals.fpu_reg_write = FpuRegWrite::NoWrite; + match r.funct3 { + 0 => self.signals.data_src = DataSrc::FloatingPointBits, + 1 => { + self.signals.fpu_alu_op = FpuAluOp::Class; + self.signals.data_src = DataSrc::FloatingPointUnitMask; + } + _ => self.error("Unsupported Instruction!"), + } + } + 30 => { + self.signals.data_write = DataWrite::YesWrite; + self.signals.fpu_reg_write = FpuRegWrite::YesWrite; + self.signals.data_src = DataSrc::MainProcessorBits; + } + _ => self.error("Unsupported Instruction!"), + } + + match r.funct3 { + 0 => self.signals.round_mode = RoundingMode::RNE, + 1 => self.signals.round_mode = RoundingMode::RTZ, + 2 => self.signals.round_mode = RoundingMode::RDN, + 3 => self.signals.round_mode = RoundingMode::RUP, + 4 => self.signals.round_mode = RoundingMode::RMM, + 7 => self.signals.round_mode = RoundingMode::DRM, + _ => self.error("Unsupported Rounding Mode!"), + } + } + Instruction::IType(i) => { + self.signals = FpuControlSignals { + data_write: DataWrite::NoWrite, + fpu_mem_to_reg: FpuMemToReg::UseMemory, + fpu_reg_dst: FpuRegDst::Reg3, + fpu_reg_write: FpuRegWrite::YesWrite, + ..Default::default() + }; + + if i.op != OPCODE_LOAD_FP { + self.signals.fpu_reg_write = FpuRegWrite::NoWrite; + } + } + Instruction::SType(_s) => { + self.signals = FpuControlSignals { + data_write: DataWrite::NoWrite, + fpu_reg_write: FpuRegWrite::NoWrite, + ..Default::default() + }; + } + Instruction::R4Type(r4) => { + self.signals = FpuControlSignals { + data_write: DataWrite::NoWrite, + fpu_mem_to_reg: FpuMemToReg::UseDataWrite, + fpu_reg_dst: FpuRegDst::Reg3, + fpu_reg_write: FpuRegWrite::YesWrite, + ..Default::default() + }; + + self.signals.fpu_alu_op = match r4.op { + OPCODE_MADD => FpuAluOp::MAdd, + OPCODE_MSUB => FpuAluOp::MSub, + OPCODE_NMSUB => FpuAluOp::NMSub, + OPCODE_NMADD => FpuAluOp::NMAdd, + _ => { + self.error("Unsupported Instruction!"); + FpuAluOp::Addition + } + }; + } + _ => self.error("Unsupported Instruction!"), + } + } + + /// Read the registers as specified from the instruction and pass + /// the data into the datapath. + fn read_registers(&mut self) { + let reg1 = self.state.rs1 as usize; + let reg2 = self.state.rs2 as usize; + let reg3 = self.state.rs3 as usize; + + self.state.read_data_1 = self.registers.fpr[reg1]; + self.state.read_data_2 = self.registers.fpr[reg2]; + self.state.read_data_3 = self.registers.fpr[reg3]; + } + + // ======================= Execute (EX) ======================= + /// Perform an ALU operation. + fn alu(&mut self) { + let input1 = self.state.read_data_1 as u32; + let input2 = self.state.read_data_2 as u32; + let input3 = self.state.read_data_3 as u32; + let input1_f32 = f32::from_bits(input1); + let input2_f32 = f32::from_bits(input2); + let input3_f32 = f32::from_bits(input3); + let mut input_mask = 0b0000000000; + + let result_f32: f32 = match self.signals.fpu_alu_op { + FpuAluOp::Addition => input1_f32 + input2_f32, + FpuAluOp::Subtraction => input1_f32 - input2_f32, + FpuAluOp::MultiplicationOrEqual => input1_f32 * input2_f32, + FpuAluOp::Division => { + if input2_f32 == 0.0 { + 0.0 + } else { + input1_f32 / input2_f32 + } + } + FpuAluOp::Sqrt => input1_f32.sqrt(), + FpuAluOp::Min => { + if input1_f32 < input2_f32 { + input1_f32 + } else { + input2_f32 + } + } + FpuAluOp::Max => { + if input1_f32 > input2_f32 { + input1_f32 + } else { + input2_f32 + } + } + FpuAluOp::SGNJ => f32::from_bits((input1 & 0x01111111) | (input2 >> 31 << 31)), + FpuAluOp::SGNJN => f32::from_bits((input1 & 0x01111111) | (!(input2 >> 31) << 31)), + FpuAluOp::SGNJX => { + f32::from_bits((input1 & 0x01111111) | (((input2 >> 31) ^ (input1 >> 31)) << 31)) + } + FpuAluOp::Class => { + if input1_f32.is_sign_negative() { + if input1_f32.is_infinite() { + input_mask = 0b1; + } else if input1_f32.is_normal() { + input_mask = 0b10; + } else if input1_f32.is_subnormal() { + input_mask = 0b100; + } else { + input_mask = 0b1000; + } + } else if input1_f32.is_sign_positive() { + if input1_f32.is_infinite() { + input_mask = 0b10000000; + } else if input1_f32.is_normal() { + input_mask = 0b1000000; + } else if input1_f32.is_subnormal() { + input_mask = 0b100000; + } else { + input_mask = 0b10000; + } + } else if input1_f32.is_nan() { + input_mask = 0b100000000; + } else { + input_mask = 0b1000000000; + } + 0.0 + } + FpuAluOp::MAdd => input1_f32 * input2_f32 + input3_f32, + FpuAluOp::MSub => input1_f32 * input2_f32 - input3_f32, + FpuAluOp::NMSub => input1_f32.neg() * input2_f32 + input3_f32, + FpuAluOp::NMAdd => input1_f32.neg() * input2_f32 - input3_f32, + // No operation. + FpuAluOp::Slt | FpuAluOp::Sle => 0.0, + }; + + if result_f32.is_nan() { + self.state.alu_result = RISC_NAN as u64; + return; + } + + if (self.signals.fpu_alu_op == FpuAluOp::SGNJ) + | (self.signals.fpu_alu_op == FpuAluOp::SGNJN) + | (self.signals.fpu_alu_op == FpuAluOp::SGNJX) + { + self.state.alu_result = f32::to_bits(result_f32) as u64; + return; + } + + if self.signals.fpu_alu_op == FpuAluOp::Class { + self.state.alu_result = input_mask as u64; + return; + } + + self.state.alu_result = match self.signals.round_mode { + RoundingMode::RNE => f32::to_bits( + if (result_f32.ceil() - result_f32).abs() == (result_f32 - result_f32.floor()).abs() + { + if result_f32.ceil() % 2.0 == 0.0 { + result_f32.ceil() + } else { + result_f32.floor() + } + } else { + result_f32.round() + }, + ) as u64, + RoundingMode::RTZ => f32::to_bits(result_f32.trunc()) as u64, + RoundingMode::RDN => f32::to_bits(result_f32.floor()) as u64, + RoundingMode::RUP => f32::to_bits(result_f32.ceil()) as u64, + RoundingMode::RMM => f32::to_bits(result_f32.round()) as u64, + _ => f32::to_bits(result_f32) as u64, + }; + } + + /// Perform a comparison. + fn comparator(&mut self) { + let input1 = self.state.read_data_1 as u32; + let input2 = self.state.read_data_2 as u32; + let input1_f32 = f32::from_bits(input1); + let input2_f32 = f32::from_bits(input2); + + self.state.comparator_result = match self.signals.fpu_alu_op { + FpuAluOp::MultiplicationOrEqual => (input1_f32 == input2_f32) as u64, + FpuAluOp::Slt => (input1_f32 < input2_f32) as u64, + FpuAluOp::Sle => (input1_f32 <= input2_f32) as u64, + _ => 0, + } + } + + /// Write to the `Data` register. This register is used to transfer data between + /// the main processor and the coprocessor. + fn write_data(&mut self) { + if let DataWrite::NoWrite = self.signals.data_write { + return; + } + + self.data = match self.signals.data_src { + DataSrc::FloatingPointUnitRS1 => self.state.read_data_1, + DataSrc::FloatingPointUnitComp => self.state.comparator_result, + DataSrc::FloatingPointUnitMask => self.state.alu_result, + DataSrc::FloatingPointBits => self.state.read_data_1 as u32 as i64 as u64, + _ => self.state.data_from_main_processor, + }; + } + + /// Set the data line that goes from `Read Data 2` to the multiplexer in the main processor + /// controlled by [`MemWriteSrc`](super::control_signals::MemWriteSrc). + fn write_fp_register_to_memory(&mut self) { + self.state.fp_register_to_memory = self.state.read_data_2; + } + + // ======================= Memory (MEM) ======================= + /// Set the data line between the multiplexer after the `Data` register and the + /// multiplexer in the main processor controlled by the [`DataWrite`] control signal. + fn set_data_writeback(&mut self) { + if let DataWrite::NoWrite = self.signals.data_write { + return; + } + + self.state.data_writeback = match self.signals.data_src { + DataSrc::MainProcessorUnit => match self.state.rs2 { + 0 => f32::to_bits(self.data as i32 as f32) as u64, + 1 => f32::to_bits(self.data as u32 as f32) as u64, + 2 => f32::to_bits(self.data as i64 as f32) as u64, + 3 => f32::to_bits(self.data as f32) as u64, + _ => { + self.error(&format!( + "Unsupported Register Width `{:?}`", + self.state.rs2 + )); + 0 + } + }, + DataSrc::FloatingPointUnitRS1 => { + let data_unrounded = f32::from_bits(self.data as u32); + let data_rounded = match self.signals.round_mode { + RoundingMode::RNE => { + if (data_unrounded.ceil() - data_unrounded).abs() + == (data_unrounded - data_unrounded.floor()).abs() + { + if data_unrounded.ceil() % 2.0 == 0.0 { + data_unrounded.ceil() + } else { + data_unrounded.floor() + } + } else { + data_unrounded.round() + } + } + RoundingMode::RTZ => data_unrounded.trunc(), + RoundingMode::RDN => data_unrounded.floor(), + RoundingMode::RUP => data_unrounded.ceil(), + _ => data_unrounded.round(), + }; + + match self.state.rs2 { + 0 => { + if (data_rounded <= (-(2_i64.pow(31))) as f32) + | (data_rounded == f32::NEG_INFINITY) + { + -(2_i64.pow(31)) as u64 + } else if (data_rounded >= (2_i64.pow(31) - 1) as f32) + | (data_rounded == f32::INFINITY) + | (data_rounded.is_nan()) + { + (2_i64.pow(31) - 1) as u64 + } else { + data_rounded as i32 as u64 + } + } + 1 => { + if (data_rounded <= 0.0) | (data_rounded == f32::NEG_INFINITY) { + 0 + } else if (data_rounded >= (2_u64.pow(32) - 1) as f32) + | (data_rounded == f32::INFINITY) + | (data_rounded.is_nan()) + { + 2_u64.pow(32) - 1 + } else { + data_rounded as u32 as u64 + } + } + 2 => { + if (data_rounded <= (-(2_i64.pow(63))) as f32) + | (data_rounded == f32::NEG_INFINITY) + { + -(2_i64.pow(63)) as u64 + } else if (data_rounded >= (2_i64.pow(63) - 1) as f32) + | (data_rounded == f32::INFINITY) + | (data_rounded.is_nan()) + { + (2_i64.pow(63) - 1) as u64 + } else { + data_rounded as i64 as u64 + } + } + 3 => { + if (data_rounded <= 0.0) | (data_rounded == f32::NEG_INFINITY) { + 0 + } else if (data_rounded >= (0x1111111111111111_i64) as f32) + | (data_rounded == f32::INFINITY) + | (data_rounded.is_nan()) + { + 0x1111111111111111 + } else { + data_rounded as u64 + } + } + _ => { + self.error(&format!( + "Unsupported Register Width `{:?}`", + self.state.rs2 + )); + 0 + } + } + } + DataSrc::MainProcessorBits => self.data as u32 as u64, + _ => self.data, + } + } + + // ====================== Writeback (WB) ====================== + /// Write data to the floating-point register file. + fn register_write(&mut self) { + if let FpuRegWrite::NoWrite = self.signals.fpu_reg_write { + return; + } + + self.state.destination = match self.signals.fpu_reg_dst { + FpuRegDst::Reg1 => self.state.rs1 as usize, + FpuRegDst::Reg2 => self.state.rs2 as usize, + FpuRegDst::Reg3 => self.state.rd as usize, + }; + + self.state.register_write_mux_to_mux = match self.signals.data_write { + DataWrite::NoWrite => self.state.alu_result, + DataWrite::YesWrite => self.state.data_writeback, + }; + self.state.register_write_data = match self.signals.fpu_mem_to_reg { + FpuMemToReg::UseDataWrite => self.state.register_write_mux_to_mux, + FpuMemToReg::UseMemory => self.state.fp_register_data_from_main_processor, + }; + self.registers.fpr[self.state.destination] = self.state.register_write_data; + } +} diff --git a/src/emulation_core/riscv/datapath.rs b/src/emulation_core/riscv/datapath.rs index 00287aa29..6ce8fee8d 100644 --- a/src/emulation_core/riscv/datapath.rs +++ b/src/emulation_core/riscv/datapath.rs @@ -49,7 +49,9 @@ use super::super::datapath::Datapath; use super::constants::*; +use super::control_signals::floating_point::*; use super::control_signals::*; +use super::coprocessor::RiscFpCoprocessor; use super::datapath_signals::*; use super::instruction::*; use super::registers::RiscGpRegisterType; @@ -66,6 +68,7 @@ use serde::{Deserialize, Serialize}; pub struct RiscDatapath { pub registers: RiscGpRegisters, pub memory: Memory, + pub coprocessor: RiscFpCoprocessor, pub stack: Stack, pub instruction: Instruction, @@ -91,8 +94,10 @@ pub struct RiscDatapathState { pub instruction: u32, pub rs1: u32, pub rs2: u32, + pub rs3: u32, pub rd: u32, pub shamt: u32, + pub funct2: u32, pub funct3: u32, pub funct7: u32, pub imm: u32, @@ -201,6 +206,7 @@ impl Default for RiscDatapath { let mut datapath = RiscDatapath { registers: RiscGpRegisters::default(), memory: Memory::default(), + coprocessor: RiscFpCoprocessor::default(), stack: Stack::default(), instruction: Instruction::default(), signals: ControlSignals::default(), @@ -265,6 +271,11 @@ impl Datapath for RiscDatapath { RiscStage::WriteBack => self.stage_writeback(), }; + // If the FPU has halted, reflect this in the main unit. + if self.coprocessor.is_halted { + self.is_halted = true; + } + self.current_stage = RiscStage::get_next_stage(self.current_stage); res } @@ -294,8 +305,9 @@ impl Datapath for RiscDatapath { DatapathRef::RISCV(self) } - fn set_fp_register_by_str(&mut self, _register: &str, _data: Self::RegisterData) { - todo!() + fn set_fp_register_by_str(&mut self, register: &str, data: Self::RegisterData) { + let register = &mut self.coprocessor.registers[register]; + *register = data; } fn get_memory_mut(&mut self) -> &mut Memory { @@ -344,10 +356,12 @@ impl RiscDatapath { // Upper part of datapath, PC calculation self.pc_plus_4(); + self.coprocessor.set_instruction(self.state.instruction); // Both state and coprocessor state always update DatapathUpdateSignal { changed_state: true, + changed_coprocessor_state: true, ..Default::default() } } @@ -364,6 +378,10 @@ impl RiscDatapath { self.set_immediate(); self.read_registers(); + self.coprocessor.stage_instruction_decode(); + self.coprocessor + .set_data_from_main_processor(self.state.read_data_1); + // Check if we hit a syscall or breakpoint and signal it to the caller. let (hit_syscall, hit_breakpoint) = ( self.signals.sys_op == SysOp::ECALL, @@ -373,6 +391,7 @@ impl RiscDatapath { // Instruction decode always involves a state update DatapathUpdateSignal { changed_state: true, + changed_coprocessor_state: true, hit_syscall, hit_breakpoint, ..Default::default() @@ -386,9 +405,11 @@ impl RiscDatapath { self.alu(); self.calc_relative_pc_branch(); self.calc_cpu_branch_signal(); + self.coprocessor.stage_execute(); DatapathUpdateSignal { changed_state: true, + changed_coprocessor_state: true, ..Default::default() } } @@ -412,6 +433,8 @@ impl RiscDatapath { ReadWrite::StoreDouble => self.memory_write(), } + self.coprocessor.stage_memory(); + // PC calculation stuff from upper part of datapath self.calc_general_branch_signal(); self.pick_pc_plus_4_or_relative_branch_addr_mux1(); @@ -453,6 +476,7 @@ impl RiscDatapath { | (self.signals.read_write == ReadWrite::StoreDouble) | (self.signals.read_write == ReadWrite::StoreHalf) | (self.signals.read_write == ReadWrite::StoreWord)), + changed_coprocessor_state: true, changed_stack, ..Default::default() } @@ -464,7 +488,10 @@ impl RiscDatapath { /// if desired. Additionally, set the PC for the next instruction. fn stage_writeback(&mut self) -> DatapathUpdateSignal { self.register_write(); + self.coprocessor + .set_fp_register_data_from_main_processor(self.state.data_result); self.set_pc(); + self.coprocessor.stage_writeback(); // check if we are writing to the stack pointer let mut changed_stack = false; @@ -480,7 +507,10 @@ impl RiscDatapath { DatapathUpdateSignal { changed_state: true, + changed_coprocessor_state: true, changed_registers: true, // Always true because pc always gets updated + changed_coprocessor_registers: self.coprocessor.signals.fpu_reg_write + == FpuRegWrite::YesWrite, changed_stack, ..Default::default() } @@ -555,8 +585,13 @@ impl RiscDatapath { self.state.imm = j.imm; self.state.rd = j.rd as u32; } - Instruction::R4Type(r) => { - self.state.rd = r.rd as u32; + Instruction::R4Type(r4) => { + self.state.rs3 = r4.rs3 as u32; + self.state.funct2 = r4.funct2 as u32; + self.state.rs2 = r4.rs2 as u32; + self.state.rs1 = r4.rs1 as u32; + self.state.funct3 = r4.funct3 as u32; + self.state.rd = r4.rd as u32; } } } @@ -611,7 +646,9 @@ impl RiscDatapath { Instruction::JType(j) => { self.set_jtype_control_signals(j); } - _ => self.error("Unsupported Instruction!"), + Instruction::R4Type(r4) => { + self.set_r4type_control_signals(r4); + } } } @@ -632,6 +669,15 @@ impl RiscDatapath { self.datapath_signals.reg_width = RegisterWidth::HalfWidth; } + if r.op == OPCODE_OP_FP { + match r.funct7 >> 2 { + 20 => self.signals.reg_write_en = RegWriteEn::YesWrite, + 24 => self.signals.reg_write_en = RegWriteEn::YesWrite, + 28 => self.signals.reg_write_en = RegWriteEn::YesWrite, + _ => self.signals.reg_write_en = RegWriteEn::NoWrite, + } + } + match r.funct3 { 0 => match r.funct7 { 0b0000000 => self.signals.alu_op = AluOp::Addition, @@ -736,6 +782,20 @@ impl RiscDatapath { _ => (), } } + OPCODE_LOAD_FP => { + self.signals.wb_sel = WBSel::UseMemory; + self.signals.reg_write_en = RegWriteEn::NoWrite; + match i.funct3 { + 0 => self.signals.read_write = ReadWrite::LoadByte, + 1 => self.signals.read_write = ReadWrite::LoadHalf, + 2 => self.signals.read_write = ReadWrite::LoadWord, + 3 => self.signals.read_write = ReadWrite::LoadDouble, + 4 => self.signals.read_write = ReadWrite::LoadByteUnsigned, + 5 => self.signals.read_write = ReadWrite::LoadHalfUnsigned, + 6 => self.signals.read_write = ReadWrite::LoadWordUnsigned, + _ => (), + } + } OPCODE_SYSTEM => { self.signals.imm_select = ImmSelect::IUnsigned; self.signals.wb_sel = WBSel::UseAlu; @@ -782,6 +842,10 @@ impl RiscDatapath { ..Default::default() }; + if s.op == OPCODE_STORE_FP { + self.signals.mem_write_src = MemWriteSrc::FloatingPointUnit; + } + match s.funct3 { 0 => self.signals.read_write = ReadWrite::StoreByte, 1 => self.signals.read_write = ReadWrite::StoreHalf, @@ -841,6 +905,19 @@ impl RiscDatapath { }; } + /// Set the control signals for the datapath, specifically in the + /// case where the instruction is an R$-type. + fn set_r4type_control_signals(&mut self, _r4: R4Type) { + self.signals = ControlSignals { + op2_select: OP2Select::DATA2, + branch_jump: BranchJump::NoBranch, + read_write: ReadWrite::NoLoadStore, + wb_sel: WBSel::UseAlu, + reg_write_en: RegWriteEn::NoWrite, + ..Default::default() + }; + } + /// Read the registers as specified from the instruction and pass /// the data into the datapath. fn read_registers(&mut self) { @@ -1057,7 +1134,10 @@ impl RiscDatapath { fn memory_write(&mut self) { let address = self.state.alu_result; - self.state.write_data = self.state.read_data_2; + self.state.write_data = match self.signals.mem_write_src { + MemWriteSrc::PrimaryUnit => self.state.read_data_2, + MemWriteSrc::FloatingPointUnit => self.coprocessor.get_fp_register_to_memory(), + }; // Choose the correct store function based on the RegWidth // control signal. @@ -1124,7 +1204,10 @@ impl RiscDatapath { }; // Decide to retrieve data either from the main processor or the coprocessor. - self.state.register_write_data = self.state.data_result; + self.state.register_write_data = match self.coprocessor.signals.data_write { + DataWrite::NoWrite => self.state.data_result, + DataWrite::YesWrite => self.coprocessor.get_data_writeback(), + }; // Abort if the RegWrite signal is not set. if self.signals.reg_write_en == RegWriteEn::NoWrite { diff --git a/src/emulation_core/riscv/instruction.rs b/src/emulation_core/riscv/instruction.rs index 5007a9445..e1c9bc92f 100644 --- a/src/emulation_core/riscv/instruction.rs +++ b/src/emulation_core/riscv/instruction.rs @@ -1,5 +1,7 @@ //! Abstract representation of an instruction. +use serde::{Deserialize, Serialize}; + use super::constants::*; /// Register (R-Type) Instruction @@ -20,7 +22,7 @@ use super::constants::*; /// - funct3: /// - rd: CPU register - can be used as a destination for the result of executed instructions. /// - opcode: Determines the type of instruction executed. This is typically 0110011 in R-type instructions. -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct RType { pub funct7: u8, pub rs2: u8, @@ -30,7 +32,7 @@ pub struct RType { pub op: u8, } -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct IType { pub imm: u16, pub rs1: u8, @@ -39,7 +41,7 @@ pub struct IType { pub op: u8, } -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct SType { pub imm1: u8, pub rs2: u8, @@ -49,7 +51,7 @@ pub struct SType { pub op: u8, } -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct BType { pub imm1: u8, pub rs2: u8, @@ -59,21 +61,21 @@ pub struct BType { pub op: u8, } -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct UType { pub imm: u32, pub rd: u8, pub op: u8, } -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct JType { pub imm: u32, pub rd: u8, pub op: u8, } -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct R4Type { pub rs3: u8, pub funct2: u8, @@ -84,7 +86,7 @@ pub struct R4Type { pub op: u8, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Instruction { RType(RType), IType(IType), @@ -109,7 +111,7 @@ impl TryFrom for Instruction { let op = (value & 0x7f) as u8; match op { // R-type instructions: - OPCODE_OP | OPCODE_OP_32 => Ok(Instruction::RType(RType { + OPCODE_OP | OPCODE_OP_32 | OPCODE_OP_FP => Ok(Instruction::RType(RType { funct7: (value >> 25) as u8, rs2: ((value >> 20) & 0x1f) as u8, rs1: ((value >> 15) & 0x1f) as u8, @@ -119,18 +121,17 @@ impl TryFrom for Instruction { })), // I-type instructions: - OPCODE_IMM | OPCODE_IMM_32 | OPCODE_JALR | OPCODE_LOAD | OPCODE_SYSTEM => { - Ok(Instruction::IType(IType { - imm: (value >> 20) as u16, - rs1: ((value >> 15) & 0x1f) as u8, - funct3: ((value >> 12) & 0x07) as u8, - rd: ((value >> 7) & 0x1f) as u8, - op: (value & 0x7f) as u8, - })) - } + OPCODE_IMM | OPCODE_IMM_32 | OPCODE_JALR | OPCODE_LOAD | OPCODE_SYSTEM + | OPCODE_LOAD_FP => Ok(Instruction::IType(IType { + imm: (value >> 20) as u16, + rs1: ((value >> 15) & 0x1f) as u8, + funct3: ((value >> 12) & 0x07) as u8, + rd: ((value >> 7) & 0x1f) as u8, + op: (value & 0x7f) as u8, + })), // S-type instruction: - OPCODE_STORE => Ok(Instruction::SType(SType { + OPCODE_STORE | OPCODE_STORE_FP => Ok(Instruction::SType(SType { imm1: (value >> 25) as u8, rs2: ((value >> 20) & 0x1f) as u8, rs1: ((value >> 15) & 0x1f) as u8, @@ -163,6 +164,19 @@ impl TryFrom for Instruction { op: (value & 0x7f) as u8, })), + // R4-type instruction: + OPCODE_MADD | OPCODE_MSUB | OPCODE_NMSUB | OPCODE_NMADD => { + Ok(Instruction::R4Type(R4Type { + rs3: (value >> 27) as u8, + funct2: ((value >> 25) & 0x3) as u8, + rs2: ((value >> 20) & 0x1f) as u8, + rs1: ((value >> 15) & 0x1f) as u8, + funct3: ((value >> 12) & 0x07) as u8, + rd: ((value >> 7) & 0x1f) as u8, + op: (value & 0x7f) as u8, + })) + } + _ => Err(format!("opcode `{op}` not supported")), } } diff --git a/src/emulation_core/riscv/registers.rs b/src/emulation_core/riscv/registers.rs index 4672f077f..aeb28bcc7 100644 --- a/src/emulation_core/riscv/registers.rs +++ b/src/emulation_core/riscv/registers.rs @@ -257,3 +257,166 @@ impl IntoIterator for RiscGpRegisters { } } } + +/// Collection of general-purpose registers used by the datapath. +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct RiscFpRegisters { + pub fpr: [u64; 32], +} + +/// Specifies all of the valid registers accessible in an instance +/// of [`RiscFpRegisters`]. +#[derive(Clone, Copy, Debug, Display, EnumIter, EnumString, Eq, PartialEq)] +#[strum(ascii_case_insensitive)] +#[strum(serialize_all = "lowercase")] +pub enum FpRegisterType { + F0 = 0, + F1 = 1, + F2 = 2, + F3 = 3, + F4 = 4, + F5 = 5, + F6 = 6, + F7 = 7, + F8 = 8, + F9 = 9, + F10 = 10, + F11 = 11, + F12 = 12, + F13 = 13, + F14 = 14, + F15 = 15, + F16 = 16, + F17 = 17, + F18 = 18, + F19 = 19, + F20 = 20, + F21 = 21, + F22 = 22, + F23 = 23, + F24 = 24, + F25 = 25, + F26 = 26, + F27 = 27, + F28 = 28, + F29 = 29, + F30 = 30, + F31 = 31, +} + +impl RegisterType for FpRegisterType { + fn get_register_name(&self) -> String { + format!("f{}", *self as u32) + } + fn is_valid_register_value(&self, _value: u64, _pc_limit: usize) -> bool { + true + } +} + +impl Registers for RiscFpRegisters { + fn get_dyn_register_list(&self) -> Vec<(Rc, u64)> { + self.into_iter() + .map(|(register, val)| { + let register: Rc = Rc::new(register); + (register, val) + }) + .collect() + } +} + +impl ToString for RiscFpRegisters { + fn to_string(&self) -> String { + let mut output = String::new(); + + let fpr_registers = self + .fpr + .iter() + .enumerate() + .map(|(i, inst)| format!("fpr[{i}] = {inst}")) + .collect::>() + .join("\n"); + output.push_str(&fpr_registers); + + output + } +} + +impl Index<&str> for RiscFpRegisters { + type Output = u64; + + // Convert string to the corresponding RegistersEnum value and use this to index. + // If this is an invalid string, no enum will be returned, causing a panic as desired. + fn index(&self, index: &str) -> &Self::Output { + match FpRegisterType::from_str(index) { + Ok(register) => &self[register], + _ => panic!("{index} is not a valid register"), + } + } +} + +impl IndexMut<&str> for RiscFpRegisters { + // Convert string to the corresponding RegistersEnum value and use this to index. + // If this is an invalid string, no enum will be returned, causing a panic as desired. + fn index_mut(&mut self, index: &str) -> &mut Self::Output { + match FpRegisterType::from_str(index) { + Ok(register) => &mut self[register], + _ => panic!("{index} is not a valid register"), + } + } +} + +impl Index for RiscFpRegisters { + type Output = u64; + + fn index(&self, index: FpRegisterType) -> &Self::Output { + &self.fpr[index as usize] + } +} + +impl IndexMut for RiscFpRegisters { + fn index_mut(&mut self, index: FpRegisterType) -> &mut Self::Output { + &mut self.fpr[index as usize] + } +} + +/// Iterator that is used to view each register in the register file. +/// +/// This contains a copy of all the registers and their values, and a [`FpRegisterTypeIter`], +/// as generated by [`strum::IntoEnumIterator`]. In other iterator implementations, +/// the internal state might be data like a [`FpRegisterType`]. However, since we can't +/// normally just "add 1" to get to the next register, we use an internal iterator +/// that can track the progression of one [`FpRegisterType`] to the next. +pub struct FpRegistersIter { + registers: RiscFpRegisters, + register_iter: FpRegisterTypeIter, +} + +/// This implementation of the [`Iterator`] trait essentially wraps the existing +/// [`FpRegisterTypeIter`] so that the register type can be paired with register data. +impl Iterator for FpRegistersIter { + type Item = (FpRegisterType, u64); + + fn next(&mut self) -> Option { + match self.register_iter.next() { + Some(register_type) => Some((register_type, self.registers[register_type])), + None => None, + } + } +} + +/// [`IntoIterator`] is a standard library trait that can convert any type into +/// an [`Iterator`]. In this case, this is an instance of [`FpRegistersIter`] with all the +/// data in the registers and a new [`FpRegisterTypeIter`]. +impl IntoIterator for RiscFpRegisters { + type Item = (FpRegisterType, u64); + type IntoIter = FpRegistersIter; + + /// Consumes the [`RiscFpRegisters`] struct to create a new [`FpRegistersIter`] that can + /// be iterated over. + fn into_iter(self) -> Self::IntoIter { + FpRegistersIter { + registers: self, + register_iter: FpRegisterType::iter(), + } + } +} diff --git a/src/ui/regview/component.rs b/src/ui/regview/component.rs index eb4abcf14..f7953e28c 100644 --- a/src/ui/regview/component.rs +++ b/src/ui/regview/component.rs @@ -177,8 +177,8 @@ pub fn generate_fpr_rows(props: &Regviewprops, unit_type: UnitState) -> Html { }} value={ match unit_type { - UnitState::Float => format!("{:e}", data).to_string(), - UnitState::Double => format!("{:e}", data).to_string(), + UnitState::Float => format!("{}", f32::from_bits(data as u32)).to_string(), + UnitState::Double => format!("{}", f64::from_bits(data)).to_string(), UnitState::Hex => format!("{:#04x?}", data).to_string(), UnitState::Bin => format!("{:#b}", data).to_string(), _ => format!("{:?}", data).to_string(),