blob: 3d55d9004880e81a40793ee3fe45c0787cba5502 [file] [log] [blame] [raw]
/* 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";
}