NMI/IRQ: Fixes and refactoring to attempt to better represent the hardware

Fixes Power Rangers - The Fighting Edition having partially corrupted graphics during fights
This commit is contained in:
Sour 2019-12-05 21:51:03 -05:00
parent 7f805b9a62
commit 73c1a90833
12 changed files with 89 additions and 63 deletions

View file

@ -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
);
}

View file

@ -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<CpuType::Cpu>(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
}

View file

@ -50,6 +50,7 @@ private:
uint16_t GetResetVector();
void UpdateIrqNmiFlags();
void ProcessCpuCycle();
void Idle();
@ -326,7 +327,9 @@ public:
template<uint64_t value>
void IncreaseCycleCount();
void SetNmiFlag();
void SetNmiFlag(bool nmiFlag);
void DetectNmiSignalEdge();
void SetIrqSource(IrqSource source);
bool CheckIrqSource(IrqSource source);
void ClearIrqSource(IrqSource source);

View file

@ -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;

View file

@ -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;

View file

@ -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 = (

View file

@ -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<CpuType::Cpu>(addr, value, MemoryOperationType::DmaWrite);

View file

@ -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;

View file

@ -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<unique_ptr<IMemoryHandler>> &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);
}
}

View file

@ -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<CpuType::Sa1>(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)

View file

@ -49,6 +49,9 @@ private:
void IdleEndJump();
void IdleTakeBranch();
void DetectNmiSignalEdge();
void UpdateIrqNmiFlags();
bool IsAccessConflict();
uint8_t ReadOperandByte();
@ -320,7 +323,7 @@ public:
template<uint64_t value>
void IncreaseCycleCount();
void SetNmiFlag();
void SetNmiFlag(bool nmiFlag);
void SetIrqSource(IrqSource source);
bool CheckIrqSource(IrqSource source);
void ClearIrqSource(IrqSource source);

View file

@ -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;