mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
DIV.S, SQRT.S and RSQRT.S applied a partial truncation law and fell through to a correctly rounded host divide, which left every operand the law did not reach one ULP away from the console. The unit is not a rounding rule at all: it is a radix-2 SRT digit recurrence, so eeDivide() and eeSqrtBits() now run the digits. What that recurrence is, and what it was measured against, is at eeSrtDigit(). The recurrence is not ours: it is PS2Float.cpp's Div() and Sqrt() from GitHubProUser67's proposed PCSX2 soft-float series, carried and since revised in the pcsx2-reliquary fork, whose only documentation is a DOI. The three ops are integer now, so ScopedDivRoundMode and eeISqrt48 go with them and FPUDivFPCR's rounding mode no longer reaches the interpreter. The divide-unit tests move with it. They were written around a model with a residue: the two tripwires are enabled, the console tables assert the console on every cell, and the engine differentials now pin the shape of the one-ULP divergence that is left rather than the region the old law settled.
1080 lines
42 KiB
C++
1080 lines
42 KiB
C++
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include "Common.h"
|
|
#include "Config.h"
|
|
|
|
#include "common/FPControl.h"
|
|
|
|
#include <cmath>
|
|
#include <cstring>
|
|
|
|
// Helper Macros
|
|
//****************************************************************
|
|
|
|
// IEEE 754 Values
|
|
#define PosInfinity 0x7f800000
|
|
#define NegInfinity 0xff800000
|
|
#define posFmax 0x7F7FFFFF
|
|
#define negFmax 0xFF7FFFFF
|
|
|
|
|
|
/* Used in compare function to compensate for differences between IEEE 754 and the FPU.
|
|
Setting it to ~0x00000000 = Compares Exact Value. (comment out this macro for faster Exact Compare method)
|
|
Setting it to ~0x00000001 = Discards the least significant bit when comparing.
|
|
Setting it to ~0x00000003 = Discards the least 2 significant bits when comparing... etc.. */
|
|
//#define comparePrecision ~0x00000001
|
|
|
|
// Operands
|
|
#define _Ft_ ( ( cpuRegs.code >> 16 ) & 0x1F )
|
|
#define _Fs_ ( ( cpuRegs.code >> 11 ) & 0x1F )
|
|
#define _Fd_ ( ( cpuRegs.code >> 6 ) & 0x1F )
|
|
|
|
// Floats
|
|
#define _FtValf_ fpuRegs.fpr[ _Ft_ ].f
|
|
#define _FsValf_ fpuRegs.fpr[ _Fs_ ].f
|
|
#define _FdValf_ fpuRegs.fpr[ _Fd_ ].f
|
|
#define _FAValf_ fpuRegs.ACC.f
|
|
|
|
// U32's
|
|
#define _FtValUl_ fpuRegs.fpr[ _Ft_ ].UL
|
|
#define _FsValUl_ fpuRegs.fpr[ _Fs_ ].UL
|
|
#define _FdValUl_ fpuRegs.fpr[ _Fd_ ].UL
|
|
#define _FAValUl_ fpuRegs.ACC.UL
|
|
|
|
// S32's - useful for ensuring sign extension when needed.
|
|
#define _FtValSl_ fpuRegs.fpr[ _Ft_ ].SL
|
|
#define _FsValSl_ fpuRegs.fpr[ _Fs_ ].SL
|
|
#define _FdValSl_ fpuRegs.fpr[ _Fd_ ].SL
|
|
#define _FAValSl_ fpuRegs.ACC.SL
|
|
|
|
// FPU Control Reg (FCR31)
|
|
#define _ContVal_ fpuRegs.fprc[ 31 ]
|
|
|
|
// FCR31 Flags
|
|
#define FPUflagC 0X00800000
|
|
#define FPUflagI 0X00020000
|
|
#define FPUflagD 0X00010000
|
|
#define FPUflagO 0X00008000
|
|
#define FPUflagU 0X00004000
|
|
#define FPUflagSI 0X00000040
|
|
#define FPUflagSD 0X00000020
|
|
#define FPUflagSO 0X00000010
|
|
#define FPUflagSU 0X00000008
|
|
|
|
//****************************************************************
|
|
|
|
/* The EE value of a raw FPR word, exactly, as a double.
|
|
|
|
The only way an operand enters this file: every arithmetic op, every flag
|
|
decision and every compare reads through here. Here 0x7F800000 is 2^128, an
|
|
ordinary number, and 0x7FFFFFFF is the largest one; a double holds the whole
|
|
EE range, so nothing is rewritten on the way in.
|
|
|
|
fpuDouble()/fpuOperandBits(), which this replaced, folded exponent 255 to
|
|
+-0x7F7FFFFF to fit a host single, so the op ran on a different operand than
|
|
the one it was given; 368 of the 1147 captured cases touch the top binade.
|
|
The arithmetic stopped reading them in ccae642180 and 4ce2b543cb, the
|
|
compares last.
|
|
|
|
Denormal operands flush to signed zero. The EE has none, and U is raised only
|
|
when the result computed from the flushed operands is nonzero and below the
|
|
smallest normal -- "mul 1.0, MIN_DENORM" returns +0 with FCR31 untouched
|
|
(autocases_fpuovf.h).
|
|
*/
|
|
static double eeToDouble(u32 f)
|
|
{
|
|
const u32 exp = (f >> 23) & 0xFF;
|
|
u64 bits = static_cast<u64>(f & 0x80000000u) << 32;
|
|
if (exp != 0)
|
|
{
|
|
bits |= (static_cast<u64>(exp) + (1023 - 127)) << 52;
|
|
bits |= static_cast<u64>(f & 0x007FFFFFu) << 29;
|
|
}
|
|
double d;
|
|
std::memcpy(&d, &bits, sizeof(d));
|
|
return d;
|
|
}
|
|
|
|
/* The ends of the EE's representable range.
|
|
0x7FFFFFFF == (2 - 2^-23) * 2^128, one binade above IEEE single's max,
|
|
because exponent 255 is an ordinary exponent on this FPU.
|
|
0x00800000 == 2^-126, the smallest normal; there is nothing below it.
|
|
*/
|
|
static constexpr double kEeFpuMax = 0x1.fffffep+128;
|
|
static constexpr double kEeMinNormal = 0x1p-126;
|
|
|
|
/* Fold a result the host produced into something the EE could hold: an
|
|
infinity to +/-fMax, a denormal to signed zero. This is what
|
|
checkOverflow()/checkUnderflow() did to the value, unchanged; the +/-FLT_MAX
|
|
saturation compromise in it is pinned by
|
|
EeFpuOverflowConsole.DefaultClampModeSaturatesToFltMaxOnBothEngines.
|
|
|
|
DIV.S, SQRT.S and RSQRT.S call this and no flag helper: they touch I and D
|
|
but must leave O and U as they found them.
|
|
*/
|
|
static void clampToEeRange(u32& xReg)
|
|
{
|
|
if ((xReg & ~0x80000000) == PosInfinity)
|
|
xReg = (xReg & 0x80000000) | posFmax;
|
|
else if (((xReg & 0x7F800000) == 0) && ((xReg & 0x007FFFFF) != 0))
|
|
xReg &= 0x80000000;
|
|
}
|
|
|
|
/* One rounding step's worth of FCR31 O/U maintenance, from the magnitude of
|
|
the exact result. `exact` is the step's result recomputed through
|
|
eeToDouble(), where nothing was clamped, rounded away or flushed.
|
|
|
|
checkOverflow()/checkUnderflow() used to ask instead whether xReg had come
|
|
back as a host infinity or a host denormal. Neither ever appears in the FP
|
|
environment the EE actually runs in: rounding toward zero makes an
|
|
overflowing multiply saturate to FLT_MAX, and FZ flushes an underflowing one
|
|
to zero. Both are the shipping default (Pcsx2Config.cpp), so O and U were
|
|
raised only under a rounding mode no game selects.
|
|
|
|
Both causes are cleared before either is set, so an overflow clears U, which
|
|
the old early-return structure did not: silicon returns O|SO|SU, U clear,
|
|
for MUL.S of +FLT_MAX by itself with U preset.
|
|
|
|
This rule and the two-step rule below reproduce O/U/SO/SU on all 674
|
|
arithmetic cases of the FP matrix corpus's console column.
|
|
*/
|
|
static void raiseOrClearOU(double exact)
|
|
{
|
|
_ContVal_ &= ~(FPUflagO | FPUflagU);
|
|
if (std::fabs(exact) > kEeFpuMax)
|
|
_ContVal_ |= FPUflagO | FPUflagSO;
|
|
else if (exact != 0.0 && std::fabs(exact) < kEeMinNormal)
|
|
_ContVal_ |= FPUflagU | FPUflagSU;
|
|
}
|
|
|
|
/* The multiply-accumulates round twice, so they raise twice: once on the
|
|
intermediate product, once on the accumulate. These two predicates are what
|
|
the product hands on to the second step.
|
|
|
|
An underflowing product is flushed to signed zero before the accumulate, so
|
|
the accumulate sees ACC and clears the cause U again, leaving the sticky SU
|
|
up. 68 cases in the capture come back with SU set and U clear; all are
|
|
multiply-accumulates and no plain MUL/MULA ever does, which is what says two
|
|
steps rather than one.
|
|
|
|
An overflowing product ends the instruction. Silicon saturates there and the
|
|
accumulate cannot bring it back: MADD of 2^128 by 2.0 onto an ACC of -2^128
|
|
returns +0x7FFFFFFF with O|SO, not the 2^128 the arithmetic says.
|
|
eeMulAccumulate() applies the same test to the value. The fast path has no
|
|
such test -- recMADD_S_xmm accumulates the raw product in the default clamp
|
|
mode -- so that corner is an engine divergence by design.
|
|
*/
|
|
static bool madAccumulandOverflowed(double product)
|
|
{
|
|
return std::fabs(product) > kEeFpuMax;
|
|
}
|
|
|
|
static double madFlushedProduct(double product)
|
|
{
|
|
if (product != 0.0 && std::fabs(product) < kEeMinNormal)
|
|
return std::copysign(0.0, product);
|
|
return product;
|
|
}
|
|
|
|
__fi u32 fp_max(u32 a, u32 b)
|
|
{
|
|
return ((s32)a < 0 && (s32)b < 0) ? std::min<s32>(a, b) : std::max<s32>(a, b);
|
|
}
|
|
|
|
__fi u32 fp_min(u32 a, u32 b)
|
|
{
|
|
return ((s32)a < 0 && (s32)b < 0) ? std::max<s32>(a, b) : std::min<s32>(a, b);
|
|
}
|
|
|
|
/* Checks if Divide by Zero will occur. (z/y = x)
|
|
cFlagsToSet1 = Flags to set if (z != 0)
|
|
cFlagsToSet2 = Flags to set if (z == 0)
|
|
( Denormals are counted as "0" )
|
|
*/
|
|
bool checkDivideByZero(u32& xReg, u32 yDivisorReg, u32 zDividendReg, u32 cFlagsToSet1, u32 cFlagsToSet2) {
|
|
|
|
if ( (yDivisorReg & 0x7F800000) == 0 ) {
|
|
_ContVal_ |= ( (zDividendReg & 0x7F800000) == 0 ) ? cFlagsToSet2 : cFlagsToSet1;
|
|
// Rows 38-43 of the overflow capture are all divide-by-zero: all
|
|
// 0x7FFFFFFF, the EE's maximum, and all signed with the xor of the two
|
|
// operands.
|
|
xReg = ( (yDivisorReg ^ zDividendReg) & 0x80000000 ) | 0x7FFFFFFF;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Clears the "Cause Flags" of the Control/Status Reg
|
|
The "EE Core Users Manual" implies that all the Cause flags are cleared every instruction...
|
|
But, the "EE Core Instruction Set Manual" says that only certain Cause Flags are cleared
|
|
for specific instructions... I'm just setting them to clear when the Instruction Set Manual
|
|
says to... (cottonvibes)
|
|
*/
|
|
#define clearFPUFlags(cFlags) { \
|
|
_ContVal_ &= ~( cFlags ) ; \
|
|
}
|
|
|
|
#ifdef comparePrecision
|
|
// This compare discards the least-significant bit(s) in order to solve some rounding issues.
|
|
#define C_cond_S(cond) { \
|
|
FPRreg tempA, tempB; \
|
|
tempA.UL = _FsValUl_ & comparePrecision; \
|
|
tempB.UL = _FtValUl_ & comparePrecision; \
|
|
_ContVal_ = ( ( tempA.f ) cond ( tempB.f ) ) ? \
|
|
( _ContVal_ | FPUflagC ) : \
|
|
( _ContVal_ & ~FPUflagC ); \
|
|
}
|
|
#else
|
|
/* Used for Comparing; This compares if the floats are exactly the same.
|
|
|
|
In doubles, which hold every EE value exactly. Host singles cannot be used
|
|
here: 0x7FFFFFFF is the EE's largest number and the same bits are a NaN to
|
|
the host, unordered against everything.
|
|
|
|
Both operands used to come through fpuDouble(), whose fold collapsed the
|
|
whole top binade onto one value: every operand from 0x7F800000 up compared
|
|
equal to 0x7F7FFFFF and less than nothing. Console rows 432/434 (c.eq.s of
|
|
0x7F7FFFFF and 0x7F800000 against 0x7FFFFFFF, both false on silicon) and
|
|
452/454 (the c.lt.s of the same pairs, both true) are the four the clamp
|
|
lost. */
|
|
#define C_cond_S(cond) { \
|
|
_ContVal_ = ( eeToDouble(_FsValUl_) cond eeToDouble(_FtValUl_) ) ? \
|
|
( _ContVal_ | FPUflagC ) : \
|
|
( _ContVal_ & ~FPUflagC ); \
|
|
}
|
|
#endif
|
|
|
|
// Conditional Branch
|
|
#define BC1(cond) \
|
|
if ( ( _ContVal_ & FPUflagC ) cond 0 ) { \
|
|
intDoBranch( _BranchTarget_ ); \
|
|
}
|
|
|
|
// Conditional Branch
|
|
#define BC1L(cond) \
|
|
if ( ( _ContVal_ & FPUflagC ) cond 0 ) { \
|
|
intDoBranch( _BranchTarget_ ); \
|
|
} else cpuRegs.pc += 4;
|
|
|
|
namespace R5900 {
|
|
namespace Interpreter {
|
|
namespace OpcodeImpl {
|
|
namespace COP1 {
|
|
|
|
//****************************************************************
|
|
// FPU Opcodes
|
|
//****************************************************************
|
|
|
|
/* fpuDouble() and fpuOperandBits() lived here.
|
|
|
|
They were the operand model: exponent 0 to signed zero, which is right,
|
|
this FPU has no denormals; and exponent 255 folded to +-0x7F7FFFFF, which
|
|
was the largest single source of divergence from the console in the whole
|
|
capture. On the EE exponent 255 is an ordinary binade: 0x7F800000 is 2^128
|
|
and 0x7FFFFFFF is the largest number there is. Folding those operands
|
|
destroyed information before the op ever ran; 368 of the 1147 captured cases
|
|
touch the top binade.
|
|
|
|
The clamp came off the arithmetic first (ccae642180, 4ce2b543cb), which left
|
|
the compares as its last user on the grounds that clamped and unclamped
|
|
compares agree except where both operands are in the top binade. One is
|
|
enough: it only has to collide with an unclamped 0x7F7FFFFF, and four
|
|
captured rows do. See C_cond_S above. The arithmetic reads eeToDouble(),
|
|
which clamps nothing, and rounds once through eeRoundToSingle().
|
|
|
|
SQRT_S was the other caller with work to do on exponent 255, and does it
|
|
itself now: eeSqrtBits() works in integers, so nothing has to be clamped to
|
|
keep the operand in a host single. */
|
|
|
|
/* Round an exact result into the EE encoding. This is the only rounding step
|
|
the arithmetic ops below perform.
|
|
|
|
Three things a bare (float) cast does not do:
|
|
|
|
* Saturation is at the EE's maximum, 0x7FFFFFFF, not at FLT_MAX. On
|
|
silicon, add.s of +2^128 to itself is 2^129, out of range, and comes
|
|
back 0x7FFFFFFF; add.s of 0x7F7FFFFF to itself is exactly 0x7FFFFFFF and
|
|
comes back unrounded. Folding either to 0x7F7FFFFF, which is what
|
|
clampToEeRange did to a host infinity, is a whole binade short.
|
|
* The top binade has no host single. Anything at or above 2^126 is rounded
|
|
through a scaled-down copy and its exponent put back afterwards. Scaling
|
|
by a power of two is exact and leaves the mantissa alone, so the rounding
|
|
decision is bit-identical to the one the host would have made if float
|
|
had the range -- the same reason SQRT.S can compute sqrt(|Ft|/4)*2.
|
|
* Underflowing results are not all flushed; `addsub` picks the rule. See
|
|
the block below.
|
|
|
|
The (float) casts round under the host FPCR, so the EE's rounding mode is
|
|
honoured here without naming it -- including the divide/sqrt unit's separate
|
|
mode, which its callers scope in.
|
|
*/
|
|
/* Underflow: a result strictly below 2^-126 and not zero is not always
|
|
flushed. The add/sub family leaves the mantissa bits where normalisation put
|
|
them and forces the exponent field to 0; MUL and DIV clear them and return
|
|
signed zero. That is the raw output of an adder with no denormal path:
|
|
`exact` is a double 1.m * 2^E, and what comes out is m's top 23 bits, i.e.
|
|
bits [51:29] of the double, with the exponent thrown away. It is not the
|
|
arithmetic answer, only its bits, and no rounding mode produces it.
|
|
|
|
The console rows this reproduces, how they were sampled and how they rule
|
|
out flushing and the true denormal value, are in
|
|
tests/ctest/core/recompilers/ee_fpu_underflow_console_tests.cpp.
|
|
*/
|
|
static u32 eeRoundToSingle(double exact, bool addsub = false)
|
|
{
|
|
const double mag = std::fabs(exact);
|
|
|
|
if (mag > kEeFpuMax)
|
|
return (std::signbit(exact) ? 0x80000000u : 0u) | 0x7FFFFFFFu;
|
|
|
|
if (mag < kEeMinNormal)
|
|
{
|
|
/* Ahead of the (float) cast, so the answer does not depend on the
|
|
ambient FPCR having FZ set and nothing can round up out of the
|
|
region -- the console returns +0 for a product of 2^-126 - 2^-150,
|
|
which is nearer 2^-126 than to zero. */
|
|
const u32 sign = std::signbit(exact) ? 0x80000000u : 0u;
|
|
if (!addsub || exact == 0.0)
|
|
return sign;
|
|
|
|
u64 bits;
|
|
std::memcpy(&bits, &exact, sizeof(bits));
|
|
return sign | static_cast<u32>((bits >> 29) & 0x7FFFFFu);
|
|
}
|
|
|
|
FPRreg r;
|
|
if (mag >= 0x1p126)
|
|
{
|
|
/* Scale down by 2^4, round there, then add the 4 exponents back. The
|
|
scaled exponent field is at most 251, so the +4 cannot carry into
|
|
the sign, and the >= 2^126 floor keeps the scaled value normal, so
|
|
nothing is flushed on the way through. */
|
|
r.f = static_cast<float>(exact * 0x1p-4);
|
|
r.UL += 4u << 23;
|
|
return r.UL;
|
|
}
|
|
|
|
r.f = static_cast<float>(exact);
|
|
return r.UL;
|
|
}
|
|
|
|
/* The EE FPU's adder carries no guard bits to the right of the mantissa. A
|
|
compliant adder shifts the smaller operand right into extra bits it keeps for
|
|
the rounding decision; whatever shifts past this one's mantissa is gone.
|
|
Subtraction -- and addition of unlike signs -- can then renormalise left and
|
|
pull the hole up into the result, landing one ULP toward zero from the IEEE
|
|
answer:
|
|
|
|
sub.s 0x00800000, 0x3F000000 -> console BF000000, plain IEEE BEFFFFFF
|
|
|
|
The model is the exponent difference, which is how far the smaller operand
|
|
gets shifted: it loses its low (|diff| - 1) mantissa bits, and past 24 it has
|
|
nothing left but its sign. |diff| <= 1 masks nothing.
|
|
|
|
Ported from x86 FPU_ADD_SUB (x86/iFPU.cpp) and, on arm64, fpuEmitGuardedAddSub
|
|
(iFPU-arm64.cpp, the single-precision fast path) and FPU_ADD_SUB
|
|
(iFPUd-arm64.cpp, the Full-clamp DOUBLE path).
|
|
|
|
Both recompilers gate the masking on CHECK_FPU_GUARDED, the fpuGuardedAddSub
|
|
INI bool, so an EE-FPU-heavy title can buy back one op per ADD.S/SUB.S. The
|
|
interpreter does not read it: its target is the console, not the recompiler's
|
|
speed. With fpuGuardedAddSub=false the engines therefore disagree on exactly
|
|
these cases, which EeRecFpuGuardBit.GuardOffDivergesFromInterpreterByDesign
|
|
pins.
|
|
|
|
The console rows this reproduces, with their corpus ordinals, are tabulated in
|
|
tests/ctest/core/recompilers/ee_fpu_guarded_addsub_console_tests.cpp.
|
|
*/
|
|
static void fpuGuardMask(u32& a, u32& b)
|
|
{
|
|
const s32 diff = (s32)((a >> 23) & 0xFF) - (s32)((b >> 23) & 0xFF);
|
|
|
|
if (diff >= 25)
|
|
b &= 0x80000000;
|
|
else if (diff >= 2)
|
|
b &= 0xffffffffu << (diff - 1);
|
|
else if (diff <= -25)
|
|
a &= 0x80000000;
|
|
else if (diff <= -2)
|
|
a &= 0xffffffffu << (-diff - 1);
|
|
}
|
|
|
|
/* The EE's adder: mask the guard bits away, add exactly, round once.
|
|
|
|
The mask is what makes the add exact. Within 24 exponents the sum needs 48
|
|
bits of the double's 53; beyond that the mask has already reduced the
|
|
smaller operand to +-0. So eeRoundToSingle() below is the only rounding, as
|
|
on the hardware.
|
|
|
|
Subtraction is addition of the negated operand, as IEEE defines it: that gets
|
|
the zero signs right, including for a masked +-0. */
|
|
static u32 eeGuardedAddSub(u32 a, u32 b, bool issub)
|
|
{
|
|
fpuGuardMask(a, b);
|
|
if (issub)
|
|
b ^= 0x80000000;
|
|
return eeRoundToSingle(eeToDouble(a) + eeToDouble(b), true);
|
|
}
|
|
|
|
/* The EE's divide/square-root unit, digit by digit.
|
|
|
|
It is not a correctly rounding divider, and no rounding mode makes it one.
|
|
The exact quotient lies between two singles and silicon returns one of them,
|
|
but which one is decided by where a digit recurrence lands, not by how far the
|
|
exact value sits from either end. So the error reaches nearly a whole ULP in
|
|
both directions on the same divisor, and two operand pairs agreeing in every
|
|
coordinate a rounding rule can see -- branch, divisor, remainder, the exact
|
|
fraction -- can still go opposite ways.
|
|
|
|
It is a radix-2 SRT digit recurrence over the signed digit set {-1, 0, +1},
|
|
partial remainder carried in redundant carry-save form, 24 selections
|
|
producing 25 digits, and no rounding step anywhere in it: no round bit, no
|
|
sticky, no final correction. SQRT.S is the same recurrence with the root so
|
|
far fed back in place of the divisor, and RSQRT.S is SQRT.S followed by DIV.S
|
|
with an ordinary 24-bit single in between, which is what silicon does.
|
|
|
|
The model is the selection function eeSrtDigit() below.
|
|
|
|
* DIV.S -- 150,994,944 rows. Eighteen divisors swept exhaustively, every
|
|
one of the 2^23 numerator significands at each, both branches, including
|
|
the seven degenerate divisors that broke every earlier frame. Zero
|
|
result-word disagreements, and zero rows where the recurrence lands
|
|
outside {T, T+1}. Through the same scorer, truncation gets 52,434,906 of
|
|
those rows wrong and round-to-nearest 29,430,553.
|
|
* SQRT.S -- 16,777,216 rows, every significand at both exponent parities.
|
|
Zero disagreements; truncation 3,994,228 and round-to-nearest 4,395,034.
|
|
* A 3000-operand holdout (seed 0x243F6A88, captured before any of this was
|
|
modelled): 2000 div and 1000 sqrt, zero disagreements. 229 of the div
|
|
rows saturate or flush, which the exhaustive sweeps cannot see: they hold
|
|
both exponents at 127.
|
|
|
|
Nothing else moves the result. FCR31 has no rounding-mode field on this FPU
|
|
-- bit 0 reads 1 whatever is written and bit 1 never sticks -- and the answer
|
|
is unchanged when the preceding instruction is varied or the case list
|
|
reversed. What that leaves EmuConfig.Cpu.FPUDivFPCR to do is in the block
|
|
below eeSqrtBits().
|
|
|
|
This is PS2Float.cpp's Div() and Sqrt() from the proposed PCSX2 soft-float
|
|
series (GitHubProUser67), whose only documentation is M. Prabhu and
|
|
G. Zyner, "167 MHz radix-8 divide and square root using overlapped
|
|
radix-2 stages", DOI 10.1109/ARITH.1995.465363.
|
|
*/
|
|
|
|
/* The partial remainder, in the redundant form the recurrence carries it in.
|
|
No step propagates a carry across the width of the operand, which is why the
|
|
selector below cannot see the true remainder. */
|
|
struct EeSrtRemainder
|
|
{
|
|
u32 sum, carry;
|
|
};
|
|
|
|
static __fi EeSrtRemainder eeSrtCarrySave(u32 a, u32 b, u32 c)
|
|
{
|
|
const u32 u = a ^ b;
|
|
const u32 h = (a & b) | (u & c);
|
|
return {u ^ c, h << 1};
|
|
}
|
|
|
|
/* The selection function: which of -1, 0, +1 the next digit takes.
|
|
|
|
It assimilates the redundant remainder only partially -- the carry word is
|
|
added in above bit 23 while the low 24 bits of the sum are OR-ed back rather
|
|
than added -- so it decides on something other than the remainder, and picks a
|
|
different digit from the one an exact comparison would. The thresholds are
|
|
+2^23 and -2^24, with the binary point between bits 24 and 25; that asymmetry
|
|
is what biases the unit toward truncation. The digit set is redundant, so the
|
|
last digit can still be -1 and the result reach T+1 on rows a truncation could
|
|
never reach. */
|
|
static __fi s32 eeSrtDigit(EeSrtRemainder r)
|
|
{
|
|
constexpr u32 mask = (1u << 24) - 1u;
|
|
const s32 estimate = (s32)(((r.sum & ~mask) + r.carry) | (r.sum & mask));
|
|
return (estimate >= (1 << 23)) - (estimate < (s32)(~0u << 24));
|
|
}
|
|
|
|
/* On a zero digit the next digit is selected from the un-recompressed pair
|
|
while the state advances with the recompressed one, so selection and state
|
|
see different splittings of the same value. Drop the distinction and the
|
|
model stops reproducing silicon. */
|
|
static __fi EeSrtRemainder eeSrtSelect(EeSrtRemainder cur, EeSrtRemainder next, s32 digit)
|
|
{
|
|
const u32 m = 0u - (u32)(digit != 0);
|
|
return {(cur.sum & ~m) | (next.sum & m), (cur.carry & ~m) | (next.carry & m)};
|
|
}
|
|
|
|
/* The quotient of two 24-bit significands as 25 digits, weights 2^24 down to
|
|
2^0. A positive digit subtracts the divisor as ~divisor with the +1 fed into
|
|
the carry word, which the selector then sees -- that is one of the places
|
|
the estimate and the state come apart. The value returned is 25 bits when
|
|
sma >= smb and 24 bits when it is not; the caller normalises, and the digit
|
|
that falls off the bottom there is simply dropped. */
|
|
static u32 eeDivideSignificand(u32 sma, u32 smb)
|
|
{
|
|
const u32 divisor = smb << 2;
|
|
EeSrtRemainder rem = {sma << 2, 0};
|
|
u32 quotient = 0;
|
|
s32 digit = 1;
|
|
|
|
for (int i = 0; i < 24; ++i)
|
|
{
|
|
quotient = (quotient << 1) + (u32)digit;
|
|
const u32 addend = (digit > 0) ? ~divisor : ((digit < 0) ? divisor : 0u);
|
|
rem.carry += (u32)(digit > 0);
|
|
const EeSrtRemainder next = eeSrtCarrySave(rem.sum, rem.carry, addend);
|
|
digit = eeSrtDigit(eeSrtSelect(rem, next, digit));
|
|
rem.sum = next.sum << 1;
|
|
rem.carry = next.carry << 1;
|
|
}
|
|
return (quotient << 1) + (u32)digit;
|
|
}
|
|
|
|
static u32 eeDivide(u32 a, u32 b)
|
|
{
|
|
const s32 ea = (s32)((a >> 23) & 0xFF);
|
|
const s32 eb = (s32)((b >> 23) & 0xFF);
|
|
const u32 sign = (a ^ b) & 0x80000000u;
|
|
|
|
if (ea == 0)
|
|
return sign; // zero dividend (denormals are zero), sign from both operands
|
|
|
|
// Exponent 255 is an ordinary binade on this FPU, so every finite operand
|
|
// reaches here and the hidden bit is always present. The divisor is already
|
|
// known nonzero: that is a flag question the callers answer first.
|
|
u32 quotient = eeDivideSignificand(0x800000u | (a & 0x7FFFFFu), 0x800000u | (b & 0x7FFFFFu));
|
|
s32 e = ea - eb + 126;
|
|
if (quotient >= (1u << 24))
|
|
{
|
|
quotient >>= 1;
|
|
++e;
|
|
}
|
|
|
|
// No carry out of the significand is possible below this point -- there is
|
|
// no rounding step left that could walk the quotient into the next binade.
|
|
if (e > 255)
|
|
return sign | 0x7FFFFFFFu; // the EE's maximum, not FLT_MAX
|
|
if (e < 1)
|
|
return sign; // the EE has no denormals to underflow into
|
|
return sign | ((u32)e << 23) | (quotient & 0x7FFFFFu);
|
|
}
|
|
|
|
/* sqrt(|Ft|) as EE bits, including the top binade.
|
|
|
|
The same recurrence as eeDivideSignificand(), with the root so far fed back
|
|
in place of a fixed divisor: adding digit d at weight w to the root adds
|
|
d*(2*root + d*w) to its square, which is what has to leave the partial
|
|
remainder, so the addend is rebuilt each step instead of being a constant.
|
|
Everything else -- the carry-save state, the selector, the zero-digit quirk,
|
|
the absence of any rounding step -- is shared, and so is the evidence: see
|
|
the block comment above eeSrtDigit().
|
|
|
|
The radicand is placed so the root is a 24-bit integer. With E the exponent
|
|
field, the significand's hidden bit restored, and the result's exponent
|
|
(E + 127) / 2 rounded down, the operand's own exponent parity decides how far
|
|
to shift: one place when E is odd, two when it is even. The top binade needs
|
|
no special case: in integers it is just another odd E.
|
|
*/
|
|
static u32 eeSqrtSignificand(u32 m)
|
|
{
|
|
EeSrtRemainder rem = {m, 0};
|
|
u32 root = 0;
|
|
s32 digit = 1;
|
|
|
|
for (int i = 0; i < 24; ++i)
|
|
{
|
|
const u32 addend_base = root + ((u32)digit << (24 - i));
|
|
root += (u32)digit << (25 - i);
|
|
const u32 addend = (digit > 0) ? ~addend_base : ((digit < 0) ? addend_base : 0u);
|
|
rem.carry += (u32)(digit > 0);
|
|
const EeSrtRemainder next = eeSrtCarrySave(rem.sum, rem.carry, addend);
|
|
digit = eeSrtDigit(eeSrtSelect(rem, next, digit));
|
|
rem.sum = next.sum << 1;
|
|
rem.carry = next.carry << 1;
|
|
}
|
|
// The last digit carries weight 2^1, below the root's least significant
|
|
// bit, so it only reaches the result by borrowing out of it.
|
|
root += (u32)digit << 1;
|
|
return (root >> 2) & 0xFFFFFFu;
|
|
}
|
|
|
|
static u32 eeSqrtBits(u32 t)
|
|
{
|
|
const u32 E = (t >> 23) & 0xFFu;
|
|
if (E == 0)
|
|
return 0; // +/-0 and the denormals: the EE drops the sign here, and so
|
|
// do both recompilers (they take |Ft| first). See
|
|
// EeRecFpu.SqrtSOfNegativeZeroIsPositiveZero.
|
|
|
|
const u32 m = (0x800000u | (t & 0x7FFFFFu)) << ((E & 1u) ? 1 : 2);
|
|
return (((E + 127u) >> 1) << 23) | (eeSqrtSignificand(m) & 0x7FFFFFu);
|
|
}
|
|
|
|
/* Nothing in the interpreter swaps EmuConfig.Cpu.FPUDivFPCR into the host FPCR
|
|
any more: DIV.S, SQRT.S and RSQRT.S are integer arithmetic now, and no
|
|
rounding mode reaches a digit recurrence. That register is PCSX2's surrogate
|
|
for the divide unit rounding to nearest while the rest of the FPU chops, and
|
|
both recompilers still swap it in, because they run these ops on host singles
|
|
(arm64 recDIV_S_xmm / recSQRT_S_xmm / recRSQRT_S_xmm in iFPU-arm64.cpp and
|
|
the DOUBLE:: twins in iFPUd-arm64.cpp; x86 iFPU.cpp / iFPUd.cpp with
|
|
xLDMXCSR): 1.0 rsqrt 1.5 is 0x3F5105EB on the console and 0x3F5105EC without
|
|
the swap. So the two engines part company on every operand silicon is not
|
|
correctly rounded on, which EeRecFpuDivUnitRounding and EeRecFpuRsqrt pin.
|
|
*/
|
|
|
|
void ABS_S() {
|
|
_FdValUl_ = _FsValUl_ & 0x7fffffff;
|
|
clearFPUFlags( FPUflagO | FPUflagU );
|
|
}
|
|
|
|
/* Every op below computes `exact` before writing its destination: fd may alias
|
|
fs or ft, and the accumulator forms read the ACC they are about to write.
|
|
*/
|
|
void ADD_S() {
|
|
const double exact = eeToDouble( _FsValUl_ ) + eeToDouble( _FtValUl_ );
|
|
_FdValUl_ = eeGuardedAddSub( _FsValUl_, _FtValUl_, false );
|
|
raiseOrClearOU( exact );
|
|
}
|
|
|
|
void ADDA_S() {
|
|
const double exact = eeToDouble( _FsValUl_ ) + eeToDouble( _FtValUl_ );
|
|
_FAValUl_ = eeGuardedAddSub( _FsValUl_, _FtValUl_, false );
|
|
raiseOrClearOU( exact );
|
|
}
|
|
|
|
void BC1F() {
|
|
BC1(==);
|
|
}
|
|
|
|
void BC1FL() {
|
|
BC1L(==); // Equal to 0
|
|
}
|
|
|
|
void BC1T() {
|
|
BC1(!=);
|
|
}
|
|
|
|
void BC1TL() {
|
|
BC1L(!=); // different from 0
|
|
}
|
|
|
|
void C_EQ() {
|
|
C_cond_S(==);
|
|
}
|
|
|
|
void C_F() {
|
|
clearFPUFlags( FPUflagC ); //clears C regardless
|
|
}
|
|
|
|
void C_LE() {
|
|
C_cond_S(<=);
|
|
}
|
|
|
|
void C_LT() {
|
|
C_cond_S(<);
|
|
}
|
|
|
|
void CFC1() {
|
|
if (!_Rt_) return;
|
|
|
|
// Only bit 4 of the register field is decoded: 0-15 alias FCR0, 16-31
|
|
// alias FCR31. Both recompilers implement this (iFPU.cpp recCFC1,
|
|
// iFPU-arm64.cpp recCFC1); the SD[0] stores force sign extension to 64 bit.
|
|
if (_Fs_ >= 16)
|
|
cpuRegs.GPR.r[_Rt_].SD[0] = (s32)((fpuRegs.fprc[31] & 0x0083c078) | 0x01000001); // drop always-zero bits, set always-one bits
|
|
else
|
|
cpuRegs.GPR.r[_Rt_].SD[0] = (s32)fpuRegs.fprc[0];
|
|
}
|
|
|
|
void CTC1() {
|
|
if ( _Fs_ != 31 ) return;
|
|
fpuRegs.fprc[_Fs_] = cpuRegs.GPR.r[_Rt_].UL[0];
|
|
}
|
|
|
|
void CVT_S() {
|
|
_FdValf_ = (float)_FsValSl_;
|
|
}
|
|
|
|
void CVT_W() {
|
|
if ( ( _FsValUl_ & 0x7F800000 ) <= 0x4E800000 ) { _FdValSl_ = (s32)_FsValf_; }
|
|
else if ( ( _FsValUl_ & 0x80000000 ) == 0 ) { _FdValUl_ = 0x7fffffff; }
|
|
else { _FdValUl_ = 0x80000000; }
|
|
}
|
|
|
|
void DIV_S() {
|
|
if (checkDivideByZero( _FdValUl_, _FtValUl_, _FsValUl_, FPUflagD | FPUflagSD, FPUflagI | FPUflagSI)) return;
|
|
_FdValUl_ = eeDivide( _FsValUl_, _FtValUl_ );
|
|
}
|
|
|
|
/* The EE multiplier's one-ULP deficit.
|
|
|
|
The console's multiply array is not a correctly-rounding multiplier: it
|
|
comes back exactly one step closer to zero on a large fraction of operands,
|
|
and which operands depends on operand order. Upstream states the rule in a
|
|
comment (the note above FPU_MUL in `pcsx2/x86/iFPU.cpp`) and never tests it;
|
|
FpuMulHack is a one-point sample of it.
|
|
|
|
Measured on SCPH-90000 (FCR0 0x2e40), captures/fpmul/ in the session
|
|
archive. Runs 2 and 3 sweep all 2^23 ft significands at each of twelve fs
|
|
significands, 100663296 rows, and every one of them is either exact or one
|
|
ULP low -- nothing ever came back high, or two ULP low:
|
|
|
|
* `mul.s(1.0, x)` was measured for every one of the 2^23 significands.
|
|
8257536 of them come back one ULP low and 131072 exact.
|
|
* `mul.s(x, 1.0)` is exact for all 2^23. The asymmetry is total, not
|
|
statistical: only ft is recoded, so only ft can contribute a negative
|
|
digit whose correction the truncated columns drop, which is why the
|
|
operation is not commutative.
|
|
* Unchanged across twelve exponent-field pairs from (1,254) to (254,1),
|
|
so it is a significand-domain effect with no exponent term.
|
|
|
|
The mechanism: ft is the Booth-recoded operand and the array's low columns
|
|
are not built, so the low partial products arrive at the summation tree
|
|
missing their bottom bits and each low negative digit's two's-complement
|
|
correction is dropped. eeMulArray() reconstructs that truncated low half and
|
|
compares its column 15 against the exact product's. Where the two disagree
|
|
the array lost exactly 2^15 there, and the loss reaches the result only if
|
|
borrowing it crosses the single ULP.
|
|
|
|
The reconstruction is not ours. It is the multiplier out of a proposed PCSX2
|
|
soft-float series -- GitHubProUser67, "Core/EE: Implements Soft-Floats for
|
|
the interpreters", 2025-04-20, crediting Gregory Gaines' write-up, the
|
|
PS2FloatLibrary in MultiServer3 and Goatman13's accurate_int_add_sub branch.
|
|
It is unmerged and still moving: PCSX2 master has no PS2Float, and the
|
|
pcsx2-reliquary fork that carries it as pcsx2/PS2Float.inl has revised it
|
|
since. The shape below is that routine, MulMantissa(), with its two small
|
|
structs unpacked. Bit-exact on all 100663296 measured rows, 15283477 of
|
|
them one ULP low.
|
|
|
|
What this replaced: on the 15585118 rows whose product is exactly
|
|
representable the decision collapses to a closed form in ft's mantissa alone
|
|
-- one ULP low iff a negative Booth digit appears among digits 0..4
|
|
(`m & 0x2AA`), or bit 11 disagrees with a boundary term on bits 12..15 --
|
|
the same column-15 test specialised to a zero tail. It is exact there and
|
|
short on 58585 of the remaining rows, where the decision needs fs and so no
|
|
predicate over ft can reach it. The arm64 emitters still implement a cut of
|
|
it, which is cheap where this is not; see iFPUd-arm64.cpp.
|
|
|
|
The gate below is exact: a borrow of 2^15 crosses a multiple of 2^k only
|
|
where the tail beneath it is already smaller than 2^15. It leaves the array
|
|
running on 0.27% of random operand pairs.
|
|
|
|
Applied only where it was measured: a saturating or flushed result, and a
|
|
decrement that would walk the exponent field to zero, are left alone.
|
|
*/
|
|
/* The 3-bit window that selects what digit `bit` of b contributes: 0 and 7
|
|
select zero, 1 and 2 select +a, 3 selects +2a, 4 selects -2a, 5 and 6
|
|
select -a. */
|
|
static u32 eeBoothWindow(u32 b, u32 bit)
|
|
{
|
|
return (bit ? b >> (bit * 2 - 1) : b << 1) & 7;
|
|
}
|
|
|
|
/* That digit's partial product of a. 32-bit on purpose: no column above 31 can
|
|
reach a decision taken at column 15, and letting the shift overflow is what
|
|
discards them. A negative digit is left as a one's complement here -- the
|
|
`+1` that would complete the negation is eeBoothCorrection() below. */
|
|
static u32 eeBoothPartial(u32 a, u32 b, u32 bit)
|
|
{
|
|
const u32 window = eeBoothWindow(b, bit);
|
|
a <<= bit * 2;
|
|
a += (window == 3 || window == 4) ? a : 0;
|
|
if (window >= 4 && window <= 6)
|
|
a ^= 0u - (1u << (bit * 2));
|
|
return (window >= 1 && window <= 6) ? a : 0;
|
|
}
|
|
|
|
/* The `+1` a negative digit owes, at that digit's own weight. Digits 0..4
|
|
never receive theirs -- their columns are not built, which is the whole
|
|
defect -- so only 5..7 get one. */
|
|
static u32 eeBoothCorrection(u32 b, u32 bit)
|
|
{
|
|
const u32 window = eeBoothWindow(b, bit);
|
|
return (window >= 4 && window <= 6) ? (1u << (bit * 2)) : 0;
|
|
}
|
|
|
|
/* One 3:2 carry-save row: returns the sum bits, writes the carry bits. */
|
|
static u32 eeCarrySaveAdd(u32 a, u32 b, u32 c, u32& carry)
|
|
{
|
|
const u32 u = a ^ b;
|
|
carry = ((u & c) | (a & b)) << 1;
|
|
return u ^ c;
|
|
}
|
|
|
|
/* The 48-bit significand product as the console's array computes it: the exact
|
|
product, less 2^15 where the truncated low columns come up short there. The
|
|
masks are the columns silicon does not build. */
|
|
static u64 eeMulArray(u32 a, u32 b)
|
|
{
|
|
const u64 full = static_cast<u64>(a) * static_cast<u64>(b);
|
|
|
|
const u32 p0 = eeBoothPartial(a, b, 0);
|
|
const u32 p1 = eeBoothPartial(a, b, 1);
|
|
const u32 p2 = eeBoothPartial(a, b, 2);
|
|
const u32 p3 = eeBoothPartial(a, b, 3);
|
|
const u32 p4 = eeBoothPartial(a, b, 4);
|
|
const u32 p5 = eeBoothPartial(a, b, 5);
|
|
const u32 p6 = eeBoothPartial(a, b, 6);
|
|
const u32 p7 = eeBoothPartial(a, b, 7);
|
|
|
|
/* The tree below is four carry-save levels deep and each lifts a bit by one
|
|
column, so nothing under bit 11 can reach the decision at column 15.
|
|
Digit 4's mask is exactly that boundary -- widening it changes no output,
|
|
narrowing it by one does. Digit 5's sits one higher because its bits 10
|
|
and 11 do not travel through the tree; they are re-injected below. */
|
|
u32 carry0, carry1, carry2, carry3, carry4, carry5;
|
|
const u32 sum0 = eeCarrySaveAdd(p1, p2, p3, carry0);
|
|
const u32 sum1 = eeCarrySaveAdd(p4 & ~0x7ffu, p5 & ~0xfffu, p6, carry1);
|
|
|
|
// Digit 5's two surviving product bits, and the corrections digits 5 and 6
|
|
// still receive, ride on rows they did not originate in.
|
|
const u32 hi1 = carry1 | eeBoothCorrection(b, 6) | (p5 & 0x800);
|
|
const u32 row7 = p7 | ((p5 & 0x400) + eeBoothCorrection(b, 5));
|
|
|
|
const u32 sum2 = eeCarrySaveAdd(p0, sum0, carry0, carry2);
|
|
const u32 sum3 = eeCarrySaveAdd(row7, sum1, hi1, carry3);
|
|
const u32 sum4 = eeCarrySaveAdd(carry2, sum3, carry3, carry4);
|
|
const u32 sum5 = eeCarrySaveAdd(sum2, sum4, carry4, carry5);
|
|
|
|
const u32 lo = sum5 & ~0x7fffu;
|
|
const u32 hi = (carry5 + eeBoothCorrection(b, 7)) & ~0x7fffu;
|
|
return full - (((lo + hi) ^ full) & 0x8000);
|
|
}
|
|
|
|
static bool eeMulOneUlpLow(u32 fs, u32 ft)
|
|
{
|
|
if ((fs & 0x7F800000) == 0 || (ft & 0x7F800000) == 0)
|
|
return false; // a zero operand (denormals are zero): the product is zero
|
|
|
|
const u32 a = 0x800000u | (fs & 0x7FFFFF);
|
|
const u32 b = 0x800000u | (ft & 0x7FFFFF);
|
|
const u64 prod = static_cast<u64>(a) * static_cast<u64>(b); // exact in 64
|
|
const int k = (prod >> 47) ? 24 : 23;
|
|
if ((prod & ((1ull << k) - 1u)) >= 0x8000u)
|
|
return false; // the tail below the ULP absorbs the whole borrow
|
|
|
|
return (prod >> k) != (eeMulArray(a, b) >> k);
|
|
}
|
|
|
|
/* eeRoundToSingle() for a product, plus the multiplier defect. */
|
|
static u32 eeMulRound(u32 fs, u32 ft, double exact)
|
|
{
|
|
const u32 w = eeRoundToSingle(exact);
|
|
|
|
if (std::fabs(exact) > kEeFpuMax) // saturated: never measured, leave it
|
|
return w;
|
|
if ((w & 0x7F800000) == 0) // flushed to zero
|
|
return w;
|
|
if ((w & 0x7FFFFFFF) == 0x00800000) // a decrement would leave the normals
|
|
return w;
|
|
|
|
return eeMulOneUlpLow(fs, ft) ? w - 1u : w;
|
|
}
|
|
|
|
/* The Instruction Set manual has an overly complicated way of
|
|
determining the flags that are set. Hopefully this shorter
|
|
method provides a similar outcome and is faster. (cottonvibes)
|
|
*/
|
|
/* The product is its own named double, so -ffp-contract cannot fuse away the
|
|
two roundings the PS2 ISA mandates -- and so the two flag steps below have
|
|
something separate to look at. See raiseOrClearOU/madFlushedProduct.
|
|
|
|
The A-forms name their single-precision product too, because the guarded adder
|
|
needs its bits, and that takes the accumulate out of the compiler's reach as
|
|
well: `_FAValf_ += fs * ft` is one expression a contracting compiler can turn
|
|
into a single-rounded FMA, with only the -ffp-contract=off line in
|
|
pcsx2/CMakeLists.txt between it and that. On corpus cases 567 and 1130 the
|
|
fused value is the console's, so a contracting build hid the guard-bit defect
|
|
on MSUBA.
|
|
|
|
Both forms read the accumulator through eeToDouble, so the value path and
|
|
the flag path see the same accumulator.
|
|
*/
|
|
/* fd = ACC +/- fs * ft, in the two rounding steps the ISA mandates: the product
|
|
lands in an EE single before the accumulate sees it, and an overflowing one
|
|
ends the instruction there rather than being accumulated. That is the test
|
|
madAccumulandOverflowed() has always made for the flag; the value follows it
|
|
now too.
|
|
*/
|
|
static u32 eeMulAccumulate(u32 fs, u32 ft, u32 accbits, bool issub)
|
|
{
|
|
const double product = eeToDouble( fs ) * eeToDouble( ft );
|
|
const u32 rounded = eeMulRound( fs, ft, product ) ^ (issub ? 0x80000000u : 0u);
|
|
if (madAccumulandOverflowed( product ))
|
|
return rounded;
|
|
return eeGuardedAddSub( accbits, rounded, false );
|
|
}
|
|
|
|
void MADD_S() {
|
|
const double product = eeToDouble( _FsValUl_ ) * eeToDouble( _FtValUl_ );
|
|
const double acc = eeToDouble( _FAValUl_ );
|
|
_FdValUl_ = eeMulAccumulate( _FsValUl_, _FtValUl_, _FAValUl_, false );
|
|
raiseOrClearOU( product );
|
|
if (madAccumulandOverflowed( product )) return;
|
|
raiseOrClearOU( acc + madFlushedProduct( product ) );
|
|
}
|
|
|
|
void MADDA_S() {
|
|
const double product = eeToDouble( _FsValUl_ ) * eeToDouble( _FtValUl_ );
|
|
const double acc = eeToDouble( _FAValUl_ );
|
|
_FAValUl_ = eeMulAccumulate( _FsValUl_, _FtValUl_, _FAValUl_, false );
|
|
raiseOrClearOU( product );
|
|
if (madAccumulandOverflowed( product )) return;
|
|
raiseOrClearOU( acc + madFlushedProduct( product ) );
|
|
}
|
|
|
|
void MAX_S() {
|
|
_FdValUl_ = fp_max( _FsValUl_, _FtValUl_ );
|
|
clearFPUFlags( FPUflagO | FPUflagU );
|
|
}
|
|
|
|
void MFC1() {
|
|
if ( !_Rt_ ) return;
|
|
cpuRegs.GPR.r[_Rt_].SD[0] = _FsValSl_; // sign extension into 64bit
|
|
}
|
|
|
|
void MIN_S() {
|
|
_FdValUl_ = fp_min(_FsValUl_, _FtValUl_);
|
|
clearFPUFlags( FPUflagO | FPUflagU );
|
|
}
|
|
|
|
void MOV_S() {
|
|
_FdValUl_ = _FsValUl_;
|
|
}
|
|
|
|
void MSUB_S() {
|
|
const double product = eeToDouble( _FsValUl_ ) * eeToDouble( _FtValUl_ );
|
|
const double acc = eeToDouble( _FAValUl_ );
|
|
_FdValUl_ = eeMulAccumulate( _FsValUl_, _FtValUl_, _FAValUl_, true );
|
|
raiseOrClearOU( product );
|
|
if (madAccumulandOverflowed( product )) return;
|
|
raiseOrClearOU( acc - madFlushedProduct( product ) );
|
|
}
|
|
|
|
void MSUBA_S() {
|
|
const double product = eeToDouble( _FsValUl_ ) * eeToDouble( _FtValUl_ );
|
|
const double acc = eeToDouble( _FAValUl_ );
|
|
_FAValUl_ = eeMulAccumulate( _FsValUl_, _FtValUl_, _FAValUl_, true );
|
|
raiseOrClearOU( product );
|
|
if (madAccumulandOverflowed( product )) return;
|
|
raiseOrClearOU( acc - madFlushedProduct( product ) );
|
|
}
|
|
|
|
void MTC1() {
|
|
_FsValUl_ = cpuRegs.GPR.r[_Rt_].UL[0];
|
|
}
|
|
|
|
/* The product of two EE singles is 48 significand bits, so a double holds it
|
|
exactly at any exponent and eeRoundToSingle() does the only rounding.
|
|
|
|
The multiplier's own one-ULP deficit rides on top of that, in eeMulRound --
|
|
see the block comment above eeMulArray for what the array loses and where
|
|
the loss reaches the result. The flag path stays on the exact product: O/U
|
|
is a magnitude test on the exact result and a one-ULP move cannot change it.
|
|
*/
|
|
void MUL_S() {
|
|
const double exact = eeToDouble( _FsValUl_ ) * eeToDouble( _FtValUl_ );
|
|
_FdValUl_ = eeMulRound( _FsValUl_, _FtValUl_, exact );
|
|
raiseOrClearOU( exact );
|
|
}
|
|
|
|
void MULA_S() {
|
|
const double exact = eeToDouble( _FsValUl_ ) * eeToDouble( _FtValUl_ );
|
|
_FAValUl_ = eeMulRound( _FsValUl_, _FtValUl_, exact );
|
|
raiseOrClearOU( exact );
|
|
}
|
|
|
|
void NEG_S() {
|
|
_FdValUl_ = (_FsValUl_ ^ 0x80000000);
|
|
clearFPUFlags( FPUflagO | FPUflagU );
|
|
}
|
|
|
|
void RSQRT_S() {
|
|
clearFPUFlags(FPUflagD | FPUflagI);
|
|
|
|
if ( ( _FtValUl_ & 0x7F800000 ) == 0 ) { // Ft is zero (Denormals are Zero)
|
|
_ContVal_ |= FPUflagD | FPUflagSD;
|
|
// The EE maximum, and the sign of Fs alone -- no xor, unlike DIV.S:
|
|
// rsqrt takes |Ft|, so the divisor has no sign left to contribute by the
|
|
// time the division happens. Console rows 59 and 63: rsqrt(+0, -0) is
|
|
// +0x7FFFFFFF and rsqrt(-0, -0) is -0x7FFFFFFF, and an xor rule flips
|
|
// both.
|
|
_FdValUl_ = ( _FsValUl_ & 0x80000000 ) | 0x7FFFFFFF;
|
|
return;
|
|
}
|
|
else if ( _FtValUl_ & 0x80000000 ) // Ft is negative
|
|
_ContVal_ |= FPUflagI | FPUflagSI;
|
|
|
|
// Both paths divide by a sqrt rounded to single. Dividing by the unrounded
|
|
// double instead lands 1 ULP from the EE FPU and from both recompilers,
|
|
// which stay in single-precision fsqrt+fdiv (x86 recRSQRThelper1, arm64
|
|
// recRSQRT_S_xmm): 1.0 rsqrt 1.5 comes back 0x3F5105EC that way, hardware
|
|
// gives 0x3F5105EB.
|
|
//
|
|
|
|
// Neither operand is clamped any more, which is all-or-nothing by design:
|
|
// unclamping only the sqrt used to break rsqrt(2^128, 2^128), which came out
|
|
// right solely because its two clamps cancelled. Unclamping both fixes that
|
|
// row and 13 others. Scored against the console over the corpus, RSQRT.S
|
|
// went 17/32 to 31/32.
|
|
//
|
|
// The composition itself comes from a dedicated console capture: 2231
|
|
// operand pairs over two probes, each pair run as sqrt.s, rsqrt.s and
|
|
// div.s, and rsqrt.s equals div.s(Fs, sqrt.s(Ft)) on every row, with a
|
|
// plain 24-bit single in between. See ee_fpu_divunit_console_tests.cpp.
|
|
_FdValUl_ = eeDivide( _FsValUl_, eeSqrtBits( _FtValUl_ ) );
|
|
}
|
|
|
|
void SQRT_S() {
|
|
clearFPUFlags(FPUflagI | FPUflagD);
|
|
|
|
// Invalid-operation keys off the SIGN BIT ALONE. -0 and the negative
|
|
// denormals raise it too, even though they are flushed to -0 and produce a
|
|
// perfectly ordinary +0: the exponent field plays no part. It used to sit
|
|
// inside a negative-normal arm, so those two operand classes came back with
|
|
// FCR31 untouched. x86's recSQRT_S_xmm has always tested the sign
|
|
// bit alone (iFPU.cpp, MOVMSKPS & 1), as has the FULL-mode DOUBLE path in
|
|
// iFPUd-arm64.cpp. Scored against a first-party capture over the sign x
|
|
// exponent matrix -- see EeRecFpu.SqrtSInvalidFlagFollowsTheSignBitAlone.
|
|
if ( _FtValUl_ & 0x80000000 )
|
|
_ContVal_ |= FPUflagI | FPUflagSI;
|
|
|
|
_FdValUl_ = eeSqrtBits( _FtValUl_ );
|
|
}
|
|
|
|
void SUB_S() {
|
|
const double exact = eeToDouble( _FsValUl_ ) - eeToDouble( _FtValUl_ );
|
|
_FdValUl_ = eeGuardedAddSub( _FsValUl_, _FtValUl_, true );
|
|
raiseOrClearOU( exact );
|
|
}
|
|
|
|
void SUBA_S() {
|
|
const double exact = eeToDouble( _FsValUl_ ) - eeToDouble( _FtValUl_ );
|
|
_FAValUl_ = eeGuardedAddSub( _FsValUl_, _FtValUl_, true );
|
|
raiseOrClearOU( exact );
|
|
}
|
|
|
|
} // End Namespace COP1
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// COP1 (FPU) Load/Store Instructions
|
|
|
|
// These are actually EE opcodes but since they're related to FPU registers and such they
|
|
// seem more appropriately located here.
|
|
|
|
void LWC1() {
|
|
u32 addr;
|
|
addr = cpuRegs.GPR.r[_Rs_].UL[0] + (s16)(cpuRegs.code & 0xffff); // force sign extension to 32bit
|
|
if (addr & 0x00000003) { Console.Error( "FPU (LWC1 Opcode): Invalid Unaligned Memory Address" ); return; } // Should signal an exception?
|
|
fpuRegs.fpr[_Rt_].UL = memRead32(addr);
|
|
}
|
|
|
|
void SWC1() {
|
|
u32 addr;
|
|
addr = cpuRegs.GPR.r[_Rs_].UL[0] + (s16)(cpuRegs.code & 0xffff); // force sign extension to 32bit
|
|
if (addr & 0x00000003) { Console.Error( "FPU (SWC1 Opcode): Invalid Unaligned Memory Address" ); return; } // Should signal an exception?
|
|
memWrite32(addr, fpuRegs.fpr[_Rt_].UL);
|
|
}
|
|
|
|
} } }
|