Skip to content

Commit

Permalink
PCE: Improved VRAM read/write delays
Browse files Browse the repository at this point in the history
Not perfect, but a lot closer than before. Matches hardware results pretty well in most scenarios, but most of it is guesswork
Fixed Wonder Momo and seems to not have any negative impacts on other games
  • Loading branch information
SourMesen committed Dec 23, 2023
1 parent d0f8980 commit b84d4f2
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 50 deletions.
5 changes: 5 additions & 0 deletions Core/PCE/PceCpu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,11 @@ void PceCpu::SetZeroNegativeFlags(uint8_t value)
}
}

void PceCpu::RunIdleCpuCycle()
{
ProcessCpuCycle();
}

void PceCpu::ProcessCpuCycle()
{
_state.CycleCount++;
Expand Down
2 changes: 2 additions & 0 deletions Core/PCE/PceCpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ class PceCpu final : public ISerializable
PceCpu(Emulator* emu, PceMemoryManager* memoryManager);

PceCpuState& GetState() { return _state; }

void RunIdleCpuCycle();

void Exec();

Expand Down
9 changes: 5 additions & 4 deletions Core/PCE/PceMemoryManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "PCE/PceVce.h"
#include "PCE/PceTimer.h"
#include "PCE/PcePsg.h"
#include "PCE/PceCpu.h"
#include "PCE/PceControlManager.h"
#include "PCE/CdRom/PceCdRom.h"
#include "Shared/MessageManager.h"
Expand Down Expand Up @@ -173,8 +174,8 @@ void PceMemoryManager::UpdateExecCallback()
}
}

_fastExec = _exec;
if(!_state.FastCpuSpeed) {
_fastExec = _exec;
_exec = &PceMemoryManager::ExecSlow;
}
}
Expand Down Expand Up @@ -268,12 +269,12 @@ uint8_t PceMemoryManager::ReadRegister(uint16_t addr)
void PceMemoryManager::WriteRegister(uint16_t addr, uint8_t value)
{
if(addr <= 0x3FF) {
_console->GetCpu()->RunIdleCpuCycle(); //CPU is delayed by 1 CPU cycle when reading/writing to VDC/VCE
_vpc->Write(addr, value);
Exec(); //CPU is delayed by 1 CPU cycle when reading/writing to VDC/VCE
} else if(addr <= 0x7FF) {
_console->GetCpu()->RunIdleCpuCycle(); //CPU is delayed by 1 CPU cycle when reading/writing to VDC/VCE
_vpc->DrawScanline();
_vce->Write(addr, value);
Exec(); //CPU is delayed by 1 CPU cycle when reading/writing to VDC/VCE
} else if(addr <= 0xBFF) {
//PSG
_psg->Write(addr, value);
Expand Down Expand Up @@ -310,8 +311,8 @@ void PceMemoryManager::WriteRegister(uint16_t addr, uint8_t value)
void PceMemoryManager::WriteVdc(uint16_t addr, uint8_t value)
{
if(_emu->ProcessMemoryWrite<CpuType::Pce>(addr, value, MemoryOperationType::Write)) {
_console->GetCpu()->RunIdleCpuCycle(); //CPU is delayed by 1 CPU cycle when reading/writing to VDC/VCE
_vpc->StVdcWrite(addr, value);
Exec(); //CPU is delayed by 1 CPU cycle when reading/writing to VDC/VCE
}
}

Expand Down
121 changes: 78 additions & 43 deletions Core/PCE/PceVdc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -426,9 +426,11 @@ void PceVdc::LoadBackgroundTiles()
void PceVdc::LoadBackgroundTilesWidth2(uint16_t end, uint16_t scrollOffset, uint16_t columnMask, uint16_t row)
{
for(uint16_t i = _loadBgLastCycle; i < end; i++) {
_allowVramAccess = false;
switch(i & 0x07) {
case 1: LoadBatEntry(scrollOffset, columnMask, row); break;
case 2: _allowVramAccess = true; break; //CPU
case 3: _allowVramAccess = true; break; //CPU
case 5: LoadTileDataCg0(row); break;
case 7: LoadTileDataCg1(row); break;
}
Expand All @@ -446,7 +448,6 @@ void PceVdc::LoadBackgroundTilesWidth4(uint16_t end, uint16_t scrollOffset, uint
//Load CG0 or CG1 based on CG mode flag
_tiles[_tileCount].TileData[0] = ReadVram(_tiles[_tileCount].TileAddr + (row & 0x07) + (_state.CgMode ? 8 : 0));
_tiles[_tileCount].TileData[1] = 0;
_allowVramAccess = false;
_tileCount++;
break;
}
Expand Down Expand Up @@ -941,13 +942,29 @@ void PceVdc::ProcessVramWrite()

void PceVdc::ProcessVramAccesses()
{
if(_transferDelay) {
_transferDelay -= 3;
if(_transferDelay > 0) {
return;
}
}

_transferDelay = 0;

bool inBgFetch = !_state.BurstModeEnabled && _state.HClock >= _loadBgStart && _state.HClock < _loadBgEnd;
bool accessBlocked = (
_state.SatbTransferRunning ||
(_vramDmaRunning && _vMode != PceVdcModeV::Vdw) ||
(inBgFetch && !_allowVramAccess) ||
(_state.SpritesEnabled && !inBgFetch && _vMode == PceVdcModeV::Vdw)
);
bool accessBlocked;

if(_vMode != PceVdcModeV::Vdw || _state.BurstModeEnabled || ((!_state.SpritesEnabled && !inBgFetch && _vMode == PceVdcModeV::Vdw))) {
//During SATB/VRAM DMA, prevent all transfers
//During vblank, forced blank (burst mode) or sprite fetching while sprites are disabled, allow a VRAM read/write every other dot
accessBlocked = (_state.SatbTransferRunning || _vramDmaRunning || ((_state.HClock / GetClockDivider()) & 0x01)) ? true : false;
} else {
//During active rendering, only allow access on the CPU slots available during background tile fetches
accessBlocked = (
(inBgFetch && !_allowVramAccess) ||
(_state.SpritesEnabled && !inBgFetch && _vMode == PceVdcModeV::Vdw)
);
}

if(!accessBlocked) {
if(_pendingMemoryRead) {
Expand All @@ -958,6 +975,42 @@ void PceVdc::ProcessVramAccesses()
}
}

void PceVdc::QueueMemoryRead()
{
//All of this is guesswork based on results from a test rom
//Read operations appear to be processed slightly slower than writes?
_pendingMemoryRead = true;
switch(GetClockDivider()) {
case 2: _transferDelay = 15; break; //5 exec ticks
case 3: _transferDelay = 24; break; //8 exec ticks
case 4: _transferDelay = 24; break; //8 exec ticks
}
}

void PceVdc::QueueMemoryWrite()
{
//All of this is guesswork based on results from a test rom
//Write operations appear to be processed slightly faster than reads?
_pendingMemoryWrite = true;
switch(GetClockDivider()) {
case 2: _transferDelay = 12; break; //4 exec ticks
case 3: _transferDelay = 18; break; //6 exec ticks
case 4: _transferDelay = 21; break; //7 exec ticks
}
}

void PceVdc::WaitForVramAccess()
{
//Stall the CPU when a read/write operation is pending
while(_pendingMemoryRead || _pendingMemoryWrite) {
//TODO timing, this is probably not quite right. CPU will be stalled until
//a VDC cycle that allows VRAM access is reached. This isn't always going to
//be a multiple of 3 master clocks like this currently assumes
_console->GetMemoryManager()->ExecFastCycle();
DrawScanline();
}
}

uint8_t PceVdc::ReadRegister(uint16_t addr)
{
DrawScanline();
Expand Down Expand Up @@ -989,48 +1042,25 @@ uint8_t PceVdc::ReadRegister(uint16_t addr)

//Reads to 2/3 will always return the read buffer, but the
//read address will only increment when register 2 is selected
case 2:
WaitForVramAccess();
case 2:
if(_pendingMemoryRead) {
//D&D Order of the Griffon breaks without this
WaitForVramAccess();
}
return (uint8_t)_state.ReadBuffer;

case 3:
WaitForVramAccess();

if(_pendingMemoryRead) {
WaitForVramAccess();
}
uint8_t value = _state.ReadBuffer >> 8;
if(_state.CurrentReg == 0x02) {
_pendingMemoryRead = true;
QueueMemoryRead();
}
return value;
}
}

bool PceVdc::IsVramAccessBlocked()
{
bool inBgFetch = !_state.BurstModeEnabled && _state.HClock >= _loadBgStart && _state.HClock < _loadBgEnd;
//TODO timing:
//does disabling sprites allow vram access during hblank?
//can you access vram after the VDC is done loading sprites for that scanline?
return (
_pendingMemoryRead ||
_pendingMemoryWrite ||
_state.SatbTransferRunning ||
(_vramDmaRunning && _vMode != PceVdcModeV::Vdw) ||
(inBgFetch && !_allowVramAccess) ||
(_state.SpritesEnabled && !inBgFetch && _vMode == PceVdcModeV::Vdw)
);
}

void PceVdc::WaitForVramAccess()
{
while(IsVramAccessBlocked()) {
//TODO timing, this is probably not quite right. CPU will be stalled until
//a VDC cycle that allows VRAM access is reached. This isn't always going to
//be a multiple of 3 master clocks like this currently assumes
_console->GetMemoryManager()->ExecFastCycle();
DrawScanline();
}
}

void PceVdc::WriteRegister(uint16_t addr, uint8_t value)
{
DrawScanline();
Expand All @@ -1045,20 +1075,24 @@ void PceVdc::WriteRegister(uint16_t addr, uint8_t value)
bool msb = (addr & 0x03) == 0x03;

switch(_state.CurrentReg) {
case 0x00: UpdateReg(_state.MemAddrWrite, value, msb); break;
case 0x00:
WaitForVramAccess(); //Wonder Momo has graphical issues without this
UpdateReg(_state.MemAddrWrite, value, msb);
break;

case 0x01:
WaitForVramAccess();
UpdateReg(_state.MemAddrRead, value, msb);
if(msb) {
_pendingMemoryRead = true;
QueueMemoryRead();
}
break;

case 0x02:
WaitForVramAccess();
if(msb) {
WaitForVramAccess();
UpdateReg(_state.VramData, value, true);
_pendingMemoryWrite = true;
QueueMemoryWrite();
} else {
UpdateReg(_state.VramData, value, false);
}
Expand Down Expand Up @@ -1279,6 +1313,7 @@ void PceVdc::Serialize(Serializer& s)

SV(_pendingMemoryRead);
SV(_pendingMemoryWrite);
SV(_transferDelay);

SV(_vramDmaRunning);
SV(_vramDmaReadCycle);
Expand Down
8 changes: 5 additions & 3 deletions Core/PCE/PceVdc.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class PceVdc final : public ISerializable

bool _pendingMemoryRead = false;
bool _pendingMemoryWrite = false;
int8_t _transferDelay = 0;

bool _vramDmaRunning = false;
bool _vramDmaReadCycle = false;
Expand Down Expand Up @@ -134,6 +135,10 @@ class PceVdc final : public ISerializable
void ProcessVramWrite();
__noinline void ProcessVramAccesses();

void QueueMemoryRead();
void QueueMemoryWrite();
void WaitForVramAccess();

uint8_t GetClockDivider();
uint16_t GetScanlineCount();
uint16_t DotsToClocks(int dots);
Expand Down Expand Up @@ -172,9 +177,6 @@ class PceVdc final : public ISerializable

__forceinline uint16_t ReadVram(uint16_t addr);

void WaitForVramAccess();
bool IsVramAccessBlocked();

template<bool hasSprites, bool hasSprite0, bool skipRender> __forceinline void InternalDrawScanline();

public:
Expand Down

0 comments on commit b84d4f2

Please sign in to comment.