common: add tests and opcode method

This commit is contained in:
2025-06-15 04:36:06 +01:00
parent 3c493d93fa
commit e55a1fced5
4 changed files with 271 additions and 41 deletions
+92 -40
View File
@@ -154,69 +154,121 @@ impl std::fmt::Display for Register {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum Instruction {
// No-op
Nop,
Nop = 0x0,
// Data transfer instructions
Mov(Register, Register),
MovSigned(Register, Register),
Mov(Register, Register) = 0x1,
MovSigned(Register, Register) = 0x2,
LoadByte(Register, Offset, Register),
LoadByteSigned(Register, Offset, Register),
LoadHalfword(Register, Offset, Register),
LoadHalfwordSigned(Register, Offset, Register),
LoadWord(Register, Offset, Register),
LoadByte(Register, Offset, Register) = 0x3,
LoadByteSigned(Register, Offset, Register) = 0x4,
LoadHalfword(Register, Offset, Register) = 0x5,
LoadHalfwordSigned(Register, Offset, Register) = 0x6,
LoadWord(Register, Offset, Register) = 0x7,
StoreByte(Register, Offset, Register),
StoreHalfword(Register, Offset, Register),
StoreWord(Register, Offset, Register),
StoreByte(Register, Offset, Register) = 0x8,
StoreHalfword(Register, Offset, Register) = 0x9,
StoreWord(Register, Offset, Register) = 0xA,
LoadLowerImmediate(Register, Immediate),
LoadUpperImmediate(Register, Immediate),
LoadLowerImmediate(Register, Immediate) = 0xB,
LoadUpperImmediate(Register, Immediate) = 0xC,
// Jump Instructions
Jump(Register, Offset),
JumpEq(Register, Offset),
JumpNeq(Register, Offset),
JumpGt(Register, Offset),
JumpGe(Register, Offset),
JumpLt(Register, Offset),
JumpLe(Register, Offset),
Jump(Register, Offset) = 0xD,
JumpEq(Register, Offset) = 0xE,
JumpNeq(Register, Offset) = 0xF,
JumpGt(Register, Offset) = 0x10,
JumpGe(Register, Offset) = 0x11,
JumpLt(Register, Offset) = 0x12,
JumpLe(Register, Offset) = 0x13,
// Comparison
Compare(Register, Register),
Compare(Register, Register) = 0x14,
// Arithmetic
Add(Register, Register, Register),
Sub(Register, Register, Register),
Increment(Register),
Decrement(Register),
ShiftLeft(Register, Register, Immediate),
ShiftRight(Register, Register, Immediate),
Add(Register, Register, Register) = 0x19,
Sub(Register, Register, Register) = 0x1A,
Increment(Register) = 0x15,
Decrement(Register) = 0x16,
ShiftLeft(Register, Register, Immediate) = 0x17,
ShiftRight(Register, Register, Immediate) = 0x18,
// Logical
And(Register, Register, Register),
Or(Register, Register, Register),
Not(Register, Register),
Xor(Register, Register, Register),
Nand(Register, Register, Register),
Nor(Register, Register, Register),
Xnor(Register, Register, Register),
And(Register, Register, Register) = 0x1B,
Or(Register, Register, Register) = 0x1C,
Not(Register, Register) = 0x1D,
Xor(Register, Register, Register) = 0x1E,
Nand(Register, Register, Register) = 0x1F,
Nor(Register, Register, Register) = 0x20,
Xnor(Register, Register, Register) = 0x21,
// Misc
Interrupt(Interrupt),
IntReturn,
Halt,
Interrupt(Interrupt) = 0x22,
IntReturn = 0x23,
Halt = 0x24,
}
impl Instruction {
/// Returns the opcode of an instruction.
///
/// # Notes
///
/// The top two bits shall be 0, opcodes are 6-bits long.
#[must_use]
pub fn encode(&self) -> u32 {
todo!("imlement instruction encoding")
pub const fn opcode(&self) -> u8 {
unsafe { *std::ptr::from_ref::<Self>(self).cast::<u8>() }
}
#[must_use]
#[allow(unused)]
pub fn encode(&self) -> u32 {
match self {
Self::Nop => todo!(),
Self::Mov(src, dst) => todo!(),
Self::MovSigned(register, register1) => todo!(),
Self::LoadByte(register, _, register1) => todo!(),
Self::LoadByteSigned(register, _, register1) => todo!(),
Self::LoadHalfword(register, _, register1) => todo!(),
Self::LoadHalfwordSigned(register, _, register1) => todo!(),
Self::LoadWord(register, _, register1) => todo!(),
Self::StoreByte(register, _, register1) => todo!(),
Self::StoreHalfword(register, _, register1) => todo!(),
Self::StoreWord(register, _, register1) => todo!(),
Self::LoadLowerImmediate(register, _) => todo!(),
Self::LoadUpperImmediate(register, _) => todo!(),
Self::Jump(register, _) => todo!(),
Self::JumpEq(register, _) => todo!(),
Self::JumpNeq(register, _) => todo!(),
Self::JumpGt(register, _) => todo!(),
Self::JumpGe(register, _) => todo!(),
Self::JumpLt(register, _) => todo!(),
Self::JumpLe(register, _) => todo!(),
Self::Compare(register, register1) => todo!(),
Self::Add(register, register1, register2) => todo!(),
Self::Sub(register, register1, register2) => todo!(),
Self::Increment(register) => todo!(),
Self::Decrement(register) => todo!(),
Self::ShiftLeft(register, register1, _) => todo!(),
Self::ShiftRight(register, register1, _) => todo!(),
Self::And(register, register1, register2) => todo!(),
Self::Or(register, register1, register2) => todo!(),
Self::Not(register, register1) => todo!(),
Self::Xor(register, register1, register2) => todo!(),
Self::Nand(register, register1, register2) => todo!(),
Self::Nor(register, register1, register2) => todo!(),
Self::Xnor(register, register1, register2) => todo!(),
Self::Interrupt(interrupt) => todo!(),
Self::IntReturn => todo!(),
Self::Halt => todo!(),
}
}
const fn _encode_mov() {}
#[must_use]
pub const fn decode(_data: u32) -> Self {
// TODO: this needs to actually decode something
+3
View File
@@ -1 +1,4 @@
pub mod instructions;
#[cfg(test)]
mod tests;
+175
View File
@@ -0,0 +1,175 @@
use crate::common::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);
}
#[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
);
}
#[test]
fn test_opcode_immediate_instructions() {
assert_eq!(
Instruction::LoadLowerImmediate(Register::Rg0, 0).opcode(),
0xB
);
assert_eq!(
Instruction::LoadUpperImmediate(Register::Rg0, 0).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);
}
#[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
);
}
#[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
);
}
#[test]
fn test_opcode_system_instructions() {
assert_eq!(
Instruction::Interrupt(Interrupt::Software(0)).opcode(),
0x22
);
assert_eq!(Instruction::IntReturn.opcode(), 0x23);
}
#[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,
];
for instruction in instructions {
let opcode = instruction.opcode();
assert_eq!(
opcode & 0xC0,
0,
"Top 2 bits should be 0 for opcode {opcode:#02x}"
);
}
}
#[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()
);
}
+1 -1
View File
@@ -48,7 +48,7 @@ impl Processor {
// decode and execute the instruction
let instruction = Instruction::decode(val);
instruction.clone().execute(self);
instruction.execute(self);
instruction
}