Implement stack push and pop

jsmoo
Nick Krichevsky 2022-04-11 22:54:55 -04:00
parent cfebc1b912
commit 84e680fd15
7 changed files with 191 additions and 1 deletions

View File

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

View File

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

View File

@ -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 {

View File

@ -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 {

View File

@ -1,4 +1,5 @@
//! Holds functions to help produce 16 bit load instructions
pub mod immediate;
pub mod stack;
pub mod transfer;

View File

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

View File

@ -353,7 +353,6 @@ fn make_load_then_do_arithmetic<F: Fn() -> Instruction>(make: F, data: &[u8]) ->
instruction: make(),
cycles: 8,
},
// guaranteed to succeed given we found the opcode
remaining_data,
)
})