summaryrefslogtreecommitdiffstats
path: root/arch/arm/nwfpe/fpa11_cprt.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/nwfpe/fpa11_cprt.c')
-rw-r--r--arch/arm/nwfpe/fpa11_cprt.c361
1 files changed, 361 insertions, 0 deletions
diff --git a/arch/arm/nwfpe/fpa11_cprt.c b/arch/arm/nwfpe/fpa11_cprt.c
new file mode 100644
index 000000000..a8c142e45
--- /dev/null
+++ b/arch/arm/nwfpe/fpa11_cprt.c
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ NetWinder Floating Point Emulator
+ (c) Rebel.COM, 1998,1999
+ (c) Philip Blundell, 1999, 2001
+
+ Direct questions, comments to Scott Bambrough <scottb@netwinder.org>
+
+*/
+
+#include "fpa11.h"
+#include "fpopcode.h"
+#include "fpa11.inl"
+#include "fpmodule.h"
+#include "fpmodule.inl"
+#include "softfloat.h"
+
+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)
+{
+
+ 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:
+ return PerformFLT(opcode);
+ break;
+ case FIX_CODE >> 20:
+ return PerformFIX(opcode);
+ break;
+
+ case WFS_CODE >> 20:
+ writeFPSR(readRegister(getRd(opcode)));
+ break;
+ case RFS_CODE >> 20:
+ writeRegister(getRd(opcode), readFPSR());
+ break;
+
+ default:
+ return 0;
+ }
+
+ return 1;
+}
+
+unsigned int PerformFLT(const unsigned int opcode)
+{
+ FPA11 *fpa11 = GET_FPA11();
+ struct roundingData roundData;
+
+ roundData.mode = SetRoundingMode(opcode);
+ roundData.precision = SetRoundingPrecision(opcode);
+ roundData.exception = 0;
+
+ switch (opcode & MASK_ROUNDING_PRECISION) {
+ case ROUND_SINGLE:
+ {
+ fpa11->fType[getFn(opcode)] = typeSingle;
+ fpa11->fpreg[getFn(opcode)].fSingle = int32_to_float32(&roundData, readRegister(getRd(opcode)));
+ }
+ break;
+
+ case ROUND_DOUBLE:
+ {
+ fpa11->fType[getFn(opcode)] = typeDouble;
+ fpa11->fpreg[getFn(opcode)].fDouble = int32_to_float64(readRegister(getRd(opcode)));
+ }
+ break;
+
+#ifdef CONFIG_FPE_NWFPE_XP
+ case ROUND_EXTENDED:
+ {
+ fpa11->fType[getFn(opcode)] = typeExtended;
+ fpa11->fpreg[getFn(opcode)].fExtended = int32_to_floatx80(readRegister(getRd(opcode)));
+ }
+ break;
+#endif
+
+ default:
+ return 0;
+ }
+
+ if (roundData.exception)
+ float_raise(roundData.exception);
+
+ return 1;
+}
+
+unsigned int PerformFIX(const unsigned int opcode)
+{
+ FPA11 *fpa11 = GET_FPA11();
+ unsigned int Fn = getFm(opcode);
+ struct roundingData roundData;
+
+ roundData.mode = SetRoundingMode(opcode);
+ roundData.precision = SetRoundingPrecision(opcode);
+ roundData.exception = 0;
+
+ switch (fpa11->fType[Fn]) {
+ case typeSingle:
+ {
+ writeRegister(getRd(opcode), float32_to_int32(&roundData, fpa11->fpreg[Fn].fSingle));
+ }
+ break;
+
+ case typeDouble:
+ {
+ writeRegister(getRd(opcode), float64_to_int32(&roundData, fpa11->fpreg[Fn].fDouble));
+ }
+ break;
+
+#ifdef CONFIG_FPE_NWFPE_XP
+ case typeExtended:
+ {
+ writeRegister(getRd(opcode), floatx80_to_int32(&roundData, fpa11->fpreg[Fn].fExtended));
+ }
+ break;
+#endif
+
+ default:
+ return 0;
+ }
+
+ if (roundData.exception)
+ float_raise(roundData.exception);
+
+ return 1;
+}
+
+/* This instruction sets the flags N, Z, C, V in the FPSR. */
+static unsigned int PerformComparison(const unsigned int opcode)
+{
+ FPA11 *fpa11 = GET_FPA11();
+ unsigned int Fn = getFn(opcode), Fm = getFm(opcode);
+ int e_flag = opcode & 0x400000; /* 1 if CxFE */
+ int n_flag = opcode & 0x200000; /* 1 if CNxx */
+ unsigned int flags = 0;
+
+#ifdef CONFIG_FPE_NWFPE_XP
+ floatx80 rFn, rFm;
+
+ /* 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]) {
+ case typeSingle:
+ //printk("single.\n");
+ if (float32_is_nan(fpa11->fpreg[Fn].fSingle))
+ goto unordered;
+ rFn = float32_to_floatx80(fpa11->fpreg[Fn].fSingle);
+ break;
+
+ case typeDouble:
+ //printk("double.\n");
+ if (float64_is_nan(fpa11->fpreg[Fn].fDouble))
+ goto unordered;
+ rFn = float64_to_floatx80(fpa11->fpreg[Fn].fDouble);
+ break;
+
+ case typeExtended:
+ //printk("extended.\n");
+ if (floatx80_is_nan(fpa11->fpreg[Fn].fExtended))
+ goto unordered;
+ rFn = fpa11->fpreg[Fn].fExtended;
+ break;
+
+ 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]) {
+ case typeSingle:
+ //printk("single.\n");
+ if (float32_is_nan(fpa11->fpreg[Fm].fSingle))
+ goto unordered;
+ rFm = float32_to_floatx80(fpa11->fpreg[Fm].fSingle);
+ break;
+
+ case typeDouble:
+ //printk("double.\n");
+ if (float64_is_nan(fpa11->fpreg[Fm].fDouble))
+ goto unordered;
+ rFm = float64_to_floatx80(fpa11->fpreg[Fm].fDouble);
+ break;
+
+ case typeExtended:
+ //printk("extended.\n");
+ if (floatx80_is_nan(fpa11->fpreg[Fm].fExtended))
+ goto unordered;
+ rFm = fpa11->fpreg[Fm].fExtended;
+ break;
+
+ default:
+ return 0;
+ }
+ }
+
+ if (n_flag)
+ rFm.high ^= 0x8000;
+
+ /* test for less than condition */
+ if (floatx80_lt(rFn, rFm))
+ flags |= CC_NEGATIVE;
+
+ /* test for equal condition */
+ if (floatx80_eq(rFn, rFm))
+ flags |= CC_ZERO;
+
+ /* test for greater than or equal condition */
+ if (floatx80_lt(rFm, rFn))
+ flags |= CC_CARRY;
+
+#else
+ if (CONSTANT_FM(opcode)) {
+ /* Fm is a constant. Do the comparison in whatever precision
+ Fn happens to be stored in. */
+ if (fpa11->fType[Fn] == typeSingle) {
+ float32 rFm = getSingleConstant(Fm);
+ float32 rFn = fpa11->fpreg[Fn].fSingle;
+
+ if (float32_is_nan(rFn))
+ goto unordered;
+
+ if (n_flag)
+ rFm ^= 0x80000000;
+
+ /* test for less than condition */
+ if (float32_lt_nocheck(rFn, rFm))
+ flags |= CC_NEGATIVE;
+
+ /* test for equal condition */
+ if (float32_eq_nocheck(rFn, rFm))
+ flags |= CC_ZERO;
+
+ /* test for greater than or equal condition */
+ if (float32_lt_nocheck(rFm, rFn))
+ flags |= CC_CARRY;
+ } else {
+ float64 rFm = getDoubleConstant(Fm);
+ float64 rFn = fpa11->fpreg[Fn].fDouble;
+
+ if (float64_is_nan(rFn))
+ goto unordered;
+
+ if (n_flag)
+ rFm ^= 0x8000000000000000ULL;
+
+ /* test for less than condition */
+ if (float64_lt_nocheck(rFn, rFm))
+ flags |= CC_NEGATIVE;
+
+ /* test for equal condition */
+ if (float64_eq_nocheck(rFn, rFm))
+ flags |= CC_ZERO;
+
+ /* test for greater than or equal condition */
+ if (float64_lt_nocheck(rFm, rFn))
+ flags |= CC_CARRY;
+ }
+ } else {
+ /* Both operands are in registers. */
+ if (fpa11->fType[Fn] == typeSingle
+ && fpa11->fType[Fm] == typeSingle) {
+ float32 rFm = fpa11->fpreg[Fm].fSingle;
+ float32 rFn = fpa11->fpreg[Fn].fSingle;
+
+ if (float32_is_nan(rFn)
+ || float32_is_nan(rFm))
+ goto unordered;
+
+ if (n_flag)
+ rFm ^= 0x80000000;
+
+ /* test for less than condition */
+ if (float32_lt_nocheck(rFn, rFm))
+ flags |= CC_NEGATIVE;
+
+ /* test for equal condition */
+ if (float32_eq_nocheck(rFn, rFm))
+ flags |= CC_ZERO;
+
+ /* test for greater than or equal condition */
+ if (float32_lt_nocheck(rFm, rFn))
+ flags |= CC_CARRY;
+ } else {
+ /* Promote 32-bit operand to 64 bits. */
+ float64 rFm, rFn;
+
+ rFm = (fpa11->fType[Fm] == typeSingle) ?
+ float32_to_float64(fpa11->fpreg[Fm].fSingle)
+ : fpa11->fpreg[Fm].fDouble;
+
+ rFn = (fpa11->fType[Fn] == typeSingle) ?
+ float32_to_float64(fpa11->fpreg[Fn].fSingle)
+ : fpa11->fpreg[Fn].fDouble;
+
+ if (float64_is_nan(rFn)
+ || float64_is_nan(rFm))
+ goto unordered;
+
+ if (n_flag)
+ rFm ^= 0x8000000000000000ULL;
+
+ /* test for less than condition */
+ if (float64_lt_nocheck(rFn, rFm))
+ flags |= CC_NEGATIVE;
+
+ /* test for equal condition */
+ if (float64_eq_nocheck(rFn, rFm))
+ flags |= CC_ZERO;
+
+ /* test for greater than or equal condition */
+ if (float64_lt_nocheck(rFm, rFn))
+ flags |= CC_CARRY;
+ }
+ }
+
+#endif
+
+ writeConditionCodes(flags);
+
+ return 1;
+
+ 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;
+
+ if (e_flag)
+ float_raise(float_flag_invalid);
+
+ writeConditionCodes(flags);
+ return 1;
+}