2004-02-17 00:43:58 +03:00
|
|
|
/*
|
|
|
|
NetWinder Floating Point Emulator
|
|
|
|
(c) Rebel.COM, 1998,1999
|
|
|
|
(c) Philip Blundell, 1999
|
|
|
|
|
|
|
|
Direct questions, comments to Scott Bambrough <scottb@netwinder.org>
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "fpa11.h"
|
|
|
|
#include "softfloat.h"
|
|
|
|
#include "fpopcode.h"
|
|
|
|
#include "fpa11.inl"
|
|
|
|
//#include "fpmodule.h"
|
|
|
|
//#include "fpmodule.inl"
|
|
|
|
|
|
|
|
unsigned int PerformFLT(const unsigned int opcode);
|
|
|
|
unsigned int PerformFIX(const unsigned int opcode);
|
|
|
|
|
|
|
|
static unsigned int
|
|
|
|
PerformComparison(const unsigned int opcode);
|
|
|
|
|
|
|
|
unsigned int EmulateCPRT(const unsigned int opcode)
|
|
|
|
{
|
|
|
|
unsigned int nRc = 1;
|
|
|
|
|
|
|
|
//printk("EmulateCPRT(0x%08x)\n",opcode);
|
|
|
|
|
|
|
|
if (opcode & 0x800000)
|
|
|
|
{
|
|
|
|
/* This is some variant of a comparison (PerformComparison will
|
|
|
|
sort out which one). Since most of the other CPRT
|
|
|
|
instructions are oddball cases of some sort or other it makes
|
|
|
|
sense to pull this out into a fast path. */
|
|
|
|
return PerformComparison(opcode);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Hint to GCC that we'd like a jump table rather than a load of CMPs */
|
|
|
|
switch ((opcode & 0x700000) >> 20)
|
|
|
|
{
|
|
|
|
case FLT_CODE >> 20: nRc = PerformFLT(opcode); break;
|
|
|
|
case FIX_CODE >> 20: nRc = PerformFIX(opcode); break;
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
case WFS_CODE >> 20: writeFPSR(readRegister(getRd(opcode))); break;
|
|
|
|
case RFS_CODE >> 20: writeRegister(getRd(opcode),readFPSR()); break;
|
|
|
|
|
|
|
|
#if 0 /* We currently have no use for the FPCR, so there's no point
|
|
|
|
in emulating it. */
|
|
|
|
case WFC_CODE >> 20: writeFPCR(readRegister(getRd(opcode)));
|
|
|
|
case RFC_CODE >> 20: writeRegister(getRd(opcode),readFPCR()); break;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
default: nRc = 0;
|
|
|
|
}
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
return nRc;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int PerformFLT(const unsigned int opcode)
|
|
|
|
{
|
|
|
|
FPA11 *fpa11 = GET_FPA11();
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
unsigned int nRc = 1;
|
|
|
|
SetRoundingMode(opcode);
|
|
|
|
|
|
|
|
switch (opcode & MASK_ROUNDING_PRECISION)
|
|
|
|
{
|
|
|
|
case ROUND_SINGLE:
|
|
|
|
{
|
|
|
|
fpa11->fType[getFn(opcode)] = typeSingle;
|
|
|
|
fpa11->fpreg[getFn(opcode)].fSingle =
|
2005-03-13 19:55:58 +03:00
|
|
|
int32_to_float32(readRegister(getRd(opcode)), &fpa11->fp_status);
|
2004-02-17 00:43:58 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ROUND_DOUBLE:
|
|
|
|
{
|
|
|
|
fpa11->fType[getFn(opcode)] = typeDouble;
|
|
|
|
fpa11->fpreg[getFn(opcode)].fDouble =
|
2005-03-13 19:55:58 +03:00
|
|
|
int32_to_float64(readRegister(getRd(opcode)), &fpa11->fp_status);
|
2004-02-17 00:43:58 +03:00
|
|
|
}
|
|
|
|
break;
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
case ROUND_EXTENDED:
|
|
|
|
{
|
|
|
|
fpa11->fType[getFn(opcode)] = typeExtended;
|
|
|
|
fpa11->fpreg[getFn(opcode)].fExtended =
|
2005-03-13 19:55:58 +03:00
|
|
|
int32_to_floatx80(readRegister(getRd(opcode)), &fpa11->fp_status);
|
2004-02-17 00:43:58 +03:00
|
|
|
}
|
|
|
|
break;
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
default: nRc = 0;
|
|
|
|
}
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
return nRc;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int PerformFIX(const unsigned int opcode)
|
|
|
|
{
|
|
|
|
FPA11 *fpa11 = GET_FPA11();
|
|
|
|
unsigned int nRc = 1;
|
|
|
|
unsigned int Fn = getFm(opcode);
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
SetRoundingMode(opcode);
|
|
|
|
|
|
|
|
switch (fpa11->fType[Fn])
|
|
|
|
{
|
|
|
|
case typeSingle:
|
|
|
|
{
|
|
|
|
writeRegister(getRd(opcode),
|
2005-03-13 19:55:58 +03:00
|
|
|
float32_to_int32(fpa11->fpreg[Fn].fSingle, &fpa11->fp_status));
|
2004-02-17 00:43:58 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case typeDouble:
|
|
|
|
{
|
2006-06-25 22:15:32 +04:00
|
|
|
//printf("F%d is 0x%" PRIx64 "\n",Fn,fpa11->fpreg[Fn].fDouble);
|
2004-02-17 00:43:58 +03:00
|
|
|
writeRegister(getRd(opcode),
|
2005-03-13 19:55:58 +03:00
|
|
|
float64_to_int32(fpa11->fpreg[Fn].fDouble, &fpa11->fp_status));
|
2004-02-17 00:43:58 +03:00
|
|
|
}
|
|
|
|
break;
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
case typeExtended:
|
|
|
|
{
|
|
|
|
writeRegister(getRd(opcode),
|
2005-03-13 19:55:58 +03:00
|
|
|
floatx80_to_int32(fpa11->fpreg[Fn].fExtended, &fpa11->fp_status));
|
2004-02-17 00:43:58 +03:00
|
|
|
}
|
|
|
|
break;
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
default: nRc = 0;
|
|
|
|
}
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
return nRc;
|
|
|
|
}
|
|
|
|
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
static unsigned int __inline__
|
|
|
|
PerformComparisonOperation(floatx80 Fn, floatx80 Fm)
|
|
|
|
{
|
2005-03-13 19:55:58 +03:00
|
|
|
FPA11 *fpa11 = GET_FPA11();
|
2004-02-17 00:43:58 +03:00
|
|
|
unsigned int flags = 0;
|
|
|
|
|
|
|
|
/* test for less than condition */
|
2005-03-13 19:55:58 +03:00
|
|
|
if (floatx80_lt(Fn,Fm, &fpa11->fp_status))
|
2004-02-17 00:43:58 +03:00
|
|
|
{
|
|
|
|
flags |= CC_NEGATIVE;
|
|
|
|
}
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
/* test for equal condition */
|
2005-03-13 19:55:58 +03:00
|
|
|
if (floatx80_eq(Fn,Fm, &fpa11->fp_status))
|
2004-02-17 00:43:58 +03:00
|
|
|
{
|
|
|
|
flags |= CC_ZERO;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* test for greater than or equal condition */
|
2005-03-13 19:55:58 +03:00
|
|
|
if (floatx80_lt(Fm,Fn, &fpa11->fp_status))
|
2004-02-17 00:43:58 +03:00
|
|
|
{
|
|
|
|
flags |= CC_CARRY;
|
|
|
|
}
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
writeConditionCodes(flags);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This instruction sets the flags N, Z, C, V in the FPSR. */
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
static unsigned int PerformComparison(const unsigned int opcode)
|
|
|
|
{
|
|
|
|
FPA11 *fpa11 = GET_FPA11();
|
|
|
|
unsigned int Fn, Fm;
|
|
|
|
floatx80 rFn, rFm;
|
|
|
|
int e_flag = opcode & 0x400000; /* 1 if CxFE */
|
|
|
|
int n_flag = opcode & 0x200000; /* 1 if CNxx */
|
|
|
|
unsigned int flags = 0;
|
|
|
|
|
|
|
|
//printk("PerformComparison(0x%08x)\n",opcode);
|
|
|
|
|
|
|
|
Fn = getFn(opcode);
|
|
|
|
Fm = getFm(opcode);
|
|
|
|
|
|
|
|
/* Check for unordered condition and convert all operands to 80-bit
|
|
|
|
format.
|
|
|
|
?? Might be some mileage in avoiding this conversion if possible.
|
|
|
|
Eg, if both operands are 32-bit, detect this and do a 32-bit
|
|
|
|
comparison (cheaper than an 80-bit one). */
|
|
|
|
switch (fpa11->fType[Fn])
|
|
|
|
{
|
2007-09-17 01:08:06 +04:00
|
|
|
case typeSingle:
|
2004-02-17 00:43:58 +03:00
|
|
|
//printk("single.\n");
|
|
|
|
if (float32_is_nan(fpa11->fpreg[Fn].fSingle))
|
|
|
|
goto unordered;
|
2005-03-13 19:55:58 +03:00
|
|
|
rFn = float32_to_floatx80(fpa11->fpreg[Fn].fSingle, &fpa11->fp_status);
|
2004-02-17 00:43:58 +03:00
|
|
|
break;
|
|
|
|
|
2007-09-17 01:08:06 +04:00
|
|
|
case typeDouble:
|
2004-02-17 00:43:58 +03:00
|
|
|
//printk("double.\n");
|
|
|
|
if (float64_is_nan(fpa11->fpreg[Fn].fDouble))
|
|
|
|
goto unordered;
|
2005-03-13 19:55:58 +03:00
|
|
|
rFn = float64_to_floatx80(fpa11->fpreg[Fn].fDouble, &fpa11->fp_status);
|
2004-02-17 00:43:58 +03:00
|
|
|
break;
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2007-09-17 01:08:06 +04:00
|
|
|
case typeExtended:
|
2004-02-17 00:43:58 +03:00
|
|
|
//printk("extended.\n");
|
|
|
|
if (floatx80_is_nan(fpa11->fpreg[Fn].fExtended))
|
|
|
|
goto unordered;
|
|
|
|
rFn = fpa11->fpreg[Fn].fExtended;
|
|
|
|
break;
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
default: return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (CONSTANT_FM(opcode))
|
|
|
|
{
|
|
|
|
//printk("Fm is a constant: #%d.\n",Fm);
|
|
|
|
rFm = getExtendedConstant(Fm);
|
|
|
|
if (floatx80_is_nan(rFm))
|
|
|
|
goto unordered;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//printk("Fm = r%d which contains a ",Fm);
|
|
|
|
switch (fpa11->fType[Fm])
|
|
|
|
{
|
2007-09-17 01:08:06 +04:00
|
|
|
case typeSingle:
|
2004-02-17 00:43:58 +03:00
|
|
|
//printk("single.\n");
|
|
|
|
if (float32_is_nan(fpa11->fpreg[Fm].fSingle))
|
|
|
|
goto unordered;
|
2005-03-13 19:55:58 +03:00
|
|
|
rFm = float32_to_floatx80(fpa11->fpreg[Fm].fSingle, &fpa11->fp_status);
|
2004-02-17 00:43:58 +03:00
|
|
|
break;
|
|
|
|
|
2007-09-17 01:08:06 +04:00
|
|
|
case typeDouble:
|
2004-02-17 00:43:58 +03:00
|
|
|
//printk("double.\n");
|
|
|
|
if (float64_is_nan(fpa11->fpreg[Fm].fDouble))
|
|
|
|
goto unordered;
|
2005-03-13 19:55:58 +03:00
|
|
|
rFm = float64_to_floatx80(fpa11->fpreg[Fm].fDouble, &fpa11->fp_status);
|
2004-02-17 00:43:58 +03:00
|
|
|
break;
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2007-09-17 01:08:06 +04:00
|
|
|
case typeExtended:
|
2004-02-17 00:43:58 +03:00
|
|
|
//printk("extended.\n");
|
|
|
|
if (floatx80_is_nan(fpa11->fpreg[Fm].fExtended))
|
|
|
|
goto unordered;
|
|
|
|
rFm = fpa11->fpreg[Fm].fExtended;
|
|
|
|
break;
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2004-02-17 00:43:58 +03:00
|
|
|
default: return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (n_flag)
|
|
|
|
{
|
|
|
|
rFm.high ^= 0x8000;
|
|
|
|
}
|
|
|
|
|
|
|
|
return PerformComparisonOperation(rFn,rFm);
|
|
|
|
|
|
|
|
unordered:
|
|
|
|
/* ?? The FPA data sheet is pretty vague about this, in particular
|
|
|
|
about whether the non-E comparisons can ever raise exceptions.
|
|
|
|
This implementation is based on a combination of what it says in
|
|
|
|
the data sheet, observation of how the Acorn emulator actually
|
|
|
|
behaves (and how programs expect it to) and guesswork. */
|
|
|
|
flags |= CC_OVERFLOW;
|
|
|
|
flags &= ~(CC_ZERO | CC_NEGATIVE);
|
|
|
|
|
|
|
|
if (BIT_AC & readFPSR()) flags |= CC_CARRY;
|
|
|
|
|
2005-03-13 19:55:58 +03:00
|
|
|
if (e_flag) float_raise(float_flag_invalid, &fpa11->fp_status);
|
2004-02-17 00:43:58 +03:00
|
|
|
|
|
|
|
writeConditionCodes(flags);
|
|
|
|
return 1;
|
|
|
|
}
|