diff --git a/Core/Debugger/ExpressionEvaluator.Snes.cpp b/Core/Debugger/ExpressionEvaluator.Snes.cpp index 73c64824..deaf84a8 100644 --- a/Core/Debugger/ExpressionEvaluator.Snes.cpp +++ b/Core/Debugger/ExpressionEvaluator.Snes.cpp @@ -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; diff --git a/Core/SNES/Coprocessors/SA1/Sa1.cpp b/Core/SNES/Coprocessors/SA1/Sa1.cpp index c3ae87db..b33dd71a 100644 --- a/Core/SNES/Coprocessors/SA1/Sa1.cpp +++ b/Core/SNES/Coprocessors/SA1/Sa1.cpp @@ -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); diff --git a/Core/SNES/Coprocessors/SA1/Sa1Cpu.cpp b/Core/SNES/Coprocessors/SA1/Sa1Cpu.cpp index 16fe157c..1e5e1427 100644 --- a/Core/SNES/Coprocessors/SA1/Sa1Cpu.cpp +++ b/Core/SNES/Coprocessors/SA1/Sa1Cpu.cpp @@ -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(); } @@ -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) diff --git a/Core/SNES/Coprocessors/SA1/Sa1Cpu.h b/Core/SNES/Coprocessors/SA1/Sa1Cpu.h index 7e382932..c7906869 100644 --- a/Core/SNES/Coprocessors/SA1/Sa1Cpu.h +++ b/Core/SNES/Coprocessors/SA1/Sa1Cpu.h @@ -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 void IncreaseCycleCount(); - void SetNmiFlag(bool nmiFlag); + void SetNmiFlag(uint8_t delay); void SetIrqSource(SnesIrqSource source); bool CheckIrqSource(SnesIrqSource source); void ClearIrqSource(SnesIrqSource source); diff --git a/Core/SNES/InternalRegisters.cpp b/Core/SNES/InternalRegisters.cpp index b047c9e7..3a8cf8f0 100644 --- a/Core/SNES/InternalRegisters.cpp +++ b/Core/SNES/InternalRegisters.cpp @@ -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); } diff --git a/Core/SNES/InternalRegisters.h b/Core/SNES/InternalRegisters.h index a01455de..d8fed135 100644 --- a/Core/SNES/InternalRegisters.h +++ b/Core/SNES/InternalRegisters.h @@ -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(); } \ No newline at end of file diff --git a/Core/SNES/SnesCpu.Instructions.h b/Core/SNES/SnesCpu.Instructions.h index 5e6e4ed9..46b8553e 100644 --- a/Core/SNES/SnesCpu.Instructions.h +++ b/Core/SNES/SnesCpu.Instructions.h @@ -1164,6 +1164,7 @@ void SnesCpu::WAI() { //Wait for interrupt _state.StopState = SnesCpuStopState::WaitingForIrq; + _waiOver = false; } /**************** diff --git a/Core/SNES/SnesCpu.Shared.h b/Core/SNES/SnesCpu.Shared.h index 264fff50..bbabf420 100644 --- a/Core/SNES/SnesCpu.Shared.h +++ b/Core/SNES/SnesCpu.Shared.h @@ -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); } diff --git a/Core/SNES/SnesCpu.cpp b/Core/SNES/SnesCpu.cpp index 7c9b1e50..edeb1adc 100644 --- a/Core/SNES/SnesCpu.cpp +++ b/Core/SNES/SnesCpu.cpp @@ -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(); - UpdateIrqNmiFlags(); #endif } @@ -107,8 +105,6 @@ void SnesCpu::IdleOrDummyWrite(uint32_t addr, uint8_t value) _memoryManager->IncMasterClock6(); _emu->ProcessIdleCycle(); } - - 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 diff --git a/Core/SNES/SnesCpu.h b/Core/SNES/SnesCpu.h index 03256b3d..d34deb9b 100644 --- a/Core/SNES/SnesCpu.h +++ b/Core/SNES/SnesCpu.h @@ -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 void IncreaseCycleCount(); - void SetNmiFlag(bool nmiFlag); + void SetNmiFlag(uint8_t delay); void DetectNmiSignalEdge(); void SetIrqSource(SnesIrqSource source); diff --git a/Core/SNES/SnesCpuTypes.h b/Core/SNES/SnesCpuTypes.h index 7475b6e5..f0ee03d2 100644 --- a/Core/SNES/SnesCpuTypes.h +++ b/Core/SNES/SnesCpuTypes.h @@ -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; diff --git a/Core/SNES/SnesMemoryManager.cpp b/Core/SNES/SnesMemoryManager.cpp index 4957bff8..dc259e1f 100644 --- a/Core/SNES/SnesMemoryManager.cpp +++ b/Core/SNES/SnesMemoryManager.cpp @@ -184,6 +184,7 @@ void SnesMemoryManager::Exec() if((_hClock & 0x03) == 0) { _emu->ProcessPpuCycle(); + } 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(addr, value, MemoryOperationType::DmaWrite)) { IMemoryHandler* handler = _mappings.GetHandler(addr); diff --git a/Core/SNES/SnesPpu.cpp b/Core/SNES/SnesPpu.cpp index 5e34367d..47a66b51 100644 --- a/Core/SNES/SnesPpu.cpp +++ b/Core/SNES/SnesPpu.cpp @@ -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() diff --git a/Core/SNES/SnesPpu.h b/Core/SNES/SnesPpu.h index 71620051..97efd704 100644 --- a/Core/SNES/SnesPpu.h +++ b/Core/SNES/SnesPpu.h @@ -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(); diff --git a/UI/Debugger/StatusViews/SnesStatusViewModel.cs b/UI/Debugger/StatusViews/SnesStatusViewModel.cs index ac34116b..0c3d06da 100644 --- a/UI/Debugger/StatusViews/SnesStatusViewModel.cs +++ b/UI/Debugger/StatusViews/SnesStatusViewModel.cs @@ -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) diff --git a/UI/Interop/ConsoleState/SnesState.cs b/UI/Interop/ConsoleState/SnesState.cs index bfb64052..6faa06c1 100644 --- a/UI/Interop/ConsoleState/SnesState.cs +++ b/UI/Interop/ConsoleState/SnesState.cs @@ -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;