Files
ARMSX2/pcsx2/R3000A.cpp
T
2026-05-16 19:54:54 -04:00

360 lines
10 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "R3000A.h"
#include "Common.h"
#include "SIO/Sio0.h"
#include "Sif.h"
#include "DebugTools/Breakpoints.h"
#include "R5900OpcodeTables.h"
#include "IopCounters.h"
#include "IopBios.h"
#include "IopHw.h"
#include "IopDma.h"
#include "Mdec.h"
#include "CDVD/Ps1CD.h"
#include "CDVD/CDVD.h"
using namespace R3000A;
R3000Acpu *psxCpu;
// used for constant propagation
u32 g_psxConstRegs[32];
u32 g_psxHasConstReg, g_psxFlushedConstReg;
// Used to signal to the EE when important actions that need IOP-attention have
// happened (hsyncs, vsyncs, IOP exceptions, etc). IOP runs code whenever this
// is true, even if it's already running ahead a bit.
bool iopEventAction = false;
static constexpr uint iopWaitCycles = 384; // Keep inline with EE wait cycle max.
bool iopEventTestIsActive = false;
alignas(16) psxRegisters psxRegs;
void psxReset()
{
std::memset(&psxRegs, 0, sizeof(psxRegs));
psxRegs.pc = 0xbfc00000; // Start in bootstrap
psxRegs.CP0.n.Status = 0x00400000; // BEV = 1
psxRegs.CP0.n.PRid = 0x0000001f; // PRevID = Revision ID, same as the IOP R3000A
psxRegs.iopBreak = 0;
psxRegs.iopCycleEE = -1;
psxRegs.iopCycleEECarry = 0;
psxRegs.iopNextEventCycle = psxRegs.cycle + 4;
psxHwReset();
PSXCLK = 36864000;
ioman::reset();
psxBiosReset();
}
void psxShutdown() {
//psxCpu->Shutdown();
}
void psxException(u32 code, u32 bd)
{
// PSXCPU_LOG("psxException %x: %x, %x", code, psxHu32(0x1070), psxHu32(0x1074));
//Console.WriteLn("!! psxException %x: %x, %x", code, psxHu32(0x1070), psxHu32(0x1074));
// Set the Cause
psxRegs.CP0.n.Cause &= ~0x7f;
psxRegs.CP0.n.Cause |= code;
// Set the EPC & PC
if (bd)
{
PSXCPU_LOG("bd set");
psxRegs.CP0.n.Cause|= 0x80000000;
psxRegs.CP0.n.EPC = (psxRegs.pc - 4);
}
else
psxRegs.CP0.n.EPC = (psxRegs.pc);
if (psxRegs.CP0.n.Status & 0x400000)
psxRegs.pc = 0xbfc00180;
else
psxRegs.pc = 0x80000080;
// Set the Status
psxRegs.CP0.n.Status = (psxRegs.CP0.n.Status &~0x3f) |
((psxRegs.CP0.n.Status & 0xf) << 2);
/*if ((((PSXMu32(psxRegs.CP0.n.EPC) >> 24) & 0xfe) == 0x4a)) {
// "hokuto no ken" / "Crash Bandicot 2" ... fix
PSXMu32(psxRegs.CP0.n.EPC)&= ~0x02000000;
}*/
/*if (psxRegs.CP0.n.Cause == 0x400 && (!(psxHu32(0x1450) & 0x8))) {
hwIntcIrq(INTC_SBUS);
}*/
}
__fi void psxSetNextBranch( u64 startCycle, s32 delta )
{
// typecast the conditional to signed so that things don't blow up
// if startCycle is greater than our next branch cycle.
if( (int)(psxRegs.iopNextEventCycle - startCycle) > delta )
psxRegs.iopNextEventCycle = startCycle + delta;
}
__fi void psxSetNextBranchDelta( s32 delta )
{
psxSetNextBranch( psxRegs.cycle, delta );
}
__fi int psxTestCycle( u64 startCycle, s32 delta )
{
// typecast the conditional to signed so that things don't explode
// if the startCycle is ahead of our current cpu cycle.
return (int)(psxRegs.cycle - startCycle) >= delta;
}
__fi int psxRemainingCycles(IopEventId n)
{
if (psxRegs.interrupt & (1 << n))
return ((psxRegs.cycle - psxRegs.sCycle[n]) + psxRegs.eCycle[n]);
else
return 0;
}
// ===================== PSX_INT PER-EVENT TUNING KNOBS =====================
// Scale factor (in 1/256ths) applied to each PSX_INT(n, ecycle) call, so we
// can characterize which IOP-side interrupt's delay is too short and is
// causing the PS1 "global speedup" by letting the IOP run more code per
// real-second than it should. 256 = 1.0× (default upstream behavior).
//
// Crank an entry up — say 1024 (4×) or 4096 (16×) — and observe whether
// PS1 game speed changes. If cranking IopEvt_X slows the game, that path
// is dispatching too often / undercharging cycles in the upstream code.
//
// Sized to NUM_EVENTS (= IopEvt count). Order MUST match enum IopEventId.
static constexpr u32 PSX_INT_SCALE_256[] = {
256, // IopEvt_SIF2
256, // IopEvt_Cdvd
256, // IopEvt_SIF0
256, // IopEvt_SIF1
256, // IopEvt_Dma11 (SIO2 in)
256, // IopEvt_Dma12 (SIO2 out)
256, // IopEvt_SIO
256, // IopEvt_Cdrom (PS1 CDROM)
256, // IopEvt_CdromRead (PS1 CDROM read)
256, // IopEvt_CdvdRead
256, // IopEvt_CdvdSectorReady
256, // IopEvt_DEV9
256, // IopEvt_USB
256, // IopEvt_DmaMDECin (PS1 MDEC IN DMA channel 0)
256, // IopEvt_DmaMDECout (PS1 MDEC OUT DMA channel 1)
};
static_assert(sizeof(PSX_INT_SCALE_256) / sizeof(PSX_INT_SCALE_256[0]) ==
(IopEvt_DmaMDECout + 1), "PSX_INT_SCALE_256 size must match IopEventId enum");
__fi void PSX_INT( IopEventId n, s32 ecycle )
{
// 19 is CDVD read int, it's supposed to be high.
//if (ecycle > 8192 && n != 19)
// DevCon.Warning( "IOP cycles high: %d, n %d", ecycle, n );
// Apply the per-event tuning knob. ecycle is signed; scale in 64-bit
// to avoid 32-bit overflow when scale > 256, then clamp to s32.
const s64 scaled = (static_cast<s64>(ecycle) * static_cast<s64>(PSX_INT_SCALE_256[n])) >> 8;
if (scaled > 0x7FFFFFFFLL) ecycle = 0x7FFFFFFF;
else if (scaled < -0x7FFFFFFFLL) ecycle = -0x7FFFFFFF;
else ecycle = static_cast<s32>(scaled);
psxRegs.interrupt |= 1 << n;
psxRegs.sCycle[n] = psxRegs.cycle;
psxRegs.eCycle[n] = ecycle;
psxSetNextBranchDelta(ecycle);
// PS2 mode (PSXCLK=36864000) → ratio is exactly 8, shift instead of float
// div + cvt. PS1 mode (PSXCLK=33868800) keeps the precise path. See
// matching gate in R5900.cpp _cpuEventTest_Shared and the
// armsx2_ps1_mode_pacing_audit memo for PS1 mode pacing.
const s32 iopDeltaRaw = static_cast<s32>(psxRegs.iopNextEventCycle - psxRegs.cycle);
s32 iopDelta;
if (PSXCLK == 36864000) [[likely]]
{
iopDelta = iopDeltaRaw << 3;
}
else
{
const float mutiplier = static_cast<float>(PS2CLK) / static_cast<float>(PSXCLK);
iopDelta = static_cast<s32>(iopDeltaRaw * mutiplier);
}
if (psxRegs.iopCycleEE < iopDelta)
{
// The EE called this int, so inform it to branch as needed:
cpuSetNextEventDelta(iopDelta - psxRegs.iopCycleEE);
}
}
static __fi void IopTestEvent( IopEventId n, void (*callback)() )
{
if( !(psxRegs.interrupt & (1 << n)) ) return;
if( psxTestCycle( psxRegs.sCycle[n], psxRegs.eCycle[n] ) )
{
psxRegs.interrupt &= ~(1 << n);
callback();
}
else
psxSetNextBranch( psxRegs.sCycle[n], psxRegs.eCycle[n] );
}
static __fi void Sio0TestEvent(IopEventId n)
{
if (!(psxRegs.interrupt & (1 << n)))
{
return;
}
if (psxTestCycle(psxRegs.sCycle[n], psxRegs.eCycle[n]))
{
psxRegs.interrupt &= ~(1 << n);
g_Sio0.Interrupt(Sio0Interrupt::TEST_EVENT);
}
else
{
psxSetNextBranch(psxRegs.sCycle[n], psxRegs.eCycle[n]);
}
}
static __fi void _psxTestInterrupts()
{
IopTestEvent(IopEvt_SIF0, sif0Interrupt); // SIF0
IopTestEvent(IopEvt_SIF1, sif1Interrupt); // SIF1
IopTestEvent(IopEvt_SIF2, sif2Interrupt); // SIF2
Sio0TestEvent(IopEvt_SIO);
IopTestEvent(IopEvt_CdvdSectorReady, cdvdSectorReady);
IopTestEvent(IopEvt_CdvdRead, cdvdReadInterrupt);
// Profile-guided Optimization (sorta)
// The following ints are rarely called. Encasing them in a conditional
// as follows helps speed up most games.
if( psxRegs.interrupt & ((1 << IopEvt_Cdvd) | (1 << IopEvt_Dma11) | (1 << IopEvt_Dma12)
| (1 << IopEvt_Cdrom) | (1 << IopEvt_CdromRead) | (1 << IopEvt_DEV9) | (1 << IopEvt_USB)
| (1 << IopEvt_DmaMDECin) | (1 << IopEvt_DmaMDECout)))
{
IopTestEvent(IopEvt_Cdvd, cdvdActionInterrupt);
IopTestEvent(IopEvt_Dma11, psxDMA11Interrupt); // SIO2
IopTestEvent(IopEvt_Dma12, psxDMA12Interrupt); // SIO2
IopTestEvent(IopEvt_Cdrom, cdrInterrupt);
IopTestEvent(IopEvt_CdromRead, cdrReadInterrupt);
IopTestEvent(IopEvt_DEV9, dev9Interrupt);
IopTestEvent(IopEvt_USB, usbInterrupt);
IopTestEvent(IopEvt_DmaMDECin, psxDMAMDECinInterrupt);
IopTestEvent(IopEvt_DmaMDECout, psxDMAMDECoutInterrupt);
}
}
__ri void iopEventTest()
{
psxRegs.iopNextEventCycle = psxRegs.cycle + iopWaitCycles;
if (psxTestCycle(psxNextStartCounter, psxNextDeltaCounter))
{
psxRcntUpdate();
iopEventAction = true;
}
else
{
// start the next branch at the next counter event by default
// the interrupt code below will assign nearer branches if needed.
if (psxNextDeltaCounter < static_cast<s32>(psxRegs.iopNextEventCycle - psxNextStartCounter))
psxRegs.iopNextEventCycle = psxNextStartCounter + psxNextDeltaCounter;
}
if (psxRegs.interrupt)
{
iopEventTestIsActive = true;
_psxTestInterrupts();
iopEventTestIsActive = false;
}
if ((psxHu32(HW_ICTRL) != 0) && ((psxHu32(HW_ISTAT) & psxHu32(HW_IMASK)) != 0))
{
if ((psxRegs.CP0.n.Status & 0xFE01) >= 0x401)
{
PSXCPU_LOG("Interrupt: %x %x", psxHu32(HW_ISTAT), psxHu32(HW_IMASK));
psxException(0, 0);
iopEventAction = true;
}
}
}
void iopTestIntc()
{
if( psxHu32(HW_ICTRL) == 0 ) return;
if( (psxHu32(HW_ISTAT) & psxHu32(HW_IMASK)) == 0 ) return;
if( !eeEventTestIsActive )
{
// An iop exception has occurred while the EE is running code.
// Inform the EE to branch so the IOP can handle it promptly:
cpuSetNextEventDelta( 16 );
iopEventAction = true;
//Console.Error( "** IOP Needs an EE EventText, kthx ** %d", iopCycleEE );
// Note: No need to set the iop's branch delta here, since the EE
// will run an IOP branch test regardless.
}
else if ( !iopEventTestIsActive )
psxSetNextBranchDelta( 2 );
}
inline bool psxIsBranchOrJump(u32 addr)
{
u32 op = iopMemRead32(addr);
const R5900::OPCODE& opcode = R5900::GetInstruction(op);
return (opcode.flags & IS_BRANCH) != 0;
}
// The next two functions return 0 if no breakpoint is needed,
// 1 if it's needed on the current pc, 2 if it's needed in the delay slot
// 3 if needed in both
int psxIsBreakpointNeeded(u32 addr)
{
int bpFlags = 0;
if (CBreakPoints::IsAddressBreakPoint(BREAKPOINT_IOP, addr))
bpFlags += 1;
// there may be a breakpoint in the delay slot
if (psxIsBranchOrJump(addr) && CBreakPoints::IsAddressBreakPoint(BREAKPOINT_IOP, addr + 4))
bpFlags += 2;
return bpFlags;
}
int psxIsMemcheckNeeded(u32 pc)
{
if (CBreakPoints::GetNumMemchecks() == 0)
return 0;
u32 addr = pc;
if (psxIsBranchOrJump(addr))
addr += 4;
u32 op = iopMemRead32(addr);
const R5900::OPCODE& opcode = R5900::GetInstruction(op);
if (opcode.flags & IS_MEMORY)
return addr == pc ? 1 : 2;
return 0;
}