diff --git a/.gitignore b/.gitignore index 335122a..a877ce0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target /roms/ -config.toml \ No newline at end of file +config.toml +/.vscode/ \ No newline at end of file diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..870bbe4 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +stable \ No newline at end of file diff --git a/src/gameboy.rs b/src/gameboy.rs index 25c26e1..1873ecf 100644 --- a/src/gameboy.rs +++ b/src/gameboy.rs @@ -1,3 +1,4 @@ +mod cpu; mod interrupts; mod joypad; mod mapper; @@ -12,6 +13,8 @@ use memory::Memory; use ppu::Ppu; use timer::Timer; +use self::cpu::Registers; + pub struct DmaState { pub base: u8, pub remaining_cycles: u8, @@ -36,6 +39,7 @@ pub struct Gameboy { cartridge: Option>, interrupts: Interrupts, timer: Timer, + pub registers: Registers, pub joypad: Joypad, pub dma: DmaState, } @@ -50,6 +54,7 @@ impl Gameboy { joypad: Joypad::new(), dma: DmaState::new(), ppu: Ppu::new(), + registers: Registers::default(), } } @@ -58,14 +63,12 @@ impl Gameboy { self.interrupts.write_if_timer(true); } - self.tick_cpu(); + cpu::tick_cpu(self); let redraw_requested = self.ppu.tick(&mut self.interrupts); self.tick_dma(); redraw_requested } - fn tick_cpu(&mut self) {} - fn tick_dma(&mut self) { if self.dma.remaining_delay > 0 { self.dma.remaining_delay -= 1; @@ -166,8 +169,11 @@ impl Gameboy { } } - pub fn cpu_read_u8(&self, address: u16) -> u8 { - if self.dma.remaining_cycles == 0 { + pub fn cpu_read_u8(&mut self, address: u16) { + 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 { 0..=0xFF if !self.memory.bootrom_disabled => self.memory.bootrom[address as usize], 0..=0x7FFF => match self.cartridge.as_ref() { @@ -194,10 +200,12 @@ impl Gameboy { 0xFF80..=0xFFFE => self.memory.hram[address as usize - 0xFF80], 0xFFFF => self.interrupts.interrupt_enable, } - } + }) } 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 { match address { 0..=0xFF if !self.memory.bootrom_disabled => {} diff --git a/src/gameboy/cpu.rs b/src/gameboy/cpu.rs new file mode 100644 index 0000000..39a5456 --- /dev/null +++ b/src/gameboy/cpu.rs @@ -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 [](&self) -> u16 { + (self.$lident as u16) << 8 | self.$rident as u16 + } + + pub fn [](&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 [](&self) -> bool { + self.f >> $bit == 1 + } + + pub fn [](&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, + pub opcode_len: Option, + pub current_opcode: Option, + pub mem_read_hold: Option, + 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; + } +} diff --git a/src/gameboy/cpu/alu.rs b/src/gameboy/cpu/alu.rs new file mode 100644 index 0000000..461e883 --- /dev/null +++ b/src/gameboy/cpu/alu.rs @@ -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 [](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 [](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!(), + } +} diff --git a/src/gameboy/cpu/load_store_move.rs b/src/gameboy/cpu/load_store_move.rs new file mode 100644 index 0000000..0310dd6 --- /dev/null +++ b/src/gameboy/cpu/load_store_move.rs @@ -0,0 +1,254 @@ +use crate::gameboy::{cpu::CycleResult, Gameboy}; + +macro_rules! define_ld_reg_imm_u16 { + ($reg:ident) => { + paste::paste! { + pub fn [](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.[](); + reg &= 0xFF00; + reg |= state.registers.take_mem() as u16; + state.registers.[](reg); + state.cpu_read_u8(state.registers.pc.overflowing_add(2).0); + CycleResult::NeedsMore + }, + 2 => { + let mut reg = state.registers.[](); + reg &= 0xFF; + reg |= (state.registers.take_mem() as u16) << 8; + state.registers.[](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 [](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 [](state: &mut Gameboy) -> CycleResult { + match state.registers.cycle { + 0 => { + state.cpu_read_u8(state.registers.[]()); + 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!(), + } +}