Files
PS2Recomp/ps2xRuntime/include/runtime/ps2_vu1.h
Ran-j e27a658b32 feat: wip performance patch
feat: added parallel gs
feat: optmized IOP emulator
feat: added guest and game fps count
feat: small vu1 optmization and guards
2026-09-25 14:29:05 -03:00

355 lines
11 KiB
C++

#ifndef PS2_VU1_H
#define PS2_VU1_H
#include <array>
#include <bit>
#include <cstddef>
#include <limits>
#include <cstdint>
class GS;
class PS2Memory;
struct VU1State
{
float vf[32][4];
int32_t vi[16];
float acc[4];
float q;
float p;
float i;
uint32_t r;
uint32_t pc;
uint32_t mac;
uint32_t clip;
uint32_t status;
uint64_t cycles;
bool ebit;
bool haltAfterDelaySlot;
bool dBitEnabled;
bool tBitEnabled;
bool stoppedByD;
bool stoppedByT;
uint32_t top; // VIF TOP visible to XTOP
uint32_t itop; // VIF ITOP visible to XITOP
bool branchPending;
uint32_t branchTarget;
uint32_t branchDelay;
};
class VU1Interpreter
{
public:
enum class Unit : uint8_t
{
VU0,
VU1
};
explicit VU1Interpreter(Unit unit = Unit::VU1);
void reset();
void execute(uint8_t *vuCode, uint32_t codeSize,
uint8_t *vuData, uint32_t dataSize,
GS &gs, PS2Memory *memory = nullptr,
uint32_t startPC = 0, uint32_t top = 0, uint32_t itop = 0,
uint32_t maxCycles = 65536);
void resume(uint8_t *vuCode, uint32_t codeSize,
uint8_t *vuData, uint32_t dataSize,
GS &gs, PS2Memory *memory = nullptr,
uint32_t top = 0, uint32_t itop = 0, uint32_t maxCycles = 65536);
VU1State &state() { return m_state; }
const VU1State &state() const { return m_state; }
private:
enum Pipeline : uint8_t
{
PipelineNone = 0,
PipelineFmac,
PipelineLsu,
PipelineFdiv,
PipelineEfu,
PipelineIalu,
PipelineBranch,
PipelineXgkick
};
struct VfAccess
{
uint8_t reg = 0;
uint8_t lanes = 0;
};
struct InstructionUsage
{
std::array<VfAccess, 2> vfRead{};
VfAccess vfWrite{};
uint8_t vfReadCount = 0;
uint16_t viRead = 0;
uint16_t viWrite = 0;
uint8_t accRead = 0;
uint8_t accWrite = 0;
uint8_t latency = 0;
uint8_t vfLatency = 0;
uint8_t viLatency = 0;
Pipeline pipeline = PipelineNone;
bool waitQ = false;
bool waitP = false;
bool readsClip = false;
bool writesClip = false;
bool delaysNextBranchRead = false;
bool reserved = false;
};
static constexpr uint32_t kVfReadyCount = 32u * 4u;
static constexpr uint32_t kViReadyBase = kVfReadyCount;
static constexpr uint32_t kAccReadyBase = kViReadyBase + 16u;
static constexpr uint32_t kRegisterReadyCount = kAccReadyBase + 4u;
struct DecodedInstructionPair
{
uint32_t lower = 0;
uint32_t upper = 0;
InstructionUsage lowerUsage{};
InstructionUsage upperUsage{};
bool iBit = false;
bool eBit = false;
bool mBit = false;
bool dBit = false;
bool tBit = false;
uint8_t suppressedLowerVf = 0;
std::array<uint8_t, 4u * 4u + 15u + 4u> readDependencies{};
uint8_t readDependencyCount = 0;
};
struct FlagPipelineEntry
{
uint64_t readyCycle = 0;
uint64_t issueCycle = 0;
uint32_t mac = 0;
uint32_t status = 0;
uint32_t extraSticky = 0;
uint32_t clip = 0;
bool valid = false;
bool writesMac = false;
bool writesStatus = false;
bool writesSticky = false;
bool writesClip = false;
};
struct ScalarPipelineEntry
{
uint64_t readyCycle = 0;
float value = 0.0f;
uint32_t statusDi = 0;
bool valid = false;
};
struct PendingStore
{
uint64_t readyCycle = 0;
uint32_t address = 0;
std::array<uint32_t, 4> words{};
uint8_t laneMask = 0;
bool valid = false;
};
struct PendingVfWrite
{
uint64_t readyCycle = 0;
uint64_t sequence = 0;
std::array<float, 4> value{};
uint8_t reg = 0;
uint8_t laneMask = 0;
bool valid = false;
};
struct PendingViWrite
{
uint64_t readyCycle = 0;
uint64_t sequence = 0;
int32_t value = 0;
uint8_t reg = 0;
bool valid = false;
};
struct PendingAccWrite
{
uint64_t readyCycle = 0;
uint64_t sequence = 0;
std::array<float, 4> value{};
uint8_t laneMask = 0;
bool valid = false;
};
struct XgkickPipeline
{
static constexpr uint32_t kBufferSize = 0x10000u;
std::array<uint8_t, kBufferSize> packet{};
uint32_t sourceAddress = 0;
uint32_t totalBytes = 0;
uint32_t copiedBytes = 0;
uint32_t currentTagEnd = 0;
uint32_t cycleCredit = 0;
uint64_t issueCycle = 0;
bool active = false;
bool currentTagEop = false;
void reset()
{
sourceAddress = totalBytes = copiedBytes = currentTagEnd = cycleCredit = 0;
issueCycle = 0;
active = currentTagEop = false;
}
};
static constexpr uint32_t kFmacLatency = 4u;
static constexpr uint32_t kAccForwardLatency = 1u;
static constexpr uint32_t kMaxFlagEntries = 8u;
static constexpr uint32_t kMaxPendingStores = 8u;
static constexpr uint32_t kMaxPendingVfWrites = 16u;
static constexpr uint32_t kMaxPendingViWrites = 8u;
static constexpr uint32_t kMaxPendingAccWrites = 8u;
static constexpr uint32_t kMaxDecodedPairs = 0x4000u / 8u;
Unit m_unit;
VU1State m_state;
std::array<DecodedInstructionPair, kMaxDecodedPairs> m_decodedCodeCache{};
std::array<uint64_t, kMaxDecodedPairs / 64u> m_decodedPairValid{};
DecodedInstructionPair m_uncachedDecoded{};
const uint8_t *m_cachedVuCode = nullptr;
const PS2Memory *m_cachedMemory = nullptr;
uint32_t m_cachedCodeSize = 0;
uint64_t m_cachedCodeGeneration = 0;
bool m_decodedCodeCacheValid = false;
std::array<FlagPipelineEntry, kMaxFlagEntries> m_flagPipeline{};
ScalarPipelineEntry m_fdiv{};
std::array<ScalarPipelineEntry, 2> m_efu{};
std::array<PendingStore, kMaxPendingStores> m_storePipeline{};
std::array<PendingVfWrite, kMaxPendingVfWrites> m_vfWritePipeline{};
std::array<PendingViWrite, kMaxPendingViWrites> m_viWritePipeline{};
std::array<PendingAccWrite, kMaxPendingAccWrites> m_accWritePipeline{};
uint32_t m_flagActive = 0;
uint32_t m_efuActive = 0;
uint32_t m_storeActive = 0;
uint32_t m_vfWriteActive = 0;
uint32_t m_viWriteActive = 0;
uint32_t m_accWriteActive = 0;
static constexpr uint64_t kNoPipelineEvent = std::numeric_limits<uint64_t>::max();
uint64_t m_nextPipelineCycle = kNoPipelineEvent;
bool m_schedulerClean = true;
XgkickPipeline m_xgkick{};
std::array<uint64_t, kRegisterReadyCount> m_registerReady{};
std::array<std::array<uint64_t, 4>, 32> m_vfLatestWrite{};
std::array<uint64_t, 16> m_viLatestWrite{};
std::array<uint64_t, 4> m_accLatestWrite{};
uint64_t m_cycle = 0;
uint64_t m_nextWriteSequence = 0;
uint64_t m_efuResourceReady = 0;
uint32_t m_workingClip = 0;
uint32_t m_currentUpperInstruction = 0;
struct UpperOperands
{
float vs[4], vt[4], acc[4], q, i;
} m_upperOperands{};
int32_t m_viBranchBackupValue = 0;
uint8_t m_viBranchBackupReg = 0;
bool m_viBranchBackupValid = false;
uint8_t *m_activeVuData = nullptr;
uint32_t m_activeVuDataSize = 0;
GS *m_activeGs = nullptr;
PS2Memory *m_activeMemory = nullptr;
bool m_stopRequested = false;
bool m_pendingHaltD = false;
bool m_pendingHaltT = false;
void run(uint8_t *vuCode, uint32_t codeSize,
uint8_t *vuData, uint32_t dataSize,
GS &gs, PS2Memory *memory, uint32_t maxCycles);
InstructionUsage decodeUpperUsage(uint32_t upper) const;
InstructionUsage decodeLowerUsage(uint32_t lower) const;
static void addVfRead(InstructionUsage &usage, uint8_t reg, uint8_t lanes);
static void addVfWrite(InstructionUsage &usage, uint8_t reg, uint8_t lanes);
DecodedInstructionPair decodeInstructionPair(const uint8_t *vuCode, uint32_t pc) const;
const DecodedInstructionPair &getDecodedInstructionPairForPc(const uint8_t *vuCode, uint32_t codeSize, PS2Memory *memory, uint32_t pc);
void invalidateDecodedCodeCache(const uint8_t *vuCode, uint32_t codeSize, const PS2Memory *memory, uint64_t generation);
void execUpper(uint32_t instr, float *vfResult, float *accResult);
void execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSize, GS &gs, PS2Memory *memory, uint32_t upperInstr);
void applyDest(float *dst, const float *result, uint8_t dest);
void applyFmacDest(float *dst, float *result, uint8_t dest);
void normalizeFmacResult(float *result, uint8_t dest, uint8_t laneFlags[4]);
bool calculateFmacExactResult(uint32_t component, long double &result) const;
uint8_t normalizeFmacExactResult(float &value, long double exactResult) const;
uint32_t calculateFmacProductSticky(uint8_t dest) const;
void updateFmacFlags(const uint8_t laneFlags[4], uint8_t dest, uint32_t extraSticky);
void queueFsset(uint16_t immediate);
void queueClip(uint32_t clip);
void queueFcset(uint32_t clip);
void queueQ(float value, uint32_t latency, uint32_t statusDi);
void queueP(float value, uint32_t latency);
void queueStore(uint32_t address, const uint32_t words[4], uint8_t laneMask);
void queueVfWrite(uint8_t reg, uint8_t laneMask, const float value[4], uint32_t latency);
void queueViWrite(uint8_t reg, int32_t value, uint32_t latency);
void queueAccWrite(uint8_t laneMask, const float value[4], uint32_t latency);
void startXgkick(uint32_t qwordAddress);
template <typename Entry, std::size_t Capacity>
Entry *allocatePipelineEntry(std::array<Entry, Capacity> &entries, uint32_t &active, uint64_t readyCycle)
{
static_assert(Capacity > 0 && Capacity <= 32);
const uint32_t slot = std::countr_zero(~active);
if (slot >= Capacity)
return nullptr;
active |= 1u << slot;
Entry &entry = entries[slot];
entry = {};
entry.valid = true;
entry.readyCycle = readyCycle;
if (readyCycle < m_nextPipelineCycle)
m_nextPipelineCycle = readyCycle;
return &entry;
}
void resetScheduler();
void commitReadyPipelines();
void advanceOneCycle();
void advanceTo(uint64_t targetCycle);
void flushPipelines();
void progressXgkick(uint32_t elapsedCycles = 1u);
void finishXgkick();
uint64_t calculatePairReadyCycle(const DecodedInstructionPair &decoded) const;
void markPairWrites(const DecodedInstructionPair &decoded);
bool pipelinesPending() const;
static float normalizeOperand(float value)
{
uint32_t bits = std::bit_cast<uint32_t>(value);
const uint32_t exponent = bits & 0x7F800000u;
if (exponent == 0u)
bits &= 0x80000000u;
else if (exponent == 0x7F800000u)
bits = (bits & 0x80000000u) | 0x7F7FFFFFu;
return std::bit_cast<float>(bits);
}
float normalizeResult(float value, uint32_t &laneFlags) const;
uint32_t microAddressMask() const;
int32_t readBranchVi(uint8_t reg) const;
void recordViWriteForBranch(uint8_t reg, int32_t oldValue);
void reportReservedInstruction(bool upper, uint32_t instruction);
};
#endif