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::{
|
use crate::{
|
||||||
memory::{self, Memory},
|
memory::Memory,
|
||||||
register::{self, Registers},
|
register::{self, Registers},
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use self::instructions::{
|
|
||||||
arith8::EightBitArithmeticInstruction, load16::SixteenBitLoadInstruction,
|
|
||||||
load8::EightBitLoadInstruction,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod instructions;
|
mod instructions;
|
||||||
mod parse;
|
mod parse;
|
||||||
|
mod run;
|
||||||
|
|
||||||
/// `Error` represents a run error. Typically these will be fatal and panicking on their emission
|
/// `Error` represents a run error. Typically these will be fatal and panicking on their emission
|
||||||
/// is likely the best course of action
|
/// is likely the best course of action
|
||||||
|
@ -33,275 +29,12 @@ pub struct Processor {
|
||||||
memory: Memory,
|
memory: Memory,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! assert_ok {
|
|
||||||
($result: expr) => {
|
|
||||||
let res: Result<_, _> = $result;
|
|
||||||
assert!(res.is_ok(), "{}", res.unwrap_err());
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Processor {
|
impl Processor {
|
||||||
fn run(&mut self, instruction: &RunnableInstruction) {
|
fn run(&mut self, instruction: &RunnableInstruction) {
|
||||||
match &instruction.instruction {
|
run::run_instruction(self, &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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.num_cycles += u64::from(instruction.cycles);
|
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(
|
fn load_from_register_to_register_address(
|
||||||
&mut self,
|
&mut self,
|
||||||
register_with_dst_address: register::Combined,
|
register_with_dst_address: register::Combined,
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
register,
|
register,
|
||||||
run::{
|
cpu::{
|
||||||
instructions::{arith8::EightBitArithmeticInstruction, Instruction, RunnableInstruction},
|
instructions::{arith8::EightBitArithmeticInstruction, Instruction, RunnableInstruction},
|
||||||
parse::{self, Error, OpcodeParser, ParseResult},
|
parse::{self, Error, OpcodeParser, ParseResult},
|
||||||
},
|
},
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::register;
|
use crate::register;
|
||||||
use crate::run::instructions::load16::SixteenBitLoadInstruction;
|
use crate::cpu::instructions::load16::SixteenBitLoadInstruction;
|
||||||
use crate::run::{
|
use crate::cpu::{
|
||||||
instructions::{Instruction, RunnableInstruction},
|
instructions::{Instruction, RunnableInstruction},
|
||||||
parse::{self, Error, OpcodeParser, ParseResult},
|
parse::{self, Error, OpcodeParser, ParseResult},
|
||||||
};
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::register;
|
use crate::register;
|
||||||
use crate::run::instructions::load16::SixteenBitLoadInstruction;
|
use crate::cpu::instructions::load16::SixteenBitLoadInstruction;
|
||||||
use crate::run::instructions::{Instruction, RunnableInstruction};
|
use crate::cpu::instructions::{Instruction, RunnableInstruction};
|
||||||
use crate::run::parse::{self, Error, OpcodeParser, ParseResult};
|
use crate::cpu::parse::{self, Error, OpcodeParser, ParseResult};
|
||||||
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
#[allow(clippy::module_name_repetitions)]
|
||||||
pub struct StackLoadParser;
|
pub struct StackLoadParser;
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::register;
|
use crate::register;
|
||||||
use crate::run::instructions::load16::SixteenBitLoadInstruction;
|
use crate::cpu::instructions::load16::SixteenBitLoadInstruction;
|
||||||
use crate::run::instructions::Instruction;
|
use crate::cpu::instructions::Instruction;
|
||||||
use crate::run::{
|
use crate::cpu::{
|
||||||
instructions::RunnableInstruction,
|
instructions::RunnableInstruction,
|
||||||
parse::{self, Error, OpcodeParser, ParseResult},
|
parse::{self, Error, OpcodeParser, ParseResult},
|
||||||
};
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::register;
|
use crate::register;
|
||||||
use crate::run::instructions::load8::EightBitLoadInstruction;
|
use crate::cpu::instructions::load8::EightBitLoadInstruction;
|
||||||
use crate::run::instructions::Instruction;
|
use crate::cpu::instructions::Instruction;
|
||||||
use crate::run::{
|
use crate::cpu::{
|
||||||
instructions::RunnableInstruction,
|
instructions::RunnableInstruction,
|
||||||
parse::{self, Error, OpcodeParser, ParseResult},
|
parse::{self, Error, OpcodeParser, ParseResult},
|
||||||
};
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::register;
|
use crate::register;
|
||||||
use crate::run::instructions::load8::EightBitLoadInstruction;
|
use crate::cpu::instructions::load8::EightBitLoadInstruction;
|
||||||
use crate::run::instructions::{Instruction, RunnableInstruction};
|
use crate::cpu::instructions::{Instruction, RunnableInstruction};
|
||||||
use crate::run::parse::{self, Error, OpcodeParser, ParseResult};
|
use crate::cpu::parse::{self, Error, OpcodeParser, ParseResult};
|
||||||
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
#[allow(clippy::module_name_repetitions)]
|
||||||
pub struct Memory8BitLoadParser;
|
pub struct Memory8BitLoadParser;
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::register;
|
use crate::register;
|
||||||
use crate::run::instructions::load8::EightBitLoadInstruction;
|
use crate::cpu::instructions::load8::EightBitLoadInstruction;
|
||||||
use crate::run::instructions::Instruction;
|
use crate::cpu::instructions::Instruction;
|
||||||
use crate::run::{
|
use crate::cpu::{
|
||||||
instructions::RunnableInstruction,
|
instructions::RunnableInstruction,
|
||||||
parse::{self, Error, OpcodeParser, ParseResult},
|
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 memory;
|
||||||
mod register;
|
mod register;
|
||||||
mod run;
|
mod cpu;
|
||||||
|
|
Loading…
Reference in New Issue