blob: c7c16825ed45edb69117010c734d075e1b919868 [file] [log] [blame] [raw]
/*
Copyright (c) 2015-2016, John Forecast
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
JOHN FORECAST 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 John Forecast shall not
be used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from John Forecast.
*/
/* cdc1700_cpu.c: CDC1700 CPU simulator
*/
/*
* Implementation notes:
*
* 1. Interrupts. There is very little technical details about the interrupt
* system available in the documentation. The following information has
* been deduced from the SMM diagnostic routines.
*
* - Device interrupts
*
* Device interrupts are level-triggered. A device driver may lower the
* the interrupt trigger by:
*
* - Issue a "Clear Controller" command
* - Issue a "Clear Interrupts" command
* - Issue a device-dependent command
* (e.g. on PTP, output a new value)
*
* - CPU interrupts (Power fail, parity and protect fault)
*
* CPU interrupts are edge-triggered. The interrupt trigger is
* automatically lowered when the CPU starts processing interrupt 0.
*
* 2. There is no documention on relative timing. For example, the paper tape
* punch diagnostic enables Alarm+Data interrupts and assumes that it will
* be able to execute some number of instructions before the interrupt
* occurs. How many instructions should we delay if interrupts are enabled
* and all conditions are met to deliver the interrupt immediately?
*
* 3. Some peripherals, notably the teletypewriter, do not have a protected
* status bit. Does this mean that any application can directly affect
* them?
*
* - The teletypewriter may be addressed by either a protected or a
* nonprotected instruction (see SC17 Reference Manual).
*
* 4. The 1740/1742 line printer controllers are incorrectly documented as
* having the status register at offset 3, it is at offset 1 like all
* other peripherals.
*
* 5. For the 1738 disk pack controller, what is the correct response if an
* operation is initiated with no drive selected? For now, we'll reject
* the request.
*
* 6. For the 1706-A buffered data channel, what interrupt is used to signal
* "End of Operation"? A channel-specific interrupt or a pass-through
* interrupt from the device being controlled or some other?
*/
#include "cdc1700_defs.h"
uint16 M[MAXMEMSIZE];
uint8 P[MAXMEMSIZE];
t_uint64 Instructions;
uint16 Preg, Areg, Qreg, Mreg, CAenable, OrigPreg, Pending, IOAreg, IOQreg;
uint8 Pfault, Protected, lastP, Oflag, INTflag, DEFERflag;
t_bool ExecutionStarted = FALSE;
uint16 CharAddrMode[16];
uint16 INTlevel;
char INTprefix[8];
t_bool FirstRejSeen = FALSE;
uint32 CountRejects = 0;
t_bool FirstAddr = TRUE;
extern int disassem(char *, uint16, t_bool, t_bool, t_bool);
extern enum IOstatus doIO(t_bool, DEVICE **);
extern void fw_init(void);
extern void rebuildPending(void);
extern void dev1Interrupts(char *);
t_stat cpu_reset(DEVICE *);
t_stat cpu_set_size(UNIT *, int32, CONST char *, void *);
t_stat cpu_ex(t_value *, t_addr, UNIT *, int32);
t_stat cpu_dep(t_value, t_addr, UNIT *uptr, int32 sw);
t_stat cpu_help(FILE *, DEVICE *, UNIT *, int32, const char *);
#define UNIT_V_STOPSW (UNIT_V_UF + 1) /* Selective STOP switch */
#define UNIT_STOPSW (1 << UNIT_V_STOPSW)
#define UNIT_V_SKIPSW (UNIT_V_UF + 2) /* Selective SKIP switch */
#define UNIT_SKIPSW (1 << UNIT_V_SKIPSW)
#define UNIT_V_MODE65K (UNIT_V_UF + 3) /* 32K/65K mode switch */
#define UNIT_MODE65K (1 << UNIT_V_MODE65K)
#define UNIT_V_CHAR (UNIT_V_UF + 4) /* Character addressing */
#define UNIT_CHAR (1 << UNIT_V_CHAR)
#define UNIT_V_PROT (UNIT_V_UF + 5)
#define UNIT_PROT (1 << UNIT_V_PROT)
#define UNIT_V_MSIZE (UNIT_V_UF + 6) /* Memory size */
#define UNIT_MSIZE (1 << UNIT_V_MSIZE)
IO_DEVICE CPUdev = IODEV(NULL, "1714", CPU, 0, 0xFF, 0,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
0, 0, 0, 0, 0, 0, 0, 0, NULL);
/* CPU data structures
cpu_dev CPU device descriptor
cpu_unit CPU unit
cpu_reg CPU register list
cpu_mod CPU modifier list
*/
UNIT cpu_unit = { UDATA(NULL, UNIT_FIX+UNIT_BINK, DEFAULTMEMSIZE) };
REG cpu_reg[] = {
{ HRDATAD(P, Preg, 16, "Program address counter") },
{ HRDATAD(A, Areg, 16, "Principal arithmetic register") },
{ HRDATAD(Q, Qreg, 16, "Index register") },
{ HRDATAD(M, Mreg, 16, "Interrupt mask register") },
{ HRDATAD(O, Oflag, 1, "Overflow flag") },
{ HRDATAD(CH, CAenable, 1, "Character addressing enable flag") },
{ HRDATAD(INT, INTflag, 1, "Interrupt enable flag") },
{ HRDATAD(DEFER, DEFERflag, 1, "Interrupt deferred flag") },
{ HRDATAD(PENDING, Pending, 16, "Pending interrupt flags") },
{ HRDATAD(PFAULT, Pfault, 1, "Protect fault pending flag") },
{ NULL }
};
MTAB cpu_mod[] = {
{ MTAB_XTD|MTAB_VDV, 0, "1714 CDC 1700 series CPU", NULL, NULL, NULL },
{ UNIT_STOPSW, UNIT_STOPSW, "Selective Stop", "SSTOP",
NULL, NULL, NULL, "Enable Selective Stop" },
{ UNIT_STOPSW, 0, "No Selective Stop", "NOSSTOP",
NULL, NULL, NULL, "Disable Selective Stop" },
{ UNIT_SKIPSW, UNIT_SKIPSW, "Selective Skip", "SSKIP",
NULL, NULL, NULL, "Enable Selective Skip" },
{ UNIT_SKIPSW, 0, "No Selective Skip", "NOSSKIP",
NULL, NULL, NULL, "Disable Selective Skip" },
{ UNIT_MODE65K, UNIT_MODE65K, "65K Addressing Mode", "MODE65K",
NULL, NULL, NULL, "Enable 65K Indirect Addressing Mode" },
{ UNIT_MODE65K, 0, "32K Addressing Mode", "MODE32K",
NULL, NULL, NULL, "Enable 32K Indirect Addressing Mode" },
{ UNIT_CHAR, UNIT_CHAR, "Character Addressing", "CHAR",
NULL, NULL, NULL, "Enable Character Addressing Mode" },
{ UNIT_CHAR, 0, "No Character Addressing", "NOCHAR",
NULL, NULL, NULL, "Disable Character Addressing Mode" },
{ UNIT_PROT, UNIT_PROT, "Program Protect", "PROTECT",
NULL, NULL, NULL, "Enable Protect Mode Operation" },
{ UNIT_PROT, 0, "", "NOPROTECT",
NULL, NULL, NULL, "Disable Protect Mode Operation" },
{ UNIT_MSIZE, 4096, NULL, "4K",
&cpu_set_size, NULL, NULL, "Set Memory Size to 4KW" },
{ UNIT_MSIZE, 8192, NULL, "8K",
&cpu_set_size, NULL, NULL, "Set Memory Size to 8KW" },
{ UNIT_MSIZE, 16384, NULL, "16K",
&cpu_set_size, NULL, NULL, "Set Memory Size to 16KW" },
{ UNIT_MSIZE, 32768, NULL, "32K",
&cpu_set_size, NULL, NULL, "Set Memory Size to 32KW" },
#if MAXMEMSIZE > 32768
{ UNIT_MSIZE, 65536, NULL, "64K",
&cpu_set_size, NULL, NULL, "Set Memory Size to 64KW" },
#endif
{ 0 }
};
#define DBG_ALL \
(DBG_DISASS | DBG_TRACE | DBG_TARGET | DBG_INPUT | DBG_OUTPUT | DBG_FULL)
DEBTAB cpu_deb[] = {
{ "DISASSEMBLE", DBG_DISASS, "Disassemble instructions while tracing" },
{ "IDISASSEMBLE", DBG_IDISASS, "Disassemble while interrupts active" },
{ "INTERRUPT", DBG_INTR, "Display interrupt entry/exit" },
{ "TRACE", DBG_TRACE, "Trace instruction execution" },
{ "ITRACE", DBG_ITRACE, "Trace while interrupts active" },
{ "TARGET", DBG_TARGET, "Display target address of instructions" },
{ "INPUT", DBG_INPUT, "Display INP instruction execution" },
{ "OUTPUT", DBG_OUTPUT, "Display OUT instruction execution" },
{ "IO", DBG_INPUT | DBG_OUTPUT, "Display INP and OUT execution" },
{ "INTLVL", DBG_INTLVL, "Add interrupt level to all displays" },
{ "PROTECT", DBG_PROTECT, "Display protect faults" },
{ "MISSING", DBG_MISSING, "Display info about missing devices" },
{ "FULL", DBG_ALL },
{ NULL }
};
DEVICE cpu_dev = {
"CPU", &cpu_unit, cpu_reg, cpu_mod,
1, 16, 16, 1, 16, 16,
&cpu_ex, &cpu_dep, &cpu_reset,
NULL, NULL, NULL,
&CPUdev,
DEV_DEBUG | DEV_NOEQUIP, 0, cpu_deb,
NULL, NULL, &cpu_help, NULL, NULL, NULL
};
/*
* Table of instructions which store to memory
*/
static t_bool storagemode[] = {
FALSE, FALSE, FALSE, FALSE, /* SPECIAL, JMP, MUI, DVI */
TRUE, FALSE, TRUE, TRUE, /* STQ, RTJ, STA, SPA */
FALSE, FALSE, FALSE, FALSE, /* ADD, SUB, AND, EOR */
FALSE, TRUE, FALSE, FALSE /* LDA, RAO, LDQ, ADQ */
};
/*
* Table of parity values
*/
static uint8 parity[256] = {
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0
};
/*
* Table of interrupt bits
*/
static uint16 interruptBit[] = {
0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080,
0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000, 0x8000
};
/*
* Reset routine
*/
t_stat cpu_reset(DEVICE *dptr)
{
int i;
INTlevel = 0;
CAenable = 0;
Pending = 0;
fw_init();
sim_brk_types = sim_brk_dflt = SWMASK('E');
Pfault = FALSE;
FirstRejSeen = FALSE;
CountRejects = 0;
/*
* Reset the saved character addressing mode for each interrupt level.
*/
for (i = 0; i < 16; i++)
CharAddrMode[i] = 0;
return SCPE_OK;
}
/*
* Memory size change
*/
t_stat cpu_set_size(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
uint16 mc = 0;
uint32 i;
if ((val <= 0) || (val > MAXMEMSIZE))
return SCPE_ARG;
for (i = val; i < cpu_unit.capac; i++)
mc |= M[i];
if ((mc != 0) && (!get_yn("Really truncate memory [N]?", FALSE)))
return SCPE_OK;
cpu_unit.capac = val;
for (i = cpu_unit.capac; i < MAXMEMSIZE; i++)
M[i] = 0;
return SCPE_OK;
}
/*
* Memory examine
*/
t_stat cpu_ex(t_value *vptr, t_addr addr, UNIT *uptr, int32 sw)
{
if (addr >= cpu_unit.capac)
return SCPE_NXM;
if (vptr != NULL)
*vptr = M[addr];
return SCPE_OK;
}
/*
* Memory deposit
*/
t_stat cpu_dep(t_value val, t_addr addr, UNIT *uptr, int32 sw)
{
if (addr >= cpu_unit.capac)
return SCPE_NXM;
M[addr] = TRUNC16(val);
return SCPE_OK;
}
/*
* Dump the current register contents on debugging output.
*/
void dumpRegisters(void)
{
fprintf(DBGOUT,
"%s[A: %04X, Q: %04X, M: %04X, Ovf: %d, Pfault: %d, I: %d, D: %d]",
INTprefix, Areg, Qreg, Mreg, Oflag, Pfault, INTflag, DEFERflag);
}
/*
* Indicate processor is running in protected mode.
*/
t_bool inProtectedMode(void)
{
return (cpu_unit.flags & UNIT_PROT) != 0;
}
/*
* Returns CPU interrupt status. This always returns 0 since the interrupt
* has already been set in the Pending register.
*/
uint16 cpuINTR(DEVICE *dptr)
{
return 0;
}
/*
* Raise an internal interrupt. Used for Power Fail, Parity Error and
* Program Protect Fault. Only Program Protect Fault can occur in emulation.
*/
void RaiseInternalInterrupt(void)
{
if ((cpu_dev.dctrl & DBG_INTR) != 0) {
fprintf(DBGOUT,
"%sINT(0)[A: %04X, Q: %04X, M: %04X, Ovf: %d, Pfault: %d, I: %d, D: %d]\r\n",
INTprefix, Areg, Qreg, Mreg, Oflag, Pfault, INTflag, DEFERflag);
}
Pending |= 1;
}
/*
* Raise an external interrupt associated with a peripheral device.
*/
void RaiseExternalInterrupt(DEVICE *dev)
{
IO_DEVICE *iod = IODEVICE(dev);
uint16 Opending = Pending;
/*
* Don't touch the STATUS register if the device has completely
* non-standard interrupts.
*/
if (iod->iod_raised == NULL)
iod->STATUS |= IO_ST_INT;
rebuildPending();
if ((cpu_dev.dctrl & DBG_INTR) != 0) {
uint16 level = iod->iod_equip;
fprintf(DBGOUT,
"%sINT(%d, %s)[A: %04X, Q: %04X, M: %04X, P: %04x->%04x, Ovf: %d, I: %d, D: %d]\r\n",
INTprefix, level, dev->name, Areg, Qreg, Mreg, Opending, Pending,
Oflag, INTflag, DEFERflag);
}
}
/*
* Memory reference routines
*/
/*
* Reads are always allowed
*/
uint16 LoadFromMem(uint16 addr)
{
return M[MEMADDR(addr)];
}
/*
* Writes require checking for protected mode. This routine returns TRUE
* if the write succeeded and FALSE if the write failed and an interrupt
* has been scheduled.
*/
t_bool StoreToMem(uint16 addr, uint16 value)
{
if ((cpu_unit.flags & UNIT_PROT) != 0) {
if (!Protected) {
if (P[MEMADDR(addr)]) {
if ((cpu_dev.dctrl & DBG_PROTECT) != 0) {
fprintf(DBGOUT,
"%sProtect fault storing to memory at %04x => %04X\r\n",
INTprefix, OrigPreg, addr);
}
Pfault = TRUE;
RaiseInternalInterrupt();
return FALSE;
}
}
}
M[MEMADDR(addr)] = value;
return TRUE;
}
/*
* I/O devices can maintain their own protected status. Perform similar
* checking as StoreToMem() using the device protected status but do not
* generate a "protect fault" since the error will be reported back through
* the device status. Return TRUE if the write succeeded and FALSE if the
* write failed due to a protect failure.
*/
t_bool IOStoreToMem(uint16 addr, uint16 value, t_bool prot)
{
if ((cpu_unit.flags & UNIT_PROT) != 0) {
if (!prot) {
if (P[MEMADDR(addr)]) {
return FALSE;
}
}
}
M[MEMADDR(addr)] = value;
return TRUE;
}
/*
* The 1700 adder is a 16-bit one's complement subtractive adder which
* eliminates minus zero in all but one case (the only case is when minus zero
* is added to minus zero).
*/
uint16 doSUB(uint16 a, uint16 b)
{
uint32 ea = EXTEND16(a);
uint32 eb = EXTEND16(b);
uint32 result = ea - eb;
if (((a - b) & 0x10000) != 0)
result -= 1;
if (((result & 0x18000) != 0x18000) &&
((result & 0x18000) != 0x00000))
Oflag = 1;
return TRUNC16(result);
}
uint16 doADD(uint16 a, uint16 b)
{
return doSUB(a, TRUNC16(~b));
}
/*
* Internal operations such as address computations do not modify the
* overflow flag.
*/
uint16 doADDinternal(uint16 a, uint16 b)
{
uint32 result = a - TRUNC16(~b);
if ((result & 0x10000) != 0)
result -= 1;
return TRUNC16(result);
}
/*
* For multiply, we do the actual multiply in the positive domain and adjust
* the resulting sign based on the input values.
*/
void doMUL(uint16 a)
{
uint32 val1, result = 0;
uint16 sign = Areg ^ a;
int i;
val1 = ABS(Areg) & 0xFFFF;
a = ABS(a);
/*
* Accumulate the result via shift and add.
*/
for (i = 0; i < 15; i++) {
if ((a & 1) != 0)
result += val1;
val1 <<= 1;
a >>= 1;
}
if ((sign & SIGN) != 0)
result = ~result;
Qreg = result >> 16;
Areg = TRUNC16(result);
}
/*
* For divide, we once again do the actual division in the positive domain
* and adjust the resulting signs based on the input values.
*/
void doDIV(uint16 a)
{
uint32 result = 0, divisor, remainder = (Qreg << 16) | Areg;
uint32 mask = 1;
uint8 sign = 0, rsign = 0;
if ((Qreg & SIGN) != 0) {
remainder = ~remainder;
sign++;
rsign++;
}
divisor = ABS(a) & 0xFFFF;
if ((a & SIGN) != 0)
sign++;
/*
* The documentation does not specify the result of a divide by zero.
* Hopefully, the diagnostics will provide some insight. Until then we
* just set the overflow flag and return saturated positive values.
*/
if (divisor == 0) {
Oflag = 1;
Areg = Qreg = MAXPOS;
return;
}
while (divisor < remainder) {
divisor <<= 1;
mask <<= 1;
}
do {
if (remainder >= divisor) {
remainder -= divisor;
result += mask;
}
divisor >>= 1;
mask >>= 1;
} while (mask != 0);
/*
* Again the documentation does not specify whether the result/remainder
* can be negative zero. For now I'm going to assume that they cannot.
*/
if ((result & 0xFFFF8000) != 0)
Oflag = 1;
if ((result & 0x7FFF) != 0)
if ((sign & 1) != 0)
result = ~result;
if ((remainder & 0x7FFF) != 0)
if (rsign != 0)
remainder = ~remainder;
Areg = TRUNC16(result);
Qreg = TRUNC16(remainder);
}
/*
* Compute the effective address of an instruction
*/
t_stat getEffectiveAddr(uint16 p, uint16 instr, uint16 *addr)
{
uint16 count = MAXINDIRECT;
uint16 delta = instr & OPC_ADDRMASK;
uint32 result = delta;
if (delta == 0) {
result = Preg;
INCP;
switch (instr & (MOD_RE | MOD_IN)) {
/*
* Mode 0, delta == 0 does not follow the regular addressing model
* of the other modes.
*/
case 0:
if (!storagemode[(instr & OPC_MASK) >> 12] ||
((instr & (MOD_I1 | MOD_I2)) != 0))
result = LoadFromMem(result);
break;
case MOD_RE:
result = doADDinternal(result, LoadFromMem(result));
break;
case MOD_RE | MOD_IN:
result = doADDinternal(result, LoadFromMem(result));
/* FALLTHROUGH */
case MOD_IN:
result = LoadFromMem(result);
if ((cpu_unit.flags & UNIT_MODE65K) == 0) {
while (result & 0x8000) {
if (--count == 0)
return SCPE_LOOP;
result = LoadFromMem(result & 0x7FFF);
}
}
break;
}
} else {
switch (instr & (MOD_RE | MOD_IN)) {
case 0:
break;
case MOD_RE:
result = doADDinternal(EXTEND8(result), p);
break;
case MOD_RE | MOD_IN:
result = doADDinternal(EXTEND8(result), p);
/* FALLTHROUGH */
case MOD_IN:
result = LoadFromMem(result);
if ((cpu_unit.flags & UNIT_MODE65K) == 0) {
while (result & 0x8000) {
if (--count == 0)
return SCPE_LOOP;
result = LoadFromMem(result & 0x7FFF);
}
}
break;
}
}
/*
* Handle indexing
*/
if ((instr & MOD_I1) != 0)
result = doADDinternal(result, Qreg);
if ((instr & MOD_I2) != 0)
result = doADDinternal(result, LoadFromMem(0xFF));
*addr = result;
return SCPE_OK;
}
/*
* Similar effective address calculation routines without modifying the
* CPU registers.
*/
/*
* Compute the effective address of an instruction
*/
t_stat disEffectiveAddr(uint16 p, uint16 instr, uint16 *base, uint16 *addr)
{
uint16 count = MAXINDIRECT;
uint16 delta = instr & OPC_ADDRMASK;
uint32 result = delta;
if (delta == 0) {
result = MEMADDR(p + 1);
switch (instr & (MOD_RE | MOD_IN)) {
case 0:
if ((instr & (MOD_I1 | MOD_I2)) != 0)
result = LoadFromMem(result);
break;
case MOD_RE:
result = doADDinternal(result, LoadFromMem(result));
break;
case MOD_RE | MOD_IN:
result = doADDinternal(result, LoadFromMem(result));
/* FALLTHROUGH */
case MOD_IN:
result = LoadFromMem(result);
if ((cpu_unit.flags & UNIT_MODE65K) == 0) {
while (result & 0x8000) {
if (--count == 0)
return SCPE_LOOP;
result = LoadFromMem(result & 0x7FFF);
}
}
break;
}
} else {
switch (instr & (MOD_RE | MOD_IN)) {
case 0:
break;
case MOD_RE:
result = doADDinternal(EXTEND8(result), p);
break;
case MOD_RE | MOD_IN:
result = doADDinternal(EXTEND8(result), p);
/* FALLTHROUGH */
case MOD_IN:
result = LoadFromMem(result);
if ((cpu_unit.flags & UNIT_MODE65K) == 0) {
while (result & 0x8000) {
if (--count == 0)
return SCPE_LOOP;
result = LoadFromMem(result & 0x7FFF);
}
}
break;
}
}
*base = result;
/*
* Handle indexing
*/
if ((instr & MOD_I1) != 0)
result = doADDinternal(result, Qreg);
if ((instr & MOD_I2) != 0)
result = doADDinternal(result, LoadFromMem(0xFF));
*addr = result;
return SCPE_OK;
}
/*
* Execute a single instruction on the current CPU. Register P must be
* pointing at the instruction to execute.
*/
t_stat executeAnInstruction(void)
{
DEVICE *dev;
uint16 instr, operand, operand1, operand2, from;
uint32 temp;
t_stat status;
INTprefix[0] = '\0';
if ((cpu_dev.dctrl & DBG_INTLVL) != 0)
sprintf(INTprefix, "%02d> ", INTlevel);
if (INTflag && !DEFERflag) {
if ((operand = Pending & Mreg) != 0) {
int i, maxIntr = INTR_1705;
for (i = 0; i < maxIntr; i++) {
if ((operand & interruptBit[i]) != 0) {
operand1 = INTERRUPT_BASE + (4 * i);
operand2 = from = Preg;
if ((cpu_unit.flags & UNIT_MODE65K) == 0) {
operand2 = (operand2 & 0x7FFF) | (Oflag ? 0x8000 : 0);
Oflag = 0;
}
Protected = TRUE;
StoreToMem(operand1, operand2);
Preg = operand1 + 1;
INTflag = 0;
INTlevel++;
if ((cpu_unit.flags & UNIT_CHAR) != 0) {
CharAddrMode[i] = CAenable;
CAenable = 0;
}
if (FirstRejSeen) {
fprintf(DBGOUT,
"%s %u Rejects terminated by interrupt\r\n",
INTprefix, CountRejects);
FirstRejSeen = FALSE;
CountRejects = 0;
}
if ((cpu_dev.dctrl & DBG_INTR) != 0) {
if (i == 1) {
char intbuf[32];
char *buf = &intbuf[0];
dev1Interrupts(buf);
if (buf[0] == ' ')
buf++;
fprintf(DBGOUT,
"%s===> Device 1 Stations [%s]\n",
INTprefix, buf);
}
fprintf(DBGOUT,
"%s===> Interrupt %d entered at 0x%04X, from %04X, Inst: %llu\r\n",
INTprefix, i, Preg, from, Instructions);
}
if (i == 0)
Pending &= 0xFFFE;
if ((cpu_dev.dctrl & DBG_INTLVL) != 0)
sprintf(INTprefix, "%02d> ", INTlevel);
if (sim_brk_summ && sim_brk_test(Preg, SWMASK('E'))) {
/*
* This was not really an instruction execution.
*/
sim_interval++;
return SCPE_IBKPT;
}
break;
}
}
}
}
DEFERflag = 0;
if (((cpu_dev.dctrl & DBG_TRACE) != 0) ||
(((cpu_dev.dctrl & DBG_ITRACE) != 0) && (INTlevel != 0))) {
fprintf(DBGOUT,
"%sA:%04X Q:%04X I:%04X M:%04X Ovf:%d Pfault: %d Inst:%llu\r\n",
INTprefix, Areg, Qreg, LoadFromMem(0xFF),
Mreg, Oflag, Pfault, Instructions);
}
if (((cpu_dev.dctrl & DBG_DISASS) != 0) ||
(((cpu_dev.dctrl & DBG_IDISASS) != 0) && (INTlevel != 0))) {
char buf[128];
t_bool target = (cpu_dev.dctrl & DBG_TARGET) != 0;
disassem(buf, Preg, TRUE, target, TRUE);
fprintf(DBGOUT, "%s%s\r\n", INTprefix, buf);
}
/*
* Get the next instruction, moving the current PC to the next word address.
* We need to save the PC of the current instruction so that we can pass it
* into the operand calculation routine(s).
*/
OrigPreg = Preg;
lastP = Protected;
Protected = P[MEMADDR(OrigPreg)];
instr = LoadFromMem(OrigPreg);
INCP;
/*
* Check for protected mode operation where we are about to execute a
* protected instruction and the previous instruction was unprotected.
*/
if ((cpu_unit.flags & UNIT_PROT) != 0) {
if (!lastP && Protected) {
if ((cpu_dev.dctrl & DBG_PROTECT) != 0) {
fprintf(DBGOUT,
"%sProtect fault, unprotected after protected at %04X\r\n",
INTprefix, OrigPreg);
}
Pfault = TRUE;
RaiseInternalInterrupt();
/*
* Make sure we skip over the failing instruction.
*/
if ((instr & OPC_MASK) != 0) {
if ((instr & OPC_ADDRMASK) == 0) {
INCP;
}
}
/*
* Execute this instructionas an unprotected Selective Stop.
*/
if ((cpu_unit.flags & UNIT_STOPSW) != 0) {
dumpRegisters();
return SCPE_SSTOP;
}
return SCPE_OK;
}
}
Instructions++;
switch (instr & OPC_MASK) {
case OPC_ADQ:
if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK)
return status;
if (!ISCONSTANT(instr))
operand = LoadFromMem(operand);
Qreg = doADD(Qreg, operand);
break;
case OPC_LDQ:
if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK)
return status;
if (!ISCONSTANT(instr))
operand = LoadFromMem(operand);
Qreg = operand;
break;
case OPC_RAO:
if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK)
return status;
StoreToMem(operand, doADD(LoadFromMem(operand), 1));
break;
case OPC_LDA:
if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK)
return status;
if (!ISCONSTANT(instr))
operand = LoadFromMem(operand);
if ((cpu_unit.flags & UNIT_CHAR) != 0) {
uint16 xxx = operand;
if (CAenable != 0) {
if ((LoadFromMem(0xFF) & 0x01) == 0)
operand >>= 8;
operand = (Areg & 0xFF00) | (operand & 0xFF);
fprintf(DBGOUT,
"CM LDA at P: %04X, A: %04X, I: %04X, SRC: %04X, Result: %04X\r\n",
OrigPreg, Areg, LoadFromMem(0xFF), xxx, operand);
}
}
Areg = operand;
break;
case OPC_EOR:
if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK)
return status;
if (!ISCONSTANT(instr))
operand = LoadFromMem(operand);
Areg ^= operand;
break;
case OPC_AND:
if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK)
return status;
if (!ISCONSTANT(instr))
operand = LoadFromMem(operand);
Areg &= operand;
break;
case OPC_SUB:
if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK)
return status;
if (!ISCONSTANT(instr))
operand = LoadFromMem(operand);
Areg = doSUB(Areg, operand);
break;
case OPC_ADD:
if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK)
return status;
if (!ISCONSTANT(instr))
operand = LoadFromMem(operand);
Areg = doADD(Areg, operand);
break;
case OPC_SPA:
if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK)
return status;
if (StoreToMem(operand, Areg)) {
temp = parity[Areg & 0xFF] + parity[(Areg >> 8) & 0xFF];
if ((temp & 1) != 0)
Areg = 0;
else Areg = 1;
}
break;
case OPC_STA:
if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK)
return status;
if ((cpu_unit.flags & UNIT_CHAR) != 0) {
if (CAenable != 0) {
operand1 = LoadFromMem(operand);
if ((LoadFromMem(0xFF) & 0x01) == 0)
operand1 = (operand1 & 0xFF) | ((Areg << 8) & 0xFF00);
else operand1 = (operand1 & 0xFF00) | (Areg & 0xFF);
StoreToMem(operand, operand1);
break;
}
}
StoreToMem(operand, Areg);
break;
case OPC_RTJ:
if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK)
return status;
StoreToMem(operand, Preg);
Preg = operand;
INCP;
break;
case OPC_STQ:
if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK)
return status;
StoreToMem(operand, Qreg);
break;
case OPC_DVI:
if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK)
return status;
if (!ISCONSTANT(instr))
operand = LoadFromMem(operand);
doDIV(operand);
break;
case OPC_MUI:
if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK)
return status;
if (!ISCONSTANT(instr))
operand = LoadFromMem(operand);
doMUL(operand);
break;
case OPC_JMP:
if ((status = getEffectiveAddr(OrigPreg, instr, &operand)) != SCPE_OK)
return status;
Preg = operand;
break;
case OPC_SPECIAL:
switch (instr & OPC_SPECIALMASK) {
case OPC_SLS:
if ((cpu_unit.flags & UNIT_STOPSW) != 0) {
dumpRegisters();
return SCPE_SSTOP;
}
break;
case OPC_SKIPS:
switch (instr & (OPC_SKIPS | OPC_SKIPMASK)) {
case OPC_SAZ:
if (Areg == 0)
Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT);
break;
case OPC_SAN:
if (Areg != 0)
Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT);
break;
case OPC_SAP:
if ((Areg & SIGN) == 0)
Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT);
break;
case OPC_SAM:
if ((Areg & SIGN) != 0)
Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT);
break;
case OPC_SQZ:
if (Qreg == 0)
Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT);
break;
case OPC_SQN:
if (Qreg != 0)
Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT);
break;
case OPC_SQP:
if ((Qreg & SIGN) == 0)
Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT);
break;
case OPC_SQM:
if ((Qreg & SIGN) != 0)
Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT);
break;
case OPC_SWS:
if ((cpu_unit.flags & UNIT_SKIPSW) != 0)
Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT);
break;
case OPC_SWN:
if ((cpu_unit.flags & UNIT_SKIPSW) == 0)
Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT);
break;
case OPC_SOV:
if (Oflag)
Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT);
Oflag = 0;
break;
case OPC_SNO:
if (!Oflag)
Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT);
Oflag = 0;
break;
/*
* The emulator does not generate/check storage parity, so these
* skips always operate as though parity is valid.
*/
case OPC_SPE:
break;
case OPC_SNP:
Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT);
break;
case OPC_SPF:
if (Pfault)
Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT);
Pfault = FALSE;
rebuildPending();
break;
case OPC_SNF:
if (!Pfault)
Preg = doADDinternal(Preg, instr & OPC_SKIPCOUNT);
break;
}
break;
case OPC_INP:
if ((cpu_dev.dctrl & DBG_INPUT) != 0)
if (!FirstRejSeen)
fprintf(DBGOUT,
"%sINP:[A: %04X, Q: %04X, M: %04X, Ovf: %d, I: %d, D: %d]\r\n",
INTprefix, Areg, Qreg, Mreg, Oflag, INTflag, DEFERflag);
switch (doIO(FALSE, &dev)) {
case IO_REPLY:
if (FirstRejSeen) {
fprintf(DBGOUT,
"%s %u Rejects terminated by a Reply\r\n",
INTprefix, CountRejects);
FirstRejSeen = FALSE;
CountRejects = 0;
}
if ((cpu_dev.dctrl & DBG_INPUT) != 0)
fprintf(DBGOUT, "%sINP: ==> REPLY, A: %04X\r\n",
INTprefix, Areg);
break;
case IO_REJECT:
if ((cpu_dev.dctrl & DBG_INPUT) != 0)
if (!FirstRejSeen)
fprintf(DBGOUT, "%sINP: ==> REJECT\r\n", INTprefix);
Preg = doADDinternal(Preg, EXTEND8(instr & OPC_MODMASK));
if ((dev != NULL) && ((dev->flags & DEV_REJECT) != 0))
return SCPE_REJECT;
/*
* Check if reject forces the instruction to restart. If so,
* reduce a sequence of Reject logs into a single entry.
*/
if (Preg == OrigPreg) {
if ((dev != NULL) && ((dev->dctrl & DBG_DFIRSTREJ) != 0)) {
if (!FirstRejSeen) {
FirstRejSeen = TRUE;
CountRejects = 1;
}
} else CountRejects++;
}
break;
case IO_INTERNALREJECT:
if ((cpu_dev.dctrl & DBG_INPUT) != 0)
fprintf(DBGOUT, "%sINP: ==> INTERNALREJECT\r\n", INTprefix);
Preg = doADDinternal(OrigPreg, EXTEND8(instr & OPC_MODMASK));
if ((dev != NULL) && ((dev->flags & DEV_REJECT) != 0))
return SCPE_REJECT;
break;
}
break;
case OPC_OUT:
if ((cpu_dev.dctrl & DBG_OUTPUT) != 0)
if (!FirstRejSeen)
fprintf(DBGOUT,
"%sOUT:[A: %04X, Q: %04X, M: %04X, Ovf: %d, I: %d, D: %d]\r\n",
INTprefix, Areg, Qreg, Mreg, Oflag, INTflag, DEFERflag);
switch (doIO(TRUE, &dev)) {
case IO_REPLY:
if (FirstRejSeen) {
fprintf(DBGOUT,
"%s %u Rejects terminated by a Reply\r\n",
INTprefix, CountRejects);
FirstRejSeen = FALSE;
CountRejects = 0;
}
if ((cpu_dev.dctrl & DBG_OUTPUT) != 0)
fprintf(DBGOUT, "%sOUT: ==> REPLY\r\n", INTprefix);
break;
case IO_REJECT:
if ((cpu_dev.dctrl & DBG_OUTPUT) != 0)
fprintf(DBGOUT, "%sOUT: ==> REJECT\r\n", INTprefix);
Preg = doADDinternal(Preg, EXTEND8(instr & OPC_MODMASK));
if ((dev != NULL) && ((dev->flags & DEV_REJECT) != 0))
return SCPE_REJECT;
/*
* Check if reject forces the instruction to restart. If so,
* reduce a sequence of Reject logs into a single entry.
*/
if (Preg == OrigPreg) {
if ((dev != NULL) && ((dev->dctrl & DBG_DFIRSTREJ) != 0)) {
if (!FirstRejSeen) {
FirstRejSeen = TRUE;
CountRejects = 1;
}
} else CountRejects++;
}
break;
case IO_INTERNALREJECT:
if ((cpu_dev.dctrl & DBG_OUTPUT) != 0)
fprintf(DBGOUT, "%sOUT: ==> INTERNALREJECT\r\n", INTprefix);
Preg = doADDinternal(OrigPreg, EXTEND8(instr & OPC_MODMASK));
if ((dev != NULL) && ((dev->flags & DEV_REJECT) != 0))
return SCPE_REJECT;
break;
}
break;
/*
* The following instructions (EIN, IIN, SPB, CPB and EXI)
* generate a protect fault if the protect switch is set and
* the instruction is not protected. If the system is unable
* to handle the interrupt (interrupts disabled or interrupt 0
* masked), the instruction executes as a "Selective Stop". Note
* that the character addressing instructions are a subset
* of IIN and have to be checked separately.
*/
case OPC_IIN:
if ((cpu_unit.flags & UNIT_CHAR) != 0) {
if ((instr & 0xFF) != 0) {
switch (instr) {
case OPC_ECA:
CAenable = 1;
break;
case OPC_DCA:
CAenable = 0;
break;
}
break;
}
}
case OPC_EIN:
case OPC_SPB:
case OPC_CPB:
case OPC_EXI:
if ((cpu_unit.flags & UNIT_PROT) != 0) {
if (!Protected) {
if ((cpu_dev.dctrl & DBG_PROTECT) != 0) {
fprintf(DBGOUT,
"%sProtect fault EIN/SPB/CPB/EXI at %04X\r\n",
INTprefix, OrigPreg);
}
Pfault = TRUE;
RaiseInternalInterrupt();
/*
* Execute this instruction as though it was a "Selective Stop".
*/
if ((cpu_unit.flags & UNIT_STOPSW) != 0) {
dumpRegisters();
return SCPE_SSTOP;
}
break;
}
}
/*
* Execute the instruction.
*/
switch (instr & OPC_SPECIALMASK) {
case OPC_EIN:
if ((cpu_dev.dctrl & DBG_INTR) != 0) {
fprintf(DBGOUT,
"%sEIN:[A: %04X, Q: %04X, M: %04X, Ovf: %d, I: %d, D: %d]\r\n",
INTprefix, Areg, Qreg, Mreg, Oflag, INTflag, DEFERflag);
}
INTflag = DEFERflag = 1;
break;
case OPC_IIN:
if ((cpu_dev.dctrl & DBG_INTR) != 0) {
fprintf(DBGOUT,
"%sIIN:[A: %04X, Q: %04X, M: %04X, Ovf: %d, I: %d, D: %d]\r\n",
INTprefix, Areg, Qreg, Mreg, Oflag, INTflag, DEFERflag);
}
INTflag = 0;
break;
case OPC_SPB:
SETPROTECT(Qreg);
break;
case OPC_CPB:
CLRPROTECT(Qreg);
break;
case OPC_EXI:
operand = instr & OPC_MODMASK;
if ((operand & 0xC3) != 0) {
Preg = OrigPreg;
return SCPE_INVEXI;
}
if ((cpu_dev.dctrl & DBG_INTR) != 0)
fprintf(DBGOUT, "%s<=== Interrupt %d exit [M: %04X]\r\n",
INTprefix, (operand >> 2) & 0xF, Mreg);
Preg = operand2 = LoadFromMem(INTERRUPT_BASE + operand);
if ((cpu_unit.flags & UNIT_MODE65K) == 0) {
Preg &= 0x7FFF;
Oflag = operand2 & 0x8000 ? 1 : 0;
}
if (INTlevel != 0)
INTlevel--;
INTflag = 1;
if ((cpu_unit.flags & UNIT_CHAR) != 0) {
CAenable = CharAddrMode[(operand >> 2) & 0xF];
CharAddrMode[(operand >> 2) & 0xF] = 0;
}
break;
}
break;
case OPC_INTER:
/*
* Protection fault if the instruction is not protected and
* modifies M
*/
if ((cpu_unit.flags & UNIT_PROT) != 0) {
if ((instr & MOD_D_M) != 0) {
if (!Protected) {
if ((cpu_dev.dctrl & DBG_PROTECT) != 0) {
fprintf(DBGOUT,
"%sProtect fault INTER to M at %04X\r\n",
INTprefix, OrigPreg);
}
Pfault = TRUE;
RaiseInternalInterrupt();
/*
* Execute the instruction as a "Selective Stop".
*/
if ((cpu_unit.flags & UNIT_STOPSW) != 0) {
dumpRegisters();
return SCPE_SSTOP;
}
break;
}
}
}
operand1 = instr & MOD_O_A ? Areg : 0xFFFF;
switch (instr & (MOD_O_Q | MOD_O_M)) {
case 0:
operand2 = 0xFFFF;
break;
case MOD_O_M:
operand2 = Mreg;
break;
case MOD_O_Q:
operand2 = Qreg;
break;
case MOD_O_M | MOD_O_Q:
operand2 = Qreg | Mreg;
break;
}
switch (instr & (MOD_LP | MOD_XR)) {
case 0:
operand = doADD(operand1, operand2);
break;
case MOD_XR:
operand = operand1 ^ operand2;
break;
case MOD_LP:
operand = operand1 & operand2;
break;
case MOD_XR | MOD_LP:
operand = ~(operand1 & operand2);
break;
}
if ((instr & MOD_D_A) != 0)
Areg = operand;
if ((instr & MOD_D_Q) != 0)
Qreg = operand;
if ((instr & MOD_D_M) != 0) {
if ((cpu_dev.dctrl & DBG_INTR) != 0)
fprintf(DBGOUT, "%s<=== M changed from %04X to %04X\r\n",
INTprefix, Mreg, operand);
Mreg = operand;
}
break;
case OPC_INA:
Areg = doADD(Areg, EXTEND8(instr & OPC_MODMASK));
break;
case OPC_ENA:
Areg = EXTEND8(instr & OPC_MODMASK);
break;
case OPC_NOP:
break;
case OPC_ENQ:
Qreg = EXTEND8(instr & OPC_MODMASK);
break;
case OPC_INQ:
Qreg = doADD(Qreg, EXTEND8(instr & OPC_MODMASK));
break;
case OPC_SHIFTS:
/* Assume shifts without A or Q are a NOP */
if ((instr & (MOD_S_A | MOD_S_Q)) != 0) {
int i, count = instr & OPC_SHIFTCOUNT;
uint32 temp32;
if (count) {
switch (instr & (OPC_SHIFTS | OPC_SHIFTMASK)) {
case OPC_QRS:
temp32 = Qreg;
for (i = 0; i < count; i++) {
temp32 >>= 1;
if ((temp32 & 0x4000) != 0)
temp32 |= SIGN;
}
Qreg = TRUNC16(temp32);
break;
case OPC_ARS:
temp32 = Areg;
for (i = 0; i < count; i++) {
temp32 >>= 1;
if ((temp32 & 0x4000) != 0)
temp32 |= SIGN;
}
Areg = TRUNC16(temp32);
break;
case OPC_LRS:
temp32 = (Qreg << 16) | Areg;
for (i = 0; i < count; i++) {
temp32 >>= 1;
if ((temp32 & 0x40000000) != 0)
temp32 |= 0x80000000;
}
Areg = TRUNC16(temp32);
Qreg = TRUNC16(temp32 >> 16);
break;
case OPC_QLS:
temp32 = Qreg;
for (i = 0; i < count; i++) {
temp32 <<= 1;
if ((temp32 & 0x10000) != 0)
temp32 |= 1;
}
Qreg = TRUNC16(temp32);
break;
case OPC_ALS:
temp32 = Areg;
for (i = 0; i < count; i++) {
temp32 <<= 1;
if ((temp32 & 0x10000) != 0)
temp32 |= 1;
}
Areg = TRUNC16(temp32);
break;
case OPC_LLS:
temp32 = (Qreg << 16) | Areg;
for (i = 0; i < count; i++) {
uint32 sign = temp32 & 0x80000000;
temp32 <<= 1;
if (sign)
temp32 |= 1;
}
Areg = TRUNC16(temp32);
Qreg = TRUNC16(temp32 >> 16);
break;
}
}
}
break;
}
break;
}
return SCPE_OK;
}
t_stat sim_instr(void)
{
t_stat reason = 0;
ExecutionStarted = TRUE;
while (reason == 0) {
if (sim_interval <= 0) {
if ((reason = sim_process_event()) != 0)
break;
}
if (sim_brk_summ && sim_brk_test(Preg, SWMASK('E')))
return SCPE_IBKPT;
reason = executeAnInstruction();
sim_interval--;
if (reason == SCPE_OK)
if (sim_step && (--sim_step <= 0))
reason = SCPE_STOP;
}
return reason;
}
t_stat cpu_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
const char helpString[] =
/****************************************************************************/
" The %D device is a 1714 central processing unit.\n"
"1 Hardware Description\n"
" The 1714 can access up to 64KW of memory (4KW, 8KW, 16KW, 32KW and 64KW\n"
" are supported). A 1705 multi-level interrupt system with a direct\n"
" storage access bus and 3 1706-A buffered data channels are included.\n\n"
" The amount of memory available to the system can be changed with:\n\n"
"+sim> SET CPU nK\n\n"
" The original 1700 series CPU (the 1704) only allowed up to 32KW to be\n"
" attached to the CPU and indirect memory references would continue to\n"
" loop through memory if bit 15 of the target address was set. When 64KW\n"
" support was added indirect addressing was limited to a single level\n"
" so that the entire 16-bits of address could be used. The indirect\n"
" addressing mode may be changed by:\n\n"
"+sim> SET CPU MODE32K\n"
"+sim> SET CPU MODE65K\n\n"
" In 32KW addressing mode, the number of indirect address chaining\n"
" operations is limited to 10000 to avoid infinite loops.\n"
"2 Equipment Address\n"
" The CPU is not directly accessible via an equipment address but it does\n"
" reserve interrupt 0 (and therefore equipment address 0) for parity\n"
" errors (never detected by the simulator), protect faults and power fail\n"
" (not supported by the simulator).\n"
"2 $Registers\n"
"2 Front Panel Switches\n"
" The 1714 front panel includes a number of switches which control the\n"
" operation of the CPU. Note that selective stop and selective skip are\n"
" used extensively to control execution of the System Maintenance\n"
" Monitor.\n"
"3 Selective Stop\n"
" The selective stop switch controls how the 'Selective Stop' (SLS)\n"
" instruction executes. If the switch is off, SLS executes as a\n"
" no-operation. If the switch is on, SLS executes as a halt instruction.\n"
" Continuing after the halt causes the CPU to resume execution at the\n"
" instruction following the SLS.\n\n"
"+sim> SET CPU SSTOP\n"
"+sim> SET CPU NOSSTOP\n\n"
"3 Selective Skip\n"
" The selective skip switch controls how the SWS and SWN skip\n"
" instructions execute. SWS will skip if the switch is set and SWN will\n"
" skip if the switch is not set.\n\n"
"+sim> SET CPU SSKIP\n"
"+sim> SET CPU NOSSKIP\n\n"
"3 Protect\n"
" Each word of memory on the CDC 1700 series consists of 18-bits; 16-bits\n"
" of data/instruction, a parity bit (which is not implemented in the\n"
" simulator) and a program bit. If the protect switch is off, any program\n"
" may reference any word of memory. If the protect switch is on, there are\n"
" a set of rules which control how memory accesses work and when to\n"
" generate a program protect violation - see one of the 1700 reference\n"
" manuals on bitsavers.org for exact details. This means that the\n"
" operating system can be protected from modification by application\n"
" programs but there is no isolation between application programs.\n\n"
"+sim> SET CPU PROTECT\n"
"+sim> SET CPU NOPROTECT\n\n"
" The Simulator fully implements CPU protect mode allowing protected\n"
" operating systems such as MSOS 5 to execute. It does not implement\n"
" peripheral protect operation which allows unprotected applications to\n"
" directly access some unprotected peripherals.\n\n"
" Operating systems and other programs which run with the protect switch\n"
" on usually start up with the protect switch off, manipulate the\n"
" protect bits in memory (using the CPB/SPB instructions) and then ask\n"
" the operator to set the protect switch on.\n"
"1 Configuration\n"
" The CPU is configured with various simh SET commands.\n"
"2 $Set commands\n";
return scp_help(st, dptr, uptr, flag, helpString, cptr);
}