blob: f0dad15b9f7cd68ac11077e7d3aee5911715b1e2 [file] [log] [blame] [raw]
/* hp3000_sys.c: HP 3000 system common interface
Copyright (c) 2016-2018, J. David Bryan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
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.
05-Sep-17 JDB Removed the -B (binary display) option; use -2 instead
Rewrote "fprint_sym" for better coverage
11-May-17 JDB Corrected comment in "fprint_value"
28-Apr-17 JDB Added void cast to "fprint_instruction" call for left stackop
03-Mar-17 JDB Added an implementation note to the "parse_sym" routine
29-Dec-16 JDB Changed the switch for STA format from -S to -T;
changed the status mnemonic flag from REG_S to REG_T
28-Nov-16 JDB hp_device_conflict accumulates names of active traces only
Improved parse_addr error detection
26-Oct-16 JDB Added "fprint_edit" to print EDIT subprogram mnemonics
27-Sep-16 JDB Added COBOL firmware mnemonics
Modified "fprint_instruction" to handle two-word instructions
15-Sep-16 JDB Modified "one_time_init" to set aux_cmds "message" field
03-Sep-16 JDB Added the STOP_POWER and STOP_ARSINH status messages
01-Sep-16 JDB Moved the "hp_cold_cmd" routine to the CPU (as "cpu_cold_cmd")
Added the POWER command
03-Aug-16 JDB Improved "fmt_char" and "fmt_bitset" to allow multiple calls
15-Jul-16 JDB Fixed the word count display for SIO read/write orders
14-Jul-16 JDB Improved the cold dump invocation
21-Jun-16 JDB Changed fprint_instruction mask type from t_value to uint32
08-Jun-16 JDB Corrected %d format to %u for unsigned values
16-May-16 JDB Prefix in fprint_instruction is now a pointer-to-constant
13-May-16 JDB Modified for revised SCP API function parameter types
20-Apr-16 JDB Added implementation notes to "fmt_bitset"
14-Apr-16 JDB Fixed INTMASK setting and display
24-Mar-16 JDB Added the LP device
21-Mar-16 JDB Changed uint16 types to HP_WORD
23-Nov-15 JDB First release version
11-Dec-12 JDB Created
References:
- Machine Instruction Set Reference Manual
(30000-90022, February 1980)
- Systems Programming Language Reference Manual
(30000-90024, December 1976)
This module provides the interface between the Simulation Control Program
(SCP) and the HP 3000 simulator. It includes the required global VM
interface data definitions (e.g., the simulator name, device array, etc.),
symbolic display and parsing routines, utility routines for tracing and
execution support, and SCP command replacement routines.
*/
#include <ctype.h>
#include <stdarg.h>
#include "hp3000_defs.h"
#include "hp3000_cpu.h"
#include "hp3000_cpu_ims.h"
#include "hp3000_mem.h"
#include "hp3000_io.h"
/* External I/O data structures */
extern DEVICE iop_dev; /* I/O Processor */
extern DEVICE mpx_dev; /* Multiplexer Channel */
extern DEVICE sel_dev; /* Selector Channel */
extern DEVICE scmb_dev [2]; /* Selector Channel Maintenance Boards */
extern DEVICE atcd_dev; /* Asynchronous Terminal Controller TDI */
extern DEVICE atcc_dev; /* Asynchronous Terminal Controller TCI */
extern DEVICE clk_dev; /* System Clock */
extern DEVICE lp_dev; /* Line Printer */
extern DEVICE ds_dev; /* 79xx MAC Disc */
extern DEVICE ms_dev; /* 7970 Magnetic Tape */
/* Program constants */
/* Symbolic production/consumption values */
#define SCPE_OK_2_WORDS ((t_stat) -1) /* two words produced or consumed */
#define SCPE_OK_3_WORDS ((t_stat) -2) /* three words produced or consumed */
/* Symbolic mode and format override switches */
#define A_SWITCH SWMASK ('A')
#define B_SWITCH SWMASK ('B')
#define C_SWITCH SWMASK ('C')
#define D_SWITCH SWMASK ('D')
#define E_SWITCH SWMASK ('E')
#define H_SWITCH SWMASK ('H')
#define I_SWITCH SWMASK ('I')
#define M_SWITCH SWMASK ('M')
#define O_SWITCH SWMASK ('O')
#define T_SWITCH SWMASK ('T')
#define MODE_SWITCHES (C_SWITCH | E_SWITCH | I_SWITCH | M_SWITCH | T_SWITCH)
#define FORMAT_SWITCHES (A_SWITCH | B_SWITCH | D_SWITCH | H_SWITCH | O_SWITCH)
#define SYMBOLIC_SWITCHES (MODE_SWITCHES | A_SWITCH) /* -A is both a mode and a format switch */
#define ALL_SWITCHES (MODE_SWITCHES | FORMAT_SWITCHES)
/* Address parsing configuration flags */
typedef enum {
apcNone = 000, /* no configuration */
apcBank_Offset = 001, /* <bank>.<offset> address form allowed */
apcBank_Override = 002, /* bank override switches allowed */
apcDefault_DBANK = 004, /* default bank is DBANK */
apcDefault_PBANK = 010 /* default bank is PBANK */
} APC_FLAGS;
/* Operand types.
Operand types indicate how to print or parse instruction mnemonics. There is
a separate operand type for each unique operand interpretation. For
printing, the operand type and associated operand mask indicate which bits
form the operand value and what interpretation is to be imposed on that
value. For parsing, the operand type additionally implies the acceptable
syntax for symbolic entry.
Operand masks are used to isolate the operand value from the instruction
word. As provided, a logical AND removes the operand value; an AND with the
complement leaves only the operand value. The one-for-one correspondence
between operand types and masks must be preserved when adding new operand
types.
Implementation notes:
1. Immediate values, displacements, and decrements are assumed to be
right-justified in the instruction word, i.e., extend from bits n-15,
unless otherwise noted.
2. Operands for one-word instructions are in the lower (first) word;
operands for two-word instructions are in the upper (second) word.
3. Operand masks for signed values must include both signs and magnitudes.
4. Operand masks are defined as the complements of the operand bits to make
it easier to see which bits will be cleared when the value is ANDed. The
complements are calculated at compile-time and so impose no run-time
penalty.
5. The base set "move" instructions contain S-decrement fields that indicate
the counts of parameters to remove from the stack when the instructions
complete. Certain "decimal arithmetic" instructions of the optional
Extended Instruction Set and certain "numeric conversion and load"
instructions of the optional COBOL II Extended Instruction Set contain
one or two S-decrement bits, but these encode the stack adjustment rather
than expressing the adjustment directly.
The SPL "ASMB" statement accepts the EIS instructions with the stack
adjustment given as the encoded value, e.g., CVAD 0 and CVAD 1 are
accepted, with the former deleting two words and the latter deleting four
words from the stack. Therefore, all S-decrement operands are printed
and parsed as their direct field values, rather than decoding and
encoding the actual decrements.
*/
typedef enum {
opNone, /* no operand */
opB15, /* PB/DB base bit 15 */
opU1, /* unsigned value range 0-1 */
opU1515, /* unsigned value pair range 0-15 */
opU63, /* unsigned value range 0-63 */
opU63X, /* unsigned value range 0-63, index bit 4 */
opU255, /* unsigned value range 0-255 */
opC15, /* CIR display value range 0-15 */
opR255L, /* register selection value range 0-255 left-to-right */
opR255R, /* register selection value range 0-255 right-to-left */
opPS31I, /* P +/- displacement range 0-31, indirect bit 4 */
opPS255, /* P +/- displacement range 0-255 */
opPU255, /* P unsigned displacement range 0-255 */
opPS255IX, /* P +/- displacement range 0-255, indirect bit 5, index bit 4 */
opS11, /* S decrement selection bit 11 */
opS15, /* S decrement selection bit 15 */
opSCS3, /* sign control bits 9-10, S decrement bit 11 */
opSU2, /* S decrement range 0-2 bits 10-11 */
opSU3, /* S decrement range 0-3 */
opSU3B, /* S decrement range 0-3, base bit 11 */
opSU3NAS, /* S decrement range 0-3, N/A/S bits 11-13 */
opSU7, /* S decrement range 0-7 */
opSU15, /* S decrement range 0-15 */
opD255IX, /* DB+/Q+/Q-/S- displacements, indirect bit 5, index bit 4 */
opPD255IX, /* P+/P-/DB+/Q+/Q-/S- displacements, indirect bit 5, index bit 4 */
opX, /* index bit 4 */
opB31, /* second word: PB/DB base bit 15 */
opS31, /* second word: S decrement selection bit 15 */
opCC7, /* second word: condition code flags bits 13-15 */
opSCS4 /* second word: sign control bits 12-14, S decrement bit 15 */
} OP_TYPE;
static const t_value op_mask [] = { /* operand masks, indexed by OP_TYPE */
~0000000u, /* opNone */
~0000001u, /* opB15 */
~0000001u, /* opU1 */
~0000377u, /* opU1515 */
~0000077u, /* opU63 */
~0004077u, /* opU63X */
~0000377u, /* opU255 */
~0000017u, /* opC15 */
~0000377u, /* opR255L */
~0000377u, /* opR255R */
~0004077u, /* opPS31I */
~0000777u, /* opPS255 */
~0000377u, /* opPU255 */
~0006777u, /* opPS255IX */
~0000020u, /* opS11 */
~0000001u, /* opS15 */
~0000160u, /* opSCS3 */
~0000060u, /* opSU2 */
~0000003u, /* opSU3 */
~0000023u, /* opSU3B */
~0000037u, /* opSU3NAS */
~0000007u, /* opSU7 */
~0000017u, /* opSU15 */
~0006777u, /* opD255IX */
~0007777u, /* opPD255IX */
~0004000u, /* opX */
~TO_DWORD (0000001u, 0000000u), /* opB31 */
~TO_DWORD (0000001u, 0000000u), /* opS31 */
~TO_DWORD (0000007u, 0000000u), /* opCC7 */
~TO_DWORD (0000017u, 0000000u) /* opSCS4 */
};
/* Instruction classifications.
Machine instructions on the HP 3000 are identified by a varying number of
bits. In general, the four most-significant bits identify the general class
of instruction, and additional bits form a sub-opcode within a class to
identify an instruction uniquely. However, some instructions are irregular
or have reserved bits. These bits are defined to be zero, but correct
hardware decoding may or may not depend on the value being zero.
Each instruction is classified by a mnemonic, a base operation code (without
the operand), an operand type, and a mask for the reserved bits, if any.
Classifications are grouped by class of instruction into arrays that are
indexed by sub-opcodes, if applicable.
An operation table consists of two parts, either of which is optional. If a
given class has a sub-opcode that fully or almost fully decodes the class,
the first (primary) part of the table contains the appropriate number of
classification elements. This allows rapid access to a specific instruction
based on its bit pattern. In this primary part, the reserved bits masks are
not defined or used.
If some instructions in a class have reserved bits, if the sub-opcode
decoding is not regular, or if the instruction requires two words to decode,
then the second (secondary) part of the table contains classification
elements that specify reserved bits masks. This part is searched linearly.
As an example, the stack instructions have bits 0-3 = 0. The remaining
twelve bits are broken into two six-bit fields. Each field encodes one of 64
stack operations (actually, 63 operations, as one is unassigned). As the
stack operations are fully decoded, the table consists only of 64 primary
elements, corresponding one-for-one to the 64 operations.
As as contrasting example, the shift and branch operations have bits 0-3 = 1
and are fully decoded by bits 5-9, except for two instructions that have
reserved bit fields (SCAN and TNSL), and two instructions that require one
more bit for full decoding (QASL and QASR). The table consists of a primary
part of 32 elements and a secondary part of four elements. The three primary
elements corresponding to the four partially-decoded instructions are
indicated by zero-length mnemonics. The four secondary elements contain an
entry for each instruction that requires additional masking before unique
identification is possible.
Some instructions contain reserved bits that may or may not affect hardware
instruction decoding. For example, the MOVE instruction defines bits 12-13
as 00, but the bits are not decoded, so MOVE will result regardless of the
values. IXIT also defines bits 12-13 as 00, but in this case they must be 00
for the instruction to execute; any other value executes a PCN instruction.
For those instructions dependent on their reserved bits for interpretation,
the operations table has two entries for each opcode. The first entry
specifies a reserved bits mask of all-ones; this entry matches the canonical
opcode. The second entry specifies a mask that matches the opcode to the
range of opcodes that decode to the instruction; this entry presents the
opcode mnemonic in lowercase as an indicator that it is not the canonical
representation.
Two-word instructions are, by definition, never decoded by a primary opcode,
so they will always be found in the secondary entries of a classification
table with 32-bit opcode and reserved bits mask values. The opcode value
contains the first word of the instruction in the lower half and the second
word in the upper half. The lower half of the reserved bits mask value will
have all bits set, and the upper half will have all bits set except for those
corresponding to reserved bits in the second instruction word (if any). A
two-word entry, therefore, may be identified by a mask value with a non-zero
upper half.
A secondary entry with a zero-length mnemonic may be used to match the first
word of a two-word instruction if none of the second words match earlier
entries. This ensures that unimplemented two-word instructions are printed
as a two-word octal value rather than as a one-word octal value followed by
the mnemonic for a one-word instruction.
The end of an operations table is indicated by a NULL mnemonic pointer.
*/
typedef struct {
const char *mnemonic; /* symbolic name */
t_value opcode; /* base opcode */
OP_TYPE operand; /* operand type */
t_value rsvd_mask; /* reserved bits mask */
} INST_CLASS;
typedef INST_CLASS OP_TABLE []; /* an array of classifications */
/* Stack operations.
The stack instructions are fully decoded by bits 4-9 or 10-15. The table
consists of 64 primary entries.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 0 | 1st stack opcode | 2nd stack opcode | Stack
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Implementation notes:
1. Opcode 072 is undefined and will cause an Unimplemented Instruction trap
if it is executed. Normally, an unimplemented instruction is printed in
numeric form during mnemonic display. However, as two stack operations
are contained in a single word, the entry for opcode 072 has "072" as the
mnemonic to allow the other stack op to be decoded for printing.
*/
static const OP_TABLE stack_ops = {
{ "NOP", 0000000, opNone },
{ "DELB", 0000100, opNone },
{ "DDEL", 0000200, opNone },
{ "ZROX", 0000300, opNone },
{ "INCX", 0000400, opNone },
{ "DECX", 0000500, opNone },
{ "ZERO", 0000600, opNone },
{ "DZRO", 0000700, opNone },
{ "DCMP", 0001000, opNone },
{ "DADD", 0001100, opNone },
{ "DSUB", 0001200, opNone },
{ "MPYL", 0001300, opNone },
{ "DIVL", 0001400, opNone },
{ "DNEG", 0001500, opNone },
{ "DXCH", 0001600, opNone },
{ "CMP", 0001700, opNone },
{ "ADD", 0002000, opNone },
{ "SUB", 0002100, opNone },
{ "MPY", 0002200, opNone },
{ "DIV", 0002300, opNone },
{ "NEG", 0002400, opNone },
{ "TEST", 0002500, opNone },
{ "STBX", 0002600, opNone },
{ "DTST", 0002700, opNone },
{ "DFLT", 0003000, opNone },
{ "BTST", 0003100, opNone },
{ "XCH", 0003200, opNone },
{ "INCA", 0003300, opNone },
{ "DECA", 0003400, opNone },
{ "XAX", 0003500, opNone },
{ "ADAX", 0003600, opNone },
{ "ADXA", 0003700, opNone },
{ "DEL", 0004000, opNone },
{ "ZROB", 0004100, opNone },
{ "LDXB", 0004200, opNone },
{ "STAX", 0004300, opNone },
{ "LDXA", 0004400, opNone },
{ "DUP", 0004500, opNone },
{ "DDUP", 0004600, opNone },
{ "FLT", 0004700, opNone },
{ "FCMP", 0005000, opNone },
{ "FADD", 0005100, opNone },
{ "FSUB", 0005200, opNone },
{ "FMPY", 0005300, opNone },
{ "FDIV", 0005400, opNone },
{ "FNEG", 0005500, opNone },
{ "CAB", 0005600, opNone },
{ "LCMP", 0005700, opNone },
{ "LADD", 0006000, opNone },
{ "LSUB", 0006100, opNone },
{ "LMPY", 0006200, opNone },
{ "LDIV", 0006300, opNone },
{ "NOT", 0006400, opNone },
{ "OR", 0006500, opNone },
{ "XOR", 0006600, opNone },
{ "AND", 0006700, opNone },
{ "FIXR", 0007000, opNone },
{ "FIXT", 0007100, opNone },
{ "072", 0007200, opNone }, /* unassigned opcode */
{ "INCB", 0007300, opNone },
{ "DECB", 0007400, opNone },
{ "XBX", 0007500, opNone },
{ "ADBX", 0007600, opNone },
{ "ADXB", 0007700, opNone },
{ NULL }
};
/* Shift, branch, and bit test operations.
The shift, branch, and bit test instructions are fully decoded by bits 5-9,
except for SCAN and TNSL, whose reserved bits are don't cares, and QASL and
QASR, which depend on bit 4. The table consists of 32 primary entries and
four secondary entries.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 1 | X | shift opcode | shift count | Shift
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 1 | I | branch opcode |+/-| P displacement | Branch
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 1 | X | bit test opcode | bit position | Bit Test
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
static const OP_TABLE sbb_ops = {
{ "ASL", 0010000, opU63X },
{ "ASR", 0010100, opU63X },
{ "LSL", 0010200, opU63X },
{ "LSR", 0010300, opU63X },
{ "CSL", 0010400, opU63X },
{ "CSR", 0010500, opU63X },
{ "", 0010600, opNone }, /* SCAN */
{ "IABZ", 0010700, opPS31I },
{ "TASL", 0011000, opU63X },
{ "TASR", 0011100, opU63X },
{ "IXBZ", 0011200, opPS31I },
{ "DXBZ", 0011300, opPS31I },
{ "BCY", 0011400, opPS31I },
{ "BNCY", 0011500, opPS31I },
{ "", 0011600, opNone }, /* TNSL */
{ "", 0011700, opNone }, /* QASL, QASR */
{ "DASL", 0012000, opU63X },
{ "DASR", 0012100, opU63X },
{ "DLSL", 0012200, opU63X },
{ "DLSR", 0012300, opU63X },
{ "DCSL", 0012400, opU63X },
{ "DCSR", 0012500, opU63X },
{ "CPRB", 0012600, opPS31I },
{ "DABZ", 0012700, opPS31I },
{ "BOV", 0013000, opPS31I },
{ "BNOV", 0013100, opPS31I },
{ "TBC", 0013200, opU63X },
{ "TRBC", 0013300, opU63X },
{ "TSBC", 0013400, opU63X },
{ "TCBC", 0013500, opU63X },
{ "BRO", 0013600, opPS31I },
{ "BRE", 0013700, opPS31I },
{ "SCAN", 0010600, opX, 0177700 },
{ "TNSL", 0011600, opX, 0177700 },
{ "QASL", 0011700, opU63, 0177777 },
{ "QASR", 0015700, opU63, 0177777 },
{ NULL }
};
/* Move, special, firmware, immediate, bit field, and register operations.
The move and special instructions are partially decoded by bits 8-10. Only
MABS, MTDS, MDS, MFDS, and MVBW are fully decoded; the other 19 instructions
are not. Therefore, it's easier to treat all of the instructions as
potentially containing reserved bits and use secondary table entries.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 0 | move op | opts/S decrement | Move
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 0 | special op | 0 0 | sp op | Special
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
The firmware extension instructions, including DMUL and DDIV in the base set,
have generally unique encodings. They are rare, so it's easier to use
secondary entries for all of them.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | firmware option op | Firmware
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
The immediate, bit field, and register instructions are fully decoded by bits
4-7.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | immediate op | immediate operand | Immediate
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | field opcode | J field | K field | Field
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | register op | SK| DB| DL| Z |STA| X | Q | S | Register
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
The table consists of 16 primary entries for the immediate, bit field, and
register instructions, followed by the secondary entries for the remaining
instructions.
Implementation notes:
1. The IXIT, PCN, LOCK, and UNLK instructions specify bits 12-15 as
0000-0011. However, values other than zero in bits 12-13 will decode to
one of these instructions. Specifically, the value of bits 12-15 for
IXIT = 0000, PCN = nnn0, LOCK = nn01, and UNLK = nn11, where n is any
collective value other than 0.
2. The secondary entry with the empty mnemonic ensures that an unimplemented
two-word instruction is printed as "020477,000001" (e.g.) rather than
"020477" followed by "NOP,DELB".
3. By convention, SIMH simulators always decode all supported instructions,
regardless of whether or not they are enabled by current CPU firmware
configurations. So the EIS, APL, and COBOL-II instructions are present
in the table and are not conditional on the CPU options set.
*/
static const OP_TABLE msfifr_ops = {
{ "", 0020000, opNone }, /* move and special ops */
{ "", 0020400, opNone }, /* DMUL, DDIV, and firmware extension opcodes */
{ "LDI", 0021000, opU255 },
{ "LDXI", 0021400, opU255 },
{ "CMPI", 0022000, opU255 },
{ "ADDI", 0022400, opU255 },
{ "SUBI", 0023000, opU255 },
{ "MPYI", 0023400, opU255 },
{ "DIVI", 0024000, opU255 },
{ "PSHR", 0024400, opR255R },
{ "LDNI", 0025000, opU255 },
{ "LDXN", 0025400, opU255 },
{ "CMPN", 0026000, opU255 },
{ "EXF", 0026400, opU1515 },
{ "DPF", 0027000, opU1515 },
{ "SETR", 0027400, opR255L },
{ "MOVE", 0020000, opSU3B, 0177763 },
{ "MVB", 0020040, opSU3B, 0177763 },
{ "MVBL", 0020100, opSU3, 0177773 },
{ "MABS", 0020110, opSU7, 0177777 },
{ "SCW", 0020120, opSU3, 0177773 },
{ "MTDS", 0020130, opSU7, 0177777 },
{ "MVLB", 0020140, opSU3, 0177773 },
{ "MDS", 0020150, opSU7, 0177777 },
{ "SCU", 0020160, opSU3, 0177773 },
{ "MFDS", 0020170, opSU7, 0177777 },
{ "MVBW", 0020200, opSU3NAS, 0177777 },
{ "CMPB", 0020240, opSU3B, 0177763 },
{ "RSW", 0020300, opNone, 0177761 },
{ "LLSH", 0020301, opNone, 0177761 },
{ "PLDA", 0020320, opNone, 0177761 },
{ "PSTA", 0020321, opNone, 0177761 },
{ "LSEA", 0020340, opNone, 0177763 },
{ "SSEA", 0020341, opNone, 0177763 },
{ "LDEA", 0020342, opNone, 0177763 },
{ "SDEA", 0020343, opNone, 0177763 },
{ "IXIT", 0020360, opNone, 0177777 },
{ "LOCK", 0020361, opNone, 0177777 },
{ "lock", 0020361, opNone, 0177763 }, /* decodes bits 12-15 as nn01 */
{ "PCN", 0020362, opNone, 0177777 },
{ "pcn", 0020360, opNone, 0177761 }, /* decodes bits 12-15 as nnn0 */
{ "UNLK", 0020363, opNone, 0177777 },
{ "unlk", 0020363, opNone, 0177763 }, /* decodes bits 12-15 as nn11 */
{ "EADD", 0020410, opNone, 0177777 },
{ "ESUB", 0020411, opNone, 0177777 },
{ "EMPY", 0020412, opNone, 0177777 },
{ "EDIV", 0020413, opNone, 0177777 },
{ "ENEG", 0020414, opNone, 0177777 },
{ "ECMP", 0020415, opNone, 0177777 },
{ "ALGN", 0020460, opS15, 0177777 },
{ "ABSN", 0020462, opS15, 0177777 },
{ "EDIT", 0020470, opB15, 0177777 },
{ "CMPS", 0020472, opB15, 0177777 },
{ "XBR", 0020474, opNone, 0177777 },
{ "PARC", 0020475, opNone, 0177777 },
{ "ENDP", 0020476, opNone, 0177777 },
{ "CMPT", TO_DWORD (0000006, 0020477), opB31, D32_MASK },
{ "TCCS", TO_DWORD (0000010, 0020477), opCC7, D32_MASK },
{ "CVND", TO_DWORD (0000020, 0020477), opSCS4, D32_MASK },
{ "LDW", TO_DWORD (0000040, 0020477), opS31, D32_MASK },
{ "LDDW", TO_DWORD (0000042, 0020477), opS31, D32_MASK },
{ "TR", TO_DWORD (0000044, 0020477), opB31, D32_MASK },
{ "ABSD", TO_DWORD (0000046, 0020477), opS31, D32_MASK },
{ "NEGD", TO_DWORD (0000050, 0020477), opS31, D32_MASK },
{ "", 0020477, opNone, 0177777 }, /* catch unimplemented two-word instructions */
{ "DMUL", 0020570, opNone, 0177777 },
{ "DDIV", 0020571, opNone, 0177777 },
{ "DMPY", 0020601, opNone, 0177617 },
{ "CVAD", 0020602, opS11, 0177637 },
{ "CVDA", 0020603, opSCS3, 0177777 },
{ "CVBD", 0020604, opS11, 0177637 },
{ "CVDB", 0020605, opS11, 0177637 },
{ "SLD", 0020606, opSU2, 0177677 },
{ "NSLD", 0020607, opSU2, 0177677 },
{ "SRD", 0020610, opSU2, 0177677 },
{ "ADDD", 0020611, opSU2, 0177677 },
{ "CMPD", 0020612, opSU2, 0177677 },
{ "SUBD", 0020613, opSU2, 0177677 },
{ "MPYD", 0020614, opSU2, 0177677 },
{ NULL }
};
/* I/O and control operations.
The I/O instructions are fully decoded by bits 8-11. The control
instructions are partially decoded and require additional decoding by bits
14-15. The table consists of 16 primary entries, followed by the secondary
entries for the instructions that are partially decoded or have reserved
bits.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 1 | 0 0 0 0 | I/O opcode | K field | I/O
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 1 | 0 0 0 0 | cntl opcode | 0 0 | cn op | Control
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Implementation notes:
1. The SED instruction specifies bits 12-14 as 000, and the instruction only
works correctly if opcodes 030040 and 030041 are used. Values other than
000 will decode and execute as SED, but the status register is set
improperly (the I bit is cleared, bits 12-15 are rotated right twice and
then ORed into the status register).
2. The XCHD, DISP, PSDB, and PSEB instructions specify bits 12-15 as
0000-0011. However, values other than zero in bits 12-13 will decode to
one of these instructions. Specifically, the value of bits 12-15 for
XCHD = 0000, DISP = nnn0, PSDB = nn01, and PSEB = nn11, where n is any
collective value other than 0.
3. The SMSK, SCLK, RMSK, and RCLK instructions specify bits 12-15 as
0000-0011. However, values other than zero in bits 12-14 will decode to
one of these instructions. Specifically, the value of bits 12-15 for
SMSK and RMSK = 0000, and SCLK and SMSK = nnnn, where n is any collective
value other than 0.
4. The double entries for DISP, SCLK, and RCLK ensure that their full ranges
decode to the indicated instructions for printing but only the primary
opcode is encoded when entering instructions in symbolic form.
*/
static const OP_TABLE ioc_ops = {
{ "LST", 0030000, opSU15 },
{ "PAUS", 0030020, opC15 },
{ "", 0030040, opNone }, /* SED */
{ "", 0030060, opNone }, /* XCHD, PSDB, DISP, PSEB */
{ "", 0030100, opNone }, /* SMSK, SCLK */
{ "", 0030120, opNone }, /* RMSK, RCLK */
{ "XEQ", 0030140, opSU15 },
{ "SIO", 0030160, opSU15 },
{ "RIO", 0030200, opSU15 },
{ "WIO", 0030220, opSU15 },
{ "TIO", 0030240, opSU15 },
{ "CIO", 0030260, opSU15 },
{ "CMD", 0030300, opSU15 },
{ "SST", 0030320, opSU15 },
{ "SIN", 0030340, opSU15 },
{ "HALT", 0030360, opC15 },
{ "SED", 0030040, opU1, 0177777 },
{ "sed", 0030040, opU1, 0177760 }, /* decodes bits 12-14 as nnn */
{ "XCHD", 0030060, opNone, 0177777 },
{ "PSDB", 0030061, opNone, 0177777 },
{ "psdb", 0030061, opNone, 0177763 }, /* decodes bits 12-15 as nn01 */
{ "DISP", 0030062, opNone, 0177777 },
{ "disp", 0030060, opNone, 0177761 }, /* decodes bits 12-15 as nnn0 */
{ "PSEB", 0030063, opNone, 0177777 },
{ "pseb", 0030063, opNone, 0177763 }, /* decodes bits 12-15 as nn11 */
{ "SMSK", 0030100, opNone, 0177777 },
{ "SCLK", 0030101, opNone, 0177777 },
{ "sclk", 0030100, opNone, 0177760 }, /* decodes bits 12-15 as nnnn */
{ "RMSK", 0030120, opNone, 0177777 },
{ "RCLK", 0030121, opNone, 0177777 },
{ "rclk", 0030120, opNone, 0177760 }, /* decodes bits 12-15 as nnnn */
{ NULL }
};
/* Program, immediate, and memory operations.
The program, immediate, and memory instructions are fully decoded by bits
4-7. The table consists of 16 primary entries. Entry 0 is a placeholder for
the separate I/O and control instructions table.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 1 | program op | N field | Program
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 1 | immediate op | immediate operand | Immediate
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 1 | memory op | P displacement | Memory
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
static const OP_TABLE pmi_ops = {
{ "", 0000000, opNone }, /* placeholder for subop 00 */
{ "SCAL", 0030400, opPU255 },
{ "PCAL", 0031000, opPU255 },
{ "EXIT", 0031400, opPU255 },
{ "SXIT", 0032000, opPU255 },
{ "ADXI", 0032400, opU255 },
{ "SBXI", 0033000, opU255 },
{ "LLBL", 0033400, opPU255 },
{ "LDPP", 0034000, opPU255 },
{ "LDPN", 0034400, opPU255 },
{ "ADDS", 0035000, opU255 },
{ "SUBS", 0035400, opU255 },
{ "", 0036000, opNone }, /* unassigned opcode */
{ "ORI", 0036400, opU255 },
{ "XORI", 0037000, opU255 },
{ "ANDI", 0037400, opU255 },
{ NULL }
};
/* Memory, loop, and branch operations.
The memory and loop instructions are fully decoded by bits 0-3, except for
TBA, MTBA, TBX, MTBX, STOR, INCM, DECM, LDB, LDD, STB, and STD, which depend
on bits 4-6. The branch instructions also depend on 4-6, except for BCC,
which also depends on bits 7-9. The table consists of 16 primary entries,
followed by the secondary entries for the instructions that are partially
decoded or have reserved bits. Entries 0-3 are placeholders for the other
instruction tables.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| memory op | X | I | mode and displacement | Memory
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | 0 | P+ displacement 0-255 |
+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | P- displacement 0-255 |
+---+---+---+---+---+---+---+---+---+---+
| 1 | 0 | DB+ displacement 0-255 |
+---+---+---+---+---+---+---+---+---+---+
| 1 | 1 | 0 | Q+ displacement 0-127 |
+---+---+---+---+---+---+---+---+---+---+
| 1 | 1 | 1 | 0 | Q- displacement 0-63 |
+---+---+---+---+---+---+---+---+---+---+
| 1 | 1 | 1 | 1 | S- displacement 0-63 |
+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| memory op | X | I | s | mode and displacement | Memory
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | DB+ displacement 0-255 |
+---+---+---+---+---+---+---+---+---+
| 1 | 0 | Q+ displacement 0-127 |
+---+---+---+---+---+---+---+---+---+
| 1 | 1 | 0 | Q- displacement 0-63 |
+---+---+---+---+---+---+---+---+---+
| 1 | 1 | 1 | S- displacement 0-63 |
+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 1 0 1 |loop op| 0 |+/-| P-relative displacement | Loop
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 1 1 0 0 | I | 0 1 | > | = | < | P+- displacement 0-31 | Branch
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Implementation notes:
1. The BCC instruction specifies the branch condition in bits 7-9. There
are separate secondary entries for each of the conditions.
2. The BR (Branch) instruction has two forms. When bit 6 = 0, it has a
P-relative displacement with optional indexing and indirection. When bit
6 = 1, it has an indirect DB/Q/S-relative displacement with optional
indexing. Two secondary entries are needed for the two operand types.
The opcode for BR DB/Q/S,I is 143000, i.e., with the I bit forced on.
The second opcode entry is 141000 to put the I bit with the operand for
proper decoding.
3. Signed displacements are in sign-magnitude form, not two's complement.
*/
static const OP_TABLE mlb_ops = {
{ "", 0000000, opNone }, /* placeholder for opcode 00 */
{ "", 0010000, opNone }, /* placeholder for opcode 01 */
{ "", 0020000, opNone }, /* placeholder for opcode 02 */
{ "", 0030000, opNone }, /* placeholder for opcode 03 */
{ "LOAD", 0040000, opPD255IX },
{ "", 0050000, opNone }, /* TBA, MTBA, TBX, MTBX, STOR */
{ "CMPM", 0060000, opPD255IX },
{ "ADDM", 0070000, opPD255IX },
{ "SUBM", 0100000, opPD255IX },
{ "MPYM", 0110000, opPD255IX },
{ "", 0120000, opNone }, /* INCM, DECM */
{ "LDX", 0130000, opPD255IX },
{ "", 0140000, opNone }, /* BR, BCC */
{ "", 0150000, opNone }, /* LDB, LDD */
{ "", 0160000, opNone }, /* STB, STD */
{ "LRA", 0170000, opPD255IX },
{ "TBA", 0050000, opPS255, 0177777 },
{ "MTBA", 0052000, opPS255, 0177777 },
{ "TBX", 0054000, opPS255, 0177777 },
{ "MTBX", 0056000, opPS255, 0177777 },
{ "STOR", 0051000, opD255IX, 0177777 },
{ "INCM", 0120000, opD255IX, 0177777 },
{ "DECM", 0121000, opD255IX, 0177777 },
{ "BR", 0140000, opPS255IX, 0177777 }, /* P-relative displacement */
{ "BN", 0141000, opPS31I, 0177777 }, /* branch never */
{ "BL", 0141100, opPS31I, 0177777 }, /* branch on less than */
{ "BE", 0141200, opPS31I, 0177777 }, /* branch on equal */
{ "BLE", 0141300, opPS31I, 0177777 }, /* branch on less than or equal */
{ "BG", 0141400, opPS31I, 0177777 }, /* branch on greater than */
{ "BNE", 0141500, opPS31I, 0177777 }, /* branch on not equal */
{ "BGE", 0141600, opPS31I, 0177777 }, /* branch on greater than or equal */
{ "BA", 0141700, opPS31I, 0177777 }, /* branch always */
{ "BR", 0141000, opD255IX, 0177777 }, /* indirect DB/Q/S-relative displacement */
{ "LDB", 0150000, opD255IX, 0177777 },
{ "LDD", 0151000, opD255IX, 0177777 },
{ "STB", 0160000, opD255IX, 0177777 },
{ "STD", 0161000, opD255IX, 0177777 },
{ NULL }
};
/* EDIT subprogram operations.
The EDIT instruction, part of the COBOL II Extended Instruction Set,
implements the COBOL "PICTURE" clause. The formation of the edited string is
controlled by a subprogram whose address is pushed onto the stack.
Subprogram operations are encoded into variable-length sequences of bytes.
Each operation begins with an opcode of the form:
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| opcode | imm. operand |
+---+---+---+---+---+---+---+---+
Opcodes 0-16 directly decode to specific operations, and the lower half of
the first byte contains an immediate operand value from 1-15 or a zero value
that indicates that the immediate operand is contained in the next byte.
Opcode 17 is a prefix for the subopcode contained in the lower half of the
first byte in place of the immediate operand. Opcode bytes are followed by
zero or more operand bytes, depending on the operation.
The opcodes and their assigned operations are:
Opcode SubOp Mnem Movement Operation
------ ----- ---- -------- ---------------------------------------
00 - MC s => t move characters
01 - MA s => t move alphabetics
02 - MN s => t move numerics
03 - MNS s => t move numerics suppressed
04 - MFL s => t move numerics with floating insertion
05 - IC c => t insert character
06 - ICS c => t insert character suppressed
07 - ICI p => t insert characters immediate
10 - ICSI p => t insert characters suppressed immediate
11 - BRIS (none) branch if significance
12 - SUFT (none) subtract from target
13 - SUFS (none) subtract from source
14 - ICP c -> t insert character punctuation
15 - ICPS c -> t insert character punctuation suppressed
16 - IS p => t insert character on sign
17 00 TE (none) terminate edit
17 01 ENDF c -> t end floating point insertion
17 02 SST1 (none) set significance to 1
17 03 SST0 (none) set significance to 0
17 04 MDWO s -> t move digit with overpunch
17 05 SFC (none) set fill character
17 06 SFLC (none) set float character
17 07 DFLC (none) define float character
17 10 SETC (none) set loop count
17 11 DBNZ (none) decrement loop count and branch
Where:
s = source byte string operand
t = target byte string operand
p = program byte string operand
c = character operand
-> = move 1 byte
=> = move n bytes
Implementation notes:
1. The operation name table is indexed by the sum of the opcode and
subopcode, where the latter is zero if the opcode is not extended. This
provides simple access to a single table.
*/
static const char *const edit_ops [] = { /* EDIT operation names */
"MC", /* 000 - move characters */
"MA", /* 001 - move alphabetics */
"MN", /* 002 - move numerics */
"MNS", /* 003 - move numerics suppressed */
"MFL", /* 004 - move numerics with floating insertion */
"IC", /* 005 - insert character */
"ICS", /* 006 - insert character suppressed */
"ICI", /* 007 - insert characters immediate */
"ICSI", /* 010 - insert characters suppressed immediate */
"BRIS", /* 011 - branch if significance */
"SUFT", /* 012 - subtract from target */
"SUFS", /* 013 - subtract from source */
"ICP", /* 014 - insert character punctuation */
"ICPS", /* 015 - insert character punctuation suppressed */
"IS", /* 016 - insert character on sign */
"TE", /* 017 (017 + 000) - terminate edit */
"ENDF", /* 020 (017 + 001) - end floating point insertion */
"SST1", /* 021 (017 + 002) - set significance to 1 */
"SST0", /* 022 (017 + 003) - set significance to 0 */
"MDWO", /* 023 (017 + 004) - move digit with overpunch */
"SFC", /* 024 (017 + 005) - set fill character */
"SFLC", /* 025 (017 + 006) - set float character */
"DFLC", /* 026 (017 + 007) - define float character */
"SETC", /* 027 (017 + 010) - set loop count */
"DBNZ" /* 030 (017 + 011) - decrement loop count and branch */
};
/* System interface local SCP support routines */
static void one_time_init (void);
static t_bool fprint_stopped (FILE *st, t_stat reason);
static void fprint_addr (FILE *st, DEVICE *dptr, t_addr addr);
static t_addr parse_addr (DEVICE *dptr, CONST char *cptr, CONST char **tptr);
static t_stat hp_exdep_cmd (int32 arg, CONST char *buf);
static t_stat hp_run_cmd (int32 arg, CONST char *buf);
static t_stat hp_brk_cmd (int32 arg, CONST char *buf);
/* System interface local utility routines */
static t_stat fprint_value (FILE *ofile, t_value val, uint32 radix, uint32 width, uint32 format);
static t_stat fprint_order (FILE *ofile, t_value *val, uint32 radix);
static t_stat fprint_subop (FILE *ofile, t_value *val, uint32 radix, t_addr addr, int32 switches);
static t_stat fprint_instruction (FILE *ofile, const OP_TABLE ops, t_value *val,
uint32 mask, uint32 shift, uint32 radix);
static t_stat parse_cpu (CONST char *cptr, t_addr address, UNIT *uptr, t_value *value, int32 switches);
/* System interface state */
static size_t device_size = 0; /* maximum device name size */
static size_t flag_size = 0; /* maximum debug flag name size */
static APC_FLAGS parse_config = apcNone; /* address parser configuration */
/* System interface global data structures */
#define E 0400u /* parity bit for even parity */
#define O 0000u /* parity bit for odd parity */
const HP_WORD odd_parity [256] = { /* odd parity table */
E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 000-017 */
O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 020-037 */
O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 040-067 */
E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 060-077 */
O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 100-117 */
E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 120-137 */
E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 140-157 */
O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 160-177 */
O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 200-217 */
E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 220-237 */
E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 240-267 */
O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 260-277 */
E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 300-317 */
O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 320-337 */
O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 340-357 */
E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E /* 360-377 */
};
static const BITSET_NAME inbound_names [] = { /* Inbound signal names, in INBOUND_SIGNAL order */
"DSETINT", /* 000000000001 */
"DCONTSTB", /* 000000000002 */
"DSTARTIO", /* 000000000004 */
"DWRITESTB", /* 000000000010 */
"DRESETINT", /* 000000000020 */
"DSTATSTB", /* 000000000040 */
"DSETMASK", /* 000000000100 */
"DREADSTB", /* 000000000200 */
"ACKSR", /* 000000000400 */
"TOGGLESR", /* 000000001000 */
"SETINT", /* 000000002000 */
"PCMD1", /* 000000004000 */
"PCONTSTB", /* 000000010000 */
"SETJMP", /* 000000020000 */
"PSTATSTB", /* 000000040000 */
"PWRITESTB", /* 000000100000 */
"PREADSTB", /* 000000200000 */
"EOT", /* 000000400000 */
"TOGGLEINXFER", /* 000001000000 */
"TOGGLEOUTXFER", /* 000002000000 */
"READNEXTWD", /* 000004000000 */
"TOGGLESIOOK", /* 000010000000 */
"DEVNODB", /* 000020000000 */
"INTPOLLIN", /* 000040000000 */
"XFERERROR", /* 000100000000 */
"CHANSO", /* 000200000000 */
"PFWARN" /* 000400000000 */
};
const BITSET_FORMAT inbound_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (inbound_names, 0, lsb_first, no_alt, no_bar) };
static const BITSET_NAME outbound_names [] = { /* Outbound signal names, in OUTBOUND_SIGNAL order */
"INTREQ", /* 000000200000 */
"INTACK", /* 000000400000 */
"INTPOLLOUT", /* 000001000000 */
"DEVEND", /* 000002000000 */
"JMPMET", /* 000004000000 */
"CHANACK", /* 000010000000 */
"CHANSR", /* 000020000000 */
"SRn" /* 000040000000 */
};
const BITSET_FORMAT outbound_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (outbound_names, 16, lsb_first, no_alt, no_bar) };
/* System interface global SCP data definitions */
char sim_name [] = "HP 3000"; /* the simulator name */
int32 sim_emax = 2; /* the maximum number of words in any instruction */
void (*sim_vm_init) (void) = &one_time_init; /* a pointer to the one-time initializer */
DEVICE *sim_devices [] = { /* an array of pointers to the simulated devices */
&cpu_dev, /* CPU (must be first) */
&iop_dev, /* I/O Processor */
&mpx_dev, /* Multiplexer Channel */
&sel_dev, /* Selector Channel */
&scmb_dev [0], &scmb_dev [1], /* Selector Channel Maintenance Boards */
&atcd_dev, &atcc_dev, /* Asynchronous Terminal Controller (TDI and TCI) */
&clk_dev, /* System Clock */
&lp_dev, /* Line Printer */
&ds_dev, /* 7905/06/20/25 MAC Disc Interface */
&ms_dev, /* 7970B/E Magnetic Tape Interface */
NULL /* end of the device list */
};
#define DEVICE_COUNT (sizeof sim_devices / sizeof sim_devices [0] - 1)
const char *sim_stop_messages [] = { /* an array of pointers to the stop messages in STOP_nnn order */
"Impossible error", /* 0 (never returned) */
"System halt", /* STOP_SYSHALT */
"Unimplemented instruction", /* STOP_UNIMPL */
"Undefined instruction", /* STOP_UNDEF */
"CPU paused", /* STOP_PAUS */
"Programmed halt", /* STOP_HALT */
"Breakpoint", /* STOP_BRKPNT */
"Infinite loop", /* STOP_INFLOOP */
"Cold load complete", /* STOP_CLOAD */
"Cold dump complete", /* STOP_CDUMP */
"Auto-restart disabled", /* STOP_ARSINH */
"Power is off" /* STOP_POWER */
};
/* Local command table.
This table defines commands and command behaviors that are specific to this
simulator. One new command is defined, and several commands are repurposed
or extended. Specifically:
* EXAMINE, DEPOSIT, IEXAMINE, and IDEPOSIT accept bank/offset form, implied
DBANK offsets, and memory bank override switches.
* RUN and GO accept implied PBANK offsets and reject bank/offset form and
memory bank override switches.
* BREAK and NOBREAK accept bank/offset form and implied PBANK offsets and
reject memory bank override switches.
* LOAD and DUMP invoke the CPU cold load/cold dump facility, rather than
loading or dumping binary files.
* POWER adds the ability to fail or restore power to the CPU.
The table is initialized with only those fields that differ from the standard
command table. During one-time simulator initialization, the empty fields
are filled in from the corresponding standard command table entries. This
ensures that the auxiliary table automatically picks up any changes to the
standard commands that it modifies.
Implementation notes:
1. The RESET and BOOT commands are duplicated from the standard SCP command
table so that entering "R" doesn't invoke the RUN command and entering
"B" doesn't invoke the BREAK command. This would otherwise occur because
a VM-specific command table is searched before the standard command
table.
*/
static CTAB aux_cmds [] = {
/* Name Action Routine Argument Help String */
/* ---------- -------------- --------- ---------------------------------------------------- */
{ "RESET", NULL, 0, NULL },
{ "BOOT", NULL, 0, NULL },
{ "EXAMINE", &hp_exdep_cmd, 0, NULL },
{ "IEXAMINE", &hp_exdep_cmd, 0, NULL },
{ "DEPOSIT", &hp_exdep_cmd, 0, NULL },
{ "IDEPOSIT", &hp_exdep_cmd, 0, NULL },
{ "RUN", &hp_run_cmd, 0, NULL },
{ "GO", &hp_run_cmd, 0, NULL },
{ "BREAK", &hp_brk_cmd, 0, NULL },
{ "NOBREAK", &hp_brk_cmd, 0, NULL },
{ "LOAD", &cpu_cold_cmd, Cold_Load, "l{oad} {cntlword} cold load from a device\n" },
{ "DUMP", &cpu_cold_cmd, Cold_Dump, "du{mp} {cntlword} cold dump to a device\n" },
{ "POWER", &cpu_power_cmd, 0, "p{ower} f{ail} fail the CPU power\n"
"p{ower} r{estore} restore the CPU power\n" },
{ NULL }
};
/* System interface global SCP support routines */
/* Load and dump memory images from and to files.
The LOAD and DUMP commands are intended to provide a basic method of loading
and dumping programs into and from memory. Typically, these commands operate
on a simple, low-level format, e.g., a memory image.
However, the HP 3000 requires the bank and segment registers being set up
appropriately before execution. In addition, the CPU microcode depends on
segment tables being present in certain fixed memory locations as part of a
program load. These actions will not take place unless the system cold load
facility is employed.
Consequently, the LOAD and DUMP commands are repurposed to invoke the cold
load and cold dump facilities, respectively, and this is a dummy routine that
will never be called. It is present only to satisfy the external declared in
the SCP module.
*/
t_stat sim_load (FILE *fptr, CONST char *cptr, CONST char *fnam, int flag)
{
return SCPE_ARG; /* return an error if called inadvertently */
}
/* Print a value in symbolic format.
This routine prints a data value in the format specified by the optional
switches on the output stream provided. On entry, "ofile" is the opened
output stream, and the other parameters depend on the reason the routine was
called, as follows:
* To print the next instruction mnemonic when the simulator stops:
- addr = the program counter
- val = a pointer to sim_eval [0]
- uptr = NULL
- sw = "-M" | SIM_SW_STOP
* To print the result of EXAMining a register with REG_VMIO or a user flag:
- addr = the ORed register radix and user flags
- val = a pointer to a single t_value
- uptr = NULL
- sw = the command line switches | SIM_SW_REG
* To print the result of EXAMining a memory address:
- addr = the memory address
- val = a pointer to sim_eval [0]
- uptr = a pointer to the named unit
- sw = the command line switches
* To print the result of EVALuating a symbol:
- addr = the symbol index
- val = a pointer to sim_eval [addr]
- uptr = a pointer to the default unit (cpu_unit)
- sw = the command line switches
On exit, a status code is returned to the caller. If the format requested is
not supported, SCPE_ARG status is returned, which causes the caller to print
the value in numeric format with the default radix. Otherwise, SCPE_OK
status is returned if a single-word value was consumed, or the negative
number of extra words (beyond the first) consumed in printing the symbol is
returned. For example, printing a two-word symbol would return
SCPE_OK_2_WORDS (= -1).
The following symbolic modes are supported by including the indicated
switches on the command line:
Switch Display Interpretation
------ --------------------------------------------------
-A a single character in the right-hand byte
-C a two-character packed string
-E an EDIT instruction subprogram mnemonic
-ER an EDIT mnemonic starting with the right-hand byte
-I an I/O program instruction mnemonic
-M a CPU instruction mnemonic
-T a CPU status mnemonic
In the absence of a mode switch, the value is displayed in a numeric format.
When displaying data in one of the mnemonic modes, an additional switch may
be specified to indicate the desired operand format, as follows:
Switch Operand Interpretation
------ --------------------------------------------------
-A a single character in the right-hand byte
-B a binary value
-O an octal value
-D a decimal value
-H a hexadecimal value
Except for -B, these switches may be used without a mode switch to display a
numeric value in the specified form. To summarize, the valid switch
combinations are:
-A
-C
-E [ -R ] [ -A | -B | -O | -D | -H ]
-I [ -A | -B | -O | -D | -H ]
-M [ -A | -B | -O | -D | -H ]
-T [ -A | -B | -O | -D | -H ]
When displaying mnemonics, operand values by default are displayed in a radix
suitable to the type of the value. Address values are displayed in the CPU's
address radix, which is octal, and data values are displayed in the CPU's
data radix, which defaults to octal but may be set to a different radix or
overridden by a switch on the command line.
Implementation notes:
1. Because mnemonics are specific to the CPU/MPX/SEL, the CPU's radix
settings are used, even if the unit is a peripheral. For example,
displaying disc sector data as CPU instructions uses the CPU's address
and data radix values, rather than the disc's values.
2. Displaying a register having a symbolic default format (e.g., CIR) will
use the default unless the radix is overridden on the command line. For
example, "EXAMINE CIR" displays the CIR value as an instruction mnemonic,
whereas "EXAMINE -D CIR" displays the value as decimal. Adding "-M" will
force mnemonic display and allow the radix switch to override the operand
display. For example, "EXAMINE -M -D CIR" displays the value as mnemonic
and overrides the operand radix to decimal.
3. We return SCPE_INVSW when multiple modes or formats are specified, but
the callers do not act on this; they use the fallback formatter if any
status error is returned. We could work around this by printing "Invalid
switch" to the console and returning SCPE_OK, but this does not stop
IEXAMINE from prompting for the replacement value(s) or EXAMINE from
printing a range.
4. Radix switches and the -C switch are conceptually mutually exclusive.
However, if we return an error when "format" is non-zero, then -C will be
ignored, and the fallback formatter will use the radix switch. The other
choice is to process -C and ignore the radix switch; this is the option
implemented.
5. Because -A is both a mode and a format switch, we must check its presence
using SYMBOLIC_SWITCHES separately from the other modes to allow (e.g.)
both "EXAMINE -A" and "EXAMINE -M -A". If -A is added to MODE_SWITCHES,
the latter form would be rejected as having conflicting modes.
6. The penultimate condition of the multiway "if-else if" mode test checks
for no mode switches. This succeeds when -A is specified alone because
the earlier SYMBOLIC_SWITCHES test failed (so -A is present), but none of
the other mode switches are present.
*/
t_stat fprint_sym (FILE *ofile, t_addr addr, t_value *val, UNIT *uptr, int32 sw)
{
int32 formats, modes;
uint32 radix;
if ((sw & (SIM_SW_REG | ALL_SWITCHES)) == SIM_SW_REG) /* if we are formatting a register without overrides */
if (addr & REG_A) /* then if the default format is character */
sw |= A_SWITCH; /* then set the -A switch */
else if (addr & REG_C) /* otherwise if the default mode is string */
sw |= C_SWITCH; /* then set the -C switch */
else if (addr & REG_M) /* otherwise if the default mode is instruction mnemonic */
sw |= M_SWITCH; /* then set the -M switch */
else if (addr & REG_T) /* otherwise if the default mode is status */
sw |= T_SWITCH; /* then set the -T switch */
if ((sw & SYMBOLIC_SWITCHES) == 0) /* if there are no symbolic mode overrides */
return SCPE_ARG; /* then return an error to use the standard formatter */
formats = sw & FORMAT_SWITCHES; /* separate the format switches */
modes = sw & MODE_SWITCHES; /* from the mode switches */
if (formats == A_SWITCH) /* if the -A switch is specified */
radix = 256; /* then override the radix to character */
else if (formats == B_SWITCH) /* otherwise if the -B switch is specified */
radix = 2; /* then override the radix to binary */
else if (formats == D_SWITCH) /* otherwise if the -D switch is specified */
radix = 10; /* then override the radix to decimal */
else if (formats == H_SWITCH) /* otherwise if the -H switch is specified */
radix = 16; /* then override the radix to hexadecimal */
else if (formats == O_SWITCH) /* otherwise if the -O switch is specified */
radix = 8; /* then override the radix to octal */
else if (formats == 0) /* otherwise if no format switch is specified */
radix = 0; /* then indicate that the default radix is to be used */
else /* otherwise more than one format is specified */
return SCPE_INVSW; /* so return an error */
if (modes == M_SWITCH) /* if mnemonic mode is specified */
return fprint_cpu (ofile, val, radix, sw); /* then format and print the value in mnemonic format */
else if (modes == I_SWITCH) /* otherwise if I/O channel order mode is specified */
return fprint_order (ofile, val, radix); /* then format and print it */
else if (modes == E_SWITCH) /* otherwise if an EDIT subop memory display is requested */
return fprint_subop (ofile, val, radix, addr, sw); /* then format and print it */
else if (modes == T_SWITCH) { /* otherwise if status display is requested */
fputs (fmt_status ((uint32) val [0]), ofile); /* then format the status flags and condition code */
fputc (' ', ofile); /* and add a separator */
if (fprint_value (ofile, STATUS_CS (val [0]), /* if the code segment number */
(radix ? radix : cpu_dev.dradix), /* prints with the specified radix */
STATUS_CS_WIDTH, PV_RZRO) == SCPE_OK)
return SCPE_OK; /* then return success */
else /* otherwise print it */
return fprint_val (ofile, STATUS_CS (val [0]), /* in the CPU's default data radix */
cpu_dev.dradix, D8_WIDTH, PV_RZRO);
}
else if (modes == C_SWITCH) { /* otherwise if ASCII string mode is specified */
fputs (fmt_char (UPPER_BYTE (val [0])), ofile); /* then format and print the upper byte */
fputc (',', ofile); /* followed by a separator */
fputs (fmt_char (LOWER_BYTE (val [0])), ofile); /* followed by the lower byte */
return SCPE_OK;
}
else if (modes == 0) /* otherwise if single-character mode was specified */
return fprint_value (ofile, val [0], radix, 0, 0); /* then format and print it */
else /* otherwise the modes conflict */
return SCPE_INVSW; /* so return an error */
}
/* Parse a string in symbolic format.
Parse the input string using the interpretation specified by the optional
switches, and return the resulting value(s). This routine is called to
parse an input string when:
- DEPOSITing into a register marked with a user flag
- DEPOSITing into a memory address
- EVALuating a symbol
On entry, "cptr" points at the string to parse, "addr" is the register radix
and flags, memory address, or 0 (respectively), "uptr" is NULL, a pointer to
the named unit, or a pointer to the default unit (respectively), "val" is a
pointer to an array of t_values of depth "sim_emax" representing the value(s)
returned, and "sw" contains any switches passed on the command line. "sw"
also includes SIM_SW_REG for a register call.
On exit, a status code is returned to the caller. If the format requested is
not supported or the parse failed, SCPE_ARG status is returned, which causes
the caller to attempt to parse the value in numeric format. Otherwise,
SCPE_OK status is returned if the parse produced a single-word value, or the
negative number of extra words (beyond the first) produced by parsing the
symbol is returned. For example, parsing a symbol that resulted in two words
being stored (in val [0] and val [1]) would return SCPE_OK_2_WORDS (= -1).
The following symbolic formats are supported by the listed switches:
Switch Interpretation
------ ----------------------------------
-a a single character the in low byte
-c a two-character packed string
-o override numeric input to octal
-d override numeric input to decimal
-h override numeric input to hex
In the absence of switches, a leading ' implies "-a", a leading " implies
"-c", and a leading alphabetic character implies an instruction mnemonic. If
a single character is supplied with "-c", the low byte of the resulting value
will be zero; follow the character with a space if the low byte is to be
padded with a space.
Caution must be exercised when entering hex values without a leading digit.
A value that is the same as an instruction mnemonic will be interpreted as
the latter unless overridden by the "-h" switch. For example, "ADD" is an
instruction mnemonic, but "ADE" is a hex value. To avoid confusion, always
enter hex values with the "-h" switch or with a leading zero (i.e., "0ADD").
When entering mnemonics, operand values are parsed in a radix suitable to the
type of the value. Address values are parsed in the CPU's address radix,
which is octal, and data values are parsed in the CPU's data radix, which
defaults to octal but may be set to a different radix or overridden by a
switch on the command line.
Implementation notes:
1. Because the mnemonics are specific to the CPU/MPX/SEL, the CPU's radix
settings are used, even if the unit is a peripheral. For example,
entering disc sector data as CPU instructions uses the CPU's address and
data radix values, rather than the disc's values.
2. The "cptr" post-increments are logically ANDed with the tests for ' and "
so that the increments are performed only if the tests succeed. The
intent is to skip over the leading ' or " character. The increments
themselves always succeed, so they don't affect the outcome of the tests.
*/
t_stat parse_sym (CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw)
{
while (isspace ((int) *cptr)) /* skip over any leading spaces */
cptr++; /* that are present in the line */
if (sw & SWMASK ('A') || *cptr == '\'' && cptr++) /* if an ASCII character parse is requested */
if (cptr [0] != '\0') { /* then if a character is present */
val [0] = (t_value) cptr [0]; /* then convert the character value */
return SCPE_OK; /* and indicate success */
}
else /* otherwise */
return SCPE_ARG; /* report that the line cannot be parsed */
else if (sw & SWMASK ('C') || *cptr == '"' && cptr++) /* otherwise if a character string parse is requested */
if (cptr [0] != '\0') { /* then if characters are present */
val [0] = (t_value) TO_WORD (cptr [0], cptr [1]); /* then convert the character value(s) */
return SCPE_OK; /* and indicate success */
}
else /* otherwise */
return SCPE_ARG; /* report that the line cannot be parsed */
else /* otherwise */
return parse_cpu (cptr, addr, uptr, val, sw); /* attempt a mnemonic instruction parse */
}
/* Set a device configuration value.
This validation routine is called to set a device's I/O configuration (device
number, interrupt mask, interrupt priority, and service request number). The
"uptr" parameter points to the unit being configured, "code" is a validation
constant (VAL_DEVNO, VAL_INTMASK, VAL_INTPRI, or VAL_SRNO), "cptr" points to
the first character of the value to be set, and "desc" points to the DIB
associated with the device.
If the supplied value is acceptable, it is stored in the DIB, and the routine
returns SCPE_OK. Otherwise, an error code is returned.
For the following validation constants, the acceptable ranges of values are:
VAL_DEVNO -- 0-127
VAL_INTMASK -- 0-15 | E | D
VAL_INTPRI -- 0-31
VAL_SRNO -- 0-15
Implementation notes:
1. For a numeric interrupt mask entry value <n>, the value stored in the DIB
is 2 ^ <15 - n> to match the HP 3000 bit numbering. For mask entry
values "D" and "E", the stored values are 0 and 0177777, respectively.
2. The SCMB is the only device that may or may not have a service request
number, depending on whether or not it is connected to the multiplexer
channel bus. Therefore, the current request number must be valid before
it may be changed.
*/
t_stat hp_set_dib (UNIT *uptr, int32 code, CONST char *cptr, void *desc)
{
DIB *const dibptr = (DIB *) desc; /* a pointer to the associated DIB */
t_stat status = SCPE_OK;
t_value value;
if (cptr == NULL || *cptr == '\0') /* if the expected value is missing */
status = SCPE_MISVAL; /* then report the error */
else /* otherwise a value is present */
switch (code) { /* and parsing depends on the value expected */
case VAL_DEVNO: /* DEVNO=0-127 */
value = get_uint (cptr, DEVNO_BASE, /* parse the supplied device number */
DEVNO_MAX, &status);
if (status == SCPE_OK) /* if it is valid */
dibptr->device_number = (uint32) value; /* then save it in the DIB */
break;
case VAL_INTMASK: /* INTMASK=0-15/E/D */
if (*cptr == 'E') /* if the mask value is "E" (enable) */
dibptr->interrupt_mask = INTMASK_E; /* then set all mask bits on */
else if (*cptr == 'D') /* otherwise if the mask value is "D" (disable) */
dibptr->interrupt_mask = INTMASK_D; /* then set all mask bits off */
else { /* otherwise */
value = get_uint (cptr, INTMASK_BASE, /* parse the supplied numeric mask value */
INTMASK_MAX, &status);
if (status == SCPE_OK) /* if it is valid */
dibptr->interrupt_mask = D16_SIGN >> value; /* then set the corresponding mask bit in the DIB */
}
break;
case VAL_INTPRI: /* INTPRI=0-31 */
value = get_uint (cptr, INTPRI_BASE, /* parse the supplied priority number */
INTPRI_MAX, &status);
if (status == SCPE_OK) /* if it is valid */
dibptr->interrupt_priority = (uint32) value; /* then save it in the DIB */
break;
case VAL_SRNO: /* SRNO=0-15 */
if (dibptr->service_request_number == SRNO_UNUSED) /* if the current setting is "unused" */
status = SCPE_NOFNC; /* then report that it cannot be set */
else { /* otherwise */
value = get_uint (cptr, SRNO_BASE, /* parse the supplied service request number */
SRNO_MAX, &status);
if (status == SCPE_OK) /* if it is valid */
dibptr->service_request_number = (uint32) value; /* then save it in the DIB */
}
break;
default: /* if an illegal code was passed */
status = SCPE_IERR; /* then report an internal coding error */
}
return status; /* return the validation result */
}
/* Show the device configuration values.
This display routine is called to show a device's I/O configuration (device
number, interrupt mask, interrupt priority, or service request number). The
"st" parameter is the open output stream, "uptr" points to the unit being
queried, "code" is a validation constant (VAL_DEVNO, VAL_INTMASK, VAL_INTPRI,
or VAL_SRNO), and "desc" points at the DIB associated with the device.
If the code is acceptable, the routine prints the DIB value for the specified
characteristic and returns SCPE_OK. Otherwise, an error code is returned.
For the following validation constants, the configuration values printed are:
VAL_DEVNO -- DEVNO=0-127
VAL_INTMASK -- INTMASK=0-15 | E | D
VAL_INTPRI -- INTPRI=0-31
VAL_SRNO -- SRNO=0-15
Implementation notes:
1. For a numeric interrupt mask entry value <n>, the value stored in the DIB
is 2 ^ <15 - n> to match the HP 3000 bit numbering. For mask entry
values "D" and "E", the stored values are 0 and 0177777, respectively.
*/
t_stat hp_show_dib (FILE *st, UNIT *uptr, int32 code, CONST void *desc)
{
const DIB *const dibptr = (const DIB *) desc; /* a pointer to the associated DIB */
uint32 mask, value;
switch (code) { /* display the requested value */
case VAL_DEVNO: /* show the device number */
fprintf (st, "DEVNO=%u", dibptr->device_number);
break;
case VAL_INTMASK: /* show the interrupt mask */
fputs ("INTMASK=", st);
if (dibptr->interrupt_mask == INTMASK_D) /* if the mask is disabled */
fputc ('D', st); /* then display "D" */
else if (dibptr->interrupt_mask == INTMASK_E) /* otherwise if the mask is enabled */
fputc ('E', st); /* then display "E" */
else { /* otherwise */
mask = dibptr->interrupt_mask; /* display a specific mask value */
for (value = 0; !(mask & D16_SIGN); value++) /* count the number of mask bit shifts */
mask = mask << 1; /* until the correct one is found */
fprintf (st, "%u", value); /* display the mask bit number */
}
break;
case VAL_INTPRI: /* show the interrupt priority */
fprintf (st, "INTPRI=%u", dibptr->interrupt_priority);
break;
case VAL_SRNO: /* show the service request number */
if (dibptr->service_request_number == SRNO_UNUSED) /* if the current setting is "unused" */
fprintf (st, "SRNO not used"); /* then report it */
else /* otherwise report the SR number */
fprintf (st, "SRNO=%u", dibptr->service_request_number);
break;
default: /* if an illegal code was passed */
return SCPE_IERR; /* then report an internal coding error */
}
return SCPE_OK; /* return the display result */
}
/* System interface global utility routines */
/* Print a CPU instruction in symbolic format.
This routine is called to format and print an instruction in mnemonic form.
The "ofile" parameter is the opened output stream, "val [*]" contains the
word(s) comprising the machine instruction to print, "radix" contains the
desired operand radix or zero if the default radix is to be used, and
"switches" includes the SIM_SW_STOP switch if the routine was called as part
of a simulation stop.
The routine returns a status code to the caller. SCPE_OK status is returned
if the print consumed a single-word value, or the negative number of extra
words (beyond the first) consumed by printing the instruction is returned.
For example, printing a symbol that resulted in two words being consumed
(from val [0] and val [1]) would return SCPE_OK_2_WORDS (= -1).
HP 3000 machine instructions are generally classified by the first four bits.
Within each class, additional bits identify sub-classes or individual
instructions.
Most of the decoding work is handled by the "fprint_instruction" routine,
which prints mnemonics and operands and returns a status code indicating the
number of words consumed for the current instruction.
Implementation notes:
1. For a stack instruction, if the R (right stack-op pending) bit in the
status word is set, and the request is for a simulation stop, the
left-hand opcode will print as dashes to indicate that it has already
been executed.
2. The status return from "fprint_instruction" is always SCPE_OK for the
stack_ops table, which is fully decoded, so the return value from
printing the left stack opcode is not used.
*/
t_stat fprint_cpu (FILE *ofile, t_value *val, uint32 radix, int32 switches)
{
const char *dashes = "----,";
t_stat status = SCPE_OK;
switch (SUBOP (val [0])) { /* dispatch based on the instruction sub-opcode */
case 000: /* stack operations */
if (STA & STATUS_R && switches & SIM_SW_STOP) /* if right stack-op pending and this is a simulation stop */
fputs (dashes + 4 /* then indicate that the left stack-op has completed */
- strlen (stack_ops [STACKOP_A (val [0])].mnemonic), ofile);
else { /* otherwise */
(void) fprint_instruction (ofile, stack_ops, /* print the left operation */
val, STACKOP_A_MASK, /* while discarding the status */
STACKOP_A_SHIFT, radix);
fputc (',', ofile); /* add a separator */
}
status = fprint_instruction (ofile, stack_ops, /* print the right operation */
val, STACKOP_B_MASK,
STACKOP_B_SHIFT, radix);
break;
case 001: /* shift/branch/bit operations */
status = fprint_instruction (ofile, sbb_ops, /* print the operation */
val, SBBOP_MASK,
SBBOP_SHIFT, radix);
break;
case 002: /* move/special/firmware/immediate/field/register operations */
status = fprint_instruction (ofile, msfifr_ops, /* print the operation */
val, MSFIFROP_MASK,
MSFIFROP_SHIFT, radix);
break;
case 003: /* I/O/control/program/immediate/memory operations */
if (val [0] & IOCPIMOP_MASK) /* if it is a program, immediate, or memory instruction */
status = fprint_instruction (ofile, pmi_ops, /* then print the operation */
val, IOCPIMOP_MASK,
IOCPIMOP_SHIFT, radix);
else /* otherwise it is an I/O or control operation */
status = fprint_instruction (ofile, ioc_ops, /* so print the operation */
val, IOCSUBOP_MASK,
IOCSUBOP_SHIFT, radix);
break;
default: /* memory, loop, and branch operations */
status = fprint_instruction (ofile, mlb_ops, /* print the operation */
val, MLBOP_MASK,
MLBOP_SHIFT, radix);
break;
}
return status; /* return the consumption status */
}
/* Print an EDIT instruction subprogram operation in symbolic format.
This routine prints a single EDIT subprogram operation. The opcode name is
obtained from the name table and printed, followed by the operands.
On entry, the "ofile" parameter is the opened output stream, "val" is NULL or
points to an array containing the first two words of the operation, "radix"
contains the desired operand radix or zero if the default radix is to be
used, and "byte_address" points at the first byte of the subprogram
operation. The return value is the number of subprogram bytes used by the
operation.
This routine is called by the SCP "EXAMINE" command processor to display an
EDIT subprogram contained in memory in symbolic format. It is also called
by the EDIT instruction executor when tracing memory operands. In the former
case, the "EXAMINE" command has obtained two words from memory and placed
them in the array pointed to by the "val" parameter. In the latter case,
"val" will be NULL, indicating that the array must be loaded explicitly. In
either case, additional memory bytes are obtained as needed by the operation
decoder.
For each operation, the following type of operand and the number of bytes
required are listed below:
Opcode SubOp Mnem Operand Byte Count
------ ----- ---- --------- ----------
00 - MC ux 1-2
01 - MA ux 1-2
02 - MN ux 1-2
03 - MNS ux 1-2
04 - MFL ux 1-2
05 - IC ux,'c' 2-3
06 - ICS ux,'c' 2-3
07 - ICI ux,"s" 2-257
10 - ICSI ux,"s" 2-257
11 - BRIS sx 1-2
12 - SUFT sx 1-2
13 - SUFS sx 1-2
14 - ICP 'p' 1
15 - ICPS 'p' 1
16 - IS u,"s","s" 1-31
17 00 TE (none) 1
17 01 ENDF (none) 1
17 02 SST1 (none) 1
17 03 SST0 (none) 1
17 04 MDWO (none) 1
17 05 SFC 'c' 2
17 06 SFLC 'p','p' 2
17 07 DFLC 'c','c' 3
17 10 SETC ui 2
17 11 DBNZ s 2
Where the operands are:
Operand Meaning
------- -----------------------------------------------
u unsigned value 0 .. 15
ux unsigned value 1 .. 15 or 0 .. 255 (extended)
ui unsigned value 1 .. 256
s signed value -128 .. +127
sx signed value 1 .. 15 or -128 .. +127 (extended)
'p' punctuation character value ' ' + 0 .. 15
'c' character value 0 .. 255
"s" string value of 0 .. 255 characters
Note that in only three cases must additional memory accesses be performed.
Implementation notes:
1. Operation decoding is simplified by translating the prefix opcode (17)
and subopcodes (00-11) into extended opcodes 17+0 to 17+11 (17-30).
2. Branch operation displacements are printed as positive to indicate a
forward jump and negative to indicate a backward jump. The radix used is
the CPU's address radix, which is octal.
3. The Machine Instruction Set manual says that the DBNZ ("decrement loop
count and branch") operation adds the displacement operand. However,
the microcode manual shows that the displacement is actually subtracted.
Therefore, we print the operand as a negative value to conform with the
hardware action.
4. A simple GET_BYTE macro is defined for easy access to the bytes contained
in the "val" array. The macro also increments the byte address and
increments the word array pointer as needed.
*/
#define GET_BYTE(b) \
if (byte_address++ & 1) \
b = LOWER_BYTE (*val++); \
else \
b = UPPER_BYTE (*val)
uint32 fprint_edit (FILE *ofile, t_value *val, uint32 radix, uint32 byte_address)
{
uint8 byte, opcode, operand;
uint32 word_address, byte_count, disp_radix;
t_value eval_array [2];
if (radix == 0) /* if the supplied radix is defaulted */
disp_radix = 10; /* then assume decimal numeric output */
else /* otherwise */
disp_radix = radix; /* use the radix given */
if (val == NULL) { /* if the "val" array has not been loaded */
word_address = byte_address / 2; /* then get the word address of the operation */
mem_examine (&eval_array [0], word_address, NULL, 0); /* load the two words */
mem_examine (&eval_array [1], word_address + 1, NULL, 0); /* containing the target operation */
val = eval_array; /* point at the array containing the operation */
}
GET_BYTE (byte); /* get the opcode byte */
byte_count = 1; /* and count it */
opcode = UPPER_HALF (byte); /* separate the opcode */
operand = LOWER_HALF (byte); /* and the immediate operand */
if (operand == 0 && opcode <= EDIT_SUFS) { /* if an extended immediate operand is indicated */
GET_BYTE (operand); /* then get it */
byte_count = 2; /* and count it */
}
if (opcode == EDIT_EXOP) /* if the opcode is extended */
opcode = opcode + operand; /* then translate it */
if (opcode <= EDIT_DBNZ) { /* if the extended opcode is valid */
fputs (edit_ops [opcode], ofile); /* then print the associated mnemonic */
if (opcode < EDIT_TE || opcode > EDIT_MDWO) /* if the operation has an operand */
fputc (' ', ofile); /* then add a space as a separator */
}
switch (opcode) { /* dispatch by the extended opcode */
case 000: /* MC - move characters */
case 001: /* MA - move alphabetics */
case 002: /* MN - move numerics */
case 003: /* MNS - move numerics suppressed */
case 004: /* MFL - move numerics with floating insertion */
fprint_value (ofile, operand, disp_radix, /* print the character count */
D8_WIDTH, PV_LEFT);
break;
case 005: /* IC - insert character */
case 006: /* ICS - insert character suppressed */
GET_BYTE (byte); /* get the insertion character */
byte_count = byte_count + 1; /* and count it */
fprint_value (ofile, operand, disp_radix, /* print the insertion count */
D8_WIDTH, PV_LEFT);
fputc (',', ofile); /* add a separator */
fputs (fmt_char ((uint32) byte), ofile); /* and print the insertion character */
break;
case 007: /* ICI - insert characters immediate */
case 010: /* ICSI - insert characters suppressed immediate */
fprint_value (ofile, operand, disp_radix, /* print the character count */
D8_WIDTH, PV_LEFT);
fputs (",\"", ofile); /* add a separator and open the quotation */
fputs (fmt_byte_operand (byte_address, operand), ofile); /* print the string operand */
fputc ('"', ofile); /* and close the quotation */
byte_count = byte_count + operand; /* count the bytes */
break;
case 030: /* DBNZ - decrement loop count and branch */
GET_BYTE (operand); /* get the signed branch displacement */
byte_count = byte_count + 1; /* and count it */
if (operand == 0200) { /* if the operand is -128 */
fputc ('+', ofile); /* then print a plus sign for display */
fprint_value (ofile, operand, cpu_dev.aradix, /* and the displacement */
D8_WIDTH, PV_LEFT); /* in the CPU's address radix */
break; /* and we're done */
}
else /* otherwise */
operand = NEG8 (operand); /* negate the operand for display */
/* fall into the BRIS case */
case 011: /* BRIS - branch if significance */
if (operand & D8_SIGN) { /* if the displacement is negative */
fputc ('-', ofile); /* then print a minus sign */
operand = NEG8 (operand); /* and make the value positive */
}
else /* otherwise */
fputc ('+', ofile); /* print a plus sign */
fprint_value (ofile, operand, cpu_dev.aradix, /* print the displacement */
D8_WIDTH, PV_LEFT); /* in the CPU's address radix */
break;
case 012: /* SUFT - subtract from target */
case 013: /* SUFS - subtract from source */
if (operand & D8_SIGN) { /* if the displacement is negative */
fputc ('-', ofile); /* then print a minus sign */
operand = NEG8 (operand); /* and make the value positive */
}
fprint_value (ofile, operand, disp_radix, /* print the displacement */
D8_WIDTH, PV_LEFT);
break;
case 014: /* ICP - insert character punctuation */
case 015: /* ICPS - insert character punctuation suppressed */
fputs (fmt_char ((uint32) (operand + ' ')), ofile); /* print the punctuation character */
break;
case 016: /* IS - insert character on sign */
fprint_value (ofile, operand, disp_radix, /* print the character count */
D8_WIDTH, PV_LEFT);
fputs (",\"", ofile); /* add a separator and open the quotation */
fputs (fmt_byte_operand (byte_address, operand), ofile); /* print the string operand */
fputs ("\",\"", ofile); /* close and open the second quotation */
fputs (fmt_byte_operand (byte_address + operand, operand), ofile); /* print the string operand */
fputc ('"', ofile); /* and close the quotation */
byte_count = byte_count + 2 * operand; /* count the bytes */
break;
case 017: /* TE - terminate edit */
case 020: /* ENDF - end floating point insertion */
case 021: /* SST1 - set significance to 1 */
case 022: /* SST0 - set significance to 0 */
case 023: /* MDWO - move digit with overpunch */
break; /* these opcodes have no operands */
case 024: /* SFC - set fill character */
GET_BYTE (byte); /* get the fill character */
byte_count = byte_count + 1; /* and count it */
fputs (fmt_char ((uint32) byte), ofile); /* print the fill character */
break;
case 025: /* SFLC - set float character */
GET_BYTE (byte); /* get the float character */
byte_count = byte_count + 1; /* and count it */
fputs (fmt_char ((uint32) (UPPER_HALF (byte) + ' ')), ofile); /* print the positive float character */
fputc (',', ofile); /* and a separator */
fputs (fmt_char ((uint32) (LOWER_HALF (byte) + ' ')), ofile); /* and the negative float character */
break;
case 026: /* DFLC - define float character */
GET_BYTE (byte); /* get the float character */
fputs (fmt_char ((uint32) byte), ofile); /* print the positive float character */
GET_BYTE (byte); /* get the float character */
byte_count = byte_count + 2; /* and count both characters */
fputc (',', ofile); /* print a separator */
fputs (fmt_char ((uint32) byte), ofile); /* and the negative float character */
break;
case 027: /* SETC - set loop count */
GET_BYTE (byte); /* get the loop count */
byte_count = byte_count + 1; /* and count it */
if (byte == 0) /* if the count is zero */
fprint_value (ofile, 256, disp_radix, /* then the loop executes 256 times */
D16_WIDTH, PV_LEFT);
else /* otherwise */
fprint_value (ofile, byte, disp_radix, /* print the given loop count */
D8_WIDTH, PV_LEFT);
break;
default: /* the opcode is invalid */
if (radix == 0) /* if the supplied radix is defaulted */
disp_radix = cpu_dev.dradix; /* then use the data radix */
else /* otherwise */
disp_radix = radix; /* use the radix given */
fprint_value (ofile, EDIT_EXOP, disp_radix, /* print the extended opcode */
D4_WIDTH, PV_RZRO);
fputc (',', ofile); /* print a separator */
fprint_value (ofile, opcode - EDIT_EXOP, /* and the invalid opcode in numeric form */
disp_radix, D4_WIDTH, PV_RZRO);
break;
}
return byte_count; /* return the number of bytes used by the operation */
}
/* Format the status register flags and condition code.
This routine formats the flags and condition code part of the status register
and returns a pointer to the formatted string. It does not format the
current code segment number part of the register.
The six status flags are represented by letters. If the flag is set, an
uppercase letter is used; if it is clear, a lowercase letter is used. The
condition code is represented by the strings "CCL", "CCE", or "CCG" for the
less than, equal to, or greater than conditions. If the condition code is
the invalid value, "CC?" is used.
*/
const char *fmt_status (uint32 status)
{
static const char conditions [] = "GLE?";
static const char flags [] = "m i t r o c CCx";
static char formatted [sizeof flags];
uint32 index;
strcpy (formatted, flags); /* copy the initial flags template */
formatted [14] = conditions [TO_CCN (status)]; /* set the condition code representation */
for (index = 0; index < 6 * 2; index = index + 2) { /* loop through the six MSBs (the flags) */
if (status & D16_SIGN) /* if the bit is set */
formatted [index] = /* then convert the corresponding flag */
(char) toupper (formatted [index]); /* to upper case */
status = status << 1; /* position the next flag for testing */
}
return formatted; /* return a pointer to the formatted string */
}
/* Format a character for printing.
This routine formats a single 8-bit character value into a printable string
and returns a pointer to that string. Printable characters retain their
original form but are enclosed in single quotes. Control characters are
translated to readable strings. Characters outside of the ASCII range are
presented as escaped octal values.
Implementation notes:
1. The longest string to be returned is a five-character escaped string
consisting of a backslash, three octal digits, and a trailing NUL. The
end-of-buffer pointer has an allowance to ensure that the string will
fit.
2. The routine returns a pointer to a static buffer containing the printable
string. To allow the routine to be called more than once per trace line,
the null-terminated format strings are concatenated in the buffer, and
each call returns a pointer that is offset into the buffer to point at
the latest formatted string.
3. There is no explicit buffer-free action. Instead, newly formatted
strings are appended to the buffer until there is no more space
available. At that point, the pointers are reset to the start of the
buffer. In effect, this provides a circular buffer, as previously
formatted strings are overwritten by subsequent calls.
4. The buffer is sized to hold the maximum number of concurrent strings
needed for a single trace line. If more concurrent strings are used, one
or more strings from the earliest calls will be overwritten.
*/
const char *fmt_char (uint32 charval)
{
static const char *const control [] = {
"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
"BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI",
"DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
"CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US"
};
static char fmt_buffer [64]; /* the return buffer */
static char *freeptr = fmt_buffer; /* pointer to the first free character in the buffer */
static char *endptr = fmt_buffer + sizeof fmt_buffer - 5; /* pointer to the end of the buffer (less allowance) */
const char *fmtptr;
if (charval <= '\037') /* if the value is an ASCII control character */
return control [charval]; /* then return a readable representation */
else if (charval == '\177') /* otherwise if the value is the delete character */
return "DEL"; /* then return a readable representation */
else {
if (freeptr > endptr) /* if there is not enough room left to append the string */
freeptr = fmt_buffer; /* then reset to point at the start of the buffer */
fmtptr = freeptr; /* initialize the return pointer */
*freeptr = '\0'; /* and the format accumulator */
if (charval > '\177') /* otherwise if the value is beyond the printable range */
freeptr = freeptr + sprintf (freeptr, "\\%03o", /* then format the value */
charval & D8_MASK); /* and update the free pointer */
else { /* otherwise it's a printable character */
*freeptr++ = '\''; /* so form a representation */
*freeptr++ = (char) charval; /* containing the character */
*freeptr++ = '\''; /* surrounded by single quotes */
*freeptr = '\0';
}
freeptr = freeptr + 1; /* advance past the NUL terminator */
return fmtptr; /* and return the formatted string */
}
}
/* Format a set of named bits.
This routine formats a set of up to 32 named bits into a printable string and
returns a pointer to that string. The names of the active bits are
concatenated and separated by vertical bars. For example:
SIO OK | ready | no error | unit 0
On entry, "bitset" is a value specifying the bits to format, and "bitfmt" is
a BITSET_FORMAT structure describing the format to use. The structure
contains a count and a pointer to an array of character strings specifying
the names of the valid bits in "bitset", the offset in bits from the LSB to
the least-significant named bit, the direction in which to process the bits
(from MSB to LSB, or vice versa), whether or not alternate names are present
in the name array, and whether or not to append a final separator. The names
in the name array appear in the order corresponding to the supplied
direction; invalid bits are indicated by NULL character names. The pointer
returned points at a character buffer containing the names of the valid bits
that are set in the supplied value. If no valid bits are set, then the
buffer contains "(none)" if a trailing separator is omitted, or a null string
("") if a trailing separator is requested.
The name_count and names fields describe the separately defined name string
array. The array must start with the first valid bit but need only contain
entries through the last valid bit; NULL entries for the remaining bits in
the word are not necessary. For example, if bits 1-3 of a word are valid,
then the name string array would have three entries. If bits 1-3 and 5 are
valid, then the array would have five entries, with the fourth entry set to
NULL.
The offset field specifies the number of unnamed bits to the right of the
least-significant named bit. Using the same examples as above, the offsets
would be 12 and 10, respectively.
The direction field specifies whether the bits are named from MSB to LSB
(msb_first) or vice versa (lsb_first). The order of the entries in the name
string array must match the direction specified. Continuing with the first
example above, if the direction is msb_first, then the first name is for bit
1; if the direction is lsb_first, then the first name is for bit 3.
The alternate field specifies whether (has_alt) or not (no_alt) alternate
conditions are represented by one or more bits. Generally, bits represent
Boolean conditions, e.g., a condition is present when the bit is 1 and absent
when the bit is zero. In these cases, the corresponding bit name is included
or omitted, respectively, in the return string.
Occasionally, bits will represent alternate conditions, e.g., where condition
A is present when the bit is 1, and condition B is present when the bit is 0.
For these, the bit name string should consist of both condition names in that
order, with the "1" name preceded by the '\1' character and the "0" name
preceded by the '\0' character. For example, if 1 corresponds to "load" and
0 to "store", then the bit name string would be "\1load\0store". If
alternate names are present, the has_alt identifier should be given, so that
the indicated bits are checked for zero conditions. If no_alt is specified,
the routine stops as soon as all of the one-bits have been processed.
The bar field specifies whether (append_bar) or not (no_bar) a vertical bar
separator is appended to the formatted string. Typically, a bitset
represents a peripheral control or status word. If the word also contains
multiple-bit fields, a trailing separator should be requested, and the
decoded fields should be concatenated by the caller with any named bits. If
the bitset is empty, the returned null string will present the proper display
containing just the decoded fields. If the bitset completely describes the
word, then no appended separator is needed.
Peripheral control and status words generally are decoded from MSB to LSB. A
bitset may also represent a set of inbound or outbound signals. These should
be decoded from LSB to MSB, as that is the order in which they are executed
by the device interface routines.
The implementation first generates a mask for the significant bits and
positions the mask with the offset specified. Then a test bit mask is
generated; the bit is either the most- or least-significant bit of the
bitset, depending on the direction indicated.
For each name in the array of names, if the name is defined (not NULL), the
corresponding bit in the bitset is tested. If it is set, the name is
appended to the output buffer; otherwise, it is omitted (unless the name has
an alternate, in which case the alternate is appended). The bitset is then
shifted in the indicated direction, remasking to just the significant bits.
Processing continues until there are no remaining significant bits (if no
alternates are specified), or until there are no remaining names in the array
(if alternates are specified).
Implementation notes:
1. The routine returns a pointer to a static buffer containing the printable
string. To allow the routine to be called more than once per trace line,
the null-terminated format strings are concatenated in the buffer, and
each call returns a pointer that is offset into the buffer to point at
the latest formatted string.
2. There is no explicit buffer-free action. Instead, newly formatted
strings are appended to the buffer until there is no more space
available. At that point, the string currently being assembled is moved
to the start of the buffer, and the pointers are reset. In effect, this
provides a circular buffer, as previously formatted strings are
overwritten by subsequent calls.
3. The buffer is sized to hold the maximum number of concurrent strings
needed for a single trace line. If more concurrent strings are used, one
or more strings from the earliest calls will be overwritten. If an
attempt is made to format a string larger than the buffer, an error
indication string is returned.
4. The location of the end of the buffer used to determine if the next name
will fit includes an allowance for two separators that might be placed on
either side of the name and a terminating NUL character.
*/
const char *fmt_bitset (uint32 bitset, const BITSET_FORMAT bitfmt)
{
static const char separator [] = " | "; /* the separator to use between names */
static char fmt_buffer [1024]; /* the return buffer */
static char *freeptr = fmt_buffer; /* pointer to the first free character in the buffer */
static char *endptr = fmt_buffer + sizeof fmt_buffer /* pointer to the end of the buffer */
- 2 * (sizeof separator - 1) - 1; /* less allowance for two separators and a terminator */
const char *bnptr, *fmtptr;
uint32 test_bit, index, bitmask;
size_t name_length;
if (bitfmt.name_count < D32_WIDTH) /* if the name count is the less than the mask width */
bitmask = (1 << bitfmt.name_count) - 1; /* then create a mask for the name count specified */
else /* otherwise use a predefined value for the mask */
bitmask = D32_MASK; /* to prevent shifting the bit off the MSB end */
bitmask = bitmask << bitfmt.offset; /* align the mask to the named bits */
bitset = bitset & bitmask; /* and mask to just the significant bits */
if (bitfmt.direction == msb_first) /* if the examination is left-to-right */
test_bit = 1 << bitfmt.name_count + bitfmt.offset - 1; /* then create a test bit for the MSB */
else /* otherwise */
test_bit = 1 << bitfmt.offset; /* create a test bit for the LSB */
fmtptr = freeptr; /* initialize the return pointer */
*freeptr = '\0'; /* and the format accumulator */
index = 0; /* and the name index */
while ((bitfmt.alternate || bitset) /* while more bits */
&& index < bitfmt.name_count) { /* and more names exist */
bnptr = bitfmt.names [index]; /* point at the name for the current bit */
if (bnptr) /* if the name is defined */
if (*bnptr == '\1' && bitfmt.alternate) /* then if this name has an alternate */
if (bitset & test_bit) /* then if the bit is asserted */
bnptr++; /* then point at the name for the "1" state */
else /* otherwise */
bnptr = bnptr + strlen (bnptr) + 1; /* point at the name for the "0" state */
else /* otherwise the name is unilateral */
if ((bitset & test_bit) == 0) /* so if the bit is denied */
bnptr = NULL; /* then clear the name pointer */
if (bnptr) { /* if the name pointer is set */
name_length = strlen (bnptr); /* then get the length needed */
if (freeptr + name_length > endptr) { /* if there is not enough room left to append the name */
strcpy (fmt_buffer, fmtptr); /* then move the partial string to the start of the buffer */
freeptr = fmt_buffer + (freeptr - fmtptr); /* point at the new first free character location */
fmtptr = fmt_buffer; /* and reset the return pointer */
if (freeptr + name_length > endptr) /* if there is still not enough room left to append */
return "(buffer overflow)"; /* then this call is requires a larger buffer! */
}
if (*fmtptr != '\0') { /* if this is not the first name added */
strcpy (freeptr, separator); /* then add a separator to the string */
freeptr = freeptr + strlen (separator); /* and move the free pointer */
}
strcpy (freeptr, bnptr); /* append the bit's mnemonic to the accumulator */
freeptr = freeptr + name_length; /* and move the free pointer */
}
if (bitfmt.direction == msb_first) /* if formatting is left-to-right */
bitset = bitset << 1 & bitmask; /* then shift the next bit to the MSB and remask */
else /* otherwise formatting is right-to-left */
bitset = bitset >> 1 & bitmask; /* so shift the next bit to the LSB and remask */
index = index + 1; /* bump the bit name index */
}
if (*fmtptr == '\0') /* if no names were output */
if (bitfmt.bar == append_bar) /* then if concatenating with more information */
return ""; /* then return an empty string */
else /* otherwise it's a standalone format */
return "(none)"; /* so return a placeholder */
else if (bitfmt.bar == append_bar) { /* otherwise if a trailing separator is specified */
strcpy (freeptr, separator); /* then add a separator to the string */
freeptr = freeptr + strlen (separator) + 1; /* and account for it plus the trailing NUL */
}
else /* otherwise */
freeptr = freeptr + 1; /* just account for the trailing NUL */
return fmtptr; /* return a pointer to the formatted string */
}
/* Format and print a debugging trace line to the debug log.
A formatted line is assembled and sent to the previously opened debug output
stream. On entry, "dptr" points to the device issuing the trace, "flag" is
the debug flag that has enabled the trace, and the remaining parameters
consist of the format string and associated values.
This routine is usually not called directly but rather via the "dprintf"
macro, which tests that debugging is enabled for the specified flag before
calling this function. This eliminates the calling overhead if debugging is
disabled.
This routine prints a prefix before the supplied format string consisting of
the device name (in upper case) and the debug flag name (in lower case),
e.g.:
>>MPX state: Channel SR 3 entered State A
~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
prefix supplied format string
The names are padded to the lengths of the largest device name and debug flag
name among the devices enabled for debugging to ensure that all trace lines
will align for easier reading.
Implementation notes:
1. ISO C99 allows assignment expressions as the bounds for array
declarators. VC++ 2008 requires constant expressions. To accommodate
the latter, we must allocate "sufficiently large" arrays for the flag
name and format, rather than arrays of the exact size required by the
call parameters.
*/
#define FLAG_SIZE 32 /* sufficiently large to accommodate all flag names */
#define FORMAT_SIZE 1024 /* sufficiently large to accommodate all format strings */
void hp_debug (DEVICE *dptr, uint32 flag, ...)
{
va_list argptr;
DEBTAB *debptr;
char *format, *fptr;
const char *nptr;
char flag_name [FLAG_SIZE]; /* desired size is [flag_size + 1] */
char header_fmt [FORMAT_SIZE]; /* desired size is [device_size + flag_size + format_size + 6] */
if (sim_deb != NULL && dptr != NULL) { /* if the output stream and device pointer are valid */
debptr = dptr->debflags; /* then get a pointer to the debug flags table */
if (debptr != NULL) /* if the debug table exists */
while (debptr->name != NULL) /* then search it for an entry with the supplied flag */
if (debptr->mask & flag) { /* if the flag matches this entry */
nptr = debptr->name; /* then get a pointer to the flag name */
fptr = flag_name; /* and the buffer */
do
*fptr++ = (char) tolower (*nptr); /* copy and downshift the flag name */
while (*nptr++ != '\0');
sprintf (header_fmt, ">>%-*s %*s: ", /* format the prefix and store it */
(int) device_size, sim_dname (dptr), /* while padding the device and flag names */
(int) flag_size, flag_name); /* as needed for proper alignment */
va_start (argptr, flag); /* set up the argument list */
format = va_arg (argptr, char *); /* get the format string parameter */
strcat (header_fmt, format); /* append the supplied format */
vfprintf (sim_deb, header_fmt, argptr); /* format and print to the debug stream */
va_end (argptr); /* clean up the argument list */
break; /* and exit with the job complete */
}
else /* otherwise */
debptr++; /* look at the next debug table entry */
}
return;
}
/* Check for device conflicts.
The device information blocks (DIBs) for the set of enabled devices are
checked for consistency. Each device number, interrupt priority number, and
service request number must be unique among the enabled devices. These
requirements are checked as part of the instruction execution prelude; this
allows the user to exchange two device numbers (e.g.) simply by setting each
device to the other's device number. If conflicts were enforced instead at
the time the numbers were entered, the first device would have to be set to
an unused number before the second could be set to the first device's number.
The routine begins by filling in a DIB value table from all of the device
DIBs to allow indexed access to the values to be checked. Unused DIB values
and values corresponding to devices that have no DIBs or are disabled are set
to the corresponding UNUSED constants.
As part of the device scan, the sizes of the largest device name and active
debug flag name among the devices enabled for debugging are accumulated for
use in aligning the debug tracing statements.
After the DIB value table is filled in, a conflict check is made for each
conflict type (i.e., device number, interrupt priority, or service request
number). For each check, a conflict table is built, where each array element
is set to the count of devices that contain DIB values equal to the element
index. For example, when processing device number values, conflict table
element 6 is set to the count of devices that have dibptr->device_number set
to 6. If any conflict table element is set more than once, the "conflict_is"
variable is set to the type of conflict.
If any conflicts exist for the current type, the conflict table is scanned.
A conflict table element value (i.e., device count) greater than 1 indicates
a conflict. For each such value, the DIB value table is scanned to find
matching values, and the device names associated with the matching values are
printed.
This routine returns TRUE if any conflicts exist and FALSE there are none.
Implementation notes:
1. When this routine is called, the console and optional log file have
already been put into "raw" output mode. Therefore, newlines are not
translated to the correct line ends on systems that require it. Before
reporting a conflict, "sim_ttcmd" is called to restore the console and
log file translation. This is OK because a conflict will abort the run
and return to the command line anyway.
2. sim_dname is called instead of using dptr->name directly to ensure that
we pick up an assigned logical device name.
3. Only the names of active trace (debug) options are accumulated to produce
the most compact trace log. However, if the CPU device's EXEC option is
enabled, then all of the CPU option names are accumulated, as EXEC
enables all trace options for a given instruction or instruction class.
4. Even though the routine is called only from the sim_instr routine in the
CPU simulator module, it must be located here to use the DEVICE_COUNT
constant to allocate the dib_val matrix. If it were located in the CPU
module, the matrix would have to be allocated dynamically after a
run-time determination of the count of simulator devices.
*/
t_bool hp_device_conflict (void)
{
#define CONFLICT_COUNT 3 /* the number of conflict types to check */
typedef enum { /* conflict types */
Device, /* device number conflict */
Interrupt, /* interrupt priority conflict */
Service, /* service request number conflict */
None /* no conflict */
} CONFLICT_TYPE;
static const uint32 max_number [CONFLICT_COUNT] = { /* the last element index, in CONFLICT_TYPE order */
DEVNO_MAX,
INTPRI_MAX,
SRNO_MAX
};
static const char *conflict_label [CONFLICT_COUNT] = { /* the conflict names, in CONFLICT_TYPE order */
"Device number",
"Interrupt priority",
"Service request number"
};
const DIB *dibptr;
const DEBTAB *tptr;
DEVICE *dptr;
size_t name_length, flag_length;
uint32 dev, val;
CONFLICT_TYPE conf, conflict_is;
int32 count;
int32 dib_val [DEVICE_COUNT] [CONFLICT_COUNT];
int32 conflicts [DEVNO_MAX + 1];
device_size = 0; /* reset the device and flag name sizes */
flag_size = 0; /* to those of the devices actively debugging */
for (dev = 0; dev < DEVICE_COUNT; dev++) { /* fill in the DIB value table */
dptr = (DEVICE *) sim_devices [dev]; /* from the device table */
dibptr = (DIB *) dptr->ctxt; /* and their associated DIBs */
if (dibptr && !(dptr->flags & DEV_DIS)) { /* if the DIB is defined and the device is enabled */
dib_val [dev] [Device] = dibptr->device_number; /* then copy the values to the DIB table */
dib_val [dev] [Interrupt] = dibptr->interrupt_priority;
dib_val [dev] [Service] = dibptr->service_request_number;
}
else { /* otherwise the device will not participate in I/O */
dib_val [dev] [Device] = DEVNO_UNUSED; /* so set this table entry */
dib_val [dev] [Interrupt] = INTPRI_UNUSED; /* to the "unused" values */
dib_val [dev] [Service] = SRNO_UNUSED;
}
if (sim_deb && dptr->dctrl) { /* if debugging is active for this device */
name_length = strlen (sim_dname (dptr)); /* then get the length of the device name */
if (name_length > device_size) /* if it's greater than the current maximum */
device_size = name_length; /* then reset the size */
if (dptr->debflags) /* if the device has a debug flags table */
for (tptr = dptr->debflags; /* then scan the table */
tptr->name != NULL; tptr++)
if (dev == 0 && dptr->dctrl & DEB_EXEC /* if the CPU device is tracing executions */
|| tptr->mask & dptr->dctrl) { /* or this debug option is active */
flag_length = strlen (tptr->name); /* then get the flag name length */
if (flag_length > flag_size) /* if it's greater than the current maximum */
flag_size = flag_length; /* then reset the size */
}
}
}
conflict_is = None; /* assume that no conflicts exist */
for (conf = Device; conf <= Service; conf++) { /* check for conflicts for each type */
memset (conflicts, 0, sizeof conflicts); /* zero the conflict table for each check */
for (dev = 0; dev < DEVICE_COUNT; dev++) /* populate the conflict table from the DIB value table */
if (dib_val [dev] [conf] >= 0) /* if this device has an assigned value */
if (++conflicts [dib_val [dev] [conf]] > 1) /* then increment the count of references */
conflict_is = conf; /* if there is more than one reference, a conflict occurs */
if (conflict_is == conf) { /* if a conflict exists for this type */
sim_ttcmd (); /* then restore the console and log I/O mode */
for (val = 0; val <= max_number [conf]; val++) /* search the conflict table for the next conflict */
if (conflicts [val] > 1) { /* if a conflict is present for this value */
count = conflicts [val]; /* then get the number of conflicting devices */
cprintf ("%s %u conflict (", conflict_label [conf], val);
dev = 0; /* search for the devices that conflict */
while (count > 0) { /* search the DIB value table */
if (dib_val [dev] [conf] == (int32) val) { /* to find the conflicting entries */
if (count < conflicts [val]) /* and report them to the console */
cputs (" and ");
cputs (sim_dname ((DEVICE *) sim_devices [dev]));
count = count - 1;
}
dev = dev + 1;
}
cputs (")\n");
}
}
}
return (conflict_is != None); /* return TRUE if any conflicts exist */
}
/* System interface local SCP support routines */
/* One-time initialization.
This routine is called once by the SCP startup code. It fills in the
auxiliary command table from the corresponding system command table entries,
sets up the VM-specific routine pointers, and registers the supported
breakpoint types.
*/
static void one_time_init (void)
{
CTAB *contab, *systab, *auxtab = aux_cmds;
contab = find_cmd ("CONT"); /* find the entry for the CONTINUE command */
while (auxtab->name != NULL) { /* loop through the auxiliary command table */
systab = find_cmd (auxtab->name); /* find the corresponding system command table entry */
if (systab != NULL) { /* if it is present */
if (auxtab->action == NULL) /* then if the action routine field is empty */
auxtab->action = systab->action; /* then fill it in */
if (auxtab->arg == 0) /* if the command argument field is empty */
auxtab->arg = systab->arg; /* then fill it in */
if (auxtab->help == NULL) /* if the help string field is empty */
auxtab->help = systab->help; /* then fill it in */
auxtab->help_base = systab->help_base; /* fill in the help base and message fields */
auxtab->message = systab->message; /* as we never override them */
}
if (auxtab->action == &cpu_cold_cmd /* if this is the LOAD or DUMP command entry */
|| auxtab->action == &cpu_power_cmd) /* or the POWER command entry */
auxtab->message = contab->message; /* then set the execution completion message routine */
auxtab++; /* point at the next table entry */
}
sim_vm_cmd = aux_cmds; /* set up the auxiliary command table */
sim_vm_fprint_stopped = &fprint_stopped; /* set up the simulation-stop printer */
sim_vm_fprint_addr = &fprint_addr; /* set up the address printer */
sim_vm_parse_addr = &parse_addr; /* set up the address parser */
sim_brk_types = BP_SUPPORTED; /* register the supported breakpoint types */
sim_brk_dflt = BP_EXEC; /* the default breakpoint type is "execution" */
return;
}
/* Format and print a VM simulation stop message.
When the instruction loop is exited, a simulation stop message is printed and
control returns to SCP. An SCP stop prints a message with this format:
<reason>, P: <addr> (<inst>)
For example:
SCPE_STOP prints "Simulation stopped, P: 24713 (LOAD 1)"
SCPE_STEP prints "Step expired, P: 24713 (LOAD 1)"
For VM stops, this routine is called after the message has been printed and
before the comma and program counter label and value are printed. Depending
on the reason for the stop, the routine may insert additional information,
and it may request omission of the PC value by returning FALSE instead of
TRUE.
This routine modifies the default output for these stop codes:
STOP_SYSHALT prints "System halt 3, P: 24713 (LOAD 1)"
STOP_HALT prints "Programmed halt, CIR: 030365 (HALT 5), P: 24713 (LOAD 1)"
STOP_CDUMP prints "Cold dump complete, CIR: 000020"
Implementation notes:
1. HALT instructions are always one word in length, so only sim_eval [0]
needs to be set up before calling fprint_cpu.
2. The system halt reason is present in RA.
*/
static t_bool fprint_stopped (FILE *st, t_stat reason)
{
if (reason == STOP_HALT) { /* if this is a halt instruction stop */
sim_eval [0] = CIR; /* then save the instruction for evaluation */
fputs (", CIR: ", st); /* print the register label */
fprint_val (st, CIR, cpu_dev.dradix, /* and the numeric value */
cpu_dev.dwidth, PV_RZRO);
fputs (" (", st); /* print the halt mnemonic */
fprint_cpu (st, sim_eval, 0, SIM_SW_STOP); /* (which cannot fail) */
fputc (')', st); /* within parentheses */
return TRUE; /* return TRUE to append the program counter */
}
else if (reason == STOP_CDUMP) { /* otherwise if this is a cold dump completion stop */
fputs (", CIR: ", st); /* then print the register label */
fprint_val (st, CIR, cpu_dev.dradix, /* and the numeric value */
cpu_dev.dwidth, PV_RZRO);
fputc ('\n', st); /* append an end-of-line character */
return FALSE; /* and return FALSE to omit the program counter */
}
else if (reason == STOP_SYSHALT) { /* otherwise if this is a system halt stop */
fprintf (st, " %u", RA); /* then print the halt reason */
return TRUE; /* and return TRUE to append the program counter */
}
else /* otherwise all other stops */
return TRUE; /* return TRUE to append the program counter */
}
/* Format and print a memory address.
This routine is called by SCP to print memory addresses. It is also called
to print the contents of registers tagged with the REG_VMAD flag.
On entry, the "st" parameter is the opened output stream, "dptr" points to
the device to which the address refers, and "addr" contains the address to
print. The routine prints the linear address in <bank>.<offset> form for the
CPU and as a scalar value for all other devices.
*/
static void fprint_addr (FILE *st, DEVICE *dptr, t_addr addr)
{
uint32 bank, offset;
if (dptr == &cpu_dev) { /* if the address originates in the CPU */
bank = TO_BANK (addr); /* then separate bank and offset */
offset = TO_OFFSET (addr); /* from the linear address */
fprint_val (st, bank, dptr->aradix, BA_WIDTH, PV_RZRO); /* print the bank address */
fputc ('.', st); /* followed by a period */
fprint_val (st, offset, dptr->aradix, LA_WIDTH, PV_RZRO); /* and concluding with the offset */
}
else /* otherwise print the value */
fprint_val (st, addr, dptr->aradix, dptr->awidth, PV_LEFT); /* as a scalar for all other devices */
return;
}
/* Parse a memory address.
This routine is called by SCP to parse memory addresses. It is also called
to parse values to be stored in registers tagged with the REG_VMAD flag.
On entry, the "dptr" parameter points to the device to which the address
refers, and "cptr" points to the first character of the address operand on
the command line. On exit, the linear address is returned, and the pointer
pointed to by "tptr" is set to point at the first character after the parsed
address. Parsing errors, including use of features disallowed by the command
in process, are indicated by the "tptr" pointer being set to "cptr".
The HP 3000 divides memory into 64K-word banks. Each bank is identified by a
bank address from 0-15. The current bank addresses for the program, data,
and stack segments are kept in the PBANK, DBANK, and SBANK registers.
The simulator supports only linear addresses for all devices other than the
CPU. For the CPU, two forms of address entries are allowed:
- an absolute address consisting of a 4-bit bank address and a 16-bit
offset within the bank, separated by a period (e.g., 17.177777)
- a relative address consisting of a 16-bit offset within a bank specified
by a bank register (e.g., 177777).
Command line switches modify the interpretation of relative addresses as
follows:
* -P specifies an implied bank address obtained from PBANK
* -S specifies an implied bank address obtained from SBANK
* no switch specifies an implied bank address obtained from DBANK
The "parse_config" global specifies the allowed parse configurations. For
example, the memory examine/deposit commands allow both absolute addresses
and offsets from any of the three bank registers, whereas the run command
only allows an implied offset from PBANK.
*/
static t_addr parse_addr (DEVICE *dptr, CONST char *cptr, CONST char **tptr)
{
CONST char *sptr;
uint32 overrides;
t_addr bank;
t_addr address = 0;
if (dptr != &cpu_dev) /* if this is not a CPU memory address */
return (t_addr) strtotv (cptr, tptr, dptr->aradix); /* then parse a scalar and return the value */
overrides = sim_switches & (SWMASK ('P') | SWMASK ('S')); /* mask to just the bank address overrides */
if (overrides && !(parse_config & apcBank_Override) /* if overrides are present but not allowed */
|| overrides & ~SWMASK ('P') && overrides & ~SWMASK ('S')) /* or multiple overrides are specified */
*tptr = cptr; /* then report a parse error */
else /* otherwise the switches are consistent */
address = strtotv (cptr, tptr, dptr->aradix); /* so parse the address */
if (cptr != *tptr) /* if the parse succeeded */
if (**tptr == '.') /* then if this a banked address */
if (! (parse_config & apcBank_Offset) /* but it is not allowed */
|| address > BA_MAX) /* or the bank number is out of range */
*tptr = cptr; /* then report a parse error */
else { /* otherwise the <bank>.<offset> form is allowed */
sptr = *tptr + 1; /* point to the offset */
bank = address; /* save the first part as the bank address */
address = strtotv (sptr, tptr, dptr->aradix); /* parse the offset */
if (address > LA_MAX) /* if the offset is too large */
*tptr = cptr; /* then report a parse error */
else /* otherwise it is in range */
address = TO_PA (bank, address); /* so form the linear address */
}
else if (address > LA_MAX) /* otherwise if the non-banked offset is too large */
*tptr = cptr; /* then report a parse error */
else if (overrides & SWMASK ('S')) /* otherwise if the stack-bank override is specified */
address = TO_PA (SBANK, address); /* then base the address on SBANK */
else if (overrides & SWMASK ('P')) /* otherwise if the program-bank override is specified */
address = TO_PA (PBANK, address); /* then base the address on PBANK */
else if (parse_config & apcDefault_PBANK) /* otherwise if PBANK is the default */
if (PB <= address && address <= PL) /* then if the address lies within the segment limits */
address = TO_PA (PBANK, address); /* then base the address on PBANK */
else /* otherwise it is outside of the segment */
*tptr = cptr; /* so report a parse error */
else if (parse_config & apcDefault_DBANK) /* otherwise if the default is DBANK */
address = TO_PA (DBANK, address); /* then base the address on DBANK */
return address; /* return the linear address */
}
/* Execute the EXAMINE, DEPOSIT, IEXAMINE, and IDEPOSIT commands.
These commands are intercepted to configure address parsing. The following
address forms are valid:
EXAMINE <bank>.<offset>
EXAMINE <dbank-offset>
EXAMINE -P <pbank-offset>
EXAMINE -S <sbank-offset>
This routine configures the address parser and calls the standard command
handler.
*/
static t_stat hp_exdep_cmd (int32 arg, CONST char *buf)
{
parse_config = apcBank_Offset | /* allow the <bank>.<offset> address form */
apcBank_Override | /* allow bank override switches */
apcDefault_DBANK; /* set the default bank register to DBANK */
return exdep_cmd (arg, buf); /* return the result of the standard handler */
}
/* Execute the RUN and GO commands.
These commands are intercepted to configure address parsing. The following
address form is valid:
RUN { <pbank-offset> }
GO { <pbank-offset> }
This routine configures the address parser and calls the standard command
handler. The <pbank-offset>, if specified, must lie between PB and PL, or
the command will be rejected when the offset is parsed.
Implementation notes:
1. The RUN command uses the RU_GO argument instead of RU_RUN so that the
run_cmd SCP routine will not reset all devices before entering the
instruction executor. As is done in hardware, resetting the CPU clears
the ICS flag, which corrupts the CPU state set up after a cold load. A
CPU reset is only valid prior to a cold load -- never when a program is
resident in memory.
*/
static t_stat hp_run_cmd (int32 arg, CONST char *buf)
{
parse_config = apcDefault_PBANK; /* set the default bank register to PBANK */
cpu_front_panel (SWCH, Run); /* set up run request */
return run_cmd (RU_GO, buf); /* return the result of the standard handler */
}
/* Execute the BREAK and NOBREAK commands.
These commands are intercepted to configure address parsing. The following
address forms are valid:
BREAK
BREAK <bank>.<offset>
BREAK <pbank-offset>
If no argument is specified, the breakpoint address defaults to the current
values of PBANK and P. The standard command handler will accommodate this,
but only if the program counter contains a physical address. Therefore, for
the duration of the call, the SCP pointer to the P register structure is
changed to point at a temporary register structure that contains the physical
address.
The <pbank-offset>, if specified, must lie between PB and PL, or the command
will be rejected by the parse_addr routine when it is called by the brk_cmd
routine to parse the offset.
*/
static t_stat hp_brk_cmd (int32 arg, CONST char *buf)
{
static uint32 PC;
static REG PR = { ORDATA (PP, PC, 32) };
REG *save_PC;
t_stat status;
save_PC = sim_PC; /* temporarily change the P-register pointer */
sim_PC = & PR; /* to point at a structure holding the physical address */
PC = TO_PA (PBANK, P); /* set the physical address from the program counter */
parse_config = apcBank_Offset | apcDefault_PBANK; /* allow the <bank>.<offset> form with a PBANK default */
status = brk_cmd (arg, buf); /* call the standard breakpoint command handler */
sim_PC = save_PC; /* restore the P-register pointer */
return status; /* return the handler status */
}
/* System interface local utility routines */
/* Print a numeric value in a given radix with a radix identifier.
This routine prints a numeric value using the specified radix, width, and
output format. If the radix is 256, then the value is printed as a single
character. Otherwise, it is printed as a numeric value with a leading radix
indicator if the specified print radix is not the same as the current CPU
data radix. It uses the HP 3000 convention of a leading "%", "#", or "!"
character to indicate an octal, decimal, or hexadecimal number (there is no
binary convention, so a leading "@" is used arbitrarily).
On entry, the "ofile" parameter is the opened output stream, "val" is the
value to print, "radix" is the desired print radix, "width" is the number of
significant bits in the value, and "format" is a format specifier (PV_RZRO,
PV_RSPC, or PV_LEFT). On exit, the routine returns SCPE_OK if the value was
printed successfully, or SCPE_ARG if the value could not be printed.
*/
static t_stat fprint_value (FILE *ofile, t_value val, uint32 radix, uint32 width, uint32 format)
{
if (radix == 256) /* if ASCII character display is requested */
if (val <= D8_SMAX) { /* then if the value is a single character */
fputs (fmt_char ((uint32) val), ofile); /* then format and print it */
return SCPE_OK; /* and report success */
}
else /* otherwise */
return SCPE_ARG; /* report that it cannot be displayed */
else if (radix != cpu_dev.dradix) /* otherwise if the requested radix is not the current data radix */
if (radix == 2) /* then if the requested radix is binary */
fputc ('@', ofile); /* then print the binary indicator */
else if (radix == 8) /* otherwise if the requested radix is octal */
fputc ('%', ofile); /* then print the octal indicator */
else if (radix == 10) /* otherwise if it is decimal */
fputc ('#', ofile); /* then print the decimal indicator */
else if (radix == 16) /* otherwise if it is hexadecimal */
fputc ('!', ofile); /* then print the hexadecimal indicator */
else /* otherwise it must be some other radix */
fputc ('?', ofile); /* with no defined indicator */
fprint_val (ofile, val, radix, width, format); /* print the value in the radix specified */
return SCPE_OK; /* return success */
}
/* Print an I/O program instruction in symbolic format.
This routine prints a pair of data words as an I/O channel order and the
associated operand(s) on the output stream supplied.
On entry, the "ofile" parameter is the opened output stream, "val [0]"
contains the I/O Control Word, "val [1]" contains the I/O Address Word, and
"radix" contains the desired operand radix or zero if the default radix is to
be used. The control and address words are decoded as follows:
IOCW IOCW IOAW
0 1 2 3 4-15 0-15 Action
------- -------------- -------------- ---------------------
0 0 0 0 0 XXXXXXXXXXX Jump Address Unconditional Jump
0 0 0 0 1 XXXXXXXXXXX Jump Address Conditional Jump
0 0 0 1 0 XXXXXXXXXXX Residue Count Return Residue
0 0 0 1 1 XXXXXXXXXXX Bank Address Set Bank
0 0 1 0 X XXXXXXXXXXX (don't care) Interrupt
0 0 1 1 0 XXXXXXXXXXX Status Value End
0 0 1 1 1 XXXXXXXXXXX Status Value End with Interrupt
0 1 0 0 Control Word 1 Control Word 2 Control
0 1 0 1 X XXXXXXXXXXX Status Value Sense
C 1 1 0 Neg Word Count Write Address Write
C 1 1 1 Neg Word Count Read Address Read
Operand values are printed in a radix suitable to the type of the value, as
follows:
- Address values are printed in the CPU's address radix, which is octal.
- Counts are printed by default in decimal.
- Control and status values are printed in the CPU's data radix, which
defaults to octal.
The radix for operand values other than addresses may be overridden by a
switch on the command line. A value printed in a radix other than the
current data radix is preceded by a radix identifier ("@" for binary, "%" for
octal, "#" for decimal, or "!" for hexadecimal).
The routine returns SCPE_OK_2_WORDS to indicate that two words were consumed.
Implementation notes:
1. The Return Residue and Read/Write count values are printed as positive
numbers, even though the values in memory are negative.
*/
static const char *const order_names [] = { /* indexed by SIO_ORDER */
"JUMP ", /* sioJUMP -- Jump unconditionally */
"JUMPC ", /* sioJUMPC -- Jump conditionally */
"RTNRES ", /* sioRTRES -- Return residue */
"SETBNK ", /* sioSBANK -- Set bank */
"INTRPT", /* sioINTRP -- Interrupt */
"END ", /* sioEND -- End */
"ENDINT ", /* sioENDIN -- End with interrupt */
"CONTRL ", /* sioCNTL -- Control */
"SENSE ", /* sioSENSE -- Sense */
"WRITE ", /* sioWRITE -- Write */
"WRITEC ", /* sioWRITEC -- Write (chained) */
"READ ", /* sioREAD -- Read */
"READC " /* sioREADC -- Read (chained) */
};
static t_stat fprint_order (FILE *ofile, t_value *val, uint32 radix)
{
t_value iocw, ioaw;
SIO_ORDER order;
iocw = val [0]; /* get the I/O control word */
ioaw = val [1]; /* and I/O address word */
order = IOCW_ORDER (iocw); /* get the SIO I/O order from the IOCW */
fputs (order_names [order], ofile); /* print the I/O order mnemonic */
switch (order) { /* dispatch operand printing based on the order */
case sioJUMP:
case sioJUMPC: /* print the jump target address */
fprint_value (ofile, ioaw, cpu_dev.aradix, /* in the CPU's address radix */
LA_WIDTH, PV_RZRO);
break;
case sioRTRES: /* print the residue count */
fprint_value (ofile, NEG16 (ioaw),
(radix ? radix : 10),
DV_WIDTH, PV_LEFT);
break;
case sioSBANK: /* print the bank address */
fprint_value (ofile, ioaw & BA_MASK, /* in the CPU's address radix */
cpu_dev.aradix, BA_WIDTH, PV_RZRO);
break;
case sioINTRP: /* no operand to print */
break;
case sioEND:
case sioENDIN:
case sioSENSE: /* print the status value */
fprint_value (ofile, ioaw,
(radix ? radix : cpu_dev.dradix),
DV_WIDTH, PV_RZRO);
break;
case sioCNTL: /* print control words 1 and 2 */
fprint_value (ofile, IOCW_CNTL (iocw),
(radix ? radix : cpu_dev.dradix),
DV_WIDTH, PV_RZRO);
fputc (',', ofile);
fprint_value (ofile, ioaw,
(radix ? radix : cpu_dev.dradix),
DV_WIDTH, PV_RZRO);
break;
case sioWRITE:
case sioWRITEC:
case sioREAD:
case sioREADC: /* print the count and address */
fprint_value (ofile, NEG16 (IOCW_COUNT (iocw)),
(radix ? radix : 10), DV_WIDTH, PV_LEFT);
fputc (',', ofile);
fprint_value (ofile, ioaw, cpu_dev.aradix, /* prnit the address in the CPU's address radix */
LA_WIDTH, PV_RZRO);
break;
}
return SCPE_OK_2_WORDS; /* indicate that each instruction uses one extra word */
}
/* Format and print an EDIT instruction subprogram operation in symbolic format.
This routine formats and prints a set of memory bytes as an EDIT subprogram
operation. It is called when the "-E" command-line switch is included with a
memory display request.
On entry, the "ofile" parameter is the opened output stream, "val [*]"
contains the first two words of the operation, "radix" contains the desired
operand radix or zero if the default radix is to be used, "addr" points at
the memory word containing the first subprogram operation byte, and
"switches" contains the set of command-line switches supplied with the
command. The return value is one more than the negative number of memory
words consumed by the print request (e.g., a one-word consumption returns
zero, a two-word consumption returns -1, etc.).
The routine is called to print a single memory word. If the EXAMINE command
is given a memory range, the routine will be called multiple times, with
"val" pointing at an array containing the first two words of the range.
An operation may encompass from 1-257 bytes. The operation is assumed to
begin in the upper byte of the first memory word unless the "-R" switch is
supplied, in which case the lower byte is assumed. If "-R" is not used, and
the operation in the upper byte consumes only a single byte, the operation
(beginning) in the lower byte is also printed.
Full operations are always printed, so a single call on the routine prints
one or two operations and then returns the number of memory words consumed
(biased by 1). If a range is being printed, this value determines the
address presented at the following call.
Before returning, the "R" switch is set or cleared to indicate whether the
next call in a range should start with the lower or upper byte of the word.
Implementation notes:
1. Within a single command, this routine may be called (via fprint_sym)
successively for the same address. This will occur if console logging is
enabled. The first call will be to write to the console, and then it
will be called again to write to the log file. However, sim_switches is
updated after the first call in anticipation of additional calls for the
subsequent addresses in a range. To avoid this, sim_switches is tested
only if the routine is NOT called for the log file; if it is, then the
previous setting of sim_switches is used.
*/
static t_stat fprint_subop (FILE *ofile, t_value *val, uint32 radix, t_addr addr, int32 switches)
{
static uint32 odd_byte = 0;
uint32 byte_addr, bytes_used, total_bytes_used;
if (ofile != sim_log) /* if this is not a logging call */
odd_byte = (uint32) ((switches & SWMASK ('R')) != 0); /* then recalculate the odd-byte flag */
total_bytes_used = odd_byte; /* initialize the bytes used accumulator */
byte_addr = (uint32) addr * 2 + odd_byte; /* form the initial byte address */
do {
bytes_used = fprint_edit (ofile, val, radix, byte_addr); /* format and print an operation */
byte_addr = byte_addr + bytes_used; /* point at the next operation */
total_bytes_used = total_bytes_used + bytes_used; /* and add the byte count to the accumulator */
if (total_bytes_used < 2) /* if a full word has not been consumed yet */
fputs ("\n\t\t", ofile); /* then start a new line for the second operation */
}
while (total_bytes_used < 2); /* continue until at least one word is consumed */
if (byte_addr & 1) /* if the next operation begins in the lower byte */
sim_switches |= SWMASK ('R'); /* then set the switch for the next call */
else /* otherwise it begins in the upper byte */
sim_switches &= ~SWMASK ('R'); /* so clear the switch */
return -(t_stat) (total_bytes_used / 2 - 1); /* return the (biased) negative number of words consumed */
}
/* Print a CPU instruction and operand in symbolic format.
This routine prints a CPU instruction and its operand, if any, using the
mnemonics specified in the Machine Instruction Set and Systems Programming
Language Reference manuals. Specified bits in the instruction word are used
as an index into a supplied classification table. The entry corresponding to
the instruction gives the mnemonic string, operand type, and reserved bits
(if any).
On entry, the "ofile" parameter is the opened output stream, "ops" is the
table of classifications containing the instruction, "val" contains the
word(s) of the machine instruction to print, "mask" is the opcode mask to
apply to get the index bits, "shift" is the right-shift count to align the
index, and "radix" contains the desired operand radix or zero if the default
radix is to be used.
On exit, a status code is returned to the caller. SCPE_OK status is returned
if the print consumed a single-word value, or the negative number of extra
words (beyond the first) consumed by printing the instruction is returned.
For example, printing a symbol that resulted in two words being consumed
(from val [0] and val [1]) would return SCPE_OK_2_WORDS (= -1). If the
supplied instruction is not in the table, SCPE_ARG is returned.
The classification table consists of a set of entries that are indexed by
opcode, followed optionally by a set of entries that are searched linearly.
Empty mnemonics, i.e., "", are used in the indexed part to indicate that the
linear part must be searched, and in the linear part to indicate a two-word
instruction whose second word did not match a defined entry. A NULL mnemonic
ends the array (this allows string searches for parsing to fail without
aborting).
The supplied instruction is ANDed with the "mask" parameter and then
right-shifted by the "shift" parameter to produce an index into the "ops"
table. If the entry contains a non-empty mnemonic string, it is printed.
Otherwise, starting at the index implied by the size of the mask, i.e., at
mask + 1, a linear search of the entries is performed. For each entry, the
instruction is masked to remove the operand and optionally the reserved bits,
and the result is compared to the base opcode. If it matches, the associated
mnemonic is printed. If the table is exhausted without a match, the
instruction is undefined, and SCPE_ARG is returned.
For defined instructions, the operand, if any, is printed after the mnemonic.
Operand values are printed in a radix suitable to the type of the value, as
follows:
- Register-relative displacements, S-register decrements, and K fields are
printed in the CPU's address radix, which is octal.
- Shift counts, bit positions, and starting bits and counts are printed in
decimal.
- CIR values for the PAUS and HALT instructions are printed in octal.
- Immediate values are printed in the CPU's data radix, which defaults to
octal but may be set to a different radix with SET CPU OCT|DEC|HEX.
The radix for operand values other than addresses may be overridden by a
switch on the command line. A value printed in a radix other than the
current data radix is preceded by a radix identifier ("%" for octal, "#" for
decimal, or "!" for hexadecimal).
Implementation notes:
1. All instructions in the base set are single words. However, some
extension instructions, including instructions for later-series CPUs,
e.g., the Series 33, use two words. For example, the WIOC (write I/O
channel) instruction is the two-word sequence 020302,000003, and the SIOP
(start I/O program) sequence is 020302,000000.
2. The operand type dispatch handlers either set up operand printing by
assigning the prefix, indirect, and index values, or print the operand(s)
directly if special formatting is required.
3. Register flags for the PSHR and SETR instructions are printed using the
SPL register names.
4. The "rsvd_mask" fields of secondary entries for one-word instructions
have the upper 16 bits of the values set to zero. This masks off the
second instruction word, which is arbitrary, before comparison.
5. A secondary entry with a zero-length mnemonic is used to match the first
word of a two-word instruction when the second word fails to match any of
the earlier entries. This causes the instruction to be printed as a pair
of numeric values.
*/
static const char *const register_name [] = { /* PSHR/SETR register names corresponding to bits 8-15 */
"SBANK", /* bit 8 */
"DB", /* bit 9 */
"DL", /* bit 10 */
"Z", /* bit 11 */
"STATUS", /* bit 12 */
"X", /* bit 13 */
"Q", /* bit 14 */
"S" /* bit 15 */
};
static const char *const cc_flags [] = { /* TCCS condition code names corresponding to bits 13-15 */
" N", /* 000 = never */
" L", /* 001 = less than */
" E", /* 010 = equal */
" LE", /* 011 = less than or equal */
" G", /* 100 = greater than */
" NE", /* 101 = not equal */
" GE", /* 110 = greater than or equal */
" A" /* 111 = always */
};
static const char *const sign_cntl [] = { /* CVND sign control names corresponding to bits 12-14 */
" LS", /* 000 = sign is leading separate */
" TS", /* 001 = sign is trailing separate */
" LO", /* 010 = sign is leading overpunch or unsigned */
" TO", /* 011 = sign is trailing overpunch or unsigned */
" UN", /* 100 = unsigned */
" UN", /* 101 = unsigned */
" UN", /* 110 = unsigned */
" UN" /* 111 = unsigned */
};
static t_stat fprint_instruction (FILE *ofile, const OP_TABLE ops, t_value *val,
uint32 mask, uint32 shift, uint32 radix)
{
uint32 op_index, op_radix;
int32 reg_index;
t_bool reg_first;
t_value instruction, op_value;
const char *prefix = NULL; /* label to print before the operand */
t_bool index = FALSE; /* TRUE if the instruction is indexed */
t_bool indirect = FALSE; /* TRUE if the instruction is indirect */
t_stat status = SCPE_OK; /* result status */
instruction = TO_DWORD (val [1], val [0]); /* merge the two supplied values */
op_index = ((uint32) instruction & mask) >> shift; /* extract the opcode index */
if (ops [op_index].mnemonic [0]) /* if a primary entry is defined */
fputs (ops [op_index].mnemonic, ofile); /* then print the mnemonic */
else { /* otherwise search through the secondary entries */
for (op_index = (mask >> shift) + 1; /* in the table starting after the primary entries */
ops [op_index].mnemonic != NULL; /* until the NULL entry at the end */
op_index++)
if (ops [op_index].opcode == /* if the opcode in this table entry */
(instruction & ops [op_index].rsvd_mask /* matches the instruction with the reserved bits */
& op_mask [ops [op_index].operand])) /* and operand bits masked off */
if (ops [op_index].mnemonic [0]) { /* then if the entry is defined */
fputs (ops [op_index].mnemonic, ofile); /* then print it */
break; /* and terminate the search */
}
else { /* otherwise this two-word instruction is unimplemented */
fprint_val (ofile, val [0], cpu_dev.dradix, /* so print the first word */
cpu_dev.dwidth, PV_RZRO);
fputc (',', ofile); /* and a separator */
fprint_val (ofile, val [1], cpu_dev.dradix, /* and the second word */
cpu_dev.dwidth, PV_RZRO);
return SCPE_OK_2_WORDS; /* return success to indicate printing is complete */
}
if (ops [op_index].mnemonic == NULL) /* if the opcode was not found */
return SCPE_ARG; /* then return error status to print it in octal */
}
op_value = instruction & ~op_mask [ops [op_index].operand]; /* mask the instruction to the operand value */
if (ops [op_index].rsvd_mask > D16_MASK) { /* if this is a two-word instruction */
op_value = op_value >> D16_WIDTH; /* then the operand is in the upper word */
status = SCPE_OK_2_WORDS; /* and the routine will consume two words */
}
op_radix = cpu_dev.aradix; /* assume that operand is an address */
switch (ops [op_index].operand) { /* dispatch by the operand type */
/* no operand */
case opNone:
break; /* no formatting needed */
/* condition code flags in bits 13-15 */
case opCC7:
fputs (cc_flags [op_value], ofile); /* print the condition code flags */
break;
/* unsigned value pair range 0-15 */
case opU1515:
fputc (' ', ofile); /* print a separator */
fprint_value (ofile, START_BIT (op_value), /* print the starting bit position */
(radix ? radix : 10), DV_WIDTH, PV_LEFT);
fputc (':', ofile); /* print a separator */
fprint_value (ofile, BIT_COUNT (op_value), /* print the bit count */
(radix ? radix : 10), DV_WIDTH, PV_LEFT);
break;
/* P +/- displacement range 0-31, indirect bit 4 */
case opPS31I:
indirect = (instruction & I_FLAG_BIT_4) != 0; /* save the indirect condition */
prefix = (op_value & DISPL_31_SIGN ? " P-" : " P+"); /* set the base register and sign label */
op_value = op_value & DISPL_31_MASK; /* and remove the sign from the displacement value */
break;
/* P +/- displacement range 0-255, indirect bit 5, index bit 4 */
case opPS255IX:
index = (instruction & X_FLAG) != 0; /* save the index condition */
indirect = (instruction & I_FLAG_BIT_5) != 0; /* and the indirect condition */
/* fall into the P-relative displacement case */
/* P +/- displacement range 0-255 */
case opPS255:
prefix = (op_value & DISPL_255_SIGN ? " P-" : " P+"); /* set the base register and sign label */
op_value = op_value & DISPL_255_MASK; /* and remove the sign from the displacement value */
break;
/* base bit 15 */
case opB15:
case opB31:
if (op_value == 0) /* if the base bit is 0 */
fputs (" PB", ofile); /* then PB is the base register label */
break;
/* S decrement range 0-3, base register bit 11 */
case opSU3B:
prefix = (instruction & DB_FLAG) ? " " : " PB,"; /* set the base register label */
op_value = op_value & ~op_mask [opSU3]; /* and remove the base flag from the S decrement value */
break;
/* S decrement range 0-3, N/A/S bits 11-13 */
case opSU3NAS:
if (instruction & MVBW_CCF) /* if any flags are present */
fputc (' ', ofile); /* then print a space as a separator */
if (instruction & MVBW_A_FLAG) /* if the alphabetic flag is present */
fputc ('A', ofile); /* then print an "A" as the indicator */
if (instruction & MVBW_N_FLAG) /* if the numeric flag is present */
fputc ('N', ofile); /* then print an "N" as the indicator */
if (instruction & MVBW_S_FLAG) /* if the upshift flag is present */
fputc ('S', ofile); /* then print an "S" as the indicator */
prefix = ","; /* separate the value from the flags */
op_value = op_value & ~op_mask [opSU3]; /* and remove the flags from the S decrement value */
break;
/* register selection bits 8-15, execution from left-to-right */
case opR255L:
if (op_value != 0) { /* if any registers are to be output */
fputc (' ', ofile); /* then print a space as a separator */
reg_first = TRUE; /* set the first-time-through flag */
for (reg_index = 0; reg_index <= 7; reg_index++) { /* loop through the register bits */
if (op_value & PSR_LR_MASK) { /* if the register selection bit is set */
if (reg_first) /* then if this is the first time */
reg_first = FALSE; /* then clear the flag */
else /* otherwise */
fputc (',', ofile); /* output a comma separator */
fputs (register_name [reg_index], ofile); /* output the register name */
}
op_value = op_value << 1; /* position the next register selection bit */
}
}
break;
/* register selection bits 8-15, execution from right-to-left */
case opR255R:
if (op_value != 0) { /* if any registers are to be output */
fputc (' ', ofile); /* then print a space as a separator */
reg_first = TRUE; /* set the first-time-through flag */
for (reg_index = 7; reg_index >= 0; reg_index--) { /* loop through the register bits */
if (op_value & PSR_RL_MASK) { /* if the register selection bit is set */
if (reg_first) /* then if this is the first time */
reg_first = FALSE; /* then clear the flag */
else /* otherwise */
fputc (',', ofile); /* output a comma separator */
fputs (register_name [reg_index], ofile); /* output the register name */
}
op_value = op_value >> 1; /* position the next register selection bit */
}
}
break;
/* P+/P-/DB+/Q+/Q-/S- displacements, indirect bit 5, index bit 4 */
case opPD255IX:
if ((instruction & DISPL_P_FLAG) == 0) { /* if this a P-relative displacement */
prefix = (op_value & DISPL_255_SIGN ? " P-" : " P+"); /* then set the base register and sign label */
op_value = op_value & DISPL_255_MASK; /* and remove the sign from the displacement value */
index = (instruction & X_FLAG) != 0; /* save the index condition */
indirect = (instruction & I_FLAG_BIT_5) != 0; /* and the indirect condition */
break;
}
/* otherwise the displacement is not P-relative, so fall into the data-relative handler */
/* DB+/Q+/Q-/S- displacements, indirect bit 5, index bit 4 */
case opD255IX:
if ((instruction & DISPL_DB_FLAG) == 0) { /* if this a DB-relative displacement */
prefix = " DB+"; /* then set the base register label */
op_value = op_value & DISPL_255_MASK; /* and remove the base flag from the displacement value */
}
else if ((instruction & DISPL_QPOS_FLAG) == 0) { /* otherwise if this a positive Q-relative displacement */
prefix = " Q+"; /* then set the base register label */
op_value = op_value & DISPL_127_MASK; /* and remove the base flag from the displacement value */
}
else if ((instruction & DISPL_QNEG_FLAG) == 0) { /* otherwise if this a negative Q-relative displacement */
prefix = " Q-"; /* then set the base register label */
op_value = op_value & DISPL_63_MASK; /* and remove the base flag from the displacement value */
}
else { /* otherwise it must be a negative S-relative displacement */
prefix = " S-"; /* so set the base register label */
op_value = op_value & DISPL_63_MASK; /* and remove the base flag from the displacement value */
}
indirect = (instruction & I_FLAG_BIT_5) != 0; /* save the indirect condition */
/* fall into the index case */
/* index bit 4 */
case opX:
index = (instruction & X_FLAG) != 0; /* save the index condition */
break;
/* unsigned value range 0-63, index bit 4 */
case opU63X:
index = (instruction & X_FLAG) != 0; /* save the index condition */
op_value = op_value & DISPL_63_MASK; /* and mask to the operand value */
/* fall into the unsigned value case */
/* unsigned value range 0-63 */
case opU63:
op_radix = (radix ? radix : 10); /* set the print radix */
prefix = " "; /* and add a separator */
break;
/* sign control bits 9-10, S decrement bit 11 */
case opSCS3:
if (instruction & NABS_FLAG) { /* if the negative absolute flag is present */
fputs (" NABS", ofile); /* then print "NABS" as the indicator */
prefix = ","; /* we will need to separate the flag and value */
}
else if (instruction & ABS_FLAG) { /* otherwise if the absolute flag is present */
fputs (" ABS", ofile); /* then print "ABS" as the indicator */
prefix = ","; /* we will need to separate the flag and value */
}
else /* otherwise neither flag is present */
prefix = " "; /* so just use a space to separate the value */
op_value = (op_value & ~op_mask [opS11]) >> EIS_SDEC_SHIFT; /* remove the flags from the S decrement value */
break;
/* sign control bits 12-14, S decrement bit 15 */
case opSCS4:
fputs (sign_cntl [op_value >> CVND_SC_SHIFT], ofile); /* print the sign-control mnemonic */
prefix = ","; /* separate the flag and value */
op_value = op_value & CIS_SDEC_MASK; /* and remove the flags from the S decrement value */
break;
/* S decrement bit 11 */
/* S decrement range 0-2 bits 10-11 */
case opS11:
case opSU2:
op_value = op_value >> EIS_SDEC_SHIFT; /* align the S decrement value */
/* fall into the unsigned operand case */
/* unsigned value range 0-1 */
/* unsigned value range 0-255 */
case opU1:
case opU255:
op_radix = (radix ? radix : cpu_dev.dradix); /* set the print radix */
prefix = " "; /* and add a separator */
break;
/* CIR display bits 12-15 */
case opC15:
op_radix = (radix ? radix : 8); /* set the print radix */
prefix = " "; /* and add a separator */
break;
/* P unsigned displacement range 0-255 */
/* S decrement range 0-1 */
/* S decrement range 0-3 */
/* S decrement range 0-7 */
/* S decrement range 0-15 */
case opPU255:
case opS15:
case opS31:
case opSU3:
case opSU7:
case opSU15:
prefix = " "; /* add a separator */
break;
} /* end of the operand type dispatch */
if (prefix) { /* if an operand is present */
fputs (prefix, ofile); /* then label it */
fprint_value (ofile, op_value, op_radix, /* and then print the value */
DV_WIDTH, PV_LEFT);
}
if (indirect) /* add an indirect indicator */
fputs (",I", ofile); /* if specified by the instruction */
if (index) /* add an index indicator */
fputs (",X", ofile); /* if specified by the instruction */
return status; /* return the applicable status */
}
/* Parse a CPU instruction */
static t_stat parse_cpu (CONST char *cptr, t_addr address, UNIT *uptr, t_value *value, int32 switches)
{
return SCPE_ARG; /* mnemonic support is not present in this release */
}