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
|
* | 0x00154 - 0x00157: Next event
|
||||||
* | 0x00158 - 0x0015B: Event diff
|
* | 0x00158 - 0x0015B: Event diff
|
||||||
* | 0x0015C - 0x0015F: Next DIV
|
* | 0x0015C - 0x0015F: Next DIV
|
||||||
* | 0x00160 - 0x00163: Next TIMA
|
* | 0x00160 - 0x00163: Inernal DIV
|
||||||
* | 0x00164 - 0x00167: TIMA period
|
* | 0x00164: TIMA period
|
||||||
|
* | 0x00165: Flags
|
||||||
|
* | bit 0: Is IRQ pending?
|
||||||
|
* | 0x00166 - 0x00167: Reserved
|
||||||
* 0x000168 - 0x000197: Memory state
|
* 0x000168 - 0x000197: Memory state
|
||||||
* | 0x00168 - 0x00169: Current ROM bank
|
* | 0x00168 - 0x00169: Current ROM bank
|
||||||
* | 0x0016A: Current WRAM bank
|
* | 0x0016A: Current WRAM bank
|
||||||
|
@ -205,6 +208,8 @@ DECL_BIT(GBSerializedCpuFlags, Condition, 0);
|
||||||
DECL_BIT(GBSerializedCpuFlags, IrqPending, 1);
|
DECL_BIT(GBSerializedCpuFlags, IrqPending, 1);
|
||||||
DECL_BIT(GBSerializedCpuFlags, DoubleSpeed, 2);
|
DECL_BIT(GBSerializedCpuFlags, DoubleSpeed, 2);
|
||||||
|
|
||||||
|
DECL_BITFIELD(GBSerializedTimerFlags, uint8_t);
|
||||||
|
DECL_BIT(GBSerializedTimerFlags, IrqPending, 0);
|
||||||
|
|
||||||
DECL_BITFIELD(GBSerializedVideoFlags, uint8_t);
|
DECL_BITFIELD(GBSerializedVideoFlags, uint8_t);
|
||||||
DECL_BIT(GBSerializedVideoFlags, BcpIncrement, 0);
|
DECL_BIT(GBSerializedVideoFlags, BcpIncrement, 0);
|
||||||
|
@ -290,8 +295,10 @@ struct GBSerializedState {
|
||||||
int32_t eventDiff;
|
int32_t eventDiff;
|
||||||
|
|
||||||
int32_t nextDiv;
|
int32_t nextDiv;
|
||||||
int32_t nextTima;
|
uint32_t internalDiv;
|
||||||
int32_t timaPeriod;
|
uint8_t timaPeriod;
|
||||||
|
GBSerializedTimerFlags flags;
|
||||||
|
uint16_t reserved;
|
||||||
} timer;
|
} timer;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
|
@ -11,10 +11,10 @@
|
||||||
|
|
||||||
void GBTimerReset(struct GBTimer* timer) {
|
void GBTimerReset(struct GBTimer* timer) {
|
||||||
timer->nextDiv = GB_DMG_DIV_PERIOD; // TODO: GBC differences
|
timer->nextDiv = GB_DMG_DIV_PERIOD; // TODO: GBC differences
|
||||||
timer->nextTima = INT_MAX;
|
|
||||||
timer->nextEvent = GB_DMG_DIV_PERIOD;
|
timer->nextEvent = GB_DMG_DIV_PERIOD;
|
||||||
timer->eventDiff = 0;
|
timer->eventDiff = 0;
|
||||||
timer->timaPeriod = 1024;
|
timer->timaPeriod = 1024 >> 4;
|
||||||
|
timer->internalDiv = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t GBTimerProcessEvents(struct GBTimer* timer, int32_t cycles) {
|
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;
|
timer->nextEvent -= cycles;
|
||||||
if (timer->nextEvent <= 0) {
|
if (timer->nextEvent <= 0) {
|
||||||
timer->nextDiv -= timer->eventDiff;
|
timer->nextDiv -= timer->eventDiff;
|
||||||
if (timer->nextDiv <= 0) {
|
if (timer->irqPending) {
|
||||||
++timer->p->memory.io[REG_DIV];
|
timer->p->memory.io[REG_TIMA] = timer->p->memory.io[REG_TMA];
|
||||||
timer->nextDiv = GB_DMG_DIV_PERIOD;
|
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) {
|
// Make sure to trigger when the correct bit is a falling edge
|
||||||
timer->nextTima -= timer->eventDiff;
|
if (timer->timaPeriod == 1 || (timer->internalDiv & (timer->timaPeriod - 1)) == (timer->timaPeriod >> 1) - 1) {
|
||||||
if (timer->nextTima <= 0) {
|
++timer->p->memory.io[REG_TIMA];
|
||||||
if (!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->irqPending = true;
|
||||||
timer->p->memory.io[REG_IF] |= (1 << GB_IRQ_TIMER);
|
timer->nextEvent = 4;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (timer->nextTima < timer->nextEvent) {
|
|
||||||
timer->nextEvent = timer->nextTima;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
timer->eventDiff = 0;
|
timer->eventDiff = 0;
|
||||||
}
|
}
|
||||||
return timer->nextEvent;
|
return timer->nextEvent;
|
||||||
|
@ -57,60 +51,49 @@ int32_t GBTimerProcessEvents(struct GBTimer* timer, int32_t cycles) {
|
||||||
|
|
||||||
void GBTimerDivReset(struct GBTimer* timer) {
|
void GBTimerDivReset(struct GBTimer* timer) {
|
||||||
timer->p->memory.io[REG_DIV] = 0;
|
timer->p->memory.io[REG_DIV] = 0;
|
||||||
timer->nextDiv = timer->eventDiff + timer->p->cpu->cycles + GB_DMG_DIV_PERIOD;
|
timer->internalDiv = 0;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) {
|
uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) {
|
||||||
if (GBRegisterTACIsRun(tac)) {
|
if (GBRegisterTACIsRun(tac)) {
|
||||||
switch (GBRegisterTACGetClock(tac)) {
|
switch (GBRegisterTACGetClock(tac)) {
|
||||||
case 0:
|
case 0:
|
||||||
timer->timaPeriod = 1024;
|
timer->timaPeriod = 1024 >> 4;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
timer->timaPeriod = 16;
|
timer->timaPeriod = 16 >> 4;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
timer->timaPeriod = 64;
|
timer->timaPeriod = 64 >> 4;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
timer->timaPeriod = 256;
|
timer->timaPeriod = 256 >> 4;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
GBTimerUpdateTIMA(timer);
|
|
||||||
} else {
|
} else {
|
||||||
timer->nextTima = INT_MAX;
|
timer->timaPeriod = 0;
|
||||||
}
|
}
|
||||||
return tac;
|
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) {
|
void GBTimerSerialize(const struct GBTimer* timer, struct GBSerializedState* state) {
|
||||||
STORE_32LE(timer->nextEvent, 0, &state->timer.nextEvent);
|
STORE_32LE(timer->nextEvent, 0, &state->timer.nextEvent);
|
||||||
STORE_32LE(timer->eventDiff, 0, &state->timer.eventDiff);
|
STORE_32LE(timer->eventDiff, 0, &state->timer.eventDiff);
|
||||||
STORE_32LE(timer->nextDiv, 0, &state->timer.nextDiv);
|
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);
|
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) {
|
void GBTimerDeserialize(struct GBTimer* timer, const struct GBSerializedState* state) {
|
||||||
LOAD_32LE(timer->nextEvent, 0, &state->timer.nextEvent);
|
LOAD_32LE(timer->nextEvent, 0, &state->timer.nextEvent);
|
||||||
LOAD_32LE(timer->eventDiff, 0, &state->timer.eventDiff);
|
LOAD_32LE(timer->eventDiff, 0, &state->timer.eventDiff);
|
||||||
LOAD_32LE(timer->nextDiv, 0, &state->timer.nextDiv);
|
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);
|
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);
|
DECL_BIT(GBRegisterTAC, Run, 2);
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
GB_DMG_DIV_PERIOD = 256
|
GB_DMG_DIV_PERIOD = 16
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GB;
|
struct GB;
|
||||||
|
@ -23,16 +23,16 @@ struct GBTimer {
|
||||||
int32_t nextEvent;
|
int32_t nextEvent;
|
||||||
int32_t eventDiff;
|
int32_t eventDiff;
|
||||||
|
|
||||||
|
uint32_t internalDiv;
|
||||||
int32_t nextDiv;
|
int32_t nextDiv;
|
||||||
int32_t nextTima;
|
uint32_t timaPeriod;
|
||||||
int32_t timaPeriod;
|
bool irqPending;
|
||||||
};
|
};
|
||||||
|
|
||||||
void GBTimerReset(struct GBTimer*);
|
void GBTimerReset(struct GBTimer*);
|
||||||
int32_t GBTimerProcessEvents(struct GBTimer*, int32_t cycles);
|
int32_t GBTimerProcessEvents(struct GBTimer*, int32_t cycles);
|
||||||
void GBTimerDivReset(struct GBTimer*);
|
void GBTimerDivReset(struct GBTimer*);
|
||||||
uint8_t GBTimerUpdateTAC(struct GBTimer*, GBRegisterTAC tac);
|
uint8_t GBTimerUpdateTAC(struct GBTimer*, GBRegisterTAC tac);
|
||||||
void GBTimerUpdateTIMA(struct GBTimer* timer);
|
|
||||||
|
|
||||||
struct GBSerializedState;
|
struct GBSerializedState;
|
||||||
void GBTimerSerialize(const struct GBTimer* timer, struct GBSerializedState* state);
|
void GBTimerSerialize(const struct GBTimer* timer, struct GBSerializedState* state);
|
||||||
|
|
Loading…
Reference in New Issue