1886 lines
60 KiB
C
1886 lines
60 KiB
C
/*
|
|
|
|
Copyright (c) 2015-2017, 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. Interrupts - undocumented feature
|
|
*
|
|
* The 1704 and 1784 processor doucmentation has a section describing
|
|
* interrupt handling. There is a sub-section titled "Sharing subroutines
|
|
* between interrupt levels" which indicates that a subroutine such as:
|
|
*
|
|
* SUBR ADC 0
|
|
* IIN
|
|
* <code>
|
|
* EIN
|
|
* JMP* (SUBR)
|
|
*
|
|
* may be shared between interrupt levels. It include the text "Interrupts
|
|
* occuring after the execution of the RTJ are blocked because the IIN is
|
|
* executed. These interrupts are not recognized until after the jump is
|
|
* executed, because one instruction must be executed after an EIN before
|
|
* the interrupt system is active".
|
|
*
|
|
* The implication of this is that interrupts must be deferred for one
|
|
* instruction following an RTJ. And indeed, deferring interrupts after
|
|
* an RTJ fixed a crash I was seeing on a customized version of MSOS 5.0.
|
|
*
|
|
* 3. 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?
|
|
*
|
|
* 4. 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).
|
|
*
|
|
* 5. 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.
|
|
*
|
|
* 6. 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.
|
|
*
|
|
* 7. 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?
|
|
*
|
|
* Instruction set evolution
|
|
*
|
|
* Over time the instruction set for the CDC 1700/Cyber 18 was extended in,
|
|
* sometime incompatible, ways. The emulator will attempt to implement the
|
|
* various discreet instructions sets that were available within the
|
|
* constraints of the emulator environment:
|
|
*
|
|
* 1. Original
|
|
*
|
|
* This was the original instruction set defined when the 1700 series
|
|
* was first released. The instruction set encoding wasted a number of
|
|
* bits (e.g. IIN, EIN, SPB and CPB each had 8 unused bits which were
|
|
* ignored during execution).
|
|
*
|
|
* Character addressing was an optional extension to the 1774 (and maybe
|
|
* the 1714) which was enabled/disabled by new instructions which made
|
|
* use of the used bits of the IIN instruction. Note that the encoding
|
|
* of these instructions is incompatible with the enhanced instruction
|
|
* set (see below).
|
|
*
|
|
* 2. Basic
|
|
*
|
|
* The basic instruction set is identical to the original instruction set
|
|
* but limits the encoding of the unused bits in some instructions. For
|
|
* example, IIN will only execute the IIN functionality if the low-order
|
|
* 8 bits are encoded as zero, any other value will cause the instruction
|
|
* to execute as a NOP.
|
|
*
|
|
* 3. Enhanced (Unimplemented)
|
|
*
|
|
* The enhanced instruction set makes use of the unused bits of the basic
|
|
* instruction set to add new functionality:
|
|
*
|
|
* - Additional 4 registers
|
|
* - Character addressing mode
|
|
* - Field references
|
|
* - Multi-register save/restore
|
|
* - etc
|
|
*
|
|
*/
|
|
|
|
#include "cdc1700_defs.h"
|
|
|
|
uint16 M[MAXMEMSIZE];
|
|
uint8 P[MAXMEMSIZE];
|
|
|
|
t_uint64 Instructions;
|
|
uint16 Preg, Areg, Qreg, Mreg, CAenable, OrigPreg, Pending, IOAreg, IOQreg;
|
|
uint16 R1reg, R2reg, R3reg, R4reg;
|
|
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;
|
|
|
|
/*
|
|
* Memory location holding MSOS5 system request routine address
|
|
*/
|
|
#define NMON 0x00F4
|
|
|
|
extern void MSOS5request(uint16, uint16);
|
|
|
|
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 VMinit(void);
|
|
extern void rebuildPending(void);
|
|
|
|
extern void dev1Interrupts(char *);
|
|
|
|
t_stat cpu_set_instr(UNIT *, int32, CONST char *, void *);
|
|
t_stat cpu_show_instr(FILE *, UNIT *, int32, CONST void *);
|
|
|
|
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) /* Protect mode */
|
|
#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,
|
|
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 },
|
|
{ MTAB_XTD|MTAB_VDV, 0, "INSTR", "INSTR={ORIGINAL|BASIC|ENHANCED}",
|
|
&cpu_set_instr, &cpu_show_instr, NULL, "Set CPU instruction set" },
|
|
{ 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, NULL, "CHAR",
|
|
NULL, NULL, NULL, "Enable Character Addressing Extensions" },
|
|
{ UNIT_CHAR, 0, NULL, "NOCHAR",
|
|
NULL, NULL, NULL, "Disable Character Addressing Extensions" },
|
|
{ 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" },
|
|
{ "ENHANCED", DBG_ENH, "Display enh. instructions in basic mode" },
|
|
{ "MSOS5", DBG_MSOS5, "Display MSOS5 requests" },
|
|
{ "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
|
|
};
|
|
|
|
t_stat cpu_set_instr(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
|
|
{
|
|
if (!cptr)
|
|
return SCPE_IERR;
|
|
|
|
if (!strcmp(cptr, "ORIGINAL")) {
|
|
INSTR_SET = INSTR_ORIGINAL;
|
|
} else if (!strcmp(cptr, "BASIC")) {
|
|
INSTR_SET = INSTR_BASIC;
|
|
} else if (!strcmp(cptr, "ENHANCED")) {
|
|
INSTR_SET = INSTR_ENHANCED;
|
|
} else return SCPE_ARG;
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat cpu_show_instr(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
|
|
{
|
|
switch (INSTR_SET) {
|
|
case INSTR_ORIGINAL:
|
|
fprintf(st, "\n\tOriginal instruction set");
|
|
if ((cpu_unit.flags & UNIT_CHAR) != 0)
|
|
fprintf(st, " + character addressing");
|
|
break;
|
|
|
|
case INSTR_BASIC:
|
|
fprintf(st, "\n\tBasic instruction set");
|
|
break;
|
|
|
|
case INSTR_ENHANCED:
|
|
fprintf(st, "\n\tEnhanced instruction set (Unimplemented)");
|
|
break;
|
|
|
|
default:
|
|
return SCPE_IERR;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
VMinit();
|
|
|
|
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 (inProtectedMode()) {
|
|
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 (inProtectedMode()) {
|
|
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++;
|
|
|
|
/*
|
|
* Handle divide by 0 (plus or minus) as documented in the 1784 reference
|
|
* manual.
|
|
*/
|
|
if (divisor == 0) {
|
|
Oflag = 1;
|
|
Qreg = Areg;
|
|
Areg = (sign & 1) != 0 ? 0 : ~0;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Special case check for zero dividend.
|
|
*/
|
|
if (remainder == 0) {
|
|
Areg = Qreg = 0;
|
|
|
|
if ((sign & 1) != 0)
|
|
Areg = ~Areg;
|
|
if (rsign)
|
|
Qreg = ~Qreg;
|
|
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 ((sign & 1) != 0)
|
|
result = ~result;
|
|
|
|
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 (inProtectedMode()) {
|
|
if (!lastP && Protected) {
|
|
if ((cpu_dev.dctrl & DBG_PROTECT) != 0) {
|
|
fprintf(DBGOUT,
|
|
"%sProtect fault, protected after unprotected at %04X\r\n",
|
|
INTprefix, OrigPreg);
|
|
}
|
|
Pfault = TRUE;
|
|
RaiseInternalInterrupt();
|
|
|
|
/*
|
|
* The exact semantics of a protected fault are not documented in any
|
|
* the hardware references. The code in this simulator was created
|
|
* by examining the source code of MSOS 5.0. In the case of a 2 word
|
|
* instruction causing the trap, P is left pointing at the second word
|
|
* of the instruction. Note that the SMM diagnostics do not check for
|
|
* this case.
|
|
*/
|
|
|
|
/*
|
|
* Execute this instruction as an unprotected Selective Stop. If a
|
|
* stop occurs, P may not point to a valid instruction (see above).
|
|
* A subsequent "continue" command will cause a trap to the protect
|
|
* fault processor.
|
|
*/
|
|
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;
|
|
DEFERflag = 1;
|
|
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:
|
|
switch (INSTR_SET) {
|
|
case INSTR_BASIC:
|
|
if ((instr & OPC_MODMASK) != 0) {
|
|
if ((cpu_dev.dctrl & DBG_ENH) != 0)
|
|
fprintf(DBGOUT, "%s Possible Enh. Instruction (%04X) at %04x\r\n",
|
|
INTprefix, instr, OrigPreg);
|
|
}
|
|
/* FALLTHROUGH */
|
|
|
|
case INSTR_ORIGINAL:
|
|
if ((cpu_unit.flags & UNIT_STOPSW) != 0) {
|
|
dumpRegisters();
|
|
return SCPE_SSTOP;
|
|
}
|
|
break;
|
|
|
|
case INSTR_ENHANCED:
|
|
if ((instr & OPC_MODMASK) == 0) {
|
|
if ((cpu_unit.flags & UNIT_STOPSW) != 0) {
|
|
dumpRegisters();
|
|
return SCPE_SSTOP;
|
|
}
|
|
break;
|
|
}
|
|
Preg = OrigPreg;
|
|
return SCPE_UNIMPL;
|
|
break;
|
|
}
|
|
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);
|
|
Pfault = FALSE;
|
|
rebuildPending();
|
|
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;
|
|
|
|
/*
|
|
* EIN, IIN, SPB and CPB operate differently depending on the
|
|
* currently selected instruction set.
|
|
*/
|
|
case OPC_IIN:
|
|
case OPC_EIN:
|
|
case OPC_SPB:
|
|
case OPC_CPB:
|
|
switch (INSTR_SET) {
|
|
case INSTR_ORIGINAL:
|
|
/*
|
|
* Character addressing enable/disable is only available as
|
|
* an extension to the original instruction set.
|
|
*/
|
|
if ((instr & OPC_SPECIALMASK) == OPC_IIN) {
|
|
if ((cpu_unit.flags & UNIT_CHAR) != 0) {
|
|
if ((instr & OPC_MODMASK) != 0) {
|
|
switch (instr) {
|
|
case OPC_ECA:
|
|
CAenable = 1;
|
|
break;
|
|
|
|
case OPC_DCA:
|
|
CAenable = 0;
|
|
break;
|
|
}
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case INSTR_BASIC:
|
|
if ((instr & OPC_MODMASK) != 0) {
|
|
if ((cpu_dev.dctrl & DBG_ENH) != 0)
|
|
fprintf(DBGOUT, "%s Possible Enh. Instruction (%04X) at %04x\r\n",
|
|
INTprefix, instr, OrigPreg);
|
|
}
|
|
break;
|
|
|
|
case INSTR_ENHANCED:
|
|
if ((instr & OPC_MODMASK) != 0) {
|
|
Preg = OrigPreg;
|
|
return SCPE_UNIMPL;
|
|
}
|
|
break;
|
|
}
|
|
/* FALLTHROUGH */
|
|
|
|
/*
|
|
* 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".
|
|
*/
|
|
case OPC_EXI:
|
|
if (inProtectedMode()) {
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* Check for MSOS5 system requests. If we are executing the first
|
|
* instruction of the MSOS5 request processor (which is also an
|
|
* IIN instruction), dump information about the current request.
|
|
* This test will work correctly independent of whether a 1 or 2
|
|
* word RTJ is used to call the request processor.
|
|
*/
|
|
if ((cpu_dev.dctrl & DBG_MSOS5) != 0) {
|
|
if (OrigPreg == (M[NMON] + 1))
|
|
MSOS5request(M[M[NMON]], 0);
|
|
}
|
|
|
|
INTflag = 0;
|
|
done:
|
|
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 (inProtectedMode()) {
|
|
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:
|
|
switch (INSTR_SET) {
|
|
case INSTR_ORIGINAL:
|
|
break;
|
|
|
|
case INSTR_BASIC:
|
|
if ((instr & OPC_MODMASK) != 0) {
|
|
if ((cpu_dev.dctrl & DBG_ENH) != 0)
|
|
fprintf(DBGOUT, "%s Possible Enh. Instruction (%04X) at %04x\r\n",
|
|
INTprefix, instr, OrigPreg);
|
|
}
|
|
break;
|
|
|
|
case INSTR_ENHANCED:
|
|
if ((instr & OPC_MODMASK) != 0) {
|
|
Preg = OrigPreg;
|
|
return SCPE_UNIMPL;
|
|
}
|
|
break;
|
|
}
|
|
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 of\n"
|
|
"to be attached to the CPU and indirect memory references would continue\n"
|
|
"to 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. Systems which\n"
|
|
" supported 64KW of memory had a front-panel switch to allow software\n"
|
|
" to run in either mode. The indirect 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 Instruction Set\n"
|
|
" The instruction set implemented by the CDC 1700 series, and later\n"
|
|
" Cyber-18 models changed as new features were added. When originally\n"
|
|
" released, the 1704 had a number of instruction bits which were ignored\n"
|
|
" by the CPU (e.g. the IIN and EIN instructions each had 8 unused bits).\n"
|
|
" Later the instruction set was refined into Basic and Enhanced. The\n"
|
|
" Basic instruction set reserved these unsed bits (e.g. IIN and EIN\n"
|
|
" instructions were only recognised if the previously unused bits were\n"
|
|
" all set to zero). The MP17 microprocessor implementation of the\n"
|
|
" architecture made use of these newly available bits to implement\n"
|
|
" the Enhanced instruction set. The supported instruction set may be\n"
|
|
" changed by:\n\n"
|
|
"+sim> SET CPU INSTR=ORIGINAL\n"
|
|
"+sim> SET CPU INSTR=BASIC\n"
|
|
"+sim> SET CPU INSTR=ENHANCED\n\n"
|
|
" The Enhanced instruction set is not currently implemented by the\n"
|
|
" simulator. Note that disassembly will always be done with respect to\n"
|
|
" the currently selected instruction set. If the instruction set is set\n"
|
|
" to BASIC, enhanced instructions will be displayed as:\n\n"
|
|
"+ NOP [ Possible enhanced instruction\n"
|
|
"2 Character Addressing Mode\n"
|
|
" The ORIGINAL instruction set could be enhanced with character (8-bit)\n"
|
|
" addressing mode which added 2 new instructions; enable/disable\n"
|
|
" character addressing mode (ECA/DCA). These new instructions and the\n"
|
|
" ability to perform character addressing may be controlled by:\n\n"
|
|
"+sim> SET CPU CHAR\n"
|
|
"+sim> SET CPU NOCHAR\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);
|
|
}
|