blob: e81c2e1ae869e462fc4f5ebb15fe46a29489079c [file] [log] [blame] [raw]
/* hp3000_cpu_base.c: HP 3000 CPU base set instruction simulator
Copyright (c) 2016, 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.
11-Jun-16 JDB Bit mask constants are now unsigned
13-Jan-16 JDB First release version
11-Dec-12 JDB Created
References:
- HP 3000 Series II System Microprogram Listing
(30000-90023, August 1976)
- HP 3000 Series II/III System Reference Manual
(30000-90020, July 1978)
- Machine Instruction Set Reference Manual
(30000-90022, June 1984)
This module implements all of the HP 3000 Series II/III base set
instructions, except for the memory address instructions, which are
implemented in the main CPU module.
Implementation notes:
1. Each instruction executor begins with a comment listing the instruction
mnemonic and, following in parentheses, the condition code setting, or
"none" if the condition code is not altered, and a list of any traps that
might be generated. The condition code and trap mnemonics are those used
in the Machine Instruction Set manual.
2. In the instruction executors, "TOS" refers to the top-of-the-stack value,
and "NOS" refers to the next-to-the-top-of-the-stack value.
3. The order of operations in the executors follows the microcode so that
the registers, condition code, etc. have the expected values if stack
overflow or underflow traps occur.
4. There is no common "cpu_div_16" routine, as each of the five base-set
division instructions (DIVI, DIV, LDIV, DIVL, and DDIV) has a different
overflow condition. Therefore, they are all implemented inline.
*/
#include "hp3000_defs.h"
#include "hp3000_cpu.h"
#include "hp3000_cpu_fp.h"
#include "hp3000_cpu_ims.h"
/* External I/O data structures */
extern DEVICE cpu_dev; /* Central Processing Unit */
/* Program constants */
#define SIO_OK 0100000u /* TIO bit 0 = SIO OK */
#define DIO_OK 0040000u /* TIO bit 1 = DIO OK */
#define NORM_BIT (D48_SIGN >> 6) /* triple normalizing examines bit 6 */
#define NORM_MASK (D48_MASK >> 6) /* triple normalizing masks off bits 0-5 */
#define TO_UPPERCASE(b) ((b) & ~040u) /* alphabetic byte upshift */
/* CPU base set global data structures */
typedef enum { /* types of shifts */
arithmetic, /* arithmetic shift */
logical, /* logical shift */
circular, /* circular shift (rotate) */
normalizing /* normalizing shift */
} SHIFT_TYPE;
typedef enum { /* shift operand sizes */
size_16, /* 16-bit single word */
size_32, /* 32-bit double word */
size_48, /* 48-bit triple word */
size_64 /* 64-bit quad word */
} OPERAND_SIZE;
/* CPU base set local utility routines */
static uint32 add_32 (uint32 augend, uint32 addend);
static uint32 sub_32 (uint32 minuend, uint32 subtrahend);
static void shift_16_32 (HP_WORD opcode, SHIFT_TYPE shift, OPERAND_SIZE op_size);
static void shift_48_64 (HP_WORD opcode, SHIFT_TYPE shift, OPERAND_SIZE op_size);
static void check_stack_bounds (HP_WORD new_value);
static uint32 tcs_io (IO_COMMAND command);
static uint32 srw_io (IO_COMMAND command, HP_WORD ready_flag);
static t_bool interrupt_pending (t_stat *status);
static void decrement_stack (uint32 decrement);
static uint32 byte_to_word_address (ACCESS_CLASS class, uint32 byte_offset, uint32 block_length);
static t_stat move_words (ACCESS_CLASS source_class, uint32 source_base,
ACCESS_CLASS dest_class, uint32 dest_base,
uint32 decrement);
/* CPU base set local instruction execution routines */
static t_stat move_spec (void);
static t_stat firmware_extension (void);
static t_stat io_control (void);
/* CPU base set global utility routines */
/* Execute a short branch.
The program counter is adjusted by the displacement specified in the CIR, and
the NIR is loaded with the target instruction. If the "check_loop" parameter
is TRUE, an infinite loop check is made if the corresponding simulator stop
is enabled. Branch instructions that cannot cause an infinite loop because
they modify the CPU state during execution will specify the parameter as
FALSE.
On entry, the CIR must be loaded with a branch instruction having a short
(5-bit plus sign bit) displacement. The instruction format is:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 1 | I | branch opcode |+/-| P displacement | Branch
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
On exit, the NIR and P registers are updated, and STOP_INFLOOP is returned if
an infinite loop is enabled and was detected, or SCPE_OK is returned if
simulation may continue.
*/
t_stat cpu_branch_short (t_bool check_loop)
{
HP_WORD displacement, address;
t_stat status;
displacement = CIR & DISPL_31_MASK; /* get the displacement */
if (CIR & DISPL_31_SIGN) /* if the displacement is negative */
address = P - 2 - displacement & LA_MASK; /* then subtract the displacement from the base */
else /* otherwise */
address = P - 2 + displacement & LA_MASK; /* add the displacement to the base */
if ((CIR & I_FLAG_BIT_4) != 0) { /* if the mode is indirect */
cpu_read_memory (program_checked, address, &displacement); /* then get the displacement value */
address = address + displacement & LA_MASK; /* add the displacement to the base */
}
if (cpu_stop_flags & SS_LOOP /* if the infinite loop stop is active */
&& check_loop /* and an infinite loop is possible */
&& address == (P - 2 & LA_MASK)) /* and the target is the current instruction */
status = STOP_INFLOOP; /* then stop the simulator */
else /* otherwise */
status = SCPE_OK; /* continue */
cpu_read_memory (fetch_checked, address, &NIR); /* load the next instruction register */
P = address + 1 & R_MASK; /* and increment the program counter */
return status; /* return the execution status */
}
/* Add two 16-bit numbers.
Two 16-bit values are added, and the 16-bit sum is returned. The C (carry)
bit in the status register is set if the result is truncated and cleared
otherwise. The O (overflow) bit is set if the result exceeds the maximum
positive or negative range, i.e., the result overflows into the sign bit. In
addition, an integer overflow interrupt (ARITH trap) occurs if the user trap
bit is set.
*/
HP_WORD cpu_add_16 (HP_WORD augend, HP_WORD addend)
{
uint32 sum;
sum = augend + addend; /* sum the values */
SET_CARRY (sum > D16_UMAX); /* set C if there's a carry out of the MSB */
SET_OVERFLOW (D16_SIGN /* set O if the signs */
& (~augend ^ addend) /* of the operands are the same */
& (augend ^ sum)); /* but the sign of the result differs */
return (HP_WORD) LOWER_WORD (sum); /* return the lower 16 bits of the sum */
}
/* Subtract two 16-bit numbers.
Two 16-bit values are subtracted, and the 16-bit difference is returned. The
C (carry) bit in the status register is set if the subtraction did not
require a borrow for the most-significant bit. The O (overflow) bit is set
if the result exceeds the maximum positive or negative range, i.e., the
result borrows from the sign bit. In addition, an integer overflow interrupt
(ARITH trap) occurs if the user trap bit is set.
Implementation notes:
1. The carry bit is set to the complement of the borrow, i.e., carry = 0 if
there is a borrow and 1 is there is not.
*/
HP_WORD cpu_sub_16 (HP_WORD minuend, HP_WORD subtrahend)
{
uint32 difference;
difference = minuend - subtrahend; /* subtract the values */
SET_CARRY (subtrahend <= minuend); /* set C if no borrow from the MSB was done */
SET_OVERFLOW (D16_SIGN /* set O if the signs */
& (minuend ^ subtrahend) /* of the operands differ */
& (minuend ^ difference)); /* as do the signs of the minuend and result */
return (HP_WORD) LOWER_WORD (difference); /* return the lower 16 bits of the difference */
}
/* Multiply two 16-bit numbers.
Two 16-bit values are multiplied, and the 16-bit product is returned. The O
(overflow) bit in the status register is set if the result exceeds the
maximum positive or negative range, i.e., if the top 17 bits of the 32-bit
result are not all zeros or ones. In addition, an integer overflow interrupt
(ARITH trap) occurs if the user trap bit is set.
*/
HP_WORD cpu_mpy_16 (HP_WORD multiplicand, HP_WORD multiplier)
{
int32 product;
uint32 check;
product = SEXT (multiplicand) * SEXT (multiplier); /* sign-extend the operands and multiply */
check = (uint32) product & S16_OVFL_MASK; /* check the top 17 bits and set overflow */
SET_OVERFLOW (check != 0 && check != S16_OVFL_MASK); /* if they are not all zeros or all ones */
return (HP_WORD) LOWER_WORD (product); /* return the lower 16 bits of the product */
}
/* CPU base set global instruction execution routines */
/* Execute a stack instruction (subopcode 00).
This routine is called to execute a single stack instruction held in the CIR.
The instruction format is:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 0 | 1st stack opcode | 2nd stack opcode | Stack
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
As a single program word holds two stack opcodes, this routine is generally
called twice. If the R (right-hand) bit in the status register is set, the
opcode in the lower six bits of the CIR is executed; otherwise, the opcode in
the upper six bits is executed. The R bit is set when the left-hand opcode
is executing if the right-hand opcode is not a NOP. This is an optimization
that causes the instruction loop to fetch the next instruction in lieu of
calling this routine again to execute the right-hand NOP. The R bit also
marks a pending right-hand stack opcode execution when an interrupt is
detected after the left-hand stack opcode completes.
Implementation notes:
1. The entry status must be saved so that it may be restored if the
unimplemented opcode 072 is executed with the SS_UNIMPL simulator stop
flag set. This allows the instruction to be reexecuted and the
Unimplemented Instruction trap taken if the stop is subsequently
bypassed.
2. In hardware, the NEXT microcode order present at the end of each
instruction transfers the NIR content to the CIR, reads the memory word
at P into the NIR, and increments P. However, if an interrupt is
present, then this action is omitted, and a microcode jump is performed
to control store location 3, which then jumps to the microcoded interrupt
handler. In simulation, the CIR/NIR/P update is performed before the
next instruction is executed, rather than after the last instruction
completes, so that interrupts are handled before updating.
In addition, the NEXT action is modified in hardware if the NIR contains
a stack instruction with a non-NOP B (right-hand) stack opcode. In this
case, NEXT transfers the NIR content to the CIR, reads the memory word at
P into the NIR, but does not increment P. Instead, the R bit of the
status register is set to indicate that a B stackop is pending. When the
NEXT at the completion of the A (left-hand) stackop is executed, the NIR
and CIR are untouched, but P is incremented, and the R bit is cleared.
This ensures that if an interrupt or trap occurs between the stackops, P
will point correctly at the next instruction to be executed.
In simulation, following the hardware would require testing the NIR for a
non-NOP B stackop at every pass through the instruction execution loop.
To avoid this, the NEXT simulation unilaterally increments P, rather than
only when a B stackop is not present, and the stack instruction executor
tests for the B stackop and sets the R bit there. However, by that time,
P has already been incremented, so we decrement it here to return it to
the correct value.
3. Increments, decrements, and negates use the "cpu_add_16" and "cpu_sub_16"
instead of inline adds and subtracts in order to set the carry and
overflow status bits properly.
4. On division by zero, the FDIV microcode sets condition code CCA before
trapping. All other floating-point arithmetic traps are taken before
setting the condition code.
*/
t_stat cpu_stack_op (void)
{
static const uint8 preadjustment [64] = { /* stack preadjustment, indexed by operation */
0, 2, 2, 0, 0, 0, 0, 0, /* NOP DELB DDEL ZROX INCX DECX ZERO DZRO */
4, 4, 4, 2, 3, 2, 4, 2, /* DCMP DADD DSUB MPYL DIVL DNEG DXCH CMP */
2, 2, 2, 2, 1, 1, 2, 2, /* ADD SUB MPY DIV NEG TEST STBX DTST */
2, 1, 2, 1, 1, 1, 1, 1, /* DFLT BTST XCH INCA DECA XAX ADAX ADXA */
1, 2, 2, 1, 0, 1, 2, 1, /* DEL ZROB LDXB STAX LDXA DUP DDUP FLT */
4, 4, 4, 4, 4, 2, 3, 2, /* FCMP FADD FSUB FMPY FDIV FNEG CAB LCMP */
2, 2, 2, 3, 1, 2, 2, 2, /* LADD LSUB LMPY LDIV NOT OR XOR AND */
2, 2, 0, 2, 2, 2, 2, 2 /* FIXR FIXT -- INCB DECB XBX ADBX ADXB */
};
HP_WORD entry_status, exchanger;
uint32 operation, sum, difference, uproduct, udividend, uquotient, uremainder, check;
int32 product, dividend, divisor, quotient, remainder;
FP_OPND operand_u, operand_v, operand_w;
t_stat status = SCPE_OK;
entry_status = STA; /* save the entry status for a potential rollback */
if (STA & STATUS_R) { /* if right-hand stackop is pending */
operation = STACKOP_B (CIR); /* then get the right-hand opcode */
STA &= ~STATUS_R; /* and flip the flag off */
}
else { /* otherwise */
operation = STACKOP_A (CIR); /* get the left-hand opcode */
if (STACKOP_B (CIR) != NOP) { /* if the right-hand opcode is a not a NOP */
STA |= STATUS_R; /* then set the right-hand stackop pending flag */
P = P - 1 & R_MASK; /* and decrement P to cancel the later increment */
}
}
PREADJUST_SR (preadjustment [operation]); /* preadjust the TOS registers to the required number */
switch (operation) { /* dispatch the stack operation */
case 000: /* NOP (none; none)*/
break; /* there is nothing to do for a no-operation */
case 001: /* DELB (none; STUN) */
RB = RA; /* copy the TOS into the NOS */
cpu_pop (); /* and pop the TOS, effectively deleting the NOS */
break;
case 002: /* DDEL (none; STUN) */
cpu_pop (); /* pop the TOS */
cpu_pop (); /* and the NOS */
break;
case 003: /* ZROX (none; none) */
X = 0; /* set X to zero */
break;
case 004: /* INCX (CCA, C, O; ARITH) */
X = cpu_add_16 (X, 1); /* increment X */
SET_CCA (X, 0); /* and set the condition code */
break;
case 005: /* DECX (CCA, C, O; ARITH) */
X = cpu_sub_16 (X, 1); /* decrement X */
SET_CCA (X, 0); /* and set the condition code */
break;
case 006: /* ZERO (none; STOV) */
cpu_push (); /* push the stack down */
RA = 0; /* and set the TOS to zero */
break;
case 007: /* DZRO (none; STOV) */
cpu_push (); /* push the stack */
cpu_push (); /* down twice */
RA = 0; /* set the TOS */
RB = 0; /* and NOS to zero */
break;
case 010: /* DCMP (CCC; STUN) */
SR = 0; /* pop all four values from the stack */
SET_CCC (RD, RC, RB, RA); /* and set the (integer) condition code */
break;
case 011: /* DADD (CCA, C, O; STUN, ARTIH) */
sum = add_32 (TO_DWORD (RD, RC), /* add the two 32-bit double words on the stack */
TO_DWORD (RB, RA));
RD = UPPER_WORD (sum); /* split the MSW */
RC = LOWER_WORD (sum); /* and the LSW of the sum */
cpu_pop (); /* pop the old TOS */
cpu_pop (); /* and the old NOS */
SET_CCA (RB, RA); /* set the condition code */
break;
case 012: /* DSUB (CCA, C, O; STUN, ARTIH) */
difference = sub_32 (TO_DWORD (RD, RC), /* subtract the two 32-bit double words on the stack */
TO_DWORD (RB, RA));
RD = UPPER_WORD (difference); /* split the MSW */
RC = LOWER_WORD (difference); /* and the LSW of the difference */
cpu_pop (); /* pop the old TOS */
cpu_pop (); /* and the old NOS */
SET_CCA (RB, RA); /* set the condition code */
break;
case 013: /* MPYL (CCA, C, O; STUN, ARITH) */
product = SEXT (RA) * SEXT (RB); /* sign-extend the 16-bit operands and multiply */
RB = UPPER_WORD (product); /* split the MSW */
RA = LOWER_WORD (product); /* and the LSW of the product */
check = (uint32) product & S16_OVFL_MASK; /* check the top 17 bits and set carry */
SET_CARRY (check != 0 && check != S16_OVFL_MASK); /* if they are not all zeros or all ones */
STA &= ~STATUS_O; /* clear O as this operation cannot overflow */
SET_CCA (RB, RA); /* set the condition code */
break;
case 014: /* DIVL (CCA, O; STUN, ARITH) */
dividend = INT32 (TO_DWORD (RC, RB)); /* convert the 32-bit dividend to a signed value */
divisor = SEXT (RA); /* and sign-extend the 16-bit divisor */
RB = RA; /* delete the LSW from the stack now */
cpu_pop (); /* to conform with the microcode */
if (RA == 0) /* if dividing by zero */
MICRO_ABORT (trap_Integer_Zero_Divide); /* then trap or set the overflow flag */
if (abs (divisor) <= abs (SEXT (RB))) /* if the divisor is <= the MSW of the dividend */
SET_OVERFLOW (TRUE); /* an overflow will occur on the division */
else { /* otherwise, the divisor might be large enough */
quotient = dividend / divisor; /* form the 32-bit signed quotient */
remainder = dividend % divisor; /* and 32-bit signed remainder */
check = (uint32) quotient & S16_OVFL_MASK; /* check the top 17 bits and set overflow */
SET_OVERFLOW (check != 0 && check != S16_OVFL_MASK); /* if they are not all zeros or all ones */
RA = remainder & R_MASK; /* store the remainder on the TOS */
RB = quotient & R_MASK; /* and the quotient on the NOS */
SET_CCA (RB, 0); /* set the condition code */
}
break;
case 015: /* DNEG (CCA, O; STUN, ARITH) */
difference = sub_32 (0, TO_DWORD (RB, RA)); /* negate the 32-bit double word on the stack */
RB = UPPER_WORD (difference); /* split the MSW */
RA = LOWER_WORD (difference); /* and the LSW of the difference */
SET_CCA (RB, RA); /* set the condition code */
break;
case 016: /* DXCH (CCA; STUN) */
exchanger = RA; /* exchange */
RA = RC; /* the TOS */
RC = exchanger; /* and the third stack word */
exchanger = RB; /* exchange */
RB = RD; /* the NOS */
RD = exchanger; /* and the fourth stack word */
SET_CCA (RB, RA); /* set the condition code */
break;
case 017: /* CMP (CCC; STUN) */
SET_CCC (RB, 0, RA, 0); /* set the (integer) condition code */
cpu_pop (); /* and pop the TOS */
cpu_pop (); /* and the NOS */
break;
case 020: /* ADD (CCA, C, O; STUN, ARITH) */
RB = cpu_add_16 (RB, RA); /* add the NOS and TOS */
SET_CCA (RB, 0); /* set the condition code */
cpu_pop (); /* and pop the old TOS */
break;
case 021: /* SUB (CCA, C, O; STUN, ARITH) */
RB = cpu_sub_16 (RB, RA); /* subtract the NOS and TOS */
SET_CCA (RB, 0); /* set the condition code */
cpu_pop (); /* and pop the old TOS */
break;
case 022: /* MPY (CCA, O; STUN, ARITH) */
RB = cpu_mpy_16 (RA, RB); /* multiply the NOS and TOS */
SET_CCA (RB, 0); /* set the condition code */
cpu_pop (); /* and pop the old TOS */
break;
case 023: /* DIV (CCA, O; STUN, ARITH) */
if (RA == 0) /* if dividing by zero */
MICRO_ABORT (trap_Integer_Zero_Divide); /* then trap or set the overflow flag */
dividend = SEXT (RB); /* sign-extend the 16-bit dividend */
divisor = SEXT (RA); /* and the 16-bit divisor */
quotient = dividend / divisor; /* form the 32-bit signed quotient */
remainder = dividend % divisor; /* and 32-bit signed remainder */
SET_OVERFLOW (dividend == -32768 && divisor == -1); /* set overflow for -2**15 / -1 */
RA = remainder & R_MASK; /* store the remainder on the TOS */
RB = quotient & R_MASK; /* and the quotient on the NOS */
SET_CCA (RB, 0); /* set the condition code */
break;
case 024: /* NEG (CCA, C, O; STUN, ARTIH) */
RA = cpu_sub_16 (0, RA); /* negate the TOS */
SET_CCA (RA, 0); /* and set the condition code */
break;
case 025: /* TEST (CCA; STUN) */
SET_CCA (RA, 0); /* set the condition code */
break;
case 026: /* STBX (CCA; STUN) */
X = RB; /* store the NOS into X */
SET_CCA (X, 0); /* and set the condition code */
break;
case 027: /* DTST (CCA, C; STUN) */
SET_CCA (RB, RA); /* set the condition code */
check = TO_DWORD (RB, RA) & S16_OVFL_MASK; /* check the top 17 bits and set carry */
SET_CARRY (check != 0 && check != S16_OVFL_MASK); /* if they are not all zeros or all ones */
break;
case 030: /* DFLT (CCA; none) */
operand_u.precision = in_d; /* set the operand precision to double integer */
operand_u.words [0] = RB; /* load the MSW */
operand_u.words [1] = RA; /* and LSW of the operand */
operand_v = fp_exec (fp_flt, operand_u, FP_NOP); /* convert the integer to floating point */
RB = operand_v.words [0]; /* unload the MSW */
RA = operand_v.words [1]; /* and the LSW of the result */
SET_CCA (RB, RA); /* set the condition code */
break;
case 031: /* BTST (CCB; STUN) */
SET_CCB (LOWER_BYTE (RA)); /* set the condition code */
break;
case 032: /* XCH (CCA; STUN) */
exchanger = RA; /* exchange */
RA = RB; /* the TOS */
RB = exchanger; /* and the NOS */
SET_CCA (RA, 0); /* set the condition code */
break;
case 033: /* INCA (CCA, C, O; STUN, ARITH) */
RA = cpu_add_16 (RA, 1); /* increment the TOS */
SET_CCA (RA, 0); /* and set the condition code */
break;
case 034: /* DECA (CCA, C, O; STUN, ARITH) */
RA = cpu_sub_16 (RA, 1); /* decrement the TOS */
SET_CCA (RA, 0); /* and set the condition code */
break;
case 035: /* XAX (CCA; STUN) */
exchanger = X; /* exchange */
X = RA; /* the TOS */
RA = exchanger; /* and X */
SET_CCA (RA, 0); /* set the condition code */
break;
case 036: /* ADAX (CCA, C, O; STUN, ARITH) */
X = cpu_add_16 (X, RA); /* add the TOS to X */
cpu_pop (); /* and pop the TOS */
SET_CCA (X, 0); /* set the condition code */
break;
case 037: /* ADXA (CCA, C, O; STUN, ARITH) */
RA = cpu_add_16 (X, RA); /* add X to the TOS */
SET_CCA (RA, 0); /* and set the condition code */
break;
case 040: /* DEL (none; STUN) */
cpu_pop (); /* pop the TOS */
break;
case 041: /* ZROB (none; STUN) */
RB = 0; /* set the NOS to zero */
break;
case 042: /* LDXB (CCA; STUN) */
RB = X; /* load X into the NOS */
SET_CCA (RB, 0); /* and set the condition code */
break;
case 043: /* STAX (CCA; STUN) */
X = RA; /* store the TOS into X */
cpu_pop (); /* and pop the TOS */
SET_CCA (X, 0); /* set the condition code */
break;
case 044: /* LDXA (CCA; STOV) */
cpu_push (); /* push the stack down */
RA = X; /* and set the TOS to X */
SET_CCA (RA, 0); /* set the condition code */
break;
case 045: /* DUP (CCA; STUN, STOV) */
cpu_push (); /* push the stack down */
RA = RB; /* and copy the old TOS to the new TOS */
SET_CCA (RA, 0); /* set the condition code */
break;
case 046: /* DDUP (CCA; STUN, STOV) */
cpu_push (); /* push the stack */
cpu_push (); /* down twice */
RA = RC; /* copy the old TOS and NOS */
RB = RD; /* to the new TOS and NOS */
SET_CCA (RB, RA); /* set the condition code */
break;
case 047: /* FLT (CCA; none) */
operand_u.precision = in_s; /* set the operand precision to single integer */
operand_u.words [0] = RA; /* load the operand */
operand_v = fp_exec (fp_flt, operand_u, FP_NOP); /* convert the integer to floating point */
cpu_push (); /* push the stack down */
RB = operand_v.words [0]; /* unload the MSW */
RA = operand_v.words [1]; /* and the LSW of the result */
SET_CCA (RB, RA); /* set the condition code */
break;
case 050: /* FCMP (CCC; STUN) */
if (RB & RD & D16_SIGN) /* if the operand signs are both negative */
SET_CCC (RB, RA, RD, RC); /* then swap operands and compare the magnitudes */
else /* otherwise */
SET_CCC (RD, RC, RB, RA); /* compare them as they are */
SR = 0; /* pop all four values */
break;
case 051: /* FADD (CCA, O; STUN, ARITH) */
case 052: /* FSUB (CCA, O; STUN, ARITH) */
case 053: /* FMPY (CCA, O; STUN, ARITH) */
case 054: /* FDIV (CCA, O; STUN, ARITH) */
operand_u.precision = fp_f; /* set the operand precision to single_float */
operand_v.precision = fp_f; /* and the result precision to single float */
operand_u.words [0] = RD; /* load the MSW */
operand_u.words [1] = RC; /* and LSW of the first operand */
operand_v.words [0] = RB; /* load the MSW */
operand_v.words [1] = RA; /* and LSW of the second operand */
STA &= ~STATUS_O; /* clear the overflow flag */
cpu_pop (); /* delete two words */
cpu_pop (); /* from the stack */
operand_w = /* call the floating-point executor */
fp_exec ((FP_OPR) (operation - 051 + fp_add), /* and convert the opcode */
operand_u, operand_v); /* to an arithmetic operation */
RB = operand_w.words [0]; /* unload the MSW */
RA = operand_w.words [1]; /* and the LSW of the result */
if (operand_w.trap != trap_None) { /* if an error occurred */
if (operand_w.trap == trap_Float_Zero_Divide) /* then if it is division by zero */
SET_CCA (RB, RA); /* then set the condition code */
MICRO_ABORT (operand_w.trap); /* trap or set overflow */
}
SET_CCA (RB, RA); /* set the condition code */
break;
case 055: /* FNEG (CCA; STUN) */
if ((RB | RA) == 0) /* if the floating point value is zero */
SET_CCE; /* then it remains zero after negation */
else { /* otherwise */
RB = RB ^ D16_SIGN; /* flip the sign bit */
SET_CCA (RB, 1); /* and set CCL or CCG from the sign bit */
}
break;
case 056: /* CAB (CCA; STUN) */
exchanger = RC; /* rotate */
RC = RB; /* the TOS */
RB = RA; /* the NOS */
RA = exchanger; /* and the third stack word */
SET_CCA (RA, 0); /* set the condition code */
break;
case 057: /* LCMP (CCC; STUN) */
SET_CCC (0, RB, 0, RA); /* set the (logical) condition code */
cpu_pop (); /* pop the TOS */
cpu_pop (); /* and the NOS */
break;
case 060: /* LADD (CCA, C; STUN) */
sum = RB + RA; /* add the values */
SET_CARRY (sum > D16_UMAX); /* set C if there's a carry out of the MSB */
RB = sum & R_MASK; /* store the sum in the NOS */
cpu_pop (); /* and pop the TOS */
SET_CCA (RA, 0); /* set the (integer) condition code */
break;
case 061: /* LSUB (CCA, C; STUN) */
SET_CARRY (RA <= RB); /* set C if there will not be a borrow by the MSB */
RB = RB - RA & R_MASK; /* subtract the values */
cpu_pop (); /* and pop the TOS */
SET_CCA (RA, 0); /* set the (integer) condition code */
break;
case 062: /* LMPY (CCA, C; STUN) */
uproduct = RB * RA; /* multiply the operands */
RA = LOWER_WORD (uproduct); /* split the MSW */
RB = UPPER_WORD (uproduct); /* and the LSW of the product */
SET_CARRY (RB > 0); /* set C if the product doesn't fit in one word */
SET_CCA (RB, RA); /* set the (integer) condition code */
break;
case 063: /* LDIV (CCA, O; STUN, ARITH) */
if (RA == 0) /* if dividing by zero */
MICRO_ABORT (trap_Integer_Zero_Divide); /* then trap or set the overflow flag */
udividend = TO_DWORD (RC, RB); /* form the 32-bit unsigned dividend */
uquotient = udividend / RA; /* form the 32-bit unsigned quotient */
uremainder = udividend % RA; /* and 32-bit unsigned remainder */
SET_OVERFLOW (uquotient & ~D16_MASK); /* set O if the quotient needs more than 16 bits */
cpu_pop (); /* pop the TOS */
RA = LOWER_WORD (uremainder); /* store the remainder on the TOS */
RB = LOWER_WORD (uquotient); /* and the quotient on the NOS */
SET_CCA (RB, 0); /* set the (integer) condition code */
break;
case 064: /* NOT (CCA; STUN) */
RA = ~RA & R_MASK; /* complement the TOS */
SET_CCA (RA, 0); /* and set the condition code */
break;
case 065: /* OR (CCA; STUN) */
RB = RA | RB; /* logically OR the TOS and NOS */
SET_CCA (RB, 0); /* set the condition code */
cpu_pop (); /* and pop the TOS */
break;
case 066: /* XOR (CCA; STUN) */
RB = RA ^ RB; /* logically XOR the TOS and NOS */
SET_CCA (RB, 0); /* set the condition code */
cpu_pop (); /* and pop the TOS */
break;
case 067: /* AND (CCA; STUN) */
RB = RA & RB; /* logically AND the TOS and NOS */
SET_CCA (RB, 0); /* set the condition code */
cpu_pop (); /* and pop the TOS */
break;
case 070: /* FIXR (CCA, C, O; STUN, ARITH) */
case 071: /* FIXT (CCA, C, O; STUN, ARITH) */
operand_u.precision = fp_f; /* set the operand precision to single_float */
operand_u.words [0] = RB; /* load the MSW */
operand_u.words [1] = RA; /* and LSW of the operand */
STA &= ~(STATUS_C | STATUS_O); /* the microcode clears the carry and overflow flags here */
operand_v = /* call the floating-point executor */
fp_exec ((FP_OPR) (operation - 070 + fp_fixr), /* and convert the opcode */
operand_u, FP_NOP); /* to a fix operation */
if (operand_v.trap != trap_None) { /* if an overflow occurs */
RB = RB & FRACTION_BITS | ASSUMED_BIT; /* then the microcode masks and restores */
MICRO_ABORT (operand_v.trap); /* the leading 1 to the mantissa before trapping */
}
RB = operand_v.words [0]; /* unload the MSW */
RA = operand_v.words [1]; /* and the LSW of the result */
check = TO_DWORD (RB, RA) & S16_OVFL_MASK; /* check the top 17 bits and set carry */
SET_CARRY (check != 0 && check != S16_OVFL_MASK); /* if they are not all zeros or all ones */
SET_CCA (RB, RA); /* set the condition code */
break;
case 072: /* unimplemented */
status = STOP_UNIMPL; /* report that the instruction was not executed */
STA = entry_status; /* and restore the status register entry value */
break;
case 073: /* INCB (CCA, C, O; STUN, ARITH) */
RB = cpu_add_16 (RB, 1); /* increment the NOS */
SET_CCA (RB, 0); /* and set the condition code */
break;
case 074: /* DECB (CCA, C, O; STUN, ARITH) */
RB = cpu_sub_16 (RB, 1); /* decrement the NOS */
SET_CCA (RB, 0); /* and set the condition code */
break;
case 075: /* XBX (none; STUN) */
exchanger = X; /* exchange */
X = RB; /* the NOS */
RB = exchanger; /* and X */
break;
case 076: /* ADBX (CCA, C, O; STUN, ARITH) */
X = cpu_add_16 (X, RB); /* add the NOS to X */
SET_CCA (X, 0); /* and set the condition code */
break;
case 077: /* ADXB (CCA, C, O; STUN, ARITH) */
RB = cpu_add_16 (X, RB); /* add X to the NOS */
SET_CCA (RB, 0); /* and set the condition code */
break;
} /* all cases are handled */
return status; /* return the execution status */
}
/* Execute a shift, branch, or bit test instruction (subopcode 01).
This routine is called to execute the shift, branch, or bit test instruction
currently in the CIR. The instruction formats are:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 1 | X | shift opcode | shift count | Shift
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 1 | I | branch opcode |+/-| P displacement | Branch
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 1 | X | bit test opcode | bit position | Bit Test
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Implementation notes:
1. The BCY, BNCY, BOV, and BNOV instructions will enter infinite loops if
their displacements are zero, so they call the "cpu_branch_short" routine
with loop checking enabled. The other branch instructions will not enter
an infinite loop, even with zero displacements, as they modify registers
or the stack during execution, so they call the routine with loop
checking disabled.
2. All of the shift instructions except QASL and QASR use bit 9 to indicate
a left (0) or right (1) shift and bit 4 to indicate that the shift count
includes the index register value. Bit 9 is always on for QASL and QASR,
which use bit 4 to indicate a left or right shift, and which always
include the index register value. To simplify handling in the shifting
routine, the QASL and QASR executors move the left/right indication to
bit 9 and set bit 4 on before calling.
*/
t_stat cpu_shift_branch_bit_op (void)
{
static const uint8 preadjustment [32] = { /* stack preadjustment, indexed by operation */
1, 1, 1, 1, 1, 1, 1, 1, /* ASL ASR LSL LSR CSL CSR SCAN IABZ */
3, 3, 0, 0, 0, 0, 3, 4, /* TASL TASR IXBZ DXBZ BCY BNCY TNSL QAS(LR) */
2, 2, 2, 2, 2, 2, 2, 1, /* DASL DASR DLSL DLSR DCSL DCSR CPRB DABZ */
0, 0, 1, 1, 1, 1, 1, 1 /* BOV BNOV TBC TRBC TSBC TCBC BRO BRE */
};
HP_WORD opcode;
uint32 operation, bit_position, bit_mask, count;
t_stat status = SCPE_OK;
operation = SBBOP (CIR); /* get the opcode from the instruction */
PREADJUST_SR (preadjustment [operation]); /* preadjust the TOS registers to the required number */
switch (operation) { /* dispatch the shift/branch/bit operation */
case 000: /* ASL (CCA; STUN) */
case 001: /* ASR (CCA; STUN) */
shift_16_32 (CIR, arithmetic, size_16); /* do an arithmetic left or right shift */
break;
case 002: /* LSL (CCA; STUN) */
case 003: /* LSR (CCA; STUN) */
shift_16_32 (CIR, logical, size_16); /* do a logical left or right shift */
break;
case 004: /* CSL (CCA; STUN) */
case 005: /* CSR (CCA; STUN) */
shift_16_32 (CIR, circular, size_16); /* do a circular left or right shift */
break;
case 006: /* SCAN (CCA; STUN) */
if (RA == 0) /* if the TOS is zero */
if (CIR & X_FLAG) /* then if the instruction is indexed */
X = X + 16 & R_MASK; /* then add 16 to the index register value */
else /* otherwise */
X = 16; /* set the index register value to 16 */
else { /* otherwise the TOS is not zero */
count = 0; /* so set up to scan for the first "one" bit */
while ((RA & D16_SIGN) == 0) { /* while the MSB is clear */
RA = RA << 1; /* shift the TOS left */
count = count + 1; /* while counting the shifts */
}
if (CIR & X_FLAG) /* if the instruction is indexed */
X = X + count + 1 & R_MASK; /* then return the count + 1 */
else /* otherwise */
X = count; /* return the count */
RA = RA << 1 & R_MASK; /* shift the leading "one" bit out of the TOS */
}
SET_CCA (RA, 0); /* set the condition code */
break;
case 007: /* IABZ (CCA, C, O; STUN, BNDV) */
RA = cpu_add_16 (RA, 1); /* increment the TOS */
SET_CCA (RA, 0); /* and set the condition code */
if (RA == 0) /* if the TOS is now zero */
status = cpu_branch_short (FALSE); /* then branch to the target address */
break;
case 010: /* TASL (CCA; STUN) */
case 011: /* TASR (CCA; STUN) */
shift_48_64 (CIR, arithmetic, size_48); /* do a triple arithmetic left or right shift */
break;
case 012: /* IXBZ (CCA, C, O; BNDV) */
X = cpu_add_16 (X, 1); /* increment X */
SET_CCA (X, 0); /* and set the condition code */
if (X == 0) /* if X is now zero */
status = cpu_branch_short (FALSE); /* then branch to the target address */
break;
case 013: /* DXBZ (CCA, C, O; BNDV) */
X = cpu_sub_16 (X, 1); /* decrement X */
SET_CCA (X, 0); /* and set the condition code */
if (X == 0) /* if X is now zero */
status = cpu_branch_short (FALSE); /* then branch to the target address */
break;
case 014: /* BCY (C = 0; BNDV) */
if (STA & STATUS_C) { /* if the carry bit is set */
STA &= ~STATUS_C; /* then clear it */
status = cpu_branch_short (TRUE); /* and branch to the target address */
}
break;
case 015: /* BNCY (C = 0; BNDV) */
if (STA & STATUS_C) /* if the carry bit is set */
STA &= ~STATUS_C; /* then clear it and do not branch */
else /* otherwise the carry bit is clear */
status = cpu_branch_short (TRUE); /* so branch to the target address */
break;
case 016: /* TNSL (CCA; STUN) */
shift_48_64 (CIR, normalizing, size_48); /* do a triple normalizing left shift */
break;
case 017: /* QASL (CCA; STUN), QASR (CCA; STUN) */
if ((CIR & ~SHIFT_COUNT_MASK) == QASR) /* transfer the left/right flag */
opcode = CIR | SHIFT_RIGHT_FLAG | X_FLAG; /* to the same position */
else /* as the other shift instructions use */
opcode = CIR & ~SHIFT_RIGHT_FLAG | X_FLAG; /* and set the indexed flag on */
shift_48_64 (opcode, arithmetic, size_64); /* do a quadruple arithmetic left or right shift */
break;
case 020: /* DASL (CCA; STUN) */
case 021: /* DASR (CCA; STUN) */
shift_16_32 (CIR, arithmetic, size_32); /* do a double arithmetic left or right shift */
break;
case 022: /* DLSL (CCA; STUN) */
case 023: /* DLSR (CCA; STUN) */
shift_16_32 (CIR, logical, size_32); /* do a double logical left or right shift */
break;
case 024: /* DCSL (CCA; STUN) */
case 025: /* DCSR (CCA; STUN) */
shift_16_32 (CIR, circular, size_32); /* do a double circular left or right shift */
break;
case 026: /* CPRB (CCE, CCL, CCG; STUN, BNDV) */
if (SEXT (X) < SEXT (RB)) /* if X is less than the lower bound */
SET_CCL; /* then set CCL and continue */
else if (SEXT (X) > SEXT (RA)) /* otherwise if X is greater than the upper bound */
SET_CCG; /* then set CCG and continue */
else { /* otherwise lower bound <= X <= upper bound */
SET_CCE; /* so set CCE */
status = cpu_branch_short (FALSE); /* and branch to the target address */
}
cpu_pop (); /* pop the TOS */
cpu_pop (); /* and the NOS */
break;
case 027: /* DABZ (CCA, C, O; STUN, BNDV) */
RA = cpu_sub_16 (RA, 1); /* decrement the TOS */
SET_CCA (RA, 0); /* and set the condition code */
if (RA == 0) /* if the TOS is now zero */
status = cpu_branch_short (FALSE); /* then branch to the target address */
break;
case 030: /* BOV (O = 0; BNDV) */
if (STA & STATUS_O) { /* if the overflow bit is set */
STA &= ~STATUS_O; /* then clear it */
status = cpu_branch_short (TRUE); /* and branch to the target address */
}
break;
case 031: /* BNOV (O = 0; BNDV) */
if (STA & STATUS_O) /* if the overflow bit is set */
STA &= ~STATUS_O; /* then clear it and do not branch */
else /* otherwise the overflow bit is clear */
status = cpu_branch_short (TRUE); /* so branch to the target address */
break;
case 032: /* TBC (CCA; STUN) */
case 033: /* TRBC (CCA; STUN) */
case 034: /* TSBC (CCA; STUN) */
case 035: /* TCBC (CCA; STUN) */
bit_position = BIT_POSITION (CIR); /* get the position of the bit to test */
if (CIR & X_FLAG) /* if the instruction is indexed */
bit_position = bit_position + X; /* then add the index register value */
bit_mask = D16_SIGN >> bit_position % D16_WIDTH; /* shift the bit mask to the desired location */
SET_CCA (RA & bit_mask, 0); /* set the condition code */
if (operation == 033) /* if the instruction is TRBC */
RA = RA & ~bit_mask; /* then reset the bit */
else if (operation == 034) /* otherwise if the instruction is TSBC */
RA = RA | bit_mask; /* then set the bit */
else if (operation == 035) /* otherwise if the instruction is TCBC */
RA = RA ^ bit_mask; /* then complement the bit */
break; /* or leave it alone for TBC */
case 036: /* BRO (none; STUN, BNDV) */
if ((RA & 1) == 1) /* if the TOS is odd */
status = cpu_branch_short (FALSE); /* then branch to the target address */
cpu_pop (); /* pop the TOS */
break;
case 037: /* BRE (none; STUN, BNDV) */
if ((RA & 1) == 0) /* if the TOS is even */
status = cpu_branch_short (FALSE); /* then branch to the target address */
cpu_pop (); /* pop the TOS */
break;
} /* all cases are handled */
return status; /* return the execution status */
}
/* Execute a move, special, firmware, immediate, field, or register instruction (subopcode 02).
This routine is called to execute the move, special, firmware, immediate,
field, or register instruction currently in the CIR. The instruction formats
are:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 0 | move op | opts/S decrement | Move
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 0 | special op | 0 0 | sp op | Special
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | firmware option op | Firmware
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | imm opcode | immediate operand | Immediate
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | field opcode | J field | K field | Field
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | register op | SK| DB| DL| Z |STA| X | Q | S | Register
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Implementation notes:
1. The PSHR and SETR instructions follow the stack usage in the microcode
so that SR contains the same value at the end of the instruction as in
the hardware. The sequence of stack flushes and queue-ups is therefore
somewhat asymmetric.
2. The microcode for the EXF and DPF instructions calculate the alignment
shifts as 16 - (J + K) MOD 16 and then perform circular right and left
shifts, respectively, to align the fields. In simulation, the alignments
are calculated as (J + K) MOD 16, and the opposite shifts (left and
right, respectively) are employed. This produces the same result, as a
circular left shift of N bits is identical to a circular right shift of
16 - N bits.
*/
t_stat cpu_move_spec_fw_imm_field_reg_op (void)
{
static const uint8 preadjustment [16] = { /* stack preadjustment, indexed by operation */
0, 4, 0, 0, 1, 1, 1, 1, /* ---- ---- LDI LDXI CMPI ADDI SUBI MPYI */
1, 0, 0, 0, 1, 1, 2, 4 /* DIVI PSHR LDNI LDXN CMPN EXF DPF SETR */
};
int32 divisor;
uint32 operation;
HP_WORD new_sbank, new_sm, new_q, start_bit, bit_count, bit_shift, bit_mask;
t_stat status = SCPE_OK;
operation = MSFIFROP (CIR); /* get the opcode from the instruction */
PREADJUST_SR (preadjustment [operation]); /* preadjust the TOS registers to the required number */
switch (operation) { /* dispatch the operation */
case 000:
status = move_spec (); /* execute the move or special instruction */
break;
case 001:
status = firmware_extension (); /* execute the DMUL, DDIV, or firmware extension instruction */
break;
case 002: /* LDI (CCA; STOV) */
cpu_push (); /* push the stack down */
RA = CIR & IMMED_MASK; /* store the immediate value on the TOS */
SET_CCA (RA, 0); /* and set the condition code */
break;
case 003: /* LDXI (none; none) */
X = CIR & IMMED_MASK; /* load the immediate value into X */
break;
case 004: /* CMPI (CCC; STUN) */
SET_CCC (RA, 0, CIR & IMMED_MASK, 0); /* set the condition code */
cpu_pop (); /* and pop the TOS */
break;
case 005: /* ADDI (CCA, C, O; STUN, ARITH) */
RA = cpu_add_16 (RA, CIR & IMMED_MASK); /* sum the TOS and the immediate value */
SET_CCA (RA, 0); /* and set the condition code */
break;
case 006: /* SUBI (CCA, C, O; STUN, ARITH) */
RA = cpu_sub_16 (RA, CIR & IMMED_MASK); /* difference the TOS and the immediate value */
SET_CCA (RA, 0); /* and set the condition code */
break;
case 007: /* MPYI (CCA, O; STUN, STOV, ARITH) */
cpu_push (); /* the microcode does this for commonality with */
cpu_pop (); /* MPY and MPYM, so we must too to get STOV */
RA = cpu_mpy_16 (RA, CIR & IMMED_MASK); /* multiply the TOS and the immediate value */
SET_CCA (RA, 0); /* and set the condition code */
break;
case 010: /* DIVI (CCA; STUN, ARITH) */
divisor = (int32) CIR & IMMED_MASK; /* get the immediate (positive) divisor */
if (divisor == 0) /* if dividing by zero */
MICRO_ABORT (trap_Integer_Zero_Divide); /* then trap or set the overflow flag */
RA = SEXT (RA) / divisor & R_MASK; /* store the quotient (which cannot overflow) on the TOS */
SET_CCA (RA, 0); /* and set the condition code */
break;
case 011: /* PSHR (none; STOV, MODE) */
cpu_flush (); /* flush the TOS register file */
if (SM + 9 > Z) /* check the stack for enough space */
MICRO_ABORT (trap_Stack_Overflow); /* before pushing any of the registers */
if (CIR & PSR_S) { /* if S is to be stored */
cpu_push (); /* then push the stack down */
RA = SM - DB & R_MASK; /* and store delta S on the TOS */
}
if (CIR & PSR_Q) { /* if Q is to be stored */
cpu_push (); /* then push the stack down */
RA = Q - DB & R_MASK; /* and store delta Q on the TOS */
}
if (CIR & PSR_X) { /* if X is to be stored */
cpu_push (); /* then push the stack down */
RA = X; /* and store X on the TOS */
}
if (CIR & PSR_STA) { /* if STA is to be stored */
cpu_push (); /* then push the stack down */
RA = STA; /* and store the status register on the TOS */
cpu_flush (); /* flush the TOS register queue */
}
if (CIR & PSR_Z) { /* if Z is to be stored */
cpu_push (); /* then push the stack down */
RA = Z - DB & R_MASK; /* and store delta Z on the TOS */
}
cpu_flush (); /* flush the TOS register queue */
if (CIR & PSR_DL) { /* if DL is to be stored */
cpu_push (); /* then push the stack down */
RA = DL - DB & R_MASK; /* and store delta DL on the TOS */
}
if (CIR & (PSR_DB_DBANK | PSR_SBANK)) { /* if a bank register is to be stored */
if (NPRV) /* then if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
if (CIR & PSR_DB_DBANK) { /* if DBANK and DB are to be stored */
cpu_push (); /* then push the stack */
cpu_push (); /* down twice */
RA = DB; /* and store DB on the TOS */
RB = DBANK; /* and DBANK in the NOS */
}
if (CIR & PSR_SBANK) { /* if SBANK is to be stored */
cpu_push (); /* then push the stack down */
RA = SBANK; /* and store SBANK on the TOS */
}
}
break;
case 012: /* LDNI (CCA; STOV) */
cpu_push (); /* push the stack down */
RA = NEG16 (CIR & IMMED_MASK); /* and store the negated immediate value on the TOS */
SET_CCA (RA, 0); /* set the condition code */
break;
case 013: /* LDXN (none; none) */
X = NEG16 (CIR & IMMED_MASK); /* store the negated immediate value into X */
break;
case 014: /* CMPN (CCC; STUN) */
SET_CCC (RA, 0, NEG16 (CIR & IMMED_MASK), 0); /* set the condition code */
cpu_pop (); /* and pop the TOS */
break;
case 015: /* EXF (CCA; STUN) */
start_bit = START_BIT (CIR); /* get the starting bit number */
bit_count = BIT_COUNT (CIR); /* and the number of bits */
bit_shift = (start_bit + bit_count) % D16_WIDTH; /* calculate the alignment shift */
bit_mask = (1 << bit_count) - 1; /* form a right-justified mask */
RA = (RA << bit_shift | RA >> D16_WIDTH - bit_shift) /* rotate the TOS to align with the mask */
& bit_mask; /* and then mask to the desired field */
SET_CCA (RA, 0); /* set the condition code */
break;
case 016: /* DPF (CCA; STUN) */
start_bit = START_BIT (CIR); /* get the starting bit number */
bit_count = BIT_COUNT (CIR); /* and the number of bits */
bit_shift = (start_bit + bit_count) % D16_WIDTH; /* calculate the alignment shift */
bit_mask = (1 << bit_count) - 1; /* form a right-justified mask */
bit_mask = bit_mask >> bit_shift /* rotate it into the correct position */
| bit_mask << D16_WIDTH - bit_shift; /* to mask the target field */
RB = (RB & ~bit_mask /* mask the NOS and rotate and mask the TOS to fill */
| (RA >> bit_shift | RA << D16_WIDTH - bit_shift) & bit_mask)
& R_MASK;
cpu_pop (); /* pop the TOS */
SET_CCA (RA, 0); /* and set the condition code */
break;
case 017: /* SETR (none; STUN, STOV, MODE)*/
new_sbank = 0; /* quell erroneous uninitialized use warning */
if (CIR & PSR_PRIV) { /* setting SBANK, DB, DL, and Z are privileged */
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
if (CIR & PSR_SBANK) { /* if SBANK is to be set */
new_sbank = RA; /* then change it after the parameters are retrieved */
cpu_pop (); /* pop the parameter */
}
if (CIR & PSR_DB_DBANK) { /* if DBANK and DB are to be set */
DB = RA; /* then set the */
DBANK = RB & BA_MASK; /* new values */
cpu_pop (); /* and pop the */
cpu_pop (); /* parameters */
}
if (CIR & PSR_DL) { /* if DL is to be set */
DL = RA + DB & R_MASK; /* then set the new value as an offset from DB */
cpu_pop (); /* and pop the parameter */
}
if (SR == 0) /* queue up a parameter */
cpu_queue_up (); /* if it is needed */
if (CIR & PSR_Z) { /* if Z is to be set */
Z = RA + DB & R_MASK; /* then set the new value as an offset from DB */
cpu_pop (); /* and pop the parameter */
}
/* queue up another parameter */
if (SR == 0) /* if it is needed */
cpu_queue_up ();
}
if (CIR & PSR_STA) { /* if STA is to be set */
if (NPRV) /* then if the mode is not privileged */
STA = STA & ~STATUS_NPRV | RA & STATUS_NPRV; /* then only T, O, C, and CC can be set */
else /* otherwise privileged mode */
STA = RA; /* allows the entire word to be set */
if ((STA & STATUS_OVTRAP) == STATUS_OVTRAP) /* if overflow was set with trap enabled */
CPX1 |= cpx1_INTOVFL; /* then an interrupt occurs */
cpu_pop (); /* pop the parameter */
if (SR == 0) /* queue up another parameter */
cpu_queue_up (); /* if it is needed */
}
if (CIR & PSR_X) { /* if X is to be set */
X = RA; /* then set the new value */
cpu_pop (); /* and pop the parameter */
}
if (CIR & PSR_Q) { /* if Q is to be set */
if (SR == 0) /* then queue up another parameter */
cpu_queue_up (); /* if it is needed */
new_q = RA + DB & R_MASK; /* set the new value as an offset from DB */
check_stack_bounds (new_q); /* trap if the new value is outside of the stack */
Q = new_q; /* set the new value */
cpu_pop (); /* and pop the parameter */
}
if (CIR & PSR_S) { /* if S is to be set */
if (SR == 0) /* then queue up another parameter */
cpu_queue_up (); /* if it is needed */
new_sm = RA + DB & R_MASK; /* set the new value as an offset from DB */
check_stack_bounds (new_sm); /* trap if the new value is outside of the stack */
cpu_flush (); /* flush the TOS register file */
SM = new_sm; /* and set the new stack pointer value */
}
if (CIR & PSR_SBANK) /* if SBANK is to be set */
SBANK = new_sbank & BA_MASK; /* then update the new value now */
cpu_base_changed = TRUE; /* this instruction changed the base registers */
break;
} /* all cases are handled */
return status; /* return the execution status */
}
/* Execute an I/O, control, program, immediate, or memory instruction (subopcode 03).
This routine is called to execute the I/O, control, program, immediate, or
memory instruction currently in the CIR. The instruction formats are:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 1 | program op | N field | Program
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 1 | immediate op | immediate operand | Immediate
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 1 | memory op | P displacement | Memory
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
The "N field" of the program instructions contains an index that is used to
locate the "program label" that describes the procedure or subroutine to call
or exit. Labels have this format:
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | U | address | Local
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| M | STT number | segment number | External
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
U (uncallable) = the procedure is callable from privileged mode only
M (mapped) = the segment number is physically mapped
address = the PB-relative address of the procedure entry
STT number = the Segment Transfer Table entry within the target segment
segment number = the number of the target segment
The label is located either on the top of the stack (N = 0) or by indexing
into the STT of the current code segment (N > 0). Labels may be either
local, indicating a transfer within the current segment, or external,
indicating a transfer to another segment.
Implementation notes:
1. In hardware, the LDPP and LDPN microcode performs the bounds test E >= PB
on the effective address E, then does a queue down if necessary, then
performs the bounds test E < PL (instead of <= to account for second
word), and then does another queue down if necessary, before reading the
two words and storing them in the RA and RB registers. Therefore, the
order of possible traps is BNDV, STOV, BNDV, and STOV.
In simulation, the "cpu_read_memory" routine normally checks the upper
and lower bounds together. This would lead to a trap order of BNDV,
BNDV, STOV, and STOV. To implement the microcode order, explicit bounds
checks are interleaved with the stack pushes, and then unchecked reads
are done to obtain the operands.
*/
t_stat cpu_io_cntl_prog_imm_mem_op (void)
{
static const uint8 preadjustment [16] = { /* stack preadjustment, indexed by operation */
0, 0, 0, 0, 1, 0, 0, 0, /* ---- SCAL PCAL EXIT SXIT ADXI SBXI LLBL */
0, 0, 1, 1, 0, 1, 1, 1 /* LDPP LDPN ADDS SUBS ---- ORI XORI ANDI */
};
ACCESS_CLASS class;
uint32 operation;
HP_WORD field, operand, offset, new_p, new_q, new_sm, stt_length, label;
t_stat status = SCPE_OK;
field = CIR & DISPL_255_MASK; /* get the N/immediate/displacement field value */
operation = IOCPIMOP (CIR); /* get the opcode from the instruction */
PREADJUST_SR (preadjustment [operation]); /* preadjust the TOS registers to the required number */
switch (operation) { /* dispatch the operation */
case 000:
status = io_control (); /* execute the I/O or control instruction */
break;
case 001: /* SCAL (none; STOV, STUN, STTV, BNDV) */
if (field == 0) { /* if the label is on the TOS */
PREADJUST_SR (1); /* then ensure a valid TOS register */
label = RA; /* before getting the label */
cpu_pop (); /* and popping it from the stack */
}
else /* otherwise, the label is at M [PL-N] */
cpu_read_memory (program_checked, /* so check the bounds */
PL - field & LA_MASK, &label); /* and then read the label */
cpu_flush (); /* flush the TOS registers to memory */
if (SM > Z) /* if the stack limit was exceeded */
MICRO_ABORT (trap_Stack_Overflow); /* then trap for a stack overflow */
if (label & LABEL_EXTERNAL) /* if the label is non-local */
MICRO_ABORTP (trap_STT_Violation, STA); /* then trap for an STT violation */
cpu_push (); /* push the stack down */
RA = P - 1 - PB & R_MASK; /* and store the return address on the TOS */
new_p = PB + (label & LABEL_ADDRESS_MASK); /* get the subroutine entry address */
cpu_read_memory (fetch_checked, new_p, &NIR); /* check the bounds and get the first instruction */
P = new_p + 1 & R_MASK; /* and set P to point at the next instruction */
break;
case 002: /* PCAL (none; STUN, STOV, CSTV, STTV, ABS CST, TRACE, UNCAL, BNDV) */
if (field == 0) { /* if the label is on the TOS */
PREADJUST_SR (1); /* then ensure a valid TOS register */
label = RA; /* before getting the label */
cpu_pop (); /* and popping it from the stack */
}
else /* otherwise, the label is at M [PL-N] */
cpu_read_memory (program_checked, /* so check the bounds */
PL - field & LA_MASK, &label); /* and then read the label */
cpu_flush (); /* flush the TOS registers to memory */
if (SM > Z) /* if the stack limit was exceeded */
MICRO_ABORT (trap_Stack_Overflow); /* then trap for a stack overflow */
cpu_mark_stack (); /* write a stack marker */
cpu_call_procedure (label); /* set up PB, P, PL, and STA to call the procedure */
break;
case 003: /* EXIT (CC; STUN, STOV, MODE, CSTV, TRACE, ABSCST, BNDV) */
if (SM < Q) /* if the stack memory pointer is below the stack marker */
cpu_flush (); /* then flush the TOS registers to memory */
SR = 0; /* invalidate the TOS registers */
new_sm = Q - 4 - field & R_MASK; /* compute the new stack pointer value */
cpu_read_memory (stack_checked, Q, &operand); /* read the delta Q value from the stack marker */
new_q = Q - operand & R_MASK; /* and determine the new Q value */
cpu_exit_procedure (new_q, new_sm, field); /* set up the return code segment and stack */
break;
case 004: /* SXIT (none; STUN, STOV, BNDV) */
new_p = RA + PB & R_MASK; /* get the return address */
cpu_read_memory (fetch_checked, new_p, &NIR); /* check the bounds and then load the NIR */
cpu_pop (); /* pop the return address from the stack */
if (field > 0 && SR > 0) /* if an adjustment is wanted and the TOS registers are occupied */
cpu_flush (); /* then flush the registers to memory */
new_sm = SM - field & R_MASK; /* adjust the stack pointer as requested */
check_stack_bounds (new_sm); /* trap if the new value is outside of the stack */
SM = new_sm; /* before setting the new stack pointer value */
P = new_p + 1 & R_MASK; /* set the new P value for the return */
break;
case 005: /* ADXI (CCA; none) */
X = X + field & R_MASK; /* add the immediate value to X */
SET_CCA (X, 0); /* and set the condition code */
break;
case 006: /* SBXI (CCA; none) */
X = X - field & R_MASK; /* subtract the immediate value from X */
SET_CCA (X, 0); /* and set the condition code */
break;
case 007: /* LLBL (none; STOV, STTV) */
cpu_read_memory (program_checked, PL, &stt_length); /* read the STT length */
if ((stt_length & STT_LENGTH_MASK) < field) /* if the STT index is not within the STT */
MICRO_ABORTP (trap_STT_Violation, STA); /* then trap for an STT violation */
cpu_read_memory (program_checked, /* check the bounds */
PL - field & LA_MASK, &label); /* and then read the label */
if ((label & LABEL_EXTERNAL) == 0) /* if the label is a local label */
if (field > LABEL_STTN_MAX) /* then if the STT number is too big for an external */
MICRO_ABORTP (trap_STT_Violation, STA); /* then trap for an STT violation */
else /* otherwise */
label = LABEL_EXTERNAL /* convert it to an external label */
| (field << LABEL_STTN_SHIFT) /* by merging the STT number */
| STA & STATUS_CS_MASK; /* with the currently executing segment number */
cpu_push (); /* push the stack down */
RA = label; /* and store the label on the TOS */
break;
case 010: /* LDPP (CCA; STOV, BNDV) */
case 011: /* LDPN (CCA; STOV, BNDV) */
cpu_ea (CIR & MODE_DISP_MASK, /* get the address of the first word */
&class, &offset, NULL);
if (offset < PB && NPRV) /* if the offset is below PB and the mode is not privileged */
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
cpu_push (); /* push the stack down */
if (offset >= PL && NPRV) /* if the offset is at or above PL and the mode is not privileged */
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
cpu_push (); /* push the stack down again */
cpu_read_memory (program, offset, &operand); /* read the first word */
RB = operand; /* and store it in the NOS */
offset = offset + 1 & LA_MASK; /* point at the second word */
cpu_read_memory (program, offset, &operand); /* read the second word */
RA = operand; /* and store the on the TOS */
SET_CCA (RB, RA); /* set the condition code */
break;
case 012: /* ADDS (none; STUN, STOV) */
if (field == 0) /* if the immediate value is zero */
field = RA - 1; /* then use the TOS value - 1 instead */
cpu_flush (); /* empty the TOS registers */
new_sm = SM + field & R_MASK; /* get the new stack pointer value */
check_stack_bounds (new_sm); /* trap if the new value is outside of the stack */
SM = new_sm; /* before setting the new stack pointer value */
break;
case 013: /* SUBS (none; STUN, STOV) */
if (field == 0) /* if the immediate value is zero */
field = RA + 1; /* then use the TOS value + 1 instead */
cpu_flush (); /* empty the TOS registers */
new_sm = SM - field & R_MASK; /* get the new stack pointer value */
check_stack_bounds (new_sm); /* trap if the new value is outside of the stack */
SM = new_sm; /* before setting the new stack pointer value */
break;
case 014:
status = STOP_UNIMPL; /* opcodes 036000-036777 are unimplemented */
break;
case 015: /* ORI (CCA; STUN) */
RA = RA | field; /* logically OR the TOS and the immediate value */
SET_CCA (RA, 0); /* and set the condition code */
break;
case 016: /* XORI (CCA; STUN) */
RA = RA ^ field; /* logically XOR the TOS and the immediate value */
SET_CCA (RA, 0); /* and set the condition code */
break;
case 017: /* ANDI (CCA; STUN) */
RA = RA & field; /* logically AND the TOS and the immediate value */
SET_CCA (RA, 0); /* and set the condition code */
break;
} /* all cases are handled */
return status; /* return the execution status */
}
/* CPU base set local utility routines */
/* Add two 32-bit numbers.
Two 32-bit values are added, and the 32-bit sum is returned. The C (carry)
bit in the status register is set if the result is truncated and cleared
otherwise. The O (overflow) bit is set if the result exceeds the maximum
positive or negative range, i.e., the result overflows into the sign bit. In
addition, an integer overflow interrupt (ARITH trap) occurs if the user trap
bit is set.
*/
static uint32 add_32 (uint32 augend, uint32 addend)
{
t_uint64 sum;
sum = (t_uint64) augend + (t_uint64) addend; /* sum the values */
SET_CARRY (sum > D32_UMAX); /* set C if there is a carry out of the MSB */
SET_OVERFLOW (D32_SIGN /* set O if the signs */
& (~augend ^ addend) /* of the operands are the same */
& (augend ^ sum)); /* but the sign of the result differs */
return (uint32) sum & D32_MASK; /* return the lower 32 bits of the sum */
}
/* Subtract two 32-bit numbers.
Two 32-bit values are subtracted, and the 32-bit difference is returned. The
C (carry) bit in the status register is set if the subtraction did not
require a borrow for the most-significant bit. The O (overflow) bit is set
if the result exceeds the maximum positive or negative range, i.e., the
result borrows from the sign bit. In addition, an integer overflow interrupt
(ARITH trap) occurs if the user trap bit is set.
Implementation notes:
1. The carry bit is set to the complement of the borrow, i.e., carry = 0 if
there is a borrow and 1 is there is not.
*/
static uint32 sub_32 (uint32 minuend, uint32 subtrahend)
{
t_uint64 difference;
difference = (t_uint64) minuend - (t_uint64) subtrahend; /* subtract the values */
SET_CARRY (subtrahend <= minuend); /* set C if no borrow from the MSB was done */
SET_OVERFLOW (D32_SIGN /* set O if the signs */
& (minuend ^ subtrahend) /* of the operands differ */
& (minuend ^ difference)); /* as do the signs of the minuend and result */
return (uint32) difference & D32_MASK; /* return the lower 32 bits of the difference */
}
/* Shift single- and double-word operands.
An arithmetic, logical, or circular left or right shift is performed in place
on the 16-bit or 32-bit operand in RA or RB and RA, respectively. Condition
code A is set for the result. The shift count and shift direction are
derived from the instruction supplied.
An arithmetic left shift retains the sign bit; an arithmetic right shift
copies the sign bit. Logical shifts fill zeros into the LSB or MSB.
Circular shifts rotate bits out of the MSB and into the LSB, or vice versa.
On entry, the shift count is extracted from the instruction. If the
instruction is indexed, the value in the X register is added to the count.
For the type of shift selected, the fill bits are determined: sign bits fill
for an arithmetic shift, zero bits fill for a logical shift, and operand bits
fill for a circular shift. The result of a shift in excess of the operand
size is also determined.
If the shift count is zero, then the result is the original operand.
Otherwise, if the count is less than the operand size, the selected shift is
performed. A right shift of any type is done by shifting the operand and
filling with bits of the appropriate type. An arithmetic left shift is done
by shifting the operand and restoring the sign. A logical or circular shift
is done by shifting the operand and filling with bits of the appropriate
type.
The result is restored to the TOS register(s), and CCA is set before
returning.
Implementation notes:
1. 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.
2. The C standard specifies that the results of bitwise shifts with counts
greater than the operand sizes are undefined, so we must handle excessive
shifts explicitly.
3. 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.
4. The compiler requires a "default" case (instead of a "normalizing"
case) for the switch statement. Otherwise, it will complain that
"fill" and "result" are potentially undefined, even though all
enumeration values are covered.
*/
static void shift_16_32 (HP_WORD opcode, SHIFT_TYPE shift, OPERAND_SIZE op_size)
{
typedef struct {
uint32 sign; /* the sign bit of the operand */
uint32 data; /* the data mask of the operand */
uint32 width; /* the width of the operand in bits */
} PROPERTY;
static const PROPERTY prop [2] = {
{ D16_SIGN, D16_MASK & ~D16_SIGN, D16_WIDTH }, /* 16-bit operand properties */
{ D32_SIGN, D32_MASK & ~D32_SIGN, D32_WIDTH } /* 32-bit operand properties */
};
uint32 count, operand, fill, result;
count = SHIFT_COUNT (opcode); /* get the shift count from the instruction */
if (opcode & X_FLAG) /* if the instruction is indexed */
count = count + X & SHIFT_COUNT_MASK; /* then add the index to the count modulo 64 */
operand = RA; /* get the (lower half of the) operand */
if (op_size == size_32) /* if the operand size is 32 bits */
operand = RB << D16_WIDTH | operand; /* then merge the upper half of the operand */
switch (shift) { /* dispatch the shift operation */
case arithmetic: /* for an arithmetic shift */
fill = operand & prop [op_size].sign ? ~0 : 0; /* fill with copies of the sign bit */
if (opcode & SHIFT_RIGHT_FLAG) /* for a right shift */
result = fill; /* the excessive shift result is all fill bits */
else /* whereas for a left shift */
result = prop [op_size].sign; /* the excessive shift result is just the sign bit */
break;
case logical: /* for a logical shift */
fill = 0; /* fill with zeros */
result = 0; /* the excessive shift result is all zeros */
break;
case circular: /* for a circular shift */
fill = operand; /* fill with the operand */
count = count % prop [op_size].width; /* an excessive shift count is reduced modulo the word width */
result = 0; /* so there is no excessive shift result */
break;
default: /* normalizing shifts are not used */
return;
}
if (count == 0) /* if the shift count is zero */
result = operand; /* then the result is the operand value */
else if (count < prop [op_size].width) /* otherwise if the shift count is not excessive */
if (opcode & SHIFT_RIGHT_FLAG) /* then if this is a right shift of any type */
result = operand >> count /* then shift the operand */
| fill << prop [op_size].width - count; /* and fill with fill bits */
else if (shift == arithmetic) /* otherwise if this is an arithmetic left shift */
result = operand << count & prop [op_size].data /* then shift the operand */
| fill & prop [op_size].sign; /* and restore the sign bit */
else /* otherwise this is a logical or circular left shift */
result = operand << count /* so shift the operand */
| fill >> prop [op_size].width - count; /* and fill with fill bits */
RA = LOWER_WORD (result); /* store the lower word on the TOS */
if (op_size == size_16) /* if the operand is a single word */
SET_CCA (RA, 0); /* then set the condition code */
else { /* otherwise the operand is a double word */
RB = UPPER_WORD (result); /* so store the upper word in the NOS */
SET_CCA (RB, RA); /* and then set the condition code */
}
return;
}
/* Shift triple- and quad-word operands.
An arithmetic left or right shift or normalizing left shift is performed
in place on the 48-bit or 64-bit operand in RC/RB/RA or RD/RC/RB/RA,
respectively. Condition code A is set for the result. The shift count and
shift direction are derived from the instruction supplied.
An arithmetic left shift retains the sign bit; an arithmetic right shift
copies the sign bit. A normalizing shift does not specify a shift count.
Instead, the operand is shifted until bit 6 is set, bits 0-5 are cleared, and
the shift count is returned in the X register.
On entry for an arithmetic shift, the shift count is extracted from the
instruction. If the instruction is indexed, the value in the X register is
added to the count. If the shift count is zero, then the result is the
original operand. Otherwise, if the count is less than the operand size, the
selected shift is performed. A right shift is done by shifting the operand
and filling with sign bits. A left shift is done by shifting the operand and
restoring the sign.
For a normalizing shift with at least one bit set to the right of bit 5, the
operand is left-shifted and X is incremented until bit 6 is set. Bits 0-5
are then masked off. If no bits are set to the right of bit 5, X is set to,
or incremented by, the maximum shift count, CCE is set, and the operand is
not altered.
After a successful shift, the result is restored to the TOS registers, and
CCA is set before returning.
Implementation notes:
1. Logical and circular shifts are unsupported as they are not offered by
the instruction set.
2. All of the shift instructions except QASL and QASR use bit 9 to indicate
a left (0) or right (1) shift and bit 4 to indicate that the shift count
includes the index register value. Bit 9 is always on for QASL and QASR,
which use bit 4 to indicate a left or right shift, and which always
include the index register value. To simplify handling, the QASL and
QASR executors move the left/right indication to bit 9 and set bit 4 on
before calling this routine.
*/
static void shift_48_64 (HP_WORD opcode, SHIFT_TYPE shift, OPERAND_SIZE op_size)
{
typedef struct {
t_uint64 sign; /* the sign bit of the operand */
t_uint64 data; /* the data mask of the operand */
uint32 width; /* the width of the operand in bits */
uint32 padding; /* unused padding to suppress an alignment warning */
} PROPERTY;
static const PROPERTY prop [4] = {
{ 0, 0, 0 }, /* (unused 16-bit properties) */
{ 0, 0, 0 }, /* (unused 32-bit properties) */
{ D48_SIGN, D48_MASK & ~D48_SIGN, D48_WIDTH }, /* 48-bit operand properties */
{ D64_SIGN, D64_MASK & ~D64_SIGN, D64_WIDTH } /* 64-bit operand properties */
};
uint32 count;
t_uint64 operand, fill, result;
operand = (t_uint64) RC << D32_WIDTH | TO_DWORD (RB, RA); /* merge the first three words of the operand */
if (op_size == size_64) /* if the operand size is 64 bits */
operand = (t_uint64) RD << D48_WIDTH | operand; /* then merge the fourth word of the operand */
if (shift == arithmetic) { /* if this is an arithmetic shift */
count = SHIFT_COUNT (opcode); /* then the instruction contains the shift count */
if (opcode & X_FLAG) /* if the instruction is indexed */
count = count + X & SHIFT_COUNT_MASK; /* then add the index to the count modulo 64 */
fill = operand & prop [op_size].sign ? ~0 : 0; /* filling will use copies of the sign bit */
if (count == 0) /* if the shift count is zero */
result = operand; /* then the result is the operand value */
else if (count < prop [op_size].width) /* otherwise if the shift count is not excessive */
if (opcode & SHIFT_RIGHT_FLAG) /* then if this is a right shift */
result = operand >> count /* then shift the operand */
| fill << prop [op_size].width - count; /* and fill with fill bits */
else /* otherwise it is a left shift */
result = operand << count & prop [op_size].data /* so shift the operand */
| fill & prop [op_size].sign; /* and restore the sign bit */
else /* otherwise the shift count exceeds the operand size */
if (opcode & SHIFT_RIGHT_FLAG) /* so if this is a right shift */
result = fill; /* then the excessive shift result is all fill bits */
else /* whereas for a left shift */
result = prop [op_size].sign; /* the excessive shift result is just the sign bit */
}
else if (shift == normalizing) { /* otherwise if this is a (left) normalizing shift */
if ((opcode & X_FLAG) == 0) /* then if the instruction is not indexed */
X = 0; /* then clear the shift count */
if (operand & NORM_MASK) { /* if there's at least one unnormalized bit set */
result = operand; /* then start with the operand */
while ((result & NORM_BIT) == 0) { /* while the result is unnormalized */
result = result << 1; /* left-shift the result */
X = X + 1; /* and increment the shift count */
}
result = result & NORM_MASK; /* mask off the leading bits */
X = X & R_MASK; /* and wrap the count value */
}
else { /* otherwise there are no bits to normalize */
X = X + 42 & R_MASK; /* so report the maximum shift count */
SET_CCE; /* set the condition code */
return; /* and return with the operand unmodified */
}
}
else /* otherwise the shift type */
return; /* is not supported by this routine */
RA = LOWER_WORD (result); /* restore the */
RB = UPPER_WORD (result); /* lower three words */
RC = LOWER_WORD (result >> D32_WIDTH); /* to the stack */
if (op_size == size_48) /* if the operand size is 48 bits */
SET_CCA (RC, RB | RA); /* then set the condition code */
else { /* otherwise the size is 64 bits */
RD = LOWER_WORD (result >> D48_WIDTH); /* so merge the upper word */
SET_CCA (RD, RC | RB | RA); /* and then set the condition code */
}
return;
}
/* Check a value against the stack bounds.
This routine checks a new frame (Q) or stack memory (SM) pointer value to
ensure that it is within the stack bounds. If the value does not lie between
DB and Z, a trap will occur.
The SETR instruction sets the frame and stack pointers, and the SXIT, ADDS,
and SUBS instructions adjust the stack pointer. Each verifies that the new
value is between DB and Z before storing the value in the Q or SM register.
If the value is greater than Z, a stack overflow trap is taken; if the value
is less than DB, a stack underflow trap is taken.
Implementation notes:
1. Conceptually, ADDS can only exceed Z, whereas SXIT and SUBS can only drop
below DB. However, the microcode for all three instructions checks that
both Z - new_SM and new_SM - DB are positive; if not, the routine traps
to stack overflow or underflow, respectively. As the new SM value is
calculated modulo 2^16, wraparound overflows and underflows are caught
only if they are within 32K of the Z or DB values. For full coverage,
both tests are necessary for each call, as an ADDS wraparound of 48K,
e.g., would be caught as a stack underflow. Simulation performs the same
tests to obtain the same behavior, rather than checking that new_SM <= Z
and DB <= new_SM.
2. 32-bit subtractions are performed to ensure that wraparound overflows are
caught.
*/
static void check_stack_bounds (HP_WORD new_value)
{
if ((uint32) Z - new_value > D16_SMAX) /* if the new value is not within 32K below Z */
MICRO_ABORT (trap_Stack_Overflow); /* then trap for an overflow */
else if ((uint32) new_value - DB > D16_SMAX && NPRV) /* otherwise if the new value is not within 32K above DB */
MICRO_ABORT (trap_Stack_Underflow); /* then trap for an underflow unless the mode is privileged */
else /* otherwise the new value */
return; /* is within the stack bounds */
}
/* Perform a test, control, or set interrupt I/O operation.
The I/O operation specified in the "command" parameter is sent to the device
whose device number stored on the stack at location S - K. The K-field of
the I/O instruction present in the CIR is extracted and subtracted from the
current stack pointer. The resulting memory location is read, and the lower
byte is used as the device number. The I/O command is sent, along with the
value in the TOS for a CIO instruction, and the result is obtained.
If the device number is invalid, an I/O timeout will result. If this occurs,
the timeout flag in CPX1 is reset, condition code "less than" is set, and
this routine returns 0. Otherwise, condition code "equal" is set to indicate
success, and the device and result values are merged and returned (which will
be non-zero, because zero is not a valid device number).
Implementation notes:
1. A checked access to memory is requested to obtain the device number. As
privileged mode has been previously ascertained, the memory check serves
only to return a TOS register value if the resulting address is between
SM and SR.
*/
static uint32 tcs_io (IO_COMMAND command)
{
uint32 address;
HP_WORD device, result;
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
address = SM + SR - IO_K (CIR) & LA_MASK; /* get the location of the device number */
cpu_read_memory (stack_checked, address, &device); /* read it from the stack */
device = LOWER_BYTE (device); /* and use only the lower byte of the value */
result = iop_direct_io (device, command, /* send the I/O order to the device */
(command == ioCIO ? RA : 0)); /* along with the control word for a CIO instruction */
if (CPX1 & cpx1_IOTIMER) { /* if an I/O timeout occurred */
CPX1 &= ~cpx1_IOTIMER; /* then clear the timer */
SET_CCL; /* and set condition code "less than" */
return 0; /* and fail the instruction */
}
else { /* otherwise */
SET_CCE; /* set the condition code for success */
return TO_DWORD (device, result); /* and return the (non-zero) device and result values */
}
}
/* Perform a start, read, or write I/O operation.
The I/O operation specified in the "command" parameter is sent to the device
whose device number stored on the stack at location S - K, where K is the
K-field value of the I/O instruction present in the CIR. A Test I/O order is
first sent to the device to determine if it is ready. If the device number
is invalid, the routine returns zero with condition code "less than" set to
indicate failure. If the Test I/O succeeded, the device number and test
result are obtained.
The test result is checked to see if the bit specified by the "ready_flag"
parameter is set. If it is not, then the device is not ready, so the test
result is pushed onto the TOS, condition code "greater than" is set, and zero
is returned to indicate failure. If the bit is set, the device is ready for
the operation.
For a Start I/O order, the starting address of the I/O program, located on
the TOS, is stored in the first word of the Device Reference Table entry
corresponding to the device number. The I/O command is sent, along with the
value in the TOS for a WIO instruction, and the result is obtained.
Condition code "equal" is set to indicate success, and the device and result
values are merged and returned (which will be non-zero, because zero is not a
valid device number).
Implementation notes:
1. The initial Test I/O order verifies that the mode is privileged and that
the device number is valid. Therefore, the result of the command
operation need not be tested for validity.
*/
static uint32 srw_io (IO_COMMAND command, HP_WORD ready_flag)
{
uint32 test;
HP_WORD device, result;
test = tcs_io (ioTIO); /* send a Test I/O order to the device */
if (test == 0) /* if an I/O timeout occurred */
return 0; /* then return 0 with CCL set to fail the instruction */
device = UPPER_WORD (test); /* split the returned value */
result = LOWER_WORD (test); /* into the device number and test result */
if (result & ready_flag) { /* if the device is ready */
if (command == ioSIO) /* then if this is an SIO order */
cpu_write_memory (absolute, device * 4, RA); /* then write the I/O program address to the DRT */
result = iop_direct_io (device, command, /* send the I/O order to the device */
(command == ioWIO ? RA : 0)); /* along with the data word for a WIO instruction */
SET_CCE; /* set the condition code for success */
return TO_DWORD (device, result); /* and return the (non-zero) device and result values */
}
else { /* otherwise the device is not ready */
cpu_push (); /* so push the stack down */
RA = result; /* and store the TIO response on the TOS */
SET_CCG; /* set the condition code to indicate "not ready" */
return 0; /* and fail the instruction */
}
}
/* Test for a pending interrupt.
This routine is called from within an executor for an interruptible
instruction to test for a pending interrupt. It counts an event tick and
returns TRUE if the instruction should yield, either for an interrupt or for
an event error, or FALSE if the instruction should continue.
Instructions that potentially take a long time (e.g., MOVE, SCU, LLSH) test
for pending interrupts after each word or byte moved or scanned. The design
of these instructions is such that an interrupt may be serviced and the
instruction resumed without disruption. For example, the MOVE instruction
updates the source and target addresses and word count on the stack after
each word moved. If the instruction is interrupted, the values on the stack
indicate where to resume after the interrupt handler completes.
Implementation notes:
1. The routine is essentially the same sequence as is performed at the top
of the instruction execution loop in the "sim_instr" routine. The
differences are that this routine backs up P to rerun the instruction
after the interrupt is serviced, and the interrupt holdoff test necessary
for the SED instruction isn't done here, as this routine is not called by
the SED executor.
2. The event interval decrement that occurs in the main instruction loop
after each instruction execution is cancelled here if "sim_process_event"
returns an error code. This is done so that a STEP command does not
decrement sim_interval twice. Note that skipping the initial decrement
here does not help, as it's the sim_interval value AFTER the call to
sim_process_event that must be preserved.
*/
static t_bool interrupt_pending (t_stat *status)
{
uint32 device_number = 0;
sim_interval = sim_interval - 1; /* count the cycle */
if (sim_interval <= 0) { /* if an event timeout expired */
*status = sim_process_event (); /* then process the event service */
if (*status != SCPE_OK) { /* if the service failed */
P = P - 1 & R_MASK; /* then back up to reenter the instruction */
sim_interval = sim_interval + 1; /* and cancel the instruction loop increment */
return TRUE; /* abort the instruction and stop the simulator */
}
}
else /* otherwise */
*status = SCPE_OK; /* indicate good status from the service */
if (sel_request) /* if a selector channel request is pending */
sel_service (1); /* then service it */
if (mpx_request_set) /* if a multiplexer channel request is pending */
mpx_service (1); /* then service it */
if (iop_interrupt_request_set && STA & STATUS_I) /* if a hardware interrupt request is pending and enabled */
device_number = iop_poll (); /* then poll to acknowledge the request */
if (CPX1 & CPX1_IRQ_SET) { /* if an interrupt is pending */
P = P - 1 & R_MASK; /* then back up to reenter the instruction */
cpu_run_mode_interrupt (device_number); /* and set up the service routine */
return TRUE; /* abort the instruction */
}
else /* otherwise */
return FALSE; /* continue with the current instruction */
}
/* Decrement the stack pointer.
Pop values from the stack until the stack pointer has been decremented by the
amount indicated by the "decrement" parameter.
The word and byte move and comparison instructions include a stack decrement
field that may be zero or a positive value indicating the number of words to
remove at the end of the instruction. This routine is called to implement
this feature.
Note that the stack decrement is performed only at the completion of these
instructions. If an instruction is interrupted, the decrement is not done,
as the parameters on the stack will be needed when execution of the
instruction is resumed after the interrupt handler completes.
*/
static void decrement_stack (uint32 decrement)
{
while (decrement > 0) { /* decrement the stack pointer */
cpu_pop (); /* by the count specified */
decrement = decrement - 1; /* by the instruction */
}
return;
}
/* Convert a data- or program-relative byte address to a word address.
The supplied byte offset from DB or PB is converted to a memory address,
bounds-checked, and then returned. If the supplied block length is not zero,
the converted address is assumed to be the starting address of a block, and
an ending address, based on the block length, is calculated and
bounds-checked. If either address lies outside of the segment associated
with the access class, a Bounds Violation trap occurs, unless a privileged
data access is requested.
Byte offsets into data segments present problems, in that negative offsets
are permitted (to access the DL-to-DB area), but there are not enough bits to
represent all locations unambiguously in the potential -32K to +32K word
offset range. Therefore, a byte offset with bit 0 = 1 can represent either a
positive or negative word offset from DB, depending on the interpretation.
The HP 3000 adopts the convention that if the address resulting from a
positive-offset interpretation does not fall within the DL-to-S range, then
32K is added to the address, effectively changing the interpretation from a
positive to a negative offset. If this new address does not fall within the
DL-to-S range, a Bounds Violation trap occurs if the mode is non-privileged.
The reinterpretation as a negative offset is performed only if the CPU is not
in split-stack mode (where either DBANK is different from SBANK, or DB does
not lie between DL and Z), as extra data segments do not permit negative-DB
addressing. Reinterpretation is also not used for code segments, as negative
offsets from PB are not permitted.
Implementation notes:
1. This routine implements the DBBC microcode subroutine.
*/
static uint32 byte_to_word_address (ACCESS_CLASS class, uint32 byte_offset, uint32 block_length)
{
uint32 starting_word, ending_word, increment;
if (block_length & D16_SIGN) /* if the block length is negative */
increment = 0177777; /* then the memory increment is negative also */
else /* otherwise */
increment = 1; /* the increment is positive */
if (class == data) { /* if this is a data access */
starting_word = DB + (byte_offset >> 1) & LA_MASK; /* then determine the starting word address */
if (DBANK == SBANK && DL <= DB && DB <= Z /* if not in split-stack mode */
&& (starting_word < DL || starting_word > SM)) { /* and the word address is out of range */
starting_word = starting_word ^ D16_SIGN; /* then add 32K and try again */
if (NPRV && (starting_word < DL || starting_word > SM)) /* if non-privileged and still out of range */
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
}
if (block_length != 0) { /* if a block length was supplied */
ending_word = /* then determine the ending word address */
starting_word + ((block_length - increment + (byte_offset & 1)) >> 1) & LA_MASK;
if (NPRV && (ending_word < DL || ending_word > SM)) /* if non-privileged and the address is out of range */
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
}
}
else { /* otherwise this is a program address */
starting_word = PB + (byte_offset >> 1) & LA_MASK; /* so determine the starting word address */
if (starting_word < PB || starting_word > PL) /* if the starting address is out of range */
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
if (block_length != 0) { /* if a block length was supplied */
ending_word = /* then determine the ending address */
starting_word + ((block_length - increment + (byte_offset & 1)) >> 1) & LA_MASK;
if (ending_word < PB || ending_word > PL) /* if the ending address is out of range */
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
}
}
return starting_word; /* return the starting word address */
}
/* Move a block of words in memory.
A block of words is moved from a source address to a destination address. If
a pending interrupt is detected during the move, the move is interrupted to
service it. Otherwise at the completion of the move, the stack is
decremented by the amount indicated.
On entry, the "source_class" parameter indicates the memory classification
for source reads. If the classification is absolute, the "source_base"
parameter contains the physical address (i.e., memory bank and offset) of the
base of the first word to move. If it is not absolute, the parameter
contains the offset within the bank implied by the classification.
Similarly, the "dest_class" and "dest_base" parameters designate the base of
the first word to write. The "decrement" parameter contains the number of
stack words to delete if the move completed successfully.
If the source is absolute, the TOS registers RA, RB, and RD contain the
count, source offset from the source base, and destination offset from the
destination base, respectively. Otherwise, the the TOS registers RA, RB, and
RC contain the count and bases.
Register RA contains an unsigned (positive) word count when called for the
MTDS and MFDS instructions, and a signed word count otherwise. If the word
count is negative, the move is performed in reverse order, i.e., starting
with the last word of the block and ending with the first word of the block.
If the word count is zero on entry, the move is skipped, but the stack
decrement is still performed.
On exit, the TOS registers are updated for the block (or partial block, in
the case of an intervening interrupt), and normal or error status from the
interrupt check is returned.
Implementation notes:
1. This routine implements the MVWS microcode subroutine.
2. The type of count (unsigned or signed) is determined by whether or not
the CIR holds an MTDS or MFDS instruction.
3. Incrementing and masking of the TOS registers must be done after each
word is moved, rather than at loop completion, so that an interrupt will
flush the correct TOS values to memory.
*/
static t_stat move_words (ACCESS_CLASS source_class, uint32 source_base,
ACCESS_CLASS dest_class, uint32 dest_base,
uint32 decrement)
{
HP_WORD operand, *RX;
uint32 increment, source_bank, dest_bank;
t_stat status;
if (RA & D16_SIGN && (CIR & MTFDS_MASK) != MTFDS) /* if the count is signed and negative */
increment = 0177777; /* then the memory increment is negative */
else /* otherwise */
increment = 1; /* the increment is positive */
source_bank = source_base & ~LA_MASK; /* extract the source bank */
dest_bank = dest_base & ~LA_MASK; /* and destination bank in case they are needed */
if (source_class == absolute) /* if the source transfer is absolute */
RX = & RD; /* then the destination offset is in RD */
else /* otherwise */
RX = & RC; /* it is in RC */
while (RA != 0) { /* while there are words to move */
cpu_read_memory (source_class, /* read a source word */
source_bank | source_base + RB & LA_MASK,
&operand);
cpu_write_memory (dest_class, /* move it to the destination */
dest_bank | dest_base + *RX & LA_MASK,
operand);
RA = RA - increment & R_MASK; /* update the count */
RB = RB + increment & R_MASK; /* and the source */
*RX = *RX + increment & R_MASK; /* and destination offsets */
if (interrupt_pending (&status)) /* if an interrupt is pending */
return status; /* then return with an interrupt set up or an error */
}
decrement_stack (decrement); /* decrement the stack as indicated */
return SCPE_OK; /* and return the success of the move */
}
/* CPU base set local instruction execution routines */
/* Execute a move or special instruction (subopcode 02, field 00).
This routine is called to execute the move or register instruction currently
in the CIR. The instruction formats are:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 0 | move op | opts/S decrement | Move
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 0 | special op | 0 0 | sp op | Special
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Implementation notes:
1. CIR bits 8-12 are decoded to determine the instruction. For some
instructions, e.g., MOVE, bits 11 and 12 either designate options or are
not decoded (i.e., are "don't care" bits). These instructions are
duplicated in the SR preadjustment table and carry multiple case labels
in the instruction dispatcher.
2. The IXIT, LOCK, PCN, and UNLK instructions decode bits 12-15, including
the reserved bits 12 and 13. The canonical forms have the reserved bits
set to zero, but the hardware decodes bits 12-15 as IXIT = 0000, LOCK =
nn01, PCN = nnn0, and UNLK = nn11 (where "n..." is any collective value
other than 0). If a non-canonical form is used, and the UNDEF stop is
active, a simulation stop will occur. If the stop is bypassed or not
set, then the instruction will execute as in hardware.
The LSEA, SSEA, LDEA, and SDEA instructions decode bits 14-15; the
reserved bits 12-13 are not decoded and do not affect the instruction
interpretation.
3. Two methods of mapping non-canonical forms were examined: using a 16-way
remapping table if SS_UNDEF was not set and a 4-way switch statement with
the default returning STOP_UNDEF, or using a 16-way switch statement with
the non-canonical values returning STOP_UNDEF if SS_UNDEF is set and
falling into their respective executors otherwise. The former required
significantly more instructions and imposed a lookup penalty on canonical
instructions for the non-stop case. The latter used fewer instructions,
imposed no penalty in the non-stop case, and the two extra tests required
only three instructions each. The latter method was adopted.
4. The MVB and MVBW byte-move instructions perform read-modify-write actions
for each byte moved. This is inefficient -- each word is read and
updated twice -- but it is necessary, as interrupts are checked after
each byte is moved, and it is how the microcode handles these
instructions.
5. The MVBW instruction microcode performs bounds checks on the movement by
determining the number of words from the source and target starting
addresses to the address of the top of the stack (SM). The smaller of
these values is used as a count that is decremented within the move loop.
When the count reaches zero, a bounds violation occurs if the mode is not
privileged. This is used instead of comparing the source and target
addresses to SM to reduce the per-iteration checks from two to one.
6. The IXIT microcode assumes that the machine is in privileged mode if the
dispatcher-is-active flag is set. In simulation, the privileged mode
check is performed for all IXIT paths.
7. When IXIT returns to a user process, the microcode sets the "trace flag"
located at Q-13 in the ICS global area to -1. The only description of
this location is in the system tables manual, which says "flag set
non-zero on IXIT away from ICS." The action to set this value was added
as a patch to the Series II microcode; however, this action is not
present in the corresponding Series 64 microcode. The description
appears in the MPE V tables manual for version G.01.00 but is gone from
the manual for version G.08.00. No further information regarding the
purpose of this flag has been found.
8. The PCN microcode clears a TOS register via a queue-down operation, if
necessary, before checking that the machine is in privileged mode. In
simulation, the check is performed before the register clear. However,
if a Mode Violation trap occurs, all of the TOS registers are flushed to
memory, so the result is the same.
*/
static t_stat move_spec (void)
{
static const uint8 preadjustment [32] = { /* stack preadjustment, indexed by operation */
3, 3, 3, 3, 3, 3, 3, 3, /* MOVE MOVE MOVE MOVE MVB MVB MVB MVB */
4, 4, 2, 4, 4, 4, 2, 4, /* MVBL MABS SCW MTDS MVLB MDS SCU MFDS */
2, 2, 2, 2, 3, 3, 3, 3, /* MVBW MVBW MVBW MVBW CMPB CMPB CMPB CMPB */
4, 4, 0, 0, 2, 2, 0, 0 /* RSW/LLSH PLDA/PSTA xSEA/xDEA IXIT/etc. */
};
int32 byte_count;
uint32 operation, address;
HP_WORD operand, bank, offset, base;
HP_WORD byte, test_byte, terminal_byte, increment, byte_class, loop_condition;
HP_WORD source_bank, source, source_end, target_bank, target, target_end;
HP_WORD stack_db, ics_q, delta_qi, disp_counter, delta_q, new_q, new_sm, device;
t_bool q_is_qi, disp_active;
ACCESS_CLASS class;
t_stat status = SCPE_OK;
operation = MSSUBOP (CIR); /* get the suboperation from the instruction */
PREADJUST_SR (preadjustment [operation]); /* preadjust the TOS registers to the required number */
switch (operation) { /* dispatch the move or special operation */
case 000: /* MOVE (none; STUN, STOV, BNDV) */
case 001:
case 002:
case 003:
if (RA != 0) { /* if the word count is non-zero */
if (RA & D16_SIGN) /* then if it is negative */
increment = 0177777; /* then the memory increment is negative */
else /* otherwise */
increment = 1; /* the increment is positive */
while (SR > 3) /* if more than three TOS register are valid */
cpu_queue_down (); /* then queue them down until exactly three are left */
if (CIR & DB_FLAG) { /* if the move is from the data segment */
class = data; /* then set for data access */
base = DB; /* and base the offset on DB */
source = DB + RB & LA_MASK; /* determine the starting */
source_end = source + RA - increment & LA_MASK; /* and ending data source addresses */
if (NPRV /* if the mode is non-privileged */
&& (source < DL || source > SM /* and the starting or ending address */
|| source_end < DL || source_end > SM)) /* is outside of the data segment */
MICRO_ABORT (trap_Bounds_Violation); /* then trap with a Bounds Violation */
}
else { /* otherwise the move is from the code segment */
class = program; /* so set for program access */
base = PB; /* and base the offset on PB */
source = PB + RB & LA_MASK; /* determine the starting */
source_end = source + RA - increment & LA_MASK; /* and ending program source addresses */
if (source < PB || source > PL /* if the starting or ending address */
|| source_end < PB || source_end > PL) /* is outside of the code segment */
MICRO_ABORT (trap_Bounds_Violation); /* then trap with a Bounds Violation */
}
target = DB + RC & LA_MASK; /* calculate the starting */
target_end = target + RA - increment & LA_MASK; /* and ending data target addresses */
if (NPRV /* if the mode is non-privileged */
&& (target < DL || target > SM /* and the starting or ending target address */
|| target_end < DL || target_end > SM)) /* is outside of the data segment */
MICRO_ABORT (trap_Bounds_Violation); /* then trap with a Bounds Violation */
status = move_words (class, base, data, DB, /* move the words and adjust the stack */
SDEC2 (CIR));
}
else /* otherwise there are no words to move */
decrement_stack (SDEC2 (CIR)); /* so just adjust the stack as indicated */
break;
case 004: /* MVB (none; STUN, STOV, BNDV) */
case 005:
case 006:
case 007:
while (SR > 3) /* if more than three TOS register are valid */
cpu_queue_down (); /* then queue them down until exactly three are left */
if (RA != 0) { /* if the byte count is non-zero */
if (RA & D16_SIGN) /* then if it is negative */
increment = 0177777; /* then the memory increment is negative */
else /* otherwise */
increment = 1; /* the increment is positive */
if (CIR & DB_FLAG) /* if the move is from the data segment */
class = data; /* then classify as a data access */
else /* otherwise the move is from the code segment */
class = program; /* so classify as a program access */
source = byte_to_word_address (class, RB, RA); /* convert the source byte address and check bounds */
target = byte_to_word_address (data, RC, RA); /* convert the target byte address and check bounds */
while (RA != 0) { /* while there are bytes to move */
cpu_read_memory (class, source, &operand); /* read a source word */
if (RB & 1) /* if the byte address is odd */
byte = LOWER_BYTE (operand); /* then get the lower byte */
else /* otherwise the address is even */
byte = UPPER_BYTE (operand); /* so get the upper byte */
if ((RB & 1) == (HP_WORD) (increment == 1)) /* if the last byte of the source word was accessed */
source = source + increment & LA_MASK; /* then update the word address */
cpu_read_memory (data, target, &operand); /* read the target word */
if (RC & 1) /* if the byte address is odd */
operand = REPLACE_LOWER (operand, byte); /* then replace the lower byte */
else /* otherwise the address is even */
operand = REPLACE_UPPER (operand, byte); /* so replace the upper byte */
cpu_write_memory (data, target, operand); /* write the word back */
if ((RC & 1) == (HP_WORD) (increment == 1)) /* if the last byte of the target word was accessed */
target = target + increment & LA_MASK; /* then update the word address */
RA = RA - increment & R_MASK; /* update the count */
RB = RB + increment & R_MASK; /* and the source */
RC = RC + increment & R_MASK; /* and destination offsets */
if (interrupt_pending (&status)) /* if an interrupt is pending */
return status; /* then return with an interrupt set up or an error */
}
}
decrement_stack (SDEC2 (CIR)); /* adjust the stack as indicated by the instruction */
break;
case 010: /* MVBL (none; STUN, STOV, MODE) */
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
if (RA != 0) { /* if the word count is non-zero */
cpu_queue_down (); /* then queue down so exactly three TOS registers are left */
status = move_words (data, DB, stack, DL, /* and move the words and adjust the stack */
SDEC2 (CIR));
}
else /* otherwise there are no words to move */
decrement_stack (SDEC2 (CIR)); /* so just adjust the stack as indicated */
break;
case 011: /* MABS (none; MODE, STUN) */
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
if (RA != 0) { /* if the word count is non-zero */
cpu_read_memory (stack, SM, &target_bank); /* then get the target data bank number */
status = move_words (absolute, TO_PA (RC, 0), /* move the words and adjust the stack */
absolute, TO_PA (target_bank, 0),
SDEC3 (CIR));
}
else /* otherwise there are no words to move */
decrement_stack (SDEC3 (CIR)); /* so just adjust the stack as indicated */
break;
case 012: /* SCW (CCB, C; STUN, STOV, BNDV) */
case 016: /* SCU (C; STUN, STOV, BNDV) */
while (SR > 2) /* if more than two TOS register are valid */
cpu_queue_down (); /* then queue them down until exactly two are left */
test_byte = LOWER_BYTE (RA); /* get the test byte */
terminal_byte = UPPER_BYTE (RA); /* and the terminal byte */
source = byte_to_word_address (data, RB, 0); /* convert the source byte address and check the bounds */
cpu_read_memory (data, source, &operand); /* read the first word */
while (TRUE) {
if (RB & 1) { /* if the byte address is odd */
if (interrupt_pending (&status)) /* then if an interrupt is pending */
return status; /* then return with an interrupt set up or an error */
byte = LOWER_BYTE (operand); /* get the lower byte */
source = source + 1 & LA_MASK; /* and update the word address */
if (NPRV && source > SM) /* if non-privileged and the address is out of range */
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
cpu_read_memory (data, source, &operand); /* read the next word */
}
else /* otherwise the address is even */
byte = UPPER_BYTE (operand); /* so get the upper byte */
if (operation == 012) /* if this is the "scan while" instruction */
if (byte == test_byte) /* then if the byte matches the test byte */
RB = RB + 1 & R_MASK; /* then update the byte offset and continue */
else { /* otherwise the "while" condition fails */
SET_CARRY (byte == terminal_byte); /* so set carry if the byte matches the terminal byte */
SET_CCB (byte); /* and set the condition code */
break; /* and terminate the scan */
}
else /* otherwise this is the "scan until" instruction */
if (byte == terminal_byte) { /* so if the byte matches the terminal byte */
STA |= STATUS_C; /* then set the carry flag */
break; /* and terminate the scan */
}
else if (byte == test_byte) { /* otherwise if the byte matches the test byte */
STA &= ~STATUS_C; /* then clear the carry flag */
break; /* and terminate the scan */
}
else /* otherwise neither byte matches */
RB = RB + 1 & R_MASK; /* so update the byte offset and continue */
}
decrement_stack (SDEC2 (CIR)); /* adjust the stack as indicated by the instruction */
break;
case 013: /* MTDS (none; MODE, DSTB, STUN, ABSDST) */
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
if (RA != 0) { /* if the word count is non-zero */
cpu_setup_data_segment (RD, &bank, &offset); /* then get the target segment bank and address */
status = move_words (data, DB, /* move the words and adjust the stack */
absolute, TO_PA (bank, offset),
SDEC3 (CIR));
}
else /* otherwise there are no words to move */
decrement_stack (SDEC3 (CIR)); /* so just adjust the stack as indicated */
break;
case 014: /* MVLB (none; STUN, STOV, MODE) */
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
if (RA != 0) { /* if the word count is non-zero */
cpu_queue_down (); /* then queue down so exactly three TOS registers are left */
status = move_words (stack, DL, data, DB, /* move the words and adjust the stack */
SDEC2 (CIR));
}
else /* otherwise there are no words to move */
decrement_stack (SDEC2 (CIR)); /* so just adjust the stack as indicated */
break;
case 015: /* MDS (none; MODE, DSTV, STUN, ABSDST) */
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
if (RA != 0) { /* if the word count is non-zero */
cpu_read_memory (stack, SM, &operand); /* then get the target data segment number */
cpu_setup_data_segment (operand, &target_bank, /* get the target segment bank and address */
&target);
cpu_setup_data_segment (RC, &source_bank, /* and the source segment bank and address */
&source);
status = move_words (absolute, TO_PA (source_bank, source), /* move the words and adjust the stack */
absolute, TO_PA (target_bank, target),
SDEC3 (CIR));
}
else /* otherwise there are no words to move */
decrement_stack (SDEC3 (CIR)); /* so just adjust the stack as indicated */
break;
case 017: /* MFDS (none; MODE, DSTV, STUN, ABSDST) */
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
if (RA != 0) { /* if the word count is non-zero */
cpu_setup_data_segment (RC, &bank, &offset); /* then get the source segment bank and address */
status = move_words (absolute, TO_PA (bank, offset), /* move the words and adjust the stack */
data, DB, SDEC3 (CIR));
}
else /* otherwise there are no words to move */
decrement_stack (SDEC3 (CIR)); /* so just adjust the stack as indicated */
break;
case 020: /* MVBW (CCB; STUN, STOV, BNDV() */
case 021:
case 022:
case 023:
while (SR > 2) /* if more than two TOS registers are valid */
cpu_queue_down (); /* then queue them down until exactly two are left */
source = byte_to_word_address (data, RA, 0); /* convert the source */
target = byte_to_word_address (data, RB, 0); /* and target byte addresses and check the bounds */
if (source > target) { /* if the source is closer to SM than the target */
byte_count = (int32) (SM - source + 1) * 2; /* then set the byte count from the source */
if (RA & 1) /* if starting with the lower byte */
byte_count = byte_count - 1; /* then decrease the count by 1 */
}
else { /* otherwise the target is closer to SM */
byte_count = (int32) (SM - target + 1) * 2; /* so set the byte count from the target */
if (RB & 1) /* if starting with the lower byte */
byte_count = byte_count - 1; /* then decrease the count by 1 */
}
loop_condition = (CIR & MVBW_CCF) << MVBW_CCF_SHIFT; /* get the loop condition code flags */
while (TRUE) { /* while the loop condition holds */
cpu_read_memory (data, source, &operand); /* get the source word */
if (RA & 1) { /* if the byte address is odd */
byte = LOWER_BYTE (operand); /* then get the lower byte */
source = source + 1 & LA_MASK; /* and update the word address */
}
else /* otherwise the address is even */
byte = UPPER_BYTE (operand); /* so get the upper byte */
byte_class = cpu_ccb_table [byte]; /* classify the byte */
if ((byte_class & loop_condition) == 0) /* if the loop condition is false */
break; /* then terminate the move */
if (byte_class == CFE && CIR & MVBW_S_FLAG) /* if it's alphabetic and upshift is requested */
byte = TO_UPPERCASE (byte); /* then upshift the character */
if (byte_count == 0 && NPRV) /* if source is beyond SM and not privileged */
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
cpu_read_memory (data, target, &operand); /* read the target word */
if (RB & 1) /* if the byte address is odd */
operand = REPLACE_LOWER (operand, byte); /* then replace the lower byte */
else /* otherwise the address is even */
operand = REPLACE_UPPER (operand, byte); /* so replace the upper byte */
cpu_write_memory (data, target, operand); /* write the word back */
if (RB & 1) /* if the byte address is odd */
target = target + 1 & LA_MASK; /* then update the word address */
byte_count = byte_count - 1; /* update the count */
RA = RA + 1 & R_MASK; /* and the source */
RB = RB + 1 & R_MASK; /* and destination offsets */
if (interrupt_pending (&status)) /* if an interrupt is pending */
return status; /* then return with an interrupt set up or an error */
}
SET_CCB (byte); /* set the condition code */
decrement_stack (SDEC2 (CIR)); /* adjust the stack as indicated by the instruction */
break;
case 024: /* CMPB (CCx; STUN, STOV, BNDV) */
case 025:
case 026:
case 027:
while (SR > 3) /* if more than three TOS register are valid */
cpu_queue_down (); /* then queue them down until exactly three are left */
if (RA != 0) { /* if the byte count is non-zero */
if (RA & D16_SIGN) /* then if it is negative */
increment = 0177777; /* then the memory increment is negative */
else /* otherwise */
increment = 1; /* the increment is positive */
if (CIR & DB_FLAG) /* if the move is from the data segment */
class = data; /* then classify as a data access */
else /* otherwise the move is from the code segment */
class = program; /* so classify as a program access */
source = byte_to_word_address (class, RB, RA); /* convert the source byte address and check bounds */
target = byte_to_word_address (data, RC, RA); /* convert the target byte address and check bounds */
while (RA != 0) { /* while there are bytes to compare */
cpu_read_memory (class, source, &operand); /* read a source word */
if (RB & 1) /* if the byte address is odd */
byte = LOWER_BYTE (operand); /* then get the lower byte */
else /* otherwise the address is even */
byte = UPPER_BYTE (operand); /* so get the upper byte */
if ((RB & 1) == (HP_WORD) (increment == 1)) /* if the last byte of the source word was accessed */
source = source + increment & LA_MASK; /* then update the word address */
cpu_read_memory (data, target, &operand); /* read the target word */
if (RC & 1) /* if the byte address is odd */
test_byte = LOWER_BYTE (operand); /* then get the lower byte */
else /* otherwise the address is even */
test_byte = UPPER_BYTE (operand); /* so get the upper byte */
if (test_byte != byte) /* if the bytes do not compare */
break; /* then terminate the loop */
if ((RC & 1) == (HP_WORD) (increment == 1)) /* if the last byte of the target word was accessed */
target = target + increment & LA_MASK; /* then update the word address */
RA = RA - increment & R_MASK; /* update the count */
RB = RB + increment & R_MASK; /* and the source */
RC = RC + increment & R_MASK; /* and destination offsets */
if (interrupt_pending (&status)) /* if an interrupt is pending */
return status; /* then return with an interrupt set up or an error */
}
}
if (RA == 0) /* if the count expired */
SET_CCE; /* then set condition code "equal" */
else if (test_byte > byte) /* otherwise if the target byte > the source byte */
SET_CCG; /* set condition code "greater than" */
else /* otherwise the target byte < the source byte */
SET_CCL; /* so set condition code "less than" */
decrement_stack (SDEC2 (CIR)); /* adjust the stack as indicated by the instruction */
break;
case 030: /* RSW and LLSH */
case 031:
if (CIR & 1) { /* LLSH (CCx; STUN, MODE) */
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
while (X > 0) { /* while the link count is non-zero */
cpu_read_memory (absolute, /* read the target value */
TO_PA (RB, RA + RD & LA_MASK),
&target);
if (target >= RC) { /* if the target is greater than or equal to the test word */
if (target == DV_UMAX) /* then if the target is the largest possible value */
SET_CCG; /* then set condition code "greater than" */
else /* otherwise */
SET_CCE; /* set condition code "equal" */
break; /* end the search */
}
address = TO_PA (RB, RA + 1 & LA_MASK); /* otherwise save the link offset address */
cpu_read_memory (absolute, TO_PA (RB, RA), &RB); /* read the next link bank */
cpu_read_memory (absolute, address, &RA); /* and link offset */
X = X - 1 & R_MASK; /* decrement the count */
if (interrupt_pending (&status)) /* if an interrupt is pending */
return status; /* then return with an interrupt set up or an error */
}
if (X == 0) /* if the count expired */
SET_CCL; /* then set condition code "less than" */
}
else { /* RSW (CCA; STUN, STOV) */
cpu_push (); /* push the stack down */
RA = SWCH; /* and set the TOS from the switch register */
SET_CCA (RA, 0); /* set the condition code */
}
break;
case 032: /* PLDA and PSTA */
case 033:
if (PRIV) /* if the mode is privileged */
if (CIR & 1) { /* PSTA (none; STUN, MODE) */
PREADJUST_SR (1); /* ensure a valid TOS register */
cpu_write_memory (absolute_checked, /* before writing the TOS to memory */
X, RA); /* at address X */
cpu_pop (); /* and popping the stack */
}
else { /* PLDA (CCA; STOV, MODE) */
cpu_read_memory (absolute_checked, /* read the value at address X */
X, &operand);
cpu_push (); /* push the stack down */
RA = operand; /* and store the value on the TOS */
SET_CCA (RA, 0); /* set the condition code */
}
else /* otherwise the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* so abort with a privilege violation */
break;
case 034: /* LSEA, SSEA, LDEA, and SDEA */
case 035:
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
switch (SPECOP (CIR) & 3) { /* dispatch the special operation (bits 12-13 are not decoded) */
case 000: /* LSEA (CCA; STUN, STOV, MODE) */
while (SR > 2) /* if more than two TOS register are valid */
cpu_queue_down (); /* then queue them down until exactly two are left */
address = TO_PA (RB, RA); /* form the physical address */
cpu_read_memory (absolute_checked, /* and read the word from memory */
address, &operand);
cpu_push (); /* push the stack down */
RA = operand; /* and store the word on the TOS */
SET_CCA (RA, 0); /* set the condition code */
break;
case 001: /* SSEA (none; STUN, STOV, MODE) */
PREADJUST_SR (3); /* ensure there are three valid TOS registers */
while (SR > 3) /* if more than three TOS register are valid */
cpu_queue_down (); /* then queue them down until exactly three are left */
address = TO_PA (RC, RB); /* form the physical address */
cpu_write_memory (absolute_checked, /* and write the word on the TOS to memory */
address, RA);
cpu_pop (); /* pop the TOS */
break;
case 002: /* LDEA (CCA; STUN, STOV, MODE) */
while (SR > 2) /* if more than two TOS register are valid */
cpu_queue_down (); /* then queue them down until exactly two are left */
address = TO_PA (RB, RA); /* form the physical address */
cpu_read_memory (absolute_checked, /* and read the MSW from memory */
address, &operand);
cpu_push (); /* push the stack down */
RA = operand; /* and store the MSW on the TOS */
address = TO_PA (RC, RB + 1 & LA_MASK); /* increment the physical address */
cpu_read_memory (absolute_checked, /* read the LSW from memory */
address, &operand);
cpu_push (); /* push the stack down again */
RA = operand; /* and store the LSW on the TOS */
SET_CCA (RB, RA); /* set the condition code */
break;
case 003: /* SDEA (none; STUN, MODE) */
PREADJUST_SR (4); /* ensure there are four valid TOS registers */
address = TO_PA (RD, RC); /* form the physical address */
cpu_write_memory (absolute_checked, /* write the MSW from the NOS to memory */
address, RB);
address = TO_PA (RD, RC + 1 & LA_MASK); /* increment the physical address */
cpu_write_memory (absolute_checked, /* write the LSW on the TOS to memory */
address, RA);
cpu_pop (); /* pop the TOS */
cpu_pop (); /* and the NOS */
break;
} /* all cases are handled */
break;
case 036: /* IXIT, LOCK, PCN, and UNLK */
case 037:
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
switch (SPECOP (CIR)) { /* dispatch the special operation */
case 000: /* IXIT (none; MODE, STOV, CSTV, TRACE, ABS CST, BNDV) */
SR = 0; /* invalidate the TOS registers */
cpu_read_memory (stack, Q, &delta_q); /* read the stack marker link value */
cpu_read_memory (absolute, ICS_Q, &ics_q); /* the stack marker initial value */
cpu_read_memory (absolute, ics_q, &delta_qi); /* the dispatcher stack marker link */
cpu_read_memory (absolute, ics_q - 18 & LA_MASK, /* and the dispatcher counter */
&disp_counter);
q_is_qi = (Q == ics_q); /* TRUE if Q = QI, i.e., a user process was interrupted */
disp_active = (CPX1 & cpx1_DISPFLAG) != 0; /* TRUE if the dispatcher is currently active */
new_sm = 0; /* these will be set by every path through IXIT */
new_q = 0; /* but the compiler doesn't realize this and so warns */
if (!disp_active) { /* if not called by the dispatcher to start a process */
if ((STA & STATUS_CS_MASK) > 1) { /* then if an external interrupt was serviced */
cpu_read_memory (stack, /* then get the device number (parameter) */
Q + 3 & LA_MASK,
&device);
iop_direct_io (device, ioRIN, 0); /* send a Reset Interrupt I/O order to the device */
if (CPX1 & cpx1_IOTIMER) /* if an I/O timeout occurred */
MICRO_ABORT (trap_SysHalt_IO_Timeout); /* then trap for a system halt */
if (iop_interrupt_request_set /* if a hardware interrupt request is pending */
&& STA & STATUS_I) /* and interrupts are enabled */
device = iop_poll (); /* then poll to see if it can be granted */
if (CPX1 & cpx1_EXTINTR) { /* if a device is ready to interrupt */
CPX1 &= ~cpx1_EXTINTR; /* then handle it without exiting and restacking */
dprintf (cpu_dev, DEB_INSTR, BOV_FORMAT " external interrupt\n",
PBANK, P - 1 & R_MASK, device);
cpu_setup_irq_handler (irq_IXIT, device); /* set up entry into the interrupt handler */
break; /* with the prior context still on the stack */
}
}
if (delta_q & STMK_D) { /* if the dispatcher was interrupted */
CPX1 |= cpx1_DISPFLAG; /* then set the dispatcher-is-active flag */
new_q = ics_q; /* set the returning Q value */
new_sm = ics_q + 2 & R_MASK; /* and the returning SM value */
if (delta_qi & STMK_D /* if the dispatcher is scheduled */
&& disp_counter == 0) { /* and enabled */
cpu_start_dispatcher (); /* then restart it now */
break; /* to redispatch */
}
}
}
if (disp_active /* if the dispatcher is launching a process */
|| q_is_qi && ((delta_q & STMK_D) == 0 /* or a process was interrupted */
|| disp_counter != 0)) { /* or the dispatcher is disabled */
cpu_read_memory (absolute, Q - 4 & LA_MASK, &stack_db); /* then read the DB and stack bank */
cpu_read_memory (absolute, Q - 5 & LA_MASK, &SBANK); /* for the return process */
cpu_read_memory (absolute, Q - 7 & LA_MASK, &operand); /* read the stack-DB-relative data limit */
DL = stack_db + operand & R_MASK; /* and restore it */
cpu_read_memory (absolute, Q - 8 & LA_MASK, &operand); /* read the stack-DB-relative stack limit */
Z = stack_db + operand & R_MASK; /* and restore it */
cpu_write_memory (absolute, Q - 13, D16_UMAX); /* set the trace flag to a non-zero value */
cpu_read_memory (absolute, Q - 6 & LA_MASK, &operand); /* read the stack-DB-relative stack pointer */
Q = stack_db + operand - 2 & R_MASK; /* and restore it */
cpu_read_memory (stack, Q, &delta_q); /* read the relative frame pointer */
CPX1 &= ~(cpx1_ICSFLAG | cpx1_DISPFLAG); /* clear the ICS and dispatcher-is-running flags */
new_sm = Q - 4 & R_MASK; /* set up the return TOS pointer */
new_q = Q - delta_q & R_MASK; /* and frame pointer */
}
if (!disp_active /* if not launching a new process */
&& !q_is_qi /* and returning */
&& ((delta_q & STMK_D) == 0 /* to an interrupted interrupt handler */
|| (delta_qi & STMK_D) == 0 /* or to the interrupted dispatcher */
|| disp_counter != 0)) { /* or to the dispatcher requesting a disabled redispatch */
new_sm = Q - 4 & R_MASK; /* then set up the return TOS pointer */
new_q = Q - (delta_q & ~STMK_D) & R_MASK; /* and frame pointer */
}
cpu_read_memory (stack, Q + 1 & LA_MASK, &DBANK); /* restore the data bank */
cpu_read_memory (stack, Q + 2 & LA_MASK, &DB); /* and data base values */
cpu_exit_procedure (new_q, new_sm, 0); /* set up the return code segment and stack */
break;
case 005: /* these decode as LOCK in hardware */
case 011:
case 015:
if (cpu_stop_flags & SS_UNDEF) { /* if the undefined instruction stop is active */
status = STOP_UNIMPL; /* then stop the simulator */
break;
}
/* fall into the LOCK executor */
case 001: /* LOCK (none; MODE) */
if (UNIT_CPU_MODEL == UNIT_SERIES_II) { /* if the CPU is a Series II */
status = STOP_UNIMPL; /* THIS INSTRUCTION IS NOT IMPLEMENTED YET */
}
else /* otherwise the instruction */
status = STOP_UNIMPL; /* is not implemented on this machine */
break;
case 004: /* these decode as PCN in hardware */
case 006:
case 010:
case 012:
case 014:
case 016:
if (cpu_stop_flags & SS_UNDEF) { /* if the undefined instruction stop is active */
status = STOP_UNIMPL; /* then stop the simulator */
break;
}
/* fall into the PCN executor */
case 002: /* PCN (none; STOV, MODE) */
cpu_push (); /* push the stack down */
if (UNIT_CPU_MODEL == UNIT_SERIES_II) /* if the CPU is a Series II */
RA = 1; /* then the CPU number is 1 */
else if (UNIT_CPU_MODEL == UNIT_SERIES_III) /* if the CPU is a Series III */
RA = 2; /* then the CPU number is 2 */
else /* if it's anything else */
status = SCPE_IERR; /* then there's a problem! */
break;
case 007: /* these decode as UNLK in hardware */
case 013:
case 017:
if (cpu_stop_flags & SS_UNDEF) { /* if the undefined instruction stop is active */
status = STOP_UNIMPL; /* then stop the simulator */
break;
} /* otherwise fall into the UNLK executor */
/* fall into the UNLK executor */
case 003: /* UNLK (none; MODE) */
if (UNIT_CPU_MODEL == UNIT_SERIES_II) { /* if the CPU is a Series II */
status = STOP_UNIMPL; /* THIS INSTRUCTION IS NOT IMPLEMENTED YET */
}
else /* otherwise the instruction */
status = STOP_UNIMPL; /* is not implemented on this machine */
break;
} /* all cases are handled */
break;
} /* all cases are handled */
return status; /* return the execution status */
}
/* Execute a firmware extension instruction (subopcode 02, field 01).
This routine is called to execute the DMUL, DDIV, or firmware extension
instruction currently in the CIR. Optional firmware extension instruction
sets occupy instruction codes 020400-020777. Two instructions in this range
are base set instructions: DMUL (020570) and DDIV (020571). The instruction
formats are:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 1 1 1 1 0 0 | x | DMUL/DDIV
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 0 0 1 | ext fp op | Extended FP
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 1 | options | decimal op | Decimal
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
In hardware, optional instructions depend on the presence of the microcode
that implements them. All extension instructions initially check the
"firmware option present" bit in CPX1 before branching to a calculated
address for the extension microcode. This bit is set by comparing jumpers
W1-W8 on the CIR PCA to CIR bits 8-11. If the "present" bit is clear, the
firmware takes an Unimplemented Instruction trap.
A machine with no options has all jumpers installed. Removing jumpers sets
the "firmware option present" bit for specific CIR ranges, as follows:
Jumper CIR 8-11 CIR Range Option
------ -------- ------------- -----------------------------------------
W1 0000 020400-020417 Extended Instruction Set (Floating Point)
W2 0001 020420-020437 32105A APL Instruction Set
W3 0010 020440-020457
W4 0011 020460-020477 32234A COBOL II Instruction Set
W5 0100 020500-020517
W6 0101 020520-020537
W7 0110 020540-020557
-- 0111 020560-020577 Base Set (DMUL/DDIV)
W8 1000 020600-020777 Extended Instruction Set (Decimal Arith)
The range occupied by the base set has no jumper and is hardwired as
"present".
In simulation, presence is determined by the settings of the CPU unit flags.
Currently, the only defined option flag is UNIT_EIS, although the EIS itself
has not been implemented.
Implementation notes:
1. In simulation, the DDIV instruction must check for 32-bit overflow before
dividing. Otherwise, an Integer Overflow Exception may occur on the
underlying machine instruction, aborting the simulator.
*/
static t_stat firmware_extension (void)
{
int32 dividend, divisor, quotient, remainder;
uint32 operation, suboperation;
t_int64 product;
t_uint64 check;
t_stat status = SCPE_OK;
operation = FIRMEXTOP (CIR); /* get the operation from the instruction */
switch (operation) { /* dispatch the operation */
case 007: /* base set */
suboperation = FMEXSUBOP (CIR); /* get the suboperation from the instruction */
switch (suboperation) { /* dispatch the suboperation */
case 010: /* DMUL (CCA, O; STUN, ARITH) */
product = /* form a 64-bit product from a 32 x 32 multiplication */
INT32 (TO_DWORD (RD, RC)) * INT32 (TO_DWORD (RB, RA));
check = (t_uint64) product & S32_OVFL_MASK; /* check the top 33 bits and set overflow */
SET_OVERFLOW (check != 0 && check != S32_OVFL_MASK); /* if they are not all zeros or all ones */
cpu_pop (); /* pop two words */
cpu_pop (); /* from the stack */
RB = UPPER_WORD (product); /* move the 32-bit product */
RA = LOWER_WORD (product); /* to the stack */
SET_CCA (RB, RA); /* set the condition code */
break;
case 011: /* DDIV (CCA, O; STUN, ARITH) */
dividend = INT32 (TO_DWORD (RD, RC)); /* get the 32-bit signed dividend */
divisor = INT32 (TO_DWORD (RB, RA)); /* and the 32-bit signed divisor from the stack */
if (divisor == 0) /* if dividing by zero */
MICRO_ABORT (trap_Integer_Zero_Divide); /* then trap or set the overflow flag */
if (dividend == (int32) D32_SMIN && divisor == -1) { /* if the division will overflow */
quotient = dividend; /* then set the quotient */
remainder = 0; /* and remainder explicitly */
SET_OVERFLOW (TRUE); /* and trap or set overflow */
}
else { /* otherwise */
quotient = dividend / divisor; /* form the 32-bit signed quotient */
remainder = dividend % divisor; /* and 32-bit signed remainder */
}
RD = UPPER_WORD (quotient); /* move the 32-bit quotient */
RC = LOWER_WORD (quotient); /* to the stack */
RB = UPPER_WORD (remainder); /* move the 32-bit remainder */
RA = LOWER_WORD (remainder); /* to the stack */
SET_CCA (RD, RC); /* set the condition code */
break;
default:
status = STOP_UNIMPL; /* the rest of the base set codes are unimplemented */
}
break;
default:
status = STOP_UNIMPL; /* the firmware extension instruction is unimplemented */
}
return status; /* return the execution status */
}
/* Execute an I/O or control instruction (subopcode 03, field 00).
This routine is called to execute the I/O or control instruction currently in
the CIR. The instruction formats are:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 1 | 0 0 0 0 | I/O opcode | K field | I/O
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 1 | 0 0 0 0 | cntl opcode | 0 0 | cn op | Control
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Implementation notes:
1. The PAUS instruction suspends instruction execution until an interrupt
occurs. It is intended to idle the CPU while suspending instruction
fetches from memory to allow full-bandwidth access to the selector and
multiplexer channels.
If the simulation is stopped while a PAUS instruction is executing, it
may be resumed after the PAUS by adding the -B switch to the STEP,
CONTINUE, GO, or RUN command. This corresponds in hardware to pressing
the RUN/HALT switch twice. Without the switch, execution will resume at
the PAUS instruction.
The CNTR register is set to the value of the SR register when the
micromachine pauses. This allows the SR value to be accessed by the
diagnostics. The top-of-stack registers are flushed to main memory when
this occurs, which clears SR. Resuming into a PAUS and then stopping the
simulation again will show CNTR = 0.
2. The SED instruction decodes bits 12-15, including the reserved bits
12-14. The canonical form has the reserved bits set to zero, and in
hardware SED works correctly only if opcodes 030040 and 030041 are used.
Opcodes 030042-030057 also decode as SED, but the status register is set
improperly (the I bit is cleared, bits 12-15 are rotated right twice and
then ORed into the status register). If a non-canonical form is used in
simulation, and the UNDEF stop is active, a simulation stop will occur.
If the stop is bypassed or not set, then the instruction will execute as
though the reserved bits were zero.
*/
static t_stat io_control (void)
{
static const uint8 preadjustment [16] = { /* stack preadjustment, indexed by operation */
1, 0, 0, 2, 1, 0, 1, 1, /* LST PAUS SED **** **** **** XEQ SIO */
0, 1, 0, 1, 1, 2, 0, 0 /* RIO WIO TIO CIO CMD SST SIN HALT */
};
uint32 operation;
HP_WORD operand, address, offset;
HP_WORD ics_q, delta_qi, disp_counter;
t_stat status = SCPE_OK;
operation = IOCSUBOP (CIR); /* get the suboperation from the instruction */
PREADJUST_SR (preadjustment [operation]); /* preadjust the TOS registers to the required number */
switch (operation) { /* dispatch the I/O or control operation */
case 000: /* LST (CCA; STUN, STOV, MODE) */
offset = IO_K (CIR); /* get the system table pointer offset */
if (offset == 0) { /* if the specified offset is zero */
cpu_read_memory (absolute, /* then offset using the TOS */
RA + SGT_POINTER & LA_MASK,
&operand);
cpu_pop (); /* delete the TOS */
}
else /* otherwise */
cpu_read_memory (absolute, /* use the specified offset */
offset + SGT_POINTER & LA_MASK,
&operand);
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
cpu_push (); /* push the stack down */
cpu_read_memory (absolute, /* and read the table word onto the TOS */
X + operand + SGT_POINTER & LA_MASK,
&RA);
SET_CCA (RA, 0); /* set the condition code */
break;
case 001: /* PAUS (none; MODE) */
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
CNTR = SR; /* copy the stack register to the counter */
cpu_flush (); /* and flush the TOS registers to memory */
if (cpu_stop_flags & SS_PAUSE) /* if the pause stop is active */
status = STOP_PAUS; /* then stop the simulation */
else if (!(cpu_stop_flags & SS_BYPASSED)) /* otherwise if stops are not bypassed */
cpu_micro_state = paused; /* then pause the micromachine */
break; /* otherwise bypass the pause */
case 002: /* SED (none; MODE) */
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
if (IO_K (CIR) > 1 /* if the K field is not 0 or 1 */
&& cpu_stop_flags & SS_UNDEF) /* and the undefined instruction stop is active */
status = STOP_UNIMPL; /* then stop the simulator */
else if (CIR & 1) /* otherwise if bit 15 of the instruction is 1 */
STA |= STATUS_I; /* then enable interrupts */
else /* otherwise */
STA &= ~STATUS_I; /* disable them */
break;
case 003: /* XCHD, PSDB, DISP, and PSEB */
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
switch (CNTLOP (CIR)) { /* dispatch the control operation */
case 000: /* XCHD (none; STUN, MODE) */
operand = RA; /* exchange the */
RA = DB; /* RA and */
DB = operand; /* DB values */
operand = RB; /* exchange the */
RB = DBANK; /* RB and */
DBANK = operand & BA_MASK; /* DBANK values */
cpu_base_changed = TRUE; /* this instruction changed the base registers */
break;
case 005: /* these decode as PSDB in hardware */
case 011:
case 015:
if (cpu_stop_flags & SS_UNDEF) { /* if the undefined instruction stop is active */
status = STOP_UNIMPL; /* then stop the simulator */
break;
}
/* fall into the PSDB executor */
case 001: /* PSDB (none; MODE) */
cpu_read_memory (absolute, ICS_Q, /* read the ICS stack marker pointer value */
&ics_q);
cpu_read_memory (absolute, ics_q - 18 & LA_MASK, /* read the dispatcher counter */
&disp_counter);
cpu_write_memory (absolute, ics_q - 18 & LA_MASK, /* and increment it */
disp_counter + 1 & DV_MASK);
break;
case 004: /* these decode as DISP in hardware */
case 006:
case 010:
case 012:
case 014:
case 016:
if (cpu_stop_flags & SS_UNDEF) { /* if the undefined instruction stop is active */
status = STOP_UNIMPL; /* then stop the simulator */
break;
}
/* fall into the DISP executor */
case 002: /* DISP (CCx; MODE, CSTV, TRACE, ABS CST, BNDV) */
cpu_read_memory (absolute, ICS_Q, /* read the stack marker initial value */
&ics_q);
cpu_read_memory (absolute, ics_q - 18 & LA_MASK, /* read the dispatcher counter */
&disp_counter);
cpu_write_memory (absolute, ics_q, /* set the dispatcher-is-scheduled flag */
STMK_D);
if (CPX1 & (cpx1_ICSFLAG | cpx1_DISPFLAG) /* if the dispatcher is currently running */
|| disp_counter > 0) /* or the dispatcher is inhibited */
SET_CCG; /* then set condition code "greater than" */
else { /* otherwise */
SET_CCE; /* set condition code "equal" */
cpu_setup_ics_irq (irq_Dispatch, 0); /* and set up the ICS */
STA = STATUS_M; /* enter privileged mode with interrupts disabled */
cpu_start_dispatcher (); /* and start the dispatcher */
}
break;
case 007: /* these decode as PSEB in hardware */
case 013:
case 017:
if (cpu_stop_flags & SS_UNDEF) { /* if the undefined instruction stop is active */
status = STOP_UNIMPL; /* then stop the simulator */
break;
}
/* fall into the PSEB executor */
case 003: /* PSEB (CCx; MODE, CSTV, TRACE, ABS CST, BNDV) */
cpu_read_memory (absolute, ICS_Q, /* read the stack marker initial value */
&ics_q);
cpu_read_memory (absolute, ics_q - 18 & LA_MASK, /* read the dispatcher counter */
&disp_counter);
cpu_write_memory (absolute, ics_q - 18 & LA_MASK, /* and decrement it */
disp_counter - 1 & DV_MASK);
if (disp_counter == 0) /* if the dispatcher is already enabled */
MICRO_ABORT (trap_SysHalt_PSEB_Enabled); /* then trap for a system halt */
else if (disp_counter > 1) /* otherwise if the dispatcher is still inhibited */
SET_CCG; /* then set condition code "greater than" */
else if (CPX1 & cpx1_DISPFLAG) { /* otherwise if the dispatcher is currently running */
cpu_write_memory (absolute, ics_q, 0); /* then clear any start dispatcher requests */
SET_CCG; /* and set condition code "greater than" */
}
else { /* otherwise the dispatcher is ready to run */
cpu_read_memory (absolute, ics_q, /* read the dispatcher's stack marker */
&delta_qi);
if ((delta_qi & STMK_D) == 0 /* if the dispatcher is not scheduled */
|| (CPX1 & cpx1_ICSFLAG)) /* or if we're currently executing on the ICS */
SET_CCG; /* then set condition code "greater than" */
else { /* otherwise */
SET_CCE; /* set condition code "equal" */
cpu_setup_ics_irq (irq_Dispatch, 0); /* and set up the ICS */
STA = STATUS_M; /* enter privileged mode with interrupts disabled */
cpu_start_dispatcher (); /* and start the dispatcher */
}
}
break;
} /* all cases are handled */
break;
case 004: /* SMSK and SCLK */
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
if (CNTLOP (CIR) > 1 /* if the reserved field is not 0 or 1 */
&& cpu_stop_flags & SS_UNDEF) /* and the undefined instruction stop is active */
status = STOP_UNIMPL; /* then stop the simulator */
else if (CNTLOP (CIR) == 0) { /* SMSK (CCx; STUN, MODE) */
iop_direct_io (0, ioSMSK, RA); /* send the "set mask" I/O order to all interfaces */
if (CPX1 & cpx1_IOTIMER) { /* if an I/O timeout occurred */
CPX1 &= ~cpx1_IOTIMER; /* then clear the timer */
SET_CCL; /* and set condition code "greater than" */
}
else { /* otherwise the mask was set */
cpu_write_memory (absolute, INTERRUPT_MASK, /* so write the interrupt mask to memory */
RA);
cpu_pop (); /* pop the TOS */
SET_CCE; /* and set condition code "equal" */
}
}
else { /* SCLK (none; STUN, MODE) */
cpu_update_pclk (); /* update the process clock counter */
PCLK = RA; /* and then set it */
cpu_pop (); /* delete the TOS */
}
break;
case 005: /* RMSK and RCLK */
cpu_push (); /* push the stack down */
if (CNTLOP (CIR) > 1 /* if the reserved field is not 0 or 1 */
&& cpu_stop_flags & SS_UNDEF) /* and the undefined instruction stop is active */
status = STOP_UNIMPL; /* then stop the simulator */
else if (CNTLOP (CIR) == 0) /* RMSK (STOV) */
cpu_read_memory (absolute, INTERRUPT_MASK, /* read the interrupt mask from memory */
&RA);
else { /* RCLK (none; STOV) */
cpu_update_pclk (); /* update the process clock counter */
RA = PCLK; /* and then read it */
}
break;
case 006: /* XEQ (none; BNDV) */
address = SM + SR - IO_K (CIR) & LA_MASK; /* get the address of the target instruction */
if (address >= DB || PRIV) { /* if the address is not below DB or the mode is privileged */
cpu_read_memory (stack_checked, address, &NIR); /* the read the word at S - K into the NIR */
P = P - 1 & R_MASK; /* decrement P so the instruction after XEQ is next */
sim_interval = sim_interval + 1; /* but don't count the XEQ against a STEP count */
}
else /* otherwise the address is below DB and not privileged */
MICRO_ABORT (trap_Bounds_Violation); /* so trap with a bounds violation */
break;
case 007: /* SIO (CCx; STUN, STOV, MODE) */
operand = srw_io (ioSIO, SIO_OK); /* send the SIO order to the device */
if (operand) /* if the start I/O operation succeeded */
cpu_pop (); /* then delete the I/O program address */
break;
case 010: /* RIO (CCX; STOV, MODE) */
operand = srw_io (ioRIO, DIO_OK); /* send the RIO order to the device */
if (operand) { /* if the read I/O operation succeeded */
cpu_push (); /* then push the stack down */
RA = LOWER_WORD (operand); /* and save the RIO response on the TOS */
}
break;
case 011: /* WIO (CCX; STUN, STOV, MODE) */
operand = srw_io (ioWIO, DIO_OK); /* send the WIO order to the device */
if (operand) /* if the write I/O operation succeeded */
cpu_pop (); /* then delete the write value */
break;
case 012: /* TIO (CCx; STOV, MODE) */
operand = tcs_io (ioTIO); /* send the TIO order to the device */
if (operand) { /* if the test I/O operation succeeded */
cpu_push (); /* then push the stack down */
RA = LOWER_WORD (operand); /* and save the I/O response on the TOS */
}
break;
case 013: /* CIO (CCx; STUN, MODE) */
operand = tcs_io (ioCIO); /* send the CIO order to the device */
if (operand) /* if the control operation succeeded */
cpu_pop (); /* then delete the control value */
break;
case 014: /* CMD (none; STUN, MODE) */
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
status = STOP_UNIMPL; /* THIS INSTRUCTION IS NOT IMPLEMENTED YET! */
break;
case 015: /* SST (none; STUN, MODE) */
offset = IO_K (CIR); /* get the system table pointer offset */
if (offset == 0) { /* if the specified offset is zero */
cpu_read_memory (absolute, /* then offset using the TOS */
RA + SGT_POINTER & LA_MASK,
&operand);
cpu_pop (); /* delete the TOS */
}
else /* otherwise */
cpu_read_memory (absolute, /* use the specified offset */
offset + SGT_POINTER & LA_MASK,
&operand);
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
cpu_write_memory (absolute, /* write the TOS value into the table */
X + operand + SGT_POINTER & LA_MASK,
RA);
cpu_pop (); /* delete the TOS */
break;
case 016: /* SIN (CCx; MODE) */
tcs_io (ioSIN); /* send the SIN order to the device */
break;
case 017: /* HALT (none; MODE) */
if (NPRV) /* if the mode is not privileged */
MICRO_ABORT (trap_Privilege_Violation); /* then abort with a privilege violation */
CNTR = SR; /* copy the stack register to the counter */
cpu_flush (); /* and flush the TOS registers to memory */
CPX2 &= ~cpx2_RUN; /* clear the run flip-flop */
status = STOP_HALT; /* and stop the simulator */
break;
} /* all cases are handled */
return status; /* return the execution status */
}