blob: f4ac86b9478dec6526fe1e73c28c5aea16304dca [file] [log] [blame] [raw]
/* hp3000_cpu_cis.c: HP 32234A COBOL II Instruction Set simulator
Copyright (c) 2016-2017, J. David Bryan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of the author shall not be used
in advertising or otherwise to promote the sale, use or other dealings in
this Software without prior written authorization from the author.
22-Apr-17 JDB Corrected the significance flag setting in "edit"
29-Dec-16 JDB Disabled interrupt checks pending test generation
19-Oct-16 JDB Passes the COBOL-II firmware "A" diagnostic (D441A)
06-Oct-16 JDB Passes the COBOL-II firmware "B" diagnostic (D442A)
21-Sep-16 JDB Created
References:
- Machine Instruction Set Reference Manual
(30000-90022, June 1984)
- HP 3000 Series 64/68/70 Computer Systems Microcode Manual
(30140-90045, October 1986)
This module implements the HP 32234A COBOL II Extended Instruction
Set firmware, also known as the Language Extension Instructions. The set
contains these instructions in the firmware extension range 020460-020477:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | 0 0 0 | S | ALGN
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | 0 0 1 | S | ABSN
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | 1 0 0 | B | EDIT
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | 1 0 1 | B | CMPS
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | 1 1 0 0 | XBR
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | 1 1 0 1 | PARC
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | 1 1 1 0 | ENDP
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | 1 1 1 1 | CMPT
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 0 0 0 0 0 0 0 | 0 0 0 1 1 | B |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | 1 1 1 1 | TCCS
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 0 0 0 0 0 0 0 | 0 0 1 | > | = | < |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | 1 1 1 1 | CVND
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 0 0 0 0 0 0 0 | 0 1 | sign op | S |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | 1 1 1 1 | LDW
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 0 0 0 0 0 0 0 | 1 0 0 0 0 | S |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | 1 1 1 1 | LDDW
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 0 0 0 0 0 0 0 | 1 0 0 0 1 | S |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | 1 1 1 1 | TR
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 0 0 0 0 0 0 0 | 1 0 0 1 0 | B |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | 1 1 1 1 | ABSD
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 0 0 0 0 0 0 0 | 1 0 0 1 1 | S |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | 1 1 1 1 | NEGD
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 0 0 0 0 0 0 0 0 | 1 0 1 0 0 | S |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
S = S-decrement
B = Bank select (0/1 = PB-relative/DB-relative)
The PARC, ENDP, and XBR instructions implement the COBOL "PERFORM" statement.
ABSD and NEGD manipulate packed decimal numbers. ALGN, ABSN, EDIT, and CVND
manipulate external decimal numbers. The LDW and LDDW instructions load
single and double-words, respectively, from byte-aligned addresses. TCCS
tests the status register for a specific condition code and loads the logical
result; operation is similar to the Bcc instruction, except that the result
is explicit rather than implied by the branch. TR translates a string using
a mapping table. CMPS and CMPT compare two strings (with translation for
CMPT) and set the condition code accordingly. CMPS is similar to CMPB,
except that a shorter string is blank-padded for comparison.
Packed decimal (also known as COMPUTATIONAL-3, BCD, and binary-coded decimal)
numbers contain from 1 to 28 digits that are stored in pairs in successive
memory bytes in this format:
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| unused/digit | digit |
+---+---+---+---+---+---+---+---+
| digit | digit |
+---+---+---+---+---+---+---+---+
[...]
+---+---+---+---+---+---+---+---+
| digit | digit |
+---+---+---+---+---+---+---+---+
| digit | sign |
+---+---+---+---+---+---+---+---+
The sign is always located in the lower four bits of the final byte, so
numbers with an even number of digits will not use the upper four bits of the
first byte. Digits are represented by four-bit values from 0-9 (i.e., in
Binary-Coded Decimal or BCD), with the most-significant digit first and the
least-significant digit last. The sign is given by one of these encodings:
1100 - the number is positive
1101 - the number is negative
1111 - the number is unsigned
All other values are interpreted as meaning the number is positive; however,
only one of the three values above is generated.
Numbers may begin at an even or odd byte address, and the size of the number
(in digits) may be even or odd, so there are four possible cases of packing
into 16-bit words:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| unused/digit | digit | digit | digit | addr even
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| digit | digit | digit | sign | size even
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| unused/digit | digit | digit | digit | addr even
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| digit | sign | ... | size odd
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| ... | unused/digit | digit | addr odd
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| digit | digit | digit | sign | size ?...
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| ... | unused/digit | digit | addr odd
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| digit | sign | ... |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
External decimal (also known as DISPLAY, numeric display, and ASCII) values
contain contain from 1 to 28 digits that are stored as ASCII characters in
successive memory bytes in this format:
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| digit |
+---+---+---+---+---+---+---+---+
| digit |
+---+---+---+---+---+---+---+---+
[...]
+---+---+---+---+---+---+---+---+
| digit |
+---+---+---+---+---+---+---+---+
| digit and sign |
+---+---+---+---+---+---+---+---+
The number begins with the most-significant digit. The sign is combined with
the least-significant digit in the final byte. Each digit except the LSD
must be in the ASCII range "0" through "9". Leading blanks are allowed, and
the entire number may be blank, but blanks within a number are not. The
least-signifiant digit and sign are represented by either:
"0" and "1" through "9" for an unsigned number
"{" and "A" through "I" for a positive number
"}" and "J" through "R" for a negative number
Numbers may begin at an even or odd byte address, and the size of the number
(in digits) may be even or odd, so there are four possible cases of packing
into 16-bit words:
- the number completely fills the words
- the number has an unused leading byte in the first word
- the number has an unused trailing byte in the last word
- the number has an unused byte at each end
Any unused bytes are not part of the number and are not disturbed.
The EDIT instruction moves bytes from a source string to a target string
under the control of a subprogram indicated by a PB- or DB-relative address.
The subprogram consists of 8-bit instructions, each followed by zero or more
8-bit operands. The subprogram ends with a TE (Terminate Edit) instruction.
The supported instructions are:
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 0 0 0 | imm. operand | MC - move characters
+---+---+---+---+---+---+---+---+
{ | extended operand | } (present if imm. operand = 0)
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 0 0 0 1 | imm. operand | MA - move alphabetics
+---+---+---+---+---+---+---+---+
{ | extended operand | } (present if imm. operand = 0)
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 0 0 1 0 | imm. operand | MN - move numerics
+---+---+---+---+---+---+---+---+
{ | extended operand | } (present if imm. operand = 0)
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 0 0 1 1 | imm. operand | MNS - move numerics suppressed
+---+---+---+---+---+---+---+---+
{ | extended operand | } (present if imm. operand = 0)
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 0 1 0 0 | imm. operand | MFL - move numerics with floating insertion
+---+---+---+---+---+---+---+---+
{ | extended operand | } (present if imm. operand = 0)
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 0 1 0 1 | imm. operand | IC - insert character
+---+---+---+---+---+---+---+---+
{ | extended operand | } (present if imm. operand = 0)
+---+---+---+---+---+---+---+---+
| character to insert |
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 0 1 1 0 | imm. operand | ICS - insert character suppressed
+---+---+---+---+---+---+---+---+
{ | extended operand | } (present if imm. operand = 0)
+---+---+---+---+---+---+---+---+
| character to insert |
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 0 1 1 1 | imm. operand | ICI - insert characters immediate
+---+---+---+---+---+---+---+---+
{ | extended operand | } (present if imm. operand = 0)
+---+---+---+---+---+---+---+---+
| character 1 to insert |
+---+---+---+---+---+---+---+---+
...
+---+---+---+---+---+---+---+---+
{ | character n to insert | } (present if operand > 1)
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 0 0 0 | imm. operand | ICSI - insert characters suppressed immediate
+---+---+---+---+---+---+---+---+
{ | extended operand | } (present if imm. operand = 0)
+---+---+---+---+---+---+---+---+
| character 1 to insert |
+---+---+---+---+---+---+---+---+
...
+---+---+---+---+---+---+---+---+
{ | character n to insert | } (present if operand > 1)
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 0 0 1 | imm. operand | BRIS - branch if significance
+---+---+---+---+---+---+---+---+
{ | extended operand | } (present if imm. operand = 0)
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 0 1 0 | imm. operand | SUFT - subtract from target
+---+---+---+---+---+---+---+---+
{ | extended operand | } (present if imm. operand = 0)
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 0 1 1 | imm. operand | SUFS - subtract from source
+---+---+---+---+---+---+---+---+
{ | extended operand | } (present if imm. operand = 0)
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 0 1 1 | imm. operand | ICP - insert character punctuation
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 1 0 0 | imm. operand | ICPS - insert character punctuation suppressed
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 1 1 0 | imm. operand | IS - insert character on sign
+---+---+---+---+---+---+---+---+
| positive sign character 1 |
+---+---+---+---+---+---+---+---+
...
+---+---+---+---+---+---+---+---+
{ | character n to insert | } (present if operand > 1)
+---+---+---+---+---+---+---+---+
| negative sign character 1 |
+---+---+---+---+---+---+---+---+
...
+---+---+---+---+---+---+---+---+
{ | character n to insert | } (present if operand > 1)
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 1 1 1 | 0 0 0 0 | TE - terminate edit
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 1 1 1 | 0 0 0 1 | ENDF - end floating point insertion
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 1 1 1 | 0 0 1 0 | SST1 - set significance to 1
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 1 1 1 | 0 0 1 1 | SST0 - set significance to 0
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 1 1 1 | 0 1 0 0 | MDWO - move digit with overpunch
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 1 1 1 | 0 1 0 1 | SFC - set fill character
+---+---+---+---+---+---+---+---+
| fill character |
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 1 1 1 | 0 1 1 0 | SFLC - set float character
+---+---+---+---+---+---+---+---+
| + float char | - float char |
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 1 1 1 | 0 1 1 1 | DFLC - define float character
+---+---+---+---+---+---+---+---+
| positive float character |
+---+---+---+---+---+---+---+---+
| negative float character |
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 1 1 1 | 1 0 0 0 | SETC - set loop count
+---+---+---+---+---+---+---+---+
| loop count |
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 1 1 1 | 1 0 0 1 | DBNZ - decrement loop count and branch
+---+---+---+---+---+---+---+---+
| branch displacement |
+---+---+---+---+---+---+---+---+
The EDIT instruction is interruptible after each subprogram command. The TR,
CMPS, and CMPT instructions are interruptible after each byte processed. The
remaining instructions execute to completion.
Two user traps may be taken by these instructions if the T bit is on in the
status register:
- Word Count Overflow (parameter 17)
- Invalid ASCII Digit (parameter 14)
The Word Count Overflow trap is taken when an instruction is given an
external decimal number with more than 28 digits. The Invalid ASCII Digit
trap occurs when an external decimal number contains characters other than
"0"-"9", a "+" or "-" sign in any position other than the first or last
position, blanks other than leading blanks, an invalid overpunch character,
or an overpunch character in any position other than the last digit.
Enabling the OPND debug flag traces the instruction operands, including the
subprogram operations of the EDIT instruction.
Implementation notes:
1. In several cases noted below, the hardware microcode implementations
differ from the descriptions in the Machine Instruction Set manual.
Also, the comments in the microcode source sometimes do not correctly
described the microcode actions. In all cases of conflict, the simulator
follows the microcode implementation.
2. The Machine Instruction Set manual references trap conditions (Invalid
Alphabetic Character, Invalid Operand Length, Invalid Source Character
Count, and Invalid Digit Count) that are not defined in the Series II/III
System Reference Manual. Examination of the microcode indicates that
only the Invalid ASCII Digit and Word Count Overflow traps are taken.
3. Target operand tracing is not done if a trap occurred, as the result will
be invalid.
4. The calls to "cpu_interrupt_pending" are currently stubbed out pending
testing of interrupted instruction exits and reentries. The COBOL II
firmware diagnostics do not test interruptibility, so tests have to be
written before correct action is assured. This will be addressed in a
future release.
*/
#include "hp3000_defs.h"
#include "hp3000_cpu.h"
#include "hp3000_mem.h"
/* Disable intra-instruction interrupt checking pending testing */
#define cpu_interrupt_pending(x) FALSE /* TEMPORARY ONLY */
/* Program constants */
#define MAX_DIGITS 28 /* maximum number of decimal digits accepted */
/* Packed decimal constants */
#define SIGN_MASK 0360u /* 8-bit numeric sign mask */
#define SIGN_TEST_MASK 0017u /* 8-bit numeric sign test mask */
#define SIGN_PLUS 0014u /* 1100 -> the number is positive */
#define SIGN_MINUS 0015u /* 1101 -> the number is negative */
#define SIGN_UNSIGNED 0017u /* 1111 -> the number is unsigned */
#define IS_NEG(v) (((v) & SIGN_TEST_MASK) == SIGN_MINUS)
/* External decimal constants */
typedef enum { /* location and type of the numeric sign character */
Leading_Separate = 0, /* plus or minus prefix */
Trailing_Separate = 1, /* plus or minus suffix */
Leading_Overpunch = 2, /* overpunched first character */
Trailing_Overpunch = 3, /* overpunched last character */
Absolute = 4 /* no sign character */
} DISPLAY_MODE;
typedef enum { /* numeric sign values */
Negative,
Unsigned,
Positive
} NUMERIC_SIGN;
static uint8 overpunch [3] [10] = { /* sign overpunches, indexed by NUMERIC_SIGN */
{ '}', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R' }, /* Negative */
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }, /* Unsigned */
{ '{', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I' } /* Positive */
};
/* Operand printer function */
typedef char * (*OP_PRINT) (uint32 byte_address, uint32 byte_length);
/* CIS local utility routine declarations */
static void branch_external (HP_WORD segment, HP_WORD offset);
static uint32 strip_overpunch (uint8 *byte, NUMERIC_SIGN *sign);
static uint32 convert (HP_WORD sba, HP_WORD tba, HP_WORD count, DISPLAY_MODE mode);
static t_bool edit (t_stat *status, uint32 *trap);
static t_bool compare (BYTE_ACCESS *source, BYTE_ACCESS *target, BYTE_ACCESS *table, t_stat *status);
static void fprint_operands (BYTE_ACCESS *source, BYTE_ACCESS *target, uint32 trap);
static void fprint_translated_operands (BYTE_ACCESS *source, BYTE_ACCESS *target, BYTE_ACCESS *table);
static void fprint_operand (BYTE_ACCESS *op, char *label, OP_PRINT operand_printer);
/* CIS global routines */
/* Execute a CIS operation.
This routine is called to execute the COBOL II instruction currently in the
CIR. The instruction format is:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | CIS opcode |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Instructions 020460 - 020476 are decoded directly. Code 020477 introduces a
set of two-word instructions; the second word decodes the operation. Codes
020464 through 020467 and code 020477 with the second word less than 000006
or greater than 000051 are unimplemented.
Entry is with four TOS registers preloaded (this is done for all firmware
extension instructions). Therefore, no SR preload is needed here.
Instructions that provide option bits to leave addresses on the stack do not
modify those addresses during instruction execution.
If an invalid digit or digit count is detected, a microcode abort occurs. If
the T (trap) bit is not set in the status register, the O (overflow) bit is
set and execution continues. If the T bit is set, the O bit is not set and
the trap is taken. In either case, the stack is popped according to the
instruction.
Implementation notes:
1. If a two-word instruction has a valid first word (020477) but an invalid
second word, the CIR must retain the first-word value and P must be
incremented over the second word when the Unimplemented Instruction trap
is taken.
2. The MICRO_ABORT macro does a longjmp to the microcode abort handler in
the sim_instr routine. If the user trap bit (T-bit) in the status word
is set, the routine clears the overflow bit (O-bit) and invokes the trap
handler. If the T-bit is clear, the routine sets the O-bit and continues
with the next instruction. Therefore, we do not need to check the T-bit
here and can simply do an unconditional MICRO_ABORT if a trap is
indicated.
3. Regarding error traps, the "Instruction Commentary" for the Language
Extension Instructions section of the Machine Instruction Set manual
says, in part:
If the User Traps bit (STA.2) is not set, the Overflow bit (STA.4) is
set, the stack is popped in accordance with the instruction, and
execution continues with the following instruction. If the Traps bit
is set, the Overflow bit is not set, the stack is not popped, and a
trap to the Traps segment, segment 1, is taken.
However, the Series 6x microcode for, e.g., the ALGN instruction does a
"JSZ TRPR" after unconditionally popping the stack (page 334). TRPR
checks STA.2 and either starts trap processing if it is set or sets
Overflow and proceeds to the next instruction if it is clear. So the
stack is popped in either case. This implementation follows this
behavior.
4. In the comments for the ABSD/NEGD instructions, the Series 6x microcode
manual says, "Extract the last two bits of the sign..." and then "If the
sign bits = 01 (the sign was negative => D), then...", implying that only
the lower two bits are tested. The microcode itself, however, tests all
four bits and assumes a negative value only if they equal 1101. This
behavior agrees with the EIS commentary in the Machine Instruction Set
manual, which says that all sign values other than those defined as plus,
minus, and unsigned, are treated as plus. This implementation follows
this behavior.
*/
t_stat cpu_cis_op (void)
{
BYTE_ACCESS source, target, table;
NUMERIC_SIGN sign;
HP_WORD segment, offset, sign_cntl;
HP_WORD source_rba, table_rba;
char label [64];
uint8 byte;
uint8 source_length, source_lead, source_fraction;
uint8 target_length, target_lead, target_fraction;
uint32 opcode;
t_bool store, zero_fill;
uint32 trap = trap_None;
t_stat status = SCPE_OK;
opcode = FMEXSUBOP (CIR); /* get the opcode from the instruction */
switch (opcode) { /* dispatch the opcode */
case 000: /* ALGN (O; INV DIG, WC OVF, STOV, STUN, BNDV) */
case 001:
source_length = LOWER_BYTE (RA); /* get the source digit count */
source_fraction = UPPER_BYTE (RA); /* and the source fraction count */
target_length = LOWER_BYTE (RC); /* get the target digit count */
target_fraction = UPPER_BYTE (RC); /* and the target fraction count */
if (source_fraction > source_length /* if the source fraction count is invalid */
|| source_length > MAX_DIGITS /* or the source digit count is too large */
|| target_fraction > target_length /* or the target fraction count is invalid */
|| target_length > MAX_DIGITS) /* or the target digit count is too large */
trap = trap_Word_Count_Overflow; /* then trap for a count overflow */
else if (source_length > 0 && target_length > 0) { /* otherwise if there is an alignment to do */
sign = Unsigned; /* then assume the source is unsigned */
store = TRUE; /* enable storing */
zero_fill = TRUE; /* and zero fill of the target */
source_lead = source_length - source_fraction; /* get the counts of the leading digits */
target_lead = target_length - target_fraction; /* of the source and target numbers */
mem_init_byte (&source, data_checked, &RB, source_length); /* set up byte accessors */
mem_init_byte (&target, data_checked, &RD, target_length); /* for the source and target strings */
while (source_length > 0 || target_length > 0) { /* while digits remain to be aligned */
if (source_lead < target_lead /* if the target has more leading digits */
|| source_length == 0) /* or there are no more source digits */
byte = '0'; /* then transfer a zero */
else { /* otherwise */
store = (source_lead == target_lead); /* don't store if still more leading source digits */
byte = mem_read_byte (&source); /* get a source digit */
source_length = source_length - 1; /* and count it */
if (source_length == 0) { /* if this is the last source digit */
trap = strip_overpunch (&byte, &sign); /* then strip the sign from the digit */
if (trap != trap_None) /* if the overpunch was invalid */
break; /* then abandon the transfer */
}
if (source_lead > 0) /* if this is a leading digit */
source_lead = source_lead - 1; /* then count it */
if (byte >= '0' && byte <= '9') /* if it is numeric */
zero_fill = FALSE; /* then turn zero-filling off */
else if (byte == ' ' && zero_fill) /* otherwise if it's a blank and zero-filling is on */
byte = '0'; /* then fill */
else { /* otherwise */
trap = trap_Invalid_ASCII_Digit; /* the digit is invalid */
break; /* so abandon the transfer */
}
}
if (store && target_length > 0) { /* if storing and target space is available */
if (target_length == 1) /* then if this is the last byte to store */
byte = overpunch [sign] [byte - '0']; /* then overpunch with the source sign */
mem_write_byte (&target, byte); /* store the target digit */
target_length = target_length - 1; /* and count it */
if (target_lead > 0) /* if this is a leading digit */
target_lead = target_lead - 1; /* then count it */
}
} /* continue the alignment loop */
mem_update_byte (&target); /* update the final target byte */
if (DPRINTING (cpu_dev, DEB_MOPND)) {
sprintf (label, "source fraction %d length", source_fraction);
fprint_operand (&source, label, &fmt_byte_operand);
if (trap == trap_None) {
sprintf (label, "target fraction %d length", target_fraction);
fprint_operand (&target, label, &fmt_byte_operand);
}
}
}
if (CIR & CIS_SDEC_MASK) /* if the S-decrement bit is set in the instruction */
SR = 0; /* then pop all four operands */
else { /* otherwise */
cpu_pop (); /* pop all */
cpu_pop (); /* of the operands */
cpu_pop (); /* except the target address */
}
if (trap == trap_None) /* if the instruction succeeded */
STA &= ~STATUS_O; /* then clear overflow status */
else /* otherwise */
MICRO_ABORT (trap); /* abort with the indicated trap */
break;
case 002: /* ABSN (CCA, O; INV DIG, WC OVF, STOV, STUN, BNDV) */
case 003:
if (RA > MAX_DIGITS) /* if the digit count is too large */
trap = trap_Word_Count_Overflow; /* then trap for a count overflow */
else if (RA > 0) { /* otherwise if there are digits to process */
source_rba = RB; /* then use a working source byte address pointer */
mem_init_byte (&source, data_checked, /* and set up a byte accessor for the string */
&source_rba, RA);
if (DPRINTING (cpu_dev, DEB_MOPND))
fprint_operand (&source, "source", &fmt_byte_operand);
byte = mem_read_byte (&source); /* get the first source digit */
while (byte == ' ' && RA > 0) { /* while there are leading blanks */
mem_modify_byte (&source, '0'); /* replace each blank with a zero digit */
RA = RA - 1; /* and count it */
if (RA > 0) /* if there are more digits */
byte = mem_read_byte (&source); /* then get the next one */
}
while (RA > 1 && byte >= '0' && byte <= '9') { /* validate the digits */
byte = mem_read_byte (&source); /* by getting and checking */
RA = RA - 1; /* each digit until the last */
}
if (RA == 1) { /* if this is the last digit */
trap = strip_overpunch (&byte, &sign); /* then strip the sign from the digit */
if (trap == trap_None) { /* if the overpunch was valid */
if (sign == Negative) /* then if the number was negative */
SET_CCL; /* then set the less-than condition code */
else /* otherwise a positive or zero number */
SET_CCG; /* sets the greater-than condition code */
mem_modify_byte (&source, byte); /* rewrite the digit without the sign overpunch */
}
}
else if (RA > 0) /* otherwise if we've abandoned the validation */
trap = trap_Invalid_ASCII_Digit; /* then trap for an invalid digit */
mem_post_byte (&source); /* post the last byte written */
if (DPRINTING (cpu_dev, DEB_MOPND) && trap == trap_None)
fprint_operand (&source, "target", &fmt_byte_operand);
}
cpu_pop (); /* pop the digit count */
if (CIR & CIS_SDEC_MASK) /* if the S-decrement bit is set in the instruction */
cpu_pop (); /* then pop the address as well */
if (trap == trap_None) /* if the instruction succeeded */
STA &= ~STATUS_O; /* then clear overflow status */
else /* otherwise */
MICRO_ABORT (trap); /* abort with the indicated trap */
break;
case 010: /* EDIT (O; INV DIG, WC OVF, STUN, BNDV) */
case 011:
if (edit (&status, &trap)) { /* process the edit subprogram; if no interrupt intervened */
SR = 0; /* then pop all four values from the stack */
if (trap != trap_None) /* if a trap occurred */
MICRO_ABORT (trap); /* then take it now */
}
break;
case 012: /* CMPS (CCx; STOV, STUN, BNDV) */
case 013:
mem_init_byte (&source, data_checked, &RB, RA); /* set up a byte accessor for the first operand */
if (CIR & CIS_DB_FLAG) /* if the second operand is in the data segment */
mem_init_byte (&target, data_checked, &RD, RC); /* then set up a DB byte accessor */
else /* otherwise */
mem_init_byte (&target, program_checked, &RD, RC); /* set up a PB byte accessor */
if (compare (&source, &target, NULL, &status)) { /* compare the strings; if no interrupt intervened */
SR = 0; /* then pop all four values from the stack */
if (DPRINTING (cpu_dev, DEB_MOPND))
fprint_operands (&source, &target, trap);
}
break;
case 014: /* XBR (none; STUN, BNDV, CSTV, MODE, ABS CST, TRACE) */
segment = RA; /* get the segment number */
offset = RB; /* and PB-relative offset of the target */
cpu_pop (); /* pop the operands */
cpu_pop ();
branch_external (segment, offset); /* branch to the target location */
break;
case 015: /* PARC (none; STOV, STUN, BNDV, CSTV, MODE, ABS CST, TRACE) */
segment = RB; /* get the segment number */
offset = RC; /* and the PB-relative offset of the target */
RB = STA; /* replace the segment number with the current status */
RC = P - 1 - PB & R_MASK; /* and the target with the PB-relative return address */
branch_external (segment, offset); /* branch to the target location */
break;
case 016: /* ENDP (none; STUN, BNDV, CSTV, MODE, ABS CST, TRACE) */
if (RA == RB) { /* if the paragraph numbers are equal */
SR = 0; /* then pop all of the parameters */
branch_external (RC, RD); /* and return to the caller */
}
else /* otherwise the paragraph numbers are unequal */
cpu_pop (); /* so pop the current paragraph number and continue */
break;
case 017: /* double-word instructions */
opcode = NIR; /* get the operation code from the second word */
cpu_read_memory (fetch, P, &NIR); /* load the next instruction */
P = P + 1 & R_MASK; /* and point to the following instruction */
switch (opcode) { /* dispatch the second instruction word */
case 006: /* CMPT (CCx; STOV, STUN, BNDV) */
case 007:
cpu_read_memory (stack, SM, &table_rba); /* get the byte address of the translation table */
mem_init_byte (&source, data_checked, &RB, RA); /* set up a byte accessor for the first operand */
if (opcode & CIS_DB_FLAG) /* if the second operand is in the data segment */
mem_init_byte (&target, data_checked, &RD, RC); /* then set up a DB byte accessor */
else /* otherwise */
mem_init_byte (&target, program_checked, &RD, RC); /* set up a PB byte accessor */
mem_init_byte (&table, data, &table_rba, 0); /* set up a byte accessor for the translation table */
if (compare (&source, &target, &table, &status)) { /* compare the strings; if no interrupt intervened */
SR = 0; /* then pop the first four parameters */
cpu_pop (); /* and then the fifth parameter */
if (DPRINTING (cpu_dev, DEB_MOPND))
fprint_translated_operands (&source, &target, &table);
}
break;
case 010: /* TCCS (Test condition code and set, bits 13-15 options) */
case 011:
case 012:
case 013:
case 014:
case 015:
case 016:
case 017:
cpu_push (); /* push the stack down */
if ((STA & STATUS_CC_MASK) != STATUS_CCI /* if the condition code is not invalid */
&& TO_CCF (STA) & opcode << TCCS_CCF_SHIFT) /* and the test succeeds */
RA = D16_UMAX; /* then set the TOS to TRUE */
else /* otherwise */
RA = 0; /* set the TOS to FALSE */
break;
case 020: /* CVND (O; INV DIG, WC OVF, STOV, STUN, BNDV) */
case 021:
case 022:
case 023:
case 024:
case 025:
case 026:
case 027:
case 030:
case 031:
case 032:
case 033:
case 034:
case 035:
case 036:
case 037:
if (RA > MAX_DIGITS) /* if the digit count is too large */
trap = trap_Word_Count_Overflow; /* then trap for a count overflow */
else if (RA > 0) { /* otherwise if there are digits to convert */
sign_cntl = (opcode & CVND_SC_MASK) /* then get the sign control code */
>> CVND_SC_SHIFT;
if (sign_cntl > Absolute) /* if the code is one of the unnormalized values */
sign_cntl = Absolute; /* then normalize it */
trap = convert (RB, RC, RA, sign_cntl); /* convert the number as directed */
}
cpu_pop (); /* pop the source character count */
cpu_pop (); /* and source byte address */
if (opcode & CIS_SDEC_MASK) /* if the S-decrement bit is set in the instruction */
cpu_pop (); /* then pop the target byte address also */
if (trap == trap_None) /* if the instruction succeeded */
STA &= ~STATUS_O; /* then clear overflow status */
else /* otherwise */
MICRO_ABORT (trap); /* abort with the indicated trap */
break;
case 040: /* LDW (none; STOV, STUN, BNDV) */
case 041:
source_rba = RA; /* use a working source byte address pointer */
if ((opcode & CIS_SDEC_MASK) == 0) /* if the S-decrement bit is clear */
cpu_push (); /* then push the stack down for the return value */
mem_init_byte (&source, data_checked, &source_rba, 2); /* set up a byte accessor for the characters */
byte = mem_read_byte (&source); /* get the first byte */
RA = TO_WORD (byte, mem_read_byte (&source)); /* and the second byte and merge them */
if (DPRINTING (cpu_dev, DEB_MOPND))
fprint_operand (&source, "source", &fmt_byte_operand);
break;
case 042: /* LDDW (none; STOV, STUN, BNDV) */
case 043:
source_rba = RA; /* use a working source byte address pointer */
if ((opcode & CIS_SDEC_MASK) == 0) /* if the S-decrement bit is clear */
cpu_push (); /* then push the stack down for the return value */
cpu_push (); /* push again for the two-word return value */
mem_init_byte (&source, data_checked, &source_rba, 4); /* set up a byte accessor for the characters */
byte = mem_read_byte (&source); /* get the first byte */
RA = TO_WORD (byte, mem_read_byte (&source)); /* and the second byte and merge them */
byte = mem_read_byte (&source); /* get the third byte */
RB = TO_WORD (byte, mem_read_byte (&source)); /* and the fourth byte and merge them */
if (DPRINTING (cpu_dev, DEB_MOPND))
fprint_operand (&source, "source", &fmt_byte_operand);
break;
case 044: /* TR (none; STUN, STOV, BNDV) */
case 045:
if (RA > 0) { /* if there are bytes to translate */
mem_init_byte (&source, data_checked, &RC, RA); /* then set up byte accessors */
mem_init_byte (&target, data_checked, &RB, RA); /* for the source and target strings */
if (opcode & CIS_DB_FLAG) /* if the table is in the data segment */
mem_init_byte (&table, data, &RD, 0); /* then set up a DB byte accessor */
else /* otherwise */
mem_init_byte (&table, program, &RD, 0); /* set up a PB byte accessor */
while (RA > 0) { /* while there are bytes to translate */
byte = mem_read_byte (&source); /* get the next byte */
byte = mem_lookup_byte (&table, byte); /* look up the translated value */
mem_write_byte (&target, byte); /* and write it to the target */
RA = RA - 1; /* update the byte count (cannot underflow) */
if (cpu_interrupt_pending (&status)) { /* if an interrupt is pending */
mem_update_byte (&target); /* then update the last word written */
return status; /* and return with an interrupt set up or an error */
}
}
mem_update_byte (&target); /* update the final target byte */
if (DPRINTING (cpu_dev, DEB_MOPND))
fprint_operands (&source, &target, trap);
}
SR = 0; /* pop all four values from the stack */
break;
case 046: /* ABSD (CCA, O; WC OVF, STOV, STUN, BNDV) */
case 047:
case 050: /* NEGD (CCA, O; WC OVF, STOV, STUN, BNDV) */
case 051:
if (RA > MAX_DIGITS) /* if the digit count is too large */
trap = trap_Word_Count_Overflow; /* then trap for a count overflow */
else { /* otherwise */
mem_init_byte (&source, data, &RB, RA); /* set up a byte accessor for the operand */
source_rba = RA / 2 + RB; /* index to the trailing sign digit */
mem_init_byte (&target, data, &source_rba, 0); /* and set up a byte accessor for the sign */
if (DPRINTING (cpu_dev, DEB_MOPND))
fprint_operand (&source, "source", &fmt_bcd_operand);
byte = mem_read_byte (&target); /* get the sign byte */
if (opcode < 050) { /* if this is an ABSD instruction */
if (IS_NEG (byte)) /* then if the number is negative */
SET_CCL; /* then set the less-than condition code */
else /* otherwise a positive or zero number */
SET_CCG; /* sets the greater-than condition code */
byte |= SIGN_UNSIGNED; /* change the number to unsigned */
}
else /* otherwise this is a NEGD instruction */
if (IS_NEG (byte)) { /* so if the number is negative */
byte = byte & SIGN_MASK | SIGN_PLUS; /* then make the number positive */
SET_CCG; /* and set the greater-than condition code */
}
else { /* otherwise the number is positive */
byte = byte & SIGN_MASK | SIGN_MINUS; /* so make it negative */
SET_CCL; /* and set the less-than condition code */
}
mem_modify_byte (&target, byte); /* rewrite the digit */
mem_post_byte (&target); /* and post it */
if (DPRINTING (cpu_dev, DEB_MOPND))
fprint_operand (&source, "target", &fmt_bcd_operand);
}
cpu_pop (); /* pop the digit count */
if (opcode & CIS_SDEC_MASK) /* if the S-decrement bit is set in the instruction */
cpu_pop (); /* then pop the address as well */
if (trap == trap_None) /* if the instruction succeeded */
STA &= ~STATUS_O; /* then clear overflow status */
else /* otherwise */
MICRO_ABORT (trap); /* abort with the indicated trap */
break;
default:
status = STOP_UNIMPL; /* the second instruction word is unimplemented */
}
break; /* end of the double-word instructions */
default:
status = STOP_UNIMPL; /* the firmware extension instruction is unimplemented */
}
return status; /* return the execution status */
}
/* CIS local utility routine declarations */
/* Execute a branch to a location in a specified segment.
If the target segment is the same as the current segment, as indicated in the
status register, then a local label is used. Otherwise, an external label is
used that specifies the target segment entry 0 of the STT, which specifies
the start of the segment. The target is then set up in the same manner as a
procedure call, with the program counter adjusted by the target offset.
The procedure setup may abort, rather than returning, if a trap prevents the
setup from succeeding.
*/
static void branch_external (HP_WORD segment, HP_WORD offset)
{
HP_WORD label;
if (STATUS_CS (segment) == STATUS_CS (STA)) /* if the target segment is current */
label = LABEL_LOCAL; /* then use a local label */
else /* otherwise */
label = LABEL_EXTERNAL /* use an external label */
| TO_LABEL (STATUS_CS (segment), 0); /* that specifies the target segment and STT 0 */
cpu_call_procedure (label, offset); /* set up the segment as for a procedure call */
return; /* return with the branch completed */
}
/* Strip the sign from an overpunched digit.
If the supplied digit includes an overpunched sign, it is stripped and
returned separately, along with the stripped digit; trap_None is returned to
indicate success. If the digit is not a valid overpunch character, then
trap_Invalid_ASCII_Digit is returned to indicate failure.
Implementation notes:
1. A set of direct tests is faster than looping through the overpunch table
to search for the matching digit. Faster still would be a direct 256-way
reverse overpunch lookup, but the gain is not significant.
*/
static uint32 strip_overpunch (uint8 *byte, NUMERIC_SIGN *sign)
{
if (*byte == '{') { /* if the digit is a zero with positive overpunch */
*byte = '0'; /* then strip the overpunch */
*sign = Positive; /* and set the returned sign positive */
}
else if (*byte >= 'A' && *byte <= 'I') { /* otherwise if the digit is a positive overpunch */
*byte = *byte - 'A' + '1'; /* then strip the overpunch to yield a 1-9 value */
*sign = Positive; /* and set the returned sign positive */
}
else if (*byte == '}') { /* otherwise if the digit is a zero with negative overpunch */
*byte = '0'; /* then strip the overpunch */
*sign = Negative; /* and set the returned sign negative */
}
else if (*byte >= 'J' && *byte <= 'R') { /* otherwise if the digit is a negative overpunch */
*byte = *byte - 'J' + '1'; /* then strip the overpunch to yield a 1-9 value */
*sign = Negative; /* and set the returned sign negative */
}
else if (*byte >= '0' && *byte <= '9') /* otherwise if the digit is not overpunched */
*sign = Unsigned; /* then simply set the return as unsigned */
else /* otherwise the digit is not a valid overpunch */
return trap_Invalid_ASCII_Digit; /* so return an Invalid Digit trap as failure */
return trap_None; /* return no trap for successful stripping */
}
/* Convert a numeric display string.
This routine converts a numeric display string to an external decimal number.
A display string consists of the ASCII characters "0" to "9" and optional
leading spaces. The sign may be omitted (unsigned), a separate leading or
trailing "+" or "-" sign, or an integral leading or trailing overpunch. The
result is an external decimal number, consisting of "0" to "9" digits with a
trailing sign overpunch. The routine implements the CVND instruction.
The "sba" parameter is the DB-relative byte address of the source string,
"tba" is the DB-relative byte address of the target string, "count" is the
number of source characters (including the sign character, if separate), and
"mode" is the sign display mode of the source (leading separate, trailing
separate, leading overpunch, trailing overpunch, or unsigned). The count is
always non-zero on entry.
The routine validates all of the source characters, even if no conversion is
needed, and returns trap_Invalid_ASCII_Digit if validation fails. trap_None
is returned if the conversion succeeds.
The implementation starts by setting indices for the location of the sign.
Separate indices for separate and overpunched signs are kept, with the unused
index set to a value beyond the string so that it never matches. If the
input has separate sign mode and only one character, then that character is
the sign, and a zero value is implied. Leading blanks are zero-filled until
the first numeric digit (if the mode is leading overpunch, then the first
digit is numeric, so leading blanks are not permitted).
Implementation notes:
1. The "last_digit" variable is only used in Trailing_Separate mode when
there is at least one character other than the sign. Consequently, it
will never be used before it is set. However, the compiler cannot detect
this and will warn erroneously that it could be used uninitialized.
Therefore, it is set (redundantly) before the conversion loop entry.
*/
static uint32 convert (HP_WORD sba, HP_WORD tba, HP_WORD count, DISPLAY_MODE mode)
{
BYTE_ACCESS source, target;
NUMERIC_SIGN sign;
HP_WORD separate_index, overpunch_index;
uint8 byte, last_digit;
uint32 trap = trap_None;
t_bool zero_fill = TRUE;
t_bool bare_sign = FALSE;
switch (mode) { /* set up the sign flag and indices */
case Leading_Separate:
separate_index = count; /* the first character is the separate sign */
overpunch_index = MAX_DIGITS + 1; /* and no character is the overpunched sign */
bare_sign = (count == 1); /* only one character implies a bare sign */
break;
case Trailing_Separate:
separate_index = 1; /* the last character is the separate sign */
overpunch_index = MAX_DIGITS + 1; /* and no character is the overpunched sign */
bare_sign = (count == 1); /* only one character implies a bare sign */
break;
case Leading_Overpunch:
overpunch_index = count; /* the first character is the overpunched sign */
separate_index = MAX_DIGITS + 1; /* and no character is the separate sign */
break;
case Trailing_Overpunch:
overpunch_index = 1; /* the last character is the overpunched sign */
separate_index = MAX_DIGITS + 1; /* and no character is the separate sign */
break;
case Absolute:
default: /* quiet "potentially uninitialized local variable" warnings */
separate_index = MAX_DIGITS + 1; /* no character is the overpunched sign */
overpunch_index = MAX_DIGITS + 1; /* and no character is the separate sign */
break;
}
mem_init_byte (&source, data_checked, &sba, count); /* set up byte accessors */
mem_init_byte (&target, data_checked, &tba, count); /* for the source and target strings */
sign = Unsigned; /* assume that the source is unsigned */
byte = 0; /* shut up incorrect "may be used uninitialized" warning */
while (count > 0) { /* while there are characters to convert */
last_digit = byte; /* then save any previous character */
byte = mem_read_byte (&source); /* and get the next source character */
if (count == separate_index) { /* If this is the separate sign character */
if (byte == '+' || byte == ' ') /* then a plus or blank */
sign = Positive; /* indicates a positive number */
else if (byte == '-') /* otherwise a minus */
sign = Negative; /* indicates a negative number */
else { /* otherwise */
trap = trap_Invalid_ASCII_Digit; /* the character is not a valid sign */
break; /* so abandon the conversion */
}
if (bare_sign) /* if this is the only character */
byte = '0'; /* then supply a zero for overpunching */
else { /* otherwise */
if (mode == Trailing_Separate) { /* if this is the trailing sign */
byte = overpunch [sign] [last_digit - '0']; /* then overpunch the last numeric digit with the sign */
mem_modify_byte (&target, byte); /* and update it */
}
count = count - 1; /* count the separate sign character */
continue; /* and continue with the next character */
}
}
else if (count == overpunch_index && byte != ' ') { /* otherwise if this is the non-blank overpunched sign */
trap = strip_overpunch (&byte, &sign); /* then strip the overpunch and set the sign */
if (trap == trap_None) /* if the overpunch was valid */
zero_fill = FALSE; /* then turn zero-filling off */
else /* otherwise the overpunch was not valid */
break; /* so abandon the conversion */
}
if (byte >= '0' && byte <= '9') /* if the character is numeric */
zero_fill = FALSE; /* then turn zero-filling off */
else if (byte == ' ' && zero_fill) /* otherwise if it's a blank and zero-filling is on */
byte = '0'; /* then fill */
else { /* otherwise */
trap = trap_Invalid_ASCII_Digit; /* the digit is invalid */
break; /* so abandon the conversion */
}
if (count == 1 && mode != Absolute) /* if this is the last character and the value is signed */
byte = overpunch [sign] [byte - '0']; /* then overpunch with the sign */
mem_write_byte (&target, byte); /* store it in the target string */
count = count - 1; /* count the character */
} /* and continue */
mem_update_byte (&target); /* update the final target byte */
if (DPRINTING (cpu_dev, DEB_MOPND))
fprint_operands (&source, &target, trap);
return trap; /* return the trap condition */
}
/* Edit a number into a formatted picture.
This routine moves an external decimal number to a target buffer under the
control of an editing subprogram. The subprogram consists of one or more
editing operations. The routine is interruptible between operations.
On entry, the TOS registers and the condition code in the STA register are
set as follows:
RA = 0 on initial entry, or 177777 on reentry after an interrupt
RB = the source byte address (DB-relative)
RC = the target byte address (DB-relative)
RD = the subprogram byte address (PB or DB-relative)
CC = the sign of the source number
On return, the "status" parameter is set to the SCP status returned by the
interrupt test, and "trap" is set to trap_Invalid_ASCII_Digit if an operation
encountered an invalid digit, or trap_None if the edit succeeded. The
routine returns TRUE if the operation ran to completion, or FALSE if the
operation was interrupted and should be resumed.
If an interrupt is detected between operations, two words are pushed onto the
stack before the interrupt handler is called. These words hold the current
significance trigger (a flag indicating that a significant digit has been
seen or that zero filling should occur), loop count, float character, and
fill character, in this format:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 | TOS
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| S | 0 0 0 0 0 0 0 | loop count | TOS - 1
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| fill character | float character | TOS - 2
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
S = significance trigger (0/1)
The loop count/significance trigger word is written over the zero that
resides at the TOS on instruction entry, and then the fill/float character
word and a word of all ones are pushed. The source, target, and subprogram
byte addresses are updated during execution, so they retain their modified
values on reentry. When reentry is detected, the fill, float, and count
values are reestablished, and two words are popped off the stack to restore
the initial entry conditions.
If operand tracing is enabled, each subprogram operation is printed in this
format:
>>CPU opnd: 00.136024 000000 IS 6,"AAAAAA","BBBBBB"
~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
| | | |
| | | +-- subprogram operation and operands (if any)
| | +------------ octal loop counter value
| +-------------------- octal subprogram address (effective address)
+------------------------- octal bank (PBANK, DBANK, or SBANK)
...and source and target operands are printed in this format:
>>CPU opnd: 00.045177 000467 source 3,"ABC"
~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~
| | | |
| | | +-- byte operand label, length, and value
| | +------------ octal relative byte offset from base register
| +-------------------- octal address (effective address)
+------------------------- octal bank (PBANK, DBANK, or SBANK)
Implementation notes:
1. The Machine Instruction Set manual says that the DBNZ ("decrement loop
count and branch") operation adds the displacement operand. However,
the microcode manual shows that the displacement is actually subtracted.
The simulator follows the microcode implementation.
2. The BRIS ("branch if significance") operation adds the displacement to
the address of the displacement byte. After reading the displacement
byte, the address is incremented to the next location, so the operation
subtracts 1 before adding the displacement value.
3. The significance trigger is represented by the "filling" flag; its value
is the opposite of the trigger, i.e., FALSE if a significant digit has
been seen, TRUE if all leading digits have been zeros, because that more
clearly indicates that it controls leading zero suppression and
replacement.
*/
static t_bool edit (t_stat *status, uint32 *trap)
{
BYTE_ACCESS source, target, prog;
ACCESS_CLASS class;
HP_WORD bank;
char fill_char, float_char;
uint8 byte, opcode, operand, count;
uint32 loop_count;
t_bool filling = TRUE; /* TRUE if zero-filling is enabled */
t_bool terminate = FALSE; /* TRUE if the operation loop is ending */
*status = SCPE_OK; /* initialize the return status */
*trap = trap_None; /* and trap condition */
if (RA != 0) { /* if this is a reentry after an interrupt */
filling = ((RB & D16_SIGN) == 0); /* then reset the zero-filling flag */
loop_count = LOWER_BYTE (RB); /* and the loop counter */
fill_char = UPPER_BYTE (RC); /* reset the fill */
float_char = LOWER_BYTE (RC); /* and float characters */
cpu_pop (); /* pop the extra words */
cpu_pop (); /* added to save the context */
}
else { /* otherwise this is an initial entry */
filling = TRUE; /* so set the zero-filling flag */
loop_count = 0; /* and clear the loop counter */
fill_char = ' '; /* set the fill */
float_char = '$'; /* and float character defaults */
}
RA = D16_UMAX; /* set the in-process flag in case of microcode abort */
STA &= ~STATUS_O; /* clear overflow status */
if (CIR & CIS_DB_FLAG) { /* if the subprogram is in the data segment */
class = data; /* then set up for data reads */
bank = DBANK; /* and use DBANK for traces */
}
else { /* otherwise it's in the program segment */
class = program; /* so set up for program reads */
bank = PBANK; /* and use PBANK for traces */
}
mem_init_byte (&source, data, &RB, 0); /* set up byte accessors */
mem_init_byte (&target, data, &RC, 0); /* for the source and target strings */
mem_init_byte (&prog, class, &RD, 0); /* and the subprogram */
do { /* process operations while "terminate" is FALSE */
operand = mem_read_byte (&prog); /* get the next operation */
if (DPRINTING (cpu_dev, DEB_MOPND)) { /* if operand tracing is enabled */
hp_debug (&cpu_dev, DEB_MOPND, BOV_FORMAT " ", /* then print the current subprogram address */
bank, prog.word_address, loop_count); /* and loop count as octal values */
fprint_edit (sim_deb, NULL, 0, /* print the operation mnemonic */
prog.initial_byte_address /* at the current physical byte address */
+ prog.count - 1);
fputc ('\n', sim_deb); /* end the trace with a newline */
}
opcode = UPPER_HALF (operand); /* split the opcode */
operand = LOWER_HALF (operand); /* from the immediate operand */
if (operand == 0 && opcode < 014) /* if this is an extended operand */
operand = mem_read_byte (&prog); /* then read the full value from the next byte */
switch (opcode) { /* dispatch on the opcode */
case 000: /* MC - move characters */
while (operand > 0) { /* while there are characters to move */
byte = mem_read_byte (&source); /* get the next byte */
mem_write_byte (&target, byte); /* move it to the target */
operand = operand - 1; /* and count it */
}
break;
case 001: /* MA - move alphabetics */
while (operand > 0) { /* while there are characters to move */
byte = mem_read_byte (&source); /* get the next byte */
if (byte >= 'A' && byte <= 'Z' /* if the character is an uppercase letter */
|| byte >= 'a' && byte <= 'z' /* or a lowercase letter */
|| byte == ' ') { /* or a space */
mem_write_byte (&target, byte); /* then move it to the target */
operand = operand - 1; /* and count it */
}
else { /* otherwise */
*trap = trap_Invalid_ASCII_Digit; /* the character is not a valid alphabetic */
terminate = TRUE; /* so abandon the subprogram */
break; /* and the move */
}
}
break;
case 002: /* MN - move numerics */
while (operand > 0) { /* while there are characters to move */
byte = mem_read_byte (&source); /* get the next byte */
if (byte == ' ' && filling) /* if it's a blank and zero-filling is on */
byte = '0'; /* then fill */
else if (byte >= '0' && byte <= '9') /* otherwise if the character is a digit */
filling = FALSE; /* then turn zero-filling off */
else { /* otherwise */
*trap = trap_Invalid_ASCII_Digit; /* the character is not a valid numeric */
terminate = TRUE; /* so abandon the subprogram */
break; /* and the move */
}
mem_write_byte (&target, byte); /* move the character to the target */
operand = operand - 1; /* and count it */
}
break;
case 003: /* MNS - move numerics suppressed */
while (operand > 0) { /* while there are characters to move */
byte = mem_read_byte (&source); /* get the next byte */
if (filling /* if zero-filling is on */
&& (byte == ' ' || byte == '0')) /* and it's a blank or zero */
byte = fill_char; /* then substitute the fill character */
else if (byte >= '0' && byte <= '9') /* otherwise if the character is a digit */
filling = FALSE; /* then turn zero-filling off */
else { /* otherwise */
*trap = trap_Invalid_ASCII_Digit; /* the character is not a valid numeric */
terminate = TRUE; /* so abandon the subprogram */
break; /* and the move */
}
mem_write_byte (&target, byte); /* move the character to the target */
operand = operand - 1; /* and count it */
}
break;
case 004: /* MFL - move numerics with floating insertion */
while (operand > 0) { /* while there are characters to move */
byte = mem_read_byte (&source); /* get the next byte */
if (filling /* if zero-filling is on */
&& (byte == ' ' || byte == '0')) /* and it's a blank or zero */
byte = fill_char; /* then substitute the fill character */
else if (byte >= '0' && byte <= '9') { /* otherwise if the character is a digit */
if (filling) { /* then if zero-filling is still on */
filling = FALSE; /* then turn it off */
mem_write_byte (&target, float_char); /* insert the float character before the digit */
}
}
else { /* otherwise */
*trap = trap_Invalid_ASCII_Digit; /* the character is not a valid numeric */
terminate = TRUE; /* so abandon the subprogram */
break; /* and the move */
}
mem_write_byte (&target, byte); /* move the character to the target */
operand = operand - 1; /* and count it */
}
break;
case 005: /* IC - insert character */
byte = mem_read_byte (&prog); /* get the insertion character */
while (operand > 0) { /* while there are characters to insert */
mem_write_byte (&target, byte); /* copy the character to the target */
operand = operand - 1; /* and count it */
}
break;
case 006: /* ICS - insert character suppressed */
byte = mem_read_byte (&prog); /* get the insertion character */
if (filling) /* if zero-filling is on */
byte = fill_char; /* then substitute the fill character */
while (operand > 0) { /* while there are characters to insert */
mem_write_byte (&target, byte); /* copy the character to the target */
operand = operand - 1; /* and count it */
}
break;
case 007: /* ICI - insert characters immediate */
while (operand > 0) { /* while there are characters to insert */
byte = mem_read_byte (&prog); /* get the next byte */
mem_write_byte (&target, byte); /* move it to the target */
operand = operand - 1; /* and count it */
}
break;
case 010: /* ICSI - insert characters suppressed immediate */
while (operand > 0) { /* while there are characters to insert */
byte = mem_read_byte (&prog); /* get the next byte */
if (filling) /* if zero-filling is on */
byte = fill_char; /* then substitute the fill character */
mem_write_byte (&target, byte); /* copy the character to the target */
operand = operand - 1; /* and count it */
}
break;
case 011: /* BRIS - branch if significance */
if (filling == FALSE) { /* if zero-filling is off */
RD = RD - 1 + SEXT8 (operand) & R_MASK; /* then add the signed displacement to the offset */
mem_set_byte (&prog); /* and reset the subprogram accessor */
}
break;
case 012: /* SUFT - subtract from target */
mem_update_byte (&target); /* update the final target byte if needed */
RC = RC - SEXT8 (operand) & R_MASK; /* subtract the signed displacement from the offset */
mem_set_byte (&target); /* and reset the target accessor */
break;
case 013: /* SUFS - subtract from source */
RB = RB - SEXT8 (operand) & R_MASK; /* subtract the signed displacement from the offset */
mem_set_byte (&source); /* and reset the source accessor */
break;
case 014: /* ICP - insert character punctuation */
mem_write_byte (&target, operand + ' '); /* write the punctuation character to the target */
break;
case 015: /* ICPS - insert character punctuation suppressed */
if (filling) /* if zero-filling is on */
byte = fill_char; /* then substitute the fill character */
else /* otherwise */
byte = operand + ' '; /* use the supplied punctuation character */
mem_write_byte (&target, byte); /* write the character to the target */
break;
case 016: /* IS - insert characters on sign */
if (operand > 0) { /* if the character strings are present */
count = operand; /* then get the character count */
if ((STA & STATUS_CC_MASK) == STATUS_CCL) { /* if the sign is negative */
RD = RD + count & R_MASK; /* then index to the negative character string */
mem_set_byte (&prog); /* and reset the subprogram accessor */
}
while (count > 0) { /* while there are characters to copy */
byte = mem_read_byte (&prog); /* get the next byte */
mem_write_byte (&target, byte); /* copy the character to the target */
count = count - 1; /* and count it */
}
if ((STA & STATUS_CC_MASK) != STATUS_CCL) { /* if the sign is positive */
RD = RD + operand & R_MASK; /* then skip over the negative character string */
mem_set_byte (&prog); /* and reset the subprogram accessor */
}
}
break;
case 017: /* two-byte operations */
switch (operand) { /* dispatch on the second operation byte */
case 000: /* TE - terminate edit */
terminate = TRUE; /* terminate the subprogram */
break;
case 001: /* ENDF - end floating point insertion */
if (filling) /* if zero-filling is on */
mem_write_byte (&target, float_char); /* then insert the float character */
break;
case 002: /* SST1 - set significance to 1 */
filling = FALSE; /* set zero-filling off */
break;
case 003: /* SST0 - set significance to 0 */
filling = TRUE; /* set zero-filling on */
break;
case 004: /* MDWO - move digit with overpunch */
byte = mem_read_byte (&source); /* get the digit */
if (byte == ' ' && filling) /* if it's a blank and zero-filling is on */
byte = '0'; /* then fill */
else if (byte < '0' || byte > '9') { /* otherwise if the character is not a digit */
*trap = trap_Invalid_ASCII_Digit; /* then the it is not a valid number */
terminate = TRUE; /* so abandon the subprogram */
break;
}
if ((STA & STATUS_CC_MASK) == STATUS_CCL) /* if the number is negative */
byte = overpunch [Negative] [byte - '0']; /* then overpunch a minus sign */
else /* otherwise */
byte = overpunch [Positive] [byte - '0']; /* overpunch a plus sign */
mem_write_byte (&target, byte); /* write the overpunched character to the target */
break;
case 005: /* SFC - set fill character */
fill_char = mem_read_byte (&prog); /* set the fill character from the next byte */
break;
case 006: /* SFLC - set float character on sign */
byte = mem_read_byte (&prog); /* get the float characters */
if ((STA & STATUS_CC_MASK) == STATUS_CCL) /* if the number is negative */
float_char = LOWER_HALF (byte) + ' '; /* then use the negative float character */
else /* otherwise */
float_char = UPPER_HALF (byte) + ' '; /* use the positive float character */
break;
case 007: /* DFLC - define float character on sign */
float_char = mem_read_byte (&prog); /* set the positive float character */
if ((STA & STATUS_CC_MASK) == STATUS_CCL) /* if the number is negative */
float_char = mem_read_byte (&prog); /* then set the negative float character */
else /* otherwise */
mem_read_byte (&prog); /* skip over it */
break;
case 010: /* SETC - set loop count */
loop_count = mem_read_byte (&prog); /* get the new loop count */
break;
case 011: /* DBNZ - decrement loop count and branch */
byte = mem_read_byte (&prog); /* get the displacement */
loop_count = loop_count - 1 & D8_MASK; /* decrement the loop count modulo 256 */
if (loop_count > 0) { /* if the count is not zero */
RD = RD - 1 - SEXT8 (byte) & R_MASK; /* then subtract the signed displacement from the offset */
mem_set_byte (&prog); /* and reset the subprogram accessor */
}
break;
default: /* invalid two-word opcodes */
break; /* are ignored */
} /* end of two-word dispatcher */
break;
} /* all cases are handled */
if (terminate == FALSE /* if the subprogram is continuing */
&& cpu_interrupt_pending (status)) { /* and an interrupt is pending */
cpu_push (); /* then push the stack down twice */
cpu_push (); /* to save the subprogram execution state */
RA = D16_UMAX; /* set the resumption flag */
RB = (filling ? 0 : D16_SIGN) | loop_count; /* save the significance trigger and loop count */
RC = TO_WORD (fill_char, float_char); /* save the fill and float characters */
mem_update_byte (&target); /* update the last word written */
return FALSE; /* and return with an interrupt set up or a status error */
}
}
while (terminate == FALSE); /* continue subprogram execution until terminated */
mem_update_byte (&target); /* update the final target byte */
if (DPRINTING (cpu_dev, DEB_MOPND)) { /* if operand tracing is enabled */
mem_set_byte (&source); /* then reset the source and target accessors */
mem_set_byte (&target); /* to finalize the operand extents */
if (source.length > 0) /* if the source operand was used */
fprint_operands (&source, &target, *trap); /* then print both source and target operands */
else /* otherwise */
fprint_operand (&target, "target", &fmt_byte_operand); /* print just the target operand */
}
RA = 0; /* clear the resumption flag */
return TRUE; /* return with completion status */
}
/* Compare two padded byte strings.
This routine compares two byte strings with optional translation and sets the
condition code in the status register to indicate the result. Starting with
the first, successive pairs of bytes are compared; if the string lengths are
unequal, the shorter string is padded with blanks. The comparison stops when
the bytes are unequal or the end of the strings is reached. The condition
code is CCG if the source byte is greater than the target byte, CCL if the
source byte is less than the target byte, or CCE if the strings are equal or
both strings are of zero-length.
On entry, the RA and RC TOS registers contain the lengths of the source and
target strings, and the "source" and "target" parameters point at the byte
accessors for the source and target strings, respectively. The "table"
parameter either points at a 256-byte translation table or is NULL if no
translation is desired. If supplied, the table is used to translate the
source bytes and the target bytes if the target string is DB-relative. If
the target is PB-relative, no target translation is performed.
The routine is interruptible between bytes. If an interrupt is pending, the
routine will return FALSE with the TOS registers updated to reflect the
partial comparison; reentering the routine will complete the operation. If
the comparison runs to completion, the condition code will be set, and the
routine will return TRUE. In either case, the "status" parameter is set to
the SCP status returned by the interrupt test.
This routine implements the CMPS and CMPT instructions.
*/
static t_bool compare (BYTE_ACCESS *source, BYTE_ACCESS *target, BYTE_ACCESS *table, t_stat *status)
{
uint8 source_byte, target_byte;
*status = SCPE_OK; /* initialize the return status */
while (RA > 0 || RC > 0) { /* while there are bytes to compare */
if (RA == 0) /* if the source string is exhausted */
source_byte = ' '; /* then use a blank */
else /* otherwise */
source_byte = mem_read_byte (source); /* get the next source byte */
if (RC == 0) /* if the target string is exhausted */
target_byte = ' '; /* then use a blank */
else /* otherwise */
target_byte = mem_read_byte (target); /* get the next target byte */
if (table != NULL) { /* if the translation table was supplied */
source_byte = mem_lookup_byte (table, source_byte); /* then translate the source byte */
if (target->class == data || RC == 0) /* if the target is in the data segment or is exhausted */
target_byte = mem_lookup_byte (table, target_byte); /* then translate the target byte */
}
if (source_byte != target_byte) /* if the bytes do not compare */
break; /* then terminate the loop */
if (RA > 0) /* if source bytes remain */
RA = RA - 1; /* then count the byte (cannot underflow) */
if (RC > 0) /* if target bytes remain */
RC = RC - 1; /* then count the byte (cannot underflow) */
if (cpu_interrupt_pending (status)) /* if an interrupt is pending */
return FALSE; /* then return with an interrupt set up or a status error */
}
if (RA == 0 && RC == 0) /* if the counts expired together */
SET_CCE; /* then the strings are equal */
else if (source_byte > target_byte) /* otherwise if the source byte > the target byte */
SET_CCG; /* set the source string is greater */
else /* otherwise the source byte < the target byte */
SET_CCL; /* so the source string is less */
return TRUE; /* return comparison completion status */
}
/* Format and print byte string operands.
This routine formats and prints source and target byte string operands. The
source operand is always printed. The target operand is printed only if the
supplied trap condition is "trap_None"; otherwise, it is omitted. Tracing
must be enabled when the routine is called.
*/
static void fprint_operands (BYTE_ACCESS *source, BYTE_ACCESS *target, uint32 trap)
{
fprint_operand (source, "source", &fmt_byte_operand);
if (trap == trap_None)
fprint_operand (target, "target", &fmt_byte_operand);
return;
}
/* Format, translate, and print byte string operands.
This routine formats, optionally translates, and prints source and target
byte string operands. The source operand is always translated. The target
operand is translated only if it resides in the data segment. Tracing must
be enabled when the routine is called; the trace format is identical to that
produced by the fprint_operand routine.
*/
static void fprint_translated_operands (BYTE_ACCESS *source, BYTE_ACCESS *target, BYTE_ACCESS *table)
{
hp_debug (&cpu_dev, DEB_MOPND, BOV_FORMAT " source %d,\"%s\"\n",
TO_BANK (source->first_byte_address / 2),
TO_OFFSET (source->first_byte_address / 2),
source->first_byte_offset, source->length,
fmt_translated_byte_operand (source->first_byte_address,
source->length,
table->first_byte_address));
if (target->class == program)
fprint_operand (target, "target", &fmt_byte_operand);
else
hp_debug (&cpu_dev, DEB_MOPND, BOV_FORMAT " target %d,\"%s\"\n",
TO_BANK (target->first_byte_address / 2),
TO_OFFSET (target->first_byte_address / 2),
target->first_byte_offset, target->length,
fmt_translated_byte_operand (target->first_byte_address,
target->length,
table->first_byte_address));
return;
}
/* Format and print a memory operand.
The byte operand described by the byte accessor is sent to the debug trace
log file. Tracing must be enabled when the routine is called.
On entry, "op" points at the byte accessor describing the operand, "label"
points to text used to label the operand, and "operand_printer" points to the
routine used to print the operand. The latter may be "fprint_byte_operand"
to print operands consisting of 8-bit characters, or "fprint_bcd_operand" to
print extended-decimal (BCD) operands as character strings; the operand
length field of the trace line will indicate the number of characters or BCD
digits, respectively.
The operand is printed in this format:
>>CPU opnd: 00.045177 000467 source 15,"NOW IS THE TIME"
~~ ~~~~~~ ~~~~~~ ~~~~~~ ~~ ~~~~~~~~~~~~~~~~~
| | | | | |
| | | | | +-- operand value
| | | | +------------- operand length
| | | +------------------ operand label
| | +---------------------------- octal relative byte offset from base register
| +------------------------------------ octal operand address (effective address)
+----------------------------------------- octal operand bank (PBANK, DBANK, or SBANK)
*/
static void fprint_operand (BYTE_ACCESS *op, char *label, OP_PRINT operand_printer)
{
hp_debug (&cpu_dev, DEB_MOPND, BOV_FORMAT " %s %d,\"%s\"\n",
TO_BANK (op->first_byte_address / 2),
TO_OFFSET (op->first_byte_address / 2),
op->first_byte_offset, label, op->length,
operand_printer (op->first_byte_address, op->length));
return;
}