GB Timer: Make timers behave accurately
Jeffrey Pfau jeffrey@endrift.com
Fri, 26 Aug 2016 11:51:24 -0700
3 files changed,
47 insertions(+),
57 deletions(-)
M
src/gb/serialize.h
→
src/gb/serialize.h
@@ -121,8 +121,11 @@ * 0x00154 - 0x000167: Timer state
* | 0x00154 - 0x00157: Next event * | 0x00158 - 0x0015B: Event diff * | 0x0015C - 0x0015F: Next DIV - * | 0x00160 - 0x00163: Next TIMA - * | 0x00164 - 0x00167: TIMA period + * | 0x00160 - 0x00163: Inernal DIV + * | 0x00164: TIMA period + * | 0x00165: Flags + * | bit 0: Is IRQ pending? + * | 0x00166 - 0x00167: Reserved * 0x000168 - 0x000197: Memory state * | 0x00168 - 0x00169: Current ROM bank * | 0x0016A: Current WRAM bank@@ -205,6 +208,8 @@ DECL_BIT(GBSerializedCpuFlags, Condition, 0);
DECL_BIT(GBSerializedCpuFlags, IrqPending, 1); DECL_BIT(GBSerializedCpuFlags, DoubleSpeed, 2); +DECL_BITFIELD(GBSerializedTimerFlags, uint8_t); +DECL_BIT(GBSerializedTimerFlags, IrqPending, 0); DECL_BITFIELD(GBSerializedVideoFlags, uint8_t); DECL_BIT(GBSerializedVideoFlags, BcpIncrement, 0);@@ -290,8 +295,10 @@ int32_t nextEvent;
int32_t eventDiff; int32_t nextDiv; - int32_t nextTima; - int32_t timaPeriod; + uint32_t internalDiv; + uint8_t timaPeriod; + GBSerializedTimerFlags flags; + uint16_t reserved; } timer; struct {
M
src/gb/timer.c
→
src/gb/timer.c
@@ -11,10 +11,10 @@ #include "gb/serialize.h"
void GBTimerReset(struct GBTimer* timer) { timer->nextDiv = GB_DMG_DIV_PERIOD; // TODO: GBC differences - timer->nextTima = INT_MAX; timer->nextEvent = GB_DMG_DIV_PERIOD; timer->eventDiff = 0; - timer->timaPeriod = 1024; + timer->timaPeriod = 1024 >> 4; + timer->internalDiv = 0; } int32_t GBTimerProcessEvents(struct GBTimer* timer, int32_t cycles) {@@ -22,34 +22,28 @@ timer->eventDiff += cycles;
timer->nextEvent -= cycles; if (timer->nextEvent <= 0) { timer->nextDiv -= timer->eventDiff; + if (timer->irqPending) { + timer->p->memory.io[REG_TIMA] = timer->p->memory.io[REG_TMA]; + timer->p->memory.io[REG_IF] |= (1 << GB_IRQ_TIMER); + GBUpdateIRQs(timer->p); + timer->irqPending = false; + timer->nextEvent = timer->nextDiv; + } if (timer->nextDiv <= 0) { - ++timer->p->memory.io[REG_DIV]; + ++timer->internalDiv; + timer->p->memory.io[REG_DIV] = timer->internalDiv >> 4; timer->nextDiv = GB_DMG_DIV_PERIOD; - } - timer->nextEvent = timer->nextDiv; + timer->nextEvent = timer->nextDiv; - if (timer->nextTima != INT_MAX) { - timer->nextTima -= timer->eventDiff; - if (timer->nextTima <= 0) { + // Make sure to trigger when the correct bit is a falling edge + if (timer->timaPeriod == 1 || (timer->internalDiv & (timer->timaPeriod - 1)) == (timer->timaPeriod >> 1) - 1) { + ++timer->p->memory.io[REG_TIMA]; if (!timer->p->memory.io[REG_TIMA]) { - timer->p->memory.io[REG_TIMA] = timer->p->memory.io[REG_TMA]; - timer->p->memory.io[REG_IF] |= (1 << GB_IRQ_TIMER); - GBUpdateIRQs(timer->p); - timer->nextTima = timer->timaPeriod - 4; - } else { - ++timer->p->memory.io[REG_TIMA]; - if (!timer->p->memory.io[REG_TIMA]) { - timer->nextTima = 4; - } else { - timer->nextTima = timer->timaPeriod; - } + timer->irqPending = true; + timer->nextEvent = 4; } } - if (timer->nextTima < timer->nextEvent) { - timer->nextEvent = timer->nextTima; - } } - timer->eventDiff = 0; } return timer->nextEvent;@@ -57,60 +51,49 @@ }
void GBTimerDivReset(struct GBTimer* timer) { timer->p->memory.io[REG_DIV] = 0; - timer->nextDiv = timer->eventDiff + timer->p->cpu->cycles + GB_DMG_DIV_PERIOD; - if (timer->nextDiv - timer->eventDiff < timer->nextEvent) { - timer->nextEvent = timer->nextDiv - timer->eventDiff; - if (timer->nextEvent < timer->p->cpu->nextEvent) { - timer->p->cpu->nextEvent = timer->nextEvent; - } - } + timer->internalDiv = 0; } uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) { if (GBRegisterTACIsRun(tac)) { switch (GBRegisterTACGetClock(tac)) { case 0: - timer->timaPeriod = 1024; + timer->timaPeriod = 1024 >> 4; break; case 1: - timer->timaPeriod = 16; + timer->timaPeriod = 16 >> 4; break; case 2: - timer->timaPeriod = 64; + timer->timaPeriod = 64 >> 4; break; case 3: - timer->timaPeriod = 256; + timer->timaPeriod = 256 >> 4; break; } - GBTimerUpdateTIMA(timer); } else { - timer->nextTima = INT_MAX; + timer->timaPeriod = 0; } return tac; } - -void GBTimerUpdateTIMA(struct GBTimer* timer) { - timer->nextTima = timer->eventDiff + timer->p->cpu->cycles + timer->timaPeriod; - if (timer->nextTima - timer->eventDiff < timer->nextEvent) { - timer->nextEvent = timer->nextTima - timer->eventDiff; - if (timer->nextEvent < timer->p->cpu->nextEvent) { - timer->p->cpu->nextEvent = timer->nextEvent; - } - } -} - void GBTimerSerialize(const struct GBTimer* timer, struct GBSerializedState* state) { STORE_32LE(timer->nextEvent, 0, &state->timer.nextEvent); STORE_32LE(timer->eventDiff, 0, &state->timer.eventDiff); STORE_32LE(timer->nextDiv, 0, &state->timer.nextDiv); - STORE_32LE(timer->nextTima, 0, &state->timer.nextTima); + STORE_32LE(timer->internalDiv, 0, &state->timer.internalDiv); STORE_32LE(timer->timaPeriod, 0, &state->timer.timaPeriod); + + GBSerializedTimerFlags flags = 0; + flags = GBSerializedTimerFlagsSetIrqPending(flags, state->timer.flags); + state->timer.flags = flags; } void GBTimerDeserialize(struct GBTimer* timer, const struct GBSerializedState* state) { LOAD_32LE(timer->nextEvent, 0, &state->timer.nextEvent); LOAD_32LE(timer->eventDiff, 0, &state->timer.eventDiff); LOAD_32LE(timer->nextDiv, 0, &state->timer.nextDiv); - LOAD_32LE(timer->nextTima, 0, &state->timer.nextTima); + LOAD_32LE(timer->internalDiv, 0, &state->timer.internalDiv); LOAD_32LE(timer->timaPeriod, 0, &state->timer.timaPeriod); + + GBSerializedTimerFlags flags = state->timer.flags ; + timer->irqPending = GBSerializedTimerFlagsIsIrqPending(flags); }
M
src/gb/timer.h
→
src/gb/timer.h
@@ -13,7 +13,7 @@ DECL_BITS(GBRegisterTAC, Clock, 0, 2);
DECL_BIT(GBRegisterTAC, Run, 2); enum { - GB_DMG_DIV_PERIOD = 256 + GB_DMG_DIV_PERIOD = 16 }; struct GB;@@ -23,16 +23,16 @@
int32_t nextEvent; int32_t eventDiff; + uint32_t internalDiv; int32_t nextDiv; - int32_t nextTima; - int32_t timaPeriod; + uint32_t timaPeriod; + bool irqPending; }; void GBTimerReset(struct GBTimer*); int32_t GBTimerProcessEvents(struct GBTimer*, int32_t cycles); void GBTimerDivReset(struct GBTimer*); uint8_t GBTimerUpdateTAC(struct GBTimer*, GBRegisterTAC tac); -void GBTimerUpdateTIMA(struct GBTimer* timer); struct GBSerializedState; void GBTimerSerialize(const struct GBTimer* timer, struct GBSerializedState* state);