/* hp3000_sys.c: HP 3000 system common interface Copyright (c) 2016-2018, 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. 27-Dec-18 JDB Revised fall through comments to comply with gcc 7 29-May-18 JDB Added a check for the "alternate" flag to "fmt_bitset" 05-Sep-17 JDB Removed the -B (binary display) option; use -2 instead Rewrote "fprint_sym" for better coverage 11-May-17 JDB Corrected comment in "fprint_value" 28-Apr-17 JDB Added void cast to "fprint_instruction" call for left stackop 03-Mar-17 JDB Added an implementation note to the "parse_sym" routine 29-Dec-16 JDB Changed the switch for STA format from -S to -T; changed the status mnemonic flag from REG_S to REG_T 28-Nov-16 JDB hp_device_conflict accumulates names of active traces only Improved parse_addr error detection 26-Oct-16 JDB Added "fprint_edit" to print EDIT subprogram mnemonics 27-Sep-16 JDB Added COBOL firmware mnemonics Modified "fprint_instruction" to handle two-word instructions 15-Sep-16 JDB Modified "one_time_init" to set aux_cmds "message" field 03-Sep-16 JDB Added the STOP_POWER and STOP_ARSINH status messages 01-Sep-16 JDB Moved the "hp_cold_cmd" routine to the CPU (as "cpu_cold_cmd") Added the POWER command 03-Aug-16 JDB Improved "fmt_char" and "fmt_bitset" to allow multiple calls 15-Jul-16 JDB Fixed the word count display for SIO read/write orders 14-Jul-16 JDB Improved the cold dump invocation 21-Jun-16 JDB Changed fprint_instruction mask type from t_value to uint32 08-Jun-16 JDB Corrected %d format to %u for unsigned values 16-May-16 JDB Prefix in fprint_instruction is now a pointer-to-constant 13-May-16 JDB Modified for revised SCP API function parameter types 20-Apr-16 JDB Added implementation notes to "fmt_bitset" 14-Apr-16 JDB Fixed INTMASK setting and display 24-Mar-16 JDB Added the LP device 21-Mar-16 JDB Changed uint16 types to HP_WORD 23-Nov-15 JDB First release version 11-Dec-12 JDB Created References: - Machine Instruction Set Reference Manual (30000-90022, February 1980) - Systems Programming Language Reference Manual (30000-90024, December 1976) This module provides the interface between the Simulation Control Program (SCP) and the HP 3000 simulator. It includes the required global VM interface data definitions (e.g., the simulator name, device array, etc.), symbolic display and parsing routines, utility routines for tracing and execution support, and SCP command replacement routines. */ #include #include #include "hp3000_defs.h" #include "hp3000_cpu.h" #include "hp3000_cpu_ims.h" #include "hp3000_mem.h" #include "hp3000_io.h" /* Global release string */ const char *hp_vm_release = "8"; /* HP 3000 simulator release number */ const char *hp_vm_release_message = "This is the last version of this simulator which is API compatible\n" "with the 4.x version of the simh framework. A supported version of\n" "this simulator can be found at: http://simh.trailing-edge.com/hp\n"; /* External I/O data structures */ extern DEVICE iop_dev; /* I/O Processor */ extern DEVICE mpx_dev; /* Multiplexer Channel */ extern DEVICE sel_dev; /* Selector Channel */ extern DEVICE scmb_dev [2]; /* Selector Channel Maintenance Boards */ extern DEVICE atcd_dev; /* Asynchronous Terminal Controller TDI */ extern DEVICE atcc_dev; /* Asynchronous Terminal Controller TCI */ extern DEVICE clk_dev; /* System Clock */ extern DEVICE lp_dev; /* Line Printer */ extern DEVICE ds_dev; /* 79xx MAC Disc */ extern DEVICE ms_dev; /* 7970 Magnetic Tape */ /* Program constants */ /* Symbolic production/consumption values */ #define SCPE_OK_2_WORDS ((t_stat) -1) /* two words produced or consumed */ #define SCPE_OK_3_WORDS ((t_stat) -2) /* three words produced or consumed */ /* Symbolic mode and format override switches */ #define A_SWITCH SWMASK ('A') #define B_SWITCH SWMASK ('B') #define C_SWITCH SWMASK ('C') #define D_SWITCH SWMASK ('D') #define E_SWITCH SWMASK ('E') #define H_SWITCH SWMASK ('H') #define I_SWITCH SWMASK ('I') #define M_SWITCH SWMASK ('M') #define O_SWITCH SWMASK ('O') #define T_SWITCH SWMASK ('T') #define MODE_SWITCHES (C_SWITCH | E_SWITCH | I_SWITCH | M_SWITCH | T_SWITCH) #define FORMAT_SWITCHES (A_SWITCH | B_SWITCH | D_SWITCH | H_SWITCH | O_SWITCH) #define SYMBOLIC_SWITCHES (MODE_SWITCHES | A_SWITCH) /* -A is both a mode and a format switch */ #define ALL_SWITCHES (MODE_SWITCHES | FORMAT_SWITCHES) /* Address parsing configuration flags */ typedef enum { apcNone = 000, /* no configuration */ apcBank_Offset = 001, /* . address form allowed */ apcBank_Override = 002, /* bank override switches allowed */ apcDefault_DBANK = 004, /* default bank is DBANK */ apcDefault_PBANK = 010 /* default bank is PBANK */ } APC_FLAGS; /* Operand types. Operand types indicate how to print or parse instruction mnemonics. There is a separate operand type for each unique operand interpretation. For printing, the operand type and associated operand mask indicate which bits form the operand value and what interpretation is to be imposed on that value. For parsing, the operand type additionally implies the acceptable syntax for symbolic entry. Operand masks are used to isolate the operand value from the instruction word. As provided, a logical AND removes the operand value; an AND with the complement leaves only the operand value. The one-for-one correspondence between operand types and masks must be preserved when adding new operand types. Implementation notes: 1. Immediate values, displacements, and decrements are assumed to be right-justified in the instruction word, i.e., extend from bits n-15, unless otherwise noted. 2. Operands for one-word instructions are in the lower (first) word; operands for two-word instructions are in the upper (second) word. 3. Operand masks for signed values must include both signs and magnitudes. 4. Operand masks are defined as the complements of the operand bits to make it easier to see which bits will be cleared when the value is ANDed. The complements are calculated at compile-time and so impose no run-time penalty. 5. The base set "move" instructions contain S-decrement fields that indicate the counts of parameters to remove from the stack when the instructions complete. Certain "decimal arithmetic" instructions of the optional Extended Instruction Set and certain "numeric conversion and load" instructions of the optional COBOL II Extended Instruction Set contain one or two S-decrement bits, but these encode the stack adjustment rather than expressing the adjustment directly. The SPL "ASMB" statement accepts the EIS instructions with the stack adjustment given as the encoded value, e.g., CVAD 0 and CVAD 1 are accepted, with the former deleting two words and the latter deleting four words from the stack. Therefore, all S-decrement operands are printed and parsed as their direct field values, rather than decoding and encoding the actual decrements. */ typedef enum { opNone, /* no operand */ opB15, /* PB/DB base bit 15 */ opU1, /* unsigned value range 0-1 */ opU1515, /* unsigned value pair range 0-15 */ opU63, /* unsigned value range 0-63 */ opU63X, /* unsigned value range 0-63, index bit 4 */ opU255, /* unsigned value range 0-255 */ opC15, /* CIR display value range 0-15 */ opR255L, /* register selection value range 0-255 left-to-right */ opR255R, /* register selection value range 0-255 right-to-left */ opPS31I, /* P +/- displacement range 0-31, indirect bit 4 */ opPS255, /* P +/- displacement range 0-255 */ opPU255, /* P unsigned displacement range 0-255 */ opPS255IX, /* P +/- displacement range 0-255, indirect bit 5, index bit 4 */ opS11, /* S decrement selection bit 11 */ opS15, /* S decrement selection bit 15 */ opSCS3, /* sign control bits 9-10, S decrement bit 11 */ opSU2, /* S decrement range 0-2 bits 10-11 */ opSU3, /* S decrement range 0-3 */ opSU3B, /* S decrement range 0-3, base bit 11 */ opSU3NAS, /* S decrement range 0-3, N/A/S bits 11-13 */ opSU7, /* S decrement range 0-7 */ opSU15, /* S decrement range 0-15 */ opD255IX, /* DB+/Q+/Q-/S- displacements, indirect bit 5, index bit 4 */ opPD255IX, /* P+/P-/DB+/Q+/Q-/S- displacements, indirect bit 5, index bit 4 */ opX, /* index bit 4 */ opB31, /* second word: PB/DB base bit 15 */ opS31, /* second word: S decrement selection bit 15 */ opCC7, /* second word: condition code flags bits 13-15 */ opSCS4 /* second word: sign control bits 12-14, S decrement bit 15 */ } OP_TYPE; static const t_value op_mask [] = { /* operand masks, indexed by OP_TYPE */ ~0000000u, /* opNone */ ~0000001u, /* opB15 */ ~0000001u, /* opU1 */ ~0000377u, /* opU1515 */ ~0000077u, /* opU63 */ ~0004077u, /* opU63X */ ~0000377u, /* opU255 */ ~0000017u, /* opC15 */ ~0000377u, /* opR255L */ ~0000377u, /* opR255R */ ~0004077u, /* opPS31I */ ~0000777u, /* opPS255 */ ~0000377u, /* opPU255 */ ~0006777u, /* opPS255IX */ ~0000020u, /* opS11 */ ~0000001u, /* opS15 */ ~0000160u, /* opSCS3 */ ~0000060u, /* opSU2 */ ~0000003u, /* opSU3 */ ~0000023u, /* opSU3B */ ~0000037u, /* opSU3NAS */ ~0000007u, /* opSU7 */ ~0000017u, /* opSU15 */ ~0006777u, /* opD255IX */ ~0007777u, /* opPD255IX */ ~0004000u, /* opX */ ~TO_DWORD (0000001u, 0000000u), /* opB31 */ ~TO_DWORD (0000001u, 0000000u), /* opS31 */ ~TO_DWORD (0000007u, 0000000u), /* opCC7 */ ~TO_DWORD (0000017u, 0000000u) /* opSCS4 */ }; /* Instruction classifications. Machine instructions on the HP 3000 are identified by a varying number of bits. In general, the four most-significant bits identify the general class of instruction, and additional bits form a sub-opcode within a class to identify an instruction uniquely. However, some instructions are irregular or have reserved bits. These bits are defined to be zero, but correct hardware decoding may or may not depend on the value being zero. Each instruction is classified by a mnemonic, a base operation code (without the operand), an operand type, and a mask for the reserved bits, if any. Classifications are grouped by class of instruction into arrays that are indexed by sub-opcodes, if applicable. An operation table consists of two parts, either of which is optional. If a given class has a sub-opcode that fully or almost fully decodes the class, the first (primary) part of the table contains the appropriate number of classification elements. This allows rapid access to a specific instruction based on its bit pattern. In this primary part, the reserved bits masks are not defined or used. If some instructions in a class have reserved bits, if the sub-opcode decoding is not regular, or if the instruction requires two words to decode, then the second (secondary) part of the table contains classification elements that specify reserved bits masks. This part is searched linearly. As an example, the stack instructions have bits 0-3 = 0. The remaining twelve bits are broken into two six-bit fields. Each field encodes one of 64 stack operations (actually, 63 operations, as one is unassigned). As the stack operations are fully decoded, the table consists only of 64 primary elements, corresponding one-for-one to the 64 operations. As as contrasting example, the shift and branch operations have bits 0-3 = 1 and are fully decoded by bits 5-9, except for two instructions that have reserved bit fields (SCAN and TNSL), and two instructions that require one more bit for full decoding (QASL and QASR). The table consists of a primary part of 32 elements and a secondary part of four elements. The three primary elements corresponding to the four partially-decoded instructions are indicated by zero-length mnemonics. The four secondary elements contain an entry for each instruction that requires additional masking before unique identification is possible. Some instructions contain reserved bits that may or may not affect hardware instruction decoding. For example, the MOVE instruction defines bits 12-13 as 00, but the bits are not decoded, so MOVE will result regardless of the values. IXIT also defines bits 12-13 as 00, but in this case they must be 00 for the instruction to execute; any other value executes a PCN instruction. For those instructions dependent on their reserved bits for interpretation, the operations table has two entries for each opcode. The first entry specifies a reserved bits mask of all-ones; this entry matches the canonical opcode. The second entry specifies a mask that matches the opcode to the range of opcodes that decode to the instruction; this entry presents the opcode mnemonic in lowercase as an indicator that it is not the canonical representation. Two-word instructions are, by definition, never decoded by a primary opcode, so they will always be found in the secondary entries of a classification table with 32-bit opcode and reserved bits mask values. The opcode value contains the first word of the instruction in the lower half and the second word in the upper half. The lower half of the reserved bits mask value will have all bits set, and the upper half will have all bits set except for those corresponding to reserved bits in the second instruction word (if any). A two-word entry, therefore, may be identified by a mask value with a non-zero upper half. A secondary entry with a zero-length mnemonic may be used to match the first word of a two-word instruction if none of the second words match earlier entries. This ensures that unimplemented two-word instructions are printed as a two-word octal value rather than as a one-word octal value followed by the mnemonic for a one-word instruction. The end of an operations table is indicated by a NULL mnemonic pointer. */ typedef struct { const char *mnemonic; /* symbolic name */ t_value opcode; /* base opcode */ OP_TYPE operand; /* operand type */ t_value rsvd_mask; /* reserved bits mask */ } INST_CLASS; typedef INST_CLASS OP_TABLE []; /* an array of classifications */ /* Stack operations. The stack instructions are fully decoded by bits 4-9 or 10-15. The table consists of 64 primary entries. 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 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Implementation notes: 1. Opcode 072 is undefined and will cause an Unimplemented Instruction trap if it is executed. Normally, an unimplemented instruction is printed in numeric form during mnemonic display. However, as two stack operations are contained in a single word, the entry for opcode 072 has "072" as the mnemonic to allow the other stack op to be decoded for printing. */ static const OP_TABLE stack_ops = { { "NOP", 0000000, opNone }, { "DELB", 0000100, opNone }, { "DDEL", 0000200, opNone }, { "ZROX", 0000300, opNone }, { "INCX", 0000400, opNone }, { "DECX", 0000500, opNone }, { "ZERO", 0000600, opNone }, { "DZRO", 0000700, opNone }, { "DCMP", 0001000, opNone }, { "DADD", 0001100, opNone }, { "DSUB", 0001200, opNone }, { "MPYL", 0001300, opNone }, { "DIVL", 0001400, opNone }, { "DNEG", 0001500, opNone }, { "DXCH", 0001600, opNone }, { "CMP", 0001700, opNone }, { "ADD", 0002000, opNone }, { "SUB", 0002100, opNone }, { "MPY", 0002200, opNone }, { "DIV", 0002300, opNone }, { "NEG", 0002400, opNone }, { "TEST", 0002500, opNone }, { "STBX", 0002600, opNone }, { "DTST", 0002700, opNone }, { "DFLT", 0003000, opNone }, { "BTST", 0003100, opNone }, { "XCH", 0003200, opNone }, { "INCA", 0003300, opNone }, { "DECA", 0003400, opNone }, { "XAX", 0003500, opNone }, { "ADAX", 0003600, opNone }, { "ADXA", 0003700, opNone }, { "DEL", 0004000, opNone }, { "ZROB", 0004100, opNone }, { "LDXB", 0004200, opNone }, { "STAX", 0004300, opNone }, { "LDXA", 0004400, opNone }, { "DUP", 0004500, opNone }, { "DDUP", 0004600, opNone }, { "FLT", 0004700, opNone }, { "FCMP", 0005000, opNone }, { "FADD", 0005100, opNone }, { "FSUB", 0005200, opNone }, { "FMPY", 0005300, opNone }, { "FDIV", 0005400, opNone }, { "FNEG", 0005500, opNone }, { "CAB", 0005600, opNone }, { "LCMP", 0005700, opNone }, { "LADD", 0006000, opNone }, { "LSUB", 0006100, opNone }, { "LMPY", 0006200, opNone }, { "LDIV", 0006300, opNone }, { "NOT", 0006400, opNone }, { "OR", 0006500, opNone }, { "XOR", 0006600, opNone }, { "AND", 0006700, opNone }, { "FIXR", 0007000, opNone }, { "FIXT", 0007100, opNone }, { "072", 0007200, opNone }, /* unassigned opcode */ { "INCB", 0007300, opNone }, { "DECB", 0007400, opNone }, { "XBX", 0007500, opNone }, { "ADBX", 0007600, opNone }, { "ADXB", 0007700, opNone }, { NULL } }; /* Shift, branch, and bit test operations. The shift, branch, and bit test instructions are fully decoded by bits 5-9, except for SCAN and TNSL, whose reserved bits are don't cares, and QASL and QASR, which depend on bit 4. The table consists of 32 primary entries and four secondary entries. 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 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ static const OP_TABLE sbb_ops = { { "ASL", 0010000, opU63X }, { "ASR", 0010100, opU63X }, { "LSL", 0010200, opU63X }, { "LSR", 0010300, opU63X }, { "CSL", 0010400, opU63X }, { "CSR", 0010500, opU63X }, { "", 0010600, opNone }, /* SCAN */ { "IABZ", 0010700, opPS31I }, { "TASL", 0011000, opU63X }, { "TASR", 0011100, opU63X }, { "IXBZ", 0011200, opPS31I }, { "DXBZ", 0011300, opPS31I }, { "BCY", 0011400, opPS31I }, { "BNCY", 0011500, opPS31I }, { "", 0011600, opNone }, /* TNSL */ { "", 0011700, opNone }, /* QASL, QASR */ { "DASL", 0012000, opU63X }, { "DASR", 0012100, opU63X }, { "DLSL", 0012200, opU63X }, { "DLSR", 0012300, opU63X }, { "DCSL", 0012400, opU63X }, { "DCSR", 0012500, opU63X }, { "CPRB", 0012600, opPS31I }, { "DABZ", 0012700, opPS31I }, { "BOV", 0013000, opPS31I }, { "BNOV", 0013100, opPS31I }, { "TBC", 0013200, opU63X }, { "TRBC", 0013300, opU63X }, { "TSBC", 0013400, opU63X }, { "TCBC", 0013500, opU63X }, { "BRO", 0013600, opPS31I }, { "BRE", 0013700, opPS31I }, { "SCAN", 0010600, opX, 0177700 }, { "TNSL", 0011600, opX, 0177700 }, { "QASL", 0011700, opU63, 0177777 }, { "QASR", 0015700, opU63, 0177777 }, { NULL } }; /* Move, special, firmware, immediate, bit field, and register operations. The move and special instructions are partially decoded by bits 8-10. Only MABS, MTDS, MDS, MFDS, and MVBW are fully decoded; the other 19 instructions are not. Therefore, it's easier to treat all of the instructions as potentially containing reserved bits and use secondary table entries. 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 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The firmware extension instructions, including DMUL and DDIV in the base set, have generally unique encodings. They are rare, so it's easier to use secondary entries for all of them. 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 0 | 0 0 0 1 | firmware option op | Firmware +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The immediate, bit field, and register instructions are fully decoded by bits 4-7. 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 0 1 0 | immediate op | 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 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The table consists of 16 primary entries for the immediate, bit field, and register instructions, followed by the secondary entries for the remaining instructions. Implementation notes: 1. The IXIT, PCN, LOCK, and UNLK instructions specify bits 12-15 as 0000-0011. However, values other than zero in bits 12-13 will decode to one of these instructions. Specifically, the value of bits 12-15 for IXIT = 0000, PCN = nnn0, LOCK = nn01, and UNLK = nn11, where n is any collective value other than 0. 2. The secondary entry with the empty mnemonic ensures that an unimplemented two-word instruction is printed as "020477,000001" (e.g.) rather than "020477" followed by "NOP,DELB". 3. By convention, SIMH simulators always decode all supported instructions, regardless of whether or not they are enabled by current CPU firmware configurations. So the EIS, APL, and COBOL-II instructions are present in the table and are not conditional on the CPU options set. */ static const OP_TABLE msfifr_ops = { { "", 0020000, opNone }, /* move and special ops */ { "", 0020400, opNone }, /* DMUL, DDIV, and firmware extension opcodes */ { "LDI", 0021000, opU255 }, { "LDXI", 0021400, opU255 }, { "CMPI", 0022000, opU255 }, { "ADDI", 0022400, opU255 }, { "SUBI", 0023000, opU255 }, { "MPYI", 0023400, opU255 }, { "DIVI", 0024000, opU255 }, { "PSHR", 0024400, opR255R }, { "LDNI", 0025000, opU255 }, { "LDXN", 0025400, opU255 }, { "CMPN", 0026000, opU255 }, { "EXF", 0026400, opU1515 }, { "DPF", 0027000, opU1515 }, { "SETR", 0027400, opR255L }, { "MOVE", 0020000, opSU3B, 0177763 }, { "MVB", 0020040, opSU3B, 0177763 }, { "MVBL", 0020100, opSU3, 0177773 }, { "MABS", 0020110, opSU7, 0177777 }, { "SCW", 0020120, opSU3, 0177773 }, { "MTDS", 0020130, opSU7, 0177777 }, { "MVLB", 0020140, opSU3, 0177773 }, { "MDS", 0020150, opSU7, 0177777 }, { "SCU", 0020160, opSU3, 0177773 }, { "MFDS", 0020170, opSU7, 0177777 }, { "MVBW", 0020200, opSU3NAS, 0177777 }, { "CMPB", 0020240, opSU3B, 0177763 }, { "RSW", 0020300, opNone, 0177761 }, { "LLSH", 0020301, opNone, 0177761 }, { "PLDA", 0020320, opNone, 0177761 }, { "PSTA", 0020321, opNone, 0177761 }, { "LSEA", 0020340, opNone, 0177763 }, { "SSEA", 0020341, opNone, 0177763 }, { "LDEA", 0020342, opNone, 0177763 }, { "SDEA", 0020343, opNone, 0177763 }, { "IXIT", 0020360, opNone, 0177777 }, { "LOCK", 0020361, opNone, 0177777 }, { "lock", 0020361, opNone, 0177763 }, /* decodes bits 12-15 as nn01 */ { "PCN", 0020362, opNone, 0177777 }, { "pcn", 0020360, opNone, 0177761 }, /* decodes bits 12-15 as nnn0 */ { "UNLK", 0020363, opNone, 0177777 }, { "unlk", 0020363, opNone, 0177763 }, /* decodes bits 12-15 as nn11 */ { "EADD", 0020410, opNone, 0177777 }, { "ESUB", 0020411, opNone, 0177777 }, { "EMPY", 0020412, opNone, 0177777 }, { "EDIV", 0020413, opNone, 0177777 }, { "ENEG", 0020414, opNone, 0177777 }, { "ECMP", 0020415, opNone, 0177777 }, { "ALGN", 0020460, opS15, 0177777 }, { "ABSN", 0020462, opS15, 0177777 }, { "EDIT", 0020470, opB15, 0177777 }, { "CMPS", 0020472, opB15, 0177777 }, { "XBR", 0020474, opNone, 0177777 }, { "PARC", 0020475, opNone, 0177777 }, { "ENDP", 0020476, opNone, 0177777 }, { "CMPT", TO_DWORD (0000006, 0020477), opB31, D32_MASK }, { "TCCS", TO_DWORD (0000010, 0020477), opCC7, D32_MASK }, { "CVND", TO_DWORD (0000020, 0020477), opSCS4, D32_MASK }, { "LDW", TO_DWORD (0000040, 0020477), opS31, D32_MASK }, { "LDDW", TO_DWORD (0000042, 0020477), opS31, D32_MASK }, { "TR", TO_DWORD (0000044, 0020477), opB31, D32_MASK }, { "ABSD", TO_DWORD (0000046, 0020477), opS31, D32_MASK }, { "NEGD", TO_DWORD (0000050, 0020477), opS31, D32_MASK }, { "", 0020477, opNone, 0177777 }, /* catch unimplemented two-word instructions */ { "DMUL", 0020570, opNone, 0177777 }, { "DDIV", 0020571, opNone, 0177777 }, { "DMPY", 0020601, opNone, 0177617 }, { "CVAD", 0020602, opS11, 0177637 }, { "CVDA", 0020603, opSCS3, 0177777 }, { "CVBD", 0020604, opS11, 0177637 }, { "CVDB", 0020605, opS11, 0177637 }, { "SLD", 0020606, opSU2, 0177677 }, { "NSLD", 0020607, opSU2, 0177677 }, { "SRD", 0020610, opSU2, 0177677 }, { "ADDD", 0020611, opSU2, 0177677 }, { "CMPD", 0020612, opSU2, 0177677 }, { "SUBD", 0020613, opSU2, 0177677 }, { "MPYD", 0020614, opSU2, 0177677 }, { NULL } }; /* I/O and control operations. The I/O instructions are fully decoded by bits 8-11. The control instructions are partially decoded and require additional decoding by bits 14-15. The table consists of 16 primary entries, followed by the secondary entries for the instructions that are partially decoded or have reserved bits. 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 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Implementation notes: 1. The SED instruction specifies bits 12-14 as 000, and the instruction only works correctly if opcodes 030040 and 030041 are used. Values other than 000 will decode and execute 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). 2. The XCHD, DISP, PSDB, and PSEB instructions specify bits 12-15 as 0000-0011. However, values other than zero in bits 12-13 will decode to one of these instructions. Specifically, the value of bits 12-15 for XCHD = 0000, DISP = nnn0, PSDB = nn01, and PSEB = nn11, where n is any collective value other than 0. 3. The SMSK, SCLK, RMSK, and RCLK instructions specify bits 12-15 as 0000-0011. However, values other than zero in bits 12-14 will decode to one of these instructions. Specifically, the value of bits 12-15 for SMSK and RMSK = 0000, and SCLK and SMSK = nnnn, where n is any collective value other than 0. 4. The double entries for DISP, SCLK, and RCLK ensure that their full ranges decode to the indicated instructions for printing but only the primary opcode is encoded when entering instructions in symbolic form. */ static const OP_TABLE ioc_ops = { { "LST", 0030000, opSU15 }, { "PAUS", 0030020, opC15 }, { "", 0030040, opNone }, /* SED */ { "", 0030060, opNone }, /* XCHD, PSDB, DISP, PSEB */ { "", 0030100, opNone }, /* SMSK, SCLK */ { "", 0030120, opNone }, /* RMSK, RCLK */ { "XEQ", 0030140, opSU15 }, { "SIO", 0030160, opSU15 }, { "RIO", 0030200, opSU15 }, { "WIO", 0030220, opSU15 }, { "TIO", 0030240, opSU15 }, { "CIO", 0030260, opSU15 }, { "CMD", 0030300, opSU15 }, { "SST", 0030320, opSU15 }, { "SIN", 0030340, opSU15 }, { "HALT", 0030360, opC15 }, { "SED", 0030040, opU1, 0177777 }, { "sed", 0030040, opU1, 0177760 }, /* decodes bits 12-14 as nnn */ { "XCHD", 0030060, opNone, 0177777 }, { "PSDB", 0030061, opNone, 0177777 }, { "psdb", 0030061, opNone, 0177763 }, /* decodes bits 12-15 as nn01 */ { "DISP", 0030062, opNone, 0177777 }, { "disp", 0030060, opNone, 0177761 }, /* decodes bits 12-15 as nnn0 */ { "PSEB", 0030063, opNone, 0177777 }, { "pseb", 0030063, opNone, 0177763 }, /* decodes bits 12-15 as nn11 */ { "SMSK", 0030100, opNone, 0177777 }, { "SCLK", 0030101, opNone, 0177777 }, { "sclk", 0030100, opNone, 0177760 }, /* decodes bits 12-15 as nnnn */ { "RMSK", 0030120, opNone, 0177777 }, { "RCLK", 0030121, opNone, 0177777 }, { "rclk", 0030120, opNone, 0177760 }, /* decodes bits 12-15 as nnnn */ { NULL } }; /* Program, immediate, and memory operations. The program, immediate, and memory instructions are fully decoded by bits 4-7. The table consists of 16 primary entries. Entry 0 is a placeholder for the separate I/O and control instructions table. 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 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 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ static const OP_TABLE pmi_ops = { { "", 0000000, opNone }, /* placeholder for subop 00 */ { "SCAL", 0030400, opPU255 }, { "PCAL", 0031000, opPU255 }, { "EXIT", 0031400, opPU255 }, { "SXIT", 0032000, opPU255 }, { "ADXI", 0032400, opU255 }, { "SBXI", 0033000, opU255 }, { "LLBL", 0033400, opPU255 }, { "LDPP", 0034000, opPU255 }, { "LDPN", 0034400, opPU255 }, { "ADDS", 0035000, opU255 }, { "SUBS", 0035400, opU255 }, { "", 0036000, opNone }, /* unassigned opcode */ { "ORI", 0036400, opU255 }, { "XORI", 0037000, opU255 }, { "ANDI", 0037400, opU255 }, { NULL } }; /* Memory, loop, and branch operations. The memory and loop instructions are fully decoded by bits 0-3, except for TBA, MTBA, TBX, MTBX, STOR, INCM, DECM, LDB, LDD, STB, and STD, which depend on bits 4-6. The branch instructions also depend on 4-6, except for BCC, which also depends on bits 7-9. The table consists of 16 primary entries, followed by the secondary entries for the instructions that are partially decoded or have reserved bits. Entries 0-3 are placeholders for the other instruction tables. 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 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Implementation notes: 1. The BCC instruction specifies the branch condition in bits 7-9. There are separate secondary entries for each of the conditions. 2. The BR (Branch) instruction has two forms. When bit 6 = 0, it has a P-relative displacement with optional indexing and indirection. When bit 6 = 1, it has an indirect DB/Q/S-relative displacement with optional indexing. Two secondary entries are needed for the two operand types. The opcode for BR DB/Q/S,I is 143000, i.e., with the I bit forced on. The second opcode entry is 141000 to put the I bit with the operand for proper decoding. 3. Signed displacements are in sign-magnitude form, not two's complement. */ static const OP_TABLE mlb_ops = { { "", 0000000, opNone }, /* placeholder for opcode 00 */ { "", 0010000, opNone }, /* placeholder for opcode 01 */ { "", 0020000, opNone }, /* placeholder for opcode 02 */ { "", 0030000, opNone }, /* placeholder for opcode 03 */ { "LOAD", 0040000, opPD255IX }, { "", 0050000, opNone }, /* TBA, MTBA, TBX, MTBX, STOR */ { "CMPM", 0060000, opPD255IX }, { "ADDM", 0070000, opPD255IX }, { "SUBM", 0100000, opPD255IX }, { "MPYM", 0110000, opPD255IX }, { "", 0120000, opNone }, /* INCM, DECM */ { "LDX", 0130000, opPD255IX }, { "", 0140000, opNone }, /* BR, BCC */ { "", 0150000, opNone }, /* LDB, LDD */ { "", 0160000, opNone }, /* STB, STD */ { "LRA", 0170000, opPD255IX }, { "TBA", 0050000, opPS255, 0177777 }, { "MTBA", 0052000, opPS255, 0177777 }, { "TBX", 0054000, opPS255, 0177777 }, { "MTBX", 0056000, opPS255, 0177777 }, { "STOR", 0051000, opD255IX, 0177777 }, { "INCM", 0120000, opD255IX, 0177777 }, { "DECM", 0121000, opD255IX, 0177777 }, { "BR", 0140000, opPS255IX, 0177777 }, /* P-relative displacement */ { "BN", 0141000, opPS31I, 0177777 }, /* branch never */ { "BL", 0141100, opPS31I, 0177777 }, /* branch on less than */ { "BE", 0141200, opPS31I, 0177777 }, /* branch on equal */ { "BLE", 0141300, opPS31I, 0177777 }, /* branch on less than or equal */ { "BG", 0141400, opPS31I, 0177777 }, /* branch on greater than */ { "BNE", 0141500, opPS31I, 0177777 }, /* branch on not equal */ { "BGE", 0141600, opPS31I, 0177777 }, /* branch on greater than or equal */ { "BA", 0141700, opPS31I, 0177777 }, /* branch always */ { "BR", 0141000, opD255IX, 0177777 }, /* indirect DB/Q/S-relative displacement */ { "LDB", 0150000, opD255IX, 0177777 }, { "LDD", 0151000, opD255IX, 0177777 }, { "STB", 0160000, opD255IX, 0177777 }, { "STD", 0161000, opD255IX, 0177777 }, { NULL } }; /* EDIT subprogram operations. The EDIT instruction, part of the COBOL II Extended Instruction Set, implements the COBOL "PICTURE" clause. The formation of the edited string is controlled by a subprogram whose address is pushed onto the stack. Subprogram operations are encoded into variable-length sequences of bytes. Each operation begins with an opcode of the form: 0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | opcode | imm. operand | +---+---+---+---+---+---+---+---+ Opcodes 0-16 directly decode to specific operations, and the lower half of the first byte contains an immediate operand value from 1-15 or a zero value that indicates that the immediate operand is contained in the next byte. Opcode 17 is a prefix for the subopcode contained in the lower half of the first byte in place of the immediate operand. Opcode bytes are followed by zero or more operand bytes, depending on the operation. The opcodes and their assigned operations are: Opcode SubOp Mnem Movement Operation ------ ----- ---- -------- --------------------------------------- 00 - MC s => t move characters 01 - MA s => t move alphabetics 02 - MN s => t move numerics 03 - MNS s => t move numerics suppressed 04 - MFL s => t move numerics with floating insertion 05 - IC c => t insert character 06 - ICS c => t insert character suppressed 07 - ICI p => t insert characters immediate 10 - ICSI p => t insert characters suppressed immediate 11 - BRIS (none) branch if significance 12 - SUFT (none) subtract from target 13 - SUFS (none) subtract from source 14 - ICP c -> t insert character punctuation 15 - ICPS c -> t insert character punctuation suppressed 16 - IS p => t insert character on sign 17 00 TE (none) terminate edit 17 01 ENDF c -> t end floating point insertion 17 02 SST1 (none) set significance to 1 17 03 SST0 (none) set significance to 0 17 04 MDWO s -> t move digit with overpunch 17 05 SFC (none) set fill character 17 06 SFLC (none) set float character 17 07 DFLC (none) define float character 17 10 SETC (none) set loop count 17 11 DBNZ (none) decrement loop count and branch Where: s = source byte string operand t = target byte string operand p = program byte string operand c = character operand -> = move 1 byte => = move n bytes Implementation notes: 1. The operation name table is indexed by the sum of the opcode and subopcode, where the latter is zero if the opcode is not extended. This provides simple access to a single table. */ static const char *const edit_ops [] = { /* EDIT operation names */ "MC", /* 000 - move characters */ "MA", /* 001 - move alphabetics */ "MN", /* 002 - move numerics */ "MNS", /* 003 - move numerics suppressed */ "MFL", /* 004 - move numerics with floating insertion */ "IC", /* 005 - insert character */ "ICS", /* 006 - insert character suppressed */ "ICI", /* 007 - insert characters immediate */ "ICSI", /* 010 - insert characters suppressed immediate */ "BRIS", /* 011 - branch if significance */ "SUFT", /* 012 - subtract from target */ "SUFS", /* 013 - subtract from source */ "ICP", /* 014 - insert character punctuation */ "ICPS", /* 015 - insert character punctuation suppressed */ "IS", /* 016 - insert character on sign */ "TE", /* 017 (017 + 000) - terminate edit */ "ENDF", /* 020 (017 + 001) - end floating point insertion */ "SST1", /* 021 (017 + 002) - set significance to 1 */ "SST0", /* 022 (017 + 003) - set significance to 0 */ "MDWO", /* 023 (017 + 004) - move digit with overpunch */ "SFC", /* 024 (017 + 005) - set fill character */ "SFLC", /* 025 (017 + 006) - set float character */ "DFLC", /* 026 (017 + 007) - define float character */ "SETC", /* 027 (017 + 010) - set loop count */ "DBNZ" /* 030 (017 + 011) - decrement loop count and branch */ }; /* System interface local SCP support routines */ void hp_one_time_init (void); static t_bool fprint_stopped (FILE *st, t_stat reason); static void fprint_addr (FILE *st, DEVICE *dptr, t_addr addr); static t_addr parse_addr (DEVICE *dptr, CONST char *cptr, CONST char **tptr); static t_stat hp_exdep_cmd (int32 arg, CONST char *buf); static t_stat hp_run_cmd (int32 arg, CONST char *buf); static t_stat hp_brk_cmd (int32 arg, CONST char *buf); /* System interface local utility routines */ static t_stat fprint_value (FILE *ofile, t_value val, uint32 radix, uint32 width, uint32 format); static t_stat fprint_order (FILE *ofile, t_value *val, uint32 radix); static t_stat fprint_subop (FILE *ofile, t_value *val, uint32 radix, t_addr addr, int32 switches); static t_stat fprint_instruction (FILE *ofile, const OP_TABLE ops, t_value *val, uint32 mask, uint32 shift, uint32 radix); static t_stat parse_cpu (CONST char *cptr, t_addr address, UNIT *uptr, t_value *value, int32 switches); /* System interface state */ static size_t device_size = 0; /* maximum device name size */ static size_t flag_size = 0; /* maximum debug flag name size */ static APC_FLAGS parse_config = apcNone; /* address parser configuration */ /* System interface global data structures */ #define E 0400u /* parity bit for even parity */ #define O 0000u /* parity bit for odd parity */ const HP_WORD odd_parity [256] = { /* odd parity table */ E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 000-017 */ O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 020-037 */ O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 040-067 */ E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 060-077 */ O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 100-117 */ E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 120-137 */ E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 140-157 */ O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 160-177 */ O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 200-217 */ E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 220-237 */ E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 240-267 */ O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 260-277 */ E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 300-317 */ O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 320-337 */ O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 340-357 */ E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E /* 360-377 */ }; static const BITSET_NAME inbound_names [] = { /* Inbound signal names, in INBOUND_SIGNAL order */ "DSETINT", /* 000000000001 */ "DCONTSTB", /* 000000000002 */ "DSTARTIO", /* 000000000004 */ "DWRITESTB", /* 000000000010 */ "DRESETINT", /* 000000000020 */ "DSTATSTB", /* 000000000040 */ "DSETMASK", /* 000000000100 */ "DREADSTB", /* 000000000200 */ "ACKSR", /* 000000000400 */ "TOGGLESR", /* 000000001000 */ "SETINT", /* 000000002000 */ "PCMD1", /* 000000004000 */ "PCONTSTB", /* 000000010000 */ "SETJMP", /* 000000020000 */ "PSTATSTB", /* 000000040000 */ "PWRITESTB", /* 000000100000 */ "PREADSTB", /* 000000200000 */ "EOT", /* 000000400000 */ "TOGGLEINXFER", /* 000001000000 */ "TOGGLEOUTXFER", /* 000002000000 */ "READNEXTWD", /* 000004000000 */ "TOGGLESIOOK", /* 000010000000 */ "DEVNODB", /* 000020000000 */ "INTPOLLIN", /* 000040000000 */ "XFERERROR", /* 000100000000 */ "CHANSO", /* 000200000000 */ "PFWARN" /* 000400000000 */ }; const BITSET_FORMAT inbound_format = /* names, offset, direction, alternates, bar */ { FMT_INIT (inbound_names, 0, lsb_first, no_alt, no_bar) }; static const BITSET_NAME outbound_names [] = { /* Outbound signal names, in OUTBOUND_SIGNAL order */ "INTREQ", /* 000000200000 */ "INTACK", /* 000000400000 */ "INTPOLLOUT", /* 000001000000 */ "DEVEND", /* 000002000000 */ "JMPMET", /* 000004000000 */ "CHANACK", /* 000010000000 */ "CHANSR", /* 000020000000 */ "SRn" /* 000040000000 */ }; const BITSET_FORMAT outbound_format = /* names, offset, direction, alternates, bar */ { FMT_INIT (outbound_names, 16, lsb_first, no_alt, no_bar) }; /* System interface global SCP data definitions */ char sim_name [] = "HP 3000"; /* the simulator name */ int32 sim_emax = 2; /* the maximum number of words in any instruction */ DEVICE *sim_devices [] = { /* an array of pointers to the simulated devices */ &cpu_dev, /* CPU (must be first) */ &iop_dev, /* I/O Processor */ &mpx_dev, /* Multiplexer Channel */ &sel_dev, /* Selector Channel */ &scmb_dev [0], &scmb_dev [1], /* Selector Channel Maintenance Boards */ &atcd_dev, &atcc_dev, /* Asynchronous Terminal Controller (TDI and TCI) */ &clk_dev, /* System Clock */ &lp_dev, /* Line Printer */ &ds_dev, /* 7905/06/20/25 MAC Disc Interface */ &ms_dev, /* 7970B/E Magnetic Tape Interface */ NULL /* end of the device list */ }; #define DEVICE_COUNT (sizeof sim_devices / sizeof sim_devices [0] - 1) const char *sim_stop_messages [SCPE_BASE] = { /* an array of pointers to the stop messages in STOP_nnn order */ "Impossible error", /* 0 (never returned) */ "System halt", /* STOP_SYSHALT */ "Unimplemented instruction", /* STOP_UNIMPL */ "Undefined instruction", /* STOP_UNDEF */ "CPU paused", /* STOP_PAUS */ "Programmed halt", /* STOP_HALT */ "Breakpoint", /* STOP_BRKPNT */ "Infinite loop", /* STOP_INFLOOP */ "Cold load complete", /* STOP_CLOAD */ "Cold dump complete", /* STOP_CDUMP */ "Auto-restart disabled", /* STOP_ARSINH */ "Power is off" /* STOP_POWER */ }; /* Local command table. This table defines commands and command behaviors that are specific to this simulator. One new command is defined, and several commands are repurposed or extended. Specifically: * EXAMINE, DEPOSIT, IEXAMINE, and IDEPOSIT accept bank/offset form, implied DBANK offsets, and memory bank override switches. * RUN and GO accept implied PBANK offsets and reject bank/offset form and memory bank override switches. * BREAK and NOBREAK accept bank/offset form and implied PBANK offsets and reject memory bank override switches. * LOAD and DUMP invoke the CPU cold load/cold dump facility, rather than loading or dumping binary files. * POWER adds the ability to fail or restore power to the CPU. The table is initialized with only those fields that differ from the standard command table. During one-time simulator initialization, the empty fields are filled in from the corresponding standard command table entries. This ensures that the auxiliary table automatically picks up any changes to the standard commands that it modifies. Implementation notes: 1. The RESET and BOOT commands are duplicated from the standard SCP command table so that entering "R" doesn't invoke the RUN command and entering "B" doesn't invoke the BREAK command. This would otherwise occur because a VM-specific command table is searched before the standard command table. */ static CTAB aux_cmds [] = { /* Name Action Routine Argument Help String */ /* ---------- -------------- --------- ---------------------------------------------------- */ { "RESET", NULL, 0, NULL }, { "BOOT", NULL, 0, NULL }, { "EXAMINE", &hp_exdep_cmd, 0, NULL }, { "IEXAMINE", &hp_exdep_cmd, 0, NULL }, { "DEPOSIT", &hp_exdep_cmd, 0, NULL }, { "IDEPOSIT", &hp_exdep_cmd, 0, NULL }, { "RUN", &hp_run_cmd, 0, NULL }, { "GO", &hp_run_cmd, 0, NULL }, { "BREAK", &hp_brk_cmd, 0, NULL }, { "NOBREAK", &hp_brk_cmd, 0, NULL }, { "LOAD", &cpu_cold_cmd, Cold_Load, "l{oad} {cntlword} cold load from a device\n" }, { "DUMP", &cpu_cold_cmd, Cold_Dump, "du{mp} {cntlword} cold dump to a device\n" }, { "POWER", &cpu_power_cmd, 0, "p{ower} f{ail} fail the CPU power\n" "p{ower} r{estore} restore the CPU power\n" }, { NULL } }; /* System interface global SCP support routines */ /* Load and dump memory images from and to files. The LOAD and DUMP commands are intended to provide a basic method of loading and dumping programs into and from memory. Typically, these commands operate on a simple, low-level format, e.g., a memory image. However, the HP 3000 requires the bank and segment registers being set up appropriately before execution. In addition, the CPU microcode depends on segment tables being present in certain fixed memory locations as part of a program load. These actions will not take place unless the system cold load facility is employed. Consequently, the LOAD and DUMP commands are repurposed to invoke the cold load and cold dump facilities, respectively, and this is a dummy routine that will never be called. It is present only to satisfy the external declared in the SCP module. */ t_stat sim_load (FILE *fptr, CONST char *cptr, CONST char *fnam, int flag) { return SCPE_ARG; /* return an error if called inadvertently */ } /* Print a value in symbolic format. This routine prints a data value in the format specified by the optional switches on the output stream provided. On entry, "ofile" is the opened output stream, and the other parameters depend on the reason the routine was called, as follows: * To print the next instruction mnemonic when the simulator stops: - addr = the program counter - val = a pointer to sim_eval [0] - uptr = NULL - sw = "-M" | SIM_SW_STOP * To print the result of EXAMining a register with REG_VMIO or a user flag: - addr = the ORed register radix and user flags - val = a pointer to a single t_value - uptr = NULL - sw = the command line switches | SIM_SW_REG * To print the result of EXAMining a memory address: - addr = the memory address - val = a pointer to sim_eval [0] - uptr = a pointer to the named unit - sw = the command line switches * To print the result of EVALuating a symbol: - addr = the symbol index - val = a pointer to sim_eval [addr] - uptr = a pointer to the default unit (cpu_unit) - sw = the command line switches On exit, a status code is returned to the caller. If the format requested is not supported, SCPE_ARG status is returned, which causes the caller to print the value in numeric format with the default radix. Otherwise, SCPE_OK status is returned if a single-word value was consumed, or the negative number of extra words (beyond the first) consumed in printing the symbol is returned. For example, printing a two-word symbol would return SCPE_OK_2_WORDS (= -1). The following symbolic modes are supported by including the indicated switches on the command line: Switch Display Interpretation ------ -------------------------------------------------- -A a single character in the right-hand byte -C a two-character packed string -E an EDIT instruction subprogram mnemonic -ER an EDIT mnemonic starting with the right-hand byte -I an I/O program instruction mnemonic -M a CPU instruction mnemonic -T a CPU status mnemonic In the absence of a mode switch, the value is displayed in a numeric format. When displaying data in one of the mnemonic modes, an additional switch may be specified to indicate the desired operand format, as follows: Switch Operand Interpretation ------ -------------------------------------------------- -A a single character in the right-hand byte -B a binary value -O an octal value -D a decimal value -H a hexadecimal value Except for -B, these switches may be used without a mode switch to display a numeric value in the specified form. To summarize, the valid switch combinations are: -A -C -E [ -R ] [ -A | -B | -O | -D | -H ] -I [ -A | -B | -O | -D | -H ] -M [ -A | -B | -O | -D | -H ] -T [ -A | -B | -O | -D | -H ] When displaying mnemonics, operand values by default are displayed in a radix suitable to the type of the value. Address values are displayed in the CPU's address radix, which is octal, and data values are displayed in the CPU's data radix, which defaults to octal but may be set to a different radix or overridden by a switch on the command line. Implementation notes: 1. Because mnemonics are specific to the CPU/MPX/SEL, the CPU's radix settings are used, even if the unit is a peripheral. For example, displaying disc sector data as CPU instructions uses the CPU's address and data radix values, rather than the disc's values. 2. Displaying a register having a symbolic default format (e.g., CIR) will use the default unless the radix is overridden on the command line. For example, "EXAMINE CIR" displays the CIR value as an instruction mnemonic, whereas "EXAMINE -D CIR" displays the value as decimal. Adding "-M" will force mnemonic display and allow the radix switch to override the operand display. For example, "EXAMINE -M -D CIR" displays the value as mnemonic and overrides the operand radix to decimal. 3. We return SCPE_INVSW when multiple modes or formats are specified, but the callers do not act on this; they use the fallback formatter if any status error is returned. We could work around this by printing "Invalid switch" to the console and returning SCPE_OK, but this does not stop IEXAMINE from prompting for the replacement value(s) or EXAMINE from printing a range. 4. Radix switches and the -C switch are conceptually mutually exclusive. However, if we return an error when "format" is non-zero, then -C will be ignored, and the fallback formatter will use the radix switch. The other choice is to process -C and ignore the radix switch; this is the option implemented. 5. Because -A is both a mode and a format switch, we must check its presence using SYMBOLIC_SWITCHES separately from the other modes to allow (e.g.) both "EXAMINE -A" and "EXAMINE -M -A". If -A is added to MODE_SWITCHES, the latter form would be rejected as having conflicting modes. 6. The penultimate condition of the multiway "if-else if" mode test checks for no mode switches. This succeeds when -A is specified alone because the earlier SYMBOLIC_SWITCHES test failed (so -A is present), but none of the other mode switches are present. */ t_stat fprint_sym (FILE *ofile, t_addr addr, t_value *val, UNIT *uptr, int32 sw) { int32 formats, modes; uint32 radix; if ((sw & (SIM_SW_REG | ALL_SWITCHES)) == SIM_SW_REG) /* if we are formatting a register without overrides */ if (addr & REG_A) /* then if the default format is character */ sw |= A_SWITCH; /* then set the -A switch */ else if (addr & REG_C) /* otherwise if the default mode is string */ sw |= C_SWITCH; /* then set the -C switch */ else if (addr & REG_M) /* otherwise if the default mode is instruction mnemonic */ sw |= M_SWITCH; /* then set the -M switch */ else if (addr & REG_T) /* otherwise if the default mode is status */ sw |= T_SWITCH; /* then set the -T switch */ if ((sw & SYMBOLIC_SWITCHES) == 0) /* if there are no symbolic mode overrides */ return SCPE_ARG; /* then return an error to use the standard formatter */ formats = sw & FORMAT_SWITCHES; /* separate the format switches */ modes = sw & MODE_SWITCHES; /* from the mode switches */ if (formats == A_SWITCH) /* if the -A switch is specified */ radix = 256; /* then override the radix to character */ else if (formats == B_SWITCH) /* otherwise if the -B switch is specified */ radix = 2; /* then override the radix to binary */ else if (formats == D_SWITCH) /* otherwise if the -D switch is specified */ radix = 10; /* then override the radix to decimal */ else if (formats == H_SWITCH) /* otherwise if the -H switch is specified */ radix = 16; /* then override the radix to hexadecimal */ else if (formats == O_SWITCH) /* otherwise if the -O switch is specified */ radix = 8; /* then override the radix to octal */ else if (formats == 0) /* otherwise if no format switch is specified */ radix = 0; /* then indicate that the default radix is to be used */ else /* otherwise more than one format is specified */ return SCPE_INVSW; /* so return an error */ if (modes == M_SWITCH) /* if mnemonic mode is specified */ return fprint_cpu (ofile, val, radix, sw); /* then format and print the value in mnemonic format */ else if (modes == I_SWITCH) /* otherwise if I/O channel order mode is specified */ return fprint_order (ofile, val, radix); /* then format and print it */ else if (modes == E_SWITCH) /* otherwise if an EDIT subop memory display is requested */ return fprint_subop (ofile, val, radix, addr, sw); /* then format and print it */ else if (modes == T_SWITCH) { /* otherwise if status display is requested */ fputs (fmt_status ((uint32) val [0]), ofile); /* then format the status flags and condition code */ fputc (' ', ofile); /* and add a separator */ if (fprint_value (ofile, STATUS_CS (val [0]), /* if the code segment number */ (radix ? radix : cpu_dev.dradix), /* prints with the specified radix */ STATUS_CS_WIDTH, PV_RZRO) == SCPE_OK) return SCPE_OK; /* then return success */ else /* otherwise print it */ return fprint_val (ofile, STATUS_CS (val [0]), /* in the CPU's default data radix */ cpu_dev.dradix, D8_WIDTH, PV_RZRO); } else if (modes == C_SWITCH) { /* otherwise if ASCII string mode is specified */ fputs (fmt_char (UPPER_BYTE (val [0])), ofile); /* then format and print the upper byte */ fputc (',', ofile); /* followed by a separator */ fputs (fmt_char (LOWER_BYTE (val [0])), ofile); /* followed by the lower byte */ return SCPE_OK; } else if (modes == 0) /* otherwise if single-character mode was specified */ return fprint_value (ofile, val [0], radix, 0, 0); /* then format and print it */ else /* otherwise the modes conflict */ return SCPE_INVSW; /* so return an error */ } /* Parse a string in symbolic format. Parse the input string using the interpretation specified by the optional switches, and return the resulting value(s). This routine is called to parse an input string when: - DEPOSITing into a register marked with a user flag - DEPOSITing into a memory address - EVALuating a symbol On entry, "cptr" points at the string to parse, "addr" is the register radix and flags, memory address, or 0 (respectively), "uptr" is NULL, a pointer to the named unit, or a pointer to the default unit (respectively), "val" is a pointer to an array of t_values of depth "sim_emax" representing the value(s) returned, and "sw" contains any switches passed on the command line. "sw" also includes SIM_SW_REG for a register call. On exit, a status code is returned to the caller. If the format requested is not supported or the parse failed, SCPE_ARG status is returned, which causes the caller to attempt to parse the value in numeric format. Otherwise, SCPE_OK status is returned if the parse produced a single-word value, or the negative number of extra words (beyond the first) produced by parsing the symbol is returned. For example, parsing a symbol that resulted in two words being stored (in val [0] and val [1]) would return SCPE_OK_2_WORDS (= -1). The following symbolic formats are supported by the listed switches: Switch Interpretation ------ ---------------------------------- -a a single character the in low byte -c a two-character packed string -o override numeric input to octal -d override numeric input to decimal -h override numeric input to hex In the absence of switches, a leading ' implies "-a", a leading " implies "-c", and a leading alphabetic character implies an instruction mnemonic. If a single character is supplied with "-c", the low byte of the resulting value will be zero; follow the character with a space if the low byte is to be padded with a space. Caution must be exercised when entering hex values without a leading digit. A value that is the same as an instruction mnemonic will be interpreted as the latter unless overridden by the "-h" switch. For example, "ADD" is an instruction mnemonic, but "ADE" is a hex value. To avoid confusion, always enter hex values with the "-h" switch or with a leading zero (i.e., "0ADD"). When entering mnemonics, operand values are parsed in a radix suitable to the type of the value. Address values are parsed in the CPU's address radix, which is octal, and data values are parsed in the CPU's data radix, which defaults to octal but may be set to a different radix or overridden by a switch on the command line. Implementation notes: 1. Because the mnemonics are specific to the CPU/MPX/SEL, the CPU's radix settings are used, even if the unit is a peripheral. For example, entering disc sector data as CPU instructions uses the CPU's address and data radix values, rather than the disc's values. 2. The "cptr" post-increments are logically ANDed with the tests for ' and " so that the increments are performed only if the tests succeed. The intent is to skip over the leading ' or " character. The increments themselves always succeed, so they don't affect the outcome of the tests. */ t_stat parse_sym (CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw) { while (isspace ((int) *cptr)) /* skip over any leading spaces */ cptr++; /* that are present in the line */ if (sw & SWMASK ('A') || *cptr == '\'' && cptr++) /* if an ASCII character parse is requested */ if (cptr [0] != '\0') { /* then if a character is present */ val [0] = (t_value) cptr [0]; /* then convert the character value */ return SCPE_OK; /* and indicate success */ } else /* otherwise */ return SCPE_ARG; /* report that the line cannot be parsed */ else if (sw & SWMASK ('C') || *cptr == '"' && cptr++) /* otherwise if a character string parse is requested */ if (cptr [0] != '\0') { /* then if characters are present */ val [0] = (t_value) TO_WORD (cptr [0], cptr [1]); /* then convert the character value(s) */ return SCPE_OK; /* and indicate success */ } else /* otherwise */ return SCPE_ARG; /* report that the line cannot be parsed */ else /* otherwise */ return parse_cpu (cptr, addr, uptr, val, sw); /* attempt a mnemonic instruction parse */ } /* Set a device configuration value. This validation routine is called to set a device's I/O configuration (device number, interrupt mask, interrupt priority, and service request number). The "uptr" parameter points to the unit being configured, "code" is a validation constant (VAL_DEVNO, VAL_INTMASK, VAL_INTPRI, or VAL_SRNO), "cptr" points to the first character of the value to be set, and "desc" points to the DIB associated with the device. If the supplied value is acceptable, it is stored in the DIB, and the routine returns SCPE_OK. Otherwise, an error code is returned. For the following validation constants, the acceptable ranges of values are: VAL_DEVNO -- 0-127 VAL_INTMASK -- 0-15 | E | D VAL_INTPRI -- 0-31 VAL_SRNO -- 0-15 Implementation notes: 1. For a numeric interrupt mask entry value , the value stored in the DIB is 2 ^ <15 - n> to match the HP 3000 bit numbering. For mask entry values "D" and "E", the stored values are 0 and 0177777, respectively. 2. The SCMB is the only device that may or may not have a service request number, depending on whether or not it is connected to the multiplexer channel bus. Therefore, the current request number must be valid before it may be changed. */ t_stat hp_set_dib (UNIT *uptr, int32 code, CONST char *cptr, void *desc) { DIB *const dibptr = (DIB *) desc; /* a pointer to the associated DIB */ t_stat status = SCPE_OK; t_value value; if (cptr == NULL || *cptr == '\0') /* if the expected value is missing */ status = SCPE_MISVAL; /* then report the error */ else /* otherwise a value is present */ switch (code) { /* and parsing depends on the value expected */ case VAL_DEVNO: /* DEVNO=0-127 */ value = get_uint (cptr, DEVNO_BASE, /* parse the supplied device number */ DEVNO_MAX, &status); if (status == SCPE_OK) /* if it is valid */ dibptr->device_number = (uint32) value; /* then save it in the DIB */ break; case VAL_INTMASK: /* INTMASK=0-15/E/D */ if (*cptr == 'E') /* if the mask value is "E" (enable) */ dibptr->interrupt_mask = INTMASK_E; /* then set all mask bits on */ else if (*cptr == 'D') /* otherwise if the mask value is "D" (disable) */ dibptr->interrupt_mask = INTMASK_D; /* then set all mask bits off */ else { /* otherwise */ value = get_uint (cptr, INTMASK_BASE, /* parse the supplied numeric mask value */ INTMASK_MAX, &status); if (status == SCPE_OK) /* if it is valid */ dibptr->interrupt_mask = D16_SIGN >> value; /* then set the corresponding mask bit in the DIB */ } break; case VAL_INTPRI: /* INTPRI=0-31 */ value = get_uint (cptr, INTPRI_BASE, /* parse the supplied priority number */ INTPRI_MAX, &status); if (status == SCPE_OK) /* if it is valid */ dibptr->interrupt_priority = (uint32) value; /* then save it in the DIB */ break; case VAL_SRNO: /* SRNO=0-15 */ if (dibptr->service_request_number == SRNO_UNUSED) /* if the current setting is "unused" */ status = SCPE_NOFNC; /* then report that it cannot be set */ else { /* otherwise */ value = get_uint (cptr, SRNO_BASE, /* parse the supplied service request number */ SRNO_MAX, &status); if (status == SCPE_OK) /* if it is valid */ dibptr->service_request_number = (uint32) value; /* then save it in the DIB */ } break; default: /* if an illegal code was passed */ status = SCPE_IERR; /* then report an internal coding error */ } return status; /* return the validation result */ } /* Show the device configuration values. This display routine is called to show a device's I/O configuration (device number, interrupt mask, interrupt priority, or service request number). The "st" parameter is the open output stream, "uptr" points to the unit being queried, "code" is a validation constant (VAL_DEVNO, VAL_INTMASK, VAL_INTPRI, or VAL_SRNO), and "desc" points at the DIB associated with the device. If the code is acceptable, the routine prints the DIB value for the specified characteristic and returns SCPE_OK. Otherwise, an error code is returned. For the following validation constants, the configuration values printed are: VAL_DEVNO -- DEVNO=0-127 VAL_INTMASK -- INTMASK=0-15 | E | D VAL_INTPRI -- INTPRI=0-31 VAL_SRNO -- SRNO=0-15 Implementation notes: 1. For a numeric interrupt mask entry value , the value stored in the DIB is 2 ^ <15 - n> to match the HP 3000 bit numbering. For mask entry values "D" and "E", the stored values are 0 and 0177777, respectively. */ t_stat hp_show_dib (FILE *st, UNIT *uptr, int32 code, CONST void *desc) { const DIB *const dibptr = (const DIB *) desc; /* a pointer to the associated DIB */ uint32 mask, value; switch (code) { /* display the requested value */ case VAL_DEVNO: /* show the device number */ fprintf (st, "DEVNO=%u", dibptr->device_number); break; case VAL_INTMASK: /* show the interrupt mask */ fputs ("INTMASK=", st); if (dibptr->interrupt_mask == INTMASK_D) /* if the mask is disabled */ fputc ('D', st); /* then display "D" */ else if (dibptr->interrupt_mask == INTMASK_E) /* otherwise if the mask is enabled */ fputc ('E', st); /* then display "E" */ else { /* otherwise */ mask = dibptr->interrupt_mask; /* display a specific mask value */ for (value = 0; !(mask & D16_SIGN); value++) /* count the number of mask bit shifts */ mask = mask << 1; /* until the correct one is found */ fprintf (st, "%u", value); /* display the mask bit number */ } break; case VAL_INTPRI: /* show the interrupt priority */ fprintf (st, "INTPRI=%u", dibptr->interrupt_priority); break; case VAL_SRNO: /* show the service request number */ if (dibptr->service_request_number == SRNO_UNUSED) /* if the current setting is "unused" */ fprintf (st, "SRNO not used"); /* then report it */ else /* otherwise report the SR number */ fprintf (st, "SRNO=%u", dibptr->service_request_number); break; default: /* if an illegal code was passed */ return SCPE_IERR; /* then report an internal coding error */ } return SCPE_OK; /* return the display result */ } /* System interface global utility routines */ /* Print a CPU instruction in symbolic format. This routine is called to format and print an instruction in mnemonic form. The "ofile" parameter is the opened output stream, "val [*]" contains the word(s) comprising the machine instruction to print, "radix" contains the desired operand radix or zero if the default radix is to be used, and "switches" includes the SIM_SW_STOP switch if the routine was called as part of a simulation stop. The routine returns a status code to the caller. SCPE_OK status is returned if the print consumed a single-word value, or the negative number of extra words (beyond the first) consumed by printing the instruction is returned. For example, printing a symbol that resulted in two words being consumed (from val [0] and val [1]) would return SCPE_OK_2_WORDS (= -1). HP 3000 machine instructions are generally classified by the first four bits. Within each class, additional bits identify sub-classes or individual instructions. Most of the decoding work is handled by the "fprint_instruction" routine, which prints mnemonics and operands and returns a status code indicating the number of words consumed for the current instruction. Implementation notes: 1. For a stack instruction, if the R (right stack-op pending) bit in the status word is set, and the request is for a simulation stop, the left-hand opcode will print as dashes to indicate that it has already been executed. 2. The status return from "fprint_instruction" is always SCPE_OK for the stack_ops table, which is fully decoded, so the return value from printing the left stack opcode is not used. */ t_stat fprint_cpu (FILE *ofile, t_value *val, uint32 radix, int32 switches) { const char *dashes = "----,"; t_stat status = SCPE_OK; switch (SUBOP (val [0])) { /* dispatch based on the instruction sub-opcode */ case 000: /* stack operations */ if (STA & STATUS_R && switches & SIM_SW_STOP) /* if right stack-op pending and this is a simulation stop */ fputs (dashes + 4 /* then indicate that the left stack-op has completed */ - strlen (stack_ops [STACKOP_A (val [0])].mnemonic), ofile); else { /* otherwise */ (void) fprint_instruction (ofile, stack_ops, /* print the left operation */ val, STACKOP_A_MASK, /* while discarding the status */ STACKOP_A_SHIFT, radix); fputc (',', ofile); /* add a separator */ } status = fprint_instruction (ofile, stack_ops, /* print the right operation */ val, STACKOP_B_MASK, STACKOP_B_SHIFT, radix); break; case 001: /* shift/branch/bit operations */ status = fprint_instruction (ofile, sbb_ops, /* print the operation */ val, SBBOP_MASK, SBBOP_SHIFT, radix); break; case 002: /* move/special/firmware/immediate/field/register operations */ status = fprint_instruction (ofile, msfifr_ops, /* print the operation */ val, MSFIFROP_MASK, MSFIFROP_SHIFT, radix); break; case 003: /* I/O/control/program/immediate/memory operations */ if (val [0] & IOCPIMOP_MASK) /* if it is a program, immediate, or memory instruction */ status = fprint_instruction (ofile, pmi_ops, /* then print the operation */ val, IOCPIMOP_MASK, IOCPIMOP_SHIFT, radix); else /* otherwise it is an I/O or control operation */ status = fprint_instruction (ofile, ioc_ops, /* so print the operation */ val, IOCSUBOP_MASK, IOCSUBOP_SHIFT, radix); break; default: /* memory, loop, and branch operations */ status = fprint_instruction (ofile, mlb_ops, /* print the operation */ val, MLBOP_MASK, MLBOP_SHIFT, radix); break; } return status; /* return the consumption status */ } /* Print an EDIT instruction subprogram operation in symbolic format. This routine prints a single EDIT subprogram operation. The opcode name is obtained from the name table and printed, followed by the operands. On entry, the "ofile" parameter is the opened output stream, "val" is NULL or points to an array containing the first two words of the operation, "radix" contains the desired operand radix or zero if the default radix is to be used, and "byte_address" points at the first byte of the subprogram operation. The return value is the number of subprogram bytes used by the operation. This routine is called by the SCP "EXAMINE" command processor to display an EDIT subprogram contained in memory in symbolic format. It is also called by the EDIT instruction executor when tracing memory operands. In the former case, the "EXAMINE" command has obtained two words from memory and placed them in the array pointed to by the "val" parameter. In the latter case, "val" will be NULL, indicating that the array must be loaded explicitly. In either case, additional memory bytes are obtained as needed by the operation decoder. For each operation, the following type of operand and the number of bytes required are listed below: Opcode SubOp Mnem Operand Byte Count ------ ----- ---- --------- ---------- 00 - MC ux 1-2 01 - MA ux 1-2 02 - MN ux 1-2 03 - MNS ux 1-2 04 - MFL ux 1-2 05 - IC ux,'c' 2-3 06 - ICS ux,'c' 2-3 07 - ICI ux,"s" 2-257 10 - ICSI ux,"s" 2-257 11 - BRIS sx 1-2 12 - SUFT sx 1-2 13 - SUFS sx 1-2 14 - ICP 'p' 1 15 - ICPS 'p' 1 16 - IS u,"s","s" 1-31 17 00 TE (none) 1 17 01 ENDF (none) 1 17 02 SST1 (none) 1 17 03 SST0 (none) 1 17 04 MDWO (none) 1 17 05 SFC 'c' 2 17 06 SFLC 'p','p' 2 17 07 DFLC 'c','c' 3 17 10 SETC ui 2 17 11 DBNZ s 2 Where the operands are: Operand Meaning ------- ----------------------------------------------- u unsigned value 0 .. 15 ux unsigned value 1 .. 15 or 0 .. 255 (extended) ui unsigned value 1 .. 256 s signed value -128 .. +127 sx signed value 1 .. 15 or -128 .. +127 (extended) 'p' punctuation character value ' ' + 0 .. 15 'c' character value 0 .. 255 "s" string value of 0 .. 255 characters Note that in only three cases must additional memory accesses be performed. Implementation notes: 1. Operation decoding is simplified by translating the prefix opcode (17) and subopcodes (00-11) into extended opcodes 17+0 to 17+11 (17-30). 2. Branch operation displacements are printed as positive to indicate a forward jump and negative to indicate a backward jump. The radix used is the CPU's address radix, which is octal. 3. The Machine Instruction Set manual says that the DBNZ ("decrement loop count and branch") operation adds the displacement operand. However, the microcode manual shows that the displacement is actually subtracted. Therefore, we print the operand as a negative value to conform with the hardware action. 4. A simple GET_BYTE macro is defined for easy access to the bytes contained in the "val" array. The macro also increments the byte address and increments the word array pointer as needed. */ #define GET_BYTE(b) \ if (byte_address++ & 1) \ b = LOWER_BYTE (*val++); \ else \ b = UPPER_BYTE (*val) uint32 fprint_edit (FILE *ofile, t_value *val, uint32 radix, uint32 byte_address) { uint8 byte, opcode, operand; uint32 word_address, byte_count, disp_radix; t_value eval_array [2]; if (radix == 0) /* if the supplied radix is defaulted */ disp_radix = 10; /* then assume decimal numeric output */ else /* otherwise */ disp_radix = radix; /* use the radix given */ if (val == NULL) { /* if the "val" array has not been loaded */ word_address = byte_address / 2; /* then get the word address of the operation */ mem_examine (&eval_array [0], word_address, NULL, 0); /* load the two words */ mem_examine (&eval_array [1], word_address + 1, NULL, 0); /* containing the target operation */ val = eval_array; /* point at the array containing the operation */ } GET_BYTE (byte); /* get the opcode byte */ byte_count = 1; /* and count it */ opcode = UPPER_HALF (byte); /* separate the opcode */ operand = LOWER_HALF (byte); /* and the immediate operand */ if (operand == 0 && opcode <= EDIT_SUFS) { /* if an extended immediate operand is indicated */ GET_BYTE (operand); /* then get it */ byte_count = 2; /* and count it */ } if (opcode == EDIT_EXOP) /* if the opcode is extended */ opcode = opcode + operand; /* then translate it */ if (opcode <= EDIT_DBNZ) { /* if the extended opcode is valid */ fputs (edit_ops [opcode], ofile); /* then print the associated mnemonic */ if (opcode < EDIT_TE || opcode > EDIT_MDWO) /* if the operation has an operand */ fputc (' ', ofile); /* then add a space as a separator */ } switch (opcode) { /* dispatch by the extended opcode */ case 000: /* MC - move characters */ case 001: /* MA - move alphabetics */ case 002: /* MN - move numerics */ case 003: /* MNS - move numerics suppressed */ case 004: /* MFL - move numerics with floating insertion */ fprint_value (ofile, operand, disp_radix, /* print the character count */ D8_WIDTH, PV_LEFT); break; case 005: /* IC - insert character */ case 006: /* ICS - insert character suppressed */ GET_BYTE (byte); /* get the insertion character */ byte_count = byte_count + 1; /* and count it */ fprint_value (ofile, operand, disp_radix, /* print the insertion count */ D8_WIDTH, PV_LEFT); fputc (',', ofile); /* add a separator */ fputs (fmt_char ((uint32) byte), ofile); /* and print the insertion character */ break; case 007: /* ICI - insert characters immediate */ case 010: /* ICSI - insert characters suppressed immediate */ fprint_value (ofile, operand, disp_radix, /* print the character count */ D8_WIDTH, PV_LEFT); fputs (",\"", ofile); /* add a separator and open the quotation */ fputs (fmt_byte_operand (byte_address, operand), ofile); /* print the string operand */ fputc ('"', ofile); /* and close the quotation */ byte_count = byte_count + operand; /* count the bytes */ break; case 030: /* DBNZ - decrement loop count and branch */ GET_BYTE (operand); /* get the signed branch displacement */ byte_count = byte_count + 1; /* and count it */ if (operand == 0200) { /* if the operand is -128 */ fputc ('+', ofile); /* then print a plus sign for display */ fprint_value (ofile, operand, cpu_dev.aradix, /* and the displacement */ D8_WIDTH, PV_LEFT); /* in the CPU's address radix */ break; /* and we're done */ } else /* otherwise */ operand = NEG8 (operand); /* negate the operand for display */ /* fall through into the BRIS case */ case 011: /* BRIS - branch if significance */ if (operand & D8_SIGN) { /* if the displacement is negative */ fputc ('-', ofile); /* then print a minus sign */ operand = NEG8 (operand); /* and make the value positive */ } else /* otherwise */ fputc ('+', ofile); /* print a plus sign */ fprint_value (ofile, operand, cpu_dev.aradix, /* print the displacement */ D8_WIDTH, PV_LEFT); /* in the CPU's address radix */ break; case 012: /* SUFT - subtract from target */ case 013: /* SUFS - subtract from source */ if (operand & D8_SIGN) { /* if the displacement is negative */ fputc ('-', ofile); /* then print a minus sign */ operand = NEG8 (operand); /* and make the value positive */ } fprint_value (ofile, operand, disp_radix, /* print the displacement */ D8_WIDTH, PV_LEFT); break; case 014: /* ICP - insert character punctuation */ case 015: /* ICPS - insert character punctuation suppressed */ fputs (fmt_char ((uint32) (operand + ' ')), ofile); /* print the punctuation character */ break; case 016: /* IS - insert character on sign */ fprint_value (ofile, operand, disp_radix, /* print the character count */ D8_WIDTH, PV_LEFT); fputs (",\"", ofile); /* add a separator and open the quotation */ fputs (fmt_byte_operand (byte_address, operand), ofile); /* print the string operand */ fputs ("\",\"", ofile); /* close and open the second quotation */ fputs (fmt_byte_operand (byte_address + operand, operand), ofile); /* print the string operand */ fputc ('"', ofile); /* and close the quotation */ byte_count = byte_count + 2 * operand; /* count the bytes */ break; case 017: /* TE - terminate edit */ case 020: /* ENDF - end floating point insertion */ case 021: /* SST1 - set significance to 1 */ case 022: /* SST0 - set significance to 0 */ case 023: /* MDWO - move digit with overpunch */ break; /* these opcodes have no operands */ case 024: /* SFC - set fill character */ GET_BYTE (byte); /* get the fill character */ byte_count = byte_count + 1; /* and count it */ fputs (fmt_char ((uint32) byte), ofile); /* print the fill character */ break; case 025: /* SFLC - set float character */ GET_BYTE (byte); /* get the float character */ byte_count = byte_count + 1; /* and count it */ fputs (fmt_char ((uint32) (UPPER_HALF (byte) + ' ')), ofile); /* print the positive float character */ fputc (',', ofile); /* and a separator */ fputs (fmt_char ((uint32) (LOWER_HALF (byte) + ' ')), ofile); /* and the negative float character */ break; case 026: /* DFLC - define float character */ GET_BYTE (byte); /* get the float character */ fputs (fmt_char ((uint32) byte), ofile); /* print the positive float character */ GET_BYTE (byte); /* get the float character */ byte_count = byte_count + 2; /* and count both characters */ fputc (',', ofile); /* print a separator */ fputs (fmt_char ((uint32) byte), ofile); /* and the negative float character */ break; case 027: /* SETC - set loop count */ GET_BYTE (byte); /* get the loop count */ byte_count = byte_count + 1; /* and count it */ if (byte == 0) /* if the count is zero */ fprint_value (ofile, 256, disp_radix, /* then the loop executes 256 times */ D16_WIDTH, PV_LEFT); else /* otherwise */ fprint_value (ofile, byte, disp_radix, /* print the given loop count */ D8_WIDTH, PV_LEFT); break; default: /* the opcode is invalid */ if (radix == 0) /* if the supplied radix is defaulted */ disp_radix = cpu_dev.dradix; /* then use the data radix */ else /* otherwise */ disp_radix = radix; /* use the radix given */ fprint_value (ofile, EDIT_EXOP, disp_radix, /* print the extended opcode */ D4_WIDTH, PV_RZRO); fputc (',', ofile); /* print a separator */ fprint_value (ofile, opcode - EDIT_EXOP, /* and the invalid opcode in numeric form */ disp_radix, D4_WIDTH, PV_RZRO); break; } return byte_count; /* return the number of bytes used by the operation */ } /* Format the status register flags and condition code. This routine formats the flags and condition code part of the status register and returns a pointer to the formatted string. It does not format the current code segment number part of the register. The six status flags are represented by letters. If the flag is set, an uppercase letter is used; if it is clear, a lowercase letter is used. The condition code is represented by the strings "CCL", "CCE", or "CCG" for the less than, equal to, or greater than conditions. If the condition code is the invalid value, "CC?" is used. */ const char *fmt_status (uint32 status) { static const char conditions [] = "GLE?"; static const char flags [] = "m i t r o c CCx"; static char formatted [sizeof flags]; uint32 index; strcpy (formatted, flags); /* copy the initial flags template */ formatted [14] = conditions [TO_CCN (status)]; /* set the condition code representation */ for (index = 0; index < 6 * 2; index = index + 2) { /* loop through the six MSBs (the flags) */ if (status & D16_SIGN) /* if the bit is set */ formatted [index] = /* then convert the corresponding flag */ (char) toupper (formatted [index]); /* to upper case */ status = status << 1; /* position the next flag for testing */ } return formatted; /* return a pointer to the formatted string */ } /* Format a character for printing. This routine formats a single 8-bit character value into a printable string and returns a pointer to that string. Printable characters retain their original form but are enclosed in single quotes. Control characters are translated to readable strings. Characters outside of the ASCII range are presented as escaped octal values. Implementation notes: 1. The longest string to be returned is a five-character escaped string consisting of a backslash, three octal digits, and a trailing NUL. The end-of-buffer pointer has an allowance to ensure that the string will fit. 2. The routine returns a pointer to a static buffer containing the printable string. To allow the routine to be called more than once per trace line, the null-terminated format strings are concatenated in the buffer, and each call returns a pointer that is offset into the buffer to point at the latest formatted string. 3. There is no explicit buffer-free action. Instead, newly formatted strings are appended to the buffer until there is no more space available. At that point, the pointers are reset to the start of the buffer. In effect, this provides a circular buffer, as previously formatted strings are overwritten by subsequent calls. 4. The buffer is sized to hold the maximum number of concurrent strings needed for a single trace line. If more concurrent strings are used, one or more strings from the earliest calls will be overwritten. */ const char *fmt_char (uint32 charval) { static const char *const control [] = { "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US" }; static char fmt_buffer [64]; /* the return buffer */ static char *freeptr = fmt_buffer; /* pointer to the first free character in the buffer */ static char *endptr = fmt_buffer + sizeof fmt_buffer - 5; /* pointer to the end of the buffer (less allowance) */ const char *fmtptr; if (charval <= '\037') /* if the value is an ASCII control character */ return control [charval]; /* then return a readable representation */ else if (charval == '\177') /* otherwise if the value is the delete character */ return "DEL"; /* then return a readable representation */ else { if (freeptr > endptr) /* if there is not enough room left to append the string */ freeptr = fmt_buffer; /* then reset to point at the start of the buffer */ fmtptr = freeptr; /* initialize the return pointer */ *freeptr = '\0'; /* and the format accumulator */ if (charval > '\177') /* otherwise if the value is beyond the printable range */ freeptr = freeptr + sprintf (freeptr, "\\%03o", /* then format the value */ charval & D8_MASK); /* and update the free pointer */ else { /* otherwise it's a printable character */ *freeptr++ = '\''; /* so form a representation */ *freeptr++ = (char) charval; /* containing the character */ *freeptr++ = '\''; /* surrounded by single quotes */ *freeptr = '\0'; } freeptr = freeptr + 1; /* advance past the NUL terminator */ return fmtptr; /* and return the formatted string */ } } /* Format a set of named bits. This routine formats a set of up to 32 named bits into a printable string and returns a pointer to that string. The names of the active bits are concatenated and separated by vertical bars. For example: SIO OK | ready | no error | unit 0 On entry, "bitset" is a value specifying the bits to format, and "bitfmt" is a BITSET_FORMAT structure describing the format to use. The structure contains a count and a pointer to an array of character strings specifying the names of the valid bits in "bitset", the offset in bits from the LSB to the least-significant named bit, the direction in which to process the bits (from MSB to LSB, or vice versa), whether or not alternate names are present in the name array, and whether or not to append a final separator. The names in the name array appear in the order corresponding to the supplied direction; invalid bits are indicated by NULL character names. The pointer returned points at a character buffer containing the names of the valid bits that are set in the supplied value. If no valid bits are set, then the buffer contains "(none)" if a trailing separator is omitted, or a null string ("") if a trailing separator is requested. The name_count and names fields describe the separately defined name string array. The array must start with the first valid bit but need only contain entries through the last valid bit; NULL entries for the remaining bits in the word are not necessary. For example, if bits 1-3 of a word are valid, then the name string array would have three entries. If bits 1-3 and 5 are valid, then the array would have five entries, with the fourth entry set to NULL. The offset field specifies the number of unnamed bits to the right of the least-significant named bit. Using the same examples as above, the offsets would be 12 and 10, respectively. The direction field specifies whether the bits are named from MSB to LSB (msb_first) or vice versa (lsb_first). The order of the entries in the name string array must match the direction specified. Continuing with the first example above, if the direction is msb_first, then the first name is for bit 1; if the direction is lsb_first, then the first name is for bit 3. The alternate field specifies whether (has_alt) or not (no_alt) alternate conditions are represented by one or more bits. Generally, bits represent Boolean conditions, e.g., a condition is present when the bit is 1 and absent when the bit is zero. In these cases, the corresponding bit name is included or omitted, respectively, in the return string. Occasionally, bits will represent alternate conditions, e.g., where condition A is present when the bit is 1, and condition B is present when the bit is 0. For these, the bit name string should consist of both condition names in that order, with the "1" name preceded by the '\1' character and the "0" name preceded by the '\0' character. For example, if 1 corresponds to "load" and 0 to "store", then the bit name string would be "\1load\0store". If alternate names are present, the has_alt identifier should be given, so that the indicated bits are checked for zero conditions. If no_alt is specified, the routine stops as soon as all of the one-bits have been processed. The bar field specifies whether (append_bar) or not (no_bar) a vertical bar separator is appended to the formatted string. Typically, a bitset represents a peripheral control or status word. If the word also contains multiple-bit fields, a trailing separator should be requested, and the decoded fields should be concatenated by the caller with any named bits. If the bitset is empty, the returned null string will present the proper display containing just the decoded fields. If the bitset completely describes the word, then no appended separator is needed. Peripheral control and status words generally are decoded from MSB to LSB. A bitset may also represent a set of inbound or outbound signals. These should be decoded from LSB to MSB, as that is the order in which they are executed by the device interface routines. The implementation first generates a mask for the significant bits and positions the mask with the offset specified. Then a test bit mask is generated; the bit is either the most- or least-significant bit of the bitset, depending on the direction indicated. For each name in the array of names, if the name is defined (not NULL), the corresponding bit in the bitset is tested. If it is set, the name is appended to the output buffer; otherwise, it is omitted (unless the name has an alternate, in which case the alternate is appended). The bitset is then shifted in the indicated direction, remasking to just the significant bits. Processing continues until there are no remaining significant bits (if no alternates are specified), or until there are no remaining names in the array (if alternates are specified). Implementation notes: 1. The routine returns a pointer to a static buffer containing the printable string. To allow the routine to be called more than once per trace line, the null-terminated format strings are concatenated in the buffer, and each call returns a pointer that is offset into the buffer to point at the latest formatted string. 2. There is no explicit buffer-free action. Instead, newly formatted strings are appended to the buffer until there is no more space available. At that point, the string currently being assembled is moved to the start of the buffer, and the pointers are reset. In effect, this provides a circular buffer, as previously formatted strings are overwritten by subsequent calls. 3. The buffer is sized to hold the maximum number of concurrent strings needed for a single trace line. If more concurrent strings are used, one or more strings from the earliest calls will be overwritten. If an attempt is made to format a string larger than the buffer, an error indication string is returned. 4. The location of the end of the buffer used to determine if the next name will fit includes an allowance for two separators that might be placed on either side of the name and a terminating NUL character. */ const char *fmt_bitset (uint32 bitset, const BITSET_FORMAT bitfmt) { static const char separator [] = " | "; /* the separator to use between names */ static char fmt_buffer [1024]; /* the return buffer */ static char *freeptr = fmt_buffer; /* pointer to the first free character in the buffer */ static char *endptr = fmt_buffer + sizeof fmt_buffer /* pointer to the end of the buffer */ - 2 * (sizeof separator - 1) - 1; /* less allowance for two separators and a terminator */ const char *bnptr, *fmtptr; uint32 test_bit, index, bitmask; size_t name_length; if (bitfmt.name_count < D32_WIDTH) /* if the name count is the less than the mask width */ bitmask = (1 << bitfmt.name_count) - 1; /* then create a mask for the name count specified */ else /* otherwise use a predefined value for the mask */ bitmask = D32_MASK; /* to prevent shifting the bit off the MSB end */ bitmask = bitmask << bitfmt.offset; /* align the mask to the named bits */ bitset = bitset & bitmask; /* and mask to just the significant bits */ if (bitfmt.direction == msb_first) /* if the examination is left-to-right */ test_bit = 1 << bitfmt.name_count + bitfmt.offset - 1; /* then create a test bit for the MSB */ else /* otherwise */ test_bit = 1 << bitfmt.offset; /* create a test bit for the LSB */ fmtptr = freeptr; /* initialize the return pointer */ *freeptr = '\0'; /* and the format accumulator */ index = 0; /* and the name index */ while ((bitfmt.alternate || bitset) /* while more bits */ && index < bitfmt.name_count) { /* and more names exist */ bnptr = bitfmt.names [index]; /* point at the name for the current bit */ if (bnptr) /* if the name is defined */ if (*bnptr == '\1' && bitfmt.alternate) /* then if this name has an alternate */ if (bitset & test_bit) /* then if the bit is asserted */ bnptr++; /* then point at the name for the "1" state */ else /* otherwise */ bnptr = bnptr + strlen (bnptr) + 1; /* point at the name for the "0" state */ else /* otherwise the name is unilateral */ if ((bitset & test_bit) == 0) /* so if the bit is denied */ bnptr = NULL; /* then clear the name pointer */ if (bnptr) { /* if the name pointer is set */ name_length = strlen (bnptr); /* then get the length needed */ if (freeptr + name_length > endptr) { /* if there is not enough room left to append the name */ strcpy (fmt_buffer, fmtptr); /* then move the partial string to the start of the buffer */ freeptr = fmt_buffer + (freeptr - fmtptr); /* point at the new first free character location */ fmtptr = fmt_buffer; /* and reset the return pointer */ if (freeptr + name_length > endptr) /* if there is still not enough room left to append */ return "(buffer overflow)"; /* then this call is requires a larger buffer! */ } if (*fmtptr != '\0') { /* if this is not the first name added */ strcpy (freeptr, separator); /* then add a separator to the string */ freeptr = freeptr + strlen (separator); /* and move the free pointer */ } strcpy (freeptr, bnptr); /* append the bit's mnemonic to the accumulator */ freeptr = freeptr + name_length; /* and move the free pointer */ } if (bitfmt.direction == msb_first) /* if formatting is left-to-right */ bitset = bitset << 1 & bitmask; /* then shift the next bit to the MSB and remask */ else /* otherwise formatting is right-to-left */ bitset = bitset >> 1 & bitmask; /* so shift the next bit to the LSB and remask */ index = index + 1; /* bump the bit name index */ } if (*fmtptr == '\0') /* if no names were output */ if (bitfmt.bar == append_bar) /* then if concatenating with more information */ return ""; /* then return an empty string */ else /* otherwise it's a standalone format */ return "(none)"; /* so return a placeholder */ else if (bitfmt.bar == append_bar) { /* otherwise if a trailing separator is specified */ strcpy (freeptr, separator); /* then add a separator to the string */ freeptr = freeptr + strlen (separator) + 1; /* and account for it plus the trailing NUL */ } else /* otherwise */ freeptr = freeptr + 1; /* just account for the trailing NUL */ return fmtptr; /* return a pointer to the formatted string */ } /* Format and print a debugging trace line to the debug log. A formatted line is assembled and sent to the previously opened debug output stream. On entry, "dptr" points to the device issuing the trace, "flag" is the debug flag that has enabled the trace, and the remaining parameters consist of the format string and associated values. This routine is usually not called directly but rather via the "dprintf" macro, which tests that debugging is enabled for the specified flag before calling this function. This eliminates the calling overhead if debugging is disabled. This routine prints a prefix before the supplied format string consisting of the device name (in upper case) and the debug flag name (in lower case), e.g.: >>MPX state: Channel SR 3 entered State A ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ prefix supplied format string The names are padded to the lengths of the largest device name and debug flag name among the devices enabled for debugging to ensure that all trace lines will align for easier reading. Implementation notes: 1. ISO C99 allows assignment expressions as the bounds for array declarators. VC++ 2008 requires constant expressions. To accommodate the latter, we must allocate "sufficiently large" arrays for the flag name and format, rather than arrays of the exact size required by the call parameters. */ #define FLAG_SIZE 32 /* sufficiently large to accommodate all flag names */ #define FORMAT_SIZE 1024 /* sufficiently large to accommodate all format strings */ void hp_debug (DEVICE *dptr, uint32 flag, ...) { va_list argptr; DEBTAB *debptr; char *format, *fptr; const char *nptr; char flag_name [FLAG_SIZE]; /* desired size is [flag_size + 1] */ char header_fmt [FORMAT_SIZE]; /* desired size is [device_size + flag_size + format_size + 6] */ if (sim_deb != NULL && dptr != NULL) { /* if the output stream and device pointer are valid */ debptr = dptr->debflags; /* then get a pointer to the debug flags table */ if (debptr != NULL) /* if the debug table exists */ while (debptr->name != NULL) /* then search it for an entry with the supplied flag */ if (debptr->mask & flag) { /* if the flag matches this entry */ nptr = debptr->name; /* then get a pointer to the flag name */ fptr = flag_name; /* and the buffer */ do *fptr++ = (char) tolower (*nptr); /* copy and downshift the flag name */ while (*nptr++ != '\0'); sprintf (header_fmt, ">>%-*s %*s: ", /* format the prefix and store it */ (int) device_size, sim_dname (dptr), /* while padding the device and flag names */ (int) flag_size, flag_name); /* as needed for proper alignment */ va_start (argptr, flag); /* set up the argument list */ format = va_arg (argptr, char *); /* get the format string parameter */ strcat (header_fmt, format); /* append the supplied format */ vfprintf (sim_deb, header_fmt, argptr); /* format and print to the debug stream */ va_end (argptr); /* clean up the argument list */ break; /* and exit with the job complete */ } else /* otherwise */ debptr++; /* look at the next debug table entry */ } return; } /* Check for device conflicts. The device information blocks (DIBs) for the set of enabled devices are checked for consistency. Each device number, interrupt priority number, and service request number must be unique among the enabled devices. These requirements are checked as part of the instruction execution prelude; this allows the user to exchange two device numbers (e.g.) simply by setting each device to the other's device number. If conflicts were enforced instead at the time the numbers were entered, the first device would have to be set to an unused number before the second could be set to the first device's number. The routine begins by filling in a DIB value table from all of the device DIBs to allow indexed access to the values to be checked. Unused DIB values and values corresponding to devices that have no DIBs or are disabled are set to the corresponding UNUSED constants. As part of the device scan, the sizes of the largest device name and active debug flag name among the devices enabled for debugging are accumulated for use in aligning the debug tracing statements. After the DIB value table is filled in, a conflict check is made for each conflict type (i.e., device number, interrupt priority, or service request number). For each check, a conflict table is built, where each array element is set to the count of devices that contain DIB values equal to the element index. For example, when processing device number values, conflict table element 6 is set to the count of devices that have dibptr->device_number set to 6. If any conflict table element is set more than once, the "conflict_is" variable is set to the type of conflict. If any conflicts exist for the current type, the conflict table is scanned. A conflict table element value (i.e., device count) greater than 1 indicates a conflict. For each such value, the DIB value table is scanned to find matching values, and the device names associated with the matching values are printed. This routine returns TRUE if any conflicts exist and FALSE there are none. Implementation notes: 1. When this routine is called, the console and optional log file have already been put into "raw" output mode. Therefore, newlines are not translated to the correct line ends on systems that require it. Before reporting a conflict, "sim_ttcmd" is called to restore the console and log file translation. This is OK because a conflict will abort the run and return to the command line anyway. 2. sim_dname is called instead of using dptr->name directly to ensure that we pick up an assigned logical device name. 3. Only the names of active trace (debug) options are accumulated to produce the most compact trace log. However, if the CPU device's EXEC option is enabled, then all of the CPU option names are accumulated, as EXEC enables all trace options for a given instruction or instruction class. 4. Even though the routine is called only from the sim_instr routine in the CPU simulator module, it must be located here to use the DEVICE_COUNT constant to allocate the dib_val matrix. If it were located in the CPU module, the matrix would have to be allocated dynamically after a run-time determination of the count of simulator devices. */ t_bool hp_device_conflict (void) { #define CONFLICT_COUNT 3 /* the number of conflict types to check */ typedef enum { /* conflict types */ Device, /* device number conflict */ Interrupt, /* interrupt priority conflict */ Service, /* service request number conflict */ None /* no conflict */ } CONFLICT_TYPE; static const uint32 max_number [CONFLICT_COUNT] = { /* the last element index, in CONFLICT_TYPE order */ DEVNO_MAX, INTPRI_MAX, SRNO_MAX }; static const char *conflict_label [CONFLICT_COUNT] = { /* the conflict names, in CONFLICT_TYPE order */ "Device number", "Interrupt priority", "Service request number" }; const DIB *dibptr; const DEBTAB *tptr; DEVICE *dptr; size_t name_length, flag_length; uint32 dev, val; CONFLICT_TYPE conf, conflict_is; int32 count; int32 dib_val [DEVICE_COUNT] [CONFLICT_COUNT]; int32 conflicts [DEVNO_MAX + 1]; device_size = 0; /* reset the device and flag name sizes */ flag_size = 0; /* to those of the devices actively debugging */ for (dev = 0; dev < DEVICE_COUNT; dev++) { /* fill in the DIB value table */ dptr = (DEVICE *) sim_devices [dev]; /* from the device table */ dibptr = (DIB *) dptr->ctxt; /* and their associated DIBs */ if (dibptr && !(dptr->flags & DEV_DIS)) { /* if the DIB is defined and the device is enabled */ dib_val [dev] [Device] = dibptr->device_number; /* then copy the values to the DIB table */ dib_val [dev] [Interrupt] = dibptr->interrupt_priority; dib_val [dev] [Service] = dibptr->service_request_number; } else { /* otherwise the device will not participate in I/O */ dib_val [dev] [Device] = DEVNO_UNUSED; /* so set this table entry */ dib_val [dev] [Interrupt] = INTPRI_UNUSED; /* to the "unused" values */ dib_val [dev] [Service] = SRNO_UNUSED; } if (sim_deb && dptr->dctrl) { /* if debugging is active for this device */ name_length = strlen (sim_dname (dptr)); /* then get the length of the device name */ if (name_length > device_size) /* if it's greater than the current maximum */ device_size = name_length; /* then reset the size */ if (dptr->debflags) /* if the device has a debug flags table */ for (tptr = dptr->debflags; /* then scan the table */ tptr->name != NULL; tptr++) if (dev == 0 && dptr->dctrl & DEB_EXEC /* if the CPU device is tracing executions */ || tptr->mask & dptr->dctrl) { /* or this debug option is active */ flag_length = strlen (tptr->name); /* then get the flag name length */ if (flag_length > flag_size) /* if it's greater than the current maximum */ flag_size = flag_length; /* then reset the size */ } } } conflict_is = None; /* assume that no conflicts exist */ for (conf = Device; conf <= Service; conf++) { /* check for conflicts for each type */ memset (conflicts, 0, sizeof conflicts); /* zero the conflict table for each check */ for (dev = 0; dev < DEVICE_COUNT; dev++) /* populate the conflict table from the DIB value table */ if (dib_val [dev] [conf] >= 0) /* if this device has an assigned value */ if (++conflicts [dib_val [dev] [conf]] > 1) /* then increment the count of references */ conflict_is = conf; /* if there is more than one reference, a conflict occurs */ if (conflict_is == conf) { /* if a conflict exists for this type */ sim_ttcmd (); /* then restore the console and log I/O mode */ for (val = 0; val <= max_number [conf]; val++) /* search the conflict table for the next conflict */ if (conflicts [val] > 1) { /* if a conflict is present for this value */ count = conflicts [val]; /* then get the number of conflicting devices */ cprintf ("%s %u conflict (", conflict_label [conf], val); dev = 0; /* search for the devices that conflict */ while (count > 0) { /* search the DIB value table */ if (dib_val [dev] [conf] == (int32) val) { /* to find the conflicting entries */ if (count < conflicts [val]) /* and report them to the console */ cputs (" and "); cputs (sim_dname ((DEVICE *) sim_devices [dev])); count = count - 1; } dev = dev + 1; } cputs (")\n"); } } } return (conflict_is != None); /* return TRUE if any conflicts exist */ } /* System interface local SCP support routines */ /* One-time initialization. This routine is called once by the SCP startup code. It fills in the auxiliary command table from the corresponding system command table entries, sets up the VM-specific routine pointers, and registers the supported breakpoint types. */ void hp_one_time_init (void) { CTAB *contab, *systab, *auxtab = aux_cmds; static int inited = 0; if (inited == 1) /* Be sure to only do these things once */ return; inited = 1; sim_vm_release = hp_vm_release; sim_vm_release_message = hp_vm_release_message; contab = find_cmd ("CONT"); /* find the entry for the CONTINUE command */ while (auxtab->name != NULL) { /* loop through the auxiliary command table */ systab = find_cmd (auxtab->name); /* find the corresponding system command table entry */ if (systab != NULL) { /* if it is present */ if (auxtab->action == NULL) /* then if the action routine field is empty */ auxtab->action = systab->action; /* then fill it in */ if (auxtab->arg == 0) /* if the command argument field is empty */ auxtab->arg = systab->arg; /* then fill it in */ if (auxtab->help == NULL) /* if the help string field is empty */ auxtab->help = systab->help; /* then fill it in */ auxtab->help_base = systab->help_base; /* fill in the help base and message fields */ auxtab->message = systab->message; /* as we never override them */ } if (auxtab->action == &cpu_cold_cmd /* if this is the LOAD or DUMP command entry */ || auxtab->action == &cpu_power_cmd) /* or the POWER command entry */ auxtab->message = contab->message; /* then set the execution completion message routine */ auxtab++; /* point at the next table entry */ } sim_vm_cmd = aux_cmds; /* set up the auxiliary command table */ sim_vm_fprint_stopped = &fprint_stopped; /* set up the simulation-stop printer */ sim_vm_fprint_addr = &fprint_addr; /* set up the address printer */ sim_vm_parse_addr = &parse_addr; /* set up the address parser */ sim_brk_types = BP_SUPPORTED; /* register the supported breakpoint types */ sim_brk_dflt = BP_EXEC; /* the default breakpoint type is "execution" */ return; } /* Format and print a VM simulation stop message. When the instruction loop is exited, a simulation stop message is printed and control returns to SCP. An SCP stop prints a message with this format: , P: () For example: SCPE_STOP prints "Simulation stopped, P: 24713 (LOAD 1)" SCPE_STEP prints "Step expired, P: 24713 (LOAD 1)" For VM stops, this routine is called after the message has been printed and before the comma and program counter label and value are printed. Depending on the reason for the stop, the routine may insert additional information, and it may request omission of the PC value by returning FALSE instead of TRUE. This routine modifies the default output for these stop codes: STOP_SYSHALT prints "System halt 3, P: 24713 (LOAD 1)" STOP_HALT prints "Programmed halt, CIR: 030365 (HALT 5), P: 24713 (LOAD 1)" STOP_CDUMP prints "Cold dump complete, CIR: 000020" Implementation notes: 1. HALT instructions are always one word in length, so only sim_eval [0] needs to be set up before calling fprint_cpu. 2. The system halt reason is present in RA. */ static t_bool fprint_stopped (FILE *st, t_stat reason) { if (reason == STOP_HALT) { /* if this is a halt instruction stop */ sim_eval [0] = CIR; /* then save the instruction for evaluation */ fputs (", CIR: ", st); /* print the register label */ fprint_val (st, CIR, cpu_dev.dradix, /* and the numeric value */ cpu_dev.dwidth, PV_RZRO); fputs (" (", st); /* print the halt mnemonic */ fprint_cpu (st, sim_eval, 0, SIM_SW_STOP); /* (which cannot fail) */ fputc (')', st); /* within parentheses */ return TRUE; /* return TRUE to append the program counter */ } else if (reason == STOP_CDUMP) { /* otherwise if this is a cold dump completion stop */ fputs (", CIR: ", st); /* then print the register label */ fprint_val (st, CIR, cpu_dev.dradix, /* and the numeric value */ cpu_dev.dwidth, PV_RZRO); fputc ('\n', st); /* append an end-of-line character */ return FALSE; /* and return FALSE to omit the program counter */ } else if (reason == STOP_SYSHALT) { /* otherwise if this is a system halt stop */ fprintf (st, " %u", RA); /* then print the halt reason */ return TRUE; /* and return TRUE to append the program counter */ } else /* otherwise all other stops */ return TRUE; /* return TRUE to append the program counter */ } /* Format and print a memory address. This routine is called by SCP to print memory addresses. It is also called to print the contents of registers tagged with the REG_VMAD flag. On entry, the "st" parameter is the opened output stream, "dptr" points to the device to which the address refers, and "addr" contains the address to print. The routine prints the linear address in . form for the CPU and as a scalar value for all other devices. */ static void fprint_addr (FILE *st, DEVICE *dptr, t_addr addr) { uint32 bank, offset; if (dptr == &cpu_dev) { /* if the address originates in the CPU */ bank = TO_BANK (addr); /* then separate bank and offset */ offset = TO_OFFSET (addr); /* from the linear address */ fprint_val (st, bank, dptr->aradix, BA_WIDTH, PV_RZRO); /* print the bank address */ fputc ('.', st); /* followed by a period */ fprint_val (st, offset, dptr->aradix, LA_WIDTH, PV_RZRO); /* and concluding with the offset */ } else /* otherwise print the value */ fprint_val (st, addr, dptr->aradix, dptr->awidth, PV_LEFT); /* as a scalar for all other devices */ return; } /* Parse a memory address. This routine is called by SCP to parse memory addresses. It is also called to parse values to be stored in registers tagged with the REG_VMAD flag. On entry, the "dptr" parameter points to the device to which the address refers, and "cptr" points to the first character of the address operand on the command line. On exit, the linear address is returned, and the pointer pointed to by "tptr" is set to point at the first character after the parsed address. Parsing errors, including use of features disallowed by the command in process, are indicated by the "tptr" pointer being set to "cptr". The HP 3000 divides memory into 64K-word banks. Each bank is identified by a bank address from 0-15. The current bank addresses for the program, data, and stack segments are kept in the PBANK, DBANK, and SBANK registers. The simulator supports only linear addresses for all devices other than the CPU. For the CPU, two forms of address entries are allowed: - an absolute address consisting of a 4-bit bank address and a 16-bit offset within the bank, separated by a period (e.g., 17.177777) - a relative address consisting of a 16-bit offset within a bank specified by a bank register (e.g., 177777). Command line switches modify the interpretation of relative addresses as follows: * -P specifies an implied bank address obtained from PBANK * -S specifies an implied bank address obtained from SBANK * no switch specifies an implied bank address obtained from DBANK The "parse_config" global specifies the allowed parse configurations. For example, the memory examine/deposit commands allow both absolute addresses and offsets from any of the three bank registers, whereas the run command only allows an implied offset from PBANK. */ static t_addr parse_addr (DEVICE *dptr, CONST char *cptr, CONST char **tptr) { CONST char *sptr; uint32 overrides; t_addr bank; t_addr address = 0; if (dptr != &cpu_dev) /* if this is not a CPU memory address */ return (t_addr) strtotv (cptr, tptr, dptr->aradix); /* then parse a scalar and return the value */ overrides = sim_switches & (SWMASK ('P') | SWMASK ('S')); /* mask to just the bank address overrides */ if (overrides && !(parse_config & apcBank_Override) /* if overrides are present but not allowed */ || overrides & ~SWMASK ('P') && overrides & ~SWMASK ('S')) /* or multiple overrides are specified */ *tptr = cptr; /* then report a parse error */ else /* otherwise the switches are consistent */ address = strtotv (cptr, tptr, dptr->aradix); /* so parse the address */ if (cptr != *tptr) /* if the parse succeeded */ if (**tptr == '.') /* then if this a banked address */ if (! (parse_config & apcBank_Offset) /* but it is not allowed */ || address > BA_MAX) /* or the bank number is out of range */ *tptr = cptr; /* then report a parse error */ else { /* otherwise the . form is allowed */ sptr = *tptr + 1; /* point to the offset */ bank = address; /* save the first part as the bank address */ address = strtotv (sptr, tptr, dptr->aradix); /* parse the offset */ if (address > LA_MAX) /* if the offset is too large */ *tptr = cptr; /* then report a parse error */ else /* otherwise it is in range */ address = TO_PA (bank, address); /* so form the linear address */ } else if (address > LA_MAX) /* otherwise if the non-banked offset is too large */ *tptr = cptr; /* then report a parse error */ else if (overrides & SWMASK ('S')) /* otherwise if the stack-bank override is specified */ address = TO_PA (SBANK, address); /* then base the address on SBANK */ else if (overrides & SWMASK ('P')) /* otherwise if the program-bank override is specified */ address = TO_PA (PBANK, address); /* then base the address on PBANK */ else if (parse_config & apcDefault_PBANK) /* otherwise if PBANK is the default */ if (PB <= address && address <= PL) /* then if the address lies within the segment limits */ address = TO_PA (PBANK, address); /* then base the address on PBANK */ else /* otherwise it is outside of the segment */ *tptr = cptr; /* so report a parse error */ else if (parse_config & apcDefault_DBANK) /* otherwise if the default is DBANK */ address = TO_PA (DBANK, address); /* then base the address on DBANK */ return address; /* return the linear address */ } /* Execute the EXAMINE, DEPOSIT, IEXAMINE, and IDEPOSIT commands. These commands are intercepted to configure address parsing. The following address forms are valid: EXAMINE . EXAMINE EXAMINE -P EXAMINE -S This routine configures the address parser and calls the standard command handler. */ static t_stat hp_exdep_cmd (int32 arg, CONST char *buf) { parse_config = apcBank_Offset | /* allow the . address form */ apcBank_Override | /* allow bank override switches */ apcDefault_DBANK; /* set the default bank register to DBANK */ return exdep_cmd (arg, buf); /* return the result of the standard handler */ } /* Execute the RUN and GO commands. These commands are intercepted to configure address parsing. The following address form is valid: RUN { } GO { } This routine configures the address parser and calls the standard command handler. The , if specified, must lie between PB and PL, or the command will be rejected when the offset is parsed. Implementation notes: 1. The RUN command uses the RU_GO argument instead of RU_RUN so that the run_cmd SCP routine will not reset all devices before entering the instruction executor. As is done in hardware, resetting the CPU clears the ICS flag, which corrupts the CPU state set up after a cold load. A CPU reset is only valid prior to a cold load -- never when a program is resident in memory. */ static t_stat hp_run_cmd (int32 arg, CONST char *buf) { parse_config = apcDefault_PBANK; /* set the default bank register to PBANK */ cpu_front_panel (SWCH, Run); /* set up run request */ return run_cmd (RU_GO, buf); /* return the result of the standard handler */ } /* Execute the BREAK and NOBREAK commands. These commands are intercepted to configure address parsing. The following address forms are valid: BREAK BREAK . BREAK If no argument is specified, the breakpoint address defaults to the current values of PBANK and P. The standard command handler will accommodate this, but only if the program counter contains a physical address. Therefore, for the duration of the call, the SCP pointer to the P register structure is changed to point at a temporary register structure that contains the physical address. The , if specified, must lie between PB and PL, or the command will be rejected by the parse_addr routine when it is called by the brk_cmd routine to parse the offset. */ static t_stat hp_brk_cmd (int32 arg, CONST char *buf) { static uint32 PC; static REG PR = { ORDATA (PP, PC, 32) }; REG *save_PC; t_stat status; save_PC = sim_PC; /* temporarily change the P-register pointer */ sim_PC = & PR; /* to point at a structure holding the physical address */ PC = TO_PA (PBANK, P); /* set the physical address from the program counter */ parse_config = apcBank_Offset | apcDefault_PBANK; /* allow the . form with a PBANK default */ status = brk_cmd (arg, buf); /* call the standard breakpoint command handler */ sim_PC = save_PC; /* restore the P-register pointer */ return status; /* return the handler status */ } /* System interface local utility routines */ /* Print a numeric value in a given radix with a radix identifier. This routine prints a numeric value using the specified radix, width, and output format. If the radix is 256, then the value is printed as a single character. Otherwise, it is printed as a numeric value with a leading radix indicator if the specified print radix is not the same as the current CPU data radix. It uses the HP 3000 convention of a leading "%", "#", or "!" character to indicate an octal, decimal, or hexadecimal number (there is no binary convention, so a leading "@" is used arbitrarily). On entry, the "ofile" parameter is the opened output stream, "val" is the value to print, "radix" is the desired print radix, "width" is the number of significant bits in the value, and "format" is a format specifier (PV_RZRO, PV_RSPC, or PV_LEFT). On exit, the routine returns SCPE_OK if the value was printed successfully, or SCPE_ARG if the value could not be printed. */ static t_stat fprint_value (FILE *ofile, t_value val, uint32 radix, uint32 width, uint32 format) { if (radix == 256) /* if ASCII character display is requested */ if (val <= D8_SMAX) { /* then if the value is a single character */ fputs (fmt_char ((uint32) val), ofile); /* then format and print it */ return SCPE_OK; /* and report success */ } else /* otherwise */ return SCPE_ARG; /* report that it cannot be displayed */ else if (radix != cpu_dev.dradix) /* otherwise if the requested radix is not the current data radix */ if (radix == 2) /* then if the requested radix is binary */ fputc ('@', ofile); /* then print the binary indicator */ else if (radix == 8) /* otherwise if the requested radix is octal */ fputc ('%', ofile); /* then print the octal indicator */ else if (radix == 10) /* otherwise if it is decimal */ fputc ('#', ofile); /* then print the decimal indicator */ else if (radix == 16) /* otherwise if it is hexadecimal */ fputc ('!', ofile); /* then print the hexadecimal indicator */ else /* otherwise it must be some other radix */ fputc ('?', ofile); /* with no defined indicator */ fprint_val (ofile, val, radix, width, format); /* print the value in the radix specified */ return SCPE_OK; /* return success */ } /* Print an I/O program instruction in symbolic format. This routine prints a pair of data words as an I/O channel order and the associated operand(s) on the output stream supplied. On entry, the "ofile" parameter is the opened output stream, "val [0]" contains the I/O Control Word, "val [1]" contains the I/O Address Word, and "radix" contains the desired operand radix or zero if the default radix is to be used. The control and address words are decoded as follows: IOCW IOCW IOAW 0 1 2 3 4-15 0-15 Action ------- -------------- -------------- --------------------- 0 0 0 0 0 XXXXXXXXXXX Jump Address Unconditional Jump 0 0 0 0 1 XXXXXXXXXXX Jump Address Conditional Jump 0 0 0 1 0 XXXXXXXXXXX Residue Count Return Residue 0 0 0 1 1 XXXXXXXXXXX Bank Address Set Bank 0 0 1 0 X XXXXXXXXXXX (don't care) Interrupt 0 0 1 1 0 XXXXXXXXXXX Status Value End 0 0 1 1 1 XXXXXXXXXXX Status Value End with Interrupt 0 1 0 0 Control Word 1 Control Word 2 Control 0 1 0 1 X XXXXXXXXXXX Status Value Sense C 1 1 0 Neg Word Count Write Address Write C 1 1 1 Neg Word Count Read Address Read Operand values are printed in a radix suitable to the type of the value, as follows: - Address values are printed in the CPU's address radix, which is octal. - Counts are printed by default in decimal. - Control and status values are printed in the CPU's data radix, which defaults to octal. The radix for operand values other than addresses may be overridden by a switch on the command line. A value printed in a radix other than the current data radix is preceded by a radix identifier ("@" for binary, "%" for octal, "#" for decimal, or "!" for hexadecimal). The routine returns SCPE_OK_2_WORDS to indicate that two words were consumed. Implementation notes: 1. The Return Residue and Read/Write count values are printed as positive numbers, even though the values in memory are negative. */ static const char *const order_names [] = { /* indexed by SIO_ORDER */ "JUMP ", /* sioJUMP -- Jump unconditionally */ "JUMPC ", /* sioJUMPC -- Jump conditionally */ "RTNRES ", /* sioRTRES -- Return residue */ "SETBNK ", /* sioSBANK -- Set bank */ "INTRPT", /* sioINTRP -- Interrupt */ "END ", /* sioEND -- End */ "ENDINT ", /* sioENDIN -- End with interrupt */ "CONTRL ", /* sioCNTL -- Control */ "SENSE ", /* sioSENSE -- Sense */ "WRITE ", /* sioWRITE -- Write */ "WRITEC ", /* sioWRITEC -- Write (chained) */ "READ ", /* sioREAD -- Read */ "READC " /* sioREADC -- Read (chained) */ }; static t_stat fprint_order (FILE *ofile, t_value *val, uint32 radix) { t_value iocw, ioaw; SIO_ORDER order; iocw = val [0]; /* get the I/O control word */ ioaw = val [1]; /* and I/O address word */ order = IOCW_ORDER (iocw); /* get the SIO I/O order from the IOCW */ fputs (order_names [order], ofile); /* print the I/O order mnemonic */ switch (order) { /* dispatch operand printing based on the order */ case sioJUMP: case sioJUMPC: /* print the jump target address */ fprint_value (ofile, ioaw, cpu_dev.aradix, /* in the CPU's address radix */ LA_WIDTH, PV_RZRO); break; case sioRTRES: /* print the residue count */ fprint_value (ofile, NEG16 (ioaw), (radix ? radix : 10), DV_WIDTH, PV_LEFT); break; case sioSBANK: /* print the bank address */ fprint_value (ofile, ioaw & BA_MASK, /* in the CPU's address radix */ cpu_dev.aradix, BA_WIDTH, PV_RZRO); break; case sioINTRP: /* no operand to print */ break; case sioEND: case sioENDIN: case sioSENSE: /* print the status value */ fprint_value (ofile, ioaw, (radix ? radix : cpu_dev.dradix), DV_WIDTH, PV_RZRO); break; case sioCNTL: /* print control words 1 and 2 */ fprint_value (ofile, IOCW_CNTL (iocw), (radix ? radix : cpu_dev.dradix), DV_WIDTH, PV_RZRO); fputc (',', ofile); fprint_value (ofile, ioaw, (radix ? radix : cpu_dev.dradix), DV_WIDTH, PV_RZRO); break; case sioWRITE: case sioWRITEC: case sioREAD: case sioREADC: /* print the count and address */ fprint_value (ofile, NEG16 (IOCW_COUNT (iocw)), (radix ? radix : 10), DV_WIDTH, PV_LEFT); fputc (',', ofile); fprint_value (ofile, ioaw, cpu_dev.aradix, /* prnit the address in the CPU's address radix */ LA_WIDTH, PV_RZRO); break; } return SCPE_OK_2_WORDS; /* indicate that each instruction uses one extra word */ } /* Format and print an EDIT instruction subprogram operation in symbolic format. This routine formats and prints a set of memory bytes as an EDIT subprogram operation. It is called when the "-E" command-line switch is included with a memory display request. On entry, the "ofile" parameter is the opened output stream, "val [*]" contains the first two words of the operation, "radix" contains the desired operand radix or zero if the default radix is to be used, "addr" points at the memory word containing the first subprogram operation byte, and "switches" contains the set of command-line switches supplied with the command. The return value is one more than the negative number of memory words consumed by the print request (e.g., a one-word consumption returns zero, a two-word consumption returns -1, etc.). The routine is called to print a single memory word. If the EXAMINE command is given a memory range, the routine will be called multiple times, with "val" pointing at an array containing the first two words of the range. An operation may encompass from 1-257 bytes. The operation is assumed to begin in the upper byte of the first memory word unless the "-R" switch is supplied, in which case the lower byte is assumed. If "-R" is not used, and the operation in the upper byte consumes only a single byte, the operation (beginning) in the lower byte is also printed. Full operations are always printed, so a single call on the routine prints one or two operations and then returns the number of memory words consumed (biased by 1). If a range is being printed, this value determines the address presented at the following call. Before returning, the "R" switch is set or cleared to indicate whether the next call in a range should start with the lower or upper byte of the word. Implementation notes: 1. Within a single command, this routine may be called (via fprint_sym) successively for the same address. This will occur if console logging is enabled. The first call will be to write to the console, and then it will be called again to write to the log file. However, sim_switches is updated after the first call in anticipation of additional calls for the subsequent addresses in a range. To avoid this, sim_switches is tested only if the routine is NOT called for the log file; if it is, then the previous setting of sim_switches is used. */ static t_stat fprint_subop (FILE *ofile, t_value *val, uint32 radix, t_addr addr, int32 switches) { static uint32 odd_byte = 0; uint32 byte_addr, bytes_used, total_bytes_used; if (ofile != sim_log) /* if this is not a logging call */ odd_byte = (uint32) ((switches & SWMASK ('R')) != 0); /* then recalculate the odd-byte flag */ total_bytes_used = odd_byte; /* initialize the bytes used accumulator */ byte_addr = (uint32) addr * 2 + odd_byte; /* form the initial byte address */ do { bytes_used = fprint_edit (ofile, val, radix, byte_addr); /* format and print an operation */ byte_addr = byte_addr + bytes_used; /* point at the next operation */ total_bytes_used = total_bytes_used + bytes_used; /* and add the byte count to the accumulator */ if (total_bytes_used < 2) /* if a full word has not been consumed yet */ fputs ("\n\t\t", ofile); /* then start a new line for the second operation */ } while (total_bytes_used < 2); /* continue until at least one word is consumed */ if (byte_addr & 1) /* if the next operation begins in the lower byte */ sim_switches |= SWMASK ('R'); /* then set the switch for the next call */ else /* otherwise it begins in the upper byte */ sim_switches &= ~SWMASK ('R'); /* so clear the switch */ return -(t_stat) (total_bytes_used / 2 - 1); /* return the (biased) negative number of words consumed */ } /* Print a CPU instruction and operand in symbolic format. This routine prints a CPU instruction and its operand, if any, using the mnemonics specified in the Machine Instruction Set and Systems Programming Language Reference manuals. Specified bits in the instruction word are used as an index into a supplied classification table. The entry corresponding to the instruction gives the mnemonic string, operand type, and reserved bits (if any). On entry, the "ofile" parameter is the opened output stream, "ops" is the table of classifications containing the instruction, "val" contains the word(s) of the machine instruction to print, "mask" is the opcode mask to apply to get the index bits, "shift" is the right-shift count to align the index, and "radix" contains the desired operand radix or zero if the default radix is to be used. On exit, a status code is returned to the caller. SCPE_OK status is returned if the print consumed a single-word value, or the negative number of extra words (beyond the first) consumed by printing the instruction is returned. For example, printing a symbol that resulted in two words being consumed (from val [0] and val [1]) would return SCPE_OK_2_WORDS (= -1). If the supplied instruction is not in the table, SCPE_ARG is returned. The classification table consists of a set of entries that are indexed by opcode, followed optionally by a set of entries that are searched linearly. Empty mnemonics, i.e., "", are used in the indexed part to indicate that the linear part must be searched, and in the linear part to indicate a two-word instruction whose second word did not match a defined entry. A NULL mnemonic ends the array (this allows string searches for parsing to fail without aborting). The supplied instruction is ANDed with the "mask" parameter and then right-shifted by the "shift" parameter to produce an index into the "ops" table. If the entry contains a non-empty mnemonic string, it is printed. Otherwise, starting at the index implied by the size of the mask, i.e., at mask + 1, a linear search of the entries is performed. For each entry, the instruction is masked to remove the operand and optionally the reserved bits, and the result is compared to the base opcode. If it matches, the associated mnemonic is printed. If the table is exhausted without a match, the instruction is undefined, and SCPE_ARG is returned. For defined instructions, the operand, if any, is printed after the mnemonic. Operand values are printed in a radix suitable to the type of the value, as follows: - Register-relative displacements, S-register decrements, and K fields are printed in the CPU's address radix, which is octal. - Shift counts, bit positions, and starting bits and counts are printed in decimal. - CIR values for the PAUS and HALT instructions are printed in octal. - Immediate values are printed in the CPU's data radix, which defaults to octal but may be set to a different radix with SET CPU OCT|DEC|HEX. The radix for operand values other than addresses may be overridden by a switch on the command line. A value printed in a radix other than the current data radix is preceded by a radix identifier ("%" for octal, "#" for decimal, or "!" for hexadecimal). Implementation notes: 1. All instructions in the base set are single words. However, some extension instructions, including instructions for later-series CPUs, e.g., the Series 33, use two words. For example, the WIOC (write I/O channel) instruction is the two-word sequence 020302,000003, and the SIOP (start I/O program) sequence is 020302,000000. 2. The operand type dispatch handlers either set up operand printing by assigning the prefix, indirect, and index values, or print the operand(s) directly if special formatting is required. 3. Register flags for the PSHR and SETR instructions are printed using the SPL register names. 4. The "rsvd_mask" fields of secondary entries for one-word instructions have the upper 16 bits of the values set to zero. This masks off the second instruction word, which is arbitrary, before comparison. 5. A secondary entry with a zero-length mnemonic is used to match the first word of a two-word instruction when the second word fails to match any of the earlier entries. This causes the instruction to be printed as a pair of numeric values. */ static const char *const register_name [] = { /* PSHR/SETR register names corresponding to bits 8-15 */ "SBANK", /* bit 8 */ "DB", /* bit 9 */ "DL", /* bit 10 */ "Z", /* bit 11 */ "STATUS", /* bit 12 */ "X", /* bit 13 */ "Q", /* bit 14 */ "S" /* bit 15 */ }; static const char *const cc_flags [] = { /* TCCS condition code names corresponding to bits 13-15 */ " N", /* 000 = never */ " L", /* 001 = less than */ " E", /* 010 = equal */ " LE", /* 011 = less than or equal */ " G", /* 100 = greater than */ " NE", /* 101 = not equal */ " GE", /* 110 = greater than or equal */ " A" /* 111 = always */ }; static const char *const sign_cntl [] = { /* CVND sign control names corresponding to bits 12-14 */ " LS", /* 000 = sign is leading separate */ " TS", /* 001 = sign is trailing separate */ " LO", /* 010 = sign is leading overpunch or unsigned */ " TO", /* 011 = sign is trailing overpunch or unsigned */ " UN", /* 100 = unsigned */ " UN", /* 101 = unsigned */ " UN", /* 110 = unsigned */ " UN" /* 111 = unsigned */ }; static t_stat fprint_instruction (FILE *ofile, const OP_TABLE ops, t_value *val, uint32 mask, uint32 shift, uint32 radix) { uint32 op_index, op_radix; int32 reg_index; t_bool reg_first; t_value instruction, op_value; const char *prefix = NULL; /* label to print before the operand */ t_bool index = FALSE; /* TRUE if the instruction is indexed */ t_bool indirect = FALSE; /* TRUE if the instruction is indirect */ t_stat status = SCPE_OK; /* result status */ instruction = TO_DWORD (val [1], val [0]); /* merge the two supplied values */ op_index = ((uint32) instruction & mask) >> shift; /* extract the opcode index */ if (ops [op_index].mnemonic [0]) /* if a primary entry is defined */ fputs (ops [op_index].mnemonic, ofile); /* then print the mnemonic */ else { /* otherwise search through the secondary entries */ for (op_index = (mask >> shift) + 1; /* in the table starting after the primary entries */ ops [op_index].mnemonic != NULL; /* until the NULL entry at the end */ op_index++) if (ops [op_index].opcode == /* if the opcode in this table entry */ (instruction & ops [op_index].rsvd_mask /* matches the instruction with the reserved bits */ & op_mask [ops [op_index].operand])) /* and operand bits masked off */ if (ops [op_index].mnemonic [0]) { /* then if the entry is defined */ fputs (ops [op_index].mnemonic, ofile); /* then print it */ break; /* and terminate the search */ } else { /* otherwise this two-word instruction is unimplemented */ fprint_val (ofile, val [0], cpu_dev.dradix, /* so print the first word */ cpu_dev.dwidth, PV_RZRO); fputc (',', ofile); /* and a separator */ fprint_val (ofile, val [1], cpu_dev.dradix, /* and the second word */ cpu_dev.dwidth, PV_RZRO); return SCPE_OK_2_WORDS; /* return success to indicate printing is complete */ } if (ops [op_index].mnemonic == NULL) /* if the opcode was not found */ return SCPE_ARG; /* then return error status to print it in octal */ } op_value = instruction & ~op_mask [ops [op_index].operand]; /* mask the instruction to the operand value */ if (ops [op_index].rsvd_mask > D16_MASK) { /* if this is a two-word instruction */ op_value = op_value >> D16_WIDTH; /* then the operand is in the upper word */ status = SCPE_OK_2_WORDS; /* and the routine will consume two words */ } op_radix = cpu_dev.aradix; /* assume that operand is an address */ switch (ops [op_index].operand) { /* dispatch by the operand type */ /* no operand */ case opNone: break; /* no formatting needed */ /* condition code flags in bits 13-15 */ case opCC7: fputs (cc_flags [op_value], ofile); /* print the condition code flags */ break; /* unsigned value pair range 0-15 */ case opU1515: fputc (' ', ofile); /* print a separator */ fprint_value (ofile, START_BIT (op_value), /* print the starting bit position */ (radix ? radix : 10), DV_WIDTH, PV_LEFT); fputc (':', ofile); /* print a separator */ fprint_value (ofile, BIT_COUNT (op_value), /* print the bit count */ (radix ? radix : 10), DV_WIDTH, PV_LEFT); break; /* P +/- displacement range 0-31, indirect bit 4 */ case opPS31I: indirect = (instruction & I_FLAG_BIT_4) != 0; /* save the indirect condition */ prefix = (op_value & DISPL_31_SIGN ? " P-" : " P+"); /* set the base register and sign label */ op_value = op_value & DISPL_31_MASK; /* and remove the sign from the displacement value */ break; /* P +/- displacement range 0-255, indirect bit 5, index bit 4 */ case opPS255IX: index = (instruction & X_FLAG) != 0; /* save the index condition */ indirect = (instruction & I_FLAG_BIT_5) != 0; /* and the indirect condition */ /* fall through into the P-relative displacement case */ /* P +/- displacement range 0-255 */ case opPS255: prefix = (op_value & DISPL_255_SIGN ? " P-" : " P+"); /* set the base register and sign label */ op_value = op_value & DISPL_255_MASK; /* and remove the sign from the displacement value */ break; /* base bit 15 */ case opB15: case opB31: if (op_value == 0) /* if the base bit is 0 */ fputs (" PB", ofile); /* then PB is the base register label */ break; /* S decrement range 0-3, base register bit 11 */ case opSU3B: prefix = (instruction & DB_FLAG) ? " " : " PB,"; /* set the base register label */ op_value = op_value & ~op_mask [opSU3]; /* and remove the base flag from the S decrement value */ break; /* S decrement range 0-3, N/A/S bits 11-13 */ case opSU3NAS: if (instruction & MVBW_CCF) /* if any flags are present */ fputc (' ', ofile); /* then print a space as a separator */ if (instruction & MVBW_A_FLAG) /* if the alphabetic flag is present */ fputc ('A', ofile); /* then print an "A" as the indicator */ if (instruction & MVBW_N_FLAG) /* if the numeric flag is present */ fputc ('N', ofile); /* then print an "N" as the indicator */ if (instruction & MVBW_S_FLAG) /* if the upshift flag is present */ fputc ('S', ofile); /* then print an "S" as the indicator */ prefix = ","; /* separate the value from the flags */ op_value = op_value & ~op_mask [opSU3]; /* and remove the flags from the S decrement value */ break; /* register selection bits 8-15, execution from left-to-right */ case opR255L: if (op_value != 0) { /* if any registers are to be output */ fputc (' ', ofile); /* then print a space as a separator */ reg_first = TRUE; /* set the first-time-through flag */ for (reg_index = 0; reg_index <= 7; reg_index++) { /* loop through the register bits */ if (op_value & PSR_LR_MASK) { /* if the register selection bit is set */ if (reg_first) /* then if this is the first time */ reg_first = FALSE; /* then clear the flag */ else /* otherwise */ fputc (',', ofile); /* output a comma separator */ fputs (register_name [reg_index], ofile); /* output the register name */ } op_value = op_value << 1; /* position the next register selection bit */ } } break; /* register selection bits 8-15, execution from right-to-left */ case opR255R: if (op_value != 0) { /* if any registers are to be output */ fputc (' ', ofile); /* then print a space as a separator */ reg_first = TRUE; /* set the first-time-through flag */ for (reg_index = 7; reg_index >= 0; reg_index--) { /* loop through the register bits */ if (op_value & PSR_RL_MASK) { /* if the register selection bit is set */ if (reg_first) /* then if this is the first time */ reg_first = FALSE; /* then clear the flag */ else /* otherwise */ fputc (',', ofile); /* output a comma separator */ fputs (register_name [reg_index], ofile); /* output the register name */ } op_value = op_value >> 1; /* position the next register selection bit */ } } break; /* P+/P-/DB+/Q+/Q-/S- displacements, indirect bit 5, index bit 4 */ case opPD255IX: if ((instruction & DISPL_P_FLAG) == 0) { /* if this a P-relative displacement */ prefix = (op_value & DISPL_255_SIGN ? " P-" : " P+"); /* then set the base register and sign label */ op_value = op_value & DISPL_255_MASK; /* and remove the sign from the displacement value */ index = (instruction & X_FLAG) != 0; /* save the index condition */ indirect = (instruction & I_FLAG_BIT_5) != 0; /* and the indirect condition */ break; } /* otherwise the displacement is not P-relative, so fall through into the data-relative handler */ /* DB+/Q+/Q-/S- displacements, indirect bit 5, index bit 4 */ case opD255IX: if ((instruction & DISPL_DB_FLAG) == 0) { /* if this a DB-relative displacement */ prefix = " DB+"; /* then set the base register label */ op_value = op_value & DISPL_255_MASK; /* and remove the base flag from the displacement value */ } else if ((instruction & DISPL_QPOS_FLAG) == 0) { /* otherwise if this a positive Q-relative displacement */ prefix = " Q+"; /* then set the base register label */ op_value = op_value & DISPL_127_MASK; /* and remove the base flag from the displacement value */ } else if ((instruction & DISPL_QNEG_FLAG) == 0) { /* otherwise if this a negative Q-relative displacement */ prefix = " Q-"; /* then set the base register label */ op_value = op_value & DISPL_63_MASK; /* and remove the base flag from the displacement value */ } else { /* otherwise it must be a negative S-relative displacement */ prefix = " S-"; /* so set the base register label */ op_value = op_value & DISPL_63_MASK; /* and remove the base flag from the displacement value */ } indirect = (instruction & I_FLAG_BIT_5) != 0; /* save the indirect condition */ /* fall through into the index case */ /* index bit 4 */ case opX: index = (instruction & X_FLAG) != 0; /* save the index condition */ break; /* unsigned value range 0-63, index bit 4 */ case opU63X: index = (instruction & X_FLAG) != 0; /* save the index condition */ op_value = op_value & DISPL_63_MASK; /* and mask to the operand value */ /* fall through into the unsigned value case */ /* unsigned value range 0-63 */ case opU63: op_radix = (radix ? radix : 10); /* set the print radix */ prefix = " "; /* and add a separator */ break; /* sign control bits 9-10, S decrement bit 11 */ case opSCS3: if (instruction & NABS_FLAG) { /* if the negative absolute flag is present */ fputs (" NABS", ofile); /* then print "NABS" as the indicator */ prefix = ","; /* we will need to separate the flag and value */ } else if (instruction & ABS_FLAG) { /* otherwise if the absolute flag is present */ fputs (" ABS", ofile); /* then print "ABS" as the indicator */ prefix = ","; /* we will need to separate the flag and value */ } else /* otherwise neither flag is present */ prefix = " "; /* so just use a space to separate the value */ op_value = (op_value & ~op_mask [opS11]) >> EIS_SDEC_SHIFT; /* remove the flags from the S decrement value */ break; /* sign control bits 12-14, S decrement bit 15 */ case opSCS4: fputs (sign_cntl [op_value >> CVND_SC_SHIFT], ofile); /* print the sign-control mnemonic */ prefix = ","; /* separate the flag and value */ op_value = op_value & CIS_SDEC_MASK; /* and remove the flags from the S decrement value */ break; /* S decrement bit 11 */ /* S decrement range 0-2 bits 10-11 */ case opS11: case opSU2: op_value = op_value >> EIS_SDEC_SHIFT; /* align the S decrement value */ /* fall through into the unsigned operand case */ /* unsigned value range 0-1 */ /* unsigned value range 0-255 */ case opU1: case opU255: op_radix = (radix ? radix : cpu_dev.dradix); /* set the print radix */ prefix = " "; /* and add a separator */ break; /* CIR display bits 12-15 */ case opC15: op_radix = (radix ? radix : 8); /* set the print radix */ prefix = " "; /* and add a separator */ break; /* P unsigned displacement range 0-255 */ /* S decrement range 0-1 */ /* S decrement range 0-3 */ /* S decrement range 0-7 */ /* S decrement range 0-15 */ case opPU255: case opS15: case opS31: case opSU3: case opSU7: case opSU15: prefix = " "; /* add a separator */ break; } /* end of the operand type dispatch */ if (prefix) { /* if an operand is present */ fputs (prefix, ofile); /* then label it */ fprint_value (ofile, op_value, op_radix, /* and then print the value */ DV_WIDTH, PV_LEFT); } if (indirect) /* add an indirect indicator */ fputs (",I", ofile); /* if specified by the instruction */ if (index) /* add an index indicator */ fputs (",X", ofile); /* if specified by the instruction */ return status; /* return the applicable status */ } /* Parse a CPU instruction */ static t_stat parse_cpu (CONST char *cptr, t_addr address, UNIT *uptr, t_value *value, int32 switches) { return SCPE_ARG; /* mnemonic support is not present in this release */ }