/* | |
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 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; | |
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); | |
} |