From e841931d554c932e9bb57afe1bf7cd4170e1b1f4 Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Sat, 13 May 2023 14:41:09 -0400 Subject: [PATCH] Add support for OR instructions --- src/cpu/instructions/arith8.rs | 1 + src/cpu/parse.rs | 1 + src/cpu/parse/arith8.rs | 1 + src/cpu/parse/arith8/or.rs | 42 ++++ src/cpu/run/arith8.rs | 26 ++- src/cpu/run/arith8/or.rs | 16 ++ tests/cpu/arith8.rs | 207 ++++++++++++++++++ .../cpu/jsmoo/testdata/{disabled => }/b0.json | 0 .../cpu/jsmoo/testdata/{disabled => }/b1.json | 0 .../cpu/jsmoo/testdata/{disabled => }/b2.json | 0 .../cpu/jsmoo/testdata/{disabled => }/b3.json | 0 .../cpu/jsmoo/testdata/{disabled => }/b4.json | 0 .../cpu/jsmoo/testdata/{disabled => }/b5.json | 0 .../cpu/jsmoo/testdata/{disabled => }/b6.json | 0 .../cpu/jsmoo/testdata/{disabled => }/f6.json | 0 15 files changed, 284 insertions(+), 10 deletions(-) create mode 100644 src/cpu/parse/arith8/or.rs create mode 100644 src/cpu/run/arith8/or.rs rename tests/cpu/jsmoo/testdata/{disabled => }/b0.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/b1.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/b2.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/b3.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/b4.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/b5.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/b6.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/f6.json (100%) diff --git a/src/cpu/instructions/arith8.rs b/src/cpu/instructions/arith8.rs index 79ae33f..f9418d7 100644 --- a/src/cpu/instructions/arith8.rs +++ b/src/cpu/instructions/arith8.rs @@ -9,6 +9,7 @@ pub enum Operation { SubWithCarry, And, Xor, + Or, } #[derive(Debug, Copy, Clone)] diff --git a/src/cpu/parse.rs b/src/cpu/parse.rs index f21ab7a..1e51d95 100644 --- a/src/cpu/parse.rs +++ b/src/cpu/parse.rs @@ -45,6 +45,7 @@ pub fn next_instruction(data: &View) -> ParseResult { arith8::sub::EightBitSubParser::parse_opcode, arith8::and::EightBitAndParser::parse_opcode, arith8::xor::EightBitXorParser::parse_opcode, + arith8::or::EightBitOrParser::parse_opcode, ]; for parse_func in parse_funcs { diff --git a/src/cpu/parse/arith8.rs b/src/cpu/parse/arith8.rs index c226226..926e340 100644 --- a/src/cpu/parse/arith8.rs +++ b/src/cpu/parse/arith8.rs @@ -11,6 +11,7 @@ use super::ParseOutput; pub mod add; pub mod and; +pub mod or; pub mod sub; pub mod xor; diff --git a/src/cpu/parse/arith8/or.rs b/src/cpu/parse/arith8/or.rs new file mode 100644 index 0000000..c0cd3a9 --- /dev/null +++ b/src/cpu/parse/arith8/or.rs @@ -0,0 +1,42 @@ +use crate::{ + cpu::{ + instructions::arith8::Operation, + parse::{self, Error, OpcodeParser, ParseOutput, ParseResult}, + }, + memory::View, + register, +}; + +pub struct EightBitOrParser; + +impl OpcodeParser for EightBitOrParser { + fn parse_opcode(data: &View) -> ParseResult { + let opcode = parse::get_opcode_from_data(data); + match opcode { + 0xB0 => Ok(build_or_register_with_a_data(register::SingleEightBit::B)), + 0xB1 => Ok(build_or_register_with_a_data(register::SingleEightBit::C)), + 0xB2 => Ok(build_or_register_with_a_data(register::SingleEightBit::D)), + 0xB3 => Ok(build_or_register_with_a_data(register::SingleEightBit::E)), + 0xB4 => Ok(build_or_register_with_a_data(register::SingleEightBit::H)), + 0xB5 => Ok(build_or_register_with_a_data(register::SingleEightBit::L)), + 0xB7 => Ok(build_or_register_with_a_data(register::SingleEightBit::A)), + + 0xB6 => Ok(build_or_hl_value_with_a_data()), + 0xF6 => Ok(build_or_immediate_with_a_data(data)), + + _ => Err(Error::UnknownOpcode(opcode)), + } + } +} + +fn build_or_register_with_a_data(src: register::SingleEightBit) -> ParseOutput { + super::build_instruction_between_register_and_a_data(Operation::Or, src) +} + +fn build_or_hl_value_with_a_data() -> ParseOutput { + super::build_instruction_between_hl_value_and_a_data(Operation::Or) +} + +fn build_or_immediate_with_a_data(data: &View) -> ParseOutput { + super::build_instruction_between_immediate_and_a_data(Operation::Or, data) +} diff --git a/src/cpu/run/arith8.rs b/src/cpu/run/arith8.rs index 6e12a16..770a013 100644 --- a/src/cpu/run/arith8.rs +++ b/src/cpu/run/arith8.rs @@ -14,6 +14,7 @@ use super::{ mod add; mod and; +mod or; mod sub; mod xor; @@ -25,16 +26,6 @@ impl InstructionRunner for EightBitArithmeticRunn instruction: EightBitArithmeticInstruction, ) -> Result<(), Error> { match instruction.operation { - Operation::And => { - let (lhs, rhs) = gather_operands(processor, instruction.operand)?; - and::run(processor, lhs, rhs); - } - - Operation::Xor => { - let (lhs, rhs) = gather_operands(processor, instruction.operand)?; - xor::run(processor, lhs, rhs); - } - Operation::Add => { let (lhs, rhs) = gather_operands(processor, instruction.operand)?; add::run(processor, lhs, rhs); @@ -54,6 +45,21 @@ impl InstructionRunner for EightBitArithmeticRunn let (lhs, rhs) = gather_operands_with_carry(processor, instruction.operand)?; sub::run(processor, lhs, rhs); } + + Operation::And => { + let (lhs, rhs) = gather_operands(processor, instruction.operand)?; + and::run(processor, lhs, rhs); + } + + Operation::Xor => { + let (lhs, rhs) = gather_operands(processor, instruction.operand)?; + xor::run(processor, lhs, rhs); + } + + Operation::Or => { + let (lhs, rhs) = gather_operands(processor, instruction.operand)?; + or::run(processor, lhs, rhs); + } } Ok(()) diff --git a/src/cpu/run/arith8/or.rs b/src/cpu/run/arith8/or.rs new file mode 100644 index 0000000..3e0377e --- /dev/null +++ b/src/cpu/run/arith8/or.rs @@ -0,0 +1,16 @@ +use crate::{cpu::Processor, register}; + +pub fn run(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); +} diff --git a/tests/cpu/arith8.rs b/tests/cpu/arith8.rs index dff3f86..b316b75 100644 --- a/tests/cpu/arith8.rs +++ b/tests/cpu/arith8.rs @@ -1555,3 +1555,210 @@ fn test_xor_immediate_value_with_a_flags(a_value: u8, operand: u8, expected_zero (register::Flag::Carry, 0), ); } + +#[test_case(0xB0, 0xCC, register::SingleEightBit::B, 0x11, 0xDD; "register b")] +#[test_case(0xB1, 0xCC, register::SingleEightBit::C, 0x11, 0xDD; "register c")] +#[test_case(0xB2, 0xCC, register::SingleEightBit::D, 0x11, 0xDD; "register d")] +#[test_case(0xB3, 0xCC, register::SingleEightBit::E, 0x11, 0xDD; "register e")] +#[test_case(0xB4, 0xCC, register::SingleEightBit::H, 0x11, 0xDD; "register h")] +#[test_case(0xB5, 0xCC, register::SingleEightBit::L, 0x11, 0xDD; "register l")] +fn test_or_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(0xB0, 0xCC, register::SingleEightBit::B, 0x22, 0; "register b, not zero")] +#[test_case(0xB0, 0x00, register::SingleEightBit::B, 0x00, 1; "register b, zero")] +#[test_case(0xB1, 0xCC, register::SingleEightBit::C, 0x22, 0; "register c, not zero")] +#[test_case(0xB1, 0x00, register::SingleEightBit::C, 0x00, 1; "register c, zero")] +#[test_case(0xB2, 0xCC, register::SingleEightBit::D, 0x22, 0; "register d, not zero")] +#[test_case(0xB2, 0x00, register::SingleEightBit::D, 0x00, 1; "register d, zero")] +#[test_case(0xB3, 0xCC, register::SingleEightBit::E, 0x22, 0; "register e, not zero")] +#[test_case(0xB3, 0x00, register::SingleEightBit::E, 0x00, 1; "register e, zero")] +#[test_case(0xB4, 0xCC, register::SingleEightBit::H, 0x22, 0; "register h, not zero")] +#[test_case(0xB4, 0x00, register::SingleEightBit::H, 0x00, 1; "register h, zero")] +#[test_case(0xB5, 0xCC, register::SingleEightBit::L, 0x22, 0; "register l, not zero")] +#[test_case(0xB5, 0x00, register::SingleEightBit::L, 0x00, 1; "register l, zero")] +fn test_or_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_or_register_a_with_itself_value() { + let mut processor = Processor::default(); + processor.registers.a = 0x45; + + let data = [0xB7, 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!(0x45, processor.registers.a); +} + +#[test_case(0xCC, 0)] +#[test_case(0x00, 1)] +fn test_or_register_a_with_itself_flags(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 = operand; + + let data = [0xB7, 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, expected_zero_flag), + (register::Flag::Subtract, 0), + (register::Flag::HalfCarry, 0), + (register::Flag::Carry, 0), + ); +} + +#[test] +fn test_or_hl_value_with_a_value() { + let mut processor = Processor::default(); + processor + .registers + .set_single_8bit_register(register::SingleEightBit::A, 0x11); + processor + .registers + .set_combined_register(register::Combined::HL, 0xFF00); + + processor + .memory + .set(0xFF00, 0xAA) + .expect("failed to set memory value"); + + let data = [0xB6, 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!(0xBB, processor.registers.a); +} + +#[test_case(0xBB, 0x11, 0)] +#[test_case(0x00, 0x00, 1)] +fn test_or_hl_value_with_a_flags(a_value: u8, hl_value: 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 + .set_single_8bit_register(register::SingleEightBit::A, a_value); + processor + .registers + .set_combined_register(register::Combined::HL, 0xFF00); + + processor + .memory + .set(0xFF00, hl_value) + .expect("failed to set memory value"); + + let data = [0xB7, 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, expected_zero_flag), + (register::Flag::Subtract, 0), + (register::Flag::HalfCarry, 0), + (register::Flag::Carry, 0), + ); +} + +#[test] +fn test_or_immediate_value_with_a_value() { + let mut processor = Processor::default(); + processor + .registers + .set_single_8bit_register(register::SingleEightBit::A, 0x33); + + let data = [0xF6, 0xAA, 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!(0xBB, processor.registers.a); +} + +#[test_case(0xFF, 0x00, 0; "not zero")] +#[test_case(0x00, 0x00, 1; "zero")] +fn test_or_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 = [0xF6, 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/b0.json b/tests/cpu/jsmoo/testdata/b0.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/b0.json rename to tests/cpu/jsmoo/testdata/b0.json diff --git a/tests/cpu/jsmoo/testdata/disabled/b1.json b/tests/cpu/jsmoo/testdata/b1.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/b1.json rename to tests/cpu/jsmoo/testdata/b1.json diff --git a/tests/cpu/jsmoo/testdata/disabled/b2.json b/tests/cpu/jsmoo/testdata/b2.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/b2.json rename to tests/cpu/jsmoo/testdata/b2.json diff --git a/tests/cpu/jsmoo/testdata/disabled/b3.json b/tests/cpu/jsmoo/testdata/b3.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/b3.json rename to tests/cpu/jsmoo/testdata/b3.json diff --git a/tests/cpu/jsmoo/testdata/disabled/b4.json b/tests/cpu/jsmoo/testdata/b4.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/b4.json rename to tests/cpu/jsmoo/testdata/b4.json diff --git a/tests/cpu/jsmoo/testdata/disabled/b5.json b/tests/cpu/jsmoo/testdata/b5.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/b5.json rename to tests/cpu/jsmoo/testdata/b5.json diff --git a/tests/cpu/jsmoo/testdata/disabled/b6.json b/tests/cpu/jsmoo/testdata/b6.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/b6.json rename to tests/cpu/jsmoo/testdata/b6.json diff --git a/tests/cpu/jsmoo/testdata/disabled/f6.json b/tests/cpu/jsmoo/testdata/f6.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/f6.json rename to tests/cpu/jsmoo/testdata/f6.json