GB: Add basic I/O, interrupts and video

This commit is contained in:
Jeffrey Pfau 2016-01-14 20:50:43 -08:00
parent 64676529ba
commit 8622ba7ed0
10 changed files with 500 additions and 20 deletions

View File

@ -5,6 +5,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gb.h"
#include "gb/io.h"
#include "util/crc32.h"
#include "util/memory.h"
#include "util/math.h"
@ -30,17 +32,18 @@ void GBCreate(struct GB* gb) {
}
static void GBInit(struct LR35902Core* cpu, struct LR35902Component* component) {
struct GB* gba = (struct GB*) component;
gba->cpu = cpu;
struct GB* gb = (struct GB*) component;
gb->cpu = cpu;
GBInterruptHandlerInit(&cpu->irqh);
GBMemoryInit(gba);
GBMemoryInit(gb);
GBVideoInit(&gb->video);
gba->romVf = 0;
gb->romVf = 0;
gba->pristineRom = 0;
gba->pristineRomSize = 0;
gba->yankedRomSize = 0;
gb->pristineRom = 0;
gb->pristineRomSize = 0;
gb->yankedRomSize = 0;
}
bool GBLoadROM(struct GB* gb, struct VFile* vf, struct VFile* sav, const char* fname) {
@ -120,17 +123,62 @@ void GBReset(struct LR35902Core* cpu) {
gb->yankedRomSize = 0;
}
GBMemoryReset(gb);
gb->video.p = gb;
GBVideoReset(&gb->video);
}
void GBUpdateIRQs(struct GB* gb) {
if (!gb->memory.ime) {
return;
}
int irqs = gb->memory.ie & gb->memory.io[REG_IF];
if (!irqs) {
return;
}
if (irqs & (1 << GB_IRQ_VBLANK)) {
LR35902RaiseIRQ(gb->cpu, GB_VECTOR_VBLANK);
return;
}
if (irqs & (1 << GB_IRQ_LCDSTAT)) {
LR35902RaiseIRQ(gb->cpu, GB_VECTOR_LCDSTAT);
return;
}
if (irqs & (1 << GB_IRQ_TIMER)) {
LR35902RaiseIRQ(gb->cpu, GB_VECTOR_TIMER);
return;
}
if (irqs & (1 << GB_IRQ_SIO)) {
LR35902RaiseIRQ(gb->cpu, GB_VECTOR_SIO);
return;
}
if (irqs & (1 << GB_IRQ_KEYPAD)) {
LR35902RaiseIRQ(gb->cpu, GB_VECTOR_KEYPAD);
}
}
void GBProcessEvents(struct LR35902Core* cpu) {
// TODO
struct GB* gb = (struct GB*) cpu->master;
int32_t cycles = cpu->nextEvent;
int32_t nextEvent = INT_MAX;
int32_t testEvent;
testEvent = GBVideoProcessEvents(&gb->video, cycles);
if (testEvent < nextEvent) {
nextEvent = testEvent;
}
cpu->cycles -= cycles;
cpu->nextEvent = nextEvent;
}
void GBSetInterrupts(struct LR35902Core* cpu, bool enable) {
// TODO
struct GB* gb = (struct GB*) cpu->master;
gb->memory.ime = enable;
GBUpdateIRQs(gb);
}
void GBHitStub(struct LR35902Core* cpu) {
// TODO
printf("Hit stub at address %04X\n", cpu->pc);
//printf("Hit stub at address %04X\n", cpu->pc);
}

View File

@ -11,6 +11,7 @@
#include "lr35902/lr35902.h"
#include "gb/memory.h"
#include "gb/video.h"
extern const uint32_t DMG_LR35902_FREQUENCY;
extern const uint32_t CGB_LR35902_FREQUENCY;
@ -25,11 +26,20 @@ enum GBIRQ {
GB_IRQ_KEYPAD = 0x4,
};
enum GBIRQVector {
GB_VECTOR_VBLANK = 0x40,
GB_VECTOR_LCDSTAT = 0x48,
GB_VECTOR_TIMER = 0x50,
GB_VECTOR_SIO = 0x58,
GB_VECTOR_KEYPAD = 0x60,
};
struct GB {
struct LR35902Component d;
struct LR35902Core* cpu;
struct GBMemory memory;
struct GBVideo video;
int* keySource;
@ -47,9 +57,7 @@ void GBDestroy(struct GB* gb);
void GBReset(struct LR35902Core* cpu);
void GBWriteIE(struct GB* gb, uint8_t value);
void GBRaiseIRQ(struct GB* gb, enum GBIRQ irq);
void GBTestIRQ(struct LR35902Core* cpu);
void GBUpdateIRQs(struct GB* gb);
void GBHalt(struct GB* gb);
void GBStop(struct GB* gb);

52
src/gb/io.c Normal file
View File

@ -0,0 +1,52 @@
/* 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 "io.h"
#include "gb/gb.h"
void GBIOInit(struct GB* gb) {
memset(gb->memory.io, 0, sizeof(gb->memory.io));
}
void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) {
switch (address) {
case REG_IF:
gb->memory.io[REG_IF] = value;
GBUpdateIRQs(gb);
return;
case REG_LCDC:
// TODO: handle GBC differences
GBVideoWriteLCDC(&gb->video, value);
break;
case REG_IE:
gb->memory.ie = value;
GBUpdateIRQs(gb);
break;
default:
// TODO: Log
if (address >= GB_SIZE_IO) {
return;
}
break;
}
gb->memory.io[address] = value;
}
uint8_t GBIORead(struct GB* gb, unsigned address) {
switch (address) {
case REG_IF:
break;
case REG_IE:
return gb->memory.ie;
default:
// TODO: Log
if (address >= GB_SIZE_IO) {
return 0;
}
break;
}
return gb->memory.io[address];
}

86
src/gb/io.h Normal file
View File

@ -0,0 +1,86 @@
/* 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_IO_H
#define GB_IO_H
#include "util/common.h"
enum GBIORegisters {
REG_JOYP = 0x00,
REG_SB = 0x01,
REG_SC = 0x02,
// Timing
REG_DIV = 0x04,
REG_TIMA = 0x05,
REG_TMA = 0x06,
REG_TAC = 0x07,
// Interrupts
REG_IF = 0x0F,
REG_IE = 0xFF,
// Audio
REG_NR10 = 0x10,
REG_NR11 = 0x11,
REG_NR12 = 0x12,
REG_NR13 = 0x13,
REG_NR14 = 0x14,
REG_NR21 = 0x16,
REG_NR22 = 0x17,
REG_NR23 = 0x18,
REG_NR24 = 0x19,
REG_NR30 = 0x1A,
REG_NR31 = 0x1B,
REG_NR32 = 0x1C,
REG_NR33 = 0x1D,
REG_NR34 = 0x1E,
REG_NR41 = 0x20,
REG_NR42 = 0x21,
REG_NR43 = 0x22,
REG_NR44 = 0x23,
REG_NR50 = 0x24,
REG_NR51 = 0x25,
REG_NR52 = 0x26,
REG_WAVE_0 = 0x30,
REG_WAVE_1 = 0x31,
REG_WAVE_2 = 0x32,
REG_WAVE_3 = 0x33,
REG_WAVE_4 = 0x34,
REG_WAVE_5 = 0x35,
REG_WAVE_6 = 0x36,
REG_WAVE_7 = 0x37,
REG_WAVE_8 = 0x38,
REG_WAVE_9 = 0x39,
REG_WAVE_A = 0x3A,
REG_WAVE_B = 0x3B,
REG_WAVE_C = 0x3C,
REG_WAVE_D = 0x3D,
REG_WAVE_E = 0x3E,
REG_WAVE_F = 0x3F,
// Video
REG_LCDC = 0x40,
REG_STAT = 0x41,
REG_SCY = 0x42,
REG_SCX = 0x43,
REG_LY = 0x44,
REG_LYC = 0x45,
REG_DMA = 0x46,
REG_BGP = 0x47,
REG_OBP0 = 0x48,
REG_OBP1 = 0x49,
REG_WY = 0x4A,
REG_WX = 0x4B,
};
struct GB;
void GBIOInit(struct GB* gb);
void GBIOWrite(struct GB* gb, unsigned address, uint8_t value);
uint8_t GBIORead(struct GB* gb, unsigned address);
#endif

View File

@ -6,6 +6,7 @@
#include "memory.h"
#include "gb/gb.h"
#include "gb/io.h"
#include "util/memory.h"
@ -26,6 +27,10 @@ void GBMemoryInit(struct GB* gb) {
gb->memory.rom = 0;
gb->memory.romBank = 0;
gb->memory.romSize = 0;
memset(gb->memory.hram, 0, sizeof(gb->memory.hram));
GBIOInit(gb);
}
void GBMemoryDeinit(struct GB* gb) {
@ -80,8 +85,20 @@ uint8_t GBLoad8(struct LR35902Core* cpu, uint16_t address) {
case GB_REGION_WORKING_RAM_BANK1:
return memory->wramBank[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)];
default:
// TODO
return 0;
if (address < GB_BASE_OAM) {
return memory->wramBank[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)];
}
if (address < GB_BASE_IO) {
// TODO
return 0;
}
if (address < GB_BASE_HRAM) {
return GBIORead(gb, address & (GB_SIZE_IO - 1));
}
if (address < GB_BASE_IE) {
return memory->hram[address & GB_SIZE_HRAM];
}
return GBIORead(gb, REG_IE);
}
}
@ -121,8 +138,17 @@ void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t value) {
memory->wramBank[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)] = value;
return;
default:
// TODO
return;
if (address < GB_BASE_OAM) {
memory->wramBank[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)] = value;
} else if (address < GB_BASE_IO) {
// TODO
} else if (address < GB_BASE_HRAM) {
GBIOWrite(gb, address & (GB_SIZE_IO - 1), value);
} else if (address < GB_BASE_IE) {
memory->hram[address & GB_SIZE_HRAM] = value;
} else {
GBIOWrite(gb, REG_IE, value);
}
}
}

View File

@ -54,6 +54,12 @@ struct GBMemory {
uint8_t* wram;
uint8_t* wramBank;
uint8_t io[GB_SIZE_IO];
bool ime;
uint8_t ie;
uint8_t hram[GB_SIZE_HRAM];
size_t romSize;
};

166
src/gb/video.c Normal file
View File

@ -0,0 +1,166 @@
/* 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 "video.h"
#include "gb/gb.h"
#include "gb/io.h"
#include "util/memory.h"
static void GBVideoDummyRendererInit(struct GBVideoRenderer* renderer);
static void GBVideoDummyRendererReset(struct GBVideoRenderer* renderer);
static void GBVideoDummyRendererDeinit(struct GBVideoRenderer* renderer);
static uint8_t GBVideoDummyRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value);
static void GBVideoDummyRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address);
static void GBVideoDummyRendererDrawScanline(struct GBVideoRenderer* renderer, int y);
static void GBVideoDummyRendererFinishFrame(struct GBVideoRenderer* renderer);
static void GBVideoDummyRendererGetPixels(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels);
static struct GBVideoRenderer dummyRenderer = {
.init = GBVideoDummyRendererInit,
.reset = GBVideoDummyRendererReset,
.deinit = GBVideoDummyRendererDeinit,
.writeVideoRegister = GBVideoDummyRendererWriteVideoRegister,
.writeVRAM = GBVideoDummyRendererWriteVRAM,
.drawScanline = GBVideoDummyRendererDrawScanline,
.finishFrame = GBVideoDummyRendererFinishFrame,
.getPixels = GBVideoDummyRendererGetPixels
};
void GBVideoInit(struct GBVideo* video) {
video->renderer = &dummyRenderer;
video->vram = 0;
video->frameskip = 0;
}
void GBVideoReset(struct GBVideo* video) {
video->ly = 0;
video->mode = 1;
video->nextEvent = INT_MAX;
video->eventDiff = 0;
video->nextMode = INT_MAX;
video->frameCounter = 0;
video->frameskipCounter = 0;
if (video->vram) {
mappedMemoryFree(video->vram, GB_SIZE_VRAM);
}
video->vram = anonymousMemoryMap(GB_SIZE_VRAM);
video->renderer->vram = video->vram;
video->renderer->deinit(video->renderer);
video->renderer->init(video->renderer);
}
void GBVideoDeinit(struct GBVideo* video) {
GBVideoAssociateRenderer(video, &dummyRenderer);
mappedMemoryFree(video->vram, GB_SIZE_VRAM);
}
void GBVideoAssociateRenderer(struct GBVideo* video, struct GBVideoRenderer* renderer) {
// TODO
}
int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) {
video->eventDiff += cycles;
if (video->nextEvent != INT_MAX) {
video->nextEvent -= cycles;
}
if (video->nextEvent <= 0) {
if (video->nextEvent != INT_MAX) {
video->nextMode -= video->eventDiff;
}
if (video->nextMode <= 0) {
video->mode = (video->mode + 1) & 3;
switch (video->mode) {
case 0:
video->nextMode = GB_VIDEO_MODE_0_LENGTH;
break;
case 1:
video->nextMode = GB_VIDEO_MODE_1_LENGTH;
break;
case 2:
video->nextMode = GB_VIDEO_MODE_2_LENGTH;
++video->ly;
if (video->ly >= GB_VIDEO_VERTICAL_TOTAL_PIXELS) {
video->ly = 0;
++video->frameCounter;
}
video->p->memory.io[REG_LY] = video->ly;
break;
case 3:
video->nextMode = GB_VIDEO_MODE_3_LENGTH;
break;
}
}
video->nextEvent = video->nextMode;
video->eventDiff = 0;
}
return video->nextEvent;
}
void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) {
if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) && GBRegisterLCDCIsEnable(value)) {
// TODO: Does enabling the LCD start in vblank?
video->mode = 2;
video->nextMode = GB_VIDEO_MODE_2_LENGTH;
video->nextEvent = video->nextMode;
video->eventDiff = 0;
if (video->nextEvent < video->p->cpu->nextEvent) {
video->p->cpu->nextEvent = video->nextEvent;
}
return;
}
}
static void GBVideoDummyRendererInit(struct GBVideoRenderer* renderer) {
UNUSED(renderer);
// Nothing to do
}
static void GBVideoDummyRendererReset(struct GBVideoRenderer* renderer) {
UNUSED(renderer);
// Nothing to do
}
static void GBVideoDummyRendererDeinit(struct GBVideoRenderer* renderer) {
UNUSED(renderer);
// Nothing to do
}
static uint8_t GBVideoDummyRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value) {
UNUSED(renderer);
UNUSED(address);
return value;
}
static void GBVideoDummyRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address) {
UNUSED(renderer);
UNUSED(address);
// Nothing to do
}
static void GBVideoDummyRendererDrawScanline(struct GBVideoRenderer* renderer, int y) {
UNUSED(renderer);
UNUSED(y);
// Nothing to do
}
static void GBVideoDummyRendererFinishFrame(struct GBVideoRenderer* renderer) {
UNUSED(renderer);
// Nothing to do
}
static void GBVideoDummyRendererGetPixels(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels) {
UNUSED(renderer);
UNUSED(stride);
UNUSED(pixels);
// Nothing to do
}

78
src/gb/video.h Normal file
View File

@ -0,0 +1,78 @@
/* 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_VIDEO_H
#define GB_VIDEO_H
#include "util/common.h"
#include "gb/memory.h"
enum {
GB_VIDEO_HORIZONTAL_PIXELS = 160,
GB_VIDEO_VERTICAL_PIXELS = 144,
GB_VIDEO_VBLANK_PIXELS = 10,
GB_VIDEO_VERTICAL_TOTAL_PIXELS = GB_VIDEO_VERTICAL_PIXELS + GB_VIDEO_VBLANK_PIXELS,
GB_VIDEO_MODE_0_LENGTH = 203, // Estimates, figure out with more precision
GB_VIDEO_MODE_2_LENGTH = 81,
GB_VIDEO_MODE_3_LENGTH = 172,
GB_VIDEO_HORIZONTAL_LENGTH = GB_VIDEO_MODE_0_LENGTH + GB_VIDEO_MODE_2_LENGTH + GB_VIDEO_MODE_3_LENGTH,
GB_VIDEO_MODE_1_LENGTH = GB_VIDEO_HORIZONTAL_LENGTH * GB_VIDEO_VBLANK_PIXELS,
GB_VIDEO_TOTAL_LENGTH = GB_VIDEO_HORIZONTAL_LENGTH * GB_VIDEO_VERTICAL_TOTAL_PIXELS,
};
struct GBVideoRenderer {
void (*init)(struct GBVideoRenderer* renderer);
void (*reset)(struct GBVideoRenderer* renderer);
void (*deinit)(struct GBVideoRenderer* renderer);
uint8_t (*writeVideoRegister)(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value);
void (*writeVRAM)(struct GBVideoRenderer* renderer, uint16_t address);
void (*drawScanline)(struct GBVideoRenderer* renderer, int y);
void (*finishFrame)(struct GBVideoRenderer* renderer);
void (*getPixels)(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels);
void (*putPixels)(struct GBVideoRenderer* renderer, unsigned stride, void* pixels);
uint8_t* vram;
};
DECL_BITFIELD(GBRegisterLCDC, uint8_t);
DECL_BIT(GBRegisterLCDC, Enable, 7);
DECL_BITFIELD(GBRegisterSTAT, uint8_t);
struct GBVideo {
struct GB* p;
struct GBVideoRenderer* renderer;
int ly;
int mode;
int32_t nextEvent;
int32_t eventDiff;
int32_t nextMode;
uint8_t* vram;
int32_t frameCounter;
int frameskip;
int frameskipCounter;
};
void GBVideoInit(struct GBVideo* video);
void GBVideoReset(struct GBVideo* video);
void GBVideoDeinit(struct GBVideo* video);
void GBVideoAssociateRenderer(struct GBVideo* video, struct GBVideoRenderer* renderer);
int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles);
void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value);
void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value);
#endif

View File

@ -69,8 +69,9 @@ void LR35902Reset(struct LR35902Core* cpu) {
cpu->irqh.reset(cpu);
}
void LR35902RaiseIRQ(struct LR35902Core* cpu) {
// TODO
void LR35902RaiseIRQ(struct LR35902Core* cpu, uint8_t vector) {
cpu->irqPending = true;
cpu->irqVector = vector;
}
void LR35902Tick(struct LR35902Core* cpu) {
@ -80,6 +81,12 @@ void LR35902Tick(struct LR35902Core* cpu) {
cpu->executionState &= 3;
switch (state) {
case LR35902_CORE_FETCH:
if (cpu->irqPending) {
cpu->pc = cpu->irqVector;
cpu->irqPending = false;
cpu->irqh.setInterrupts(cpu, false);
// TODO: stall
}
cpu->bus = cpu->memory.load8(cpu, cpu->pc);
break;
case LR35902_CORE_DECODE:

View File

@ -120,6 +120,9 @@ struct LR35902Core {
bool condition;
LR35902Instruction instruction;
bool irqPending;
uint16_t irqVector;
struct LR35902Memory memory;
struct LR35902InterruptHandler irqh;
@ -142,7 +145,7 @@ void LR35902HotplugAttach(struct LR35902Core* cpu, size_t slot);
void LR35902HotplugDetach(struct LR35902Core* cpu, size_t slot);
void LR35902Reset(struct LR35902Core* cpu);
void LR35902RaiseIRQ(struct LR35902Core*);
void LR35902RaiseIRQ(struct LR35902Core* cpu, uint8_t vector);
void LR35902Tick(struct LR35902Core* cpu);