/* sigma_cis.c: Sigma decimal instructions | |
Copyright (c) 2007-2018, 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. | |
Questions: | |
1. On the Sigma 9, in ASCII mode, is an ASCII blank used in EBS? | |
02-Jun-2018 RMS Fixed unsigned < 0 in decimal compare (Mark Pizzolato) | |
*/ | |
#include "sigma_defs.h" | |
/* Decimal string structure */ | |
#define DSTRLNT 4 /* words per dec string */ | |
#define DECA 12 /* first dec accum reg */ | |
/* Standard characters */ | |
#define ZONE_E 0xF0 /* EBCDIC zone bits */ | |
#define ZONE_A 0x30 /* ASCII zone bits */ | |
#define ZONE ((PSW1 & PSW1_AS)? ZONE_A: ZONE_E) | |
#define PKPLUS_E 0xC /* EBCDIC preferred plus */ | |
#define PKPLUS_A 0xA /* ASCII preferred plus */ | |
#define PKPLUS ((PSW1 & PSW1_AS)? PKPLUS_A: PKPLUS_E) | |
#define BLANK_E 0x40 /* EBCDIC blank */ | |
#define BLANK_A 0x20 /* ASCII blank */ | |
#define BLANK ((PSW1 & PSW1_AS)? BLANK_A: BLANK_E) | |
/* Edit special characters */ | |
#define ED_DS 0x20 /* digit select */ | |
#define ED_SS 0x21 /* start significance */ | |
#define ED_FS 0x22 /* field separator */ | |
#define ED_SI 0x23 /* immediate significance */ | |
/* Decimal strings run low order (word 0/R15) to high order (word 3/R12) */ | |
typedef struct { | |
uint32 sign; | |
uint32 val[DSTRLNT]; | |
} dstr_t; | |
/* Copy decimal accumulator to decimal string, no validation or sign separation */ | |
#define ReadDecA(src) for (i = 0; i < DSTRLNT; i++) \ | |
src.val[DSTRLNT - 1 - i] = R[DECA + i]; | |
static dstr_t Dstr_zero = { 0, 0, 0, 0, 0 }; | |
extern uint32 *R; | |
extern uint32 CC; | |
extern uint32 PSW1; | |
extern uint32 bvamqrx; | |
extern uint32 cpu_model; | |
uint32 ReadDstr (uint32 lnt, uint32 addr, dstr_t *dec); | |
uint32 WriteDstr (uint32 lnt, uint32 addr, dstr_t *dec); | |
void WriteDecA (dstr_t *dec, t_bool cln); | |
void SetCC2Dstr (uint32 lnt, dstr_t *dst); | |
uint32 TestDstrValid (dstr_t *src); | |
uint32 DstrInvd (void); | |
uint32 AddDstr (dstr_t *src1, dstr_t *src2, dstr_t *dst, uint32 cin); | |
void SubDstr (dstr_t *src1, dstr_t *src2, dstr_t *dst); | |
int32 CmpDstr (dstr_t *src1, dstr_t *src2); | |
uint32 LntDstr (dstr_t *dsrc); | |
uint32 NibbleLshift (dstr_t *dsrc, uint32 sc, uint32 cin); | |
uint32 NibbleRshift (dstr_t *dsrc, uint32 sc, uint32 cin); | |
t_bool GenLshift (dstr_t *dsrc, uint32 sc); | |
void GenRshift (dstr_t *dsrc, uint32 sc); | |
uint32 ed_getsrc (uint32 sa, uint32 *c, uint32 *d); | |
void ed_advsrc (uint32 rn, uint32 c); | |
t_bool cis_test_int (dstr_t *src1, uint32 *kint); | |
void cis_dm_int (dstr_t *src, dstr_t *dst, uint32 kint); | |
void cis_dd_int (dstr_t *src, dstr_t *dst, uint32 t, uint32 *kint); | |
/* Decimal instructions */ | |
uint32 cis_dec (uint32 op, uint32 lnt, uint32 bva) | |
{ | |
dstr_t src1, src2, src2x, dst; | |
uint32 i, t, kint, ldivr, ldivd, ad, c, d, end; | |
int32 sc, scmp; | |
uint32 tr; | |
if (lnt == 0) /* adjust length */ | |
lnt = 16; | |
CC &= ~(CC1|CC2); /* clear CC1, CC2 */ | |
switch (op) { /* case on opcode */ | |
case OP_DL: /* decimal load */ | |
if ((tr = ReadDstr (lnt, bva, &dst)) != 0) /* read mem string */ | |
return tr; | |
WriteDecA (&dst, FALSE); /* store result */ | |
break; | |
case OP_DST: /* decimal store */ | |
ReadDecA (dst); /* read dec accum */ | |
if ((tr = TestDstrValid (&dst)) != 0) /* valid? */ | |
return tr; | |
if ((tr = WriteDstr (lnt, bva, &dst)) != 0) /* write to mem */ | |
return tr; | |
break; | |
case OP_DS: /* decimal subtract */ | |
case OP_DA: /* decimal add */ | |
ReadDecA (src1); /* read dec accum */ | |
if ((tr = TestDstrValid (&src1)) != 0) /* valid? */ | |
return tr; | |
if ((tr = ReadDstr (lnt, bva, &src2)) != 0) /* read mem string */ | |
return tr; | |
if (op == OP_DS) /* sub? invert sign */ | |
src2.sign = src2.sign ^ 1; | |
if (src1.sign ^ src2.sign) { /* opp signs? sub */ | |
if (CmpDstr (&src1, &src2) < 0) { /* src1 < src2? */ | |
SubDstr (&src1, &src2, &dst); /* src2 - src1 */ | |
dst.sign = src2.sign; /* sign = src2 */ | |
} | |
else { | |
SubDstr (&src2, &src1, &dst); /* src1 - src2 */ | |
dst.sign = src1.sign; /* sign = src1 */ | |
} | |
} | |
else { /* addition */ | |
if (AddDstr (&src1, &src2, &dst, 0)) { /* add, overflow? */ | |
CC |= CC2; /* set CC2 */ | |
return (PSW1 & PSW1_DM)? TR_DEC: 0; /* trap if enabled */ | |
} | |
dst.sign = src1.sign; /* set result sign */ | |
} | |
WriteDecA (&dst, TRUE); /* store result */ | |
break; | |
case OP_DC: /* decimal compare */ | |
ReadDecA ( src1); /* read dec accum */ | |
if ((tr = TestDstrValid (&src1)) != 0) /* valid? */ | |
return tr; | |
if ((tr = ReadDstr (lnt, bva, &src2)) != 0) /* read mem string */ | |
return tr; | |
LntDstr (&src1); /* clean -0 */ | |
LntDstr (&src2); | |
if (src1.sign ^ src2.sign) /* signs differ? */ | |
CC = src1.sign? CC4: CC3; /* set < or > */ | |
else { /* same signs */ | |
scmp = CmpDstr (&src1, &src2); /* compare strings */ | |
if (scmp < 0) | |
CC = (src1.sign? CC3: CC4); | |
else if (scmp > 0) | |
CC = (src1.sign? CC4: CC3); | |
else CC = 0; | |
} | |
break; | |
/* Decimal multiply - algorithm from George Plue. | |
The Sigma does decimal multiply one digit at a time, using the multiplicand | |
and a doubled copy of the multiplicand. Multiplying by digits 1-5 is | |
synthesized by 1-3 adds; multiplying by digits 6-9 is synthesized by 1-2 | |
subtractions, and adding 1 to the next multiplier digit. (That is, | |
multiplying by 7 is done by multiplying by "10 - 3".) This requires at | |
most one extra add to fixup the last digit, and minimizes the overall | |
number of adds (average 1.5 adds per multiplier digit). Note that | |
multiplication proceeds from right to left. | |
The Sigma 5-9 allowed decimal multiply to be interrupted; the 5X0 series | |
did not. An interrupted multiply uses a sign digit in R12 and R13 as the | |
divider between the remaining multiplier (to the left of the sign, and | |
in the low-order digit of R15) and the partial product (to the right of | |
the sign). Because the partial product may be negative, leading 0x99's | |
may have been stripped and need to be restored. | |
The real Sigma's probably didn't run a validty test after separation of | |
the partial product and multiplier, but it doesn't hurt, and prevents | |
certain corner cases from causing errors. */ | |
case OP_DM: /* decimal multiply */ | |
if (lnt >= 9) /* invalid length? */ | |
return DstrInvd (); | |
ReadDecA (src1); /* get dec accum */ | |
if ((tr = ReadDstr (lnt, bva, &src2)) != 0) /* read mem string */ | |
return tr; | |
dst = Dstr_zero; /* clear result */ | |
kint = 0; /* assume no int */ | |
if (!QCPU_5X0 && /* S5-9? */ | |
(cis_test_int (&src1, &kint))) { /* interrupted? */ | |
src1.sign = 0; | |
cis_dm_int (&src1, &dst, kint); /* restore */ | |
} | |
else if ((tr = TestDstrValid (&src1)) != 0) /* mpyr valid? */ | |
return tr; | |
if (LntDstr (&src1) && LntDstr (&src2)) { /* both opnds != 0? */ | |
dst.sign = src1.sign ^ src2.sign; /* sign of result */ | |
AddDstr (&src2, &src2, &src2x, 0); /* get 2*mplcnd */ | |
for (i = 1; i <= 16; i++) { /* 16 iterations */ | |
if (i >= kint) { /* past int point? */ | |
NibbleRshift (&src1, 1, 0); /* mpyr right 4 */ | |
d = src1.val[0] & 0xF; /* get digit */ | |
switch (d) { /* case */ | |
case 5: /* + 2 + 2 + 1 */ | |
AddDstr (&src2x, &dst, &dst, 0); | |
case 3: /* + 2 + 1 */ | |
AddDstr (&src2x, &dst, &dst, 0); | |
case 1: /* + 1 */ | |
AddDstr (&src2, &dst, &dst, 0); | |
case 0: | |
break; | |
case 4: /* + 2 + 2 */ | |
AddDstr (&src2x, &dst, &dst, 0); | |
case 2: /* + 2 */ | |
AddDstr (&src2x, &dst, &dst, 0); | |
break; | |
case 6: /* - 2 - 2 + 10 */ | |
SubDstr (&src2x, &dst, &dst); | |
case 8: /* - 2 + 10 */ | |
SubDstr (&src2x, &dst, &dst); | |
src1.val[0] += 0x10; /* + 10 */ | |
break; | |
case 7: /* -2 - 1 + 10 */ | |
SubDstr (&src2x, &dst, &dst); | |
case 9: /* -1 + 10 */ | |
SubDstr (&src2, &dst, &dst); | |
default: /* + 10 */ | |
src1.val[0] += 0x10; | |
} /* end switch */ | |
} /* end if >= kint */ | |
NibbleLshift (&src2, 1, 0); /* shift mplcnds */ | |
NibbleLshift (&src2x, 1, 0); | |
} /* end for */ | |
} /* end if != 0 */ | |
WriteDecA (&dst, TRUE); /* store result */ | |
break; | |
/* Decimal divide overflow calculation - if the dividend has true length d, | |
and the divisor true length r, then the quotient will have (d - r) or | |
(d - r + 1) digits. Therefore, if (d - r) > 15, the quotient will not | |
fit. However, if (d - r) == 15, it may or may not fit, depending on | |
whether the first subtract succeeds. Therefore, it's necessary to test | |
after the divide to see if the quotient has one extra digit. */ | |
case OP_DD: /* decimal divide */ | |
if (lnt >= 9) /* invalid length? */ | |
return DstrInvd (); | |
ReadDecA (src1); /* read dec accum */ | |
if ((tr = ReadDstr (lnt, bva, &src2)) != 0) /* read mem string */ | |
return tr; | |
dst = Dstr_zero; /* clear result */ | |
kint = 0; /* no interrupt */ | |
if (!QCPU_5X0 && /* S5-9? */ | |
(cis_test_int (&src1, &t))) { /* interrupted? */ | |
src1.sign = 0; | |
cis_dd_int (&src1, &dst, t, &kint); /* restore */ | |
t = t - 1; | |
} | |
else { /* normal start? */ | |
if ((tr = TestDstrValid (&src1)) != 0) /* divd valid? */ | |
return tr; | |
ldivr = LntDstr (&src2); /* divr lnt */ | |
ldivd = LntDstr (&src1); /* divd lnt */ | |
if ((ldivr == 0) || /* div by zero? */ | |
(ldivd > (ldivr + 15))) { /* quo too big? */ | |
CC |= CC2; /* divide check */ | |
return (PSW1 & PSW1_DM)? TR_DEC: 0; /* trap if enabled */ | |
} | |
if (CmpDstr (&src1, &src2) < 0) { /* no divide? */ | |
R[12] = src1.val[1]; /* remainder */ | |
R[13] = src1.val[0] | (PKPLUS + src1.sign); | |
R[14] = 0; /* quotient */ | |
R[15] = PKPLUS; | |
CC = 0; | |
return SCPE_OK; | |
} | |
t = ldivd - ldivr; | |
} | |
dst.sign = src1.sign ^ src2.sign; /* calculate sign */ | |
GenLshift (&src2, t); /* align */ | |
for (i = 0; i <= t; i++) { /* divide loop */ | |
for (d = kint; /* find digit */ | |
(d < 10) && (CmpDstr (&src1, &src2) >= 0); | |
d++) | |
SubDstr (&src2, &src1, &src1); | |
dst.val[0] = (dst.val[0] & ~0xF) | d; /* insert quo dig */ | |
NibbleLshift (&dst, 1, 0); /* shift quotient */ | |
NibbleRshift (&src2, 1, 0); /* shift divisor */ | |
kint = 0; /* no more int */ | |
} /* end divide loop */ | |
if (dst.val[2]) { /* quotient too big? */ | |
CC |= CC2; /* divide check */ | |
return (PSW1 & PSW1_DM)? TR_DEC: 0; /* trap if enabled */ | |
} | |
CC = dst.sign? CC4: CC3; /* set CC's */ | |
R[12] = src1.val[1]; /* remainder */ | |
R[13] = src1.val[0] | (PKPLUS + src1.sign); | |
R[14] = dst.val[1]; /* quotient */ | |
R[15] = dst.val[0] | (PKPLUS + dst.sign); | |
break; | |
case OP_DSA: /* decimal shift */ | |
ReadDecA (dst); /* read dec accum */ | |
if ((tr = TestDstrValid (&dst)) != 0) /* valid? */ | |
return tr; | |
CC = 0; /* clear CC's */ | |
sc = SEXT_H_W (bva >> 2); /* shift count */ | |
if (sc > 31) /* sc in [-31,31] */ | |
sc = 31; | |
if (sc < -31) | |
sc = -31; | |
if (sc < 0) { /* right shift? */ | |
sc = -sc; /* |shift| */ | |
GenRshift (&dst, sc); /* do shift */ | |
dst.val[0] = dst.val[0] & ~0xF; /* clear sign */ | |
} /* end right shift */ | |
else if (sc) { /* left shift? */ | |
if (GenLshift (&dst, sc)) /* do shift */ | |
CC |= CC2; | |
} /* end left shift */ | |
WriteDecA (&dst, FALSE); /* store result */ | |
break; | |
case OP_PACK: /* zoned to packed */ | |
dst = Dstr_zero; /* clear result */ | |
end = (2 * lnt) - 1; /* zoned length */ | |
for (i = 1; i <= end; i++) { /* loop thru char */ | |
ad = (bva + end - i) & bvamqrx; /* zoned character */ | |
if ((tr = ReadB (ad, &c, VR)) != 0) /* read char */ | |
return tr; | |
if (i == 1) { /* sign + digit? */ | |
uint32 s; | |
s = (c >> 4) & 0xF; /* get sign */ | |
if (s < 0xA) | |
return DstrInvd (); | |
if ((s == 0xB) || (s == 0xD)) /* negative */ | |
dst.sign = 1; | |
} | |
d = c & 0xF; /* get digit */ | |
if (d > 0x9) | |
return DstrInvd (); | |
dst.val[i / 8] = dst.val[i / 8] | (d << ((i % 8) * 4)); | |
} | |
WriteDecA (&dst, FALSE); /* write result */ | |
break; | |
case OP_UNPK: /* packed to zoned */ | |
ReadDecA (dst); /* read dec accum */ | |
if ((tr = TestDstrValid (&dst)) != 0) /* valid? */ | |
return tr; | |
end = (2 * lnt) - 1; /* zoned length */ | |
if ((tr = ReadB (bva, &c, VW)) != 0) /* prove writeable */ | |
return tr; | |
for (i = 1; i <= end; i++) { /* loop thru chars */ | |
c = (dst.val[i / 8] >> ((i % 8) * 4)) & 0xF; /* get digit */ | |
if (i == 1) /* first? */ | |
c |= ((PKPLUS + dst.sign) << 4); /* or in sign */ | |
else c |= ZONE; /* no, or in zone */ | |
ad = (bva + end - i) & bvamqrx; | |
if ((tr = WriteB (ad, c, VW)) != 0) /* write to memory */ | |
return tr; | |
} | |
SetCC2Dstr (lnt, &dst); /* see if too long */ | |
break; | |
} | |
return 0; | |
} | |
/* Test for interrupted multiply or divide */ | |
t_bool cis_test_int (dstr_t *src, uint32 *kint) | |
{ | |
int32 i; | |
uint32 wd, sc, d; | |
for (i = 15; i >= 1; i--) { /* test 15 nibbles */ | |
wd = (DSTRLNT/2) + (i / 8); | |
sc = (i % 8) * 4; | |
d = (src->val[wd] >> sc) & 0xF; | |
if (d >= 0xA) { | |
*kint = (uint32) i; | |
return TRUE; | |
} | |
} | |
return FALSE; | |
} | |
/* Resume interrupted multiply | |
The sign that was found is the "fence" between the the remaining multiplier | |
and the partial product: | |
R val | |
+--+--+--+--+--+--+--+--+ | |
| mpyer |sn|pp| 12 3 | |
+--+--+--+--+--+--+--+--+ | |
| partial product | 13 2 | |
+--+--+--+--+--+--+--+--+ | |
| partial product | 14 1 | |
+--+--+--+--+--+--+--+--+ | |
| partial product |mp| 15 0 | |
+--+--+--+--+--+--+--+--+ | |
This routine separates the multiplier and partial product, returns the | |
multiplier as a valid decimal string in src, and the partial product | |
as a value with no sign in dst */ | |
void cis_dm_int (dstr_t *src, dstr_t *dst, uint32 kint) | |
{ | |
uint32 ppneg, wd, sc, d, curd; | |
int32 k; | |
*dst = *src; /* copy input */ | |
wd = (DSTRLNT/2) + (kint / 8); | |
sc = (kint % 8) * 4; | |
d = (src->val[wd] >> sc) & 0xF; /* get sign fence */ | |
ppneg = ((d >> 2) & 1) ^ 1; /* partial prod neg? */ | |
curd = (src->val[0] & 0xF) + ppneg; /* bias cur digit */ | |
src->val[wd] = (src->val[wd] & ~(0xF << sc)) | /* replace sign */ | |
(curd << sc); /* with digit */ | |
GenRshift (src, kint + 15); /* right justify */ | |
src->sign = ((d == 0xB) || (d == 0xD))? 1: 0; /* set mpyr sign */ | |
src->val[0] = src->val[0] & ~0xF; /* clear sign pos */ | |
/* Mask out multiplier */ | |
for (k = DSTRLNT - 1; k >= (int32) wd; k--) /* words hi to lo */ | |
dst->val[k] &= ~(0xFFFFFFFFu << | |
((k > (int32) wd)? 0: sc)); | |
/* Recreate missing high order digits for negative partial product */ | |
if (ppneg) { /* negative? */ | |
for (k = (DSTRLNT * 4) - 1; k != 0; k--) { /* bytes hi to lo */ | |
wd = k / 4; | |
sc = (k % 4) * 8; | |
if (((dst->val[wd] >> sc) & 0xFF) != 0) | |
break; | |
dst->val[wd] |= (0x99 << sc); /* repl 00 with 99 */ | |
} /* end for */ | |
} | |
dst->val[0] &= ~0xF; /* clear pp sign */ | |
return; | |
} | |
/* Resume interrupted divide | |
The sign that was found is the "fence" between the the quotient and the | |
remaining dividend product: | |
R val | |
+--+--+--+--+--+--+--+--+ | |
| quotient |sn|dv| 12 3 | |
+--+--+--+--+--+--+--+--+ | |
| dividend | 13 2 | |
+--+--+--+--+--+--+--+--+ | |
| dividend | 14 1 | |
+--+--+--+--+--+--+--+--+ | |
| dividend |qu| 15 0 | |
+--+--+--+--+--+--+--+--+ | |
This routine separates the quotient and the remaining dividend, returns | |
the dividend as a valid decimal string, the quotient as a decimal string | |
without sign, and kint is the partial value of the last quotient digit. */ | |
void cis_dd_int (dstr_t *src, dstr_t *dst, uint32 nib, uint32 *kint) | |
{ | |
uint32 wd, sc, d, curd; | |
int32 k; | |
wd = (DSTRLNT/2) + (nib / 8); | |
sc = (nib % 8) * 4; | |
curd = src->val[0] & 0xF; /* last quo digit */ | |
*dst = *src; /* copy input */ | |
GenRshift (dst, nib + 16); /* right justify quo */ | |
d = dst->val[0] & 0xF; /* get sign fence */ | |
dst->val[0] = (dst->val[0] & ~0xF) | curd; /* repl with digit */ | |
*kint = curd; | |
/* Mask out quotient */ | |
for (k = DSTRLNT - 1; k >= (int32) wd; k--) /* words hi to lo */ | |
src->val[k] &= ~(0xFFFFFFFFu << | |
((k > (int32) wd)? 0: sc)); | |
src->sign = ((d == 0xB) || (d == 0xD))? 1: 0; /* set divd sign */ | |
src->val[0] = src->val[0] & ~0xF; /* clr sign digit */ | |
return; | |
} | |
/* Get packed decimal string from memory | |
Arguments: | |
lnt = decimal string length | |
adr = decimal string address | |
src = decimal string structure | |
Output: | |
trap or abort signal | |
Per the Sigma spec, bad digits or signs cause a fault or abort */ | |
uint32 ReadDstr (uint32 lnt, uint32 adr, dstr_t *src) | |
{ | |
uint32 i, c, bva; | |
uint32 tr; | |
*src = Dstr_zero; /* clear result */ | |
for (i = 0; i < lnt; i++) { /* loop thru string */ | |
bva = (adr + lnt - i - 1) & bvamqrx; /* from low to high */ | |
if ((tr = ReadB (bva, &c, VR)) != 0) /* read byte */ | |
return tr; | |
src->val[i / 4] = src->val[i / 4] | (c << ((i % 4) * 8)); | |
} /* end for */ | |
return TestDstrValid (src); | |
} | |
/* Separate sign, validate sign and digits of decimal string */ | |
uint32 TestDstrValid (dstr_t *src) | |
{ | |
uint32 i, j, s, t; | |
s = src->val[0] & 0xF; /* get sign */ | |
if (s < 0xA) /* valid? */ | |
return DstrInvd (); | |
if ((s == 0xB) || (s == 0xD)) /* negative? */ | |
src->sign = 1; | |
else src->sign = 0; | |
src->val[0] &= ~0xF; /* clear sign */ | |
for (i = 0; i < DSTRLNT; i++) { /* check 4 words */ | |
for (j = 0; j < 8; j++) { /* 8 digit/word */ | |
t = (src->val[i] >> (28 - (j * 4))) & 0xF; /* get digit */ | |
if (t > 0x9) /* invalid digit? */ | |
return DstrInvd (); /* exception */ | |
} | |
} | |
return 0; | |
} | |
/* Invalid digit or sign: set CC1, trap or abort instruction */ | |
uint32 DstrInvd (void) | |
{ | |
CC |= CC1; /* set CC1 */ | |
if (PSW1 & PSW1_DM) /* if enabled, trap */ | |
return TR_DEC; | |
return WSIGN; /* otherwise, abort */ | |
} | |
/* Store decimal string | |
Arguments: | |
lnt = decimal string length | |
adr = decimal string address | |
dst = decimal string structure | |
Returns memory management traps (if any) | |
Bad digits and invalid sign are impossible | |
*/ | |
uint32 WriteDstr (uint32 lnt, uint32 adr, dstr_t *dst) | |
{ | |
uint32 i, bva, c; | |
uint32 tr; | |
dst->val[0] = dst->val[0] | (PKPLUS + dst->sign); /* set sign */ | |
if ((tr = ReadB (adr, &c, VW)) != 0) /* prove writeable */ | |
return tr; | |
for (i = 0; i < lnt; i++) { /* loop thru bytes */ | |
c = (dst->val[i / 4] >> ((i % 4) * 8)) & 0xFF; /* from low to high */ | |
bva = (adr + lnt - i - 1) & bvamqrx; | |
if ((tr = WriteB (bva, c, VW)) != 0) /* store byte */ | |
return tr; | |
} /* end for */ | |
SetCC2Dstr (lnt, dst); /* check overflow */ | |
return 0; | |
} | |
/* Store result in decimal accumulator | |
Arguments: | |
dst = decimal string structure | |
cln = clean -0 if true | |
Sets condition codes CC3 and CC4 | |
Bad digits and invalid sign are impossible */ | |
void WriteDecA (dstr_t *dst, t_bool cln) | |
{ | |
uint32 i, nz; | |
CC &= ~(CC3|CC4); /* assume zero */ | |
for (i = 0, nz = 0; i < DSTRLNT; i++) { /* save 32 digits */ | |
R[DECA + i] = dst->val[DSTRLNT - 1 - i]; | |
nz |= dst->val[DSTRLNT - 1 - i]; | |
} | |
if (nz) /* non-zero? */ | |
CC |= (dst->sign)? CC4: CC3; /* set CC3 or CC4 */ | |
else if (cln) /* zero, clean? */ | |
dst->sign = 0; /* clear sign */ | |
R[DECA + DSTRLNT - 1] |= (PKPLUS + dst->sign); /* or in sign */ | |
return; | |
} | |
/* Set CC2 for decimal string store | |
Arguments: | |
lnt = string length | |
dst = decimal string structure | |
Output: | |
sets CC2 if information won't fit */ | |
void SetCC2Dstr (uint32 lnt, dstr_t *dst) | |
{ | |
uint32 i, limit, mask; | |
static uint32 masktab[8] = { | |
0xFFFFFFF0, 0xFFFFFF00, 0xFFFFF000, 0xFFFF0000, | |
0xFFF00000, 0xFF000000, 0xF0000000, 0x00000000 | |
}; | |
lnt = (lnt * 2) - 1; /* number of digits */ | |
mask = 0; /* can't ovflo */ | |
limit = lnt / 8; /* limit for test */ | |
for (i = 0; i < DSTRLNT; i++) { /* loop thru value */ | |
if (i == limit) /* @limit, get mask */ | |
mask = masktab[lnt % 8]; | |
else if (i > limit) /* >limit, test all */ | |
mask = 0xFFFFFFFF; | |
if (dst->val[i] & mask) /* test for ovflo */ | |
CC |= CC2; | |
} | |
return; | |
} | |
/* Add decimal string magnitudes | |
Arguments: | |
s1 = src1 decimal string | |
s2 = src2 decimal string | |
ds = dest decimal string | |
cy = carry in | |
Output: | |
1 if carry, 0 if no carry | |
This algorithm courtesy Anton Chernoff, circa 1992 or even earlier. | |
We trace the history of a pair of adjacent digits to see how the | |
carry is fixed; each parenthesized item is a 4b digit. | |
Assume we are adding: | |
(a)(b) I | |
+ (x)(y) J | |
First compute I^J: | |
(a^x)(b^y) TMP | |
Note that the low bit of each digit is the same as the low bit of | |
the sum of the digits, ignoring the carry, since the low bit of the | |
sum is the xor of the bits. | |
Now compute I+J+66 to get decimal addition with carry forced left | |
one digit: | |
(a+x+6+carry mod 16)(b+y+6 mod 16) SUM | |
Note that if there was a carry from b+y+6, then the low bit of the | |
left digit is different from the expected low bit from the xor. | |
If we xor this SUM into TMP, then the low bit of each digit is 1 | |
if there was a carry, and 0 if not. We need to subtract 6 from each | |
digit that did not have a carry, so take ~(SUM ^ TMP) & 0x11, shift | |
it right 4 to the digits that are affected, and subtract 6*adjustment | |
(actually, shift it right 3 and subtract 3*adjustment). | |
*/ | |
uint32 AddDstr (dstr_t *s1, dstr_t *s2, dstr_t *ds, uint32 cy) | |
{ | |
uint32 i; | |
uint32 sm1, sm2, tm1, tm2, tm3, tm4; | |
for (i = 0; i < DSTRLNT; i++) { /* loop low to high */ | |
tm1 = s1->val[i] ^ (s2->val[i] + cy); /* xor operands */ | |
sm1 = s1->val[i] + (s2->val[i] + cy); /* sum operands */ | |
sm2 = sm1 + 0x66666666; /* force carry out */ | |
cy = ((sm1 < s1->val[i]) || (sm2 < sm1)); /* check for ovflo */ | |
tm2 = tm1 ^ sm2; /* get carry flags */ | |
tm3 = (tm2 >> 3) | (cy << 29); /* compute adjust */ | |
tm4 = 0x22222222 & ~tm3; /* clrr where carry */ | |
ds->val[i] = (sm2 - (3 * tm4)) & WMASK; /* final result */ | |
} | |
return cy; | |
} | |
/* Subtract decimal string magnitudes | |
Arguments: | |
s1 = src1 decimal string | |
s2 = src2 decimal string | |
ds = dest decimal string | |
Note: the routine assumes that s1 <= s2 | |
*/ | |
void SubDstr (dstr_t *s1, dstr_t *s2, dstr_t *ds) | |
{ | |
uint32 i; | |
dstr_t complm; | |
for (i = 0; i < DSTRLNT; i++) /* 9's comp s2 */ | |
complm.val[i] = 0x99999999 - s1->val[i]; | |
AddDstr (&complm, s2, ds, 1); /* s1 + ~s2 + 1 */ | |
return; | |
} | |
/* Compare decimal string magnitudes | |
Arguments: | |
s1 = src1 decimal string | |
s2 = src2 decimal string | |
Output: | |
1 if >, 0 if =, -1 if < | |
*/ | |
int32 CmpDstr (dstr_t *s1, dstr_t *s2) | |
{ | |
int32 i; | |
for (i = DSTRLNT - 1; i >=0; i--) { | |
if (s1->val[i] > s2->val[i]) | |
return 1; | |
if (s1->val[i] < s2->val[i]) | |
return -1; | |
} | |
return 0; | |
} | |
/* Get exact length of decimal string, clean -0 | |
Arguments: | |
dst = decimal string structure | |
Output: | |
number of non-zero digits | |
*/ | |
uint32 LntDstr (dstr_t *dst) | |
{ | |
int32 nz, i; | |
for (nz = DSTRLNT - 1; nz >= 0; nz--) { | |
if (dst->val[nz]) { | |
for (i = 7; i >= 0; i--) { | |
if ((dst->val[nz] >> (i * 4)) & 0xF) | |
return (nz * 8) + i; | |
} | |
} | |
} | |
dst->sign = 0; | |
return 0; | |
} | |
/* Word shift right | |
Arguments: | |
dsrc = decimal string structure | |
sc = shift count in nibbles | |
*/ | |
void GenRshift (dstr_t *dsrc, uint32 cnt) | |
{ | |
uint32 i, sc, sc1; | |
sc = cnt / 8; | |
sc1 = cnt % 8; | |
if (sc) { | |
for (i = 0; i < DSTRLNT; i++) { | |
if ((i + sc) < DSTRLNT) | |
dsrc->val[i] = dsrc->val[i + sc]; | |
else dsrc->val[i] = 0; | |
} | |
} | |
if (sc1) | |
NibbleRshift (dsrc, sc1, 0); | |
return; | |
} | |
/* General shift left | |
Arguments: | |
dsrc = decimal string structure | |
cnt = shift count in nibbles | |
*/ | |
t_bool GenLshift (dstr_t *dsrc, uint32 cnt) | |
{ | |
t_bool i, c, sc, sc1; | |
c = 0; | |
sc = cnt / 8; | |
sc1 = cnt % 8; | |
if (sc) { | |
for (i = DSTRLNT - 1; (int32) i >= 0; i--) { | |
if (i >= sc) | |
dsrc->val[i] = dsrc->val[i - sc]; | |
else { | |
c |= dsrc->val[i]; | |
dsrc->val[i] = 0; | |
} | |
} | |
} | |
if (sc1) | |
c |= NibbleLshift (dsrc, sc1, 0); | |
return (c? TRUE: FALSE); | |
} | |
/* Nibble shift right | |
Arguments: | |
dsrc = decimal string structure | |
sc = shift count in nibbles | |
cin = carry in | |
*/ | |
uint32 NibbleRshift (dstr_t *dsrc, uint32 sc, uint32 cin) | |
{ | |
int32 i; | |
uint32 s, nc; | |
if ((s = sc * 4)) { | |
for (i = DSTRLNT - 1; (int32) i >= 0; i--) { | |
nc = (dsrc->val[i] << (32 - s)) & WMASK; | |
dsrc->val[i] = ((dsrc->val[i] >> s) | | |
cin) & WMASK; | |
cin = nc; | |
} | |
return cin; | |
} | |
return 0; | |
} | |
/* Nibble shift left | |
Arguments: | |
dsrc = decimal string structure | |
sc = shift count in nibbles | |
cin = carry in | |
*/ | |
uint32 NibbleLshift (dstr_t *dsrc, uint32 sc, uint32 cin) | |
{ | |
uint32 i, s, nc; | |
if ((s = sc * 4)) { | |
for (i = 0; i < DSTRLNT; i++) { | |
nc = dsrc->val[i] >> (32 - s); | |
dsrc->val[i] = ((dsrc->val[i] << s) | | |
cin) & WMASK; | |
cin = nc; | |
} | |
return cin; | |
} | |
return 0; | |
} | |
/* Edit instruction */ | |
uint32 cis_ebs (uint32 rn, uint32 disp) | |
{ | |
uint32 sa, da, c, d, dst, fill, pat; | |
uint32 tr; | |
disp = SEXT_LIT_W (disp) & WMASK; /* sext operand */ | |
fill = S_GETMCNT (R[rn]); /* fill char */ | |
while (S_GETMCNT (R[rn|1])) { /* while pattern */ | |
sa = (disp + R[rn]) & bvamqrx; /* dec str addr */ | |
da = R[rn|1] & bvamqrx; /* pattern addr */ | |
if ((tr = ReadB (da, &pat, VR)) != 0) /* get pattern byte */ | |
return tr; | |
switch (pat) { /* case on pattern */ | |
case ED_DS: /* digit select */ | |
if ((tr = ed_getsrc (sa, &c, &d)) != 0) /* get src digit */ | |
return tr; | |
if (CC & CC4) /* signif? unpack */ | |
dst = ZONE | d; | |
else if (d) { /* non-zero? */ | |
R[1] = da; /* save addr */ | |
dst = ZONE | d; /* unpack */ | |
CC |= CC4; /* set signif */ | |
} | |
else dst = fill; /* otherwise fill */ | |
if ((tr = WriteB (da, dst, VW)) != 0) /* overwrite dst */ | |
return tr; | |
ed_advsrc (rn, c); /* next src digit */ | |
break; | |
case ED_SS: /* signif start */ | |
if ((tr = ed_getsrc (sa, &c, &d)) != 0) /* get src digit */ | |
return tr; | |
if (CC & CC4) /* signif? unpack */ | |
dst = ZONE | d; | |
else if (d) { /* non-zero? */ | |
R[1] = da; /* save addr */ | |
dst = ZONE | d; /* unpack */ | |
} | |
else { /* otherwise */ | |
R[1] = da + 1; /* save next */ | |
dst = fill; /* fill */ | |
} | |
CC |= CC4; /* set signif */ | |
if ((tr = WriteB (da, dst, VW)) != 0) /* overwrite dst */ | |
return tr; | |
ed_advsrc (rn, c); /* next src digit */ | |
break; | |
case ED_SI: /* signif immediate */ | |
if ((tr = ed_getsrc (sa, &c, &d)) != 0) /* get src digit */ | |
return tr; | |
R[1] = da; /* save addr */ | |
dst = ZONE | d; /* unpack */ | |
CC |= CC4; /* set signif */ | |
if ((tr = WriteB (da, dst, VW)) != 0) /* overwrite dst */ | |
return tr; | |
ed_advsrc (rn, c); /* next src digit */ | |
break; | |
case ED_FS: /* field separator */ | |
CC &= ~(CC1|CC3|CC4); /* clr all exc CC2 */ | |
if ((tr = WriteB (da, fill, VW)) != 0) /* overwrite dst */ | |
return tr; | |
break; | |
default: /* all others */ | |
if ((CC & CC4) == 0) { /* signif off? */ | |
dst = (CC & CC1)? BLANK: fill; /* blank or fill */ | |
if ((tr = WriteB (da, dst, VW)) != 0) /* overwrite dst */ | |
return tr; | |
} | |
break; | |
} /* end switch dst */ | |
R[rn|1] = (R[rn|1] + S_ADDRINC) & WMASK; /* next pattern */ | |
} /* end while */ | |
return 0; | |
} | |
/* Routine to get and validate the next source digit */ | |
uint32 ed_getsrc (uint32 sa, uint32 *c, uint32 *d) | |
{ | |
uint32 tr; | |
if ((tr = ReadB (sa, c, VR)) != 0) /* read source byte */ | |
return tr; | |
*d = ((CC & CC2)? *c: *c >> 4) & 0xF; /* isolate digit */ | |
if (*d > 0x9) /* invalid? */ | |
return TR_DEC; | |
if (*d) /* non-zero? */ | |
CC |= CC3; | |
return 0; | |
} | |
/* Routine to advance source string */ | |
void ed_advsrc (uint32 rn, uint32 c) | |
{ | |
c = c & 0xF; /* get low digit */ | |
if (((CC & CC2) == 0) && (c > 0x9)) { /* sel left, with sign? */ | |
if ((c == 0xB) || (c == 0xD)) /* minus? */ | |
CC = CC | (CC1|CC4); /* CC1, CC4 */ | |
else CC = (CC | CC1) & ~CC4; /* no, CC1, ~CC4 */ | |
R[rn] = R[rn] + 1; /* skip two digits */ | |
} | |
else { /* adv 1 digit */ | |
if (CC & CC2) | |
R[rn] = R[rn] + 1; | |
CC = CC ^ CC2; | |
} | |
return; | |
} |