mirror of https://github.com/mgba-emu/mgba.git
GBA Timer: Use global cycles for timers
This commit is contained in:
parent
4cd249e632
commit
ab9b398317
4
CHANGES
4
CHANGES
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
15
src/gba/io.c
15
src/gba/io.c
|
@ -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)) {
|
||||||
|
|
|
@ -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, ¤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) {
|
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, ¤tTimer->event);
|
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;
|
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, ¤tTimer->event);
|
mTimingDeschedule(&gba->timing, ¤tTimer->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, ¤tTimer->event);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue