diff --git a/src/cpu.rs b/src/cpu.rs index acd08a9..13ee486 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -548,8 +548,9 @@ mod tests { #[test_case(0x00C0, 0x7f, 0, 1; "overflow 2")] #[test_case(0x00FF, 0x01, 1, 1; "overflow with half carry")] #[test_case(0x000A, 0x0C, 1, 0; "half carry")] - #[test_case(0x0018, -0xC, 1, 0; "negative half carry")] - #[test_case(0x000E, -0x0F, 1, 1; "underflow with half carry")] + #[test_case(0x0018, -0x4, 1, 0; "negative half carry")] + #[test_case(0x000F, -0x10, 1, 1; "underflow with half carry")] + #[test_case(0x000E, -0x0F, 0, 1; "underflow with no half carry")] fn test_load_effective_address_flags( starting_sp: u16, add_value: i8, diff --git a/src/cpu/run/arithutil.rs b/src/cpu/run/arithutil.rs index f7943a7..8ad7fca 100644 --- a/src/cpu/run/arithutil.rs +++ b/src/cpu/run/arithutil.rs @@ -24,6 +24,22 @@ impl CarryingAdd for u16 { } } +impl CarryingAdd for u16 { + fn add_with_carry(self, rhs: i8) -> (u16, bool, bool) { + if rhs >= 0 { + return self.add_with_carry(rhs.unsigned_abs()); + } + + let (total, carry) = self.overflowing_sub(rhs.unsigned_abs().into()); + // This is a special case of half carry. Though we could probably twiddle the bits to make one + // unified half carry check, I'm gonna trust the guy who has debugged a real gameboy. + // https://stackoverflow.com/a/7261149/1103734 + let half_carry = total & 0xF <= self & 0xF; + + (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 @@ -54,12 +70,40 @@ mod tests { assert_eq!((expected, half_carry, full_carry), a.add_with_carry(b)); } - #[test_case(0x0000, 0x00, 0, false, false; "no addition")] - #[test_case(0x00FF, 0x10, 271, false, true; "overflow")] - #[test_case(0x00C0, 0x7F, 319, false, true; "overflow 2")] - #[test_case(0x00FF, 0x01, 256, true, true; "overflow with half carry")] - #[test_case(0x0F0A, 0x0C, 3862, true, false; "half carry")] - fn test_u8_u16_carrying_add(a: u16, b: u8, expected: u16, half_carry: bool, full_carry: bool) { - assert_eq!((expected, half_carry, full_carry), a.add_with_carry(b)); + macro_rules! u16_u8_positive_cases { + ($test: item) => { + #[test_case(0x0000, 0x00, 0, false, false; "no addition")] + #[test_case(0x00FF, 0x10, 271, false, true; "overflow")] + #[test_case(0x00C0, 0x7F, 319, false, true; "overflow 2")] + #[test_case(0x00FF, 0x01, 256, true, true; "overflow with half carry")] + #[test_case(0x0F0A, 0x0C, 3862, true, false; "half carry")] + $test + }; + } + + u16_u8_positive_cases! { + fn test_u8_u16_carrying_add(a: u16, b: u8, expected: u16, half_carry: bool, full_carry: bool) { + assert_eq!((expected, half_carry, full_carry), a.add_with_carry(b)); + } + } + + // our positive testcases should work just the same + u16_u8_positive_cases! { + // negative numbers, though, have their own cases to consider + // + // this first testcase is one that I generated by taking the difference of these two lists + // (there were many more examples but I picked one) + // In [1]: for sp in range(0xFFFF): + // ...: for n in range(1, 0xFF): + // ...: a.append((sp, n)) + // ...: if ((sp - n) & 0xF) <= (sp & 0xF): + // ...: c.append((sp, n)) + #[test_case(0x1D21, -0x46, 0x1CDB, false, false; "negative; no carry")] + #[test_case(0x000F, -0x10, 0xFFFF, true, true; "negative; underflow")] + #[test_case(0x000F, -0x7F, 0xFF90, true, true; "negative; underflow 2")] + #[test_case(0x249D, -0x08, 0x2495, true, false; "negative; half carry")] + fn test_i8_u16_carrying_add(a: u16, b: i8, expected: u16, half_carry: bool, full_carry: bool) { + assert_eq!((expected, half_carry, full_carry), a.add_with_carry(b)); + } } } diff --git a/src/cpu/run/load16.rs b/src/cpu/run/load16.rs index e229703..68ced79 100644 --- a/src/cpu/run/load16.rs +++ b/src/cpu/run/load16.rs @@ -31,26 +31,10 @@ impl InstructionRunner for SixteenBitLoadRunner { .registers .get_single_16bit_register(register::SingleSixteenBit::StackPointer); - let (new_sp, half_carry, carry) = if offset >= 0 { - let unsigned_offset = u8::try_from(offset) - .expect("failed to put positive (or zero) 8 bit value into unsigned eight bit, which should always work..."); - - current_sp.add_with_carry(unsigned_offset) - } 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"); - // TODO: This could be less gross but it's theo nly place we do it... - let (new_sp, underflow) = current_sp.overflowing_sub(sixteen_bit_offset); - // https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/ - let half_carry = - ((current_sp & 0xf) + (sixteen_bit_offset & 0xf)) & 0x10 == 0x10; - - (new_sp, half_carry, underflow) - }; + let (new_sp, half_carry, carry) = current_sp.add_with_carry(offset); let half_carry_bit = if half_carry { 1 } else { 0 }; let carry_bit = if carry { 1 } else { 0 }; - processor .registers .set_flag_bit(register::Flag::Carry, carry_bit);