Add support for carry bit set instructions

old-bit-manip
Nick Krichevsky 2023-11-18 19:05:05 -05:00
parent 78458d6d31
commit 1bbd14e5d2
14 changed files with 164 additions and 16 deletions

View File

@ -8,6 +8,7 @@ pub mod arith16;
pub mod arith8; pub mod arith8;
pub mod load16; pub mod load16;
pub mod load8; pub mod load8;
pub mod misc;
// these are indexed with the instruction numbers in this manual // these are indexed with the instruction numbers in this manual
// http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf // http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf
@ -18,6 +19,7 @@ pub enum Instruction {
EightBitArithmetic(arith8::EightBitArithmeticInstruction), EightBitArithmetic(arith8::EightBitArithmeticInstruction),
StackPointerAdjust(arith8::AdjustStackPointerInstruction), StackPointerAdjust(arith8::AdjustStackPointerInstruction),
SixteenBitArithmetic(arith16::SixteenBitArithmeticInstruction), SixteenBitArithmetic(arith16::SixteenBitArithmeticInstruction),
Misc(misc::MiscInstruction),
} }
/// `RunnableInstruction` is an instruction that can run on the processor, and has any metadata needed to do so /// `RunnableInstruction` is an instruction that can run on the processor, and has any metadata needed to do so

View File

@ -28,6 +28,6 @@ pub enum SixteenBitLoadInstruction {
}, },
LoadStackPointerToImmediateAddress { LoadStackPointerToImmediateAddress {
dst_address: u16 dst_address: u16,
} },
} }

View File

@ -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,
}

View File

@ -9,6 +9,7 @@ mod arith16;
mod arith8; mod arith8;
mod load16; mod load16;
mod load8; mod load8;
mod misc;
#[derive(Error, Debug, Clone)] #[derive(Error, Debug, Clone)]
pub enum Error { pub enum Error {
@ -44,6 +45,7 @@ pub fn next_instruction(data: &View) -> ParseResult {
load16::stack::Parser::parse_opcode, load16::stack::Parser::parse_opcode,
arith8::Parser::parse_opcode, arith8::Parser::parse_opcode,
arith16::Parser::parse_opcode, arith16::Parser::parse_opcode,
misc::Parser::parse_opcode,
]; ];
for parse_func in parse_funcs { for parse_func in parse_funcs {

View File

@ -1,7 +1,7 @@
use crate::cpu::instructions::load16::SixteenBitLoadInstruction; use crate::cpu::instructions::load16::SixteenBitLoadInstruction;
use crate::cpu::instructions::{Instruction, RunnableInstruction}; use crate::cpu::instructions::{Instruction, RunnableInstruction};
use crate::cpu::parse::{self, Error, OpcodeParser, ParseOutput, ParseResult}; use crate::cpu::parse::{self, Error, OpcodeParser, ParseOutput, ParseResult};
use crate::memory::{View, GetViewTuple}; use crate::memory::{GetViewTuple, View};
use crate::register; use crate::register;
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
@ -17,9 +17,7 @@ impl OpcodeParser for Parser {
fn parse_opcode(data: &View) -> ParseResult { fn parse_opcode(data: &View) -> ParseResult {
let opcode = parse::get_opcode_from_data(data); let opcode = parse::get_opcode_from_data(data);
match opcode { match opcode {
0x08 => Ok(make_load_sp_to_address_data( 0x08 => Ok(make_load_sp_to_address_data(data)),
data,
)),
0xF5 => Ok(make_stack_operation_data( 0xF5 => Ok(make_stack_operation_data(
Operation::Push, Operation::Push,
register::Combined::AF, register::Combined::AF,
@ -81,10 +79,10 @@ fn make_load_sp_to_address_data(data: &View) -> ParseOutput {
( (
RunnableInstruction { RunnableInstruction {
instruction: Instruction::SixteenBitLoad( instruction: Instruction::SixteenBitLoad(
SixteenBitLoadInstruction::LoadStackPointerToImmediateAddress { dst_address } SixteenBitLoadInstruction::LoadStackPointerToImmediateAddress { dst_address },
), ),
cycles: 20, cycles: 20,
}, },
3 3,
) )
} }

38
src/cpu/parse/misc.rs Normal file
View File

@ -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,
)
}

View File

@ -9,6 +9,7 @@ mod arith8;
mod arithutil; mod arithutil;
mod load16; mod load16;
mod load8; mod load8;
mod misc;
/// `Error` represents a run error. Typically these will be fatal and panicking on their emission /// `Error` represents a run error. Typically these will be fatal and panicking on their emission
/// is likely the best course of action /// 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::EightBitArithmetic(arith_instruction) => arith_instruction.run_on(processor),
Instruction::StackPointerAdjust(adjust_instruction) => adjust_instruction.run_on(processor), Instruction::StackPointerAdjust(adjust_instruction) => adjust_instruction.run_on(processor),
Instruction::SixteenBitArithmetic(arith_instruction) => arith_instruction.run_on(processor), Instruction::SixteenBitArithmetic(arith_instruction) => arith_instruction.run_on(processor),
Instruction::Misc(instruction) => instruction.run_on(processor),
} }
} }

View File

@ -113,19 +113,17 @@ impl Run for SixteenBitLoadInstruction {
); );
Ok(()) Ok(())
}, }
SixteenBitLoadInstruction::LoadStackPointerToImmediateAddress { dst_address } => { SixteenBitLoadInstruction::LoadStackPointerToImmediateAddress { dst_address } => {
let current_sp = processor let current_sp = processor
.registers .registers
.get_single_16bit_register(register::SingleSixteenBit::StackPointer); .get_single_16bit_register(register::SingleSixteenBit::StackPointer);
let [top_half, bottom_half] = current_sp.to_be_bytes(); let [top_half, bottom_half] = current_sp.to_be_bytes();
let memory_res = processor.memory.set_both( let memory_res = processor.memory.set_both((
( (usize::from(dst_address), bottom_half),
(usize::from(dst_address), bottom_half), (usize::from(dst_address + 1), top_half),
(usize::from(dst_address + 1), top_half), ));
)
);
match memory_res { match memory_res {
Ok(_) => Ok(()), Ok(_) => Ok(()),

View File

@ -130,7 +130,7 @@ impl Run for EightBitLoadInstruction {
} => { } => {
let src_address = memory::IO_REGISTER_START_ADDRESS + usize::from(offset); let src_address = memory::IO_REGISTER_START_ADDRESS + usize::from(offset);
processor.load_from_address_to_register(dst, src_address) processor.load_from_address_to_register(dst, src_address)
}, }
} }
} }
} }

32
src/cpu/run/misc.rs Normal file
View File

@ -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);
}

View File

@ -3,4 +3,5 @@ mod arith8;
mod jsmoo; mod jsmoo;
mod load16; mod load16;
mod load8; mod load8;
mod misc;
mod testutil; mod testutil;

68
tests/cpu/misc.rs Normal file
View File

@ -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),
);
}