| /* | |
| 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. There is no documention on relative timing. For example, the paper tape | |
| * punch diagnostic enables Alarm+Data interrupts and assumes that it will | |
| * be able to execute some number of instructions before the interrupt | |
| * occurs. How many instructions should we delay if interrupts are enabled | |
| * and all conditions are met to deliver the interrupt immediately? | |
| * | |
| * 3. Some peripherals, notably the teletypewriter, do not have a protected | |
| * status bit. Does this mean that any application can directly affect | |
| * them? | |
| * | |
| * - The teletypewriter may be addressed by either a protected or a | |
| * nonprotected instruction (see SC17 Reference Manual). | |
| * | |
| * 4. The 1740/1742 line printer controllers are incorrectly documented as | |
| * having the status register at offset 3, it is at offset 1 like all | |
| * other peripherals. | |
| * | |
| * 5. For the 1738 disk pack controller, what is the correct response if an | |
| * operation is initiated with no drive selected? For now, we'll reject | |
| * the request. | |
| * | |
| * 6. For the 1706-A buffered data channel, what interrupt is used to signal | |
| * "End of Operation"? A channel-specific interrupt or a pass-through | |
| * interrupt from the device being controlled or some other? | |
| * | |
| * 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; | |
| 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, | |
| 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; | |
| 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; | |
| } | |
| /*** TODO: Enhanced skip instructions ***/ | |
| 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) { | |
| /*** TODO: Enhanced instructions ***/ | |
| goto done; | |
| } | |
| 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) { | |
| /*** TODO: Enhanced miscellaneous instructions ***/ | |
| } | |
| 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); | |
| } |