diff --git a/Cargo.toml b/Cargo.toml index ece2496..929a112 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,9 @@ required-features = ["gui"] sdl2 = { version = "0.37.0", optional = true } [features] -gui = ["sdl2"] +default = ["std", "gui"] +std = [] +gui = ["std", "sdl2"] # Easiest to bundle on windows since theres not a super easy way to install [target.'cfg(windows)'.dependencies] diff --git a/src/emulator.rs b/src/emulator.rs index 0830440..89b86f3 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -1,9 +1,3 @@ -use std::{ - sync::{Arc, Mutex}, - time::{Duration, Instant}, -}; - -use crate::debug_println; use crate::types::{GameInput, KeyState}; mod cpu; @@ -24,7 +18,6 @@ pub struct Emulator { screen: graphics::Screen, cpu: cpu::CPU, joypad: joypad::Joypad, - memory: mem::SharedMemory, paused: bool, } @@ -40,11 +33,6 @@ impl Emulator { /// This matches the approximate number of cycles a Game Boy runs in one /// video frame. The update loop runs until this budget is reached. const MAXCYCLES: u32 = 69905; - /// Target duration for a single frame (approx. 59.7 Hz). - /// - /// The update loop sleeps to align with this duration after processing - /// enough CPU cycles. - const FRAME_DURATION: Duration = Duration::from_nanos(16_741_000); /// Create a new emulator instance with initialized subsystems. /// @@ -53,40 +41,29 @@ impl Emulator { /// /// Returns a ready-to-use `Emulator` in the paused state. pub fn new() -> Self { - let mem = Arc::new(Mutex::new(mem::Memory::new())); - mem.lock().unwrap().ram_startup(); - Emulator { - screen: graphics::Screen::new(Arc::clone(&mem)), - cpu: cpu::CPU::new(Arc::clone(&mem)), - joypad: joypad::Joypad::new(Arc::clone(&mem)), - memory: mem, - paused: true, - } + let mut cpu = cpu::CPU::new(); + cpu.memory_mut().ram_startup(); + Emulator { screen: graphics::Screen::new(), cpu, joypad: joypad::Joypad::new(), paused: true } } /// Execute one frame of emulation if not paused. /// /// Runs CPU instructions, updates timers and graphics, and handles - /// interrupts until the frame's cycle budget is consumed. The function then - /// sleeps to maintain the target frame rate. + /// interrupts until the frame's cycle budget is consumed. /// /// Returns `()` and has no effect if the emulator is paused. pub fn update(&mut self) { if self.paused { return; } - let frame_start = Instant::now(); let mut num_cycles: u32 = 0; while num_cycles < Self::MAXCYCLES { let cycles = self.cpu.execute_next_opcode(false); num_cycles += cycles as u32; - self.cpu.timers.update_timers(cycles as i32); - self.screen.update_screen(cycles as i32); + self.cpu.update_timers(cycles as i32); + self.screen.update_screen(self.cpu.memory_mut(), cycles as i32); self.cpu.handle_interrupts(); } - if let Some(remaining) = Self::FRAME_DURATION.checked_sub(frame_start.elapsed()) { - std::thread::sleep(remaining); - } } /// Toggle the paused state. @@ -107,6 +84,16 @@ impl Emulator { self.paused } + /// Load ROM data and reset CPU/memory state. + pub fn load_rom_data(&mut self, data: &[u8]) { + let mem = self.cpu.memory_mut(); + mem.load_rom_data(data); + mem.ram_startup(); + self.cpu.reset(); + self.paused = false; + } + + #[cfg(feature = "std")] /// Load a ROM from disk and reset the CPU and memory state. /// /// The ROM contents are copied into memory, memory is reinitialized, and @@ -116,14 +103,9 @@ impl Emulator { /// - `path`: filesystem path to the ROM file. /// /// Returns `Ok(())` on success or a string I/O error on failure. - pub fn load_rom(&mut self, path: &str) -> Result<(), String> { + pub fn load_rom(&mut self, path: &str) -> Result<(), std::string::String> { let data = std::fs::read(path).map_err(|e| e.to_string())?; - let mut mem = self.memory.lock().unwrap(); - mem.load_rom_data(&data); - mem.ram_startup(); - self.cpu.reset(); - - self.paused = false; + self.load_rom_data(&data); Ok(()) } @@ -137,37 +119,28 @@ impl Emulator { &self.screen.buffer } - /// Dump key LCD and input registers for debugging. - /// - /// This is only compiled in debug builds and logs directly to stdout via - /// `debug_println`. If not compiled for debug this function does nothing. - /// - /// Takes `&self` and returns `()`. All details are dumped to the console pub fn dump_lcd_mem(&self) { - #[cfg(debug_assertions)] - let mem = self.memory.lock().unwrap(); - - debug_println!("IDK: {:X}", mem.read_byte_forced(0xFF26)); - debug_println!( - "LCD Control: {:X}", - mem.read_byte_forced(crate::types::LCD_CONTROL) - ); - debug_println!("Scroll Y: {:X}", mem.read_byte_forced(0xFF42)); - debug_println!("Scroll X: {:X}", mem.read_byte_forced(0xFF43)); - debug_println!("BG Palette: {:X}", mem.read_byte_forced(0xFF47)); - debug_println!("OBJ palette: {:X}", mem.read_byte_forced(0xFF48)); - debug_println!( - "Current Scanline: {:X}", - mem.read_byte_forced(crate::types::CURRENT_SCANLINE) - ); - debug_println!( - "LCD Control: {:X}", - mem.read_byte_forced(crate::types::LCD_CONTROL) - ); - debug_println!( - "Joystick Register: 0x{:X}", - mem.read_byte_forced(crate::types::INPUT_REGISTER) - ); + #[cfg(feature = "std")] + { + let mem = self.cpu.memory(); + println!("IDK: {:X}", mem.read_byte_forced(0xFF26)); + println!( + "LCD Control: {:X}", + mem.read_byte_forced(crate::types::LCD_CONTROL) + ); + println!("Scroll Y: {:X}", mem.read_byte_forced(0xFF42)); + println!("Scroll X: {:X}", mem.read_byte_forced(0xFF43)); + println!("BG Palette: {:X}", mem.read_byte_forced(0xFF47)); + println!("OBJ palette: {:X}", mem.read_byte_forced(0xFF48)); + println!( + "Current Scanline: {:X}", + mem.read_byte_forced(crate::types::CURRENT_SCANLINE) + ); + println!( + "Joystick Register: 0x{:X}", + mem.read_byte_forced(crate::types::INPUT_REGISTER) + ); + } } /// Handle input for the emulator's joypad. @@ -181,6 +154,6 @@ impl Emulator { /// /// Returns `()`. pub fn game_input(&mut self, input: GameInput, val: KeyState) { - self.joypad.log_input(input, val) + self.joypad.log_input(self.cpu.memory_mut(), input, val) } } diff --git a/src/emulator/cpu.rs b/src/emulator/cpu.rs index bc79236..62ef4cd 100644 --- a/src/emulator/cpu.rs +++ b/src/emulator/cpu.rs @@ -1,4 +1,4 @@ -use crate::emulator::mem::SharedMemory; +use crate::emulator::mem::Memory; use crate::types::{DIVIDER_REGISTER, IE, IF, TIMA, TMA, TMC}; use registers::{ CpuFlag::{C, H, N, Z}, @@ -23,12 +23,12 @@ pub struct CPU { } impl CPU { - pub fn new(mem: SharedMemory) -> CPU { + pub fn new() -> CPU { let registers = Registers::new(); - let timers = Timer::new(mem.clone()); + let timers = Timer::new(); CPU { reg: registers, - mmu: MemoryAdapter::new(mem), + mmu: MemoryAdapter::new(), timers, halted: false, halt_bug: false, @@ -55,6 +55,19 @@ impl CPU { self.handleinterrupt(); } + pub fn update_timers(&mut self, cycles: i32) { + self.timers.update_timers(&mut self.mmu.mem, cycles); + } + + #[cfg(feature = "std")] + pub fn memory(&self) -> &Memory { + &self.mmu.mem + } + + pub fn memory_mut(&mut self) -> &mut Memory { + &mut self.mmu.mem + } + fn docycle(&mut self) -> u32 { self.updateime(); match self.handleinterrupt() { @@ -2533,30 +2546,29 @@ impl CPU { } } -#[derive(Clone)] struct MemoryAdapter { - mem: SharedMemory, + mem: Memory, } impl MemoryAdapter { - fn new(mem: SharedMemory) -> Self { - Self { mem } + fn new() -> Self { + Self { mem: Memory::new() } } fn rb(&self, address: u16) -> u8 { - self.mem.lock().unwrap().read_byte(address) + self.mem.read_byte(address) } - fn wb(&self, address: u16, value: u8) { - self.mem.lock().unwrap().write_byte(address, value); + fn wb(&mut self, address: u16, value: u8) { + self.mem.write_byte(address, value); } fn rw(&self, address: u16) -> u16 { - self.mem.lock().unwrap().read_word(address) + self.mem.read_word(address) } - fn ww(&self, address: u16, value: u16) { - self.mem.lock().unwrap().write_word(address, value); + fn ww(&mut self, address: u16, value: u16) { + self.mem.write_word(address, value); } fn read_ie(&self) -> u8 { @@ -2567,7 +2579,7 @@ impl MemoryAdapter { self.rb(IF) } - fn write_if(&self, value: u8) { + fn write_if(&mut self, value: u8) { self.wb(IF, value); } @@ -2575,23 +2587,18 @@ impl MemoryAdapter { } pub struct Timer { - mem: SharedMemory, divider_counter: u32, } impl Timer { - pub fn new(mem: SharedMemory) -> Self { - Timer { - mem, - divider_counter: 0, - } + pub fn new() -> Self { + Timer { divider_counter: 0 } } - pub fn update_timers(&mut self, cycles: i32) { - self.do_divider_registers(cycles); + pub fn update_timers(&mut self, mem: &mut Memory, cycles: i32) { + self.do_divider_registers(mem, cycles); - if self.is_clock_enabled() { - let mut mem = self.mem.lock().unwrap(); + if Self::is_clock_enabled(mem) { mem.timer_counter -= cycles; if mem.timer_counter <= 0 { @@ -2609,19 +2616,16 @@ impl Timer { } } - fn do_divider_registers(&mut self, cycles: i32) { + fn do_divider_registers(&mut self, mem: &mut Memory, cycles: i32) { self.divider_counter += cycles as u32; if self.divider_counter >= 255 { self.divider_counter = 0; - - let mut mem = self.mem.lock().unwrap(); let divider_register = mem.read_byte_forced(DIVIDER_REGISTER).wrapping_add(1); mem.write_byte_forced(DIVIDER_REGISTER, divider_register); } } - fn is_clock_enabled(&self) -> bool { - let mem = self.mem.lock().unwrap(); + fn is_clock_enabled(mem: &Memory) -> bool { let tmc_reg = mem.read_byte(TMC); tmc_reg & 0x4 != 0 } diff --git a/src/emulator/graphics.rs b/src/emulator/graphics.rs index be5fd06..6abe573 100644 --- a/src/emulator/graphics.rs +++ b/src/emulator/graphics.rs @@ -4,7 +4,6 @@ use crate::types::*; /// Basic implementation and methods for the LCD Screen pub struct Screen { scanline_counter: i32, - device_memory: SharedMemory, // 64KB address space //buffer is of size (h * w * 3) //buffer can be indexed as (h + (w*3)) @@ -12,28 +11,25 @@ pub struct Screen { } impl Screen { - pub fn new(mem: SharedMemory) -> Self { + pub fn new() -> Self { // TODO: Initialize the screen buffer Screen { buffer: [0; (SCREEN_HEIGHT * SCREEN_WIDTH * 3) as usize], scanline_counter: 456, - device_memory: mem, } } - pub fn update_screen(&mut self, cycles: i32) { - self.set_lcd_status(); + pub fn update_screen(&mut self, mem: &mut Memory, cycles: i32) { + self.set_lcd_status(mem); - if self.is_lcd_enabled() { + if self.is_lcd_enabled(mem) { self.scanline_counter -= cycles; } else { // LCD is not enabled so do nothing return; } - let mut mem = self.device_memory.lock().unwrap(); - if self.scanline_counter <= 0 { // Time to move onto the next scanline let scanline = mem.read_byte(CURRENT_SCANLINE).wrapping_add(1); @@ -51,32 +47,26 @@ impl Screen { } // Otherwise we draw the current line else if scanline < 144 { - // We are done with the lock - drop(mem); - self.draw_scanline(); + self.draw_scanline(mem); } } } - fn draw_scanline(&mut self) { - let mem = self.device_memory.lock().unwrap(); + fn draw_scanline(&mut self, mem: &Memory) { let control = mem.read_byte(LCD_CONTROL); - drop(mem); if control & (1 << 7) != 0 { if control & 0x1 != 0 { - self.render_tiles(control); + self.render_tiles(mem, control); } if control & 0x2 != 0 { - self.render_sprites(control); + self.render_sprites(mem, control); } } } - fn render_tiles(&mut self, control: Byte) { - let mem = self.device_memory.lock().unwrap(); - + fn render_tiles(&mut self, mem: &Memory, control: Byte) { let mut unsigned = true; let tile_data = if control & (1 << 4) != 0 { 0x8000 @@ -159,9 +149,7 @@ impl Screen { } } - fn render_sprites(&mut self, control: Byte) { - let mem = self.device_memory.lock().unwrap(); - + fn render_sprites(&mut self, mem: &Memory, control: Byte) { // test if display is enabled if control & 0x2 != 0 { let mut use8x16 = false; @@ -273,10 +261,8 @@ impl Screen { } } - fn set_lcd_status(&mut self) { - let lcd_enabled = self.is_lcd_enabled(); - // Gets lock on memory - let mut mem = self.device_memory.lock().unwrap(); + fn set_lcd_status(&mut self, mem: &mut Memory) { + let lcd_enabled = self.is_lcd_enabled(mem); let mut status = mem.read_byte(LCD_STATUS); @@ -343,9 +329,8 @@ impl Screen { mem.write_byte(LCD_STATUS, status); } - fn is_lcd_enabled(&mut self) -> bool { + fn is_lcd_enabled(&self, mem: &Memory) -> bool { // Check bit 7 of LCD Control register (0xFF40) - let mem = self.device_memory.lock().unwrap(); mem.read_byte(LCD_CONTROL) & (1 << 7) != 0 } @@ -356,47 +341,37 @@ impl Screen { mod test { use super::*; use ntest::timeout; - use std::sync::{Arc, Mutex}; #[test] #[timeout(10)] fn test_is_lcd_enabled() { - let mem = Arc::new(Mutex::new(Memory::new())); - { - let mut m = mem.lock().unwrap(); - m.write_byte(LCD_CONTROL, 0x80); - } - let mut screen = Screen::new(Arc::clone(&mem)); - assert!(screen.is_lcd_enabled()); + let mut mem = Memory::new(); + mem.write_byte(LCD_CONTROL, 0x80); + let screen = Screen::new(); + assert!(screen.is_lcd_enabled(&mem)); - { - let mut m = mem.lock().unwrap(); - m.write_byte(LCD_CONTROL, 0x00); - } - assert!(!screen.is_lcd_enabled()); + mem.write_byte(LCD_CONTROL, 0x00); + assert!(!screen.is_lcd_enabled(&mem)); } #[test] #[timeout(10)] fn test_render_tile_indexing() { - let mem = Arc::new(Mutex::new(Memory::new())); - mem.lock().unwrap().ram_startup(); - let mut screen = Screen::new(Arc::clone(&mem)); - - { - let mut m = mem.lock().unwrap(); - m.write_byte_forced(CURRENT_SCANLINE, 1); - m.write_byte_forced(0xFF42, 0); - m.write_byte_forced(0xFF43, 0); - m.write_byte_forced(0xFF4A, 0); - m.write_byte_forced(0xFF4B, 7); - m.write_byte_forced(0xFF47, 0); - m.write_byte_forced(0x9800, 0); - m.write_byte_forced(0x8000, 0); - m.write_byte_forced(0x8001, 0); - } - - screen.render_tiles(0x31); + let mut mem = Memory::new(); + mem.ram_startup(); + let mut screen = Screen::new(); + + mem.write_byte_forced(CURRENT_SCANLINE, 1); + mem.write_byte_forced(0xFF42, 0); + mem.write_byte_forced(0xFF43, 0); + mem.write_byte_forced(0xFF4A, 0); + mem.write_byte_forced(0xFF4B, 7); + mem.write_byte_forced(0xFF47, 0); + mem.write_byte_forced(0x9800, 0); + mem.write_byte_forced(0x8000, 0); + mem.write_byte_forced(0x8001, 0); + + screen.render_tiles(&mem, 0x31); let correct = (SCREEN_WIDTH as usize) * 3; assert_eq!(screen.buffer[correct], 255); diff --git a/src/emulator/joypad.rs b/src/emulator/joypad.rs index f1cd3cb..818bdc0 100644 --- a/src/emulator/joypad.rs +++ b/src/emulator/joypad.rs @@ -1,13 +1,10 @@ //! Contains all code for interfacing io to the gameboy use crate::types::{GameInput, KeyState}; -use super::mem::SharedMemory; +use super::mem::Memory; #[derive(Default)] pub struct Joypad { - // access to global shared memory - mem: SharedMemory, - // group 1 buttons a: KeyState, b: KeyState, @@ -22,15 +19,12 @@ pub struct Joypad { } impl Joypad { - pub fn new(mem: SharedMemory) -> Self { - Joypad { - mem, - ..Default::default() - } + pub fn new() -> Self { + Joypad { ..Default::default() } } /// Takes the input and affects the associated memory value with what it should be - pub fn log_input(&mut self, input: GameInput, val: KeyState) { + pub fn log_input(&mut self, mem: &mut Memory, input: GameInput, val: KeyState) { match input { // mode 0 buttons GameInput::A => self.a = val, @@ -46,11 +40,11 @@ impl Joypad { _ => (), } - self.write_input_to_mem(); + self.write_input_to_mem(mem); } /// Main hardworking function that does the work to write the joypad state to RAM - fn write_input_to_mem(&mut self) { + fn write_input_to_mem(&mut self, mem: &mut Memory) { let mut buttons = 0; buttons |= self.a as u8; buttons |= (self.b as u8) << 1; @@ -63,7 +57,6 @@ impl Joypad { directions |= (self.up as u8) << 2; directions |= (self.down as u8) << 3; - let mut mem = self.mem.lock().unwrap(); mem.update_joypad_state(buttons, directions); } } @@ -73,57 +66,57 @@ mod tests { use super::*; use crate::types::IF; use crate::{emulator::mem::Memory, types::INPUT_REGISTER}; - use std::sync::{Arc, Mutex}; - - fn setup_joypad(select_bits: u8) -> (Joypad, SharedMemory) { - let mem = Arc::new(Mutex::new(Memory::new())); - mem.lock() - .unwrap() - .write_byte_forced(INPUT_REGISTER, 0xC0 | select_bits | 0x0F); - (Joypad::new(Arc::clone(&mem)), mem) + + fn setup_joypad(mem: &mut Memory, select_bits: u8) -> Joypad { + mem.write_byte_forced(INPUT_REGISTER, 0xC0 | select_bits | 0x0F); + Joypad::new() } #[test] fn buttons_selection_updates_lower_nibble_and_interrupts() { - let (mut joypad, mem) = setup_joypad(0x10); // select buttons - joypad.log_input(GameInput::A, KeyState::Pressed); + let mut mem = Memory::new(); + let mut joypad = setup_joypad(&mut mem, 0x10); // select buttons + joypad.log_input(&mut mem, GameInput::A, KeyState::Pressed); - let value = mem.lock().unwrap().read_byte_forced(INPUT_REGISTER); + let value = mem.read_byte_forced(INPUT_REGISTER); assert_eq!(value, 0xDE); - let if_val = mem.lock().unwrap().read_byte_forced(IF); + let if_val = mem.read_byte_forced(IF); assert_eq!(if_val & (1 << 4), 1 << 4); } #[test] fn directions_selection_updates_lower_nibble() { - let (mut joypad, mem) = setup_joypad(0x20); // select directions - joypad.log_input(GameInput::Up, KeyState::Pressed); + let mut mem = Memory::new(); + let mut joypad = setup_joypad(&mut mem, 0x20); // select directions + joypad.log_input(&mut mem, GameInput::Up, KeyState::Pressed); - let value = mem.lock().unwrap().read_byte_forced(INPUT_REGISTER); + let value = mem.read_byte_forced(INPUT_REGISTER); assert_eq!(value, 0xEB); } #[test] fn no_selection_keeps_low_nibble_high_and_no_interrupt() { - let (mut joypad, mem) = setup_joypad(0x30); // no selection - joypad.log_input(GameInput::A, KeyState::Pressed); + let mut mem = Memory::new(); + let mut joypad = setup_joypad(&mut mem, 0x30); // no selection + joypad.log_input(&mut mem, GameInput::A, KeyState::Pressed); - let value = mem.lock().unwrap().read_byte_forced(INPUT_REGISTER); + let value = mem.read_byte_forced(INPUT_REGISTER); assert_eq!(value, 0xFF); - let if_val = mem.lock().unwrap().read_byte_forced(IF); + let if_val = mem.read_byte_forced(IF); assert_eq!(if_val & (1 << 4), 0); } #[test] fn release_does_not_request_interrupt() { - let (mut joypad, mem) = setup_joypad(0x10); // select buttons - joypad.log_input(GameInput::A, KeyState::Pressed); - mem.lock().unwrap().write_byte_forced(IF, 0x00); + let mut mem = Memory::new(); + let mut joypad = setup_joypad(&mut mem, 0x10); // select buttons + joypad.log_input(&mut mem, GameInput::A, KeyState::Pressed); + mem.write_byte_forced(IF, 0x00); - joypad.log_input(GameInput::A, KeyState::Released); - let if_val = mem.lock().unwrap().read_byte_forced(IF); + joypad.log_input(&mut mem, GameInput::A, KeyState::Released); + let if_val = mem.read_byte_forced(IF); assert_eq!(if_val & (1 << 4), 0); } } diff --git a/src/emulator/mem.rs b/src/emulator/mem.rs index 082af9e..84193d9 100644 --- a/src/emulator/mem.rs +++ b/src/emulator/mem.rs @@ -1,16 +1,12 @@ -use std::{ - ops::BitAnd, - sync::{Arc, Mutex}, -}; +use core::ops::BitAnd; /// Functions and storage for operating on device memory use crate::types::*; -pub type SharedMemory = Arc>; - pub struct Memory { mem: Ram, - rom: Vec, + rom: [Byte; MAX_ROM_SIZE], + rom_len: usize, external_ram: [[Byte; 0x2000]; 4], rom_banking_type: RomBankingType, rom_banks: CurrentRomBank, @@ -33,7 +29,8 @@ impl Memory { pub fn new() -> Self { Memory { mem: [0; MEM_SIZE], - rom: Vec::new(), + rom: [0; MAX_ROM_SIZE], + rom_len: 0, external_ram: [[0; 0x2000]; 4], rom_banking_type: RomBankingType::None, rom_banks: CurrentRomBank::Bank(1), @@ -106,10 +103,12 @@ impl Memory { pub fn write_byte_forced(&mut self, addr: Word, value: Byte) { if addr < 0x8000 { let index = addr as usize; - if self.rom.len() <= index { - self.rom.resize(index + 1, 0); + if index < MAX_ROM_SIZE { + self.rom[index] = value; + if self.rom_len <= index { + self.rom_len = index + 1; + } } - self.rom[index] = value; } else if (0xA000..=0xBFFF).contains(&addr) { let offset = (addr - 0xA000) as usize; let bank = self.ram_banks as usize; @@ -318,8 +317,10 @@ impl Memory { /// Loads the given ROM bytes into memory pub fn load_rom_data(&mut self, data: &[u8]) { self.mem.fill(0); // clear VRAM, WRAM, OAM, I/O mirrors - self.rom.clear(); - self.rom.extend_from_slice(data); + self.rom.fill(0); + let copy_len = core::cmp::min(data.len(), MAX_ROM_SIZE); + self.rom[..copy_len].copy_from_slice(&data[..copy_len]); + self.rom_len = copy_len; self.external_ram = [[0; 0x2000]; 4]; self.rom_banks = CurrentRomBank::Bank(1); @@ -377,15 +378,14 @@ impl Memory { } fn read_rom_byte(&self, index: usize) -> Byte { - if self.rom.is_empty() { + if self.rom_len == 0 { if index < self.mem.len() { self.mem[index] } else { 0 } } else { - let len = self.rom.len(); - let masked = index % len; + let masked = index % self.rom_len; self.rom[masked] } } @@ -775,4 +775,42 @@ mod test { } assert_eq!(mem.read_byte(0x8000), 0); } + + #[test] + #[timeout(10)] + fn test_write_byte_forced_sets_rom_len_and_reads_back() { + let mut mem = Memory::new(); + + mem.write_byte_forced(0x1234, 0x5A); + assert_eq!(mem.rom_len, 0x1235); + assert_eq!(mem.read_byte(0x1234), 0x5A); + + mem.write_byte_forced(0x3FFF, 0x99); + assert_eq!(mem.rom_len, 0x4000); + assert_eq!(mem.read_byte(0x3FFF), 0x99); + } + + #[test] + #[timeout(10)] + fn test_load_rom_data_resets_banking_state() { + let mut mem = Memory::new(); + mem.ram_startup(); + + mem.write_byte_forced(0x147, 1); + mem.refresh_rom_banking_type(); + mem.write_byte(0x0001, 0x0A); + mem.write_byte(0x6000, 1); + mem.write_byte(0x4000, 2); + + assert!(mem.ram_write_enable); + assert!(!mem.rom_bank_enable); + assert_eq!(mem.ram_banks, CurrentRamBank::Bank2); + + mem.load_rom_data(&[0x00, 0x01, 0x02, 0x03]); + + assert_eq!(mem.rom_banks, CurrentRomBank::Bank(1)); + assert_eq!(mem.ram_banks, CurrentRamBank::Bank0); + assert!(mem.rom_bank_enable); + assert!(!mem.ram_write_enable); + } } diff --git a/src/lib.rs b/src/lib.rs index 5eb9aff..afaa8fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(not(feature = "std"), no_std)] //! Game Boy emulator core. //! //! This crate provides the core emulation loop and subsystems for a classic @@ -11,8 +12,8 @@ //! - Provide input through [`Emulator::game_input`], and read pixels from //! [`Emulator::get_display_buffer`]. //! -//! The emulation state is backed by shared memory (`Arc>`) so the -//! CPU, graphics, and joypad stay in sync with memory-mapped I/O. +//! The emulation state is shared across CPU, graphics, and joypad through a +//! single memory model owned by the emulator core. //! //! # Example //! ```no_run diff --git a/src/types.rs b/src/types.rs index 50e79a0..70bdb27 100644 --- a/src/types.rs +++ b/src/types.rs @@ -59,6 +59,7 @@ pub enum Color { /// RAM Device Memory pub const MEM_SIZE: usize = 0x10000; +pub const MAX_ROM_SIZE: usize = 0x80000; // 512 KiB //Likely at some point will switch the RAM and ROM to be part of the Emulator struct @@ -98,14 +99,6 @@ pub enum CurrentRamBank { Bank3, } -/// Prints to the standard ouput only in debug build. -/// In release build this macro is not compiled thanks to `#[cfg(debug_assertions)]`. -/// see [https://doc.rust-lang.org/std/macro.println.html](https://doc.rust-lang.org/std/macro.println.html) for more info. -#[macro_export] -macro_rules! debug_println { - ($($arg:tt)*) => (#[cfg(debug_assertions)] println!($($arg)*)); -} - #[cfg(test)] mod test { use super::*;