From d77288e5b119f7953b6664189636e81ad31e832f Mon Sep 17 00:00:00 2001 From: rharding8 Date: Wed, 27 Mar 2024 18:28:53 -0400 Subject: [PATCH 01/22] Updated with ECALL fix from SWIM-v2 --- src/emulation_core/riscv/datapath.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/emulation_core/riscv/datapath.rs b/src/emulation_core/riscv/datapath.rs index bf145f799..d8d052c54 100644 --- a/src/emulation_core/riscv/datapath.rs +++ b/src/emulation_core/riscv/datapath.rs @@ -671,7 +671,8 @@ impl RiscDatapath { 0 => SysOp::ECALL, 1 => SysOp::EBREAK, _ => SysOp::None, - } + }; + self.signals.reg_write_en = RegWriteEn::NoWrite; } 1 => self.signals.sys_op = SysOp::CSRReadWrite, 2 => self.signals.sys_op = SysOp::CSRReadSet, @@ -879,7 +880,8 @@ impl RiscDatapath { self.registers.gpr[self.state.imm as usize] &= !self.state.alu_input1; } } - _ => self.error("Impossible/Unsupported Instruction!"), + SysOp::None => self.error("Impossible/Unsupported Instruction!"), + _ => self.state.alu_result = 0, } } From af693bd3cd2339ef33dd83e156fcaa468591a10a Mon Sep 17 00:00:00 2001 From: rharding8 Date: Wed, 27 Mar 2024 18:59:32 -0400 Subject: [PATCH 02/22] Added Coprocessor. Implemented instruction_decode. Moving on to FPU control signals. --- src/emulation_core/riscv.rs | 1 + src/emulation_core/riscv/constants.rs | 10 + src/emulation_core/riscv/control_signals.rs | 249 ++++++++++++ src/emulation_core/riscv/coprocessor.rs | 429 ++++++++++++++++++++ src/emulation_core/riscv/datapath.rs | 4 +- src/emulation_core/riscv/instruction.rs | 31 +- src/emulation_core/riscv/registers.rs | 164 ++++++++ 7 files changed, 877 insertions(+), 11 deletions(-) create mode 100644 src/emulation_core/riscv/coprocessor.rs diff --git a/src/emulation_core/riscv.rs b/src/emulation_core/riscv.rs index a937a8c28..8085e380a 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..b9f29eeaa 100644 --- a/src/emulation_core/riscv/constants.rs +++ b/src/emulation_core/riscv/constants.rs @@ -70,6 +70,16 @@ 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; + // "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 dd9a1717b..c315f7a23 100644 --- a/src/emulation_core/riscv/control_signals.rs +++ b/src/emulation_core/riscv/control_signals.rs @@ -201,3 +201,252 @@ 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 cc: Cc, + pub cc_write: CcWrite, + pub data_src: DataSrc, + pub data_write: DataWrite, + pub fpu_alu_op: FpuAluOp, + pub fpu_branch: FpuBranch, + pub fpu_mem_to_reg: FpuMemToReg, + pub fpu_reg_dst: FpuRegDst, + pub fpu_reg_width: FpuRegWidth, + pub fpu_reg_write: FpuRegWrite, + pub fpu_take_branch: FpuTakeBranch, + } + + /// Determines, given that [`CcWrite`] is set, which condition code register + /// should be written to or read from for a given operation. + /// + /// For the sake of this project, it will usually be assumed that this will + /// be 0, however the functionality is available to be extended. + #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] + pub enum Cc { + /// Use condition code register 0. Default in most operations. Can be + /// additionally used in the case where the condition code register is + /// irrelevant to the current instruction. + #[default] + Cc0 = 0, + } + + /// Determines if the condition code register file should be written to. + #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] + pub enum CcWrite { + #[default] + NoWrite = 0, + YesWrite = 1, + } + + /// 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 + /// `rt` 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 `fs` + /// from a given instruction. + #[default] + FloatingPointUnit = 1, + } + + /// 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 [`RegWrite`](super::RegWrite) 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 `fs` 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 `rt` 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] + /// `_0000` (0): + /// - ALU: Perform an addition. + Addition = 0, + + /// `_0001` (1): + /// - ALU: Perform a subtraction. + Subtraction = 1, + + /// `_0010` (2): + /// - ALU: Perform a multiplication. + /// - Comparator: Set if equal. + MultiplicationOrEqual = 2, + + /// `_0011` (3): + /// - ALU: Perform a division. + Division = 3, + + /// `_0100` (4): + /// - ALU: Perform an "AND" operation. + And = 4, + + /// `_0101` (5): + /// - ALU: Perform an "OR" operation. + Or = 5, + + /// `_1100` (12): + /// - Comparator: Set if less than. + Slt = 12, + + /// `_1101` (13): + /// - Comparator: Set if not greater than or equal. + Snge = 13, + + /// `_1110` (14): + /// - Comparator: Set if less than or equal. + Sle = 14, + + /// `_1111` (15): + /// - Comparator: Set if not greater than. + Sngt = 15, + } + + 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_NGE => Ok(Self::Snge), + FUNCTION_C_LE => Ok(Self::Sle), + FUNCTION_C_NGT => Ok(Self::Sngt), + _ => Err(format!("Unsupported function code `{function}`")), + } + } + } + + /// Determines if the floating-point unit should consider branching, based on the + /// contents of the condition code register. + /// + /// This directly overrides any branch decisions decided by the main processing unit. + /// The [`Branch`](super::Branch) control signal should not be set in addition to this signal. + #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] + pub enum FpuBranch { + /// Do not consider branching. + #[default] + NoBranch = 0, + + /// Consider branching. + YesBranch = 1, + } + + /// 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 `ft`. + Reg1 = 0, + + /// Use register `fs`. + Reg2 = 1, + + /// Use register `fd`. + #[default] + Reg3 = 2, + } + + /// Determines the amount of data to be sent or received from registers and the ALU. + /// + /// While all buses carrying information are 64-bits wide, some bits of the bus may be + /// ignored in the case of this control signal. + #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] + pub enum FpuRegWidth { + /// Use words (32 bits). Equivalent to a single-precision floating-point value. + Word = 0, + + /// Use doublewords (64 bits). Equivalent to a double-precision floating-point value. + #[default] + DoubleWord = 1, + } + + impl FpuRegWidth { + /// Get the corresponding [`FpuRegWidth`] control signal based on + /// the `fmt` field in an instruction. + pub fn from_fmt(fmt: u8) -> Result { + match fmt { + FMT_SINGLE => Ok(Self::Word), + FMT_DOUBLE => Ok(Self::DoubleWord), + _ => Err(format!("`{fmt}` is an invalid fmt value")), + } + } + } + + /// 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, + } + + /// After checking the [`FpuBranch`] and condition code, this signal determines whether + /// to follow through with a branch. + /// + /// This signal is what is sent to the main processor. + #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] + pub enum FpuTakeBranch { + #[default] + NoBranch = 0, + YesBranch = 1, + } +} diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs new file mode 100644 index 000000000..54f019272 --- /dev/null +++ b/src/emulation_core/riscv/coprocessor.rs @@ -0,0 +1,429 @@ +//! Implementation of a RISC-V floating-point coprocessor. + +use super::constants::*; +use super::control_signals::floating_point::*; +use super::registers::FpRegisters; +use super::instruction::Instruction; +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: FpRegisters, + 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 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_condition_code(); + self.write_fp_register_to_memory(); + self.set_condition_code_line(); + } + + pub fn stage_memory(&mut self) { + self.write_data(); + self.set_data_writeback(); + self.set_fpu_branch(); + } + + 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) { + + } + + /// Read the registers as specified from the instruction and pass + /// the data into the datapath. + fn read_registers(&mut self) { + let reg1 = self.state.fs as usize; + let reg2 = self.state.ft as usize; + + self.state.read_data_1 = self.registers.fpr[reg1]; + self.state.read_data_2 = self.registers.fpr[reg2]; + + // Truncate the variable data if a 32-bit word is requested. + if let FpuRegWidth::Word = self.signals.fpu_reg_width { + self.state.read_data_1 = self.registers.fpr[reg1] as u32 as u64; + self.state.read_data_2 = self.registers.fpr[reg2] as u32 as u64; + } + } + + // ======================= Execute (EX) ======================= + /// Perform an ALU operation. + fn alu(&mut self) { + let input1 = self.state.read_data_1; + let input2 = self.state.read_data_2; + + let mut input1_f32 = 0f32; + let mut input2_f32 = 0f32; + let mut input1_f64 = 0f64; + let mut input2_f64 = 0f64; + + // Truncate the inputs if 32-bit operations are expected. + if let FpuRegWidth::Word = self.signals.fpu_reg_width { + input1_f32 = f32::from_bits(input1 as u32); + input2_f32 = f32::from_bits(input2 as u32); + } else { + input1_f64 = f64::from_bits(input1); + input2_f64 = f64::from_bits(input2); + } + + self.state.alu_result = match self.signals.fpu_alu_op { + FpuAluOp::Addition => match self.signals.fpu_reg_width { + FpuRegWidth::Word => f32::to_bits(input1_f32 + input2_f32) as u64, + FpuRegWidth::DoubleWord => f64::to_bits(input1_f64 + input2_f64), + }, + FpuAluOp::Subtraction => match self.signals.fpu_reg_width { + FpuRegWidth::Word => f32::to_bits(input1_f32 - input2_f32) as u64, + FpuRegWidth::DoubleWord => f64::to_bits(input1_f64 - input2_f64), + }, + FpuAluOp::MultiplicationOrEqual => match self.signals.fpu_reg_width { + FpuRegWidth::Word => f32::to_bits(input1_f32 * input2_f32) as u64, + FpuRegWidth::DoubleWord => f64::to_bits(input1_f64 * input2_f64), + }, + FpuAluOp::Division => match self.signals.fpu_reg_width { + FpuRegWidth::Word => { + if input2_f32 == 0f32 { + f32::to_bits(0f32) as u64 + } else { + f32::to_bits(input1_f32 / input2_f32) as u64 + } + } + FpuRegWidth::DoubleWord => { + if input2_f64 == 0.0 { + f64::to_bits(0.0) + } else { + f64::to_bits(input1_f64 / input2_f64) + } + } + }, + // No operation. + FpuAluOp::Slt | FpuAluOp::Snge | FpuAluOp::Sle | FpuAluOp::Sngt => 0, + _ => { + self.error(&format!( + "Unsupported operation in FPU `{:?}`", + self.signals.fpu_alu_op + )); + 0 + } + }; + } + + /// Perform a comparison. + fn comparator(&mut self) { + let input1 = self.state.read_data_1; + let input2 = self.state.read_data_2; + + let input1_f32 = f32::from_bits(input1 as u32); + let input2_f32 = f32::from_bits(input2 as u32); + let input1_f64 = f64::from_bits(input1); + let input2_f64 = f64::from_bits(input2); + + self.state.comparator_result = match self.signals.fpu_alu_op { + FpuAluOp::MultiplicationOrEqual => match self.signals.fpu_reg_width { + FpuRegWidth::Word => (input1_f32 == input2_f32) as u64, + FpuRegWidth::DoubleWord => (input1_f64 == input2_f64) as u64, + }, + FpuAluOp::Slt => match self.signals.fpu_reg_width { + FpuRegWidth::Word => (input1_f32 < input2_f32) as u64, + FpuRegWidth::DoubleWord => (input1_f64 < input2_f64) as u64, + }, + FpuAluOp::Sle => match self.signals.fpu_reg_width { + FpuRegWidth::Word => (input1_f32 <= input2_f32) as u64, + FpuRegWidth::DoubleWord => (input1_f64 <= input2_f64) as u64, + }, + FpuAluOp::Sngt => match self.signals.fpu_reg_width { + FpuRegWidth::Word => !input1_f32.gt(&input2_f32) as u64, + FpuRegWidth::DoubleWord => !input1_f64.gt(&input2_f64) as u64, + }, + FpuAluOp::Snge => match self.signals.fpu_reg_width { + FpuRegWidth::Word => !input1_f32.ge(&input2_f32) as u64, + FpuRegWidth::DoubleWord => !input1_f64.ge(&input2_f64) as u64, + }, + FpuAluOp::Addition | FpuAluOp::Subtraction | FpuAluOp::Division => 0, // No operation + _ => { + self.error(&format!( + "Unsupported operation in comparator `{:?}`", + self.signals.fpu_alu_op + )); + 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::FloatingPointUnit => self.state.read_data_1, + DataSrc::MainProcessorUnit => self.state.data_from_main_processor, + }; + } + + /// Set the condition code (CC) register based on the result from the comparator. + fn write_condition_code(&mut self) { + if let CcWrite::YesWrite = self.signals.cc_write { + self.condition_code = self.state.comparator_result; + } + } + + /// 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 that goes out of the condition code register file. + fn set_condition_code_line(&mut self) { + // The MIPS architecture supports more than one condition code, but SWIM + // manually uses only one. This stubs the possible use of more than one + // for future development. + let selected_register_data = match self.signals.cc { + Cc::Cc0 => self.condition_code, + }; + + // This only considers one bit of the selected condition code register. + self.state.condition_code_bit = match selected_register_data % 2 { + 0 => 0, + _ => 1, + }; + } + + /// 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) { + self.state.sign_extend_data = self.data as i32 as i64 as u64; + self.state.data_writeback = match self.signals.fpu_reg_width { + FpuRegWidth::Word => self.state.sign_extend_data, + FpuRegWidth::DoubleWord => self.data, + } + } + + /// Simulate the logic between `self.state.condition_code_bit` and the FPU branch + /// AND gate. + fn set_fpu_branch(&mut self) { + // Invert the condition code. (In this case, instead of using a bitwise NOT, this + // will invert only the last digit and leave the rest as 0.) + self.state.condition_code_bit_inverted = match self.state.condition_code_bit % 2 { + 0 => 1, + _ => 0, + }; + + // Run the multiplexer. + self.state.condition_code_mux = match self.state.branch_flag { + // 0 - Use inverted condition code. + false => self.state.condition_code_bit_inverted, + // 1 - Use condition code value as-is. + true => self.state.condition_code_bit, + }; + + // Set the result of the AND gate. + self.signals.fpu_take_branch = if self.signals.fpu_branch == FpuBranch::YesBranch + && self.state.condition_code_mux == 1 + { + FpuTakeBranch::YesBranch + } else { + FpuTakeBranch::NoBranch + }; + } + + // ====================== 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.ft as usize, + FpuRegDst::Reg2 => self.state.fs as usize, + FpuRegDst::Reg3 => self.state.fd as usize, + }; + + self.state.register_write_mux_to_mux = match self.signals.data_write { + DataWrite::NoWrite => self.state.alu_result, + DataWrite::YesWrite => self.data, + }; + 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 d8d052c54..b69860bc5 100644 --- a/src/emulation_core/riscv/datapath.rs +++ b/src/emulation_core/riscv/datapath.rs @@ -506,9 +506,7 @@ impl RiscDatapath { self.state.imm = j.imm; self.state.rd = j.rd as u32; } - Instruction::R4Type(r) => { - self.state.rd = r.rd as u32; - } + _ => (), } } diff --git a/src/emulation_core/riscv/instruction.rs b/src/emulation_core/riscv/instruction.rs index 5007a9445..57bd1ede8 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), @@ -163,6 +165,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 77f092d8e..d69c76366 100644 --- a/src/emulation_core/riscv/registers.rs +++ b/src/emulation_core/riscv/registers.rs @@ -257,3 +257,167 @@ impl IntoIterator for RiscGpRegisters { } } } + +/// Collection of general-purpose registers used by the datapath. +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct FpRegisters { + pub fpr: [u64; 32], +} + +/// Specifies all of the valid registers accessible in an instance +/// of [`FpRegisters`]. +#[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, + FCSR = 32, +} + +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 FpRegisters { + 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 FpRegisters { + 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 FpRegisters { + 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 FpRegisters { + // 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 FpRegisters { + type Output = u64; + + fn index(&self, index: FpRegisterType) -> &Self::Output { + &self.fpr[index as usize] + } +} + +impl IndexMut for FpRegisters { + 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: FpRegisters, + 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 FpRegisters { + type Item = (FpRegisterType, u64); + type IntoIter = FpRegistersIter; + + /// Consumes the [`FpRegisters`] struct to create a new [`FpRegistersIter`] that can + /// be iterated over. + fn into_iter(self) -> Self::IntoIter { + FpRegistersIter { + registers: self, + register_iter: FpRegisterType::iter(), + } + } +} From 23213e5edcf17ce69e5a7c58d6337bebca64da7b Mon Sep 17 00:00:00 2001 From: rharding8 Date: Wed, 27 Mar 2024 20:17:30 -0400 Subject: [PATCH 03/22] Updated Datapath logic for FPU and implemented FLW the entire way through. --- src/emulation_core/riscv.rs | 2 +- src/emulation_core/riscv/constants.rs | 5 ++ src/emulation_core/riscv/control_signals.rs | 59 ++--------------- src/emulation_core/riscv/coprocessor.rs | 45 +++++++++---- src/emulation_core/riscv/datapath.rs | 71 +++++++++++++++++++-- 5 files changed, 109 insertions(+), 73 deletions(-) diff --git a/src/emulation_core/riscv.rs b/src/emulation_core/riscv.rs index 8085e380a..8e83c2b45 100644 --- a/src/emulation_core/riscv.rs +++ b/src/emulation_core/riscv.rs @@ -3,7 +3,7 @@ pub mod constants; pub mod control_signals; -// pub mod coprocessor; +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 b9f29eeaa..bdf49f599 100644 --- a/src/emulation_core/riscv/constants.rs +++ b/src/emulation_core/riscv/constants.rs @@ -52,11 +52,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; @@ -80,6 +82,9 @@ pub const OPCODE_NMSUB: u8 = 0b1001011; // FNMADD.S pub const OPCODE_NMADD: u8 = 0b1001111; +/// Not a Number +pub const NAN: u8 = 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 c315f7a23..5181aafbb 100644 --- a/src/emulation_core/riscv/control_signals.rs +++ b/src/emulation_core/riscv/control_signals.rs @@ -208,41 +208,16 @@ pub mod floating_point { #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] pub struct FpuControlSignals { - pub cc: Cc, - pub cc_write: CcWrite, pub data_src: DataSrc, pub data_write: DataWrite, pub fpu_alu_op: FpuAluOp, pub fpu_branch: FpuBranch, pub fpu_mem_to_reg: FpuMemToReg, pub fpu_reg_dst: FpuRegDst, - pub fpu_reg_width: FpuRegWidth, pub fpu_reg_write: FpuRegWrite, pub fpu_take_branch: FpuTakeBranch, } - /// Determines, given that [`CcWrite`] is set, which condition code register - /// should be written to or read from for a given operation. - /// - /// For the sake of this project, it will usually be assumed that this will - /// be 0, however the functionality is available to be extended. - #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] - pub enum Cc { - /// Use condition code register 0. Default in most operations. Can be - /// additionally used in the case where the condition code register is - /// irrelevant to the current instruction. - #[default] - Cc0 = 0, - } - - /// Determines if the condition code register file should be written to. - #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] - pub enum CcWrite { - #[default] - NoWrite = 0, - YesWrite = 1, - } - /// Determines the source of the `Data` register in the floating-point unit. /// /// This is a special intermediary register that facilitates passing data between @@ -265,7 +240,7 @@ pub mod floating_point { /// 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 [`RegWrite`](super::RegWrite) and + /// 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)] @@ -391,43 +366,17 @@ pub mod floating_point { /// to, which largely depends on the instruction format. #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] pub enum FpuRegDst { - /// Use register `ft`. + /// Use register `rs1`. Reg1 = 0, - /// Use register `fs`. + /// Use register `rs2`. Reg2 = 1, - /// Use register `fd`. + /// Use register `rd`. #[default] Reg3 = 2, } - /// Determines the amount of data to be sent or received from registers and the ALU. - /// - /// While all buses carrying information are 64-bits wide, some bits of the bus may be - /// ignored in the case of this control signal. - #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] - pub enum FpuRegWidth { - /// Use words (32 bits). Equivalent to a single-precision floating-point value. - Word = 0, - - /// Use doublewords (64 bits). Equivalent to a double-precision floating-point value. - #[default] - DoubleWord = 1, - } - - impl FpuRegWidth { - /// Get the corresponding [`FpuRegWidth`] control signal based on - /// the `fmt` field in an instruction. - pub fn from_fmt(fmt: u8) -> Result { - match fmt { - FMT_SINGLE => Ok(Self::Word), - FMT_DOUBLE => Ok(Self::DoubleWord), - _ => Err(format!("`{fmt}` is an invalid fmt value")), - } - } - } - /// Determines if the floating-point register file should be written to. #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] pub enum FpuRegWrite { diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index 54f019272..777990a03 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -200,23 +200,44 @@ impl RiscFpCoprocessor { /// 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.set_rtype_control_signals(r); + } + Instruction::IType(i) => { + self.signals = FpuControlSignals { + data_write: DataWrite::NoWrite, + fpu_branch: FpuBranch::NoBranch, + fpu_mem_to_reg: FpuMemToReg::UseMemory, + fpu_reg_dst: FpuRegDst::Reg3, + fpu_reg_write: FpuRegWrite::YesWrite, + ..Default::default() + } + } + Instruction::SType(s) => { + self.set_stype_control_signals(s); + } + Instruction::BType(b) => { + self.set_btype_control_signals(b); + } + Instruction::UType(u) => { + self.set_utype_control_signals(u); + } + Instruction::JType(j) => { + self.set_jtype_control_signals(j); + } + _ => 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.fs as usize; - let reg2 = self.state.ft as usize; + let reg1 = self.state.rs1 as usize; + let reg2 = self.state.rs2 as usize; self.state.read_data_1 = self.registers.fpr[reg1]; self.state.read_data_2 = self.registers.fpr[reg2]; - - // Truncate the variable data if a 32-bit word is requested. - if let FpuRegWidth::Word = self.signals.fpu_reg_width { - self.state.read_data_1 = self.registers.fpr[reg1] as u32 as u64; - self.state.read_data_2 = self.registers.fpr[reg2] as u32 as u64; - } } // ======================= Execute (EX) ======================= @@ -411,9 +432,9 @@ impl RiscFpCoprocessor { } self.state.destination = match self.signals.fpu_reg_dst { - FpuRegDst::Reg1 => self.state.ft as usize, - FpuRegDst::Reg2 => self.state.fs as usize, - FpuRegDst::Reg3 => self.state.fd as usize, + 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 { diff --git a/src/emulation_core/riscv/datapath.rs b/src/emulation_core/riscv/datapath.rs index b69860bc5..f5d16091c 100644 --- a/src/emulation_core/riscv/datapath.rs +++ b/src/emulation_core/riscv/datapath.rs @@ -50,8 +50,10 @@ use super::super::datapath::Datapath; use super::constants::*; use super::control_signals::*; +use super::control_signals::floating_point::*; use super::datapath_signals::*; use super::instruction::*; +use super::coprocessor::RiscFpCoprocessor; use super::{super::mips::memory::Memory, registers::RiscGpRegisters}; use crate::emulation_core::architectures::DatapathRef; use crate::emulation_core::datapath::{DatapathUpdateSignal, Syscall}; @@ -63,6 +65,7 @@ use serde::{Deserialize, Serialize}; pub struct RiscDatapath { pub registers: RiscGpRegisters, pub memory: Memory, + pub coprocessor: RiscFpCoprocessor, pub instruction: Instruction, pub signals: ControlSignals, @@ -87,8 +90,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, @@ -197,6 +202,7 @@ impl Default for RiscDatapath { let mut datapath = RiscDatapath { registers: RiscGpRegisters::default(), memory: Memory::default(), + coprocessor: RiscFpCoprocessor::default(), instruction: Instruction::default(), signals: ControlSignals::default(), datapath_signals: DatapathSignals::default(), @@ -260,6 +266,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 } @@ -289,8 +300,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 { @@ -339,10 +351,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() } } @@ -359,6 +373,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, @@ -368,6 +386,7 @@ impl RiscDatapath { // Instruction decode always involves a state update DatapathUpdateSignal { changed_state: true, + changed_coprocessor_state: true, hit_syscall, hit_breakpoint, ..Default::default() @@ -381,9 +400,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() } } @@ -407,6 +428,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(); @@ -418,6 +441,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, ..Default::default() } } @@ -428,11 +452,17 @@ 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(); 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, ..Default::default() } } @@ -506,7 +536,14 @@ impl RiscDatapath { self.state.imm = j.imm; self.state.rd = j.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; + } } } @@ -659,6 +696,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; @@ -955,7 +1006,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. @@ -991,6 +1045,10 @@ impl RiscDatapath { if let CpuBranch::YesBranch = self.datapath_signals.cpu_branch { self.datapath_signals.general_branch = GeneralBranch::YesBranch; } + + if let FpuTakeBranch::YesBranch = self.coprocessor.signals.fpu_take_branch { + self.datapath_signals.general_branch = GeneralBranch::YesBranch; + } } fn pick_pc_plus_4_or_relative_branch_addr_mux1(&mut self) { @@ -1022,7 +1080,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 { From 7dbadebb4b1582ce19dde07cac8f8aa29730662b Mon Sep 17 00:00:00 2001 From: rharding8 Date: Wed, 27 Mar 2024 20:37:06 -0400 Subject: [PATCH 04/22] Implemented FSW all the way. Still errors. --- src/emulation_core/riscv/coprocessor.rs | 11 ++++++++--- src/emulation_core/riscv/datapath.rs | 4 ++++ src/emulation_core/riscv/instruction.rs | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index 777990a03..4edd22d35 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -215,7 +215,12 @@ impl RiscFpCoprocessor { } } Instruction::SType(s) => { - self.set_stype_control_signals(s); + self.signals = FpuControlSignals { + data_write: DataWrite::NoWrite, + fpu_branch: FpuBranch::NoBranch, + fpu_reg_write: FpuRegWrite::NoWrite, + ..Default::default() + } } Instruction::BType(b) => { self.set_btype_control_signals(b); @@ -363,10 +368,10 @@ impl RiscFpCoprocessor { } } - /// Set the data line that goes from `Read Data 2` to the multiplexer in the main processor + /// Set the data line that goes from `Read Data 1` 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; + self.state.fp_register_to_memory = self.state.read_data_1; } // ======================= Memory (MEM) ======================= diff --git a/src/emulation_core/riscv/datapath.rs b/src/emulation_core/riscv/datapath.rs index f5d16091c..af8c40583 100644 --- a/src/emulation_core/riscv/datapath.rs +++ b/src/emulation_core/riscv/datapath.rs @@ -756,6 +756,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, diff --git a/src/emulation_core/riscv/instruction.rs b/src/emulation_core/riscv/instruction.rs index 57bd1ede8..e53e947b0 100644 --- a/src/emulation_core/riscv/instruction.rs +++ b/src/emulation_core/riscv/instruction.rs @@ -132,7 +132,7 @@ impl TryFrom for Instruction { } // 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, From f5c369a24d78ea9616d551c407af8f7524254783 Mon Sep 17 00:00:00 2001 From: rharding8 Date: Wed, 27 Mar 2024 21:35:00 -0400 Subject: [PATCH 05/22] Added logic for Floating Point Addition, Subtraction, Multiplication, and Division. Fixed errors. SWIM_v2 once again compiles and works. FPU support still rudimentary, will continue to add instructions until the end of Thursday. --- src/emulation_core/riscv/constants.rs | 3 +- src/emulation_core/riscv/control_signals.rs | 12 ++ src/emulation_core/riscv/coprocessor.rs | 143 +++++++------------- src/emulation_core/riscv/datapath.rs | 8 +- src/emulation_core/riscv/instruction.rs | 2 +- 5 files changed, 73 insertions(+), 95 deletions(-) diff --git a/src/emulation_core/riscv/constants.rs b/src/emulation_core/riscv/constants.rs index bdf49f599..7f3647ab5 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; @@ -83,7 +84,7 @@ pub const OPCODE_NMSUB: u8 = 0b1001011; pub const OPCODE_NMADD: u8 = 0b1001111; /// Not a Number -pub const NAN: u8 = 0x7fc00000; +pub const 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 diff --git a/src/emulation_core/riscv/control_signals.rs b/src/emulation_core/riscv/control_signals.rs index 5181aafbb..77abfdd4c 100644 --- a/src/emulation_core/riscv/control_signals.rs +++ b/src/emulation_core/riscv/control_signals.rs @@ -203,11 +203,13 @@ pub enum RegWriteEn { } 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, @@ -332,6 +334,16 @@ pub mod floating_point { } } } + #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] + pub enum RoundingMode { + RNE = 0, + RTZ = 1, + RDN = 2, + RUP = 3, + RMM = 4, + #[default] + DRM = 7, + } /// Determines if the floating-point unit should consider branching, based on the /// contents of the condition code register. diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index 4edd22d35..a711a6dcf 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -1,9 +1,9 @@ //! Implementation of a RISC-V floating-point coprocessor. -use super::constants::*; +// use super::constants::*; use super::control_signals::floating_point::*; -use super::registers::FpRegisters; use super::instruction::Instruction; +use super::registers::FpRegisters; use serde::{Deserialize, Serialize}; /// An implementation of a floating-point coprocessor for the RISC-V ISA. @@ -202,9 +202,34 @@ impl RiscFpCoprocessor { fn set_control_signals(&mut self) { match self.instruction { Instruction::RType(r) => { - self.set_rtype_control_signals(r); + self.signals = FpuControlSignals { + data_write: DataWrite::NoWrite, + fpu_branch: FpuBranch::NoBranch, + fpu_mem_to_reg: FpuMemToReg::UseDataWrite, + fpu_reg_dst: FpuRegDst::Reg3, + fpu_reg_write: FpuRegWrite::YesWrite, + ..Default::default() + }; + + match r.funct7 >> 2 { + 0 => self.signals.fpu_alu_op = FpuAluOp::Addition, + 4 => self.signals.fpu_alu_op = FpuAluOp::Subtraction, + 8 => self.signals.fpu_alu_op = FpuAluOp::MultiplicationOrEqual, + 12 => self.signals.fpu_alu_op = FpuAluOp::Division, + _ => 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) => { + Instruction::IType(_i) => { self.signals = FpuControlSignals { data_write: DataWrite::NoWrite, fpu_branch: FpuBranch::NoBranch, @@ -214,7 +239,7 @@ impl RiscFpCoprocessor { ..Default::default() } } - Instruction::SType(s) => { + Instruction::SType(_s) => { self.signals = FpuControlSignals { data_write: DataWrite::NoWrite, fpu_branch: FpuBranch::NoBranch, @@ -222,15 +247,6 @@ impl RiscFpCoprocessor { ..Default::default() } } - Instruction::BType(b) => { - self.set_btype_control_signals(b); - } - Instruction::UType(u) => { - self.set_utype_control_signals(u); - } - Instruction::JType(j) => { - self.set_jtype_control_signals(j); - } _ => self.error("Unsupported Instruction!"), } } @@ -250,50 +266,21 @@ impl RiscFpCoprocessor { fn alu(&mut self) { let input1 = self.state.read_data_1; let input2 = self.state.read_data_2; + let input1_f64 = f64::from_bits(input1); + let input2_f64 = f64::from_bits(input2); - let mut input1_f32 = 0f32; - let mut input2_f32 = 0f32; - let mut input1_f64 = 0f64; - let mut input2_f64 = 0f64; - - // Truncate the inputs if 32-bit operations are expected. - if let FpuRegWidth::Word = self.signals.fpu_reg_width { - input1_f32 = f32::from_bits(input1 as u32); - input2_f32 = f32::from_bits(input2 as u32); - } else { - input1_f64 = f64::from_bits(input1); - input2_f64 = f64::from_bits(input2); - } - + // REMINDER: IMPLEMENT ROUNDING MODE!!! self.state.alu_result = match self.signals.fpu_alu_op { - FpuAluOp::Addition => match self.signals.fpu_reg_width { - FpuRegWidth::Word => f32::to_bits(input1_f32 + input2_f32) as u64, - FpuRegWidth::DoubleWord => f64::to_bits(input1_f64 + input2_f64), - }, - FpuAluOp::Subtraction => match self.signals.fpu_reg_width { - FpuRegWidth::Word => f32::to_bits(input1_f32 - input2_f32) as u64, - FpuRegWidth::DoubleWord => f64::to_bits(input1_f64 - input2_f64), - }, - FpuAluOp::MultiplicationOrEqual => match self.signals.fpu_reg_width { - FpuRegWidth::Word => f32::to_bits(input1_f32 * input2_f32) as u64, - FpuRegWidth::DoubleWord => f64::to_bits(input1_f64 * input2_f64), - }, - FpuAluOp::Division => match self.signals.fpu_reg_width { - FpuRegWidth::Word => { - if input2_f32 == 0f32 { - f32::to_bits(0f32) as u64 - } else { - f32::to_bits(input1_f32 / input2_f32) as u64 - } - } - FpuRegWidth::DoubleWord => { - if input2_f64 == 0.0 { - f64::to_bits(0.0) - } else { - f64::to_bits(input1_f64 / input2_f64) - } + FpuAluOp::Addition => f64::to_bits(input1_f64 + input2_f64), + FpuAluOp::Subtraction => f64::to_bits(input1_f64 - input2_f64), + FpuAluOp::MultiplicationOrEqual => f64::to_bits(input1_f64 * input2_f64), + FpuAluOp::Division => { + if input2_f64 == 0.0 { + f64::to_bits(0.0) + } else { + f64::to_bits(input1_f64 / input2_f64) } - }, + } // No operation. FpuAluOp::Slt | FpuAluOp::Snge | FpuAluOp::Sle | FpuAluOp::Sngt => 0, _ => { @@ -310,33 +297,15 @@ impl RiscFpCoprocessor { fn comparator(&mut self) { let input1 = self.state.read_data_1; let input2 = self.state.read_data_2; - - let input1_f32 = f32::from_bits(input1 as u32); - let input2_f32 = f32::from_bits(input2 as u32); let input1_f64 = f64::from_bits(input1); let input2_f64 = f64::from_bits(input2); self.state.comparator_result = match self.signals.fpu_alu_op { - FpuAluOp::MultiplicationOrEqual => match self.signals.fpu_reg_width { - FpuRegWidth::Word => (input1_f32 == input2_f32) as u64, - FpuRegWidth::DoubleWord => (input1_f64 == input2_f64) as u64, - }, - FpuAluOp::Slt => match self.signals.fpu_reg_width { - FpuRegWidth::Word => (input1_f32 < input2_f32) as u64, - FpuRegWidth::DoubleWord => (input1_f64 < input2_f64) as u64, - }, - FpuAluOp::Sle => match self.signals.fpu_reg_width { - FpuRegWidth::Word => (input1_f32 <= input2_f32) as u64, - FpuRegWidth::DoubleWord => (input1_f64 <= input2_f64) as u64, - }, - FpuAluOp::Sngt => match self.signals.fpu_reg_width { - FpuRegWidth::Word => !input1_f32.gt(&input2_f32) as u64, - FpuRegWidth::DoubleWord => !input1_f64.gt(&input2_f64) as u64, - }, - FpuAluOp::Snge => match self.signals.fpu_reg_width { - FpuRegWidth::Word => !input1_f32.ge(&input2_f32) as u64, - FpuRegWidth::DoubleWord => !input1_f64.ge(&input2_f64) as u64, - }, + FpuAluOp::MultiplicationOrEqual => (input1_f64 == input2_f64) as u64, + FpuAluOp::Slt => (input1_f64 < input2_f64) as u64, + FpuAluOp::Sle => (input1_f64 <= input2_f64) as u64, + FpuAluOp::Sngt => !input1_f64.gt(&input2_f64) as u64, + FpuAluOp::Snge => !input1_f64.ge(&input2_f64) as u64, FpuAluOp::Addition | FpuAluOp::Subtraction | FpuAluOp::Division => 0, // No operation _ => { self.error(&format!( @@ -363,9 +332,9 @@ impl RiscFpCoprocessor { /// Set the condition code (CC) register based on the result from the comparator. fn write_condition_code(&mut self) { - if let CcWrite::YesWrite = self.signals.cc_write { - self.condition_code = self.state.comparator_result; - } + // if let CcWrite::YesWrite = self.signals.cc_write { + self.condition_code = self.state.comparator_result; + // } } /// Set the data line that goes from `Read Data 1` to the multiplexer in the main processor @@ -377,12 +346,7 @@ impl RiscFpCoprocessor { // ======================= Memory (MEM) ======================= /// Set the data line that goes out of the condition code register file. fn set_condition_code_line(&mut self) { - // The MIPS architecture supports more than one condition code, but SWIM - // manually uses only one. This stubs the possible use of more than one - // for future development. - let selected_register_data = match self.signals.cc { - Cc::Cc0 => self.condition_code, - }; + let selected_register_data = self.condition_code; // This only considers one bit of the selected condition code register. self.state.condition_code_bit = match selected_register_data % 2 { @@ -395,10 +359,7 @@ impl RiscFpCoprocessor { /// multiplexer in the main processor controlled by the [`DataWrite`] control signal. fn set_data_writeback(&mut self) { self.state.sign_extend_data = self.data as i32 as i64 as u64; - self.state.data_writeback = match self.signals.fpu_reg_width { - FpuRegWidth::Word => self.state.sign_extend_data, - FpuRegWidth::DoubleWord => self.data, - } + self.state.data_writeback = self.data; } /// Simulate the logic between `self.state.condition_code_bit` and the FPU branch diff --git a/src/emulation_core/riscv/datapath.rs b/src/emulation_core/riscv/datapath.rs index af8c40583..3c48d75bf 100644 --- a/src/emulation_core/riscv/datapath.rs +++ b/src/emulation_core/riscv/datapath.rs @@ -49,11 +49,11 @@ use super::super::datapath::Datapath; use super::constants::*; -use super::control_signals::*; use super::control_signals::floating_point::*; +use super::control_signals::*; +use super::coprocessor::RiscFpCoprocessor; use super::datapath_signals::*; use super::instruction::*; -use super::coprocessor::RiscFpCoprocessor; use super::{super::mips::memory::Memory, registers::RiscGpRegisters}; use crate::emulation_core::architectures::DatapathRef; use crate::emulation_core::datapath::{DatapathUpdateSignal, Syscall}; @@ -618,6 +618,10 @@ impl RiscDatapath { self.datapath_signals.reg_width = RegisterWidth::HalfWidth; } + if r.op == OPCODE_OP_FP { + self.signals.reg_write_en = RegWriteEn::NoWrite; + } + match r.funct3 { 0 => match r.funct7 { 0b0000000 => self.signals.alu_op = AluOp::Addition, diff --git a/src/emulation_core/riscv/instruction.rs b/src/emulation_core/riscv/instruction.rs index e53e947b0..7cad14a46 100644 --- a/src/emulation_core/riscv/instruction.rs +++ b/src/emulation_core/riscv/instruction.rs @@ -111,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, From e1532c94bcd82fd40b2abb3760cccfcef5a28186 Mon Sep 17 00:00:00 2001 From: rharding8 Date: Thu, 28 Mar 2024 13:19:59 -0400 Subject: [PATCH 06/22] Implemented Sqrt function and fixed funct7 match statement. --- src/emulation_core/riscv/control_signals.rs | 4 ++-- src/emulation_core/riscv/coprocessor.rs | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/emulation_core/riscv/control_signals.rs b/src/emulation_core/riscv/control_signals.rs index 77abfdd4c..bad24c925 100644 --- a/src/emulation_core/riscv/control_signals.rs +++ b/src/emulation_core/riscv/control_signals.rs @@ -297,8 +297,8 @@ pub mod floating_point { Division = 3, /// `_0100` (4): - /// - ALU: Perform an "AND" operation. - And = 4, + /// - ALU: Perform a Square Root. + Sqrt = 4, /// `_0101` (5): /// - ALU: Perform an "OR" operation. diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index a711a6dcf..c609e082c 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -213,9 +213,10 @@ impl RiscFpCoprocessor { match r.funct7 >> 2 { 0 => self.signals.fpu_alu_op = FpuAluOp::Addition, - 4 => self.signals.fpu_alu_op = FpuAluOp::Subtraction, - 8 => self.signals.fpu_alu_op = FpuAluOp::MultiplicationOrEqual, - 12 => self.signals.fpu_alu_op = FpuAluOp::Division, + 1 => self.signals.fpu_alu_op = FpuAluOp::Subtraction, + 2 => self.signals.fpu_alu_op = FpuAluOp::MultiplicationOrEqual, + 3 => self.signals.fpu_alu_op = FpuAluOp::Division, + 11 => self.signals.fpu_alu_op = FpuAluOp::Sqrt, _ => self.error("Unsupported Instruction!"), } @@ -281,6 +282,7 @@ impl RiscFpCoprocessor { f64::to_bits(input1_f64 / input2_f64) } } + FpuAluOp::Sqrt => f64::to_bits(input1_f64.sqrt()), // No operation. FpuAluOp::Slt | FpuAluOp::Snge | FpuAluOp::Sle | FpuAluOp::Sngt => 0, _ => { From 4d753d10b335c71ddb18b3b8238bd959b5db7472 Mon Sep 17 00:00:00 2001 From: rharding8 Date: Thu, 28 Mar 2024 15:42:50 -0400 Subject: [PATCH 07/22] Implemented FMIN and FMAX. Remaining FP Operations: * R4 Types * Conversion + Move * Comparisons * Classify * 64-Bits --- src/emulation_core/riscv/control_signals.rs | 8 ++++++-- src/emulation_core/riscv/coprocessor.rs | 21 ++++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/emulation_core/riscv/control_signals.rs b/src/emulation_core/riscv/control_signals.rs index bad24c925..70a01c198 100644 --- a/src/emulation_core/riscv/control_signals.rs +++ b/src/emulation_core/riscv/control_signals.rs @@ -301,8 +301,12 @@ pub mod floating_point { Sqrt = 4, /// `_0101` (5): - /// - ALU: Perform an "OR" operation. - Or = 5, + /// - ALU: Take the Minimum value. + Min = 5, + + /// `_0110` (6): + /// - ALU: Take the Maximum value. + Max = 6, /// `_1100` (12): /// - Comparator: Set if less than. diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index c609e082c..fd178bfad 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -216,6 +216,11 @@ impl RiscFpCoprocessor { 1 => self.signals.fpu_alu_op = FpuAluOp::Subtraction, 2 => self.signals.fpu_alu_op = FpuAluOp::MultiplicationOrEqual, 3 => self.signals.fpu_alu_op = FpuAluOp::Division, + 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, _ => self.error("Unsupported Instruction!"), } @@ -283,8 +288,22 @@ impl RiscFpCoprocessor { } } FpuAluOp::Sqrt => f64::to_bits(input1_f64.sqrt()), + FpuAluOp::Min => { + if input1_f64 < input2_f64 { + f64::to_bits(input1_f64) + } else { + f64::to_bits(input2_f64) + } + } + FpuAluOp::Max => { + if input1_f64 > input2_f64 { + f64::to_bits(input1_f64) + } else { + f64::to_bits(input2_f64) + } + } // No operation. - FpuAluOp::Slt | FpuAluOp::Snge | FpuAluOp::Sle | FpuAluOp::Sngt => 0, + // FpuAluOp::Slt | FpuAluOp::Snge | FpuAluOp::Sle | FpuAluOp::Sngt => 0, _ => { self.error(&format!( "Unsupported operation in FPU `{:?}`", From d436914e1cf0a7d3e6f2c6b64b76e7b1d6001fac Mon Sep 17 00:00:00 2001 From: rharding8 Date: Thu, 28 Mar 2024 17:46:37 -0400 Subject: [PATCH 08/22] Implemented Conversion from Float Registers to Integer Registers. --- src/emulation_core/riscv/constants.rs | 2 +- src/emulation_core/riscv/control_signals.rs | 8 +- src/emulation_core/riscv/coprocessor.rs | 150 ++++++++++++++++++-- src/emulation_core/riscv/datapath.rs | 5 +- 4 files changed, 144 insertions(+), 21 deletions(-) diff --git a/src/emulation_core/riscv/constants.rs b/src/emulation_core/riscv/constants.rs index 7f3647ab5..022dfd788 100644 --- a/src/emulation_core/riscv/constants.rs +++ b/src/emulation_core/riscv/constants.rs @@ -84,7 +84,7 @@ pub const OPCODE_NMSUB: u8 = 0b1001011; pub const OPCODE_NMADD: u8 = 0b1001111; /// Not a Number -pub const NAN: u32 = 0x7fc00000; +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 diff --git a/src/emulation_core/riscv/control_signals.rs b/src/emulation_core/riscv/control_signals.rs index 70a01c198..661de1674 100644 --- a/src/emulation_core/riscv/control_signals.rs +++ b/src/emulation_core/riscv/control_signals.rs @@ -227,11 +227,11 @@ pub mod floating_point { #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] pub enum DataSrc { /// Use data from the main processing unit. Specifically, the data from register - /// `rt` from a given instruction. This value can additionally be used in the cases + /// `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 `fs` + /// Use data from the floating-point unit. Specifically, the data from register `rs1` /// from a given instruction. #[default] FloatingPointUnit = 1, @@ -258,10 +258,10 @@ pub mod floating_point { /// - 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 `fs` from a given instruction. This data source + /// 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 `rt` from a given instruction. + /// in the FPU, likely from register `rs1` from a given instruction. YesWrite = 1, } diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index fd178bfad..042c9e128 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -1,9 +1,9 @@ //! Implementation of a RISC-V floating-point coprocessor. // use super::constants::*; -use super::control_signals::floating_point::*; use super::instruction::Instruction; use super::registers::FpRegisters; +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. @@ -222,6 +222,10 @@ impl RiscFpCoprocessor { _ => self.error("Unsupported Instruction!"), }, 11 => self.signals.fpu_alu_op = FpuAluOp::Sqrt, + 24 => { + self.signals.data_write = DataWrite::YesWrite; + self.signals.fpu_reg_write = FpuRegWrite::NoWrite; + } _ => self.error("Unsupported Instruction!"), } @@ -275,35 +279,65 @@ impl RiscFpCoprocessor { let input1_f64 = f64::from_bits(input1); let input2_f64 = f64::from_bits(input2); - // REMINDER: IMPLEMENT ROUNDING MODE!!! - self.state.alu_result = match self.signals.fpu_alu_op { - FpuAluOp::Addition => f64::to_bits(input1_f64 + input2_f64), - FpuAluOp::Subtraction => f64::to_bits(input1_f64 - input2_f64), - FpuAluOp::MultiplicationOrEqual => f64::to_bits(input1_f64 * input2_f64), + let result_f64: f64 = match self.signals.fpu_alu_op { + FpuAluOp::Addition => input1_f64 + input2_f64, + FpuAluOp::Subtraction => input1_f64 - input2_f64, + FpuAluOp::MultiplicationOrEqual => input1_f64 * input2_f64, FpuAluOp::Division => { if input2_f64 == 0.0 { - f64::to_bits(0.0) + 0.0 } else { - f64::to_bits(input1_f64 / input2_f64) + input1_f64 / input2_f64 } } - FpuAluOp::Sqrt => f64::to_bits(input1_f64.sqrt()), + FpuAluOp::Sqrt => input1_f64.sqrt(), FpuAluOp::Min => { if input1_f64 < input2_f64 { - f64::to_bits(input1_f64) + input1_f64 } else { - f64::to_bits(input2_f64) + input2_f64 } } FpuAluOp::Max => { if input1_f64 > input2_f64 { - f64::to_bits(input1_f64) + input1_f64 } else { - f64::to_bits(input2_f64) + input2_f64 } } // No operation. // FpuAluOp::Slt | FpuAluOp::Snge | FpuAluOp::Sle | FpuAluOp::Sngt => 0, + _ => { + self.error(&format!( + "Unsupported operation in FPU `{:?}`", + self.signals.fpu_alu_op + )); + 0.0 + } + }; + + if result_f64.is_nan() { + self.state.alu_result = RISC_NAN as u64; + return; + } + + self.state.alu_result = match self.signals.round_mode { + RoundingMode::RNE => f64::to_bits( + if (result_f64.ceil() - result_f64).abs() == (result_f64 - result_f64.floor()).abs() + { + if result_f64.ceil() % 2.0 == 0.0 { + result_f64.ceil() + } else { + result_f64.floor() + } + } else { + result_f64.round() + }, + ), + RoundingMode::RTZ => f64::to_bits(result_f64.trunc()), + RoundingMode::RDN => f64::to_bits(result_f64.floor()), + RoundingMode::RUP => f64::to_bits(result_f64.ceil()), + RoundingMode::RMM => f64::to_bits(result_f64.round()), _ => { self.error(&format!( "Unsupported operation in FPU `{:?}`", @@ -379,8 +413,94 @@ impl RiscFpCoprocessor { /// 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) { - self.state.sign_extend_data = self.data as i32 as i64 as u64; - self.state.data_writeback = self.data; + let data_unrounded = f64::from_bits(self.data); + + 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(), + RoundingMode::RMM => data_unrounded.round(), + _ => { + self.error(&format!( + "Unsupported Rounding Mode `{:?}`", + self.signals.round_mode + )); + 0.0 + } + }; + + self.state.data_writeback = match self.state.rs2 { + 0 => { + if (data_rounded <= (-(2_i32.pow(31))).into()) | (data_rounded == f64::NEG_INFINITY) + { + -(2_i32.pow(31)) as u64 + } else if (data_rounded >= (2_i32.pow(31) - 1).into()) + | (data_rounded == f64::INFINITY) + | (data_rounded.is_nan()) + { + (2_i32.pow(31) - 1) as u64 + } else { + data_rounded as i32 as u64 + } + } + 1 => { + if (data_rounded <= 0.0) | (data_rounded == f64::NEG_INFINITY) { + 0 + } else if (data_rounded >= (2_u32.pow(32) - 1).into()) + | (data_rounded == f64::INFINITY) + | (data_rounded.is_nan()) + { + (2_u32.pow(32) - 1) as u64 + } else { + data_rounded as u32 as u64 + } + } + 2 => { + if (data_rounded <= (-(2_i64.pow(63))) as f64) | (data_rounded == f64::NEG_INFINITY) + { + -(2_i64.pow(63)) as u64 + } else if (data_rounded >= (2_i64.pow(63) - 1) as f64) + | (data_rounded == f64::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 == f64::NEG_INFINITY) { + 0 + } else if (data_rounded >= (2_u64.pow(64) - 1) as f64) + | (data_rounded == f64::INFINITY) + | (data_rounded.is_nan()) + { + 2_u64.pow(64) - 1 + } else { + data_rounded as u64 + } + } + _ => { + self.error(&format!( + "Unsupported Register Width `{:?}`", + self.state.rs2 + )); + 0 + } + } } /// Simulate the logic between `self.state.condition_code_bit` and the FPU branch diff --git a/src/emulation_core/riscv/datapath.rs b/src/emulation_core/riscv/datapath.rs index 3c48d75bf..6214721b7 100644 --- a/src/emulation_core/riscv/datapath.rs +++ b/src/emulation_core/riscv/datapath.rs @@ -619,7 +619,10 @@ impl RiscDatapath { } if r.op == OPCODE_OP_FP { - self.signals.reg_write_en = RegWriteEn::NoWrite; + match r.funct7 >> 2 { + 24 => self.signals.reg_write_en = RegWriteEn::YesWrite, + _ => self.signals.reg_write_en = RegWriteEn::NoWrite, + } } match r.funct3 { From bfac4a20cb8ddcfe6fa0afff40d1165ee33bcf83 Mon Sep 17 00:00:00 2001 From: rharding8 Date: Thu, 28 Mar 2024 18:02:23 -0400 Subject: [PATCH 09/22] Implemented conversion from Integer Registers to FP Registers. --- src/emulation_core/riscv/coprocessor.rs | 186 +++++++++++++----------- 1 file changed, 103 insertions(+), 83 deletions(-) diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index 042c9e128..0a0194af1 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -226,6 +226,10 @@ impl RiscFpCoprocessor { 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; + } _ => self.error("Unsupported Instruction!"), } @@ -413,93 +417,109 @@ impl RiscFpCoprocessor { /// 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) { - let data_unrounded = f64::from_bits(self.data); - - 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(), - RoundingMode::RMM => data_unrounded.round(), - _ => { - self.error(&format!( - "Unsupported Rounding Mode `{:?}`", - self.signals.round_mode - )); - 0.0 - } - }; - - self.state.data_writeback = match self.state.rs2 { - 0 => { - if (data_rounded <= (-(2_i32.pow(31))).into()) | (data_rounded == f64::NEG_INFINITY) - { - -(2_i32.pow(31)) as u64 - } else if (data_rounded >= (2_i32.pow(31) - 1).into()) - | (data_rounded == f64::INFINITY) - | (data_rounded.is_nan()) - { - (2_i32.pow(31) - 1) as u64 - } else { - data_rounded as i32 as u64 - } - } - 1 => { - if (data_rounded <= 0.0) | (data_rounded == f64::NEG_INFINITY) { + self.state.data_writeback = match self.signals.data_src { + DataSrc::MainProcessorUnit => match self.state.rs2 { + 0 => f64::to_bits(self.data as i32 as f64), + 1 => f64::to_bits(self.data as u32 as f64), + 2 => f64::to_bits(self.data as i64 as f64), + 3 => f64::to_bits(self.data as f64), + _ => { + self.error(&format!( + "Unsupported Register Width `{:?}`", + self.state.rs2 + )); 0 - } else if (data_rounded >= (2_u32.pow(32) - 1).into()) - | (data_rounded == f64::INFINITY) - | (data_rounded.is_nan()) - { - (2_u32.pow(32) - 1) as u64 - } else { - data_rounded as u32 as u64 - } - } - 2 => { - if (data_rounded <= (-(2_i64.pow(63))) as f64) | (data_rounded == f64::NEG_INFINITY) - { - -(2_i64.pow(63)) as u64 - } else if (data_rounded >= (2_i64.pow(63) - 1) as f64) - | (data_rounded == f64::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 == f64::NEG_INFINITY) { - 0 - } else if (data_rounded >= (2_u64.pow(64) - 1) as f64) - | (data_rounded == f64::INFINITY) - | (data_rounded.is_nan()) - { - 2_u64.pow(64) - 1 - } else { - data_rounded as u64 + DataSrc::FloatingPointUnit => { + let data_unrounded = f64::from_bits(self.data); + 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(), + RoundingMode::RMM => data_unrounded.round(), + _ => { + self.error(&format!( + "Unsupported Rounding Mode `{:?}`", + self.signals.round_mode + )); + 0.0 + } + }; + + match self.state.rs2 { + 0 => { + if (data_rounded <= (-(2_i32.pow(31))).into()) | (data_rounded == f64::NEG_INFINITY) + { + -(2_i32.pow(31)) as u64 + } else if (data_rounded >= (2_i32.pow(31) - 1).into()) + | (data_rounded == f64::INFINITY) + | (data_rounded.is_nan()) + { + (2_i32.pow(31) - 1) as u64 + } else { + data_rounded as i32 as u64 + } + } + 1 => { + if (data_rounded <= 0.0) | (data_rounded == f64::NEG_INFINITY) { + 0 + } else if (data_rounded >= (2_u32.pow(32) - 1).into()) + | (data_rounded == f64::INFINITY) + | (data_rounded.is_nan()) + { + (2_u32.pow(32) - 1) as u64 + } else { + data_rounded as u32 as u64 + } + } + 2 => { + if (data_rounded <= (-(2_i64.pow(63))) as f64) | (data_rounded == f64::NEG_INFINITY) + { + -(2_i64.pow(63)) as u64 + } else if (data_rounded >= (2_i64.pow(63) - 1) as f64) + | (data_rounded == f64::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 == f64::NEG_INFINITY) { + 0 + } else if (data_rounded >= (2_u64.pow(64) - 1) as f64) + | (data_rounded == f64::INFINITY) + | (data_rounded.is_nan()) + { + 2_u64.pow(64) - 1 + } else { + data_rounded as u64 + } + } + _ => { + self.error(&format!( + "Unsupported Register Width `{:?}`", + self.state.rs2 + )); + 0 + } } } - _ => { - self.error(&format!( - "Unsupported Register Width `{:?}`", - self.state.rs2 - )); - 0 - } } } @@ -546,7 +566,7 @@ impl RiscFpCoprocessor { self.state.register_write_mux_to_mux = match self.signals.data_write { DataWrite::NoWrite => self.state.alu_result, - DataWrite::YesWrite => self.data, + 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, From 4de6fa935baa39bc8616ad1061b1df426d28958e Mon Sep 17 00:00:00 2001 From: rharding8 Date: Thu, 28 Mar 2024 18:03:13 -0400 Subject: [PATCH 10/22] Fixed formatting. --- src/emulation_core/riscv/coprocessor.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index 0a0194af1..0f416fbc3 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -430,7 +430,7 @@ impl RiscFpCoprocessor { )); 0 } - } + }, DataSrc::FloatingPointUnit => { let data_unrounded = f64::from_bits(self.data); let data_rounded = match self.signals.round_mode { @@ -459,10 +459,11 @@ impl RiscFpCoprocessor { 0.0 } }; - + match self.state.rs2 { 0 => { - if (data_rounded <= (-(2_i32.pow(31))).into()) | (data_rounded == f64::NEG_INFINITY) + if (data_rounded <= (-(2_i32.pow(31))).into()) + | (data_rounded == f64::NEG_INFINITY) { -(2_i32.pow(31)) as u64 } else if (data_rounded >= (2_i32.pow(31) - 1).into()) @@ -487,7 +488,8 @@ impl RiscFpCoprocessor { } } 2 => { - if (data_rounded <= (-(2_i64.pow(63))) as f64) | (data_rounded == f64::NEG_INFINITY) + if (data_rounded <= (-(2_i64.pow(63))) as f64) + | (data_rounded == f64::NEG_INFINITY) { -(2_i64.pow(63)) as u64 } else if (data_rounded >= (2_i64.pow(63) - 1) as f64) From 7356d31dffba7373e01a58a1612d5b0b94f120e9 Mon Sep 17 00:00:00 2001 From: rharding8 Date: Thu, 28 Mar 2024 18:29:23 -0400 Subject: [PATCH 11/22] Implemented Sign-Injection functions. --- src/emulation_core/riscv/control_signals.rs | 12 ++++++++++++ src/emulation_core/riscv/coprocessor.rs | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/emulation_core/riscv/control_signals.rs b/src/emulation_core/riscv/control_signals.rs index 661de1674..f7d574dd2 100644 --- a/src/emulation_core/riscv/control_signals.rs +++ b/src/emulation_core/riscv/control_signals.rs @@ -308,6 +308,18 @@ pub mod floating_point { /// - ALU: Take the Maximum value. Max = 6, + /// `_0111` (7): + /// - ALU: Sign-Injection. + SGNJ = 7, + + /// `_1000` (8): + /// - ALU: Negative Sign-Injection. + SGNJN = 8, + + /// `_1001` (9): + /// - ALU: Xor Sign-Injection. + SGNJX = 9, + /// `_1100` (12): /// - Comparator: Set if less than. Slt = 12, diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index 0f416fbc3..eed32f204 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -216,6 +216,12 @@ impl RiscFpCoprocessor { 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, @@ -309,6 +315,11 @@ impl RiscFpCoprocessor { input2_f64 } } + FpuAluOp::SGNJ => f64::from_bits((input1 & 0x01111111) | (input2 >> 63 << 63)), + FpuAluOp::SGNJN => f64::from_bits((input1 & 0x01111111) | (!(input2 >> 63) << 63)), + FpuAluOp::SGNJX => { + f64::from_bits((input1 & 0x01111111) | (((input2 >> 63) ^ (input1 >> 63)) << 63)) + } // No operation. // FpuAluOp::Slt | FpuAluOp::Snge | FpuAluOp::Sle | FpuAluOp::Sngt => 0, _ => { @@ -325,6 +336,14 @@ impl RiscFpCoprocessor { 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 = f64::to_bits(result_f64); + return; + } + self.state.alu_result = match self.signals.round_mode { RoundingMode::RNE => f64::to_bits( if (result_f64.ceil() - result_f64).abs() == (result_f64 - result_f64.floor()).abs() From e578a955acc1ba2c5ff6016d17ef28d45c5b758a Mon Sep 17 00:00:00 2001 From: rharding8 Date: Thu, 28 Mar 2024 18:53:54 -0400 Subject: [PATCH 12/22] Implemented FCMP instructions. --- src/emulation_core/riscv/control_signals.rs | 2 +- src/emulation_core/riscv/coprocessor.rs | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/emulation_core/riscv/control_signals.rs b/src/emulation_core/riscv/control_signals.rs index f7d574dd2..28eec2736 100644 --- a/src/emulation_core/riscv/control_signals.rs +++ b/src/emulation_core/riscv/control_signals.rs @@ -333,7 +333,7 @@ pub mod floating_point { Sle = 14, /// `_1111` (15): - /// - Comparator: Set if not greater than. + /// - Comparator: Set if equal. Sngt = 15, } diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index eed32f204..5c39fed3e 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -228,6 +228,16 @@ impl RiscFpCoprocessor { _ => 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; + 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; @@ -321,7 +331,7 @@ impl RiscFpCoprocessor { f64::from_bits((input1 & 0x01111111) | (((input2 >> 63) ^ (input1 >> 63)) << 63)) } // No operation. - // FpuAluOp::Slt | FpuAluOp::Snge | FpuAluOp::Sle | FpuAluOp::Sngt => 0, + FpuAluOp::Slt | FpuAluOp::Snge | FpuAluOp::Sle | FpuAluOp::Sngt => 0.0, _ => { self.error(&format!( "Unsupported operation in FPU `{:?}`", @@ -436,6 +446,11 @@ impl RiscFpCoprocessor { /// 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 self.state.funct7 >> 2 == 20 { + self.state.data_writeback = self.state.comparator_result; + return; + } + self.state.data_writeback = match self.signals.data_src { DataSrc::MainProcessorUnit => match self.state.rs2 { 0 => f64::to_bits(self.data as i32 as f64), From 53af4bf6656b5303189935563bf1e780a3e462fa Mon Sep 17 00:00:00 2001 From: rharding8 Date: Thu, 28 Mar 2024 19:32:54 -0400 Subject: [PATCH 13/22] Added FCLASS. --- src/emulation_core/riscv/control_signals.rs | 12 +++- src/emulation_core/riscv/coprocessor.rs | 62 ++++++++++++++++++--- src/emulation_core/riscv/datapath.rs | 2 + 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/src/emulation_core/riscv/control_signals.rs b/src/emulation_core/riscv/control_signals.rs index 28eec2736..90757f688 100644 --- a/src/emulation_core/riscv/control_signals.rs +++ b/src/emulation_core/riscv/control_signals.rs @@ -234,7 +234,13 @@ pub mod floating_point { /// Use data from the floating-point unit. Specifically, the data from register `rs1` /// from a given instruction. #[default] - FloatingPointUnit = 1, + 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, } /// Determines whether to write to the `Data` register in the floating-point unit. @@ -320,6 +326,10 @@ pub mod floating_point { /// - ALU: Xor Sign-Injection. SGNJX = 9, + /// `_1010` (10): + /// - ALU: Classification Mask. + Class = 10, + /// `_1100` (12): /// - Comparator: Set if less than. Slt = 12, diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index 5c39fed3e..ddc60fc17 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -231,6 +231,7 @@ impl RiscFpCoprocessor { 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, @@ -246,6 +247,12 @@ impl RiscFpCoprocessor { self.signals.data_write = DataWrite::YesWrite; self.signals.data_src = DataSrc::MainProcessorUnit; } + 28 => { + self.signals.data_write = DataWrite::YesWrite; + self.signals.data_src = DataSrc::FloatingPointUnitMask; + self.signals.fpu_reg_write = FpuRegWrite::NoWrite; + self.signals.fpu_alu_op = FpuAluOp::Class; + } _ => self.error("Unsupported Instruction!"), } @@ -298,6 +305,7 @@ impl RiscFpCoprocessor { let input2 = self.state.read_data_2; let input1_f64 = f64::from_bits(input1); let input2_f64 = f64::from_bits(input2); + let mut input_mask = 0b0000000000; let result_f64: f64 = match self.signals.fpu_alu_op { FpuAluOp::Addition => input1_f64 + input2_f64, @@ -330,6 +338,43 @@ impl RiscFpCoprocessor { FpuAluOp::SGNJX => { f64::from_bits((input1 & 0x01111111) | (((input2 >> 63) ^ (input1 >> 63)) << 63)) } + FpuAluOp::Class => { + if input1_f64.is_sign_negative() { + if input1_f64.is_infinite() { + input_mask = 0b1; + } + else if input1_f64.is_normal() { + input_mask = 0b10; + } + else if input1_f64.is_subnormal() { + input_mask = 0b100; + } + else { + input_mask = 0b1000; + } + } + else if input1_f64.is_sign_positive() { + if input1_f64.is_infinite() { + input_mask = 0b10000000; + } + else if input1_f64.is_normal() { + input_mask = 0b1000000; + } + else if input1_f64.is_subnormal() { + input_mask = 0b100000; + } + else { + input_mask = 0b10000; + } + } + else if input1_f64.is_nan() { + input_mask = 0b100000000; + } + else { + input_mask = 0b1000000000; + } + 0.0 + } // No operation. FpuAluOp::Slt | FpuAluOp::Snge | FpuAluOp::Sle | FpuAluOp::Sngt => 0.0, _ => { @@ -354,6 +399,11 @@ impl RiscFpCoprocessor { 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 => f64::to_bits( if (result_f64.ceil() - result_f64).abs() == (result_f64 - result_f64.floor()).abs() @@ -413,7 +463,9 @@ impl RiscFpCoprocessor { } self.data = match self.signals.data_src { - DataSrc::FloatingPointUnit => self.state.read_data_1, + DataSrc::FloatingPointUnitRS1 => self.state.read_data_1, + DataSrc::FloatingPointUnitComp => self.state.comparator_result, + DataSrc::FloatingPointUnitMask => self.state.alu_result, DataSrc::MainProcessorUnit => self.state.data_from_main_processor, }; } @@ -446,11 +498,6 @@ impl RiscFpCoprocessor { /// 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 self.state.funct7 >> 2 == 20 { - self.state.data_writeback = self.state.comparator_result; - return; - } - self.state.data_writeback = match self.signals.data_src { DataSrc::MainProcessorUnit => match self.state.rs2 { 0 => f64::to_bits(self.data as i32 as f64), @@ -465,7 +512,7 @@ impl RiscFpCoprocessor { 0 } }, - DataSrc::FloatingPointUnit => { + DataSrc::FloatingPointUnitRS1 => { let data_unrounded = f64::from_bits(self.data); let data_rounded = match self.signals.round_mode { RoundingMode::RNE => { @@ -556,6 +603,7 @@ impl RiscFpCoprocessor { } } } + _ => self.data, } } diff --git a/src/emulation_core/riscv/datapath.rs b/src/emulation_core/riscv/datapath.rs index 6214721b7..89b936ed2 100644 --- a/src/emulation_core/riscv/datapath.rs +++ b/src/emulation_core/riscv/datapath.rs @@ -620,7 +620,9 @@ impl RiscDatapath { 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, } } From 9d78bd11e77daa47d82a421da3dcd55533f2c3a7 Mon Sep 17 00:00:00 2001 From: rharding8 Date: Thu, 28 Mar 2024 20:02:11 -0400 Subject: [PATCH 14/22] Implemented Fused Multiplication Instructions (R4s) --- src/emulation_core/riscv/control_signals.rs | 56 ++++++++++++--------- src/emulation_core/riscv/coprocessor.rs | 44 ++++++++++++---- src/emulation_core/riscv/datapath.rs | 17 ++++++- 3 files changed, 81 insertions(+), 36 deletions(-) diff --git a/src/emulation_core/riscv/control_signals.rs b/src/emulation_core/riscv/control_signals.rs index 90757f688..d0d400c82 100644 --- a/src/emulation_core/riscv/control_signals.rs +++ b/src/emulation_core/riscv/control_signals.rs @@ -285,66 +285,74 @@ pub mod floating_point { #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub enum FpuAluOp { #[default] - /// `_0000` (0): + /// `_00000` (0): /// - ALU: Perform an addition. Addition = 0, - /// `_0001` (1): + /// `_00001` (1): /// - ALU: Perform a subtraction. Subtraction = 1, - /// `_0010` (2): + /// `_00010` (2): /// - ALU: Perform a multiplication. /// - Comparator: Set if equal. MultiplicationOrEqual = 2, - /// `_0011` (3): + /// `_00011` (3): /// - ALU: Perform a division. Division = 3, - /// `_0100` (4): + /// `_00100` (4): /// - ALU: Perform a Square Root. Sqrt = 4, - /// `_0101` (5): + /// `_00101` (5): /// - ALU: Take the Minimum value. Min = 5, - /// `_0110` (6): + /// `_00110` (6): /// - ALU: Take the Maximum value. Max = 6, - /// `_0111` (7): + /// `_00111` (7): /// - ALU: Sign-Injection. SGNJ = 7, - /// `_1000` (8): + /// `_01000` (8): /// - ALU: Negative Sign-Injection. SGNJN = 8, - /// `_1001` (9): + /// `_01001` (9): /// - ALU: Xor Sign-Injection. SGNJX = 9, - /// `_1010` (10): + /// `_01010` (10): /// - ALU: Classification Mask. Class = 10, - /// `_1100` (12): - /// - Comparator: Set if less than. - Slt = 12, + /// `_01011` (11): + /// - ALU: Fused Multiplication-Addition. + MAdd = 11, - /// `_1101` (13): - /// - Comparator: Set if not greater than or equal. - Snge = 13, + /// `_01100` (12): + /// - ALU: Fused Multiplication-Subtraction. + MSub = 12, - /// `_1110` (14): - /// - Comparator: Set if less than or equal. - Sle = 14, + /// `_01101` (13): + /// - ALU: Fused Negated Multiplication-Subtraction. + NMSub = 13, - /// `_1111` (15): - /// - Comparator: Set if equal. - Sngt = 15, + /// `_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 { @@ -353,9 +361,7 @@ pub mod floating_point { match function { FUNCTION_C_EQ => Ok(Self::MultiplicationOrEqual), FUNCTION_C_LT => Ok(Self::Slt), - FUNCTION_C_NGE => Ok(Self::Snge), FUNCTION_C_LE => Ok(Self::Sle), - FUNCTION_C_NGT => Ok(Self::Sngt), _ => Err(format!("Unsupported function code `{function}`")), } } diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index ddc60fc17..9fa374ac3 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -1,5 +1,8 @@ //! Implementation of a RISC-V floating-point coprocessor. +use std::ops::Neg; + +use super::constants::{OPCODE_MADD, OPCODE_MSUB, OPCODE_NMADD, OPCODE_NMSUB}; // use super::constants::*; use super::instruction::Instruction; use super::registers::FpRegisters; @@ -51,6 +54,7 @@ pub struct RiscFpuState { 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, @@ -284,6 +288,27 @@ impl RiscFpCoprocessor { ..Default::default() } } + Instruction::R4Type(r4) => { + self.signals = FpuControlSignals { + data_write: DataWrite::NoWrite, + fpu_branch: FpuBranch::NoBranch, + 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!"), } } @@ -293,9 +318,11 @@ impl RiscFpCoprocessor { 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) ======================= @@ -303,8 +330,10 @@ impl RiscFpCoprocessor { fn alu(&mut self) { let input1 = self.state.read_data_1; let input2 = self.state.read_data_2; + let input3 = self.state.read_data_3; let input1_f64 = f64::from_bits(input1); let input2_f64 = f64::from_bits(input2); + let input3_f64 = f64::from_bits(input3); let mut input_mask = 0b0000000000; let result_f64: f64 = match self.signals.fpu_alu_op { @@ -375,15 +404,12 @@ impl RiscFpCoprocessor { } 0.0 } + FpuAluOp::MAdd => input1_f64 * input2_f64 + input3_f64, + FpuAluOp::MSub => input1_f64 * input2_f64 - input3_f64, + FpuAluOp::NMSub => input1_f64.neg() * input2_f64 + input3_f64, + FpuAluOp::NMAdd => input1_f64.neg() * input2_f64 - input3_f64, // No operation. - FpuAluOp::Slt | FpuAluOp::Snge | FpuAluOp::Sle | FpuAluOp::Sngt => 0.0, - _ => { - self.error(&format!( - "Unsupported operation in FPU `{:?}`", - self.signals.fpu_alu_op - )); - 0.0 - } + FpuAluOp::Slt | FpuAluOp::Sle => 0.0, }; if result_f64.is_nan() { @@ -442,8 +468,6 @@ impl RiscFpCoprocessor { FpuAluOp::MultiplicationOrEqual => (input1_f64 == input2_f64) as u64, FpuAluOp::Slt => (input1_f64 < input2_f64) as u64, FpuAluOp::Sle => (input1_f64 <= input2_f64) as u64, - FpuAluOp::Sngt => !input1_f64.gt(&input2_f64) as u64, - FpuAluOp::Snge => !input1_f64.ge(&input2_f64) as u64, FpuAluOp::Addition | FpuAluOp::Subtraction | FpuAluOp::Division => 0, // No operation _ => { self.error(&format!( diff --git a/src/emulation_core/riscv/datapath.rs b/src/emulation_core/riscv/datapath.rs index 89b936ed2..8a6bac23d 100644 --- a/src/emulation_core/riscv/datapath.rs +++ b/src/emulation_core/riscv/datapath.rs @@ -597,7 +597,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); + } } } @@ -828,6 +830,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) { From 5fd4c731cd2365b0762ad04d8c67852fc3b8ed05 Mon Sep 17 00:00:00 2001 From: rharding8 Date: Thu, 28 Mar 2024 20:17:16 -0400 Subject: [PATCH 15/22] Fixed the Floating Point inputs and outputs to 32 bits, compliant with RV64F (In spite of its confusing name). --- src/emulation_core/riscv/coprocessor.rs | 144 ++++++++++++------------ 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index 9fa374ac3..eac2e52fb 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -328,75 +328,75 @@ impl RiscFpCoprocessor { // ======================= Execute (EX) ======================= /// Perform an ALU operation. fn alu(&mut self) { - let input1 = self.state.read_data_1; - let input2 = self.state.read_data_2; - let input3 = self.state.read_data_3; - let input1_f64 = f64::from_bits(input1); - let input2_f64 = f64::from_bits(input2); - let input3_f64 = f64::from_bits(input3); + 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_f64: f64 = match self.signals.fpu_alu_op { - FpuAluOp::Addition => input1_f64 + input2_f64, - FpuAluOp::Subtraction => input1_f64 - input2_f64, - FpuAluOp::MultiplicationOrEqual => input1_f64 * input2_f64, + 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_f64 == 0.0 { + if input2_f32 == 0.0 { 0.0 } else { - input1_f64 / input2_f64 + input1_f32 / input2_f32 } } - FpuAluOp::Sqrt => input1_f64.sqrt(), + FpuAluOp::Sqrt => input1_f32.sqrt(), FpuAluOp::Min => { - if input1_f64 < input2_f64 { - input1_f64 + if input1_f32 < input2_f32 { + input1_f32 } else { - input2_f64 + input2_f32 } } FpuAluOp::Max => { - if input1_f64 > input2_f64 { - input1_f64 + if input1_f32 > input2_f32 { + input1_f32 } else { - input2_f64 + input2_f32 } } - FpuAluOp::SGNJ => f64::from_bits((input1 & 0x01111111) | (input2 >> 63 << 63)), - FpuAluOp::SGNJN => f64::from_bits((input1 & 0x01111111) | (!(input2 >> 63) << 63)), + FpuAluOp::SGNJ => f32::from_bits((input1 & 0x01111111) | (input2 >> 31 << 31)), + FpuAluOp::SGNJN => f32::from_bits((input1 & 0x01111111) | (!(input2 >> 31) << 31)), FpuAluOp::SGNJX => { - f64::from_bits((input1 & 0x01111111) | (((input2 >> 63) ^ (input1 >> 63)) << 63)) + f32::from_bits((input1 & 0x01111111) | (((input2 >> 31) ^ (input1 >> 31)) << 31)) } FpuAluOp::Class => { - if input1_f64.is_sign_negative() { - if input1_f64.is_infinite() { + if input1_f32.is_sign_negative() { + if input1_f32.is_infinite() { input_mask = 0b1; } - else if input1_f64.is_normal() { + else if input1_f32.is_normal() { input_mask = 0b10; } - else if input1_f64.is_subnormal() { + else if input1_f32.is_subnormal() { input_mask = 0b100; } else { input_mask = 0b1000; } } - else if input1_f64.is_sign_positive() { - if input1_f64.is_infinite() { + else if input1_f32.is_sign_positive() { + if input1_f32.is_infinite() { input_mask = 0b10000000; } - else if input1_f64.is_normal() { + else if input1_f32.is_normal() { input_mask = 0b1000000; } - else if input1_f64.is_subnormal() { + else if input1_f32.is_subnormal() { input_mask = 0b100000; } else { input_mask = 0b10000; } } - else if input1_f64.is_nan() { + else if input1_f32.is_nan() { input_mask = 0b100000000; } else { @@ -404,15 +404,15 @@ impl RiscFpCoprocessor { } 0.0 } - FpuAluOp::MAdd => input1_f64 * input2_f64 + input3_f64, - FpuAluOp::MSub => input1_f64 * input2_f64 - input3_f64, - FpuAluOp::NMSub => input1_f64.neg() * input2_f64 + input3_f64, - FpuAluOp::NMAdd => input1_f64.neg() * input2_f64 - input3_f64, + 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_f64.is_nan() { + if result_f32.is_nan() { self.state.alu_result = RISC_NAN as u64; return; } @@ -421,7 +421,7 @@ impl RiscFpCoprocessor { | (self.signals.fpu_alu_op == FpuAluOp::SGNJN) | (self.signals.fpu_alu_op == FpuAluOp::SGNJX) { - self.state.alu_result = f64::to_bits(result_f64); + self.state.alu_result = f32::to_bits(result_f32) as u64; return; } @@ -431,22 +431,22 @@ impl RiscFpCoprocessor { } self.state.alu_result = match self.signals.round_mode { - RoundingMode::RNE => f64::to_bits( - if (result_f64.ceil() - result_f64).abs() == (result_f64 - result_f64.floor()).abs() + RoundingMode::RNE => f32::to_bits( + if (result_f32.ceil() - result_f32).abs() == (result_f32 - result_f32.floor()).abs() { - if result_f64.ceil() % 2.0 == 0.0 { - result_f64.ceil() + if result_f32.ceil() % 2.0 == 0.0 { + result_f32.ceil() } else { - result_f64.floor() + result_f32.floor() } } else { - result_f64.round() + result_f32.round() }, - ), - RoundingMode::RTZ => f64::to_bits(result_f64.trunc()), - RoundingMode::RDN => f64::to_bits(result_f64.floor()), - RoundingMode::RUP => f64::to_bits(result_f64.ceil()), - RoundingMode::RMM => f64::to_bits(result_f64.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, _ => { self.error(&format!( "Unsupported operation in FPU `{:?}`", @@ -461,13 +461,13 @@ impl RiscFpCoprocessor { fn comparator(&mut self) { let input1 = self.state.read_data_1; let input2 = self.state.read_data_2; - let input1_f64 = f64::from_bits(input1); - let input2_f64 = f64::from_bits(input2); + let input1_f32 = f32::from_bits(input1 as u32); + let input2_f32 = f32::from_bits(input2 as u32); self.state.comparator_result = match self.signals.fpu_alu_op { - FpuAluOp::MultiplicationOrEqual => (input1_f64 == input2_f64) as u64, - FpuAluOp::Slt => (input1_f64 < input2_f64) as u64, - FpuAluOp::Sle => (input1_f64 <= input2_f64) as u64, + FpuAluOp::MultiplicationOrEqual => (input1_f32 == input2_f32) as u64, + FpuAluOp::Slt => (input1_f32 < input2_f32) as u64, + FpuAluOp::Sle => (input1_f32 <= input2_f32) as u64, FpuAluOp::Addition | FpuAluOp::Subtraction | FpuAluOp::Division => 0, // No operation _ => { self.error(&format!( @@ -524,10 +524,10 @@ impl RiscFpCoprocessor { fn set_data_writeback(&mut self) { self.state.data_writeback = match self.signals.data_src { DataSrc::MainProcessorUnit => match self.state.rs2 { - 0 => f64::to_bits(self.data as i32 as f64), - 1 => f64::to_bits(self.data as u32 as f64), - 2 => f64::to_bits(self.data as i64 as f64), - 3 => f64::to_bits(self.data as f64), + 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 `{:?}`", @@ -537,7 +537,7 @@ impl RiscFpCoprocessor { } }, DataSrc::FloatingPointUnitRS1 => { - let data_unrounded = f64::from_bits(self.data); + 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() @@ -567,12 +567,12 @@ impl RiscFpCoprocessor { match self.state.rs2 { 0 => { - if (data_rounded <= (-(2_i32.pow(31))).into()) - | (data_rounded == f64::NEG_INFINITY) + if (data_rounded <= ((-(2_i32.pow(31)))) as f32) + | (data_rounded == f32::NEG_INFINITY) { -(2_i32.pow(31)) as u64 - } else if (data_rounded >= (2_i32.pow(31) - 1).into()) - | (data_rounded == f64::INFINITY) + } else if (data_rounded >= ((2_i32.pow(31) - 1)) as f32) + | (data_rounded == f32::INFINITY) | (data_rounded.is_nan()) { (2_i32.pow(31) - 1) as u64 @@ -581,10 +581,10 @@ impl RiscFpCoprocessor { } } 1 => { - if (data_rounded <= 0.0) | (data_rounded == f64::NEG_INFINITY) { + if (data_rounded <= 0.0) | (data_rounded == f32::NEG_INFINITY) { 0 - } else if (data_rounded >= (2_u32.pow(32) - 1).into()) - | (data_rounded == f64::INFINITY) + } else if (data_rounded >= ((2_u32.pow(32) - 1)) as f32) + | (data_rounded == f32::INFINITY) | (data_rounded.is_nan()) { (2_u32.pow(32) - 1) as u64 @@ -593,12 +593,12 @@ impl RiscFpCoprocessor { } } 2 => { - if (data_rounded <= (-(2_i64.pow(63))) as f64) - | (data_rounded == f64::NEG_INFINITY) + 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 f64) - | (data_rounded == f64::INFINITY) + } 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 @@ -607,10 +607,10 @@ impl RiscFpCoprocessor { } } 3 => { - if (data_rounded <= 0.0) | (data_rounded == f64::NEG_INFINITY) { + if (data_rounded <= 0.0) | (data_rounded == f32::NEG_INFINITY) { 0 - } else if (data_rounded >= (2_u64.pow(64) - 1) as f64) - | (data_rounded == f64::INFINITY) + } else if (data_rounded >= (2_u64.pow(64) - 1) as f32) + | (data_rounded == f32::INFINITY) | (data_rounded.is_nan()) { 2_u64.pow(64) - 1 From 05ea41441e40111175fa2d2581f5341cc1a474a2 Mon Sep 17 00:00:00 2001 From: rharding8 Date: Thu, 28 Mar 2024 20:38:14 -0400 Subject: [PATCH 16/22] Implemented Final Float Instructions. --- src/emulation_core/riscv/control_signals.rs | 6 +++ src/emulation_core/riscv/coprocessor.rs | 55 ++++++++++----------- src/emulation_core/riscv/datapath.rs | 2 +- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/emulation_core/riscv/control_signals.rs b/src/emulation_core/riscv/control_signals.rs index d0d400c82..33f426399 100644 --- a/src/emulation_core/riscv/control_signals.rs +++ b/src/emulation_core/riscv/control_signals.rs @@ -241,6 +241,12 @@ pub mod floating_point { /// 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. diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index eac2e52fb..3e70fc4fa 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -253,9 +253,15 @@ impl RiscFpCoprocessor { } 28 => { self.signals.data_write = DataWrite::YesWrite; - self.signals.data_src = DataSrc::FloatingPointUnitMask; self.signals.fpu_reg_write = FpuRegWrite::NoWrite; - self.signals.fpu_alu_op = FpuAluOp::Class; + 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!"), + } } _ => self.error("Unsupported Instruction!"), } @@ -371,35 +377,26 @@ impl RiscFpCoprocessor { if input1_f32.is_sign_negative() { if input1_f32.is_infinite() { input_mask = 0b1; - } - else if input1_f32.is_normal() { + } else if input1_f32.is_normal() { input_mask = 0b10; - } - else if input1_f32.is_subnormal() { + } else if input1_f32.is_subnormal() { input_mask = 0b100; - } - else { + } else { input_mask = 0b1000; } - } - else if input1_f32.is_sign_positive() { + } else if input1_f32.is_sign_positive() { if input1_f32.is_infinite() { input_mask = 0b10000000; - } - else if input1_f32.is_normal() { + } else if input1_f32.is_normal() { input_mask = 0b1000000; - } - else if input1_f32.is_subnormal() { + } else if input1_f32.is_subnormal() { input_mask = 0b100000; - } - else { + } else { input_mask = 0b10000; } - } - else if input1_f32.is_nan() { + } else if input1_f32.is_nan() { input_mask = 0b100000000; - } - else { + } else { input_mask = 0b1000000000; } 0.0 @@ -459,10 +456,10 @@ impl RiscFpCoprocessor { /// Perform a comparison. fn comparator(&mut self) { - let input1 = self.state.read_data_1; - let input2 = self.state.read_data_2; - let input1_f32 = f32::from_bits(input1 as u32); - let input2_f32 = f32::from_bits(input2 as u32); + 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, @@ -490,7 +487,8 @@ impl RiscFpCoprocessor { DataSrc::FloatingPointUnitRS1 => self.state.read_data_1, DataSrc::FloatingPointUnitComp => self.state.comparator_result, DataSrc::FloatingPointUnitMask => self.state.alu_result, - DataSrc::MainProcessorUnit => self.state.data_from_main_processor, + DataSrc::FloatingPointBits => self.state.read_data_1 as u32 as i64 as u64, + _ => self.state.data_from_main_processor, }; } @@ -567,11 +565,11 @@ impl RiscFpCoprocessor { match self.state.rs2 { 0 => { - if (data_rounded <= ((-(2_i32.pow(31)))) as f32) + if (data_rounded <= (-(2_i32.pow(31))) as f32) | (data_rounded == f32::NEG_INFINITY) { -(2_i32.pow(31)) as u64 - } else if (data_rounded >= ((2_i32.pow(31) - 1)) as f32) + } else if (data_rounded >= (2_i32.pow(31) - 1) as f32) | (data_rounded == f32::INFINITY) | (data_rounded.is_nan()) { @@ -583,7 +581,7 @@ impl RiscFpCoprocessor { 1 => { if (data_rounded <= 0.0) | (data_rounded == f32::NEG_INFINITY) { 0 - } else if (data_rounded >= ((2_u32.pow(32) - 1)) as f32) + } else if (data_rounded >= (2_u32.pow(32) - 1) as f32) | (data_rounded == f32::INFINITY) | (data_rounded.is_nan()) { @@ -627,6 +625,7 @@ impl RiscFpCoprocessor { } } } + DataSrc::MainProcessorBits => self.data as u32 as u64, _ => self.data, } } diff --git a/src/emulation_core/riscv/datapath.rs b/src/emulation_core/riscv/datapath.rs index 8a6bac23d..c740029ce 100644 --- a/src/emulation_core/riscv/datapath.rs +++ b/src/emulation_core/riscv/datapath.rs @@ -624,7 +624,7 @@ impl RiscDatapath { 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, + 28 => self.signals.reg_write_en = RegWriteEn::YesWrite, _ => self.signals.reg_write_en = RegWriteEn::NoWrite, } } From 23821d2e1dd45b50fe346cb6ba7d10f40de5d768 Mon Sep 17 00:00:00 2001 From: rharding8 Date: Thu, 28 Mar 2024 20:42:38 -0400 Subject: [PATCH 17/22] Removed pointless functions and control signals. Notable FPU branching logic which doesn't exist in RISC-V. --- src/emulation_core/riscv/control_signals.rs | 28 ----------- src/emulation_core/riscv/coprocessor.rs | 53 --------------------- src/emulation_core/riscv/datapath.rs | 4 -- 3 files changed, 85 deletions(-) diff --git a/src/emulation_core/riscv/control_signals.rs b/src/emulation_core/riscv/control_signals.rs index 33f426399..3908a5bcf 100644 --- a/src/emulation_core/riscv/control_signals.rs +++ b/src/emulation_core/riscv/control_signals.rs @@ -213,11 +213,9 @@ pub mod floating_point { pub data_src: DataSrc, pub data_write: DataWrite, pub fpu_alu_op: FpuAluOp, - pub fpu_branch: FpuBranch, pub fpu_mem_to_reg: FpuMemToReg, pub fpu_reg_dst: FpuRegDst, pub fpu_reg_write: FpuRegWrite, - pub fpu_take_branch: FpuTakeBranch, } /// Determines the source of the `Data` register in the floating-point unit. @@ -383,21 +381,6 @@ pub mod floating_point { DRM = 7, } - /// Determines if the floating-point unit should consider branching, based on the - /// contents of the condition code register. - /// - /// This directly overrides any branch decisions decided by the main processing unit. - /// The [`Branch`](super::Branch) control signal should not be set in addition to this signal. - #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] - pub enum FpuBranch { - /// Do not consider branching. - #[default] - NoBranch = 0, - - /// Consider branching. - YesBranch = 1, - } - /// Determines, given that [`FpuRegWrite`] is set, what the source of a floating-point /// register's new data will be. /// @@ -437,15 +420,4 @@ pub mod floating_point { /// Write to the floating-point register file. YesWrite = 1, } - - /// After checking the [`FpuBranch`] and condition code, this signal determines whether - /// to follow through with a branch. - /// - /// This signal is what is sent to the main processor. - #[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)] - pub enum FpuTakeBranch { - #[default] - NoBranch = 0, - YesBranch = 1, - } } diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index 3e70fc4fa..6d73cf351 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -79,15 +79,12 @@ impl RiscFpCoprocessor { pub fn stage_execute(&mut self) { self.alu(); self.comparator(); - self.write_condition_code(); self.write_fp_register_to_memory(); - self.set_condition_code_line(); } pub fn stage_memory(&mut self) { self.write_data(); self.set_data_writeback(); - self.set_fpu_branch(); } pub fn stage_writeback(&mut self) { @@ -208,7 +205,6 @@ impl RiscFpCoprocessor { Instruction::RType(r) => { self.signals = FpuControlSignals { data_write: DataWrite::NoWrite, - fpu_branch: FpuBranch::NoBranch, fpu_mem_to_reg: FpuMemToReg::UseDataWrite, fpu_reg_dst: FpuRegDst::Reg3, fpu_reg_write: FpuRegWrite::YesWrite, @@ -279,7 +275,6 @@ impl RiscFpCoprocessor { Instruction::IType(_i) => { self.signals = FpuControlSignals { data_write: DataWrite::NoWrite, - fpu_branch: FpuBranch::NoBranch, fpu_mem_to_reg: FpuMemToReg::UseMemory, fpu_reg_dst: FpuRegDst::Reg3, fpu_reg_write: FpuRegWrite::YesWrite, @@ -289,7 +284,6 @@ impl RiscFpCoprocessor { Instruction::SType(_s) => { self.signals = FpuControlSignals { data_write: DataWrite::NoWrite, - fpu_branch: FpuBranch::NoBranch, fpu_reg_write: FpuRegWrite::NoWrite, ..Default::default() } @@ -297,7 +291,6 @@ impl RiscFpCoprocessor { Instruction::R4Type(r4) => { self.signals = FpuControlSignals { data_write: DataWrite::NoWrite, - fpu_branch: FpuBranch::NoBranch, fpu_mem_to_reg: FpuMemToReg::UseDataWrite, fpu_reg_dst: FpuRegDst::Reg3, fpu_reg_write: FpuRegWrite::YesWrite, @@ -492,13 +485,6 @@ impl RiscFpCoprocessor { }; } - /// Set the condition code (CC) register based on the result from the comparator. - fn write_condition_code(&mut self) { - // if let CcWrite::YesWrite = self.signals.cc_write { - self.condition_code = self.state.comparator_result; - // } - } - /// Set the data line that goes from `Read Data 1` to the multiplexer in the main processor /// controlled by [`MemWriteSrc`](super::control_signals::MemWriteSrc). fn write_fp_register_to_memory(&mut self) { @@ -506,17 +492,6 @@ impl RiscFpCoprocessor { } // ======================= Memory (MEM) ======================= - /// Set the data line that goes out of the condition code register file. - fn set_condition_code_line(&mut self) { - let selected_register_data = self.condition_code; - - // This only considers one bit of the selected condition code register. - self.state.condition_code_bit = match selected_register_data % 2 { - 0 => 0, - _ => 1, - }; - } - /// 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) { @@ -630,34 +605,6 @@ impl RiscFpCoprocessor { } } - /// Simulate the logic between `self.state.condition_code_bit` and the FPU branch - /// AND gate. - fn set_fpu_branch(&mut self) { - // Invert the condition code. (In this case, instead of using a bitwise NOT, this - // will invert only the last digit and leave the rest as 0.) - self.state.condition_code_bit_inverted = match self.state.condition_code_bit % 2 { - 0 => 1, - _ => 0, - }; - - // Run the multiplexer. - self.state.condition_code_mux = match self.state.branch_flag { - // 0 - Use inverted condition code. - false => self.state.condition_code_bit_inverted, - // 1 - Use condition code value as-is. - true => self.state.condition_code_bit, - }; - - // Set the result of the AND gate. - self.signals.fpu_take_branch = if self.signals.fpu_branch == FpuBranch::YesBranch - && self.state.condition_code_mux == 1 - { - FpuTakeBranch::YesBranch - } else { - FpuTakeBranch::NoBranch - }; - } - // ====================== Writeback (WB) ====================== /// Write data to the floating-point register file. fn register_write(&mut self) { diff --git a/src/emulation_core/riscv/datapath.rs b/src/emulation_core/riscv/datapath.rs index c740029ce..da08d1d2d 100644 --- a/src/emulation_core/riscv/datapath.rs +++ b/src/emulation_core/riscv/datapath.rs @@ -1073,10 +1073,6 @@ impl RiscDatapath { if let CpuBranch::YesBranch = self.datapath_signals.cpu_branch { self.datapath_signals.general_branch = GeneralBranch::YesBranch; } - - if let FpuTakeBranch::YesBranch = self.coprocessor.signals.fpu_take_branch { - self.datapath_signals.general_branch = GeneralBranch::YesBranch; - } } fn pick_pc_plus_4_or_relative_branch_addr_mux1(&mut self) { From e049880712174ada70aa1b365be2caa13da02b8a Mon Sep 17 00:00:00 2001 From: rharding8 Date: Thu, 28 Mar 2024 23:10:46 -0400 Subject: [PATCH 18/22] Fixed issues. Every instruction aside from FLW is working correctly. --- src/emulation_core/riscv/coprocessor.rs | 66 +++++++++++-------------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index 6d73cf351..d1bd8ecde 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -2,8 +2,7 @@ use std::ops::Neg; -use super::constants::{OPCODE_MADD, OPCODE_MSUB, OPCODE_NMADD, OPCODE_NMSUB}; -// use super::constants::*; +use super::constants::*; use super::instruction::Instruction; use super::registers::FpRegisters; use super::{constants::RISC_NAN, control_signals::floating_point::*}; @@ -206,11 +205,16 @@ impl RiscFpCoprocessor { self.signals = FpuControlSignals { data_write: DataWrite::NoWrite, fpu_mem_to_reg: FpuMemToReg::UseDataWrite, - fpu_reg_dst: FpuRegDst::Reg3, 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, @@ -272,13 +276,17 @@ impl RiscFpCoprocessor { _ => self.error("Unsupported Rounding Mode!"), } } - Instruction::IType(_i) => { + 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) => { @@ -286,7 +294,7 @@ impl RiscFpCoprocessor { data_write: DataWrite::NoWrite, fpu_reg_write: FpuRegWrite::NoWrite, ..Default::default() - } + }; } Instruction::R4Type(r4) => { self.signals = FpuControlSignals { @@ -437,13 +445,7 @@ impl RiscFpCoprocessor { 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, - _ => { - self.error(&format!( - "Unsupported operation in FPU `{:?}`", - self.signals.fpu_alu_op - )); - 0 - } + _ => f32::to_bits(result_f32) as u64, }; } @@ -458,14 +460,7 @@ impl RiscFpCoprocessor { FpuAluOp::MultiplicationOrEqual => (input1_f32 == input2_f32) as u64, FpuAluOp::Slt => (input1_f32 < input2_f32) as u64, FpuAluOp::Sle => (input1_f32 <= input2_f32) as u64, - FpuAluOp::Addition | FpuAluOp::Subtraction | FpuAluOp::Division => 0, // No operation - _ => { - self.error(&format!( - "Unsupported operation in comparator `{:?}`", - self.signals.fpu_alu_op - )); - 0 - } + _ => 0, } } @@ -488,13 +483,17 @@ impl RiscFpCoprocessor { /// Set the data line that goes from `Read Data 1` 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_1; + self.state.fp_register_to_memory = f32::from_bits(self.state.read_data_2 as u32) as u64; } // ======================= 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, @@ -528,27 +527,20 @@ impl RiscFpCoprocessor { RoundingMode::RTZ => data_unrounded.trunc(), RoundingMode::RDN => data_unrounded.floor(), RoundingMode::RUP => data_unrounded.ceil(), - RoundingMode::RMM => data_unrounded.round(), - _ => { - self.error(&format!( - "Unsupported Rounding Mode `{:?}`", - self.signals.round_mode - )); - 0.0 - } + _ => data_unrounded.round(), }; match self.state.rs2 { 0 => { - if (data_rounded <= (-(2_i32.pow(31))) as f32) + if (data_rounded <= (-(2_i64.pow(31))) as f32) | (data_rounded == f32::NEG_INFINITY) { - -(2_i32.pow(31)) as u64 - } else if (data_rounded >= (2_i32.pow(31) - 1) as f32) + -(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_i32.pow(31) - 1) as u64 + (2_i64.pow(31) - 1) as u64 } else { data_rounded as i32 as u64 } @@ -556,11 +548,11 @@ impl RiscFpCoprocessor { 1 => { if (data_rounded <= 0.0) | (data_rounded == f32::NEG_INFINITY) { 0 - } else if (data_rounded >= (2_u32.pow(32) - 1) as f32) + } else if (data_rounded >= (2_u64.pow(32) - 1) as f32) | (data_rounded == f32::INFINITY) | (data_rounded.is_nan()) { - (2_u32.pow(32) - 1) as u64 + 2_u64.pow(32) - 1 } else { data_rounded as u32 as u64 } @@ -582,11 +574,11 @@ impl RiscFpCoprocessor { 3 => { if (data_rounded <= 0.0) | (data_rounded == f32::NEG_INFINITY) { 0 - } else if (data_rounded >= (2_u64.pow(64) - 1) as f32) + } else if (data_rounded >= (0x1111111111111111_i64) as f32) | (data_rounded == f32::INFINITY) | (data_rounded.is_nan()) { - 2_u64.pow(64) - 1 + 0x1111111111111111 } else { data_rounded as u64 } From f81a8404c4d01cfa48951361ad621481424a7a03 Mon Sep 17 00:00:00 2001 From: rharding8 Date: Thu, 28 Mar 2024 23:14:17 -0400 Subject: [PATCH 19/22] One instruction was wrong. Fixed now. --- src/emulation_core/riscv/coprocessor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index d1bd8ecde..cc12133d9 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -483,7 +483,7 @@ impl RiscFpCoprocessor { /// Set the data line that goes from `Read Data 1` 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 = f32::from_bits(self.state.read_data_2 as u32) as u64; + self.state.fp_register_to_memory = self.state.read_data_2; } // ======================= Memory (MEM) ======================= From bb6b00406ad491d3a68ce158c68078d261e33dc4 Mon Sep 17 00:00:00 2001 From: rharding8 Date: Fri, 29 Mar 2024 00:48:00 -0400 Subject: [PATCH 20/22] Fixed my idiotic programming. Everything works now --- src/emulation_core/riscv/coprocessor.rs | 2 +- src/emulation_core/riscv/instruction.rs | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index cc12133d9..9aba1f522 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -480,7 +480,7 @@ impl RiscFpCoprocessor { }; } - /// Set the data line that goes from `Read Data 1` to the multiplexer in the 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; diff --git a/src/emulation_core/riscv/instruction.rs b/src/emulation_core/riscv/instruction.rs index 7cad14a46..e1c9bc92f 100644 --- a/src/emulation_core/riscv/instruction.rs +++ b/src/emulation_core/riscv/instruction.rs @@ -121,15 +121,14 @@ 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 | OPCODE_STORE_FP => Ok(Instruction::SType(SType { From e35fa7bd26550227dede8077d955717dd17b79f6 Mon Sep 17 00:00:00 2001 From: rharding8 Date: Fri, 29 Mar 2024 01:00:01 -0400 Subject: [PATCH 21/22] Fixed final instructions. --- src/emulation_core/riscv/coprocessor.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/emulation_core/riscv/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index 9aba1f522..c20b4ffb4 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -263,6 +263,11 @@ impl RiscFpCoprocessor { _ => 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!"), } From ecf76fdd73bfc0ded7b0ef3aeca0ecbdf4a79215 Mon Sep 17 00:00:00 2001 From: Brooks McKinley <31021010+brooksmckinley@users.noreply.github.com> Date: Fri, 29 Mar 2024 01:28:05 -0400 Subject: [PATCH 22/22] Connect the FP registers to the visual display --- src/agent.rs | 5 +++++ src/agent/datapath_reducer.rs | 18 +++++++++++++++++- src/agent/messages.rs | 3 ++- src/bin/main.rs | 3 +-- src/emulation_core/riscv/coprocessor.rs | 4 ++-- src/emulation_core/riscv/registers.rs | 23 +++++++++++------------ src/ui/regview/component.rs | 4 ++-- 7 files changed, 40 insertions(+), 20 deletions(-) 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/coprocessor.rs b/src/emulation_core/riscv/coprocessor.rs index c20b4ffb4..c23ef4041 100644 --- a/src/emulation_core/riscv/coprocessor.rs +++ b/src/emulation_core/riscv/coprocessor.rs @@ -4,7 +4,7 @@ use std::ops::Neg; use super::constants::*; use super::instruction::Instruction; -use super::registers::FpRegisters; +use super::registers::RiscFpRegisters; use super::{constants::RISC_NAN, control_signals::floating_point::*}; use serde::{Deserialize, Serialize}; @@ -18,7 +18,7 @@ pub struct RiscFpCoprocessor { pub signals: FpuControlSignals, pub state: RiscFpuState, pub is_halted: bool, - pub registers: FpRegisters, + pub registers: RiscFpRegisters, pub condition_code: u64, pub data: u64, } diff --git a/src/emulation_core/riscv/registers.rs b/src/emulation_core/riscv/registers.rs index c5d763b8e..aeb28bcc7 100644 --- a/src/emulation_core/riscv/registers.rs +++ b/src/emulation_core/riscv/registers.rs @@ -260,12 +260,12 @@ impl IntoIterator for RiscGpRegisters { /// Collection of general-purpose registers used by the datapath. #[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct FpRegisters { +pub struct RiscFpRegisters { pub fpr: [u64; 32], } /// Specifies all of the valid registers accessible in an instance -/// of [`FpRegisters`]. +/// of [`RiscFpRegisters`]. #[derive(Clone, Copy, Debug, Display, EnumIter, EnumString, Eq, PartialEq)] #[strum(ascii_case_insensitive)] #[strum(serialize_all = "lowercase")] @@ -302,7 +302,6 @@ pub enum FpRegisterType { F29 = 29, F30 = 30, F31 = 31, - FCSR = 32, } impl RegisterType for FpRegisterType { @@ -314,7 +313,7 @@ impl RegisterType for FpRegisterType { } } -impl Registers for FpRegisters { +impl Registers for RiscFpRegisters { fn get_dyn_register_list(&self) -> Vec<(Rc, u64)> { self.into_iter() .map(|(register, val)| { @@ -325,7 +324,7 @@ impl Registers for FpRegisters { } } -impl ToString for FpRegisters { +impl ToString for RiscFpRegisters { fn to_string(&self) -> String { let mut output = String::new(); @@ -342,7 +341,7 @@ impl ToString for FpRegisters { } } -impl Index<&str> for FpRegisters { +impl Index<&str> for RiscFpRegisters { type Output = u64; // Convert string to the corresponding RegistersEnum value and use this to index. @@ -355,7 +354,7 @@ impl Index<&str> for FpRegisters { } } -impl IndexMut<&str> for FpRegisters { +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 { @@ -366,7 +365,7 @@ impl IndexMut<&str> for FpRegisters { } } -impl Index for FpRegisters { +impl Index for RiscFpRegisters { type Output = u64; fn index(&self, index: FpRegisterType) -> &Self::Output { @@ -374,7 +373,7 @@ impl Index for FpRegisters { } } -impl IndexMut for FpRegisters { +impl IndexMut for RiscFpRegisters { fn index_mut(&mut self, index: FpRegisterType) -> &mut Self::Output { &mut self.fpr[index as usize] } @@ -388,7 +387,7 @@ impl IndexMut for FpRegisters { /// 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: FpRegisters, + registers: RiscFpRegisters, register_iter: FpRegisterTypeIter, } @@ -408,11 +407,11 @@ impl Iterator for FpRegistersIter { /// [`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 FpRegisters { +impl IntoIterator for RiscFpRegisters { type Item = (FpRegisterType, u64); type IntoIter = FpRegistersIter; - /// Consumes the [`FpRegisters`] struct to create a new [`FpRegistersIter`] that can + /// Consumes the [`RiscFpRegisters`] struct to create a new [`FpRegistersIter`] that can /// be iterated over. fn into_iter(self) -> Self::IntoIter { FpRegistersIter { 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(),