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