Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
107 changes: 40 additions & 67 deletions src/emulator.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
use std::{
sync::{Arc, Mutex},
time::{Duration, Instant},
};

use crate::debug_println;
use crate::types::{GameInput, KeyState};

mod cpu;
Expand All @@ -24,7 +18,6 @@ pub struct Emulator {
screen: graphics::Screen,
cpu: cpu::CPU,
joypad: joypad::Joypad,
memory: mem::SharedMemory,
paused: bool,
}

Expand All @@ -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.
///
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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(())
}

Expand All @@ -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.
Expand All @@ -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)
}
}
64 changes: 34 additions & 30 deletions src/emulator/cpu.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand All @@ -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,
Expand All @@ -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() {
Expand Down Expand Up @@ -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 {
Expand All @@ -2567,31 +2579,26 @@ impl MemoryAdapter {
self.rb(IF)
}

fn write_if(&self, value: u8) {
fn write_if(&mut self, value: u8) {
self.wb(IF, value);
}

fn switch_speed(&self) {}
}

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 {
Expand All @@ -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
}
Expand Down
Loading