Implement basic SUB operation

old-bit-manip
Nick Krichevsky 2023-05-04 22:14:45 -04:00
parent c05f8c57e9
commit b0554a098e
7 changed files with 228 additions and 16 deletions

View File

@ -11,4 +11,6 @@ pub enum EightBitArithmeticInstruction {
AddSingleRegisterToAWithCarry { src: register::SingleEightBit },
AddImmediateToAWithCarry { n: u8 },
AddHLAddressToAWithCarry,
SubSingleRegisterFromA { src: register::SingleEightBit },
}

View File

@ -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 {

View File

@ -1 +1,2 @@
pub mod add;
pub mod sub;

View File

@ -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,
)
}

View File

@ -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<EightBitArithmeticInstruction> 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<EightBitArithmeticInstruction> 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<EightBitArithmeticInstruction> 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);
}

View File

@ -63,18 +63,28 @@ pub trait CarryingAdd<R, O> {
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<R, O> {
/// `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<u8, u8> 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<u8, u16> 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<i8, u16> 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<CarriedNumber<u8, u16>, 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<u8, u8> 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<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_add_half_carry<L: Into<u16>, R: Into<u16>>(lhs: L, rhs: R) -> bool {
did_8bit_add_half_carry_including_carry_bit(lhs, rhs, 0)
}
fn did_8bit_half_carry_including_carry_bit<L: Into<u16>, R: Into<u16>>(
fn did_8bit_add_half_carry_including_carry_bit<L: Into<u16>, R: Into<u16>>(
lhs: L,
rhs: R,
carry_bit: u8,
@ -136,7 +156,15 @@ fn did_8bit_half_carry_including_carry_bit<L: Into<u16>, R: Into<u16>>(
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));
}
}

View File

@ -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),
);
}