|
@@ -0,0 +1,1485 @@
|
|
|
|
+/*
|
|
|
|
+ * this doesn't attempt to implement MIPS floating-point properties
|
|
|
|
+ * that aren't visible in the Inferno environment.
|
|
|
|
+ * all arithmetic is done in double precision.
|
|
|
|
+ * the FP trap status isn't updated.
|
|
|
|
+ *
|
|
|
|
+ * we emulate the original MIPS FP register model: 32-bits each,
|
|
|
|
+ * F(2n) and F(2n+1) are a double, with lower-order word first;
|
|
|
|
+ * note that this is little-endian order, unlike the rest of the
|
|
|
|
+ * machine, so double-word operations will need to swap the words
|
|
|
|
+ * when transferring between FP registers and memory.
|
|
|
|
+ *
|
|
|
|
+ * on some machines, we can convert to an FP internal representation when
|
|
|
|
+ * moving to FPU registers and back (to integer, for example) when moving
|
|
|
|
+ * from them. the MIPS is different: its conversion instructions operate
|
|
|
|
+ * on FP registers only, and there's no way to tell if data being moved
|
|
|
|
+ * into an FP register is integer or FP, so it must be possible to store
|
|
|
|
+ * integers in FP registers without conversion. Furthermore, pairs of FP
|
|
|
|
+ * registers can be combined into a double. So we keep the raw bits
|
|
|
|
+ * around as the canonical representation and convert only to and from
|
|
|
|
+ * Internal FP format when we must (i.e., before calling the common fpi
|
|
|
|
+ * code).
|
|
|
|
+ */
|
|
|
|
+#include "u.h"
|
|
|
|
+#include "../port/lib.h"
|
|
|
|
+#include "mem.h"
|
|
|
|
+#include "dat.h"
|
|
|
|
+#include "fns.h"
|
|
|
|
+#include "ureg.h"
|
|
|
|
+#include "../port/fpi.h"
|
|
|
|
+#include <tos.h>
|
|
|
|
+
|
|
|
|
+#ifdef FPEMUDEBUG
|
|
|
|
+#define DBG(bits) (fpemudebug & (bits))
|
|
|
|
+#define intpr _intpr
|
|
|
|
+#define internsane _internsane
|
|
|
|
+#define dbgstuck _dbgstuck
|
|
|
|
+#else
|
|
|
|
+#define DBG(bits) (0)
|
|
|
|
+#define internsane(i, ur) do { USED(ur); } while(0)
|
|
|
|
+#define intpr(i, reg, fmt, ufp) do {} while(0)
|
|
|
|
+#define dbgstuck(pc, ur, ufp) do {} while(0)
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+#define OFR(memb) (uintptr)&((Ureg*)0)->memb /* offset into Ureg of memb */
|
|
|
|
+#define REG(ur, r) *acpureg(ur, r) /* cpu reg in Ureg */
|
|
|
|
+#define FREG(ufp, fr) (ufp)->reg[(fr) & REGMASK] /* fp reg raw bits */
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * instruction decoding for COP1 instructions; integer instructions
|
|
|
|
+ * are laid out differently.
|
|
|
|
+ */
|
|
|
|
+#define OP(ul) ((ul) >> 26)
|
|
|
|
+#define REGMASK MASK(5) /* mask for a register number */
|
|
|
|
+#define FMT(ul) (((ul) >> 21) & REGMASK) /* data type */
|
|
|
|
+#define REGT(ul) (((ul) >> 16) & REGMASK) /* source2 register */
|
|
|
|
+#define REGS(ul) (((ul) >> 11) & REGMASK) /* source1 register */
|
|
|
|
+#define REGD(ul) (((ul) >> 6) & REGMASK) /* destination register */
|
|
|
|
+#define FUNC(ul) ((ul) & MASK(6))
|
|
|
|
+
|
|
|
|
+enum {
|
|
|
|
+ Dbgbasic = 1<<0, /* base debugging: ops, except'ns */
|
|
|
|
+ Dbgmoves = 1<<1, /* not very exciting usually */
|
|
|
|
+ Dbgregs = 1<<2, /* print register contents around ops */
|
|
|
|
+ Dbgdelay = 1<<3, /* branch-delay-slot-related machinery */
|
|
|
|
+
|
|
|
|
+ /* fpimips status codes */
|
|
|
|
+ Failed = -1,
|
|
|
|
+ Advpc, /* advance pc normally */
|
|
|
|
+ Leavepc, /* don't change the pc */
|
|
|
|
+ Leavepcret, /* ... and return to user mode now */
|
|
|
|
+ Nomatch,
|
|
|
|
+
|
|
|
|
+ /* no-ops */
|
|
|
|
+ NOP = 0x27, /* NOR R0, R0, R0 */
|
|
|
|
+ MIPSNOP = 0, /* SLL R0, R0, R0 */
|
|
|
|
+
|
|
|
|
+ /* fp op-codes */
|
|
|
|
+ COP1 = 0x11, /* fpu op */
|
|
|
|
+ LWC1 = 0x31, /* load float/long */
|
|
|
|
+ LDC1 = 0x35, /* load double/vlong */
|
|
|
|
+ SWC1 = 0x39, /* store float/long */
|
|
|
|
+ SDC1 = 0x3d, /* store double/vlong */
|
|
|
|
+
|
|
|
|
+ N = 1<<31, /* condition codes */
|
|
|
|
+ Z = 1<<30,
|
|
|
|
+ C = 1<<29,
|
|
|
|
+ V = 1<<28,
|
|
|
|
+
|
|
|
|
+ /* data types (format field values) */
|
|
|
|
+ MFC1 = 0, /* and func == 0 ... */
|
|
|
|
+ DMFC1, /* vlong move */
|
|
|
|
+ CFC1, /* ctl word move */
|
|
|
|
+ MTC1 = 4,
|
|
|
|
+ DMTC1,
|
|
|
|
+ CTC1, /* ... end `and func == 0' */
|
|
|
|
+ BRANCH = 8,
|
|
|
|
+ Ffloat = 16,
|
|
|
|
+ Fdouble,
|
|
|
|
+ Flong = 20,
|
|
|
|
+ Fvlong,
|
|
|
|
+
|
|
|
|
+ /* fp control registers */
|
|
|
|
+ Fpimp = 0,
|
|
|
|
+ Fpcsr = 31,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+typedef struct FP1 FP1;
|
|
|
|
+typedef struct FP2 FP2;
|
|
|
|
+typedef struct FPcvt FPcvt;
|
|
|
|
+typedef struct Instr Instr;
|
|
|
|
+
|
|
|
|
+struct Instr { /* a COP1 instruction, broken out and registers converted */
|
|
|
|
+ int iw; /* whole word */
|
|
|
|
+ uintptr pc;
|
|
|
|
+ int o; /* opcode or cop1 func code */
|
|
|
|
+ int fmt; /* operand format */
|
|
|
|
+ int rm; /* first operand register */
|
|
|
|
+ int rn; /* second operand register */
|
|
|
|
+ int rd; /* destination register */
|
|
|
|
+
|
|
|
|
+ Internal *fm; /* converted from FREG(ufp, rm) */
|
|
|
|
+ Internal *fn;
|
|
|
|
+ char *dfmt;
|
|
|
|
+ FPsave *ufp; /* fp state, including fp registers */
|
|
|
|
+ Ureg *ur; /* user registers */
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct FP2 {
|
|
|
|
+ char* name;
|
|
|
|
+ void (*f)(Internal*, Internal*, Internal*);
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct FP1 {
|
|
|
|
+ char* name;
|
|
|
|
+ void (*f)(Internal*, Internal*);
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct FPcvt {
|
|
|
|
+ char* name;
|
|
|
|
+ void (*f)(int, int, int, Ureg *, FPsave *);
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int roff[32] = {
|
|
|
|
+ 0, OFR(r1), OFR(r2), OFR(r3),
|
|
|
|
+ OFR(r4), OFR(r5), OFR(r6), OFR(r7),
|
|
|
|
+ OFR(r8), OFR(r9), OFR(r10), OFR(r11),
|
|
|
|
+ OFR(r12), OFR(r13), OFR(r14), OFR(r15),
|
|
|
|
+ OFR(r16), OFR(r17), OFR(r18), OFR(r19),
|
|
|
|
+ OFR(r20), OFR(r21), OFR(r22), OFR(r23),
|
|
|
|
+ OFR(r24), OFR(r25), OFR(r26), OFR(r27),
|
|
|
|
+ OFR(r28), OFR(sp), OFR(r30), OFR(r31),
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * plan 9 assumes F24 initialized to 0.0, F26 to 0.5, F28 to 1.0, F30 to 2.0.
|
|
|
|
+ */
|
|
|
|
+enum {
|
|
|
|
+ FZERO = 24,
|
|
|
|
+ FHALF = 26,
|
|
|
|
+};
|
|
|
|
+static Internal fpconst[Nfpregs] = { /* indexed by register no. */
|
|
|
|
+ /* s, e, l, h */
|
|
|
|
+[FZERO] {0, 0x1, 0x00000000, 0x00000000}, /* 0.0 */
|
|
|
|
+[FHALF] {0, 0x3FE, 0x00000000, 0x08000000}, /* 0.5 */
|
|
|
|
+[28] {0, 0x3FF, 0x00000000, 0x08000000}, /* 1.0 */
|
|
|
|
+[30] {0, 0x400, 0x00000000, 0x08000000}, /* 2.0 */
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static char *fmtnames[] = {
|
|
|
|
+[MFC1] "MF",
|
|
|
|
+[DMFC1] "DMF",
|
|
|
|
+[CFC1] "CF",
|
|
|
|
+[MTC1] "MT",
|
|
|
|
+[DMTC1] "DMT",
|
|
|
|
+[CTC1] "CT",
|
|
|
|
+[BRANCH]"BR",
|
|
|
|
+
|
|
|
|
+[Ffloat]"F",
|
|
|
|
+[Fdouble]"D",
|
|
|
|
+[Flong] "W",
|
|
|
|
+[Fvlong]"L",
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static char *prednames[] = {
|
|
|
|
+[0] "F",
|
|
|
|
+[1] "UN",
|
|
|
|
+[2] "EQ",
|
|
|
|
+[3] "UEQ",
|
|
|
|
+[4] "OLT",
|
|
|
|
+[5] "ULT",
|
|
|
|
+[6] "OLE",
|
|
|
|
+[7] "ULE",
|
|
|
|
+[8] "SF",
|
|
|
|
+[9] "NGLE",
|
|
|
|
+[10] "SEQ",
|
|
|
|
+[11] "NGL",
|
|
|
|
+[12] "LT",
|
|
|
|
+[13] "NGE",
|
|
|
|
+[14] "LE",
|
|
|
|
+[15] "NGT",
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+int fpemudebug = 0; /* settable via /dev/archctl */
|
|
|
|
+
|
|
|
|
+static ulong dummyr0;
|
|
|
|
+static QLock watchlock; /* lock for watch-points */
|
|
|
|
+
|
|
|
|
+ulong branch(Ureg*, ulong);
|
|
|
|
+int isbranch(ulong *);
|
|
|
|
+
|
|
|
|
+static int fpimips(ulong, ulong, Ureg *, FPsave *);
|
|
|
|
+
|
|
|
|
+char *
|
|
|
|
+fpemuprint(char *p, char *ep)
|
|
|
|
+{
|
|
|
|
+#ifdef FPEMUDEBUG
|
|
|
|
+ return seprint(p, ep, "fpemudebug %d\n", fpemudebug);
|
|
|
|
+#else
|
|
|
|
+ USED(ep);
|
|
|
|
+ return p;
|
|
|
|
+#endif
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static ulong *
|
|
|
|
+acpureg(Ureg *ur, int r)
|
|
|
|
+{
|
|
|
|
+ r &= REGMASK;
|
|
|
|
+ if (r == 0 || roff[r] == 0) {
|
|
|
|
+ dummyr0 = 0;
|
|
|
|
+ return &dummyr0;
|
|
|
|
+ }
|
|
|
|
+ return (ulong *)((char*)ur + roff[r]);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+ulong *
|
|
|
|
+reg(Ureg *ur, int r) /* for faultmips */
|
|
|
|
+{
|
|
|
|
+ return ®(ur, r);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void
|
|
|
|
+_internsane(Internal *i, Ureg *ur)
|
|
|
|
+{
|
|
|
|
+ static char buf[ERRMAX];
|
|
|
|
+
|
|
|
|
+ USED(i);
|
|
|
|
+ if (!(DBG(Dbgbasic)))
|
|
|
|
+ return;
|
|
|
|
+ if ((unsigned)i->s > 1) {
|
|
|
|
+ snprint(buf, sizeof buf,
|
|
|
|
+ "fpuemu: bogus Internal sign at pc=%#p", ur->pc);
|
|
|
|
+ error(buf);
|
|
|
|
+ }
|
|
|
|
+ if ((unsigned)i->e > DoubleExpMax) {
|
|
|
|
+ snprint(buf, sizeof buf,
|
|
|
|
+ "fpuemu: bogus Internal exponent at pc=%#p", ur->pc);
|
|
|
|
+ error(buf);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * mips binary operations (d = n operator m)
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+static void
|
|
|
|
+fadd(Internal *m, Internal *n, Internal *d)
|
|
|
|
+{
|
|
|
|
+ (m->s == n->s? fpiadd: fpisub)(m, n, d);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void
|
|
|
|
+fsub(Internal *m, Internal *n, Internal *d)
|
|
|
|
+{
|
|
|
|
+ m->s ^= 1;
|
|
|
|
+ (m->s == n->s? fpiadd: fpisub)(m, n, d);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * mips unary operations
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+static void
|
|
|
|
+frnd(Internal *m, Internal *d)
|
|
|
|
+{
|
|
|
|
+ short e;
|
|
|
|
+ Internal tmp;
|
|
|
|
+
|
|
|
|
+ tmp = fpconst[FHALF];
|
|
|
|
+ (m->s? fsub: fadd)(&tmp, m, d);
|
|
|
|
+ if(IsWeird(d))
|
|
|
|
+ return;
|
|
|
|
+ fpiround(d);
|
|
|
|
+ e = (d->e - ExpBias) + 1;
|
|
|
|
+ if(e <= 0)
|
|
|
|
+ SetZero(d);
|
|
|
|
+ else if(e > FractBits){
|
|
|
|
+ if(e < 2*FractBits)
|
|
|
|
+ d->l &= ~((1<<(2*FractBits - e))-1);
|
|
|
|
+ }else{
|
|
|
|
+ d->l = 0;
|
|
|
|
+ if(e < FractBits)
|
|
|
|
+ d->h &= ~((1<<(FractBits-e))-1);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* debugging: print internal representation of an fp reg */
|
|
|
|
+static void
|
|
|
|
+_intpr(Internal *i, int reg, int fmt, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ USED(i);
|
|
|
|
+ if (!(DBG(Dbgregs)))
|
|
|
|
+ return;
|
|
|
|
+ if (fmt == Fdouble && reg < 31)
|
|
|
|
+ iprint("\tD%02d: l %08lux h %08lux =\ts %d e %04d h %08lux l %08lux\n",
|
|
|
|
+ reg, FREG(ufp, reg), FREG(ufp, reg+1),
|
|
|
|
+ i->s, i->e, i->h, i->l);
|
|
|
|
+ else
|
|
|
|
+ iprint("\tF%02d: %08lux =\ts %d e %04d h %08lux l %08lux\n",
|
|
|
|
+ reg, FREG(ufp, reg),
|
|
|
|
+ i->s, i->e, i->h, i->l);
|
|
|
|
+ delay(75);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void
|
|
|
|
+dreg2dbl(Double *dp, int reg, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ reg &= ~1;
|
|
|
|
+ dp->l = FREG(ufp, reg);
|
|
|
|
+ dp->h = FREG(ufp, reg+1);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void
|
|
|
|
+dbl2dreg(int reg, Double *dp, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ reg &= ~1;
|
|
|
|
+ FREG(ufp, reg) = dp->l;
|
|
|
|
+ FREG(ufp, reg+1) = dp->h;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void
|
|
|
|
+vreg2dbl(Double *dp, int reg, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ reg &= ~1;
|
|
|
|
+ dp->l = FREG(ufp, reg+1);
|
|
|
|
+ dp->h = FREG(ufp, reg);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void
|
|
|
|
+dbl2vreg(int reg, Double *dp, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ reg &= ~1;
|
|
|
|
+ FREG(ufp, reg+1) = dp->l;
|
|
|
|
+ FREG(ufp, reg) = dp->h;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* convert fmt (rm) to double (rd) */
|
|
|
|
+static void
|
|
|
|
+fcvtd(int fmt, int rm, int rd, Ureg *ur, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ Double d;
|
|
|
|
+ Internal intrn;
|
|
|
|
+
|
|
|
|
+ switch (fmt) {
|
|
|
|
+ case Ffloat:
|
|
|
|
+ fpis2i(&intrn, &FREG(ufp, rm));
|
|
|
|
+ internsane(&intrn, ur);
|
|
|
|
+ fpii2d(&d, &intrn);
|
|
|
|
+ break;
|
|
|
|
+ case Fdouble:
|
|
|
|
+ dreg2dbl(&d, rm, ufp);
|
|
|
|
+ break;
|
|
|
|
+ case Flong:
|
|
|
|
+ fpiw2i(&intrn, &FREG(ufp, rm));
|
|
|
|
+ internsane(&intrn, ur);
|
|
|
|
+ fpii2d(&d, &intrn);
|
|
|
|
+ break;
|
|
|
|
+ case Fvlong:
|
|
|
|
+ vreg2dbl(&d, rm, ufp);
|
|
|
|
+ fpiv2i(&intrn, &d);
|
|
|
|
+ internsane(&intrn, ur);
|
|
|
|
+ fpii2d(&d, &intrn);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ dbl2dreg(rd, &d, ufp);
|
|
|
|
+ if (fmt != Fdouble && DBG(Dbgregs))
|
|
|
|
+ intpr(&intrn, rm, Fdouble, ufp);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* convert fmt (rm) to single (rd) */
|
|
|
|
+static void
|
|
|
|
+fcvts(int fmt, int rm, int rd, Ureg *ur, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ Double d;
|
|
|
|
+ Internal intrn;
|
|
|
|
+
|
|
|
|
+ switch (fmt) {
|
|
|
|
+ case Ffloat:
|
|
|
|
+ FREG(ufp, rd) = FREG(ufp, rm);
|
|
|
|
+ break;
|
|
|
|
+ case Fdouble:
|
|
|
|
+ dreg2dbl(&d, rm, ufp);
|
|
|
|
+ fpid2i(&intrn, &d);
|
|
|
|
+ break;
|
|
|
|
+ case Flong:
|
|
|
|
+ fpiw2i(&intrn, &FREG(ufp, rm));
|
|
|
|
+ break;
|
|
|
|
+ case Fvlong:
|
|
|
|
+ vreg2dbl(&d, rm, ufp);
|
|
|
|
+ fpiv2i(&intrn, &d);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ if (fmt != Ffloat) {
|
|
|
|
+ if(DBG(Dbgregs))
|
|
|
|
+ intpr(&intrn, rm, Ffloat, ufp);
|
|
|
|
+ internsane(&intrn, ur);
|
|
|
|
+ fpii2s(&FREG(ufp, rd), &intrn);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* convert fmt (rm) to long (rd) */
|
|
|
|
+static void
|
|
|
|
+fcvtw(int fmt, int rm, int rd, Ureg *ur, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ Double d;
|
|
|
|
+ Internal intrn;
|
|
|
|
+
|
|
|
|
+ switch (fmt) {
|
|
|
|
+ case Ffloat:
|
|
|
|
+ fpis2i(&intrn, &FREG(ufp, rm));
|
|
|
|
+ break;
|
|
|
|
+ case Fdouble:
|
|
|
|
+ dreg2dbl(&d, rm, ufp);
|
|
|
|
+ fpid2i(&intrn, &d);
|
|
|
|
+ break;
|
|
|
|
+ case Flong:
|
|
|
|
+ FREG(ufp, rd) = FREG(ufp, rm);
|
|
|
|
+ break;
|
|
|
|
+ case Fvlong:
|
|
|
|
+ vreg2dbl(&d, rm, ufp);
|
|
|
|
+ fpiv2i(&intrn, &d);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ if (fmt != Flong) {
|
|
|
|
+ if(DBG(Dbgregs))
|
|
|
|
+ intpr(&intrn, rm, Flong, ufp);
|
|
|
|
+ internsane(&intrn, ur);
|
|
|
|
+ fpii2w((long *)&FREG(ufp, rd), &intrn);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* convert fmt (rm) to vlong (rd) */
|
|
|
|
+static void
|
|
|
|
+fcvtv(int fmt, int rm, int rd, Ureg *ur, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ Double d;
|
|
|
|
+ Internal intrn;
|
|
|
|
+
|
|
|
|
+ switch (fmt) {
|
|
|
|
+ case Ffloat:
|
|
|
|
+ fpis2i(&intrn, &FREG(ufp, rm));
|
|
|
|
+ break;
|
|
|
|
+ case Fdouble:
|
|
|
|
+ dreg2dbl(&d, rm, ufp);
|
|
|
|
+ fpid2i(&intrn, &d);
|
|
|
|
+ break;
|
|
|
|
+ case Flong:
|
|
|
|
+ fpiw2i(&intrn, &FREG(ufp, rm));
|
|
|
|
+ break;
|
|
|
|
+ case Fvlong:
|
|
|
|
+ vreg2dbl(&d, rm, ufp);
|
|
|
|
+ dbl2vreg(rd, &d, ufp);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ if (fmt != Fvlong) {
|
|
|
|
+ if(DBG(Dbgregs))
|
|
|
|
+ intpr(&intrn, rm, Fvlong, ufp);
|
|
|
|
+ internsane(&intrn, ur);
|
|
|
|
+ fpii2v((vlong *)&FREG(ufp, rd), &intrn);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * MIPS function codes
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+static FP2 optab2[] = { /* Fd := Fn OP Fm (binary) */
|
|
|
|
+[0] {"ADDF", fadd}, /* can ignore fmt, just use doubles */
|
|
|
|
+[1] {"SUBF", fsub},
|
|
|
|
+[2] {"MULF", fpimul},
|
|
|
|
+[3] {"DIVF", fpidiv},
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static FP1 optab1[32] = { /* Fd := OP Fm (unary) */
|
|
|
|
+[4] {"SQTF", /*fsqt*/0},
|
|
|
|
+[5] {"ABSF", /*fabsf*/0}, /* inline in unaryemu... */
|
|
|
|
+[6] {"MOVF", /*fmov*/0},
|
|
|
|
+[7] {"NEGF", /*fmovn*/0},
|
|
|
|
+[8] {"ROUND.L", /*froundl*/0}, /* 64-bit integer results ... */
|
|
|
|
+[9] {"TRUNC.L", /*ftruncl*/0},
|
|
|
|
+[10] {"CEIL.L", /*fceill*/0},
|
|
|
|
+[11] {"FLOOR.L", /*ffloorl*/0},
|
|
|
|
+[12] {"ROUND.W", frnd}, /* 32-bit integer results ... */
|
|
|
|
+[13] {"TRUNC.W", /*ftrunc*/0},
|
|
|
|
+[14] {"CEIL.W", /*fceil*/0},
|
|
|
|
+[15] {"FLOOR.W", /*ffloor*/0},
|
|
|
|
+/* 17—19 are newish MIPS32/64 conditional moves */
|
|
|
|
+/* 21, 22, 28—31 are newish reciprocal or sqrt */
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static FPcvt optabcvt[] = { /* Fd := OP(fmt, Fm) (unary) */
|
|
|
|
+[32] {"CVT.S", fcvts}, /* must honour fmt as src format */
|
|
|
|
+[33] {"CVT.D", fcvtd},
|
|
|
|
+[36] {"CVT.W", fcvtw},
|
|
|
|
+[37] {"CVT.L", fcvtv},
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * No type conversion is implied and the type of the cpu register is
|
|
|
|
+ * unknown, so copy the bits into reg.
|
|
|
|
+ * Later instructions will have to know the correct type and use the
|
|
|
|
+ * right format specifier to convert to or from Internal FP.
|
|
|
|
+ */
|
|
|
|
+static void
|
|
|
|
+fld(int d, ulong ea, int n, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ if(DBG(Dbgmoves))
|
|
|
|
+ iprint("MOV%c #%lux, F%d\n", n==8? 'D': 'F', ea, d);
|
|
|
|
+ if (n == 4)
|
|
|
|
+ memmove(&FREG(ufp, d), (void *)ea, 4);
|
|
|
|
+ else if (n == 8){
|
|
|
|
+ d &= ~1;
|
|
|
|
+ /* NB: we swap order of the words */
|
|
|
|
+ memmove(&FREG(ufp, d), (void *)(ea+4), 4);
|
|
|
|
+ memmove(&FREG(ufp, d+1), (void *)ea, 4);
|
|
|
|
+ } else
|
|
|
|
+ panic("fld: n (%d) not 4 nor 8", n);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void
|
|
|
|
+fst(ulong ea, int s, int n, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ if(DBG(Dbgmoves))
|
|
|
|
+ iprint("MOV%c F%d,#%lux\n", n==8? 'D': 'F', s, ea);
|
|
|
|
+ if (n == 4)
|
|
|
|
+ memmove((void *)ea, &FREG(ufp, s), 4);
|
|
|
|
+ else if (n == 8){
|
|
|
|
+ s &= ~1;
|
|
|
|
+ /* NB: we swap order of the words */
|
|
|
|
+ memmove((void *)(ea+4), &FREG(ufp, s), 4);
|
|
|
|
+ memmove((void *)ea, &FREG(ufp, s+1), 4);
|
|
|
|
+ } else
|
|
|
|
+ panic("fst: n (%d) not 4 nor 8", n);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void
|
|
|
|
+unimp(ulong pc, ulong op, char *msg)
|
|
|
|
+{
|
|
|
|
+ char buf[120];
|
|
|
|
+
|
|
|
|
+ snprint(buf, sizeof(buf), "sys: fp: pc=%#lux unimp fp %#.8lux: %s",
|
|
|
|
+ pc, op, msg);
|
|
|
|
+ if(DBG(Dbgbasic))
|
|
|
|
+ iprint("FPE: %s\n", buf);
|
|
|
|
+ error(buf);
|
|
|
|
+ /* no return */
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int
|
|
|
|
+isfpop(ulong iw)
|
|
|
|
+{
|
|
|
|
+ switch (OP(iw)) {
|
|
|
|
+ case COP1:
|
|
|
|
+ case LWC1:
|
|
|
|
+ case LDC1:
|
|
|
|
+ case SWC1:
|
|
|
|
+ case SDC1:
|
|
|
|
+ return 1;
|
|
|
|
+ default:
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int
|
|
|
|
+ldst(ulong op, Ureg *ur, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ int rn, rd, o, size, wr;
|
|
|
|
+ short off;
|
|
|
|
+ ulong ea;
|
|
|
|
+
|
|
|
|
+ /* we're using the COP1 macros, but the fields have diff'nt meanings */
|
|
|
|
+ o = OP(op);
|
|
|
|
+ rn = FMT(op);
|
|
|
|
+ off = op;
|
|
|
|
+ ea = REG(ur, rn) + off;
|
|
|
|
+ rd = REGT(op);
|
|
|
|
+//iprint("fpemu: ld/st (F%d)=%#lux + %d => ea %#lux\n", rn, REG(ur, rn), off, ea);
|
|
|
|
+
|
|
|
|
+ size = 4;
|
|
|
|
+ if (o == LDC1 || o == SDC1)
|
|
|
|
+ size = 8;
|
|
|
|
+ wr = (o == SWC1 || o == SDC1);
|
|
|
|
+ validaddr(ea, size, wr);
|
|
|
|
+
|
|
|
|
+ switch (o) {
|
|
|
|
+ case LWC1: /* load an fp register, rd, from memory */
|
|
|
|
+ case LDC1: /* load an fp register pair, (rd, rd+1), from memory */
|
|
|
|
+ fld(rd, ea, size, ufp);
|
|
|
|
+ break;
|
|
|
|
+ case SWC1: /* store an fp register, rd, into memory */
|
|
|
|
+ case SDC1: /* store an fp register pair, (rd, rd+1), into memory */
|
|
|
|
+ fst(ea, rd, size, ufp);
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ unimp(ur->pc, op, "unknown non-COP1 load or store");
|
|
|
|
+ return Failed;
|
|
|
|
+ }
|
|
|
|
+ return Advpc;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int
|
|
|
|
+cop1mov(Instr *ip)
|
|
|
|
+{
|
|
|
|
+ int fs, rt;
|
|
|
|
+ uvlong vl;
|
|
|
|
+ FPsave *ufp;
|
|
|
|
+ Ureg *ur;
|
|
|
|
+
|
|
|
|
+ fs = ip->rm; /* F(s) aka rm */
|
|
|
|
+ rt = ip->rn; /* R(t) aka rn */
|
|
|
|
+ ur = ip->ur;
|
|
|
|
+ ufp = ip->ufp;
|
|
|
|
+//iprint("fpemu: cop1 prob ld/st (R%d)=%#lux FREG%d\n", rn, REG(ip->ur, rn), rm);
|
|
|
|
+
|
|
|
|
+ /* MIPS fp register pairs are in little-endian order: low word first */
|
|
|
|
+ switch (ip->fmt) {
|
|
|
|
+ case MTC1:
|
|
|
|
+ /* load an fp register, F(s), from cpu register R(t) */
|
|
|
|
+ fld(fs, (uintptr)®(ur, rt), 4, ufp);
|
|
|
|
+ return Advpc;
|
|
|
|
+ case DMTC1:
|
|
|
|
+ /*
|
|
|
|
+ * load an fp register pair, (F(s), F(s+1)),
|
|
|
|
+ * from cpu registers (rt, rt+1)
|
|
|
|
+ */
|
|
|
|
+ iprint("fpemu: 64-bit DMTC1 may have words backward\n");
|
|
|
|
+ rt &= ~1;
|
|
|
|
+ vl = (uvlong)REG(ur, rt+1) << 32 | REG(ur, rt);
|
|
|
|
+ fld(fs & ~1, (uintptr)&vl, 8, ufp);
|
|
|
|
+ return Advpc;
|
|
|
|
+ case MFC1:
|
|
|
|
+ /* store an fp register, fs, into a cpu register rt */
|
|
|
|
+ fst((uintptr)®(ur, rt), fs, 4, ufp);
|
|
|
|
+ return Advpc;
|
|
|
|
+ case DMFC1:
|
|
|
|
+ /*
|
|
|
|
+ * store an fp register pair, (F(s), F(s+1)),
|
|
|
|
+ * into cpu registers (rt, rt+1)
|
|
|
|
+ */
|
|
|
|
+ iprint("fpemu: 64-bit DMFC1 may have words backward\n");
|
|
|
|
+ fst((uintptr)&vl, fs & ~1, 8, ufp);
|
|
|
|
+ rt &= ~1;
|
|
|
|
+ REG(ur, rt) = (ulong)vl;
|
|
|
|
+ REG(ur, rt+1) = vl>>32;
|
|
|
|
+ return Advpc;
|
|
|
|
+ case CFC1:
|
|
|
|
+ switch (fs) {
|
|
|
|
+ case Fpimp: /* MOVW FCR0,Rn */
|
|
|
|
+ REG(ur, rt) = 0x500; /* claim to be r4k */
|
|
|
|
+ break;
|
|
|
|
+ case Fpcsr:
|
|
|
|
+ REG(ur, rt) = ufp->fpcontrol;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ if(DBG(Dbgbasic))
|
|
|
|
+ iprint("MOVW FCR%d, R%d\n", fs, rt);
|
|
|
|
+ return Advpc;
|
|
|
|
+ case CTC1:
|
|
|
|
+ switch (fs) {
|
|
|
|
+ case Fpcsr:
|
|
|
|
+ ufp->fpcontrol = REG(ur, rt);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ if(DBG(Dbgbasic))
|
|
|
|
+ iprint("MOVW R%d, FCR%d\n", rt, fs);
|
|
|
|
+ return Advpc;
|
|
|
|
+ }
|
|
|
|
+ return Nomatch; /* not a load or store; keep looking */
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static char *
|
|
|
|
+decodefmt(int fmt)
|
|
|
|
+{
|
|
|
|
+ if (fmtnames[fmt])
|
|
|
|
+ return fmtnames[fmt];
|
|
|
|
+ else
|
|
|
|
+ return "GOK";
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static char *
|
|
|
|
+predname(int pred) /* predicate name */
|
|
|
|
+{
|
|
|
|
+ if (prednames[pred])
|
|
|
|
+ return prednames[pred];
|
|
|
|
+ else
|
|
|
|
+ return "GOK";
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int
|
|
|
|
+fcmpf(Internal m, Internal n, int, int cond)
|
|
|
|
+{
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ if(IsWeird(&m) || IsWeird(&n)){
|
|
|
|
+ /* BUG: should trap if not masked */
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ fpiround(&n);
|
|
|
|
+ fpiround(&m);
|
|
|
|
+ i = fpicmp(&m, &n); /* returns -1, 0, or 1 */
|
|
|
|
+ switch (cond) {
|
|
|
|
+ case 0: /* F - false */
|
|
|
|
+ case 1: /* UN - unordered */
|
|
|
|
+ return 0;
|
|
|
|
+ case 2: /* EQ */
|
|
|
|
+ case 3: /* UEQ */
|
|
|
|
+ return i == 0;
|
|
|
|
+ case 4: /* OLT */
|
|
|
|
+ case 5: /* ULT */
|
|
|
|
+ return i < 0;
|
|
|
|
+ case 6: /* OLE */
|
|
|
|
+ case 7: /* ULE */
|
|
|
|
+ return i <= 0;
|
|
|
|
+ case 8: /* SF */
|
|
|
|
+ case 9: /* NGLE - not >, < or = */
|
|
|
|
+ return 0;
|
|
|
|
+ case 10: /* SEQ */
|
|
|
|
+ return i == 0;
|
|
|
|
+ case 11: /* NGL */
|
|
|
|
+ return i != 0;
|
|
|
|
+ case 12: /* LT */
|
|
|
|
+ case 13: /* NGE */
|
|
|
|
+ return i < 0;
|
|
|
|
+ case 14: /* LE */
|
|
|
|
+ case 15: /* NGT */
|
|
|
|
+ return i <= 0;
|
|
|
|
+ }
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * assuming that ur->pc points to a branch instruction,
|
|
|
|
+ * change it to point to the branch's target and return it.
|
|
|
|
+ */
|
|
|
|
+static uintptr
|
|
|
|
+followbr(Ureg *ur)
|
|
|
|
+{
|
|
|
|
+ uintptr npc;
|
|
|
|
+
|
|
|
|
+ npc = branch(ur, up->fpsave.fpstatus);
|
|
|
|
+ if(npc == 0)
|
|
|
|
+ panic("fpemu: branch expected but not seen at %#p", ur->pc);
|
|
|
|
+ ur->pc = npc;
|
|
|
|
+ return npc;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* emulate COP1 instruction in branch delay slot */
|
|
|
|
+static void
|
|
|
|
+dsemu(Instr *ip, ulong dsinsn, Ureg *ur, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ uintptr npc;
|
|
|
|
+
|
|
|
|
+ npc = ur->pc; /* save ur->pc since fpemu will change it */
|
|
|
|
+ if(DBG(Dbgdelay))
|
|
|
|
+ iprint(">>> emulating br delay slot\n");
|
|
|
|
+
|
|
|
|
+ fpimips(ip->pc + 4, dsinsn, ur, ufp);
|
|
|
|
+
|
|
|
|
+ if(DBG(Dbgdelay))
|
|
|
|
+ iprint("<<< done emulating br delay slot\n");
|
|
|
|
+ ur->pc = npc;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * execute non-COP1 instruction in branch delay slot, in user mode with
|
|
|
|
+ * user registers, then trap so we can finish up and take the branch.
|
|
|
|
+ */
|
|
|
|
+static void
|
|
|
|
+dsexec(Instr *ip, Ureg *ur, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ ulong dsaddr, wpaddr;
|
|
|
|
+ Tos *tos;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * copy delay slot, EHB, EHB, EHB to tos->kscr, flush caches,
|
|
|
|
+ * point pc there, set watch point on tos->kscr[2], return.
|
|
|
|
+ * this is safe since we've already checked for branches (and FP
|
|
|
|
+ * instructions) in the delay slot, so the instruction can be
|
|
|
|
+ * executed at any address.
|
|
|
|
+ */
|
|
|
|
+ dsaddr = ip->pc + 4;
|
|
|
|
+ tos = (Tos*)(USTKTOP-sizeof(Tos));
|
|
|
|
+ tos->kscr[0] = *(ulong *)dsaddr;
|
|
|
|
+ tos->kscr[1] = 0xc0; /* EHB; we could use some trap instead */
|
|
|
|
+ tos->kscr[2] = 0xc0; /* EHB */
|
|
|
|
+ tos->kscr[3] = 0xc0; /* EHB */
|
|
|
|
+ dcflush(tos->kscr, sizeof tos->kscr);
|
|
|
|
+ icflush(tos->kscr, sizeof tos->kscr);
|
|
|
|
+
|
|
|
|
+ wpaddr = (ulong)&tos->kscr[2] & ~7; /* clear I/R/W bits */
|
|
|
|
+ ufp->fpdelayexec = 1;
|
|
|
|
+ ufp->fpdelaypc = ip->pc; /* remember branch ip->pc */
|
|
|
|
+ ufp->fpdelaysts = ufp->fpstatus; /* remember state of FPCOND */
|
|
|
|
+ ur->pc = (ulong)tos->kscr; /* restart in tos */
|
|
|
|
+ qlock(&watchlock); /* wait for first watchpoint */
|
|
|
|
+ setwatchlo0(wpaddr | 1<<2); /* doubleword addr(!); i-fetches only */
|
|
|
|
+ setwatchhi0(TLBPID(tlbvirt())<<16); /* asid; see mmu.c */
|
|
|
|
+ if (DBG(Dbgdelay))
|
|
|
|
+ iprint("fpemu: set %s watch point at %#lux, after br ds %#lux...",
|
|
|
|
+ up->text, wpaddr, *(ulong *)dsaddr);
|
|
|
|
+ /* return to user mode, await fpwatch() trap */
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void
|
|
|
|
+fpwatch(Ureg *ur) /* called on watch-point trap */
|
|
|
|
+{
|
|
|
|
+ FPsave *ufp;
|
|
|
|
+
|
|
|
|
+ ufp = &up->fpsave;
|
|
|
|
+ if(ufp->fpdelayexec == 0)
|
|
|
|
+ panic("fpwatch: unexpected watch trap");
|
|
|
|
+
|
|
|
|
+ /* assume we got here after branch-delay-slot execution */
|
|
|
|
+ ufp->fpdelayexec = 0;
|
|
|
|
+ setwatchlo0(0);
|
|
|
|
+ setwatchhi0(0);
|
|
|
|
+ qunlock(&watchlock);
|
|
|
|
+
|
|
|
|
+ ur->pc = ufp->fpdelaypc; /* pc of fp branch */
|
|
|
|
+ ur->cause &= BD; /* take no chances */
|
|
|
|
+ ufp->fpstatus = ufp->fpdelaysts;
|
|
|
|
+ followbr(ur); /* sets ur->pc to fp branch target */
|
|
|
|
+ if (DBG(Dbgdelay))
|
|
|
|
+ iprint("delay slot executed; resuming at %#lux\n", ur->pc);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static ulong
|
|
|
|
+validiw(uintptr pc)
|
|
|
|
+{
|
|
|
|
+ validaddr(pc, 4, 0);
|
|
|
|
+ return *(ulong*)pc;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * COP1 (6) | BRANCH (5) | cc (3) | likely | true | offset(16)
|
|
|
|
+ * cc = ip->rn >> 2; // assume cc == 0
|
|
|
|
+ */
|
|
|
|
+static int
|
|
|
|
+bremu(Instr *ip)
|
|
|
|
+{
|
|
|
|
+ int off, taken;
|
|
|
|
+ ulong dsinsn;
|
|
|
|
+ FPsave *ufp;
|
|
|
|
+ Ureg *ur;
|
|
|
|
+
|
|
|
|
+ if (ip->iw & (1<<17))
|
|
|
|
+ error("fpuemu: `likely' fp branch (obs)");
|
|
|
|
+ ufp = ip->ufp;
|
|
|
|
+ if (ufp->fpstatus & FPCOND)
|
|
|
|
+ taken = ip->iw & (1<<16); /* taken iff BCT */
|
|
|
|
+ else
|
|
|
|
+ taken = !(ip->iw & (1<<16)); /* taken iff BCF */
|
|
|
|
+ dsinsn = validiw(ip->pc + 4); /* delay slot addressible? */
|
|
|
|
+ if(DBG(Dbgdelay)){
|
|
|
|
+ off = (short)(ip->iw & MASK(16));
|
|
|
|
+ iprint("BFP%c\t%d(PC): %staken\n", (ip->iw & (1<<16)? 'T': 'F'),
|
|
|
|
+ off, taken? "": "not ");
|
|
|
|
+ iprint("\tdelay slot: %08lux\n", dsinsn);
|
|
|
|
+ delay(75);
|
|
|
|
+ }
|
|
|
|
+ ur = ip->ur;
|
|
|
|
+ assert(ur->pc == ip->pc);
|
|
|
|
+ if(!taken)
|
|
|
|
+ return Advpc; /* didn't branch, so return to delay slot */
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * fp branch taken; emulate or execute the delay slot, then jump.
|
|
|
|
+ */
|
|
|
|
+ if(dsinsn == NOP || dsinsn == MIPSNOP){
|
|
|
|
+ ; /* delay slot does nothing */
|
|
|
|
+ }else if(isbranch((ulong *)(ip->pc + 4)))
|
|
|
|
+ error("fpuemu: branch in fp branch delay slot");
|
|
|
|
+ else if (isfpop(dsinsn))
|
|
|
|
+ dsemu(ip, dsinsn, ur, ufp); /* emulate delay slot */
|
|
|
|
+ else{
|
|
|
|
+ /*
|
|
|
|
+ * The hard case: we need to execute the delay slot
|
|
|
|
+ * in user mode with user registers. Set a watch point,
|
|
|
|
+ * return to user mode, await fpwatch() trap.
|
|
|
|
+ */
|
|
|
|
+ dsexec(ip, ur, ufp);
|
|
|
|
+ return Leavepcret;
|
|
|
|
+ }
|
|
|
|
+ followbr(ur);
|
|
|
|
+ return Leavepc;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* interpret fp reg as fmt (float or double) and convert to Internal */
|
|
|
|
+static void
|
|
|
|
+reg2intern(Internal *i, int reg, int fmt, Ureg *ur)
|
|
|
|
+{
|
|
|
|
+ Double d;
|
|
|
|
+ FPsave *ufp;
|
|
|
|
+
|
|
|
|
+ /* we may see other fmt types on conversion or unary ops; ignore */
|
|
|
|
+ ufp = &up->fpsave;
|
|
|
|
+ switch (fmt) {
|
|
|
|
+ case Ffloat:
|
|
|
|
+ fpis2i(i, &FREG(ufp, reg));
|
|
|
|
+ internsane(i, ur);
|
|
|
|
+ break;
|
|
|
|
+ case Fdouble:
|
|
|
|
+ dreg2dbl(&d, reg, ufp);
|
|
|
|
+ fpid2i(i, &d);
|
|
|
|
+ internsane(i, ur);
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ SetQNaN(i); /* cause trouble if we try to use i */
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* convert Internal to fp reg as fmt (float or double) */
|
|
|
|
+static void
|
|
|
|
+intern2reg(int reg, Internal *i, int fmt, Ureg *ur)
|
|
|
|
+{
|
|
|
|
+ Double d;
|
|
|
|
+ FPsave *ufp;
|
|
|
|
+ Internal tmp;
|
|
|
|
+
|
|
|
|
+ ufp = &up->fpsave;
|
|
|
|
+ tmp = *i; /* make a disposable copy */
|
|
|
|
+ internsane(&tmp, ur);
|
|
|
|
+ switch (fmt) {
|
|
|
|
+ case Ffloat:
|
|
|
|
+ fpii2s(&FREG(ufp, reg), &tmp);
|
|
|
|
+ break;
|
|
|
|
+ case Fdouble:
|
|
|
|
+ fpii2d(&d, &tmp);
|
|
|
|
+ dbl2dreg(reg, &d, ufp);
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ panic("intern2reg: bad fmt %d", fmt);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * comparisons - encoded slightly differently than arithmetic:
|
|
|
|
+ * COP1 (6) | fmt(5) | ft (5) | fs (5) | # same
|
|
|
|
+ * cc (3) | 0 | A=0 | # diff, was REGD
|
|
|
|
+ * FC=11 | cond (4) # FUNC
|
|
|
|
+ */
|
|
|
|
+static int
|
|
|
|
+cmpemu(Instr *ip)
|
|
|
|
+{
|
|
|
|
+ int cc, cond;
|
|
|
|
+
|
|
|
|
+ cc = ip->rd >> 2;
|
|
|
|
+ cond = ip->o & MASK(4);
|
|
|
|
+ reg2intern(ip->fn, ip->rn, ip->fmt, ip->ur);
|
|
|
|
+ /* fpicmp args are swapped, so this is `n compare m' */
|
|
|
|
+ if (fcmpf(*ip->fm, *ip->fn, cc, cond))
|
|
|
|
+ ip->ufp->fpstatus |= FPCOND;
|
|
|
|
+ else
|
|
|
|
+ ip->ufp->fpstatus &= ~FPCOND;
|
|
|
|
+ if(DBG(Dbgbasic))
|
|
|
|
+ iprint("CMP%s.%s F%d,F%d =%d\n", predname(cond), ip->dfmt,
|
|
|
|
+ ip->rm, ip->rn, (ip->ufp->fpstatus & FPCOND? 1: 0));
|
|
|
|
+ if(DBG(Dbgregs)) {
|
|
|
|
+ intpr(ip->fm, ip->rm, ip->fmt, ip->ufp);
|
|
|
|
+ intpr(ip->fn, ip->rn, ip->fmt, ip->ufp);
|
|
|
|
+ delay(75);
|
|
|
|
+ }
|
|
|
|
+ return Advpc;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int
|
|
|
|
+binemu(Instr *ip)
|
|
|
|
+{
|
|
|
|
+ FP2 *fp;
|
|
|
|
+ Internal fd, prfd;
|
|
|
|
+ Internal *fn;
|
|
|
|
+
|
|
|
|
+ fp = &optab2[ip->o];
|
|
|
|
+ if(fp->f == nil)
|
|
|
|
+ unimp(ip->pc, ip->iw, "missing binary op");
|
|
|
|
+
|
|
|
|
+ /* convert the second operand */
|
|
|
|
+ fn = ip->fn;
|
|
|
|
+ reg2intern(fn, ip->rn, ip->fmt, ip->ur);
|
|
|
|
+ if(DBG(Dbgregs))
|
|
|
|
+ intpr(fn, ip->rn, ip->fmt, ip->ufp);
|
|
|
|
+
|
|
|
|
+ if(DBG(Dbgbasic)){
|
|
|
|
+ iprint("%s.%s\tF%d,F%d,F%d\n", fp->name, ip->dfmt,
|
|
|
|
+ ip->rm, ip->rn, ip->rd);
|
|
|
|
+ delay(75);
|
|
|
|
+ }
|
|
|
|
+ /*
|
|
|
|
+ * fn and fm are scratch Internals just for this instruction,
|
|
|
|
+ * so it's okay to let the fpi routines trash them in the course
|
|
|
|
+ * of operation.
|
|
|
|
+ */
|
|
|
|
+ /* NB: fpi routines take m and n (s and t) in reverse order */
|
|
|
|
+ (*fp->f)(fn, ip->fm, &fd);
|
|
|
|
+
|
|
|
|
+ /* convert the result */
|
|
|
|
+ if(DBG(Dbgregs))
|
|
|
|
+ prfd = fd; /* intern2reg modifies fd */
|
|
|
|
+ intern2reg(ip->rd, &fd, ip->fmt, ip->ur);
|
|
|
|
+ if(DBG(Dbgregs))
|
|
|
|
+ intpr(&prfd, ip->rd, ip->fmt, ip->ufp);
|
|
|
|
+ return Advpc;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int
|
|
|
|
+unaryemu(Instr *ip)
|
|
|
|
+{
|
|
|
|
+ int o;
|
|
|
|
+ FP1 *fp;
|
|
|
|
+ FPsave *ufp;
|
|
|
|
+
|
|
|
|
+ o = ip->o;
|
|
|
|
+ fp = &optab1[o];
|
|
|
|
+ if(DBG(Dbgbasic)){
|
|
|
|
+ iprint("%s.%s\tF%d,F%d\n", fp->name, ip->dfmt, ip->rm, ip->rd);
|
|
|
|
+ delay(75);
|
|
|
|
+ }
|
|
|
|
+ if(o == 6){ /* MOV */
|
|
|
|
+ int rm, rd;
|
|
|
|
+
|
|
|
|
+ ufp = ip->ufp;
|
|
|
|
+ rd = ip->rd;
|
|
|
|
+ rm = ip->rm;
|
|
|
|
+ if(ip->fmt == Fdouble){
|
|
|
|
+ rd &= ~1;
|
|
|
|
+ rm &= ~1;
|
|
|
|
+ FREG(ufp, rd+1) = FREG(ufp, rm+1);
|
|
|
|
+ }
|
|
|
|
+ FREG(ufp, rd) = FREG(ufp, rm);
|
|
|
|
+ }else{
|
|
|
|
+ Internal fdint, prfd;
|
|
|
|
+ Internal *fd;
|
|
|
|
+
|
|
|
|
+ switch(o){
|
|
|
|
+ case 5: /* ABS */
|
|
|
|
+ fd = ip->fm; /* use src Internal as dest */
|
|
|
|
+ fd->s = 0;
|
|
|
|
+ break;
|
|
|
|
+ case 7: /* NEG */
|
|
|
|
+ fd = ip->fm; /* use src Internal as dest */
|
|
|
|
+ fd->s ^= 1;
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ if(fp->f == nil)
|
|
|
|
+ unimp(ip->pc, ip->iw, "missing unary op");
|
|
|
|
+ fd = &fdint;
|
|
|
|
+ (*fp->f)(ip->fm, fd);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ if(DBG(Dbgregs))
|
|
|
|
+ prfd = *fd; /* intern2reg modifies fd */
|
|
|
|
+ intern2reg(ip->rd, fd, ip->fmt, ip->ur);
|
|
|
|
+ if(DBG(Dbgregs))
|
|
|
|
+ intpr(&prfd, ip->rd, ip->fmt, ip->ufp);
|
|
|
|
+ }
|
|
|
|
+ return Advpc;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int
|
|
|
|
+cvtemu(Instr *ip)
|
|
|
|
+{
|
|
|
|
+ FPcvt *fp;
|
|
|
|
+
|
|
|
|
+ fp = &optabcvt[ip->o];
|
|
|
|
+ if(fp->f == nil)
|
|
|
|
+ unimp(ip->pc, ip->iw, "missing conversion op");
|
|
|
|
+ if(DBG(Dbgbasic)){
|
|
|
|
+ iprint("%s.%s\tF%d,F%d\n", fp->name, ip->dfmt, ip->rm, ip->rd);
|
|
|
|
+ delay(75);
|
|
|
|
+ }
|
|
|
|
+ (*fp->f)(ip->fmt, ip->rm, ip->rd, ip->ur, ip->ufp);
|
|
|
|
+ return Advpc;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void
|
|
|
|
+cop1decode(Instr *ip, ulong iw, ulong pc, Ureg *ur, FPsave *ufp,
|
|
|
|
+ Internal *imp, Internal *inp)
|
|
|
|
+{
|
|
|
|
+ ip->iw = iw;
|
|
|
|
+ ip->pc = pc;
|
|
|
|
+ ip->ur = ur;
|
|
|
|
+ ip->ufp = ufp;
|
|
|
|
+ ip->fmt = FMT(iw);
|
|
|
|
+ ip->rm = REGS(iw); /* 1st operand */
|
|
|
|
+ ip->rn = REGT(iw); /* 2nd operand (ignored by unary ops) */
|
|
|
|
+ ip->rd = REGD(iw); /* destination */
|
|
|
|
+ ip->o = FUNC(iw);
|
|
|
|
+ ip->fm = imp;
|
|
|
|
+ ip->fn = inp;
|
|
|
|
+ if (DBG(Dbgbasic))
|
|
|
|
+ ip->dfmt = decodefmt(ip->fmt);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void
|
|
|
|
+fpstuck(uintptr pc, FPsave *fp)
|
|
|
|
+{
|
|
|
|
+ USED(pc);
|
|
|
|
+ if(!(DBG(Dbgbasic)))
|
|
|
|
+ return;
|
|
|
|
+ if (fp->fppc == pc) {
|
|
|
|
+ fp->fpcnt++;
|
|
|
|
+ if (fp->fpcnt > 4)
|
|
|
|
+ panic("fpuemu: cpu%d stuck at pid %ld %s pc %#p "
|
|
|
|
+ "instr %#8.8lux", m->machno, up->pid, up->text,
|
|
|
|
+ pc, *(ulong *)pc);
|
|
|
|
+ } else {
|
|
|
|
+ fp->fppc = pc;
|
|
|
|
+ fp->fpcnt = 0;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void
|
|
|
|
+_dbgstuck(ulong pc, Ureg *ur, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ fpstuck(pc, ufp);
|
|
|
|
+ if (DBG(Dbgdelay) && ur->cause & BD)
|
|
|
|
+ iprint("fpuemu: FP in a branch delay slot\n");
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* decode the opcode and call common emulation code */
|
|
|
|
+static int
|
|
|
|
+fpimips(ulong pc, ulong op, Ureg *ur, FPsave *ufp)
|
|
|
|
+{
|
|
|
|
+ int r, o;
|
|
|
|
+ Instr insn;
|
|
|
|
+ Instr *ip;
|
|
|
|
+ Internal im, in;
|
|
|
|
+
|
|
|
|
+ /* note: would update fault status here if we noted numeric exceptions */
|
|
|
|
+ dummyr0 = 0;
|
|
|
|
+ switch (OP(op)) {
|
|
|
|
+ case LWC1:
|
|
|
|
+ case LDC1:
|
|
|
|
+ case SWC1:
|
|
|
|
+ case SDC1:
|
|
|
|
+ dbgstuck(pc, ur, ufp);
|
|
|
|
+ return ldst(op, ur, ufp);
|
|
|
|
+ default:
|
|
|
|
+ unimp(pc, op, "non-FP instruction");
|
|
|
|
+ return Failed;
|
|
|
|
+ case COP1:
|
|
|
|
+ dbgstuck(pc, ur, ufp);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ip = &insn;
|
|
|
|
+ cop1decode(ip, op, pc, ur, ufp, &im, &in);
|
|
|
|
+ if (ip->fmt == BRANCH) { /* FP conditional branch? */
|
|
|
|
+ r = bremu(ip);
|
|
|
|
+ if(DBG(Dbgdelay)){
|
|
|
|
+ iprint("resuming after br, at %#lux", ur->pc);
|
|
|
|
+ if (r == Leavepcret)
|
|
|
|
+ iprint("..."); /* we'll be right back */
|
|
|
|
+ else
|
|
|
|
+ iprint("\n");
|
|
|
|
+ }
|
|
|
|
+ return r;
|
|
|
|
+ }
|
|
|
|
+ o = ip->o;
|
|
|
|
+ if (o == 0 && ip->rd == 0) { /* *[TF]C1 load or store? */
|
|
|
|
+ r = cop1mov(ip);
|
|
|
|
+ if (r != Nomatch)
|
|
|
|
+ return r;
|
|
|
|
+ /* else wasn't a [tf]c1 move */
|
|
|
|
+ }
|
|
|
|
+ /* don't decode & print rm yet; it might be an integer */
|
|
|
|
+ if(o >= 32 && o < 40) /* conversion? */
|
|
|
|
+ return cvtemu(ip);
|
|
|
|
+
|
|
|
|
+ /* decode the mandatory operand, rm */
|
|
|
|
+ reg2intern(ip->fm, ip->rm, ip->fmt, ip->ur);
|
|
|
|
+ if(DBG(Dbgregs))
|
|
|
|
+ intpr(&im, ip->rm, ip->fmt, ip->ufp);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * arithmetic
|
|
|
|
+ * all operands must be of the same format
|
|
|
|
+ */
|
|
|
|
+ if(o >= 4 && o < 32) /* monadic */
|
|
|
|
+ return unaryemu(ip);
|
|
|
|
+ if(o < 4) /* the few binary ops */
|
|
|
|
+ return binemu(ip);
|
|
|
|
+
|
|
|
|
+ if(o >= 48 && (ip->rd & MASK(2)) == 0) /* comparison? */
|
|
|
|
+ return cmpemu(ip);
|
|
|
|
+
|
|
|
|
+ /* don't recognise the opcode */
|
|
|
|
+ if(DBG(Dbgbasic))
|
|
|
|
+ iprint("fp at %#lux: %#8.8lux BOGON\n", pc, op);
|
|
|
|
+ unimp(pc, op, "unknown opcode");
|
|
|
|
+ return Failed;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static FPsave *
|
|
|
|
+fpinit(Ureg *ur)
|
|
|
|
+{
|
|
|
|
+ int i, n;
|
|
|
|
+ Double d;
|
|
|
|
+ FPsave *ufp;
|
|
|
|
+ Internal tmp;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * because all the emulated fp state is in the proc structure,
|
|
|
|
+ * it need not be saved/restored
|
|
|
|
+ */
|
|
|
|
+ ufp = &up->fpsave;
|
|
|
|
+ switch(up->fpstate){
|
|
|
|
+ case FPactive:
|
|
|
|
+ case FPinactive:
|
|
|
|
+ error("fpu (in)active but fp is emulated");
|
|
|
|
+ case FPinit:
|
|
|
|
+ up->fpstate = FPemu;
|
|
|
|
+ ufp->fpcontrol = 0;
|
|
|
|
+ ufp->fpstatus = 0;
|
|
|
|
+ ufp->fpcnt = 0;
|
|
|
|
+ ufp->fppc = 0;
|
|
|
|
+ for(n = 0; n < Nfpregs-1; n += 2) {
|
|
|
|
+ if (fpconst[n].h == 0) /* uninitialised consts */
|
|
|
|
+ i = FZERO; /* treated as 0.0 */
|
|
|
|
+ else
|
|
|
|
+ i = n;
|
|
|
|
+ tmp = fpconst[i];
|
|
|
|
+ internsane(&tmp, ur);
|
|
|
|
+ fpii2d(&d, &tmp);
|
|
|
|
+ dbl2dreg(n, &d, ufp);
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ return ufp;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * called from trap.c's CCPU case, only to deal with user-mode
|
|
|
|
+ * instruction faults.
|
|
|
|
+ *
|
|
|
|
+ * libc/mips/lock.c reads FCR0 to determine what kind of system
|
|
|
|
+ * this is (and thus if it can use LL/SC or must use some
|
|
|
|
+ * system-dependent method). So we simulate the move from FCR0.
|
|
|
|
+ * All modern mips have LL/SC, so just claim to be an r4k.
|
|
|
|
+ */
|
|
|
|
+int
|
|
|
|
+fpuemu(Ureg *ureg)
|
|
|
|
+{
|
|
|
|
+ int s;
|
|
|
|
+ uintptr pc;
|
|
|
|
+ ulong iw, r;
|
|
|
|
+
|
|
|
|
+ if(waserror()){
|
|
|
|
+ postnote(up, 1, up->errstr, NDebug);
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(up->fpstate & FPillegal)
|
|
|
|
+ error("floating point in note handler");
|
|
|
|
+ if(up->fpsave.fpdelayexec)
|
|
|
|
+ panic("fpuemu: entered with outstanding watch trap");
|
|
|
|
+
|
|
|
|
+ pc = ureg->pc;
|
|
|
|
+ validaddr(pc, 4, 0);
|
|
|
|
+ /* only the first instruction can be in a branch delay slot */
|
|
|
|
+ if(ureg->cause & BD) {
|
|
|
|
+ pc += 4;
|
|
|
|
+ validaddr(pc, 4, 0); /* check branch delay slot */
|
|
|
|
+ }
|
|
|
|
+ iw = *(ulong*)pc;
|
|
|
|
+ do {
|
|
|
|
+ /* recognise & optimise a common case */
|
|
|
|
+ if (iw == 0x44410000){ /* MOVW FCR0,R1 (CFC1) */
|
|
|
|
+ ureg->r1 = 0x500; /* claim an r4k */
|
|
|
|
+ r = Advpc;
|
|
|
|
+ if (DBG(Dbgbasic))
|
|
|
|
+ iprint("faked MOVW FCR0,R1\n");
|
|
|
|
+ }else{
|
|
|
|
+ s = spllo();
|
|
|
|
+ if(waserror()){
|
|
|
|
+ splx(s);
|
|
|
|
+ nexterror();
|
|
|
|
+ }
|
|
|
|
+ r = fpimips(pc, iw, ureg, fpinit(ureg));
|
|
|
|
+ splx(s);
|
|
|
|
+ poperror();
|
|
|
|
+ if (r == Failed || r == Leavepcret)
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ if (r == Advpc) /* simulation succeeded, advance the pc? */
|
|
|
|
+ if(ureg->cause & BD)
|
|
|
|
+ followbr(ureg);
|
|
|
|
+ else
|
|
|
|
+ ureg->pc += 4;
|
|
|
|
+ ureg->cause &= ~BD;
|
|
|
|
+
|
|
|
|
+ pc = ureg->pc;
|
|
|
|
+ iw = validiw(pc);
|
|
|
|
+ while (iw == NOP || iw == MIPSNOP) { /* skip NOPs */
|
|
|
|
+ pc += 4;
|
|
|
|
+ ureg->pc = pc;
|
|
|
|
+ iw = validiw(pc);
|
|
|
|
+ }
|
|
|
|
+ /* is next ins'n also FP? */
|
|
|
|
+ } while (isfpop(iw));
|
|
|
|
+ if (r == Failed){
|
|
|
|
+ iprint("fpuemu: fp emulation failed for %#lux"
|
|
|
|
+ " at pc %#p in %lud %s\n",
|
|
|
|
+ iw, ureg->pc, up->pid, up->text);
|
|
|
|
+ unimp(ureg->pc, iw, "no fp instruction");
|
|
|
|
+ /* no return */
|
|
|
|
+ }
|
|
|
|
+ ureg->cause &= ~BD;
|
|
|
|
+ poperror();
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int
|
|
|
|
+isbranch(ulong *pc)
|
|
|
|
+{
|
|
|
|
+ ulong iw;
|
|
|
|
+
|
|
|
|
+ iw = *(ulong*)pc;
|
|
|
|
+ /*
|
|
|
|
+ * Integer unit jumps first
|
|
|
|
+ */
|
|
|
|
+ switch(iw>>26){
|
|
|
|
+ case 0: /* SPECIAL: JR or JALR */
|
|
|
|
+ switch(iw&0x3F){
|
|
|
|
+ case 0x09: /* JALR */
|
|
|
|
+ case 0x08: /* JR */
|
|
|
|
+ return 1;
|
|
|
|
+ default:
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ case 1: /* BCOND */
|
|
|
|
+ switch((iw>>16) & 0x1F){
|
|
|
|
+ case 0x10: /* BLTZAL */
|
|
|
|
+ case 0x00: /* BLTZ */
|
|
|
|
+ case 0x11: /* BGEZAL */
|
|
|
|
+ case 0x01: /* BGEZ */
|
|
|
|
+ return 1;
|
|
|
|
+ default:
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ case 3: /* JAL */
|
|
|
|
+ case 2: /* JMP */
|
|
|
|
+ case 4: /* BEQ */
|
|
|
|
+ case 5: /* BNE */
|
|
|
|
+ case 6: /* BLEZ */
|
|
|
|
+ case 7: /* BGTZ */
|
|
|
|
+ return 1;
|
|
|
|
+ }
|
|
|
|
+ /*
|
|
|
|
+ * Floating point unit jumps
|
|
|
|
+ */
|
|
|
|
+ if((iw>>26) == COP1)
|
|
|
|
+ switch((iw>>16) & 0x3C1){
|
|
|
|
+ case 0x101: /* BCT */
|
|
|
|
+ case 0x181: /* BCT */
|
|
|
|
+ case 0x100: /* BCF */
|
|
|
|
+ case 0x180: /* BCF */
|
|
|
|
+ return 1;
|
|
|
|
+ }
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * if current instruction is a (taken) branch, return new pc and,
|
|
|
|
+ * for jump-and-links, set r31.
|
|
|
|
+ */
|
|
|
|
+ulong
|
|
|
|
+branch(Ureg *ur, ulong fcr31)
|
|
|
|
+{
|
|
|
|
+ ulong iw, npc, rs, rt, rd, offset, targ, next;
|
|
|
|
+
|
|
|
|
+ iw = ur->pc;
|
|
|
|
+ iw = *(ulong*)iw;
|
|
|
|
+ rs = (iw>>21) & 0x1F;
|
|
|
|
+ if(rs)
|
|
|
|
+ rs = REG(ur, rs);
|
|
|
|
+ rt = (iw>>16) & 0x1F;
|
|
|
|
+ if(rt)
|
|
|
|
+ rt = REG(ur, rt);
|
|
|
|
+ offset = iw & ((1<<16)-1);
|
|
|
|
+ if(offset & (1<<15)) /* sign extend */
|
|
|
|
+ offset |= ~((1<<16)-1);
|
|
|
|
+ offset <<= 2;
|
|
|
|
+ targ = ur->pc + 4 + offset; /* branch target */
|
|
|
|
+ /* ins'n after delay slot (assumes delay slot has already been exec'd) */
|
|
|
|
+ next = ur->pc + 8;
|
|
|
|
+ /*
|
|
|
|
+ * Integer unit jumps first
|
|
|
|
+ */
|
|
|
|
+ switch(iw>>26){
|
|
|
|
+ case 0: /* SPECIAL: JR or JALR */
|
|
|
|
+ switch(iw&0x3F){
|
|
|
|
+ case 0x09: /* JALR */
|
|
|
|
+ rd = (iw>>11) & 0x1F;
|
|
|
|
+ if(rd)
|
|
|
|
+ REG(ur, rd) = next;
|
|
|
|
+ /* fall through */
|
|
|
|
+ case 0x08: /* JR */
|
|
|
|
+ return rs;
|
|
|
|
+ default:
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ case 1: /* BCOND */
|
|
|
|
+ switch((iw>>16) & 0x1F){
|
|
|
|
+ case 0x10: /* BLTZAL */
|
|
|
|
+ ur->r31 = next;
|
|
|
|
+ /* fall through */
|
|
|
|
+ case 0x00: /* BLTZ */
|
|
|
|
+ if((long)rs < 0)
|
|
|
|
+ return targ;
|
|
|
|
+ return next;
|
|
|
|
+ case 0x11: /* BGEZAL */
|
|
|
|
+ ur->r31 = next;
|
|
|
|
+ /* fall through */
|
|
|
|
+ case 0x01: /* BGEZ */
|
|
|
|
+ if((long)rs >= 0)
|
|
|
|
+ return targ;
|
|
|
|
+ return next;
|
|
|
|
+ default:
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ case 3: /* JAL */
|
|
|
|
+ ur->r31 = next;
|
|
|
|
+ /* fall through */
|
|
|
|
+ case 2: /* JMP */
|
|
|
|
+ npc = iw & ((1<<26)-1);
|
|
|
|
+ npc <<= 2;
|
|
|
|
+ return npc | (ur->pc&0xF0000000);
|
|
|
|
+ case 4: /* BEQ */
|
|
|
|
+ if(rs == rt)
|
|
|
|
+ return targ;
|
|
|
|
+ return next;
|
|
|
|
+ case 5: /* BNE */
|
|
|
|
+ if(rs != rt)
|
|
|
|
+ return targ;
|
|
|
|
+ return next;
|
|
|
|
+ case 6: /* BLEZ */
|
|
|
|
+ if((long)rs <= 0)
|
|
|
|
+ return targ;
|
|
|
|
+ return next;
|
|
|
|
+ case 7: /* BGTZ */
|
|
|
|
+ if((long)rs > 0)
|
|
|
|
+ return targ;
|
|
|
|
+ return next;
|
|
|
|
+ }
|
|
|
|
+ /*
|
|
|
|
+ * Floating point unit jumps
|
|
|
|
+ */
|
|
|
|
+ if((iw>>26) == COP1)
|
|
|
|
+ switch((iw>>16) & 0x3C1){
|
|
|
|
+ case 0x101: /* BCT */
|
|
|
|
+ case 0x181: /* BCT */
|
|
|
|
+ if(fcr31 & FPCOND)
|
|
|
|
+ return targ;
|
|
|
|
+ return next;
|
|
|
|
+ case 0x100: /* BCF */
|
|
|
|
+ case 0x180: /* BCF */
|
|
|
|
+ if(!(fcr31 & FPCOND))
|
|
|
|
+ return targ;
|
|
|
|
+ return next;
|
|
|
|
+ }
|
|
|
|
+ /* shouldn't get here */
|
|
|
|
+ return 0;
|
|
|
|
+}
|