From 1bbd14e5d2c868a4fb39c1742126e4ce0c4f2e22 Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Sat, 18 Nov 2023 19:05:05 -0500 Subject: [PATCH] Add support for carry bit set instructions --- src/cpu/instructions.rs | 2 + src/cpu/instructions/load16.rs | 4 +- src/cpu/instructions/misc.rs | 7 ++ src/cpu/parse.rs | 2 + src/cpu/parse/load16/stack.rs | 10 ++- src/cpu/parse/misc.rs | 38 +++++++++++ src/cpu/run.rs | 2 + src/cpu/run/load16.rs | 12 ++-- src/cpu/run/load8.rs | 2 +- src/cpu/run/misc.rs | 32 +++++++++ .../cpu/jsmoo/testdata/{disabled => }/37.json | 0 .../cpu/jsmoo/testdata/{disabled => }/3f.json | 0 tests/cpu/main.rs | 1 + tests/cpu/misc.rs | 68 +++++++++++++++++++ 14 files changed, 164 insertions(+), 16 deletions(-) create mode 100644 src/cpu/instructions/misc.rs create mode 100644 src/cpu/parse/misc.rs create mode 100644 src/cpu/run/misc.rs rename tests/cpu/jsmoo/testdata/{disabled => }/37.json (100%) rename tests/cpu/jsmoo/testdata/{disabled => }/3f.json (100%) create mode 100644 tests/cpu/misc.rs diff --git a/src/cpu/instructions.rs b/src/cpu/instructions.rs index 7c638cb..e45057b 100644 --- a/src/cpu/instructions.rs +++ b/src/cpu/instructions.rs @@ -8,6 +8,7 @@ pub mod arith16; pub mod arith8; pub mod load16; pub mod load8; +pub mod misc; // these are indexed with the instruction numbers in this manual // http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf @@ -18,6 +19,7 @@ pub enum Instruction { EightBitArithmetic(arith8::EightBitArithmeticInstruction), StackPointerAdjust(arith8::AdjustStackPointerInstruction), SixteenBitArithmetic(arith16::SixteenBitArithmeticInstruction), + Misc(misc::MiscInstruction), } /// `RunnableInstruction` is an instruction that can run on the processor, and has any metadata needed to do so diff --git a/src/cpu/instructions/load16.rs b/src/cpu/instructions/load16.rs index 1d63028..736fff4 100644 --- a/src/cpu/instructions/load16.rs +++ b/src/cpu/instructions/load16.rs @@ -28,6 +28,6 @@ pub enum SixteenBitLoadInstruction { }, LoadStackPointerToImmediateAddress { - dst_address: u16 - } + dst_address: u16, + }, } diff --git a/src/cpu/instructions/misc.rs b/src/cpu/instructions/misc.rs new file mode 100644 index 0000000..4815ac9 --- /dev/null +++ b/src/cpu/instructions/misc.rs @@ -0,0 +1,7 @@ +//! The `misc` module holds instructions that I couldn't think of a better category for + +#[derive(Debug, Clone, Copy)] +pub enum MiscInstruction { + SetCarryFlag, + ComplementCarryFlag, +} diff --git a/src/cpu/parse.rs b/src/cpu/parse.rs index 7367349..e3b4bc5 100644 --- a/src/cpu/parse.rs +++ b/src/cpu/parse.rs @@ -9,6 +9,7 @@ mod arith16; mod arith8; mod load16; mod load8; +mod misc; #[derive(Error, Debug, Clone)] pub enum Error { @@ -44,6 +45,7 @@ pub fn next_instruction(data: &View) -> ParseResult { load16::stack::Parser::parse_opcode, arith8::Parser::parse_opcode, arith16::Parser::parse_opcode, + misc::Parser::parse_opcode, ]; for parse_func in parse_funcs { diff --git a/src/cpu/parse/load16/stack.rs b/src/cpu/parse/load16/stack.rs index d580b1c..b4dbb5e 100644 --- a/src/cpu/parse/load16/stack.rs +++ b/src/cpu/parse/load16/stack.rs @@ -1,7 +1,7 @@ use crate::cpu::instructions::load16::SixteenBitLoadInstruction; use crate::cpu::instructions::{Instruction, RunnableInstruction}; use crate::cpu::parse::{self, Error, OpcodeParser, ParseOutput, ParseResult}; -use crate::memory::{View, GetViewTuple}; +use crate::memory::{GetViewTuple, View}; use crate::register; #[allow(clippy::module_name_repetitions)] @@ -17,9 +17,7 @@ impl OpcodeParser for Parser { fn parse_opcode(data: &View) -> ParseResult { let opcode = parse::get_opcode_from_data(data); match opcode { - 0x08 => Ok(make_load_sp_to_address_data( - data, - )), + 0x08 => Ok(make_load_sp_to_address_data(data)), 0xF5 => Ok(make_stack_operation_data( Operation::Push, register::Combined::AF, @@ -81,10 +79,10 @@ fn make_load_sp_to_address_data(data: &View) -> ParseOutput { ( RunnableInstruction { instruction: Instruction::SixteenBitLoad( - SixteenBitLoadInstruction::LoadStackPointerToImmediateAddress { dst_address } + SixteenBitLoadInstruction::LoadStackPointerToImmediateAddress { dst_address }, ), cycles: 20, }, - 3 + 3, ) } diff --git a/src/cpu/parse/misc.rs b/src/cpu/parse/misc.rs new file mode 100644 index 0000000..967d81a --- /dev/null +++ b/src/cpu/parse/misc.rs @@ -0,0 +1,38 @@ +use super::{OpcodeParser, ParseOutput, ParseResult}; +use crate::{ + cpu::instructions::{misc::MiscInstruction, Instruction, RunnableInstruction}, + memory::View, +}; + +pub struct Parser; + +impl OpcodeParser for Parser { + fn parse_opcode(data: &View) -> ParseResult { + let opcode = super::get_opcode_from_data(data); + match opcode { + 0x37 => Ok(build_set_carry_flag_data()), + 0x3F => Ok(build_complement_carry_flag_data()), + _ => Err(super::Error::UnknownOpcode(opcode)), + } + } +} + +fn build_set_carry_flag_data() -> ParseOutput { + ( + RunnableInstruction { + instruction: Instruction::Misc(MiscInstruction::SetCarryFlag), + cycles: 4, + }, + 1, + ) +} + +fn build_complement_carry_flag_data() -> ParseOutput { + ( + RunnableInstruction { + instruction: Instruction::Misc(MiscInstruction::ComplementCarryFlag), + cycles: 4, + }, + 1, + ) +} diff --git a/src/cpu/run.rs b/src/cpu/run.rs index 436c3ba..4e6983a 100644 --- a/src/cpu/run.rs +++ b/src/cpu/run.rs @@ -9,6 +9,7 @@ mod arith8; mod arithutil; mod load16; mod load8; +mod misc; /// `Error` represents a run error. Typically these will be fatal and panicking on their emission /// is likely the best course of action @@ -41,5 +42,6 @@ pub fn run_instruction(processor: &mut Processor, instruction: Instruction) -> R Instruction::EightBitArithmetic(arith_instruction) => arith_instruction.run_on(processor), Instruction::StackPointerAdjust(adjust_instruction) => adjust_instruction.run_on(processor), Instruction::SixteenBitArithmetic(arith_instruction) => arith_instruction.run_on(processor), + Instruction::Misc(instruction) => instruction.run_on(processor), } } diff --git a/src/cpu/run/load16.rs b/src/cpu/run/load16.rs index 777f50b..cf50a26 100644 --- a/src/cpu/run/load16.rs +++ b/src/cpu/run/load16.rs @@ -113,19 +113,17 @@ impl Run for SixteenBitLoadInstruction { ); Ok(()) - }, + } SixteenBitLoadInstruction::LoadStackPointerToImmediateAddress { dst_address } => { let current_sp = processor .registers .get_single_16bit_register(register::SingleSixteenBit::StackPointer); let [top_half, bottom_half] = current_sp.to_be_bytes(); - let memory_res = processor.memory.set_both( - ( - (usize::from(dst_address), bottom_half), - (usize::from(dst_address + 1), top_half), - ) - ); + let memory_res = processor.memory.set_both(( + (usize::from(dst_address), bottom_half), + (usize::from(dst_address + 1), top_half), + )); match memory_res { Ok(_) => Ok(()), diff --git a/src/cpu/run/load8.rs b/src/cpu/run/load8.rs index ba33bf2..b71898c 100644 --- a/src/cpu/run/load8.rs +++ b/src/cpu/run/load8.rs @@ -130,7 +130,7 @@ impl Run for EightBitLoadInstruction { } => { let src_address = memory::IO_REGISTER_START_ADDRESS + usize::from(offset); processor.load_from_address_to_register(dst, src_address) - }, + } } } } diff --git a/src/cpu/run/misc.rs b/src/cpu/run/misc.rs new file mode 100644 index 0000000..d4025ba --- /dev/null +++ b/src/cpu/run/misc.rs @@ -0,0 +1,32 @@ +use crate::cpu::{instructions::misc::MiscInstruction, register, run::Error, Processor}; + +use super::Run; + +impl Run for MiscInstruction { + fn run_on(&self, processor: &mut Processor) -> Result<(), Error> { + match *self { + MiscInstruction::SetCarryFlag => { + set_flags_in_carry_bit_instruction(processor, 1); + + Ok(()) + } + MiscInstruction::ComplementCarryFlag => { + let current_carry_flag = processor.registers.get_flag_bit(register::Flag::Carry); + let flipped = (current_carry_flag == 0).into(); + + set_flags_in_carry_bit_instruction(processor, flipped); + + Ok(()) + } + } + } +} + +fn set_flags_in_carry_bit_instruction(processor: &mut Processor, carry_flag: u8) { + processor + .registers + .set_flag_bit(register::Flag::Carry, carry_flag); + + processor.registers.set_flag_bit(register::Flag::HalfCarry, 0); + processor.registers.set_flag_bit(register::Flag::Subtract, 0); +} diff --git a/tests/cpu/jsmoo/testdata/disabled/37.json b/tests/cpu/jsmoo/testdata/37.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/37.json rename to tests/cpu/jsmoo/testdata/37.json diff --git a/tests/cpu/jsmoo/testdata/disabled/3f.json b/tests/cpu/jsmoo/testdata/3f.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/3f.json rename to tests/cpu/jsmoo/testdata/3f.json diff --git a/tests/cpu/main.rs b/tests/cpu/main.rs index 2be1b09..0e23c35 100644 --- a/tests/cpu/main.rs +++ b/tests/cpu/main.rs @@ -3,4 +3,5 @@ mod arith8; mod jsmoo; mod load16; mod load8; +mod misc; mod testutil; diff --git a/tests/cpu/misc.rs b/tests/cpu/misc.rs new file mode 100644 index 0000000..de1bd46 --- /dev/null +++ b/tests/cpu/misc.rs @@ -0,0 +1,68 @@ +use ferris_boi::{ + cpu::{instructions::RunnableInstruction, Processor}, + register, +}; +use test_case::test_case; +use crate::testutil; + +#[test_case(1)] +#[test_case(0)] +fn test_set_carry_flag_always_sets_to_1(starting_value: u8) { + let mut processor = Processor::default(); + processor + .registers + .set_flag_bit(register::Flag::Carry, starting_value); + + let data = [0x37, 0x03]; + let (ins, extra_data) = + RunnableInstruction::from_data(&data).expect("could not parse instruction"); + + assert_eq!(extra_data, &[0x03]); + processor.run_instruction(&ins); + + let new_carry_bit = processor.registers.get_flag_bit(register::Flag::Carry); + assert_eq!(1, new_carry_bit); +} + +#[test_case(1, 0)] +#[test_case(0, 1)] +fn test_complement_carry_bit(starting_value: u8, expected_value: u8) { + let mut processor = Processor::default(); + processor + .registers + .set_flag_bit(register::Flag::Carry, starting_value); + + let data = [0x3F, 0x03]; + let (ins, extra_data) = + RunnableInstruction::from_data(&data).expect("could not parse instruction"); + + assert_eq!(extra_data, &[0x03]); + processor.run_instruction(&ins); + + let new_carry_bit = processor.registers.get_flag_bit(register::Flag::Carry); + assert_eq!(expected_value, new_carry_bit); +} + +#[test_case(0x37)] +#[test_case(0x3F)] +fn test_all_carry_bit_instructions_adjust_flags(opcode: u8) { + let mut processor = Processor::default(); + testutil::set_opposite_of_expected_flags(&mut processor, (0, 0, 0, 1)); + + 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, + // Always zero + (register::Flag::HalfCarry, 0), + (register::Flag::Carry, 1), + (register::Flag::Subtract, 0), + // Value is preserved + (register::Flag::Zero, 1), + ); +}