diff --git a/src/cpu/instructions/arith8.rs b/src/cpu/instructions/arith8.rs index c387d10..5bbd221 100644 --- a/src/cpu/instructions/arith8.rs +++ b/src/cpu/instructions/arith8.rs @@ -11,4 +11,6 @@ pub enum EightBitArithmeticInstruction { AddSingleRegisterToAWithCarry { src: register::SingleEightBit }, AddImmediateToAWithCarry { n: u8 }, AddHLAddressToAWithCarry, + + SubSingleRegisterFromA { src: register::SingleEightBit }, } diff --git a/src/cpu/parse.rs b/src/cpu/parse.rs index 105323d..951ea3a 100644 --- a/src/cpu/parse.rs +++ b/src/cpu/parse.rs @@ -42,6 +42,7 @@ pub fn next_instruction(data: &View) -> ParseResult { load16::transfer::Between16BitRegisterParser::parse_opcode, load16::stack::StackLoadParser::parse_opcode, arith8::add::EightBitAddParser::parse_opcode, + arith8::sub::EightBitSubParser::parse_opcode, ]; for parse_func in parse_funcs { diff --git a/src/cpu/parse/arith8.rs b/src/cpu/parse/arith8.rs index cced7b4..42f1cf0 100644 --- a/src/cpu/parse/arith8.rs +++ b/src/cpu/parse/arith8.rs @@ -1 +1,2 @@ pub mod add; +pub mod sub; diff --git a/src/cpu/parse/arith8/sub.rs b/src/cpu/parse/arith8/sub.rs new file mode 100644 index 0000000..938e03f --- /dev/null +++ b/src/cpu/parse/arith8/sub.rs @@ -0,0 +1,38 @@ +use crate::{ + cpu::{ + instructions::{arith8::EightBitArithmeticInstruction, Instruction, RunnableInstruction}, + parse::{self, Error, OpcodeParser, ParseOutput, ParseResult}, + }, + memory::View, + register, +}; + +pub struct EightBitSubParser; + +impl OpcodeParser for EightBitSubParser { + fn parse_opcode(data: &View) -> ParseResult { + let opcode = parse::get_opcode_from_data(data); + match opcode { + 0x90 => Ok(build_sub_register_from_a_data(register::SingleEightBit::B)), + 0x91 => Ok(build_sub_register_from_a_data(register::SingleEightBit::C)), + 0x92 => Ok(build_sub_register_from_a_data(register::SingleEightBit::D)), + 0x93 => Ok(build_sub_register_from_a_data(register::SingleEightBit::E)), + 0x94 => Ok(build_sub_register_from_a_data(register::SingleEightBit::H)), + 0x95 => Ok(build_sub_register_from_a_data(register::SingleEightBit::L)), + 0x97 => Ok(build_sub_register_from_a_data(register::SingleEightBit::A)), + _ => Err(Error::UnknownOpcode(opcode)), + } + } +} + +fn build_sub_register_from_a_data(src_register: register::SingleEightBit) -> ParseOutput { + ( + RunnableInstruction { + instruction: Instruction::EightBitArithmetic( + EightBitArithmeticInstruction::SubSingleRegisterFromA { src: src_register }, + ), + cycles: 4, + }, + 1, + ) +} diff --git a/src/cpu/run/arith8.rs b/src/cpu/run/arith8.rs index 7105483..58ce762 100644 --- a/src/cpu/run/arith8.rs +++ b/src/cpu/run/arith8.rs @@ -1,7 +1,7 @@ use crate::cpu::{instructions::arith8::EightBitArithmeticInstruction, run::Error, Processor}; use crate::{memory, register}; -use super::arithutil; +use super::arithutil::{self, CarryingSub}; use super::{arithutil::CarryingAdd, InstructionRunner}; pub(super) struct EightBitArithmeticRunner; @@ -120,11 +120,16 @@ impl InstructionRunner for EightBitArithmeticRunn EightBitArithmeticInstruction::AddHLAddressToAWithCarry => { let a_value = processor.registers.a; - let src_address = processor.registers.get_combined_register(register::Combined::HL); + let src_address = processor + .registers + .get_combined_register(register::Combined::HL); // While this is true, we really do want a wildcard match in map_err #[allow(clippy::match_wildcard_for_single_variants)] - let stored_value = processor.memory.get(src_address.into()) + let stored_value = + processor + .memory + .get(src_address.into()) .map_err(|err| match err { memory::Error::GetInvalidAddress(bad_addr) => { Error::InvalidRegisterAddress( @@ -136,7 +141,7 @@ impl InstructionRunner for EightBitArithmeticRunn })?; let carried_operand = arithutil::CarriedNumber::new( stored_value, - processor.registers.get_flag_bit(register::Flag::Carry) + processor.registers.get_flag_bit(register::Flag::Carry), ) .map_err(|err| match err { arithutil::Error::InvalidCarryBit(value) => Error::InvalidCarryFlagValue(value), @@ -146,6 +151,15 @@ impl InstructionRunner for EightBitArithmeticRunn processor.registers.a = result; set_addition_flags(processor, result, half_carry, carry); + Ok(()) + } + EightBitArithmeticInstruction::SubSingleRegisterFromA { src } => { + let a_value = processor.registers.a; + let src_register_value = processor.registers.get_single_8bit_register(src); + let (result, half_carry, full_carry) = a_value.sub_with_carry(src_register_value); + processor.registers.a = result; + set_subtraction_flags(processor, result, half_carry, full_carry); + Ok(()) } } @@ -169,3 +183,10 @@ fn set_addition_flags(processor: &mut Processor, total: u8, half_carry: bool, ca .registers .set_flag_bit(register::Flag::Carry, carry.into()); } + +fn set_subtraction_flags(processor: &mut Processor, total: u8, half_carry: bool, carry: bool) { + set_addition_flags(processor, total, half_carry, carry); + processor + .registers + .set_flag_bit(register::Flag::Subtract, 1); +} diff --git a/src/cpu/run/arithutil.rs b/src/cpu/run/arithutil.rs index 6451d2f..5813501 100644 --- a/src/cpu/run/arithutil.rs +++ b/src/cpu/run/arithutil.rs @@ -63,18 +63,28 @@ pub trait CarryingAdd { fn add_with_carry(self, rhs: R) -> (O, bool, bool); } +/// `CarryingSub` describes a type that can perform addition to produce the carry flags needed for the operation +/// of the gameboy +pub trait CarryingSub { + /// `sub_with_carry` will perform a sub operation, and then return the result, whether or not a half carry was + /// performed, and whether or not a full carry was performed. This can be thought of analgous to + /// [`u8::overflowing_sub`] (or `overflowing_sub` on any of the other numeric types), but with the addition of + /// the half carry flag + fn sub_with_carry(self, rhs: R) -> (O, bool, bool); +} + impl CarryingAdd for u8 { fn add_with_carry(self, rhs: u8) -> (u8, bool, bool) { let (total, carry) = self.overflowing_add(rhs); - (total, did_8bit_half_carry(self, rhs), carry) + (total, did_8bit_add_half_carry(self, rhs), carry) } } impl CarryingAdd for u16 { fn add_with_carry(self, rhs: u8) -> (u16, bool, bool) { let (total, _16_bit_carry) = self.overflowing_add(rhs.into()); - let half_carry = did_8bit_half_carry(self, rhs); + let half_carry = did_8bit_add_half_carry(self, rhs); let full_carry = did_8bit_full_carry(self, rhs); (total, half_carry, full_carry) @@ -92,14 +102,15 @@ impl CarryingAdd for u16 { // with some tweaks to handle overflow let carry_adjustment = if !carry && rhs < 0 { // Subtract from the 8th bit - !(1_u16 << 8)+1 + !(1_u16 << 8) + 1 } else if carry && rhs > 0 { // Add to the 8th bit 1_u16 << 8 } else { 0 }; - let (total, _final_carry) = u16::from_be_bytes([upper_8_bits, lower_8_bit_total]).overflowing_add(carry_adjustment); + let (total, _final_carry) = + u16::from_be_bytes([upper_8_bits, lower_8_bit_total]).overflowing_add(carry_adjustment); (total, half_carry, carry) } @@ -114,20 +125,29 @@ impl CarryingAdd, u8> for u8 { ( smallsized_total, - did_8bit_half_carry_including_carry_bit(self, rhs.num, rhs.carry_bit), + did_8bit_add_half_carry_including_carry_bit(self, rhs.num, rhs.carry_bit), did_8bit_full_carry_including_carry_bit(self, rhs.num, rhs.carry_bit), ) } } +impl CarryingSub for u8 { + fn sub_with_carry(self, rhs: u8) -> (u8, bool, bool) { + let (total, carry) = self.overflowing_sub(rhs); + let half_carry = did_8_bit_sub_half_carry(self, rhs); + + (total, half_carry, carry) + } +} + /// `did_8_bit_half_carry` checks whether or not the given addition would have done an 8 bit half carry. // NOTE: These generics are not as generic as they could be. The Into is just a shortcut because we // only use up to u16s -fn did_8bit_half_carry, R: Into>(lhs: L, rhs: R) -> bool { - did_8bit_half_carry_including_carry_bit(lhs, rhs, 0) +fn did_8bit_add_half_carry, R: Into>(lhs: L, rhs: R) -> bool { + did_8bit_add_half_carry_including_carry_bit(lhs, rhs, 0) } -fn did_8bit_half_carry_including_carry_bit, R: Into>( +fn did_8bit_add_half_carry_including_carry_bit, R: Into>( lhs: L, rhs: R, carry_bit: u8, @@ -136,7 +156,15 @@ fn did_8bit_half_carry_including_carry_bit, R: Into>( assert!(carry_bit <= 1, "carry bit must be zero or one"); // https://stackoverflow.com/a/7261149/1103734 - (lhs.into() & 0xf) + (rhs.into() & 0xf) + u16::from(carry_bit) > 0xf + let four_bit_result = (lhs.into() & 0xf) + .wrapping_add(rhs.into() & 0xf) + .wrapping_add(u16::from(carry_bit)); + + four_bit_result > 0xf +} + +fn did_8_bit_sub_half_carry(lhs: u8, rhs: u8) -> bool { + (lhs & 0xf).wrapping_sub(rhs & 0xf) > 0xf } /// `did_8_bit_full_carry` checks whether or not the given addition would have done an 8 bit carry. @@ -253,4 +281,14 @@ mod tests { ) { assert_eq!(expected, value.add_with_carry(carried_number)); } + + #[test_case(0xFF, 0x0F, (0xF0, false, false); "no borrow")] + #[test_case(0xF0, 0x0F, (0xE1, true, false); "half borrow")] + #[test_case(0x0F, 0xF0, (0x1F, false, true); "full borrow")] + #[test_case(0x0E, 0xFF, (0x0F, true, true); "both borrows")] + #[test_case(0x00, 0x0F, (0xF1, true, true); "wrapping")] + #[test_case(0x00, 0xFF, (0x01, true, true); "wrapping a lot")] + fn test_sub_u8_from_u8(lhs: u8, rhs: u8, expected: (u8, bool, bool)) { + assert_eq!(expected, lhs.sub_with_carry(rhs)); + } } diff --git a/tests/cpu/arith8.rs b/tests/cpu/arith8.rs index 4638e2c..e23d14f 100644 --- a/tests/cpu/arith8.rs +++ b/tests/cpu/arith8.rs @@ -383,7 +383,11 @@ fn test_add_a_to_itself_with_carry_value(initial_value: u8, carry_bit: u8, expec #[test_case(0xF0, 0, AdditionOperationFlags{zero: 0, carry: 1, half_carry: 0}; "carry bit")] #[test_case(0x0F, 0, AdditionOperationFlags{zero: 0, carry: 0, half_carry: 1}; "half carry bit")] #[test_case(0x0E, 1, AdditionOperationFlags{zero: 0, carry: 0, half_carry: 1}; "half carry bit with input carry bit")] -fn test_add_a_to_itself_with_carry_flags(initial_value: u8, carry_bit: u8, expected_flags: AdditionOperationFlags) { +fn test_add_a_to_itself_with_carry_flags( + initial_value: u8, + carry_bit: u8, + expected_flags: AdditionOperationFlags, +) { let mut processor = Processor::default(); processor.registers.a = initial_value; processor @@ -472,7 +476,9 @@ fn test_add_hl_addr_to_a_with_carry_value(carry_flag: u8, expected: u8) { .registers .set_combined_register(register::Combined::HL, 0xFFFE); processor.registers.a = 0x0E; - processor.registers.set_flag_bit(register::Flag::Carry, carry_flag); + processor + .registers + .set_flag_bit(register::Flag::Carry, carry_flag); let data = [0x8E, 0x02]; @@ -506,7 +512,9 @@ fn test_add_hl_addr_to_a_with_carry_flags( .set_combined_register(register::Combined::HL, 0xFFFE); processor.registers.a = initial_value; - processor.registers.set_flag_bit(register::Flag::Carry, carry_bit); + processor + .registers + .set_flag_bit(register::Flag::Carry, carry_bit); let data = [0x8E, 0x02]; @@ -524,3 +532,106 @@ fn test_add_hl_addr_to_a_with_carry_flags( (register::Flag::Carry, expected_flags.carry), ); } + +#[test_case(0x90, 0xFF, 0x0F, register::SingleEightBit::B, 0xF0; "subtract B")] +#[test_case(0x91, 0xFF, 0x0F, register::SingleEightBit::C, 0xF0; "subtract C")] +#[test_case(0x92, 0xFF, 0x0F, register::SingleEightBit::D, 0xF0; "subtract D")] +#[test_case(0x93, 0xFF, 0x0F, register::SingleEightBit::E, 0xF0; "subtract E")] +#[test_case(0x94, 0xFF, 0x0F, register::SingleEightBit::H, 0xF0; "subtract H")] +#[test_case(0x95, 0xFF, 0x0F, register::SingleEightBit::L, 0xF0; "subtract L")] +fn test_sub_register_from_a_value( + opcode: u8, + a_value: u8, + operand: u8, + operand_register: register::SingleEightBit, + expected: u8, +) { + let mut processor = Processor::default(); + processor.registers.a = a_value; + processor + .registers + .set_single_8bit_register(operand_register, operand); + + let data = [opcode, 0x02]; + + let (ins, extra_data) = + RunnableInstruction::from_data(&data).expect("could not parse instruction"); + assert_eq!(extra_data, &[0x02]); + + processor.run_instruction(&ins); + assert_eq!(processor.registers.a, expected); +} + +#[test_case(0x90, 0xFF, 0x0F, register::SingleEightBit::B, AdditionOperationFlags { zero: 0, half_carry: 0, carry: 0 }; "no flags from B")] +#[test_case(0x90, 0xF0, 0x0F, register::SingleEightBit::B, AdditionOperationFlags { zero: 0, half_carry: 1, carry: 0 }; "half carry from B")] +#[test_case(0x90, 0x0F, 0xF0, register::SingleEightBit::B, AdditionOperationFlags { zero: 0, half_carry: 0, carry: 1 }; "full carry from B")] +#[test_case(0x90, 0x01, 0x01, register::SingleEightBit::B, AdditionOperationFlags { zero: 1, half_carry: 0, carry: 0 }; "zero from B")] +#[test_case(0x91, 0xFF, 0x0F, register::SingleEightBit::C, AdditionOperationFlags { zero: 0, half_carry: 0, carry: 0 }; "no flags from C")] +#[test_case(0x91, 0xF0, 0x0F, register::SingleEightBit::C, AdditionOperationFlags { zero: 0, half_carry: 1, carry: 0 }; "half carry from C")] +#[test_case(0x91, 0x0F, 0xF0, register::SingleEightBit::C, AdditionOperationFlags { zero: 0, half_carry: 0, carry: 1 }; "full carry from C")] +#[test_case(0x91, 0x01, 0x01, register::SingleEightBit::C, AdditionOperationFlags { zero: 1, half_carry: 0, carry: 0 }; "zero from C")] +#[test_case(0x92, 0xFF, 0x0F, register::SingleEightBit::D, AdditionOperationFlags { zero: 0, half_carry: 0, carry: 0 }; "no flags from D")] +#[test_case(0x92, 0xF0, 0x0F, register::SingleEightBit::D, AdditionOperationFlags { zero: 0, half_carry: 1, carry: 0 }; "half carry from D")] +#[test_case(0x92, 0x0F, 0xF0, register::SingleEightBit::D, AdditionOperationFlags { zero: 0, half_carry: 0, carry: 1 }; "full carry from D")] +#[test_case(0x92, 0x01, 0x01, register::SingleEightBit::D, AdditionOperationFlags { zero: 1, half_carry: 0, carry: 0 }; "zero from D")] +#[test_case(0x93, 0xFF, 0x0F, register::SingleEightBit::E, AdditionOperationFlags { zero: 0, half_carry: 0, carry: 0 }; "no flags from E")] +#[test_case(0x93, 0xF0, 0x0F, register::SingleEightBit::E, AdditionOperationFlags { zero: 0, half_carry: 1, carry: 0 }; "half carry from E")] +#[test_case(0x93, 0x0F, 0xF0, register::SingleEightBit::E, AdditionOperationFlags { zero: 0, half_carry: 0, carry: 1 }; "full carry from E")] +#[test_case(0x93, 0x01, 0x01, register::SingleEightBit::E, AdditionOperationFlags { zero: 1, half_carry: 0, carry: 0 }; "zero from E")] +#[test_case(0x94, 0xFF, 0x0F, register::SingleEightBit::H, AdditionOperationFlags { zero: 0, half_carry: 0, carry: 0 }; "no flags from H")] +#[test_case(0x94, 0xF0, 0x0F, register::SingleEightBit::H, AdditionOperationFlags { zero: 0, half_carry: 1, carry: 0 }; "half carry from H")] +#[test_case(0x94, 0x0F, 0xF0, register::SingleEightBit::H, AdditionOperationFlags { zero: 0, half_carry: 0, carry: 1 }; "full carry from H")] +#[test_case(0x94, 0x01, 0x01, register::SingleEightBit::H, AdditionOperationFlags { zero: 1, half_carry: 0, carry: 0 }; "zero from H")] +#[test_case(0x95, 0xFF, 0x0F, register::SingleEightBit::L, AdditionOperationFlags { zero: 0, half_carry: 0, carry: 0 }; "no flags from L")] +#[test_case(0x95, 0xF0, 0x0F, register::SingleEightBit::L, AdditionOperationFlags { zero: 0, half_carry: 1, carry: 0 }; "half carry from L")] +#[test_case(0x95, 0x0F, 0xF0, register::SingleEightBit::L, AdditionOperationFlags { zero: 0, half_carry: 0, carry: 1 }; "full carry from L")] +#[test_case(0x95, 0x01, 0x01, register::SingleEightBit::L, AdditionOperationFlags { zero: 1, half_carry: 0, carry: 0 }; "zero from L")] +fn test_sub_register_from_a_flags( + opcode: u8, + a_value: u8, + operand: u8, + operand_register: register::SingleEightBit, + expected_flags: AdditionOperationFlags, +) { + let mut processor = Processor::default(); + processor.registers.a = a_value; + processor + .registers + .set_single_8bit_register(operand_register, operand); + + let data = [opcode, 0x02]; + let (ins, extra_data) = + RunnableInstruction::from_data(&data).expect("could not parse instruction"); + assert_eq!(extra_data, &[0x02]); + + processor.run_instruction(&ins); + + testutil::assert_flags_eq!( + processor, + (register::Flag::Zero, expected_flags.zero), + (register::Flag::Subtract, 1), + (register::Flag::HalfCarry, expected_flags.half_carry), + (register::Flag::Carry, expected_flags.carry), + ); +} + +#[test] +fn test_sub_a_from_itself() { + let mut processor = Processor::default(); + processor.registers.a = 0xFF; + + let data = [0x97, 0x02]; + let (ins, extra_data) = + RunnableInstruction::from_data(&data).expect("could not parse instruction"); + assert_eq!(extra_data, &[0x02]); + + processor.run_instruction(&ins); + + testutil::assert_flags_eq!( + processor, + (register::Flag::Zero, 1), + (register::Flag::Subtract, 1), + (register::Flag::HalfCarry, 0), + (register::Flag::Carry, 0), + ); +}