4595 lines
246 KiB
C
4595 lines
246 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
|
|
|
|
29_Dec-16 JDB Changed the status mnemonic flag from REG_S to REG_T
|
|
07-Nov-16 JDB Renamed cpu_byte_to_word_ea to cpu_byte_ea
|
|
03-Nov-16 JDB Added zero offsets to the cpu_call_procedure calls
|
|
01-Nov-16 JDB Added per-instruction trace capability
|
|
24-Oct-16 JDB Renamed SEXT macro to SEXT16
|
|
22-Sep-16 JDB Moved byte_to_word_address from hp3000_cpu_base.c
|
|
21-Sep-16 JDB Added CIS/NOCIS option
|
|
01-Sep-16 JDB Add power fail/power restore support
|
|
23-Aug-16 JDB Add module interrupt support
|
|
14-Jul-16 JDB Implemented the cold dump process
|
|
Changed "loading" EXEC_STATE to "waiting"
|
|
11-Jul-16 JDB Change "cpu_unit" from a UNIT to an array of one UNIT
|
|
08-Jun-16 JDB Corrected %d format to %u for unsigned values
|
|
16-May-16 JDB ACCESS_PROPERTIES.name is now a pointer-to-constant
|
|
13-May-16 JDB Modified for revised SCP API function parameter types
|
|
24-Mar-16 JDB Changed memory word type from uint16 to MEMORY_WORD
|
|
21-Mar-16 JDB Changed cpu_ccb_table type from uint16 to HP_WORD
|
|
11-Mar-16 JDB Fixed byte EA calculations with negative indexes
|
|
22-Dec-15 JDB First release version
|
|
01-Apr-15 JDB First successful run of MPE-V/R through account login
|
|
11-Dec-12 JDB Created
|
|
|
|
References:
|
|
- HP 3000 Series II System Microprogram Listing
|
|
(30000-90023, August 1976)
|
|
- HP 3000 Series II/III System Reference Manual
|
|
(30000-90020, July 1978)
|
|
- Machine Instruction Set Reference Manual
|
|
(30000-90022, June 1984)
|
|
|
|
|
|
The HP 3000 is a family of general-purpose business computers that were sold
|
|
by Hewlett-Packard from 1972 through 2001. There are two major divisions
|
|
within this family: the "classic" 16-bit, stack-oriented CISC machines, and
|
|
the "Precision Architecture" 32-bit, register-oriented RISC machines that
|
|
succeeded them. All machines run versions of MPE, the "Multiprogramming
|
|
Executive" operating system.
|
|
|
|
Within the "classic" division, there are two additional subdivisions, based
|
|
on the method used for peripheral connections: the original "SIO" machines,
|
|
and the later "HP-IB" machines. The I/O interfacing hardware differs between
|
|
the two types of machines, as do the privileged I/O machine instructions.
|
|
The user instruction sets are identical, as are the register sets visible to
|
|
the programmer. The I/O drivers are different to account for the hardware
|
|
differences, and therefore they run slightly different versions of MPE.
|
|
|
|
This implementation is a simulator for the classic SIO machines. This group
|
|
consists of the 3000 CX, the Series I, Series II, and Series III; the last is
|
|
simulated here. The CX and Series I, which is a repackaged CX, are
|
|
essentially subsets of the Series II/III -- a smaller instruction set,
|
|
limited memory size, and lower-precision floating-point instructions.
|
|
Simulation of these machines may be added in the future. Future simulation
|
|
of the HP-IB machines (the Series 30 through 70) is desirable, as the latest
|
|
MPE versions run only on these machines, but documentation on the internals
|
|
of the HP-IB hardware controllers is nonexistent.
|
|
|
|
The CX and Series I support 64K 16-bit words of memory. The Series II
|
|
supports up to 256K, divided into four banks of 64K words each, and the
|
|
Series III extends this to 1024K words using 16 banks. Memory is divided
|
|
into variable-length code and data segments, with the latter containing a
|
|
program's global data area and stack.
|
|
|
|
Memory protection is accomplished by checking program, data, and stack
|
|
accesses against segment base and limit registers, which can be set only by
|
|
MPE. Bounds violations cause automatic hardware traps to a handler routine
|
|
within MPE. Some violations may be permitted; for example, a Stack Overflow
|
|
trap may cause MPE to allocate a larger stack and then restart the
|
|
interrupted instruction. Almost all memory references are position-
|
|
independent, so moving segments to accommodate expansion requires only
|
|
resetting of the segment registers to point at the new locations. 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 | option set | option subset | 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 | 0 0 0 1 | APL op | APL
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | COBOL op | COBOL
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 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.
|
|
|
|
All of the base set instructions are one word in length. Most of the
|
|
firmware extension instructions occupy one word as well, although some are
|
|
two words long. If the first word of a two-word instruction is valid but the
|
|
second is not, an Unimplemented Instruction trap will occur, with P pointing
|
|
to the location after the second word.
|
|
|
|
|
|
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 OPND option traces operand values. Some instructions that take memory
|
|
and register operands that are difficult to decode from DATA or REG traces
|
|
present the operand values in a higher-level format. The memory bank and
|
|
address values are always those of the operands. The operand data and value
|
|
presented are specific to the instruction; see the instruction executor
|
|
comments for details.
|
|
|
|
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 opnd: 00.135771 000000 DFLC '+','!'
|
|
>>CPU opnd: 00.045071 000252 target fraction 3 length 6,"002222"
|
|
~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| | | |
|
|
| | | +-- operand-specific value
|
|
| | +------------ operand-specific data
|
|
| +-------------------- octal address (effective address)
|
|
+------------------------- octal bank (PBANK, DBANK, or SBANK)
|
|
|
|
>>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_mem.h"
|
|
#include "hp3000_io.h"
|
|
|
|
|
|
|
|
/* 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 */
|
|
|
|
#define CPU_IO_RESET 0 /* reset CPU and all I/O devices */
|
|
#define IO_RESET 1 /* reset just the I/O devices */
|
|
|
|
#define CNTL_BASE 8 /* the radix for the cold dump control byte */
|
|
#define CNTL_MAX 0377 /* the maximum cold dump control byte value */
|
|
|
|
#define SIO_JUMP 0000000u /* Jump unconditionally */
|
|
#define SIO_SBANK 0014000u /* Set bank */
|
|
#define SIO_ENDIN 0034000u /* End with interrupt */
|
|
#define SIO_CNTL 0040000u /* Control */
|
|
#define SIO_WRITE 0060000u /* Write 4096 words */
|
|
#define SIO_READ 0077760u /* Read 16 words */
|
|
|
|
#define MS_CN_GAP 0000005u /* Write Gap */
|
|
#define MS_CN_WRR 0000004u /* Write Record */
|
|
#define MS_CN_RST 0000011u /* Rewind and Reset */
|
|
#define MS_CN_BSR 0000012u /* Backspace Record */
|
|
#define MS_CN_WFM 0000015u /* Write File Mark */
|
|
|
|
#define MS_ST_PROTECTED 0001000u /* write protected */
|
|
#define MS_ST_READY 0000400u /* unit ready */
|
|
|
|
#define MS_ST_MASK (MS_ST_PROTECTED | MS_ST_READY)
|
|
|
|
|
|
/* CPU global SCP data definitions */
|
|
|
|
DEVICE cpu_dev; /* incomplete device structure */
|
|
|
|
REG *sim_PC = NULL; /* 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 MOD = 0; /* module control 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 HP_WORD 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 */
|
|
|
|
POWER_STATE cpu_power_state = power_on; /* the power supply state */
|
|
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 [0]; /* a (constant) 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 */
|
|
static uint32 dump_control = 0002006u; /* the cold dump control word (default CNTL = 4, DEVNO = 6 */
|
|
static uint32 debug_save = 0; /* the current debug trace flag settings */
|
|
static HP_WORD exec_mask = 0; /* the current instruction execution trace mask */
|
|
static HP_WORD exec_match = D16_UMAX; /* the current instruction execution trace matching value */
|
|
|
|
|
|
/* CPU local data structures */
|
|
|
|
|
|
/* 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 */
|
|
UNIT_CIS,
|
|
1024 * 1024 },
|
|
{ 0, /* UNIT_SERIES_II */
|
|
0,
|
|
256 * 1024 }
|
|
};
|
|
|
|
|
|
/* CPU local SCP support routine declarations */
|
|
|
|
static t_stat cpu_service (UNIT *uptr);
|
|
static t_stat cpu_reset (DEVICE *dptr);
|
|
|
|
static t_stat set_stops (UNIT *uptr, int32 option, CONST char *cptr, void *desc);
|
|
static t_stat set_exec (UNIT *uptr, int32 option, CONST char *cptr, void *desc);
|
|
static t_stat set_dump (UNIT *uptr, int32 option, CONST char *cptr, void *desc);
|
|
static t_stat set_size (UNIT *uptr, int32 new_size, CONST char *cptr, void *desc);
|
|
static t_stat set_model (UNIT *uptr, int32 new_model, CONST char *cptr, void *desc);
|
|
static t_stat set_option (UNIT *uptr, int32 new_option, CONST char *cptr, void *desc);
|
|
static t_stat set_pfars (UNIT *uptr, int32 setting, CONST char *cptr, void *desc);
|
|
|
|
static t_stat show_stops (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
|
|
static t_stat show_exec (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
|
|
static t_stat show_dump (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
|
|
static t_stat show_speed (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
|
|
|
|
|
|
/* CPU local utility routine declarations */
|
|
|
|
static t_stat halt_mode_interrupt (HP_WORD device_number);
|
|
static t_stat machine_instruction (void);
|
|
|
|
|
|
/* CPU SCP data structures */
|
|
|
|
|
|
/* Unit list.
|
|
|
|
The CPU unit holds the main memory capacity and is used to schedule the
|
|
process clock events.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The unit structure must be global for other modules to obtain the memory
|
|
size via the MEMSIZE macro, which references the "capac" field.
|
|
*/
|
|
|
|
#define UNIT_FLAGS (UNIT_PFARS | UNIT_CALTIME)
|
|
|
|
UNIT cpu_unit [] = {
|
|
{ UDATA (&cpu_service, UNIT_FLAGS | UNIT_FIX | UNIT_BINK | UNIT_IDLE, 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_T 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 Radix Width Offset 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_T | 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 (MOD, MOD, 16), REG_HRO | REG_FIT }, /* module control register */
|
|
|
|
{ DRDATA (POWER, cpu_power_state, 2), REG_HRO }, /* system power supply state */
|
|
{ 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_CIS, UNIT_CIS, "CIS", "CIS", &set_option, NULL, NULL },
|
|
{ UNIT_CIS, 0, "no CIS", "NOCIS", NULL, NULL, NULL },
|
|
|
|
{ UNIT_PFARS, UNIT_PFARS, "auto-restart", "ARS", &set_pfars, NULL, NULL },
|
|
{ UNIT_PFARS, 0, "no auto-restart", "NOARS", &set_pfars, 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, 0, "DUMP", "DUMPDEV", &set_dump, &show_dump, NULL },
|
|
{ MTAB_XDV, 1, NULL, "DUMPCTL", &set_dump, 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, 1, "EXEC", "EXEC", &set_exec, &show_exec, NULL },
|
|
{ MTAB_XDV, 0, NULL, "NOEXEC", &set_exec, NULL, NULL },
|
|
|
|
{ MTAB_XDV | MTAB_NMO, 0, "SPEED", NULL, NULL, &show_speed, NULL },
|
|
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
/* Debugging trace list */
|
|
|
|
static DEBTAB cpu_deb [] = {
|
|
{ "INSTR", DEB_INSTR }, /* instructions */
|
|
{ "DATA", DEB_MDATA }, /* memory data accesses */
|
|
{ "FETCH", DEB_MFETCH }, /* memory instruction fetches */
|
|
{ "REG", DEB_REG }, /* register values */
|
|
{ "OPND", DEB_MOPND }, /* memory operand values */
|
|
{ "EXEC", DEB_EXEC }, /* instruction execution states */
|
|
{ "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 */
|
|
&mem_examine, /* examine routine */
|
|
&mem_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 system power is on and 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 and the RUN light on the front panel, 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
|
|
- waiting : the halt-mode cold load or dump 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.
|
|
|
|
5. The execution trace (DEB_EXEC) match test is performed in two parts to
|
|
display the register values both before and after the instruction
|
|
execution. Consequently, the enable test is done before the register
|
|
trace, and the disable test is done after.
|
|
|
|
6. The execution test (exec_test) is set FALSE even though execution tracing
|
|
is not specified. This is done solely to reassure the compiler that the
|
|
value is not clobbered by a longjmp call.
|
|
|
|
7. The set of debug trace flags is restored in the postlude (and therefore
|
|
also saved in the instruction prelude) to ensure that a simulation stop
|
|
that occurs while an execution trace is in progress does not exit with
|
|
the flags set improperly.
|
|
*/
|
|
|
|
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_bool exec_test;
|
|
t_stat status = SCPE_OK;
|
|
|
|
|
|
/* Instruction prelude */
|
|
|
|
debug_save = cpu_dev.dctrl; /* save the current set of debug flags for later restoration */
|
|
|
|
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 (cpu_power_state == power_off) /* if system power is off */
|
|
status = STOP_POWER; /* then execution is not possible until restoration */
|
|
|
|
else if (hp_device_conflict ()) /* otherwise if device assignment is inconsistent */
|
|
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 via STEP/CONT */
|
|
}
|
|
|
|
|
|
/* 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 */
|
|
cpu_setup_ics_irq (irq_Trap, trap); /* so set it up */
|
|
cpu_power_state = power_on; /* and return to the power-on state */
|
|
|
|
if (CPX2 & cpx2_INHPFARS) /* if auto-restart is inhibited */
|
|
status = STOP_ARSINH; /* then exit and wait for a manual restart */
|
|
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, 0); /* 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_power_state != power_failing) /* and power is not currently failing */
|
|
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_EXEC | DEB_REG)) { /* if execution or register tracing is enabled */
|
|
if (cpu_dev.dctrl & DEB_EXEC) /* then if tracing execution */
|
|
if (STA & STATUS_R) /* then if the right-hand stack op is pending */
|
|
exec_test = /* then the execution test succeeds if */
|
|
(CIR << STACKOP_A_SHIFT /* the right-hand stack op */
|
|
& STACKOP_A_MASK /* matches the test criteria */
|
|
& exec_mask) == exec_match; /* for the left-hand stack op value */
|
|
else /* otherwise */
|
|
exec_test = /* then the execution test succeeds if */
|
|
(NIR & exec_mask) == exec_match; /* the next instruction matches the test criteria */
|
|
else /* otherwise */
|
|
exec_test = FALSE; /* there is no execution test */
|
|
|
|
if (cpu_dev.dctrl & DEB_EXEC /* if execution tracing is enabled */
|
|
&& cpu_dev.dctrl != DEB_ALL /* and is currently inactive */
|
|
&& exec_test) { /* and the matching test succeeds */
|
|
debug_save = cpu_dev.dctrl; /* then save the current trace flag set */
|
|
cpu_dev.dctrl = DEB_ALL; /* and turn on full tracing */
|
|
}
|
|
|
|
if (cpu_dev.dctrl & 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 been altered */
|
|
hp_debug (&cpu_dev, DEB_REG, /* then output the base register values */
|
|
BOV_FORMAT " PB %06o, PL %06o, DL %06o, DB %06o, Q %06o, Z %06o\n",
|
|
DBANK, 0, STATUS_CS (STA),
|
|
PB, PL, DL, DB, Q, Z);
|
|
|
|
cpu_base_changed = FALSE; /* clear the base registers changed flag */
|
|
}
|
|
}
|
|
|
|
if (cpu_dev.dctrl & DEB_EXEC /* if execution tracing is enabled */
|
|
&& cpu_dev.dctrl == DEB_ALL /* and is currently active */
|
|
&& ! exec_test) { /* and the matching test fails */
|
|
cpu_dev.dctrl = debug_save; /* then restore the saved debug flag set */
|
|
hp_debug (&cpu_dev, DEB_EXEC, /* and add a separator to the trace log */
|
|
"*****************\n");
|
|
}
|
|
}
|
|
|
|
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 (DPRINTING (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_ARG) /* 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_dev.dctrl = debug_save; /* restore the flag set */
|
|
|
|
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 */
|
|
|
|
if (cpu_power_state == power_failing /* if power is failing */
|
|
&& status == STOP_HALT) /* and we executed a HALT instruction */
|
|
cpu_power_state = power_off; /* then power will be off when we return */
|
|
|
|
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 */
|
|
}
|
|
|
|
|
|
/* Execute the LOAD and DUMP commands.
|
|
|
|
This command processing routine implements the cold load and cold dump
|
|
commands. The syntax is:
|
|
|
|
LOAD { <control/devno> }
|
|
DUMP { <control/devno> }
|
|
|
|
The <control/devno> is a number that is deposited into the SWCH register
|
|
before invoking the CPU cold load or cold dump facility. The CPU radix is
|
|
used to interpret the number; it defaults to octal. If the number is
|
|
omitted, the SWCH register value is not altered before loading or dumping.
|
|
|
|
On entry, the "arg" parameter is "Cold_Load" for a LOAD command and
|
|
"Cold_Dump" for a DUMP command, and "buf" points at the remainder of the
|
|
command line. If characters exist on the command line, they are parsed,
|
|
converted to a numeric value, and stored in the SWCH register. Then the
|
|
CPU's cold load/dump routine is called to set up the CPU state. Finally, the
|
|
CPU is started to begin the requested action.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The RUN command uses the RU_CONT argument instead of RU_RUN so that the
|
|
run_cmd SCP routine will not reset all devices before entering the
|
|
instruction executor. The halt mode interrupt handlers for cold load and
|
|
cold dump reset the simulator as appropriate for their commands (i.e.,
|
|
the CPU and all I/O devices, or just the I/O devices, respectively).
|
|
*/
|
|
|
|
t_stat cpu_cold_cmd (int32 arg, CONST char *buf)
|
|
{
|
|
const char *cptr;
|
|
char gbuf [CBUFSIZE];
|
|
t_stat status;
|
|
HP_WORD value;
|
|
|
|
if (*buf != '\0') { /* if more characters exist on the command line */
|
|
cptr = get_glyph (buf, gbuf, 0); /* then get the next glyph */
|
|
|
|
if (*cptr != '\0') /* if that does not exhaust the input */
|
|
return SCPE_2MARG; /* then report that there are too many arguments */
|
|
|
|
value = (HP_WORD) get_uint (gbuf, cpu_dev.dradix, /* get the parameter value */
|
|
D16_UMAX, &status);
|
|
|
|
if (status == SCPE_OK) /* if a valid number was present */
|
|
SWCH = value; /* then set it into the switch register */
|
|
else /* otherwise */
|
|
return status; /* return the error status */
|
|
}
|
|
|
|
else if (arg == Cold_Dump) /* otherwise if no dump value was given */
|
|
SWCH = dump_control; /* then use the system control panel presets */
|
|
|
|
cpu_front_panel (SWCH, (PANEL_TYPE) arg); /* set up the cold load or dump microcode */
|
|
|
|
return run_cmd (RU_CONT, buf); /* execute the halt-mode routine */
|
|
}
|
|
|
|
|
|
/* Execute the POWER commands.
|
|
|
|
This command processing routine is called to initiate a power failure or
|
|
power restoration. The "cptr" parameter points to the power option keyword;
|
|
the "arg" parameter is not used.
|
|
|
|
The routine processes commands of the form:
|
|
|
|
POWER { FAIL | OFF | DOWN }
|
|
POWER { RESTORE | ON | UP }
|
|
|
|
In simulation, the "cpu_power_state" global variable indicates the current
|
|
state of system power. The simulator starts in the power_on state. The
|
|
POWER FAIL command moves from the power_on to the power_failing state if the
|
|
CPU is running, or to the power_off state if it is not. Execution of a HALT
|
|
in the power_failing state moves to the power_off state. The POWER RESTORE
|
|
command moves from the power_off to the power_returning state if the CPU is
|
|
running, or to the power_on state if it is not. Execution of the power-on
|
|
trap moves from the power_returning to the power_on state.
|
|
|
|
The POWER FAIL and POWER RESTORE commands are only valid in the power_on and
|
|
power_off states, respectively; otherwise, they print "Command not allowed."
|
|
|
|
The four enumeration values model the states of the PON (power on) and PFW
|
|
(power-fail warning) hardware signals, as follows:
|
|
|
|
PON PFW State Simulator Action
|
|
--- --- --------------- ----------------------------
|
|
1 0 power on executing normally
|
|
1 1 power failing executing with cpx1_PFINTR
|
|
0 1 power off will not execute
|
|
0 0 power returning executing with trap_Power_On
|
|
|
|
In microcode, the power-fail routine writes the current value of the CPX2
|
|
register to the word following the last word of the ICS. This value is used
|
|
by the power-on routine to decide if the CPU was running (cpx2_RUN bit is
|
|
set) or halted at the time of the power failure. A power failure is
|
|
indicated by setting the cpx1_PFINTR bit in the CPX1 register; this causes an
|
|
interrupt to the power-failure routine in the operating system, which
|
|
performs an orderly shutdown followed by a programmed HALT to wait for power
|
|
to die. When power is restored, the power-on trap (trap_Power_On) is set up,
|
|
and then, if the PF/ARS switch is in the "enable" position, the trap is
|
|
taken, which restarts the operating system and any I/O that was in progress
|
|
when power failed. If the switch is in the "disable" position, the CPU
|
|
remains halted, and the trap is taken when the RUN button is pressed.
|
|
|
|
The POWER commands are entered at the SCP prompt. If the machine was running
|
|
at the time of power failure, execution is resumed automatically to execute
|
|
the power-fail or power-restore OS routines. If the machine was halted when
|
|
the POWER commands were entered, the machine remains halted -- just the power
|
|
state changes.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. In hardware, when the power fail interrupt is serviced, the microcode
|
|
sets the PWR INH flip-flop, which locks out the RUN switch and inhibits
|
|
all other halt-mode (CPX2) and run-mode (CPX1) interrupts until the CPU
|
|
is reset. This ensures that the software power-fail interrupt handler
|
|
executes unimpeded. In simulation, a POWER FAIL command with the CPU
|
|
running sets the power-fail interrupt bit in the CPX1 register and
|
|
resumes execution in the power_on state. When the interrupt is detected
|
|
by the "cpu_run_mode_interrupt" routine, the state is changed to
|
|
power_failing. In this state, run-mode interrupts are not recognized.
|
|
Halt-mode interrupts need no special handling, as the power state is
|
|
changed to power_off when the CPU halts. Therefore, the CPU is never in
|
|
a "halted-and-waiting-for-power-to-fade-away" state.
|
|
|
|
2. The RUN command uses the RU_CONT argument instead of RU_RUN so that the
|
|
run_cmd SCP routine will not reset all devices before entering the
|
|
instruction executor. The halt-mode interrupt handlers for cold load and
|
|
cold dump reset the simulator as appropriate for their commands (i.e.,
|
|
the CPU and all I/O devices, or just the I/O devices, respectively).
|
|
|
|
3. In order to set up the power-on trap, this routine presses the RUN button
|
|
and continues the simulation. After the trap is set up in the
|
|
"sim_instr" routine, the halt-mode interrupt handler checks the PF/ARS
|
|
switch and stops simulation if auto-restart is disabled.
|
|
*/
|
|
|
|
t_stat cpu_power_cmd (int32 arg, CONST char *cptr)
|
|
{
|
|
static CTAB options [] = {
|
|
{ "FAIL", NULL, power_failing },
|
|
{ "RESTORE", NULL, power_returning },
|
|
{ "OFF", NULL, power_failing },
|
|
{ "ON", NULL, power_returning },
|
|
{ "DOWN", NULL, power_failing },
|
|
{ "UP", NULL, power_returning },
|
|
{ NULL }
|
|
};
|
|
|
|
char gbuf [CBUFSIZE];
|
|
CTAB *ctptr;
|
|
HP_WORD zi, failure_cpx2;
|
|
t_stat status;
|
|
|
|
if (cptr == NULL || *cptr == '\0') /* if there is no option word */
|
|
return SCPE_2FARG; /* then report a missing argument */
|
|
|
|
cptr = get_glyph (cptr, gbuf, 0); /* parse (and upshift) the option specified */
|
|
ctptr = find_ctab (options, gbuf); /* and look it up in the option table */
|
|
|
|
if (ctptr == NULL) /* if the option is not valid */
|
|
status = SCPE_ARG; /* then report a bad argument */
|
|
|
|
else if (*cptr != '\0') /* otherwise if something follows the option */
|
|
return SCPE_2MARG; /* then report too many arguments */
|
|
|
|
else if (ctptr->arg == power_failing) /* otherwise if a power-fail option was given */
|
|
if (cpu_power_state != power_on) /* but the CPU power is not on */
|
|
status = SCPE_NOFNC; /* then the command is not allowed */
|
|
|
|
else { /* otherwise the failure is valid */
|
|
iop_assert_PFWARN (); /* so send a power-fail warning to all devices */
|
|
|
|
cpu_read_memory (absolute, ICS_Z, &zi); /* get the ICS stack limit */
|
|
cpu_write_memory (absolute, zi + 1, CPX2); /* and save the CPX2 value in the following word */
|
|
|
|
if (CPX2 & cpx2_RUN) { /* if the CPU is currently running */
|
|
CPX1 |= cpx1_PFINTR; /* then set the power-fail interrupt */
|
|
CPX2 |= cpx2_RUNSWCH; /* and assume a RUN command */
|
|
status = run_cmd (RU_CONT, cptr); /* and continue execution */
|
|
}
|
|
|
|
else { /* otherwise the CPU is currently halted */
|
|
cpu_power_state = power_off; /* so remain halted in the "power is off" state */
|
|
status = SCPE_OK; /* and return command success */
|
|
}
|
|
}
|
|
|
|
else if (ctptr->arg == power_returning) /* otherwise if a power-restoration option was given */
|
|
if (cpu_power_state != power_off) /* but the CPU power is not off */
|
|
status = SCPE_NOFNC; /* then the command is not allowed */
|
|
|
|
else { /* otherwise the restoration is valid */
|
|
reset_all_p (0); /* so reset all devices to their power on states */
|
|
|
|
cpu_read_memory (absolute, ICS_Z, &zi); /* get the ICS stack limit */
|
|
cpu_read_memory (absolute, zi + 1, &failure_cpx2); /* and get the value of CPX2 at the time of failure */
|
|
cpu_write_memory (absolute, zi + 1, CPX2); /* and replace it with the current CPX2 value */
|
|
|
|
if (failure_cpx2 & cpx2_RUN) { /* if the CPU was running at the time of power failure */
|
|
cpu_power_state = power_returning; /* then move to the "power is returning" state */
|
|
CPX2 |= cpx2_RUNSWCH; /* and assume a RUN command */
|
|
status = run_cmd (RU_CONT, cptr); /* and continue execution */
|
|
}
|
|
|
|
else { /* otherwise the CPU was halted when power failed */
|
|
cpu_power_state = power_on; /* so remain halted in the "power is on" state */
|
|
status = SCPE_OK; /* and return command success */
|
|
}
|
|
}
|
|
|
|
else /* otherwise a valid option has no handler */
|
|
status = SCPE_IERR; /* so report an internal error */
|
|
|
|
return status; /* return the operation status */
|
|
}
|
|
|
|
|
|
|
|
/* CPU global utility routines */
|
|
|
|
|
|
/* 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 is 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 = UPPER_BYTE (MOD); /* then the parameter is the module number */
|
|
MOD = 0; /* clear the register to prevent a second interrupt */
|
|
}
|
|
|
|
else if (class == irq_Power_Fail) { /* otherwise if a power fail interrupt occurred */
|
|
parameter = TO_LABEL (LABEL_IRQ, class); /* then the parameter is the label */
|
|
cpu_power_state = power_failing; /* and system power is now failing */
|
|
}
|
|
|
|
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 = cpu_unit [0].wait /* then the elapsed time is the original wait time */
|
|
- sim_activate_time (&cpu_unit [0]); /* less the time remaining before the next service */
|
|
|
|
ticks = /* the adjustment is */
|
|
(elapsed * PCLK_MULTIPLIER) / cpu_unit [0].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.
|
|
|
|
4. This routine calculates the effective address for the memory address
|
|
instructions. These instructions always bounds-check their accesses, and
|
|
data and stack accesses are mapped to the TOS registers if the effective
|
|
address lies between SM and SM + SR within the stack bank.
|
|
*/
|
|
|
|
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_mapped_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_mapped_checked; /* reclassify as a data reference */
|
|
}
|
|
/* otherwise, this is a program reference */
|
|
} /* which is self-referential */
|
|
|
|
if ((CIR & LSDX_MASK) == LDD_X /* if the mode */
|
|
|| (CIR & LSDX_MASK) == STD_X) /* is double-word indexed */
|
|
displacement = displacement + X * 2 & DV_MASK; /* then add the doubled index to the displacement */
|
|
|
|
else if (mode_disp & X_FLAG) /* otherwise if the mode is indexed */
|
|
displacement = displacement + X & DV_MASK; /* then add the index to the displacement */
|
|
|
|
if (selector == NULL) /* if a word address is requested */
|
|
base = base + displacement; /* then add in the word displacement */
|
|
|
|
else if ((mode_disp & (X_FLAG | I_FLAG_BIT_5)) == 0) /* otherwise if a direct byte address is requested */
|
|
*selector = upper; /* then it references the upper byte */
|
|
|
|
else { /* otherwise an indexed or indirect byte address is requested */
|
|
if (displacement & 1) /* so if the byte displacement is odd */
|
|
*selector = lower; /* then the lower byte was requested */
|
|
else /* otherwise it is even */
|
|
*selector = upper; /* and the upper byte was requested */
|
|
|
|
base = base + (displacement >> 1) & LA_MASK; /* convert the displacement from byte to word and add */
|
|
|
|
if (DBANK == SBANK && DL <= DB && DB <= Z /* if not in split-stack mode */
|
|
&& (base < DL || base > SM + SR)) /* and the word address is out of range */
|
|
base = base ^ D16_SIGN; /* then add 32K to swap the offset polarity */
|
|
}
|
|
|
|
*offset = base & LA_MASK; /* set the */
|
|
*classification = class; /* return values */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Convert a data- or program-relative byte address to a word effective address.
|
|
|
|
The supplied byte offset from DB or PB is converted to a memory address,
|
|
bounds-checked if requested, and returned. If the supplied block length is
|
|
not zero, the converted address is assumed to be the starting address of a
|
|
block, and an ending address, based on the block length, is calculated and
|
|
bounds-checked if requested. If either address lies outside of the segment
|
|
associated with the access class, a Bounds Violation trap occurs, unless a
|
|
privileged data access is requested.
|
|
|
|
The "class" parameter indicates whether the offset is from DB or PB and
|
|
must be "data", "data_checked", "program", or "program_checked". No other
|
|
access classes are supported. In particular, "data_mapped" and
|
|
"data_mapped_checked" are not allowed, as callers of this routine never map
|
|
accesses to the TOS registers.
|
|
|
|
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. This routine implements the DBBC microcode subroutine.
|
|
*/
|
|
|
|
uint32 cpu_byte_ea (ACCESS_CLASS class, uint32 byte_offset, uint32 block_length)
|
|
{
|
|
uint32 starting_word, ending_word, increment;
|
|
|
|
if (block_length & D16_SIGN) /* if the block length is negative */
|
|
increment = 0177777; /* then the memory increment is negative also */
|
|
else /* otherwise */
|
|
increment = 1; /* the increment is positive */
|
|
|
|
if (class == program || class == program_checked) { /* if this is a program access */
|
|
starting_word = PB + (byte_offset >> 1) & LA_MASK; /* then determine the starting word address */
|
|
|
|
if (class == program_checked /* if checking is requested */
|
|
&& starting_word < PB || starting_word > PL) /* and the starting address is out of range */
|
|
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
|
|
|
|
if (block_length != 0) { /* if a block length was supplied */
|
|
ending_word = /* then determine the ending address */
|
|
starting_word + ((block_length - increment + (byte_offset & 1)) >> 1) & LA_MASK;
|
|
|
|
if (class == program_checked /* if checking is requested */
|
|
&& ending_word < PB || ending_word > PL) /* and the ending address is out of range */
|
|
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
|
|
}
|
|
}
|
|
|
|
else { /* otherwise this is a data address */
|
|
starting_word = DB + (byte_offset >> 1) & LA_MASK; /* so determine the starting word address */
|
|
|
|
if (DBANK == SBANK && DL <= DB && DB <= Z /* if not in split-stack mode */
|
|
&& (starting_word < DL || starting_word > SM)) { /* and the word address is out of range */
|
|
starting_word = starting_word ^ D16_SIGN; /* then add 32K and try again */
|
|
|
|
if (class == data_checked && NPRV /* if checking is requested and non-privileged */
|
|
&& (starting_word < DL || starting_word > SM)) /* and still out of range */
|
|
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
|
|
}
|
|
|
|
if (block_length != 0) { /* if a block length was supplied */
|
|
ending_word = /* then determine the ending word address */
|
|
starting_word + ((block_length - increment + (byte_offset & 1)) >> 1) & LA_MASK;
|
|
|
|
if (class == data_checked && NPRV /* if checking is requested and non-privileged */
|
|
&& (ending_word < DL || ending_word > SM)) /* and the address is out of range */
|
|
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
|
|
}
|
|
}
|
|
|
|
|
|
return starting_word; /* return the starting word address */
|
|
}
|
|
|
|
|
|
/* 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.
|
|
*/
|
|
|
|
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 */
|
|
cpu_setup_ics_irq (class, 0); /* then set it up on the ICS */
|
|
|
|
label = TO_LABEL (LABEL_IRQ, class); /* form the label for the specified classification */
|
|
|
|
STA = STATUS_M; /* clear status and enter privileged mode */
|
|
}
|
|
|
|
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, 0); /* 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 /* if this is not */
|
|
|| trap != trap_Cold_Load && trap != trap_Power_On) { /* a cold load or power on 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 for a cold load or power on */
|
|
|
|
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, and "offset"
|
|
contains an offset to be added to the starting address. 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.
|
|
|
|
The "offset" parameter is used only by the XBR instruction executor. The
|
|
PCAL executor and the interrupt handlers pass an offset of zero to begin
|
|
execution at the first instruction of the designated procedure.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. This routine implements the microcode PCL3 and PCL5 subroutines.
|
|
*/
|
|
|
|
void cpu_call_procedure (HP_WORD label, HP_WORD offset)
|
|
{
|
|
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 */
|
|
|
|
cpu_base_changed = TRUE; /* the program base registers have changed for tracing */
|
|
}
|
|
|
|
new_p = PB + (label + offset & 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 */
|
|
|
|
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 (STATUS_CS (new_status) != STATUS_CS (STA)) { /* 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 several
|
|
registers are cleared.
|
|
|
|
|
|
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 (sim_PC == NULL) { /* if this is the first call after simulator start */
|
|
if (mem_initialize (PA_MAX)) {
|
|
set_model (&cpu_unit [0], 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! */
|
|
}
|
|
|
|
else /* otherwise memory initialization failed */
|
|
return SCPE_MEM; /* so report the error and abort the simulation */
|
|
}
|
|
|
|
if (sim_switches & SWMASK ('P')) { /* if this is a power-on reset */
|
|
sim_rtcn_init (cpu_unit [0].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 [0], cpu_unit [0].wait); /* and schedule the process clock */
|
|
|
|
PCLK = 0; /* 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 */
|
|
|
|
if (cpu_unit [0].flags & UNIT_PFARS) /* if the PF/ARS switch position is ENBL */
|
|
CPX2 &= ~cpx2_INHPFARS; /* then clear the auto-restart inhibit flag */
|
|
else /* otherwise the position is DSBL */
|
|
CPX2 |= cpx2_INHPFARS; /* so set the auto-restart inhibit flag */
|
|
|
|
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 */
|
|
}
|
|
|
|
|
|
/* Set the CPU simulation stop conditions.
|
|
|
|
This validation routine is called to configure the set of CPU stop
|
|
conditions. The "option" parameter is 0 to clear the stops and 1 to set
|
|
them, and "cptr" points to the first character of the name of the stop to be
|
|
cleared or set. The unit and description pointers are not used.
|
|
|
|
The routine processes commands of the form:
|
|
|
|
SET CPU STOP
|
|
SET CPU STOP=<stopname>[;<stopname>...]
|
|
SET CPU NOSTOP
|
|
SET CPU NOSTOP=<stopname>[;<stopname>...]
|
|
|
|
The valid <stopname>s are contained in the debug table "cpu_stop". If names
|
|
are not specified, all stop conditions are enabled or disabled.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The CPU simulator maintains a private and a public set of simulator
|
|
stops. This routine sets the private set. The private set is copied to
|
|
the public set as part of the instruction execution prelude, unless the
|
|
"-B" ("bypass") command-line switch is used with the run command. This
|
|
allows the stops to be bypassed conveniently for the first instruction
|
|
execution only.
|
|
*/
|
|
|
|
static t_stat set_stops (UNIT *uptr, int32 option, CONST char *cptr, void *desc)
|
|
{
|
|
char gbuf [CBUFSIZE];
|
|
uint32 stop;
|
|
|
|
if (cptr == NULL) { /* if there are no arguments */
|
|
sim_stops = 0; /* then clear all of the stop flags */
|
|
|
|
if (option == 1) /* if we're setting the stops */
|
|
for (stop = 0; cpu_stop [stop].name != NULL; stop++) /* then loop through the flags */
|
|
sim_stops |= cpu_stop [stop].mask; /* and add each one to the set */
|
|
}
|
|
|
|
else if (*cptr == '\0') /* otherwise if the argument is empty */
|
|
return SCPE_MISVAL; /* then report the missing value */
|
|
|
|
else /* otherwise at least one argument is present */
|
|
while (*cptr) { /* loop through the arguments */
|
|
cptr = get_glyph (cptr, gbuf, ';'); /* get the next argument */
|
|
|
|
for (stop = 0; cpu_stop [stop].name != NULL; stop++) /* loop through the flags */
|
|
if (strcmp (cpu_stop [stop].name, gbuf) == 0) { /* and if the argument matches */
|
|
if (option == 1) /* then if it's a STOP argument */
|
|
sim_stops |= cpu_stop [stop].mask; /* then add the stop flag */
|
|
else /* otherwise it's a NOSTOP argument */
|
|
sim_stops &= ~cpu_stop [stop].mask; /* so remove the flag */
|
|
|
|
break; /* this argument has been processed */
|
|
}
|
|
|
|
if (cpu_stop [stop].name == NULL) /* if the argument was not found */
|
|
return SCPE_ARG; /* then report it */
|
|
}
|
|
|
|
return SCPE_OK; /* the stops were successfully processed */
|
|
}
|
|
|
|
|
|
/* Change the instruction execution trace criteria.
|
|
|
|
This validation routine is called to configure the criteria that select
|
|
instruction execution tracing. The "option" parameter is 0 to clear and 1 to
|
|
set the criteria, and "cptr" points to the first character of the match value
|
|
to be set. The unit and description pointers are not used.
|
|
|
|
The routine processes commands of the form:
|
|
|
|
SET CPU EXEC=<match>[;<mask>]
|
|
SET CPU NOEXEC
|
|
|
|
If the <mask> value is not supplied, a mask of 177777 octal is used. The
|
|
values are entered in the current CPU data radix, which defaults to octal,
|
|
unless an override switch is present on the command line.
|
|
*/
|
|
|
|
static t_stat set_exec (UNIT *uptr, int32 option, CONST char *cptr, void *desc)
|
|
{
|
|
char gbuf [CBUFSIZE];
|
|
uint32 match, mask, radix;
|
|
t_stat status;
|
|
|
|
if (option == 0) /* if this is a NOEXEC request */
|
|
if (cptr == NULL) { /* then if there are no arguments */
|
|
exec_match = D16_UMAX; /* then set the match and mask values */
|
|
exec_mask = 0; /* to prevent matching */
|
|
return SCPE_OK; /* and return success */
|
|
}
|
|
|
|
else /* otherwise there are extraneous characters */
|
|
return SCPE_2MARG; /* so report that there are too many arguments */
|
|
|
|
else if (cptr == NULL || *cptr == '\0') /* otherwise if the EXEC request supplies no arguments */
|
|
return SCPE_MISVAL; /* then report a missing value */
|
|
|
|
else { /* otherwise at least one argument is present */
|
|
cptr = get_glyph (cptr, gbuf, ';'); /* so get the match argument */
|
|
|
|
if (sim_switches & SWMASK ('O')) /* if an octal override is present */
|
|
radix = 8; /* then parse the value in base 8 */
|
|
else if (sim_switches & SWMASK ('D')) /* otherwise if a decimal override is present */
|
|
radix = 10; /* then parse the value in base 10 */
|
|
else if (sim_switches & SWMASK ('H')) /* otherwise if a hex override is present */
|
|
radix = 16; /* then parse the value in base 16 */
|
|
else /* otherwise */
|
|
radix = cpu_dev.dradix; /* use the current CPU data radix */
|
|
|
|
match = (uint32) get_uint (gbuf, radix, D16_UMAX, &status); /* parse the match value */
|
|
|
|
if (status != SCPE_OK) /* if a parsing error occurred */
|
|
return status; /* then return the error status */
|
|
|
|
else if (*cptr == '\0') { /* otherwise if no more characters are present */
|
|
exec_match = match; /* then set the match value */
|
|
exec_mask = D16_MASK; /* and default the mask value */
|
|
return SCPE_OK; /* and return success */
|
|
}
|
|
|
|
else { /* otherwise another argument is present */
|
|
cptr = get_glyph (cptr, gbuf, ';'); /* so get the mask argument */
|
|
|
|
mask = (uint32) get_uint (gbuf, radix, D16_UMAX, &status); /* parse the mask value */
|
|
|
|
if (status != SCPE_OK) /* if a parsing error occurred */
|
|
return status; /* then return the error status */
|
|
|
|
else if (*cptr == '\0') /* if no more characters are present */
|
|
if (mask == 0) /* then if the mask value is zero */
|
|
return SCPE_ARG; /* then the match will never succeed */
|
|
|
|
else { /* otherwise */
|
|
exec_match = match; /* set the match value */
|
|
exec_mask = mask; /* and the mask value */
|
|
return SCPE_OK; /* and return success */
|
|
}
|
|
|
|
else /* otherwise extraneous characters are present */
|
|
return SCPE_2MARG; /* so report that there are too many arguments */
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Set the CPU cold dump configuration jumpers.
|
|
|
|
This validation routine is called to configure the set of jumpers on the
|
|
system control panel that preset the device number and control value for the
|
|
cold dump process. The "option" parameter is 0 to set the device number
|
|
and 1 to set the control value. The "cptr" parameter points to the first
|
|
character of the value to be set. The unit and description pointers are not
|
|
used.
|
|
|
|
The routine processes commands of the form:
|
|
|
|
SET CPU DUMPDEV=<devno>
|
|
SET CPU DUMPCTL=<cntlval>
|
|
|
|
The device number is a decimal value between 3 and 127, and the control value
|
|
is an octal number between 0 and 377. Values outside of these ranges are
|
|
rejected.
|
|
*/
|
|
|
|
static t_stat set_dump (UNIT *uptr, int32 option, CONST char *cptr, void *desc)
|
|
{
|
|
t_value value;
|
|
t_stat status = SCPE_OK;
|
|
|
|
if (cptr == NULL || *cptr == '\0') /* if the expected value is missing */
|
|
status = SCPE_MISVAL; /* then report the error */
|
|
|
|
else if (option == 0) { /* otherwise if a device number is present */
|
|
value = get_uint (cptr, DEVNO_BASE, /* then parse the supplied value */
|
|
DEVNO_MAX, &status);
|
|
|
|
if (status == SCPE_OK) /* if it is valid */
|
|
if (value >= 3) /* and in the proper range */
|
|
dump_control = REPLACE_LOWER (dump_control, /* then set the new device number */
|
|
(uint32) value); /* into the dump control word */
|
|
else /* otherwise the device number */
|
|
status = SCPE_ARG; /* is invalid */
|
|
}
|
|
|
|
else { /* otherwise a control byte is present */
|
|
value = get_uint (cptr, CNTL_BASE, /* so parse the supplied value */
|
|
CNTL_MAX, &status);
|
|
|
|
if (status == SCPE_OK) /* if it is valid */
|
|
dump_control = REPLACE_UPPER (dump_control, /* then set the new control value */
|
|
(uint32) value); /* into the dump control word */
|
|
}
|
|
|
|
return status; /* return the operation status */
|
|
}
|
|
|
|
|
|
/* Change the CPU memory size.
|
|
|
|
This validation routine is called to configure the CPU memory size. The
|
|
"new_size" parameter is set to the size desired and will be one of the
|
|
discrete sizes supported by the machine. The "uptr" parameter points to the
|
|
CPU unit and is used to obtain the CPU model. The other parameters are not
|
|
used.
|
|
|
|
The routine processes commands of the form:
|
|
|
|
SET [-F] CPU <memsize>
|
|
|
|
If the new memory size is larger than the supported size for the CPU model
|
|
currently selected, the routine returns an error. If the new size is smaller
|
|
than the previous size, and if the area that would be lost contains non-zero
|
|
data, the user is prompted to confirm that memory should be truncated. If
|
|
the user denies the request, the change is rejected. Otherwise, the new size
|
|
is set. The user may omit the confirmation request and force truncation by
|
|
specifying the "-F" switch on the command line.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The memory access routines return a zero value for locations beyond the
|
|
currently defined memory size. Therefore, the unused area need not be
|
|
explicitly zeroed.
|
|
*/
|
|
|
|
static t_stat set_size (UNIT *uptr, int32 new_size, CONST char *cptr, void *desc)
|
|
{
|
|
static CONST char confirm [] = "Really truncate memory [N]?";
|
|
|
|
const uint32 model = CPU_MODEL (uptr->flags); /* the current CPU model index */
|
|
|
|
if ((uint32) new_size > cpu_features [model].maxmem) /* if the new memory size is not supported on current model */
|
|
return SCPE_NOFNC; /* then report the error */
|
|
|
|
if (!(sim_switches & SWMASK ('F')) /* if truncation is not explicitly forced */
|
|
&& ! mem_is_empty (new_size) /* and the truncated part is not empty */
|
|
&& get_yn (confirm, FALSE) == FALSE) /* and the user denies confirmation */
|
|
return SCPE_INCOMP; /* then abort the command */
|
|
|
|
MEMSIZE = new_size; /* set the new memory size */
|
|
|
|
return SCPE_OK; /* confirm that the change is OK */
|
|
}
|
|
|
|
|
|
/* Change the CPU model.
|
|
|
|
This validation routine is called to configure the CPU model. The
|
|
"new_model" parameter is set to the model desired and will be one of the unit
|
|
model flags. The other parameters are not used.
|
|
|
|
The routine processes commands of the form:
|
|
|
|
SET [-F] CPU <model>
|
|
|
|
Setting the model establishes a set of typical hardware features. It also
|
|
verifies that the current memory size is supported by the new model. If it
|
|
is not, the size is reduced to the maximum supported memory configuration.
|
|
If the area that would be lost contains non-zero data, the user is prompted
|
|
to confirm that memory should be truncated. If the user denies the request,
|
|
the change is rejected. Otherwise, the new size is set. The user may omit
|
|
the confirmation request and force truncation by specifying the "-F" switch
|
|
on the command line.
|
|
|
|
This routine is also called once from the CPU reset routine to establish the
|
|
initial CPU model. The current memory size will be 0 when this call is made.
|
|
*/
|
|
|
|
static t_stat set_model (UNIT *uptr, int32 new_model, CONST char *cptr, void *desc)
|
|
{
|
|
const uint32 new_index = CPU_MODEL (new_model); /* the new index into the CPU features table */
|
|
uint32 new_memsize;
|
|
t_stat status;
|
|
|
|
if (MEMSIZE == 0 /* if this is the initial establishing call */
|
|
|| MEMSIZE > cpu_features [new_index].maxmem) /* or if the current memory size is unsupported */
|
|
new_memsize = cpu_features [new_index].maxmem; /* then set the new size to the maximum supported size */
|
|
else /* otherwise the current size is valid for the new model */
|
|
new_memsize = (uint32) MEMSIZE; /* so leave it unchanged */
|
|
|
|
status = set_size (uptr, new_memsize, NULL, NULL); /* set the new memory size */
|
|
|
|
if (status == SCPE_OK) /* if the change succeeded */
|
|
uptr->flags = uptr->flags & ~UNIT_OPTS /* then set the typical features */
|
|
| cpu_features [new_index].typ; /* for the new model */
|
|
|
|
return status; /* return the validation result */
|
|
}
|
|
|
|
|
|
/* Change a CPU option.
|
|
|
|
This validation routine is called to configure the option set for the current
|
|
CPU model. The "new_option" parameter is set to the option desired and will
|
|
be one of the unit option flags. The "uptr" parameter points to the CPU unit
|
|
and is used to obtain the CPU model. The other parameters are not used.
|
|
|
|
The routine processes commands of the form:
|
|
|
|
SET CPU <option>[,<option>...]
|
|
|
|
The option must be valid for the current CPU model, or the command is
|
|
rejected.
|
|
*/
|
|
|
|
static t_stat set_option (UNIT *uptr, int32 new_option, CONST char *cptr, void *desc)
|
|
{
|
|
const uint32 model = CPU_MODEL (uptr->flags); /* the current CPU model index */
|
|
|
|
if ((cpu_features [model].opt & new_option) != 0) /* if the option is supported on the current model */
|
|
return SCPE_OK; /* then confirm the change */
|
|
else /* otherwise */
|
|
return SCPE_NOFNC; /* reject the change */
|
|
}
|
|
|
|
|
|
/* Change the power-fail auto-restart switch setting.
|
|
|
|
This validation routine is called to configure the PF/ARS switch that is
|
|
located behind the system control panel. If set to the ENBL (enable)
|
|
position, the CPU will perform an auto-restart when power is restored after a
|
|
failure. In the DSBL (disable) position, the CPU will remain halted after
|
|
power restoration; execution may be continued by pressing the RUN button.
|
|
|
|
In simulation, a SET CPU ARS command enables auto-restart, and SET CPU NOARS
|
|
disables auto-restart. The "setting" parameter is set to the UNIT_ARS flag
|
|
in the former cast and to zero in the latter case. The other parameters are
|
|
not used. The routine reflects the ARS setting in "inhibit auto-restart" bit
|
|
of the CPX2 register.
|
|
*/
|
|
|
|
static t_stat set_pfars (UNIT *uptr, int32 setting, CONST char *cptr, void *desc)
|
|
{
|
|
if (setting == UNIT_PFARS) /* if the option is ARS */
|
|
CPX2 &= ~cpx2_INHPFARS; /* then clear the auto-restart inhibit flag */
|
|
else /* otherwise the option is NOARS */
|
|
CPX2 |= cpx2_INHPFARS; /* so set the auto-restart inhibit flag */
|
|
|
|
return SCPE_OK; /* confirm the change */
|
|
}
|
|
|
|
|
|
/* Show the CPU simulation stop conditions.
|
|
|
|
This display routine is called to show the set of CPU stop conditions. The
|
|
"st" parameter is the open output stream. The other parameters are not used.
|
|
|
|
If at least one stop condition is enabled, the routine searches through the
|
|
stop table for flag bits that are set in the stop set. For each one it
|
|
finds, the routine prints the corresponding stop name.
|
|
|
|
This routine services an extended modifier entry, so it must add the trailing
|
|
newline to the output before returning.
|
|
*/
|
|
|
|
static t_stat show_stops (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
|
|
{
|
|
uint32 stop;
|
|
t_bool need_spacer = FALSE;
|
|
|
|
if (sim_stops == 0) /* if no simulation stops are set */
|
|
fputs ("Stops disabled", st); /* then report that all are disabled */
|
|
|
|
else { /* otherwise at least one stop is valid */
|
|
fputs ("Stop=", st); /* so prepare to report the list of conditions */
|
|
|
|
for (stop = 0; cpu_stop [stop].name != NULL; stop++) /* loop through the set of stops in the table */
|
|
if (cpu_stop [stop].mask & sim_stops) { /* if the current stop is enabled */
|
|
if (need_spacer) /* then if a spacer is needed */
|
|
fputc (';', st); /* then add it first */
|
|
|
|
fputs (cpu_stop [stop].name, st); /* report the stop name */
|
|
|
|
need_spacer = TRUE; /* a spacer will be needed next time */
|
|
}
|
|
}
|
|
|
|
fputc ('\n', st); /* add the trailing newline */
|
|
|
|
return SCPE_OK; /* report the success of the display */
|
|
}
|
|
|
|
|
|
/* Show the instruction execution trace criteria.
|
|
|
|
This display routine is called to show the criteria that select instruction
|
|
execution tracing. The "st" parameter is the open output stream. The other
|
|
parameters are not used.
|
|
|
|
This routine services an extended modifier entry, so it must add the trailing
|
|
newline to the output before returning.
|
|
*/
|
|
|
|
static t_stat show_exec (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
|
|
{
|
|
uint32 radix;
|
|
|
|
if (exec_mask == 0) /* if the instruction is entirely masked */
|
|
fputs ("Execution trace disabled\n", st); /* then report that matching is disabled */
|
|
|
|
else { /* otherwise */
|
|
if (sim_switches & SWMASK ('O')) /* if an octal override is present */
|
|
radix = 8; /* then print the value in base 8 */
|
|
else if (sim_switches & SWMASK ('D')) /* otherwise if a decimal override is present */
|
|
radix = 10; /* then print the value in base 10 */
|
|
else if (sim_switches & SWMASK ('H')) /* otherwise if a hex override is present */
|
|
radix = 16; /* then print the value in base 16 */
|
|
else /* otherwise */
|
|
radix = cpu_dev.dradix; /* use the current CPU data radix */
|
|
|
|
fputs ("Execution trace match = ", st); /* print the label */
|
|
fprint_val (st, exec_match, radix, cpu_dev.dwidth, PV_RZRO); /* and the match value */
|
|
|
|
fputs (", mask = ", st); /* print a separator */
|
|
fprint_val (st, exec_mask, radix, cpu_dev.dwidth, PV_RZRO); /* and the mask value */
|
|
|
|
fputc ('\n', st); /* tie off the line */
|
|
}
|
|
|
|
return SCPE_OK; /* report the success of the display */
|
|
}
|
|
|
|
|
|
/* Show the CPU cold dump configuration jumpers.
|
|
|
|
This display routine is called to show the device number and control byte
|
|
that are preset on the rear of the system control panel for the cold dump
|
|
process. The "st" parameter is the open output stream. The other parameters
|
|
are not used.
|
|
*/
|
|
|
|
static t_stat show_dump (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
|
|
{
|
|
fprintf (st, "Dump device = %u, dump control = %03o\n",
|
|
LOWER_BYTE (dump_control), UPPER_BYTE (dump_control));
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Show the current CPU simulation speed.
|
|
|
|
This display routine is called to show the current simulation speed. The
|
|
"st" parameter is the open output stream. The other parameters are not used.
|
|
|
|
The CPU speed, expressed as a multiple of the hardware speed, is calculated
|
|
by the process clock service routine. It is only representative when the
|
|
process clock is calibrated, and the CPU is not executing a PAUS instruction
|
|
(which suspends the normal fetch/execute instruction cycle).
|
|
*/
|
|
|
|
static t_stat show_speed (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
|
|
{
|
|
fprintf (st, "Simulation speed = %ux\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 DUMP switch is pressed, an I/O Reset is performed on all devices
|
|
except the CPU, which is skipped to preserve the register state. The dump
|
|
device number is obtained from the SWCH register and tested to ensure that a
|
|
tape is mounted with a write ring and the unit is online. Then the contents
|
|
of the CPU registers are written to the reserved memory area starting at
|
|
address 1400 octal in bank 0. This is followed by two SIO programs. The
|
|
main program at addresses 1430-1437 write 4K-word blocks of memory to the
|
|
dump device. The error recovery program at addresses 1422-1427 is invoked
|
|
when a write fails. It does a Backspace Record followed by a Write Gap to
|
|
skip the bad spot on the tape, and then the write is retried.
|
|
|
|
The DUMP switch remains set, so after each pass through the main execution
|
|
loop to run a channel cycle, this routine is reentered. The "waiting" state
|
|
causes the second part of the process to check for device completion. When
|
|
it occurs, the expected external interrupt is cleared, and if the dump is
|
|
complete, the DUMP switch is reset, the original SIO pointer in the DRT is
|
|
restored, and the micromachine is halted. Otherwise, the SIO pointer is
|
|
read. If it points at the end of the program, then the operation completed
|
|
normally. In this case, the dump address is advanced, and, if all of memory
|
|
has been dumped, the SIO pointer is reset to the recovery program, and that
|
|
program is changed to finish up with Write File Mark and Rewind/Offline
|
|
commands. Otherwise, the pointer is reset to the main program in preparation
|
|
for the next 4K write. The SIO program is then restarted.
|
|
|
|
If SIO pointer failed to complete, the pointer is reset to point at the error
|
|
recovery program, and the memory address is unchanged; the same 4K write will
|
|
be performed once the recovery program runs. If the recovery program fails,
|
|
the dump is terminated at that point with a failure indication.
|
|
|
|
During the dump operation, the CIR register is continually updated with the
|
|
current memory bank number. If the dump runs to completion, CIR will contain
|
|
the number of 64K memory banks installed in the machine. A value less than
|
|
the installed memory value indicates a dump failure. Except for the CIR, the
|
|
machine state is restored, so that another dump may be attempted.
|
|
|
|
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 "waiting" 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 "waiting" 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 Series
|
|
II 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 and dump 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_device, sio_pointer, status, offset, pointer;
|
|
static uint32 address;
|
|
static t_bool error_recovery;
|
|
|
|
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 */
|
|
|
|
if (cpu_power_state == power_returning) { /* if power is returning after a failure */
|
|
if (CPX2 & cpx2_INHPFARS) /* then if auto-restart is inhibited */
|
|
CPX2 &= ~cpx2_RUN; /* then clear the Run flip-flop */
|
|
|
|
MICRO_ABORT (trap_Power_On); /* set up the trap to the power-on routine */
|
|
}
|
|
}
|
|
|
|
|
|
else if (CPX2 & cpx2_DUMPSWCH) { /* otherwise if the DUMP switch is pressed */
|
|
if (cpu_micro_state != waiting) { /* then if the dump is not in progress */
|
|
reset_all (IO_RESET); /* then reset all I/O devices */
|
|
|
|
cold_device = LOWER_BYTE (SWCH) & DEVNO_MASK; /* get the device number from the lower SWCH byte */
|
|
|
|
status = iop_direct_io (cold_device, ioTIO, 0); /* get the device status */
|
|
|
|
if ((status & MS_ST_MASK) != MS_ST_READY) { /* if the tape is not ready and unprotected */
|
|
CPX2 &= ~cpx2_DUMPSWCH; /* then clear the dump switch */
|
|
|
|
CIR = 0; /* clear CIR to indicate a failure */
|
|
return STOP_CDUMP; /* and terminate the dump */
|
|
}
|
|
|
|
cpu_read_memory (absolute, cold_device * 4, /* get the original DRT pointer */
|
|
&sio_pointer);
|
|
|
|
cpu_write_memory (absolute, 01400, 1); /* set the machine ID to 1 for the Series III */
|
|
cpu_write_memory (absolute, 01401, sio_pointer); /* store the original DRT pointer */
|
|
cpu_write_memory (absolute, 01402, SM); /* store the stack pointer */
|
|
cpu_write_memory (absolute, 01403, 0); /* store zeros for the scratch pad 1 */
|
|
cpu_write_memory (absolute, 01404, 0); /* and scratch pad 2 register values */
|
|
cpu_write_memory (absolute, 01405, DB); /* store the data base */
|
|
cpu_write_memory (absolute, 01406, DBANK << 12 /* store DBANK in 0:4 */
|
|
| PBANK << 8 /* and PBANK in 4:4 */
|
|
| SBANK); /* and SBANK in 12:4 */
|
|
cpu_write_memory (absolute, 01407, Z); /* store the stack limit */
|
|
cpu_write_memory (absolute, 01410, DL); /* and the data limit */
|
|
cpu_write_memory (absolute, 01411, X); /* and the index register */
|
|
cpu_write_memory (absolute, 01412, Q); /* and the frame pointer */
|
|
cpu_write_memory (absolute, 01413, CIR); /* and the current instruction */
|
|
cpu_write_memory (absolute, 01414, PB); /* and the program base */
|
|
cpu_write_memory (absolute, 01415, PL); /* and the program limit */
|
|
cpu_write_memory (absolute, 01416, P); /* and the program counter */
|
|
cpu_write_memory (absolute, 01417, CPX1); /* store the CPX1 register */
|
|
cpu_write_memory (absolute, 01420, STA); /* and the status register */
|
|
cpu_write_memory (absolute, 01421, /* store the lower byte of the CPX2 register */
|
|
LOWER_WORD (CPX2 << 8 /* in the upper byte of memory */
|
|
| MEMSIZE / 65536)); /* and the memory bank count in the lower byte */
|
|
|
|
cpu_write_memory (absolute, 01422, SIO_CNTL); /* CONTRL 0,BSR */
|
|
cpu_write_memory (absolute, 01423, MS_CN_BSR);
|
|
cpu_write_memory (absolute, 01424, SIO_CNTL); /* CONTRL 0,GAP */
|
|
cpu_write_memory (absolute, 01425, MS_CN_GAP);
|
|
cpu_write_memory (absolute, 01426, SIO_JUMP); /* JUMP 001436 */
|
|
cpu_write_memory (absolute, 01427, 001436);
|
|
|
|
cpu_write_memory (absolute, 01430, SIO_SBANK); /* SETBNK 0 */
|
|
cpu_write_memory (absolute, 01431, 000000);
|
|
cpu_write_memory (absolute, 01432, SIO_CNTL); /* CONTRL 0,<SWCH-upper> */
|
|
cpu_write_memory (absolute, 01433, UPPER_BYTE (SWCH));
|
|
cpu_write_memory (absolute, 01434, SIO_WRITE); /* WRITE #4096,000000 */
|
|
cpu_write_memory (absolute, 01435, 000000);
|
|
cpu_write_memory (absolute, 01436, SIO_ENDIN); /* ENDINT */
|
|
cpu_write_memory (absolute, 01437, 000000);
|
|
|
|
address = 0; /* clear the address */
|
|
offset = 0; /* and memory offset counters */
|
|
|
|
CIR = 0; /* clear the memory bank counter */
|
|
|
|
cpu_write_memory (absolute, cold_device * 4, 01430); /* point the DRT at the cold dump program */
|
|
error_recovery = FALSE;
|
|
|
|
iop_direct_io (cold_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 */
|
|
status = STA; /* so save the original status register value */
|
|
|
|
STA = STATUS_I | STATUS_O; /* enable interrupts and set overflow */
|
|
cpu_micro_state = waiting; /* and set the load-in-progress state */
|
|
}
|
|
}
|
|
|
|
else if (CPX1 & cpx1_EXTINTR) { /* otherwise 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_device) /* if the expected device interrupted */
|
|
if (address >= MEMSIZE) { /* then if all of memory has been dumped */
|
|
CPX2 &= ~cpx2_DUMPSWCH; /* then reset the DUMP switch */
|
|
|
|
STA = status; /* restore the original status register value */
|
|
|
|
cpu_write_memory (absolute, /* restore the */
|
|
cold_device * 4, /* original SIO pointer */
|
|
sio_pointer); /* to the DRT */
|
|
|
|
cpu_micro_state = halted; /* clear the dump-in-progress state */
|
|
return STOP_CDUMP; /* and report dump completion */
|
|
}
|
|
|
|
else { /* otherwise the dump continues */
|
|
cpu_read_memory (absolute, /* read the */
|
|
cold_device * 4, /* current SIO pointer address */
|
|
&pointer); /* from the DRT */
|
|
|
|
if (pointer == 01440) { /* if the SIO program completed normally */
|
|
cpu_write_memory (absolute, /* then reset the pointer */
|
|
cold_device * 4, /* to the start */
|
|
001430); /* of the program */
|
|
|
|
if (error_recovery) /* if this was a successful error recovery */
|
|
error_recovery = FALSE; /* then clear the flag and keep the current address */
|
|
|
|
else { /* otherwise this was a successful write */
|
|
address = address + 4096; /* so bump the memory address */
|
|
offset = offset + 4096 & LA_MASK; /* and offset to the next 4K block */
|
|
|
|
cpu_write_memory (absolute, /* store the new write buffer address */
|
|
001435, offset);
|
|
|
|
if (offset == 0) { /* if the offset wrapped around */
|
|
CIR = CIR + 1; /* then increment the bank number */
|
|
cpu_write_memory (absolute, /* and store it as the SET BANK target */
|
|
001431, CIR);
|
|
|
|
if (address >= MEMSIZE) { /* if all of memory has been dumped */
|
|
cpu_write_memory (absolute, 001423, /* then change the error recovery program */
|
|
MS_CN_WFM); /* to write a file mark */
|
|
cpu_write_memory (absolute, 001425, /* followed by */
|
|
MS_CN_RST); /* a rewind/offline request */
|
|
|
|
cpu_write_memory (absolute, /* point at the recovery program */
|
|
cold_device * 4,
|
|
001422);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (error_recovery) { /* otherwise if the recover program failed */
|
|
CPX2 &= ~cpx2_DUMPSWCH; /* then reset the DUMP switch */
|
|
|
|
STA = status; /* restore the original status register value */
|
|
|
|
cpu_write_memory (absolute, /* restore the */
|
|
cold_device * 4, /* original SIO pointer */
|
|
sio_pointer); /* to the DRT */
|
|
|
|
cpu_micro_state = halted; /* clear the dump-in-progress state */
|
|
return STOP_CDUMP; /* and report dump failure */
|
|
}
|
|
|
|
else { /* otherwise attempt error recovery */
|
|
cpu_write_memory (absolute, /* by setting the SIO pointer */
|
|
cold_device * 4, /* to the backspace/write gap */
|
|
001422); /* program */
|
|
|
|
error_recovery = TRUE; /* indicate that recovery is in progress */
|
|
}
|
|
|
|
iop_direct_io (cold_device, ioSIO, 0); /* start the device */
|
|
}
|
|
} /* otherwise wait for the cold dump device to interrupt */
|
|
}
|
|
|
|
else if (CPX2 & cpx2_LOADSWCH) /* otherwise if the LOAD switch is pressed */
|
|
if (cpu_micro_state != waiting) { /* then if the load is not in progress */
|
|
reset_all (CPU_IO_RESET); /* then reset the CPU and all I/O devices */
|
|
|
|
if ((SWCH & 000200) == 0) /* if switch register bit 8 is clear */
|
|
mem_fill (0, HALT_10); /* then fill all of memory with HALT 10 instructions */
|
|
|
|
SBANK = 0; /* set the stack bank to bank 0 */
|
|
|
|
cold_device = LOWER_BYTE (SWCH) & DEVNO_MASK; /* get the device number from the lower SWCH byte */
|
|
|
|
if (cold_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_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, SIO_SBANK); /* SETBNK 0 */
|
|
cpu_write_memory (absolute, 01431, 000000);
|
|
cpu_write_memory (absolute, 01432, SIO_CNTL); /* CONTRL 0,<SWCH-upper> */
|
|
cpu_write_memory (absolute, 01433, UPPER_BYTE (SWCH));
|
|
cpu_write_memory (absolute, 01434, SIO_READ); /* READ #16,001400 */
|
|
cpu_write_memory (absolute, 01435, 001400);
|
|
cpu_write_memory (absolute, 01436, SIO_JUMP); /* JUMP 001400 */
|
|
cpu_write_memory (absolute, 01437, 001400);
|
|
|
|
cpu_write_memory (absolute, cold_device * 4, 01430); /* point the DRT to the cold load program */
|
|
|
|
iop_direct_io (cold_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 = waiting; /* 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_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 = SEXT16 (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 = SEXT16 (X); /* sign-extend the control value */
|
|
}
|
|
|
|
limit = SEXT16 (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;
|
|
}
|