mirror of https://github.com/mgba-emu/mgba.git
GB Timer: Make timers behave accurately
This commit is contained in:
parent
a43c465f9f
commit
2347bc4a52
|
@ -121,8 +121,11 @@ mLOG_DECLARE_CATEGORY(GB_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 @@ struct GBSerializedState {
|
|||
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 {
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
|
||||
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 @@ int32_t GBTimerProcessEvents(struct GBTimer* timer, int32_t cycles) {
|
|||
timer->nextEvent -= cycles;
|
||||
if (timer->nextEvent <= 0) {
|
||||
timer->nextDiv -= timer->eventDiff;
|
||||
if (timer->nextDiv <= 0) {
|
||||
++timer->p->memory.io[REG_DIV];
|
||||
timer->nextDiv = GB_DMG_DIV_PERIOD;
|
||||
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;
|
||||
}
|
||||
timer->nextEvent = timer->nextDiv;
|
||||
if (timer->nextDiv <= 0) {
|
||||
++timer->internalDiv;
|
||||
timer->p->memory.io[REG_DIV] = timer->internalDiv >> 4;
|
||||
timer->nextDiv = GB_DMG_DIV_PERIOD;
|
||||
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 @@ int32_t GBTimerProcessEvents(struct GBTimer* timer, int32_t cycles) {
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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 @@ struct GBTimer {
|
|||
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);
|
||||
|
|
Loading…
Reference in New Issue