382 lines
15 KiB
C
382 lines
15 KiB
C
/* 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";
|
|
}
|