Add support for INC instruction
parent
e1a0cdfe62
commit
c25f0cdefa
|
@ -11,6 +11,7 @@ pub enum Operation {
|
||||||
Xor,
|
Xor,
|
||||||
Or,
|
Or,
|
||||||
Compare,
|
Compare,
|
||||||
|
Inc,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
|
|
@ -47,6 +47,7 @@ pub fn next_instruction(data: &View) -> ParseResult {
|
||||||
arith8::xor::Parser::parse_opcode,
|
arith8::xor::Parser::parse_opcode,
|
||||||
arith8::or::Parser::parse_opcode,
|
arith8::or::Parser::parse_opcode,
|
||||||
arith8::compare::Parser::parse_opcode,
|
arith8::compare::Parser::parse_opcode,
|
||||||
|
arith8::inc::Parser::parse_opcode,
|
||||||
];
|
];
|
||||||
|
|
||||||
for parse_func in parse_funcs {
|
for parse_func in parse_funcs {
|
||||||
|
|
|
@ -15,6 +15,7 @@ pub mod compare;
|
||||||
pub mod or;
|
pub mod or;
|
||||||
pub mod sub;
|
pub mod sub;
|
||||||
pub mod xor;
|
pub mod xor;
|
||||||
|
pub mod inc;
|
||||||
|
|
||||||
fn build_instruction_between_register_and_a_data(
|
fn build_instruction_between_register_and_a_data(
|
||||||
operation: Operation,
|
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 and;
|
||||||
mod or;
|
mod or;
|
||||||
mod sub;
|
mod sub;
|
||||||
|
mod inc;
|
||||||
|
mod unary;
|
||||||
mod xor;
|
mod xor;
|
||||||
|
|
||||||
pub struct EightBitArithmeticRunner;
|
pub struct EightBitArithmeticRunner;
|
||||||
|
@ -69,6 +71,10 @@ impl InstructionRunner<EightBitArithmeticInstruction> for EightBitArithmeticRunn
|
||||||
sub::run(processor, lhs, rhs);
|
sub::run(processor, lhs, rhs);
|
||||||
processor.registers.a = a_value;
|
processor.registers.a = a_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Operation::Inc => {
|
||||||
|
inc::run(processor, instruction.operand)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
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,
|
carry: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct IncrementOperationFlags {
|
||||||
|
zero: u8,
|
||||||
|
half_carry: u8,
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_a_to_itself() {
|
fn test_add_a_to_itself() {
|
||||||
let mut processor = Processor::default();
|
let mut processor = Processor::default();
|
||||||
|
@ -2129,3 +2134,168 @@ fn test_comparing_immediate_value_to_a(
|
||||||
(register::Flag::Carry, expected_flags.carry),
|
(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