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