/* hp2100_cpu6.c: HP 1000 RTE-6/VM OS instructions

   Copyright (c) 2006-2012, J. David Bryan

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

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

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

   Except as contained in this notice, the name of the author shall not be
   used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from the author.

   CPU6         RTE-6/VM OS instructions

   09-May-12    JDB     Separated assignments from conditional expressions
   29-Oct-10    JDB     DMA channels renamed from 0,1 to 1,2 to match documentation
   18-Sep-08    JDB     Corrected .SIP debug formatting
   11-Sep-08    JDB     Moved microcode function prototypes to hp2100_cpu1.h
   05-Sep-08    JDB     Removed option-present tests (now in UIG dispatchers)
   26-Jun-08    JDB     Rewrote device I/O to model backplane signals
   27-Nov-07    JDB     Implemented OS instructions
   26-Sep-06    JDB     Created

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

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


#include <setjmp.h>
#include "hp2100_defs.h"
#include "hp2100_cpu.h"
#include "hp2100_cpu1.h"


/* external variables */

extern jmp_buf save_env;                                /* MP abort handler */


/* Offsets to data and addresses within RTE. */

static const uint32 xi    = 0001647;                    /* XI address */
static const uint32 intba = 0001654;                    /* INTBA address */
static const uint32 intlg = 0001655;                    /* INTLG address */
static const uint32 eqt1  = 0001660;                    /* EQT1  address */
static const uint32 eqt11 = 0001672;                    /* EQT11 address */
static const uint32 pvcn  = 0001712;                    /* PVCN  address */
static const uint32 xsusp = 0001730;                    /* XSUSP address */
static const uint32 dummy = 0001737;                    /* DUMMY address */
static const uint32 mptfl = 0001770;                    /* MPTFL address */
static const uint32 eqt12 = 0001771;                    /* EQT12 address */
static const uint32 eqt15 = 0001774;                    /* EQT15 address */
static const uint32 vctr  = 0002000;                    /* VCTR address */

static const uint32 CLC_0   = 0004700;                  /* CLC 0 instruction */
static const uint32 STC_0   = 0000700;                  /* STC 0 instruction */
static const uint32 CLF_0   = 0001100;                  /* CLF 0 instruction */
static const uint32 STF_0   = 0000100;                  /* STF 0 instruction */
static const uint32 SFS_0_C = 0003300;                  /* SFS 0,C instruction */

enum vctr_offsets { dms_offset = 0,                     /* DMS status */
                    int_offset,                         /* interrupt system status */
                    sc_offset,                          /* select code */
                    clck_offset,                        /* TBG IRQ handler */
                    cic4_offset,                        /* illegal IRQ handler */
                    cic2_offset,                        /* device IRQ handler */
                    sked_offset,                        /* prog sched IRQ handler */
                    rqst_offset,                        /* EXEC request handler */
                    cic_offset,                         /* IRQ location */
                    perr_offset,                        /* parity error IRQ handler */
                    mper_offset,                        /* memory protect IRQ handler */
                    lxnd_offset };                      /* $LIBR return */


/* RTE-6/VM Operating System Instructions

   The OS instructions were added to acccelerate certain time-consuming
   operations of the RTE-6/VM operating system, HP product number 92084A.
   Microcode was available for the E- and F-Series; the M-Series used software
   equivalents.

   Option implementation by CPU was as follows:

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

   The routines are mapped to instruction codes as follows:

     Instr.  1000-E/F   Description
     ------  --------  ----------------------------------------------
     $LIBR    105340   Enter privileged/reentrant library routine
     $LIBX    105341   Exit privileged/reentrant library routine
     .TICK    105342   TBG tick interrupt handler
     .TNAM    105343   Find ID segment that matches name
     .STIO    105344   Configure I/O instructions
     .FNW     105345   Find word with user increment
     .IRT     105346   Interrupt return processing
     .LLS     105347   Linked list search

     .SIP     105350   Skip if interrupt pending
     .YLD     105351   .SIP completion return point
     .CPM     105352   Compare words LT/EQ/GT
     .ETEQ    105353   Set up EQT pointers in base page
     .ENTN    105354   Transfer parameter addresses (utility)
     $OTST *  105355   OS firmware self test
     .ENTC    105356   Transfer parameter addresses (priv/reent)
     .DSPI    105357   Set display indicator

   Opcodes 105354-105357 are "dual use" instructions that take different
   actions, depending on whether they are executed from a trap cell during an
   interrupt.  When executed from a trap cell, they have these actions:

     Instr.  1000-E/F   Description
     ------  --------  ----------------------------------------------
     $DCPC *  105354   DCPC channel interrupt processing
     $MPV  *  105355   MP/DMS/PE interrupt processing
     $DEV  *  105356   Standard device interrupt processing
     $TBG  *  105357   TBG interrupt processing

   * These mnemonics are recognized by symbolic examine/deposit but are not
     official HP mnemonics.

   Implementation notes:

    1. The microcode differentiates between interrupt processing and normal
       execution of the "dual use" instructions by testing the CPU flag.
       Interrupt vectoring sets the flag; a normal instruction fetch clears it.
       Under simulation, interrupt vectoring is indicated by the value of the
       "iotrap" parameter (0 = normal instruction, 1 = trap cell instruction).

    2. The operand patterns for .ENTN and .ENTC normally would be coded as
       "OP_A", as each takes a single address as a parameter.  However, because
       they might also be executed from a trap cell, we cannot assume that P+1
       is an address, or we might cause a DM abort when trying to resolve
       indirects.  Therefore, "OP_A" handling is done within each routine, once
       the type of use is determined.

    3. The microcode for .ENTC, .ENTN, .FNW, .LLS, .TICK, and .TNAM explicitly
       checks for interrupts during instruction execution.  In addition, the
       .STIO, .CPM, and .LLS instructions implicitly check for interrupts during
       parameter indirect resolution.  Because the simulator calculates
       interrupt requests only between instructions, this behavior is not
       simulated.

    4. The microcode executes certain I/O instructions (e.g., CLF 0) by building
       the instruction in the IR and executing an IOG micro-order.  We simulate
       this behavior by calling the "iogrp" handler with the appropriate
       instruction, rather than manipulating the I/O system directly, so that we
       will remain unaffected by any future changes to the underlying I/O
       simulation structure.

    5. The $OTST and .DSPI microcode uses features (reading the RPL switches and
       boot loader ROM data, loading the display register) that are not
       simulated.  The remaining functions of the $OTST instruction are
       provided. The .DSPI instruction is a NOP or unimplemented instruction
       stop.

    6. Because of the volume of calls to the OS firmware, debug printouts
       attempt to write only one line per instruction invocation.  This means
       that calling and returned register values are printed separately, with a
       newline added at the end of execution.  However, many instructions can MP
       or DM abort, either intentionally or due to improper use.  That would
       leave debug lines without the required trailing newlines.

       There are two ways to address this: either we could replace the CPU's
       setjmp buffer with one that points to a routine that adds the missing
       newline, or we can add a semaphore that is tested on entry to see if it
       is already set, implying a longjmp occurred, and then add the newline if
       so.  The latter would add the newline when the MP trap cell instruction
       was entered or when the next user-level instruction was executed.
       However, the merged-line problem would still exist if some other module
       generated intervening debug printouts.  So we do the former.  This does
       mean that this routine must be changed if the MP abort mechanism is
       changed.

    7. The $LIBX instruction is executed to complete either a privileged or
       reentrant execution.  In the former case, the privileged nest counter
       ($PVCN) is decremented.  In the latter, $PVCN decrement is attempted but
       the write will trap with an MP violation, as reentrant routines execute
       with the interrupt system on.  RTE will then complete the release of
       memory allocated for the original $LIBR call.

    8. The documentation for the .SIP and .YLD instructions is misleading in
       several places.  Comments in the RTE $SIP source file say that .SIP
       doesn't return if a "known" interrupt is pending.  Actually, .SIP always
       returns, either to P+1 for no pending interrupt, or to P+2 if one is
       pending.  There is no check for "known" interrupt handlers.  The
       microcode source comments say that the interrupting select code is
       returned in the B register.  Actually, the B register is unchanged.  The
       RTE Tech Specs say that .SIP "services any pending system interrupts."
       Actually, .SIP only checks for interrupts; no servicing is performed.

       For .YLD, the microcode comments say that two parameters are passed: the
       new P value, and the interrupting select code.  Actually, only the new P
       value is passed.

       The .SIP and .YLD simulations follow the actual microcode rather than the
       documentation.

   Additional references:
    - RTE-6/VM OS Microcode Source (92084-18831, revision 8).
    - RTE-6/VM Technical Specifications (92084-90015, Apr-1983).
*/


/* Save the CPU registers.

   The CPU registers are saved in the current ID segment in preparation for
   interrupt handling.  Although the RTE base page has separate pointers for the
   P, A, B, and E/O registers, they are always contiguous, and the microcode
   simply increments the P-register pointer (XSUSP) to store the remaining
   values.

   This routine is called from the trap cell interrupt handlers and from the
   $LIBX processor.  In the latter case, the privileged system interrupt
   handling is not required, so it is bypassed.  In either case, the current map
   will be the system map when we are called.
*/

static t_stat cpu_save_regs (uint32 iotrap)
{
uint16 save_area, priv_fence;
t_stat reason = SCPE_OK;

save_area = ReadW (xsusp);                              /* addr of PABEO save area */

WriteW (save_area + 0, PC);                             /* save P */
WriteW (save_area + 1, AR);                             /* save A */
WriteW (save_area + 2, BR);                             /* save B */
WriteW (save_area + 3, (E << 15) & SIGN | O & 1);       /* save E and O */

save_area = ReadW (xi);                                 /* addr of XY save area */
WriteWA (save_area + 0, XR);                            /* save X (in user map) */
WriteWA (save_area + 1, YR);                            /* save Y (in user map) */

if (iotrap) {                                           /* do priv setup only if IRQ */
    priv_fence = ReadW (dummy);                         /* get priv fence select code */

    if (priv_fence) {                                   /* privileged system? */
        reason = iogrp (STC_0 + priv_fence, iotrap);    /* STC SC on priv fence */
        reason = iogrp (CLC_0 + DMA1, iotrap);          /* CLC 6 to inh IRQ on DCPC 1 */
        reason = iogrp (CLC_0 + DMA2, iotrap);          /* CLC 7 to inh IRQ on DCPC 2 */
        reason = iogrp (STF_0, iotrap);                 /* turn interrupt system back on */
        }
    }

return reason;
}


/* Save the machine state at interrupt.

   This routine is called from each of the trap cell instructions.  Its purpose
   is to save the complete state of the machine in preparation for interrupt
   handling.

   For the MP/DMS/PE interrupt, the interrupting device must not be cleared and
   the CPU registers must not be saved until it is established that the
   interrupt is not caused by a parity error.  Parity errors cannot be
   inhibited, so the interrupt may have occurred while in RTE.  Saving the
   registers would overwrite the user's registers that were saved at RTE entry.

   Note that the trap cell instructions are dual-use and invoke this routine
   only when they are executed during interrupts.  Therefore, the current map
   will always be the system map when we are called.
*/

static t_stat cpu_save_state (uint32 iotrap)
{
uint16 vectors;
uint32 saved_PC, int_sys_off;
t_stat reason;

saved_PC = PC;                                          /* save current PC */
reason = iogrp (SFS_0_C, iotrap);                       /* turn interrupt system off */
int_sys_off = (PC == saved_PC);                         /* set flag if already off */
PC = saved_PC;                                          /* restore PC in case it bumped */

vectors = ReadW (vctr);                                 /* get address of vectors (in SMAP) */

WriteW (vectors + dms_offset, dms_upd_sr ());           /* save DMS status (SSM) */
WriteW (vectors + int_offset, int_sys_off);             /* save int status */
WriteW (vectors + sc_offset,  intaddr);                 /* save select code */

WriteW (mptfl, 1);                                      /* show MP is off */

if (intaddr != 5) {                                     /* only if not MP interrupt */
    reason = iogrp (CLF_0 + intaddr, iotrap);           /* issue CLF to device */
    cpu_save_regs (iotrap);                             /* save CPU registers */
    }

return reason;
}


/* Get the interrupt table entry corresponding to a select code.

   Return the word in the RTE interrupt table that corresponds to the
   interrupting select code.  Return 0 if the select code is beyond the end of
   the table.
*/

uint32 cpu_get_intbl (uint32 select_code)
{
uint16 interrupt_table;                                 /* interrupt table (starts with SC 06) */
uint16 table_length;                                    /* length of interrupt table */

interrupt_table = ReadW (intba);                        /* get int table address */
table_length = ReadW (intlg);                           /* get int table length */

if (select_code - 6 > table_length)                     /* SC beyond end of table? */
    return 0;                                           /* return 0 for illegal interrupt */
else
    return ReadW (interrupt_table + select_code - 6);   /* else return table entry */
}


/* RTE-6/VM OS instruction dispatcher.

   Debugging printouts are provided with the OS and OSTBG debug flags.  The OS
   flag enables tracing for all instructions except for the three-instruction
   sequence executed for the time-base generator interrupt ($TBG, .TICK, and
   .IRT).  The OSTBG flag enables tracing for just the TBG sequence.  The flags
   are separate, as the TBG generates 100 interrupts per second.  Use caution
   when specifying the OSTBG flag, as the debug output file will grow rapidly.
   Note that the OS flag enables the .IRT instruction trace for all cases except
   a TBG interrupt.

   The default (user microcode) dispatcher will allow the firmware self-test
   instruction (105355) to execute as NOP.  This is because RTE-6/VM will always
   test for the presence of OS and VMA firmware on E/F-Series machines.  If the
   firmware is not present, then these instructions will return to P+1, and RTE
   will then HLT 21.  This means that RTE-6/VM will not run on an E/F-Series
   machine without the OS and VMA firmware.

   Howwever, RTE allows the firmware instructions to be disabled for debugging
   purposes.  If the firmware is present and returns to P+2 but sets the X
   register to 0, then RTE will use software equivalents.  We enable this
   condition when the OS firmware is enabled (SET CPU VMA), the OS debug flag is
   set (SET CPU DEBUG=OS), but debug output has been disabled (SET CONSOLE
   NODEBUG).  That is:

                 OS     Debug
     Firmware   Debug   Output   Tracing   Self-Test Instruction
     ========   =====   ======   =======   =====================
     disabled     x       x        off     NOP
     enabled    clear     x        off     X = revision code
     enabled     set     off       off     X = 0
     enabled     set     on        on      X = revision code
*/

static const OP_PAT op_os[16] = {
  OP_A,    OP_A,    OP_N,    OP_N,                      /* $LIBR  $LIBX  .TICK  .TNAM  */
  OP_A,    OP_K,    OP_A,    OP_KK,                     /* .STIO  .FNW   .IRT   .LLS   */
  OP_N,    OP_C,    OP_KK,   OP_N,                      /* .SIP   .YLD   .CPM   .ETEQ  */
  OP_N,    OP_N,    OP_N,    OP_N                       /* .ENTN  $OTST  .ENTC  .DSPI  */
  };

t_stat cpu_rte_os (uint32 IR, uint32 intrq, uint32 iotrap)
{
t_stat reason = SCPE_OK;
OPS op;
OP_PAT pattern;
uint32 entry, count, cp, sa, da, i, ma;
uint16 vectors, save_area, priv_fence, eoreg, eqt, key;
char test[6], target[6];
jmp_buf mp_handler;
int abortval;
t_bool debug_print;
static t_bool tbg_tick = FALSE;                         /* set if processing TBG interrupt */

entry = IR & 017;                                       /* mask to entry point */
pattern = op_os[entry];                                 /* get operand pattern */

if (pattern != OP_N) {
    reason = cpu_ops (pattern, op, intrq);              /* get instruction operands */

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

tbg_tick = tbg_tick || (IR == 0105357) && iotrap;       /* set TBG interrupting flag */

debug_print = (DEBUG_PRI (cpu_dev, DEB_OS) && !tbg_tick) ||
              (DEBUG_PRI (cpu_dev, DEB_OSTBG) && tbg_tick);

if (debug_print) {
    fprintf (sim_deb, ">>CPU OS: IR = %06o (", IR);     /* print preamble and IR */
    fprint_sym (sim_deb, (iotrap ? intaddr : err_PC),   /* print instruction mnemonic */
                (t_value *) &IR, NULL, SWMASK('M'));
    fputc (')', sim_deb);

    fprint_ops (pattern, op);                           /* print operands */

    memcpy (mp_handler, save_env, sizeof (jmp_buf));    /* save original MP handler */
    abortval = setjmp (save_env);                       /* set new MP abort handler */

    if (abortval != 0) {                                /* MP abort? */
        fputs ("...MP abort\n", sim_deb);               /* report it and terminate line */
        memcpy (save_env, mp_handler, sizeof (jmp_buf));    /* restore original MP handler */
        longjmp (save_env, abortval);                       /* transfer to MP handler */
        }
    }

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

    case 000:                                           /* $LIBR 105340 (OP_A) */
        if ((op[0].word != 0) ||                        /* reentrant call? */
            (mp_control && (ReadW (dummy) != 0))) {     /* or priv call + MP on + priv sys? */
            if (dms_ump) {                              /* called from user map? */
                dms_viol (err_PC, MVI_PRV);             /* privilege violation */
                }
            dms_ump = SMAP;                             /* set system map */

            vectors = ReadW (vctr);                     /* get address of vectors (in SMAP) */
            PC = ReadW (vectors + mper_offset);         /* vector to $MPER for processing */
            }

        else {                                          /* privileged call */
            if (mp_control) {                           /* memory protect on? */
                mp_control = CLEAR;                     /* turn it off */
                reason = iogrp (CLF_0, iotrap);         /* turn interrupt system off */
                WriteW (mptfl, 1);                      /* show MP is off */
                save_area = ReadW (xsusp);              /* get addr of P save area */

                if (dms_ump)                                /* user map current? */
                    WriteWA (save_area, (PC - 2) & VAMASK); /* set point of suspension */
                else                                        /* system map current */
                    WriteW (save_area, (PC - 2) & VAMASK);  /* set point of suspension */
                }

            WriteW (pvcn, (ReadW (pvcn) + 1) & DMASK);  /* increment priv nest counter */
            }
        break;

    case 001:                                           /* $LIBX 105341 (OP_A) */
        PC = ReadW (op[0].word);                        /* set P to return point */
        count = (ReadW (pvcn) - 1) & DMASK;             /* decrement priv nest counter */
        WriteW (pvcn, count);                           /* write it back */

        if (count == 0) {                               /* end of priv mode? */
            dms_ump = SMAP;                             /* set system map */
            reason = cpu_save_regs (iotrap);            /* save registers */
            vectors = ReadW (vctr);                     /* get address of vectors */
            PC = ReadW (vectors + lxnd_offset);         /* vector to $LXND for processing */
            }
        break;

    case 002:                                           /* .TICK 105342 (OP_N) */
        if (debug_print)                                /* debugging? */
            fprint_regs (",", REG_A | REG_B, 0);        /* print entry registers */

        do {
            eqt = (ReadW (AR) + 1) & DMASK;             /* bump timeout from EQT15 */

            if (eqt != 1) {                             /* was timeout active? */
                WriteW (AR, eqt);                       /* yes, write it back */

                if (eqt == 0)                           /* did timeout expire? */
                    break;                              /* P+0 return for timeout */
                }

            AR = (AR + 15) & DMASK;                     /* point at next EQT15 */
            BR = (BR - 1) & DMASK;                      /* decrement count of EQTs */
        } while ((BR > 0) && (eqt != 0));               /* loop until timeout or done */

        if (BR == 0)                                    /* which termination condition? */
            PC = (PC + 1) & VAMASK;                     /* P+1 return for no timeout */

        if (debug_print)                                /* debugging? */
            fprint_regs ("; result:",                   /* print return registers */
                         REG_A | REG_B | REG_P_REL,
                         err_PC + 1);
        break;

    case 003:                                           /* .TNAM 105343 (OP_N) */
        if (debug_print)                                /* debugging? */
            fprint_regs (",", REG_A | REG_B, 0);        /* print entry registers */

        E = 1;                                          /* preset flag for not found */
        cp = (BR << 1) & DMASK;                         /* form char addr (B is direct) */

        for (i = 0; i < 5; i++) {                       /* copy target name */
            target[i] = (char) ReadB (cp);              /* name is only 5 chars */
            cp = (cp + 1) & DMASK;
            }

        if ((target[0] == '\0') && (target[1] == '\0')) /* if name is null, */
            break;                                      /* return immed to P+0 */

        key = ReadW (AR);                               /* get first keyword addr */

        while (key != 0) {                              /* end of keywords? */
            cp = ((key + 12) << 1) & DMASK;             /* form char addr of name */

            for (i = 0; i < 6; i++) {                   /* copy test name */
                test[i] = (char) ReadB (cp);            /* name is only 5 chars */
                cp = (cp + 1) & DMASK;                  /* but copy 6 to get flags */
                }

            if (strncmp (target, test, 5) == 0) {       /* names match? */
                AR = (key + 15) & DMASK;                /* A = addr of IDSEG [15] */
                BR = key;                               /* B = addr of IDSEG [0] */
                E = (uint32) ((test[5] >> 4) & 1);      /* E = short ID segment bit */
                PC = (PC + 1) & VAMASK;                 /* P+1 for found return */
                break;
                }

            AR = (AR + 1) & DMASK;                      /* bump to next keyword */
            key = ReadW (AR);                           /* get next keyword */
            };

        if (debug_print)                                /* debugging? */
            fprint_regs ("; result:",                   /* print return registers */
                         REG_A | REG_B | REG_E | REG_P_REL,
                         err_PC + 1);
        break;

    case 004:                                           /* .STIO 105344 (OP_A) */
        count = op[0].word - PC;                        /* get count of operands */

        if (debug_print)                                /* debugging? */
            fprintf (sim_deb,                           /* print registers on entry */
                     ", A = %06o, count = %d", AR, count);

        for (i = 0; i < count; i++) {
            ma = ReadW (PC);                            /* get operand address */

            reason = resolve (ma, &ma, intrq);          /* resolve indirect */

            if (reason != SCPE_OK) {                    /* resolution failed? */
                PC = err_PC;                            /* IRQ restarts instruction */
                break;
                }

            WriteW (ma, ReadW (ma) & ~I_DEVMASK | AR);  /* set SC into instruction */
            PC = (PC + 1) & VAMASK;                     /* bump to next */
            }
        break;

    case 005:                                           /* .FNW  105345 (OP_K) */
        if (debug_print)                                    /* debugging? */
            fprint_regs (",", REG_A | REG_B | REG_X, 0);    /* print entry registers */

        while (XR != 0) {                               /* all comparisons done? */
            key = ReadW (BR);                           /* read a buffer word */

            if (key == AR) {                            /* does it match? */
                PC = (PC + 1) & VAMASK;                 /* P+1 found return */
                break;
                }

            BR = (BR + op[0].word) & DMASK;             /* increment buffer ptr */
            XR = (XR - 1) & DMASK;                      /* decrement remaining count */
            }
                                                        /* P+0 not found return */
        if (debug_print)                                /* debugging? */
            fprint_regs ("; result:",                   /* print return registers */
                         REG_A | REG_B | REG_X | REG_P_REL,
                         err_PC + 2);
        break;

    case 006:                                           /* .IRT  105346 (OP_A) */
        save_area = ReadW (xsusp);                      /* addr of PABEO save area */

        WriteW (op[0].word, ReadW (save_area + 0));     /* restore P to DEF RTN */

        AR = ReadW (save_area + 1);                     /* restore A */
        BR = ReadW (save_area + 2);                     /* restore B */

        eoreg = ReadW (save_area + 3);                  /* get combined E and O */
        E = (eoreg >> 15) & 1;                          /* restore E */
        O = eoreg & 1;                                  /* restore O */

        save_area = ReadW (xi);                         /* addr of XY save area */
        XR = ReadWA (save_area + 0);                    /* restore X (from user map) */
        YR = ReadWA (save_area + 1);                    /* restore Y (from user map) */

        reason = iogrp (CLF_0, iotrap);                 /* turn interrupt system off */
        WriteW (mptfl, 0);                              /* show MP is on */

        priv_fence = ReadW (dummy);                     /* get priv fence select code */

        if (priv_fence) {                               /* privileged system? */
            reason = iogrp (CLC_0 + priv_fence, iotrap);    /* CLC SC on priv fence */
            reason = iogrp (STF_0 + priv_fence, iotrap);    /* STF SC on priv fence */

            if (cpu_get_intbl (DMA1) & SIGN)            /* DCPC 1 active? */
                reason = iogrp (STC_0 + DMA1, iotrap);  /* STC 6 to enable IRQ on DCPC 1 */

            if (cpu_get_intbl (DMA2) & SIGN)            /* DCPC 2 active? */
                reason = iogrp (STC_0 + DMA2, iotrap);  /* STC 7 to enable IRQ on DCPC 2 */
            }

        tbg_tick = 0;                                   /* .IRT terminates TBG servicing */
        break;

    case 007:                                           /* .LLS  105347 (OP_KK) */
        if (debug_print)                                    /* debugging? */
            fprint_regs (",", REG_A | REG_B | REG_E, 0);    /* print entry registers */

        AR = AR & ~SIGN;                                /* clear sign bit of A */

        while ((AR != 0) && ((AR & SIGN) == 0)) {       /* end of list or bad list? */
            key = ReadW ((AR + op[1].word) & VAMASK);   /* get key value */

            if ((E == 0) && (key == op[0].word) ||      /* for E = 0, key = arg? */
                (E != 0) && (key >  op[0].word))        /* for E = 1, key > arg? */
                break;                                  /* search is done */

            BR = AR;                                    /* B = last link */
            AR = ReadW (AR);                            /* A = next link */
            }

        if (AR == 0)                                    /* exhausted list? */
            PC = (PC + 1) & VAMASK;                     /* P+1 arg not found */
        else if ((AR & SIGN) == 0)                      /* good link? */
            PC = (PC + 2) & VAMASK;                     /* P+2 arg found */
                                                        /* P+0 bad link */
        if (debug_print)                                /* debugging? */
            fprint_regs ("; result:",                   /* print return registers */
                         REG_A | REG_B | REG_P_REL,
                         err_PC + 3);
        break;

    case 010:                                           /* .SIP  105350 (OP_N) */
        reason = iogrp (STF_0, iotrap);                 /* turn interrupt system on */
        intrq = calc_int ();                            /* check for interrupt requests */
        reason = iogrp (CLF_0, iotrap);                 /* turn interrupt system off */

        if (intrq)                                      /* was interrupt pending? */
            PC = (PC + 1) & VAMASK;                     /* P+1 return for pending IRQ */
                                                        /* P+0 return for no pending IRQ */
        if (debug_print)                                /* debugging? */
            fprintf (sim_deb,                           /* print return registers */
                     ", CIR = %02o, return = P+%d",
                     intrq, PC - (err_PC + 1));
        break;

    case 011:                                           /* .YLD  105351 (OP_C) */
        PC = op[0].word;                                /* pick up point of resumption */
        reason = iogrp (STF_0, iotrap);                 /* turn interrupt system on */
        ion_defer = 0;                                  /* kill defer so irq occurs immed */
        break;

    case 012:                                           /* .CPM  105352 (OP_KK) */
        if (INT16 (op[0].word) > INT16 (op[1].word))
            PC = (PC + 2) & VAMASK;                     /* P+2 arg1 > arg2 */
        else if (INT16 (op[0].word) < INT16 (op[1].word))
            PC = (PC + 1) & VAMASK;                     /* P+1 arg1 < arg2 */
                                                        /* P+0 arg1 = arg2 */
        if (debug_print)                                /* debugging? */
            fprint_regs (",", REG_P_REL, err_PC + 3);   /* print return registers */
        break;

    case 013:                                           /* .ETEQ 105353 (OP_N) */
        eqt = ReadW (eqt1);                             /* get addr of EQT1 */

        if (AR != eqt) {                                /* already set up? */
            for (eqt = eqt1; eqt <= eqt11; eqt++)       /* init EQT1-EQT11 */
                WriteW (eqt & VAMASK, (AR++ & DMASK));
            for (eqt = eqt12; eqt <= eqt15; eqt++)      /* init EQT12-EQT15 */
                WriteW (eqt & VAMASK, (AR++ & DMASK));  /* (not contig with EQT1-11) */
            }

        if (debug_print)                                /* debugging? */
            fprintf (sim_deb,                           /* print return registers */
                     ", A = %06o, EQT1 = %06o", AR, eqt);
        break;

    case 014:                                           /* .ENTN/$DCPC 105354 (OP_N) */
        if (iotrap) {                                   /* in trap cell? */
            reason = cpu_save_state (iotrap);           /* DMA interrupt */
            AR = cpu_get_intbl (intaddr) & ~SIGN;       /* get intbl value and strip sign */
            goto DEVINT;                                /* vector by intbl value */
            }

        else {                                          /* .ENTN instruction */
            ma = (PC - 2) & VAMASK;                     /* get addr of entry point */

        ENTX:                                           /* enter here from .ENTC */
            reason = cpu_ops (OP_A, op, intrq);         /* get instruction operand */
            da = op[0].word;                            /* get addr of 1st formal */
            count = ma - da;                            /* get count of formals */
            sa = ReadW (ma);                            /* get addr of 1st actual */
            WriteW (ma, (sa + count) & VAMASK);         /* adjust return point to skip actuals */

            if (debug_print)                            /* debugging? */
                fprintf (sim_deb,                       /* print entry registers */
                         ", op [0] = %06o, pcount = %d",
                         da, count);

            for (i = 0; i < count; i++) {               /* parameter loop */
                ma = ReadW (sa);                        /* get addr of actual */
                sa = (sa + 1) & VAMASK;                 /* increment address */

                reason = resolve (ma, &ma, intrq);      /* resolve indirect */

                if (reason != SCPE_OK) {                /* resolution failed? */
                    PC = err_PC;                        /* irq restarts instruction */
                    break;
                    }

                WriteW (da, ma);                        /* put addr into formal */
                da = (da + 1) & VAMASK;                 /* increment address */
                }

            if (entry == 016)                           /* call was .ENTC? */
                AR = sa;                                /* set A to return address */
            }
        break;

    case 015:                                           /* $OTST/$MPV 105355 (OP_N) */
        if (iotrap) {                                   /* in trap cell? */
            reason = cpu_save_state (iotrap);           /* MP/DMS/PE interrupt */
            vectors = ReadW (vctr);                     /* get address of vectors (in SMAP) */

            if (mp_viol & SIGN) {                       /* parity error? */
                WriteW (vectors + cic_offset, PC);      /* save point of suspension in $CIC */
                PC = ReadW (vectors + perr_offset);     /* vector to $PERR for processing */
                }

            else {                                      /* MP/DMS violation */
                cpu_save_regs (iotrap);                 /* save CPU registers */
                PC = ReadW (vectors + rqst_offset);     /* vector to $RQST for processing */
                }

            if (debug_print) {                          /* debugging? */
                fprint_regs (",", REG_CIR, 0);          /* print interrupt source */
                                                        /* and cause */
                if (mp_viol & SIGN)
                    fputs (", parity error", sim_deb);
                else if (mp_mevff)
                    fputs (", DM violation", sim_deb);
                else
                    fputs (", MP violation", sim_deb);
                }
            }

        else {                                          /* self-test instruction */
            YR = 0000000;                               /* RPL switch (not implemented) */
            AR = 0000000;                               /* LDR [B] (not implemented) */
            SR = 0102077;                               /* test passed code */
            PC = (PC + 1) & VAMASK;                     /* P+1 return for firmware OK */

            if ((cpu_dev.dctrl & DEB_OS) &&             /* OS debug flag set, */
                (sim_deb == NULL))                      /* but debugging disabled? */
                XR = 0;                                 /* rev = 0 means RTE won't use ucode */
            else
                XR = 010;                               /* firmware revision 10B = 8 */

            if (debug_print)                            /* debugging? */
                fprint_regs (",", REG_X | REG_P_REL,    /* print return registers */
                             err_PC + 1);
            }
        break;

    case 016:                                           /* .ENTC/$DEV 105356 (OP_N) */
        if (iotrap) {                                   /* in trap cell? */
            reason = cpu_save_state (iotrap);           /* device interrupt */
            AR = cpu_get_intbl (intaddr);               /* get interrupt table value */

        DEVINT:
            vectors = ReadW (vctr);                     /* get address of vectors (in SMAP) */

            if (INT16 (AR) < 0)                         /* negative (program ID)? */
                PC = ReadW (vectors + sked_offset);     /* vector to $SKED for processing */
            else if (AR > 0)                            /* positive (EQT address)? */
                PC = ReadW (vectors + cic2_offset);     /* vector to $CIC2 for processing */
            else                                        /* zero (illegal interrupt) */
                PC = ReadW (vectors + cic4_offset);     /* vector to $CIC4 for processing */

            if (debug_print)                            /* debugging? */
                fprintf (sim_deb,                       /* print return registers */
                         ", CIR = %02o, INTBL = %06o",
                         intaddr, AR);
            }

        else {                                          /* .ENTC instruction */
            ma = (PC - 4) & VAMASK;                     /* get addr of entry point */
            goto ENTX;                                  /* continue with common processing */
            }
        break;

    case 017:                                           /* .DSPI/$TBG 105357 (OP_N) */
        if (iotrap) {                                   /* in trap cell? */
            reason = cpu_save_state (iotrap);           /* TBG interrupt */
            vectors = ReadW (vctr);                     /* get address of vectors (in SMAP) */
            PC = ReadW (vectors + clck_offset);         /* vector to $CLCK for processing */

            if (debug_print)                            /* debugging? */
                fprint_regs (",", REG_CIR, 0);          /* print interrupt source */
            }

        else                                            /* .DSPI instruction */
            reason = stop_inst;                         /* not implemented yet */

        break;
    }

if (debug_print) {                                      /* debugging? */
    fputc ('\n', sim_deb);                              /* terminate line */
    memcpy (save_env, mp_handler, sizeof (jmp_buf));    /* restore original MP handler */
    }

return reason;
}
