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:
parent
7f805b9a62
commit
73c1a90833
12 changed files with 89 additions and 63 deletions
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
31
Core/Cpu.cpp
31
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<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
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
22
Core/Sa1.cpp
22
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<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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue