diff --git a/src/core/timing.c b/src/core/timing.c new file mode 100644 index 000000000..c3ef55135 --- /dev/null +++ b/src/core/timing.c @@ -0,0 +1,58 @@ +/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "timing.h" + +DEFINE_VECTOR(mTimingEventList, struct mTimingEvent*); + +void mTimingInit(struct mTiming* timing) { + mTimingEventListInit(&timing->events, 0); + timing->masterCycles = 0; +} + +void mTimingDeinit(struct mTiming* timing) { + mTimingEventListDeinit(&timing->events); +} + +void mTimingClear(struct mTiming* timing) { + mTimingEventListClear(&timing->events); + timing->masterCycles = 0; +} + +void mTimingSchedule(struct mTiming* timing, struct mTimingEvent* event, int32_t when) { + event->when = when + timing->masterCycles; + size_t e; + for (e = 0; e < mTimingEventListSize(&timing->events); ++e) { + struct mTimingEvent* next = *mTimingEventListGetPointer(&timing->events, e); + int32_t nextWhen = next->when - timing->masterCycles; + if (nextWhen > when) { + mTimingEventListUnshift(&timing->events, e, 1); + *mTimingEventListGetPointer(&timing->events, e) = event; + return; + } + } + *mTimingEventListAppend(&timing->events) = event; +} + +void mTimingTick(struct mTiming* timing, int32_t cycles) { + timing->masterCycles += cycles; + while (mTimingEventListSize(&timing->events)) { + struct mTimingEvent* next = *mTimingEventListGetPointer(&timing->events, 0); + int32_t nextWhen = next->when - timing->masterCycles; + if (nextWhen > 0) { + return; + } + mTimingEventListShift(&timing->events, 0, 1); + next->callback(timing, next->context, -nextWhen); + } +} + +int32_t mTimingNextEvent(struct mTiming* timing) { + if (!mTimingEventListSize(&timing->events)) { + return INT_MAX; + } + struct mTimingEvent* next = *mTimingEventListGetPointer(&timing->events, 0); + return next->when - timing->masterCycles; +} diff --git a/src/core/timing.h b/src/core/timing.h new file mode 100644 index 000000000..f8c6c1c9c --- /dev/null +++ b/src/core/timing.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef M_CORE_TIMING +#define M_CORE_TIMING + +#include "util/common.h" +#include "util/vector.h" + +struct mTiming; +struct mTimingEvent { + void* context; + void (*callback)(struct mTiming*, void* context, uint32_t); + const char* name; + uint32_t when; +}; + +DECLARE_VECTOR(mTimingEventList, struct mTimingEvent*); + +struct mTiming { + struct mTimingEventList events; + + uint32_t masterCycles; +}; + +void mTimingInit(struct mTiming* timing); +void mTimingDeinit(struct mTiming* timing); +void mTimingClear(struct mTiming* timing); +void mTimingSchedule(struct mTiming* timing, struct mTimingEvent*, int32_t when); +void mTimingTick(struct mTiming* timing, int32_t cycles); +int32_t mTimingNextEvent(struct mTiming* timing); + +#endif diff --git a/src/gb/gb.c b/src/gb/gb.c index 1df6c4891..50d61b194 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -32,6 +32,7 @@ static const uint8_t _knownHeader[4] = { 0xCE, 0xED, 0x66, 0x66}; mLOG_DEFINE_CATEGORY(GB, "GB"); static void GBInit(void* cpu, struct mCPUComponent* component); +static void GBDeinit(struct mCPUComponent* component); static void GBInterruptHandlerInit(struct LR35902InterruptHandler* irqh); static void GBProcessEvents(struct LR35902Core* cpu); static void GBSetInterrupts(struct LR35902Core* cpu, bool enable); @@ -46,7 +47,7 @@ extern size_t romBufferSize; void GBCreate(struct GB* gb) { gb->d.id = GB_COMPONENT_MAGIC; gb->d.init = GBInit; - gb->d.deinit = 0; + gb->d.deinit = GBDeinit; } static void GBInit(void* cpu, struct mCPUComponent* component) { @@ -81,6 +82,13 @@ static void GBInit(void* cpu, struct mCPUComponent* component) { gb->coreCallbacks = NULL; gb->stream = NULL; + + mTimingInit(&gb->timing); +} + +static void GBDeinit(struct mCPUComponent* component) { + struct GB* gb = (struct GB*) component; + mTimingDeinit(&gb->timing); } bool GBLoadROM(struct GB* gb, struct VFile* vf) { @@ -425,9 +433,14 @@ void GBReset(struct LR35902Core* cpu) { gb->memory.romSize = gb->yankedRomSize; gb->yankedRomSize = 0; } + + mTimingClear(&gb->timing); + GBMemoryReset(gb); GBVideoReset(&gb->video); GBTimerReset(&gb->timer); + mTimingSchedule(&gb->timing, &gb->timer.event, GB_DMG_DIV_PERIOD); + GBAudioReset(&gb->audio); GBIOReset(gb); GBSIOReset(&gb->sio); @@ -530,6 +543,12 @@ void GBProcessEvents(struct LR35902Core* cpu) { } } + mTimingTick(&gb->timing, cycles); + testEvent = mTimingNextEvent(&gb->timing); + if (testEvent < nextEvent) { + nextEvent = testEvent; + } + testEvent = GBVideoProcessEvents(&gb->video, cycles >> gb->doubleSpeed); if (testEvent != INT_MAX) { testEvent <<= gb->doubleSpeed; @@ -546,11 +565,6 @@ void GBProcessEvents(struct LR35902Core* cpu) { } } - testEvent = GBTimerProcessEvents(&gb->timer, cycles); - if (testEvent < nextEvent) { - nextEvent = testEvent; - } - testEvent = GBSIOProcessEvents(&gb->sio, cycles); if (testEvent < nextEvent) { nextEvent = testEvent; diff --git a/src/gb/gb.h b/src/gb/gb.h index ed1f5b726..584ce4bba 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -9,6 +9,7 @@ #include "util/common.h" #include "core/log.h" +#include "core/timing.h" #include "lr35902/lr35902.h" @@ -57,6 +58,7 @@ struct GB { enum GBModel model; struct mCoreSync* sync; + struct mTiming timing; uint8_t* keySource; diff --git a/src/gb/timer.c b/src/gb/timer.c index a1acdb574..dfe0d6efc 100644 --- a/src/gb/timer.c +++ b/src/gb/timer.c @@ -9,68 +9,62 @@ #include "gb/io.h" #include "gb/serialize.h" -void GBTimerReset(struct GBTimer* timer) { - timer->nextDiv = GB_DMG_DIV_PERIOD; // TODO: GBC differences - timer->nextEvent = GB_DMG_DIV_PERIOD; - timer->eventDiff = 0; - timer->timaPeriod = 1024 >> 4; - timer->internalDiv = 0; +void _GBTimerIRQ(struct mTiming* timing, void* context, uint32_t cyclesLate) { + UNUSED(timing); + UNUSED(cyclesLate); + struct GBTimer* timer = context; + 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); } -int32_t GBTimerProcessEvents(struct GBTimer* timer, int32_t cycles) { - timer->eventDiff += cycles; - timer->nextEvent -= cycles; - if (timer->nextEvent <= 0) { - timer->nextDiv -= timer->eventDiff; - 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; - } - while (timer->nextDiv <= 0) { - timer->nextDiv += GB_DMG_DIV_PERIOD; +void _GBTimerIncrement(struct mTiming* timing, void* context, uint32_t cyclesLate) { + struct GBTimer* timer = context; + timer->nextDiv += cyclesLate; + while (timer->nextDiv > 0) { + timer->nextDiv -= GB_DMG_DIV_PERIOD; - // Make sure to trigger when the correct bit is a falling edge - if (timer->timaPeriod > 0 && (timer->internalDiv & (timer->timaPeriod - 1)) == timer->timaPeriod - 1) { - ++timer->p->memory.io[REG_TIMA]; - if (!timer->p->memory.io[REG_TIMA]) { - timer->irqPending = true; - timer->nextEvent += 4; - } + // Make sure to trigger when the correct bit is a falling edge + if (timer->timaPeriod > 0 && (timer->internalDiv & (timer->timaPeriod - 1)) == timer->timaPeriod - 1) { + ++timer->p->memory.io[REG_TIMA]; + if (!timer->p->memory.io[REG_TIMA]) { + mTimingSchedule(timing, &timer->irq, 4 - cyclesLate); } - ++timer->internalDiv; - timer->p->memory.io[REG_DIV] = timer->internalDiv >> 4; } - if (timer->nextEvent <= 0) { - // Batch div increments - int divsToGo = 16 - (timer->internalDiv & 15); - int timaToGo = INT_MAX; - if (timer->timaPeriod) { - timaToGo = timer->timaPeriod - (timer->internalDiv & (timer->timaPeriod - 1)); - } - if (timaToGo < divsToGo) { - divsToGo = timaToGo; - } - timer->nextEvent += GB_DMG_DIV_PERIOD * divsToGo; - } - timer->eventDiff = 0; + ++timer->internalDiv; + timer->p->memory.io[REG_DIV] = timer->internalDiv >> 4; } - return timer->nextEvent; + // Batch div increments + int divsToGo = 16 - (timer->internalDiv & 15); + int timaToGo = INT_MAX; + if (timer->timaPeriod) { + timaToGo = timer->timaPeriod - (timer->internalDiv & (timer->timaPeriod - 1)); + } + if (timaToGo < divsToGo) { + divsToGo = timaToGo; + } + timer->nextDiv = GB_DMG_DIV_PERIOD * divsToGo; + mTimingSchedule(timing, &timer->event, timer->nextDiv - cyclesLate); +} + +void GBTimerReset(struct GBTimer* timer) { + timer->event.context = timer; + timer->event.name = "GB Timer"; + timer->event.callback = _GBTimerIncrement; + timer->irq.context = timer; + timer->irq.name = "GB Timer IRQ"; + timer->irq.callback = _GBTimerIRQ; + + timer->nextDiv = GB_DMG_DIV_PERIOD; // TODO: GBC differences + timer->timaPeriod = 1024 >> 4; + timer->internalDiv = 0; } void GBTimerDivReset(struct GBTimer* timer) { timer->p->memory.io[REG_DIV] = 0; timer->internalDiv = 0; - timer->nextDiv = timer->p->cpu->cycles + GB_DMG_DIV_PERIOD; - if (timer->nextDiv < timer->nextEvent) { - timer->nextEvent = timer->nextDiv; - } - if (timer->nextDiv < timer->p->cpu->nextEvent) { - timer->p->cpu->nextEvent = timer->nextDiv; - } - timer->nextDiv += timer->eventDiff; + timer->nextDiv = GB_DMG_DIV_PERIOD; + mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv); } uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) { @@ -94,25 +88,15 @@ uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) { } return tac; } + 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->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->internalDiv, 0, &state->timer.internalDiv); LOAD_32LE(timer->timaPeriod, 0, &state->timer.timaPeriod); - - GBSerializedTimerFlags flags = state->timer.flags ; - timer->irqPending = GBSerializedTimerFlagsIsIrqPending(flags); } diff --git a/src/gb/timer.h b/src/gb/timer.h index 8261cd20a..9ce673b56 100644 --- a/src/gb/timer.h +++ b/src/gb/timer.h @@ -8,6 +8,8 @@ #include "util/common.h" +#include "core/timing.h" + DECL_BITFIELD(GBRegisterTAC, uint8_t); DECL_BITS(GBRegisterTAC, Clock, 0, 2); DECL_BIT(GBRegisterTAC, Run, 2); @@ -20,17 +22,15 @@ struct GB; struct GBTimer { struct GB* p; - int32_t nextEvent; - int32_t eventDiff; + struct mTimingEvent event; + struct mTimingEvent irq; uint32_t internalDiv; int32_t nextDiv; 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);