From 84e680fd15b0ac4da3386fb44f68a6bc019be08e Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Mon, 11 Apr 2022 22:54:55 -0400 Subject: [PATCH] Implement stack push and pop --- src/memory.rs | 38 +++++++++++++++ src/run.rs | 90 +++++++++++++++++++++++++++++++++++ src/run/instructions.rs | 7 +++ src/run/parse.rs | 1 + src/run/parse/load16.rs | 1 + src/run/parse/load16/stack.rs | 54 +++++++++++++++++++++ src/run/parse/load8/memory.rs | 1 - 7 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 src/run/parse/load16/stack.rs diff --git a/src/memory.rs b/src/memory.rs index f7b7cc2..f343d44 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -26,6 +26,20 @@ impl Memory { Some(*val_ref) } + + pub fn set_both( + &mut self, + (index_value1, index_value2): ((usize, u8), (usize, u8)), + ) -> Option<(u8, u8)> { + if index_value1.0 >= self.data.len() || index_value2.0 >= self.data.len() { + return None; + } + + self.data[index_value1.0] = index_value1.1; + self.data[index_value2.0] = index_value2.1; + + Some((index_value1.1, index_value2.1)) + } } impl Default for Memory { @@ -63,4 +77,28 @@ mod tests { let set_val = memory.set(0xCC_CC_CC_CC_CC, 100); assert_eq!(None, set_val); } + + #[test] + fn test_set_many_all_succeed() { + let mut memory = Memory::new(); + let res = memory.set_both(((0xCC, 10), (0xDD, 11))); + assert!(res.is_some()); + + assert_eq!(Some(10), memory.get(0xCC)); + assert_eq!(Some(11), memory.get(0xDD)); + } + + #[test] + fn test_set_many_one_failure_fails_everything() { + let mut memory = Memory::new(); + let res = memory.set(0xCC, 10); + assert!(res.is_some()); + + // We can't set past the end of memory, obviously. + let res = memory.set_both(((0xCC, 20), (MAX_MEMORY_ADDRESS + 100, 50))); + assert!(res.is_none()); + + // Nothing should be written + assert_eq!(Some(10), memory.get(0xCC)); + } } diff --git a/src/run.rs b/src/run.rs index 78f433f..9b903dd 100644 --- a/src/run.rs +++ b/src/run.rs @@ -198,6 +198,53 @@ impl Processor { self.registers.set_combined_register(dst, new_sp); } + + Instruction::Push { src } => { + let current_sp = self + .registers + .get_16bit_register(register::SixteenBit::Single( + register::SingleSixteenBit::StackPointer, + )); + + // we want to pop the LSB first (i.e. we write the MSB first) + // https://rgbds.gbdev.io/docs/v0.5.2/gbz80.7#PUSH_r16 + let [lower_bits, higher_bits] = + self.registers.get_combined_register(src).to_le_bytes(); + self.memory.set_both(( + ((current_sp - 1).into(), higher_bits), + ((current_sp - 2).into(), lower_bits), + )); + + self.registers.set_16bit_register( + register::SixteenBit::Single(register::SingleSixteenBit::StackPointer), + current_sp - 2, + ); + } + + Instruction::Pop { dst } => { + let current_sp = self + .registers + .get_16bit_register(register::SixteenBit::Single( + register::SingleSixteenBit::StackPointer, + )); + + let popped_bytes = [ + self.memory + .get(current_sp.into()) + .expect("stack pointer pointed to invalid address"), + self.memory + .get((current_sp + 1).into()) + .expect("stack pointer pointed to invalid address"), + ]; + + let popped_value = u16::from_le_bytes(popped_bytes); + + self.registers.set_combined_register(dst, popped_value); + self.registers.set_16bit_register( + register::SixteenBit::Single(register::SingleSixteenBit::StackPointer), + current_sp + 2, + ); + } } self.num_cycles += u64::from(instruction.cycles); @@ -771,4 +818,47 @@ mod tests { "incorrect subtract bit" ); } + + #[test_case(0xF5, register::Combined::AF)] + #[test_case(0xC5, register::Combined::BC)] + #[test_case(0xD5, register::Combined::DE)] + #[test_case(0xE5, register::Combined::HL)] + fn test_stack_push(opcode: u8, src: register::Combined) { + let mut processor = Processor::default(); + processor.registers.set_combined_register(src, 0x1234); + processor.registers.stack_pointer = 0xFFFE; + let data = [opcode, 0x01]; + + let (ins, extra_data) = + RunnableInstruction::from_data(&data).expect("could not parse instruction"); + + assert_eq!(extra_data, &[0x01]); + processor.run(&ins); + + // we want to pop the LSB first (i.e. we write the MSB first) + // https://rgbds.gbdev.io/docs/v0.5.2/gbz80.7#PUSH_r16 + assert_eq!(Some(0x12), processor.memory.get(0xFFFE - 1)); + assert_eq!(Some(0x34), processor.memory.get(0xFFFE - 2)); + assert_eq!(0xFFFE - 2, processor.registers.stack_pointer); + } + + #[test_case(0xF1, register::Combined::AF)] + #[test_case(0xC1, register::Combined::BC)] + #[test_case(0xD1, register::Combined::DE)] + #[test_case(0xE1, register::Combined::HL)] + fn test_stack_pop(opcode: u8, dst: register::Combined) { + let mut processor = Processor::default(); + processor.registers.stack_pointer = 0xFFF0; + processor.memory.set_both(((0xFFF0, 0x34), (0xFFF1, 0x12))); + let data = [opcode, 0x01]; + + let (ins, extra_data) = + RunnableInstruction::from_data(&data).expect("could not parse instruction"); + + assert_eq!(extra_data, &[0x01]); + processor.run(&ins); + + assert_eq!(0x1234, processor.registers.get_combined_register(dst)); + assert_eq!(0xFFF0 + 2, processor.registers.stack_pointer); + } } diff --git a/src/run/instructions.rs b/src/run/instructions.rs index ade1ee8..9e34374 100644 --- a/src/run/instructions.rs +++ b/src/run/instructions.rs @@ -108,6 +108,13 @@ pub enum Instruction { dst: register::Combined, offset: i8, }, + // 3.3.3.5 + Push { + src: register::Combined, + }, + Pop { + dst: register::Combined, + }, } pub struct RunnableInstruction { diff --git a/src/run/parse.rs b/src/run/parse.rs index 94a1362..0070d7e 100644 --- a/src/run/parse.rs +++ b/src/run/parse.rs @@ -34,6 +34,7 @@ pub fn next_instruction(data: &[u8]) -> ParseResult { load8::memory::Memory8BitLoadParser::parse_opcode, load16::immediate::Immediate16BitLoadParser::parse_opcode, load16::transfer::Between16BitRegisterParser::parse_opcode, + load16::stack::StackLoadParser::parse_opcode, ]; for parse_func in parse_funcs { diff --git a/src/run/parse/load16.rs b/src/run/parse/load16.rs index a69346b..80614fe 100644 --- a/src/run/parse/load16.rs +++ b/src/run/parse/load16.rs @@ -1,4 +1,5 @@ //! Holds functions to help produce 16 bit load instructions pub mod immediate; +pub mod stack; pub mod transfer; diff --git a/src/run/parse/load16/stack.rs b/src/run/parse/load16/stack.rs new file mode 100644 index 0000000..660c795 --- /dev/null +++ b/src/run/parse/load16/stack.rs @@ -0,0 +1,54 @@ +use crate::register; +use crate::run::instructions::{Instruction, RunnableInstruction}; +use crate::run::parse::{self, Error, OpcodeParser, ParseResult}; + +#[allow(clippy::module_name_repetitions)] +pub struct StackLoadParser; + +#[derive(Debug, Clone, Copy)] +enum Operation { + Push, + Pop, +} + +impl OpcodeParser for StackLoadParser { + fn parse_opcode(data: &[u8]) -> ParseResult { + let opcode = parse::get_opcode_from_data(data)?; + match opcode { + 0xF5 => make_stack_operation_data(Operation::Push, register::Combined::AF, data), + 0xC5 => make_stack_operation_data(Operation::Push, register::Combined::BC, data), + 0xD5 => make_stack_operation_data(Operation::Push, register::Combined::DE, data), + 0xE5 => make_stack_operation_data(Operation::Push, register::Combined::HL, data), + + 0xF1 => make_stack_operation_data(Operation::Pop, register::Combined::AF, data), + 0xC1 => make_stack_operation_data(Operation::Pop, register::Combined::BC, data), + 0xD1 => make_stack_operation_data(Operation::Pop, register::Combined::DE, data), + 0xE1 => make_stack_operation_data(Operation::Pop, register::Combined::HL, data), + + _ => Err(Error::UnknownOpcode(opcode)), + } + } +} + +fn make_stack_operation_data( + operation: Operation, + reg: register::Combined, + data: &[u8], +) -> ParseResult { + data.get(1..) + .map(|remaining_data| { + let instruction = match operation { + Operation::Push => RunnableInstruction { + instruction: Instruction::Push { src: reg }, + cycles: 16, + }, + Operation::Pop => RunnableInstruction { + instruction: Instruction::Pop { dst: reg }, + cycles: 12, + }, + }; + + (instruction, remaining_data) + }) + .ok_or(Error::NoData) +} diff --git a/src/run/parse/load8/memory.rs b/src/run/parse/load8/memory.rs index 5bca37c..4ecf7d7 100644 --- a/src/run/parse/load8/memory.rs +++ b/src/run/parse/load8/memory.rs @@ -353,7 +353,6 @@ fn make_load_then_do_arithmetic Instruction>(make: F, data: &[u8]) -> instruction: make(), cycles: 8, }, - // guaranteed to succeed given we found the opcode remaining_data, ) })