From 16385f6387ad046893b7ab5b75ab2e831cc2b6e5 Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Sat, 16 Apr 2022 10:59:07 -0400 Subject: [PATCH] Break out instruction running code into its own module --- src/{run.rs => cpu.rs} | 275 +-------------------- src/{run => cpu}/instructions.rs | 0 src/{run => cpu}/instructions/arith8.rs | 0 src/{run => cpu}/instructions/load16.rs | 0 src/{run => cpu}/instructions/load8.rs | 0 src/{run => cpu}/parse.rs | 0 src/{run => cpu}/parse/arith8.rs | 0 src/{run => cpu}/parse/arith8/add.rs | 2 +- src/{run => cpu}/parse/load16.rs | 0 src/{run => cpu}/parse/load16/immediate.rs | 4 +- src/{run => cpu}/parse/load16/stack.rs | 6 +- src/{run => cpu}/parse/load16/transfer.rs | 6 +- src/{run => cpu}/parse/load8.rs | 0 src/{run => cpu}/parse/load8/immediate.rs | 6 +- src/{run => cpu}/parse/load8/memory.rs | 6 +- src/{run => cpu}/parse/load8/transfer.rs | 6 +- src/cpu/run.rs | 29 +++ src/cpu/run/arith8.rs | 47 ++++ src/cpu/run/load16.rs | 118 +++++++++ src/cpu/run/load8.rs | 136 ++++++++++ src/cpu/run/util.rs | 8 + src/lib.rs | 2 +- 22 files changed, 361 insertions(+), 290 deletions(-) rename src/{run.rs => cpu.rs} (69%) rename src/{run => cpu}/instructions.rs (100%) rename src/{run => cpu}/instructions/arith8.rs (100%) rename src/{run => cpu}/instructions/load16.rs (100%) rename src/{run => cpu}/instructions/load8.rs (100%) rename src/{run => cpu}/parse.rs (100%) rename src/{run => cpu}/parse/arith8.rs (100%) rename src/{run => cpu}/parse/arith8/add.rs (99%) rename src/{run => cpu}/parse/load16.rs (100%) rename src/{run => cpu}/parse/load16/immediate.rs (97%) rename src/{run => cpu}/parse/load16/stack.rs (91%) rename src/{run => cpu}/parse/load16/transfer.rs (91%) rename src/{run => cpu}/parse/load8.rs (100%) rename src/{run => cpu}/parse/load8/immediate.rs (94%) rename src/{run => cpu}/parse/load8/memory.rs (98%) rename src/{run => cpu}/parse/load8/transfer.rs (97%) create mode 100644 src/cpu/run.rs create mode 100644 src/cpu/run/arith8.rs create mode 100644 src/cpu/run/load16.rs create mode 100644 src/cpu/run/load8.rs create mode 100644 src/cpu/run/util.rs diff --git a/src/run.rs b/src/cpu.rs similarity index 69% rename from src/run.rs rename to src/cpu.rs index f4c049b..8bed10a 100644 --- a/src/run.rs +++ b/src/cpu.rs @@ -1,18 +1,14 @@ -use instructions::{Instruction, RunnableInstruction}; +use instructions::RunnableInstruction; use crate::{ - memory::{self, Memory}, + memory::Memory, register::{self, Registers}, }; use thiserror::Error; -use self::instructions::{ - arith8::EightBitArithmeticInstruction, load16::SixteenBitLoadInstruction, - load8::EightBitLoadInstruction, -}; - mod instructions; mod parse; +mod run; /// `Error` represents a run error. Typically these will be fatal and panicking on their emission /// is likely the best course of action @@ -33,275 +29,12 @@ pub struct Processor { memory: Memory, } -macro_rules! assert_ok { - ($result: expr) => { - let res: Result<_, _> = $result; - assert!(res.is_ok(), "{}", res.unwrap_err()); - }; -} - impl Processor { fn run(&mut self, instruction: &RunnableInstruction) { - match &instruction.instruction { - Instruction::EightBitLoad(load_instruction) => self.run_8_bit_load(load_instruction), - Instruction::SixteenBitLoad(load_instruction) => self.run_16_bit_load(load_instruction), - Instruction::EightBitArithmetic(arith_instruction) => { - self.run_8_bit_arithmetic(arith_instruction); - } - } - + run::run_instruction(self, &instruction.instruction); self.num_cycles += u64::from(instruction.cycles); } - fn run_8_bit_load(&mut self, instruction: &EightBitLoadInstruction) { - match *instruction { - EightBitLoadInstruction::LoadImmediateToRegister { value, register } => { - self.registers.set_single_8bit_register(register, value); - } - - EightBitLoadInstruction::LoadBetweenRegisters { dst, src } => { - let src_value = self.registers.get_single_8bit_register(src); - self.registers.set_single_8bit_register(dst, src_value); - } - - EightBitLoadInstruction::LoadFromRegisterAddress { src, dst } => { - let load_res = self.load_from_register_address_to_register(dst, src); - assert_ok!(load_res); - } - - EightBitLoadInstruction::LoadFromImmediateAddress { src_address, dst } => { - let load_res = self.load_from_address_to_register(dst, src_address.into()); - assert_ok!(load_res); - } - - EightBitLoadInstruction::LoadToRegisterAddress { src, dst } => { - let load_res = self.load_from_register_to_register_address(dst, src); - assert_ok!(load_res); - } - - EightBitLoadInstruction::LoadToImmediateAddress { src, dst_address } => { - let load_res = self.load_from_register_to_address(dst_address.into(), src); - assert_ok!(load_res); - } - - EightBitLoadInstruction::LoadnToHLAddress { value } => { - let dest_address = self.registers.get_combined_register(register::Combined::HL); - let load_res = self.load_8bit_immediate_to_address(dest_address.into(), value); - assert_ok!(load_res); - } - - EightBitLoadInstruction::LoadFromMemoryRelativeToIORegisterStart { - offset_register, - dst, - } => { - let src_address_offset = self.registers.get_single_8bit_register(offset_register); - let src_address = - memory::IO_REGISTER_START_ADDRESS + usize::from(src_address_offset); - let load_res = self.load_from_address_to_register(dst, src_address); - assert_ok!(load_res); - } - - EightBitLoadInstruction::LoadToMemoryRelativeToIORegisterStart { - src, - offset_register, - } => { - let dst_address_offset = self.registers.get_single_8bit_register(offset_register); - let dst_address = - memory::IO_REGISTER_START_ADDRESS + usize::from(dst_address_offset); - let load_res = self.load_from_register_to_address(dst_address, src); - assert_ok!(load_res); - } - - EightBitLoadInstruction::LoadFromRegisterAddressThenDec { dst, src } => { - let load_res = self.load_from_register_address_to_register(dst, src); - assert_ok!(load_res); - - let src_address = self.registers.get_combined_register(src); - self.registers.set_combined_register(src, src_address - 1); - } - - EightBitLoadInstruction::LoadFromRegisterAddressThenInc { dst, src } => { - let load_res = self.load_from_register_address_to_register(dst, src); - assert_ok!(load_res); - - let src_address = self.registers.get_combined_register(src); - self.registers.set_combined_register(src, src_address + 1); - } - - EightBitLoadInstruction::LoadToRegisterAddressThenDec { dst, src } => { - let dst_address = self.registers.get_combined_register(dst); - let load_res = self.load_from_register_to_address(dst_address.into(), src); - assert_ok!(load_res); - - self.registers.set_combined_register(dst, dst_address - 1); - } - - EightBitLoadInstruction::LoadToRegisterAddressThenInc { dst, src } => { - let dst_address = self.registers.get_combined_register(dst); - let load_res = self.load_from_register_to_address(dst_address.into(), src); - assert_ok!(load_res); - - self.registers.set_combined_register(dst, dst_address + 1); - } - - EightBitLoadInstruction::LoadToMemoryRelativeToIORegisterStartByImmediate { - src, - offset, - } => { - let dst_address = memory::IO_REGISTER_START_ADDRESS + usize::from(offset); - let load_res = self.load_from_register_to_address(dst_address, src); - assert_ok!(load_res); - } - - EightBitLoadInstruction::LoadFromMemoryRelativeToIORegisterStartByImmediate { - dst, - offset, - } => { - let src_address = memory::IO_REGISTER_START_ADDRESS + usize::from(offset); - let load_res = self.load_from_address_to_register(dst, src_address); - assert_ok!(load_res); - } - } - } - - fn run_16_bit_load(&mut self, instruction: &SixteenBitLoadInstruction) { - match *instruction { - SixteenBitLoadInstruction::LoadImmediateToRegister { dst, value } => { - self.registers.set_16bit_register(dst, value); - } - - SixteenBitLoadInstruction::LoadBetweenRegisters { dst, src } => { - let value = self.registers.get_16bit_register(src); - self.registers.set_16bit_register(dst, value); - } - - SixteenBitLoadInstruction::LoadEffectiveAddress { dst, offset } => { - let current_sp = self - .registers - .get_single_16bit_register(register::SingleSixteenBit::StackPointer); - - // TODO: This is gross. I'll clean this up when I do more ALU instructions - // because there's bound to be reuse - let (sixteen_bit_offset, new_sp, carry) = if offset >= 0 { - let sixteen_bit_offset = u16::try_from(offset) - .expect("failed to put positive (or zero) 8 bit value into unsigned sixteen bit, which should always work..."); - let new_sp = current_sp + sixteen_bit_offset; - // If the lower eight bits are lower than where we started (with a known positive number) - // we must have carried. - let carry = - (((current_sp & 0xff) + (sixteen_bit_offset & 0xff)) & 0x100) == 0x100; - - (sixteen_bit_offset, new_sp, carry) - } else { - let sixteen_bit_offset = u16::try_from(i16::from(offset).abs()) - .expect("failed to convert an abs'd 16 bit value to a u16"); - let (new_sp, underflow) = current_sp.overflowing_sub(sixteen_bit_offset); - - (sixteen_bit_offset, new_sp, underflow) - }; - - let carry_bit = if carry { 1 } else { 0 }; - let half_carry_bit = - // https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ - if ((current_sp & 0xf) + (sixteen_bit_offset & 0xf)) & 0x10 == 0x10 { - 1 - } else { - 0 - }; - - self.registers - .set_flag_bit(register::Flag::Carry, carry_bit); - self.registers - .set_flag_bit(register::Flag::HalfCarry, half_carry_bit); - - // Manual says we reset these here. - self.registers.set_flag_bit(register::Flag::Subtract, 0); - self.registers.set_flag_bit(register::Flag::Zero, 0); - - self.registers.set_combined_register(dst, new_sp); - } - - SixteenBitLoadInstruction::Push { src } => { - let current_sp = self - .registers - .get_single_16bit_register(register::SingleSixteenBit::StackPointer); - - // we want to pop the LSB first (i.e. we write the MSB first) - // https://rgbds.gbdev.io/docs/v0.5.2/gbz80.7#PUSH_r16 - let [lower_bits, higher_bits] = - self.registers.get_combined_register(src).to_le_bytes(); - self.memory.set_both(( - ((current_sp - 1).into(), higher_bits), - ((current_sp - 2).into(), lower_bits), - )); - - self.registers.set_16bit_register( - register::SixteenBit::Single(register::SingleSixteenBit::StackPointer), - current_sp - 2, - ); - } - - SixteenBitLoadInstruction::Pop { dst } => { - let current_sp = self - .registers - .get_single_16bit_register(register::SingleSixteenBit::StackPointer); - - let popped_bytes = [ - self.memory - .get(current_sp.into()) - .expect("stack pointer pointed to invalid address"), - self.memory - .get((current_sp + 1).into()) - .expect("stack pointer pointed to invalid address"), - ]; - - let popped_value = u16::from_le_bytes(popped_bytes); - - self.registers.set_combined_register(dst, popped_value); - self.registers.set_16bit_register( - register::SixteenBit::Single(register::SingleSixteenBit::StackPointer), - current_sp + 2, - ); - } - } - } - - fn run_8_bit_arithmetic(&mut self, instruction: &EightBitArithmeticInstruction) { - match *instruction { - EightBitArithmeticInstruction::AddSingleRegisterToA { src } => { - let a_value = self - .registers - .get_single_8bit_register(register::SingleEightBit::A); - let src_value = self.registers.get_single_8bit_register(src); - let (added_value, carry) = a_value.overflowing_add(src_value); - - self.registers - .set_single_8bit_register(register::SingleEightBit::A, added_value); - - self.registers.set_flag_bit( - register::Flag::Zero, - // carry must be false, as if it's true, the value is indeed > 0 - if added_value == 0 && !carry { 1 } else { 0 }, - ); - - // TODO: This is copy/paste from the stack add above; break this out - let half_carry_bit = - // https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ - if ((a_value & 0xf) + (src_value & 0xf)) & 0x10 == 0x10 { - 1 - } else { - 0 - }; - self.registers - .set_flag_bit(register::Flag::HalfCarry, half_carry_bit); - - let full_carry_bit = if carry { 1 } else { 0 }; - self.registers - .set_flag_bit(register::Flag::Carry, full_carry_bit); - } - } - } - fn load_from_register_to_register_address( &mut self, register_with_dst_address: register::Combined, diff --git a/src/run/instructions.rs b/src/cpu/instructions.rs similarity index 100% rename from src/run/instructions.rs rename to src/cpu/instructions.rs diff --git a/src/run/instructions/arith8.rs b/src/cpu/instructions/arith8.rs similarity index 100% rename from src/run/instructions/arith8.rs rename to src/cpu/instructions/arith8.rs diff --git a/src/run/instructions/load16.rs b/src/cpu/instructions/load16.rs similarity index 100% rename from src/run/instructions/load16.rs rename to src/cpu/instructions/load16.rs diff --git a/src/run/instructions/load8.rs b/src/cpu/instructions/load8.rs similarity index 100% rename from src/run/instructions/load8.rs rename to src/cpu/instructions/load8.rs diff --git a/src/run/parse.rs b/src/cpu/parse.rs similarity index 100% rename from src/run/parse.rs rename to src/cpu/parse.rs diff --git a/src/run/parse/arith8.rs b/src/cpu/parse/arith8.rs similarity index 100% rename from src/run/parse/arith8.rs rename to src/cpu/parse/arith8.rs diff --git a/src/run/parse/arith8/add.rs b/src/cpu/parse/arith8/add.rs similarity index 99% rename from src/run/parse/arith8/add.rs rename to src/cpu/parse/arith8/add.rs index 47f53dd..4f5ab8f 100644 --- a/src/run/parse/arith8/add.rs +++ b/src/cpu/parse/arith8/add.rs @@ -1,6 +1,6 @@ use crate::{ register, - run::{ + cpu::{ instructions::{arith8::EightBitArithmeticInstruction, Instruction, RunnableInstruction}, parse::{self, Error, OpcodeParser, ParseResult}, }, diff --git a/src/run/parse/load16.rs b/src/cpu/parse/load16.rs similarity index 100% rename from src/run/parse/load16.rs rename to src/cpu/parse/load16.rs diff --git a/src/run/parse/load16/immediate.rs b/src/cpu/parse/load16/immediate.rs similarity index 97% rename from src/run/parse/load16/immediate.rs rename to src/cpu/parse/load16/immediate.rs index cfe1ccd..bf24e81 100644 --- a/src/run/parse/load16/immediate.rs +++ b/src/cpu/parse/load16/immediate.rs @@ -1,6 +1,6 @@ use crate::register; -use crate::run::instructions::load16::SixteenBitLoadInstruction; -use crate::run::{ +use crate::cpu::instructions::load16::SixteenBitLoadInstruction; +use crate::cpu::{ instructions::{Instruction, RunnableInstruction}, parse::{self, Error, OpcodeParser, ParseResult}, }; diff --git a/src/run/parse/load16/stack.rs b/src/cpu/parse/load16/stack.rs similarity index 91% rename from src/run/parse/load16/stack.rs rename to src/cpu/parse/load16/stack.rs index 54e5029..351bd17 100644 --- a/src/run/parse/load16/stack.rs +++ b/src/cpu/parse/load16/stack.rs @@ -1,7 +1,7 @@ use crate::register; -use crate::run::instructions::load16::SixteenBitLoadInstruction; -use crate::run::instructions::{Instruction, RunnableInstruction}; -use crate::run::parse::{self, Error, OpcodeParser, ParseResult}; +use crate::cpu::instructions::load16::SixteenBitLoadInstruction; +use crate::cpu::instructions::{Instruction, RunnableInstruction}; +use crate::cpu::parse::{self, Error, OpcodeParser, ParseResult}; #[allow(clippy::module_name_repetitions)] pub struct StackLoadParser; diff --git a/src/run/parse/load16/transfer.rs b/src/cpu/parse/load16/transfer.rs similarity index 91% rename from src/run/parse/load16/transfer.rs rename to src/cpu/parse/load16/transfer.rs index b0f9b80..0133e6c 100644 --- a/src/run/parse/load16/transfer.rs +++ b/src/cpu/parse/load16/transfer.rs @@ -1,7 +1,7 @@ use crate::register; -use crate::run::instructions::load16::SixteenBitLoadInstruction; -use crate::run::instructions::Instruction; -use crate::run::{ +use crate::cpu::instructions::load16::SixteenBitLoadInstruction; +use crate::cpu::instructions::Instruction; +use crate::cpu::{ instructions::RunnableInstruction, parse::{self, Error, OpcodeParser, ParseResult}, }; diff --git a/src/run/parse/load8.rs b/src/cpu/parse/load8.rs similarity index 100% rename from src/run/parse/load8.rs rename to src/cpu/parse/load8.rs diff --git a/src/run/parse/load8/immediate.rs b/src/cpu/parse/load8/immediate.rs similarity index 94% rename from src/run/parse/load8/immediate.rs rename to src/cpu/parse/load8/immediate.rs index 7fd1db3..fbb4783 100644 --- a/src/run/parse/load8/immediate.rs +++ b/src/cpu/parse/load8/immediate.rs @@ -1,7 +1,7 @@ use crate::register; -use crate::run::instructions::load8::EightBitLoadInstruction; -use crate::run::instructions::Instruction; -use crate::run::{ +use crate::cpu::instructions::load8::EightBitLoadInstruction; +use crate::cpu::instructions::Instruction; +use crate::cpu::{ instructions::RunnableInstruction, parse::{self, Error, OpcodeParser, ParseResult}, }; diff --git a/src/run/parse/load8/memory.rs b/src/cpu/parse/load8/memory.rs similarity index 98% rename from src/run/parse/load8/memory.rs rename to src/cpu/parse/load8/memory.rs index 7d4a973..58cee3d 100644 --- a/src/run/parse/load8/memory.rs +++ b/src/cpu/parse/load8/memory.rs @@ -1,7 +1,7 @@ use crate::register; -use crate::run::instructions::load8::EightBitLoadInstruction; -use crate::run::instructions::{Instruction, RunnableInstruction}; -use crate::run::parse::{self, Error, OpcodeParser, ParseResult}; +use crate::cpu::instructions::load8::EightBitLoadInstruction; +use crate::cpu::instructions::{Instruction, RunnableInstruction}; +use crate::cpu::parse::{self, Error, OpcodeParser, ParseResult}; #[allow(clippy::module_name_repetitions)] pub struct Memory8BitLoadParser; diff --git a/src/run/parse/load8/transfer.rs b/src/cpu/parse/load8/transfer.rs similarity index 97% rename from src/run/parse/load8/transfer.rs rename to src/cpu/parse/load8/transfer.rs index 84cc61e..a7ad8c6 100644 --- a/src/run/parse/load8/transfer.rs +++ b/src/cpu/parse/load8/transfer.rs @@ -1,7 +1,7 @@ use crate::register; -use crate::run::instructions::load8::EightBitLoadInstruction; -use crate::run::instructions::Instruction; -use crate::run::{ +use crate::cpu::instructions::load8::EightBitLoadInstruction; +use crate::cpu::instructions::Instruction; +use crate::cpu::{ instructions::RunnableInstruction, parse::{self, Error, OpcodeParser, ParseResult}, }; diff --git a/src/cpu/run.rs b/src/cpu/run.rs new file mode 100644 index 0000000..a20708d --- /dev/null +++ b/src/cpu/run.rs @@ -0,0 +1,29 @@ +use super::{instructions::Instruction, Processor}; + +mod arith8; +mod load16; +mod load8; +mod util; + +/// `InstructionRunner` takes a single instruction and runs it on the given processor. +trait InstructionRunner { + // TODO: Make this return a result instead of impls panicking like they do now. + fn run_instruction(processor: &mut Processor, instruction: &I); +} + +/// `run_instruction` will run the given instruction on the processor. +// TODO: Right now this panics, but this should return a result of some kind. +#[allow(clippy::module_name_repetitions)] +pub fn run_instruction(processor: &mut Processor, instruction: &Instruction) { + match instruction { + Instruction::EightBitLoad(load_instruction) => { + load8::EightBitLoadRunner::run_instruction(processor, load_instruction); + } + Instruction::SixteenBitLoad(load_instruction) => { + load16::SixteenBitLoadRunner::run_instruction(processor, load_instruction); + } + Instruction::EightBitArithmetic(arith_instruction) => { + arith8::EightBitArithmeticRunner::run_instruction(processor, arith_instruction); + } + } +} diff --git a/src/cpu/run/arith8.rs b/src/cpu/run/arith8.rs new file mode 100644 index 0000000..be1b318 --- /dev/null +++ b/src/cpu/run/arith8.rs @@ -0,0 +1,47 @@ +use crate::cpu::{instructions::arith8::EightBitArithmeticInstruction, Processor}; +use crate::register; + +use super::InstructionRunner; + +pub(super) struct EightBitArithmeticRunner; + +impl InstructionRunner for EightBitArithmeticRunner { + fn run_instruction(processor: &mut Processor, instruction: &EightBitArithmeticInstruction) { + match *instruction { + EightBitArithmeticInstruction::AddSingleRegisterToA { src } => { + let a_value = processor + .registers + .get_single_8bit_register(register::SingleEightBit::A); + let src_value = processor.registers.get_single_8bit_register(src); + let (added_value, carry) = a_value.overflowing_add(src_value); + + processor + .registers + .set_single_8bit_register(register::SingleEightBit::A, added_value); + + processor.registers.set_flag_bit( + register::Flag::Zero, + // carry must be false, as if it's true, the value is indeed > 0 + if added_value == 0 && !carry { 1 } else { 0 }, + ); + + // TODO: This is copy/paste from the stack add above; break this out + let half_carry_bit = + // https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ + if ((a_value & 0xf) + (src_value & 0xf)) & 0x10 == 0x10 { + 1 + } else { + 0 + }; + processor + .registers + .set_flag_bit(register::Flag::HalfCarry, half_carry_bit); + + let full_carry_bit = if carry { 1 } else { 0 }; + processor + .registers + .set_flag_bit(register::Flag::Carry, full_carry_bit); + } + } + } +} diff --git a/src/cpu/run/load16.rs b/src/cpu/run/load16.rs new file mode 100644 index 0000000..8d067e6 --- /dev/null +++ b/src/cpu/run/load16.rs @@ -0,0 +1,118 @@ +use super::InstructionRunner; +use crate::cpu::{instructions::load16::SixteenBitLoadInstruction, Processor}; +use crate::register; + +pub(super) struct SixteenBitLoadRunner; + +impl InstructionRunner for SixteenBitLoadRunner { + fn run_instruction(processor: &mut Processor, instruction: &SixteenBitLoadInstruction) { + match *instruction { + SixteenBitLoadInstruction::LoadImmediateToRegister { dst, value } => { + processor.registers.set_16bit_register(dst, value); + } + + SixteenBitLoadInstruction::LoadBetweenRegisters { dst, src } => { + let value = processor.registers.get_16bit_register(src); + processor.registers.set_16bit_register(dst, value); + } + + SixteenBitLoadInstruction::LoadEffectiveAddress { dst, offset } => { + let current_sp = processor + .registers + .get_single_16bit_register(register::SingleSixteenBit::StackPointer); + + // TODO: This is gross. I'll clean this up when I do more ALU instructions + // because there's bound to be reuse + let (sixteen_bit_offset, new_sp, carry) = if offset >= 0 { + let sixteen_bit_offset = u16::try_from(offset) + .expect("failed to put positive (or zero) 8 bit value into unsigned sixteen bit, which should always work..."); + let new_sp = current_sp + sixteen_bit_offset; + // If the lower eight bits are lower than where we started (with a known positive number) + // we must have carried. + let carry = + (((current_sp & 0xff) + (sixteen_bit_offset & 0xff)) & 0x100) == 0x100; + + (sixteen_bit_offset, new_sp, carry) + } else { + let sixteen_bit_offset = u16::try_from(i16::from(offset).abs()) + .expect("failed to convert an abs'd 16 bit value to a u16"); + let (new_sp, underflow) = current_sp.overflowing_sub(sixteen_bit_offset); + + (sixteen_bit_offset, new_sp, underflow) + }; + + let carry_bit = if carry { 1 } else { 0 }; + let half_carry_bit = + // https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ + if ((current_sp & 0xf) + (sixteen_bit_offset & 0xf)) & 0x10 == 0x10 { + 1 + } else { + 0 + }; + + processor + .registers + .set_flag_bit(register::Flag::Carry, carry_bit); + processor + .registers + .set_flag_bit(register::Flag::HalfCarry, half_carry_bit); + + // Manual says we reset these here. + processor + .registers + .set_flag_bit(register::Flag::Subtract, 0); + processor.registers.set_flag_bit(register::Flag::Zero, 0); + + processor.registers.set_combined_register(dst, new_sp); + } + + SixteenBitLoadInstruction::Push { src } => { + let current_sp = processor + .registers + .get_single_16bit_register(register::SingleSixteenBit::StackPointer); + + // we want to pop the LSB first (i.e. we write the MSB first) + // https://rgbds.gbdev.io/docs/v0.5.2/gbz80.7#PUSH_r16 + let [lower_bits, higher_bits] = + processor.registers.get_combined_register(src).to_le_bytes(); + processor + .memory + .set_both(( + ((current_sp - 1).into(), higher_bits), + ((current_sp - 2).into(), lower_bits), + )) + .expect("failed to load to stack"); + + processor.registers.set_16bit_register( + register::SixteenBit::Single(register::SingleSixteenBit::StackPointer), + current_sp - 2, + ); + } + + SixteenBitLoadInstruction::Pop { dst } => { + let current_sp = processor + .registers + .get_single_16bit_register(register::SingleSixteenBit::StackPointer); + + let popped_bytes = [ + processor + .memory + .get(current_sp.into()) + .expect("stack pointer pointed to invalid address"), + processor + .memory + .get((current_sp + 1).into()) + .expect("stack pointer pointed to invalid address"), + ]; + + let popped_value = u16::from_le_bytes(popped_bytes); + + processor.registers.set_combined_register(dst, popped_value); + processor.registers.set_16bit_register( + register::SixteenBit::Single(register::SingleSixteenBit::StackPointer), + current_sp + 2, + ); + } + } + } +} diff --git a/src/cpu/run/load8.rs b/src/cpu/run/load8.rs new file mode 100644 index 0000000..fb51612 --- /dev/null +++ b/src/cpu/run/load8.rs @@ -0,0 +1,136 @@ +use super::{util::assert_ok, InstructionRunner}; +use crate::cpu::{instructions::load8::EightBitLoadInstruction, Processor}; +use crate::{memory, register}; + +pub(super) struct EightBitLoadRunner; + +impl InstructionRunner for EightBitLoadRunner { + // TODO: Break this up somehow + #[allow(clippy::too_many_lines)] + fn run_instruction(processor: &mut Processor, instruction: &EightBitLoadInstruction) { + match *instruction { + EightBitLoadInstruction::LoadImmediateToRegister { value, register } => { + processor + .registers + .set_single_8bit_register(register, value); + } + + EightBitLoadInstruction::LoadBetweenRegisters { dst, src } => { + let src_value = processor.registers.get_single_8bit_register(src); + processor.registers.set_single_8bit_register(dst, src_value); + } + + EightBitLoadInstruction::LoadFromRegisterAddress { src, dst } => { + let load_res = processor.load_from_register_address_to_register(dst, src); + assert_ok!(load_res); + } + + EightBitLoadInstruction::LoadFromImmediateAddress { src_address, dst } => { + let load_res = processor.load_from_address_to_register(dst, src_address.into()); + assert_ok!(load_res); + } + + EightBitLoadInstruction::LoadToRegisterAddress { src, dst } => { + let load_res = processor.load_from_register_to_register_address(dst, src); + assert_ok!(load_res); + } + + EightBitLoadInstruction::LoadToImmediateAddress { src, dst_address } => { + let load_res = processor.load_from_register_to_address(dst_address.into(), src); + assert_ok!(load_res); + } + + EightBitLoadInstruction::LoadnToHLAddress { value } => { + let dest_address = processor + .registers + .get_combined_register(register::Combined::HL); + let load_res = processor.load_8bit_immediate_to_address(dest_address.into(), value); + assert_ok!(load_res); + } + + EightBitLoadInstruction::LoadFromMemoryRelativeToIORegisterStart { + offset_register, + dst, + } => { + let src_address_offset = processor + .registers + .get_single_8bit_register(offset_register); + let src_address = + memory::IO_REGISTER_START_ADDRESS + usize::from(src_address_offset); + let load_res = processor.load_from_address_to_register(dst, src_address); + assert_ok!(load_res); + } + + EightBitLoadInstruction::LoadToMemoryRelativeToIORegisterStart { + src, + offset_register, + } => { + let dst_address_offset = processor + .registers + .get_single_8bit_register(offset_register); + let dst_address = + memory::IO_REGISTER_START_ADDRESS + usize::from(dst_address_offset); + let load_res = processor.load_from_register_to_address(dst_address, src); + assert_ok!(load_res); + } + + EightBitLoadInstruction::LoadFromRegisterAddressThenDec { dst, src } => { + let load_res = processor.load_from_register_address_to_register(dst, src); + assert_ok!(load_res); + + let src_address = processor.registers.get_combined_register(src); + processor + .registers + .set_combined_register(src, src_address - 1); + } + + EightBitLoadInstruction::LoadFromRegisterAddressThenInc { dst, src } => { + let load_res = processor.load_from_register_address_to_register(dst, src); + assert_ok!(load_res); + + let src_address = processor.registers.get_combined_register(src); + processor + .registers + .set_combined_register(src, src_address + 1); + } + + EightBitLoadInstruction::LoadToRegisterAddressThenDec { dst, src } => { + let dst_address = processor.registers.get_combined_register(dst); + let load_res = processor.load_from_register_to_address(dst_address.into(), src); + assert_ok!(load_res); + + processor + .registers + .set_combined_register(dst, dst_address - 1); + } + + EightBitLoadInstruction::LoadToRegisterAddressThenInc { dst, src } => { + let dst_address = processor.registers.get_combined_register(dst); + let load_res = processor.load_from_register_to_address(dst_address.into(), src); + assert_ok!(load_res); + + processor + .registers + .set_combined_register(dst, dst_address + 1); + } + + EightBitLoadInstruction::LoadToMemoryRelativeToIORegisterStartByImmediate { + src, + offset, + } => { + let dst_address = memory::IO_REGISTER_START_ADDRESS + usize::from(offset); + let load_res = processor.load_from_register_to_address(dst_address, src); + assert_ok!(load_res); + } + + EightBitLoadInstruction::LoadFromMemoryRelativeToIORegisterStartByImmediate { + dst, + offset, + } => { + let src_address = memory::IO_REGISTER_START_ADDRESS + usize::from(offset); + let load_res = processor.load_from_address_to_register(dst, src_address); + assert_ok!(load_res); + } + } + } +} diff --git a/src/cpu/run/util.rs b/src/cpu/run/util.rs new file mode 100644 index 0000000..aecfdca --- /dev/null +++ b/src/cpu/run/util.rs @@ -0,0 +1,8 @@ +macro_rules! assert_ok { + ($result: expr) => { + let res: Result<_, _> = $result; + assert!(res.is_ok(), "{}", res.unwrap_err()); + }; +} + +pub(super) use assert_ok; diff --git a/src/lib.rs b/src/lib.rs index 7abac75..912918d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,4 +2,4 @@ mod memory; mod register; -mod run; +mod cpu;