From 59bafee8382f7e640bfec0026a0f16a08f63c917 Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Tue, 9 May 2023 22:32:02 -0400 Subject: [PATCH] Implement xor instructions --- src/cpu/instructions.rs | 1 + src/cpu/instructions/arith8.rs | 7 + src/cpu/parse.rs | 1 + src/cpu/parse/arith8.rs | 1 + src/cpu/parse/arith8/xor.rs | 61 ++++++ src/cpu/run.rs | 9 +- src/cpu/run/arith8.rs | 2 + src/cpu/run/arith8/xor.rs | 57 +++++ tests/cpu/arith8.rs | 205 ++++++++++++++++++ .../cpu/jsmoo/testdata/{disabled => }/a8.json | 0 .../cpu/jsmoo/testdata/{disabled => }/a9.json | 0 .../cpu/jsmoo/testdata/{disabled => }/aa.json | 0 .../cpu/jsmoo/testdata/{disabled => }/ab.json | 0 .../cpu/jsmoo/testdata/{disabled => }/ac.json | 0 .../cpu/jsmoo/testdata/{disabled => }/ad.json | 0 .../cpu/jsmoo/testdata/{disabled => }/ae.json | 0 .../cpu/jsmoo/testdata/{disabled => }/af.json | 0 .../cpu/jsmoo/testdata/{disabled => }/ee.json | 0 18 files changed, 341 insertions(+), 3 deletions(-) create mode 100644 src/cpu/parse/arith8/xor.rs create mode 100644 src/cpu/run/arith8/xor.rs rename tests/cpu/jsmoo/testdata/{disabled => }/a8.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/a9.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/aa.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/ab.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/ac.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/ad.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/ae.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/af.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/ee.json (100%) diff --git a/src/cpu/instructions.rs b/src/cpu/instructions.rs index 95bc1ac..1391138 100644 --- a/src/cpu/instructions.rs +++ b/src/cpu/instructions.rs @@ -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 diff --git a/src/cpu/instructions/arith8.rs b/src/cpu/instructions/arith8.rs index b1172a6..4550d68 100644 --- a/src/cpu/instructions/arith8.rs +++ b/src/cpu/instructions/arith8.rs @@ -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 }, +} diff --git a/src/cpu/parse.rs b/src/cpu/parse.rs index 037c721..f21ab7a 100644 --- a/src/cpu/parse.rs +++ b/src/cpu/parse.rs @@ -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 { diff --git a/src/cpu/parse/arith8.rs b/src/cpu/parse/arith8.rs index e1f730a..a73b0cd 100644 --- a/src/cpu/parse/arith8.rs +++ b/src/cpu/parse/arith8.rs @@ -1,3 +1,4 @@ pub mod add; pub mod and; pub mod sub; +pub mod xor; diff --git a/src/cpu/parse/arith8/xor.rs b/src/cpu/parse/arith8/xor.rs new file mode 100644 index 0000000..638d927 --- /dev/null +++ b/src/cpu/parse/arith8/xor.rs @@ -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, + ) +} diff --git a/src/cpu/run.rs b/src/cpu/run.rs index 6959bbe..ae0f942 100644 --- a/src/cpu/run.rs +++ b/src/cpu/run.rs @@ -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), } @@ -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) } } } diff --git a/src/cpu/run/arith8.rs b/src/cpu/run/arith8.rs index b0d4bb9..15c667d 100644 --- a/src/cpu/run/arith8.rs +++ b/src/cpu/run/arith8.rs @@ -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 { diff --git a/src/cpu/run/arith8/xor.rs b/src/cpu/run/arith8/xor.rs new file mode 100644 index 0000000..70a3feb --- /dev/null +++ b/src/cpu/run/arith8/xor.rs @@ -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 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 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(()) + } +} diff --git a/tests/cpu/arith8.rs b/tests/cpu/arith8.rs index 236ecc4..dff3f86 100644 --- a/tests/cpu/arith8.rs +++ b/tests/cpu/arith8.rs @@ -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), + ); +} diff --git a/tests/cpu/jsmoo/testdata/disabled/a8.json b/tests/cpu/jsmoo/testdata/a8.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/a8.json rename to tests/cpu/jsmoo/testdata/a8.json diff --git a/tests/cpu/jsmoo/testdata/disabled/a9.json b/tests/cpu/jsmoo/testdata/a9.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/a9.json rename to tests/cpu/jsmoo/testdata/a9.json diff --git a/tests/cpu/jsmoo/testdata/disabled/aa.json b/tests/cpu/jsmoo/testdata/aa.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/aa.json rename to tests/cpu/jsmoo/testdata/aa.json diff --git a/tests/cpu/jsmoo/testdata/disabled/ab.json b/tests/cpu/jsmoo/testdata/ab.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/ab.json rename to tests/cpu/jsmoo/testdata/ab.json diff --git a/tests/cpu/jsmoo/testdata/disabled/ac.json b/tests/cpu/jsmoo/testdata/ac.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/ac.json rename to tests/cpu/jsmoo/testdata/ac.json diff --git a/tests/cpu/jsmoo/testdata/disabled/ad.json b/tests/cpu/jsmoo/testdata/ad.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/ad.json rename to tests/cpu/jsmoo/testdata/ad.json diff --git a/tests/cpu/jsmoo/testdata/disabled/ae.json b/tests/cpu/jsmoo/testdata/ae.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/ae.json rename to tests/cpu/jsmoo/testdata/ae.json diff --git a/tests/cpu/jsmoo/testdata/disabled/af.json b/tests/cpu/jsmoo/testdata/af.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/af.json rename to tests/cpu/jsmoo/testdata/af.json diff --git a/tests/cpu/jsmoo/testdata/disabled/ee.json b/tests/cpu/jsmoo/testdata/ee.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/ee.json rename to tests/cpu/jsmoo/testdata/ee.json