| /* sigma_fp.c: XDS Sigma floating point simulator | |
| Copyright (c) 2007-2008, Robert M Supnik | |
| Permission is hereby granted, free of charge, to any person obtaining a | |
| copy of this software and associated documentation files (the "Software"), | |
| to deal in the Software without restriction, including without limitation | |
| the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| and/or sell copies of the Software, and to permit persons to whom the | |
| Software is furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in | |
| all copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
| ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| Except as contained in this notice, the name of Robert M Supnik shall not be | |
| used in advertising or otherwise to promote the sale, use or other dealings | |
| in this Software without prior written authorization from Robert M Supnik. | |
| */ | |
| #include "sigma_defs.h" | |
| #define UFP_V_GUARD 4 | |
| #define UFP_NORM (FP_NORM << UFP_V_GUARD) | |
| #define UFP_CARRY (UFP_NORM << 4) | |
| #define UFP_FRHI (UFP_CARRY|UFP_NORM|FP_M_FRHI) | |
| #define UFP_FRLO 0xFFFFFFFF | |
| /* Double precision fraction add/subtract/compare */ | |
| /* Note: UFP_ADD (s, r, r) will not work!!! */ | |
| #define UFP_ADD(s1,s2,d) do { \ | |
| d.l = (s1.l + s2.l) & UFP_FRLO; \ | |
| d.h = (s1.h + s2.h + (d.l < s2.l)) & UFP_FRHI; \ | |
| } while (0) | |
| #define UFP_SUB(s1,s2,d) do { \ | |
| d.h = (s1.h - s2.h - (s1.l < s2.l)) & UFP_FRHI; \ | |
| d.l = (s1.l - s2.l) & UFP_FRLO; \ | |
| } while (0) | |
| #define UFP_GE(s1,s2) ((s1.h > s2.h) || \ | |
| ((s1.h == s2.h) && (s1.l >= s2.l))) | |
| /* Variable and constant shifts; for constants, 0 < k < 32 */ | |
| #define UFP_RSH_V(v,s) do { \ | |
| if ((s) < 32) { \ | |
| v.l = ((v.l >> (s)) | \ | |
| ( v.h << (32 - (s)))) & UFP_FRLO; \ | |
| v.h = v.h >> (s); \ | |
| } \ | |
| else if ((s) < 64) { \ | |
| v.l = v.h >> ((s) - 32); \ | |
| v.h = 0; \ | |
| } \ | |
| else v.l = v.h = 0; \ | |
| } while (0) | |
| #define UFP_RSH_K(v,s) do { \ | |
| v.l = ((v.l >> (s)) | \ | |
| (v.h << (32 - (s)))) & UFP_FRLO; \ | |
| v.h = v.h >> (s); \ | |
| } while (0) | |
| #define UFP_LSH_K(v,s) do { \ | |
| v.h = ((v.h << (s)) | \ | |
| (v.l >> (32 - (s)))) & UFP_FRHI; \ | |
| v.l = (v.l << (s)) & UFP_FRLO; \ | |
| } while (0) | |
| #define UFP_RSH_KP(v,s) do { \ | |
| v->l = ((v->l >> (s)) | \ | |
| (v->h << (32 - (s)))) & UFP_FRLO; \ | |
| v->h = v->h >> (s); \ | |
| } while (0) | |
| #define UFP_LSH_KP(v,s) do { \ | |
| v->h = ((v->h << (s)) | \ | |
| (v->l >> (32 - (s)))) & UFP_FRHI; \ | |
| v->l = (v->l << (s)) & UFP_FRLO; \ | |
| } while (0) | |
| typedef struct { | |
| uint32 sign; | |
| int32 exp; | |
| uint32 h; | |
| uint32 l; | |
| } ufp_t; | |
| extern uint32 *R; | |
| extern uint32 PSW1; | |
| extern uint32 CC; | |
| void fp_unpack (uint32 hi, uint32 lo, ufp_t *dst); | |
| t_bool fp_clnzro (ufp_t *src, t_bool abnorm); | |
| uint32 fp_pack (ufp_t *src, uint32 rn, t_bool dbl, t_bool rndtrap); | |
| uint32 fp_norm (ufp_t *src); | |
| uint32 fp (uint32 op, uint32 rn, uint32 bva) | |
| { | |
| uint32 rh, rl, mh, ml, i, ediff, nsh; | |
| t_bool s1nz, s2nz; | |
| t_bool dbl = ((op & 0x20) == 0); | |
| ufp_t fop1, fop2, t; | |
| ufp_t res = { 0, 0, 0, 0 }; | |
| uint32 tr; | |
| if (dbl) { /* double prec? */ | |
| rh = R[rn]; /* get reg operands */ | |
| rl = R[rn|1]; | |
| if ((tr = ReadD (bva, &mh, &ml, VR)) != 0) /* get mem word */ | |
| return tr; | |
| } | |
| else { /* single precision */ | |
| rh = R[rn]; /* pad to double */ | |
| rl = 0; | |
| if ((tr = ReadW (bva, &mh, VR)) != 0) | |
| return tr; | |
| ml = 0; | |
| } | |
| fp_unpack (rh, rl, &fop1); /* unpack, test */ | |
| fp_unpack (mh, ml, &fop2); | |
| CC = 0; | |
| switch (op) { /* case on opcode */ | |
| case OP_FSS: /* subtract */ | |
| case OP_FSL: | |
| fop2.sign = fop2.sign ^ 1; /* invert mem sign */ | |
| /* fall through */ | |
| case OP_FAS: /* add */ | |
| case OP_FAL: | |
| s1nz = fp_clnzro (&fop1, TRUE); /* test, clean op1 */ | |
| s2nz = fp_clnzro (&fop2, TRUE); | |
| if (!s1nz) /* op1 = 0? res = op2 */ | |
| res = fop2; | |
| else if (!s2nz) /* op2 = 0? res = op1 */ | |
| res = fop1; | |
| else { /* both non-zero */ | |
| if (fop1.exp < fop2.exp) { /* exp1 < exp2? */ | |
| t = fop2; /* swap operands */ | |
| fop2 = fop1; | |
| fop1 = t; | |
| } | |
| ediff = fop1.exp - fop2.exp; /* exp difference */ | |
| res.sign = fop1.sign; /* result sign, exp */ | |
| res.exp = fop1.exp; | |
| if (ediff) { /* any difference? */ | |
| UFP_RSH_V (fop2, ediff * 4); /* shift frac */ | |
| if (dbl) { /* double? */ | |
| if ((PSW1 & PSW1_FR) == 0) /* rounding off? */ | |
| fop2.l &= ~0xF; /* no guard */ | |
| } | |
| else fop2.l = 0; /* single? clr lo */ | |
| } | |
| if (fop1.sign ^ fop2.sign) { /* eff subtract */ | |
| if (UFP_GE (fop1, fop2)) { /* fop1 >= fop2? */ | |
| UFP_SUB (fop1, fop2, res); /* sub fractions */ | |
| } | |
| else { /* fop2 > fop1 */ | |
| UFP_SUB (fop2, fop1, res); /* rev subtract */ | |
| res.sign = fop2.sign; /* change signs */ | |
| } | |
| } /* end subtract */ | |
| else { /* eff add */ | |
| UFP_ADD (fop1, fop2, res); /* add fractions */ | |
| if (res.h & UFP_CARRY) { /* carry out? */ | |
| UFP_RSH_K (res, 4); /* renormalize */ | |
| res.exp = res.exp + 1; /* incr exp */ | |
| } | |
| } /* end add */ | |
| } /* end nz operands */ | |
| if (!dbl) /* single? clr lo */ | |
| res.l = 0; | |
| if ((PSW1 & PSW1_FN) == 0) { /* postnormalize? */ | |
| if ((res.h | res.l) == 0) { /* result zero? */ | |
| CC = CC1; /* set signif flag */ | |
| if (PSW1 & PSW1_FS) /* trap enabled? */ | |
| return TR_FLT; | |
| return fp_pack (&res, rn, dbl, FALSE); /* pack up */ | |
| } | |
| nsh = fp_norm (&res); /* normalize */ | |
| if ((res.exp < 0) && /* underflow? */ | |
| !(PSW1 & PSW1_FZ) && /* !FN */ | |
| (PSW1 & PSW1_FS) && /* FS */ | |
| (nsh > 2)) { /* shifts > 2? */ | |
| CC = CC1 | (res.sign? CC4: CC3); /* signif CC's */ | |
| return TR_FLT; /* trap */ | |
| } /* end if underflow */ | |
| else if (nsh > 2) { /* shifts > 2? */ | |
| CC |= CC1 | (res.sign? CC4: CC3); /* set CC1 */ | |
| if (PSW1 & PSW1_FS) /* trap enabled? */ | |
| return TR_FLT; | |
| } | |
| } /* end if postnorm */ | |
| return fp_pack (&res, rn, dbl, TRUE); /* pack result */ | |
| case OP_FMS: | |
| case OP_FML: /* floating multiply */ | |
| s1nz = fp_clnzro (&fop1, FALSE); /* test, clean op1 */ | |
| s2nz = fp_clnzro (&fop2, FALSE); | |
| if (s1nz && s2nz) { /* both non-zero? */ | |
| fp_norm (&fop1); /* prenormalize */ | |
| fp_norm (&fop2); | |
| UFP_RSH_K (fop2, 4); /* undo guard */ | |
| res.sign = fop1.sign ^ fop2.sign; /* result sign */ | |
| res.exp = fop1.exp + fop2.exp - FP_BIAS; /* result exp */ | |
| if (!dbl) { /* 24b x 24b? */ | |
| for (i = 0; i < 24; i++) { /* 24 iterations */ | |
| if (fop2.h & 1) | |
| res.h = res.h + fop1.h; /* add hi only */ | |
| UFP_RSH_K (res, 1); /* shift dp res */ | |
| fop2.h = fop2.h >> 1; | |
| } | |
| res.l = 0; /* single prec */ | |
| } | |
| else { /* some low 0's */ | |
| for (i = 0; i < 56; i++) { /* 56 iterations */ | |
| if (fop2.l & 1) { | |
| UFP_ADD (res, fop1, res); | |
| } | |
| UFP_RSH_K (res, 1); | |
| UFP_RSH_K (fop2, 1); | |
| } | |
| } | |
| fp_norm (&res); /* normalize result */ | |
| } | |
| return fp_pack (&res, rn, dbl, TRUE); /* pack result */ | |
| case OP_FDS: | |
| case OP_FDL: /* floating divide */ | |
| s1nz = fp_clnzro (&fop1, FALSE); /* test, clean op1 */ | |
| s2nz = fp_clnzro (&fop2, FALSE); | |
| if (!s2nz) { /* divide by zero? */ | |
| CC = CC2; /* set CC2 */ | |
| return TR_FLT; /* trap */ | |
| } | |
| if (s1nz) { /* divd non-zero? */ | |
| fp_norm (&fop1); /* prenormalize */ | |
| fp_norm (&fop2); | |
| res.sign = fop1.sign ^ fop2.sign; /* result sign */ | |
| res.exp = fop1.exp - fop2.exp + FP_BIAS; /* result exp */ | |
| if (!UFP_GE (fop1, fop2)) { | |
| UFP_LSH_K (fop1, 4); /* ensure success */ | |
| } | |
| else res.exp = res.exp + 1; /* incr exponent */ | |
| for (i = 0; i < (uint32)(dbl? 15: 7); i++) {/* 7/15 hex digits */ | |
| UFP_LSH_K (res, 4); /* shift quotient */ | |
| while (UFP_GE (fop1, fop2)) { /* while sub works */ | |
| UFP_SUB (fop1, fop2, fop1); /* decrement */ | |
| res.l = res.l + 1; /* add quo bit */ | |
| } | |
| UFP_LSH_K (fop1, 4); /* shift divd */ | |
| } /* end hex loop */ | |
| if (!dbl) { /* single? */ | |
| res.h = res.l; /* move quotient */ | |
| res.l = 0; | |
| } | |
| fp_norm (&res); /* normalize result */ | |
| } | |
| return fp_pack (&res, rn, dbl, TRUE); /* pack result */ | |
| } /* end case */ | |
| return SCPE_IERR; | |
| } | |
| void ShiftF (uint32 rn, uint32 stype, uint32 sc) | |
| { | |
| uint32 opnd, opnd1; | |
| ufp_t src; | |
| opnd = R[rn]; /* get operands */ | |
| opnd1 = stype? R[rn|1]: 0; /* zextend single */ | |
| fp_unpack (opnd, opnd1, &src); /* unpack */ | |
| CC = 0; | |
| if (sc & SCSIGN) { /* right? */ | |
| sc = SHF_M_SC + 1 - sc; | |
| while (sc > 0) { /* while count */ | |
| UFP_RSH_K (src, 4); /* shift right hex */ | |
| if (stype) /* zero guard */ | |
| src.l &= ~0xF; | |
| else src.h &= ~0xF; | |
| src.exp++; /* incr exp */ | |
| sc--; | |
| if (src.exp > FP_M_EXP) { /* overflow? */ | |
| CC |= CC2; /* set CC2, stop */ | |
| break; | |
| } | |
| } /* end while */ | |
| if ((src.h | src.l) == 0) { /* result 0? */ | |
| if (stype) /* result is true 0 */ | |
| R[rn|1] = 0; | |
| R[rn] = 0; | |
| CC = 0; | |
| return; | |
| } | |
| } | |
| else { /* left */ | |
| if ((src.h | src.l) == 0) { /* fraction 0? */ | |
| if (stype) /* result is true 0 */ | |
| R[rn|1] = 0; | |
| R[rn] = 0; | |
| CC = CC1; | |
| return; | |
| } | |
| while ((sc > 0) && ((src.h & UFP_NORM) == 0)) { /* while count & !norm */ | |
| UFP_LSH_K (src, 4); /* hex shift left */ | |
| src.exp--; /* decr exp */ | |
| sc--; | |
| if (src.exp < 0) { /* underflow? */ | |
| CC |= CC2; /* set CC2, stop */ | |
| break; | |
| } | |
| } /* end while */ | |
| if (src.h & UFP_NORM) /* normalized? */ | |
| CC |= CC1; /* set CC1 */ | |
| } | |
| fp_pack (&src, rn, stype, FALSE); /* pack result */ | |
| return; | |
| } | |
| void fp_unpack (uint32 hi, uint32 lo, ufp_t *dst) | |
| { | |
| dst->sign = FP_GETSIGN (hi); /* get sign */ | |
| if (dst->sign) /* negative? */ | |
| NEG_D (hi, lo); /* 2's compl */ | |
| dst->h = FP_GETFRHI (hi); /* get fraction */ | |
| dst->l = FP_GETFRLO (lo); | |
| dst->exp = FP_GETEXP (hi); /* get exp */ | |
| UFP_LSH_KP (dst, 4); /* guard result */ | |
| return; | |
| } | |
| /* Test for and clean a floating point zero | |
| abnorm defines whether to allow "abnormal" zeros */ | |
| t_bool fp_clnzro (ufp_t *src, t_bool abnorm) | |
| { | |
| if (((src->h | src->l) == 0) && /* frac zero and */ | |
| (!abnorm || (src->exp == 0))) { /* exp zero or !ab */ | |
| src->sign = 0; /* true zero */ | |
| src->exp = 0; | |
| return FALSE; | |
| } | |
| return TRUE; /* non-zero */ | |
| } | |
| uint32 fp_pack (ufp_t *src, uint32 rn, t_bool dbl, t_bool rndtrap) | |
| { | |
| static ufp_t fp_zero = { 0, 0, 0, 0}; | |
| uint32 opnd, opnd1; | |
| if (src->h || (dbl && src->l)) { /* result != 0? */ | |
| CC |= (src->sign? CC4: CC3); /* set CC's */ | |
| if (rndtrap) { /* round, trap? */ | |
| if (PSW1 & PSW1_FR) { /* round? */ | |
| if (dbl) { /* double prec? */ | |
| src->l = (src->l + 0x8) & UFP_FRLO; | |
| src->h = src->h + (src->l < 0x8); | |
| } | |
| else src->h = src->h + 0x8; /* no, single */ | |
| if (src->h & UFP_CARRY) { /* carry out? */ | |
| UFP_RSH_KP (src, 4); /* renormalize */ | |
| src->exp = src->exp + 1; | |
| } | |
| } /* end if round */ | |
| if (src->exp > FP_M_EXP) { /* overflow? */ | |
| CC |= CC2; /* flag */ | |
| return TR_FLT; | |
| } | |
| else if (src->exp < 0) { /* underflow? */ | |
| if (PSW1 & PSW1_FZ) { /* trap enabled? */ | |
| CC |= CC1 | CC2; /* flag */ | |
| return TR_FLT; | |
| } | |
| *src = fp_zero; /* result 0 */ | |
| CC = CC1|CC2; /* special CC's */ | |
| } | |
| } /* end rnd trap */ | |
| UFP_RSH_KP (src, 4); /* remove guard */ | |
| if (!dbl) /* single? lose lower */ | |
| src->l = 0; | |
| if ((src->h | src->l) == 0) /* result now 0? */ | |
| src->exp = src->sign = 0; | |
| } | |
| else *src = fp_zero; | |
| opnd = ((src->exp & FP_M_EXP) << FP_V_EXP) | /* repack */ | |
| ((src->h & FP_M_FRHI) << FP_V_FRHI); | |
| opnd1 = src->l & FP_M_FRLO; | |
| if (src->sign) /* negative? */ | |
| NEG_D (opnd, opnd1); | |
| R[rn] = opnd; /* store result */ | |
| if (dbl && ((rn & 1) == 0)) | |
| R[rn|1] = opnd1; | |
| return 0; | |
| } | |
| uint32 fp_norm (ufp_t *src) | |
| { | |
| uint32 nsh; | |
| nsh = 0; | |
| src->h &= UFP_FRHI; | |
| if (src->h || src->l) { /* if non-zero */ | |
| while ((src->h & UFP_NORM) == 0) { /* until normalized */ | |
| UFP_LSH_KP (src, 4); /* hex shift left */ | |
| src->exp--; /* decr exponent */ | |
| nsh++; /* count shifts */ | |
| } | |
| } | |
| return nsh; | |
| } | |