Add support for INC instruction
parent
e1a0cdfe62
commit
c25f0cdefa
|
@ -11,6 +11,7 @@ pub enum Operation {
|
|||
Xor,
|
||||
Or,
|
||||
Compare,
|
||||
Inc,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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(())
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue