From 9d55eb6c344c57e6c21cc8934b53b4cdb138b315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com> Date: Mon, 24 Apr 2023 17:41:23 +0100 Subject: [PATCH] feat: initial support for config --- src/cpu.rs | 10 ++- src/gb.rs | 174 +++++++++++++++++++++++++++++++++++++++++----------- src/inst.rs | 25 ++++++++ src/mmu.rs | 21 ++++++- 4 files changed, 190 insertions(+), 40 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index ba115c2e..2466a71f 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -1,8 +1,10 @@ use core::panic; +use std::{cell::RefCell, rc::Rc}; use crate::{ apu::Apu, debugln, + gb::GameBoyConfig, inst::{EXTENDED, INSTRUCTIONS}, mmu::Mmu, pad::Pad, @@ -38,10 +40,15 @@ pub struct Cpu { /// Temporary counter used to control the number of cycles /// taken by the current or last CPU operation. pub cycles: u8, + + /// The pointer to the parent configuration of the running + /// Game Boy emulator, that can be used to control the behaviour + /// of Game Boy emulation. + gbc: Rc<RefCell<GameBoyConfig>>, } impl Cpu { - pub fn new(mmu: Mmu) -> Self { + pub fn new(mmu: Mmu, gbc: Rc<RefCell<GameBoyConfig>>) -> Self { Self { pc: 0x0, sp: 0x0, @@ -60,6 +67,7 @@ impl Cpu { halted: false, mmu, cycles: 0, + gbc: gbc, } } diff --git a/src/gb.rs b/src/gb.rs index 4c43caa9..6e36863e 100644 --- a/src/gb.rs +++ b/src/gb.rs @@ -13,7 +13,7 @@ use crate::{ util::read_file, }; -use std::collections::VecDeque; +use std::{cell::RefCell, collections::VecDeque, rc::Rc}; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; @@ -32,28 +32,22 @@ use std::{ /// Enumeration that describes the multiple running // modes of the Game Boy emulator. +#[cfg_attr(feature = "wasm", wasm_bindgen)] +#[derive(Clone, Copy, PartialEq, Eq)] pub enum GBMode { Dmg = 1, Cgb = 2, Sgb = 3, } -/// Top level structure that abstracts the usage of the -/// Game Boy system under the Boytacean emulator. -/// Should serve as the main entry-point API. #[cfg_attr(feature = "wasm", wasm_bindgen)] -pub struct GameBoy { +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct GameBoyConfig { /// The current running mode of the emulator, this /// may affect many aspects of the emulation, like /// CPU frequency, PPU frequency, Boot rome size, etc. mode: GBMode, - /// Reference to the Game Boy CPU component to be - /// used as the main element of the system, when - /// clocked, the amount of ticks from it will be - /// used as reference or the rest of the components. - cpu: Cpu, - /// If the PPU is enabled, it will be clocked. ppu_enabled: bool, @@ -75,6 +69,98 @@ pub struct GameBoy { clock_freq: u32, } +#[cfg_attr(feature = "wasm", wasm_bindgen)] +impl GameBoyConfig { + #[inline(always)] + pub fn is_dmg(&self) -> bool { + self.mode == GBMode::Dmg + } + + #[inline(always)] + pub fn is_cgb(&self) -> bool { + self.mode == GBMode::Cgb + } + + #[inline(always)] + pub fn is_sgb(&self) -> bool { + self.mode == GBMode::Sgb + } + + #[inline(always)] + pub fn mode(&self) -> GBMode { + self.mode + } + + #[inline(always)] + pub fn set_mode(&mut self, value: GBMode) { + self.mode = value; + } + + #[inline(always)] + pub fn ppu_enabled(&self) -> bool { + self.ppu_enabled + } + + #[inline(always)] + pub fn set_ppu_enabled(&mut self, value: bool) { + self.ppu_enabled = value; + } + + #[inline(always)] + pub fn apu_enabled(&self) -> bool { + self.apu_enabled + } + + #[inline(always)] + pub fn set_apu_enabled(&mut self, value: bool) { + self.apu_enabled = value; + } + + #[inline(always)] + pub fn timer_enabled(&self) -> bool { + self.timer_enabled + } + + #[inline(always)] + pub fn set_timer_enabled(&mut self, value: bool) { + self.timer_enabled = value; + } + + #[inline(always)] + pub fn serial_enabled(&self) -> bool { + self.serial_enabled + } + + #[inline(always)] + pub fn set_serial_enabled(&mut self, value: bool) { + self.serial_enabled = value; + } + + #[inline(always)] + pub fn clock_freq(&self) -> u32 { + self.clock_freq + } + + #[inline(always)] + pub fn set_clock_freq(&mut self, value: u32) { + self.clock_freq = value; + } +} + +/// Top level structure that abstracts the usage of the +/// Game Boy system under the Boytacean emulator. +/// Should serve as the main entry-point API. +#[cfg_attr(feature = "wasm", wasm_bindgen)] +pub struct GameBoy { + /// Reference to the Game Boy CPU component to be + /// used as the main element of the system, when + /// clocked, the amount of ticks from it will be + /// used as reference or the rest of the components. + cpu: Cpu, + + gbc: Rc<RefCell<GameBoyConfig>>, +} + #[cfg_attr(feature = "wasm", wasm_bindgen)] pub struct Registers { pub pc: u16, @@ -104,22 +190,24 @@ pub trait AudioProvider { impl GameBoy { #[cfg_attr(feature = "wasm", wasm_bindgen(constructor))] pub fn new(mode: GBMode) -> Self { - let ppu = Ppu::default(); - let apu = Apu::default(); - let pad = Pad::default(); - let timer = Timer::default(); - let serial = Serial::default(); - let mmu = Mmu::new(ppu, apu, pad, timer, serial); - let cpu = Cpu::new(mmu); - Self { + let gbc = Rc::new(RefCell::new(GameBoyConfig { mode, - cpu, ppu_enabled: true, apu_enabled: true, timer_enabled: true, serial_enabled: true, clock_freq: GameBoy::CPU_FREQ, - } + })); + + let ppu = Ppu::default(); + let apu = Apu::default(); + let pad = Pad::default(); + let timer = Timer::default(); + let serial = Serial::default(); + let mmu = Mmu::new(ppu, apu, pad, timer, serial, gbc.clone()); + let cpu = Cpu::new(mmu, gbc.clone()); + + Self { cpu, gbc } } pub fn reset(&mut self) { @@ -133,16 +221,16 @@ impl GameBoy { pub fn clock(&mut self) -> u8 { let cycles = self.cpu_clock(); - if self.ppu_enabled { + if self.ppu_enabled() { self.ppu_clock(cycles); } - if self.apu_enabled { + if self.apu_enabled() { self.apu_clock(cycles); } - if self.timer_enabled { + if self.timer_enabled() { self.timer_clock(cycles); } - if self.serial_enabled { + if self.serial_enabled() { self.serial_clock(cycles); } cycles @@ -193,7 +281,7 @@ impl GameBoy { } pub fn load(&mut self, boot: bool) { - match self.mode { + match self.mode() { GBMode::Dmg => self.load_dmg(boot), GBMode::Cgb => self.load_cgb(boot), GBMode::Sgb => todo!(), @@ -357,44 +445,58 @@ impl GameBoy { String::from(COMPILATION_TIME) } + #[inline(always)] + pub fn mode(&self) -> GBMode { + (*self.gbc).borrow().mode() + } + + pub fn set_mode(&mut self, value: GBMode) { + (*self.gbc).borrow_mut().set_mode(value); + } + + #[inline(always)] pub fn ppu_enabled(&self) -> bool { - self.ppu_enabled + (*self.gbc).borrow().ppu_enabled() } pub fn set_ppu_enabled(&mut self, value: bool) { - self.ppu_enabled = value; + (*self.gbc).borrow_mut().set_ppu_enabled(value); } + #[inline(always)] pub fn apu_enabled(&self) -> bool { - self.apu_enabled + (*self.gbc).borrow().apu_enabled } pub fn set_apu_enabled(&mut self, value: bool) { - self.apu_enabled = value; + (*self.gbc).borrow_mut().set_apu_enabled(value); } + #[inline(always)] pub fn timer_enabled(&self) -> bool { - self.timer_enabled + (*self.gbc).borrow().timer_enabled() } pub fn set_timer_enabled(&mut self, value: bool) { - self.timer_enabled = value; + (*self.gbc).borrow_mut().set_timer_enabled(value); } + #[inline(always)] pub fn serial_enabled(&self) -> bool { - self.serial_enabled + (*self.gbc).borrow().serial_enabled() } pub fn set_serial_enabled(&mut self, value: bool) { - self.serial_enabled = value; + (*self.gbc).borrow_mut().set_serial_enabled(value); } + #[inline(always)] pub fn clock_freq(&self) -> u32 { - self.clock_freq + (*self.gbc).borrow().clock_freq() } pub fn set_clock_freq(&mut self, value: u32) { - self.clock_freq = value; + (*self.gbc).borrow_mut().set_clock_freq(value); self.apu().set_clock_freq(value); } diff --git a/src/inst.rs b/src/inst.rs index 8b059a59..9c58eff3 100644 --- a/src/inst.rs +++ b/src/inst.rs @@ -3119,11 +3119,13 @@ fn set_7_a(cpu: &mut Cpu) { } /// Helper function to set one bit in a u8. +#[inline(always)] fn set(value: u8, bit: u8) -> u8 { value | (1u8 << (bit as usize)) } /// Helper function to clear one bit in a u8. +#[inline(always)] fn res(value: u8, bit: u8) -> u8 { value & !(1u8 << (bit as usize)) } @@ -3131,6 +3133,7 @@ fn res(value: u8, bit: u8) -> u8 { /// Helper function that rotates (shifts) left the given /// byte (probably from a register) and updates the /// proper flag registers. +#[inline(always)] fn rl(cpu: &mut Cpu, value: u8) -> u8 { let carry = cpu.carry(); @@ -3145,6 +3148,7 @@ fn rl(cpu: &mut Cpu, value: u8) -> u8 { result } +#[inline(always)] fn rlc(cpu: &mut Cpu, value: u8) -> u8 { cpu.set_carry((value & 0x80) == 0x80); @@ -3160,6 +3164,7 @@ fn rlc(cpu: &mut Cpu, value: u8) -> u8 { /// Helper function that rotates (shifts) right the given /// byte (probably from a register) and updates the /// proper flag registers. +#[inline(always)] fn rr(cpu: &mut Cpu, value: u8) -> u8 { let carry = cpu.carry(); @@ -3174,6 +3179,7 @@ fn rr(cpu: &mut Cpu, value: u8) -> u8 { result } +#[inline(always)] fn rrc(cpu: &mut Cpu, value: u8) -> u8 { cpu.set_carry((value & 0x1) == 0x1); @@ -3188,52 +3194,61 @@ fn rrc(cpu: &mut Cpu, value: u8) -> u8 { /// Helper function to test one bit in a u8. /// Returns true if bit is 0. +#[inline(always)] fn bit_zero(val: u8, bit: u8) -> bool { (val & (1u8 << (bit as usize))) == 0 } +#[inline(always)] fn bit_a(cpu: &mut Cpu, bit: u8) { cpu.set_sub(false); cpu.set_zero(bit_zero(cpu.a, bit)); cpu.set_half_carry(true); } +#[inline(always)] fn bit_b(cpu: &mut Cpu, bit: u8) { cpu.set_sub(false); cpu.set_zero(bit_zero(cpu.b, bit)); cpu.set_half_carry(true); } +#[inline(always)] fn bit_c(cpu: &mut Cpu, bit: u8) { cpu.set_sub(false); cpu.set_zero(bit_zero(cpu.c, bit)); cpu.set_half_carry(true); } +#[inline(always)] fn bit_d(cpu: &mut Cpu, bit: u8) { cpu.set_sub(false); cpu.set_zero(bit_zero(cpu.d, bit)); cpu.set_half_carry(true); } +#[inline(always)] fn bit_e(cpu: &mut Cpu, bit: u8) { cpu.set_sub(false); cpu.set_zero(bit_zero(cpu.e, bit)); cpu.set_half_carry(true); } +#[inline(always)] fn bit_h(cpu: &mut Cpu, bit: u8) { cpu.set_sub(false); cpu.set_zero(bit_zero(cpu.h, bit)); cpu.set_half_carry(true); } +#[inline(always)] fn bit_l(cpu: &mut Cpu, bit: u8) { cpu.set_sub(false); cpu.set_zero(bit_zero(cpu.l, bit)); cpu.set_half_carry(true); } +#[inline(always)] fn bit_mhl(cpu: &mut Cpu, bit: u8) { let byte = cpu.mmu.read(cpu.hl()); cpu.set_sub(false); @@ -3241,6 +3256,7 @@ fn bit_mhl(cpu: &mut Cpu, bit: u8) { cpu.set_half_carry(true); } +#[inline(always)] fn add_set_flags(cpu: &mut Cpu, first: u8, second: u8) -> u8 { let first = first as u32; let second = second as u32; @@ -3256,6 +3272,7 @@ fn add_set_flags(cpu: &mut Cpu, first: u8, second: u8) -> u8 { result_b } +#[inline(always)] fn add_carry_set_flags(cpu: &mut Cpu, first: u8, second: u8) -> u8 { let first = first as u32; let second = second as u32; @@ -3272,6 +3289,7 @@ fn add_carry_set_flags(cpu: &mut Cpu, first: u8, second: u8) -> u8 { result_b } +#[inline(always)] fn sub_set_flags(cpu: &mut Cpu, first: u8, second: u8) -> u8 { let first = first as u32; let second = second as u32; @@ -3287,6 +3305,7 @@ fn sub_set_flags(cpu: &mut Cpu, first: u8, second: u8) -> u8 { result_b } +#[inline(always)] fn sub_carry_set_flags(cpu: &mut Cpu, first: u8, second: u8) -> u8 { let first = first as u32; let second = second as u32; @@ -3303,6 +3322,7 @@ fn sub_carry_set_flags(cpu: &mut Cpu, first: u8, second: u8) -> u8 { result_b } +#[inline(always)] fn add_u16_u16(cpu: &mut Cpu, first: u16, second: u16) -> u16 { let first = first as u32; let second = second as u32; @@ -3315,6 +3335,7 @@ fn add_u16_u16(cpu: &mut Cpu, first: u16, second: u16) -> u16 { result as u16 } +#[inline(always)] fn swap(cpu: &mut Cpu, value: u8) -> u8 { cpu.set_sub(false); cpu.set_zero(value == 0); @@ -3326,6 +3347,7 @@ fn swap(cpu: &mut Cpu, value: u8) -> u8 { /// Helper function to shift an `u8` to the left and update CPU /// flags. +#[inline(always)] fn sla(cpu: &mut Cpu, value: u8) -> u8 { let result = value << 1; @@ -3337,6 +3359,7 @@ fn sla(cpu: &mut Cpu, value: u8) -> u8 { result } +#[inline(always)] fn sra(cpu: &mut Cpu, value: u8) -> u8 { let result = (value >> 1) | (value & 0x80); @@ -3348,6 +3371,7 @@ fn sra(cpu: &mut Cpu, value: u8) -> u8 { result } +#[inline(always)] fn srl(cpu: &mut Cpu, value: u8) -> u8 { let result = value >> 1; @@ -3362,6 +3386,7 @@ fn srl(cpu: &mut Cpu, value: u8) -> u8 { /// Helper function for RST instructions, pushes the /// current PC to the stack and jumps to the provided /// address. +#[inline(always)] fn rst(cpu: &mut Cpu, addr: u16) { cpu.push_word(cpu.pc); cpu.pc = addr; diff --git a/src/mmu.rs b/src/mmu.rs index c659df2a..ff011776 100644 --- a/src/mmu.rs +++ b/src/mmu.rs @@ -1,4 +1,9 @@ -use crate::{apu::Apu, debugln, pad::Pad, ppu::Ppu, rom::Cartridge, serial::Serial, timer::Timer}; +use std::{cell::RefCell, rc::Rc}; + +use crate::{ + apu::Apu, debugln, gb::GameBoyConfig, pad::Pad, ppu::Ppu, rom::Cartridge, serial::Serial, + timer::Timer, +}; pub const BOOT_SIZE_DMG: usize = 256; pub const BOOT_SIZE_CGB: usize = 2304; @@ -55,14 +60,23 @@ pub struct Mmu { ram: Vec<u8>, /// The RAM bank to be used in the read and write operation of - /// the 0xD000-0xDFFF memory range. CGB Only + /// the 0xD000-0xDFFF memory range (CGB Only). ram_bank: u8, ram_offset: u16, + + gbc: Rc<RefCell<GameBoyConfig>>, } impl Mmu { - pub fn new(ppu: Ppu, apu: Apu, pad: Pad, timer: Timer, serial: Serial) -> Self { + pub fn new( + ppu: Ppu, + apu: Apu, + pad: Pad, + timer: Timer, + serial: Serial, + gbc: Rc<RefCell<GameBoyConfig>>, + ) -> Self { Self { ppu, apu, @@ -76,6 +90,7 @@ impl Mmu { ram_bank: 0x1, ram_offset: 0x1000, ie: 0x0, + gbc: gbc, } } -- GitLab