From d97ee4c493a3f9441cb155e12a997aded61e75bb Mon Sep 17 00:00:00 2001 From: velllu <91963404+velllu@users.noreply.github.com> Date: Fri, 27 Sep 2024 18:09:39 +0200 Subject: [PATCH] Made DIV timer more accurate --- examples/debugger.rs | 4 ++-- src/bus/mod.rs | 30 ++++++++++++++++++++++-------- src/cpu/mod.rs | 40 +++++++++++++++++++++++++--------------- 3 files changed, 49 insertions(+), 25 deletions(-) diff --git a/examples/debugger.rs b/examples/debugger.rs index 6a6e465..0be9f1c 100644 --- a/examples/debugger.rs +++ b/examples/debugger.rs @@ -63,11 +63,11 @@ fn pretty_print_gameboy(gameboy: &GameBoy) -> Result<(), io::Error> { writeln!(lock, "{}", "Flags".bold().red())?; writeln!( lock, - " Zero: {}, Carry: {}, Subtraction: {}, Half Carry: {}, IME: {}", + " Zero: {}, Subtraction: {}, Half Carry: {}, Carry: {}, IME: {}", bool_to_symbol(gameboy.flags.zero), - bool_to_symbol(gameboy.flags.carry), bool_to_symbol(gameboy.flags.subtraction), bool_to_symbol(gameboy.flags.half_carry), + bool_to_symbol(gameboy.flags.carry), bool_to_symbol(gameboy.cpu.ime), )?; diff --git a/src/bus/mod.rs b/src/bus/mod.rs index 1f99158..982b313 100644 --- a/src/bus/mod.rs +++ b/src/bus/mod.rs @@ -3,7 +3,11 @@ use std::{error::Error, fmt::Display, fs::File, io::Read}; use mbc1::Mbc1; use mbc_no::NoMbc; -use crate::{common::merge_two_u8s_into_u16, consts::bus::*, registers::Registers}; +use crate::{ + common::merge_two_u8s_into_u16, + consts::{bus::*, cpu::DIV}, + registers::Registers, +}; mod mbc1; mod mbc_no; @@ -46,6 +50,10 @@ pub struct Bus { /// Gets true when the user writes to OAM DMA register pub needs_to_dispatch_oam_dma: bool, + + /// Gets true when the emulator writes to DIV, this means that we must reset the div + /// register internal cycle counter + pub(crate) needs_to_reset_div_register: bool, } impl Bus { @@ -74,6 +82,7 @@ impl Bus { io: new_io(), unusable_ram: [0u8; UNUSABLE_RAM_SIZE], needs_to_dispatch_oam_dma: false, + needs_to_reset_div_register: false, }) } @@ -88,6 +97,7 @@ impl Bus { io: new_io(), unusable_ram: [0u8; UNUSABLE_RAM_SIZE], needs_to_dispatch_oam_dma: false, + needs_to_reset_div_register: false, } } } @@ -112,6 +122,16 @@ impl Bus { pub fn write(&mut self, address: u16, value: u8) { match address { + DMA => { + self.needs_to_dispatch_oam_dma = true; + self.io[0x46] = value; + } + + DIV => { + self.needs_to_reset_div_register = true; + self.io[0x04] = 0; + } + 0x0000..=0x7FFF => self.mbc.signal_rom_write(address, value), 0x8000..=0x9FFF => self.video_ram[(address - 0x8000) as usize] = value, 0xA000..=0xBFFF => self.mbc.set_external_ram(address - 0xA000, value), @@ -119,13 +139,7 @@ impl Bus { 0xE000..=0xFDFF => self.work_ram[(address - 0xE000) as usize] = value, 0xFE00..=0xFE9F => self.eom[(address - 0xFE00) as usize] = value, 0xFEA0..=0xFEFF => self.unusable_ram[(address - 0xFEA0) as usize] = value, - 0xFF00..=0xFF7F => { - if address == DMA { - self.needs_to_dispatch_oam_dma = true; - } - - self.io[(address - IO_START as u16) as usize] = value; - } + 0xFF00..=0xFF7F => self.io[(address - IO_START as u16) as usize] = value, 0xFF80..=0xFFFE => self.high_ram[(address - 0xFF80) as usize] = value, 0xFFFF => self.ie = value, }; diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index b47a787..de9905c 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -23,11 +23,11 @@ pub struct Cpu { /// Wheter or not the CPU is halted pub halt: bool, - /// This is increased after each cycle, it's used for the timers - pub(crate) div_register: u16, + /// A cycle counter for keeping track of when to increment the DIV register + pub(crate) div_cycle_counter: u8, - /// This is increased based on the TAC (timer control) register - pub(crate) tima_register: u16, + /// A cycle counter for keeping track of when to increment the TIMA register + pub(crate) tima_cycle_counter: u16, } impl Cpu { @@ -35,19 +35,29 @@ impl Cpu { Self { ime: false, halt: false, - div_register: 0, - tima_register: 0, + div_cycle_counter: 1, + tima_cycle_counter: 0, } } - /// Increase the div register + /// Increment the div register every 64 cycles pub(crate) fn update_div_register(&mut self, bus: &mut Bus, cycles_num: u8) { - self.div_register = self.div_register.wrapping_add(cycles_num as u16); + // When we write to the div register we also reset the cycle counter + if bus.needs_to_reset_div_register { + // I don't know why this must be reset to 1, but it works! + self.div_cycle_counter = 1; + bus.needs_to_reset_div_register = false; + } + + self.div_cycle_counter += cycles_num; - // While the div register is 16 bit, we only actually give the bus the higher 8 - // bits, this means that it will increment after 256 cycles - let higher_byte = (self.div_register >> 8) as u8; - bus.write(DIV, higher_byte); + if self.div_cycle_counter >= 64 { + self.div_cycle_counter -= 64; + + // Increment the DIV register, by using IO directly, otherwise writing to DIV + // will trigger a DIV reset + bus.io[0x04] = bus.read(DIV).wrapping_add(1); + } } /// Increse the tima register based on the contents of the timer control register @@ -61,7 +71,7 @@ impl Cpu { return; } - self.tima_register += cycles_num as u16; + self.tima_cycle_counter += cycles_num as u16; // This is the threshold at which we increment the timer counter let increment_every = match (timer_control.get_bit(1), timer_control.get_bit(0)) { @@ -71,8 +81,8 @@ impl Cpu { (true, true) => 64, }; - if self.tima_register > increment_every { - self.tima_register -= increment_every; + if self.tima_cycle_counter > increment_every { + self.tima_cycle_counter -= increment_every; // When the timer counter overflows we have to run a timer interrupt let (result, has_overflown) = timer_counter.overflowing_add(1);