diff --git a/src/cpu/instructions.rs b/src/cpu/instructions.rs index a0b3c99..95bc1ac 100644 --- a/src/cpu/instructions.rs +++ b/src/cpu/instructions.rs @@ -16,6 +16,7 @@ pub enum Instruction { SixteenBitLoad(load16::SixteenBitLoadInstruction), EightBitAdd(arith8::EightBitAddInstruction), EightBitSub(arith8::EightBitSubInstruction), + EightBitAnd(arith8::EightBitAndInstruction), } /// `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 96c184c..b1172a6 100644 --- a/src/cpu/instructions/arith8.rs +++ b/src/cpu/instructions/arith8.rs @@ -24,3 +24,10 @@ pub enum EightBitSubInstruction { SubHLAddressFromAWithCarry, SubImmediateFromAWithCarry { n: u8 }, } + +#[derive(Debug, Clone, Copy)] +pub enum EightBitAndInstruction { + AndSingleRegisterWithA { src: register::SingleEightBit }, + AndHLAddressWithA, + AndImmediateWithA { n: u8 }, +} diff --git a/src/cpu/parse.rs b/src/cpu/parse.rs index 951ea3a..037c721 100644 --- a/src/cpu/parse.rs +++ b/src/cpu/parse.rs @@ -43,6 +43,7 @@ pub fn next_instruction(data: &View) -> ParseResult { load16::stack::StackLoadParser::parse_opcode, arith8::add::EightBitAddParser::parse_opcode, arith8::sub::EightBitSubParser::parse_opcode, + arith8::and::EightBitAndParser::parse_opcode, ]; for parse_func in parse_funcs { diff --git a/src/cpu/parse/arith8.rs b/src/cpu/parse/arith8.rs index 42f1cf0..e1f730a 100644 --- a/src/cpu/parse/arith8.rs +++ b/src/cpu/parse/arith8.rs @@ -1,2 +1,3 @@ pub mod add; +pub mod and; pub mod sub; diff --git a/src/cpu/parse/arith8/and.rs b/src/cpu/parse/arith8/and.rs new file mode 100644 index 0000000..b588053 --- /dev/null +++ b/src/cpu/parse/arith8/and.rs @@ -0,0 +1,62 @@ +use crate::{ + cpu::{ + instructions::{arith8::EightBitAndInstruction, Instruction, RunnableInstruction}, + parse::{self, Error, OpcodeParser, ParseOutput, ParseResult}, + }, + memory::{GetViewTuple, View}, + register, +}; + +pub struct EightBitAndParser; + +impl OpcodeParser for EightBitAndParser { + fn parse_opcode(data: &View) -> ParseResult { + let opcode = parse::get_opcode_from_data(data); + match opcode { + 0xA0 => Ok(build_and_register_with_a_data(register::SingleEightBit::B)), + 0xA1 => Ok(build_and_register_with_a_data(register::SingleEightBit::C)), + 0xA2 => Ok(build_and_register_with_a_data(register::SingleEightBit::D)), + 0xA3 => Ok(build_and_register_with_a_data(register::SingleEightBit::E)), + 0xA4 => Ok(build_and_register_with_a_data(register::SingleEightBit::H)), + 0xA5 => Ok(build_and_register_with_a_data(register::SingleEightBit::L)), + 0xA7 => Ok(build_and_register_with_a_data(register::SingleEightBit::A)), + 0xA6 => Ok(build_and_hl_value_with_a_data()), + 0xE6 => Ok(build_and_immediate_with_a_data(data)), + _ => Err(Error::UnknownOpcode(opcode)), + } + } +} + +fn build_and_register_with_a_data(src: register::SingleEightBit) -> ParseOutput { + ( + RunnableInstruction { + instruction: Instruction::EightBitAnd(EightBitAndInstruction::AndSingleRegisterWithA { + src, + }), + cycles: 4, + }, + 1, + ) +} + +fn build_and_hl_value_with_a_data() -> ParseOutput { + ( + RunnableInstruction { + instruction: Instruction::EightBitAnd(EightBitAndInstruction::AndHLAddressWithA), + cycles: 8, + }, + 1, + ) +} + +fn build_and_immediate_with_a_data(data: &View) -> ParseOutput { + let (_opcode, n) = data.get_tuple(); + + ( + RunnableInstruction { + instruction: Instruction::EightBitAnd(EightBitAndInstruction::AndImmediateWithA { n }), + cycles: 8, + }, + 2, + ) +} diff --git a/src/cpu/run.rs b/src/cpu/run.rs index 03f8fec..6959bbe 100644 --- a/src/cpu/run.rs +++ b/src/cpu/run.rs @@ -47,5 +47,8 @@ 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) + } } } diff --git a/src/cpu/run/arith8.rs b/src/cpu/run/arith8.rs index b5b447e..b0d4bb9 100644 --- a/src/cpu/run/arith8.rs +++ b/src/cpu/run/arith8.rs @@ -1,11 +1,13 @@ use crate::{cpu::run::Error, cpu::Processor, memory, register}; pub use add::EightBitAddRunner; +pub use and::EightBitAndRunner; pub use sub::EightBitSubRunner; use super::arithutil::{self, CarriedNumber}; mod add; +mod and; mod sub; #[derive(Debug, Clone, Copy)] diff --git a/src/cpu/run/arith8/and.rs b/src/cpu/run/arith8/and.rs new file mode 100644 index 0000000..71d7a80 --- /dev/null +++ b/src/cpu/run/arith8/and.rs @@ -0,0 +1,70 @@ +use crate::{ + cpu::{ + instructions::arith8::EightBitAndInstruction, + run::{Error, InstructionRunner}, + Processor, + }, + register, +}; + +pub struct EightBitAndRunner; + +impl EightBitAndRunner { + fn do_and(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, 1); + processor.registers.set_flag_bit(register::Flag::Carry, 0); + } +} + +impl InstructionRunner for EightBitAndRunner { + fn run_instruction( + processor: &mut Processor, + instruction: EightBitAndInstruction, + ) -> Result<(), Error> { + match instruction { + EightBitAndInstruction::AndSingleRegisterWithA { src } => { + let (lhs, rhs) = super::gather_operands( + processor, + super::EightBitArithmeticOperation::SingleRegisterToA { src }, + )?; + + Self::do_and(processor, lhs, rhs); + + Ok(()) + } + + EightBitAndInstruction::AndHLAddressWithA => { + let (lhs, rhs) = super::gather_operands( + processor, + super::EightBitArithmeticOperation::HLAddressToA, + )?; + + Self::do_and(processor, lhs, rhs); + + Ok(()) + } + + EightBitAndInstruction::AndImmediateWithA { n } => { + let (lhs, rhs) = super::gather_operands( + processor, + super::EightBitArithmeticOperation::ImmediateToA { n }, + )?; + + Self::do_and(processor, lhs, rhs); + + Ok(()) + } + } + } +} diff --git a/tests/cpu/arith8.rs b/tests/cpu/arith8.rs index a4f8251..236ecc4 100644 --- a/tests/cpu/arith8.rs +++ b/tests/cpu/arith8.rs @@ -1156,3 +1156,197 @@ fn test_sub_hl_addr_from_a_with_carry_flags( (register::Flag::Carry, expected_flags.carry), ); } + +#[test_case(0xA0, 0xFB, register::SingleEightBit::B, 0x0F, 0x0B; "AND with B")] +#[test_case(0xA1, 0xFC, register::SingleEightBit::C, 0x0F, 0x0C; "AND with C")] +#[test_case(0xA2, 0xFD, register::SingleEightBit::D, 0x0F, 0x0D; "AND with D")] +#[test_case(0xA3, 0xFE, register::SingleEightBit::E, 0x0F, 0x0E; "AND with E")] +#[test_case(0xA4, 0xF0, register::SingleEightBit::H, 0x0F, 0x00; "AND with H")] +#[test_case(0xA5, 0xF1, register::SingleEightBit::L, 0x0F, 0x01; "AND with L")] +fn test_and_single_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.a = a_value; + processor + .registers + .set_single_8bit_register(operand_register, operand); + + let data = [opcode, 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!(expected_value, processor.registers.a); +} + +#[test_case(0xA0, 0xFA, register::SingleEightBit::B, 0x0F, 0; "AND with B, not zero")] +#[test_case(0xA0, 0xFA, register::SingleEightBit::B, 0x00, 1; "AND with B, zero")] +#[test_case(0xA1, 0xFA, register::SingleEightBit::C, 0x0F, 0; "AND with C, not zero")] +#[test_case(0xA1, 0xFA, register::SingleEightBit::C, 0x00, 1; "AND with C, zero")] +#[test_case(0xA2, 0xFA, register::SingleEightBit::D, 0x0F, 0; "AND with D, not zero")] +#[test_case(0xA2, 0xFA, register::SingleEightBit::D, 0x00, 1; "AND with D, zero")] +#[test_case(0xA3, 0xFA, register::SingleEightBit::E, 0x0F, 0; "AND with E, not zero")] +#[test_case(0xA3, 0xFA, register::SingleEightBit::E, 0x00, 1; "AND with E, zero")] +fn test_and_single_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, 1, 0)); + + processor.registers.a = a_value; + processor + .registers + .set_single_8bit_register(operand_register, operand); + + let data = [opcode, 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, 1), + (register::Flag::Carry, 0), + ); +} + +#[test] +fn test_and_a_with_itself_value() { + let mut processor = Processor::default(); + processor.registers.a = 0xAB; + + let data = [0xA7, 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!(0xAB, processor.registers.a); +} + +#[test] +fn test_and_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, (0, 0, 1, 0)); + + processor.registers.a = 0xAB; + + let data = [0xA7, 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, 0), + (register::Flag::Subtract, 0), + (register::Flag::HalfCarry, 1), + (register::Flag::Carry, 0), + ); +} + +#[test] +fn test_and_hl_value_with_a_value() { + let mut processor = Processor::default(); + processor.registers.a = 0xAB; + processor + .registers + .set_combined_register(register::Combined::HL, 0x00FF); + processor + .memory + .set(0x00FF, 0xF0) + .expect("failed to set value"); + + let data = [0xA6, 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!(0xA0, processor.registers.a); +} + +#[test_case(0xFA, 0x0F, 0; "not zero")] +#[test_case(0xFA, 0x00, 1; "zero")] +fn test_and_hl_value_with_a_flags(a_value: u8, operand: u8, expected_zero_flag: u8) { + let mut processor = Processor::default(); + processor.registers.a = a_value; + processor + .registers + .set_combined_register(register::Combined::HL, 0x00FF); + processor + .memory + .set(0x00FF, operand) + .expect("failed to set 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_zero_flag, 0, 1, 0)); + + let data = [0xA6, 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, 1), + (register::Flag::Carry, 0), + ); +} + +#[test] +fn test_and_immediate_with_a_value() { + let mut processor = Processor::default(); + processor.registers.a = 0xAB; + + let data = [0xE6, 0x0F, 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!(0x0B, processor.registers.a); +} + +#[test_case(0xFA, 0x0F, 0; "not zero")] +#[test_case(0xFA, 0x00, 1; "zero")] +fn test_and_immediate_with_a_flags(a_value: u8, operand: u8, expected_zero_flag: u8) { + let mut processor = Processor::default(); + processor.registers.a = a_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_zero_flag, 0, 1, 0)); + + let data = [0xE6, operand, 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, 1), + (register::Flag::Carry, 0), + ); +} diff --git a/tests/cpu/jsmoo/testdata/disabled/a0.json b/tests/cpu/jsmoo/testdata/a0.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/a0.json rename to tests/cpu/jsmoo/testdata/a0.json diff --git a/tests/cpu/jsmoo/testdata/disabled/a1.json b/tests/cpu/jsmoo/testdata/a1.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/a1.json rename to tests/cpu/jsmoo/testdata/a1.json diff --git a/tests/cpu/jsmoo/testdata/disabled/a2.json b/tests/cpu/jsmoo/testdata/a2.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/a2.json rename to tests/cpu/jsmoo/testdata/a2.json diff --git a/tests/cpu/jsmoo/testdata/disabled/a3.json b/tests/cpu/jsmoo/testdata/a3.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/a3.json rename to tests/cpu/jsmoo/testdata/a3.json diff --git a/tests/cpu/jsmoo/testdata/disabled/a4.json b/tests/cpu/jsmoo/testdata/a4.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/a4.json rename to tests/cpu/jsmoo/testdata/a4.json diff --git a/tests/cpu/jsmoo/testdata/disabled/a5.json b/tests/cpu/jsmoo/testdata/a5.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/a5.json rename to tests/cpu/jsmoo/testdata/a5.json diff --git a/tests/cpu/jsmoo/testdata/disabled/a6.json b/tests/cpu/jsmoo/testdata/a6.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/a6.json rename to tests/cpu/jsmoo/testdata/a6.json diff --git a/tests/cpu/jsmoo/testdata/disabled/a7.json b/tests/cpu/jsmoo/testdata/a7.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/a7.json rename to tests/cpu/jsmoo/testdata/a7.json diff --git a/tests/cpu/jsmoo/testdata/disabled/e6.json b/tests/cpu/jsmoo/testdata/e6.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/e6.json rename to tests/cpu/jsmoo/testdata/e6.json