diff --git a/src/cpu/instructions.rs b/src/cpu/instructions.rs index e2d321d..7c638cb 100644 --- a/src/cpu/instructions.rs +++ b/src/cpu/instructions.rs @@ -16,6 +16,7 @@ pub enum Instruction { EightBitLoad(load8::EightBitLoadInstruction), SixteenBitLoad(load16::SixteenBitLoadInstruction), EightBitArithmetic(arith8::EightBitArithmeticInstruction), + StackPointerAdjust(arith8::AdjustStackPointerInstruction), SixteenBitArithmetic(arith16::SixteenBitArithmeticInstruction), } diff --git a/src/cpu/instructions/arith8.rs b/src/cpu/instructions/arith8.rs index ca0e245..9ff6820 100644 --- a/src/cpu/instructions/arith8.rs +++ b/src/cpu/instructions/arith8.rs @@ -28,3 +28,8 @@ pub struct EightBitArithmeticInstruction { pub operation: Operation, pub operand: Operand, } + +#[derive(Debug, Copy, Clone)] +pub struct AdjustStackPointerInstruction { + pub operand: i8, +} diff --git a/src/cpu/parse/arith8.rs b/src/cpu/parse/arith8.rs index 7a7f096..8ac0e1a 100644 --- a/src/cpu/parse/arith8.rs +++ b/src/cpu/parse/arith8.rs @@ -1,6 +1,8 @@ use crate::{ cpu::instructions::{ - arith8::{EightBitArithmeticInstruction, Operand, Operation}, + arith8::{ + AdjustStackPointerInstruction, EightBitArithmeticInstruction, Operand, Operation, + }, Instruction, RunnableInstruction, }, memory::{GetViewTuple, View}, @@ -22,28 +24,51 @@ pub struct Parser; impl OpcodeParser for Parser { fn parse_opcode(data: &View) -> super::ParseResult { - let opcode = super::get_opcode_from_data(data); - let operation = operation_for_opcode(opcode)?; - let operand = operand_for_opcode(opcode)?; + parse_eight_bit_arithmetic_instruction(data) + .or_else(|_err| parse_stack_pointer_adjust_instruction(data)) + } +} - let (instruction, bytes_read) = match (operation, operand) { - (_operation, OpcodeOperand::SingleRegister(register)) => { - build_instruction_between_register_and_a_data(operation, register) - } - (Operation::Inc | Operation::Dec, OpcodeOperand::HLAddressValue) => { - let (runnable_ins, bytes_read) = build_instruction_between_hl_value_and_a_data(operation); - // This is an exception to the other arithmetic instructions - (RunnableInstruction{cycles: 12, ..runnable_ins}, bytes_read) - } - (_operation, OpcodeOperand::HLAddressValue) => { - build_instruction_between_hl_value_and_a_data(operation) - } - (_operation, OpcodeOperand::Immediate) => { - build_instruction_between_immediate_and_a_data(operation, data) - } - }; +fn parse_eight_bit_arithmetic_instruction(data: &View) -> super::ParseResult { + let opcode = super::get_opcode_from_data(data); - Ok((instruction, bytes_read)) + let operation = operation_for_opcode(opcode)?; + let operand = operand_for_opcode(opcode)?; + + let parse_data = match (operation, operand) { + (_operation, OpcodeOperand::SingleRegister(register)) => { + build_instruction_between_register_and_a_data(operation, register) + } + (Operation::Inc | Operation::Dec, OpcodeOperand::HLAddressValue) => { + let (runnable_ins, bytes_read) = + build_instruction_between_hl_value_and_a_data(operation); + // This is an exception to the other arithmetic instructions + ( + RunnableInstruction { + cycles: 12, + ..runnable_ins + }, + bytes_read, + ) + } + (_operation, OpcodeOperand::HLAddressValue) => { + build_instruction_between_hl_value_and_a_data(operation) + } + (_operation, OpcodeOperand::Immediate) => { + build_instruction_between_immediate_and_a_data(operation, data) + } + }; + + Ok(parse_data) +} + +fn parse_stack_pointer_adjust_instruction(data: &View) -> super::ParseResult { + let opcode = super::get_opcode_from_data(data); + + if opcode == 0xE8 { + Ok(build_stack_pointer_adjust_data(data)) + } else { + Err(super::Error::UnknownOpcode(opcode)) } } @@ -176,6 +201,21 @@ fn build_instruction_between_immediate_and_a_data( ) } +fn build_stack_pointer_adjust_data(data: &View) -> ParseOutput { + let (_opcode, n) = data.get_tuple(); + let instruction = AdjustStackPointerInstruction { + operand: i8::from_be_bytes([n]), + }; + + ( + RunnableInstruction { + instruction: Instruction::StackPointerAdjust(instruction), + cycles: 16, + }, + 2, + ) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/cpu/run.rs b/src/cpu/run.rs index 8232de0..436c3ba 100644 --- a/src/cpu/run.rs +++ b/src/cpu/run.rs @@ -39,6 +39,7 @@ pub fn run_instruction(processor: &mut Processor, instruction: Instruction) -> R Instruction::EightBitLoad(load_instruction) => load_instruction.run_on(processor), Instruction::SixteenBitLoad(load_instruction) => load_instruction.run_on(processor), 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), } } diff --git a/src/cpu/run/arith8.rs b/src/cpu/run/arith8.rs index 2a3f2f2..c07cdf5 100644 --- a/src/cpu/run/arith8.rs +++ b/src/cpu/run/arith8.rs @@ -1,10 +1,13 @@ use crate::{ cpu::{instructions::arith8::EightBitArithmeticInstruction, run::Error}, - cpu::{instructions::arith8::Operation, Processor}, + cpu::{ + instructions::arith8::{AdjustStackPointerInstruction, Operation}, + Processor, + }, register, }; -use super::Run; +use super::{arithutil::CarryingAdd, Run}; mod binary; mod unary; @@ -48,6 +51,31 @@ impl Run for EightBitArithmeticInstruction { } } +impl Run for AdjustStackPointerInstruction { + fn run_on(&self, processor: &mut Processor) -> Result<(), Error> { + let stack_pointer = processor + .registers + .get_single_16bit_register(register::SingleSixteenBit::StackPointer); + + let (result, half_carry, full_carry) = stack_pointer.add_with_carry(self.operand); + processor + .registers + .set_single_16bit_register(register::SingleSixteenBit::StackPointer, result); + + store_flags( + processor, + OperationFlagOutput { + zero_flag: 0, + subtract_flag: 0, + half_carry_flag: half_carry.into(), + carry_flag: full_carry.into(), + }, + ); + + Ok(()) + } +} + fn store_flags(processor: &mut Processor, flags: OperationFlagOutput) { processor .registers diff --git a/tests/cpu/arith8.rs b/tests/cpu/arith8.rs index c675a59..ede7075 100644 --- a/tests/cpu/arith8.rs +++ b/tests/cpu/arith8.rs @@ -2260,3 +2260,55 @@ fn test_increment_decrement_hl_flags( (register::Flag::Carry, 1), ); } + +#[test_case(0xFF00, 0x22, 0xFF22)] +#[test_case(0xFF00, -0x22, 0xFEDE)] +#[test_case(0x0000, -0x22, 0xFFDE)] +#[test_case(0xFFFF, 0x22, 0x0021)] +fn test_adjust_stack_pointer_value(initial_sp: u16, adjustment: i8, expected_sp: u16) { + let mut processor = Processor::default(); + processor + .registers + .set_single_16bit_register(register::SingleSixteenBit::StackPointer, initial_sp); + + let twos_comp_adjustment = adjustment.to_be_bytes()[0]; + let data = [0xE8, twos_comp_adjustment, 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_sp, processor.registers.stack_pointer); +} + +#[test_case(0xFF00, 0x22, false, false)] +#[test_case(0x04F0, -0x22, false, true; "subtraction full carry")] +#[test_case(0x0022, -0x22, true, true; "even as zero, zero flags are unset")] +fn test_adjust_stack_pointer(initial_sp: u16, adjustment: i8, half_carry: bool, carry: bool) { + let mut processor = Processor::default(); + processor + .registers + .set_single_16bit_register(register::SingleSixteenBit::StackPointer, initial_sp); + + // 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, half_carry.into(), carry.into()), + ); + + let twos_comp_adjustment = adjustment.to_be_bytes()[0]; + let data = [0xE8, twos_comp_adjustment, 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::HalfCarry, u8::from(half_carry)), + (register::Flag::Carry, u8::from(carry)), + // Both are always zero + (register::Flag::Subtract, 0), + (register::Flag::Zero, 0), + ); +} diff --git a/tests/cpu/jsmoo/testdata/disabled/e8.json b/tests/cpu/jsmoo/testdata/e8.json similarity index 100% rename from tests/cpu/jsmoo/testdata/disabled/e8.json rename to tests/cpu/jsmoo/testdata/e8.json