| /* hp3000_sys.c: HP 3000 system common interface | |
| Copyright (c) 2016-2017, 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. | |
| 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 <ctype.h> | |
| #include <stdarg.h> | |
| #include "hp3000_defs.h" | |
| #include "hp3000_cpu.h" | |
| #include "hp3000_cpu_ims.h" | |
| #include "hp3000_mem.h" | |
| #include "hp3000_io.h" | |
| /* 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, /* <bank>.<offset> 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 */ | |
| static void 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 */ | |
| void (*sim_vm_init) (void) = &one_time_init; /* a pointer to the one-time initializer */ | |
| 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 [] = { /* 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 <n>, 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 <n>, 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 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') /* 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. | |
| */ | |
| static void one_time_init (void) | |
| { | |
| CTAB *contab, *systab, *auxtab = aux_cmds; | |
| 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: | |
| <reason>, P: <addr> (<inst>) | |
| 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 <bank>.<offset> 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 <bank>.<offset> 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 <bank>.<offset> | |
| EXAMINE <dbank-offset> | |
| EXAMINE -P <pbank-offset> | |
| EXAMINE -S <sbank-offset> | |
| 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 <bank>.<offset> 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 { <pbank-offset> } | |
| GO { <pbank-offset> } | |
| This routine configures the address parser and calls the standard command | |
| handler. The <pbank-offset>, 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 <bank>.<offset> | |
| BREAK <pbank-offset> | |
| 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 <pbank-offset>, 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 <bank>.<offset> 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 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 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 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 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 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 */ | |
| } |