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(
configs.emu_config.clone(),
configs.config_dir.clone(),
window,
Some(window),
prepared.rom,
output,
)

View file

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

View file

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

View file

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

View file

@ -115,7 +115,7 @@ where
ColourFormat: From<Colour> + Copy,
C: PocketCamera + Send + 'static,
{
window: Sender<RendererMessage<ColourFormat>>,
window: Option<Sender<RendererMessage<ColourFormat>>>,
audio: AudioOutput,
serial_target: SerialTarget,
tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
@ -130,7 +130,7 @@ where
C: PocketCamera + Send + 'static,
{
pub(crate) fn new(
window: Sender<RendererMessage<ColourFormat>>,
window: Option<Sender<RendererMessage<ColourFormat>>>,
audio: AudioOutput,
serial_target: SerialTarget,
tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
@ -253,12 +253,13 @@ where
self.rom.set(address, data);
if self.rom.can_rumble() {
// rumble
self.gpu
.window
.send(RendererMessage::Rumble {
rumble: self.rom.is_rumbling(),
})
.expect("message error");
if let Some(window) = &self.gpu.window {
window
.send(RendererMessage::Rumble {
rumble: self.rom.is_rumbling(),
})
.expect("message error");
}
}
}
Address::Vram(address) => self.gpu.set_vram(address, data),
@ -462,14 +463,19 @@ where
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);
self.memory
.interrupts
.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
.interrupts

View file

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

View file

@ -33,6 +33,7 @@ where
should_halt_bug: bool,
pub(super) cycle_count: usize,
pub(crate) is_skipping: bool,
pub(crate) no_output: bool,
pub(super) next_joypad_state: Option<JoypadState>,
}
@ -41,7 +42,7 @@ where
ColourFormat: From<Colour> + Copy,
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 {
memory,
reg: Registers::init(),
@ -51,6 +52,7 @@ where
should_halt_bug: false,
cycle_count: 0,
is_skipping: !run_bootrom,
no_output,
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,
})
}