Add ADC instruction implementations

jsmoo
Nick Krichevsky 2023-04-15 18:29:39 -04:00
parent 16c275282e
commit 040133eb82
7 changed files with 381 additions and 2 deletions

View File

@ -64,6 +64,8 @@ impl Processor {
dst_address: usize,
value: u8,
) -> Result<(), Error> {
// This lint is technically correct but we know this is the only error that can really happen here
#[allow(clippy::match_wildcard_for_single_variants)]
self.memory
.set(dst_address, value)
.map(|_| ())
@ -880,4 +882,150 @@ mod tests {
(register::Flag::Carry, carry_flag),
);
}
#[test_case(0x88, 0x12, 0, register::SingleEightBit::B, 0x5, 0x17; "no carry to register b")]
#[test_case(0x88, 0x12, 1, register::SingleEightBit::B, 0x5, 0x18; "carry to register b")]
#[test_case(0x89, 0x12, 0, register::SingleEightBit::C, 0x5, 0x17; "no carry to register c")]
#[test_case(0x89, 0x12, 1, register::SingleEightBit::C, 0x5, 0x18; "carry to register c")]
#[test_case(0x8A, 0x12, 0, register::SingleEightBit::D, 0x5, 0x17; "no carry to register d")]
#[test_case(0x8A, 0x12, 1, register::SingleEightBit::D, 0x5, 0x18; "carry to register d")]
#[test_case(0x8B, 0x12, 0, register::SingleEightBit::E, 0x5, 0x17; "no carry to register e")]
#[test_case(0x8B, 0x12, 1, register::SingleEightBit::E, 0x5, 0x18; "carry to register e")]
#[test_case(0x8C, 0x12, 0, register::SingleEightBit::H, 0x5, 0x17; "no carry to register h")]
#[test_case(0x8C, 0x12, 1, register::SingleEightBit::H, 0x5, 0x18; "carry to register h")]
#[test_case(0x8D, 0x12, 0, register::SingleEightBit::L, 0x5, 0x17; "no carry to register l")]
#[test_case(0x8D, 0x12, 1, register::SingleEightBit::L, 0x5, 0x18; "carry to register l")]
fn test_add_with_carry_to_a_value(
opcode: u8,
initial_value: u8,
carry_bit: u8,
operand_register: register::SingleEightBit,
operand: u8,
expected_value: u8,
) {
let mut processor = Processor::default();
processor.registers.a = initial_value;
processor
.registers
.set_single_8bit_register(operand_register, operand);
processor
.registers
.set_flag_bit(register::Flag::Carry, carry_bit);
let data = [opcode, 0x01];
let (ins, extra_data) =
RunnableInstruction::from_data(&data).expect("could not parse instruction");
assert_eq!(extra_data, &[0x01]);
processor.run(&ins);
assert_eq!(expected_value, processor.registers.a);
}
// these parameters are like impossible to read but I can't think of a nicer way to do this right now
// B register
#[test_case(0x88, 0x12, 0, register::SingleEightBit::B, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register b")]
#[test_case(0x88, 0x12, 1, register::SingleEightBit::B, 0x5, 0, 0, 0; "carry bit set, results in no flags, register b")]
#[test_case(0x88, 0x00, 0, register::SingleEightBit::B, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register b")]
#[test_case(0x88, 0x00, 1, register::SingleEightBit::B, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register b")]
#[test_case(0x88, 0x0F, 0, register::SingleEightBit::B, 0x1, 0, 1, 0; "no carry bit, half carry, register b")]
#[test_case(0x88, 0x0F, 1, register::SingleEightBit::B, 0x1, 0, 1, 0; "carry bit, half carry, register b")]
#[test_case(0x88, 0x0F, 0, register::SingleEightBit::B, 0x0F, 0, 1, 0; "no carry bit, full carry, register b")]
#[test_case(0x88, 0x0F, 1, register::SingleEightBit::B, 0x0F, 0, 1, 0; "carry bit, full carry, register b")]
#[test_case(0x88, 0xFF, 1, register::SingleEightBit::B, 0xFF, 0, 1, 1; "carry bit, both carries, register b")]
// C register
#[test_case(0x89, 0x12, 0, register::SingleEightBit::C, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register c")]
#[test_case(0x89, 0x12, 1, register::SingleEightBit::C, 0x5, 0, 0, 0; "carry bit set, results in no flags, register c")]
#[test_case(0x89, 0x00, 0, register::SingleEightBit::C, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register c")]
#[test_case(0x89, 0x00, 1, register::SingleEightBit::C, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register c")]
#[test_case(0x89, 0x0F, 0, register::SingleEightBit::C, 0x1, 0, 1, 0; "no carry bit, half carry, register c")]
#[test_case(0x89, 0x0F, 1, register::SingleEightBit::C, 0x1, 0, 1, 0; "carry bit, half carry, register c")]
#[test_case(0x89, 0x0F, 0, register::SingleEightBit::C, 0x0F, 0, 1, 0; "no carry bit, full carry, register c")]
#[test_case(0x89, 0x0F, 1, register::SingleEightBit::C, 0x0F, 0, 1, 0; "carry bit, full carry, register c")]
#[test_case(0x89, 0xFF, 1, register::SingleEightBit::C, 0xFF, 0, 1, 1; "carry bit, both carries, register c")]
// D register
#[test_case(0x8A, 0x12, 0, register::SingleEightBit::D, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register d")]
#[test_case(0x8A, 0x12, 1, register::SingleEightBit::D, 0x5, 0, 0, 0; "carry bit set, results in no flags, register d")]
#[test_case(0x8A, 0x00, 0, register::SingleEightBit::D, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register d")]
#[test_case(0x8A, 0x00, 1, register::SingleEightBit::D, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register d")]
#[test_case(0x8A, 0x0F, 0, register::SingleEightBit::D, 0x1, 0, 1, 0; "no carry bit, half carry, register d")]
#[test_case(0x8A, 0x0F, 1, register::SingleEightBit::D, 0x1, 0, 1, 0; "carry bit, half carry, register d")]
#[test_case(0x8A, 0x0F, 0, register::SingleEightBit::D, 0x0F, 0, 1, 0; "no carry bit, full carry, register d")]
#[test_case(0x8A, 0x0F, 1, register::SingleEightBit::D, 0x0F, 0, 1, 0; "carry bit, full carry, register d")]
#[test_case(0x8A, 0xFF, 1, register::SingleEightBit::D, 0xFF, 0, 1, 1; "carry bit, both carries, register d")]
// E register
#[test_case(0x8B, 0x12, 0, register::SingleEightBit::E, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register e")]
#[test_case(0x8B, 0x12, 1, register::SingleEightBit::E, 0x5, 0, 0, 0; "carry bit set, results in no flags, register e")]
#[test_case(0x8B, 0x00, 0, register::SingleEightBit::E, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register e")]
#[test_case(0x8B, 0x00, 1, register::SingleEightBit::E, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register e")]
#[test_case(0x8B, 0x0F, 0, register::SingleEightBit::E, 0x1, 0, 1, 0; "no carry bit, half carry, register e")]
#[test_case(0x8B, 0x0F, 1, register::SingleEightBit::E, 0x1, 0, 1, 0; "carry bit, half carry, register e")]
#[test_case(0x8B, 0x0F, 0, register::SingleEightBit::E, 0x0F, 0, 1, 0; "no carry bit, full carry, register e")]
#[test_case(0x8B, 0x0F, 1, register::SingleEightBit::E, 0x0F, 0, 1, 0; "carry bit, full carry, register e")]
#[test_case(0x8B, 0xFF, 1, register::SingleEightBit::E, 0xFF, 0, 1, 1; "carry bit, both carries, register e")]
// H register
#[test_case(0x8C, 0x12, 0, register::SingleEightBit::H, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register h")]
#[test_case(0x8C, 0x12, 1, register::SingleEightBit::H, 0x5, 0, 0, 0; "carry bit set, results in no flags, register h")]
#[test_case(0x8C, 0x00, 0, register::SingleEightBit::H, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register h")]
#[test_case(0x8C, 0x00, 1, register::SingleEightBit::H, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register h")]
#[test_case(0x8C, 0x0F, 0, register::SingleEightBit::H, 0x1, 0, 1, 0; "no carry bit, half carry, register h")]
#[test_case(0x8C, 0x0F, 1, register::SingleEightBit::H, 0x1, 0, 1, 0; "carry bit, half carry, register h")]
#[test_case(0x8C, 0x0F, 0, register::SingleEightBit::H, 0x0F, 0, 1, 0; "no carry bit, full carry, register h")]
#[test_case(0x8C, 0x0F, 1, register::SingleEightBit::H, 0x0F, 0, 1, 0; "carry bit, full carry, register h")]
#[test_case(0x8C, 0xFF, 1, register::SingleEightBit::H, 0xFF, 0, 1, 1; "carry bit, both carries, register h")]
// L register
#[test_case(0x8D, 0x12, 0, register::SingleEightBit::L, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register l")]
#[test_case(0x8D, 0x12, 1, register::SingleEightBit::L, 0x5, 0, 0, 0; "carry bit set, results in no flags, register l")]
#[test_case(0x8D, 0x00, 0, register::SingleEightBit::L, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register l")]
#[test_case(0x8D, 0x00, 1, register::SingleEightBit::L, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register l")]
#[test_case(0x8D, 0x0F, 0, register::SingleEightBit::L, 0x1, 0, 1, 0; "no carry bit, half carry, register l")]
#[test_case(0x8D, 0x0F, 1, register::SingleEightBit::L, 0x1, 0, 1, 0; "carry bit, half carry, register l")]
#[test_case(0x8D, 0x0F, 0, register::SingleEightBit::L, 0x0F, 0, 1, 0; "no carry bit, full carry, register l")]
#[test_case(0x8D, 0x0F, 1, register::SingleEightBit::L, 0x0F, 0, 1, 0; "carry bit, full carry, register l")]
#[test_case(0x8D, 0xFF, 1, register::SingleEightBit::L, 0xFF, 0, 1, 1; "carry bit, both carries, register l")]
fn test_add_with_carry_to_a_flags(
opcode: u8,
initial_value: u8,
carry_bit: u8,
operand_register: register::SingleEightBit,
operand: u8,
expected_zero_flag: u8,
expected_half_carry_flag: u8,
expected_carry_flag: u8,
) {
let mut processor = Processor::default();
processor.registers.a = initial_value;
processor
.registers
.set_single_8bit_register(operand_register, operand);
// Set all the register to the opposite we expect to ensure they all get set
set_opposite_of_expected_flags(
&mut processor,
(
expected_zero_flag,
0,
expected_half_carry_flag,
expected_carry_flag,
),
);
// ...except for the carry bit, which we must set for the test
processor
.registers
.set_flag_bit(register::Flag::Carry, carry_bit);
let data = [opcode, 0x01];
let (ins, extra_data) =
RunnableInstruction::from_data(&data).expect("could not parse instruction");
assert_eq!(extra_data, &[0x01]);
processor.run(&ins);
assert_flags_eq!(
processor,
(register::Flag::Zero, expected_zero_flag),
(register::Flag::Subtract, 0),
(register::Flag::HalfCarry, expected_half_carry_flag),
(register::Flag::Carry, expected_carry_flag),
);
}
}

View File

@ -8,4 +8,5 @@ pub enum EightBitArithmeticInstruction {
AddSingleRegisterToA { src: register::SingleEightBit },
AddImmediateToA { n: u8 },
AddHLAddressToA,
AddSingleRegisterToAWithCarry { src: register::SingleEightBit },
}

View File

@ -21,6 +21,12 @@ impl OpcodeParser for EightBitAddParser {
0x85 => build_add_register_to_a_data(register::SingleEightBit::L, data),
0x86 => build_add_hl_address_to_a_data(data),
0xC6 => build_add_immediate_to_a_data(data),
0x88 => build_add_register_to_a_with_carry_data(register::SingleEightBit::B, data),
0x89 => build_add_register_to_a_with_carry_data(register::SingleEightBit::C, data),
0x8A => build_add_register_to_a_with_carry_data(register::SingleEightBit::D, data),
0x8B => build_add_register_to_a_with_carry_data(register::SingleEightBit::E, data),
0x8C => build_add_register_to_a_with_carry_data(register::SingleEightBit::H, data),
0x8D => build_add_register_to_a_with_carry_data(register::SingleEightBit::L, data),
_ => Err(Error::UnknownOpcode(opcode)),
}
}
@ -42,6 +48,25 @@ fn build_add_register_to_a_data(src: register::SingleEightBit, data: &[u8]) -> P
.ok_or(Error::NoData)
}
fn build_add_register_to_a_with_carry_data(
src: register::SingleEightBit,
data: &[u8],
) -> ParseResult {
data.get(1..)
.map(|remaining_data| {
(
RunnableInstruction {
instruction: Instruction::EightBitArithmetic(
EightBitArithmeticInstruction::AddSingleRegisterToAWithCarry { src },
),
cycles: 4,
},
remaining_data,
)
})
.ok_or(Error::NoData)
}
fn build_add_hl_address_to_a_data(data: &[u8]) -> ParseResult {
data.get(1..)
.map(|remaining_data| {

View File

@ -19,6 +19,9 @@ pub enum Error {
/// An invalid address was stored in the given register and attempted to be loaded to/from
#[error("invalid address stored in register ({0}): {1:X}")]
InvalidRegisterAddress(register::SixteenBit, usize),
/// An invalid address was stored in the given register and attempted to be loaded to/from
#[error("invalid value stored in carry flag: {0:X}")]
InvalidCarryFlagValue(u8),
#[error("an unknown error occured: {0}")]
Unknown(Box<dyn std::error::Error>),
}

View File

@ -1,6 +1,7 @@
use crate::cpu::{instructions::arith8::EightBitArithmeticInstruction, run::Error, Processor};
use crate::{memory, register};
use super::arithutil;
use super::{arithutil::CarryingAdd, InstructionRunner};
pub(super) struct EightBitArithmeticRunner;
@ -76,6 +77,26 @@ impl InstructionRunner<EightBitArithmeticInstruction> for EightBitArithmeticRunn
Ok(())
}
EightBitArithmeticInstruction::AddSingleRegisterToAWithCarry { src } => {
let src_register_value = processor.registers.get_single_8bit_register(src);
let carried_operand = arithutil::CarriedNumber::new(
src_register_value,
processor.registers.get_flag_bit(register::Flag::Carry),
)
.map_err(|err| match err {
arithutil::Error::InvalidCarryBit(value) => Error::InvalidCarryFlagValue(value),
})?;
let (result, half_carry, carry) =
processor.registers.a.add_with_carry(carried_operand);
processor.registers.a = result;
set_addition_flags(processor, result, half_carry, carry);
Ok(())
}
}
}
}

View File

@ -1,4 +1,57 @@
//! arithutil contains utilities to generalize the implementation of arithmetic
//! arithutil contains utilities to generalize the implementation of arithmetic instructions
use std::{marker::PhantomData, ops::Add};
use thiserror::Error;
use self::upgrade::UpgradableNumber;
mod upgrade;
#[derive(Error, Debug)]
pub enum Error {
#[error("invalid carry bit value {0}. Must be zero or one.")]
InvalidCarryBit(u8),
}
/// `CarriedNumber` is a number combined with its carry bit, intended to be used to perform carry-based arithmetic
/// operations such as ADC.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CarriedNumber<N, V> {
num: N,
carry_bit: u8,
_phantom: PhantomData<V>,
}
impl<N, V> CarriedNumber<N, V>
where
N: Copy + Clone + PartialEq + UpgradableNumber<V>,
V: From<N> + From<u8> + Add<Output = V>,
{
/// Create a new [`CarriedNumber`] with a raw value and its carry bit. Note that the carry bit must be 0 or 1,
/// or an [`Error::InvalidCarryBit`] will be returned
pub fn new(num: N, carry_bit: u8) -> Result<Self, Error> {
if carry_bit > 1 {
return Err(Error::InvalidCarryBit(carry_bit));
}
Ok(Self {
num,
carry_bit,
_phantom: PhantomData,
})
}
/// Get the combination between the value and the carry bit. The return value is the "upgraded" version of `N`.
/// See [`upgrade::UpgradableNumber`] for more details
pub fn value(self) -> V {
self.num.upgrade() + V::from(self.carry_bit)
}
/// Get the number and its carry bit directly
pub fn raw_values(self) -> (N, u8) {
(self.num, self.carry_bit)
}
}
/// `CarryingAdd` describes a type that can perform addition to produce the carry flags needed for the operation
/// of the gameboy
@ -44,12 +97,38 @@ impl CarryingAdd<i8, u16> for u16 {
}
}
impl CarryingAdd<CarriedNumber<u8, u16>, u8> for u8 {
fn add_with_carry(self, rhs: CarriedNumber<u8, u16>) -> (u8, bool, bool) {
let total = rhs.value() + u16::from(self);
// Given this is adding two u8s, the largest value we can produce is 0x01FF (both operands 0xFF, plus a carry bit)
// which can never wrap, so we just take the lower byte to convert back to u8
let [_, smallsized_total] = total.to_be_bytes();
(
smallsized_total,
did_8bit_half_carry_including_carry_bit(self, rhs.num, rhs.carry_bit),
did_8bit_full_carry_including_carry_bit(self, rhs.num, rhs.carry_bit),
)
}
}
/// `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<u16> is just a shortcut because we
// only use up to u16s
fn did_8bit_half_carry<L: Into<u16>, R: Into<u16>>(lhs: L, rhs: R) -> bool {
did_8bit_half_carry_including_carry_bit(lhs, rhs, 0)
}
fn did_8bit_half_carry_including_carry_bit<L: Into<u16>, R: Into<u16>>(
lhs: L,
rhs: R,
carry_bit: u8,
) -> bool {
// This should be validated far before use
assert!(carry_bit <= 1, "carry bit must be zero or one");
// https://stackoverflow.com/a/7261149/1103734
(lhs.into() & 0xf) + (rhs.into() & 0xf) > 0xf
(lhs.into() & 0xf) + (rhs.into() & 0xf) + u16::from(carry_bit) > 0xf
}
/// `did_8_bit_full_carry` checks whether or not the given addition would have done an 8 bit carry.
@ -60,6 +139,18 @@ fn did_8bit_full_carry<L: Into<u16>, R: Into<u16>>(lhs: L, rhs: R) -> bool {
((lhs.into() & 0xff) + (rhs.into() & 0xff)) > 0xff
}
fn did_8bit_full_carry_including_carry_bit<L: Into<u16>, R: Into<u16>>(
lhs: L,
rhs: R,
carry_bit: u8,
) -> bool {
// This should be validated far before use
assert!(carry_bit <= 1, "carry bit must be zero or one");
// https://stackoverflow.com/a/7261149/1103734
((lhs.into() & 0xff) + (rhs.into() & 0xff) + u16::from(carry_bit)) > 0xff
}
#[cfg(test)]
mod tests {
use super::*;
@ -68,6 +159,7 @@ mod tests {
#[test_case(0x00, 0x00, 0, false, false; "no addition")]
#[test_case(0xFF, 0x10, 15, false, true; "overflow")]
#[test_case(0xC0, 0x7F, 63, false, true; "overflow 2")]
#[test_case(0xFE, 0x01, 0xFF, false, false; "hits max, no overflow")]
#[test_case(0xFF, 0x01, 0, true, true; "overflow with half carry")]
#[test_case(0x0A, 0x0C, 22, true, false; "half carry")]
fn test_u8_carrying_add(a: u8, b: u8, expected: u8, half_carry: bool, full_carry: bool) {
@ -110,4 +202,39 @@ mod tests {
assert_eq!((expected, half_carry, full_carry), a.add_with_carry(b));
}
}
#[test]
fn can_make_carried_number() {
let n = CarriedNumber::new(10, 1).expect("failed to construct CarriedNumber");
assert_eq!((10, 1), n.raw_values());
}
#[test]
fn cannot_construct_carried_number_with_bad_carry_bit() {
assert!(CarriedNumber::new(10, 20).is_err());
}
#[test_case(0x12, 1, 0x13)]
#[test_case(0x12, 0, 0x12)]
#[test_case(0xFF, 1, 0x100)]
#[test_case(0xFF, 0, 0xFF)]
fn test_get_value_from_carried_number(value: u8, carry_bit: u8, expected_value: u16) {
let carried_number = CarriedNumber::new(value, carry_bit)
.expect("should have been able construct CarriedNumber");
assert_eq!(expected_value, carried_number.value());
}
#[test_case(0x12, CarriedNumber::new(0x01, 1).unwrap(), (0x14, false, false))]
#[test_case(0x80, CarriedNumber::new(0x81, 1).unwrap(), (0x02, false, true))]
#[test_case(0xFD, CarriedNumber::new(0x01, 1).unwrap(), (0xFF, false, false))]
#[test_case(0x7F, CarriedNumber::new(0x01, 1).unwrap(), (0x81, true, false))]
#[test_case(0xFF, CarriedNumber::new(0x01, 1).unwrap(), (0x01, true, true))]
#[test_case(0xFF, CarriedNumber::new(0xFF, 1).unwrap(), (0xFF, true, true))]
fn test_add_carried_number_to_u8(
value: u8,
carried_number: CarriedNumber<u8, u16>,
expected: (u8, bool, bool),
) {
assert_eq!(expected, value.add_with_carry(carried_number));
}
}

View File

@ -0,0 +1,54 @@
/// `UpgradableNumber` represents an number that can be "upgraded" to its next largest type.
/// This is slightly different than `From`, in that it only allows you to upgrade "one step up".
/// You cannot, for instance, upgrade a `u8` to a `u64` directly.
pub trait UpgradableNumber<U: From<Self>>
where
Self: Sized,
{
fn upgrade(self) -> U {
self.into()
}
}
impl UpgradableNumber<u16> for u8 {}
impl UpgradableNumber<u32> for u16 {}
impl UpgradableNumber<u64> for u32 {}
impl UpgradableNumber<i16> for i8 {}
impl UpgradableNumber<i32> for i16 {}
impl UpgradableNumber<i64> for i32 {}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! upgrade_test {
($test_name: ident, $input_type:ty, $output_type:ty, $value:expr) => {
#[test]
fn $test_name() {
let input: $input_type = $value;
let expected: $output_type = $value.into();
assert_eq!(expected, input.upgrade());
}
};
}
upgrade_test!(upgrade_u8_to_u16, u8, u16, 64_u8);
upgrade_test!(upgrade_u8_max_to_u16, u8, u16, u8::MAX);
upgrade_test!(upgrade_u16_u32, u16, u32, 325_u16);
upgrade_test!(upgrade_u16_max_to_u32, u16, u32, u16::MAX);
upgrade_test!(upgrade_u32_u32, u32, u64, 2_u32.pow(26));
upgrade_test!(upgrade_u32_max_to_u32, u32, u64, u32::MAX);
upgrade_test!(upgrade_i8_to_i16, i8, i16, 64_i8);
upgrade_test!(upgrade_i8_max_to_i16, i8, i16, i8::MAX);
upgrade_test!(upgrade_i16_i32, i16, i32, 325_i16);
upgrade_test!(upgrade_i16_max_to_i32, i16, i32, i16::MAX);
upgrade_test!(upgrade_i32_i32, i32, i64, 2_i32.pow(26));
upgrade_test!(upgrade_i32_max_to_i32, i32, i64, i32::MAX);
}