CPU/PPU: Improved timing and implemented catch-up in PPU when registers are written to in the middle of a scanline

This commit is contained in:
Sour 2019-03-04 17:49:14 -05:00
parent 944f94b271
commit 4139f6dca8
8 changed files with 80 additions and 39 deletions

View file

@ -137,7 +137,7 @@ void Console::LoadRom(VirtualFile romFile, VirtualFile patchFile)
_memoryManager->Initialize(shared_from_this());
_cpu.reset(new Cpu(_memoryManager.get()));
_memoryManager->IncrementMasterClockValue<160>();
_memoryManager->IncrementMasterClockValue<162>();
//if(_debugger) {
//Reset debugger if it was running before

View file

@ -268,11 +268,13 @@ void Cpu::SEI()
void Cpu::REP()
{
Idle();
ClearFlags((uint8_t)_operand);
}
void Cpu::SEP()
{
Idle();
SetFlags((uint8_t)_operand);
if(CheckFlag(ProcFlags::IndexMode8)) {
//Truncate X/Y when 8-bit indexes are enabled

View file

@ -11,7 +11,9 @@ Cpu::Cpu(MemoryManager* memoryManager)
_state.SP = 0x1FF;
_state.EmulationMode = true;
_nmiFlag = false;
_prevNmiFlag = false;
_irqSource = (uint8_t)IrqSource::None;
_prevIrqSource = (uint8_t)IrqSource::None;
SetFlags(ProcFlags::MemoryMode8);
SetFlags(ProcFlags::IndexMode8);
}
@ -286,11 +288,12 @@ void Cpu::Exec()
case 0xFE: AddrMode_AbsIdxX(); INC(); break;
case 0xFF: AddrMode_AbsLngIdxX(); SBC(); break;
}
if(_nmiFlag) {
//Use the state of the IRQ/NMI flags on the previous cycle to determine if an IRQ is processed or not
if(_prevNmiFlag) {
ProcessInterrupt(_state.EmulationMode ? Cpu::LegacyNmiVector : Cpu::NmiVector);
_nmiFlag = false;
} else if(_irqSource && !CheckFlag(ProcFlags::IrqDisable)) {
} else if(_prevIrqSource && !CheckFlag(ProcFlags::IrqDisable)) {
ProcessInterrupt(_state.EmulationMode ? Cpu::LegacyIrqVector : Cpu::IrqVector);
}
}
@ -339,6 +342,8 @@ uint8_t Cpu::GetOpCode()
void Cpu::Idle()
{
#ifndef DUMMYCPU
_prevNmiFlag = _nmiFlag;
_prevIrqSource = _irqSource;
_memoryManager->IncrementMasterClockValue<6>();
#endif
}
@ -372,6 +377,8 @@ uint8_t Cpu::Read(uint32_t addr, MemoryOperationType type)
LogRead(addr, value);
return value;
#else
_prevNmiFlag = _nmiFlag;
_prevIrqSource = _irqSource;
return _memoryManager->Read(addr, type);
#endif
}
@ -415,6 +422,8 @@ void Cpu::Write(uint32_t addr, uint8_t value, MemoryOperationType type)
#ifdef DUMMYCPU
LogWrite(addr, value);
#else
_prevNmiFlag = _nmiFlag;
_prevIrqSource = _irqSource;
_memoryManager->Write(addr, value, type);
#endif
}

View file

@ -38,7 +38,9 @@ private:
CpuState _state;
uint32_t _operand;
bool _nmiFlag;
bool _prevNmiFlag;
uint8_t _irqSource;
uint8_t _prevIrqSource;
uint32_t GetProgramAddress(uint16_t addr);
uint32_t GetDataAddress(uint16_t addr);

View file

@ -14,7 +14,7 @@
void MemoryManager::Initialize(shared_ptr<Console> console)
{
_lastMasterClock = 0;
_cyclesToRun = 0;
_masterClock = 0;
_console = console;
_regs = console->GetInternalRegisters().get();
@ -102,7 +102,7 @@ void MemoryManager::GenerateMasterClockTable()
uint8_t page = (i & 0xFF);
if(page <= 0x1F) {
//Slow
_masterClockTable[j][i] = 6;
_masterClockTable[j][i] = 8;
} else if(page >= 0x20 && page <= 0x3F) {
//Fast
_masterClockTable[j][i] = 6;
@ -127,20 +127,26 @@ void MemoryManager::GenerateMasterClockTable()
void MemoryManager::IncrementMasterClock(uint32_t addr)
{
_previousSpeed = _masterClockTable[(uint8_t)_regs->IsFastRomEnabled()][addr >> 8];
_masterClock += _previousSpeed;
while(_lastMasterClock < _masterClock - 3) {
_ppu->Exec();
_lastMasterClock += 4;
}
IncrementMasterClockValue(_masterClockTable[(uint8_t)_regs->IsFastRomEnabled()][addr >> 8]);
}
void MemoryManager::IncrementMasterClockValue(uint16_t value)
{
_masterClock += value;
while(_lastMasterClock < _masterClock - 3) {
_cyclesToRun += value;
if(_cyclesToRun >= 12) {
_cyclesToRun -= 12;
_ppu->Exec();
_ppu->Exec();
_ppu->Exec();
} else if(_cyclesToRun >= 8) {
_cyclesToRun -= 8;
_ppu->Exec();
_ppu->Exec();
} else if(_cyclesToRun >= 4) {
_cyclesToRun -= 4;
_ppu->Exec();
_lastMasterClock += 4;
}
}

View file

@ -23,17 +23,16 @@ private:
shared_ptr<RegisterHandlerA> _registerHandlerA;
shared_ptr<RegisterHandlerB> _registerHandlerB;
InternalRegisters* _regs;
InternalRegisters *_regs;
shared_ptr<Ppu> _ppu;
IMemoryHandler* _handlers[0x100 * 0x10];
vector<unique_ptr<RamHandler>> _workRamHandlers;
uint8_t * _workRam;
uint8_t *_workRam;
uint64_t _masterClock;
uint8_t _previousSpeed;
uint64_t _lastMasterClock;
uint64_t _cyclesToRun;
uint8_t _masterClockTable[2][0x10000];
public:
@ -67,8 +66,9 @@ template<uint16_t value>
void MemoryManager::IncrementMasterClockValue()
{
_masterClock += value;
while(_lastMasterClock < _masterClock - 3) {
_cyclesToRun += value;
while(_cyclesToRun >= 4) {
_cyclesToRun -= 4;
_ppu->Exec();
_lastMasterClock += 4;
}
}

View file

@ -71,9 +71,19 @@ PpuState Ppu::GetState()
void Ppu::Exec()
{
//"normally dots 323 and 327 are 6 master cycles instead of 4."
//Add 1 extra dot to compensate (0-340 instead of 0-339)
//TODO fix this properly
if(_cycle == 340) {
_cycle = -1;
_scanline++;
_drawStartX = 0;
_drawEndX = 0;
_pixelsDrawn = 0;
_subPixelsDrawn = 0;
memset(_rowPixelFlags, 0, sizeof(_rowPixelFlags));
memset(_subScreenFilled, 0, sizeof(_subScreenFilled));
if(_scanline == (_overscanMode ? 240 : 225)) {
//Reset OAM address at the start of vblank?
@ -93,8 +103,9 @@ void Ppu::Exec()
}
} else if(_scanline == 240 && _frameCount & 0x01) {
//Skip 1 tick every other frame
//TODO : some modes don't skip this?
_cycle++;
} else if(_scanline == 261) {
} else if(_scanline == 262) {
_regs->SetNmiFlag(false);
_scanline = 0;
_rangeOver = false;
@ -109,6 +120,7 @@ void Ppu::Exec()
if(_regs->IsVerticalIrqEnabled() && !_regs->IsHorizontalIrqEnabled() && _scanline == _regs->GetVerticalTimer()) {
//An IRQ will occur sometime just after the V Counter reaches the value set in $4209/$420A.
_console->GetCpu()->SetIrqSource(IrqSource::Ppu);
_irqDelay = 4;
}
}
@ -117,18 +129,24 @@ void Ppu::Exec()
if(_regs->IsHorizontalIrqEnabled() && _cycle == _regs->GetHorizontalTimer() && (!_regs->IsVerticalIrqEnabled() || _scanline == _regs->GetVerticalTimer())) {
//An IRQ will occur sometime just after the H Counter reaches the value set in $4207/$4208.
_console->GetCpu()->SetIrqSource(IrqSource::Ppu);
_irqDelay = 4;
}
if(_irqDelay > 0) {
_irqDelay--;
if(_irqDelay == 0) {
_console->GetCpu()->SetIrqSource(IrqSource::Ppu);
}
}
if(_cycle == 278 && _scanline <= (_overscanMode ? 239 : 224)) {
if(_scanline != 0) {
RenderScanline();
} else {
EvaluateNextLineSprites();
}
EvaluateNextLineSprites();
_console->GetDmaController()->ProcessHdmaChannels();
} else if(_cycle == 134) {
//TODO Approximation
//TODO Approximation (DRAM refresh timing is not exact)
_console->GetMemoryManager()->IncrementMasterClockValue<40>();
}
}
@ -380,10 +398,7 @@ void Ppu::RenderMode7()
void Ppu::RenderScanline()
{
_pixelsDrawn = 0;
_subPixelsDrawn = 0;
memset(_rowPixelFlags, 0, sizeof(_rowPixelFlags));
memset(_subScreenFilled, 0, sizeof(_subScreenFilled));
_drawEndX = std::min(_cycle - 22, 255);
if(_forcedVblank) {
RenderBgColor<true>();
@ -437,9 +452,8 @@ void Ppu::RenderScanline()
ApplyColorMath();
ApplyBrightness<true>();
ApplyHiResMode();
//Process sprites for next scanline
EvaluateNextLineSprites();
_drawStartX = _drawEndX + 1;
}
template<bool forMainScreen>
@ -450,7 +464,7 @@ void Ppu::RenderBgColor()
}
uint16_t bgColor = _cgram[0] | (_cgram[1] << 8);
for(int x = 0; x < 256; x++) {
for(int x = _drawStartX; x <= _drawEndX; x++) {
if(forMainScreen) {
if(!_rowPixelFlags[x]) {
uint8_t pixelFlags = PixelFlags::Filled | ((_colorMathEnabled & 0x20) ? PixelFlags::AllowColorMath : 0);
@ -484,7 +498,7 @@ void Ppu::RenderSprites()
}
if(forMainScreen) {
for(int x = 0; x < 256; x++) {
for(int x = _drawStartX; x <= _drawEndX; x++) {
if(!_rowPixelFlags[x] && _spritePriority[x] == priority) {
if(activeWindowCount && ProcessMaskWindow<Ppu::SpriteLayerIndex>(activeWindowCount, x)) {
//This pixel was masked
@ -496,7 +510,7 @@ void Ppu::RenderSprites()
}
}
} else {
for(int x = 0; x < 256; x++) {
for(int x = _drawStartX; x < _drawEndX; x++) {
if(!_subScreenFilled[x] && _spritePriority[x] == priority) {
if(activeWindowCount && ProcessMaskWindow<Ppu::SpriteLayerIndex>(activeWindowCount, x)) {
//This pixel was masked
@ -562,7 +576,7 @@ void Ppu::RenderTilemap()
/* The tilemap address to read the tile data from */
uint16_t addr;
for(int x = 0; x < 256; x++) {
for(int x = _drawStartX; x <= _drawEndX; x++) {
if(hiResMode) {
realX = (x << 1) + (forMainScreen ? 1 : 0);
} else {
@ -791,7 +805,7 @@ void Ppu::RenderTilemapMode7()
uint8_t pixelFlags = PixelFlags::Filled | (((_colorMathEnabled >> layerIndex) & 0x01) ? PixelFlags::AllowColorMath : 0);
for(int x = 0; x < 256; x++) {
for(int x = _drawStartX; x <= _drawEndX; x++) {
uint16_t realX = _mode7.HorizontalMirroring ? (255 - x) : x;
if(forMainScreen) {
@ -884,7 +898,7 @@ void Ppu::ApplyColorMath()
uint8_t activeWindowCount = (uint8_t)_window[0].ActiveLayers[Ppu::ColorWindowIndex] + (uint8_t)_window[1].ActiveLayers[Ppu::ColorWindowIndex];
for(int x = 0; x < 256; x++) {
for(int x = _drawStartX; x < _drawEndX; x++) {
if(_rowPixelFlags[x] & PixelFlags::AllowColorMath) {
uint8_t halfShift = _colorMathHalveResult ? 1 : 0;
uint16_t &mainPixel = _mainScreenBuffer[x];
@ -966,7 +980,7 @@ template<bool forMainScreen>
void Ppu::ApplyBrightness()
{
if(_screenBrightness != 15) {
for(int x = 0; x < 256; x++) {
for(int x = _drawStartX; x < _drawEndX; x++) {
uint16_t &pixel = (forMainScreen ? _mainScreenBuffer : _subScreenBuffer)[x];
uint16_t r = (pixel & 0x1F) * _screenBrightness / 15;
uint16_t g = ((pixel >> 5) & 0x1F) * _screenBrightness / 15;
@ -1222,6 +1236,10 @@ uint8_t Ppu::Read(uint16_t addr)
void Ppu::Write(uint32_t addr, uint8_t value)
{
if(_scanline < (_overscanMode ? 239 : 224) && _scanline > 0 && _cycle >= 22 && _cycle <= 277) {
RenderScanline();
}
switch(addr) {
case 0x2100:
_forcedVblank = (value & 0x80) != 0;

View file

@ -51,6 +51,10 @@ private:
uint16_t _cycle = 0;
uint16_t _scanline = 0;
uint32_t _frameCount = 0;
uint8_t _drawStartX = 0;
uint8_t _drawEndX = 0;
uint8_t _irqDelay = 0;
uint8_t _bgMode = 0;
bool _mode1Bg3Priority = false;