/* hp3000_sys.c: HP 3000 system common interface

   Copyright (c) 2016-2023, 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.

   20-Apr-23    JDB     Fixed "hp_word_examine" read within one byte of EOF
   02-Mar-23    JDB     Added "unit_name" routine to return names for standalone units
   13-Nov-22    JDB     Modified "parse_sym" and "fprint_sym" to handle byte calls
                        Modified "fprint_value" to handle full 8-bit characters
                        Added "hp_word_examine" and "hp_word_deposit" routines
   12-Oct-22    JDB     Added "dc_dev" to the device list
   06-Oct-22    JDB     Added "hp_bit_number" definitions
   08-Sep-22    JDB     Added "fmt_value" routine
   18-May-22    JDB     Added the IMB, CPP, IMBA, and GIC devices
                        Added a call to "fprint_cpi" to print channel programs
   23-Oct-21    JDB     Added "hp_align_trace" to set alignment parameters
                        "vm_align_trace" set to point at "hp_align_trace"
   30-Apr-21    JDB     Modified POWER help in "aux_cmds" to spell out commands
   29-Apr-21    JDB     Added initialization of "vm_execution_rate"
   31-Mar-21    JDB     "parse_sym" and "fprint_sym" now call "sim_get_radix"
   26-Aug-20    JDB     Modified "hp_trace" to output CR LFs to stdout
   25-Aug-20    JDB     Now sets SCP hooks in "one_time_init" routine
   09-Dec-19    JDB     Renamed "hp_debug" to "hp_trace"
   07-Apr-19    JDB     Added command handler pointers
   04-Mar-19    JDB     Added "sim_vm_release" character string declaration
   27-Dec-18    JDB     Revised fall through comments to comply with gcc 7
   29-May-18    JDB     Added a check for the "alternate" flag to "fmt_bitset"
   05-Sep-17    JDB     Removed the -B (binary display) option; use -2 instead
                        Rewrote "fprint_sym" for better coverage
   11-May-17    JDB     Corrected comment in "fprint_value"
   28-Apr-17    JDB     Added void cast to "fprint_instruction" call for left stackop
   03-Mar-17    JDB     Added an implementation note to the "parse_sym" routine
   29-Dec-16    JDB     Changed the switch for STA format from -S to -T;
                        changed the status mnemonic flag from REG_S to REG_T
   28-Nov-16    JDB     hp_device_conflict accumulates names of active traces only
                        Improved parse_addr error detection
   26-Oct-16    JDB     Added "fprint_edit" to print EDIT subprogram mnemonics
   27-Sep-16    JDB     Added COBOL firmware mnemonics
                        Modified "fprint_instruction" to handle two-word instructions
   15-Sep-16    JDB     Modified "one_time_init" to set aux_cmds "message" field
   03-Sep-16    JDB     Added the STOP_POWER and STOP_ARSINH status messages
   01-Sep-16    JDB     Moved the "hp_cold_cmd" routine to the CPU (as "cpu_cold_cmd")
                        Added the POWER command
   03-Aug-16    JDB     Improved "fmt_char" and "fmt_bitset" to allow multiple calls
   15-Jul-16    JDB     Fixed the word count display for SIO read/write orders
   14-Jul-16    JDB     Improved the cold dump invocation
   21-Jun-16    JDB     Changed fprint_instruction mask type from t_value to uint32
   08-Jun-16    JDB     Corrected %d format to %u for unsigned values
   16-May-16    JDB     Prefix in fprint_instruction is now a pointer-to-constant
   13-May-16    JDB     Modified for revised SCP API function parameter types
   20-Apr-16    JDB     Added implementation notes to "fmt_bitset"
   14-Apr-16    JDB     Fixed INTMASK setting and display
   24-Mar-16    JDB     Added the LP device
   21-Mar-16    JDB     Changed uint16 types to HP_WORD
   23-Nov-15    JDB     First release version
   11-Dec-12    JDB     Created

   References:
     - Machine Instruction Set Reference Manual
         (30000-90022, February 1980)
     - Systems Programming Language Reference Manual
         (30000-90024, December 1976)


   This module provides the interface between the Simulation Control Program
   (SCP) and the HP 3000 simulator.  It includes the required global VM
   interface data definitions (e.g., the simulator name, device array, etc.),
   symbolic display and parsing routines, utility routines for tracing and
   execution support, and SCP command replacement routines.
*/



#include <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"



/* HP 3000 simulator release string */

static char *hp_release = "12";


/* 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 */
extern DEVICE cpp_dev;                          /* Channel Program Processor */
extern DEVICE imba_dev;                         /* 30341 HP-IB Interface Module */
extern DEVICE gic_dev;                          /* 31262 General I/O Channel */
extern DEVICE dc_dev;                           /* 31262 GIC CS/80 protocol */


/* Program constants */


/* Symbolic production/consumption values */

#define SCPE_OK_2_WORDS     ((t_stat) -1)       /* two words produced or consumed */


/* Symbolic mode and format override switches */

#define A_SWITCH            SWMASK ('A')
#define C_SWITCH            SWMASK ('C')
#define E_SWITCH            SWMASK ('E')
#define H_SWITCH            SWMASK ('H')
#define I_SWITCH            SWMASK ('I')
#define L_SWITCH            SWMASK ('L')
#define M_SWITCH            SWMASK ('M')
#define P_SWITCH            SWMASK ('P')
#define R_SWITCH            SWMASK ('R')
#define S_SWITCH            SWMASK ('S')
#define T_SWITCH            SWMASK ('T')
#define W_SWITCH            SWMASK ('W')

#define MNEMONIC_SWITCHES   (E_SWITCH | I_SWITCH | L_SWITCH | M_SWITCH | T_SWITCH)
#define MODE_SWITCHES       (A_SWITCH | C_SWITCH | E_SWITCH | I_SWITCH | L_SWITCH | M_SWITCH | T_SWITCH)


/* 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, char   *cptr,   char   **tptr);
static char   *unit_name     (const UNIT *uptr);

static t_stat hp_exdep_cmd   (int32 arg, char *buf);
static t_stat hp_run_cmd     (int32 arg, char *buf);
static t_stat hp_brk_cmd     (int32 arg, char *buf);
static uint32 hp_align_trace (uint32 device_max, uint32 flag_max);


/* 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          (char *cptr, t_addr addr, t_value *val, uint32 radix);


/* 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 = VM_EMAX;                       /* 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 [SIM_DEVICE_SIZE] = {       /* 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 */
    &imba_dev,                                  /*   30341 HP-IB Interface Module */
    &cpp_dev,                                   /*   Channel Program Processor */
    &gic_dev,                                   /*   31262 General I/O Channel */
    &dc_dev,                                    /*   31262 GIC CS/80 protocol */
    NULL                                        /* end of the device list */
    };

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.
*/

typedef t_stat HANDLER (int32 flag, char *cptr);  /* command handler routine */

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,         "power fail               fail the CPU power\n"
                                             "power restore            restore the CPU power\n"   },

    { NULL }
    };

static HANDLER *exdep_handler = NULL;           /* EXAMINE/DEPOSIT command handler */
static HANDLER *run_handler   = NULL;           /* RUN/GO command handler */
static HANDLER *break_handler = NULL;           /* BREAK/NOBREAK command handler */



/* 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, char *cptr, 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.  It is called for:

     - EVALuating a symbol
     - EXAMINing a memory address
     - EXAMINing a register marked with a user flag
     - printing the next instruction at a simulator stop

   The entry parameters vary, depending on the calling context:

     Param   EVAL             EXAMINE address   EXAMINE register         Simulation stop
     -----   --------------   ---------------   ----------------------   ----------------
     ofile   output stream    output stream     output stream            output stream
     addr    0                address           user flags | radix       program counter
     val     supplied value   memory value      register value           memory value
     uptr    CPU unit         specified unit    NULL                     NULL
     sw      switches         switches          switches | SIM_SW_REG    -M | SIM_SW_STOP

   On exit, a status code is returned to the caller.  If symbolic printing is
   not requested, SCPE_ARG status is returned, which causes the caller to print
   the value in numeric format with the specified or 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 for printing are supported by including one of
   the switches:

     Switch   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
       -L     a channel program instruction mnemonic
       -M     a CPU instruction mnemonic
       -T     a CPU status mnemonic

   When printing in one of the mnemonic modes, an additional format 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

   The -A switch thus has two meanings.  In combination with a mnemonic mode
   switch, printing of instruction mnemonics assumes single characters for any
   data operands present.  By itself, printing of single characters occurs.  The
   numeric switches may also be used by themselves to print a value in the
   specified radix.  In the absence of switches, the value is displayed in the
   default format for the specified source.

   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.

   Byte-oriented devices that provide optional word orientation, such as the
   HP-IB disc drives, display bytes by default; the -A switch may be included to
   display the bytes as characters instead of numbers.

   Including the -C switch uses two successive bytes to form the character pair,
   and the address increments by two between pairs when displaying a range of
   addresses.  Including the -E, -ER, -I, -M, -P, or -T switch consumes two or
   more byte pairs to produce the designated instruction mnemonic, and the
   address is incremented correspondingly.

   One additional switch is applicable for byte-oriented devices:

     Switch   Interpretation
     ------   ----------------------------
       -W     two bytes as a 16-bit number

   Two successive bytes are joined to produce the 16-bit value; the address is
   incremented by two.  The -B, -O, -D, or -H switch may be included with -W to
   print the word in the specified radix.


   Implementation notes:

    1. Because mnemonics are specific to the CPU/MPX/SEL/CPP, 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 -O CIR" displays the value as octal.

    3. When we are called to format a register, the "val" parameter points at a
       single t_value containing the register value.  However, the instruction
       mnemonic formatter expects to receive a pointer to an array that holds
       at least the number of words for the instruction being decoded.  For
       example, if the register holds a double-word firmware extension opcode,
       the formatter will expect the sub-opcode in the second array word.
       Multi-word instructions cannot be displayed correctly if they originate
       in a register, so the best we can do is copy the value to "sim_eval [0]"
       and zero the remaining words before calling the mnemonic formatter.  A
       call to "memset" is used because this can be optimized to a single REP
       STOSD instruction.

    4. The specific error value returned is irrelevant, as callers will use the
       fallback printer if any status error is returned.  By convention,
       SCPE_ARG is returned both to indicate a formatting error and to request
       the default numeric printer.

    5. The radix and -C switches are mutually exclusive (as are -C and the
       mnemonic mode switches, and all of the format switches).  While there is
       a status code we could return (SCPE_INVSW, "Invalid switch"), it will not
       report the error, as explained above.  Instead, the fallback printer
       would ignore -C and use just the radix switch.  Therefore, we simply
       ignore conflicting switches and pick an arbitrary execution order.

    6. The radix specified by a supplied numeric radix switch is determined
       prior to calling this routine.  But only in the register case is the
       value communicated to this routine.  Therefore, we must call
       "sim_get_radix" again here if the mnemonic mode or default byte mode is
       specified.

    7. There are two possible implementations for byte-oriented devices to
       examine and deposit word values.  One method is to declare the data width
       as 8 and the address increment as 1.  In this case, the "val" array
       contains bytes that must be merged in place before calling "fprint_cpu"
       or "fprint_value".  The other method is to declare the data width as 16
       and the address increment as 2.  For this, the "val" array contains
       words, and a byte display must extract the upper byte of the first
       element.  We use the second implementation because the first requires
       that "sim_emax" be set to twice the expected value, as it must
       accommodate reading bytes or words (so we read twice the number of words
       needed for word-only devices), and the bytes must be packed if a
       word-oriented format is specified.  The second method is therefore
       simpler and faster.

    8. In the absence of an error, the return value represents the number of
       additional addressing units (words or bytes) by which to increment the
       address counter when formatting a block of values.  When addresses
       represent words, increments of 1, 2, 3, etc. are represented by return
       values of 0, -1, -2, etc.  For byte-oriented devices, word increments of
       1, 2, 3, etc. are adjusted for bytes instead of words, producing
       return values of -1, -3, -5, etc.
*/

t_stat fprint_sym (FILE *ofile, t_addr addr, t_value *val, UNIT *uptr, int32 sw)
{
DEVICE  *dptr;
uint32  radix;
t_stat  result;
t_value value;
t_bool  packed;

if (uptr == NULL)                                       /* if this is a register or sim stop print */
    packed = FALSE;                                     /*   then byte packing does not apply */

else {                                                  /* otherwise it's an evaluation or address print */
    dptr = find_dev_from_unit (uptr);                   /*   so get the device pointer from the unit */

    if (dptr == NULL)                                   /* if the unit is not associated with a device */
        return SCPE_IERR;                               /*   then it cannot be printed */
    else                                                /* otherwise */
        packed = (dptr->aincr == 2);                    /*   the device is packed if the increment is 2 */
    }

if (sw & SIM_SW_REG                                     /* if we are printing a register */
  && not (sw & MODE_SWITCHES))                         /*   and no symbolic modes are present */
    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 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 & MNEMONIC_SWITCHES) {                           /* if this is a mnemonic mode */
    if (sw & A_SWITCH)                                  /*   then if the operands are alphabetic */
        radix = 256;                                    /*     then use radix 256 */
    else                                                /*   otherwise */
        radix = sim_get_radix (NULL, sw, 0);            /*     use an explicit operand radix if present */

    if (sw & SIM_SW_REG) {                              /* if a register value is being formatted */
        memset (sim_eval, 0,                            /*   then clear the sim_eval array */
                sim_emax * sizeof (t_value));           /*     in case the instruction is multi-word */

        sim_eval [0] = *val;                            /* copy the register value */
        val = sim_eval;                                 /*   and point at the sim_eval array */
        }

    if (sw & I_SWITCH)                                          /* if an I/O channel order mode is specified */
        result = fprint_order (ofile, val, radix);              /*   then format and print it */

    else if (sw & E_SWITCH)                                     /* otherwise if an EDIT subop mode is requested */
        result = fprint_subop (ofile, val, radix, addr, sw);    /*   then format and print it */

    else if (sw & L_SWITCH)                                     /* otherwise if a channel instruction is requested */
        result = fprint_cpi (ofile, val, radix, addr, uptr);    /*   then format and print it */

    else if (sw & T_SWITCH) {                                   /* otherwise if a status mode 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)
            result = SCPE_OK;                                   /*     then return success */

        else                                                    /* otherwise print it */
            result = fprint_val (ofile, STATUS_CS (val [0]),    /*   in the CPU's default data radix */
                               cpu_dev.dradix, D8_WIDTH, PV_RZRO);
        }

    else                                                        /* otherwise a CPU mnemonic mode is specified */
        result = fprint_cpu (ofile, val, radix, sw);            /*   so format and print the CPU instruction */
    }

else if (sw & 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 */
    result = SCPE_OK;
    }

else if (sw & A_SWITCH                                  /* otherwise if single character mode is specified */
  || packed && not (sw & W_SWITCH)) {                   /*   or single bytes are requested */
    if (sw & A_SWITCH)                                  /*     then if characters are wanted */
        radix = 256;                                    /*       then use radix 256 formatting */
    else                                                /*     otherwise */
        radix = sim_get_radix (NULL, sw, dptr->dradix); /*       use an explicit operand radix if present */

    if (packed)                                         /* if byte packing is used */
        value = UPPER_BYTE (val [0]);                   /*   then the upper byte is addressed */
    else                                                /* otherwise */
        value = LOWER_BYTE (val [0]);                   /*   use the lower byte of the multibyte word */

    return fprint_value (ofile, value, radix, 8, PV_RZRO);  /* format and print the byte and return the increment */
    }

else                                                    /* otherwise it's not a symbolic print */
    result = SCPE_ARG;                                  /*   so use the fallback numeric printer */

if (not packed || result > 0)                           /* if bytes are not packed or an error occurred */
    return result;                                      /*   then return the result value */
else                                                    /* otherwise excess words were consumed */
    return result * 2 - 1;                              /*   so return the conversion to excess bytes */
}


/* Parse a value in symbolic format.

   Parse the input string in the format specified by the optional switches, and
   return the resulting value(s).  This routine is called to parse an input
   string when:

     - EVALuating a symbol
     - DEPOSITing into a memory address
     - DEPOSITing into a register marked with a user flag

   The entry parameters vary, depending on the calling context:

     Param   EVAL               DEPOSIT address    DEPOSIT register
     -----   ----------------   ----------------   ---------------------
     cptr    symbol to parse    symbol to parse    symbol to parse
     addr    0                  address            user flags | radix
     uptr    CPU unit           specified unit     NULL
     val     pointer to array   pointer to array   pointer to array
     sw      switches           switches           switches | SIM_SW_REG

   The "val" parameter is a pointer to an array of t_values of depth "sim_emax"
   representing the value(s) returned.

   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 modes for parsing the input string are supported by
   including one of the indicated switches:

     Switch   Interpretation
     ------   -----------------------------------------
       -A     a single character in the right-hand byte
       -C     a two-character packed string
       -M     a CPU instruction mnemonic

   When parsing instruction mnemonics, an additional format switch may be
   specified to indicate the radix of any supplied operands, as follows:

     Switch   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

   The -A switch thus has two meanings.  In combination with -M, the routine
   accepts an instruction mnemonic and then single characters for any data
   operands present.  By itself, the routine accepts a single character.  The
   numeric switches may also be used by themselves to accept a value in the
   specified radix.

   When entering machine instruction mnemonics, operand values are parsed in a
   radix suitable for the type of the value.  Address values are parsed in the
   CPU's address radix, which is octal.  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.

   In the absence of switches, a leading apostrophe (') implies -A, a leading
   quotation mark (") implies -C, and a leading alphabetic or punctuation
   character implies -M.  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.

   The operand format for -A is a single (displayable) character.  The operand
   format for -C is two (displayable) characters.

   Byte-oriented devices that provide optional word orientation, such as the
   HP-IB disc drives, parse bytes by default; the -A switch may be included to
   parse the bytes as characters instead of numbers.

   Including the -C switch parses a character pair to produce two successive
   bytes, and the address increments by two between pairs when depositing to a
   range of addresses.  Including the -M switch produces two or more byte pairs
   from a CPU instruction mnemonic, and the address is incremented
   correspondingly.

   One additional switch is applicable for these devices:

     Switch   Interpretation
     ------   ----------------------------
       -W     two bytes as a 16-bit number

   A 16-bit value is parsed to produce two successive bytes; the address is
   incremented by two.  The -B, -O, -D, or -H switch may be included with -W to
   specify the radix interpretation.


   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.

    3. A hex value that is also a machine instruction mnemonic (e.g., ADD) is
       parsed as the latter unless the -H switch is specified.  If both -M and
       -H are specified, the mnemonic is parsed as a machine instruction, and
       any operands present are parsed as hex values.  Preceding a hex value
       with a leading zero ensures a numeric interpretation.

    4. The specific error value returned is irrelevant, as callers will use the
       fallback parser if any status error is returned.  By convention, SCPE_ARG
       is returned both to indicate a parsing error and to request the default
       numeric parse.

    5. The radix and -C switches are mutually exclusive (as are -C and the
       mnemonic mode switches, and all of the format switches).  While there is
       a status code we could return (SCPE_INVSW, "Invalid switch"), it will not
       report the error, as explained above.  Instead, the fallback parser
       would ignore -C and use just the radix switch.  Therefore, we simply
       ignore conflicting switches and pick an arbitrary execution order.

    6. The radix specified by a supplied numeric radix switch is determined
       prior to calling this routine.  But only in the register case is the
       value communicated to this routine.  Therefore, we must call
       "sim_get_radix" again here if the mnemonic mode or default byte mode is
       specified.

    7. When a byte value is specified for a byte-oriented device, the parsed
       value must be limited to the 8-bit range.  If the value is out of range,
       we must not allow the caller to deposit it.  However, simply returning
       SCPE_ARG will invoke the default numeric parser, which will restrict the
       value to the 16-bit word range.  To prevent this, we alter the parsed
       symbol to a character, so that the default numeric parser will fail and
       communicate an "Invalid argument" error back to the user.

    8. In the absence of an error, the return value represents the number of
       additional addressing units (words or bytes) by which to increment the
       address counter when storing a block of values.  When addresses represent
       words, increments of 1, 2, 3, etc. are represented by return values of 0,
       -1, -2, etc.  For byte-oriented devices, word increments of 1, 2, 3, etc.
       are adjusted for bytes instead of words, producing return values of -1,
       -3, -5, etc.
*/

t_stat parse_sym (char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw)
{
DEVICE  *dptr;
uint32  radix;
t_value value;
t_stat  result;
t_bool  packed;

if (uptr == NULL)                                       /* if this is a register or sim stop print */
    packed = FALSE;                                     /*   then byte packing does not apply */

else {                                                  /* otherwise it's an evaluation or address print */
    dptr = find_dev_from_unit (uptr);                   /*   so get the device pointer from the unit */

    if (dptr == NULL)                                   /* if the unit is not associated with a device */
        return SCPE_IERR;                               /*   then it cannot be printed */
    else                                                /* otherwise */
        packed = (dptr->aincr == 2);                    /*   the device is packed if the increment is 2 */
    }

if (not (sw & MODE_SWITCHES))                           /* if no symbolic modes are present */
    if (*cptr == '\'' && cptr++)                        /*   then if a character parse is implied */
        sw |= A_SWITCH;                                 /*     then set the -A switch */

    else if (*cptr == '"' && cptr++)                    /*   otherwise if a character string parse is implied */
        sw |= C_SWITCH;                                 /*     then set the -C switch */

    else if (not (sw & H_SWITCH) && isalpha (*cptr))    /*   otherwise if this is not a hex number but is alphabetic */
        sw |= M_SWITCH;                                 /*     then set the -M switch */

if (sw & M_SWITCH) {                                    /* if this is an instruction parse */
    if (sw & A_SWITCH)                                  /*   then if the operands are alphabetic */
        radix = 256;                                    /*     then use radix 256 */
    else                                                /*   otherwise */
        radix = sim_get_radix (NULL, sw, 0);            /*     use an explicit operand radix if present */

    result = parse_cpu (cptr, addr, val, radix);        /* attempt a mnemonic instruction parse */
    }

else if (sw & C_SWITCH && cptr [0] != '\0') {           /* otherwise if a string is present */
    val [0] = (t_value) TO_WORD (cptr [0], cptr [1]);   /*   then convert the character value(s) */
    result = SCPE_OK;                                   /*     and indicate success */
    }

else if (sw & W_SWITCH)                                 /* otherwise if parsing a full word */
    result = SCPE_ARG;                                  /*   then use the fallback numeric parser */

else if (sw & A_SWITCH && cptr [0] != '\0') {           /* otherwise if a character is present */
    if (packed)                                         /*   then if bytes are packed */
        val [0] = REPLACE_UPPER (val [0], cptr [0]);    /*     then put the character in the upper byte */
    else                                                /*   otherwise */
        val [0] = REPLACE_LOWER (val [0], cptr [0]);    /*     put the character in the lower byte */

    return SCPE_OK;                                     /* indicate a successful parse */
    }

else if (packed) {                                      /* otherwise if bytes are packed */
    radix = sim_get_radix (NULL, sw, dptr->dradix);     /*   then an explicit operand radix if present */
    value = get_uint (cptr, radix, D8_UMAX, &result);   /*     and parse a numeric byte value */

    if (result == SCPE_OK)                              /* if a byte was successfully parsed */
        val [0] = REPLACE_UPPER (val [0], value);       /*   then save the value in the upper byte of the word */
    else                                                /* otherwise the value is bad or out of range */
        *cptr = 'X';                                    /*   so force an error when the default parser is called */

    return result;                                      /* return the parse status */
    }

else                                                    /* otherwise it's not a symbolic parse */
    result = SCPE_ARG;                                  /*   so use the fallback numeric parser */

if (not packed || result > 0)                           /* if bytes are not packed or an error occurred */
    return result;                                      /*   then return the result value */
else                                                    /* otherwise excess words were consumed */
    return result * 2 - 1;                              /*   so return the conversion to excess bytes */
}


/* 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, 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, 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; not (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 */
}


/* Examine a word from a byte-oriented device.

   This SCP support routine is called to retrieve a 16-bit word from a device
   that operates on 8-bit bytes.  Two bytes are read from the device and
   assembled into a 16-bit word.  When such HP devices store words, the
   most-significant byte is read first.

   On entry, the "value" parameter points at the location to receive the
   assembled word, "addr" contains the device address of the desired word,
   "uptr" points at the unit of the device from which to read, and "switches"
   contains any command-line switches set by the user.  The routine returns
   SCPE_OK if the word was read successfully and stored in the "value" location.
   If an error occurs, the appropriate status code is returned.


   Implementation notes:

    1. The presence of the UNIT_FIX flag indicates that the attached file has a
       fixed size.  If an EOF is encountered, the user has requested a valid
       address (this is checked before entry) that has not yet been written to
       the file.  Therefore, we return SCPE_OK to use the unchanged value
       (typically zero).  If UNIT_FIX is not present, the file has a variable
       size, and accessing beyond the EOF is an error.
*/

t_stat hp_word_examine (t_value *value, t_addr addr, UNIT *uptr, int32 switches)
{
uint8  buffer [2];
size_t count;

if (not (uptr->flags & UNIT_ATT))                       /* if the unit is not attached */
    return SCPE_UNATT;                                  /*   then we cannot examine it */

else if (uptr->fileref == NULL                          /* otherwise if the attached object is not a file */
  || uptr->dynflags & UNIT_PIPE)                        /*   or the file is a pipe */
    return SCPE_NOFNC;                                  /*     then we cannot examine it */

else if (uptr->flags & UNIT_FIX && addr >= uptr->capac) /* otherwise if the address is out of range */
    return SCPE_NXM;                                    /*   then report an addressing error */

else {                                                  /* otherwise it's an attached file */
    sim_fseek (uptr->fileref, addr, SEEK_SET);          /*   so position to the addressed word */
    count = sim_fread (buffer, sizeof (uint8), 2,       /*     and read two bytes */
                       uptr->fileref);

    if (count == 1) {                                   /* if only one byte was read */
        *value = TO_WORD (buffer [0], 0);               /*   then store it in the upper byte */
        return SCPE_OK;                                 /*     and return success */
        }

    else if (feof (uptr->fileref))                      /* otherwise if we hit the end of the file */
        if (uptr->flags & UNIT_FIX)                     /*   then if the file size is fixed */
            return SCPE_OK;                             /*     then return with no change to the value */
        else                                            /*   otherwise */
            return SCPE_EOF;                            /*     report the EOF error */

    else if (ferror (uptr->fileref)) {                  /* otherwise if a host file system error occurred */
        clearerr (uptr->fileref);                       /*   then clear the error */
        return SCPE_IOERR;                              /*     and report it to the user */
        }

    else {                                              /* otherwise the read was good */
        *value = TO_WORD (buffer [0], buffer [1]);      /*   so assemble the HP word */
        return SCPE_OK;                                 /*     and return success */
        }
    }
}


/* Deposit a word to a byte-oriented device.

   This SCP support routine is called to store a 16-bit word to a device that
   operates on 8-bit bytes.  The 16-bit word is split into two bytes and written
   to the device.  When such HP devices store words, the most-significant byte
   is written first.

   On entry, the "value" parameter contains the word to deposit, "addr" contains
   the device address of the desired word, "uptr" points at the unit of the
   device to which to write, and "switches" contains any command-line switches
   set by the user.  The routine returns SCPE_OK if the word was written
   successfully.  If an error occurs, the appropriate status code is returned.
*/

t_stat hp_word_deposit (t_value value, t_addr addr, UNIT *uptr, int32 switches)
{
uint8 buffer [2];

if (not (uptr->flags & UNIT_ATT))                       /* if the unit is not attached */
    return SCPE_UNATT;                                  /*   then we cannot deposit to it */

else if (uptr->fileref == NULL                          /* otherwise if the attached object is not a file */
  || uptr->dynflags & UNIT_PIPE)                        /*   or the file is a pipe */
    return SCPE_NOFNC;                                  /*     then we cannot deposit to it */

else if (uptr->flags & UNIT_FIX && addr >= uptr->capac) /* otherwise if the address is out of range */
    return SCPE_NXM;                                    /*   then report an addressing error */

else {                                                  /* otherwise it's an attached file */
    buffer [0] = UPPER_BYTE (value);                    /*   then split the word */
    buffer [1] = LOWER_BYTE (value);                    /*     into high and low bytes */

    sim_fseek (uptr->fileref, addr, SEEK_SET);              /* position to the addressed word */
    sim_fwrite (buffer, sizeof (uint8), 2, uptr->fileref);  /*   and write two bytes */

    if (ferror (uptr->fileref)) {                       /* if a host file system error occurred */
        clearerr (uptr->fileref);                       /*   then clear the error */
        return SCPE_IOERR;                              /*     and report it to the user */
        }

    else                                                /* otherwise the write was good */
        return SCPE_OK;                                 /*   so report success to the user */
    }
}



/* System interface global utility routines */


/* Return the bit number of the LSB.

   The number of the first 1-bit encountered when scanning from the LSB end of a
   word is returned.  This is equivalent to counting the number of 0-bits
   preceding the first 1-bit.  The GNU C, clang, and MSVC compilers provide
   intrinsic functions that result in single host-processor machine
   instructions.  For other compilers, we provide a software function that
   provides the same result.

   The MSVC intrinsic requires a target variable in which the bit number is
   placed, so we define a global here that is referenced by uses of the
   intrinsic call elsewhere.


   Implementation notes:

    1. The MSVC intrinsic requires an unsigned long int variable as the target.

    2. If the parameter value is zero, the software routine returns the size of
       the word in bits, e.g. 32.  The return value from the hardware
       instruction is processor-dependent.

    3. Shifting the mask bit "off the end" of the mask variable is guaranteed to
       be zero by the C99 standard ("6.5.7 Bitwise shift operators", paragraph
       4, "If E1 has an unsigned type, the value of the result is E1 * 2 ^ E2,
       reduced modulo one more than the maximum value representable in the
       result type").
*/

#if defined (_MSC_VER)                          /* MSVC intrinsic */

  unsigned long int hp_bit_number;              /* target for the _BitScanForward intrinsic */

#elif not defined (__GNUC__)                    /* not gcc or clang, which have intrinsics */

  unsigned int hp_bit_number (unsigned int bits, unsigned int hpib)
  {
  unsigned int bit_number, mask;

  if (hpib)                                     /* if HP-IB numbering is selected */
      for (bit_number = 0, mask = 0x80;         /*   so starting with bit 7 (MSB) and continuing */
           ~bits & mask;                        /*     until a 1-bit is found or all bits are examined */
           bit_number++, mask >>= 1);           /*       bump the bit number and move the bit mask */

  else                                          /* otherwise normal bit numbering is selected */
      for (bit_number = 0, mask = 1;            /*   so starting with bit 0 (LSB) and continuing */
           ~bits & mask;                        /*     until a 1-bit is found or all bits are examined */
           bit_number++, mask <<= 1);           /*       bump the bit number and move the bit mask */

  return bit_number;
  }

#endif


/* Print a CPU instruction in symbolic format.

   This routine is called to format and print an instruction in mnemonic form.
   The "ofile" parameter is the opened output stream, "val [*]" contains the
   word(s) comprising the machine instruction to print, "radix" contains the
   desired operand radix or zero if the default radix is to be used, and
   "switches" includes the SIM_SW_STOP switch if the routine was called as part
   of a simulation stop.

   The routine returns a status code to the caller.  SCPE_OK status is returned
   if the print consumed a single-word value, or the negative number of extra
   words (beyond the first) consumed by printing the instruction is returned.
   For example, printing a symbol that resulted in two words being consumed
   (from val [0] and val [1]) would return SCPE_OK_2_WORDS (= -1).

   HP 3000 machine instructions are generally classified by the first four bits.
   Within each class, additional bits identify sub-classes or individual
   instructions.

   Most of the decoding work is handled by the "fprint_instruction" routine,
   which prints mnemonics and operands and returns a status code indicating the
   number of words consumed for the current instruction.


   Implementation notes:

    1. For a stack instruction, if the R (right stack-op pending) bit in the
       status word is set, and the request is for a simulation stop, the
       left-hand opcode will print as dashes to indicate that it has already
       been executed.

    2. The status return from "fprint_instruction" is always SCPE_OK for the
       stack_ops table, which is fully decoded, so the return value from
       printing the left stack opcode is not used.
*/

t_stat fprint_cpu (FILE *ofile, t_value *val, uint32 radix, int32 switches)
{
const char *dashes = "----,";
t_stat status = SCPE_OK;

switch (SUBOP (val [0])) {                                  /* dispatch based on the instruction sub-opcode */

    case 000:                                               /* stack operations */
        if (STA & STATUS_R && switches & SIM_SW_STOP)       /* if right stack-op pending and this is a simulation stop */
            fputs (dashes + 4                               /*   then indicate that the left stack-op has completed */
               - strlen (stack_ops [STACKOP_A (val [0])].mnemonic), ofile);

        else {                                              /* otherwise */
            (void) fprint_instruction (ofile, stack_ops,    /*   print the left operation */
                                       val, STACKOP_A_MASK, /*     while discarding the status */
                                       STACKOP_A_SHIFT, radix);
            fputc (',', ofile);                             /* add a separator */
            }

        status = fprint_instruction (ofile, stack_ops,      /* print the right operation */
                                     val, STACKOP_B_MASK,
                                     STACKOP_B_SHIFT, radix);
        break;

    case 001:                                               /* shift/branch/bit operations */
        status = fprint_instruction (ofile, sbb_ops,        /* print the operation */
                                     val, SBBOP_MASK,
                                     SBBOP_SHIFT, radix);
        break;

    case 002:                                               /* move/special/firmware/immediate/field/register operations */
        status = fprint_instruction (ofile, msfifr_ops,     /* print the operation */
                                     val, MSFIFROP_MASK,
                                     MSFIFROP_SHIFT, radix);
        break;

    case 003:                                               /* I/O/control/program/immediate/memory operations */
        if (val [0] & IOCPIMOP_MASK)                        /* if it is a program, immediate, or memory instruction */
            status = fprint_instruction (ofile, pmi_ops,    /*   then print the operation */
                                         val, IOCPIMOP_MASK,
                                         IOCPIMOP_SHIFT, radix);

        else                                                /* otherwise it is an I/O or control operation */
            status = fprint_instruction (ofile, ioc_ops,    /*   so print the operation */
                                         val, IOCSUBOP_MASK,
                                         IOCSUBOP_SHIFT, radix);
        break;


    default:                                                /* memory, loop, and branch operations */
        status = fprint_instruction (ofile, mlb_ops,        /* print the operation */
                                     val, MLBOP_MASK,
                                     MLBOP_SHIFT, radix);
        break;
    }

return status;                                              /* return the consumption status */
}


/* Print an EDIT instruction subprogram operation in symbolic format.

   This routine prints a single EDIT subprogram operation.  The opcode name is
   obtained from the name table and printed, followed by the operands.

   On entry, the "ofile" parameter is the opened output stream, "val" is NULL or
   points to an array containing the first two words of the operation, "radix"
   contains the desired operand radix or zero if the default radix is to be
   used, and "byte_address" points at the first byte of the subprogram
   operation.  The return value is the number of subprogram bytes used by the
   operation.

   This routine is called by the SCP "EXAMINE" command processor to display an
   EDIT subprogram contained in memory in symbolic format.  It is also called
   by the EDIT instruction executor when tracing memory operands.  In the former
   case, the "EXAMINE" command has obtained two words from memory and placed
   them in the array pointed to by the "val" parameter.  In the latter case,
   "val" will be NULL, indicating that the array must be loaded explicitly.  In
   either case, additional memory bytes are obtained as needed by the operation
   decoder.

   For each operation, the following type of operand and the number of bytes
   required are listed below:

     Opcode  SubOp  Mnem  Operand    Byte Count
     ------  -----  ----  ---------  ----------
       00      -    MC    ux            1-2
       01      -    MA    ux            1-2
       02      -    MN    ux            1-2
       03      -    MNS   ux            1-2
       04      -    MFL   ux            1-2
       05      -    IC    ux,'c'        2-3
       06      -    ICS   ux,'c'        2-3
       07      -    ICI   ux,"s"       2-257
       10      -    ICSI  ux,"s"       2-257
       11      -    BRIS  sx            1-2
       12      -    SUFT  sx            1-2
       13      -    SUFS  sx            1-2
       14      -    ICP   'p'            1
       15      -    ICPS  'p'            1
       16      -    IS    u,"s","s"     1-31
       17     00    TE    (none)         1
       17     01    ENDF  (none)         1
       17     02    SST1  (none)         1
       17     03    SST0  (none)         1
       17     04    MDWO  (none)         1
       17     05    SFC   'c'            2
       17     06    SFLC  'p','p'        2
       17     07    DFLC  'c','c'        3
       17     10    SETC  ui             2
       17     11    DBNZ  s              2

   Where the operands are:

     Operand  Meaning
     -------  -----------------------------------------------
     u        unsigned value 0 .. 15
     ux       unsigned value 1 .. 15 or 0 .. 255 (extended)
     ui       unsigned value 1 .. 256
     s        signed value -128 .. +127
     sx       signed value 1 .. 15 or -128 .. +127 (extended)
     'p'      punctuation character value ' ' + 0 .. 15
     'c'      character value 0 .. 255
     "s"      string value of 0 .. 255 characters

   Note that in only three cases must additional memory accesses be performed.


   Implementation notes:

    1. Operation decoding is simplified by translating the prefix opcode (17)
       and subopcodes (00-11) into extended opcodes 17+0 to 17+11 (17-30).

    2. Branch operation displacements are printed as positive to indicate a
       forward jump and negative to indicate a backward jump.  The radix used is
       the CPU's address radix, which is octal.

    3. The Machine Instruction Set manual says that the DBNZ ("decrement loop
       count and branch") operation adds the displacement operand.  However,
       the microcode manual shows that the displacement is actually subtracted.
       Therefore, we print the operand as a negative value to conform with the
       hardware action.

    4. A simple GET_BYTE macro is defined for easy access to the bytes contained
       in the "val" array.  The macro also increments the byte address and
       increments the word array pointer as needed.
*/

#define GET_BYTE(b) \
          if (byte_address++ & 1) \
              b = LOWER_BYTE (*val++); \
          else \
              b = UPPER_BYTE (*val)

uint32 fprint_edit (FILE *ofile, t_value *val, uint32 radix, uint32 byte_address)
{
uint8   byte, opcode, operand;
uint32  word_address, byte_count, disp_radix;
t_value eval_array [2];

if (radix == 0)                                         /* if the supplied radix is defaulted */
    disp_radix = 10;                                    /*   then assume decimal numeric output */
else                                                    /* otherwise */
    disp_radix = radix;                                 /*   use the radix given */

if (val == NULL) {                                      /* if the "val" array has not been loaded */
    word_address = byte_address / 2;                    /*   then get the word address of the operation */

    mem_examine (&eval_array [0], word_address,     NULL, 0);   /* load the two words */
    mem_examine (&eval_array [1], word_address + 1, NULL, 0);   /*   containing the target operation */

    val = eval_array;                                   /* point at the array containing the operation */
    }

GET_BYTE (byte);                                        /* get the opcode byte */
byte_count = 1;                                         /*   and count it */

opcode  = UPPER_HALF (byte);                            /* separate the opcode */
operand = LOWER_HALF (byte);                            /*   and the immediate operand */

if (operand == 0 && opcode <= EDIT_SUFS) {              /* if an extended immediate operand is indicated */
    GET_BYTE (operand);                                 /*   then get it */
    byte_count = 2;                                     /*     and count it */
    }

if (opcode == EDIT_EXOP)                                /* if the opcode is extended */
    opcode = opcode + operand;                          /*   then translate it */

if (opcode <= EDIT_DBNZ) {                              /* if the extended opcode is valid */
    fputs (edit_ops [opcode], ofile);                   /*   then print the associated mnemonic */

    if (opcode < EDIT_TE || opcode > EDIT_MDWO)         /* if the operation has an operand */
        fputc (' ', ofile);                             /*   then add a space as a separator */
    }

switch (opcode) {                                       /* dispatch by the extended opcode */

    case 000:                                           /* MC   - move characters */
    case 001:                                           /* MA   - move alphabetics */
    case 002:                                           /* MN   - move numerics */
    case 003:                                           /* MNS  - move numerics suppressed */
    case 004:                                           /* MFL  - move numerics with floating insertion */
        fprint_value (ofile, operand, disp_radix,       /* print the character count */
                      D8_WIDTH, PV_LEFT);
        break;


    case 005:                                           /* IC   - insert character */
    case 006:                                           /* ICS  - insert character suppressed */
        GET_BYTE (byte);                                /* get the insertion character */
        byte_count = byte_count + 1;                    /*   and count it */

        fprint_value (ofile, operand, disp_radix,       /* print the insertion count */
                      D8_WIDTH, PV_LEFT);

        fputc (',', ofile);                             /* add a separator */
        fputs (fmt_char ((uint32) byte), ofile);        /*   and print the insertion character */
        break;


    case 007:                                           /* ICI  - insert characters immediate */
    case 010:                                           /* ICSI - insert characters suppressed immediate */
        fprint_value (ofile, operand, disp_radix,       /* print the character count */
                      D8_WIDTH, PV_LEFT);

        fputs (",\"", ofile);                                       /* add a separator and open the quotation */
        fputs (fmt_byte_operand (byte_address, operand), ofile);    /* print the string operand */
        fputc ('"', ofile);                                         /*   and close the quotation */

        byte_count = byte_count + operand;              /* count the bytes */
        break;


    case 030:                                           /* DBNZ - decrement loop count and branch */
        GET_BYTE (operand);                             /* get the signed branch displacement */
        byte_count = byte_count + 1;                    /*   and count it */

        if (operand == 0200) {                              /* if the operand is -128 */
            fputc ('+', ofile);                             /*   then print a plus sign for display */
            fprint_value (ofile, operand, cpu_dev.aradix,   /*     and the displacement */
                          D8_WIDTH, PV_LEFT);               /*       in the CPU's address radix */
            break;                                          /*         and we're done */
            }

        else                                            /* otherwise */
            operand = NEG8 (operand);                   /*   negate the operand for display */

    /* fall through into the BRIS case */

    case 011:                                           /* BRIS - branch if significance */
        if (operand & D8_SIGN) {                        /* if the displacement is negative */
            fputc ('-', ofile);                         /*   then print a minus sign */
            operand = NEG8 (operand);                   /*     and make the value positive */
            }

        else                                            /* otherwise */
            fputc ('+', ofile);                         /*   print a plus sign */

        fprint_value (ofile, operand, cpu_dev.aradix,   /* print the displacement */
                      D8_WIDTH, PV_LEFT);               /*   in the CPU's address radix */
        break;


    case 012:                                           /* SUFT - subtract from target */
    case 013:                                           /* SUFS - subtract from source */
        if (operand & D8_SIGN) {                        /* if the displacement is negative */
            fputc ('-', ofile);                         /*   then print a minus sign */
            operand = NEG8 (operand);                   /*     and make the value positive */
            }

        fprint_value (ofile, operand, disp_radix,       /* print the displacement */
                      D8_WIDTH, PV_LEFT);
        break;


    case 014:                                               /* ICP  - insert character punctuation */
    case 015:                                               /* ICPS - insert character punctuation suppressed */
        fputs (fmt_char ((uint32) (operand + ' ')), ofile); /* print the punctuation character */
        break;


    case 016:                                           /* IS   - insert character on sign */
        fprint_value (ofile, operand, disp_radix,       /* print the character count */
                      D8_WIDTH, PV_LEFT);

        fputs (",\"", ofile);                                       /* add a separator and open the quotation */
        fputs (fmt_byte_operand (byte_address, operand), ofile);    /* print the string operand */

        fputs ("\",\"", ofile);                                             /* close and open the second quotation */
        fputs (fmt_byte_operand (byte_address + operand, operand), ofile);  /* print the string operand */
        fputc ('"', ofile);                                                 /*   and close the quotation */

        byte_count = byte_count + 2 * operand;          /* count the bytes */
        break;


    case 017:                                           /* TE   - terminate edit */
    case 020:                                           /* ENDF - end floating point insertion */
    case 021:                                           /* SST1 - set significance to 1 */
    case 022:                                           /* SST0 - set significance to 0 */
    case 023:                                           /* MDWO - move digit with overpunch */
        break;                                          /* these opcodes have no operands */


    case 024:                                           /* SFC  - set fill character */
        GET_BYTE (byte);                                /* get the fill character */
        byte_count = byte_count + 1;                    /*   and count it */

        fputs (fmt_char ((uint32) byte), ofile);        /* print the fill character */
        break;

    case 025:                                           /* SFLC - set float character */
        GET_BYTE (byte);                                /* get the float character */
        byte_count = byte_count + 1;                    /*   and count it */

        fputs (fmt_char ((uint32) (UPPER_HALF (byte) + ' ')), ofile);   /* print the positive float character */
        fputc (',', ofile);                                             /*   and a separator */
        fputs (fmt_char ((uint32) (LOWER_HALF (byte) + ' ')), ofile);   /*     and the negative float character */
        break;


    case 026:                                           /* DFLC - define float character */
        GET_BYTE (byte);                                /* get the float character */

        fputs (fmt_char ((uint32) byte), ofile);        /* print the positive float character */

        GET_BYTE (byte);                                /* get the float character */
        byte_count = byte_count + 2;                    /*   and count both characters */

        fputc (',', ofile);                             /* print a separator */
        fputs (fmt_char ((uint32) byte), ofile);        /*   and the negative float character */
        break;


    case 027:                                           /* SETC - set loop count */
        GET_BYTE (byte);                                /* get the loop count */
        byte_count = byte_count + 1;                    /*   and count it */

        if (byte == 0)                                  /* if the count is zero */
            fprint_value (ofile, 256, disp_radix,       /*   then the loop executes 256 times */
                          D16_WIDTH, PV_LEFT);
        else                                            /* otherwise */
            fprint_value (ofile, byte, disp_radix,      /*   print the given loop count */
                          D8_WIDTH, PV_LEFT);
        break;


    default:                                            /* the opcode is invalid */
        if (radix == 0)                                 /* if the supplied radix is defaulted */
            disp_radix = cpu_dev.dradix;                /*   then use the data radix */
        else                                            /* otherwise */
            disp_radix = radix;                         /*   use the radix given */

        fprint_value (ofile, EDIT_EXOP, disp_radix,     /* print the extended opcode */
                      D4_WIDTH, PV_RZRO);

        fputc (',', ofile);                             /* print a separator */
        fprint_value (ofile, opcode - EDIT_EXOP,        /*   and the invalid opcode in numeric form */
                      disp_radix, D4_WIDTH, PV_RZRO);
        break;
    }

return byte_count;                                      /* return the number of bytes used by the operation */
}


/* Format the status register flags and condition code.

   This routine formats the flags and condition code part of the status register
   and returns a pointer to the formatted string.  It does not format the
   current code segment number part of the register.

   The six status flags are represented by letters.  If the flag is set, an
   uppercase letter is used; if it is clear, a lowercase letter is used.  The
   condition code is represented by the strings "CCL", "CCE", or "CCG" for the
   less than, equal to, or greater than conditions.  If the condition code is
   the invalid value, "CC?" is used.
*/

const char *fmt_status (uint32 status)
{
static const char conditions [] = "GLE?";
static const char flags [] = "m i t r o c CCx";
static char formatted [sizeof flags];
uint32 index;

strcpy (formatted, flags);                              /* copy the initial flags template */

formatted [14] = conditions [TO_CCN (status)];          /* set the condition code representation */

for (index = 0; index < 6 * 2; index = index + 2) {     /* loop through the six MSBs (the flags) */
    if (status & D16_SIGN)                              /* if the bit is set */
        formatted [index] =                             /*   then convert the corresponding flag */
           (char) toupper (formatted [index]);          /*     to upper case */

    status = status << 1;                               /* position the next flag for testing */
    }

return formatted;                                       /* return a pointer to the formatted string */
}


/* Format a character for printing.

   This routine formats a single 8-bit character value into a printable string
   and returns a pointer to that string.  Printable characters retain their
   original form but are enclosed in single quotes.  Control characters are
   translated to readable strings.  Characters outside of the ASCII range are
   presented as escaped octal values.


   Implementation notes:

    1. The longest string to be returned is a five-character escaped string
       consisting of a backslash, three octal digits, and a trailing NUL.  The
       end-of-buffer pointer has an allowance to ensure that the string will
       fit.

    2. The routine returns a pointer to a static buffer containing the printable
       string.  To allow the routine to be called more than once per trace line,
       the null-terminated format strings are concatenated in the buffer, and
       each call returns a pointer that is offset into the buffer to point at
       the latest formatted string.

    3. There is no explicit buffer-free action.  Instead, newly formatted
       strings are appended to the buffer until there is no more space
       available.  At that point, the pointers are reset to the start of the
       buffer.  In effect, this provides a circular buffer, as previously
       formatted strings are overwritten by subsequent calls.

    4. The buffer is sized to hold the maximum number of concurrent strings
       needed for a single trace line.  If more concurrent strings are used, one
       or more strings from the earliest calls will be overwritten.
*/

const char *fmt_char (uint32 charval)
{
static const char *const control [] = {
    "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
    "BS",  "HT",  "LF",  "VT",  "FF",  "CR",  "SO",  "SI",
    "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
    "CAN", "EM",  "SUB", "ESC", "FS",  "GS",  "RS",  "US"
    };
static char fmt_buffer [64];                                /* the return buffer */
static char *freeptr = fmt_buffer;                          /* pointer to the first free character in the buffer */
static char *endptr  = fmt_buffer + sizeof fmt_buffer - 5;  /* pointer to the end of the buffer (less allowance) */
const  char *fmtptr;

if (charval <= '\037')                                  /* if the value is an ASCII control character */
    return control [charval];                           /*   then return a readable representation */

else if (charval == '\177')                             /* otherwise if the value is the delete character */
    return "DEL";                                       /*   then return a readable representation */

else {
    if (freeptr > endptr)                               /* if there is not enough room left to append the string */
        freeptr = fmt_buffer;                           /*   then reset to point at the start of the buffer */

    fmtptr = freeptr;                                   /* initialize the return pointer */
    *freeptr = '\0';                                    /*   and the format accumulator */

    if (charval > '\177')                                   /* otherwise if the value is beyond the printable range */
        freeptr = freeptr + sprintf (freeptr, "\\%03o",     /*   then format the value */
                                     charval & D8_MASK);    /*     and update the free pointer */

    else {                                              /* otherwise it's a printable character */
        *freeptr++ = '\'';                              /*   so form a representation */
        *freeptr++ = (char) charval;                    /*     containing the character */
        *freeptr++ = '\'';                              /*       surrounded by single quotes */
        *freeptr   = '\0';
        }

    freeptr = freeptr + 1;                              /* advance past the NUL terminator */
    return fmtptr;                                      /*   and return the formatted string */
    }
}


/* Format a numeric value.

   This routine formats a 32-bit numeric value into a printable string and
   returns a pointer to that string.  It performs the same function as the SCP
   "fprint_val" routine, except that the formatted string is returned rather
   than printed on the supplied stream.  This allows the returned string to be
   part of a single "fprintf" call, rather than having to piecemeal the
   formatted line from multiple stream output calls.

   On entry, "value" is the numeric value to format, "radix" is the radix for
   the display, "width" is the number of bits that encompasses the maximum
   value, and "format" is one of the print value format codes (PV_LEFT,
   PV_RSPC, or PV_RZRO).

   The routine returns a pointer into a static buffer containing the formatted
   value.  The buffer is circular and large enough to accommodate many
   concurrent formatted values, so the routine can be called multiple times
   before any of the formatted values are used.
*/

const char *fmt_value (uint32 value, uint32 radix, uint32 width, uint32 format)
{
static char fmt_buffer [1024];                              /* the return buffer */
static char *freeptr = fmt_buffer;                          /* the pointer to the first free character in the buffer */
static char *endptr  = fmt_buffer + sizeof fmt_buffer - 1;  /* the pointer to the end of the buffer */
uint32 digit, maxval, provided, test;
char   digits [33];
char   *digptr = digits + sizeof digits - 1;            /* the pointer to the end of the buffer */
uint32 used = 0;                                        /* the number of digits in the formatted value */

*digptr = '\0';                                         /* store a terminating NUL in the buffer */

do {                                                    /* add each digit to the buffer */
    digit = value % radix;                              /* get the current digit */
    value = value / radix;                              /*   and the remaining value */

    if (digit > 9)                                      /* if the digit is non-numeric */
        *--digptr = (char) (digit - 10 + 'A');          /*   then convert to alpha */
    else                                                /* otherwise */
        *--digptr = (char) (digit + '0');               /*   convert to a number */

    used++;                                             /* bump the number of digits stored */
    }
while (value > 0 && digptr > digits);                   /* continue until all digits are added */

if (format != PV_LEFT) {                                /* if the format is right-justified */
    if (width == sizeof value * CHAR_BIT)               /*   then if the width encompasses all value bits */
        maxval = ~0u;                                   /*     then the maximum value is 2 ^ n - 1 */
    else                                                /*   otherwise */
        maxval = (1u << width) - 1u;                    /*     get the largest value for the width */

    provided = 1;                                       /* initialize the number of digits provided */
    test = radix;                                       /*   by the width specification */

    while (test < maxval) {                             /* while more digits are available */
        provided = provided + 1;                        /*   increase the digit count */

        if (test > test * radix)                        /* if the test value would overflow */
            break;                                      /*   then the provided digit count is complete */
        else                                            /* otherwise */
            test = test * radix;                        /*   add a new digit to the test value */
        }

    if (provided > used) {                                  /* if the width provided is larger than needed */
        digptr = digptr - (provided - used);                /*   then back up the pointer */
        memset (digptr, (format == PV_RZRO ? '0' : ' '),    /*     and fill the leading characters */
                provided - used);                           /*       with zeros or blanks as directed */

        used = provided;                                    /* use all of the digits provided */
        }
    }

if (freeptr + used > endptr)                            /* if there is not enough room left to append the value */
    freeptr = fmt_buffer;                               /*   then point back to the start of the buffer */

digptr = strcpy (freeptr, digptr);                      /* append the value to the buffer */

freeptr = freeptr + used + 1;                           /* advance the free space pointer past the digits and NUL */
return digptr;                                          /*   and return a pointer to the formatted value */
}


/* Format a set of named bits.

   This routine formats a set of up to 32 named bits into a printable string and
   returns a pointer to that string.  The names of the active bits are
   concatenated and separated by vertical bars.  For example:

     SIO OK | ready | no error | unit 0

   On entry, "bitset" is a value specifying the bits to format, and "bitfmt" is
   a BITSET_FORMAT structure describing the format to use.  The structure
   contains a count and a pointer to an array of character strings specifying
   the names of the valid bits in "bitset", the offset in bits from the LSB to
   the least-significant named bit, the direction in which to process the bits
   (from MSB to LSB, or vice versa), whether or not alternate names are present
   in the name array, and whether or not to append a final separator.  The names
   in the name array appear in the order corresponding to the supplied
   direction; invalid bits are indicated by NULL character names.  The pointer
   returned points at a character buffer containing the names of the valid bits
   that are set in the supplied value.  If no valid bits are set, then the
   buffer contains "(none)" if a trailing separator is omitted, or a null string
   ("") if a trailing separator is requested.

   The name_count and names fields describe the separately defined name string
   array.  The array must start with the first valid bit but need only contain
   entries through the last valid bit; NULL entries for the remaining bits in
   the word are not necessary.  For example, if bits 1-3 of a word are valid,
   then the name string array would have three entries.  If bits 1-3 and 5 are
   valid, then the array would have five entries, with the fourth entry set to
   NULL.

   The offset field specifies the number of unnamed bits to the right of the
   least-significant named bit.  Using the same examples as above, the offsets
   would be 12 and 10, respectively.

   The direction field specifies whether the bits are named from MSB to LSB
   (msb_first) or vice versa (lsb_first).  The order of the entries in the name
   string array must match the direction specified.  Continuing with the first
   example above, if the direction is msb_first, then the first name is for bit
   1; if the direction is lsb_first, then the first name is for bit 3.

   The alternate field specifies whether (has_alt) or not (no_alt) alternate
   conditions are represented by one or more bits.  Generally, bits represent
   Boolean conditions, e.g., a condition is present when the bit is 1 and absent
   when the bit is zero.  In these cases, the corresponding bit name is included
   or omitted, respectively, in the return string.

   Occasionally, bits will represent alternate conditions, e.g., where condition
   A is present when the bit is 1, and condition B is present when the bit is 0.
   For these, the bit name string should consist of both condition names in that
   order, with the "1" name preceded by the '\1' character and the "0" name
   preceded by the '\0' character.  For example, if 1 corresponds to "load" and
   0 to "store", then the bit name string would be "\1load\0store".  If
   alternate names are present, the has_alt identifier should be given, so that
   the indicated bits are checked for zero conditions.  If no_alt is specified,
   the routine stops as soon as all of the one-bits have been processed.

   The bar field specifies whether (append_bar) or not (no_bar) a vertical bar
   separator is appended to the formatted string.  Typically, a bitset
   represents a peripheral control or status word.  If the word also contains
   multiple-bit fields, a trailing separator should be requested, and the
   decoded fields should be concatenated by the caller with any named bits.  If
   the bitset is empty, the returned null string will present the proper display
   containing just the decoded fields.  If the bitset completely describes the
   word, then no appended separator is needed.

   Peripheral control and status words generally are decoded from MSB to LSB.  A
   bitset may also represent a set of inbound or outbound signals.  These should
   be decoded from LSB to MSB, as that is the order in which they are executed
   by the device interface routines.

   The implementation first generates a mask for the significant bits and
   positions the mask with the offset specified.  Then a test bit mask is
   generated; the bit is either the most- or least-significant bit of the
   bitset, depending on the direction indicated.

   For each name in the array of names, if the name is defined (not NULL), the
   corresponding bit in the bitset is tested.  If it is set, the name is
   appended to the output buffer; otherwise, it is omitted (unless the name has
   an alternate, in which case the alternate is appended).  The bitset is then
   shifted in the indicated direction, remasking to just the significant bits.
   Processing continues until there are no remaining significant bits (if no
   alternates are specified), or until there are no remaining names in the array
   (if alternates are specified).


   Implementation notes:

    1. The routine returns a pointer to a static buffer containing the printable
       string.  To allow the routine to be called more than once per trace line,
       the null-terminated format strings are concatenated in the buffer, and
       each call returns a pointer that is offset into the buffer to point at
       the latest formatted string.

    2. There is no explicit buffer-free action.  Instead, newly formatted
       strings are appended to the buffer until there is no more space
       available.  At that point, the string currently being assembled is moved
       to the start of the buffer, and the pointers are reset.  In effect, this
       provides a circular buffer, as previously formatted strings are
       overwritten by subsequent calls.

    3. The buffer is sized to hold the maximum number of concurrent strings
       needed for a single trace line.  If more concurrent strings are used, one
       or more strings from the earliest calls will be overwritten.  If an
       attempt is made to format a string larger than the buffer, an error
       indication string is returned.

    4. The location of the end of the buffer used to determine if the next name
       will fit includes an allowance for two separators that might be placed on
       either side of the name and a terminating NUL character.
*/

const char *fmt_bitset (uint32 bitset, const BITSET_FORMAT bitfmt)
{
static const char separator [] = " | ";                     /* the separator to use between names */
static char fmt_buffer [1024];                              /* the return buffer */
static char *freeptr = fmt_buffer;                          /* pointer to the first free character in the buffer */
static char *endptr  = fmt_buffer + sizeof fmt_buffer       /* pointer to the end of the buffer */
                         - 2 * (sizeof separator - 1) - 1;  /*   less allowance for two separators and a terminator */
const  char *bnptr, *fmtptr;
uint32 test_bit, index, bitmask;
size_t name_length;

if (bitfmt.name_count < D32_WIDTH)                      /* if the name count is the less than the mask width */
    bitmask = (1 << bitfmt.name_count) - 1;             /*   then create a mask for the name count specified */

else                                                    /* otherwise use a predefined value for the mask */
    bitmask = D32_MASK;                                 /*   to prevent shifting the bit off the MSB end */

bitmask = bitmask << bitfmt.offset;                     /* align the mask to the named bits */
bitset = bitset & bitmask;                              /*   and mask to just the significant bits */

if (bitfmt.direction == msb_first)                          /* if the examination is left-to-right */
    test_bit = 1 << bitfmt.name_count + bitfmt.offset - 1;  /*   then create a test bit for the MSB */
else                                                        /* otherwise */
    test_bit = 1 << bitfmt.offset;                          /*   create a test bit for the LSB */


fmtptr = freeptr;                                       /* initialize the return pointer */
*freeptr = '\0';                                        /*   and the format accumulator */
index = 0;                                              /*     and the name index */

while ((bitfmt.alternate || bitset)                     /* while more bits */
  && index < bitfmt.name_count) {                       /*   and more names exist */
    bnptr = bitfmt.names [index];                       /*     point at the name for the current bit */

    if (bnptr)                                          /* if the name is defined */
        if (*bnptr == '\1' && bitfmt.alternate)         /*   then if this name has an alternate */
            if (bitset & test_bit)                      /*     then if the bit is asserted */
                bnptr++;                                /*       then point at the name for the "1" state */
            else                                        /*     otherwise */
                bnptr = bnptr + strlen (bnptr) + 1;     /*       point at the name for the "0" state */

        else                                            /*   otherwise the name is unilateral */
            if ((bitset & test_bit) == 0)               /*     so if the bit is denied */
                bnptr = NULL;                           /*       then clear the name pointer */

    if (bnptr) {                                        /* if the name pointer is set */
        name_length = strlen (bnptr);                   /*   then get the length needed */

        if (freeptr + name_length > endptr) {           /* if there is not enough room left to append the name */
            strcpy (fmt_buffer, fmtptr);                /*   then move the partial string to the start of the buffer */

            freeptr = fmt_buffer + (freeptr - fmtptr);  /* point at the new first free character location */
            fmtptr = fmt_buffer;                        /*   and reset the return pointer */

            if (freeptr + name_length > endptr)         /* if there is still not enough room left to append */
                return "(buffer overflow)";             /*   then this call is requires a larger buffer! */
            }

        if (*fmtptr != '\0') {                          /* if this is not the first name added */
            strcpy (freeptr, separator);                /*   then add a separator to the string */
            freeptr = freeptr + strlen (separator);     /*     and move the free pointer */
            }

        strcpy (freeptr, bnptr);                        /* append the bit's mnemonic to the accumulator */
        freeptr = freeptr + name_length;                /*   and move the free pointer */
        }

    if (bitfmt.direction == msb_first)                  /* if formatting is left-to-right */
        bitset = bitset << 1 & bitmask;                 /*   then shift the next bit to the MSB and remask */
    else                                                /* otherwise formatting is right-to-left */
        bitset = bitset >> 1 & bitmask;                 /*   so shift the next bit to the LSB and remask */

    index = index + 1;                                  /* bump the bit name index */
    }


if (*fmtptr == '\0')                                    /* if no names were output */
    if (bitfmt.bar == append_bar)                       /*   then if concatenating with more information */
        return "";                                      /*     then return an empty string */
    else                                                /*   otherwise it's a standalone format */
        return "(none)";                                /*     so return a placeholder */

else if (bitfmt.bar == append_bar) {                    /* otherwise if a trailing separator is specified */
    strcpy (freeptr, separator);                        /*   then add a separator to the string */
    freeptr = freeptr + strlen (separator) + 1;         /*     and account for it plus the trailing NUL */
    }

else                                                    /* otherwise */
    freeptr = freeptr + 1;                              /*   just account for the trailing NUL */

return fmtptr;                                          /* return a pointer to the formatted string */
}


/* Format and print a trace line to the debug log file.

   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 trace 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 "tprintf"
   macro, which tests that tracing is enabled for the specified flag before
   calling this function.  This eliminates the calling overhead if tracing is
   disabled.

   This routine prints a prefix before the supplied format string consisting of
   the device name (in upper case) and the trace 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 trace flag
   name among the devices enabled for tracing to ensure that all trace lines
   will align for easier reading.

   Because the prefix is output only once, embedded newlines should not be
   present in the format string.  If multiple output lines are desired, then
   this routine should be called multiple times, so that each line receives an
   identifying trace prefix.


   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.

    2. If the trace output is being written to stdout, a terminating LF must
       be translated to CR LF.  This is because the console is in "raw" mode
       while the CPU is executing instructions.  Output to a file does not need
       this processing, as text mode handles the host line-end convention.

    3. Handling embedded newlines properly would require multiple calls to
       "vfprintf", each preceded by the prefix, and each having a format string
       consisting of the next segment that ends with an embedded newline.
       However, multiple calls are not allowed.  The C standard says:

         "As the functions vfprintf [etc.] invoke the va_arg macro, the value of
          arg after the return is indeterminate."

       So there is no way to have the second (e.g.) call start with those
       (variable) parameters not consumed by the prior call.  Consequently, the
       terminating LF check need only be done at the end of the format string.
*/

#define FLAG_SIZE           32                          /* sufficiently large to accommodate all flag names */
#define FORMAT_SIZE         1024                        /* sufficiently large to accommodate all format strings */

void hp_trace (DEVICE *dptr, uint32 flag, ...)
{
const char *nptr;
va_list    argptr;
DEBTAB     *debptr;
char       *format, *fptr;
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 trace flags table */

    if (debptr != NULL)                                 /* if the trace 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 */

                if (sim_deb == stdout) {                            /* if debug output is to the (raw) console */
                    fptr = header_fmt + strlen (header_fmt) - 1;    /*   then find the end of the string */

                    if (*fptr == '\n') {                /* if the format ends with a LF */
                        *fptr++ = '\r';                 /*   then replace it */
                        *fptr++ = '\n';                 /*     with a CR LF sequence */
                        *fptr   = '\0';                 /*       and terminate with a NUL */
                        }
                    }

                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 trace 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;
DEVICE        *dptr;
uint32        dev, val, device_count;
CONFLICT_TYPE conf, conflict_is;
int32         count;
int32         dib_val   [SIM_DEVICE_SIZE] [CONFLICT_COUNT];
int32         conflicts [DEVNO_MAX + 1];

for (dev = 0; dev < SIM_DEVICE_SIZE; dev++) {           /* fill in the DIB value table */
    dptr = (DEVICE *) sim_devices [dev];                /*   from the device table */

    if (dptr == NULL)                                   /* if the end of the table is reached */
        break;                                          /*   then quit */

    dibptr = (DIB *) dptr->ctxt;                        /* get the associated DIB */

    if (dibptr && not (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;
        }
    }

device_count = dev;                                     /* save the count of devices */

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 */
    }

exdep_handler = find_cmd ("EXAMINE")->action;           /* set the EXAMINE/DEPOSIT command handler */
run_handler   = find_cmd ("RUN")->action;               /*   and the RUN/GO command handler */
break_handler = find_cmd ("BREAK")->action;             /*     and the BREAK/NOBREAK command handler */

sim_vm_release        = hp_release;                     /* set up the release string */
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_vm_unit_name      = unit_name;                      /* set up the custom unit name handler */

sim_brk_types = BP_SUPPORTED;                           /* register the supported breakpoint types */
sim_brk_dflt = BP_EXEC;                                 /* the default breakpoint type is "execution" */

vm_execution_rate = S (1);                              /* set the one-second execution rate */
vm_align_trace = hp_align_trace;                        /* set up the trace aligner */

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, char *cptr, char **tptr)
{
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 & (P_SWITCH | S_SWITCH);       /* mask to just the bank address overrides */

if (overrides && not (parse_config & apcBank_Override)  /* if overrides are present but not allowed */
  || overrides & ~P_SWITCH && overrides & ~S_SWITCH)    /*   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 (not (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 & S_SWITCH)                      /* otherwise if the stack-bank override is specified */
        address = TO_PA (SBANK, address);               /*   then base the address on SBANK */

    else if (overrides & P_SWITCH)                      /* 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 */
}


/* Return the name of a standalone unit.

   Standalone units do not have corresponding devices, so the SHOW QUEUE command
   will fail to find such units in the list of devices.  To provide the name, we
   set the "sim_vm_unit_name" hook in the one-time initialization to point at
   this routine, which supplies the name when passed the unit pointer as the
   parameter.

   If the "uptr" parameter points at a recognized standalone unit, the routine
   returns a pointer to a string that names the unit.  Otherwise, the routine
   returns NULL to indicate that the unit is not recognized.


   Implementation notes:

    1. Initially, there are no standalone units.  This routine is provided for
       future use when there is a standalone keyboard poll unit.
*/

static char *unit_name (const UNIT *uptr)
{
return NULL;                                            /* report that we do not recognize the unit */
}


/* 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, 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_handler (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, char *buf)
{
parse_config = apcDefault_PBANK;                        /* set the default bank register to PBANK */

cpu_front_panel (SWCH, Run);                            /* set up run request */

return run_handler (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, 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 = break_handler (arg, buf);                      /* call the standard breakpoint command handler */

sim_PC = save_PC;                                       /* restore the P-register pointer */

return status;                                          /* return the handler status */
}


/* Align the trace facility.

   This routine is called to set the sizes of the largest device name and active
   trace flag name among the devices enabled for tracing.  These are accumulated
   by the SCP extension before execution begins for use in aligning the trace
   statements.  The routine returns the CPU flag that indicates that all trace
   options are enabled.
*/

static uint32 hp_align_trace (uint32 device_max, uint32 flag_max)
{
device_size = device_max;                               /* set the device and trace flag name sizes */
flag_size = flag_max;                                   /*   to the largest of those actively tracing */

return TRACE_EXEC;                                      /* return the "all trace options on" flag */
}



/* 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_UMAX) {                               /*   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 & R_SWITCH) != 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 |= R_SWITCH;                           /*   then set the switch for the next call */
else                                                    /* otherwise it begins in the upper byte */
    sim_switches &= ~R_SWITCH;                          /*   so clear the switch */

return -(t_stat) (total_bytes_used / 2 - 1);            /* return the (biased) negative number of words consumed */
}


/* Print a CPU instruction and operand in symbolic format.

   This routine prints a CPU instruction and its operand, if any, using the
   mnemonics specified in the Machine Instruction Set and Systems Programming
   Language Reference manuals.  Specified bits in the instruction word are used
   as an index into a supplied classification table.  The entry corresponding to
   the instruction gives the mnemonic string, operand type, and reserved bits
   (if any).

   On entry, the "ofile" parameter is the opened output stream, "ops" is the
   table of classifications containing the instruction, "val" contains the
   word(s) of the machine instruction to print, "mask" is the opcode mask to
   apply to get the index bits, "shift" is the right-shift count to align the
   index, and "radix" contains the desired operand radix or zero if the default
   radix is to be used.

   On exit, a status code is returned to the caller.  SCPE_OK status is returned
   if the print consumed a single-word value, or the negative number of extra
   words (beyond the first) consumed by printing the instruction is returned.
   For example, printing a symbol that resulted in two words being consumed
   (from val [0] and val [1]) would return SCPE_OK_2_WORDS (= -1).  If the
   supplied instruction is not in the table, SCPE_ARG is returned.

   The classification table consists of a set of entries that are indexed by
   opcode, followed optionally by a set of entries that are searched linearly.
   Empty mnemonics, i.e., "", are used in the indexed part to indicate that the
   linear part must be searched, and in the linear part to indicate a two-word
   instruction whose second word did not match a defined entry.  A NULL mnemonic
   ends the array (this allows string searches for parsing to fail without
   aborting).

   The supplied instruction is ANDed with the "mask" parameter and then
   right-shifted by the "shift" parameter to produce an index into the "ops"
   table.  If the entry contains a non-empty mnemonic string, it is printed.
   Otherwise, starting at the index implied by the size of the mask, i.e., at
   mask + 1, a linear search of the entries is performed.  For each entry, the
   instruction is masked to remove the operand and optionally the reserved bits,
   and the result is compared to the base opcode.  If it matches, the associated
   mnemonic is printed.  If the table is exhausted without a match, the
   instruction is undefined, and SCPE_ARG is returned.

   For defined instructions, the operand, if any, is printed after the mnemonic.
   Operand values are printed in a radix suitable to the type of the value, as
   follows:

     - Register-relative displacements, S-register decrements, and K fields are
       printed in the CPU's address radix, which is octal.

     - Shift counts, bit positions, and starting bits and counts are printed in
       decimal.

     - CIR values for the PAUS and HALT instructions are printed in octal.

     - Immediate values are printed in the CPU's data radix, which defaults to
       octal but may be set to a different radix with SET CPU OCT|DEC|HEX.

   The radix for operand values other than addresses may be overridden by a
   switch on the command line.  A value printed in a radix other than the
   current data radix is preceded by a radix identifier ("%" for octal, "#" for
   decimal, or "!" for hexadecimal).


   Implementation notes:

    1. All instructions in the base set are single words.  However, some
       extension instructions, including instructions for later-series CPUs,
       e.g., the Series 33, use two words.  For example, the WIOC (write I/O
       channel) instruction is the two-word sequence 020302,000003, and the SIOP
       (start I/O program) sequence is 020302,000000.

    2. The operand type dispatch handlers either set up operand printing by
       assigning the prefix, indirect, and index values, or print the operand(s)
       directly if special formatting is required.

    3. Register flags for the PSHR and SETR instructions are printed using the
       SPL register names.

    4. The "rsvd_mask" fields of secondary entries for one-word instructions
       have the upper 16 bits of the values set to zero.  This masks off the
       second instruction word, which is arbitrary, before comparison.

    5. A secondary entry with a zero-length mnemonic is used to match the first
       word of a two-word instruction when the second word fails to match any of
       the earlier entries.  This causes the instruction to be printed as a pair
       of numeric values.
*/

static const char *const register_name [] = {   /* PSHR/SETR register names corresponding to bits 8-15 */
    "SBANK",                                    /*   bit  8 */
    "DB",                                       /*   bit  9 */
    "DL",                                       /*   bit 10 */
    "Z",                                        /*   bit 11 */
    "STATUS",                                   /*   bit 12 */
    "X",                                        /*   bit 13 */
    "Q",                                        /*   bit 14 */
    "S"                                         /*   bit 15 */
    };

static const char *const cc_flags [] = {        /* TCCS condition code names corresponding to bits 13-15 */
    " N",                                       /*   000 = never */
    " L",                                       /*   001 = less than */
    " E",                                       /*   010 = equal */
    " LE",                                      /*   011 = less than or equal */
    " G",                                       /*   100 = greater than */
    " NE",                                      /*   101 = not equal */
    " GE",                                      /*   110 = greater than or equal */
    " A"                                        /*   111 = always */
    };

static const char *const sign_cntl [] = {       /* CVND sign control names corresponding to bits 12-14 */
    " LS",                                      /*   000 = sign is leading separate */
    " TS",                                      /*   001 = sign is trailing separate */
    " LO",                                      /*   010 = sign is leading overpunch or unsigned */
    " TO",                                      /*   011 = sign is trailing overpunch or unsigned */
    " UN",                                      /*   100 = unsigned */
    " UN",                                      /*   101 = unsigned */
    " UN",                                      /*   110 = unsigned */
    " UN"                                       /*   111 = unsigned */
    };

static t_stat fprint_instruction (FILE *ofile, const OP_TABLE ops, t_value *val,
                                  uint32 mask, uint32 shift, uint32 radix)
{
uint32     op_index, op_radix;
int32      reg_index;
t_bool     reg_first;
t_value    instruction, op_value;
const char *prefix  = NULL;                             /* label to print before the operand */
t_bool     index    = FALSE;                            /* TRUE if the instruction is indexed */
t_bool     indirect = FALSE;                            /* TRUE if the instruction is indirect */
t_stat     status   = SCPE_OK;                          /* result status */

instruction = TO_DWORD (val [1], val [0]);              /* merge the two supplied values */

op_index = ((uint32) instruction & mask) >> shift;      /* extract the opcode index */

if (ops [op_index].mnemonic [0])                        /* if a primary entry is defined */
    fputs (ops [op_index].mnemonic, ofile);             /*   then print the mnemonic */

else {                                                  /* otherwise search through the secondary entries */
    for (op_index = (mask >> shift) + 1;                /*   in the table starting after the primary entries */
         ops [op_index].mnemonic != NULL;               /*     until the NULL entry at the end */
         op_index++)
        if (ops [op_index].opcode ==                    /* if the opcode in this table entry */
          (instruction & ops [op_index].rsvd_mask       /*   matches the instruction with the reserved bits */
          & op_mask [ops [op_index].operand]))          /*     and operand bits masked off */
            if (ops [op_index].mnemonic [0]) {          /*       then if the entry is defined */
                fputs (ops [op_index].mnemonic, ofile); /*         then print it */
                break;                                  /*           and terminate the search */
                }

            else {                                          /* otherwise this two-word instruction is unimplemented */
                fprint_val (ofile, val [0], cpu_dev.dradix, /*   so print the first word */
                            cpu_dev.dwidth, PV_RZRO);

                fputc (',', ofile);                         /*     and a separator */

                fprint_val (ofile, val [1], cpu_dev.dradix, /*       and the second word */
                            cpu_dev.dwidth, PV_RZRO);

                return SCPE_OK_2_WORDS;                     /* return success to indicate printing is complete */
                }

    if (ops [op_index].mnemonic == NULL)                /* if the opcode was not found */
        return SCPE_ARG;                                /*   then return error status to print it in octal */
    }


op_value = instruction & ~op_mask [ops [op_index].operand]; /* mask the instruction to the operand value */

if (ops [op_index].rsvd_mask > D16_MASK) {              /* if this is a two-word instruction */
    op_value = op_value >> D16_WIDTH;                   /*   then the operand is in the upper word */
    status = SCPE_OK_2_WORDS;                           /*     and the routine will consume two words */
    }

op_radix = cpu_dev.aradix;                              /* assume that operand is an address */

switch (ops [op_index].operand) {                       /* dispatch by the operand type */

    /* no operand */

    case opNone:
        break;                                          /* no formatting needed */


    /* condition code flags in bits 13-15 */

    case opCC7:
        fputs (cc_flags [op_value], ofile);             /* print the condition code flags */
        break;


    /* unsigned value pair range 0-15 */

    case opU1515:
        fputc (' ', ofile);                                     /* print a separator */

        fprint_value (ofile, START_BIT (op_value),              /* print the starting bit position */
                      (radix ? radix : 10), DV_WIDTH, PV_LEFT);

        fputc (':', ofile);                                     /* print a separator */

        fprint_value (ofile, BIT_COUNT (op_value),              /* print the bit count */
                     (radix ? radix : 10), DV_WIDTH, PV_LEFT);
        break;


    /* P +/- displacement range 0-31, indirect bit 4 */

    case opPS31I:
        indirect = (instruction & I_FLAG_BIT_4) != 0;           /* save the indirect condition */
        prefix = (op_value & DISPL_31_SIGN ? " P-" : " P+");    /* set the base register and sign label */
        op_value = op_value & DISPL_31_MASK;                    /*   and remove the sign from the displacement value */
        break;


    /* P +/- displacement range 0-255, indirect bit 5, index bit 4 */

    case opPS255IX:
        index = (instruction & X_FLAG) != 0;            /* save the index condition */
        indirect = (instruction & I_FLAG_BIT_5) != 0;   /*   and the indirect condition */

    /* fall through into the P-relative displacement case */

    /* P +/- displacement range 0-255 */

    case opPS255:
        prefix = (op_value & DISPL_255_SIGN ? " P-" : " P+");   /* set the base register and sign label */
        op_value = op_value & DISPL_255_MASK;                   /*   and remove the sign from the displacement value */
        break;


    /* base bit 15 */

    case opB15:
    case opB31:
        if (op_value == 0)                              /* if the base bit is 0 */
            fputs (" PB", ofile);                       /*   then PB is the base register label */
        break;


    /* S decrement range 0-3, base register bit 11 */

    case opSU3B:
        prefix = (instruction & DB_FLAG) ? " " : " PB,";    /* set the base register label */
        op_value = op_value & ~op_mask [opSU3];             /*   and remove the base flag from the S decrement value */
        break;


    /* S decrement range 0-3, N/A/S bits 11-13 */

    case opSU3NAS:
        if (instruction & MVBW_CCF)                     /* if any flags are present */
            fputc (' ', ofile);                         /*   then print a space as a separator */

        if (instruction & MVBW_A_FLAG)                  /* if the alphabetic flag is present */
            fputc ('A', ofile);                         /*   then print an "A" as the indicator */

        if (instruction & MVBW_N_FLAG)                  /* if the numeric flag is present */
            fputc ('N', ofile);                         /*   then print an "N" as the indicator */

        if (instruction & MVBW_S_FLAG)                  /* if the upshift flag is present */
            fputc ('S', ofile);                         /*   then print an "S" as the indicator */

        prefix = ",";                                   /* separate the value from the flags */
        op_value = op_value & ~op_mask [opSU3];         /*   and remove the flags from the S decrement value */
        break;


    /* register selection bits 8-15, execution from left-to-right */

    case opR255L:
        if (op_value != 0) {                                    /* if any registers are to be output */
            fputc (' ', ofile);                                 /*   then print a space as a separator */

            reg_first = TRUE;                                   /* set the first-time-through flag */

            for (reg_index = 0; reg_index <= 7; reg_index++) {  /* loop through the register bits */
                if (op_value & PSR_LR_MASK) {                   /* if the register selection bit is set */
                    if (reg_first)                              /*   then if this is the first time */
                        reg_first = FALSE;                      /*     then clear the flag */
                    else                                        /* otherwise */
                        fputc (',', ofile);                     /*   output a comma separator */

                    fputs (register_name [reg_index], ofile);   /* output the register name */
                    }

                op_value = op_value << 1;                       /* position the next register selection bit */
                }
            }
        break;


    /* register selection bits 8-15, execution from right-to-left */

    case opR255R:
        if (op_value != 0) {                                    /* if any registers are to be output */
            fputc (' ', ofile);                                 /*   then print a space as a separator */

            reg_first = TRUE;                                   /* set the first-time-through flag */

            for (reg_index = 7; reg_index >= 0; reg_index--) {  /* loop through the register bits */
                if (op_value & PSR_RL_MASK) {                   /* if the register selection bit is set */
                    if (reg_first)                              /*   then if this is the first time */
                        reg_first = FALSE;                      /*     then clear the flag */
                    else                                        /* otherwise */
                        fputc (',', ofile);                     /*   output a comma separator */

                    fputs (register_name [reg_index], ofile);   /* output the register name */
                    }

                op_value = op_value >> 1;                       /* position the next register selection bit */
                }
            }
        break;


    /* P+/P-/DB+/Q+/Q-/S- displacements, indirect bit 5, index bit 4 */

    case opPD255IX:
        if ((instruction & DISPL_P_FLAG) == 0) {                    /* if this a P-relative displacement */
            prefix = (op_value & DISPL_255_SIGN ? " P-" : " P+");   /*   then set the base register and sign label */
            op_value = op_value & DISPL_255_MASK;                   /*     and remove the sign from the displacement value */

            index = (instruction & X_FLAG) != 0;            /* save the index condition */
            indirect = (instruction & I_FLAG_BIT_5) != 0;   /*   and the indirect condition */
            break;
            }

    /* otherwise the displacement is not P-relative, so fall through into the data-relative handler */

    /* DB+/Q+/Q-/S- displacements, indirect bit 5, index bit 4 */

    case opD255IX:
        if ((instruction & DISPL_DB_FLAG) == 0) {           /* if this a DB-relative displacement */
            prefix = " DB+";                                /*   then set the base register label */
            op_value = op_value & DISPL_255_MASK;           /*     and remove the base flag from the displacement value */
            }

        else if ((instruction & DISPL_QPOS_FLAG) == 0) {    /* otherwise if this a positive Q-relative displacement */
            prefix = " Q+";                                 /*   then set the base register label */
            op_value = op_value & DISPL_127_MASK;           /*     and remove the base flag from the displacement value */
            }

        else if ((instruction & DISPL_QNEG_FLAG) == 0) {    /* otherwise if this a negative Q-relative displacement */
            prefix = " Q-";                                 /*   then set the base register label */
            op_value = op_value & DISPL_63_MASK;            /*     and remove the base flag from the displacement value */
            }

        else {                                              /* otherwise it must be a negative S-relative displacement */
            prefix = " S-";                                 /*   so set the base register label */
            op_value = op_value & DISPL_63_MASK;            /*     and remove the base flag from the displacement value */
            }

        indirect = (instruction & I_FLAG_BIT_5) != 0;       /* save the indirect condition */

    /* fall through into the index case */

    /* index bit 4 */

    case opX:
        index = (instruction & X_FLAG) != 0;            /* save the index condition */
        break;


    /* unsigned value range 0-63, index bit 4 */

    case opU63X:
        index = (instruction & X_FLAG) != 0;            /* save the index condition */
        op_value = op_value & DISPL_63_MASK;            /*   and mask to the operand value */

    /* fall through into the unsigned value case */

    /* unsigned value range 0-63 */

    case opU63:
        op_radix = (radix ? radix : 10);                /* set the print radix */
        prefix = " ";                                   /*   and add a separator */
        break;


    /* sign control bits 9-10, S decrement bit 11 */

    case opSCS3:
        if (instruction & NABS_FLAG) {                  /* if the negative absolute flag is present */
            fputs (" NABS", ofile);                     /*   then print "NABS" as the indicator */
            prefix = ",";                               /* we will need to separate the flag and value */
            }

        else if (instruction & ABS_FLAG) {              /* otherwise if the absolute flag is present */
            fputs (" ABS", ofile);                      /*   then print "ABS" as the indicator */
            prefix = ",";                               /* we will need to separate the flag and value */
            }

        else                                            /* otherwise neither flag is present */
            prefix = " ";                               /*   so just use a space to separate the value */

        op_value = (op_value & ~op_mask [opS11]) >> EIS_SDEC_SHIFT; /* remove the flags from the S decrement value */
        break;


    /* sign control bits 12-14, S decrement bit 15 */

    case opSCS4:
        fputs (sign_cntl [op_value >> CVND_SC_SHIFT], ofile);   /* print the sign-control mnemonic */

        prefix = ",";                                   /* separate the flag and value */
        op_value = op_value & CIS_SDEC_MASK;            /*   and remove the flags from the S decrement value */
        break;


    /* S decrement bit 11 */
    /* S decrement range 0-2 bits 10-11 */

    case opS11:
    case opSU2:
        op_value = op_value >> EIS_SDEC_SHIFT;          /* align the S decrement value */

    /* fall through into the unsigned operand case */

    /* unsigned value range 0-1 */
    /* unsigned value range 0-255 */

    case opU1:
    case opU255:
        op_radix = (radix ? radix : cpu_dev.dradix);    /* set the print radix */
        prefix = " ";                                   /*   and add a separator */
        break;


    /* CIR display bits 12-15 */

    case opC15:
        op_radix = (radix ? radix : 8);                 /* set the print radix */
        prefix = " ";                                   /*   and add a separator */
        break;


    /* P unsigned displacement range 0-255 */
    /* S decrement range 0-1 */
    /* S decrement range 0-3 */
    /* S decrement range 0-7 */
    /* S decrement range 0-15 */

    case opPU255:
    case opS15:
    case opS31:
    case opSU3:
    case opSU7:
    case opSU15:
        prefix = " ";                                   /* add a separator */
        break;

    }                                                   /* end of the operand type dispatch */


if (prefix) {                                           /* if an operand is present */
    fputs (prefix, ofile);                              /*   then label it */
    fprint_value (ofile, op_value, op_radix,            /*     and then print the value */
                  DV_WIDTH, PV_LEFT);
    }

if (indirect)                                           /* add an indirect indicator */
    fputs (",I", ofile);                                /*   if specified by the instruction */

if (index)                                              /* add an index indicator */
    fputs (",X", ofile);                                /*   if specified by the instruction */

return status;                                          /* return the applicable status */
}


/* Parse a CPU instruction */

static t_stat parse_cpu (char *cptr, t_addr addr, t_value *val, uint32 radix)
{
return SCPE_ARG;                                        /* mnemonic support is not present in this release */
}
