Implement stack push and pop
parent
cfebc1b912
commit
84e680fd15
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
90
src/run.rs
90
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//! Holds functions to help produce 16 bit load instructions
|
||||
|
||||
pub mod immediate;
|
||||
pub mod stack;
|
||||
pub mod transfer;
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue