Break out instruction running code into its own module
parent
54cb2b8b2c
commit
16385f6387
|
@ -1,18 +1,14 @@
|
|||
use instructions::{Instruction, RunnableInstruction};
|
||||
use instructions::RunnableInstruction;
|
||||
|
||||
use crate::{
|
||||
memory::{self, Memory},
|
||||
memory::Memory,
|
||||
register::{self, Registers},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
use self::instructions::{
|
||||
arith8::EightBitArithmeticInstruction, load16::SixteenBitLoadInstruction,
|
||||
load8::EightBitLoadInstruction,
|
||||
};
|
||||
|
||||
mod instructions;
|
||||
mod parse;
|
||||
mod run;
|
||||
|
||||
/// `Error` represents a run error. Typically these will be fatal and panicking on their emission
|
||||
/// is likely the best course of action
|
||||
|
@ -33,275 +29,12 @@ pub struct Processor {
|
|||
memory: Memory,
|
||||
}
|
||||
|
||||
macro_rules! assert_ok {
|
||||
($result: expr) => {
|
||||
let res: Result<_, _> = $result;
|
||||
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||
};
|
||||
}
|
||||
|
||||
impl Processor {
|
||||
fn run(&mut self, instruction: &RunnableInstruction) {
|
||||
match &instruction.instruction {
|
||||
Instruction::EightBitLoad(load_instruction) => self.run_8_bit_load(load_instruction),
|
||||
Instruction::SixteenBitLoad(load_instruction) => self.run_16_bit_load(load_instruction),
|
||||
Instruction::EightBitArithmetic(arith_instruction) => {
|
||||
self.run_8_bit_arithmetic(arith_instruction);
|
||||
}
|
||||
}
|
||||
|
||||
run::run_instruction(self, &instruction.instruction);
|
||||
self.num_cycles += u64::from(instruction.cycles);
|
||||
}
|
||||
|
||||
fn run_8_bit_load(&mut self, instruction: &EightBitLoadInstruction) {
|
||||
match *instruction {
|
||||
EightBitLoadInstruction::LoadImmediateToRegister { value, register } => {
|
||||
self.registers.set_single_8bit_register(register, value);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadBetweenRegisters { dst, src } => {
|
||||
let src_value = self.registers.get_single_8bit_register(src);
|
||||
self.registers.set_single_8bit_register(dst, src_value);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadFromRegisterAddress { src, dst } => {
|
||||
let load_res = self.load_from_register_address_to_register(dst, src);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadFromImmediateAddress { src_address, dst } => {
|
||||
let load_res = self.load_from_address_to_register(dst, src_address.into());
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadToRegisterAddress { src, dst } => {
|
||||
let load_res = self.load_from_register_to_register_address(dst, src);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadToImmediateAddress { src, dst_address } => {
|
||||
let load_res = self.load_from_register_to_address(dst_address.into(), src);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadnToHLAddress { value } => {
|
||||
let dest_address = self.registers.get_combined_register(register::Combined::HL);
|
||||
let load_res = self.load_8bit_immediate_to_address(dest_address.into(), value);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadFromMemoryRelativeToIORegisterStart {
|
||||
offset_register,
|
||||
dst,
|
||||
} => {
|
||||
let src_address_offset = self.registers.get_single_8bit_register(offset_register);
|
||||
let src_address =
|
||||
memory::IO_REGISTER_START_ADDRESS + usize::from(src_address_offset);
|
||||
let load_res = self.load_from_address_to_register(dst, src_address);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadToMemoryRelativeToIORegisterStart {
|
||||
src,
|
||||
offset_register,
|
||||
} => {
|
||||
let dst_address_offset = self.registers.get_single_8bit_register(offset_register);
|
||||
let dst_address =
|
||||
memory::IO_REGISTER_START_ADDRESS + usize::from(dst_address_offset);
|
||||
let load_res = self.load_from_register_to_address(dst_address, src);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadFromRegisterAddressThenDec { dst, src } => {
|
||||
let load_res = self.load_from_register_address_to_register(dst, src);
|
||||
assert_ok!(load_res);
|
||||
|
||||
let src_address = self.registers.get_combined_register(src);
|
||||
self.registers.set_combined_register(src, src_address - 1);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadFromRegisterAddressThenInc { dst, src } => {
|
||||
let load_res = self.load_from_register_address_to_register(dst, src);
|
||||
assert_ok!(load_res);
|
||||
|
||||
let src_address = self.registers.get_combined_register(src);
|
||||
self.registers.set_combined_register(src, src_address + 1);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadToRegisterAddressThenDec { dst, src } => {
|
||||
let dst_address = self.registers.get_combined_register(dst);
|
||||
let load_res = self.load_from_register_to_address(dst_address.into(), src);
|
||||
assert_ok!(load_res);
|
||||
|
||||
self.registers.set_combined_register(dst, dst_address - 1);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadToRegisterAddressThenInc { dst, src } => {
|
||||
let dst_address = self.registers.get_combined_register(dst);
|
||||
let load_res = self.load_from_register_to_address(dst_address.into(), src);
|
||||
assert_ok!(load_res);
|
||||
|
||||
self.registers.set_combined_register(dst, dst_address + 1);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadToMemoryRelativeToIORegisterStartByImmediate {
|
||||
src,
|
||||
offset,
|
||||
} => {
|
||||
let dst_address = memory::IO_REGISTER_START_ADDRESS + usize::from(offset);
|
||||
let load_res = self.load_from_register_to_address(dst_address, src);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadFromMemoryRelativeToIORegisterStartByImmediate {
|
||||
dst,
|
||||
offset,
|
||||
} => {
|
||||
let src_address = memory::IO_REGISTER_START_ADDRESS + usize::from(offset);
|
||||
let load_res = self.load_from_address_to_register(dst, src_address);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_16_bit_load(&mut self, instruction: &SixteenBitLoadInstruction) {
|
||||
match *instruction {
|
||||
SixteenBitLoadInstruction::LoadImmediateToRegister { dst, value } => {
|
||||
self.registers.set_16bit_register(dst, value);
|
||||
}
|
||||
|
||||
SixteenBitLoadInstruction::LoadBetweenRegisters { dst, src } => {
|
||||
let value = self.registers.get_16bit_register(src);
|
||||
self.registers.set_16bit_register(dst, value);
|
||||
}
|
||||
|
||||
SixteenBitLoadInstruction::LoadEffectiveAddress { dst, offset } => {
|
||||
let current_sp = self
|
||||
.registers
|
||||
.get_single_16bit_register(register::SingleSixteenBit::StackPointer);
|
||||
|
||||
// TODO: This is gross. I'll clean this up when I do more ALU instructions
|
||||
// because there's bound to be reuse
|
||||
let (sixteen_bit_offset, new_sp, carry) = if offset >= 0 {
|
||||
let sixteen_bit_offset = u16::try_from(offset)
|
||||
.expect("failed to put positive (or zero) 8 bit value into unsigned sixteen bit, which should always work...");
|
||||
let new_sp = current_sp + sixteen_bit_offset;
|
||||
// If the lower eight bits are lower than where we started (with a known positive number)
|
||||
// we must have carried.
|
||||
let carry =
|
||||
(((current_sp & 0xff) + (sixteen_bit_offset & 0xff)) & 0x100) == 0x100;
|
||||
|
||||
(sixteen_bit_offset, new_sp, carry)
|
||||
} else {
|
||||
let sixteen_bit_offset = u16::try_from(i16::from(offset).abs())
|
||||
.expect("failed to convert an abs'd 16 bit value to a u16");
|
||||
let (new_sp, underflow) = current_sp.overflowing_sub(sixteen_bit_offset);
|
||||
|
||||
(sixteen_bit_offset, new_sp, underflow)
|
||||
};
|
||||
|
||||
let carry_bit = if carry { 1 } else { 0 };
|
||||
let half_carry_bit =
|
||||
// https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
|
||||
if ((current_sp & 0xf) + (sixteen_bit_offset & 0xf)) & 0x10 == 0x10 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
self.registers
|
||||
.set_flag_bit(register::Flag::Carry, carry_bit);
|
||||
self.registers
|
||||
.set_flag_bit(register::Flag::HalfCarry, half_carry_bit);
|
||||
|
||||
// Manual says we reset these here.
|
||||
self.registers.set_flag_bit(register::Flag::Subtract, 0);
|
||||
self.registers.set_flag_bit(register::Flag::Zero, 0);
|
||||
|
||||
self.registers.set_combined_register(dst, new_sp);
|
||||
}
|
||||
|
||||
SixteenBitLoadInstruction::Push { src } => {
|
||||
let current_sp = self
|
||||
.registers
|
||||
.get_single_16bit_register(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,
|
||||
);
|
||||
}
|
||||
|
||||
SixteenBitLoadInstruction::Pop { dst } => {
|
||||
let current_sp = self
|
||||
.registers
|
||||
.get_single_16bit_register(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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_8_bit_arithmetic(&mut self, instruction: &EightBitArithmeticInstruction) {
|
||||
match *instruction {
|
||||
EightBitArithmeticInstruction::AddSingleRegisterToA { src } => {
|
||||
let a_value = self
|
||||
.registers
|
||||
.get_single_8bit_register(register::SingleEightBit::A);
|
||||
let src_value = self.registers.get_single_8bit_register(src);
|
||||
let (added_value, carry) = a_value.overflowing_add(src_value);
|
||||
|
||||
self.registers
|
||||
.set_single_8bit_register(register::SingleEightBit::A, added_value);
|
||||
|
||||
self.registers.set_flag_bit(
|
||||
register::Flag::Zero,
|
||||
// carry must be false, as if it's true, the value is indeed > 0
|
||||
if added_value == 0 && !carry { 1 } else { 0 },
|
||||
);
|
||||
|
||||
// TODO: This is copy/paste from the stack add above; break this out
|
||||
let half_carry_bit =
|
||||
// https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
|
||||
if ((a_value & 0xf) + (src_value & 0xf)) & 0x10 == 0x10 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
self.registers
|
||||
.set_flag_bit(register::Flag::HalfCarry, half_carry_bit);
|
||||
|
||||
let full_carry_bit = if carry { 1 } else { 0 };
|
||||
self.registers
|
||||
.set_flag_bit(register::Flag::Carry, full_carry_bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_from_register_to_register_address(
|
||||
&mut self,
|
||||
register_with_dst_address: register::Combined,
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
register,
|
||||
run::{
|
||||
cpu::{
|
||||
instructions::{arith8::EightBitArithmeticInstruction, Instruction, RunnableInstruction},
|
||||
parse::{self, Error, OpcodeParser, ParseResult},
|
||||
},
|
|
@ -1,6 +1,6 @@
|
|||
use crate::register;
|
||||
use crate::run::instructions::load16::SixteenBitLoadInstruction;
|
||||
use crate::run::{
|
||||
use crate::cpu::instructions::load16::SixteenBitLoadInstruction;
|
||||
use crate::cpu::{
|
||||
instructions::{Instruction, RunnableInstruction},
|
||||
parse::{self, Error, OpcodeParser, ParseResult},
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
use crate::register;
|
||||
use crate::run::instructions::load16::SixteenBitLoadInstruction;
|
||||
use crate::run::instructions::{Instruction, RunnableInstruction};
|
||||
use crate::run::parse::{self, Error, OpcodeParser, ParseResult};
|
||||
use crate::cpu::instructions::load16::SixteenBitLoadInstruction;
|
||||
use crate::cpu::instructions::{Instruction, RunnableInstruction};
|
||||
use crate::cpu::parse::{self, Error, OpcodeParser, ParseResult};
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct StackLoadParser;
|
|
@ -1,7 +1,7 @@
|
|||
use crate::register;
|
||||
use crate::run::instructions::load16::SixteenBitLoadInstruction;
|
||||
use crate::run::instructions::Instruction;
|
||||
use crate::run::{
|
||||
use crate::cpu::instructions::load16::SixteenBitLoadInstruction;
|
||||
use crate::cpu::instructions::Instruction;
|
||||
use crate::cpu::{
|
||||
instructions::RunnableInstruction,
|
||||
parse::{self, Error, OpcodeParser, ParseResult},
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
use crate::register;
|
||||
use crate::run::instructions::load8::EightBitLoadInstruction;
|
||||
use crate::run::instructions::Instruction;
|
||||
use crate::run::{
|
||||
use crate::cpu::instructions::load8::EightBitLoadInstruction;
|
||||
use crate::cpu::instructions::Instruction;
|
||||
use crate::cpu::{
|
||||
instructions::RunnableInstruction,
|
||||
parse::{self, Error, OpcodeParser, ParseResult},
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
use crate::register;
|
||||
use crate::run::instructions::load8::EightBitLoadInstruction;
|
||||
use crate::run::instructions::{Instruction, RunnableInstruction};
|
||||
use crate::run::parse::{self, Error, OpcodeParser, ParseResult};
|
||||
use crate::cpu::instructions::load8::EightBitLoadInstruction;
|
||||
use crate::cpu::instructions::{Instruction, RunnableInstruction};
|
||||
use crate::cpu::parse::{self, Error, OpcodeParser, ParseResult};
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct Memory8BitLoadParser;
|
|
@ -1,7 +1,7 @@
|
|||
use crate::register;
|
||||
use crate::run::instructions::load8::EightBitLoadInstruction;
|
||||
use crate::run::instructions::Instruction;
|
||||
use crate::run::{
|
||||
use crate::cpu::instructions::load8::EightBitLoadInstruction;
|
||||
use crate::cpu::instructions::Instruction;
|
||||
use crate::cpu::{
|
||||
instructions::RunnableInstruction,
|
||||
parse::{self, Error, OpcodeParser, ParseResult},
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
use super::{instructions::Instruction, Processor};
|
||||
|
||||
mod arith8;
|
||||
mod load16;
|
||||
mod load8;
|
||||
mod util;
|
||||
|
||||
/// `InstructionRunner` takes a single instruction and runs it on the given processor.
|
||||
trait InstructionRunner<I> {
|
||||
// TODO: Make this return a result instead of impls panicking like they do now.
|
||||
fn run_instruction(processor: &mut Processor, instruction: &I);
|
||||
}
|
||||
|
||||
/// `run_instruction` will run the given instruction on the processor.
|
||||
// TODO: Right now this panics, but this should return a result of some kind.
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub fn run_instruction(processor: &mut Processor, instruction: &Instruction) {
|
||||
match instruction {
|
||||
Instruction::EightBitLoad(load_instruction) => {
|
||||
load8::EightBitLoadRunner::run_instruction(processor, load_instruction);
|
||||
}
|
||||
Instruction::SixteenBitLoad(load_instruction) => {
|
||||
load16::SixteenBitLoadRunner::run_instruction(processor, load_instruction);
|
||||
}
|
||||
Instruction::EightBitArithmetic(arith_instruction) => {
|
||||
arith8::EightBitArithmeticRunner::run_instruction(processor, arith_instruction);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
use crate::cpu::{instructions::arith8::EightBitArithmeticInstruction, Processor};
|
||||
use crate::register;
|
||||
|
||||
use super::InstructionRunner;
|
||||
|
||||
pub(super) struct EightBitArithmeticRunner;
|
||||
|
||||
impl InstructionRunner<EightBitArithmeticInstruction> for EightBitArithmeticRunner {
|
||||
fn run_instruction(processor: &mut Processor, instruction: &EightBitArithmeticInstruction) {
|
||||
match *instruction {
|
||||
EightBitArithmeticInstruction::AddSingleRegisterToA { src } => {
|
||||
let a_value = processor
|
||||
.registers
|
||||
.get_single_8bit_register(register::SingleEightBit::A);
|
||||
let src_value = processor.registers.get_single_8bit_register(src);
|
||||
let (added_value, carry) = a_value.overflowing_add(src_value);
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_single_8bit_register(register::SingleEightBit::A, added_value);
|
||||
|
||||
processor.registers.set_flag_bit(
|
||||
register::Flag::Zero,
|
||||
// carry must be false, as if it's true, the value is indeed > 0
|
||||
if added_value == 0 && !carry { 1 } else { 0 },
|
||||
);
|
||||
|
||||
// TODO: This is copy/paste from the stack add above; break this out
|
||||
let half_carry_bit =
|
||||
// https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
|
||||
if ((a_value & 0xf) + (src_value & 0xf)) & 0x10 == 0x10 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::HalfCarry, half_carry_bit);
|
||||
|
||||
let full_carry_bit = if carry { 1 } else { 0 };
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::Carry, full_carry_bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
use super::InstructionRunner;
|
||||
use crate::cpu::{instructions::load16::SixteenBitLoadInstruction, Processor};
|
||||
use crate::register;
|
||||
|
||||
pub(super) struct SixteenBitLoadRunner;
|
||||
|
||||
impl InstructionRunner<SixteenBitLoadInstruction> for SixteenBitLoadRunner {
|
||||
fn run_instruction(processor: &mut Processor, instruction: &SixteenBitLoadInstruction) {
|
||||
match *instruction {
|
||||
SixteenBitLoadInstruction::LoadImmediateToRegister { dst, value } => {
|
||||
processor.registers.set_16bit_register(dst, value);
|
||||
}
|
||||
|
||||
SixteenBitLoadInstruction::LoadBetweenRegisters { dst, src } => {
|
||||
let value = processor.registers.get_16bit_register(src);
|
||||
processor.registers.set_16bit_register(dst, value);
|
||||
}
|
||||
|
||||
SixteenBitLoadInstruction::LoadEffectiveAddress { dst, offset } => {
|
||||
let current_sp = processor
|
||||
.registers
|
||||
.get_single_16bit_register(register::SingleSixteenBit::StackPointer);
|
||||
|
||||
// TODO: This is gross. I'll clean this up when I do more ALU instructions
|
||||
// because there's bound to be reuse
|
||||
let (sixteen_bit_offset, new_sp, carry) = if offset >= 0 {
|
||||
let sixteen_bit_offset = u16::try_from(offset)
|
||||
.expect("failed to put positive (or zero) 8 bit value into unsigned sixteen bit, which should always work...");
|
||||
let new_sp = current_sp + sixteen_bit_offset;
|
||||
// If the lower eight bits are lower than where we started (with a known positive number)
|
||||
// we must have carried.
|
||||
let carry =
|
||||
(((current_sp & 0xff) + (sixteen_bit_offset & 0xff)) & 0x100) == 0x100;
|
||||
|
||||
(sixteen_bit_offset, new_sp, carry)
|
||||
} else {
|
||||
let sixteen_bit_offset = u16::try_from(i16::from(offset).abs())
|
||||
.expect("failed to convert an abs'd 16 bit value to a u16");
|
||||
let (new_sp, underflow) = current_sp.overflowing_sub(sixteen_bit_offset);
|
||||
|
||||
(sixteen_bit_offset, new_sp, underflow)
|
||||
};
|
||||
|
||||
let carry_bit = if carry { 1 } else { 0 };
|
||||
let half_carry_bit =
|
||||
// https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
|
||||
if ((current_sp & 0xf) + (sixteen_bit_offset & 0xf)) & 0x10 == 0x10 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::Carry, carry_bit);
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::HalfCarry, half_carry_bit);
|
||||
|
||||
// Manual says we reset these here.
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::Subtract, 0);
|
||||
processor.registers.set_flag_bit(register::Flag::Zero, 0);
|
||||
|
||||
processor.registers.set_combined_register(dst, new_sp);
|
||||
}
|
||||
|
||||
SixteenBitLoadInstruction::Push { src } => {
|
||||
let current_sp = processor
|
||||
.registers
|
||||
.get_single_16bit_register(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] =
|
||||
processor.registers.get_combined_register(src).to_le_bytes();
|
||||
processor
|
||||
.memory
|
||||
.set_both((
|
||||
((current_sp - 1).into(), higher_bits),
|
||||
((current_sp - 2).into(), lower_bits),
|
||||
))
|
||||
.expect("failed to load to stack");
|
||||
|
||||
processor.registers.set_16bit_register(
|
||||
register::SixteenBit::Single(register::SingleSixteenBit::StackPointer),
|
||||
current_sp - 2,
|
||||
);
|
||||
}
|
||||
|
||||
SixteenBitLoadInstruction::Pop { dst } => {
|
||||
let current_sp = processor
|
||||
.registers
|
||||
.get_single_16bit_register(register::SingleSixteenBit::StackPointer);
|
||||
|
||||
let popped_bytes = [
|
||||
processor
|
||||
.memory
|
||||
.get(current_sp.into())
|
||||
.expect("stack pointer pointed to invalid address"),
|
||||
processor
|
||||
.memory
|
||||
.get((current_sp + 1).into())
|
||||
.expect("stack pointer pointed to invalid address"),
|
||||
];
|
||||
|
||||
let popped_value = u16::from_le_bytes(popped_bytes);
|
||||
|
||||
processor.registers.set_combined_register(dst, popped_value);
|
||||
processor.registers.set_16bit_register(
|
||||
register::SixteenBit::Single(register::SingleSixteenBit::StackPointer),
|
||||
current_sp + 2,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
use super::{util::assert_ok, InstructionRunner};
|
||||
use crate::cpu::{instructions::load8::EightBitLoadInstruction, Processor};
|
||||
use crate::{memory, register};
|
||||
|
||||
pub(super) struct EightBitLoadRunner;
|
||||
|
||||
impl InstructionRunner<EightBitLoadInstruction> for EightBitLoadRunner {
|
||||
// TODO: Break this up somehow
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn run_instruction(processor: &mut Processor, instruction: &EightBitLoadInstruction) {
|
||||
match *instruction {
|
||||
EightBitLoadInstruction::LoadImmediateToRegister { value, register } => {
|
||||
processor
|
||||
.registers
|
||||
.set_single_8bit_register(register, value);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadBetweenRegisters { dst, src } => {
|
||||
let src_value = processor.registers.get_single_8bit_register(src);
|
||||
processor.registers.set_single_8bit_register(dst, src_value);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadFromRegisterAddress { src, dst } => {
|
||||
let load_res = processor.load_from_register_address_to_register(dst, src);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadFromImmediateAddress { src_address, dst } => {
|
||||
let load_res = processor.load_from_address_to_register(dst, src_address.into());
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadToRegisterAddress { src, dst } => {
|
||||
let load_res = processor.load_from_register_to_register_address(dst, src);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadToImmediateAddress { src, dst_address } => {
|
||||
let load_res = processor.load_from_register_to_address(dst_address.into(), src);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadnToHLAddress { value } => {
|
||||
let dest_address = processor
|
||||
.registers
|
||||
.get_combined_register(register::Combined::HL);
|
||||
let load_res = processor.load_8bit_immediate_to_address(dest_address.into(), value);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadFromMemoryRelativeToIORegisterStart {
|
||||
offset_register,
|
||||
dst,
|
||||
} => {
|
||||
let src_address_offset = processor
|
||||
.registers
|
||||
.get_single_8bit_register(offset_register);
|
||||
let src_address =
|
||||
memory::IO_REGISTER_START_ADDRESS + usize::from(src_address_offset);
|
||||
let load_res = processor.load_from_address_to_register(dst, src_address);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadToMemoryRelativeToIORegisterStart {
|
||||
src,
|
||||
offset_register,
|
||||
} => {
|
||||
let dst_address_offset = processor
|
||||
.registers
|
||||
.get_single_8bit_register(offset_register);
|
||||
let dst_address =
|
||||
memory::IO_REGISTER_START_ADDRESS + usize::from(dst_address_offset);
|
||||
let load_res = processor.load_from_register_to_address(dst_address, src);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadFromRegisterAddressThenDec { dst, src } => {
|
||||
let load_res = processor.load_from_register_address_to_register(dst, src);
|
||||
assert_ok!(load_res);
|
||||
|
||||
let src_address = processor.registers.get_combined_register(src);
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(src, src_address - 1);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadFromRegisterAddressThenInc { dst, src } => {
|
||||
let load_res = processor.load_from_register_address_to_register(dst, src);
|
||||
assert_ok!(load_res);
|
||||
|
||||
let src_address = processor.registers.get_combined_register(src);
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(src, src_address + 1);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadToRegisterAddressThenDec { dst, src } => {
|
||||
let dst_address = processor.registers.get_combined_register(dst);
|
||||
let load_res = processor.load_from_register_to_address(dst_address.into(), src);
|
||||
assert_ok!(load_res);
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(dst, dst_address - 1);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadToRegisterAddressThenInc { dst, src } => {
|
||||
let dst_address = processor.registers.get_combined_register(dst);
|
||||
let load_res = processor.load_from_register_to_address(dst_address.into(), src);
|
||||
assert_ok!(load_res);
|
||||
|
||||
processor
|
||||
.registers
|
||||
.set_combined_register(dst, dst_address + 1);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadToMemoryRelativeToIORegisterStartByImmediate {
|
||||
src,
|
||||
offset,
|
||||
} => {
|
||||
let dst_address = memory::IO_REGISTER_START_ADDRESS + usize::from(offset);
|
||||
let load_res = processor.load_from_register_to_address(dst_address, src);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
|
||||
EightBitLoadInstruction::LoadFromMemoryRelativeToIORegisterStartByImmediate {
|
||||
dst,
|
||||
offset,
|
||||
} => {
|
||||
let src_address = memory::IO_REGISTER_START_ADDRESS + usize::from(offset);
|
||||
let load_res = processor.load_from_address_to_register(dst, src_address);
|
||||
assert_ok!(load_res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
macro_rules! assert_ok {
|
||||
($result: expr) => {
|
||||
let res: Result<_, _> = $result;
|
||||
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||
};
|
||||
}
|
||||
|
||||
pub(super) use assert_ok;
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
mod memory;
|
||||
mod register;
|
||||
mod run;
|
||||
mod cpu;
|
||||
|
|
Loading…
Reference in New Issue