mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
Feature vu1 cache (#158)
* feat: explode vu1 in files feat: added way more tests for vu1 * feat: added VU1 cache
This commit is contained in:
@@ -358,7 +358,9 @@ add_library(ps2_runtime STATIC
|
||||
src/lib/ps2_pad.cpp
|
||||
src/lib/ps2_runtime.cpp
|
||||
src/lib/ps2_vif1_interpreter.cpp
|
||||
src/lib/ps2_vu1.cpp
|
||||
src/lib/vu/ps2_vu1_core.cpp
|
||||
src/lib/vu/ps2_vu1_upper.cpp
|
||||
src/lib/vu/ps2_vu1_lower.cpp
|
||||
src/lib/games_database.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -285,6 +285,7 @@ public:
|
||||
uint64_t gifCopyCount() const { return m_gifCopyCount.load(std::memory_order_relaxed); }
|
||||
uint64_t gsWriteCount() const { return m_gsWriteCount.load(std::memory_order_relaxed); }
|
||||
uint64_t vifWriteCount() const { return m_vifWriteCount.load(std::memory_order_relaxed); }
|
||||
uint64_t getVU1CodeGeneration() const { return m_vu1CodeGeneration.load(std::memory_order_relaxed); }
|
||||
|
||||
// Read/write memory
|
||||
uint8_t read8(uint32_t address);
|
||||
@@ -371,6 +372,7 @@ public:
|
||||
std::atomic<uint64_t> m_gifCopyCount{0};
|
||||
std::atomic<uint64_t> m_gsWriteCount{0};
|
||||
std::atomic<uint64_t> m_vifWriteCount{0};
|
||||
std::atomic<uint64_t> m_vu1CodeGeneration{0};
|
||||
// I/O registers
|
||||
std::unordered_map<uint32_t, uint32_t> m_ioRegisters;
|
||||
|
||||
@@ -429,6 +431,7 @@ public:
|
||||
|
||||
bool isAddressInRegion(uint32_t address, const CodeRegion ®ion);
|
||||
void markModified(uint32_t address, uint32_t size);
|
||||
void markVU1CodeModified() { m_vu1CodeGeneration.fetch_add(1, std::memory_order_relaxed); }
|
||||
bool isScratchpad(uint32_t address) const;
|
||||
uint8_t *mapVuMemory(uint32_t physAddr, uint32_t size, uint32_t &offset, uint32_t &limit);
|
||||
const uint8_t *mapVuMemory(uint32_t physAddr, uint32_t size, uint32_t &offset, uint32_t &limit) const;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define PS2_VU1_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
class GS;
|
||||
class PS2Memory;
|
||||
@@ -19,8 +20,8 @@ struct VU1State
|
||||
uint32_t clip;
|
||||
uint32_t status;
|
||||
bool ebit;
|
||||
uint32_t top; // VIF1 TOP visible to VU1 XTOP
|
||||
uint32_t itop; // VIF1 ITOP visible to VU1 XITOP
|
||||
uint32_t top; // VIF1 TOP visible to VU1 XTOP
|
||||
uint32_t itop; // VIF1 ITOP visible to VU1 XITOP
|
||||
|
||||
bool branchPending;
|
||||
uint32_t branchTarget;
|
||||
@@ -49,12 +50,33 @@ public:
|
||||
const VU1State &state() const { return m_state; }
|
||||
|
||||
private:
|
||||
struct DecodedInstructionPair
|
||||
{
|
||||
uint32_t lower = 0;
|
||||
uint32_t upper = 0;
|
||||
bool iBit = false;
|
||||
bool eBit = false;
|
||||
bool lowerBeforeUpper = false;
|
||||
};
|
||||
|
||||
VU1State m_state;
|
||||
std::vector<DecodedInstructionPair> m_decodedCodeCache;
|
||||
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;
|
||||
|
||||
void run(uint8_t *vuCode, uint32_t codeSize,
|
||||
uint8_t *vuData, uint32_t dataSize,
|
||||
GS &gs, PS2Memory *memory, uint32_t maxCycles);
|
||||
|
||||
DecodedInstructionPair decodeInstructionPair(const uint8_t *vuCode, uint32_t pc) const;
|
||||
DecodedInstructionPair getDecodedInstructionPairForPc(const uint8_t *vuCode, uint32_t codeSize,
|
||||
PS2Memory *memory, uint32_t pc);
|
||||
void rebuildDecodedCodeCache(const uint8_t *vuCode, uint32_t codeSize,
|
||||
const PS2Memory *memory, uint64_t generation);
|
||||
|
||||
void execUpper(uint32_t instr);
|
||||
void execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSize, GS &gs, PS2Memory *memory, uint32_t upperInstr);
|
||||
|
||||
|
||||
@@ -351,6 +351,7 @@ bool PS2Memory::initialize(size_t ramSize)
|
||||
m_vu1Data = new uint8_t[PS2_VU1_DATA_SIZE];
|
||||
std::memset(m_vu1Code, 0, PS2_VU1_CODE_SIZE);
|
||||
std::memset(m_vu1Data, 0, PS2_VU1_DATA_SIZE);
|
||||
markVU1CodeModified();
|
||||
|
||||
// Initialize VIF registers
|
||||
memset(&vif0_regs, 0, sizeof(vif0_regs));
|
||||
@@ -764,6 +765,8 @@ void PS2Memory::write8(uint32_t address, uint8_t value)
|
||||
{
|
||||
(void)vuLimit;
|
||||
vuMem[vuOffset] = value;
|
||||
if (vuMem == m_vu1Code)
|
||||
markVU1CodeModified();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -803,6 +806,8 @@ void PS2Memory::write16(uint32_t address, uint16_t value)
|
||||
if (uint8_t *vuMem = mapVuMemory(physAddr, sizeof(uint16_t), vuOffset, vuLimit))
|
||||
{
|
||||
storeScalar<uint16_t>(vuMem, vuOffset, vuLimit, value, "write16 vu", address);
|
||||
if (vuMem == m_vu1Code)
|
||||
markVU1CodeModified();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -863,6 +868,8 @@ void PS2Memory::write32(uint32_t address, uint32_t value)
|
||||
if (uint8_t *vuMem = mapVuMemory(physAddr, sizeof(uint32_t), vuOffset, vuLimit))
|
||||
{
|
||||
storeScalar<uint32_t>(vuMem, vuOffset, vuLimit, value, "write32 vu", address);
|
||||
if (vuMem == m_vu1Code)
|
||||
markVU1CodeModified();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -914,6 +921,8 @@ void PS2Memory::write64(uint32_t address, uint64_t value)
|
||||
if (uint8_t *vuMem = mapVuMemory(physAddr, sizeof(uint64_t), vuOffset, vuLimit))
|
||||
{
|
||||
storeScalar<uint64_t>(vuMem, vuOffset, vuLimit, value, "write64 vu", address);
|
||||
if (vuMem == m_vu1Code)
|
||||
markVU1CodeModified();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -953,6 +962,8 @@ void PS2Memory::write128(uint32_t address, __m128i value)
|
||||
{
|
||||
inRange(vuOffset, sizeof(__m128i), vuLimit, "write128 vu", address);
|
||||
_mm_storeu_si128(reinterpret_cast<__m128i *>(vuMem + vuOffset), value);
|
||||
if (vuMem == m_vu1Code)
|
||||
markVU1CodeModified();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1208,9 +1219,9 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
|
||||
auto appendCompactVif1TagData = [&](uint32_t localTagAddr, uint32_t qwCount)
|
||||
{
|
||||
uint32_t tagPhys = 0u;
|
||||
const bool tagScratch = isScratchpad(localTagAddr);
|
||||
const bool tagScratch = isScratchpad(localTagAddr);
|
||||
tagPhys = translateAddress(localTagAddr);
|
||||
|
||||
|
||||
const uint8_t *localBase = tagScratch ? m_scratchpad : m_rdram;
|
||||
const uint32_t localMax = tagScratch ? PS2_SCRATCHPAD_SIZE : PS2_RAM_SIZE;
|
||||
if (tagPhys + 16u > localMax)
|
||||
@@ -1718,11 +1729,11 @@ void PS2Memory::processGIFPacket(uint32_t srcPhysAddr, uint32_t qwCount)
|
||||
chunk = PS2_RAM_SIZE - srcPhysAddr;
|
||||
if (chunk == 0)
|
||||
break;
|
||||
|
||||
|
||||
m_seenGifCopy = true;
|
||||
m_gifCopyCount.fetch_add(1, std::memory_order_relaxed);
|
||||
submitGifPacket(GifPathId::Path3, m_rdram + srcPhysAddr, chunk);
|
||||
|
||||
|
||||
bytesLeft -= chunk;
|
||||
srcPhysAddr += chunk;
|
||||
}
|
||||
|
||||
@@ -445,7 +445,10 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes)
|
||||
if (destAddr + copyBytes > PS2_VU1_CODE_SIZE)
|
||||
copyBytes = PS2_VU1_CODE_SIZE - destAddr;
|
||||
if (pos + copyBytes <= sizeBytes)
|
||||
{
|
||||
std::memcpy(m_vu1Code + destAddr, data + pos, copyBytes);
|
||||
markVU1CodeModified();
|
||||
}
|
||||
}
|
||||
pos += mpgBytes;
|
||||
if (pos > sizeBytes)
|
||||
|
||||
+1
-1464
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,201 @@
|
||||
#include "runtime/ps2_vu1.h"
|
||||
#include "runtime/ps2_memory.h"
|
||||
#include "ps2_vu1_detail.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
VU1Interpreter::VU1Interpreter()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void VU1Interpreter::reset()
|
||||
{
|
||||
std::memset(&m_state, 0, sizeof(m_state));
|
||||
m_state.vf[0][3] = 1.0f; // VF0.w = 1.0
|
||||
m_state.q = 1.0f;
|
||||
}
|
||||
|
||||
float VU1Interpreter::broadcast(const float *vf, uint8_t bc)
|
||||
{
|
||||
return vf[bc & 3];
|
||||
}
|
||||
|
||||
void VU1Interpreter::applyDest(float *dst, const float *result, uint8_t dest)
|
||||
{
|
||||
if (dest & 0x8)
|
||||
dst[0] = result[0]; // x
|
||||
if (dest & 0x4)
|
||||
dst[1] = result[1]; // y
|
||||
if (dest & 0x2)
|
||||
dst[2] = result[2]; // z
|
||||
if (dest & 0x1)
|
||||
dst[3] = result[3]; // w
|
||||
}
|
||||
|
||||
void VU1Interpreter::applyDestAcc(const float *result, uint8_t dest)
|
||||
{
|
||||
applyDest(m_state.acc, result, dest);
|
||||
}
|
||||
|
||||
VU1Interpreter::DecodedInstructionPair VU1Interpreter::decodeInstructionPair(const uint8_t *vuCode, uint32_t pc) const
|
||||
{
|
||||
DecodedInstructionPair decoded;
|
||||
std::memcpy(&decoded.lower, vuCode + pc, sizeof(decoded.lower));
|
||||
std::memcpy(&decoded.upper, vuCode + pc + sizeof(decoded.lower), sizeof(decoded.upper));
|
||||
|
||||
decoded.iBit = ((decoded.upper >> 31) & 1u) != 0u;
|
||||
decoded.eBit = ((decoded.upper >> 30) & 1u) != 0u;
|
||||
decoded.lowerBeforeUpper = !decoded.iBit && vuLowerShouldRunBeforeUpper(decoded.upper, decoded.lower);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
void VU1Interpreter::rebuildDecodedCodeCache(const uint8_t *vuCode, uint32_t codeSize,
|
||||
const PS2Memory *memory, uint64_t generation)
|
||||
{
|
||||
const uint32_t pairCount = codeSize / 8u;
|
||||
m_decodedCodeCache.resize(pairCount);
|
||||
for (uint32_t i = 0; i < pairCount; ++i)
|
||||
{
|
||||
m_decodedCodeCache[i] = decodeInstructionPair(vuCode, i * 8u);
|
||||
}
|
||||
|
||||
m_cachedVuCode = vuCode;
|
||||
m_cachedMemory = memory;
|
||||
m_cachedCodeSize = codeSize;
|
||||
m_cachedCodeGeneration = generation;
|
||||
m_decodedCodeCacheValid = true;
|
||||
}
|
||||
|
||||
VU1Interpreter::DecodedInstructionPair VU1Interpreter::getDecodedInstructionPairForPc(const uint8_t *vuCode,
|
||||
uint32_t codeSize,
|
||||
PS2Memory *memory,
|
||||
uint32_t pc)
|
||||
{
|
||||
// Only 8-byte aligned VU instruction pairs can use the decode cache.
|
||||
if ((pc & 7u) != 0u)
|
||||
{
|
||||
return decodeInstructionPair(vuCode, pc);
|
||||
}
|
||||
|
||||
const bool trackedVu1Code = vuCode == memory->getVU1Code();
|
||||
if (!trackedVu1Code)
|
||||
{
|
||||
return decodeInstructionPair(vuCode, pc);
|
||||
}
|
||||
|
||||
const uint64_t generation = memory->getVU1CodeGeneration();
|
||||
const bool rebuild =
|
||||
!m_decodedCodeCacheValid ||
|
||||
m_cachedVuCode != vuCode ||
|
||||
m_cachedMemory != memory ||
|
||||
m_cachedCodeSize != codeSize ||
|
||||
m_cachedCodeGeneration != generation;
|
||||
|
||||
if (rebuild)
|
||||
{
|
||||
rebuildDecodedCodeCache(vuCode, codeSize, memory, generation);
|
||||
}
|
||||
|
||||
return m_decodedCodeCache[pc / 8u];
|
||||
}
|
||||
|
||||
void VU1Interpreter::execute(uint8_t *vuCode, uint32_t codeSize,
|
||||
uint8_t *vuData, uint32_t dataSize,
|
||||
GS &gs, PS2Memory *memory,
|
||||
uint32_t startPC, uint32_t top, uint32_t itop,
|
||||
uint32_t maxCycles)
|
||||
{
|
||||
m_state.pc = startPC & 0x3FFFu;
|
||||
m_state.ebit = false;
|
||||
m_state.top = top;
|
||||
m_state.itop = itop;
|
||||
m_state.branchPending = false;
|
||||
m_state.branchTarget = 0;
|
||||
m_state.branchDelay = 0;
|
||||
m_state.vf[0][0] = 0.0f;
|
||||
m_state.vf[0][1] = 0.0f;
|
||||
m_state.vf[0][2] = 0.0f;
|
||||
m_state.vf[0][3] = 1.0f;
|
||||
run(vuCode, codeSize, vuData, dataSize, gs, memory, maxCycles);
|
||||
}
|
||||
|
||||
void VU1Interpreter::resume(uint8_t *vuCode, uint32_t codeSize,
|
||||
uint8_t *vuData, uint32_t dataSize,
|
||||
GS &gs, PS2Memory *memory,
|
||||
uint32_t top, uint32_t itop, uint32_t maxCycles)
|
||||
{
|
||||
m_state.ebit = false;
|
||||
m_state.top = top;
|
||||
m_state.itop = itop;
|
||||
run(vuCode, codeSize, vuData, dataSize, gs, memory, maxCycles);
|
||||
}
|
||||
|
||||
void VU1Interpreter::run(uint8_t *vuCode, uint32_t codeSize,
|
||||
uint8_t *vuData, uint32_t dataSize,
|
||||
GS &gs, PS2Memory *memory, uint32_t maxCycles)
|
||||
{
|
||||
for (uint32_t cycle = 0; cycle < maxCycles; ++cycle)
|
||||
{
|
||||
if (m_state.pc + 8 > codeSize)
|
||||
break;
|
||||
|
||||
const DecodedInstructionPair decoded = getDecodedInstructionPairForPc(vuCode, codeSize, memory, m_state.pc);
|
||||
|
||||
// LOI is controlled by the upper I-bit. The lower word is the float immediate.
|
||||
// DobieStation executes the upper instruction first, then commits lower into I.
|
||||
if (decoded.iBit)
|
||||
{
|
||||
// LOI is special: the upper instruction sees the old I value, then LOI loads I.
|
||||
execUpper(decoded.upper);
|
||||
std::memcpy(&m_state.i, &decoded.lower, sizeof(decoded.lower));
|
||||
}
|
||||
else if (decoded.lowerBeforeUpper)
|
||||
{
|
||||
// VU upper/lower execute as a pair. If the upper op writes a VF register
|
||||
// that the lower op reads or also writes, Dobie runs the lower side first
|
||||
// so it observes the old VF value and the upper write has priority.
|
||||
execLower(decoded.lower, vuData, dataSize, gs, memory, decoded.upper);
|
||||
execUpper(decoded.upper);
|
||||
}
|
||||
else
|
||||
{
|
||||
execUpper(decoded.upper);
|
||||
execLower(decoded.lower, vuData, dataSize, gs, memory, decoded.upper);
|
||||
}
|
||||
|
||||
// Enforce VF0 invariant
|
||||
m_state.vf[0][0] = 0.0f;
|
||||
m_state.vf[0][1] = 0.0f;
|
||||
m_state.vf[0][2] = 0.0f;
|
||||
m_state.vf[0][3] = 1.0f;
|
||||
// Enforce VI0 invariant
|
||||
m_state.vi[0] = 0;
|
||||
|
||||
uint32_t nextPC = m_state.pc + 8;
|
||||
if (nextPC >= codeSize)
|
||||
nextPC = 0;
|
||||
m_state.pc = nextPC;
|
||||
|
||||
// VU branch/jump has a delay slot. Branch handlers set a pending target;
|
||||
// we execute one sequential instruction before committing the branch.
|
||||
if (m_state.branchPending)
|
||||
{
|
||||
if (m_state.branchDelay == 0)
|
||||
{
|
||||
m_state.pc = m_state.branchTarget & 0x3FFFu;
|
||||
m_state.branchPending = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
--m_state.branchDelay;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_state.ebit)
|
||||
break;
|
||||
|
||||
if (decoded.eBit)
|
||||
m_state.ebit = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
#ifndef PS2_VU1_DETAIL_H
|
||||
#define PS2_VU1_DETAIL_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// Instruction field extraction helpers
|
||||
static inline uint8_t DEST(uint32_t i) { return (uint8_t)((i >> 21) & 0xF); }
|
||||
static inline uint8_t FT(uint32_t i) { return (uint8_t)((i >> 16) & 0x1F); }
|
||||
static inline uint8_t FS(uint32_t i) { return (uint8_t)((i >> 11) & 0x1F); }
|
||||
static inline uint8_t FD(uint32_t i) { return (uint8_t)((i >> 6) & 0x1F); }
|
||||
static inline uint8_t BC(uint32_t i) { return (uint8_t)(i & 0x3); }
|
||||
|
||||
// Lower instruction field helpers
|
||||
static inline uint8_t LIT(uint32_t i) { return (uint8_t)((i >> 16) & 0x1F); }
|
||||
static inline uint8_t LIS(uint32_t i) { return (uint8_t)((i >> 11) & 0x1F); }
|
||||
static inline uint8_t LID(uint32_t i) { return (uint8_t)((i >> 6) & 0x1F); }
|
||||
static inline uint8_t VIT(uint32_t i) { return (uint8_t)((i >> 16) & 0xF); }
|
||||
static inline uint8_t VIS(uint32_t i) { return (uint8_t)((i >> 11) & 0xF); }
|
||||
static inline uint8_t VID(uint32_t i) { return (uint8_t)((i >> 6) & 0xF); }
|
||||
static inline int16_t IMM11(uint32_t i) { return (int16_t)(int32_t)((int32_t)(i << 21) >> 21); }
|
||||
static inline int16_t IMM15(uint32_t i)
|
||||
{
|
||||
uint32_t lo11 = i & 0x7FF;
|
||||
uint32_t hi4 = (i >> 21) & 0xF;
|
||||
uint32_t raw = (hi4 << 11) | lo11;
|
||||
return (int16_t)(int32_t)((int32_t)(raw << 17) >> 17);
|
||||
}
|
||||
|
||||
|
||||
static inline uint8_t vuUpperVfWriteReg(uint32_t upper)
|
||||
{
|
||||
const uint8_t op = upper & 0x3Fu;
|
||||
const uint8_t dest = DEST(upper);
|
||||
const uint8_t ft = FT(upper);
|
||||
const uint8_t fd = FD(upper);
|
||||
|
||||
if (dest == 0u)
|
||||
return 0u;
|
||||
|
||||
if (op <= 0x2Fu)
|
||||
return fd;
|
||||
|
||||
if (op >= 0x3Cu)
|
||||
{
|
||||
const uint8_t specialOp = static_cast<uint8_t>((upper & 0x3u) | ((upper >> 4) & 0x7Cu));
|
||||
switch (specialOp)
|
||||
{
|
||||
// Upper special ops that write a VF register use FT as destination.
|
||||
case 0x10: // ITOF0
|
||||
case 0x11: // ITOF4
|
||||
case 0x12: // ITOF12
|
||||
case 0x13: // ITOF15
|
||||
case 0x14: // FTOI0
|
||||
case 0x15: // FTOI4
|
||||
case 0x16: // FTOI12
|
||||
case 0x17: // FTOI15
|
||||
case 0x1D: // ABS
|
||||
return ft;
|
||||
default:
|
||||
return 0u; // ACC/NOP/CLIP/etc.
|
||||
}
|
||||
}
|
||||
|
||||
return 0u;
|
||||
}
|
||||
|
||||
static inline void vuSetRegBit(uint32_t &mask, uint8_t reg)
|
||||
{
|
||||
if (reg != 0u && reg < 32u)
|
||||
mask |= (1u << reg);
|
||||
}
|
||||
|
||||
static inline void vuLowerVfReadWriteMasks(uint32_t lower, uint32_t &readMask, uint32_t &writeMask)
|
||||
{
|
||||
readMask = 0u;
|
||||
writeMask = 0u;
|
||||
|
||||
if (lower == 0u || lower == 0x8000033Cu)
|
||||
return;
|
||||
|
||||
const uint8_t opHi = static_cast<uint8_t>((lower >> 25) & 0x7Fu);
|
||||
const uint8_t it = LIT(lower);
|
||||
const uint8_t is = LIS(lower);
|
||||
|
||||
if ((lower & 0x80000000u) != 0u)
|
||||
{
|
||||
const uint8_t funct = lower & 0x3Fu;
|
||||
if (funct >= 0x3Cu && funct <= 0x3Fu)
|
||||
{
|
||||
const uint8_t specialOp = static_cast<uint8_t>((lower & 0x3u) | ((lower >> 4) & 0x7Cu));
|
||||
switch (specialOp)
|
||||
{
|
||||
case 0x30: // MOVE
|
||||
case 0x31: // MR32
|
||||
vuSetRegBit(readMask, is);
|
||||
vuSetRegBit(writeMask, it);
|
||||
return;
|
||||
case 0x34: // LQI
|
||||
case 0x36: // LQD
|
||||
vuSetRegBit(writeMask, it);
|
||||
return;
|
||||
case 0x35: // SQI
|
||||
case 0x37: // SQD
|
||||
vuSetRegBit(readMask, is);
|
||||
return;
|
||||
case 0x38: // DIV
|
||||
case 0x3A: // RSQRT
|
||||
vuSetRegBit(readMask, is);
|
||||
vuSetRegBit(readMask, it);
|
||||
return;
|
||||
case 0x39: // SQRT
|
||||
vuSetRegBit(readMask, it);
|
||||
return;
|
||||
case 0x3C: // MTIR
|
||||
case 0x3E: // ILWR source base is integer, but field source is VF for MTIR only.
|
||||
if (specialOp == 0x3C)
|
||||
vuSetRegBit(readMask, is);
|
||||
return;
|
||||
case 0x3D: // MFIR
|
||||
case 0x64: // MFP
|
||||
vuSetRegBit(writeMask, it);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (opHi)
|
||||
{
|
||||
case 0x00: // LQ
|
||||
vuSetRegBit(writeMask, it);
|
||||
return;
|
||||
case 0x01: // SQ
|
||||
vuSetRegBit(readMask, is);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool vuLowerShouldRunBeforeUpper(uint32_t upper, uint32_t lower)
|
||||
{
|
||||
const uint8_t upperWrite = vuUpperVfWriteReg(upper);
|
||||
if (upperWrite == 0u)
|
||||
return false;
|
||||
|
||||
uint32_t lowerReads = 0u;
|
||||
uint32_t lowerWrites = 0u;
|
||||
vuLowerVfReadWriteMasks(lower, lowerReads, lowerWrites);
|
||||
|
||||
const uint32_t upperBit = (1u << upperWrite);
|
||||
return ((lowerReads | lowerWrites) & upperBit) != 0u;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,718 @@
|
||||
#include "runtime/ps2_vu1.h"
|
||||
#include "runtime/ps2_gif_arbiter.h"
|
||||
#include "runtime/ps2_gs_gpu.h"
|
||||
#include "runtime/ps2_memory.h"
|
||||
#include "ps2_vu1_detail.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
// ============================================================================
|
||||
// Lower instructions
|
||||
// ============================================================================
|
||||
void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSize, GS &gs, PS2Memory *memory, uint32_t upperInstr)
|
||||
{
|
||||
(void)upperInstr;
|
||||
if (instr == 0x00000000 || instr == 0x8000033C) // NOP
|
||||
return;
|
||||
|
||||
uint8_t opHi = (instr >> 25) & 0x7F;
|
||||
|
||||
// The lower instruction encoding uses bits 31:25 for the primary opcode
|
||||
switch (opHi)
|
||||
{
|
||||
case 0x00: // LQ (Load Quadword from VU data memory)
|
||||
{
|
||||
uint8_t it = FT(instr); // VF destination
|
||||
uint8_t is = VIS(instr); // VI base
|
||||
uint8_t dest = (instr >> 21) & 0xF;
|
||||
int16_t imm = IMM11(instr);
|
||||
uint32_t addr = ((uint32_t)(int32_t)(m_state.vi[is] + imm)) * 16u;
|
||||
addr &= (dataSize - 1);
|
||||
if (addr + 16 <= dataSize)
|
||||
{
|
||||
float tmp[4];
|
||||
std::memcpy(tmp, vuData + addr, 16);
|
||||
applyDest(m_state.vf[it], tmp, dest);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x01: // SQ (Store Quadword to VU data memory)
|
||||
{
|
||||
uint8_t is = FS(instr); // VF source
|
||||
uint8_t it = VIT(instr); // VI base
|
||||
uint8_t dest = (instr >> 21) & 0xF;
|
||||
int16_t imm = IMM11(instr);
|
||||
uint32_t addr = ((uint32_t)(int32_t)(m_state.vi[it] + imm)) * 16u;
|
||||
addr &= (dataSize - 1);
|
||||
if (addr + 16 <= dataSize)
|
||||
{
|
||||
float tmp[4];
|
||||
std::memcpy(tmp, vuData + addr, 16);
|
||||
if (dest & 0x8)
|
||||
tmp[0] = m_state.vf[is][0];
|
||||
if (dest & 0x4)
|
||||
tmp[1] = m_state.vf[is][1];
|
||||
if (dest & 0x2)
|
||||
tmp[2] = m_state.vf[is][2];
|
||||
if (dest & 0x1)
|
||||
tmp[3] = m_state.vf[is][3];
|
||||
std::memcpy(vuData + addr, tmp, 16);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x04: // ILW (Integer Load Word from VU data memory)
|
||||
{
|
||||
uint8_t it = VIT(instr); // VI destination
|
||||
uint8_t is = VIS(instr); // VI base
|
||||
uint8_t dest = (instr >> 21) & 0xF;
|
||||
int16_t imm = IMM11(instr);
|
||||
uint32_t addr = ((uint32_t)(int32_t)(m_state.vi[is] + imm)) * 16u;
|
||||
addr &= (dataSize - 1);
|
||||
if (addr + 16 <= dataSize)
|
||||
{
|
||||
int comp = 0;
|
||||
if (dest & 0x8)
|
||||
comp = 0;
|
||||
else if (dest & 0x4)
|
||||
comp = 1;
|
||||
else if (dest & 0x2)
|
||||
comp = 2;
|
||||
else
|
||||
comp = 3;
|
||||
uint32_t v;
|
||||
std::memcpy(&v, vuData + addr + comp * 4, 4);
|
||||
if (it != 0)
|
||||
m_state.vi[it] = (int32_t)(int16_t)(v & 0xFFFF);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x05: // ISW (Integer Store Word to VU data memory)
|
||||
{
|
||||
uint8_t it = VIT(instr); // VI source
|
||||
uint8_t is = VIS(instr); // VI base
|
||||
uint8_t dest = (instr >> 21) & 0xF;
|
||||
int16_t imm = IMM11(instr);
|
||||
uint32_t addr = ((uint32_t)(int32_t)(m_state.vi[is] + imm)) * 16u;
|
||||
addr &= (dataSize - 1);
|
||||
if (addr + 16 <= dataSize)
|
||||
{
|
||||
uint32_t val = (uint32_t)(uint16_t)(m_state.vi[it] & 0xFFFF);
|
||||
if (dest & 0x8)
|
||||
std::memcpy(vuData + addr + 0, &val, 4);
|
||||
if (dest & 0x4)
|
||||
std::memcpy(vuData + addr + 4, &val, 4);
|
||||
if (dest & 0x2)
|
||||
std::memcpy(vuData + addr + 8, &val, 4);
|
||||
if (dest & 0x1)
|
||||
std::memcpy(vuData + addr + 12, &val, 4);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x08: // IADDIU
|
||||
{
|
||||
uint8_t it = VIT(instr);
|
||||
uint8_t is = VIS(instr);
|
||||
int16_t imm = (int16_t)(instr & 0x7FF) | ((instr >> 10) & 0x7800);
|
||||
if (it != 0)
|
||||
m_state.vi[it] = (int16_t)(m_state.vi[is] + imm);
|
||||
return;
|
||||
}
|
||||
case 0x09: // ISUBIU
|
||||
{
|
||||
uint8_t it = VIT(instr);
|
||||
uint8_t is = VIS(instr);
|
||||
int16_t imm = (int16_t)(instr & 0x7FF) | ((instr >> 10) & 0x7800);
|
||||
if (it != 0)
|
||||
m_state.vi[it] = (int16_t)(m_state.vi[is] - imm);
|
||||
return;
|
||||
}
|
||||
case 0x10: // FCEQ
|
||||
{
|
||||
uint32_t imm24 = instr & 0xFFFFFF;
|
||||
if (1 != 0)
|
||||
m_state.vi[1] = ((m_state.clip & 0xFFFFFF) == imm24) ? 1 : 0;
|
||||
return;
|
||||
}
|
||||
case 0x11: // FCSET
|
||||
{
|
||||
m_state.clip = instr & 0xFFFFFF;
|
||||
return;
|
||||
}
|
||||
case 0x12: // FCAND
|
||||
{
|
||||
uint32_t imm24 = instr & 0xFFFFFF;
|
||||
if (1 != 0)
|
||||
m_state.vi[1] = ((m_state.clip & imm24) != 0) ? 1 : 0;
|
||||
return;
|
||||
}
|
||||
case 0x13: // FCOR
|
||||
{
|
||||
uint32_t imm24 = instr & 0xFFFFFF;
|
||||
if (1 != 0)
|
||||
m_state.vi[1] = ((m_state.clip | imm24) == 0xFFFFFF) ? 1 : 0;
|
||||
return;
|
||||
}
|
||||
case 0x14: // FSEQ
|
||||
{
|
||||
uint16_t imm12 = instr & 0xFFF;
|
||||
if (1 != 0)
|
||||
m_state.vi[1] = ((m_state.status & 0xFFF) == imm12) ? 1 : 0;
|
||||
return;
|
||||
}
|
||||
case 0x15: // FSSET
|
||||
{
|
||||
m_state.status = (instr >> 6) & 0xFC0;
|
||||
return;
|
||||
}
|
||||
case 0x16: // FSAND
|
||||
{
|
||||
uint16_t imm12 = instr & 0xFFF;
|
||||
if (1 != 0)
|
||||
m_state.vi[1] = (int32_t)(m_state.status & imm12);
|
||||
return;
|
||||
}
|
||||
case 0x17: // FSOR
|
||||
{
|
||||
uint16_t imm12 = instr & 0xFFF;
|
||||
if (1 != 0)
|
||||
m_state.vi[1] = ((m_state.status | imm12) == 0xFFF) ? 1 : 0;
|
||||
return;
|
||||
}
|
||||
case 0x18: // FMAND
|
||||
{
|
||||
uint8_t it = VIT(instr);
|
||||
uint8_t is = VIS(instr);
|
||||
if (it != 0)
|
||||
m_state.vi[it] = (int32_t)(m_state.mac & (uint32_t)(uint16_t)m_state.vi[is]);
|
||||
return;
|
||||
}
|
||||
case 0x1A: // FMEQ
|
||||
{
|
||||
uint8_t it = VIT(instr);
|
||||
uint8_t is = VIS(instr);
|
||||
if (it != 0)
|
||||
m_state.vi[it] = ((m_state.mac & 0xFFFF) == (uint32_t)(uint16_t)m_state.vi[is]) ? 1 : 0;
|
||||
return;
|
||||
}
|
||||
case 0x1C: // FMOR
|
||||
{
|
||||
uint8_t it = VIT(instr);
|
||||
uint8_t is = VIS(instr);
|
||||
if (it != 0)
|
||||
m_state.vi[it] = (int32_t)(m_state.mac | (uint32_t)(uint16_t)m_state.vi[is]);
|
||||
return;
|
||||
}
|
||||
case 0x20: // B (unconditional branch)
|
||||
{
|
||||
int16_t imm = IMM11(instr);
|
||||
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
|
||||
m_state.branchPending = true;
|
||||
m_state.branchTarget = target;
|
||||
m_state.branchDelay = 1;
|
||||
return;
|
||||
}
|
||||
case 0x21: // BAL (Branch and link)
|
||||
{
|
||||
uint8_t it = VIT(instr);
|
||||
int16_t imm = IMM11(instr);
|
||||
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
|
||||
if (it != 0)
|
||||
m_state.vi[it] = (int32_t)((m_state.pc + 16) / 8);
|
||||
m_state.branchPending = true;
|
||||
m_state.branchTarget = target;
|
||||
m_state.branchDelay = 1;
|
||||
return;
|
||||
}
|
||||
case 0x24: // JR
|
||||
{
|
||||
uint8_t is = VIS(instr);
|
||||
uint32_t target = ((uint32_t)(uint16_t)m_state.vi[is] * 8u) & 0x3FFF;
|
||||
m_state.branchPending = true;
|
||||
m_state.branchTarget = target;
|
||||
m_state.branchDelay = 1;
|
||||
return;
|
||||
}
|
||||
case 0x25: // JALR
|
||||
{
|
||||
uint8_t it = VIT(instr);
|
||||
uint8_t is = VIS(instr);
|
||||
uint32_t target = ((uint32_t)(uint16_t)m_state.vi[is] * 8u) & 0x3FFF;
|
||||
if (it != 0)
|
||||
m_state.vi[it] = (int32_t)((m_state.pc + 16) / 8);
|
||||
m_state.branchPending = true;
|
||||
m_state.branchTarget = target;
|
||||
m_state.branchDelay = 1;
|
||||
return;
|
||||
}
|
||||
case 0x28: // IBEQ
|
||||
{
|
||||
uint8_t it = VIT(instr);
|
||||
uint8_t is = VIS(instr);
|
||||
int16_t imm = IMM11(instr);
|
||||
if ((int16_t)m_state.vi[is] == (int16_t)m_state.vi[it])
|
||||
{
|
||||
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
|
||||
m_state.branchPending = true;
|
||||
m_state.branchTarget = target;
|
||||
m_state.branchDelay = 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x29: // IBNE
|
||||
{
|
||||
uint8_t it = VIT(instr);
|
||||
uint8_t is = VIS(instr);
|
||||
int16_t imm = IMM11(instr);
|
||||
if ((int16_t)m_state.vi[is] != (int16_t)m_state.vi[it])
|
||||
{
|
||||
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
|
||||
m_state.branchPending = true;
|
||||
m_state.branchTarget = target;
|
||||
m_state.branchDelay = 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x2C: // IBLTZ
|
||||
{
|
||||
uint8_t is = VIS(instr);
|
||||
int16_t imm = IMM11(instr);
|
||||
if ((int16_t)m_state.vi[is] < 0)
|
||||
{
|
||||
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
|
||||
m_state.branchPending = true;
|
||||
m_state.branchTarget = target;
|
||||
m_state.branchDelay = 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x2D: // IBGTZ
|
||||
{
|
||||
uint8_t is = VIS(instr);
|
||||
int16_t imm = IMM11(instr);
|
||||
if ((int16_t)m_state.vi[is] > 0)
|
||||
{
|
||||
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
|
||||
m_state.branchPending = true;
|
||||
m_state.branchTarget = target;
|
||||
m_state.branchDelay = 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x2E: // IBLEZ
|
||||
{
|
||||
uint8_t is = VIS(instr);
|
||||
int16_t imm = IMM11(instr);
|
||||
if ((int16_t)m_state.vi[is] <= 0)
|
||||
{
|
||||
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
|
||||
m_state.branchPending = true;
|
||||
m_state.branchTarget = target;
|
||||
m_state.branchDelay = 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x2F: // IBGEZ
|
||||
{
|
||||
uint8_t is = VIS(instr);
|
||||
int16_t imm = IMM11(instr);
|
||||
if ((int16_t)m_state.vi[is] >= 0)
|
||||
{
|
||||
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
|
||||
m_state.branchPending = true;
|
||||
m_state.branchTarget = target;
|
||||
m_state.branchDelay = 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x40: // Lower1 / lower special. Bit31 set; low 6 bits select integer or special op.
|
||||
{
|
||||
const uint8_t funct = instr & 0x3Fu;
|
||||
const uint8_t vfT = FT(instr);
|
||||
const uint8_t vfS = FS(instr);
|
||||
const uint8_t viT = VIT(instr);
|
||||
const uint8_t viS = VIS(instr);
|
||||
const uint8_t viD = VID(instr);
|
||||
const uint8_t dest = (instr >> 21) & 0xF;
|
||||
|
||||
auto doXgkick = [&]()
|
||||
{
|
||||
if (!vuData || dataSize < 16u)
|
||||
return;
|
||||
|
||||
auto wrapOffset = [&](uint32_t off) -> uint32_t
|
||||
{
|
||||
return off % dataSize;
|
||||
};
|
||||
|
||||
auto read64Wrap = [&](uint32_t off) -> uint64_t
|
||||
{
|
||||
uint8_t bytes[8];
|
||||
for (uint32_t i = 0; i < 8u; ++i)
|
||||
{
|
||||
bytes[i] = vuData[wrapOffset(off + i)];
|
||||
}
|
||||
uint64_t value = 0;
|
||||
std::memcpy(&value, bytes, sizeof(value));
|
||||
return value;
|
||||
};
|
||||
|
||||
uint32_t addr = ((uint32_t)(uint16_t)m_state.vi[viS]) * 16u;
|
||||
addr = wrapOffset(addr);
|
||||
uint32_t pktOff = addr;
|
||||
uint32_t totalBytes = 0u;
|
||||
bool done = false;
|
||||
|
||||
for (int safety = 0; safety < 256 && !done; ++safety)
|
||||
{
|
||||
uint64_t tagLo = read64Wrap(pktOff);
|
||||
uint32_t nloop = (uint32_t)(tagLo & 0x7FFFu);
|
||||
uint8_t flg = (uint8_t)((tagLo >> 58) & 0x3u);
|
||||
uint32_t nreg = (uint32_t)((tagLo >> 60) & 0xFu);
|
||||
if (nreg == 0u)
|
||||
nreg = 16u;
|
||||
bool eop = ((tagLo >> 15) & 0x1ull) != 0ull;
|
||||
|
||||
uint32_t pktSize = 16u;
|
||||
if (flg == 0u)
|
||||
{
|
||||
pktSize += nloop * nreg * 16u;
|
||||
}
|
||||
else if (flg == 1u)
|
||||
{
|
||||
uint32_t regs = nloop * nreg;
|
||||
pktSize += regs * 8u;
|
||||
if ((regs & 1u) != 0u)
|
||||
pktSize += 8u;
|
||||
}
|
||||
else if (flg == 2u)
|
||||
{
|
||||
pktSize += nloop * 16u;
|
||||
}
|
||||
|
||||
if (pktSize == 0u)
|
||||
break;
|
||||
|
||||
totalBytes += pktSize;
|
||||
pktOff = wrapOffset(pktOff + pktSize);
|
||||
if (eop)
|
||||
done = true;
|
||||
}
|
||||
|
||||
if (totalBytes == 0u)
|
||||
return;
|
||||
|
||||
if (addr + totalBytes <= dataSize)
|
||||
{
|
||||
if (memory)
|
||||
memory->submitGifPacket(GifPathId::Path1, vuData + addr, totalBytes);
|
||||
else
|
||||
gs.processGIFPacket(vuData + addr, totalBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<uint8_t> wrappedPacket(totalBytes);
|
||||
for (uint32_t i = 0; i < totalBytes; ++i)
|
||||
{
|
||||
wrappedPacket[i] = vuData[wrapOffset(addr + i)];
|
||||
}
|
||||
|
||||
if (memory)
|
||||
memory->submitGifPacket(GifPathId::Path1, wrappedPacket.data(), totalBytes);
|
||||
else
|
||||
gs.processGIFPacket(wrappedPacket.data(), totalBytes);
|
||||
}
|
||||
};
|
||||
|
||||
switch (funct)
|
||||
{
|
||||
case 0x30: // IADD
|
||||
if (viD != 0)
|
||||
m_state.vi[viD] = (int16_t)(m_state.vi[viS] + m_state.vi[viT]);
|
||||
return;
|
||||
case 0x31: // ISUB
|
||||
if (viD != 0)
|
||||
m_state.vi[viD] = (int16_t)(m_state.vi[viS] - m_state.vi[viT]);
|
||||
return;
|
||||
case 0x32: // IADDI
|
||||
{
|
||||
int16_t imm5 = (int16_t)((int32_t)((instr >> 6) & 0x1F) << 27 >> 27);
|
||||
if (viT != 0)
|
||||
m_state.vi[viT] = (int16_t)(m_state.vi[viS] + imm5);
|
||||
return;
|
||||
}
|
||||
case 0x34: // IAND
|
||||
if (viD != 0)
|
||||
m_state.vi[viD] = m_state.vi[viS] & m_state.vi[viT];
|
||||
return;
|
||||
case 0x35: // IOR
|
||||
if (viD != 0)
|
||||
m_state.vi[viD] = m_state.vi[viS] | m_state.vi[viT];
|
||||
return;
|
||||
|
||||
case 0x3C:
|
||||
case 0x3D:
|
||||
case 0x3E:
|
||||
case 0x3F: // Lower1 special. Dobie decodes this as (instr & 3) | ((instr >> 4) & 0x7C).
|
||||
{
|
||||
const uint8_t funct2 = (uint8_t)((instr & 0x3u) | ((instr >> 4) & 0x7Cu));
|
||||
switch (funct2)
|
||||
{
|
||||
case 0x30: // MOVE
|
||||
{
|
||||
float tmp[4];
|
||||
std::memcpy(tmp, m_state.vf[vfS], 16);
|
||||
applyDest(m_state.vf[vfT], tmp, dest);
|
||||
return;
|
||||
}
|
||||
case 0x31: // MR32 (rotate right by 32 bits = shift xyzw -> yzwx)
|
||||
{
|
||||
float tmp[4] = {m_state.vf[vfS][1], m_state.vf[vfS][2], m_state.vf[vfS][3], m_state.vf[vfS][0]};
|
||||
applyDest(m_state.vf[vfT], tmp, dest);
|
||||
return;
|
||||
}
|
||||
case 0x34: // LQI (Load Quadword, post-increment)
|
||||
{
|
||||
uint32_t addr = ((uint32_t)(uint16_t)m_state.vi[viS]) * 16u;
|
||||
addr &= (dataSize - 1);
|
||||
if (addr + 16 <= dataSize)
|
||||
{
|
||||
float tmp[4];
|
||||
std::memcpy(tmp, vuData + addr, 16);
|
||||
applyDest(m_state.vf[vfT], tmp, dest);
|
||||
}
|
||||
if (viS != 0)
|
||||
m_state.vi[viS] = (int16_t)(m_state.vi[viS] + 1);
|
||||
return;
|
||||
}
|
||||
case 0x35: // SQI (Store Quadword, post-increment)
|
||||
{
|
||||
uint32_t addr = ((uint32_t)(uint16_t)m_state.vi[viT]) * 16u;
|
||||
addr &= (dataSize - 1);
|
||||
if (addr + 16 <= dataSize)
|
||||
{
|
||||
float tmp[4];
|
||||
std::memcpy(tmp, vuData + addr, 16);
|
||||
if (dest & 0x8)
|
||||
tmp[0] = m_state.vf[vfS][0];
|
||||
if (dest & 0x4)
|
||||
tmp[1] = m_state.vf[vfS][1];
|
||||
if (dest & 0x2)
|
||||
tmp[2] = m_state.vf[vfS][2];
|
||||
if (dest & 0x1)
|
||||
tmp[3] = m_state.vf[vfS][3];
|
||||
std::memcpy(vuData + addr, tmp, 16);
|
||||
}
|
||||
if (viT != 0)
|
||||
m_state.vi[viT] = (int16_t)(m_state.vi[viT] + 1);
|
||||
return;
|
||||
}
|
||||
case 0x36: // LQD (Load Quadword, pre-decrement)
|
||||
{
|
||||
if (viS != 0)
|
||||
m_state.vi[viS] = (int16_t)(m_state.vi[viS] - 1);
|
||||
uint32_t addr = ((uint32_t)(uint16_t)m_state.vi[viS]) * 16u;
|
||||
addr &= (dataSize - 1);
|
||||
if (addr + 16 <= dataSize)
|
||||
{
|
||||
float tmp[4];
|
||||
std::memcpy(tmp, vuData + addr, 16);
|
||||
applyDest(m_state.vf[vfT], tmp, dest);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x37: // SQD (Store Quadword, pre-decrement)
|
||||
{
|
||||
if (viT != 0)
|
||||
m_state.vi[viT] = (int16_t)(m_state.vi[viT] - 1);
|
||||
uint32_t addr = ((uint32_t)(uint16_t)m_state.vi[viT]) * 16u;
|
||||
addr &= (dataSize - 1);
|
||||
if (addr + 16 <= dataSize)
|
||||
{
|
||||
float tmp[4];
|
||||
std::memcpy(tmp, vuData + addr, 16);
|
||||
if (dest & 0x8)
|
||||
tmp[0] = m_state.vf[vfS][0];
|
||||
if (dest & 0x4)
|
||||
tmp[1] = m_state.vf[vfS][1];
|
||||
if (dest & 0x2)
|
||||
tmp[2] = m_state.vf[vfS][2];
|
||||
if (dest & 0x1)
|
||||
tmp[3] = m_state.vf[vfS][3];
|
||||
std::memcpy(vuData + addr, tmp, 16);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x38: // DIV
|
||||
{
|
||||
int fsf = (instr >> 21) & 0x3;
|
||||
int ftf = (instr >> 23) & 0x3;
|
||||
float num = m_state.vf[vfS][fsf];
|
||||
float den = m_state.vf[vfT][ftf];
|
||||
if (den != 0.0f)
|
||||
m_state.q = num / den;
|
||||
else
|
||||
m_state.q = (num >= 0.0f) ? std::numeric_limits<float>::max() : -std::numeric_limits<float>::max();
|
||||
return;
|
||||
}
|
||||
case 0x39: // SQRT
|
||||
{
|
||||
int ftf = (instr >> 23) & 0x3;
|
||||
float val = m_state.vf[vfT][ftf];
|
||||
m_state.q = std::sqrt(std::fabs(val));
|
||||
return;
|
||||
}
|
||||
case 0x3A: // RSQRT
|
||||
{
|
||||
int fsf = (instr >> 21) & 0x3;
|
||||
int ftf = (instr >> 23) & 0x3;
|
||||
float num = m_state.vf[vfS][fsf];
|
||||
float den = std::sqrt(std::fabs(m_state.vf[vfT][ftf]));
|
||||
if (den != 0.0f)
|
||||
m_state.q = num / den;
|
||||
else
|
||||
m_state.q = std::numeric_limits<float>::max();
|
||||
return;
|
||||
}
|
||||
case 0x3B: // WAITQ
|
||||
return;
|
||||
case 0x3C: // MTIR (Move To Integer Register)
|
||||
{
|
||||
int comp = 0;
|
||||
if (dest & 0x8)
|
||||
comp = 0;
|
||||
else if (dest & 0x4)
|
||||
comp = 1;
|
||||
else if (dest & 0x2)
|
||||
comp = 2;
|
||||
else
|
||||
comp = 3;
|
||||
uint32_t fval;
|
||||
std::memcpy(&fval, &m_state.vf[vfS][comp], 4);
|
||||
if (viT != 0)
|
||||
m_state.vi[viT] = (int32_t)(int16_t)(fval & 0xFFFF);
|
||||
return;
|
||||
}
|
||||
case 0x3D: // MFIR (Move From Integer Register)
|
||||
{
|
||||
float result[4];
|
||||
int32_t val = (int32_t)(int16_t)(m_state.vi[viS] & 0xFFFF);
|
||||
std::memcpy(&result[0], &val, 4);
|
||||
result[1] = result[0];
|
||||
result[2] = result[0];
|
||||
result[3] = result[0];
|
||||
applyDest(m_state.vf[vfT], result, dest);
|
||||
return;
|
||||
}
|
||||
case 0x3E: // ILWR - integer load word from address in VI[is]
|
||||
{
|
||||
uint32_t addr = ((uint32_t)(uint16_t)m_state.vi[viS]) * 16u;
|
||||
addr &= (dataSize - 1);
|
||||
if (addr + 16 <= dataSize)
|
||||
{
|
||||
int comp = 0;
|
||||
if (dest & 0x8)
|
||||
comp = 0;
|
||||
else if (dest & 0x4)
|
||||
comp = 1;
|
||||
else if (dest & 0x2)
|
||||
comp = 2;
|
||||
else
|
||||
comp = 3;
|
||||
uint32_t v;
|
||||
std::memcpy(&v, vuData + addr + comp * 4, 4);
|
||||
if (viT != 0)
|
||||
m_state.vi[viT] = (int32_t)(int16_t)(v & 0xFFFF);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x3F: // ISWR - integer store word to address in VI[is]
|
||||
{
|
||||
uint32_t addr = ((uint32_t)(uint16_t)m_state.vi[viS]) * 16u;
|
||||
addr &= (dataSize - 1);
|
||||
if (addr + 16 <= dataSize)
|
||||
{
|
||||
uint32_t val = (uint32_t)(uint16_t)(m_state.vi[viT] & 0xFFFF);
|
||||
if (dest & 0x8)
|
||||
std::memcpy(vuData + addr + 0, &val, 4);
|
||||
if (dest & 0x4)
|
||||
std::memcpy(vuData + addr + 4, &val, 4);
|
||||
if (dest & 0x2)
|
||||
std::memcpy(vuData + addr + 8, &val, 4);
|
||||
if (dest & 0x1)
|
||||
std::memcpy(vuData + addr + 12, &val, 4);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x40: // RNEXT
|
||||
return;
|
||||
case 0x41: // RGET
|
||||
return;
|
||||
case 0x42: // RINIT
|
||||
return;
|
||||
case 0x43: // RXOR
|
||||
return;
|
||||
case 0x64: // MFP (Move From P register)
|
||||
{
|
||||
float result[4] = {m_state.p, m_state.p, m_state.p, m_state.p};
|
||||
applyDest(m_state.vf[vfT], result, dest);
|
||||
return;
|
||||
}
|
||||
case 0x68: // XTOP - move current VIF1 TOP into VI register
|
||||
{
|
||||
if (viT != 0)
|
||||
m_state.vi[viT] = (int32_t)(m_state.top & 0x3FFu);
|
||||
return;
|
||||
}
|
||||
case 0x69: // XITOP - move current VIF1 ITOP into VI register
|
||||
{
|
||||
if (viT != 0)
|
||||
m_state.vi[viT] = (int32_t)(m_state.itop & 0x3FFu);
|
||||
return;
|
||||
}
|
||||
case 0x6C: // XGKICK - send GIF packet from VU1 data memory
|
||||
doXgkick();
|
||||
return;
|
||||
case 0x70: // ESADD
|
||||
return;
|
||||
case 0x71: // ERSADD
|
||||
return;
|
||||
case 0x72: // ELENG
|
||||
{
|
||||
float s = m_state.vf[vfS][0] * m_state.vf[vfS][0] + m_state.vf[vfS][1] * m_state.vf[vfS][1] + m_state.vf[vfS][2] * m_state.vf[vfS][2];
|
||||
m_state.p = std::sqrt(s);
|
||||
return;
|
||||
}
|
||||
case 0x73: // ERLENG
|
||||
{
|
||||
float s = m_state.vf[vfS][0] * m_state.vf[vfS][0] + m_state.vf[vfS][1] * m_state.vf[vfS][1] + m_state.vf[vfS][2] * m_state.vf[vfS][2];
|
||||
float len = std::sqrt(s);
|
||||
m_state.p = (len != 0.0f) ? (1.0f / len) : std::numeric_limits<float>::max();
|
||||
return;
|
||||
}
|
||||
case 0x7A: // ERCPR
|
||||
{
|
||||
int fsf = (instr >> 21) & 0x3;
|
||||
float val = m_state.vf[vfS][fsf];
|
||||
m_state.p = (val != 0.0f) ? (1.0f / val) : std::numeric_limits<float>::max();
|
||||
return;
|
||||
}
|
||||
case 0x7B: // WAITP
|
||||
return;
|
||||
case 0x7D: // EATAN / EATANxy / EATANxz placeholder
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,458 @@
|
||||
#include "runtime/ps2_vu1.h"
|
||||
#include "ps2_vu1_detail.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
// ============================================================================
|
||||
// Upper instructions (FMAC pipeline)
|
||||
// ============================================================================
|
||||
void VU1Interpreter::execUpper(uint32_t instr)
|
||||
{
|
||||
uint8_t dest = DEST(instr);
|
||||
uint8_t ft = FT(instr);
|
||||
uint8_t fs = FS(instr);
|
||||
uint8_t fd = FD(instr);
|
||||
uint8_t op = instr & 0x3F;
|
||||
|
||||
float *vd = m_state.vf[fd];
|
||||
const float *vs = m_state.vf[fs];
|
||||
const float *vt = m_state.vf[ft];
|
||||
float result[4];
|
||||
|
||||
// Upper opcode decoding (bits 5:0 of upper word)
|
||||
switch (op)
|
||||
{
|
||||
case 0x00:
|
||||
case 0x01:
|
||||
case 0x02:
|
||||
case 0x03: // ADDbc
|
||||
{
|
||||
float bc = broadcast(vt, op & 3);
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] + bc;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
}
|
||||
case 0x04:
|
||||
case 0x05:
|
||||
case 0x06:
|
||||
case 0x07: // SUBbc
|
||||
{
|
||||
float bc = broadcast(vt, op & 3);
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] - bc;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
}
|
||||
case 0x08:
|
||||
case 0x09:
|
||||
case 0x0A:
|
||||
case 0x0B: // MADDbc
|
||||
{
|
||||
float bc = broadcast(vt, op & 3);
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] + vs[c] * bc;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
}
|
||||
case 0x0C:
|
||||
case 0x0D:
|
||||
case 0x0E:
|
||||
case 0x0F: // MSUBbc
|
||||
{
|
||||
float bc = broadcast(vt, op & 3);
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] - vs[c] * bc;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
}
|
||||
case 0x10:
|
||||
case 0x11:
|
||||
case 0x12:
|
||||
case 0x13: // MAXbc
|
||||
{
|
||||
float bc = broadcast(vt, op & 3);
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = (vs[c] > bc) ? vs[c] : bc;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
}
|
||||
case 0x14:
|
||||
case 0x15:
|
||||
case 0x16:
|
||||
case 0x17: // MINIbc
|
||||
{
|
||||
float bc = broadcast(vt, op & 3);
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = (vs[c] < bc) ? vs[c] : bc;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
}
|
||||
case 0x18:
|
||||
case 0x19:
|
||||
case 0x1A:
|
||||
case 0x1B: // MULbc
|
||||
{
|
||||
float bc = broadcast(vt, op & 3);
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] * bc;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
}
|
||||
case 0x1C: // MULq
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] * m_state.q;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x1D: // MAXi
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = (vs[c] > m_state.i) ? vs[c] : m_state.i;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x1E: // MULi
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] * m_state.i;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x1F: // MINIi
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = (vs[c] < m_state.i) ? vs[c] : m_state.i;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x20: // ADDq
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] + m_state.q;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x21: // MADDq
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] + vs[c] * m_state.q;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x22: // ADDi
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] + m_state.i;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x23: // MADDi
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] + vs[c] * m_state.i;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x24: // SUBq
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] - m_state.q;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x25: // MSUBq
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] - vs[c] * m_state.q;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x26: // SUBi
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] - m_state.i;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x27: // MSUBi
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] - vs[c] * m_state.i;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x28: // ADD
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] + vt[c];
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x29: // MADD
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] + vs[c] * vt[c];
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x2A: // MUL
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] * vt[c];
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x2B: // MAX
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = (vs[c] > vt[c]) ? vs[c] : vt[c];
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x2C: // SUB
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] - vt[c];
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x2D: // MSUB
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] - vs[c] * vt[c];
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x2E: // OPMSUB
|
||||
result[0] = m_state.acc[0] - vs[1] * vt[2];
|
||||
result[1] = m_state.acc[1] - vs[2] * vt[0];
|
||||
result[2] = m_state.acc[2] - vs[0] * vt[1];
|
||||
result[3] = 0.0f;
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
case 0x2F: // MINI
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = (vs[c] < vt[c]) ? vs[c] : vt[c];
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
|
||||
// Upper special group (low op 0x3C..0x3F).
|
||||
// Like lower1 special, the real selector is not just bits 5:0. Dobie decodes:
|
||||
// op = (instr & 0x3) | ((instr >> 4) & 0x7C)
|
||||
// Several instructions in this group also use FT as the destination, not FD.
|
||||
case 0x3C:
|
||||
case 0x3D:
|
||||
case 0x3E:
|
||||
case 0x3F:
|
||||
{
|
||||
const uint8_t specialOp = static_cast<uint8_t>((instr & 0x3u) | ((instr >> 4) & 0x7Cu));
|
||||
float *vtDest = m_state.vf[ft];
|
||||
|
||||
switch (specialOp)
|
||||
{
|
||||
case 0x00:
|
||||
case 0x01:
|
||||
case 0x02:
|
||||
case 0x03: // ADDAbc
|
||||
{
|
||||
float bc = broadcast(vt, specialOp & 3);
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] + bc;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
}
|
||||
case 0x04:
|
||||
case 0x05:
|
||||
case 0x06:
|
||||
case 0x07: // SUBAbc
|
||||
{
|
||||
float bc = broadcast(vt, specialOp & 3);
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] - bc;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
}
|
||||
case 0x08:
|
||||
case 0x09:
|
||||
case 0x0A:
|
||||
case 0x0B: // MADDAbc
|
||||
{
|
||||
float bc = broadcast(vt, specialOp & 3);
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] + vs[c] * bc;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
}
|
||||
case 0x0C:
|
||||
case 0x0D:
|
||||
case 0x0E:
|
||||
case 0x0F: // MSUBAbc
|
||||
{
|
||||
float bc = broadcast(vt, specialOp & 3);
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] - vs[c] * bc;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
}
|
||||
case 0x10: // ITOF0
|
||||
for (int c = 0; c < 4; c++)
|
||||
{
|
||||
int32_t iv;
|
||||
std::memcpy(&iv, &vs[c], 4);
|
||||
result[c] = static_cast<float>(iv);
|
||||
}
|
||||
applyDest(vtDest, result, dest);
|
||||
return;
|
||||
case 0x11: // ITOF4
|
||||
for (int c = 0; c < 4; c++)
|
||||
{
|
||||
int32_t iv;
|
||||
std::memcpy(&iv, &vs[c], 4);
|
||||
result[c] = static_cast<float>(iv) / 16.0f;
|
||||
}
|
||||
applyDest(vtDest, result, dest);
|
||||
return;
|
||||
case 0x12: // ITOF12
|
||||
for (int c = 0; c < 4; c++)
|
||||
{
|
||||
int32_t iv;
|
||||
std::memcpy(&iv, &vs[c], 4);
|
||||
result[c] = static_cast<float>(iv) / 4096.0f;
|
||||
}
|
||||
applyDest(vtDest, result, dest);
|
||||
return;
|
||||
case 0x13: // ITOF15
|
||||
for (int c = 0; c < 4; c++)
|
||||
{
|
||||
int32_t iv;
|
||||
std::memcpy(&iv, &vs[c], 4);
|
||||
result[c] = static_cast<float>(iv) / 32768.0f;
|
||||
}
|
||||
applyDest(vtDest, result, dest);
|
||||
return;
|
||||
case 0x14: // FTOI0
|
||||
for (int c = 0; c < 4; c++)
|
||||
{
|
||||
int32_t iv = static_cast<int32_t>(vs[c]);
|
||||
std::memcpy(&result[c], &iv, 4);
|
||||
}
|
||||
applyDest(vtDest, result, dest);
|
||||
return;
|
||||
case 0x15: // FTOI4
|
||||
for (int c = 0; c < 4; c++)
|
||||
{
|
||||
int32_t iv = static_cast<int32_t>(vs[c] * 16.0f);
|
||||
std::memcpy(&result[c], &iv, 4);
|
||||
}
|
||||
applyDest(vtDest, result, dest);
|
||||
return;
|
||||
case 0x16: // FTOI12
|
||||
for (int c = 0; c < 4; c++)
|
||||
{
|
||||
int32_t iv = static_cast<int32_t>(vs[c] * 4096.0f);
|
||||
std::memcpy(&result[c], &iv, 4);
|
||||
}
|
||||
applyDest(vtDest, result, dest);
|
||||
return;
|
||||
case 0x17: // FTOI15
|
||||
for (int c = 0; c < 4; c++)
|
||||
{
|
||||
int32_t iv = static_cast<int32_t>(vs[c] * 32768.0f);
|
||||
std::memcpy(&result[c], &iv, 4);
|
||||
}
|
||||
applyDest(vtDest, result, dest);
|
||||
return;
|
||||
case 0x18:
|
||||
case 0x19:
|
||||
case 0x1A:
|
||||
case 0x1B: // MULAbc
|
||||
{
|
||||
float bc = broadcast(vt, specialOp & 3);
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] * bc;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
}
|
||||
case 0x1C: // MULAq
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] * m_state.q;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x1D: // ABS
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = std::fabs(vs[c]);
|
||||
applyDest(vtDest, result, dest);
|
||||
return;
|
||||
case 0x1E: // MULAi
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] * m_state.i;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x1F: // CLIP
|
||||
{
|
||||
float w = std::fabs(vt[3]);
|
||||
uint32_t flags = 0;
|
||||
if (vs[0] > +w) flags |= 0x01;
|
||||
if (vs[0] < -w) flags |= 0x02;
|
||||
if (vs[1] > +w) flags |= 0x04;
|
||||
if (vs[1] < -w) flags |= 0x08;
|
||||
if (vs[2] > +w) flags |= 0x10;
|
||||
if (vs[2] < -w) flags |= 0x20;
|
||||
m_state.clip = (m_state.clip << 6) | flags;
|
||||
return;
|
||||
}
|
||||
case 0x20: // ADDAq
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] + m_state.q;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x21: // MADDAq
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] + vs[c] * m_state.q;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x22: // ADDAi
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] + m_state.i;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x23: // MADDAi
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] + vs[c] * m_state.i;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x24: // SUBAq
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] - m_state.q;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x25: // MSUBAq
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] - vs[c] * m_state.q;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x26: // SUBAi
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] - m_state.i;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x27: // MSUBAi
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] - vs[c] * m_state.i;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x28: // ADDA
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] + vt[c];
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x29: // MADDA
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] + vs[c] * vt[c];
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x2A: // MULA
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] * vt[c];
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x2C: // SUBA
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = vs[c] - vt[c];
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x2D: // MSUBA
|
||||
for (int c = 0; c < 4; c++)
|
||||
result[c] = m_state.acc[c] - vs[c] * vt[c];
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x2E: // OPMULA
|
||||
result[0] = vs[1] * vt[2];
|
||||
result[1] = vs[2] * vt[0];
|
||||
result[2] = vs[0] * vt[1];
|
||||
result[3] = 0.0f;
|
||||
applyDestAcc(result, dest);
|
||||
return;
|
||||
case 0x2F:
|
||||
case 0x30: // NOP
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
case 0x30:
|
||||
case 0x31:
|
||||
case 0x32:
|
||||
case 0x33:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ add_library(ps2_test_lib STATIC
|
||||
src/ps2_runtime_kernel_tests.cpp
|
||||
src/ps2_runtime_interrupt_tests.cpp
|
||||
src/ps2_memory_tests.cpp
|
||||
src/ps2_vu1_tests.cpp
|
||||
src/ps2_gs_tests.cpp
|
||||
src/ps2_sif_rpc_tests.cpp
|
||||
src/ps2_sif_dma_tests.cpp
|
||||
|
||||
@@ -10,6 +10,7 @@ void register_ps2_runtime_io_tests();
|
||||
void register_ps2_runtime_kernel_tests();
|
||||
void register_ps2_runtime_interrupt_tests();
|
||||
void register_ps2_memory_tests();
|
||||
void register_ps2_vu1_tests();
|
||||
void register_ps2_gs_tests();
|
||||
void register_ps2_sif_rpc_tests();
|
||||
void register_ps2_sif_dma_tests();
|
||||
@@ -29,6 +30,7 @@ int main()
|
||||
register_ps2_runtime_kernel_tests();
|
||||
register_ps2_runtime_interrupt_tests();
|
||||
register_ps2_memory_tests();
|
||||
register_ps2_vu1_tests();
|
||||
register_ps2_gs_tests();
|
||||
register_ps2_sif_rpc_tests();
|
||||
register_ps2_sif_dma_tests();
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "runtime/ps2_memory.h"
|
||||
#include "runtime/ps2_gs_gpu.h"
|
||||
#include "runtime/ps2_gs_psmct32.h"
|
||||
#include "runtime/ps2_vu1.h"
|
||||
#include "ps2_runtime.h"
|
||||
#include "ps2_runtime_macros.h"
|
||||
#include "Stubs/DMA.h"
|
||||
@@ -156,17 +155,6 @@ namespace
|
||||
appendU64(packet, 0u);
|
||||
}
|
||||
|
||||
uint32_t makeVuLowerSpecial(uint8_t specialOp, uint8_t is, uint8_t it = 0u, uint8_t id = 0u, uint8_t dest = 0u)
|
||||
{
|
||||
return (0x40u << 25) |
|
||||
(static_cast<uint32_t>(dest & 0xFu) << 21) |
|
||||
(static_cast<uint32_t>(it & 0x1Fu) << 16) |
|
||||
(static_cast<uint32_t>(is & 0x1Fu) << 11) |
|
||||
(static_cast<uint32_t>(id & 0x1Fu) << 6) |
|
||||
(static_cast<uint32_t>(specialOp & 0x7Cu) << 4) |
|
||||
static_cast<uint32_t>(specialOp & 0x3u) |
|
||||
0x3Cu;
|
||||
}
|
||||
}
|
||||
|
||||
void register_ps2_memory_tests()
|
||||
@@ -1732,72 +1720,6 @@ void register_ps2_memory_tests()
|
||||
t.Equals(mem.readIORegister(kDstadr), 0u, "sceDmaReset should clear D_STADR");
|
||||
});
|
||||
|
||||
tc.Run("VU1 XGKICK wraps packet payload across VU1 memory boundary", [](TestCase &t)
|
||||
{
|
||||
PS2Memory mem;
|
||||
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");
|
||||
|
||||
std::vector<std::vector<uint8_t>> captured;
|
||||
mem.setGifPacketCallback([&](const uint8_t *data, uint32_t sizeBytes)
|
||||
{
|
||||
captured.emplace_back(data, data + sizeBytes);
|
||||
});
|
||||
|
||||
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
|
||||
GS gs;
|
||||
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
|
||||
|
||||
uint8_t *vuCode = mem.getVU1Code();
|
||||
uint8_t *vuData = mem.getVU1Data();
|
||||
std::memset(vuCode, 0, PS2_VU1_CODE_SIZE);
|
||||
std::memset(vuData, 0, PS2_VU1_DATA_SIZE);
|
||||
|
||||
constexpr uint32_t kLastQw = (PS2_VU1_DATA_SIZE / 16u) - 1u;
|
||||
const uint32_t tagOffset = kLastQw * 16u;
|
||||
|
||||
const uint64_t imageTag = makeGifTag(1u, GIF_FMT_IMAGE, 0u, true);
|
||||
std::memcpy(vuData + tagOffset, &imageTag, sizeof(imageTag));
|
||||
|
||||
for (uint32_t i = 0; i < 16u; ++i)
|
||||
{
|
||||
vuData[i] = static_cast<uint8_t>(0xC0u + i);
|
||||
}
|
||||
|
||||
const uint32_t lower = makeVuLowerSpecial(0x6Cu, 1u);
|
||||
std::memcpy(vuCode + 0u, &lower, sizeof(lower));
|
||||
const uint32_t upper = 0u;
|
||||
std::memcpy(vuCode + 4u, &upper, sizeof(upper));
|
||||
|
||||
VU1Interpreter vu1;
|
||||
vu1.state().vi[1] = static_cast<int32_t>(kLastQw);
|
||||
vu1.execute(vuCode,
|
||||
PS2_VU1_CODE_SIZE,
|
||||
vuData,
|
||||
PS2_VU1_DATA_SIZE,
|
||||
gs,
|
||||
&mem,
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
1u);
|
||||
|
||||
t.Equals(captured.size(), static_cast<size_t>(1u), "XGKICK should emit one wrapped GIF packet");
|
||||
if (!captured.empty())
|
||||
{
|
||||
t.Equals(captured[0].size(), static_cast<size_t>(32u), "wrapped packet should include tag plus one qword payload");
|
||||
bool payloadOk = true;
|
||||
for (uint32_t i = 0; i < 16u; ++i)
|
||||
{
|
||||
if (captured[0].size() < 32u || captured[0][16u + i] != static_cast<uint8_t>(0xC0u + i))
|
||||
{
|
||||
payloadOk = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
t.IsTrue(payloadOk, "wrapped payload should be copied from start of VU1 memory");
|
||||
}
|
||||
});
|
||||
|
||||
tc.Run("VIF1 DMA DIRECT image packet reaches GS through arbiter", [](TestCase &t)
|
||||
{
|
||||
PS2Memory mem;
|
||||
@@ -1919,85 +1841,6 @@ void register_ps2_memory_tests()
|
||||
t.IsTrue(imageOk, "raw qwords after a DIRECT image tag should continue the PATH2 image upload");
|
||||
});
|
||||
|
||||
tc.Run("VIF MSCAL callback can execute XGKICK and update GS VRAM", [](TestCase &t)
|
||||
{
|
||||
PS2Memory mem;
|
||||
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");
|
||||
|
||||
GS gs;
|
||||
gs.init(mem.getGSVRAM(), static_cast<uint32_t>(PS2_GS_VRAM_SIZE), &mem.gs());
|
||||
GifArbiter arbiter([&](const uint8_t *data, uint32_t sizeBytes)
|
||||
{
|
||||
gs.processGIFPacket(data, sizeBytes);
|
||||
});
|
||||
mem.setGifArbiter(&arbiter);
|
||||
|
||||
const uint64_t bitblt =
|
||||
(static_cast<uint64_t>(0u) << 0) |
|
||||
(static_cast<uint64_t>(1u) << 16) |
|
||||
(static_cast<uint64_t>(0u) << 24) |
|
||||
(static_cast<uint64_t>(0u) << 32) |
|
||||
(static_cast<uint64_t>(1u) << 48) |
|
||||
(static_cast<uint64_t>(0u) << 56);
|
||||
gs.writeRegister(GS_REG_BITBLTBUF, bitblt);
|
||||
gs.writeRegister(GS_REG_TRXPOS, 0ull);
|
||||
gs.writeRegister(GS_REG_TRXREG, (4ull << 0) | (1ull << 32));
|
||||
gs.writeRegister(GS_REG_TRXDIR, 0ull);
|
||||
|
||||
uint8_t *vuCode = mem.getVU1Code();
|
||||
uint8_t *vuData = mem.getVU1Data();
|
||||
std::memset(vuCode, 0, PS2_VU1_CODE_SIZE);
|
||||
std::memset(vuData, 0, PS2_VU1_DATA_SIZE);
|
||||
|
||||
const uint32_t lower = makeVuLowerSpecial(0x6Cu, 0u);
|
||||
std::memcpy(vuCode + 0u, &lower, sizeof(lower));
|
||||
const uint32_t upper = 0u;
|
||||
std::memcpy(vuCode + 4u, &upper, sizeof(upper));
|
||||
|
||||
const uint64_t gifTag = makeGifTag(1u, GIF_FMT_IMAGE, 0u, true);
|
||||
std::memcpy(vuData + 0u, &gifTag, sizeof(gifTag));
|
||||
const uint64_t tagHi = 0u;
|
||||
std::memcpy(vuData + 8u, &tagHi, sizeof(tagHi));
|
||||
for (uint32_t i = 0; i < 16u; ++i)
|
||||
{
|
||||
vuData[16u + i] = static_cast<uint8_t>(0x90u + i);
|
||||
}
|
||||
|
||||
VU1Interpreter vu1;
|
||||
mem.setVu1MscalCallback([&](uint32_t startPC, uint32_t top, uint32_t itop)
|
||||
{
|
||||
vu1.execute(vuCode,
|
||||
PS2_VU1_CODE_SIZE,
|
||||
vuData,
|
||||
PS2_VU1_DATA_SIZE,
|
||||
gs,
|
||||
&mem,
|
||||
startPC,
|
||||
top,
|
||||
itop,
|
||||
1u);
|
||||
});
|
||||
|
||||
const uint32_t mscalCmd = makeVifCmd(0x14u, 0u, 0u);
|
||||
mem.processVIF1Data(reinterpret_cast<const uint8_t *>(&mscalCmd), sizeof(mscalCmd));
|
||||
|
||||
const uint8_t *vramOut = mem.getGSVRAM();
|
||||
bool imageOk = true;
|
||||
for (uint32_t x = 0; x < 4u && imageOk; ++x)
|
||||
{
|
||||
const uint32_t off = GSPSMCT32::addrPSMCT32(0u, 1u, x, 0u);
|
||||
for (uint32_t c = 0; c < 4u; ++c)
|
||||
{
|
||||
if (vramOut[off + c] != static_cast<uint8_t>(0x90u + x * 4u + c))
|
||||
{
|
||||
imageOk = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
t.IsTrue(imageOk, "MSCAL-triggered XGKICK should route PATH1 packet into GS VRAM");
|
||||
});
|
||||
|
||||
tc.Run("unaligned accesses throw", [](TestCase &t)
|
||||
{
|
||||
PS2Memory mem;
|
||||
|
||||
@@ -0,0 +1,559 @@
|
||||
#include "MiniTest.h"
|
||||
#include "runtime/ps2_gif_arbiter.h"
|
||||
#include "runtime/ps2_gs_gpu.h"
|
||||
#include "runtime/ps2_gs_psmct32.h"
|
||||
#include "runtime/ps2_memory.h"
|
||||
#include "runtime/ps2_vu1.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr uint32_t kVuUpperNop = 0u;
|
||||
|
||||
struct Vu1Fixture
|
||||
{
|
||||
PS2Memory mem;
|
||||
GS gs;
|
||||
uint8_t *code = nullptr;
|
||||
uint8_t *data = nullptr;
|
||||
|
||||
bool initialize()
|
||||
{
|
||||
if (!mem.initialize())
|
||||
return false;
|
||||
gs.init(mem.getGSVRAM(), static_cast<uint32_t>(PS2_GS_VRAM_SIZE), &mem.gs());
|
||||
code = mem.getVU1Code();
|
||||
data = mem.getVU1Data();
|
||||
std::memset(code, 0, PS2_VU1_CODE_SIZE);
|
||||
std::memset(data, 0, PS2_VU1_DATA_SIZE);
|
||||
return code != nullptr && data != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t makeVifCmd(uint8_t opcode, uint8_t num, uint16_t imm)
|
||||
{
|
||||
return (static_cast<uint32_t>(opcode) << 24) |
|
||||
(static_cast<uint32_t>(num) << 16) |
|
||||
static_cast<uint32_t>(imm);
|
||||
}
|
||||
|
||||
uint64_t makeGifTag(uint16_t nloop, uint8_t flg, uint8_t nreg, bool eop = true)
|
||||
{
|
||||
uint64_t tag = static_cast<uint64_t>(nloop & 0x7FFFu);
|
||||
if (eop)
|
||||
tag |= (1ull << 15);
|
||||
tag |= (static_cast<uint64_t>(flg & 0x3u) << 58);
|
||||
tag |= (static_cast<uint64_t>(nreg & 0xFu) << 60);
|
||||
return tag;
|
||||
}
|
||||
|
||||
uint32_t makeVuLowerSpecial(uint8_t specialOp, uint8_t is, uint8_t it = 0u, uint8_t id = 0u, uint8_t dest = 0u)
|
||||
{
|
||||
return (0x40u << 25) |
|
||||
(static_cast<uint32_t>(dest & 0xFu) << 21) |
|
||||
(static_cast<uint32_t>(it & 0x1Fu) << 16) |
|
||||
(static_cast<uint32_t>(is & 0x1Fu) << 11) |
|
||||
(static_cast<uint32_t>(id & 0x1Fu) << 6) |
|
||||
(static_cast<uint32_t>(specialOp & 0x7Cu) << 4) |
|
||||
static_cast<uint32_t>(specialOp & 0x3u) |
|
||||
0x3Cu;
|
||||
}
|
||||
|
||||
uint32_t makeVuLowerDirect(uint8_t funct, uint8_t is, uint8_t it = 0u, uint8_t id = 0u, uint8_t dest = 0u)
|
||||
{
|
||||
return (0x40u << 25) |
|
||||
(static_cast<uint32_t>(dest & 0xFu) << 21) |
|
||||
(static_cast<uint32_t>(it & 0x1Fu) << 16) |
|
||||
(static_cast<uint32_t>(is & 0x1Fu) << 11) |
|
||||
(static_cast<uint32_t>(id & 0x1Fu) << 6) |
|
||||
static_cast<uint32_t>(funct & 0x3Fu);
|
||||
}
|
||||
|
||||
uint32_t makeVuUpper(uint8_t op, uint8_t dest, uint8_t ft, uint8_t fs, uint8_t fd)
|
||||
{
|
||||
return (static_cast<uint32_t>(dest & 0xFu) << 21) |
|
||||
(static_cast<uint32_t>(ft & 0x1Fu) << 16) |
|
||||
(static_cast<uint32_t>(fs & 0x1Fu) << 11) |
|
||||
(static_cast<uint32_t>(fd & 0x1Fu) << 6) |
|
||||
static_cast<uint32_t>(op & 0x3Fu);
|
||||
}
|
||||
|
||||
uint32_t makeVuLq(uint8_t dest, uint8_t targetVf, uint8_t baseVi, int16_t imm)
|
||||
{
|
||||
return (static_cast<uint32_t>(dest & 0xFu) << 21) |
|
||||
(static_cast<uint32_t>(targetVf & 0x1Fu) << 16) |
|
||||
(static_cast<uint32_t>(baseVi & 0xFu) << 11) |
|
||||
(static_cast<uint32_t>(imm) & 0x7FFu);
|
||||
}
|
||||
|
||||
uint32_t makeVuSq(uint8_t dest, uint8_t sourceVf, uint8_t baseVi, int16_t imm)
|
||||
{
|
||||
return (0x01u << 25) |
|
||||
(static_cast<uint32_t>(dest & 0xFu) << 21) |
|
||||
(static_cast<uint32_t>(baseVi & 0xFu) << 16) |
|
||||
(static_cast<uint32_t>(sourceVf & 0x1Fu) << 11) |
|
||||
(static_cast<uint32_t>(imm) & 0x7FFu);
|
||||
}
|
||||
|
||||
uint32_t makeVuIaddiu(uint8_t it, uint8_t is, int16_t imm)
|
||||
{
|
||||
return (0x08u << 25) |
|
||||
(static_cast<uint32_t>(it & 0xFu) << 16) |
|
||||
(static_cast<uint32_t>(is & 0xFu) << 11) |
|
||||
(static_cast<uint32_t>(imm) & 0x7FFu);
|
||||
}
|
||||
|
||||
uint32_t makeVuBranch(int16_t imm)
|
||||
{
|
||||
return (0x20u << 25) | (static_cast<uint32_t>(imm) & 0x7FFu);
|
||||
}
|
||||
|
||||
uint32_t makeVuDiv(uint8_t fs, uint8_t ft, uint8_t fsf, uint8_t ftf)
|
||||
{
|
||||
return makeVuLowerSpecial(0x38u, fs, ft, 0u, static_cast<uint8_t>(((ftf & 0x3u) << 2) | (fsf & 0x3u)));
|
||||
}
|
||||
|
||||
uint32_t makeVuSqrt(uint8_t ft, uint8_t ftf)
|
||||
{
|
||||
return makeVuLowerSpecial(0x39u, 0u, ft, 0u, static_cast<uint8_t>((ftf & 0x3u) << 2));
|
||||
}
|
||||
|
||||
void writeVuInstructionPair(uint8_t *code, uint32_t pc, uint32_t lower, uint32_t upper)
|
||||
{
|
||||
std::memcpy(code + pc, &lower, sizeof(lower));
|
||||
std::memcpy(code + pc + sizeof(lower), &upper, sizeof(upper));
|
||||
}
|
||||
|
||||
uint64_t packVuInstructionPair(uint32_t lower, uint32_t upper)
|
||||
{
|
||||
return static_cast<uint64_t>(lower) | (static_cast<uint64_t>(upper) << 32);
|
||||
}
|
||||
|
||||
void appendU32(std::vector<uint8_t> &bytes, uint32_t value)
|
||||
{
|
||||
const uint8_t *src = reinterpret_cast<const uint8_t *>(&value);
|
||||
bytes.insert(bytes.end(), src, src + sizeof(value));
|
||||
}
|
||||
|
||||
void uploadVu1Mpg(PS2Memory &mem, uint16_t instructionAddress, uint32_t lower, uint32_t upper)
|
||||
{
|
||||
std::vector<uint8_t> packet;
|
||||
appendU32(packet, makeVifCmd(0x4Au, 1u, instructionAddress));
|
||||
appendU32(packet, lower);
|
||||
appendU32(packet, upper);
|
||||
mem.processVIF1Data(packet.data(), static_cast<uint32_t>(packet.size()));
|
||||
}
|
||||
|
||||
void writeVuQword(uint8_t *data, uint32_t qwordIndex, const float values[4])
|
||||
{
|
||||
std::memcpy(data + qwordIndex * 16u, values, sizeof(float) * 4u);
|
||||
}
|
||||
|
||||
void readVuQword(const uint8_t *data, uint32_t qwordIndex, float values[4])
|
||||
{
|
||||
std::memcpy(values, data + qwordIndex * 16u, sizeof(float) * 4u);
|
||||
}
|
||||
}
|
||||
|
||||
void register_ps2_vu1_tests()
|
||||
{
|
||||
MiniTest::Case("PS2VU1", [](TestCase &tc)
|
||||
{
|
||||
tc.Run("upper ADD applies the destination mask", [](TestCase &t)
|
||||
{
|
||||
Vu1Fixture fx;
|
||||
t.IsTrue(fx.initialize(), "VU1 fixture should initialize");
|
||||
|
||||
writeVuInstructionPair(fx.code, 0u, 0u, makeVuUpper(0x28u, 0xAu, 2u, 1u, 3u)); // ADD.xz vf3, vf1, vf2
|
||||
|
||||
VU1Interpreter vu1;
|
||||
vu1.state().vf[1][0] = 1.0f;
|
||||
vu1.state().vf[1][1] = 2.0f;
|
||||
vu1.state().vf[1][2] = 3.0f;
|
||||
vu1.state().vf[1][3] = 4.0f;
|
||||
vu1.state().vf[2][0] = 10.0f;
|
||||
vu1.state().vf[2][1] = 20.0f;
|
||||
vu1.state().vf[2][2] = 30.0f;
|
||||
vu1.state().vf[2][3] = 40.0f;
|
||||
vu1.state().vf[3][0] = -1.0f;
|
||||
vu1.state().vf[3][1] = -2.0f;
|
||||
vu1.state().vf[3][2] = -3.0f;
|
||||
vu1.state().vf[3][3] = -4.0f;
|
||||
|
||||
vu1.execute(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, fx.gs, &fx.mem, 0u, 0u, 0u, 1u);
|
||||
|
||||
t.Equals(vu1.state().vf[3][0], 11.0f, "ADD.x should write x");
|
||||
t.Equals(vu1.state().vf[3][1], -2.0f, "ADD.xz should preserve y");
|
||||
t.Equals(vu1.state().vf[3][2], 33.0f, "ADD.xz should write z");
|
||||
t.Equals(vu1.state().vf[3][3], -4.0f, "ADD.xz should preserve w");
|
||||
});
|
||||
|
||||
tc.Run("LOI commits the lower immediate after the upper instruction", [](TestCase &t)
|
||||
{
|
||||
Vu1Fixture fx;
|
||||
t.IsTrue(fx.initialize(), "VU1 fixture should initialize");
|
||||
|
||||
const float newI = 7.0f;
|
||||
uint32_t lowerImmediate = 0u;
|
||||
std::memcpy(&lowerImmediate, &newI, sizeof(newI));
|
||||
const uint32_t upperAddiWithIBit = makeVuUpper(0x22u, 0xFu, 0u, 1u, 2u) | 0x80000000u; // ADDi.xyzw vf2, vf1
|
||||
writeVuInstructionPair(fx.code, 0u, lowerImmediate, upperAddiWithIBit);
|
||||
|
||||
VU1Interpreter vu1;
|
||||
vu1.state().i = 2.0f;
|
||||
vu1.state().vf[1][0] = 1.0f;
|
||||
vu1.state().vf[1][1] = 2.0f;
|
||||
vu1.state().vf[1][2] = 3.0f;
|
||||
vu1.state().vf[1][3] = 4.0f;
|
||||
|
||||
vu1.execute(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, fx.gs, &fx.mem, 0u, 0u, 0u, 1u);
|
||||
|
||||
t.Equals(vu1.state().vf[2][0], 3.0f, "ADDi should use old I for x");
|
||||
t.Equals(vu1.state().vf[2][1], 4.0f, "ADDi should use old I for y");
|
||||
t.Equals(vu1.state().vf[2][2], 5.0f, "ADDi should use old I for z");
|
||||
t.Equals(vu1.state().vf[2][3], 6.0f, "ADDi should use old I for w");
|
||||
t.Equals(vu1.state().i, 7.0f, "LOI should commit lower immediate into I after upper execution");
|
||||
});
|
||||
|
||||
tc.Run("LQ and SQ use VI qword addressing and destination masks", [](TestCase &t)
|
||||
{
|
||||
Vu1Fixture fx;
|
||||
t.IsTrue(fx.initialize(), "VU1 fixture should initialize");
|
||||
|
||||
const float sourceQw[4] = {10.0f, 20.0f, 30.0f, 40.0f};
|
||||
const float destQw[4] = {-1.0f, -2.0f, -3.0f, -4.0f};
|
||||
writeVuQword(fx.data, 3u, sourceQw);
|
||||
writeVuQword(fx.data, 5u, destQw);
|
||||
writeVuInstructionPair(fx.code, 0u, makeVuLq(0x5u, 4u, 1u, 1), kVuUpperNop); // LQ.yw vf4, 1(vi1)
|
||||
writeVuInstructionPair(fx.code, 8u, makeVuSq(0xAu, 4u, 2u, 1), kVuUpperNop); // SQ.xz vf4, 1(vi2)
|
||||
|
||||
VU1Interpreter vu1;
|
||||
vu1.state().vi[1] = 2;
|
||||
vu1.state().vi[2] = 4;
|
||||
vu1.state().vf[4][0] = 100.0f;
|
||||
vu1.state().vf[4][1] = 200.0f;
|
||||
vu1.state().vf[4][2] = 300.0f;
|
||||
vu1.state().vf[4][3] = 400.0f;
|
||||
|
||||
vu1.execute(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, fx.gs, &fx.mem, 0u, 0u, 0u, 2u);
|
||||
|
||||
t.Equals(vu1.state().vf[4][0], 100.0f, "LQ.yw should preserve x");
|
||||
t.Equals(vu1.state().vf[4][1], 20.0f, "LQ.yw should load y");
|
||||
t.Equals(vu1.state().vf[4][2], 300.0f, "LQ.yw should preserve z");
|
||||
t.Equals(vu1.state().vf[4][3], 40.0f, "LQ.yw should load w");
|
||||
|
||||
float stored[4] = {};
|
||||
readVuQword(fx.data, 5u, stored);
|
||||
t.Equals(stored[0], 100.0f, "SQ.xz should store x");
|
||||
t.Equals(stored[1], -2.0f, "SQ.xz should preserve y");
|
||||
t.Equals(stored[2], 300.0f, "SQ.xz should store z");
|
||||
t.Equals(stored[3], -4.0f, "SQ.xz should preserve w");
|
||||
});
|
||||
|
||||
tc.Run("integer lower ops keep VI0 hardwired to zero", [](TestCase &t)
|
||||
{
|
||||
Vu1Fixture fx;
|
||||
t.IsTrue(fx.initialize(), "VU1 fixture should initialize");
|
||||
|
||||
writeVuInstructionPair(fx.code, 0u, makeVuIaddiu(2u, 1u, 5), kVuUpperNop); // IADDIU vi2, vi1, 5
|
||||
writeVuInstructionPair(fx.code, 8u, makeVuIaddiu(0u, 2u, 7), kVuUpperNop); // IADDIU vi0, vi2, 7
|
||||
writeVuInstructionPair(fx.code, 16u, makeVuLowerDirect(0x30u, 2u, 1u, 3u), kVuUpperNop); // IADD vi3, vi2, vi1
|
||||
|
||||
VU1Interpreter vu1;
|
||||
vu1.state().vi[0] = 99;
|
||||
vu1.state().vi[1] = 10;
|
||||
|
||||
vu1.execute(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, fx.gs, &fx.mem, 0u, 0u, 0u, 3u);
|
||||
|
||||
t.Equals(vu1.state().vi[2], 15, "IADDIU should add signed immediate to VI source");
|
||||
t.Equals(vu1.state().vi[3], 25, "IADD should add VI source registers");
|
||||
t.Equals(vu1.state().vi[0], 0, "VI0 should remain hardwired to zero");
|
||||
});
|
||||
|
||||
tc.Run("XTOP and XITOP expose VIF TOP values to VI registers", [](TestCase &t)
|
||||
{
|
||||
Vu1Fixture fx;
|
||||
t.IsTrue(fx.initialize(), "VU1 fixture should initialize");
|
||||
|
||||
writeVuInstructionPair(fx.code, 0u, makeVuLowerSpecial(0x68u, 0u, 2u), kVuUpperNop); // XTOP vi2
|
||||
writeVuInstructionPair(fx.code, 8u, makeVuLowerSpecial(0x69u, 0u, 3u), kVuUpperNop); // XITOP vi3
|
||||
|
||||
VU1Interpreter vu1;
|
||||
vu1.execute(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, fx.gs, &fx.mem, 0u, 0x123u, 0x2ABu, 2u);
|
||||
|
||||
t.Equals(vu1.state().vi[2], 0x123, "XTOP should move TOP into the target VI register");
|
||||
t.Equals(vu1.state().vi[3], 0x2AB, "XITOP should move ITOP into the target VI register");
|
||||
});
|
||||
|
||||
tc.Run("lower branch commits after one delay-slot instruction", [](TestCase &t)
|
||||
{
|
||||
Vu1Fixture fx;
|
||||
t.IsTrue(fx.initialize(), "VU1 fixture should initialize");
|
||||
|
||||
writeVuInstructionPair(fx.code, 0u, makeVuBranch(2), kVuUpperNop); // target pc = 24
|
||||
writeVuInstructionPair(fx.code, 8u, makeVuIaddiu(1u, 0u, 1), kVuUpperNop); // delay slot
|
||||
writeVuInstructionPair(fx.code, 16u, makeVuIaddiu(2u, 0u, 99), kVuUpperNop); // skipped
|
||||
writeVuInstructionPair(fx.code, 24u, makeVuIaddiu(3u, 0u, 7), kVuUpperNop); // branch target
|
||||
|
||||
VU1Interpreter vu1;
|
||||
vu1.execute(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, fx.gs, &fx.mem, 0u, 0u, 0u, 3u);
|
||||
|
||||
t.Equals(vu1.state().vi[1], 1, "branch delay slot should execute");
|
||||
t.Equals(vu1.state().vi[2], 0, "instruction between delay slot and target should be skipped");
|
||||
t.Equals(vu1.state().vi[3], 7, "branch target should execute after the delay slot");
|
||||
});
|
||||
|
||||
tc.Run("lower side sees old VF value when upper writes the same register", [](TestCase &t)
|
||||
{
|
||||
Vu1Fixture fx;
|
||||
t.IsTrue(fx.initialize(), "VU1 fixture should initialize");
|
||||
|
||||
writeVuInstructionPair(fx.code,
|
||||
0u,
|
||||
makeVuSq(0xFu, 1u, 1u, 0), // SQ.xyzw vf1, 0(vi1)
|
||||
makeVuUpper(0x28u, 0xFu, 3u, 2u, 1u)); // ADD.xyzw vf1, vf2, vf3
|
||||
|
||||
VU1Interpreter vu1;
|
||||
vu1.state().vi[1] = 6;
|
||||
vu1.state().vf[1][0] = 1.0f;
|
||||
vu1.state().vf[1][1] = 2.0f;
|
||||
vu1.state().vf[1][2] = 3.0f;
|
||||
vu1.state().vf[1][3] = 4.0f;
|
||||
vu1.state().vf[2][0] = 10.0f;
|
||||
vu1.state().vf[2][1] = 20.0f;
|
||||
vu1.state().vf[2][2] = 30.0f;
|
||||
vu1.state().vf[2][3] = 40.0f;
|
||||
vu1.state().vf[3][0] = 100.0f;
|
||||
vu1.state().vf[3][1] = 200.0f;
|
||||
vu1.state().vf[3][2] = 300.0f;
|
||||
vu1.state().vf[3][3] = 400.0f;
|
||||
|
||||
vu1.execute(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, fx.gs, &fx.mem, 0u, 0u, 0u, 1u);
|
||||
|
||||
float stored[4] = {};
|
||||
readVuQword(fx.data, 6u, stored);
|
||||
t.Equals(stored[0], 1.0f, "SQ should observe old VF value for x");
|
||||
t.Equals(stored[1], 2.0f, "SQ should observe old VF value for y");
|
||||
t.Equals(stored[2], 3.0f, "SQ should observe old VF value for z");
|
||||
t.Equals(stored[3], 4.0f, "SQ should observe old VF value for w");
|
||||
t.Equals(vu1.state().vf[1][0], 110.0f, "upper ADD should write x after lower read");
|
||||
t.Equals(vu1.state().vf[1][1], 220.0f, "upper ADD should write y after lower read");
|
||||
t.Equals(vu1.state().vf[1][2], 330.0f, "upper ADD should write z after lower read");
|
||||
t.Equals(vu1.state().vf[1][3], 440.0f, "upper ADD should write w after lower read");
|
||||
});
|
||||
|
||||
tc.Run("DIV and SQRT update the Q register from selected vector components", [](TestCase &t)
|
||||
{
|
||||
Vu1Fixture fx;
|
||||
t.IsTrue(fx.initialize(), "VU1 fixture should initialize");
|
||||
|
||||
writeVuInstructionPair(fx.code, 0u, makeVuDiv(1u, 2u, 1u, 2u), kVuUpperNop); // Q = vf1.y / vf2.z
|
||||
writeVuInstructionPair(fx.code, 8u, makeVuSqrt(3u, 3u), kVuUpperNop); // Q = sqrt(abs(vf3.w))
|
||||
|
||||
VU1Interpreter vu1;
|
||||
vu1.state().vf[1][1] = 18.0f;
|
||||
vu1.state().vf[2][2] = 3.0f;
|
||||
vu1.state().vf[3][3] = 25.0f;
|
||||
|
||||
vu1.execute(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, fx.gs, &fx.mem, 0u, 0u, 0u, 1u);
|
||||
t.Equals(vu1.state().q, 6.0f, "DIV should divide selected FS and FT components into Q");
|
||||
|
||||
vu1.resume(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, fx.gs, &fx.mem, 0u, 0u, 1u);
|
||||
t.Equals(vu1.state().q, 5.0f, "SQRT should write square root of selected FT component into Q");
|
||||
});
|
||||
|
||||
tc.Run("MPG upload invalidates cached VU1 decode before MSCAL", [](TestCase &t)
|
||||
{
|
||||
Vu1Fixture fx;
|
||||
t.IsTrue(fx.initialize(), "VU1 fixture should initialize");
|
||||
|
||||
VU1Interpreter vu1;
|
||||
fx.mem.setVu1MscalCallback([&](uint32_t startPC, uint32_t top, uint32_t itop)
|
||||
{
|
||||
vu1.execute(fx.code,
|
||||
PS2_VU1_CODE_SIZE,
|
||||
fx.data,
|
||||
PS2_VU1_DATA_SIZE,
|
||||
fx.gs,
|
||||
&fx.mem,
|
||||
startPC,
|
||||
top,
|
||||
itop,
|
||||
1u);
|
||||
});
|
||||
|
||||
uploadVu1Mpg(fx.mem, 0u, makeVuIaddiu(1u, 0u, 1), kVuUpperNop);
|
||||
const uint32_t firstMscal = makeVifCmd(0x14u, 0u, 0u);
|
||||
fx.mem.processVIF1Data(reinterpret_cast<const uint8_t *>(&firstMscal), sizeof(firstMscal));
|
||||
t.Equals(vu1.state().vi[1], 1, "first MSCAL should execute the first uploaded program");
|
||||
|
||||
uploadVu1Mpg(fx.mem, 0u, makeVuIaddiu(1u, 0u, 2), kVuUpperNop);
|
||||
const uint32_t secondMscal = makeVifCmd(0x14u, 0u, 0u);
|
||||
fx.mem.processVIF1Data(reinterpret_cast<const uint8_t *>(&secondMscal), sizeof(secondMscal));
|
||||
t.Equals(vu1.state().vi[1], 2, "second MSCAL should see the MPG-updated instruction");
|
||||
});
|
||||
|
||||
tc.Run("direct VU1 code writes invalidate cached decode", [](TestCase &t)
|
||||
{
|
||||
Vu1Fixture fx;
|
||||
t.IsTrue(fx.initialize(), "VU1 fixture should initialize");
|
||||
|
||||
VU1Interpreter vu1;
|
||||
fx.mem.write64(PS2_VU1_CODE_BASE, packVuInstructionPair(makeVuIaddiu(1u, 0u, 1), kVuUpperNop));
|
||||
vu1.execute(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, fx.gs, &fx.mem, 0u, 0u, 0u, 1u);
|
||||
t.Equals(vu1.state().vi[1], 1, "first execution should use the original direct write");
|
||||
|
||||
fx.mem.write64(PS2_VU1_CODE_BASE, packVuInstructionPair(makeVuIaddiu(1u, 0u, 2), kVuUpperNop));
|
||||
vu1.execute(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, fx.gs, &fx.mem, 0u, 0u, 0u, 1u);
|
||||
t.Equals(vu1.state().vi[1], 2, "second execution should rebuild decode after the direct write");
|
||||
});
|
||||
|
||||
tc.Run("XGKICK sends a VU memory GIF packet through PATH1", [](TestCase &t)
|
||||
{
|
||||
PS2Memory mem;
|
||||
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");
|
||||
|
||||
std::vector<std::vector<uint8_t>> captured;
|
||||
mem.setGifPacketCallback([&](const uint8_t *data, uint32_t sizeBytes)
|
||||
{
|
||||
captured.emplace_back(data, data + sizeBytes);
|
||||
});
|
||||
|
||||
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
|
||||
GS gs;
|
||||
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
|
||||
|
||||
uint8_t *vuCode = mem.getVU1Code();
|
||||
uint8_t *vuData = mem.getVU1Data();
|
||||
std::memset(vuCode, 0, PS2_VU1_CODE_SIZE);
|
||||
std::memset(vuData, 0, PS2_VU1_DATA_SIZE);
|
||||
|
||||
constexpr uint32_t kLastQw = (PS2_VU1_DATA_SIZE / 16u) - 1u;
|
||||
const uint32_t tagOffset = kLastQw * 16u;
|
||||
|
||||
const uint64_t imageTag = makeGifTag(1u, GIF_FMT_IMAGE, 0u, true);
|
||||
std::memcpy(vuData + tagOffset, &imageTag, sizeof(imageTag));
|
||||
|
||||
for (uint32_t i = 0; i < 16u; ++i)
|
||||
{
|
||||
vuData[i] = static_cast<uint8_t>(0xC0u + i);
|
||||
}
|
||||
|
||||
const uint32_t lower = makeVuLowerSpecial(0x6Cu, 1u);
|
||||
std::memcpy(vuCode + 0u, &lower, sizeof(lower));
|
||||
const uint32_t upper = 0u;
|
||||
std::memcpy(vuCode + 4u, &upper, sizeof(upper));
|
||||
|
||||
VU1Interpreter vu1;
|
||||
vu1.state().vi[1] = static_cast<int32_t>(kLastQw);
|
||||
vu1.execute(vuCode,
|
||||
PS2_VU1_CODE_SIZE,
|
||||
vuData,
|
||||
PS2_VU1_DATA_SIZE,
|
||||
gs,
|
||||
&mem,
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
1u);
|
||||
|
||||
t.Equals(captured.size(), static_cast<size_t>(1u), "XGKICK should emit one wrapped GIF packet");
|
||||
if (!captured.empty())
|
||||
{
|
||||
t.Equals(captured[0].size(), static_cast<size_t>(32u), "wrapped packet should include tag plus one qword payload");
|
||||
bool payloadOk = true;
|
||||
for (uint32_t i = 0; i < 16u; ++i)
|
||||
{
|
||||
if (captured[0].size() < 32u || captured[0][16u + i] != static_cast<uint8_t>(0xC0u + i))
|
||||
{
|
||||
payloadOk = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
t.IsTrue(payloadOk, "wrapped payload should be copied from start of VU1 memory");
|
||||
}
|
||||
});
|
||||
|
||||
tc.Run("MSCAL can start a VU1 XGKICK program and update GS VRAM", [](TestCase &t)
|
||||
{
|
||||
PS2Memory mem;
|
||||
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");
|
||||
|
||||
GS gs;
|
||||
gs.init(mem.getGSVRAM(), static_cast<uint32_t>(PS2_GS_VRAM_SIZE), &mem.gs());
|
||||
GifArbiter arbiter([&](const uint8_t *data, uint32_t sizeBytes)
|
||||
{
|
||||
gs.processGIFPacket(data, sizeBytes);
|
||||
});
|
||||
mem.setGifArbiter(&arbiter);
|
||||
|
||||
const uint64_t bitblt =
|
||||
(static_cast<uint64_t>(0u) << 0) |
|
||||
(static_cast<uint64_t>(1u) << 16) |
|
||||
(static_cast<uint64_t>(0u) << 24) |
|
||||
(static_cast<uint64_t>(0u) << 32) |
|
||||
(static_cast<uint64_t>(1u) << 48) |
|
||||
(static_cast<uint64_t>(0u) << 56);
|
||||
gs.writeRegister(GS_REG_BITBLTBUF, bitblt);
|
||||
gs.writeRegister(GS_REG_TRXPOS, 0ull);
|
||||
gs.writeRegister(GS_REG_TRXREG, (4ull << 0) | (1ull << 32));
|
||||
gs.writeRegister(GS_REG_TRXDIR, 0ull);
|
||||
|
||||
uint8_t *vuCode = mem.getVU1Code();
|
||||
uint8_t *vuData = mem.getVU1Data();
|
||||
std::memset(vuCode, 0, PS2_VU1_CODE_SIZE);
|
||||
std::memset(vuData, 0, PS2_VU1_DATA_SIZE);
|
||||
|
||||
const uint32_t lower = makeVuLowerSpecial(0x6Cu, 0u);
|
||||
std::memcpy(vuCode + 0u, &lower, sizeof(lower));
|
||||
const uint32_t upper = 0u;
|
||||
std::memcpy(vuCode + 4u, &upper, sizeof(upper));
|
||||
|
||||
const uint64_t gifTag = makeGifTag(1u, GIF_FMT_IMAGE, 0u, true);
|
||||
std::memcpy(vuData + 0u, &gifTag, sizeof(gifTag));
|
||||
const uint64_t tagHi = 0u;
|
||||
std::memcpy(vuData + 8u, &tagHi, sizeof(tagHi));
|
||||
for (uint32_t i = 0; i < 16u; ++i)
|
||||
{
|
||||
vuData[16u + i] = static_cast<uint8_t>(0x90u + i);
|
||||
}
|
||||
|
||||
VU1Interpreter vu1;
|
||||
mem.setVu1MscalCallback([&](uint32_t startPC, uint32_t top, uint32_t itop)
|
||||
{
|
||||
vu1.execute(vuCode,
|
||||
PS2_VU1_CODE_SIZE,
|
||||
vuData,
|
||||
PS2_VU1_DATA_SIZE,
|
||||
gs,
|
||||
&mem,
|
||||
startPC,
|
||||
top,
|
||||
itop,
|
||||
1u);
|
||||
});
|
||||
|
||||
const uint32_t mscalCmd = makeVifCmd(0x14u, 0u, 0u);
|
||||
mem.processVIF1Data(reinterpret_cast<const uint8_t *>(&mscalCmd), sizeof(mscalCmd));
|
||||
|
||||
const uint8_t *vramOut = mem.getGSVRAM();
|
||||
bool imageOk = true;
|
||||
for (uint32_t x = 0; x < 4u && imageOk; ++x)
|
||||
{
|
||||
const uint32_t off = GSPSMCT32::addrPSMCT32(0u, 1u, x, 0u);
|
||||
for (uint32_t c = 0; c < 4u; ++c)
|
||||
{
|
||||
if (vramOut[off + c] != static_cast<uint8_t>(0x90u + x * 4u + c))
|
||||
{
|
||||
imageOk = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
t.IsTrue(imageOk, "MSCAL-triggered XGKICK should route PATH1 packet into GS VRAM");
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user