GB: Start on timers
Jeffrey Pfau jeffrey@endrift.com
Wed, 20 Jan 2016 20:09:07 -0800
5 files changed,
149 insertions(+),
0 deletions(-)
M
src/gb/gb.c
→
src/gb/gb.c
@@ -41,6 +41,8 @@
gb->video.p = gb; GBVideoInit(&gb->video); + gb->timer.p = gb; + gb->romVf = 0; gb->pristineRom = 0;@@ -126,6 +128,7 @@ gb->yankedRomSize = 0;
} GBMemoryReset(gb); GBVideoReset(&gb->video); + GBTimerReset(&gb->timer); GBIOReset(gb); }@@ -172,6 +175,11 @@ int32_t nextEvent = INT_MAX;
int32_t testEvent; testEvent = GBVideoProcessEvents(&gb->video, cycles); + if (testEvent < nextEvent) { + nextEvent = testEvent; + } + + testEvent = GBTimerProcessEvents(&gb->timer, cycles); if (testEvent < nextEvent) { nextEvent = testEvent; }
M
src/gb/gb.h
→
src/gb/gb.h
@@ -11,6 +11,7 @@
#include "lr35902/lr35902.h" #include "gb/memory.h" +#include "gb/timer.h" #include "gb/video.h" extern const uint32_t DMG_LR35902_FREQUENCY;@@ -40,6 +41,7 @@
struct LR35902Core* cpu; struct GBMemory memory; struct GBVideo video; + struct GBTimer timer; int* keySource;
M
src/gb/io.c
→
src/gb/io.c
@@ -50,6 +50,18 @@ }
void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { switch (address) { + case REG_DIV: + GBTimerDivReset(&gb->timer); + return; + case REG_TIMA: + // ??? + return; + case REG_TMA: + // Handled transparently by the registers + break; + case REG_TAC: + value = GBTimerUpdateTAC(&gb->timer, value); + break; case REG_IF: gb->memory.io[REG_IF] = value; GBUpdateIRQs(gb);@@ -108,6 +120,11 @@ case REG_IF:
break; case REG_IE: return gb->memory.ie; + case REG_DIV: + case REG_TIMA: + case REG_TMA: + case REG_TAC: + break; default: // TODO: Log if (address >= GB_SIZE_IO) {
A
src/gb/timer.c
@@ -0,0 +1,84 @@
+/* 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 "timer.h" + +#include "gb/gb.h" +#include "gb/io.h" + +void GBTimerReset(struct GBTimer* timer) { + timer->nextDiv = GB_DMG_DIV_PERIOD; // TODO: GBC differences + timer->nextTima = INT_MAX; + timer->nextEvent = INT_MAX; +} + +int32_t GBTimerProcessEvents(struct GBTimer* timer, int32_t cycles) { + if (timer->nextEvent == INT_MAX) { + return INT_MAX; + } + timer->eventDiff += 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; + } + timer->nextEvent = timer->nextDiv; + + if (timer->nextTima != INT_MAX) { + timer->nextTima -= timer->eventDiff; + if (timer->nextTima <= 0) { + ++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; + } + if (timer->nextTima < timer->nextEvent) { + timer->nextEvent = timer->nextTima; + } + } + + timer->eventDiff = 0; + } + return timer->nextEvent; +} + +void GBTimerDivReset(struct GBTimer* timer) { + timer->p->memory.io[REG_DIV] = 0; + // TODO: Do we need to reset the event? +} + +uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) { + if (GBRegisterTACIsRun(tac)) { + switch (GBRegisterTACGetClock(tac)) { + case 0: + timer->timaPeriod = 1024; + break; + case 1: + timer->timaPeriod = 16; + break; + case 2: + timer->timaPeriod = 64; + break; + case 3: + timer->timaPeriod = 256; + break; + } + timer->nextTima = timer->eventDiff + timer->timaPeriod; + if (timer->nextTima < timer->nextEvent) { + timer->nextEvent = timer->nextTima; + if (timer->nextEvent < timer->p->cpu->nextEvent) { + timer->p->cpu->nextEvent = timer->nextEvent; + } + } + } else { + timer->nextTima = INT_MAX; + } + return tac; +}
A
src/gb/timer.h
@@ -0,0 +1,38 @@
+/* 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 GB_TIMER_H +#define GB_TIMER_H + +#include "util/common.h" + +DECL_BITFIELD(GBRegisterTAC, uint8_t); +DECL_BITS(GBRegisterTAC, Clock, 0, 2); +DECL_BIT(GBRegisterTAC, Run, 2); + +enum { + GB_DMG_DIV_PERIOD = 256 +}; + +struct GB; +struct GBTimer { + struct GB* p; + + int mode; + + int32_t nextEvent; + int32_t eventDiff; + + int32_t nextDiv; + int32_t nextTima; + int32_t timaPeriod; +}; + +void GBTimerReset(struct GBTimer*); +int32_t GBTimerProcessEvents(struct GBTimer*, int32_t cycles); +void GBTimerDivReset(struct GBTimer*); +uint8_t GBTimerUpdateTAC(struct GBTimer*, GBRegisterTAC tac); + +#endif