diff --git a/src/cpu/instructions/arith8.rs b/src/cpu/instructions/arith8.rs index e83f725..8e8b4dc 100644 --- a/src/cpu/instructions/arith8.rs +++ b/src/cpu/instructions/arith8.rs @@ -11,6 +11,7 @@ pub enum Operation { Xor, Or, Compare, + Inc, } #[derive(Debug, Copy, Clone)] diff --git a/src/cpu/parse.rs b/src/cpu/parse.rs index e73305e..5a37f71 100644 --- a/src/cpu/parse.rs +++ b/src/cpu/parse.rs @@ -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 { diff --git a/src/cpu/parse/arith8.rs b/src/cpu/parse/arith8.rs index c7161a5..c7fdde1 100644 --- a/src/cpu/parse/arith8.rs +++ b/src/cpu/parse/arith8.rs @@ -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, diff --git a/src/cpu/parse/arith8/inc.rs b/src/cpu/parse/arith8/inc.rs new file mode 100644 index 0000000..9ae000c --- /dev/null +++ b/src/cpu/parse/arith8/inc.rs @@ -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) +} diff --git a/src/cpu/run/arith8.rs b/src/cpu/run/arith8.rs index bc34f27..0b60d5a 100644 --- a/src/cpu/run/arith8.rs +++ b/src/cpu/run/arith8.rs @@ -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 for EightBitArithmeticRunn sub::run(processor, lhs, rhs); processor.registers.a = a_value; } + + Operation::Inc => { + inc::run(processor, instruction.operand)?; + } } Ok(()) diff --git a/src/cpu/run/arith8/inc.rs b/src/cpu/run/arith8/inc.rs new file mode 100644 index 0000000..5fb450d --- /dev/null +++ b/src/cpu/run/arith8/inc.rs @@ -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, + } + }) +} diff --git a/src/cpu/run/arith8/unary.rs b/src/cpu/run/arith8/unary.rs new file mode 100644 index 0000000..ba0dae4 --- /dev/null +++ b/src/cpu/run/arith8/unary.rs @@ -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( + 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 { + 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); +} diff --git a/tests/cpu/arith8.rs b/tests/cpu/arith8.rs index 526a647..4fc7c22 100644 --- a/tests/cpu/arith8.rs +++ b/tests/cpu/arith8.rs @@ -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), + ); +} diff --git a/tests/cpu/jsmoo/testdata/disabled/04.json b/tests/cpu/jsmoo/testdata/04.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/04.json rename to tests/cpu/jsmoo/testdata/04.json diff --git a/tests/cpu/jsmoo/testdata/disabled/0c.json b/tests/cpu/jsmoo/testdata/0c.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/0c.json rename to tests/cpu/jsmoo/testdata/0c.json diff --git a/tests/cpu/jsmoo/testdata/disabled/14.json b/tests/cpu/jsmoo/testdata/14.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/14.json rename to tests/cpu/jsmoo/testdata/14.json diff --git a/tests/cpu/jsmoo/testdata/disabled/1c.json b/tests/cpu/jsmoo/testdata/1c.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/1c.json rename to tests/cpu/jsmoo/testdata/1c.json diff --git a/tests/cpu/jsmoo/testdata/disabled/24.json b/tests/cpu/jsmoo/testdata/24.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/24.json rename to tests/cpu/jsmoo/testdata/24.json diff --git a/tests/cpu/jsmoo/testdata/disabled/2c.json b/tests/cpu/jsmoo/testdata/2c.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/2c.json rename to tests/cpu/jsmoo/testdata/2c.json diff --git a/tests/cpu/jsmoo/testdata/disabled/34.json b/tests/cpu/jsmoo/testdata/34.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/34.json rename to tests/cpu/jsmoo/testdata/34.json diff --git a/tests/cpu/jsmoo/testdata/disabled/3c.json b/tests/cpu/jsmoo/testdata/3c.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/3c.json rename to tests/cpu/jsmoo/testdata/3c.json