mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
Tests: pin the VU div unit against the console
502 VU0 macro-mode cases of DIV, SQRT and RSQRT over zero, denormal, normal, exponent-255 and saturated operands, scored on all four engines. The three commits before this one each fixed one thing an engine had wrong; this holds the whole grid rather than the operands that happened to expose them. DIV earns its place by separating the two rules those commits turned on: its Invalid never comes from a sign, and its saturated quotient takes the xor RSQRT's cannot. An RSQRT-only table would fit either rule. Q is scored by class rather than by row, with exact per-engine tallies. The arithmetic gap under it is a separate piece of work, and this is where it gets a number to move. vu_rsqrt_divisor_sign_tests states the same rules on hand-picked witnesses, where they can be read instead of counted.
This commit is contained in:
@@ -143,6 +143,8 @@ add_pcsx2_test(recompiler_tests
|
||||
vu_madd_contract_console_tests.cpp
|
||||
vu1_efu_console_conformance_tests.cpp
|
||||
vu_sticky_console_conformance_tests.cpp
|
||||
vu_rsqrt_divisor_sign_tests.cpp
|
||||
vu_divunit_console_conformance_tests.cpp
|
||||
vu_branch_console_conformance_tests.cpp
|
||||
vu_pipeline_console_conformance_tests.cpp
|
||||
vu_memory_xgkick_console_conformance_tests.cpp
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,267 @@
|
||||
// SPDX-FileCopyrightText: 2026 yaps2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
// The VU div unit against a first-party console capture: VDIV, VSQRT and
|
||||
// VRSQRT over zero, denormal, normal, exponent-255 and saturated operands,
|
||||
// scoring STATUS and Q. 502 cases, table in autocases_vurs.h.
|
||||
//
|
||||
// The capture was taken in VU0 macro mode, where VU0's registers are
|
||||
// EE-readable, so the macro engines are scored against it directly and the
|
||||
// micro engines are scored on the D/I cause and sticky pair, which is the div
|
||||
// unit's own output rather than the mode's bookkeeping.
|
||||
//
|
||||
// What it settles, and what it does not:
|
||||
//
|
||||
// STATUS. One rule fits all 502 rows. I comes from the operand's SIGN BIT
|
||||
// for the two ops that contain a square root -- exponent field ignored, so
|
||||
// -0 and the negative denormals raise it -- decided ahead of the zero test
|
||||
// and independently of it. D comes from a zero divisor with a nonzero
|
||||
// dividend; a zero dividend over a zero divisor raises I instead, and the
|
||||
// two are exclusive. So VRSQRT over -0 is the one operand class where both
|
||||
// causes stand together: the root's I and the division's D.
|
||||
//
|
||||
// VDIV is the control that says the sign clause belongs to the square root
|
||||
// and not to the unit: a negative divisor raises nothing there. It is also
|
||||
// where the two ops' quotient signs part company -- VDIV's saturated
|
||||
// quotient takes the xor of the operand signs, VRSQRT's takes the dividend's
|
||||
// alone, because its divisor is a square root and never negative.
|
||||
//
|
||||
// Q. Not settled, and not asserted row by row. PCSX2 saturates a binade low
|
||||
// of the console (0x7F7FFFFF against 0x7FFFFFFF -- the VU clamp mode), and
|
||||
// its DIV/SQRT/RSQRT go through a host divide rather than through the EE's
|
||||
// divide-unit model in FPU.cpp. Both gaps are pinned here as exact tallies
|
||||
// per engine so that neither can move unnoticed, and so that whoever ports
|
||||
// eeDivide/eeSqrtBits to these call sites has a number to move.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "harness/EeRecTestHarness.h"
|
||||
#include "harness/MipsEncode.h"
|
||||
#include "harness/RecompilerTestEnvironment.h"
|
||||
#include "harness/VuEncode.h"
|
||||
#include "harness/VuTestHarness.h"
|
||||
|
||||
#include "VU.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "autocases_vurs.h"
|
||||
|
||||
using namespace console_vurs;
|
||||
|
||||
namespace recompiler_tests
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using namespace mips;
|
||||
using namespace mips::ee;
|
||||
|
||||
constexpr u32 kFs = 4, kFt = 5;
|
||||
constexpr u32 kRSeed = 20, kRStatus = 8, kRQ = 9;
|
||||
|
||||
// STATUS bits the div unit owns: the D/I cause pair and the two stickies they
|
||||
// set. The ZSUO cause is the FMAC's and is zero throughout the capture.
|
||||
constexpr u32 kDiMask = 0xC30u;
|
||||
|
||||
u32 MacroOp(const VursCase& c)
|
||||
{
|
||||
switch (c.op)
|
||||
{
|
||||
case VURS_DIV: return VDIV_C2(0, 0, kFs, kFt);
|
||||
case VURS_SQRT: return VSQRT_C2(0, kFt);
|
||||
default: return VRSQRT_C2(0, 0, kFs, kFt);
|
||||
}
|
||||
}
|
||||
|
||||
void BuildMacro(EeRecTestHarness& h, const VursCase& c)
|
||||
{
|
||||
h.EnableVu0Capture();
|
||||
h.SeedVu0VfBits(kFs, c.fs, c.fs, c.fs, c.fs);
|
||||
h.SeedVu0VfBits(kFt, c.ft, c.ft, c.ft, c.ft);
|
||||
h.LoadProgram(std::vector<u32>{
|
||||
ORI(kRSeed, 0, c.seed),
|
||||
CTC2(kRSeed, REG_STATUS_FLAG),
|
||||
MacroOp(c),
|
||||
CFC2(kRStatus, REG_STATUS_FLAG),
|
||||
CFC2(kRQ, REG_Q),
|
||||
});
|
||||
}
|
||||
|
||||
u32 MicroOp(const VursCase& c)
|
||||
{
|
||||
switch (c.op)
|
||||
{
|
||||
case VURS_DIV: return vu::VDIV_L(kFs, 0, kFt, 0);
|
||||
case VURS_SQRT: return vu::VSQRT_L(kFt, 0);
|
||||
default: return vu::VRSQRT_L(kFs, 0, kFt, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// The div unit's flags reach STATUS up to 13 cycles downstream in micro mode
|
||||
// (mVUanalyzeFDIV), so the program has to outrun that before either side can
|
||||
// be read. Micro mode has no CTC2, so only the unseeded rows run here.
|
||||
void BuildMicro(VuTestHarness& h, const VursCase& c)
|
||||
{
|
||||
h.SetVfBits(kFs, c.fs, c.fs, c.fs, c.fs);
|
||||
h.SetVfBits(kFt, c.ft, c.ft, c.ft, c.ft);
|
||||
std::vector<vu::VuOp> prog;
|
||||
prog.push_back(vu::VuOp{MicroOp(c), vu::VNOP_U()});
|
||||
for (int i = 0; i < 16; ++i)
|
||||
prog.push_back(vu::NopPair());
|
||||
prog.push_back(vu::VuOp{vu::VWAITQ_L(), vu::VNOP_U()});
|
||||
prog.push_back(vu::EBitNopPair());
|
||||
h.LoadProgram(prog);
|
||||
}
|
||||
|
||||
// Where an engine's Q lands relative to the console's.
|
||||
struct QTally
|
||||
{
|
||||
int ok = 0; // the console's word
|
||||
int sat = 0; // the console saturated and the engine saturated a binade low
|
||||
int unit = 0; // everything else: the divide unit's arithmetic
|
||||
};
|
||||
|
||||
void ScoreQ(QTally& t, const VursCase& c, u32 got)
|
||||
{
|
||||
if (got == c.q)
|
||||
t.ok++;
|
||||
else if ((c.q & 0x7FFFFFFFu) == 0x7FFFFFFFu && got == ((c.q & 0x80000000u) | 0x7F7FFFFFu))
|
||||
t.sat++;
|
||||
else
|
||||
t.unit++;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(VuDivUnitConsole, MacroStatusMatchesConsoleOnEveryRow)
|
||||
{
|
||||
int checked = 0;
|
||||
for (const VursCase& c : kVursCases)
|
||||
{
|
||||
SCOPED_TRACE(c.tag);
|
||||
|
||||
EeRecTestHarness hj;
|
||||
BuildMacro(hj, c);
|
||||
hj.RunJitNoDiff();
|
||||
EXPECT_EQ(hj.GetGprJit(kRStatus) & 0xFFFu, c.status) << "[macro jit] STATUS";
|
||||
|
||||
EeRecTestHarness hi;
|
||||
BuildMacro(hi, c);
|
||||
hi.RunInterpOnly();
|
||||
EXPECT_EQ(hi.GetGprInterp(kRStatus) & 0xFFFu, c.status) << "[macro interp] STATUS";
|
||||
++checked;
|
||||
}
|
||||
EXPECT_EQ(checked, static_cast<int>(std::size(kVursCases)));
|
||||
}
|
||||
|
||||
TEST(VuDivUnitConsole, MicroCauseAndStickyMatchConsole)
|
||||
{
|
||||
int checked = 0;
|
||||
for (const VursCase& c : kVursCases)
|
||||
{
|
||||
if (c.seed != 0)
|
||||
continue;
|
||||
SCOPED_TRACE(c.tag);
|
||||
|
||||
VuTestHarness m(0);
|
||||
m.IgnoreViInDiff(REG_Q); // scored separately, and it diverges by class
|
||||
BuildMicro(m, c);
|
||||
m.Run(); // also diffs micro JIT against micro interp
|
||||
EXPECT_EQ(m.GetViJit(REG_STATUS_FLAG) & kDiMask, c.status & kDiMask)
|
||||
<< "[micro jit] STATUS D/I";
|
||||
EXPECT_EQ(m.GetViInterp(REG_STATUS_FLAG) & kDiMask, c.status & kDiMask)
|
||||
<< "[micro interp] STATUS D/I";
|
||||
++checked;
|
||||
}
|
||||
EXPECT_EQ(checked, 422);
|
||||
}
|
||||
|
||||
// The console's saturated quotient is the EE maximum 0x7FFFFFFF; PCSX2's is
|
||||
// FLT_MAX, one binade lower. The sign is not part of that difference, so the
|
||||
// class is defined with the sign carried over -- which is what makes it a
|
||||
// statement about the clamp and not a place for a sign bug to hide.
|
||||
TEST(VuDivUnitConsole, QSaturatesABinadeLowOfTheConsole)
|
||||
{
|
||||
QTally mj, mi, uj, ui;
|
||||
int consoleSaturated = 0;
|
||||
for (const VursCase& c : kVursCases)
|
||||
{
|
||||
if ((c.q & 0x7FFFFFFFu) == 0x7FFFFFFFu)
|
||||
++consoleSaturated;
|
||||
|
||||
EeRecTestHarness hj;
|
||||
BuildMacro(hj, c);
|
||||
hj.RunJitNoDiff();
|
||||
ScoreQ(mj, c, hj.GetGprJit(kRQ));
|
||||
|
||||
EeRecTestHarness hi;
|
||||
BuildMacro(hi, c);
|
||||
hi.RunInterpOnly();
|
||||
ScoreQ(mi, c, hi.GetGprInterp(kRQ));
|
||||
|
||||
if (c.seed != 0)
|
||||
continue;
|
||||
VuTestHarness m(0);
|
||||
m.IgnoreViInDiff(REG_STATUS_FLAG);
|
||||
m.IgnoreViInDiff(REG_Q);
|
||||
BuildMicro(m, c);
|
||||
m.Run();
|
||||
ScoreQ(uj, c, m.GetViJit(REG_Q));
|
||||
ScoreQ(ui, c, m.GetViInterp(REG_Q));
|
||||
}
|
||||
|
||||
EXPECT_EQ(consoleSaturated, 226);
|
||||
|
||||
// Two rows saturate on the console and come back from the emulator as
|
||||
// something other than the sign-matched FLT_MAX; they fall in `unit`
|
||||
// below rather than being counted as clamp-mode misses.
|
||||
EXPECT_EQ(mj.sat, 224);
|
||||
EXPECT_EQ(mi.sat, 226);
|
||||
EXPECT_EQ(uj.sat, 176);
|
||||
EXPECT_EQ(ui.sat, 178);
|
||||
|
||||
// The arithmetic gap. The JIT is worse than the interpreter by 30 rows in
|
||||
// each mode -- its clamp runs before anything else can look at the result.
|
||||
EXPECT_EQ(mj.unit, 86);
|
||||
EXPECT_EQ(mi.unit, 56);
|
||||
EXPECT_EQ(uj.unit, 82);
|
||||
EXPECT_EQ(ui.unit, 52);
|
||||
|
||||
EXPECT_EQ(mj.ok, 192);
|
||||
EXPECT_EQ(mi.ok, 220);
|
||||
EXPECT_EQ(uj.ok, 164);
|
||||
EXPECT_EQ(ui.ok, 192);
|
||||
|
||||
EXPECT_EQ(mj.ok + mj.sat + mj.unit, static_cast<int>(std::size(kVursCases)));
|
||||
EXPECT_EQ(uj.ok + uj.sat + uj.unit, 422);
|
||||
}
|
||||
|
||||
// The two ops' saturated quotients take their sign by different rules, and the
|
||||
// capture pins both. VDIV xors the operand signs; VRSQRT cannot, because its
|
||||
// divisor is a square root. Stated on the smallest witnesses because the
|
||||
// tallies above would still pass if the two rules were swapped.
|
||||
TEST(VuDivUnitConsole, SaturatedQuotientSignsDifferBetweenDivAndRsqrt)
|
||||
{
|
||||
int div = 0, rsqrt = 0;
|
||||
for (const VursCase& c : kVursCases)
|
||||
{
|
||||
if ((c.q & 0x7FFFFFFFu) != 0x7FFFFFFFu || (c.ft & 0x7F800000u) != 0)
|
||||
continue;
|
||||
const u32 sign = c.q & 0x80000000u;
|
||||
if (c.op == VURS_DIV)
|
||||
{
|
||||
EXPECT_EQ(sign, (c.fs ^ c.ft) & 0x80000000u) << c.tag;
|
||||
++div;
|
||||
}
|
||||
else if (c.op == VURS_RSQRT)
|
||||
{
|
||||
EXPECT_EQ(sign, c.fs & 0x80000000u) << c.tag;
|
||||
++rsqrt;
|
||||
}
|
||||
}
|
||||
// 80 VDIV rows: the 8 zero-exponent divisors over all 10 dividends. 130
|
||||
// VRSQRT rows: the same 80, the seeded pass's 8 x 6, and the two rig-check
|
||||
// repeats of the row the earlier capture already held.
|
||||
EXPECT_EQ(div, 80);
|
||||
EXPECT_EQ(rsqrt, 130);
|
||||
}
|
||||
} // namespace recompiler_tests
|
||||
@@ -0,0 +1,264 @@
|
||||
// SPDX-FileCopyrightText: 2026 yaps2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
// The div unit's flag and quotient-sign rules on hand-picked witnesses, across
|
||||
// `_vuRSQRT`/`_vuSQRT`, `recCOP2_VRSQRT`/`recCOP2_VSQRT` and
|
||||
// `mVU_RSQRT`/`mVU_SQRT`. VuDivUnitConsole scores the same rules over a whole
|
||||
// operand grid; this is the readable form, and it names which engine had each
|
||||
// case wrong.
|
||||
//
|
||||
// Q's magnitude is the engines' 0x7F7FFFFF where the console gives 0x7FFFFFFF
|
||||
// -- the VU clamp mode, left alone here as it is there. The sign is not part
|
||||
// of that difference and is asserted against the console.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "harness/EeRecTestHarness.h"
|
||||
#include "harness/MipsEncode.h"
|
||||
#include "harness/RecompilerTestEnvironment.h"
|
||||
#include "harness/VuEncode.h"
|
||||
#include "harness/VuTestHarness.h"
|
||||
|
||||
#include "VU.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace recompiler_tests
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using namespace mips;
|
||||
using namespace mips::ee;
|
||||
using namespace vu;
|
||||
|
||||
inline VuOp LowerOnly(u32 lower) { return VuOp{lower, VNOP_U()}; }
|
||||
inline VuOp WaitQPair() { return VuOp{VWAITQ_L(), VNOP_U()}; }
|
||||
|
||||
constexpr u32 kFs = 4, kFt = 5;
|
||||
constexpr u32 kRStatus = 8, kRQ = 9;
|
||||
|
||||
// Cause D|I (0x30) plus the sticky pair they set (0xC00). Everything outside
|
||||
// this mask is the ZSUO cause, which no div-unit op touches.
|
||||
constexpr u32 kDiMask = 0xC30u;
|
||||
constexpr u32 kI = 0x410u; // cause I + sticky I
|
||||
constexpr u32 kD = 0x820u; // cause D + sticky D
|
||||
|
||||
struct Row
|
||||
{
|
||||
const char* what;
|
||||
u32 fs;
|
||||
u32 ft;
|
||||
u32 di; // expected STATUS & kDiMask
|
||||
u32 q;
|
||||
};
|
||||
|
||||
// Every row is checked on all four engines. 0/0 goes to its own test below.
|
||||
constexpr Row kRows[] = {
|
||||
// Under xor both quotients would come back with the opposite sign, so this
|
||||
// pair alone refutes it.
|
||||
{"+1 / -0", 0x3F800000u, 0x80000000u, kI | kD, 0x7F7FFFFFu},
|
||||
{"-1 / -0", 0xBF800000u, 0x80000000u, kI | kD, 0xFF7FFFFFu},
|
||||
|
||||
// Positive divisor, D alone: says the pair above moved for the sign bit
|
||||
// and not because the whole zero branch changed.
|
||||
{"+1 / +0", 0x3F800000u, 0x00000000u, kD, 0x7F7FFFFFu},
|
||||
{"-1 / +0", 0xBF800000u, 0x00000000u, kD, 0xFF7FFFFFu},
|
||||
|
||||
// Zero exponent, nonzero mantissa: the VU has no denormals, so this
|
||||
// reaches the zero branch too.
|
||||
{"+1 / -denorm", 0x3F800000u, 0x80000001u, kI | kD, 0x7F7FFFFFu},
|
||||
|
||||
// Nonzero divisors: the sign bit still decides I, and nothing raises D.
|
||||
{"+1 / -4", 0x3F800000u, 0xC0800000u, kI, 0x3F000000u},
|
||||
{"-1 / -4", 0xBF800000u, 0xC0800000u, kI, 0xBF000000u},
|
||||
{"+1 / +4", 0x3F800000u, 0x40800000u, 0, 0x3F000000u},
|
||||
};
|
||||
|
||||
void BuildMacro(EeRecTestHarness& h, u32 fs, u32 ft)
|
||||
{
|
||||
h.EnableVu0Capture();
|
||||
h.SeedVu0VfBits(kFs, fs, fs, fs, fs);
|
||||
h.SeedVu0VfBits(kFt, ft, ft, ft, ft);
|
||||
h.LoadProgram(std::vector<u32>{
|
||||
CTC2(0, REG_STATUS_FLAG), // clear the sticky field the prologue left
|
||||
VRSQRT_C2(0, 0, kFs, kFt),
|
||||
CFC2(kRStatus, REG_STATUS_FLAG),
|
||||
CFC2(kRQ, REG_Q),
|
||||
});
|
||||
}
|
||||
|
||||
// The FDIV flag reaches STATUS 13 cycles after an RSQRT (mVUanalyzeFDIV), so
|
||||
// the program has to run past that before the JIT side can be read.
|
||||
void BuildMicro(VuTestHarness& h, u32 fs, u32 ft)
|
||||
{
|
||||
h.SetVfBits(kFs, fs, fs, fs, fs);
|
||||
h.SetVfBits(kFt, ft, ft, ft, ft);
|
||||
std::vector<VuOp> prog;
|
||||
prog.push_back(LowerOnly(VRSQRT_L(kFs, 0, kFt, 0)));
|
||||
for (int i = 0; i < 16; ++i)
|
||||
prog.push_back(NopPair());
|
||||
prog.push_back(WaitQPair());
|
||||
prog.push_back(EBitNopPair());
|
||||
h.LoadProgram(prog);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// "+1 / -0" and "+1 / -denorm" were red on all three engines; the rest hold
|
||||
// the surrounding behaviour still.
|
||||
TEST(VuRsqrtDivisorSign, InvalidComesFromTheDivisorSignBitAlone)
|
||||
{
|
||||
for (const Row& r : kRows)
|
||||
{
|
||||
SCOPED_TRACE(r.what);
|
||||
|
||||
EeRecTestHarness hj;
|
||||
BuildMacro(hj, r.fs, r.ft);
|
||||
hj.RunJitNoDiff();
|
||||
EXPECT_EQ(hj.GetGprJit(kRStatus) & kDiMask, r.di) << "[macro jit] STATUS";
|
||||
|
||||
EeRecTestHarness hi;
|
||||
BuildMacro(hi, r.fs, r.ft);
|
||||
hi.RunInterpOnly();
|
||||
EXPECT_EQ(hi.GetGprInterp(kRStatus) & kDiMask, r.di) << "[macro interp] STATUS";
|
||||
|
||||
VuTestHarness m(0);
|
||||
BuildMicro(m, r.fs, r.ft);
|
||||
m.Run(); // also diffs micro JIT against micro interp
|
||||
EXPECT_EQ(m.GetViJit(REG_STATUS_FLAG) & kDiMask, r.di) << "[micro jit] STATUS";
|
||||
EXPECT_EQ(m.GetViInterp(REG_STATUS_FLAG) & kDiMask, r.di) << "[micro interp] STATUS";
|
||||
}
|
||||
}
|
||||
|
||||
// Asserted as the whole word so the magnitude is held still too, then as the
|
||||
// sign alone, which is the half the console can arbitrate.
|
||||
TEST(VuRsqrtDivisorSign, QuotientSignIsTheDividendsAlone)
|
||||
{
|
||||
for (const Row& r : kRows)
|
||||
{
|
||||
SCOPED_TRACE(r.what);
|
||||
|
||||
EeRecTestHarness hj;
|
||||
BuildMacro(hj, r.fs, r.ft);
|
||||
hj.RunJitNoDiff();
|
||||
EXPECT_EQ(hj.GetGprJit(kRQ), r.q) << "[macro jit] Q";
|
||||
|
||||
EeRecTestHarness hi;
|
||||
BuildMacro(hi, r.fs, r.ft);
|
||||
hi.RunInterpOnly();
|
||||
EXPECT_EQ(hi.GetGprInterp(kRQ), r.q) << "[macro interp] Q";
|
||||
|
||||
VuTestHarness m(0);
|
||||
BuildMicro(m, r.fs, r.ft);
|
||||
m.Run();
|
||||
EXPECT_EQ(m.GetViJit(REG_Q), r.q) << "[micro jit] Q";
|
||||
EXPECT_EQ(m.GetViInterp(REG_Q), r.q) << "[micro interp] Q";
|
||||
|
||||
EXPECT_EQ(r.q & 0x80000000u, r.fs & 0x80000000u) << "quotient sign is the dividend's";
|
||||
}
|
||||
}
|
||||
|
||||
// The row autocases_vusticky.h already held before the div-unit grid existed:
|
||||
// cause and sticky D|I together, and a positive quotient over a negative zero.
|
||||
TEST(VuRsqrtDivisorSign, MatchesTheConsoleRowForOneOverNegativeZero)
|
||||
{
|
||||
EeRecTestHarness h;
|
||||
BuildMacro(h, 0x3F800000u, 0x80000000u);
|
||||
h.RunJitNoDiff();
|
||||
EXPECT_EQ(h.GetGprJit(kRStatus) & kDiMask, 0xC30u);
|
||||
EXPECT_EQ(h.GetGprJit(kRQ) & 0x80000000u, 0x00000000u);
|
||||
EXPECT_EQ(0x7FFFFFFFu & 0x80000000u, h.GetGprJit(kRQ) & 0x80000000u)
|
||||
<< "console returned 0x7FFFFFFF; the magnitude is the VU clamp mode, the sign is not";
|
||||
}
|
||||
|
||||
// Three of the four engines used to raise both causes here and return ±0.
|
||||
TEST(VuRsqrtDivisorSign, ZeroOverZeroRaisesInvalidWithoutDivideByZero)
|
||||
{
|
||||
struct { const char* what; u32 fs; u32 ft; u32 q; } rows[] = {
|
||||
{"+0 / +0", 0x00000000u, 0x00000000u, 0x7F7FFFFFu},
|
||||
{"+0 / -0", 0x00000000u, 0x80000000u, 0x7F7FFFFFu},
|
||||
{"-0 / +0", 0x80000000u, 0x00000000u, 0xFF7FFFFFu},
|
||||
{"-0 / -0", 0x80000000u, 0x80000000u, 0xFF7FFFFFu},
|
||||
};
|
||||
|
||||
for (const auto& r : rows)
|
||||
{
|
||||
SCOPED_TRACE(r.what);
|
||||
|
||||
EeRecTestHarness hj;
|
||||
BuildMacro(hj, r.fs, r.ft);
|
||||
hj.RunJitNoDiff();
|
||||
EXPECT_EQ(hj.GetGprJit(kRStatus) & kDiMask, kI) << "[macro jit] STATUS";
|
||||
EXPECT_EQ(hj.GetGprJit(kRQ), r.q) << "[macro jit] Q";
|
||||
|
||||
EeRecTestHarness hi;
|
||||
BuildMacro(hi, r.fs, r.ft);
|
||||
hi.RunInterpOnly();
|
||||
EXPECT_EQ(hi.GetGprInterp(kRStatus) & kDiMask, kI) << "[macro interp] STATUS";
|
||||
EXPECT_EQ(hi.GetGprInterp(kRQ), r.q) << "[macro interp] Q";
|
||||
|
||||
VuTestHarness m(0);
|
||||
BuildMicro(m, r.fs, r.ft);
|
||||
m.Run();
|
||||
EXPECT_EQ(m.GetViJit(REG_STATUS_FLAG) & kDiMask, kI) << "[micro jit] STATUS";
|
||||
EXPECT_EQ(m.GetViJit(REG_Q), r.q) << "[micro jit] Q";
|
||||
EXPECT_EQ(m.GetViInterp(REG_STATUS_FLAG) & kDiMask, kI) << "[micro interp] STATUS";
|
||||
EXPECT_EQ(m.GetViInterp(REG_Q), r.q) << "[micro interp] Q";
|
||||
}
|
||||
}
|
||||
|
||||
// VSQRT shared the defect: only mVU_SQRT tested the sign bit, so the other
|
||||
// three lost I on -0 and on the denormals vuDouble flushes to it.
|
||||
TEST(VuRsqrtDivisorSign, SqrtInvalidComesFromTheSignBitToo)
|
||||
{
|
||||
struct { const char* what; u32 ft; u32 di; } rows[] = {
|
||||
{"sqrt -0", 0x80000000u, kI},
|
||||
{"sqrt -denorm.min", 0x80000001u, kI},
|
||||
{"sqrt -denorm.max", 0x807FFFFFu, kI},
|
||||
{"sqrt +0", 0x00000000u, 0},
|
||||
{"sqrt +denorm.max", 0x007FFFFFu, 0},
|
||||
{"sqrt -4", 0xC0800000u, kI},
|
||||
{"sqrt +4", 0x40800000u, 0},
|
||||
};
|
||||
|
||||
for (const auto& r : rows)
|
||||
{
|
||||
SCOPED_TRACE(r.what);
|
||||
|
||||
EeRecTestHarness hj;
|
||||
hj.EnableVu0Capture();
|
||||
hj.SeedVu0VfBits(kFt, r.ft, r.ft, r.ft, r.ft);
|
||||
hj.LoadProgram(std::vector<u32>{
|
||||
CTC2(0, REG_STATUS_FLAG),
|
||||
VSQRT_C2(0, kFt),
|
||||
CFC2(kRStatus, REG_STATUS_FLAG),
|
||||
});
|
||||
hj.RunJitNoDiff();
|
||||
EXPECT_EQ(hj.GetGprJit(kRStatus) & kDiMask, r.di) << "[macro jit] STATUS";
|
||||
|
||||
EeRecTestHarness hi;
|
||||
hi.EnableVu0Capture();
|
||||
hi.SeedVu0VfBits(kFt, r.ft, r.ft, r.ft, r.ft);
|
||||
hi.LoadProgram(std::vector<u32>{
|
||||
CTC2(0, REG_STATUS_FLAG),
|
||||
VSQRT_C2(0, kFt),
|
||||
CFC2(kRStatus, REG_STATUS_FLAG),
|
||||
});
|
||||
hi.RunInterpOnly();
|
||||
EXPECT_EQ(hi.GetGprInterp(kRStatus) & kDiMask, r.di) << "[macro interp] STATUS";
|
||||
|
||||
VuTestHarness m(0);
|
||||
m.SetVfBits(kFt, r.ft, r.ft, r.ft, r.ft);
|
||||
std::vector<VuOp> prog;
|
||||
prog.push_back(LowerOnly(VSQRT_L(kFt, 0)));
|
||||
for (int i = 0; i < 16; ++i)
|
||||
prog.push_back(NopPair());
|
||||
prog.push_back(WaitQPair());
|
||||
prog.push_back(EBitNopPair());
|
||||
m.LoadProgram(prog);
|
||||
m.Run();
|
||||
EXPECT_EQ(m.GetViJit(REG_STATUS_FLAG) & kDiMask, r.di) << "[micro jit] STATUS";
|
||||
EXPECT_EQ(m.GetViInterp(REG_STATUS_FLAG) & kDiMask, r.di) << "[micro interp] STATUS";
|
||||
}
|
||||
}
|
||||
} // namespace recompiler_tests
|
||||
|
||||
Reference in New Issue
Block a user