Add support for INC instruction

old-bit-manip
Nick Krichevsky 2023-06-16 15:54:06 -04:00
parent e1a0cdfe62
commit c25f0cdefa
16 changed files with 335 additions and 0 deletions

View File

@ -11,6 +11,7 @@ pub enum Operation {
Xor,
Or,
Compare,
Inc,
}
#[derive(Debug, Copy, Clone)]

View File

@ -47,6 +47,7 @@ pub fn next_instruction(data: &View) -> ParseResult {
arith8::xor::Parser::parse_opcode,
arith8::or::Parser::parse_opcode,
arith8::compare::Parser::parse_opcode,
arith8::inc::Parser::parse_opcode,
];
for parse_func in parse_funcs {

View File

@ -15,6 +15,7 @@ pub mod compare;
pub mod or;
pub mod sub;
pub mod xor;
pub mod inc;
fn build_instruction_between_register_and_a_data(
operation: Operation,

View File

@ -0,0 +1,37 @@
use crate::{
cpu::{
instructions::arith8::Operation,
parse::{self, Error, OpcodeParser, ParseOutput, ParseResult},
},
memory::View,
register,
};
pub struct Parser;
impl OpcodeParser for Parser {
fn parse_opcode(data: &View) -> ParseResult {
let opcode = parse::get_opcode_from_data(data);
match opcode {
0x04 => Ok(build_inc_single_register_data(register::SingleEightBit::B)),
0x0C => Ok(build_inc_single_register_data(register::SingleEightBit::C)),
0x14 => Ok(build_inc_single_register_data(register::SingleEightBit::D)),
0x1C => Ok(build_inc_single_register_data(register::SingleEightBit::E)),
0x24 => Ok(build_inc_single_register_data(register::SingleEightBit::H)),
0x2C => Ok(build_inc_single_register_data(register::SingleEightBit::L)),
0x3C => Ok(build_inc_single_register_data(register::SingleEightBit::A)),
0x34 => Ok(build_inc_hl_value_data()),
_ => Err(Error::UnknownOpcode(opcode)),
}
}
}
fn build_inc_single_register_data(src: register::SingleEightBit) -> ParseOutput {
super::build_instruction_between_register_and_a_data(Operation::Inc, src)
}
fn build_inc_hl_value_data() -> ParseOutput {
super::build_instruction_between_hl_value_and_a_data(Operation::Inc)
}

View File

@ -16,6 +16,8 @@ mod add;
mod and;
mod or;
mod sub;
mod inc;
mod unary;
mod xor;
pub struct EightBitArithmeticRunner;
@ -69,6 +71,10 @@ impl InstructionRunner<EightBitArithmeticInstruction> for EightBitArithmeticRunn
sub::run(processor, lhs, rhs);
processor.registers.a = a_value;
}
Operation::Inc => {
inc::run(processor, instruction.operand)?;
}
}
Ok(())

18
src/cpu/run/arith8/inc.rs Normal file
View File

@ -0,0 +1,18 @@
use crate::{cpu::{Processor, instructions::arith8::Operand, run::{arithutil::CarryingAdd, Error}}, register};
use super::unary;
pub fn run(processor: &mut Processor, operand: Operand) -> Result<(), Error> {
let current_carry_flag = processor.registers.get_flag_bit(register::Flag::Carry);
unary::run_operation(processor, operand, |value| {
let (result, half_carry, _full_carry) = value.add_with_carry(1);
unary::Output{
value: result,
zero_flag: (result == 0).into(),
half_carry_flag: half_carry.into(),
carry_flag: current_carry_flag,
subtract_flag: 0,
}
})
}

101
src/cpu/run/arith8/unary.rs Normal file
View File

@ -0,0 +1,101 @@
use crate::{
cpu::{instructions::arith8::Operand, run::Error, Processor},
memory, register,
};
#[derive(Copy, Clone, Debug)]
pub struct Output {
pub value: u8,
pub zero_flag: u8,
pub carry_flag: u8,
pub subtract_flag: u8,
pub half_carry_flag: u8,
}
pub fn run_operation<F>(
processor: &mut Processor,
operand: Operand,
operation: F,
) -> Result<(), Error>
where
F: Fn(u8) -> Output,
{
let start_value = gather_operand_value(processor, operand)?;
let output = operation(start_value);
store_output_value(processor, operand, output)?;
store_flags(processor, output);
Ok(())
}
fn gather_operand_value(processor: &mut Processor, operand: Operand) -> Result<u8, Error> {
match operand {
Operand::SingleRegister(register) => {
Ok(processor.registers.get_single_8bit_register(register))
}
Operand::HLAddressValue => {
let src_addr = 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)]
processor
.memory
.get(src_addr.into())
.map_err(|err| match err {
memory::Error::GetInvalidAddress(bad_addr) => Error::InvalidRegisterAddress(
register::SixteenBit::Combined(register::Combined::HL),
bad_addr,
),
err => Error::Unknown(Box::new(err)),
})
}
Operand::Immediate(_) => unreachable!("cannot increment an immediate"),
}
}
fn store_output_value(
processor: &mut Processor,
operand: Operand,
Output{value: result, ..}: Output,
) -> Result<(), Error> {
match operand {
Operand::SingleRegister(register) => {
processor
.registers
.set_single_8bit_register(register, result);
Ok(())
}
Operand::HLAddressValue => {
let src_addr = 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)]
processor
.memory
.set(src_addr.into(), result)
.map(|_old_value| ())
.map_err(|err| match err {
memory::Error::SetInvalidAddress(bad_addr, _value) => {
Error::InvalidRegisterAddress(
register::SixteenBit::Combined(register::Combined::HL),
bad_addr,
)
}
err => Error::Unknown(Box::new(err)),
})
}
Operand::Immediate(_) => unreachable!("cannot increment an immediate"),
}
}
fn store_flags(processor: &mut Processor, output: Output) {
processor.registers.set_flag_bit(register::Flag::Zero, output.zero_flag);
processor.registers.set_flag_bit(register::Flag::HalfCarry, output.half_carry_flag);
processor.registers.set_flag_bit(register::Flag::Carry, output.carry_flag);
processor.registers.set_flag_bit(register::Flag::Subtract, output.subtract_flag);
}

View File

@ -13,6 +13,11 @@ struct AdditionOperationFlags {
carry: u8,
}
struct IncrementOperationFlags {
zero: u8,
half_carry: u8,
}
#[test]
fn test_add_a_to_itself() {
let mut processor = Processor::default();
@ -2129,3 +2134,168 @@ fn test_comparing_immediate_value_to_a(
(register::Flag::Carry, expected_flags.carry),
);
}
#[test_case(0x04, register::SingleEightBit::B, 0x05, 0x06; "add one, register B")]
#[test_case(0x04, register::SingleEightBit::B, 0xFF, 0x00; "add one with wrapping, register B")]
#[test_case(0x0C, register::SingleEightBit::C, 0x05, 0x06; "add one, register C")]
#[test_case(0x0C, register::SingleEightBit::C, 0xFF, 0x00; "add one with wrapping, register C")]
#[test_case(0x14, register::SingleEightBit::D, 0x05, 0x06; "add one, register D")]
#[test_case(0x14, register::SingleEightBit::D, 0xFF, 0x00; "add one with wrapping, register D")]
#[test_case(0x1C, register::SingleEightBit::E, 0x05, 0x06; "add one, register E")]
#[test_case(0x1C, register::SingleEightBit::E, 0xFF, 0x00; "add one with wrapping, register E")]
#[test_case(0x24, register::SingleEightBit::H, 0x05, 0x06; "add one, register H")]
#[test_case(0x24, register::SingleEightBit::H, 0xFF, 0x00; "add one with wrapping, register H")]
#[test_case(0x2C, register::SingleEightBit::L, 0x05, 0x06; "add one, register L")]
#[test_case(0x2C, register::SingleEightBit::L, 0xFF, 0x00; "add one with wrapping, register L")]
#[test_case(0x3C, register::SingleEightBit::A, 0x05, 0x06; "add one, register A")]
#[test_case(0x3C, register::SingleEightBit::A, 0xFF, 0x00; "add one with wrapping, register A")]
fn test_increment_single_register_value(
opcode: u8,
register: register::SingleEightBit,
initial_value: u8,
expected_value: u8,
) {
let mut processor = Processor::default();
processor
.registers
.set_single_8bit_register(register, initial_value);
let data = [opcode, 0x03];
let (ins, extra_data) =
RunnableInstruction::from_data(&data).expect("could not parse instruction");
assert_eq!(extra_data, &[0x03]);
processor.run_instruction(&ins);
assert_eq!(
expected_value,
processor.registers.get_single_8bit_register(register)
);
}
#[test_case(0x04, register::SingleEightBit::B, 0x05, IncrementOperationFlags { zero: 0, half_carry: 0 }; "increment, register B")]
#[test_case(0x04, register::SingleEightBit::B, 0xFF, IncrementOperationFlags { zero: 1, half_carry: 1 }; "increment with wrapping, register B")]
#[test_case(0x04, register::SingleEightBit::B, 0x0F, IncrementOperationFlags { zero: 0, half_carry: 1 }; "increment with half carry, register B")]
#[test_case(0x0C, register::SingleEightBit::C, 0x05, IncrementOperationFlags { zero: 0, half_carry: 0 }; "increment, register C")]
#[test_case(0x0C, register::SingleEightBit::C, 0xFF, IncrementOperationFlags { zero: 1, half_carry: 1 }; "increment with wrapping, register C")]
#[test_case(0x0C, register::SingleEightBit::C, 0x0F, IncrementOperationFlags { zero: 0, half_carry: 1 }; "increment with half carry, register C")]
#[test_case(0x14, register::SingleEightBit::D, 0x05, IncrementOperationFlags { zero: 0, half_carry: 0 }; "increment, register D")]
#[test_case(0x14, register::SingleEightBit::D, 0xFF, IncrementOperationFlags { zero: 1, half_carry: 1 }; "increment with wrapping, register D")]
#[test_case(0x14, register::SingleEightBit::D, 0x0F, IncrementOperationFlags { zero: 0, half_carry: 1 }; "increment with half carry, register D")]
#[test_case(0x1C, register::SingleEightBit::E, 0x05, IncrementOperationFlags { zero: 0, half_carry: 0 }; "increment, register E")]
#[test_case(0x1C, register::SingleEightBit::E, 0xFF, IncrementOperationFlags { zero: 1, half_carry: 1 }; "increment with wrapping, register E")]
#[test_case(0x1C, register::SingleEightBit::E, 0x0F, IncrementOperationFlags { zero: 0, half_carry: 1 }; "increment with half carry, register E")]
#[test_case(0x24, register::SingleEightBit::H, 0x05, IncrementOperationFlags { zero: 0, half_carry: 0 }; "increment, register H")]
#[test_case(0x24, register::SingleEightBit::H, 0xFF, IncrementOperationFlags { zero: 1, half_carry: 1 }; "increment with wrapping, register H")]
#[test_case(0x24, register::SingleEightBit::H, 0x0F, IncrementOperationFlags { zero: 0, half_carry: 1 }; "increment with half carry, register H")]
#[test_case(0x2C, register::SingleEightBit::L, 0x05, IncrementOperationFlags { zero: 0, half_carry: 0 }; "increment, register L")]
#[test_case(0x2C, register::SingleEightBit::L, 0xFF, IncrementOperationFlags { zero: 1, half_carry: 1 }; "increment with wrapping, register L")]
#[test_case(0x2C, register::SingleEightBit::L, 0x0F, IncrementOperationFlags { zero: 0, half_carry: 1 }; "increment with half carry, register L")]
#[test_case(0x3C, register::SingleEightBit::A, 0x05, IncrementOperationFlags { zero: 0, half_carry: 0 }; "increment, register A")]
#[test_case(0x3C, register::SingleEightBit::A, 0xFF, IncrementOperationFlags { zero: 1, half_carry: 1 }; "increment with wrapping, register A")]
#[test_case(0x3C, register::SingleEightBit::A, 0x0F, IncrementOperationFlags { zero: 0, half_carry: 1 }; "increment with half carry, register A")]
fn test_increment_single_register_flags(
opcode: u8,
register: register::SingleEightBit,
initial_value: u8,
expected_flags: IncrementOperationFlags,
) {
let mut processor = Processor::default();
processor
.registers
.set_single_8bit_register(register, initial_value);
// Set all the register to the opposite we expect to ensure they all get set
testutil::set_opposite_of_expected_flags(
&mut processor,
(
expected_flags.zero,
0,
expected_flags.half_carry,
// Carry flag should remain unchanged - quick lazy hack to just set this to 1.
0,
),
);
let data = [opcode, 0x03];
let (ins, extra_data) =
RunnableInstruction::from_data(&data).expect("could not parse instruction");
assert_eq!(extra_data, &[0x03]);
processor.run_instruction(&ins);
testutil::assert_flags_eq!(
processor,
(register::Flag::Zero, expected_flags.zero),
(register::Flag::Subtract, 0),
(register::Flag::HalfCarry, expected_flags.half_carry),
// Carry flag should remain unchanged
(register::Flag::Carry, 1),
);
}
#[test_case(0x05, 0x06; "add one")]
#[test_case(0xFF, 0x00; "add one with wrapping")]
fn test_increment_hl_value(initial_value: u8, expected_value: u8) {
let mut processor = Processor::default();
processor
.registers
.set_combined_register(register::Combined::HL, 0xFF23);
processor
.memory
.set(0xFF23, initial_value)
.expect("failed to set memory value");
let data = [0x34, 0x03];
let (ins, extra_data) =
RunnableInstruction::from_data(&data).expect("could not parse instruction");
assert_eq!(extra_data, &[0x03]);
processor.run_instruction(&ins);
assert_eq!(
expected_value,
processor
.memory
.get(0xFF23)
.expect("failed to get memory value")
);
}
#[test_case(0x05, IncrementOperationFlags { zero: 0, half_carry: 0 }; "increment")]
#[test_case(0xFF, IncrementOperationFlags { zero: 1, half_carry: 1 }; "increment with wrapping")]
#[test_case(0x0F, IncrementOperationFlags { zero: 0, half_carry: 1 }; "increment with half carry")]
fn test_increment_hl_flags(initial_value: u8, expected_flags: IncrementOperationFlags) {
let mut processor = Processor::default();
processor
.registers
.set_combined_register(register::Combined::HL, 0xFF23);
processor
.memory
.set(0xFF23, initial_value)
.expect("failed to set memory value");
// Set all the register to the opposite we expect to ensure they all get set
testutil::set_opposite_of_expected_flags(
&mut processor,
(
expected_flags.zero,
0,
expected_flags.half_carry,
// Carry flag should remain unchanged - quick lazy hack to just set this to 1.
0,
),
);
let data = [0x34, 0x03];
let (ins, extra_data) =
RunnableInstruction::from_data(&data).expect("could not parse instruction");
assert_eq!(extra_data, &[0x03]);
processor.run_instruction(&ins);
testutil::assert_flags_eq!(
processor,
(register::Flag::Zero, expected_flags.zero),
(register::Flag::Subtract, 0),
(register::Flag::HalfCarry, expected_flags.half_carry),
// Carry flag should remain unchanged
(register::Flag::Carry, 1),
);
}