diff --git a/src/run.rs b/src/run.rs new file mode 100644 index 0000000..4b95629 --- /dev/null +++ b/src/run.rs @@ -0,0 +1,96 @@ +use std::time::Instant; + +use crate::register::{self, Registers}; + +#[derive(Debug, Default, Clone)] +pub struct Processor { + num_cycles: u64, + registers: Registers, +} + +// these are indexed with the instruction numbers in this manual +// http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf +pub enum Instruction { + // 3.3.1.1 + LDnnn { + value: u8, + register: register::Single, + }, +} + +struct RunnableInstruction { + instruction: Instruction, + cycles: u8, +} + +impl RunnableInstruction { + /// `from_data` will produce an instruction from the given data and return the data after + /// processing that operation. + pub fn from_data(data: &[u8]) -> (Self, &[u8]) { + // TODO: Maybe we should return an err? I'm somewhat of the mind maybe we should + // panic with bad data because what really can we do? + let opcode = data[0]; + match opcode { + 0x06 => Self::make_ldnn_data(register::Single::B, data), + 0x0E => Self::make_ldnn_data(register::Single::C, data), + 0x16 => Self::make_ldnn_data(register::Single::D, data), + 0x1E => Self::make_ldnn_data(register::Single::E, data), + 0x26 => Self::make_ldnn_data(register::Single::H, data), + 0x2E => Self::make_ldnn_data(register::Single::L, data), + _ => panic!("invalid opcode {opcode:0x}"), + } + } + + fn make_ldnn_data(register: register::Single, data: &[u8]) -> (RunnableInstruction, &[u8]) { + ( + RunnableInstruction { + instruction: Instruction::LDnnn { + register, + value: data[1], + }, + cycles: 8, + }, + &data[2..], + ) + } +} + +impl Processor { + fn run(&mut self, instruction: &RunnableInstruction) { + match instruction.instruction { + Instruction::LDnnn { value, register } => { + self.registers.set_single_register(register, value); + } + } + + self.num_cycles += u64::from(instruction.cycles); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + #[test_case(0x06, register::Single::B)] + #[test_case(0x0E, register::Single::C)] + #[test_case(0x16, register::Single::D)] + #[test_case(0x1E, register::Single::E)] + #[test_case(0x26, register::Single::H)] + #[test_case(0x2E, register::Single::L)] + fn test_load_immediate(ld_opcode: u8, expected_register: register::Single) { + let mut processor = Processor::default(); + let data = [ld_opcode, 0x23, 0x00, 0x01]; + let (ins, extra_data) = RunnableInstruction::from_data(&data); + + // assert our extra data after the instruction is returned + // this data is just garbage; no idea if it's a valid instruction + assert_eq!(extra_data, &[0x00, 0x01]); + + processor.run(&ins); + assert_eq!( + 0x23, + processor.registers.get_single_register(expected_register) + ); + } +}