/* hp3000_cpu.c: HP 3000 Central Processing Unit simulator | |
Copyright (c) 2016, 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 | |
AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | |
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
Except as contained in this notice, the name of the author 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 author. | |
CPU HP 3000 Series III Central Processing Unit | |
13-May-16 JDB Modified for revised SCP API function parameter types | |
11-Mar-16 JDB Fixed byte EA calculations with negative indexes | |
22-Dec-15 JDB First release version | |
01-Apr-15 JDB First successful run of MPE-V/R through account login | |
11-Dec-12 JDB Created | |
References: | |
- HP 3000 Series II System Microprogram Listing | |
(30000-90023, August 1976) | |
- HP 3000 Series II/III System Reference Manual | |
(30000-90020, July 1978) | |
- Machine Instruction Set Reference Manual | |
(30000-90022, June 1984) | |
The HP 3000 is a family of general-purpose business computers that were sold | |
by Hewlett-Packard from 1972 through 2001. There are two major divisions | |
within this family: the "classic" 16-bit, stack-oriented CISC machines, and | |
the "Precision Architecture" 32-bit, register-oriented RISC machines that | |
succeeded them. All machines run versions of MPE, the "Multiprogramming | |
Executive" operating system. | |
Within the "classic" division, there are two additional subdivisions, based | |
on the method used for peripheral connections: the original "SIO" machines, | |
and the later "HP-IB" machines. The I/O interfacing hardware differs between | |
the two types of machines, as do the privileged I/O machine instructions. | |
The user instruction sets are identical, as are the register sets visible to | |
the programmer. The I/O drivers are different to account for the hardware | |
differences, and therefore they run slightly different versions of MPE. | |
This implementation is a simulator for the classic SIO machines. This group | |
consists of the 3000 CX, the Series I, Series II, and Series III; the last is | |
simulated here. The CX and Series I, which is a repackaged CX, are | |
essentially subsets of the Series II/III -- a smaller instruction set, | |
limited memory size, and lower-precision floating-point instructions. | |
Simulation of these machines may be added in the future. Future simulation | |
of the HP-IB machines (the Series 30 through 70) is desirable, as the latest | |
MPE versions run only on these machines, but documentation on the internals | |
of the HP-IB hardware controllers is nonexistent. | |
The CX and Series I support 64K 16-bit words of memory. The Series II | |
supports up to 256K, divided into four banks of 64K words each, and the | |
Series III extends this to 1024K words using 16 banks. Memory is divided | |
into variable-length code and data segments, with the latter containing a | |
program's global data area and stack. | |
Memory protection is accomplished by checking program, data, and stack | |
accesses against segment base and limit registers, which can be set only by | |
MPE. Bounds violations cause automatic hardware traps to a handler routine | |
within MPE. Some violations may be permitted; for example, a Stack Overflow | |
trap may cause MPE to allocate a larger stack and then restart the | |
interrupted instruction. Almost all memory references are position- | |
independent, so moving segments to accommodate expansion requires only | |
resetting of the segment registers to point at the new location. Code | |
segments are fully reentrant and shareable, and both code and data are | |
virtual, as the hardware supports absent code and data segment traps. | |
The classic 3000s are stack machines. Most of the instructions operate on | |
the value on the top of the stack (TOS) or on the TOS and the next-to-the-top | |
of the stack (NOS). To improve execution speed, the 3000 has a set of | |
hardware registers that are accessed as the first four locations at the top | |
of the stack, while the remainder of the stack locations reside in main | |
memory. A hardware register renamer provides fast stack pushes and pops | |
without physically copying values between registers. | |
In hardware, the stack registers are referenced internally as TR0-TR3 and | |
externally as RA-RD. An access to the RA (TOS) register is translated by the | |
renamer to access TR0, TR1, etc. depending on which internal register is | |
designated as the current top of the stack. For example, assume that RA | |
corresponds to TR0. To push a new value onto the top of the stack, the | |
renamer is adjusted so that RA corresponds to TR1, and the new value is | |
stored there. In this state, RB corresponds to TR0, the previous TOS (and | |
current NOS) value. Additional pushes rename RA as TR2 and then TR3, with RB | |
being renamed to TR1 and then TR2, and similarly for RC and RD. The number | |
of valid TOS registers is given by the value in the SR register, and the | |
first stack location in memory is given by the value in the SM register. | |
Pops reverse the sequence: a pop with RA corresponding to TR3 renames RA to | |
TR2, RB to TR1, etc. When all four stack registers are in use, a push will | |
copy the value in RD to the top of the memory stack (SM + 1) before the | |
registers are renamed and the new value stored into RA. Similarly, when all | |
four stack registers are empty, a pop simply decrements the SM register, | |
effectively deleting the top of the memory stack. | |
Because of the renamer, the microcode can always use RA to refer to the top | |
of the stack, regardless of which internal TR register is being used, and | |
similarly for RB-RD. Execution of each stack instruction begins with a | |
preadjustment, if needed, that loads the TOS registers that will be used by | |
the instruction from the top of the memory stack. For example, if only RA | |
contains a value (i.e., the SR register value is 1), the ADD instruction, | |
which adds the values in RA and RB, will load RB with the value on the top of | |
the memory stack, the SR count will be incremented, and the SM register will | |
be decremented. On the other hand, if both RA and RB contained values (SR >= | |
2), then no preadjustment would be required before the ADD instruction | |
microcode manipulated RA and RB. | |
In simulation, the renamer is implemented by physically copying the values | |
between registers, as this is much faster than mapping via array index | |
values, as the hardware does. A set of functions provides the support to | |
implement the hardware stack operations: | |
cpu_push - empty the TOS register (SR++, caller stores into RA) | |
cpu_pop - delete the TOS register (SR--) | |
cpu_queue_up - move from memory to the lowest TOS register (SR++, SM--) | |
cpu_queue_down - move from the lowest TOS register to memory (SR--, SM++) | |
cpu_flush - move all stack registers to memory (SR = 0 on return) | |
cpu_adjust_sr - queue up until the required SR count is reached | |
cpu_mark_stack - write a stack marker to memory | |
The renamer is described in US Patent 3,737,871 (Katzman, "Stack Register | |
Renamer", June 1973). | |
The MPE operating system is supported by special microcode features that | |
perform code and data segment mapping, segment access bounds checking, | |
privilege checking, etc. The layout of certain in-memory tables is known to | |
both the OS and the microcode and is used to validate execution of | |
instructions. For instance, every stack instruction is checked for a valid | |
access within the stack segment boundaries, which are set up by the OS | |
before program dispatch. For this reason, the 3000 cannot be operated as a | |
"bare" machine, because these tables would not have been initialized. | |
Similarly, the "cold load" process by which the OS is loaded from storage | |
media into memory is entirely in microcode, as machine instructions cannot be | |
executed until the required tables are loaded into memory. | |
This OS/microcode integration means that the microcode may detect conditions | |
that make continued execution impossible. An example would be a not-present | |
segment fault for the segment containing the disc I/O driver. If such a | |
condition is detected, the CPU does a "system halt." This fatal microcode | |
error, distinct from a regular programmed halt, causes operation to cease | |
until the CPU is reset. | |
The CPU hardware includes a free-running 16-bit process clock that increments | |
once per millisecond whenever a user program is executing. MPE uses the | |
process clock to charge CPU usage to programs, and thereby to users, groups, | |
and accounts. Instructions are provided to read (RCLK) and set (SCLK) the | |
process clock. | |
The data types supported by the instruction set are: | |
- 8-bit unsigned byte | |
- 16-bit unsigned integer ("logical" format) | |
- 16-bit two's-complement integer | |
- 32-bit two's-complement integer | |
- 32-bit sign-magnitude floating point | |
- 64-bit sign-magnitude floating point | |
Multi-word values are stored in memory with the most-significant words in the | |
lowest addresses. The stack is organized in memory from lower to higher | |
addresses, so a value on the stack has its least-significant word on the top | |
of the stack. | |
Machine instructions are initially decoded by a sub-opcode contained in the | |
four highest bits, as follows (note that the HP 3000 numbers bits from | |
left-to-right, i.e., bit 0 is the MSB and bit 15 is the LSB): | |
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 0 0 | 1st stack opcode | 2nd stack opcode | Stack | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 0 1 | X | shift opcode | shift count | Shift | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 0 1 | I | branch opcode |+/-| P displacement | Branch | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 0 1 | X | bit test opcode | bit position | Bit Test | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 1 0 | 0 0 0 0 | move op | opts/S decrement | Move | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 1 0 | 0 0 0 0 | special op | 0 0 | sp op | Special | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 1 0 | 0 0 0 1 | firmware option op | Firmware | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 1 0 | imm opcode | immediate operand | Immediate | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 1 0 | field opcode | J field | K field | Field | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 1 0 | register op | SK| DB| DL| Z |STA| X | Q | S | Register | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 1 1 | 0 0 0 0 | I/O opcode | K field | I/O | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 1 1 | 0 0 0 0 | cntl opcode | 0 0 | cn op | Control | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 1 1 | program op | N field | Program | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 1 1 | immediate op | immediate operand | Immediate | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 1 1 | memory op | P displacement | Memory | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
The memory, loop, and branch instructions occupy the remainder of the | |
sub-opcodes (04-17): | |
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| memory op | X | I | mode and displacement | Memory | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 | 0 | P+ displacement 0-255 | | |
+---+---+---+---+---+---+---+---+---+---+ | |
| 0 | 1 | P- displacement 0-255 | | |
+---+---+---+---+---+---+---+---+---+---+ | |
| 1 | 0 | DB+ displacement 0-255 | | |
+---+---+---+---+---+---+---+---+---+---+ | |
| 1 | 1 | 0 | Q+ displacement 0-127 | | |
+---+---+---+---+---+---+---+---+---+---+ | |
| 1 | 1 | 1 | 0 | Q- displacement 0-63 | | |
+---+---+---+---+---+---+---+---+---+---+ | |
| 1 | 1 | 1 | 1 | S- displacement 0-63 | | |
+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| memory op | X | I | s | mode and displacement | Memory | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 | DB+ displacement 0-255 | | |
+---+---+---+---+---+---+---+---+---+ | |
| 1 | 0 | Q+ displacement 0-127 | | |
+---+---+---+---+---+---+---+---+---+ | |
| 1 | 1 | 0 | Q- displacement 0-63 | | |
+---+---+---+---+---+---+---+---+---+ | |
| 1 | 1 | 1 | S- displacement 0-63 | | |
+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 1 0 1 |loop op| 0 |+/-| P-relative displacement | Loop | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 1 1 0 0 | I | 0 1 | > | = | < | P+- displacement 0-31 | Branch | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Optional firmware extension instruction sets occupy instruction codes | |
020400-020777, except for the DMUL (020570) and DDIV (020571) instructions | |
that are part of the base set, as follows: | |
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 1 0 | 0 0 0 1 | 0 1 1 1 1 0 0 | x | DMUL/DDIV | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 1 0 | 0 0 0 1 | 0 0 0 0 1 | ext fp op | Extended FP | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 0 1 0 | 0 0 0 1 | 1 | options | decimal op | Decimal | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Most instructions are defined by unique 16-bit codes that specify the | |
operations, operands, addressing modes, shift counts, etc. For each of these | |
instructions, there is only one canonical form that encodes a given | |
instruction (or instruction pair, in the case of stack instructions). For | |
example, the octal value 140003 is the only value that encodes the "BR P+3" | |
instruction. | |
There are also instruction codes that contain one or more bits designated as | |
"reserved" in the Machine Instruction Set Reference Manual. For some of | |
these instructions, the reserved bits do not affect instruction decoding. | |
Each instruction of this type has a defined canonical form -- typically with | |
the reserved bits set to zero -- but will execute identically if one of the | |
undefined forms is used. For example, the "MOVE" instructions define bits | |
12-13 as 00, but the bits are not decoded, so values of 01, 10, and 11 for | |
these bits will result in identical execution. Specifically, "MOVE 0" is | |
encoded canonically as octal value 020020, but the undefined codes 020024, | |
020030, and 020034 execute "MOVE 0" as well. | |
For the rest of these instructions, the reserved bits are decoded and will | |
affect the instruction interpretation. An example of this is the "IXIT" | |
instruction. It also defines bits 12-13 as 00 (canonical encoding 020360), | |
but in this case the bits must be 00 for the instruction to execute; any | |
other value (e.g., undefined codes 020364, 020370, or 020374) executes as a | |
"PCN" instruction, whose canonical encoding is 020362. | |
Finally, some codes are not assigned to any instructions, or they are | |
assigned to instructions that are supplied by optional firmware that is not | |
present in the machine. These instruction codes are designated as | |
"unimplemented" and will cause Unimplemented Instruction traps if they are | |
executed. Examples are the stack operation code 072 in either the left-hand | |
or right-hand position (i.e., instruction codes 0072xx and 00xx72), codes | |
020410-020415 if the Extended Instruction Set firmware option is not | |
installed, and codes 036000-036777. | |
When the simulator examines the bit patterns of instructions to execute, each | |
will fall into one of four categories: | |
1. Defined (canonical) instruction encodings. | |
2. Undefined (non-canonical) instruction encodings, where reserved fields | |
are "don't care" bits (e.g., MOVE). | |
3. Undefined (non-canonical) instruction encodings, where reserved fields | |
are decoded (e.g., IXIT). | |
4. Unimplemented instruction encodings (e.g., stack opcode 072 or EADD with | |
no EIS installed). | |
When examining memory or register values in instruction-mnemonic form, the | |
names of the canonical instructions in category 1 are displayed in uppercase, | |
as are the names of the non-canonical instructions in category 2. The | |
non-canonical instruction names in category 3 are displayed in lowercase. | |
This is to indicate to the user that the instructions that will be executed | |
may not be the ones expected from the decoding. Instruction names in | |
category 4 that correspond to supported firmware options are displayed in | |
uppercase, regardless of whether or not the option is enabled. Category 4 | |
encodings that do not correspond to instructions are displayed in octal. | |
The simulator provides four stop conditions related to instruction execution | |
that may be enabled with a SET CPU STOP=<stop> command: | |
<stop> Action | |
------ ------------------------------------ | |
LOOP stop on an infinite loop | |
PAUSE stop on a PAUS instruction | |
UNDEF stop on an undefined instruction | |
UNIMPL stop on an unimplemented instruction | |
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 LOOP option stops the simulator if it attempts to execute an instruction | |
that enters an infinite loop (e.g., BR P+0). The branch instructions TBA, | |
TBX, BCC, BR, BCY, BNCY, BOV, and BNOV result in an infinite loop if the | |
branch displacement is zero and the branch condition is true. The remaining | |
branch instructions cannot result in an infinite loop, as they all modify the | |
CPU state and so eventually reach a point where they drop out of the loop. | |
The PAUSE option stops the simulator if execution of a PAUS instruction is | |
attempted. This instruction normally suspends instruction fetching until an | |
interrupt occurs. Clearing the stop and resuming execution suspends the | |
fetch/execute process until an external interrupt occurs. Resuming with the | |
stop bypassed continues execution with the instruction following the PAUS; | |
this is the same action that occurs when pressing the HALT button and then | |
the RUN button in hardware. | |
The UNDEF option stops the simulator if execution of a non-canonical | |
instruction from decoding category 3 (i.e., an instruction containing a | |
decoded reserved bit pattern other than that defined in the Machine | |
Instruction Set manual) is attempted. The intent 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 HP 3000 microcode. | |
The UNIMPL option stops the simulator if execution of an instruction from | |
decoding category 4 is attempted. Bypassing the stop will cause an | |
Unimplemented Instruction trap. Instructions that depend on the presence of | |
firmware options are implemented if the option is present and unimplemented | |
if the option is absent. For example, instruction code 020410 executes as | |
the EADD instruction if the Extended Instruction Set is enabled but is | |
unimplemented if the EIS is disabled. It is displayed as EADD in either | |
case. | |
The instructions in category 2 whose non-canonical forms do not cause an | |
UNDEF stop are: | |
Canonical Reserved | |
Inst Encoding Bits Defined As Decoded As | |
---- --------- -------- ----------- ----------- | |
SCAN 010600 10-15 0 0 0 0 0 0 x x x x x x | |
TNSL 011600 10-15 0 0 0 0 0 0 x x x x x x | |
MOVE 020000 12-13 0 0 x x | |
MVB 020040 12-13 0 0 x x | |
MVBL 020100 13 0 x | |
SCW 020120 13 0 x | |
MVLB 020140 13 0 x | |
SCU 020160 13 0 x | |
CMPB 020240 12-13 0 0 x x | |
RSW 020300 12-14 0 0 0 x x x | |
LLSH 020301 12-14 0 0 0 x x x | |
PLDA 020320 12-14 0 0 0 x x x | |
PSTA 020321 12-14 0 0 0 x x x | |
LSEA 020340 12-13 0 0 x x | |
SSEA 020341 12-13 0 0 x x | |
LDEA 020342 12-13 0 0 x x | |
SDEA 020343 12-13 0 0 x x | |
PAUS 030020 12-15 0 0 0 0 x x x x | |
The instructions in category 3 whose non-canonical forms cause an UNDEF stop | |
are: | |
Canonical Reserved | |
Inst Encoding Bits Defined As Decoded As | |
---- --------- -------- ---------- ---------- | |
IXIT 020360 12-15 0 0 0 0 0 0 0 0 | |
LOCK 020361 12-15 0 0 0 1 n n 0 1 | |
PCN 020362 12-15 0 0 1 0 n n n 0 | |
UNLK 020363 12-15 0 0 1 1 n n 1 1 | |
SED 030040 12-15 0 0 0 x n n n x | |
XCHD 030060 12-15 0 0 0 0 0 0 0 0 | |
PSDB 030061 12-15 0 0 0 1 n n 0 1 | |
DISP 030062 12-15 0 0 1 0 n n n 0 | |
PSEB 030063 12-15 0 0 1 1 n n 1 1 | |
SMSK 030100 12-15 0 0 0 0 0 0 0 0 | |
SCLK 030101 12-15 0 0 0 1 n n n n | |
RMSK 030120 12-15 0 0 0 0 0 0 0 0 | |
RCLK 030121 12-15 0 0 0 1 n n n n | |
Where: | |
x = 0 or 1 | |
n = any collective value other than 0 | |
In hardware, the SED instruction works correctly only if opcodes 030040 and | |
030041 are used. Opcodes 030042-030057 also decode as SED, but the status | |
register is set improperly (the I bit is cleared, bits 12-15 are rotated | |
right twice and then ORed into the status register). In simulation, opcodes | |
030042-030057 work correctly but will cause an UNDEF simulation stop if | |
enabled. | |
The CPU simulator provides extensive tracing capabilities that may be enabled | |
with the SET CONSOLE DEBUG=<filename> and SET CPU DEBUG=<trace> commands. | |
The trace options that may be specified are: | |
Trace Action | |
----- ---------------------------------- | |
INSTR trace instruction executions | |
DATA trace memory data accesses | |
FETCH trace memory instruction fetches | |
REG trace registers | |
PSERV trace process clock service events | |
A section of an example trace is: | |
>>CPU fetch: 00.010342 020320 instruction fetch | |
>>CPU instr: 00.010341 000300 ZROX,NOP | |
>>CPU reg: 00.006500 000000 X 000000, M i t r o c CCG | |
>>CPU fetch: 00.010343 041100 instruction fetch | |
>>CPU instr: 00.010342 020320 PLDA | |
>>CPU data: 00.000000 001340 absolute read | |
>>CPU reg: 00.006500 000001 A 001340, X 000000, M i t r o c CCG | |
>>CPU fetch: 00.010344 037777 instruction fetch | |
>>CPU instr: 00.010343 041100 LOAD DB+100 | |
>>CPU data: 00.002100 123003 data read | |
>>CPU reg: 00.006500 000002 A 123003, B 001340, X 000000, M i t r o c CCL | |
>>CPU fetch: 00.010345 023404 instruction fetch | |
>>CPU instr: 00.010344 037777 ANDI 377 | |
>>CPU reg: 00.006500 000002 A 000003, B 001340, X 000000, M i t r o c CCG | |
>>CPU fetch: 00.010346 002043 instruction fetch | |
>>CPU instr: 00.010345 023404 MPYI 4 | |
>>CPU reg: 00.006500 000002 A 000014, B 001340, X 000000, M i t r o c CCG | |
>>CPU fetch: 00.010347 020320 instruction fetch | |
The INSTR option traces instruction executions. Each instruction is printed | |
before it is executed. The two opcodes of a stack instruction are printed | |
together before the left-hand opcode is executed. If the right-hand opcode | |
is not NOP, it is reprinted before execution, with dashes replacing the | |
just-executed left-hand opcode. | |
The DATA option traces reads from and writes to memory. Each access is | |
classified by the memory bank register that is paired with the specified | |
offset; they are: dma, absolute, program, data, and stack. DMA accesses | |
derive their bank addresses from the banks specified in Set Bank I/O program | |
orders. Absolute accesses always use bank 0. Program, data, and stack | |
accesses use the bank addresses in the PBANK, DBANK, and SBANK registers, | |
respectively. | |
The FETCH option traces instruction fetches from memory. These accesses are | |
separated from those traced by the DATA option because fetches usually are of | |
little interest except when debugging the fetch/execute sequence. Because | |
the HP 3000 has a two-stage pipeline, fetches load the NIR (Next Instruction | |
Register) with the instruction after the instruction to be executed from the | |
CIR (Current Instruction Register). | |
The REG option traces register values. Two sets of registers are printed. | |
After executing each instruction, the currently active TOS registers, the | |
index register, and the status register are printed. After executing an | |
instruction that may alter the base registers, the program, data, and stack | |
segment base registers are printed. | |
The PSERV option traces process clock event service entries. Each trace | |
reports whether or not the CPU was executing on the Interrupt Control Stack | |
when the process clock ticked. Execution on the ICS implies that the | |
operating system is executing. As the process clock ticks every millisecond, | |
enabling PSERV tracing can quickly produce a large number of trace lines. | |
The various trace formats are interpreted as follows: | |
>>CPU instr: 00.010341 000300 ZROX,NOP | |
~~ ~~~~~~ ~~~~~~ ~~~~~~~~ | |
| | | | | |
| | | +-- instruction mnemonic(s) | |
| | +---------- octal data (instruction opcode) | |
| +------------------ octal address (P) | |
+----------------------- octal bank (PBANK) | |
>>CPU instr: 00.001240 000006 external interrupt | |
>>CPU instr: 00.023736 000000 unimplemented instruction trap | |
~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| | | | | |
| | | +-- interrupt classification | |
| | +---------- parameter | |
| +------------------ octal address (P) at interrupt | |
+----------------------- octal bank (PBANK) at interrupt | |
>>CPU data: 00.000000 001340 absolute read | |
>>CPU data: 00.002100 123003 data read | |
~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~ | |
| | | | | |
| | | +-- memory access classification | |
| | +------------ octal data (memory contents) | |
| +-------------------- octal address (effective address) | |
+------------------------- octal bank (PBANK, DBANK, or SBANK) | |
>>CPU fetch: 00.010342 020320 instruction fetch | |
~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~ | |
| | | | | |
| | | +-- memory access classification | |
| | +------------ octal data (instruction opcode) | |
| +-------------------- octal address (P + 1) | |
+------------------------- octal bank (PBANK) | |
>>CPU reg: 00.006500 000002 A 123003, B 001340, X 000000, M i t r o c CCL | |
~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| | | | | |
| | | +-- register values (from 0-4 TOS registers, X, STA) | |
| | +------------ octal stack register count (SR) | |
| +-------------------- octal stack memory address (SM) | |
+------------------------- octal bank (SBANK) | |
>>CPU reg: 00.000000 000001 PB 010000, PL 025227, DL 001770, DB 002000, Q 006510, Z 007000 | |
~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| | | | | |
| | | +-- base register values | |
| | +------------ current code segment number (from STA) | |
| +-------------------- zero | |
+------------------------- octal bank (DBANK) | |
>>CPU pserv: Process clock service entered not on the ICS | |
The process clock offers a user-selectable choice of calibrated or realistic | |
timing. Calibrated timing adjusts the clock to match actual elapsed time | |
(i.e., wall-clock time). Realistic timing bases the process-clock interval | |
on machine instructions executed, using a mean instruction time of 2.5 | |
microseconds. Running on a typical host platform, the simulator is one or | |
two orders of magnitude faster than a real HP 3000, so the number of machine | |
instructions executed for a given calibrated time interval will be | |
correspondingly greater. When the process clock is calibrated, the current | |
simulation speed, expressed as a multiple of the speed of a real HP 3000 | |
Series III, may be obtained with the SHOW CPU SPEED command. The speed | |
reported will not be representative if the machine was executing a PAUS | |
instruction when the simulator was stopped. | |
When enabled by a SET CPU IDLE command, execution of a PAUS instruction will | |
idle the simulator. While idle, the simulator does not use any host system | |
processor time. Idle detection requires that the process clock and system | |
clock be set to calibrated timing. Idling is disabled by default. | |
Implementation notes: | |
1. Three timing sources in the simulator may be calibrated to wall-clock | |
time. These are the process clock, the system clock, and the ATC poll | |
timer. The process clock is always enabled and running, although the | |
PCLK register only increments if the CPU is not executing on the ICS. | |
The system clock and poll timer run continuously if their respective | |
devices are enabled. If the ATC is disabled, then the process clock | |
takes over polling for the simulation console. | |
The three sources must be synchronized to allow efficient simulator | |
idling. This is accomplished by designating the process clock as the | |
master device, which calls the SCP timer calibration routines, and | |
setting the system clock and ATC poll timer to the process clock wait. | |
All three devices will then enter their respective service routines | |
concurrently. | |
2. In hardware, the process clock period is fixed at one millisecond, and | |
the system clock period, while potentially variable, is set by MPE to one | |
millisecond with an interrupt every 100 ticks. These periods are too | |
short to allow the simulator to idle, as the host OS clock resolution is | |
typically also one millisecond. | |
Therefore, the process and system clock simulators are scheduled with | |
ten-millisecond service times, and the PCLK and counter registers are | |
incremented by ten for each event service. To present the correct values | |
when the registers are read, the counts are incremented by amounts | |
proportional to the fractions of the service intervals that have elapsed | |
when the reads occur. | |
3. In simulation, the TOS renamer is implemented by permanently assigning | |
the register names RA-RD to TR [0]-TR [3], respectively, and copying the | |
contents between registers to pop and push values. An alternate | |
implementation approach is to use a renaming register, RN, that tracks | |
the correspondence between registers and array entries, and to assign the | |
register names dynamically using modular indices, e.g., RA is TR [RN], RB | |
is TR [(RN + 1) & 3], etc. In lieu of copying, incrementing and | |
decrementing RN is done to pop and push values. | |
Both implementations were mocked up and timing measurements made. The | |
results were that copying values is much faster than mapping via array | |
index values, so this is the implementation chosen. | |
*/ | |
#include "hp3000_defs.h" | |
#include "hp3000_cpu.h" | |
#include "hp3000_cpu_ims.h" | |
#include "hp3000_io.h" | |
/* External I/O data structures */ | |
extern DEVICE iop_dev; /* I/O Processor */ | |
extern DEVICE sel_dev; /* Selector Channel */ | |
/* Program constants */ | |
#define PCLK_PERIOD mS (1) /* 1 millisecond process clock period */ | |
#define PCLK_MULTIPLIER 10 /* number of hardware process clock ticks per service */ | |
#define PCLK_RATE (1000 / PCLK_MULTIPLIER) /* process clock rate in ticks per second */ | |
#define UNIT_OPTS (UNIT_EIS) /* the standard equipment feature set */ | |
/* CPU global SCP data definitions */ | |
DEVICE cpu_dev; /* incomplete device structure */ | |
REG *sim_PC; /* the pointer to the P register */ | |
/* CPU global data structures */ | |
/* CPU registers */ | |
HP_WORD CIR = 0; /* current instruction register */ | |
HP_WORD NIR = 0; /* next instruction register */ | |
HP_WORD PB = 0; /* program base register */ | |
HP_WORD P = 0; /* program counter register */ | |
HP_WORD PL = 0; /* program limit register */ | |
HP_WORD PBANK = 0; /* program segment bank register */ | |
HP_WORD DL = 0; /* data limit register */ | |
HP_WORD DB = 0; /* data base register */ | |
HP_WORD DBANK = 0; /* data segment bank register */ | |
HP_WORD Q = 0; /* stack marker register */ | |
HP_WORD SM = 0; /* stack memory register */ | |
HP_WORD SR = 0; /* stack register counter */ | |
HP_WORD Z = 0; /* stack limit register */ | |
HP_WORD SBANK = 0; /* stack segment bank register */ | |
HP_WORD TR [4] = { 0, 0, 0, 0 }; /* top of stack registers */ | |
HP_WORD X = 0; /* index register */ | |
HP_WORD STA = 0; /* status register */ | |
HP_WORD SWCH = 0; /* switch register */ | |
HP_WORD CPX1 = 0; /* run-mode interrupt flags register */ | |
HP_WORD CPX2 = 0; /* halt-mode interrupt flags register */ | |
HP_WORD PCLK = 0; /* process clock register */ | |
HP_WORD CNTR = 0; /* microcode counter */ | |
/* Condition Code B lookup table. | |
Byte-oriented instructions set the condition code in the status word using | |
Pattern B (CCB). For this encoding: | |
CCG = ASCII numeric character | |
CCE = ASCII alphabetic character | |
CCL = ASCII special character | |
The simplest implementation of this pattern is a 256-way lookup table using | |
disjoint condition code flags. The SET_CCB macro uses this table to set the | |
condition code appropriate for the supplied operand into the status word. | |
*/ | |
const uint16 cpu_ccb_table [256] = { | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* NUL SOH STX ETX EOT ENQ ACK BEL */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* BS HT LF VT FF CR SO SI */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* DLE DC1 DC2 DC3 DC4 NAK SYN ETB */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* CAN EM SUB ESC FS GS RS US */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* spa ! " # $ % & ' */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* ( ) * + , - . / */ | |
CFG, CFG, CFG, CFG, CFG, CFG, CFG, CFG, /* 0 1 2 3 4 5 6 7 */ | |
CFG, CFG, CFL, CFL, CFL, CFL, CFL, CFL, /* 8 9 : ; < = > ? */ | |
CFL, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* @ A B C D E F G */ | |
CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* H I J K L M N O */ | |
CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* P Q R S T U V W */ | |
CFE, CFE, CFE, CFL, CFL, CFL, CFL, CFL, /* X Y Z [ \ ] ^ _ */ | |
CFL, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* ` a b c d e f g */ | |
CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* h i j k l m n o */ | |
CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* p q r s t u v w */ | |
CFE, CFE, CFE, CFL, CFL, CFL, CFL, CFL, /* x y z { | } ~ DEL */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 200 - 207 */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 210 - 217 */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 220 - 227 */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 230 - 237 */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 240 - 247 */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 250 - 257 */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 260 - 267 */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 270 - 277 */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 300 - 307 */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 310 - 317 */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 320 - 327 */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 330 - 337 */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 340 - 347 */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 350 - 357 */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 360 - 367 */ | |
CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL /* 370 - 377 */ | |
}; | |
/* CPU global state */ | |
jmp_buf cpu_save_env; /* the saved environment for microcode aborts */ | |
uint32 cpu_stop_flags; /* the simulation stop flag set */ | |
EXEC_STATE cpu_micro_state = halted; /* the microcode execution state */ | |
t_bool cpu_base_changed = FALSE; /* TRUE if any base register is changed */ | |
t_bool cpu_is_calibrated = TRUE; /* TRUE if the process clock is calibrated */ | |
UNIT *cpu_pclk_uptr = &cpu_unit; /* a pointer to the process clock unit */ | |
/* CPU local state */ | |
static uint32 sim_stops = 0; /* the current simulation stop flag settings */ | |
static uint32 cpu_speed = 1; /* the CPU speed, expressed as a multiplier of a real machine */ | |
static uint32 pclk_increment = 1; /* the process clock increment per event service */ | |
/* CPU local data structures */ | |
/* Main memory */ | |
#define MEMSIZE (cpu_unit.capac) /* the current memory size in 16-bit words */ | |
static uint16 *M = NULL; /* the pointer to the main memory allocation */ | |
/* Interrupt classification names */ | |
static const char *const interrupt_name [] = { /* class names, indexed by IRQ_CLASS */ | |
"integer overflow", /* 000 irq_Integer_Overflow */ | |
"bounds violation", /* 001 irq_Bounds_Violation */ | |
"illegal memory address error", /* 002 irq_Illegal_Address */ | |
"non-responding module error", /* 003 irq_Timeout */ | |
"system parity error", /* 004 irq_System_Parity */ | |
"address parity error", /* 005 irq_Address_Parity */ | |
"data parity error", /* 006 irq_Data_Parity */ | |
"module", /* 007 irq_Module */ | |
"external", /* 010 irq_External */ | |
"power fail", /* 011 irq_Power_Fail */ | |
"ICS trap", /* 012 irq_Trap */ | |
"dispatch", /* 013 irq_Dispatch */ | |
"exit" /* 014 irq_IXIT */ | |
}; | |
/* Trap classification names */ | |
static const char *const trap_name [] = { /* trap names, indexed by TRAP_CLASS */ | |
"no", /* 000 trap_None */ | |
"bounds violation", /* 001 trap_Bounds_Violation */ | |
NULL, /* 002 (unused) */ | |
NULL, /* 003 (unused) */ | |
NULL, /* 004 (unused) */ | |
NULL, /* 005 (unused) */ | |
NULL, /* 006 (unused) */ | |
NULL, /* 007 (unused) */ | |
NULL, /* 010 (unused) */ | |
NULL, /* 011 (unused) */ | |
NULL, /* 012 (unused) */ | |
NULL, /* 013 (unused) */ | |
NULL, /* 014 (unused) */ | |
NULL, /* 015 (unused) */ | |
NULL, /* 016 (unused) */ | |
NULL, /* 017 (unused) */ | |
"unimplemented instruction", /* 020 trap_Unimplemented */ | |
"STT violation", /* 021 trap_STT_Violation */ | |
"CST violation", /* 022 trap_CST_Violation */ | |
"DST violation", /* 023 trap_DST_Violation */ | |
"stack underflow", /* 024 trap_Stack_Underflow */ | |
"privileged mode violation", /* 025 trap_Privilege_Violation */ | |
NULL, /* 026 (unused) */ | |
NULL, /* 027 (unused) */ | |
"stack overflow", /* 030 trap_Stack_Overflow */ | |
"user", /* 031 trap_User */ | |
NULL, /* 032 (unused) */ | |
NULL, /* 033 (unused) */ | |
NULL, /* 034 (unused) */ | |
NULL, /* 035 (unused) */ | |
NULL, /* 036 (unused) */ | |
"absent code segment", /* 037 trap_CS_Absent */ | |
"trace", /* 040 trap_Trace */ | |
"STT entry uncallable", /* 041 trap_Uncallable */ | |
"absent data segment", /* 042 trap_DS_Absent */ | |
"power on", /* 043 trap_Power_On */ | |
"cold load", /* 044 trap_Cold_Load */ | |
"system halt" /* 045 trap_System_Halt */ | |
}; | |
/* 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. | |
Implementation notes: | |
1. The EIS was standard equipment for the Series II and III, so UNIT_EIS | |
should appear in their "typ" fields. However, the EIS instructions are | |
not currently implemented, so the value is omitted below. | |
*/ | |
struct FEATURE_TABLE { | |
uint32 typ; /* standard features plus typically configured options */ | |
uint32 opt; /* complete list of optional features */ | |
uint32 maxmem; /* maximum configurable memory in 16-bit words */ | |
}; | |
static const struct FEATURE_TABLE cpu_features [] = { /* features indexed by CPU_MODEL */ | |
{ 0, /* UNIT_SERIES_III */ | |
0, | |
1024 * 1024 }, | |
{ 0, /* UNIT_SERIES_II */ | |
0, | |
256 * 1024 } | |
}; | |
/* Memory access classification table */ | |
typedef struct { | |
HP_WORD *bank_ptr; /* a pointer to the bank register */ | |
DEVICE *device_ptr; /* a pointer to the accessing device */ | |
uint32 debug_flag; /* the debug flag for tracing */ | |
t_bool irq; /* TRUE if an interrupt is requested on error */ | |
char *const name; /* the classification name */ | |
} ACCESS_PROPERTIES; | |
static const ACCESS_PROPERTIES access [] = { /* indexed by ACCESS_CLASS */ | |
/* bank_ptr device_ptr debug_flag irq name */ | |
/* -------- ---------- ---------- ------ ------------------- */ | |
{ NULL, & iop_dev, DEB_MDATA, FALSE, "absolute" }, /* absolute_iop */ | |
{ NULL, & iop_dev, DEB_MDATA, FALSE, "dma" }, /* dma_iop */ | |
{ NULL, & sel_dev, DEB_MDATA, FALSE, "absolute" }, /* absolute_sel */ | |
{ NULL, & sel_dev, DEB_MDATA, FALSE, "dma" }, /* dma_sel */ | |
{ NULL, & cpu_dev, DEB_MDATA, TRUE, "absolute" }, /* absolute */ | |
{ NULL, & cpu_dev, DEB_MDATA, TRUE, "absolute" }, /* absolute_checked */ | |
{ & PBANK, & cpu_dev, DEB_FETCH, TRUE, "instruction fetch" }, /* fetch */ | |
{ & PBANK, & cpu_dev, DEB_FETCH, TRUE, "instruction fetch" }, /* fetch_checked */ | |
{ & PBANK, & cpu_dev, DEB_MDATA, TRUE, "program" }, /* program */ | |
{ & PBANK, & cpu_dev, DEB_MDATA, TRUE, "program" }, /* program_checked */ | |
{ & DBANK, & cpu_dev, DEB_MDATA, TRUE, "data" }, /* data */ | |
{ & DBANK, & cpu_dev, DEB_MDATA, TRUE, "data" }, /* data_checked */ | |
{ & SBANK, & cpu_dev, DEB_MDATA, TRUE, "stack" }, /* stack */ | |
{ & SBANK, & cpu_dev, DEB_MDATA, TRUE, "stack" } /* stack_checked */ | |
}; | |
/* CPU local SCP support routine declarations */ | |
static t_stat cpu_service (UNIT *uptr); | |
static t_stat cpu_reset (DEVICE *dptr); | |
static t_stat cpu_examine (t_value *eval_array, 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 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 new_option, CONST char *cptr, void *desc); | |
static t_stat show_stops (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 halt_mode_interrupt (HP_WORD device_number); | |
static t_stat machine_instruction (void); | |
/* CPU SCP data structures */ | |
/* Unit list. | |
The CPU unit holds the main memory capacity and is used to schedule the | |
process clock events. | |
Implementation notes: | |
1. The unit structure must be global for other modules to obtain the memory | |
size via the MEMSIZE macro, which references the "capac" field. | |
*/ | |
UNIT cpu_unit = { | |
UDATA (&cpu_service, UNIT_FIX | UNIT_BINK | UNIT_IDLE | UNIT_CALTIME, 0), PCLK_PERIOD * PCLK_MULTIPLIER | |
}; | |
/* Register list. | |
The CPU register list exposes the machine registers for user inspection and | |
modification. User flags describe the permitted and default display formats, | |
as follows: | |
- REG_A permits any display | |
- REG_B permits binary display | |
- REG_M defaults to CPU instruction mnemonic display | |
- REG_S defaults to CPU status mnemonic display | |
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 CNTR register is set to the value of the SR register when the | |
micromachine halts or pauses. This allows the SR value to be accessed by | |
the diagnostics. The top-of-stack registers are flushed to main memory | |
when the machine halts or pauses, which alters SR. | |
*/ | |
static REG cpu_reg [] = { | |
/* Macro Name Location Width Flags */ | |
/* ------ ------ ------------ ----- ------------------------ */ | |
{ ORDATA (CIR, CIR, 16), REG_M | REG_RO | REG_FIT }, /* current instruction register */ | |
{ ORDATA (NIR, NIR, 16), REG_M | REG_RO | REG_FIT }, /* next instruction register */ | |
{ ORDATA (PB, PB, 16), REG_FIT }, /* program base register */ | |
{ ORDATA (P, P, 16), REG_FIT }, /* program counter register */ | |
{ ORDATA (PL, PL, 16), REG_FIT }, /* program limit register */ | |
{ ORDATA (PBANK, PBANK, 4), REG_FIT }, /* program segment bank register */ | |
{ ORDATA (DL, DL, 16), REG_FIT }, /* data limit register */ | |
{ ORDATA (DB, DB, 16), REG_FIT }, /* data base register */ | |
{ ORDATA (DBANK, DBANK, 4), REG_FIT }, /* data segment bank register */ | |
{ ORDATA (Q, Q, 16), REG_FIT }, /* stack marker register */ | |
{ ORDATA (SM, SM, 16), REG_FIT }, /* stack memory register */ | |
{ ORDATA (SR, SR, 3), REG_FIT }, /* stack register counter */ | |
{ ORDATA (Z, Z, 16), REG_FIT }, /* stack limit register */ | |
{ ORDATA (SBANK, SBANK, 4), REG_FIT }, /* stack segment bank register */ | |
{ ORDATA (RA, TR [0], 16), REG_A | REG_FIT }, /* top of stack register */ | |
{ ORDATA (RB, TR [1], 16), REG_A | REG_FIT }, /* top of stack - 1 register */ | |
{ ORDATA (RC, TR [2], 16), REG_A | REG_FIT }, /* top of stack - 2 register */ | |
{ ORDATA (RD, TR [3], 16), REG_A | REG_FIT }, /* top of stack - 3 register */ | |
{ ORDATA (X, X, 16), REG_A | REG_FIT }, /* index register */ | |
{ ORDATA (STA, STA, 16), REG_S | REG_B | REG_FIT }, /* status register */ | |
{ ORDATA (SWCH, SWCH, 16), REG_A | REG_FIT }, /* switch register */ | |
{ ORDATA (CPX1, CPX1, 16), REG_B | REG_FIT }, /* run-mode interrupt flags */ | |
{ ORDATA (CPX2, CPX2, 16), REG_B | REG_FIT }, /* halt-mode interrupt flags */ | |
{ ORDATA (PCLK, PCLK, 16), REG_FIT }, /* process clock register */ | |
{ ORDATA (CNTR, CNTR, 6), REG_HRO | REG_FIT }, /* microcode counter */ | |
{ ORDATA (WRU, sim_int_char, 8), REG_HRO }, /* SCP interrupt character */ | |
{ ORDATA (BRK, sim_brk_char, 8), REG_HRO }, /* SCP break character */ | |
{ ORDATA (DEL, sim_del_char, 8), REG_HRO }, /* SCP delete character */ | |
{ NULL } | |
}; | |
/* Modifier list */ | |
static MTAB cpu_mod [] = { | |
/* Mask Value Match Value Print String Match String Validation Display Descriptor */ | |
/* ------------ --------------- ------------------- ------------ ----------- ------- ---------- */ | |
{ UNIT_MODEL, UNIT_SERIES_II, "Series II", NULL, &set_model, NULL, NULL }, | |
{ UNIT_MODEL, UNIT_SERIES_III, "Series III", "III", &set_model, NULL, NULL }, | |
{ UNIT_EIS, UNIT_EIS, "EIS", NULL, &set_option, NULL, NULL }, | |
{ UNIT_EIS, 0, "no EIS", "NOEIS", NULL, NULL, NULL }, | |
{ UNIT_CALTIME, UNIT_CALTIME, "calibrated timing", "CALTIME", NULL, NULL, NULL }, | |
{ UNIT_CALTIME, 0, "realistic timing", "REALTIME", NULL, NULL, NULL }, | |
/* Entry Flags Value Print String Match String Validation Display Descriptor */ | |
/* ------------------- ----------- ------------ ------------ ------------- -------------- ---------- */ | |
{ MTAB_XDV, 128 * 1024, NULL, "128K", &set_size, NULL, NULL }, | |
{ MTAB_XDV, 256 * 1024, NULL, "256K", &set_size, NULL, NULL }, | |
{ MTAB_XDV, 384 * 1024, NULL, "384K", &set_size, NULL, NULL }, | |
{ MTAB_XDV, 512 * 1024, NULL, "512K", &set_size, NULL, NULL }, | |
{ MTAB_XDV, 768 * 1024, NULL, "768K", &set_size, NULL, NULL }, | |
{ MTAB_XDV, 1024 * 1024, NULL, "1024K", &set_size, NULL, NULL }, | |
{ MTAB_XDV, 0, "IDLE", "IDLE", &sim_set_idle, &sim_show_idle, NULL }, | |
{ MTAB_XDV, 0, NULL, "NOIDLE", &sim_clr_idle, NULL, 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, 0, "SPEED", NULL, NULL, &show_speed, NULL }, | |
{ 0 } | |
}; | |
/* Debugging trace list */ | |
static DEBTAB cpu_deb [] = { | |
{ "INSTR", DEB_INSTR }, /* instruction executions */ | |
{ "DATA", DEB_MDATA }, /* data accesses */ | |
{ "FETCH", DEB_FETCH }, /* instruction fetches */ | |
{ "REG", DEB_REG }, /* register values */ | |
{ "PSERV", DEB_PSERV }, /* process clock service events */ | |
{ NULL, 0 } | |
}; | |
/* Debugging stop list */ | |
static DEBTAB cpu_stop [] = { | |
{ "LOOP", SS_LOOP }, /* stop on an infinite loop */ | |
{ "PAUSE", SS_PAUSE }, /* stop on a PAUS instruction */ | |
{ "UNDEF", SS_UNDEF }, /* stop on an undefined instruction */ | |
{ "UNIMPL", SS_UNIMPL }, /* stop on an unimplemented instruction */ | |
{ 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_WIDTH, /* address width */ | |
1, /* address increment */ | |
8, /* data radix */ | |
16, /* data width */ | |
&cpu_examine, /* examine routine */ | |
&cpu_deposit, /* deposit routine */ | |
&cpu_reset, /* reset routine */ | |
NULL, /* boot routine */ | |
NULL, /* attach routine */ | |
NULL, /* detach routine */ | |
NULL, /* device information block pointer */ | |
DEV_DEBUG, /* device flags */ | |
0, /* debug control flags */ | |
cpu_deb, /* debug flag name array */ | |
NULL, /* memory size change routine */ | |
NULL /* logical device name */ | |
}; | |
/* CPU global SCP support routines */ | |
/* Execute CPU instructions. | |
This is the instruction decode routine for the HP 3000. It is called from | |
the simulator control program 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 (or the current | |
instruction, in the case of a simulator stop during a PAUS instruction or | |
after the first of two stack operations). | |
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 device number), initializing the I/O processor and | |
channels, and setting the RUN switch if no other front panel switches are | |
pressed. These actions accommodate reconfiguration of the I/O device | |
settings and program counter while the simulator was stopped. The prelude | |
also responds to one command-line switch: if "-B" is specified, the current | |
set of simulation stop conditions is bypassed for the first instruction | |
executed. This allows, e.g., a PAUS instruction to be bypassed or an | |
unimplemented instruction trap to be taken. | |
Second, the microcode abort mechanism is set up. Microcode 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, e.g., due to a memory access violation. It corresponds to direct | |
microcode jumps out of the execution sequence and to the appropriate trap | |
handlers. | |
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 includes execution of a HALT | |
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 I/O channel cycles. During | |
instruction execution, the CIR register contains the currently executing | |
instruction, the NIR register contains the next instruction to execute, and | |
the P register points to the memory location two instructions ahead of the | |
current 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 CPU process clock, 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. | |
If enabled, the simulator is idled when a PAUS instruction has been executed | |
and no service requests for the multiplexer or selector channels are active. | |
Execution of a PAUS instruction suspends the fetch-and-execute process until | |
an interrupt occurs or the simulator is stopped and then resumed with a GO -B | |
or RUN -B command. | |
The HP 3000 is a microcoded machine. In hardware, the micromachine is always | |
executing microinstructions, even when the CPU is "halted." The halt/run | |
state is simply a flip-flop setting, reflected in bit 15 of the CPX2 | |
register, that determines whether the "halt-mode" or "run-mode" microprogram | |
is currently executing. | |
In simulation, the "cpu_micro_state" variable indicates the state of the | |
micromachine, i.e., which section of the microcode it is executing, while | |
CPX2 bit 15 indicates whether the macromachine is halted or running. The | |
micromachine may be in one of four states: | |
- running : the run-mode fetch-and-execute microcode is executing | |
- paused : the run-mode PAUS instruction microcode is executing | |
- loading : the halt-mode COLD LOAD microcode is executing | |
- halted : the halt-mode front panel microcode is executing | |
Simulation provides a variety of stop conditions that break instruction | |
execution and return to the SCP prompt with the CPU still in run mode. These | |
have no analog in hardware; the only way to stop the CPU is to press the HALT | |
button on the front panel, which shifts the micromachine into halt-mode | |
microcode execution. When any of these conditions occur, the micromachine | |
state is set to "halted," but the CPX2 run flag is remains set unless the | |
stop was caused by execution of a HALT instruction. Resuming execution with | |
a STEP, CONT, GO, or RUN command proceeds as though the hardware RUN switch | |
was pressed after a programmed halt. This provides the proper semantics for | |
seamlessly stopping and restarting instruction execution. | |
A microcode abort is performed by executing 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 Segment Transfer Table | |
index of the trap handler in the lower word and an optional parameter in the | |
upper word. Aborts are invoked by the MICRO_ABORT macro, which takes as its | |
parameter a trap classification value, e.g.: | |
MICRO_ABORT (trap_Privilege_Violation); | |
MICRO_ABORT (trap_Integer_Zero_Divide); | |
Some aborts require an additional parameter and must be invoked by the | |
MICRO_ABORTP macro, which takes a trap classification value and a | |
trap-specific value as parameters. The traps that require additional | |
parameters are: | |
MICRO_ABORTP (trap_CST_Violation, segment_number); | |
MICRO_ABORTP (trap_STT_Violation, segment_number); | |
MICRO_ABORTP (trap_CS_Absent, label/n/0); | |
MICRO_ABORTP (trap_DS_Absent, DST_number); | |
MICRO_ABORTP (trap_Uncallable, label); | |
MICRO_ABORTP (trap_Trace, label/n/0); | |
MICRO_ABORTP (trap_User, trap_number); | |
trap_User is not usually called explicitly via MICRO_ABORTP; rather, | |
MICRO_ABORT is used with one of the specific user-trap identifiers, e.g., | |
trap_Integer_Overflow, trap_Float_Overflow, trap_Decimal_Overflow, etc., that | |
supplies both the trap classification and the trap parameter value. | |
In addition, user traps must be enabled by setting the T-bit in the status | |
word. If the T bit is not set, a user trap sets the O-bit (overflow) in the | |
status word and resumes execution with the next instruction instead of | |
invoking the user trap handler. | |
When an abort occurs, an equivalent PCAL to the appropriate STT entry is set | |
up. Then execution drops into the instruction loop to execute the first | |
instruction of the trap handler. | |
When the instruction loop is exited, the CPU process clock and system clock | |
registers are updated, the micromachine is halted, and control returns to | |
SCP. Upon return, P points at the next instruction to execute, i.e., the | |
instruction that will execute when the instruction loop is reentered. | |
If the micromachine is paused, then P is reset to point to the PAUS | |
instruction, which will be reexecuted when the routine is reentered. If it | |
is running, then P is reset to point to the current instruction if the stop | |
allows it to be rerun, or at the next instruction. | |
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, after a microcode abort, we cannot depend upon the values of | |
any local variables. | |
2. In hardware, the NEXT microcode order present at the end of each | |
instruction transfers the NIR content to the CIR, reads the memory word | |
at P into the NIR, and increments P. However, if an interrupt is | |
present, then this action is omitted, and a microcode jump is performed | |
to control store location 3, which then jumps to the microcoded interrupt | |
handler. In simulation, the CIR/NIR/P update is performed before the | |
next instruction is executed, rather than after the last instruction | |
completes, so that interrupts are handled before updating. | |
In addition, the NEXT action is modified in hardware if the NIR contains | |
a stack instruction with a non-NOP B stackop. In this case, NEXT | |
transfers the NIR content to the CIR, reads the memory word at P into the | |
NIR, but does not increment P. Instead, the R bit of the status register | |
is set to indicate that a B stackop is pending. When the NEXT at the | |
completion of the A stackop is executed, the NIR and CIR are untouched, | |
but P is incremented, and the R bit is cleared. This ensures that if an | |
interrupt or trap occurs between the stackops, P will point correctly at | |
the next instruction to be executed. | |
In simulation, following the hardware would require testing the NIR for a | |
non-NOP B stackop at every pass through the instruction execution loop. | |
To avoid this, the NEXT simulation unilaterally increments P, rather than | |
only when a B stackop is not present, and the stack instruction executor | |
tests for the B stackop and sets the R bit there. However, by that time, | |
P has already been incremented, so we decrement it there to return it to | |
the correct value. | |
3. The System Halt trap has no handler. Instead, the simulator is halted, | |
and control returns to the SCP prompt. | |
4. The trace display for a trap reports the parameter value supplied with | |
the microcode abort. This is not necessarily the same as the parameter | |
that is pushed on the stack for the trap handler. As some traps, e.g., | |
trap_CST_Violation, can cause a System Halt, the placement of the trace | |
call is dictated by the desire to report both the original trap and the | |
System Halt trap, even though the placement results in the display of the | |
incoming parameter value, rather than the stacked parameter value. | |
*/ | |
t_stat sim_instr (void) | |
{ | |
static const char *const stack_formats [] = { /* stack register display formats, indexed by SR */ | |
BOV_FORMAT " ", /* SR = 0 format */ | |
BOV_FORMAT " A %06o, ", /* SR = 1 format */ | |
BOV_FORMAT " A %06o, B %06o, ", /* SR = 2 format */ | |
BOV_FORMAT " A %06o, B %06o, C %06o, ", /* SR = 3 format */ | |
BOV_FORMAT " A %06o, B %06o, C %06o, D %06o, " /* SR = 4 format */ | |
}; | |
int abortval; | |
HP_WORD label, parameter, device; | |
TRAP_CLASS trap; | |
t_stat status = SCPE_OK; | |
/* Instruction prelude */ | |
if (sim_switches & SWMASK ('B')) /* if a simulation stop bypass was requested */ | |
cpu_stop_flags = SS_BYPASSED; /* then clear the stop flags for the first instruction */ | |
else /* otherwise */ | |
cpu_stop_flags = sim_stops; /* set the stops as indicated */ | |
if (hp_device_conflict ()) /* if the check for device assignment consistency fails */ | |
status = SCPE_STOP; /* then inhibit execution */ | |
else { /* otherwise */ | |
device = iop_initialize (); /* initialize the IOP */ | |
mpx_initialize (); /* and the multiplexer channel */ | |
sel_initialize (); /* and the selector channel */ | |
if ((CPX2 & CPX2_IRQ_SET) == 0) /* if no halt-mode interrupt is present */ | |
CPX2 |= cpx2_RUNSWCH; /* then assume a RUN command */ | |
} | |
/* Microcode abort processor */ | |
abortval = setjmp (cpu_save_env); /* set the microcode abort handler */ | |
if (abortval) { /* if a microcode abort occurred */ | |
trap = TRAP (abortval); /* then get the trap classification */ | |
parameter = PARAM (abortval); /* and the optional parameter */ | |
label = TO_LABEL (LABEL_IRQ, trap); /* form the label from the STT number */ | |
dprintf (cpu_dev, DEB_INSTR, BOV_FORMAT "%s trap%s\n", | |
PBANK, P - 1 & R_MASK, parameter, trap_name [trap], | |
(trap == trap_User && !(STA & STATUS_T) ? " (disabled)" : "")); | |
switch (trap) { /* dispatch on the trap classification */ | |
case trap_None: /* trap_None should never occur */ | |
case trap_System_Halt: | |
CNTR = SR; /* copy the stack register to the counter */ | |
cpu_flush (); /* and flush the TOS registers to memory */ | |
RA = parameter; /* set RA to the parameter (system halt condition) */ | |
CPX2 = CPX2 & ~cpx2_RUN | cpx2_SYSHALT; /* halt the CPU and set the system halt flag */ | |
status = STOP_SYSHALT; /* and report the system halt condition */ | |
label = 0; /* there is no trap handler for a system halt */ | |
break; | |
case trap_CST_Violation: | |
if (STT_SEGMENT (parameter) <= ISR_SEGMENT) /* if the trap occurred in segment 1 */ | |
MICRO_ABORT (trap_SysHalt_CSTV_1); /* then the failure is fatal */ | |
/* fall into the next trap handler */ | |
case trap_STT_Violation: | |
if (STT_SEGMENT (parameter) <= ISR_SEGMENT) /* if the trap occurred in segment 1 */ | |
MICRO_ABORT (trap_SysHalt_STTV_1); /* then the failure is fatal */ | |
/* fall into the next trap handler */ | |
case trap_Unimplemented: | |
case trap_DST_Violation: | |
case trap_Stack_Underflow: | |
case trap_Privilege_Violation: | |
case trap_Bounds_Violation: | |
parameter = label; /* the label is the parameter for these traps */ | |
/* fall into the next trap handler */ | |
case trap_DS_Absent: | |
cpu_flush (); /* flush the TOS registers to memory */ | |
cpu_mark_stack (); /* and then write a stack marker */ | |
/* fall into the next trap handler */ | |
case trap_Uncallable: | |
break; /* set up the trap handler */ | |
case trap_User: | |
if (STA & STATUS_T) { /* if user traps are enabled */ | |
STA &= ~STATUS_O; /* then clear overflow status */ | |
cpu_flush (); /* and flush the TOS registers to memory */ | |
cpu_mark_stack (); /* and write a stack marker */ | |
} /* and set up the trap handler */ | |
else { /* otherwise in lieu of trapping */ | |
STA |= STATUS_O; /* set overflow status */ | |
label = 0; /* and continue execution with the next instruction */ | |
} | |
break; | |
case trap_CS_Absent: | |
if (CPX1 & cpx1_ICSFLAG) /* if the trap occurred while on the ICS */ | |
MICRO_ABORT (trap_SysHalt_Absent_ICS); /* then the failure is fatal */ | |
else if (STT_SEGMENT (STA) <= ISR_SEGMENT) /* otherwise if the trap occurred in segment 1 */ | |
MICRO_ABORT (trap_SysHalt_Absent_1); /* then the failure is fatal */ | |
break; /* otherwise set up the trap handler */ | |
case trap_Trace: | |
if (STT_SEGMENT (STA) <= ISR_SEGMENT) /* if the trap occurred in segment 1 */ | |
MICRO_ABORT (trap_SysHalt_Trace_1); /* then the failure is fatal */ | |
break; /* otherwise set up the trap handler */ | |
case trap_Cold_Load: /* this trap executes on the ICS */ | |
status = STOP_CLOAD; /* report that the cold load is complete */ | |
/* fall into trap_Stack_Overflow */ | |
case trap_Stack_Overflow: /* this trap executes on the ICS */ | |
if (CPX1 & cpx1_ICSFLAG) /* so if the trap occurred while on the ICS */ | |
MICRO_ABORT (trap_SysHalt_Overflow_ICS); /* then the failure is fatal */ | |
cpu_setup_ics_irq (irq_Trap, trap); /* otherwise, set up the ICS */ | |
break; /* and then the trap handler */ | |
case trap_Power_On: /* this trap executes on the ICS */ | |
status = SCPE_INCOMP; /* but is not implemented yet */ | |
label = 0; /* the trap handler is not called */ | |
break; | |
} /* all cases are handled */ | |
if (label != 0) { /* if the trap handler is to be called */ | |
STA = STATUS_M; /* then clear the status and enter privileged mode */ | |
SM = SM + 1 & R_MASK; /* increment the stack pointer */ | |
cpu_write_memory (stack, SM, parameter); /* and push the parameter on the stack */ | |
X = CIR; /* save the current instruction for restarting */ | |
cpu_call_procedure (label); /* set up PB, P, PL, and STA to call the procedure */ | |
cpu_base_changed = TRUE; /* one or more base registers have changed */ | |
} | |
sim_interval = sim_interval - 1; /* count the execution cycle that aborted */ | |
} | |
/* Instruction loop */ | |
while (status == SCPE_OK) { /* execute until simulator status prevents continuation */ | |
if (sim_interval <= 0) { /* if an event timeout has expired */ | |
status = sim_process_event (); /* then call the event service */ | |
if (status != SCPE_OK) /* if the service failed */ | |
break; /* then abort execution and report the failure */ | |
} | |
if (sel_request) /* if a selector channel request is pending */ | |
sel_service (1); /* then service it */ | |
if (mpx_request_set) /* if a multiplexer channel request is pending */ | |
mpx_service (1); /* then service it */ | |
if (iop_interrupt_request_set /* if a hardware interrupt request is pending */ | |
&& STA & STATUS_I /* and interrupts are enabled */ | |
&& CIR != SED_1) /* and not deferred by a SED 1 instruction */ | |
device = iop_poll (); /* then poll to acknowledge the request */ | |
if (cpu_micro_state == running) /* if the micromachine is running */ | |
if (CPX1 & CPX1_IRQ_SET) /* then if a run-mode interrupt is pending */ | |
cpu_run_mode_interrupt (device); /* then service it */ | |
else if (sim_brk_summ /* otherwise if a breakpoint exists */ | |
&& sim_brk_test (TO_PA (PBANK, P - 1 & LA_MASK), /* at the next location */ | |
BP_EXEC)) { /* to execute */ | |
status = STOP_BRKPNT; /* then stop the simulation */ | |
sim_interval = sim_interval + 1; /* and don't count the cycle */ | |
} | |
else { /* otherwise execute the next instruction */ | |
if (DPRINTING (cpu_dev, DEB_REG)) { /* if register tracing is enabled */ | |
hp_debug (&cpu_dev, DEB_REG, /* then output the active TOS registers */ | |
stack_formats [SR], | |
SBANK, SM, SR, RA, RB, RC, RD); | |
fprintf (sim_deb, "X %06o, %s\n", /* output the index and status registers */ | |
X, fmt_status (STA)); | |
if (cpu_base_changed) { /* if the base registers have changed since last time */ | |
hp_debug (&cpu_dev, DEB_REG, /* then output the base registers */ | |
BOV_FORMAT " PB %06o, PL %06o, DL %06o, DB %06o, Q %06o, Z %06o\n", | |
DBANK, 0, STA & STATUS_CS_MASK, | |
PB, PL, DL, DB, Q, Z); | |
cpu_base_changed = FALSE; /* clear the base register change flag */ | |
} | |
} | |
if (!(STA & STATUS_R)) { /* (NEXT) if the right-hand stack op is not pending */ | |
CIR = NIR; /* then update the current instruction */ | |
cpu_read_memory (fetch, P, &NIR); /* and load the next instruction */ | |
} | |
P = P + 1 & R_MASK; /* point to the following instruction */ | |
if (DEBUG_PRI (cpu_dev, DEB_INSTR)) { /* if instruction tracing is enabled */ | |
sim_eval [0] = CIR; /* then save the instruction that will be executed */ | |
sim_eval [1] = NIR; /* and the following word for evaluation */ | |
hp_debug (&cpu_dev, DEB_INSTR, BOV_FORMAT, /* print the address and the instruction opcode */ | |
PBANK, P - 2 & R_MASK, CIR); /* as an octal value */ | |
if (fprint_cpu (sim_deb, sim_eval, 0, SIM_SW_STOP) != 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 */ | |
} | |
status = machine_instruction (); /* execute one machine instruction */ | |
cpu_stop_flags = sim_stops; /* reset the stop flags as indicated */ | |
} | |
else if (cpu_micro_state == paused) { /* otherwise if the micromachine is paused */ | |
if (CPX1 & CPX1_IRQ_SET) /* then if a run-mode interrupt is pending */ | |
cpu_run_mode_interrupt (device); /* then service it */ | |
else if (sim_idle_enab /* otherwise if idling is enabled */ | |
&& ! sel_request && mpx_request_set == 0) /* and there are no channel requests pending */ | |
sim_idle (TMR_PCLK, FALSE); /* then idle the simulator */ | |
} | |
else if (CPX2 & CPX2_IRQ_SET) /* otherwise if a halt-mode interrupt is pending */ | |
status = halt_mode_interrupt (device); /* then service it */ | |
sim_interval = sim_interval - 1; /* count the execution cycle */ | |
} /* and continue with the instruction loop */ | |
/* Instruction postlude */ | |
cpu_update_pclk (); /* update the process clock */ | |
clk_update_counter (); /* and system clock counters */ | |
if (cpu_micro_state == paused) /* if the micromachine is paused */ | |
P = P - 2 & R_MASK; /* then set P to point to the PAUS instruction */ | |
else if (cpu_micro_state == running) /* otherwise if it is running */ | |
if (status <= STOP_RERUN) /* then if the instruction will be rerun when resumed */ | |
P = P - 2 & R_MASK; /* then set P to point to it */ | |
else /* otherwise */ | |
P = P - 1 & R_MASK; /* set P to point to the next instruction */ | |
cpu_micro_state = halted; /* halt the micromachine */ | |
dprintf (cpu_dev, cpu_dev.dctrl, BOV_FORMAT "simulation stop: %s\n", | |
PBANK, P, STA, sim_error_text (status)); | |
return status; /* return the reason for the stop */ | |
} | |
/* CPU global utility routines */ | |
/* Read a word from memory. | |
Read and return a word from memory at the indicated offset and implied bank. | |
If the access succeeds, the routine returns TRUE. If the accessed word is | |
outside of physical memory, the Illegal Address interrupt flag is set for | |
CPU accesses, the value is set to 0, and the routine returns FALSE. If | |
access checking is requested, and the check fails, a Bounds Violation trap is | |
taken. | |
On entry, "offset" is a logical offset into the memory bank implied by the | |
access classification, except for absolute and DMA accesses, for which | |
"offset" is a physical address. CPU access classifications other than fetch | |
may be checked or unchecked. Checked accesses must specify locations within | |
the corresponding segments (PB <= ea <= PL for program, or DL <= ea <= S for | |
data or stack) unless the CPU in is privileged mode, and those that reference | |
the TOS locations return values from the TOS registers instead of memory. | |
For checked data and stack accesses, there are three cases, depending on the | |
effective address: | |
- EA >= DL and EA <= SM : read from memory | |
- EA > SM and EA <= SM + SR : read from a TOS register if bank = stack bank | |
- EA < DL or EA > SM + SR : trap if not privileged, else read from memory | |
Implementation notes: | |
1. The physical address is formed by merging the bank and offset without | |
masking either value to their respective register sizes. Masking is not | |
necessary, as it was done when the bank registers were loaded, and it is | |
faster to avoid it. Primarily, though, it is not done so that an invalid | |
bank register value (e.g., loaded from a corrupted stack) will generate | |
an illegal address interrupt and so will pinpoint the problem for | |
debugging. | |
2. In hardware, bounds checking is performed explicitly by microcode. In | |
simulation, bounds checking is performed explicitly by employing the | |
"_checked" versions of the desired access classifications. | |
3. The "_iop" and "_sel" classifications serve only to select the correct | |
accessing device pointer and illegal address interrupt request state. | |
*/ | |
t_bool cpu_read_memory (ACCESS_CLASS classification, uint32 offset, HP_WORD *value) | |
{ | |
uint32 bank, address; | |
if (access [classification].bank_ptr == NULL) { /* if this is an absolute or DMA access */ | |
address = offset; /* then the "offset" is already a physical address */ | |
bank = TO_BANK (offset); /* separate the bank and offset */ | |
offset = TO_OFFSET (offset); /* in case tracing is active */ | |
} | |
else { /* otherwise the bank register is implied */ | |
bank = *access [classification].bank_ptr; /* by the access classification */ | |
address = bank << LA_WIDTH | offset; /* form the physical address with the supplied offset */ | |
} | |
if (address >= MEMSIZE) { /* if this access is beyond the memory size */ | |
if (access [classification].irq) /* then if an interrupt is requested */ | |
CPX1 |= cpx1_ILLADDR; /* then set the Illegal Address interrupt */ | |
*value = 0; /* return a zero value */ | |
return FALSE; /* and indicate failure to the caller */ | |
} | |
else { /* otherwise the access is within the memory range */ | |
switch (classification) { /* so dispatch on the access classification */ | |
case absolute_iop: | |
case dma_iop: | |
case absolute_sel: | |
case dma_sel: | |
case absolute: | |
case fetch: | |
case program: | |
case data: | |
case stack: | |
*value = (HP_WORD) M [address]; /* unchecked access values comes from memory */ | |
break; | |
case absolute_checked: | |
if (offset > SM && offset <= SM + SR && bank == SBANK) /* if the offset is within the TOS */ | |
*value = TR [SM + SR - offset]; /* then the value comes from a TOS register */ | |
else /* otherwise */ | |
*value = (HP_WORD) M [address]; /* the value comes from memory */ | |
break; | |
case fetch_checked: | |
if (PB <= offset && offset <= PL) /* if the offset is within the program segment bounds */ | |
*value = (HP_WORD) M [address]; /* then the value comes from memory */ | |
else /* otherwise */ | |
MICRO_ABORT (trap_Bounds_Violation); /* trap for a bounds violation */ | |
break; | |
case program_checked: | |
if (PB <= offset && offset <= PL || PRIV) /* if the offset is within bounds or is privileged */ | |
*value = (HP_WORD) M [address]; /* then the value comes from memory */ | |
else /* otherwise */ | |
MICRO_ABORT (trap_Bounds_Violation); /* trap for a bounds violation */ | |
break; | |
case data_checked: | |
case stack_checked: | |
if (offset > SM && offset <= SM + SR && bank == SBANK) /* if the offset is within the TOS */ | |
*value = TR [SM + SR - offset]; /* then the value comes from a TOS register */ | |
else if (DL <= offset && offset <= SM + SR || PRIV) /* if the offset is within bounds or is privileged */ | |
*value = (HP_WORD) M [address]; /* then the value comes from memory */ | |
else /* otherwise */ | |
MICRO_ABORT (trap_Bounds_Violation); /* trap for a bounds violation */ | |
break; | |
} /* all cases are handled */ | |
dpprintf (access [classification].device_ptr, access [classification].debug_flag, | |
BOV_FORMAT " %s%s\n", bank, offset, *value, | |
access [classification].name, | |
access [classification].debug_flag == DEB_MDATA ? " read" : ""); | |
return TRUE; /* indicate success with the returned value stored */ | |
} | |
} | |
/* Write a word to memory. | |
Write a word to memory at the indicated offset and implied bank. If the | |
write succeeds, the routine returns TRUE. If the accessed location is outside | |
of physical memory, the Illegal Address interrupt flag is set for CPU | |
accesses, the write is ignored, and the routine returns FALSE. If access | |
checking is requested, and the check fails, a Bounds Violation trap is taken. | |
For checked data and stack accesses, there are three cases, depending on the | |
effective address: | |
- EA >= DL and EA <= SM + SR : write to memory | |
- EA > SM and EA <= SM + SR : write to a TOS register if bank = stack bank | |
- EA < DL or EA > SM + SR : trap if not privileged, else write to memory | |
Note that cases 1 and 2 together imply that a write to a TOS register also | |
writes through to the underlying memory. | |
Implementation notes: | |
1. The physical address is formed by merging the bank and offset without | |
masking either value to their respective register sizes. Masking is not | |
necessary, as it was done when the bank registers were loaded, and it is | |
faster to avoid it. Primarily, though, it is not done so that an invalid | |
bank register value (e.g., loaded from a corrupted stack) will generate | |
an illegal address interrupt and so will pinpoint the problem for | |
debugging. | |
2. In hardware, bounds checking is performed explicitly by microcode. In | |
simulation, bounds checking is performed explicitly by employing the | |
"_checked" versions of the desired access classifications. | |
3. The Series II microcode shows that only the STOR and STD instructions | |
write through to memory when the effective address is in a TOS register. | |
However, in simulation, all (checked) stack and data writes will write | |
through. | |
4. The "_iop" and "_sel" classifications serve only to select the correct | |
accessing device pointer and illegal address interrupt request state. | |
*/ | |
t_bool cpu_write_memory (ACCESS_CLASS classification, uint32 offset, HP_WORD value) | |
{ | |
uint32 bank, address; | |
if (access [classification].bank_ptr == NULL) { /* if this is an absolute or DMA access */ | |
address = offset; /* then "offset" is already a physical address */ | |
bank = TO_BANK (offset); /* separate the bank and offset */ | |
offset = TO_OFFSET (offset); /* in case tracing is active */ | |
} | |
else { /* otherwise the bank register is implied */ | |
bank = *access [classification].bank_ptr; /* by the access classification */ | |
address = bank << LA_WIDTH | offset; /* form the physical address with the supplied offset */ | |
} | |
if (address >= MEMSIZE) { /* if this access is beyond the memory size */ | |
if (access [classification].irq) /* then if an interrupt is requested */ | |
CPX1 |= cpx1_ILLADDR; /* then set the Illegal Address interrupt */ | |
return FALSE; /* indicate failure to the caller */ | |
} | |
else { /* otherwise the access is within the memory range */ | |
switch (classification) { /* so dispatch on the access classification */ | |
case absolute_iop: | |
case dma_iop: | |
case absolute_sel: | |
case dma_sel: | |
case absolute: | |
case data: | |
case stack: | |
M [address] = (uint16) value; /* write the value to memory */ | |
break; | |
case absolute_checked: | |
if (offset > SM && offset <= SM + SR && bank == SBANK) /* if the offset is within the TOS */ | |
TR [SM + SR - offset] = value; /* then write the value to a TOS register */ | |
else /* otherwise */ | |
M [address] = (uint16) value; /* write the value to memory */ | |
break; | |
case data_checked: | |
case stack_checked: | |
if (offset > SM && offset <= SM + SR && bank == SBANK) /* if the offset is within the TOS */ | |
TR [SM + SR - offset] = value; /* then write the value to a TOS register */ | |
if (DL <= offset && offset <= SM + SR || PRIV) /* if the offset is within bounds or is privileged */ | |
M [address] = (uint16) value; /* then write the value to memory */ | |
else /* otherwise */ | |
MICRO_ABORT (trap_Bounds_Violation); /* trap for a bounds violation */ | |
break; | |
case fetch: | |
case fetch_checked: | |
case program: | |
case program_checked: /* these classes cannot be used for writing */ | |
CPX1 |= cpx1_ADDRPAR; /* so set an Address Parity Error interrupt */ | |
return FALSE; /* and indicate failure to the caller */ | |
} /* all cases are handled */ | |
dpprintf (access [classification].device_ptr, access [classification].debug_flag, | |
BOV_FORMAT " %s write\n", bank, offset, value, | |
access [classification].name); | |
return TRUE; /* indicate success with the value written */ | |
} | |
} | |
/* Process a run-mode interrupt. | |
This routine is called when one or more of the interrupt request bits are set | |
in the CPX1 register. The highest-priority request is identified and | |
cleared, the interrupt classification and parameter are established, and the | |
associated interrupt handler is set up for execution. On return, the CPU has | |
been configured and is ready to execute the first instruction in the handler. | |
On entry, the routine first checks for an external interrupt alone; this is | |
done to improve performance, as this is the most common case. If some other | |
interrupt is requested, or if multiple interrupts are requested, the CPX1 | |
register is scanned from MSB to LSB to identify the request. | |
Implementation notes: | |
1. In hardware, halting the CPU while a PAUS instruction is executing leaves | |
P pointing to the following instruction, which is executed when the RUN | |
button is pressed. In simulation, this action occurs only when execution | |
is resumed with the "-B" option to bypass the pause. Otherwise, resuming | |
(or stepping) continues with the PAUS instruction. | |
If an interrupt occurs while PAUS is executing, the interrupt handler | |
will return to the instruction following the PAUS, as though HALT and RUN | |
had been pressed. This occurs because P normally points two instructions | |
beyond the current instruction, so stacking the value P - 1 during the | |
interrupt will return to the next instruction to be executed. When | |
resuming the PAUS instruction from a simulation stop with an interrupt | |
pending, though, P is set so that P - 1 points to the PAUS instruction, | |
which is (correctly) the next instruction to execute on resumption. | |
Stacking the usual P - 1 value, therefore, will cause the interrupt | |
handler to return to the PAUS instruction instead of to the instruction | |
following, as would have occurred had a simulation stop not been | |
involved. | |
Therefore, this case is detected when a PAUS instruction is in the CIR | |
but the micromachine state is "running" instead of "paused", and P is | |
incremented so that the return will be to the instruction following PAUS. | |
*/ | |
void cpu_run_mode_interrupt (HP_WORD device_number) | |
{ | |
HP_WORD request_set, request_count, parameter; | |
IRQ_CLASS class; | |
if (cpu_micro_state == running /* if we are resuming from a sim stop */ | |
&& (CIR & PAUS_MASK) == PAUS) /* into a PAUS instruction */ | |
P = P + 1 & R_MASK; /* then return is to the instruction following */ | |
else /* otherwise the micromachine may be paused */ | |
cpu_micro_state = running; /* but is no longer */ | |
request_set = CPX1 & CPX1_IRQ_SET; /* get the set of active interrupt requests */ | |
if (request_set == cpx1_EXTINTR) { /* if only an external request present */ | |
class = irq_External; /* (the most common case) then set the class */ | |
parameter = device_number; /* and set the parameter to the device number */ | |
} | |
else { /* otherwise scan for the class */ | |
request_count = 0; /* where CPX1.1 through CPX1.9 */ | |
request_set = D16_SIGN; /* correspond to IRQ classes 1-9 */ | |
while ((CPX1 & request_set) == 0) { /* scan from left to right for the first request */ | |
request_count = request_count + 1; /* while incrementing the request number */ | |
request_set = request_set >> 1; /* and shifting the current request bit */ | |
} | |
class = (IRQ_CLASS) request_count; /* set the class from the request count */ | |
if (class == irq_Integer_Overflow) { /* if an integer overflow occurred */ | |
parameter = 1; /* then set the parameter to 1 */ | |
STA &= ~STATUS_O; /* and clear the overflow flag */ | |
} | |
else if (class == irq_External) /* otherwise if an external interrupt occurred */ | |
parameter = device_number; /* then set the parameter to the device number */ | |
else if (class == irq_Module) /* otherwise if the class is a module interrupt */ | |
parameter = 0; /* then the parameter is the module number */ | |
else /* otherwise the parameter */ | |
parameter = TO_LABEL (LABEL_IRQ, class); /* is the label */ | |
} | |
CPX1 &= ~request_set; /* clear the associated CPX request bit */ | |
dprintf (cpu_dev, DEB_INSTR, BOV_FORMAT "%s interrupt\n", | |
PBANK, P - 1 & R_MASK, parameter, interrupt_name [class]); | |
cpu_setup_irq_handler (class, parameter); /* set up the entry into the interrupt handler */ | |
return; | |
} | |
/* Set up a front panel operation. | |
This routine sets the SWCH register to the supplied value and then sets the | |
appropriate bit in the CPX2 register. This will cause a halt-mode interrupt | |
when simulated execution is resumed. | |
Implementation notes: | |
1. We do this here to avoid having to export the registers and CPX values | |
globally. | |
*/ | |
void cpu_front_panel (HP_WORD switch_reg, PANEL_TYPE request) | |
{ | |
SWCH = switch_reg; /* set the SWCH register value */ | |
switch (request) { /* dispatch on the request type */ | |
case Run: /* a run request */ | |
CPX2 |= cpx2_RUNSWCH; /* set the RUN switch */ | |
break; | |
case Cold_Load: /* a cold load request */ | |
CPX2 |= cpx2_LOADSWCH; /* set the LOAD switch */ | |
break; | |
case Cold_Dump: /* a cold dump request */ | |
CPX2 |= cpx2_DUMPSWCH; /* set the DUMP switch */ | |
break; | |
} /* all cases are handled */ | |
return; | |
} | |
/* Update the process clock. | |
If the process clock is currently calibrated, then the service interval is | |
actually ten times the hardware period of 1 millisecond. This provides | |
sufficient event service call spacing to allow idling to work. | |
To present the correct value when the process clock is read, this routine is | |
called to increment the count by an amount proportional to the fraction of | |
the service interval that has elapsed. In addition, it is called by the CPU | |
instruction postlude, so that the PCLK register will have the correct value | |
if it is examined from the SCP command prompt. | |
*/ | |
void cpu_update_pclk (void) | |
{ | |
int32 elapsed, ticks; | |
if (cpu_is_calibrated) { /* if the process clock is calibrated */ | |
elapsed = /* then the elapsed time is the original wait time */ | |
cpu_unit.wait - sim_activate_time (&cpu_unit); /* less the time remaining before the next service */ | |
ticks = /* the adjustment is */ | |
(elapsed * PCLK_MULTIPLIER) / cpu_unit.wait /* the elapsed fraction of the multiplier */ | |
- (PCLK_MULTIPLIER - pclk_increment); /* less the amount of any adjustment already made */ | |
PCLK = PCLK + ticks & R_MASK; /* update the process clock counter with rollover */ | |
pclk_increment = pclk_increment - ticks; /* and reduce the amount remaining to add at service */ | |
} | |
return; | |
} | |
/* CPU global instruction execution routines */ | |
/* Push the stack down. | |
This routine implements the PUSH micro-order to create space on the stack for | |
a new value. On return, the new value may be stored in the RA register. | |
If the SR register indicates that all of the TOS registers are in use, then | |
the RD register is freed by performing a queue down. Then the values in the | |
TOS registers are shifted down, freeing the RA register. Finally, SR is | |
incremented in preparation for the store. | |
*/ | |
void cpu_push (void) | |
{ | |
if (SR == 4) /* if all TOS registers are full */ | |
cpu_queue_down (); /* then move the RD value to memory */ | |
RD = RC; /* shift */ | |
RC = RB; /* the register */ | |
RB = RA; /* values down */ | |
SR = SR + 1; /* increment the register-in-use count */ | |
return; | |
} | |
/* Pop the stack up. | |
This routine implements the POP micro-order to delete the top-of-stack value. | |
On entry, if the SR register indicates that all of the TOS registers are | |
empty, then if decrementing the SM register would move it below the DB | |
register value, and the CPU is not in privileged mode, then a Stack Underflow | |
trap is taken. Otherwise, the stack memory pointer is decremented. | |
If one or more values exist in the TOS registers, the values are shifted up, | |
deleting the previous value in the RA register, and SR is decremented. | |
*/ | |
void cpu_pop (void) | |
{ | |
if (SR == 0) { /* if the TOS registers are empty */ | |
if (SM <= DB && NPRV) /* then if SM isn't above DB and the mode is non-privileged */ | |
MICRO_ABORT (trap_Stack_Underflow); /* then trap with a Stack Underflow */ | |
SM = SM - 1 & R_MASK; /* decrement the stack memory register */ | |
} | |
else { /* otherwise at least one TOS register is occupied */ | |
RA = RB; /* so shift */ | |
RB = RC; /* the register */ | |
RC = RD; /* values up */ | |
SR = SR - 1; /* decrement the register-in-use count */ | |
} | |
return; | |
} | |
/* Queue a value from memory up to the register file. | |
This routine implements the QUP micro-order to move the value at the top of | |
the memory stack into the bottom of the TOS register file. There must be a | |
free TOS register when this routine is called. | |
On entry, if decrementing the SM register would move it below the DB register | |
value, and the CPU is not in privileged mode, then a Stack Underflow trap is | |
taken. Otherwise, the value pointed to by SM is read into the first unused | |
TOS register, SM is decremented to account for the removed value, and SR is | |
incremented to account for the new TOS register in use. | |
Implementation notes: | |
1. SR must be less than 4 on entry, so that TR [SR] is the first unused TOS | |
register. | |
2. SM and SR must not be modified within the call to cpu_read_memory. For | |
example, SR++ cannot be passed as a parameter. | |
*/ | |
void cpu_queue_up (void) | |
{ | |
if (SM <= DB && NPRV) /* if SM isn't above DB and the mode is non-privileged */ | |
MICRO_ABORT (trap_Stack_Underflow); /* then trap with a Stack Underflow */ | |
else { /* otherwise */ | |
cpu_read_memory (stack, SM, &TR [SR]); /* read the value from memory into a TOS register */ | |
SM = SM - 1 & R_MASK; /* decrement the stack memory register */ | |
SR = SR + 1; /* and increment the register-in-use count */ | |
} | |
return; | |
} | |
/* Queue a value from the register file down to memory. | |
This routine implements the QDWN micro-order to move the value at the bottom | |
of the TOS register file into the top of the memory stack. There must be a | |
TOS register in use when this routine is called. | |
On entry, if incrementing the SM register would move it above the Z register | |
value, then a Stack Overflow trap is taken. Otherwise, SM is incremented to | |
account for the new value written, SR is decremented to account for the TOS | |
register removed from use, and the value in that TOS register is written into | |
memory at the top of the memory stack. | |
Implementation notes: | |
1. SR must be greater than 0 on entry, so that TR [SR - 1] is the last TOS | |
register in use. | |
2. SM and SR must not be modified within the call to cpu_write_memory. For | |
example, SR-- cannot be passed as a parameter. | |
*/ | |
void cpu_queue_down (void) | |
{ | |
if (SM >= Z) /* if SM isn't below Z */ | |
MICRO_ABORT (trap_Stack_Overflow); /* then trap with a Stack Overflow */ | |
SM = SM + 1 & R_MASK; /* increment the stack memory register */ | |
SR = SR - 1; /* and decrement the register-in-use count */ | |
cpu_write_memory (stack, SM, TR [SR]); /* write the value from a TOS register to memory */ | |
return; | |
} | |
/* Flush the register file. | |
This routine implements the PSHA microcode subroutine that writes the values | |
of all TOS registers in use to the memory stack. As each value is written, | |
the SM register is incremented and the SR register is decremented. On | |
return, the SR register value will be zero. | |
The routine does not check for stack overflow. | |
*/ | |
void cpu_flush (void) | |
{ | |
while (SR > 0) { /* while one or more registers are in use */ | |
SM = SM + 1 & R_MASK; /* increment the stack memory register */ | |
SR = SR - 1; /* and decrement the register-in-use count */ | |
cpu_write_memory (stack, SM, TR [SR]); /* write the value from a TOS register to memory */ | |
} | |
return; | |
} | |
/* Adjust SR until it reaches a specified value. | |
This routine implements the SRP1-SRP4 microcode subroutines that adjust the | |
stack until the prescribed number (1-4) of TOS registers are occupied. It | |
performs queue ups, i.e., moves values from the top of the memory stack to | |
the bottom of the register file, until the specified SR value is reached. | |
Stack underflow is checked after the all of the values have been moved. The | |
routine assumes that at least one value must be moved. | |
Implementation notes: | |
1. SR must be greater than 0 on entry, so that TR [SR - 1] is the last TOS | |
register in use. | |
2. SM and SR must not be modified within the call to cpu_read_memory. For | |
example, SR++ cannot be passed as a parameter. | |
3. The cpu_queue_up routine isn't used, as that routine checks for a stack | |
underflow after each word is moved rather than only after the last word. | |
*/ | |
void cpu_adjust_sr (uint32 target) | |
{ | |
do { | |
cpu_read_memory (stack, SM, &TR [SR]); /* read the value from memory into a TOS register */ | |
SM = SM - 1 & R_MASK; /* decrement the stack memory register */ | |
SR = SR + 1; /* and increment the register-in-use count */ | |
} | |
while (SR < target); /* queue up until the requested number of registers are in use */ | |
if (SM <= DB && NPRV) /* if SM isn't above DB, or the mode is non-privileged */ | |
MICRO_ABORT (trap_Stack_Underflow); /* then trap with a Stack Underflow */ | |
return; | |
} | |
/* Write a stack marker to memory. | |
This routine implements the STMK microcode subroutine that writes a four-word | |
marker to the stack. The format of the marker is as follows: | |
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| X register value | [Q - 3] X | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| PB-relative return address | [Q - 2] P + 1 - PB | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| Status register value | [Q - 1] STA | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| Delta Q value | [Q - 0] S - Q | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
After the values are written, the Q register is set to point to the marker. | |
This routine is always entered with SR = 0, i.e., with the TOS registers | |
flushed to the memory stack. It does not check for stack overflow. | |
Implementation notes: | |
1. The PB-relative return address points to the instruction after the point | |
of the call. Conceptually, this is location P + 1, but because the CPU | |
uses a two-instruction prefetch, the location is actually P - 1. | |
*/ | |
void cpu_mark_stack (void) | |
{ | |
SM = SM + 4 & R_MASK; /* adjust the stack pointer */ | |
cpu_write_memory (stack, SM - 3, X); /* push the index register */ | |
cpu_write_memory (stack, SM - 2, P - 1 - PB & LA_MASK); /* and delta P */ | |
cpu_write_memory (stack, SM - 1, STA); /* and the status register */ | |
cpu_write_memory (stack, SM - 0, SM - Q & LA_MASK); /* and delta Q */ | |
Q = SM; /* set Q to point to the new stack marker */ | |
return; | |
} | |
/* Calculate an effective memory address. | |
This routine calculates the effective address for a memory reference or | |
branch instruction. On entry, "mode_disp" contains the mode, displacement, | |
index, and indirect fields of the instruction, "classification" and "offset" | |
point to variables to receive the corresponding values, and "selector" points | |
to a variable to receive the byte selection ("upper" or "lower") for byte- | |
addressable instructions or is NULL for word-addressable instructions. On | |
exit, "classification" is set to the memory access classification, "offset" | |
is set to the address offset within the memory bank implied by the | |
classification, and "selector" is set to indicate the byte referenced if the | |
pointer is non-NULL. | |
The mode and displacement fields of the instruction encode an address | |
relative to one of the base registers P, DB, Q, or S, as follows: | |
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| memory op | X | I | mode and displacement | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 | 0 | P+ displacement 0-255 | | |
+---+---+---+---+---+---+---+---+---+---+ | |
| 0 | 1 | P- displacement 0-255 | | |
+---+---+---+---+---+---+---+---+---+---+ | |
| 1 | 0 | DB+ displacement 0-255 | | |
+---+---+---+---+---+---+---+---+---+---+ | |
| 1 | 1 | 0 | Q+ displacement 0-127 | | |
+---+---+---+---+---+---+---+---+---+---+ | |
| 1 | 1 | 1 | 0 | Q- displacement 0-63 | | |
+---+---+---+---+---+---+---+---+---+---+ | |
| 1 | 1 | 1 | 1 | S- displacement 0-63 | | |
+---+---+---+---+---+---+---+---+---+---+ | |
The displacement encoded in the instruction is an unsigned value that is | |
added to or subtracted from the indicated base register. | |
If the X and I fields are both 0, the addressing is direct. If the X field | |
is 1, the addressing is indexed. If the I field is 1, the addressing is | |
indirect. If both fields are 1, the addressing is indirect indexed, with | |
indirection performed before indexing. | |
To improve execution speed in hardware, a preadder is implemented that sums | |
the offset contained in certain bits of the CIR with the index register (if | |
enabled). The primary use is to save a microinstruction cycle during memory | |
reference instructions, which must add a base register, the offset in the | |
CIR, and potentially the X register (either directly or shifted left or right | |
by one place for LDD/STD or LDB/STB, respectively). The preadder also serves | |
to hold other counts obtained from the CIR, e.g., shift counts, although in | |
these cases, the addition function is not used. | |
This routine simulates the preadder as part of the effective address | |
calculation. The calculations employed for word addressing are: | |
Direct word addressing: | |
ea = PBANK.(P + displacement) | |
ea = DBANK.(DB + displacement) | |
ea = SBANK.(Q,S + displacement) | |
Direct indexed word addressing: | |
ea = PBANK.(P + displacement + X) | |
ea = DBANK.(DB + displacement + X) | |
ea = SBANK.(Q,S + displacement + X) | |
Indirect word addressing: | |
ea = PBANK.(P + displacement + M [PBANK.(P + displacement)]) | |
ea = DBANK.(DB + M [DBANK.(DB + displacement)]) | |
ea = DBANK.(DB + M [SBANK.(Q,S + displacement)]) | |
Indirect indexed word addressing: | |
ea = PBANK.(P + displacement + M [PBANK.(P + displacement)] + X) | |
ea = DBANK.(DB + M [DBANK.(DB + displacement)] + X) | |
ea = DBANK.(DB + M [SBANK.(Q,S + displacement)] + X) | |
The indirect cell contains either a self-relative, P-relative address or a | |
DB-relative address, even for S or Q-relative modes. Indirect branches with | |
DB/Q/S-relative mode are offsets from PB, not DB. | |
The effective address calculations employed for byte addressing are: | |
Direct byte addressing: | |
ea = DBANK.(DB + displacement).byte [0] | |
ea = SBANK.(Q,S + displacement).byte [0] | |
Direct indexed byte addressing: | |
ea = DBANK.(DB + displacement + X / 2).byte [X & 1] | |
ea = SBANK.(Q,S + displacement + X / 2).byte [X & 1] | |
Indirect byte addressing: | |
ea,I = DBANK.(DB + M [DBANK.(DB + displacement)] / 2).byte [cell & 1] | |
ea,I = DBANK.(DB + M [SBANK.(Q,S + displacement)] / 2).byte [cell & 1] | |
Indirect indexed byte addressing: | |
ea,I = DBANK.(DB + (M [DBANK.(DB + displacement)] + X) / 2).byte [cell + X & 1] | |
ea,I = DBANK.(DB + (M [SBANK.(Q,S + displacement)] + X) / 2).byte [cell + X & 1] | |
For all modes, the displacement is a word address, whereas the indirect cell | |
and index register contain byte offsets. For direct addressing, the byte | |
selected is byte 0. For all other modes, the byte selected is the byte at | |
(offset & 1), where the offset is the index register value, the indirect cell | |
value, or the sum of the two. | |
Byte offsets into data segments present problems, in that negative offsets | |
are permitted (to access the DL-to-DB area), but there are not enough bits to | |
represent all locations unambiguously in the potential -32K to +32K word | |
offset range. Therefore, a byte offset with bit 0 = 1 can represent either a | |
positive or negative word offset from DB, depending on the interpretation. | |
The HP 3000 adopts the convention that if the address resulting from a | |
positive-offset interpretation does not fall within the DL-to-S range, then | |
32K is added to the address, effectively changing the interpretation from a | |
positive to a negative offset. If this new address does not fall within the | |
DL-to-S range, a Bounds Violation trap occurs if the mode is non-privileged. | |
The reinterpretation as a negative offset is performed only if the CPU is not | |
in split-stack mode (where either DBANK is different from SBANK, or DB does | |
not lie between DL and Z), as extra data segments do not permit negative-DB | |
addressing. Reinterpretation is also not used for code segments, as negative | |
offsets from PB are not permitted. | |
Implementation notes: | |
1. On entry, the program counter points to the instruction following the | |
next instruction (i.e., the NIR location + 1). However, P-relative | |
offsets are calculated from the current instruction (CIR location). | |
Therefore, we decrement P by two before adding the offset. | |
In hardware, P-relative addresses obtained from the preadder are offset | |
from P + 1, whereas P-relative addresses obtained by summing with | |
P-register values obtained directly from the S-Bus are offset from P + 2. | |
This is because the P-register increment that occurs as part of a NEXT | |
micro-order is coincident with the R-Bus and S-Bus register loads; both | |
operations occur when the NXT+1 signal asserts. Therefore, the microcode | |
handling P-relative memory reference address resolution subtracts one to | |
get the P value corresponding to the CIR, whereas branches on overflow, | |
carry, etc. subtract two. | |
2. If the mode is indirect, this routine handles bounds checks and TOS | |
register accesses on the initial address. | |
3. The System Reference Manual states that byte offsets are interpreted as | |
negative if the effective address does not lie between DL and Z. | |
However, the Series II microcode actually uses DL and S for the limits. | |
*/ | |
void cpu_ea (HP_WORD mode_disp, ACCESS_CLASS *classification, HP_WORD *offset, BYTE_SELECTOR *selector) | |
{ | |
HP_WORD base, displacement; | |
ACCESS_CLASS class; | |
switch ((mode_disp & MODE_MASK) >> MODE_SHIFT) { /* dispatch on the addressing mode */ | |
case 000: | |
case 001: | |
case 002: | |
case 003: /* positive P-relative displacement */ | |
base = P - 2 + (mode_disp & DISPL_255_MASK); /* add the displacement to the base */ | |
class = program_checked; /* and classify as a program reference */ | |
break; | |
case 004: | |
case 005: | |
case 006: | |
case 007: /* negative P-relative displacement */ | |
base = P - 2 - (mode_disp & DISPL_255_MASK); /* subtract the displacement from the base */ | |
class = program_checked; /* and classify as a program reference */ | |
break; | |
case 010: | |
case 011: | |
case 012: | |
case 013: /* positive DB-relative displacement */ | |
base = DB + (mode_disp & DISPL_255_MASK); /* add the displacement to the base */ | |
class = data_checked; /* and classify as a data reference */ | |
break; | |
case 014: | |
case 015: /* positive Q-relative displacement */ | |
base = Q + (mode_disp & DISPL_127_MASK); /* add the displacement to the base */ | |
class = stack_checked; /* and classify as a stack reference */ | |
break; | |
case 016: /* negative Q-relative displacement */ | |
base = Q - (mode_disp & DISPL_63_MASK); /* subtract the displacement from the base */ | |
class = stack_checked; /* and classify as a stack reference */ | |
break; | |
case 017: /* negative S-relative displacement */ | |
base = SM + SR - (mode_disp & DISPL_63_MASK); /* subtract the displacement from the base */ | |
class = stack_checked; /* and classify as a stack reference */ | |
break; | |
} /* all cases are handled */ | |
if (!(mode_disp & I_FLAG_BIT_5)) /* if the mode is direct */ | |
displacement = 0; /* then there's no displacement */ | |
else { /* otherwise the mode is indirect */ | |
cpu_read_memory (class, base & LA_MASK, &displacement); /* so get the displacement value */ | |
if ((CIR & BR_MASK) == BR_DBQS_I) { /* if this a DB/Q/S-relative indirect BR instruction */ | |
base = PB; /* then PB is the base for the displacement */ | |
class = program_checked; /* reclassify as a program reference */ | |
} | |
else if (class != program_checked) { /* otherwise if it is a data or stack reference */ | |
base = DB; /* then DB is the base for the displacement */ | |
class = data_checked; /* reclassify as a data reference */ | |
} | |
/* otherwise, this is a program reference */ | |
} /* which is self-referential */ | |
if ((CIR & LSDX_MASK) == LDD_X /* if the mode */ | |
|| (CIR & LSDX_MASK) == STD_X) /* is double-word indexed */ | |
displacement = displacement + X * 2 & DV_MASK; /* then add the doubled index to the displacement */ | |
else if (mode_disp & X_FLAG) /* otherwise if the mode is indexed */ | |
displacement = displacement + X & DV_MASK; /* then add the index to the displacement */ | |
if (selector == NULL) /* if a word address is requested */ | |
base = base + displacement; /* then add in the word displacement */ | |
else if ((mode_disp & (X_FLAG | I_FLAG_BIT_5)) == 0) /* otherwise if a direct byte address is requested */ | |
*selector = upper; /* then it references the upper byte */ | |
else { /* otherwise an indexed or indirect byte address is requested */ | |
if (displacement & 1) /* so if the byte displacement is odd */ | |
*selector = lower; /* then the lower byte was requested */ | |
else /* otherwise it is even */ | |
*selector = upper; /* and the upper byte was requested */ | |
base = base + (displacement >> 1) & LA_MASK; /* convert the displacement from byte to word and add */ | |
if (DBANK == SBANK && DL <= DB && DB <= Z /* if not in split-stack mode */ | |
&& (base < DL || base > SM + SR)) /* and the word address is out of range */ | |
base = base ^ D16_SIGN; /* then add 32K to swap the offset polarity */ | |
} | |
*offset = base & LA_MASK; /* set the */ | |
*classification = class; /* return values */ | |
return; | |
} | |
/* Set up the entry into an interrupt handler. | |
This routine prepares the CPU state to execute an interrupt handling | |
procedure. On entry, "class" is the classification of the current interrupt, | |
and "parameter" is the parameter associated with the interrupt. On exit, the | |
stack has been set up correctly, and the PB, P, PL, and status registers have | |
been set up for entry into the interrupt procedure. | |
Run-mode interrupts are classified as external or internal and ICS or | |
non-ICS. External interrupts are those originating with the device | |
controllers, and internal interrupts are conditions detected by the microcode | |
(e.g., a bounds violation or arithmetic overflow). ICS interrupts execute | |
their handlers on the system's Interrupt Control Stack. Non-ICS interrupts | |
execute on the user's stack. | |
Of the run-mode interrupts, the External, System Parity Error, Address | |
Parity Error, Data Parity Error, and Module interrupts execute on the ICS. | |
All other interrupts execute on the user's stack. The routine begins by | |
determining whether an ICS or non-ICS interrupt is indicated. The | |
appropriate stack is established, and the stack marker is written to preserve | |
the state of the interrupted routine. The label of the handler procedure is | |
obtained, and then the procedure designated by the label is set up. On | |
return, the first instruction of the handler is ready to execute. | |
Implementation notes: | |
1. This routine implements various execution paths through the microcode | |
labeled as INT0 through INT7. | |
2. This routine is also called directly by the IXIT instruction executor if | |
an external interrupt is pending. This is handled as an external | |
interrupt but is classified differently so that the teardown and rebuild | |
of the stack may be avoided to improve performance. | |
3. ICS interrupts other than external interrupts (e.g., parity errors) are | |
not currently generated or handled by the simulation. | |
*/ | |
void cpu_setup_irq_handler (IRQ_CLASS class, HP_WORD parameter) | |
{ | |
HP_WORD label; | |
if (class == irq_External || class == irq_IXIT) { /* if entry is for an external interrupt */ | |
if (class == irq_External) /* then if it was detected during normal execution */ | |
cpu_setup_ics_irq (class, 0); /* then set it up on the ICS */ | |
else /* otherwise it was detected during IXIT */ | |
SM = Q + 2 & R_MASK; /* so the ICS is already set up */ | |
DBANK = 0; /* all handlers are in bank 0 */ | |
STA = STATUS_M | STATUS_I; /* enter privileged mode with interrupts enabled */ | |
cpu_read_memory (stack, parameter * 4 + 2, &DB); /* read the DB value */ | |
cpu_read_memory (stack, parameter * 4 + 1, &label); /* and the procedure label from the DRT */ | |
} | |
else if (class >= irq_System_Parity /* otherwise if entry is for */ | |
&& class <= irq_Power_Fail) { /* another ICS interrupt */ | |
return; /* then.... [not handled yet] */ | |
} | |
else { /* otherwise entry is for a non-ICS interrupt */ | |
if (class == irq_Integer_Overflow) /* if this is an integer overflow interrupt */ | |
label = TO_LABEL (LABEL_IRQ, trap_User); /* then form the label for a user trap */ | |
else /* otherwise form the label */ | |
label = TO_LABEL (LABEL_IRQ, class); /* for the specified classification */ | |
cpu_flush (); /* flush the TOS registers to memory */ | |
cpu_mark_stack (); /* and write a stack marker */ | |
STA = STATUS_M; /* clear status and enter privileged mode */ | |
} | |
SM = SM + 1 & R_MASK; /* increment the stack pointer */ | |
cpu_write_memory (stack, SM, parameter); /* and push the parameter on the stack */ | |
X = CIR; /* save the CIR in the index register */ | |
cpu_call_procedure (label); /* set up to call the interrupt handling procedure */ | |
return; | |
} | |
/* Set up an interrupt on the Interrupt Control Stack. | |
This routine prepares the Interrupt Control Stack (ICS) to support interrupt | |
processing. It is called from the run-time interrupt routine for ICS | |
interrupts, the microcode abort routine for ICS traps, and from the DISP and | |
PSEB instruction executors before entering the dispatcher. On entry, "class" | |
is the interrupt classification, and, if the class is "irq_Trap", then "trap" | |
is the trap classification. The trap classification is ignored for | |
interrupts, including the dispatcher start interrupt. | |
Unless entry is for a Cold Load trap, the routine begins by writing a | |
six-word stack marker. This special ICS marker extends the standard marker | |
by adding the DBANK and DB values as follows: | |
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| X register value | [Q - 3] X | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| PB-relative return address | [Q - 2] P + 1 - PB | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| Status register value | [Q - 1] STA | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| D | Delta Q value | [Q - 0] S - Q | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| DB-Bank value | [Q + 1] DBANK | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| DB value | [Q + 2] DB | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
D = the dispatcher was interrupted | |
After the values are written, the Q register is set to point to the marker. | |
The stack bank register is then cleared, as the ICS is always located in | |
memory bank 0. | |
If the interrupt or trap occurred while executing on the ICS, and the | |
dispatcher was running at the time, the "dispatcher running" bit in the CPX1 | |
register is cleared, and the D-bit is set in the delta-Q value word of the | |
stack marker. This bit will be used during interrupt exit to decide whether | |
to restart the dispatcher. | |
If the CPU was executing on the user's stack, the "ICS flag" bit in CPX1 is | |
set, the Q register is reset to point at the permanent dispatcher stack | |
marker established by the operating system, and the Z register is reset to | |
the stack limit established by the OS for the ICS; the values are obtained | |
from reserved memory locations 5 and 6, respectively. The ICS DB value is | |
read from the ICS global area that precedes the dispatcher stack marker and | |
is used to write the stack-DB-relative S value back to the global area. | |
Finally, the stack pointer is set to point just above the stack marker. | |
Implementation notes: | |
1. This routine implements various execution paths through the microcode | |
labeled as INT1 through INT5. | |
*/ | |
void cpu_setup_ics_irq (IRQ_CLASS class, TRAP_CLASS trap) | |
{ | |
HP_WORD delta_q, stack_db; | |
if (class != irq_Trap || trap != trap_Cold_Load) { /* if this is not a cold load trap entry */ | |
cpu_flush (); /* then flush the TOS registers to memory */ | |
cpu_mark_stack (); /* and write a four-word stack marker */ | |
cpu_write_memory (stack, SM + 1 & LA_MASK, DBANK); /* add DBANK and DB to the stack */ | |
cpu_write_memory (stack, SM + 2 & LA_MASK, DB); /* to form a six-word ICS marker */ | |
} | |
SBANK = 0; /* the ICS is always located in bank 0 */ | |
if (CPX1 & cpx1_ICSFLAG) { /* if execution is currently on the ICS */ | |
if (CPX1 & cpx1_DISPFLAG) { /* then if the dispatcher was interrupted */ | |
CPX1 &= ~cpx1_DISPFLAG; /* then clear the dispatcher flag */ | |
cpu_read_memory (stack, Q, &delta_q); /* get the delta Q value from the stack marker */ | |
cpu_write_memory (stack, Q, delta_q | STMK_D); /* and set the dispatcher-interrupted flag */ | |
} | |
} | |
else { /* otherwise execution is on the user's stack */ | |
CPX1 |= cpx1_ICSFLAG; /* so set the ICS flag */ | |
cpu_read_memory (stack, ICS_Q, &Q); /* set Q = QI */ | |
cpu_read_memory (stack, ICS_Z, &Z); /* set Z = ZI */ | |
cpu_read_memory (stack, Q - 4 & LA_MASK, /* read the stack DB value */ | |
&stack_db); | |
cpu_write_memory (stack, Q - 6 & LA_MASK, /* write the stack-DB-relative S value */ | |
SM + 2 - stack_db & DV_MASK); /* which is meaningless if this is a cold load */ | |
SR = 0; /* invalidate the stack registers for a cold load */ | |
DL = D16_UMAX; /* and set the data limit */ | |
} | |
SM = Q + 2 & R_MASK; /* set S above the stack marker */ | |
return; | |
} | |
/* Set up a code segment. | |
This routine is called to set up a code segment in preparation for calling or | |
exiting a procedure located in a segment different from the currently | |
executing segment. On entry, "label" indicates the segment number containing | |
the procedure. On exit, the new status register value and the first word of | |
the Code Segment Table entry are returned to the variables pointed to by | |
"status" and "entry_0", respectively. | |
The routine begins by reading the CST pointer. The CST is split into two | |
parts: a base table, and an extension table. The table to use is determined | |
by the requested segment number. Segment numbers 0 and 192, corresponding to | |
the first entries of the two tables, are reserved and cause a CST Violation | |
trap if specified. | |
The CST entry corresponding to the segment number is examined to set the | |
program bank, base, and limit registers (the segment length stored in the | |
table is number of quad-words, which must be multiplied by four to get the | |
size in words). The new status register value is set up and returned, along | |
with the first word of the CST entry. | |
Implementation notes: | |
1. This routine implements the microcode SSEG subroutine. | |
2. Passing -1 as a parameter to trap_CST_Violation ensures that the segment | |
number >= 2 check will pass and the trap handler will be invoked. | |
3. The Series II microcode sets PBANK and PB unilaterally but sets PL only | |
if the code segment is not absent. An absent segment entry contains the | |
disc address in words 3 and 4 instead of the bank address and base | |
address, so PBANK and PB will contain invalid values in this case. It is | |
not clear why the microcode avoids setting PL; the microinstruction in | |
question also sets Flag 2, so conditioning PL may be just a side effect. | |
In any case, we duplicate the firmware behavior here. | |
4. This routine is only used locally, but we leave it as a global entry to | |
support future firmware extensions that may need to call it. | |
*/ | |
void cpu_setup_code_segment (HP_WORD label, HP_WORD *status, HP_WORD *entry_0) | |
{ | |
HP_WORD cst_pointer, cst_size, cst_entry, cst_bank, segment_number, entry_number; | |
segment_number = STT_SEGMENT (label); /* isolate the segment number from the label */ | |
if (segment_number < CST_RESERVED) { /* if the target segment is in the base table */ | |
cpu_read_memory (absolute, CSTB_POINTER, &cst_pointer); /* then read the CST base pointer */ | |
entry_number = segment_number; /* and set the entry number */ | |
} | |
else { /* otherwise it is in the extension table */ | |
cpu_read_memory (absolute, CSTX_POINTER, &cst_pointer); /* so read the CST extension pointer */ | |
entry_number = segment_number - CST_RESERVED; /* and set the entry number */ | |
} | |
if (entry_number == 0) /* segment numbers 0 and 192 do not exist */ | |
MICRO_ABORTP (trap_CST_Violation, -1); /* so trap for a violation if either is specified */ | |
cpu_read_memory (absolute, cst_pointer, &cst_size); /* read the table size */ | |
if (entry_number > cst_size) /* if the entry is outside of the table */ | |
MICRO_ABORTP (trap_CST_Violation, entry_number); /* then trap for a violation */ | |
cst_entry = cst_pointer + entry_number * 4; /* get the address of the target CST entry */ | |
cpu_read_memory (absolute, cst_entry, entry_0); /* get the first word of the entry */ | |
cpu_write_memory (absolute, cst_entry, *entry_0 | CST_R_BIT); /* and set the segment reference bit */ | |
cpu_read_memory (absolute, cst_entry + 2, &cst_bank); /* read the bank address word */ | |
PBANK = cst_bank & CST_BANK_MASK; /* and mask to just the bank number */ | |
cpu_read_memory (absolute, cst_entry + 3, &PB); /* read the segment's base address */ | |
PL = (*entry_0 & CST_SEGLEN_MASK) * 4 - 1; /* set PL to the segment length - 1 */ | |
*status = STA & ~LABEL_SEGMENT_MASK | segment_number; /* set the segment number in the new status word */ | |
if (*entry_0 & CST_M_BIT) /* if the segment executes in privileged mode */ | |
*status |= STATUS_M; /* then set up to enter privileged mode */ | |
if (! (*entry_0 & CST_A_BIT)) /* if the segment is not absent */ | |
PL = PL + PB; /* then set the segment limit */ | |
return; | |
} | |
/* Set up a data segment. | |
This routine is called to set up a data segment for access. It is called by | |
the MDS, MFDS, and MTDS instruction executors to obtain the bank and offset | |
of specified segments from the Data Segment Table. On entry, | |
"segment_number" indicates the number of the desired data segment. On exit, | |
the memory bank number and offset of the data segment base are returned to | |
the variables pointed to by "bank" and "address", respectively. | |
The routine begins by reading the DST pointer. Segment number 0, | |
corresponding to the first entry of the table, is reserved and causes a DST | |
Violation trap if specified. | |
The DST entry corresponding to the segment number is examined to obtain the | |
bank and base address. If the segment is absent, a Data Segment Absent trap | |
is taken. Otherwise, the bank and address values are returned. | |
Implementation notes: | |
1. This routine implements the microcode DSEG subroutine. | |
*/ | |
void cpu_setup_data_segment (HP_WORD segment_number, HP_WORD *bank, HP_WORD *address) | |
{ | |
HP_WORD dst_pointer, dst_size, dst_entry, entry_0; | |
cpu_read_memory (absolute, DST_POINTER, &dst_pointer); /* read the DST base pointer */ | |
if (segment_number == 0) /* segment number 0 does not exist */ | |
MICRO_ABORT (trap_DST_Violation); /* so trap for a violation if it is specified */ | |
cpu_read_memory (absolute, dst_pointer, &dst_size); /* read the table size */ | |
if (segment_number > dst_size) /* if the entry is outside of the table */ | |
MICRO_ABORT (trap_DST_Violation); /* then trap for a violation */ | |
dst_entry = dst_pointer + segment_number * 4; /* get the address of the target DST entry */ | |
cpu_read_memory (absolute, dst_entry, &entry_0); /* get the first word of the entry */ | |
cpu_write_memory (absolute, dst_entry, entry_0 | DST_R_BIT); /* and set the segment reference bit */ | |
if (entry_0 & DST_A_BIT) /* if the segment is absent */ | |
MICRO_ABORTP (trap_DS_Absent, segment_number); /* then trap for an absentee violation */ | |
cpu_read_memory (absolute, dst_entry + 2, bank); /* read the segment bank number */ | |
cpu_read_memory (absolute, dst_entry + 3, address); /* and base address */ | |
*bank = *bank & DST_BANK_MASK; /* mask off the reserved bits */ | |
return; /* before returning to the caller */ | |
} | |
/* Call a procedure. | |
This routine sets up the PB, P, PL, and status registers to enter a | |
procedure. It is called by the PCAL instruction executor and by the | |
interrupt and trap routines to set up the handler procedures. On entry, | |
"label" contains an external program label indicating the segment number and | |
Segment Transfer Table entry number describing the procedure, or a local | |
program label indicating the starting address of the procedure. On exit, the | |
registers are set up for execution to resume with the first instruction of | |
the procedure. | |
If the label is a local label, the PB-relative address is obtained from the | |
label and stored in the P register, and the Next Instruction Register is | |
loaded with the first instruction of the procedure. | |
If the label is external, the code segment referenced by the label is set up. | |
If the "trace" or "absent" bits are set, the corresponding trap is taken. | |
Otherwise, the Segment Transfer Table length is read, and the STT entry | |
number is validated; if it references a location outside of the table, a STT | |
violation trap is taken. | |
Otherwise, the valid STT entry is examined. If the target procedure is not | |
in the designated code segment or is uncallable if not in privileged mode, | |
the appropriate traps are taken. If the STT entry contains a local label, it | |
is used to set up the P register and NIR as above. | |
Implementation notes: | |
1. This routine implements the microcode PCL3 and PCL5 subroutines. | |
*/ | |
void cpu_call_procedure (HP_WORD label) | |
{ | |
HP_WORD new_status, new_label, new_p, cst_entry, stt_size, stt_entry; | |
new_status = STA; /* save the status for a local label */ | |
if (label & LABEL_EXTERNAL) { /* if the label is non-local */ | |
cpu_setup_code_segment (label, &new_status, &cst_entry); /* then set up the corresponding code segment */ | |
stt_entry = STT_NUMBER (label); /* get the STT entry number from the label */ | |
if (cst_entry & (CST_A_BIT | CST_T_BIT)) { /* if the code segment is absent or being traced */ | |
STA = new_status; /* then set the new status before trapping */ | |
cpu_mark_stack (); /* and write a stack marker to memory */ | |
if (cst_entry & CST_A_BIT) /* if the code segment is absent */ | |
MICRO_ABORTP (trap_CS_Absent, label); /* then trap to load it */ | |
else /* otherwise */ | |
MICRO_ABORTP (trap_Trace, label); /* trap to trace it */ | |
} | |
cpu_read_memory (program_checked, PL, &stt_size); /* read the table size */ | |
if (stt_entry > STT_LENGTH (stt_size)) /* if the entry is outside of the table */ | |
MICRO_ABORTP (trap_STT_Violation, new_status); /* then trap for a violation */ | |
cpu_read_memory (program_checked, PL - stt_entry, &new_label); /* read the label from the STT */ | |
if (new_label & LABEL_EXTERNAL) /* if the procedure is not in the target segment */ | |
MICRO_ABORTP (trap_STT_Violation, new_status); /* then trap for a violation */ | |
if ((new_label & LABEL_UNCALLABLE) && NPRV) /* if the procedure is uncallable in the current mode */ | |
MICRO_ABORTP (trap_Uncallable, label); /* then trap for a violation */ | |
if (stt_entry == 0) /* if the STT number is zero in an external label */ | |
label = 0; /* then the starting address is PB */ | |
else /* otherwise */ | |
label = new_label; /* the PB offset is contained in the new label */ | |
} | |
new_p = PB + (label & LABEL_ADDRESS_MASK); /* get the procedure starting address */ | |
cpu_read_memory (fetch_checked, new_p, &NIR); /* check the bounds and get the next instruction */ | |
P = new_p + 1 & R_MASK; /* the bounds are valid, so set the new P value */ | |
STA = new_status; /* set the new status value */ | |
cpu_base_changed = TRUE; /* one or more base registers have changed for tracing */ | |
return; | |
} | |
/* Return from a procedure. | |
This routine sets up the P, Q, SM, and status registers to return from a | |
procedure. It is called by the EXIT and IXIT instruction executors and by | |
the cpu_start_dispatcher routine to enter the dispatcher. On entry, "new_q" | |
and "new_sm" contain the new values for the Q and SM registers that unwind | |
the stack. The "parameter" value is used only if a Trace or Code Segment | |
Absent trap is taken. For EXIT, the parameter is the stack adjustment value | |
(the N field). For IXIT, the parameter is zero. On exit, the registers are | |
set up for execution to resume with the first instruction after the procedure | |
call or interrupt. | |
The routine begins by reloading register values from the stack marker. The | |
stack marker format is: | |
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| X register value | [Q - 3] X | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| T | M | PB-relative return address | [Q - 2] P + 1 - PB | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| Status register value | [Q - 1] STA | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| Delta Q value | [Q - 0] S - Q | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
T = a trace or control-Y interrupt is pending | |
M = the code segment is physically mapped | |
The T and M bits are set by the operating system, if applicable, after the | |
stack marker was originally written. | |
Stack underflow and overflow are checked, and privilege changes are | |
validated. If the return will be to a different code segment, it is set up. | |
Finally, the new P, Q, SM, and status register values are loaded, and NIR is | |
loaded with the first instruction after the return. | |
Implementation notes: | |
1. This routine implements the microcode EXI1 subroutine. | |
2. We pass a temporary status to cpu_setup_code_segment because it forms the | |
returned new status from the current STA register value. But for EXIT | |
and IXIT, the new status comes from the stack marker, which already has | |
the segment number to which we're returning, and which must not be | |
altered. | |
3. The NEXT action is modified when the R-bit is set in the status word | |
being restored. This occurs when an interrupt occurred between the two | |
stackops of a stack instruction. The main instruction loop does not | |
alter CIR in this case, so we must set it up here. | |
*/ | |
void cpu_exit_procedure (HP_WORD new_q, HP_WORD new_sm, HP_WORD parameter) | |
{ | |
HP_WORD temp_status, new_status, new_p, cst_entry; | |
SM = Q; /* delete any local values from the stack */ | |
if (new_q > Z || new_sm > Z) /* if either the new Q or SM exceed the stack limit */ | |
MICRO_ABORT (trap_Stack_Overflow); /* then trap with a stack overflow */ | |
cpu_read_memory (stack, Q - 1, &new_status); /* read the new status value from the stack marker */ | |
if ((CIR & EXIT_MASK) == EXIT /* if an EXIT instruction is executing */ | |
&& (new_q < DB || new_sm < DB) /* and either new Q or new S are below the data base */ | |
&& (new_status & STATUS_M) == 0) /* and the new mode is non-privileged */ | |
MICRO_ABORT (trap_Stack_Underflow); /* then trap with a stack underflow */ | |
cpu_read_memory (stack, Q - 2, &new_p); /* read the PB-relative return value from the stack marker */ | |
if (NPRV /* if currently in user mode */ | |
&& ((new_status & STATUS_M) /* and returning to privileged mode */ | |
|| (new_status & STATUS_I) != (STA & STATUS_I))) /* or attempting to change interrupt state */ | |
MICRO_ABORT (trap_Privilege_Violation); /* then trap with a privilege violation */ | |
STA &= ~STATUS_I; /* turn off external interrupts */ | |
cpu_read_memory (stack, Q - 3, &X); /* read the new X value from the stack marker */ | |
if ((new_status & STATUS_CS_MASK) != (STA & STATUS_CS_MASK)) { /* if returning to a different segment */ | |
cpu_setup_code_segment (new_status, &temp_status, &cst_entry); /* then set up the new segment */ | |
if (NPRV && (temp_status & STATUS_M)) /* if in user mode now and returning to a privileged segment */ | |
MICRO_ABORT (trap_Privilege_Violation); /* then trap with a privilege violation */ | |
if (new_p & STMK_T) /* if the new code segment is being traced */ | |
MICRO_ABORTP (trap_Trace, parameter); /* then trap to trace it */ | |
if (cst_entry & CST_A_BIT) /* if the code segment is absent */ | |
MICRO_ABORTP (trap_CS_Absent, parameter); /* then trap to load it */ | |
} | |
new_p = PB + (new_p & STMK_RTN_ADDR); /* convert the relative address to absolute */ | |
cpu_read_memory (fetch_checked, new_p, &NIR); /* check the bounds and get the next instruction */ | |
P = new_p + 1 & R_MASK; /* the bounds are valid, so set the new P value */ | |
STA = new_status; /* set the new status value */ | |
Q = new_q; /* and the stack marker */ | |
SM = new_sm; /* and the stack pointer */ | |
if (STA & STATUS_R) { /* if a right-hand stack op is pending */ | |
CIR = NIR; /* then set the current instruction */ | |
cpu_read_memory (fetch, P, &NIR); /* and load the next instruction */ | |
} | |
cpu_base_changed = TRUE; /* one or more base registers have changed for tracing */ | |
return; | |
} | |
/* Start the dispatcher. | |
This routine is called by the DISP and PSEB instruction executors to start | |
the dispatcher and by the IXIT executor to restart the dispatcher if it was | |
interrupted. | |
On entry, the ICS has been set up. The "dispatcher running" bit in the CPX1 | |
register is set, Q is set to point at the permanent dispatcher stack marker | |
on the ICS, the dispatcher's DBANK and DB registers are loaded, and an "exit | |
procedure" is performed to return to the dispatcher. | |
*/ | |
void cpu_start_dispatcher (void) | |
{ | |
dprintf (cpu_dev, DEB_INSTR, BOV_FORMAT "%s interrupt\n", | |
PBANK, P - 1 & R_MASK, 0, interrupt_name [irq_Dispatch]); | |
CPX1 |= cpx1_DISPFLAG; /* set the "dispatcher is running" flag */ | |
cpu_read_memory (absolute, ICS_Q, &Q); /* set Q to point to the dispatcher's stack marker */ | |
cpu_write_memory (absolute, Q, 0); /* and clear the stack marker delta Q value */ | |
cpu_read_memory (stack, Q + 1 & LA_MASK, &DBANK); /* load the dispatcher's data bank */ | |
cpu_read_memory (stack, Q + 2 & LA_MASK, &DB); /* and data base registers */ | |
cpu_exit_procedure (Q, Q + 2, 0); /* return to the dispatcher */ | |
return; | |
} | |
/* CPU local SCP support routines */ | |
/* Service the CPU process clock. | |
The process clock is used by the operating system to time per-process CPU | |
usage. It is always enabled and running, although the PCLK register only | |
increments if the CPU is not executing on the ICS. | |
The process clock may be calibrated to wall-clock time or set to real time. | |
In hardware, the process clock has a one-millisecond period. Setting the | |
mode to real time schedules clock events based on the number of event ticks | |
equivalent to one millisecond. Because the simulator is an order of | |
magnitude faster than the hardware, this short period precludes idling. | |
In the calibrated mode, the short period would still preclude idling. | |
Therefore, in this mode, the clock is scheduled with a ten-millisecond | |
service time, and the PCLK register is incremented by ten for each event | |
service. To present the correct value when PCLK is read, the | |
"cpu_update_pclk" routine is called by the RCLK instruction executor to | |
increment the count by an amount proportional to the fraction of the service | |
interval that has elapsed. In addition, that routine is called by the CPU | |
instruction postlude, so that PCLK will have the correct value if it is | |
examined from the SCP command prompt. | |
The simulation console is normally hosted by, and therefore polled by, the | |
ATC on channel 0. If the console is not hosted by the ATC, due either to a | |
SET ATC NOCONSOLE or a SET ATC DISABLED command, the process clock assumes | |
polling control over the console. | |
Implementation notes: | |
1. If the process clock is calibrated, the system clock and ATC poll | |
services are synchronized with the process clock service to improve | |
idling. | |
2. The current CPU speed, expressed as a multiple of the hardware speed, is | |
calculated for each service entry. It may be displayed at the SCP prompt | |
with the SHOW CPU SPEED command. The speed is only representative when | |
the process clock is calibrated, and the CPU is not executing a PAUS | |
instruction (which suspends the normal fetch/execute instruction cycle). | |
*/ | |
static t_stat cpu_service (UNIT *uptr) | |
{ | |
const t_bool ics_exec = (CPX1 & cpx1_ICSFLAG) != 0; /* TRUE if the CPU is executing on the ICS */ | |
t_stat status; | |
dprintf (cpu_dev, DEB_PSERV, "Process clock service entered on the %s\n", | |
(ics_exec ? "ICS" : "user stack")); | |
if (!ics_exec) /* if the CPU is not executing on the ICS */ | |
PCLK = PCLK + pclk_increment & R_MASK; /* then increment the process clock */ | |
cpu_is_calibrated = (uptr->flags & UNIT_CALTIME) != 0; /* TRUE if the process clock is calibrated */ | |
if (cpu_is_calibrated) { /* if the process clock is tracking wall-clock time */ | |
uptr->wait = sim_rtcn_calb (PCLK_RATE, TMR_PCLK); /* then calibrate it */ | |
pclk_increment = PCLK_MULTIPLIER; /* and set the increment to the multiplier */ | |
} | |
else { /* otherwise */ | |
uptr->wait = PCLK_PERIOD; /* set the delay as an event tick count */ | |
pclk_increment = 1; /* and set the increment without multiplying */ | |
} | |
sim_activate (uptr, uptr->wait); /* reschedule the timer */ | |
cpu_speed = uptr->wait / (PCLK_PERIOD * pclk_increment); /* calculate the current CPU speed multiplier */ | |
if (atc_is_polling == FALSE) { /* if the ATC is not polling for the simulation console */ | |
status = sim_poll_kbd (); /* then we must poll for a console interrupt */ | |
if (status < SCPE_KFLAG) /* if the result is not a character */ | |
return status; /* then return the resulting status */ | |
} | |
return SCPE_OK; /* return the success of the service */ | |
} | |
/* Reset the CPU. | |
This routine is called for a RESET, RESET CPU, or BOOT CPU command. It is the | |
simulation equivalent of the CPURESET signal, which is asserted by the front | |
panel LOAD switch. In hardware, this causes a microcode restart in addition | |
to clearing certain registers. | |
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. | |
If this is a power-on reset ("RESET -P"), the process clock calibrated timer | |
is initialized, and any LOAD or DUMP request in progress is cleared. | |
The micromachine is halted, the process clock is scheduled, and, if a DUMP is | |
not in progress, several registers are cleared. The register values are | |
preserved for a DUMP to record the state of the machine accurately. | |
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. | |
*/ | |
static t_stat cpu_reset (DEVICE *dptr) | |
{ | |
if (M == NULL) { /* if this is the first call after simulator start */ | |
M = (uint16 *) calloc (PA_MAX, sizeof (uint16)); /* then allocate the maximum amount of memory needed */ | |
if (M == NULL) /* if the allocation failed */ | |
return SCPE_MEM; /* then report the error and abort the simulation */ | |
else /* otherwise the memory was allocated */ | |
set_model (&cpu_unit, UNIT_SERIES_III, /* so establish the initial CPU model */ | |
NULL, NULL); | |
for (sim_PC = dptr->registers; /* find the P register entry */ | |
sim_PC->loc != &P && 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! */ | |
} | |
if (sim_switches & SWMASK ('P')) { /* if this is a power-on reset */ | |
sim_rtcn_init (cpu_unit.wait, TMR_PCLK); /* then initialize the process clock timer */ | |
CPX2 &= ~(cpx2_LOADSWCH | cpx2_DUMPSWCH); /* and clear any cold load or dump request */ | |
} | |
cpu_micro_state = halted; /* halt the micromachine */ | |
sim_activate_abs (&cpu_unit, cpu_unit.wait); /* and schedule the process clock */ | |
if (!(CPX2 & cpx2_DUMPSWCH)) { /* if the DUMP switch is inactive */ | |
PCLK = 0; /* then clear the process clock counter */ | |
CPX1 = 0; /* and all run-mode signals */ | |
CPX2 &= ~(cpx2_RUN | cpx2_SYSHALT); /* and the run and system halt flip-flops */ | |
CNTR = SR; /* copy the stack register to the counter */ | |
cpu_flush (); /* and flush the TOS registers to memory */ | |
} | |
return SCPE_OK; /* indicate that the reset succeeded */ | |
} | |
/* Examine a 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, if "switches" includes SIM_SW_STOP, then "address" is an offset | |
from PBANK; otherwise, it is an absolute address. If the supplied address is | |
beyond the current memory limit, "non-existent memory" status is returned. | |
Otherwise, the value is obtained from memory and returned in "eval_array." | |
*/ | |
static t_stat cpu_examine (t_value *eval_array, t_addr address, UNIT *uptr, int32 switches) | |
{ | |
if (switches & SIM_SW_STOP) /* if entry is for a simulator stop */ | |
address = TO_PA (PBANK, address); /* then form a PBANK-based physical address */ | |
if (address >= MEMSIZE) /* if the address is beyond memory limits */ | |
return SCPE_NXM; /* then return non-existent memory status */ | |
else if (eval_array == NULL) /* if the value pointer was not supplied */ | |
return SCPE_IERR; /* then return internal error status */ | |
else { /* otherwise */ | |
eval_array [0] = (t_value) M [address]; /* store the return value */ | |
return SCPE_OK; /* and return success */ | |
} | |
} | |
/* Deposit to a 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. If the supplied | |
address is beyond the current memory limit, "non-existent memory" status is | |
returned. | |
The presence of any "switches" supplied on the command line does not affect | |
the operation of the routine. | |
*/ | |
static t_stat cpu_deposit (t_value value, t_addr address, UNIT *uptr, int32 switches) | |
{ | |
if (address >= MEMSIZE) /* if the address is beyond memory limits */ | |
return SCPE_NXM; /* then return non-existent memory status */ | |
else { /* otherwise */ | |
M [address] = value & DV_MASK; /* store the supplied value into memory */ | |
return SCPE_OK; /* and return success */ | |
} | |
} | |
/* 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 and 1 to set | |
them, and "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>...] | |
The valid <stopname>s are contained in the debug table "cpu_stop". If names | |
are not specified, all stop conditions are enabled or disabled. | |
Implementation notes: | |
1. The CPU simulator maintains a private and a public set of simulator | |
stops. This routine sets the private set. The private set is copied to | |
the public set as part of the instruction execution prelude, unless the | |
"-B" ("bypass") command-line switch is used with the run command. This | |
allows the stops to be bypassed conveniently for the first instruction | |
execution only. | |
*/ | |
static t_stat set_stops (UNIT *uptr, int32 option, CONST char *cptr, void *desc) | |
{ | |
char gbuf [CBUFSIZE]; | |
uint32 stop; | |
if (cptr == NULL) { /* if there are no arguments */ | |
sim_stops = 0; /* then clear all of the stop flags */ | |
if (option == 1) /* if we're setting the stops */ | |
for (stop = 0; cpu_stop [stop].name != NULL; stop++) /* then loop through the flags */ | |
sim_stops |= cpu_stop [stop].mask; /* and add each one to the set */ | |
} | |
else if (*cptr == '\0') /* otherwise if the argument is empty */ | |
return SCPE_MISVAL; /* then report the missing value */ | |
else /* otherwise at least one 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 */ | |
sim_stops |= cpu_stop [stop].mask; /* then add the stop flag */ | |
else /* otherwise it's a NOSTOP argument */ | |
sim_stops &= ~cpu_stop [stop].mask; /* so remove the flag */ | |
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 machine. 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. | |
Implementation notes: | |
1. The memory access routines return a zero value for locations beyond the | |
currently defined memory size. Therefore, the unused area need not be | |
explicitly zeroed. | |
*/ | |
static t_stat set_size (UNIT *uptr, int32 new_size, CONST char *cptr, void *desc) | |
{ | |
static CONST char confirm [] = "Really truncate memory [N]?"; | |
const uint32 model = CPU_MODEL (uptr->flags); /* the current CPU model index */ | |
uint32 address; | |
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 */ | |
for (address = new_size; address < MEMSIZE; address++) /* then check the values in truncated memory, if any */ | |
if (M [address] != 0) /* if this location is non-zero */ | |
if (get_yn (confirm, FALSE) == FALSE) /* then if the user denies confirmation */ | |
return SCPE_INCOMP; /* then abort the command */ | |
else /* otherwise we have explicit confirmation to proceed */ | |
break; /* so checking is no longer necessary */ | |
MEMSIZE = new_size; /* set the new memory size */ | |
return SCPE_OK; /* confirm that the change is OK */ | |
} | |
/* Change the CPU model. | |
This validation routine is called to configure the CPU model. The | |
"new_model" parameter is set to the model desired and will be one of the unit | |
model flags. The other parameters are not used. | |
The routine processes commands of the form: | |
SET [-F] CPU <model> | |
Setting the model establishes a set of typical hardware features. It also | |
verifies that the current memory size is supported by the new model. If it | |
is not, the size is reduced to the maximum supported memory configuration. | |
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. | |
This routine is also called once from the CPU reset routine to establish the | |
initial CPU model. The current memory size will be 0 when this call is made. | |
*/ | |
static t_stat set_model (UNIT *uptr, int32 new_model, CONST char *cptr, void *desc) | |
{ | |
const uint32 new_index = CPU_MODEL (new_model); /* the new index into the CPU features table */ | |
uint32 new_memsize; | |
t_stat status; | |
if (MEMSIZE == 0 /* if this is the initial establishing call */ | |
|| MEMSIZE > cpu_features [new_index].maxmem) /* or if the current memory size is unsupported */ | |
new_memsize = cpu_features [new_index].maxmem; /* then set the new size to the maximum supported size */ | |
else /* otherwise the current size is valid for the new model */ | |
new_memsize = MEMSIZE; /* so leave it unchanged */ | |
status = set_size (uptr, new_memsize, NULL, NULL); /* set the new memory size */ | |
if (status == SCPE_OK) /* if the change succeeded */ | |
uptr->flags = uptr->flags & ~UNIT_OPTS /* then set the typical features */ | |
| cpu_features [new_index].typ; /* for the new model */ | |
return status; /* return the validation result */ | |
} | |
/* Change a CPU option. | |
This validation routine is called to configure the option set for the current | |
CPU model. The "new_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. | |
*/ | |
static t_stat set_option (UNIT *uptr, int32 new_option, CONST char *cptr, void *desc) | |
{ | |
const uint32 model = CPU_MODEL (uptr->flags); /* the current CPU model index */ | |
if ((cpu_features [model].opt & new_option) != 0) /* if the option is supported on the current model */ | |
return SCPE_OK; /* then confirm the change */ | |
else /* otherwise */ | |
return SCPE_NOFNC; /* reject the change */ | |
} | |
/* Show the CPU simulation stop conditions. | |
This display routine is called to show the set of CPU stop conditions. The | |
"st" parameter is the open output stream. The other parameters are not used. | |
If at least one stop condition is enabled, the routine searches through the | |
stop table for flag bits that are set in the stop set. For each one it | |
finds, the routine prints the corresponding stop name. | |
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 (sim_stops == 0) /* if no simulation stops are set */ | |
fputs ("Stops disabled", st); /* then report that all are disabled */ | |
else { /* otherwise at least one stop is valid */ | |
fputs ("Stop=", st); /* so prepare to report the list of conditions */ | |
for (stop = 0; cpu_stop [stop].name != NULL; stop++) /* loop through the set of stops in the table */ | |
if (cpu_stop [stop].mask & sim_stops) { /* if the current stop is enabled */ | |
if (need_spacer) /* then if a spacer is needed */ | |
fputc (';', st); /* then add it first */ | |
fputs (cpu_stop [stop].name, st); /* report the stop name */ | |
need_spacer = TRUE; /* a spacer will be needed next time */ | |
} | |
} | |
fputc ('\n', st); /* add the trailing newline */ | |
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 process clock service routine. It is only representative when the | |
process clock is calibrated, and the CPU is not executing a PAUS instruction | |
(which suspends the normal fetch/execute instruction cycle). | |
*/ | |
static t_stat show_speed (FILE *st, UNIT *uptr, int32 val, CONST void *desc) | |
{ | |
fprintf (st, "Simulation speed = %dx\n", cpu_speed); /* display the current CPU speed */ | |
return SCPE_OK; /* and report success */ | |
} | |
/* CPU local utility routines */ | |
/* Process a halt-mode interrupt. | |
This routine is called when one or more of the interrupt request bits are set | |
in the CPX2 register. These bits represent switch closures on the CPU front | |
panel or on the optional maintenance display panel. The specific switch | |
closure is identified, and the corresponding microcode routine is executed. | |
If multiple bits are set in CPX2, the microcode recognizes them in order from | |
MSB to LSB. | |
If the RUN switch is set, a test is made for a System Halt, which inhibits | |
recognition of the switch. If the System Halt flag is set, the CPU must be | |
reset before execution may be resumed. Otherwise, the NIR is reloaded in | |
case P was changed during the simulation stop. If the R-bit (right stack op | |
pending) flag in the status register is set, but the NIR no longer contains a | |
stack instruction, R is cleared. If a stack instruction is present, and the | |
R-bit is set, then the CIR is set, and the following instruction is fetched. | |
The micromachine state is set to "running", and one event tick is added back | |
to the accumulator to ensure that a single step won't complete without | |
executing an instruction. | |
If the LOAD switch is pressed, the cold load process begins by filling memory | |
with HALT 10 instructions if SWCH register bit 8 is clear. The cold load | |
device number is obtained from the lower byte of the SWCH register. | |
The first part of the cold load process clears the TOS and STA registers, | |
stores the initial channel program in memory, and executes an SIO instruction | |
to start the channel. Once the device starts, interrupts are enabled, and | |
the micromachine state is set to "loading" in preparation for executing the | |
second part of the cold load process once the channel program ends. The | |
routine then exits to begin channel execution. | |
The LOAD switch remains set, so after each pass through the main execution | |
loop to run a channel cycle, this routine is reentered. The "loading" state | |
causes the second part of the process to check for device completion. The | |
expected external interrupt is cleared, the LOAD switch is cleared, the | |
micromachine state is set to "running", and the Cold Load trap is taken to | |
complete the process. | |
Implementation notes: | |
1. After RUN switch processing and return to the instruction loop, if no | |
interrupt is present, and the R-bit in the status register is clear, then | |
CIR will be set from NIR, and NIR will be reloaded. If the R-bit is set, | |
then CIR and NIR will not be changed, i.e., the NEXT action will be | |
skipped, so we perform it here. If an interrupt is pending, then the | |
interrupt will be processed using the old value of the CIR, and the | |
instruction in the NIR will become the first instruction executed after | |
the interrupt handler completes. | |
2. The cold load microcode is shared with the cold dump process. The dump | |
process saves memory locations DRT + 0 through DRT + 3 in the TOS | |
registers. The load process uses the same microcode but does not perform | |
the memory read, so the TOS registers are loaded with the previous | |
contents of the OPND register, which is effectively a random value. In | |
simulation, the TOS registers are cleared. | |
3. The cold load microcode waits forever for an interrupt from the cold | |
load device. If it doesn't occur, the microcode hangs until a system | |
reset is performed (it tests CPX1 bit 8 and does a JMP *-1 if the bit is | |
not set). The simulation follows the microcode behavior. | |
4. Front panel diagnostics and direct I/O cold loading is not implemented. | |
*/ | |
static t_stat halt_mode_interrupt (HP_WORD device_number) | |
{ | |
static HP_WORD cold_load_device; | |
uint32 address; | |
if (CPX2 & cpx2_RUNSWCH) { /* if the RUN switch is pressed */ | |
if (CPX2 & cpx2_SYSHALT) { /* then if the System Halt flip-flop is set */ | |
CPX2 &= ~CPX2_IRQ_SET; /* then clear all switches */ | |
return STOP_SYSHALT; /* as the CPU cannot run until it is reset */ | |
} | |
else /* otherwise */ | |
CPX2 = CPX2 & ~cpx2_RUNSWCH | cpx2_RUN; /* clear the switch and set the Run flip-flop */ | |
cpu_read_memory (fetch, P, &NIR); /* load the next instruction to execute */ | |
P = P + 1 & R_MASK; /* and point to the following instruction */ | |
if ((NIR & SUBOP_MASK) != 0) /* if the instruction is not a stack instruction */ | |
STA &= ~STATUS_R; /* then clear the R-bit in case it had been set */ | |
else if (STA & STATUS_R) { /* otherwise if a right-hand stack op is pending */ | |
CIR = NIR; /* then set the current instruction */ | |
cpu_read_memory (fetch, P, &NIR); /* and load the next instruction */ | |
} | |
cpu_micro_state = running; /* start the micromachine */ | |
sim_interval = sim_interval + 1; /* don't count this cycle against a STEP count */ | |
} | |
else if (CPX2 & cpx2_DUMPSWCH) { /* otherwise if the DUMP switch is pressed */ | |
CPX2 &= ~CPX2_IRQ_SET; /* then clear all switches */ | |
return SCPE_INCOMP; /* and report that DUMP is not implemented yet */ | |
} | |
else if (CPX2 & cpx2_LOADSWCH) /* otherwise if the LOAD switch is pressed */ | |
if (cpu_micro_state != loading) { /* then if the load is not in progress */ | |
reset_all (0); /* then reset the CPU and all I/O devices */ | |
if ((SWCH & 000200) == 0) /* if switch register bit 8 is clear */ | |
for (address = 0; address < MEMSIZE; address++) /* then fill memory */ | |
M [address] = HALT_10; /* with HALT 10 instructions */ | |
SBANK = 0; /* set the stack bank to bank 0 */ | |
cold_load_device = LOWER_BYTE (SWCH) & DEVNO_MASK; /* get the device number from the lower SWCH byte */ | |
if (cold_load_device < 3) { /* if the device number is between 0 and 2 */ | |
CPX2 &= ~cpx2_LOADSWCH; /* then reset the LOAD switch */ | |
return SCPE_INCOMP; /* and execute a front panel diagnostic */ | |
} | |
else if (cold_load_device > 63) { /* otherwise if the device number is > 63 */ | |
CPX2 &= ~cpx2_LOADSWCH; /* then reset the LOAD switch */ | |
return SCPE_INCOMP; /* and execute a direct I/O cold load */ | |
} | |
else { /* otherwise the device number is in the channel I/O range */ | |
RA = 0; /* set the */ | |
RB = 0; /* TOS registers */ | |
RC = 0; /* to the same */ | |
RD = 0; /* (random) value */ | |
SR = 4; /* mark the TOS registers as valid */ | |
STA = 0; /* and clear the status register */ | |
cpu_write_memory (absolute, 01430, 014000); /* SETBNK 0 */ | |
cpu_write_memory (absolute, 01431, 000000); | |
cpu_write_memory (absolute, 01432, 040000); /* CONTRL 0,<SWCH-upper> */ | |
cpu_write_memory (absolute, 01433, UPPER_BYTE (SWCH)); | |
cpu_write_memory (absolute, 01434, 077760); /* READ #16,001400 */ | |
cpu_write_memory (absolute, 01435, 001400); | |
cpu_write_memory (absolute, 01436, 000000); /* JUMP 001400 */ | |
cpu_write_memory (absolute, 01437, 001400); | |
cpu_write_memory (absolute, cold_load_device * 4, 01430); /* point the DRT to the cold load program */ | |
iop_direct_io (cold_load_device, ioSIO, 0); /* start the device */ | |
if (CPX1 & cpx1_IOTIMER) /* if the device did not respond */ | |
MICRO_ABORT (trap_SysHalt_IO_Timeout); /* then a System Halt occurs */ | |
else { /* otherwise the device has started */ | |
STA = STATUS_I | STATUS_O; /* so enable interrupts and set overflow */ | |
cpu_micro_state = loading; /* and set the load-in-progress state */ | |
} | |
} | |
} | |
else /* otherwise the load is in progress */ | |
if (CPX1 & cpx1_EXTINTR) { /* if an external interrupt is pending */ | |
CPX1 &= ~cpx1_EXTINTR; /* then clear it */ | |
iop_direct_io (device_number, ioRIN, 0); /* reset the device interrupt */ | |
if (device_number == cold_load_device) { /* if the expected device interrupted */ | |
CPX2 &= ~cpx2_LOADSWCH; /* then reset the LOAD switch */ | |
cpu_micro_state = running; /* clear the load-in-progress state */ | |
MICRO_ABORT (trap_Cold_Load); /* and execute the cold load trap handler */ | |
} | |
} /* otherwise wait for the cold load device to interrupt */ | |
return SCPE_OK; | |
} | |
/* Execute one machine instruction. | |
This routine executes the CPU instruction present in the CIR. 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. | |
Unimplemented instructions are detected by those decoding branches that | |
result from bit patterns not corresponding to legal instructions or | |
corresponding to optional instructions not currently enabled. Normally, | |
execution of an unimplemented instruction would result in an Unimplemented | |
Instruction trap. However, for debugging purposes, a simulator stop may be | |
requested instead by returning STOP_UNIMPL status if the SS_UNIMPL simulation | |
stop flag is set. | |
This routine implements the main instruction dispatcher, as well as memory | |
address instructions (subopcodes 04-17). Instructions corresponding to | |
subopcodes 00-03 are executed by routines in the base instruction set module. | |
Implementation notes: | |
1. Each instruction executor begins with a comment listing the instruction | |
mnemonic and, following in parentheses, the condition code setting, or | |
"none" if the condition code is not altered, and a list of any traps that | |
might be generated. | |
2. Stack preadjusts are simpler if performed explicitly due to overlapping | |
requirements that reduce the number of static preadjusts to 4 of the 16 | |
entries. | |
3. The order of operations for each instruction follows the microcode. For | |
example, the LOAD instruction performs the bounds check on the effective | |
address and reads the operand before pushing the stack down and storing | |
it in the RA registrer. Pushing the stack down first and then reading | |
the value directly into the RA register would leave the stack in a | |
different state if the memory access caused a Bounds Violation trap. | |
4. The TBA, MTBA, TBX, and MTBX instructions take longer to execute than the | |
nominal 2.5 microseconds assumed for the average instruction execution | |
time. Consequently, the 7905 disc diagnostic fails Step 66 (the retry | |
counter test) if the DS device is set for REALTIME operation. The | |
diagnostic uses the MTBA P+0 instruction in a timing loop, which expires | |
before the disc operations complete. | |
A workaround for this diagnostic is to decrement sim_interval twice for | |
these instructions. However, doing so causes the 7970 tape diagnostic to | |
fail Step 532 (the timing error test) for the opposite reason: a wait | |
loop of MTBA P+0 instructions causes the tape data transfer service event | |
time to count down twice as fast, while the multiplexer channel data | |
transfer polls occur at the usual one per instruction. This could be | |
remedied by having the channel polls execute twice as many I/O cycles for | |
these instructions, although the general solution would be to recast | |
sim_intervals as microseconds and to decrement sim_interval by differing | |
amounts appropriate for each instruction. | |
*/ | |
static t_stat machine_instruction (void) | |
{ | |
HP_WORD displacement, opcode, offset, operand, operand_1, operand_2, result; | |
int32 control, limit; | |
ACCESS_CLASS class; | |
BYTE_SELECTOR selector; | |
t_bool branch; | |
t_stat status = SCPE_OK; | |
switch (SUBOP (CIR)) { /* dispatch on bits 0-3 of the instruction */ | |
case 000: /* stack operations */ | |
status = cpu_stack_op (); /* set the status from the instruction executor */ | |
break; | |
case 001: /* shift, branch, and bit test operations */ | |
status = cpu_shift_branch_bit_op (); /* set the status from the instruction executor */ | |
break; | |
case 002: /* move, special, firmware, immediate, field, and register operations */ | |
status = cpu_move_spec_fw_imm_field_reg_op (); /* set the status from the instruction executor */ | |
break; | |
case 003: /* I/O, control, program, immediate, and memory operations */ | |
status = cpu_io_cntl_prog_imm_mem_op (); /* set the status from the instruction executor */ | |
break; | |
case 004: /* LOAD (CCA; STOV, BNDV) */ | |
cpu_ea (CIR, &class, &offset, NULL); /* get the effective address */ | |
cpu_read_memory (class, offset, &operand); /* and read the operand */ | |
cpu_push (); /* push the operand */ | |
RA = operand; /* onto the stack */ | |
SET_CCA (RA, 0); /* set the condition code */ | |
break; | |
case 005: /* TBA, MTBA, TBX, MTBX, and STOR */ | |
if (CIR & M_FLAG) { /* STOR (none; STUN, BNDV) */ | |
cpu_ea (CIR, &class, &offset, NULL); /* get the effective address */ | |
PREADJUST_SR (1); /* ensure that at least one TOS register is loaded */ | |
cpu_write_memory (class, offset, RA); /* write the TOS to memory */ | |
cpu_pop (); /* and pop the stack */ | |
} | |
else { /* TBA, MTBA, TBX, or MTBX */ | |
opcode = CIR & TBR_MASK; /* get the test and branch operation */ | |
if (opcode == TBA || opcode == MTBA) { /* TBA or MTBA (none; STUN, STOV, BNDV) */ | |
PREADJUST_SR (3); /* ensure that at least three TOS registers are loaded */ | |
while (SR > 3) /* if more than three TOS register are loaded */ | |
cpu_queue_down (); /* queue them down until exactly three are left */ | |
offset = DB + RC & LA_MASK; /* get the address of the control value */ | |
if (DL <= offset && offset <= SM || PRIV) /* if the address is within the segment */ | |
cpu_read_memory (data, offset, &operand); /* then read the value */ | |
else /* otherwise */ | |
MICRO_ABORT (trap_Bounds_Violation); /* trap with a bounds violation if not privileged */ | |
if (opcode == MTBA) { /* if the instruction is MTBA */ | |
operand = operand + RB & DV_MASK; /* then add the step size */ | |
cpu_write_memory (data, offset, operand); /* to the control variable */ | |
} | |
control = SEXT (operand); /* sign-extend the control value */ | |
} | |
else { /* TBX or MTBX (none; STUN, BNDV) */ | |
PREADJUST_SR (2); /* ensure that at least two TOS registers are loaded */ | |
if (opcode == MTBX) /* if the instruction is MTBX */ | |
X = X + RB & R_MASK; /* then add the step size to the control variable */ | |
control = SEXT (X); /* sign-extend the control value */ | |
} | |
limit = SEXT (RA); /* sign-extend the limit value */ | |
if (RB & D16_SIGN) /* if the step size is negative */ | |
branch = control >= limit; /* then branch if the value is not below the limit */ | |
else /* otherwise */ | |
branch = control <= limit; /* branch if the value is not above the limit */ | |
if (branch) { /* if the test succeeded */ | |
displacement = CIR & DISPL_255_MASK; /* then get the branch displacement */ | |
if (CIR & DISPL_255_SIGN) /* if the displacement is negative */ | |
offset = P - 2 - displacement & LA_MASK; /* then subtract the displacement from the base */ | |
else /* otherwise */ | |
offset = P - 2 + displacement & LA_MASK; /* add the displacement to the base */ | |
if (cpu_stop_flags & SS_LOOP /* if the infinite loop stop is active */ | |
&& displacement == 0 /* and the target is the current instruction */ | |
&& (opcode == TBA || opcode == TBX)) /* and the instruction must be checked */ | |
status = STOP_INFLOOP; /* then stop the simulator */ | |
else /* otherwise */ | |
status = SCPE_OK; /* continue */ | |
cpu_read_memory (fetch_checked, offset, &NIR); /* load the next instruction register */ | |
P = offset + 1 & R_MASK; /* and increment the program counter */ | |
} | |
else { /* otherwise the test failed */ | |
cpu_pop (); /* so pop the limit */ | |
cpu_pop (); /* and the step size from the stack */ | |
if (opcode == TBA || opcode == MTBA) /* if the instruction is TBA or MTBA */ | |
cpu_pop (); /* then pop the variable address too */ | |
} /* and continue execution at P + 1 */ | |
} | |
break; | |
case 006: /* CMPM (CCC; STUN) */ | |
cpu_ea (CIR, &class, &offset, NULL); /* get the effective address */ | |
cpu_read_memory (class, offset, &operand); /* and read the operand */ | |
PREADJUST_SR (1); /* ensure that at least one TOS register is loaded */ | |
SET_CCC (RA, 0, operand, 0); /* set the condition code from the TOS value */ | |
cpu_pop (); /* and then pop the value from the stack */ | |
break; | |
case 007: /* ADDM (CCA, C, O; STUN, BNDV, ARITH) */ | |
cpu_ea (CIR, &class, &offset, NULL); /* get the effective address */ | |
cpu_read_memory (class, offset, &operand); /* and read the operand */ | |
PREADJUST_SR (1); /* ensure that at least one TOS register is loaded */ | |
RA = cpu_add_16 (RA, operand); /* add the operands */ | |
SET_CCA (RA, 0); /* and set the condition code */ | |
break; | |
case 010: /* SUBM (CCA, C, O; STUN, BNDV, ARITH) */ | |
cpu_ea (CIR, &class, &offset, NULL); /* get the effective address */ | |
cpu_read_memory (class, offset, &operand); /* and read the operand */ | |
PREADJUST_SR (1); /* ensure that at least one TOS register is loaded */ | |
RA = cpu_sub_16 (RA, operand); /* subtract the operands */ | |
SET_CCA (RA, 0); /* and set the condition code */ | |
break; | |
case 011: /* MPYM (CCA, O; STUN, STOV, BNDV, ARITH) */ | |
cpu_ea (CIR, &class, &offset, NULL); /* get the effective address */ | |
cpu_read_memory (class, offset, &operand); /* and read the operand */ | |
PREADJUST_SR (1); /* ensure that at least one TOS register is loaded */ | |
RA = cpu_mpy_16 (RA, operand); /* multiply the operands */ | |
SET_CCA (RA, 0); /* and set the condition code */ | |
break; | |
case 012: /* INCM, DECM */ | |
cpu_ea (CIR | M_FLAG, &class, &offset, NULL); /* get the effective address (forced to data-relative) */ | |
cpu_read_memory (class, offset, &operand); /* and read the operand */ | |
if (CIR & M_FLAG) /* DECM (CCA, C, O; BNDV, ARITH) */ | |
result = cpu_sub_16 (operand, 1); /* decrement the operand and set C and O as necessary */ | |
else /* INCM (CCA, C, O; BNDV, ARITH) */ | |
result = cpu_add_16 (operand, 1); /* increment the operand and set C and O as necessary */ | |
cpu_write_memory (class, offset, result); /* write the operand back to memory */ | |
SET_CCA (result, 0); /* and set the condition code */ | |
break; | |
case 013: /* LDX (CCA; BNDV) */ | |
cpu_ea (CIR, &class, &offset, NULL); /* get the effective address */ | |
cpu_read_memory (class, offset, &X); /* and read the operand into the X register */ | |
SET_CCA (X, 0); /* set the condition code */ | |
break; | |
case 014: /* BR (none; BNDV), BCC (none; BNDV) */ | |
if ((CIR & BR_MASK) != BCC) { /* if the instruction is BR */ | |
cpu_ea (CIR, &class, &offset, NULL); /* then get the effective address of the branch */ | |
if (cpu_stop_flags & SS_LOOP /* if the infinite loop stop is active */ | |
&& offset == (P - 2 & LA_MASK)) /* and the target is the current instruction */ | |
status = STOP_INFLOOP; /* then stop the simulator */ | |
else /* otherwise */ | |
status = SCPE_OK; /* continue */ | |
cpu_read_memory (fetch_checked, offset, &NIR); /* load the next instruction register */ | |
P = offset + 1 & R_MASK; /* and increment the program counter */ | |
} | |
else if (TO_CCF (STA) & (CIR << BCC_CCF_SHIFT)) /* otherwise if the BCC test succeeds */ | |
status = cpu_branch_short (TRUE); /* then branch to the target address */ | |
break; /* otherwise continue execution at P + 1 */ | |
case 015: /* LDD (CCA; STOV, BNDV), LDB (CCB; STOV, BNDV) */ | |
if (CIR & M_FLAG) { /* if the instruction is LDD */ | |
cpu_ea (CIR, &class, &offset, NULL); /* then get the effective address of the double-word */ | |
cpu_read_memory (class, offset, &operand_1); /* read the MSW */ | |
cpu_read_memory (class, offset + 1 & LA_MASK, &operand_2); /* and the LSW of the operand */ | |
cpu_push (); /* push the MSW */ | |
cpu_push (); /* and the LSW */ | |
RB = operand_1; /* of the operand */ | |
RA = operand_2; /* onto the stack */ | |
SET_CCA (RB, RA); /* set the condition code */ | |
} | |
else { /* otherwise the instruction is LDB */ | |
cpu_ea (CIR | M_FLAG, &class, &offset, &selector); /* so get the effective word address of the byte */ | |
cpu_read_memory (class, offset, &operand); /* and read the operand */ | |
cpu_push (); /* push the stack down */ | |
if (selector == upper) /* if the upper byte is selected */ | |
RA = UPPER_BYTE (operand); /* then store it in the TOS */ | |
else /* otherwise */ | |
RA = LOWER_BYTE (operand); /* store the lower byte in the TOS */ | |
SET_CCB (RA); /* set the condition code */ | |
} | |
break; | |
case 016: /* STD (none; STUN, BNDV), STB (none; STUN, BNDV) */ | |
if (CIR & M_FLAG) { /* if the instruction is STD */ | |
cpu_ea (CIR, &class, &offset, NULL); /* then get the effective address of the double-word */ | |
PREADJUST_SR (2); /* ensure that at least two TOS registers are loaded */ | |
cpu_write_memory (class, offset + 1 & LA_MASK, RA); /* write the LSW first to follow the microcode */ | |
cpu_write_memory (class, offset, RB); /* and then write the MSW */ | |
cpu_pop (); /* pop the TOS */ | |
cpu_pop (); /* and the NOS */ | |
} | |
else { /* otherwise the instruction is STB */ | |
cpu_ea (CIR | M_FLAG, &class, &offset, &selector); /* so get the effective word address of the byte */ | |
PREADJUST_SR (1); /* ensure that at least one TOS register is loaded */ | |
cpu_read_memory (class, offset, &operand); /* read the word containing the target byte */ | |
if (selector == upper) /* if the upper byte is targeted */ | |
operand = REPLACE_UPPER (operand, RA); /* then store the TOS into it */ | |
else /* otherwise */ | |
operand = REPLACE_LOWER (operand, RA); /* store the TOS into the lower byte */ | |
cpu_write_memory (class, offset, operand); /* write the word back */ | |
cpu_pop (); /* and pop the TOS */ | |
} | |
break; | |
case 017: /* LRA (none; STOV, BNDV) */ | |
cpu_ea (CIR, &class, &offset, NULL); /* get the effective address */ | |
if (class == program_checked) /* if this a program reference */ | |
offset = offset - PB; /* then subtract PB to get the relative address */ | |
else /* otherwise it is a data or stack reference */ | |
offset = offset - DB; /* so subtract DB to get the address */ | |
cpu_push (); /* push the relative address */ | |
RA = offset & R_MASK; /* onto the stack */ | |
break; | |
} /* all cases are handled */ | |
if (status == STOP_UNIMPL /* if the instruction is unimplemented */ | |
&& (cpu_stop_flags & SS_UNIMPL) == 0) /* and the unimplemented instruction stop is inactive */ | |
MICRO_ABORT (trap_Unimplemented); /* then trap to handle it */ | |
return status; | |
} |