/* hp2100_sys.c: HP 2100 system common interface

   Copyright (c) 1993-2016, Robert M. Supnik
   Copyright (c) 2017-2018, J. David Bryan

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
   AUTHORS 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 names of the authors 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 authors.

   04-Oct-18    JDB     Reordered the device list alphabetically
   02-Aug-18    JDB     Added MEM device and keyboard poll device
   19-Jun-18    JDB     Fixed display of $MPV instruction
   07-Mar-18    JDB     Added the GET_SWITCHES macro from scp.c
   22-Feb-18    JDB     Added the <dev> option to the LOAD command
   07-Sep-17    JDB     Replaced "uint16" cast with "MEMORY_WORD" for loader ROM
   07-Aug-17    JDB     Added "hp_attach" to attach a file for appending
   01-Aug-17    JDB     Added "ispunct" test for implied mnemonic parse
   20-Jul-17    JDB     Removed STOP_OFFLINE, STOP_PWROFF messages
   14-Jul-17    JDB     Interrupt deferral is now calculated in instr postlude
   11-Jul-17    JDB     Moved "hp_enbdis_pair" here from hp2100_cpu.c
                        Renamed "ibl_copy" to "cpu_ibl"
   16-May-17    JDB     Rewrote "fprint_sym" and "parse_sym" for better coverage
   03-Apr-17    JDB     Rewrote "parse_cpu" to improve efficiency and coverage
   25-Mar-17    JDB     Increased "sim_emax" from 3 to 10 for VIS instructions
   20-Nar-17    JDB     Rewrote "fprint_cpu" to improve efficiency and coverage
   03-Mar-17    JDB     Added binary input parsing
   01-Mar-17    JDB     Added physical vs. logical address parsing and printing
   27-Feb-17    JDB     Revised the LOAD command and added the DUMP command
   25-Jan-17    JDB     Replaced ReadIO with mem_fast_read, added hp_trace routine
   22-Jan-17    JDB     Separated instruction mnemonic printing
   15-Jan-17    JDB     Corrected HLT decoding to add the 1060xx and 1070xx ranges
                        Corrected SFB decoding
   14-Jan-17    JDB     Removed use of Fprintf functions for version 4.x and on
   30-Dec-16    JDB     Corrected parsing of memory reference instructions
   13-May-16    JDB     Modified for revised SCP API function parameter types
   19-Jun-15    JDB     Conditionally use Fprintf function for version 4.x and on
   18-Jun-15    JDB     Added cast to int for isspace parameter
   24-Dec-14    JDB     Added casts to t_addr and t_value for 64-bit compatibility
                        Made local routines static
   05-Feb-13    JDB     Added hp_fprint_stopped to handle HLT instruction message
   18-Mar-13    JDB     Moved CPU state variable declarations to hp2100_cpu.h
   09-May-12    JDB     Quieted warnings for assignments in conditional expressions
   10-Feb-12    JDB     Deprecated DEVNO in favor of SC
                        Added hp_setsc, hp_showsc functions to support SC modifier
   15-Dec-11    JDB     Added DA and dummy DC devices
   29-Oct-10    JDB     DMA channels renamed from 0,1 to 1,2 to match documentation
   26-Oct-10    JDB     Changed DIB access for revised signal model
   03-Sep-08    JDB     Fixed IAK instruction dual-use mnemonic display
   07-Aug-08    JDB     Moved hp_setdev, hp_showdev from hp2100_cpu.c
                        Changed sim_load to use WritePW instead of direct M[] access
   18-Jun-08    JDB     Added PIF device
   17-Jun-08    JDB     Moved fmt_char() function from hp2100_baci.c
   26-May-08    JDB     Added MPX device
   24-Apr-08    JDB     Changed fprint_sym to handle step with irq pending
   07-Dec-07    JDB     Added BACI device
   27-Nov-07    JDB     Added RTE OS/VMA/EMA mnemonics
   21-Dec-06    JDB     Added "fwanxm" external for sim_load check
   19-Nov-04    JDB     Added STOP_OFFLINE, STOP_PWROFF messages
   25-Sep-04    JDB     Added memory protect device
                        Fixed display of CCA/CCB/CCE instructions
   01-Jun-04    RMS     Added latent 13037 support
   19-Apr-04    RMS     Recognize SFS x,C and SFC x,C
   22-Mar-02    RMS     Revised for dynamically allocated memory
   14-Feb-02    RMS     Added DMS instructions
   04-Feb-02    RMS     Fixed bugs in alter/skip display and parsing
   01-Feb-02    RMS     Added terminal multiplexor support
   16-Jan-02    RMS     Added additional device support
   17-Sep-01    RMS     Removed multiconsole support
   27-May-01    RMS     Added multiconsole support
   14-Mar-01    RMS     Revised load/dump interface (again)
   30-Oct-00    RMS     Added examine to file support
   15-Oct-00    RMS     Added dynamic device number support
   27-Oct-98    RMS     V2.4 load interface

   References:
     - HP 1000 M/E/F-Series Computers Technical Reference Handbook
         (5955-0282, March 1990)
     - RTE-IV Assembler Reference Manual
         (92067-90003, October 1980)


   This module provides the interface between the Simulation Control Program
   (SCP) and the HP 2100 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 <stdlib.h>

#include "hp2100_defs.h"
#include "hp2100_cpu.h"
#include "hp2100_cpu_dmm.h"



/* Global release string */

const char *hp_vm_release = "29";               /* HP 2100 simulator release number */
const char *hp_vm_release_message =
   "This is the last version of this simulator which is API compatible\n"
   "with the 4.x version of the simh framework.  A supported version of\n"
   "this simulator can be found at: http://simh.trailing-edge.com/hp\n";


/* Command-line switch parsing from scp.c */

#define GET_SWITCHES(cp) \
    if ((cp = get_sim_sw (cp)) == NULL) return SCPE_INVSW


/* External I/O data structures */

/*
extern DEVICE cpu_dev;                          ** CPU device structure (declared in hp2100_cpu.h) */
extern DEVICE dma1_dev, dma2_dev;               /* Direct Memory Access/Dual-Channel Port Controller */
extern DEVICE mp_dev;                           /* Memory Protect */
extern DEVICE meu_dev;                          /* Memory Expansion Module */

extern DEVICE baci_dev;                         /* Buffered Asynchronous Communication Interface */
extern DEVICE da_dev;                           /* 7906H/20H/25H ICD Disc Drive */
extern DEVICE dc_dev;                           /* Dummy HP-IB Interface */
extern DEVICE dpd_dev, dpc_dev;                 /* 2870/7900 Disc Drive */
extern DEVICE dqd_dev, dqc_dev;                 /* 2883 Disc Drive */
extern DEVICE drd_dev, drc_dev;                 /* 277x Disc/Drum Drive */
extern DEVICE ds_dev;                           /* 7905/06/20/25 Disc Drive */
extern DEVICE ipli_dev, iplo_dev;               /* Processor Interconnect */
extern DEVICE lps_dev;                          /* 2767 Line Printer */
extern DEVICE lpt_dev;                          /* 2607/13/17/18 Line Printer */
extern DEVICE mc1_dev, mc2_dev;                 /* Microcircuit Interface */
extern DEVICE mpx_dev;                          /* Eight-Channel Asynchronous Multiplexer */
extern DEVICE msd_dev, msc_dev;                 /* 7970B/E Magnetic Tape Drive */
extern DEVICE mtd_dev, mtc_dev;                 /* 3030 Magnetic Tape Drive */
extern DEVICE muxl_dev, muxu_dev, muxc_dev;     /* Sixteen-Channel Asynchronous Multiplexer */
extern DEVICE pif_dev;                          /* Privileged Interrupt Fence */
extern DEVICE ptp_dev;                          /* Paper Tape Punch */
extern DEVICE ptr_dev;                          /* Paper Tape Reader */
extern DEVICE tbg_dev;                          /* Time Base Generator */
extern DEVICE tty_dev;                          /* Teleprinter */


/* Program constants */

#define VAL_EMPTY           (1u << PA_WIDTH)    /* flag indicating that the value array must be loaded */


/* 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 B_SWITCH            SWMASK ('B')
#define C_SWITCH            SWMASK ('C')
#define D_SWITCH            SWMASK ('D')
#define H_SWITCH            SWMASK ('H')
#define M_SWITCH            SWMASK ('M')
#define O_SWITCH            SWMASK ('O')

#define MODE_SWITCHES       (C_SWITCH | M_SWITCH)
#define FORMAT_SWITCHES     (A_SWITCH | B_SWITCH | D_SWITCH | H_SWITCH | O_SWITCH)
#define SYMBOLIC_SWITCHES   (C_SWITCH | M_SWITCH | A_SWITCH)

#define ALL_SWITCHES        (MODE_SWITCHES | FORMAT_SWITCHES)


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

typedef enum {
    opNone,                                     /* no operand */
    opMPOI,                                     /* MRG page bit 10, offset 0000-1777 octal, indirect bit 15 */
    opSC,                                       /* IOG select code range 00-77 octal */
    opSCHC,                                     /* IOG select code range 00-77 octal, hold/clear bit 9 */
    opSCOHC,                                    /* IOG optional select code range 00-77 octal, hold/clear bit 9 */
    opHC,                                       /* IOG hold/clear bit 9 */
    opShift,                                    /* EAU shift/rotate count range 1-16 */

    opIOPON,                                    /* IOP index negative offset range 1-20 octal */
    opIOPOP,                                    /* IOP index positive offset range 0-17 octal */
    opIOPO,                                     /* IOP index offset range 0-37 octal (+20 bias) */

    opZA4,                                      /* UIG zero word, four (indirect) memory addresses */
    opZA5,                                      /* UIG zero word, five (indirect) memory addresses */
    opZA6,                                      /* UIG zero word, six (indirect) memory addresses */
    opZA8,                                      /* UIG zero word, eight (indirect) memory addresses */

    opV1,                                       /* UIG one data value */
    opV2,                                       /* UIG two data values */

    opA1V1,                                     /* UIG one (indirect) memory address, one data value */
    opV2A1,                                     /* UIG two data values, one (indirect) memory address */
    opV1A5,                                     /* UIG one data value, five (indirect) memory addresses */

    opMA1ZI,                                    /* One memory address range 00000-77777 octal, zero word, indirect bit 15 */
    opMA1I,                                     /* One memory address range 00000-77777 octal, indirect bit 15 */
    opMA2I,                                     /* Two memory addresses range 00000-77777 octal, indirect bit 15 */
    opMA3I,                                     /* Three memory addresses range 00000-77777 octal, indirect bit 15 */
    opMA4I,                                     /* Four memory addresses range 00000-77777 octal, indirect bit 15 */
    opMA5I,                                     /* Five memory addresses range 00000-77777 octal, indirect bit 15 */
    opMA6I,                                     /* Six memory addresses range 00000-77777 octal, indirect bit 15 */
    opMA7I                                      /* Seven memory addresses range 00000-77777 octal, indirect bit 15 */
    } OP_TYPE;

typedef struct {
    t_value  mask;                              /* operand mask */
    int32    count;                             /* operand count */
    uint32   address_set;                       /* address operand bitset */
    } OP_PROP;

static const OP_PROP op_props [] = {            /* operand properties, indexed by OP_TYPE */
    { ~0000000u,  0,  000000u },                /*   opNone  */
    { ~0101777u,  0,  000001u },                /*   opMPOI  */
    { ~0000077u,  0,  000000u },                /*   opSC    */
    { ~0000077u,  0,  000000u },                /*   opSCHC  */
    { ~0000077u,  0,  000000u },                /*   opSCOHC */
    { ~0001000u,  0,  000000u },                /*   opHC    */
    { ~0000017u,  0,  000000u },                /*   opShift */

    { ~0000017u,  0,  000000u },                /*   opIOPON */
    { ~0000017u,  0,  000000u },                /*   opIOPOP */
    { ~0000037u,  0,  000000u },                /*   opIOPO  */

    { ~0000000u,  5,  000036u },                /*   opZA4   */
    { ~0000000u,  6,  000076u },                /*   opZA5   */
    { ~0000000u,  7,  000176u },                /*   opZA6   */
    { ~0000000u,  9,  000776u },                /*   opZA8   */

    { ~0000000u,  1,  000000u },                /*   opV1    */
    { ~0000000u,  2,  000000u },                /*   opV2    */

    { ~0000000u,  2,  000001u },                /*   opA1V1  */
    { ~0000000u,  3,  000004u },                /*   opV2A1  */
    { ~0000000u,  6,  000076u },                /*   opV1A5  */

    { ~0000000u,  2,  000001u },                /*   opMA1ZI */
    { ~0000000u,  1,  000001u },                /*   opMA1I  */
    { ~0000000u,  2,  000003u },                /*   opMA2I  */
    { ~0000000u,  3,  000007u },                /*   opMA3I  */
    { ~0000000u,  4,  000017u },                /*   opMA4I  */
    { ~0000000u,  5,  000037u },                /*   opMA5I  */
    { ~0000000u,  6,  000077u },                /*   opMA6I  */
    { ~0000000u,  7,  000177u }                 /*   opMA7I  */
    };


/* Instruction classifications.

   Machine instructions on the HP 21xx/1000 are identified by a varying number
   of bits.  In general, the five most-significant bits identify the general
   group of instruction, and additional bits form a sub-opcode within a group to
   identify an instruction uniquely.  However, many instructions are irregular,
   and those from two groups -- the Shift-Rotate Group and the Alter-Skip Group
   -- are formed from multiple "micro-operations" that may be combined to
   perform up to eight operations per instruction.  Instructions from the
   Extended Arithmetic Group have a number of reserved bits that are defined to
   be zero.  Correct hardware decoding may or may not depend on these bits being
   zero and varies from CPU model to model.

   Each instruction is classified by a mnemonic, a base operation code (without
   the operand), an operand type, and a mask for the significant bits that
   identify the opcode.  Classifications are grouped by class of instruction
   into arrays that are indexed by sub-opcodes, if applicable.  Two-word
   instructions will have base operation codes and signifiant bits masks that
   extend to 32 bits.

   An opcode 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 significant opcode bits
   masks are not defined or used.

   A number of instructions use bit 11 to indicate whether the instruction
   applies to the A-register or B-register.  If a class uses a primary part, and
   that class also uses the A/B-register indicator, then the primary table is
   twice the expected length (based on the number of significant sub-opcode
   bits).  The first half of the table applies to the sub-opcodes where bit 11
   is zero (meaning, "use the A-register"), and the second half applies where
   bit 11 is one (meaning, "use the B-register").

   If some opcodes in a class are defined by a limited set of significant bits,
   if the sub-opcode decoding is not regular, or if the instruction requires two
   words to decode, then a second (secondary) part of the table contains
   classification elements that specify which bits of the opcode are
   significant.  This part is searched linearly.

   The secondary table may contain dependent or independent entries.  In the
   former case, an instruction will match only one of the entries, and the
   search terminates when the match occurs.  An example is the EAG table.  In
   the latter case, an instruction may independently match multiple entries, and
   the search must continue through the end of the table.  Examples are the SRG
   and ASG tables.

   A primary entry that requires additional bits to decode may indicate that the
   secondary section is to be searched by setting a null string ("") as the
   instruction mnemonic.  The end of the opcode table is indicated by a NULL
   mnemonic pointer.

   As an example, the Memory Reference Group of instructions have bits 14-12
   not equal to zero.  These seven values are combined with bit 11 to encode one
   of 14 instructions.  The opcode table consists of 16 primary entries, indexed
   by bits 14-11 (the first two entries are not valid MRG instructions).

   As a contrasting example, the Shift-Rotate Group instructions contain four
   fields.  The first field is defined by bits 8-6 that select one of eight
   shift or rotate operations, plus bit 11 that selects whether the operation
   applies to the A-register or the B-register.  This field is decoded by an
   opcode table consisting of 16 primary entries in two halves.  The first half
   is indexed by bits 8-6 when bit 11 is zero; the second half is indexed by
   bits 8-6 when bit 11 is one.

   The second and third fields use bits 5 and 3 to enable "clear E-register" and
   "skip if the A/B-register is zero" operations, respectively.  These are
   decoded by a secondary table of four entries that is searched linearly.  The
   significant opcode bits masks are used to mask the instruction word to those
   bits that identify the particular instruction.

   The fourth field encodes a second shift/rotate operation using bits 2-0.  The
   opcode table is structured identically to the one for the first field.

   A third example is the I/O Group opcode table.  IOG instructions are
   primarily encoded by bits 8-6.  A few of these sub-opcodes additionally use
   bit 11 to select the A/B-register or bit 9 for additional instruction
   differentiation.  The opcode table consists of a two-part primary section,
   indexed by bits 8-6 and bit 11, and a secondary section that is searched
   linearly to decode the additional bits.

   The content of each opcode table is described by an opcode descriptor.  This
   structure contains the mask and alignment shift to apply to the instruction
   to obtain the primary index, the mask to obtain the A/B-register select bit
   from the instruction, and the CPU feature that must be enabled to enable the
   associated instructions.  If the opcode table contains only secondary
   entries, the mask field contains the OP_LINEAR value, and the shift field
   contains the OP_SINGLE or OP_MULTIPLE value, depending on whether the opcode
   entries are dependent or independent, respectively.  The register selector
   field contains either AB_MASK or AB_UNUSED values, depending on whether or
   not the instruction uses bit 11 to select between the A and B registers.  The
   required feature field contains a mask that is applied to the user flags
   field of the CPU device to select a CPU type and firmware option set.  For
   example, the descriptor for the I/O Processor opcodes for the 2100 CPU
   contains "CPU_IOP | CPU_2100" as the required feature field value.


   Implementation notes:

    1. An opcode table containing both primary and secondary entries uses the
       "mask" and "shift" fields to obtain the primary index.  Therefore, the
       alternate use of the "shift" field to designate dependent vs. independent
       secondaries is not available, and secondaries are assumed to be
       dependent.

    2. Two-word instructions have the first word in the lower half of the 32-bit
       "opcode" field, and the second word in the upper half.  The "op_bits"
       field is similarly arranged.

    3. An opcode descriptor and its associated opcode table form an integral
       object.  Ideally, they would be a single structure.  However, while C
       allows a structure to contain a "flexible array member" as the last
       field, which could represent the opcode table array, the array cannot be
       initialized statically.  Therefore, separate structures and arrays are
       used.

    4. Opcode entries with lower-case mnemonics can be printed but will never
       match when parsing (because the command line is upshifted before calling
       the "parse_sym" routine).  Lower-case mnemonics are used for instructions
       with undocumented names (e.g., self-test instructions) and for
       non-canonical opcodes that execute as other, canonical instructions
       (e.g., 105700, which executes as XMM).
*/

#define OP_LINEAR           0u                  /* (mask) table contains linear search entries only */
#define OP_SINGLE           0u                  /* (shift) match only a single linear entry */
#define OP_MULTIPLE         1u                  /* (shift) match multiple linear entries */

#define AB_UNUSED           0000000u            /* mask to use when the A/B register bit is not used */

typedef struct {                                /* opcode descriptor */
    uint32          mask;                       /*   mask to get opcode selection */
    uint32          shift;                      /*   shift to get the opcode selection */
    uint32          ab_selector;                /*   mask to get the A/B-register selector */
    CPU_OPTION_SET  feature;                    /*   feature set to which opcodes apply */
    } OP_DESC;

typedef struct {                                /* opcode table entry */
    const char  *mnemonic;                      /*   symbolic name of the opcode */
    t_value     opcode;                         /*   base value of the opcode */
    OP_TYPE     type;                           /*   type of operand(s) to the opcode */
    t_value     op_bits;                        /*   significant opcode bits mask */
    } OP_ENTRY;

typedef OP_ENTRY OP_TABLE [];                   /* a table of opcode entries */


/* Memory Reference Group.

   The memory reference instructions are indicated by bits 14-12 not equal to
   000.  They are are fully decoded by bits 14-11.  The table consists of 16
   primary entries.

      15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | I |    mem op     | C |            memory address             |  MRG
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | I |  mem op   | B | C |            memory address             |  MRG
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     I = direct/indirect (0/1)
     B = A/B register (0/1)
     C = base/current page (0/1)

   Memory operation:

     0010 = AND
     0011 = JDB
     0100 = XOR
     0101 = JMP
     0110 = IOR
     0111 = ISZ
     100x = ADA/ADB
     101x = CPA/CPB
     110x = LDA/LDB
     111x = STA/STB


   Implementation notes:

    1. The first two table entries are placeholders for bits 14-11 = 000x.
       These represent Shift-Rotate Group instructions, which will not be
       decoded with this table.

    2. The A/B-register selector descriptor is set to "unused" because the
       primary index includes bit 11.
*/

static const OP_DESC mrg_desc = {               /* Memory Reference Group descriptor */
    0074000u,                                   /*   opcode mask */
    11u,                                        /*   opcode shift */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_BASE | CPU_ALL                          /*   applicable feature flags */
    };

static const OP_TABLE mrg_ops = {               /* MRG opcodes, indexed by IR bits 14-11 */
    { "",    0000000u, opNone },                /* SRG */
    { "",    0004000u, opNone },                /* SRG */
    { "AND", 0010000u, opMPOI },
    { "JSB", 0014000u, opMPOI },
    { "XOR", 0020000u, opMPOI },
    { "JMP", 0024000u, opMPOI },
    { "IOR", 0030000u, opMPOI },
    { "ISZ", 0034000u, opMPOI },
    { "ADA", 0040000u, opMPOI },
    { "ADB", 0044000u, opMPOI },
    { "CPA", 0050000u, opMPOI },
    { "CPB", 0054000u, opMPOI },
    { "LDA", 0060000u, opMPOI },
    { "LDB", 0064000u, opMPOI },
    { "STA", 0070000u, opMPOI },
    { "STB", 0074000u, opMPOI },

    { NULL }
    };


/* Shift-Rotate Group.

   The shift-rotate instructions are indicated by bits 15, 14-12, and 10 all
   equal to 0.  They are decoded in three parts.  First, if bit 9 is 1, then
   bits 11 + 8-6 fully decode one of 16 shifts or rotations.  Second, bits 5 and
   11 + 3 independently decode the CLE and SLA/SLB operations.  Third, if bit 4
   is 1, then bits 11 + 2-0 fully decode one of 16 shifts or rotations.

   Three tables are used: two to decode the shifts or rotates, and a third to
   decode the CLE and SLA/SLB operations, plus the NOP that results when all IR
   bits are zero.  The first and third tables consist of 16 primary entries.
   The second consists of four independent secondary entries.

      15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0 | 0   0   0 | B | 0 | E |   op 1    | C | E | S |   op 2    |  SRG
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     B = A/B register (0/1)
     E = disable/enable op
     C = CLE
     S = SLA/B

   Shift/Rotate operation:

     000 = ALS/BLS
     001 = ARS/BRS
     010 = RAL/RBL
     011 = RAR/RBR
     100 = ALR/BLR
     101 = ERA/ERB
     110 = ELA/ELB
     111 = ALF/BLF


   Implementation notes:

    1. The srg1_ops and srg2_ops tables contain only primary entries.  Normally,
       primary entries don't define the op_bits fields.  In these cases,
       however, they are required to provide A/B conflict detection among the
       two SRG operations and the SLA/SLB micro-op when parsing.

    2. An SRG instruction executes as NOP if both shift-rotate operation fields
       are disabled and the CLE and SLA/B fields are zero.  In these cases, all
       remaining bits are "don't care."  The "NOP" entry in the micro-ops table
       catches these cases.
*/

static const OP_DESC srg1_desc = {              /* Shift-Rotate Group first descriptor */
    0000700u,                                   /*   opcode mask */
    6u,                                         /*   opcode shift */
    AB_MASK,                                    /*   A/B-register selector */
    CPU_BASE | CPU_ALL                          /*   applicable feature flags */
    };

static const OP_TABLE srg1_ops = {              /* SRG opcodes, indexed by IR bits 11 + 8-6 */
    { "ALS", 0001000u, opNone, 0005700u },
    { "ARS", 0001100u, opNone, 0005700u },
    { "RAL", 0001200u, opNone, 0005700u },
    { "RAR", 0001300u, opNone, 0005700u },
    { "ALR", 0001400u, opNone, 0005700u },
    { "ERA", 0001500u, opNone, 0005700u },
    { "ELA", 0001600u, opNone, 0005700u },
    { "ALF", 0001700u, opNone, 0005700u },

    { "BLS", 0005000u, opNone, 0005700u },
    { "BRS", 0005100u, opNone, 0005700u },
    { "RBL", 0005200u, opNone, 0005700u },
    { "RBR", 0005300u, opNone, 0005700u },
    { "BLR", 0005400u, opNone, 0005700u },
    { "ERB", 0005500u, opNone, 0005700u },
    { "ELB", 0005600u, opNone, 0005700u },
    { "BLF", 0005700u, opNone, 0005700u },

    { NULL }
    };

static const OP_DESC srg_udesc = {              /* Shift-Rotate Group micro-ops descriptor */
    OP_LINEAR,                                  /*   linear search only */
    OP_MULTIPLE,                                /*   multiple matches allowed */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_BASE | CPU_ALL                          /*   applicable feature flags */
    };

static const OP_TABLE srg_uops = {              /* SRG micro-opcodes, searched linearly */
    { "CLE", 0000040u, opNone, 0000040u },
    { "SLA", 0000010u, opNone, 0004010u },
    { "SLB", 0004010u, opNone, 0004010u },
    { "NOP", 0000000u, opNone, 0173070u },

    { NULL }
    };

static const OP_DESC srg2_desc = {              /* Shift-Rotate Group second descriptor */
    0000007u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_MASK,                                    /*   A/B-register selector */
    CPU_BASE | CPU_ALL                          /*   applicable feature flags */
    };

static const OP_TABLE srg2_ops = {              /* SRG opcodes, indexed by IR bits 11 + 2-0 */
    { "ALS", 0000020u, opNone, 0004027u },
    { "ARS", 0000021u, opNone, 0004027u },
    { "RAL", 0000022u, opNone, 0004027u },
    { "RAR", 0000023u, opNone, 0004027u },
    { "ALR", 0000024u, opNone, 0004027u },
    { "ERA", 0000025u, opNone, 0004027u },
    { "ELA", 0000026u, opNone, 0004027u },
    { "ALF", 0000027u, opNone, 0004027u },

    { "BLS", 0004020u, opNone, 0004027u },
    { "BRS", 0004021u, opNone, 0004027u },
    { "RBL", 0004022u, opNone, 0004027u },
    { "RBR", 0004023u, opNone, 0004027u },
    { "BLR", 0004024u, opNone, 0004027u },
    { "ERB", 0004025u, opNone, 0004027u },
    { "ELB", 0004026u, opNone, 0004027u },
    { "BLF", 0004027u, opNone, 0004027u },

    { NULL }
    };


/* Alter-Skip Group.

   The alter-skip instructions are indicated by bits 15 and 14-12 equal to 0
   and bit 10 equal to 1.  All of the operations are independent, and the table
   consists of independent secondary entries for each operation, plus a final
   entry for the instruction that enables none of the operations.

      15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 0 | 0   0   0 | B | 1 | a op  | e op  | E | S | L | I | Z | V |  ASG
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     B = A/B register (0/1)
     E = SEZ
     S = SSA/B
     L = SLA/B
     I = INA/B
     Z = SZA/B
     V = RSS

   Accumulator operation:

     00 = NOP
     01 = CLA/CLB
     10 = CMA/CMB
     11 = CCA/CCB

   Extend operation:

     00 = NOP
     01 = CLE
     10 = CME
     11 = CCE


   Implementation notes:

    1. The CLE/CME/CCE, SEZ, and RSS fields do not depend on the A/B register
       select bit.  This bit is "don't care" if only these fields appear in the
       instruction.  The entries in the table catch these cases.
*/

static const OP_DESC asg_udesc = {              /* Alter-Skip Group micro-ops descriptor */
    OP_LINEAR,                                  /*   linear search only */
    OP_MULTIPLE,                                /*   multiple matches allowed */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_BASE | CPU_ALL                          /*   applicable feature flags */
    };

static const OP_TABLE asg_uops = {              /* ASG micro-opcodes, searched linearly */
    { "CLA", 0002400u, opNone, 0007400u },
    { "CMA", 0003000u, opNone, 0007400u },
    { "CCA", 0003400u, opNone, 0007400u },
    { "CLB", 0006400u, opNone, 0007400u },
    { "CMB", 0007000u, opNone, 0007400u },
    { "CCB", 0007400u, opNone, 0007400u },

    { "SEZ", 0002040u, opNone, 0002040u },

    { "CLE", 0002100u, opNone, 0002300u },
    { "CME", 0002200u, opNone, 0002300u },
    { "CCE", 0002300u, opNone, 0002300u },

    { "SSA", 0002020u, opNone, 0006020u },
    { "SLA", 0002010u, opNone, 0006010u },
    { "INA", 0002004u, opNone, 0006004u },
    { "SZA", 0002002u, opNone, 0006002u },
    { "SSB", 0006020u, opNone, 0006020u },
    { "SLB", 0006010u, opNone, 0006010u },
    { "INB", 0006004u, opNone, 0006004u },
    { "SZB", 0006002u, opNone, 0006002u },

    { "RSS", 0002001u, opNone, 0002001u },
    { "NOP", 0002000u, opNone, 0173777u },

    { NULL }
    };


/* I/O Group.

   The I/O instructions are indicated by bits 14-12 equal to 0 and bits 15 and
   10 equal to 1.  They are fully decoded by bits 11 + 9-6.  However, the HP
   assembler assigns special mnemonics to the flag instructions that reference
   select code 1 (the overflow register).  Therefore, secondary entries are used
   to provide the flag mnemonics, based on the select code employed.

   Because only the flag instructions use bit 9 to differentiate between
   instructions, and because the flag instructions are all relegated to
   secondary entries, the primary entries need not be indexed with bit 9.
   Therefore, the table consists of 16 primary entries and 8 secondary entries
   for the flag instructions.

      15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 1 | 0   0   0 | B | 1 | C |  i/o op   |      select code      |  IOG
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     B = A/B register (0/1)
     C = hold/clear flag (0/1)

   I/O operation:

     000 = HLT
     001 = STF/CLF
     010 = SFC
     011 = SFS
     100 = MIA/MIB
     101 = LIA/LIB
     110 = OTA/OTB
     111 = STC/CLC


   Implementation notes:

    1. Bit 11 is "don't care" for the HLT, STF, CLF, SFC, and SFS instructions.

    2. Bit 9, the "hold/clear" bit, is defined as 0 for the SFC and SFS
       instructions; however, setting bit 9 to 1 will clear the flag after
       testing (RTE depends on this behavior in $CIC, where it is used to test
       the state of the interrupt system and turn it off in the same
       instruction).  Therefore, these entries use the "opSCHC" operand type
       instead of the expected "opSC" type.

    3. The select code may be omitted when entering the HLT instruction (it
       defaults to 0), so a special operand code is needed when parsing this
       case.
*/

static const OP_DESC iog_desc = {               /* Input/Output Group descriptor */
    0000700u,                                   /*   opcode mask */
    6u,                                         /*   opcode shift */
    AB_MASK,                                    /*   A/B-register selector */
    CPU_BASE | CPU_ALL                          /*   applicable feature flags */
    };

static const OP_TABLE iog_ops = {               /* IOG opcodes, indexed by IR bits 11 + 8-6 */
    { "HLT", 0102000u, opSCOHC },
    { "",    0102100u, opNone  },               /* STF/CLF and STO/CLO */
    { "",    0102200u, opNone  },               /* SFC and SOC */
    { "",    0102300u, opNone  },               /* SFS and SOS */
    { "MIA", 0102400u, opSCHC  },
    { "LIA", 0102500u, opSCHC  },
    { "OTA", 0102600u, opSCHC  },
    { "STC", 0102700u, opSCHC  },

    { "HLT", 0106000u, opSCOHC },
    { "",    0106100u, opNone  },               /* STF/CLF and STO/CLO */
    { "",    0106200u, opNone  },               /* SFC and SOC */
    { "",    0106300u, opNone  },               /* SFS and SOS */
    { "MIB", 0106400u, opSCHC  },
    { "LIB", 0106500u, opSCHC  },
    { "OTB", 0106600u, opSCHC  },
    { "CLC", 0106700u, opSCHC  },

    { "STO", 0102101u, opNone, 0173777u },      /* STF 01 */
    { "STF", 0102100u, opSC,   0173700u },      /* STF nn */
    { "CLO", 0103101u, opNone, 0173777u },      /* CLF 01 */
    { "CLF", 0103100u, opSC,   0173700u },      /* CLF nn */
    { "SOC", 0102201u, opHC,   0172777u },      /* SFC 01 */
    { "SFC", 0102200u, opSCHC, 0172700u },      /* SFC nn */
    { "SOS", 0102301u, opHC,   0172777u },      /* SFS 01 */
    { "SFS", 0102300u, opSCHC, 0172700u },      /* SFS nn */

    { NULL }
    };


/* Extended Arithmetic Group.

   The EAG instructions are part of the MAC instruction space, which is
   indicated by bit 15 equal to 1 and bits 14-12 and 10 equal to 0.  They are
   enabled by the Extended Arithmetic Unit feature that is optional for the 2116
   and standard for the 2100 and 1000-M/E/F.  There are ten canonical EAG
   instructions.  Four arithmetic instructions are decoded by bits 11 and 9-6
   with bits 5-0 equal to 0.  Six shift-rotate instructions are decoded by bits
   11 and 9-4 with bits 3-0 indicating the shift count.  The remaining 118 bit
   combinations are undefined.

   Three of these undefined combinations are reserved in the 1000 E/F-Series.
   Opcode 100000 is the DIAG instruction, 100060 is the TIMER instruction, and
   100120 is the EXECUTE instruction.  The simulator implements DIAG and TIMER;
   EXECUTE does not appear to have been implemented in the original microcode
   and is not simulated.

   Results of execution of the other undefined instructions are dependent on the
   CPU model.  Rather than implementing each machine's specific behavior for
   each instruction, the simulator follows the 1000 M-Series decoding,
   regardless of the CPU model.

   The table consists of 11 secondary entries; the final entry allows parsing of
   the SWP mnemonic and encoding as RRR 16 as is permitted by the HP Assembler.

      15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 1 | 0   0   0 |   | 0 |    eau op     | 0   0   0   0   0   0 |  EAG
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 1 | 0   0   0 |   | 0 | eau shift/rotate op   |  shift count  |  EAG
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   The MAC instruction space also includes the User Instruction Group.  EAG
   instructions use the opcode ranges 100000-101377 and 104000-104777.  UIG
   instructions use the ranges 101400-101477 and 105000-105777.


   Implementation notes:

    1. The 2100 has the strictest decoding of the EAU set, so that is the
       encoding represented in the operations table.
*/

static const OP_DESC eag_desc = {               /* Extended Arithmetic Group descriptor */
    OP_LINEAR,                                  /*   linear search only */
    OP_SINGLE,                                  /*   single match allowed */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_EAU | CPU_ALL                           /*   applicable feature flags */
    };

static const OP_TABLE eag_ops = {               /* EAG opcodes, searched linearly */
    { "MPY", 0100200u, opMA1I,  0177760u },
    { "DIV", 0100400u, opMA1I,  0177760u },
    { "DLD", 0104200u, opMA1I,  0177760u },
    { "DST", 0104400u, opMA1I,  0177760u },

    { "ASL", 0100020u, opShift, 0177760u },
    { "LSL", 0100040u, opShift, 0177760u },
    { "RRL", 0100100u, opShift, 0177760u },
    { "ASR", 0101020u, opShift, 0177760u },
    { "LSR", 0101040u, opShift, 0177760u },
    { "RRR", 0101100u, opShift, 0177760u },
    { "SWP", 0101100u, opNone,  0177777u },     /* SWP is equivalent to RRR 16 */

    { NULL }
    };

static const OP_DESC eag_ef_desc = {            /* Extended Arithmetic Group 1000-E/F descriptor */
    OP_LINEAR,                                  /*   linear search only */
    OP_SINGLE,                                  /*   single match allowed */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_EAU | CPU_1000_E | CPU_1000_F           /*   applicable feature flags */
    };

static const OP_TABLE eag_ef_ops = {            /* EAG opcodes, searched linearly */
    { "DIAG",  0100000u, opNone, 0177777u },
    { "TIMER", 0100060u, opNone, 0177777u },

    { NULL }
    };


/* User Instruction Group.

   The UIG instructions are part of the MAC instruction space, which is
   indicated by bit 15 equal to 1 and bits 14-12 and 10 equal to 0.  They are
   divided into two sub-groups.  The first, UIG-0, occupies opcodes
   105000-105377.  The second, UIG-1, occupies opcodes 101400-101777 and
   105400-105777.  The 2100 decodes only UIG-0 instructions, whereas the 1000s
   use both UIG sets.  In particular, the 105740-105777 range is used by the
   1000 Extended Instruction Group (EIG), which is part of the base set.

   All of the optional firmware sets for the 2100 and 1000-M/E/F CPUs implement
   UIG instructions.  Therefore, there are separate tables for each firmware
   feature.

      15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     | 1 | 0   0   0 | B | 0   1 |    module     |     operation     |  UIG
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   Where:

     B = A/B register (0/1)

   For instructions in the range 10x400-10x777, bit 11 optionally may be
   significant.  If it is, then the 101xxx and 105xxx instructions are distinct
   and require separate table entries.  If it is not, then either 105xxx or
   101xxx may be used to invoke the instruction, although 105xxx is the
   canonical form.


   Implementation notes:

    1. If bit 11 is significant for some instructions and not for others, then
       the instructions that ignore bit 11 must be duplicated in the table and
       given the 105xxx forms in both sets of entries.  This ensures that when
       used as a parsing table, the canonical form is obtained.
*/


/* 105000-105362  2000 I/O Processor Instructions */

static const OP_DESC iop_2100_desc = {          /* 2100 I/O Processor descriptor */
    OP_LINEAR,                                  /*   linear search only */
    OP_SINGLE,                                  /*   single match allowed */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_IOP | CPU_2100                          /*   applicable feature flags */
    };

static const OP_TABLE iop_2100_ops = {          /* 2100 IOP opcodes, searched linearly */
    { "SBYTE", 0105300u, opNone,  0177777u },
    { "LBYTE", 0105320u, opNone,  0177777u },
    { "MBYTE", 0105120u, opMA1I,  0177777u },
    { "MWORD", 0105200u, opMA1ZI, 0177777u },

    { "LAI",   0105020u, opIOPON, 0177777u },   /* these must be split into negative and positive operands */
    { "LAI",   0105040u, opIOPOP, 0177777u },   /*   because the offset does not occupy bits */
    { "SAI",   0105060u, opIOPON, 0177777u },   /*     reserved for the value but instead are */
    { "SAI",   0105100u, opIOPOP, 0177777u },   /*       ADDED to the base opcode value */

    { "CRC",   0105150u, opV1,    0177777u },
    { "RESTR", 0105340u, opNone,  0177777u },
    { "READF", 0105220u, opNone,  0177777u },
    { "ENQ",   0105240u, opNone,  0177777u },
    { "PENQ",  0105257u, opNone,  0177777u },
    { "DEQ",   0105260u, opNone,  0177777u },
    { "TRSLT", 0105160u, opV1,    0177777u },
    { "ILIST", 0105000u, opA1V1,  0177777u },
    { "PRFEI", 0105222u, opV2A1,  0177777u },
    { "PRFEX", 0105223u, opMA1I,  0177777u },
    { "PRFIO", 0105221u, opV2,    0177777u },
    { "SAVE",  0105362u, opNone,  0177777u },

    { NULL }
    };


/* 105000-105137  Single-Precision Floating Point Instructions */

static const OP_DESC fp_desc = {                    /* Single-Precision Floating Point descriptor */
    OP_LINEAR,                                      /*   linear search only */
    OP_SINGLE,                                      /*   single match allowed */
    AB_UNUSED,                                      /*   A/B-register selector */
    CPU_FP | CPU_2100 | CPU_1000_M | CPU_1000_E     /*   applicable feature flags */
    };

static const OP_TABLE fp_ops = {                    /* FP opcodes, searched linearly */
    { "FAD", 0105000u, opMA1I, 0177760u },          /* bits 3-0 do not affect decoding */
    { "FSB", 0105020u, opMA1I, 0177760u },          /* bits 3-0 do not affect decoding */
    { "FMP", 0105040u, opMA1I, 0177760u },          /* bits 3-0 do not affect decoding */
    { "FDV", 0105060u, opMA1I, 0177760u },          /* bits 3-0 do not affect decoding */
    { "FIX", 0105100u, opNone, 0177760u },          /* bits 3-0 do not affect decoding */
    { "FLT", 0105120u, opNone, 0177760u },          /* bits 3-0 do not affect decoding */

    { NULL }
    };


/* 105000-105137  Floating Point Processor Instructions */

static const OP_DESC fpp_desc = {               /* Floating Point Processor descriptor */
    0000137u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_FP | CPU_1000_F                         /*   applicable feature flags */
    };

static const OP_TABLE fpp_ops = {               /* FPP opcodes, indexed by IR bits 6-0 */
    { "FAD",   0105000u, opMA1I },
    { ".XADD", 0105001u, opMA3I },
    { ".TADD", 0105002u, opMA3I },
    { ".EADD", 0105003u, opMA3I },
    { "fptst", 0105004u, opNone },              /* self-test */
    { "xexp",  0105005u, opNone },              /* expand exponent */
    { "reset", 0105006u, opNone },              /* processor reset */
    { "exstk", 0105007u, opNone },              /* execute a stack of operands */
    { "adchk", 0105010u, opNone },              /* addressing check */
    { "",      0105011u, opNone },              /* unimplemented */
    { "",      0105012u, opNone },              /* unimplemented */
    { "",      0105013u, opNone },              /* unimplemented */
    { ".DAD",  0105014u, opMA1I },
    { "",      0105015u, opNone },              /* unimplemented */
    { "",      0105016u, opNone },              /* unimplemented */
    { "",      0105017u, opNone },              /* unimplemented */
    { "FSB",   0105020u, opMA1I },
    { ".XSUB", 0105021u, opMA3I },
    { ".TSUB", 0105022u, opMA3I },
    { ".ESUB", 0105023u, opMA3I },
    { "",      0105024u, opNone },              /* unimplemented */
    { "",      0105025u, opNone },              /* unimplemented */
    { "",      0105026u, opNone },              /* unimplemented */
    { "",      0105027u, opNone },              /* unimplemented */
    { "",      0105030u, opNone },              /* unimplemented */
    { "",      0105031u, opNone },              /* unimplemented */
    { "",      0105032u, opNone },              /* unimplemented */
    { "",      0105033u, opNone },              /* unimplemented */
    { ".DSB",  0105034u, opMA1I },
    { "",      0105035u, opNone },              /* unimplemented */
    { "",      0105036u, opNone },              /* unimplemented */
    { "",      0105037u, opNone },              /* unimplemented */
    { "FMP",   0105040u, opMA1I },
    { ".XMPY", 0105041u, opMA3I },
    { ".TMPY", 0105042u, opMA3I },
    { ".EMPY", 0105043u, opMA3I },
    { "",      0105044u, opNone },              /* unimplemented */
    { "",      0105045u, opNone },              /* unimplemented */
    { "",      0105046u, opNone },              /* unimplemented */
    { "",      0105047u, opNone },              /* unimplemented */
    { "",      0105050u, opNone },              /* unimplemented */
    { "",      0105051u, opNone },              /* unimplemented */
    { "",      0105052u, opNone },              /* unimplemented */
    { "",      0105053u, opNone },              /* unimplemented */
    { ".DMP",  0105054u, opMA1I },
    { "",      0105055u, opNone },              /* unimplemented */
    { "",      0105056u, opNone },              /* unimplemented */
    { "",      0105057u, opNone },              /* unimplemented */
    { "FDV",   0105060u, opMA1I },
    { ".XDIV", 0105061u, opMA3I },
    { ".TDIV", 0105062u, opMA3I },
    { ".EDIV", 0105063u, opMA3I },
    { "",      0105064u, opNone },              /* unimplemented */
    { "",      0105065u, opNone },              /* unimplemented */
    { "",      0105066u, opNone },              /* unimplemented */
    { "",      0105067u, opNone },              /* unimplemented */
    { "",      0105070u, opNone },              /* unimplemented */
    { "",      0105071u, opNone },              /* unimplemented */
    { "",      0105072u, opNone },              /* unimplemented */
    { "",      0105073u, opNone },              /* unimplemented */
    { ".DDI",  0105074u, opMA1I },
    { "",      0105075u, opNone },              /* unimplemented */
    { "",      0105076u, opNone },              /* unimplemented */
    { "",      0105077u, opNone },              /* unimplemented */
    { "FIX",   0105100u, opNone },
    { ".XFXS", 0105101u, opMA1I },
    { ".TXFS", 0105102u, opMA1I },
    { ".EXFS", 0105103u, opMA1I },
    { ".FIXD", 0105104u, opNone },
    { ".XFXD", 0105105u, opMA1I },
    { ".TFXD", 0105106u, opMA1I },
    { ".EFXD", 0105107u, opMA1I },
    { "",      0105110u, opNone },              /* unimplemented */
    { "",      0105111u, opNone },              /* unimplemented */
    { "",      0105112u, opNone },              /* unimplemented */
    { "",      0105113u, opNone },              /* unimplemented */
    { ".DSBR", 0105114u, opMA1I },
    { "",      0105115u, opNone },              /* unimplemented */
    { "",      0105116u, opNone },              /* unimplemented */
    { "",      0105117u, opNone },              /* unimplemented */
    { "FLT",   0105120u, opNone },
    { ".XFTS", 0105121u, opMA1I },
    { ".TFTS", 0105122u, opMA1I },
    { ".EFTS", 0105123u, opMA1I },
    { ".FLTD", 0105124u, opNone },
    { ".XFTD", 0105125u, opMA1I },
    { ".TFTD", 0105126u, opMA1I },
    { ".EFTD", 0105127u, opMA1I },
    { "",      0105130u, opNone },              /* unimplemented */
    { "",      0105131u, opNone },              /* unimplemented */
    { "",      0105132u, opNone },              /* unimplemented */
    { "",      0105133u, opNone },              /* unimplemented */
    { ".DDIR", 0105134u, opMA1I },
    { "",      0105135u, opNone },              /* unimplemented */
    { "",      0105136u, opNone },              /* unimplemented */
    { "",      0105137u, opNone },              /* unimplemented */

    { NULL }
    };


/* 105200-105237  Fast FORTRAN Processor Instructions */

static const OP_DESC ffp_2100_desc = {          /* 2100 Fast FORTRAN Processor descriptor */
    0000037u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_FFP | CPU_2100                          /*   applicable feature flags */
    };

static const OP_TABLE ffp_2100_ops = {          /* 2100 FFP opcodes, indexed by IR bits 4-0 */
    { "",      0105200u, opNone },              /* unimplemented */
    { "DBLE",  0105201u, opMA3I },
    { "SNGL",  0105202u, opMA2I },
    { ".XMPY", 0105203u, opMA3I },
    { ".XDIV", 0105204u, opMA3I },
    { ".DFER", 0105205u, opMA2I },
    { "",      0105206u, opNone },              /* unimplemented */
    { "",      0105207u, opNone },              /* unimplemented */

    { "",      0105210u, opNone },              /* unimplemented */
    { "",      0105211u, opNone },              /* unimplemented */
    { "",      0105212u, opNone },              /* unimplemented */
    { ".XADD", 0105213u, opMA3I },
    { ".XSUB", 0105214u, opMA3I },
    { "",      0105215u, opNone },              /* unimplemented */
    { "",      0105216u, opNone },              /* unimplemented */
    { "",      0105217u, opNone },              /* unimplemented */

    { ".XFER", 0105220u, opNone },
    { ".GOTO", 0105221u, opMA2I },
    { "..MAP", 0105222u, opMA4I },
    { ".ENTR", 0105223u, opMA1I },
    { ".ENTP", 0105224u, opMA1I },
    { "",      0105225u, opNone },              /* unimplemented */
    { "",      0105226u, opNone },              /* unimplemented */
    { "$SETP", 0105227u, opMA1I },

    { "",      0105230u, opNone },              /* unimplemented */
    { "",      0105231u, opNone },              /* unimplemented */
    { "",      0105232u, opNone },              /* unimplemented */
    { "",      0105233u, opNone },              /* unimplemented */
    { "",      0105234u, opNone },              /* unimplemented */
    { "",      0105235u, opNone },              /* unimplemented */
    { "",      0105236u, opNone },              /* unimplemented */
    { "",      0105237u, opNone },              /* unimplemented */

    { NULL }
    };

static const OP_DESC ffp_m_desc = {             /* M-Series Fast FORTRAN Processor descriptor */
    0000037u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_FFP | CPU_1000_M                        /*   applicable feature flags */
    };

static const OP_TABLE ffp_m_ops = {             /* M-Series FFP opcodes, indexed by IR bits 4-0 */
    { "fftst", 0105200u, opNone },              /* self-test */
    { "DBLE",  0105201u, opMA3I },
    { "SNGL",  0105202u, opMA2I },
    { ".XMPY", 0105203u, opMA3I },
    { ".XDIV", 0105204u, opMA3I },
    { ".DFER", 0105205u, opMA2I },
    { ".XPAK", 0105206u, opMA1I },
    { "XADD",  0105207u, opMA4I },

    { "XSUB",  0105210u, opMA4I },
    { "XMPY",  0105211u, opMA4I },
    { "XDIV",  0105212u, opMA4I },
    { ".XADD", 0105213u, opMA3I },
    { ".XSUB", 0105214u, opMA3I },
    { ".XCOM", 0105215u, opMA1I },
    { "..DCM", 0105216u, opMA1I },
    { "DDINT", 0105217u, opMA3I },

    { ".XFER", 0105220u, opNone },
    { ".GOTO", 0105221u, opMA2I },
    { "..MAP", 0105222u, opMA4I },
    { ".ENTR", 0105223u, opMA1I },
    { ".ENTP", 0105224u, opMA1I },
    { ".PWR2", 0105225u, opMA2I },
    { ".FLUN", 0105226u, opMA1I },
    { "$SETP", 0105227u, opMA1I },

    { ".PACK", 0105230u, opMA2I },
    { "",      0105231u, opNone },              /* unimplemented */
    { "",      0105232u, opNone },              /* unimplemented */
    { "",      0105233u, opNone },              /* unimplemented */
    { "",      0105234u, opNone },              /* unimplemented */
    { "",      0105235u, opNone },              /* unimplemented */
    { "",      0105236u, opNone },              /* unimplemented */
    { "",      0105237u, opNone },              /* unimplemented */

    { NULL }
    };

static const OP_DESC ffp_e_desc = {             /* E-Series Fast FORTRAN Processor descriptor */
    0000037u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_FFP | CPU_1000_E                        /*   applicable feature flags */
    };

static const OP_TABLE ffp_e_ops = {             /* E-Series FFP opcodes, indexed by IR bits 4-0 */
    { "fftst", 0105200u, opNone },              /* self-test */
    { "DBLE",  0105201u, opMA3I },
    { "SNGL",  0105202u, opMA2I },
    { ".XMPY", 0105203u, opMA3I },
    { ".XDIV", 0105204u, opMA3I },
    { ".DFER", 0105205u, opMA2I },
    { ".XPAK", 0105206u, opMA1I },
    { "XADD",  0105207u, opMA4I },

    { "XSUB",  0105210u, opMA4I },
    { "XMPY",  0105211u, opMA4I },
    { "XDIV",  0105212u, opMA4I },
    { ".XADD", 0105213u, opMA3I },
    { ".XSUB", 0105214u, opMA3I },
    { ".XCOM", 0105215u, opMA1I },
    { "..DCM", 0105216u, opMA1I },
    { "DDINT", 0105217u, opMA3I },

    { ".XFER", 0105220u, opNone },
    { ".GOTO", 0105221u, opMA2I },
    { "..MAP", 0105222u, opMA4I },
    { ".ENTR", 0105223u, opMA1I },
    { ".ENTP", 0105224u, opMA1I },
    { ".PWR2", 0105225u, opMA2I },
    { ".FLUN", 0105226u, opMA1I },
    { "$SETP", 0105227u, opMA1I },

    { ".PACK", 0105230u, opMA2I },
    { ".CFER", 0105231u, opMA2I },
    { "",      0105232u, opNone },              /* unimplemented */
    { "",      0105233u, opNone },              /* unimplemented */
    { "",      0105234u, opNone },              /* unimplemented */
    { "",      0105235u, opNone },              /* unimplemented */
    { "",      0105236u, opNone },              /* unimplemented */
    { "",      0105237u, opNone },              /* unimplemented */

    { NULL }
    };

static const OP_DESC ffp_f_desc = {             /* F-Series Fast FORTRAN Processor descriptor */
    0000037u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_FFP | CPU_1000_F                        /*   applicable feature flags */
    };

static const OP_TABLE ffp_f_ops = {             /* F-Series FFP opcodes, indexed by IR bits 4-0 */
    { "fftst", 0105200u, opNone },              /* self-test */
    { "DBLE",  0105201u, opMA3I },
    { "SNGL",  0105202u, opMA2I },
    { ".DNG",  0105203u, opNone },
    { ".DCO",  0105204u, opNone },
    { ".DFER", 0105205u, opMA2I },
    { ".XPAK", 0105206u, opMA1I },
    { ".BLE",  0105207u, opMA3I },

    { ".DIN",  0105210u, opNone },
    { ".DDE",  0105211u, opNone },
    { ".DIS",  0105212u, opNone },
    { ".DDS",  0105213u, opNone },
    { ".NGL",  0105214u, opMA2I },
    { ".XCOM", 0105215u, opMA1I },
    { "..DCM", 0105216u, opMA1I },
    { "DDINT", 0105217u, opMA3I },

    { ".XFER", 0105220u, opNone },
    { ".GOTO", 0105221u, opMA2I },
    { "..MAP", 0105222u, opMA4I },
    { ".ENTR", 0105223u, opMA1I },
    { ".ENTP", 0105224u, opMA1I },
    { ".PWR2", 0105225u, opMA2I },
    { ".FLUN", 0105226u, opMA1I },
    { "$SETP", 0105227u, opMA1I },

    { ".PACK", 0105230u, opMA2I },
    { ".CFER", 0105231u, opMA2I },
    { "..FCM", 0105232u, opMA1I },
    { "..TCM", 0105233u, opMA1I },
    { "",      0105234u, opNone },              /* unimplemented */
    { "",      0105235u, opNone },              /* unimplemented */
    { "",      0105236u, opNone },              /* unimplemented */
    { "",      0105237u, opNone },              /* unimplemented */

    { NULL }
    };


/* 105240-105257  RTE-IVA/B Extended Memory Instructions */

static const OP_DESC ema_desc = {               /* Extended Memory Area descriptor */
    0000017u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_EMA | CPU_1000_E | CPU_1000_F           /*   applicable feature flags */
    };

static const OP_TABLE ema_ops = {               /* EMA opcodes, indexed by IR bits 3-0 */
    { ".EMIO", 0105240u, opMA3I },              /* variable operand count */
    { "MMAP",  0105241u, opMA3I },
    { "emtst", 0105242u, opNone },              /* self-test */
    { "",      0105243u, opNone },              /* unimplemented */
    { "",      0105244u, opNone },              /* unimplemented */
    { "",      0105245u, opNone },              /* unimplemented */
    { "",      0105246u, opNone },              /* unimplemented */
    { "",      0105247u, opNone },              /* unimplemented */

    { "",      0105250u, opNone },              /* unimplemented */
    { "",      0105251u, opNone },              /* unimplemented */
    { "",      0105252u, opNone },              /* unimplemented */
    { "",      0105253u, opNone },              /* unimplemented */
    { "",      0105254u, opNone },              /* unimplemented */
    { "",      0105255u, opNone },              /* unimplemented */
    { "",      0105256u, opNone },              /* unimplemented */
    { ".EMAP", 0105257u, opMA3I },              /* variable operand count */

    { NULL }
    };


/* 105240-105257  RTE-6/VM Virtual Memory Instructions */

static const OP_DESC vma_desc = {               /* Virtual Memory Area descriptor */
    0000017u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_VMAOS | CPU_1000_E | CPU_1000_F         /*   applicable feature flags */
    };

static const OP_TABLE vma_ops = {               /* VMA opcodes, indexed by IR bits 3-0 */
    { ".PMAP", 0105240u, opNone },
    { "$LOC",  0105241u, opMA6I },
    { "vmtst", 0105242u, opNone },              /* self-test */
    { ".SWP",  0105243u, opNone },
    { ".STAS", 0105244u, opNone },
    { ".LDAS", 0105245u, opNone },
    { "",      0105246u, opNone },              /* unimplemented */
    { ".UMPY", 0105247u, opMA1I },

    { ".IMAP", 0105250u, opMA1I },              /* operand count varies from 1-n */
    { ".IMAR", 0105251u, opMA1I },
    { ".JMAP", 0105252u, opMA1I },              /* operand count varies from 1-n */
    { ".JMAR", 0105253u, opMA1I },
    { ".LPXR", 0105254u, opMA2I },
    { ".LPX",  0105255u, opMA1I },
    { ".LBPR", 0105256u, opMA1I },
    { ".LBP",  0105257u, opNone },

    { NULL }
    };


/* 105320-105337  Double Integer Instructions */

static const OP_DESC dbi_desc = {               /* Double Integer Instructions descriptor */
    0000017u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_DBI | CPU_1000_E                        /*   applicable feature flags */
    };

static const OP_TABLE dbi_ops = {               /* DBI opcodes, indexed by IR bits 3-0 */
    { "dbtst", 0105320u, opNone },              /* self-test */
    { ".DAD",  0105321u, opMA1I },
    { ".DMP",  0105322u, opMA1I },
    { ".DNG",  0105323u, opNone },
    { ".DCO",  0105324u, opMA1I },
    { ".DDI",  0105325u, opMA1I },
    { ".DDIR", 0105326u, opMA1I },
    { ".DSB",  0105327u, opMA1I },
    { ".DIN",  0105330u, opNone },
    { ".DDE",  0105331u, opNone },
    { ".DIS",  0105332u, opMA1I },
    { ".DDS",  0105333u, opMA1I },
    { ".DSBR", 0105334u, opMA1I },
    { "",      0105335u, opNone },              /* unimplemented */
    { "",      0105336u, opNone },              /* unimplemented */
    { "",      0105337u, opNone },              /* unimplemented */

    { NULL }
    };


/* 105320-105337  Scientific Instruction Set */

static const OP_DESC sis_desc = {               /* Scientific Instruction Set descriptor */
    0000017u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_BASE | CPU_1000_F                       /*   applicable feature flags */
    };

static const OP_TABLE sis_ops = {               /* SIS opcodes, indexed by IR bits 3-0 */
    { "TAN",   0105320u, opNone },
    { "SQRT",  0105321u, opNone },
    { "ALOG",  0105322u, opNone },
    { "ATAN",  0105323u, opNone },
    { "COS",   0105324u, opNone },
    { "SIN",   0105325u, opNone },
    { "EXP",   0105326u, opNone },
    { "ALOGT", 0105327u, opNone },
    { "TANH",  0105330u, opNone },
    { "DPOLY", 0105331u, opV1A5 },
    { "/CMRT", 0105332u, opMA3I },
    { "/ATLG", 0105333u, opMA1I },
    { ".FPWR", 0105334u, opMA1I },
    { ".TPWR", 0105335u, opMA2I },
    { "",      0105336u, opNone },              /* unimplemented */
    { "sitst", 0105337u, opNone },              /* self-test */

    { NULL }
    };


/* 105340-105357  RTE-6/VM Operating System Instructions */

static const OP_DESC os_desc = {                /* RTE-6/VM Operating System descriptor */
    0000017u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_VMAOS | CPU_1000_E | CPU_1000_F         /*   applicable feature flags */
    };

static const OP_TABLE os_ops = {                /* RTE-6/VM OS opcodes, indexed by IR bits 3-0 */
    { "$LIBR", 0105340u, opMA1I },
    { "$LIBX", 0105341u, opMA1I },
    { ".TICK", 0105342u, opNone },
    { ".TNAM", 0105343u, opNone },
    { ".STIO", 0105344u, opMA1I },              /* operand count varies from 1-n */
    { ".FNW",  0105345u, opMA1I },
    { ".IRT",  0105346u, opMA1I },
    { ".LLS",  0105347u, opMA2I },

    { ".SIP",  0105350u, opNone },
    { ".YLD",  0105351u, opMA1I },
    { ".CPM",  0105352u, opMA2I },
    { ".ETEQ", 0105353u, opNone },
    { ".ENTN", 0105354u, opMA1I },
    { "ostst", 0105355u, opNone },              /* self-test */
    { ".ENTC", 0105356u, opMA1I },
    { ".DSPI", 0105357u, opNone },

    { NULL }
    };

static const OP_DESC trap_desc = {              /* RTE-6/VM Operating System trap instructions descriptor */
    OP_LINEAR,                                  /*   linear search only */
    OP_SINGLE,                                  /*   single match allowed */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_VMAOS | CPU_1000_E | CPU_1000_F         /*   applicable feature flags */
    };

static const OP_TABLE trap_ops = {              /* RTE-6/VM OS trap opcodes, searched linearly */
    { "$DCPC", 0105354u, opNone, 0177777u },
    { "$MPV",  0105355u, opNone, 0177777u },
    { "$DEV",  0105356u, opNone, 0177777u },
    { "$TBG",  0105357u, opNone, 0177777u },

    { NULL }
    };


/* 10x400-10x437  2000 I/O Processor Instructions */

static const OP_DESC iop1_1000_desc = {         /* M/E-Series I/O Processor Instructions descriptor */
    OP_LINEAR,                                  /*   linear search only */
    OP_SINGLE,                                  /*   single match allowed */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_IOP | CPU_1000_M | CPU_1000_E           /*   applicable feature flags */
    };

static const OP_TABLE iop1_1000_ops = {         /* M/E-Series IOP opcodes, searched linearly */
    { "SAI", 0101400u, opIOPO, 0177777u },
    { "LAI", 0105400u, opIOPO, 0177777u },

    { NULL }
    };

/* 10x460-10x477  2000 I/O Processor Instructions */

static const OP_DESC iop2_1000_desc = {         /* M/E-Series I/O Processor Instructions descriptor */
    0000017u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_IOP | CPU_1000_M | CPU_1000_E           /*   applicable feature flags */
    };

static const OP_TABLE iop2_1000_ops = {         /* M/E-Series IOP opcodes, indexed by IR bits 3-0 */
    { "CRC",   0105460u, opV1   },
    { "RESTR", 0105461u, opNone },
    { "READF", 0105462u, opNone },
    { "INS",   0105463u, opNone },
    { "ENQ",   0105464u, opNone },
    { "PENQ",  0105465u, opNone },
    { "DEQ",   0105466u, opNone },
    { "TRSLT", 0105467u, opV1   },
    { "ILIST", 0105470u, opA1V1 },
    { "PRFEI", 0105471u, opV2A1 },
    { "PRFEX", 0105472u, opMA1I },
    { "PRFIO", 0105473u, opV2   },
    { "SAVE",  0105474u, opNone },
    { "",      0105475u, opNone },              /* unimplemented */
    { "",      0105476u, opNone },              /* unimplemented */
    { "",      0105477u, opNone },              /* unimplemented */

    { NULL }
    };


/* 10x460-10x477  Vector Instruction Set */

static const OP_DESC vis_desc = {               /* Vector Instruction Set descriptor */
    0000017u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_MASK,                                    /*   A/B-register selector */
    CPU_VIS | CPU_1000_F                        /*   applicable feature flags */
    };

static const OP_TABLE vis_ops = {               /* VIS opcodes, indexed by IR bits 11 + 3-0 */
    { "",      0101460u, opNone },              /* .VECT single-precision */
    { "VPIV",  0101461u, opZA8  },
    { "VABS",  0101462u, opZA5  },
    { "VSUM",  0101463u, opZA4  },
    { "VNRM",  0101464u, opZA4  },
    { "VDOT",  0101465u, opZA6  },
    { "VMAX",  0101466u, opZA4  },
    { "VMAB",  0101467u, opZA4  },
    { "VMIN",  0101470u, opZA4  },
    { "VMIB",  0101471u, opZA4  },
    { "VMOV",  0101472u, opZA5  },
    { "VSWP",  0101473u, opZA5  },
    { ".ERES", 0101474u, opMA3I },              /* variable operand count */
    { ".ESEG", 0101475u, opMA2I },
    { ".VSET", 0101476u, opMA7I },
    { "vitst", 0105477u, opNone },              /* self-test */

    { "",      0105460u, opNone },              /* .VECT double-precision */
    { "DVPIV", 0105461u, opZA8  },
    { "DVABS", 0105462u, opZA5  },
    { "DVSUM", 0105463u, opZA4  },
    { "DVNRM", 0105464u, opZA4  },
    { "DVDOT", 0105465u, opZA6  },
    { "DVMAX", 0105466u, opZA4  },
    { "DVMAB", 0105467u, opZA4  },
    { "DVMIN", 0105470u, opZA4  },
    { "DVMIB", 0105471u, opZA4  },
    { "DVMOV", 0105472u, opZA5  },
    { "DVSWP", 0105473u, opZA5  },
    { ".ERES", 0101474u, opMA3I },              /* variable operand count */
    { ".ESEG", 0101475u, opMA2I },
    { ".VSET", 0101476u, opMA7I },
    { "vitst", 0105477u, opNone },              /* self-test */

    { "VADD",  TO_DWORD (0000000u, 0101460u), opMA7I, D32_MASK },
    { "VSUB",  TO_DWORD (0000020u, 0101460u), opMA7I, D32_MASK },
    { "VMPY",  TO_DWORD (0000040u, 0101460u), opMA7I, D32_MASK },
    { "VDIV",  TO_DWORD (0000060u, 0101460u), opMA7I, D32_MASK },
    { "VSAD",  TO_DWORD (0000400u, 0101460u), opMA6I, D32_MASK },
    { "VSSB",  TO_DWORD (0000420u, 0101460u), opMA6I, D32_MASK },
    { "VSMY",  TO_DWORD (0000440u, 0101460u), opMA6I, D32_MASK },
    { "VSDV",  TO_DWORD (0000460u, 0101460u), opMA6I, D32_MASK },
    { "",      0101460u, opNone,   0177777u },                      /* catch unimplemented two-word instructions */

    { "DVADD", TO_DWORD (0004002u, 0105460u), opMA7I, D32_MASK },
    { "DVSUB", TO_DWORD (0004022u, 0105460u), opMA7I, D32_MASK },
    { "DVMPY", TO_DWORD (0004042u, 0105460u), opMA7I, D32_MASK },
    { "DVDIV", TO_DWORD (0004062u, 0105460u), opMA7I, D32_MASK },
    { "DVSAD", TO_DWORD (0004402u, 0105460u), opMA6I, D32_MASK },
    { "DVSSB", TO_DWORD (0004422u, 0105460u), opMA6I, D32_MASK },
    { "DVSMY", TO_DWORD (0004442u, 0105460u), opMA6I, D32_MASK },
    { "DVSDV", TO_DWORD (0004462u, 0105460u), opMA6I, D32_MASK },
    { "",      0105460u, opNone,   0177777u },                      /* catch unimplemented two-word instructions */

    { NULL }
    };


/* 10x600-10x617  SIGNAL/1000 Instruction Set */

static const OP_DESC sig_desc = {               /* SIGNAL/1000 Instruction Set descriptor */
    0000017u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_UNUSED,                                  /*   A/B-register selector */
    CPU_SIGNAL | CPU_1000_F                     /*   applicable feature flags */
    };

static const OP_TABLE sig_ops = {               /* SIGNAL opcodes, indexed by IR bits 3-0 */
    { "BITRV", 0105600u, opMA4I },
    { "BTRFY", 0105601u, opMA6I },
    { "UNSCR", 0105602u, opMA6I },
    { "PRSCR", 0105603u, opMA6I },
    { "BITR1", 0105604u, opMA5I },
    { "BTRF1", 0105605u, opMA7I },
    { ".CADD", 0105606u, opMA3I },
    { ".CSUB", 0105607u, opMA3I },
    { ".CMPY", 0105610u, opMA3I },
    { ".CDIV", 0105611u, opMA3I },
    { "CONJG", 0105612u, opMA3I },
    { "..CCM", 0105613u, opMA1I },
    { "AIMAG", 0105614u, opMA2I },
    { "CMPLX", 0105615u, opMA4I },
    { "",      0105616u, opNone },              /* unimplemented */
    { "sitst", 0105617u, opNone },              /* self-test */

    { NULL }
    };


/* 10x700-10x737  Dynamic Mapping System Instructions */

static const OP_DESC dms_desc = {               /* Dynamic Mapping System instructions descriptor */
    0000037u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_MASK,                                    /*   A/B-register selector */
    CPU_DMS | CPU_1000                          /*   applicable feature flags */
    };

static const OP_TABLE dms_ops = {               /* DMS opcodes, indexed by IR bits 11 + 4-0 */
    { "xmm",   0105700u, opNone },              /* decodes as XMM */
    { "dmtst", 0105701u, opNone },              /* self-test (E/F-Series) or NOP (M-Series) */
    { "MBI",   0105702u, opNone },              /* bit 11 is not significant */
    { "MBF",   0105703u, opNone },              /* bit 11 is not significant */
    { "MBW",   0105704u, opNone },              /* bit 11 is not significant */
    { "MWI",   0105705u, opNone },              /* bit 11 is not significant */
    { "MWF",   0105706u, opNone },              /* bit 11 is not significant */
    { "MWW",   0105707u, opNone },              /* bit 11 is not significant */
    { "SYA",   0101710u, opNone },
    { "USA",   0101711u, opNone },
    { "PAA",   0101712u, opNone },
    { "PBA",   0101713u, opNone },
    { "SSM",   0105714u, opMA1I },              /* bit 11 is not significant */
    { "JRS",   0105715u, opMA2I },              /* bit 11 is not significant */
    { "",      0101716u, opNone },              /* unimplemented */
    { "",      0101717u, opNone },              /* unimplemented */

    { "XMM",   0105720u, opNone },              /* bit 11 is not significant */
    { "XMS",   0105721u, opNone },              /* bit 11 is not significant */
    { "XMA",   0101722u, opNone },
    { "",      0101723u, opNone },              /* unimplemented */
    { "XLA",   0101724u, opMA1I },
    { "XSA",   0101725u, opMA1I },
    { "XCA",   0101726u, opMA1I },
    { "LFA",   0101727u, opNone },
    { "RSA",   0101730u, opNone },
    { "RVA",   0101731u, opNone },
    { "DJP",   0105732u, opMA1I },              /* bit 11 is not significant */
    { "DJS",   0105733u, opMA1I },              /* bit 11 is not significant */
    { "SJP",   0105734u, opMA1I },              /* bit 11 is not significant */
    { "SJS",   0105735u, opMA1I },              /* bit 11 is not significant */
    { "UJP",   0105736u, opMA1I },              /* bit 11 is not significant */
    { "UJS",   0105737u, opMA1I },              /* bit 11 is not significant */

    { "xmm",   0105700u, opNone },              /* decodes as XMM */
    { "dmtst", 0105701u, opNone },              /* self-test (E/F-Series) or NOP (M-Series) */
    { "MBI",   0105702u, opNone },
    { "MBF",   0105703u, opNone },
    { "MBW",   0105704u, opNone },
    { "MWI",   0105705u, opNone },
    { "MWF",   0105706u, opNone },
    { "MWW",   0105707u, opNone },
    { "SYB",   0105710u, opNone },
    { "USB",   0105711u, opNone },
    { "PAB",   0105712u, opNone },
    { "PBB",   0105713u, opNone },
    { "SSM",   0105714u, opMA1I },
    { "JRS",   0105715u, opMA2I },
    { "",      0105716u, opNone },              /* unimplemented */
    { "",      0105717u, opNone },              /* unimplemented */

    { "XMM",   0105720u, opNone },
    { "XMS",   0105721u, opNone },
    { "XMB",   0105722u, opNone },
    { "",      0105723u, opNone },              /* unimplemented */
    { "XLB",   0105724u, opMA1I },
    { "XSB",   0105725u, opMA1I },
    { "XCB",   0105726u, opMA1I },
    { "LFB",   0105727u, opNone },
    { "RSB",   0105730u, opNone },
    { "RVB",   0105731u, opNone },
    { "DJP",   0105732u, opMA1I },
    { "DJS",   0105733u, opMA1I },
    { "SJP",   0105734u, opMA1I },
    { "SJS",   0105735u, opMA1I },
    { "UJP",   0105736u, opMA1I },
    { "UJS",   0105737u, opMA1I },

    { NULL }
    };


/* 10x740-10x777  Extended Instruction Group */

static const OP_DESC eig_desc = {               /* Extended Instruction Group descriptor */
    0000037u,                                   /*   opcode mask */
    0u,                                         /*   opcode shift */
    AB_MASK,                                    /*   A/B-register selector */
    CPU_BASE | CPU_1000                         /*   applicable feature flags */
    };

static const OP_TABLE eig_ops = {               /* EIG opcodes, indexed by IR bits 11 + 4-0 */
    { "SAX", 0101740u, opMA1I  },
    { "CAX", 0101741u, opNone  },
    { "LAX", 0101742u, opMA1I  },
    { "STX", 0105743u, opMA1I  },               /* bit 11 is not significant */
    { "CXA", 0101744u, opNone  },
    { "LDX", 0105745u, opMA1I  },               /* bit 11 is not significant */
    { "ADX", 0105746u, opMA1I  },               /* bit 11 is not significant */
    { "XAX", 0101747u, opNone  },
    { "SAY", 0101750u, opMA1I  },
    { "CAY", 0101751u, opNone  },
    { "LAY", 0101752u, opMA1I  },
    { "STY", 0105753u, opMA1I  },               /* bit 11 is not significant */
    { "CYA", 0101754u, opNone  },
    { "LDY", 0105755u, opMA1I  },               /* bit 11 is not significant */
    { "ADY", 0105756u, opMA1I  },               /* bit 11 is not significant */
    { "XAY", 0101757u, opNone  },

    { "ISX", 0105760u, opNone  },               /* bit 11 is not significant */
    { "DSX", 0105761u, opNone  },               /* bit 11 is not significant */
    { "JLY", 0105762u, opMA1I  },               /* bit 11 is not significant */
    { "LBT", 0105763u, opNone  },               /* bit 11 is not significant */
    { "SBT", 0105764u, opNone  },               /* bit 11 is not significant */
    { "MBT", 0105765u, opMA1ZI },               /* bit 11 is not significant */
    { "CBT", 0105766u, opMA1ZI },               /* bit 11 is not significant */
    { "SFB", 0105767u, opNone  },               /* bit 11 is not significant */
    { "ISY", 0105770u, opNone  },               /* bit 11 is not significant */
    { "DSY", 0105771u, opNone  },               /* bit 11 is not significant */
    { "JPY", 0105772u, opMA1I  },               /* bit 11 is not significant */
    { "SBS", 0105773u, opMA2I  },               /* bit 11 is not significant */
    { "CBS", 0105774u, opMA2I  },               /* bit 11 is not significant */
    { "TBS", 0105775u, opMA2I  },               /* bit 11 is not significant */
    { "CMW", 0105776u, opMA1ZI },               /* bit 11 is not significant */
    { "MVW", 0105777u, opMA1ZI },               /* bit 11 is not significant */

    { "SBX", 0105740u, opMA1I  },
    { "CBX", 0105741u, opNone  },
    { "LBX", 0105742u, opMA1I  },
    { "STX", 0105743u, opMA1I  },
    { "CXB", 0105744u, opNone  },
    { "LDX", 0105745u, opMA1I  },
    { "ADX", 0105746u, opMA1I  },
    { "XBX", 0105747u, opNone  },
    { "SBY", 0105750u, opMA1I  },
    { "CBY", 0105751u, opNone  },
    { "LBY", 0105752u, opMA1I  },
    { "STY", 0105753u, opMA1I  },
    { "CYB", 0105754u, opNone  },
    { "LDY", 0105755u, opMA1I  },
    { "ADY", 0105756u, opMA1I  },
    { "XBY", 0105757u, opNone  },

    { "ISX", 0105760u, opNone  },
    { "DSX", 0105761u, opNone  },
    { "JLY", 0105762u, opMA1I  },
    { "LBT", 0105763u, opNone  },
    { "SBT", 0105764u, opNone  },
    { "MBT", 0105765u, opMA1ZI },
    { "CBT", 0105766u, opMA1ZI },
    { "SFB", 0105767u, opNone  },
    { "ISY", 0105770u, opNone  },
    { "DSY", 0105771u, opNone  },
    { "JPY", 0105772u, opMA1I  },
    { "SBS", 0105773u, opMA2I  },
    { "CBS", 0105774u, opMA2I  },
    { "TBS", 0105775u, opMA2I  },
    { "CMW", 0105776u, opMA1ZI },
    { "MVW", 0105777u, opMA1ZI },

    { NULL }
    };


/* Parsing tables.

   Symbolic entry of instructions requires parsing the command line and matching
   the instruction mnemonic to an entry in an opcode table.  The parser table
   contains pointers to all of the opcode tables and their associated
   descriptors.  Parsing searches linearly in the order specified, so more
   common instructions should appear before less common ones.
*/

typedef struct {                                /* parser table entry */
    const OP_DESC  *descriptor;                 /*   opcode descriptor pointer */
    const OP_ENTRY *opcodes;                    /*   opcode table pointer */
    } PARSER_ENTRY;

static const PARSER_ENTRY parser_table [] = {   /* parser table array, searched linearly */
    { &mrg_desc,       mrg_ops       },
    { &srg1_desc,      srg1_ops      },
    { &srg_udesc,      srg_uops      },
    { &asg_udesc,      asg_uops      },
    { &iog_desc,       iog_ops       },
    { &eag_desc,       eag_ops       },
    { &eag_ef_desc,    eag_ef_ops    },
    { &iop_2100_desc,  iop_2100_ops  },
    { &fp_desc,        fp_ops        },
    { &fpp_desc,       fpp_ops       },
    { &ffp_2100_desc,  ffp_2100_ops  },
    { &ffp_m_desc,     ffp_m_ops     },
    { &ffp_e_desc,     ffp_e_ops     },
    { &ffp_f_desc,     ffp_f_ops     },
    { &ema_desc,       ema_ops       },
    { &vma_desc,       vma_ops       },
    { &dbi_desc,       dbi_ops       },
    { &sis_desc,       sis_ops       },
    { &os_desc,        os_ops        },
    { &trap_desc,      trap_ops      },
    { &iop1_1000_desc, iop1_1000_ops },
    { &iop2_1000_desc, iop2_1000_ops },
    { &vis_desc,       vis_ops       },
    { &sig_desc,       sig_ops       },
    { &dms_desc,       dms_ops       },
    { &eig_desc,       eig_ops       },

    { NULL,            NULL           }
    };


/* Poll device local SCP support routines */

static t_stat poll_service (UNIT *uptr);
static t_stat poll_reset   (DEVICE *dptr);


/* Poll device SCP data structures */

/* Unit list */

static UNIT poll_unit [] = {
/*           Event Routine  Unit Flags  Capacity  Delay       */
/*           -------------  ----------  --------  ----------- */
    { UDATA (&poll_service, UNIT_IDLE,     0),    POLL_PERIOD }
    };


/* Device descriptor */

static DEVICE poll_dev = {
    "Keyboard poll",                            /* device name */
    poll_unit,                                  /* unit array */
    NULL,                                       /* register array */
    NULL,                                       /* modifier array */
    1,                                          /* number of units */
    8,                                          /* address radix */
    32,                                         /* address width */
    1,                                          /* address increment */
    8,                                          /* data radix */
    32,                                         /* data width */
    NULL,                                       /* examine routine */
    NULL,                                       /* deposit routine */
    &poll_reset,                                /* reset routine */
    NULL,                                       /* boot routine */
    NULL,                                       /* attach routine */
    NULL,                                       /* detach routine */
    NULL,                                       /* device information block pointer */
    0,                                          /* device flags */
    0,                                          /* debug control flags */
    NULL,                                       /* debug flag name table */
    NULL,                                       /* memory size change routine */
    NULL                                        /* logical device name */
    };



/* System interface local SCP support routines */

void   hp_one_time_init  (void);
static t_bool fprint_stopped (FILE *st, t_stat reason);
static void   fprint_addr    (FILE *st, DEVICE *dptr, t_addr addr);
static t_addr parse_addr     (DEVICE *dptr, CONST char *cptr, CONST char **tptr);

static t_stat hp_exdep_cmd (int32 arg, CONST char *buf);
static t_stat hp_run_cmd   (int32 arg, CONST char *buf);
static t_stat hp_brk_cmd   (int32 arg, CONST char *buf);
static t_stat hp_load_cmd  (int32 arg, CONST char *buf);


/* System interface local utility routines */

static t_stat  fprint_value       (FILE *ofile, t_value val,  uint32 radix, uint32 width, uint32 format);
static t_stat  fprint_instruction (FILE *ofile, t_addr addr, t_value *val, uint32 radix,
                                   const OP_DESC op_desc, const OP_TABLE ops);

static t_value parse_address     (CONST char *cptr, t_stat *status);
static t_value parse_value       (CONST char *cptr, uint32 radix, t_value max, t_stat *status);
static t_stat  parse_cpu         (CONST char *cptr, t_addr addr, t_value *val, uint32 radix, SYMBOL_SOURCE target);
static t_stat  parse_instruction (CONST char *cptr, t_addr addr, t_value *val, uint32 radix, const OP_ENTRY *optr);
static t_stat  parse_micro_ops   (const OP_ENTRY *optr, char *gbuf, t_value *val,
                                  CONST char **gptr, uint32 *accumulator);

static int fgetword (FILE *fileref);
static int fputword (int data, FILE *fileref);


/* System interface state */

static size_t device_size    = 0;               /* the maximum device name size */
static size_t flag_size      = 0;               /* the maximum trace flag name size */
static t_bool parse_physical = TRUE;            /* the address parser configuration */
static UNIT   *cpu_uptr      = NULL;            /* a pointer to the CPU unit */

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


/* System interface global SCP data definitions */

char sim_name [] = "HP 2100";                   /* the simulator name */

int32 sim_emax = MAX_INSTR_LENGTH;              /* the maximum number of words in any instruction */

DEVICE *sim_devices [] = {                      /* an array of pointers to the simulated devices */
    &cpu_dev,                                   /*   CPU (must be first) */
    &dma1_dev, &dma2_dev,                       /*   DMA/DCPC */
    &mp_dev,                                    /*   Memory Protect */
    &meu_dev,                                   /*   Memory Expansion Module */

    &baci_dev,                                  /*   12966A Buffered Asynchronous Communications Interface */
    &da_dev,                                    /*   7906H/20H/25H ICD Disc Drives */
    &dc_dev,                                    /*   Dummy Disc Drive for diagnostics */
    &dpc_dev,  &dpd_dev,                        /*   2870/7900 Disc Drives */
    &dqc_dev,  &dqd_dev,                        /*   2883 Disc Drives */
    &drc_dev,  &drd_dev,                        /*   277x Disc/Drum */
    &ds_dev,                                    /*   7905/06/20/25 MAC Disc Drives */
    &ipli_dev, &iplo_dev,                       /*   Processor Interconnect Kit */
    &lps_dev,                                   /*   2767 Line Printer */
    &lpt_dev,                                   /*   2607 Line Printer */
    &mc1_dev,  &mc2_dev,                        /*   12566B Microcircuit Interface */
    &mpx_dev,                                   /*   12792C Eight-Channel Asynchronous Multiplexer */
    &msc_dev,  &msd_dev,                        /*   7970B/E Magnetic Tape Drives */
    &mtc_dev,  &mtd_dev,                        /*   3030 Magnetic Tape Drive */
    &muxu_dev, &muxl_dev, &muxc_dev,            /*   12920A Sixteen-Channel Asynchronous Multiplexer */
    &pif_dev,                                   /*   12620A/12936A Privileged Interrupt Fence */
    &ptp_dev,                                   /*   2895 Paper Tape Punch */
    &ptr_dev,                                   /*   2748 Paper Tape Reader */
    &tbg_dev,                                   /*   Time Base Generator */
    &tty_dev,                                   /*   2752/2754 Teleprinter */

    &poll_dev,                                  /*   simulation console polling device */
    NULL
    };                                          /* end of the device list */

#define DEVICE_COUNT        (sizeof sim_devices / sizeof sim_devices [0] - 1)   /* the count excludes the NULL pointer */


const char *sim_stop_messages [SCPE_BASE] = {   /* an array of pointers to the stop messages in STOP_nnn order */
    "Impossible error",                         /*   0 (never returned) */
    "Unimplemented instruction",                /*   STOP_UNIMPL */
    "Unassigned select code",                   /*   STOP_UNSC */
    "Undefined instruction",                    /*   STOP_UNDEF */
    "Indirect address loop",                    /*   STOP_INDIR */
    "Programmed halt",                          /*   STOP_HALT */
    "Breakpoint",                               /*   STOP_BRKPNT */
    "Cable not connected to",                   /*   STOP_NOCONN */
    "No tape loaded in",                        /*   STOP_NOTAPE */
    "End of tape on"                            /*   STOP_EOT    */
    };


/* Local command table.

   This table defines commands and command behaviors that are specific to this
   simulator.  Specifically:

     * EXAMINE, DEPOSIT, IEXAMINE, and IDEPOSIT accept the page/offset physical
       address form.

     * RUN, GO, BREAK, and NOBREAK accept the logical address form and reject
       the page/offset physical address form.

   The table is initialized with only those fields that differ from the standard
   command table.  During one-time simulator initialization, the empty fields
   are filled in from the corresponding standard command table entries.  This
   ensures that the auxiliary table automatically picks up any changes to the
   standard commands that it modifies.


   Implementation notes:

    1. The RESET and BOOT commands are duplicated from the standard SCP command
       table so that entering "R" doesn't invoke the RUN command and entering
       "B" doesn't invoke the BREAK command.  This would otherwise occur because
       a VM-specific command table is searched before the standard command
       table.
*/

static CTAB aux_cmds [] = {
/*    Name        Action Routine  Argument   Help String */
/*    ----------  --------------  ---------  ----------- */
    { "RESET",    NULL,           0,         NULL        },
    { "BOOT",     NULL,           0,         NULL        },

    { "EXAMINE",  &hp_exdep_cmd,  0,         NULL        },
    { "IEXAMINE", &hp_exdep_cmd,  0,         NULL        },
    { "DEPOSIT",  &hp_exdep_cmd,  0,         NULL        },
    { "IDEPOSIT", &hp_exdep_cmd,  0,         NULL        },

    { "RUN",      &hp_run_cmd,    0,         NULL        },
    { "GO",       &hp_run_cmd,    0,         NULL        },

    { "BREAK",    &hp_brk_cmd,    0,         NULL        },
    { "NOBREAK",  &hp_brk_cmd,    0,         NULL        },

    { "LOAD",     &hp_load_cmd,   0,         NULL        },

    { NULL }
    };



/* System interface global SCP support routines */


/* Load and dump memory images from and to files.

   The LOAD and DUMP commands are intended to provide a basic method of loading
   and dumping programs into and from memory.  Typically, these commands operate
   on a simple, low-level format, e.g., a memory image.

   For this simulator, the LOAD command provides a way of installing bootstrap
   loaders into the last 64 words of memory.  The command format is:

     LOAD <image-filename> { <select-code> }

   ...where <image-filename> is an absolute binary file containing the loader,
   and <select-code> is an optional value from 10-77 octal used for configuring
   the loader's I/O instructions.  If the select code is omitted, the loader is
   used as-is.  When the command completes, the loader remains enabled so that
   it may be executed.  The loader should be protected if it will not be used
   immediately.

   The binary file must be targeted to addresses in the range x7700-x7777, where
   "x" is irrelevant.  The loaded program will be relocated to the last 64 words
   of memory, so the desired memory size must be set before issuing the LOAD
   command.  If the configuration select code is supplied, all I/O instructions
   in the program that reference select codes >= 10 octal will be changed by
   adding the supplied value minus 10 to the instruction.  The effect is that
   instructions that reference select code 10 + n will be changed to reference
   the supplied select code + n; this permits configuration of loaders that use
   two-card interfaces.

   The core-memory 21xx machines reserve the last 64 words in memory for a
   access-protected resident boot loader.  In hardware, the loaders could be
   changed only by entering the replacement loader via the front panel switch
   register.  In simulation, the LOAD command serves this purpose.

   The 1000-series machines used semiconductor memory, so the loaders were
   implemented in socketed ROMs that were read into the last 64 words of memory
   via the front-panel IBL (Initial Binary Loader) button.  For these machines,
   the LOAD command serves to install ROM images other than the ones included
   with the device simulators.  If the CPU is configured with more than 32K of
   memory, the loader is installed in the last 64 words of the 32K logical
   address space.


   The DUMP command writes the bootstrap loader currently residing in memory to
   an absolute binary file.  The command format is:

     DUMP <image-filename>

   The loader must be enabled before entering the DUMP command; if the loader
   is disabled, the output file will contain all zeros.  When the command
   completes, the loader remains enabled.  The resulting file may be used in a
   subsequent LOAD command to reload the bootstrap program.


   Implementation notes:

    1. Previous simulator versions did not restrict LOAD command addressing.
       However, the LOAD command is not a convenient replacement for the ATTACH
       PTR and BOOT PTR commands.  The bootstrap loaders clear the A and B
       registers at completion, and certain HP software depends on this behavior
       for proper operation.  The LOAD command alters nothing other than the
       memory occupied by the program, and its use as a general absolute binary
       paper tape loader would result in software failures.

    2. The absolute binary format is described in Appendix H of the RTE-IV
       Assembler Reference Manual.

    3. The LOADed absolute binary file may contain a leader of any length,
       including zero length (i.e., omitted).  The logical end of file occurs
       after reading ten null bytes or the physical EOF, whichever occurs first.

    4. The DUMP command writes the 64-word loader in two records of 57 and 7
       words, respectively, to conform with the format written by the HP
       Assembler.
*/

t_stat sim_load (FILE *fptr, CONST char *cptr, CONST char *fnam, int flag)
{
const int    reclen [2] = { TO_WORD (57, 0),            /* the two DUMP record length words */
                            TO_WORD (7, 0) };
const int    reccnt [2] = { 57, 7 };                    /* the two DUMP record word counts */
int          record, count, address, word, checksum;
t_stat       result;
int32        trailer = 1;                               /* > 0 while reading leader, < 0 while reading trailer */
HP_WORD      select_code = 0;                           /* select code to configure; 0 implies no configuration */
LOADER_ARRAY boot = {                                   /* an array of two BOOT_LOADER structures */
    { 000,       IBL_NA,  IBL_NA,  { 0 } },             /*   HP 21xx Loader */
    { IBL_START, IBL_DMA, IBL_FWA, { 0 } }              /*   HP 1000 Loader */
    };

if (flag == 0) {                                        /* if this is a LOAD command */
    if (*cptr != '\0') {                                /*   then if a parameter follows */
        select_code =                                   /*     then parse it as an octal number */
          (HP_WORD) get_uint (cptr, 8, SC_MAX, &result);

        if (result != SCPE_OK)                          /* if a parse error occurred */
            return result;                              /*   then report it */

        else if (select_code < SC_VAR)                  /* otherwise if the select code is invalid */
            return SCPE_ARG;                            /*   then report a bad argument */
        }

    while (TRUE) {                                      /* read absolute binary records from the file */
        do {                                            /* skip any blank leader or trailer present */
            count = fgetc (fptr);                       /* get the next byte from the tape */

            if (count == EOF)                           /* if an EOF occurred */
                if (trailer > 0)                        /*   then if we are reading the leader */
                    return SCPE_FMT;                    /*     then the tape format is bad */
                else                                    /*   otherwise we are reading the trailer */
                    trailer = 0;                        /*     and now we are done */

            else if (count == 0)                        /* otherwise if this is a null value */
                trailer = trailer + 1;                  /*   then increment the trailer count */
            }
        while (count == 0 && trailer != 0);             /* continue if a null was read or trailer is exhausted */


        if (trailer == 0)                               /* if the physical EOF was seen */
            break;                                      /*   then the binary read is complete */

        else if (fgetc (fptr) == EOF)                   /* otherwise discard the unused byte after the record count */
            return SCPE_FMT;                            /* if it is not there, then the tape format is bad */

        address = fgetword (fptr);                      /* get the record load address word */

        if (address == EOF)                             /* if the load address is not present */
            return SCPE_FMT;                            /*   then the tape format is bad */
        else                                            /* otherwise */
            checksum = address;                         /*   start the record checksum with the load address */

        if ((address & 0007777u) < 0007700u)            /* if the address does not fall at the end of a 4K block */
            return SCPE_NXM;                            /*   then report the address error */
        else                                            /* otherwise mask the address */
            address = address & IBL_MASK;               /*   to form an index into the loader array */


        while (count-- > 0) {                           /* read the data record */
            word = fgetword (fptr);                     /* get the next data word from the file */

            if (word == EOF)                            /* if the word is not present */
                return SCPE_FMT;                        /*   then the tape format is bad */

            else {                                                  /* otherwise */
                boot [0].loader [address]   = (MEMORY_WORD) word;   /*   save the data word in */
                boot [1].loader [address++] = (MEMORY_WORD) word;   /*     both loader arrays */
                checksum = checksum + word;                         /*       and include it in the record checksum */
                }
            }


        word = fgetword (fptr);                         /* read the record checksum word */

        if (word == EOF)                                /* if it is not present */
            return SCPE_FMT;                            /*   then the tape format is bad */

        else if (word != (int) (checksum & D16_MASK))   /* otherwise if the checksums do not match */
            return SCPE_CSUM;                           /*   then report a checksum error */

        else                                            /* otherwise the record is good */
            trailer = -10;                              /*   so prepare for a potential trailer */
        }                                               /*     and loop until all records are read */

    cpu_copy_loader (boot, select_code,                 /* install the loader */
                     IBL_S_NOCLEAR, IBL_S_NOSET);       /*   and configure the select code if requested */
    }


else {                                                  /* otherwise this is a DUMP command */
    address = mem_size - 1 & ~IBL_MASK & LA_MASK;       /* the loader occupies the last 64 words in memory */

    for (record = 0; record < 2; record++) {            /* write two absolute records */
        if (fputword (reclen [record], fptr) == EOF)    /*   starting with the record length; if it fails */
            return SCPE_IOERR;                          /*     then report an I/O error */

        if (fputword (address, fptr) == EOF)            /* write the starting address; if it fails */
            return SCPE_IOERR;                          /*   then report an I/O error */

        checksum = address;                             /* start the record checksum with the load address */

        for (count = 0; count < reccnt [record]; count++) { /* write a data record */
            word = mem_examine (address++);                 /* get the next data word from memory */

            if (fputword (word, fptr) == EOF)               /* write the data word; if it fails */
                return SCPE_IOERR;                          /*   then report an I/O error */
            else                                            /* otherwise */
                checksum = checksum + word;                 /*   include it in the record checksum */
            }                                               /*     and loop until all words are written */

        if (fputword (checksum & D16_MASK, fptr) == EOF)    /* write the checksum word; if it fails */
            return SCPE_IOERR;                              /*   then report an I/O error */
        }                                                   /* loop until both records are written */
    }

return SCPE_OK;
}


/* Print a value in symbolic format.

   This routine prints a data value in the format specified by the optional
   switches on the output stream provided.  On entry, "ofile" is the opened
   output stream, and the other parameters depend on the reason the routine was
   called, as follows:

     * To print the next instruction mnemonic when the simulator stops:
        - addr = the program counter
        - val  = a pointer to sim_eval [0]
        - uptr = NULL
        - sw   = "-M" | SIM_SW_STOP

     * To print the result of EXAMining a register with REG_VMIO or a user flag:
        - addr = the ORed register radix and user flags
        - val  = a pointer to a single t_value
        - uptr = NULL
        - sw   = the command line switches | SIM_SW_REG

     * To print the result of EXAMining a memory address:
        - addr = the memory address
        - val  = a pointer to sim_eval [0]
        - uptr = a pointer to the named unit
        - sw   = the command line switches

     * To print the result of EVALuating a symbol:
        - addr = the symbol index
        - val  = a pointer to sim_eval [addr]
        - uptr = a pointer to the default unit (cpu_unit)
        - sw   = the command line switches

   On exit, a status code is returned to the caller.  If the format requested is
   not supported, SCPE_ARG status is returned, which causes the caller to print
   the value in numeric format with the default radix.  Otherwise, SCPE_OK
   status is returned if a single-word value was consumed, or the negative
   number of extra words (beyond the first) consumed in printing the symbol is
   returned.  For example, printing a two-word symbol would return
   SCPE_OK_2_WORDS (= -1).

   The following symbolic modes are supported by including the indicated
   switches:

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

   In the absence of a mode switch, the value is displayed in a numeric format.

   When displaying in the instruction mnemonic form, an additional format switch
   may be specified to indicate the desired operand radix, as follows:

     Switch   Interpretation
     ------   -----------------------------------------
       -B     a binary value
       -O     an octal value
       -D     a decimal value
       -H     a hexadecimal value

   These switches (except for -B) may be used without a mode switch to display a
   numeric value in the specified radix.  To summarize, the valid switch
   combinations are:

     -C
     -M [ -A | -B | -O | -D | -H ]
     -A | -O | -D | -H

   When displaying mnemonics, operand values by default are displayed in a radix
   suitable to the type of the value.  Address values are displayed in the CPU's
   address radix, which is octal, and data values are displayed in the CPU's
   data radix, which defaults to octal but may be set to a different radix or
   overridden by a switch on the command line.


   Implementation notes:

    1. If we are being called as a result of a VM stop to display the next
       instruction to be executed, a check is made to see if an interrupt is
       pending and not deferred.  If so, then the interrupt source and the trap
       cell instruction are displayed as the next instruction to be executed,
       rather than the instruction at the current PC.

    2. The trap cell instruction is read by calling "mem_fast_read" directly
       into the "val" array (which points to the "sim_eval" array), rather than
       by setting the VAL_EMPTY flag and letting the "fprint_instruction"
       routine load any required operands, because the instruction must be read
       from the system map instead of the current map (which may be the user map
       until the interrupt is actually processed).

    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 the MPY opcode, the formatter will expect
       the operand address 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 "fprint_cpu" routine needs to know whether the "addr" parameter value
       represents a CPU memory address in order to interpret MRG instructions
       correctly.  It does for simulator stops and CPU memory examinations but
       not for register or device memory examinations and symbol evaluations.
       The symbol source is set to "CPU_Symbol" or "Device_Symbol",
       respectively, to reflect these conditions.

    5. We return SCPE_INVSW when multiple modes or formats are specified, but
       the callers do not act on this; they use the fallback formatter if any
       status error is returned.  We could work around this by printing "Invalid
       switch" to the console and returning SCPE_OK, but this does not stop
       IEXAMINE from prompting for the replacement value(s) or EXAMINE from
       printing a range.

    6. Radix switches and the -C switch are conceptually mutually exclusive.
       However, if we return an error when "format" is non-zero, then -C will be
       ignored, and the fallback formatter will use the radix switch.  The other
       choice is to process -C and ignore the radix switch; this is the option
       implemented.
*/

t_stat fprint_sym (FILE *ofile, t_addr addr, t_value *val, UNIT *uptr, int32 sw)
{
int32         formats, modes, i;
uint32        radix;
SYMBOL_SOURCE source;

if ((sw & (SIM_SW_REG | ALL_SWITCHES)) == SIM_SW_REG)   /* if we are formatting a register without overrides */
    if (addr & REG_A)                                   /*   then if the default format is character */
        sw |= A_SWITCH;                                 /*     then set the -A switch */

    else if (addr & REG_C)                              /*   otherwise if the default mode is string */
        sw |= C_SWITCH;                                 /*     then set the -C switch */

    else if (addr & REG_M)                              /*   otherwise if the default mode is mnemonic */
        sw |= M_SWITCH;                                 /*     then set the -M switch */

if ((sw & SYMBOLIC_SWITCHES) == 0)                      /* if there are no symbolic overrides */
    return SCPE_ARG;                                    /*   then return an error to use the standard formatter */


formats = sw & FORMAT_SWITCHES;                         /* separate the format switches */
modes   = sw & MODE_SWITCHES;                           /*   from the mode switches */

if (formats == A_SWITCH)                                /* if the -A switch is specified */
    radix = 256;                                        /*   then override the radix to character */

else if (formats == B_SWITCH)                           /* otherwise if the -B switch is specified */
    radix = 2;                                          /*   then override the radix to binary */

else if (formats == D_SWITCH)                           /* otherwise if the -D switch is specified */
    radix = 10;                                         /*   then override the radix to decimal */

else if (formats == H_SWITCH)                           /* otherwise if the -H switch is specified */
    radix = 16;                                         /*   then override the radix to hexadecimal */

else if (formats == O_SWITCH)                           /* otherwise if the -O switch is specified */
    radix = 8;                                          /*   then override the radix to octal */

else if (formats == 0)                                  /* otherwise if no format switch is specified */
    radix = 0;                                          /*   then indicate that the default radix is to be used */

else                                                    /* otherwise more than one format is specified */
    return SCPE_INVSW;                                  /*   so return an error */

if (modes == M_SWITCH) {                                /* if mnemonic mode is specified */
    if (sw & SIM_SW_STOP) {                             /*   then if this is a simulator stop */
        source = CPU_Symbol;                            /*     then report as a CPU symbol */

        if (cpu_pending_interrupt) {                    /* if a pending interrupt is present and not deferred */
            addr = cpu_pending_interrupt;               /*   then set the display address to the trap cell */

            for (i = 0; i < sim_emax; i++)              /* load the trap cell instruction */
                val [i] =                               /*   which might be multi-word (e.g., JLY) */
                  mem_fast_read ((HP_WORD) (cpu_pending_interrupt + i), System_Map);

            fprintf (ofile, "IAK %02o: ",               /* report that the interrupt will be acknowledged  */
                     cpu_pending_interrupt);
            }
        }

    else if (sw & SIM_SW_REG) {                         /* otherwise if a register value is being formatted */
        source = Device_Symbol;                         /*   then report it as a device symbol */

        memset (sim_eval, 0,                            /* clear the sim_eval array */
                MAX_INSTR_LENGTH * 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 */
        }

    else if (uptr == cpu_uptr)                          /* otherwise if access is to CPU memory */
        source = CPU_Symbol;                            /*   then report as a CPU symbol */

    else                                                /* otherwise access is to device memory */
        source = Device_Symbol;                         /*   so report it as a device symbol */

    return fprint_cpu (ofile, addr, val, radix, source);    /* format and print the value in mnemonic format */
    }

else if (modes == C_SWITCH) {                           /* otherwise if ASCII string mode is specified */
    fputs (fmt_char (UPPER_BYTE (val [0])), ofile);     /*   then format and print the upper byte */
    fputc (',', ofile);                                 /*     followed by a separator */
    fputs (fmt_char (LOWER_BYTE (val [0])), ofile);     /*       followed by the lower byte */
    return SCPE_OK;
    }

else if (modes == 0)                                    /* otherwise if no mode was specified */
    return fprint_value (ofile, val [0], radix,         /*   then format and print it with the specified radix */
                         DV_WIDTH, PV_RZRO);            /*     and data width */

else                                                    /* otherwise the modes conflict */
    return SCPE_INVSW;                                  /*   so return an error */
}


/* Parse a value in symbolic format.

   Print the data value in the format specified by the optional switches on the
   output stream supplied.  This routine is called to print:

   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:

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

   On entry, "cptr" points at the string to parse, "addr" is the register radix
   and flags, memory address, or 0 (respectively), "uptr" is NULL, a pointer to
   the named unit, or a pointer to the default unit (respectively), "val" is a
   pointer to an array of t_values of depth "sim_emax" representing the value(s)
   returned, and "sw" contains any switches passed on the command line.  "sw"
   also includes SIM_SW_REG for a register call.

   On exit, a status code is returned to the caller.  If the format requested is
   not supported or the parse failed, SCPE_ARG status is returned, which causes
   the caller to attempt to parse the value in numeric format.  Otherwise,
   SCPE_OK status is returned if the parse produced a single-word value, or the
   negative number of extra words (beyond the first) produced by parsing the
   symbol is returned.  For example, parsing a symbol that resulted in two words
   being stored (in val [0] and val [1]) would return SCPE_OK_2_WORDS (= -1).

   The following symbolic modes are supported by including the indicated
   switches:

     Switch   Interpretation
     ------   -----------------------------
       -C     a two-character packed string
       -M     a CPU instruction mnemonic

   When parsing in the instruction mnemonic form, an additional format switch
   may be specified to indicate the supplied operand radix, 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

   These switches (except for -B) may be used without a mode switch to parse a
   numeric value in the specified radix.  To summarize, the valid switch
   combinations are:

     -C
     -M [ -A | -B | -O | -D | -H ]
     -A | -O | -D | -H

   When entering machine instruction mnemonics, operand values are parsed in a
   radix suitable to the type of the value.  Address values are parsed in the
   CPU's address radix, which is octal, and data values are parsed in the CPU's
   data radix, which defaults to octal but may be set to a different radix or
   overridden by a switch on the command line.

   In the absence of switches, a leading ' implies "-A", a leading " 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.


   Implementation notes:

    1. 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.

    2. A hex value that is also a machine instruction mnemonic (e.g., CCE) 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 operand is parsed as a hex value.

    3. We return SCPE_INVSW when multiple modes or formats are specified, but
       the callers do not act on this; they use the fallback parser if any
       status error is returned.  We could work around this by printing "Invalid
       switch" to the console and returning SCPE_OK, but this does not stop
       IDEPOSIT from prompting for the replacement value(s) or DEPOSIT from
       printing the error for each address in a range.

    4. Radix switches and the -C switch are conceptually mutually exclusive.
       However, if we return an error when "format" is non-zero, then -C will be
       ignored, and the fallback formatter will use the radix switch.  The other
       option is to process -C and ignore the radix switch; this is the option
       implemented.
*/

t_stat parse_sym (CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw)
{
int32         formats, modes;
uint32        radix;
t_stat        status;
SYMBOL_SOURCE target;

if ((sw & (SIM_SW_REG | ALL_SWITCHES)) == SIM_SW_REG)   /* if we are parsing a register without overrides */
    if (addr & REG_A)                                   /*   then if the default format is character */
        sw |= A_SWITCH;                                 /*     then set the -A switch */

    else if (addr & REG_C)                              /*   otherwise if the default mode is string */
        sw |= C_SWITCH;                                 /*     then set the -C switch */

    else if (addr & REG_M)                              /*   otherwise if the default mode is mnemonic */
        sw |= M_SWITCH;                                 /*     then set the -M switch */


if ((sw & ALL_SWITCHES) == 0)                           /* if there are no default or explicit overrides */
    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 (isalpha (*cptr) || ispunct (*cptr))        /*   otherwise if an instruction mnemonic parse is implied */
        sw |= M_SWITCH;                                 /*     then set the -M switch */


if ((sw & SYMBOLIC_SWITCHES) == 0)                      /* if there are no symbolic overrides */
    return SCPE_ARG;                                    /*   then return an error to use the standard parser */


formats = sw & FORMAT_SWITCHES;                         /* separate the format switches */
modes   = sw & MODE_SWITCHES;                           /*   from the mode switches */

if (formats == A_SWITCH)                                /* if the -A switch is specified */
    radix = 256;                                        /*   then override the radix to character */

else if (formats == B_SWITCH)                           /* otherwise if the -B switch is specified */
    radix = 2;                                          /*   then override the radix to binary */

else if (formats == D_SWITCH)                           /* otherwise if the -D switch is specified */
    radix = 10;                                         /*   then override the radix to decimal */

else if (formats == H_SWITCH)                           /* otherwise if the -H switch is specified */
    radix = 16;                                         /*   then override the radix to hexadecimal */

else if (formats == O_SWITCH)                           /* otherwise if the -O switch is specified */
    radix = 8;                                          /*   then override the radix to octal */

else if (formats == 0)                                  /* otherwise if no format switch is specified */
    radix = 0;                                          /*   then indicate that the default radix is to be used */

else                                                    /* otherwise more than one format is specified */
    return SCPE_INVSW;                                  /*   so return an error */

if (modes == M_SWITCH) {                                /* if instruction mnemonic mode is specified */
    if (uptr == NULL || uptr == cpu_uptr)               /*   then if access is to a register or CPU memory */
        target = CPU_Symbol;                            /*     then report as a CPU symbol */
    else                                                /*   otherwise access is to device memory */
        target = Device_Symbol;                         /*     so report it as a device symbol */

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

else if (modes == C_SWITCH)                                 /* otherwise if string mode is specified */
    if (cptr [0] != '\0') {                                 /*   then if characters are present */
        val [0] = (t_value) TO_WORD (cptr [0], cptr [1]);   /*     then convert the character values */
        return SCPE_OK;                                     /*       and indicate success */
        }

    else                                                    /* otherwise */
        return SCPE_ARG;                                    /*   report that the line cannot be parsed */

else if (modes == 0) {                                      /* otherwise if no mode was specified */
    val [0] = parse_value (cptr, radix, DV_UMAX, &status);  /*   then parse using the specified radix */
    return status;                                          /*     and return the parsing status */
    }

else                                                    /* otherwise the modes conflict */
    return SCPE_INVSW;                                  /*   so return an error */
}


/* Attach a file for appending.

   This routine is called to attach a file to a specified unit and set the file
   position to the end for appending.  The routine returns the result of the
   operation, which will be SCPE_OK if the attach and optional EOF seek succeed,
   SCPE_IOERR if the attach succeeds but the EOF seek fails (in this case, the
   file will be detached before returning), or an appropriate status code if the
   attach fails.

   The standard "attach_unit" routine handles the "-N" switch to use an empty
   ("new") file.  If the file exists, "attach_unit" returns with the file
   positioned at the start.  For write-only devices, such as printers, this is
   almost never desirable, as it means that existing content may be only
   partially overwritten.  This routine provides the proper semantics for these
   devices, i.e., if the file exists, position it for writing to the end of the
   file.


   Implementation notes:

    1. "attach_unit" opens the file using one of the following modes:

         - "r" (reading) if the -R (read-only) switch or UNIT_RO is present or
           the file is marked read-only by the host file system

         - "w+" (truncate and update) if the -N (new) switch is present or the
           file does not exist

         - "r+" (update) otherwise

    2. Reopening with mode "a" or "a+" doesn't have the desired semantics.
       These modes permit writing only at the EOF, but we want to permit
       overwriting if the user explicitly sets POS.  The resulting "fseek" on
       resumption would be ignored if the mode is "a" or "a+".  Therefore, we
       accept mode "r+" and explicitly "fseek" to the end of file here.

    3. If we are called during a RESTORE command to reattach a file previously
       attached when the simulation was SAVEd, the file position is not altered.

    4. It is not necessary to report the device or unit if the 'fseek" fails, as
       this routine is called only is response to a user command that specifies
       the unit to attach.
*/

t_stat hp_attach (UNIT *uptr, CONST char *cptr)
{
t_stat result;

result = attach_unit (uptr, cptr);                      /* attach the specified image file */

if (result == SCPE_OK                                   /* if the attach was successful */
  && (sim_switches & SIM_SW_REST) == 0)                 /*   and we are not being called during a RESTORE command */
    if (fseek (uptr->fileref, 0, SEEK_END) == 0)        /*     then append by seeking to the end of the file */
        uptr->pos = (t_addr) ftell (uptr->fileref);     /*       and repositioning if the seek succeeded */

    else {                                              /* otherwise the seek failed */
        cprintf ("%s simulator seek error: %s\n",       /*   so report the error to the console */
                 sim_name, strerror (errno));

        detach_unit (uptr);                             /* detach the unit */
        result = SCPE_IOERR;                            /*   and report that the seek failed */
        }

return result;
}


/* Set a device select code.

   This validation routine is called to set a device's select code.  The "uptr"
   parameter points to the unit being configured, "count" gives the number of
   select codes to configure, "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.

   Some devices (e.g., the DP disc device) use multiple interface cards.  These
   are assigned sequential select codes.  For these devices, "desc" must point
   at the first element of an array of DIBs to be assigned, and "count" must be
   set to the number of elements.  For single-card devices, "desc" points at the
   DIB, and "count" is 1.


   Implementation notes:

    1. The legacy modifier "DEVNO" is supported in addition to the preferred
       "SC" modifier, but the corresponding MTAB entry is tagged with MTAB_NMO
       to prevent its display.  To differentiate between the two MTAB entries,
       which is necessary for display, the "DEVNO" entry's "count" parameter is
       complemented (the corresponding MTAB "match" field that supplies the
       "count" parameter to this routine is unsigned).
*/

t_stat hp_set_dib (UNIT *uptr, int32 count, CONST char *cptr, void *desc)
{
DIB    *dibptr = (DIB *) desc;                          /* a pointer to the associated DIB array */
t_stat status = SCPE_OK;
uint32 value;
int32  index;

if (cptr == NULL || *cptr == '\0')                      /* if the expected value is missing */
    status = SCPE_MISVAL;                               /*   then report the error */

else {                                                  /* otherwise a value is present */
    if (count < 0)                                      /* if the count has been complemented */
        count = ~count;                                 /*   then restore it to a positive value */

    value = (uint32) get_uint (cptr, SC_BASE,           /* parse the supplied device number */
                               SC_MAX + 1 - count, &status);

    if (status == SCPE_OK) {                            /* if it is valid */
        if (value < SC_VAR)                             /*   then if it is an internal select code */
            return SCPE_ARG;                            /*     then reject it */

        for (index = 0; index < count; index++, dibptr++)   /* loop through the associated interfaces */
            dibptr->select_code = value + index;            /*   and set the select codes in order */
        }
    }

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


/* Show a device select code.

   This display routine is called to show a device's select code.  The "st"
   parameter is the open output stream, "uptr" points to the unit being queried,
   "count" gives the number of additional select codes to display, and "desc"
   points to the DIB associated with the device.

   Some devices (e.g., the DP disc device) use multiple interface cards.  For
   these devices, "desc" must point at the first element of an array of DIBs to
   be assigned, and "count" must be set to the index of the last element (note:
   not the size) of the array.  The select codes will be printed together.

   For single-card devices, "desc" points at the DIB, and "count" is zero.


   Implementation notes:

    1. The legacy modifier "DEVNO" is supported in addition to the preferred
       "SC" modifier, but the corresponding MTAB entry is tagged with MTAB_NMO
       to prevent its display.  When displaying an MTAB_NMO entry, a newline is
       not automatically added to the end of the line, so we must add it here.
       To differentiate between the two MTAB entries, the "DEVNO" entry's
       "count" parameter is complemented (the corresponding MTAB "match" field
       that supplies the "count" parameter to this routine is unsigned).
*/

t_stat hp_show_dib (FILE *st, UNIT *uptr, int32 count, CONST void *desc)
{
const DIB *dibptr = (const DIB *) desc;                 /* a pointer to the associated DIB array */
int32     index, limit;

if (count < 0)                                          /* if the count has been complemented */
    limit = ~count;                                     /*   then restore it to a positive value */
else                                                    /* otherwise */
    limit = count;                                      /*   the value is already positive */

fprintf (st, "select code=%o", dibptr++->select_code);  /* print the interface's select code */

for (index = 2; index <= limit; index++, dibptr++)      /* if the device uses more than one interface */
    fprintf (st, "/%o", dibptr->select_code);           /*   then append the additional select code(s) */

if (count < 0)                                          /* if this is a DEVNO request (MTAB_NMO) */
    fputc ('\n', st);                                   /*   then append a newline */

return SCPE_OK;                                         /* return the display result */
}



/* System interface global utility routines */


/* Print a CPU instruction in symbolic format.

   This routine is called to format and print an instruction in mnemonic form.
   The "ofile" parameter is the opened output stream, "val" is a pointer to an
   array of t_values of depth "sim_emax" containing 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 "source" indicates the source of
   the instruction (device, CPU, or trace).

   The routine returns a status code to the caller.  SCPE_OK status is returned
   if a single-word instruction was printed, or the negative number of extra
   words (beyond the first) consumed in printing the instruction is returned.
   For example, printing a two-word instruction returns SCPE_OK_2_WORDS (= -1).
   If the supplied instruction is not a valid opcode, or is not valid for the
   current CPU feature set, SCPE_ARG is returned.

   The routine determines the instruction group that includes the supplied
   instruction value and calls the "fprint_instruction" routine with the
   associated opcode descriptor and table.  For MRG instructions, the
   instruction address is set to an invalid value if the source is not a CPU
   memory location.  This causes the instruction to be printed as a base-page or
   current-page offset, rather than as an absolute address.

   SRG instructions consist of two encoded micro-op fields, plus separate
   micro-ops for CLE and SLA/SLB.  These are printed separately.

   MAC instructions must be decoded into the indicated feature groups.  Some of
   these groups overlap and must be differentiated by the current CPU model or
   installed microcode options before the correct opcode descriptor and table
   may be determined.  Primary MAC decoding is bits 11-8, as follows (note that
   bit 10 = 0 for the MAC group):

     Bits
     11-8  Group
     ----  -----
     0000  EAU
     0001  EAU
     0010  EAU
     0011  UIG-1
     1000  EAU
     1001  EAU
     1010  UIG-0
     1011  UIG-1

   Bits 7-4 further decode the UIG instruction feature group.  UIG-0 pertains
   to the 2100 and 1000 M/E/F-Series, as follows:

     Instructions   IR 7-4  Option Name                  2100   1000-M  1000-E  1000-F
     -------------  ------  --------------------------  ------  ------  ------  ------
     105000-105362  00-17   2000 I/O Processor           opt      -       -       -
     105000-105137  00-05   Floating Point               opt     std     std     std
     105200-105237  10-11   Fast FORTRAN Processor       opt     opt     opt     std
     105240-105257    12    RTE-IVA/B Extended Memory     -       -      opt     opt
     105240-105257    12    RTE-6/VM Virtual Memory       -       -      opt     opt
     105300-105317    14    Distributed System            -       -      opt     opt
     105320-105337    15    Double Integer                -       -      opt      -
     105320-105337    15    Scientific Instruction Set    -       -       -      std
     105340-105357    16    RTE-6/VM Operating System     -       -      opt     opt

   UIG-1 pertains only to the 1000 M/E/F-Series machines, as follows:

     Instructions   IR 7-4  Option Name                  2100   1000-M  1000-E  1000-F
     -------------  ------  --------------------------  ------  ------  ------  ------
     10x400-10x437  00-01   2000 I/O Processor            -      opt     opt      -
     10x460-10x477    03    2000 I/O Processor            -      opt     opt      -
     10x460-10x477    03    Vector Instruction Set        -       -       -      opt
     10x520-10x537    05    Distributed System            -      opt      -       -
     10x600-10x617    10    SIGNAL/1000 Instruction Set   -       -       -      opt
     10x700-10x737  14-15   Dynamic Mapping System        -      opt     opt     std
     10x740-10x777  16-17   Extended Instruction Group    -      std     std     std


   Implementation notes:

    1. Indexing is employed where possible, but the 21xx/1000 instruction set is
       quite irregular, so conditionals and linear searches are needed to
       resolve many of the instructions.

    2. The no-operation opcode is handled as a special case within the SRG
       micro-ops table.
*/

t_stat fprint_cpu (FILE *ofile, t_addr addr, t_value *val, uint32 radix, SYMBOL_SOURCE source)
{
const  t_value opcode = val [0];                        /* the instruction opcode */
t_bool separator = FALSE;                               /* TRUE if a separator between multiple ops is needed */
t_stat status    = SCPE_ARG;                            /* initial return status is "invalid opcode" */

if (MRGOP (opcode)) {                                   /* if this is an MRG instruction */
    if (source == Device_Symbol)                        /*   then if the offset must be relative */
        addr = PA_MAX + 1;                              /*     then pass an invalid address */

    status = fprint_instruction (ofile, addr, val,      /* print the instruction */
                                  radix, mrg_desc, mrg_ops);
    }


else if (SRGOP (opcode)) {                              /* otherwise if this is an SRG instruction */
    if (opcode & SRG1_DE_MASK) {                        /*   then if the first shift is enabled */
        status = fprint_instruction (ofile, addr, val,  /*     then print the first shift operation */
                                     radix, srg1_desc, srg1_ops);

        separator = TRUE;                               /* we will need a separator */
        }

    if (opcode == SRG_NOP                               /* if this is a NOP */
      || (opcode & (SRG_CLE | SRG_SLx))) {              /*   or a micro-op is present */
        if (separator)                                  /*     then if a separator is needed */
            fputc (',', ofile);                         /*       then print it */

        status = fprint_instruction (ofile, addr, val,  /* print the micro-op(s) */
                                     radix, srg_udesc, srg_uops);

        separator = TRUE;                               /* we will need a separator */
        }

    if (opcode & SRG2_DE_MASK) {                        /* if the second shift is enabled */
        if (separator)                                  /*   then if a separator is needed */
            fputc (',', ofile);                         /*     then print it */

        status = fprint_instruction (ofile, addr, val,  /* print the second shift operation */
                                     radix, srg2_desc, srg2_ops);
        }
    }


else if (ASGOP (opcode))                                /* otherwise if this is an ASG instruction */
    status = fprint_instruction (ofile, addr, val,      /*   then print it */
                                 radix, asg_udesc, asg_uops);


else if (IOGOP (opcode))                                /* otherwise if this is an IOG instruction */
    status = fprint_instruction (ofile, addr, val,      /*   then print it */
                                 radix, iog_desc, iog_ops);

else {                                                  /* otherwise this is a MAC group instruction */
    if (source == CPU_Trace)                            /* if this is a CPU trace call */
        addr = addr | VAL_EMPTY;                        /*   then indicate that the value array has not been loaded */

    if (UIG_0_OP (opcode)) {                            /* if this is a UIG-0 instruction */
        status = fprint_instruction (ofile, addr, val,  /*   then try to print it as a 2100 IOP instruction */
                                     radix, iop_2100_desc, iop_2100_ops);

        if (status == SCPE_UNK)                         /* if it's not a 2100 IOP instruction */
            switch (UIG (opcode)) {                     /*   then dispatch on the feature group */

                case 000:                                           /* 105000-105017 */
                case 001:                                           /* 105020-105037 */
                case 002:                                           /* 105040-105057 */
                case 003:                                           /* 105060-105077 */
                case 004:                                           /* 105100-105117 */
                case 005:                                           /* 105120-105137 */
                    status = fprint_instruction (ofile, addr, val,  /* try to print as an FP instruction */
                                                 radix, fp_desc, fp_ops);

                    if (status == SCPE_UNK)                             /* if it's not an FP instruction */
                        status = fprint_instruction (ofile, addr, val,  /*   then try again as an FPP instruction */
                                                     radix, fpp_desc, fpp_ops);
                    break;


                case 010:                                               /* 105200-105217 */
                case 011:                                               /* 105220-105237 */
                    status = fprint_instruction (ofile, addr, val,      /* try to print as a 2100 FFP instruction */
                                                 radix, ffp_2100_desc, ffp_2100_ops);

                    if (status == SCPE_UNK)                             /* if it's not a 2100 FFP opcode */
                        status = fprint_instruction (ofile, addr, val,  /*   then try again as a 1000-F FFP opcode */
                                                     radix, ffp_f_desc, ffp_f_ops);

                    if (status == SCPE_UNK)                             /* if it's not a 1000-F FFP opcode */
                        status = fprint_instruction (ofile, addr, val,  /*   then try again as a 1000-E FFP opcode */
                                                     radix, ffp_e_desc, ffp_e_ops);

                    if (status == SCPE_UNK)                             /* if it's not a 1000-E FFP opcode */
                        status = fprint_instruction (ofile, addr, val,  /*   then try again as a 1000-M FFP opcode */
                                                     radix, ffp_m_desc, ffp_m_ops);
                    break;


                case 012:                                               /* 105240-105257 */
                    status = fprint_instruction (ofile, addr, val,      /* try to print as a VMA instruction */
                                                 radix, vma_desc, vma_ops);

                    if (status == SCPE_UNK)                             /* if it's not a VMA instruction */
                        status = fprint_instruction (ofile, addr, val,  /*   then try again as an EMA instruction */
                                                     radix, ema_desc, ema_ops);
                    break;


                case 014:                                               /* 105300-105317  Distributed System */
                    break;

                case 015:                                               /* 105320-105337 */
                    status = fprint_instruction (ofile, addr, val,      /* try to print as an SIS instruction */
                                                 radix, sis_desc, sis_ops);

                    if (status == SCPE_UNK)                             /* if it's not an SIS instruction */
                        status = fprint_instruction (ofile, addr, val,  /*   then try again as a DBI instruction */
                                                     radix, dbi_desc, dbi_ops);
                    break;


                case 016:                                               /* 105340-105357 */
                    if (opcode >= RTE_IRQ_RANGE                         /* if the opcode is in the interrupt use range */
                      && (addr & LA_MASK) >= SC_OPT                     /*   and it's located */
                      && (addr & LA_MASK) <= SC_MAX)                    /*     in a trap cell */
                        status = fprint_instruction (ofile, addr, val,  /*       then print as an IRQ instruction */
                                                     radix, trap_desc, trap_ops);
                    else                                                /* otherwise */
                        status = fprint_instruction (ofile, addr, val,  /*   print as an OS-assist instruction */
                                                     radix, os_desc, os_ops);
                    break;


                default:                                /* all other feature groups */
                    status = SCPE_ARG;                  /*   are unimplemented */
                }
        }


    else if (UIG_1_OP (opcode))                         /* otherwise if this is a UIG-1 instruction */
        switch (UIG (opcode)) {                         /*   then dispatch on the feature group */

            case 000:                                           /* 10x400-10x417 */
            case 001:                                           /* 10x420-10x437 */
                status = fprint_instruction (ofile, addr, val,  /* print as an IOP instruction */
                                             radix, iop1_1000_desc, iop1_1000_ops);
                break;

            case 003:                                           /* 10x460-10x477 */
                status = fprint_instruction (ofile, addr, val,  /* try to print as an IOP instruction */
                                             radix, iop2_1000_desc, iop2_1000_ops);

                if (status == SCPE_UNK) {                                           /* if it's not an IOP opcode */
                    if (source == CPU_Trace)                                        /*   then if this is a CPU trace call */
                        val [1] = mem_fast_read (addr + 1 & LA_MASK, Current_Map);  /*     then load the following word */

                    status = fprint_instruction (ofile, addr, val,  /* print as a VIS instruction */
                                                 radix, vis_desc, vis_ops);
                    }
                break;


            case 010:                                           /* 10x600-10x617 */
                status = fprint_instruction (ofile, addr, val,  /* print as a SIGNAL instruction */
                                             radix, sig_desc, sig_ops);
                break;


            case 014:
            case 015:                                           /* 10x700-10x737 */
                status = fprint_instruction (ofile, addr, val,  /* print as a DMS instruction */
                                             radix, dms_desc, dms_ops);
                break;


            case 016:
            case 017:                                           /* 10x740-10x777 */
                status = fprint_instruction (ofile, addr, val,  /* print as an EIG instruction */
                                             radix, eig_desc, eig_ops);
                break;


            default:                                    /* all other feature groups */
                status = SCPE_ARG;                      /*   are unimplemented */
            }


    else {                                              /* otherwise */
        status = fprint_instruction (ofile, addr, val,  /*   print as an EAG instruction */
                                     radix, eag_desc, eag_ops);

        if (status == SCPE_ARG)                             /* if it's not an EAG opcode but the EAU is present */
            status = fprint_instruction (ofile, addr, val,  /*   then try as a 1000-E/F extension instruction */
                                         radix, eag_ef_desc, eag_ef_ops);
        }
    }

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


/* 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 but
   within the full character range (i.e., less than 377 octal) are presented as
   escaped octal values.  Characters outside of the full character range are
   masked to the lower eight bits and printed as a plain octal value.


   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 <= 0037u)                                   /* if the value is an ASCII control character */
    return control [charval];                           /*   then return a readable representation */

else if (charval == 0177u)                              /* 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 > D8_UMAX)                                  /* if the value is beyond the 8-bit character range */
        freeptr = freeptr + sprintf (freeptr, "%03o",       /*   then format the lower byte as octal */
                                     LOWER_BYTE (charval)); /*     and update the free pointer */

    else if (charval > 0177u)                               /* otherwise if the value is beyond the printable range */
        freeptr = freeptr + sprintf (freeptr, "\\%03o",     /*   then format as an escaped octal value */
                                     charval);              /*     and update the free pointer */

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

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


/* Format a set of named bits.

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

     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 14-12 of a word are valid,
   then the name string array would have three entries.  If bits 14-12 and 10
   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
   14; if the direction is lsb_first, then the first name is for bit 12.

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


/* Initialize 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
   during the CPU's I/O initialization for use in aligning the trace statements.
*/

void hp_initialize_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;
}


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


   Implementation notes:

    1. ISO C99 allows assignment expressions as the bounds for array
       declarators.  VC++ 2008 requires constant expressions.  To accommodate
       the latter, we must allocate "sufficiently large" arrays for the flag
       name and format, rather than arrays of the exact size required by the
       call parameters.
*/

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

void hp_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 */

                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;
}


/* Make a pair of devices consistent */

void hp_enbdis_pair (DEVICE *ccptr, DEVICE *dcptr)
{
if (ccptr->flags & DEV_DIS)
    dcptr->flags |= DEV_DIS;
else
    dcptr->flags &= ~DEV_DIS;

return;
}



/* System interface local SCP support routines */


/* One-time initialization.

   This routine is called once by the SCP startup code.  It fills in the
   auxiliary command table from the corresponding system command table entries,
   sets up the VM-specific routine pointers, and registers the supported
   breakpoint types.
*/

void hp_one_time_init (void)
{
CTAB *systab, *auxtab = aux_cmds;
static int inited = 0;

if (inited == 1)    /* Be sure to only do these things once */
    return;
inited = 1;

sim_vm_release = hp_vm_release;
sim_vm_release_message = hp_vm_release_message;

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

    auxtab++;                                           /* point at the next table entry */
    }

sim_vm_cmd            = aux_cmds;                       /* set up the auxiliary command table */
sim_vm_fprint_stopped = &fprint_stopped;                /* set up the simulation-stop printer */
sim_vm_fprint_addr    = &fprint_addr;                   /* set up the address printer */
sim_vm_parse_addr     = &parse_addr;                    /* set up the address parser */
sim_vm_post           = &cpu_post_cmd;                  /* set up the command post-processor */

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

cpu_uptr = cpu_dev.units;                               /* set up the pointer to the CPU unit */

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 (CLA)"
     SCPE_STEP prints "Step expired, P: 24713 (CLA)"

   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_HALT   prints "Programmed halt, T: 102077 (HLT 77), P: 24713 (CLA)"
     STOP_NOTAPE prints "Tape not loaded in the <dev> device, P: 24713 (CLA)"
     STOP_EOT    prints "End of tape on the <dev> device, P: 24713 (CLA)"
     STOP_NOCONN prints "Cable not connected to the <dev> device, P: 24713 (CLA)"

   The HP 21xx/1000 halt instruction opcode includes select code and device flag
   hold/clear bit fields.  In practice, these are not used to affect the device
   interface; rather, they communicate to the operator the significance of the
   particular halt encountered.

   Under simulation, the halt opcode must be communicated to the user as part of
   the stop message.  To so do, we define a sim_vm_fprint_stopped handler that
   is called for all VM stops.  When called for a STOP_HALT, the halt message
   has been printed, and we add the opcode value in the T register before
   returning TRUE, so that SCP will add the program counter value.

   For unreported I/O error stops, the message must include the device name, so
   the user will know how to correct the error.  For these stops, the global
   variable "cpu_ioerr_uptr" will point at the unit that encountered the error.


   Implementation notes:

    1. Normally, the "sim_eval" global value array either is preloaded with the
       instruction and its operand addresses, or the first element is set to the
       instruction and the address parameter includes the VAL_EMPTY flag to
       indicate that the operands have not yet been loaded.  In the STOP_HALT
       case, the instruction is always a HLT, which is a single-word
       instruction.  Therefore, the VAL_EMPTY flag will not be checked and need
       not be included.
*/

static t_bool fprint_stopped (FILE *st, t_stat reason)
{
DEVICE *dptr;

if (reason == STOP_HALT) {                              /* if this is a halt instruction stop */
    sim_eval [0] = (t_value) TR;                        /*   then save the opcode for display */

    fprintf (st, ", T: %06o (", TR);                    /* print the T register value */
    fprint_cpu (st, MR, sim_eval, 0, CPU_Symbol);       /*   then format and print the halt mnemonic */
    fputc (')', st);                                    /*     and finally close the parenthesis */
    }

else if (cpu_ioerr_uptr) {                              /* otherwise if this is an I/O error stop */
    dptr = find_dev_from_unit (cpu_ioerr_uptr);         /*   then get the device pointer from the unit */

    if (dptr)                                               /* if the search succeeded */
        fprintf (st, " the %s device", sim_dname (dptr));   /*   then report the device */
    else                                                    /* otherwise */
        fputs (" an unknown device", st);                   /*   report that the device is unknown */
    }

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 <page>.<offset> form for CPU
   addresses > 32K and as a scalar value for CPU addresses <= 32K and all other
   devices.
*/

static void fprint_addr (FILE *st, DEVICE *dptr, t_addr addr)
{
uint32 page, offset;

if (dptr == &cpu_dev && addr > LA_MAX) {                /* if a CPU address is outside of the logical address space */
    page = P_PAGE (addr);                               /*   then separate the physical page and offset */
    offset = OFFSET (addr);                             /*     from the linear address */

    fprint_val (st, page, dptr->aradix, PP_WIDTH, PV_RZRO);     /* print the physical page address */
    fputc ('.', st);                                            /*   followed by a period */
    fprint_val (st, offset, dptr->aradix, OF_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 1000 divides memory into 1K-word pages within a 32K logical address
   space.  With memory expansion disabled, only the first 32 pages are
   accessible.  With memory expansion enabled, memory maps are used to translate
   logical to physical addresses.  Each translation uses one of four possible
   maps.  Program accesses use the system map or the user map, depending on
   which one is designated the "current map."  DCPC accesses use the port A or
   port B map, depending on whether channel 1 or channel 2, respectively, is
   performing the access.  Each map translates the 32 logical pages to 32 of the
   1024 physical pages.

   The HP 2114, 2115, 2116, and 2100 provide up to 32 pages of memory.  Memory
   expansion is not provided with these machines.

   The simulator supports only linear addresses for all devices other than the
   CPU.  For the CPU, two forms of address entries are allowed:

     - a logical address consisting of a 15-bit offset within the 32K logical
       address space (e.g., 77777).

     - a physical address consisting of a 10-bit page number and a 10-bit
       offset within the page, separated by a period (e.g., 1777.1777)

   Command line switches modify the interpretation of logical addresses as
   follows:

     Switch   Interpretation
     ------   --------------------------------------------------
       -N     Use no mapping
       -S     If memory expansion is enabled, use the system map
       -U     If memory expansion is enabled, use the user map
       -P     If memory expansion is enabled, use the port A map
       -Q     If memory expansion is enabled, use the port B map

   If no switch is specified, the address is interpreted using the current map
   if memory expansion is enabled; otherwise, the address is not mapped.  If the
   current or specified map is used, then the address must lie within the 32K
   logical address space.

   The "parse_physical" global specifies whether or not a physical address is
   permitted.  Addresses used by the RUN, GO, BREAK, and NOBREAK commands must
   resolve to logical addresses, whereas EXAMINE and DEPOSIT commands allow both
   physical and logical address specifications.
*/

static t_addr parse_addr (DEVICE *dptr, CONST char *cptr, CONST char **tptr)
{
CONST char *sptr;
t_addr     page;
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 */

address = strtotv (cptr, tptr, dptr->aradix);           /* parse the address */

if (cptr != *tptr)                                      /* if the parse succeeded */
    if (**tptr == '.')                                  /*   then if this a paged address */
        if (address > PP_MAX)                           /*     then if the physical page 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 */
            page = address;                                 /* save the first part as the bank address */
            address = strtotv (sptr, tptr, dptr->aradix);   /* parse the offset */

            if (address > OF_MAX)                       /* if the offset is too large */
                *tptr = cptr;                           /*   then report a parse error */
            else                                        /* otherwise it is in range */
                address = TO_PA (page, address);        /*   so form the linear address */
            }

    else if (address > LA_MAX)                          /* otherwise if the non-paged offset is too large */
        *tptr = cptr;                                   /*   then report a parse error */

if (parse_physical == FALSE                             /* if only logical addresses are permitted */
  && address > LA_MAX)                                  /*   and the parsed address is out of range */
    *tptr = cptr;                                       /*     then report a parse error */

return address;                                         /* return the linear address */
}


/* Execute the EXAMINE, DEPOSIT, IEXAMINE, and IDEPOSIT commands.

   These commands are intercepted to configure address parsing.  The following
   address forms are valid:

     EXAMINE <page>.<offset>
     EXAMINE <logical-address>

   This routine configures the address parser and calls the standard command
   handler.
*/

static t_stat hp_exdep_cmd (int32 arg, CONST char *buf)
{
parse_physical = TRUE;                                  /* allow the <page.<offset> address form */

return exdep_cmd (arg, buf);                            /* return the result of the standard handler */
}


/* Execute the RUN and GO commands.

   These commands are intercepted to configure address parsing.  The following
   address form is valid:

     RUN { <logical-address> }
     GO  { <logical-address> }

   If no argument is specified, the breakpoint address defaults to the current
   value of P.  This routine configures the address parser and calls the
   standard command handler.
*/

static t_stat hp_run_cmd (int32 arg, CONST char *buf)
{
parse_physical = FALSE;                                 /* allow the <logical-address> address form only */

return run_cmd (arg, 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 <logical-address>

   If no argument is specified, the breakpoint address defaults to the current
   value of P.  This routine configures the address parser and calls the
   standard command handler.
*/

static t_stat hp_brk_cmd (int32 arg, CONST char *buf)
{
parse_physical = FALSE;                                 /* allow the <logical-address> address form only */

return brk_cmd (arg, buf);                              /* return the result of the standard handler */
}


/* Execute the LOAD command.

   This command is intercepted to permit a device boot routine to be loaded
   into memory.  The following command forms are valid:

     LOAD <dev>
     LOAD <filename> [ <select-code> ]

   If the first form is used, and the device name is valid and bootable, the
   corresponding boot loader is copied into memory in the highest 64 locations
   of the logical address space.  Upon return, the loader will have been
   configured for the device's select code or for the device and paper tape
   reader select codes, in the case of a dual-use 21xx boot loader, and the P
   register will have been set to point at the start of the loader.  The loader
   then may be executed by a RUN command.  If the device name is valid, but the
   device is not bootable, or no boot loader exists for the current CPU
   configuration, the command is rejected.

   If the second form is used, the file containing a loader in absolute binary
   form is read into memory and configured as above.  See the "sim_load"
   comments for details of this operation.


   Implementation notes:

    1. The "find_dev" routine requires that the device name be in upper case to
       match.  The "get_glyph" routine performs case-shifting on the input
       buffer.
*/

static t_stat hp_load_cmd (int32 arg, CONST char *buf)
{
CONST char *cptr;
char cbuf [CBUFSIZE];
DEVICE *dptr = NULL;

GET_SWITCHES (buf);                                     /* parse any switches present */

cptr = get_glyph (buf, cbuf, '\0');                     /* parse a potential device name */

if (cbuf [0] != '\0') {                                 /* if the name is present */
    dptr = find_dev (cbuf);                             /*   then see if it matches a device */

    if (dptr != NULL)                                   /* if it does */
        if (dptr->boot == NULL)                         /*   then if the device is not bootable */
            return SCPE_NOFNC;                          /*     then report "Command not allowed" */
        else if (*cptr != '\0')                         /*   otherwise if more characters follow */
            return SCPE_2MARG;                          /*     then report "Too many arguments" */
        else                                            /*   otherwise the device name stands alone */
            return dptr->boot (0, dptr);                /*     so load the corresponding boot loader */
    }

return load_cmd (arg, buf);                             /* if it's not a device name, then try loading a file */
}



/* System interface local utility routines */


/* Print a numeric value in a given radix.

   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.

   On entry, the "ofile" parameter is the opened output stream, "val" is the
   value to print, "radix" is the desired print radix, "width" is the number of
   significant bits in the value, and "format" is a format specifier (PV_RZRO,
   PV_RSPC, or PV_LEFT).  On exit, the routine returns SCPE_OK if the value was
   printed successfully, or SCPE_ARG if the value could not be printed.
*/

static t_stat fprint_value (FILE *ofile, t_value val, uint32 radix, uint32 width, uint32 format)
{
if (radix == 256)                                       /* if ASCII character display is requested */
    if (val <= D8_SMAX) {                               /*   then if the value is a single character */
        fputs (fmt_char ((uint32) val), ofile);         /*     then format and print it */
        return SCPE_OK;                                 /*       and report success */
        }

    else                                                /* otherwise */
        return SCPE_ARG;                                /*   report that it cannot be displayed */

else                                                        /* otherwise format and print the value */
    return fprint_val (ofile, val, radix, width, format);   /*   using the supplied radix and format */
}


/* Print a CPU instruction in symbolic format.

   This routine is called to decode and print a CPU instruction mnemonic and any
   associated operand(s).  On entry, "ofile" is the opened output stream, "addr"
   is the memory address location of the instruction, "val" is a pointer to an
   array of t_values of depth "sim_emax" containing the instruction to be
   printed, "radix" is zero to use the default radix for the operand(s) or a
   value indicating a requested radix override, "op_desc" is a structure that
   describes the characteristics of the instruction opcode, and "ops" is the
   table of opcodes that pertain to the class to which the supplied instruction
   belongs.

   On exit, a status code is returned to the caller.  If the instruction is
   provided by a firmware option that is not installed, SCPE_UNK status is
   returned; this permits the caller to try a different opcode table if the
   opcode is in a shared address space (e.g., the EMA and VMA firmware).  If the
   instruction is not present in the supplied opcode table, SCPE_ARG status is
   returned, which causes the caller to print the instruction in numeric format
   with the default radix.  Otherwise, SCPE_OK status is returned if a
   single-word instruction was printed, or the negative number of extra words
   (beyond the first) consumed in printing the instruction is returned.  For
   example, printing a two-word instruction returns SCPE_OK_2_WORDS (= -1).

   The "addr" parameter is used in two cases: to supply the operand address(es)
   that follow the instruction when the "val" array has not been fully
   populated, and to supply the current page for memory reference instructions
   that have the C bit set.

   If the routine is called to examine a memory location, the "val" array will
   be fully populated with the instruction and the values that follow.  However,
   if the routine is called by an instruction trace, only the first word is set;
   this is to avoid the overhead of loading extra operand words for instructions
   that do not have operands (the vast majority).  In this case, the "addr"
   parameter will contain the address of the instruction word ORed with the
   VAL_EMPTY flag.  Once the instruction is decoded, the routine knows the
   number of operand words required, and these are loaded before printing the
   operands.

   When printing a current-page memory reference instruction residing in CPU
   memory, the instruction word supplies the 10-bit offset from the page
   containing the instruction.  This is merged with the page number from the
   "addr" parameter to form the operand address.

   However, if the instruction is not in memory (e.g., it resides in a disc data
   buffer or device register), then the final load address is indeterminate.  In
   this case, the "addr" parameter is set to a value greater than the maximum
   possible memory address as an indication that only the current-page offset is
   valid.

   All machine instruction opcodes are single-word, with the exception of the
   vector arithmetic instructions in the Vector Instruction Set; these use
   two-word opcodes.  If the routine is entered to print a VIS instruction, "val
   [0]" and "val [1]" must both be set, even if the VAL_EMPTY flag is set.

   Upon entry, the required feature set from the opcode description is checked
   against the current CPU feature set.  If the required option is not
   installed, the routine exits with SCPE_UNK.  Otherwise, the instruction is
   masked and shifted to obtain the primary index into the opcode table.  For
   instructions that use the A/B-selector bit (bit 11), the index is shifted to
   the second half of the table if the bit is set.

   If the primary entry exists, then the opcode mnemonic is obtained from the
   entry and printed.  Otherwise, the secondary table entries must be searched
   linearly.  Each entry is compared to the instruction with the operand bits
   masked off; if a match occurs, then the opcode mnemonic is checked.  If it is
   empty, then this is a two-word instruction whose second word is invalid.  In
   this case, the routine prints the two words in numeric format, separated by a
   comma, and returns success to indicate that the instruction was printed.
   Otherwise the mnemonic is present, so it is printed.  If the opcode
   descriptor indicates that only a single match is permitted, the loop is
   exited to print the operand(s), if any.  Otherwise, multiple matches are
   allowed (e.g., for SRG instructions), so the secondary entry search is
   continued until the end of the table.

   If the instruction does not match either a primary or a secondary entry, the
   routine exits with SCPE_ARG status.  The caller will then print the value in
   numeric form.

   The type and count of operand(s), if any, are indicated by the "type" and
   "count" values in the matched opcode table entry.  The entry also indicates
   which operand(s) are addresses and which are data values.

   If operands are needed but not present in the "val" array, they are loaded
   now using the current DMS map.  The operand type dispatches to one of several
   handlers that obtain the operand(s) from the instruction.  Operands that are
   extracted from the instruction are placed in "val [1]" for printing.  Each
   operand is printed using the address radix for addresses and the supplied
   data radix for data.

   After printing the operand(s), the routine returns the number of words
   consumed.


   Implementation notes:

    1. If the opcode descriptor mask is zero, then the opcode table contains
       only secondary entries.  The descriptor shift value then determines
       whether a single match is allowed (zero) or multiple matches are allowed
       (non-zero).

    2. If the opcode descriptor A/B-register selector indicates that bit 11 is
       decoded, then the opcode primary table will be twice the size implied by
       the opcode mask width.  The first half of the table decodes instructions
       with bit 11 equal to zero; the second half decodes instruction with bit
       11 equal to 1.

    3. Only the SRG and ASG instructions contain multiple micro-opcodes, and
       none of these take operands.
*/

static t_stat fprint_instruction (FILE *ofile, t_addr addr, t_value *val, uint32 radix,
                                  const OP_DESC op_desc, const OP_TABLE ops)
{
OP_TYPE    op_type;
uint32     op_index, op_size, op_count, op_radix, op_address_set;
t_value    instruction, op_value;
t_stat     status;
const char *prefix   = NULL;                            /* label to print before the operand */
t_bool     clear     = FALSE;                           /* TRUE if the instruction contains a CLF micro-op */
t_bool     separator = FALSE;                           /* TRUE if a separator between multiple ops is needed */
uint32     op_start  = 1;                               /* the "val" array index of the first operand */

if (!(cpu_configuration & op_desc.feature & CPU_OPTION_MASK /* if the required feature set is not enabled */
  && cpu_configuration & op_desc.feature & CPU_MODEL_MASK)) /*   for the current CPU configuration */
    return SCPE_UNK;                                        /*     then we cannot decode the instruction */

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

op_size = (op_desc.mask >> op_desc.shift)               /* determine the size of the primary table part */
            + (op_desc.mask != 0);                      /*   if it is present */

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

if (op_desc.ab_selector) {                              /* if the A/B-register selector is significant */
    if (op_desc.ab_selector & instruction)              /*   then if the A/B-register selector bit is set */
        op_index = op_index + op_size;                  /*     then use the second half of the table */

    op_size = op_size * 2;                              /* the primary table is twice the indicated size */
    }

if (op_desc.mask && 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 = op_size;                            /*   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                                  /*   matches the instruction */
          & ops [op_index].op_bits                      /*     masked to the significant opcode bits */
          & op_props [ops [op_index].type].mask))       /*       and with the operand bits masked off */
            if (ops [op_index].mnemonic [0]) {          /*         then if the entry is defined */
                if (separator)                          /*           then if a separator is needed */
                    fputc (',', ofile);                 /*             then print it */

                fputs (ops [op_index].mnemonic, ofile); /* print the opcode mnemonic */

                if (op_desc.mask == OP_LINEAR           /* if multiple matches */
                  && op_desc.shift == OP_MULTIPLE)      /*   are allowed */
                    separator = TRUE;                   /*     then separators will be needed between mnemonics */

                else                                    /* otherwise */
                    break;                              /*   the search terminates on the first match */
                }

            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);       /*     with the default radix */

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

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

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

    if (separator)                                      /* if one or more micro-ops was found */
        return SCPE_OK;                                 /*   then return, as there are no operands */

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

op_type = ops [op_index].type;                          /* get the type of the instruction operand(s) */

op_value = val [0] & ~op_props [op_type].mask;          /* mask the first instruction word to the operand value */

op_count = (uint32) op_props [op_type].count;           /* get the number of operands */
status = (t_stat) - op_props [op_type].count;           /*   and set the initial number of words consumed */

op_address_set = op_props [op_type].address_set;        /* get the address/data-selector bit set */

op_radix = (radix ? radix : cpu_dev.dradix);            /* assume that the (only) operand is data */

if (ops [op_index].op_bits > D16_MASK) {                /* if this is a two-word instruction */
    op_start = 2;                                       /*   then the operands start after the second word */
    op_count = op_count + 1;                            /*     and extend for an extra word */
    status = status - 1;                                /*       and consume an additional word */
    }

if (op_count > 0 && (addr & VAL_EMPTY)) {               /* if operand words are needed but not loaded */
    addr = addr & LA_MASK;                              /*   then restore the logical address */

    for (op_index = op_start; op_index <= op_count; op_index++)                     /* starting with the first operand */
        val [op_index] = mem_fast_read ((HP_WORD) (addr + op_index), Current_Map);  /*   load the operands from memory */
    }


switch (op_type) {                                      /* dispatch by the operand type */

    /* no operand */

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


    /* MRG page bit 10, offset 0000-1777 octal, indirect bit 15 */

    case opMPOI:
        if (addr > LA_MAX)                              /* if the instruction location is indeterminate */
            if (instruction & IR_CP)                    /*   then if the current-page bit is set */
                prefix = " C ";                         /*     then prefix the offset with "C" */
            else                                        /*   otherwise it's a base-page reference */
                prefix = " Z ";                         /*     so prefix the offset with "Z" */

        else {                                          /* otherwise the address is valid */
            prefix = " ";                               /*   so use a blank separator */

            if (instruction & IR_CP)                    /* if the current-page bit is set */
                op_value |= addr & MR_PAGE;             /*   then merge the offset with the current page address */
            }

        val [1] = op_value;                             /* set the operand value */
        op_count = 1;                                   /*   and print only one operand */
        break;


    /* IOG hold/clear bit 9 */

    case opHC:
        if (op_value)                                   /* if the clear-flag bit is set */
            fputs (" C", ofile);                        /*   then add the "C" to the mnemonic */
        break;


    /* IOG select code range 00-77 octal, hold/clear bit 9 */
    /* IOG optional select code range 00-77 octal, hold/clear bit 9 */

    case opSCHC:
    case opSCOHC:
        clear = (instruction & IR_HCF);                 /* set TRUE if the clear-flag bit is set */

    /* fall through into the opSC case */

    /* IOG select code range 00-77 octal */

    case opSC:
        prefix = " ";                                   /* add a separator */
        val [1] = op_value;                             /*   and set the operand value */
        op_count = 1;                                   /*     and print only one operand */
        break;


    /* EAU shift/rotate count range 1-16 */

    case opShift:
        prefix = " ";                                   /* add a separator */
        op_radix = (radix ? radix : 10);                /*   and default the shift counts to decimal */

        if (op_value == 0)                              /* if the operand is zero */
            val [1] = 16;                               /*   then the shift count is 16 */
        else                                            /* otherwise */
            val [1] = op_value;                         /*   then shift count is the operand value */

        op_count = 1;                                   /* print only one operand */
        break;


    /* IOP index negative offset range 1-20 octal */

    case opIOPON:
        prefix = " -";                                  /* prefix the operand with the sign */
        val [1] = 16 - op_value;                        /*   and set the (absolute) offset */

        op_radix = (radix ? radix : 10);                /* default the offset to decimal */
        op_count = 1;                                   /*   and print only one operand */
        break;


    /* IOP index positive offset range 0-17 octal */

    case opIOPOP:
        prefix = " ";                                   /* add a separator */
        val [1] = op_value;                             /*   and set the (positive) offset */

        op_radix = (radix ? radix : 10);                /* default the offset to decimal */
        op_count = 1;                                   /*   and print only one operand */
        break;


    /* IOP index offset range 0-37 octal (+20 bias) */

    case opIOPO:
        if (op_value >= 020) {                          /* if the operand is positive */
            prefix = " ";                               /*   then omit the sign */
            val [1] = op_value - 020;                   /*     and remove the bias */
            }

        else {                                          /* otherwise the operand is negative */
            prefix = " -";                              /*   so prefix a minus sign */
            val [1] = 020 - op_value;                   /*     and remove the bias to get the absolute value */
            }

        op_radix = (radix ? radix : 10);                /* default the offset to decimal */
        op_count = 1;                                   /*   and print only one operand */
        break;


    /* UIG zero word, four (indirect) memory addresses */
    /* UIG zero word, five (indirect) memory addresses */
    /* UIG zero word, six (indirect) memory addresses */
    /* UIG zero word, eight (indirect) memory addresses */

    case opZA4:
    case opZA5:
    case opZA6:
    case opZA8:
        prefix = " ";                                   /* add a separator */
        op_start = 2;                                   /*   and skip the all-zeros word */
        break;


    /* One memory address range 00000-77777 octal, zero word, indirect bit 15 */

    case opMA1ZI:
        prefix = " ";                                   /* add a separator */
        op_count = 1;                                   /*   and print only one operand */
        break;


    /* One to seven memory addresses range 00000-77777 octal, indirect bit 15 */

    case opMA1I:
    case opMA2I:
    case opMA3I:
    case opMA4I:
    case opMA5I:
    case opMA6I:
    case opMA7I:

    /* UIG one data value */
    /* UIG two data values */
    /* UIG one (indirect) memory address, one data value */
    /* UIG two data values, one (indirect) memory address */
    /* UIG one data value, five (indirect) memory addresses */

    case opV1:
    case opV2:
    case opA1V1:
    case opV2A1:
    case opV1A5:
        prefix = " ";                                   /* add a separator */
        break;
    }                                                   /* end of the operand type dispatch */


if (prefix)                                             /* if an operand is present */
    for (op_index = op_start;                           /*   then format the operands */
      op_index <= op_count;                             /*     starting with the first operand word */
      op_index++) {
        fputs (prefix, ofile);                          /* print the operand prefix or separator */

        if (op_address_set & 1) {                           /* if this operand is an address */
            fprint_val (ofile, val [op_index] & LA_MASK,    /*   then print the logical address */
                        cpu_dev.aradix, LA_WIDTH, PV_LEFT); /*     using the address radix */

            if (val [op_index] & IR_IND)                /* add an indirect indicator */
                fputs (",I", ofile);                    /*   if specified by the operand */
            }

        else                                            /* otherwise it's a data value */
            fprint_value (ofile, val [op_index],        /*  so print the full value */
                          op_radix, DV_WIDTH, PV_LEFT); /*    using the specified radix */

        op_address_set = op_address_set >> 1;           /* shift the next address/data flag bit into place */
        }                                               /*   and loop until all operands are printed */

if (clear)                                              /* add a clear-flag indicator */
    fputs (",C", ofile);                                /*   if specified by the instruction */

return status;                                          /* return the number of words consumed */
}


/* Parse an address operand.

   Address operands of the form:

     <octal value>[,I]

  ...are parsed.  On entry, "cptr" points at the first character of the octal
  value, and "status" points at a variable to hold the return status.  If the
  parse succeeds, the address with the optional indirect bit (bit 15) is
  returned, and the status is set to SCPE_OK.  If the parse fails, the value is
  out of the logical range, or extraneous characters follow the operand,
  then zero is returned, and the status is set to SCPE_ARG.


  Implementation notes:

   1. The string that "cptr" points to is trimmed of trailing spaces before this
      routine is called.  Therefore, the trailing NUL test is sufficient to
      determine if there are more characters in the input buffer.
*/


static t_value parse_address (CONST char *cptr, t_stat *status)
{
CONST char *iptr;
t_value address;

address = strtotv (cptr, &iptr, cpu_dev.aradix);        /* parse the address */

if (cptr == iptr || address > LA_MAX) {                 /* if a parse error occurred or the value is too big */
    *status = SCPE_ARG;                                 /*   then return an invalid argument error */
    return 0;
    }

else if (*iptr == '\0') {                               /* otherwise if there is no indirect indicator */
    *status = SCPE_OK;                                  /*   then the parse succeeds */
    return address;                                     /*     and return the address */
    }

else if (strcmp (iptr, ",I") == 0) {                    /* otherwise if there is an indirect indicator */
    *status = SCPE_OK;                                  /*   then the parse succeeds */
    return address | IR_IND;                            /*     and return the address with the indirect bit set */
    }

else {                                                  /* otherwise there are extraneous characters following */
    *status = SCPE_ARG;                                 /*   so return an invalid argument error */
    return 0;
    }
}


/* Parse a character or numeric value.

   This routine parses a token pointed to by "cptr" using the supplied "radix"
   and with the maximum allowed value "max".  If the parse succeeds, the routine
   sets "status" to SCPE_OK and returns the parsed value.

   Single-character ASCII parsing is performed if the specified radix is 256.
   Otherwise, a numeric parse is attempted.
*/

static t_value parse_value (CONST char *cptr, uint32 radix, t_value max, t_stat *status)
{
if (radix == 256)                                       /* if ASCII character parsing is requested */
    if (cptr [0] != '\0' && (t_value) cptr [0] < max) { /*   then if a character is present and within range */
        *status = SCPE_OK;                              /*     then indicate success */
        return (t_value) cptr [0];                      /*       and convert the character value */
        }

    else {                                              /* otherwise */
        *status = SCPE_ARG;                             /*   report that the character parse failed */
        return 0;
        }

else                                                    /* otherwise parse as a number */
    return get_uint (cptr, radix, max, status);         /*   with the specified radix and maximum value */
}


/* Parse a CPU instruction.

   This routine parses the command line for a CPU instruction and its
   operand(s), if any.  On entry, "cptr" points at the instruction mnemonic,
   "addr" is the address where the instruction will be stored, "val" points to
   an array of t_values of depth "sim_emax" where the word(s) comprising the
   machine instruction will be saved, "radix" contains the desired operand radix
   or zero if the default radix is to be used, and "target" indicates the target
   of the instruction (device, CPU, or trace).

   The routine returns a status code to the caller.  SCPE_OK status is returned
   if a single-word instruction was parsed, or the negative number of extra
   words (beyond the first) occupied by the instruction is returned.  For
   example, parsing a two-word instruction returns SCPE_OK_2_WORDS (= -1).  If
   "cptr" does not point at a valid mnemonic, or the mnemonic is not valid for
   the current CPU feature set, or an operand error is present, SCPE_ARG is
   returned.

   The routine first separates the instruction mnemonic from its operands, if
   any.  If the first token is not present or starts with a digit, SCPE_ARG is
   returned.  If the target for the parsed value is not a CPU memory location,
   the address is reset to an invalid value to indicate that a relative offset
   parse should be performed for Memory Reference Group instructions.  Then the
   parser table is scanned to search for a match.

   The parser table contains entries pointing to pairs of opcode descriptors and
   opcode tables, in order of decreasing frequency of appearance in the typical
   instruction mix.  For each entry, if the required firmware option is
   currently installed in the CPU, then the table is searched linearly for a
   match with the mnemonic token.  If a match is found, the "parse_instruction"
   routine is called to assemble the instruction opcode and any associated
   operands into the "val" array for return to the caller.

   If the token does not match any legal instruction mnemonic, the routine
   returns SCPE_ARG.


   Implementation notes:

    1. The mnemonic token is delineated by either a space or a comma; the latter
       is used to separate the multiple mnemonics of SRG and ASG instructions.
*/

static t_stat parse_cpu (CONST char *cptr, t_addr addr, t_value *val, uint32 radix, SYMBOL_SOURCE target)
{
CONST char *gptr;
const PARSER_ENTRY *pptr;
const OP_ENTRY     *eptr;
char  gbuf [CBUFSIZE];

gptr = get_glyph (cptr, gbuf, ',');                     /* parse the opcode and shift to uppercase */

if (gbuf [0] == '\0' || isdigit (gbuf [0]))             /* if the glyph is missing or is numeric */
    return SCPE_ARG;                                    /*   then it is not an instruction mnemonic */

if (target == Device_Symbol)                            /* if the target is not main memory */
    addr = PA_MAX + 1;                                  /*   then invalidate the target address */

for (pptr = parser_table; pptr->descriptor != NULL; pptr++)                 /* search the parser table */
    if (cpu_configuration & pptr->descriptor->feature & CPU_OPTION_MASK     /* if the required feature set is enabled */
      && cpu_configuration & pptr->descriptor->feature & CPU_MODEL_MASK)    /*   for the current CPU configuration */
        for (eptr = pptr->opcodes; eptr->mnemonic != NULL; eptr++)          /*     then search the opcode table */
            if (strcmp (eptr->mnemonic, gbuf) == 0)                         /* if a matching mnemonic is found */
                return parse_instruction (gptr, addr, val, radix, eptr);    /*  then parse the operands */

return SCPE_ARG;                                        /* no match was found, so return failure */
}


/* Parse a CPU instruction in symbolic format.

   This routine is called to parse and encode a CPU instruction mnemonic and any
   associated operand(s).  On entry, "cptr" points at the input buffer after the
   first mnemonic token, "addr" is the address where the instruction will be
   stored, "val" points to an array of t_values of depth "sim_emax" where the
   word(s) comprising the machine instruction will be saved, "radix" contains
   the desired operand parsing radix or zero if the default radix is to be used,
   and "optr" points at the entry in an opcode table corresponding to the
   initial instruction mnemonic.  If "optr" points at an SRG or ASG opcode
   entry, then "cptr" may point to additional micro-ops to be merged with the
   original opcode.  Otherwise, "cptr" will point to any needed instruction
   operands.

   On exit, a status code is returned to the caller.  SCPE_OK status is returned
   if a single-word instruction was parsed, or the negative number of extra
   words (beyond the first) occupied by the instruction is returned.  For
   example, parsing a two-word instruction returns SCPE_OK_2_WORDS (= -1).  If
   the remainder of the input buffer does not parse correctly, or does not
   contain the operands required by the instruction, SCPE_ARG is returned.

   The routine begins by saving the initial opcode word(s) from the opcode table
   entry.  If the opcode belongs to the SRG or ASG, it may be the first of
   several micro-ops that must be parsed individually.  To ensure that each
   micro-op is specified only once and that any A/B-register usage is consistent
   between micro-ops, the significant bits of each parsed micro-op are
   accumulated.  The accumulated bits are passed to the "parse_micro_op"
   routine, where the checks are made.

   If the initial opcode is from the ASG, any additional micro-op mnemonics in
   the input buffer must be in the order they appear in the "asg_uops" table.
   The scan starts with the opcode table entry after the one corresponding to
   the initial opcode.

   Parsing SRG opcodes is more involved.  The first opcode may be a shift or
   rotate instruction, or it may be an SRG micro-op (CLE and/or SLA/B).  In
   either case, parsing continues with the (remaining) micro-ops and then with a
   second shift or rotate instruction.

   A complication is that an initial CLE, SLA, and SLB may be either an SRG or
   an ASG micro-op.  The determination depends on whether they appear with a
   shift or rotate instruction or with a micro-op belonging only to the ASG.
   The decision must be deferred until a mnemonic from one of these two groups
   is parsed.  If CLE and/or SLA/B appears alone, then it is encoded as an SRG
   instruction.

   Examples of SRG and ASG parsing follow:

     Input String     Parsed as  Reason
     ---------------  ---------  ---------------
     CLE                 SRG
     CLE,SLA             SRG
     CLE,SLA,SSA         ASG
     CME,SLA             ASG
     SLA,SSB            error    mixed A/B
     SEZ,ERA            error    mixed ASG/SRG
     SLA,CLE            error    out of order

     RAL                 SRG
     RAL,RAL             SRG
     RAL,CLE,SLA,ERA     SRG
     RAL,CLE,SLA,ERB    error    mixed A/B
     RAL,SLB            error    mixed A/B
     RAL,CLE,SSA        error    mixed SRG/ASG
     CLA,CCA            error    bad combination
     CLA,CLA            error    duplicate

   If the initial opcode is not an SRG or ASG opcode, then only a single
   mnemonic is allowed, and the operand(s), if any, are now examined.  The type
   and count of operand(s), if any, are indicated by the "type" and "count"
   values in the matched opcode table entry.  The entry also indicates which
   operand(s) are addresses and which are data values.  These values drive the
   parsing of the operands.  Parsed operand values are placed in the "val" array
   in order of appearance after the instruction word.

   An MRG memory address operand may be specified either as an absolute address
   or as an explicit page indicator ("Z" or "C") and a page offset.  An absolute
   address is converted into an offset and zero/current page indicator by
   examining the address value.  If the value is less than 2000 octal, a
   base-page reference is encoded.  If the value falls within the same page as
   the "addr" parameter value, a current-page reference is encoded.  If neither
   condition is true, then SCPE_ARG is returned.

   IOG instructions parse the select code and an optional hold-or-clear-flag
   indicator.  These values are encoded into the instruction.  Parsing is
   complicated in that specification of the select code is optional for the HLT
   instruction, defaulting to select code 0.  If the select code is omitted, the
   comma that separates it and the "C" indicator is also omitted.

   EAG shift and rotate counts are specified as 1-16 but are encoded in the
   instruction as 1-15 and 0, respectively.  IOP index instructions offsets are
   specified as -16 to 15 (or +15) but are encoded in the instruction in
   excess-16 format.

   The remaining instructions encode operands in separate memory words following
   the opcode.  A few instructions that are interruptible require emission of an
   all-zeros word following the instruction (either preceding or following the
   operands).  Address operands are parsed as absolute logical addresses with
   optional indirect indicators.  Data operands are parsed as absolute values.

   Parsing concludes with a check for extraneous characters on the line;
   presence causes an SCPE_ARG return.


   Implementation notes:

    1. The "repeated instruction" test for the second shift/rotate op is needed
       to catch the "NOP,ALS" sequence.

    2. The select code may be omitted when entering the HLT instruction (it
       defaults to 0), so "HLT" and "HLT C" are permissible entries.  For the
       latter, if the radix is overridden to hexadecimal, the "C" is interpreted
       as select code 14 (octal) rather than as a "halt and clear flag"
       instruction.

    3. Parsing the 2100 IOP instruction mnemonics "LAI" and "SAI" always matches
       the "opIOPON" entries.  Therefore, the opcodes are those of the negative
       index instructions.  However, because the negative and positive opcode
       values are adjacent, adding (not ORing) the offset value produces the
       correct instruction encoding for both (index -16 => encoded 0, -1 => 15,
       0 => 16, and +15 => 31).
*/

static t_stat parse_instruction (CONST char *cptr, t_addr addr, t_value *val, uint32 radix, const OP_ENTRY *optr)
{
CONST char *gptr;
const char *mptr;
char       gbuf [CBUFSIZE];
OP_TYPE    op_type;
uint32     accumulator, op_index, op_count, op_radix, op_address_set;
t_stat     status, consumption;
t_value    op_value;
t_bool     op_implicit;
t_bool     op_flag  = FALSE;
uint32     op_start = 1;                                /* the "val" array index of the first operand */

val [0] = LOWER_WORD (optr->opcode);                    /* set the (initial) opcode */
val [1] = UPPER_WORD (optr->opcode);                    /*   and the subopcode if it is a two-word instruction */

if (*cptr != '\0'                                       /* if there is more to parse */
  && (SRGOP (optr->opcode) || ASGOP (optr->opcode))) {  /*   and the first opcode is SRG or ASG */
    accumulator = (uint32) optr->op_bits;               /*     then accumulate the significant opcode bits */

    gptr = get_glyph (cptr, gbuf, ',');                 /* parse the next opcode, if present */

    if (ASGOP (optr->opcode))                           /* if the initial opcode is ASG */
        optr++;                                         /*   then point at the next table entry for parsing */

    else {                                              /* otherwise this is an SRG opcode */
        if (optr->opcode & SRG1_DE_MASK) {              /* if it is a shift or rotate instruction */
            mptr = NULL;                                /*   then it cannot be an ASG instruction */
            optr = srg_uops;                            /*     and parsing continues with the micro-ops */
            }

        else {                                          /* otherwise it's a micro-op that could be ASG */
            mptr = optr->mnemonic;                      /*   so save the initial mnemonic pointer */
            optr++;                                     /*     and start with the next micro-op */
            }

        status = parse_micro_ops (optr, gbuf, val,      /* parse the SRG micro-ops */
                                  &gptr, &accumulator);

        if (status != SCPE_INCOMP)                      /* if the parse is complete */
            return status;                              /*   then return success or failure as appropriate */

        optr = srg2_ops;                                /* the mnemonic is not a micro-op, so try a shift/rotate op */

        status = parse_micro_ops (optr, gbuf, val,      /* parse the SRG shift-rotate opcode */
                                  NULL, &accumulator);

        if (status == SCPE_OK && *gptr != '\0')         /* if the opcode was found but there is more to parse */
            return SCPE_ARG;                            /*   then return failure as only one opcode is allowed */

        else if (status != SCPE_INCOMP)                 /* otherwise if the parse is complete */
            return status;                              /*   then return success or failure as appropriate */

        if (mptr == NULL)                               /* the mnemonic is not an SRG, and if it cannot be an ASG */
            return SCPE_ARG;                            /*   then fail with an invalid mnemonic */

        else {                                          /* otherwise attempt to reparse as ASG instructions */
            val [0] = 0;                                /* clear the assembled opcode */
            accumulator = 0;                            /*   and the accumulated significant opcode bits */

            strcpy (gbuf, mptr);                        /* restore the original mnemonic to the buffer */
            gptr = cptr;                                /*   and the original remainder-of-line pointer */

            optr = asg_uops;                            /* set to search the ASG micro-ops table */
            }                                           /*   and fall into the ASG parser */
        }


    status = parse_micro_ops (optr, gbuf, val,          /* parse the ASG micro-ops */
                              &gptr, &accumulator);

    if (status != SCPE_INCOMP)                          /* if the parse is complete */
        return status;                                  /*   then return success or failure as appropriate */
    }

else {                                                  /* otherwise, it's a single opcode */
    op_type = optr->type;                               /*   so get the type of the instruction operand(s) */

    op_count = (uint32) op_props [op_type].count;       /* get the number of operands */
    consumption = (t_stat) - op_props [op_type].count;  /*   and set the initial number of words consumed */

    op_address_set = op_props [op_type].address_set;    /* get the address/data-selector bit set */

    op_radix = (radix ? radix : cpu_dev.dradix);        /* assume that the (only) operand is data */

    if (optr->op_bits > D16_MASK) {                     /* if this is a two-word instruction */
        op_start = 2;                                   /*   then the operands start after the second word */
        op_count = op_count + 1;                        /*     and extend for an extra word */
        consumption = consumption - 1;                  /*       and consume an additional word */
        }


    switch (op_type) {                                  /* dispatch by the operand type */

        /* no operand */

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


        /* MRG page bit 10, offset 0000-1777 octal, indirect bit 15 */

        case opMPOI:
            cptr = get_glyph (cptr, gbuf, '\0');            /* get the next token */

            if (gbuf [0] == 'C' && gbuf [1] == '\0') {      /* if the "C" modifier was specified */
                val [0] = val [0] | IR_CP;                  /*   then add the current-page flag */
                cptr = get_glyph (cptr, gbuf, '\0');        /* get the address */
                op_implicit = FALSE;                        /*   and clear the implicit-page flag */
                }

            else if (gbuf [0] == 'Z' && gbuf [1] == '\0') { /* otherwise if the "Z" modifier was specified */
                cptr = get_glyph (cptr, gbuf, '\0');        /*   then get the address */
                op_implicit = FALSE;                        /*     and clear the implicit-page flag */
                }

            else                                            /* otherwise neither modifier is present */
                op_implicit = TRUE;                         /*   so set the flag to allow implicit-page addressing */

            op_value = parse_address (gbuf, &status);       /* parse the address and optional indirection indicator */

            if (status != SCPE_OK)                          /* if a parse error occurred */
                return status;                              /*   then return an invalid argument error */

            if ((op_value & LA_MASK) <= OF_MAX)             /* if a base-page address was given */
                val [0] |= op_value;                        /*   then merge the offset into the instruction */

            else if (addr <= LA_MAX && op_implicit                  /* otherwise if an implicit-page address is allowed */
              && ((addr ^ op_value) & MR_PAGE) == 0)                /*   and the target is in the current page */
                val [0] |= IR_CP | op_value & (IR_IND | IR_OFFSET); /*     then merge the offset with the current-page flag */

            else                                            /* otherwise the address cannot be reached */
                return SCPE_ARG;                            /*   from the current instruction's location */
            break;


        /* IOG select code range 00-77 octal, hold/clear bit 9 */
        /* IOG optional select code range 00-77 octal, hold/clear bit 9 */

        case opSCHC:
        case opSCOHC:
            op_flag = TRUE;                             /* set a flag to enable an optional ",C" */

        /* fall through into the opSC case */

        /* IOG select code range 00-77 octal */

        case opSC:
            cptr = get_glyph (cptr, gbuf, (op_flag ? ',' : '\0'));  /* get the next glyph */
            op_value = get_uint (gbuf, op_radix, SC_MASK, &status); /*   and parse the select code */

            if (status == SCPE_OK)                      /* if the select code is good */
                val [0] |= op_value;                    /*   then merge it into the opcode */

            else if (op_type == opSCOHC)                /* otherwise if the select code is optional */
                if (gbuf [0] == '\0')                   /*   then if it is not supplied */
                    break;                              /*     then use the base instruction value */

                else if (gbuf [0] == 'C' && gbuf [1] == '\0') { /*   otherwise if the "C" modifier was specified */
                    val [0] |= IR_HCF;                          /*     then merge the clear-flag bit */
                    break;                                      /*       into the base instruction value */
                    }

            else                                        /* otherwise the select code is bad */
                return SCPE_ARG;                        /*   so report failure for a bad argument */

        /* fall through into opHC case */

        /* IOG hold/clear bit 9 */

        case opHC:
            if (*cptr != '\0')                                  /* if there is more */
                if (op_type != opSC) {                          /*   and it is expected */
                    cptr = get_glyph (cptr, gbuf, '\0');        /*     then get the glyph */

                    if (gbuf [0] == 'C' && gbuf [1] == '\0')    /* if the "C" modifier was specified */
                        val [0] |= IR_HCF;                      /*   then merge the clear-flag bit */
                    else                                        /* otherwise it's something else */
                        return SCPE_ARG;                        /*   so report failure for a bad argument */
                    }

                else                                    /* otherwise it's not expected */
                    return SCPE_ARG;                    /*   so report failure for a bad argument */
            break;


        /* EAU shift/rotate count range 1-16 */

        case opShift:
            op_radix = (radix ? radix : 10);            /* shift counts default to decimal */

            cptr = get_glyph (cptr, gbuf, '\0');                    /* get the next glyph */
            op_value = parse_value (gbuf, op_radix, 16, &status);   /*   and parse the shift count */

            if (status != SCPE_OK || op_value == 0)     /* if a parsing error occurred or the count is zero */
                return SCPE_ARG;                        /*   then report failure for a bad argument */
            else if (op_value < 16)                     /* otherwise the count is good */
                val [0] |= op_value;                    /*   so merge it into the opcode (0 encodes 16) */
            break;


        /* IOP index negative offset range 1-16 */
        /* IOP index positive offset range 0-15 */
        /* IOP index offset range -16 to +15 (+16 bias) */

        case opIOPON:
        case opIOPOP:
        case opIOPO:
            op_radix = (radix ? radix : 10);            /* index offsets default to decimal */

            if (*cptr == '+')                           /* if there is a leading plus sign */
                cptr++;                                 /*   then skip it */

            else if (*cptr == '-') {                    /* otherwise if there is a leading minus sign */
                op_flag = TRUE;                         /*   then set the negative flag */
                cptr++;                                 /*     and skip it */
                }

            cptr = get_glyph (cptr, gbuf, '\0');        /* get the next glyph */
            op_value = parse_value (gbuf, op_radix,     /*   and parse the index offset with the appropriate range */
                                    15 + op_flag, &status);

            if (status != SCPE_OK)                      /* if a parsing error or value out of range occurred */
                return SCPE_ARG;                        /*   then report failure for a bad argument */

            else if (op_flag)                           /* otherwise the offset is good; if it is negative */
                val [0] = val [0] + 16 - op_value;      /*   then scale and merge the offset */

            else                                        /* otherwise it is positive */
                val [0] = val [0] + op_value + 16;      /*   so shift to the positive opcode and merge the offset */
            break;


        /* UIG zero word, four (indirect) memory addresses */
        /* UIG zero word, five (indirect) memory addresses */
        /* UIG zero word, six (indirect) memory addresses */
        /* UIG zero word, eight (indirect) memory addresses */

        case opZA4:
        case opZA5:
        case opZA6:
        case opZA8:
            val [1] = 0;                                /* add the zero word */
            op_start = 2;                               /*   and bump the operand starting index */
            break;


        /* One memory address range 00000-77777 octal, zero word, indirect bit 15 */

        case opMA1ZI:
            val [2] = 0;                                /* add the zero word */
            op_count = 1;                               /*   and reduce the operand count to 1 */
            break;


        /* One to seven memory addresses range 00000-77777 octal, indirect bit 15 */

        case opMA1I:
        case opMA2I:
        case opMA3I:
        case opMA4I:
        case opMA5I:
        case opMA6I:
        case opMA7I:

        /* UIG one data value */
        /* UIG two data values */
        /* UIG one (indirect) memory address, one data value */
        /* UIG two data values, one (indirect) memory address */
        /* UIG one data value, five (indirect) memory addresses */

        case opV1:
        case opV2:
        case opA1V1:
        case opV2A1:
        case opV1A5:
            break;                                      /* no special operand handling needed */
        }                                               /* end of the operand type dispatch */


    if (op_count > 0)                                   /* if one or more operands are required */
        for (op_index = op_start;                       /*   then parse and load them */
          op_index <= op_count;                         /*     in to the return array */
          op_index++) {
            cptr = get_glyph (cptr, gbuf, '\0');        /* get the next glyph */

            if (op_address_set & 1)                         /* if this operand is an address */
                op_value = parse_address (gbuf, &status);   /*   then parse it with an optional indirection indicator */
            else                                            /* otherwise it's a data value */
                op_value = parse_value (gbuf, op_radix,     /*   so parse it as an unsigned number */
                                        DV_UMAX, &status);

            if (status != SCPE_OK)                      /* if a parse error occurred */
                return status;                          /*   then return an invalid argument error */
            else                                        /* otherwise */
                val [op_index] = op_value;              /*   store the operand value */

            op_address_set = op_address_set >> 1;       /* shift the next type bit into place */
            }                                           /*   and loop until all operands are parsed */

    if (*cptr == '\0')                                  /* if parsing is complete */
        return consumption;                             /*   then return the number of instruction words */
    }

return SCPE_ARG;                                        /* return an error for extraneous characters present */
}


/* Parse micro-ops.

   This routine parses a set of micro-operations that form a single instruction.
   It is called after a token has matched an entry in the SRG or ASG opcode
   tables.

   On entry, "optr" points at the opcode table entry after the first token
   match, "gbuf" points at the next mnemonic token in the input buffer, "val"
   points at the array of t_values that will hold the assembled instruction and
   its operands, "gptr" points at the pointer that is set to the next character
   to parse, and "accumulator" points at the variable that holds the accumulated
   significant opcode bits for the instruction.  On exit, the routine returns
   SCPE_OK if the input buffer was parsed correctly, SCPE_ARG if conflicting
   A/B-register selectors were encountered (e.g., "CLA,CMB"), or SCPE_INCOMP if
   the opcode table was exhausted without consuming the input buffer.  The
   pointer "gptr" points at is updated to point at the first character after the
   last token parsed, and the value pointed at by "accumulator" contains the
   accumulated significant opcode bits for the set of parsed micro-ops.  The
   former is used by the caller to continue parsing, while the latter is used to
   track the micro-ops that have been specified to ensure that each appears only
   once.

   The routine searches the supplied table for mnemonics that match the tokens
   parsed from the input buffer.  For each match, checks are made to ensure that
   the same mnemonic has not been specified before and that opcodes that specify
   an accumulator are consistent throughout the instruction.  The opcode from
   each matching entry is then logically ORed into the instruction word, and the
   significant opcode bits are accumulated to provide a check for duplicates.
   When the input buffer or opcode table is exhausted, or a check fails, the
   routine exits with the appropriate status.


   Implementation notes:

    1. The "repeated operation" significant-bits test is necessary only to catch
       two cases.  One is the NOP,RAL and RAL,NOP case where NOP must be
       specified alone and yet it decodes as an SRG instruction, which allows
       micro-ops.  The other is the CLA,CMA case where multiple encoded ASG
       operations are specified.  All other repeated operation cases (e.g.,
       CLE,SLA,CLE) are prohibited by the single pass through a table arranged
       in the required order (so the second CLE will not be found because SLA
       comes after CLE in the table).

       The first case could be handled by substituting an operand test for the
       group test (e.g., optr->type == opSRG || optr->type == opASG and marking
       NOP as opNone and all other SRG opcodes as opSRG, so that NOP will not be
       detected as an SRG instruction) in "parse_instruction", placing the NOP
       entry first, and starting the search with entry 2 in the SRG micro-ops
       table.

       The second case could be handled by treating the encoded operations
       (CLx/CMx/CCx and CLE/CME/CCE) as indexed searches of separate primary
       tables.

       The added complexity of these alternates is not worth the effort, given
       the relative simplicity of the "repeated operation" test.

    2. The A/B-register selector state must be the same for all micro-ops in a
       given instruction.  That is, all micro-ops that use the A or B registers
       must reference the same accumulator.  The check is performed by logically
       ANDing the accumulated and current significant opcode bits with the
       AB_MASK (to determine if the A/B-register select is significant and has
       been set), and then with the exclusive-OR of the accumulated and current
       micro-ops (to determine if the current A/B-register select is the same as
       the prior A/B-register selects).  If there is a discrepancy, then the
       current micro-op does not use the same register as a previously specified
       micro-op.
*/

static t_stat parse_micro_ops (const OP_ENTRY *optr, char *gbuf, t_value *val, CONST char **gptr, uint32 *accumulator)
{
while (optr->mnemonic != NULL)                                  /* search the table until the NULL entry at the end */
    if (strcmp (optr->mnemonic, gbuf) == 0) {                   /* if the mnemonic matches this entry */
        if (*accumulator & optr->op_bits & ~(AB_MASK | ASG))    /*   then if this opcode has been entered before */
            return SCPE_ARG;                                    /*     then fail with a repeated instruction error */

        if (*accumulator & optr->op_bits & AB_MASK      /* if the A/B-register selector is significant */
          & (optr->opcode ^ val [0]))                   /*   and the new opcode disagrees with the prior ones */
            return SCPE_ARG;                            /*     then fail with an A/B inconsistent error */

        val [0] |= optr->opcode;                        /* include the opcode */
        *accumulator |= optr->op_bits;                  /*   and accumulate the significant opcode bits */

        if (gptr == NULL || **gptr == '\0')             /* if the line is fully parsed or only one match allowed */
            return SCPE_OK;                             /*   then we are done */
        else                                            /* otherwise there is more */
            *gptr = get_glyph (*gptr, gbuf, ',');       /*   so parse the next opcode */
        }

    else                                                /* otherwise this entry does not match */
        optr++;                                         /*   so point at the next one */

return SCPE_INCOMP;
}


/* Get a 16-bit word from a file.

   This routine gets the next two bytes from the open file stream "fileref" and
   returns a 16-bit word with the first byte in the upper half and the second
   byte in the lower half.  If a file error occurs (either a read error or an
   unexpected end-of-file), the routine returns EOF.
*/

static int fgetword (FILE *fileref)
{
int c1, c2;

c1 = fgetc (fileref);                                   /* get the first byte from the file */

if (c1 == EOF)                                          /* if the read failed */
    return c1;                                          /*   then return EOF */

c2 = fgetc (fileref);                                   /* get the second byte from the file */

if (c2 == EOF)                                          /* if the read failed */
    return c2;                                          /*   then return EOF */
else                                                    /* otherwise */
    return (int) TO_WORD (c1, c2);                      /*   return the word formed by the two bytes */
}


/* Put a 16-bit word to a file.

   This routine writes two bytes from the 16-bit word "data" into the open file
   stream "fileref".  The upper half of the word supplies the first byte, and
   the lower half supplies the second byte.  If a write error occurs, the
   routine returns EOF.  Otherwise, it returns the second byte written.
*/

static int fputword (int data, FILE *fileref)
{
int status;

status = fputc (UPPER_BYTE (data), fileref);            /* write the first byte */

if (status != EOF)                                      /* if the write succeeded */
    status = fputc (LOWER_BYTE (data), fileref);        /*   then write the second byte */

return status;                                          /* return the result of the write */
}


/* Service the keyboard poll.

   The keyboard poll service routine is entered after every ten milliseconds of
   wall-clock time.  If the TTY (system console) device is disabled, then the
   routine checks for an SCP stop command (initially, CTRL+E); all other
   characters are discarded.  If the TTY is enabled, then it performs this
   function.


   Implementation notes:

    1. The current CPU speed, expressed as a multiple of the hardware speed, is
       calculated for each service entry.  It may be displayed at the SCP prompt
       with the SHOW CPU SPEED command.  The speed is only representative when
       the CPU is not idling.
*/

static t_stat poll_service (UNIT *uptr)
{
t_stat status;

uptr->wait = sim_rtcn_calb (POLL_RATE, TMR_POLL);       /* recalibrate */
sim_activate (uptr, uptr->wait);                        /* reschedule the timer */

cpu_speed = uptr->wait / POLL_PERIOD;                   /* calculate the current CPU speed multiplier */

if (tty_dev.flags & DEV_DIS) {                          /* if the TTY is disabled */
    status = sim_poll_kbd ();                           /*   then we must poll for a console interrupt */

    if (status < SCPE_KFLAG)                            /* if the result is not a character */
        return status;                                  /*   then return the resulting status */
    }

return SCPE_OK;
}


static t_stat poll_reset (DEVICE *dptr)
{
if (sim_switches & SWMASK ('P'))                        /* if this is a power-on reset */
    sim_rtcn_init (poll_unit [0].wait, TMR_POLL);       /*   then initialize the poll timer */

sim_activate_abs (&poll_unit [0], poll_unit [0].wait);  /* schedule the poll timer */

return SCPE_OK;
}


/* Synchronize polling.

   Return an event time corresponding either with the amount of time remaining
   in the current poll (mode = INITIAL) or the amount of time in a full poll
   period (mode = SERVICE).  If the former call is made when the device service
   routine is started, then making the latter call during unit service will
   ensure that the polls remain synchronized.
 */

int32 hp_sync_poll (POLLMODE poll_mode)
{
int32 poll_time;

if (poll_mode == INITIAL) {                             /* if this is an initial poll request */
    poll_time = sim_activate_time (&poll_unit [0]);     /*   then get the time remaining */

    if (poll_time == 0)                                 /* if no time remains */
        return POLL_PERIOD + 1;                         /*   then ensure queuing after the poll device */
    else                                                /* otherwise */
        return poll_time;                               /*   return the remaining time */
    }

else                                                    /* otherwise this is a event service request */
    return poll_unit [0].wait;                          /*   so return the poll unit time to remain synchronized */
}
