diff --git a/CHANGES b/CHANGES index d15921656..79b56b392 100644 --- a/CHANGES +++ b/CHANGES @@ -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 diff --git a/include/mgba/internal/gba/gba.h b/include/mgba/internal/gba/gba.h index 5710864e0..5440e3f12 100644 --- a/include/mgba/internal/gba/gba.h +++ b/include/mgba/internal/gba/gba.h @@ -79,6 +79,7 @@ struct GBA { uint32_t bus; int performingDMA; + struct mTimingEvent timerMaster; struct GBATimer timers[4]; int springIRQ; diff --git a/include/mgba/internal/gba/timer.h b/include/mgba/internal/gba/timer.h index d031f3d4f..5cb23605b 100644 --- a/include/mgba/internal/gba/timer.h +++ b/include/mgba/internal/gba/timer.h @@ -21,15 +21,14 @@ DECL_BIT(GBATimerFlags, Enable, 6); 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); diff --git a/src/gba/io.c b/src/gba/io.c index 4d36f2800..ebcb30d95 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -707,17 +707,18 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) { } 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 @@ void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) { 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 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) { } 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)) { diff --git a/src/gba/timer.c b/src/gba/timer.c index 7bdd362df..ee965aa78 100644 --- a/src/gba/timer.c +++ b/src/gba/timer.c @@ -8,11 +8,12 @@ #include #include -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 @@ static void GBATimerUpdate(struct mTiming* timing, struct GBA* gba, int timerId, 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, ¤tTimer->event); + if (tickIncrement >= 0) { + mTimingSchedule(&gba->timing, ¤tTimer->event, 7 - (tickIncrement << prescaleBits)); + } else { + int32_t nextIncrement = mTimingUntil(&gba->timing, &gba->timerMaster); + tickIncrement = -tickIncrement; + tickIncrement <<= prescaleBits; + if (nextIncrement - tickIncrement > 0) { + mTimingSchedule(&gba->timing, ¤tTimer->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 @@ void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) { 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, ¤tTimer->event); - if (!GBATimerFlagsIsCountUp(currentTimer->flags)) { - mTimingSchedule(&gba->timing, ¤tTimer->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, ¤tTimer->event); } else if (GBATimerFlagsIsEnable(currentTimer->flags) && GBATimerFlagsGetPrescaleBits(currentTimer->flags) != oldPrescale && !GBATimerFlagsIsCountUp(currentTimer->flags)) { mTimingDeschedule(&gba->timing, ¤tTimer->event); - mTimingSchedule(&gba->timing, ¤tTimer->event, currentTimer->overflowInterval - currentTimer->lastEvent); + int32_t tickMask = (1 << prescaleBits) - 1; + currentTimer->lastEvent = (mTimingCurrentTime(&gba->timing)) & ~tickMask; + GBATimerUpdateRegister(gba, timer, 0); } }