refactor + blarg tests

This commit is contained in:
Alex Janka 2023-12-21 13:01:38 +11:00
parent 55af282e7e
commit 9c2cbbd05a
9 changed files with 175 additions and 39 deletions

View file

@ -200,7 +200,7 @@ where
let emulator_options = EmulatorOptions::new_with_config( let emulator_options = EmulatorOptions::new_with_config(
configs.emu_config.clone(), configs.emu_config.clone(),
configs.config_dir.clone(), configs.config_dir.clone(),
window, Some(window),
prepared.rom, prepared.rom,
output, output,
) )

View file

@ -315,7 +315,7 @@ impl Plugin for GameboyEmu {
let options = EmulatorOptions::new_with_config( let options = EmulatorOptions::new_with_config(
configs.emu_config.clone(), configs.emu_config.clone(),
configs.config_dir.clone(), configs.config_dir.clone(),
emu_sender, Some(emu_sender),
rom, rom,
output, output,
) )

View file

@ -231,7 +231,7 @@ where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub(crate) window: Sender<RendererMessage<ColourFormat>>, pub(crate) window: Option<Sender<RendererMessage<ColourFormat>>>,
pub(crate) tile_window: Option<Sender<RendererMessage<ColourFormat>>>, pub(crate) tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
pub(crate) layer_window: Option<Sender<RendererMessage<ColourFormat>>>, pub(crate) layer_window: Option<Sender<RendererMessage<ColourFormat>>>,
pub(crate) rom: Rom<C>, pub(crate) rom: Rom<C>,
@ -241,6 +241,7 @@ where
pub(crate) dmg_bootrom: Option<RomFile>, pub(crate) dmg_bootrom: Option<RomFile>,
pub(crate) cgb_bootrom: Option<RomFile>, pub(crate) cgb_bootrom: Option<RomFile>,
pub(crate) show_bootrom: bool, pub(crate) show_bootrom: bool,
pub(crate) no_output: bool,
pub(crate) serial_target: SerialTarget, pub(crate) serial_target: SerialTarget,
pub(crate) cgb_mode: bool, pub(crate) cgb_mode: bool,
_spooky: PhantomData<ColourFormat>, _spooky: PhantomData<ColourFormat>,
@ -252,7 +253,7 @@ where
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub fn new( pub fn new(
window: Sender<RendererMessage<ColourFormat>>, window: Option<Sender<RendererMessage<ColourFormat>>>,
rom: Rom<C>, rom: Rom<C>,
output: AudioOutput, output: AudioOutput,
) -> Self { ) -> Self {
@ -266,6 +267,7 @@ where
dmg_bootrom: None, dmg_bootrom: None,
cgb_bootrom: None, cgb_bootrom: None,
show_bootrom: false, show_bootrom: false,
no_output: false,
serial_target: SerialTarget::None, serial_target: SerialTarget::None,
cgb_mode: true, cgb_mode: true,
_spooky: PhantomData, _spooky: PhantomData,
@ -276,7 +278,7 @@ where
pub fn new_with_config( pub fn new_with_config(
config: crate::config::Config, config: crate::config::Config,
config_dir: PathBuf, config_dir: PathBuf,
window: Sender<RendererMessage<ColourFormat>>, window: Option<Sender<RendererMessage<ColourFormat>>>,
rom: Rom<C>, rom: Rom<C>,
output: AudioOutput, output: AudioOutput,
) -> Self { ) -> Self {
@ -296,6 +298,7 @@ where
.map(|v| config_dir.join(v)) .map(|v| config_dir.join(v))
.map(RomFile::Path), .map(RomFile::Path),
show_bootrom: config.show_bootrom, show_bootrom: config.show_bootrom,
no_output: false,
serial_target: SerialTarget::None, serial_target: SerialTarget::None,
cgb_mode: config.prefer_cgb, cgb_mode: config.prefer_cgb,
_spooky: Default::default(), _spooky: Default::default(),
@ -322,6 +325,11 @@ where
self self
} }
pub fn with_no_output(mut self, no_output: bool) -> Self {
self.no_output = no_output;
self
}
pub fn with_stdout(mut self) -> Self { pub fn with_stdout(mut self) -> Self {
self.serial_target = SerialTarget::Stdout(StdoutType::Ascii); self.serial_target = SerialTarget::Stdout(StdoutType::Ascii);
self self

View file

@ -70,15 +70,14 @@ where
.load_data() .load_data()
.expect("Error loading bootrom!"); .expect("Error loading bootrom!");
options if let Some(window) = &options.window {
.window window
.send(connect::RendererMessage::Prepare { .send(connect::RendererMessage::Prepare {
width: WIDTH, width: WIDTH,
height: HEIGHT, height: HEIGHT,
}) })
.expect("message error"); .expect("message error");
options window
.window
.send(connect::RendererMessage::SetTitle { .send(connect::RendererMessage::SetTitle {
title: format!( title: format!(
"{} on {} on {}", "{} on {} on {}",
@ -88,6 +87,7 @@ where
), ),
}) })
.expect("message error"); .expect("message error");
}
Self::new( Self::new(
paused, paused,
@ -107,6 +107,7 @@ where
), ),
), ),
options.show_bootrom, options.show_bootrom,
options.no_output,
), ),
) )
} }

View file

@ -115,7 +115,7 @@ where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
window: Sender<RendererMessage<ColourFormat>>, window: Option<Sender<RendererMessage<ColourFormat>>>,
audio: AudioOutput, audio: AudioOutput,
serial_target: SerialTarget, serial_target: SerialTarget,
tile_window: Option<Sender<RendererMessage<ColourFormat>>>, tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
@ -130,7 +130,7 @@ where
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub(crate) fn new( pub(crate) fn new(
window: Sender<RendererMessage<ColourFormat>>, window: Option<Sender<RendererMessage<ColourFormat>>>,
audio: AudioOutput, audio: AudioOutput,
serial_target: SerialTarget, serial_target: SerialTarget,
tile_window: Option<Sender<RendererMessage<ColourFormat>>>, tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
@ -253,14 +253,15 @@ where
self.rom.set(address, data); self.rom.set(address, data);
if self.rom.can_rumble() { if self.rom.can_rumble() {
// rumble // rumble
self.gpu if let Some(window) = &self.gpu.window {
.window window
.send(RendererMessage::Rumble { .send(RendererMessage::Rumble {
rumble: self.rom.is_rumbling(), rumble: self.rom.is_rumbling(),
}) })
.expect("message error"); .expect("message error");
} }
} }
}
Address::Vram(address) => self.gpu.set_vram(address, data), Address::Vram(address) => self.gpu.set_vram(address, data),
Address::CartRam(address) => self.rom.set_ram(address, data), Address::CartRam(address) => self.rom.set_ram(address, data),
Address::WorkRam(address) => self.ram.bank_0[address.get_local() as usize] = data, Address::WorkRam(address) => self.ram.bank_0[address.get_local() as usize] = data,
@ -462,14 +463,19 @@ where
self.memory.interrupts.set_interrupt(Interrupt::Timer, true); self.memory.interrupts.set_interrupt(Interrupt::Timer, true);
} }
self.memory.apu.tick(logical_steps, !self.is_skipping); self.memory
.apu
.tick(logical_steps, !(self.is_skipping || self.no_output));
let serial_interrupt = self.memory.serial.tick(steps, self.memory.ime); let serial_interrupt = self.memory.serial.tick(steps, self.memory.ime);
self.memory self.memory
.interrupts .interrupts
.set_interrupt(Interrupt::Serial, serial_interrupt); .set_interrupt(Interrupt::Serial, serial_interrupt);
let gpu_interrupts = self.memory.gpu.tick(logical_steps, !self.is_skipping); let gpu_interrupts = self
.memory
.gpu
.tick(logical_steps, !(self.is_skipping || self.no_output));
self.memory self.memory
.interrupts .interrupts

View file

@ -55,7 +55,7 @@ where
pub buffer: Buffer<ColourFormat, { WIDTH * HEIGHT }>, pub buffer: Buffer<ColourFormat, { WIDTH * HEIGHT }>,
pub vram: Vram, pub vram: Vram,
pub oam: Oam, pub oam: Oam,
pub window: Sender<RendererMessage<ColourFormat>>, pub window: Option<Sender<RendererMessage<ColourFormat>>>,
is_bg_zero: [bool; WIDTH], is_bg_zero: [bool; WIDTH],
is_bg_priority: [bool; WIDTH], is_bg_priority: [bool; WIDTH],
lcdc: Lcdc, lcdc: Lcdc,
@ -84,7 +84,7 @@ where
{ {
pub fn new( pub fn new(
cgb: bool, cgb: bool,
window: Sender<RendererMessage<ColourFormat>>, window: Option<Sender<RendererMessage<ColourFormat>>>,
tile_window_renderer: Option<Sender<RendererMessage<ColourFormat>>>, tile_window_renderer: Option<Sender<RendererMessage<ColourFormat>>>,
layer_window_renderer: Option<Sender<RendererMessage<ColourFormat>>>, layer_window_renderer: Option<Sender<RendererMessage<ColourFormat>>>,
) -> Self { ) -> Self {
@ -513,9 +513,11 @@ where
fn render_window(&mut self) { fn render_window(&mut self) {
let mut buffer = Vec::new(); let mut buffer = Vec::new();
buffer.extend_from_slice(self.buffer.as_ref()); buffer.extend_from_slice(self.buffer.as_ref());
self.window if let Some(window) = &self.window {
window
.send(RendererMessage::display_message(buffer)) .send(RendererMessage::display_message(buffer))
.expect("message error"); .expect("message error");
}
self.tile_window = self self.tile_window = self
.tile_window .tile_window

View file

@ -33,6 +33,7 @@ where
should_halt_bug: bool, should_halt_bug: bool,
pub(super) cycle_count: usize, pub(super) cycle_count: usize,
pub(crate) is_skipping: bool, pub(crate) is_skipping: bool,
pub(crate) no_output: bool,
pub(super) next_joypad_state: Option<JoypadState>, pub(super) next_joypad_state: Option<JoypadState>,
} }
@ -41,7 +42,7 @@ where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub(crate) fn new(memory: Memory<ColourFormat, C>, run_bootrom: bool) -> Self { pub(crate) fn new(memory: Memory<ColourFormat, C>, run_bootrom: bool, no_output: bool) -> Self {
Self { Self {
memory, memory,
reg: Registers::init(), reg: Registers::init(),
@ -51,6 +52,7 @@ where
should_halt_bug: false, should_halt_bug: false,
cycle_count: 0, cycle_count: 0,
is_skipping: !run_bootrom, is_skipping: !run_bootrom,
no_output,
next_joypad_state: None, next_joypad_state: None,
} }
} }

64
lib/tests/blargg.rs Normal file
View file

@ -0,0 +1,64 @@
use std::time::{Duration, Instant};
use common::emulator_setup;
use gb_emu_lib::connect::{EmulatorCoreTrait, RomFile};
mod common;
#[test]
fn cpu_instrs() -> Result<(), String> {
run_blargg_test("cpu_instrs\n\n01:ok 02:ok 03:ok 04:ok 05:ok 06:ok 07:ok 08:ok 09:ok 10:ok 11:ok \n\nPassed all tests", include_bytes!(
"../../test-roms/blargg/cpu_instrs/cpu_instrs.gb"
),None)
}
#[test]
fn instr_timing() -> Result<(), String> {
run_blargg_test(
"instr_timing\n\n\nPassed",
include_bytes!("../../test-roms/blargg/instr_timing/instr_timing.gb"),
Some(vec!["Failed"]),
)
}
#[test]
fn mem_timing() -> Result<(), String> {
run_blargg_test(
"mem_timing\n\n\nPassed",
include_bytes!("../../test-roms/blargg/mem_timing/mem_timing.gb"),
None,
)
}
fn run_blargg_test<const N: usize>(
correct_output: &str,
rom: &[u8; N],
extra_end: Option<Vec<&str>>,
) -> Result<(), String> {
let mut emu = emulator_setup(RomFile::Raw(rom.to_vec()))?;
let mut end_strings = extra_end.unwrap_or_default();
end_strings.append(&mut vec![".", "tests"]);
let mut chars = String::new();
let began = Instant::now();
let timeout = Duration::from_secs(60);
'outer: loop {
emu.core.run(100);
while let Ok(v) = emu.serial_rx.try_recv() {
chars.push(v as char);
if end_strings.iter().any(|end| chars.ends_with(end)) {
break 'outer;
}
}
if began.elapsed() > timeout {
return Err(format!("Test timed out: output was {chars}"));
}
}
assert_eq!(correct_output, chars);
Ok(())
}

53
lib/tests/common/mod.rs Normal file
View file

@ -0,0 +1,53 @@
use std::sync::mpsc::{channel, Receiver, Sender};
use async_ringbuf::AsyncHeapConsumer;
use gb_emu_lib::{
connect::{AudioOutput, EmulatorMessage, EmulatorOptions, NoCamera, RomFile},
EmulatorCore,
};
pub struct TestEmulator {
pub core: EmulatorCore<[u8; 4], NoCamera>,
pub sender: Sender<EmulatorMessage<[u8; 4]>>,
pub audio_rx: AsyncHeapConsumer<[f32; 2]>,
pub serial_rx: Receiver<u8>,
}
pub fn emulator_setup(rom_file: RomFile) -> Result<TestEmulator, String> {
let (sender, receiver) = channel::<EmulatorMessage<[u8; 4]>>();
let (rom, camera) = rom_file
.load(gb_emu_lib::connect::SramType::None, NoCamera::default())
.map_err(|_e| String::from("Error reading ROM: {_e:?}"))?;
let (audio_output, audio_rx) = AudioOutput::new(
48000.,
1,
gb_emu_lib::connect::DownsampleType::ZeroOrderHold,
);
// let (tx_to_gameboy, rx_from_test) = channel::<u8>();
let (tx_to_test, serial_rx) = channel::<u8>();
let options = EmulatorOptions::new(None, rom, audio_output)
.with_no_output(true)
.with_serial_target(gb_emu_lib::connect::SerialTarget::Custom {
rx: None,
tx: Some(tx_to_test),
})
.with_cgb_bootrom(Some(RomFile::Raw(
include_bytes!("../../../sameboy-bootroms/cgb_boot.bin").to_vec(),
)))
.with_dmg_bootrom(Some(RomFile::Raw(
include_bytes!("../../../sameboy-bootroms/dmg_boot.bin").to_vec(),
)));
let core = EmulatorCore::init(true, receiver, options, camera);
sender
.send(EmulatorMessage::Start)
.map_err(|_e| String::from("Error sending message: {_e:?}"))?;
Ok(TestEmulator {
core,
sender,
audio_rx,
serial_rx,
})
}