From 55cbe5d4d0972213d37abf4e7afbb33d9542d730 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 5 Aug 2016 17:22:12 +0300 Subject: [PATCH] Accuracy improvements to timers --- Core/debugger.c | 1 - Core/gb.h | 3 ++- Core/memory.c | 10 ++++++-- Core/timing.c | 66 ++++++++++++++++++++++++++++++++++++------------- Core/timing.h | 3 ++- 5 files changed, 61 insertions(+), 22 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index bddc9eaa..cb7e7ea0 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -695,7 +695,6 @@ static bool registers(GB_gameboy_t *gb, char *arguments) GB_log(gb, "SP = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_SP], false)); GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false)); - GB_log(gb, "TIMA = %d/%u\n", gb->io_registers[GB_IO_TIMA], gb->tima_cycles); GB_log(gb, "Display Controller: LY = %d/%u\n", gb->io_registers[GB_IO_LY], gb->display_cycles % 456); return true; } diff --git a/Core/gb.h b/Core/gb.h index 53b5dea8..52890cf6 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -142,6 +142,7 @@ enum { #define LCDC_PERIOD 70224 #define CPU_FREQUENCY 0x400000 #define DIV_CYCLES (0x100) +#define INTERNAL_DIV_CYCLES (0x400) #define FRAME_LENGTH 16742706 // in nanoseconds typedef enum { @@ -283,7 +284,7 @@ typedef struct GB_gameboy_s { int64_t last_vblank; uint32_t display_cycles; uint32_t div_cycles; - uint32_t tima_cycles; + GB_PADDING(uint32_t, tima_cycles); GB_PADDING(uint32_t, dma_cycles); GB_aligned_double apu_cycles; ); diff --git a/Core/memory.c b/Core/memory.c index c8b5e685..e4fb9a88 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -6,6 +6,7 @@ #include "memory.h" #include "debugger.h" #include "mbc.h" +#include "timing.h" typedef uint8_t GB_read_function_t(GB_gameboy_t *gb, uint16_t addr); typedef void GB_write_function_t(GB_gameboy_t *gb, uint16_t addr, uint8_t value); @@ -353,7 +354,6 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) /* Hardware registers */ switch (addr & 0xFF) { - case GB_IO_TAC: case GB_IO_SCX: case GB_IO_IF: case GB_IO_TIMA: @@ -374,6 +374,12 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->io_registers[addr & 0xFF] = value; return; + case GB_IO_TAC: + GB_emulate_timer_glitch(gb, gb->io_registers[GB_IO_TAC], value); + gb->io_registers[GB_IO_TAC] = value; + return; + + case GB_IO_LCDC: if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) { gb->display_cycles = 0; @@ -391,7 +397,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_DIV: - gb->div_cycles = 0; + GB_set_internal_div_counter(gb, 0); gb->io_registers[GB_IO_DIV] = 0; return; diff --git a/Core/timing.c b/Core/timing.c index babafb59..4b15da1c 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -18,8 +18,10 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) { // Affected by speed boost gb->dma_cycles += cycles; - gb->div_cycles += cycles; - gb->tima_cycles += cycles; + + for (int i = 0; i < cycles; i += 4) { + GB_set_internal_div_counter(gb, gb->div_cycles + 4); + } if (gb->cgb_double_speed) { cycles >>=1; @@ -33,30 +35,60 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_since_input_ir_change += cycles; GB_dma_run(gb); GB_hdma_run(gb); - GB_timers_run(gb); GB_apu_run(gb); GB_display_run(gb); GB_ir_run(gb); } -void GB_timers_run(GB_gameboy_t *gb) -{ - /* Standard Timers */ - static const unsigned int GB_TAC_RATIOS[] = {1024, 16, 64, 256}; +/* Standard Timers */ +static const unsigned int GB_TAC_RATIOS[] = {1024, 16, 64, 256}; - if (gb->div_cycles >= DIV_CYCLES) { - gb->div_cycles -= DIV_CYCLES; +static void increase_tima(GB_gameboy_t *gb) +{ + gb->io_registers[GB_IO_TIMA]++; + if (gb->io_registers[GB_IO_TIMA] == 0) { + gb->io_registers[GB_IO_TIMA] = gb->io_registers[GB_IO_TMA]; + gb->io_registers[GB_IO_IF] |= 4; + } +} + +static bool counter_overflow_check(uint32_t old, uint32_t new, uint32_t max) +{ + return (old & (max >> 1)) && !(new & (max >> 1)); +} + +void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value) +{ + /* DIV and TIMA increase when a specific high-bit becomes a low-bit. */ + value &= INTERNAL_DIV_CYCLES - 1; + if (counter_overflow_check(gb->div_cycles, value, DIV_CYCLES)) { gb->io_registers[GB_IO_DIV]++; } + if ((gb->io_registers[GB_IO_TAC] & 4) && + counter_overflow_check(gb->div_cycles, value, GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3])) { + increase_tima(gb); + } + gb->div_cycles = value; +} - while (gb->tima_cycles >= GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3]) { - gb->tima_cycles -= GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3]; - if (gb->io_registers[GB_IO_TAC] & 4) { - gb->io_registers[GB_IO_TIMA]++; - if (gb->io_registers[GB_IO_TIMA] == 0) { - gb->io_registers[GB_IO_TIMA] = gb->io_registers[GB_IO_TMA]; - gb->io_registers[GB_IO_IF] |= 4; - } +/* + This glitch is based on the expected results of mooneye-gb rapid_toggle test. + This glitch happens because how TIMA is increased, see GB_set_internal_div_counter. + According to GiiBiiAdvance, GBC's behavior is different, but this was not tested or implemented. +*/ +void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) +{ + /* Glitch only happens when old_tac is enabled. */ + if (!(old_tac & 4)) return; + + unsigned int old_clocks = GB_TAC_RATIOS[old_tac & 3]; + unsigned int new_clocks = GB_TAC_RATIOS[new_tac & 3]; + + /* The bit used for overflow testing must have been 1 */ + if (gb->div_cycles & (old_clocks >> 1)) { + /* And now either the timer must be disabled, or the new bit used for overflow testing be 0. */ + if (!(new_tac & 4) || gb->div_cycles & (new_clocks >> 1)) { + increase_tima(gb); } } } diff --git a/Core/timing.h b/Core/timing.h index 75e66ee1..426ad9d9 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -3,6 +3,7 @@ #include "gb.h" void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); -void GB_timers_run(GB_gameboy_t *gb); +void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value); void GB_rtc_run(GB_gameboy_t *gb); +void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac); #endif /* timing_h */