/* hp2100_cpu1.c: HP 2100/1000 EAU/FP/IOP microcode simulator

   Copyright (c) 2005-2016, Robert M. Supnik
   Copyright (c) 2017-2019, J. David Bryan

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

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

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
   AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
   WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   Except as contained in this notice, the 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.

   CPU1         Extended Arithmetic Unit, Floating Point, and I/O Processor
                instructions

   23-Jan-19    JDB     Moved fmt_ab to hp2100_cpu5.c
   02-Oct-18    JDB     Replaced DMASK with D16_MASK or R_MASK as appropriate
   02-Aug-18    JDB     Moved UIG dispatcher to hp2100_cpu0.c
                        Moved FP and IOP dispatchers from hp2100_cpu2.c
   25-Jul-18    JDB     Use cpu_configuration instead of cpu_unit.flags for tests
   07-Sep-17    JDB     Removed unnecessary "uint16" casts
   01-Aug-17    JDB     Changed TIMER and RRR 16 to test for undefined stops
   07-Jul-17    JDB     Changed "iotrap" from uint32 to t_bool
   26-Jun-17    JDB     Replaced SEXT with SEXT16
   22-Apr-17    JDB     Improved the EAU shift/rotate instructions
   21-Mar-17    JDB     Fixed UIG 1 comment regarding 2000 IOP and F-Series
   31-Jan-17    JDB     Added fmt_ab to print A/B-register error codes
   30-Jan-17    JDB     Removed fprint_ops, fprint_regs (now redundant)
   05-Aug-16    JDB     Renamed the P register from "PC" to "PR"
   24-Dec-14    JDB     Added casts for explicit downward conversions
   05-Apr-14    JDB     Corrected typo in comments for cpu_ops
   09-May-12    JDB     Separated assignments from conditional expressions
   11-Sep-08    JDB     Moved microcode function prototypes to hp2100_cpu1.h
   05-Sep-08    JDB     Moved option-present tests to UIG dispatchers
                        Call "user microcode" dispatcher for unclaimed UIG instructions
   20-Apr-08    JDB     Fixed VIS and SIGNAL to depend on the FPP and HAVE_INT64
   28-Nov-07    JDB     Added fprint_ops, fprint_regs for debug printouts
   17-Nov-07    JDB     Enabled DIAG as NOP on 1000 F-Series
   04-Jan-07    JDB     Added special DBI dispatcher for non-INT64 diagnostic
   29-Dec-06    JDB     Allows RRR as NOP if 2114 (diag config test)
   01-Dec-06    JDB     Substitutes FPP for firmware FP if HAVE_INT64
   16-Oct-06    JDB     Generalized operands for F-Series FP types
   26-Sep-06    JDB     Split hp2100_cpu1.c into multiple modules to simplify extensions
                        Added iotrap parameter to UIG dispatchers for RTE microcode
   22-Feb-05    JDB     Removed EXECUTE instruction (is NOP in actual microcode)
   21-Jan-05    JDB     Reorganized CPU option and operand processing flags
                        Split code along microcode modules
   15-Jan-05    RMS     Cloned from hp2100_cpu.c

   Primary references:
     - HP 1000 M/E/F-Series Computers Technical Reference Handbook
         (5955-0282, March 1980)
     - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation
         (92851-90001, March 1981)
     - Macro/1000 Reference Manual
         (92059-90001, December 1992)

   Additional references are listed with the associated firmware
   implementations, as are the HP option model numbers pertaining to the
   applicable CPUs.


   This source file contains the Extended Arithmetic Unit simulator, the
   single-precision floating-pointer simulator, and the HP 2000 I/O Processor
   instructions simulator.

*/



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

#if !defined (HAVE_INT64)                               /* int64 support unavailable */

  #include "hp2100_cpu_fp.h"

#endif



/* EAU.

   The Extended Arithmetic Unit (EAU) adds ten instructions with double-word
   operands, including multiply, divide, shifts, and rotates.  Option
   implementation by CPU was as follows:

      2114    2115    2116    2100   1000-M  1000-E  1000-F
     ------  ------  ------  ------  ------  ------  ------
      N/A    12579A  12579A   std     std     std     std

   The instruction codes are mapped to routines as follows:

     Instr.    Bits
      Code   15-8 7-4   2116    2100   1000-M  1000-E  1000-F  Note
     ------  ---- ---  ------  ------  ------  ------  ------  ---------------------
     100000   200  00                          [diag]  [diag]  [self test]
     100020   200  01   ASL     ASL     ASL     ASL     ASL    Bits 3-0 encode shift
     100040   200  02   LSL     LSL     LSL     LSL     LSL    Bits 3-0 encode shift
     100060   200  03                          TIMER   TIMER   [deterministic delay]
     100100   200  04   RRL     RRL     RRL     RRL     RRL    Bits 3-0 encode shift
     100200   200  10   MPY     MPY     MPY     MPY     MPY
     100400   201  xx   DIV     DIV     DIV     DIV     DIV
     101020   202  01   ASR     ASR     ASR     ASR     ASR    Bits 3-0 encode shift
     101040   202  02   LSR     LSR     LSR     LSR     LSR    Bits 3-0 encode shift
     101100   202  04   RRR     RRR     RRR     RRR     RRR    Bits 3-0 encode shift
     104200   210  xx   DLD     DLD     DLD     DLD     DLD
     104400   211  xx   DST     DST     DST     DST     DST

   The remaining codes for bits 7-4 are undefined and will cause a simulator
   stop if enabled.  On a real 1000-M, all undefined instructions in the 200
   group decode as MPY, and all in the 202 group decode as NOP.  On a real
   1000-E, instruction patterns 200/05 through 200/07 and 202/03 decode as NOP;
   all others cause erroneous execution.

   EAU instruction decoding on the 1000 M-series is convoluted.  The JEAU
   microorder maps IR bits 11, 9-7 and 5-4 to bits 2-0 of the microcode jump
   address.  The map is detailed on page IC-84 of the ERD.

   The 1000 E/F-series add two undocumented instructions to the 200 group: TIMER
   and DIAG.  These are described in the ERD on page IA 5-5, paragraph 5-7.  The
   M-series executes these as MPY and RRL, respectively.  A third instruction,
   EXECUTE (100120), is also described but was never implemented, and the
   E/F-series microcode execute a NOP for this instruction code.

   If the EAU is not installed in a 2115 or 2116, EAU instructions execute as
   NOPs or cause unimplemented instruction stops if enabled.


   Implementation notes:

    1. Under simulation, TIMER and DIAG cause undefined instruction stops if the
       CPU is not an E/F-Series.  Note that TIMER is intentionally executed by
       several HP programs to differentiate between M- and E/F-series machines.

    2. DIAG is not implemented under simulation.  On the E/F, it performs a
       destructive test of all installed memory.  Because of this, it is only
       functional if the machine is halted, i.e., if the instruction is
       executed with the INSTR STEP button.  If it is executed in a program,
       the result is NOP.

    3. The RRR 16 instruction is intentionally executed by the diagnostic
       configurator on the 2114, which does not have an EAU, to differentiate
       between 2114 and 2100/1000 CPUs.

    4. The shift count is calculated unconditionally, as six of the ten
       instructions will be using the value.

    5. An arithmetic left shift must be handled as a special case because the
       shifted operand bits "skip over" the sign bit.  That is, the bits are
       lost from the next-most-significant bit while preserving the MSB.  For
       all other shifts, including the arithmetic right shift, the operand may
       be shifted and then merged with the appropriate fill bits.

    6. The C standard specifies that the results of bitwise shifts with negative
       signed operands are undefined (for left shifts) or implementation-defined
       (for right shifts).  Therefore, we must use unsigned operands and handle
       arithmetic shifts explicitly.
*/

t_stat cpu_eau (void)
{
t_stat reason = SCPE_OK;
OPS op;
uint32 rs, qs, v1, v2, operand, fill, mask, shift;
int32 sop1, sop2;

if (!(cpu_configuration & CPU_EAU))                     /* if the EAU is not installed */
    return STOP (cpu_ss_unimpl);                        /*   then the instructions execute as NOPs */

if (IR & 017)                                           /* if the shift count is 1-15 */
    shift = IR & 017;                                   /*   then use it verbatim */
else                                                    /* otherwise the count iz zero */
    shift = 16;                                         /*   so use a shift count of 16 */

switch ((IR >> 8) & 0377) {                             /* decode IR<15:8> */

    case 0200:                                          /* EAU group 0 */
        switch ((IR >> 4) & 017) {                      /* decode IR<7:4> */

            case 000:                                       /* DIAG 100000 */
                if (!(cpu_configuration & CPU_1000_E_F))    /* if the CPU is not an E- or F-series */
                    return STOP (cpu_ss_undef);             /*   then the instruction is undefined */
                break;                                      /*     and executes as NOP */


            case 001:                                   /* ASL 100020-100037 */
                operand = TO_DWORD (BR, AR);            /* form the double-word operand */

                mask = D32_UMAX << 31 - shift;          /* form a mask for the bits that will be lost */

                if (operand & D32_SIGN)                     /* if the operand is negative */
                    O = (~operand & mask & D32_MASK) != 0;  /*   then set overflow if any of the lost bits are zeros */
                else                                        /* otherwise it's positive */
                    O = (operand & mask & D32_MASK) != 0;   /*   so set overflow if any of the lost bits are ones */

                operand = operand << shift & D32_SMAX   /* shift the operand left */
                            | operand & D32_SIGN;       /*   while keeping the original sign bit */

                BR = UPPER_WORD (operand);              /* split the operand */
                AR = LOWER_WORD (operand);              /*   into its constituent parts */
                break;


            case 002:                                   /* LSL 100040-100057 */
                operand = TO_DWORD (BR, AR) << shift;   /* shift the double-word operand left */

                BR = UPPER_WORD (operand);              /* split the operand */
                AR = LOWER_WORD (operand);              /*   into its constituent parts */
                break;


            case 004:                                   /* RRL 100100-100117 */
                operand = TO_DWORD (BR, AR);            /* form the double-word operand */
                fill = operand;                         /*   and fill with operand bits */

                operand = operand << shift              /* rotate the operand left */
                            | fill >> 32 - shift;       /*   while filling in on the right */

                BR = UPPER_WORD (operand);              /* split the operand */
                AR = LOWER_WORD (operand);              /*   into its constituent parts */
                break;


            case 003:                                   /* TIMER 100060 */
                if (cpu_configuration & CPU_1000_E_F) { /* if the CPU is an E- or F-series */
                    BR = BR + 1 & R_MASK;               /*   then increment B */

                    if (BR != 0)                        /* if B did not roll over */
                        PR = err_PR;                    /*   then repeat the instruction */
                    break;
                    }

                else {                                  /* otherwise it's a 21xx or 1000 M-Series */
                    reason = STOP (cpu_ss_undef);       /*   and the instruction is undefined */

                    if (reason != SCPE_OK               /* if a stop is indicated */
                      || cpu_configuration & CPU_21XX)  /*   or the CPU is a 21xx */
                        break;                          /*     then the instruction executes as NOP */
                    }

            /* fall through into the MPY case if 1000 M-Series */

            case 010:                                   /* MPY 100200 (OP_K) */
                reason = cpu_ops (OP_K, op);            /* get operand */

                if (reason == SCPE_OK) {                /* successful eval? */
                    sop1 = SEXT16 (AR);                 /* sext AR */
                    sop2 = SEXT16 (op[0].word);         /* sext mem */
                    sop1 = sop1 * sop2;                 /* signed mpy */
                    BR = UPPER_WORD (sop1);             /* to BR'AR */
                    AR = LOWER_WORD (sop1);
                    O = 0;                              /* no overflow */
                    }
                break;


            default:                                    /* others undefined */
                return STOP (cpu_ss_unimpl);
            }

        break;


    case 0201:                                          /* DIV 100400 (OP_K) */
        reason = cpu_ops (OP_K, op);                    /* get operand */

        if (reason != SCPE_OK)                          /* eval failed? */
            break;

        rs = qs = BR & D16_SIGN;                        /* save divd sign */

        if (rs) {                                       /* neg? */
            AR = (~AR + 1) & R_MASK;                    /* make B'A pos */
            BR = (~BR + (AR == 0)) & R_MASK;            /* make divd pos */
            }

        v2 = op[0].word;                                /* divr = mem */

        if (v2 & D16_SIGN) {                            /* neg? */
            v2 = (~v2 + 1) & D16_MASK;                  /* make divr pos */
            qs = qs ^ D16_SIGN;                         /* sign of quotient */
            }

        if (BR >= v2)                                   /* if the divisor is too small */
            O = 1;                                      /*   then set overflow */

        else {                                          /* maybe... */
            O = 0;                                      /* assume ok */
            v1 = (BR << 16) | AR;                       /* 32b divd */
            AR = (v1 / v2) & R_MASK;                    /* quotient */
            BR = (v1 % v2) & R_MASK;                    /* remainder */

            if (AR) {                                   /* quotient > 0? */
                if (qs)                                 /* apply quo sign */
                    AR = NEG16 (AR);

                if ((AR ^ qs) & D16_SIGN)               /* still wrong? ovflo */
                    O = 1;
                }

            if (rs)
                BR = NEG16 (BR);                        /* apply rem sign */
            }
        break;


    case 0202:                                          /* EAU group 2 */
        switch ((IR >> 4) & 017) {                      /* decode IR<7:4> */

            case 001:                                   /* ASR 101020-101037 */
                O = 0;                                  /* clear ovflo */

                operand = TO_DWORD (BR, AR);            /* form the double-word operand */
                fill = (operand & D32_SIGN ? ~0 : 0);   /*   and fill with copies of the sign bit */

                operand = operand >> shift              /* shift the operand right */
                            | fill << 32 - shift;       /*   while filling in with sign bits */

                BR = UPPER_WORD (operand);              /* split the operand */
                AR = LOWER_WORD (operand);              /*   into its constituent parts */
                break;


            case 002:                                   /* LSR 101040-101057 */
                operand = TO_DWORD (BR, AR) >> shift;   /* shift the double-word operand right */

                BR = UPPER_WORD (operand);              /* split the operand */
                AR = LOWER_WORD (operand);              /*   into its constituent parts */
                break;


            case 004:                                   /* RRR 101100-101117 */
                operand = TO_DWORD (BR, AR);            /* form the double-word operand */
                fill = operand;                         /*   and fill with operand bits */

                operand = operand >> shift              /* rotate the operand right */
                            | fill << 32 - shift;       /*   while filling in on the left */

                BR = UPPER_WORD (operand);              /* split the operand */
                AR = LOWER_WORD (operand);              /*   into its constituent parts */
                break;


            default:                                    /* others undefined */
                return STOP (cpu_ss_undef);
            }

        break;


    case 0210:                                          /* DLD 104200 (OP_D) */
        reason = cpu_ops (OP_D, op);                    /* get operand */

        if (reason == SCPE_OK) {                        /* successful eval? */
            AR = UPPER_WORD (op[0].dword);              /* load AR */
            BR = LOWER_WORD (op[0].dword);              /* load BR */
            }
        break;


    case 0211:                                          /* DST 104400 (OP_A) */
        reason = cpu_ops (OP_A, op);                    /* get operand */

        if (reason == SCPE_OK) {                        /* successful eval? */
            WriteW (op[0].word, AR);                    /* store AR */
            WriteW ((op[0].word + 1) & LA_MASK, BR);    /* store BR */
            }
        break;


    default:                                            /* should never get here */
        return SCPE_IERR;                               /* bad call from cpu_instr */
    }

return reason;
}



#if !defined (HAVE_INT64)                               /* int64 support unavailable */

/* Single-Precision Floating Point Instructions.

   The 2100 and 1000 CPUs share the single-precision (two word) floating-point
   instruction codes.  Floating-point firmware was an option on the 2100 and was
   standard on the 1000-M and E.  The 1000-F had a standard hardware Floating
   Point Processor that executed these six instructions and added extended- and
   double-precision floating- point instructions, as well as double-integer
   instructions (the FPP is simulated separately).

   Option implementation by CPU was as follows:

      2114    2115    2116    2100   1000-M  1000-E  1000-F
     ------  ------  ------  ------  ------  ------  ------
      N/A     N/A     N/A    12901A   std     std     N/A

   The instruction codes for the 2100 and 1000-M/E systems are mapped to
   routines as follows:

     Instr.  2100/1000-M/E   Description
     ------  -------------  -----------------------------------
     105000       FAD       Single real add
     105020       FSB       Single real subtract
     105040       FMP       Single real multiply
     105060       FDV       Single real divide
     105100       FIX       Single integer to single real fix
     105120       FLT       Single real to single integer float

   Bits 3-0 are not decoded by these instructions, so FAD (e.g.) would be
   executed by any instruction in the range 105000-105017.

   Implementation note: rather than have two simulators that each executes the
   single-precision FP instruction set, we compile conditionally, based on the
   availability of 64-bit integer support in the host compiler.  64-bit integers
   are required for the FPP, so if they are available, then the FPP is used to
   handle the six single-precision instructions for the 2100 and M/E-Series, and
   this function is omitted.  If support is unavailable, this function is used
   instead.

   Implementation note: the operands to FAD, etc. are floating-point values, so
   OP_F would normally be used.  However, the firmware FP support routines want
   floating-point operands as 32-bit integer values, so OP_D is used to achieve
   this.
*/

static const OP_PAT op_fp[8] = {
  OP_D,    OP_D,    OP_D,    OP_D,                      /*  FAD    FSB    FMP    FDV  */
  OP_N,    OP_N,    OP_N,    OP_N                       /*  FIX    FLT    ---    ---  */
  };

t_stat cpu_fp (void)
{
t_stat reason = SCPE_OK;
OPS op;
uint32 entry;

entry = (IR >> 4) & 017;                                /* mask to entry point */

if (op_fp [entry] != OP_N) {
    reason = cpu_ops (op_fp [entry], op);               /* get instruction operands */

    if (reason != SCPE_OK)                              /* evaluation failed? */
        return reason;                                  /* return reason for failure */
    }

switch (entry) {                                        /* decode IR<7:4> */

    case 000:                                           /* FAD 105000 (OP_D) */
        O = f_as (op[0].dword, 0);                      /* add, upd ovflo */
        break;

    case 001:                                           /* FSB 105020 (OP_D) */
        O = f_as (op[0].dword, 1);                      /* sub, upd ovflo */
        break;

    case 002:                                           /* FMP 105040 (OP_D) */
        O = f_mul (op[0].dword);                        /* mul, upd ovflo */
        break;

    case 003:                                           /* FDV 105060 (OP_D) */
        O = f_div (op[0].dword);                        /* div, upd ovflo */
        break;

    case 004:                                           /* FIX 105100 (OP_N) */
        O = f_fix ();                                   /* fix, upd ovflo */
        break;

    case 005:                                           /* FLT 105120 (OP_N) */
        O = f_flt ();                                   /* float, upd ovflo */
        break;

    default:                                            /* should be impossible */
        return SCPE_IERR;
        }

return reason;
}

#endif                                                  /* int64 support unavailable */



/* HP 2000 I/O Processor.

   The IOP accelerates certain operations of the HP 2000 Time-Share BASIC system
   I/O processor.  Most 2000 systems were delivered with 2100 CPUs, although IOP
   microcode was developed for the 1000-M and 1000-E.  As the I/O processors
   were specific to the 2000 system, general compatibility with other CPU
   microcode options was unnecessary, and indeed no other options were possible
   for the 2100.

   Option implementation by CPU was as follows:

      2114    2115    2116    2100   1000-M  1000-E  1000-F
     ------  ------  ------  ------  ------  ------  ------
      N/A     N/A     N/A    13206A  13207A  22702A   N/A

   The routines are mapped to instruction codes as follows:

     Instr.     2100      1000-M/E   Description
     ------  ----------  ----------  --------------------------------------------
     SAI     105060-117  101400-037  Store A indexed by B (+/- offset in IR<4:0>)
     LAI     105020-057  105400-037  Load A indexed by B (+/- offset in IR<4:0>)
     CRC     105150      105460      Generate CRC
     REST    105340      105461      Restore registers from stack
     READF   105220      105462      Read F register (stack pointer)
     INS       --        105463      Initialize F register (stack pointer)
     ENQ     105240      105464      Enqueue
     PENQ    105257      105465      Priority enqueue
     DEQ     105260      105466      Dequeue
     TRSLT   105160      105467      Translate character
     ILIST   105000      105470      Indirect address list (similar to $SETP)
     PRFEI   105222      105471      Power fail exit with I/O
     PRFEX   105223      105472      Power fail exit
     PRFIO   105221      105473      Power fail I/O
     SAVE    105362      105474      Save registers to stack

     MBYTE   105120      105765      Move bytes (MBT)
     MWORD   105200      105777      Move words (MVW)
     SBYTE   105300      105764      Store byte (SBT)
     LBYTE   105320      105763      Load byte (LBT)

   The INS instruction was not required in the 2100 implementation because the
   stack pointer was actually the memory protect fence register and so could be
   loaded directly with an OTA/B 05.  Also, the 1000 implementation did not
   offer the MBYTE, MWORD, SBYTE, and LBYTE instructions because the equivalent
   instructions from the standard Extended Instruction Group were used instead.

   Note that the 2100 MBYTE and MWORD instructions operate slightly differently
   from the 1000 MBT and MVW instructions.  Specifically, the move count is
   signed on the 2100 and unsigned on the 1000.  A negative count on the 2100
   results in a NOP.

   The simulator remaps the 2100 instructions to the 1000 codes.  The four EIG
   equivalents are dispatched to the EIG simulator.  The rest are handled here.

   Additional reference:
     - HP 2000 Computer System Sources and Listings Documentation
         (22687-90020, undated), section 3, pages 2-74 through 2-91


   Implementation notes:

    1. The SAVE and RESTR instructions use the (otherwise unused) SP register on
       the 1000 as the stack pointer.  On the 2100, there is no SP register, so
       the instructions use the memory protect fence register as the stack
       pointer.  We update the 2100 fence because it could affect CPU operation
       if MP is turned on (although, in practice, the 2100 IOP does not use
       memory protect and so never enables it).
*/

static const OP_PAT op_iop[16] = {
  OP_V,    OP_N,    OP_N,    OP_N,                      /* CRC    RESTR  READF  INS   */
  OP_N,    OP_N,    OP_N,    OP_V,                      /* ENQ    PENQ   DEQ    TRSLT */
  OP_AC,   OP_CVA,  OP_A,    OP_CV,                     /* ILIST  PRFEI  PRFEX  PRFIO */
  OP_N,    OP_N,    OP_N,    OP_N                       /* SAVE    ---    ---    ---  */
  };

t_stat cpu_iop (uint32 intrq)
{
t_stat reason = SCPE_OK;
OPS op;
uint8 byte;
uint32 entry, i;
HP_WORD hp, tp, t, wc, MA;

if (cpu_configuration & CPU_2100) {                     /* 2100 IOP? */
    if ((IR >= 0105020) && (IR <= 0105057))             /* remap LAI */
        IR = 0105400 | (IR - 0105020);
    else if ((IR >= 0105060) && (IR <= 0105117))        /* remap SAI */
        IR = 0101400 | (IR - 0105060);
    else {
        switch (IR) {                                   /* remap others */
        case 0105000: IR = 0105470; break;              /* ILIST */
        case 0105120: return cpu_eig (0105765, intrq);  /* MBYTE (maps to MBT) */
        case 0105150: IR = 0105460; break;              /* CRC   */
        case 0105160: IR = 0105467; break;              /* TRSLT */
        case 0105200: return cpu_eig (0105777, intrq);  /* MWORD (maps to MVW) */
        case 0105220: IR = 0105462; break;              /* READF */
        case 0105221: IR = 0105473; break;              /* PRFIO */
        case 0105222: IR = 0105471; break;              /* PRFEI */
        case 0105223: IR = 0105472; break;              /* PRFEX */
        case 0105240: IR = 0105464; break;              /* ENQ   */
        case 0105257: IR = 0105465; break;              /* PENQ  */
        case 0105260: IR = 0105466; break;              /* DEQ   */
        case 0105300: return cpu_eig (0105764, intrq);  /* SBYTE (maps to SBT) */
        case 0105320: return cpu_eig (0105763, intrq);  /* LBYTE (maps to LBT) */
        case 0105340: IR = 0105461; break;              /* REST  */
        case 0105362: IR = 0105474; break;              /* SAVE  */

        default:                                        /* all others invalid */
            return STOP (cpu_ss_unimpl);
            }
        }
    }

entry = IR & 077;                                       /* mask to entry point */

if (entry <= 037) {                                     /* LAI/SAI 10x400-437 */
    MA = ((entry - 020) + BR) & LA_MASK;                /* +/- offset */

    if (IR & AB_MASK)                                   /* if this is an LAI instruction */
        AR = ReadW (MA);                                /*   then load the A register */
    else                                                /* otherwise */
        WriteW (MA, AR);                                /*   store the A register */

    return reason;
    }

else if (entry <= 057)                                  /* IR = 10x440-457? */
    return STOP (cpu_ss_unimpl);                        /* not part of IOP */

entry = entry - 060;                                    /* offset 10x460-477 */

if (op_iop [entry] != OP_N) {
    reason = cpu_ops (op_iop [entry], op);              /* get instruction operands */

    if (reason != SCPE_OK)                              /* evaluation failed? */
        return reason;                                  /* return reason for failure */
    }

switch (entry) {                                        /* decode IR<5:0> */

    case 000:                                           /* CRC 105460 (OP_V) */
        t = ReadW (op[0].word) ^ (AR & 0377);           /* xor prev CRC and char */
        for (i = 0; i < 8; i++) {                       /* apply polynomial */
            t = (t >> 1) | ((t & 1) << 15);             /* rotate right */
            if (t & D16_SIGN) t = t ^ 020001;           /* old t<0>? xor */
            }
        WriteW (op[0].word, t);                         /* rewrite CRC */
        break;

    case 001:                                           /* RESTR 105461 (OP_N) */
        SPR = (SPR - 1) & LA_MASK;                      /* decr stack ptr */
        t = ReadW (SPR);                                /* get E and O */
        O = ((t >> 1) ^ 1) & 1;                         /* restore O */
        E = t & 1;                                      /* restore E */
        SPR = (SPR - 1) & LA_MASK;                      /* decr sp */
        BR = ReadW (SPR);                               /* restore B */
        SPR = (SPR - 1) & LA_MASK;                      /* decr sp */
        AR = ReadW (SPR);                               /* restore A */
        if (cpu_configuration & CPU_2100)               /* 2100 keeps sp in MP FR */
            mp_fence = SPR;                             /*   (in case MP is turned on) */
        break;

    case 002:                                           /* READF 105462 (OP_N) */
        AR = SPR;                                       /* copy stk ptr */
        break;

    case 003:                                           /* INS 105463 (OP_N) */
        SPR = AR;                                       /* init stk ptr */
        break;

    case 004:                                           /* ENQ 105464 (OP_N) */
        hp = ReadW (AR & LA_MASK);                      /* addr of head */
        tp = ReadW ((AR + 1) & LA_MASK);                /* addr of tail */
        WriteW ((BR - 1) & LA_MASK, 0);                 /* entry link */
        WriteW ((tp - 1) & LA_MASK, BR);                /* tail link */
        WriteW ((AR + 1) & LA_MASK, BR);                /* queue tail */
        if (hp != 0) PR = (PR + 1) & LA_MASK;           /* q not empty? skip */
        break;

    case 005:                                           /* PENQ 105465 (OP_N) */
        hp = ReadW (AR & LA_MASK);                      /* addr of head */
        WriteW ((BR - 1) & LA_MASK, hp);                /* becomes entry link */
        WriteW (AR & LA_MASK, BR);                      /* queue head */
        if (hp == 0)                                    /* q empty? */
            WriteW ((AR + 1) & LA_MASK, BR);            /* queue tail */
        else PR = (PR + 1) & LA_MASK;                   /* skip */
        break;

    case 006:                                           /* DEQ 105466 (OP_N) */
        BR = ReadW (AR & LA_MASK);                      /* addr of head */
        if (BR) {                                       /* queue not empty? */
            hp = ReadW ((BR - 1) & LA_MASK);            /* read hd entry link */
            WriteW (AR & LA_MASK, hp);                  /* becomes queue head */
            if (hp == 0)                                /* q now empty? */
                WriteW ((AR + 1) & LA_MASK, (AR + 1) & R_MASK);
            PR = (PR + 1) & LA_MASK;                    /* skip */
            }
        break;

    case 007:                                           /* TRSLT 105467 (OP_V) */
        wc = ReadW (op[0].word);                        /* get count */
        if (wc & D16_SIGN) break;                       /* cnt < 0? */
        while (wc != 0) {                               /* loop */
            MA = (AR + AR + ReadB (BR)) & LA_MASK;
            byte = ReadB (MA);                          /* xlate */
            WriteB (BR, byte);                          /* store char */
            BR = (BR + 1) & R_MASK;                     /* incr ptr */
            wc = (wc - 1) & D16_MASK;                   /* decr cnt */
            if (wc && intrq) {                          /* more and intr? */
                WriteW (op[0].word, wc);                /* save count */
                PR = err_PR;                            /* stop for now */
                break;
                }
            }
        break;

    case 010:                                           /* ILIST 105470 (OP_AC) */
        do {                                            /* for count */
            WriteW (op[0].word, AR);                    /* write AR to mem */
            AR = (AR + 1) & R_MASK;                     /* incr AR */
            op[0].word = (op[0].word + 1) & LA_MASK;    /* incr MA */
            op[1].word = (op[1].word - 1) & D16_MASK;   /* decr count */
            }
        while (op[1].word != 0);
        break;

    case 011:                                           /* PRFEI 105471 (OP_CVA) */
        WriteW (op[1].word, 1);                         /* set flag */
        reason = cpu_iog (op[0].word);                  /* execute I/O instr */
        op[0].word = op[2].word;                        /* set rtn and fall through */

    case 012:                                           /* PRFEX 105472 (OP_A) */
        PCQ_ENTRY;
        PR = ReadW (op[0].word) & LA_MASK;              /* jump indirect */
        WriteW (op[0].word, 0);                         /* clear exit */
        break;

    case 013:                                           /* PRFIO 105473 (OP_CV) */
        WriteW (op[1].word, 1);                         /* set flag */
        reason = cpu_iog (op[0].word);                  /* execute instr */
        break;

    case 014:                                           /* SAVE 105474 (OP_N) */
        WriteW (SPR, AR);                               /* save A */
        SPR = (SPR + 1) & LA_MASK;                      /* incr stack ptr */
        WriteW (SPR, BR);                               /* save B */
        SPR = (SPR + 1) & LA_MASK;                      /* incr stack ptr */
        t = (HP_WORD) ((O ^ 1) << 1 | E);               /* merge E and O */
        WriteW (SPR, t);                                /* save E and O */
        SPR = (SPR + 1) & LA_MASK;                      /* incr stack ptr */
        if (cpu_configuration & CPU_2100)               /* 2100 keeps sp in MP FR */
            mp_fence = SPR;                             /*   (in case MP is turned on) */
        break;

    default:                                            /* instruction unimplemented */
        return STOP (cpu_ss_unimpl);
        }

return reason;
}
