Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ps2xRuntime/include/runtime/ps2_vu1.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ class VU1Interpreter
void execUpper(uint32_t instr);
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 writeDestMasked(float *dst, const float *result, uint8_t dest);
void applyDestAcc(const float *result, uint8_t dest);
void computeFmacFlags(const float *result, uint8_t dest);
float broadcast(const float *vf, uint8_t bc);
};

Expand Down
69 changes: 67 additions & 2 deletions ps2xRuntime/src/lib/vu/ps2_vu1_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ float VU1Interpreter::broadcast(const float *vf, uint8_t bc)
return vf[bc & 3];
}

void VU1Interpreter::applyDest(float *dst, const float *result, uint8_t dest)
void VU1Interpreter::writeDestMasked(float *dst, const float *result, uint8_t dest)
{
if (dest & 0x8)
dst[0] = result[0]; // x
Expand All @@ -35,7 +35,72 @@ void VU1Interpreter::applyDest(float *dst, const float *result, uint8_t dest)

void VU1Interpreter::applyDestAcc(const float *result, uint8_t dest)
{
applyDest(m_state.acc, result, dest);
// ACC-writers are FMAC ops: MAC reflects the FMAC result whether it targets vf or acc.
computeFmacFlags(result, dest);
writeDestMasked(m_state.acc, result, dest);
}

// MAC bit layout sourced from PCSX2 pcsx2/VUflags.cpp (VU_MAC_UPDATE / VU_STAT_UPDATE):
// nibbles from low to high are Z[3:0], S[7:4], U[11:8], O[15:12]; within each nibble the
// bit order is x,y,z,w from high to low (x uses shift 3, y shift 2, z shift 1, w shift 0).
// Z is set for +/-0.0f or a denormal (an underflow PCSX2 flushes to zero, raising both U
// and Z together); S mirrors the IEEE754 sign bit unconditionally; U is a nonzero denormal;
// O is an exponent field of all ones (inf or NaN).
// Lanes outside DEST are not evaluated and read 0 in every group, matching PCSX2's per-lane
// VU_MACx/y/z/w_CLEAR calls for lanes the FMAC op did not write.
void VU1Interpreter::computeFmacFlags(const float *result, uint8_t dest)
{
static const uint8_t kLaneDestBit[4] = {0x8u, 0x4u, 0x2u, 0x1u}; // x, y, z, w
uint32_t mac = 0u;

for (int c = 0; c < 4; ++c)
{
if ((dest & kLaneDestBit[c]) == 0u)
continue;

uint32_t bits;
std::memcpy(&bits, &result[c], sizeof(bits));
const uint32_t exp = (bits >> 23) & 0xFFu;
const int shift = 3 - c;

if (bits & 0x80000000u)
mac |= (0x0010u << shift); // S

if (result[c] == 0.0f)
{
mac |= (0x0001u << shift); // Z
}
else if (exp == 0u)
{
mac |= (0x0101u << shift); // U + Z: a denormal is an underflow PCSX2 flushes to zero
}
else if (exp == 0xFFu)
{
mac |= (0x1000u << shift); // O
}
}

m_state.mac = mac;

// STATUS folds MAC the way PCSX2's VU_STAT_UPDATE does: a live bit is set if ANY dest
// lane raised that condition this instruction. The live half is bits[3:0] (Z,S,U,O in
// that order) and is replaced whole every FMAC; the sticky half is bits[9:6] and only
// ever accumulates (OR), matching how FSOR/FSAND read "has this ever happened". Bits
// 4,5,10,11 (I/D, live and sticky) are not modelled here and stay zero.
uint32_t live = 0u;
if (mac & 0x000Fu)
live |= 0x1u; // Z
if (mac & 0x00F0u)
live |= 0x2u; // S
if (mac & 0x0F00u)
live |= 0x4u; // U
if (mac & 0xF000u)
live |= 0x8u; // O

const uint32_t stickyPrev = (m_state.status >> 6) & 0xFu;
const uint32_t sticky = stickyPrev | live;

m_state.status = (live & 0x3Fu) | ((sticky & 0x3Fu) << 6);
}

VU1Interpreter::DecodedInstructionPair VU1Interpreter::decodeInstructionPair(const uint8_t *vuCode, uint32_t pc) const
Expand Down
39 changes: 26 additions & 13 deletions ps2xRuntime/src/lib/vu/ps2_vu1_lower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
{
float tmp[4];
std::memcpy(tmp, vuData + addr, 16);
applyDest(m_state.vf[it], tmp, dest);
writeDestMasked(m_state.vf[it], tmp, dest);
}
return;
}
Expand Down Expand Up @@ -164,7 +164,11 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
}
case 0x15: // FSSET
{
m_state.status = (instr >> 6) & 0xFC0;
// Immediate extraction sourced from PCSX2 pcsx2/VUops.cpp _vuFSSET:
// imm12 = ((instr>>21)&1)<<11 | (instr&0x7FF). FSSET only ever writes the sticky
// half (bits 11:6); the live half (bits 5:0) is left as whatever the last FMAC set.
uint32_t imm12 = (((instr >> 21) & 0x1u) << 11) | (instr & 0x7FFu);
m_state.status = (imm12 & 0xFC0u) | (m_state.status & 0x3Fu);
return;
}
case 0x16: // FSAND
Expand All @@ -181,30 +185,39 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
m_state.vi[1] = ((m_state.status | imm12) == 0xFFF) ? 1 : 0;
return;
}
case 0x18: // FMAND
case 0x18: // FMEQ
{
// Lower-op table sourced from PCSX2 pcsx2/VUops.cpp _LOWER_OPCODE[128]: 0x18=FMEQ,
// 0x19 unassigned, 0x1A=FMAND, 0x1B=FMOR, 0x1C=FCGET.
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]);
m_state.vi[it] = ((m_state.mac & 0xFFFF) == (uint32_t)(uint16_t)m_state.vi[is]) ? 1 : 0;
return;
}
case 0x1A: // FMEQ
case 0x1A: // FMAND
{
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;
m_state.vi[it] = (int32_t)(m_state.mac & (uint32_t)(uint16_t)m_state.vi[is]);
return;
}
case 0x1C: // FMOR
case 0x1B: // 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 0x1C: // FCGET
{
uint8_t it = VIT(instr);
if (it != 0)
m_state.vi[it] = (int32_t)(m_state.clip & 0xFFFu);
return;
}
case 0x20: // B (unconditional branch)
{
int16_t imm = IMM11(instr);
Expand Down Expand Up @@ -465,13 +478,13 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
{
float tmp[4];
std::memcpy(tmp, m_state.vf[vfS], 16);
applyDest(m_state.vf[vfT], tmp, dest);
writeDestMasked(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);
writeDestMasked(m_state.vf[vfT], tmp, dest);
return;
}
case 0x34: // LQI (Load Quadword, post-increment)
Expand All @@ -482,7 +495,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
{
float tmp[4];
std::memcpy(tmp, vuData + addr, 16);
applyDest(m_state.vf[vfT], tmp, dest);
writeDestMasked(m_state.vf[vfT], tmp, dest);
}
if (viS != 0)
m_state.vi[viS] = (int16_t)(m_state.vi[viS] + 1);
Expand Down Expand Up @@ -520,7 +533,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
{
float tmp[4];
std::memcpy(tmp, vuData + addr, 16);
applyDest(m_state.vf[vfT], tmp, dest);
writeDestMasked(m_state.vf[vfT], tmp, dest);
}
return;
}
Expand Down Expand Up @@ -604,7 +617,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
result[1] = result[0];
result[2] = result[0];
result[3] = result[0];
applyDest(m_state.vf[vfT], result, dest);
writeDestMasked(m_state.vf[vfT], result, dest);
return;
}
case 0x3E: // ILWR - integer load word from address in VI[is]
Expand Down Expand Up @@ -658,7 +671,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
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);
writeDestMasked(m_state.vf[vfT], result, dest);
return;
}
case 0x68: // XTOP - move current VIF1 TOP into VI register
Expand Down
Loading