From 73c1a908333d21a8ab41b459a5c73d5ac11ea091 Mon Sep 17 00:00:00 2001 From: Sour Date: Thu, 5 Dec 2019 21:51:03 -0500 Subject: [PATCH] NMI/IRQ: Fixes and refactoring to attempt to better represent the hardware Fixes Power Rangers - The Fighting Edition having partially corrupted graphics during fights --- Core/Cpu.Shared.h | 30 ++++++++++++++++++++++++++---- Core/Cpu.cpp | 31 ++++++++++++------------------- Core/Cpu.h | 5 ++++- Core/CpuTypes.h | 5 +++++ Core/InternalRegisters.cpp | 22 +++++++++++----------- Core/InternalRegisters.h | 3 ++- Core/MemoryManager.cpp | 2 ++ Core/Ppu.cpp | 4 ---- Core/Sa1.cpp | 22 ++++++++++------------ Core/Sa1Cpu.cpp | 18 ++++++++---------- Core/Sa1Cpu.h | 5 ++++- UI/Interop/DebugState.cs | 5 +++++ 12 files changed, 89 insertions(+), 63 deletions(-) diff --git a/Core/Cpu.Shared.h b/Core/Cpu.Shared.h index d838d41..309da55 100644 --- a/Core/Cpu.Shared.h +++ b/Core/Cpu.Shared.h @@ -1,4 +1,4 @@ -void Cpu::PowerOn() +void Cpu::PowerOn() { _state = {}; _state.PC = GetResetVector(); @@ -309,9 +309,31 @@ uint64_t Cpu::GetCycleCount() return _state.CycleCount; } -void Cpu::SetNmiFlag() +void Cpu::SetNmiFlag(bool nmiFlag) { - _state.NmiFlag = true; + _state.NmiFlag = nmiFlag; +} + +void Cpu::DetectNmiSignalEdge() +{ + //"This edge detector polls the status of the NMI line during φ2 of each CPU cycle (i.e., during the + //second half of each cycle) and raises an internal signal if the input goes from being high during + //one cycle to being low during the next" + if(!_state.PrevNmiFlag && _state.NmiFlag) { + _state.NeedNmi = true; + } + _state.PrevNmiFlag = _state.NmiFlag; +} + +void Cpu::UpdateIrqNmiFlags() +{ + if(!_state.IrqLock) { + //"The internal signal goes high during φ1 of the cycle that follows the one where the edge is detected, + //and stays high until the NMI has been handled. " + _state.PrevNeedNmi = _state.NeedNmi; + _state.PrevIrqSource = _state.IrqSource && !CheckFlag(ProcFlags::IrqDisable); + } + _state.IrqLock = false; } void Cpu::SetIrqSource(IrqSource source) @@ -563,6 +585,6 @@ void Cpu::Serialize(Serializer &s) s.Stream( _state.A, _state.CycleCount, _state.D, _state.DBR, _state.EmulationMode, _state.IrqSource, _state.K, _state.NmiFlag, _state.PC, _state.PrevIrqSource, _state.PrevNmiFlag, _state.PS, _state.SP, _state.StopState, - _state.X, _state.Y + _state.X, _state.Y, _state.IrqLock, _state.NeedNmi, _state.PrevNeedNmi ); } diff --git a/Core/Cpu.cpp b/Core/Cpu.cpp index 34b69bd..fa0a05a 100644 --- a/Core/Cpu.cpp +++ b/Core/Cpu.cpp @@ -1,4 +1,4 @@ -#include "stdafx.h" +#include "stdafx.h" #include "../Utilities/Serializer.h" #include "CpuTypes.h" #include "Cpu.h" @@ -35,10 +35,8 @@ void Cpu::Exec() case CpuStopState::WaitingForIrq: //WAI - if(!_state.IrqSource && !_state.NmiFlag) { - Idle(); - } else { - Idle(); + Idle(); + if(_state.IrqSource || _state.NeedNmi) { Idle(); _state.StopState = CpuStopState::Running; } @@ -47,11 +45,11 @@ void Cpu::Exec() #ifndef DUMMYCPU //Use the state of the IRQ/NMI flags on the previous cycle to determine if an IRQ is processed or not - if(_state.PrevNmiFlag) { + if(_state.PrevNeedNmi) { + _state.NeedNmi = false; uint32_t originalPc = GetProgramAddress(_state.PC); ProcessInterrupt(_state.EmulationMode ? Cpu::LegacyNmiVector : Cpu::NmiVector, true); _console->ProcessInterrupt(originalPc, GetProgramAddress(_state.PC), true); - _state.NmiFlag = false; } else if(_state.PrevIrqSource) { uint32_t originalPc = GetProgramAddress(_state.PC); ProcessInterrupt(_state.EmulationMode ? Cpu::LegacyIrqVector : Cpu::IrqVector, true); @@ -66,6 +64,7 @@ void Cpu::Idle() _memoryManager->SetCpuSpeed(6); ProcessCpuCycle(); _memoryManager->IncMasterClock6(); + UpdateIrqNmiFlags(); #endif } @@ -82,17 +81,8 @@ void Cpu::IdleTakeBranch() void Cpu::ProcessCpuCycle() { _state.CycleCount++; - - bool irqLock = false; - if(_dmaController->ProcessPendingTransfers()) { - //If we just finished processing a DMA transfer, ignore the NMI/IRQ flags for this cycle - irqLock = true; - } - - if(!irqLock) { - _state.PrevNmiFlag = _state.NmiFlag; - _state.PrevIrqSource = _state.IrqSource && !CheckFlag(ProcFlags::IrqDisable); - } + DetectNmiSignalEdge(); + _state.IrqLock = _dmaController->ProcessPendingTransfers(); } uint8_t Cpu::Read(uint32_t addr, MemoryOperationType type) @@ -104,7 +94,9 @@ uint8_t Cpu::Read(uint32_t addr, MemoryOperationType type) #else _memoryManager->SetCpuSpeed(_memoryManager->GetCpuSpeed(addr)); ProcessCpuCycle(); - return _memoryManager->Read(addr, type); + uint8_t value = _memoryManager->Read(addr, type); + UpdateIrqNmiFlags(); + return value; #endif } @@ -116,6 +108,7 @@ void Cpu::Write(uint32_t addr, uint8_t value, MemoryOperationType type) _memoryManager->SetCpuSpeed(_memoryManager->GetCpuSpeed(addr)); ProcessCpuCycle(); _memoryManager->Write(addr, value, type); + UpdateIrqNmiFlags(); #endif } diff --git a/Core/Cpu.h b/Core/Cpu.h index 154d51a..12d064c 100644 --- a/Core/Cpu.h +++ b/Core/Cpu.h @@ -50,6 +50,7 @@ private: uint16_t GetResetVector(); + void UpdateIrqNmiFlags(); void ProcessCpuCycle(); void Idle(); @@ -326,7 +327,9 @@ public: template void IncreaseCycleCount(); - void SetNmiFlag(); + void SetNmiFlag(bool nmiFlag); + void DetectNmiSignalEdge(); + void SetIrqSource(IrqSource source); bool CheckIrqSource(IrqSource source); void ClearIrqSource(IrqSource source); diff --git a/Core/CpuTypes.h b/Core/CpuTypes.h index 5593a49..61c805b 100644 --- a/Core/CpuTypes.h +++ b/Core/CpuTypes.h @@ -41,6 +41,11 @@ struct CpuState /* Misc internal state */ bool NmiFlag; bool PrevNmiFlag; + + bool IrqLock; + bool PrevNeedNmi; + bool NeedNmi; + uint8_t IrqSource; uint8_t PrevIrqSource; CpuStopState StopState; diff --git a/Core/InternalRegisters.cpp b/Core/InternalRegisters.cpp index 96bc549..97b73b7 100644 --- a/Core/InternalRegisters.cpp +++ b/Core/InternalRegisters.cpp @@ -16,7 +16,8 @@ InternalRegisters::InternalRegisters() void InternalRegisters::Initialize(Console* console) { - _aluMulDiv.Initialize(console->GetCpu().get()); + _cpu = console->GetCpu().get(); + _aluMulDiv.Initialize(_cpu); _console = console; _memoryManager = console->GetMemoryManager().get(); _ppu = _console->GetPpu().get(); @@ -79,13 +80,14 @@ uint8_t InternalRegisters::GetIoPortOutput() void InternalRegisters::SetNmiFlag(bool nmiFlag) { _nmiFlag = nmiFlag; + _cpu->SetNmiFlag(_state.EnableNmi && _nmiFlag); } uint8_t InternalRegisters::Peek(uint16_t addr) { switch(addr) { case 0x4210: return (_nmiFlag ? 0x80 : 0) | 0x02; - case 0x4211: return (_console->GetCpu()->CheckIrqSource(IrqSource::Ppu) ? 0x80 : 0); + case 0x4211: return (_cpu->CheckIrqSource(IrqSource::Ppu) ? 0x80 : 0); default: return Read(addr); } } @@ -98,13 +100,13 @@ uint8_t InternalRegisters::Read(uint16_t addr) (_nmiFlag ? 0x80 : 0) | 0x02; //CPU revision - _nmiFlag = false; + SetNmiFlag(false); return value | (_memoryManager->GetOpenBus() & 0x70); } case 0x4211: { - uint8_t value = (_console->GetCpu()->CheckIrqSource(IrqSource::Ppu) ? 0x80 : 0); - _console->GetCpu()->ClearIrqSource(IrqSource::Ppu); + uint8_t value = (_cpu->CheckIrqSource(IrqSource::Ppu) ? 0x80 : 0); + _cpu->ClearIrqSource(IrqSource::Ppu); return value | (_memoryManager->GetOpenBus() & 0x7F); } @@ -152,20 +154,18 @@ void InternalRegisters::Write(uint16_t addr, uint8_t value) case 0x4200: if((value & 0x30) == 0x20 && !_state.EnableVerticalIrq && _ppu->GetRealScanline() == _state.VerticalTimer) { //When enabling vertical irqs, if the current scanline matches the target scanline, set the irq flag right away - _console->GetCpu()->SetIrqSource(IrqSource::Ppu); - } - - if((value & 0x80) && !_state.EnableNmi && _nmiFlag) { - _console->GetCpu()->SetNmiFlag(); + _cpu->SetIrqSource(IrqSource::Ppu); } _state.EnableNmi = (value & 0x80) != 0; + SetNmiFlag(_nmiFlag); + _state.EnableVerticalIrq = (value & 0x20) != 0; _state.EnableHorizontalIrq = (value & 0x10) != 0; if(!_state.EnableHorizontalIrq && !_state.EnableVerticalIrq) { //TODO VERIFY - _console->GetCpu()->ClearIrqSource(IrqSource::Ppu); + _cpu->ClearIrqSource(IrqSource::Ppu); } _state.EnableAutoJoypadRead = (value & 0x01) != 0; diff --git a/Core/InternalRegisters.h b/Core/InternalRegisters.h index f305e89..b642343 100644 --- a/Core/InternalRegisters.h +++ b/Core/InternalRegisters.h @@ -13,6 +13,7 @@ class InternalRegisters final : public ISerializable { private: Console* _console; + Cpu* _cpu; Ppu* _ppu; MemoryManager* _memoryManager; @@ -57,7 +58,7 @@ void InternalRegisters::ProcessIrqCounters() { if(_needIrq) { _needIrq = false; - _console->GetCpu()->SetIrqSource(IrqSource::Ppu); + _cpu->SetIrqSource(IrqSource::Ppu); } bool irqLevel = ( diff --git a/Core/MemoryManager.cpp b/Core/MemoryManager.cpp index 1c6ac96..b617018 100644 --- a/Core/MemoryManager.cpp +++ b/Core/MemoryManager.cpp @@ -267,6 +267,7 @@ uint8_t MemoryManager::Read(uint32_t addr, MemoryOperationType type) uint8_t MemoryManager::ReadDma(uint32_t addr, bool forBusA) { + _cpu->DetectNmiSignalEdge(); IncMasterClock4(); uint8_t value; @@ -331,6 +332,7 @@ void MemoryManager::Write(uint32_t addr, uint8_t value, MemoryOperationType type void MemoryManager::WriteDma(uint32_t addr, uint8_t value, bool forBusA) { + _cpu->DetectNmiSignalEdge(); IncMasterClock4(); _console->ProcessMemoryWrite(addr, value, MemoryOperationType::DmaWrite); diff --git a/Core/Ppu.cpp b/Core/Ppu.cpp index 40b15ce..290a235 100644 --- a/Core/Ppu.cpp +++ b/Core/Ppu.cpp @@ -469,10 +469,6 @@ bool Ppu::ProcessEndOfScanline(uint16_t hClock) SendFrame(); _console->ProcessEndOfFrame(); - - if(_regs->IsNmiEnabled()) { - _console->GetCpu()->SetNmiFlag(); - } } else if(_scanline >= _vblankEndScanline + 1) { //"Frames are 262 scanlines in non-interlace mode, while in interlace mode frames with $213f.7=0 are 263 scanlines" _oddFrame ^= 1; diff --git a/Core/Sa1.cpp b/Core/Sa1.cpp index 435036c..8d8fae6 100644 --- a/Core/Sa1.cpp +++ b/Core/Sa1.cpp @@ -119,9 +119,11 @@ void Sa1::Sa1RegisterWrite(uint16_t addr, uint8_t value) case 0x2225: //BMAP (SA-1 BW-RAM Address Mapping) - _state.Sa1BwBank = value & 0x7F; - _state.Sa1BwMode = (value & 0x80); - UpdateSaveRamMappings(); + if(_state.Sa1BwBank != (value & 0x7F) || _state.Sa1BwMode != (value & 0x80)) { + _state.Sa1BwBank = value & 0x7F; + _state.Sa1BwMode = (value & 0x80); + UpdateSaveRamMappings(); + } break; case 0x2227: _state.Sa1BwWriteEnabled = (value & 0x80) != 0; break; //CBWE (SA-1 CPU BW-RAM Write Enable) @@ -403,11 +405,7 @@ void Sa1::ProcessInterrupts() _cpu->ClearIrqSource(IrqSource::Coprocessor); } - if(_state.Sa1NmiRequested && _state.Sa1NmiEnabled) { - _cpu->SetNmiFlag(); - } else { - //...? - } + _cpu->SetNmiFlag(_state.Sa1NmiRequested && _state.Sa1NmiEnabled); if((_state.CpuIrqRequested && _state.CpuIrqEnabled) || (_state.CharConvIrqFlag && _state.CharConvIrqEnabled)) { _snesCpu->SetIrqSource(IrqSource::Coprocessor); @@ -598,11 +596,11 @@ void Sa1::UpdateSaveRamMappings() vector> &saveRamHandlers = _cart->GetSaveRamHandlers(); MemoryMappings* cpuMappings = _memoryManager->GetMemoryMappings(); for(int i = 0; i < 0x3F; i++) { - _mappings.RegisterHandler(i, i, 0x6000, 0x7FFF, saveRamHandlers, 0, _state.Sa1BwBank * 2); - _mappings.RegisterHandler(i + 0x80, i + 0x80, 0x6000, 0x7FFF, saveRamHandlers, 0, _state.Sa1BwBank * 2); + _mappings.RegisterHandler(i, i, 0x6000, 0x7FFF, saveRamHandlers, 0, (_state.Sa1BwBank & ((_cpuBwRamHandlers.size() / 2) - 1)) * 2); + _mappings.RegisterHandler(i + 0x80, i + 0x80, 0x6000, 0x7FFF, saveRamHandlers, 0, (_state.Sa1BwBank & ((_cpuBwRamHandlers.size() / 2) - 1)) * 2); - cpuMappings->RegisterHandler(i, i, 0x6000, 0x7FFF, saveRamHandlers, 0, _state.CpuBwBank * 2); - cpuMappings->RegisterHandler(i + 0x80, i + 0x80, 0x6000, 0x7FFF, saveRamHandlers, 0, _state.CpuBwBank * 2); + cpuMappings->RegisterHandler(i, i, 0x6000, 0x7FFF, saveRamHandlers, 0, (_state.CpuBwBank & ((_cpuBwRamHandlers.size() / 2) - 1)) * 2); + cpuMappings->RegisterHandler(i + 0x80, i + 0x80, 0x6000, 0x7FFF, saveRamHandlers, 0, (_state.CpuBwBank & ((_cpuBwRamHandlers.size() / 2) - 1)) * 2); } } diff --git a/Core/Sa1Cpu.cpp b/Core/Sa1Cpu.cpp index 7051777..7052836 100644 --- a/Core/Sa1Cpu.cpp +++ b/Core/Sa1Cpu.cpp @@ -36,10 +36,8 @@ void Sa1Cpu::Exec() case CpuStopState::WaitingForIrq: //WAI - if(!_state.IrqSource && !_state.NmiFlag) { - Idle(); - } else { - Idle(); + Idle(); + if(_state.IrqSource || _state.NeedNmi) { Idle(); _state.StopState = CpuStopState::Running; } @@ -47,11 +45,11 @@ void Sa1Cpu::Exec() } //Use the state of the IRQ/NMI flags on the previous cycle to determine if an IRQ is processed or not - if(_state.PrevNmiFlag) { + if(_state.PrevNeedNmi) { + _state.NeedNmi = false; uint32_t originalPc = GetProgramAddress(_state.PC); ProcessInterrupt(_state.EmulationMode ? Sa1Cpu::LegacyNmiVector : Sa1Cpu::NmiVector, true); _console->ProcessInterrupt(originalPc, GetProgramAddress(_state.PC), true); - _state.NmiFlag = false; } else if(_state.PrevIrqSource) { uint32_t originalPc = GetProgramAddress(_state.PC); ProcessInterrupt(_state.EmulationMode ? Sa1Cpu::LegacyIrqVector : Sa1Cpu::IrqVector, true); @@ -63,8 +61,8 @@ void Sa1Cpu::Idle() { //Do not apply any delay to internal cycles: "internal SA-1 cycles are still 10.74 MHz." _state.CycleCount++; - _state.PrevNmiFlag = _state.NmiFlag; - _state.PrevIrqSource = _state.IrqSource && !CheckFlag(ProcFlags::IrqDisable); + DetectNmiSignalEdge(); + UpdateIrqNmiFlags(); } void Sa1Cpu::IdleEndJump() @@ -115,8 +113,8 @@ void Sa1Cpu::ProcessCpuCycle(uint32_t addr) } } - _state.PrevNmiFlag = _state.NmiFlag; - _state.PrevIrqSource = _state.IrqSource && !CheckFlag(ProcFlags::IrqDisable); + DetectNmiSignalEdge(); + UpdateIrqNmiFlags(); } uint8_t Sa1Cpu::Read(uint32_t addr, MemoryOperationType type) diff --git a/Core/Sa1Cpu.h b/Core/Sa1Cpu.h index e5c7286..aed09ec 100644 --- a/Core/Sa1Cpu.h +++ b/Core/Sa1Cpu.h @@ -49,6 +49,9 @@ private: void IdleEndJump(); void IdleTakeBranch(); + void DetectNmiSignalEdge(); + void UpdateIrqNmiFlags(); + bool IsAccessConflict(); uint8_t ReadOperandByte(); @@ -320,7 +323,7 @@ public: template void IncreaseCycleCount(); - void SetNmiFlag(); + void SetNmiFlag(bool nmiFlag); void SetIrqSource(IrqSource source); bool CheckIrqSource(IrqSource source); void ClearIrqSource(IrqSource source); diff --git a/UI/Interop/DebugState.cs b/UI/Interop/DebugState.cs index e3f6787..f2d7e92 100644 --- a/UI/Interop/DebugState.cs +++ b/UI/Interop/DebugState.cs @@ -63,6 +63,11 @@ namespace Mesen.GUI [MarshalAs(UnmanagedType.I1)] public bool NmiFlag; [MarshalAs(UnmanagedType.I1)] public bool PrevNmiFlag; + + [MarshalAs(UnmanagedType.I1)] public bool IrqLock; + [MarshalAs(UnmanagedType.I1)] public bool PrevNeedNmi; + [MarshalAs(UnmanagedType.I1)] public bool NeedNmi; + public byte IrqSource; public byte PrevIrqSource; public CpuStopState StopState;