diff --git a/src/common/instructions.rs b/src/common/instructions.rs index 5ac9277..c820622 100644 --- a/src/common/instructions.rs +++ b/src/common/instructions.rs @@ -1,4 +1,9 @@ -use crate::common::instructions::encode::Encode; +use crate::common::instructions::{ + args::{ITypeArgs, RTypeArgs}, + encode::Encode, + errors::InstructionDecodeError, + errors::RegisterParseError, +}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Interrupt { @@ -15,6 +20,7 @@ impl Interrupt { } } +// TODO: This should be TryFrom. impl From for Interrupt { #[allow(unreachable_code)] fn from(_code: u8) -> Self { @@ -24,7 +30,15 @@ impl From for Interrupt { } } +/// Whether an [`Instruction`] is an I-type or R-type instruction. +#[non_exhaustive] +pub enum InstructionType { + Register, + Immediate, +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] pub enum Register { // general purpose registers Rg0, @@ -68,22 +82,6 @@ impl Default for Register { } } -#[derive(Debug)] -/// Error type for parsing register numbers. -pub enum RegisterParseError { - InvalidIndex(u8), -} - -impl std::fmt::Display for RegisterParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::InvalidIndex(idx) => write!(f, "invalid index given ({idx})"), - } - } -} - -impl std::error::Error for RegisterParseError {} - impl TryFrom for Register { type Error = RegisterParseError; @@ -159,146 +157,63 @@ impl std::fmt::Display for Register { } } -// /// Used by instructions with no arguments. -// struct NoArgs {} - -#[derive(Debug, Clone, Copy)] -/// Used by instructions with 2 registers and an immediate argument. -pub struct ITypeArgs { - pub immediate: u16, - pub r1: Register, - /// May not actually be used by some instructions taking an immediate e.g. LUI. This is solved by making the constructor take Options. - pub r2: Register, -} - -impl ITypeArgs { - #[must_use] - /// Creates a new [`ITypeArgs`]. If r1 or r2 is unset, they will be replaced with [`Register::NoReg`]. - pub fn new(immediate: u16, r1: Option, r2: Option) -> Self { - let r1 = r1.unwrap_or_default(); - let r2 = r2.unwrap_or_default(); - - Self { immediate, r1, r2 } - } -} - -impl Encode for ITypeArgs { - /// Encodes an I-type instruction from its fields. These must have some unused high-order - /// bits set to 0 else the bit shifting logic gets fucked. - fn encode(self, opcode: u8) -> u32 { - let opcode = u32::from(opcode); - let r1 = self.r1 as u32; - let dr = self.r2 as u32; - let immediate = u32::from(self.immediate); - - (opcode << 26) | (r1 << 21) | (dr << 16) | immediate - } -} - -/// Used by instructions not using immediates (besides 5 bit shift values). -#[derive(Debug, Clone, Copy)] -pub struct RTypeArgs { - pub sr1: Register, - pub sr2: Register, - pub dr: Register, - /// 5 bit shift amount. - pub shamt: u8, -} - -impl RTypeArgs { - #[must_use] - /// Creates a new [`RTypeArgs`]. If any registers are unset, they will be replaced with [`Register::NoReg`]. If `shamt` is unset, it will be set to 0. - pub fn new( - sr1: Option, - sr2: Option, - dr: Option, - shamt: Option, - ) -> Self { - let sr1 = sr1.unwrap_or_default(); - let shamt = shamt.unwrap_or_default(); - let sr2 = sr2.unwrap_or_default(); - let dr = dr.unwrap_or_default(); - - Self { - sr1, - sr2, - dr, - shamt, - } - } -} - -impl Encode for RTypeArgs { - /// Encodes an R-type instruction from its fields. These must have unused high-order - /// bits set to 0 else the bit shifting logic is fucked. - /// - /// # Arguments - /// - /// - `shamt`: The amount to shift value (used only in shift instructions, otherwise 0). - fn encode(self, opcode: u8) -> u32 { - let opcode = u32::from(opcode); - let sr1 = self.sr1 as u32; - let sr2 = self.sr2 as u32; - let dr = self.dr as u32; - let shamt = u32::from(self.shamt); - - (opcode << 26) | (sr1 << 21) | (sr2 << 16) | (dr << 11) | (shamt << 6) - } -} - -/// TODO: Turn argument tuples into simple structs like `TwoReg`, `TwoRegAndOff` -/// etc just to make code a little cleaner. #[derive(Debug, Clone, Copy)] #[repr(u8)] +#[non_exhaustive] +/// A list of all current instructions in the DSA. +/// +/// # Note +/// +/// This is subject to change and is therefore marked non exhaustive. pub enum Instruction { // No-op Nop = 0x0, // Data transfer instructions - Mov(RTypeArgs) = 0x1, - MovSigned(RTypeArgs) = 0x2, + Mov(args::RTypeArgs) = 0x1, + MovSigned(args::RTypeArgs) = 0x2, - LoadByte(ITypeArgs) = 0x3, - LoadByteSigned(ITypeArgs) = 0x4, - LoadHalfword(ITypeArgs) = 0x5, - LoadHalfwordSigned(ITypeArgs) = 0x6, - LoadWord(ITypeArgs) = 0x7, + LoadByte(args::ITypeArgs) = 0x3, + LoadByteSigned(args::ITypeArgs) = 0x4, + LoadHalfword(args::ITypeArgs) = 0x5, + LoadHalfwordSigned(args::ITypeArgs) = 0x6, + LoadWord(args::ITypeArgs) = 0x7, - StoreByte(ITypeArgs) = 0x8, - StoreHalfword(ITypeArgs) = 0x9, - StoreWord(ITypeArgs) = 0xA, + StoreByte(args::ITypeArgs) = 0x8, + StoreHalfword(args::ITypeArgs) = 0x9, + StoreWord(args::ITypeArgs) = 0xA, - LoadLowerImmediate(ITypeArgs) = 0xB, - LoadUpperImmediate(ITypeArgs) = 0xC, + LoadLowerImmediate(args::ITypeArgs) = 0xB, + LoadUpperImmediate(args::ITypeArgs) = 0xC, // Jump Instructions - Jump(ITypeArgs) = 0xD, - JumpEq(ITypeArgs) = 0xE, - JumpNeq(ITypeArgs) = 0xF, - JumpGt(ITypeArgs) = 0x10, - JumpGe(ITypeArgs) = 0x11, - JumpLt(ITypeArgs) = 0x12, - JumpLe(ITypeArgs) = 0x13, + Jump(args::ITypeArgs) = 0xD, + JumpEq(args::ITypeArgs) = 0xE, + JumpNeq(args::ITypeArgs) = 0xF, + JumpGt(args::ITypeArgs) = 0x10, + JumpGe(args::ITypeArgs) = 0x11, + JumpLt(args::ITypeArgs) = 0x12, + JumpLe(args::ITypeArgs) = 0x13, // Comparison - Compare(RTypeArgs) = 0x14, + Compare(args::RTypeArgs) = 0x14, // Arithmetic - Add(RTypeArgs) = 0x19, - Sub(RTypeArgs) = 0x1A, - Increment(RTypeArgs) = 0x15, - Decrement(RTypeArgs) = 0x16, - ShiftLeft(RTypeArgs) = 0x17, - ShiftRight(RTypeArgs) = 0x18, + Add(args::RTypeArgs) = 0x19, + Sub(args::RTypeArgs) = 0x1A, + Increment(args::RTypeArgs) = 0x15, + Decrement(args::RTypeArgs) = 0x16, + ShiftLeft(args::RTypeArgs) = 0x17, + ShiftRight(args::RTypeArgs) = 0x18, // Logical - And(RTypeArgs) = 0x1B, - Or(RTypeArgs) = 0x1C, - Not(RTypeArgs) = 0x1D, - Xor(RTypeArgs) = 0x1E, - Nand(RTypeArgs) = 0x1F, - Nor(RTypeArgs) = 0x20, - Xnor(RTypeArgs) = 0x21, + And(args::RTypeArgs) = 0x1B, + Or(args::RTypeArgs) = 0x1C, + Not(args::RTypeArgs) = 0x1D, + Xor(args::RTypeArgs) = 0x1E, + Nand(args::RTypeArgs) = 0x1F, + Nor(args::RTypeArgs) = 0x20, + Xnor(args::RTypeArgs) = 0x21, // Misc Interrupt(Interrupt) = 0x22, @@ -317,92 +232,179 @@ impl Instruction { unsafe { *std::ptr::from_ref::(self).cast::() } } + /// Encodes an [`Instruction`] into a word. #[must_use] - #[allow(unused)] pub fn encode(&self) -> u32 { Encode::encode(*self, self.opcode()) } - #[must_use] - #[deprecated = "I am a little confused as to whether we keep these around for jumps."] - /// Encodes a J-type instruction from its fields. These must have some unused high-order - /// bits set to 0 else the bit shifting logic gets fucked. - /// - /// Note: the address argument is passed as-is, caller is responsible for resolving this - /// address properly. - pub const fn _encode_j_type(opcode: u8, addr: u32) -> u32 { - let opcode = opcode as u32; - - (opcode << 26) | addr + /// Decodes an [`Instruction`] from a word `data`. + pub fn decode(data: u32) -> Result { + data.try_into() } + /// Returns the mnemonic for a given [`Instruction`]. #[must_use] - pub const fn decode(_data: u32) -> Self { - // TODO: this needs to actually decode something - Self::Nop - } -} - -impl std::fmt::Display for Instruction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + pub const fn mnemonic(self) -> &'static str { match self { - Self::Nop => write!(f, "NOP"), - Self::Mov(args) => write!(f, "MOV {}, {}", args.sr1, args.dr), - Self::MovSigned(args) => write!(f, "MOVS {}, {}", args.sr1, args.dr), - Self::LoadByte(args) => write!(f, "LDB {:x}({}), {}", args.immediate, args.r1, args.r2), - Self::LoadByteSigned(args) => { - write!(f, "LDBS {:x}({}), {}", args.immediate, args.r1, args.r2) - } - Self::LoadHalfword(args) => { - write!(f, "LDH {:x}({}), {}", args.immediate, args.r1, args.r2) - } - Self::LoadHalfwordSigned(args) => { - write!(f, "LDHS {:x}({}), {}", args.immediate, args.r1, args.r2) - } - Self::LoadWord(args) => write!(f, "LDW {:x}({}), {}", args.immediate, args.r1, args.r2), - Self::StoreByte(args) => { - write!(f, "STB {:x}({}), {}", args.immediate, args.r1, args.r2) - } - Self::StoreHalfword(args) => { - write!(f, "STH {:x}({}), {}", args.immediate, args.r1, args.r2) - } - Self::StoreWord(args) => { - write!(f, "STW {:x}({}), {}", args.immediate, args.r1, args.r2) - } + Self::Add(_) => "add", + Self::Sub(_) => "sub", + Self::Increment(_) => "inc", + Self::Decrement(_) => "dec", + Self::Compare(_) => "cmp", + Self::Halt => "hlt", + Self::And(_) => "and", + Self::IntReturn => "intr", + Self::Interrupt(_) => "int", + Self::Jump(_) => "jmp", + Self::JumpEq(_) => "jeq", + Self::JumpNeq(_) => "jneq", + Self::JumpGt(_) => "jgt", + Self::JumpGe(_) => "jge", + Self::JumpLt(_) => "jlt", + Self::JumpLe(_) => "jle", + Self::Mov(_) => "mov", + Self::MovSigned(_) => "movs", + Self::LoadByte(_) => "ldb", + Self::LoadByteSigned(_) => "ldbs", + Self::LoadHalfword(_) => "ldh", + Self::LoadHalfwordSigned(_) => "ldhs", + Self::LoadWord(_) => "ldw", + Self::StoreByte(_) => "stb", + Self::StoreHalfword(_) => "sth", + Self::StoreWord(_) => "stw", + Self::LoadLowerImmediate(_) => "lli", + Self::LoadUpperImmediate(_) => "lui", + Self::ShiftLeft(_) => "shl", + Self::ShiftRight(_) => "shr", + Self::Or(_) => "or", + Self::Not(_) => "not", + Self::Nop => "nop", + Self::Xor(_) => "xor", + Self::Nand(_) => "nand", + Self::Nor(_) => "nor", + Self::Xnor(_) => "xnor", + } + } - Self::LoadLowerImmediate(args) => write!(f, "LLI {}, {}", args.r1, args.r2), - Self::LoadUpperImmediate(args) => write!(f, "LUI {}, {}", args.r1, args.r2), + /// Returns the [`InstructionType`] for the given [`Instruction`]. + #[must_use] + pub const fn instruction_type(self) -> InstructionType { + Self::instruction_type_from_opcode(self.opcode()) + } - Self::Jump(args) => write!(f, "JMP ({:x}){}", args.immediate, args.r1), - Self::JumpEq(args) => write!(f, "JEQ ({:x}){}", args.immediate, args.r1), - Self::JumpNeq(args) => write!(f, "JNEQ ({:x}){}", args.immediate, args.r1), - Self::JumpGt(args) => write!(f, "JGT {:x}({})", args.immediate, args.r1), - Self::JumpGe(args) => write!(f, "JGE {:x}({})", args.immediate, args.r1), - Self::JumpLt(args) => write!(f, "JLT {:x}({})", args.immediate, args.r1), - Self::JumpLe(args) => write!(f, "JLE {:x}({})", args.immediate, args.r1), - - Self::Compare(args) => write!(f, "CMP {}, {}", args.sr1, args.sr2), - - Self::Add(args) => write!(f, "ADD {}, {}, {}", args.sr1, args.sr2, args.dr), - Self::Sub(args) => write!(f, "SUB {}, {}, {}", args.sr1, args.sr2, args.dr), - Self::Increment(a) => write!(f, "INC {}", a.dr), - Self::Decrement(a) => write!(f, "DEC {}", a.dr), - Self::ShiftLeft(args) => write!(f, "SHL {}, {}, {}", args.sr1, args.sr2, args.dr), - Self::ShiftRight(args) => write!(f, "SHR {}, {}, {}", args.sr1, args.sr2, args.dr), - - Self::And(args) => write!(f, "AND {}, {}, {}", args.sr1, args.sr2, args.dr), - Self::Or(args) => write!(f, "OR {}, {}, {}", args.sr1, args.sr2, args.dr), - Self::Not(args) => write!(f, "NOT {}, {}", args.sr1, args.sr2), - Self::Xor(args) => write!(f, "XOR {}, {}, {}", args.sr1, args.sr2, args.dr), - Self::Nand(args) => write!(f, "NAND {}, {}, {}", args.sr1, args.sr2, args.dr), - Self::Nor(args) => write!(f, "NOR {}, {}, {}", args.sr1, args.sr2, args.dr), - Self::Xnor(args) => write!(f, "XNOR {}, {}, {}", args.sr1, args.sr2, args.dr), - - Self::Interrupt(a) => write!(f, "INT {}", a.as_u8()), - Self::IntReturn => write!(f, "INTR"), - Self::Halt => write!(f, "HALT"), + /// Returns the [`InstructionType`] for the given `opcode`. + #[must_use] + pub const fn instruction_type_from_opcode(opcode: u8) -> InstructionType { + match opcode { + 0x3..=0x13 => InstructionType::Immediate, + _ => InstructionType::Register, } } } +// Instruction decoding logic goes here. +impl std::fmt::Display for Instruction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.mnemonic())?; + + match self { + Self::Mov(args) | Self::MovSigned(args) => write!(f, " {}, {}", args.sr1, args.dr), + Self::LoadByte(args) + | Self::LoadByteSigned(args) + | Self::LoadHalfword(args) + | Self::LoadHalfwordSigned(args) + | Self::LoadWord(args) + | Self::StoreByte(args) + | Self::StoreHalfword(args) + | Self::StoreWord(args) => { + write!(f, " {:x}({}), {}", args.immediate, args.r1, args.r2) + } + Self::Jump(args) + | Self::JumpEq(args) + | Self::JumpNeq(args) + | Self::JumpGt(args) + | Self::JumpGe(args) + | Self::JumpLt(args) + | Self::JumpLe(args) => { + write!(f, " ({:x}){}", args.immediate, args.r1) + } + Self::LoadLowerImmediate(args) | Self::LoadUpperImmediate(args) => { + write!(f, " {}, {}", args.r1, args.r2) + } + Self::Compare(args) | Self::Not(args) => write!(f, " {}, {}", args.sr1, args.sr2), + + Self::Add(args) + | Self::Sub(args) + | Self::Xor(args) + | Self::Nand(args) + | Self::Nor(args) + | Self::Xnor(args) + | Self::ShiftLeft(args) + | Self::ShiftRight(args) + | Self::And(args) + | Self::Or(args) => { + write!(f, " {}, {}, {}", args.sr1, args.sr2, args.dr) + } + + Self::Increment(a) | Self::Decrement(a) => write!(f, " {}", a.dr), + Self::Interrupt(a) => write!(f, " {}", a.as_u8()), + _ => Ok(()), + } + } +} + +impl TryFrom for Instruction { + type Error = InstructionDecodeError; + + /// Instruction decoding can be using using [`Instruction::try_from`] + fn try_from(data: u32) -> Result { + // Pull the opcode out so we can parse it correctly. + let opcode: u8 = ((data & 0xfc << 26) >> 26) as u8; + + match opcode { + 0x0 => Ok(Self::Nop), + 0x1 => Ok(Self::Mov(RTypeArgs::try_from(data)?)), + 0x2 => Ok(Self::MovSigned(RTypeArgs::try_from(data)?)), + 0x3 => Ok(Self::LoadByte(ITypeArgs::try_from(data)?)), + 0x4 => Ok(Self::LoadByteSigned(ITypeArgs::try_from(data)?)), + 0x5 => Ok(Self::LoadHalfword(ITypeArgs::try_from(data)?)), + 0x6 => Ok(Self::LoadHalfwordSigned(ITypeArgs::try_from(data)?)), + 0x7 => Ok(Self::LoadWord(ITypeArgs::try_from(data)?)), + 0x8 => Ok(Self::StoreByte(ITypeArgs::try_from(data)?)), + 0x9 => Ok(Self::StoreHalfword(ITypeArgs::try_from(data)?)), + 0xA => Ok(Self::StoreWord(ITypeArgs::try_from(data)?)), + 0xB => Ok(Self::LoadLowerImmediate(ITypeArgs::try_from(data)?)), + 0xC => Ok(Self::LoadUpperImmediate(ITypeArgs::try_from(data)?)), + 0xD => Ok(Self::Jump(ITypeArgs::try_from(data)?)), + 0xE => Ok(Self::JumpEq(ITypeArgs::try_from(data)?)), + 0xF => Ok(Self::JumpNeq(ITypeArgs::try_from(data)?)), + 0x10 => Ok(Self::JumpGt(ITypeArgs::try_from(data)?)), + 0x11 => Ok(Self::JumpGe(ITypeArgs::try_from(data)?)), + 0x12 => Ok(Self::JumpLt(ITypeArgs::try_from(data)?)), + 0x13 => Ok(Self::JumpLe(ITypeArgs::try_from(data)?)), + 0x14 => Ok(Self::Compare(RTypeArgs::try_from(data)?)), + 0x15 => Ok(Self::Increment(RTypeArgs::try_from(data)?)), + 0x16 => Ok(Self::Decrement(RTypeArgs::try_from(data)?)), + 0x17 => Ok(Self::ShiftLeft(RTypeArgs::try_from(data)?)), + 0x18 => Ok(Self::ShiftRight(RTypeArgs::try_from(data)?)), + 0x19 => Ok(Self::Add(RTypeArgs::try_from(data)?)), + 0x1A => Ok(Self::Sub(RTypeArgs::try_from(data)?)), + 0x1B => Ok(Self::And(RTypeArgs::try_from(data)?)), + 0x1C => Ok(Self::Or(RTypeArgs::try_from(data)?)), + 0x1D => Ok(Self::Not(RTypeArgs::try_from(data)?)), + 0x1E => Ok(Self::Xor(RTypeArgs::try_from(data)?)), + 0x1F => Ok(Self::Nand(RTypeArgs::try_from(data)?)), + 0x20 => Ok(Self::Nor(RTypeArgs::try_from(data)?)), + 0x21 => Ok(Self::Xnor(RTypeArgs::try_from(data)?)), + 0x22 => Ok(Self::Interrupt(Interrupt::from((data & 0xFF) as u8))), + 0x23 => Ok(Self::IntReturn), + 0x24 => Ok(Self::Halt), + _ => Err(InstructionDecodeError::InvalidOpcode(opcode)), + } + } +} + +pub mod args; mod encode; +pub mod errors; diff --git a/src/common/instructions/args.rs b/src/common/instructions/args.rs new file mode 100644 index 0000000..80528da --- /dev/null +++ b/src/common/instructions/args.rs @@ -0,0 +1,152 @@ +//! Various types of arguments that instructions can take, alongside encoding and decoding logic. + +use crate::common::instructions::{RegisterParseError, encode::Encode}; + +use super::Register; + +/// A list of errors that can be returned when decoding instruction arguments. +#[derive(Debug)] +pub enum ArgsDecodeError { + /// The register was not valid. + InvalidRegister(u8), +} + +impl From for ArgsDecodeError { + fn from(value: RegisterParseError) -> Self { + match value { + RegisterParseError::InvalidIndex(idx) => Self::InvalidRegister(idx), + } + } +} + +impl std::fmt::Display for ArgsDecodeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidRegister(idx) => { + write!(f, "invalid register index, got {idx:x}")?; + } + } + + Ok(()) + } +} + +impl std::error::Error for ArgsDecodeError {} + +#[derive(Debug, Clone, Copy)] +/// Used by instructions with 2 registers and an immediate argument. +pub struct ITypeArgs { + pub immediate: u16, + pub r1: Register, + /// May not actually be used by some instructions taking an immediate e.g. LUI. This is solved by making the constructor take Options. + pub r2: Register, +} + +impl ITypeArgs { + #[must_use] + /// Creates a new [`ITypeArgs`]. If r1 or r2 is unset, they will be replaced with [`Register::NoReg`]. + pub fn new(immediate: u16, r1: Option, r2: Option) -> Self { + let r1 = r1.unwrap_or_default(); + let r2 = r2.unwrap_or_default(); + + Self { immediate, r1, r2 } + } +} + +impl Encode for ITypeArgs { + /// Encodes an I-type instruction from its fields. These must have some unused high-order + /// bits set to 0 else the bit shifting logic gets fucked. + fn encode(self, opcode: u8) -> u32 { + let opcode = u32::from(opcode); + let r1 = self.r1 as u32; + let dr = self.r2 as u32; + let immediate = u32::from(self.immediate); + + (opcode << 26) | (r1 << 21) | (dr << 16) | immediate + } +} + +impl TryFrom for ITypeArgs { + type Error = ArgsDecodeError; + + fn try_from(data: u32) -> Result { + let r1 = ((data >> 21) as u8).try_into()?; + let r2 = ((data >> 16) as u8).try_into()?; + let immediate = data as u16; + + Ok(Self { immediate, r1, r2 }) + } +} + +/// Used by instructions not using immediates (besides 5 bit shift values). +#[derive(Debug, Clone, Copy)] +pub struct RTypeArgs { + pub sr1: Register, + pub sr2: Register, + pub dr: Register, + /// 5 bit shift amount. + pub shamt: u8, +} + +impl RTypeArgs { + #[must_use] + /// Creates a new [`RTypeArgs`]. If any registers are unset, they will be replaced with [`Register::NoReg`]. If `shamt` is unset, it will be set to 0. + pub fn new( + sr1: Option, + sr2: Option, + dr: Option, + shamt: Option, + ) -> Self { + let sr1 = sr1.unwrap_or_default(); + let shamt = shamt.unwrap_or_default(); + let sr2 = sr2.unwrap_or_default(); + let dr = dr.unwrap_or_default(); + + Self { + sr1, + sr2, + dr, + shamt, + } + } +} + +impl Encode for RTypeArgs { + /// Encodes an R-type instruction from its fields. These must have unused high-order + /// bits set to 0 else the bit shifting logic is fucked. + /// + /// # Arguments + /// + /// - `shamt`: The amount to shift value (used only in shift instructions, otherwise 0). + fn encode(self, opcode: u8) -> u32 { + let opcode = u32::from(opcode); + let sr1 = self.sr1 as u32; + let sr2 = self.sr2 as u32; + let dr = self.dr as u32; + let shamt = u32::from(self.shamt); + + (opcode << 26) | (sr1 << 21) | (sr2 << 16) | (dr << 11) | (shamt << 6) + } +} + +impl TryFrom for RTypeArgs { + type Error = ArgsDecodeError; + + fn try_from(data: u32) -> Result { + let sr1 = (data >> 21) as u8; + let sr2 = (data >> 16) as u8; + let dr = (data >> 11) as u8; + let shamt = (data >> 6) as u8; + + let sr1_reg = sr1.try_into()?; + let sr2_reg = sr2.try_into()?; + let dr_reg = dr.try_into()?; + + Ok(Self { + sr1: sr1_reg, + sr2: sr2_reg, + dr: dr_reg, + shamt, + }) + } +} diff --git a/src/common/instructions/errors.rs b/src/common/instructions/errors.rs new file mode 100644 index 0000000..c5936d7 --- /dev/null +++ b/src/common/instructions/errors.rs @@ -0,0 +1,54 @@ +//! All the errors that may be returned from [`instructions`]. + +use super::args::ArgsDecodeError; + +#[derive(Debug)] +/// Error type for parsing register numbers. +pub enum RegisterParseError { + InvalidIndex(u8), +} + +impl std::fmt::Display for RegisterParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidIndex(idx) => write!(f, "invalid index given ({idx})"), + } + } +} + +impl std::error::Error for RegisterParseError {} + +/// A list of errors that can be returned when decoding instructions. +#[derive(Debug)] +pub enum InstructionDecodeError { + /// Some field was incorrect. Returns an error for debugging purposes. + InvalidArgument(ArgsDecodeError), + /// Some opcode was invalid. Returns the offending opcode for debugging purposes etc. + InvalidOpcode(u8), +} + +impl From for InstructionDecodeError { + fn from(err: ArgsDecodeError) -> Self { + Self::InvalidArgument(err) + } +} + +impl std::fmt::Display for InstructionDecodeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidOpcode(code) => write!(f, "invalid opcode, got {code:x}")?, + Self::InvalidArgument(err) => write!(f, "invalid arguments, got an error {err}")?, + } + + Ok(()) + } +} + +impl std::error::Error for InstructionDecodeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::InvalidArgument(err) => Some(err), + _ => None, + } + } +} diff --git a/src/common/tests.rs b/src/common/tests.rs index 960803b..9ca6683 100644 --- a/src/common/tests.rs +++ b/src/common/tests.rs @@ -1,3 +1,5 @@ +use crate::common::instructions::args::{ITypeArgs, RTypeArgs}; + use super::instructions::*; #[test] diff --git a/src/emulator/system/emulator.rs b/src/emulator/system/emulator.rs index 9d3bfbb..a35b3bc 100644 --- a/src/emulator/system/emulator.rs +++ b/src/emulator/system/emulator.rs @@ -67,8 +67,17 @@ pub fn run_emulator( } Command::Step => { running = Running::Paused; - // Execute one cycle - cpu_lock.cycle(); // or advance() - whatever your method is called + + // Execute one cycle. + match cpu_lock.cycle() { + Ok(_) => {} + Err(why) => { + let pcx = cpu_lock.get(Register::Pcx); + eprintln!("Could not decode instruction at {pcx:x}. Reason: {why}"); + continue; + } + } + instruction_count += 1; println!("Stepped one instruction"); } @@ -93,12 +102,20 @@ pub fn run_emulator( if running == Running::Running { let mut update = false; - // Execute one cycle - cpu_lock.cycle(); - if matches!( - Instruction::decode(cpu_lock.get(Register::Cir)), - Instruction::Halt - ) { + + // Execute one cycle. + let instruction = match cpu_lock.cycle() { + Ok(instruction) => instruction, + Err(why) => { + let pcx = cpu_lock.get(Register::Pcx); + eprintln!("Could not decode instruction at {pcx:x}. Reason: {why}"); + continue; + } + }; + + // let instruction = match Instruction::decode(cpu_lock.get(Register::Cir)) {}; + + if matches!(instruction, Instruction::Halt) { running = Running::Halted; update = true; } diff --git a/src/emulator/system/processor/mod.rs b/src/emulator/system/processor/mod.rs index dfa2258..bf356d3 100644 --- a/src/emulator/system/processor/mod.rs +++ b/src/emulator/system/processor/mod.rs @@ -1,7 +1,7 @@ use std::cmp::{max, min}; use crate::{ - common::instructions::{Instruction, Interrupt, Register}, + common::instructions::{Instruction, Interrupt, Register, errors::InstructionDecodeError}, emulator::system::{memory::MemoryUnit, model::RegFile}, }; @@ -32,24 +32,27 @@ impl Processor { self.memory.reset(); } - pub fn cycle(&mut self) -> Instruction { + pub fn cycle(&mut self) -> Result { self.halted = false; - // get value from PCX + + // Get value from PCX. let addr = self.fetch(); - // increment PCX + // Increment PCX. self.advance(); - // set MAR to the previous value of PCX + // Set MAR to the previous value of PCX. *self.reg(Register::Mar) = addr; let val = self.memory.read_word(addr); - // set CIR to the value of RAM[MAR] + // Set CIR to the value of RAM[MAR]. *self.reg(Register::Mar) = val; - // decode and execute the instruction - let instruction = Instruction::decode(val); + + // Decode and execute the instruction. + let instruction = Instruction::decode(val)?; instruction.execute(self); - instruction + + Ok(instruction) } fn fetch(&self) -> u32 { diff --git a/src/emulator/system/processor/tests.rs b/src/emulator/system/processor/tests.rs index e6a71f1..9a6ce68 100644 --- a/src/emulator/system/processor/tests.rs +++ b/src/emulator/system/processor/tests.rs @@ -1,5 +1,11 @@ use super::*; -use crate::{common::instructions::*, emulator::system::memory::*}; +use crate::{ + common::instructions::{ + args::{ITypeArgs, RTypeArgs}, + *, + }, + emulator::system::memory::*, +}; fn create_test_processor() -> Processor { let memory = Box::new(MainStore::new());