GBA Timer: Use global cycles for timers

This commit is contained in:
Vicki Pfau 2017-07-03 23:53:01 -07:00
parent 4cd249e632
commit ab9b398317
5 changed files with 81 additions and 42 deletions

View File

@ -1,3 +1,7 @@
0.7.0: (Future)
Misc:
- GBA Timer: Use global cycles for timers
0.6.0: (Future) 0.6.0: (Future)
Features: Features:
- GBA: Support printing debug strings from inside a game - GBA: Support printing debug strings from inside a game

View File

@ -79,6 +79,7 @@ struct GBA {
uint32_t bus; uint32_t bus;
int performingDMA; int performingDMA;
struct mTimingEvent timerMaster;
struct GBATimer timers[4]; struct GBATimer timers[4];
int springIRQ; int springIRQ;

View File

@ -21,15 +21,14 @@ DECL_BIT(GBATimerFlags, Enable, 6);
struct GBA; struct GBA;
struct GBATimer { struct GBATimer {
uint16_t reload; uint16_t reload;
uint16_t oldReload; int32_t lastEvent;
uint32_t lastEvent;
struct mTimingEvent event; struct mTimingEvent event;
int32_t overflowInterval; int32_t overflowInterval;
GBATimerFlags flags; GBATimerFlags flags;
}; };
void GBATimerInit(struct GBA* gba); 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_LO(struct GBA* gba, int timer, uint16_t value);
void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t value); void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t value);

View File

@ -707,17 +707,18 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
} }
switch (address) { switch (address) {
// Reading this takes two cycles (1N+1I), so let's remove them preemptively
case REG_TM0CNT_LO: case REG_TM0CNT_LO:
GBATimerUpdateRegister(gba, 0); GBATimerUpdateRegister(gba, 0, 2);
break; break;
case REG_TM1CNT_LO: case REG_TM1CNT_LO:
GBATimerUpdateRegister(gba, 1); GBATimerUpdateRegister(gba, 1, 2);
break; break;
case REG_TM2CNT_LO: case REG_TM2CNT_LO:
GBATimerUpdateRegister(gba, 2); GBATimerUpdateRegister(gba, 2, 2);
break; break;
case REG_TM3CNT_LO: case REG_TM3CNT_LO:
GBATimerUpdateRegister(gba, 3); GBATimerUpdateRegister(gba, 3, 2);
break; break;
case REG_KEYINPUT: case REG_KEYINPUT:
@ -925,7 +926,6 @@ void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) {
for (i = 0; i < 4; ++i) { 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->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].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].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].event.when - mTimingCurrentTime(&gba->timing), 0, &state->timers[i].nextEvent);
STORE_32(gba->timers[i].overflowInterval, 0, &state->timers[i].overflowInterval); 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; 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) { for (i = 0; i < 4; ++i) {
LOAD_16(gba->timers[i].reload, 0, &state->timers[i].reload); 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].overflowInterval, 0, &state->timers[i].overflowInterval);
LOAD_32(gba->timers[i].flags, 0, &state->timers[i].flags); LOAD_32(gba->timers[i].flags, 0, &state->timers[i].flags);
if (i > 0 && GBATimerFlagsIsCountUp(gba->timers[i].flags)) { if (i > 0 && GBATimerFlagsIsCountUp(gba->timers[i].flags)) {

View File

@ -8,11 +8,12 @@
#include <mgba/internal/gba/gba.h> #include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/io.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]; struct GBATimer* timer = &gba->timers[timerId];
gba->memory.io[(REG_TM0CNT_LO >> 1) + (timerId << 1)] = timer->reload; gba->memory.io[(REG_TM0CNT_LO >> 1) + (timerId << 1)] = timer->reload;
timer->oldReload = timer->reload; GBATimerUpdateRegister(gba, timerId, cyclesLate);
timer->lastEvent = timing->masterCycles - cyclesLate;
if (GBATimerFlagsIsDoIrq(timer->flags)) { if (GBATimerFlagsIsDoIrq(timer->flags)) {
GBARaiseIRQ(gba, IRQ_TIMER0 + timerId); 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? if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled?
++gba->memory.io[(REG_TM1CNT_LO >> 1) + (timerId << 1)]; ++gba->memory.io[(REG_TM1CNT_LO >> 1) + (timerId << 1)];
if (!gba->memory.io[(REG_TM1CNT_LO >> 1) + (timerId << 1)] && GBATimerFlagsIsEnable(nextTimer->flags)) { 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)) { static void GBATimerMasterUpdate(struct mTiming* timing, void* context, uint32_t cyclesLate) {
uint32_t nextEvent = timer->overflowInterval - cyclesLate; struct GBA* gba = context;
mTimingSchedule(timing, &timer->event, nextEvent); 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) { 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) { 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) { 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) { 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) { 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)); memset(gba->timers, 0, sizeof(gba->timers));
gba->timers[0].event.name = "GBA Timer 0"; gba->timers[0].event.name = "GBA Timer 0";
gba->timers[0].event.callback = GBATimerUpdate0; gba->timers[0].event.callback = GBATimerUpdate0;
gba->timers[0].event.context = gba; 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.name = "GBA Timer 1";
gba->timers[1].event.callback = GBATimerUpdate1; gba->timers[1].event.callback = GBATimerUpdate1;
gba->timers[1].event.context = gba; 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.name = "GBA Timer 2";
gba->timers[2].event.callback = GBATimerUpdate2; gba->timers[2].event.callback = GBATimerUpdate2;
gba->timers[2].event.context = gba; 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.name = "GBA Timer 3";
gba->timers[3].event.callback = GBATimerUpdate3; gba->timers[3].event.callback = GBATimerUpdate3;
gba->timers[3].event.context = gba; 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]; struct GBATimer* currentTimer = &gba->timers[timer];
if (GBATimerFlagsIsEnable(currentTimer->flags) && !GBATimerFlagsIsCountUp(currentTimer->flags)) { if (!GBATimerFlagsIsEnable(currentTimer->flags) || GBATimerFlagsIsCountUp(currentTimer->flags)) {
int32_t prefetchSkew = -2; return;
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 (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) { void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t reload) {
gba->timers[timer].reload = 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) { void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) {
struct GBATimer* currentTimer = &gba->timers[timer]; struct GBATimer* currentTimer = &gba->timers[timer];
GBATimerUpdateRegister(gba, timer); GBATimerUpdateRegister(gba, timer, 0);
unsigned oldPrescale = GBATimerFlagsGetPrescaleBits(currentTimer->flags); unsigned oldPrescale = GBATimerFlagsGetPrescaleBits(currentTimer->flags);
unsigned prescaleBits; 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 = GBATimerFlagsSetPrescaleBits(currentTimer->flags, prescaleBits);
currentTimer->flags = GBATimerFlagsTestFillCountUp(currentTimer->flags, timer > 0 && (control & 0x0004)); currentTimer->flags = GBATimerFlagsTestFillCountUp(currentTimer->flags, timer > 0 && (control & 0x0004));
currentTimer->flags = GBATimerFlagsTestFillDoIrq(currentTimer->flags, control & 0x0040); currentTimer->flags = GBATimerFlagsTestFillDoIrq(currentTimer->flags, control & 0x0040);
currentTimer->overflowInterval = (0x10000 - currentTimer->reload) << GBATimerFlagsGetPrescaleBits(currentTimer->flags);
bool wasEnabled = GBATimerFlagsIsEnable(currentTimer->flags); bool wasEnabled = GBATimerFlagsIsEnable(currentTimer->flags);
currentTimer->flags = GBATimerFlagsTestFillEnable(currentTimer->flags, control & 0x0080); currentTimer->flags = GBATimerFlagsTestFillEnable(currentTimer->flags, control & 0x0080);
if (!wasEnabled && GBATimerFlagsIsEnable(currentTimer->flags)) { if (!wasEnabled && GBATimerFlagsIsEnable(currentTimer->flags)) {
mTimingDeschedule(&gba->timing, &currentTimer->event); 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; gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->reload;
currentTimer->oldReload = currentTimer->reload; int32_t tickMask = (1 << prescaleBits) - 1;
currentTimer->lastEvent = gba->timing.masterCycles + gba->cpu->cycles; currentTimer->lastEvent = (mTimingCurrentTime(&gba->timing)) & ~tickMask;
GBATimerUpdateRegister(gba, timer, 0);
} else if (wasEnabled && !GBATimerFlagsIsEnable(currentTimer->flags)) { } else if (wasEnabled && !GBATimerFlagsIsEnable(currentTimer->flags)) {
mTimingDeschedule(&gba->timing, &currentTimer->event); mTimingDeschedule(&gba->timing, &currentTimer->event);
} else if (GBATimerFlagsIsEnable(currentTimer->flags) && GBATimerFlagsGetPrescaleBits(currentTimer->flags) != oldPrescale && !GBATimerFlagsIsCountUp(currentTimer->flags)) { } else if (GBATimerFlagsIsEnable(currentTimer->flags) && GBATimerFlagsGetPrescaleBits(currentTimer->flags) != oldPrescale && !GBATimerFlagsIsCountUp(currentTimer->flags)) {
mTimingDeschedule(&gba->timing, &currentTimer->event); 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);
} }
} }