/* pdp11_ke.c: PDP-11/20 extended arithmetic element | |
Copyright (c) 1993-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 ke_ACTION OF CONTRke_ACT, 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. | |
This code draws on prior work by Tim Shoppa and Brad Parker. My thanks for | |
to them for letting me use their work. | |
EAE PDP-11/20 extended arithmetic element | |
*/ | |
#include "pdp11_defs.h" | |
#define GET_SIGN_L(v) (((v) >> 31) & 1) | |
#define GET_SIGN_W(v) (((v) >> 15) & 1) | |
#define GET_SIGN_B(v) (((v) >> 7) & 1) | |
/* KE11A I/O address offsets 0177300 - 0177316 */ | |
#define KE_DIV 000 /* divide */ | |
#define KE_AC 002 /* accumulator */ | |
#define KE_MQ 004 /* MQ */ | |
#define KE_MUL 006 /* multiply */ | |
#define KE_SC 010 /* step counter */ | |
#define KE_NOR 012 /* normalize */ | |
#define KE_LSH 014 /* logical shift */ | |
#define KE_ASH 016 /* arithmetic shift */ | |
/* Status register */ | |
#define KE_SR_C 0001 /* carry */ | |
#define KE_SR_SXT 0002 /* AC<15:0> = MQ<15> */ | |
#define KE_SR_Z 0004 /* AC = MQ = 0 */ | |
#define KE_SR_MQZ 0010 /* MQ = 0 */ | |
#define KE_SR_ACZ 0020 /* AC = 0 */ | |
#define KE_SR_ACM1 0040 /* AC = 177777 */ | |
#define KE_SR_N 0100 /* last op negative */ | |
#define KE_SR_NXV 0200 /* last op ovf XOR N */ | |
#define KE_SR_DYN (KE_SR_SXT|KE_SR_Z|KE_SR_MQZ|KE_SR_ACZ|KE_SR_ACM1) | |
/* Visible state */ | |
uint32 ke_AC = 0; | |
uint32 ke_MQ = 0; | |
uint32 ke_SC = 0; | |
uint32 ke_SR = 0; | |
t_stat ke_rd (int32 *data, int32 PA, int32 access); | |
t_stat ke_wr (int32 data, int32 PA, int32 access); | |
t_stat ke_reset (DEVICE *dptr); | |
uint32 ke_set_SR (void); | |
t_stat ke_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); | |
const char *ke_description (DEVICE *dptr); | |
#define IOLN_KE 020 | |
DIB ke_dib = { IOBA_AUTO, IOLN_KE, &ke_rd, &ke_wr, 0 }; | |
UNIT ke_unit = { | |
UDATA (NULL, UNIT_DISABLE, 0) | |
}; | |
REG ke_reg[] = { | |
{ ORDATAD (AC, ke_AC, 16, "accumulator") }, | |
{ ORDATAD (MQ, ke_MQ, 16, "multiplier-quotient") }, | |
{ ORDATAD (SC, ke_SC, 6, "shift count") }, | |
{ ORDATAD (SR, ke_SR, 8, "status register") }, | |
{ NULL } | |
}; | |
MTAB ke_mod[] = { | |
{ MTAB_XTD|MTAB_VDV|MTAB_VALR, 010, "ADDRESS", NULL, | |
NULL, &show_addr, NULL, "Bus address" }, | |
{ 0 } | |
}; | |
DEVICE ke_dev = { | |
"KE", &ke_unit, ke_reg, ke_mod, | |
1, 10, 31, 1, 8, 8, | |
NULL, NULL, &ke_reset, | |
NULL, NULL, NULL, | |
&ke_dib, DEV_DISABLE | DEV_DIS | DEV_UBUS, 0, | |
NULL, NULL, NULL, &ke_help, NULL, NULL, | |
&ke_description | |
}; | |
/* KE read - reads are always 16b, to even addresses */ | |
t_stat ke_rd (int32 *data, int32 PA, int32 access) | |
{ | |
switch (PA & 016) { /* decode PA<3:1> */ | |
case KE_AC: /* AC */ | |
*data = ke_AC; | |
break; | |
case KE_MQ: /* MQ */ | |
*data = ke_MQ; | |
break; | |
case KE_NOR: /* norm (SC) */ | |
*data = ke_SC; | |
break; | |
case KE_SC: /* SR/SC */ | |
*data = (ke_set_SR () << 8) | ke_SC; | |
break; | |
default: | |
*data = 0; | |
break; | |
} | |
return SCPE_OK; | |
} | |
/* KE write - writes trigger actual arithmetic */ | |
t_stat ke_wr (int32 data, int32 PA, int32 access) | |
{ | |
int32 quo, t32, sout, sign; | |
uint32 absd, absr; | |
switch (PA & 017) { /* decode PA<3:0> */ | |
case KE_DIV: /* divide */ | |
if ((access == WRITEB) && GET_SIGN_B (data)) /* byte write? */ | |
data |= 0177400; /* sext data to 16b */ | |
ke_SR = 0; /* N = V = C = 0 */ | |
t32 = (ke_AC << 16) | ke_MQ; /* 32b divd */ | |
if (GET_SIGN_W (ke_AC)) /* sext (divd) */ | |
t32 = t32 | ~017777777777; | |
if (GET_SIGN_W (data)) /* sext (divr) */ | |
data = data | ~077777; | |
absd = abs (t32); | |
absr = abs (data); | |
if ((absd >> 16) >= absr) { /* divide fails? */ | |
/* Based on the documentation, here's what has happened: | |
SC = 16. | |
SR<c> = (AC<15> == data<15>) | |
AC'MQ = (AC'MQ << 1) | SR<c> | |
AC = SR<c>? AC - data: AC + data | |
SR<c> = (AC<15> == data<15>) | |
SC = SC - 1 | |
stop | |
*/ | |
sign = GET_SIGN_W (ke_AC ^ data) ^ 1; /* 1 if signs match */ | |
ke_AC = (ke_AC << 1) | (ke_MQ >> 15); | |
ke_AC = (sign? ke_AC - data: ke_AC + data) & DMASK; | |
ke_MQ = ((ke_MQ << 1) | sign) & DMASK; | |
if (GET_SIGN_W (ke_AC ^ data) == 0) /* 0 if signs match */ | |
ke_SR |= KE_SR_C; | |
ke_SC = 15; /* SC clocked once */ | |
ke_SR |= KE_SR_NXV; /* set overflow */ | |
} | |
else { | |
ke_SC = 0; | |
quo = t32 / data; | |
ke_MQ = quo & DMASK; /* MQ has quo */ | |
ke_AC = (t32 % data) & DMASK; /* AC has rem */ | |
if ((quo > 32767) || (quo < -32768)) /* quo overflow? */ | |
ke_SR |= KE_SR_NXV; /* set overflow */ | |
} | |
if (GET_SIGN_W (ke_MQ)) /* result negative? */ | |
ke_SR ^= (KE_SR_N | KE_SR_NXV); /* N = 1, compl NXV */ | |
break; | |
case KE_AC: /* AC */ | |
if ((access == WRITEB) && GET_SIGN_B (data)) /* byte write? */ | |
data |= 0177400; /* sext data to 16b */ | |
ke_AC = data; | |
break; | |
case KE_AC + 1: /* AC odd byte */ | |
ke_AC = (ke_AC & 0377) | (data << 8); | |
break; | |
case KE_MQ: /* MQ */ | |
if ((access == WRITEB) && GET_SIGN_B (data)) /* byte write? */ | |
data |= 0177400; /* sext data to 16b */ | |
ke_MQ = data; | |
if (GET_SIGN_W (ke_MQ)) /* sext MQ to AC */ | |
ke_AC = 0177777; | |
else ke_AC = 0; | |
break; | |
case KE_MQ + 1: /* MQ odd byte */ | |
ke_MQ = (ke_MQ & 0377) | (data << 8); | |
if (GET_SIGN_W (ke_MQ)) /* sext MQ to AC */ | |
ke_AC = 0177777; | |
else ke_AC = 0; | |
break; | |
case KE_MUL: /* multiply */ | |
if ((access == WRITEB) && GET_SIGN_B (data)) /* byte write? */ | |
data |= 0177400; /* sext data to 16b */ | |
ke_SC = 0; | |
if (GET_SIGN_W (data)) /* sext operands */ | |
data |= ~077777; | |
t32 = ke_MQ; | |
if (GET_SIGN_W (t32)) | |
t32 |= ~077777; | |
t32 = t32 * data; | |
ke_AC = (t32 >> 16) & DMASK; | |
ke_MQ = t32 & DMASK; | |
if (GET_SIGN_W (ke_AC)) /* result negative? */ | |
ke_SR = KE_SR_N | KE_SR_NXV; /* N = 1, V = C = 0 */ | |
else ke_SR = 0; /* N = 0, V = C = 0 */ | |
break; | |
case KE_SC: /* SC */ | |
if (access == WRITEB) /* ignore byte writes */ | |
return SCPE_OK; | |
ke_SR = (data >> 8) & (KE_SR_NXV|KE_SR_N|KE_SR_C); | |
ke_SC = data & 077; | |
break; | |
case KE_NOR: /* normalize */ | |
for (ke_SC = 0; ke_SC < 31; ke_SC++) { /* max 31 shifts */ | |
if (((ke_AC == 0140000) && (ke_MQ == 0)) || /* special case? */ | |
(GET_SIGN_W (ke_AC ^ (ke_AC << 1)))) /* AC<15> != AC<14>? */ | |
break; | |
ke_AC = ((ke_AC << 1) | (ke_MQ >> 15)) & DMASK; | |
ke_MQ = (ke_MQ << 1) & DMASK; | |
} | |
if (GET_SIGN_W (ke_AC)) /* result negative? */ | |
ke_SR = KE_SR_N | KE_SR_NXV; /* N = 1, V = C = 0 */ | |
else ke_SR = 0; /* N = 0, V = C = 0 */ | |
break; | |
case KE_LSH: /* logical shift */ | |
ke_SC = 0; | |
ke_SR = 0; /* N = V = C = 0 */ | |
data = data & 077; /* 6b shift count */ | |
if (data != 0) { | |
t32 = (ke_AC << 16) | ke_MQ; /* 32b operand */ | |
if ((sign = GET_SIGN_W (ke_AC))) /* sext operand */ | |
t32 = t32 | ~017777777777; | |
if (data < 32) { /* [1,31] - left */ | |
sout = (t32 >> (32 - data)) | (-sign << data); | |
t32 = ((uint32) t32) << data; /* do shift (zext) */ | |
if (sout != (GET_SIGN_L (t32)? -1: 0)) /* bits lost = sext? */ | |
ke_SR |= KE_SR_NXV; /* no, V = 1 */ | |
if (sout & 1) /* last bit lost = 1? */ | |
ke_SR |= KE_SR_C; /* yes, C = 1 */ | |
} | |
else { /* [32,63] = -32,-1 */ | |
if ((t32 >> (63 - data)) & 1) /* last bit lost = 1? */ | |
ke_SR |= KE_SR_C; /* yes, C = 1*/ | |
t32 = (data != 32)? ((uint32) t32) >> (64 - data): 0; | |
} | |
ke_AC = (t32 >> 16) & DMASK; | |
ke_MQ = t32 & DMASK; | |
} | |
if (GET_SIGN_W (ke_AC)) /* result negative? */ | |
ke_SR ^= (KE_SR_N | KE_SR_NXV); /* N = 1, compl NXV */ | |
break; | |
/* EAE ASH differs from EIS ASH and cannot use the same overflow test */ | |
case KE_ASH: /* arithmetic shift */ | |
ke_SC = 0; | |
ke_SR = 0; /* N = V = C = 0 */ | |
data = data & 077; /* 6b shift count */ | |
if (data != 0) { | |
t32 = (ke_AC << 16) | ke_MQ; /* 32b operand */ | |
if ((sign = GET_SIGN_W (ke_AC))) /* sext operand */ | |
t32 = t32 | ~017777777777; | |
if (data < 32) { /* [1,31] - left */ | |
sout = (t32 >> (31 - data)) | (-sign << data); | |
t32 = (t32 & 020000000000) | ((t32 << data) & 017777777777); | |
if (sout != (GET_SIGN_L (t32)? -1: 0)) /* bits lost = sext? */ | |
ke_SR |= KE_SR_NXV; /* no, V = 1 */ | |
if (sout & 1) /* last bit lost = 1? */ | |
ke_SR |= KE_SR_C; /* yes, C = 1 */ | |
} | |
else { /* [32,63] = -32,-1 */ | |
if ((t32 >> (63 - data)) & 1) /* last bit lost = 1? */ | |
ke_SR |= KE_SR_C; /* yes, C = 1 */ | |
t32 = (data != 32)? /* special case 32 */ | |
(((uint32) t32) >> (64 - data)) | (-sign << (data - 32)): | |
-sign; | |
} | |
ke_AC = (t32 >> 16) & DMASK; | |
ke_MQ = t32 & DMASK; | |
} | |
if (GET_SIGN_W (ke_AC)) /* result negative? */ | |
ke_SR ^= (KE_SR_N | KE_SR_NXV); /* N = 1, compl NXV */ | |
break; | |
default: /* all others ignored */ | |
return SCPE_OK; | |
} /* end switch PA */ | |
ke_set_SR (); | |
return SCPE_OK; | |
} | |
/* Update status register based on current AC, MQ */ | |
uint32 ke_set_SR (void) | |
{ | |
ke_SR &= ~KE_SR_DYN; /* clr dynamic bits */ | |
if (ke_MQ == 0) /* MQ == 0? */ | |
ke_SR |= KE_SR_MQZ; | |
if (ke_AC == 0) { /* AC == 0? */ | |
ke_SR |= KE_SR_ACZ; | |
if (GET_SIGN_W (ke_MQ) == 0) /* MQ positive? */ | |
ke_SR |= KE_SR_SXT; | |
if (ke_MQ == 0) /* MQ zero? */ | |
ke_SR |= KE_SR_Z; | |
} | |
if (ke_AC == 0177777) { /* AC == 177777? */ | |
ke_SR |= KE_SR_ACM1; | |
if (GET_SIGN_W (ke_MQ) == 1) /* MQ negative? */ | |
ke_SR |= KE_SR_SXT; | |
} | |
return ke_SR; | |
} | |
/* Reset routine */ | |
t_stat ke_reset (DEVICE *dptr) | |
{ | |
ke_SR = 0; | |
ke_SC = 0; | |
ke_AC = 0; | |
ke_MQ = 0; | |
return auto_config(0, 0); | |
} | |
t_stat ke_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) | |
{ | |
const char *const text = | |
/*567901234567890123456789012345678901234567890123456789012345678901234567890*/ | |
"KE11A Extended Arithmetic Option (KE)\n" | |
"\n" | |
" The KE11A extended arithmetic option (KE) provides multiply, divide,\n" | |
" normalization, and multi-bit shift capability on Unibus PDP-11's that\n" | |
" lack the EIS instruction set.\n" | |
"\n" | |
" The KE11-A performs five arithmetic operations.\n" | |
" a. Multiplication\n" | |
" b. Division\n" | |
" c. Three different shift operations on data operands of up to 32 bits.\n" | |
"\n" | |
" In practice, it was only sold with the PDP-11/20.\n" | |
" The KE is disabled by default.\n"; | |
fprintf (st, "%s", text); | |
fprint_set_help (st, dptr); | |
fprint_show_help (st, dptr); | |
fprint_reg_help (st, dptr); | |
return SCPE_OK; | |
} | |
const char *ke_description (DEVICE *dptr) | |
{ | |
return "KE11-A extended arithmetic element"; | |
} |