/* hp3000_sys.c: HP 3000 system common interface | |
Copyright (c) 2016-2017, 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') /* 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 */ | |
} |