mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
SNES: Fixed NMI/IRQ timing issues
Passes all NMI/IRQ tests: demo_irqtest, demo_nmitest, irq, nmi, test_irq, test_irq4200, test_irq4209, test_irqb, test_nmi, Also fixes timing issues with WAI
This commit is contained in:
parent
831e177d87
commit
59f106b8cc
16 changed files with 179 additions and 89 deletions
|
@ -52,7 +52,7 @@ int64_t ExpressionEvaluator::GetSnesTokenValue(int64_t token, EvalResultType& re
|
|||
case EvalValues::RegDB: return s.DBR;
|
||||
case EvalValues::RegD: return s.D;
|
||||
case EvalValues::RegPC: return (s.K << 16) | s.PC;
|
||||
case EvalValues::Nmi: return ReturnBool(s.NmiFlag, resultType);
|
||||
case EvalValues::Nmi: return ReturnBool(s.NmiFlagCounter > 0 || s.NeedNmi, resultType);
|
||||
case EvalValues::Irq: return ReturnBool(s.IrqSource, resultType);
|
||||
case EvalValues::PpuFrameCount: return getPpuState().FrameCount;
|
||||
case EvalValues::PpuCycle: return getPpuState().Cycle;
|
||||
|
|
|
@ -430,7 +430,9 @@ void Sa1::ProcessInterrupts()
|
|||
_cpu->ClearIrqSource(SnesIrqSource::Coprocessor);
|
||||
}
|
||||
|
||||
_cpu->SetNmiFlag(_state.Sa1NmiRequested && _state.Sa1NmiEnabled);
|
||||
if(_state.Sa1NmiRequested && _state.Sa1NmiEnabled) {
|
||||
_cpu->SetNmiFlag(1);
|
||||
}
|
||||
|
||||
if((_state.CpuIrqRequested && _state.CpuIrqEnabled) || (_state.CharConvIrqFlag && _state.CharConvIrqEnabled)) {
|
||||
_snesCpu->SetIrqSource(SnesIrqSource::Coprocessor);
|
||||
|
|
|
@ -41,7 +41,7 @@ void Sa1Cpu::Exec()
|
|||
void Sa1Cpu::CheckForInterrupts()
|
||||
{
|
||||
//Use the state of the IRQ/NMI flags on the previous cycle to determine if an IRQ is processed or not
|
||||
if(_state.PrevNeedNmi) {
|
||||
if(_state.NeedNmi) {
|
||||
_state.NeedNmi = false;
|
||||
uint32_t originalPc = GetProgramAddress(_state.PC);
|
||||
ProcessInterrupt(_state.EmulationMode ? Sa1Cpu::LegacyNmiVector : Sa1Cpu::NmiVector, true);
|
||||
|
@ -60,10 +60,9 @@ void Sa1Cpu::ProcessHaltedState()
|
|||
_state.CycleCount++;
|
||||
} else {
|
||||
//WAI
|
||||
bool over = _waiOver;
|
||||
Idle();
|
||||
if(_state.IrqSource || _state.NeedNmi) {
|
||||
Idle();
|
||||
Idle();
|
||||
if(over) {
|
||||
_state.StopState = SnesCpuStopState::Running;
|
||||
CheckForInterrupts();
|
||||
}
|
||||
|
@ -75,7 +74,6 @@ void Sa1Cpu::Idle()
|
|||
//Do not apply any delay to internal cycles: "internal SA-1 cycles are still 10.74 MHz."
|
||||
_state.CycleCount++;
|
||||
DetectNmiSignalEdge();
|
||||
UpdateIrqNmiFlags();
|
||||
_emu->ProcessIdleCycle<CpuType::Sa1>();
|
||||
}
|
||||
|
||||
|
@ -89,7 +87,6 @@ void Sa1Cpu::IdleOrDummyWrite(uint32_t addr, uint8_t value)
|
|||
}
|
||||
|
||||
DetectNmiSignalEdge();
|
||||
UpdateIrqNmiFlags();
|
||||
}
|
||||
|
||||
void Sa1Cpu::IdleEndJump()
|
||||
|
@ -150,7 +147,6 @@ void Sa1Cpu::ProcessCpuCycle(uint32_t addr)
|
|||
}
|
||||
|
||||
DetectNmiSignalEdge();
|
||||
UpdateIrqNmiFlags();
|
||||
}
|
||||
|
||||
uint8_t Sa1Cpu::Read(uint32_t addr, MemoryOperationType type)
|
||||
|
|
|
@ -31,6 +31,7 @@ private:
|
|||
|
||||
SnesCpuState _state = {};
|
||||
uint32_t _operand = 0;
|
||||
bool _waiOver = true;
|
||||
|
||||
uint32_t GetProgramAddress(uint16_t addr);
|
||||
uint32_t GetDataAddress(uint16_t addr);
|
||||
|
@ -55,7 +56,6 @@ private:
|
|||
void IdleTakeBranch();
|
||||
|
||||
void DetectNmiSignalEdge();
|
||||
void UpdateIrqNmiFlags();
|
||||
|
||||
bool IsAccessConflict();
|
||||
|
||||
|
@ -335,7 +335,7 @@ public:
|
|||
template<uint64_t value>
|
||||
void IncreaseCycleCount();
|
||||
|
||||
void SetNmiFlag(bool nmiFlag);
|
||||
void SetNmiFlag(uint8_t delay);
|
||||
void SetIrqSource(SnesIrqSource source);
|
||||
bool CheckIrqSource(SnesIrqSource source);
|
||||
void ClearIrqSource(SnesIrqSource source);
|
||||
|
|
|
@ -62,7 +62,7 @@ void InternalRegisters::ProcessAutoJoypad()
|
|||
{
|
||||
//Some details/timings may still be incorrect
|
||||
//This is based on these test roms: https://github.com/undisbeliever/snes-test-roms/blob/master/src/hardware-tests/
|
||||
//As well as the CPU schematics here: https://github.com/rgalland/SNES_S-CPU_Schematics/
|
||||
//See CPU schematics here: https://github.com/rgalland/SNES_S-CPU_Schematics/
|
||||
uint64_t masterClock = _console->GetMasterClock();
|
||||
if(masterClock < _autoReadClockStart) {
|
||||
return;
|
||||
|
@ -180,9 +180,7 @@ void InternalRegisters::SetNmiFlag(bool nmiFlag)
|
|||
void InternalRegisters::SetIrqFlag(bool irqFlag)
|
||||
{
|
||||
_irqFlag = irqFlag && (_state.EnableHorizontalIrq || _state.EnableVerticalIrq);
|
||||
if(_irqFlag) {
|
||||
_cpu->SetIrqSource(SnesIrqSource::Ppu);
|
||||
} else {
|
||||
if(!_irqFlag) {
|
||||
_cpu->ClearIrqSource(SnesIrqSource::Ppu);
|
||||
}
|
||||
}
|
||||
|
@ -233,10 +231,12 @@ uint8_t InternalRegisters::Read(uint16_t addr)
|
|||
|
||||
uint8_t value = (_nmiFlag ? 0x80 : 0) | cpuRevision;
|
||||
|
||||
//Reading $4210 on any cycle turns the NMI signal off (except presumably on the first PPU cycle (first 4 master clocks) of the NMI scanline.)
|
||||
//Reading $4210 on any cycle clears the NMI flag,
|
||||
//Except between from cycles 2 to 5 on the NMI scanline, because the CPU forces the
|
||||
//flag to remain set for 4 cycles, only allowing it to be cleared starting on cycle 6
|
||||
//i.e: reading $4210 at the same it gets set will return it as set, and will keep it set.
|
||||
//Without this, Terranigma has corrupted sprites on some frames.
|
||||
if(_memoryManager->GetHClock() >= 4 || _ppu->GetScanline() != _ppu->GetNmiScanline()) {
|
||||
if(_nmiFlag && (_memoryManager->GetHClock() >= 6 || _ppu->GetScanline() != _ppu->GetNmiScanline())) {
|
||||
SetNmiFlag(false);
|
||||
}
|
||||
|
||||
|
@ -245,7 +245,11 @@ uint8_t InternalRegisters::Read(uint16_t addr)
|
|||
|
||||
case 0x4211: {
|
||||
uint8_t value = (_irqFlag ? 0x80 : 0);
|
||||
SetIrqFlag(false);
|
||||
if(_irqFlag && !_needIrq) {
|
||||
//Clear IRQ flag, if it wasn't just set within the last 4 cycles
|
||||
//Same as the NMI flag, the CPU forces the IRQ flag to be set for at least 4 cycles
|
||||
SetIrqFlag(false);
|
||||
}
|
||||
return value | (_memoryManager->GetOpenBus() & 0x7F);
|
||||
}
|
||||
|
||||
|
@ -305,13 +309,22 @@ void InternalRegisters::Write(uint16_t addr, uint8_t value)
|
|||
}
|
||||
}
|
||||
|
||||
_state.EnableNmi = (value & 0x80) != 0;
|
||||
_state.EnableVerticalIrq = (value & 0x20) != 0;
|
||||
_state.EnableHorizontalIrq = (value & 0x10) != 0;
|
||||
_state.EnableAutoJoypadRead = autoRead;
|
||||
_irqEnabled = _state.EnableHorizontalIrq || _state.EnableVerticalIrq;
|
||||
|
||||
SetNmiFlag(_nmiFlag);
|
||||
SetIrqFlag(_irqFlag);
|
||||
bool enableNmi = (value & 0x80) != 0;
|
||||
if(_nmiFlag && enableNmi && !_state.EnableNmi) {
|
||||
//When enabling NMIs in the middle of vblank, trigger an NMI right away
|
||||
_cpu->SetNmiFlag(2);
|
||||
}
|
||||
_state.EnableNmi = enableNmi;
|
||||
|
||||
if(!_irqEnabled) {
|
||||
//Clearing both H+V irq enabled flags will immediately clear the IRQ flag
|
||||
SetIrqFlag(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -338,12 +351,12 @@ void InternalRegisters::Write(uint16_t addr, uint8_t value)
|
|||
|
||||
case 0x4207:
|
||||
_state.HorizontalTimer = (_state.HorizontalTimer & 0x100) | value;
|
||||
ProcessIrqCounters();
|
||||
UpdateIrqLevel();
|
||||
break;
|
||||
|
||||
case 0x4208:
|
||||
_state.HorizontalTimer = (_state.HorizontalTimer & 0xFF) | ((value & 0x01) << 8);
|
||||
ProcessIrqCounters();
|
||||
UpdateIrqLevel();
|
||||
break;
|
||||
|
||||
case 0x4209:
|
||||
|
@ -352,12 +365,12 @@ void InternalRegisters::Write(uint16_t addr, uint8_t value)
|
|||
//Calling this here fixes flashing issue in "Shin Nihon Pro Wrestling Kounin - '95 Tokyo Dome Battle 7"
|
||||
//The write to change from scanline 16 to 17 occurs between both ProcessIrqCounter calls, which causes the IRQ
|
||||
//line to always be high (since the previous check is on scanline 16, and the next check on scanline 17)
|
||||
ProcessIrqCounters();
|
||||
UpdateIrqLevel();
|
||||
break;
|
||||
|
||||
case 0x420A:
|
||||
_state.VerticalTimer = (_state.VerticalTimer & 0xFF) | ((value & 0x01) << 8);
|
||||
ProcessIrqCounters();
|
||||
UpdateIrqLevel();
|
||||
break;
|
||||
|
||||
case 0x420D: _state.EnableFastRom = (value & 0x01) != 0; break;
|
||||
|
@ -378,7 +391,7 @@ AluState InternalRegisters::GetAluState()
|
|||
return _aluMulDiv.GetState();
|
||||
}
|
||||
|
||||
void InternalRegisters::Serialize(Serializer &s)
|
||||
void InternalRegisters::Serialize(Serializer& s)
|
||||
{
|
||||
SV(_state.EnableFastRom); SV(_nmiFlag); SV(_state.EnableNmi); SV(_state.EnableHorizontalIrq); SV(_state.EnableVerticalIrq); SV(_state.HorizontalTimer);
|
||||
SV(_state.VerticalTimer); SV(_state.IoPortOutput); SV(_state.ControllerData[0]); SV(_state.ControllerData[1]); SV(_state.ControllerData[2]); SV(_state.ControllerData[3]);
|
||||
|
@ -392,4 +405,8 @@ void InternalRegisters::Serialize(Serializer &s)
|
|||
SV(_autoReadDisabled);
|
||||
SV(_autoReadPort1Value);
|
||||
SV(_autoReadPort2Value);
|
||||
|
||||
SV(_hCounter);
|
||||
SV(_vCounter);
|
||||
SV(_irqEnabled);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "SNES/AluMulDiv.h"
|
||||
#include "SNES/SnesCpu.h"
|
||||
#include "SNES/SnesPpu.h"
|
||||
#include "SNES/SnesMemoryManager.h"
|
||||
#include "SNES/InternalRegisterTypes.h"
|
||||
#include "Utilities/ISerializable.h"
|
||||
|
||||
|
@ -26,6 +27,10 @@ private:
|
|||
bool _irqLevel = false;
|
||||
uint8_t _needIrq = 0;
|
||||
bool _irqFlag = false;
|
||||
bool _irqEnabled = false;
|
||||
|
||||
uint16_t _hCounter = 0;
|
||||
uint16_t _vCounter = 0;
|
||||
|
||||
uint64_t _autoReadClockStart = 0;
|
||||
uint64_t _autoReadNextClock = 0;
|
||||
|
@ -38,6 +43,8 @@ private:
|
|||
|
||||
uint8_t ReadControllerData(uint8_t port, bool getMsb);
|
||||
|
||||
__forceinline void UpdateIrqLevel();
|
||||
|
||||
public:
|
||||
InternalRegisters();
|
||||
void Initialize(SnesConsole* console);
|
||||
|
@ -69,25 +76,85 @@ public:
|
|||
void Serialize(Serializer &s) override;
|
||||
};
|
||||
|
||||
void InternalRegisters::ProcessIrqCounters()
|
||||
void InternalRegisters::UpdateIrqLevel()
|
||||
{
|
||||
if(_needIrq > 0) {
|
||||
_needIrq--;
|
||||
if(_needIrq == 0) {
|
||||
SetIrqFlag(true);
|
||||
}
|
||||
if(!_irqEnabled) {
|
||||
_irqLevel = false;
|
||||
return;
|
||||
}
|
||||
|
||||
bool irqLevel = (
|
||||
(_state.EnableHorizontalIrq || _state.EnableVerticalIrq) &&
|
||||
(!_state.EnableHorizontalIrq || (_state.HorizontalTimer <= 339 && (_ppu->GetCycle() == _state.HorizontalTimer) && (_ppu->GetLastScanline() != _ppu->GetRealScanline() || _state.HorizontalTimer < 339))) &&
|
||||
(!_state.EnableVerticalIrq || _ppu->GetRealScanline() == _state.VerticalTimer)
|
||||
(!_state.EnableHorizontalIrq || _state.HorizontalTimer == _hCounter) &&
|
||||
(!_state.EnableVerticalIrq || _state.VerticalTimer == _vCounter)
|
||||
);
|
||||
|
||||
if(!_irqLevel && irqLevel) {
|
||||
//Trigger IRQ signal 16 master clocks later
|
||||
_needIrq = 4;
|
||||
if(_state.EnableHorizontalIrq && _memoryManager->GetHClock() == 6) {
|
||||
_needIrq = 3;
|
||||
} else {
|
||||
_needIrq = 2;
|
||||
}
|
||||
}
|
||||
|
||||
_irqLevel = irqLevel;
|
||||
_cpu->SetNmiFlag(_state.EnableNmi & _nmiFlag);
|
||||
}
|
||||
|
||||
void InternalRegisters::ProcessIrqCounters()
|
||||
{
|
||||
//This circuit runs at MasterClock/4, and the signal is inverted. This means it ticks at hclock 2, 6, etc. on every scanline.
|
||||
//VBLANK is cleared by the PPU at H=0, and which will be picked up on H=2
|
||||
//HBLANK however is cleared by the PPU at H=4, and this will only be picked up at H=6, which causes IRQs that depend on the value of H
|
||||
//to be delayed by an extra 4 cycles compared to IRQs that trigger only based on V.
|
||||
//Once VBLANK is cleared, a 4-cycle reset signal is triggered (from H=2 to H=6) and the V counter is reset to 0 (at H=2)
|
||||
//Once HBLANK is cleared (every scanline), a 4-cycle reset signal is triggered and the H counter is reset to 0 (at H=6) and then starts
|
||||
//counting again start from H=14+.
|
||||
//HBLANK getting cleared also triggers a V counter increment (at H=6).
|
||||
//When the H/V counters match the values in $4207-420A (+ the correct IRQ enable flags are turned on), an IRQ is triggered 2 ticks (8 master clocks) later.
|
||||
//Some not-quite-figured out edge cases remain:
|
||||
// -IRQs for H=0 (i.e when 4207/8 == 0) seem to be delayed by an extra tick (4 master clocks)?
|
||||
// -What's causing the 2 ticks/8 master clock delay for the IRQ signal after it was detected?
|
||||
// -What's causing the 1 tick/4 master clock delay for the NMI signal after it was detected? (e.g why does the CPU behave like it was set on H=6 instead of H=2?)
|
||||
//Notes:
|
||||
// -IRQs can't trigger on V=261 H=339 (reg values), because V gets reset to 0 at H=2 at the same time as H increments to 339. This might mean that an IRQ at
|
||||
// V=0 and H=339 triggers both on scanline 0 and scanline 1 (at H=2)? (unverified)
|
||||
//See CPU schematics here: https://github.com/rgalland/SNES_S-CPU_Schematics/
|
||||
if(_needIrq > 0) {
|
||||
_needIrq--;
|
||||
if(_needIrq == 1) {
|
||||
SetIrqFlag(true);
|
||||
} else if(_needIrq == 0) {
|
||||
if(_irqFlag) {
|
||||
_cpu->SetIrqSource(SnesIrqSource::Ppu);
|
||||
} else {
|
||||
_cpu->ClearIrqSource(SnesIrqSource::Ppu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t hClock = _memoryManager->GetHClock();
|
||||
|
||||
if(hClock > 10) {
|
||||
_hCounter++;
|
||||
} else if(hClock == 10) {
|
||||
_hCounter = 0;
|
||||
} else if(hClock == 6) {
|
||||
_hCounter = 0;
|
||||
if(_ppu->GetScanline() > 0 && !_ppu->IsInOverclockedScanline()) {
|
||||
_vCounter++;
|
||||
}
|
||||
|
||||
if(_state.EnableNmi && _ppu->GetScanline() == _ppu->GetNmiScanline()) {
|
||||
_cpu->SetNmiFlag(1);
|
||||
}
|
||||
} else if(hClock == 2) {
|
||||
_hCounter++;
|
||||
if(_ppu->GetScanline() == _ppu->GetNmiScanline()) {
|
||||
_nmiFlag = true;
|
||||
} else if(_ppu->GetScanline() == 0) {
|
||||
_nmiFlag = false;
|
||||
_vCounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateIrqLevel();
|
||||
}
|
|
@ -1164,6 +1164,7 @@ void SnesCpu::WAI()
|
|||
{
|
||||
//Wait for interrupt
|
||||
_state.StopState = SnesCpuStopState::WaitingForIrq;
|
||||
_waiOver = false;
|
||||
}
|
||||
|
||||
/****************
|
||||
|
|
|
@ -5,8 +5,7 @@ void SnesCpu::PowerOn()
|
|||
_state.SP = 0x1FF;
|
||||
_state.PS = ProcFlags::IrqDisable;
|
||||
_state.EmulationMode = true;
|
||||
_state.NmiFlag = false;
|
||||
_state.PrevNmiFlag = false;
|
||||
_state.NmiFlagCounter = 0;
|
||||
_state.StopState = SnesCpuStopState::Running;
|
||||
_state.IrqSource = (uint8_t)SnesIrqSource::None;
|
||||
_state.PrevIrqSource = (uint8_t)SnesIrqSource::None;
|
||||
|
@ -30,8 +29,7 @@ void SnesCpu::Reset()
|
|||
_state.PC = GetResetVector();
|
||||
SetSP(_state.SP);
|
||||
|
||||
_state.NmiFlag = false;
|
||||
_state.PrevNmiFlag = false;
|
||||
_state.NmiFlagCounter = 0;
|
||||
_state.StopState = SnesCpuStopState::Running;
|
||||
_state.IrqSource = (uint8_t)SnesIrqSource::None;
|
||||
_state.PrevIrqSource = (uint8_t)SnesIrqSource::None;
|
||||
|
@ -243,7 +241,7 @@ void SnesCpu::RunOp()
|
|||
case 0xC8: AddrMode_Imp(); INY(); break;
|
||||
case 0xC9: AddrMode_ImmM(); CMP(); break;
|
||||
case 0xCA: AddrMode_Imp(); DEX(); break;
|
||||
case 0xCB: AddrMode_Imp(); WAI(); break;
|
||||
case 0xCB: WAI(); break;
|
||||
case 0xCC: AddrMode_Abs(); CPY(); break;
|
||||
case 0xCD: AddrMode_Abs(); CMP(); break;
|
||||
case 0xCE: AddrMode_Abs(); DEC(); break;
|
||||
|
@ -309,9 +307,9 @@ uint64_t SnesCpu::GetCycleCount()
|
|||
return _state.CycleCount;
|
||||
}
|
||||
|
||||
void SnesCpu::SetNmiFlag(bool nmiFlag)
|
||||
void SnesCpu::SetNmiFlag(uint8_t delay)
|
||||
{
|
||||
_state.NmiFlag = nmiFlag;
|
||||
_state.NmiFlagCounter = delay;
|
||||
}
|
||||
|
||||
void SnesCpu::DetectNmiSignalEdge()
|
||||
|
@ -319,20 +317,33 @@ void SnesCpu::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;
|
||||
if(_state.NmiFlagCounter) {
|
||||
_state.NmiFlagCounter--;
|
||||
if(_state.NmiFlagCounter == 0) {
|
||||
if(!_state.IrqLock) {
|
||||
//Trigger an NMI on the next instruction
|
||||
_state.NmiFlagCounter = false;
|
||||
_state.NeedNmi = true;
|
||||
_waiOver = true;
|
||||
} else {
|
||||
//Delay NMI if a DMA/HDMA just finished running
|
||||
_state.NmiFlagCounter = 1;
|
||||
_state.NeedNmi = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
_state.PrevNmiFlag = _state.NmiFlag;
|
||||
}
|
||||
|
||||
void SnesCpu::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.PrevIrqSource = _state.IrqSource != 0 && !CheckFlag(ProcFlags::IrqDisable);
|
||||
if(_state.IrqSource != 0) {
|
||||
//WAI ends even if the I flag is set (but execution won't jump to the IRQ handler)
|
||||
_waiOver = true;
|
||||
}
|
||||
} else {
|
||||
//Delay IRQ if a DMA/HDMA just finished running
|
||||
_state.PrevIrqSource = 0;
|
||||
}
|
||||
|
||||
_state.IrqLock = false;
|
||||
}
|
||||
|
||||
|
@ -374,7 +385,8 @@ uint8_t SnesCpu::GetOpCode()
|
|||
|
||||
void SnesCpu::IdleOrRead()
|
||||
{
|
||||
if(_state.PrevIrqSource) {
|
||||
if(!_state.IrqLock && ((_state.IrqSource != 0 || _state.PrevIrqSource != 0) && !CheckFlag(ProcFlags::IrqDisable)) || (_state.NmiFlagCounter == 1 || _state.NeedNmi)) {
|
||||
//If an IRQ or NMI will be triggered on the next instruction/cycle, the 6-clock idle cycle turns into a dummy read at the current PC
|
||||
ReadCode(_state.PC);
|
||||
} else {
|
||||
Idle();
|
||||
|
@ -614,6 +626,6 @@ bool SnesCpu::CheckFlag(uint8_t flag)
|
|||
void SnesCpu::Serialize(Serializer &s)
|
||||
{
|
||||
SV(_state.A); SV(_state.CycleCount); SV(_state.D); SV(_state.DBR); SV(_state.EmulationMode); SV(_state.IrqSource); SV(_state.K);
|
||||
SV(_state.NmiFlag); SV(_state.PC); SV(_state.PrevIrqSource); SV(_state.PrevNmiFlag); SV(_state.PS); SV(_state.SP); SV(_state.StopState);
|
||||
SV(_state.X); SV(_state.Y); SV(_state.IrqLock); SV(_state.NeedNmi); SV(_state.PrevNeedNmi);
|
||||
SV(_state.NmiFlagCounter); SV(_state.PC); SV(_state.PrevIrqSource); SV(_state.PS); SV(_state.SP); SV(_state.StopState);
|
||||
SV(_state.X); SV(_state.Y); SV(_state.IrqLock); SV(_state.NeedNmi); SV(_waiOver);
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ void SnesCpu::CheckForInterrupts()
|
|||
{
|
||||
#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.PrevNeedNmi) {
|
||||
if(_state.NeedNmi) {
|
||||
_state.NeedNmi = false;
|
||||
uint32_t originalPc = GetProgramAddress(_state.PC);
|
||||
_emu->GetCheatManager()->RefreshRamCheats(CpuType::Snes);
|
||||
|
@ -74,10 +74,9 @@ void SnesCpu::ProcessHaltedState()
|
|||
#endif
|
||||
} else {
|
||||
//WAI
|
||||
bool over = _waiOver;
|
||||
Idle();
|
||||
if(_state.IrqSource || _state.NeedNmi) {
|
||||
Idle();
|
||||
Idle();
|
||||
if(over) {
|
||||
_state.StopState = SnesCpuStopState::Running;
|
||||
CheckForInterrupts();
|
||||
}
|
||||
|
@ -91,7 +90,6 @@ void SnesCpu::Idle()
|
|||
ProcessCpuCycle();
|
||||
_memoryManager->IncMasterClock6();
|
||||
_emu->ProcessIdleCycle<CpuType::Snes>();
|
||||
UpdateIrqNmiFlags();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -107,8 +105,6 @@ void SnesCpu::IdleOrDummyWrite(uint32_t addr, uint8_t value)
|
|||
_memoryManager->IncMasterClock6();
|
||||
_emu->ProcessIdleCycle<CpuType::Snes>();
|
||||
}
|
||||
|
||||
UpdateIrqNmiFlags();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -125,8 +121,8 @@ void SnesCpu::IdleTakeBranch()
|
|||
void SnesCpu::ProcessCpuCycle()
|
||||
{
|
||||
_state.CycleCount++;
|
||||
DetectNmiSignalEdge();
|
||||
_state.IrqLock = _dmaController->ProcessPendingTransfers();
|
||||
DetectNmiSignalEdge();
|
||||
}
|
||||
|
||||
uint16_t SnesCpu::ReadVector(uint16_t vector)
|
||||
|
@ -145,9 +141,7 @@ uint8_t SnesCpu::Read(uint32_t addr, MemoryOperationType type)
|
|||
{
|
||||
_memoryManager->SetCpuSpeed(_memoryManager->GetCpuSpeed(addr));
|
||||
ProcessCpuCycle();
|
||||
uint8_t value = _memoryManager->Read(addr, type);
|
||||
UpdateIrqNmiFlags();
|
||||
return value;
|
||||
return _memoryManager->Read(addr, type);
|
||||
}
|
||||
|
||||
void SnesCpu::Write(uint32_t addr, uint8_t value, MemoryOperationType type)
|
||||
|
@ -155,6 +149,5 @@ void SnesCpu::Write(uint32_t addr, uint8_t value, MemoryOperationType type)
|
|||
_memoryManager->SetCpuSpeed(_memoryManager->GetCpuSpeed(addr));
|
||||
ProcessCpuCycle();
|
||||
_memoryManager->Write(addr, value, type);
|
||||
UpdateIrqNmiFlags();
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -42,6 +42,7 @@ private:
|
|||
|
||||
SnesCpuState _state = {};
|
||||
uint32_t _operand = 0;
|
||||
bool _waiOver = true;
|
||||
|
||||
uint32_t GetProgramAddress(uint16_t addr);
|
||||
uint32_t GetDataAddress(uint16_t addr);
|
||||
|
@ -56,7 +57,6 @@ private:
|
|||
|
||||
uint16_t GetResetVector();
|
||||
|
||||
void UpdateIrqNmiFlags();
|
||||
void ProcessCpuCycle();
|
||||
|
||||
void Idle();
|
||||
|
@ -347,7 +347,7 @@ public:
|
|||
template<uint64_t value>
|
||||
void IncreaseCycleCount();
|
||||
|
||||
void SetNmiFlag(bool nmiFlag);
|
||||
void SetNmiFlag(uint8_t delay);
|
||||
void DetectNmiSignalEdge();
|
||||
|
||||
void SetIrqSource(SnesIrqSource source);
|
||||
|
|
|
@ -40,11 +40,9 @@ struct SnesCpuState : BaseState
|
|||
bool EmulationMode;
|
||||
|
||||
/* Misc internal state */
|
||||
bool NmiFlag;
|
||||
bool PrevNmiFlag;
|
||||
uint8_t NmiFlagCounter;
|
||||
|
||||
bool IrqLock;
|
||||
bool PrevNeedNmi;
|
||||
bool NeedNmi;
|
||||
|
||||
uint8_t IrqSource;
|
||||
|
|
|
@ -184,6 +184,7 @@ void SnesMemoryManager::Exec()
|
|||
|
||||
if((_hClock & 0x03) == 0) {
|
||||
_emu->ProcessPpuCycle<CpuType::Snes>();
|
||||
} else if(_hClock & 0x02) {
|
||||
_regs->ProcessIrqCounters();
|
||||
}
|
||||
|
||||
|
@ -201,9 +202,6 @@ void SnesMemoryManager::ProcessEvent()
|
|||
|
||||
case SnesEventType::DramRefresh:
|
||||
IncMasterClock40();
|
||||
//TODOv2?
|
||||
//_cpu->IncreaseCycleCount<5>();
|
||||
|
||||
if(_ppu->GetScanline() < _ppu->GetVblankStart()) {
|
||||
_nextEvent = SnesEventType::HdmaStart;
|
||||
_nextEventClock = 276 * 4;
|
||||
|
@ -221,11 +219,11 @@ void SnesMemoryManager::ProcessEvent()
|
|||
|
||||
case SnesEventType::EndOfScanline:
|
||||
if(_ppu->ProcessEndOfScanline(_hClock)) {
|
||||
_dramRefreshPosition = 538 - (_masterClock & 0x07);
|
||||
if(_ppu->GetScanline() == 0) {
|
||||
_nextEvent = SnesEventType::HdmaInit;
|
||||
_nextEventClock = 12 + (_masterClock & 0x07);
|
||||
} else {
|
||||
_dramRefreshPosition = 538 - (_masterClock & 0x07);
|
||||
_nextEvent = SnesEventType::DramRefresh;
|
||||
_nextEventClock = _dramRefreshPosition;
|
||||
}
|
||||
|
@ -261,7 +259,6 @@ uint8_t SnesMemoryManager::Read(uint32_t addr, MemoryOperationType type)
|
|||
|
||||
uint8_t SnesMemoryManager::ReadDma(uint32_t addr, bool forBusA)
|
||||
{
|
||||
_cpu->DetectNmiSignalEdge();
|
||||
IncMasterClock4();
|
||||
|
||||
uint8_t value;
|
||||
|
@ -329,7 +326,6 @@ void SnesMemoryManager::Write(uint32_t addr, uint8_t value, MemoryOperationType
|
|||
|
||||
void SnesMemoryManager::WriteDma(uint32_t addr, uint8_t value, bool forBusA)
|
||||
{
|
||||
_cpu->DetectNmiSignalEdge();
|
||||
IncMasterClock4();
|
||||
if(_emu->ProcessMemoryWrite<CpuType::Snes>(addr, value, MemoryOperationType::DmaWrite)) {
|
||||
IMemoryHandler* handler = _mappings.GetHandler(addr);
|
||||
|
|
|
@ -478,14 +478,12 @@ bool SnesPpu::ProcessEndOfScanline(uint16_t& hClock)
|
|||
_emu->ProcessEvent(EventType::EndFrame);
|
||||
|
||||
_frameCount++;
|
||||
_regs->SetNmiFlag(true);
|
||||
SendFrame();
|
||||
|
||||
_console->ProcessEndOfFrame();
|
||||
} 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;
|
||||
_regs->SetNmiFlag(false);
|
||||
_scanline = 0;
|
||||
_rangeOver = false;
|
||||
_timeOver = false;
|
||||
|
@ -514,6 +512,11 @@ bool SnesPpu::ProcessEndOfScanline(uint16_t& hClock)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool SnesPpu::IsInOverclockedScanline()
|
||||
{
|
||||
return _inOverclockedScanline;
|
||||
}
|
||||
|
||||
void SnesPpu::UpdateSpcState()
|
||||
{
|
||||
//When using overclocking, turn off the SPC during the extra scanlines
|
||||
|
@ -521,13 +524,16 @@ void SnesPpu::UpdateSpcState()
|
|||
if(_scanline > _adjustedVblankEndScanline) {
|
||||
//Disable APU for extra lines after NMI
|
||||
_spc->SetSpcState(false);
|
||||
_inOverclockedScanline = true;
|
||||
} else if(_scanline >= _vblankStartScanline && _scanline < _nmiScanline) {
|
||||
//Disable APU for extra lines before NMI
|
||||
_spc->SetSpcState(false);
|
||||
_inOverclockedScanline = true;
|
||||
} else {
|
||||
_spc->SetSpcState(true);
|
||||
}
|
||||
}
|
||||
_inOverclockedScanline = false;
|
||||
}
|
||||
|
||||
void SnesPpu::UpdateNmiScanline()
|
||||
|
|
|
@ -59,6 +59,7 @@ private:
|
|||
uint16_t _adjustedVblankEndScanline;
|
||||
uint16_t _nmiScanline;
|
||||
bool _overclockEnabled;
|
||||
bool _inOverclockedScanline = false;
|
||||
|
||||
uint8_t _oddFrame = 0;
|
||||
|
||||
|
@ -237,6 +238,7 @@ public:
|
|||
void GetState(SnesPpuState &state, bool returnPartialState);
|
||||
|
||||
bool ProcessEndOfScanline(uint16_t& hClock);
|
||||
bool IsInOverclockedScanline();
|
||||
void UpdateSpcState();
|
||||
void UpdateNmiScanline();
|
||||
uint16_t GetLastScanline();
|
||||
|
|
|
@ -103,7 +103,7 @@ namespace Mesen.Debugger.StatusViews
|
|||
RegPS = (byte)cpu.PS;
|
||||
|
||||
FlagE = cpu.EmulationMode;
|
||||
FlagNmi = cpu.NmiFlag;
|
||||
FlagNmi = cpu.NmiFlagCounter > 0 || cpu.NeedNmi;
|
||||
FlagIrqHvCounters = (cpu.IrqSource & (byte)SnesIrqSource.Ppu) != 0;
|
||||
FlagIrqCoprocessor = (cpu.IrqSource & (byte)SnesIrqSource.Coprocessor) != 0;
|
||||
|
||||
|
@ -140,7 +140,9 @@ namespace Mesen.Debugger.StatusViews
|
|||
cpu.PS = (SnesCpuFlags)RegPS;
|
||||
|
||||
cpu.EmulationMode = FlagE;
|
||||
cpu.NmiFlag = FlagNmi;
|
||||
if(FlagNmi && cpu.NmiFlagCounter == 0 && !cpu.NeedNmi) {
|
||||
cpu.NmiFlagCounter = (byte)(FlagNmi ? 1 : 0);
|
||||
}
|
||||
cpu.IrqSource = (byte)(
|
||||
(FlagIrqHvCounters ? (byte)SnesIrqSource.Ppu : 0) |
|
||||
(FlagIrqCoprocessor ? (byte)SnesIrqSource.Coprocessor : 0)
|
||||
|
|
|
@ -60,11 +60,9 @@ public struct SnesCpuState : BaseState
|
|||
public SnesCpuFlags PS;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool EmulationMode;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)] public bool NmiFlag;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool PrevNmiFlag;
|
||||
[MarshalAs(UnmanagedType.I1)] public byte NmiFlagCounter;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)] public bool IrqLock;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool PrevNeedNmi;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool NeedNmi;
|
||||
|
||||
public byte IrqSource;
|
||||
|
|
Loading…
Add table
Reference in a new issue