/* hp2100_sys.c: HP 2100 system common interface | |
Copyright (c) 1993-2016, Robert M. Supnik | |
Copyright (c) 2017-2018, J. David Bryan | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in | |
all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | |
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
Except as contained in this notice, the names of the authors shall not be | |
used in advertising or otherwise to promote the sale, use or other dealings | |
in this Software without prior written authorization from the authors. | |
07-Mar-18 JDB Added the GET_SWITCHES macro from scp.c | |
22-Feb-18 JDB Added the <dev> option to the LOAD command | |
07-Sep-17 JDB Replaced "uint16" cast with "MEMORY_WORD" for loader ROM | |
07-Aug-17 JDB Added "hp_attach" to attach a file for appending | |
01-Aug-17 JDB Added "ispunct" test for implied mnemonic parse | |
20-Jul-17 JDB Removed STOP_OFFLINE, STOP_PWROFF messages | |
14-Jul-17 JDB Interrupt deferral is now calculated in instr postlude | |
11-Jul-17 JDB Moved "hp_enbdis_pair" here from hp2100_cpu.c | |
Renamed "ibl_copy" to "cpu_ibl" | |
16-May-17 JDB Rewrote "fprint_sym" and "parse_sym" for better coverage | |
03-Apr-17 JDB Rewrote "parse_cpu" to improve efficiency and coverage | |
25-Mar-17 JDB Increased "sim_emax" from 3 to 10 for VIS instructions | |
20-Nar-17 JDB Rewrote "fprint_cpu" to improve efficiency and coverage | |
03-Mar-17 JDB Added binary input parsing | |
01-Mar-17 JDB Added physical vs. logical address parsing and printing | |
27-Feb-17 JDB Revised the LOAD command and added the DUMP command | |
25-Jan-17 JDB Replaced ReadIO with mem_fast_read, added hp_trace routine | |
22-Jan-17 JDB Separated instruction mnemonic printing | |
15-Jan-17 JDB Corrected HLT decoding to add the 1060xx and 1070xx ranges | |
Corrected SFB decoding | |
14-Jan-17 JDB Removed use of Fprintf functions for version 4.x and on | |
30-Dec-16 JDB Corrected parsing of memory reference instructions | |
13-May-16 JDB Modified for revised SCP API function parameter types | |
19-Jun-15 JDB Conditionally use Fprintf function for version 4.x and on | |
18-Jun-15 JDB Added cast to int for isspace parameter | |
24-Dec-14 JDB Added casts to t_addr and t_value for 64-bit compatibility | |
Made local routines static | |
05-Feb-13 JDB Added hp_fprint_stopped to handle HLT instruction message | |
18-Mar-13 JDB Moved CPU state variable declarations to hp2100_cpu.h | |
09-May-12 JDB Quieted warnings for assignments in conditional expressions | |
10-Feb-12 JDB Deprecated DEVNO in favor of SC | |
Added hp_setsc, hp_showsc functions to support SC modifier | |
15-Dec-11 JDB Added DA and dummy DC devices | |
29-Oct-10 JDB DMA channels renamed from 0,1 to 1,2 to match documentation | |
26-Oct-10 JDB Changed DIB access for revised signal model | |
03-Sep-08 JDB Fixed IAK instruction dual-use mnemonic display | |
07-Aug-08 JDB Moved hp_setdev, hp_showdev from hp2100_cpu.c | |
Changed sim_load to use WritePW instead of direct M[] access | |
18-Jun-08 JDB Added PIF device | |
17-Jun-08 JDB Moved fmt_char() function from hp2100_baci.c | |
26-May-08 JDB Added MPX device | |
24-Apr-08 JDB Changed fprint_sym to handle step with irq pending | |
07-Dec-07 JDB Added BACI device | |
27-Nov-07 JDB Added RTE OS/VMA/EMA mnemonics | |
21-Dec-06 JDB Added "fwanxm" external for sim_load check | |
19-Nov-04 JDB Added STOP_OFFLINE, STOP_PWROFF messages | |
25-Sep-04 JDB Added memory protect device | |
Fixed display of CCA/CCB/CCE instructions | |
01-Jun-04 RMS Added latent 13037 support | |
19-Apr-04 RMS Recognize SFS x,C and SFC x,C | |
22-Mar-02 RMS Revised for dynamically allocated memory | |
14-Feb-02 RMS Added DMS instructions | |
04-Feb-02 RMS Fixed bugs in alter/skip display and parsing | |
01-Feb-02 RMS Added terminal multiplexor support | |
16-Jan-02 RMS Added additional device support | |
17-Sep-01 RMS Removed multiconsole support | |
27-May-01 RMS Added multiconsole support | |
14-Mar-01 RMS Revised load/dump interface (again) | |
30-Oct-00 RMS Added examine to file support | |
15-Oct-00 RMS Added dynamic device number support | |
27-Oct-98 RMS V2.4 load interface | |
References: | |
- HP 1000 M/E/F-Series Computers Technical Reference Handbook | |
(5955-0282, March 1990) | |
- RTE-IV Assembler Reference Manual | |
(92067-90003, October 1980) | |
This module provides the interface between the Simulation Control Program | |
(SCP) and the HP 2100 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 <stdlib.h> | |
#include "hp2100_defs.h" | |
#include "hp2100_cpu.h" | |
/* Command-line switch parsing from scp.c */ | |
#define GET_SWITCHES(cp) \ | |
if ((cp = get_sim_sw (cp)) == NULL) return SCPE_INVSW | |
/* External I/O data structures */ | |
extern DEVICE mp_dev; /* Memory Protect */ | |
extern DEVICE dma1_dev, dma2_dev; /* Direct Memory Access/Dual-Channel Port Controller */ | |
extern DEVICE ptr_dev; /* Paper Tape Reader */ | |
extern DEVICE ptp_dev; /* Paper Tape Punch */ | |
extern DEVICE tty_dev; /* Teleprinter */ | |
extern DEVICE clk_dev; /* Time Base Generator */ | |
extern DEVICE lps_dev; /* 2767 Line Printer */ | |
extern DEVICE lpt_dev; /* 2607/13/17/18 Line Printer */ | |
extern DEVICE baci_dev; /* Buffered Asynchronous Communication Interface */ | |
extern DEVICE mpx_dev; /* Eight-Channel Asynchronous Multiplexer */ | |
extern DEVICE mtd_dev, mtc_dev; /* 3030 Magnetic Tape Drive */ | |
extern DEVICE msd_dev, msc_dev; /* 7970B/E Magnetic Tape Drive */ | |
extern DEVICE dpd_dev, dpc_dev; /* 2870/7900 Disc Drive */ | |
extern DEVICE dqd_dev, dqc_dev; /* 2883 Disc Drive */ | |
extern DEVICE drd_dev, drc_dev; /* 277x Disc/Drum Drive */ | |
extern DEVICE ds_dev; /* 7905/06/20/25 Disc Drive */ | |
extern DEVICE muxl_dev, muxu_dev, muxc_dev; /* Sixteen-Channel Asynchronous Multiplexer */ | |
extern DEVICE ipli_dev, iplo_dev; /* Processor Interconnect */ | |
extern DEVICE pif_dev; /* Privileged Interrupt Fence */ | |
extern DEVICE da_dev; /* 7906H/20H/25H ICD Disc Drive */ | |
extern DEVICE dc_dev; /* Dummy HP-IB Interface */ | |
/* Program constants */ | |
#define VAL_EMPTY (PA_MAX + 1) /* flag indicating that the value array must be loaded */ | |
/* Symbolic production/consumption values */ | |
#define SCPE_OK_2_WORDS ((t_stat) -1) /* two 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 H_SWITCH SWMASK ('H') | |
#define M_SWITCH SWMASK ('M') | |
#define O_SWITCH SWMASK ('O') | |
#define MODE_SWITCHES (C_SWITCH | M_SWITCH) | |
#define FORMAT_SWITCHES (A_SWITCH | B_SWITCH | D_SWITCH | H_SWITCH | O_SWITCH) | |
#define SYMBOLIC_SWITCHES (C_SWITCH | M_SWITCH | A_SWITCH) | |
#define ALL_SWITCHES (MODE_SWITCHES | FORMAT_SWITCHES) | |
/* 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. 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. | |
*/ | |
typedef enum { | |
opNone, /* no operand */ | |
opMPOI, /* MRG page bit 10, offset 0000-1777 octal, indirect bit 15 */ | |
opSC, /* IOG select code range 00-77 octal */ | |
opSCHC, /* IOG select code range 00-77 octal, hold/clear bit 9 */ | |
opSCOHC, /* IOG optional select code range 00-77 octal, hold/clear bit 9 */ | |
opHC, /* IOG hold/clear bit 9 */ | |
opShift, /* EAU shift/rotate count range 1-16 */ | |
opIOPON, /* IOP index negative offset range 1-20 octal */ | |
opIOPOP, /* IOP index positive offset range 0-17 octal */ | |
opIOPO, /* IOP index offset range 0-37 octal (+20 bias) */ | |
opZA4, /* UIG zero word, four (indirect) memory addresses */ | |
opZA5, /* UIG zero word, five (indirect) memory addresses */ | |
opZA6, /* UIG zero word, six (indirect) memory addresses */ | |
opZA8, /* UIG zero word, eight (indirect) memory addresses */ | |
opV1, /* UIG one data value */ | |
opV2, /* UIG two data values */ | |
opA1V1, /* UIG one (indirect) memory address, one data value */ | |
opV2A1, /* UIG two data values, one (indirect) memory address */ | |
opV1A5, /* UIG one data value, five (indirect) memory addresses */ | |
opMA1ZI, /* One memory address range 00000-77777 octal, zero word, indirect bit 15 */ | |
opMA1I, /* One memory address range 00000-77777 octal, indirect bit 15 */ | |
opMA2I, /* Two memory addresses range 00000-77777 octal, indirect bit 15 */ | |
opMA3I, /* Three memory addresses range 00000-77777 octal, indirect bit 15 */ | |
opMA4I, /* Four memory addresses range 00000-77777 octal, indirect bit 15 */ | |
opMA5I, /* Five memory addresses range 00000-77777 octal, indirect bit 15 */ | |
opMA6I, /* Six memory addresses range 00000-77777 octal, indirect bit 15 */ | |
opMA7I /* Seven memory addresses range 00000-77777 octal, indirect bit 15 */ | |
} OP_TYPE; | |
typedef struct { | |
t_value mask; /* operand mask */ | |
int32 count; /* operand count */ | |
uint32 address_set; /* address operand bitset */ | |
} OP_PROP; | |
static const OP_PROP op_props [] = { /* operand properties, indexed by OP_TYPE */ | |
{ ~0000000u, 0, 000000u }, /* opNone */ | |
{ ~0101777u, 0, 000001u }, /* opMPOI */ | |
{ ~0000077u, 0, 000000u }, /* opSC */ | |
{ ~0000077u, 0, 000000u }, /* opSCHC */ | |
{ ~0000077u, 0, 000000u }, /* opSCOHC */ | |
{ ~0001000u, 0, 000000u }, /* opHC */ | |
{ ~0000017u, 0, 000000u }, /* opShift */ | |
{ ~0000017u, 0, 000000u }, /* opIOPON */ | |
{ ~0000017u, 0, 000000u }, /* opIOPOP */ | |
{ ~0000037u, 0, 000000u }, /* opIOPO */ | |
{ ~0000000u, 5, 000036u }, /* opZA4 */ | |
{ ~0000000u, 6, 000076u }, /* opZA5 */ | |
{ ~0000000u, 7, 000176u }, /* opZA6 */ | |
{ ~0000000u, 9, 000776u }, /* opZA8 */ | |
{ ~0000000u, 1, 000000u }, /* opV1 */ | |
{ ~0000000u, 2, 000000u }, /* opV2 */ | |
{ ~0000000u, 2, 000001u }, /* opA1V1 */ | |
{ ~0000000u, 3, 000004u }, /* opV2A1 */ | |
{ ~0000000u, 6, 000076u }, /* opV1A5 */ | |
{ ~0000000u, 2, 000001u }, /* opMA1ZI */ | |
{ ~0000000u, 1, 000001u }, /* opMA1I */ | |
{ ~0000000u, 2, 000003u }, /* opMA2I */ | |
{ ~0000000u, 3, 000007u }, /* opMA3I */ | |
{ ~0000000u, 4, 000017u }, /* opMA4I */ | |
{ ~0000000u, 5, 000037u }, /* opMA5I */ | |
{ ~0000000u, 6, 000077u }, /* opMA6I */ | |
{ ~0000000u, 7, 000177u } /* opMA7I */ | |
}; | |
/* Instruction classifications. | |
Machine instructions on the HP 21xx/1000 are identified by a varying number | |
of bits. In general, the five most-significant bits identify the general | |
group of instruction, and additional bits form a sub-opcode within a group to | |
identify an instruction uniquely. However, many instructions are irregular, | |
and those from two groups -- the Shift-Rotate Group and the Alter-Skip Group | |
-- are formed from multiple "micro-operations" that may be combined to | |
perform up to eight operations per instruction. Instructions from the | |
Extended Arithmetic Group have a number of reserved bits that are defined to | |
be zero. Correct hardware decoding may or may not depend on these bits being | |
zero and varies from CPU model to model. | |
Each instruction is classified by a mnemonic, a base operation code (without | |
the operand), an operand type, and a mask for the significant bits that | |
identify the opcode. Classifications are grouped by class of instruction | |
into arrays that are indexed by sub-opcodes, if applicable. Two-word | |
instructions will have base operation codes and signifiant bits masks that | |
extend to 32 bits. | |
An opcode 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 significant opcode bits | |
masks are not defined or used. | |
A number of instructions use bit 11 to indicate whether the instruction | |
applies to the A-register or B-register. If a class uses a primary part, and | |
that class also uses the A/B-register indicator, then the primary table is | |
twice the expected length (based on the number of significant sub-opcode | |
bits). The first half of the table applies to the sub-opcodes where bit 11 | |
is zero (meaning, "use the A-register"), and the second half applies where | |
bit 11 is one (meaning, "use the B-register"). | |
If some opcodes in a class are defined by a limited set of significant bits, | |
if the sub-opcode decoding is not regular, or if the instruction requires two | |
words to decode, then a second (secondary) part of the table contains | |
classification elements that specify which bits of the opcode are | |
significant. This part is searched linearly. | |
The secondary table may contain dependent or independent entries. In the | |
former case, an instruction will match only one of the entries, and the | |
search terminates when the match occurs. An example is the EAG table. In | |
the latter case, an instruction may independently match multiple entries, and | |
the search must continue through the end of the table. Examples are the SRG | |
and ASG tables. | |
A primary entry that requires additional bits to decode may indicate that the | |
secondary section is to be searched by setting a null string ("") as the | |
instruction mnemonic. The end of the opcode table is indicated by a NULL | |
mnemonic pointer. | |
As an example, the Memory Reference Group of instructions have bits 14-12 | |
not equal to zero. These seven values are combined with bit 11 to encode one | |
of 14 instructions. The opcode table consists of 16 primary entries, indexed | |
by bits 14-11 (the first two entries are not valid MRG instructions). | |
As a contrasting example, the Shift-Rotate Group instructions contain four | |
fields. The first field is defined by bits 8-6 that select one of eight | |
shift or rotate operations, plus bit 11 that selects whether the operation | |
applies to the A-register or the B-register. This field is decoded by an | |
opcode table consisting of 16 primary entries in two halves. The first half | |
is indexed by bits 8-6 when bit 11 is zero; the second half is indexed by | |
bits 8-6 when bit 11 is one. | |
The second and third fields use bits 5 and 3 to enable "clear E-register" and | |
"skip if the A/B-register is zero" operations, respectively. These are | |
decoded by a secondary table of four entries that is searched linearly. The | |
significant opcode bits masks are used to mask the instruction word to those | |
bits that identify the particular instruction. | |
The fourth field encodes a second shift/rotate operation using bits 2-0. The | |
opcode table is structured identically to the one for the first field. | |
A third example is the I/O Group opcode table. IOG instructions are | |
primarily encoded by bits 8-6. A few of these sub-opcodes additionally use | |
bit 11 to select the A/B-register or bit 9 for additional instruction | |
differentiation. The opcode table consists of a two-part primary section, | |
indexed by bits 8-6 and bit 11, and a secondary section that is searched | |
linearly to decode the additional bits. | |
The content of each opcode table is described by an opcode descriptor. This | |
structure contains the mask and alignment shift to apply to the instruction | |
to obtain the primary index, the mask to obtain the A/B-register select bit | |
from the instruction, and the CPU feature that must be enabled to enable the | |
associated instructions. If the opcode table contains only secondary | |
entries, the mask field contains the OP_LINEAR value, and the shift field | |
contains the OP_SINGLE or OP_MULTIPLE value, depending on whether the opcode | |
entries are dependent or independent, respectively. The register selector | |
field contains either AB_SELECT or AB_UNUSED values, depending on whether or | |
not the instruction uses bit 11 to select between the A and B registers. The | |
required feature field contains a mask that is applied to the user flags | |
field of the CPU device to select a CPU type and firmware option set. For | |
example, the descriptor for the I/O Processor opcodes for the 2100 CPU | |
contains "UNIT_IOP | CPU_2100" as the required feature field value. | |
Implementation notes: | |
1. An opcode table containing both primary and secondary entries uses the | |
"mask" and "shift" fields to obtain the primary index. Therefore, the | |
alternate use of the "shift" field to designate dependent vs. independent | |
secondaries is not available, and secondaries are assumed to be | |
dependent. | |
2. Two-word instructions have the first word in the lower half of the 32-bit | |
"opcode" field, and the second word in the upper half. The "op_bits" | |
field is similarly arranged. | |
3. An opcode descriptor and its associated opcode table form an integral | |
object. Ideally, they would be a single structure. However, while C | |
allows a structure to contain a "flexible array member" as the last | |
field, which could represent the opcode table array, the array cannot be | |
initialized statically. Therefore, separate structures and arrays are | |
used. | |
4. Opcode entries with lower-case mnemonics can be printed but will never | |
match when parsing (because the command line is upshifted before calling | |
the "parse_sym" routine). Lower-case mnemonics are used for instructions | |
with undocumented names (e.g., self-test instructions) and for | |
non-canonical opcodes that execute as other, canonical instructions | |
(e.g., 105700, which executes as XMM). | |
*/ | |
#define OP_LINEAR 0u /* (mask) table contains linear search entries only */ | |
#define OP_SINGLE 0u /* (shift) match only a single linear entry */ | |
#define OP_MULTIPLE 1u /* (shift) match multiple linear entries */ | |
#define AB_SELECT 0004000u /* mask to select the A or B register */ | |
#define AB_UNUSED 0000000u /* mask to use when the A/B register bit is not used */ | |
#define OPTION_MASK (UNIT_OPTS | UNIT_MODEL_MASK) /* feature flags mask */ | |
#define BASE_SET UNIT_MODEL_MASK /* base set feature flags */ | |
typedef struct { /* opcode descriptor */ | |
uint32 mask; /* mask to get opcode selection */ | |
uint32 shift; /* shift to get the opcode selection */ | |
uint32 ab_selector; /* mask to get the A/B-register selector */ | |
uint32 feature; /* feature set to which opcodes apply */ | |
} OP_DESC; | |
typedef struct { /* opcode table entry */ | |
const char *mnemonic; /* symbolic name of the opcode */ | |
t_value opcode; /* base value of the opcode */ | |
OP_TYPE type; /* type of operand(s) to the opcode */ | |
t_value op_bits; /* significant opcode bits mask */ | |
} OP_ENTRY; | |
typedef OP_ENTRY OP_TABLE []; /* a table of opcode entries */ | |
/* Memory Reference Group. | |
The memory reference instructions are indicated by bits 14-12 not equal to | |
000. They are are fully decoded by bits 14-11. The table consists of 16 | |
primary entries. | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| I | mem op | C | memory address | MRG | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| I | mem op | B | C | memory address | MRG | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
I = direct/indirect (0/1) | |
B = A/B register (0/1) | |
C = base/current page (0/1) | |
Memory operation: | |
0010 = AND | |
0011 = JDB | |
0100 = XOR | |
0101 = JMP | |
0110 = IOR | |
0111 = ISZ | |
100x = ADA/ADB | |
101x = CPA/CPB | |
110x = LDA/LDB | |
111x = STA/STB | |
Implementation notes: | |
1. The first two table entries are placeholders for bits 14-11 = 000x. | |
These represent Shift-Rotate Group instructions, which will not be | |
decoded with this table. | |
2. The A/B-register selector descriptor is set to "unused" because the | |
primary index includes bit 11. | |
*/ | |
static const OP_DESC mrg_desc = { /* Memory Reference Group descriptor */ | |
0074000u, /* opcode mask */ | |
11u, /* opcode shift */ | |
AB_UNUSED, /* A/B-register selector */ | |
BASE_SET | CPU_ALL /* applicable feature flags */ | |
}; | |
static const OP_TABLE mrg_ops = { /* MRG opcodes, indexed by IR bits 14-11 */ | |
{ "", 0000000u, opNone }, /* SRG */ | |
{ "", 0004000u, opNone }, /* SRG */ | |
{ "AND", 0010000u, opMPOI }, | |
{ "JSB", 0014000u, opMPOI }, | |
{ "XOR", 0020000u, opMPOI }, | |
{ "JMP", 0024000u, opMPOI }, | |
{ "IOR", 0030000u, opMPOI }, | |
{ "ISZ", 0034000u, opMPOI }, | |
{ "ADA", 0040000u, opMPOI }, | |
{ "ADB", 0044000u, opMPOI }, | |
{ "CPA", 0050000u, opMPOI }, | |
{ "CPB", 0054000u, opMPOI }, | |
{ "LDA", 0060000u, opMPOI }, | |
{ "LDB", 0064000u, opMPOI }, | |
{ "STA", 0070000u, opMPOI }, | |
{ "STB", 0074000u, opMPOI }, | |
{ NULL } | |
}; | |
/* Shift-Rotate Group. | |
The shift-rotate instructions are indicated by bits 15, 14-12, and 10 all | |
equal to 0. They are decoded in three parts. First, if bit 9 is 1, then | |
bits 11 + 8-6 fully decode one of 16 shifts or rotations. Second, bits 5 and | |
11 + 3 independently decode the CLE and SLA/SLB operations. Third, if bit 4 | |
is 1, then bits 11 + 2-0 fully decode one of 16 shifts or rotations. | |
Three tables are used: two to decode the shifts or rotates, and a third to | |
decode the CLE and SLA/SLB operations, plus the NOP that results when all IR | |
bits are zero. The first and third tables consist of 16 primary entries. | |
The second consists of four independent secondary entries. | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 | 0 0 0 | B | 0 | E | op 1 | C | E | S | op 2 | SRG | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
B = A/B register (0/1) | |
E = disable/enable op | |
C = CLE | |
S = SLA/B | |
Shift/Rotate operation: | |
000 = ALS/BLS | |
001 = ARS/BRS | |
010 = RAL/RBL | |
011 = RAR/RBR | |
100 = ALR/BLR | |
101 = ERA/ERB | |
110 = ELA/ELB | |
111 = ALF/BLF | |
Implementation notes: | |
1. The srg1_ops and srg2_ops tables contain only primary entries. Normally, | |
primary entries don't define the op_bits fields. In these cases, | |
however, they are required to provide A/B conflict detection among the | |
two SRG operations and the SLA/SLB micro-op when parsing. | |
2. An SRG instruction executes as NOP if both shift-rotate operation fields | |
are disabled and the CLE and SLA/B fields are zero. In these cases, all | |
remaining bits are "don't care." The "NOP" entry in the micro-ops table | |
catches these cases. | |
*/ | |
static const OP_DESC srg1_desc = { /* Shift-Rotate Group first descriptor */ | |
0000700u, /* opcode mask */ | |
6u, /* opcode shift */ | |
AB_SELECT, /* A/B-register selector */ | |
BASE_SET | CPU_ALL /* applicable feature flags */ | |
}; | |
static const OP_TABLE srg1_ops = { /* SRG opcodes, indexed by IR bits 11 + 8-6 */ | |
{ "ALS", 0001000u, opNone, 0005700u }, | |
{ "ARS", 0001100u, opNone, 0005700u }, | |
{ "RAL", 0001200u, opNone, 0005700u }, | |
{ "RAR", 0001300u, opNone, 0005700u }, | |
{ "ALR", 0001400u, opNone, 0005700u }, | |
{ "ERA", 0001500u, opNone, 0005700u }, | |
{ "ELA", 0001600u, opNone, 0005700u }, | |
{ "ALF", 0001700u, opNone, 0005700u }, | |
{ "BLS", 0005000u, opNone, 0005700u }, | |
{ "BRS", 0005100u, opNone, 0005700u }, | |
{ "RBL", 0005200u, opNone, 0005700u }, | |
{ "RBR", 0005300u, opNone, 0005700u }, | |
{ "BLR", 0005400u, opNone, 0005700u }, | |
{ "ERB", 0005500u, opNone, 0005700u }, | |
{ "ELB", 0005600u, opNone, 0005700u }, | |
{ "BLF", 0005700u, opNone, 0005700u }, | |
{ NULL } | |
}; | |
static const OP_DESC srg_udesc = { /* Shift-Rotate Group micro-ops descriptor */ | |
OP_LINEAR, /* linear search only */ | |
OP_MULTIPLE, /* multiple matches allowed */ | |
AB_UNUSED, /* A/B-register selector */ | |
BASE_SET | CPU_ALL /* applicable feature flags */ | |
}; | |
static const OP_TABLE srg_uops = { /* SRG micro-opcodes, searched linearly */ | |
{ "CLE", 0000040u, opNone, 0000040u }, | |
{ "SLA", 0000010u, opNone, 0004010u }, | |
{ "SLB", 0004010u, opNone, 0004010u }, | |
{ "NOP", 0000000u, opNone, 0173070u }, | |
{ NULL } | |
}; | |
static const OP_DESC srg2_desc = { /* Shift-Rotate Group second descriptor */ | |
0000007u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_SELECT, /* A/B-register selector */ | |
BASE_SET | CPU_ALL /* applicable feature flags */ | |
}; | |
static const OP_TABLE srg2_ops = { /* SRG opcodes, indexed by IR bits 11 + 2-0 */ | |
{ "ALS", 0000020u, opNone, 0004027u }, | |
{ "ARS", 0000021u, opNone, 0004027u }, | |
{ "RAL", 0000022u, opNone, 0004027u }, | |
{ "RAR", 0000023u, opNone, 0004027u }, | |
{ "ALR", 0000024u, opNone, 0004027u }, | |
{ "ERA", 0000025u, opNone, 0004027u }, | |
{ "ELA", 0000026u, opNone, 0004027u }, | |
{ "ALF", 0000027u, opNone, 0004027u }, | |
{ "BLS", 0004020u, opNone, 0004027u }, | |
{ "BRS", 0004021u, opNone, 0004027u }, | |
{ "RBL", 0004022u, opNone, 0004027u }, | |
{ "RBR", 0004023u, opNone, 0004027u }, | |
{ "BLR", 0004024u, opNone, 0004027u }, | |
{ "ERB", 0004025u, opNone, 0004027u }, | |
{ "ELB", 0004026u, opNone, 0004027u }, | |
{ "BLF", 0004027u, opNone, 0004027u }, | |
{ NULL } | |
}; | |
/* Alter-Skip Group. | |
The alter-skip instructions are indicated by bits 15 and 14-12 equal to 0 | |
and bit 10 equal to 1. All of the operations are independent, and the table | |
consists of independent secondary entries for each operation, plus a final | |
entry for the instruction that enables none of the operations. | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 0 | 0 0 0 | B | 1 | a op | e op | E | S | L | I | Z | V | ASG | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
B = A/B register (0/1) | |
E = SEZ | |
S = SSA/B | |
L = SLA/B | |
I = INA/B | |
Z = SZA/B | |
V = RSS | |
Accumulator operation: | |
00 = NOP | |
01 = CLA/CLB | |
10 = CMA/CMB | |
11 = CCA/CCB | |
Extend operation: | |
00 = NOP | |
01 = CLE | |
10 = CME | |
11 = CCE | |
Implementation notes: | |
1. The CLE/CME/CCE, SEZ, and RSS fields do not depend on the A/B register | |
select bit. This bit is "don't care" if only these fields appear in the | |
instruction. The entries in the table catch these cases. | |
*/ | |
static const OP_DESC asg_udesc = { /* Alter-Skip Group micro-ops descriptor */ | |
OP_LINEAR, /* linear search only */ | |
OP_MULTIPLE, /* multiple matches allowed */ | |
AB_UNUSED, /* A/B-register selector */ | |
BASE_SET | CPU_ALL /* applicable feature flags */ | |
}; | |
static const OP_TABLE asg_uops = { /* ASG micro-opcodes, searched linearly */ | |
{ "CLA", 0002400u, opNone, 0007400u }, | |
{ "CMA", 0003000u, opNone, 0007400u }, | |
{ "CCA", 0003400u, opNone, 0007400u }, | |
{ "CLB", 0006400u, opNone, 0007400u }, | |
{ "CMB", 0007000u, opNone, 0007400u }, | |
{ "CCB", 0007400u, opNone, 0007400u }, | |
{ "SEZ", 0002040u, opNone, 0002040u }, | |
{ "CLE", 0002100u, opNone, 0002300u }, | |
{ "CME", 0002200u, opNone, 0002300u }, | |
{ "CCE", 0002300u, opNone, 0002300u }, | |
{ "SSA", 0002020u, opNone, 0006020u }, | |
{ "SLA", 0002010u, opNone, 0006010u }, | |
{ "INA", 0002004u, opNone, 0006004u }, | |
{ "SZA", 0002002u, opNone, 0006002u }, | |
{ "SSB", 0006020u, opNone, 0006020u }, | |
{ "SLB", 0006010u, opNone, 0006010u }, | |
{ "INB", 0006004u, opNone, 0006004u }, | |
{ "SZB", 0006002u, opNone, 0006002u }, | |
{ "RSS", 0002001u, opNone, 0002001u }, | |
{ "NOP", 0002000u, opNone, 0173777u }, | |
{ NULL } | |
}; | |
/* I/O Group. | |
The I/O instructions are indicated by bits 14-12 equal to 0 and bits 15 and | |
10 equal to 1. They are fully decoded by bits 11 + 9-6. However, the HP | |
assembler assigns special mnemonics to the flag instructions that reference | |
select code 1 (the overflow register). Therefore, secondary entries are used | |
to provide the flag mnemonics, based on the select code employed. | |
Because only the flag instructions use bit 9 to differentiate between | |
instructions, and because the flag instructions are all relegated to | |
secondary entries, the primary entries need not be indexed with bit 9. | |
Therefore, the table consists of 16 primary entries and 8 secondary entries | |
for the flag instructions. | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 1 | 0 0 0 | B | 1 | C | i/o op | select code | IOG | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
B = A/B register (0/1) | |
C = hold/clear flag (0/1) | |
I/O operation: | |
000 = HLT | |
001 = STF/CLF | |
010 = SFC | |
011 = SFS | |
100 = MIA/MIB | |
101 = LIA/LIB | |
110 = OTA/OTB | |
111 = STC/CLC | |
Implementation notes: | |
1. Bit 11 is "don't care" for the HLT, STF, CLF, SFC, and SFS instructions. | |
2. Bit 9, the "hold/clear" bit, is defined as 0 for the SFC and SFS | |
instructions; however, setting bit 9 to 1 will clear the flag after | |
testing (RTE depends on this behavior in $CIC, where it is used to test | |
the state of the interrupt system and turn it off in the same | |
instruction). Therefore, these entries use the "opSCHC" operand type | |
instead of the expected "opSC" type. | |
3. The select code may be omitted when entering the HLT instruction (it | |
defaults to 0), so a special operand code is needed when parsing this | |
case. | |
*/ | |
static const OP_DESC iog_desc = { /* Input/Output Group descriptor */ | |
0000700u, /* opcode mask */ | |
6u, /* opcode shift */ | |
AB_SELECT, /* A/B-register selector */ | |
BASE_SET | CPU_ALL /* applicable feature flags */ | |
}; | |
static const OP_TABLE iog_ops = { /* IOG opcodes, indexed by IR bits 11 + 8-6 */ | |
{ "HLT", 0102000u, opSCOHC }, | |
{ "", 0102100u, opNone }, /* STF/CLF and STO/CLO */ | |
{ "", 0102200u, opNone }, /* SFC and SOC */ | |
{ "", 0102300u, opNone }, /* SFS and SOS */ | |
{ "MIA", 0102400u, opSCHC }, | |
{ "LIA", 0102500u, opSCHC }, | |
{ "OTA", 0102600u, opSCHC }, | |
{ "STC", 0102700u, opSCHC }, | |
{ "HLT", 0106000u, opSCOHC }, | |
{ "", 0106100u, opNone }, /* STF/CLF and STO/CLO */ | |
{ "", 0106200u, opNone }, /* SFC and SOC */ | |
{ "", 0106300u, opNone }, /* SFS and SOS */ | |
{ "MIB", 0106400u, opSCHC }, | |
{ "LIB", 0106500u, opSCHC }, | |
{ "OTB", 0106600u, opSCHC }, | |
{ "CLC", 0106700u, opSCHC }, | |
{ "STO", 0102101u, opNone, 0173777u }, /* STF 01 */ | |
{ "STF", 0102100u, opSC, 0173700u }, /* STF nn */ | |
{ "CLO", 0103101u, opNone, 0173777u }, /* CLF 01 */ | |
{ "CLF", 0103100u, opSC, 0173700u }, /* CLF nn */ | |
{ "SOC", 0102201u, opHC, 0172777u }, /* SFC 01 */ | |
{ "SFC", 0102200u, opSCHC, 0172700u }, /* SFC nn */ | |
{ "SOS", 0102301u, opHC, 0172777u }, /* SFS 01 */ | |
{ "SFS", 0102300u, opSCHC, 0172700u }, /* SFS nn */ | |
{ NULL } | |
}; | |
/* Extended Arithmetic Group. | |
The EAG instructions are part of the MAC instruction space, which is | |
indicated by bit 15 equal to 1 and bits 14-12 and 10 equal to 0. They are | |
enabled by the Extended Arithmetic Unit feature that is optional for the 2116 | |
and standard for the 2100 and 1000-M/E/F. There are ten canonical EAG | |
instructions. Four arithmetic instructions are decoded by bits 11 and 9-6 | |
with bits 5-0 equal to 0. Six shift-rotate instructions are decoded by bits | |
11 and 9-4 with bits 3-0 indicating the shift count. The remaining 118 bit | |
combinations are undefined. | |
Three of these undefined combinations are reserved in the 1000 E/F-Series. | |
Opcode 100000 is the DIAG instruction, 100060 is the TIMER instruction, and | |
100120 is the EXECUTE instruction. The simulator implements DIAG and TIMER; | |
EXECUTE does not appear to have been implemented in the original microcode | |
and is not simulated. | |
Results of execution of the other undefined instructions are dependent on the | |
CPU model. Rather than implementing each machine's specific behavior for | |
each instruction, the simulator follows the 1000 M-Series decoding, | |
regardless of the CPU model. | |
The table consists of 11 secondary entries; the final entry allows parsing of | |
the SWP mnemonic and encoding as RRR 16 as is permitted by the HP Assembler. | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 1 | 0 0 0 | | 0 | eau op | 0 0 0 0 0 0 | EAG | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 1 | 0 0 0 | | 0 | eau shift/rotate op | shift count | EAG | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
The MAC instruction space also includes the User Instruction Group. EAG | |
instructions use the opcode ranges 100000-101377 and 104000-104777. UIG | |
instructions use the ranges 101400-101477 and 105000-105777. | |
Implementation notes: | |
1. The 2100 has the strictest decoding of the EAU set, so that is the | |
encoding represented in the operations table. | |
*/ | |
static const OP_DESC eag_desc = { /* Extended Arithmetic Group descriptor */ | |
OP_LINEAR, /* linear search only */ | |
OP_SINGLE, /* single match allowed */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_EAU | CPU_ALL /* applicable feature flags */ | |
}; | |
static const OP_TABLE eag_ops = { /* EAG opcodes, searched linearly */ | |
{ "MPY", 0100200u, opMA1I, 0177760u }, | |
{ "DIV", 0100400u, opMA1I, 0177760u }, | |
{ "DLD", 0104200u, opMA1I, 0177760u }, | |
{ "DST", 0104400u, opMA1I, 0177760u }, | |
{ "ASL", 0100020u, opShift, 0177760u }, | |
{ "LSL", 0100040u, opShift, 0177760u }, | |
{ "RRL", 0100100u, opShift, 0177760u }, | |
{ "ASR", 0101020u, opShift, 0177760u }, | |
{ "LSR", 0101040u, opShift, 0177760u }, | |
{ "RRR", 0101100u, opShift, 0177760u }, | |
{ "SWP", 0101100u, opNone, 0177777u }, /* SWP is equivalent to RRR 16 */ | |
{ NULL } | |
}; | |
static const OP_DESC eag_ef_desc = { /* Extended Arithmetic Group 1000-E/F descriptor */ | |
OP_LINEAR, /* linear search only */ | |
OP_SINGLE, /* single match allowed */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_EAU | CPU_1000_E | CPU_1000_F /* applicable feature flags */ | |
}; | |
static const OP_TABLE eag_ef_ops = { /* EAG opcodes, searched linearly */ | |
{ "DIAG", 0100000u, opNone, 0177777u }, | |
{ "TIMER", 0100060u, opNone, 0177777u }, | |
{ NULL } | |
}; | |
/* User Instruction Group. | |
The UIG instructions are part of the MAC instruction space, which is | |
indicated by bit 15 equal to 1 and bits 14-12 and 10 equal to 0. They are | |
divided into two sub-groups. The first, UIG-0, occupies opcodes | |
105000-105377. The second, UIG-1, occupies opcodes 101400-101777 and | |
105400-105777. The 2100 decodes only UIG-0 instructions, whereas the 1000s | |
use both UIG sets. In particular, the 105740-105777 range is used by the | |
1000 Extended Instruction Group (EIG), which is part of the base set. | |
All of the optional firmware sets for the 2100 and 1000-M/E/F CPUs implement | |
UIG instructions. Therefore, there are separate tables for each firmware | |
feature. | |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
| 1 | 0 0 0 | B | 0 1 | module | operation | UIG | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | |
Where: | |
B = A/B register (0/1) | |
For instructions in the range 10x400-10x777, bit 11 optionally may be | |
significant. If it is, then the 101xxx and 105xxx instructions are distinct | |
and require separate table entries. If it is not, then either 105xxx or | |
101xxx may be used to invoke the instruction, although 105xxx is the | |
canonical form. | |
Implementation notes: | |
1. If bit 11 is significant for some instructions and not for others, then | |
the instructions that ignore bit 11 must be duplicated in the table and | |
given the 105xxx forms in both sets of entries. This ensures that when | |
used as a parsing table, the canonical form is obtained. | |
*/ | |
/* 105000-105362 2000 I/O Processor Instructions */ | |
static const OP_DESC iop_2100_desc = { /* 2100 I/O Processor descriptor */ | |
OP_LINEAR, /* linear search only */ | |
OP_SINGLE, /* single match allowed */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_IOP | CPU_2100 /* applicable feature flags */ | |
}; | |
static const OP_TABLE iop_2100_ops = { /* 2100 IOP opcodes, searched linearly */ | |
{ "SBYTE", 0105300u, opNone, 0177777u }, | |
{ "LBYTE", 0105320u, opNone, 0177777u }, | |
{ "MBYTE", 0105120u, opMA1I, 0177777u }, | |
{ "MWORD", 0105200u, opMA1ZI, 0177777u }, | |
{ "LAI", 0105020u, opIOPON, 0177777u }, /* these must be split into negative and positive operands */ | |
{ "LAI", 0105040u, opIOPOP, 0177777u }, /* because the offset does not occupy bits */ | |
{ "SAI", 0105060u, opIOPON, 0177777u }, /* reserved for the value but instead are */ | |
{ "SAI", 0105100u, opIOPOP, 0177777u }, /* ADDED to the base opcode value */ | |
{ "CRC", 0105150u, opV1, 0177777u }, | |
{ "RESTR", 0105340u, opNone, 0177777u }, | |
{ "READF", 0105220u, opNone, 0177777u }, | |
{ "ENQ", 0105240u, opNone, 0177777u }, | |
{ "PENQ", 0105257u, opNone, 0177777u }, | |
{ "DEQ", 0105260u, opNone, 0177777u }, | |
{ "TRSLT", 0105160u, opV1, 0177777u }, | |
{ "ILIST", 0105000u, opA1V1, 0177777u }, | |
{ "PRFEI", 0105222u, opV2A1, 0177777u }, | |
{ "PRFEX", 0105223u, opMA1I, 0177777u }, | |
{ "PRFIO", 0105221u, opV2, 0177777u }, | |
{ "SAVE", 0105362u, opNone, 0177777u }, | |
{ NULL } | |
}; | |
/* 105000-105137 Single-Precision Floating Point Instructions */ | |
static const OP_DESC fp_desc = { /* Single-Precision Floating Point descriptor */ | |
OP_LINEAR, /* linear search only */ | |
OP_SINGLE, /* single match allowed */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_FP | CPU_2100 | CPU_1000_M | CPU_1000_E /* applicable feature flags */ | |
}; | |
static const OP_TABLE fp_ops = { /* FP opcodes, searched linearly */ | |
{ "FAD", 0105000u, opMA1I, 0177760u }, /* bits 3-0 do not affect decoding */ | |
{ "FSB", 0105020u, opMA1I, 0177760u }, /* bits 3-0 do not affect decoding */ | |
{ "FMP", 0105040u, opMA1I, 0177760u }, /* bits 3-0 do not affect decoding */ | |
{ "FDV", 0105060u, opMA1I, 0177760u }, /* bits 3-0 do not affect decoding */ | |
{ "FIX", 0105100u, opNone, 0177760u }, /* bits 3-0 do not affect decoding */ | |
{ "FLT", 0105120u, opNone, 0177760u }, /* bits 3-0 do not affect decoding */ | |
{ NULL } | |
}; | |
/* 105000-105137 Floating Point Processor Instructions */ | |
static const OP_DESC fpp_desc = { /* Floating Point Processor descriptor */ | |
0000137u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_FP | CPU_1000_F /* applicable feature flags */ | |
}; | |
static const OP_TABLE fpp_ops = { /* FPP opcodes, indexed by IR bits 6-0 */ | |
{ "FAD", 0105000u, opMA1I }, | |
{ ".XADD", 0105001u, opMA3I }, | |
{ ".TADD", 0105002u, opMA3I }, | |
{ ".EADD", 0105003u, opMA3I }, | |
{ "fptst", 0105004u, opNone }, /* self-test */ | |
{ "xexp", 0105005u, opNone }, /* expand exponent */ | |
{ "reset", 0105006u, opNone }, /* processor reset */ | |
{ "exstk", 0105007u, opNone }, /* execute a stack of operands */ | |
{ "adchk", 0105010u, opNone }, /* addressing check */ | |
{ "", 0105011u, opNone }, /* unimplemented */ | |
{ "", 0105012u, opNone }, /* unimplemented */ | |
{ "", 0105013u, opNone }, /* unimplemented */ | |
{ ".DAD", 0105014u, opMA1I }, | |
{ "", 0105015u, opNone }, /* unimplemented */ | |
{ "", 0105016u, opNone }, /* unimplemented */ | |
{ "", 0105017u, opNone }, /* unimplemented */ | |
{ "FSB", 0105020u, opMA1I }, | |
{ ".XSUB", 0105021u, opMA3I }, | |
{ ".TSUB", 0105022u, opMA3I }, | |
{ ".ESUB", 0105023u, opMA3I }, | |
{ "", 0105024u, opNone }, /* unimplemented */ | |
{ "", 0105025u, opNone }, /* unimplemented */ | |
{ "", 0105026u, opNone }, /* unimplemented */ | |
{ "", 0105027u, opNone }, /* unimplemented */ | |
{ "", 0105030u, opNone }, /* unimplemented */ | |
{ "", 0105031u, opNone }, /* unimplemented */ | |
{ "", 0105032u, opNone }, /* unimplemented */ | |
{ "", 0105033u, opNone }, /* unimplemented */ | |
{ ".DSB", 0105034u, opMA1I }, | |
{ "", 0105035u, opNone }, /* unimplemented */ | |
{ "", 0105036u, opNone }, /* unimplemented */ | |
{ "", 0105037u, opNone }, /* unimplemented */ | |
{ "FMP", 0105040u, opMA1I }, | |
{ ".XMPY", 0105041u, opMA3I }, | |
{ ".TMPY", 0105042u, opMA3I }, | |
{ ".EMPY", 0105043u, opMA3I }, | |
{ "", 0105044u, opNone }, /* unimplemented */ | |
{ "", 0105045u, opNone }, /* unimplemented */ | |
{ "", 0105046u, opNone }, /* unimplemented */ | |
{ "", 0105047u, opNone }, /* unimplemented */ | |
{ "", 0105050u, opNone }, /* unimplemented */ | |
{ "", 0105051u, opNone }, /* unimplemented */ | |
{ "", 0105052u, opNone }, /* unimplemented */ | |
{ "", 0105053u, opNone }, /* unimplemented */ | |
{ ".DMP", 0105054u, opMA1I }, | |
{ "", 0105055u, opNone }, /* unimplemented */ | |
{ "", 0105056u, opNone }, /* unimplemented */ | |
{ "", 0105057u, opNone }, /* unimplemented */ | |
{ "FDV", 0105060u, opMA1I }, | |
{ ".XDIV", 0105061u, opMA3I }, | |
{ ".TDIV", 0105062u, opMA3I }, | |
{ ".EDIV", 0105063u, opMA3I }, | |
{ "", 0105064u, opNone }, /* unimplemented */ | |
{ "", 0105065u, opNone }, /* unimplemented */ | |
{ "", 0105066u, opNone }, /* unimplemented */ | |
{ "", 0105067u, opNone }, /* unimplemented */ | |
{ "", 0105070u, opNone }, /* unimplemented */ | |
{ "", 0105071u, opNone }, /* unimplemented */ | |
{ "", 0105072u, opNone }, /* unimplemented */ | |
{ "", 0105073u, opNone }, /* unimplemented */ | |
{ ".DDI", 0105074u, opMA1I }, | |
{ "", 0105075u, opNone }, /* unimplemented */ | |
{ "", 0105076u, opNone }, /* unimplemented */ | |
{ "", 0105077u, opNone }, /* unimplemented */ | |
{ "FIX", 0105100u, opNone }, | |
{ ".XFXS", 0105101u, opMA1I }, | |
{ ".TXFS", 0105102u, opMA1I }, | |
{ ".EXFS", 0105103u, opMA1I }, | |
{ ".FIXD", 0105104u, opNone }, | |
{ ".XFXD", 0105105u, opMA1I }, | |
{ ".TFXD", 0105106u, opMA1I }, | |
{ ".EFXD", 0105107u, opMA1I }, | |
{ "", 0105110u, opNone }, /* unimplemented */ | |
{ "", 0105111u, opNone }, /* unimplemented */ | |
{ "", 0105112u, opNone }, /* unimplemented */ | |
{ "", 0105113u, opNone }, /* unimplemented */ | |
{ ".DSBR", 0105114u, opMA1I }, | |
{ "", 0105115u, opNone }, /* unimplemented */ | |
{ "", 0105116u, opNone }, /* unimplemented */ | |
{ "", 0105117u, opNone }, /* unimplemented */ | |
{ "FLT", 0105120u, opNone }, | |
{ ".XFTS", 0105121u, opMA1I }, | |
{ ".TFTS", 0105122u, opMA1I }, | |
{ ".EFTS", 0105123u, opMA1I }, | |
{ ".FLTD", 0105124u, opNone }, | |
{ ".XFTD", 0105125u, opMA1I }, | |
{ ".TFTD", 0105126u, opMA1I }, | |
{ ".EFTD", 0105127u, opMA1I }, | |
{ "", 0105130u, opNone }, /* unimplemented */ | |
{ "", 0105131u, opNone }, /* unimplemented */ | |
{ "", 0105132u, opNone }, /* unimplemented */ | |
{ "", 0105133u, opNone }, /* unimplemented */ | |
{ ".DDIR", 0105134u, opMA1I }, | |
{ "", 0105135u, opNone }, /* unimplemented */ | |
{ "", 0105136u, opNone }, /* unimplemented */ | |
{ "", 0105137u, opNone }, /* unimplemented */ | |
{ NULL } | |
}; | |
/* 105200-105237 Fast FORTRAN Processor Instructions */ | |
static const OP_DESC ffp_2100_desc = { /* 2100 Fast FORTRAN Processor descriptor */ | |
0000037u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_FFP | CPU_2100 /* applicable feature flags */ | |
}; | |
static const OP_TABLE ffp_2100_ops = { /* 2100 FFP opcodes, indexed by IR bits 4-0 */ | |
{ "", 0105200u, opNone }, /* unimplemented */ | |
{ "DBLE", 0105201u, opMA3I }, | |
{ "SNGL", 0105202u, opMA2I }, | |
{ ".XMPY", 0105203u, opMA3I }, | |
{ ".XDIV", 0105204u, opMA3I }, | |
{ ".DFER", 0105205u, opMA2I }, | |
{ "", 0105206u, opNone }, /* unimplemented */ | |
{ "", 0105207u, opNone }, /* unimplemented */ | |
{ "", 0105210u, opNone }, /* unimplemented */ | |
{ "", 0105211u, opNone }, /* unimplemented */ | |
{ "", 0105212u, opNone }, /* unimplemented */ | |
{ ".XADD", 0105213u, opMA3I }, | |
{ ".XSUB", 0105214u, opMA3I }, | |
{ "", 0105215u, opNone }, /* unimplemented */ | |
{ "", 0105216u, opNone }, /* unimplemented */ | |
{ "", 0105217u, opNone }, /* unimplemented */ | |
{ ".XFER", 0105220u, opNone }, | |
{ ".GOTO", 0105221u, opMA2I }, | |
{ "..MAP", 0105222u, opMA4I }, | |
{ ".ENTR", 0105223u, opMA1I }, | |
{ ".ENTP", 0105224u, opMA1I }, | |
{ "", 0105225u, opNone }, /* unimplemented */ | |
{ "", 0105226u, opNone }, /* unimplemented */ | |
{ "$SETP", 0105227u, opMA1I }, | |
{ "", 0105230u, opNone }, /* unimplemented */ | |
{ "", 0105231u, opNone }, /* unimplemented */ | |
{ "", 0105232u, opNone }, /* unimplemented */ | |
{ "", 0105233u, opNone }, /* unimplemented */ | |
{ "", 0105234u, opNone }, /* unimplemented */ | |
{ "", 0105235u, opNone }, /* unimplemented */ | |
{ "", 0105236u, opNone }, /* unimplemented */ | |
{ "", 0105237u, opNone }, /* unimplemented */ | |
{ NULL } | |
}; | |
static const OP_DESC ffp_m_desc = { /* M-Series Fast FORTRAN Processor descriptor */ | |
0000037u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_FFP | CPU_1000_M /* applicable feature flags */ | |
}; | |
static const OP_TABLE ffp_m_ops = { /* M-Series FFP opcodes, indexed by IR bits 4-0 */ | |
{ "fftst", 0105200u, opNone }, /* self-test */ | |
{ "DBLE", 0105201u, opMA3I }, | |
{ "SNGL", 0105202u, opMA2I }, | |
{ ".XMPY", 0105203u, opMA3I }, | |
{ ".XDIV", 0105204u, opMA3I }, | |
{ ".DFER", 0105205u, opMA2I }, | |
{ ".XPAK", 0105206u, opMA1I }, | |
{ "XADD", 0105207u, opMA4I }, | |
{ "XSUB", 0105210u, opMA4I }, | |
{ "XMPY", 0105211u, opMA4I }, | |
{ "XDIV", 0105212u, opMA4I }, | |
{ ".XADD", 0105213u, opMA3I }, | |
{ ".XSUB", 0105214u, opMA3I }, | |
{ ".XCOM", 0105215u, opMA1I }, | |
{ "..DCM", 0105216u, opMA1I }, | |
{ "DDINT", 0105217u, opMA3I }, | |
{ ".XFER", 0105220u, opNone }, | |
{ ".GOTO", 0105221u, opMA2I }, | |
{ "..MAP", 0105222u, opMA4I }, | |
{ ".ENTR", 0105223u, opMA1I }, | |
{ ".ENTP", 0105224u, opMA1I }, | |
{ ".PWR2", 0105225u, opMA2I }, | |
{ ".FLUN", 0105226u, opMA1I }, | |
{ "$SETP", 0105227u, opMA1I }, | |
{ ".PACK", 0105230u, opMA2I }, | |
{ "", 0105231u, opNone }, /* unimplemented */ | |
{ "", 0105232u, opNone }, /* unimplemented */ | |
{ "", 0105233u, opNone }, /* unimplemented */ | |
{ "", 0105234u, opNone }, /* unimplemented */ | |
{ "", 0105235u, opNone }, /* unimplemented */ | |
{ "", 0105236u, opNone }, /* unimplemented */ | |
{ "", 0105237u, opNone }, /* unimplemented */ | |
{ NULL } | |
}; | |
static const OP_DESC ffp_e_desc = { /* E-Series Fast FORTRAN Processor descriptor */ | |
0000037u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_FFP | CPU_1000_E /* applicable feature flags */ | |
}; | |
static const OP_TABLE ffp_e_ops = { /* E-Series FFP opcodes, indexed by IR bits 4-0 */ | |
{ "fftst", 0105200u, opNone }, /* self-test */ | |
{ "DBLE", 0105201u, opMA3I }, | |
{ "SNGL", 0105202u, opMA2I }, | |
{ ".XMPY", 0105203u, opMA3I }, | |
{ ".XDIV", 0105204u, opMA3I }, | |
{ ".DFER", 0105205u, opMA2I }, | |
{ ".XPAK", 0105206u, opMA1I }, | |
{ "XADD", 0105207u, opMA4I }, | |
{ "XSUB", 0105210u, opMA4I }, | |
{ "XMPY", 0105211u, opMA4I }, | |
{ "XDIV", 0105212u, opMA4I }, | |
{ ".XADD", 0105213u, opMA3I }, | |
{ ".XSUB", 0105214u, opMA3I }, | |
{ ".XCOM", 0105215u, opMA1I }, | |
{ "..DCM", 0105216u, opMA1I }, | |
{ "DDINT", 0105217u, opMA3I }, | |
{ ".XFER", 0105220u, opNone }, | |
{ ".GOTO", 0105221u, opMA2I }, | |
{ "..MAP", 0105222u, opMA4I }, | |
{ ".ENTR", 0105223u, opMA1I }, | |
{ ".ENTP", 0105224u, opMA1I }, | |
{ ".PWR2", 0105225u, opMA2I }, | |
{ ".FLUN", 0105226u, opMA1I }, | |
{ "$SETP", 0105227u, opMA1I }, | |
{ ".PACK", 0105230u, opMA2I }, | |
{ ".CFER", 0105231u, opMA2I }, | |
{ "", 0105232u, opNone }, /* unimplemented */ | |
{ "", 0105233u, opNone }, /* unimplemented */ | |
{ "", 0105234u, opNone }, /* unimplemented */ | |
{ "", 0105235u, opNone }, /* unimplemented */ | |
{ "", 0105236u, opNone }, /* unimplemented */ | |
{ "", 0105237u, opNone }, /* unimplemented */ | |
{ NULL } | |
}; | |
static const OP_DESC ffp_f_desc = { /* F-Series Fast FORTRAN Processor descriptor */ | |
0000037u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_FFP | CPU_1000_F /* applicable feature flags */ | |
}; | |
static const OP_TABLE ffp_f_ops = { /* F-Series FFP opcodes, indexed by IR bits 4-0 */ | |
{ "fftst", 0105200u, opNone }, /* self-test */ | |
{ "DBLE", 0105201u, opMA3I }, | |
{ "SNGL", 0105202u, opMA2I }, | |
{ ".DNG", 0105203u, opNone }, | |
{ ".DCO", 0105204u, opNone }, | |
{ ".DFER", 0105205u, opMA2I }, | |
{ ".XPAK", 0105206u, opMA1I }, | |
{ ".BLE", 0105207u, opMA3I }, | |
{ ".DIN", 0105210u, opNone }, | |
{ ".DDE", 0105211u, opNone }, | |
{ ".DIS", 0105212u, opNone }, | |
{ ".DDS", 0105213u, opNone }, | |
{ ".NGL", 0105214u, opMA2I }, | |
{ ".XCOM", 0105215u, opMA1I }, | |
{ "..DCM", 0105216u, opMA1I }, | |
{ "DDINT", 0105217u, opMA3I }, | |
{ ".XFER", 0105220u, opNone }, | |
{ ".GOTO", 0105221u, opMA2I }, | |
{ "..MAP", 0105222u, opMA4I }, | |
{ ".ENTR", 0105223u, opMA1I }, | |
{ ".ENTP", 0105224u, opMA1I }, | |
{ ".PWR2", 0105225u, opMA2I }, | |
{ ".FLUN", 0105226u, opMA1I }, | |
{ "$SETP", 0105227u, opMA1I }, | |
{ ".PACK", 0105230u, opMA2I }, | |
{ ".CFER", 0105231u, opMA2I }, | |
{ "..FCM", 0105232u, opMA1I }, | |
{ "..TCM", 0105233u, opMA1I }, | |
{ "", 0105234u, opNone }, /* unimplemented */ | |
{ "", 0105235u, opNone }, /* unimplemented */ | |
{ "", 0105236u, opNone }, /* unimplemented */ | |
{ "", 0105237u, opNone }, /* unimplemented */ | |
{ NULL } | |
}; | |
/* 105240-105257 RTE-IVA/B Extended Memory Instructions */ | |
static const OP_DESC ema_desc = { /* Extended Memory Area descriptor */ | |
0000017u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_EMA | CPU_1000_E | CPU_1000_F /* applicable feature flags */ | |
}; | |
static const OP_TABLE ema_ops = { /* EMA opcodes, indexed by IR bits 3-0 */ | |
{ ".EMIO", 0105240u, opMA3I }, /* variable operand count */ | |
{ "MMAP", 0105241u, opMA3I }, | |
{ "emtst", 0105242u, opNone }, /* self-test */ | |
{ "", 0105243u, opNone }, /* unimplemented */ | |
{ "", 0105244u, opNone }, /* unimplemented */ | |
{ "", 0105245u, opNone }, /* unimplemented */ | |
{ "", 0105246u, opNone }, /* unimplemented */ | |
{ "", 0105247u, opNone }, /* unimplemented */ | |
{ "", 0105250u, opNone }, /* unimplemented */ | |
{ "", 0105251u, opNone }, /* unimplemented */ | |
{ "", 0105252u, opNone }, /* unimplemented */ | |
{ "", 0105253u, opNone }, /* unimplemented */ | |
{ "", 0105254u, opNone }, /* unimplemented */ | |
{ "", 0105255u, opNone }, /* unimplemented */ | |
{ "", 0105256u, opNone }, /* unimplemented */ | |
{ ".EMAP", 0105257u, opMA3I }, /* variable operand count */ | |
{ NULL } | |
}; | |
/* 105240-105257 RTE-6/VM Virtual Memory Instructions */ | |
static const OP_DESC vma_desc = { /* Virtual Memory Area descriptor */ | |
0000017u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_VMAOS | CPU_1000_E | CPU_1000_F /* applicable feature flags */ | |
}; | |
static const OP_TABLE vma_ops = { /* VMA opcodes, indexed by IR bits 3-0 */ | |
{ ".PMAP", 0105240u, opNone }, | |
{ "$LOC", 0105241u, opMA6I }, | |
{ "vmtst", 0105242u, opNone }, /* self-test */ | |
{ ".SWP", 0105243u, opNone }, | |
{ ".STAS", 0105244u, opNone }, | |
{ ".LDAS", 0105245u, opNone }, | |
{ "", 0105246u, opNone }, /* unimplemented */ | |
{ ".UMPY", 0105247u, opMA1I }, | |
{ ".IMAP", 0105250u, opMA1I }, /* operand count varies from 1-n */ | |
{ ".IMAR", 0105251u, opMA1I }, | |
{ ".JMAP", 0105252u, opMA1I }, /* operand count varies from 1-n */ | |
{ ".JMAR", 0105253u, opMA1I }, | |
{ ".LPXR", 0105254u, opMA2I }, | |
{ ".LPX", 0105255u, opMA1I }, | |
{ ".LBPR", 0105256u, opMA1I }, | |
{ ".LBP", 0105257u, opNone }, | |
{ NULL } | |
}; | |
/* 105320-105337 Double Integer Instructions */ | |
static const OP_DESC dbi_desc = { /* Double Integer Instructions descriptor */ | |
0000017u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_DBI | CPU_1000_E /* applicable feature flags */ | |
}; | |
static const OP_TABLE dbi_ops = { /* DBI opcodes, indexed by IR bits 3-0 */ | |
{ "dbtst", 0105320u, opNone }, /* self-test */ | |
{ ".DAD", 0105321u, opMA1I }, | |
{ ".DMP", 0105322u, opMA1I }, | |
{ ".DNG", 0105323u, opNone }, | |
{ ".DCO", 0105324u, opMA1I }, | |
{ ".DDI", 0105325u, opMA1I }, | |
{ ".DDIR", 0105326u, opMA1I }, | |
{ ".DSB", 0105327u, opMA1I }, | |
{ ".DIN", 0105330u, opNone }, | |
{ ".DDE", 0105331u, opNone }, | |
{ ".DIS", 0105332u, opMA1I }, | |
{ ".DDS", 0105333u, opMA1I }, | |
{ ".DSBR", 0105334u, opMA1I }, | |
{ "", 0105335u, opNone }, /* unimplemented */ | |
{ "", 0105336u, opNone }, /* unimplemented */ | |
{ "", 0105337u, opNone }, /* unimplemented */ | |
{ NULL } | |
}; | |
/* 105320-105337 Scientific Instruction Set */ | |
static const OP_DESC sis_desc = { /* Scientific Instruction Set descriptor */ | |
0000017u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_UNUSED, /* A/B-register selector */ | |
BASE_SET | CPU_1000_F /* applicable feature flags */ | |
}; | |
static const OP_TABLE sis_ops = { /* SIS opcodes, indexed by IR bits 3-0 */ | |
{ "TAN", 0105320u, opNone }, | |
{ "SQRT", 0105321u, opNone }, | |
{ "ALOG", 0105322u, opNone }, | |
{ "ATAN", 0105323u, opNone }, | |
{ "COS", 0105324u, opNone }, | |
{ "SIN", 0105325u, opNone }, | |
{ "EXP", 0105326u, opNone }, | |
{ "ALOGT", 0105327u, opNone }, | |
{ "TANH", 0105330u, opNone }, | |
{ "DPOLY", 0105331u, opV1A5 }, | |
{ "/CMRT", 0105332u, opMA3I }, | |
{ "/ATLG", 0105333u, opMA1I }, | |
{ ".FPWR", 0105334u, opMA1I }, | |
{ ".TPWR", 0105335u, opMA2I }, | |
{ "", 0105336u, opNone }, /* unimplemented */ | |
{ "sitst", 0105337u, opNone }, /* self-test */ | |
{ NULL } | |
}; | |
/* 105340-105357 RTE-6/VM Operating System Instructions */ | |
static const OP_DESC os_desc = { /* RTE-6/VM Operating System descriptor */ | |
0000017u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_VMAOS | CPU_1000_E | CPU_1000_F /* applicable feature flags */ | |
}; | |
static const OP_TABLE os_ops = { /* RTE-6/VM OS opcodes, indexed by IR bits 3-0 */ | |
{ "$LIBR", 0105340u, opMA1I }, | |
{ "$LIBX", 0105341u, opMA1I }, | |
{ ".TICK", 0105342u, opNone }, | |
{ ".TNAM", 0105343u, opNone }, | |
{ ".STIO", 0105344u, opMA1I }, /* operand count varies from 1-n */ | |
{ ".FNW", 0105345u, opMA1I }, | |
{ ".IRT", 0105346u, opMA1I }, | |
{ ".LLS", 0105347u, opMA2I }, | |
{ ".SIP", 0105350u, opNone }, | |
{ ".YLD", 0105351u, opMA1I }, | |
{ ".CPM", 0105352u, opMA2I }, | |
{ ".ETEQ", 0105353u, opNone }, | |
{ ".ENTN", 0105354u, opMA1I }, | |
{ "ostst", 0105355u, opNone }, /* self-test */ | |
{ ".ENTC", 0105356u, opMA1I }, | |
{ ".DSPI", 0105357u, opNone }, | |
{ NULL } | |
}; | |
static const OP_DESC trap_desc = { /* RTE-6/VM Operating System trap instructions descriptor */ | |
OP_LINEAR, /* linear search only */ | |
OP_SINGLE, /* single match allowed */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_VMAOS | CPU_1000_E | CPU_1000_F /* applicable feature flags */ | |
}; | |
static const OP_TABLE trap_ops = { /* RTE-6/VM OS trap opcodes, searched linearly */ | |
{ "$DCPC", 0105354u, opNone, 0177777u }, | |
{ "$MPV", 0105355u, opNone, 0177777u }, | |
{ "$DEV", 0105356u, opNone, 0177777u }, | |
{ "$TBG", 0105357u, opNone, 0177777u }, | |
{ NULL } | |
}; | |
/* 10x400-10x437 2000 I/O Processor Instructions */ | |
static const OP_DESC iop1_1000_desc = { /* M/E-Series I/O Processor Instructions descriptor */ | |
OP_LINEAR, /* linear search only */ | |
OP_SINGLE, /* single match allowed */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_IOP | CPU_1000_M | CPU_1000_E /* applicable feature flags */ | |
}; | |
static const OP_TABLE iop1_1000_ops = { /* M/E-Series IOP opcodes, searched linearly */ | |
{ "SAI", 0101400u, opIOPO, 0177777u }, | |
{ "LAI", 0105400u, opIOPO, 0177777u }, | |
{ NULL } | |
}; | |
/* 10x460-10x477 2000 I/O Processor Instructions */ | |
static const OP_DESC iop2_1000_desc = { /* M/E-Series I/O Processor Instructions descriptor */ | |
0000017u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_IOP | CPU_1000_M | CPU_1000_E /* applicable feature flags */ | |
}; | |
static const OP_TABLE iop2_1000_ops = { /* M/E-Series IOP opcodes, indexed by IR bits 3-0 */ | |
{ "CRC", 0105460u, opV1 }, | |
{ "RESTR", 0105461u, opNone }, | |
{ "READF", 0105462u, opNone }, | |
{ "INS", 0105463u, opNone }, | |
{ "ENQ", 0105464u, opNone }, | |
{ "PENQ", 0105465u, opNone }, | |
{ "DEQ", 0105466u, opNone }, | |
{ "TRSLT", 0105467u, opV1 }, | |
{ "ILIST", 0105470u, opA1V1 }, | |
{ "PRFEI", 0105471u, opV2A1 }, | |
{ "PRFEX", 0105472u, opMA1I }, | |
{ "PRFIO", 0105473u, opV2 }, | |
{ "SAVE", 0105474u, opNone }, | |
{ "", 0105475u, opNone }, /* unimplemented */ | |
{ "", 0105476u, opNone }, /* unimplemented */ | |
{ "", 0105477u, opNone }, /* unimplemented */ | |
{ NULL } | |
}; | |
/* 10x460-10x477 Vector Instruction Set */ | |
static const OP_DESC vis_desc = { /* Vector Instruction Set descriptor */ | |
0000017u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_SELECT, /* A/B-register selector */ | |
UNIT_VIS | CPU_1000_F /* applicable feature flags */ | |
}; | |
static const OP_TABLE vis_ops = { /* VIS opcodes, indexed by IR bits 11 + 3-0 */ | |
{ "", 0101460u, opNone }, /* .VECT single-precision */ | |
{ "VPIV", 0101461u, opZA8 }, | |
{ "VABS", 0101462u, opZA5 }, | |
{ "VSUM", 0101463u, opZA4 }, | |
{ "VNRM", 0101464u, opZA4 }, | |
{ "VDOT", 0101465u, opZA6 }, | |
{ "VMAX", 0101466u, opZA4 }, | |
{ "VMAB", 0101467u, opZA4 }, | |
{ "VMIN", 0101470u, opZA4 }, | |
{ "VMIB", 0101471u, opZA4 }, | |
{ "VMOV", 0101472u, opZA5 }, | |
{ "VSWP", 0101473u, opZA5 }, | |
{ ".ERES", 0101474u, opMA3I }, /* variable operand count */ | |
{ ".ESEG", 0101475u, opMA2I }, | |
{ ".VSET", 0101476u, opMA7I }, | |
{ "vitst", 0105477u, opNone }, /* self-test */ | |
{ "", 0105460u, opNone }, /* .VECT double-precision */ | |
{ "DVPIV", 0105461u, opZA8 }, | |
{ "DVABS", 0105462u, opZA5 }, | |
{ "DVSUM", 0105463u, opZA4 }, | |
{ "DVNRM", 0105464u, opZA4 }, | |
{ "DVDOT", 0105465u, opZA6 }, | |
{ "DVMAX", 0105466u, opZA4 }, | |
{ "DVMAB", 0105467u, opZA4 }, | |
{ "DVMIN", 0105470u, opZA4 }, | |
{ "DVMIB", 0105471u, opZA4 }, | |
{ "DVMOV", 0105472u, opZA5 }, | |
{ "DVSWP", 0105473u, opZA5 }, | |
{ ".ERES", 0101474u, opMA3I }, /* variable operand count */ | |
{ ".ESEG", 0101475u, opMA2I }, | |
{ ".VSET", 0101476u, opMA7I }, | |
{ "vitst", 0105477u, opNone }, /* self-test */ | |
{ "VADD", TO_DWORD (0000000u, 0101460u), opMA7I, D32_MASK }, | |
{ "VSUB", TO_DWORD (0000020u, 0101460u), opMA7I, D32_MASK }, | |
{ "VMPY", TO_DWORD (0000040u, 0101460u), opMA7I, D32_MASK }, | |
{ "VDIV", TO_DWORD (0000060u, 0101460u), opMA7I, D32_MASK }, | |
{ "VSAD", TO_DWORD (0000400u, 0101460u), opMA6I, D32_MASK }, | |
{ "VSSB", TO_DWORD (0000420u, 0101460u), opMA6I, D32_MASK }, | |
{ "VSMY", TO_DWORD (0000440u, 0101460u), opMA6I, D32_MASK }, | |
{ "VSDV", TO_DWORD (0000460u, 0101460u), opMA6I, D32_MASK }, | |
{ "", 0101460u, opNone, 0177777u }, /* catch unimplemented two-word instructions */ | |
{ "DVADD", TO_DWORD (0004002u, 0105460u), opMA7I, D32_MASK }, | |
{ "DVSUB", TO_DWORD (0004022u, 0105460u), opMA7I, D32_MASK }, | |
{ "DVMPY", TO_DWORD (0004042u, 0105460u), opMA7I, D32_MASK }, | |
{ "DVDIV", TO_DWORD (0004062u, 0105460u), opMA7I, D32_MASK }, | |
{ "DVSAD", TO_DWORD (0004402u, 0105460u), opMA6I, D32_MASK }, | |
{ "DVSSB", TO_DWORD (0004422u, 0105460u), opMA6I, D32_MASK }, | |
{ "DVSMY", TO_DWORD (0004442u, 0105460u), opMA6I, D32_MASK }, | |
{ "DVSDV", TO_DWORD (0004462u, 0105460u), opMA6I, D32_MASK }, | |
{ "", 0105460u, opNone, 0177777u }, /* catch unimplemented two-word instructions */ | |
{ NULL } | |
}; | |
/* 10x600-10x617 SIGNAL/1000 Instruction Set */ | |
static const OP_DESC sig_desc = { /* SIGNAL/1000 Instruction Set descriptor */ | |
0000017u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_UNUSED, /* A/B-register selector */ | |
UNIT_SIGNAL | CPU_1000_F /* applicable feature flags */ | |
}; | |
static const OP_TABLE sig_ops = { /* SIGNAL opcodes, indexed by IR bits 3-0 */ | |
{ "BITRV", 0105600u, opMA4I }, | |
{ "BTRFY", 0105601u, opMA6I }, | |
{ "UNSCR", 0105602u, opMA6I }, | |
{ "PRSCR", 0105603u, opMA6I }, | |
{ "BITR1", 0105604u, opMA5I }, | |
{ "BTRF1", 0105605u, opMA7I }, | |
{ ".CADD", 0105606u, opMA3I }, | |
{ ".CSUB", 0105607u, opMA3I }, | |
{ ".CMPY", 0105610u, opMA3I }, | |
{ ".CDIV", 0105611u, opMA3I }, | |
{ "CONJG", 0105612u, opMA3I }, | |
{ "..CCM", 0105613u, opMA1I }, | |
{ "AIMAG", 0105614u, opMA2I }, | |
{ "CMPLX", 0105615u, opMA4I }, | |
{ "", 0105616u, opNone }, /* unimplemented */ | |
{ "sitst", 0105617u, opNone }, /* self-test */ | |
{ NULL } | |
}; | |
/* 10x700-10x737 Dynamic Mapping System Instructions */ | |
static const OP_DESC dms_desc = { /* Dynamic Mapping System instructions descriptor */ | |
0000037u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_SELECT, /* A/B-register selector */ | |
UNIT_DMS | CPU_1000 /* applicable feature flags */ | |
}; | |
static const OP_TABLE dms_ops = { /* DMS opcodes, indexed by IR bits 11 + 4-0 */ | |
{ "xmm", 0105700u, opNone }, /* decodes as XMM */ | |
{ "dmtst", 0105701u, opNone }, /* self-test (E/F-Series) or NOP (M-Series) */ | |
{ "MBI", 0105702u, opNone }, /* bit 11 is not significant */ | |
{ "MBF", 0105703u, opNone }, /* bit 11 is not significant */ | |
{ "MBW", 0105704u, opNone }, /* bit 11 is not significant */ | |
{ "MWI", 0105705u, opNone }, /* bit 11 is not significant */ | |
{ "MWF", 0105706u, opNone }, /* bit 11 is not significant */ | |
{ "MWW", 0105707u, opNone }, /* bit 11 is not significant */ | |
{ "SYA", 0101710u, opNone }, | |
{ "USA", 0101711u, opNone }, | |
{ "PAA", 0101712u, opNone }, | |
{ "PBA", 0101713u, opNone }, | |
{ "SSM", 0105714u, opMA1I }, /* bit 11 is not significant */ | |
{ "JRS", 0105715u, opMA2I }, /* bit 11 is not significant */ | |
{ "", 0101716u, opNone }, /* unimplemented */ | |
{ "", 0101717u, opNone }, /* unimplemented */ | |
{ "XMM", 0105720u, opNone }, /* bit 11 is not significant */ | |
{ "XMS", 0105721u, opNone }, /* bit 11 is not significant */ | |
{ "XMA", 0101722u, opNone }, | |
{ "", 0101723u, opNone }, /* unimplemented */ | |
{ "XLA", 0101724u, opMA1I }, | |
{ "XSA", 0101725u, opMA1I }, | |
{ "XCA", 0101726u, opMA1I }, | |
{ "LFA", 0101727u, opNone }, | |
{ "RSA", 0101730u, opNone }, | |
{ "RVA", 0101731u, opNone }, | |
{ "DJP", 0105732u, opMA1I }, /* bit 11 is not significant */ | |
{ "DJS", 0105733u, opMA1I }, /* bit 11 is not significant */ | |
{ "SJP", 0105734u, opMA1I }, /* bit 11 is not significant */ | |
{ "SJS", 0105735u, opMA1I }, /* bit 11 is not significant */ | |
{ "UJP", 0105736u, opMA1I }, /* bit 11 is not significant */ | |
{ "UJS", 0105737u, opMA1I }, /* bit 11 is not significant */ | |
{ "xmm", 0105700u, opNone }, /* decodes as XMM */ | |
{ "dmtst", 0105701u, opNone }, /* self-test (E/F-Series) or NOP (M-Series) */ | |
{ "MBI", 0105702u, opNone }, | |
{ "MBF", 0105703u, opNone }, | |
{ "MBW", 0105704u, opNone }, | |
{ "MWI", 0105705u, opNone }, | |
{ "MWF", 0105706u, opNone }, | |
{ "MWW", 0105707u, opNone }, | |
{ "SYB", 0105710u, opNone }, | |
{ "USB", 0105711u, opNone }, | |
{ "PAB", 0105712u, opNone }, | |
{ "PBB", 0105713u, opNone }, | |
{ "SSM", 0105714u, opMA1I }, | |
{ "JRS", 0105715u, opMA2I }, | |
{ "", 0105716u, opNone }, /* unimplemented */ | |
{ "", 0105717u, opNone }, /* unimplemented */ | |
{ "XMM", 0105720u, opNone }, | |
{ "XMS", 0105721u, opNone }, | |
{ "XMB", 0105722u, opNone }, | |
{ "", 0105723u, opNone }, /* unimplemented */ | |
{ "XLB", 0105724u, opMA1I }, | |
{ "XSB", 0105725u, opMA1I }, | |
{ "XCB", 0105726u, opMA1I }, | |
{ "LFB", 0105727u, opNone }, | |
{ "RSB", 0105730u, opNone }, | |
{ "RVB", 0105731u, opNone }, | |
{ "DJP", 0105732u, opMA1I }, | |
{ "DJS", 0105733u, opMA1I }, | |
{ "SJP", 0105734u, opMA1I }, | |
{ "SJS", 0105735u, opMA1I }, | |
{ "UJP", 0105736u, opMA1I }, | |
{ "UJS", 0105737u, opMA1I }, | |
{ NULL } | |
}; | |
/* 10x740-10x777 Extended Instruction Group */ | |
static const OP_DESC eig_desc = { /* Extended Instruction Group descriptor */ | |
0000037u, /* opcode mask */ | |
0u, /* opcode shift */ | |
AB_SELECT, /* A/B-register selector */ | |
BASE_SET | CPU_1000 /* applicable feature flags */ | |
}; | |
static const OP_TABLE eig_ops = { /* EIG opcodes, indexed by IR bits 11 + 4-0 */ | |
{ "SAX", 0101740u, opMA1I }, | |
{ "CAX", 0101741u, opNone }, | |
{ "LAX", 0101742u, opMA1I }, | |
{ "STX", 0105743u, opMA1I }, /* bit 11 is not significant */ | |
{ "CXA", 0101744u, opNone }, | |
{ "LDX", 0105745u, opMA1I }, /* bit 11 is not significant */ | |
{ "ADX", 0105746u, opMA1I }, /* bit 11 is not significant */ | |
{ "XAX", 0101747u, opNone }, | |
{ "SAY", 0101750u, opMA1I }, | |
{ "CAY", 0101751u, opNone }, | |
{ "LAY", 0101752u, opMA1I }, | |
{ "STY", 0105753u, opMA1I }, /* bit 11 is not significant */ | |
{ "CYA", 0101754u, opNone }, | |
{ "LDY", 0105755u, opMA1I }, /* bit 11 is not significant */ | |
{ "ADY", 0105756u, opMA1I }, /* bit 11 is not significant */ | |
{ "XAY", 0101757u, opNone }, | |
{ "ISX", 0105760u, opNone }, /* bit 11 is not significant */ | |
{ "DSX", 0105761u, opNone }, /* bit 11 is not significant */ | |
{ "JLY", 0105762u, opMA1I }, /* bit 11 is not significant */ | |
{ "LBT", 0105763u, opNone }, /* bit 11 is not significant */ | |
{ "SBT", 0105764u, opNone }, /* bit 11 is not significant */ | |
{ "MBT", 0105765u, opMA1ZI }, /* bit 11 is not significant */ | |
{ "CBT", 0105766u, opMA1ZI }, /* bit 11 is not significant */ | |
{ "SFB", 0105767u, opNone }, /* bit 11 is not significant */ | |
{ "ISY", 0105770u, opNone }, /* bit 11 is not significant */ | |
{ "DSY", 0105771u, opNone }, /* bit 11 is not significant */ | |
{ "JPY", 0105772u, opMA1I }, /* bit 11 is not significant */ | |
{ "SBS", 0105773u, opMA2I }, /* bit 11 is not significant */ | |
{ "CBS", 0105774u, opMA2I }, /* bit 11 is not significant */ | |
{ "TBS", 0105775u, opMA2I }, /* bit 11 is not significant */ | |
{ "CMW", 0105776u, opMA1ZI }, /* bit 11 is not significant */ | |
{ "MVW", 0105777u, opMA1ZI }, /* bit 11 is not significant */ | |
{ "SBX", 0105740u, opMA1I }, | |
{ "CBX", 0105741u, opNone }, | |
{ "LBX", 0105742u, opMA1I }, | |
{ "STX", 0105743u, opMA1I }, | |
{ "CXB", 0105744u, opNone }, | |
{ "LDX", 0105745u, opMA1I }, | |
{ "ADX", 0105746u, opMA1I }, | |
{ "XBX", 0105747u, opNone }, | |
{ "SBY", 0105750u, opMA1I }, | |
{ "CBY", 0105751u, opNone }, | |
{ "LBY", 0105752u, opMA1I }, | |
{ "STY", 0105753u, opMA1I }, | |
{ "CYB", 0105754u, opNone }, | |
{ "LDY", 0105755u, opMA1I }, | |
{ "ADY", 0105756u, opMA1I }, | |
{ "XBY", 0105757u, opNone }, | |
{ "ISX", 0105760u, opNone }, | |
{ "DSX", 0105761u, opNone }, | |
{ "JLY", 0105762u, opMA1I }, | |
{ "LBT", 0105763u, opNone }, | |
{ "SBT", 0105764u, opNone }, | |
{ "MBT", 0105765u, opMA1ZI }, | |
{ "CBT", 0105766u, opMA1ZI }, | |
{ "SFB", 0105767u, opNone }, | |
{ "ISY", 0105770u, opNone }, | |
{ "DSY", 0105771u, opNone }, | |
{ "JPY", 0105772u, opMA1I }, | |
{ "SBS", 0105773u, opMA2I }, | |
{ "CBS", 0105774u, opMA2I }, | |
{ "TBS", 0105775u, opMA2I }, | |
{ "CMW", 0105776u, opMA1ZI }, | |
{ "MVW", 0105777u, opMA1ZI }, | |
{ NULL } | |
}; | |
/* Parsing tables. | |
Symbolic entry of instructions requires parsing the command line and matching | |
the instruction mnemonic to an entry in an opcode table. The parser table | |
contains pointers to all of the opcode tables and their associated | |
descriptors. Parsing searches linearly in the order specified, so more | |
common instructions should appear before less common ones. | |
*/ | |
typedef struct { /* parser table entry */ | |
const OP_DESC *descriptor; /* opcode descriptor pointer */ | |
const OP_ENTRY *opcodes; /* opcode table pointer */ | |
} PARSER_ENTRY; | |
static const PARSER_ENTRY parser_table [] = { /* parser table array, searched linearly */ | |
{ &mrg_desc, mrg_ops }, | |
{ &srg1_desc, srg1_ops }, | |
{ &srg_udesc, srg_uops }, | |
{ &asg_udesc, asg_uops }, | |
{ &iog_desc, iog_ops }, | |
{ &eag_desc, eag_ops }, | |
{ &eag_ef_desc, eag_ef_ops }, | |
{ &iop_2100_desc, iop_2100_ops }, | |
{ &fp_desc, fp_ops }, | |
{ &fpp_desc, fpp_ops }, | |
{ &ffp_2100_desc, ffp_2100_ops }, | |
{ &ffp_m_desc, ffp_m_ops }, | |
{ &ffp_e_desc, ffp_e_ops }, | |
{ &ffp_f_desc, ffp_f_ops }, | |
{ &ema_desc, ema_ops }, | |
{ &vma_desc, vma_ops }, | |
{ &dbi_desc, dbi_ops }, | |
{ &sis_desc, sis_ops }, | |
{ &os_desc, os_ops }, | |
{ &trap_desc, trap_ops }, | |
{ &iop1_1000_desc, iop1_1000_ops }, | |
{ &iop2_1000_desc, iop2_1000_ops }, | |
{ &vis_desc, vis_ops }, | |
{ &sig_desc, sig_ops }, | |
{ &dms_desc, dms_ops }, | |
{ &eig_desc, eig_ops }, | |
{ NULL, NULL } | |
}; | |
/* 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); | |
static t_stat hp_load_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_instruction (FILE *ofile, t_addr addr, t_value *val, uint32 radix, | |
const OP_DESC op_desc, const OP_TABLE ops); | |
static t_value parse_address (CONST char *cptr, t_stat *status); | |
static t_value parse_value (CONST char *cptr, uint32 radix, t_value max, t_stat *status); | |
static t_stat parse_cpu (CONST char *cptr, t_addr addr, t_value *val, uint32 radix, SYMBOL_SOURCE target); | |
static t_stat parse_instruction (CONST char *cptr, t_addr addr, t_value *val, uint32 radix, const OP_ENTRY *optr); | |
static t_stat parse_micro_ops (const OP_ENTRY *optr, char *gbuf, t_value *val, | |
CONST char **gptr, uint32 *accumulator); | |
static int fgetword (FILE *fileref); | |
static int fputword (int data, FILE *fileref); | |
/* System interface state */ | |
static size_t device_size = 0; /* the maximum device name size */ | |
static size_t flag_size = 0; /* the maximum trace flag name size */ | |
static t_bool parse_physical = TRUE; /* the 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 */ | |
}; | |
/* System interface global SCP data definitions */ | |
char sim_name [] = "HP 2100"; /* the simulator name */ | |
int32 sim_emax = MAX_INSTR_LENGTH; /* 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) */ | |
&mp_dev, /* Memory Protect */ | |
&dma1_dev, &dma2_dev, /* DMA/DCPC */ | |
&ptr_dev, /* 2748 Paper Tape Reader */ | |
&ptp_dev, /* 2895 Paper Tape Punch */ | |
&tty_dev, /* 2752 Teleprinter */ | |
&clk_dev, /* Time-Base Generator */ | |
&lps_dev, /* 2767 Line Printer */ | |
&lpt_dev, /* 2607 Line Printer */ | |
&baci_dev, /* Buffered Asynchronous Communications Interface */ | |
&mpx_dev, /* Eight-Channel Asynchronous Multiplexer */ | |
&dpd_dev, &dpc_dev, /* 2870/7900 Disc Interface */ | |
&dqd_dev, &dqc_dev, /* 2883 Disc Interface */ | |
&drd_dev, &drc_dev, /* 277x Disc/Drum Interface */ | |
&ds_dev, /* 7905/06/20/25 MAC Disc Interface */ | |
&mtd_dev, &mtc_dev, /* 3030 Magnetic Tape Interface */ | |
&msd_dev, &msc_dev, /* 7970B/E Magnetic Tape Interface */ | |
&muxl_dev, &muxu_dev, &muxc_dev, /* Sixteen-Channel Asynchronous Multiplexer */ | |
&ipli_dev, &iplo_dev, /* Processor Interconnect Kit */ | |
&pif_dev, /* Privileged Interrupt Fence */ | |
&da_dev, /* 7906H/20H/25H ICD Disc Interface */ | |
&dc_dev, /* Dummy Disc Interface for diagnostics */ | |
NULL | |
}; /* end of the device list */ | |
#define DEVICE_COUNT (sizeof sim_devices / sizeof sim_devices [0] - 1) /* the count excludes the NULL pointer */ | |
const char *sim_stop_messages [] = { /* an array of pointers to the stop messages in STOP_nnn order */ | |
"Impossible error", /* 0 (never returned) */ | |
"Unimplemented instruction", /* STOP_UNIMPL */ | |
"Unassigned select code", /* STOP_UNSC */ | |
"Undefined instruction", /* STOP_UNDEF */ | |
"Indirect address loop", /* STOP_INDIR */ | |
"Programmed halt", /* STOP_HALT */ | |
"Breakpoint", /* STOP_BRKPNT */ | |
"Cable not connected to", /* STOP_NOCONN */ | |
"No tape loaded in", /* STOP_NOTAPE */ | |
"End of tape on" /* STOP_EOT */ | |
}; | |
/* Local command table. | |
This table defines commands and command behaviors that are specific to this | |
simulator. Specifically: | |
* EXAMINE, DEPOSIT, IEXAMINE, and IDEPOSIT accept the page/offset physical | |
address form. | |
* RUN, GO, BREAK, and NOBREAK accept the logical address form and reject | |
the page/offset physical address form. | |
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", &hp_load_cmd, 0, NULL }, | |
{ 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. | |
For this simulator, the LOAD command provides a way of installing bootstrap | |
loaders into the last 64 words of memory. The command format is: | |
LOAD <image-filename> { <select-code> } | |
...where <image-filename> is an absolute binary file containing the loader, | |
and <select-code> is an optional value from 10-77 octal used for configuring | |
the loader's I/O instructions. If the select code is omitted, the loader is | |
used as-is. When the command completes, the loader remains enabled so that | |
it may be executed. The loader should be protected if it will not be used | |
immediately. | |
The binary file must be targeted to addresses in the range x7700-x7777, where | |
"x" is irrelevant. The loaded program will be relocated to the last 64 words | |
of memory, so the desired memory size must be set before issuing the LOAD | |
command. If the configuration select code is supplied, all I/O instructions | |
in the program that reference select codes >= 10 octal will be changed by | |
adding the supplied value minus 10 to the instruction. The effect is that | |
instructions that reference select code 10 + n will be changed to reference | |
the supplied select code + n; this permits configuration of loaders that use | |
two-card interfaces. | |
The core-memory 21xx machines reserve the last 64 words in memory for a | |
access-protected resident boot loader. In hardware, the loaders could be | |
changed only by entering the replacement loader via the front panel switch | |
register. In simulation, the LOAD command serves this purpose. | |
The 1000-series machines used semiconductor memory, so the loaders were | |
implemented in socketed ROMs that were read into the last 64 words of memory | |
via the front-panel IBL (Initial Binary Loader) button. For these machines, | |
the LOAD command serves to install ROM images other than the ones included | |
with the device simulators. If the CPU is configured with more than 32K of | |
memory, the loader is installed in the last 64 words of the 32K logical | |
address space. | |
The DUMP command writes the bootstrap loader currently residing in memory to | |
an absolute binary file. The command format is: | |
DUMP <image-filename> | |
The loader must be enabled before entering the DUMP command; if the loader | |
is disabled, the output file will contain all zeros. When the command | |
completes, the loader remains enabled. The resulting file may be used in a | |
subsequent LOAD command to reload the bootstrap program. | |
Implementation notes: | |
1. Previous simulator versions did not restrict LOAD command addressing. | |
However, the LOAD command is not a convenient replacement for the ATTACH | |
PTR and BOOT PTR commands. The bootstrap loaders clear the A and B | |
registers at completion, and certain HP software depends on this behavior | |
for proper operation. The LOAD command alters nothing other than the | |
memory occupied by the program, and its use as a general absolute binary | |
paper tape loader would result in software failures. | |
2. The absolute binary format is described in Appendix H of the RTE-IV | |
Assembler Reference Manual. | |
3. The LOADed absolute binary file may contain a leader of any length, | |
including zero length (i.e., omitted). The logical end of file occurs | |
after reading ten null bytes or the physical EOF, whichever occurs first. | |
4. The DUMP command writes the 64-word loader in two records of 57 and 7 | |
words, respectively, to conform with the format written by the HP | |
Assembler. | |
*/ | |
t_stat sim_load (FILE *fptr, CONST char *cptr, CONST char *fnam, int flag) | |
{ | |
const int reclen [2] = { TO_WORD (57, 0), /* the two DUMP record length words */ | |
TO_WORD (7, 0) }; | |
const int reccnt [2] = { 57, 7 }; /* the two DUMP record word counts */ | |
int record, count, address, word, checksum; | |
t_stat result; | |
int32 trailer = 1; /* > 0 while reading leader, < 0 while reading trailer */ | |
HP_WORD select_code = 0; /* select code to configure; 0 implies no configuration */ | |
LOADER_ARRAY boot = { /* an array of two BOOT_LOADER structures */ | |
{ 000, IBL_NA, IBL_NA, { 0 } }, /* HP 21xx Loader */ | |
{ IBL_START, IBL_DMA, IBL_FWA, { 0 } } /* HP 1000 Loader */ | |
}; | |
if (flag == 0) { /* if this is a LOAD command */ | |
if (*cptr != '\0') { /* then if a parameter follows */ | |
select_code = /* then parse it as an octal number */ | |
(HP_WORD) get_uint (cptr, 8, MAXDEV, &result); | |
if (result != SCPE_OK) /* if a parse error occurred */ | |
return result; /* then report it */ | |
else if (select_code < VARDEV) /* otherwise if the select code is invalid */ | |
return SCPE_ARG; /* then report a bad argument */ | |
} | |
while (TRUE) { /* read absolute binary records from the file */ | |
do { /* skip any blank leader or trailer present */ | |
count = fgetc (fptr); /* get the next byte from the tape */ | |
if (count == EOF) /* if an EOF occurred */ | |
if (trailer > 0) /* then if we are reading the leader */ | |
return SCPE_FMT; /* then the tape format is bad */ | |
else /* otherwise we are reading the trailer */ | |
trailer = 0; /* and now we are done */ | |
else if (count == 0) /* otherwise if this is a null value */ | |
trailer = trailer + 1; /* then increment the trailer count */ | |
} | |
while (count == 0 && trailer != 0); /* continue if a null was read or trailer is exhausted */ | |
if (trailer == 0) /* if the physical EOF was seen */ | |
break; /* then the binary read is complete */ | |
else if (fgetc (fptr) == EOF) /* otherwise discard the unused byte after the record count */ | |
return SCPE_FMT; /* if it is not there, then the tape format is bad */ | |
address = fgetword (fptr); /* get the record load address word */ | |
if (address == EOF) /* if the load address is not present */ | |
return SCPE_FMT; /* then the tape format is bad */ | |
else /* otherwise */ | |
checksum = address; /* start the record checksum with the load address */ | |
if ((address & 0007777u) < 0007700u) /* if the address does not fall at the end of a 4K block */ | |
return SCPE_NXM; /* then report the address error */ | |
else /* otherwise mask the address */ | |
address = address & IBL_MASK; /* to form an index into the loader array */ | |
while (count-- > 0) { /* read the data record */ | |
word = fgetword (fptr); /* get the next data word from the file */ | |
if (word == EOF) /* if the word is not present */ | |
return SCPE_FMT; /* then the tape format is bad */ | |
else { /* otherwise */ | |
boot [0].loader [address] = (MEMORY_WORD) word; /* save the data word in */ | |
boot [1].loader [address++] = (MEMORY_WORD) word; /* both loader arrays */ | |
checksum = checksum + word; /* and include it in the record checksum */ | |
} | |
} | |
word = fgetword (fptr); /* read the record checksum word */ | |
if (word == EOF) /* if it is not present */ | |
return SCPE_FMT; /* then the tape format is bad */ | |
else if (word != (int) (checksum & D16_MASK)) /* otherwise if the checksums do not match */ | |
return SCPE_CSUM; /* then report a checksum error */ | |
else /* otherwise the record is good */ | |
trailer = -10; /* so prepare for a potential trailer */ | |
} /* and loop until all records are read */ | |
cpu_copy_loader (boot, select_code, /* install the loader */ | |
IBL_S_NOCLEAR, IBL_S_NOSET); /* and configure the select code if requested */ | |
} | |
else { /* otherwise this is a DUMP command */ | |
address = MEMSIZE - 1 & ~IBL_MASK & LA_MASK; /* the loader occupies the last 64 words in memory */ | |
for (record = 0; record < 2; record++) { /* write two absolute records */ | |
if (fputword (reclen [record], fptr) == EOF) /* starting with the record length; if it fails */ | |
return SCPE_IOERR; /* then report an I/O error */ | |
if (fputword (address, fptr) == EOF) /* write the starting address; if it fails */ | |
return SCPE_IOERR; /* then report an I/O error */ | |
checksum = address; /* start the record checksum with the load address */ | |
for (count = 0; count < reccnt [record]; count++) { /* write a data record */ | |
word = mem_examine (address++); /* get the next data word from memory */ | |
if (fputword (word, fptr) == EOF) /* write the data word; if it fails */ | |
return SCPE_IOERR; /* then report an I/O error */ | |
else /* otherwise */ | |
checksum = checksum + word; /* include it in the record checksum */ | |
} /* and loop until all words are written */ | |
if (fputword (checksum & D16_MASK, fptr) == EOF) /* write the checksum word; if it fails */ | |
return SCPE_IOERR; /* then report an I/O error */ | |
} /* loop until both records are written */ | |
} | |
return SCPE_OK; | |
} | |
/* 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: | |
Switch Interpretation | |
------ ----------------------------------------- | |
-A a single character in the right-hand byte | |
-C a two-character packed string | |
-M a CPU instruction mnemonic | |
In the absence of a mode switch, the value is displayed in a numeric format. | |
When displaying in the instruction mnemonic form, an additional format switch | |
may be specified to indicate the desired operand radix, as follows: | |
Switch Interpretation | |
------ ----------------------------------------- | |
-B a binary value | |
-O an octal value | |
-D a decimal value | |
-H a hexadecimal value | |
These switches (except for -B) may be used without a mode switch to display a | |
numeric value in the specified radix. To summarize, the valid switch | |
combinations are: | |
-C | |
-M [ -A | -B | -O | -D | -H ] | |
-A | -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. If we are being called as a result of a VM stop to display the next | |
instruction to be executed, a check is made to see if an interrupt is | |
pending and not deferred. If so, then the interrupt source and the trap | |
cell instruction are displayed as the next instruction to be executed, | |
rather than the instruction at the current PC. | |
2. The trap cell instruction is read by calling "mem_fast_read" directly | |
into the "val" array (which points to the "sim_eval" array), rather than | |
by setting the VAL_EMPTY flag and letting the "fprint_instruction" | |
routine load any required operands, because the instruction must be read | |
from the system map instead of the current map (which may be the user map | |
until the interrupt is actually processed). | |
3. When we are called to format a register, the "val" parameter points at a | |
single t_value containing the register value. However, the instruction | |
mnemonic formatter expects to receive a pointer to an array that holds | |
at least the number of words for the instruction being decoded. For | |
example, if the register holds the MPY opcode, the formatter will expect | |
the operand address in the second array word. Multi-word instructions | |
cannot be displayed correctly if they originate in a register, so the | |
best we can do is copy the value to "sim_eval [0]" and zero the remaining | |
words before calling the mnemonic formatter. A call to "memset" is used | |
because this can be optimized to a single REP STOSD instruction. | |
4. The "fprint_cpu" routine needs to know whether the "addr" parameter value | |
represents a CPU memory address in order to interpret MRG instructions | |
correctly. It does for simulator stops and CPU memory examinations but | |
not for register or device memory examinations and symbol evaluations. | |
The symbol source is set to "CPU_Symbol" or "Device_Symbol", | |
respectively, to reflect these conditions. | |
5. 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. | |
6. 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. | |
*/ | |
t_stat fprint_sym (FILE *ofile, t_addr addr, t_value *val, UNIT *uptr, int32 sw) | |
{ | |
int32 formats, modes, i; | |
uint32 irq, radix; | |
SYMBOL_SOURCE source; | |
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 mnemonic */ | |
sw |= M_SWITCH; /* then set the -M switch */ | |
if ((sw & SYMBOLIC_SWITCHES) == 0) /* if there are no symbolic 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 */ | |
if (sw & SIM_SW_STOP) { /* then if this is a simulator stop */ | |
source = CPU_Symbol; /* then report as a CPU symbol */ | |
irq = calc_int (); /* check for a pending interrupt */ | |
if (irq && !ion_defer) { /* if a pending interrupt is present and not deferred */ | |
addr = irq; /* then set the display address to the trap cell */ | |
for (i = 0; i < sim_emax; i++) /* load the trap cell instruction */ | |
val [i] = mem_fast_read ((HP_WORD) (irq + i), SMAP); /* which might be multi-word (e.g., JLY) */ | |
fprintf (ofile, "IAK %2o: ", irq); /* report that the interrupt will be acknowledged */ | |
} | |
} | |
else if (sw & SIM_SW_REG) { /* otherwise if a register value is being formatted */ | |
source = Device_Symbol; /* then report it as a device symbol */ | |
memset (sim_eval, 0, /* clear the sim_eval array */ | |
MAX_INSTR_LENGTH * sizeof (t_value)); /* in case the instruction is multi-word */ | |
sim_eval [0] = *val; /* copy the register value */ | |
val = sim_eval; /* and point at the sim_eval array */ | |
} | |
else if (uptr == &cpu_unit) /* otherwise if access is to CPU memory */ | |
source = CPU_Symbol; /* then report as a CPU symbol */ | |
else /* otherwise access is to device memory */ | |
source = Device_Symbol; /* so report it as a device symbol */ | |
return fprint_cpu (ofile, addr, val, radix, source); /* format and print the value in mnemonic format */ | |
} | |
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 no mode was specified */ | |
return fprint_value (ofile, val [0], radix, /* then format and print it with the specified radix */ | |
DV_WIDTH, PV_RZRO); /* and data width */ | |
else /* otherwise the modes conflict */ | |
return SCPE_INVSW; /* so return an error */ | |
} | |
/* Parse a value in symbolic format. | |
Print the data value in the format specified by the optional switches on the | |
output stream supplied. This routine is called to print: | |
Parse the input string in the format 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 REG_VMIO or 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 modes are supported by including the indicated | |
switches: | |
Switch Interpretation | |
------ ----------------------------- | |
-C a two-character packed string | |
-M a CPU instruction mnemonic | |
When parsing in the instruction mnemonic form, an additional format switch | |
may be specified to indicate the supplied operand radix, as follows: | |
Switch 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 | |
These switches (except for -B) may be used without a mode switch to parse a | |
numeric value in the specified radix. To summarize, the valid switch | |
combinations are: | |
-C | |
-M [ -A | -B | -O | -D | -H ] | |
-A | -O | -D | -H | |
When entering machine instruction 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. | |
In the absence of switches, a leading ' implies "-A", a leading " implies | |
"-C", and a leading alphabetic or punctuation character implies "-M". 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. | |
The operand format for -A is a single (displayable) character. The operand | |
format for -C is two (displayable) characters. | |
Implementation notes: | |
1. 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. | |
2. A hex value that is also a machine instruction mnemonic (e.g., CCE) is | |
parsed as the latter unless the -H switch is specified. If both -M and | |
-H are specified, the mnemonic is parsed as a machine instruction, and | |
any operand is parsed as a hex value. | |
3. We return SCPE_INVSW when multiple modes or formats are specified, but | |
the callers do not act on this; they use the fallback parser 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 | |
IDEPOSIT from prompting for the replacement value(s) or DEPOSIT from | |
printing the error for each address in 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 | |
option is to process -C and ignore the radix switch; this is the option | |
implemented. | |
*/ | |
t_stat parse_sym (CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw) | |
{ | |
int32 formats, modes; | |
uint32 radix; | |
t_stat status; | |
SYMBOL_SOURCE target; | |
if ((sw & (SIM_SW_REG | ALL_SWITCHES)) == SIM_SW_REG) /* if we are parsing 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 mnemonic */ | |
sw |= M_SWITCH; /* then set the -M switch */ | |
if ((sw & ALL_SWITCHES) == 0) /* if there are no default or explicit overrides */ | |
if (*cptr == '\'' && cptr++) /* then if a character parse is implied */ | |
sw |= A_SWITCH; /* then set the -A switch */ | |
else if (*cptr == '"' && cptr++) /* otherwise if a character string parse is implied */ | |
sw |= C_SWITCH; /* then set the -C switch */ | |
else if (isalpha (*cptr) || ispunct (*cptr)) /* otherwise if an instruction mnemonic parse is implied */ | |
sw |= M_SWITCH; /* then set the -M switch */ | |
if ((sw & SYMBOLIC_SWITCHES) == 0) /* if there are no symbolic overrides */ | |
return SCPE_ARG; /* then return an error to use the standard parser */ | |
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 instruction mnemonic mode is specified */ | |
if (uptr == NULL || uptr == &cpu_unit) /* then if access is to a register or CPU memory */ | |
target = CPU_Symbol; /* then report as a CPU symbol */ | |
else /* otherwise access is to device memory */ | |
target = Device_Symbol; /* so report it as a device symbol */ | |
return parse_cpu (cptr, addr, val, radix, target); /* attempt a mnemonic instruction parse */ | |
} | |
else if (modes == C_SWITCH) /* otherwise if string mode is specified */ | |
if (cptr [0] != '\0') { /* then if characters are present */ | |
val [0] = (t_value) TO_WORD (cptr [0], cptr [1]); /* then convert the character values */ | |
return SCPE_OK; /* and indicate success */ | |
} | |
else /* otherwise */ | |
return SCPE_ARG; /* report that the line cannot be parsed */ | |
else if (modes == 0) { /* otherwise if no mode was specified */ | |
val [0] = parse_value (cptr, radix, DV_UMAX, &status); /* then parse using the specified radix */ | |
return status; /* and return the parsing status */ | |
} | |
else /* otherwise the modes conflict */ | |
return SCPE_INVSW; /* so return an error */ | |
} | |
/* Attach a file for appending. | |
This routine is called to attach a file to a specified unit and set the file | |
position to the end for appending. The routine returns the result of the | |
operation, which will be SCPE_OK if the attach and optional EOF seek succeed, | |
SCPE_IOERR if the attach succeeds but the EOF seek fails (in this case, the | |
file will be detached before returning), or an appropriate status code if the | |
attach fails. | |
The standard "attach_unit" routine handles the "-N" switch to use an empty | |
("new") file. If the file exists, "attach_unit" returns with the file | |
positioned at the start. For write-only devices, such as printers, this is | |
almost never desirable, as it means that existing content may be only | |
partially overwritten. This routine provides the proper semantics for these | |
devices, i.e., if the file exists, position it for writing to the end of the | |
file. | |
Implementation notes: | |
1. "attach_unit" opens the file using one of the following modes: | |
- "r" (reading) if the -R (read-only) switch or UNIT_RO is present or | |
the file is marked read-only by the host file system | |
- "w+" (truncate and update) if the -N (new) switch is present or the | |
file does not exist | |
- "r+" (update) otherwise | |
2. Reopening with mode "a" or "a+" doesn't have the desired semantics. | |
These modes permit writing only at the EOF, but we want to permit | |
overwriting if the user explicitly sets POS. The resulting "fseek" on | |
resumption would be ignored if the mode is "a" or "a+". Therefore, we | |
accept mode "r+" and explicitly "fseek" to the end of file here. | |
3. If we are called during a RESTORE command to reattach a file previously | |
attached when the simulation was SAVEd, the file position is not altered. | |
4. It is not necessary to report the device or unit if the 'fseek" fails, as | |
this routine is called only is response to a user command that specifies | |
the unit to attach. | |
*/ | |
t_stat hp_attach (UNIT *uptr, CONST char *cptr) | |
{ | |
t_stat result; | |
result = attach_unit (uptr, cptr); /* attach the specified image file */ | |
if (result == SCPE_OK /* if the attach was successful */ | |
&& (sim_switches & SIM_SW_REST) == 0) /* and we are not being called during a RESTORE command */ | |
if (fseek (uptr->fileref, 0, SEEK_END) == 0) /* then append by seeking to the end of the file */ | |
uptr->pos = (t_addr) ftell (uptr->fileref); /* and repositioning if the seek succeeded */ | |
else { /* otherwise the seek failed */ | |
cprintf ("%s simulator seek error: %s\n", /* so report the error to the console */ | |
sim_name, strerror (errno)); | |
detach_unit (uptr); /* detach the unit */ | |
result = SCPE_IOERR; /* and report that the seek failed */ | |
} | |
return result; | |
} | |
/* Set a device select code. | |
This validation routine is called to set a device's select code. The "uptr" | |
parameter points to the unit being configured, "count" gives the number of | |
select codes to configure, "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. | |
Some devices (e.g., the DP disc device) use multiple interface cards. These | |
are assigned sequential select codes. For these devices, "desc" must point | |
at the first element of an array of DIBs to be assigned, and "count" must be | |
set to the number of elements. For single-card devices, "desc" points at the | |
DIB, and "count" is 1. | |
Implementation notes: | |
1. The legacy modifier "DEVNO" is supported in addition to the preferred | |
"SC" modifier, but the corresponding MTAB entry is tagged with MTAB_NMO | |
to prevent its display. To differentiate between the two MTAB entries, | |
which is necessary for display, the "DEVNO" entry's "count" parameter is | |
complemented (the corresponding MTAB "match" field that supplies the | |
"count" parameter to this routine is unsigned). | |
*/ | |
t_stat hp_set_dib (UNIT *uptr, int32 count, CONST char *cptr, void *desc) | |
{ | |
DIB *dibptr = (DIB *) desc; /* a pointer to the associated DIB array */ | |
t_stat status = SCPE_OK; | |
uint32 value; | |
int32 index; | |
if (cptr == NULL || *cptr == '\0') /* if the expected value is missing */ | |
status = SCPE_MISVAL; /* then report the error */ | |
else { /* otherwise a value is present */ | |
if (count < 0) /* if the count has been complemented */ | |
count = ~count; /* then restore it to a positive value */ | |
value = (uint32) get_uint (cptr, SC_BASE, /* parse the supplied device number */ | |
SC_MAX + 1 - count, &status); | |
if (status == SCPE_OK) { /* if it is valid */ | |
if (value < VARDEV) /* then if it is an internal select code */ | |
return SCPE_ARG; /* then reject it */ | |
for (index = 0; index < count; index++, dibptr++) /* loop through the associated interfaces */ | |
dibptr->select_code = value + index; /* and set the select codes in order */ | |
} | |
} | |
return status; /* return the validation result */ | |
} | |
/* Show a device select code. | |
This display routine is called to show a device's select code. The "st" | |
parameter is the open output stream, "uptr" points to the unit being queried, | |
"count" gives the number of additional select codes to display, and "desc" | |
points to the DIB associated with the device. | |
Some devices (e.g., the DP disc device) use multiple interface cards. For | |
these devices, "desc" must point at the first element of an array of DIBs to | |
be assigned, and "count" must be set to the index of the last element (note: | |
not the size) of the array. The select codes will be printed together. | |
For single-card devices, "desc" points at the DIB, and "count" is zero. | |
Implementation notes: | |
1. The legacy modifier "DEVNO" is supported in addition to the preferred | |
"SC" modifier, but the corresponding MTAB entry is tagged with MTAB_NMO | |
to prevent its display. To differentiate between the two MTAB entries, | |
which is necessary for display, the "DEVNO" entry's "count" parameter is | |
complemented (the corresponding MTAB "match" field that supplies the | |
"count" parameter to this routine is unsigned). | |
1. The legacy modifier "DEVNO" is supported in addition to the preferred | |
"SC" modifier, but the corresponding MTAB entry is tagged with MTAB_NMO | |
to prevent its display. When displaying an MTAB_NMO entry, a newline is | |
not automatically added to the end of the line, so we must add it here. | |
To differentiate between the two MTAB entries, the "DEVNO" entry's | |
"count" parameter is complemented (the corresponding MTAB "match" field | |
that supplies the "count" parameter to this routine is unsigned). | |
*/ | |
t_stat hp_show_dib (FILE *st, UNIT *uptr, int32 count, CONST void *desc) | |
{ | |
const DIB *dibptr = (const DIB *) desc; /* a pointer to the associated DIB array */ | |
int32 index, limit; | |
if (count < 0) /* if the count has been complemented */ | |
limit = ~count; /* then restore it to a positive value */ | |
else /* otherwise */ | |
limit = count; /* the value is already positive */ | |
fprintf (st, "select code=%o", dibptr++->select_code); /* print the interface's select code */ | |
for (index = 2; index <= limit; index++, dibptr++) /* if the device uses more than one interface */ | |
fprintf (st, "/%o", dibptr->select_code); /* then append the additional select code(s) */ | |
if (count < 0) /* if this is a DEVNO request (MTAB_NMO) */ | |
fputc ('\n', st); /* then append a newline */ | |
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" is a pointer to an | |
array of t_values of depth "sim_emax" containing 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 "source" indicates the source of | |
the instruction (device, CPU, or trace). | |
The routine returns a status code to the caller. SCPE_OK status is returned | |
if a single-word instruction was printed, or the negative number of extra | |
words (beyond the first) consumed in printing the instruction is returned. | |
For example, printing a two-word instruction returns SCPE_OK_2_WORDS (= -1). | |
If the supplied instruction is not a valid opcode, or is not valid for the | |
current CPU feature set, SCPE_ARG is returned. | |
The routine determines the instruction group that includes the supplied | |
instruction value and calls the "fprint_instruction" routine with the | |
associated opcode descriptor and table. For MRG instructions, the | |
instruction address is set to an invalid value if the source is not a CPU | |
memory location. This causes the instruction to be printed as a base-page or | |
current-page offset, rather than as an absolute address. | |
SRG instructions consist of two encoded micro-op fields, plus separate | |
micro-ops for CLE and SLA/SLB. These are printed separately. | |
MAC instructions must be decoded into the indicated feature groups. Some of | |
these groups overlap and must be differentiated by the current CPU model or | |
installed microcode options before the correct opcode descriptor and table | |
may be determined. Primary MAC decoding is bits 11-8, as follows (note that | |
bit 10 = 0 for the MAC group): | |
Bits | |
11-8 Group | |
---- ----- | |
0000 EAU | |
0001 EAU | |
0010 EAU | |
0011 UIG-1 | |
1000 EAU | |
1001 EAU | |
1010 UIG-0 | |
1011 UIG-1 | |
Bits 7-4 further decode the UIG instruction feature group. UIG-0 pertains | |
to the 2100 and 1000 M/E/F-Series, as follows: | |
Instructions IR 7-4 Option Name 2100 1000-M 1000-E 1000-F | |
------------- ------ -------------------------- ------ ------ ------ ------ | |
105000-105362 00-17 2000 I/O Processor opt - - - | |
105000-105137 00-05 Floating Point opt std std std | |
105200-105237 10-11 Fast FORTRAN Processor opt opt opt std | |
105240-105257 12 RTE-IVA/B Extended Memory - - opt opt | |
105240-105257 12 RTE-6/VM Virtual Memory - - opt opt | |
105300-105317 14 Distributed System - - opt opt | |
105320-105337 15 Double Integer - - opt - | |
105320-105337 15 Scientific Instruction Set - - - std | |
105340-105357 16 RTE-6/VM Operating System - - opt opt | |
UIG-1 pertains only to the 1000 M/E/F-Series machines, as follows: | |
Instructions IR 7-4 Option Name 2100 1000-M 1000-E 1000-F | |
------------- ------ -------------------------- ------ ------ ------ ------ | |
10x400-10x437 00-01 2000 I/O Processor - opt opt - | |
10x460-10x477 03 2000 I/O Processor - opt opt - | |
10x460-10x477 03 Vector Instruction Set - - - opt | |
10x520-10x537 05 Distributed System - opt - - | |
10x600-10x617 10 SIGNAL/1000 Instruction Set - - - opt | |
10x700-10x737 14-15 Dynamic Mapping System - opt opt std | |
10x740-10x777 16-17 Extended Instruction Group - std std std | |
Implementation notes: | |
1. Indexing is employed where possible, but the 21xx/1000 instruction set is | |
quite irregular, so conditionals and linear searches are needed to | |
resolve many of the instructions. | |
2. The no-operation opcode is handled as a special case within the SRG | |
micro-ops table. | |
*/ | |
t_stat fprint_cpu (FILE *ofile, t_addr addr, t_value *val, uint32 radix, SYMBOL_SOURCE source) | |
{ | |
const t_value opcode = val [0]; /* the instruction opcode */ | |
t_bool separator = FALSE; /* TRUE if a separator between multiple ops is needed */ | |
t_stat status = SCPE_ARG; /* initial return status is "invalid opcode" */ | |
if (MRGOP (opcode)) { /* if this is an MRG instruction */ | |
if (source == Device_Symbol) /* then if the offset must be relative */ | |
addr = PA_MAX + 1; /* then pass an invalid address */ | |
status = fprint_instruction (ofile, addr, val, /* print the instruction */ | |
radix, mrg_desc, mrg_ops); | |
} | |
else if (SRGOP (opcode)) { /* otherwise if this is an SRG instruction */ | |
if (opcode & SRG1_DE_MASK) { /* then if the first shift is enabled */ | |
status = fprint_instruction (ofile, addr, val, /* then print the first shift operation */ | |
radix, srg1_desc, srg1_ops); | |
separator = TRUE; /* we will need a separator */ | |
} | |
if (opcode == SRG_NOP /* if this is a NOP */ | |
|| (opcode & (SRG_CLE | SRG_SLx))) { /* or a micro-op is present */ | |
if (separator) /* then if a separator is needed */ | |
fputc (',', ofile); /* then print it */ | |
status = fprint_instruction (ofile, addr, val, /* print the micro-op(s) */ | |
radix, srg_udesc, srg_uops); | |
separator = TRUE; /* we will need a separator */ | |
} | |
if (opcode & SRG2_DE_MASK) { /* if the second shift is enabled */ | |
if (separator) /* then if a separator is needed */ | |
fputc (',', ofile); /* then print it */ | |
status = fprint_instruction (ofile, addr, val, /* print the second shift operation */ | |
radix, srg2_desc, srg2_ops); | |
} | |
} | |
else if (ASGOP (opcode)) /* otherwise if this is an ASG instruction */ | |
status = fprint_instruction (ofile, addr, val, /* then print it */ | |
radix, asg_udesc, asg_uops); | |
else if (IOGOP (opcode)) /* otherwise if this is an IOG instruction */ | |
status = fprint_instruction (ofile, addr, val, /* then print it */ | |
radix, iog_desc, iog_ops); | |
else { /* otherwise this is a MAC group instruction */ | |
if (source == CPU_Trace) /* if this is a CPU trace call */ | |
addr = addr | VAL_EMPTY; /* then indicate that the value array has not been loaded */ | |
if (UIG_0_OP (opcode)) { /* if this is a UIG-0 instruction */ | |
status = fprint_instruction (ofile, addr, val, /* then try to print it as a 2100 IOP instruction */ | |
radix, iop_2100_desc, iop_2100_ops); | |
if (status == SCPE_UNK) /* if it's not a 2100 IOP instruction */ | |
switch (UIG (opcode)) { /* then dispatch on the feature group */ | |
case 000: /* 105000-105017 */ | |
case 001: /* 105020-105037 */ | |
case 002: /* 105040-105057 */ | |
case 003: /* 105060-105077 */ | |
case 004: /* 105100-105117 */ | |
case 005: /* 105120-105137 */ | |
status = fprint_instruction (ofile, addr, val, /* try to print as an FP instruction */ | |
radix, fp_desc, fp_ops); | |
if (status == SCPE_UNK) /* if it's not an FP instruction */ | |
status = fprint_instruction (ofile, addr, val, /* then try again as an FPP instruction */ | |
radix, fpp_desc, fpp_ops); | |
break; | |
case 010: /* 105200-105217 */ | |
case 011: /* 105220-105237 */ | |
status = fprint_instruction (ofile, addr, val, /* try to print as a 2100 FFP instruction */ | |
radix, ffp_2100_desc, ffp_2100_ops); | |
if (status == SCPE_UNK) /* if it's not a 2100 FFP opcode */ | |
status = fprint_instruction (ofile, addr, val, /* then try again as a 1000-F FFP opcode */ | |
radix, ffp_f_desc, ffp_f_ops); | |
if (status == SCPE_UNK) /* if it's not a 1000-F FFP opcode */ | |
status = fprint_instruction (ofile, addr, val, /* then try again as a 1000-E FFP opcode */ | |
radix, ffp_e_desc, ffp_e_ops); | |
if (status == SCPE_UNK) /* if it's not a 1000-E FFP opcode */ | |
status = fprint_instruction (ofile, addr, val, /* then try again as a 1000-M FFP opcode */ | |
radix, ffp_m_desc, ffp_m_ops); | |
break; | |
case 012: /* 105240-105257 */ | |
status = fprint_instruction (ofile, addr, val, /* try to print as a VMA instruction */ | |
radix, vma_desc, vma_ops); | |
if (status == SCPE_UNK) /* if it's not a VMA instruction */ | |
status = fprint_instruction (ofile, addr, val, /* then try again as an EMA instruction */ | |
radix, ema_desc, ema_ops); | |
break; | |
case 014: /* 105300-105317 Distributed System */ | |
break; | |
case 015: /* 105320-105337 */ | |
status = fprint_instruction (ofile, addr, val, /* try to print as an SIS instruction */ | |
radix, sis_desc, sis_ops); | |
if (status == SCPE_UNK) /* if it's not an SIS instruction */ | |
status = fprint_instruction (ofile, addr, val, /* then try again as a DBI instruction */ | |
radix, dbi_desc, dbi_ops); | |
break; | |
case 016: /* 105340-105357 */ | |
if (opcode >= RTE_IRQ_RANGE /* if the opcode is in the interrupt use range */ | |
&& addr >= OPTDEV && addr <= MAXDEV) /* and it's located in a trap cell */ | |
status = fprint_instruction (ofile, addr, val, /* then print as an IRQ instruction */ | |
radix, trap_desc, trap_ops); | |
else /* otherwise */ | |
status = fprint_instruction (ofile, addr, val, /* print as an OS-assist instruction */ | |
radix, os_desc, os_ops); | |
break; | |
default: /* all other feature groups */ | |
status = SCPE_ARG; /* are unimplemented */ | |
} | |
} | |
else if (UIG_1_OP (opcode)) /* otherwise if this is a UIG-1 instruction */ | |
switch (UIG (opcode)) { /* then dispatch on the feature group */ | |
case 000: /* 10x400-10x417 */ | |
case 001: /* 10x420-10x437 */ | |
status = fprint_instruction (ofile, addr, val, /* print as an IOP instruction */ | |
radix, iop1_1000_desc, iop1_1000_ops); | |
break; | |
case 003: /* 10x460-10x477 */ | |
status = fprint_instruction (ofile, addr, val, /* try to print as an IOP instruction */ | |
radix, iop2_1000_desc, iop2_1000_ops); | |
if (status == SCPE_UNK) { /* if it's not an IOP opcode */ | |
if (source == CPU_Trace) /* then if this is a CPU trace call */ | |
val [1] = mem_fast_read (addr + 1 & LA_MASK, dms_ump); /* then load the following word */ | |
status = fprint_instruction (ofile, addr, val, /* print as a VIS instruction */ | |
radix, vis_desc, vis_ops); | |
} | |
break; | |
case 010: /* 10x600-10x617 */ | |
status = fprint_instruction (ofile, addr, val, /* print as a SIGNAL instruction */ | |
radix, sig_desc, sig_ops); | |
break; | |
case 014: | |
case 015: /* 10x700-10x737 */ | |
status = fprint_instruction (ofile, addr, val, /* print as a DMS instruction */ | |
radix, dms_desc, dms_ops); | |
break; | |
case 016: | |
case 017: /* 10x740-10x777 */ | |
status = fprint_instruction (ofile, addr, val, /* print as an EIG instruction */ | |
radix, eig_desc, eig_ops); | |
break; | |
default: /* all other feature groups */ | |
status = SCPE_ARG; /* are unimplemented */ | |
} | |
else { /* otherwise */ | |
status = fprint_instruction (ofile, addr, val, /* print as an EAG instruction */ | |
radix, eag_desc, eag_ops); | |
if (status == SCPE_ARG) /* if it's not an EAG opcode but the EAU is present */ | |
status = fprint_instruction (ofile, addr, val, /* then try as a 1000-E/F extension instruction */ | |
radix, eag_ef_desc, eag_ef_ops); | |
} | |
} | |
return status; /* return the consumption status */ | |
} | |
/* 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 but | |
within the full character range (i.e., less than 377 octal) are presented as | |
escaped octal values. Characters outside of the full character range are | |
masked to the lower eight bits and printed as a plain octal value. | |
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 <= 0037u) /* if the value is an ASCII control character */ | |
return control [charval]; /* then return a readable representation */ | |
else if (charval == 0177u) /* 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 > D8_UMAX) /* if the value is beyond the 8-bit character range */ | |
freeptr = freeptr + sprintf (freeptr, "%03o", /* then format the lower byte as octal */ | |
LOWER_BYTE (charval)); /* and update the free pointer */ | |
else if (charval > 0177u) /* otherwise if the value is beyond the printable range */ | |
freeptr = freeptr + sprintf (freeptr, "\\%03o", /* then format as an escaped octal value */ | |
charval); /* 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: | |
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 14-12 of a word are valid, | |
then the name string array would have three entries. If bits 14-12 and 10 | |
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 | |
14; if the direction is lsb_first, then the first name is for bit 12. | |
The alternate field specifies whether (has_alt) or not (no_alt) alternate | |
conditions are represented by one or more bits. Generally, bits represent | |
Boolean conditions, e.g., a condition is present when the bit is 1 and absent | |
when the bit is zero. In these cases, the corresponding bit name is included | |
or omitted, respectively, in the return string. | |
Occasionally, bits will represent alternate conditions, e.g., where condition | |
A is present when the bit is 1, and condition B is present when the bit is 0. | |
For these, the bit name string should consist of both condition names in that | |
order, with the "1" name preceded by the '\1' character and the "0" name | |
preceded by the '\0' character. For example, if 1 corresponds to "load" and | |
0 to "store", then the bit name string would be "\1load\0store". If | |
alternate names are present, the has_alt identifier should be given, so that | |
the indicated bits are checked for zero conditions. If no_alt is specified, | |
the routine stops as soon as all of the one-bits have been processed. | |
The bar field specifies whether (append_bar) or not (no_bar) a vertical bar | |
separator is appended to the formatted string. Typically, a bitset | |
represents a peripheral control or status word. If the word also contains | |
multiple-bit fields, a trailing separator should be requested, and the | |
decoded fields should be concatenated by the caller with any named bits. If | |
the bitset is empty, the returned null string will present the proper display | |
containing just the decoded fields. If the bitset completely describes the | |
word, then no appended separator is needed. | |
Peripheral control and status words generally are decoded from MSB to LSB. A | |
bitset may also represent a set of inbound or outbound signals. These should | |
be decoded from LSB to MSB, as that is the order in which they are executed | |
by the device interface routines. | |
The implementation first generates a mask for the significant bits and | |
positions the mask with the offset specified. Then a test bit mask is | |
generated; the bit is either the most- or least-significant bit of the | |
bitset, depending on the direction indicated. | |
For each name in the array of names, if the name is defined (not NULL), the | |
corresponding bit in the bitset is tested. If it is set, the name is | |
appended to the output buffer; otherwise, it is omitted (unless the name has | |
an alternate, in which case the alternate is appended). The bitset is then | |
shifted in the indicated direction, remasking to just the significant bits. | |
Processing continues until there are no remaining significant bits (if no | |
alternates are specified), or until there are no remaining names in the array | |
(if alternates are specified). | |
Implementation notes: | |
1. The routine returns a pointer to a static buffer containing the printable | |
string. To allow the routine to be called more than once per trace line, | |
the null-terminated format strings are concatenated in the buffer, and | |
each call returns a pointer that is offset into the buffer to point at | |
the latest formatted string. | |
2. There is no explicit buffer-free action. Instead, newly formatted | |
strings are appended to the buffer until there is no more space | |
available. At that point, the string currently being assembled is moved | |
to the start of the buffer, and the pointers are reset. In effect, this | |
provides a circular buffer, as previously formatted strings are | |
overwritten by subsequent calls. | |
3. The buffer is sized to hold the maximum number of concurrent strings | |
needed for a single trace line. If more concurrent strings are used, one | |
or more strings from the earliest calls will be overwritten. If an | |
attempt is made to format a string larger than the buffer, an error | |
indication string is returned. | |
4. The location of the end of the buffer used to determine if the next name | |
will fit includes an allowance for two separators that might be placed on | |
either side of the name and a terminating NUL character. | |
*/ | |
const char *fmt_bitset (uint32 bitset, const BITSET_FORMAT bitfmt) | |
{ | |
static const char separator [] = " | "; /* the separator to use between names */ | |
static char fmt_buffer [1024]; /* the return buffer */ | |
static char *freeptr = fmt_buffer; /* pointer to the first free character in the buffer */ | |
static char *endptr = fmt_buffer + sizeof fmt_buffer /* pointer to the end of the buffer */ | |
- 2 * (sizeof separator - 1) - 1; /* less allowance for two separators and a terminator */ | |
const char *bnptr, *fmtptr; | |
uint32 test_bit, index, bitmask; | |
size_t name_length; | |
if (bitfmt.name_count < D32_WIDTH) /* if the name count is the less than the mask width */ | |
bitmask = (1 << bitfmt.name_count) - 1; /* then create a mask for the name count specified */ | |
else /* otherwise use a predefined value for the mask */ | |
bitmask = D32_MASK; /* to prevent shifting the bit off the MSB end */ | |
bitmask = bitmask << bitfmt.offset; /* align the mask to the named bits */ | |
bitset = bitset & bitmask; /* and mask to just the significant bits */ | |
if (bitfmt.direction == msb_first) /* if the examination is left-to-right */ | |
test_bit = 1 << bitfmt.name_count + bitfmt.offset - 1; /* then create a test bit for the MSB */ | |
else /* otherwise */ | |
test_bit = 1 << bitfmt.offset; /* create a test bit for the LSB */ | |
fmtptr = freeptr; /* initialize the return pointer */ | |
*freeptr = '\0'; /* and the format accumulator */ | |
index = 0; /* and the name index */ | |
while ((bitfmt.alternate || bitset) /* while more bits */ | |
&& index < bitfmt.name_count) { /* and more names exist */ | |
bnptr = bitfmt.names [index]; /* point at the name for the current bit */ | |
if (bnptr) /* if the name is defined */ | |
if (*bnptr == '\1' && bitfmt.alternate) /* then if this name has an alternate */ | |
if (bitset & test_bit) /* then if the bit is asserted */ | |
bnptr++; /* then point at the name for the "1" state */ | |
else /* otherwise */ | |
bnptr = bnptr + strlen (bnptr) + 1; /* point at the name for the "0" state */ | |
else /* otherwise the name is unilateral */ | |
if ((bitset & test_bit) == 0) /* so if the bit is denied */ | |
bnptr = NULL; /* then clear the name pointer */ | |
if (bnptr) { /* if the name pointer is set */ | |
name_length = strlen (bnptr); /* then get the length needed */ | |
if (freeptr + name_length > endptr) { /* if there is not enough room left to append the name */ | |
strcpy (fmt_buffer, fmtptr); /* then move the partial string to the start of the buffer */ | |
freeptr = fmt_buffer + (freeptr - fmtptr); /* point at the new first free character location */ | |
fmtptr = fmt_buffer; /* and reset the return pointer */ | |
if (freeptr + name_length > endptr) /* if there is still not enough room left to append */ | |
return "(buffer overflow)"; /* then this call is requires a larger buffer! */ | |
} | |
if (*fmtptr != '\0') { /* if this is not the first name added */ | |
strcpy (freeptr, separator); /* then add a separator to the string */ | |
freeptr = freeptr + strlen (separator); /* and move the free pointer */ | |
} | |
strcpy (freeptr, bnptr); /* append the bit's mnemonic to the accumulator */ | |
freeptr = freeptr + name_length; /* and move the free pointer */ | |
} | |
if (bitfmt.direction == msb_first) /* if formatting is left-to-right */ | |
bitset = bitset << 1 & bitmask; /* then shift the next bit to the MSB and remask */ | |
else /* otherwise formatting is right-to-left */ | |
bitset = bitset >> 1 & bitmask; /* so shift the next bit to the LSB and remask */ | |
index = index + 1; /* bump the bit name index */ | |
} | |
if (*fmtptr == '\0') /* if no names were output */ | |
if (bitfmt.bar == append_bar) /* then if concatenating with more information */ | |
return ""; /* then return an empty string */ | |
else /* otherwise it's a standalone format */ | |
return "(none)"; /* so return a placeholder */ | |
else if (bitfmt.bar == append_bar) { /* otherwise if a trailing separator is specified */ | |
strcpy (freeptr, separator); /* then add a separator to the string */ | |
freeptr = freeptr + strlen (separator) + 1; /* and account for it plus the trailing NUL */ | |
} | |
else /* otherwise */ | |
freeptr = freeptr + 1; /* just account for the trailing NUL */ | |
return fmtptr; /* return a pointer to the formatted string */ | |
} | |
/* Format and print a trace line to the debug log file. | |
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 trace 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 "tprintf" | |
macro, which tests that tracing is enabled for the specified flag before | |
calling this function. This eliminates the calling overhead if tracing is | |
disabled. | |
This routine prints a prefix before the supplied format string consisting of | |
the device name (in upper case) and the trace 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 trace flag | |
name among the devices enabled for tracing 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_trace (DEVICE *dptr, uint32 flag, ...) | |
{ | |
const char *nptr; | |
va_list argptr; | |
DEBTAB *debptr; | |
char *format, *fptr; | |
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 trace flags table */ | |
if (debptr != NULL) /* if the trace 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 trace table entry */ | |
} | |
return; | |
} | |
/* Check for device conflicts. | |
The device information blocks (DIBs) for the set of enabled devices are | |
checked for consistency. Each select code must be unique among the enabled | |
devices. This requirement is checked as part of the instruction execution | |
prelude; this allows the user to exchange two select codes simply by setting | |
each device to the other's select code. If conflicts were enforced instead | |
at the time the codes were entered, the first device would have to be set to | |
an unused select code before the second could be set to the first device's | |
code. | |
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 | |
trace flag name among the devices enabled for tracing are accumulated for use | |
in aligning the trace statements. | |
After the DIB value table is filled in, a conflict check is made by building | |
a conflict table, where each array element is set to the count of devices | |
that contain DIB value equal to the element index. For example, conflict | |
table element 6 is set to the count of devices that have dibptr->select_code | |
set to 6. If any conflict table element is set more than once, the | |
"conflict_is" variable is set. | |
If any conflicts exist, the conflict table is scanned. A conflict table | |
element value 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) | |
{ | |
const DIB *dibptr; | |
const DEBTAB *tptr; | |
DEVICE *dptr; | |
size_t name_length, flag_length; | |
uint32 dev, val; | |
int32 count; | |
int32 dib_val [DEVICE_COUNT]; | |
int32 conflicts [MAXDEV + 1]; | |
t_bool is_conflict = FALSE; | |
device_size = 0; /* reset the device and flag name sizes */ | |
flag_size = 0; /* to those of the devices actively tracing */ | |
memset (conflicts, 0, sizeof conflicts); /* zero the conflict table */ | |
for (dev = 0; dev < DEVICE_COUNT; dev++) { /* fill in the DIB value table */ | |
dptr = sim_devices [dev]; /* from the device table */ | |
dibptr = (DIB *) dptr->ctxt; /* and the associated DIBs */ | |
if (dibptr && !(dptr->flags & DEV_DIS)) { /* if the DIB is defined and the device is enabled */ | |
dib_val [dev] = dibptr->select_code; /* then copy the values to the DIB table */ | |
if (++conflicts [dibptr->select_code] > 1) /* increment the count of references; if more than one */ | |
is_conflict = TRUE; /* then a conflict occurs */ | |
} | |
if (sim_deb && dptr->dctrl) { /* if tracing 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 trace flags table */ | |
for (tptr = dptr->debflags; /* then scan the table */ | |
tptr->name != NULL; tptr++) | |
if (dev == 0 && dptr->dctrl & TRACE_EXEC /* if the CPU device is tracing executions */ | |
|| tptr->mask & dptr->dctrl) { /* or this trace 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 */ | |
} | |
} | |
} | |
if (is_conflict) { /* if a conflict exists */ | |
sim_ttcmd (); /* then restore the console and log I/O mode */ | |
for (val = 0; val <= MAXDEV; 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 ("Select code %o conflict (", val); /* report the multiply-assigned select code */ | |
dev = 0; /* search for the devices that conflict */ | |
while (count > 0) { /* search the DIB value table */ | |
if (dib_val [dev] == (int32) val) { /* to find the conflicting entries */ | |
if (count < conflicts [val]) /* and report them to the console */ | |
cputs (" and "); | |
cputs (sim_dname (sim_devices [dev])); /* report the conflicting device name */ | |
count = count - 1; /* and drop the count of remaining conflicts */ | |
} | |
dev = dev + 1; /* move to the next device */ | |
} /* and loop until all conflicting devices are reported */ | |
cputs (")\n"); /* tie off the line */ | |
} /* and continue to look for other conflicting select codes */ | |
} | |
return (is_conflict); /* return TRUE if any conflicts exist */ | |
} | |
/* Make a pair of devices consistent */ | |
void hp_enbdis_pair (DEVICE *ccptr, DEVICE *dcptr) | |
{ | |
if (ccptr->flags & DEV_DIS) | |
dcptr->flags |= DEV_DIS; | |
else | |
dcptr->flags &= ~DEV_DIS; | |
return; | |
} | |
/* 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 *systab, *auxtab = aux_cmds; | |
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 */ | |
} | |
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_vm_post = &cpu_post_cmd; /* set up the command post-processor */ | |
sim_brk_types = BP_SUPPORTED; /* register the supported breakpoint types */ | |
sim_brk_dflt = BP_ENONE; /* 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 (CLA)" | |
SCPE_STEP prints "Step expired, P: 24713 (CLA)" | |
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_HALT prints "Programmed halt, T: 102077 (HLT 77), P: 24713 (CLA)" | |
STOP_NOTAPE prints "Tape not loaded in the <dev> device, P: 24713 (CLA)" | |
STOP_EOT prints "End of tape on the <dev> device, P: 24713 (CLA)" | |
STOP_NOCONN prints "Cable not connected to the <dev> device, P: 24713 (CLA)" | |
The HP 21xx/1000 halt instruction opcode includes select code and device flag | |
hold/clear bit fields. In practice, these are not used to affect the device | |
interface; rather, they communicate to the operator the significance of the | |
particular halt encountered. | |
Under simulation, the halt opcode must be communicated to the user as part of | |
the stop message. To so do, we define a sim_vm_fprint_stopped handler that | |
is called for all VM stops. When called for a STOP_HALT, the halt message | |
has been printed, and we add the opcode value in the T register before | |
returning TRUE, so that SCP will add the program counter value. | |
For unreported I/O error stops, the message must include the device name, so | |
the user will know how to correct the error. For these stops, the global | |
variable "cpu_ioerr_uptr" will point at the unit that encountered the error. | |
Implementation notes: | |
1. Normally, the "sim_eval" global value array either is preloaded with the | |
instruction and its operand addresses, or the first element is set to the | |
instruction and the address parameter includes the VAL_EMPTY flag to | |
indicate that the operands have not yet been loaded. In the STOP_HALT | |
case, the instruction is always a HLT, which is a single-word | |
instruction. Therefore, the VAL_EMPTY flag will not be checked and need | |
not be included. | |
*/ | |
static t_bool fprint_stopped (FILE *st, t_stat reason) | |
{ | |
DEVICE *dptr; | |
if (reason == STOP_HALT) { /* if this is a halt instruction stop */ | |
sim_eval [0] = (t_value) TR; /* then save the opcode for display */ | |
fprintf (st, ", T: %06o (", TR); /* print the T register value */ | |
fprint_cpu (st, MR, sim_eval, 0, CPU_Symbol); /* then format and print the halt mnemonic */ | |
fputc (')', st); /* and finally close the parenthesis */ | |
} | |
else if (cpu_ioerr_uptr) { /* otherwise if this is an I/O error stop */ | |
dptr = find_dev_from_unit (cpu_ioerr_uptr); /* then get the device pointer from the unit */ | |
if (dptr) /* if the search succeeded */ | |
fprintf (st, " the %s device", sim_dname (dptr)); /* then report the device */ | |
else /* otherwise */ | |
fputs (" an unknown device", st); /* report that the device is unknown */ | |
} | |
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 <page>.<offset> form for CPU | |
addresses > 32K and as a scalar value for CPU addresses <= 32K and all other | |
devices. | |
*/ | |
static void fprint_addr (FILE *st, DEVICE *dptr, t_addr addr) | |
{ | |
uint32 page, offset; | |
if (dptr == &cpu_dev && addr > LA_MAX) { /* if a CPU address is outside of the logical address space */ | |
page = PAGE (addr); /* then separate page and offset */ | |
offset = OFFSET (addr); /* from the linear address */ | |
fprint_val (st, page, dptr->aradix, PG_WIDTH, PV_RZRO); /* print the page address */ | |
fputc ('.', st); /* followed by a period */ | |
fprint_val (st, offset, dptr->aradix, OF_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 1000 divides memory into 1K-word pages within a 32K logical address | |
space. With memory expansion disabled, only the first 32 pages are | |
accessible. With memory expansion enabled, memory maps are used to translate | |
logical to physical addresses. Each translation uses one of four possible | |
maps. Program accesses use the system map or the user map, depending on | |
which one is designated the "current map." DCPC accesses use the port A or | |
port B map, depending on whether channel 1 or channel 2, respectively, is | |
performing the access. Each map translates the 32 logical pages to 32 of the | |
1024 physical pages. | |
The HP 2114, 2115, 2116, and 2100 provide up to 32 pages of memory. Memory | |
expansion is not provided with these machines. | |
The simulator supports only linear addresses for all devices other than the | |
CPU. For the CPU, two forms of address entries are allowed: | |
- a logical address consisting of a 15-bit offset within the 32K logical | |
address space (e.g., 77777). | |
- a physical address consisting of a 10-bit page number and a 10-bit | |
offset within the page, separated by a period (e.g., 1777.1777) | |
Command line switches modify the interpretation of logical addresses as | |
follows: | |
Switch Interpretation | |
------ -------------------------------------------------- | |
-N Use no mapping | |
-S If memory expansion is enabled, use the system map | |
-U If memory expansion is enabled, use the user map | |
-P If memory expansion is enabled, use the port A map | |
-Q If memory expansion is enabled, use the port B map | |
If no switch is specified, the address is interpreted using the current map | |
if memory expansion is enabled; otherwise, the address is not mapped. If the | |
current or specified map is used, then the address must lie within the 32K | |
logical address space. | |
The "parse_physical" global specifies whether or not a physical address is | |
permitted. Addresses used by the RUN, GO, BREAK, and NOBREAK commands must | |
resolve to logical addresses, whereas EXAMINE and DEPOSIT commands allow both | |
physical and logical address specifications. | |
*/ | |
static t_addr parse_addr (DEVICE *dptr, CONST char *cptr, CONST char **tptr) | |
{ | |
CONST char *sptr; | |
t_addr page; | |
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 */ | |
address = strtotv (cptr, tptr, dptr->aradix); /* parse the address */ | |
if (cptr != *tptr) /* if the parse succeeded */ | |
if (**tptr == '.') /* then if this a paged address */ | |
if (address > PG_MAX) /* then if the page 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 */ | |
page = address; /* save the first part as the bank address */ | |
address = strtotv (sptr, tptr, dptr->aradix); /* parse the offset */ | |
if (address > OF_MAX) /* if the offset is too large */ | |
*tptr = cptr; /* then report a parse error */ | |
else /* otherwise it is in range */ | |
address = TO_PA (page, address); /* so form the linear address */ | |
} | |
else if (address > LA_MAX) /* otherwise if the non-paged offset is too large */ | |
*tptr = cptr; /* then report a parse error */ | |
if (parse_physical == FALSE /* if only logical addresses are permitted */ | |
&& address > LA_MAX) /* and the parsed address is out of range */ | |
*tptr = cptr; /* then report a parse error */ | |
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 <page>.<offset> | |
EXAMINE <logical-address> | |
This routine configures the address parser and calls the standard command | |
handler. | |
*/ | |
static t_stat hp_exdep_cmd (int32 arg, CONST char *buf) | |
{ | |
parse_physical = TRUE; /* allow the <page.<offset> address form */ | |
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 { <logical-address> } | |
GO { <logical-address> } | |
If no argument is specified, the breakpoint address defaults to the current | |
value of P. This routine configures the address parser and calls the | |
standard command handler. | |
*/ | |
static t_stat hp_run_cmd (int32 arg, CONST char *buf) | |
{ | |
parse_physical = FALSE; /* allow the <logical-address> address form only */ | |
return run_cmd (arg, 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 <logical-address> | |
If no argument is specified, the breakpoint address defaults to the current | |
value of P. This routine configures the address parser and calls the | |
standard command handler. | |
*/ | |
static t_stat hp_brk_cmd (int32 arg, CONST char *buf) | |
{ | |
parse_physical = FALSE; /* allow the <logical-address> address form only */ | |
return brk_cmd (arg, buf); /* return the result of the standard handler */ | |
} | |
/* Execute the LOAD command. | |
This command is intercepted to permit a device boot routine to be loaded | |
into memory. The following command forms are valid: | |
LOAD <dev> | |
LOAD <filename> [ <select-code> ] | |
If the first form is used, and the device name is valid and bootable, the | |
corresponding boot loader is copied into memory in the highest 64 locations | |
of the logical address space. Upon return, the loader will have been | |
configured for the device's select code or for the device and paper tape | |
reader select codes, in the case of a dual-use 21xx boot loader, and the P | |
register will have been set to point at the start of the loader. The loader | |
then may be executed by a RUN command. If the device name is valid, but the | |
device is not bootable, or no boot loader exists for the current CPU | |
configuration, the command is rejected. | |
If the second form is used, the file containing a loader in absolute binary | |
form is read into memory and configured as above. See the "sim_load" | |
comments for details of this operation. | |
Implementation notes: | |
1. The "find_dev" routine requires that the device name be in upper case to | |
match. The "get_glyph" routine performs case-shifting on the input | |
buffer. | |
*/ | |
static t_stat hp_load_cmd (int32 arg, CONST char *buf) | |
{ | |
CONST char *cptr; | |
char cbuf [CBUFSIZE]; | |
DEVICE *dptr = NULL; | |
GET_SWITCHES (buf); /* parse any switches present */ | |
cptr = get_glyph (buf, cbuf, '\0'); /* parse a potential device name */ | |
if (cbuf [0] != '\0') { /* if the name is present */ | |
dptr = find_dev (cbuf); /* then see if it matches a device */ | |
if (dptr != NULL) /* if it does */ | |
if (dptr->boot == NULL) /* then if the device is not bootable */ | |
return SCPE_NOFNC; /* then report "Command not allowed" */ | |
else if (*cptr != '\0') /* otherwise if more characters follow */ | |
return SCPE_2MARG; /* then report "Too many arguments" */ | |
else /* otherwise the device name stands alone */ | |
return dptr->boot (0, dptr); /* so load the corresponding boot loader */ | |
} | |
return load_cmd (arg, buf); /* if it's not a device name, then try loading a file */ | |
} | |
/* System interface local utility routines */ | |
/* Print a numeric value in a given radix. | |
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. | |
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 /* otherwise format and print the value */ | |
return fprint_val (ofile, val, radix, width, format); /* using the supplied radix and format */ | |
} | |
/* Print a CPU instruction in symbolic format. | |
This routine is called to decode and print a CPU instruction mnemonic and any | |
associated operand(s). On entry, "ofile" is the opened output stream, "addr" | |
is the memory address location of the instruction, "val" is a pointer to an | |
array of t_values of depth "sim_emax" containing the instruction to be | |
printed, "radix" is zero to use the default radix for the operand(s) or a | |
value indicating a requested radix override, "op_desc" is a structure that | |
describes the characteristics of the instruction opcode, and "ops" is the | |
table of opcodes that pertain to the class to which the supplied instruction | |
belongs. | |
On exit, a status code is returned to the caller. If the instruction is | |
provided by a firmware option that is not installed, SCPE_UNK status is | |
returned; this permits the caller to try a different opcode table if the | |
opcode is in a shared address space (e.g., the EMA and VMA firmware). If the | |
instruction is not present in the supplied opcode table, SCPE_ARG status is | |
returned, which causes the caller to print the instruction in numeric format | |
with the default radix. Otherwise, SCPE_OK status is returned if a | |
single-word instruction was printed, or the negative number of extra words | |
(beyond the first) consumed in printing the instruction is returned. For | |
example, printing a two-word instruction returns SCPE_OK_2_WORDS (= -1). | |
The "addr" parameter is used in two cases: to supply the operand address(es) | |
that follow the instruction when the "val" array has not been fully | |
populated, and to supply the current page for memory reference instructions | |
that have the C bit set. | |
If the routine is called to examine a memory location, the "val" array will | |
be fully populated with the instruction and the values that follow. However, | |
if the routine is called by an instruction trace, only the first word is set; | |
this is to avoid the overhead of loading extra operand words for instructions | |
that do not have operands (the vast majority). In this case, the "addr" | |
parameter will contain the address of the instruction word ORed with the | |
VAL_EMPTY flag. Once the instruction is decoded, the routine knows the | |
number of operand words required, and these are loaded before printing the | |
operands. | |
When printing a current-page memory reference instruction residing in CPU | |
memory, the instruction word supplies the 10-bit offset from the page | |
containing the instruction. This is merged with the page number from the | |
"addr" parameter to form the operand address. | |
However, if the instruction is not in memory (e.g., it resides in a disc data | |
buffer or device register), then the final load address is indeterminate. In | |
this case, the "addr" parameter is set to a value greater than the maximum | |
possible memory address as an indication that only the current-page offset is | |
valid. | |
All machine instruction opcodes are single-word, with the exception of the | |
vector arithmetic instructions in the Vector Instruction Set; these use | |
two-word opcodes. If the routine is entered to print a VIS instruction, "val | |
[0]" and "val [1]" must both be set, even if the VAL_EMPTY flag is set. | |
Upon entry, the required feature set from the opcode description is checked | |
against the current CPU feature set. If the required option is not | |
installed, the routine exits with SCPE_UNK. Otherwise, the instruction is | |
masked and shifted to obtain the primary index into the opcode table. For | |
instructions that use the A/B-selector bit (bit 11), the index is shifted to | |
the second half of the table if the bit is set. | |
If the primary entry exists, then the opcode mnemonic is obtained from the | |
entry and printed. Otherwise, the secondary table entries must be searched | |
linearly. Each entry is compared to the instruction with the operand bits | |
masked off; if a match occurs, then the opcode mnemonic is checked. If it is | |
empty, then this is a two-word instruction whose second word is invalid. In | |
this case, the routine prints the two words in numeric format, separated by a | |
comma, and returns success to indicate that the instruction was printed. | |
Otherwise the mnemonic is present, so it is printed. If the opcode | |
descriptor indicates that only a single match is permitted, the loop is | |
exited to print the operand(s), if any. Otherwise, multiple matches are | |
allowed (e.g., for SRG instructions), so the secondary entry search is | |
continued until the end of the table. | |
If the instruction does not match either a primary or a secondary entry, the | |
routine exits with SCPE_ARG status. The caller will then print the value in | |
numeric form. | |
The type and count of operand(s), if any, are indicated by the "type" and | |
"count" values in the matched opcode table entry. The entry also indicates | |
which operand(s) are addresses and which are data values. | |
If operands are needed but not present in the "val" array, they are loaded | |
now using the current DMS map. The operand type dispatches to one of several | |
handlers that obtain the operand(s) from the instruction. Operands that are | |
extracted from the instruction are placed in "val [1]" for printing. Each | |
operand is printed using the address radix for addresses and the supplied | |
data radix for data. | |
After printing the operand(s), the routine returns the number of words | |
consumed. | |
Implementation notes: | |
1. If the opcode descriptor mask is zero, then the opcode table contains | |
only secondary entries. The descriptor shift value then determines | |
whether a single match is allowed (zero) or multiple matches are allowed | |
(non-zero). | |
2. If the opcode descriptor A/B-register selector indicates that bit 11 is | |
decoded, then the opcode primary table will be twice the size implied by | |
the opcode mask width. The first half of the table decodes instructions | |
with bit 11 equal to zero; the second half decodes instruction with bit | |
11 equal to 1. | |
3. Only the SRG and ASG instructions contain multiple micro-opcodes, and | |
none of these take operands. | |
*/ | |
static t_stat fprint_instruction (FILE *ofile, t_addr addr, t_value *val, uint32 radix, | |
const OP_DESC op_desc, const OP_TABLE ops) | |
{ | |
OP_TYPE op_type; | |
uint32 op_index, op_size, op_count, op_radix, op_address_set; | |
t_value instruction, op_value; | |
t_stat status; | |
const char *prefix = NULL; /* label to print before the operand */ | |
t_bool clear = FALSE; /* TRUE if the instruction contains a CLF micro-op */ | |
t_bool separator = FALSE; /* TRUE if a separator between multiple ops is needed */ | |
uint32 op_start = 1; /* the "val" array index of the first operand */ | |
if (!(cpu_configuration & op_desc.feature & OPTION_MASK /* if the required feature set is not enabled */ | |
&& cpu_configuration & op_desc.feature & CPU_MASK)) /* for the current CPU configuration */ | |
return SCPE_UNK; /* then we cannot decode the instruction */ | |
instruction = TO_DWORD (val [1], val [0]); /* merge the two supplied values */ | |
op_size = (op_desc.mask >> op_desc.shift) /* determine the size of the primary table part */ | |
+ (op_desc.mask != 0); /* if it is present */ | |
op_index = ((uint32) instruction & op_desc.mask) >> op_desc.shift; /* extract the opcode primary index */ | |
if (op_desc.ab_selector) { /* if the A/B-register selector is significant */ | |
if (op_desc.ab_selector & instruction) /* then if the A/B-register selector bit is set */ | |
op_index = op_index + op_size; /* then use the second half of the table */ | |
op_size = op_size * 2; /* the primary table is twice the indicated size */ | |
} | |
if (op_desc.mask && 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 = op_size; /* 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 /* matches the instruction */ | |
& ops [op_index].op_bits /* masked to the significant opcode bits */ | |
& op_props [ops [op_index].type].mask)) /* and with the operand bits masked off */ | |
if (ops [op_index].mnemonic [0]) { /* then if the entry is defined */ | |
if (separator) /* then if a separator is needed */ | |
fputc (',', ofile); /* then print it */ | |
fputs (ops [op_index].mnemonic, ofile); /* print the opcode mnemonic */ | |
if (op_desc.mask == OP_LINEAR /* if multiple matches */ | |
&& op_desc.shift == OP_MULTIPLE) /* are allowed */ | |
separator = TRUE; /* then separators will be needed between mnemonics */ | |
else /* otherwise */ | |
break; /* the search terminates on the first match */ | |
} | |
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); /* with the default radix */ | |
fputc (',', ofile); /* add a separator */ | |
fprint_val (ofile, val [1], cpu_dev.dradix, /* print the second word */ | |
cpu_dev.dwidth, PV_RZRO); | |
return SCPE_OK_2_WORDS; /* return success to indicate printing is complete */ | |
} | |
if (separator) /* if one or more micro-ops was found */ | |
return SCPE_OK; /* then return, as there are no operands */ | |
else if (ops [op_index].mnemonic == NULL) /* otherwise if the opcode was not found */ | |
return SCPE_ARG; /* then return error status to print it in octal */ | |
} | |
op_type = ops [op_index].type; /* get the type of the instruction operand(s) */ | |
op_value = val [0] & ~op_props [op_type].mask; /* mask the first instruction word to the operand value */ | |
op_count = (uint32) op_props [op_type].count; /* get the number of operands */ | |
status = (t_stat) - op_props [op_type].count; /* and set the initial number of words consumed */ | |
op_address_set = op_props [op_type].address_set; /* get the address/data-selector bit set */ | |
op_radix = (radix ? radix : cpu_dev.dradix); /* assume that the (only) operand is data */ | |
if (ops [op_index].op_bits > D16_MASK) { /* if this is a two-word instruction */ | |
op_start = 2; /* then the operands start after the second word */ | |
op_count = op_count + 1; /* and extend for an extra word */ | |
status = status - 1; /* and consume an additional word */ | |
} | |
if (op_count > 0 && (addr & VAL_EMPTY)) { /* if operand words are needed but not loaded */ | |
addr = addr & LA_MASK; /* then restore the logical address */ | |
for (op_index = op_start; op_index <= op_count; op_index++) /* starting with the first operand */ | |
val [op_index] = mem_fast_read ((HP_WORD) (addr + op_index), dms_ump); /* load the operands from memory */ | |
} | |
switch (op_type) { /* dispatch by the operand type */ | |
/* no operand */ | |
case opNone: | |
break; /* no formatting needed */ | |
/* MRG page bit 10, offset 0000-1777 octal, indirect bit 15 */ | |
case opMPOI: | |
if (addr > LA_MAX) /* if the instruction location is indeterminate */ | |
if (instruction & I_CP) /* then if the current-page bit is set */ | |
prefix = " C "; /* then prefix the offset with "C" */ | |
else /* otherwise it's a base-page reference */ | |
prefix = " Z "; /* so prefix the offset with "Z" */ | |
else { /* otherwise the address is valid */ | |
prefix = " "; /* so use a blank separator */ | |
if (instruction & I_CP) /* if the current-page bit is set */ | |
op_value |= addr & I_PAGENO; /* then merge the offset with the current page address */ | |
} | |
val [1] = op_value; /* set the operand value */ | |
op_count = 1; /* and print only one operand */ | |
break; | |
/* IOG hold/clear bit 9 */ | |
case opHC: | |
if (op_value) /* if the clear-flag bit is set */ | |
fputs (" C", ofile); /* then add the "C" to the mnemonic */ | |
break; | |
/* IOG select code range 00-77 octal, hold/clear bit 9 */ | |
/* IOG optional select code range 00-77 octal, hold/clear bit 9 */ | |
case opSCHC: | |
case opSCOHC: | |
clear = (instruction & I_HC); /* set TRUE if the clear-flag bit is set */ | |
/* fall into the opSC case */ | |
/* IOG select code range 00-77 octal */ | |
case opSC: | |
prefix = " "; /* add a separator */ | |
val [1] = op_value; /* and set the operand value */ | |
op_count = 1; /* and print only one operand */ | |
break; | |
/* EAU shift/rotate count range 1-16 */ | |
case opShift: | |
prefix = " "; /* add a separator */ | |
op_radix = (radix ? radix : 10); /* and default the shift counts to decimal */ | |
if (op_value == 0) /* if the operand is zero */ | |
val [1] = 16; /* then the shift count is 16 */ | |
else /* otherwise */ | |
val [1] = op_value; /* then shift count is the operand value */ | |
op_count = 1; /* print only one operand */ | |
break; | |
/* IOP index negative offset range 1-20 octal */ | |
case opIOPON: | |
prefix = " -"; /* prefix the operand with the sign */ | |
val [1] = 16 - op_value; /* and set the (absolute) offset */ | |
op_radix = (radix ? radix : 10); /* default the offset to decimal */ | |
op_count = 1; /* and print only one operand */ | |
break; | |
/* IOP index positive offset range 0-17 octal */ | |
case opIOPOP: | |
prefix = " "; /* add a separator */ | |
val [1] = op_value; /* and set the (positive) offset */ | |
op_radix = (radix ? radix : 10); /* default the offset to decimal */ | |
op_count = 1; /* and print only one operand */ | |
break; | |
/* IOP index offset range 0-37 octal (+20 bias) */ | |
case opIOPO: | |
if (op_value >= 020) { /* if the operand is positive */ | |
prefix = " "; /* then omit the sign */ | |
val [1] = op_value - 020; /* and remove the bias */ | |
} | |
else { /* otherwise the operand is negative */ | |
prefix = " -"; /* so prefix a minus sign */ | |
val [1] = 020 - op_value; /* and remove the bias to get the absolute value */ | |
} | |
op_radix = (radix ? radix : 10); /* default the offset to decimal */ | |
op_count = 1; /* and print only one operand */ | |
break; | |
/* UIG zero word, four (indirect) memory addresses */ | |
/* UIG zero word, five (indirect) memory addresses */ | |
/* UIG zero word, six (indirect) memory addresses */ | |
/* UIG zero word, eight (indirect) memory addresses */ | |
case opZA4: | |
case opZA5: | |
case opZA6: | |
case opZA8: | |
prefix = " "; /* add a separator */ | |
op_start = 2; /* and skip the all-zeros word */ | |
break; | |
/* One memory address range 00000-77777 octal, zero word, indirect bit 15 */ | |
case opMA1ZI: | |
prefix = " "; /* add a separator */ | |
op_count = 1; /* and print only one operand */ | |
break; | |
/* One to seven memory addresses range 00000-77777 octal, indirect bit 15 */ | |
case opMA1I: | |
case opMA2I: | |
case opMA3I: | |
case opMA4I: | |
case opMA5I: | |
case opMA6I: | |
case opMA7I: | |
/* UIG one data value */ | |
/* UIG two data values */ | |
/* UIG one (indirect) memory address, one data value */ | |
/* UIG two data values, one (indirect) memory address */ | |
/* UIG one data value, five (indirect) memory addresses */ | |
case opV1: | |
case opV2: | |
case opA1V1: | |
case opV2A1: | |
case opV1A5: | |
prefix = " "; /* add a separator */ | |
break; | |
} /* end of the operand type dispatch */ | |
if (prefix) /* if an operand is present */ | |
for (op_index = op_start; /* then format the operands */ | |
op_index <= op_count; /* starting with the first operand word */ | |
op_index++) { | |
fputs (prefix, ofile); /* print the operand prefix or separator */ | |
if (op_address_set & 1) { /* if this operand is an address */ | |
fprint_val (ofile, val [op_index] & LA_MASK, /* then print the logical address */ | |
cpu_dev.aradix, LA_WIDTH, PV_LEFT); /* using the address radix */ | |
if (val [op_index] & I_IA) /* add an indirect indicator */ | |
fputs (",I", ofile); /* if specified by the operand */ | |
} | |
else /* otherwise it's a data value */ | |
fprint_value (ofile, val [op_index], /* so print the full value */ | |
op_radix, DV_WIDTH, PV_LEFT); /* using the specified radix */ | |
op_address_set = op_address_set >> 1; /* shift the next address/data flag bit into place */ | |
} /* and loop until all operands are printed */ | |
if (clear) /* add a clear-flag indicator */ | |
fputs (",C", ofile); /* if specified by the instruction */ | |
return status; /* return the number of words consumed */ | |
} | |
/* Parse an address operand. | |
Address operands of the form: | |
<octal value>[,I] | |
...are parsed. On entry, "cptr" points at the first character of the octal | |
value, and "status" points at a variable to hold the return status. If the | |
parse succeeds, the address with the optional indirect bit (bit 15) is | |
returned, and the status is set to SCPE_OK. If the parse fails, the value is | |
out of the logical range, or extraneous characters follow the operand, | |
then zero is returned, and the status is set to SCPE_ARG. | |
Implementation notes: | |
1. The string that "cptr" points to is trimmed of trailing spaces before this | |
routine is called. Therefore, the trailing NUL test is sufficient to | |
determine if there are more characters in the input buffer. | |
*/ | |
static t_value parse_address (CONST char *cptr, t_stat *status) | |
{ | |
CONST char *iptr; | |
t_value address; | |
address = strtotv (cptr, &iptr, cpu_dev.aradix); /* parse the address */ | |
if (cptr == iptr || address > LA_MAX) { /* if a parse error occurred or the value is too big */ | |
*status = SCPE_ARG; /* then return an invalid argument error */ | |
return 0; | |
} | |
else if (*iptr == '\0') { /* otherwise if there is no indirect indicator */ | |
*status = SCPE_OK; /* then the parse succeeds */ | |
return address; /* and return the address */ | |
} | |
else if (strcmp (iptr, ",I") == 0) { /* otherwise if there is an indirect indicator */ | |
*status = SCPE_OK; /* then the parse succeeds */ | |
return address | I_IA; /* and return the address with the indirect bit set */ | |
} | |
else { /* otherwise there are extraneous characters following */ | |
*status = SCPE_ARG; /* so return an invalid argument error */ | |
return 0; | |
} | |
} | |
/* Parse a character or numeric value. | |
This routine parses a token pointed to by "cptr" using the supplied "radix" | |
and with the maximum allowed value "max". If the parse succeeds, the routine | |
sets "status" to SCPE_OK and returns the parsed value. | |
Single-character ASCII parsing is performed if the specified radix is 256. | |
Otherwise, a numeric parse is attempted. | |
*/ | |
static t_value parse_value (CONST char *cptr, uint32 radix, t_value max, t_stat *status) | |
{ | |
if (radix == 256) /* if ASCII character parsing is requested */ | |
if (cptr [0] != '\0' && (t_value) cptr [0] < max) { /* then if a character is present and within range */ | |
*status = SCPE_OK; /* then indicate success */ | |
return (t_value) cptr [0]; /* and convert the character value */ | |
} | |
else { /* otherwise */ | |
*status = SCPE_ARG; /* report that the character parse failed */ | |
return 0; | |
} | |
else /* otherwise parse as a number */ | |
return get_uint (cptr, radix, max, status); /* with the specified radix and maximum value */ | |
} | |
/* Parse a CPU instruction. | |
This routine parses the command line for a CPU instruction and its | |
operand(s), if any. On entry, "cptr" points at the instruction mnemonic, | |
"addr" is the address where the instruction will be stored, "val" points to | |
an array of t_values of depth "sim_emax" where the word(s) comprising the | |
machine instruction will be saved, "radix" contains the desired operand radix | |
or zero if the default radix is to be used, and "target" indicates the target | |
of the instruction (device, CPU, or trace). | |
The routine returns a status code to the caller. SCPE_OK status is returned | |
if a single-word instruction was parsed, or the negative number of extra | |
words (beyond the first) occupied by the instruction is returned. For | |
example, parsing a two-word instruction returns SCPE_OK_2_WORDS (= -1). If | |
"cptr" does not point at a valid mnemonic, or the mnemonic is not valid for | |
the current CPU feature set, or an operand error is present, SCPE_ARG is | |
returned. | |
The routine first separates the instruction mnemonic from its operands, if | |
any. If the first token is not present or starts with a digit, SCPE_ARG is | |
returned. If the target for the parsed value is not a CPU memory location, | |
the address is reset to an invalid value to indicate that a relative offset | |
parse should be performed for Memory Reference Group instructions. Then the | |
parser table is scanned to search for a match. | |
The parser table contains entries pointing to pairs of opcode descriptors and | |
opcode tables, in order of decreasing frequency of appearance in the typical | |
instruction mix. For each entry, if the required firmware option is | |
currently installed in the CPU, then the table is searched linearly for a | |
match with the mnemonic token. If a match is found, the "parse_instruction" | |
routine is called to assemble the instruction opcode and any associated | |
operands into the "val" array for return to the caller. | |
If the token does not match any legal instruction mnemonic, the routine | |
returns SCPE_ARG. | |
Implementation notes: | |
1. The mnemonic token is delineated by either a space or a comma; the latter | |
is used to separate the multiple mnemonics of SRG and ASG instructions. | |
*/ | |
static t_stat parse_cpu (CONST char *cptr, t_addr addr, t_value *val, uint32 radix, SYMBOL_SOURCE target) | |
{ | |
CONST char *gptr; | |
const PARSER_ENTRY *pptr; | |
const OP_ENTRY *eptr; | |
char gbuf [CBUFSIZE]; | |
gptr = get_glyph (cptr, gbuf, ','); /* parse the opcode and shift to uppercase */ | |
if (gbuf [0] == '\0' || isdigit (gbuf [0])) /* if the glyph is missing or is numeric */ | |
return SCPE_ARG; /* then it is not an instruction mnemonic */ | |
if (target == Device_Symbol) /* if the target is not main memory */ | |
addr = PA_MAX + 1; /* then invalidate the target address */ | |
for (pptr = parser_table; pptr->descriptor != NULL; pptr++) /* search the parser table */ | |
if (cpu_configuration & pptr->descriptor->feature & OPTION_MASK /* if the required feature set is enabled */ | |
&& cpu_configuration & pptr->descriptor->feature & CPU_MASK) /* for the current CPU configuration */ | |
for (eptr = pptr->opcodes; eptr->mnemonic != NULL; eptr++) /* then search the opcode table */ | |
if (strcmp (eptr->mnemonic, gbuf) == 0) /* if a matching mnemonic is found */ | |
return parse_instruction (gptr, addr, val, radix, eptr); /* then parse the operands */ | |
return SCPE_ARG; /* no match was found, so return failure */ | |
} | |
/* Parse a CPU instruction in symbolic format. | |
This routine is called to parse and encode a CPU instruction mnemonic and any | |
associated operand(s). On entry, "cptr" points at the input buffer after the | |
first mnemonic token, "addr" is the address where the instruction will be | |
stored, "val" points to an array of t_values of depth "sim_emax" where the | |
word(s) comprising the machine instruction will be saved, "radix" contains | |
the desired operand parsing radix or zero if the default radix is to be used, | |
and "optr" points at the entry in an opcode table corresponding to the | |
initial instruction mnemonic. If "optr" points at an SRG or ASG opcode | |
entry, then "cptr" may point to additional micro-ops to be merged with the | |
original opcode. Otherwise, "cptr" will point to any needed instruction | |
operands. | |
On exit, a status code is returned to the caller. SCPE_OK status is returned | |
if a single-word instruction was parsed, or the negative number of extra | |
words (beyond the first) occupied by the instruction is returned. For | |
example, parsing a two-word instruction returns SCPE_OK_2_WORDS (= -1). If | |
the remainder of the input buffer does not parse correctly, or does not | |
contain the operands required by the instruction, SCPE_ARG is returned. | |
The routine begins by saving the initial opcode word(s) from the opcode table | |
entry. If the opcode belongs to the SRG or ASG, it may be the first of | |
several micro-ops that must be parsed individually. To ensure that each | |
micro-op is specified only once and that any A/B-register usage is consistent | |
between micro-ops, the significant bits of each parsed micro-op are | |
accumulated. The accumulated bits are passed to the "parse_micro_op" | |
routine, where the checks are made. | |
If the initial opcode is from the ASG, any additional micro-op mnemonics in | |
the input buffer must be in the order they appear in the "asg_uops" table. | |
The scan starts with the opcode table entry after the one corresponding to | |
the initial opcode. | |
Parsing SRG opcodes is more involved. The first opcode may be a shift or | |
rotate instruction, or it may be an SRG micro-op (CLE and/or SLA/B). In | |
either case, parsing continues with the (remaining) micro-ops and then with a | |
second shift or rotate instruction. | |
A complication is that an initial CLE, SLA, and SLB may be either an SRG or | |
an ASG micro-op. The determination depends on whether they appear with a | |
shift or rotate instruction or with a micro-op belonging only to the ASG. | |
The decision must be deferred until a mnemonic from one of these two groups | |
is parsed. If CLE and/or SLA/B appears alone, then it is encoded as an SRG | |
instruction. | |
Examples of SRG and ASG parsing follow: | |
Input String Parsed as Reason | |
--------------- --------- --------------- | |
CLE SRG | |
CLE,SLA SRG | |
CLE,SLA,SSA ASG | |
CME,SLA ASG | |
SLA,SSB error mixed A/B | |
SEZ,ERA error mixed ASG/SRG | |
SLA,CLE error out of order | |
RAL SRG | |
RAL,RAL SRG | |
RAL,CLE,SLA,ERA SRG | |
RAL,CLE,SLA,ERB error mixed A/B | |
RAL,SLB error mixed A/B | |
RAL,CLE,SSA error mixed SRG/ASG | |
CLA,CCA error bad combination | |
CLA,CLA error duplicate | |
If the initial opcode is not an SRG or ASG opcode, then only a single | |
mnemonic is allowed, and the operand(s), if any, are now examined. The type | |
and count of operand(s), if any, are indicated by the "type" and "count" | |
values in the matched opcode table entry. The entry also indicates which | |
operand(s) are addresses and which are data values. These values drive the | |
parsing of the operands. Parsed operand values are placed in the "val" array | |
in order of appearance after the instruction word. | |
An MRG memory address operand may be specified either as an absolute address | |
or as an explicit page indicator ("Z" or "C") and a page offset. An absolute | |
address is converted into an offset and zero/current page indicator by | |
examining the address value. If the value is less than 2000 octal, a | |
base-page reference is encoded. If the value falls within the same page as | |
the "addr" parameter value, a current-page reference is encoded. If neither | |
condition is true, then SCPE_ARG is returned. | |
IOG instructions parse the select code and an optional hold-or-clear-flag | |
indicator. These values are encoded into the instruction. Parsing is | |
complicated in that specification of the select code is optional for the HLT | |
instruction, defaulting to select code 0. If the select code is omitted, the | |
comma that separates it and the "C" indicator is also omitted. | |
EAG shift and rotate counts are specified as 1-16 but are encoded in the | |
instruction as 1-15 and 0, respectively. IOP index instructions offsets are | |
specified as -16 to 15 (or +15) but are encoded in the instruction in | |
excess-16 format. | |
The remaining instructions encode operands in separate memory words following | |
the opcode. A few instructions that are interruptible require emission of an | |
all-zeros word following the instruction (either preceding or following the | |
operands). Address operands are parsed as absolute logical addresses with | |
optional indirect indicators. Data operands are parsed as absolute values. | |
Parsing concludes with a check for extraneous characters on the line; | |
presence causes an SCPE_ARG return. | |
Implementation notes: | |
1. The "repeated instruction" test for the second shift/rotate op is needed | |
to catch the "NOP,ALS" sequence. | |
2. The select code may be omitted when entering the HLT instruction (it | |
defaults to 0), so "HLT" and "HLT C" are permissible entries. For the | |
latter, if the radix is overridden to hexadecimal, the "C" is interpreted | |
as select code 14 (octal) rather than as a "halt and clear flag" | |
instruction. | |
3. Parsing the 2100 IOP instruction mnemonics "LAI" and "SAI" always matches | |
the "opIOPON" entries. Therefore, the opcodes are those of the negative | |
index instructions. However, because the negative and positive opcode | |
values are adjacent, adding (not ORing) the offset value produces the | |
correct instruction encoding for both (index -16 => encoded 0, -1 => 15, | |
0 => 16, and +15 => 31). | |
*/ | |
static t_stat parse_instruction (CONST char *cptr, t_addr addr, t_value *val, uint32 radix, const OP_ENTRY *optr) | |
{ | |
CONST char *gptr; | |
const char *mptr; | |
char gbuf [CBUFSIZE]; | |
OP_TYPE op_type; | |
uint32 accumulator, op_index, op_count, op_radix, op_address_set; | |
t_stat status, consumption; | |
t_value op_value; | |
t_bool op_implicit; | |
t_bool op_flag = FALSE; | |
uint32 op_start = 1; /* the "val" array index of the first operand */ | |
val [0] = LOWER_WORD (optr->opcode); /* set the (initial) opcode */ | |
val [1] = UPPER_WORD (optr->opcode); /* and the subopcode if it is a two-word instruction */ | |
if (*cptr != '\0' /* if there is more to parse */ | |
&& (SRGOP (optr->opcode) || ASGOP (optr->opcode))) { /* and the first opcode is SRG or ASG */ | |
accumulator = (uint32) optr->op_bits; /* then accumulate the significant opcode bits */ | |
gptr = get_glyph (cptr, gbuf, ','); /* parse the next opcode, if present */ | |
if (ASGOP (optr->opcode)) /* if the initial opcode is ASG */ | |
optr++; /* then point at the next table entry for parsing */ | |
else { /* otherwise this is an SRG opcode */ | |
if (optr->opcode & SRG1_DE_MASK) { /* if it is a shift or rotate instruction */ | |
mptr = NULL; /* then it cannot be an ASG instruction */ | |
optr = srg_uops; /* and parsing continues with the micro-ops */ | |
} | |
else { /* otherwise it's a micro-op that could be ASG */ | |
mptr = optr->mnemonic; /* so save the initial mnemonic pointer */ | |
optr++; /* and start with the next micro-op */ | |
} | |
status = parse_micro_ops (optr, gbuf, val, /* parse the SRG micro-ops */ | |
&gptr, &accumulator); | |
if (status != SCPE_INCOMP) /* if the parse is complete */ | |
return status; /* then return success or failure as appropriate */ | |
optr = srg2_ops; /* the mnemonic is not a micro-op, so try a shift/rotate op */ | |
status = parse_micro_ops (optr, gbuf, val, /* parse the SRG shift-rotate opcode */ | |
NULL, &accumulator); | |
if (status == SCPE_OK && *gptr != '\0') /* if the opcode was found but there is more to parse */ | |
return SCPE_ARG; /* then return failure as only one opcode is allowed */ | |
else if (status != SCPE_INCOMP) /* otherwise if the parse is complete */ | |
return status; /* then return success or failure as appropriate */ | |
if (mptr == NULL) /* the mnemonic is not an SRG, and if it cannot be an ASG */ | |
return SCPE_ARG; /* then fail with an invalid mnemonic */ | |
else { /* otherwise attempt to reparse as ASG instructions */ | |
val [0] = 0; /* clear the assembled opcode */ | |
accumulator = 0; /* and the accumulated significant opcode bits */ | |
strcpy (gbuf, mptr); /* restore the original mnemonic to the buffer */ | |
gptr = cptr; /* and the original remainder-of-line pointer */ | |
optr = asg_uops; /* set to search the ASG micro-ops table */ | |
} /* and fall into the ASG parser */ | |
} | |
status = parse_micro_ops (optr, gbuf, val, /* parse the ASG micro-ops */ | |
&gptr, &accumulator); | |
if (status != SCPE_INCOMP) /* if the parse is complete */ | |
return status; /* then return success or failure as appropriate */ | |
} | |
else { /* otherwise, it's a single opcode */ | |
op_type = optr->type; /* so get the type of the instruction operand(s) */ | |
op_count = (uint32) op_props [op_type].count; /* get the number of operands */ | |
consumption = (t_stat) - op_props [op_type].count; /* and set the initial number of words consumed */ | |
op_address_set = op_props [op_type].address_set; /* get the address/data-selector bit set */ | |
op_radix = (radix ? radix : cpu_dev.dradix); /* assume that the (only) operand is data */ | |
if (optr->op_bits > D16_MASK) { /* if this is a two-word instruction */ | |
op_start = 2; /* then the operands start after the second word */ | |
op_count = op_count + 1; /* and extend for an extra word */ | |
consumption = consumption - 1; /* and consume an additional word */ | |
} | |
switch (op_type) { /* dispatch by the operand type */ | |
/* no operand */ | |
case opNone: | |
break; /* no parsing needed */ | |
/* MRG page bit 10, offset 0000-1777 octal, indirect bit 15 */ | |
case opMPOI: | |
cptr = get_glyph (cptr, gbuf, '\0'); /* get the next token */ | |
if (gbuf [0] == 'C' && gbuf [1] == '\0') { /* if the "C" modifier was specified */ | |
val [0] = val [0] | I_CP; /* then add the current-page flag */ | |
cptr = get_glyph (cptr, gbuf, '\0'); /* get the address */ | |
op_implicit = FALSE; /* and clear the implicit-page flag */ | |
} | |
else if (gbuf [0] == 'Z' && gbuf [1] == '\0') { /* otherwise if the "Z" modifier was specified */ | |
cptr = get_glyph (cptr, gbuf, '\0'); /* then get the address */ | |
op_implicit = FALSE; /* and clear the implicit-page flag */ | |
} | |
else /* otherwise neither modifier is present */ | |
op_implicit = TRUE; /* so set the flag to allow implicit-page addressing */ | |
op_value = parse_address (gbuf, &status); /* parse the address and optional indirection indicator */ | |
if (status != SCPE_OK) /* if a parse error occurred */ | |
return status; /* then return an invalid argument error */ | |
if ((op_value & VAMASK) <= I_DISP) /* if a base-page address was given */ | |
val [0] |= op_value; /* then merge the offset into the instruction */ | |
else if (addr <= LA_MAX && op_implicit /* otherwise if an implicit-page address is allowed */ | |
&& ((addr ^ op_value) & I_PAGENO) == 0) /* and the target is in the current page */ | |
val [0] |= I_CP | op_value & (I_IA | I_DISP); /* then merge the offset with the current-page flag */ | |
else /* otherwise the address cannot be reached */ | |
return SCPE_ARG; /* from the current instruction's location */ | |
break; | |
/* IOG select code range 00-77 octal, hold/clear bit 9 */ | |
/* IOG optional select code range 00-77 octal, hold/clear bit 9 */ | |
case opSCHC: | |
case opSCOHC: | |
op_flag = TRUE; /* set a flag to enable an optional ",C" */ | |
/* fall into the opSC case */ | |
/* IOG select code range 00-77 octal */ | |
case opSC: | |
cptr = get_glyph (cptr, gbuf, (op_flag ? ',' : '\0')); /* get the next glyph */ | |
op_value = get_uint (gbuf, op_radix, I_DEVMASK, &status); /* and parse the select code */ | |
if (status == SCPE_OK) /* if the select code is good */ | |
val [0] |= op_value; /* then merge it into the opcode */ | |
else if (op_type == opSCOHC) /* otherwise if the select code is optional */ | |
if (gbuf [0] == '\0') /* then if it is not supplied */ | |
break; /* then use the base instruction value */ | |
else if (gbuf [0] == 'C' && gbuf [1] == '\0') { /* otherwise if the "C" modifier was specified */ | |
val [0] |= I_HC; /* then merge the H/C bit */ | |
break; /* into the base instruction value */ | |
} | |
else /* otherwise the select code is bad */ | |
return SCPE_ARG; /* so report failure for a bad argument */ | |
/* fall into opHC case */ | |
/* IOG hold/clear bit 9 */ | |
case opHC: | |
if (*cptr != '\0') /* if there is more */ | |
if (op_type != opSC) { /* and it is expected */ | |
cptr = get_glyph (cptr, gbuf, '\0'); /* then get the glyph */ | |
if (gbuf [0] == 'C' && gbuf [1] == '\0') /* if the "C" modifier was specified */ | |
val [0] |= I_HC; /* then merge the H/C bit */ | |
else /* otherwise it's something else */ | |
return SCPE_ARG; /* so report failure for a bad argument */ | |
} | |
else /* otherwise it's not expected */ | |
return SCPE_ARG; /* so report failure for a bad argument */ | |
break; | |
/* EAU shift/rotate count range 1-16 */ | |
case opShift: | |
op_radix = (radix ? radix : 10); /* shift counts default to decimal */ | |
cptr = get_glyph (cptr, gbuf, '\0'); /* get the next glyph */ | |
op_value = parse_value (gbuf, op_radix, 16, &status); /* and parse the shift count */ | |
if (status != SCPE_OK || op_value == 0) /* if a parsing error occurred or the count is zero */ | |
return SCPE_ARG; /* then report failure for a bad argument */ | |
else if (op_value < 16) /* otherwise the count is good */ | |
val [0] |= op_value; /* so merge it into the opcode (0 encodes 16) */ | |
break; | |
/* IOP index negative offset range 1-16 */ | |
/* IOP index positive offset range 0-15 */ | |
/* IOP index offset range -16 to +15 (+16 bias) */ | |
case opIOPON: | |
case opIOPOP: | |
case opIOPO: | |
op_radix = (radix ? radix : 10); /* index offsets default to decimal */ | |
if (*cptr == '+') /* if there is a leading plus sign */ | |
cptr++; /* then skip it */ | |
else if (*cptr == '-') { /* otherwise if there is a leading minus sign */ | |
op_flag = TRUE; /* then set the negative flag */ | |
cptr++; /* and skip it */ | |
} | |
cptr = get_glyph (cptr, gbuf, '\0'); /* get the next glyph */ | |
op_value = parse_value (gbuf, op_radix, /* and parse the index offset with the appropriate range */ | |
15 + op_flag, &status); | |
if (status != SCPE_OK) /* if a parsing error or value out of range occurred */ | |
return SCPE_ARG; /* then report failure for a bad argument */ | |
else if (op_flag) /* otherwise the offset is good; if it is negative */ | |
val [0] = val [0] + 16 - op_value; /* then scale and merge the offset */ | |
else /* otherwise it is positive */ | |
val [0] = val [0] + op_value + 16; /* so shift to the positive opcode and merge the offset */ | |
break; | |
/* UIG zero word, four (indirect) memory addresses */ | |
/* UIG zero word, five (indirect) memory addresses */ | |
/* UIG zero word, six (indirect) memory addresses */ | |
/* UIG zero word, eight (indirect) memory addresses */ | |
case opZA4: | |
case opZA5: | |
case opZA6: | |
case opZA8: | |
val [1] = 0; /* add the zero word */ | |
op_start = 2; /* and bump the operand starting index */ | |
break; | |
/* One memory address range 00000-77777 octal, zero word, indirect bit 15 */ | |
case opMA1ZI: | |
val [2] = 0; /* add the zero word */ | |
op_count = 1; /* and reduce the operand count to 1 */ | |
break; | |
/* One to seven memory addresses range 00000-77777 octal, indirect bit 15 */ | |
case opMA1I: | |
case opMA2I: | |
case opMA3I: | |
case opMA4I: | |
case opMA5I: | |
case opMA6I: | |
case opMA7I: | |
/* UIG one data value */ | |
/* UIG two data values */ | |
/* UIG one (indirect) memory address, one data value */ | |
/* UIG two data values, one (indirect) memory address */ | |
/* UIG one data value, five (indirect) memory addresses */ | |
case opV1: | |
case opV2: | |
case opA1V1: | |
case opV2A1: | |
case opV1A5: | |
break; /* no special operand handling needed */ | |
} /* end of the operand type dispatch */ | |
if (op_count > 0) /* if one or more operands are required */ | |
for (op_index = op_start; /* then parse and load them */ | |
op_index <= op_count; /* in to the return array */ | |
op_index++) { | |
cptr = get_glyph (cptr, gbuf, '\0'); /* get the next glyph */ | |
if (op_address_set & 1) /* if this operand is an address */ | |
op_value = parse_address (gbuf, &status); /* then parse it with an optional indirection indicator */ | |
else /* otherwise it's a data value */ | |
op_value = parse_value (gbuf, op_radix, /* so parse it as an unsigned number */ | |
DV_UMAX, &status); | |
if (status != SCPE_OK) /* if a parse error occurred */ | |
return status; /* then return an invalid argument error */ | |
else /* otherwise */ | |
val [op_index] = op_value; /* store the operand value */ | |
op_address_set = op_address_set >> 1; /* shift the next type bit into place */ | |
} /* and loop until all operands are parsed */ | |
if (*cptr == '\0') /* if parsing is complete */ | |
return consumption; /* then return the number of instruction words */ | |
} | |
return SCPE_ARG; /* return an error for extraneous characters present */ | |
} | |
/* Parse micro-ops. | |
This routine parses a set of micro-operations that form a single instruction. | |
It is called after a token has matched an entry in the SRG or ASG opcode | |
tables. | |
On entry, "optr" points at the opcode table entry after the first token | |
match, "gbuf" points at the next mnemonic token in the input buffer, "val" | |
points at the array of t_values that will hold the assembled instruction and | |
its operands, "gptr" points at the pointer that is set to the next character | |
to parse, and "accumulator" points at the variable that holds the accumulated | |
significant opcode bits for the instruction. On exit, the routine returns | |
SCPE_OK if the input buffer was parsed correctly, SCPE_ARG if conflicting | |
A/B-register selectors were encountered (e.g., "CLA,CMB"), or SCPE_INCOMP if | |
the opcode table was exhausted without consuming the input buffer. The | |
pointer "gptr" points at is updated to point at the first character after the | |
last token parsed, and the value pointed at by "accumulator" contains the | |
accumulated significant opcode bits for the set of parsed micro-ops. The | |
former is used by the caller to continue parsing, while the latter is used to | |
track the micro-ops that have been specified to ensure that each appears only | |
once. | |
The routine searches the supplied table for mnemonics that match the tokens | |
parsed from the input buffer. For each match, checks are made to ensure that | |
the same mnemonic has not been specified before and that opcodes that specify | |
an accumulator are consistent throughout the instruction. The opcode from | |
each matching entry is then logically ORed into the instruction word, and the | |
significant opcode bits are accumulated to provide a check for duplicates. | |
When the input buffer or opcode table is exhausted, or a check fails, the | |
routine exits with the appropriate status. | |
Implementation notes: | |
1. The "repeated operation" significant-bits test is necessary only to catch | |
two cases. One is the NOP,RAL and RAL,NOP case where NOP must be | |
specified alone and yet it decodes as an SRG instruction, which allows | |
micro-ops. The other is the CLA,CMA case where multiple encoded ASG | |
operations are specified. All other repeated operation cases (e.g., | |
CLE,SLA,CLE) are prohibited by the single pass through a table arranged | |
in the required order (so the second CLE will not be found because SLA | |
comes after CLE in the table). | |
The first case could be handled by substituting an operand test for the | |
group test (e.g., optr->type == opSRG || optr->type == opASG and marking | |
NOP as opNone and all other SRG opcodes as opSRG, so that NOP will not be | |
detected as an SRG instruction) in "parse_instruction", placing the NOP | |
entry first, and starting the search with entry 2 in the SRG micro-ops | |
table. | |
The second case could be handled by treating the encoded operations | |
(CLx/CMx/CCx and CLE/CME/CCE) as indexed searches of separate primary | |
tables. | |
The added complexity of these alternates is not worth the effort, given | |
the relative simplicity of the "repeated operation" test. | |
2. The A/B-register selector state must be the same for all micro-ops in a | |
given instruction. That is, all micro-ops that use the A or B registers | |
must reference the same accumulator. The check is performed by logically | |
ANDing the accumulated and current significant opcode bits with the | |
AB_SELECT mask (to determine if the A/B-register select is significant | |
and has been set), and then with the exclusive-OR of the accumulated and | |
current micro-ops (to determine if the current A/B-register select is the | |
same as the prior A/B-register selects). If there is a discrepancy, then | |
the current micro-op does not use the same register as a previously | |
specified micro-op. | |
*/ | |
static t_stat parse_micro_ops (const OP_ENTRY *optr, char *gbuf, t_value *val, CONST char **gptr, uint32 *accumulator) | |
{ | |
while (optr->mnemonic != NULL) /* search the table until the NULL entry at the end */ | |
if (strcmp (optr->mnemonic, gbuf) == 0) { /* if the mnemonic matches this entry */ | |
if (*accumulator & optr->op_bits & ~(AB_SELECT | ASG)) /* then if this opcode has been entered before */ | |
return SCPE_ARG; /* then fail with a repeated instruction error */ | |
if (*accumulator & optr->op_bits & AB_SELECT /* if the A/B-register selector is significant */ | |
& (optr->opcode ^ val [0])) /* and the new opcode disagrees with the prior ones */ | |
return SCPE_ARG; /* then fail with an A/B inconsistent error */ | |
val [0] |= optr->opcode; /* include the opcode */ | |
*accumulator |= optr->op_bits; /* and accumulate the significant opcode bits */ | |
if (gptr == NULL || **gptr == '\0') /* if the line is fully parsed or only one match allowed */ | |
return SCPE_OK; /* then we are done */ | |
else /* otherwise there is more */ | |
*gptr = get_glyph (*gptr, gbuf, ','); /* so parse the next opcode */ | |
} | |
else /* otherwise this entry does not match */ | |
optr++; /* so point at the next one */ | |
return SCPE_INCOMP; | |
} | |
/* Get a 16-bit word from a file. | |
This routine gets the next two bytes from the open file stream "fileref" and | |
returns a 16-bit word with the first byte in the upper half and the second | |
byte in the lower half. If a file error occurs (either a read error or an | |
unexpected end-of-file), the routine returns EOF. | |
*/ | |
static int fgetword (FILE *fileref) | |
{ | |
int c1, c2; | |
c1 = fgetc (fileref); /* get the first byte from the file */ | |
if (c1 == EOF) /* if the read failed */ | |
return c1; /* then return EOF */ | |
c2 = fgetc (fileref); /* get the second byte from the file */ | |
if (c2 == EOF) /* if the read failed */ | |
return c2; /* then return EOF */ | |
else /* otherwise */ | |
return (int) TO_WORD (c1, c2); /* return the word formed by the two bytes */ | |
} | |
/* Put a 16-bit word to a file. | |
This routine writes two bytes from the 16-bit word "data" into the open file | |
stream "fileref". The upper half of the word supplies the first byte, and | |
the lower half supplies the second byte. If a write error occurs, the | |
routine returns EOF. Otherwise, it returns the second byte written. | |
*/ | |
static int fputword (int data, FILE *fileref) | |
{ | |
int status; | |
status = fputc (UPPER_BYTE (data), fileref); /* write the first byte */ | |
if (status != EOF) /* if the write succeeded */ | |
status = fputc (LOWER_BYTE (data), fileref); /* then write the second byte */ | |
return status; /* return the result of the write */ | |
} |