diff --git a/src/alu.rs b/src/alu.rs index a59abd6..727fbc0 100644 --- a/src/alu.rs +++ b/src/alu.rs @@ -1,220 +1,290 @@ use std::marker::PhantomData; -use crate::isa::{Opcode, Opcode::*}; -use crate::diag::{DiagNode, Diagnosable}; +use crate::utils::diag::{DiagNode, Diagnosable}; +use crate::utils::isa::{Opcode, Opcode::*}; /* In this abstraction, the ALU is a collection of pipes, each of which can perform a given set of operations, each with a given latency. Note that kinds like `LoadStore` and `Branch` do not mean that - the performs a branch or LoadStore operation, but that it can + the performs a branch or LoadStore operation, but that it can performs associated calculations (e.g. address calc, branch predicate calc etc.) */ #[derive(PartialEq)] pub enum FuncUnitKind { - IntAlu, - IntMul, - LoadStore, - Branch, + IntAlu, + IntMul, + LoadStore, + Branch, } // Predicate matching each functional unit kind // to operations they can support. fn kind_supports(kind: &FuncUnitKind, opcode: Opcode) -> bool { - match kind { - FuncUnitKind::IntAlu => matches!(opcode, - Add | Sub | And | Or | Xor | Sll | Srl | Sra | Slt | Sltu | - Addi | Andi | Ori | Xori | Slli | Srli | Srai | Slti | Sltiu | - Lui | Auipc - ), - FuncUnitKind::IntMul => false, // no M extension yet - FuncUnitKind::LoadStore => matches!(opcode, - Lb | Lh | Lw | Lbu | Lhu | - Sb | Sh | Sw - ), - FuncUnitKind::Branch => matches!(opcode, - Beq | Bne | Blt | Bge | Bltu | Bgeu | - Jal | Jalr - ), - } + match kind { + FuncUnitKind::IntAlu => matches!( + opcode, + Add | Sub + | And + | Or + | Xor + | Sll + | Srl + | Sra + | Slt + | Sltu + | Addi + | Andi + | Ori + | Xori + | Slli + | Srli + | Srai + | Slti + | Sltiu + | Lui + | Auipc + ), + FuncUnitKind::IntMul => false, // no M extension yet + FuncUnitKind::LoadStore => matches!(opcode, Lb | Lh | Lw | Lbu | Lhu | Sb | Sh | Sw), + FuncUnitKind::Branch => matches!(opcode, Beq | Bne | Blt | Bge | Bltu | Bgeu | Jal | Jalr), + } } pub struct AluPipe { - supported_kinds: Vec, - // also need instruction latencies + supported_kinds: Vec, + // also need instruction latencies } impl AluPipe { - pub fn new(supported_kinds: Vec) -> Self { - Self { supported_kinds } - } + pub fn new(supported_kinds: Vec) -> Self { + Self { supported_kinds } + } } // Check if a pipe supports a given operation fn pipe_supports(pipe: &AluPipe, opcode: Opcode) -> bool { - for kind in &pipe.supported_kinds { - if kind_supports(kind, opcode) { - return true; + for kind in &pipe.supported_kinds { + if kind_supports(kind, opcode) { + return true; + } } - } - false + false } // Traits for communicating with ALU. // These can be used to pass the ALU metadata along with an instruction, // for benchmarking, instruction tracking etc., without any ALU noise pub struct ExecInputs { - pub pc: u32, - pub rs1: u32, - pub rs2: u32, - pub imm: i32, + pub pc: u32, + pub rs1: u32, + pub rs2: u32, + pub imm: i32, } pub trait OpaqueInstruction { - fn get_opcode(&self) -> Opcode; - fn get_inputs(&self) -> ExecInputs; + fn get_opcode(&self) -> Opcode; + fn get_inputs(&self) -> ExecInputs; } pub trait OpaqueResult { - fn from_instr_and_result(instr: I, result: u32) -> Self; + fn from_instr_and_result(instr: I, result: u32) -> Self; } - pub struct InstructionProgress { - instruction: I, // the instruction being executed - latency: u8, // number of latency to complete - age: u8, // number of latency spent in ALU - _resources_used: usize, // index of the resource used. + instruction: I, // the instruction being executed + latency: u8, // number of latency to complete + age: u8, // number of latency spent in ALU + _resources_used: usize, // index of the resource used. } pub struct ALU> { - pipes: Vec, - in_progress_instructions: Vec>, - resource_usage: Vec, // `true` if in use - _phantom: PhantomData, + pipes: Vec, + in_progress_instructions: Vec>, + resource_usage: Vec, // `true` if in use + _phantom: PhantomData, } impl> Diagnosable for ALU { - fn diagnose(&self) -> DiagNode { - let pipe_usage: String = self.resource_usage.iter() - .map(|&u| if u { 'X' } else { '.' }) - .collect(); - let mut children = vec![ - DiagNode::leaf("pipes", format!("{} ({} in-flight)", self.pipes.len(), self.in_progress_instructions.len())), - DiagNode::leaf("pipe usage", format!("[{pipe_usage}]")), - ]; - for (i, p) in self.in_progress_instructions.iter().enumerate() { - children.push(DiagNode::leaf( - format!("[{i}]"), - format!("{:?} age={}/{} pipe={}", p.instruction.get_opcode(), p.age, p.latency, p._resources_used), - )); + fn diagnose(&self) -> DiagNode { + let pipe_usage: String = self + .resource_usage + .iter() + .map(|&u| if u { 'X' } else { '.' }) + .collect(); + let mut children = vec![ + DiagNode::leaf( + "pipes", + format!( + "{} ({} in-flight)", + self.pipes.len(), + self.in_progress_instructions.len() + ), + ), + DiagNode::leaf("pipe usage", format!("[{pipe_usage}]")), + ]; + for (i, p) in self.in_progress_instructions.iter().enumerate() { + children.push(DiagNode::leaf( + format!("[{i}]"), + format!( + "{:?} age={}/{} pipe={}", + p.instruction.get_opcode(), + p.age, + p.latency, + p._resources_used + ), + )); + } + DiagNode::inner("alu", children) } - DiagNode::inner("alu", children) - } } fn compute(opcode: Opcode, i: &ExecInputs) -> u32 { - let (a, b, imm) = (i.rs1, i.rs2, i.imm as u32); - match opcode { - Add => a.wrapping_add(b), - Sub => a.wrapping_sub(b), - And => a & b, - Or => a | b, - Xor => a ^ b, - Sll => a << (b & 0x1f), - Srl => a >> (b & 0x1f), - Sra => ((a as i32) >> (b & 0x1f)) as u32, - Slt => ((a as i32) < (b as i32)) as u32, - Sltu => (a < b) as u32, - - Addi => a.wrapping_add(imm), - Andi => a & imm, - Ori => a | imm, - Xori => a ^ imm, - Slli => a << (imm & 0x1f), - Srli => a >> (imm & 0x1f), - Srai => ((a as i32) >> (imm & 0x1f)) as u32, - Slti => ((a as i32) < i.imm) as u32, - Sltiu => (a < imm) as u32, - - Lb | Lbu | Lh | Lhu | Lw => a.wrapping_add(imm), // address calc - Sb | Sh | Sw => a.wrapping_add(imm), // address calc - - Lui => imm, - Auipc => i.pc.wrapping_add(imm), - - Jal | Jalr => i.pc.wrapping_add(4), // return address - - Beq => if i.rs1 == i.rs2 { i.pc.wrapping_add(imm) } else { i.pc + 4 }, - Bne => if i.rs1 != i.rs2 { i.pc.wrapping_add(imm) } else { i.pc + 4 }, - Blt => if (i.rs1 as i32) < (i.rs2 as i32) { i.pc.wrapping_add(imm) } else { i.pc + 4 }, - Bge => if (i.rs1 as i32) >= (i.rs2 as i32) { i.pc.wrapping_add(imm) } else { i.pc + 4 }, - Bltu => if i.rs1 < i.rs2 { i.pc.wrapping_add(imm) } else { i.pc + 4 }, - Bgeu => if i.rs1 >= i.rs2 { i.pc.wrapping_add(imm) } else { i.pc + 4 }, - } + let (a, b, imm) = (i.rs1, i.rs2, i.imm as u32); + match opcode { + Add => a.wrapping_add(b), + Sub => a.wrapping_sub(b), + And => a & b, + Or => a | b, + Xor => a ^ b, + Sll => a << (b & 0x1f), + Srl => a >> (b & 0x1f), + Sra => ((a as i32) >> (b & 0x1f)) as u32, + Slt => ((a as i32) < (b as i32)) as u32, + Sltu => (a < b) as u32, + + Addi => a.wrapping_add(imm), + Andi => a & imm, + Ori => a | imm, + Xori => a ^ imm, + Slli => a << (imm & 0x1f), + Srli => a >> (imm & 0x1f), + Srai => ((a as i32) >> (imm & 0x1f)) as u32, + Slti => ((a as i32) < i.imm) as u32, + Sltiu => (a < imm) as u32, + + Lb | Lbu | Lh | Lhu | Lw => a.wrapping_add(imm), // address calc + Sb | Sh | Sw => a.wrapping_add(imm), // address calc + + Lui => imm, + Auipc => i.pc.wrapping_add(imm), + + Jal | Jalr => i.pc.wrapping_add(4), // return address + + Beq => { + if i.rs1 == i.rs2 { + i.pc.wrapping_add(imm) + } else { + i.pc + 4 + } + } + Bne => { + if i.rs1 != i.rs2 { + i.pc.wrapping_add(imm) + } else { + i.pc + 4 + } + } + Blt => { + if (i.rs1 as i32) < (i.rs2 as i32) { + i.pc.wrapping_add(imm) + } else { + i.pc + 4 + } + } + Bge => { + if (i.rs1 as i32) >= (i.rs2 as i32) { + i.pc.wrapping_add(imm) + } else { + i.pc + 4 + } + } + Bltu => { + if i.rs1 < i.rs2 { + i.pc.wrapping_add(imm) + } else { + i.pc + 4 + } + } + Bgeu => { + if i.rs1 >= i.rs2 { + i.pc.wrapping_add(imm) + } else { + i.pc + 4 + } + } + } } impl> ALU { - - pub fn new(pipes: Vec) -> Self { - let n = pipes.len(); - Self { pipes, in_progress_instructions: vec![], resource_usage: vec![false; n], _phantom: PhantomData } - } - - // TODO: implement proper resolution from latency table or similar - fn get_instr_execution_latency(&self, _opcode: Opcode) -> u8 { - 2 - } - - pub fn try_enqueue(&mut self, instr: I) -> bool { - for i in 0..self.pipes.len() { - if (!self.resource_usage[i]) && (pipe_supports(&self.pipes[i], instr.get_opcode())) { - self.resource_usage[i] = true; - self.in_progress_instructions.push(InstructionProgress { - latency: self.get_instr_execution_latency(instr.get_opcode()), - instruction: instr, - age: 0, - _resources_used: i, - }); - return true; - } + pub fn new(pipes: Vec) -> Self { + let n = pipes.len(); + Self { + pipes, + in_progress_instructions: vec![], + resource_usage: vec![false; n], + _phantom: PhantomData, + } } - false - } - - // Advance the state of the operations in the ALU - // If there is a ready operation, return it. In the - // case of multiple ready instructions, return the oldest. - pub fn tick(&mut self) -> Option { - let mut max_age: u8= 0; - let mut index: Option = None; - for i in 0..self.in_progress_instructions.len() { - let mut record = true; - let instr = &mut self.in_progress_instructions[i]; - if instr.latency < instr.age { record = false; } // Not ready yet - if instr.age <= max_age { record = false; } // Not the oldest - - instr.age += 1; - - if !record { continue; } - - max_age = instr.age; - index = Some(i); - } - - if let Some(index_to_remove) = index { - let in_progress_instruction = self.in_progress_instructions.remove(index_to_remove); - let instruction = in_progress_instruction.instruction; - self.resource_usage[in_progress_instruction._resources_used] = false; - let result = compute(instruction.get_opcode(), &instruction.get_inputs()); - return Some(O::from_instr_and_result(instruction, result)); + // TODO: implement proper resolution from latency table or similar + fn get_instr_execution_latency(&self, _opcode: Opcode) -> u8 { + 2 } - None - } + pub fn try_enqueue(&mut self, instr: I) -> bool { + for i in 0..self.pipes.len() { + if (!self.resource_usage[i]) && (pipe_supports(&self.pipes[i], instr.get_opcode())) { + self.resource_usage[i] = true; + self.in_progress_instructions.push(InstructionProgress { + latency: self.get_instr_execution_latency(instr.get_opcode()), + instruction: instr, + age: 0, + _resources_used: i, + }); + return true; + } + } + false + } -} \ No newline at end of file + // Advance the state of the operations in the ALU + // If there is a ready operation, return it. In the + // case of multiple ready instructions, return the oldest. + pub fn tick(&mut self) -> Option { + let mut max_age: u8 = 0; + let mut index: Option = None; + for i in 0..self.in_progress_instructions.len() { + let mut record = true; + let instr = &mut self.in_progress_instructions[i]; + if instr.latency < instr.age { + record = false; + } // Not ready yet + if instr.age <= max_age { + record = false; + } // Not the oldest + + instr.age += 1; + + if !record { + continue; + } + + max_age = instr.age; + index = Some(i); + } + + if let Some(index_to_remove) = index { + let in_progress_instruction = self.in_progress_instructions.remove(index_to_remove); + let instruction = in_progress_instruction.instruction; + self.resource_usage[in_progress_instruction._resources_used] = false; + let result = compute(instruction.get_opcode(), &instruction.get_inputs()); + return Some(O::from_instr_and_result(instruction, result)); + } + + None + } +} diff --git a/src/assembler.rs b/src/assembler.rs deleted file mode 100644 index cb27ed2..0000000 --- a/src/assembler.rs +++ /dev/null @@ -1,290 +0,0 @@ -use std::collections::HashMap; - -#[derive(Debug)] -pub enum AsmError { - UnknownInstruction(String), - UnknownRegister(String), - BadOperands(String), - UndefinedLabel(String), - ImmediateOutOfRange(String), -} - -impl std::fmt::Display for AsmError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AsmError::UnknownInstruction(s) => write!(f, "unknown instruction: {s}"), - AsmError::UnknownRegister(s) => write!(f, "unknown register: {s}"), - AsmError::BadOperands(s) => write!(f, "bad operands: {s}"), - AsmError::UndefinedLabel(s) => write!(f, "undefined label: {s}"), - AsmError::ImmediateOutOfRange(s) => write!(f, "immediate out of range: {s}"), - } - } -} - -pub fn assemble(source: &str) -> Result, AsmError> { - // Two passes: first collect labels, then emit. - let lines = clean(source); - let labels = collect_labels(&lines)?; - emit(&lines, &labels) -} - -// ── Pass 1: strip comments, blank lines, record label positions ─────────────── - -fn clean(source: &str) -> Vec<(usize, String)> { - let mut out = Vec::new(); - let mut addr = 0usize; - for raw in source.lines() { - let line = raw.split('#').next().unwrap().trim().to_string(); - if line.is_empty() { continue; } - if line.ends_with(':') { out.push((addr, line)); continue; } - out.push((addr, line)); - addr += 4; - } - out -} - -fn collect_labels(lines: &[(usize, String)]) -> Result, AsmError> { - let mut labels = HashMap::new(); - for (addr, line) in lines { - if let Some(label) = line.strip_suffix(':') { - labels.insert(label.trim().to_string(), *addr as u32); - } - } - Ok(labels) -} - -// ── Pass 2: emit instruction words ─────────────────────────────────────────── - -fn emit(lines: &[(usize, String)], labels: &HashMap) -> Result, AsmError> { - let mut out = Vec::new(); - for (addr, line) in lines { - if line.ends_with(':') { continue; } - let word = assemble_line(line, *addr as u32, labels)?; - out.push(word); - } - Ok(out) -} - -fn assemble_line(line: &str, pc: u32, labels: &HashMap) -> Result { - let (mnemonic, rest) = line.split_once(char::is_whitespace) - .map(|(m, r)| (m, r.trim())) - .unwrap_or((line, "")); - - // Expand pseudo-instructions first - match mnemonic { - "nop" => return Ok(0x00000013), // addi x0, x0, 0 - "mv" => { let (rd, rs) = reg2(rest)?; return Ok(i_type(rd, rs, 0, 0x0, 0b0010011)); } - "li" => { let (rd, imm) = reg_imm(rest, pc, labels)?; return Ok(i_type(rd, 0, imm, 0x0, 0b0010011)); } - "j" => { let off = resolve(rest, pc, labels)?; return Ok(j_type(0, off)); } - "jr" => { let rs = reg(rest)?; return Ok(i_type(0, rs, 0, 0x0, 0b1100111)); } - "ret" => return Ok(i_type(0, 1, 0, 0x0, 0b1100111)), // jalr x0, x1, 0 - "not" => { let (rd, rs) = reg2(rest)?; return Ok(i_type(rd, rs, -1, 0x4, 0b0010011)); } // xori rd, rs, -1 - "neg" => { let (rd, rs) = reg2(rest)?; return Ok(r_type(rd, 0, rs, 0x0, 0x20, 0b0110011)); } // sub rd, x0, rs - _ => {} - } - - match mnemonic { - // R-type - "add" => { let (rd, rs1, rs2) = reg3(rest)?; Ok(r_type(rd, rs1, rs2, 0x0, 0x00, 0b0110011)) } - "sub" => { let (rd, rs1, rs2) = reg3(rest)?; Ok(r_type(rd, rs1, rs2, 0x0, 0x20, 0b0110011)) } - "and" => { let (rd, rs1, rs2) = reg3(rest)?; Ok(r_type(rd, rs1, rs2, 0x7, 0x00, 0b0110011)) } - "or" => { let (rd, rs1, rs2) = reg3(rest)?; Ok(r_type(rd, rs1, rs2, 0x6, 0x00, 0b0110011)) } - "xor" => { let (rd, rs1, rs2) = reg3(rest)?; Ok(r_type(rd, rs1, rs2, 0x4, 0x00, 0b0110011)) } - "sll" => { let (rd, rs1, rs2) = reg3(rest)?; Ok(r_type(rd, rs1, rs2, 0x1, 0x00, 0b0110011)) } - "srl" => { let (rd, rs1, rs2) = reg3(rest)?; Ok(r_type(rd, rs1, rs2, 0x5, 0x00, 0b0110011)) } - "sra" => { let (rd, rs1, rs2) = reg3(rest)?; Ok(r_type(rd, rs1, rs2, 0x5, 0x20, 0b0110011)) } - "slt" => { let (rd, rs1, rs2) = reg3(rest)?; Ok(r_type(rd, rs1, rs2, 0x2, 0x00, 0b0110011)) } - "sltu" => { let (rd, rs1, rs2) = reg3(rest)?; Ok(r_type(rd, rs1, rs2, 0x3, 0x00, 0b0110011)) } - - // I-type ALU - "addi" => { let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; Ok(i_type(rd, rs1, imm, 0x0, 0b0010011)) } - "andi" => { let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; Ok(i_type(rd, rs1, imm, 0x7, 0b0010011)) } - "ori" => { let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; Ok(i_type(rd, rs1, imm, 0x6, 0b0010011)) } - "xori" => { let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; Ok(i_type(rd, rs1, imm, 0x4, 0b0010011)) } - "slli" => { let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; Ok(i_type(rd, rs1, imm, 0x1, 0b0010011)) } - "srli" => { let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; Ok(i_type(rd, rs1, imm, 0x5, 0b0010011)) } - "srai" => { let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; Ok(i_type(rd, rs1, imm | (0x20 << 5), 0x5, 0b0010011)) } - "slti" => { let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; Ok(i_type(rd, rs1, imm, 0x2, 0b0010011)) } - "sltiu" => { let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; Ok(i_type(rd, rs1, imm, 0x3, 0b0010011)) } - - // Loads - "lb" => { let (rd, rs1, imm) = reg_mem(rest)?; Ok(i_type(rd, rs1, imm, 0x0, 0b0000011)) } - "lh" => { let (rd, rs1, imm) = reg_mem(rest)?; Ok(i_type(rd, rs1, imm, 0x1, 0b0000011)) } - "lw" => { let (rd, rs1, imm) = reg_mem(rest)?; Ok(i_type(rd, rs1, imm, 0x2, 0b0000011)) } - "lbu" => { let (rd, rs1, imm) = reg_mem(rest)?; Ok(i_type(rd, rs1, imm, 0x4, 0b0000011)) } - "lhu" => { let (rd, rs1, imm) = reg_mem(rest)?; Ok(i_type(rd, rs1, imm, 0x5, 0b0000011)) } - - // Stores - "sb" => { let (rs1, rs2, imm) = reg_mem_store(rest)?; Ok(s_type(rs1, rs2, imm, 0x0)) } - "sh" => { let (rs1, rs2, imm) = reg_mem_store(rest)?; Ok(s_type(rs1, rs2, imm, 0x1)) } - "sw" => { let (rs1, rs2, imm) = reg_mem_store(rest)?; Ok(s_type(rs1, rs2, imm, 0x2)) } - - // Branches - "beq" => { let (rs1, rs2, off) = reg_reg_label(rest, pc, labels)?; Ok(b_type(rs1, rs2, off, 0x0)) } - "bne" => { let (rs1, rs2, off) = reg_reg_label(rest, pc, labels)?; Ok(b_type(rs1, rs2, off, 0x1)) } - "blt" => { let (rs1, rs2, off) = reg_reg_label(rest, pc, labels)?; Ok(b_type(rs1, rs2, off, 0x4)) } - "bge" => { let (rs1, rs2, off) = reg_reg_label(rest, pc, labels)?; Ok(b_type(rs1, rs2, off, 0x5)) } - "bltu" => { let (rs1, rs2, off) = reg_reg_label(rest, pc, labels)?; Ok(b_type(rs1, rs2, off, 0x6)) } - "bgeu" => { let (rs1, rs2, off) = reg_reg_label(rest, pc, labels)?; Ok(b_type(rs1, rs2, off, 0x7)) } - - // Jumps - "jal" => { let (rd, off) = reg_label(rest, pc, labels)?; Ok(j_type(rd, off)) } - "jalr" => { let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; Ok(i_type(rd, rs1, imm, 0x0, 0b1100111)) } - - // Upper immediate - "lui" => { let (rd, imm) = reg_imm(rest, pc, labels)?; Ok(u_type(rd, imm, 0b0110111)) } - "auipc" => { let (rd, imm) = reg_imm(rest, pc, labels)?; Ok(u_type(rd, imm, 0b0010111)) } - - other => Err(AsmError::UnknownInstruction(other.to_string())), - } -} - -// ── Encoding helpers ────────────────────────────────────────────────────────── - -fn r_type(rd: u8, rs1: u8, rs2: u8, funct3: u32, funct7: u32, opcode: u32) -> u32 { - opcode | ((rd as u32) << 7) | (funct3 << 12) | ((rs1 as u32) << 15) | ((rs2 as u32) << 20) | (funct7 << 25) -} - -fn i_type(rd: u8, rs1: u8, imm: i32, funct3: u32, opcode: u32) -> u32 { - opcode | ((rd as u32) << 7) | (funct3 << 12) | ((rs1 as u32) << 15) | (((imm as u32) & 0xfff) << 20) -} - -fn s_type(rs1: u8, rs2: u8, imm: i32, funct3: u32) -> u32 { - let imm = imm as u32; - 0b0100011 | (funct3 << 12) | ((rs1 as u32) << 15) | ((rs2 as u32) << 20) - | ((imm & 0x1f) << 7) | ((imm >> 5) << 25) -} - -fn b_type(rs1: u8, rs2: u8, imm: i32, funct3: u32) -> u32 { - let imm = imm as u32; - 0b1100011 | (funct3 << 12) | ((rs1 as u32) << 15) | ((rs2 as u32) << 20) - | (((imm >> 11) & 1) << 7) - | (((imm >> 1) & 0xf) << 8) - | (((imm >> 5) & 0x3f) << 25) - | (((imm >> 12) & 1) << 31) -} - -fn u_type(rd: u8, imm: i32, opcode: u32) -> u32 { - opcode | ((rd as u32) << 7) | ((imm as u32) & 0xfffff000) -} - -fn j_type(rd: u8, imm: i32) -> u32 { - let imm = imm as u32; - 0b1101111 | ((rd as u32) << 7) - | ((imm & 0xff000)) - | (((imm >> 11) & 1) << 20) - | (((imm >> 1) & 0x3ff) << 21) - | (((imm >> 20) & 1) << 31) -} - -// ── Operand parsers ─────────────────────────────────────────────────────────── - -fn reg(s: &str) -> Result { - let s = s.trim(); - // ABI names - let abi = match s { - "zero" => Some(0), "ra" => Some(1), "sp" => Some(2), "gp" => Some(3), - "tp" => Some(4), "t0" => Some(5), "t1" => Some(6), "t2" => Some(7), - "s0" | "fp" => Some(8), "s1" => Some(9), - "a0" => Some(10), "a1" => Some(11), "a2" => Some(12), "a3" => Some(13), - "a4" => Some(14), "a5" => Some(15), "a6" => Some(16), "a7" => Some(17), - "s2" => Some(18), "s3" => Some(19), "s4" => Some(20), "s5" => Some(21), - "s6" => Some(22), "s7" => Some(23), "s8" => Some(24), "s9" => Some(25), - "s10" => Some(26), "s11" => Some(27), - "t3" => Some(28), "t4" => Some(29), "t5" => Some(30), "t6" => Some(31), - _ => None, - }; - if let Some(n) = abi { return Ok(n); } - - if let Some(n) = s.strip_prefix('x') { - n.parse::().map_err(|_| AsmError::UnknownRegister(s.to_string())) - } else { - Err(AsmError::UnknownRegister(s.to_string())) - } -} - -fn split_ops(s: &str) -> Vec<&str> { - s.splitn(4, ',').map(|p| p.trim()).collect() -} - -fn reg2(s: &str) -> Result<(u8, u8), AsmError> { - let ops = split_ops(s); - if ops.len() < 2 { return Err(AsmError::BadOperands(s.to_string())); } - Ok((reg(ops[0])?, reg(ops[1])?)) -} - -fn reg3(s: &str) -> Result<(u8, u8, u8), AsmError> { - let ops = split_ops(s); - if ops.len() < 3 { return Err(AsmError::BadOperands(s.to_string())); } - Ok((reg(ops[0])?, reg(ops[1])?, reg(ops[2])?)) -} - -fn reg_imm(s: &str, pc: u32, labels: &HashMap) -> Result<(u8, i32), AsmError> { - let ops = split_ops(s); - if ops.len() < 2 { return Err(AsmError::BadOperands(s.to_string())); } - Ok((reg(ops[0])?, resolve(ops[1], pc, labels)?)) -} - -fn reg_reg_imm(s: &str, pc: u32, labels: &HashMap) -> Result<(u8, u8, i32), AsmError> { - let ops = split_ops(s); - if ops.len() < 3 { return Err(AsmError::BadOperands(s.to_string())); } - Ok((reg(ops[0])?, reg(ops[1])?, resolve(ops[2], pc, labels)?)) -} - -fn reg_reg_label(s: &str, pc: u32, labels: &HashMap) -> Result<(u8, u8, i32), AsmError> { - let ops = split_ops(s); - if ops.len() < 3 { return Err(AsmError::BadOperands(s.to_string())); } - let target = resolve(ops[2], pc, labels)?; - Ok((reg(ops[0])?, reg(ops[1])?, target)) -} - -fn reg_label(s: &str, pc: u32, labels: &HashMap) -> Result<(u8, i32), AsmError> { - let ops = split_ops(s); - if ops.len() < 2 { return Err(AsmError::BadOperands(s.to_string())); } - Ok((reg(ops[0])?, resolve(ops[1], pc, labels)?)) -} - -/// Parse `offset(base)` into (rd, base_reg, offset) -fn reg_mem(s: &str) -> Result<(u8, u8, i32), AsmError> { - let comma = s.find(',').ok_or_else(|| AsmError::BadOperands(s.to_string()))?; - let rd = reg(s[..comma].trim())?; - let rest = s[comma+1..].trim(); - let (off_str, base_str) = parse_mem_operand(rest)?; - Ok((rd, reg(base_str)?, off_str)) -} - -/// Parse `rs2, offset(base)` for stores — returns (base, rs2, offset) -fn reg_mem_store(s: &str) -> Result<(u8, u8, i32), AsmError> { - let comma = s.find(',').ok_or_else(|| AsmError::BadOperands(s.to_string()))?; - let rs2 = reg(s[..comma].trim())?; - let rest = s[comma+1..].trim(); - let (off_str, base_str) = parse_mem_operand(rest)?; - Ok((reg(base_str)?, rs2, off_str)) -} - -fn parse_mem_operand(s: &str) -> Result<(i32, &str), AsmError> { - let lparen = s.find('(').ok_or_else(|| AsmError::BadOperands(s.to_string()))?; - let rparen = s.find(')').ok_or_else(|| AsmError::BadOperands(s.to_string()))?; - let offset = s[..lparen].trim().parse::().unwrap_or(0); - let base = &s[lparen+1..rparen]; - Ok((offset, base)) -} - -fn resolve(s: &str, pc: u32, labels: &HashMap) -> Result { - let s = s.trim(); - if let Ok(n) = s.parse::() { - return Ok(n); - } - if let Some(n) = parse_hex(s) { - return Ok(n); - } - // Label — return PC-relative offset - labels.get(s) - .map(|&addr| (addr as i32) - (pc as i32)) - .ok_or_else(|| AsmError::UndefinedLabel(s.to_string())) -} - -fn parse_hex(s: &str) -> Option { - s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) - .and_then(|h| i32::from_str_radix(h, 16).ok()) -} diff --git a/src/circular_buffer.rs b/src/circular_buffer.rs deleted file mode 100644 index cdd8177..0000000 --- a/src/circular_buffer.rs +++ /dev/null @@ -1,70 +0,0 @@ -pub struct CircularBuffer { - head: usize, - tail: usize, - size: usize, - data: Vec>, -} - -impl CircularBuffer { - - pub fn new(capacity: usize) -> Self { - Self { head: 0, tail: 0, size: 0, data: (0..capacity).map(|_| None).collect() } - } - - pub fn capacity(&self) -> usize { self.data.len() } - - pub fn is_full(&self) -> bool { self.size == self.data.len() } - - pub fn is_empty(&self) -> bool { self.size == 0 } - - pub fn push(&mut self, item: T) -> usize { - if self.is_full() { panic!("Trying to push to a full circular buffer."); } - let idx = self.tail; - self.data[idx] = Some(item); - self.tail = (self.tail + 1) % self.data.len(); - self.size += 1; - idx - } - - pub fn pop(&mut self) -> T { - if self.is_empty() { panic!("Trying to pop from an empty circular buffer"); } - let elem = self.data[self.head].take().unwrap(); - self.head = (self.head + 1) % self.data.len(); - self.size -= 1; - elem - } - - pub fn head_tag(&self) -> usize { - return self.head - } - - pub fn head(&self) -> &T { - self.data[self.head].as_ref().unwrap() - } - - pub fn read_by_tag(&self, tag: usize) -> &T { - self.data[tag].as_ref().expect("Invalid access in circular buffer") - } - - pub fn access_by_tag(&mut self, tag: usize) -> &mut T { - self.data[tag].as_mut().expect("Invalid access in circular buffer") - } - - pub fn len(&self) -> usize { self.size } - - pub fn iter(&self) -> impl Iterator { - let n = self.data.len(); - let head = self.head; - (0..self.size).map(move |i| self.data[(head + i) % n].as_ref().unwrap()) - } - - // Yields (tag, entry) pairs where tag is the physical slot index used as rob tag. - pub fn iter_tagged(&self) -> impl Iterator { - let n = self.data.len(); - let head = self.head; - (0..self.size).map(move |i| { - let tag = (head + i) % n; - (tag, self.data[tag].as_ref().unwrap()) - }) - } -} diff --git a/src/diag.rs b/src/diag.rs deleted file mode 100644 index cfe3420..0000000 --- a/src/diag.rs +++ /dev/null @@ -1,41 +0,0 @@ -pub struct DiagNode { - pub label: String, - pub value: Option, - pub children: Vec, -} - -impl DiagNode { - pub fn leaf(label: impl Into, value: impl Into) -> Self { - Self { label: label.into(), value: Some(value.into()), children: vec![] } - } - - pub fn inner(label: impl Into, children: Vec) -> Self { - Self { label: label.into(), value: None, children } - } - - // Entry point: print the root label then recurse into children. - pub fn print(&self) { - println!("{}", self.label); - let n = self.children.len(); - for (i, child) in self.children.iter().enumerate() { - child.render("", i == n - 1); - } - } - - fn render(&self, prefix: &str, is_last: bool) { - let connector = if is_last { "└── " } else { "├── " }; - match &self.value { - Some(v) => println!("{}{}{}: {}", prefix, connector, self.label, v), - None => println!("{}{}{}", prefix, connector, self.label), - } - let child_prefix = format!("{}{}", prefix, if is_last { " " } else { "│ " }); - let n = self.children.len(); - for (i, child) in self.children.iter().enumerate() { - child.render(&child_prefix, i == n - 1); - } - } -} - -pub trait Diagnosable { - fn diagnose(&self) -> DiagNode; -} diff --git a/src/five_stage/forwarding.rs b/src/five_stage/forwarding.rs index 9e06faf..e01e2cf 100644 --- a/src/five_stage/forwarding.rs +++ b/src/five_stage/forwarding.rs @@ -1,4 +1,4 @@ -use crate::five_stage::stages::{IdEx, ExMem, MemWb}; +use crate::five_stage::stages::{ExMem, IdEx, MemWb}; pub trait ForwardingPolicy { /// Optionally override rs1/rs2 in the decoded latch using values from @@ -18,12 +18,18 @@ pub struct FullForwarding; impl ForwardingPolicy for FullForwarding { fn forward(&self, id_ex: &mut IdEx, ex_mem: &Option, mem_wb: &Option) { - let rd_ex = ex_mem.map(|l| (l.instr.rd, l.result)); + let rd_ex = ex_mem.map(|l| (l.instr.rd, l.result)); let rd_mem = mem_wb.map(|l| (l.instr.rd, l.result)); if let Some((rd, val)) = rd_ex { - if rd != 0 && rd == id_ex.instr.rs1 { id_ex.rs1 = val; return; } - if rd != 0 && rd == id_ex.instr.rs2 { id_ex.rs2 = val; return; } + if rd != 0 && rd == id_ex.instr.rs1 { + id_ex.rs1 = val; + return; + } + if rd != 0 && rd == id_ex.instr.rs2 { + id_ex.rs2 = val; + return; + } } if let Some((rd, val)) = rd_mem { if rd != 0 && rd == id_ex.instr.rs1 { diff --git a/src/five_stage/hazard.rs b/src/five_stage/hazard.rs index f420fbf..5ee035a 100644 --- a/src/five_stage/hazard.rs +++ b/src/five_stage/hazard.rs @@ -1,8 +1,12 @@ -use crate::isa::Opcode; -use crate::five_stage::stages::{IfId, IdEx}; +use crate::five_stage::stages::{IdEx, IfId}; +use crate::utils::isa::Opcode; -fn word_rs1(word: u32) -> u8 { ((word >> 15) & 0x1f) as u8 } -fn word_rs2(word: u32) -> u8 { ((word >> 20) & 0x1f) as u8 } +fn word_rs1(word: u32) -> u8 { + ((word >> 15) & 0x1f) as u8 +} +fn word_rs2(word: u32) -> u8 { + ((word >> 20) & 0x1f) as u8 +} pub trait HazardPolicy { /// Return true if the pipeline should stall — i.e. hold fetch/decode and @@ -25,9 +29,12 @@ pub struct StallOnLoad; impl HazardPolicy for StallOnLoad { fn should_stall(&self, if_id: &Option, id_ex: &Option) -> bool { - let (Some(fetch), Some(exec)) = (if_id, id_ex) else { return false; }; + let (Some(fetch), Some(exec)) = (if_id, id_ex) else { + return false; + }; - let is_load = matches!(exec.instr.opcode, + let is_load = matches!( + exec.instr.opcode, Opcode::Lb | Opcode::Lbu | Opcode::Lh | Opcode::Lhu | Opcode::Lw ); diff --git a/src/five_stage/mod.rs b/src/five_stage/mod.rs index 94ce07f..bd18e10 100644 --- a/src/five_stage/mod.rs +++ b/src/five_stage/mod.rs @@ -1,14 +1,14 @@ -pub mod stages; -pub mod hazard; pub mod forwarding; +pub mod hazard; +pub mod stages; -use crate::isa::{self, Opcode, InstrFormat}; use crate::alu::{ALU, AluPipe, ExecInputs, FuncUnitKind, OpaqueInstruction, OpaqueResult}; -use crate::diag::{DiagNode, Diagnosable}; -use crate::latch::Latch; -use stages::{IfId, IdEx, ExMem, MemWb}; -use hazard::HazardPolicy; +use crate::utils::diag::{DiagNode, Diagnosable}; +use crate::utils::isa::{self, InstrFormat, Opcode}; +use crate::utils::latch::Latch; use forwarding::ForwardingPolicy; +use hazard::HazardPolicy; +use stages::{ExMem, IdEx, IfId, MemWb}; // The five-stage pipeline wraps each inter-stage latch in the Latch type, // which enforces the two-phase (stage then update) discipline that models @@ -17,182 +17,242 @@ use forwarding::ForwardingPolicy; // Wrapper types so the generic ALU can carry the pipeline's latch data. struct FiveStageInstr(IdEx); -struct FiveStageResult { ex_mem: ExMem } +struct FiveStageResult { + ex_mem: ExMem, +} impl OpaqueInstruction for FiveStageInstr { - fn get_opcode(&self) -> Opcode { self.0.instr.opcode } - fn get_inputs(&self) -> ExecInputs { - ExecInputs { pc: self.0.pc, rs1: self.0.rs1, rs2: self.0.rs2, imm: self.0.instr.imm } - } + fn get_opcode(&self) -> Opcode { + self.0.instr.opcode + } + fn get_inputs(&self) -> ExecInputs { + ExecInputs { + pc: self.0.pc, + rs1: self.0.rs1, + rs2: self.0.rs2, + imm: self.0.instr.imm, + } + } } impl OpaqueResult for FiveStageResult { - fn from_instr_and_result(instr: FiveStageInstr, result: u32) -> Self { - FiveStageResult { ex_mem: ExMem { instr: instr.0.instr, result, rs2: instr.0.rs2 } } - } + fn from_instr_and_result(instr: FiveStageInstr, result: u32) -> Self { + FiveStageResult { + ex_mem: ExMem { + instr: instr.0.instr, + result, + rs2: instr.0.rs2, + }, + } + } } type FiveALU = ALU; fn default_alu() -> FiveALU { - FiveALU::new(vec![ - AluPipe::new(vec![FuncUnitKind::IntAlu, FuncUnitKind::LoadStore, FuncUnitKind::Branch]), - ]) + FiveALU::new(vec![AluPipe::new(vec![ + FuncUnitKind::IntAlu, + FuncUnitKind::LoadStore, + FuncUnitKind::Branch, + ])]) } pub struct FiveStageCpu { - pub regs: [u32; 32], - pub mem: Vec, - pub pc: u32, - pub cycle: u64, - - if_id: Latch, - id_ex: Latch, - ex_mem: Latch, - mem_wb: Latch, - - alu: FiveALU, - hazard: Box, - forwarding: Box, + pub regs: [u32; 32], + pub mem: Vec, + pub pc: u32, + pub cycle: u64, + + if_id: Latch, + id_ex: Latch, + ex_mem: Latch, + mem_wb: Latch, + + alu: FiveALU, + hazard: Box, + forwarding: Box, } impl FiveStageCpu { - pub fn new( - mem_size: usize, - hazard: Box, - forwarding: Box, - ) -> Self { - Self { - regs: [0u32; 32], mem: vec![0u8; mem_size], pc: 0, cycle: 0, - if_id: Latch::new(), id_ex: Latch::new(), ex_mem: Latch::new(), mem_wb: Latch::new(), - alu: default_alu(), hazard, forwarding, + pub fn new( + mem_size: usize, + hazard: Box, + forwarding: Box, + ) -> Self { + Self { + regs: [0u32; 32], + mem: vec![0u8; mem_size], + pc: 0, + cycle: 0, + if_id: Latch::new(), + id_ex: Latch::new(), + ex_mem: Latch::new(), + mem_wb: Latch::new(), + alu: default_alu(), + hazard, + forwarding, + } } - } - pub fn load(&mut self, addr: usize, bytes: &[u8]) { - self.mem[addr..addr + bytes.len()].copy_from_slice(bytes); - } + pub fn load(&mut self, addr: usize, bytes: &[u8]) { + self.mem[addr..addr + bytes.len()].copy_from_slice(bytes); + } - pub fn tick(&mut self) { - self.cycle += 1; + pub fn tick(&mut self) { + self.cycle += 1; - // Snapshot latches for hazard/forwarding checks — these must reflect - // start-of-cycle state so stages don't observe each other's outputs. - let if_id_snap = self.if_id.peek(); - let id_ex_snap = self.id_ex.peek(); - let ex_mem_snap = self.ex_mem.peek(); - let mem_wb_snap = self.mem_wb.peek(); + // Snapshot latches for hazard/forwarding checks — these must reflect + // start-of-cycle state so stages don't observe each other's outputs. + let if_id_snap = self.if_id.peek(); + let id_ex_snap = self.id_ex.peek(); + let ex_mem_snap = self.ex_mem.peek(); + let mem_wb_snap = self.mem_wb.peek(); - let stall = self.hazard.should_stall(&if_id_snap, &id_ex_snap); + let stall = self.hazard.should_stall(&if_id_snap, &id_ex_snap); - // IF: fetch instruction word from memory. - if !stall { - self.if_id.stage(IfId { pc: self.pc, word: self.mem_read_u32(self.pc) }); - self.pc += 4; - } + // IF: fetch instruction word from memory. + if !stall { + self.if_id.stage(IfId { + pc: self.pc, + word: self.mem_read_u32(self.pc), + }); + self.pc += 4; + } - // ID: decode and read register file. - if !stall { - if let Some(latch) = if_id_snap { - if let Some(instr) = isa::decode(latch.word) { - self.id_ex.stage(IdEx { - pc: latch.pc, - rs1: self.regs[instr.rs1 as usize], - rs2: self.regs[instr.rs2 as usize], - instr, - }); + // ID: decode and read register file. + if !stall { + if let Some(latch) = if_id_snap { + if let Some(instr) = isa::decode(latch.word) { + self.id_ex.stage(IdEx { + pc: latch.pc, + rs1: self.regs[instr.rs1 as usize], + rs2: self.regs[instr.rs2 as usize], + instr, + }); + } + } } - } - } - // EX: apply forwarding then dispatch to the ALU. - // The ALU is pipelined — tick() returns a result when one is ready. - if let Some(mut latch) = id_ex_snap { - self.forwarding.forward(&mut latch, &ex_mem_snap, &mem_wb_snap); - self.alu.try_enqueue(FiveStageInstr(latch)); - } - if let Some(res) = self.alu.tick() { - self.ex_mem.stage(res.ex_mem); - } + // EX: apply forwarding then dispatch to the ALU. + // The ALU is pipelined — tick() returns a result when one is ready. + if let Some(mut latch) = id_ex_snap { + self.forwarding + .forward(&mut latch, &ex_mem_snap, &mem_wb_snap); + self.alu.try_enqueue(FiveStageInstr(latch)); + } + if let Some(res) = self.alu.tick() { + self.ex_mem.stage(res.ex_mem); + } - // MEM: perform load/store against memory. - if let Some(latch) = ex_mem_snap { - let (result, write) = self.compute_memory(&latch); - if let Some(w) = write { self.apply_mem_write(w); } - self.mem_wb.stage(MemWb { instr: latch.instr, result }); - } + // MEM: perform load/store against memory. + if let Some(latch) = ex_mem_snap { + let (result, write) = self.compute_memory(&latch); + if let Some(w) = write { + self.apply_mem_write(w); + } + self.mem_wb.stage(MemWb { + instr: latch.instr, + result, + }); + } + + // WB: write result to register file. + if let Some(latch) = mem_wb_snap { + let rd = latch.instr.rd; + let writes = matches!( + latch.instr.format, + InstrFormat::R | InstrFormat::I | InstrFormat::U | InstrFormat::J + ); + if writes && rd != 0 { + self.regs[rd as usize] = latch.result; + } + } + + // Advance all latches: staged values become active for the next cycle. + self.if_id.update(); + self.id_ex.update(); + self.ex_mem.update(); + self.mem_wb.update(); - // WB: write result to register file. - if let Some(latch) = mem_wb_snap { - let rd = latch.instr.rd; - let writes = matches!(latch.instr.format, InstrFormat::R | InstrFormat::I | InstrFormat::U | InstrFormat::J); - if writes && rd != 0 { self.regs[rd as usize] = latch.result; } + self.regs[0] = 0; } - // Advance all latches: staged values become active for the next cycle. - self.if_id.update(); - self.id_ex.update(); - self.ex_mem.update(); - self.mem_wb.update(); - - self.regs[0] = 0; - } - - fn compute_memory(&self, latch: &ExMem) -> (u32, Option) { - match latch.instr.opcode { - Opcode::Lb => (self.mem_read_u8(latch.result) as i8 as i32 as u32, None), - Opcode::Lbu => (self.mem_read_u8(latch.result) as u32, None), - Opcode::Lh => (self.mem_read_u16(latch.result) as i16 as i32 as u32, None), - Opcode::Lhu => (self.mem_read_u16(latch.result) as u32, None), - Opcode::Lw => (self.mem_read_u32(latch.result), None), - Opcode::Sb => (0, Some(MemWrite::Byte(latch.result, latch.rs2 as u8))), - Opcode::Sh => (0, Some(MemWrite::Half(latch.result, latch.rs2 as u16))), - Opcode::Sw => (0, Some(MemWrite::Word(latch.result, latch.rs2))), - _ => (latch.result, None), + fn compute_memory(&self, latch: &ExMem) -> (u32, Option) { + match latch.instr.opcode { + Opcode::Lb => (self.mem_read_u8(latch.result) as i8 as i32 as u32, None), + Opcode::Lbu => (self.mem_read_u8(latch.result) as u32, None), + Opcode::Lh => (self.mem_read_u16(latch.result) as i16 as i32 as u32, None), + Opcode::Lhu => (self.mem_read_u16(latch.result) as u32, None), + Opcode::Lw => (self.mem_read_u32(latch.result), None), + Opcode::Sb => (0, Some(MemWrite::Byte(latch.result, latch.rs2 as u8))), + Opcode::Sh => (0, Some(MemWrite::Half(latch.result, latch.rs2 as u16))), + Opcode::Sw => (0, Some(MemWrite::Word(latch.result, latch.rs2))), + _ => (latch.result, None), + } } - } - fn apply_mem_write(&mut self, w: MemWrite) { - match w { - MemWrite::Byte(addr, val) => self.mem[addr as usize] = val, - MemWrite::Half(addr, val) => self.mem[addr as usize..addr as usize+2].copy_from_slice(&val.to_le_bytes()), - MemWrite::Word(addr, val) => self.mem[addr as usize..addr as usize+4].copy_from_slice(&val.to_le_bytes()), + fn apply_mem_write(&mut self, w: MemWrite) { + match w { + MemWrite::Byte(addr, val) => self.mem[addr as usize] = val, + MemWrite::Half(addr, val) => { + self.mem[addr as usize..addr as usize + 2].copy_from_slice(&val.to_le_bytes()) + } + MemWrite::Word(addr, val) => { + self.mem[addr as usize..addr as usize + 4].copy_from_slice(&val.to_le_bytes()) + } + } } - } - fn mem_read_u8(&self, addr: u32) -> u8 { self.mem[addr as usize] } - fn mem_read_u16(&self, addr: u32) -> u16 { u16::from_le_bytes(self.mem[addr as usize..addr as usize+2].try_into().unwrap()) } - fn mem_read_u32(&self, addr: u32) -> u32 { u32::from_le_bytes(self.mem[addr as usize..addr as usize+4].try_into().unwrap()) } + fn mem_read_u8(&self, addr: u32) -> u8 { + self.mem[addr as usize] + } + fn mem_read_u16(&self, addr: u32) -> u16 { + u16::from_le_bytes( + self.mem[addr as usize..addr as usize + 2] + .try_into() + .unwrap(), + ) + } + fn mem_read_u32(&self, addr: u32) -> u32 { + u32::from_le_bytes( + self.mem[addr as usize..addr as usize + 4] + .try_into() + .unwrap(), + ) + } } impl Diagnosable for FiveStageCpu { - fn diagnose(&self) -> DiagNode { - // Non-zero registers only - /* let reg_entries: Vec = self.regs.iter().enumerate().skip(1) - .filter(|(_, v)| v != 0) - .map(|(i, &v)| DiagNode::leaf(format!("x{i}"), format!("0x{v:08x} ({v})"))) - .collect(); */ - /* let regs_node = if reg_entries.is_empty() { - DiagNode::leaf("regs", "all zero") - } else { - DiagNode::inner("regs", reg_entries) - }; */ - - DiagNode::inner("five_stage_cpu", vec![ - DiagNode::leaf("pc", format!("0x{:08x}", self.pc)), - DiagNode::leaf("cycle", self.cycle.to_string()), - DiagNode::leaf("if_id", format!("{:?}", self.if_id.peek())), - DiagNode::leaf("id_ex", format!("{:?}", self.id_ex.peek())), - DiagNode::leaf("ex_mem", format!("{:?}", self.ex_mem.peek())), - DiagNode::leaf("mem_wb", format!("{:?}", self.mem_wb.peek())), - self.alu.diagnose(), - //regs_node, - ]) - } + fn diagnose(&self) -> DiagNode { + // Non-zero registers only + /* let reg_entries: Vec = self.regs.iter().enumerate().skip(1) + .filter(|(_, v)| v != 0) + .map(|(i, &v)| DiagNode::leaf(format!("x{i}"), format!("0x{v:08x} ({v})"))) + .collect(); */ + /* let regs_node = if reg_entries.is_empty() { + DiagNode::leaf("regs", "all zero") + } else { + DiagNode::inner("regs", reg_entries) + }; */ + + DiagNode::inner( + "five_stage_cpu", + vec![ + DiagNode::leaf("pc", format!("0x{:08x}", self.pc)), + DiagNode::leaf("cycle", self.cycle.to_string()), + DiagNode::leaf("if_id", format!("{:?}", self.if_id.peek())), + DiagNode::leaf("id_ex", format!("{:?}", self.id_ex.peek())), + DiagNode::leaf("ex_mem", format!("{:?}", self.ex_mem.peek())), + DiagNode::leaf("mem_wb", format!("{:?}", self.mem_wb.peek())), + self.alu.diagnose(), + //regs_node, + ], + ) + } } enum MemWrite { - Byte(u32, u8), - Half(u32, u16), - Word(u32, u32), + Byte(u32, u8), + Half(u32, u16), + Word(u32, u32), } diff --git a/src/five_stage/stages.rs b/src/five_stage/stages.rs index f0b6eca..01dc69f 100644 --- a/src/five_stage/stages.rs +++ b/src/five_stage/stages.rs @@ -1,32 +1,32 @@ -use crate::isa::DecodedInstr; +use crate::utils::isa::DecodedInstr; /// Output of the Fetch stage #[derive(Clone, Copy, Debug)] pub struct IfId { - pub pc: u32, + pub pc: u32, pub word: u32, } /// Output of the Decode stage #[derive(Clone, Copy, Debug)] pub struct IdEx { - pub pc: u32, + pub pc: u32, pub instr: DecodedInstr, - pub rs1: u32, - pub rs2: u32, + pub rs1: u32, + pub rs2: u32, } /// Output of the Execute stage #[derive(Clone, Copy, Debug)] pub struct ExMem { - pub instr: DecodedInstr, + pub instr: DecodedInstr, pub result: u32, - pub rs2: u32, // store value + pub rs2: u32, // store value } /// Output of the Memory stage #[derive(Clone, Copy, Debug)] pub struct MemWb { - pub instr: DecodedInstr, + pub instr: DecodedInstr, pub result: u32, } diff --git a/src/latch.rs b/src/latch.rs deleted file mode 100644 index 0409107..0000000 --- a/src/latch.rs +++ /dev/null @@ -1,33 +0,0 @@ -pub struct Latch { - stage: Option, - active: Option, -} - -impl Latch { - - pub fn new() -> Self { - Self { stage: None, active: None } - } - - pub fn stage(&mut self, val: T) { - self.stage = Some(val); - } - - pub fn direct_stage(&mut self, maybe_val: Option) { - self.stage = maybe_val; - } - - pub fn update(&mut self) { - self.active = self.stage.take(); - } - - pub fn read(&mut self) -> Option { - self.active.take() - } - - // Returns a copy of the active value without consuming it. - // Used to snapshot start-of-cycle state for hazard/forwarding checks. - pub fn peek(&self) -> Option where T: Copy { - self.active - } -} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 410e134..24514cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,11 @@ -mod isa; mod alu; -mod assembler; mod five_stage; mod simple_ooo; -mod circular_buffer; -mod latch; -mod diag; +mod utils; +use five_stage::{FiveStageCpu, forwarding, hazard}; use std::env; -use diag::Diagnosable as _; -use five_stage::{FiveStageCpu, hazard, forwarding}; +use utils::diag::Diagnosable as _; fn usage() -> ! { eprintln!("Usage: cpu-simulator "); @@ -24,9 +20,11 @@ fn usage() -> ! { fn main() { let args: Vec = env::args().collect(); - if args.len() < 3 { usage(); } + if args.len() < 3 { + usage(); + } - let model = &args[1]; + let model = &args[1]; let program = &args[2]; // Accept either a file path or an inline assembly string. @@ -39,7 +37,7 @@ fn main() { program.clone() }; - let words = assembler::assemble(&source).unwrap_or_else(|e| { + let words = utils::assembler::assemble(&source).unwrap_or_else(|e| { eprintln!("Assembly error: {e}"); std::process::exit(1); }); @@ -62,13 +60,15 @@ fn run_five_stage(words: Vec) { cpu.load(i * 4, &w.to_le_bytes()); } - let cycles = words.len() + 4; // drain the pipeline + let cycles = words.len() + 4; // drain the pipeline for _ in 0..cycles { cpu.tick(); cpu.diagnose().print(); print!("cycle {:3} |", cpu.cycle); for (i, &v) in cpu.regs.iter().enumerate().skip(1) { - if v != 0 { print!(" x{i}={v}"); } + if v != 0 { + print!(" x{i}={v}"); + } } println!(); } @@ -89,7 +89,9 @@ fn run_ooo(words: Vec) { cpu.diagnose().print(); print!("cycle {:3} |", c + 1); for (i, &v) in cpu.regs.iter().enumerate().skip(1) { - if v != 0 { print!(" x{i}={v}"); } + if v != 0 { + print!(" x{i}={v}"); + } } println!(); } diff --git a/src/simple_ooo/mod.rs b/src/simple_ooo/mod.rs index 9c626cb..b71efb8 100644 --- a/src/simple_ooo/mod.rs +++ b/src/simple_ooo/mod.rs @@ -1,14 +1,14 @@ -pub mod reservation_stations; pub mod reorder_buffer; +pub mod reservation_stations; use core::panic; use crate::alu::{ALU, AluPipe, ExecInputs, FuncUnitKind, OpaqueInstruction, OpaqueResult}; -use crate::diag::{DiagNode, Diagnosable}; -use crate::isa::{DecodedInstr, InstResult, decode, Opcode}; -use crate::latch::Latch; -use crate::simple_ooo::reservation_stations::{Operand, SREntry, SimpleReservationStation}; use crate::simple_ooo::reorder_buffer::SimpleReorderBuffer; +use crate::simple_ooo::reservation_stations::{Operand, SREntry, SimpleReservationStation}; +use crate::utils::diag::{DiagNode, Diagnosable}; +use crate::utils::isa::{DecodedInstr, InstResult, Opcode, decode}; +use crate::utils::latch::Latch; /* Simple out-of-order CPU with the following pipeline: @@ -43,33 +43,48 @@ type RobTag = usize; // The OpaqueInstruction/OpaqueResult traits keep the ALU generic and // independent of pipeline-specific metadata. pub struct InstrFormat { - rob_tag: RobTag, - instr: SREntry, + rob_tag: RobTag, + instr: SREntry, } impl OpaqueInstruction for InstrFormat { - fn get_opcode(&self) -> Opcode { - self.instr.opcode - } - - fn get_inputs(&self) -> ExecInputs { - let rs1 = match self.instr.rs1 { Operand::Ready(v) => v, _ => panic!("rs1 not ready at dispatch") }; - let rs2 = match self.instr.rs2 { Operand::Ready(v) => v, _ => panic!("rs2 not ready at dispatch") }; - ExecInputs { pc: self.instr.pc, rs1, rs2, imm: self.instr.imm } - } + fn get_opcode(&self) -> Opcode { + self.instr.opcode + } + + fn get_inputs(&self) -> ExecInputs { + let rs1 = match self.instr.rs1 { + Operand::Ready(v) => v, + _ => panic!("rs1 not ready at dispatch"), + }; + let rs2 = match self.instr.rs2 { + Operand::Ready(v) => v, + _ => panic!("rs2 not ready at dispatch"), + }; + ExecInputs { + pc: self.instr.pc, + rs1, + rs2, + imm: self.instr.imm, + } + } } #[derive(Debug, Clone, Copy)] pub struct ResultFormat { - rob_tag: RobTag, - instr: SREntry, - result: u32, + rob_tag: RobTag, + instr: SREntry, + result: u32, } impl OpaqueResult for ResultFormat { - fn from_instr_and_result(instr: InstrFormat, result: u32) -> Self { - ResultFormat { rob_tag: instr.rob_tag, instr: instr.instr, result } - } + fn from_instr_and_result(instr: InstrFormat, result: u32) -> Self { + ResultFormat { + rob_tag: instr.rob_tag, + instr: instr.instr, + result, + } + } } type OOOALU = ALU; @@ -79,14 +94,14 @@ type OOOALU = ALU; // the register between stages in a real pipeline. #[derive(Debug, Clone, Copy)] struct IfId { - pc: u32, - word: u32, + pc: u32, + word: u32, } #[derive(Debug, Clone, Copy)] struct IdRs { - pc: u32, - instr: DecodedInstr, + pc: u32, + instr: DecodedInstr, } // The ReservationStation trait abstracts over different RS implementations @@ -100,15 +115,15 @@ struct IdRs { // is updated to Ready, potentially making it eligible to dispatch. // - dispatch: inspect all ready entries and send one to the ALU. trait ReservationStation: Diagnosable { - fn stall(&self) -> bool; - fn enqueue(&mut self, rob_tag: usize, rs1: Operand, rs2: Operand, instr: &IdRs); - fn update(&mut self, rob_tag: usize, val: u32); - fn dispatch(&mut self, alu: &mut OOOALU); + fn stall(&self) -> bool; + fn enqueue(&mut self, rob_tag: usize, rs1: Operand, rs2: Operand, instr: &IdRs); + fn update(&mut self, rob_tag: usize, val: u32); + fn dispatch(&mut self, alu: &mut OOOALU); } pub struct TaggedResult { - rob_tag: RobTag, - result: InstResult, + rob_tag: RobTag, + result: InstResult, } // The ReorderBuffer trait abstracts over ROB implementations. @@ -121,279 +136,319 @@ pub struct TaggedResult { // - release: if the head of the ROB is complete, retire it in program order // and return the result to be written to the register file. trait ReorderBuffer: Diagnosable { - fn stall(&self) -> bool; - fn enqueue(&mut self, instr: &IdRs) -> usize; - fn update(&mut self, rob_tag: RobTag, result: u32); - fn release(&mut self) -> Option; - fn is_tag_ready(&self, tag: RobTag) -> Option; + fn stall(&self) -> bool; + fn enqueue(&mut self, instr: &IdRs) -> usize; + fn update(&mut self, rob_tag: RobTag, result: u32); + fn release(&mut self) -> Option; + fn is_tag_ready(&self, tag: RobTag) -> Option; } pub struct SimpleOOO { - mem: Vec, - pub regs: [u32; 32], - - // Register Alias Table: rat[r] = Some(tag) means register r's current value - // is being produced by the instruction with that ROB tag, and has not yet - // committed. None means the register file holds the current value. - rat: [Option; 32], - - pc: u32, - // When a branch or jump commits we know the true next-PC. Until then the - // frontend is stalled so no speculative instructions enter the pipeline. - branch_stall: bool, - - if_id: Latch, - id_rs: Latch, - reservation_station: Box, - reorder_buffer: Box, - - alu: OOOALU, - alu_mem: Latch, - mem_rob_release: Latch, + mem: Vec, + pub regs: [u32; 32], + + // Register Alias Table: rat[r] = Some(tag) means register r's current value + // is being produced by the instruction with that ROB tag, and has not yet + // committed. None means the register file holds the current value. + rat: [Option; 32], + + pc: u32, + // When a branch or jump commits we know the true next-PC. Until then the + // frontend is stalled so no speculative instructions enter the pipeline. + branch_stall: bool, + + if_id: Latch, + id_rs: Latch, + reservation_station: Box, + reorder_buffer: Box, + + alu: OOOALU, + alu_mem: Latch, + mem_rob_release: Latch, } impl SimpleOOO { - pub fn load(&mut self, addr: usize, bytes: &[u8]) { - self.mem[addr..addr + bytes.len()].copy_from_slice(bytes); - } - - pub fn tick(&mut self) { - - // Stall the frontend if the RS or ROB are full. This models backpressure - // from the backend: we can't issue new instructions if there is nowhere - // to put them. - let stall_frontend = self.reservation_station.stall() || self.reorder_buffer.stall() || self.branch_stall; - - // IF: fetch the next instruction word from memory. - // If stalled, the latch produces no output next cycle. - if !stall_frontend { - let fetched = self.fetch(); - self.if_id.stage(fetched); + pub fn load(&mut self, addr: usize, bytes: &[u8]) { + self.mem[addr..addr + bytes.len()].copy_from_slice(bytes); } - // ID: decode the fetched instruction word into its fields. - if let Some(buffer_state) = self.if_id.read() { - let decoded = decode(buffer_state.word); - if let Some(instr) = decoded { - self.id_rs.stage(IdRs { pc: buffer_state.pc, instr }); - } else { - if buffer_state.word == 0 { return; } - panic!("Unrecognized instruction!"); - } + pub fn tick(&mut self) { + // Stall the frontend if the RS or ROB are full. This models backpressure + // from the backend: we can't issue new instructions if there is nowhere + // to put them. + let stall_frontend = + self.reservation_station.stall() || self.reorder_buffer.stall() || self.branch_stall; + + // IF: fetch the next instruction word from memory. + // If stalled, the latch produces no output next cycle. + if !stall_frontend { + let fetched = self.fetch(); + self.if_id.stage(fetched); + } + + // ID: decode the fetched instruction word into its fields. + if let Some(buffer_state) = self.if_id.read() { + let decoded = decode(buffer_state.word); + if let Some(instr) = decoded { + self.id_rs.stage(IdRs { + pc: buffer_state.pc, + instr, + }); + } else { + if buffer_state.word == 0 { + return; + } + panic!("Unrecognized instruction!"); + } + } + + // IS (Issue): allocate a ROB entry, resolve operands via the RAT, + // and enqueue the instruction into the reservation station. + // Operands that are already available come from the register file; + // operands produced by in-flight instructions are tagged with the + // ROB entry that will produce them. + if let Some(instr) = self.id_rs.read() { + if Self::is_branch(instr.instr.opcode) { + self.branch_stall = true; + } + let rob_tag = self.reorder_buffer.enqueue(&instr); + let rs1 = self.resolve_operands(instr.instr.rs1); + let rs2 = self.resolve_operands(instr.instr.rs2); + // Only update the RAT for instructions that actually write a result to rd. + // B-type and S-type encode immediates in the rd field — treating those + // bits as a destination register would corrupt the RAT. + if Self::writes_rd(instr.instr.format) && instr.instr.rd != 0 { + self.rat[instr.instr.rd as usize] = Some(rob_tag); + } + self.reservation_station.enqueue(rob_tag, rs1, rs2, &instr); + } + + // EX: dispatch a ready instruction to the ALU, then tick the ALU. + // On completion, broadcast the result on the CDB, waking any RS entries + // that were waiting for this tag. For non-load instructions the ALU result + // is final, so we also mark the ROB entry ready here. This means + // resolve_operands can observe the value in the very next cycle via + // is_tag_ready, rather than waiting two more stages. + // Loads are the exception: the ALU only computed the address; the actual + // loaded value comes from MEM and will update the ROB there instead. + self.reservation_station.dispatch(&mut self.alu); + if let Some(res) = self.alu.tick() { + if Self::is_branch(res.instr.opcode) { + self.pc = res.result; + self.branch_stall = false; + } + // Non-loads: the ALU result is the final value. Broadcast on the CDB + // and mark the ROB entry ready immediately. Loads must wait until MEM + // has the actual data — broadcasting the address here would give waiting + // instructions the wrong value. + if !Self::is_load(res.instr.opcode) { + self.reservation_station.update(res.rob_tag, res.result); + self.reorder_buffer.update(res.rob_tag, res.result); + } + self.alu_mem.stage(res); + } + + // MEM: for loads and stores the ALU computed the effective address. + // Here we perform the actual memory access. Non-memory instructions + // pass through unchanged (result forwarded as-is). + if let Some(instr) = self.alu_mem.read() { + let result = self.mem_stage(instr); + self.mem_rob_release.stage(result); + } + + // WB: for loads, the loaded data is now the final result. Broadcast on + // the CDB (waking any RS entries that were waiting on this load's rd) + // and mark the ROB entry ready. The ROB update and RS broadcast are + // always paired so no waiting entry can be left behind. + if let Some(instr) = self.mem_rob_release.read() { + if Self::is_load(instr.instr.opcode) { + self.reservation_station.update(instr.rob_tag, instr.result); + self.reorder_buffer.update(instr.rob_tag, instr.result); + } + } + + // Commit: retire the head of the ROB if it is complete. Writing to the + // register file only happens here, ensuring results become visible in + // program order. The RAT entry is cleared only if it still points to + // this instruction (a later write to the same register may have already + // replaced it). + if let Some(instr) = self.reorder_buffer.release() { + let rd = instr.result.rd as usize; + if Self::writes_rd(instr.result.format) && rd != 0 { + if let Some(rat_tag) = self.rat[rd] { + if rat_tag == instr.rob_tag { + self.rat[rd] = None; + } + } + self.regs[rd] = instr.result.result; + } + } + + // Advance all latches: the staged value becomes the readable value + // for the next cycle. + self.if_id.update(); + self.id_rs.update(); + self.alu_mem.update(); + self.mem_rob_release.update(); } - // IS (Issue): allocate a ROB entry, resolve operands via the RAT, - // and enqueue the instruction into the reservation station. - // Operands that are already available come from the register file; - // operands produced by in-flight instructions are tagged with the - // ROB entry that will produce them. - if let Some(instr) = self.id_rs.read() { - if Self::is_branch(instr.instr.opcode) { - self.branch_stall = true; - } - let rob_tag = self.reorder_buffer.enqueue(&instr); - let rs1 = self.resolve_operands(instr.instr.rs1); - let rs2 = self.resolve_operands(instr.instr.rs2); - // Only update the RAT for instructions that actually write a result to rd. - // B-type and S-type encode immediates in the rd field — treating those - // bits as a destination register would corrupt the RAT. - if Self::writes_rd(instr.instr.format) && instr.instr.rd != 0 { - self.rat[instr.instr.rd as usize] = Some(rob_tag); - } - self.reservation_station.enqueue(rob_tag, rs1, rs2, &instr); + fn fetch(&mut self) -> IfId { + let pc = self.pc as usize; + let result = IfId { + pc: self.pc, + word: u32::from_le_bytes(self.mem[pc..pc + 4].try_into().unwrap()), + }; + self.pc += 4; + result } - // EX: dispatch a ready instruction to the ALU, then tick the ALU. - // On completion, broadcast the result on the CDB, waking any RS entries - // that were waiting for this tag. For non-load instructions the ALU result - // is final, so we also mark the ROB entry ready here. This means - // resolve_operands can observe the value in the very next cycle via - // is_tag_ready, rather than waiting two more stages. - // Loads are the exception: the ALU only computed the address; the actual - // loaded value comes from MEM and will update the ROB there instead. - self.reservation_station.dispatch(&mut self.alu); - if let Some(res) = self.alu.tick() { - if Self::is_branch(res.instr.opcode) { - self.pc = res.result; - self.branch_stall = false; - } - // Non-loads: the ALU result is the final value. Broadcast on the CDB - // and mark the ROB entry ready immediately. Loads must wait until MEM - // has the actual data — broadcasting the address here would give waiting - // instructions the wrong value. - if !Self::is_load(res.instr.opcode) { - self.reservation_station.update(res.rob_tag, res.result); - self.reorder_buffer.update(res.rob_tag, res.result); - } - self.alu_mem.stage(res); + fn is_branch(opcode: Opcode) -> bool { + matches!( + opcode, + Opcode::Beq + | Opcode::Bne + | Opcode::Blt + | Opcode::Bge + | Opcode::Bltu + | Opcode::Bgeu + | Opcode::Jal + | Opcode::Jalr + ) } - // MEM: for loads and stores the ALU computed the effective address. - // Here we perform the actual memory access. Non-memory instructions - // pass through unchanged (result forwarded as-is). - if let Some(instr) = self.alu_mem.read() { - let result = self.mem_stage(instr); - self.mem_rob_release.stage(result); + fn is_load(opcode: Opcode) -> bool { + matches!( + opcode, + Opcode::Lb | Opcode::Lh | Opcode::Lw | Opcode::Lbu | Opcode::Lhu + ) } - // WB: for loads, the loaded data is now the final result. Broadcast on - // the CDB (waking any RS entries that were waiting on this load's rd) - // and mark the ROB entry ready. The ROB update and RS broadcast are - // always paired so no waiting entry can be left behind. - if let Some(instr) = self.mem_rob_release.read() { - if Self::is_load(instr.instr.opcode) { - self.reservation_station.update(instr.rob_tag, instr.result); - self.reorder_buffer.update(instr.rob_tag, instr.result); - } + // Only R, I, U, J instructions write a meaningful value to rd. + // B-type and S-type encode immediates in bits[11:7], so that field must + // not be treated as a destination register. + fn writes_rd(format: crate::utils::isa::InstrFormat) -> bool { + use crate::utils::isa::InstrFormat::*; + matches!(format, R | I | U | J) } - // Commit: retire the head of the ROB if it is complete. Writing to the - // register file only happens here, ensuring results become visible in - // program order. The RAT entry is cleared only if it still points to - // this instruction (a later write to the same register may have already - // replaced it). - if let Some(instr) = self.reorder_buffer.release() { - let rd = instr.result.rd as usize; - if Self::writes_rd(instr.result.format) && rd != 0 { - if let Some(rat_tag) = self.rat[rd] { - if rat_tag == instr.rob_tag { self.rat[rd] = None; } + // Check the RAT to determine how to read a source register. + // If the RAT maps this register to a ROB tag, the value is still + // in-flight; the instruction must wait for that tag to be broadcast. + // Otherwise the register file holds the current committed value. + fn resolve_operands(&self, rs: u8) -> Operand { + match self.rat[rs as usize] { + Some(rob_tag) => match self.reorder_buffer.is_tag_ready(rob_tag) { + Some(val) => Operand::Ready(val), + None => Operand::Waiting(rob_tag), + }, + None => Operand::Ready(self.regs[rs as usize]), } - self.regs[rd] = instr.result.result; - } } - // Advance all latches: the staged value becomes the readable value - // for the next cycle. - self.if_id.update(); - self.id_rs.update(); - self.alu_mem.update(); - self.mem_rob_release.update(); - } - - fn fetch(&mut self) -> IfId { - let pc = self.pc as usize; - let result = IfId { pc: self.pc, word: u32::from_le_bytes(self.mem[pc..pc+4].try_into().unwrap()) }; - self.pc += 4; - result - } - - fn is_branch(opcode: Opcode) -> bool { - matches!(opcode, Opcode::Beq | Opcode::Bne | Opcode::Blt | Opcode::Bge | Opcode::Bltu | Opcode::Bgeu | Opcode::Jal | Opcode::Jalr) - } - - fn is_load(opcode: Opcode) -> bool { - matches!(opcode, Opcode::Lb | Opcode::Lh | Opcode::Lw | Opcode::Lbu | Opcode::Lhu) - } - - // Only R, I, U, J instructions write a meaningful value to rd. - // B-type and S-type encode immediates in bits[11:7], so that field must - // not be treated as a destination register. - fn writes_rd(format: crate::isa::InstrFormat) -> bool { - use crate::isa::InstrFormat::*; - matches!(format, R | I | U | J) - } - - // Check the RAT to determine how to read a source register. - // If the RAT maps this register to a ROB tag, the value is still - // in-flight; the instruction must wait for that tag to be broadcast. - // Otherwise the register file holds the current committed value. - fn resolve_operands(&self, rs: u8) -> Operand { - match self.rat[rs as usize] { - Some(rob_tag) => match self.reorder_buffer.is_tag_ready(rob_tag) { - Some(val) => Operand::Ready(val), - None => Operand::Waiting(rob_tag), - } - None => Operand::Ready(self.regs[rs as usize]), - } - } - - // Perform the memory access for load/store instructions. - // The ALU has already computed the effective address and stored it in - // `result`. For stores, rs2 must be Ready by the time we reach this stage. - // Non-memory instructions pass their ALU result through unchanged. - fn mem_stage(&mut self, instr: ResultFormat) -> ResultFormat { - use crate::isa::Opcode::*; - use crate::simple_ooo::reservation_stations::Operand::Ready; - - let addr = instr.result as usize; - - let result = match instr.instr.opcode { - Lb => (self.mem[addr] as i8) as u32, - Lbu => self.mem[addr] as u32, - Lh => (u16::from_le_bytes(self.mem[addr..addr+2].try_into().unwrap()) as i16) as u32, - Lhu => u16::from_le_bytes(self.mem[addr..addr+2].try_into().unwrap()) as u32, - Lw => u32::from_le_bytes(self.mem[addr..addr+4].try_into().unwrap()), - - Sb | Sh | Sw => { - let Ready(data) = instr.instr.rs2 else { panic!("store data not ready at mem stage") }; - match instr.instr.opcode { - Sb => self.mem[addr] = (data & 0xff) as u8, - Sh => self.mem[addr..addr+2].copy_from_slice(&(data as u16).to_le_bytes()), - Sw => self.mem[addr..addr+4].copy_from_slice(&data.to_le_bytes()), - _ => unreachable!(), + // Perform the memory access for load/store instructions. + // The ALU has already computed the effective address and stored it in + // `result`. For stores, rs2 must be Ready by the time we reach this stage. + // Non-memory instructions pass their ALU result through unchanged. + fn mem_stage(&mut self, instr: ResultFormat) -> ResultFormat { + use crate::simple_ooo::reservation_stations::Operand::Ready; + use crate::utils::isa::Opcode::*; + + let addr = instr.result as usize; + + let result = match instr.instr.opcode { + Lb => (self.mem[addr] as i8) as u32, + Lbu => self.mem[addr] as u32, + Lh => (u16::from_le_bytes(self.mem[addr..addr + 2].try_into().unwrap()) as i16) as u32, + Lhu => u16::from_le_bytes(self.mem[addr..addr + 2].try_into().unwrap()) as u32, + Lw => u32::from_le_bytes(self.mem[addr..addr + 4].try_into().unwrap()), + + Sb | Sh | Sw => { + let Ready(data) = instr.instr.rs2 else { + panic!("store data not ready at mem stage") + }; + match instr.instr.opcode { + Sb => self.mem[addr] = (data & 0xff) as u8, + Sh => self.mem[addr..addr + 2].copy_from_slice(&(data as u16).to_le_bytes()), + Sw => self.mem[addr..addr + 4].copy_from_slice(&data.to_le_bytes()), + _ => unreachable!(), + } + 0 + } + + _ => instr.result, + }; + + ResultFormat { + rob_tag: instr.rob_tag, + instr: instr.instr, + result, } - 0 - } - - _ => instr.result, - }; - - ResultFormat { rob_tag: instr.rob_tag, instr: instr.instr, result } - } - + } } impl Diagnosable for SimpleOOO { - fn diagnose(&self) -> DiagNode { - let stall = self.reservation_station.stall() || self.reorder_buffer.stall() || self.branch_stall; - - // RAT: only show registers that have a pending mapping - let rat_entries: Vec = self.rat.iter().enumerate() - .filter_map(|(r, slot)| slot.map(|tag| DiagNode::leaf(format!("x{r}"), format!("rob[{tag}]")))) - .collect(); - let rat_node = if rat_entries.is_empty() { - DiagNode::leaf("rat", "empty") - } else { - DiagNode::inner("rat", rat_entries) - }; - - DiagNode::inner("simple_ooo", vec![ - DiagNode::leaf("pc", format!("0x{:08x}", self.pc)), - DiagNode::leaf("stall", stall.to_string()), - DiagNode::leaf("branch_stall", self.branch_stall.to_string()), - DiagNode::leaf("if_id", format!("{:?}", self.if_id.peek())), - DiagNode::leaf("id_rs", format!("{:?}", self.id_rs.peek())), - DiagNode::leaf("alu_mem", format!("{:?}", self.alu_mem.peek())), - DiagNode::leaf("mem_rob_release", format!("{:?}", self.mem_rob_release.peek())), - rat_node, - self.alu.diagnose(), - self.reservation_station.diagnose(), - self.reorder_buffer.diagnose(), - ]) - } + fn diagnose(&self) -> DiagNode { + let stall = + self.reservation_station.stall() || self.reorder_buffer.stall() || self.branch_stall; + + // RAT: only show registers that have a pending mapping + let rat_entries: Vec = self + .rat + .iter() + .enumerate() + .filter_map(|(r, slot)| { + slot.map(|tag| DiagNode::leaf(format!("x{r}"), format!("rob[{tag}]"))) + }) + .collect(); + let rat_node = if rat_entries.is_empty() { + DiagNode::leaf("rat", "empty") + } else { + DiagNode::inner("rat", rat_entries) + }; + + DiagNode::inner( + "simple_ooo", + vec![ + DiagNode::leaf("pc", format!("0x{:08x}", self.pc)), + DiagNode::leaf("stall", stall.to_string()), + DiagNode::leaf("branch_stall", self.branch_stall.to_string()), + DiagNode::leaf("if_id", format!("{:?}", self.if_id.peek())), + DiagNode::leaf("id_rs", format!("{:?}", self.id_rs.peek())), + DiagNode::leaf("alu_mem", format!("{:?}", self.alu_mem.peek())), + DiagNode::leaf( + "mem_rob_release", + format!("{:?}", self.mem_rob_release.peek()), + ), + rat_node, + self.alu.diagnose(), + self.reservation_station.diagnose(), + self.reorder_buffer.diagnose(), + ], + ) + } } pub fn build_default() -> SimpleOOO { - SimpleOOO { - mem: vec![0u8; 256], - regs: [0u32; 32], - rat: [None; 32], - pc: 0, - branch_stall: false, - - if_id: Latch::new(), - id_rs: Latch::new(), - - reservation_station: Box::new(SimpleReservationStation::new(8)), - reorder_buffer: Box::new(SimpleReorderBuffer::new(16)), - - alu: OOOALU::new(vec![ - AluPipe::new(vec![FuncUnitKind::IntAlu]), - AluPipe::new(vec![FuncUnitKind::LoadStore]), - AluPipe::new(vec![FuncUnitKind::Branch]), - ]), - - alu_mem: Latch::new(), - mem_rob_release: Latch::new(), - } + SimpleOOO { + mem: vec![0u8; 256], + regs: [0u32; 32], + rat: [None; 32], + pc: 0, + branch_stall: false, + + if_id: Latch::new(), + id_rs: Latch::new(), + + reservation_station: Box::new(SimpleReservationStation::new(8)), + reorder_buffer: Box::new(SimpleReorderBuffer::new(16)), + + alu: OOOALU::new(vec![ + AluPipe::new(vec![FuncUnitKind::IntAlu]), + AluPipe::new(vec![FuncUnitKind::LoadStore]), + AluPipe::new(vec![FuncUnitKind::Branch]), + ]), + + alu_mem: Latch::new(), + mem_rob_release: Latch::new(), + } } diff --git a/src/simple_ooo/reorder_buffer.rs b/src/simple_ooo/reorder_buffer.rs index 427c145..05cc1e0 100644 --- a/src/simple_ooo/reorder_buffer.rs +++ b/src/simple_ooo/reorder_buffer.rs @@ -1,78 +1,104 @@ -use crate::circular_buffer::CircularBuffer; -use crate::diag::{DiagNode, Diagnosable}; -use crate::isa::{InstResult, InstrFormat}; use crate::simple_ooo::{IdRs, ReorderBuffer, RobTag, TaggedResult}; - +use crate::utils::circular_buffer::CircularBuffer; +use crate::utils::diag::{DiagNode, Diagnosable}; +use crate::utils::isa::{InstResult, InstrFormat}; struct SROBEntry { - _pc: u32, - rd: u8, - format: InstrFormat, - result: Option, - ready: bool, + _pc: u32, + rd: u8, + format: InstrFormat, + result: Option, + ready: bool, } pub struct SimpleReorderBuffer { - buffer: CircularBuffer, + buffer: CircularBuffer, } impl SimpleReorderBuffer { - pub fn new(capacity: usize) -> Self { - Self { buffer: CircularBuffer::new(capacity) } - } + pub fn new(capacity: usize) -> Self { + Self { + buffer: CircularBuffer::new(capacity), + } + } } impl Diagnosable for SimpleReorderBuffer { - fn diagnose(&self) -> DiagNode { - let head = self.buffer.head_tag(); - let mut children = vec![ - DiagNode::leaf("size", format!("{}/{}", self.buffer.len(), self.buffer.capacity())), - DiagNode::leaf("head", format!("rob[{head}]")), - ]; - for (tag, e) in self.buffer.iter_tagged() { - let marker = if tag == head { " ← head" } else { "" }; - let val = if e.ready { - format!("rd=x{} result=0x{:08x} ready{}", e.rd, e.result.unwrap(), marker) - } else { - format!("rd=x{} pending{}", e.rd, marker) - }; - children.push(DiagNode::leaf(format!("rob[{tag}]"), val)); + fn diagnose(&self) -> DiagNode { + let head = self.buffer.head_tag(); + let mut children = vec![ + DiagNode::leaf( + "size", + format!("{}/{}", self.buffer.len(), self.buffer.capacity()), + ), + DiagNode::leaf("head", format!("rob[{head}]")), + ]; + for (tag, e) in self.buffer.iter_tagged() { + let marker = if tag == head { " ← head" } else { "" }; + let val = if e.ready { + format!( + "rd=x{} result=0x{:08x} ready{}", + e.rd, + e.result.unwrap(), + marker + ) + } else { + format!("rd=x{} pending{}", e.rd, marker) + }; + children.push(DiagNode::leaf(format!("rob[{tag}]"), val)); + } + DiagNode::inner("reorder_buffer", children) } - DiagNode::inner("reorder_buffer", children) - } } impl ReorderBuffer for SimpleReorderBuffer { - fn stall(&self) -> bool { self.buffer.is_full() } - - fn enqueue(&mut self, instr: &IdRs) -> usize { - if self.stall() { - panic!("ROB full.") + fn stall(&self) -> bool { + self.buffer.is_full() } - self.buffer.push(SROBEntry { _pc: instr.pc, rd: instr.instr.rd, format: instr.instr.format, ready: false, result: None }) - } + fn enqueue(&mut self, instr: &IdRs) -> usize { + if self.stall() { + panic!("ROB full.") + } - fn update(&mut self, rob_tag: RobTag, result: u32) { - let entry = self.buffer.access_by_tag(rob_tag); - entry.result = Some(result); - entry.ready = true; - } + self.buffer.push(SROBEntry { + _pc: instr.pc, + rd: instr.instr.rd, + format: instr.instr.format, + ready: false, + result: None, + }) + } - fn release(&mut self) -> Option { - if self.buffer.is_empty() { - return None; + fn update(&mut self, rob_tag: RobTag, result: u32) { + let entry = self.buffer.access_by_tag(rob_tag); + entry.result = Some(result); + entry.ready = true; } - if !self.buffer.head().ready { - return None; + + fn release(&mut self) -> Option { + if self.buffer.is_empty() { + return None; + } + if !self.buffer.head().ready { + return None; + } + let rob_tag = self.buffer.head_tag(); + let rob_entry = self.buffer.pop(); + let result = rob_entry + .result + .expect("ROB entry marked ready without a result"); + Some(TaggedResult { + rob_tag, + result: InstResult { + rd: rob_entry.rd, + format: rob_entry.format, + result, + }, + }) } - let rob_tag = self.buffer.head_tag(); - let rob_entry = self.buffer.pop(); - let result = rob_entry.result.expect("ROB entry marked ready without a result"); - Some(TaggedResult { rob_tag, result: InstResult { rd: rob_entry.rd, format: rob_entry.format, result } }) - } - fn is_tag_ready(&self, tag: RobTag) -> Option { - self.buffer.read_by_tag(tag).result - } -} \ No newline at end of file + fn is_tag_ready(&self, tag: RobTag) -> Option { + self.buffer.read_by_tag(tag).result + } +} diff --git a/src/simple_ooo/reservation_stations.rs b/src/simple_ooo/reservation_stations.rs index 24d6b7c..c117543 100644 --- a/src/simple_ooo/reservation_stations.rs +++ b/src/simple_ooo/reservation_stations.rs @@ -1,103 +1,118 @@ -use crate::diag::{DiagNode, Diagnosable}; -use crate::isa::Opcode; use crate::simple_ooo::{IdRs, InstrFormat, OOOALU, ReservationStation}; +use crate::utils::diag::{DiagNode, Diagnosable}; +use crate::utils::isa::Opcode; #[derive(Debug, Clone, Copy)] pub enum Operand { - Ready(u32), - Waiting(usize), + Ready(u32), + Waiting(usize), } #[derive(Debug, Clone, Copy)] pub struct SREntry { - rob_tag: usize, - pub opcode: Opcode, - pub pc: u32, - pub imm: i32, - pub rs1: Operand, - pub rs2: Operand, - ready: bool, + rob_tag: usize, + pub opcode: Opcode, + pub pc: u32, + pub imm: i32, + pub rs1: Operand, + pub rs2: Operand, + ready: bool, } // Dispatches at most one ready instruction per cycle. pub struct SimpleReservationStation { - num_stations: usize, - station_entries: Vec, + num_stations: usize, + station_entries: Vec, } impl SimpleReservationStation { - pub fn new(num_stations: usize) -> Self { - Self { num_stations, station_entries: vec![] } - } + pub fn new(num_stations: usize) -> Self { + Self { + num_stations, + station_entries: vec![], + } + } } impl Diagnosable for SimpleReservationStation { - fn diagnose(&self) -> DiagNode { - let mut children = vec![ - DiagNode::leaf("capacity", format!("{}/{}", self.station_entries.len(), self.num_stations)), - ]; - for e in self.station_entries.iter() { - let rs1 = match e.rs1 { - Operand::Ready(v) => format!("0x{v:08x}"), - Operand::Waiting(t) => format!("waiting(rob[{t}])"), - }; - let rs2 = match e.rs2 { - Operand::Ready(v) => format!("0x{v:08x}"), - Operand::Waiting(t) => format!("waiting(rob[{t}])"), - }; - let status = if e.ready { "ready" } else { "blocked" }; - children.push(DiagNode::leaf( - format!("rob[{}]", e.rob_tag), - format!("{:?} rs1={} rs2={} {}", e.opcode, rs1, rs2, status), - )); + fn diagnose(&self) -> DiagNode { + let mut children = vec![DiagNode::leaf( + "capacity", + format!("{}/{}", self.station_entries.len(), self.num_stations), + )]; + for e in self.station_entries.iter() { + let rs1 = match e.rs1 { + Operand::Ready(v) => format!("0x{v:08x}"), + Operand::Waiting(t) => format!("waiting(rob[{t}])"), + }; + let rs2 = match e.rs2 { + Operand::Ready(v) => format!("0x{v:08x}"), + Operand::Waiting(t) => format!("waiting(rob[{t}])"), + }; + let status = if e.ready { "ready" } else { "blocked" }; + children.push(DiagNode::leaf( + format!("rob[{}]", e.rob_tag), + format!("{:?} rs1={} rs2={} {}", e.opcode, rs1, rs2, status), + )); + } + DiagNode::inner("reservation_station", children) } - DiagNode::inner("reservation_station", children) - } } impl ReservationStation for SimpleReservationStation { - fn stall(&self) -> bool { - self.station_entries.len() >= self.num_stations - } - - fn enqueue(&mut self, rob_tag: usize, rs1: Operand, rs2: Operand, instr: &IdRs) { - if self.stall() { - panic!("Reservation station is full."); + fn stall(&self) -> bool { + self.station_entries.len() >= self.num_stations } - let ready = matches!(rs1, Operand::Ready(_)) && matches!(rs2, Operand::Ready(_)); - self.station_entries.push(SREntry { - rob_tag, - opcode: instr.instr.opcode, - pc: instr.pc as u32, - imm: instr.instr.imm, - rs1, - rs2, - ready, - }); - } + fn enqueue(&mut self, rob_tag: usize, rs1: Operand, rs2: Operand, instr: &IdRs) { + if self.stall() { + panic!("Reservation station is full."); + } + + let ready = matches!(rs1, Operand::Ready(_)) && matches!(rs2, Operand::Ready(_)); + self.station_entries.push(SREntry { + rob_tag, + opcode: instr.instr.opcode, + pc: instr.pc as u32, + imm: instr.instr.imm, + rs1, + rs2, + ready, + }); + } - fn update(&mut self, rob_tag: usize, val: u32) { - for entry in &mut self.station_entries { - if let Operand::Waiting(t) = entry.rs1 { if t == rob_tag { entry.rs1 = Operand::Ready(val); } } - if let Operand::Waiting(t) = entry.rs2 { if t == rob_tag { entry.rs2 = Operand::Ready(val); } } - if matches!(entry.rs1, Operand::Ready(_)) && matches!(entry.rs2, Operand::Ready(_)) { - entry.ready = true; - } + fn update(&mut self, rob_tag: usize, val: u32) { + for entry in &mut self.station_entries { + if let Operand::Waiting(t) = entry.rs1 { + if t == rob_tag { + entry.rs1 = Operand::Ready(val); + } + } + if let Operand::Waiting(t) = entry.rs2 { + if t == rob_tag { + entry.rs2 = Operand::Ready(val); + } + } + if matches!(entry.rs1, Operand::Ready(_)) && matches!(entry.rs2, Operand::Ready(_)) { + entry.ready = true; + } + } } - } - fn dispatch(&mut self, alu: &mut OOOALU) { - for i in 0..self.station_entries.len() { - if self.station_entries[i].ready { - let entry = self.station_entries.remove(i); - let instr = InstrFormat { rob_tag: entry.rob_tag, instr: entry }; - if alu.try_enqueue(instr) { - return; - } else { - self.station_entries.insert(0, entry); + fn dispatch(&mut self, alu: &mut OOOALU) { + for i in 0..self.station_entries.len() { + if self.station_entries[i].ready { + let entry = self.station_entries.remove(i); + let instr = InstrFormat { + rob_tag: entry.rob_tag, + instr: entry, + }; + if alu.try_enqueue(instr) { + return; + } else { + self.station_entries.insert(0, entry); + } + } } - } } - } -} \ No newline at end of file +} diff --git a/src/utils/assembler.rs b/src/utils/assembler.rs new file mode 100644 index 0000000..7843e45 --- /dev/null +++ b/src/utils/assembler.rs @@ -0,0 +1,496 @@ +use std::collections::HashMap; + +#[derive(Debug)] +pub enum AsmError { + UnknownInstruction(String), + UnknownRegister(String), + BadOperands(String), + UndefinedLabel(String), + ImmediateOutOfRange(String), +} + +impl std::fmt::Display for AsmError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AsmError::UnknownInstruction(s) => write!(f, "unknown instruction: {s}"), + AsmError::UnknownRegister(s) => write!(f, "unknown register: {s}"), + AsmError::BadOperands(s) => write!(f, "bad operands: {s}"), + AsmError::UndefinedLabel(s) => write!(f, "undefined label: {s}"), + AsmError::ImmediateOutOfRange(s) => write!(f, "immediate out of range: {s}"), + } + } +} + +pub fn assemble(source: &str) -> Result, AsmError> { + // Two passes: first collect labels, then emit. + let lines = clean(source); + let labels = collect_labels(&lines)?; + emit(&lines, &labels) +} + +// ── Pass 1: strip comments, blank lines, record label positions ─────────────── + +fn clean(source: &str) -> Vec<(usize, String)> { + let mut out = Vec::new(); + let mut addr = 0usize; + for raw in source.lines() { + let line = raw.split('#').next().unwrap().trim().to_string(); + if line.is_empty() { + continue; + } + if line.ends_with(':') { + out.push((addr, line)); + continue; + } + out.push((addr, line)); + addr += 4; + } + out +} + +fn collect_labels(lines: &[(usize, String)]) -> Result, AsmError> { + let mut labels = HashMap::new(); + for (addr, line) in lines { + if let Some(label) = line.strip_suffix(':') { + labels.insert(label.trim().to_string(), *addr as u32); + } + } + Ok(labels) +} + +// ── Pass 2: emit instruction words ─────────────────────────────────────────── + +fn emit(lines: &[(usize, String)], labels: &HashMap) -> Result, AsmError> { + let mut out = Vec::new(); + for (addr, line) in lines { + if line.ends_with(':') { + continue; + } + let word = assemble_line(line, *addr as u32, labels)?; + out.push(word); + } + Ok(out) +} + +fn assemble_line(line: &str, pc: u32, labels: &HashMap) -> Result { + let (mnemonic, rest) = line + .split_once(char::is_whitespace) + .map(|(m, r)| (m, r.trim())) + .unwrap_or((line, "")); + + // Expand pseudo-instructions first + match mnemonic { + "nop" => return Ok(0x00000013), // addi x0, x0, 0 + "mv" => { + let (rd, rs) = reg2(rest)?; + return Ok(i_type(rd, rs, 0, 0x0, 0b0010011)); + } + "li" => { + let (rd, imm) = reg_imm(rest, pc, labels)?; + return Ok(i_type(rd, 0, imm, 0x0, 0b0010011)); + } + "j" => { + let off = resolve(rest, pc, labels)?; + return Ok(j_type(0, off)); + } + "jr" => { + let rs = reg(rest)?; + return Ok(i_type(0, rs, 0, 0x0, 0b1100111)); + } + "ret" => return Ok(i_type(0, 1, 0, 0x0, 0b1100111)), // jalr x0, x1, 0 + "not" => { + let (rd, rs) = reg2(rest)?; + return Ok(i_type(rd, rs, -1, 0x4, 0b0010011)); + } // xori rd, rs, -1 + "neg" => { + let (rd, rs) = reg2(rest)?; + return Ok(r_type(rd, 0, rs, 0x0, 0x20, 0b0110011)); + } // sub rd, x0, rs + _ => {} + } + + match mnemonic { + // R-type + "add" => { + let (rd, rs1, rs2) = reg3(rest)?; + Ok(r_type(rd, rs1, rs2, 0x0, 0x00, 0b0110011)) + } + "sub" => { + let (rd, rs1, rs2) = reg3(rest)?; + Ok(r_type(rd, rs1, rs2, 0x0, 0x20, 0b0110011)) + } + "and" => { + let (rd, rs1, rs2) = reg3(rest)?; + Ok(r_type(rd, rs1, rs2, 0x7, 0x00, 0b0110011)) + } + "or" => { + let (rd, rs1, rs2) = reg3(rest)?; + Ok(r_type(rd, rs1, rs2, 0x6, 0x00, 0b0110011)) + } + "xor" => { + let (rd, rs1, rs2) = reg3(rest)?; + Ok(r_type(rd, rs1, rs2, 0x4, 0x00, 0b0110011)) + } + "sll" => { + let (rd, rs1, rs2) = reg3(rest)?; + Ok(r_type(rd, rs1, rs2, 0x1, 0x00, 0b0110011)) + } + "srl" => { + let (rd, rs1, rs2) = reg3(rest)?; + Ok(r_type(rd, rs1, rs2, 0x5, 0x00, 0b0110011)) + } + "sra" => { + let (rd, rs1, rs2) = reg3(rest)?; + Ok(r_type(rd, rs1, rs2, 0x5, 0x20, 0b0110011)) + } + "slt" => { + let (rd, rs1, rs2) = reg3(rest)?; + Ok(r_type(rd, rs1, rs2, 0x2, 0x00, 0b0110011)) + } + "sltu" => { + let (rd, rs1, rs2) = reg3(rest)?; + Ok(r_type(rd, rs1, rs2, 0x3, 0x00, 0b0110011)) + } + + // I-type ALU + "addi" => { + let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; + Ok(i_type(rd, rs1, imm, 0x0, 0b0010011)) + } + "andi" => { + let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; + Ok(i_type(rd, rs1, imm, 0x7, 0b0010011)) + } + "ori" => { + let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; + Ok(i_type(rd, rs1, imm, 0x6, 0b0010011)) + } + "xori" => { + let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; + Ok(i_type(rd, rs1, imm, 0x4, 0b0010011)) + } + "slli" => { + let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; + Ok(i_type(rd, rs1, imm, 0x1, 0b0010011)) + } + "srli" => { + let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; + Ok(i_type(rd, rs1, imm, 0x5, 0b0010011)) + } + "srai" => { + let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; + Ok(i_type(rd, rs1, imm | (0x20 << 5), 0x5, 0b0010011)) + } + "slti" => { + let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; + Ok(i_type(rd, rs1, imm, 0x2, 0b0010011)) + } + "sltiu" => { + let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; + Ok(i_type(rd, rs1, imm, 0x3, 0b0010011)) + } + + // Loads + "lb" => { + let (rd, rs1, imm) = reg_mem(rest)?; + Ok(i_type(rd, rs1, imm, 0x0, 0b0000011)) + } + "lh" => { + let (rd, rs1, imm) = reg_mem(rest)?; + Ok(i_type(rd, rs1, imm, 0x1, 0b0000011)) + } + "lw" => { + let (rd, rs1, imm) = reg_mem(rest)?; + Ok(i_type(rd, rs1, imm, 0x2, 0b0000011)) + } + "lbu" => { + let (rd, rs1, imm) = reg_mem(rest)?; + Ok(i_type(rd, rs1, imm, 0x4, 0b0000011)) + } + "lhu" => { + let (rd, rs1, imm) = reg_mem(rest)?; + Ok(i_type(rd, rs1, imm, 0x5, 0b0000011)) + } + + // Stores + "sb" => { + let (rs1, rs2, imm) = reg_mem_store(rest)?; + Ok(s_type(rs1, rs2, imm, 0x0)) + } + "sh" => { + let (rs1, rs2, imm) = reg_mem_store(rest)?; + Ok(s_type(rs1, rs2, imm, 0x1)) + } + "sw" => { + let (rs1, rs2, imm) = reg_mem_store(rest)?; + Ok(s_type(rs1, rs2, imm, 0x2)) + } + + // Branches + "beq" => { + let (rs1, rs2, off) = reg_reg_label(rest, pc, labels)?; + Ok(b_type(rs1, rs2, off, 0x0)) + } + "bne" => { + let (rs1, rs2, off) = reg_reg_label(rest, pc, labels)?; + Ok(b_type(rs1, rs2, off, 0x1)) + } + "blt" => { + let (rs1, rs2, off) = reg_reg_label(rest, pc, labels)?; + Ok(b_type(rs1, rs2, off, 0x4)) + } + "bge" => { + let (rs1, rs2, off) = reg_reg_label(rest, pc, labels)?; + Ok(b_type(rs1, rs2, off, 0x5)) + } + "bltu" => { + let (rs1, rs2, off) = reg_reg_label(rest, pc, labels)?; + Ok(b_type(rs1, rs2, off, 0x6)) + } + "bgeu" => { + let (rs1, rs2, off) = reg_reg_label(rest, pc, labels)?; + Ok(b_type(rs1, rs2, off, 0x7)) + } + + // Jumps + "jal" => { + let (rd, off) = reg_label(rest, pc, labels)?; + Ok(j_type(rd, off)) + } + "jalr" => { + let (rd, rs1, imm) = reg_reg_imm(rest, pc, labels)?; + Ok(i_type(rd, rs1, imm, 0x0, 0b1100111)) + } + + // Upper immediate + "lui" => { + let (rd, imm) = reg_imm(rest, pc, labels)?; + Ok(u_type(rd, imm, 0b0110111)) + } + "auipc" => { + let (rd, imm) = reg_imm(rest, pc, labels)?; + Ok(u_type(rd, imm, 0b0010111)) + } + + other => Err(AsmError::UnknownInstruction(other.to_string())), + } +} + +// ── Encoding helpers ────────────────────────────────────────────────────────── + +fn r_type(rd: u8, rs1: u8, rs2: u8, funct3: u32, funct7: u32, opcode: u32) -> u32 { + opcode + | ((rd as u32) << 7) + | (funct3 << 12) + | ((rs1 as u32) << 15) + | ((rs2 as u32) << 20) + | (funct7 << 25) +} + +fn i_type(rd: u8, rs1: u8, imm: i32, funct3: u32, opcode: u32) -> u32 { + opcode + | ((rd as u32) << 7) + | (funct3 << 12) + | ((rs1 as u32) << 15) + | (((imm as u32) & 0xfff) << 20) +} + +fn s_type(rs1: u8, rs2: u8, imm: i32, funct3: u32) -> u32 { + let imm = imm as u32; + 0b0100011 + | (funct3 << 12) + | ((rs1 as u32) << 15) + | ((rs2 as u32) << 20) + | ((imm & 0x1f) << 7) + | ((imm >> 5) << 25) +} + +fn b_type(rs1: u8, rs2: u8, imm: i32, funct3: u32) -> u32 { + let imm = imm as u32; + 0b1100011 + | (funct3 << 12) + | ((rs1 as u32) << 15) + | ((rs2 as u32) << 20) + | (((imm >> 11) & 1) << 7) + | (((imm >> 1) & 0xf) << 8) + | (((imm >> 5) & 0x3f) << 25) + | (((imm >> 12) & 1) << 31) +} + +fn u_type(rd: u8, imm: i32, opcode: u32) -> u32 { + opcode | ((rd as u32) << 7) | ((imm as u32) & 0xfffff000) +} + +fn j_type(rd: u8, imm: i32) -> u32 { + let imm = imm as u32; + 0b1101111 + | ((rd as u32) << 7) + | (imm & 0xff000) + | (((imm >> 11) & 1) << 20) + | (((imm >> 1) & 0x3ff) << 21) + | (((imm >> 20) & 1) << 31) +} + +// ── Operand parsers ─────────────────────────────────────────────────────────── + +fn reg(s: &str) -> Result { + let s = s.trim(); + // ABI names + let abi = match s { + "zero" => Some(0), + "ra" => Some(1), + "sp" => Some(2), + "gp" => Some(3), + "tp" => Some(4), + "t0" => Some(5), + "t1" => Some(6), + "t2" => Some(7), + "s0" | "fp" => Some(8), + "s1" => Some(9), + "a0" => Some(10), + "a1" => Some(11), + "a2" => Some(12), + "a3" => Some(13), + "a4" => Some(14), + "a5" => Some(15), + "a6" => Some(16), + "a7" => Some(17), + "s2" => Some(18), + "s3" => Some(19), + "s4" => Some(20), + "s5" => Some(21), + "s6" => Some(22), + "s7" => Some(23), + "s8" => Some(24), + "s9" => Some(25), + "s10" => Some(26), + "s11" => Some(27), + "t3" => Some(28), + "t4" => Some(29), + "t5" => Some(30), + "t6" => Some(31), + _ => None, + }; + if let Some(n) = abi { + return Ok(n); + } + + if let Some(n) = s.strip_prefix('x') { + n.parse::() + .map_err(|_| AsmError::UnknownRegister(s.to_string())) + } else { + Err(AsmError::UnknownRegister(s.to_string())) + } +} + +fn split_ops(s: &str) -> Vec<&str> { + s.splitn(4, ',').map(|p| p.trim()).collect() +} + +fn reg2(s: &str) -> Result<(u8, u8), AsmError> { + let ops = split_ops(s); + if ops.len() < 2 { + return Err(AsmError::BadOperands(s.to_string())); + } + Ok((reg(ops[0])?, reg(ops[1])?)) +} + +fn reg3(s: &str) -> Result<(u8, u8, u8), AsmError> { + let ops = split_ops(s); + if ops.len() < 3 { + return Err(AsmError::BadOperands(s.to_string())); + } + Ok((reg(ops[0])?, reg(ops[1])?, reg(ops[2])?)) +} + +fn reg_imm(s: &str, pc: u32, labels: &HashMap) -> Result<(u8, i32), AsmError> { + let ops = split_ops(s); + if ops.len() < 2 { + return Err(AsmError::BadOperands(s.to_string())); + } + Ok((reg(ops[0])?, resolve(ops[1], pc, labels)?)) +} + +fn reg_reg_imm(s: &str, pc: u32, labels: &HashMap) -> Result<(u8, u8, i32), AsmError> { + let ops = split_ops(s); + if ops.len() < 3 { + return Err(AsmError::BadOperands(s.to_string())); + } + Ok((reg(ops[0])?, reg(ops[1])?, resolve(ops[2], pc, labels)?)) +} + +fn reg_reg_label( + s: &str, + pc: u32, + labels: &HashMap, +) -> Result<(u8, u8, i32), AsmError> { + let ops = split_ops(s); + if ops.len() < 3 { + return Err(AsmError::BadOperands(s.to_string())); + } + let target = resolve(ops[2], pc, labels)?; + Ok((reg(ops[0])?, reg(ops[1])?, target)) +} + +fn reg_label(s: &str, pc: u32, labels: &HashMap) -> Result<(u8, i32), AsmError> { + let ops = split_ops(s); + if ops.len() < 2 { + return Err(AsmError::BadOperands(s.to_string())); + } + Ok((reg(ops[0])?, resolve(ops[1], pc, labels)?)) +} + +/// Parse `offset(base)` into (rd, base_reg, offset) +fn reg_mem(s: &str) -> Result<(u8, u8, i32), AsmError> { + let comma = s + .find(',') + .ok_or_else(|| AsmError::BadOperands(s.to_string()))?; + let rd = reg(s[..comma].trim())?; + let rest = s[comma + 1..].trim(); + let (off_str, base_str) = parse_mem_operand(rest)?; + Ok((rd, reg(base_str)?, off_str)) +} + +/// Parse `rs2, offset(base)` for stores — returns (base, rs2, offset) +fn reg_mem_store(s: &str) -> Result<(u8, u8, i32), AsmError> { + let comma = s + .find(',') + .ok_or_else(|| AsmError::BadOperands(s.to_string()))?; + let rs2 = reg(s[..comma].trim())?; + let rest = s[comma + 1..].trim(); + let (off_str, base_str) = parse_mem_operand(rest)?; + Ok((reg(base_str)?, rs2, off_str)) +} + +fn parse_mem_operand(s: &str) -> Result<(i32, &str), AsmError> { + let lparen = s + .find('(') + .ok_or_else(|| AsmError::BadOperands(s.to_string()))?; + let rparen = s + .find(')') + .ok_or_else(|| AsmError::BadOperands(s.to_string()))?; + let offset = s[..lparen].trim().parse::().unwrap_or(0); + let base = &s[lparen + 1..rparen]; + Ok((offset, base)) +} + +fn resolve(s: &str, pc: u32, labels: &HashMap) -> Result { + let s = s.trim(); + if let Ok(n) = s.parse::() { + return Ok(n); + } + if let Some(n) = parse_hex(s) { + return Ok(n); + } + // Label — return PC-relative offset + labels + .get(s) + .map(|&addr| (addr as i32) - (pc as i32)) + .ok_or_else(|| AsmError::UndefinedLabel(s.to_string())) +} + +fn parse_hex(s: &str) -> Option { + s.strip_prefix("0x") + .or_else(|| s.strip_prefix("0X")) + .and_then(|h| i32::from_str_radix(h, 16).ok()) +} diff --git a/src/utils/circular_buffer.rs b/src/utils/circular_buffer.rs new file mode 100644 index 0000000..72df441 --- /dev/null +++ b/src/utils/circular_buffer.rs @@ -0,0 +1,90 @@ +pub struct CircularBuffer { + head: usize, + tail: usize, + size: usize, + data: Vec>, +} + +impl CircularBuffer { + pub fn new(capacity: usize) -> Self { + Self { + head: 0, + tail: 0, + size: 0, + data: (0..capacity).map(|_| None).collect(), + } + } + + pub fn capacity(&self) -> usize { + self.data.len() + } + + pub fn is_full(&self) -> bool { + self.size == self.data.len() + } + + pub fn is_empty(&self) -> bool { + self.size == 0 + } + + pub fn push(&mut self, item: T) -> usize { + if self.is_full() { + panic!("Trying to push to a full circular buffer."); + } + let idx = self.tail; + self.data[idx] = Some(item); + self.tail = (self.tail + 1) % self.data.len(); + self.size += 1; + idx + } + + pub fn pop(&mut self) -> T { + if self.is_empty() { + panic!("Trying to pop from an empty circular buffer"); + } + let elem = self.data[self.head].take().unwrap(); + self.head = (self.head + 1) % self.data.len(); + self.size -= 1; + elem + } + + pub fn head_tag(&self) -> usize { + return self.head; + } + + pub fn head(&self) -> &T { + self.data[self.head].as_ref().unwrap() + } + + pub fn read_by_tag(&self, tag: usize) -> &T { + self.data[tag] + .as_ref() + .expect("Invalid access in circular buffer") + } + + pub fn access_by_tag(&mut self, tag: usize) -> &mut T { + self.data[tag] + .as_mut() + .expect("Invalid access in circular buffer") + } + + pub fn len(&self) -> usize { + self.size + } + + pub fn iter(&self) -> impl Iterator { + let n = self.data.len(); + let head = self.head; + (0..self.size).map(move |i| self.data[(head + i) % n].as_ref().unwrap()) + } + + // Yields (tag, entry) pairs where tag is the physical slot index used as rob tag. + pub fn iter_tagged(&self) -> impl Iterator { + let n = self.data.len(); + let head = self.head; + (0..self.size).map(move |i| { + let tag = (head + i) % n; + (tag, self.data[tag].as_ref().unwrap()) + }) + } +} diff --git a/src/utils/diag.rs b/src/utils/diag.rs new file mode 100644 index 0000000..de36517 --- /dev/null +++ b/src/utils/diag.rs @@ -0,0 +1,49 @@ +pub struct DiagNode { + pub label: String, + pub value: Option, + pub children: Vec, +} + +impl DiagNode { + pub fn leaf(label: impl Into, value: impl Into) -> Self { + Self { + label: label.into(), + value: Some(value.into()), + children: vec![], + } + } + + pub fn inner(label: impl Into, children: Vec) -> Self { + Self { + label: label.into(), + value: None, + children, + } + } + + // Entry point: print the root label then recurse into children. + pub fn print(&self) { + println!("{}", self.label); + let n = self.children.len(); + for (i, child) in self.children.iter().enumerate() { + child.render("", i == n - 1); + } + } + + fn render(&self, prefix: &str, is_last: bool) { + let connector = if is_last { "└── " } else { "├── " }; + match &self.value { + Some(v) => println!("{}{}{}: {}", prefix, connector, self.label, v), + None => println!("{}{}{}", prefix, connector, self.label), + } + let child_prefix = format!("{}{}", prefix, if is_last { " " } else { "│ " }); + let n = self.children.len(); + for (i, child) in self.children.iter().enumerate() { + child.render(&child_prefix, i == n - 1); + } + } +} + +pub trait Diagnosable { + fn diagnose(&self) -> DiagNode; +} diff --git a/src/isa.rs b/src/utils/isa.rs similarity index 74% rename from src/isa.rs rename to src/utils/isa.rs index 8383845..0bb5e44 100644 --- a/src/isa.rs +++ b/src/utils/isa.rs @@ -3,36 +3,73 @@ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Opcode { // R-type - Add, Sub, And, Or, Xor, Sll, Srl, Sra, Slt, Sltu, + Add, + Sub, + And, + Or, + Xor, + Sll, + Srl, + Sra, + Slt, + Sltu, // I-type ALU - Addi, Andi, Ori, Xori, Slli, Srli, Srai, Slti, Sltiu, + Addi, + Andi, + Ori, + Xori, + Slli, + Srli, + Srai, + Slti, + Sltiu, // Loads - Lb, Lh, Lw, Lbu, Lhu, + Lb, + Lh, + Lw, + Lbu, + Lhu, // Stores - Sb, Sh, Sw, + Sb, + Sh, + Sw, // Branches - Beq, Bne, Blt, Bge, Bltu, Bgeu, + Beq, + Bne, + Blt, + Bge, + Bltu, + Bgeu, // Jumps - Jal, Jalr, + Jal, + Jalr, // Upper immediate - Lui, Auipc, + Lui, + Auipc, } #[derive(Clone, Copy, Debug)] -pub enum InstrFormat { R, I, S, B, U, J } +pub enum InstrFormat { + R, + I, + S, + B, + U, + J, +} #[derive(Clone, Copy, Debug)] pub struct DecodedInstr { pub opcode: Opcode, pub format: InstrFormat, - pub rd: u8, - pub rs1: u8, - pub rs2: u8, - pub imm: i32, + pub rd: u8, + pub rs1: u8, + pub rs2: u8, + pub imm: i32, } pub struct InstResult { - pub rd: u8, + pub rd: u8, pub result: u32, pub format: InstrFormat, } @@ -41,11 +78,11 @@ pub struct InstResult { /// Returns None if the encoding is unrecognized. pub fn decode(word: u32) -> Option { let opcode_bits = word & 0x7f; - let rd = ((word >> 7) & 0x1f) as u8; - let funct3 = ((word >> 12) & 0x07) as u8; - let rs1 = ((word >> 15) & 0x1f) as u8; - let rs2 = ((word >> 20) & 0x1f) as u8; - let funct7 = ((word >> 25) & 0x7f) as u8; + let rd = ((word >> 7) & 0x1f) as u8; + let funct3 = ((word >> 12) & 0x07) as u8; + let rs1 = ((word >> 15) & 0x1f) as u8; + let rs2 = ((word >> 20) & 0x1f) as u8; + let funct7 = ((word >> 25) & 0x7f) as u8; let imm_i = (word as i32) >> 20; let imm_s = (((word >> 25) as i32) << 5) | ((word >> 7) & 0x1f) as i32; @@ -83,7 +120,13 @@ pub fn decode(word: u32) -> Option { 0x6 => Opcode::Ori, 0x4 => Opcode::Xori, 0x1 => Opcode::Slli, - 0x5 => if funct7 == 0x20 { Opcode::Srai } else { Opcode::Srli }, + 0x5 => { + if funct7 == 0x20 { + Opcode::Srai + } else { + Opcode::Srli + } + } 0x2 => Opcode::Slti, 0x3 => Opcode::Sltiu, _ => return None, @@ -122,12 +165,19 @@ pub fn decode(word: u32) -> Option { }; (op, InstrFormat::B, imm_b) } - 0b1101111 => (Opcode::Jal, InstrFormat::J, imm_j), + 0b1101111 => (Opcode::Jal, InstrFormat::J, imm_j), 0b1100111 => (Opcode::Jalr, InstrFormat::I, imm_i), - 0b0110111 => (Opcode::Lui, InstrFormat::U, imm_u), + 0b0110111 => (Opcode::Lui, InstrFormat::U, imm_u), 0b0010111 => (Opcode::Auipc, InstrFormat::U, imm_u), _ => return None, }; - Some(DecodedInstr { opcode, format, rd, rs1, rs2, imm }) + Some(DecodedInstr { + opcode, + format, + rd, + rs1, + rs2, + imm, + }) } diff --git a/src/utils/latch.rs b/src/utils/latch.rs new file mode 100644 index 0000000..0a877ef --- /dev/null +++ b/src/utils/latch.rs @@ -0,0 +1,38 @@ +pub struct Latch { + stage: Option, + active: Option, +} + +impl Latch { + pub fn new() -> Self { + Self { + stage: None, + active: None, + } + } + + pub fn stage(&mut self, val: T) { + self.stage = Some(val); + } + + pub fn direct_stage(&mut self, maybe_val: Option) { + self.stage = maybe_val; + } + + pub fn update(&mut self) { + self.active = self.stage.take(); + } + + pub fn read(&mut self) -> Option { + self.active.take() + } + + // Returns a copy of the active value without consuming it. + // Used to snapshot start-of-cycle state for hazard/forwarding checks. + pub fn peek(&self) -> Option + where + T: Copy, + { + self.active + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..ce4c506 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,6 @@ +pub mod assembler; +pub mod circular_buffer; +pub mod diag; +pub mod isa; +pub mod latch; +pub mod step; diff --git a/src/utils/step.rs b/src/utils/step.rs new file mode 100644 index 0000000..b8d70c4 --- /dev/null +++ b/src/utils/step.rs @@ -0,0 +1,64 @@ +use std::collections::VecDeque; + +pub enum Step { + Continue(S), + Done(R), +} + +pub enum PortStatus { + Idle, + InProgress(Ctx), +} + +pub trait SteppedProcess { + type Ctx; + type Result; + + fn create_context(parent: &S, task: T, task_id: u8) -> Self::Ctx; + fn step(parent: &mut S, ctx: &Self::Ctx) -> Step; +} + +pub struct Port, S> { + inbox: VecDeque<(T, u8)>, + outbox: VecDeque, + state: PortStatus, +} + +impl, S> Port { + pub fn new() -> Self { + Self { + inbox: VecDeque::new(), + outbox: VecDeque::new(), + state: PortStatus::Idle, + } + } + + pub fn send(&mut self, task: T, task_id: u8) { + self.inbox.push_back((task, task_id)); + } + + pub fn pop(&mut self) -> Option { + self.outbox.pop_front() + } + + pub fn tick(&mut self, parent: &mut S) { + if let PortStatus::Idle = self.state { + if let Some(task) = self.inbox.pop_front() { + self.state = PortStatus::InProgress(P::create_context(parent, task.0, task.1)); + } + } + + let new_state = match &self.state { + PortStatus::InProgress(ctx) => match P::step(parent, ctx) { + Step::Continue(new_ctx) => PortStatus::InProgress(new_ctx), + Step::Done(result) => { + self.outbox.push_back(result); + PortStatus::Idle + } + }, + PortStatus::Idle => PortStatus::Idle, + }; + + self.state = new_state; + } +}