all repos — mgba @ ab9b398317aef256f2aa2f0a2da524149c1a1c76

mGBA Game Boy Advance Emulator

GBA Timer: Use global cycles for timers
Vicki Pfau vi@endrift.com
Mon, 03 Jul 2017 23:53:01 -0700
commit

ab9b398317aef256f2aa2f0a2da524149c1a1c76

parent

4cd249e632f7337adf061e3e1a25442506938a84

M CHANGESCHANGES

@@ -1,3 +1,7 @@

+0.7.0: (Future) +Misc: + - GBA Timer: Use global cycles for timers + 0.6.0: (Future) Features: - GBA: Support printing debug strings from inside a game
M include/mgba/internal/gba/gba.hinclude/mgba/internal/gba/gba.h

@@ -79,6 +79,7 @@

uint32_t bus; int performingDMA; + struct mTimingEvent timerMaster; struct GBATimer timers[4]; int springIRQ;
M include/mgba/internal/gba/timer.hinclude/mgba/internal/gba/timer.h

@@ -21,15 +21,14 @@

struct GBA; struct GBATimer { uint16_t reload; - uint16_t oldReload; - uint32_t lastEvent; + int32_t lastEvent; struct mTimingEvent event; int32_t overflowInterval; GBATimerFlags flags; }; void GBATimerInit(struct GBA* gba); -void GBATimerUpdateRegister(struct GBA* gba, int timer); +void GBATimerUpdateRegister(struct GBA* gba, int timer, int32_t cyclesLate); void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t value); void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t value);
M src/gba/io.csrc/gba/io.c

@@ -707,17 +707,18 @@ gba->haltPending = false;

} switch (address) { + // Reading this takes two cycles (1N+1I), so let's remove them preemptively case REG_TM0CNT_LO: - GBATimerUpdateRegister(gba, 0); + GBATimerUpdateRegister(gba, 0, 2); break; case REG_TM1CNT_LO: - GBATimerUpdateRegister(gba, 1); + GBATimerUpdateRegister(gba, 1, 2); break; case REG_TM2CNT_LO: - GBATimerUpdateRegister(gba, 2); + GBATimerUpdateRegister(gba, 2, 2); break; case REG_TM3CNT_LO: - GBATimerUpdateRegister(gba, 3); + GBATimerUpdateRegister(gba, 3, 2); break; case REG_KEYINPUT:

@@ -925,7 +926,6 @@

for (i = 0; i < 4; ++i) { STORE_16(gba->memory.io[(REG_DMA0CNT_LO + i * 12) >> 1], (REG_DMA0CNT_LO + i * 12), state->io); STORE_16(gba->timers[i].reload, 0, &state->timers[i].reload); - STORE_16(gba->timers[i].oldReload, 0, &state->timers[i].oldReload); STORE_32(gba->timers[i].lastEvent - mTimingCurrentTime(&gba->timing), 0, &state->timers[i].lastEvent); STORE_32(gba->timers[i].event.when - mTimingCurrentTime(&gba->timing), 0, &state->timers[i].nextEvent); STORE_32(gba->timers[i].overflowInterval, 0, &state->timers[i].overflowInterval);

@@ -952,9 +952,12 @@ }

} uint32_t when; + LOAD_32(when, 0, &state->masterCycles); + when += state->cpu.cycles; + when = 0x400 - (when & 0x3FF); + mTimingSchedule(&gba->timing, &gba->timerMaster, when); for (i = 0; i < 4; ++i) { LOAD_16(gba->timers[i].reload, 0, &state->timers[i].reload); - LOAD_16(gba->timers[i].oldReload, 0, &state->timers[i].oldReload); LOAD_32(gba->timers[i].overflowInterval, 0, &state->timers[i].overflowInterval); LOAD_32(gba->timers[i].flags, 0, &state->timers[i].flags); if (i > 0 && GBATimerFlagsIsCountUp(gba->timers[i].flags)) {
M src/gba/timer.csrc/gba/timer.c

@@ -8,11 +8,12 @@

#include <mgba/internal/gba/gba.h> #include <mgba/internal/gba/io.h> -static void GBATimerUpdate(struct mTiming* timing, struct GBA* gba, int timerId, uint32_t cyclesLate) { +#define TIMER_MASTER 1024 + +static void GBATimerUpdate(struct GBA* gba, int timerId, uint32_t cyclesLate) { struct GBATimer* timer = &gba->timers[timerId]; gba->memory.io[(REG_TM0CNT_LO >> 1) + (timerId << 1)] = timer->reload; - timer->oldReload = timer->reload; - timer->lastEvent = timing->masterCycles - cyclesLate; + GBATimerUpdateRegister(gba, timerId, cyclesLate); if (GBATimerFlagsIsDoIrq(timer->flags)) { GBARaiseIRQ(gba, IRQ_TIMER0 + timerId);

@@ -33,74 +34,106 @@ struct GBATimer* nextTimer = &gba->timers[timerId + 1];

if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled? ++gba->memory.io[(REG_TM1CNT_LO >> 1) + (timerId << 1)]; if (!gba->memory.io[(REG_TM1CNT_LO >> 1) + (timerId << 1)] && GBATimerFlagsIsEnable(nextTimer->flags)) { - mTimingSchedule(timing, &nextTimer->event, -cyclesLate); + GBATimerUpdate(gba, timerId + 1, cyclesLate); } } } +} - if (!GBATimerFlagsIsCountUp(timer->flags)) { - uint32_t nextEvent = timer->overflowInterval - cyclesLate; - mTimingSchedule(timing, &timer->event, nextEvent); - } +static void GBATimerMasterUpdate(struct mTiming* timing, void* context, uint32_t cyclesLate) { + struct GBA* gba = context; + mTimingSchedule(timing, &gba->timerMaster, TIMER_MASTER - cyclesLate); + GBATimerUpdateRegister(gba, 0, cyclesLate); + GBATimerUpdateRegister(gba, 1, cyclesLate); + GBATimerUpdateRegister(gba, 2, cyclesLate); + GBATimerUpdateRegister(gba, 3, cyclesLate); } static void GBATimerUpdate0(struct mTiming* timing, void* context, uint32_t cyclesLate) { - GBATimerUpdate(timing, context, 0, cyclesLate); + UNUSED(timing); + GBATimerUpdate(context, 0, cyclesLate); } static void GBATimerUpdate1(struct mTiming* timing, void* context, uint32_t cyclesLate) { - GBATimerUpdate(timing, context, 1, cyclesLate); + UNUSED(timing); + GBATimerUpdate(context, 1, cyclesLate); } static void GBATimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) { - GBATimerUpdate(timing, context, 2, cyclesLate); + UNUSED(timing); + GBATimerUpdate(context, 2, cyclesLate); } static void GBATimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) { - GBATimerUpdate(timing, context, 3, cyclesLate); + UNUSED(timing); + GBATimerUpdate(context, 3, cyclesLate); } void GBATimerInit(struct GBA* gba) { + gba->timerMaster.name = "GBA Timer Master"; + gba->timerMaster.callback = GBATimerMasterUpdate; + gba->timerMaster.context = gba; + gba->timerMaster.priority = 0x20; + mTimingSchedule(&gba->timing, &gba->timerMaster, TIMER_MASTER); memset(gba->timers, 0, sizeof(gba->timers)); gba->timers[0].event.name = "GBA Timer 0"; gba->timers[0].event.callback = GBATimerUpdate0; gba->timers[0].event.context = gba; - gba->timers[0].event.priority = 0x20; + gba->timers[0].event.priority = 0x21; gba->timers[1].event.name = "GBA Timer 1"; gba->timers[1].event.callback = GBATimerUpdate1; gba->timers[1].event.context = gba; - gba->timers[1].event.priority = 0x21; + gba->timers[1].event.priority = 0x22; gba->timers[2].event.name = "GBA Timer 2"; gba->timers[2].event.callback = GBATimerUpdate2; gba->timers[2].event.context = gba; - gba->timers[2].event.priority = 0x22; + gba->timers[2].event.priority = 0x23; gba->timers[3].event.name = "GBA Timer 3"; gba->timers[3].event.callback = GBATimerUpdate3; gba->timers[3].event.context = gba; - gba->timers[3].event.priority = 0x23; + gba->timers[3].event.priority = 0x24; } -void GBATimerUpdateRegister(struct GBA* gba, int timer) { +void GBATimerUpdateRegister(struct GBA* gba, int timer, int32_t cyclesLate) { struct GBATimer* currentTimer = &gba->timers[timer]; - if (GBATimerFlagsIsEnable(currentTimer->flags) && !GBATimerFlagsIsCountUp(currentTimer->flags)) { - int32_t prefetchSkew = -2; - if (gba->memory.lastPrefetchedPc > (uint32_t) gba->cpu->gprs[ARM_PC]) { - prefetchSkew += ((gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * gba->cpu->memory.activeSeqCycles16) / WORD_SIZE_THUMB; + if (!GBATimerFlagsIsEnable(currentTimer->flags) || GBATimerFlagsIsCountUp(currentTimer->flags)) { + return; + } + + if (gba->memory.lastPrefetchedPc > (uint32_t) gba->cpu->gprs[ARM_PC]) { + cyclesLate -= ((gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * gba->cpu->memory.activeSeqCycles16) / WORD_SIZE_THUMB; + } + + int prescaleBits = GBATimerFlagsGetPrescaleBits(currentTimer->flags); + int32_t currentTime = mTimingCurrentTime(&gba->timing) - cyclesLate; + int32_t tickMask = (1 << prescaleBits) - 1; + currentTime &= ~tickMask; + int32_t tickIncrement = currentTime - currentTimer->lastEvent; + currentTimer->lastEvent = currentTime; + tickIncrement >>= prescaleBits; + tickIncrement += gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1]; + gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = tickIncrement; + tickIncrement -= 0x10000; + mTimingDeschedule(&gba->timing, &currentTimer->event); + if (tickIncrement >= 0) { + mTimingSchedule(&gba->timing, &currentTimer->event, 7 - (tickIncrement << prescaleBits)); + } else { + int32_t nextIncrement = mTimingUntil(&gba->timing, &gba->timerMaster); + tickIncrement = -tickIncrement; + tickIncrement <<= prescaleBits; + if (nextIncrement - tickIncrement > 0) { + mTimingSchedule(&gba->timing, &currentTimer->event, 7 + tickIncrement); } - // Reading this takes two cycles (1N+1I), so let's remove them preemptively - int32_t diff = gba->cpu->cycles - (currentTimer->lastEvent - gba->timing.masterCycles); - gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((diff + prefetchSkew) >> GBATimerFlagsGetPrescaleBits(currentTimer->flags)); } } void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t reload) { gba->timers[timer].reload = reload; - gba->timers[timer].overflowInterval = (0x10000 - gba->timers[timer].reload) << GBATimerFlagsGetPrescaleBits(gba->timers[timer].flags); } void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) { struct GBATimer* currentTimer = &gba->timers[timer]; - GBATimerUpdateRegister(gba, timer); + GBATimerUpdateRegister(gba, timer, 0); unsigned oldPrescale = GBATimerFlagsGetPrescaleBits(currentTimer->flags); unsigned prescaleBits;

@@ -121,21 +154,20 @@ }

currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, prescaleBits); currentTimer->flags = GBATimerFlagsTestFillCountUp(currentTimer->flags, timer > 0 && (control & 0x0004)); currentTimer->flags = GBATimerFlagsTestFillDoIrq(currentTimer->flags, control & 0x0040); - currentTimer->overflowInterval = (0x10000 - currentTimer->reload) << GBATimerFlagsGetPrescaleBits(currentTimer->flags); bool wasEnabled = GBATimerFlagsIsEnable(currentTimer->flags); currentTimer->flags = GBATimerFlagsTestFillEnable(currentTimer->flags, control & 0x0080); if (!wasEnabled && GBATimerFlagsIsEnable(currentTimer->flags)) { mTimingDeschedule(&gba->timing, &currentTimer->event); - if (!GBATimerFlagsIsCountUp(currentTimer->flags)) { - mTimingSchedule(&gba->timing, &currentTimer->event, currentTimer->overflowInterval + 7 - 6 * prescaleBits); - } gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->reload; - currentTimer->oldReload = currentTimer->reload; - currentTimer->lastEvent = gba->timing.masterCycles + gba->cpu->cycles; + int32_t tickMask = (1 << prescaleBits) - 1; + currentTimer->lastEvent = (mTimingCurrentTime(&gba->timing)) & ~tickMask; + GBATimerUpdateRegister(gba, timer, 0); } else if (wasEnabled && !GBATimerFlagsIsEnable(currentTimer->flags)) { mTimingDeschedule(&gba->timing, &currentTimer->event); } else if (GBATimerFlagsIsEnable(currentTimer->flags) && GBATimerFlagsGetPrescaleBits(currentTimer->flags) != oldPrescale && !GBATimerFlagsIsCountUp(currentTimer->flags)) { mTimingDeschedule(&gba->timing, &currentTimer->event); - mTimingSchedule(&gba->timing, &currentTimer->event, currentTimer->overflowInterval - currentTimer->lastEvent); + int32_t tickMask = (1 << prescaleBits) - 1; + currentTimer->lastEvent = (mTimingCurrentTime(&gba->timing)) & ~tickMask; + GBATimerUpdateRegister(gba, timer, 0); } }