diff --git a/Core/apu.c b/Core/apu.c index b79aad96..cfc0d11a 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -289,8 +289,8 @@ void GB_apu_div_event(GB_gameboy_t *gb) void GB_apu_run(GB_gameboy_t *gb) { - /* Convert 4MHZ to 2MHz. apu_cycles is always even. */ - uint8_t cycles = gb->apu.apu_cycles >> 1; + /* Convert 4MHZ to 2MHz. apu_cycles is always divisable by 4. */ + uint8_t cycles = gb->apu.apu_cycles >> 2; gb->apu.apu_cycles = 0; if (!cycles) return; diff --git a/Core/gb.h b/Core/gb.h index 7e6abb0b..ec3b328a 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -333,7 +333,8 @@ struct GB_gameboy_internal_s { /* Timing */ GB_SECTION(timing, uint32_t display_cycles; // In 8 MHz units - uint32_t div_cycles; + GB_UNIT(div); + uint32_t div_counter; uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */ uint16_t serial_cycles; uint16_t serial_length; diff --git a/Core/memory.c b/Core/memory.c index 01d4a456..ca27741e 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -176,7 +176,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } return gb->io_registers[GB_IO_TIMA]; case GB_IO_DIV: - return gb->div_cycles >> 8; + return gb->div_counter >> 8; case GB_IO_HDMA5: if (!gb->cgb_mode) return 0xFF; return ((gb->hdma_on || gb->hdma_on_hblank)? 0 : 0x80) | ((gb->hdma_steps_left - 1) & 0x7F); @@ -484,7 +484,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_DIV: - GB_set_internal_div_counter(gb, 0); + gb->div_state = 0; // Reset the div state machine return; case GB_IO_JOYP: diff --git a/Core/timing.c b/Core/timing.c index 64986c0a..67d0190f 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -6,6 +6,8 @@ #include #endif +static const unsigned int GB_TAC_RATIOS[] = {1024, 16, 64, 256}; + #ifndef DISABLE_TIMEKEEPING static int64_t get_nanoseconds(void) { @@ -105,25 +107,56 @@ static void advance_tima_state_machine(GB_gameboy_t *gb) } } +static bool counter_overflow_check(uint32_t old, uint32_t new, uint32_t max) +{ + return (old & (max >> 1)) && !(new & (max >> 1)); +} + +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->tima_reload_state = GB_TIMA_RELOADING; + } +} + +static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value) +{ + /* TIMA increases when a specific high-bit becomes a low-bit. */ + value &= INTERNAL_DIV_CYCLES - 1; + if ((gb->io_registers[GB_IO_TAC] & 4) && + counter_overflow_check(gb->div_counter, value, GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3])) { + increase_tima(gb); + } + if (counter_overflow_check(gb->div_counter, value, gb->cgb_double_speed? 0x4000 : 0x2000)) { + GB_apu_run(gb); + GB_apu_div_event(gb); + } + gb->div_counter = value; +} + +static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) +{ + GB_STATE_MACHINE(gb, div, cycles) { + GB_STATE(gb, div, 1); + } + + GB_set_internal_div_counter(gb, 0); + while (true) { + advance_tima_state_machine(gb); + GB_set_internal_div_counter(gb, gb->div_counter + 4); + gb->apu.apu_cycles += 4 << !gb->cgb_double_speed; + GB_SLEEP(gb, div, 1, 4); + } +} + void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) { // Affected by speed boost gb->dma_cycles += cycles; - advance_tima_state_machine(gb); - for (int i = 0; i < cycles; i += 4) { - /* This is a bit tricky. The DIV and APU are tightly coupled, but DIV is affected - by the speed boost while the APU is not */ - GB_set_internal_div_counter(gb, gb->div_cycles + 4); - gb->apu.apu_cycles += 4 >> gb->cgb_double_speed; - } - - if (cycles > 4) { - advance_tima_state_machine(gb); - if (cycles > 8) { - advance_tima_state_machine(gb); - } - } + GB_timers_run(gb, cycles); uint16_t previous_serial_cycles = gb->serial_cycles; gb->serial_cycles += cycles; @@ -161,38 +194,6 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) GB_ir_run(gb); } -/* Standard Timers */ -static const unsigned int GB_TAC_RATIOS[] = {1024, 16, 64, 256}; - -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->tima_reload_state = GB_TIMA_RELOADING; - } -} - -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) -{ - /* TIMA increases when a specific high-bit becomes a low-bit. */ - value &= INTERNAL_DIV_CYCLES - 1; - 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); - } - if (counter_overflow_check(gb->div_cycles, value, gb->cgb_double_speed? 0x4000 : 0x2000)) { - GB_apu_run(gb); - GB_apu_div_event(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. @@ -207,9 +208,9 @@ void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) 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)) { + if (gb->div_counter & (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)) { + if (!(new_tac & 4) || gb->div_counter & (new_clocks >> 1)) { increase_tima(gb); } } diff --git a/Core/timing.h b/Core/timing.h index ed9e15a5..68755702 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -4,18 +4,40 @@ #ifdef GB_INTERNAL void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); -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); bool GB_timing_sync_turbo(GB_gameboy_t *gb); /* Returns true if should skip frame */ void GB_timing_sync(GB_gameboy_t *gb); - enum { GB_TIMA_RUNNING = 0, GB_TIMA_RELOADING = 1, GB_TIMA_RELOADED = 2 }; + +#define GB_HALT_VALUE (0xFFFF) + +#define GB_SLEEP(gb, unit, state, cycles) do {\ + (gb)->unit##_cycles -= cycles; \ + if ((gb)->unit##_cycles <= 0) {\ + (gb)->unit##_state = state;\ + return;\ + unit##state:; \ + }\ +} while (0) + +#define GB_HALT(gb, unit) (gb)->unit##_cycles = GB_HALT_VALUE + +#define GB_STATE_MACHINE(gb, unit, cycles) \ +(gb)->unit##_cycles += cycles; \ +if ((gb)->unit##_cycles <= 0 || (gb)->unit##_cycles == GB_HALT_VALUE) {\ + return;\ +}\ +switch ((gb)->unit##_state) #endif +#define GB_STATE(gb, unit, state) case state: goto unit##state + +#define GB_UNIT(unit) uint32_t unit##_cycles, unit##_state + #endif /* timing_h */