simh-testsetgenerator/ND100/nd100_cpu.c
Anders Magnusson 5cda68abce ND100: MMS-1 support (Memory Management 1) + other fixes for Nord-100.
Can now boot the last test programs from floppy, and runs INSTRUCTION-C
and PAGING-C without errors.
2023-05-18 16:53:06 -04:00

1796 lines
51 KiB
C

/*
* Copyright (c) 2023 Anders Magnusson.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <setjmp.h>
#include "sim_defs.h"
#include "nd100_defs.h"
#define MAXMEMSIZE 512*1024
typedef struct {
int ir;
int16 sts;
int16 d;
int16 p;
int16 b;
int16 l;
int16 a;
int16 t;
int16 x;
} Hist_entry ;
#define HIST_IR_INVALID -1
#define HIST_MIN 0
#define HIST_MAX 1000000
static int32 hist_p = 0;
static int32 hist_cnt = 0;
static Hist_entry *hist = NULL;
static struct intr *ilnk[4]; /* level 10-13 */
jmp_buf env;
uint16 R[8], RBLK[16][8], regSTH, oregP;
int curlvl; /* current interrupt level */
int iic, iie, iid; /* IIC/IIE/IID register */
int pid, pie; /* PID/PIE register */
int ald, eccr, pvl, lmp, opr;
#define SETC() (regSTL |= STS_C)
#define CLRC() (regSTL &= ~STS_C)
#define SETQ() (regSTL |= STS_Q)
#define CLRQ() (regSTL &= ~STS_Q)
#define SETO() (regSTL |= STS_O)
#define CLRO() (regSTL &= ~STS_O)
t_stat cpu_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw);
t_stat cpu_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw);
t_stat cpu_reset (DEVICE *dptr);
t_stat hist_set(UNIT * uptr, int32 val, CONST char * cptr, void * desc);
t_stat hist_show(FILE * st, UNIT * uptr, int32 val, CONST void * desc);
static void hist_fprintf(FILE *fp, int itemNum, Hist_entry *hptr);
static void hist_save(int ir);
static int getoff(int ir);
static int iox_check(int dev);
static int nd_trr(int reg);
static int nd_tra(int reg);
static int nd_mcl(int reg);
static int nd_mst(int reg);
static int highest_level(void);
static void identrm(int);
static uint16 add3(uint16 a, uint16 d, uint16 c);
int fls(int);
int ins_store(int ir, int addr);
int ins_stdf(int ir, int addr);
int ins_lddf(int ir, int addr);
int ins_min(int ir, int addr);
int ins_load(int ir, int addr);
int ins_add(int ir, int addr);
int ins_andor(int ir, int addr);
void ins_dnz(int ins);
void ins_nlz(int ins);
int ins_fad(int ir, int addr);
int ins_fsb(int ir, int addr);
int ins_fmu(int ir, int addr);
int ins_fdv(int ir, int addr);
int ins_mpy(int ir, int addr);
int ins_jmpl(int ir, int addr);
int ins_cjp(int ir, int addr);
int ins_skp(int ir, int addr);
int ins_skip_ext(int IR);
int ins_rop(int ir, int addr);
int ins_mis(int IR, int addr);
int ins_sht(int IR, int addr);
int ins_na(int IR, int addr);
int ins_iox(int IR, int addr);
int ins_arg(int IR, int addr);
int ins_bop(int IR, int addr);
#define ID(x) (((x) & ND_MEMMSK) >> ND_MEMSH)
int (*ins_table[32])(int ir, int addr) = {
ins_store, ins_store, ins_store, ins_store, /* STZ/STA/STT/STX */
ins_stdf, ins_lddf, ins_stdf, ins_lddf, /* STD/LDD/STF/LDF */
ins_min, ins_load, ins_load, ins_load, /* MIN/LDA/LDT/LDX */
ins_add, ins_add, ins_andor, ins_andor, /* ADD/SUB/ADN/ORA */
ins_fad, ins_fsb, ins_fmu, ins_fdv, /* FAD/FSB/FMU/FDV */
ins_mpy, ins_jmpl, ins_cjp, ins_jmpl, /* MPY/JMP/CJP/JPL */
ins_skp, ins_rop, ins_mis, ins_sht, /* SKP/ROP/MIS/SHT */
ins_na, ins_iox, ins_arg, ins_bop /* --/IOX/ARG/BOP */
};
UNIT cpu_unit = { UDATA (NULL, UNIT_FIX + UNIT_BINK, MAXMEMSIZE) };
REG cpu_reg[] = {
{ ORDATA(STS, R[0], 16) },
{ ORDATA(D, regD, 16) },
{ ORDATA(P, regP, 16) },
{ ORDATA(B, regB, 16) },
{ ORDATA(L, regL, 16) },
{ ORDATA(A, regA, 16) },
{ ORDATA(T, regT, 16) },
{ ORDATA(X, regX, 16) },
{ ORDATA(oldP, oregP, 16) },
{ DRDATA(LVL, curlvl, 4) },
{ DRDATA(LMP, lmp, 16) },
{ DRDATA(PVL, pvl, 4) },
{ ORDATA(PID, pid, 16) },
{ ORDATA(PIE, pie, 16) },
{ ORDATA(IIC, iic, 4) },
{ ORDATA(IIE, iie, 10) },
{ ORDATA(OPR, opr, 16) },
{ NULL }
};
MTAB cpu_mod[] = {
{ MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "HISTORY", "HISTORY",
&hist_set, &hist_show },
{ 0 }
};
DEVICE cpu_dev = {
"CPU", &cpu_unit, cpu_reg, cpu_mod,
1, 8, 16, 1, 8, 16,
&cpu_ex, &cpu_dep, &cpu_reset,
NULL, NULL, NULL
};
t_stat
sim_instr(void)
{
int IR;
int reason;
int n, i;
int first = 1;
uint16 off;
(void)setjmp(env);
reason = 0;
while (reason == 0) {
if (sim_interval <= 0)
if ((reason = sim_process_event ()))
break;
if (regSTL & STS_Z)
intrpt14(IIE_V, PM_DMA); /* no longjmp here */
if ((pid & pie) > (0177777 >> (15-curlvl)) && ISION()) {
/* need to interrupt */
pvl = curlvl;
n = highest_level();
for (i = 0; i < 8; i++) {
RBLK[curlvl][i] = R[i];
R[i] = RBLK[n][i];
}
curlvl = n;
if (curlvl == 14 && iic == 1) /* mon call */
regT = SEXT8(IR);
}
/*
* ND100 manual clause 2.3.8.3 says that the instruction faults happens
* after the P reg is incremented, hence will point to one instruction
* past the instruction causing the fault.
* But; if the fault is during fetch, P will not be incremented.
*/
IR = rdmem(regP, M_FETCH);
oregP = regP++;
sim_interval--;
if (regSTH & STS_TG)
return STOP_BP;
if (first == 0 && sim_brk_summ && sim_brk_test(oregP, SWMASK ('E'))) {
reason = STOP_BP;
break;
}
first = 0;
if (hist_cnt)
hist_save(IR);
/*
* Execute instruction. We intercept it here before calling
* the instruction routine and just update IR.
*/
if (ISEXR(IR)) { /* Execute instruction */
IR = R[(IR >> 3) & 07];
#ifdef notyet
if (ISEXR(IR))
trap...
#endif
if (hist_cnt)
hist_save(IR);
}
if (ID(IR) < ND_CJP || ID(IR) == ND_JPL)
off = getoff(IR);
reason = (*ins_table[ID(IR)])(IR, off);
}
return reason;
}
t_stat
cpu_reset(DEVICE *dptr)
{
sim_brk_types = sim_brk_dflt = SWMASK ('E');
regSTH |= STS_N100;
return SCPE_OK;
}
t_stat
cpu_ex(t_value *vptr, t_addr addr, UNIT *uptr, int32 sw)
{
if (addr >= MAXMEMSIZE)
return SCPE_ARG;
*vptr = rdmem(addr, M_PT); /* XXX */
return SCPE_OK;
}
/* Memory deposit */
t_stat
cpu_dep(t_value val, t_addr addr, UNIT *uptr, int32 sw)
{
return SCPE_ARG;
}
t_stat
cpu_set_size(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
return SCPE_ARG;
}
t_stat
cpu_boot(int32 unitno, DEVICE *dptr)
{
return SCPE_ARG;
}
/*
* Store register.
*/
int
ins_store(int IR, int off)
{
int n = ((IR >> 11) & 03) + 4;
wrmem(off, n == 4 ? 0 : R[n], SELPT2(IR));
return SCPE_OK;
}
/*
* Store double or triple reg.
*/
int
ins_stdf(int IR, int off)
{
int pt = SELPT2(IR);
if (ID(IR) == ID(ND_STF) /* && 48-bit */)
wrmem(off++, regT, pt);
wrmem(off++, regA, pt);
wrmem(off, regD, pt);
return SCPE_OK;
}
/*
* Load double or triple reg.
*/
int
ins_lddf(int IR, int off)
{
int pt = SELPT2(IR);
if (ID(IR) == ID(ND_LDF) /* && 48-bit */)
regT = rdmem(off++, pt);
regA = rdmem(off++, pt);
regD = rdmem(off, pt);
return SCPE_OK;
}
/*
* Load one reg.
*/
int
ins_load(int IR, int off)
{
R[((IR&014000) >> 11) + 4] = rdmem(off, SELPT2(IR));
return SCPE_OK;
}
/*
* Increment (and skip?).
*/
int
ins_min(int IR, int off)
{
uint16 s;
int pt = SELPT2(IR);
wrmem(off, s = rdmem(off, pt) + 1, pt);
if (s == 0)
regP++;
return SCPE_OK;
}
/*
* Add/sub.
*/
int
ins_add(int IR, int off)
{
uint16 d = rdmem(off, SELPT2(IR));
int n = 0;
if (ID(IR) == ID(ND_SUB))
n = 1, d = ~d;
regA = add3(regA, d, n);
return SCPE_OK;
}
/*
* And/or
*/
int
ins_andor(int IR, int off)
{
uint16 s = rdmem(off, SELPT2(IR));
regA = BIT11(IR) ? (regA | s) : (regA & s);
return SCPE_OK;
}
/*
* Byte instructions (BFILL, MOVB, MOVBF). Requires at least ND100.
*
* Byte operands occupy fields within the memory. Operands are specified
* by two 16-bit words, known as descriptors, giving the start address
* and the field length.
*
* The start address is in register A if source, X if destination (D1)
* The rest of the descriptor is in D if source, T if destination (D2)
*
* In D2 bits
* 11-0 are the field length (in bytes).
* 12 - Ignored
* 13 - Must be 0
* 14 - 0 = normal PT, 1 = alternate
* 15 - 0 = left byte start, 1 = right byte.
*
*/
#define BYTELN(x) ((x) & 07777)
/*
* Byte fill (BFILL).
*
* This instruction has only one operand. The destination operand is
* specified in the X and T registers. The right-most byte in the A-reg
* (bits 0-7) is filled into the destination field.
* After execution, the X-register and T-register bit 15 point to the
* end of the field (after the last byte). The T-register bits (0-11)
* equal zero.
* The instruction will always have a skip return (no error condition).
*/
static void
ins_bfill(int IR)
{
while (regT & 07777) {
wrbyte(regX, regA, BIT15(regT), M_APT);
regT--;
regT ^= 0100000;
if (BIT15(regT) == 0)
regX++;
}
regP++; /* skip return */
}
/*
* Move bytes (MOVB)
*
* This instruction moves a block of bytes from the location specified for
* the source operand to the location specified for the destination operand.
* The move operation takes care of source— and destination-field overlap.
* The number of bytes moved is determined by the shortest field length of
* the operands.
* After execution, the AD and XT registers (bit 15 in D and T) point to
* the end of the field that is moved (after the last byte). D-reg. bits
* 0-11 equal zero and T-reg. bits 0-11 contain the number of bytes moved.
* The T—reg. bits 12—13 and the D-reg. bit 12 are used during the execution,
* and are left cleared. Bit 13 must be zero before execution (used as an
* interrupt mark).
* The instruction will always have a skip return (no error condition).
*
* Implementation note: Because this instruction can be interrupted (by a
* page fault for example) byte for byte must be moved, and every state
* will be stored in the registers (similar to the microcode implementation).
* The usage is:
* - BIT13(regD): all regs are setup in the middle of execution.
*/
static void
ins_movb(int IR)
{
int i;
if (BIT13(regD) == 0) {
/* setup for copy */
int len = BYTELN(regD);
if (BYTELN(regT) < len)
len = BYTELN(regT);
regT = (regT & 0140000) | len; /* T is max */
regD = (regD & 0140000); /* D is zero */
regD |= (1 << 13); /* setup done! */
}
if (regX > regA) { /* copy bottom-top */
for (i = BYTELN(regD); i < BYTELN(regT); i++, regD++) {
int8 w = rdbyte(regA, BIT15(regD), M_APT);
wrbyte(regX, w, BIT15(regT), M_APT);
regD ^= 0100000;
if (BIT15(regD) == 0) regA++;
regT ^= 0100000;
if (BIT15(regT) == 0) regX++;
}
regD &= 0140000; /* Clear setup + count bits */
} else { /* copy top-bottom */
}
regP++; /* skip return */
}
/*
* This instruction moves a block of bytes from the location specified as
* the source operand to the location specified as the destination operand.
* The move operation always starts with the first byte (lower address).
*
* The number of bytes moved is determined by the shortest field length of
* the operands. Forbidden overlap exists when the source data to be moved,
* will be destroyed. That happens when a byte is stored in a word before
* that word is read from memory. This is reported by an error return (no skip).
*
* After successful execution, the A,D and X,T registers (bit 15 in D and T)
* point to the end of the fields that are moved (after the last byte).
* The numbers initially contained in the D- and T-registers, bits 0-11,
* are decremented by the number of bytes moved.
*
* The T—reg. bits 12—13 and the D—reg. bit 12 are used during the execution
* and are left cleared. Bit 13 must be zero before execution (used as an
* interrupt mark). The instruction will have a skip—return when
* no illegal overlap exists.
*/
static void
ins_movbf(int IR)
{
int i;
if (BIT13(regD) == 0) {
/* setup for copy */
int len = BYTELN(regD);
if (BYTELN(regT) < len)
len = BYTELN(regT);
regT = (regT & 0140000) | len; /* T is max */
regD = (regD & 0140000); /* D is zero */
regD |= (1 << 13); /* setup done! */
if (regX > regA && regX < (regA + (len >> 1)))
return;
}
for (i = BYTELN(regD); i < BYTELN(regT); i++, regD++) {
int8 w = rdbyte(regA, BIT15(regD), M_APT);
wrbyte(regX, w, BIT15(regT), M_APT);
regD ^= 0100000;
if (BIT15(regD) == 0) regA++;
regT ^= 0100000;
if (BIT15(regT) == 0) regX++;
}
regD &= 0140000; /* Clear setup + count bits */
regT &= 0140000; /* Clear setup + count bits */
regP++; /* skip return */
}
/*
* Instructions with the same encoding as skip instructions.
*/
int
ins_skip_ext(int IR)
{
uint16 d;
int16 ss, sd;
int32 shc;
int paddr, reason = 0;
if ((IR & 0177707) == ND_SKP_CLEPT) {
intrpt14(IIE_II, PM_CPU);
} else if ((IR & 0177700) == ND_SKP_LDATX) {
/* Special physmem instructions */
d = regX + ((IR & 070) >> 3);
paddr = ((unsigned int)regT << 16) | (unsigned int)d;
switch (IR & ~070) {
case ND_SKP_LDATX:
regA = prdmem(paddr, PM_CPU);
break;
case ND_SKP_STZTX:
pwrmem(paddr, 0, PM_CPU);
break;
case ND_SKP_STATX:
pwrmem(paddr, regA, PM_CPU);
break;
case ND_SKP_STDTX:
pwrmem(paddr++, regA, PM_CPU);
pwrmem(paddr, regD, PM_CPU);
break;
case ND_SKP_LDXTX:
regX = prdmem(paddr, PM_CPU);
break;
case ND_SKP_LDDTX:
regA = prdmem(paddr++, PM_CPU);
regD = prdmem(paddr, PM_CPU);
break;
case ND_SKP_LDBTX:
regB = (prdmem(paddr, PM_CPU) << 1) | 0177000;
break;
default:
intrpt14(IIE_II, PM_CPU);
}
} else if (IR == ND_SKP_MIX3) {
/* X = (A-1)*3 */
regX = (regA-1)*3;
} else if (IR == ND_SKP_IDENT10) {
identrm(10);
} else if (IR == ND_SKP_IDENT11) {
identrm(11);
} else if (IR == ND_SKP_IDENT12) {
identrm(12);
} else if (IR == ND_SKP_IDENT13) {
identrm(13);
} else if (IR == ND_SKP_ADDD) {
intrpt14(IIE_II, PM_CPU);
} else if (IR == ND_SKP_BFILL) {
ins_bfill(IR);
} else if (IR == ND_SKP_MOVB) {
ins_movb(IR);
} else if (IR == ND_SKP_MOVBF) {
ins_movbf(IR);
} else if (IR == ND_SKP_LWCS) {
;
} else if (IR == 0140127) { /* XXX */
intrpt14(IIE_II, PM_CPU);
} else if (IR == ND_SKP_VERSN) {
intrpt14(IIE_II, PM_CPU);
} else if (IR == ND_SKP_LBYT) {
d = rdmem(regT + (regX >> 1), M_APT);
if (regX & 1)
regA = d & 0377;
else
regA = d >> 8;
} else if (IR == ND_SKP_SBYT) {
d = regT + (regX >> 1);
if (regX & 1)
wrmem(d, (rdmem(d, M_APT) & 0xff00) | (regA & 0xff), M_APT);
else
wrmem(d, (rdmem(d, M_APT) & 0xff) | (regA << 8), M_APT);
} else if ((IR & 0177700) == ND_SKP_RMPY) {
ss = R[(IR & 070) >> 3];
sd = R[IR & 07];
shc = ss * sd;
regD = shc;
regA = shc >> 16;
} else if ((IR & 0177700) == ND_SKP_RDIV) {
ss = R[(IR & 070) >> 3];
shc = (regA << 16) | regD;
regA = shc / ss;
regD = shc % ss;
} else if (IR == 0142700 || IR == 0143700) {
/* ??? what is this? */
intrpt14(IIE_II, PM_CPU);
} else
reason = STOP_UNHINS;
return reason;
}
/*
* The instruction SRB <level * 8> stores the contents of the register
* block on the program level specified in the level field of
* the instruction. The specified register block is stored in
* succeeding memory locations starting at the location specified by
* the contents of the X register.
* If the current program level is specified, the stored P register points
* to the instruction following SRB.
*
* Affected: (EL), +1 +2 +3 +4 +5 +6 +7
* P X T A D L STS B
*/
static int s2r[] = { rnP, rnX, rnT, rnA, rnD, rnL, rnSTS, rnB };
static void
ins_srb(int IR)
{
int i, n = (IR >> 3) & 017;
/* Save current level (maybe used) to reg block */
for (i = 0; i < 8; i++)
RBLK[curlvl][i] = R[i];
/* store requested block to memory */
for (i = 0; i < 8; i++)
wrmem(regX+i, RBLK[n][s2r[i]], M_APT);
}
/*
* The instruction LRB <level * 8> loads the contents of the register
* block on program level specified in the level field of the instruction.
* The specified register block is loaded by the contents of succeeding
* memory locations starting at the location specified by the contents
* of the X register.
* If the current program level is specified, the P register is not affected.
* Affected: All the registers on specified program level are affected.
* Note: If the current level is specified, the P register is not affected.
*/
static void
ins_lrb(int IR)
{
int i, n = (IR >> 3) & 017;
/* fetch from memory */
for (i = 0; i < 8; i++)
RBLK[n][s2r[i]] = rdmem(regX+i, M_APT);
RBLK[n][rnSTS] &= 0377;
if (n == curlvl)
for (i = 0; i < 8; i++)
if (i != rnP)
R[i] = RBLK[n][i];
}
/*
* Priv instructions, mostly for handling status reg.
*/
static int
nd_mis_opc(int IR)
{
static int srbits[] = { 0, STS_IONI, STS_IONI, 0,
STS_PONI, (STS_PONI|STS_IONI), STS_SEXI, STS_SEXI,
STS_PONI, 0, (STS_PONI|STS_IONI) };
int rv = 0;
mm_privcheck();
switch (IR) {
/* case ND_MIS_OPCOM: */
/* break; */
case ND_MIS_IOF:
case ND_MIS_POF:
case ND_MIS_PIOF:
case ND_MIS_REX:
regSTH &= ~srbits[IR & 017];
break;
case ND_MIS_ION:
case ND_MIS_SEX:
case ND_MIS_PON:
case ND_MIS_PION:
regSTH |= srbits[IR & 017];
break;
case ND_MIS_IOXT:
rv = iox_check(regT);
break;
case ND_MIS_EXAM:
regT = prdmem((regA << 16) | regD, M_FETCH);
break;
case ND_MIS_DEPO:
pwrmem((regA << 16) | regD, regT, M_FETCH);
break;
default:
rv = STOP_UNHINS;
break;
}
return rv;
}
/*
* Miscellaneous instructions.
*/
int
ins_mis(int IR, int off)
{
int reason = 0;
int n, i;
if ((IR & 0177760) == ND_MIS_OPCOM)
reason = nd_mis_opc(IR);
else if ((IR & ND_MIS_TRMSK) == ND_MIS_TRA)
reason = nd_tra(IR & ~ND_MIS_TRMSK);
else if ((IR & ND_MIS_TRMSK) == ND_MIS_TRR)
reason = nd_trr(IR & ~ND_MIS_TRMSK);
else if ((IR & ND_MIS_TRMSK) == ND_MIS_MST)
reason = nd_mst(IR & ~ND_MIS_TRMSK);
else if ((IR & ND_MIS_TRMSK) == ND_MIS_MCL)
reason = nd_mcl(IR & ~ND_MIS_TRMSK);
else if ((IR & ND_MIS_IRRMSK) == ND_MIS_IRR) {
mm_privcheck();
n = (IR >> 3) & 017;
if (n == curlvl)
regA = R[IR & 07];
else
regA = RBLK[n][IR & 07];
} else if ((IR & ND_MIS_IRRMSK) == ND_MIS_IRW) {
mm_privcheck();
n = (IR >> 3) & 017;
RBLK[n][IR & 07] = regA;
if (n == curlvl && (IR & 07) != rnP)
R[IR & 07] = regA;
} else if ((IR & ND_MIS_RBMSK) == ND_MIS_SRB) {
ins_srb(IR);
} else if ((IR & ND_MIS_RBMSK) == ND_MIS_LRB) {
ins_lrb(IR);
} else if ((IR & ND_MONMSK) == ND_MON) {
RBLK[14][rnT] = SEXT8(IR);
intrpt14(IIE_MC, PM_CPU);
} else if ((IR & ND_MONMSK) == ND_WAIT) {
if (ISION() == 0) {
reason = STOP_WAIT;
} else if (curlvl > 0) {
pid &= ~(1 << curlvl);
n = highest_level();
if (curlvl != n) {
for (i = 0; i < 8; i++) {
RBLK[curlvl][i] = R[i];
R[i] = RBLK[n][i];
}
}
curlvl = n;
}
} else if ((IR & ND_MONMSK) == ND_MIS_NLZ) {
/* convert integer to floating point */
ins_nlz(IR);
} else if ((IR & ND_MONMSK) == ND_MIS_DNZ) {
ins_dnz(IR);
} else
reason = STOP_UNHINS;
return reason;
}
int
ins_sht(int IR, int off)
{
char sht_reg[] = { rnT, rnD, rnA, 0 };
int m, n, rs, i;
uint32 ushc;
rs = sht_reg[(IR >> 7) & 03];
n = BIT5(IR) ? 32 - IR & 037 : IR & 037;
m = BIT7(regSTL);
ushc = rs ? R[rs] : (regA << 16) | regD;
if (BIT5(IR)) { /* right */
int mm = BIT0(ushc);
for (i = 0; i < n; i++) {
m = BIT0(ushc);
ushc = ushc >> 1;
switch (IR & 03000) {
case 0: /* Arithmetic */
ushc |= (rs ? (BIT14(ushc) << 15) :
(BIT30(ushc) << 31));
break;
case 01000: /* ROT */
ushc |= (rs ? (m << 15) :
(m << 31));
break;
case 02000: /* zero end input */
break;
case 03000: /* link end input */
ushc |= (rs ? (mm << 15) :
(mm << 31));
break;
}
}
} else { /* left */
int mm = (rs ? BIT15(ushc) : BIT31(ushc));
for (i = 0; i < n; i++) {
m = (rs ? BIT15(ushc) : BIT31(ushc));
ushc = ushc << 1;
switch (IR & 03000) {
case 01000: /* ROT */
ushc |= m; break;
case 0: /* Arithmetic */
case 02000: /* zero end input */
break;
case 03000: /* link end input */
ushc |= mm; break;
}
}
}
regSTL = (regSTL & ~STS_M) | (m << 7);
if (rs == 0) {
regA = ushc >> 16;
regD = ushc;
} else
R[rs] = ushc;
return SCPE_OK;
}
int
ins_na(int IR, int addr)
{
return STOP_UNHINS;
}
int
ins_iox(int IR, int addr)
{
return iox_check(IR & ND_IOXMSK);
}
int
ins_arg(int IR, int addr)
{
int rs, n = (IR >> 8) & 03;
rs = n ? n + 4 : 3;
R[rs] = add3(BIT10(IR) ? R[rs] : 0, SEXT8(IR), 0);
return SCPE_OK;
}
int
ins_bop(int IR, int addr)
{
int rd, n, reason = 0;
regSTL = (regSTL & 0377) | regSTH; /* Hack for bskp */
rd = IR & 7;
n = (IR >> 3) & 017;
switch ((IR >> 7) & 017) {
case 000: /* BSET zero/one */
R[rd] &= ~(1 << n);
break;
case 001:
R[rd] |= (1 << n);
break;
case 002: /* BSET BCM bit = ~bit */
R[rd] ^= (1 << n);
break;
case 003: /* BSET BAC bit = K */
R[rd] &= ~(1 << n);
if (regSTL & STS_K)
R[rd] |= (1 << n);
break;
case 004: /* BSKP zero/one */
case 005:
if (((R[rd] >> n) & 1) == BIT7(IR))
regP++;
break;
case 006: /* BSKP BCM K == ~bit */
if (((regSTL & STS_K) != 0) ^ (((R[rd] >> n) & 1) != 0))
regP++;
break;
case 010: /* BSTA store ~K and set K */
R[rd] &= ~(1 << n);
if ((regSTL & STS_K) == 0)
R[rd] |= (1 << n);
regSTL |= STS_K;
break;
case 011: /* BSTA store K and clear K */
R[rd] &= ~(1 << n);
if (regSTL & STS_K)
R[rd] |= (1 << n);
regSTL &= ~STS_K;
break;
case 012: /* BLDA load ~K */
regSTL &= ~STS_K;
if (((R[rd] >> n) & 1) == 0)
regSTL |= STS_K;
break;
case 013: /* BLDA load K */
regSTL &= ~STS_K;
if ((R[rd] >> n) & 1)
regSTL |= STS_K;
break;
case 014: /* BANC K = ~bit & K */
if (regSTL & STS_K) {
if ((R[rd] >> n) & 1)
regSTL &= ~STS_K;
}
break;
case 015: /* BAND K = bit & K */
if (regSTL & STS_K) {
if (((R[rd] >> n) & 1) == 0)
regSTL &= ~STS_K;
}
break;
case 016: /* BORC K = ~bit | K */
if ((regSTL & STS_K) == 0) {
if (((R[rd] >> n) & 1) == 0)
regSTL |= STS_K;
}
break;
case 017: /* BORA K = bit | K */
if ((regSTL & STS_K) == 0) {
if ((R[rd] >> n) & 1)
regSTL |= STS_K;
}
break;
default:
reason = STOP_UNHINS;
break;
}
regSTL &= 0377;
return reason;
}
/*
* 48-bit floating point. T has the sign and exponent, A has the most
* significant bits and D has the least.
* Exponent is biased 16384, mantissa is 0.5 <= X < 1.0.
*/
struct fp {
int s;
int e;
t_uint64 m;
};
static void
mkfp48(struct fp *fp, uint16 w1, uint16 w2, uint16 w3)
{
fp->s = BIT15(w1);
fp->e = (w1 & 077777) - 16384;
fp->m = ((t_uint64)w2 << 16) + (t_uint64)w3;
}
/*
* Description of DNZ instruction from the NORD-10 manual:
*
* Converts the floating number in the floating accumulator to a
* single precision fixed point number in the A register, using the
* scaling of the DNZ instruction as a scaling factor.
*
* When converting to integers, the scaling factor should be —16, a
* greater scaling factor will cause the fixed point number to be
* greater. After this instruction the contents of the T and D registers
* will all be zeros.
*
* If the conversion causes underflow, the T, A and D registers will all
* be set to zero. If the conversion causes overflow, the error
* indicator 2 is set to one. Overflow occurs if the resulting integer
* in absolute value is greater than 32767.
* The conversion will truncate and negative numbers are converted to
* positive numbers before conversion. The result will again be
* converted to a negative number.
*/
void
ins_dnz(int ins)
{
int32 val = 0; /* XXX remove warnings */
int sh;
sh = (regT & 077777) - 16384 + SEXT8(ins);
if (sh < 0) {
val = regA;
val >>= -sh;
} else {
}
#ifdef notyet
if (val > 32767)
z = 1;
#endif
if (regT & 0100000)
val = -val;
regT = regD = 0;
regA = (uint16)val;
}
/*
* Description of DNZ instruction from the NORD-10 manual:
*
* Converts the number in the A register to a standard form floating
* number in the floating accumulator, using the scaling of the NLZ
* instruction as a scaling factor. For integers, the scaling factor
* should be +16, a larger scaling factor will result in a higher floating,
* point number. Because of the single precision fixed point number,
* the D register will be cleared.
*/
void
ins_nlz(int ins)
{
int sh, s;
int val;
s = 0;
regD = 0;
if (regA == 0) { /* zero, special case */
regT = 0;
return;
}
val = (int)(int16)regA;
sh = 16384 + SEXT8(ins);
if (val < 0)
val = -val, s = 0100000;
if (val > 32767)
val >>= 1, sh++;
while ((val & 0100000) == 0) {
val <<= 1;
sh--;
}
regT = sh + s;
regA = val;
}
/*
* Multiplication of two 48-bit floating point numbers.
*
* The contents of the floating accumulator are multiplied with the
* number of the effective floating word locations with the result in
* the floating accumulator. The previous setting of the carry and overflow
* indicators are lost.
* Affected: (T), (A), (D), O, Q, TG
*/
int
ins_fmu(int IR, int addr)
{
struct fp f1, f2;
int s3, e3, pt = SELPT2(IR);
t_uint64 m3;
/* Fetch from memory */
mkfp48(&f1, rdmem(addr, pt), rdmem(addr+1, pt), rdmem(addr+2, pt));
/* From registers */
mkfp48(&f2, regT, regA, regD);
/* calc */
m3 = f1.m * f2.m;
e3 = f1.e + f2.e;
s3 = f1.s ^ f2.s;
/* normalize (if needed) */
if ((m3 & (1ULL << 63)) == 0) {
m3 <<= 1;
e3--;
}
/* restore regs */
regA = (uint16)(m3 >> 48);
regD = (uint16)(m3 >> 32);
regT = (e3 + 16384) | (s3 << 15);
if (m3 == 0 || e3 < -16383)
regT = regA = regD = 0;
return SCPE_OK;
}
/*
* Division of two 48-bit floating point numbers.
*
* The contents of the floating accumulator are divided by the number
* in the effective floating word locations. Result in floating
* accumulator. If division by zero is attempted, the error indicator 2
* is set to one. The error indicator 2 may be sensed by a BSKP instruc-
* tion (see BOP). The previous setting of the carry and overflow
* indicators are lost.
* Affected: (T), (A), (D), Z, C, O, Q, TG
*/
int
ins_fdv(int IR, int addr)
{
struct fp f1, f2;
int s3, e3, pt;
t_uint64 m3;
/* Fetch from memory */
pt = SELPT2(IR);
mkfp48(&f1, rdmem(addr, pt), rdmem(addr+1, pt), rdmem(addr+2, pt));
/* From registers */
mkfp48(&f2, regT, regA, regD);
f2.m <<= 32;
/* Initial sanity */
if (f1.m == 0) {
regSTL |= STS_Z;
regT |= 077777;
regA = regD = 0177777;
intrpt14(IIE_V, PM_CPU);
return SCPE_OK;
}
/* calc */
s3 = f1.s ^ f2.s;
e3 = f2.e - f1.e;
m3 = f2.m / f1.m;
if (f2.m%f1.m) /* "guard" bit */
m3++;
/* normalize (if needed) */
if (m3 >= (1ULL << 32)) {
m3 >>= 1;
e3++;
}
/* restore regs */
regA = (uint16)(m3 >> 16);
regD = (uint16)m3;
regT = (e3 + 16384) | (s3 << 15);
if (f2.m == 0 || e3 < -16383)
regT = regA = regD = 0;
return SCPE_OK;
}
/*
* Add two 48-bit numbers.
*
* The contents of the effective location and the two following locations
* are added to the floating accumulator with the result in the floating
* accumulator. The previous setting of the carry and overflow indicators
* are lost.
* Affected: (T), (A), (D), C, O, Q, TG
*/
static void
add48(struct fp *f1, struct fp *f2)
{
struct fp *ft;
t_uint64 m3;
int scale, gbit;
/* Ensure f1 is larger */
if (f2->e > f1->e)
ft = f1, f1 = f2, f2 = ft;
if ((scale = f1->e - f2->e) > 31) {
m3 = f1->m;
goto done;
}
/* get shifted out guard bit */
gbit = scale ? (((1LL << scale)-1) & f2->m) != 0 : 0;
f2->m >>= scale;
m3 = (f1->m + f2->m) | gbit;
if (m3 > 0xffffffffLL) {
m3 >>= 1;
f1->e++;
}
done: regT = (f1->e + 16384) | (f1->s << 15);
regA = (uint16)(m3 >> 16);
regD = (uint16)m3;
}
/*
* Subtract two 48-bit numbers.
* The numbers are of different sign.
*
* The contents of the effective location and the two following locations
* are subtracted from the floating accumulator with the result
* in the floating accumulator. The previous setting of the carry and
* overflow indicators are lost.
* Affected: (T), (A), (D), C, O, Q, TG
*/
static void
sub48(struct fp *f1, struct fp *f2, int addr)
{
struct fp *ft;
t_uint64 m3;
int scale, gbit;
/* Ensure f1 is bigger */
if (f2->e > f1->e)
ft = f1, f1 = f2, f2 = ft;
if ((scale = f1->e - f2->e) > 31) {
m3 = f1->m;
goto done;
}
/* get shifted out sticky bit */
gbit = scale ? (((1LL << scale)-1) & f2->m) != 0 : 0;
f2->m >>= scale;
f2->e = f1->e;
/* check for swap of mantissa */
if (f2->m > f1->m)
ft = f1, f1 = f2, f2 = ft;
m3 = (f1->m - f2->m) | gbit;
if (m3 == 0) {
regT = regA = regD = 0;
return;
}
while ((m3 & 0x80000000LL) == 0){
m3 <<= 1;
f1->e--;
}
done: regT = (f1->e + 16384) | (f1->s << 15);
regA = (uint16)(m3 >> 16);
regD = (uint16)m3;
}
int
ins_fad(int IR, int addr)
{
struct fp f1, f2;
int pt = SELPT2(IR);
mkfp48(&f1, rdmem(addr, pt), rdmem(addr+1, pt), rdmem(addr+2, pt));
mkfp48(&f2, regT, regA, regD);
if (f1.s ^ f2.s)
sub48(&f1, &f2, addr);
else
add48(&f1, &f2);
return SCPE_OK;
}
int
ins_fsb(int IR, int addr)
{
struct fp f1, f2;
int pt = SELPT2(IR);
mkfp48(&f1, rdmem(addr, pt), rdmem(addr+1, pt), rdmem(addr+2, pt));
mkfp48(&f2, regT, regA, regD);
f1.s ^= 1; /* swap sign of right op */
if (f1.s ^ f2.s)
sub48(&f1, &f2, addr);
else
add48(&f1, &f2);
return SCPE_OK;
}
/*
* Add three numbers, setting overflow and carry as needed.
*/
uint16
add3(uint16 a, uint16 d, uint16 c)
{
int32 res;
CLRC();
CLRQ();
res = a + d + c;;
if (res > 0177777)
SETC();
if (((a ^ d) & 0100000) == 0) {
/* sign bits equal */
if ((a ^ res) & 0100000)
SETQ(), SETO(); /* result sign differ */
}
return res;
}
/*
* Multiply A with memory. Set flags.
*/
int
ins_mpy(int IR, int off)
{
int res = (int)(int16)regA * (int)(int16)rdmem(off, SELPT2(IR));
regA = res;
regSTL &= ~STS_Q;
if (res > 32767)
regSTL |= (STS_Q|STS_O);
return SCPE_OK;
}
/*
* Jump or jump and link.
*/
int
ins_jmpl(int IR, int off)
{
if (BIT12(IR))
regL = regP;
regP = off;
return SCPE_OK;
}
/*
* Conditional jump.
* off is unused here.
*/
int
ins_cjp(int IR, int off)
{
char cjpmsk[] = { 01, 01, 02, 02, 05, 05, 02, 01 };
int n, i;
uint16 s;
n = (IR & ND_CJPMSK) >> ND_CJPSH;
if (cjpmsk[n] & 04)
regX++;
s = n & 04 ? regX : regA;
i = (SEXT8(IR)-1);
if (cjpmsk[n] & 01) { /* pos/neg */
if (BIT8(IR) == BIT15(s))
regP += i;
} else if (cjpmsk[n] & 02) { /* zero/filled */
if (BIT8(IR) == !!s)
regP += i;
}
return SCPE_OK;
}
int
ins_skp(int IR, int off)
{
int c_o, shc, n, rv = SCPE_OK;
uint16 s, d;
if (IR & 0300) { /* extended instructions */
rv = ins_skip_ext(IR);
} else {
s = ~(IR & 070 ? R[(IR & 070) >> 3] : 0);
d = IR & 07 ? R[IR & 07] : 0;
shc = d + s + 1;
c_o = (!BIT15(s ^ d) && BIT15(d ^ shc));
switch ((IR >> 8) & 03) {
case 0: /* eql */ n = ((shc & 0177777) == 0); break;
case 1: /* geq */ n = !BIT15(shc); break;
case 2: /* gre */ n = ((BIT15(shc) ^ c_o) == 0); break;
case 3: /* mgre */ n = (shc > 0177777); break;
}
if (BIT10(IR)) n = !n;
if (n) regP++;
}
return rv;
}
int
ins_rop(int IR, int off)
{
int n, rs, rd;
uint16 s, d;
rs = (IR & 070) >> 3;
rd = IR & 07;
s = rs ? R[rs] : 0;
d = rd ? R[rd] : 0;
if (BIT6(IR)) d = 0; /* clear dest */
if (BIT7(IR)) s = ~s; /* complement src */
if (BIT10(IR)) { /* add source to destination */
n = 0;
if (BIT8(IR)) n = 1;
else if (BIT9(IR)) n = BIT6(regSTL);
d = add3(s, d, n);
} else { /* logical instructons */
if (BIT8(IR)) {
if (BIT9(IR)) d |= s;
else d &= s;
} else {
if (BIT9(IR) == 0) { /* swap */
if (rs) R[rs] = d;
d = s;
} else
d ^= s;
}
}
if (rd) R[rd] = d;
return SCPE_OK;
}
/*
* Look for some device at dev.
*/
static int
iox_check(int dev)
{
mm_privcheck();
if ((dev & 0177774) == 010)
return iox_clk(dev);
if ((dev & 0177770) == 0300)
return iox_tty(dev);
if ((dev & 0177770) == 01560)
return iox_floppy(dev);
intrpt14(IIE_IOX, PM_CPU);
return SCPE_OK;
}
static int
getoff(int ir)
{
int ea;
ea = BIT8(ir) ? regB : oregP;
if (BIT10(ir) & !BIT9(ir) & !BIT8(ir))
ea = 0;
ea += SEXT8(ir);
if (BIT9(ir))
ea = rdmem(ea & 0177777, BIT8(ir) ? M_APT : M_PT);
if (BIT10(ir))
ea += regX;
return ea & 0177777;
}
/*
* bit-clear value in internal reg.
*/
static int
nd_mcl(int reg)
{
int reason = SCPE_OK;
mm_privcheck();
switch (reg) {
case IR_STS:
regSTL &= ~(regA & 0377);
break;
case 006: /* PID */
pid &= ~regA;
break;
case 007: /* PIE */
pie &= ~regA;
break;
default:
reason = STOP_UNHINS;
}
return reason;
}
/*
* Or-set value of internal reg.
*/
static int
nd_mst(int reg)
{
int reason = SCPE_OK;
mm_privcheck();
switch (reg) {
case IR_STS:
regSTL |= (regA & 0377);
break;
case IR_PID: /* PID */
pid |= regA;
break;
case IR_PIE: /* PIE */
pie |= regA;
break;
default:
reason = STOP_UNHINS;
}
return reason;
}
/*
* Set value of internal reg.
*/
static int
nd_trr(int reg)
{
int reason = SCPE_OK;
mm_privcheck();
switch (reg) {
case IR_STS:
regSTL = regA & 0377;
break;
case IR_LMP: /* lamp reg */
lmp = regA;
break;
case IR_PCR: /* PCR (paging control register) */
mm_wrpcr();
break;
case 005: /* IIE */
iie = regA & 03776;
break;
case 006: /* PID */
pid = regA;
break;
case 007: /* PIE */
pie = regA;
break;
case IR_CCL:
break;
case IR_LCIL:
break;
case IR_UCIL:
break;
case IR_ECCR:
eccr = regA;
break;
default:
reason = STOP_UNHINS;
}
return reason;
}
/*
* Read value of internal reg.
*/
static int
nd_tra(int reg)
{
int reason = SCPE_OK;
mm_privcheck();
switch (reg) {
case IR_PANS:
regA = 0;
break;
case IR_STS: /* STS */
regA = regSTL | regSTH | (curlvl << 8);
break;
case IRR_OPR:
regA = opr;
break;
case IRR_PVL:
regA = (pvl << 3) | 0153602;
break;
case IR_IIC: /* read IIC. Clears IIC and IID */
regA = iic;
iic = iid = 0;
pid &= ~(1 << 14);
break;
case IR_PID: /* 6 */
regA = pid;
break;
case IR_PIE:
regA = pie;
break;
case IR_CSR: /* CCR */
regA = 04; /* cache disabled */
break;
case 012: /* ALD */
regA = ald;
break;
default:
reason = mm_tra(reg);
}
return reason;
}
int
highest_level()
{
int i, d = pid & pie;
for (i = 15; i >= 0; i--)
if (d & (1 << i))
return i;
return 0;
}
/*
* Find last bit set.
*/
int
fls(int msk)
{
int i, j;
if (msk == 0)
return -1;
for (i = 15, j = 0100000; i >= 0; i--, j >>= 1)
if (j & msk)
return i;
return -1;
}
/*
* Post an internal interrupt for the given source.
*/
void
intrpt14(int src, int where)
{
iid |= src; /* set detect flipflop */
for (iic = 0; (src & 1) == 0; iic++, src >>= 1)
;
if (iid & iie) { /* if internal int enabled, post priority int */
pid |= (1 << 14);
/* If we can interrupt, jump directly back */
if (ISION() && curlvl < 14 && (pie & (1 << 14)) &&
(where == PM_CPU))
longjmp(env, 0);
}
}
void
extint(int lvl, struct intr *ip)
{
pid |= (1 << lvl);
lvl -= 10;
if (ip->inuse)
return;
ip->inuse = 1;
ip->next = ilnk[lvl];
ilnk[lvl] = ip;
}
/*
* Fetch ident from interrupting device.
* If no device interrupting, post IOX error.
*/
void
identrm(int id)
{
struct intr *ip = ilnk[id-10];
if (ip == 0) {
intrpt14(IIE_IOX, PM_CPU);
regA = 0;
} else {
regA = ip->ident;
ilnk[id-10] = ip->next;
ip->next = NULL;
ip->inuse = 0;
if (ilnk[id-10] == 0)
pid &= ~(1 << id);
}
}
static void
hist_save(int ir)
{
Hist_entry *hist_ptr;
if (hist == NULL || hist_cnt == 0)
return;
if (++hist_p == hist_cnt)
hist_p = 0;
hist_ptr = &hist[hist_p];
hist_ptr->ir = ir;
hist_ptr->sts = regSTL | regSTH | (curlvl << 8);
hist_ptr->d = regD;
hist_ptr->p = oregP;
hist_ptr->b = regB;
hist_ptr->l = regL;
hist_ptr->a = regA;
hist_ptr->t = regT;
hist_ptr->x = regX;
}
t_stat
hist_set(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
int32 i, lnt;
t_stat r;
if (cptr == NULL) {
for (i = 0 ; i < hist_cnt ; i++)
hist[i].ir = HIST_IR_INVALID;
hist_p = 0;
return SCPE_OK;
}
lnt = (int32) get_uint(cptr, 10, HIST_MAX, &r);
if ((r != SCPE_OK) || (lnt && (lnt < HIST_MIN)))
return SCPE_ARG;
hist_p = 0;
if (hist_cnt) {
free(hist);
hist_cnt = 0;
hist = NULL;
}
if (lnt) {
hist = (Hist_entry *) calloc(lnt, sizeof(Hist_entry));
if (hist == NULL)
return SCPE_MEM;
hist_cnt = lnt;
}
return SCPE_OK;
}
static void
hist_fprintf(FILE *fp, int itemNum, Hist_entry *hptr)
{
t_value val;
if (hptr == NULL)
return;
if (itemNum == 0)
fprintf(fp, "\n\n");
fprintf(fp, "%06o: IR=%06o STS=%06o D=%06o B=%06o "
"L=%06o A=%06o T=%06o X=%06o ",
hptr->p & 0177777, hptr->ir & 0177777, hptr->sts & 0177777,
hptr->d & 0177777, hptr->b & 0177777, hptr->l & 0177777,
hptr->a & 0177777, hptr->t & 0177777, hptr->x & 0177777);
val = hptr->ir;
fprint_sym(fp, hptr->p, &val, 0, SWMASK ('M'));
fprintf(fp, "\n");
}
static void
ioxprint(FILE *fp, Hist_entry *hptr, int ioaddr)
{
fprintf(fp, "%06o: iox(%06o) %s A=%06o\n",
hptr->p, ioaddr & 0177777, ioaddr & 1 ? "out" : "in ",
hptr->a & 0177777);
}
t_stat
hist_show(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
int32 k, di, lnt;
CONST char *cptr = (CONST char *) desc;
t_stat r;
Hist_entry *hptr;
if (hist_cnt == 0)
return SCPE_NOFNC;
if (cptr) { /* number of entries specified */
lnt = (int32)get_uint(cptr, 10, hist_cnt, &r);
if ((r != SCPE_OK) || (lnt == 0))
return SCPE_ARG;
} else
lnt = hist_cnt;
di = hist_p - lnt;
if (di < 0)
di = di + hist_cnt;
for (k = 0 ; k < lnt ; ++k) {
hptr = &hist[(++di) % hist_cnt];
if (sim_switches & SWMASK('I')) {
/* print iox instructions */
if ((hptr->ir & ND_MEMMSK) == ND_IOX)
ioxprint(st, hptr, hptr->ir & ~ND_MEMMSK);
if (hptr->ir == ND_MIS_IOXT)
ioxprint(st, hptr, hptr->t);
} else {
if (hptr->ir != HIST_IR_INVALID)
hist_fprintf(st, k, hptr);
}
}
return SCPE_OK;
}