diff --git a/ps2xRuntime/include/runtime/ps2_vu1.h b/ps2xRuntime/include/runtime/ps2_vu1.h index 67c8184fd..0000ee35f 100644 --- a/ps2xRuntime/include/runtime/ps2_vu1.h +++ b/ps2xRuntime/include/runtime/ps2_vu1.h @@ -216,6 +216,11 @@ class VU1Interpreter std::array m_vfWritePipeline{}; std::array m_viWritePipeline{}; std::array m_accWritePipeline{}; + uint32_t m_flagPipelineMask = 0; + uint32_t m_storePipelineMask = 0; + uint32_t m_vfWritePipelineMask = 0; + uint32_t m_viWritePipelineMask = 0; + uint32_t m_accWritePipelineMask = 0; XgkickPipeline m_xgkick{}; std::array, 32> m_vfReady{}; std::array m_viReady{}; diff --git a/ps2xRuntime/src/lib/vu/ps2_vu1_core.cpp b/ps2xRuntime/src/lib/vu/ps2_vu1_core.cpp index bd75bba31..d5de242c6 100644 --- a/ps2xRuntime/src/lib/vu/ps2_vu1_core.cpp +++ b/ps2xRuntime/src/lib/vu/ps2_vu1_core.cpp @@ -5,6 +5,7 @@ #include "ps2_vu1_detail.h" #include +#include #include #include #include @@ -16,6 +17,17 @@ namespace { + template + uint32_t firstAvailablePipelineSlot(uint32_t occupied) + { + static_assert(Capacity > 0u && Capacity < 32u); + constexpr uint32_t allSlots = (1u << Capacity) - 1u; + const uint32_t available = ~occupied & allSlots; + return available != 0u + ? static_cast(std::countr_zero(available)) + : Capacity; + } + constexpr uint8_t laneForComponent(uint32_t component) { return static_cast(1u << (3u - component)); @@ -73,6 +85,11 @@ void VU1Interpreter::resetScheduler() m_vfWritePipeline = {}; m_viWritePipeline = {}; m_accWritePipeline = {}; + m_flagPipelineMask = 0u; + m_storePipelineMask = 0u; + m_vfWritePipelineMask = 0u; + m_viWritePipelineMask = 0u; + m_accWritePipelineMask = 0u; m_xgkick = {}; m_vfReady = {}; m_viReady = {}; @@ -514,21 +531,15 @@ void VU1Interpreter::updateFmacFlags(const uint8_t laneFlags[4], uint8_t dest, status |= flags; } - FlagPipelineEntry *entry = nullptr; - for (FlagPipelineEntry &candidate : m_flagPipeline) - { - if (!candidate.valid) - { - entry = &candidate; - break; - } - } - if (!entry) + const uint32_t slot = + firstAvailablePipelineSlot(m_flagPipelineMask); + if (slot == kMaxFlagEntries) { reportReservedInstruction(true, 0xFFFFFFFFu); return; } + FlagPipelineEntry *entry = &m_flagPipeline[slot]; *entry = {}; entry->valid = true; entry->issueCycle = m_cycle; @@ -538,6 +549,7 @@ void VU1Interpreter::updateFmacFlags(const uint8_t laneFlags[4], uint8_t dest, entry->extraSticky = extraSticky; entry->writesMac = true; entry->writesStatus = true; + m_flagPipelineMask |= 1u << slot; } void VU1Interpreter::applyFmacDest(float *dst, float *result, uint8_t dest) @@ -558,24 +570,26 @@ void VU1Interpreter::applyFmacDestAcc(float *result, uint8_t dest) void VU1Interpreter::queueFsset(uint16_t immediate) { - for (FlagPipelineEntry &entry : m_flagPipeline) + for (uint32_t slots = m_flagPipelineMask; slots != 0u; slots &= slots - 1u) { - if (entry.valid && entry.issueCycle == m_cycle) + FlagPipelineEntry &entry = m_flagPipeline[std::countr_zero(slots)]; + if (entry.issueCycle == m_cycle) entry.writesStatus = false; } - for (FlagPipelineEntry &entry : m_flagPipeline) + const uint32_t slot = + firstAvailablePipelineSlot(m_flagPipelineMask); + if (slot != kMaxFlagEntries) { - if (!entry.valid) - { - entry = {}; - entry.valid = true; - entry.issueCycle = m_cycle; - entry.readyCycle = m_cycle + kFmacLatency; - entry.status = static_cast(immediate) & 0xFC0u; - entry.writesSticky = true; - return; - } + FlagPipelineEntry &entry = m_flagPipeline[slot]; + entry = {}; + entry.valid = true; + entry.issueCycle = m_cycle; + entry.readyCycle = m_cycle + kFmacLatency; + entry.status = static_cast(immediate) & 0xFC0u; + entry.writesSticky = true; + m_flagPipelineMask |= 1u << slot; + return; } reportReservedInstruction(false, 0xFFFFFFFEu); } @@ -583,18 +597,19 @@ void VU1Interpreter::queueFsset(uint16_t immediate) void VU1Interpreter::queueClip(uint32_t clip) { m_workingClip = ((m_workingClip << 6) | (clip & 0x3Fu)) & 0xFFFFFFu; - for (FlagPipelineEntry &entry : m_flagPipeline) + const uint32_t slot = + firstAvailablePipelineSlot(m_flagPipelineMask); + if (slot != kMaxFlagEntries) { - if (!entry.valid) - { - entry = {}; - entry.valid = true; - entry.issueCycle = m_cycle; - entry.readyCycle = m_cycle + kFmacLatency; - entry.clip = m_workingClip; - entry.writesClip = true; - return; - } + FlagPipelineEntry &entry = m_flagPipeline[slot]; + entry = {}; + entry.valid = true; + entry.issueCycle = m_cycle; + entry.readyCycle = m_cycle + kFmacLatency; + entry.clip = m_workingClip; + entry.writesClip = true; + m_flagPipelineMask |= 1u << slot; + return; } reportReservedInstruction(true, 0xFFFFFFFDu); } @@ -602,23 +617,25 @@ void VU1Interpreter::queueClip(uint32_t clip) void VU1Interpreter::queueFcset(uint32_t clip) { m_workingClip = clip & 0xFFFFFFu; - for (FlagPipelineEntry &entry : m_flagPipeline) + for (uint32_t slots = m_flagPipelineMask; slots != 0u; slots &= slots - 1u) { - if (entry.valid && entry.issueCycle == m_cycle) + FlagPipelineEntry &entry = m_flagPipeline[std::countr_zero(slots)]; + if (entry.issueCycle == m_cycle) entry.writesClip = false; } - for (FlagPipelineEntry &entry : m_flagPipeline) + const uint32_t slot = + firstAvailablePipelineSlot(m_flagPipelineMask); + if (slot != kMaxFlagEntries) { - if (!entry.valid) - { - entry = {}; - entry.valid = true; - entry.issueCycle = m_cycle; - entry.readyCycle = m_cycle + kFmacLatency; - entry.clip = m_workingClip; - entry.writesClip = true; - return; - } + FlagPipelineEntry &entry = m_flagPipeline[slot]; + entry = {}; + entry.valid = true; + entry.issueCycle = m_cycle; + entry.readyCycle = m_cycle + kFmacLatency; + entry.clip = m_workingClip; + entry.writesClip = true; + m_flagPipelineMask |= 1u << slot; + return; } reportReservedInstruction(false, 0xFFFFFFFAu); } @@ -654,17 +671,19 @@ void VU1Interpreter::queueP(float value, uint32_t latency) void VU1Interpreter::queueStore(uint32_t address, const uint32_t words[4], uint8_t laneMask) { - for (PendingStore &store : m_storePipeline) + const uint32_t slot = + firstAvailablePipelineSlot(m_storePipelineMask); + if (slot != kMaxPendingStores) { - if (!store.valid) - { - store.valid = true; - store.readyCycle = m_cycle + 1u; - store.address = address; - store.laneMask = laneMask; - std::copy(words, words + 4, store.words.begin()); - return; - } + PendingStore &store = m_storePipeline[slot]; + store = {}; + store.valid = true; + store.readyCycle = m_cycle + 1u; + store.address = address; + store.laneMask = laneMask; + std::copy(words, words + 4, store.words.begin()); + m_storePipelineMask |= 1u << slot; + return; } reportReservedInstruction(false, 0xFFFFFFFCu); } @@ -674,24 +693,25 @@ void VU1Interpreter::queueVfWrite(uint8_t reg, uint8_t laneMask, { if (reg == 0u || laneMask == 0u) return; - for (PendingVfWrite &write : m_vfWritePipeline) + const uint32_t slot = + firstAvailablePipelineSlot(m_vfWritePipelineMask); + if (slot != kMaxPendingVfWrites) { - if (!write.valid) + PendingVfWrite &write = m_vfWritePipeline[slot]; + write = {}; + write.valid = true; + write.readyCycle = m_cycle + latency; + write.sequence = ++m_nextWriteSequence; + write.reg = reg; + write.laneMask = laneMask; + std::copy(value, value + 4, write.value.begin()); + for (uint32_t component = 0; component < 4u; ++component) { - write = {}; - write.valid = true; - write.readyCycle = m_cycle + latency; - write.sequence = ++m_nextWriteSequence; - write.reg = reg; - write.laneMask = laneMask; - std::copy(value, value + 4, write.value.begin()); - for (uint32_t component = 0; component < 4u; ++component) - { - if ((laneMask & laneForComponent(component)) != 0u) - m_vfLatestWrite[reg][component] = write.sequence; - } - return; + if ((laneMask & laneForComponent(component)) != 0u) + m_vfLatestWrite[reg][component] = write.sequence; } + m_vfWritePipelineMask |= 1u << slot; + return; } reportReservedInstruction(false, 0xFFFFFFF7u); } @@ -700,19 +720,20 @@ void VU1Interpreter::queueViWrite(uint8_t reg, int32_t value, uint32_t latency) { if (reg == 0u) return; - for (PendingViWrite &write : m_viWritePipeline) + const uint32_t slot = + firstAvailablePipelineSlot(m_viWritePipelineMask); + if (slot != kMaxPendingViWrites) { - if (!write.valid) - { - write = {}; - write.valid = true; - write.readyCycle = m_cycle + latency; - write.sequence = ++m_nextWriteSequence; - write.reg = reg; - write.value = value; - m_viLatestWrite[reg] = write.sequence; - return; - } + PendingViWrite &write = m_viWritePipeline[slot]; + write = {}; + write.valid = true; + write.readyCycle = m_cycle + latency; + write.sequence = ++m_nextWriteSequence; + write.reg = reg; + write.value = value; + m_viLatestWrite[reg] = write.sequence; + m_viWritePipelineMask |= 1u << slot; + return; } reportReservedInstruction(false, 0xFFFFFFF6u); } @@ -721,32 +742,35 @@ void VU1Interpreter::queueAccWrite(uint8_t laneMask, const float value[4], uint3 { if (laneMask == 0u) return; - for (PendingAccWrite &write : m_accWritePipeline) + const uint32_t slot = + firstAvailablePipelineSlot(m_accWritePipelineMask); + if (slot != kMaxPendingAccWrites) { - if (!write.valid) + PendingAccWrite &write = m_accWritePipeline[slot]; + write = {}; + write.valid = true; + write.readyCycle = m_cycle + latency; + write.sequence = ++m_nextWriteSequence; + write.laneMask = laneMask; + std::copy(value, value + 4, write.value.begin()); + for (uint32_t component = 0; component < 4u; ++component) { - write = {}; - write.valid = true; - write.readyCycle = m_cycle + latency; - write.sequence = ++m_nextWriteSequence; - write.laneMask = laneMask; - std::copy(value, value + 4, write.value.begin()); - for (uint32_t component = 0; component < 4u; ++component) - { - if ((laneMask & laneForComponent(component)) != 0u) - m_accLatestWrite[component] = write.sequence; - } - return; + if ((laneMask & laneForComponent(component)) != 0u) + m_accLatestWrite[component] = write.sequence; } + m_accWritePipelineMask |= 1u << slot; + return; } reportReservedInstruction(true, 0xFFFFFFF5u); } void VU1Interpreter::commitReadyPipelines() { - for (FlagPipelineEntry &entry : m_flagPipeline) + for (uint32_t slots = m_flagPipelineMask; slots != 0u; slots &= slots - 1u) { - if (!entry.valid || entry.readyCycle > m_cycle) + const uint32_t slot = static_cast(std::countr_zero(slots)); + FlagPipelineEntry &entry = m_flagPipeline[slot]; + if (entry.readyCycle > m_cycle) continue; if (entry.writesMac) @@ -762,7 +786,9 @@ void VU1Interpreter::commitReadyPipelines() } if (entry.writesClip) m_state.clip = entry.clip; - entry = {}; + // All fields are integer/bool zeros; avoid MSVC's aggregate temporary and copy. + std::memset(&entry, 0, sizeof(entry)); + m_flagPipelineMask &= ~(1u << slot); } if (m_fdiv.valid && m_fdiv.readyCycle <= m_cycle) @@ -782,9 +808,11 @@ void VU1Interpreter::commitReadyPipelines() } } - for (PendingStore &store : m_storePipeline) + for (uint32_t slots = m_storePipelineMask; slots != 0u; slots &= slots - 1u) { - if (!store.valid || store.readyCycle > m_cycle) + const uint32_t slot = static_cast(std::countr_zero(slots)); + PendingStore &store = m_storePipeline[slot]; + if (store.readyCycle > m_cycle) continue; if (m_activeVuData && store.address + 16u <= m_activeVuDataSize) { @@ -798,11 +826,14 @@ void VU1Interpreter::commitReadyPipelines() std::memcpy(m_activeVuData + store.address, oldWords, sizeof(oldWords)); } store = {}; + m_storePipelineMask &= ~(1u << slot); } - for (PendingVfWrite &write : m_vfWritePipeline) + for (uint32_t slots = m_vfWritePipelineMask; slots != 0u; slots &= slots - 1u) { - if (!write.valid || write.readyCycle > m_cycle) + const uint32_t slot = static_cast(std::countr_zero(slots)); + PendingVfWrite &write = m_vfWritePipeline[slot]; + if (write.readyCycle > m_cycle) continue; for (uint32_t component = 0; component < 4u; ++component) { @@ -813,20 +844,26 @@ void VU1Interpreter::commitReadyPipelines() } } write = {}; + m_vfWritePipelineMask &= ~(1u << slot); } - for (PendingViWrite &write : m_viWritePipeline) + for (uint32_t slots = m_viWritePipelineMask; slots != 0u; slots &= slots - 1u) { - if (!write.valid || write.readyCycle > m_cycle) + const uint32_t slot = static_cast(std::countr_zero(slots)); + PendingViWrite &write = m_viWritePipeline[slot]; + if (write.readyCycle > m_cycle) continue; if (m_viLatestWrite[write.reg] == write.sequence) m_state.vi[write.reg] = static_cast(write.value); write = {}; + m_viWritePipelineMask &= ~(1u << slot); } - for (PendingAccWrite &write : m_accWritePipeline) + for (uint32_t slots = m_accWritePipelineMask; slots != 0u; slots &= slots - 1u) { - if (!write.valid || write.readyCycle > m_cycle) + const uint32_t slot = static_cast(std::countr_zero(slots)); + PendingAccWrite &write = m_accWritePipeline[slot]; + if (write.readyCycle > m_cycle) continue; for (uint32_t component = 0; component < 4u; ++component) { @@ -837,6 +874,7 @@ void VU1Interpreter::commitReadyPipelines() } } write = {}; + m_accWritePipelineMask &= ~(1u << slot); } } @@ -962,22 +1000,11 @@ bool VU1Interpreter::pipelinesPending() const for (const ScalarPipelineEntry &entry : m_efu) if (entry.valid) return true; - for (const FlagPipelineEntry &entry : m_flagPipeline) - if (entry.valid) - return true; - for (const PendingStore &store : m_storePipeline) - if (store.valid) - return true; - for (const PendingVfWrite &write : m_vfWritePipeline) - if (write.valid) - return true; - for (const PendingViWrite &write : m_viWritePipeline) - if (write.valid) - return true; - for (const PendingAccWrite &write : m_accWritePipeline) - if (write.valid) - return true; - return false; + return m_flagPipelineMask != 0u || + m_storePipelineMask != 0u || + m_vfWritePipelineMask != 0u || + m_viWritePipelineMask != 0u || + m_accWritePipelineMask != 0u; } void VU1Interpreter::flushPipelines() diff --git a/ps2xTest/src/ps2_vu1_tests.cpp b/ps2xTest/src/ps2_vu1_tests.cpp index 0754b69fc..f811989b1 100644 --- a/ps2xTest/src/ps2_vu1_tests.cpp +++ b/ps2xTest/src/ps2_vu1_tests.cpp @@ -217,6 +217,96 @@ void register_ps2_vu1_tests() { MiniTest::Case("PS2VU1", [](TestCase &tc) { + tc.Run("flag and VF deadlines preserve slot reuse across single-cycle slices", [](TestCase &t) + { + Vu1Fixture fx; + t.IsTrue(fx.initialize(), "VU1 fixture should initialize"); + if (!fx.code || !fx.data) + return; + constexpr uint32_t pairs = 40u; + for (uint32_t i = 0; i < pairs + 8u; ++i) + { + const bool alternate = ((i / 8u) & 1u) != 0u; + const uint32_t lower = i >= pairs ? 0u : i % 3u == 0u + ? makeVuFlagImmediate(0x15u, 0u, static_cast((i & 15u) << 6u)) + : makeVuLowerSpecial(0x30u, alternate ? 1u : 2u, static_cast(20u + i % 8u), 0u, 0xFu); + const uint32_t upper = i >= pairs ? kVuUpperNop + : makeVuUpper(0x28u, 0xFu, 2u, alternate ? 2u : 1u, static_cast(4u + i % 8u)); + writeTrackedVuInstructionPair(fx, i * 8u, lower, upper); + } + VU1Interpreter vu; + float expected[32][4]{}; + expected[0][3] = 1.0f; + for (uint32_t lane = 0; lane < 4u; ++lane) + { + vu.state().vf[1][lane] = expected[1][lane] = static_cast(lane + 1u); + vu.state().vf[2][lane] = expected[2][lane] = static_cast((lane + 1u) * 10u); + } + uint32_t expectedStatus = 0u; + vu.execute(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, + fx.gs, &fx.mem, 0u, 0u, 0u, 0u); + for (uint32_t cycle = 1u; cycle <= pairs + 8u; ++cycle) + { + vu.resume(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, + fx.gs, &fx.mem, 0u, 0u, 0u); + t.Equals(vu.state().cycles, uint64_t{cycle - 1u}, "Zero budget must preserve the cycle"); + vu.resume(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, + fx.gs, &fx.mem, 0u, 0u, 1u); + if (cycle >= 4u && cycle - 4u < pairs) + { + const uint32_t retired = cycle - 4u; + const bool alternate = ((retired / 8u) & 1u) != 0u; + for (uint32_t lane = 0; lane < 4u; ++lane) + { + expected[4u + retired % 8u][lane] = static_cast((lane + 1u) * (alternate ? 20u : 11u)); + if (retired % 3u != 0u) + expected[20u + retired % 8u][lane] = expected[alternate ? 1u : 2u][lane]; + } + if (retired % 3u == 0u) + expectedStatus = (retired & 15u) << 6u; + } + t.Equals(vu.state().cycles, uint64_t{cycle}, "Each slice must advance one cycle"); + t.Equals(vu.state().pc, cycle * 8u, "Independent pairs must not stall"); + t.Equals(vu.state().status, expectedStatus, "Same-pair FSSET must win at its deadline"); + t.Equals(vu.state().mac, 0u, "Positive ADD results must preserve zero MAC flags"); + for (uint32_t reg = 0; reg < 32u; ++reg) + for (uint32_t lane = 0; lane < 4u; ++lane) + t.Equals(vu.state().vf[reg][lane], expected[reg][lane], "VF values must commit at their exact deadline"); + } + }); + + tc.Run("fresh execution and reset discard pending flag and VF deadlines", [](TestCase &t) + { + Vu1Fixture fx; + t.IsTrue(fx.initialize(), "VU1 fixture should initialize"); + if (!fx.code || !fx.data) + return; + writeTrackedVuInstructionPair(fx, 0u, makeVuFlagImmediate(0x15u, 0u, 0xFC0u), + makeVuUpper(0x28u, 0xFu, 2u, 1u, 3u)); + for (uint32_t pc = 8u; pc <= 128u; pc += 8u) + writeTrackedVuInstructionPair(fx, pc, 0u, kVuUpperNop); + VU1Interpreter vu; + for (uint32_t restart = 0; restart < 12u; ++restart) + { + vu.state().vf[1][0] = 1.0f; + vu.state().vf[2][0] = 2.0f; + vu.state().vf[3][0] = 123.0f; + vu.state().status = 0x80u; + vu.execute(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, + fx.gs, &fx.mem, 0u, 0u, 0u, 1u); + if ((restart & 1u) != 0u) + { + vu.reset(); + vu.state().vf[3][0] = 123.0f; + vu.state().status = 0x80u; + } + vu.execute(fx.code, PS2_VU1_CODE_SIZE, fx.data, PS2_VU1_DATA_SIZE, + fx.gs, &fx.mem, 8u, 0u, 0u, 12u); + t.Equals(vu.state().vf[3][0], 123.0f, "Cancelled VF writes must not reappear"); + t.Equals(vu.state().status, 0x80u, "Cancelled flags must not reappear"); + } + }); + tc.Run("upper ADD applies the destination mask", [](TestCase &t) { Vu1Fixture fx;