| /* hp2100_cpu.c: HP 21xx/1000 Central Processing Unit/MEM/MP/DCPC simulator | |
| Copyright (c) 1993-2016, Robert M. Supnik | |
| Copyright (c) 2017-2018, J. David Bryan | |
| 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 THE | |
| AUTHORS 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 names of the authors shall not be | |
| used in advertising or otherwise to promote the sale, use or other dealings | |
| in this Software without prior written authorization from the authors. | |
| CPU 2114C/2115A/2116C/2100A/1000-M/E/F Central Processing Unit | |
| 12731A Memory Expansion Module | |
| DMA1,DMA2 12607B/12578A/12895A Direct Memory Access | |
| DCPC1,DCPC2 12897B Dual Channel Port Controller | |
| MP 12581A/12892B Memory Protect | |
| 21-May-18 JDB Changed "access" to "mem_access" to avoid clashing | |
| 07-May-18 JDB Modified "io_dispatch" to display outbound signals | |
| 01-May-18 JDB Multiple consecutive CLC 0 operations are now omitted | |
| 02-Apr-18 JDB SET CPU 21MX now configures an M-Series model | |
| 22-Feb-18 JDB Reworked "cpu_ibl" into "cpu_copy_loader" | |
| 11-Aug-17 JDB MEM must be disabled when DMS is disabled | |
| 01-Aug-17 JDB Changed SET/SHOW CPU [NO]IDLE to use sim_*_idle routines | |
| 22-Jul-17 JDB Renamed "intaddr" to CIR; added IR | |
| 18-Jul-17 JDB Added CPU stops | |
| 11-Jul-17 JDB Moved "hp_enbdis_pair" to hp2100_sys.c | |
| Renamed "ibl_copy" to "cpu_ibl" | |
| 10-Jul-17 JDB Renamed the global routine "iogrp" to "cpu_iog" | |
| 07-Jul-17 JDB Changed "iotrap" from uint32 to t_bool | |
| 26-Jun-17 JDB Moved I/O instruction subopcode constants from hp2100_defs.h | |
| 16-May-17 JDB Changed REG_A, REG_B to REG_X | |
| 19-Apr-17 JDB SET CPU IDLE now omits idle loop tracing | |
| 04-Apr-17 JDB Added "cpu_configuration" for symbolic ex/dep validation | |
| Rejected model change no longer changes options | |
| 21-Mar-17 JDB IOP is now illegal on the 1000 F-Series | |
| 27-Feb-17 JDB Added BBL load for 21xx machines | |
| ibl_copy no longer returns a status code | |
| 22-Feb-17 JDB Added DMA tracing | |
| 21-Feb-17 JDB Added bus tracing to the I/O dispatcher | |
| 19-Jan-17 JDB Added CPU tracing | |
| Consolidated the memory read and write routines | |
| 05-Aug-16 JDB Renamed the P register from "PC" to "PR" | |
| 13-May-16 JDB Modified for revised SCP API function parameter types | |
| 31-Dec-14 JDB Corrected devdisp data parameters | |
| 30-Dec-14 JDB Added S-register parameters to ibl_copy | |
| 24-Dec-14 JDB Added casts for explicit downward conversions | |
| 18-Mar-13 JDB Removed redundant extern declarations | |
| 05-Feb-13 JDB HLT instruction handler now relies on sim_vm_fprint_stopped | |
| 09-May-12 JDB Separated assignments from conditional expressions | |
| 13-Jan-12 JDB Minor speedup in "is_mapped" | |
| Added casts to cpu_mod, dmasio, dmapio, cpu_reset, dma_reset | |
| 07-Apr-11 JDB Fixed I/O return status bug for DMA cycles | |
| Failed I/O cycles now stop on failing instruction | |
| 28-Mar-11 JDB Tidied up signal handling | |
| 29-Oct-10 JDB Revised DMA for new multi-card paradigm | |
| Consolidated DMA reset routines | |
| DMA channels renamed from 0,1 to 1,2 to match documentation | |
| 27-Oct-10 JDB Changed I/O instructions, handlers, and DMA for revised signal model | |
| Changed I/O dispatch table to use DIB pointers | |
| 19-Oct-10 JDB Removed DMA latency counter | |
| 13-Oct-10 JDB Fixed DMA requests to enable stealing every cycle | |
| Fixed DMA priority for channel 1 over channel 2 | |
| Corrected comments for "cpu_set_idle" | |
| 30-Sep-08 JDB Breakpoints on interrupt trap cells now work | |
| 05-Sep-08 JDB VIS and IOP are now mutually exclusive on 1000-F | |
| 11-Aug-08 JDB Removed A/B shadow register variables | |
| 07-Aug-08 JDB Moved hp_setdev, hp_showdev to hp2100_sys.c | |
| Moved non-existent memory checks to WritePW | |
| 05-Aug-08 JDB Fixed mp_dms_jmp to accept lower bound, check write protection | |
| 30-Jul-08 JDB Corrected DMS violation register set conditions | |
| Refefined ABORT to pass address, moved def to hp2100_cpu.h | |
| Combined dms and dms_io routines | |
| 29-Jul-08 JDB JSB to 0/1 with W5 out and fence = 0 erroneously causes MP abort | |
| 11-Jul-08 JDB Unified I/O slot dispatch by adding DIBs for CPU, MP, and DMA | |
| 26-Jun-08 JDB Rewrote device I/O to model backplane signals | |
| EDT no longer passes DMA channel | |
| 30-Apr-08 JDB Enabled SIGNAL instructions, SIG debug flag | |
| 28-Apr-08 JDB Added SET CPU IDLE/NOIDLE, idle detection for DOS/RTE | |
| 24-Apr-08 JDB Fixed single stepping through interrupts | |
| 20-Apr-08 JDB Enabled EMA and VIS, added EMA, VIS, and SIGNAL debug flags | |
| 03-Dec-07 JDB Memory ex/dep and bkpt type default to current map mode | |
| 26-Nov-07 JDB Added SET CPU DEBUG and OS/VMA flags, enabled OS/VMA | |
| 15-Nov-07 JDB Corrected MP W5 (JSB) jumper action, SET/SHOW reversal, | |
| mp_mevff clear on interrupt with I/O instruction in trap cell | |
| 04-Nov-07 JDB Removed DBI support from 1000-M (was temporary for RTE-6/VM) | |
| 28-Apr-07 RMS Removed clock initialization | |
| 02-Mar-07 JDB EDT passes input flag and DMA channel in dat parameter | |
| 11-Jan-07 JDB Added 12578A DMA byte packing | |
| 28-Dec-06 JDB CLC 0 now sends CRS instead of CLC to devices | |
| 26-Dec-06 JDB Fixed improper IRQ deferral for 21xx CPUs | |
| Fixed improper interrupt servicing in resolve | |
| 21-Dec-06 JDB Added 21xx loader enable/disable support | |
| 16-Dec-06 JDB Added 2114 and 2115 CPU options. | |
| Added support for 12607B (2114) and 12578A (2115/6) DMA | |
| 01-Dec-06 JDB Added 1000-F CPU option (requires HAVE_INT64) | |
| SHOW CPU displays 1000-M/E instead of 21MX-M/E | |
| 16-Oct-06 JDB Moved ReadF to hp2100_cpu1.c | |
| 12-Oct-06 JDB Fixed INDMAX off-by-one error in resolve | |
| 26-Sep-06 JDB Added iotrap parameter to UIG dispatchers for RTE microcode | |
| 12-Sep-06 JDB iogrp returns NOTE_IOG to recalc interrupts | |
| resolve returns NOTE_INDINT to service held-off interrupt | |
| 16-Aug-06 JDB Added support for future microcode options, future F-Series | |
| 09-Aug-06 JDB Added double integer microcode, 1000-M/E synonyms | |
| Enhanced CPU option validity checking | |
| Added DCPC as a synonym for DMA for 21MX simulations | |
| 26-Dec-05 JDB Improved reporting in dev_conflict | |
| 22-Sep-05 RMS Fixed declarations (from Sterling Garwood) | |
| 21-Jan-05 JDB Reorganized CPU option flags | |
| 15-Jan-05 RMS Split out EAU and MAC instructions | |
| 26-Dec-04 RMS DMA reset doesn't clear alternate CTL flop (from Dave Bryan) | |
| DMA reset shouldn't clear control words (from Dave Bryan) | |
| Alternate CTL flop not visible as register (from Dave Bryan) | |
| Fixed CBS, SBS, TBS to perform virtual reads | |
| Separated A/B from M[0/1] for DMA IO (from Dave Bryan) | |
| Fixed bug in JPY (from Dave Bryan) | |
| 25-Dec-04 JDB Added SET CPU 21MX-M, 21MX-E (21MX defaults to MX-E) | |
| TIMER/EXECUTE/DIAG instructions disabled for 21MX-M | |
| T-register reflects changes in M-register when halted | |
| 25-Sep-04 JDB Moved MP into its own device; added MP option jumpers | |
| Modified DMA to allow disabling | |
| Modified SET CPU 2100/2116 to truncate memory > 32K | |
| Added -F switch to SET CPU to force memory truncation | |
| Fixed S-register behavior on 2116 | |
| Fixed LIx/MIx behavior for DMA on 2116 and 2100 | |
| Fixed LIx/MIx behavior for empty I/O card slots | |
| Modified WRU to be REG_HRO | |
| Added BRK and DEL to save console settings | |
| Fixed use of "unsigned int16" in cpu_reset | |
| Modified memory size routine to return SCPE_INCOMP if | |
| memory size truncation declined | |
| 20-Jul-04 RMS Fixed bug in breakpoint test (reported by Dave Bryan) | |
| Back up PC on instruction errors (from Dave Bryan) | |
| 14-May-04 RMS Fixed bugs and added features from Dave Bryan | |
| - SBT increments B after store | |
| - DMS console map must check dms_enb | |
| - SFS x,C and SFC x,C work | |
| - MP violation clears automatically on interrupt | |
| - SFS/SFC 5 is not gated by protection enabled | |
| - DMS enable does not disable mem prot checks | |
| - DMS status inconsistent at simulator halt | |
| - Examine/deposit are checking wrong addresses | |
| - Physical addresses are 20b not 15b | |
| - Revised DMS to use memory rather than internal format | |
| - Added instruction printout to HALT message | |
| - Added M and T internal registers | |
| - Added N, S, and U breakpoints | |
| Revised IBL facility to conform to microcode | |
| Added DMA EDT I/O pseudo-opcode | |
| Separated DMA SRQ (service request) from FLG | |
| 12-Mar-03 RMS Added logical name support | |
| 02-Feb-03 RMS Fixed last cycle bug in DMA output (found by Mike Gemeny) | |
| 22-Nov-02 RMS Added 21MX IOP support | |
| 24-Oct-02 RMS Fixed bugs in IOP and extended instructions | |
| Fixed bugs in memory protection and DMS | |
| Added clock calibration | |
| 25-Sep-02 RMS Fixed bug in DMS decode (found by Robert Alan Byer) | |
| 26-Jul-02 RMS Restructured extended instructions, added IOP support | |
| 22-Mar-02 RMS Changed to allocate memory array dynamically | |
| 11-Mar-02 RMS Cleaned up setjmp/auto variable interaction | |
| 17-Feb-02 RMS Added DMS support | |
| Fixed bugs in extended instructions | |
| 03-Feb-02 RMS Added terminal multiplexor support | |
| Changed PCQ macro to use unmodified PC | |
| Fixed flop restore logic (found by Bill McDermith) | |
| Fixed SZx,SLx,RSS bug (found by Bill McDermith) | |
| Added floating point support | |
| 16-Jan-02 RMS Added additional device support | |
| 07-Jan-02 RMS Fixed DMA register tables (found by Bill McDermith) | |
| 07-Dec-01 RMS Revised to use breakpoint package | |
| 03-Dec-01 RMS Added extended SET/SHOW support | |
| 10-Aug-01 RMS Removed register in declarations | |
| 26-Nov-00 RMS Fixed bug in dual device number routine | |
| 21-Nov-00 RMS Fixed bug in reset routine | |
| 15-Oct-00 RMS Added dynamic device number support | |
| References: | |
| - 2100A Computer Reference Manual | |
| (02100-90001, Dec-1971) | |
| - Model 2100A Computer Installation and Maintenance Manual | |
| (02100-90002, Aug-1972) | |
| - HP 1000 M/E/F-Series Computers Technical Reference Handbook | |
| (5955-0282, Mar-1980) | |
| - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation | |
| (92851-90001, Mar-1981) | |
| - HP 1000 M/E/F-Series Computers I/O Interfacing Guide | |
| (02109-90006, Sep-1980) | |
| - 12607A Direct Memory Access Operating and Service Manual | |
| (12607-90002, Jan-1970) | |
| - 12578A/12578A-01 Direct Memory Access Operating and Service Manual | |
| (12578-9001, Mar-1972) | |
| - 12892B Memory Protect Installation Manual | |
| (12892-90007, Jun-1978) | |
| - HP 1000 Computer Real-Time Systems | |
| (5091-4479, August 1992) | |
| Hewlett-Packard sold the HP 21xx/1000 family of real-time computers from 1966 | |
| through 2000. There are three major divisions within this family: the 21xx | |
| core-memory machines, the 1000 (originally 21MX) M/E/F-Series semiconductor- | |
| memory machines, and the 1000 L/A-Series machines. All machines are 16-bit | |
| accumulator-oriented CISC machines running the same base instruction set. A | |
| wide range of operating systems run on these machines, from a simple 4K word | |
| paper-tape-based monitor to a megaword multi-user, multiprogramming disc- | |
| based system and a multi-user time-shared BASIC system. | |
| This implementation is a simulator for the 2114, 2115, 2116, 2100, and 1000 | |
| M/E/F-Series machines. A large variety of CPU options, device interface | |
| cards, and peripherals are provided. High-speed I/O transfers are performed | |
| by Direct Memory Access and Dual-Channel Port Controller options. This | |
| simulator does not model the 1000 L/A-Series machines. | |
| All of the machines support a 15-bit logical address space, addressing a | |
| maximum of 32 K words, divided into 1K-word pages. Memory-referencing | |
| instructions in the base set can directly address the 1024 words of the base | |
| page (page 0) or the 1024 words of the current page (the page containing the | |
| instruction). The instructions in the extended set directly address the | |
| 32768 words in the full logical address space. The A and B accumulators may | |
| be addressed as logical addresses 0 and 1, respectively. | |
| Peripheral devices are connected to the CPU by interface cards installed in | |
| the I/O card cages present in the CPU and optional I/O extender chassis. Each | |
| slot in the card cage is assigned an address, called a select code, that may | |
| be referenced by I/O instructions in the base set. Select codes range from 0 | |
| to 77 octal, with the first eight select codes reserved for the system, | |
| providing connections for 56 possible interfaces. | |
| The 211x machines use a hardwired processor providing 70 basic instructions | |
| and up to 32K of core memory. The base instruction set is divided into the | |
| Memory Reference Group, the Shift-Rotate Group, the Alter-Skip Group, and the | |
| I/O Group. SRG instruction words may contain from one to four suboperation | |
| codes that are executed from left-to-right, and ASG instruction words may | |
| contain from one to eight suboperations. An optional Extended Arithmetic | |
| Unit may be added to the 2115 and 2116 that provides hardware multiply and | |
| divide, double-load and -store, and double-word shift and rotate | |
| instructions. | |
| The 2100 machine uses a microprogrammed processor that provides the 80 | |
| instructions of the base set and the EAU as standard equipment. Optional | |
| floating-point microcode adds six two-word single-precision instructions. | |
| User microprogramming is also supported. When used as part of an HP 2000 | |
| Time-Shared BASIC system, the CPU designated as the I/O processor may be | |
| equipped with microcode implementing 18 additional OS accelerator | |
| instructions. | |
| The 1000 M/E-Series machines also use microprogrammed processors and extend | |
| the 2100 instruction set with two new index registers, X and Y, and a new | |
| Extended Instruction Group consisting of 32 index-register instructions and | |
| 10 word-and-byte-manipulation instructions. The six 2100 floating-point | |
| instructions are also standard. The 1000 F-Series adds a hardware | |
| floating-point processor with 18 new triple- and quad-word instructions. A | |
| number of new optional microcode extensions are available with the | |
| M/E/F-Series. | |
| 1000 CPUs offer the optional Dynamic Mapping System, which provides memory | |
| mapping on a page-by-page basis. The 5-bit page number of a logical memory | |
| address selects one of 32 ten-bit map registers containing physical page | |
| numbers. The ten-bit page number combined with the ten-bit page offset | |
| yields a 20-bit physical address capable of accessing a location in a | |
| one-megaword memory. DMS provides separate maps for system and user | |
| programs, as well as for the two DCPC channels, and includes microcode that | |
| implements the 38 Dynamic Mapping Instructions used to manipulate the mapping | |
| system. | |
| Optional memory protection is accomplished by dividing the logical address | |
| space into protected and unprotected parts. When protection is enabled, any | |
| attempt to write below the fence separating the two parts is inhibited, and | |
| an interrupt to the operating system occurs, which aborts the offending user | |
| program. If the DMS option is enabled as well, protection is enhanced by | |
| specifying read and write permissions on a page-by-page basis. | |
| A note on terminology: the 1000 series of computers was originally called the | |
| 21MX at introduction. The 21MX (occasionally, 21MXM) corresponds to the 1000 | |
| M-Series, and the 21MXE (occasionally, 21XE) corresponds to the 1000 | |
| E-Series. The model numbers were changed before the introduction of the 1000 | |
| F-Series, although some internal HP documentation refers to this machine as | |
| the 21MXF. | |
| The terms MEM (Memory Expansion Module), MEU (Memory Expansion Unit), DMI | |
| (Dynamic Mapping Instructions), and DMS (Dynamic Mapping System) are used | |
| somewhat interchangeably to refer to the logical-to-physical memory address | |
| translation option provided on the 1000-Series. DMS consists of the MEM card | |
| (12731A) and the DMI firmware (13307A). However, MEM and MEU have been used | |
| interchangeably to refer to the mapping card, as have DMI and DMS to refer to | |
| the firmware instructions. | |
| These CPU hardware registers are present in all machines: | |
| Name Width Description | |
| ---- ----- ---------------------------------------------- | |
| A 16 accumulator (addressable as memory location 0) | |
| B 16 accumulator (addressable as memory location 1) | |
| P 15 program counter | |
| S 16 switch and display register | |
| M 15 memory address register | |
| T 16 memory data register | |
| E 1 extend flag (carry out) | |
| O 1 overflow flag | |
| The 1000 Series adds these CPU hardware registers: | |
| Name Width Description | |
| ---- ----- ---------------------------------------------- | |
| X 16 index register | |
| Y 16 index register | |
| The data types supported by the base instruction set are: | |
| - 8-bit unsigned byte | |
| - 16-bit unsigned integer | |
| - 16-bit two's-complement integer | |
| - 32-bit two's-complement integer | |
| - 32-bit two's-complement floating point | |
| Multi-word values are stored in memory with the most-significant words in the | |
| lowest addresses. Bytes are stored in memory with the most-significant byte | |
| in the upper half of the 16-bit word and the least-significant byte in the | |
| lower half. | |
| The instruction set is fairly irregular -- a legacy of its original | |
| implementation in hardware in the 2116 and the accretion of microprogrammed | |
| instructions in the 2100 and 1000 CPUs. Initially, there were five base-set | |
| instruction groups: | |
| 1. Memory-Reference Group (MRG) | |
| 2. Shift-Rotate Group (SRG) | |
| 3. Alter-Skip Group (ASG) | |
| 4. I/O Group (IOG) | |
| 5. Macroinstruction Group (MAC) | |
| All of the instructions added after the 2116 are in the Macroinstruction | |
| Group. | |
| The 2116 offered two hardware options that extended the instruction set. The | |
| first is the 12579A Extended Arithmetic Unit. The second is the 2152A | |
| Floating Point Processor, which is interfaced through, and therefore | |
| requires, the EAU. The EAU adds 10 instructions including integer multiply | |
| and divide and double-word loads, stores, shifts, and rotates. The FPP adds | |
| 30 floating-point arithmetic, trigonometric, logarithmic, and exponential | |
| instructions. (The 2116 FFP is not simulated.) | |
| The base set groups are decoded from bits 15-12 and 10, as follows: | |
| 15 14-12 10 Group Address Ranges | |
| -- ----- -- ----- ------------------------------- | |
| x nnn x MRG 010000-077777 and 110000-177777 | |
| 0 000 0 SRG 000000-001777 and 004000-005777 | |
| 0 000 1 ASG 002000-003777 and 006000-007777 | |
| 1 000 1 IOG 102000-103777 and 106000-107777 | |
| 1 000 0 MAC 100000-101777 and 104000-105777 | |
| Where: | |
| x = don't care | |
| n = any combination other than all zeros | |
| The MAC group is subdivided into the Extended Arithmetic Group (EAG) and the | |
| User Instruction Group (UIG), based on bits 11, 9, and 8, as follows: | |
| 11 9 8 Group Address Range | |
| -- -- -- ----- ------------- | |
| 0 0 0 EAG 100000-100377 | |
| 0 0 1 EAG 100400-100777 | |
| 0 1 0 EAG 101000-101377 | |
| 0 1 1 UIG-1 101400-101777 | |
| 1 0 0 EAG 104000-104377 | |
| 1 0 1 EAG 104400-104777 | |
| 1 1 0 UIG-0 105000-105377 | |
| 1 1 1 UIG-1 105400-105777 | |
| All of the 2116 FPP instructions are in the UIG sets: 3 use 10144x opcodes | |
| and the rest use 1050xx and 1054xx opcodes. The 2100 decodes only UIG-0 | |
| instructions, whereas the 1000s use both UIG sets. In particular, the | |
| 105740-105777 range is used by the 1000 Extended Instruction Group (EIG), | |
| which is part of the 1000-Series base set. | |
| The 21xx and 1000 M/E/F-Series machines do not trap unimplemented | |
| instructions. In general, unimplemented EAG instructions cause erroneous | |
| execution, and unimplemented UIG instructions execute as NOP. However, there | |
| are machine-to-machine variations, and some unimplemented instructions | |
| execute as other, defined instructions. | |
| The instruction set groups are encoded as follows: | |
| 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | I | mem op | P | memory address | MRG | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | I | mem op | R | P | memory address | MRG | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| Where: | |
| I = direct/indirect (0/1) | |
| R = A/B register (0/1) | |
| P = base/current page (0/1) | |
| The "mem ops" are encoded as follows: | |
| 14-11 Mnemonic Action | |
| ----- -------- ---------------------------------------- | |
| 0010 AND A = A & M [MA] | |
| 0011 JSB M [MA] = P, P = MA + 1 | |
| 0100 XOR A = A ^ M [MA] | |
| 0101 JMP P = MA | |
| 0110 IOR A = A | M [MA] | |
| 0111 ISZ M [MA] = M [MA] + 1, skip if M [MA] == 0 | |
| 1000 ADA A = A + M [MA] | |
| 1001 ADB B = B + M [MA] | |
| 1010 CPA skip if A != M [MA] | |
| 1011 CPB skip if B != M [MA] | |
| 1100 LDA A = M [MA] | |
| 1101 LDB B = M [MA] | |
| 1110 STA M [MA] = A | |
| 1111 STB M [MA] = B | |
| Bits 15 and 10 encode the type of access, as follows: | |
| 15,10 Access Type Action | |
| ----- --------------------- -------------------------- | |
| 0,0 base page direct MA = I <9:0> | |
| 0,1 current page direct MA = P <14:0>'I <9:0> | |
| 1,0 base page indirect MA = M [I <9:0>] | |
| 1,1 current page indirect MA = M [P <14:10>'I <9:0>] | |
| 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | 0 | 0 0 0 | R | 0 | E | op 1 | C | E | S | op 2 | SRG | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| Where: | |
| R = A/B register (0/1) | |
| E = disable/enable op | |
| C = CLE | |
| S = SL* | |
| 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | 0 | 0 0 0 | R | 1 | r op | e op | E | S | L | I | Z | V | ASG | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| Where: | |
| R = A/B register (0/1) | |
| E = SEZ | |
| S = SS* | |
| L = SL* | |
| I = IN* | |
| Z = SZ* | |
| V = RSS | |
| 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | 1 | 0 0 0 | R | 1 | H | I/O op | select code | IOG | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| Where: | |
| R = A/B register (0/1) | |
| H = hold/clear flag (0/1) | |
| An I/O group instruction controls the device specified by the select code. | |
| Depending on the opcode, the instruction may set or clear the device flag, | |
| start or stop I/O, or read or write data. | |
| 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | 1 | 0 0 0 | | 0 | eau op | 0 0 0 0 0 0 | EAU | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | 1 | 0 0 0 | | 0 | eau shift/rotate op | shift count | EAU | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| MAC ops decode when bits 15-12 and 10 are 1 000 0. Bits 11 and 9-0 determine | |
| the specific EAU instruction. | |
| 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | 1 | 0 0 0 | R | 0 1 | module | operation | UIG | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| Where: | |
| R = A/B register (0/1) | |
| In simulation, I/O devices are modelled by substituting software states for | |
| I/O backplane signals. The set of signals generated by I/O instructions and | |
| DMA cycles is dispatched to the target device for action. Backplane signals | |
| are processed sequentially. For example, the "STC sc,C" instruction | |
| generates the "set control" and the "clear flag" signals that are processed | |
| in that order. | |
| CPU interrupt signals are modelled as three parallel arrays: | |
| - device request priority as bit vector dev_prl [2] [31..0] | |
| - device interrupt requests as bit vector dev_irq [2] [31..0] | |
| - device service requests as bit vector dev_srq [2] [31..0] | |
| Each array forms a 64-bit vector, with bits 0-31 of the first element | |
| corresponding to select codes 00-37 octal, and bits 0-31 of the second | |
| element corresponding to select codes 40-77 octal. | |
| The HP 21xx/1000 interrupt structure is based on the PRH, PRL, IRQ, and IAK | |
| signals. PRH indicates that no higher-priority device is interrupting. PRL | |
| indicates to lower-priority devices that a given device is not interrupting. | |
| IRQ indicates that a given device is requesting an interrupt. IAK indicates | |
| that the given device's interrupt request is being acknowledged. | |
| PRH and PRL form a hardware priority chain that extends from interface to | |
| interface on the backplane. We model just PRL, as PRH is calculated from the | |
| PRLs of higher-priority devices. | |
| Typical I/O devices have a flag, flag buffer, and control flip-flops. If a | |
| device's flag, flag buffer, and control bits are set, and the device is the | |
| highest priority on the interrupt chain, it requests an interrupt by | |
| asserting IRQ. When the interrupt is acknowledged with IAK, the flag buffer | |
| is cleared, preventing further interrupt requests from that device. The | |
| combination of flag and control set blocks interrupts from lower priority | |
| devices. | |
| Service requests are used to trigger the DMA service logic. Setting the | |
| device flag typically also sets SRQ, although SRQ may be calculated | |
| independently. | |
| The simulator provides three stop conditions related to instruction execution | |
| that may be enabled with a SET CPU STOP=<stop> command: | |
| <stop> Action | |
| ------ ------------------------------------------ | |
| UNIMPL stop on an unimplemented instruction | |
| UNDEF stop on an undefined instruction | |
| UNSC stop on an access to an unused select code | |
| IOERR stop on an unreported I/O error | |
| If an enabled stop condition is detected, execution ceases with the | |
| instruction pending, and control returns to the SCP prompt. When simulation | |
| stops, execution may be resumed in two ways. If the cause of the stop has | |
| not been remedied and the stop has not been disabled, resuming execution with | |
| CONTINUE, STEP, GO, or RUN will cause the stop to occur again. Alternately, | |
| specifying the "-B" switch with any of the preceding commands will resume | |
| execution while bypassing the stop for the current instruction. | |
| The UNIMPL option stops the simulator if execution is attempted of an | |
| instruction provided by a firmware option that is not currently installed | |
| (e.g., a DAD instruction when the double-integer firmware is not installed) | |
| or of an opcode provided by an installed option but not assigned to an | |
| instruction (e.g., opcode 105335 from the double-integer firmware). | |
| Bypassing the stop will execute the instruction as a NOP (no-operation). | |
| The UNDEF option stops the simulator if execution is attempted of an | |
| instruction containing a decoded reserved bit pattern other than that defined | |
| in the Operating and Reference manual for the CPU. For example, opcodes | |
| 101700 and 105700 are not listed as DMS instructions, but they execute as | |
| XMM instructions, rather than as NOP. The intent of this stop is to catch | |
| instructions containing reserved fields with values that change the meaning | |
| of those instructions. Bypassing the stop will decode and execute the | |
| instruction in the same manner as the selected CPU. | |
| The UNSC option stops the simulator if an I/O instruction addresses a select | |
| code that is not assigned to an enabled device (equivalent to an empty | |
| hardware I/O backplane slot). Bypassing the stop will read the floating | |
| S-bus or I/O-bus for LIA/B and MIA/B instructions or do nothing for all other | |
| instructions. | |
| The IOERR option stops the simulator if an I/O error condition exists for a | |
| device that does not report this status to the CPU. For example, the paper | |
| tape reader device (PTR) does not report "no tape loaded" status, and the | |
| processor interconnect device (IPL) does not report "cable disconnected." In | |
| both cases, I/O to the device will simply hang with no indication of the | |
| problem. Enabling the IOERR option will stop the simulator with an error | |
| indication for these devices. | |
| In addition, a simulation stop will occur if an indirect addressing chain | |
| exceeds the maximum length specified by a SET CPU INDIR=<limit> command. | |
| Memory addresses may be indirect to indicate that the values point to the | |
| target addresses rather than contain the target addresses. The target of an | |
| indirect address may itself be indirect, and the CPU follows this chain of | |
| addresses until it finds a direct address. Indirect addressing is typically | |
| only one or two levels deep, but if the chain loops back on itself (e.g., if | |
| an indirect address points at itself), then instruction execution will hang. | |
| The limit may be set to any number of levels up to 32,768. This is the | |
| absolute maximum number of levels that can be created without an infinite | |
| loop -- each location in memory points to the next one except for the last, | |
| which contains the target value. In practice, anything over a few levels | |
| likely represents a programming error. The default setting is 16 levels. | |
| In addition to the CPU, this module simulates the 12578A/12607B/12895A Direct | |
| Memory Access and 12897B Dual-Channel Port Controller devices (hereafter, | |
| "DMA"). These controllers permit the CPU to transfer data directly between | |
| an I/O device and memory on a cycle-stealing basis. Depending on the CPU, | |
| the device interface, and main memory speed, DMA is capable of transferring | |
| data blocks from 1 to 32,768 words in length at rates between 500,000 and | |
| 1,000,000 words per second. The 2114 supports a single DMA channel. All | |
| other CPUs support two DMA channels. | |
| DMA is programmed by setting three control words via two select codes: 2 and | |
| 6 for channel 1, and 3 and 7 for channel 2. During simultaneous transfers, | |
| channel 1 has priority over channel 2. Otherwise, the channels are | |
| identical. Channel programming involves setting three control words, as | |
| follows: | |
| SC 06 Control Word 1 format: | |
| 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | S | B | C | - - - - - - - | device select code | | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| Where: | |
| S = assert STC during each cycle | |
| B = enable byte packing and unpacking (12578A only) | |
| C = assert CLC at the end of the block transfer | |
| SC 02 Control Word 2/3 format: | |
| 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | D | starting memory address | word 2 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | negative word count | word 3 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| Where: | |
| D = transfer direction is out of/into memory (0/1) | |
| Control word 2 is stored if the control flip-flop of select code 2 is clear, | |
| i.e., if the OTA/B is preceded by CLC; control word 3 is stored if the | |
| flip-flop is set by a preceding STC. | |
| The 12607B supports 14-bit addresses and 13-bit word counts. The 12578A | |
| supports 15-bit addresses and 14-bit word counts. The 12895A and 12897B | |
| support 15-bit addresses and 16-bit word counts. | |
| DMA is started by setting the control flip-flop on select code 6. DMA | |
| completion is indicated when the flag flip-flop sets on select code 8, which | |
| causes an interrupt if enabled. | |
| This module also simulates the 12581A/12892B Memory Protect devices for the | |
| 2116 and 1000 M/E/F-Series, respectively, and the memory protect feature that | |
| is standard equipment for the 2100. MP is addressed via select code 5 and | |
| provides a fence register that holds the address of the start of unprotected | |
| memory and a violation register that holds the address of the instruction | |
| that has caused a memory protect interrupt, as follows: | |
| 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | 0 | starting address of unprotected memory | fence | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | 0 | violating instruction address | violation | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| After setting the fence regiater with an OTA 5 or OTB 5 instruction, MP is | |
| enabled by an STC 5. | |
| This module also simulates the 12731A Memory Expansion Module for the 1000 | |
| M/E/F-Series machines. The MEM provides mapping of the 32 1024-word logical | |
| memory pages into a one-megaword physical memory. Four separate maps are | |
| provided: system, user, DCPC port A, and DCPC port B. The MEM is controlled | |
| by the associated Dynamic Mapping System instructions and contains status and | |
| violation registers, as follows: | |
| MEM Status Register: | |
| 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | I | M | E | U | P | B | base page fence address | | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| Where: | |
| I = MEM disabled/enabled (0/1) at last interrupt | |
| M = System/user map (0/1) selected at last interrupt | |
| E = MEM disabled/enabled (0/1) currently | |
| U = System/user map (0/1) selected currently | |
| P = Protected mode disabled/enabled (0/1) currently | |
| B = Base-page portion mapped (0/1 = above/below the fence) | |
| MEM Violation Register: | |
| 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | R | W | B | P | - - - - | S | E | M | map address | | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| Where: | |
| R = Read violation | |
| W = Write violation | |
| B = Base-page violation | |
| P = Privileged instruction violation | |
| S = ME bus disabled/enabled (0/1) at violation | |
| E = MEM disabled/enabled (0/1) at violation | |
| M = System/user map (0/1) selected at violation | |
| The CPU simulator provides extensive tracing capabilities that may be enabled | |
| with the SET DEBUG <filename> and SET CPU DEBUG=<trace> commands. The trace | |
| options that may be specified are: | |
| Trace Action | |
| ----- ------------------------------------------- | |
| INSTR trace instructions executed | |
| DATA trace memory data accesses | |
| FETCH trace memory instruction fetches | |
| REG trace registers | |
| OPND trace instruction operands | |
| EXEC trace matching instruction execution states | |
| A section of an example trace is: | |
| >>CPU instr: S 0002 05735 103101 CLO | |
| >>CPU fetch: S 0002 05736 000036 instruction fetch | |
| >>CPU reg: - **** 01011 042200 A 177777, B 000000, X 177777, Y 000000, e o i | |
| >>CPU instr: S 0002 05736 000036 SLA,ELA | |
| >>CPU fetch: S 0002 05737 102101 instruction fetch | |
| >>CPU reg: - **** 01011 042200 A 177776, B 000000, X 177777, Y 000000, E o i | |
| >>CPU instr: S 0002 05737 102101 STO | |
| >>CPU fetch: S 0002 05740 002400 instruction fetch | |
| >>CPU reg: - **** 01011 042200 A 177776, B 000000, X 177777, Y 000000, E O i | |
| >>CPU instr: S 0002 05755 102100 STF 0 | |
| >>CPU fetch: S 0002 05756 102705 instruction fetch | |
| >>CPU reg: - **** 01011 042200 A 177777, B 177777, X 177777, Y 000000, E O I | |
| >>CPU instr: S 0002 05756 102705 STC 5 | |
| >>CPU fetch: S 0002 05757 105736 instruction fetch | |
| >>CPU reg: P **** 01011 042200 A 177777, B 177777, X 177777, Y 000000, E O I | |
| >>CPU instr: S 0002 05757 105736 UJP 2111 | |
| >>CPU fetch: S 0002 05760 002111 instruction fetch | |
| >>CPU fetch: U 0001 02111 026111 instruction fetch | |
| >>CPU reg: P **** 01011 042200 A 177777, B 177777, X 177777, Y 000000, E O I | |
| >>CPU instr: U 0001 02111 026111 JMP 2111 | |
| >>CPU instr: U 0001 02111 000011 interrupt | |
| >>CPU fetch: S 0000 00011 115013 instruction fetch | |
| >>CPU reg: - **** 01011 042200 A 177777, B 177777, X 177777, Y 000000, E O I | |
| >>CPU reg: - **** ***** ****** MPF 000000, MPV 002111, MES 163011, MEV 030000 | |
| >>CPU instr: S 0000 00011 115013 JSB 1013,I | |
| >>CPU data: S 0000 01013 005557 data read | |
| >>CPU data: S 0002 05557 002111 data write | |
| >>CPU fetch: S 0002 05560 103100 instruction fetch | |
| >>CPU reg: - **** 01011 042200 A 177777, B 177777, X 177777, Y 000000, E O I | |
| >>CPU instr: S 0002 05560 103100 CLF 0 | |
| >>CPU fetch: S 0002 05561 105714 instruction fetch | |
| >>CPU reg: - **** 01011 042200 A 177777, B 177777, X 177777, Y 000000, E O i | |
| >>CPU exec: ******************** | |
| >>CPU reg: P **** 01567 000000 A 100036, B 000100, X 000100, Y 074000, E o I | |
| >>CPU instr: U 0220 07063 105240 .PMAP | |
| >>CPU data: U 0000 01776 000227 unprotected read | |
| >>CPU data: U 0227 76100 000233 data read | |
| >>CPU opnd: * **** 07065 105240 return location is P+2 (no error) | |
| >>CPU fetch: U 0220 07065 127055 instruction fetch | |
| >>CPU reg: P **** 01567 000000 A 100037, B 000101, X 000100, Y 074000, e o I | |
| The INSTR option traces instruction executions and interrupts. Each | |
| instruction is printed in symbolic form before it is executed. | |
| The DATA option traces reads from and writes to memory. Each access is | |
| classified by its usage type as "data" (using the current or alternate map | |
| with access protection) or "unprotected" (using a specified map without | |
| protection). | |
| The FETCH option traces instruction fetches from memory. Reads of the | |
| additional words in a multiword instruction, such as the target address of a | |
| DLD (double load) instruction, are also classified as fetches. | |
| The REG option traces register values. Two sets of registers are printed. | |
| After executing each instruction, the working registers (A, B, E, O, S, and, | |
| for 1000 CPUs, X and Y) and the state of the interrupt system (on or off) are | |
| printed. After executing an instruction that may alter the Memory Protect or | |
| Memory Expansion Module state, the MP fence and violation registers, the MEM | |
| status and violation registers, and the current protection state are printed. | |
| The OPND option traces operand values. Some instructions that take memory | |
| and register operands that are difficult to decode from DATA or REG traces | |
| present the operand values in a higher-level format. The operand data and | |
| value presented are specific to the instruction; see the instruction executor | |
| comments for details. | |
| The EXEC option traces the execution of instructions that match | |
| user-specified criteria. When a match occurs, all CPU trace options are | |
| turned on for the duration of the execution of the matched instruction. The | |
| prior trace settings are restored when a match fails. This option allows | |
| detailed tracing of specified instructions while minimizing the log file size | |
| compared to a full instruction trace. | |
| The various trace formats are interpreted as follows: | |
| >>CPU instr: U 0045 10341 016200 LDA 11200 | |
| ~ ~~~~ ~~~~~ ~~~~~~ ~~~~~~~~~ | |
| | | | | | | |
| | | | | +-- instruction mnemonic | |
| | | | +---------- octal data (instruction opcode) | |
| | | +------------------ octal logical address (P register) | |
| | +----------------------- octal physical page number | |
| +--------------------------- memory map (S/U/- system/user/disabled) | |
| >>CPU instr: U 0045 10341 000011 interrupt | |
| ~ ~~~~ ~~~~~ ~~~~~~ ~~~~~~~~~ | |
| | | | | | | |
| | | | | +-- interrupt classification | |
| | | | +---------- octal device number (CIR register) | |
| | | +------------------ octal logical address at interrupt (P register) | |
| | +----------------------- octal physical page number at interrupt | |
| +--------------------------- memory map (S/U/- system/user/disabled) | |
| >>CPU fetch: - 0000 10341 016200 instruction fetch | |
| >>CPU data: U 0013 01200 123003 data read | |
| >>CPU data: S 0013 01200 017200 unprotected write | |
| ~ ~~~~ ~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~ | |
| | | | | | | |
| | | | | +-- memory access classification | |
| | | | +------------ octal data (memory contents) | |
| | | +-------------------- octal logical address (effective address) | |
| | +------------------------- octal physical page number | |
| +----------------------------- memory map (S/U/A/B/- system/user/port A/port B/disabled) | |
| >>CPU reg: P .... 01535 040013 A 123003, B 001340, X 000000, Y 000000, e O I | |
| ~ ~~~~ ~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| | | | | | | |
| | | | | +-- A, B, X, Y, E, O, interrupt system registers | |
| | | | | (lower/upper case = 0/1 or off/on) | |
| | | | +------------ S register | |
| | | +-------------------- MEM fence | |
| | +------------------------- | |
| +----------------------------- protection state (P/- protected/unprotected) | |
| >>CPU reg: P .... ..... ...... MPF 00000, MPV 000000, MES 000000, MEV 000000 | |
| ~ ~~~~ ~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| | | | | | | |
| | | | | +-- memory protect fence and violation registers | |
| | | | | memory expansion status and violation registers | |
| | | | +------------ | |
| | | +-------------------- | |
| | +------------------------- | |
| +----------------------------- protection state (P/- protected/unprotected) | |
| >>CPU opnd: . .... 36002 101475 return location is P+3 (error EM21) | |
| >>CPU opnd: . .... 22067 105355 entry is for a dynamic mapping violation | |
| ~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| | | | | |
| | | +-- operand-specific value | |
| | +------------ operand-specific octal data | |
| +-------------------- octal logical address (P register) | |
| Implementation notes: | |
| 1. The simulator is fast enough, compared to the run-time of the longest | |
| instructions, for interruptibility not to matter. However, the HP | |
| diagnostics explicitly test interruptibility in the EIS and DMS | |
| instructions and in long indirect address chains. Accordingly, the | |
| simulator does "just enough" to pass these tests. In particular, if an | |
| interrupt is pending but deferred at the beginning of an interruptible | |
| instruction, the interrupt is taken at the appropriate point; but there | |
| is no testing for new interrupts during execution (that is, the event | |
| timer is not called). | |
| */ | |
| #include "hp2100_defs.h" | |
| #include "hp2100_cpu.h" | |
| #include "hp2100_cpu1.h" | |
| /* CPU program constants */ | |
| /* Command line switches */ | |
| #define ALL_MAPMODES (SWMASK ('S') | SWMASK ('U') | SWMASK ('P') | SWMASK ('Q')) | |
| /* RTE base-page addresses */ | |
| static const uint32 xeqt = 0001717; /* XEQT address */ | |
| static const uint32 tbg = 0001674; /* TBG address */ | |
| /* DOS base-page addresses */ | |
| static const uint32 m64 = 0000040; /* constant -64 address */ | |
| static const uint32 p64 = 0000067; /* constant +64 address */ | |
| /* CPU global SCP data definitions */ | |
| REG *sim_PC = NULL; /* the pointer to the P register */ | |
| /* CPU global data structures */ | |
| /* CPU registers */ | |
| HP_WORD ABREG [2] = { 0, 0}; /* A and B registers */ | |
| HP_WORD PR = 0; /* P register */ | |
| HP_WORD SR = 0; /* S register */ | |
| HP_WORD MR = 0; /* M register */ | |
| HP_WORD TR = 0; /* T register */ | |
| HP_WORD XR = 0; /* X register */ | |
| HP_WORD YR = 0; /* Y register */ | |
| uint32 E = 0; /* E register */ | |
| uint32 O = 0; /* O register */ | |
| HP_WORD IR = 0; /* Instruction Register */ | |
| HP_WORD CIR = 0; /* Central Interrupt Register */ | |
| /* CPU global state */ | |
| FLIP_FLOP ion = CLEAR; /* interrupt enable */ | |
| t_bool ion_defer = FALSE; /* interrupt defer */ | |
| t_stat cpu_ss_unimpl = SCPE_OK; /* status return for unimplemented instruction execution */ | |
| t_stat cpu_ss_undef = SCPE_OK; /* status return for undefined instruction execution */ | |
| t_stat cpu_ss_unsc = SCPE_OK; /* status return for I/O to an unassigned select code */ | |
| t_stat cpu_ss_ioerr = SCPE_OK; /* status return for an unreported I/O error */ | |
| t_stat cpu_ss_inhibit = SCPE_OK; /* CPU stop inhibition mask */ | |
| UNIT *cpu_ioerr_uptr = NULL; /* pointer to a unit with an unreported I/O error */ | |
| uint16 pcq [PCQ_SIZE] = { 0 }; /* PC queue (must be 16-bits wide for REG array entry) */ | |
| uint32 pcq_p = 0; /* PC queue ptr */ | |
| REG *pcq_r = NULL; /* PC queue reg ptr */ | |
| uint32 cpu_configuration; /* the current CPU option set and model */ | |
| uint32 cpu_speed = 1; /* the CPU speed, expressed as a multiplier of a real machine */ | |
| t_bool is_1000 = FALSE; /* TRUE if the CPU is a 1000 M/E/F-Series */ | |
| uint32 dev_prl [2] = { ~0u, ~0u }; /* device priority low bit vector */ | |
| uint32 dev_irq [2] = { 0u, 0u }; /* device interrupt request bit vector */ | |
| uint32 dev_srq [2] = { 0u, 0u }; /* device service request bit vector */ | |
| /* Main memory global state */ | |
| MEMORY_WORD *M = NULL; /* pointer to allocated memory */ | |
| /* Memory Expansion Unit global state */ | |
| uint32 dms_enb = 0; /* dms enable */ | |
| uint32 dms_ump = 0; /* dms user map */ | |
| HP_WORD dms_sr = 0; /* dms status reg */ | |
| /* CPU local state */ | |
| static HP_WORD saved_MR = 0; /* M-register value between SCP commands */ | |
| static uint32 fwanxm = 0; /* first word addr of nx mem */ | |
| static uint32 jsb_plb = 2; /* protected lower bound for JSB */ | |
| static uint32 exec_mask = 0; /* the current instruction execution trace mask */ | |
| static uint32 exec_match = D16_UMAX; /* the current instruction execution trace matching value */ | |
| static uint32 indirect_limit = 16; /* the indirect chain length limit */ | |
| static uint32 last_select_code = 0; /* the last select code sent over the I/O backplane */ | |
| static uint32 tbg_select_code = 0; /* the time-base generator select code (for RTE idle check) */ | |
| static DEVICE *loader_rom [4] = { NULL }; /* the four boot loader ROM sockets in a 1000 CPU */ | |
| /* Memory Expansion Unit local state */ | |
| static HP_WORD dms_vr = 0; /* dms violation reg */ | |
| static uint16 dms_map [MAP_NUM * MAP_LNT] = { 0 }; /* dms maps (must be 16-bits wide for REG array entry) */ | |
| /* CPU local data structures */ | |
| /* Interrupt deferral table (1000 version) */ | |
| static t_bool defer_tab [] = { /* deferral table, indexed by I/O sub-opcode */ | |
| FALSE, /* soHLT */ | |
| TRUE, /* soFLG */ | |
| TRUE, /* soSFC */ | |
| TRUE, /* soSFS */ | |
| FALSE, /* soMIX */ | |
| FALSE, /* soLIX */ | |
| FALSE, /* soOTX */ | |
| TRUE /* soCTL */ | |
| }; | |
| /* CPU features table. | |
| The feature table is used to validate CPU feature changes within the subset | |
| of features supported by a given CPU. Features in the typical list are | |
| enabled when the CPU model is selected. If a feature appears in the typical | |
| list but NOT in the optional list, then it is standard equipment and cannot | |
| be disabled. If a feature appears in the optional list, then it may be | |
| enabled or disabled as desired by the user. | |
| */ | |
| struct FEATURE_TABLE { /* CPU model feature table: */ | |
| uint32 typ; /* - typical features */ | |
| uint32 opt; /* - optional features */ | |
| uint32 maxmem; /* - maximum memory */ | |
| }; | |
| static struct FEATURE_TABLE cpu_features [] = { /* features indexed by CPU model */ | |
| { UNIT_DMA | UNIT_MP, /* UNIT_2116 */ | |
| UNIT_PFAIL | UNIT_DMA | UNIT_MP | UNIT_EAU, | |
| 32 * 1024 | |
| }, | |
| { UNIT_DMA, /* UNIT_2115 */ | |
| UNIT_PFAIL | UNIT_DMA | UNIT_EAU, | |
| 8 * 1024 | |
| }, | |
| { UNIT_DMA, /* UNIT_2114 */ | |
| UNIT_PFAIL | UNIT_DMA, | |
| 16 * 1024 }, | |
| { 0, 0, 0 /* unused model */ | |
| }, | |
| { UNIT_PFAIL | UNIT_MP | UNIT_DMA | UNIT_EAU, /* UNIT_2100 */ | |
| UNIT_DMA | UNIT_FP | UNIT_IOP | UNIT_FFP, | |
| 32 * 1024 | |
| }, | |
| { 0, 0, 0 /* unused model */ | |
| }, | |
| { 0, 0, 0 /* unused model */ | |
| }, | |
| { 0, 0, 0 /* unused model */ | |
| }, | |
| { UNIT_MP | UNIT_DMA | UNIT_EAU | UNIT_FP | UNIT_DMS, /* UNIT_1000_M */ | |
| UNIT_PFAIL | UNIT_DMA | UNIT_MP | UNIT_DMS | | |
| UNIT_IOP | UNIT_FFP | UNIT_DS, | |
| 1024 * 1024 | |
| }, | |
| { UNIT_MP | UNIT_DMA | UNIT_EAU | UNIT_FP | UNIT_DMS, /* UNIT_1000_E */ | |
| UNIT_PFAIL | UNIT_DMA | UNIT_MP | UNIT_DMS | | |
| UNIT_IOP | UNIT_FFP | UNIT_DBI | UNIT_DS | UNIT_EMA_VMA, | |
| 1024 * 1024 | |
| }, | |
| { UNIT_MP | UNIT_DMA | UNIT_EAU | UNIT_FP | /* UNIT_1000_F */ | |
| UNIT_FFP | UNIT_DBI | UNIT_DMS, | |
| UNIT_PFAIL | UNIT_DMA | UNIT_MP | UNIT_DMS | | |
| UNIT_VIS | UNIT_DS | UNIT_SIGNAL | UNIT_EMA_VMA, | |
| 1024 * 1024 | |
| } | |
| }; | |
| /* CPU local SCP support routine declarations */ | |
| static IOHANDLER cpuio; | |
| static IOHANDLER ovflio; | |
| static IOHANDLER pwrfio; | |
| static t_stat cpu_examine (t_value *eval, t_addr address, UNIT *uptr, int32 switches); | |
| static t_stat cpu_deposit (t_value value, t_addr address, UNIT *uptr, int32 switches); | |
| static t_stat cpu_reset (DEVICE *dptr); | |
| static t_stat cpu_boot (int32 unitno, DEVICE *dptr); | |
| static t_stat set_stops (UNIT *uptr, int32 option, CONST char *cptr, void *desc); | |
| static t_stat set_size (UNIT *uptr, int32 new_size, CONST char *cptr, void *desc); | |
| static t_stat set_model (UNIT *uptr, int32 new_model, CONST char *cptr, void *desc); | |
| static t_stat set_option (UNIT *uptr, int32 option, CONST char *cptr, void *desc); | |
| static t_stat clear_option (UNIT *uptr, int32 option, CONST char *cptr, void *desc); | |
| static t_stat set_loader (UNIT *uptr, int32 enable, CONST char *cptr, void *desc); | |
| static t_stat set_roms (UNIT *uptr, int32 option, CONST char *cptr, void *desc); | |
| static t_stat set_exec (UNIT *uptr, int32 option, CONST char *cptr, void *desc); | |
| static t_stat show_stops (FILE *st, UNIT *uptr, int32 val, CONST void *desc); | |
| static t_stat show_model (FILE *st, UNIT *uptr, int32 val, CONST void *desc); | |
| static t_stat show_roms (FILE *st, UNIT *uptr, int32 val, CONST void *desc); | |
| static t_stat show_exec (FILE *st, UNIT *uptr, int32 val, CONST void *desc); | |
| static t_stat show_speed (FILE *st, UNIT *uptr, int32 val, CONST void *desc); | |
| /* CPU local utility routine declarations */ | |
| static t_stat ea (HP_WORD IR, HP_WORD *address, uint32 irq); | |
| static HP_WORD srg_uop (HP_WORD value, HP_WORD operation); | |
| static t_stat machine_instruction (HP_WORD IR, t_bool iotrap, uint32 irq_pending, uint32 *idle_save); | |
| static t_bool check_deferral (uint32 irq_sc); | |
| static uint32 map_address (HP_WORD logical, int32 switches); | |
| static t_bool mem_is_empty (uint32 starting_address); | |
| /* Memory Expansion Unit local utility routine declarations */ | |
| static t_bool is_mapped (uint32 address); | |
| static uint32 meu_map (HP_WORD address, uint32 map, HP_WORD prot); | |
| /* CPU SCP data structures */ | |
| /* Device information blocks */ | |
| static DIB cpu_dib = { /* CPU select code 0 */ | |
| &cpuio, /* device interface */ | |
| CPU, /* select code */ | |
| 0 /* card index */ | |
| }; | |
| static DIB ovfl_dib = { /* Overflow select code 1 */ | |
| &ovflio, /* device interface */ | |
| OVF, /* select code */ | |
| 0 /* card index */ | |
| }; | |
| static DIB pwrf_dib = { /* Power Fail select code 4 */ | |
| &pwrfio, /* device interface */ | |
| PWR, /* select code */ | |
| 0 /* card index */ | |
| }; | |
| /* Unit list. | |
| The CPU unit holds the main memory capacity. | |
| Implementation notes: | |
| 1. The unit structure must be global for other modules to access the unit | |
| flags, which describe the installed options, and to obtain the memory | |
| size via the MEMSIZE macro, which references the "capac" field. | |
| */ | |
| UNIT cpu_unit = { UDATA (NULL, UNIT_FIX | UNIT_BINK, 0) }; | |
| /* Register list. | |
| The CPU register list exposes the machine registers for user inspection and | |
| modification. | |
| Implementation notes: | |
| 1. All registers that reference variables of type HP_WORD must have the | |
| REG_FIT flag for proper access if HP_WORD is a 16-bit type. | |
| 2. The REG_X flag indicates that the register may be displayed in symbolic | |
| form. | |
| */ | |
| static REG cpu_reg [] = { | |
| /* Macro Name Location Radix Width Offset Depth Flags */ | |
| /* ------ --------- ------------------ ----- ----- -------- ----------------- ----------------- */ | |
| { ORDATA (P, PR, 15) }, | |
| { ORDATA (A, AR, 16), REG_X }, | |
| { ORDATA (B, BR, 16), REG_X }, | |
| { ORDATA (M, MR, 15) }, | |
| { ORDATA (T, TR, 16), REG_RO | REG_X }, | |
| { ORDATA (X, XR, 16), REG_X }, | |
| { ORDATA (Y, YR, 16), REG_X }, | |
| { ORDATA (S, SR, 16), REG_X }, | |
| { FLDATA (E, E, 0) }, | |
| { FLDATA (O, O, 0) }, | |
| { ORDATA (CIR, CIR, 6) }, | |
| { FLDATA (ION, ion, 0) }, | |
| { FLDATA (ION_DEFER, ion_defer, 0) }, | |
| { FLDATA (DMSENB, dms_enb, 0) }, | |
| { FLDATA (DMSCUR, dms_ump, VA_N_PAG) }, | |
| { ORDATA (DMSSR, dms_sr, 16) }, | |
| { ORDATA (DMSVR, dms_vr, 16) }, | |
| { BRDATA (DMSMAP, dms_map, 8, 16, MAP_NUM * MAP_LNT) }, | |
| { ORDATA (IOPSP, iop_sp, 16) }, | |
| { BRDATA (PCQ, pcq, 8, 15, PCQ_SIZE), REG_CIRC | REG_RO }, | |
| { ORDATA (IR, IR, 16), REG_HRO }, | |
| { ORDATA (PCQP, pcq_p, 6), REG_HRO }, | |
| { ORDATA (JSBPLB, jsb_plb, 32), REG_HRO }, | |
| { ORDATA (SAVEDMR, saved_MR, 32), REG_HRO }, | |
| { ORDATA (FWANXM, fwanxm, 32), REG_HRO }, | |
| { ORDATA (CONFIG, cpu_configuration, 32), REG_HRO }, | |
| { ORDATA (WRU, sim_int_char, 8), REG_HRO }, | |
| { ORDATA (BRK, sim_brk_char, 8), REG_HRO }, | |
| { ORDATA (DEL, sim_del_char, 8), REG_HRO }, | |
| { BRDATA (PRL, dev_prl, 8, 32, 2), REG_HRO }, | |
| { BRDATA (IRQ, dev_irq, 8, 32, 2), REG_HRO }, | |
| { BRDATA (SRQ, dev_srq, 8, 32, 2), REG_HRO }, | |
| { NULL } | |
| }; | |
| /* Modifier list. | |
| Implementation notes: | |
| 1. The 21MX monikers are deprecated in favor of the 1000 designations. See | |
| the "HP 1000 Series Naming History" on the back inside cover of the | |
| Technical Reference Handbook. | |
| 2. Each CPU option requires three modifiers. The two regular modifiers | |
| control the setting and printing of the option, while the extended | |
| modifier controls clearing the option. The latter is necessary because | |
| the option must be checked before confirming the change, and so the | |
| option value must be passed to the validation routine. | |
| */ | |
| static MTAB cpu_mod [] = { | |
| /* Mask Value Match Value Print String Match String Validation Display Descriptor */ | |
| /* --------------- ----------- ------------ ------------ ------------- ----------- ----------------- */ | |
| { UNIT_MODEL_MASK, UNIT_2116, "", "2116", &set_model, &show_model, (void *) "2116" }, | |
| { UNIT_MODEL_MASK, UNIT_2115, "", "2115", &set_model, &show_model, (void *) "2115" }, | |
| { UNIT_MODEL_MASK, UNIT_2114, "", "2114", &set_model, &show_model, (void *) "2114" }, | |
| { UNIT_MODEL_MASK, UNIT_2100, "", "2100", &set_model, &show_model, (void *) "2100" }, | |
| { UNIT_MODEL_MASK, UNIT_1000_E, "", "1000-E", &set_model, &show_model, (void *) "1000-E" }, | |
| { UNIT_MODEL_MASK, UNIT_1000_M, "", "1000-M", &set_model, &show_model, (void *) "1000-M" }, | |
| #if defined (HAVE_INT64) | |
| { UNIT_MODEL_MASK, UNIT_1000_F, "", "1000-F", &set_model, &show_model, (void *) "1000-F" }, | |
| #endif | |
| { UNIT_MODEL_MASK, UNIT_1000_M, NULL, "21MX-M", &set_model, &show_model, (void *) "1000-M" }, | |
| { UNIT_MODEL_MASK, UNIT_1000_E, NULL, "21MX-E", &set_model, &show_model, (void *) "1000-E" }, | |
| { UNIT_EAU, UNIT_EAU, "EAU", "EAU", &set_option, NULL, NULL }, | |
| { UNIT_EAU, 0, "no EAU", NULL, NULL, NULL, NULL }, | |
| { MTAB_XDV, UNIT_EAU, NULL, "NOEAU", &clear_option, NULL, NULL }, | |
| { UNIT_FP, UNIT_FP, "FP", "FP", &set_option, NULL, NULL }, | |
| { UNIT_FP, 0, "no FP", NULL, NULL, NULL, NULL }, | |
| { MTAB_XDV, UNIT_FP, NULL, "NOFP", &clear_option, NULL, NULL }, | |
| { UNIT_IOP, UNIT_IOP, "IOP", "IOP", &set_option, NULL, NULL }, | |
| { UNIT_IOP, 0, "no IOP", NULL, NULL, NULL, NULL }, | |
| { MTAB_XDV, UNIT_IOP, NULL, "NOIOP", &clear_option, NULL, NULL }, | |
| { UNIT_DMS, UNIT_DMS, "DMS", "DMS", &set_option, NULL, NULL }, | |
| { UNIT_DMS, 0, "no DMS", NULL, NULL, NULL, NULL }, | |
| { MTAB_XDV, UNIT_DMS, NULL, "NODMS", &clear_option, NULL, NULL }, | |
| { UNIT_FFP, UNIT_FFP, "FFP", "FFP", &set_option, NULL, NULL }, | |
| { UNIT_FFP, 0, "no FFP", NULL, NULL, NULL, NULL }, | |
| { MTAB_XDV, UNIT_FFP, NULL, "NOFFP", &clear_option, NULL, NULL }, | |
| { UNIT_DBI, UNIT_DBI, "DBI", "DBI", &set_option, NULL, NULL }, | |
| { UNIT_DBI, 0, "no DBI", NULL, NULL, NULL, NULL }, | |
| { MTAB_XDV, UNIT_DBI, NULL, "NODBI", &clear_option, NULL, NULL }, | |
| { UNIT_EMA_VMA, UNIT_EMA, "EMA", "EMA", &set_option, NULL, NULL }, | |
| { MTAB_XDV, UNIT_EMA, NULL, "NOEMA", &clear_option, NULL, NULL }, | |
| { UNIT_EMA_VMA, UNIT_VMAOS, "VMA", "VMA", &set_option, NULL, NULL }, | |
| { MTAB_XDV, UNIT_VMAOS, NULL, "NOVMA", &clear_option, NULL, NULL }, | |
| { UNIT_EMA_VMA, 0, "no EMA/VMA", NULL, &set_option, NULL, NULL }, | |
| #if defined (HAVE_INT64) | |
| { UNIT_VIS, UNIT_VIS, "VIS", "VIS", &set_option, NULL, NULL }, | |
| { UNIT_VIS, 0, "no VIS", NULL, NULL, NULL, NULL }, | |
| { MTAB_XDV, UNIT_VIS, NULL, "NOVIS", &clear_option, NULL, NULL }, | |
| { UNIT_SIGNAL, UNIT_SIGNAL, "SIGNAL", "SIGNAL", &set_option, NULL, NULL }, | |
| { UNIT_SIGNAL, 0, "no SIGNAL", NULL, NULL, NULL, NULL }, | |
| { MTAB_XDV, UNIT_SIGNAL, NULL, "NOSIGNAL", &clear_option, NULL, NULL }, | |
| #endif | |
| /* Future microcode support. | |
| { UNIT_DS, UNIT_DS, "DS", "DS", &set_option, NULL, NULL }, | |
| { UNIT_DS, 0, "no DS", NULL, NULL, NULL, NULL }, | |
| { MTAB_XDV, UNIT_DS, NULL, "NODS", &clear_option, NULL, NULL }, | |
| */ | |
| /* Entry Flags Value Print String Match String Validation Display Descriptor */ | |
| /* ------------------- ----------- ------------ --------------- ------------- -------------- ---------- */ | |
| { MTAB_XDV, 0, "IDLE", "IDLE", &sim_set_idle, &sim_show_idle, NULL }, | |
| { MTAB_XDV, 0, NULL, "NOIDLE", &sim_clr_idle, NULL, NULL }, | |
| { MTAB_XDV, 1, NULL, "LOADERENABLE", &set_loader, NULL, NULL }, | |
| { MTAB_XDV, 0, NULL, "LOADERDISABLE", &set_loader, NULL, NULL }, | |
| { MTAB_XDV, 4 * 1024, NULL, "4K", &set_size, NULL, NULL }, | |
| { MTAB_XDV, 8 * 1024, NULL, "8K", &set_size, NULL, NULL }, | |
| { MTAB_XDV, 12 * 1024, NULL, "12K", &set_size, NULL, NULL }, | |
| { MTAB_XDV, 16 * 1024, NULL, "16K", &set_size, NULL, NULL }, | |
| { MTAB_XDV, 24 * 1024, NULL, "24K", &set_size, NULL, NULL }, | |
| { MTAB_XDV, 32 * 1024, NULL, "32K", &set_size, NULL, NULL }, | |
| { MTAB_XDV, 64 * 1024, NULL, "64K", &set_size, NULL, NULL }, | |
| { MTAB_XDV, 128 * 1024, NULL, "128K", &set_size, NULL, NULL }, | |
| { MTAB_XDV, 256 * 1024, NULL, "256K", &set_size, NULL, NULL }, | |
| { MTAB_XDV, 512 * 1024, NULL, "512K", &set_size, NULL, NULL }, | |
| { MTAB_XDV, 1024 * 1024, NULL, "1024K", &set_size, NULL, NULL }, | |
| { MTAB_XDV | MTAB_NMO, 0, "ROMS", "ROMS", &set_roms, &show_roms, NULL }, | |
| { MTAB_XDV | MTAB_NMO, 1, "STOPS", "STOP", &set_stops, &show_stops, NULL }, | |
| { MTAB_XDV, 0, NULL, "NOSTOP", &set_stops, NULL, NULL }, | |
| { MTAB_XDV | MTAB_NMO, 2, "INDIR", "INDIR", &set_stops, &show_stops, NULL }, | |
| { MTAB_XDV | MTAB_NMO, 1, "EXEC", "EXEC", &set_exec, &show_exec, NULL }, | |
| { MTAB_XDV, 0, NULL, "NOEXEC", &set_exec, NULL, NULL }, | |
| { MTAB_XDV | MTAB_NMO, 0, "SPEED", NULL, NULL, &show_speed, NULL }, | |
| { 0 } | |
| }; | |
| /* Debugging trace list */ | |
| static DEBTAB cpu_deb [] = { | |
| { "INSTR", TRACE_INSTR }, /* trace instruction executions */ | |
| { "DATA", TRACE_DATA }, /* trace memory data accesses */ | |
| { "FETCH", TRACE_FETCH }, /* trace memory instruction fetches */ | |
| { "REG", TRACE_REG }, /* trace register values */ | |
| { "OPND", TRACE_OPND }, /* trace instruction operands */ | |
| { "EXEC", TRACE_EXEC }, /* trace matching instruction execution states */ | |
| { "NOOS", DEBUG_NOOS }, /* RTE-6/VM will not use OS firmware */ | |
| { NULL, 0 } | |
| }; | |
| /* Simulation stop list. | |
| The simulator can be configured to detect certain machine instruction | |
| conditions and stop execution when one of them occurs. Stops may be enabled | |
| or disabled individually with these commands: | |
| SET CPU STOP=<option>[;<option] | |
| SET CPU NOSTOP=<option>[;<option] | |
| The CPU stop table is used to parse the commands and set the appropriate | |
| variables to enable or disable the stops. | |
| Implementation notes: | |
| 1. To avoid the testing of stop conditions at run time, they are implemented | |
| by setting individual stop status variables either to the appropriate | |
| stop code (if enabled) or to SCPE_OK (if disabled). This allows the | |
| affected routines to return the status value unconditionally and cause | |
| either a simulator stop or continued execution without a run-time test. | |
| 2. SCPE_IOERR is not actually returned for unreported I/O errors. Instead, | |
| it is simply a flag that a stop code specific to the detected error | |
| should be returned. | |
| 3. To permit stops to be bypassed for one instruction execution, routines | |
| use the STOP macro to return the value of the applicable stop variable | |
| ANDed with the complement of the value of the "cpu_ss_inhibit" variable. | |
| The latter is set in the instruction prelude to SS_INHIBIT (i.e., all | |
| ones) if a bypass is requested or to SCPE_OK (i.e., all zeros) if not, | |
| and is reset to SCPE_OK after each instruction execution. The effect is | |
| that SCPE_OK is returned instead of a simulator stop if a stop condition | |
| occurs when a bypass is specified. This action depends on the value of | |
| SCPE_OK being zero (which is guaranteed). | |
| */ | |
| typedef struct { | |
| const char *name; /* stop name */ | |
| t_stat *status; /* pointer to the stop status variable */ | |
| t_stat value; /* stop status return value */ | |
| } STOPTAB; | |
| static STOPTAB cpu_stop [] = { | |
| { "UNIMPL", &cpu_ss_unimpl, STOP_UNIMPL }, /* stop on an unimplemented instruction */ | |
| { "UNDEF", &cpu_ss_undef, STOP_UNDEF }, /* stop on an undefined instruction */ | |
| { "UNSC", &cpu_ss_unsc, STOP_UNSC }, /* stop on I/O to an unassigned select code */ | |
| { "IOERR", &cpu_ss_ioerr, SCPE_IOERR }, /* stop on an unreported I/O error */ | |
| { NULL, NULL, 0 } | |
| }; | |
| /* Device descriptor */ | |
| DEVICE cpu_dev = { | |
| "CPU", /* device name */ | |
| &cpu_unit, /* unit array */ | |
| cpu_reg, /* register array */ | |
| cpu_mod, /* modifier array */ | |
| 1, /* number of units */ | |
| 8, /* address radix */ | |
| PA_N_SIZE, /* address width */ | |
| 1, /* address increment */ | |
| 8, /* data radix */ | |
| 16, /* data width */ | |
| &cpu_examine, /* examine routine */ | |
| &cpu_deposit, /* deposit routine */ | |
| &cpu_reset, /* reset routine */ | |
| &cpu_boot, /* boot routine */ | |
| NULL, /* attach routine */ | |
| NULL, /* detach routine */ | |
| &cpu_dib, /* device information block pointer */ | |
| DEV_DEBUG, /* device flags */ | |
| 0, /* debug control flags */ | |
| cpu_deb, /* debug flag name table */ | |
| NULL, /* memory size change routine */ | |
| NULL /* logical device name */ | |
| }; | |
| /* Memory program constants */ | |
| static const char map_indicator [] = { /* MEU map indicator, indexed by map type */ | |
| 'S', /* System */ | |
| 'U', /* User */ | |
| 'A', /* Port_A */ | |
| 'B' /* Port_B */ | |
| }; | |
| /* Memory global data structures */ | |
| /* Memory access classification table */ | |
| typedef struct { | |
| uint32 debug_flag; /* the debug flag for tracing */ | |
| const char *name; /* the classification name */ | |
| } ACCESS_PROPERTIES; | |
| static const ACCESS_PROPERTIES mem_access [] = { /* indexed by ACCESS_CLASS */ | |
| /* debug_flag name */ | |
| /* ------------ ------------------- */ | |
| { TRACE_FETCH, "instruction fetch" }, /* instruction fetch */ | |
| { TRACE_DATA, "data" }, /* data access */ | |
| { TRACE_DATA, "data" }, /* data access, alternate map */ | |
| { TRACE_DATA, "unprotected" }, /* data access, system map */ | |
| { TRACE_DATA, "unprotected" }, /* data access, user map */ | |
| { TRACE_DATA, "dma" }, /* DMA channel 1, port A map */ | |
| { TRACE_DATA, "dma" } /* DMA channel 2, port B map */ | |
| }; | |
| /* DMA program constants */ | |
| #define DMA_CHAN_COUNT 2 /* number of DMA channels */ | |
| #define DMA_OE 020000000000u /* byte packing odd/even flag */ | |
| #define DMA1_STC 0100000u /* DMA - issue STC */ | |
| #define DMA1_PB 0040000u /* DMA - pack bytes */ | |
| #define DMA1_CLC 0020000u /* DMA - issue CLC */ | |
| #define DMA2_OI 0100000u /* DMA - output/input */ | |
| typedef enum { ch1, ch2 } CHANNEL; /* channel number */ | |
| #define DMA_1_REQ (1 << ch1) /* channel 1 request */ | |
| #define DMA_2_REQ (1 << ch2) /* channel 2 request */ | |
| typedef struct { | |
| FLIP_FLOP control; /* control flip-flop */ | |
| FLIP_FLOP flag; /* flag flip-flop */ | |
| FLIP_FLOP flagbuf; /* flag buffer flip-flop */ | |
| FLIP_FLOP xferen; /* transfer enable flip-flop */ | |
| FLIP_FLOP select; /* register select flip-flop */ | |
| HP_WORD cw1; /* device select */ | |
| HP_WORD cw2; /* direction, address */ | |
| HP_WORD cw3; /* word count */ | |
| uint32 packer; /* byte-packer holding reg */ | |
| } DMA_STATE; | |
| /* DMA global state */ | |
| DMA_STATE dma [DMA_CHAN_COUNT]; /* per-channel state */ | |
| /* DMA local data structures */ | |
| static const BITSET_NAME dma_cw1_names [] = { /* DMA control word 1 names */ | |
| "STC", /* bit 15 */ | |
| "byte packing", /* bit 14 */ | |
| "CLC" /* bit 13 */ | |
| }; | |
| static const BITSET_FORMAT dma_cw1_format = /* names, offset, direction, alternates, bar */ | |
| { FMT_INIT (dma_cw1_names, 13, msb_first, no_alt, append_bar) }; | |
| /* DMA local SCP support routine declarations */ | |
| static IOHANDLER dmapio; | |
| static IOHANDLER dmasio; | |
| static t_stat dma_reset (DEVICE *dptr); | |
| /* DMA local utility routine declarations */ | |
| static t_stat dma_cycle (CHANNEL chan, ACCESS_CLASS class); | |
| static uint32 calc_dma (void); | |
| /* DMA SCP data structures */ | |
| /* Device information blocks */ | |
| static DIB dmap1_dib = { | |
| &dmapio, /* device interface */ | |
| DMA1, /* select code */ | |
| ch1 /* card index */ | |
| }; | |
| static DIB dmas1_dib = { | |
| &dmasio, /* device interface */ | |
| DMALT1, /* select code */ | |
| ch1 /* card index */ | |
| }; | |
| static DIB dmap2_dib = { | |
| &dmapio, /* device interface */ | |
| DMA2, /* select code */ | |
| ch2 /* card index */ | |
| }; | |
| static DIB dmas2_dib = { | |
| &dmasio, /* device interface */ | |
| DMALT2, /* select code */ | |
| ch2 /* card index */ | |
| }; | |
| /* Unit lists */ | |
| static UNIT dma1_unit = { UDATA (NULL, 0, 0) }; | |
| static UNIT dma2_unit = { UDATA (NULL, 0, 0) }; | |
| /* Register lists */ | |
| static REG dma1_reg [] = { | |
| /* Macro Name Location Width Flags */ | |
| /* ------ ------- ------------------ ----- ----- */ | |
| { FLDATA (XFR, dma [ch1].xferen, 0) }, | |
| { FLDATA (CTL, dma [ch1].control, 0) }, | |
| { FLDATA (FLG, dma [ch1].flag, 0) }, | |
| { FLDATA (FBF, dma [ch1].flagbuf, 0) }, | |
| { FLDATA (CTL2, dma [ch1].select, 0) }, | |
| { ORDATA (CW1, dma [ch1].cw1, 16) }, | |
| { ORDATA (CW2, dma [ch1].cw2, 16) }, | |
| { ORDATA (CW3, dma [ch1].cw3, 16) }, | |
| { FLDATA (BYTE, dma [ch1].packer, 31) }, | |
| { ORDATA (PACKER, dma [ch1].packer, 8), REG_A }, | |
| { NULL } | |
| }; | |
| static REG dma2_reg [] = { | |
| /* Macro Name Location Width Flags */ | |
| /* ------ ------- ------------------ ----- ----- */ | |
| { FLDATA (XFR, dma [ch2].xferen, 0) }, | |
| { FLDATA (CTL, dma [ch2].control, 0) }, | |
| { FLDATA (FLG, dma [ch2].flag, 0) }, | |
| { FLDATA (FBF, dma [ch2].flagbuf, 0) }, | |
| { FLDATA (CTL2, dma [ch2].select, 0) }, | |
| { ORDATA (CW1, dma [ch2].cw1, 16) }, | |
| { ORDATA (CW2, dma [ch2].cw2, 16) }, | |
| { ORDATA (CW3, dma [ch2].cw3, 16) }, | |
| { FLDATA (BYTE, dma [ch2].packer, 31) }, | |
| { ORDATA (PACKER, dma [ch2].packer, 8), REG_A }, | |
| { NULL } | |
| }; | |
| /* Debugging trace list */ | |
| static DEBTAB dma_deb [] = { | |
| { "CMD", TRACE_CMD }, /* trace interface or controller commands */ | |
| { "CSRW", TRACE_CSRW }, /* trace interface control, status, read, and write actions */ | |
| { "SR", TRACE_SR }, /* trace service requests received */ | |
| { "DATA", TRACE_DATA }, /* trace memory data accesses */ | |
| { "IOBUS", TRACE_IOBUS }, /* trace I/O bus signals and data words received and returned */ | |
| { NULL, 0 } | |
| }; | |
| /* Device descriptors */ | |
| DEVICE dma1_dev = { | |
| "DMA1", /* device name */ | |
| &dma1_unit, /* unit array */ | |
| dma1_reg, /* register array */ | |
| NULL, /* modifier array */ | |
| 1, /* number of units */ | |
| 8, /* address radix */ | |
| 1, /* address width */ | |
| 1, /* address increment */ | |
| 8, /* data radix */ | |
| 16, /* data width */ | |
| NULL, /* examine routine */ | |
| NULL, /* deposit routine */ | |
| &dma_reset, /* reset routine */ | |
| NULL, /* boot routine */ | |
| NULL, /* attach routine */ | |
| NULL, /* detach routine */ | |
| &dmap1_dib, /* device information block pointer */ | |
| DEV_DISABLE | DEV_DEBUG, /* device flags */ | |
| 0, /* debug control flags */ | |
| dma_deb, /* debug flag name table */ | |
| NULL, /* memory size change routine */ | |
| NULL /* logical device name */ | |
| }; | |
| DEVICE dma2_dev = { | |
| "DMA2", /* device name */ | |
| &dma2_unit, /* unit array */ | |
| dma2_reg, /* register array */ | |
| NULL, /* modifier array */ | |
| 1, /* number of units */ | |
| 8, /* address radix */ | |
| 1, /* address width */ | |
| 1, /* address increment */ | |
| 8, /* data radix */ | |
| 16, /* data width */ | |
| NULL, /* examine routine */ | |
| NULL, /* deposit routine */ | |
| &dma_reset, /* reset routine */ | |
| NULL, /* boot routine */ | |
| NULL, /* attach routine */ | |
| NULL, /* detach routine */ | |
| &dmap2_dib, /* device information block pointer */ | |
| DEV_DISABLE | DEV_DEBUG, /* device flags */ | |
| 0, /* debug control flags */ | |
| dma_deb, /* debug flag name table */ | |
| NULL, /* memory size change routine */ | |
| NULL /* logical device name */ | |
| }; | |
| static DEVICE *dma_dptrs [] = { | |
| &dma1_dev, | |
| &dma2_dev | |
| }; | |
| /* Memory Protect program constants */ | |
| #define UNIT_V_MP_JSB (UNIT_V_UF + 0) /* MP jumper W5 */ | |
| #define UNIT_V_MP_INT (UNIT_V_UF + 1) /* MP jumper W6 */ | |
| #define UNIT_V_MP_SEL1 (UNIT_V_UF + 2) /* MP jumper W7 */ | |
| #define UNIT_MP_JSB (1 << UNIT_V_MP_JSB) /* 1 = W5 is out */ | |
| #define UNIT_MP_INT (1 << UNIT_V_MP_INT) /* 1 = W6 is out */ | |
| #define UNIT_MP_SEL1 (1 << UNIT_V_MP_SEL1) /* 1 = W7 is out */ | |
| #define MP_TEST(va) (mp_control && ((va) >= 2) && ((va) < mp_fence)) | |
| /* Memory Protect global state */ | |
| FLIP_FLOP mp_control = CLEAR; /* MP control flip-flop */ | |
| FLIP_FLOP mp_mevff = CLEAR; /* memory expansion violation flip-flop */ | |
| HP_WORD mp_fence = 0; /* MP fence register */ | |
| HP_WORD mp_viol = 0; /* MP violation register */ | |
| HP_WORD iop_sp = 0; /* iop stack reg */ | |
| HP_WORD err_PC = 0; /* error PC */ | |
| jmp_buf save_env; /* MP abort handler */ | |
| t_bool mp_mem_changed; /* TRUE if the MP or MEM registers have been altered */ | |
| /* Memory Protect local state */ | |
| static FLIP_FLOP mp_flag = CLEAR; /* MP flag flip-flop */ | |
| static FLIP_FLOP mp_flagbuf = CLEAR; /* MP flag buffer flip-flop */ | |
| static FLIP_FLOP mp_evrff = SET; /* enable violation register flip-flop */ | |
| static char meu_indicator; /* last map access indicator (S | U | A | B | -) */ | |
| static uint32 meu_page; /* last physical page number accessed */ | |
| /* Memory Protect local SCP support routine declarations */ | |
| static IOHANDLER protio; | |
| static t_stat mp_reset (DEVICE *dptr); | |
| /* Memory Protect SCP data structures */ | |
| /* Device information block */ | |
| static DIB mp_dib = { | |
| &protio, /* device interface */ | |
| PRO, /* select code */ | |
| 0 /* card index */ | |
| }; | |
| /* Unit list. | |
| Implementation notes: | |
| 1. The default flags correspond to the following jumper settings: JSB in, | |
| INT in, SEL1 out. | |
| */ | |
| static UNIT mp_unit = { UDATA (NULL, UNIT_MP_SEL1, 0) }; | |
| /* Register list */ | |
| static REG mp_reg [] = { | |
| /* Macro Name Location Width */ | |
| /* ------ ---- ----------- ----- */ | |
| { FLDATA (CTL, mp_control, 0) }, | |
| { FLDATA (FLG, mp_flag, 0) }, | |
| { FLDATA (FBF, mp_flagbuf, 0) }, | |
| { ORDATA (FR, mp_fence, 15) }, | |
| { ORDATA (VR, mp_viol, 16) }, | |
| { FLDATA (EVR, mp_evrff, 0) }, | |
| { FLDATA (MEV, mp_mevff, 0) }, | |
| { NULL } | |
| }; | |
| /* Modifier list */ | |
| static MTAB mp_mod [] = { | |
| /* Mask Value Match Value Print String Match String Validation Display Descriptor */ | |
| /* ------------- ------------ --------------- ------------ ---------- ------- ---------- */ | |
| { UNIT_MP_JSB, UNIT_MP_JSB, "JSB (W5) out", "JSBOUT", NULL, NULL, NULL }, | |
| { UNIT_MP_JSB, 0, "JSB (W5) in", "JSBIN", NULL, NULL, NULL }, | |
| { UNIT_MP_INT, UNIT_MP_INT, "INT (W6) out", "INTOUT", NULL, NULL, NULL }, | |
| { UNIT_MP_INT, 0, "INT (W6) in", "INTIN", NULL, NULL, NULL }, | |
| { UNIT_MP_SEL1, UNIT_MP_SEL1, "SEL1 (W7) out", "SEL1OUT", NULL, NULL, NULL }, | |
| { UNIT_MP_SEL1, 0, "SEL1 (W7) in", "SEL1IN", NULL, NULL, NULL }, | |
| { 0 } | |
| }; | |
| /* Device descriptor */ | |
| DEVICE mp_dev = { | |
| "MP", /* device name */ | |
| &mp_unit, /* unit array */ | |
| mp_reg, /* register array */ | |
| mp_mod, /* modifier array */ | |
| 1, /* number of units */ | |
| 8, /* address radix */ | |
| 1, /* address width */ | |
| 1, /* address increment */ | |
| 8, /* data radix */ | |
| 16, /* data width */ | |
| NULL, /* examine routine */ | |
| NULL, /* deposit routine */ | |
| &mp_reset, /* reset routine */ | |
| NULL, /* boot routine */ | |
| NULL, /* attach routine */ | |
| NULL, /* detach routine */ | |
| &mp_dib, /* device information block pointer */ | |
| DEV_DISABLE | DEV_DIS, /* device flags */ | |
| 0, /* debug control flags */ | |
| NULL, /* debug flag name table */ | |
| NULL, /* memory size change routine */ | |
| NULL /* logical device name */ | |
| }; | |
| /* I/O system program constants */ | |
| static const BITSET_NAME inbound_names [] = { /* Inbound signal names, in IOSIGNAL order */ | |
| "PON", /* 000000000001 */ | |
| "ENF", /* 000000000002 */ | |
| "IOI", /* 000000000004 */ | |
| "IOO", /* 000000000010 */ | |
| "SFS", /* 000000000020 */ | |
| "SFC", /* 000000000040 */ | |
| "STC", /* 000000000100 */ | |
| "CLC", /* 000000000200 */ | |
| "STF", /* 000000000400 */ | |
| "CLF", /* 000000001000 */ | |
| "EDT", /* 000000002000 */ | |
| "CRS", /* 000000004000 */ | |
| "POPIO", /* 000000010000 */ | |
| "IAK", /* 000000020000 */ | |
| "SIR" /* 000000040000 */ | |
| }; | |
| static const BITSET_FORMAT inbound_format = /* names, offset, direction, alternates, bar */ | |
| { FMT_INIT (inbound_names, 0, lsb_first, no_alt, no_bar) }; | |
| static const BITSET_NAME outbound_names [] = { /* Outbound signal names, in IOSIGNAL order */ | |
| "SKF" /* 000000200000 */ | |
| }; | |
| static const BITSET_FORMAT outbound_format = /* names, offset, direction, alternates, bar */ | |
| { FMT_INIT (outbound_names, 16, lsb_first, no_alt, no_bar) }; | |
| /* I/O instruction sub-opcodes */ | |
| #define soHLT 0 /* halt */ | |
| #define soFLG 1 /* set/clear flag */ | |
| #define soSFC 2 /* skip on flag clear */ | |
| #define soSFS 3 /* skip on flag set */ | |
| #define soMIX 4 /* merge into A/B */ | |
| #define soLIX 5 /* load into A/B */ | |
| #define soOTX 6 /* output from A/B */ | |
| #define soCTL 7 /* set/clear control */ | |
| /* I/O system local data structures */ | |
| static DIB *dibs [MAXDEV + 1] = { /* index by select code for I/O instruction dispatch */ | |
| &cpu_dib, /* select code 00 = interrupt system */ | |
| &ovfl_dib /* select code 01 = overflow register */ | |
| }; | |
| static DEVICE *devs [MAXDEV + 1] = { /* index by select code for I/O dispatch tracing */ | |
| &cpu_dev, /* select code 00 = interrupt system */ | |
| &cpu_dev /* select code 01 = overflow register */ | |
| }; | |
| /* I/O system local utility routine declarations */ | |
| static void io_initialize (void); | |
| static uint32 io_dispatch (uint32 select_code, IOCYCLE signal_set, HP_WORD data); | |
| /* CPU global SCP support routines */ | |
| /* Execute CPU instructions. | |
| This is the instruction decode routine for the HP 21xx/1000 simulator. It is | |
| called from the simulator control program (SCP) to execute instructions in | |
| simulated memory, starting at the simulated program counter. It runs until | |
| the status to be returned is set to a value other than SCPE_OK. | |
| On entry, P points to the instruction to execute, and the "sim_switches" | |
| global contains any command-line switches included with the run command. On | |
| exit, P points at the next instruction to execute. | |
| Execution is divided into four phases. | |
| First, the instruction prelude configures the simulation state to resume | |
| execution. This involves verifying that there are no device conflicts (e.g., | |
| two devices with the same select code) and initializing the I/O state. These | |
| actions accommodate reconfiguration of the I/O device settings and program | |
| counter while the simulator was stopped. The prelude also picks up the | |
| time-base generator's select code for use in idle testing, and it checks for | |
| one command-line switch: if "-B" is specified, the current set of simulation | |
| stop conditions is bypassed for the first instruction executed. | |
| Second, the memory protect abort mechanism is set up. MP aborts utilize the | |
| "setjmp/longjmp" mechanism to transfer control out of the instruction | |
| executors without returning through the call stack. This allows an | |
| instruction to be aborted part-way through execution when continuation is | |
| impossible due to a memory access violation. | |
| Third, the instruction execution loop decodes instructions and calls the | |
| individual executors in turn until a condition occurs that prevents further | |
| execution. Examples of such conditions include execution of a HLT | |
| instruction, a user stop request (CTRL+E) from the simulation console, a | |
| recoverable device error (such as an improperly formatted tape image), a | |
| user-specified breakpoint, and a simulation stop condition (such as execution | |
| of an unimplemented instruction). The execution loop also polls for I/O | |
| events and device interrupts, and runs DMA channel cycles. During | |
| instruction execution, the IR register contains the currently executing | |
| instruction, and the P register points to the memory location containing the | |
| next instruction. | |
| Fourth, the instruction postlude updates the simulation state in preparation | |
| for returning to the SCP command prompt. Devices that maintain an internal | |
| state different from their external state, such as the MEM status and | |
| violation registers, are updated so that their internal and external states | |
| are fully consistent. This ensures that the state visible to the user during | |
| the simulation stop is correct. It also ensures that the program counter | |
| points correctly at the next instruction to execute upon resumption. | |
| In hardware, if the Memory Protect accessory is installed and enabled, I/O | |
| operations to select codes other than 01 are prohibited. Also, in | |
| combination with the MPCK micro-order, MP validates the M-register contents | |
| (memory address) against the memory protect fence. If a violation occurs, an | |
| I/O instruction or memory write is inhibited, and a memory read returns | |
| invalid data. | |
| In simulation, an instruction executor that detects an MP violation calls the | |
| MP_ABORT macro, passing the violation address as the parameter. This | |
| executes a "longjmp" to the abort handler, which is outside of and precedes | |
| the instruction execution loop. The value passed to "longjmp" is a 32-bit | |
| integer containing the logical address of the instruction causing the | |
| violation. MP_ABORT should only be called if "mp_control" is SET, as aborts | |
| do not occur if MP is turned off. | |
| An MP interrupt (SC 05) is qualified by "ion" but not by "ion_defer". If the | |
| interrupt system is off when an MP violation is detected, the violating | |
| instruction will be aborted, even though no interrupt occurs. In this case, | |
| neither the flag nor the flag buffer are set, and EVR is not cleared. | |
| The instruction execution loop starts by checking for event timer expiration. | |
| If one occurs, the associated event service routine is called, and if it was | |
| successful, the DMA service requests and interrupt requests are recalculated. | |
| DMA cycles are requested by an I/O card asserting its SRQ signal. If a DMA | |
| channel is programmed to respond to that card's select code, a DMA cycle will | |
| be initiated. A DMA cycle consists of a memory cycle and an I/O cycle. | |
| These cycles are synchronized with the control processor on the 21xx CPUs. On | |
| the 1000s, memory cycles are asynchronous, while I/O cycles are synchronous. | |
| Memory cycle time is about 40% of the I/O cycle time. | |
| With properly designed interface cards, DMA is capable of taking consecutive | |
| I/O cycles. On all machines except the 1000 M-Series, a DMA cycle freezes | |
| the CPU for the duration of the cycle. On the M-Series, a DMA cycle freezes | |
| the CPU if it attempts an I/O cycle (including IAK) or a directly-interfering | |
| memory cycle. An interleaved memory cycle is allowed. Otherwise, the | |
| control processor is allowed to run. Therefore, during consecutive DMA | |
| cycles, the M-Series CPU will run until an IOG instruction is attempted, | |
| whereas the other CPUs will freeze completely. | |
| All DMA cards except the 12607B provide two independent channels. If both | |
| channels are active simultaneously, channel 1 has priority for I/O cycles | |
| over channel 2. | |
| Most I/O cards assert SRQ no more than 50% of the time. A few buffered | |
| cards, such as the 12821A and 13175A Disc Interfaces, are capable of | |
| asserting SRQ continuously while filling or emptying the buffer. If SRQ for | |
| channel 1 is asserted continuously when both channels are active, then no | |
| channel 2 cycles will occur until channel 1 completes. | |
| Interrupt recognition is controlled by three state variables: "ion", | |
| "ion_defer", and "intrq". "ion" corresponds to the INTSYS flip-flop in the | |
| 1000 CPU, "ion_defer" corresponds to the INTEN flip-flop, and "intrq" | |
| corresponds to the NRMINT flip-flop. STF 00 and CLF 00 set and clear INTSYS, | |
| turning the interrupt system on and off. Micro-orders ION and IOFF set and | |
| clear INTEN, deferring or allowing certain interrupts. An IRQ signal from a | |
| device, qualified by the corresponding PRL signal, will set NRMINT to request | |
| a normal interrupt; an IOFF or IAK will clear it. | |
| Under simulation, "ion" is controlled by STF/CLF 00. "ion_defer" is set or | |
| cleared as appropriate by the individual instruction simulators. "intrq" is | |
| set to the successfully interrupting device's select code, or to zero if | |
| there is no qualifying interrupt request. | |
| Presuming PRL is set to allow priority to an interrupting device: | |
| 1. Power fail (SC 04) may interrupt if "ion_defer" is clear; this is not | |
| conditional on "ion" being set. | |
| 2. Memory protect (SC 05) may interrupt if "ion" is set; this is not | |
| conditional on "ion_defer" being clear. | |
| 3. Parity error (SC 05) may interrupt always; this is not conditional on | |
| "ion" being set or "ion_defer" being clear. | |
| 4. All other devices (SC 06 and up) may interrupt if "ion" is set and | |
| "ion_defer" is clear. | |
| Qualification with "ion" is performed by "calc_int", except for case 2, which | |
| is qualified by the MP abort handler above (because qualification occurs on | |
| the MP card, rather than in the CPU). Therefore, we need only qualify by | |
| "ion_defer" here. | |
| At instruction fetch time, a pending interrupt request will be deferred if | |
| the previous instruction was a JMP indirect, JSB indirect, STC, CLC, STF, | |
| CLF, or was executing from an interrupt trap cell. In addition, the following | |
| instructions will cause deferral on the 1000 series: SFS, SFC, JRS, DJP, DJS, | |
| SJP, SJS, UJP, and UJS. | |
| On the HP 1000, the request is always deferred until after the current | |
| instruction completes. On the 21xx, the request is deferred unless the | |
| current instruction is an MRG instruction other than JMP or JMP,I or JSB,I. | |
| Note that for the 21xx, SFS and SFC are not included in the deferral | |
| criteria. | |
| When a status other than SCPE_OK is returned from an instruction executor or | |
| event service routine, the instruction execution loop exits into the | |
| instruction postlude. The set of debug trace flags is restored if it had | |
| been changed by an active execution trace or idle trace suppression. This | |
| ensures that the simulation stop does not exit with the flags set improperly. | |
| If the simulation stopped for a programmed halt, the 21xx binary loader area | |
| is protected in case it had been unprotected to run the loader. The DMS | |
| status and violation registers and the program counter queue pointer are | |
| updated to present the proper values to the user interface. The default | |
| breakpoint type is updated to reflect the current MEU state (disabled, system | |
| map enabled, or user map enabled). Finally, the P register is reset if the | |
| current instruction is to be reexecuted on reentry (for example, on an | |
| unimplemented instruction stop). | |
| Implementation notes: | |
| 1. While the Microsoft VC++ "setjmp" documentation says, "All variables | |
| (except register variables) accessible to the routine receiving control | |
| contain the values they had when longjmp was called," the ISO C99 | |
| standard says, "All accessible objects have values...as of the time the | |
| longjmp function was called, except that the values of objects of | |
| automatic storage duration that are local to the function containing the | |
| invocation of the corresponding setjmp macro that do not have | |
| volatile-qualified type and have been changed between the setjmp | |
| invocation and longjmp call are indeterminate." | |
| Therefore, the "exec_save" and "idle_save" variables are marked static | |
| to ensure that they are reloaded after a longjmp caused by a memory | |
| protect abort (they are not marked volatile to save redundant reloads | |
| within the instruction execution loop). Also, "status" and "exec_test" | |
| are set before reentering the instruction loop after an abort. This is | |
| done solely to reassure the compiler that the values are not clobbered, | |
| even though in both cases the values are reestablished after an abort | |
| before they are used. | |
| 2. The protected lower bound address for the JSB instruction depends on the | |
| W5 jumper setting. If W5 is in, then the lower bound is 2, allowing JSBs | |
| to the A and B registers. If W5 is out, then the lower bound is 0, just | |
| as with JMP. The protected lower bound is set during the instruction | |
| prelude and tested during JSB address validation. | |
| 3. The -P switch is removed from the set of command line switches to ensure | |
| that internal calls to the device reset routines are not interpreted as | |
| "power-on" resets. | |
| 4. The "longjmp" handler is used both for MP and MEM violations. The MEV | |
| flip-flop will be clear for the former and set for the latter. The MEV | |
| violation register will be updated by "dms_upd_vr" only if the call is | |
| NOT for an MEM violation; if it is, then the register has already been | |
| set and should not be disturbed. | |
| 5. For an MP/MEM abort, the violation address is passed via "longjmp" to | |
| enable the MEM violation register to be updated. The "longjmp" routine | |
| will not pass a value of 0; it is converted internally to 1. This is OK, | |
| because only the page number of the address value is used, and locations | |
| 0 and 1 are both on page 0. | |
| 6. A CPU freeze is simulated by skipping instruction execution during the | |
| current loop cycle. | |
| 7. If both DMA channels have SRQ asserted, priority is simulated by skipping | |
| the channel 2 cycle if channel 1's SRQ is still asserted at the end of | |
| its cycle. If it is not, then channel 2 steals the next cycle from the | |
| CPU. | |
| 8. The 1000 M-Series allows some CPU processing concurrently with | |
| continuous DMA cycles, whereas all other CPUs freeze. The processor | |
| freezes if an I/O cycle is attempted, including an interrupt | |
| acknowledgement. Because some microcode extensions (e.g., Access IOP, | |
| RTE-6/VM OS) perform I/O cycles, advance detection of I/O cycles is | |
| difficult. Therefore, we freeze all processing for the M-Series as well. | |
| 9. EXEC tracing is active when exec_save is non-zero. "exec_save" saves the | |
| current state of the trace flags when an EXEC trace match occurs. For | |
| this to happen, at least TRACE_EXEC must be set, so "exec_save" will be | |
| set non-zero when a match is active. | |
| 10. The execution trace (TRACE_EXEC) match test is performed in two parts to | |
| display the register values both before and after the instruction | |
| execution. Consequently, the enable test is done before the register | |
| trace, and the disable test is done after. | |
| 11. A simulation stop bypass is inactivated after the first instruction | |
| execution by the expedient of setting the stop inhibition mask to the | |
| execution status result. This must be SCPE_OK (i.e., zero) for execution | |
| to continue, which removes the stop inhibition. If a non-zero status | |
| value is returned, then the inhibition mask will be set improperly, but | |
| that is irrelevant, as execution will stop in this case. | |
| */ | |
| t_stat sim_instr (void) | |
| { | |
| static const char *const register_values [] = { /* register values, indexed by EOI concatenation */ | |
| "e o i", | |
| "e o I", | |
| "e O i", | |
| "e O I", | |
| "E o i", | |
| "E o I", | |
| "E O i", | |
| "E O I" | |
| }; | |
| static const char mp_value [] = { /* memory protection value, indexed by mp_control */ | |
| '-', | |
| 'P' | |
| }; | |
| static const char *const register_formats [] = { /* CPU register formats, indexed by is_1000 */ | |
| REGA_FORMAT " A %06o, B %06o, ", /* is_1000 = FALSE format */ | |
| REGA_FORMAT " A %06o, B %06o, X %06o, Y %06o, " /* is_1000 = TRUE format */ | |
| }; | |
| static const char *const mp_mem_formats [] = { /* MP/MEM register formats, indexed by is_1000 */ | |
| REGB_FORMAT " MPF %06o, MPV %06o\n", /* is_1000 = FALSE format */ | |
| REGB_FORMAT " MPF %06o, MPV %06o, MES %06o, MEV %06o\n" /* is_1000 = TRUE format */ | |
| }; | |
| static uint32 exec_save; /* the trace flag settings saved by an EXEC match */ | |
| static uint32 idle_save; /* the trace flag settings saved by an idle match */ | |
| DEVICE *tbg_dptr; | |
| int abortval; | |
| uint32 intrq, dmarq; /* set after setjmp */ | |
| t_bool exec_test; /* set after setjmp */ | |
| t_bool iotrap; /* set after setjmp */ | |
| t_stat status; /* set after setjmp */ | |
| /* Instruction prelude */ | |
| if (sim_switches & SWMASK ('B')) /* if a simulation stop bypass was requested */ | |
| cpu_ss_inhibit = SS_INHIBIT; /* then inhibit stops for the first instruction */ | |
| else /* otherwise */ | |
| cpu_ss_inhibit = SCPE_OK; /* clear the inhibition mask */ | |
| sim_switches &= ~SWMASK ('P'); /* clear the power-on switch to prevent interference */ | |
| if (hp_device_conflict ()) /* if device assignment is inconsistent */ | |
| return SCPE_STOP; /* then inhibit execution */ | |
| tbg_dptr = find_dev ("CLK"); /* get a pointer to the time-base generator device */ | |
| if (tbg_dptr == NULL) /* if the TBG device is not present */ | |
| return SCPE_IERR; /* then something is seriously wrong */ | |
| else /* otherwise */ | |
| tbg_select_code = ((DIB *) tbg_dptr->ctxt)->select_code; /* get the select code from the device's DIB */ | |
| io_initialize (); /* set up the I/O data structures */ | |
| cpu_ioerr_uptr = NULL; /* and clear the I/O error unit pointer */ | |
| exec_save = 0; /* clear the EXEC match */ | |
| idle_save = 0; /* and idle match trace flags */ | |
| jsb_plb = (mp_unit.flags & UNIT_MP_JSB) ? 0 : 2; /* set the protected lower bound for JSB */ | |
| mp_mem_changed = TRUE; /* request an initial MP/MEM trace */ | |
| /* Memory Protect abort processor */ | |
| abortval = setjmp (save_env); /* set abort hdlr */ | |
| if (abortval) { /* memory protect abort? */ | |
| dms_upd_vr (abortval); /* update violation register (if not MEV) */ | |
| if (ion) /* interrupt system on? */ | |
| protio (dibs [PRO], ioENF, 0); /* set flag */ | |
| } | |
| dmarq = calc_dma (); /* initial recalc of DMA masks */ | |
| intrq = calc_int (); /* initial recalc of interrupts */ | |
| status = SCPE_OK; /* clear the status */ | |
| exec_test = FALSE; /* and the execution test flag */ | |
| /* Instruction execution loop */ | |
| do { /* execute instructions until halted */ | |
| err_PC = PR; /* save P for error recovery */ | |
| if (sim_interval <= 0) { /* event timeout? */ | |
| status = sim_process_event (); /* process event service */ | |
| if (status != SCPE_OK) /* service failed? */ | |
| break; /* stop execution */ | |
| dmarq = calc_dma (); /* recalc DMA reqs */ | |
| intrq = calc_int (); /* recalc interrupts */ | |
| } | |
| if (dmarq) { /* if a DMA service request is pending */ | |
| if (dmarq & DMA_1_REQ) { /* then if the request is for channel 1 */ | |
| status = dma_cycle (ch1, DMA_Channel_1); /* then do one DMA cycle using the port A map */ | |
| if (status == SCPE_OK) /* cycle OK? */ | |
| dmarq = calc_dma (); /* recalc DMA requests */ | |
| else | |
| break; /* cycle failed, so stop */ | |
| } | |
| if ((dmarq & (DMA_1_REQ | DMA_2_REQ)) == DMA_2_REQ) { /* DMA channel 1 idle and channel 2 request? */ | |
| status = dma_cycle (ch2, DMA_Channel_2); /* do one DMA cycle using port B map */ | |
| if (status == SCPE_OK) /* cycle OK? */ | |
| dmarq = calc_dma (); /* recalc DMA requests */ | |
| else | |
| break; /* cycle failed, so stop */ | |
| } | |
| if (dmarq) /* DMA request still pending? */ | |
| continue; /* service it before instruction execution */ | |
| intrq = calc_int (); /* recalc interrupts */ | |
| } | |
| if (intrq && ion_defer) /* if an interrupt is pending but deferred */ | |
| ion_defer = check_deferral (intrq); /* then check that the deferral is applicable */ | |
| if (intrq && !ion_defer) { /* if an interrupt request is pending and not deferred */ | |
| if (sim_brk_summ && /* any breakpoints? */ | |
| sim_brk_test (intrq, SWMASK ('E') | /* unconditional or right type for DMS? */ | |
| (dms_enb ? SWMASK ('S') : SWMASK ('N')))) { | |
| status = STOP_BRKPNT; /* stop simulation */ | |
| break; | |
| } | |
| CIR = (HP_WORD) intrq; /* save int addr in CIR */ | |
| intrq = 0; /* clear request */ | |
| ion_defer = TRUE; /* defer interrupts */ | |
| iotrap = TRUE; /* mark as I/O trap cell instr */ | |
| if (idle_save != 0) { /* if idle loop tracing is suppressed */ | |
| cpu_dev.dctrl = idle_save; /* then restore the saved trace flag set */ | |
| idle_save = 0; /* and indicate that we are out of the idle loop */ | |
| } | |
| if (TRACING (cpu_dev, TRACE_INSTR)) { | |
| meu_map (PR, dms_ump, NOPROT); /* reset the indicator and page */ | |
| tprintf (cpu_dev, cpu_dev.dctrl, | |
| DMS_FORMAT "interrupt\n", | |
| meu_indicator, meu_page, | |
| PR, CIR); | |
| } | |
| if (dms_enb) /* dms enabled? */ | |
| dms_sr = dms_sr | MST_ENBI; /* set in status */ | |
| else /* not enabled */ | |
| dms_sr = dms_sr & ~MST_ENBI; /* clear in status */ | |
| if (dms_ump) { /* user map enabled at interrupt? */ | |
| dms_sr = dms_sr | MST_UMPI; /* set in status */ | |
| dms_ump = SMAP; /* switch to system map */ | |
| } | |
| else /* system map enabled at interrupt */ | |
| dms_sr = dms_sr & ~MST_UMPI; /* clear in status */ | |
| mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */ | |
| IR = ReadF (CIR); /* get trap cell instruction */ | |
| io_dispatch (CIR, ioIAK, IR); /* acknowledge interrupt */ | |
| if (CIR != PRO) /* not MP interrupt? */ | |
| protio (dibs [CIR], ioIAK, IR); /* send IAK for device to MP too */ | |
| } | |
| else { /* normal instruction */ | |
| iotrap = FALSE; /* not a trap cell instruction */ | |
| if (sim_brk_summ && /* any breakpoints? */ | |
| sim_brk_test (PR, SWMASK ('E') | /* unconditional or */ | |
| (dms_enb ? /* correct type for DMS state? */ | |
| (dms_ump ? | |
| SWMASK ('U') : SWMASK ('S')) : | |
| SWMASK ('N')))) { | |
| status = STOP_BRKPNT; /* stop simulation */ | |
| break; | |
| } | |
| if (mp_evrff) /* violation register enabled */ | |
| mp_viol = PR; /* update with current P */ | |
| IR = ReadF (PR); /* fetch instr */ | |
| PR = (PR + 1) & VAMASK; | |
| ion_defer = FALSE; | |
| } | |
| if (TRACING (cpu_dev, TRACE_EXEC | TRACE_REG)) { /* if execution or register tracing is enabled */ | |
| if (cpu_dev.dctrl & TRACE_EXEC) /* then if tracing execution */ | |
| exec_test = (IR & exec_mask) == exec_match; /* then the execution test succeeds if */ | |
| /* the next instruction matches the test criteria */ | |
| if (cpu_dev.dctrl & TRACE_EXEC /* if execution tracing is enabled */ | |
| && exec_save == 0 /* and is currently inactive */ | |
| && exec_test) { /* and the matching test succeeds */ | |
| exec_save = cpu_dev.dctrl; /* then save the current trace flag set */ | |
| cpu_dev.dctrl |= TRACE_ALL; /* and turn on full tracing */ | |
| } | |
| if (cpu_dev.dctrl & TRACE_REG) { /* if register tracing is enabled */ | |
| hp_trace (&cpu_dev, TRACE_REG, /* then output the working registers */ | |
| register_formats [is_1000], | |
| mp_value [mp_control], | |
| dms_sr & MST_FENCE, | |
| SR, AR, BR, XR, YR); | |
| fputs (register_values [E << 2 | O << 1 | ion], sim_deb); | |
| fputc ('\n', sim_deb); | |
| if (mp_mem_changed) { /* if the MP/MEM registers have been altered */ | |
| hp_trace (&cpu_dev, TRACE_REG, /* then output the register values */ | |
| mp_mem_formats [is_1000], | |
| mp_value [mp_control], | |
| mp_fence, mp_viol, dms_sr, dms_vr); | |
| mp_mem_changed = FALSE; /* clear the MP/MEM registers changed flag */ | |
| } | |
| } | |
| if (cpu_dev.dctrl & TRACE_EXEC /* if execution tracing is enabled */ | |
| && exec_save != 0 /* and is currently active */ | |
| && ! exec_test) { /* and the matching test fails */ | |
| cpu_dev.dctrl = exec_save; /* then restore the saved debug flag set */ | |
| exec_save = 0; /* and indicate that tracing is disabled */ | |
| hp_trace (&cpu_dev, TRACE_EXEC, EXEC_FORMAT "\n"); /* add a separator to the trace log */ | |
| } | |
| } | |
| if (TRACING (cpu_dev, TRACE_INSTR)) { /* if instruction tracing is enabled */ | |
| hp_trace (&cpu_dev, TRACE_INSTR, /* then output the address and opcode */ | |
| DMS_FORMAT, | |
| meu_indicator, meu_page, | |
| MR, IR); | |
| sim_eval [0] = IR; /* save the (first) instruction word in the eval array */ | |
| if (fprint_cpu (sim_deb, MR, sim_eval, 0, CPU_Trace) > SCPE_OK) /* print the mnemonic; if that fails */ | |
| fprint_val (sim_deb, sim_eval [0], cpu_dev.dradix, /* then print the numeric */ | |
| cpu_dev.dwidth, PV_RZRO); /* value again */ | |
| fputc ('\n', sim_deb); /* end the trace with a newline */ | |
| } | |
| sim_interval = sim_interval - 1; /* count the instruction */ | |
| status = machine_instruction (IR, iotrap, intrq, /* execute one machine instruction */ | |
| &idle_save); | |
| if (status == NOTE_IOG) { /* I/O instr exec? */ | |
| dmarq = calc_dma (); /* recalc DMA masks */ | |
| intrq = calc_int (); /* recalc interrupts */ | |
| status = SCPE_OK; /* continue */ | |
| } | |
| else if (status == NOTE_INDINT) { /* intr pend during indir? */ | |
| PR = err_PC; /* back out of inst */ | |
| status = SCPE_OK; /* continue */ | |
| } | |
| cpu_ss_inhibit = status; /* clear the simulation stop inhibition mask */ | |
| } | |
| while (status == SCPE_OK); /* loop until halted */ | |
| /* Instruction postlude */ | |
| if (intrq && ion_defer) /* if an interrupt is pending but deferred */ | |
| ion_defer = check_deferral (intrq); /* then check that the deferral is applicable */ | |
| if (exec_save != 0) { /* if EXEC tracing is active */ | |
| cpu_dev.dctrl = exec_save; /* then restore the saved trace flag set */ | |
| hp_trace (&cpu_dev, TRACE_EXEC, EXEC_FORMAT "\n"); /* and add a separator to the trace log */ | |
| } | |
| else if (idle_save != 0) /* otherwise if idle tracing is suppressed */ | |
| cpu_dev.dctrl = idle_save; /* then restore the saved trace flag set */ | |
| saved_MR = MR; /* save for T cmd update */ | |
| if (status == STOP_HALT) /* programmed halt? */ | |
| set_loader (NULL, FALSE, NULL, NULL); /* disable loader (after T is read) */ | |
| else if (status <= STOP_RERUN) /* simulation stop */ | |
| PR = err_PC; /* back out instruction */ | |
| dms_upd_sr (); /* update dms_sr */ | |
| dms_upd_vr (MR); /* update dms_vr */ | |
| pcq_r->qptr = pcq_p; /* update pc q ptr */ | |
| if (dms_enb) /* DMS enabled? */ | |
| if (dms_ump) /* set default */ | |
| sim_brk_dflt = SWMASK ('U'); /* breakpoint type */ | |
| else /* to current */ | |
| sim_brk_dflt = SWMASK ('S'); /* map mode */ | |
| else /* DMS disabled */ | |
| sim_brk_dflt = SWMASK ('N'); /* set breakpoint type to non-DMS */ | |
| tprintf (cpu_dev, cpu_dev.dctrl, | |
| DMS_FORMAT "simulation stop: %s\n", | |
| meu_indicator, meu_page, | |
| MR, TR, sim_error_text (status)); | |
| return status; /* return status code */ | |
| } | |
| /* VM command post-processor | |
| Update T register to contents of memory addressed by M register. | |
| Implementation notes: | |
| 1. The T register must be changed only when M has changed. Otherwise, if T | |
| is updated after every command, then T will be set to zero if M points | |
| into the protected loader area of the 21xx machines, e.g., after a HLT | |
| instruction in the loader reenables loader protection. | |
| */ | |
| void cpu_post_cmd (t_bool from_scp) | |
| { | |
| if (MR != saved_MR) { /* M changed since last update? */ | |
| saved_MR = MR; | |
| TR = mem_fast_read (MR, dms_ump); /* sync T with new M */ | |
| } | |
| return; | |
| } | |
| /* CPU global utility routines */ | |
| /* Install a bootstrap loader into memory. | |
| This routine copies the bootstrap loader specified by "boot" into the last 64 | |
| words of main memory, limited by a 32K memory size. If "sc" contains the | |
| select code of an I/O interface (i.e., select code 10 or above), this routine | |
| will configure the I/O instructions in the loader to the supplied select | |
| code. On exit, P will be set to point at the loader starting program | |
| address, and S will be altered as directed by the "sr_clear" and "sr_set" | |
| masks if the current CPU is a 1000. | |
| The currently configured CPU family (21xx or 1000) determines which of two | |
| BOOT_LOADER structures is accessed from the "boot" array. Each structure | |
| contains the 64-word loader array and three indicies into the loader | |
| array that specify the start of program execution, the element containing the | |
| DMA control word, and the element containing the (negative) address of the | |
| first loader word in memory. | |
| 21xx-series loaders consist of subsections handling one or two devices. A | |
| two-part loader is indicated by a starting program index other than 0, i.e., | |
| other than the beginning of the loader. An example is the Basic Moving-Head | |
| Disc Loader (BMDL), which consists of a paper tape loader section starting at | |
| index 0 and a disc loader section starting at index 50 octal. For these | |
| loaders, I/O configuration depends on the "start_index" field of the selected | |
| BOOTSTRAP structure: I/O instructions before the starting index are | |
| configured to the current paper-tape reader select code, and instructions at | |
| or after the starting index are configured to the device select code | |
| specified by "sc". Single-part loaders specify a starting index of 0, and | |
| all I/O instructions are configured to the "sc" select code. | |
| 1000-series loaders are always single part and always start at index 0, so | |
| they are always configured to use the "sc" select code. | |
| If a given device does not have both a 21xx-series and a 1000-series loader, | |
| the "start_index" field of the undefined loader will be set to the "IBL_NA" | |
| value. If this routine is called to copy an undefined loader, it will reject | |
| the call with a "Command not allowed" error. | |
| If I/O configuration is requested, each instruction in the loader array is | |
| examined as it is copied to memory. If the instruction is a non-HLT I/O | |
| instruction referencing a select code >= 10, the select code will be reset by | |
| subtracting 10 and adding the value of the select code supplied by the "sc" | |
| parameter (or the paper-tape reader select code, as above). This permits | |
| configuration of loaders that address two- or three-card interfaces. Passing | |
| an "sc" value of 0 will inhibit configuration, and the loader array will be | |
| copied verbatim. | |
| As an example, passing an "sc" value of 24 octal will alter these I/O-group | |
| instructions as follows: | |
| Loader Configured | |
| Instruction Instruction Note | |
| ----------- ----------- ------------------------------ | |
| OTA 10 OTA 24 Normal configuration | |
| LIA 11 LIA 25 Second card configuration | |
| STC 6 STC 6 DCPC configuration not changed | |
| HLT 11 HLT 11 Halt instruction not changed | |
| If configuration is performed, two additional operations may be performed. | |
| First, the routine will alter the word at the index specified by the | |
| "dma_index" field of the selected BOOTSTRAP structure unconditionally as | |
| above. This word is assumed to contain a DMA control word; it is configured | |
| to reference the supplied select code. Second, it will set the word at the | |
| index specified by the "fwa_index" field to the two's-complement of the | |
| starting address of the loader in memory. This value may be used by the | |
| loader to check that it will not be overwritten by loaded data. | |
| If either field is set to the IBL_NA value, then the corresponding | |
| modification is not made. For example, the 21xx Basic Binary Loader (BBL) | |
| does not use DMA, so its "dma_index" field is set to IBL_NA, and so no DMA | |
| control word modification is done. | |
| This routine also unconditionally sets the P register to the starting | |
| address for loader execution. This is derived from the "start_index" field | |
| and the starting memory address to which the loader is copied. | |
| Finally, if the current CPU is a 1000-series machine, the S register bits | |
| corresponding to those set in the "sr_clear" value are masked off, and the | |
| bits corresponding to those in the "sr_set" value are set. In addition, the | |
| select code from the "sc" value is shifted left and ORed into the value. | |
| This action presets the S-register to the correct value for the selected | |
| loader. | |
| Implementation notes: | |
| 1. The paper-tape reader's select code is determined on each entry to the | |
| routine to accommodate select code reassignment by the user. | |
| */ | |
| t_stat cpu_copy_loader (const LOADER_ARRAY boot, uint32 sc, HP_WORD sr_clear, HP_WORD sr_set) | |
| { | |
| uint32 index, base, ptr_sc; | |
| MEMORY_WORD word; | |
| DEVICE *ptr_dptr; | |
| if (boot [is_1000].start_index == IBL_NA) /* if the bootstrap is not defined for the current CPU */ | |
| return SCPE_NOFNC; /* then reject the command */ | |
| else if (boot [is_1000].start_index > 0 && sc > 0) { /* if this is a two-part loader with I/O reconfiguration */ | |
| ptr_dptr = find_dev ("PTR"); /* then get a pointer to the paper tape reader device */ | |
| if (ptr_dptr == NULL) /* if the PTR device is not present */ | |
| return SCPE_IERR; /* then something is seriously wrong */ | |
| else /* otherwise */ | |
| ptr_sc = ((DIB *) ptr_dptr->ctxt)->select_code; /* get the select code from the device's DIB */ | |
| } | |
| else /* otherwise this is a single-part loader */ | |
| ptr_sc = 0; /* or I/O reconfiguration is not requested */ | |
| base = MEMSIZE - 1 & ~IBL_MASK & LA_MASK; /* get the base memory address of the loader */ | |
| PR = base + boot [is_1000].start_index & R_MASK; /* and store the starting program address in P */ | |
| set_loader (NULL, TRUE, NULL, NULL); /* enable the loader (ignore errors if not 21xx) */ | |
| for (index = 0; index < IBL_SIZE; index++) { /* copy the bootstrap loader to memory */ | |
| word = boot [is_1000].loader [index]; /* get the next word */ | |
| if (sc == 0) /* if reconfiguration is not requested */ | |
| M [base + index] = word; /* then copy the instruction verbatim */ | |
| else if ((word & I_NMRMASK) == I_IO /* otherwise if this is an I/O instruction */ | |
| && (word & I_DEVMASK) >= VARDEV /* and the referenced select code is >= 10B */ | |
| && I_GETIOOP (word) != soHLT) /* and it's not a halt instruction */ | |
| if (index < boot [is_1000].start_index) /* then if this is a split loader */ | |
| M [base + index] = word + (ptr_sc - VARDEV) & DV_MASK; /* then reconfigure the paper tape reader */ | |
| else /* otherwise */ | |
| M [base + index] = word + (sc - VARDEV) & DV_MASK; /* reconfigure the target device */ | |
| else if (index == boot [is_1000].dma_index) /* otherwise if this is the DMA configuration word */ | |
| M [base + index] = word + (sc - VARDEV) & DV_MASK; /* then reconfigure the target device */ | |
| else if (index == boot [is_1000].fwa_index) /* otherwise if this is the starting address word */ | |
| M [base + index] = NEG16 (base); /* then set the negative starting address of the bootstrap */ | |
| else /* otherwise the word is not a special one */ | |
| M [base + index] = word; /* so simply copy it */ | |
| } | |
| if (is_1000) /* if the CPU is a 1000 */ | |
| SR = SR & sr_clear | sr_set | IBL_TO_SC (sc); /* then modify the S register as indicated */ | |
| return SCPE_OK; /* return success with the loader copied to memory */ | |
| } | |
| /* Execute an I/O instruction. | |
| If memory protect is enabled, and the instruction is not in a trap cell, then | |
| HLT instructions are illegal and will cause a memory protect violation. If | |
| jumper W7 (SEL1) is in, then all other I/O instructions are legal; if W7 is | |
| out, then only I/O instructions to select code 1 are legal, and I/O to other | |
| select codes will cause a violation. | |
| If the instruction is allowed, then the I/O signal corresponding to the | |
| instruction is determined, and the state of the interrupt deferral flag is | |
| set. The signal is then dispatched to the device simulator indicated by the | |
| target select code. The return value is split into status and data values, | |
| with the latter containing the SKF signal state or data to be returned in the | |
| A or B registers. | |
| Implementation notes: | |
| 1. If the H/C (hold/clear flag) bit is set, then the ioCLF signal is added | |
| to the base signal set derived from the I/O instruction. | |
| 2. ioNONE is dispatched for HLT instructions because although HLT does not | |
| assert any backplane signals, the H/C bit may be set. If it is, then the | |
| result will be to dispatch ioCLF. | |
| 3. Device simulators return either ioSKF or ioNONE in response to an SFC or | |
| SFS signal. ioSKF means that the instruction should skip. Because | |
| device simulators return the "data" parameter value by default, we | |
| initialize that parameter to ioNONE to ensure that a simulator that does | |
| not implement SFC or SFS does not skip, which is the correct action for | |
| an interface that does not drive the SKF signal. | |
| 4. STF/CLF and STC/CLC share sub-opcode values and must be further decoded | |
| by the state of instruction register bits 9 and 11, respectively. | |
| 5. We return NOTE_IOG for normal status instead of SCPE_OK to request that | |
| interrupts be recalculated at the end of the instruction (execution of | |
| the I/O group instructions can change the interrupt priority chain). We | |
| do this in preference to calling the recalculation routines directly, as | |
| some extended firmware instructions call this routine multiple times, and | |
| there is no point in recalculating until all calls are complete. | |
| 6. The I/O dispatcher returns NOTE_SKIP if the interface asserted the SKF | |
| signal. We must recalculate interrupts if the originating SFS or SFC | |
| instruction included the CLF signal (e.g., SFS 0,C). | |
| */ | |
| t_stat cpu_iog (HP_WORD IR, t_bool iotrap) | |
| { | |
| /* Translation for I/O subopcodes: soHLT, soFLG, soSFC, soSFS, soMIX, soLIX, soOTX, soCTL */ | |
| static const IOSIGNAL generate_signal [] = { ioNONE, ioSTF, ioSFC, ioSFS, ioIOI, ioIOI, ioIOO, ioSTC }; | |
| const uint32 dev = IR & I_DEVMASK; /* device select code */ | |
| const uint32 sop = I_GETIOOP (IR); /* I/O subopcode */ | |
| const uint32 ab = (IR & I_AB ? 1 : 0); /* A/B register selector */ | |
| uint32 ioreturn; | |
| t_stat iostat; | |
| IOCYCLE signal_set; | |
| HP_WORD iodata = (HP_WORD) ioNONE; /* initialize for SKF test */ | |
| if (mp_control && !iotrap /* if MP is enabled and the instruction is not in trap cell */ | |
| && (sop == soHLT /* and it is a HLT */ | |
| || dev != OVF && (mp_unit.flags & UNIT_MP_SEL1))) { /* or does not address SC 01 and SEL1 is out */ | |
| if (sop == soLIX) /* then an MP violation occurs; if it is an LIA/B */ | |
| ABREG [ab] = 0; /* then the register is written before the abort */ | |
| MP_ABORT (err_PC); /* MP abort */ | |
| } | |
| signal_set = generate_signal [sop]; /* generate I/O signal from instruction */ | |
| ion_defer = defer_tab [sop]; /* defer depending on instruction */ | |
| if (sop == soOTX) /* OTA/B instruction? */ | |
| iodata = ABREG [ab]; /* pass A/B register value */ | |
| else if (sop == soCTL && IR & I_CTL) /* CLC instruction? */ | |
| signal_set = ioCLC; /* change STC to CLC signal */ | |
| if (IR & I_HC) /* if the H/C bit is set */ | |
| if (sop == soFLG) /* then if the instruction is STF or CLF */ | |
| signal_set = ioCLF; /* then change the ioSTF signal to ioCLF */ | |
| else /* otherwise it's a non-flag instruction */ | |
| signal_set |= ioCLF; /* so add ioCLF to the instruction-specific signal */ | |
| ioreturn = io_dispatch (dev, signal_set, iodata); /* dispatch the I/O signals */ | |
| iostat = IOSTATUS (ioreturn); /* extract status */ | |
| iodata = IODATA (ioreturn); /* extract return data value */ | |
| if (iostat == NOTE_SKIP) { /* if the interface asserted SKF */ | |
| PR = PR + 1 & LA_MASK; /* then bump P to skip then next instruction */ | |
| return (IR & I_HC ? NOTE_IOG : SCPE_OK); /* and request recalculation of interrupts if needed */ | |
| } | |
| else if (iostat == SCPE_OK) { /* otherwise if instruction execution succeeded */ | |
| if (sop == soLIX) /* then if is it an LIA or LIB */ | |
| ABREG [ab] = iodata; /* then load the returned data */ | |
| else if (sop == soMIX) /* otherwise if it is an MIA or MIB */ | |
| ABREG [ab] = ABREG [ab] | iodata; /* then merge the returned data */ | |
| else if (sop == soHLT) /* otherwise if it is a HLT */ | |
| return STOP_HALT; /* then stop the simulator */ | |
| return NOTE_IOG; /* request recalculation of interrupts */ | |
| } | |
| else /* otherwise the execution failed */ | |
| return iostat; /* so return the failure status */ | |
| } | |
| /* Calculate interrupt requests. | |
| The interrupt request (IRQ) of the highest-priority device for which all | |
| higher-priority PRL bits are set is granted. That is, there must be an | |
| unbroken chain of priority to a device requesting an interrupt for that | |
| request to be granted. | |
| A device sets its IRQ bit to request an interrupt, and it clears its PRL bit | |
| to prevent lower-priority devices from interrupting. IRQ is cleared by an | |
| interrupt acknowledge (IAK) signal. PRL generally remains low while a | |
| device's interrupt service routine is executing to prevent preemption. | |
| IRQ and PRL indicate one of four possible states for a device: | |
| IRQ PRL Device state | |
| --- --- ---------------------- | |
| 0 1 Not interrupting | |
| 1 0 Interrupt requested | |
| 0 0 Interrupt acknowledged | |
| 1 1 (not allowed) | |
| Note that PRL must be dropped when requesting an interrupt (IRQ set). This | |
| is a hardware requirement of the 1000 series. The IRQ lines from the | |
| backplane are not priority encoded. Instead, the PRL chain expresses the | |
| priority by allowing only one IRQ line to be active at a time. This allows a | |
| simple pull-down encoding of the CIR inputs. | |
| The end of priority chain is marked by the highest-priority (lowest-order) | |
| bit that is clear. The device corresponding to that bit is the only device | |
| that may interrupt (a higher priority device that had IRQ set would also have | |
| had PRL set, which is a state violation). We calculate a priority mask by | |
| ANDing the complement of the PRL bits with an increment of the PRL bits. | |
| Only the lowest-order bit will differ. For example: | |
| dev_prl : ...1 1 0 1 1 0 1 1 1 1 1 1 (PRL denied for SC 06 and 11) | |
| dev_prl + 1 : ...1 1 0 1 1 1 0 0 0 0 0 0 | |
| ~dev_prl : ...0 0 1 0 0 1 0 0 0 0 0 0 | |
| ANDed value : ...0 0 0 0 0 1 0 0 0 0 0 0 (break is at SC 06) | |
| The interrupt requests are then ANDed with the priority mask to determine if | |
| a request is pending: | |
| pri mask : ...0 0 0 0 0 1 0 0 0 0 0 0 (allowed interrupt source) | |
| dev_irq : ...0 0 1 0 0 1 0 0 0 0 0 0 (devices requesting interrupts) | |
| ANDed value : ...0 0 0 0 0 1 0 0 0 0 0 0 (request to grant) | |
| The select code corresponding to the granted request is then returned to the | |
| caller. | |
| If ION is clear, only power fail (SC 04) and parity error (SC 05) are | |
| eligible to interrupt (memory protect shares SC 05, but qualification occurs | |
| in the MP abort handler, so if SC 05 is interrupting when ION is clear, it | |
| must be a parity error interrupt). | |
| */ | |
| uint32 calc_int (void) | |
| { | |
| uint32 sc, pri_mask [2], req_grant [2]; | |
| pri_mask [0] = ~dev_prl [0] & (dev_prl [0] + 1); /* calculate lower priority mask */ | |
| req_grant [0] = pri_mask [0] & dev_irq [0]; /* calculate lower request to grant */ | |
| if (ion) /* interrupt system on? */ | |
| if ((req_grant [0] == 0) && (pri_mask [0] == 0)) { /* no requests in lower set and PRL unbroken? */ | |
| pri_mask [1] = ~dev_prl [1] & (dev_prl [1] + 1); /* calculate upper priority mask */ | |
| req_grant [1] = pri_mask [1] & dev_irq [1]; /* calculate upper request to grant */ | |
| } | |
| else /* lower set has request */ | |
| req_grant [1] = 0; /* no grants to upper set */ | |
| else { /* interrupt system off */ | |
| req_grant [0] = req_grant [0] & /* only PF and PE can interrupt */ | |
| (BIT_M (PWR) | BIT_M (PRO)); | |
| req_grant [1] = 0; | |
| } | |
| if (req_grant [0]) /* device in lower half? */ | |
| for (sc = 0; sc <= 31; sc++) /* determine interrupting select code */ | |
| if (req_grant [0] & LSB) /* grant this request? */ | |
| return sc; /* return this select code */ | |
| else /* not this one */ | |
| req_grant [0] = req_grant [0] >> 1; /* position next request */ | |
| else if (req_grant [1]) /* device in upper half */ | |
| for (sc = 32; sc <= 63; sc++) /* determine interrupting select code */ | |
| if (req_grant [1] & LSB) /* grant this request? */ | |
| return sc; /* return this select code */ | |
| else /* not this one */ | |
| req_grant [1] = req_grant [1] >> 1; /* position next request */ | |
| return 0; /* no interrupt granted */ | |
| } | |
| /* Resolve a indirect address. | |
| This routine resolves a supplied memory address into a direct address by | |
| following an indirect chain, if any. On entry, "MA" contains the address to | |
| resolve, and "irq" is non-zero if an interrupt is currently pending. On | |
| exit, the variable pointed to by "addr" is set to the direct address, and | |
| SCPE_OK is returned. If an interrupt is pending and permitted, NOTE_INDINT | |
| is returned to abort the instruction, and the variable indicated by "addr" is | |
| unchanged. | |
| Logical memory addresses are 15 bits wide, providing direct access to a 32K | |
| logical address space. Addresses may also be indirect, with bit 15 (the MSB) | |
| serving as the direct/indirect indicator. An indirect address may point at | |
| either a direct or indirect address. In the latter case, the chain is | |
| followed until a direct address is obtained. | |
| Indirect addressing has implications for interrupt handling. Normally, | |
| interrupts are checked at each level of indirection, and if one is pending, | |
| the CPU will abort execution of the instruction and then service the | |
| interrupt. On return from the interrupt handler, the instruction will be | |
| restarted. | |
| However, the JMP indirect and JSB indirect instructions hold off interrupts | |
| until completion of the instruction, including complete resolution of the | |
| indirect chain. If the chain is unresolvable (i.e., it points to itself, as | |
| in the instruction sequence JMP *+1,I and DEF *,I), then interrupts are held | |
| off forever. | |
| To prevent a user program from freezing a protected OS with an infinite | |
| indirect chain, and to permit real-time interrupts to be handled while | |
| resolving a long indirect chain, the Memory Protect accessory counts indirect | |
| levels during address resolution and will reenable interrupt recognition | |
| after the third level. Operating systems that run without MP installed are | |
| subject to freezing as above, but those employing MP will be able to regain | |
| control from an infinite indirect chain. | |
| In simulation, the SET CPU INDIR=<limit> command sets the maximum number of | |
| levels; the default is 16. If the level is exceeded during address | |
| resolution, the simulator will stop. The maximum limit is 32768, which is | |
| the maximum possible address chain without an infinite loop, but an indirect | |
| chain over a few levels deep almost certainly represents a programming error. | |
| Implementation notes: | |
| 1. Virtually all valid indirect references are one level deep, so we | |
| optimize for this case. Also, we protect against entry with a direct | |
| address by simply returning the address, but the overhead can be saved by | |
| calling this routine only for indirect addresses. | |
| 2. The 12892B Memory Protect accessory jumper W6 ("INT") controls whether | |
| held off pending interrupts are serviced immediately (jumper removed) or | |
| after three levels of indirection (jumper installed). If the jumper is | |
| removed, MP must be enabled (control flip-flop set) for the interrupt | |
| hold off to be overridden. | |
| The jumper state need not be checked here, however, because this routine | |
| can be entered with an interrupt pending ("irq" non-zero) only if | |
| "ion_defer" and "check_deferral" are both true. If either is false, the | |
| pending interrupt would have been serviced before calling the instruction | |
| executor that is calling this routine to resolve its address. For | |
| "check_deferral" to return TRUE, then the INT jumper must be installed or | |
| the MP control flip-flop must be clear. | |
| 3. When employing the indirect counter, the hardware clears a pending | |
| interrupt deferral after the third level of indirection and aborts the | |
| instruction after the fourth. | |
| 4. The JRS, DJP, DJS, SJP, SJS, UJP, and UJS instructions also hold off | |
| interrupts for three indirect levels, but they count levels internally | |
| and do not depend on the presence of the MP accessory to reenable | |
| interrupt recognition. However, DMS requires MP, so simulation uses the | |
| MP indirect counter for these instructions as well. | |
| 5. In hardware, it is possible to execute an instruction with an infinite | |
| indirect loop (e.g., JMP *+1,I and DEF *,I). If MP is not installed, | |
| this freezes the CPU with interrupts disabled until HALT is pressed. In | |
| simulation, the instruction executes until the indirect limit is reached, | |
| whereupon the simulator stops with "Indirect address loop" status. | |
| Modelling the hardware CPU freeze would be difficult, as the simulation | |
| console would have to be polled locally to watch for CTRL+E (the | |
| simulation equivalent of the CPU front panel HALT button). | |
| */ | |
| t_stat resolve (HP_WORD MA, HP_WORD *address, uint32 irq) | |
| { | |
| uint32 level; | |
| t_bool pending; | |
| if (MA & I_IA) { /* if the address is indirect */ | |
| MA = ReadW (MA & LA_MASK); /* then follow the chain (first level) */ | |
| if (MA & I_IA) { /* if the address is still indirect */ | |
| pending = (irq && !(mp_unit.flags & DEV_DIS)); /* then permit a pending interrupt if MP is enabled */ | |
| for (level = 2; MA & I_IA; level++) { /* follow the chain from level 2 until the address resolves */ | |
| if (level > indirect_limit) /* if the limit is exceeded */ | |
| return STOP_INDIR; /* then stop the simulator */ | |
| else if (pending) /* otherwise if an interrupt is pending */ | |
| if (level == 3) /* then if this is the third level */ | |
| ion_defer = FALSE; /* then reenable interrupts */ | |
| else if (level == 4) /* otherwise if this is the fourth level */ | |
| return NOTE_INDINT; /* then service the interrupt now */ | |
| MA = ReadW (MA & LA_MASK); /* follow the address chain */ | |
| } | |
| } | |
| } | |
| *address = MA; /* return the direct address */ | |
| return SCPE_OK; /* and success status */ | |
| } | |
| /* Memory global utility routines */ | |
| /* Read a word from memory. | |
| Read and return a word from memory at the indicated logical address. On | |
| entry, "dptr" points to the DEVICE structure of the device requesting access, | |
| "classification" is the type of access requested, and "address" is the offset | |
| into the 32K logical address space implied by the classification. | |
| If memory expansion is enabled, the logical address is mapped into a physical | |
| memory location; the map used is determined by the access classification. | |
| The current map (user or system), alternate map (the map not currently | |
| selected), or an explicit map (system, user, DCPC port A, or port B) may be | |
| requested. Read protection is enabled for current or alternate map access | |
| and disabled for the others. If memory expansion is disabled or not present, | |
| the logical address directly accesses the first 32K of memory. | |
| The memory protect (MP) and memory expansion module (MEM) accessories provide | |
| a protected mode that guards against improper accesses by user programs. | |
| They may be enabled or disabled independently, although protection requires | |
| that both be enabled. MEM checks that read protection rules on the target | |
| page are compatible with the access desired. If the check fails, and MP is | |
| enabled, then the request is aborted. | |
| The 1000 family maps memory location 0 to the A-register and location 1 to | |
| the B-register. CPU reads of these locations return the A- or B-register | |
| values, while DCPC reads access physical memory locations 0 and 1 instead. | |
| Implementation notes: | |
| 1. A read beyond the limit of physical memory returns 0. This is handled by | |
| allocating the maximum memory array and initializing memory beyond the | |
| defined limit to zero, so no special handling is needed here.. | |
| 2. A MEM read protection violation with MP enabled causes an MP abort | |
| instead of a normal return. | |
| */ | |
| HP_WORD mem_read (DEVICE *dptr, ACCESS_CLASS classification, HP_WORD address) | |
| { | |
| uint32 index, map; | |
| HP_WORD protection; | |
| switch (classification) { /* dispatch on the access classification */ | |
| case Fetch: | |
| case Data: | |
| default: /* needed to quiet the compiler's anxiety */ | |
| map = dms_ump; /* use the currently selected map (user or system) */ | |
| protection = RDPROT; /* and enable read protection */ | |
| break; | |
| case Data_Alternate: | |
| map = dms_ump ^ MAP_LNT; /* use the alternate map (user or system) */ | |
| protection = RDPROT; /* and enable read protection */ | |
| break; | |
| case Data_System: | |
| map = SMAP; /* use the system map explicitly */ | |
| protection = NOPROT; /* without protection */ | |
| break; | |
| case Data_User: | |
| map = UMAP; /* use the user map explicitly */ | |
| protection = NOPROT; /* without protection */ | |
| break; | |
| case DMA_Channel_1: | |
| map = PAMAP; /* use the DCPC port A map */ | |
| protection = NOPROT; /* without protection */ | |
| break; | |
| case DMA_Channel_2: | |
| map = PBMAP; /* use the DCPC port B map */ | |
| protection = NOPROT; /* without protection */ | |
| break; | |
| } /* all cases are handled */ | |
| MR = address; /* save the logical memory address */ | |
| index = meu_map (address, map, protection); /* and translate to a physical address */ | |
| if (index <= 1 && map < PAMAP) /* if the A/B register is referenced */ | |
| TR = ABREG [index]; /* then return the selected register value */ | |
| else /* otherwise */ | |
| TR = (HP_WORD) M [index]; /* return the physical memory value */ | |
| tpprintf (dptr, mem_access [classification].debug_flag, | |
| DMS_FORMAT " %s%s\n", | |
| meu_indicator, meu_page, MR, TR, | |
| mem_access [classification].name, | |
| mem_access [classification].debug_flag == TRACE_FETCH ? "" : " read"); | |
| return TR; | |
| } | |
| /* Write a word to memory. | |
| Write a word to memory at the indicated logical address. On entry, "dptr" | |
| points to the DEVICE structure of the device requesting access, | |
| "classification" is the type of access requested, "address" is the offset | |
| into the 32K logical address space implied by the classification, and | |
| "value" is the value to write. | |
| If memory expansion is enabled, the logical address is mapped into a physical | |
| memory location; the map used is determined by the access classification. | |
| The current map (user or system), alternate map (the map not currently | |
| selected), or an explicit map (system, user, DCPC port A, or port B) may be | |
| requested. Write protection is enabled for current or alternate map access | |
| and disabled for the others. If memory expansion is disabled or not present, | |
| the logical address directly accesses the first 32K of memory. | |
| The memory protect (MP) and memory expansion module (MEM) accessories provide | |
| a protected mode that guards against improper accesses by user programs. | |
| They may be enabled or disabled independently, although protection requires | |
| that both be enabled. MP checks that memory writes do not fall below the | |
| Memory Protect Fence Register (MPFR) value, and MEM checks that write | |
| protection rules on the target page are compatible with the access desired. | |
| If either check fails, and MP is enabled, then the request is aborted (so, to | |
| pass, a page must be writable AND the target must be above the MP fence). In | |
| addition, a MEM write violation will occur if MP is enabled and the alternate | |
| map is selected, regardless of the page protection. | |
| The 1000 family maps memory location 0 to the A-register and location 1 to | |
| the B-register. CPU writes to these locations store the values into the A or | |
| B register, while DCPC writes access physical memory locations 0 and 1 | |
| instead. MP uses a lower bound of 2 for memory writes, allowing unrestricted | |
| access to the A and B registers. | |
| Implementation notes: | |
| 1. A write beyond the limit of physical memory is a no-operation. | |
| 2. When the alternate map is enabled, writes are permitted only in the | |
| unprotected mode, regardless of page protections or the MP fence setting. | |
| This behavior is not mentioned in the MEM documentation, but it is tested by | |
| the MEM diagnostic and is evident from the MEM schematic. Referring to | |
| Sheet 2 in the ERD, gates U125 and U127 provide this logic: | |
| WTV = MPCNDB * MAPON * (WPRO + ALTMAP) | |
| The ALTMAP signal is generated by the not-Q output of flip-flop U117, | |
| which toggles on control signal -CL3 assertion (generated by the MESP | |
| microorder) to select the alternate map. Therefore, a write violation is | |
| indicated whenever a memory protect check occurs while the MEM is enabled | |
| and either the page is write-protected or the alternate map is selected. | |
| The hardware reference manuals that contain descriptions of those DMS | |
| instructions that write to the alternate map (e.g., MBI) say, "This | |
| instruction will always cause a MEM violation when executed in the | |
| protected mode and no bytes [or words] will be transferred." However, | |
| they do not state that a write violation will be indicated, nor does the | |
| description of the write violation state that this is a potential cause. | |
| */ | |
| void mem_write (DEVICE *dptr, ACCESS_CLASS classification, HP_WORD address, HP_WORD value) | |
| { | |
| uint32 index, map; | |
| HP_WORD protection; | |
| switch (classification) { /* dispatch on the access classification */ | |
| case Data: | |
| default: /* needed to quiet the compiler's anxiety */ | |
| map = dms_ump; /* use the currently selected map (user or system) */ | |
| protection = WRPROT; /* and enable write protection */ | |
| break; | |
| case Data_Alternate: | |
| map = dms_ump ^ MAP_LNT; /* use the alternate map (user or system) */ | |
| protection = WRPROT; /* and enable write protection */ | |
| if (dms_enb) /* if the MEM is enabled */ | |
| dms_viol (address, MVI_WPR); /* then a violation always occurs if in protected mode */ | |
| break; | |
| case Data_System: | |
| map = SMAP; /* use the system map explicitly */ | |
| protection = NOPROT; /* without protection */ | |
| break; | |
| case Data_User: | |
| map = UMAP; /* use the user map explicitly */ | |
| protection = NOPROT; /* without protection */ | |
| break; | |
| case DMA_Channel_1: | |
| map = PAMAP; /* use the DCPC port A map */ | |
| protection = NOPROT; /* without protection */ | |
| break; | |
| case DMA_Channel_2: | |
| map = PBMAP; /* use the DCPC port B map */ | |
| protection = NOPROT; /* without protection */ | |
| break; | |
| case Fetch: /* instruction fetches */ | |
| return; /* do not cause writes */ | |
| } /* all cases are handled */ | |
| MR = address; /* save the logical memory address */ | |
| index = meu_map (address, map, protection); /* and translate to a physical address */ | |
| if (protection != NOPROT && MP_TEST (address)) /* if protected and the MP check fails */ | |
| MP_ABORT (address); /* then abort with an MP violation */ | |
| if (index <= 1 && map < PAMAP) /* if the A/B register is referenced */ | |
| ABREG [index] = value; /* then write the value to the selected register */ | |
| else if (index < fwanxm) /* otherwise if the location is within defined memory */ | |
| M [index] = (MEMORY_WORD) value; /* then write the value to memory */ | |
| TR = value; /* save the value */ | |
| tpprintf (dptr, mem_access [classification].debug_flag, | |
| DMS_FORMAT " %s write\n", | |
| meu_indicator, meu_page, MR, TR, | |
| mem_access [classification].name); | |
| return; | |
| } | |
| /* Read a byte from memory. | |
| Read and return a byte from memory at the indicated logical address. On | |
| entry, "dptr" points to the DEVICE structure of the device requesting access, | |
| "classification" is the type of access requested, and "byte_address" is the | |
| byte offset into the 32K logical address space implied by the classification. | |
| The 1000 is a word-oriented machine. To permit byte accesses, a logical byte | |
| address is defined as two times the associated word address. The LSB of the | |
| byte address designates the byte to access: 0 for the upper byte, and 1 for | |
| the lower byte. As all 16 bits are used, byte addresses cannot be indirect. | |
| Implementation notes: | |
| 1. Word buffering is not used to minimize memory reads, as the HP 1000 | |
| microcode does a full word read for each byte accessed. | |
| */ | |
| uint8 mem_read_byte (DEVICE *dptr, ACCESS_CLASS classification, HP_WORD byte_address) | |
| { | |
| const HP_WORD word_address = byte_address >> 1; /* the address of the word containing the byte */ | |
| HP_WORD word; | |
| word = mem_read (dptr, classification, word_address); /* read the addressed word */ | |
| if (byte_address & LSB) /* if the byte address is odd */ | |
| return LOWER_BYTE (word); /* then return the right-hand byte */ | |
| else /* otherwise */ | |
| return UPPER_BYTE (word); /* return the left-hand byte */ | |
| } | |
| /* Write a byte to memory. | |
| Write a byte to memory at the indicated logical address. On entry, "dptr" | |
| points to the DEVICE structure of the device requesting access, | |
| "classification" is the type of access requested, "byte_address" is the | |
| byte offset into the 32K logical address space implied by the classification, | |
| and "value" is the value to write. | |
| The 1000 is a word-oriented machine. To permit byte accesses, a logical byte | |
| address is defined as two times the associated word address. The LSB of the | |
| byte address designates the byte to access: 0 for the upper byte, and 1 for | |
| the lower byte. As all 16 bits are used, byte addresses cannot be indirect. | |
| Implementation notes: | |
| 1. Word buffering is not used to minimize memory writes, as the HP 1000 | |
| base-set microcode does a full word write for each byte accessed. (The | |
| DMS byte instructions, e.g., MBI, do full-word accesses for each pair of | |
| bytes, but that is to minimize the number of map switches.) | |
| */ | |
| void mem_write_byte (DEVICE *dptr, ACCESS_CLASS classification, HP_WORD byte_address, uint8 value) | |
| { | |
| const HP_WORD word_address = byte_address >> 1; /* the address of the word containing the byte */ | |
| HP_WORD word; | |
| word = mem_read (dptr, classification, word_address); /* read the addressed word */ | |
| if (byte_address & LSB) /* if the byte address is odd */ | |
| word = REPLACE_LOWER (word, value); /* then replace the right-hand byte */ | |
| else /* otherwise */ | |
| word = REPLACE_UPPER (word, value); /* replace the left-hand byte */ | |
| mem_write (dptr, classification, word_address, word); /* write the updated word back */ | |
| return; | |
| } | |
| /* Fast read from memory. | |
| This routine reads and returns a word from memory at the indicated logical | |
| address using the specified map. Memory protection is not used, and tracing | |
| is not available. | |
| This routine is used when fast, unchecked access to mapped memory is | |
| required. | |
| */ | |
| HP_WORD mem_fast_read (HP_WORD address, uint32 map) | |
| { | |
| return mem_examine (meu_map (address, map, NOPROT)); /* return the value at the translated address */ | |
| } | |
| /* Examine a physical memory address. | |
| This routine reads and returns a word from memory at the indicated physical | |
| address. If the address lies outside of allocated memory, a zero value is | |
| returned. There are no protections or error indications. | |
| */ | |
| HP_WORD mem_examine (uint32 address) | |
| { | |
| if (address <= 1) /* if the address is 0 or 1 */ | |
| return ABREG [address]; /* then return the A or B register value */ | |
| else if (address < PASIZE) /* otherwise if the address is within allocated memory */ | |
| return (HP_WORD) M [address]; /* then return the memory value */ | |
| else /* otherwise the access is outside of memory */ | |
| return 0; /* which reads as zero */ | |
| } | |
| /* Deposit into a physical memory address. | |
| This routine writes a word into memory at the indicated physical address. If | |
| the address lies outside of defined memory, the write is ignored. There are | |
| no protections or error indications. | |
| */ | |
| void mem_deposit (uint32 address, HP_WORD value) | |
| { | |
| if (address <= 1) /* if the address is 0 or 1 */ | |
| ABREG [address] = value & DV_MASK; /* then store into the A or B register */ | |
| else if (address < fwanxm) /* otherwise if the address is within defined memory */ | |
| M [address] = (MEMORY_WORD) value & DV_MASK; /* then store the value */ | |
| return; | |
| } | |
| /* Memory Expansion Unit global utility routines */ | |
| /* DMS read and write map registers */ | |
| uint16 dms_rmap (uint32 mapi) | |
| { | |
| return dms_map [mapi & MAP_MASK] & ~MAP_RSVD; | |
| } | |
| void dms_wmap (uint32 mapi, uint32 dat) | |
| { | |
| dms_map [mapi & MAP_MASK] = (uint16) (dat & ~MAP_RSVD); | |
| return; | |
| } | |
| /* Process a MEM violation. | |
| A MEM violation will report the cause in the violation register. This occurs | |
| even if the MEM is not in the protected mode (i.e., MP is not enabled). If | |
| MP is enabled, an MP abort is taken with the MEV flip-flop set. Otherwise, | |
| we return to the caller. | |
| */ | |
| void dms_viol (uint32 va, HP_WORD st) | |
| { | |
| dms_vr = st | dms_upd_vr (va); /* set violation cause in register */ | |
| if (mp_control) { /* memory protect on? */ | |
| mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */ | |
| mp_mevff = SET; /* record memory expansion violation */ | |
| MP_ABORT (va); /* abort */ | |
| } | |
| return; | |
| } | |
| /* Update the MEM violation register. | |
| In hardware, the MEM violation register (VR) is clocked on every memory read, | |
| every memory write above the lower bound of protected memory, and every | |
| execution of a privileged DMS instruction. The register is not clocked when | |
| MP is disabled by an MP or MEM error (i.e., when MEVFF sets or CTL5FF | |
| clears), in order to capture the state of the MEM. In other words, the VR | |
| continually tracks the memory map register accessed plus the MEM state | |
| (MEBEN, MAPON, and USR) until a violation occurs, and then it's "frozen." | |
| Under simulation, we do not have to update the VR on every memory access, | |
| because the visible state is only available via a programmed RVA/B | |
| instruction or via the SCP interface. Therefore, it is sufficient if the | |
| register is updated: | |
| - at a MEM violation (when freezing) | |
| - at an MP violation (when freezing) | |
| - during RVA/B execution (if not frozen) | |
| - before returning to SCP after a simulator stop (if not frozen) | |
| */ | |
| HP_WORD dms_upd_vr (uint32 va) | |
| { | |
| if (mp_control && (mp_mevff == CLEAR)) { /* violation register unfrozen? */ | |
| dms_vr = VA_GETPAG (va) | /* set map address */ | |
| (dms_enb ? MVI_MEM : 0) | /* and MEM enabled */ | |
| (dms_ump ? MVI_UMP : 0); /* and user map enabled */ | |
| if (is_mapped (va)) /* is addressed mapped? */ | |
| dms_vr = dms_vr | MVI_MEB; /* ME bus is enabled */ | |
| mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */ | |
| } | |
| return dms_vr; | |
| } | |
| /* Update the MEM status register */ | |
| HP_WORD dms_upd_sr (void) | |
| { | |
| dms_sr = dms_sr & ~(MST_ENB | MST_UMP | MST_PRO); | |
| if (dms_enb) | |
| dms_sr = dms_sr | MST_ENB; | |
| if (dms_ump) | |
| dms_sr = dms_sr | MST_UMP; | |
| if (mp_control) | |
| dms_sr = dms_sr | MST_PRO; | |
| return dms_sr; | |
| } | |
| /* Memory Protect global utility routines */ | |
| /* Memory protect and DMS validation for jumps. | |
| Jumps are a special case of write validation. The target address is treated | |
| as a write, even when no physical write takes place, so jumping to a | |
| write-protected page causes a MEM violation. In addition, a MEM violation is | |
| indicated if the jump is to the unmapped portion of the base page. Finally, | |
| jumping to a location under the memory-protect fence causes an MP violation. | |
| Because the MP and MEM hardware works in parallel, all three violations may | |
| exist concurrently. For example, a JMP to the unmapped portion of the base | |
| page that is write protected and under the MP fence will indicate a | |
| base-page, write, and MP violation, whereas a JMP to the mapped portion will | |
| indicate a write and MP violation (BPV is inhibited by the MEBEN signal). If | |
| MEM and MP violations occur concurrently, the MEM violation takes precedence, | |
| as the SFS and SFC instructions test the MEV flip-flop. | |
| The lower bound of protected memory is passed in the "plb" argument. This | |
| must be either 0 or 2. All violations are qualified by the MPCND signal, | |
| which responds to the lower bound. Therefore, if the lower bound is 2, and | |
| if the part below the base-page fence is unmapped, or if the base page is | |
| write-protected, then a MEM violation will occur only if the access is not to | |
| locations 0 or 1. The instruction set firmware uses a lower bound of 0 for | |
| JMP, JLY, and JPY (and for JSB with W5 out), and of 2 for DJP, SJP, UJP, JRS, | |
| and .GOTO (and JSB with W5 in). | |
| Finally, all violations are inhibited if MP is off (mp_control is CLEAR), and | |
| MEM violations are inhibited if the MEM is disabled. | |
| */ | |
| void mp_dms_jmp (uint32 va, uint32 plb) | |
| { | |
| HP_WORD violation = 0; | |
| uint32 pgn = VA_GETPAG (va); /* get page number */ | |
| if (mp_control) { /* MP on? */ | |
| if (dms_enb) { /* MEM on? */ | |
| if (dms_map [dms_ump + pgn] & WRPROT) /* page write protected? */ | |
| violation = MVI_WPR; /* write violation occurred */ | |
| if (!is_mapped (va) && (va >= plb)) /* base page target? */ | |
| violation = violation | MVI_BPG; /* base page violation occurred */ | |
| if (violation) /* any violation? */ | |
| dms_viol (va, violation); /* signal MEM violation */ | |
| } | |
| if ((va >= plb) && (va < mp_fence)) /* jump under fence? */ | |
| MP_ABORT (va); /* signal MP violation */ | |
| } | |
| return; | |
| } | |
| /* CPU local SCP support routine declarations */ | |
| /* CPU (SC 0) I/O signal handler. | |
| I/O instructions for select code 0 manipulate the interrupt system. STF and | |
| CLF turn the interrupt system on and off, and SFS and SFC test the state of | |
| the interrupt system. When the interrupt system is off, only power fail and | |
| parity error interrupts are allowed. | |
| A PON reset initializes certain CPU registers. The 1000 series does a | |
| microcoded memory clear and leaves the T and P registers set as a result. | |
| Front-panel PRESET performs additional initialization. We also handle MEM | |
| preset here. | |
| Implementation notes: | |
| 1. An IOI signal reads the floating I/O bus (0 on all machines). | |
| 2. A CLC 0 issues CRS to all devices, not CLC. While most cards react | |
| identically to CRS and CLC, some do not, e.g., the 12566B when used as an | |
| I/O diagnostic target. | |
| 3. RTE uses the undocumented SFS 0,C instruction to both test and turn off | |
| the interrupt system. This is confirmed in the "RTE-6/VM Technical | |
| Specifications" manual (HP 92084-90015), section 2.3.1 "Process the | |
| Interrupt", subsection "A.1 $CIC": | |
| "Test to see if the interrupt system is on or off. This is done with | |
| the SFS 0,C instruction. In either case, turn it off (the ,C does | |
| it)." | |
| ...and in section 5.8, "Parity Error Detection": | |
| "Because parity error interrupts can occur even when the interrupt | |
| system is off, the code at $CIC must be able to save the complete | |
| system status. The major hole in being able to save the complete state | |
| is in saving the interrupt system state. In order to do this in both | |
| the 21MX and the 21XE the instruction 103300 was used to both test the | |
| interrupt system and turn it off." | |
| 4. Select code 0 cannot interrupt, so there is no SIR handler. | |
| 5. To guarantee proper initialization, the 12920A terminal multiplexer | |
| requires that the Control Reset (CRS) I/O signal be asserted for a | |
| minimum of 100 milliseconds. In practice, this is achieved by executing | |
| 131,072 (128K) CLC 0 instructions in a tight loop. This is not necessary | |
| in simulation, and in fact is detrimental, as 262,000+ trace lines will | |
| be written for each device that enables IOBUS tracing. To avoid this, | |
| consecutive CLC 0 operations after the first are omitted. This is | |
| detected by checking the select code and signal set of the last I/O | |
| operation. | |
| */ | |
| static uint32 cpuio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) | |
| { | |
| static IOCYCLE last_signal_set = ioNONE; /* the last set of I/O signals processed */ | |
| uint32 sc; | |
| IOSIGNAL signal; | |
| IOCYCLE working_set = signal_set; /* no SIR handler needed */ | |
| while (working_set) { | |
| signal = IONEXT (working_set); /* isolate next signal */ | |
| switch (signal) { /* dispatch I/O signal */ | |
| case ioCLF: /* clear flag flip-flop */ | |
| ion = CLEAR; /* turn interrupt system off */ | |
| break; | |
| case ioSTF: /* set flag flip-flop */ | |
| ion = SET; /* turn interrupt system on */ | |
| break; | |
| case ioSFC: /* skip if flag is clear */ | |
| setSKF (!ion); /* skip if interrupt system is off */ | |
| break; | |
| case ioSFS: /* skip if flag is set */ | |
| setSKF (ion); /* skip if interrupt system is on */ | |
| break; | |
| case ioIOI: /* I/O input */ | |
| stat_data = IORETURN (SCPE_OK, 0); /* returns 0 */ | |
| break; | |
| case ioPON: /* power on normal */ | |
| AR = 0; /* clear A register */ | |
| BR = 0; /* clear B register */ | |
| SR = 0; /* clear S register */ | |
| TR = 0; /* clear T register */ | |
| E = 1; /* set E register */ | |
| if (is_1000) { /* 1000 series? */ | |
| memset (M, 0, (uint32) MEMSIZE * 2); /* zero allocated memory */ | |
| MR = 0077777; /* set M register */ | |
| PR = 0100000; /* set P register */ | |
| } | |
| else { /* 21xx series */ | |
| MR = 0; /* clear M register */ | |
| PR = 0; /* clear P register */ | |
| } | |
| break; | |
| case ioPOPIO: /* power-on preset to I/O */ | |
| O = 0; /* clear O register */ | |
| ion = CLEAR; /* turn off interrupt system */ | |
| ion_defer = FALSE; /* clear interrupt deferral */ | |
| dms_enb = 0; /* turn DMS off */ | |
| dms_ump = 0; /* init to system map */ | |
| dms_sr = 0; /* clear status register and BP fence */ | |
| dms_vr = 0; /* clear violation register */ | |
| mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */ | |
| break; | |
| case ioCLC: /* clear control flip-flop */ | |
| if (last_select_code != 0 /* if the last I/O instruction */ | |
| || (last_signal_set & ioCLC) == 0) /* was not a CLC 0 */ | |
| for (sc = CRSDEV; sc <= MAXDEV; sc++) /* then assert the CRS signal */ | |
| if (devs [sc] != NULL) /* to all occupied I/O slots */ | |
| io_dispatch (sc, ioCRS, 0); /* from select code 6 and up */ | |
| break; | |
| default: /* all other signals */ | |
| break; /* are ignored */ | |
| } | |
| working_set = working_set & ~signal; /* remove current signal from set */ | |
| } | |
| last_signal_set = signal_set; /* save the current signal set for the next call */ | |
| return stat_data; | |
| } | |
| /* Overflow/S-register (SC 1) I/O signal handler. | |
| Flag instructions directed to select code 1 manipulate the overflow (O) | |
| register. Input and output instructions access the switch (S) register. On | |
| the 2115 and 2116, there is no S-register indicator, so it is effectively | |
| read-only. On the other machines, a front-panel display of the S-register is | |
| provided. On all machines, front-panel switches are provided to set the | |
| contents of the S register. | |
| Implementation notes: | |
| 1. Select code 1 cannot interrupt, so there is no SIR handler. | |
| */ | |
| static uint32 ovflio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) | |
| { | |
| IOSIGNAL signal; | |
| IOCYCLE working_set = signal_set; /* no SIR handler needed */ | |
| while (working_set) { | |
| signal = IONEXT (working_set); /* isolate next signal */ | |
| switch (signal) { /* dispatch I/O signal */ | |
| case ioCLF: /* clear flag flip-flop */ | |
| O = 0; /* clear overflow */ | |
| break; | |
| case ioSTF: /* set flag flip-flop */ | |
| O = 1; /* set overflow */ | |
| break; | |
| case ioSFC: /* skip if flag is clear */ | |
| setSKF (!O); /* skip if overflow is clear */ | |
| break; | |
| case ioSFS: /* skip if flag is set */ | |
| setSKF (O); /* skip if overflow is set */ | |
| break; | |
| case ioIOI: /* I/O input */ | |
| stat_data = IORETURN (SCPE_OK, SR); /* read switch register value */ | |
| break; | |
| case ioIOO: /* I/O output */ | |
| if ((UNIT_CPU_MODEL != UNIT_2116) && /* no S register display on */ | |
| (UNIT_CPU_MODEL != UNIT_2115)) /* 2116 and 2115 machines */ | |
| SR = IODATA (stat_data); /* write S register value */ | |
| break; | |
| default: /* all other signals */ | |
| break; /* are ignored */ | |
| } | |
| working_set = working_set & ~signal; /* remove current signal from set */ | |
| } | |
| return stat_data; | |
| } | |
| /* Power fail (SC 4) I/O signal handler. | |
| Power fail detection is standard on 2100 and 1000 systems and is optional on | |
| 21xx systems. Power fail recovery is standard on the 2100 and optional on | |
| the others. Power failure or restoration will cause an interrupt on select | |
| code 4. The direction of power change (down or up) can be tested by SFC. | |
| We do not implement power fail under simulation. However, the central | |
| interrupt register (CIR) is always read by an IOI directed to select code 4. | |
| */ | |
| static uint32 pwrfio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) | |
| { | |
| IOSIGNAL signal; | |
| IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ | |
| while (working_set) { | |
| signal = IONEXT (working_set); /* isolate next signal */ | |
| switch (signal) { /* dispatch I/O signal */ | |
| case ioSTC: /* set control flip-flop */ | |
| break; /* reinitializes power fail */ | |
| case ioCLC: /* clear control flip-flop */ | |
| break; /* reinitializes power fail */ | |
| case ioSFC: /* skip if flag is clear */ | |
| break; /* skips if power fail occurred */ | |
| case ioIOI: /* I/O input */ | |
| stat_data = IORETURN (SCPE_OK, CIR); /* input CIR value */ | |
| break; | |
| default: /* all other signals */ | |
| break; /* are ignored */ | |
| } | |
| working_set = working_set & ~signal; /* remove current signal from set */ | |
| } | |
| return stat_data; | |
| } | |
| /* Examine a CPU memory location. | |
| This routine is called by the SCP to examine memory. The routine retrieves | |
| the memory location indicated by "address" as modified by any "switches" that | |
| were specified on the command line and returns the value in the first element | |
| of "eval_array". | |
| On entry, the "map_address" routine is called to translate a logical address | |
| to a physical address. If "switches" includes SIM_SW_REST or "-N", then the | |
| address is a physical address, and the routine returns the address unaltered. | |
| Otherwise, the address is a logical address interpreted in the context of the | |
| translation map implied by the specified switch and is mapped to a physical | |
| address. If memory expansion is disabled but a map is specified, then the | |
| command is rejected. Otherwise if the resulting address is beyond the | |
| current memory limit, or if mapping is implied or explicit but the address | |
| specified is outside of the logical address space, "address space exceeded" | |
| status is returned. | |
| Otherwise, the value is obtained from memory or the A/B register and returned | |
| in the first word of "eval_array." | |
| */ | |
| static t_stat cpu_examine (t_value *eval_array, t_addr address, UNIT *uptr, int32 switches) | |
| { | |
| uint32 index; | |
| index = map_address ((HP_WORD) address, switches); /* map the supplied address as directed by the switches */ | |
| if (dms_enb == 0 && switches & ALL_MAPMODES) /* if the MEM is disabled but a mapping mode was given */ | |
| return SCPE_NOFNC; /* then the command is not allowed */ | |
| else if (index >= MEMSIZE) /* otherwise if the address is beyond the memory limit */ | |
| return SCPE_NXM; /* then return non-existent memory status */ | |
| else if (eval_array == NULL) /* otherwise if the value pointer was not supplied */ | |
| return SCPE_IERR; /* then return internal error status */ | |
| else if (switches & SIM_SW_REST || index >= 2) /* otherwise if restoring or memory is being accessed */ | |
| *eval_array = (t_value) M [index]; /* then return the memory value */ | |
| else /* otherwise */ | |
| *eval_array = (t_value) ABREG [index]; /* return the A or B register value */ | |
| return SCPE_OK; /* return success status */ | |
| } | |
| /* Deposit to a CPU memory location. | |
| This routine is called by the SCP to deposit to memory. The routine stores | |
| the supplied "value" into memory at the "address" location as modified by any | |
| "switches" that were specified on the command line. | |
| On entry, the "map_address" routine is called to translate a logical address | |
| to a physical address. If "switches" includes SIM_SW_REST or "-N", then the | |
| address is a physical address, and the routine returns the address unaltered. | |
| Otherwise, the address is a logical address interpreted in the context of the | |
| translation map implied by the specified switch and is mapped to a physical | |
| address. If memory expansion is disabled but a map is specified, then the | |
| command is rejected. Otherwise if the resulting address is beyond the | |
| current memory limit, or if mapping is implied or explicit but the address | |
| specified is outside of the logical address space, "address space exceeded" | |
| status is returned. | |
| Otherwise, the value is stored into memory or the A/B register. | |
| */ | |
| static t_stat cpu_deposit (t_value value, t_addr address, UNIT *uptr, int32 switches) | |
| { | |
| uint32 index; | |
| index = map_address ((HP_WORD) address, switches); /* map the supplied address as directed by the switches */ | |
| if (dms_enb == 0 && switches & ALL_MAPMODES) /* if the MEM is disabled but a mapping mode was given */ | |
| return SCPE_NOFNC; /* then the command is not allowed */ | |
| else if (index >= MEMSIZE) /* otherwise if the address is beyond the memory limit */ | |
| return SCPE_NXM; /* then return non-existent memory status */ | |
| else if (switches & SIM_SW_REST || index >= 2) /* otherwise if restoring or memory is being accessed */ | |
| M [index] = (MEMORY_WORD) value & DV_MASK; /* then write the memory value */ | |
| else /* otherwise */ | |
| ABREG [index] = (HP_WORD) value & DV_MASK; /* write the A or B register value */ | |
| return SCPE_OK; /* return success status */ | |
| } | |
| /* Reset the CPU. | |
| This routine is called for a RESET, RESET CPU, RUN, or BOOT CPU command. It | |
| is the simulation equivalent of an initial power-on condition (corresponding | |
| to PON, POPIO, and CRS signal assertion in the CPU) or a front-panel PRESET | |
| button press (corresponding to POPIO and CRS assertion). SCP delivers a | |
| power-on reset to all devices when the simulator is started. | |
| If this is the first call after simulator startup, the initial memory array | |
| is allocated, the default CPU and memory size configuration is set, and the | |
| SCP-required program counter pointer is set to point to the REG array element | |
| corresponding to the P register. In addition, the loader ROM sockets of the | |
| 1000-series CPUs are populated with the initial ROM set, and the Basic Binary | |
| Loader (BBL) is installed in protected memory (the upper 64 words of the | |
| defined memory size). | |
| Implementation notes: | |
| 1. Setting the sim_PC value at run time accommodates changes in the register | |
| order automatically. A fixed setting runs the risk of it not being | |
| updated if a change in the register order is made. | |
| 2. The initial set of installed HP 1000 boot loader ROMs is: | |
| Socket ROM Boot Device | |
| ------ ------ ------------------------ | |
| 0 12992K 2748 Paper Tape Reader | |
| 1 12992A 7900 or 2883 Disc Drive | |
| 2 12992D 7970 Magnetic Tape Drive | |
| 3 12992B 7905/06/20/25 Disc Drive | |
| */ | |
| static t_stat cpu_reset (DEVICE *dptr) | |
| { | |
| if (M == NULL) { /* if this is the initial call after simulator startup */ | |
| pcq_r = find_reg ("PCQ", NULL, dptr); /* then get the PC queue pointer */ | |
| if (pcq_r == NULL) /* if the PCQ register is not present */ | |
| return SCPE_IERR; /* then something is seriously wrong */ | |
| else /* otherwise */ | |
| pcq_r->qptr = 0; /* initialize the register's queue pointer */ | |
| M = (MEMORY_WORD *) calloc (PASIZE, /* allocate and zero the main memory array */ | |
| sizeof (MEMORY_WORD)); /* to the maximum configurable size */ | |
| if (M == NULL) /* if the allocation failed */ | |
| return SCPE_MEM; /* then report a "Memory exhausted" error */ | |
| else { /* otherwise perform one-time initialization */ | |
| for (sim_PC = dptr->registers; /* find the P register entry */ | |
| sim_PC->loc != &PR && sim_PC->loc != NULL; /* in the register array */ | |
| sim_PC++); /* for the SCP interface */ | |
| if (sim_PC == NULL) /* if the P register entry is not present */ | |
| return SCPE_NXREG; /* then there is a serious problem! */ | |
| MEMSIZE = 32768; /* set the initial memory size */ | |
| set_model (NULL, UNIT_2116, NULL, NULL); /* and the initial CPU model */ | |
| loader_rom [0] = find_dev ("PTR"); /* install the 12992K ROM in socket 0 */ | |
| loader_rom [1] = find_dev ("DQC"); /* and the 12992A ROM in socket 1 */ | |
| loader_rom [2] = find_dev ("MSC"); /* and the 12992D ROM in socket 2 */ | |
| loader_rom [3] = find_dev ("DS"); /* and the 12992B ROM in socket 3 */ | |
| loader_rom [0]->boot (0, loader_rom [0]); /* install the BBL via the paper tape reader boot routine */ | |
| set_loader (NULL, FALSE, NULL, NULL); /* and then disable the loader, which was enabled */ | |
| } | |
| } | |
| if (sim_switches & SWMASK ('P')) /* if this is a power-on reset */ | |
| IOPOWERON (&cpu_dib); /* then issue the PON signal to the CPU */ | |
| else /* otherwise */ | |
| IOPRESET (&cpu_dib); /* issue a PRESET */ | |
| sim_brk_dflt = SWMASK ('N'); /* the default breakpoint type is "nomap" as MEM is disabled */ | |
| return SCPE_OK; | |
| } | |
| /* Device boot routine. | |
| This routine is called by the BOOT CPU and LOAD CPU commands to copy the | |
| specified boot loader ROM program into the upper 64 words of the logical | |
| address space. It is equivalent to pressing the IBL (Initial Binary Loader) | |
| button on the front panel of a 1000 M/E/F-Series CPU. | |
| On entry, the S register must be set to indicate the specific boot loader ROM | |
| and the associated device select code to be copied, as follows: | |
| 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| | ROM # | - - | select code | - - - - - - | | |
| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| Bits 15-14 select one of four loader ROM sockets on the CPU board that may | |
| contain ROMs. If the specified socket does, the contents of the ROM are | |
| copied into the upper 64 words of memory and configured to use the specified | |
| select code. The unspecified bits of the S register are available for use by | |
| the bootstrap program. | |
| If the select code is less than 10 octal, the loader is not copied, and the | |
| O (overflow) register is set to 1. A successful copy and configuration | |
| clears the O register. | |
| The 21xx-series CPUs do not provide the IBL function. If this routine is | |
| invoked while the CPU is configured as one of these machines, the command is | |
| rejected. | |
| Implementation notes: | |
| 1. In hardware, a non-existent ROM (i.e., an empty socket) reads as though | |
| all words contain 177777 octal. This would result in the loader area of | |
| memory containing 62 all-ones words, followed by a word set to 177777 + | |
| SC - 000010, where SC is the configured select code, followed by a word | |
| set to the negative starting address of the loader. This is not | |
| simulated; instead, an attempt to boot from an empty socket is rejected | |
| with "Command not allowed." | |
| */ | |
| static t_stat cpu_boot (int32 unitno, DEVICE *dptr) | |
| { | |
| const int32 select_code = IBL_SC (SR); /* the select code from S register bits 11-6 */ | |
| const int32 rom_socket = IBL_ROM (SR); /* the ROM socket number from S register bits 15-14 */ | |
| if (is_1000) /* if this is a 1000-series CPU */ | |
| if (select_code < VARDEV) { /* then if the select code is invalid */ | |
| O = 1; /* then set the overflow register */ | |
| return SCPE_ARG; /* and reject the IBL with "Invalid argument" */ | |
| } | |
| else if (loader_rom [rom_socket] == NULL) /* otherwise if the ROM socket is empty */ | |
| return SCPE_NXDEV; /* then reject with "Non-existent device" */ | |
| else { /* otherwise */ | |
| O = 0; /* clear overflow to indicate a good IBL */ | |
| return loader_rom [rom_socket]->boot (select_code, NULL); /* and copy the ROM into memory */ | |
| } | |
| else /* otherwise this is a 21xx machine */ | |
| return SCPE_NOFNC; /* and IBL isn't supported */ | |
| } | |
| /* Set the CPU simulation stop conditions. | |
| This validation routine is called to configure the set of CPU stop | |
| conditions. The "option" parameter is 0 to clear the stops, 1 to set the | |
| stops, and 2 to set the indirect chain length limit. "cptr" points to the | |
| first character of the name of the stop to be cleared or set. The unit and | |
| description pointers are not used. | |
| The routine processes commands of the form: | |
| SET CPU STOP | |
| SET CPU STOP=<stopname>[;<stopname>...] | |
| SET CPU NOSTOP | |
| SET CPU NOSTOP=<stopname>[;<stopname>...] | |
| SET CPU INDIR=<limit> | |
| The valid <stopname>s are contained in the "cpu_stop" table. If names are | |
| not specified, all stop conditions are enabled or disabled. | |
| Implementation notes: | |
| 1. The maximum indirect limit value is 32K, as an indirect chain cannot | |
| exceed the logical memory size without being in a loop. | |
| */ | |
| static t_stat set_stops (UNIT *uptr, int32 option, CONST char *cptr, void *desc) | |
| { | |
| char gbuf [CBUFSIZE]; | |
| t_stat status; | |
| uint32 stop; | |
| if (cptr == NULL) /* if there are no arguments */ | |
| if (option == 0) /* then if we're clearing the stops */ | |
| for (stop = 0; cpu_stop [stop].name != NULL; stop++) /* then loop through the flags */ | |
| *cpu_stop [stop].status = SCPE_OK; /* and clear each stop status */ | |
| else if (option == 1) /* otherwise if we're setting the stops */ | |
| for (stop = 0; cpu_stop [stop].name != NULL; stop++) /* then loop through the flags */ | |
| *cpu_stop [stop].status = cpu_stop [stop].value; /* and set each stop status */ | |
| else /* otherwise */ | |
| return SCPE_MISVAL; /* report the missing indirect limit value */ | |
| else if (*cptr == '\0') /* otherwise if the argument is empty */ | |
| return SCPE_MISVAL; /* then report the missing value */ | |
| else if (option == 2) { /* otherwise if we're setting the indirect limit */ | |
| stop = (uint32) get_uint (cptr, 10, LA_MAX + 1, &status); /* then parse the limit value */ | |
| if (status != SCPE_OK) /* if a parsing error occurred */ | |
| return status; /* then return the error status */ | |
| else /* otherwise */ | |
| indirect_limit = stop; /* set the indirect limit */ | |
| } | |
| else /* otherwise at least one stop argument is present */ | |
| while (*cptr) { /* loop through the arguments */ | |
| cptr = get_glyph (cptr, gbuf, ';'); /* get the next argument */ | |
| for (stop = 0; cpu_stop [stop].name != NULL; stop++) /* loop through the flags */ | |
| if (strcmp (cpu_stop [stop].name, gbuf) == 0) { /* and if the argument matches */ | |
| if (option == 1) /* then if it's a STOP argument */ | |
| *cpu_stop [stop].status = cpu_stop [stop].value; /* then set the stop status */ | |
| else /* otherwise it's a NOSTOP argument */ | |
| *cpu_stop [stop].status = SCPE_OK; /* so clear the stop status */ | |
| break; /* this argument has been processed */ | |
| } | |
| if (cpu_stop [stop].name == NULL) /* if the argument was not found */ | |
| return SCPE_ARG; /* then report it */ | |
| } | |
| return SCPE_OK; /* the stops were successfully processed */ | |
| } | |
| /* Change the CPU memory size. | |
| This validation routine is called to configure the CPU memory size. The | |
| "new_size" parameter is set to the size desired and will be one of the | |
| discrete sizes supported by the simulator. The "uptr" parameter points to | |
| the CPU unit and is used to obtain the CPU model. The other parameters are | |
| not used. | |
| The routine processes commands of the form: | |
| SET [-F] CPU <memsize> | |
| If the new memory size is larger than the supported size for the CPU model | |
| currently selected, the routine returns an error. If the new size is smaller | |
| than the previous size, and if the area that would be lost contains non-zero | |
| data, the user is prompted to confirm that memory should be truncated. If | |
| the user denies the request, the change is rejected. Otherwise, the new size | |
| is set. The user may omit the confirmation request and force truncation by | |
| specifying the "-F" switch on the command line. | |
| On a 21xx CPU, the last 64 words in memory are reserved for the binary | |
| loader. Before changing the memory size, the current loader is copied to the | |
| shadow RAM to preserve any manual changes that were made. Then the new | |
| memory size is set, with the beginning of the loader area set as the first | |
| word of non-existent memory. | |
| Finally, non-existent memory is zeroed, so that the mem_read routine does not | |
| need any special handling for addresses beyond the end of defined memory. | |
| Implementation notes: | |
| 1. In hardware, reads from non-existent memory return zero, and writes are | |
| ignored. In simulation, the largest possible memory is instantiated and | |
| initialized to zero. Therefore, only writes need to be checked against | |
| memory size. | |
| 2. On the 21xx machines, doing SET CPU LOADERDISABLE decreases available | |
| memory size by 64 words. | |
| */ | |
| static t_stat set_size (UNIT *uptr, int32 new_size, CONST char *cptr, void *desc) | |
| { | |
| static CONST char confirm [] = "Really truncate memory [N]?"; | |
| uint32 i; | |
| uint32 old_size = (uint32) MEMSIZE; /* current memory size */ | |
| const uint32 model = CPU_MODEL_INDEX; /* the current CPU model index */ | |
| if ((uint32) new_size > cpu_features [model].maxmem) /* if the new memory size is not supported on current model */ | |
| return SCPE_NOFNC; /* then report the error */ | |
| if (!(sim_switches & SWMASK ('F')) /* if truncation is not explicitly forced */ | |
| && ! mem_is_empty (new_size) /* and the truncated part is not empty */ | |
| && get_yn (confirm, FALSE) == FALSE) /* and the user denies confirmation */ | |
| return SCPE_INCOMP; /* then abort the command */ | |
| if (is_1000) /* loader unsupported */ | |
| MEMSIZE = fwanxm = new_size; /* set new memory size */ | |
| else { /* 21xx CPU? */ | |
| set_loader (uptr, FALSE, NULL, NULL); /* save loader to shadow RAM */ | |
| MEMSIZE = new_size; /* set new memory size */ | |
| fwanxm = (uint32) MEMSIZE - IBL_SIZE; /* reserve memory for loader */ | |
| } | |
| for (i = fwanxm; i < old_size; i++) /* zero non-existent memory */ | |
| M [i] = 0; | |
| return SCPE_OK; | |
| } | |
| /* Change CPU models. | |
| For convenience, MP and DMA are typically enabled if available; they may be | |
| disabled subsequently if desired. Note that the 2114 supports only one DMA | |
| channel (channel 1). All other models support two channels. | |
| Validation: | |
| - Sets standard equipment and convenience features. | |
| - Changes DMA device name to DCPC if 1000 is selected. | |
| - Enforces maximum memory allowed (doesn't change otherwise). | |
| - Disables loader on 21xx machines. | |
| Implementation notes: | |
| 1. "cpu_configuration" is used by the symbolic examine and deposit routines | |
| and instruction tracing to determine whether the firmware implementing a | |
| given opcode is present. It is a copy of the CPU unit option flags with | |
| the encoded CPU model decoded into model flag bits. This allows a simple | |
| (and fast) AND operation with a firmware feature word to determine | |
| applicability, saving the multiple masks and comparisons that would | |
| otherwise be required. | |
| Additionally, the configuration word has the unit CPU model bits set on | |
| permanently to permit a base-set feature test for those CPUs that have no | |
| options currently enabled (at least one non-option bit must be on for the | |
| test to succeed, and the model bits are not otherwise used). | |
| */ | |
| static t_stat set_model (UNIT *uptr, int32 new_model, CONST char *cptr, void *desc) | |
| { | |
| const uint32 old_family = UNIT_CPU_FAMILY; /* current CPU type */ | |
| const uint32 new_family = new_model & UNIT_FAMILY_MASK; /* new CPU family */ | |
| const uint32 new_index = new_model >> UNIT_V_CPU; /* new CPU model index */ | |
| uint32 new_memsize; | |
| t_stat result; | |
| if (MEMSIZE > cpu_features [new_index].maxmem) /* if the current memory size is too large for the new model */ | |
| new_memsize = cpu_features [new_index].maxmem; /* then set it to the maximum size supported */ | |
| else /* otherwise */ | |
| new_memsize = (uint32) MEMSIZE; /* leave it unchanged */ | |
| result = set_size (uptr, new_memsize, NULL, NULL); /* set the new memory size */ | |
| if (result == SCPE_OK) { /* if the change succeeded */ | |
| cpu_configuration = cpu_features [new_index].typ & UNIT_OPTS /* then set the typical options */ | |
| | UNIT_MODEL_MASK /* and the base model bits */ | |
| | 1u << new_index; /* and the new CPU model flag */ | |
| cpu_unit.flags = cpu_unit.flags & ~UNIT_OPTS /* enable the typical features */ | |
| | cpu_features [new_index].typ & UNIT_OPTS; /* for the new model */ | |
| if (cpu_features [new_index].typ & UNIT_MP) /* MP in typ config? */ | |
| mp_dev.flags &= ~DEV_DIS; /* enable it */ | |
| else | |
| mp_dev.flags |= DEV_DIS; /* disable it */ | |
| if (cpu_features[new_index].opt & UNIT_MP) /* MP an option? */ | |
| mp_dev.flags |= DEV_DISABLE; /* make it alterable */ | |
| else | |
| mp_dev.flags &= ~DEV_DISABLE; /* make it unalterable */ | |
| if (cpu_features [new_index].typ & UNIT_DMA) { /* DMA in typ config? */ | |
| dma1_dev.flags &= ~DEV_DIS; /* enable DMA channel 1 */ | |
| if (new_model == UNIT_2114) /* 2114 has only one channel */ | |
| dma2_dev.flags |= DEV_DIS; /* disable channel 2 */ | |
| else /* all others have two channels */ | |
| dma2_dev.flags &= ~DEV_DIS; /* enable it */ | |
| } | |
| else { | |
| dma1_dev.flags |= DEV_DIS; /* disable channel 1 */ | |
| dma2_dev.flags |= DEV_DIS; /* disable channel 2 */ | |
| } | |
| if (cpu_features [new_index].opt & UNIT_DMA) { /* DMA an option? */ | |
| dma1_dev.flags |= DEV_DISABLE; /* make it alterable */ | |
| if (new_model == UNIT_2114) /* 2114 has only one channel */ | |
| dma2_dev.flags &= ~DEV_DISABLE; /* make it unalterable */ | |
| else /* all others have two channels */ | |
| dma2_dev.flags |= DEV_DISABLE; /* make it alterable */ | |
| } | |
| else { /* otherwise DMA is not available */ | |
| dma1_dev.flags &= ~DEV_DISABLE; /* make it unalterable */ | |
| dma2_dev.flags &= ~DEV_DISABLE; /* make it unalterable */ | |
| } | |
| if ((old_family == UNIT_FAMILY_1000) && /* if current family is 1000 */ | |
| (new_family == UNIT_FAMILY_21XX)) { /* and new family is 21xx */ | |
| deassign_device (&dma1_dev); /* delete DCPC names */ | |
| deassign_device (&dma2_dev); | |
| } | |
| else if ((old_family == UNIT_FAMILY_21XX) && /* otherwise if current family is 21xx */ | |
| (new_family == UNIT_FAMILY_1000)) { /* and new family is 1000 */ | |
| assign_device (&dma1_dev, "DCPC1"); /* change DMA device name */ | |
| assign_device (&dma2_dev, "DCPC2"); /* to DCPC for familiarity */ | |
| } | |
| if (!(cpu_features [new_index].typ & UNIT_DMS)) /* if DMS is not being enabled */ | |
| dms_enb = 0; /* then disable MEM mapping */ | |
| is_1000 = (new_family == UNIT_FAMILY_1000); /* set model */ | |
| if (is_1000) | |
| fwanxm = (uint32) MEMSIZE; /* loader reserved only for 21xx */ | |
| else /* 2100 or 211x */ | |
| fwanxm = (uint32) MEMSIZE - IBL_SIZE; /* reserve memory for loader */ | |
| } | |
| return result; | |
| } | |
| /* Change a CPU option. | |
| This validation routine is called to configure the option set for the current | |
| CPU model. The "option" parameter is set to the option desired and will be | |
| one of the unit option flags. The "uptr" parameter points to the CPU unit | |
| and is used to obtain the CPU model. The other parameters are not used. | |
| The routine processes commands of the form: | |
| SET CPU <option>[,<option>...] | |
| The option must be valid for the current CPU model, or the command is | |
| rejected. | |
| Implementation notes: | |
| 1. "cpu_configuration" is used by the symbolic examine and deposit routines | |
| and instruction tracing to determine whether the firmware implementing a | |
| given opcode is present. It is a copy of the CPU unit option flags with | |
| the encoded CPU model decoded into model flag bits. This allows a simple | |
| (and fast) AND operation with a firmware feature word to determine | |
| applicability, saving the multiple masks and comparisons that would | |
| otherwise be required. | |
| */ | |
| static t_stat set_option (UNIT *uptr, int32 option, CONST char *cptr, void *desc) | |
| { | |
| uint32 model = CPU_MODEL_INDEX; /* current CPU model index */ | |
| if ((cpu_features [model].opt & option) == 0) /* option supported? */ | |
| return SCPE_NOFNC; /* no */ | |
| if (UNIT_CPU_TYPE == UNIT_TYPE_2100) { | |
| if ((option == UNIT_FP) || (option == UNIT_FFP)) /* 2100 IOP and FP/FFP options */ | |
| uptr->flags &= ~UNIT_IOP; /* are mutually exclusive */ | |
| else if (option == UNIT_IOP) | |
| uptr->flags &= ~(UNIT_FP | UNIT_FFP); | |
| if (option == UNIT_FFP) /* 2100 FFP option requires FP */ | |
| uptr->flags |= UNIT_FP; | |
| } | |
| cpu_configuration = cpu_configuration & ~UNIT_OPTS /* update the CPU configuration */ | |
| | uptr->flags & UNIT_OPTS; /* with the revised option settings */ | |
| if (option & UNIT_EMA_VMA) /* if EMA or VMA is being set */ | |
| cpu_configuration &= ~UNIT_EMA_VMA; /* then remove both as they are mutually exclusive */ | |
| cpu_configuration |= option; /* include the new setting */ | |
| return SCPE_OK; | |
| } | |
| /* Clear a CPU option. | |
| Validation: | |
| - Checks that the current CPU model supports the option selected. | |
| - Clears flag from unit structure (we are processing MTAB_XTD entries). | |
| - If CPU is 2100, ensures that FFP is disabled if FP disabled | |
| (FP is required for FFP installation). | |
| Implementation notes: | |
| 1. "cpu_configuration" is used by the symbolic examine and deposit routines | |
| and instruction tracing to determine whether the firmware implementing a | |
| given opcode is present. It is a copy of the CPU unit option flags with | |
| the encoded CPU model decoded into model flag bits. This allows a simple | |
| (and fast) AND operation with a firmware feature word to determine | |
| applicability, saving the multiple masks and comparisons that would | |
| otherwise be required. | |
| */ | |
| t_bool clear_option (UNIT *uptr, int32 option, CONST char *cptr, void *desc) | |
| { | |
| uint32 model = CPU_MODEL_INDEX; /* current CPU model index */ | |
| if ((cpu_features[model].opt & option) == 0) /* option supported? */ | |
| return SCPE_NOFNC; /* no */ | |
| uptr->flags = uptr->flags & ~option; /* disable option */ | |
| if (option == UNIT_DMS) /* if DMS is being disabled */ | |
| dms_enb = 0; /* then disable MEM mapping */ | |
| if ((UNIT_CPU_TYPE == UNIT_TYPE_2100) && /* disabling 2100 FP? */ | |
| (option == UNIT_FP)) | |
| uptr->flags = uptr->flags & ~UNIT_FFP; /* yes, so disable FFP too */ | |
| cpu_configuration = cpu_configuration & ~UNIT_OPTS /* update the CPU configuration */ | |
| | uptr->flags & UNIT_OPTS; /* with the revised option settings */ | |
| return SCPE_OK; | |
| } | |
| /* 21xx loader enable/disable function. | |
| The 21xx CPUs store their initial binary loaders in the last 64 words of | |
| available memory. This memory is protected by a LOADER ENABLE switch on the | |
| front panel. When the switch is off (disabled), main memory effectively ends | |
| 64 locations earlier, i.e., the loader area is treated as non-existent. | |
| Because these are core machines, the loader is retained when system power is | |
| off. | |
| 1000 CPUs do not have a protected loader feature. Instead, loaders are | |
| stored in PROMs and are copied into main memory for execution by the IBL | |
| switch. | |
| Under simulation, we keep both a total configured memory size (MEMSIZE) and a | |
| current configured memory size (fwanxm = "first word address of non-existent | |
| memory"). When the two are equal, the loader is enabled. When the current | |
| size is less than the total size, the loader is disabled. | |
| Disabling the loader copies the last 64 words to a shadow array, zeros the | |
| corresponding memory, and decreases the last word of addressable memory by | |
| 64. Enabling the loader reverses this process. | |
| Disabling may be done manually by user command or automatically when a halt | |
| instruction is executed. Enabling occurs only by user command. This differs | |
| slightly from actual machine operation, which additionally disables the | |
| loader when a manual halt is performed. We do not do this to allow | |
| breakpoints within and single-stepping through the loaders. | |
| */ | |
| static t_stat set_loader (UNIT *uptr, int32 enable, CONST char *cptr, void *desc) | |
| { | |
| static MEMORY_WORD loader [IBL_SIZE]; | |
| uint32 i; | |
| t_bool is_enabled = (fwanxm == MEMSIZE); | |
| if (is_1000 || MEMSIZE == 0) /* valid only for 21xx and for initialized memory */ | |
| return SCPE_NOFNC; | |
| if (is_enabled && (enable == 0)) { /* disable loader? */ | |
| fwanxm = (uint32) MEMSIZE - IBL_SIZE; /* decrease available memory */ | |
| for (i = 0; i < IBL_SIZE; i++) { /* copy loader */ | |
| loader [i] = M [fwanxm + i]; /* from memory */ | |
| M [fwanxm + i] = 0; /* and zero location */ | |
| } | |
| } | |
| else if ((!is_enabled) && (enable == 1)) { /* enable loader? */ | |
| for (i = 0; i < IBL_SIZE; i++) /* copy loader */ | |
| M [fwanxm + i] = loader [i]; /* to memory */ | |
| fwanxm = (uint32) MEMSIZE; /* increase available memory */ | |
| } | |
| return SCPE_OK; | |
| } | |
| /* Change the set of installed loader ROMs. | |
| This validation routine is called to install loader ROMs in the four | |
| available sockets of a 1000-series CPU. The routine processes commands of | |
| the form: | |
| SET CPU ROMS=[<dev0>][;[<dev1>][;[<dev2>][;[<dev3>]]]] | |
| On entry, "cptr" points at the the first character of the ROM list. The | |
| option value and the unit and description pointers are not used. | |
| All four ROM sockets are set for each command. If no devices are specified, | |
| then all sockets are emptied. Otherwise, specifying a valid device name | |
| installs the device loader ROM into the socket corresponding to the position | |
| of the device name in the list. Sockets may be left empty by omitting the | |
| corresponding device name or by supplying fewer than four device names. | |
| Loader ROMs may only be altered if the current CPU model is a 1000-series | |
| machine, and a device must be bootable and have a loader ROM assigned, or the | |
| command will be rejected. A rejected command does not alter any of the ROM | |
| assignments. | |
| Example commands and their effects on the installed ROM sockets follow: | |
| Command Action | |
| --------------------- ------------------------------------------------- | |
| SET CPU ROMS= Remove ROMs from sockets 0-3 | |
| SET CPU ROMS=PTR Install PTR in 0; leave 1-3 empty | |
| SET CPU ROMS=DS;MS Install DS in 0 and MS in 1; leave 2 and 3 empty | |
| SET CPU ROMS=;;DPC Install DPC in 2; leave 0, 1, and 3 empty | |
| SET CPU ROMS=DQC;;;DA Install DQC in 0 and DA in 3; leave 1 and 2 empty | |
| Implementation notes: | |
| 1. Entering "SET CPU ROMS" without an equals sign or list is rejected with a | |
| "Missing value" error. This is to prevent accidental socket clearing | |
| when "SHOW CPU ROMS" was intended. | |
| */ | |
| static t_stat set_roms (UNIT *uptr, int32 option, CONST char *cptr, void *desc) | |
| { | |
| DEVICE *dptr; | |
| char gbuf [CBUFSIZE]; | |
| uint32 socket = 0; | |
| DEVICE *rom [4] = { NULL }; | |
| if (is_1000 == FALSE) /* if the CPU is not a 1000-series unit */ | |
| return SCPE_NOFNC; /* then reject the command */ | |
| else if (cptr == NULL) /* otherwise if the list is not specified */ | |
| return SCPE_MISVAL; /* then report that the list is missing */ | |
| else if (*cptr == '\0') { /* otherwise if the list is null */ | |
| loader_rom [0] = NULL; /* then empty */ | |
| loader_rom [1] = NULL; /* all of the */ | |
| loader_rom [2] = NULL; /* ROM sockets */ | |
| loader_rom [3] = NULL; | |
| } | |
| else { /* otherwise */ | |
| while (*cptr) { /* loop through the arguments */ | |
| cptr = get_glyph (cptr, gbuf, ';'); /* get the next argument */ | |
| if (socket == 4) /* if all four sockets have been set */ | |
| return SCPE_2MARG; /* then reject the command */ | |
| else if (gbuf [0] == '\0') /* otherwise if the device name is omitted */ | |
| rom [socket++] = NULL; /* then empty the corresponding socket */ | |
| else { /* otherwise we have a device name */ | |
| dptr = find_dev (gbuf); /* so find the associated DEVICE pointer */ | |
| if (dptr == NULL) /* if the device name is not valid */ | |
| return SCPE_NXDEV; /* then reject the command */ | |
| else if (dptr->boot == NULL) /* otherwise if it's valid but not bootable */ | |
| return SCPE_NOFNC; /* then reject the command */ | |
| else /* otherwise */ | |
| rom [socket++] = dptr; /* install the boot loader ROM */ | |
| } | |
| } | |
| loader_rom [0] = rom [0]; /* install the ROM set */ | |
| loader_rom [1] = rom [1]; /* now that we have */ | |
| loader_rom [2] = rom [2]; /* a valid */ | |
| loader_rom [3] = rom [3]; /* device list */ | |
| } | |
| return SCPE_OK; /* report that the command succeeded */ | |
| } | |
| /* Change the instruction execution trace criteria. | |
| This validation routine is called to configure the criteria that select | |
| instruction execution tracing. The "option" parameter is 0 to clear and 1 to | |
| set the criteria, and "cptr" points to the first character of the match value | |
| to be set. The unit and description pointers are not used. | |
| The routine processes commands of the form: | |
| SET CPU EXEC=<match>[;<mask>] | |
| SET CPU NOEXEC | |
| If the <mask> value is not supplied, a mask of 177777 octal is used. The | |
| values are entered in the current CPU data radix, which defaults to octal, | |
| unless an override switch is present on the command line. | |
| */ | |
| static t_stat set_exec (UNIT *uptr, int32 option, CONST char *cptr, void *desc) | |
| { | |
| char gbuf [CBUFSIZE]; | |
| uint32 match, mask, radix; | |
| t_stat status; | |
| if (option == 0) /* if this is a NOEXEC request */ | |
| if (cptr == NULL) { /* then if there are no arguments */ | |
| exec_match = D16_UMAX; /* then set the match and mask values */ | |
| exec_mask = 0; /* to prevent matching */ | |
| return SCPE_OK; /* and return success */ | |
| } | |
| else /* otherwise there are extraneous characters */ | |
| return SCPE_2MARG; /* so report that there are too many arguments */ | |
| else if (cptr == NULL || *cptr == '\0') /* otherwise if the EXEC request supplies no arguments */ | |
| return SCPE_MISVAL; /* then report a missing value */ | |
| else { /* otherwise at least one argument is present */ | |
| cptr = get_glyph (cptr, gbuf, ';'); /* so get the match argument */ | |
| if (sim_switches & SWMASK ('O')) /* if an octal override is present */ | |
| radix = 8; /* then parse the value in base 8 */ | |
| else if (sim_switches & SWMASK ('D')) /* otherwise if a decimal override is present */ | |
| radix = 10; /* then parse the value in base 10 */ | |
| else if (sim_switches & SWMASK ('H')) /* otherwise if a hex override is present */ | |
| radix = 16; /* then parse the value in base 16 */ | |
| else /* otherwise */ | |
| radix = cpu_dev.dradix; /* use the current CPU data radix */ | |
| match = (uint32) get_uint (gbuf, radix, D16_UMAX, &status); /* parse the match value */ | |
| if (status != SCPE_OK) /* if a parsing error occurred */ | |
| return status; /* then return the error status */ | |
| else if (*cptr == '\0') { /* otherwise if no more characters are present */ | |
| exec_match = match; /* then set the match value */ | |
| exec_mask = D16_MASK; /* and default the mask value */ | |
| return SCPE_OK; /* and return success */ | |
| } | |
| else { /* otherwise another argument is present */ | |
| cptr = get_glyph (cptr, gbuf, ';'); /* so get the mask argument */ | |
| mask = (uint32) get_uint (gbuf, radix, D16_UMAX, &status); /* parse the mask value */ | |
| if (status != SCPE_OK) /* if a parsing error occurred */ | |
| return status; /* then return the error status */ | |
| else if (*cptr == '\0') /* if no more characters are present */ | |
| if (mask == 0) /* then if the mask value is zero */ | |
| return SCPE_ARG; /* then the match will never succeed */ | |
| else { /* otherwise */ | |
| exec_match = match; /* set the match value */ | |
| exec_mask = mask; /* and the mask value */ | |
| return SCPE_OK; /* and return success */ | |
| } | |
| else /* otherwise extraneous characters are present */ | |
| return SCPE_2MARG; /* so report that there are too many arguments */ | |
| } | |
| } | |
| } | |
| /* Show the CPU simulation stop conditions. | |
| This display routine is called to show the set of CPU stop conditions or the | |
| indirect chain length limit. The "st" parameter is the open output stream, | |
| and "val" is 1 to show the stops and 2 to show the indirect limit. The other | |
| parameters are not used. | |
| To show stops, the routine searches through the stop table for status | |
| variables that are set to values other than SCPE_OK. For each one it finds, | |
| the routine prints the corresponding stop name. If none are found, it | |
| reports that all stops are disabled. | |
| This routine services an extended modifier entry, so it must add the trailing | |
| newline to the output before returning. | |
| */ | |
| static t_stat show_stops (FILE *st, UNIT *uptr, int32 val, CONST void *desc) | |
| { | |
| uint32 stop; | |
| t_bool need_spacer = FALSE; | |
| if (val == 2) /* if the indirect limit is requested */ | |
| fprintf (st, "Limit=%d\n", indirect_limit); /* then show it */ | |
| else { /* otherwise show the enabled stops */ | |
| for (stop = 0; cpu_stop [stop].name != NULL; stop++) /* loop through the set of stops in the table */ | |
| if (*cpu_stop [stop].status != SCPE_OK) { /* if the current stop is enabled */ | |
| if (need_spacer) /* then if a spacer is needed */ | |
| fputc (';', st); /* then add it first */ | |
| else /* otherwise this is the first one reported */ | |
| fputs ("Stop=", st); /* so print the report label */ | |
| fputs (cpu_stop [stop].name, st); /* report the stop name */ | |
| need_spacer = TRUE; /* a spacer will be needed next time */ | |
| } | |
| if (need_spacer) /* if at least one simulation stop was enabled */ | |
| fputc ('\n', st); /* then add the required trailing newline */ | |
| else /* otherwise no enabled stops were found */ | |
| fputs ("Stops disabled\n", st); /* so report that all are disabled */ | |
| } | |
| return SCPE_OK; /* report the success of the display */ | |
| } | |
| /* Display the CPU model and optional loader status. | |
| Loader status is displayed for 21xx models and suppressed for 1000 models. | |
| */ | |
| static t_stat show_model (FILE *st, UNIT *uptr, int32 val, CONST void *desc) | |
| { | |
| fputs ((const char *) desc, st); /* write model name */ | |
| if (! is_1000) /* valid only for 21xx */ | |
| if (fwanxm < MEMSIZE) /* loader area non-existent? */ | |
| fputs (", loader disabled", st); /* yes, so access disabled */ | |
| else | |
| fputs (", loader enabled", st); /* no, so access enabled */ | |
| return SCPE_OK; | |
| } | |
| /* Show the set of installed loader ROMs. | |
| This display routine is called to show the set of installed loader ROMs in | |
| the four available sockets of a 1000-series CPU. On entry, the "st" | |
| parameter is the open output stream. The other parameters are not used. | |
| The routine prints a table of ROMs in this format: | |
| Socket Device ROM | |
| ------ ------- ------ | |
| 0 PTR 12992K | |
| 1 DQC 12992A | |
| 2 DS 12992B | |
| 3 <empty> | |
| If a given socket contains a ROM, the associated device name and HP part | |
| number for the loader ROM are printed. | |
| This routine services an extended modifier entry, so it must add the trailing | |
| newline to the output before returning. | |
| */ | |
| static t_stat show_roms (FILE *st, UNIT *uptr, int32 val, CONST void *desc) | |
| { | |
| struct LOOKUP_TABLE { | |
| char *name; /* device name */ | |
| char suffix; /* ROM part number suffix */ | |
| }; | |
| static const struct LOOKUP_TABLE lookup [] = { /* table of device names and ROM part numbers */ | |
| { "DQC", 'A' }, /* 12992A 7900/7901/2883 Disc Loader */ | |
| { "DS", 'B' }, /* 12992B 7905/7906/7920/7925 Disc Loader */ | |
| { "MSC", 'D' }, /* 12992D 7970 Magnetic Tape Loader */ | |
| { "DPC", 'F' }, /* 12992F 7900/7901 Disc Loader */ | |
| { "DA", 'H' }, /* 12992H 7906H/7920H/7925H/9885 Disc Loader */ | |
| { "IPLI", 'K' }, /* 12992K Paper Tape Loader */ | |
| { "PTR", 'K' }, /* 12992K Paper Tape Loader */ | |
| { NULL, '?' } | |
| }; | |
| CONST char *dname; | |
| uint32 socket, index; | |
| char letter = '?'; | |
| fputc ('\n', st); /* skip a line */ | |
| fputs ("Socket Device ROM\n", st); /* and print */ | |
| fputs ("------ ------- ------\n", st); /* the table header */ | |
| for (socket = 0; socket < 4; socket++) /* loop through the sockets */ | |
| if (loader_rom [socket] == NULL) /* if the socket is empty */ | |
| fprintf (st, " %u <empty>\n", socket); /* then report it as such */ | |
| else { /* otherwise the socket is occupied */ | |
| dname = loader_rom [socket]->name; /* so get the device name */ | |
| for (index = 0; lookup [index].name; index++) /* search the lookup table */ | |
| if (strcmp (lookup [index].name, dname) == 0) { /* for a match to the device name */ | |
| letter = lookup [index].suffix; /* and get the part number suffix */ | |
| break; | |
| } | |
| fprintf (st, " %u %-4s 12992%c\n", /* print the ROM information */ | |
| socket, dname, letter); | |
| } | |
| return SCPE_OK; /* return success status */ | |
| } | |
| /* Show the instruction execution trace criteria. | |
| This display routine is called to show the criteria that select instruction | |
| execution tracing. The "st" parameter is the open output stream. The other | |
| parameters are not used. | |
| This routine services an extended modifier entry, so it must add the trailing | |
| newline to the output before returning. | |
| */ | |
| static t_stat show_exec (FILE *st, UNIT *uptr, int32 val, CONST void *desc) | |
| { | |
| uint32 radix; | |
| if (exec_mask == 0) /* if the instruction is entirely masked */ | |
| fputs ("Execution trace disabled\n", st); /* then report that matching is disabled */ | |
| else { /* otherwise */ | |
| if (sim_switches & SWMASK ('O')) /* if an octal override is present */ | |
| radix = 8; /* then print the value in base 8 */ | |
| else if (sim_switches & SWMASK ('D')) /* otherwise if a decimal override is present */ | |
| radix = 10; /* then print the value in base 10 */ | |
| else if (sim_switches & SWMASK ('H')) /* otherwise if a hex override is present */ | |
| radix = 16; /* then print the value in base 16 */ | |
| else /* otherwise */ | |
| radix = cpu_dev.dradix; /* use the current CPU data radix */ | |
| fputs ("Execution trace match = ", st); /* print the label */ | |
| fprint_val (st, exec_match, radix, cpu_dev.dwidth, PV_RZRO); /* and the match value */ | |
| fputs (", mask = ", st); /* print a separator */ | |
| fprint_val (st, exec_mask, radix, cpu_dev.dwidth, PV_RZRO); /* and the mask value */ | |
| fputc ('\n', st); /* tie off the line */ | |
| } | |
| return SCPE_OK; /* report the success of the display */ | |
| } | |
| /* Show the current CPU simulation speed. | |
| This display routine is called to show the current simulation speed. The | |
| "st" parameter is the open output stream. The other parameters are not used. | |
| The CPU speed, expressed as a multiple of the hardware speed, is calculated | |
| by the time-base generator service routine. It is only representative when | |
| the TBG is calibrated, and the CPU is not idling. | |
| */ | |
| static t_stat show_speed (FILE *st, UNIT *uptr, int32 val, CONST void *desc) | |
| { | |
| fprintf (st, "Simulation speed = %ux\n", cpu_speed); /* display the current CPU speed */ | |
| return SCPE_OK; /* and report success */ | |
| } | |
| /* CPU local utility routine declarations */ | |
| /* Get effective address from IR */ | |
| static t_stat ea (HP_WORD IR, HP_WORD *address, uint32 irq) | |
| { | |
| HP_WORD MA; | |
| MA = IR & (I_IA | I_DISP); /* ind + disp */ | |
| if (IR & I_CP) /* current page? */ | |
| MA = ((PR - 1) & I_PAGENO) | MA; /* merge in page from P */ | |
| if (IR & I_IA) /* if the address is indirect */ | |
| return resolve (MA, address, irq); /* then resolve it to a direct address */ | |
| else { /* otherwise the address is direct */ | |
| *address = MA; /* so use it as is */ | |
| return SCPE_OK; /* and return success */ | |
| } | |
| } | |
| /* Execute a Shift/Rotate Group micro-operation. | |
| SRG instructions consist of two shift/rotate micro-operations plus a CLE and | |
| a SLA/SLB micro-op. This routine implements the shift and rotate operation. | |
| Each of the two shift/rotate operations has an enable bit that must be set to | |
| enable the operation. If the bit is not set, the operation is a NOP, with | |
| the exception that an ELA/ELB or ERA/ERB operation alters the E register (but | |
| not the A/B register). We accommodate this by including the enable/disable | |
| bit with the three-bit operation code and decode the disabled operations of | |
| ELA/ELB and ERA/ERB separately from their enabled operations. | |
| On entry, "value" is the value of the selected accumulator (A/B), and | |
| "operation" is the micro-op and enable bit. The routine returns the updated | |
| accumulator value and modifies the E register as indicated. | |
| Implementation notes: | |
| 1. The enable bit is located adjacent to the three-bit encoded operation for | |
| the first shift/rotate micro-op, but it is spaced one bit away from the | |
| encoded operation for the second micro-op. It is faster to decode | |
| separate values for each location rather than move the second enable bit | |
| adjacent to its encoded operation. The former imposes no time penalty; | |
| the jump table for the "switch" statement is simply somewhat larger. | |
| */ | |
| static HP_WORD srg_uop (HP_WORD value, HP_WORD operation) | |
| { | |
| uint32 extend; | |
| switch (operation) { /* dispatch on the micro operation */ | |
| case SRG1_EN | I_xLS: | |
| case SRG2_EN | I_xLS: /* ALS/BLS */ | |
| return value & D16_SIGN | value << 1 & D16_SMAX; /* arithmetic left shift */ | |
| case SRG1_EN | I_xRS: | |
| case SRG2_EN | I_xRS: /* ARS/BRS */ | |
| return value & D16_SIGN | value >> 1; /* arithmetic right shift */ | |
| case SRG1_EN | I_RxL: | |
| case SRG2_EN | I_RxL: /* RAL/RBL */ | |
| return (value << 1 | value >> 15) & D16_MASK; /* rotate left */ | |
| case SRG1_EN | I_RxR: | |
| case SRG2_EN | I_RxR: /* RAR/RBR */ | |
| return (value >> 1 | value << 15) & D16_MASK; /* rotate right */ | |
| case SRG1_EN | I_xLR: | |
| case SRG2_EN | I_xLR: /* ALR/BLR */ | |
| return value << 1 & D16_SMAX; /* arithmetic left shift, clear sign */ | |
| case SRG_DIS | I_ERx: /* disabled ERA/ERB */ | |
| E = value & LSB; /* rotate the LSB right into E */ | |
| return value; /* and return the original value */ | |
| case SRG1_EN | I_ERx: | |
| case SRG2_EN | I_ERx: /* ERA/ERB */ | |
| extend = E; /* save the original E value */ | |
| E = value & LSB; /* rotate the LSB right into E */ | |
| return value >> 1 | (HP_WORD) extend << 15; /* and rotate right with E filling the MSB */ | |
| case SRG_DIS | I_ELx: /* disabled ELA/ELB */ | |
| E = value >> 15 & LSB; /* rotate the MSB left into E */ | |
| return value; /* and return the original value */ | |
| case SRG1_EN | I_ELx: | |
| case SRG2_EN | I_ELx: /* ELA/ELB */ | |
| extend = E; /* save the original E value */ | |
| E = value >> 15 & LSB; /* rotate the MSB left into E */ | |
| return (value << 1 | (HP_WORD) extend) & D16_MASK; /* and rotate left with E filling the LSB */ | |
| case SRG1_EN | I_xLF: | |
| case SRG2_EN | I_xLF: /* ALF/BLF */ | |
| return (value << 4 | value >> 12) & D16_MASK; /* rotate left four */ | |
| default: /* all other (disabled) cases */ | |
| return value; /* return the original value */ | |
| } | |
| } | |
| /* Execute one machine instruction. | |
| This routine executes the CPU instruction present in the IR. The CPU state | |
| (registers, memory, interrupt status) is modified as necessary, and the | |
| routine return SCPE_OK if the instruction executed successfully. Any other | |
| status indicates that execution should cease, and control should return to | |
| the simulator console. For example, a programmed HALT instruction returns | |
| STOP_HALT status. | |
| This routine implements the main instruction dispatcher. Instructions | |
| corresponding to the MRG, SRG, and ASG are executed inline. IOG, EAG, and | |
| UIG instructions are executed in external handlers. | |
| The JMP instruction executor handles CPU idling. The 21xx/1000 CPUs have no | |
| "wait for interrupt" instruction. Idling in HP operating systems consists of | |
| sitting in "idle loops" that end with JMP instructions. We test for certain | |
| known patterns when a JMP instruction is executed to decide if the simulator | |
| should idle. The recognized patterns are: | |
| for RTE-6/VM: | |
| - ISZ <n> / JMP *-1 | |
| - mp_fence = 0 | |
| - XEQT (address 1717B) = 0 | |
| - DMS on with system map enabled | |
| - RTE verification: TBG (address 1674B) = CLK select code | |
| for RTE though RTE-IVB: | |
| - JMP * | |
| - mp_fence = 0 | |
| - XEQT (address 1717B) = 0 | |
| - DMS on with user map enabled (RTE-III through RTE-IVB only) | |
| - RTE verification: TBG (address 1674B) = CLK select code | |
| for DOS through DOS-III: | |
| - STF 0 / CCA / CCB / JMP *-3 | |
| - DOS verification: A = B = -1, address 40B = -64, address 67B = +64 | |
| - Note that in DOS, the TBG is set to 100 milliseconds | |
| Idling must not occur if an interrupt is pending. As mentioned before, the | |
| CPU will defer pending interrupts when certain instructions are executed. OS | |
| interrupt handlers exit via such deferring instructions. If there is a | |
| pending interrupt when the OS is otherwise idle, the idle loop will execute | |
| one instruction before reentering the interrupt handler. If we call | |
| sim_idle() in this case, we will lose interrupts. | |
| Consider the situation in RTE. Under simulation, the TTY and CLK events are | |
| co-scheduled, with the CLK expiring one instruction after the TTY. When the | |
| TTY interrupts, $CIC in RTE is entered. One instruction later, the CLK | |
| expires and posts its interrupt, but it is not immediately handled, because | |
| the JSB $CIC,I / JMP $CIC0,I / SFS 0,C instruction entry sequence continually | |
| defers interrupts until the interrupt system is turned off. When $CIC | |
| returns via $IRT, one instruction of the idle loop is executed, even though | |
| the CLK interrupt is still pending, because the UJP instruction used to | |
| return also defers interrupts. | |
| If "sim_idle" is called at this point, the simulator will sleep when it | |
| should be handling the pending CLK interrupt. When it awakes, TTY expiration | |
| will be moved forward to the next instruction. The still-pending CLK | |
| interrupt will then be recognized, and $CIC will be entered. But the TTY and | |
| then the CLK will then expire and attempt to interrupt again, although they | |
| are deferred by the $CIC entry sequence. This causes the second CLK | |
| interrupt to be missed, as processing of the first one is just now being | |
| started. | |
| Similarly, at the end of the CLK handling, the TTY interrupt is still | |
| pending. When $IRT returns to the idle loop, "sim_idle" would be called | |
| again, so the TTY and then CLK interrupt a third time. Because the second | |
| TTY interrupt is still pending, $CIC is entered, but the third TTY interrupt | |
| is lost. | |
| We solve this problem by testing for a pending interrupt before calling | |
| "sim_idle". The system isn't really quiescent if it is just about to handle | |
| an interrupt. | |
| Implementation notes: | |
| 1. Instruction decoding is based on the HP 1000, which does a 256-way branch | |
| on the upper eight bits of the instruction, as follows: | |
| 15 14 13 12 11 10 9 8 Instruction Group | |
| -- -- -- -- -- -- -- -- --------------------------------------- | |
| x <-!= 0-> x x x x memory reference | |
| 0 0 0 0 x 0 x x shift/rotate | |
| 0 0 0 0 x 1 x x alter/skip | |
| 1 0 0 0 x 1 x x I/O | |
| 1 0 0 0 0 0 x 0 extended arithmetic | |
| 1 0 0 0 0 0 0 1 divide (decoded as 100400) | |
| 1 0 0 0 1 0 0 0 double load (decoded as 104000) | |
| 1 0 0 0 1 0 0 1 double store (decoded as 104400) | |
| 1 0 0 0 1 0 1 0 extended instr group 0 (A/B is set) | |
| 1 0 0 0 x 0 1 1 extended instr group 1 (A/B is ignored) | |
| 2. JSB is tricky. It is possible to generate both an MP and a DM violation | |
| simultaneously, as the MP and MEM cards validate in parallel. Consider a | |
| JSB to a location under the MP fence and on a write-protected page. This | |
| situation must be reported as a DM violation, because it has priority | |
| (SFS 5 and SFC 5 check only the MEVFF, which sets independently of the MP | |
| fence violation). Under simulation, this means that DM violations must | |
| be checked, and the MEVFF must be set, before an MP abort is taken. This | |
| is done by the "mp_dms_jmp" routine. | |
| 3. Although MR (and TR) will be changed by reads of an indirect chain, the | |
| idle loop JMP will be direct, and so MR will contain the correct value | |
| for the "idle loop omitted" trace message. | |
| 4. The Alter/Skip Group RSS micro-op reverses the skip sense of the SEZ, | |
| SSA/SSB, SLA/SLB, and SZA/SZB micro-op tests. Normally, the instruction | |
| skips if any test is true. However, the specific combination of SSA/SSB, | |
| SLA/SLB, and RSS micro-ops causes a skip if BOTH of the skip cases are | |
| true, i.e., if both the MSB and LSB of the register value are ones. We | |
| handle this as a special case, because without RSS, the instruction skips | |
| if EITHER the MSB or LSB is zero. The other reversed skip cases (SEZ,RSS | |
| and SZA,RSS/SZB,RSS) are independent. | |
| */ | |
| static t_stat machine_instruction (HP_WORD IR, t_bool iotrap, uint32 irq_pending, uint32 *idle_save) | |
| { | |
| uint32 ab_selector, result, skip; | |
| HP_WORD data, MA; | |
| t_bool rss; | |
| t_stat status = SCPE_OK; | |
| switch (UPPER_BYTE (IR)) { /* dispatch on bits 15-8 of the instruction */ | |
| /* Memory Reference Group */ | |
| case 0020: case 0021: case 0022: case 0023: | |
| case 0024: case 0025: case 0026: case 0027: /* AND */ | |
| case 0220: case 0221: case 0222: case 0223: | |
| case 0224: case 0225: case 0226: case 0227: /* AND,I */ | |
| status = ea (IR, &MA, irq_pending); /* get the effective address */ | |
| if (status == SCPE_OK) /* if the address resolved */ | |
| AR = AR & ReadW (MA); /* then AND the accumulator and memory */ | |
| break; | |
| case 0230: case 0231: case 0232: case 0233: | |
| case 0234: case 0235: case 0236: case 0237: /* JSB,I */ | |
| ion_defer = TRUE; /* defer interrupts */ | |
| /* fall into the JSB case */ | |
| case 0030: case 0031: case 0032: case 0033: | |
| case 0034: case 0035: case 0036: case 0037: /* JSB */ | |
| status = ea (IR, &MA, irq_pending); /* get the effective address */ | |
| if (status == SCPE_OK) { /* if the address resolved */ | |
| mp_dms_jmp (MA, jsb_plb); /* then validate the jump address */ | |
| WriteW (MA, PR); /* store P into the target memory address */ | |
| PCQ_ENTRY; /* save P in the queue */ | |
| PR = MA + 1 & LA_MASK; /* and jump to the word after the target address */ | |
| } | |
| break; | |
| case 0040: case 0041: case 0042: case 0043: | |
| case 0044: case 0045: case 0046: case 0047: /* XOR */ | |
| case 0240: case 0241: case 0242: case 0243: | |
| case 0244: case 0245: case 0246: case 0247: /* XOR,I */ | |
| status = ea (IR, &MA, irq_pending); /* get the effective address */ | |
| if (status == SCPE_OK) /* if the address resolved */ | |
| AR = AR ^ ReadW (MA); /* then XOR the accumulator and memory */ | |
| break; | |
| case 0250: case 0251: case 0252: case 0253: | |
| case 0254: case 0255: case 0256: case 0257: /* JMP,I */ | |
| ion_defer = TRUE; /* defer interrupts */ | |
| /* fall into the JMP case */ | |
| case 0050: case 0051: case 0052: case 0053: | |
| case 0054: case 0055: case 0056: case 0057: /* JMP */ | |
| status = ea (IR, &MA, irq_pending); /* get the effective address */ | |
| if (status != SCPE_OK) /* if the address failed to resolve */ | |
| break; /* then abort execution */ | |
| mp_dms_jmp (MA, 0); /* validate the jump address */ | |
| PCQ_ENTRY; /* save P in the queue */ | |
| PR = MA; /* and jump to the target address */ | |
| if (sim_idle_enab && irq_pending == 0 /* if idle is enabled and no interrupt is pending */ | |
| && ((PR == err_PC /* and the jump target is * (RTE through RTE-IVB) */ | |
| || PR == err_PC - 1 /* or the target is *-1 (RTE-6/VM) */ | |
| && (mem_fast_read (PR, dms_ump) & I_MRG) == I_ISZ) /* and *-1 is ISZ <n> */ | |
| && mp_fence == 0 /* and the MP fence is zero */ | |
| && M [xeqt] == 0 /* and no program is executing */ | |
| && M [tbg] == tbg_select_code) /* and the TBG select code is set */ | |
| || PR == err_PC - 3 /* or the jump target is *-3 (DOS through DOS-III) */ | |
| && M [PR] == I_STF /* and *-3 is STF 0 */ | |
| && AR == 0177777 /* and the A and B registers */ | |
| && BR == 0177777 /* are both set to -1 */ | |
| && M [m64] == 0177700 /* and the -64 and +64 base-page constants */ | |
| && M [p64] == 0000100) { /* are set as expected */ | |
| tprintf (cpu_dev, cpu_dev.dctrl, | |
| DMS_FORMAT "idle loop execution omitted\n", | |
| meu_indicator, meu_page, MR, IR); | |
| if (cpu_dev.dctrl != 0) { /* if tracing is enabled */ | |
| *idle_save = cpu_dev.dctrl; /* then save the current trace flag set */ | |
| cpu_dev.dctrl = 0; /* and turn off tracing for the idle loop */ | |
| } | |
| sim_idle (TMR_POLL, FALSE); /* idle the simulator */ | |
| } | |
| break; | |
| case 0060: case 0061: case 0062: case 0063: | |
| case 0064: case 0065: case 0066: case 0067: /* IOR */ | |
| case 0260: case 0261: case 0262: case 0263: | |
| case 0264: case 0265: case 0266: case 0267: /* IOR,I */ | |
| status = ea (IR, &MA, irq_pending); /* get the effective address */ | |
| if (status == SCPE_OK) /* if the address resolved */ | |
| AR = AR | ReadW (MA); /* then OR the accumulator and memory */ | |
| break; | |
| case 0070: case 0071: case 0072: case 0073: | |
| case 0074: case 0075: case 0076: case 0077: /* ISZ */ | |
| case 0270: case 0271: case 0272: case 0273: | |
| case 0274: case 0275: case 0276: case 0277: /* ISZ,I */ | |
| status = ea (IR, &MA, irq_pending); /* get the effective address */ | |
| if (status == SCPE_OK) { /* if the address resolved */ | |
| data = ReadW (MA) + 1 & D16_MASK; /* then increment the memory word */ | |
| WriteW (MA, data); /* and write it back */ | |
| if (data == 0) /* if the value rolled over to zero */ | |
| PR = PR + 1 & LA_MASK; /* then increment P */ | |
| } | |
| break; | |
| case 0100: case 0101: case 0102: case 0103: | |
| case 0104: case 0105: case 0106: case 0107: /* ADA */ | |
| case 0300: case 0301: case 0302: case 0303: | |
| case 0304: case 0305: case 0306: case 0307: /* ADA,I */ | |
| status = ea (IR, &MA, irq_pending); /* get the effective address */ | |
| if (status == SCPE_OK) { /* if the address resolved */ | |
| data = ReadW (MA); /* then get the target word */ | |
| result = AR + data; /* and add the accumulator to memory */ | |
| if (result > D16_UMAX) /* if the result overflowed */ | |
| E = 1; /* then set the Extend register */ | |
| if (~(AR ^ data) & (AR ^ result) & D16_SIGN) /* if the sign of the result differs from the signs */ | |
| O = 1; /* of the operands, then set the Overflow register */ | |
| AR = result & R_MASK; /* store the sum into the accumulator */ | |
| } | |
| break; | |
| case 0110: case 0111: case 0112: case 0113: | |
| case 0114: case 0115: case 0116: case 0117: /* ADB */ | |
| case 0310: case 0311: case 0312: case 0313: | |
| case 0314: case 0315: case 0316: case 0317: /* ADB,I */ | |
| status = ea (IR, &MA, irq_pending); /* get the effective address */ | |
| if (status == SCPE_OK) { /* if the address resolved */ | |
| data = ReadW (MA); /* then get the target word */ | |
| result = BR + data; /* and add the accumulator to memory */ | |
| if (result > D16_UMAX) /* if the result overflowed */ | |
| E = 1; /* then set the Extend register */ | |
| if (~(BR ^ data) & (BR ^ result) & D16_SIGN) /* if the sign of the result differs from the signs */ | |
| O = 1; /* of the operands, then set the Overflow register */ | |
| BR = result & R_MASK; /* store the sum into the accumulator */ | |
| } | |
| break; | |
| case 0120: case 0121: case 0122: case 0123: | |
| case 0124: case 0125: case 0126: case 0127: /* CPA */ | |
| case 0320: case 0321: case 0322: case 0323: | |
| case 0324: case 0325: case 0326: case 0327: /* CPA,I */ | |
| status = ea (IR, &MA, irq_pending); /* get the effective address */ | |
| if (status == SCPE_OK) /* if the address resolved */ | |
| if (AR != ReadW (MA)) /* then if the accumulator and memory differ */ | |
| PR = PR + 1 & LA_MASK; /* then increment P */ | |
| break; | |
| case 0130: case 0131: case 0132: case 0133: | |
| case 0134: case 0135: case 0136: case 0137: /* CPB */ | |
| case 0330: case 0331: case 0332: case 0333: | |
| case 0334: case 0335: case 0336: case 0337: /* CPB,I */ | |
| status = ea (IR, &MA, irq_pending); /* get the effective address */ | |
| if (status == SCPE_OK) /* if the address resolved */ | |
| if (BR != ReadW (MA)) /* then if the accumulator and memory differ */ | |
| PR = PR + 1 & LA_MASK; /* then increment P */ | |
| break; | |
| case 0140: case 0141: case 0142: case 0143: | |
| case 0144: case 0145: case 0146: case 0147: /* LDA */ | |
| case 0340: case 0341: case 0342: case 0343: | |
| case 0344: case 0345: case 0346: case 0347: /* LDA,I */ | |
| status = ea (IR, &MA, irq_pending); /* get the effective address */ | |
| if (status == SCPE_OK) /* if the address resolved */ | |
| AR = ReadW (MA); /* then load the accumulator from memory */ | |
| break; | |
| case 0150: case 0151: case 0152: case 0153: | |
| case 0154: case 0155: case 0156: case 0157: /* LDB */ | |
| case 0350: case 0351: case 0352: case 0353: | |
| case 0354: case 0355: case 0356: case 0357: /* LDB,I */ | |
| status = ea (IR, &MA, irq_pending); /* get the effective address */ | |
| if (status == SCPE_OK) /* if the address resolved */ | |
| BR = ReadW (MA); /* then load the accumulator from memory */ | |
| break; | |
| case 0160: case 0161: case 0162: case 0163: | |
| case 0164: case 0165: case 0166: case 0167: /* STA */ | |
| case 0360: case 0361: case 0362: case 0363: | |
| case 0364: case 0365: case 0366: case 0367: /* STA,I */ | |
| status = ea (IR, &MA, irq_pending); /* get the effective address */ | |
| if (status == SCPE_OK) /* if the address resolved */ | |
| WriteW (MA, AR); /* then write the accumulator to memory */ | |
| break; | |
| case 0170: case 0171: case 0172: case 0173: | |
| case 0174: case 0175: case 0176: case 0177: /* STB */ | |
| case 0370: case 0371: case 0372: case 0373: | |
| case 0374: case 0375: case 0376: case 0377: /* STB,I */ | |
| status = ea (IR, &MA, irq_pending); /* get the effective address */ | |
| if (status == SCPE_OK) /* if the address resolved */ | |
| WriteW (MA, BR); /* then write the accumulator to memory */ | |
| break; | |
| /* Alter/Skip Group */ | |
| case 0004: case 0005: case 0006: case 0007: | |
| case 0014: case 0015: case 0016: case 0017: /* ASG */ | |
| skip = 0; /* assume that no skip is needed */ | |
| rss = (IR & I_RSS) != 0; /* get the Reverse Skip Sense flag */ | |
| ab_selector = (IR & I_AB ? 1 : 0); /* get the A/B register selector */ | |
| data = ABREG [ab_selector]; /* and the register data */ | |
| if (IR & I_CLx) /* if the CLA/CLB micro-op is enabled */ | |
| data = 0; /* then clear the value */ | |
| if (IR & I_CMx) /* if the CMA/CMB micro-op is enabled */ | |
| data = data ^ D16_MASK; /* then complement the value */ | |
| if (IR & I_SEZ && (E == 0) ^ rss) /* if SEZ[,RSS] is enabled and E is clear [set] */ | |
| skip = 1; /* then skip the next instruction */ | |
| if (IR & I_CLE) /* if the CLE micro-op is enabled */ | |
| E = 0; /* then clear E */ | |
| if (IR & I_CME) /* if the CME micro-op is enabled */ | |
| E = E ^ LSB; /* then complement E */ | |
| if ((IR & I_SSx_SLx_RSS) == I_SSx_SLx_RSS) { /* if the SSx, SLx, and RSS micro-ops are enabled together */ | |
| if ((data & D16_SIGN_LSB) == D16_SIGN_LSB) /* then if both sign and least-significant bits are set */ | |
| skip = 1; /* then skip the next instruction */ | |
| } | |
| else { /* otherwise */ | |
| if (IR & I_SSx && !(data & D16_SIGN) ^ rss) /* if SSx[,RSS] is enabled and the MSB is clear [set] */ | |
| skip = 1; /* then skip the next instruction */ | |
| if (IR & I_SLx && !(data & LSB) ^ rss) /* if SLx[,RSS] is enabled and the LSB is clear [set] */ | |
| skip = 1; /* then skip the next instruction */ | |
| } | |
| if (IR & I_INx) { /* if the INA/INB micro-op is enabled */ | |
| data = data + 1 & D16_MASK; /* then increment the value */ | |
| if (data == 0) /* if the value wrapped around to zero */ | |
| E = 1; /* then set the Extend register */ | |
| else if (data == D16_SIGN) /* otherwise if the value overflowed into the sign bit */ | |
| O = 1; /* then set the Overflow register */ | |
| } | |
| if (IR & I_SZx && (data == 0) ^ rss) /* if SZx[,RSS] is enabled and the value is zero [non-zero] */ | |
| skip = 1; /* then skip the next instruction */ | |
| if ((IR & I_ALL_SKIPS) == I_RSS) /* if RSS is present without any other skip micro-ops */ | |
| skip = 1; /* then skip the next instruction unconditionally */ | |
| ABREG [ab_selector] = data; /* store the result in the selected register */ | |
| PR = PR + skip & LA_MASK; /* and skip the next instruction if indicated */ | |
| break; | |
| /* Shift/Rotate Group */ | |
| case 0000: case 0001: case 0002: case 0003: | |
| case 0010: case 0011: case 0012: case 0013: /* SRG */ | |
| ab_selector = (IR & I_AB ? 1 : 0); /* get the A/B register selector */ | |
| data = ABREG [ab_selector]; /* and the register data */ | |
| data = srg_uop (data, SRG1 (IR)); /* do the first shift */ | |
| if (IR & SRG_CLE) /* if the CLE micro-op is enabled */ | |
| E = 0; /* then clear E */ | |
| if (IR & SRG_SLx && (data & LSB) == 0) /* if SLx is enabled and the LSB is clear */ | |
| PR = PR + 1 & LA_MASK; /* then skip the next instruction */ | |
| ABREG [ab_selector] = srg_uop (data, SRG2 (IR)); /* do the second shift and set the accumulator */ | |
| break; | |
| /* I/O Group */ | |
| case 0204: case 0205: case 0206: case 0207: | |
| case 0214: case 0215: case 0216: case 0217: /* IOG */ | |
| status = cpu_iog (IR, iotrap); /* execute the I/O instruction */ | |
| break; | |
| /* Extended Arithmetic Group */ | |
| case 0200: /* EAU group 0 */ | |
| case 0201: /* DIV */ | |
| case 0202: /* EAU group 2 */ | |
| case 0210: /* DLD */ | |
| case 0211: /* DST */ | |
| status = cpu_eau (IR, irq_pending); /* execute the extended arithmetic instruction */ | |
| break; | |
| /* User Instruction Group */ | |
| case 0212: /* UIG 0 */ | |
| status = cpu_uig_0 (IR, irq_pending, iotrap); /* execute the user instruction opcode */ | |
| break; | |
| case 0203: | |
| case 0213: /* UIG 1 */ | |
| status = cpu_uig_1 (IR, irq_pending, iotrap); /* execute the user instruction opcode */ | |
| break; | |
| } /* all cases are handled */ | |
| return status; /* return the execution status */ | |
| } | |
| /* Determine whether a pending interrupt deferral should be inhibited. | |
| Execution of certain instructions generally cause a pending interrupt to be | |
| deferred until the succeeding instruction completes. However, the interrupt | |
| deferral rules differ for the 21xx vs. the 1000. | |
| The 1000 always defers until the completion of the instruction following a | |
| deferring instruction. The 21xx defers unless the following instruction is | |
| an MRG instruction other than JMP or JMP,I or JSB,I. If it is, then the | |
| deferral is inhibited, i.e., the pending interrupt will be serviced. | |
| In either case, if the interrupting device is the memory protect card, or if | |
| the INT jumper is out on the 12892B MP card, then interrupts are not | |
| deferred. | |
| See the "Set Phase Logic Flowchart" for the transition from phase 1A to phase | |
| 1B, and "Section III Theory of Operation," "Control Section Detailed Theory" | |
| division, "Phase Control Logic" subsection, "Phase 1B" paragraph (3-241) in | |
| the Model 2100A Computer Installation and Maintenance Manual for details. | |
| */ | |
| static t_bool check_deferral (uint32 irq_sc) | |
| { | |
| HP_WORD next_instruction; | |
| if (! is_1000) { /* if the CPU is a 21xx model */ | |
| next_instruction = mem_fast_read (PR, dms_ump); /* then prefetch the next instruction */ | |
| if (MRGOP (next_instruction) /* if it is an MRG instruction */ | |
| && (next_instruction & I_MRG_I) != I_JSB_I /* but not JSB,I? */ | |
| && (next_instruction & I_MRG) != I_JMP) /* and not JMP or JMP,I */ | |
| return FALSE; /* then inhibit deferral */ | |
| } | |
| if (irq_sc == PRO /* if memory protect is interrupting */ | |
| || mp_unit.flags & UNIT_MP_INT && mp_control) /* or the INT jumper is out for the 12892B card */ | |
| return FALSE; /* then inhibit deferral */ | |
| else /* otherwise */ | |
| return TRUE; /* deferral is permitted */ | |
| } | |
| /* Logical-to-physical address translation for console access. | |
| This routine translates a logical address interpreted in the context of the | |
| translation map implied by the specified switch to a physical address. It is | |
| called to map addresses when the user is examining or depositing memory. It | |
| is also called to restore a saved configuration, although mapping is not used | |
| for restoration. All memory protection checks are off for console access. | |
| Command line switches modify the interpretation of logical addresses as | |
| follows: | |
| Switch Meaning | |
| ------ -------------------------------------------------- | |
| -N Use the address directly with no mapping | |
| -S If memory expansion is enabled, use the system map | |
| -U If memory expansion is enabled, use the user map | |
| -P If memory expansion is enabled, use the port A map | |
| -Q If memory expansion is enabled, use the port B map | |
| If no switch is specified, the address is interpreted using the current map | |
| if memory expansion is enabled; otherwise, the address is not mapped. If the | |
| current or specified map is used, then the address must lie within the 32K | |
| logical address space; if not, then an address larger than the current memory | |
| size is returned. | |
| */ | |
| static uint32 map_address (HP_WORD logical, int32 switches) | |
| { | |
| uint32 map; | |
| if (switches & (SWMASK ('N') | SIM_SW_REST)) /* if no mapping is requested */ | |
| return logical; /* then the address is already a physical address */ | |
| else if ((dms_enb || switches & ALL_MAPMODES) /* otherwise if mapping is enabled or requested */ | |
| && logical > LA_MAX) /* and the address is not a logical address */ | |
| return (uint32) MEMSIZE; /* then report a memory overflow */ | |
| else if (switches & SWMASK ('S')) /* otherwise if the -S switch is specified */ | |
| map = SMAP; /* then use the system map */ | |
| else if (switches & SWMASK ('U')) /* otherwise if the -U switch is specified */ | |
| map = UMAP; /* then use the user map */ | |
| else if (switches & SWMASK ('P')) /* otherwise if the -P switch is specified */ | |
| map = PAMAP; /* then use the DCPC port A map */ | |
| else if (switches & SWMASK ('Q')) /* otherwise if the -Q switch is specified */ | |
| map = PBMAP; /* then use the DCPC port B map */ | |
| else /* otherwise */ | |
| map = dms_ump; /* use the current map (system or user) */ | |
| return meu_map (logical, map, NOPROT); /* translate the address without protection */ | |
| } | |
| /* Check for non-zero value in a memory address range. | |
| A range of memory locations is checked for the presence of a non-zero value. | |
| The starting address of the range is supplied, and the check continues | |
| through the end of defined memory. The routine returns TRUE if the memory | |
| range was empty (i.e., contained only zero values) and FALSE otherwise. | |
| */ | |
| static t_bool mem_is_empty (uint32 starting_address) | |
| { | |
| uint32 address; | |
| for (address = starting_address; address < MEMSIZE; address++) /* loop through the specified address range */ | |
| if (M [address] != 0) /* if this location is non-zero */ | |
| return FALSE; /* then indicate that memory is not empty */ | |
| return TRUE; /* return TRUE if all locations contain zero values */ | |
| } | |
| /* Memory Expansion Unit local utility routine declarations */ | |
| /* Mapped access check. | |
| Return TRUE if the address will be mapped (presuming MEM is enabled). | |
| */ | |
| static t_bool is_mapped (uint32 address) | |
| { | |
| uint32 dms_fence; | |
| if (address >= 02000u) /* if the address is not on the base page */ | |
| return TRUE; /* then it is always mapped */ | |
| else { /* otherwise */ | |
| dms_fence = dms_sr & MST_FENCE; /* get the base-page fence value */ | |
| if (dms_sr & MST_FLT) /* if the lower portion is mapped */ | |
| return (address < dms_fence); /* then return TRUE if the address is below the fence */ | |
| else /* otherwise the upper portion is mapped */ | |
| return (address >= dms_fence); /* so return TRUE if the address is at or above the fence */ | |
| } | |
| } | |
| /* Map a logical address to a physical address.. | |
| This routine translates logical into physical addresses. The logical | |
| address, desired map, and desired access protection are supplied. If the | |
| access is legal, the mapped physical address is returned; if it is not, then | |
| a MEM violation is indicated. | |
| The current map may be specified by passing "dms_ump" as the "map" parameter, | |
| or a specific map may be used. Normally, read and write accesses pass RDPROT | |
| or WRPROT as the "prot" parameter to request access checking. For DMA | |
| accesses, NOPROT must be passed to inhibit access checks. | |
| This routine checks for read, write, and base-page violations and will call | |
| "dms_viol" as appropriate. The latter routine will abort if MP is enabled, | |
| or will return if protection is off. | |
| */ | |
| static uint32 meu_map (HP_WORD address, uint32 map, HP_WORD prot) | |
| { | |
| uint32 map_register; | |
| if (dms_enb) { /* if the Memory Expansion Unit is enabled */ | |
| if (address <= 1 && map < PAMAP) { /* then if the reference is to the A or B register */ | |
| meu_page = 0; /* then the physical page is page 0 */ | |
| return address; /* and the address is already physical */ | |
| } | |
| else if (is_mapped (address) == FALSE) { /* otherwise if a base-page address is not mapped */ | |
| meu_page = 0; /* then the physical page is page 0 */ | |
| if (address > 1 && prot == WRPROT) /* a write to the unmapped part of the base page */ | |
| dms_viol (address, MVI_BPG); /* causes a base-page violation if protection is enabled */ | |
| return address; /* the address is already physical */ | |
| } | |
| else { /* otherwise the address is mapped */ | |
| map_register = dms_map [map + VA_GETPAG (address)]; /* so get the map register for the logical page */ | |
| meu_page = MAP_PAGE (map_register); /* save the physical page number */ | |
| meu_indicator = map_indicator [map / MAP_LNT]; /* and set the map indicator the the applied map */ | |
| if (map_register & prot) /* if the desired access is not allowed */ | |
| dms_viol (address, prot); /* then a read or write protection violation occurs */ | |
| return TO_PAGE (meu_page) | VA_GETOFF (address); /* form the physical address from the mapped page and offset */ | |
| } | |
| } | |
| else { /* otherwise the MEU is disabled */ | |
| meu_page = VA_GETPAG (address); /* so the physical page is the logical page */ | |
| meu_indicator = '-'; /* set the map indicator to indicate no mapping */ | |
| return address; /* the physical address is the logical address */ | |
| } | |
| } | |
| /* DMA local SCP support routine declarations */ | |
| /* DMA/DCPC primary (SC 6/7) I/O signal handler. | |
| The primary DMA control interface and the service select register are | |
| manipulated through select codes 6 and 7. Each channel has transfer enable, | |
| control, flag, and flag buffer flip-flops. Transfer enable must be set via | |
| STC to start DMA. Control is used only to enable the DMA completion | |
| interrupt; it is set by STC and cleared by CLC. Flag and flag buffer are set | |
| at transfer completion to signal an interrupt. STF may be issued to abort a | |
| transfer in progress. | |
| Again, there are hardware differences between the various DMA cards. The | |
| 12607B (2114) stores only bits 2-0 of the select code and interprets them as | |
| select codes 10-16 (SRQ17 is not decoded). The 12578A (2115/16), 12895A | |
| (2100), and 12897B (1000) support the full range of select codes (10-77 | |
| octal). | |
| Implementation notes: | |
| 1. An IOI reads the floating S-bus (high on the 1000, low on the 21xx). | |
| 2. The CRS signal on the DMA card resets the secondary (SC 2/3) select | |
| flip-flops. Under simulation, ioCRS is dispatched to select codes 6 and | |
| up, so we reset the flip-flop in our handler. | |
| 3. The 12578A supports byte-sized transfers by setting bit 14. Bit 14 is | |
| ignored by all other DMA cards, which support word transfers only. | |
| Under simulation, we use a byte-packing/unpacking register to hold one | |
| byte while the other is read or written during the DMA cycle. | |
| */ | |
| static uint32 dmapio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) | |
| { | |
| const CHANNEL ch = (CHANNEL) dibptr->card_index; /* DMA channel number */ | |
| uint16 data; | |
| IOSIGNAL signal; | |
| IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ | |
| while (working_set) { | |
| signal = IONEXT (working_set); /* isolate next signal */ | |
| switch (signal) { /* dispatch I/O signal */ | |
| case ioCLF: /* clear flag flip-flop */ | |
| dma [ch].flag = dma [ch].flagbuf = CLEAR; /* clear flag and flag buffer */ | |
| break; | |
| case ioSTF: /* set flag flip-flop */ | |
| case ioENF: /* enable flag */ | |
| if (dma [ch].xferen == SET) | |
| tpprintf (dma_dptrs [ch], TRACE_CMD, "Channel transfer %s\n", | |
| (dma [ch].cw3 == 0 ? "completed" : "aborted")); | |
| dma [ch].flag = dma [ch].flagbuf = SET; /* set flag and flag buffer */ | |
| dma [ch].xferen = CLEAR; /* clear transfer enable to abort transfer */ | |
| break; | |
| case ioSFC: /* skip if flag is clear */ | |
| setstdSKF (dma [ch]); /* skip if transfer in progress */ | |
| break; | |
| case ioSFS: /* skip if flag is set */ | |
| setstdSKF (dma [ch]); /* skip if transfer is complete */ | |
| break; | |
| case ioIOI: /* I/O data input */ | |
| if (is_1000) /* 1000? */ | |
| stat_data = IORETURN (SCPE_OK, DMASK); /* return all ones */ | |
| else /* other models */ | |
| stat_data = IORETURN (SCPE_OK, 0); /* return all zeros */ | |
| break; | |
| case ioIOO: /* I/O data output */ | |
| data = IODATA (stat_data); /* clear supplied status */ | |
| if (UNIT_CPU_MODEL == UNIT_2114) /* 12607? */ | |
| dma [ch].cw1 = (data & 0137707) | 010; /* mask SC, convert to 10-17 */ | |
| else if (UNIT_CPU_TYPE == UNIT_TYPE_211X) /* 12578? */ | |
| dma [ch].cw1 = data; /* store full select code, flags */ | |
| else /* 12895, 12897 */ | |
| dma [ch].cw1 = data & ~DMA1_PB; /* clip byte-packing flag */ | |
| tpprintf (dma_dptrs [ch], TRACE_CSRW, "Control word 1 is %sselect code %02o\n", | |
| fmt_bitset (data, dma_cw1_format), data & I_DEVMASK); | |
| break; | |
| case ioPOPIO: /* power-on preset to I/O */ | |
| dma [ch].flag = dma [ch].flagbuf = SET; /* set flag and flag buffer */ | |
| break; | |
| case ioCRS: /* control reset */ | |
| dma [ch].xferen = CLEAR; /* clear transfer enable */ | |
| dma [ch].select = CLEAR; /* set secondary for word count access */ | |
| /* fall into CLC handler */ | |
| case ioCLC: /* clear control flip-flop */ | |
| dma [ch].control = CLEAR; /* clear control */ | |
| if (dma [ch].xferen == SET) | |
| tpprintf (dma_dptrs [ch], TRACE_CMD, "Channel completion interrupt is inhibited\n"); | |
| break; | |
| case ioSTC: /* set control flip-flop */ | |
| dma [ch].packer = 0; /* clear packing register */ | |
| dma [ch].xferen = dma [ch].control = SET; /* set transfer enable and control */ | |
| if (dma [ch].cw2 & DMA2_OI) | |
| tpprintf (dma_dptrs [ch], TRACE_CMD, | |
| "Channel transfer of %u words from select code %02o to address %05o started\n", | |
| NEG16 (dma [ch].cw3), dma [ch].cw1 & I_DEVMASK, dma [ch].cw2 & VAMASK); | |
| else | |
| tpprintf (dma_dptrs [ch], TRACE_CMD, | |
| "Channel transfer of %u words from address %05o to select code %02o started\n", | |
| NEG16 (dma [ch].cw3), dma [ch].cw2 & VAMASK, dma [ch].cw1 & I_DEVMASK); | |
| break; | |
| case ioSIR: /* set interrupt request */ | |
| setstdPRL (dma [ch]); | |
| setstdIRQ (dma [ch]); | |
| break; | |
| case ioIAK: /* interrupt acknowledge */ | |
| dma [ch].flagbuf = CLEAR; /* clear flag buffer */ | |
| break; | |
| default: /* all other signals */ | |
| break; /* are ignored */ | |
| } | |
| working_set = working_set & ~signal; /* remove current signal from set */ | |
| } | |
| return stat_data; | |
| } | |
| /* DMA/DCPC secondary (SC 2/3) I/O signal handler. | |
| DMA consists of one (12607B) or two (12578A/12895A/12897B) channels. Each | |
| channel uses two select codes: 2 and 6 for channel 1, and 3 and 7 for channel | |
| 2. The lower select codes are used to configure the memory address register | |
| (control word 2) and the word count register (control word 3). The upper | |
| select codes are used to configure the service select register (control word | |
| 1) and to activate and terminate the transfer. | |
| There are differences in the implementations of the memory address and word | |
| count registers among the various cards. The 12607B (2114) supports 14-bit | |
| addresses and 13-bit word counts. The 12578A (2115/6) supports 15-bit | |
| addresses and 14-bit word counts. The 12895A (2100) and 12897B (1000) | |
| support 15-bit addresses and 16-bit word counts. | |
| Implementation notes: | |
| 1. Because the I/O bus floats to zero on 211x computers, an IOI (read word | |
| count) returns zeros in the unused bit locations, even though the word | |
| count is a negative value. | |
| 2. Select codes 2 and 3 cannot interrupt, so there is no SIR handler. | |
| */ | |
| static uint32 dmasio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) | |
| { | |
| const CHANNEL ch = (CHANNEL) dibptr->card_index; /* DMA channel number */ | |
| uint16 data; | |
| IOSIGNAL signal; | |
| IOCYCLE working_set = signal_set; /* no SIR handler needed */ | |
| while (working_set) { | |
| signal = IONEXT (working_set); /* isolate next signal */ | |
| switch (signal) { /* dispatch I/O signal */ | |
| case ioIOI: /* I/O data input */ | |
| if (UNIT_CPU_MODEL == UNIT_2114) /* 2114? */ | |
| data = dma [ch].cw3 & 0017777; /* only 13-bit count */ | |
| else if (UNIT_CPU_TYPE == UNIT_TYPE_211X) /* 2115/2116? */ | |
| data = dma [ch].cw3 & 0037777; /* only 14-bit count */ | |
| else /* other models */ | |
| data = (uint16) dma [ch].cw3; /* rest use full value */ | |
| stat_data = IORETURN (SCPE_OK, data); /* merge status and remaining word count */ | |
| tpprintf (dma_dptrs [ch], TRACE_CSRW, "Remaining word count is %u\n", | |
| NEG16 (dma [ch].cw3)); | |
| break; | |
| case ioIOO: /* I/O data output */ | |
| if (dma [ch].select) { /* word count selected? */ | |
| dma [ch].cw3 = IODATA (stat_data); /* save count */ | |
| tpprintf (dma_dptrs [ch], TRACE_CSRW, "Control word 3 is word count %u\n", | |
| NEG16 (dma [ch].cw3)); | |
| } | |
| else { /* memory address selected */ | |
| if (UNIT_CPU_MODEL == UNIT_2114) /* 2114? */ | |
| dma [ch].cw2 = IODATA (stat_data) & 0137777; /* only 14-bit address */ | |
| else /* other models */ | |
| dma [ch].cw2 = IODATA (stat_data); /* full address stored */ | |
| tpprintf (dma_dptrs [ch], TRACE_CSRW, "Control word 2 is %s address %05o\n", | |
| (dma [ch].cw2 & DMA2_OI ? "input to" : "output from"), | |
| dma [ch].cw2 & VAMASK); | |
| } | |
| break; | |
| case ioCLC: /* clear control flip-flop */ | |
| dma [ch].select = CLEAR; /* set for word count access */ | |
| break; | |
| case ioSTC: /* set control flip-flop */ | |
| dma [ch].select = SET; /* set for memory address access */ | |
| break; | |
| default: /* all other signals */ | |
| break; /* are ignored */ | |
| } | |
| working_set = working_set & ~signal; /* remove current signal from set */ | |
| } | |
| return stat_data; | |
| } | |
| /* DMA reset */ | |
| static t_stat dma_reset (DEVICE *dptr) | |
| { | |
| DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ | |
| const CHANNEL ch = (CHANNEL) dibptr->card_index; /* DMA channel number */ | |
| if (UNIT_CPU_MODEL != UNIT_2114) /* 2114 has only one channel */ | |
| hp_enbdis_pair (dma_dptrs [ch], /* make specified channel */ | |
| dma_dptrs [ch ^ 1]); /* consistent with other channel */ | |
| if (sim_switches & SWMASK ('P')) { /* power-on reset? */ | |
| dma [ch].cw1 = 0; /* clear control word registers */ | |
| dma [ch].cw2 = 0; | |
| dma [ch].cw3 = 0; | |
| } | |
| IOPRESET (dibptr); /* PRESET device (does not use PON) */ | |
| dma [ch].packer = 0; /* clear byte packer */ | |
| return SCPE_OK; | |
| } | |
| /* DMA local utility routine declarations */ | |
| /* DMA cycle routine. | |
| This routine performs one DMA input or output cycle using the indicated DMA | |
| channel number and DMS map. When the transfer word count reaches zero, the | |
| flag is set on the corresponding DMA channel to indicate completion. | |
| The 12578A card supports byte-packing. If bit 14 in control word 1 is set, | |
| each transfer will involve one read/write from memory and two output/input | |
| operations in order to transfer sequential bytes to/from the device. | |
| DMA I/O cycles differ from programmed I/O cycles in that multiple I/O control | |
| backplane signals may be asserted simultaneously. With programmed I/O, only | |
| CLF may be asserted with other signals, specifically with STC, CLC, SFS, SFC, | |
| IOI, or IOO. With DMA, as many as five signals may be asserted concurrently. | |
| DMA I/O timing looks like this: | |
| ------------ Input ------------ ----------- Output ------------ | |
| Sig Normal Cycle Last Cycle Normal Cycle Last Cycle | |
| === ============== ============== ============== ============== | |
| IOI T2-T3 T2-T3 | |
| IOO T3-T4 T3-T4 | |
| STC * T3 T3 T3 | |
| CLC * T3-T4 T3-T4 | |
| CLF T3 T3 T3 | |
| EDT T4 T4 | |
| * if enabled by control word 1 | |
| Under simulation, this routine dispatches one set of I/O signals per DMA | |
| cycle to the target device's I/O signal handler. The signals correspond to | |
| the table above, except that all signals for a given cycle are concurrent | |
| (e.g., the last input cycle has IOI, EDT, and optionally CLC asserted, even | |
| though IOI and EDT are not coincident in hardware). I/O signal handlers will | |
| process these signals sequentially, in the order listed above, before | |
| returning. | |
| Implementation notes: | |
| 1. The address increment and word count decrement is done only after the I/O | |
| cycle has completed successfully. This allows a failed transfer to be | |
| retried after correcting the I/O error. | |
| */ | |
| static t_stat dma_cycle (CHANNEL ch, ACCESS_CLASS class) | |
| { | |
| const uint32 dev = dma [ch].cw1 & I_DEVMASK; /* device select code */ | |
| const uint32 stc = dma [ch].cw1 & DMA1_STC; /* STC enable flag */ | |
| const uint32 bytes = dma [ch].cw1 & DMA1_PB; /* pack bytes flag */ | |
| const uint32 clc = dma [ch].cw1 & DMA1_CLC; /* CLC enable flag */ | |
| const HP_WORD MA = dma [ch].cw2 & VAMASK; /* memory address */ | |
| const HP_WORD input = dma [ch].cw2 & DMA2_OI; /* input flag */ | |
| const uint32 even = dma [ch].packer & DMA_OE; /* odd/even packed byte flag */ | |
| HP_WORD data; | |
| t_stat status; | |
| uint32 ioresult; | |
| IOCYCLE signals; | |
| if (bytes && !even || dma [ch].cw3 != DMASK) { /* normal cycle? */ | |
| if (input) /* input cycle? */ | |
| signals = ioIOI | ioCLF; /* assert IOI and CLF */ | |
| else /* output cycle */ | |
| signals = ioIOO | ioCLF; /* assert IOO and CLF */ | |
| if (stc) /* STC wanted? */ | |
| signals = signals | ioSTC; /* assert STC */ | |
| } | |
| else { /* last cycle */ | |
| if (input) /* input cycle? */ | |
| signals = ioIOI | ioEDT; /* assert IOI and EDT */ | |
| else { /* output cycle */ | |
| signals = ioIOO | ioCLF | ioEDT; /* assert IOO and CLF and EDT */ | |
| if (stc) /* STC wanted? */ | |
| signals = signals | ioSTC; /* assert STC */ | |
| } | |
| if (clc) /* CLC wanted? */ | |
| signals = signals | ioCLC; /* assert CLC */ | |
| } | |
| if (input) { /* input cycle? */ | |
| ioresult = io_dispatch (dev, signals, 0); /* do I/O input */ | |
| status = IOSTATUS (ioresult); /* get cycle status */ | |
| if (status == SCPE_OK) { /* good I/O cycle? */ | |
| data = IODATA (ioresult); /* extract return data value */ | |
| if (bytes) { /* byte packing? */ | |
| if (even) { /* second byte? */ | |
| data = (uint16) (dma [ch].packer << 8) /* merge stored byte */ | |
| | (data & DMASK8); | |
| mem_write (dma_dptrs [ch], class, MA, data); /* store word data */ | |
| } | |
| else /* first byte */ | |
| dma [ch].packer = (data & DMASK8); /* save it */ | |
| dma [ch].packer = dma [ch].packer ^ DMA_OE; /* flip odd/even bit */ | |
| } | |
| else /* no byte packing */ | |
| mem_write (dma_dptrs [ch], class, MA, data); /* store word data */ | |
| } | |
| } | |
| else { /* output cycle */ | |
| if (bytes) { /* byte packing? */ | |
| if (even) /* second byte? */ | |
| data = dma [ch].packer & DMASK8; /* retrieve it */ | |
| else { /* first byte */ | |
| dma [ch].packer = mem_read (dma_dptrs [ch], class, MA); /* read word data */ | |
| data = (dma [ch].packer >> 8) & DMASK8; /* get high byte */ | |
| } | |
| dma [ch].packer = dma [ch].packer ^ DMA_OE; /* flip odd/even bit */ | |
| } | |
| else /* no byte packing */ | |
| data = mem_read (dma_dptrs [ch], class, MA); /* read word data */ | |
| ioresult = io_dispatch (dev, signals, data); /* do I/O output */ | |
| status = IOSTATUS (ioresult); /* get cycle status */ | |
| } | |
| if ((even || !bytes) && (status == SCPE_OK)) { /* new byte or no packing and good xfer? */ | |
| dma [ch].cw2 = input | (dma [ch].cw2 + 1) & VAMASK; /* increment address */ | |
| dma [ch].cw3 = (dma [ch].cw3 + 1) & DMASK; /* increment word count */ | |
| if (dma [ch].cw3 == 0) /* end of transfer? */ | |
| dmapio (dibs [DMA1 + ch], ioENF, 0); /* set DMA channel flag */ | |
| } | |
| return status; /* return I/O status */ | |
| } | |
| /* Calculate DMA requests */ | |
| static uint32 calc_dma (void) | |
| { | |
| uint32 r = 0; | |
| if (dma [ch1].xferen && SRQ (dma [ch1].cw1 & I_DEVMASK)) { /* check DMA1 cycle */ | |
| r = r | DMA_1_REQ; | |
| tprintf (dma1_dev, TRACE_SR, "Select code %02o asserted SRQ\n", | |
| dma [ch1].cw1 & I_DEVMASK); | |
| } | |
| if (dma [ch2].xferen && SRQ (dma [ch2].cw1 & I_DEVMASK)) { /* check DMA2 cycle */ | |
| r = r | DMA_2_REQ; | |
| tprintf (dma2_dev, TRACE_SR, "Select code %02o asserted SRQ\n", | |
| dma [ch2].cw1 & I_DEVMASK); | |
| } | |
| return r; | |
| } | |
| /* Memory Protect local SCP support routine declarations */ | |
| /* Memory protect/parity error (SC 5) I/O signal handler. | |
| The memory protect card has a number of non-standard features: | |
| - CLF and STF affect the parity error enable flip-flop, not the flag | |
| - SFC and SFS test the memory expansion violation flip-flop, not the flag | |
| - POPIO clears control, flag, and flag buffer instead of setting the flags | |
| - CLC does not clear control (the only way to turn off MP is to cause a | |
| violation) | |
| - PRL and IRQ are a function of the flag only, not flag and control | |
| - IAK is used unqualified by IRQ | |
| The IAK backplane signal is asserted when any interrupt is acknowledged by | |
| the CPU. Normally, an interface qualifies IAK with its own IRQ to ensure | |
| that it responds only to an acknowledgement of its own request. The MP card | |
| does this to reset its flag buffer and flag flip-flops, and to reset the | |
| parity error indication. However, it also responds to an unqualified IAK | |
| (i.e., for any interface) as follows: | |
| - clears the MPV flip-flop | |
| - clears the indirect counter | |
| - clears the control flip-flop | |
| - sets the INTPT flip-flop | |
| The INTPT flip-flop indicates an occurrence of an interrupt. If the trap | |
| cell of the interrupting device contains an I/O instruction that is not a | |
| HLT, action equivalent to STC 05 is taken, i.e.: | |
| - sets the control flip-flop | |
| - set the EVR flip-flop | |
| - clears the MEV flip-flop | |
| - clears the PARERR flip-flop | |
| In other words, an interrupt for any device will disable MP unless the trap | |
| cell contains an I/O instruction other than a HLT. | |
| Implementation notes: | |
| 1. Because the card uses IAK unqualified, this routine is called whenever | |
| any interrupt occurs. If the MP card itself is not interrupting, the | |
| select code passed will not be SC 05. In either case, the trap cell | |
| instruction is passed in the data portion of the "stat_data" parameter. | |
| 2. The MEV flip-flop records memory expansion (a.k.a. dynamic mapping) | |
| violations. It is set when an DM violation is encountered and can be | |
| tested via SFC/SFS. | |
| 3. MP cannot be turned off in hardware, except by causing a violation. | |
| Microcode typically does this by executing an IOG micro-order with select | |
| code /= 1, followed by an IAK to clear the interrupt and a FTCH to clear | |
| the INTPT flip-flop. Under simulation, mp_control may be set to CLEAR to | |
| produce the same effect. | |
| 4. Parity error logic is not implemented. | |
| */ | |
| static uint32 protio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) | |
| { | |
| uint16 data; | |
| IOSIGNAL signal; | |
| IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ | |
| while (working_set) { | |
| signal = IONEXT (working_set); /* isolate next signal */ | |
| switch (signal) { /* dispatch I/O signal */ | |
| case ioCLF: /* clear flag flip-flop */ | |
| break; /* turns off PE interrupt */ | |
| case ioSTF: /* set flag flip-flop */ | |
| break; /* turns on PE interrupt */ | |
| case ioENF: /* enable flag */ | |
| mp_flag = mp_flagbuf = SET; /* set flag buffer and flag flip-flops */ | |
| mp_evrff = CLEAR; /* inhibit violation register updates */ | |
| break; | |
| case ioSFC: /* skip if flag is clear */ | |
| setSKF (!mp_mevff); /* skip if MP interrupt */ | |
| break; | |
| case ioSFS: /* skip if flag is set */ | |
| setSKF (mp_mevff); /* skip if DMS interrupt */ | |
| break; | |
| case ioIOI: /* I/O input */ | |
| stat_data = IORETURN (SCPE_OK, mp_viol); /* read MP violation register */ | |
| break; | |
| case ioIOO: /* I/O output */ | |
| mp_fence = IODATA (stat_data) & VAMASK; /* write to MP fence register */ | |
| if (cpu_unit.flags & UNIT_2100) /* 2100 IOP uses MP fence */ | |
| iop_sp = mp_fence; /* as a stack pointer */ | |
| mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */ | |
| break; | |
| case ioPOPIO: /* power-on preset to I/O */ | |
| mp_control = CLEAR; /* clear control flip-flop */ | |
| mp_flag = mp_flagbuf = CLEAR; /* clear flag and flag buffer flip-flops */ | |
| mp_mevff = CLEAR; /* clear memory expansion violation flip-flop */ | |
| mp_evrff = SET; /* set enable violation register flip-flop */ | |
| break; | |
| case ioSTC: /* set control flip-flop */ | |
| mp_control = SET; /* turn on MP */ | |
| mp_mevff = CLEAR; /* clear memory expansion violation flip-flop */ | |
| mp_evrff = SET; /* set enable violation register flip-flop */ | |
| break; | |
| case ioSIR: /* set interrupt request */ | |
| setPRL (PRO, !mp_flag); /* set PRL signal */ | |
| setIRQ (PRO, mp_flag); /* set IRQ signal */ | |
| break; | |
| case ioIAK: /* interrupt acknowledge */ | |
| if (dibptr->select_code == PRO) /* MP interrupt acknowledgement? */ | |
| mp_flag = mp_flagbuf = CLEAR; /* clear flag and flag buffer */ | |
| data = IODATA (stat_data); /* get trap cell instruction */ | |
| if (((data & I_NMRMASK) != I_IO) || /* trap cell instruction not I/O */ | |
| (I_GETIOOP (data) == soHLT)) /* or is halt? */ | |
| mp_control = CLEAR; /* turn protection off */ | |
| else { /* non-HLT I/O instruction leaves MP on */ | |
| mp_mevff = CLEAR; /* but clears MEV flip-flop */ | |
| mp_evrff = SET; /* and reenables violation register flip-flop */ | |
| } | |
| break; | |
| default: /* all other signals */ | |
| break; /* are ignored */ | |
| } | |
| working_set = working_set & ~signal; /* remove current signal from set */ | |
| } | |
| return stat_data; | |
| } | |
| /* Memory protect reset */ | |
| static t_stat mp_reset (DEVICE *dptr) | |
| { | |
| IOPRESET (&mp_dib); /* PRESET device (does not use PON) */ | |
| mp_fence = 0; /* clear fence register */ | |
| mp_viol = 0; /* clear violation register */ | |
| mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */ | |
| return SCPE_OK; | |
| } | |
| /* I/O system local utility routine declarations */ | |
| /* Initialize the I/O system. | |
| This routine is called in the instruction prelude to set up the I/O data | |
| structures prior to beginning execution. It sets up two tables indexed by | |
| select code: one of DIB pointers, and the other of DEVICE pointers. This | |
| allows fast access to the device interface routine by the I/O instruction | |
| executors and to the device trace flags, respectively. | |
| It also sets the interface priority, interrupt request, and service request | |
| bit vectors from the interface flip-flop values by calling the device | |
| interface routines. | |
| Finally, it sets the interrupt deferral table entries for the SFC and SFS | |
| signals. These depend on the current CPU model, which may have been changed | |
| while the simulation was stopped. | |
| */ | |
| static void io_initialize (void) | |
| { | |
| DEVICE *dptr; | |
| DIB *dibptr; | |
| uint32 i; | |
| dev_prl [0] = dev_prl [1] = ~0u; /* set all priority lows */ | |
| dev_irq [0] = dev_irq [1] = 0; /* clear all interrupt requests */ | |
| dev_srq [0] = dev_srq [1] = 0; /* clear all service requests */ | |
| memset (&dibs [2], 0, sizeof dibs - 2 * sizeof dibs [0]); /* clear the DIB pointer table */ | |
| memset (&devs [2], 0, sizeof devs - 2 * sizeof devs [0]); /* and the device table */ | |
| for (i = 0; sim_devices [i] != NULL; i++) { /* loop through all of the devices */ | |
| dptr = sim_devices [i]; /* get a pointer to the device */ | |
| dibptr = (DIB *) dptr->ctxt; /* and to that device's DIB */ | |
| if (dibptr && !(dptr->flags & DEV_DIS)) { /* if the DIB exists and the device is enabled */ | |
| devs [dibptr->select_code] = dptr; /* then set the device pointer into the device table */ | |
| dibs [dibptr->select_code] = dibptr; /* and set the DIB pointer into the dispatch table */ | |
| if (dibptr->select_code >= SIRDEV) /* if this device receives SIR */ | |
| dibptr->io_handler (dibptr, ioSIR, 0); /* then set the interrupt request state */ | |
| } | |
| } | |
| dibs [PWR] = &pwrf_dib; /* for now, powerfail is always present */ | |
| devs [PWR] = &cpu_dev; /* and is controlled by the CPU */ | |
| if (dibs [DMA1]) { /* if the first DMA channel is enabled */ | |
| dibs [DMALT1] = &dmas1_dib; /* then set up */ | |
| devs [DMALT1] = &dma1_dev; /* the secondary device handler */ | |
| } | |
| if (dibs [DMA2]) { /* if the second DMA channel is enabled */ | |
| dibs [DMALT2] = &dmas2_dib; /* then set up */ | |
| devs [DMALT2] = &dma2_dev; /* the secondary device handler */ | |
| } | |
| defer_tab [soSFC] = is_1000; /* SFC and SFS defer */ | |
| defer_tab [soSFS] = is_1000; /* for 1000-Series CPUs only */ | |
| return; | |
| } | |
| /* Device I/O signal dispatcher. | |
| This routine calls the I/O signal handler of the device corresponding to the | |
| supplied "select_code" value, passing the "signal_set" and inbound "data" | |
| values. The combined status and outbound data value from the handler is | |
| returned to the caller. | |
| The 21xx/1000 I/O structure requires that no empty slots exist between | |
| interface cards. This is due to the hardware priority chaining (PRH/PRL) | |
| that is passed from card-to-card. If it is necessary to leave unused I/O | |
| slots, HP 12777A Priority Jumper Cards must be installed in them to maintain | |
| priority continuity. | |
| Under simulation, every unassigned I/O slot behaves as though a 12777A were | |
| resident. In this configuration, I/O instructions addressed to one of these | |
| slots read the floating bus for LIA/B and MIA/B instructions or do nothing | |
| for all other instructions. | |
| Implementation notes: | |
| 1. For select codes < 10 octal, an IOI signal reads the floating S-bus | |
| (high on the 1000, low on the 21xx). For select codes >= 10 octal, an | |
| IOI reads the floating I/O bus (low on all machines). | |
| 2. The last select code used is saved for use by the CPU I/O handler in | |
| detecting consecutive CLC 0 executions. | |
| */ | |
| static uint32 io_dispatch (uint32 select_code, IOCYCLE signal_set, HP_WORD data) | |
| { | |
| uint32 stat_data; | |
| if (dibs [select_code] != NULL) { /* if the I/O slot is occupied */ | |
| tpprintf (devs [select_code], TRACE_IOBUS, "Received data %06o with signals %s\n", | |
| data, fmt_bitset (signal_set, inbound_format)); | |
| stat_data = /* then call the device interface */ | |
| dibs [select_code]->io_handler (dibs [select_code], /* with the indicated signals and write value */ | |
| signal_set, | |
| IORETURN (SCPE_OK, data)); | |
| tpprintf (devs [select_code], TRACE_IOBUS, "Returned data %06o with signals %s\n", | |
| IODATA (stat_data), fmt_bitset (stat_data, outbound_format)); | |
| last_select_code = select_code; /* save the select code for CLC 0 detection */ | |
| if (stat_data & ioSKF) /* if the interface asserted SKF */ | |
| stat_data = IORETURN (NOTE_SKIP, 0); /* then notify the caller to increment P */ | |
| } | |
| else if (signal_set & ioIOI) /* otherwise if it is an input request */ | |
| if (select_code < VARDEV && is_1000) /* then if it is an internal device of a 1000 CPU */ | |
| stat_data = IORETURN (STOP (cpu_ss_unsc), DMASK); /* then the empty slot reads as all ones */ | |
| else /* otherwise */ | |
| stat_data = IORETURN (STOP (cpu_ss_unsc), 0); /* the empty slot reads as all zeros */ | |
| else /* otherwise */ | |
| stat_data = IORETURN (STOP (cpu_ss_unsc), 0); /* the signal is ignored */ | |
| return stat_data; | |
| } |