GBA Memory: Stall on VRAM access in mode 2 (fixes #190)
@@ -19,6 +19,7 @@ - GBA BIOS: Improve HLE BIOS timing
- GBA BIOS: Fix reloading video registers after reset (fixes mgba.io/i/1808) - GBA DMA: Linger last DMA on bus (fixes mgba.io/i/301 and mgba.io/i/1320) - GBA Memory: Improve gamepak prefetch timing + - GBA Memory: Stall on VRAM access in mode 2 (fixes mgba.io/i/190) - GBA SIO: Fix copying Normal mode transfer values - GBA Video: Latch scanline at end of Hblank (fixes mgba.io/i/1319) - GBA Video: Fix Hblank timing
@@ -206,8 +206,8 @@ struct GBA* p;
struct GBAVideoRenderer* renderer; struct mTimingEvent event; - // VCOUNT int vcount; + int shouldStall; uint16_t palette[512]; uint16_t* vram;
@@ -29,6 +29,7 @@ static uint8_t _agbPrintFunc[4] = { 0xFA, 0xDF /* swi 0xFF */, 0x70, 0x47 /* bx lr */ };
static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t region); static int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait); +static int32_t GBAMemoryStallVRAM(struct GBA* gba, int32_t wait, int extra); static const char GBA_BASE_WAITSTATES[16] = { 0, 0, 2, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4 }; static const char GBA_BASE_WAITSTATES_32[16] = { 0, 0, 5, 0, 0, 1, 1, 0, 7, 7, 9, 9, 13, 13, 9 };@@ -374,7 +375,10 @@ } \
} else { \ LOAD_32(value, address & 0x0001FFFC, gba->video.vram); \ } \ - wait += waitstatesRegion[REGION_VRAM]; + ++wait; \ + if (gba->video.shouldStall) { \ + wait += GBAMemoryStallVRAM(gba, wait, 1); \ + } #define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw);@@ -534,6 +538,9 @@ LOAD_16(value, address & 0x00017FFE, gba->video.vram);
} else { LOAD_16(value, address & 0x0001FFFE, gba->video.vram); } + if (gba->video.shouldStall) { + wait += GBAMemoryStallVRAM(gba, wait, 0); + } break; case REGION_OAM: LOAD_16(value, address & (SIZE_OAM - 2), gba->video.oam.raw);@@ -649,6 +656,9 @@ }
value = ((uint8_t*) gba->video.vram)[address & 0x00017FFF]; } else { value = ((uint8_t*) gba->video.vram)[address & 0x0001FFFF]; + } + if (gba->video.shouldStall) { + wait += GBAMemoryStallVRAM(gba, wait, 0); } break; case REGION_OAM:@@ -751,7 +761,10 @@ gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC) + 2); \
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC)); \ } \ } \ - wait += waitstatesRegion[REGION_VRAM]; + ++wait; \ + if (gba->video.shouldStall) { \ + wait += GBAMemoryStallVRAM(gba, wait, 1); \ + } #define STORE_OAM \ LOAD_32(oldValue, address & (SIZE_OAM - 4), gba->video.oam.raw); \@@ -876,6 +889,9 @@ STORE_16(value, address & 0x0001FFFE, gba->video.vram);
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE); } } + if (gba->video.shouldStall) { + wait += GBAMemoryStallVRAM(gba, wait, 0); + } break; case REGION_OAM: LOAD_16(oldValue, address & (SIZE_OAM - 2), gba->video.oam.raw);@@ -976,6 +992,9 @@ oldValue = gba->video.renderer->vram[(address & 0x1FFFE) >> 1];
if (oldValue != (((uint8_t) value) | (value << 8))) { gba->video.renderer->vram[(address & 0x1FFFE) >> 1] = ((uint8_t) value) | (value << 8); gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE); + } + if (gba->video.shouldStall) { + wait += GBAMemoryStallVRAM(gba, wait, 0); } break; case REGION_OAM:@@ -1656,6 +1675,28 @@ // The next |loads|S waitstates disappear entirely, so long as they're all in a row
wait -= stall - 1; return wait; +} + +int32_t GBAMemoryStallVRAM(struct GBA* gba, int32_t wait, int extra) { + UNUSED(extra); + // TODO + uint16_t dispcnt = gba->memory.io[REG_DISPCNT >> 1]; + int32_t stall = 0; + switch (GBARegisterDISPCNTGetMode(dispcnt)) { + case 2: + if (GBARegisterDISPCNTIsBg2Enable(dispcnt) && GBARegisterDISPCNTIsBg3Enable(dispcnt)) { + // If both backgrounds are enabled, VRAM access is entirely blocked during hdraw + stall = mTimingUntil(&gba->timing, &gba->video.event); + } + break; + default: + return 0; + } + stall -= wait; + if (stall < 0) { + return 0; + } + return stall; } void GBAMemorySerialize(const struct GBAMemory* memory, struct GBASerializedState* state) {
@@ -95,6 +95,7 @@
video->frameCounter = 0; video->frameskipCounter = 0; video->renderer->vram = video->vram; + video->shouldStall = 0; memset(video->palette, 0, sizeof(video->palette)); memset(video->oam.raw, 0, sizeof(video->oam.raw));@@ -139,6 +140,7 @@ video->p->memory.io[REG_VCOUNT >> 1] = video->vcount;
if (video->vcount < GBA_VIDEO_VERTICAL_PIXELS && video->frameskipCounter <= 0) { video->renderer->drawScanline(video->renderer, video->vcount); + video->shouldStall = 1; } GBARegisterDISPSTAT dispstat = video->p->memory.io[REG_DISPSTAT >> 1];@@ -198,6 +200,7 @@ }
if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) { GBARaiseIRQ(video->p, IRQ_HBLANK, cyclesLate); } + video->shouldStall = 0; video->p->memory.io[REG_DISPSTAT >> 1] = dispstat; }@@ -338,6 +341,7 @@ GBAStore16(video->p->cpu, BASE_PALETTE_RAM | i, value, 0);
} LOAD_32(video->frameCounter, 0, &state->video.frameCounter); + video->shouldStall = 0; int32_t flags; LOAD_32(flags, 0, &state->video.flags); GBARegisterDISPSTAT dispstat = state->io[REG_DISPSTAT >> 1];@@ -354,6 +358,7 @@ video->event.callback = _startHdraw;
break; case 2: video->event.callback = _startHblank; + video->shouldStall = 1; break; case 3: video->event.callback = _midHblank;