/* hp3000_cpu.c: HP 3000 Central Processing Unit simulator Copyright (c) 2016-2019, 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 21-Feb-19 JDB Resuming into PAUS with pending IRQ now sets P correctly Moved "debug_save" from global to "sim_instr" local 12-Feb-19 JDB Worked around idle problem (SIMH issue 622) 06-Feb-19 JDB Corrected trace report for simulation stop 27-Dec-18 JDB Revised fall through comments to comply with gcc 7 05-Sep-17 JDB Removed the -B (binary display) option; use -2 instead Changed REG_A (permit any symbolic override) to REG_X 19-Jan-17 JDB Added comments describing the OPND and EXEC trace options 29_Dec-16 JDB Changed the status mnemonic flag from REG_S to REG_T 07-Nov-16 JDB Renamed cpu_byte_to_word_ea to cpu_byte_ea 03-Nov-16 JDB Added zero offsets to the cpu_call_procedure calls 01-Nov-16 JDB Added per-instruction trace capability 24-Oct-16 JDB Renamed SEXT macro to SEXT16 22-Sep-16 JDB Moved byte_to_word_address from hp3000_cpu_base.c 21-Sep-16 JDB Added CIS/NOCIS option 01-Sep-16 JDB Add power fail/power restore support 23-Aug-16 JDB Add module interrupt support 14-Jul-16 JDB Implemented the cold dump process Changed "loading" EXEC_STATE to "waiting" 11-Jul-16 JDB Change "cpu_unit" from a UNIT to an array of one UNIT 08-Jun-16 JDB Corrected %d format to %u for unsigned values 16-May-16 JDB ACCESS_PROPERTIES.name is now a pointer-to-constant 13-May-16 JDB Modified for revised SCP API function parameter types 24-Mar-16 JDB Changed memory word type from uint16 to MEMORY_WORD 21-Mar-16 JDB Changed cpu_ccb_table type from uint16 to HP_WORD 11-Mar-16 JDB Fixed byte EA calculations with negative indexes 22-Dec-15 JDB First release version 01-Apr-15 JDB First successful run of MPE-V/R through account login 11-Dec-12 JDB Created References: - HP 3000 Series II System Microprogram Listing (30000-90023, August 1976) - HP 3000 Series II/III System Reference Manual (30000-90020, July 1978) - Machine Instruction Set Reference Manual (30000-90022, June 1984) The HP 3000 is a family of general-purpose business computers that were sold by Hewlett-Packard from 1972 through 2001. There are two major divisions within this family: the "classic" 16-bit, stack-oriented CISC machines, and the "Precision Architecture" 32-bit, register-oriented RISC machines that succeeded them. All machines run versions of MPE, the "Multiprogramming Executive" operating system. Within the "classic" division, there are two additional subdivisions, based on the method used for peripheral connections: the original "SIO" machines, and the later "HP-IB" machines. The I/O interfacing hardware differs between the two types of machines, as do the privileged I/O machine instructions. The user instruction sets are identical, as are the register sets visible to the programmer. The I/O drivers are different to account for the hardware differences, and therefore they run slightly different versions of MPE. This implementation is a simulator for the classic SIO machines. This group consists of the 3000 CX, the Series I, Series II, and Series III; the last is simulated here. The CX and Series I, which is a repackaged CX, are essentially subsets of the Series II/III -- a smaller instruction set, limited memory size, and lower-precision floating-point instructions. Simulation of these machines may be added in the future. Future simulation of the HP-IB machines (the Series 30 through 70) is desirable, as the latest MPE versions run only on these machines, but documentation on the internals of the HP-IB hardware controllers is nonexistent. The CX and Series I support 64K 16-bit words of memory. The Series II supports up to 256K, divided into four banks of 64K words each, and the Series III extends this to 1024K words using 16 banks. Memory is divided into variable-length code and data segments, with the latter containing a program's global data area and stack. Memory protection is accomplished by checking program, data, and stack accesses against segment base and limit registers, which can be set only by MPE. Bounds violations cause automatic hardware traps to a handler routine within MPE. Some violations may be permitted; for example, a Stack Overflow trap may cause MPE to allocate a larger stack and then restart the interrupted instruction. Almost all memory references are position- independent, so moving segments to accommodate expansion requires only resetting of the segment registers to point at the new locations. Code segments are fully reentrant and shareable, and both code and data are virtual, as the hardware supports absent code and data segment traps. The classic 3000s are stack machines. Most of the instructions operate on the value on the top of the stack (TOS) or on the TOS and the next-to-the-top of the stack (NOS). To improve execution speed, the 3000 has a set of hardware registers that are accessed as the first four locations at the top of the stack, while the remainder of the stack locations reside in main memory. A hardware register renamer provides fast stack pushes and pops without physically copying values between registers. In hardware, the stack registers are referenced internally as TR0-TR3 and externally as RA-RD. An access to the RA (TOS) register is translated by the renamer to access TR0, TR1, etc. depending on which internal register is designated as the current top of the stack. For example, assume that RA corresponds to TR0. To push a new value onto the top of the stack, the renamer is adjusted so that RA corresponds to TR1, and the new value is stored there. In this state, RB corresponds to TR0, the previous TOS (and current NOS) value. Additional pushes rename RA as TR2 and then TR3, with RB being renamed to TR1 and then TR2, and similarly for RC and RD. The number of valid TOS registers is given by the value in the SR register, and the first stack location in memory is given by the value in the SM register. Pops reverse the sequence: a pop with RA corresponding to TR3 renames RA to TR2, RB to TR1, etc. When all four stack registers are in use, a push will copy the value in RD to the top of the memory stack (SM + 1) before the registers are renamed and the new value stored into RA. Similarly, when all four stack registers are empty, a pop simply decrements the SM register, effectively deleting the top of the memory stack. Because of the renamer, the microcode can always use RA to refer to the top of the stack, regardless of which internal TR register is being used, and similarly for RB-RD. Execution of each stack instruction begins with a preadjustment, if needed, that loads the TOS registers that will be used by the instruction from the top of the memory stack. For example, if only RA contains a value (i.e., the SR register value is 1), the ADD instruction, which adds the values in RA and RB, will load RB with the value on the top of the memory stack, the SR count will be incremented, and the SM register will be decremented. On the other hand, if both RA and RB contained values (SR >= 2), then no preadjustment would be required before the ADD instruction microcode manipulated RA and RB. In simulation, the renamer is implemented by physically copying the values between registers, as this is much faster than mapping via array index values, as the hardware does. A set of functions provides the support to implement the hardware stack operations: cpu_push - empty the TOS register (SR++, caller stores into RA) cpu_pop - delete the TOS register (SR--) cpu_queue_up - move from memory to the lowest TOS register (SR++, SM--) cpu_queue_down - move from the lowest TOS register to memory (SR--, SM++) cpu_flush - move all stack registers to memory (SR = 0 on return) cpu_adjust_sr - queue up until the required SR count is reached cpu_mark_stack - write a stack marker to memory The renamer is described in US Patent 3,737,871 (Katzman, "Stack Register Renamer", June 1973). The MPE operating system is supported by special microcode features that perform code and data segment mapping, segment access bounds checking, privilege checking, etc. The layout of certain in-memory tables is known to both the OS and the microcode and is used to validate execution of instructions. For instance, every stack instruction is checked for a valid access within the stack segment boundaries, which are set up by the OS before program dispatch. For this reason, the 3000 cannot be operated as a "bare" machine, because these tables would not have been initialized. Similarly, the "cold load" process by which the OS is loaded from storage media into memory is entirely in microcode, as machine instructions cannot be executed until the required tables are loaded into memory. This OS/microcode integration means that the microcode may detect conditions that make continued execution impossible. An example would be a not-present segment fault for the segment containing the disc I/O driver. If such a condition is detected, the CPU does a "system halt." This fatal microcode error, distinct from a regular programmed halt, causes operation to cease until the CPU is reset. The CPU hardware includes a free-running 16-bit process clock that increments once per millisecond whenever a user program is executing. MPE uses the process clock to charge CPU usage to programs, and thereby to users, groups, and accounts. Instructions are provided to read (RCLK) and set (SCLK) the process clock. The data types supported by the instruction set are: - 8-bit unsigned byte - 16-bit unsigned integer ("logical" format) - 16-bit two's-complement integer - 32-bit two's-complement integer - 32-bit sign-magnitude floating point - 64-bit sign-magnitude floating point Multi-word values are stored in memory with the most-significant words in the lowest addresses. The stack is organized in memory from lower to higher addresses, so a value on the stack has its least-significant word on the top of the stack. Machine instructions are initially decoded by a sub-opcode contained in the four highest bits, as follows (note that the HP 3000 numbers bits from left-to-right, i.e., bit 0 is the MSB and bit 15 is the LSB): 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 0 0 | 1st stack opcode | 2nd stack opcode | Stack +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 0 1 | X | shift opcode | shift count | Shift +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 0 1 | I | branch opcode |+/-| P displacement | Branch +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 0 1 | X | bit test opcode | bit position | Bit Test +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 0 | 0 0 0 0 | move op | opts/S decrement | Move +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 0 | 0 0 0 0 | special op | 0 0 | sp op | Special +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 0 | 0 0 0 1 | option set | option subset | Firmware +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 0 | imm opcode | immediate operand | Immediate +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 0 | field opcode | J field | K field | Field +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 0 | register op | SK| DB| DL| Z |STA| X | Q | S | Register +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 1 | 0 0 0 0 | I/O opcode | K field | I/O +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 1 | 0 0 0 0 | cntl opcode | 0 0 | cn op | Control +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 1 | program op | N field | Program +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 1 | immediate op | immediate operand | Immediate +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 1 | memory op | P displacement | Memory +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The memory, loop, and branch instructions occupy the remainder of the sub-opcodes (04-17): 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | memory op | X | I | mode and displacement | Memory +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 | 0 | P+ displacement 0-255 | +---+---+---+---+---+---+---+---+---+---+ | 0 | 1 | P- displacement 0-255 | +---+---+---+---+---+---+---+---+---+---+ | 1 | 0 | DB+ displacement 0-255 | +---+---+---+---+---+---+---+---+---+---+ | 1 | 1 | 0 | Q+ displacement 0-127 | +---+---+---+---+---+---+---+---+---+---+ | 1 | 1 | 1 | 0 | Q- displacement 0-63 | +---+---+---+---+---+---+---+---+---+---+ | 1 | 1 | 1 | 1 | S- displacement 0-63 | +---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | memory op | X | I | s | mode and displacement | Memory +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 | DB+ displacement 0-255 | +---+---+---+---+---+---+---+---+---+ | 1 | 0 | Q+ displacement 0-127 | +---+---+---+---+---+---+---+---+---+ | 1 | 1 | 0 | Q- displacement 0-63 | +---+---+---+---+---+---+---+---+---+ | 1 | 1 | 1 | S- displacement 0-63 | +---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 1 0 1 |loop op| 0 |+/-| P-relative displacement | Loop +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 1 0 0 | I | 0 1 | > | = | < | P+- displacement 0-31 | Branch +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Optional firmware extension instruction sets occupy instruction codes 020400-020777, except for the DMUL (020570) and DDIV (020571) instructions that are part of the base set, as follows: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 0 | 0 0 0 1 | 0 1 1 1 | 1 0 0 | x | DMUL/DDIV +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 0 | 0 0 0 1 | 0 0 0 0 | 1 | ext fp op | Extended FP +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 0 | 0 0 0 1 | 0 0 0 1 | APL op | APL +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | COBOL op | COBOL +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 0 | 0 0 0 1 | 1 | options | decimal op | Decimal +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Most instructions are defined by unique 16-bit codes that specify the operations, operands, addressing modes, shift counts, etc. For each of these instructions, there is only one canonical form that encodes a given instruction (or instruction pair, in the case of stack instructions). For example, the octal value 140003 is the only value that encodes the "BR P+3" instruction. There are also instruction codes that contain one or more bits designated as "reserved" in the Machine Instruction Set Reference Manual. For some of these instructions, the reserved bits do not affect instruction decoding. Each instruction of this type has a defined canonical form -- typically with the reserved bits set to zero -- but will execute identically if one of the undefined forms is used. For example, the "MOVE" instructions define bits 12-13 as 00, but the bits are not decoded, so values of 01, 10, and 11 for these bits will result in identical execution. Specifically, "MOVE 0" is encoded canonically as octal value 020020, but the undefined codes 020024, 020030, and 020034 execute "MOVE 0" as well. For the rest of these instructions, the reserved bits are decoded and will affect the instruction interpretation. An example of this is the "IXIT" instruction. It also defines bits 12-13 as 00 (canonical encoding 020360), but in this case the bits must be 00 for the instruction to execute; any other value (e.g., undefined codes 020364, 020370, or 020374) executes as a "PCN" instruction, whose canonical encoding is 020362. Finally, some codes are not assigned to any instructions, or they are assigned to instructions that are supplied by optional firmware that is not present in the machine. These instruction codes are designated as "unimplemented" and will cause Unimplemented Instruction traps if they are executed. Examples are the stack operation code 072 in either the left-hand or right-hand position (i.e., instruction codes 0072xx and 00xx72), codes 020410-020415 if the Extended Instruction Set firmware option is not installed, and codes 036000-036777. When the simulator examines the bit patterns of instructions to execute, each will fall into one of four categories: 1. Defined (canonical) instruction encodings. 2. Undefined (non-canonical) instruction encodings, where reserved fields are "don't care" bits (e.g., MOVE). 3. Undefined (non-canonical) instruction encodings, where reserved fields are decoded (e.g., IXIT). 4. Unimplemented instruction encodings (e.g., stack opcode 072 or EADD with no EIS installed). When examining memory or register values in instruction-mnemonic form, the names of the canonical instructions in category 1 are displayed in uppercase, as are the names of the non-canonical instructions in category 2. The non-canonical instruction names in category 3 are displayed in lowercase. This is to indicate to the user that the instructions that will be executed may not be the ones expected from the decoding. Instruction names in category 4 that correspond to supported firmware options are displayed in uppercase, regardless of whether or not the option is enabled. Category 4 encodings that do not correspond to instructions are displayed in octal. All of the base set instructions are one word in length. Most of the firmware extension instructions occupy one word as well, although some are two words long. If the first word of a two-word instruction is valid but the second is not, an Unimplemented Instruction trap will occur, with P pointing to the location after the second word. The simulator provides four stop conditions related to instruction execution that may be enabled with a SET CPU STOP= command: 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= and SET CPU DEBUG= commands. The trace options that may be specified are: Trace Action ----- ------------------------------------------- INSTR trace instructions executed DATA trace memory data accesses FETCH trace memory instruction fetches REG trace registers OPND trace memory operands EXEC trace matching instruction execution states PSERV trace process clock service events A section of an example trace is: >>CPU fetch: 00.010342 020320 instruction fetch >>CPU instr: 00.010341 000300 ZROX,NOP >>CPU reg: 00.006500 000000 X 000000, M i t r o c CCG >>CPU fetch: 00.010343 041100 instruction fetch >>CPU instr: 00.010342 020320 PLDA >>CPU data: 00.000000 001340 absolute read >>CPU reg: 00.006500 000001 A 001340, X 000000, M i t r o c CCG >>CPU fetch: 00.010344 037777 instruction fetch >>CPU instr: 00.010343 041100 LOAD DB+100 >>CPU data: 00.002100 123003 data read >>CPU reg: 00.006500 000002 A 123003, B 001340, X 000000, M i t r o c CCL >>CPU fetch: 00.010345 023404 instruction fetch >>CPU instr: 00.010344 037777 ANDI 377 >>CPU reg: 00.006500 000002 A 000003, B 001340, X 000000, M i t r o c CCG >>CPU fetch: 00.010346 002043 instruction fetch >>CPU instr: 00.010345 023404 MPYI 4 >>CPU reg: 00.006500 000002 A 000014, B 001340, X 000000, M i t r o c CCG >>CPU fetch: 00.010347 020320 instruction fetch The INSTR option traces instruction executions. Each instruction is printed before it is executed. The two opcodes of a stack instruction are printed together before the left-hand opcode is executed. If the right-hand opcode is not NOP, it is reprinted before execution, with dashes replacing the just-executed left-hand opcode. The DATA option traces reads from and writes to memory. Each access is classified by the memory bank register that is paired with the specified offset; they are: dma, absolute, program, data, and stack. DMA accesses derive their bank addresses from the banks specified in Set Bank I/O program orders. Absolute accesses always use bank 0. Program, data, and stack accesses use the bank addresses in the PBANK, DBANK, and SBANK registers, respectively. The FETCH option traces instruction fetches from memory. These accesses are separated from those traced by the DATA option because fetches usually are of little interest except when debugging the fetch/execute sequence. Because the HP 3000 has a two-stage pipeline, fetches load the NIR (Next Instruction Register) with the instruction after the instruction to be executed from the CIR (Current Instruction Register). The REG option traces register values. Two sets of registers are printed. After executing each instruction, the currently active TOS registers, the index register, and the status register are printed. After executing an instruction that may alter the base registers, the program, data, and stack segment base registers are printed. The OPND option traces memory byte operand values. Some instructions take memory and register operands that are difficult to decode from DATA or REG traces. This option presents these operands in a higher-level format. The memory bank and address values are always those of the operands. The operand data and values printed are specific to the instruction. For example, the ALGN instruction prints its source and target operands, digit counts, and fraction counts, and the EDIT instruction displays its subprogram operations. The EXEC option traces the execution of instructions that match user-specified criteria. When a match occurs, all CPU trace options are turned on for the duration of the execution of the matched instruction. The prior trace settings are restored when a match fails. This option allows detailed tracing of specified instructions while minimizing the log file size compared to a full instruction trace. The PSERV option traces process clock event service entries. Each trace reports whether or not the CPU was executing on the Interrupt Control Stack when the process clock ticked. Execution on the ICS implies that the operating system is executing. As the process clock ticks every millisecond, enabling PSERV tracing can quickly produce a large number of trace lines. The various trace formats are interpreted as follows: >>CPU instr: 00.010341 000300 ZROX,NOP ~~ ~~~~~~ ~~~~~~ ~~~~~~~~ | | | | | | | +-- instruction mnemonic(s) | | +---------- octal data (instruction opcode) | +------------------ octal address (P) +----------------------- octal bank (PBANK) >>CPU instr: 00.001240 000006 external interrupt >>CPU instr: 00.023736 000000 unimplemented instruction trap ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | | | +-- interrupt classification | | +---------- parameter | +------------------ octal address (P) at interrupt +----------------------- octal bank (PBANK) at interrupt >>CPU data: 00.000000 001340 absolute read >>CPU data: 00.002100 123003 data read ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~ | | | | | | | +-- memory access classification | | +------------ octal data (memory contents) | +-------------------- octal address (effective address) +------------------------- octal bank (PBANK, DBANK, or SBANK) >>CPU fetch: 00.010342 020320 instruction fetch ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~ | | | | | | | +-- memory access classification | | +------------ octal data (instruction opcode) | +-------------------- octal address (P + 1) +------------------------- octal bank (PBANK) >>CPU reg: 00.006500 000002 A 123003, B 001340, X 000000, M i t r o c CCL ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | | | +-- register values (from 0-4 TOS registers, X, STA) | | +------------ octal stack register count (SR) | +-------------------- octal stack memory address (SM) +------------------------- octal bank (SBANK) >>CPU reg: 00.000000 000001 PB 010000, PL 025227, DL 001770, DB 002000, Q 006510, Z 007000 ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | | | +-- base register values | | +------------ current code segment number (from STA) | +-------------------- zero +------------------------- octal bank (DBANK) >>CPU opnd: 00.135771 000000 DFLC '+','!' >>CPU opnd: 00.045071 000252 target fraction 3 length 6,"002222" ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | | | +-- operand-specific value | | +------------ operand-specific data | +-------------------- octal address (effective address) +------------------------- octal bank (PBANK, DBANK, or SBANK) >>CPU pserv: Process clock service entered not on the ICS The process clock offers a user-selectable choice of calibrated or realistic timing. Calibrated timing adjusts the clock to match actual elapsed time (i.e., wall-clock time). Realistic timing bases the process-clock interval on machine instructions executed, using a mean instruction time of 2.5 microseconds. Running on a typical host platform, the simulator is one or two orders of magnitude faster than a real HP 3000, so the number of machine instructions executed for a given calibrated time interval will be correspondingly greater. When the process clock is calibrated, the current simulation speed, expressed as a multiple of the speed of a real HP 3000 Series III, may be obtained with the SHOW CPU SPEED command. The speed reported will not be representative if the machine was executing a PAUS instruction when the simulator was stopped. When enabled by a SET CPU IDLE command, execution of a PAUS instruction will idle the simulator. While idle, the simulator does not use any host system processor time. Idle detection requires that the process clock and system clock be set to calibrated timing. Idling is disabled by default. Implementation notes: 1. Three timing sources in the simulator may be calibrated to wall-clock time. These are the process clock, the system clock, and the ATC poll timer. The process clock is always enabled and running, although the PCLK register only increments if the CPU is not executing on the ICS. The system clock and poll timer run continuously if their respective devices are enabled. If the ATC is disabled, then the process clock takes over polling for the simulation console. The three sources must be synchronized to allow efficient simulator idling. This is accomplished by designating the process clock as the master device, which calls the SCP timer calibration routines, and setting the system clock and ATC poll timer to the process clock wait. All three devices will then enter their respective service routines concurrently. 2. In hardware, the process clock period is fixed at one millisecond, and the system clock period, while potentially variable, is set by MPE to one millisecond with an interrupt every 100 ticks. These periods are too short to allow the simulator to idle, as the host OS clock resolution is typically also one millisecond. Therefore, the process and system clock simulators are scheduled with ten-millisecond service times, and the PCLK and counter registers are incremented by ten for each event service. To present the correct values when the registers are read, the counts are incremented by amounts proportional to the fractions of the service intervals that have elapsed when the reads occur. 3. In simulation, the TOS renamer is implemented by permanently assigning the register names RA-RD to TR [0]-TR [3], respectively, and copying the contents between registers to pop and push values. An alternate implementation approach is to use a renaming register, RN, that tracks the correspondence between registers and array entries, and to assign the register names dynamically using modular indices, e.g., RA is TR [RN], RB is TR [(RN + 1) & 3], etc. In lieu of copying, incrementing and decrementing RN is done to pop and push values. Both implementations were mocked up and timing measurements made. The results were that copying values is much faster than mapping via array index values, so this is the implementation chosen. 4. Idling with 4.x simulator framework versions after June 14, 2018 (git commit ID d3986466) loses system clock ticks. This causes the MPE clock to run slowly, losing about one second per minute. A workaround is to prevent "sim_interval" from going negative on return from "sim_idle". Issue 622 on the SIMH site describes the same problem with the HP 2100 simulator. */ #include "hp3000_defs.h" #include "hp3000_cpu.h" #include "hp3000_cpu_ims.h" #include "hp3000_mem.h" #include "hp3000_io.h" /* Lost time workaround */ #if (SIM_MAJOR >= 4) #define sim_idle(timer,decrement) \ if (sim_idle (timer, decrement) == TRUE /* [workaround] idle the simulator; if idling occurred */ \ && sim_interval < 0) /* [workaround] and the time interval is negative */ \ sim_interval = 0 /* [workaround] then reset it to zero */ #endif /* Program constants */ #define PCLK_PERIOD mS (1) /* 1 millisecond process clock period */ #define PCLK_MULTIPLIER 10 /* number of hardware process clock ticks per service */ #define PCLK_RATE (1000 / PCLK_MULTIPLIER) /* process clock rate in ticks per second */ #define UNIT_OPTS (UNIT_EIS) /* the standard equipment feature set */ #define CPU_IO_RESET 0 /* reset CPU and all I/O devices */ #define IO_RESET 1 /* reset just the I/O devices */ #define CNTL_BASE 8 /* the radix for the cold dump control byte */ #define CNTL_MAX 0377 /* the maximum cold dump control byte value */ #define SIO_JUMP 0000000u /* Jump unconditionally */ #define SIO_SBANK 0014000u /* Set bank */ #define SIO_ENDIN 0034000u /* End with interrupt */ #define SIO_CNTL 0040000u /* Control */ #define SIO_WRITE 0060000u /* Write 4096 words */ #define SIO_READ 0077760u /* Read 16 words */ #define MS_CN_GAP 0000005u /* Write Gap */ #define MS_CN_WRR 0000004u /* Write Record */ #define MS_CN_RST 0000011u /* Rewind and Reset */ #define MS_CN_BSR 0000012u /* Backspace Record */ #define MS_CN_WFM 0000015u /* Write File Mark */ #define MS_ST_PROTECTED 0001000u /* write protected */ #define MS_ST_READY 0000400u /* unit ready */ #define MS_ST_MASK (MS_ST_PROTECTED | MS_ST_READY) /* CPU global SCP data definitions */ DEVICE cpu_dev; /* incomplete device structure */ REG *sim_PC = NULL; /* the pointer to the P register */ /* CPU global data structures */ /* CPU registers */ HP_WORD CIR = 0; /* current instruction register */ HP_WORD NIR = 0; /* next instruction register */ HP_WORD PB = 0; /* program base register */ HP_WORD P = 0; /* program counter register */ HP_WORD PL = 0; /* program limit register */ HP_WORD PBANK = 0; /* program segment bank register */ HP_WORD DL = 0; /* data limit register */ HP_WORD DB = 0; /* data base register */ HP_WORD DBANK = 0; /* data segment bank register */ HP_WORD Q = 0; /* stack marker register */ HP_WORD SM = 0; /* stack memory register */ HP_WORD SR = 0; /* stack register counter */ HP_WORD Z = 0; /* stack limit register */ HP_WORD SBANK = 0; /* stack segment bank register */ HP_WORD TR [4] = { 0, 0, 0, 0 }; /* top of stack registers */ HP_WORD X = 0; /* index register */ HP_WORD STA = 0; /* status register */ HP_WORD SWCH = 0; /* switch register */ HP_WORD CPX1 = 0; /* run-mode interrupt flags register */ HP_WORD CPX2 = 0; /* halt-mode interrupt flags register */ HP_WORD MOD = 0; /* module control register */ HP_WORD PCLK = 0; /* process clock register */ HP_WORD CNTR = 0; /* microcode counter */ /* Condition Code B lookup table. Byte-oriented instructions set the condition code in the status word using Pattern B (CCB). For this encoding: CCG = ASCII numeric character CCE = ASCII alphabetic character CCL = ASCII special character The simplest implementation of this pattern is a 256-way lookup table using disjoint condition code flags. The SET_CCB macro uses this table to set the condition code appropriate for the supplied operand into the status word. */ const HP_WORD cpu_ccb_table [256] = { CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* NUL SOH STX ETX EOT ENQ ACK BEL */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* BS HT LF VT FF CR SO SI */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* DLE DC1 DC2 DC3 DC4 NAK SYN ETB */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* CAN EM SUB ESC FS GS RS US */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* spa ! " # $ % & ' */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* ( ) * + , - . / */ CFG, CFG, CFG, CFG, CFG, CFG, CFG, CFG, /* 0 1 2 3 4 5 6 7 */ CFG, CFG, CFL, CFL, CFL, CFL, CFL, CFL, /* 8 9 : ; < = > ? */ CFL, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* @ A B C D E F G */ CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* H I J K L M N O */ CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* P Q R S T U V W */ CFE, CFE, CFE, CFL, CFL, CFL, CFL, CFL, /* X Y Z [ \ ] ^ _ */ CFL, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* ` a b c d e f g */ CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* h i j k l m n o */ CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* p q r s t u v w */ CFE, CFE, CFE, CFL, CFL, CFL, CFL, CFL, /* x y z { | } ~ DEL */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 200 - 207 */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 210 - 217 */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 220 - 227 */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 230 - 237 */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 240 - 247 */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 250 - 257 */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 260 - 267 */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 270 - 277 */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 300 - 307 */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 310 - 317 */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 320 - 327 */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 330 - 337 */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 340 - 347 */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 350 - 357 */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 360 - 367 */ CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL /* 370 - 377 */ }; /* CPU global state */ jmp_buf cpu_save_env; /* the saved environment for microcode aborts */ uint32 cpu_stop_flags; /* the simulation stop flag set */ POWER_STATE cpu_power_state = power_on; /* the power supply state */ EXEC_STATE cpu_micro_state = halted; /* the microcode execution state */ t_bool cpu_base_changed = FALSE; /* TRUE if any base register is changed */ t_bool cpu_is_calibrated = TRUE; /* TRUE if the process clock is calibrated */ UNIT *cpu_pclk_uptr = &cpu_unit [0]; /* a (constant) pointer to the process clock unit */ /* CPU local state */ static uint32 sim_stops = 0; /* the current simulation stop flag settings */ static uint32 cpu_speed = 1; /* the CPU speed, expressed as a multiplier of a real machine */ static uint32 pclk_increment = 1; /* the process clock increment per event service */ static uint32 dump_control = 0002006u; /* the cold dump control word (default CNTL = 4, DEVNO = 6 */ static HP_WORD exec_mask = 0; /* the current instruction execution trace mask */ static HP_WORD exec_match = D16_UMAX; /* the current instruction execution trace matching value */ /* CPU local data structures */ /* Interrupt classification names */ static const char *const interrupt_name [] = { /* class names, indexed by IRQ_CLASS */ "integer overflow", /* 000 irq_Integer_Overflow */ "bounds violation", /* 001 irq_Bounds_Violation */ "illegal memory address error", /* 002 irq_Illegal_Address */ "non-responding module error", /* 003 irq_Timeout */ "system parity error", /* 004 irq_System_Parity */ "address parity error", /* 005 irq_Address_Parity */ "data parity error", /* 006 irq_Data_Parity */ "module", /* 007 irq_Module */ "external", /* 010 irq_External */ "power fail", /* 011 irq_Power_Fail */ "ICS trap", /* 012 irq_Trap */ "dispatch", /* 013 irq_Dispatch */ "exit" /* 014 irq_IXIT */ }; /* Trap classification names */ static const char *const trap_name [] = { /* trap names, indexed by TRAP_CLASS */ "no", /* 000 trap_None */ "bounds violation", /* 001 trap_Bounds_Violation */ NULL, /* 002 (unused) */ NULL, /* 003 (unused) */ NULL, /* 004 (unused) */ NULL, /* 005 (unused) */ NULL, /* 006 (unused) */ NULL, /* 007 (unused) */ NULL, /* 010 (unused) */ NULL, /* 011 (unused) */ NULL, /* 012 (unused) */ NULL, /* 013 (unused) */ NULL, /* 014 (unused) */ NULL, /* 015 (unused) */ NULL, /* 016 (unused) */ NULL, /* 017 (unused) */ "unimplemented instruction", /* 020 trap_Unimplemented */ "STT violation", /* 021 trap_STT_Violation */ "CST violation", /* 022 trap_CST_Violation */ "DST violation", /* 023 trap_DST_Violation */ "stack underflow", /* 024 trap_Stack_Underflow */ "privileged mode violation", /* 025 trap_Privilege_Violation */ NULL, /* 026 (unused) */ NULL, /* 027 (unused) */ "stack overflow", /* 030 trap_Stack_Overflow */ "user", /* 031 trap_User */ NULL, /* 032 (unused) */ NULL, /* 033 (unused) */ NULL, /* 034 (unused) */ NULL, /* 035 (unused) */ NULL, /* 036 (unused) */ "absent code segment", /* 037 trap_CS_Absent */ "trace", /* 040 trap_Trace */ "STT entry uncallable", /* 041 trap_Uncallable */ "absent data segment", /* 042 trap_DS_Absent */ "power on", /* 043 trap_Power_On */ "cold load", /* 044 trap_Cold_Load */ "system halt" /* 045 trap_System_Halt */ }; /* CPU features table. The feature table is used to validate CPU feature changes within the subset of features supported by a given CPU. Features in the typical list are enabled when the CPU model is selected. If a feature appears in the typical list but NOT in the optional list, then it is standard equipment and cannot be disabled. If a feature appears in the optional list, then it may be enabled or disabled as desired by the user. Implementation notes: 1. The EIS was standard equipment for the Series II and III, so UNIT_EIS should appear in their "typ" fields. However, the EIS instructions are not currently implemented, so the value is omitted below. */ struct FEATURE_TABLE { uint32 typ; /* standard features plus typically configured options */ uint32 opt; /* complete list of optional features */ uint32 maxmem; /* maximum configurable memory in 16-bit words */ }; static const struct FEATURE_TABLE cpu_features [] = { /* features indexed by CPU_MODEL */ { 0, /* UNIT_SERIES_III */ UNIT_CIS, 1024 * 1024 }, { 0, /* UNIT_SERIES_II */ 0, 256 * 1024 } }; /* CPU local SCP support routine declarations */ static t_stat cpu_service (UNIT *uptr); static t_stat cpu_reset (DEVICE *dptr); static t_stat set_stops (UNIT *uptr, int32 option, CONST char *cptr, void *desc); static t_stat set_exec (UNIT *uptr, int32 option, CONST char *cptr, void *desc); static t_stat set_dump (UNIT *uptr, int32 option, CONST char *cptr, void *desc); static t_stat set_size (UNIT *uptr, int32 new_size, CONST char *cptr, void *desc); static t_stat set_model (UNIT *uptr, int32 new_model, CONST char *cptr, void *desc); static t_stat set_option (UNIT *uptr, int32 new_option, CONST char *cptr, void *desc); static t_stat set_pfars (UNIT *uptr, int32 setting, CONST char *cptr, void *desc); static t_stat show_stops (FILE *st, UNIT *uptr, int32 val, CONST void *desc); static t_stat show_exec (FILE *st, UNIT *uptr, int32 val, CONST void *desc); static t_stat show_dump (FILE *st, UNIT *uptr, int32 val, CONST void *desc); static t_stat show_speed (FILE *st, UNIT *uptr, int32 val, CONST void *desc); /* CPU local utility routine declarations */ static t_stat halt_mode_interrupt (HP_WORD device_number); static t_stat machine_instruction (void); /* CPU SCP data structures */ /* Unit list. The CPU unit holds the main memory capacity and is used to schedule the process clock events. Implementation notes: 1. The unit structure must be global for other modules to obtain the memory size via the MEMSIZE macro, which references the "capac" field. */ #define UNIT_FLAGS (UNIT_PFARS | UNIT_CALTIME) UNIT cpu_unit [] = { { UDATA (&cpu_service, UNIT_FLAGS | UNIT_FIX | UNIT_BINK | UNIT_IDLE, 0), PCLK_PERIOD * PCLK_MULTIPLIER } }; /* Register list. The CPU register list exposes the machine registers for user inspection and modification. User flags describe the permitted and default display formats, as follows: - REG_X permits any symbolic display - REG_M defaults to CPU instruction mnemonic display - REG_T defaults to CPU status mnemonic display Implementation notes: 1. All registers that reference variables of type HP_WORD must have the REG_FIT flag for proper access if HP_WORD is a 16-bit type. 2. The CNTR register is set to the value of the SR register when the micromachine halts or pauses. This allows the SR value to be accessed by the diagnostics. The top-of-stack registers are flushed to main memory when the machine halts or pauses, which alters SR. */ static REG cpu_reg [] = { /* Macro Name Location Radix Width Offset Flags */ /* ------ ------- ------------ ----- ----- ------ ------------------------ */ { ORDATA (CIR, CIR, 16), REG_M | REG_RO | REG_FIT }, /* current instruction register */ { ORDATA (NIR, NIR, 16), REG_M | REG_RO | REG_FIT }, /* next instruction register */ { ORDATA (PB, PB, 16), REG_FIT }, /* program base register */ { ORDATA (P, P, 16), REG_FIT }, /* program counter register */ { ORDATA (PL, PL, 16), REG_FIT }, /* program limit register */ { ORDATA (PBANK, PBANK, 4), REG_FIT }, /* program segment bank register */ { ORDATA (DL, DL, 16), REG_FIT }, /* data limit register */ { ORDATA (DB, DB, 16), REG_FIT }, /* data base register */ { ORDATA (DBANK, DBANK, 4), REG_FIT }, /* data segment bank register */ { ORDATA (Q, Q, 16), REG_FIT }, /* stack marker register */ { ORDATA (SM, SM, 16), REG_FIT }, /* stack memory register */ { ORDATA (SR, SR, 3), REG_FIT }, /* stack register counter */ { ORDATA (Z, Z, 16), REG_FIT }, /* stack limit register */ { ORDATA (SBANK, SBANK, 4), REG_FIT }, /* stack segment bank register */ { ORDATA (RA, TR [0], 16), REG_X | REG_FIT }, /* top of stack register */ { ORDATA (RB, TR [1], 16), REG_X | REG_FIT }, /* top of stack - 1 register */ { ORDATA (RC, TR [2], 16), REG_X | REG_FIT }, /* top of stack - 2 register */ { ORDATA (RD, TR [3], 16), REG_X | REG_FIT }, /* top of stack - 3 register */ { ORDATA (X, X, 16), REG_X | REG_FIT }, /* index register */ { ORDATA (STA, STA, 16), REG_T | REG_FIT }, /* status register */ { ORDATA (SWCH, SWCH, 16), REG_X | REG_FIT }, /* switch register */ { ORDATA (CPX1, CPX1, 16), REG_FIT }, /* run-mode interrupt flags */ { ORDATA (CPX2, CPX2, 16), REG_FIT }, /* halt-mode interrupt flags */ { ORDATA (PCLK, PCLK, 16), REG_FIT }, /* process clock register */ { ORDATA (CNTR, CNTR, 6), REG_HRO | REG_FIT }, /* microcode counter */ { ORDATA (MOD, MOD, 16), REG_HRO | REG_FIT }, /* module control register */ { DRDATA (POWER, cpu_power_state, 2), REG_HRO }, /* system power supply state */ { ORDATA (WRU, sim_int_char, 8), REG_HRO }, /* SCP interrupt character */ { ORDATA (BRK, sim_brk_char, 8), REG_HRO }, /* SCP break character */ { ORDATA (DEL, sim_del_char, 8), REG_HRO }, /* SCP delete character */ { NULL } }; /* Modifier list */ static MTAB cpu_mod [] = { /* Mask Value Match Value Print String Match String Validation Display Descriptor */ /* ------------ --------------- ------------------- ------------ ----------- ------- ---------- */ { UNIT_MODEL, UNIT_SERIES_II, "Series II", NULL, &set_model, NULL, NULL }, { UNIT_MODEL, UNIT_SERIES_III, "Series III", "III", &set_model, NULL, NULL }, { UNIT_EIS, UNIT_EIS, "EIS", NULL, &set_option, NULL, NULL }, { UNIT_EIS, 0, "no EIS", "NOEIS", NULL, NULL, NULL }, { UNIT_CIS, UNIT_CIS, "CIS", "CIS", &set_option, NULL, NULL }, { UNIT_CIS, 0, "no CIS", "NOCIS", NULL, NULL, NULL }, { UNIT_PFARS, UNIT_PFARS, "auto-restart", "ARS", &set_pfars, NULL, NULL }, { UNIT_PFARS, 0, "no auto-restart", "NOARS", &set_pfars, NULL, NULL }, { UNIT_CALTIME, UNIT_CALTIME, "calibrated timing", "CALTIME", NULL, NULL, NULL }, { UNIT_CALTIME, 0, "realistic timing", "REALTIME", NULL, NULL, NULL }, /* Entry Flags Value Print String Match String Validation Display Descriptor */ /* ------------------- ----------- ------------ ------------ ------------- -------------- ---------- */ { MTAB_XDV, 128 * 1024, NULL, "128K", &set_size, NULL, NULL }, { MTAB_XDV, 256 * 1024, NULL, "256K", &set_size, NULL, NULL }, { MTAB_XDV, 384 * 1024, NULL, "384K", &set_size, NULL, NULL }, { MTAB_XDV, 512 * 1024, NULL, "512K", &set_size, NULL, NULL }, { MTAB_XDV, 768 * 1024, NULL, "768K", &set_size, NULL, NULL }, { MTAB_XDV, 1024 * 1024, NULL, "1024K", &set_size, NULL, NULL }, { MTAB_XDV, 0, "IDLE", "IDLE", &sim_set_idle, &sim_show_idle, NULL }, { MTAB_XDV, 0, NULL, "NOIDLE", &sim_clr_idle, NULL, NULL }, { MTAB_XDV | MTAB_NMO, 0, "DUMP", "DUMPDEV", &set_dump, &show_dump, NULL }, { MTAB_XDV, 1, NULL, "DUMPCTL", &set_dump, NULL, NULL }, { MTAB_XDV | MTAB_NMO, 1, "STOPS", "STOP", &set_stops, &show_stops, NULL }, { MTAB_XDV, 0, NULL, "NOSTOP", &set_stops, NULL, NULL }, { MTAB_XDV | MTAB_NMO, 1, "EXEC", "EXEC", &set_exec, &show_exec, NULL }, { MTAB_XDV, 0, NULL, "NOEXEC", &set_exec, NULL, NULL }, { MTAB_XDV | MTAB_NMO, 0, "SPEED", NULL, NULL, &show_speed, NULL }, { 0 } }; /* Debugging trace list */ static DEBTAB cpu_deb [] = { { "INSTR", DEB_INSTR }, /* instructions */ { "DATA", DEB_MDATA }, /* memory data accesses */ { "FETCH", DEB_MFETCH }, /* memory instruction fetches */ { "REG", DEB_REG }, /* register values */ { "OPND", DEB_MOPND }, /* memory operand values */ { "EXEC", DEB_EXEC }, /* instruction execution states */ { "PSERV", DEB_PSERV }, /* process clock service events */ { NULL, 0 } }; /* Debugging stop list */ static DEBTAB cpu_stop [] = { { "LOOP", SS_LOOP }, /* stop on an infinite loop */ { "PAUSE", SS_PAUSE }, /* stop on a PAUS instruction */ { "UNDEF", SS_UNDEF }, /* stop on an undefined instruction */ { "UNIMPL", SS_UNIMPL }, /* stop on an unimplemented instruction */ { NULL, 0 } }; /* Device descriptor */ DEVICE cpu_dev = { "CPU", /* device name */ cpu_unit, /* unit array */ cpu_reg, /* register array */ cpu_mod, /* modifier array */ 1, /* number of units */ 8, /* address radix */ PA_WIDTH, /* address width */ 1, /* address increment */ 8, /* data radix */ 16, /* data width */ &mem_examine, /* examine routine */ &mem_deposit, /* deposit routine */ &cpu_reset, /* reset routine */ NULL, /* boot routine */ NULL, /* attach routine */ NULL, /* detach routine */ NULL, /* device information block pointer */ DEV_DEBUG, /* device flags */ 0, /* debug control flags */ cpu_deb, /* debug flag name array */ NULL, /* memory size change routine */ NULL /* logical device name */ }; /* CPU global SCP support routines */ /* Execute CPU instructions. This is the instruction decode routine for the HP 3000. It is called from the simulator control program to execute instructions in simulated memory, starting at the simulated program counter. It runs until the status to be returned is set to a value other than SCPE_OK. On entry, P points to the instruction to execute, and the "sim_switches" global contains any command-line switches included with the run command. On exit, P points at the next instruction to execute (or the current instruction, in the case of a simulator stop during a PAUS instruction or after the first of two stack operations). Execution is divided into four phases. First, the instruction prelude configures the simulation state to resume execution. This involves verifying that system power is on and there are no device conflicts (e.g., two devices with the same device number), initializing the I/O processor and channels, and setting the RUN switch if no other front panel switches are pressed. These actions accommodate reconfiguration of the I/O device settings and program counter while the simulator was stopped. The prelude also responds to one command-line switch: if "-B" is specified, the current set of simulation stop conditions is bypassed for the first instruction executed. This allows, e.g., a PAUS instruction to be bypassed or an unimplemented instruction trap to be taken. Second, the microcode abort mechanism is set up. Microcode aborts utilize the "setjmp/longjmp" mechanism to transfer control out of the instruction executors without returning through the call stack. This allows an instruction to be aborted part-way through execution when continuation is impossible, e.g., due to a memory access violation. It corresponds to direct microcode jumps out of the execution sequence and to the appropriate trap handlers. Third, the instruction execution loop decodes instructions and calls the individual executors in turn until a condition occurs that prevents further execution. Examples of such conditions includes execution of a HALT instruction, a user stop request (CTRL+E) from the simulation console, a recoverable device error (such as an improperly formatted tape image), a user-specified breakpoint, and a simulation stop condition (such as execution of an unimplemented instruction). The execution loop also polls for I/O events and device interrupts, and runs I/O channel cycles. During instruction execution, the CIR register contains the currently executing instruction, the NIR register contains the next instruction to execute, and the P register points to the memory location two instructions ahead of the current instruction. Fourth, the instruction postlude updates the simulation state in preparation for returning to the SCP command prompt. Devices that maintain an internal state different from their external state, such as the CPU process clock, are updated so that their internal and external states are fully consistent. This ensures that the state visible to the user during the simulation stop is correct. It also ensures that the program counter points correctly at the next instruction to execute upon resumption. If enabled, the simulator is idled when a PAUS instruction has been executed and no service requests for the multiplexer or selector channels are active. Execution of a PAUS instruction suspends the fetch-and-execute process until an interrupt occurs or the simulator is stopped and then resumed with a GO -B or RUN -B command. The HP 3000 is a microcoded machine. In hardware, the micromachine is always executing microinstructions, even when the CPU is "halted." The halt/run state is simply a flip-flop setting, reflected in bit 15 of the CPX2 register and the RUN light on the front panel, that determines whether the "halt-mode" or "run-mode" microprogram is currently executing. In simulation, the "cpu_micro_state" variable indicates the state of the micromachine, i.e., which section of the microcode it is executing, while CPX2 bit 15 indicates whether the macromachine is halted or running. The micromachine may be in one of four states: - running : the run-mode fetch-and-execute microcode is executing - paused : the run-mode PAUS instruction microcode is executing - waiting : the halt-mode cold load or dump microcode is executing - halted : the halt-mode front panel microcode is executing Simulation provides a variety of stop conditions that break instruction execution and return to the SCP prompt with the CPU still in run mode. These have no analog in hardware; the only way to stop the CPU is to press the HALT button on the front panel, which shifts the micromachine into halt-mode microcode execution. When any of these conditions occur, the micromachine state is set to "halted," but the CPX2 run flag is remains set unless the stop was caused by execution of a HALT instruction. Resuming execution with a STEP, CONT, GO, or RUN command proceeds as though the hardware RUN switch was pressed after a programmed halt. This provides the proper semantics for seamlessly stopping and restarting instruction execution. A microcode abort is performed by executing a "longjmp" to the abort handler, which is outside of and precedes the instruction execution loop. The value passed to "longjmp" is a 32-bit integer containing the Segment Transfer Table index of the trap handler in the lower word and an optional parameter in the upper word. Aborts are invoked by the MICRO_ABORT macro, which takes as its parameter a trap classification value, e.g.: MICRO_ABORT (trap_Privilege_Violation); MICRO_ABORT (trap_Integer_Zero_Divide); Some aborts require an additional parameter and must be invoked by the MICRO_ABORTP macro, which takes a trap classification value and a trap-specific value as parameters. The traps that require additional parameters are: MICRO_ABORTP (trap_CST_Violation, segment_number); MICRO_ABORTP (trap_STT_Violation, segment_number); MICRO_ABORTP (trap_CS_Absent, label/n/0); MICRO_ABORTP (trap_DS_Absent, DST_number); MICRO_ABORTP (trap_Uncallable, label); MICRO_ABORTP (trap_Trace, label/n/0); MICRO_ABORTP (trap_User, trap_number); trap_User is not usually called explicitly via MICRO_ABORTP; rather, MICRO_ABORT is used with one of the specific user-trap identifiers, e.g., trap_Integer_Overflow, trap_Float_Overflow, trap_Decimal_Overflow, etc., that supplies both the trap classification and the trap parameter value. In addition, user traps must be enabled by setting the T-bit in the status word. If the T bit is not set, a user trap sets the O-bit (overflow) in the status word and resumes execution with the next instruction instead of invoking the user trap handler. When an abort occurs, an equivalent PCAL to the appropriate STT entry is set up. Then execution drops into the instruction loop to execute the first instruction of the trap handler. When the instruction loop is exited, the CPU process clock and system clock registers are updated, the micromachine is halted, and control returns to SCP. Upon return, P points at the next instruction to execute, i.e., the instruction that will execute when the instruction loop is reentered. If the micromachine is paused, then P is reset to point to the PAUS instruction, which will be reexecuted when the routine is reentered. If it is running, then P is reset to point to the current instruction if the stop allows it to be rerun, or at the next instruction. Implementation notes: 1. While the Microsoft VC++ "setjmp" documentation says, "All variables (except register variables) accessible to the routine receiving control contain the values they had when longjmp was called," the ISO C99 standard says, "All accessible objects have values...as of the time the longjmp function was called, except that the values of objects of automatic storage duration that are local to the function containing the invocation of the corresponding setjmp macro that do not have volatile-qualified type and have been changed between the setjmp invocation and longjmp call are indeterminate." Therefore, the "device" and "status" variables are marked volatile to ensure that they are reloaded after a longjmp caused by a micrcode abort. 2. In hardware, the NEXT microcode order present at the end of each instruction transfers the NIR content to the CIR, reads the memory word at P into the NIR, and increments P. However, if an interrupt is present, then this action is omitted, and a microcode jump is performed to control store location 3, which then jumps to the microcoded interrupt handler. In simulation, the CIR/NIR/P update is performed before the next instruction is executed, rather than after the last instruction completes, so that interrupts are handled before updating. In addition, the NEXT action is modified in hardware if the NIR contains a stack instruction with a non-NOP B stackop. In this case, NEXT transfers the NIR content to the CIR, reads the memory word at P into the NIR, but does not increment P. Instead, the R bit of the status register is set to indicate that a B stackop is pending. When the NEXT at the completion of the A stackop is executed, the NIR and CIR are untouched, but P is incremented, and the R bit is cleared. This ensures that if an interrupt or trap occurs between the stackops, P will point correctly at the next instruction to be executed. In simulation, following the hardware would require testing the NIR for a non-NOP B stackop at every pass through the instruction execution loop. To avoid this, the NEXT simulation unilaterally increments P, rather than only when a B stackop is not present, and the stack instruction executor tests for the B stackop and sets the R bit there. However, by that time, P has already been incremented, so we decrement it there to return it to the correct value. 3. The System Halt trap has no handler. Instead, the simulator is halted, and control returns to the SCP prompt. 4. The trace display for a trap reports the parameter value supplied with the microcode abort. This is not necessarily the same as the parameter that is pushed on the stack for the trap handler. As some traps, e.g., trap_CST_Violation, can cause a System Halt, the placement of the trace call is dictated by the desire to report both the original trap and the System Halt trap, even though the placement results in the display of the incoming parameter value, rather than the stacked parameter value. 5. The execution trace (DEB_EXEC) match test is performed in two parts to display the register values both before and after the instruction execution. Consequently, the enable test is done before the register trace, and the disable test is done after. 6. The execution test (exec_test) is set FALSE even though execution tracing is not specified. This is done solely to reassure the compiler that the value is not clobbered by a longjmp call. 7. The set of debug trace flags is restored in the postlude (and therefore also saved in the instruction prelude) to ensure that a simulation stop that occurs while an execution trace is in progress does not exit with the flags set improperly. */ t_stat sim_instr (void) { static const char *const stack_formats [] = { /* stack register display formats, indexed by SR */ BOV_FORMAT " ", /* SR = 0 format */ BOV_FORMAT " A %06o, ", /* SR = 1 format */ BOV_FORMAT " A %06o, B %06o, ", /* SR = 2 format */ BOV_FORMAT " A %06o, B %06o, C %06o, ", /* SR = 3 format */ BOV_FORMAT " A %06o, B %06o, C %06o, D %06o, " /* SR = 4 format */ }; int abortval; HP_WORD label, parameter; TRAP_CLASS trap; t_bool exec_test; volatile uint32 debug_save; volatile HP_WORD device; volatile t_stat status = SCPE_OK; /* Instruction prelude */ debug_save = cpu_dev.dctrl; /* save the current set of debug flags for later restoration */ if (sim_switches & SWMASK ('B')) /* if a simulation stop bypass was requested */ cpu_stop_flags = SS_BYPASSED; /* then clear the stop flags for the first instruction */ else /* otherwise */ cpu_stop_flags = sim_stops; /* set the stops as indicated */ if (cpu_power_state == power_off) /* if system power is off */ status = STOP_POWER; /* then execution is not possible until restoration */ else if (hp_device_conflict ()) /* otherwise if device assignment is inconsistent */ status = SCPE_STOP; /* then inhibit execution */ else { /* otherwise */ device = iop_initialize (); /* initialize the IOP */ mpx_initialize (); /* and the multiplexer channel */ sel_initialize (); /* and the selector channel */ if ((CPX2 & CPX2_IRQ_SET) == 0) /* if no halt-mode interrupt is present */ CPX2 |= cpx2_RUNSWCH; /* then assume a RUN command via STEP/CONT */ } /* Microcode abort processor */ abortval = setjmp (cpu_save_env); /* set the microcode abort handler */ if (abortval) { /* if a microcode abort occurred */ trap = TRAP (abortval); /* then get the trap classification */ parameter = PARAM (abortval); /* and the optional parameter */ label = TO_LABEL (LABEL_IRQ, trap); /* form the label from the STT number */ dprintf (cpu_dev, DEB_INSTR, BOV_FORMAT "%s trap%s\n", PBANK, P - 1 & R_MASK, parameter, trap_name [trap], (trap == trap_User && !(STA & STATUS_T) ? " (disabled)" : "")); switch (trap) { /* dispatch on the trap classification */ case trap_None: /* trap_None should never occur */ case trap_System_Halt: CNTR = SR; /* copy the stack register to the counter */ cpu_flush (); /* and flush the TOS registers to memory */ RA = parameter; /* set RA to the parameter (system halt condition) */ CPX2 = CPX2 & ~cpx2_RUN | cpx2_SYSHALT; /* halt the CPU and set the system halt flag */ status = STOP_SYSHALT; /* and report the system halt condition */ label = 0; /* there is no trap handler for a system halt */ break; case trap_CST_Violation: if (STT_SEGMENT (parameter) <= ISR_SEGMENT) /* if the trap occurred in segment 1 */ MICRO_ABORT (trap_SysHalt_CSTV_1); /* then the failure is fatal */ /* fall through 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 through 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 through 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 through 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 through into trap_Stack_Overflow */ case trap_Stack_Overflow: /* this trap executes on the ICS */ if (CPX1 & cpx1_ICSFLAG) /* so if the trap occurred while on the ICS */ MICRO_ABORT (trap_SysHalt_Overflow_ICS); /* then the failure is fatal */ cpu_setup_ics_irq (irq_Trap, trap); /* otherwise, set up the ICS */ break; /* and then the trap handler */ case trap_Power_On: /* this trap executes on the ICS */ cpu_setup_ics_irq (irq_Trap, trap); /* so set it up */ cpu_power_state = power_on; /* and return to the power-on state */ if (CPX2 & cpx2_INHPFARS) /* if auto-restart is inhibited */ status = STOP_ARSINH; /* then exit and wait for a manual restart */ break; } /* all cases are handled */ if (label != 0) { /* if the trap handler is to be called */ STA = STATUS_M; /* then clear the status and enter privileged mode */ SM = SM + 1 & R_MASK; /* increment the stack pointer */ cpu_write_memory (stack, SM, parameter); /* and push the parameter on the stack */ X = CIR; /* save the current instruction for restarting */ cpu_call_procedure (label, 0); /* set up PB, P, PL, and STA to call the procedure */ cpu_base_changed = TRUE; /* one or more base registers have changed */ } sim_interval = sim_interval - 1; /* count the execution cycle that aborted */ } /* Instruction loop */ while (status == SCPE_OK) { /* execute until simulator status prevents continuation */ if (sim_interval <= 0) { /* if an event timeout has expired */ status = sim_process_event (); /* then call the event service */ if (status != SCPE_OK) /* if the service failed */ break; /* then abort execution and report the failure */ } if (sel_request) /* if a selector channel request is pending */ sel_service (1); /* then service it */ if (mpx_request_set) /* if a multiplexer channel request is pending */ mpx_service (1); /* then service it */ if (iop_interrupt_request_set /* if a hardware interrupt request is pending */ && STA & STATUS_I /* and interrupts are enabled */ && CIR != SED_1) /* and not deferred by a SED 1 instruction */ device = iop_poll (); /* then poll to acknowledge the request */ if (cpu_micro_state == running) /* if the micromachine is running */ if (CPX1 & CPX1_IRQ_SET /* then if a run-mode interrupt is pending */ && cpu_power_state != power_failing) /* and power is not currently failing */ cpu_run_mode_interrupt (device); /* then service it */ else if (sim_brk_summ /* otherwise if a breakpoint exists */ && sim_brk_test (TO_PA (PBANK, P - 1 & LA_MASK), /* at the next location */ BP_EXEC)) { /* to execute */ status = STOP_BRKPNT; /* then stop the simulation */ sim_interval = sim_interval + 1; /* and don't count the cycle */ } else { /* otherwise execute the next instruction */ if (DPRINTING (cpu_dev, DEB_EXEC | DEB_REG)) { /* if execution or register tracing is enabled */ if (cpu_dev.dctrl & DEB_EXEC) /* then if tracing execution */ if (STA & STATUS_R) /* then if the right-hand stack op is pending */ exec_test = /* then the execution test succeeds if */ (CIR << STACKOP_A_SHIFT /* the right-hand stack op */ & STACKOP_A_MASK /* matches the test criteria */ & exec_mask) == exec_match; /* for the left-hand stack op value */ else /* otherwise */ exec_test = /* then the execution test succeeds if */ (NIR & exec_mask) == exec_match; /* the next instruction matches the test criteria */ else /* otherwise */ exec_test = FALSE; /* there is no execution test */ if (cpu_dev.dctrl & DEB_EXEC /* if execution tracing is enabled */ && cpu_dev.dctrl != DEB_ALL /* and is currently inactive */ && exec_test) { /* and the matching test succeeds */ debug_save = cpu_dev.dctrl; /* then save the current trace flag set */ cpu_dev.dctrl = DEB_ALL; /* and turn on full tracing */ } if (cpu_dev.dctrl & DEB_REG) { /* if register tracing is enabled */ hp_debug (&cpu_dev, DEB_REG, /* then output the active TOS registers */ stack_formats [SR], SBANK, SM, SR, RA, RB, RC, RD); fprintf (sim_deb, "X %06o, %s\n", /* output the index and status registers */ X, fmt_status (STA)); if (cpu_base_changed) { /* if the base registers have been altered */ hp_debug (&cpu_dev, DEB_REG, /* then output the base register values */ BOV_FORMAT " PB %06o, PL %06o, DL %06o, DB %06o, Q %06o, Z %06o\n", DBANK, 0, STATUS_CS (STA), PB, PL, DL, DB, Q, Z); cpu_base_changed = FALSE; /* clear the base registers changed flag */ } } if (cpu_dev.dctrl & DEB_EXEC /* if execution tracing is enabled */ && cpu_dev.dctrl == DEB_ALL /* and is currently active */ && ! exec_test) { /* and the matching test fails */ cpu_dev.dctrl = debug_save; /* then restore the saved debug flag set */ hp_debug (&cpu_dev, DEB_EXEC, /* and add a separator to the trace log */ "*****************\n"); } } if (!(STA & STATUS_R)) { /* (NEXT) if the right-hand stack op is not pending */ CIR = NIR; /* then update the current instruction */ cpu_read_memory (fetch, P, &NIR); /* and load the next instruction */ } P = P + 1 & R_MASK; /* point to the following instruction */ if (DPRINTING (cpu_dev, DEB_INSTR)) { /* if instruction tracing is enabled */ sim_eval [0] = CIR; /* then save the instruction that will be executed */ sim_eval [1] = NIR; /* and the following word for evaluation */ hp_debug (&cpu_dev, DEB_INSTR, BOV_FORMAT, /* print the address and the instruction opcode */ PBANK, P - 2 & R_MASK, CIR); /* as an octal value */ if (fprint_cpu (sim_deb, sim_eval, 0, SIM_SW_STOP) == SCPE_ARG) /* print the mnemonic; if that fails */ fprint_val (sim_deb, sim_eval [0], cpu_dev.dradix, /* then print the numeric */ cpu_dev.dwidth, PV_RZRO); /* value again */ fputc ('\n', sim_deb); /* end the trace with a newline */ } status = machine_instruction (); /* execute one machine instruction */ cpu_stop_flags = sim_stops; /* reset the stop flags as indicated */ } else if (cpu_micro_state == paused) { /* otherwise if the micromachine is paused */ if (CPX1 & CPX1_IRQ_SET) /* then if a run-mode interrupt is pending */ cpu_run_mode_interrupt (device); /* then service it */ else if (sim_idle_enab /* otherwise if idling is enabled */ && ! sel_request && mpx_request_set == 0) /* and there are no channel requests pending */ sim_idle (TMR_PCLK, FALSE); /* then idle the simulator */ } else if (CPX2 & CPX2_IRQ_SET) /* otherwise if a halt-mode interrupt is pending */ status = halt_mode_interrupt (device); /* then service it */ sim_interval = sim_interval - 1; /* count the execution cycle */ } /* and continue with the instruction loop */ /* Instruction postlude */ cpu_dev.dctrl = debug_save; /* restore the flag set */ cpu_update_pclk (); /* update the process clock */ clk_update_counter (); /* and system clock counters */ if (cpu_micro_state == paused) /* if the micromachine is paused */ P = P - 2 & R_MASK; /* then set P to point to the PAUS instruction */ else if (cpu_micro_state == running) /* otherwise if it is running */ if (status <= STOP_RERUN) /* then if the instruction will be rerun when resumed */ P = P - 2 & R_MASK; /* then set P to point to it */ else /* otherwise */ P = P - 1 & R_MASK; /* set P to point to the next instruction */ cpu_micro_state = halted; /* halt the micromachine */ if (cpu_power_state == power_failing /* if power is failing */ && status == STOP_HALT) /* and we executed a HALT instruction */ cpu_power_state = power_off; /* then power will be off when we return */ dprintf (cpu_dev, cpu_dev.dctrl, BOV_FORMAT "simulation stop: %s\n", PBANK, P, STA, status >= SCPE_BASE ? sim_error_text (status) : sim_stop_messages [status]); return status; /* return the reason for the stop */ } /* Execute the LOAD and DUMP commands. This command processing routine implements the cold load and cold dump commands. The syntax is: LOAD { } DUMP { } The is a number that is deposited into the SWCH register before invoking the CPU cold load or cold dump facility. The CPU radix is used to interpret the number; it defaults to octal. If the number is omitted, the SWCH register value is not altered before loading or dumping. On entry, the "arg" parameter is "Cold_Load" for a LOAD command and "Cold_Dump" for a DUMP command, and "buf" points at the remainder of the command line. If characters exist on the command line, they are parsed, converted to a numeric value, and stored in the SWCH register. Then the CPU's cold load/dump routine is called to set up the CPU state. Finally, the CPU is started to begin the requested action. Implementation notes: 1. The RUN command uses the RU_CONT argument instead of RU_RUN so that the run_cmd SCP routine will not reset all devices before entering the instruction executor. The halt mode interrupt handlers for cold load and cold dump reset the simulator as appropriate for their commands (i.e., the CPU and all I/O devices, or just the I/O devices, respectively). */ t_stat cpu_cold_cmd (int32 arg, CONST char *buf) { const char *cptr; char gbuf [CBUFSIZE]; t_stat status; HP_WORD value; if (*buf != '\0') { /* if more characters exist on the command line */ cptr = get_glyph (buf, gbuf, 0); /* then get the next glyph */ if (*cptr != '\0') /* if that does not exhaust the input */ return SCPE_2MARG; /* then report that there are too many arguments */ value = (HP_WORD) get_uint (gbuf, cpu_dev.dradix, /* get the parameter value */ D16_UMAX, &status); if (status == SCPE_OK) /* if a valid number was present */ SWCH = value; /* then set it into the switch register */ else /* otherwise */ return status; /* return the error status */ } else if (arg == Cold_Dump) /* otherwise if no dump value was given */ SWCH = dump_control; /* then use the system control panel presets */ cpu_front_panel (SWCH, (PANEL_TYPE) arg); /* set up the cold load or dump microcode */ return run_cmd (RU_CONT, buf); /* execute the halt-mode routine */ } /* Execute the POWER commands. This command processing routine is called to initiate a power failure or power restoration. The "cptr" parameter points to the power option keyword; the "arg" parameter is not used. The routine processes commands of the form: POWER { FAIL | OFF | DOWN } POWER { RESTORE | ON | UP } In simulation, the "cpu_power_state" global variable indicates the current state of system power. The simulator starts in the power_on state. The POWER FAIL command moves from the power_on to the power_failing state if the CPU is running, or to the power_off state if it is not. Execution of a HALT in the power_failing state moves to the power_off state. The POWER RESTORE command moves from the power_off to the power_returning state if the CPU is running, or to the power_on state if it is not. Execution of the power-on trap moves from the power_returning to the power_on state. The POWER FAIL and POWER RESTORE commands are only valid in the power_on and power_off states, respectively; otherwise, they print "Command not allowed." The four enumeration values model the states of the PON (power on) and PFW (power-fail warning) hardware signals, as follows: PON PFW State Simulator Action --- --- --------------- ---------------------------- 1 0 power on executing normally 1 1 power failing executing with cpx1_PFINTR 0 1 power off will not execute 0 0 power returning executing with trap_Power_On In microcode, the power-fail routine writes the current value of the CPX2 register to the word following the last word of the ICS. This value is used by the power-on routine to decide if the CPU was running (cpx2_RUN bit is set) or halted at the time of the power failure. A power failure is indicated by setting the cpx1_PFINTR bit in the CPX1 register; this causes an interrupt to the power-failure routine in the operating system, which performs an orderly shutdown followed by a programmed HALT to wait for power to die. When power is restored, the power-on trap (trap_Power_On) is set up, and then, if the PF/ARS switch is in the "enable" position, the trap is taken, which restarts the operating system and any I/O that was in progress when power failed. If the switch is in the "disable" position, the CPU remains halted, and the trap is taken when the RUN button is pressed. The POWER commands are entered at the SCP prompt. If the machine was running at the time of power failure, execution is resumed automatically to execute the power-fail or power-restore OS routines. If the machine was halted when the POWER commands were entered, the machine remains halted -- just the power state changes. Implementation notes: 1. In hardware, when the power fail interrupt is serviced, the microcode sets the PWR INH flip-flop, which locks out the RUN switch and inhibits all other halt-mode (CPX2) and run-mode (CPX1) interrupts until the CPU is reset. This ensures that the software power-fail interrupt handler executes unimpeded. In simulation, a POWER FAIL command with the CPU running sets the power-fail interrupt bit in the CPX1 register and resumes execution in the power_on state. When the interrupt is detected by the "cpu_run_mode_interrupt" routine, the state is changed to power_failing. In this state, run-mode interrupts are not recognized. Halt-mode interrupts need no special handling, as the power state is changed to power_off when the CPU halts. Therefore, the CPU is never in a "halted-and-waiting-for-power-to-fade-away" state. 2. The RUN command uses the RU_CONT argument instead of RU_RUN so that the run_cmd SCP routine will not reset all devices before entering the instruction executor. The halt-mode interrupt handlers for cold load and cold dump reset the simulator as appropriate for their commands (i.e., the CPU and all I/O devices, or just the I/O devices, respectively). 3. In order to set up the power-on trap, this routine presses the RUN button and continues the simulation. After the trap is set up in the "sim_instr" routine, the halt-mode interrupt handler checks the PF/ARS switch and stops simulation if auto-restart is disabled. */ t_stat cpu_power_cmd (int32 arg, CONST char *cptr) { static CTAB options [] = { { "FAIL", NULL, power_failing }, { "RESTORE", NULL, power_returning }, { "OFF", NULL, power_failing }, { "ON", NULL, power_returning }, { "DOWN", NULL, power_failing }, { "UP", NULL, power_returning }, { NULL } }; char gbuf [CBUFSIZE]; CTAB *ctptr; HP_WORD zi, failure_cpx2; t_stat status; if (cptr == NULL || *cptr == '\0') /* if there is no option word */ return SCPE_2FARG; /* then report a missing argument */ cptr = get_glyph (cptr, gbuf, 0); /* parse (and upshift) the option specified */ ctptr = find_ctab (options, gbuf); /* and look it up in the option table */ if (ctptr == NULL) /* if the option is not valid */ status = SCPE_ARG; /* then report a bad argument */ else if (*cptr != '\0') /* otherwise if something follows the option */ return SCPE_2MARG; /* then report too many arguments */ else if (ctptr->arg == power_failing) /* otherwise if a power-fail option was given */ if (cpu_power_state != power_on) /* but the CPU power is not on */ status = SCPE_NOFNC; /* then the command is not allowed */ else { /* otherwise the failure is valid */ iop_assert_PFWARN (); /* so send a power-fail warning to all devices */ cpu_read_memory (absolute, ICS_Z, &zi); /* get the ICS stack limit */ cpu_write_memory (absolute, zi + 1, CPX2); /* and save the CPX2 value in the following word */ if (CPX2 & cpx2_RUN) { /* if the CPU is currently running */ CPX1 |= cpx1_PFINTR; /* then set the power-fail interrupt */ CPX2 |= cpx2_RUNSWCH; /* and assume a RUN command */ status = run_cmd (RU_CONT, cptr); /* and continue execution */ } else { /* otherwise the CPU is currently halted */ cpu_power_state = power_off; /* so remain halted in the "power is off" state */ status = SCPE_OK; /* and return command success */ } } else if (ctptr->arg == power_returning) /* otherwise if a power-restoration option was given */ if (cpu_power_state != power_off) /* but the CPU power is not off */ status = SCPE_NOFNC; /* then the command is not allowed */ else { /* otherwise the restoration is valid */ reset_all_p (0); /* so reset all devices to their power on states */ cpu_read_memory (absolute, ICS_Z, &zi); /* get the ICS stack limit */ cpu_read_memory (absolute, zi + 1, &failure_cpx2); /* and get the value of CPX2 at the time of failure */ cpu_write_memory (absolute, zi + 1, CPX2); /* and replace it with the current CPX2 value */ if (failure_cpx2 & cpx2_RUN) { /* if the CPU was running at the time of power failure */ cpu_power_state = power_returning; /* then move to the "power is returning" state */ CPX2 |= cpx2_RUNSWCH; /* and assume a RUN command */ status = run_cmd (RU_CONT, cptr); /* and continue execution */ } else { /* otherwise the CPU was halted when power failed */ cpu_power_state = power_on; /* so remain halted in the "power is on" state */ status = SCPE_OK; /* and return command success */ } } else /* otherwise a valid option has no handler */ status = SCPE_IERR; /* so report an internal error */ return status; /* return the operation status */ } /* CPU global utility routines */ /* Process a run-mode interrupt. This routine is called when one or more of the interrupt request bits are set in the CPX1 register. The highest-priority request is identified and cleared, the interrupt classification and parameter are established, and the associated interrupt handler is set up for execution. On return, the CPU has been configured and is ready to execute the first instruction in the handler. On entry, the routine first checks for an external interrupt alone; this is done to improve performance, as this is the most common case. If some other interrupt is requested, or if multiple interrupts are requested, the CPX1 register is scanned from MSB to LSB to identify the request. Implementation notes: 1. In hardware, halting the CPU while a PAUS instruction is executing leaves P pointing to the following instruction, which is executed when the RUN button is pressed. In simulation, this action occurs only when execution is resumed with the "-B" option to bypass the pause. Otherwise, resuming (or stepping) continues with the PAUS instruction. If an interrupt occurs while PAUS is executing, the interrupt handler will return to the instruction following the PAUS, as though HALT and RUN had been pressed. This occurs because the P register normally points two instructions past the instruction currently executing. When an interrupt occurs, P is decremented to point at the instruction after the current instruction, which is the correct point of return after the interrupt service routine completes. When the simulator is stopped, P is backed up to point at the next instruction to execute. In the case of a PAUS instruction, the "next instruction" is the same PAUS instruction. When simulation resumes, the PAUS instruction is fetched into the NIR, and P is incremented. If no interrupt is pending, the main instruction execution loop copies the NIR into the CIR, prefetches the instruction following the PAUS into the NIR, and increments P again, so that it points two instructions beyond the current instruction. At this point, everything is set up properly as before the simulation stop. However, if an interrupt is pending when simulation resumes, this routine is called before the NIR-to-CIR operation is performed, so P still points one instruction beyond the PAUS. Stacking the usual P - 1 value would 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, when resuming from a simulator stop, the SS_PAUSE_RESUMED flag is set in the "cpu_stop_flags" variable by the "halt_mode_interrupt" routine if a PAUS instruction is in the NIR after reloading. If an interrupt is pending, this routine will be entered with the flag set, and we then increment P so that it is correctly set to point two instructions beyond the PAUS before handling the interrupt. If an interrupt is not pending on resumption, the P adjustment is performed as described above, and P will be set properly when the next interrupt occurs. A special case occurs when resuming into a PAUS from a simulator stop if a higher-priority interrupt occurs immediately after handling a pending lower-priority interrupt. In this case, this routine will be entered a second time before the instruction execution loop performs the NIR-to-CIR operation. Although the PAUS is still in the CIR, we must not increment P a second time, because the instruction now being interrupted is not the PAUS but is the first instruction of the lower-priority interrupt routine that never had a chance to execute. Wo do this by clearing the SS_PAUSE_RESUMED flag after the first increment. */ void cpu_run_mode_interrupt (HP_WORD device_number) { HP_WORD request_set, request_count, parameter; IRQ_CLASS class; if (cpu_stop_flags & SS_PAUSE_RESUMED) { /* if we are resuming into a PAUS instruction */ P = P + 1 & R_MASK; /* then set the return to the instruction following */ cpu_stop_flags &= ~SS_PAUSE_RESUMED; /* and clear the flag in case of back-to-back interrupts */ } cpu_micro_state = running; /* the micromachine may be paused but is no longer */ request_set = CPX1 & CPX1_IRQ_SET; /* get the set of active interrupt requests */ if (request_set == cpx1_EXTINTR) { /* if only an external request is present */ class = irq_External; /* (the most common case) then set the class */ parameter = device_number; /* and set the parameter to the device number */ } else { /* otherwise scan for the class */ request_count = 0; /* where CPX1.1 through CPX1.9 */ request_set = D16_SIGN; /* correspond to IRQ classes 1-9 */ while ((CPX1 & request_set) == 0) { /* scan from left to right for the first request */ request_count = request_count + 1; /* while incrementing the request number */ request_set = request_set >> 1; /* and shifting the current request bit */ } class = (IRQ_CLASS) request_count; /* set the class from the request count */ if (class == irq_Integer_Overflow) { /* if an integer overflow occurred */ parameter = 1; /* then set the parameter to 1 */ STA &= ~STATUS_O; /* and clear the overflow flag */ } else if (class == irq_External) /* otherwise if an external interrupt occurred */ parameter = device_number; /* then set the parameter to the device number */ else if (class == irq_Module) { /* otherwise if the class is a module interrupt */ parameter = UPPER_BYTE (MOD); /* then the parameter is the module number */ MOD = 0; /* clear the register to prevent a second interrupt */ } else if (class == irq_Power_Fail) { /* otherwise if a power fail interrupt occurred */ parameter = TO_LABEL (LABEL_IRQ, class); /* then the parameter is the label */ cpu_power_state = power_failing; /* and system power is now failing */ } else /* otherwise the parameter */ parameter = TO_LABEL (LABEL_IRQ, class); /* is the label */ } CPX1 &= ~request_set; /* clear the associated CPX request bit */ dprintf (cpu_dev, DEB_INSTR, BOV_FORMAT "%s interrupt\n", PBANK, P - 1 & R_MASK, parameter, interrupt_name [class]); cpu_setup_irq_handler (class, parameter); /* set up the entry into the interrupt handler */ return; } /* Set up a front panel operation. This routine sets the SWCH register to the supplied value and then sets the appropriate bit in the CPX2 register. This will cause a halt-mode interrupt when simulated execution is resumed. Implementation notes: 1. We do this here to avoid having to export the registers and CPX values globally. */ void cpu_front_panel (HP_WORD switch_reg, PANEL_TYPE request) { SWCH = switch_reg; /* set the SWCH register value */ switch (request) { /* dispatch on the request type */ case Run: /* a run request */ CPX2 |= cpx2_RUNSWCH; /* set the RUN switch */ break; case Cold_Load: /* a cold load request */ CPX2 |= cpx2_LOADSWCH; /* set the LOAD switch */ break; case Cold_Dump: /* a cold dump request */ CPX2 |= cpx2_DUMPSWCH; /* set the DUMP switch */ break; } /* all cases are handled */ return; } /* Update the process clock. If the process clock is currently calibrated, then the service interval is actually ten times the hardware period of 1 millisecond. This provides sufficient event service call spacing to allow idling to work. To present the correct value when the process clock is read, this routine is called to increment the count by an amount proportional to the fraction of the service interval that has elapsed. In addition, it is called by the CPU instruction postlude, so that the PCLK register will have the correct value if it is examined from the SCP command prompt. */ void cpu_update_pclk (void) { int32 elapsed, ticks; if (cpu_is_calibrated) { /* if the process clock is calibrated */ elapsed = cpu_unit [0].wait /* then the elapsed time is the original wait time */ - sim_activate_time (&cpu_unit [0]); /* less the time remaining before the next service */ ticks = /* the adjustment is */ (elapsed * PCLK_MULTIPLIER) / cpu_unit [0].wait /* the elapsed fraction of the multiplier */ - (PCLK_MULTIPLIER - pclk_increment); /* less the amount of any adjustment already made */ PCLK = PCLK + ticks & R_MASK; /* update the process clock counter with rollover */ pclk_increment = pclk_increment - ticks; /* and reduce the amount remaining to add at service */ } return; } /* CPU global instruction execution routines */ /* Push the stack down. This routine implements the PUSH micro-order to create space on the stack for a new value. On return, the new value may be stored in the RA register. If the SR register indicates that all of the TOS registers are in use, then the RD register is freed by performing a queue down. Then the values in the TOS registers are shifted down, freeing the RA register. Finally, SR is incremented in preparation for the store. */ void cpu_push (void) { if (SR == 4) /* if all TOS registers are full */ cpu_queue_down (); /* then move the RD value to memory */ RD = RC; /* shift */ RC = RB; /* the register */ RB = RA; /* values down */ SR = SR + 1; /* increment the register-in-use count */ return; } /* Pop the stack up. This routine implements the POP micro-order to delete the top-of-stack value. On entry, if the SR register indicates that all of the TOS registers are empty, then if decrementing the SM register would move it below the DB register value, and the CPU is not in privileged mode, then a Stack Underflow trap is taken. Otherwise, the stack memory pointer is decremented. If one or more values exist in the TOS registers, the values are shifted up, deleting the previous value in the RA register, and SR is decremented. */ void cpu_pop (void) { if (SR == 0) { /* if the TOS registers are empty */ if (SM <= DB && NPRV) /* then if SM isn't above DB and the mode is non-privileged */ MICRO_ABORT (trap_Stack_Underflow); /* then trap with a Stack Underflow */ SM = SM - 1 & R_MASK; /* decrement the stack memory register */ } else { /* otherwise at least one TOS register is occupied */ RA = RB; /* so shift */ RB = RC; /* the register */ RC = RD; /* values up */ SR = SR - 1; /* decrement the register-in-use count */ } return; } /* Queue a value from memory up to the register file. This routine implements the QUP micro-order to move the value at the top of the memory stack into the bottom of the TOS register file. There must be a free TOS register when this routine is called. On entry, if decrementing the SM register would move it below the DB register value, and the CPU is not in privileged mode, then a Stack Underflow trap is taken. Otherwise, the value pointed to by SM is read into the first unused TOS register, SM is decremented to account for the removed value, and SR is incremented to account for the new TOS register in use. Implementation notes: 1. SR must be less than 4 on entry, so that TR [SR] is the first unused TOS register. 2. SM and SR must not be modified within the call to cpu_read_memory. For example, SR++ cannot be passed as a parameter. */ void cpu_queue_up (void) { if (SM <= DB && NPRV) /* if SM isn't above DB and the mode is non-privileged */ MICRO_ABORT (trap_Stack_Underflow); /* then trap with a Stack Underflow */ else { /* otherwise */ cpu_read_memory (stack, SM, &TR [SR]); /* read the value from memory into a TOS register */ SM = SM - 1 & R_MASK; /* decrement the stack memory register */ SR = SR + 1; /* and increment the register-in-use count */ } return; } /* Queue a value from the register file down to memory. This routine implements the QDWN micro-order to move the value at the bottom of the TOS register file into the top of the memory stack. There must be a TOS register in use when this routine is called. On entry, if incrementing the SM register would move it above the Z register value, then a Stack Overflow trap is taken. Otherwise, SM is incremented to account for the new value written, SR is decremented to account for the TOS register removed from use, and the value in that TOS register is written into memory at the top of the memory stack. Implementation notes: 1. SR must be greater than 0 on entry, so that TR [SR - 1] is the last TOS register in use. 2. SM and SR must not be modified within the call to cpu_write_memory. For example, SR-- cannot be passed as a parameter. */ void cpu_queue_down (void) { if (SM >= Z) /* if SM isn't below Z */ MICRO_ABORT (trap_Stack_Overflow); /* then trap with a Stack Overflow */ SM = SM + 1 & R_MASK; /* increment the stack memory register */ SR = SR - 1; /* and decrement the register-in-use count */ cpu_write_memory (stack, SM, TR [SR]); /* write the value from a TOS register to memory */ return; } /* Flush the register file. This routine implements the PSHA microcode subroutine that writes the values of all TOS registers in use to the memory stack. As each value is written, the SM register is incremented and the SR register is decremented. On return, the SR register value will be zero. The routine does not check for stack overflow. */ void cpu_flush (void) { while (SR > 0) { /* while one or more registers are in use */ SM = SM + 1 & R_MASK; /* increment the stack memory register */ SR = SR - 1; /* and decrement the register-in-use count */ cpu_write_memory (stack, SM, TR [SR]); /* write the value from a TOS register to memory */ } return; } /* Adjust SR until it reaches a specified value. This routine implements the SRP1-SRP4 microcode subroutines that adjust the stack until the prescribed number (1-4) of TOS registers are occupied. It performs queue ups, i.e., moves values from the top of the memory stack to the bottom of the register file, until the specified SR value is reached. Stack underflow is checked after the all of the values have been moved. The routine assumes that at least one value must be moved. Implementation notes: 1. SR must be greater than 0 on entry, so that TR [SR - 1] is the last TOS register in use. 2. SM and SR must not be modified within the call to cpu_read_memory. For example, SR++ cannot be passed as a parameter. 3. The cpu_queue_up routine isn't used, as that routine checks for a stack underflow after each word is moved rather than only after the last word. */ void cpu_adjust_sr (uint32 target) { do { cpu_read_memory (stack, SM, &TR [SR]); /* read the value from memory into a TOS register */ SM = SM - 1 & R_MASK; /* decrement the stack memory register */ SR = SR + 1; /* and increment the register-in-use count */ } while (SR < target); /* queue up until the requested number of registers are in use */ if (SM <= DB && NPRV) /* if SM isn't above DB, or the mode is non-privileged */ MICRO_ABORT (trap_Stack_Underflow); /* then trap with a Stack Underflow */ return; } /* Write a stack marker to memory. This routine implements the STMK microcode subroutine that writes a four-word marker to the stack. The format of the marker is as follows: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | X register value | [Q - 3] X +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | PB-relative return address | [Q - 2] P + 1 - PB +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | Status register value | [Q - 1] STA +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | Delta Q value | [Q - 0] S - Q +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ After the values are written, the Q register is set to point to the marker. This routine is always entered with SR = 0, i.e., with the TOS registers flushed to the memory stack. It does not check for stack overflow. Implementation notes: 1. The PB-relative return address points to the instruction after the point of the call. Conceptually, this is location P + 1, but because the CPU uses a two-instruction prefetch, the location is actually P - 1. */ void cpu_mark_stack (void) { SM = SM + 4 & R_MASK; /* adjust the stack pointer */ cpu_write_memory (stack, SM - 3, X); /* push the index register */ cpu_write_memory (stack, SM - 2, P - 1 - PB & LA_MASK); /* and delta P */ cpu_write_memory (stack, SM - 1, STA); /* and the status register */ cpu_write_memory (stack, SM - 0, SM - Q & LA_MASK); /* and delta Q */ Q = SM; /* set Q to point to the new stack marker */ return; } /* Calculate an effective memory address. This routine calculates the effective address for a memory reference or branch instruction. On entry, "mode_disp" contains the mode, displacement, index, and indirect fields of the instruction, "classification" and "offset" point to variables to receive the corresponding values, and "selector" points to a variable to receive the byte selection ("upper" or "lower") for byte- addressable instructions or is NULL for word-addressable instructions. On exit, "classification" is set to the memory access classification, "offset" is set to the address offset within the memory bank implied by the classification, and "selector" is set to indicate the byte referenced if the pointer is non-NULL. The mode and displacement fields of the instruction encode an address relative to one of the base registers P, DB, Q, or S, as follows: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | memory op | X | I | mode and displacement | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 | 0 | P+ displacement 0-255 | +---+---+---+---+---+---+---+---+---+---+ | 0 | 1 | P- displacement 0-255 | +---+---+---+---+---+---+---+---+---+---+ | 1 | 0 | DB+ displacement 0-255 | +---+---+---+---+---+---+---+---+---+---+ | 1 | 1 | 0 | Q+ displacement 0-127 | +---+---+---+---+---+---+---+---+---+---+ | 1 | 1 | 1 | 0 | Q- displacement 0-63 | +---+---+---+---+---+---+---+---+---+---+ | 1 | 1 | 1 | 1 | S- displacement 0-63 | +---+---+---+---+---+---+---+---+---+---+ The displacement encoded in the instruction is an unsigned value that is added to or subtracted from the indicated base register. If the X and I fields are both 0, the addressing is direct. If the X field is 1, the addressing is indexed. If the I field is 1, the addressing is indirect. If both fields are 1, the addressing is indirect indexed, with indirection performed before indexing. To improve execution speed in hardware, a preadder is implemented that sums the offset contained in certain bits of the CIR with the index register (if enabled). The primary use is to save a microinstruction cycle during memory reference instructions, which must add a base register, the offset in the CIR, and potentially the X register (either directly or shifted left or right by one place for LDD/STD or LDB/STB, respectively). The preadder also serves to hold other counts obtained from the CIR, e.g., shift counts, although in these cases, the addition function is not used. This routine simulates the preadder as part of the effective address calculation. The calculations employed for word addressing are: Direct word addressing: ea = PBANK.(P + displacement) ea = DBANK.(DB + displacement) ea = SBANK.(Q,S + displacement) Direct indexed word addressing: ea = PBANK.(P + displacement + X) ea = DBANK.(DB + displacement + X) ea = SBANK.(Q,S + displacement + X) Indirect word addressing: ea = PBANK.(P + displacement + M [PBANK.(P + displacement)]) ea = DBANK.(DB + M [DBANK.(DB + displacement)]) ea = DBANK.(DB + M [SBANK.(Q,S + displacement)]) Indirect indexed word addressing: ea = PBANK.(P + displacement + M [PBANK.(P + displacement)] + X) ea = DBANK.(DB + M [DBANK.(DB + displacement)] + X) ea = DBANK.(DB + M [SBANK.(Q,S + displacement)] + X) The indirect cell contains either a self-relative, P-relative address or a DB-relative address, even for S or Q-relative modes. Indirect branches with DB/Q/S-relative mode are offsets from PB, not DB. The effective address calculations employed for byte addressing are: Direct byte addressing: ea = DBANK.(DB + displacement).byte [0] ea = SBANK.(Q,S + displacement).byte [0] Direct indexed byte addressing: ea = DBANK.(DB + displacement + X / 2).byte [X & 1] ea = SBANK.(Q,S + displacement + X / 2).byte [X & 1] Indirect byte addressing: ea,I = DBANK.(DB + M [DBANK.(DB + displacement)] / 2).byte [cell & 1] ea,I = DBANK.(DB + M [SBANK.(Q,S + displacement)] / 2).byte [cell & 1] Indirect indexed byte addressing: ea,I = DBANK.(DB + (M [DBANK.(DB + displacement)] + X) / 2).byte [cell + X & 1] ea,I = DBANK.(DB + (M [SBANK.(Q,S + displacement)] + X) / 2).byte [cell + X & 1] For all modes, the displacement is a word address, whereas the indirect cell and index register contain byte offsets. For direct addressing, the byte selected is byte 0. For all other modes, the byte selected is the byte at (offset & 1), where the offset is the index register value, the indirect cell value, or the sum of the two. Byte offsets into data segments present problems, in that negative offsets are permitted (to access the DL-to-DB area), but there are not enough bits to represent all locations unambiguously in the potential -32K to +32K word offset range. Therefore, a byte offset with bit 0 = 1 can represent either a positive or negative word offset from DB, depending on the interpretation. The HP 3000 adopts the convention that if the address resulting from a positive-offset interpretation does not fall within the DL-to-S range, then 32K is added to the address, effectively changing the interpretation from a positive to a negative offset. If this new address does not fall within the DL-to-S range, a Bounds Violation trap occurs if the mode is non-privileged. The reinterpretation as a negative offset is performed only if the CPU is not in split-stack mode (where either DBANK is different from SBANK, or DB does not lie between DL and Z), as extra data segments do not permit negative-DB addressing. Reinterpretation is also not used for code segments, as negative offsets from PB are not permitted. Implementation notes: 1. On entry, the program counter points to the instruction following the next instruction (i.e., the NIR location + 1). However, P-relative offsets are calculated from the current instruction (CIR location). Therefore, we decrement P by two before adding the offset. In hardware, P-relative addresses obtained from the preadder are offset from P + 1, whereas P-relative addresses obtained by summing with P-register values obtained directly from the S-Bus are offset from P + 2. This is because the P-register increment that occurs as part of a NEXT micro-order is coincident with the R-Bus and S-Bus register loads; both operations occur when the NXT+1 signal asserts. Therefore, the microcode handling P-relative memory reference address resolution subtracts one to get the P value corresponding to the CIR, whereas branches on overflow, carry, etc. subtract two. 2. If the mode is indirect, this routine handles bounds checks and TOS register accesses on the initial address. 3. The System Reference Manual states that byte offsets are interpreted as negative if the effective address does not lie between DL and Z. However, the Series II microcode actually uses DL and S for the limits. 4. This routine calculates the effective address for the memory address instructions. These instructions always bounds-check their accesses, and data and stack accesses are mapped to the TOS registers if the effective address lies between SM and SM + SR within the stack bank. */ void cpu_ea (HP_WORD mode_disp, ACCESS_CLASS *classification, HP_WORD *offset, BYTE_SELECTOR *selector) { HP_WORD base, displacement; ACCESS_CLASS class; switch ((mode_disp & MODE_MASK) >> MODE_SHIFT) { /* dispatch on the addressing mode */ case 000: case 001: case 002: case 003: /* positive P-relative displacement */ base = P - 2 + (mode_disp & DISPL_255_MASK); /* add the displacement to the base */ class = program_checked; /* and classify as a program reference */ break; case 004: case 005: case 006: case 007: /* negative P-relative displacement */ base = P - 2 - (mode_disp & DISPL_255_MASK); /* subtract the displacement from the base */ class = program_checked; /* and classify as a program reference */ break; case 010: case 011: case 012: case 013: /* positive DB-relative displacement */ base = DB + (mode_disp & DISPL_255_MASK); /* add the displacement to the base */ class = data_mapped_checked; /* and classify as a data reference */ break; case 014: case 015: /* positive Q-relative displacement */ base = Q + (mode_disp & DISPL_127_MASK); /* add the displacement to the base */ class = stack_checked; /* and classify as a stack reference */ break; case 016: /* negative Q-relative displacement */ base = Q - (mode_disp & DISPL_63_MASK); /* subtract the displacement from the base */ class = stack_checked; /* and classify as a stack reference */ break; case 017: /* negative S-relative displacement */ base = SM + SR - (mode_disp & DISPL_63_MASK); /* subtract the displacement from the base */ class = stack_checked; /* and classify as a stack reference */ break; } /* all cases are handled */ if (!(mode_disp & I_FLAG_BIT_5)) /* if the mode is direct */ displacement = 0; /* then there's no displacement */ else { /* otherwise the mode is indirect */ cpu_read_memory (class, base & LA_MASK, &displacement); /* so get the displacement value */ if ((CIR & BR_MASK) == BR_DBQS_I) { /* if this a DB/Q/S-relative indirect BR instruction */ base = PB; /* then PB is the base for the displacement */ class = program_checked; /* reclassify as a program reference */ } else if (class != program_checked) { /* otherwise if it is a data or stack reference */ base = DB; /* then DB is the base for the displacement */ class = data_mapped_checked; /* reclassify as a data reference */ } /* otherwise, this is a program reference */ } /* which is self-referential */ if ((CIR & LSDX_MASK) == LDD_X /* if the mode */ || (CIR & LSDX_MASK) == STD_X) /* is double-word indexed */ displacement = displacement + X * 2 & DV_MASK; /* then add the doubled index to the displacement */ else if (mode_disp & X_FLAG) /* otherwise if the mode is indexed */ displacement = displacement + X & DV_MASK; /* then add the index to the displacement */ if (selector == NULL) /* if a word address is requested */ base = base + displacement; /* then add in the word displacement */ else if ((mode_disp & (X_FLAG | I_FLAG_BIT_5)) == 0) /* otherwise if a direct byte address is requested */ *selector = upper; /* then it references the upper byte */ else { /* otherwise an indexed or indirect byte address is requested */ if (displacement & 1) /* so if the byte displacement is odd */ *selector = lower; /* then the lower byte was requested */ else /* otherwise it is even */ *selector = upper; /* and the upper byte was requested */ base = base + (displacement >> 1) & LA_MASK; /* convert the displacement from byte to word and add */ if (DBANK == SBANK && DL <= DB && DB <= Z /* if not in split-stack mode */ && (base < DL || base > SM + SR)) /* and the word address is out of range */ base = base ^ D16_SIGN; /* then add 32K to swap the offset polarity */ } *offset = base & LA_MASK; /* set the */ *classification = class; /* return values */ return; } /* Convert a data- or program-relative byte address to a word effective address. The supplied byte offset from DB or PB is converted to a memory address, bounds-checked if requested, and returned. If the supplied block length is not zero, the converted address is assumed to be the starting address of a block, and an ending address, based on the block length, is calculated and bounds-checked if requested. If either address lies outside of the segment associated with the access class, a Bounds Violation trap occurs, unless a privileged data access is requested. The "class" parameter indicates whether the offset is from DB or PB and must be "data", "data_checked", "program", or "program_checked". No other access classes are supported. In particular, "data_mapped" and "data_mapped_checked" are not allowed, as callers of this routine never map accesses to the TOS registers. Byte offsets into data segments present problems, in that negative offsets are permitted (to access the DL-to-DB area), but there are not enough bits to represent all locations unambiguously in the potential -32K to +32K word offset range. Therefore, a byte offset with bit 0 = 1 can represent either a positive or negative word offset from DB, depending on the interpretation. The HP 3000 adopts the convention that if the address resulting from a positive-offset interpretation does not fall within the DL-to-S range, then 32K is added to the address, effectively changing the interpretation from a positive to a negative offset. If this new address does not fall within the DL-to-S range, a Bounds Violation trap occurs if the mode is non-privileged. The reinterpretation as a negative offset is performed only if the CPU is not in split-stack mode (where either DBANK is different from SBANK, or DB does not lie between DL and Z), as extra data segments do not permit negative-DB addressing. Reinterpretation is also not used for code segments, as negative offsets from PB are not permitted. Implementation notes: 1. This routine implements the DBBC microcode subroutine. */ uint32 cpu_byte_ea (ACCESS_CLASS class, uint32 byte_offset, uint32 block_length) { uint32 starting_word, ending_word, increment; if (block_length & D16_SIGN) /* if the block length is negative */ increment = 0177777; /* then the memory increment is negative also */ else /* otherwise */ increment = 1; /* the increment is positive */ if (class == program || class == program_checked) { /* if this is a program access */ starting_word = PB + (byte_offset >> 1) & LA_MASK; /* then determine the starting word address */ if (class == program_checked /* if checking is requested */ && starting_word < PB || starting_word > PL) /* and the starting address is out of range */ MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */ if (block_length != 0) { /* if a block length was supplied */ ending_word = /* then determine the ending address */ starting_word + ((block_length - increment + (byte_offset & 1)) >> 1) & LA_MASK; if (class == program_checked /* if checking is requested */ && ending_word < PB || ending_word > PL) /* and the ending address is out of range */ MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */ } } else { /* otherwise this is a data address */ starting_word = DB + (byte_offset >> 1) & LA_MASK; /* so determine the starting word address */ if (DBANK == SBANK && DL <= DB && DB <= Z /* if not in split-stack mode */ && (starting_word < DL || starting_word > SM)) { /* and the word address is out of range */ starting_word = starting_word ^ D16_SIGN; /* then add 32K and try again */ if (class == data_checked && NPRV /* if checking is requested and non-privileged */ && (starting_word < DL || starting_word > SM)) /* and still out of range */ MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */ } if (block_length != 0) { /* if a block length was supplied */ ending_word = /* then determine the ending word address */ starting_word + ((block_length - increment + (byte_offset & 1)) >> 1) & LA_MASK; if (class == data_checked && NPRV /* if checking is requested and non-privileged */ && (ending_word < DL || ending_word > SM)) /* and the address is out of range */ MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */ } } return starting_word; /* return the starting word address */ } /* Set up the entry into an interrupt handler. This routine prepares the CPU state to execute an interrupt handling procedure. On entry, "class" is the classification of the current interrupt, and "parameter" is the parameter associated with the interrupt. On exit, the stack has been set up correctly, and the PB, P, PL, and status registers have been set up for entry into the interrupt procedure. Run-mode interrupts are classified as external or internal and ICS or non-ICS. External interrupts are those originating with the device controllers, and internal interrupts are conditions detected by the microcode (e.g., a bounds violation or arithmetic overflow). ICS interrupts execute their handlers on the system's Interrupt Control Stack. Non-ICS interrupts execute on the user's stack. Of the run-mode interrupts, the External, System Parity Error, Address Parity Error, Data Parity Error, and Module interrupts execute on the ICS. All other interrupts execute on the user's stack. The routine begins by determining whether an ICS or non-ICS interrupt is indicated. The appropriate stack is established, and the stack marker is written to preserve the state of the interrupted routine. The label of the handler procedure is obtained, and then the procedure designated by the label is set up. On return, the first instruction of the handler is ready to execute. Implementation notes: 1. This routine implements various execution paths through the microcode labeled as INT0 through INT7. 2. This routine is also called directly by the IXIT instruction executor if an external interrupt is pending. This is handled as an external interrupt but is classified differently so that the teardown and rebuild of the stack may be avoided to improve performance. */ void cpu_setup_irq_handler (IRQ_CLASS class, HP_WORD parameter) { HP_WORD label; if (class == irq_External || class == irq_IXIT) { /* if entry is for an external interrupt */ if (class == irq_External) /* then if it was detected during normal execution */ cpu_setup_ics_irq (class, 0); /* then set it up on the ICS */ else /* otherwise it was detected during IXIT */ SM = Q + 2 & R_MASK; /* so the ICS is already set up */ DBANK = 0; /* all handlers are in bank 0 */ STA = STATUS_M | STATUS_I; /* enter privileged mode with interrupts enabled */ cpu_read_memory (stack, parameter * 4 + 2, &DB); /* read the DB value */ cpu_read_memory (stack, parameter * 4 + 1, &label); /* and the procedure label from the DRT */ } else if (class >= irq_System_Parity /* otherwise if entry is for */ && class <= irq_Power_Fail) { /* another ICS interrupt */ cpu_setup_ics_irq (class, 0); /* then set it up on the ICS */ label = TO_LABEL (LABEL_IRQ, class); /* form the label for the specified classification */ STA = STATUS_M; /* clear status and enter privileged mode */ } else { /* otherwise entry is for a non-ICS interrupt */ if (class == irq_Integer_Overflow) /* if this is an integer overflow interrupt */ label = TO_LABEL (LABEL_IRQ, trap_User); /* then form the label for a user trap */ else /* otherwise form the label */ label = TO_LABEL (LABEL_IRQ, class); /* for the specified classification */ cpu_flush (); /* flush the TOS registers to memory */ cpu_mark_stack (); /* and write a stack marker */ STA = STATUS_M; /* clear status and enter privileged mode */ } SM = SM + 1 & R_MASK; /* increment the stack pointer */ cpu_write_memory (stack, SM, parameter); /* and push the parameter on the stack */ X = CIR; /* save the CIR in the index register */ cpu_call_procedure (label, 0); /* set up to call the interrupt handling procedure */ return; } /* Set up an interrupt on the Interrupt Control Stack. This routine prepares the Interrupt Control Stack (ICS) to support interrupt processing. It is called from the run-time interrupt routine for ICS interrupts, the microcode abort routine for ICS traps, and from the DISP and PSEB instruction executors before entering the dispatcher. On entry, "class" is the interrupt classification, and, if the class is "irq_Trap", then "trap" is the trap classification. The trap classification is ignored for interrupts, including the dispatcher start interrupt. Unless entry is for a Cold Load trap, the routine begins by writing a six-word stack marker. This special ICS marker extends the standard marker by adding the DBANK and DB values as follows: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | X register value | [Q - 3] X +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | PB-relative return address | [Q - 2] P + 1 - PB +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | Status register value | [Q - 1] STA +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | D | Delta Q value | [Q - 0] S - Q +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | DB-Bank value | [Q + 1] DBANK +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | DB value | [Q + 2] DB +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: D = the dispatcher was interrupted After the values are written, the Q register is set to point to the marker. The stack bank register is then cleared, as the ICS is always located in memory bank 0. If the interrupt or trap occurred while executing on the ICS, and the dispatcher was running at the time, the "dispatcher running" bit in the CPX1 register is cleared, and the D-bit is set in the delta-Q value word of the stack marker. This bit will be used during interrupt exit to decide whether to restart the dispatcher. If the CPU was executing on the user's stack, the "ICS flag" bit in CPX1 is set, the Q register is reset to point at the permanent dispatcher stack marker established by the operating system, and the Z register is reset to the stack limit established by the OS for the ICS; the values are obtained from reserved memory locations 5 and 6, respectively. The ICS DB value is read from the ICS global area that precedes the dispatcher stack marker and is used to write the stack-DB-relative S value back to the global area. Finally, the stack pointer is set to point just above the stack marker. Implementation notes: 1. This routine implements various execution paths through the microcode labeled as INT1 through INT5. */ void cpu_setup_ics_irq (IRQ_CLASS class, TRAP_CLASS trap) { HP_WORD delta_q, stack_db; if (class != irq_Trap /* if this is not */ || trap != trap_Cold_Load && trap != trap_Power_On) { /* a cold load or power on trap entry */ cpu_flush (); /* then flush the TOS registers to memory */ cpu_mark_stack (); /* and write a four-word stack marker */ cpu_write_memory (stack, SM + 1 & LA_MASK, DBANK); /* add DBANK and DB to the stack */ cpu_write_memory (stack, SM + 2 & LA_MASK, DB); /* to form a six-word ICS marker */ } SBANK = 0; /* the ICS is always located in bank 0 */ if (CPX1 & cpx1_ICSFLAG) { /* if execution is currently on the ICS */ if (CPX1 & cpx1_DISPFLAG) { /* then if the dispatcher was interrupted */ CPX1 &= ~cpx1_DISPFLAG; /* then clear the dispatcher flag */ cpu_read_memory (stack, Q, &delta_q); /* get the delta Q value from the stack marker */ cpu_write_memory (stack, Q, delta_q | STMK_D); /* and set the dispatcher-interrupted flag */ } } else { /* otherwise execution is on the user's stack */ CPX1 |= cpx1_ICSFLAG; /* so set the ICS flag */ cpu_read_memory (stack, ICS_Q, &Q); /* set Q = QI */ cpu_read_memory (stack, ICS_Z, &Z); /* set Z = ZI */ cpu_read_memory (stack, Q - 4 & LA_MASK, /* read the stack DB value */ &stack_db); cpu_write_memory (stack, Q - 6 & LA_MASK, /* write the stack-DB-relative S value */ SM + 2 - stack_db & DV_MASK); /* which is meaningless for a cold load or power on */ SR = 0; /* invalidate the stack registers for a cold load */ DL = D16_UMAX; /* and set the data limit */ } SM = Q + 2 & R_MASK; /* set S above the stack marker */ return; } /* Set up a code segment. This routine is called to set up a code segment in preparation for calling or exiting a procedure located in a segment different from the currently executing segment. On entry, "label" indicates the segment number containing the procedure. On exit, the new status register value and the first word of the Code Segment Table entry are returned to the variables pointed to by "status" and "entry_0", respectively. The routine begins by reading the CST pointer. The CST is split into two parts: a base table, and an extension table. The table to use is determined by the requested segment number. Segment numbers 0 and 192, corresponding to the first entries of the two tables, are reserved and cause a CST Violation trap if specified. The CST entry corresponding to the segment number is examined to set the program bank, base, and limit registers (the segment length stored in the table is number of quad-words, which must be multiplied by four to get the size in words). The new status register value is set up and returned, along with the first word of the CST entry. Implementation notes: 1. This routine implements the microcode SSEG subroutine. 2. Passing -1 as a parameter to trap_CST_Violation ensures that the segment number >= 2 check will pass and the trap handler will be invoked. 3. The Series II microcode sets PBANK and PB unilaterally but sets PL only if the code segment is not absent. An absent segment entry contains the disc address in words 3 and 4 instead of the bank address and base address, so PBANK and PB will contain invalid values in this case. It is not clear why the microcode avoids setting PL; the microinstruction in question also sets Flag 2, so conditioning PL may be just a side effect. In any case, we duplicate the firmware behavior here. 4. This routine is only used locally, but we leave it as a global entry to support future firmware extensions that may need to call it. */ void cpu_setup_code_segment (HP_WORD label, HP_WORD *status, HP_WORD *entry_0) { HP_WORD cst_pointer, cst_size, cst_entry, cst_bank, segment_number, entry_number; segment_number = STT_SEGMENT (label); /* isolate the segment number from the label */ if (segment_number < CST_RESERVED) { /* if the target segment is in the base table */ cpu_read_memory (absolute, CSTB_POINTER, &cst_pointer); /* then read the CST base pointer */ entry_number = segment_number; /* and set the entry number */ } else { /* otherwise it is in the extension table */ cpu_read_memory (absolute, CSTX_POINTER, &cst_pointer); /* so read the CST extension pointer */ entry_number = segment_number - CST_RESERVED; /* and set the entry number */ } if (entry_number == 0) /* segment numbers 0 and 192 do not exist */ MICRO_ABORTP (trap_CST_Violation, -1); /* so trap for a violation if either is specified */ cpu_read_memory (absolute, cst_pointer, &cst_size); /* read the table size */ if (entry_number > cst_size) /* if the entry is outside of the table */ MICRO_ABORTP (trap_CST_Violation, entry_number); /* then trap for a violation */ cst_entry = cst_pointer + entry_number * 4; /* get the address of the target CST entry */ cpu_read_memory (absolute, cst_entry, entry_0); /* get the first word of the entry */ cpu_write_memory (absolute, cst_entry, *entry_0 | CST_R_BIT); /* and set the segment reference bit */ cpu_read_memory (absolute, cst_entry + 2, &cst_bank); /* read the bank address word */ PBANK = cst_bank & CST_BANK_MASK; /* and mask to just the bank number */ cpu_read_memory (absolute, cst_entry + 3, &PB); /* read the segment's base address */ PL = (*entry_0 & CST_SEGLEN_MASK) * 4 - 1; /* set PL to the segment length - 1 */ *status = STA & ~LABEL_SEGMENT_MASK | segment_number; /* set the segment number in the new status word */ if (*entry_0 & CST_M_BIT) /* if the segment executes in privileged mode */ *status |= STATUS_M; /* then set up to enter privileged mode */ if (! (*entry_0 & CST_A_BIT)) /* if the segment is not absent */ PL = PL + PB; /* then set the segment limit */ return; } /* Set up a data segment. This routine is called to set up a data segment for access. It is called by the MDS, MFDS, and MTDS instruction executors to obtain the bank and offset of specified segments from the Data Segment Table. On entry, "segment_number" indicates the number of the desired data segment. On exit, the memory bank number and offset of the data segment base are returned to the variables pointed to by "bank" and "address", respectively. The routine begins by reading the DST pointer. Segment number 0, corresponding to the first entry of the table, is reserved and causes a DST Violation trap if specified. The DST entry corresponding to the segment number is examined to obtain the bank and base address. If the segment is absent, a Data Segment Absent trap is taken. Otherwise, the bank and address values are returned. Implementation notes: 1. This routine implements the microcode DSEG subroutine. */ void cpu_setup_data_segment (HP_WORD segment_number, HP_WORD *bank, HP_WORD *address) { HP_WORD dst_pointer, dst_size, dst_entry, entry_0; cpu_read_memory (absolute, DST_POINTER, &dst_pointer); /* read the DST base pointer */ if (segment_number == 0) /* segment number 0 does not exist */ MICRO_ABORT (trap_DST_Violation); /* so trap for a violation if it is specified */ cpu_read_memory (absolute, dst_pointer, &dst_size); /* read the table size */ if (segment_number > dst_size) /* if the entry is outside of the table */ MICRO_ABORT (trap_DST_Violation); /* then trap for a violation */ dst_entry = dst_pointer + segment_number * 4; /* get the address of the target DST entry */ cpu_read_memory (absolute, dst_entry, &entry_0); /* get the first word of the entry */ cpu_write_memory (absolute, dst_entry, entry_0 | DST_R_BIT); /* and set the segment reference bit */ if (entry_0 & DST_A_BIT) /* if the segment is absent */ MICRO_ABORTP (trap_DS_Absent, segment_number); /* then trap for an absentee violation */ cpu_read_memory (absolute, dst_entry + 2, bank); /* read the segment bank number */ cpu_read_memory (absolute, dst_entry + 3, address); /* and base address */ *bank = *bank & DST_BANK_MASK; /* mask off the reserved bits */ return; /* before returning to the caller */ } /* Call a procedure. This routine sets up the PB, P, PL, and status registers to enter a procedure. It is called by the PCAL instruction executor and by the interrupt and trap routines to set up the handler procedures. On entry, "label" contains an external program label indicating the segment number and Segment Transfer Table entry number describing the procedure, or a local program label indicating the starting address of the procedure, and "offset" contains an offset to be added to the starting address. On exit, the registers are set up for execution to resume with the first instruction of the procedure. If the label is a local label, the PB-relative address is obtained from the label and stored in the P register, and the Next Instruction Register is loaded with the first instruction of the procedure. If the label is external, the code segment referenced by the label is set up. If the "trace" or "absent" bits are set, the corresponding trap is taken. Otherwise, the Segment Transfer Table length is read, and the STT entry number is validated; if it references a location outside of the table, a STT violation trap is taken. Otherwise, the valid STT entry is examined. If the target procedure is not in the designated code segment or is uncallable if not in privileged mode, the appropriate traps are taken. If the STT entry contains a local label, it is used to set up the P register and NIR as above. The "offset" parameter is used only by the XBR instruction executor. The PCAL executor and the interrupt handlers pass an offset of zero to begin execution at the first instruction of the designated procedure. Implementation notes: 1. This routine implements the microcode PCL3 and PCL5 subroutines. */ void cpu_call_procedure (HP_WORD label, HP_WORD offset) { HP_WORD new_status, new_label, new_p, cst_entry, stt_size, stt_entry; new_status = STA; /* save the status for a local label */ if (label & LABEL_EXTERNAL) { /* if the label is non-local */ cpu_setup_code_segment (label, &new_status, &cst_entry); /* then set up the corresponding code segment */ stt_entry = STT_NUMBER (label); /* get the STT entry number from the label */ if (cst_entry & (CST_A_BIT | CST_T_BIT)) { /* if the code segment is absent or being traced */ STA = new_status; /* then set the new status before trapping */ cpu_mark_stack (); /* and write a stack marker to memory */ if (cst_entry & CST_A_BIT) /* if the code segment is absent */ MICRO_ABORTP (trap_CS_Absent, label); /* then trap to load it */ else /* otherwise */ MICRO_ABORTP (trap_Trace, label); /* trap to trace it */ } cpu_read_memory (program_checked, PL, &stt_size); /* read the table size */ if (stt_entry > STT_LENGTH (stt_size)) /* if the entry is outside of the table */ MICRO_ABORTP (trap_STT_Violation, new_status); /* then trap for a violation */ cpu_read_memory (program_checked, PL - stt_entry, &new_label); /* read the label from the STT */ if (new_label & LABEL_EXTERNAL) /* if the procedure is not in the target segment */ MICRO_ABORTP (trap_STT_Violation, new_status); /* then trap for a violation */ if ((new_label & LABEL_UNCALLABLE) && NPRV) /* if the procedure is uncallable in the current mode */ MICRO_ABORTP (trap_Uncallable, label); /* then trap for a violation */ if (stt_entry == 0) /* if the STT number is zero in an external label */ label = 0; /* then the starting address is PB */ else /* otherwise */ label = new_label; /* the PB offset is contained in the new label */ cpu_base_changed = TRUE; /* the program base registers have changed for tracing */ } new_p = PB + (label + offset & LABEL_ADDRESS_MASK); /* get the procedure starting address */ cpu_read_memory (fetch_checked, new_p, &NIR); /* check the bounds and get the next instruction */ P = new_p + 1 & R_MASK; /* the bounds are valid, so set the new P value */ STA = new_status; /* set the new status value */ return; } /* Return from a procedure. This routine sets up the P, Q, SM, and status registers to return from a procedure. It is called by the EXIT and IXIT instruction executors and by the cpu_start_dispatcher routine to enter the dispatcher. On entry, "new_q" and "new_sm" contain the new values for the Q and SM registers that unwind the stack. The "parameter" value is used only if a Trace or Code Segment Absent trap is taken. For EXIT, the parameter is the stack adjustment value (the N field). For IXIT, the parameter is zero. On exit, the registers are set up for execution to resume with the first instruction after the procedure call or interrupt. The routine begins by reloading register values from the stack marker. The stack marker format is: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | X register value | [Q - 3] X +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | T | M | PB-relative return address | [Q - 2] P + 1 - PB +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | Status register value | [Q - 1] STA +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | Delta Q value | [Q - 0] S - Q +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: T = a trace or control-Y interrupt is pending M = the code segment is physically mapped The T and M bits are set by the operating system, if applicable, after the stack marker was originally written. Stack underflow and overflow are checked, and privilege changes are validated. If the return will be to a different code segment, it is set up. Finally, the new P, Q, SM, and status register values are loaded, and NIR is loaded with the first instruction after the return. Implementation notes: 1. This routine implements the microcode EXI1 subroutine. 2. We pass a temporary status to cpu_setup_code_segment because it forms the returned new status from the current STA register value. But for EXIT and IXIT, the new status comes from the stack marker, which already has the segment number to which we're returning, and which must not be altered. 3. The NEXT action is modified when the R-bit is set in the status word being restored. This occurs when an interrupt occurred between the two stackops of a stack instruction. The main instruction loop does not alter CIR in this case, so we must set it up here. */ void cpu_exit_procedure (HP_WORD new_q, HP_WORD new_sm, HP_WORD parameter) { HP_WORD temp_status, new_status, new_p, cst_entry; SM = Q; /* delete any local values from the stack */ if (new_q > Z || new_sm > Z) /* if either the new Q or SM exceed the stack limit */ MICRO_ABORT (trap_Stack_Overflow); /* then trap with a stack overflow */ cpu_read_memory (stack, Q - 1, &new_status); /* read the new status value from the stack marker */ if ((CIR & EXIT_MASK) == EXIT /* if an EXIT instruction is executing */ && (new_q < DB || new_sm < DB) /* and either new Q or new S are below the data base */ && (new_status & STATUS_M) == 0) /* and the new mode is non-privileged */ MICRO_ABORT (trap_Stack_Underflow); /* then trap with a stack underflow */ cpu_read_memory (stack, Q - 2, &new_p); /* read the PB-relative return value from the stack marker */ if (NPRV /* if currently in user mode */ && ((new_status & STATUS_M) /* and returning to privileged mode */ || (new_status & STATUS_I) != (STA & STATUS_I))) /* or attempting to change interrupt state */ MICRO_ABORT (trap_Privilege_Violation); /* then trap with a privilege violation */ STA &= ~STATUS_I; /* turn off external interrupts */ cpu_read_memory (stack, Q - 3, &X); /* read the new X value from the stack marker */ if (STATUS_CS (new_status) != STATUS_CS (STA)) { /* if returning to a different segment */ cpu_setup_code_segment (new_status, &temp_status, &cst_entry); /* then set up the new segment */ if (NPRV && (temp_status & STATUS_M)) /* if in user mode now and returning to a privileged segment */ MICRO_ABORT (trap_Privilege_Violation); /* then trap with a privilege violation */ if (new_p & STMK_T) /* if the new code segment is being traced */ MICRO_ABORTP (trap_Trace, parameter); /* then trap to trace it */ if (cst_entry & CST_A_BIT) /* if the code segment is absent */ MICRO_ABORTP (trap_CS_Absent, parameter); /* then trap to load it */ } new_p = PB + (new_p & STMK_RTN_ADDR); /* convert the relative address to absolute */ cpu_read_memory (fetch_checked, new_p, &NIR); /* check the bounds and get the next instruction */ P = new_p + 1 & R_MASK; /* the bounds are valid, so set the new P value */ STA = new_status; /* set the new status value */ Q = new_q; /* and the stack marker */ SM = new_sm; /* and the stack pointer */ if (STA & STATUS_R) { /* if a right-hand stack op is pending */ CIR = NIR; /* then set the current instruction */ cpu_read_memory (fetch, P, &NIR); /* and load the next instruction */ } cpu_base_changed = TRUE; /* one or more base registers have changed for tracing */ return; } /* Start the dispatcher. This routine is called by the DISP and PSEB instruction executors to start the dispatcher and by the IXIT executor to restart the dispatcher if it was interrupted. On entry, the ICS has been set up. The "dispatcher running" bit in the CPX1 register is set, Q is set to point at the permanent dispatcher stack marker on the ICS, the dispatcher's DBANK and DB registers are loaded, and an "exit procedure" is performed to return to the dispatcher. */ void cpu_start_dispatcher (void) { dprintf (cpu_dev, DEB_INSTR, BOV_FORMAT "%s interrupt\n", PBANK, P - 1 & R_MASK, 0, interrupt_name [irq_Dispatch]); CPX1 |= cpx1_DISPFLAG; /* set the "dispatcher is running" flag */ cpu_read_memory (absolute, ICS_Q, &Q); /* set Q to point to the dispatcher's stack marker */ cpu_write_memory (absolute, Q, 0); /* and clear the stack marker delta Q value */ cpu_read_memory (stack, Q + 1 & LA_MASK, &DBANK); /* load the dispatcher's data bank */ cpu_read_memory (stack, Q + 2 & LA_MASK, &DB); /* and data base registers */ cpu_exit_procedure (Q, Q + 2, 0); /* return to the dispatcher */ return; } /* CPU local SCP support routines */ /* Service the CPU process clock. The process clock is used by the operating system to time per-process CPU usage. It is always enabled and running, although the PCLK register only increments if the CPU is not executing on the ICS. The process clock may be calibrated to wall-clock time or set to real time. In hardware, the process clock has a one-millisecond period. Setting the mode to real time schedules clock events based on the number of event ticks equivalent to one millisecond. Because the simulator is an order of magnitude faster than the hardware, this short period precludes idling. In the calibrated mode, the short period would still preclude idling. Therefore, in this mode, the clock is scheduled with a ten-millisecond service time, and the PCLK register is incremented by ten for each event service. To present the correct value when PCLK is read, the "cpu_update_pclk" routine is called by the RCLK instruction executor to increment the count by an amount proportional to the fraction of the service interval that has elapsed. In addition, that routine is called by the CPU instruction postlude, so that PCLK will have the correct value if it is examined from the SCP command prompt. The simulation console is normally hosted by, and therefore polled by, the ATC on channel 0. If the console is not hosted by the ATC, due to a SET ATC DISABLED command, the process clock assumes polling control over the console. Implementation notes: 1. If the process clock is calibrated, the system clock and ATC poll services are synchronized with the process clock service to improve idling. 2. The current CPU speed, expressed as a multiple of the hardware speed, is calculated for each service entry. It may be displayed at the SCP prompt with the SHOW CPU SPEED command. The speed is only representative when the process clock is calibrated, and the CPU is not executing a PAUS instruction (which suspends the normal fetch/execute instruction cycle). */ static t_stat cpu_service (UNIT *uptr) { const t_bool ics_exec = (CPX1 & cpx1_ICSFLAG) != 0; /* TRUE if the CPU is executing on the ICS */ t_stat status; dprintf (cpu_dev, DEB_PSERV, "Process clock service entered on the %s\n", (ics_exec ? "ICS" : "user stack")); if (!ics_exec) /* if the CPU is not executing on the ICS */ PCLK = PCLK + pclk_increment & R_MASK; /* then increment the process clock */ cpu_is_calibrated = (uptr->flags & UNIT_CALTIME) != 0; /* TRUE if the process clock is calibrated */ if (cpu_is_calibrated) { /* if the process clock is tracking wall-clock time */ uptr->wait = sim_rtcn_calb (PCLK_RATE, TMR_PCLK); /* then calibrate it */ pclk_increment = PCLK_MULTIPLIER; /* and set the increment to the multiplier */ } else { /* otherwise */ uptr->wait = PCLK_PERIOD; /* set the delay as an event tick count */ pclk_increment = 1; /* and set the increment without multiplying */ } sim_activate (uptr, uptr->wait); /* reschedule the timer */ cpu_speed = uptr->wait / (PCLK_PERIOD * pclk_increment); /* calculate the current CPU speed multiplier */ if (atc_is_polling == FALSE) { /* if the ATC is not polling for the simulation console */ status = sim_poll_kbd (); /* then we must poll for a console interrupt */ if (status < SCPE_KFLAG) /* if the result is not a character */ return status; /* then return the resulting status */ } return SCPE_OK; /* return the success of the service */ } /* Reset the CPU. This routine is called for a RESET, RESET CPU, or BOOT CPU command. It is the simulation equivalent of the CPURESET signal, which is asserted by the front panel LOAD switch. In hardware, this causes a microcode restart in addition to clearing certain registers. If this is the first call after simulator startup, the initial memory array is allocated, the default CPU and memory size configuration is set, and the SCP-required program counter pointer is set to point to the REG array element corresponding to the P register. If this is a power-on reset ("RESET -P"), the process clock calibrated timer is initialized, and any LOAD or DUMP request in progress is cleared. The micromachine is halted, the process clock is scheduled, and several registers are cleared. Implementation notes: 1. Setting the sim_PC value at run time accommodates changes in the register order automatically. A fixed setting runs the risk of it not being updated if a change in the register order is made. */ static t_stat cpu_reset (DEVICE *dptr) { if (sim_PC == NULL) { /* if this is the first call after simulator start */ if (mem_initialize (PA_MAX)) { set_model (&cpu_unit [0], UNIT_SERIES_III, /* so establish the initial CPU model */ NULL, NULL); for (sim_PC = dptr->registers; /* find the P register entry */ sim_PC->loc != &P && sim_PC->loc != NULL; /* in the register array */ sim_PC++); /* for the SCP interface */ if (sim_PC == NULL) /* if the P register entry is not present */ return SCPE_NXREG; /* then there is a serious problem! */ } else /* otherwise memory initialization failed */ return SCPE_MEM; /* so report the error and abort the simulation */ } if (sim_switches & SWMASK ('P')) { /* if this is a power-on reset */ sim_rtcn_init (cpu_unit [0].wait, TMR_PCLK); /* then initialize the process clock timer */ CPX2 &= ~(cpx2_LOADSWCH | cpx2_DUMPSWCH); /* and clear any cold load or dump request */ } cpu_micro_state = halted; /* halt the micromachine */ sim_activate_abs (&cpu_unit [0], cpu_unit [0].wait); /* and schedule the process clock */ PCLK = 0; /* clear the process clock counter */ CPX1 = 0; /* and all run-mode signals */ CPX2 &= ~(cpx2_RUN | cpx2_SYSHALT); /* and the run and system halt flip-flops */ if (cpu_unit [0].flags & UNIT_PFARS) /* if the PF/ARS switch position is ENBL */ CPX2 &= ~cpx2_INHPFARS; /* then clear the auto-restart inhibit flag */ else /* otherwise the position is DSBL */ CPX2 |= cpx2_INHPFARS; /* so set the auto-restart inhibit flag */ CNTR = SR; /* copy the stack register to the counter */ cpu_flush (); /* and flush the TOS registers to memory */ return SCPE_OK; /* indicate that the reset succeeded */ } /* Set the CPU simulation stop conditions. This validation routine is called to configure the set of CPU stop conditions. The "option" parameter is 0 to clear the stops and 1 to set them, and "cptr" points to the first character of the name of the stop to be cleared or set. The unit and description pointers are not used. The routine processes commands of the form: SET CPU STOP SET CPU STOP=[;...] SET CPU NOSTOP SET CPU NOSTOP=[;...] The valid s are contained in the debug table "cpu_stop". If names are not specified, all stop conditions are enabled or disabled. Implementation notes: 1. The CPU simulator maintains a private and a public set of simulator stops. This routine sets the private set. The private set is copied to the public set as part of the instruction execution prelude, unless the "-B" ("bypass") command-line switch is used with the run command. This allows the stops to be bypassed conveniently for the first instruction execution only. */ static t_stat set_stops (UNIT *uptr, int32 option, CONST char *cptr, void *desc) { char gbuf [CBUFSIZE]; uint32 stop; if (cptr == NULL) { /* if there are no arguments */ sim_stops = 0; /* then clear all of the stop flags */ if (option == 1) /* if we're setting the stops */ for (stop = 0; cpu_stop [stop].name != NULL; stop++) /* then loop through the flags */ sim_stops |= cpu_stop [stop].mask; /* and add each one to the set */ } else if (*cptr == '\0') /* otherwise if the argument is empty */ return SCPE_MISVAL; /* then report the missing value */ else /* otherwise at least one argument is present */ while (*cptr) { /* loop through the arguments */ cptr = get_glyph (cptr, gbuf, ';'); /* get the next argument */ for (stop = 0; cpu_stop [stop].name != NULL; stop++) /* loop through the flags */ if (strcmp (cpu_stop [stop].name, gbuf) == 0) { /* and if the argument matches */ if (option == 1) /* then if it's a STOP argument */ sim_stops |= cpu_stop [stop].mask; /* then add the stop flag */ else /* otherwise it's a NOSTOP argument */ sim_stops &= ~cpu_stop [stop].mask; /* so remove the flag */ break; /* this argument has been processed */ } if (cpu_stop [stop].name == NULL) /* if the argument was not found */ return SCPE_ARG; /* then report it */ } return SCPE_OK; /* the stops were successfully processed */ } /* Change the instruction execution trace criteria. This validation routine is called to configure the criteria that select instruction execution tracing. The "option" parameter is 0 to clear and 1 to set the criteria, and "cptr" points to the first character of the match value to be set. The unit and description pointers are not used. The routine processes commands of the form: SET CPU EXEC=[;] SET CPU NOEXEC If the value is not supplied, a mask of 177777 octal is used. The values are entered in the current CPU data radix, which defaults to octal, unless an override switch is present on the command line. */ static t_stat set_exec (UNIT *uptr, int32 option, CONST char *cptr, void *desc) { char gbuf [CBUFSIZE]; uint32 match, mask, radix; t_stat status; if (option == 0) /* if this is a NOEXEC request */ if (cptr == NULL) { /* then if there are no arguments */ exec_match = D16_UMAX; /* then set the match and mask values */ exec_mask = 0; /* to prevent matching */ return SCPE_OK; /* and return success */ } else /* otherwise there are extraneous characters */ return SCPE_2MARG; /* so report that there are too many arguments */ else if (cptr == NULL || *cptr == '\0') /* otherwise if the EXEC request supplies no arguments */ return SCPE_MISVAL; /* then report a missing value */ else { /* otherwise at least one argument is present */ cptr = get_glyph (cptr, gbuf, ';'); /* so get the match argument */ if (sim_switches & SWMASK ('O')) /* if an octal override is present */ radix = 8; /* then parse the value in base 8 */ else if (sim_switches & SWMASK ('D')) /* otherwise if a decimal override is present */ radix = 10; /* then parse the value in base 10 */ else if (sim_switches & SWMASK ('H')) /* otherwise if a hex override is present */ radix = 16; /* then parse the value in base 16 */ else /* otherwise */ radix = cpu_dev.dradix; /* use the current CPU data radix */ match = (uint32) get_uint (gbuf, radix, D16_UMAX, &status); /* parse the match value */ if (status != SCPE_OK) /* if a parsing error occurred */ return status; /* then return the error status */ else if (*cptr == '\0') { /* otherwise if no more characters are present */ exec_match = match; /* then set the match value */ exec_mask = D16_MASK; /* and default the mask value */ return SCPE_OK; /* and return success */ } else { /* otherwise another argument is present */ cptr = get_glyph (cptr, gbuf, ';'); /* so get the mask argument */ mask = (uint32) get_uint (gbuf, radix, D16_UMAX, &status); /* parse the mask value */ if (status != SCPE_OK) /* if a parsing error occurred */ return status; /* then return the error status */ else if (*cptr == '\0') /* if no more characters are present */ if (mask == 0) /* then if the mask value is zero */ return SCPE_ARG; /* then the match will never succeed */ else { /* otherwise */ exec_match = match; /* set the match value */ exec_mask = mask; /* and the mask value */ return SCPE_OK; /* and return success */ } else /* otherwise extraneous characters are present */ return SCPE_2MARG; /* so report that there are too many arguments */ } } } /* Set the CPU cold dump configuration jumpers. This validation routine is called to configure the set of jumpers on the system control panel that preset the device number and control value for the cold dump process. The "option" parameter is 0 to set the device number and 1 to set the control value. The "cptr" parameter points to the first character of the value to be set. The unit and description pointers are not used. The routine processes commands of the form: SET CPU DUMPDEV= SET CPU DUMPCTL= The device number is a decimal value between 3 and 127, and the control value is an octal number between 0 and 377. Values outside of these ranges are rejected. */ static t_stat set_dump (UNIT *uptr, int32 option, CONST char *cptr, void *desc) { t_value value; t_stat status = SCPE_OK; if (cptr == NULL || *cptr == '\0') /* if the expected value is missing */ status = SCPE_MISVAL; /* then report the error */ else if (option == 0) { /* otherwise if a device number is present */ value = get_uint (cptr, DEVNO_BASE, /* then parse the supplied value */ DEVNO_MAX, &status); if (status == SCPE_OK) /* if it is valid */ if (value >= 3) /* and in the proper range */ dump_control = REPLACE_LOWER (dump_control, /* then set the new device number */ (uint32) value); /* into the dump control word */ else /* otherwise the device number */ status = SCPE_ARG; /* is invalid */ } else { /* otherwise a control byte is present */ value = get_uint (cptr, CNTL_BASE, /* so parse the supplied value */ CNTL_MAX, &status); if (status == SCPE_OK) /* if it is valid */ dump_control = REPLACE_UPPER (dump_control, /* then set the new control value */ (uint32) value); /* into the dump control word */ } return status; /* return the operation status */ } /* Change the CPU memory size. This validation routine is called to configure the CPU memory size. The "new_size" parameter is set to the size desired and will be one of the discrete sizes supported by the machine. The "uptr" parameter points to the CPU unit and is used to obtain the CPU model. The other parameters are not used. The routine processes commands of the form: SET [-F] CPU If the new memory size is larger than the supported size for the CPU model currently selected, the routine returns an error. If the new size is smaller than the previous size, and if the area that would be lost contains non-zero data, the user is prompted to confirm that memory should be truncated. If the user denies the request, the change is rejected. Otherwise, the new size is set. The user may omit the confirmation request and force truncation by specifying the "-F" switch on the command line. Implementation notes: 1. The memory access routines return a zero value for locations beyond the currently defined memory size. Therefore, the unused area need not be explicitly zeroed. */ static t_stat set_size (UNIT *uptr, int32 new_size, CONST char *cptr, void *desc) { static CONST char confirm [] = "Really truncate memory [N]?"; const uint32 model = CPU_MODEL (uptr->flags); /* the current CPU model index */ if ((uint32) new_size > cpu_features [model].maxmem) /* if the new memory size is not supported on current model */ return SCPE_NOFNC; /* then report the error */ if (!(sim_switches & SWMASK ('F')) /* if truncation is not explicitly forced */ && ! mem_is_empty (new_size) /* and the truncated part is not empty */ && get_yn (confirm, FALSE) == FALSE) /* and the user denies confirmation */ return SCPE_INCOMP; /* then abort the command */ MEMSIZE = new_size; /* set the new memory size */ return SCPE_OK; /* confirm that the change is OK */ } /* Change the CPU model. This validation routine is called to configure the CPU model. The "new_model" parameter is set to the model desired and will be one of the unit model flags. The other parameters are not used. The routine processes commands of the form: SET [-F] CPU 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