mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
Save state from power on would save the cycle count as 4, but the code in UpdateClockRatio would set it to 17, causing movies to potentially desync because the SPC and CPU aren't running with the same "alignment" they had when the movie was recorded
593 lines
No EOL
13 KiB
C++
593 lines
No EOL
13 KiB
C++
#include "pch.h"
|
|
#include "SNES/Spc.h"
|
|
#include "SNES/SnesConsole.h"
|
|
#include "SNES/SnesMemoryManager.h"
|
|
#include "SNES/SpcFileData.h"
|
|
#ifndef DUMMYSPC
|
|
#include "SNES/DSP/Dsp.h"
|
|
#else
|
|
#undef Spc
|
|
#undef DUMMYSPC
|
|
#include "SNES/DSP/Dsp.h"
|
|
#define Spc DummySpc
|
|
#define DUMMYSPC
|
|
#endif
|
|
#include "Shared/Emulator.h"
|
|
#include "Shared/EmuSettings.h"
|
|
#include "Shared/Audio/SoundMixer.h"
|
|
#include "Utilities/Serializer.h"
|
|
#include "Shared/MemoryOperationType.h"
|
|
|
|
Spc::Spc(SnesConsole* console)
|
|
{
|
|
_emu = console->GetEmulator();
|
|
_console = console;
|
|
_memoryManager = console->GetMemoryManager();
|
|
|
|
_ram = new uint8_t[Spc::SpcRamSize];
|
|
_emu->RegisterMemory(MemoryType::SpcRam, _ram, Spc::SpcRamSize);
|
|
_console->InitializeRam(_ram, Spc::SpcRamSize);
|
|
|
|
_emu->RegisterMemory(MemoryType::SpcRom, _spcBios, Spc::SpcRomSize);
|
|
|
|
#ifndef DUMMYSPC
|
|
_dsp.reset(new Dsp(_emu, this));
|
|
#endif
|
|
|
|
_state = {};
|
|
_state.WriteEnabled = true;
|
|
_state.TimersEnabled = true;
|
|
_state.RomEnabled = true;
|
|
_state.SP = 0xFF;
|
|
_state.PC = ReadWord(Spc::ResetVector);
|
|
_state.StopState = SnesCpuStopState::Running;
|
|
|
|
_opCode = 0;
|
|
_opStep = SpcOpStep::ReadOpCode;
|
|
_opSubStep = 0;
|
|
_tmp1 = 0;
|
|
_tmp2 = 0;
|
|
_tmp3 = 0;
|
|
_operandA = 0;
|
|
_operandB = 0;
|
|
_enabled = true;
|
|
|
|
UpdateClockRatio();
|
|
}
|
|
|
|
#ifndef DUMMYSPC
|
|
Spc::~Spc()
|
|
{
|
|
delete[] _ram;
|
|
}
|
|
#endif
|
|
|
|
void Spc::Reset()
|
|
{
|
|
_state.StopState = SnesCpuStopState::Running;
|
|
|
|
_state.Timer0.Reset();
|
|
_state.Timer1.Reset();
|
|
_state.Timer2.Reset();
|
|
|
|
//Resetting appears to reset the values the main CPU can read (not doing this causes a freeze in Kaite Tsukette Asoberu Dezaemon)
|
|
_state.OutputReg[0] = 0;
|
|
_state.OutputReg[1] = 0;
|
|
_state.OutputReg[2] = 0;
|
|
_state.OutputReg[3] = 0;
|
|
|
|
_state.RomEnabled = true;
|
|
_state.Cycle = 0;
|
|
_state.PC = ReadWord(Spc::ResetVector);
|
|
_state.A = 0;
|
|
_state.X = 0;
|
|
_state.Y = 0;
|
|
_state.SP = 0xFF;
|
|
|
|
//Clear P (and other flags) - if P is set after reset, the IPL ROM doesn't work properly
|
|
_state.PS = 0;
|
|
|
|
_opCode = 0;
|
|
_opStep = SpcOpStep::ReadOpCode;
|
|
_opSubStep = 0;
|
|
_tmp1 = 0;
|
|
_tmp2 = 0;
|
|
_tmp3 = 0;
|
|
_operandA = 0;
|
|
_operandB = 0;
|
|
|
|
_dsp->Reset();
|
|
}
|
|
|
|
void Spc::SetSpcState(bool enabled)
|
|
{
|
|
//Used by overclocking logic to disable SPC during the extra scanlines added to the PPU
|
|
if(_enabled != enabled) {
|
|
if(enabled) {
|
|
//When re-enabling, adjust the cycle counter to prevent running extra cycles
|
|
UpdateClockRatio();
|
|
} else {
|
|
//Catch up SPC before disabling it
|
|
Run();
|
|
}
|
|
_enabled = enabled;
|
|
}
|
|
}
|
|
|
|
void Spc::UpdateClockRatio()
|
|
{
|
|
_clockRatio = (double)(Spc::SpcSampleRate * 64) / _console->GetMasterClockRate();
|
|
|
|
//If the target cycle is off by more than 20 cycles, reset the counter to match what was expected
|
|
//This can happen due to overclocking (which disables the SPC for some scanlines) or if the SPC's
|
|
//internal sample rate is changed between versions (e.g 32000hz -> 32040hz)
|
|
uint64_t targetCycle = (uint64_t)(_memoryManager->GetMasterClock() * _clockRatio);
|
|
if(std::abs((int64_t)targetCycle - (int64_t)_state.Cycle) > 20) {
|
|
_state.Cycle = targetCycle;
|
|
}
|
|
}
|
|
|
|
void Spc::Idle()
|
|
{
|
|
IncCycleCount(-1);
|
|
}
|
|
|
|
void Spc::DummyRead()
|
|
{
|
|
Read(_state.PC, MemoryOperationType::DummyRead);
|
|
}
|
|
|
|
void Spc::DummyRead(uint16_t addr)
|
|
{
|
|
Read(addr, MemoryOperationType::DummyRead);
|
|
}
|
|
|
|
void Spc::IncCycleCount(int32_t addr)
|
|
{
|
|
static constexpr uint8_t cpuWait[4] = { 2, 4, 10, 20 };
|
|
static constexpr uint8_t timerMultiplier[4] = { 2, 4, 8, 16 };
|
|
|
|
uint8_t speedSelect;
|
|
if(addr < 0 || ((addr & 0xFFF0) == 0x00F0) || (addr >= 0xFFC0 && _state.RomEnabled)) {
|
|
//Use internal speed (bits 4-5) for idle cycles, register access or IPL rom access
|
|
speedSelect = _state.InternalSpeed;
|
|
} else {
|
|
speedSelect = _state.ExternalSpeed;
|
|
}
|
|
|
|
_state.Cycle += cpuWait[speedSelect];
|
|
#ifndef DUMMYSPC
|
|
_dsp->Exec();
|
|
#endif
|
|
|
|
uint8_t timerInc = timerMultiplier[speedSelect];
|
|
_state.Timer0.Run(timerInc);
|
|
_state.Timer1.Run(timerInc);
|
|
_state.Timer2.Run(timerInc);
|
|
}
|
|
|
|
uint8_t Spc::DebugRead(uint16_t addr)
|
|
{
|
|
if(addr >= 0xFFC0 && _state.RomEnabled) {
|
|
return _spcBios[addr & 0x3F];
|
|
}
|
|
|
|
switch(addr) {
|
|
case 0xF0: return 0;
|
|
case 0xF1: return 0;
|
|
|
|
case 0xF2: return _state.DspReg;
|
|
case 0xF3: return _dsp->Read(_state.DspReg & 0x7F);
|
|
|
|
case 0xF4: return _state.CpuRegs[0];
|
|
case 0xF5: return _state.CpuRegs[1];
|
|
case 0xF6: return _state.CpuRegs[2];
|
|
case 0xF7: return _state.CpuRegs[3];
|
|
|
|
case 0xF8: return _state.RamReg[0];
|
|
case 0xF9: return _state.RamReg[1];
|
|
|
|
case 0xFA: return 0;
|
|
case 0xFB: return 0;
|
|
case 0xFC: return 0;
|
|
|
|
case 0xFD: return _state.Timer0.DebugRead();
|
|
case 0xFE: return _state.Timer1.DebugRead();
|
|
case 0xFF: return _state.Timer2.DebugRead();
|
|
|
|
default: return _ram[addr];
|
|
}
|
|
}
|
|
|
|
void Spc::DebugWrite(uint16_t addr, uint8_t value)
|
|
{
|
|
_ram[addr] = value;
|
|
}
|
|
|
|
uint8_t Spc::Read(uint16_t addr, MemoryOperationType type)
|
|
{
|
|
IncCycleCount(addr);
|
|
|
|
uint8_t value;
|
|
if(addr >= 0xFFC0 && _state.RomEnabled) {
|
|
value = _spcBios[addr & 0x3F];
|
|
} else {
|
|
switch(addr) {
|
|
case 0xF0: value = 0; break;
|
|
case 0xF1: value = 0; break;
|
|
|
|
case 0xF2: value = _state.DspReg; break;
|
|
case 0xF3:
|
|
#ifndef DUMMYSPC
|
|
value = _dsp->Read(_state.DspReg & 0x7F);
|
|
#else
|
|
value = 0;
|
|
#endif
|
|
break;
|
|
|
|
case 0xF4: value = _state.CpuRegs[0]; break;
|
|
case 0xF5: value = _state.CpuRegs[1]; break;
|
|
case 0xF6: value = _state.CpuRegs[2]; break;
|
|
case 0xF7: value = _state.CpuRegs[3]; break;
|
|
|
|
case 0xF8: value = _state.RamReg[0]; break;
|
|
case 0xF9: value = _state.RamReg[1]; break;
|
|
|
|
case 0xFA: value = 0; break;
|
|
case 0xFB: value = 0; break;
|
|
case 0xFC: value = 0; break;
|
|
|
|
case 0xFD: value = _state.Timer0.GetOutput(); break;
|
|
case 0xFE: value = _state.Timer1.GetOutput(); break;
|
|
case 0xFF: value = _state.Timer2.GetOutput(); break;
|
|
|
|
default: value = _ram[addr]; break;
|
|
}
|
|
}
|
|
|
|
#ifndef DUMMYSPC
|
|
_emu->ProcessMemoryRead<CpuType::Spc>(addr, value, type);
|
|
#else
|
|
LogMemoryOperation(addr, value, type);
|
|
#endif
|
|
|
|
return value;
|
|
}
|
|
|
|
void Spc::Write(uint16_t addr, uint8_t value, MemoryOperationType type)
|
|
{
|
|
IncCycleCount(addr);
|
|
|
|
#ifdef DUMMYSPC
|
|
LogMemoryOperation(addr, value, type);
|
|
#else
|
|
|
|
//Writes always affect the underlying RAM
|
|
if(_state.WriteEnabled) {
|
|
_emu->ProcessMemoryWrite<CpuType::Spc>(addr, value, type);
|
|
_ram[addr] = value;
|
|
}
|
|
|
|
switch(addr) {
|
|
case 0xF0:
|
|
if(!CheckFlag(SpcFlags::DirectPage)) {
|
|
_state.InternalSpeed = (value >> 6) & 0x03;
|
|
_state.ExternalSpeed = (value >> 4) & 0x03;
|
|
_state.TimersEnabled = (value & 0x08) != 0;
|
|
_state.TimersDisabled = (value & 0x01) != 0;
|
|
_state.WriteEnabled = value & 0x02;
|
|
|
|
bool timersEnabled = _state.TimersEnabled && !_state.TimersDisabled;
|
|
_state.Timer0.SetGlobalEnabled(timersEnabled);
|
|
_state.Timer1.SetGlobalEnabled(timersEnabled);
|
|
_state.Timer2.SetGlobalEnabled(timersEnabled);
|
|
}
|
|
break;
|
|
|
|
case 0xF1:
|
|
if(value & SpcControlFlags::ClearPortsA) {
|
|
_state.CpuRegs[0] = _state.CpuRegs[1] = 0;
|
|
}
|
|
if(value & SpcControlFlags::ClearPortsB) {
|
|
_state.CpuRegs[2] = _state.CpuRegs[3] = 0;
|
|
}
|
|
|
|
_state.Timer0.SetEnabled((value & SpcControlFlags::Timer0) != 0);
|
|
_state.Timer1.SetEnabled((value & SpcControlFlags::Timer1) != 0);
|
|
_state.Timer2.SetEnabled((value & SpcControlFlags::Timer2) != 0);
|
|
|
|
_state.RomEnabled = (value & SpcControlFlags::EnableRom) != 0;
|
|
break;
|
|
|
|
case 0xF2: _state.DspReg = value; break;
|
|
case 0xF3:
|
|
if(_state.DspReg < 128) {
|
|
_dsp->Write(_state.DspReg, value);
|
|
}
|
|
break;
|
|
|
|
case 0xF4: _state.OutputReg[0] = value; break;
|
|
case 0xF5: _state.OutputReg[1] = value; break;
|
|
case 0xF6: _state.OutputReg[2] = value; break;
|
|
case 0xF7: _state.OutputReg[3] = value; break;
|
|
case 0xF8: _state.RamReg[0] = value; break;
|
|
case 0xF9: _state.RamReg[1] = value; break;
|
|
|
|
case 0xFA: _state.Timer0.SetTarget(value); break;
|
|
case 0xFB: _state.Timer1.SetTarget(value); break;
|
|
case 0xFC: _state.Timer2.SetTarget(value); break;
|
|
|
|
case 0xFD: break;
|
|
case 0xFE: break;
|
|
case 0xFF: break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
uint8_t Spc::CpuReadRegister(uint16_t addr)
|
|
{
|
|
Run();
|
|
return _state.OutputReg[addr & 0x03];
|
|
}
|
|
|
|
void Spc::CpuWriteRegister(uint32_t addr, uint8_t value)
|
|
{
|
|
Run();
|
|
_state.CpuRegs[addr & 0x03] = value;
|
|
}
|
|
|
|
uint8_t Spc::DspReadRam(uint16_t addr)
|
|
{
|
|
uint8_t value = _ram[addr];
|
|
#ifndef DUMMYSPC
|
|
//TODO
|
|
//_emu->ProcessMemoryRead<CpuType::Spc>(addr, value, MemoryOperationType::Read);
|
|
#endif
|
|
return value;
|
|
}
|
|
|
|
void Spc::DspWriteRam(uint16_t addr, uint8_t value)
|
|
{
|
|
#ifndef DUMMYSPC
|
|
//TODO
|
|
//_emu->ProcessMemoryWrite<CpuType::Spc>(addr, value, MemoryOperationType::Write);
|
|
#endif
|
|
_ram[addr] = value;
|
|
}
|
|
|
|
void Spc::Run()
|
|
{
|
|
if(!_enabled || _state.StopState != SnesCpuStopState::Running) {
|
|
//STOP or SLEEP were executed - execution is stopped forever.
|
|
return;
|
|
}
|
|
|
|
uint64_t targetCycle = (uint64_t)(_memoryManager->GetMasterClock() * _clockRatio);
|
|
while(_state.Cycle < targetCycle) {
|
|
ProcessCycle();
|
|
}
|
|
}
|
|
|
|
void Spc::ProcessCycle()
|
|
{
|
|
if(_opStep == SpcOpStep::ReadOpCode) {
|
|
#ifndef DUMMYSPC
|
|
_emu->ProcessInstruction<CpuType::Spc>();
|
|
#endif
|
|
_opCode = GetOpCode();
|
|
_opStep = SpcOpStep::Addressing;
|
|
_opSubStep = 0;
|
|
} else {
|
|
Exec();
|
|
}
|
|
}
|
|
|
|
void Spc::ProcessEndFrame()
|
|
{
|
|
Run();
|
|
|
|
UpdateClockRatio();
|
|
|
|
uint16_t sampleCount = _dsp->GetSampleCount();
|
|
if(sampleCount != 0) {
|
|
_emu->GetSoundMixer()->PlayAudioBuffer(_dsp->GetSamples(), sampleCount / 2, Spc::SpcSampleRate);
|
|
}
|
|
_dsp->ResetOutput();
|
|
}
|
|
|
|
SpcState& Spc::GetState()
|
|
{
|
|
return _state;
|
|
}
|
|
|
|
DspState& Spc::GetDspState()
|
|
{
|
|
return _dsp->GetState();
|
|
}
|
|
|
|
bool Spc::IsMuted()
|
|
{
|
|
return _dsp->IsMuted();
|
|
}
|
|
|
|
AddressInfo Spc::GetAbsoluteAddress(uint16_t addr)
|
|
{
|
|
if(addr < 0xFFC0 || !_state.RomEnabled) {
|
|
return AddressInfo { addr, MemoryType::SpcRam };
|
|
}
|
|
return AddressInfo { addr & 0x3F, MemoryType::SpcRom };
|
|
}
|
|
|
|
int Spc::GetRelativeAddress(AddressInfo &absAddress)
|
|
{
|
|
if(absAddress.Type == MemoryType::SpcRom) {
|
|
if(_state.RomEnabled) {
|
|
return 0xFFC0 | (absAddress.Address & 0x3F);
|
|
}
|
|
} else {
|
|
if(absAddress.Address < 0xFFC0 || !_state.RomEnabled) {
|
|
return absAddress.Address;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
uint8_t* Spc::GetSpcRam()
|
|
{
|
|
return _ram;
|
|
}
|
|
|
|
uint8_t* Spc::GetSpcRom()
|
|
{
|
|
return _spcBios;
|
|
}
|
|
|
|
void Spc::Serialize(Serializer &s)
|
|
{
|
|
if(s.IsSaving()) {
|
|
//Catch up SPC to main CPU before creating the state
|
|
Run();
|
|
}
|
|
|
|
SV(_state.A); SV(_state.Cycle); SV(_state.PC); SV(_state.PS); SV(_state.SP); SV(_state.X); SV(_state.Y);
|
|
SV(_state.CpuRegs[0]); SV(_state.CpuRegs[1]); SV(_state.CpuRegs[2]); SV(_state.CpuRegs[3]);
|
|
SV(_state.OutputReg[0]); SV(_state.OutputReg[1]); SV(_state.OutputReg[2]); SV(_state.OutputReg[3]);
|
|
SV(_state.RamReg[0]); SV(_state.RamReg[1]);
|
|
SV(_state.ExternalSpeed); SV(_state.InternalSpeed); SV(_state.WriteEnabled); SV(_state.TimersEnabled);
|
|
SV(_state.DspReg); SV(_state.RomEnabled); SV(_clockRatio); SV(_state.TimersDisabled);
|
|
|
|
s.PushNamePrefix("timer0", -1);
|
|
_state.Timer0.Serialize(s);
|
|
s.PopNamePrefix();
|
|
|
|
s.PushNamePrefix("timer1", -1);
|
|
_state.Timer1.Serialize(s);
|
|
s.PopNamePrefix();
|
|
|
|
s.PushNamePrefix("timer2", -1);
|
|
_state.Timer2.Serialize(s);
|
|
s.PopNamePrefix();
|
|
|
|
SVArray(_ram, Spc::SpcRamSize);
|
|
|
|
SV(_dsp);
|
|
|
|
if(!s.IsSaving()) {
|
|
UpdateClockRatio();
|
|
}
|
|
|
|
if(s.GetFormat() != SerializeFormat::Map) {
|
|
SV(_operandA); SV(_operandB); SV(_tmp1); SV(_tmp2); SV(_tmp3); SV(_opCode); SV(_opStep); SV(_opSubStep); SV(_enabled);
|
|
}
|
|
}
|
|
|
|
uint8_t Spc::GetOpCode()
|
|
{
|
|
uint8_t value = Read(_state.PC, MemoryOperationType::ExecOpCode);
|
|
_state.PC++;
|
|
return value;
|
|
}
|
|
|
|
uint8_t Spc::ReadOperandByte()
|
|
{
|
|
uint8_t value = Read(_state.PC, MemoryOperationType::ExecOperand);
|
|
_state.PC++;
|
|
return value;
|
|
}
|
|
|
|
uint16_t Spc::ReadWord(uint16_t addr, MemoryOperationType type)
|
|
{
|
|
uint8_t lsb = Read(addr, type);
|
|
uint8_t msb = Read(addr + 1, type);
|
|
return (msb << 8) | lsb;
|
|
}
|
|
|
|
void Spc::ClearFlags(uint8_t flags)
|
|
{
|
|
_state.PS &= ~flags;
|
|
}
|
|
|
|
void Spc::SetFlags(uint8_t flags)
|
|
{
|
|
_state.PS |= flags;
|
|
}
|
|
|
|
bool Spc::CheckFlag(uint8_t flag)
|
|
{
|
|
return (_state.PS & flag) == flag;
|
|
}
|
|
|
|
void Spc::SetZeroNegativeFlags(uint8_t value)
|
|
{
|
|
ClearFlags(SpcFlags::Zero | SpcFlags::Negative);
|
|
if(value == 0) {
|
|
SetFlags(SpcFlags::Zero);
|
|
} else if(value & 0x80) {
|
|
SetFlags(SpcFlags::Negative);
|
|
}
|
|
}
|
|
|
|
void Spc::SetZeroNegativeFlags16(uint16_t value)
|
|
{
|
|
ClearFlags(SpcFlags::Zero | SpcFlags::Negative);
|
|
if(value == 0) {
|
|
SetFlags(SpcFlags::Zero);
|
|
} else if(value & 0x8000) {
|
|
SetFlags(SpcFlags::Negative);
|
|
}
|
|
}
|
|
|
|
uint8_t Spc::GetByteValue()
|
|
{
|
|
return Read(_operandA);
|
|
}
|
|
|
|
void Spc::Push(uint8_t value)
|
|
{
|
|
Write(0x100 | _state.SP, value);
|
|
_state.SP--;
|
|
}
|
|
|
|
uint8_t Spc::Pop()
|
|
{
|
|
_state.SP++;
|
|
return Read(0x100 | _state.SP);
|
|
}
|
|
|
|
uint16_t Spc::GetDirectAddress(uint8_t offset)
|
|
{
|
|
return (CheckFlag(SpcFlags::DirectPage) ? 0x100 : 0) + offset;
|
|
}
|
|
|
|
void Spc::LoadSpcFile(SpcFileData* data)
|
|
{
|
|
memcpy(_ram, data->SpcRam, Spc::SpcRamSize);
|
|
|
|
memcpy(_dsp->GetState().Regs, data->DspRegs, sizeof(data->DspRegs));
|
|
|
|
_state.PC = data->PC;
|
|
_state.A = data->A;
|
|
_state.X = data->X;
|
|
_state.Y = data->Y;
|
|
_state.PS = data->PS;
|
|
_state.SP = data->SP;
|
|
|
|
Write(0xF1, data->ControlReg);
|
|
_state.DspReg = data->DspRegSelect;
|
|
|
|
_state.CpuRegs[0] = data->CpuRegs[0];
|
|
_state.CpuRegs[1] = data->CpuRegs[1];
|
|
_state.CpuRegs[2] = data->CpuRegs[2];
|
|
_state.CpuRegs[3] = data->CpuRegs[3];
|
|
|
|
_state.RamReg[0] = data->RamRegs[0];
|
|
_state.RamReg[1] = data->RamRegs[1];
|
|
|
|
_state.Timer0.SetTarget(data->TimerTarget[0]);
|
|
_state.Timer1.SetTarget(data->TimerTarget[1]);
|
|
_state.Timer2.SetTarget(data->TimerTarget[2]);
|
|
|
|
_state.Timer0.SetOutput(data->TimerOutput[0]);
|
|
_state.Timer1.SetOutput(data->TimerOutput[0]);
|
|
_state.Timer2.SetOutput(data->TimerOutput[0]);
|
|
} |