Skip to content

Commit

Permalink
m32x vdp fixes
Browse files Browse the repository at this point in the history
- SH2 memory windows have been adjusted. The framebuffer areas must be mirrored up to 0x5ffffff -- fixes Toughman Contest backgrounds.
- VDP FEN & PEN flags are closer to accurate but still missing some finer timing details.
- FEN/PEN status may block vdp memory accesses. The docs seem to indicate that accesses may be held in wait until a status change, but thats not totally clear (nor validated?). For now, these accesses are blocked and logged in debug.
- FM setting (framebufferAccess) only allows vdp access by either 68k or SH2-side, so additional guards have been inserted for cram/framebuffer accesses. VDP reg accesses should be blocked as well according to docs, but this aspect needs to be reviewed.
- VDP reg latching has been implemented. The timing isnt completely accurate, but should be decent enough for now. Fixes Mars Check image test (corrupt lines from mode switching).
- Autofill timing has been implemented according to the docs. For now, this is just signaling via the FEN flag. Fixes MK II glitching et al.
  • Loading branch information
TascoDLX authored and LukeUsher committed Feb 7, 2025
1 parent 04a51df commit e4e9044
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 37 deletions.
4 changes: 4 additions & 0 deletions ares/md/m32x/bus-external.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ auto M32X::readExternal(n1 upper, n1 lower, n24 address, n16 data) -> n16 {
}

if(address >= 0x840000 && address <= 0x87ffff) {
if(vdp.framebufferAccess) return data;
if(vdp.framebufferEngaged()) { debug(unusual, "[32X FB] 68k read while FEN==1"); return data; } // wait instead?
return vdp.bbram[address >> 1 & 0xffff];
}

Expand Down Expand Up @@ -41,6 +43,7 @@ auto M32X::writeExternal(n1 upper, n1 lower, n24 address, n16 data) -> void {

if(address >= 0x840000 && address <= 0x85ffff) {
if(vdp.framebufferAccess) return;
if(vdp.framebufferEngaged()) { debug(unusual, "[32X FB] 68k write while FEN==1"); return; } // wait instead?
if(!data && (!upper || !lower)) return; //8-bit 0x00 writes do not go through
shm.debugger.tracer.instruction->invalidate(0x0400'0000 | address & 0x1fffe);
shs.debugger.tracer.instruction->invalidate(0x0400'0000 | address & 0x1fffe);
Expand All @@ -51,6 +54,7 @@ auto M32X::writeExternal(n1 upper, n1 lower, n24 address, n16 data) -> void {

if(address >= 0x860000 && address <= 0x87ffff) {
if(vdp.framebufferAccess) return;
if(vdp.framebufferEngaged()) { debug(unusual, "[32X FB] 68k overwrite while FEN==1"); return; } // wait instead?
shm.debugger.tracer.instruction->invalidate(0x0402'0000 | address & 0x1fffe);
shs.debugger.tracer.instruction->invalidate(0x0402'0000 | address & 0x1fffe);
if(upper && data.byte(1)) vdp.bbram[address >> 1 & 0xffff].byte(1) = data.byte(1);
Expand Down
49 changes: 24 additions & 25 deletions ares/md/m32x/bus-internal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,21 @@ auto M32X::readInternal(n1 upper, n1 lower, n32 address, n16 data) -> n16 {
return readInternalIO(upper, lower, address, data);
}

if(address >= 0x0200'0000 && address <= 0x023f'ffff) {
if(address >= 0x0200'0000 && address <= 0x03ff'ffff) {
while(dreq.vram) {
// SH2 ROM accesses stall while RV is set
if(shm.active()) { shm.internalStep(1); shm.syncM68k(true); }
if(shs.active()) { shs.internalStep(1); shs.syncM68k(true); }
if(shs.active()) { shs.internalStep(1); shs.syncM68k(true); }
}

// TODO: SH2 ROM accesses need to stall while the m68k is on the bus
if(shm.active()) shm.internalStep(6); if(shs.active()) shs.internalStep(6);
return cartridge.child->read(upper, lower, address, data);
}

if(address >= 0x0400'0000 && address <= 0x0405'ffff) {
if(address >= 0x0400'0000 && address <= 0x05ff'ffff) {
if (!vdp.framebufferAccess) return data;
if(vdp.framebufferEngaged()) { debug(unusual, "[32X FB] SH2 read while FEN==1"); return data; } // wait instead?
if(shm.active()) shm.internalStep(5); if(shs.active()) shs.internalStep(5);
return vdp.bbram[address >> 1 & 0xffff];
}
Expand All @@ -38,30 +40,27 @@ auto M32X::writeInternal(n1 upper, n1 lower, n32 address, n16 data) -> void {
return writeInternalIO(upper, lower, address, data);
}

if(address >= 0x0400'0000 && address <= 0x0401'ffff) {
if (!vdp.framebufferAccess) return;
if(!data && (!upper || !lower)) return; //8-bit 0x00 writes do not go through
if(shm.active()) shm.internalStep(4); if(shs.active()) shs.internalStep(4);
if(upper) vdp.bbram[address >> 1 & 0xffff].byte(1) = data.byte(1);
if(lower) vdp.bbram[address >> 1 & 0xffff].byte(0) = data.byte(0);
return;
}
if(address >= 0x0400'0000 && address <= 0x05ff'ffff) {
address &= 0x0403'ffff;

if(address >= 0x0402'0000 && address <= 0x0403'ffff) {
if (!vdp.framebufferAccess) return;
if(shm.active()) shm.internalStep(4); if(shs.active()) shs.internalStep(4);
if(upper && data.byte(1)) vdp.bbram[address >> 1 & 0xffff].byte(1) = data.byte(1);
if(lower && data.byte(0)) vdp.bbram[address >> 1 & 0xffff].byte(0) = data.byte(0);
return;
}
if(address >= 0x0400'0000 && address <= 0x0401'ffff) {
if (!vdp.framebufferAccess) return;
if(vdp.framebufferEngaged()) { debug(unusual, "[32X FB] SH2 write while FEN==1"); return; } // wait instead?
if(!data && (!upper || !lower)) return; //8-bit 0x00 writes do not go through
if(shm.active()) shm.internalStep(4); if(shs.active()) shs.internalStep(4);
if(upper) vdp.bbram[address >> 1 & 0xffff].byte(1) = data.byte(1);
if(lower) vdp.bbram[address >> 1 & 0xffff].byte(0) = data.byte(0);
return;
}

if(address >= 0x0404'0000 && address <= 0x0405'ffff) {
if (!vdp.framebufferAccess) return;
if(!data && (!upper || !lower)) return; //8-bit 0x00 writes do not go through
if(shm.active()) shm.internalStep(4); if(shs.active()) shs.internalStep(4);
if(upper) vdp.bbram[address >> 1 & 0xffff].byte(1) = data.byte(1);
if(lower) vdp.bbram[address >> 1 & 0xffff].byte(0) = data.byte(0);
return;
if(address >= 0x0402'0000 && address <= 0x0403'ffff) {
if (!vdp.framebufferAccess) return;
if(vdp.framebufferEngaged()) { debug(unusual, "[32X FB] SH2 overwrite while FEN==1"); return; } // wait instead?
if(shm.active()) shm.internalStep(4); if(shs.active()) shs.internalStep(4);
if(upper && data.byte(1)) vdp.bbram[address >> 1 & 0xffff].byte(1) = data.byte(1);
if(lower && data.byte(0)) vdp.bbram[address >> 1 & 0xffff].byte(0) = data.byte(0);
return;
}
}

if(address >= 0x0600'0000 && address <= 0x0603'ffff) {
Expand Down
8 changes: 6 additions & 2 deletions ares/md/m32x/io-external.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,18 @@ auto M32X::readExternalIO(n1 upper, n1 lower, n24 address, n16 data) -> n16 {
//frame buffer control
if(address == 0xa1518a) {
data.bit( 0) = vdp.framebufferActive;
data.bit( 1) = MegaDrive::vdp.refreshing(); //framebuffer access
data.bit( 1) = MegaDrive::vdp.refreshing()
|| vdp.framebufferEngaged(); // FEN: frame buffer engaged
data.bit( 2, 12) = 0;
data.bit(13) = vdp.vblank || vdp.hblank; //palette access
data.bit(13) = !vdp.paletteEngaged(); // PEN: can access palette
data.bit(14) = vdp.hblank;
data.bit(15) = vdp.vblank;
}

//palette
if(address >= 0xa15200 && address <= 0xa153ff) {
if (vdp.framebufferAccess) return data;
if(vdp.paletteEngaged()) { debug(unusual, "[32X CRAM] 68k read while PEN==0"); return data; } // wait instead?
data = vdp.cram[address >> 1 & 0xff];
}

Expand Down Expand Up @@ -358,6 +361,7 @@ auto M32X::writeExternalIO(n1 upper, n1 lower, n24 address, n16 data) -> void {
//palette
if(address >= 0xa15200 && address <= 0xa153ff) {
if (vdp.framebufferAccess) return;
if(vdp.paletteEngaged()) { debug(unusual, "[32X CRAM] 68k write while PEN==0"); return; } // wait instead?
if(upper) vdp.cram[address >> 1 & 0xff].byte(1) = data.byte(1);
if(lower) vdp.cram[address >> 1 & 0xff].byte(0) = data.byte(0);
}
Expand Down
8 changes: 6 additions & 2 deletions ares/md/m32x/io-internal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,17 @@ auto M32X::readInternalIO(n1 upper, n1 lower, n29 address, n16 data) -> n16 {
if(shm.active()) shm.syncM68k();
if(shs.active()) shs.syncM68k();
data.bit( 0) = vdp.framebufferActive;
data.bit( 1) = MegaDrive::vdp.refreshing(); //framebuffer access
data.bit(13) = vdp.vblank || vdp.hblank; //palette access
data.bit( 1) = MegaDrive::vdp.refreshing()
|| vdp.framebufferEngaged(); // FEN: frame buffer engaged
data.bit(13) = !vdp.paletteEngaged(); // PEN: can access palette
data.bit(14) = vdp.hblank;
data.bit(15) = vdp.vblank;
}

//palette
if(address >= 0x4200 && address <= 0x43ff) {
if (!vdp.framebufferAccess) return data;
if(vdp.paletteEngaged()) { debug(unusual, "[32X CRAM] SH2 read while PEN==0"); return data; } // wait instead?
if(shm.active()) shm.internalStep(4); if(shs.active()) shs.internalStep(4);
data = vdp.cram[address >> 1 & 0xff];
}
Expand Down Expand Up @@ -340,6 +343,7 @@ auto M32X::writeInternalIO(n1 upper, n1 lower, n29 address, n16 data) -> void {
//palette
if(address >= 0x4200 && address <= 0x43ff) {
if (!vdp.framebufferAccess) return;
if(vdp.paletteEngaged()) { debug(unusual, "[32X CRAM] SH2 write while PEN==0"); return; } // wait instead?
if(shm.active()) shm.internalStep(4); if(shs.active()) shs.internalStep(4);
if(upper) vdp.cram[address >> 1 & 0xff].byte(1) = data.byte(1);
if(lower) vdp.cram[address >> 1 & 0xff].byte(0) = data.byte(0);
Expand Down
7 changes: 7 additions & 0 deletions ares/md/m32x/m32x.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ auto M32X::vblank(bool line) -> void {
}

auto M32X::hblank(bool line) -> void {
if(vdp.hblank > line) {
// TODO: VDP regs should be latched 192 MClks (~82 cycles) before end of hblank (according to official docs)
vdp.latch.mode = vdp.mode;
vdp.latch.lines = vdp.lines;
vdp.latch.priority = vdp.priority;
vdp.latch.dotshift = vdp.dotshift;
}
vdp.hblank = line;
shm.irq.hint.active = 0;
shs.irq.hint.active = 0;
Expand Down
10 changes: 10 additions & 0 deletions ares/md/m32x/m32x.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ struct M32X {
auto plot(u32* output, u16 color) -> void;
auto fill() -> void;
auto selectFramebuffer(n1 active) -> void;
auto framebufferEngaged() -> bool;
auto paletteEngaged() -> bool;

//serialization.cpp
auto serialize(serializer&) -> void;
Expand All @@ -117,8 +119,16 @@ struct M32X {
n1 framebufferAccess;
n1 framebufferActive;
n1 framebufferSelect;
int framebufferWait;
n1 hblank;
n1 vblank;

struct Latch {
n2 mode;
n1 lines;
n1 priority;
n1 dotshift;
} latch;
};

struct PWM : Thread {
Expand Down
5 changes: 5 additions & 0 deletions ares/md/m32x/serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,17 @@ auto M32X::VDP::serialize(serializer& s) -> void {
s(lines);
s(priority);
s(dotshift);
s(latch.mode);
s(latch.lines);
s(latch.priority);
s(latch.dotshift);
s(autofillLength);
s(autofillAddress);
s(autofillData);
s(framebufferAccess);
s(framebufferActive);
s(framebufferSelect);
s(framebufferWait);
s(hblank);
s(vblank);
selectFramebuffer(framebufferSelect);
Expand Down
2 changes: 2 additions & 0 deletions ares/md/m32x/sh7604.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ auto M32X::SH7604::step(u32 clocks) -> void {
cyclesUntilSh2Sync -= clocks;
cyclesUntilM68kSync -= clocks;

m32x.vdp.framebufferWait -= min(clocks, m32x.vdp.framebufferWait);

if(cyclesUntilSh2Sync <= 0) {
cyclesUntilSh2Sync = minCyclesBetweenSh2Syncs;
if (m32x.shm.active()) Thread::synchronize(m32x.shs);
Expand Down
29 changes: 22 additions & 7 deletions ares/md/m32x/vdp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,30 @@ auto M32X::VDP::power(bool reset) -> void {
lines = 0;
priority = 0;
dotshift = 0;
latch = {};
autofillLength = 0;
autofillAddress = 0;
autofillData = 0;
framebufferAccess = 0;
framebufferActive = 0;
framebufferSelect = 0;
framebufferWait = 0;
hblank = 0;
vblank = 1;
selectFramebuffer(framebufferSelect);
}

auto M32X::VDP::scanline(u32 pixels[1280], u32 y) -> void {
if(!Mega32X() || !pixels || y >= (lines ? 240 : 224)) return;
if(mode == 1) return scanlineMode1(pixels, y);
if(mode == 2) return scanlineMode2(pixels, y);
if(mode == 3) return scanlineMode3(pixels, y);
if(!Mega32X() || !pixels || y >= (latch.lines ? 240 : 224)) return;
if(latch.mode == 1) return scanlineMode1(pixels, y);
if(latch.mode == 2) return scanlineMode2(pixels, y);
if(latch.mode == 3) return scanlineMode3(pixels, y);
}

auto M32X::VDP::scanlineMode1(u32 pixels[1280], u32 y) -> void {
u16 address = fbram[y];
for(u32 x : range(320)) {
u8 color = fbram[address + (x + dotshift >> 1) & 0xffff].byte(!(x + dotshift & 1));
u8 color = fbram[address + (x + latch.dotshift >> 1) & 0xffff].byte(!(x + latch.dotshift & 1));
plot(&pixels[x * 4], cram[color]);
}
}
Expand Down Expand Up @@ -70,7 +72,7 @@ auto M32X::VDP::plot(u32* output, u16 color) -> void {
n1 throughbit = color >> 15;
b1 opaque = color & 0x7fff;

if(priority == 0) {
if(latch.priority == 0) {
//Mega Drive has priority
if(throughbit || backdrop) {
output[0] = color | 1 << 15;
Expand All @@ -90,6 +92,8 @@ auto M32X::VDP::plot(u32* output, u16 color) -> void {
}

auto M32X::VDP::fill() -> void {
if(framebufferWait > 0) { debug(unusual, "[32X FILL] triggered before last fill finished"); return; }
framebufferWait = 7+3*(autofillLength+1); // according to official docs
for(u32 repeat : range(1 + autofillLength)) {
bbram[autofillAddress] = autofillData;
autofillAddress.byte(0)++;
Expand All @@ -98,9 +102,20 @@ auto M32X::VDP::fill() -> void {

auto M32X::VDP::selectFramebuffer(n1 select) -> void {
framebufferSelect = select;
if(!vblank && mode) return;
if(!vblank && latch.mode) return;

framebufferActive = select;
fbram = {dram.data() + 0x10000 * (select == 0), 0x10000};
bbram = {dram.data() + 0x10000 * (select == 1), 0x10000};
}

// back buffer access
auto M32X::VDP::framebufferEngaged() -> bool {
// TODO: 40 cycle wait at start of hblank (according to official docs)
return (latch.mode != 0 && framebufferSelect != framebufferActive) || framebufferWait > 0;
}

auto M32X::VDP::paletteEngaged() -> bool {
// TODO: not available for 24 MClks (~10 cycles) at start of hblank (according to official docs)
return !vblank && !hblank && latch.mode.bit(0);
}
2 changes: 1 addition & 1 deletion ares/md/system/serialization.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
static const string SerializerVersion = "v141.1";
static const string SerializerVersion = "v142";

auto System::serialize(bool synchronize) -> serializer {
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
Expand Down

0 comments on commit e4e9044

Please sign in to comment.