Implement xor instructions

old-bit-manip
Nick Krichevsky 2023-05-09 22:32:02 -04:00
parent 05333a0bb8
commit 59bafee838
18 changed files with 341 additions and 3 deletions

View File

@ -17,6 +17,7 @@ pub enum Instruction {
EightBitAdd(arith8::EightBitAddInstruction),
EightBitSub(arith8::EightBitSubInstruction),
EightBitAnd(arith8::EightBitAndInstruction),
EightBitXor(arith8::EightBitXorInstruction),
}
/// `RunnableInstruction` is an instruction that can run on the processor, and has any metadata needed to do so

View File

@ -31,3 +31,10 @@ pub enum EightBitAndInstruction {
AndHLAddressWithA,
AndImmediateWithA { n: u8 },
}
#[derive(Debug, Clone, Copy)]
pub enum EightBitXorInstruction {
XorSingleRegisterWithA { src: register::SingleEightBit },
XorHLAddressWithA,
XorImmediateWithA { n: u8 },
}

View File

@ -44,6 +44,7 @@ pub fn next_instruction(data: &View) -> ParseResult {
arith8::add::EightBitAddParser::parse_opcode,
arith8::sub::EightBitSubParser::parse_opcode,
arith8::and::EightBitAndParser::parse_opcode,
arith8::xor::EightBitXorParser::parse_opcode,
];
for parse_func in parse_funcs {

View File

@ -1,3 +1,4 @@
pub mod add;
pub mod and;
pub mod sub;
pub mod xor;

View File

@ -0,0 +1,61 @@
use crate::{
cpu::{
instructions::{arith8::EightBitXorInstruction, Instruction, RunnableInstruction},
parse::{self, Error, OpcodeParser, ParseOutput, ParseResult},
},
memory::{GetViewTuple, View},
register,
};
pub struct EightBitXorParser;
impl OpcodeParser for EightBitXorParser {
fn parse_opcode(data: &View) -> ParseResult {
let opcode = parse::get_opcode_from_data(data);
match opcode {
0xA8 => Ok(build_xor_register_with_a_data(register::SingleEightBit::B)),
0xA9 => Ok(build_xor_register_with_a_data(register::SingleEightBit::C)),
0xAA => Ok(build_xor_register_with_a_data(register::SingleEightBit::D)),
0xAB => Ok(build_xor_register_with_a_data(register::SingleEightBit::E)),
0xAC => Ok(build_xor_register_with_a_data(register::SingleEightBit::H)),
0xAD => Ok(build_xor_register_with_a_data(register::SingleEightBit::L)),
0xAF => Ok(build_xor_register_with_a_data(register::SingleEightBit::A)),
0xAE => Ok(build_xor_hl_value_with_a_data()),
0xEE => Ok(build_xor_immediate_with_a_data(data)),
_ => Err(Error::UnknownOpcode(opcode)),
}
}
}
fn build_xor_register_with_a_data(src: register::SingleEightBit) -> ParseOutput {
(
RunnableInstruction {
instruction: Instruction::EightBitXor(EightBitXorInstruction::XorSingleRegisterWithA {
src,
}),
cycles: 4,
},
1,
)
}
fn build_xor_hl_value_with_a_data() -> ParseOutput {
(
RunnableInstruction {
instruction: Instruction::EightBitXor(EightBitXorInstruction::XorHLAddressWithA),
cycles: 8,
},
1,
)
}
fn build_xor_immediate_with_a_data(data: &View) -> ParseOutput {
let (_opcode, n) = data.get_tuple();
(
RunnableInstruction {
instruction: Instruction::EightBitXor(EightBitXorInstruction::XorImmediateWithA { n }),
cycles: 9,
},
2,
)
}

View File

@ -22,7 +22,7 @@ pub enum Error {
/// An invalid address was stored in the given register and attempted to be loaded to/from
#[error("invalid value stored in carry flag: {0:X}")]
InvalidCarryFlagValue(u8),
#[error("an unknown error occured: {0}")]
#[error("an unknown error occurred: {0}")]
Unknown(Box<dyn std::error::Error>),
}
@ -47,8 +47,11 @@ pub fn run_instruction(processor: &mut Processor, instruction: Instruction) -> R
Instruction::EightBitSub(sub_instruction) => {
arith8::EightBitSubRunner::run_instruction(processor, sub_instruction)
}
Instruction::EightBitAnd(sub_instruction) => {
arith8::EightBitAndRunner::run_instruction(processor, sub_instruction)
Instruction::EightBitAnd(and_instruction) => {
arith8::EightBitAndRunner::run_instruction(processor, and_instruction)
}
Instruction::EightBitXor(xor_instruction) => {
arith8::EightBitXorRunner::run_instruction(processor, xor_instruction)
}
}
}

View File

@ -3,12 +3,14 @@ use crate::{cpu::run::Error, cpu::Processor, memory, register};
pub use add::EightBitAddRunner;
pub use and::EightBitAndRunner;
pub use sub::EightBitSubRunner;
pub use xor::EightBitXorRunner;
use super::arithutil::{self, CarriedNumber};
mod add;
mod and;
mod sub;
mod xor;
#[derive(Debug, Clone, Copy)]
enum EightBitArithmeticOperation {

57
src/cpu/run/arith8/xor.rs Normal file
View File

@ -0,0 +1,57 @@
use crate::{
cpu::{
instructions::arith8::EightBitXorInstruction,
run::{Error, InstructionRunner},
Processor,
},
register,
};
use super::{gather_operands, EightBitArithmeticOperation};
pub struct EightBitXorRunner;
impl From<EightBitXorInstruction> for EightBitArithmeticOperation {
fn from(value: EightBitXorInstruction) -> Self {
match value {
EightBitXorInstruction::XorSingleRegisterWithA { src } => {
EightBitArithmeticOperation::SingleRegisterToA { src }
}
EightBitXorInstruction::XorImmediateWithA { n } => {
EightBitArithmeticOperation::ImmediateToA { n }
}
EightBitXorInstruction::XorHLAddressWithA => EightBitArithmeticOperation::HLAddressToA,
}
}
}
impl EightBitXorRunner {
fn do_xor(processor: &mut Processor, lhs: u8, rhs: u8) {
let result = lhs ^ rhs;
processor.registers.a = result;
processor
.registers
.set_flag_bit(register::Flag::Zero, (result == 0).into());
processor
.registers
.set_flag_bit(register::Flag::Subtract, 0);
processor
.registers
.set_flag_bit(register::Flag::HalfCarry, 0);
processor.registers.set_flag_bit(register::Flag::Carry, 0);
}
}
impl InstructionRunner<EightBitXorInstruction> for EightBitXorRunner {
fn run_instruction(
processor: &mut Processor,
instruction: EightBitXorInstruction,
) -> Result<(), Error> {
let (lhs, rhs) = gather_operands(processor, instruction.into())?;
Self::do_xor(processor, lhs, rhs);
Ok(())
}
}

View File

@ -1350,3 +1350,208 @@ fn test_and_immediate_with_a_flags(a_value: u8, operand: u8, expected_zero_flag:
(register::Flag::Carry, 0),
);
}
#[test_case(0xA8, 0xFF, register::SingleEightBit::B, 0xCC, 0x33; "register b")]
#[test_case(0xA9, 0xFF, register::SingleEightBit::C, 0x33, 0xCC; "register c")]
#[test_case(0xAA, 0xFF, register::SingleEightBit::D, 0xCC, 0x33; "register d")]
#[test_case(0xAB, 0xFF, register::SingleEightBit::E, 0x33, 0xCC; "register e")]
#[test_case(0xAC, 0xFF, register::SingleEightBit::H, 0xCC, 0x33; "register h")]
#[test_case(0xAD, 0xFF, register::SingleEightBit::L, 0x33, 0xCC; "register l")]
fn test_xor_register_with_a_value(
opcode: u8,
a_value: u8,
operand_register: register::SingleEightBit,
operand: u8,
expected_value: u8,
) {
let mut processor = Processor::default();
processor
.registers
.set_single_8bit_register(register::SingleEightBit::A, a_value);
processor
.registers
.set_single_8bit_register(operand_register, operand);
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.a);
}
#[test_case(0xA8, 0xFF, register::SingleEightBit::B, 0xCC, 0; "register b, not zero")]
#[test_case(0xA8, 0xCC, register::SingleEightBit::B, 0xCC, 1; "register b, zero")]
#[test_case(0xA9, 0xFF, register::SingleEightBit::C, 0x33, 0; "register c, not zero")]
#[test_case(0xA9, 0x33, register::SingleEightBit::C, 0x33, 1; "register c, zero")]
#[test_case(0xAA, 0xFF, register::SingleEightBit::D, 0xCC, 0; "register d, not zero")]
#[test_case(0xAA, 0xCC, register::SingleEightBit::D, 0xCC, 1; "register d, zero")]
#[test_case(0xAB, 0xFF, register::SingleEightBit::E, 0x33, 0; "register e, not zero")]
#[test_case(0xAB, 0x33, register::SingleEightBit::E, 0x33, 1; "register e, zero")]
#[test_case(0xAC, 0xFF, register::SingleEightBit::H, 0xCC, 0; "register h, not zero")]
#[test_case(0xAC, 0xCC, register::SingleEightBit::H, 0xCC, 1; "register h, zero")]
#[test_case(0xAD, 0xFF, register::SingleEightBit::L, 0x33, 0; "register l, not zero")]
#[test_case(0xAD, 0x33, register::SingleEightBit::L, 0x33, 1; "register l, zero")]
fn test_xor_register_with_a_flags(
opcode: u8,
a_value: u8,
operand_register: register::SingleEightBit,
operand: u8,
expected_zero_flag: u8,
) {
let mut processor = Processor::default();
// Set all the register to the opposite we expect to ensure they all get set
testutil::set_opposite_of_expected_flags(&mut processor, (expected_zero_flag, 0, 0, 0));
processor.registers.a = a_value;
processor
.registers
.set_single_8bit_register(operand_register, operand);
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_zero_flag),
(register::Flag::Subtract, 0),
(register::Flag::HalfCarry, 0),
(register::Flag::Carry, 0),
);
}
#[test]
fn test_xor_register_a_with_itself_value() {
let mut processor = Processor::default();
processor.registers.a = 0x45;
let data = [0xAF, 0x01];
let (ins, extra_data) =
RunnableInstruction::from_data(&data).expect("could not parse instruction");
assert_eq!(extra_data, &[0x01]);
processor.run_instruction(&ins);
assert_eq!(0, processor.registers.a);
}
#[test]
fn test_xor_register_a_with_itself_flags() {
let mut processor = Processor::default();
// Set all the register to the opposite we expect to ensure they all get set
testutil::set_opposite_of_expected_flags(&mut processor, (1, 0, 0, 0));
processor.registers.a = 0x45;
let data = [0xAF, 0x01];
let (ins, extra_data) =
RunnableInstruction::from_data(&data).expect("could not parse instruction");
assert_eq!(extra_data, &[0x01]);
processor.run_instruction(&ins);
testutil::assert_flags_eq!(
processor,
(register::Flag::Zero, 1),
(register::Flag::Subtract, 0),
(register::Flag::HalfCarry, 0),
(register::Flag::Carry, 0),
);
}
#[test]
fn test_xor_hl_value_with_a_value() {
let mut processor = Processor::default();
processor
.registers
.set_single_8bit_register(register::SingleEightBit::A, 0xEE);
processor
.registers
.set_combined_register(register::Combined::HL, 0xFF00);
processor
.memory
.set(0xFF00, 0xBB)
.expect("failed to set memory value");
let data = [0xAE, 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!(0x55, processor.registers.a);
}
#[test_case(0xCC, 0x33, 0; "not zero")]
#[test_case(0xCC, 0xCC, 1; "zero")]
fn test_xor_hl_value_with_a_flags(a_value: u8, operand: u8, expected_zero_flag: u8) {
let mut processor = Processor::default();
testutil::set_opposite_of_expected_flags(&mut processor, (expected_zero_flag, 0, 0, 0));
processor
.registers
.set_single_8bit_register(register::SingleEightBit::A, a_value);
processor
.registers
.set_combined_register(register::Combined::HL, 0xFF00);
processor
.memory
.set(0xFF00, operand)
.expect("failed to set memory value");
let data = [0xAE, 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_zero_flag),
(register::Flag::Subtract, 0),
(register::Flag::HalfCarry, 0),
(register::Flag::Carry, 0),
);
}
#[test]
fn test_xor_immediate_value_with_a_value() {
let mut processor = Processor::default();
processor
.registers
.set_single_8bit_register(register::SingleEightBit::A, 0x22);
let data = [0xEE, 0xEE, 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!(0xCC, processor.registers.a);
}
#[test_case(0xCC, 0x33, 0; "not zero")]
#[test_case(0xCC, 0xCC, 1; "zero")]
fn test_xor_immediate_value_with_a_flags(a_value: u8, operand: u8, expected_zero_flag: u8) {
let mut processor = Processor::default();
testutil::set_opposite_of_expected_flags(&mut processor, (expected_zero_flag, 0, 0, 0));
processor
.registers
.set_single_8bit_register(register::SingleEightBit::A, a_value);
let data = [0xEE, operand, 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_zero_flag),
(register::Flag::Subtract, 0),
(register::Flag::HalfCarry, 0),
(register::Flag::Carry, 0),
);
}