Break out instruction running code into its own module

jsmoo
Nick Krichevsky 2022-04-16 10:59:07 -04:00
parent 54cb2b8b2c
commit 16385f6387
22 changed files with 361 additions and 290 deletions

View File

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

View File

@ -1,6 +1,6 @@
use crate::{
register,
run::{
cpu::{
instructions::{arith8::EightBitArithmeticInstruction, Instruction, RunnableInstruction},
parse::{self, Error, OpcodeParser, ParseResult},
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

29
src/cpu/run.rs Normal file
View File

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

47
src/cpu/run/arith8.rs Normal file
View File

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

118
src/cpu/run/load16.rs Normal file
View File

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

136
src/cpu/run/load8.rs Normal file
View File

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

8
src/cpu/run/util.rs Normal file
View File

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

View File

@ -2,4 +2,4 @@
mod memory;
mod register;
mod run;
mod cpu;