use crate::gb::bus::DmgBus;
use crate::gb::bus::GbBus;
use crate::gb::cpu::Sm83;
pub mod gameboy;
pub struct Gb<B: GbBus> {
pub cpu: Sm83<B>,
}
impl<B: GbBus> Gb<B> {
pub fn new(bus: B) -> Self {
Self {
cpu: Sm83::new(bus),
}
}
pub fn step(&mut self) -> u8 {
let before = self.cpu.cycles();
self.cpu.execute();
(self.cpu.cycles() - before) as u8
}
pub fn cycles(&self) -> u64 {
self.cpu.cycles()
}
}
impl Gb<DmgBus> {
pub fn reset(&mut self, soft_reset: bool) {
if soft_reset {
self.cpu.reset_registers();
} else {
self.cpu.reset_registers();
self.cpu.regs.pc = 0x0000;
self.cpu.bus.reset();
}
}
}
impl Gb<DmgBus> {
pub fn screen_snapshot(&self) -> Vec<u8> {
self.cpu.bus.ppu.screen_buffer().snapshot()
}
pub fn is_frame_ready(&self) -> bool {
self.cpu.bus.ppu.is_frame_ready()
}
pub fn clear_frame_ready(&mut self) {
self.cpu.bus.ppu.clear_frame_ready();
}
pub fn screen_crc32(&self) -> u32 {
self.cpu.bus.ppu.screen_buffer().crc32()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gb::cartridge::load_cartridge;
fn minimal_cart() -> Box<dyn crate::gb::cartridge::GbCartridge> {
let mut rom = vec![0u8; 0x8000];
rom[0x0147] = 0x00; rom[0x0148] = 0x00; rom[0x0149] = 0x00; let chk = rom[0x0134..=0x014C]
.iter()
.fold(0u8, |acc, &b| acc.wrapping_sub(b).wrapping_sub(1));
rom[0x014D] = chk;
load_cartridge(&rom).expect("valid ROM")
}
fn make_dmg() -> Gb<DmgBus> {
Gb::new(DmgBus::new(minimal_cart()))
}
#[test]
fn test_reset_registers_restores_pc_to_0100() {
let mut gb = make_dmg();
assert_eq!(gb.cpu.regs.pc, 0x0000);
gb.cpu.reset_registers();
assert_eq!(gb.cpu.regs.pc, 0x0100);
}
#[test]
fn test_reset_registers_sets_dmg_af() {
let mut gb = make_dmg();
gb.cpu.reset_registers();
assert_eq!(gb.cpu.regs.af(), 0x01B0);
}
#[test]
fn test_reset_registers_sets_dmg_bc_de_hl_sp() {
let mut gb = make_dmg();
gb.cpu.reset_registers();
assert_eq!(gb.cpu.regs.bc(), 0x0013);
assert_eq!(gb.cpu.regs.de(), 0x00D8);
assert_eq!(gb.cpu.regs.hl(), 0x014D);
assert_eq!(gb.cpu.regs.sp, 0xFFFE);
}
#[test]
fn test_reset_registers_clears_ime_and_halted() {
let mut gb = make_dmg();
gb.cpu.ime = true;
gb.cpu.halted = true;
gb.cpu.reset_registers();
assert!(!gb.cpu.ime);
assert!(!gb.cpu.halted);
}
#[test]
fn test_soft_reset_preserves_wram() {
let mut gb = make_dmg();
gb.cpu.bus.write(0xC100, 0xAB);
gb.reset(true);
assert_eq!(gb.cpu.bus.read(0xC100), 0xAB);
}
#[test]
fn test_hard_reset_clears_wram() {
let mut gb = make_dmg();
gb.cpu.bus.write(0xC100, 0xAB);
gb.reset(false);
assert_eq!(gb.cpu.bus.read(0xC100), 0x00);
}
#[test]
fn test_hard_reset_restores_pc_and_clears_wram() {
let mut gb = make_dmg();
gb.cpu.bus.write(0xC000, 0xFF);
gb.reset(false);
assert_eq!(gb.cpu.regs.pc, 0x0000);
assert_eq!(gb.cpu.bus.read(0xC000), 0x00);
}
#[test]
fn test_soft_reset_restores_pc() {
let mut gb = make_dmg();
gb.reset(true);
assert_eq!(gb.cpu.regs.pc, 0x0100);
}
struct TrackingBus {
mem: [u8; 0x10000],
ticked_cycles: u64,
}
impl TrackingBus {
fn with_program(program: &[u8]) -> Self {
let mut mem = [0u8; 0x10000];
mem[..program.len()].copy_from_slice(program);
Self {
mem,
ticked_cycles: 0,
}
}
}
impl GbBus for TrackingBus {
fn read(&mut self, addr: u16) -> u8 {
self.mem[addr as usize]
}
fn write(&mut self, addr: u16, val: u8) {
self.mem[addr as usize] = val;
}
fn tick(&mut self, m_cycles: u8) {
self.ticked_cycles += m_cycles as u64;
}
}
#[test]
fn test_step_ticks_bus_by_nop_m_cycle_count() {
let bus = TrackingBus::with_program(&[0x00]); let mut console = Gb::new(bus);
let before = console.cpu.cycles();
console.step();
let delta = console.cpu.cycles() - before;
assert_eq!(delta, 1);
assert_eq!(console.cpu.bus.ticked_cycles, delta);
}
#[test]
fn test_step_ticks_bus_by_multi_cycle_instruction_cost() {
let bus = TrackingBus::with_program(&[0x01, 0x00, 0x00]); let mut console = Gb::new(bus);
console.step();
assert_eq!(console.cpu.bus.ticked_cycles, 3);
}
}