all repos — mgba @ 8622ba7ed0d556e2c0a4c8e7c9aa8b349ace4046

mGBA Game Boy Advance Emulator

GB: Add basic I/O, interrupts and video
Jeffrey Pfau jeffrey@endrift.com
Thu, 14 Jan 2016 20:50:43 -0800
commit

8622ba7ed0d556e2c0a4c8e7c9aa8b349ace4046

parent

64676529ba1919ac9996aa90344a4fa9c31969e3

M src/gb/gb.csrc/gb/gb.c

@@ -5,6 +5,8 @@ * 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 "gb.h" +#include "gb/io.h" + #include "util/crc32.h" #include "util/memory.h" #include "util/math.h"

@@ -30,17 +32,18 @@ gb->d.deinit = 0;

} 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 @@ gb->memory.romSize = gb->yankedRomSize;

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); }
M src/gb/gb.hsrc/gb/gb.h

@@ -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 @@ GB_IRQ_SIO = 0x3,

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);
A src/gb/io.c

@@ -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]; +}
A src/gb/io.h

@@ -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
M src/gb/memory.csrc/gb/memory.c

@@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "memory.h" #include "gb/gb.h" +#include "gb/io.h" #include "util/memory.h"

@@ -26,6 +27,10 @@ gb->memory.wramBank = 0;

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 @@ return memory->wram[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)];

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 @@ case GB_REGION_WORKING_RAM_BANK1:

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); + } } }
M src/gb/memory.hsrc/gb/memory.h

@@ -54,6 +54,12 @@

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; };
A src/gb/video.c

@@ -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 +}
A src/gb/video.h

@@ -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
M src/lr35902/lr35902.csrc/lr35902/lr35902.c

@@ -69,8 +69,9 @@

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 @@ ++cpu->executionState;

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:
M src/lr35902/lr35902.hsrc/lr35902/lr35902.h

@@ -120,6 +120,9 @@ uint8_t bus;

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);