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:
Sour 2024-12-24 17:43:09 +09:00
parent 831e177d87
commit 59f106b8cc
16 changed files with 179 additions and 89 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1164,6 +1164,7 @@ void SnesCpu::WAI()
{
//Wait for interrupt
_state.StopState = SnesCpuStopState::WaitingForIrq;
_waiOver = false;
}
/****************

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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