mirror of https://github.com/mgba-emu/mgba.git
Core, GB: Start new timing interface
This commit is contained in:
parent
c662d779a6
commit
d831de205f
|
@ -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;
|
||||
}
|
|
@ -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
|
26
src/gb/gb.c
26
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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
108
src/gb/timer.c
108
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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue