use crate::gb::bus::CgbBus;
use crate::gb::bus::DmgBus;
use crate::gb::bus::GbBus;
use crate::gb::cpu::Sm83;
#[cfg(test)]
use crate::gb::model::DmgModel;
use std::collections::VecDeque;
const MAX_CPU_TRACE_LINES: usize = 512;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CpuTraceLine {
pub addr: u16,
pub bytes: Vec<u8>,
pub text: String,
}
pub struct Gb<B: GbBus> {
pub cpu: Sm83<B>,
recent_trace: VecDeque<CpuTraceLine>,
cpu_trace_enabled: bool,
}
impl<B: GbBus> Gb<B> {
pub fn new(bus: B) -> Self {
Self {
cpu: Sm83::new(bus),
recent_trace: VecDeque::with_capacity(MAX_CPU_TRACE_LINES),
cpu_trace_enabled: false,
}
}
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()
}
pub fn set_cpu_trace_enabled(&mut self, enabled: bool) {
self.cpu_trace_enabled = enabled;
if !enabled {
self.recent_trace.clear();
}
}
pub fn cpu_trace_enabled(&self) -> bool {
self.cpu_trace_enabled
}
pub fn recent_cpu_trace(&self, limit: usize) -> Vec<CpuTraceLine> {
if limit == 0 || self.recent_trace.is_empty() {
return Vec::new();
}
let start = self.recent_trace.len().saturating_sub(limit);
self.recent_trace.iter().skip(start).cloned().collect()
}
pub fn push_cpu_trace_line(&mut self, line: CpuTraceLine) {
if self.recent_trace.len() == MAX_CPU_TRACE_LINES {
self.recent_trace.pop_front();
}
self.recent_trace.push_back(line);
}
pub fn read_for_debugger(&self, addr: u16) -> u8 {
self.cpu.bus.read_for_debugger(addr)
}
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_mut().clear_frame_ready();
}
pub(crate) fn reconcile_stop_display_after_state_load(&mut self) {
if self.cpu.stopped
&& self.cpu.bus.ppu().stop_display_mode() == crate::gb::ppu::StopDisplayMode::Inactive
{
self.cpu.bus.enter_stop_mode();
} else if !self.cpu.stopped {
self.cpu.bus.exit_stop_mode();
}
}
}
impl Gb<DmgBus> {
pub fn reset(&mut self) {
self.cpu.reset_to_power_on();
self.cpu.bus.reset();
}
pub fn screen_snapshot(&self) -> Vec<u8> {
self.cpu.bus.ppu.screen_buffer().snapshot()
}
pub fn screen_crc32(&self) -> u32 {
self.cpu.bus.ppu.screen_buffer().crc32()
}
}
impl Gb<CgbBus> {
pub fn reset(&mut self, soft_reset: bool) {
if !soft_reset {
self.cpu.bus.reset();
if self.cpu.bus.is_boot_rom_active() {
self.cpu.reset_to_power_on();
self.cpu.regs.pc = 0x0000;
} else {
self.cpu.reset_registers_cgb();
self.cpu.regs.pc = 0x0100;
}
} else {
self.cpu.reset_registers_cgb();
}
}
pub fn screen_snapshot(&self) -> Vec<u8> {
self.cpu.bus.ppu.screen_buffer().snapshot()
}
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(), DmgModel::DmgB))
}
#[test]
fn test_reset_starts_at_boot_rom_entry() {
let mut gb = make_dmg();
gb.step();
assert_ne!(gb.cpu.regs.pc, 0x0000);
gb.reset();
assert_eq!(gb.cpu.regs.pc, 0x0000);
}
#[test]
fn test_reset_clears_cpu_state() {
let mut gb = make_dmg();
gb.cpu.ime = true;
gb.cpu.halted = true;
gb.reset();
assert!(!gb.cpu.ime);
assert!(!gb.cpu.halted);
}
#[test]
fn test_reset_clears_wram() {
let mut gb = make_dmg();
gb.cpu.bus.write(0xC100, 0xAB);
gb.reset();
assert_eq!(gb.cpu.bus.read(0xC100), 0x00);
}
#[test]
fn test_reset_restores_boot_rom() {
let mut gb = make_dmg();
gb.reset();
assert_eq!(gb.cpu.regs.pc, 0x0000);
assert!(gb.cpu.bus.is_boot_rom_active());
}
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);
}
}