feat: add some more opcodes
This commit is contained in:
parent
4abfe1ae90
commit
84f13b263b
6 changed files with 670 additions and 7 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
/roms/
|
/roms/
|
||||||
config.toml
|
config.toml
|
||||||
|
/.vscode/
|
1
rust-toolchain
Normal file
1
rust-toolchain
Normal file
|
@ -0,0 +1 @@
|
||||||
|
stable
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod cpu;
|
||||||
mod interrupts;
|
mod interrupts;
|
||||||
mod joypad;
|
mod joypad;
|
||||||
mod mapper;
|
mod mapper;
|
||||||
|
@ -12,6 +13,8 @@ use memory::Memory;
|
||||||
use ppu::Ppu;
|
use ppu::Ppu;
|
||||||
use timer::Timer;
|
use timer::Timer;
|
||||||
|
|
||||||
|
use self::cpu::Registers;
|
||||||
|
|
||||||
pub struct DmaState {
|
pub struct DmaState {
|
||||||
pub base: u8,
|
pub base: u8,
|
||||||
pub remaining_cycles: u8,
|
pub remaining_cycles: u8,
|
||||||
|
@ -36,6 +39,7 @@ pub struct Gameboy {
|
||||||
cartridge: Option<Box<dyn Mapper>>,
|
cartridge: Option<Box<dyn Mapper>>,
|
||||||
interrupts: Interrupts,
|
interrupts: Interrupts,
|
||||||
timer: Timer,
|
timer: Timer,
|
||||||
|
pub registers: Registers,
|
||||||
pub joypad: Joypad,
|
pub joypad: Joypad,
|
||||||
pub dma: DmaState,
|
pub dma: DmaState,
|
||||||
}
|
}
|
||||||
|
@ -50,6 +54,7 @@ impl Gameboy {
|
||||||
joypad: Joypad::new(),
|
joypad: Joypad::new(),
|
||||||
dma: DmaState::new(),
|
dma: DmaState::new(),
|
||||||
ppu: Ppu::new(),
|
ppu: Ppu::new(),
|
||||||
|
registers: Registers::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,14 +63,12 @@ impl Gameboy {
|
||||||
self.interrupts.write_if_timer(true);
|
self.interrupts.write_if_timer(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.tick_cpu();
|
cpu::tick_cpu(self);
|
||||||
let redraw_requested = self.ppu.tick(&mut self.interrupts);
|
let redraw_requested = self.ppu.tick(&mut self.interrupts);
|
||||||
self.tick_dma();
|
self.tick_dma();
|
||||||
redraw_requested
|
redraw_requested
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick_cpu(&mut self) {}
|
|
||||||
|
|
||||||
fn tick_dma(&mut self) {
|
fn tick_dma(&mut self) {
|
||||||
if self.dma.remaining_delay > 0 {
|
if self.dma.remaining_delay > 0 {
|
||||||
self.dma.remaining_delay -= 1;
|
self.dma.remaining_delay -= 1;
|
||||||
|
@ -166,8 +169,11 @@ impl Gameboy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cpu_read_u8(&self, address: u16) -> u8 {
|
pub fn cpu_read_u8(&mut self, address: u16) {
|
||||||
if self.dma.remaining_cycles == 0 {
|
assert!(!self.registers.mem_op_happened);
|
||||||
|
assert!(self.registers.mem_read_hold.is_none());
|
||||||
|
self.registers.mem_op_happened = true;
|
||||||
|
self.registers.mem_read_hold = Some(if self.dma.remaining_cycles == 0 {
|
||||||
match address {
|
match address {
|
||||||
0..=0xFF if !self.memory.bootrom_disabled => self.memory.bootrom[address as usize],
|
0..=0xFF if !self.memory.bootrom_disabled => self.memory.bootrom[address as usize],
|
||||||
0..=0x7FFF => match self.cartridge.as_ref() {
|
0..=0x7FFF => match self.cartridge.as_ref() {
|
||||||
|
@ -194,10 +200,12 @@ impl Gameboy {
|
||||||
0xFF80..=0xFFFE => self.memory.hram[address as usize - 0xFF80],
|
0xFF80..=0xFFFE => self.memory.hram[address as usize - 0xFF80],
|
||||||
0xFFFF => self.interrupts.interrupt_enable,
|
0xFFFF => self.interrupts.interrupt_enable,
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cpu_write_u8(&mut self, address: u16, value: u8) {
|
pub fn cpu_write_u8(&mut self, address: u16, value: u8) {
|
||||||
|
assert!(!self.registers.mem_op_happened);
|
||||||
|
self.registers.mem_op_happened = true;
|
||||||
if self.dma.remaining_cycles == 0 {
|
if self.dma.remaining_cycles == 0 {
|
||||||
match address {
|
match address {
|
||||||
0..=0xFF if !self.memory.bootrom_disabled => {}
|
0..=0xFF if !self.memory.bootrom_disabled => {}
|
||||||
|
|
226
src/gameboy/cpu.rs
Normal file
226
src/gameboy/cpu.rs
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
mod alu;
|
||||||
|
mod load_store_move;
|
||||||
|
|
||||||
|
use super::Gameboy;
|
||||||
|
|
||||||
|
macro_rules! define_register {
|
||||||
|
($lident:ident, $rident:ident) => {
|
||||||
|
paste::paste! {
|
||||||
|
pub fn [<get_ $lident $rident>](&self) -> u16 {
|
||||||
|
(self.$lident as u16) << 8 | self.$rident as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn [<set_ $lident $rident>](&mut self, value: u16) {
|
||||||
|
self.$lident = (value >> 8) as u8;
|
||||||
|
self.$rident = value as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! define_flag {
|
||||||
|
($flag:ident, $bit:literal) => {
|
||||||
|
paste::paste! {
|
||||||
|
pub fn [<get_ $flag>](&self) -> bool {
|
||||||
|
self.f >> $bit == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn [<set_ $flag>](&mut self, value: bool) {
|
||||||
|
self.f &= !(1 << $bit);
|
||||||
|
self.f |= (value as u8) << $bit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum CycleResult {
|
||||||
|
NeedsMore,
|
||||||
|
Finished,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Registers {
|
||||||
|
pub a: u8,
|
||||||
|
pub f: u8,
|
||||||
|
pub b: u8,
|
||||||
|
pub c: u8,
|
||||||
|
pub d: u8,
|
||||||
|
pub e: u8,
|
||||||
|
pub h: u8,
|
||||||
|
pub l: u8,
|
||||||
|
pub sp: u16,
|
||||||
|
pub pc: u16,
|
||||||
|
|
||||||
|
// Not actual registers
|
||||||
|
pub cycle: u8,
|
||||||
|
pub hold: Option<u16>,
|
||||||
|
pub opcode_len: Option<u8>,
|
||||||
|
pub current_opcode: Option<u8>,
|
||||||
|
pub mem_read_hold: Option<u8>,
|
||||||
|
pub mem_op_happened: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Registers {
|
||||||
|
define_register!(a, f);
|
||||||
|
define_register!(b, c);
|
||||||
|
define_register!(d, e);
|
||||||
|
define_register!(h, l);
|
||||||
|
|
||||||
|
/// This is just a helper function for macros utilizing ident pasting
|
||||||
|
pub fn get_sp(&self) -> u16 {
|
||||||
|
self.sp
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is just a helper function for macros utilizing ident pasting
|
||||||
|
pub fn set_sp(&mut self, value: u16) {
|
||||||
|
self.sp = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
define_flag!(zero, 7);
|
||||||
|
define_flag!(subtract, 7);
|
||||||
|
define_flag!(half_carry, 7);
|
||||||
|
define_flag!(carry, 7);
|
||||||
|
|
||||||
|
pub fn take_mem(&mut self) -> u8 {
|
||||||
|
self.mem_read_hold.take().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_hold(&mut self) -> u16 {
|
||||||
|
self.hold.take().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_hold(&mut self, value: u16) {
|
||||||
|
assert!(self.hold.is_none());
|
||||||
|
self.hold = Some(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick_cpu(state: &mut Gameboy) {
|
||||||
|
state.registers.mem_op_happened = false;
|
||||||
|
// TODO: Interrupts
|
||||||
|
|
||||||
|
let opcode = match state.registers.current_opcode {
|
||||||
|
Some(opcode) => opcode,
|
||||||
|
None => match state.registers.mem_read_hold.take() {
|
||||||
|
Some(opcode) => {
|
||||||
|
log::debug!("Executing instruction {:#X}", opcode);
|
||||||
|
state.registers.current_opcode = Some(opcode);
|
||||||
|
opcode
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
state.cpu_read_u8(state.registers.pc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let instruction_result: CycleResult = match opcode {
|
||||||
|
0x01 => load_store_move::ld_bc_imm_u16,
|
||||||
|
0x08 => load_store_move::ld_deref_imm_u16_sp,
|
||||||
|
0x0a => load_store_move::ld_a_deref_bc,
|
||||||
|
0x11 => load_store_move::ld_de_imm_u16,
|
||||||
|
0x1a => load_store_move::ld_a_deref_de,
|
||||||
|
0x21 => load_store_move::ld_hl_imm_u16,
|
||||||
|
0x22 => load_store_move::ld_hl_plus_a,
|
||||||
|
0x2a => load_store_move::ld_a_hl_plus,
|
||||||
|
0x32 => load_store_move::ld_hl_minus_a,
|
||||||
|
0x3a => load_store_move::ld_a_hl_minus,
|
||||||
|
0x31 => load_store_move::ld_sp_imm_u16,
|
||||||
|
0x40 => load_store_move::ld_b_b,
|
||||||
|
0x41 => load_store_move::ld_b_c,
|
||||||
|
0x42 => load_store_move::ld_b_d,
|
||||||
|
0x43 => load_store_move::ld_b_e,
|
||||||
|
0x44 => load_store_move::ld_b_h,
|
||||||
|
0x45 => load_store_move::ld_b_l,
|
||||||
|
0x46 => load_store_move::ld_b_deref_hl,
|
||||||
|
0x47 => load_store_move::ld_b_a,
|
||||||
|
0x48 => load_store_move::ld_c_b,
|
||||||
|
0x49 => load_store_move::ld_c_c,
|
||||||
|
0x4a => load_store_move::ld_c_d,
|
||||||
|
0x4b => load_store_move::ld_c_e,
|
||||||
|
0x4c => load_store_move::ld_c_h,
|
||||||
|
0x4d => load_store_move::ld_c_l,
|
||||||
|
0x4e => load_store_move::ld_c_deref_hl,
|
||||||
|
0x4f => load_store_move::ld_c_a,
|
||||||
|
0x50 => load_store_move::ld_d_b,
|
||||||
|
0x51 => load_store_move::ld_d_c,
|
||||||
|
0x52 => load_store_move::ld_d_d,
|
||||||
|
0x53 => load_store_move::ld_d_e,
|
||||||
|
0x54 => load_store_move::ld_d_h,
|
||||||
|
0x55 => load_store_move::ld_d_l,
|
||||||
|
0x56 => load_store_move::ld_d_deref_hl,
|
||||||
|
0x57 => load_store_move::ld_d_a,
|
||||||
|
0x58 => load_store_move::ld_e_b,
|
||||||
|
0x59 => load_store_move::ld_e_c,
|
||||||
|
0x5a => load_store_move::ld_e_d,
|
||||||
|
0x5b => load_store_move::ld_e_e,
|
||||||
|
0x5c => load_store_move::ld_e_h,
|
||||||
|
0x5d => load_store_move::ld_e_l,
|
||||||
|
0x5e => load_store_move::ld_e_deref_hl,
|
||||||
|
0x5f => load_store_move::ld_e_a,
|
||||||
|
0x60 => load_store_move::ld_h_b,
|
||||||
|
0x61 => load_store_move::ld_h_c,
|
||||||
|
0x62 => load_store_move::ld_h_d,
|
||||||
|
0x63 => load_store_move::ld_h_e,
|
||||||
|
0x64 => load_store_move::ld_h_h,
|
||||||
|
0x65 => load_store_move::ld_h_l,
|
||||||
|
0x66 => load_store_move::ld_h_deref_hl,
|
||||||
|
0x67 => load_store_move::ld_h_a,
|
||||||
|
0x68 => load_store_move::ld_l_b,
|
||||||
|
0x69 => load_store_move::ld_l_c,
|
||||||
|
0x6a => load_store_move::ld_l_d,
|
||||||
|
0x6b => load_store_move::ld_l_e,
|
||||||
|
0x6c => load_store_move::ld_l_h,
|
||||||
|
0x6d => load_store_move::ld_l_l,
|
||||||
|
0x6e => load_store_move::ld_l_deref_hl,
|
||||||
|
0x6f => load_store_move::ld_l_a,
|
||||||
|
0x78 => load_store_move::ld_a_b,
|
||||||
|
0x79 => load_store_move::ld_a_c,
|
||||||
|
0x7a => load_store_move::ld_a_d,
|
||||||
|
0x7b => load_store_move::ld_a_e,
|
||||||
|
0x7c => load_store_move::ld_a_h,
|
||||||
|
0x7d => load_store_move::ld_a_l,
|
||||||
|
0x7e => load_store_move::ld_a_deref_hl,
|
||||||
|
0x7f => load_store_move::ld_a_a,
|
||||||
|
0x98 => alu::sbc_a_b,
|
||||||
|
0x99 => alu::sbc_a_c,
|
||||||
|
0x9A => alu::sbc_a_d,
|
||||||
|
0x9B => alu::sbc_a_e,
|
||||||
|
0x9C => alu::sbc_a_h,
|
||||||
|
0x9D => alu::sbc_a_l,
|
||||||
|
0x9E => alu::sbc_a_deref_hl,
|
||||||
|
0x9F => alu::sbc_a_a,
|
||||||
|
0xA8 => alu::xor_a_b,
|
||||||
|
0xA9 => alu::xor_a_c,
|
||||||
|
0xAA => alu::xor_a_d,
|
||||||
|
0xAB => alu::xor_a_e,
|
||||||
|
0xAC => alu::xor_a_h,
|
||||||
|
0xAD => alu::xor_a_l,
|
||||||
|
0xAE => alu::xor_a_deref_hl,
|
||||||
|
0xAF => alu::xor_a_a,
|
||||||
|
0xDE => alu::sbc_a_imm_u8,
|
||||||
|
0xEE => alu::xor_a_imm_u8,
|
||||||
|
0xF9 => load_store_move::ld_sp_hl,
|
||||||
|
unknown => panic!("Unrecognized opcode: {:#X}\nRegisters: {:#?}", unknown, state.registers),
|
||||||
|
}(state);
|
||||||
|
|
||||||
|
if instruction_result == CycleResult::Finished {
|
||||||
|
match state.registers.opcode_len {
|
||||||
|
Some(len) => state.registers.pc += len as u16,
|
||||||
|
None => panic!("Forgot to set opcode len for {:#X}", opcode),
|
||||||
|
}
|
||||||
|
|
||||||
|
if !state.registers.mem_op_happened {
|
||||||
|
log::trace!("Memory bus clear, precaching next opcode");
|
||||||
|
state.cpu_read_u8(state.registers.pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.registers.cycle = 0;
|
||||||
|
state.registers.current_opcode = None;
|
||||||
|
state.registers.opcode_len = None;
|
||||||
|
log::trace!("Cycle finished");
|
||||||
|
} else {
|
||||||
|
state.registers.cycle += 1;
|
||||||
|
}
|
||||||
|
}
|
173
src/gameboy/cpu/alu.rs
Normal file
173
src/gameboy/cpu/alu.rs
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
use super::CycleResult;
|
||||||
|
use crate::gameboy::Gameboy;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CarryResult {
|
||||||
|
pub result: u8,
|
||||||
|
pub half_carry: bool,
|
||||||
|
pub carry: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sub_with_carry(lhs: u8, rhs: u8, carry: bool) -> CarryResult {
|
||||||
|
let carry_u8 = carry as u8;
|
||||||
|
|
||||||
|
let (first_res, first_carry) = lhs.overflowing_sub(rhs);
|
||||||
|
let (result, second_carry) = first_res.overflowing_sub(carry_u8);
|
||||||
|
|
||||||
|
let carry = first_carry || second_carry;
|
||||||
|
|
||||||
|
let (first_hc_res, first_half_carry) = (lhs & 0xF).overflowing_sub(rhs & 0xF);
|
||||||
|
let (_, second_half_carry) = first_hc_res.overflowing_sub(carry_u8);
|
||||||
|
|
||||||
|
let half_carry = first_half_carry || second_half_carry;
|
||||||
|
|
||||||
|
CarryResult { result, carry, half_carry }
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! define_xor_reg {
|
||||||
|
($reg:ident) => {
|
||||||
|
paste::paste! {
|
||||||
|
pub fn [<xor_a_ $reg>](state: &mut Gameboy) -> CycleResult {
|
||||||
|
match state.registers.cycle {
|
||||||
|
0 => {
|
||||||
|
state.registers.a ^= state.registers.$reg;
|
||||||
|
state.registers.set_zero(state.registers.a == 0);
|
||||||
|
state.registers.set_subtract(false);
|
||||||
|
state.registers.set_half_carry(false);
|
||||||
|
state.registers.set_carry(false);
|
||||||
|
state.registers.opcode_len = Some(1);
|
||||||
|
CycleResult::Finished
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! define_sbc_reg {
|
||||||
|
($reg:ident) => {
|
||||||
|
paste::paste! {
|
||||||
|
pub fn [<sbc_a_ $reg>](state: &mut Gameboy) -> CycleResult {
|
||||||
|
match state.registers.cycle {
|
||||||
|
0 => {
|
||||||
|
let CarryResult { result, half_carry, carry } = sub_with_carry(state.registers.a, state.registers.$reg, state.registers.get_carry());
|
||||||
|
|
||||||
|
state.registers.a = result;
|
||||||
|
state.registers.set_zero(result == 0);
|
||||||
|
state.registers.set_subtract(true);
|
||||||
|
state.registers.set_half_carry(half_carry);
|
||||||
|
state.registers.set_carry(carry);
|
||||||
|
state.registers.opcode_len = Some(1);
|
||||||
|
CycleResult::Finished
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
define_xor_reg!(a);
|
||||||
|
define_xor_reg!(b);
|
||||||
|
define_xor_reg!(c);
|
||||||
|
define_xor_reg!(d);
|
||||||
|
define_xor_reg!(e);
|
||||||
|
define_xor_reg!(h);
|
||||||
|
define_xor_reg!(l);
|
||||||
|
|
||||||
|
pub fn xor_a_deref_hl(state: &mut Gameboy) -> CycleResult {
|
||||||
|
match state.registers.cycle {
|
||||||
|
0 => {
|
||||||
|
state.cpu_read_u8(state.registers.get_hl());
|
||||||
|
CycleResult::NeedsMore
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
state.registers.a ^= state.registers.take_mem();
|
||||||
|
state.registers.set_zero(state.registers.a == 0);
|
||||||
|
state.registers.set_subtract(false);
|
||||||
|
state.registers.set_half_carry(false);
|
||||||
|
state.registers.set_carry(false);
|
||||||
|
state.registers.opcode_len = Some(1);
|
||||||
|
CycleResult::Finished
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn xor_a_imm_u8(state: &mut Gameboy) -> CycleResult {
|
||||||
|
match state.registers.cycle {
|
||||||
|
0 => {
|
||||||
|
state.cpu_read_u8(state.registers.pc + 1);
|
||||||
|
CycleResult::NeedsMore
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
state.registers.a ^= state.registers.take_mem();
|
||||||
|
state.registers.set_zero(state.registers.a == 0);
|
||||||
|
state.registers.set_subtract(false);
|
||||||
|
state.registers.set_half_carry(false);
|
||||||
|
state.registers.set_carry(false);
|
||||||
|
state.registers.opcode_len = Some(2);
|
||||||
|
CycleResult::Finished
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
define_sbc_reg!(a);
|
||||||
|
define_sbc_reg!(b);
|
||||||
|
define_sbc_reg!(c);
|
||||||
|
define_sbc_reg!(d);
|
||||||
|
define_sbc_reg!(e);
|
||||||
|
define_sbc_reg!(h);
|
||||||
|
define_sbc_reg!(l);
|
||||||
|
|
||||||
|
pub fn sbc_a_deref_hl(state: &mut Gameboy) -> CycleResult {
|
||||||
|
match state.registers.cycle {
|
||||||
|
0 => {
|
||||||
|
state.cpu_read_u8(state.registers.get_hl());
|
||||||
|
CycleResult::NeedsMore
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let CarryResult { result, half_carry, carry } = sub_with_carry(
|
||||||
|
state.registers.a,
|
||||||
|
state.registers.take_mem(),
|
||||||
|
state.registers.get_carry(),
|
||||||
|
);
|
||||||
|
|
||||||
|
state.registers.a = result;
|
||||||
|
state.registers.set_zero(result == 0);
|
||||||
|
state.registers.set_subtract(true);
|
||||||
|
state.registers.set_half_carry(half_carry);
|
||||||
|
state.registers.set_carry(carry);
|
||||||
|
state.registers.opcode_len = Some(1);
|
||||||
|
CycleResult::Finished
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sbc_a_imm_u8(state: &mut Gameboy) -> CycleResult {
|
||||||
|
match state.registers.cycle {
|
||||||
|
0 => {
|
||||||
|
state.cpu_read_u8(state.registers.pc + 1);
|
||||||
|
CycleResult::NeedsMore
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let CarryResult { result, half_carry, carry } = sub_with_carry(
|
||||||
|
state.registers.a,
|
||||||
|
state.registers.take_mem(),
|
||||||
|
state.registers.get_carry(),
|
||||||
|
);
|
||||||
|
|
||||||
|
state.registers.a = result;
|
||||||
|
state.registers.set_zero(result == 0);
|
||||||
|
state.registers.set_subtract(true);
|
||||||
|
state.registers.set_half_carry(half_carry);
|
||||||
|
state.registers.set_carry(carry);
|
||||||
|
state.registers.opcode_len = Some(1);
|
||||||
|
CycleResult::Finished
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
254
src/gameboy/cpu/load_store_move.rs
Normal file
254
src/gameboy/cpu/load_store_move.rs
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
use crate::gameboy::{cpu::CycleResult, Gameboy};
|
||||||
|
|
||||||
|
macro_rules! define_ld_reg_imm_u16 {
|
||||||
|
($reg:ident) => {
|
||||||
|
paste::paste! {
|
||||||
|
pub fn [<ld_ $reg _imm_u16>](state: &mut Gameboy) -> CycleResult {
|
||||||
|
match state.registers.cycle {
|
||||||
|
0 => {
|
||||||
|
state.cpu_read_u8(state.registers.pc.overflowing_add(1).0);
|
||||||
|
CycleResult::NeedsMore
|
||||||
|
},
|
||||||
|
1 => {
|
||||||
|
let mut reg = state.registers.[<get_ $reg>]();
|
||||||
|
reg &= 0xFF00;
|
||||||
|
reg |= state.registers.take_mem() as u16;
|
||||||
|
state.registers.[<set_ $reg>](reg);
|
||||||
|
state.cpu_read_u8(state.registers.pc.overflowing_add(2).0);
|
||||||
|
CycleResult::NeedsMore
|
||||||
|
},
|
||||||
|
2 => {
|
||||||
|
let mut reg = state.registers.[<get_ $reg>]();
|
||||||
|
reg &= 0xFF;
|
||||||
|
reg |= (state.registers.take_mem() as u16) << 8;
|
||||||
|
state.registers.[<set_ $reg>](reg);
|
||||||
|
state.registers.opcode_len = Some(3);
|
||||||
|
CycleResult::Finished
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
define_ld_reg_imm_u16!(bc);
|
||||||
|
define_ld_reg_imm_u16!(de);
|
||||||
|
define_ld_reg_imm_u16!(hl);
|
||||||
|
define_ld_reg_imm_u16!(sp);
|
||||||
|
|
||||||
|
pub fn ld_sp_hl(state: &mut Gameboy) -> CycleResult {
|
||||||
|
match state.registers.cycle {
|
||||||
|
0 => {
|
||||||
|
state.registers.sp &= 0xFF00;
|
||||||
|
state.registers.sp |= state.registers.l as u16;
|
||||||
|
CycleResult::NeedsMore
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
state.registers.sp &= 0xFF;
|
||||||
|
state.registers.sp |= (state.registers.h as u16) << 8;
|
||||||
|
state.registers.opcode_len = Some(3);
|
||||||
|
CycleResult::Finished
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ld_deref_imm_u16_sp(state: &mut Gameboy) -> CycleResult {
|
||||||
|
match state.registers.cycle {
|
||||||
|
0 => {
|
||||||
|
state.cpu_read_u8(state.registers.pc.overflowing_add(1).0);
|
||||||
|
CycleResult::NeedsMore
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let low_addr = state.registers.take_mem() as u16;
|
||||||
|
state.registers.set_hold(low_addr);
|
||||||
|
state.cpu_read_u8(state.registers.pc.overflowing_add(2).0);
|
||||||
|
CycleResult::NeedsMore
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
let addr = ((state.registers.take_mem() as u16) << 8) | state.registers.take_hold();
|
||||||
|
state.registers.set_hold(addr);
|
||||||
|
state.cpu_write_u8(addr, state.registers.sp as u8);
|
||||||
|
CycleResult::NeedsMore
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
let addr = state.registers.take_hold().overflowing_add(1).0;
|
||||||
|
state.cpu_write_u8(addr, (state.registers.sp >> 8) as u8);
|
||||||
|
state.registers.opcode_len = Some(3);
|
||||||
|
CycleResult::Finished
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! define_ld_reg_reg {
|
||||||
|
($lreg:ident, $rreg:ident) => {
|
||||||
|
paste::paste! {
|
||||||
|
pub fn [<ld_ $lreg _ $rreg>](state: &mut Gameboy) -> CycleResult {
|
||||||
|
match state.registers.cycle {
|
||||||
|
0 => {
|
||||||
|
let res = state.registers.$rreg;
|
||||||
|
state.registers.$lreg = res;
|
||||||
|
state.registers.opcode_len = Some(1);
|
||||||
|
CycleResult::Finished
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
define_ld_reg_reg!(b, b);
|
||||||
|
define_ld_reg_reg!(b, c);
|
||||||
|
define_ld_reg_reg!(b, d);
|
||||||
|
define_ld_reg_reg!(b, e);
|
||||||
|
define_ld_reg_reg!(b, h);
|
||||||
|
define_ld_reg_reg!(b, l);
|
||||||
|
define_ld_reg_reg!(b, a);
|
||||||
|
|
||||||
|
define_ld_reg_reg!(c, b);
|
||||||
|
define_ld_reg_reg!(c, c);
|
||||||
|
define_ld_reg_reg!(c, d);
|
||||||
|
define_ld_reg_reg!(c, e);
|
||||||
|
define_ld_reg_reg!(c, h);
|
||||||
|
define_ld_reg_reg!(c, l);
|
||||||
|
define_ld_reg_reg!(c, a);
|
||||||
|
|
||||||
|
define_ld_reg_reg!(d, b);
|
||||||
|
define_ld_reg_reg!(d, c);
|
||||||
|
define_ld_reg_reg!(d, d);
|
||||||
|
define_ld_reg_reg!(d, e);
|
||||||
|
define_ld_reg_reg!(d, h);
|
||||||
|
define_ld_reg_reg!(d, l);
|
||||||
|
define_ld_reg_reg!(d, a);
|
||||||
|
|
||||||
|
define_ld_reg_reg!(e, b);
|
||||||
|
define_ld_reg_reg!(e, c);
|
||||||
|
define_ld_reg_reg!(e, d);
|
||||||
|
define_ld_reg_reg!(e, e);
|
||||||
|
define_ld_reg_reg!(e, h);
|
||||||
|
define_ld_reg_reg!(e, l);
|
||||||
|
define_ld_reg_reg!(e, a);
|
||||||
|
|
||||||
|
define_ld_reg_reg!(h, b);
|
||||||
|
define_ld_reg_reg!(h, c);
|
||||||
|
define_ld_reg_reg!(h, d);
|
||||||
|
define_ld_reg_reg!(h, e);
|
||||||
|
define_ld_reg_reg!(h, h);
|
||||||
|
define_ld_reg_reg!(h, l);
|
||||||
|
define_ld_reg_reg!(h, a);
|
||||||
|
|
||||||
|
define_ld_reg_reg!(l, b);
|
||||||
|
define_ld_reg_reg!(l, c);
|
||||||
|
define_ld_reg_reg!(l, d);
|
||||||
|
define_ld_reg_reg!(l, e);
|
||||||
|
define_ld_reg_reg!(l, h);
|
||||||
|
define_ld_reg_reg!(l, l);
|
||||||
|
define_ld_reg_reg!(l, a);
|
||||||
|
|
||||||
|
define_ld_reg_reg!(a, b);
|
||||||
|
define_ld_reg_reg!(a, c);
|
||||||
|
define_ld_reg_reg!(a, d);
|
||||||
|
define_ld_reg_reg!(a, e);
|
||||||
|
define_ld_reg_reg!(a, h);
|
||||||
|
define_ld_reg_reg!(a, l);
|
||||||
|
define_ld_reg_reg!(a, a);
|
||||||
|
|
||||||
|
macro_rules! define_ld_reg_deref {
|
||||||
|
($lreg:ident, $rreg:ident) => {
|
||||||
|
paste::paste! {
|
||||||
|
pub fn [<ld_ $lreg _deref_ $rreg>](state: &mut Gameboy) -> CycleResult {
|
||||||
|
match state.registers.cycle {
|
||||||
|
0 => {
|
||||||
|
state.cpu_read_u8(state.registers.[<get_ $rreg>]());
|
||||||
|
CycleResult::NeedsMore
|
||||||
|
},
|
||||||
|
1 => {
|
||||||
|
state.registers.$lreg = state.registers.take_mem();
|
||||||
|
state.registers.opcode_len = Some(1);
|
||||||
|
CycleResult::Finished
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
define_ld_reg_deref!(b, hl);
|
||||||
|
define_ld_reg_deref!(c, hl);
|
||||||
|
define_ld_reg_deref!(d, hl);
|
||||||
|
define_ld_reg_deref!(e, hl);
|
||||||
|
define_ld_reg_deref!(h, hl);
|
||||||
|
define_ld_reg_deref!(l, hl);
|
||||||
|
define_ld_reg_deref!(a, hl);
|
||||||
|
define_ld_reg_deref!(a, bc);
|
||||||
|
define_ld_reg_deref!(a, de);
|
||||||
|
|
||||||
|
pub fn ld_hl_minus_a(state: &mut Gameboy) -> CycleResult {
|
||||||
|
match state.registers.cycle {
|
||||||
|
0 => {
|
||||||
|
state.cpu_write_u8(state.registers.get_hl(), state.registers.a);
|
||||||
|
CycleResult::NeedsMore
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let reg = state.registers.get_hl().overflowing_sub(1).0;
|
||||||
|
state.registers.set_hl(reg);
|
||||||
|
state.registers.opcode_len = Some(1);
|
||||||
|
CycleResult::Finished
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ld_a_hl_minus(state: &mut Gameboy) -> CycleResult {
|
||||||
|
match state.registers.cycle {
|
||||||
|
0 => {
|
||||||
|
state.cpu_read_u8(state.registers.get_hl());
|
||||||
|
CycleResult::NeedsMore
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
state.registers.a = state.registers.take_mem();
|
||||||
|
let reg = state.registers.get_hl().overflowing_sub(1).0;
|
||||||
|
state.registers.set_hl(reg);
|
||||||
|
state.registers.opcode_len = Some(1);
|
||||||
|
CycleResult::Finished
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ld_hl_plus_a(state: &mut Gameboy) -> CycleResult {
|
||||||
|
match state.registers.cycle {
|
||||||
|
0 => {
|
||||||
|
state.cpu_write_u8(state.registers.get_hl(), state.registers.a);
|
||||||
|
CycleResult::NeedsMore
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let reg = state.registers.get_hl().overflowing_add(1).0;
|
||||||
|
state.registers.set_hl(reg);
|
||||||
|
state.registers.opcode_len = Some(1);
|
||||||
|
CycleResult::Finished
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ld_a_hl_plus(state: &mut Gameboy) -> CycleResult {
|
||||||
|
match state.registers.cycle {
|
||||||
|
0 => {
|
||||||
|
state.cpu_read_u8(state.registers.get_hl());
|
||||||
|
CycleResult::NeedsMore
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
state.registers.a = state.registers.take_mem();
|
||||||
|
let reg = state.registers.get_hl().overflowing_add(1).0;
|
||||||
|
state.registers.set_hl(reg);
|
||||||
|
state.registers.opcode_len = Some(1);
|
||||||
|
CycleResult::Finished
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue