// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team // SPDX-License-Identifier: GPL-3.0+ #include "Common.h" #include "VUmicro.h" #include "microVU_Divtrace.h" #include "vu_capture.h" #include #include extern void _vuFlushAll(VURegs* VU); static void _vu0ExecUpper(VURegs* VU, u32* ptr) { VU->code = ptr[1]; IdebugUPPER(VU0); VU0_UPPER_OPCODE[VU->code & 0x3f](); } static void _vu0ExecLower(VURegs* VU, u32* ptr) { VU->code = ptr[0]; IdebugLOWER(VU0); VU0_LOWER_OPCODE[VU->code >> 25](); } int vu0branch = 0; static void _vu0Exec(VURegs* VU) { _VURegsNum lregs; _VURegsNum uregs; u32* ptr; ptr = (u32*)&VU->Micro[VU->VI[REG_TPC].UL]; VU->VI[REG_TPC].UL += 8; if (ptr[1] & 0x40000000) // E flag { VU->ebit = 2; } if (ptr[1] & 0x20000000 && VU == &VU0) // M flag { VU->flags |= VUFLAG_MFLAGSET; // Console.WriteLn("fixme: M flag set"); } if (ptr[1] & 0x10000000) // D flag { if (VU0.VI[REG_FBRST].UL & 0x4) { VU0.VI[REG_VPU_STAT].UL |= 0x2; hwIntcIrq(INTC_VU0); VU->ebit = 1; } } if (ptr[1] & 0x08000000) // T flag { if (VU0.VI[REG_FBRST].UL & 0x8) { VU0.VI[REG_VPU_STAT].UL |= 0x4; hwIntcIrq(INTC_VU0); VU->ebit = 1; } } VU->code = ptr[1]; VU0regs_UPPER_OPCODE[VU->code & 0x3f](&uregs); u64 cyclesBeforeOp = VU0.cycle - 1; _vuTestUpperStalls(VU, &uregs); /* check upper flags */ if (ptr[1] & 0x80000000) // I flag { _vuTestPipes(VU); if (VU->VIBackupCycles > 0) VU->VIBackupCycles -= std::min((u8)(VU0.cycle - cyclesBeforeOp), VU->VIBackupCycles); _vu0ExecUpper(VU, ptr); VU->VI[REG_I].UL = ptr[0]; memset(&lregs, 0, sizeof(lregs)); } else { VECTOR _VF; VECTOR _VFc; REG_VI _VI; REG_VI _VIc; int vfreg = 0; int vireg = 0; int discard = 0; VU->code = ptr[0]; lregs.cycles = 0; VU0regs_LOWER_OPCODE[VU->code >> 25](&lregs); _vuTestLowerStalls(VU, &lregs); _vuTestPipes(VU); if (VU->VIBackupCycles > 0) VU->VIBackupCycles -= std::min((u8)(VU0.cycle - cyclesBeforeOp), VU->VIBackupCycles); vu0branch = lregs.pipe == VUPIPE_BRANCH; if (uregs.VFwrite) { if (lregs.VFwrite == uregs.VFwrite) { // Console.Warning("*PCSX2*: Warning, VF write to the same reg in both lower/upper cycle"); discard = 1; } if (lregs.VFread0 == uregs.VFwrite || lregs.VFread1 == uregs.VFwrite) { // Console.WriteLn("saving reg %d at pc=%x", i, VU->VI[REG_TPC].UL); _VF = VU->VF[uregs.VFwrite]; vfreg = uregs.VFwrite; } } if (uregs.VIread & (1 << REG_CLIP_FLAG)) { if (lregs.VIwrite & (1 << REG_CLIP_FLAG)) { //Console.Warning("*PCSX2*: Warning, VI write to the same reg in both lower/upper cycle"); discard = 1; } if (lregs.VIread & (1 << REG_CLIP_FLAG)) { _VI = VU0.VI[REG_CLIP_FLAG]; vireg = REG_CLIP_FLAG; } } _vu0ExecUpper(VU, ptr); if (discard == 0) { if (vfreg) { _VFc = VU->VF[vfreg]; VU->VF[vfreg] = _VF; } if (vireg) { _VIc = VU->VI[vireg]; VU->VI[vireg] = _VI; } _vu0ExecLower(VU, ptr); if (vfreg) { VU->VF[vfreg] = _VFc; } if (vireg) { VU->VI[vireg] = _VIc; } } } if (uregs.pipe == VUPIPE_FMAC || lregs.pipe == VUPIPE_FMAC) _vuClearFMAC(VU); _vuAddUpperStalls(VU, &uregs); _vuAddLowerStalls(VU, &lregs); if (VU->branch > 0) { if (VU->branch-- == 1) { VU->VI[REG_TPC].UL = VU->branchpc; if (VU->takedelaybranch) { DevCon.Warning("VU0 - Branch/Jump in Delay Slot"); VU->branch = 1; VU->branchpc = VU->delaybranchpc; VU->takedelaybranch = false; } } } if (VU->ebit > 0) { if (VU->ebit-- == 1) { VU->VIBackupCycles = 0; _vuFlushAll(VU); VU0.VI[REG_VPU_STAT].UL &= ~0x1; /* E flag */ vif0Regs.stat.VEW = false; } } // Progress the write position of the FMAC pipeline by one place if (uregs.pipe == VUPIPE_FMAC || lregs.pipe == VUPIPE_FMAC) VU->fmacwritepos = (VU->fmacwritepos + 1) & 3; } void vu0Exec(VURegs* VU) { VU0.VI[REG_TPC].UL &= VU0_PROGMASK; VU->cycle++; _vu0Exec(VU); if (VU->VI[0].UL != 0) DbgCon.Error("VI[0] != 0!!!!\n"); if (VU->VF[0].f.x != 0.0f) DbgCon.Error("VF[0].x != 0.0!!!!\n"); if (VU->VF[0].f.y != 0.0f) DbgCon.Error("VF[0].y != 0.0!!!!\n"); if (VU->VF[0].f.z != 0.0f) DbgCon.Error("VF[0].z != 0.0!!!!\n"); if (VU->VF[0].f.w != 1.0f) DbgCon.Error("VF[0].w != 1.0!!!!\n"); } // -------------------------------------------------------------------------------------- // VU0microInterpreter // -------------------------------------------------------------------------------------- InterpVU0 CpuIntVU0; InterpVU0::InterpVU0() { m_Idx = 0; IsInterpreter = true; } void InterpVU0::Reset() { DevCon.Warning("VU0 Int Reset"); VU0.fmacwritepos = 0; VU0.fmacreadpos = 0; VU0.fmaccount = 0; VU0.ialuwritepos = 0; VU0.ialureadpos = 0; VU0.ialucount = 0; } void InterpVU0::SetStartPC(u32 startPC) { VU0.start_pc = startPC; } void InterpVU0::Step() { vu0Exec(&VU0); } void InterpVU0::Execute(u32 cycles) { const FPControlRegisterBackup fpcr_backup(EmuConfig.Cpu.VU0FPCR); VU0.VI[REG_TPC].UL <<= 3; VU0.flags &= ~VUFLAG_MFLAGSET; #ifdef PCSX2_RECOMPILER_TESTS // Live-game capture probe — mirror of the mVU JIT-side probe in mVUexecute // (microVU-arm64.cpp) and the VU1 interp probe. Micro-mode VU0 programs // only; COP2 macro-mode single ops don't route through here (same as JIT // side). No-op unless PCSX2_VU_CAPTURE_DIR / PCSX2_VU_RANK_OUT is set. vu_capture::MaybeCapture(0, VU0.VI[REG_TPC].UL & 0xff8, cycles, (const u8*)VU0.Micro, VU0_PROGSIZE, (const u8*)VU0.Mem, VU0_MEMSIZE, VU0); #endif u64 startcycles = VU0.cycle; while ((VU0.cycle - startcycles) < cycles) { if (!(VU0.VI[REG_VPU_STAT].UL & 0x1)) { // Branches advance the PC to the new location if there was a branch in the E-Bit delay slot if (VU0.branch) { VU0.VI[REG_TPC].UL = VU0.branchpc; VU0.branch = 0; } break; } if (VU0.flags & VUFLAG_MFLAGSET) break; #ifdef PCSX2_RECOMPILER_TESTS // REG_TPC was shifted to byte-PC at function entry; already a byte addr. const u32 dt_pre_xPC = VU0.VI[REG_TPC].UL; #endif vu0Exec(&VU0); #ifdef PCSX2_RECOMPILER_TESTS // vudivtrace: snapshot VU0 architectural state after each interp op. // Test-hook-only; release builds drop the per-op probe entirely. if (mvu_divtrace::g_enabled.load(std::memory_order_relaxed) && mvu_divtrace::g_vu_index == 0 && mvu_divtrace::g_interp_op_idx < mvu_divtrace::g_interp_fps.size()) { const u32 idx = mvu_divtrace::g_interp_op_idx; mvu_divtrace::g_interp_fps[idx] = mvu_divtrace::FingerprintRegs(vuRegs[0]); mvu_divtrace::g_interp_xpc[idx] = dt_pre_xPC; if (idx >= mvu_divtrace::g_full_lo && idx < mvu_divtrace::g_full_hi) { auto& snap = mvu_divtrace::g_interp_snaps[idx - mvu_divtrace::g_full_lo]; std::memcpy(&snap.regs, &vuRegs[0], sizeof(VURegs)); snap.meta_idx = 0xFFFF; snap.pre_xPC = dt_pre_xPC; } ++mvu_divtrace::g_interp_op_idx; } #endif } VU0.VI[REG_TPC].UL >>= 3; if (EmuConfig.Speedhacks.EECycleRate != 0 && (!EmuConfig.Gamefixes.VUSyncHack || EmuConfig.Speedhacks.EECycleRate < 0)) { u64 cycle_change = VU0.cycle - startcycles; VU0.cycle -= cycle_change; switch (std::min(static_cast(EmuConfig.Speedhacks.EECycleRate), static_cast(cycle_change))) { case -3: // 50% cycle_change *= 2.0f; break; case -2: // 60% cycle_change *= 1.6666667f; break; case -1: // 75% cycle_change *= 1.3333333f; break; case 1: // 130% cycle_change /= 1.3f; break; case 2: // 180% cycle_change /= 1.8f; break; case 3: // 300% cycle_change /= 3.0f; break; default: break; } VU0.cycle += cycle_change; } VU0.nextBlockCycles = (VU0.cycle - cpuRegs.cycle) + 1; }