Accuracy improvements to timers

This commit is contained in:
Lior Halphon 2016-08-05 17:22:12 +03:00
parent d098458ee4
commit 55cbe5d4d0
5 changed files with 61 additions and 22 deletions

View File

@ -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;
}

View File

@ -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;
);

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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 */