mirror of https://github.com/bsnes-emu/bsnes.git
Accuracy improvements to timers
This commit is contained in:
parent
d098458ee4
commit
55cbe5d4d0
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,31 +35,61 @@ 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};
|
||||
|
||||
if (gb->div_cycles >= DIV_CYCLES) {
|
||||
gb->div_cycles -= DIV_CYCLES;
|
||||
gb->io_registers[GB_IO_DIV]++;
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Reference in New Issue