diff --git a/frontends/sdl/src/main.rs b/frontends/sdl/src/main.rs index c4137fda3bd93ae8eba5e5453b7331db0370959d..5ab6c7fcecbeb4497522fc92449d2031783c8b20 100644 --- a/frontends/sdl/src/main.rs +++ b/frontends/sdl/src/main.rs @@ -484,6 +484,8 @@ impl Emulator { } pub fn run_headless(&mut self, allowed_cycles: Option<u64>) { + let allowed_cycles = allowed_cycles.unwrap_or(u64::MAX); + // starts the variable that will control the number of cycles that // are going to move (because of overflow) from one tick to another let mut pending_cycles = 0u32; @@ -540,7 +542,7 @@ impl Emulator { // fot the current tick an in case the total number of cycles // exceeds the allowed cycles then the loop is broken total_cycles += cycle_limit as u64; - if total_cycles >= allowed_cycles.unwrap_or(u64::MAX) { + if total_cycles >= allowed_cycles { break; } @@ -635,7 +637,7 @@ fn main() { // creates a new Game Boy instance and loads both the boot ROM // and the initial game ROM to "start the engine" - let mut game_boy = GameBoy::new(mode); + let mut game_boy = GameBoy::new(Some(mode)); let device = build_device(&args.device); game_boy.set_ppu_enabled(!args.no_ppu); game_boy.set_apu_enabled(!args.no_apu); @@ -663,6 +665,10 @@ fn main() { emulator.start(SCREEN_SCALE); emulator.load_rom(Some(&args.rom_path)); emulator.toggle_palette(); + + // determines if the emulator should run in headless mode or + // not and runs it accordingly, note that if running in headless + // mode the number of cycles to be run may be specified if args.headless { emulator.run_headless(if args.cycles > 0 { Some(args.cycles) diff --git a/src/devices/buffer.rs b/src/devices/buffer.rs new file mode 100644 index 0000000000000000000000000000000000000000..cf5707b71d5c296b6c58ea75e3235ac9fe2499d3 --- /dev/null +++ b/src/devices/buffer.rs @@ -0,0 +1,62 @@ +use crate::serial::SerialDevice; + +use std::fmt::{self, Display, Formatter}; + +pub struct BufferDevice { + buffer: Vec<u8>, + callback: fn(image_buffer: &Vec<u8>), +} + +impl BufferDevice { + pub fn new() -> Self { + Self { + buffer: Vec::new(), + callback: |_| {}, + } + } + + pub fn set_callback(&mut self, callback: fn(image_buffer: &Vec<u8>)) { + self.callback = callback; + } + + pub fn buffer(&self) -> &Vec<u8> { + &self.buffer + } +} + +impl SerialDevice for BufferDevice { + fn send(&mut self) -> u8 { + 0xff + } + + fn receive(&mut self, byte: u8) { + self.buffer.push(byte); + let data = vec![byte]; + (self.callback)(&data); + } + + fn allow_slave(&self) -> bool { + false + } + + fn description(&self) -> String { + String::from("Buffer") + } + + fn state(&self) -> String { + let buffer = self.buffer.clone(); + String::from_utf8(buffer).unwrap() + } +} + +impl Default for BufferDevice { + fn default() -> Self { + Self::new() + } +} + +impl Display for BufferDevice { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Buffer") + } +} diff --git a/src/devices/mod.rs b/src/devices/mod.rs index ada19f9138884470af7746f6fa424080b2601f8a..339782b8cb892670253dea631cc9615038ff3bf1 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -1,2 +1,3 @@ +pub mod buffer; pub mod printer; pub mod stdout; diff --git a/src/devices/printer.rs b/src/devices/printer.rs index cf0161a8c90b319f39c6aa0fbe30e3210333b50d..dade2045f059a174201655d06af1322109ca3d07 100644 --- a/src/devices/printer.rs +++ b/src/devices/printer.rs @@ -316,6 +316,10 @@ impl SerialDevice for PrinterDevice { fn description(&self) -> String { format!("Printer [{}]", self.command) } + + fn state(&self) -> String { + self.command.to_string() + } } impl Default for PrinterDevice { diff --git a/src/devices/stdout.rs b/src/devices/stdout.rs index 8a13eda05802a8246799935e050b8539263965dc..75dbcd27af02c480b82d454b25f887e2cd0316f3 100644 --- a/src/devices/stdout.rs +++ b/src/devices/stdout.rs @@ -7,7 +7,7 @@ use std::{ pub struct StdoutDevice { flush: bool, - callback: fn(image_buffer: &Vec<u8>), + callback: fn(buffer: &Vec<u8>), } impl StdoutDevice { @@ -18,7 +18,7 @@ impl StdoutDevice { } } - pub fn set_callback(&mut self, callback: fn(image_buffer: &Vec<u8>)) { + pub fn set_callback(&mut self, callback: fn(buffer: &Vec<u8>)) { self.callback = callback; } } @@ -44,6 +44,10 @@ impl SerialDevice for StdoutDevice { fn description(&self) -> String { String::from("Stdout") } + + fn state(&self) -> String { + String::from("") + } } impl Default for StdoutDevice { diff --git a/src/gb.rs b/src/gb.rs index 073fa08c99cd90eaf86a0e1e026fdb11dfecb50a..66c37c44ab0f421a9c49c36675ce53cfee813ba7 100644 --- a/src/gb.rs +++ b/src/gb.rs @@ -346,7 +346,8 @@ pub struct GameBoy { #[cfg_attr(feature = "wasm", wasm_bindgen)] impl GameBoy { #[cfg_attr(feature = "wasm", wasm_bindgen(constructor))] - pub fn new(mode: GameBoyMode) -> Self { + pub fn new(mode: Option<GameBoyMode>) -> Self { + let mode = mode.unwrap_or(GameBoyMode::Dmg); let gbc = Rc::new(RefCell::new(GameBoyConfig { mode, ppu_enabled: true, @@ -1052,7 +1053,7 @@ impl AudioProvider for GameBoy { impl Default for GameBoy { fn default() -> Self { - Self::new(GameBoyMode::Dmg) + Self::new(None) } } diff --git a/src/lib.rs b/src/lib.rs index d25f5ac6f5082c43cfa6f03b76f2f88425e7bef6..0f8c9a49a05ec691e4c9fcacd0bc53de1e681123 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,5 +14,6 @@ pub mod pad; pub mod ppu; pub mod rom; pub mod serial; +pub mod test; pub mod timer; pub mod util; diff --git a/src/serial.rs b/src/serial.rs index a8f522b77356aed07ad664fa41f1276f8d13094e..a9bd6205435c22187694d291eda6d56ffe1c9f8b 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -1,7 +1,11 @@ use crate::warnln; pub trait SerialDevice { + /// Sends a byte (u8) to the attached serial connection. fn send(&mut self) -> u8; + + /// Receives a byte (u8) from the attached serial connection, + /// can be either another device or the host. fn receive(&mut self, byte: u8); /// Whether the serial device "driver" supports slave mode @@ -11,6 +15,10 @@ pub trait SerialDevice { /// Returns a short description of the serial device. fn description(&self) -> String; + + /// Returns a string describing the current state of the + /// serial device. Could be used for debugging purposes. + fn state(&self) -> String; } pub struct Serial { @@ -222,6 +230,10 @@ impl SerialDevice for NullDevice { fn description(&self) -> String { String::from("Null") } + + fn state(&self) -> String { + String::from("") + } } impl Default for NullDevice { diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000000000000000000000000000000000000..39dc1dbab4a8901e77a4782470796f175e71c11e --- /dev/null +++ b/src/test.rs @@ -0,0 +1,53 @@ +use crate::{devices::buffer::BufferDevice, gb::GameBoy}; + +#[derive(Default)] +pub struct TestOptions { + ppu_enabled: Option<bool>, + apu_enabled: Option<bool>, + dma_enabled: Option<bool>, + timer_enabled: Option<bool>, +} + +pub fn build_test(options: TestOptions) -> GameBoy { + let device = Box::<BufferDevice>::default(); + let mut game_boy = GameBoy::new(None); + game_boy.set_ppu_enabled(options.ppu_enabled.unwrap_or(true)); + game_boy.set_apu_enabled(options.apu_enabled.unwrap_or(true)); + game_boy.set_dma_enabled(options.dma_enabled.unwrap_or(true)); + game_boy.set_timer_enabled(options.timer_enabled.unwrap_or(true)); + game_boy.attach_serial(device); + game_boy.load(true); + game_boy +} + +pub fn run_test(rom_path: &str, max_cycles: Option<u64>, options: TestOptions) -> String { + let mut cycles = 0u64; + let max_cycles = max_cycles.unwrap_or(u64::MAX); + + let mut game_boy = build_test(options); + game_boy.load_rom_file(rom_path); + + loop { + cycles += game_boy.clock() as u64; + if cycles >= max_cycles { + break; + } + } + + game_boy.serial().device().state() +} + +#[cfg(test)] +mod tests { + use super::{run_test, TestOptions}; + + #[test] + fn test_blargg_cpu_instrs() { + let result = run_test( + "res/roms/test/blargg/cpu/cpu_instrs.gb", + Some(300000000), + TestOptions::default(), + ); + assert_eq!(result, "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\n"); + } +}