Skip to content

Commit

Permalink
core: test and fix that reset_after_boot is correct
Browse files Browse the repository at this point in the history
When a boot is not provided, the GameBoy call `reset_after_boot` that
reset the GameBoy to the state where it should have been after finishing
running the boot rom.

Adds a test that checks if this is really happening (and fix it, because
it wasn't).

To run this test, a dump of the GameBoy boot rom must be provided.
  • Loading branch information
Rodrigodd committed Feb 12, 2024
1 parent f99dfa1 commit 329b20a
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 16 deletions.
Binary file modified core/after_boot/ppu.sav
Binary file not shown.
8 changes: 5 additions & 3 deletions core/src/gameboy.rs
Expand Up @@ -99,12 +99,14 @@ impl std::fmt::Debug for GameBoy {
impl Eq for GameBoy {}
impl PartialEq for GameBoy {
fn eq(&self, other: &Self) -> bool {
self.update_all();

// self.trace == other.trace &&
self.cpu == other.cpu
&& self.cartridge == other.cartridge
&& self.wram == other.wram
&& self.hram == other.hram
&& self.boot_rom == other.boot_rom
// && self.boot_rom == other.boot_rom
&& self.boot_rom_active == other.boot_rom_active
&& self.clock_count == other.clock_count
&& self.timer == other.timer
Expand Down Expand Up @@ -161,7 +163,7 @@ impl GameBoy {
ppu: Ppu::default().into(),

joypad: 0xFF,
joypad_io: 0x00,
joypad_io: 0xCF,
serial: Serial::new().into(),
interrupt_flag: 0.into(),
dma: 0xff,
Expand Down Expand Up @@ -227,7 +229,7 @@ impl GameBoy {
self.sound = RefCell::new(SoundController::default());
self.ppu = Ppu::default().into();
self.joypad = 0xFF;
self.joypad_io = 0x00;
self.joypad_io = 0xCF;

self.next_interrupt = 0.into();
self.update_next_interrupt();
Expand Down
26 changes: 17 additions & 9 deletions core/src/gameboy/ppu.rs
Expand Up @@ -613,7 +613,11 @@ impl Ppu {
oam.load_state(ctx, &mut ppu_state).unwrap();
oam
},
dma_started: 0x7fff_ffff_ffff_ffff,
dma_started: {
let mut dma_started = 0x7fff_ffff_ffff_ffff;
dma_started.load_state(ctx, &mut ppu_state).unwrap();
dma_started
},
dma_running: false,
dma_block_oam: false,
oam_read_block: false,
Expand All @@ -627,7 +631,7 @@ impl Ppu {
},
sprite_buffer: [Sprite::default(); 10],
sprite_buffer_len: 0,
wyc: 0,
wyc: 0xFF,
lcdc: 0x91,
stat: 0x05,
scy: 0,
Expand All @@ -643,15 +647,19 @@ impl Ppu {
ly_for_compare: 0,

last_clock_count: 23_440_324,
next_clock_count: 23_440_377,
line_start_clock_count: 23_435_361,
next_interrupt: 23_440_324,
next_clock_count: 23_440_376,
line_start_clock_count: 23_435_360,
next_interrupt: 0,

background_fifo: PixelFifo::default(),
background_fifo: PixelFifo {
queue: [0; 16],
head: 0,
tail: 8,
},
sprite_fifo: PixelFifo::default(),

fetcher_step: 0x03,
fetcher_x: 0x14,
fetcher_step: 0,
fetcher_x: 0,
fetch_tile_number: 0,
fetch_tile_data_low: 0,
fetch_tile_data_hight: 0,
Expand All @@ -673,7 +681,7 @@ impl Ppu {
wx_just_changed: false,

screen_x: 0xa0,
scanline_x: 0x00,
scanline_x: 0xA0,
}
}
pub fn write(gb: &mut GameBoy, address: u8, value: u8) {
Expand Down
4 changes: 2 additions & 2 deletions core/src/gameboy/sound_controller.rs
Expand Up @@ -316,8 +316,8 @@ impl SoundController {
std::mem::take(&mut self.output)
}

/// Emulator the sound controller until to the currently `clock_count`, since the `clock_count` of
/// the last update.
/// Emulate the sound controller until to the currently `clock_count`, since the `clock_count`
/// of the last update.
pub fn update(&mut self, clock_count: u64) {
debug_assert!(clock_count >= self.last_clock_count);
if clock_count <= self.last_clock_count {
Expand Down
4 changes: 2 additions & 2 deletions core/src/gameboy/timer.rs
Expand Up @@ -82,10 +82,10 @@ impl Timer {
/// Return the state of timer just after the boot finished.
pub fn after_boot(clock_count: u64) -> Self {
Timer {
div: 0xabcc,
div: 0xabac,
tima: 0x00,
tma: 0x00,
tac: 0xf8,
tac: 0x00,
last_counter_bit: false,
last_clock_count: clock_count,
loading: 0,
Expand Down
134 changes: 134 additions & 0 deletions core/tests/check_boot_state.rs
@@ -0,0 +1,134 @@
use gameroy::{
gameboy::{cartridge::Cartridge, GameBoy},
interpreter::Interpreter,
};

// const BOOT_ROM: Option<[u8; 256]> = Some(*include_bytes!("../../boot/dmg_boot.bin"));
const BOOT_ROM: Option<[u8; 256]> = None;

#[test]
#[ignore]
fn test_boot_state() {
assert!(
BOOT_ROM.is_some(),
"Set the BOOT_ROM to a reference DMG boot rom"
);

let cartridge = Cartridge::halt_filled();

let mut a = GameBoy::new(BOOT_ROM, cartridge.clone());
let b = GameBoy::new(None, cartridge.clone());

while a.cpu.pc < 0x100 {
Interpreter(&mut a).interpret_op();
}

assert_eq!(a.cpu.pc, b.cpu.pc);

let mut state_a = Vec::new();
a.save_state(None, &mut state_a).unwrap();

let mut state_b = Vec::new();
b.save_state(None, &mut state_b).unwrap();

if a != b {
assert!(assert_gb_eq(&a, &b));

print_diff_mem(&a, &b);

print_diff_state(&state_a, &state_b);

panic!("States at end of boot are different");
}
}

fn print_diff_mem(a: &GameBoy, b: &GameBoy) {
println!("Address space diff:");

let mut is_different = false;
for i in (0..0xFFFF).step_by(8) {
let row_a = (0..8).map(|j| a.read(i + j)).collect::<Vec<_>>();
let row_b = (0..8).map(|j| b.read(i + j)).collect::<Vec<_>>();

if row_a != row_b {
is_different = true;
println!(" {:04x?}: {:02x?}", i, &row_a);
println!(" | {:02x?}", &row_b);
}
}
if !is_different {
println!(" No differences found");
}
}

fn print_diff_state(a: &[u8], b: &[u8]) {
assert_eq!(a.len(), b.len());

println!("State diff:");

for i in (0..a.len()).step_by(8) {
let j = if i + 8 < a.len() { i + 8 } else { a.len() };
if a[i..j] != b[i..j] {
println!(" {:04x?}: {:02x?}", i, &a[i..i + 8]);
println!(" | {:02x?}", &b[i..i + 8]);
}
}

if a == b {
println!(" No differences found");
}
}

fn assert_gb_eq(a: &GameBoy, b: &GameBoy) -> bool {
if a != b {
println!();
println!();
if a.cpu != b.cpu {
println!("cpu don't match: {:?}", a.cpu);
println!(" {:?}", b.cpu);
}
if a.cartridge != b.cartridge {
println!("cartridge don't match")
}
if a.wram != b.wram {
println!("wram don't match")
}
if a.hram != b.hram {
println!("hram don't match")
}
if a.boot_rom_active != b.boot_rom_active {
println!("boot_rom_active don't match")
}
if a.clock_count != b.clock_count {
println!("clock_count don't match")
}
if a.timer != b.timer {
println!("timer don't match: {:?}", a.timer);
println!(" {:?}", b.timer);
}
if a.sound != b.sound {
println!("sound don't match: {:?}", a.sound);
println!(" {:?}", b.sound);
}
if a.ppu != b.ppu {
println!("ppu don't match: {:?}", a.ppu);
println!(" {:?}", b.ppu);
}
if a.joypad != b.joypad {
println!("joypad don't match: {:02x}", a.joypad);
println!(" {:02x}", b.joypad);
}
if a.joypad_io != b.joypad_io {
println!("joypad_io don't match: {:02x}", a.joypad_io);
println!(" {:02x}", b.joypad_io);
}
// let mut vec = Vec::new();
// a.save_state(&mut vec);
// std::fs::write("gameboy_a.dump.bin", &vec);
// vec.clear();
// b.save_state(&mut vec);
// std::fs::write("gameboy_b.dump.bin", vec);
return false;
}
true
}
1 change: 1 addition & 0 deletions core/tests/test_rom.rs
Expand Up @@ -18,6 +18,7 @@ const TEST_ROM_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/gameboy-
/// The test run faster when not providing a boot rom (the emulator just skip to the state at the
/// end of the boot rom execution), but it may be useful to include the boot rom execution in the
/// test to check if a boot rom changes any behavior and it's a valid replacement for the original.
// const BOOT_ROM: Option<[u8; 256]> = Some(*include_bytes!("../../boot/dmg_boot.bin"));
const BOOT_ROM: Option<[u8; 256]> = None;

macro_rules! screen {
Expand Down

0 comments on commit 329b20a

Please sign in to comment.