diff --git a/src/common/instructions.rs b/src/common/instructions.rs index c75b6aa..f88b736 100644 --- a/src/common/instructions.rs +++ b/src/common/instructions.rs @@ -1,6 +1,3 @@ -type Offset = u16; -type Immediate = u16; - #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Interrupt { Software(u8), @@ -65,7 +62,7 @@ pub enum Register { impl Default for Register { fn default() -> Self { - Register::NoReg + Self::NoReg } } @@ -172,6 +169,17 @@ pub struct ITypeArgs { 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 } + } +} + /// Used by instructions not using immediates (besides 5 bit shift values). #[derive(Debug, Clone, Copy)] pub struct RTypeArgs { @@ -183,8 +191,9 @@ pub struct RTypeArgs { } impl RTypeArgs { - /// Args set to None will be replaced with 0 or [Register::NoReg] depending on their type. - fn new( + #[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, diff --git a/src/common/tests.rs b/src/common/tests.rs index cdc5a14..a1c9515 100644 --- a/src/common/tests.rs +++ b/src/common/tests.rs @@ -1,175 +1,105 @@ -use crate::common::instructions::*; +use super::instructions::*; #[test] -fn test_opcode_basic_instructions() { - assert_eq!(Instruction::Nop.opcode(), 0x0); - assert_eq!(Instruction::Mov(Register::Rg0, Register::Rg1).opcode(), 0x1); - assert_eq!( - Instruction::MovSigned(Register::Rg0, Register::Rg1).opcode(), - 0x2 - ); - assert_eq!(Instruction::Halt.opcode(), 0x24); +fn test_opcode_nop() { + let instr = Instruction::Nop; + assert_eq!(instr.opcode(), 0x0); } #[test] -fn test_opcode_data_transfer_instructions() { - assert_eq!( - Instruction::LoadByte(Register::Rg0, 0, Register::Rg1).opcode(), - 0x3 - ); - assert_eq!( - Instruction::LoadByteSigned(Register::Rg0, 0, Register::Rg1).opcode(), - 0x4 - ); - assert_eq!( - Instruction::LoadHalfword(Register::Rg0, 0, Register::Rg1).opcode(), - 0x5 - ); - assert_eq!( - Instruction::LoadHalfwordSigned(Register::Rg0, 0, Register::Rg1).opcode(), - 0x6 - ); - assert_eq!( - Instruction::LoadWord(Register::Rg0, 0, Register::Rg1).opcode(), - 0x7 - ); - assert_eq!( - Instruction::StoreByte(Register::Rg0, 0, Register::Rg1).opcode(), - 0x8 - ); - assert_eq!( - Instruction::StoreHalfword(Register::Rg0, 0, Register::Rg1).opcode(), - 0x9 - ); - assert_eq!( - Instruction::StoreWord(Register::Rg0, 0, Register::Rg1).opcode(), - 0xA - ); -} +fn test_opcode_data_transfer() { + let args = RTypeArgs::new(None, None, None, None); + assert_eq!(Instruction::Mov(args).opcode(), 0x1); + assert_eq!(Instruction::MovSigned(args).opcode(), 0x2); -#[test] -fn test_opcode_immediate_instructions() { - assert_eq!( - Instruction::LoadLowerImmediate(Register::Rg0, 0).opcode(), - 0xB - ); - assert_eq!( - Instruction::LoadUpperImmediate(Register::Rg0, 0).opcode(), - 0xC - ); + let iargs = ITypeArgs::new(0, None, None); + assert_eq!(Instruction::LoadByte(iargs).opcode(), 0x3); + assert_eq!(Instruction::LoadByteSigned(iargs).opcode(), 0x4); + assert_eq!(Instruction::LoadHalfword(iargs).opcode(), 0x5); + assert_eq!(Instruction::LoadHalfwordSigned(iargs).opcode(), 0x6); + assert_eq!(Instruction::LoadWord(iargs).opcode(), 0x7); + assert_eq!(Instruction::StoreByte(iargs).opcode(), 0x8); + assert_eq!(Instruction::StoreHalfword(iargs).opcode(), 0x9); + assert_eq!(Instruction::StoreWord(iargs).opcode(), 0xA); + assert_eq!(Instruction::LoadLowerImmediate(iargs).opcode(), 0xB); + assert_eq!(Instruction::LoadUpperImmediate(iargs).opcode(), 0xC); } #[test] fn test_opcode_jump_instructions() { - assert_eq!(Instruction::Jump(Register::Rg0, 0).opcode(), 0xD); - assert_eq!(Instruction::JumpEq(Register::Rg0, 0).opcode(), 0xE); - assert_eq!(Instruction::JumpNeq(Register::Rg0, 0).opcode(), 0xF); - assert_eq!(Instruction::JumpGt(Register::Rg0, 0).opcode(), 0x10); - assert_eq!(Instruction::JumpGe(Register::Rg0, 0).opcode(), 0x11); - assert_eq!(Instruction::JumpLt(Register::Rg0, 0).opcode(), 0x12); - assert_eq!(Instruction::JumpLe(Register::Rg0, 0).opcode(), 0x13); + let args = ITypeArgs::new(0, None, None); + assert_eq!(Instruction::Jump(args).opcode(), 0xD); + assert_eq!(Instruction::JumpEq(args).opcode(), 0xE); + assert_eq!(Instruction::JumpNeq(args).opcode(), 0xF); + assert_eq!(Instruction::JumpGt(args).opcode(), 0x10); + assert_eq!(Instruction::JumpGe(args).opcode(), 0x11); + assert_eq!(Instruction::JumpLt(args).opcode(), 0x12); + assert_eq!(Instruction::JumpLe(args).opcode(), 0x13); } #[test] -fn test_opcode_arithmetic_instructions() { - assert_eq!( - Instruction::Compare(Register::Rg0, Register::Rg1).opcode(), - 0x14 - ); - assert_eq!(Instruction::Increment(Register::Rg0).opcode(), 0x15); - assert_eq!(Instruction::Decrement(Register::Rg0).opcode(), 0x16); - assert_eq!( - Instruction::ShiftLeft(Register::Rg0, Register::Rg1, 0).opcode(), - 0x17 - ); - assert_eq!( - Instruction::ShiftRight(Register::Rg0, Register::Rg1, 0).opcode(), - 0x18 - ); - assert_eq!( - Instruction::Add(Register::Rg0, Register::Rg1, Register::Rg2).opcode(), - 0x19 - ); - assert_eq!( - Instruction::Sub(Register::Rg0, Register::Rg1, Register::Rg2).opcode(), - 0x1A - ); +fn test_opcode_arithmetic() { + let args = RTypeArgs::new(None, None, None, None); + assert_eq!(Instruction::Compare(args).opcode(), 0x14); + assert_eq!(Instruction::Increment(args).opcode(), 0x15); + assert_eq!(Instruction::Decrement(args).opcode(), 0x16); + assert_eq!(Instruction::ShiftLeft(args).opcode(), 0x17); + assert_eq!(Instruction::ShiftRight(args).opcode(), 0x18); + assert_eq!(Instruction::Add(args).opcode(), 0x19); + assert_eq!(Instruction::Sub(args).opcode(), 0x1A); } #[test] -fn test_opcode_logical_instructions() { - assert_eq!( - Instruction::And(Register::Rg0, Register::Rg1, Register::Rg2).opcode(), - 0x1B - ); - assert_eq!( - Instruction::Or(Register::Rg0, Register::Rg1, Register::Rg2).opcode(), - 0x1C - ); - assert_eq!( - Instruction::Not(Register::Rg0, Register::Rg1).opcode(), - 0x1D - ); - assert_eq!( - Instruction::Xor(Register::Rg0, Register::Rg1, Register::Rg2).opcode(), - 0x1E - ); - assert_eq!( - Instruction::Nand(Register::Rg0, Register::Rg1, Register::Rg2).opcode(), - 0x1F - ); - assert_eq!( - Instruction::Nor(Register::Rg0, Register::Rg1, Register::Rg2).opcode(), - 0x20 - ); - assert_eq!( - Instruction::Xnor(Register::Rg0, Register::Rg1, Register::Rg2).opcode(), - 0x21 - ); +fn test_opcode_logical() { + let args = RTypeArgs::new(None, None, None, None); + assert_eq!(Instruction::And(args).opcode(), 0x1B); + assert_eq!(Instruction::Or(args).opcode(), 0x1C); + assert_eq!(Instruction::Not(args).opcode(), 0x1D); + assert_eq!(Instruction::Xor(args).opcode(), 0x1E); + assert_eq!(Instruction::Nand(args).opcode(), 0x1F); + assert_eq!(Instruction::Nor(args).opcode(), 0x20); + assert_eq!(Instruction::Xnor(args).opcode(), 0x21); } #[test] -fn test_opcode_system_instructions() { - assert_eq!( - Instruction::Interrupt(Interrupt::Software(0)).opcode(), - 0x22 - ); +fn test_opcode_misc() { + let interrupt = Interrupt::Software(5); + assert_eq!(Instruction::Interrupt(interrupt).opcode(), 0x22); assert_eq!(Instruction::IntReturn.opcode(), 0x23); + assert_eq!(Instruction::Halt.opcode(), 0x24); } #[test] -fn test_opcode_top_bits_are_zero() { - // Test that opcodes have top 2 bits as 0 (6-bit opcodes) - let instructions = [ - Instruction::Nop, - Instruction::Mov(Register::Rg0, Register::Rg1), - Instruction::Add(Register::Rg0, Register::Rg1, Register::Rg2), - Instruction::Halt, - ]; +fn test_opcode_with_different_args() { + let args1 = RTypeArgs::new( + Some(Register::Rg0), + Some(Register::Rg1), + Some(Register::Rg2), + Some(5), + ); + let args2 = RTypeArgs::new( + Some(Register::Acc), + Some(Register::Spr), + Some(Register::Bpr), + Some(31), + ); - for instruction in instructions { - let opcode = instruction.opcode(); - assert_eq!( - opcode & 0xC0, - 0, - "Top 2 bits should be 0 for opcode {opcode:#02x}" - ); - } + // Opcode should be the same regardless of arguments + assert_eq!( + Instruction::Add(args1).opcode(), + Instruction::Add(args2).opcode() + ); + assert_eq!( + Instruction::Sub(args1).opcode(), + Instruction::Sub(args2).opcode() + ); } #[test] -fn test_opcode_same_instruction_different_params() { - // Same instruction type with different parameters should have same opcode - assert_eq!( - Instruction::Mov(Register::Rg0, Register::Rg1).opcode(), - Instruction::Mov(Register::Acc, Register::Spr).opcode() - ); - assert_eq!( - Instruction::Add(Register::Rg0, Register::Rg1, Register::Rg2).opcode(), - Instruction::Add(Register::Acc, Register::Spr, Register::Bpr).opcode() - ); - assert_eq!( - Instruction::LoadWord(Register::Rg0, 100, Register::Rg1).opcode(), - Instruction::LoadWord(Register::Acc, 500, Register::Spr).opcode() - ); +fn test_opcode_boundary_values() { + // Test highest opcode value + assert_eq!(Instruction::Halt.opcode(), 0x24); + + // Test lowest opcode value + assert_eq!(Instruction::Nop.opcode(), 0x0); }