4033 lines
211 KiB
C
4033 lines
211 KiB
C
/* 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
|
|
|
|
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, char *cptr, void *desc);
|
|
static t_stat set_size (UNIT *uptr, int32 new_size, char *cptr, void *desc);
|
|
static t_stat set_model (UNIT *uptr, int32 new_model, char *cptr, void *desc);
|
|
static t_stat set_option (UNIT *uptr, int32 new_option, char *cptr, void *desc);
|
|
|
|
static t_stat show_stops (FILE *st, UNIT *uptr, int32 val, void *desc);
|
|
static t_stat show_speed (FILE *st, UNIT *uptr, int32 val, 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; /* then add the doubled index to the displacement */
|
|
|
|
else if (mode_disp & X_FLAG) /* otherwise if the mode is indexed */
|
|
displacement = displacement + X; /* 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, 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, 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, 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, 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, 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, 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;
|
|
}
|