mirror of
https://github.com/simh/simh.git
synced 2026-03-22 09:02:00 +00:00
2490 lines
126 KiB
C
2490 lines
126 KiB
C
/* hp3000_cpu_eis.c: HP 30012A Extended Instruction Set simulator
|
|
|
|
Copyright (c) 2020, 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.
|
|
|
|
29-Sep-20 JDB Passes the EIS decimal arithmetic firmware diagnostic (D431)
|
|
14-Sep-20 JDB Passes the EIS floating point firmware diagnostic (D431)
|
|
05-Sep-20 JDB Created
|
|
|
|
References:
|
|
- HP 3000 Series II/III System Reference Manual
|
|
(30000-90020, July 1978)
|
|
- Machine Instruction Set Reference Manual
|
|
(30000-90022, June 1984)
|
|
- HP 3000 Series II System Microprogram Listing
|
|
(30000-90023, August 1976)
|
|
|
|
|
|
This module implements the HP 30012A Extended Instruction Set firmware
|
|
consisting of extended floating point and decimal arithmetic instructions.
|
|
The set contains these instructions:
|
|
|
|
Name Description
|
|
---- ------------------------------
|
|
EADD Extended precision add
|
|
ESUB Extended precision subtract
|
|
EMPY Extended precision multiply
|
|
EDIV Extended precision divide
|
|
ENEG Extended precision negate
|
|
ECMP Extended precision compare
|
|
|
|
ADDD Add decimal
|
|
CMPD Compare decimal
|
|
CVAD Convert ASCII to decimal
|
|
CVBD Convert binary to decimal
|
|
CVDA Convert decimal to ASCII
|
|
CVDB Convert decimal to binary
|
|
DMPY Double logical multiply
|
|
MPYD Multiply decimal
|
|
NSLD Normalizing shift left decimal
|
|
SLD Shift left decimal
|
|
SRD Shift right decimal
|
|
SUBD Subtract decimal
|
|
|
|
The floating-point instructions occupy the the firmware extension range
|
|
020400-020417. For each instruction, addresses of the operand(s) and result
|
|
as DB+ relative word offsets reside on the stack. They are encoded as
|
|
follows:
|
|
|
|
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 0 0 | 1 0 0 0 | EADD
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Add the four-word floating-point number addressed by RA to the four-word
|
|
floating-point number addressed by RB and store the result in the four-word
|
|
target area addressed by RC.
|
|
|
|
|
|
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 0 0 | 1 0 0 1 | ESUB
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Subtract the four-word floating-point number addressed by RA from the
|
|
four-word floating-point number addressed by RB and store the result in the
|
|
four-word target area addressed by RC.
|
|
|
|
|
|
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 0 0 | 1 0 1 0 | EMPY
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Multiply the four-word floating-point number addressed by RA to the four-word
|
|
floating-point number addressed by RB and store the result in the four-word
|
|
target area addressed by RC.
|
|
|
|
|
|
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 0 0 | 1 0 1 1 | EDIV
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Divide the four-word floating-point number addressed by RA into the four-word
|
|
floating-point number addressed by RB and store the result in the four-word
|
|
target area addressed by RC.
|
|
|
|
|
|
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 0 0 | 1 1 0 0 | ENEG
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Negate in place the four-word floating-point number addressed by RA.
|
|
|
|
|
|
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 0 0 | 1 1 0 1 | ECMP
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Compare the four-word floating-point number addressed by RB to the four-word
|
|
floating-point number addressed by RA and set the condition code
|
|
appropriately.
|
|
|
|
|
|
|
|
The decimal arithmetic instructions occupy the the firmware extension range
|
|
020600-020777. For most instructions, addresses of the source and target
|
|
operands as DB+ relative byte (for packed decimal) or word (for binary)
|
|
offsets reside on the stack. They are encoded as follows:
|
|
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 0 0 1 0 | 0 0 0 1 | 1 0 0 0 | 0 0 0 1 | DMPY
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Multiply the double-word unsigned integer contained in RB and RA to the
|
|
double-word unsigned integer contained in RD and RC and leaves the four-word
|
|
unsigned integer product on the stack.
|
|
|
|
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 0 0 1 0 | 0 0 0 1 | 1 0 0 | S | 0 0 1 0 | CVAD
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
S-Decrement:
|
|
|
|
0 = delete 2 words
|
|
1 = delete 4 words
|
|
|
|
Convert the external decimal number designated by RA (count) and RB (address)
|
|
to a packed decimal number designated by RC (count) and RD (address).
|
|
|
|
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 0 0 1 0 | 0 0 0 1 | 1 | sign | S | 0 0 1 1 | CVDA
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
S-Decrement:
|
|
|
|
0 = delete 1 word
|
|
1 = delete 3 words
|
|
|
|
Sign Control:
|
|
|
|
00 = target sign is source sign
|
|
01 = target sign is negative if source negative else unsigned
|
|
10 = target sign is unsigned
|
|
11 = target sign is unsigned
|
|
|
|
Convert the packed decimal number designated by RA (address) to an external
|
|
decimal number designated by RB (count) and RC (address). The number of
|
|
digits converted is also designated by RB.
|
|
|
|
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 0 0 1 0 | 0 0 0 1 | 1 0 0 | S | 0 1 0 0 | CVBD
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
S-Decrement:
|
|
|
|
0 = delete 2 words
|
|
1 = delete 4 words
|
|
|
|
Convert the binary number designated by RA (count) and RB (address) to a
|
|
packed decimal number designated by RC (count) and RD (address).
|
|
|
|
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 0 0 1 0 | 0 0 0 1 | 1 0 0 | S | 0 1 0 1 | CVDB
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
S-Decrement:
|
|
|
|
0 = delete 2 words
|
|
1 = delete 3 words
|
|
|
|
Convert the packed decimal number designated by RA (count) and RB (address)
|
|
to a binary number designated by RC (address). The number of target words is
|
|
determined by the source digit count.
|
|
|
|
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 0 0 1 0 | 0 0 0 1 | 1 0 | sdec | 0 1 1 0 | SLD
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
S-Decrement:
|
|
|
|
00 = delete no words
|
|
01 = delete 2 words
|
|
10 = delete 4 words
|
|
|
|
Shift the packed decimal number designated by RA (count) and RB (address)
|
|
left by the number of digits specified by the X register and store the result
|
|
in a packed decimal number designated by RC (count) and RD (address). Digits
|
|
shifted off the end of the number are lost.
|
|
|
|
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 0 0 1 0 | 0 0 0 1 | 1 0 | sdec | 0 1 1 1 | NSLD
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
S-Decrement:
|
|
|
|
00 = delete no words
|
|
01 = delete 2 words
|
|
10 = delete 4 words
|
|
|
|
Shift the packed decimal number designated by RA (count) and RB (address)
|
|
left by the number of digits specified by the X register and store the result
|
|
in a packed decimal number designated by RC (count) and RD (address). If
|
|
shifting would lose significant digits off the end of the number, the shift
|
|
count is reduced to leave the most-significant digit at the start of the
|
|
packed number.
|
|
|
|
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 0 0 1 0 | 0 0 0 1 | 1 0 | sdec | 1 0 0 0 | SRD
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
S-Decrement:
|
|
|
|
00 = delete no words
|
|
01 = delete 2 words
|
|
10 = delete 4 words
|
|
|
|
Shift the packed decimal number designated by RA (count) and RB (address)
|
|
right by the number of digits specified by the X register and store the
|
|
result in a packed decimal number designated by RC (count) and RD (address).
|
|
Digits shifted off the end of the number are lost.
|
|
|
|
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 0 0 1 0 | 0 0 0 1 | 1 0 | sdec | 1 0 0 1 | ADDD
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
S-Decrement:
|
|
|
|
00 = delete no words
|
|
01 = delete 2 words
|
|
10 = delete 4 words
|
|
|
|
Add the packed decimal number designated by RA (count) and RB (address) to
|
|
the packed decimal number designated by RC (count) and RD (address) and store
|
|
the result in the target area addressed by RD.
|
|
|
|
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 0 0 1 0 | 0 0 0 1 | 1 0 | sdec | 1 0 1 0 | CMPD
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
S-Decrement:
|
|
|
|
00 = delete no words
|
|
01 = delete 2 words
|
|
10 = delete 4 words
|
|
|
|
Compare the packed decimal number designated by RA (count) and RB (address)
|
|
to the packed decimal number designated by RC (count) and RD (address) and
|
|
set the condition code appropriately.
|
|
|
|
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 0 0 1 0 | 0 0 0 1 | 1 0 | sdec | 1 0 1 1 | SUBD
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
S-Decrement:
|
|
|
|
00 = delete no words
|
|
01 = delete 2 words
|
|
10 = delete 4 words
|
|
|
|
Subtract the packed decimal number designated by RA (count) and RB (address)
|
|
from the packed decimal number designated by RC (count) and RD (address) and
|
|
store the result in the target area addressed by RD.
|
|
|
|
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 0 0 1 0 | 0 0 0 1 | 1 0 | sdec | 1 1 0 0 | MPYD
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
S-Decrement:
|
|
|
|
00 = delete no words
|
|
01 = delete 2 words
|
|
10 = delete 4 words
|
|
|
|
Multiply the packed decimal number designated by RA (count) and RB (address)
|
|
by the packed decimal number designated by RC (count) and RD (address) and
|
|
store the result in the target area addressed by RD.
|
|
|
|
|
|
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
|
|
the starting digits into 16-bit words:
|
|
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 addr/size
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| unused | digit | ... | ... | even/even
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| digit | digit | ... | ... | even/odd
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| ... | ... | unused | digit | odd/even
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| ... | digit | digit | odd/odd
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Numbers always end with the sign in the lower half of the byte, so there are
|
|
two possible cases of packing the ending digits into 16-bit words, depending
|
|
on the total number of digits:
|
|
|
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| digit | sign | ... |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| ... | ... | 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.
|
|
|
|
|
|
Eight user traps may be taken by these instructions if the T bit is on in the
|
|
status register:
|
|
|
|
Parameter Description
|
|
--------- ------------------------------------------------
|
|
000010 Extended Precision Floating Point Overflow
|
|
000011 Extended Precision Floating Point Underflow
|
|
000012 Extended Precision Floating Point Divide by Zero
|
|
000013 Decimal Overflow
|
|
000014 Invalid ASCII Digit
|
|
000015 Invalid Decimal Digit
|
|
000016 Invalid Source Word Count
|
|
000017 Invalid Decimal Length
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Each instruction executor begins with a comment listing the instruction
|
|
mnemonic and, following in parentheses, the condition code setting, or
|
|
"none" if the condition code is not altered, and a list of any traps that
|
|
might be generated. The condition code and trap mnemonics are those used
|
|
in the Machine Instruction Set manual.
|
|
*/
|
|
|
|
|
|
|
|
#include "hp3000_defs.h"
|
|
#include "hp3000_cpu.h"
|
|
#include "hp3000_cpu_fp.h"
|
|
#include "hp3000_mem.h"
|
|
|
|
|
|
|
|
/* Program constants */
|
|
|
|
#define MAX_DIGITS 28 /* maximum number of decimal digits accepted */
|
|
#define MAX_WORDS 6 /* maximum number of words needed for conversion */
|
|
|
|
#define MAX_COUNT_MASK 000037u /* maximum shift count mask */
|
|
|
|
#define NOT_SET MAX_DIGITS /* indicator that an index is not set */
|
|
|
|
|
|
/* Packed decimal constants */
|
|
|
|
#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 */
|
|
|
|
|
|
/* External decimal constants */
|
|
|
|
typedef enum { /* shift mode, corresponds to EIS subopcode */
|
|
Left = 006, /* SLD (020606) */
|
|
Normalizing = 007, /* NSLD (020607) */
|
|
Right = 010 /* SRD (020610) */
|
|
} SHIFT_MODE;
|
|
|
|
typedef enum { /* numeric sign values */
|
|
Negative,
|
|
Unsigned,
|
|
Positive
|
|
} NUMERIC_SIGN;
|
|
|
|
static HP_BYTE sign_digit [3] = /* sign digit, indexed by NUMERIC_SIGN */
|
|
{ SIGN_MINUS, /* Negative */
|
|
SIGN_UNSIGNED, /* Unsigned */
|
|
SIGN_PLUS }; /* Positive */
|
|
|
|
static HP_BYTE overpunch [3] [10] = { /* sign overpunches, indexed by NUMERIC_SIGN and value */
|
|
{ '}', '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 */
|
|
};
|
|
|
|
|
|
/* Digit accessors.
|
|
|
|
Decimal numbers are stored in memory as byte-addressable arrays. Two number
|
|
formats are supported. Packed decimal numbers contain binary-coded-decimal
|
|
(BCD) digits stored two per byte. External decimal numbers contain ASCII
|
|
digits with an optional overpunched sign in the last digit position; they are
|
|
stored one per byte.
|
|
|
|
Digit accessors extend the byte accessor structure to contain additional
|
|
information useful in manipulating decimal numbers. A digit accessor is
|
|
initialized in the same manner as a byte accessor, with an additional
|
|
parameter to specify the desired numeric format. Routines are provided to
|
|
read and write decimal numbers via accessors. Unlike byte accessors, digit
|
|
accessors contain a buffer large enough to hold the maximum number of digits
|
|
allowed in a decimal number. Every decimal number is right-justified in the
|
|
buffer with leading zeros as necessary. The accessor maintains a count of
|
|
the actual number of digits specified, so that reading and writing of shorter
|
|
numbers is handled transparently.
|
|
*/
|
|
|
|
typedef enum { /* decimal number format */
|
|
Packed, /* packed decimal */
|
|
External /* external decimal */
|
|
} DECIMAL_FORMAT;
|
|
|
|
typedef struct { /* decimal number accessor */
|
|
BYTE_ACCESS bac; /* the underlying byte accessor */
|
|
DECIMAL_FORMAT format; /* the format of the decimal number */
|
|
HP_WORD byte_offset; /* the byte offset for the byte accessor routines */
|
|
uint32 starting_index; /* the index of the first digit in the number */
|
|
uint32 significant_index; /* the index of the first significant digit in the number */
|
|
uint32 digit_count; /* the count of digits in the number */
|
|
NUMERIC_SIGN sign; /* the sign of the number */
|
|
HP_BYTE digits [MAX_DIGITS]; /* the digits of the number */
|
|
} DIGIT_ACCESS;
|
|
|
|
|
|
/* EIS local utility routine declarations */
|
|
|
|
static void init_decimal (DIGIT_ACCESS *dap, DECIMAL_FORMAT format, ACCESS_CLASS class,
|
|
HP_WORD byte_offset, HP_WORD digit_count);
|
|
static uint32 read_decimal (DIGIT_ACCESS *dap);
|
|
static void write_decimal (DIGIT_ACCESS *dap, t_bool merge_digits);
|
|
|
|
static HP_WORD compare_decimal (DIGIT_ACCESS *first, DIGIT_ACCESS *second);
|
|
static uint32 add_decimal (DIGIT_ACCESS *augend, DIGIT_ACCESS *addend);
|
|
static uint32 subtract_decimal (DIGIT_ACCESS *minuend, DIGIT_ACCESS *subtrahend);
|
|
static uint32 multiply_decimal (DIGIT_ACCESS *multiplicand, DIGIT_ACCESS *multiplier);
|
|
static uint32 shift_decimal (DIGIT_ACCESS *target, DIGIT_ACCESS *source, SHIFT_MODE shift);
|
|
static uint32 convert_decimal (DIGIT_ACCESS *target, DIGIT_ACCESS *source);
|
|
|
|
static uint32 convert_binary (DIGIT_ACCESS *decimal, HP_WORD address, HP_WORD count);
|
|
|
|
static t_bool read_operands (DIGIT_ACCESS *first, DIGIT_ACCESS *second, uint32 *trap);
|
|
static void write_operand (DIGIT_ACCESS *operand);
|
|
static void set_cca_decimal (DIGIT_ACCESS *dap);
|
|
static void decrement_stack (uint32 trap, uint32 count_0, uint32 count_1, uint32 count_2);
|
|
static uint32 strip_overpunch (HP_BYTE *byte, NUMERIC_SIGN *sign);
|
|
|
|
static void fprint_decimal_operand (DIGIT_ACCESS *op, char *label);
|
|
|
|
|
|
|
|
/* EIS global routines */
|
|
|
|
|
|
/* Execute an EIS floating point operation.
|
|
|
|
This routine is called to execute the floating point 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 0 0 | 1 | EIS FP op | EIS FP
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
EIS FP Opcode:
|
|
|
|
0 = EADD (Extended precision add)
|
|
1 = ESUB (Extended precision subtract)
|
|
2 = EMPY (Extended precision multiply)
|
|
3 = EDIV (Extended precision divide)
|
|
4 = ENEG (Extended precision negate)
|
|
5 = ECMP (Extended precision compare)
|
|
6 = (undefined)
|
|
7 = (undefined)
|
|
|
|
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.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Each instruction lists a potential stack underflow trap. The underflow
|
|
is actually detected in the firmware dispatcher, which has a stack
|
|
preadjust of 4, before this routine is called. The "cpu_pop" calls
|
|
below always succeed, as the stack registers are fully populated on
|
|
entry.
|
|
|
|
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. The instruction executors follow the microcode in the placement of bounds
|
|
checks.
|
|
|
|
4. The ECMP instruction checks operand addresses against SM rather than SM +
|
|
SR. Because SR = 4 on entry, this effectively checks the entire
|
|
four-word operand (if the first word is below SM, then the fourth word is
|
|
below RB) before retrieving the individual operand words as needed for
|
|
the comparison. Therefore, no queue-downs are needed. Note that a
|
|
bounds violation can occur even if the first words differ and no
|
|
additional words are read. The diagnostic tests for this.
|
|
*/
|
|
|
|
t_stat cpu_eis_fp_op (void)
|
|
{
|
|
t_bool negative;
|
|
HP_WORD operand_x, operand_y, address_x, address_y;
|
|
FP_OPND operand_u, operand_v, operand_w;
|
|
uint32 opcode, index;
|
|
t_stat status = SCPE_OK;
|
|
|
|
opcode = FMEXSUBOP (CIR); /* get the opcode from the instruction */
|
|
|
|
switch (opcode) { /* dispatch the opcode */
|
|
|
|
case 010: /* EADD (CCA, O; STUN, STOV, ARITH) */
|
|
case 011: /* ESUB (CCA, O; STUN, STOV, ARITH) */
|
|
case 012: /* EMPY (CCA, O; STUN, STOV, ARITH) */
|
|
case 013: /* EDIV (CCA, O; STUN, STOV, ARITH) */
|
|
while (SR > 3) /* if more than three TOS register are valid */
|
|
cpu_queue_down (); /* then queue them down until exactly three are left */
|
|
|
|
operand_u.precision = fp_e; /* set the operand precision to double float */
|
|
operand_v.precision = fp_e; /* and the result precision to double float */
|
|
|
|
for (index = 0; index < 4; index++) { /* read both operands */
|
|
cpu_read_memory (data_checked, DB + RB + index & LA_MASK, &operand_u.words [index]);
|
|
cpu_read_memory (data_checked, DB + RA + index & LA_MASK, &operand_v.words [index]);
|
|
}
|
|
|
|
STA &= ~STATUS_O; /* clear the overflow flag */
|
|
|
|
operand_w = /* call the floating-point executor */
|
|
fp_exec ((FP_OPR) (opcode - 010 + fp_add), /* and convert the opcode */
|
|
operand_u, operand_v); /* to an arithmetic operation */
|
|
|
|
for (index = 0; index < 4; index++) /* write the result */
|
|
cpu_write_memory (data_checked, DB + RC + index & LA_MASK, operand_w.words [index]);
|
|
|
|
cpu_pop (); /* delete two words */
|
|
cpu_pop (); /* from the stack */
|
|
|
|
SET_CCA (operand_w.words [0], /* set the condition code */
|
|
operand_w.words [1] | operand_w.words [2] | operand_w.words [3]);
|
|
|
|
if (operand_w.trap != trap_None) { /* if an error occurred */
|
|
if (operand_w.trap == trap_Ext_Float_Overflow /* then if the result overflowed */
|
|
&& (STA & STATUS_CC_MASK) == STATUS_CCE) /* to a zero value */
|
|
SET_CCG; /* then set CCG */
|
|
|
|
if ((STA & STATUS_T) == 0) /* if user traps are disabled */
|
|
cpu_pop (); /* then delete the result address */
|
|
|
|
MICRO_ABORT (operand_w.trap); /* trap or set overflow */
|
|
}
|
|
|
|
else /* otherwise the operation completed normally */
|
|
cpu_pop (); /* so delete the result address */
|
|
break;
|
|
|
|
|
|
case 014: /* ENEG (CCA; STUN) */
|
|
cpu_read_memory (data_checked, DB + RA & LA_MASK, /* read the first word */
|
|
&operand_x); /* of the operand */
|
|
|
|
if (operand_x == 0) { /* if the first word is zero */
|
|
for (index = 1; index < 4; index++) { /* then check the other words */
|
|
cpu_read_memory (data_checked, /* to see if they are */
|
|
DB + RA + index & LA_MASK, /* all zero as well */
|
|
&operand_y);
|
|
|
|
if (operand_y != 0) /* if a non-zero word is seen */
|
|
break; /* then quit the check */
|
|
}
|
|
|
|
if (index == 4) { /* if the operand value is zero */
|
|
SET_CCE; /* then set CCE */
|
|
cpu_pop (); /* and delete the operand address */
|
|
break; /* and return without rewriting the value */
|
|
}
|
|
}
|
|
|
|
operand_x = operand_x ^ D16_SIGN; /* complement the sign bit of the non-zero operand */
|
|
|
|
cpu_write_memory (data_checked, DB + RA & LA_MASK, /* write the updated value back */
|
|
operand_x);
|
|
|
|
SET_CCA (operand_x, 1); /* set CCL or CCG from the sign bit */
|
|
cpu_pop (); /* and delete the operand address from the stack */
|
|
break;
|
|
|
|
|
|
case 015: /* ECMP (CCC; STUN) */
|
|
address_x = DB + RB & LA_MASK; /* form the data offset */
|
|
address_y = DB + RA & LA_MASK; /* for the two operands */
|
|
|
|
if (NPRV && (address_y < DL || address_y > SM)) /* if non-privileged and the operand is out of bounds */
|
|
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
|
|
|
|
cpu_pop (); /* delete two words */
|
|
cpu_pop (); /* from the stack */
|
|
|
|
if (NPRV && (address_x < DL || address_x > SM)) /* if non-privileged and the operand is out of bounds */
|
|
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
|
|
|
|
cpu_read_memory (data, address_x, &operand_x); /* read the first word */
|
|
cpu_read_memory (data, address_y, &operand_y); /* of each of the two operands */
|
|
|
|
negative = operand_x & D16_SIGN; /* TRUE if first operand is negative */
|
|
|
|
if ((operand_x ^ operand_y) & D16_SIGN) /* if the operand signs differ */
|
|
SET_CCA (operand_x, 1); /* then set the condition on the first words excluding CCE */
|
|
|
|
else if (operand_x != operand_y) /* otherwise if the first operand words differ */
|
|
if (negative) /* then if they're both negative */
|
|
SET_CCC (operand_y, 0, operand_x, 0); /* then reverse the comparison */
|
|
else /* otherwise */
|
|
SET_CCC (operand_x, 0, operand_y, 0); /* compare the integer operands */
|
|
|
|
else {
|
|
for (index = 1; index < 4; index++) { /* otherwise compare the remaining words */
|
|
cpu_read_memory (data, address_x + index & LA_MASK, &operand_x);
|
|
cpu_read_memory (data, address_y + index & LA_MASK, &operand_y);
|
|
|
|
if (operand_x != operand_y) /* once the words differ */
|
|
break; /* then the comparison is finished */
|
|
}
|
|
|
|
if (negative) /* if the operands are negative */
|
|
SET_CCC (0, operand_y, 0, operand_x); /* then reverse the logical comparison */
|
|
else /* otherwise */
|
|
SET_CCC (0, operand_x, 0, operand_y); /* compare the operand words logically */
|
|
}
|
|
break;
|
|
|
|
|
|
default:
|
|
status = STOP_UNIMPL; /* the firmware extension instruction is unimplemented */
|
|
}
|
|
|
|
return status; /* return the execution status */
|
|
}
|
|
|
|
|
|
/* Execute an EIS decimal arithmetic operation.
|
|
|
|
This routine is called to execute the decimal arithmetic 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 | 1 | options | decimal op | EIS Decimal
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Decimal Opcode:
|
|
|
|
00 - (undefined)
|
|
01 - DMPY (Double logical multiply)
|
|
02 - CVAD (Convert ASCII to decimal)
|
|
03 - CVDA (Convert decimal to ASCII)
|
|
04 - CVBD (Convert binary to decimal)
|
|
05 - CVDB (Convert decimal to binary)
|
|
06 - SLD (Shift left decimal)
|
|
07 - NSLD (Normalizing shift left decimal)
|
|
10 - SRD (Shift right decimal)
|
|
11 - ADDD (Add decimal)
|
|
12 - CMPD (Compare decimal)
|
|
13 - SUBD (Subtract decimal)
|
|
14 - MPYD (Multiply decimal)
|
|
15 - (undefined)
|
|
16 - (undefined)
|
|
17 - (undefined)
|
|
|
|
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.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Each instruction lists a potential stack underflow trap. The underflow
|
|
is actually detected in the firmware dispatcher, which has a stack
|
|
preadjust of 4, before this routine is called. The "cpu_pop" calls
|
|
below always succeed, as the stack registers are fully populated on
|
|
entry.
|
|
|
|
2. All of the decimal instruction except DMPY, SLD, NSLD, and SRD, test for
|
|
seven words of available stack space on entry (including the four words
|
|
already present in the TOS registers) and trap for a stack overflow if
|
|
they are not present. In microode, the test passes if SM + 7 <= Z. In
|
|
simulation, the test compares SM plus SR plus a per-opcode addition to Z,
|
|
which is equivalent. The test is skipped for the four instructions
|
|
above, although for SLD, NSLD, and SRD, the test is merely postponed
|
|
until after the X register is masked to the lower five bits in the opcode
|
|
handlers.
|
|
|
|
3. The CVDA, SLD, NSLD, and SRD instructions test for trap conditions before
|
|
setting the condition code for the operand. As "read_decimal" sets the
|
|
condition code, these instructions must save the status register on entry
|
|
and restore if a trap is taken.
|
|
|
|
4. If a bad decimal digit is present, the CVDA microcode converts and writes
|
|
ASCII characters until the digit is encountered, resulting in a partial
|
|
conversion before the trap is taken. The diagnostic tests for this.
|
|
|
|
5. For the CMPD instruction with both operands negative, XORing the
|
|
condition code with STATUS_CCL flips the result of the magnitude
|
|
comparison, i.e., CCL becomes CCG, and vice versa.
|
|
*/
|
|
|
|
t_stat cpu_eis_dec_op (void)
|
|
{
|
|
static const HP_WORD stack_check [] = { /* extra stack words needed, indexed by opcode */
|
|
0, 0, 3, 3, 3, 3, 0, 0,
|
|
0, 3, 3, 3, 3, 0, 0, 0
|
|
};
|
|
|
|
DIGIT_ACCESS source, target, left, right;
|
|
HP_WORD entry_status, comparison;
|
|
uint32 opcode;
|
|
t_uint64 product;
|
|
uint32 trap = trap_None;
|
|
t_stat status = SCPE_OK;
|
|
|
|
opcode = FMEXSUBOP (CIR); /* get the opcode from the instruction */
|
|
|
|
if (stack_check [opcode] > 0 /* if extra words on the stack are needed */
|
|
&& SM + SR + stack_check [opcode] > Z) /* and they aren't available */
|
|
MICRO_ABORT (trap_Stack_Overflow); /* then trap for a stack overflow */
|
|
|
|
else switch (opcode) { /* otherwise dispatch the opcode */
|
|
|
|
case 001: /* DMPY (CCA, C; STUN) */
|
|
product = (t_uint64) TO_DWORD (RB, RA) /* multiply the TOS double word */
|
|
* (t_uint64) TO_DWORD (RD, RC); /* by the NOS double word */
|
|
|
|
RD = HIGH_UPPER_WORD (product); /* separate */
|
|
RC = LOW_UPPER_WORD (product); /* then resulting */
|
|
RB = UPPER_WORD (product); /* quad word product */
|
|
RA = LOWER_WORD (product); /* and return in the TOS registers */
|
|
|
|
SET_CARRY (RD | RC); /* set carry if the upper double-word is significant */
|
|
SET_CCA (RD, RC | RB | RA); /* and set the condition code for the product */
|
|
break;
|
|
|
|
|
|
case 002: /* CVAD (CCA, O; STUN, STOV, ARITH) */
|
|
if (RA > MAX_DIGITS || RC > MAX_DIGITS) /* if the source or target digit counts are too large */
|
|
trap = trap_Invalid_Decimal_Length; /* then trap for a count overflow */
|
|
|
|
else if (RA > 0 && RC > 0) { /* otherwise if there are digits to process */
|
|
init_decimal (&source, External, data_checked, RB, RA); /* so set up digit accessors */
|
|
init_decimal (&target, Packed, data_checked, RD, RC); /* for the source and target decimals */
|
|
|
|
read_decimal (&source); /* read the source ASCII number, ignoring errors */
|
|
|
|
if (TRACING (cpu_dev, DEB_MOPND))
|
|
fprint_decimal_operand (&source, "source");
|
|
|
|
trap = convert_decimal (&target, &source); /* convert ASCII to packed decimal and check for errors */
|
|
|
|
write_decimal (&target, FALSE); /* write the decimal with a leading zero if required */
|
|
|
|
if (TRACING (cpu_dev, DEB_MOPND))
|
|
fprint_decimal_operand (&target, "result");
|
|
|
|
set_cca_decimal (&target); /* set CCA on the decimal result */
|
|
}
|
|
|
|
decrement_stack (trap, 2, 4, 0); /* decrement the stack and trap if indicated */
|
|
break;
|
|
|
|
|
|
case 003: /* CVDA (CCA, O; STUN, STOV, ARITH) */
|
|
while (SR > 3) /* if more than three TOS register are valid */
|
|
cpu_queue_down (); /* then queue them down until exactly three are left */
|
|
|
|
if (RB > MAX_DIGITS) /* if the target digit count is too large */
|
|
trap = trap_Invalid_Decimal_Length; /* then trap for a count overflow */
|
|
|
|
else if (RB > 0) { /* otherwise if there are digits to process */
|
|
entry_status = STA; /* then save the entry status for potential rollback */
|
|
|
|
init_decimal (&source, Packed, data_checked, RA, RB); /* set up digit accessors */
|
|
init_decimal (&target, External, data_checked, RC, RB); /* for the source and target decimals */
|
|
|
|
read_decimal (&source); /* read the source decimal number, ignoring errors */
|
|
|
|
if (TRACING (cpu_dev, DEB_MOPND))
|
|
fprint_decimal_operand (&source, "source");
|
|
|
|
trap = convert_decimal (&target, &source); /* convert packed decimal to ASCII and check for errors */
|
|
|
|
write_decimal (&target, TRUE); /* write the decimal number to memory */
|
|
|
|
if (TRACING (cpu_dev, DEB_MOPND))
|
|
fprint_decimal_operand (&target, "result");
|
|
|
|
if (trap == trap_None) /* if the conversion succeeded */
|
|
set_cca_decimal (&target); /* then set CCA on the decimal result */
|
|
else /* otherwise */
|
|
STA = entry_status; /* restore the original entry status */
|
|
}
|
|
|
|
decrement_stack (trap, 1, 3, 0); /* decrement the stack and trap if indicated */
|
|
break;
|
|
|
|
|
|
case 004: /* CVBD (CCA, O; STUN, STOV, ARITH) */
|
|
if (RA > MAX_WORDS) /* if the source word count is too large */
|
|
trap = trap_Invalid_Word_Count; /* then trap for a word count overflow */
|
|
|
|
else if (RC > MAX_DIGITS) /* otherwise if the target digit count is too large */
|
|
trap = trap_Invalid_Decimal_Length; /* then trap for a count overflow */
|
|
|
|
else if (RA > 0 && RC > 0) { /* otherwise if there are words to process */
|
|
init_decimal (&target, Packed, data_checked, RD, RC); /* then set up the target digit accessor */
|
|
|
|
trap = convert_binary (&target, RB, RA); /* convert the binary number (RB,RA) to decimal */
|
|
|
|
write_decimal (&target, TRUE); /* write the (possibly truncated) decimal number */
|
|
|
|
if (TRACING (cpu_dev, DEB_MOPND))
|
|
fprint_decimal_operand (&target, "result");
|
|
|
|
set_cca_decimal (&target); /* set CCA on the decimal result */
|
|
}
|
|
|
|
decrement_stack (trap, 2, 4, 0); /* decrement the stack and trap if indicated */
|
|
break;
|
|
|
|
|
|
case 005: /* CVDB (CCA, O; STUN, STOV, ARITH) */
|
|
while (SR > 3) /* if more than three TOS register are valid */
|
|
cpu_queue_down (); /* then queue them down until exactly three are left */
|
|
|
|
if (RA > MAX_DIGITS) /* if the source digit count is too large */
|
|
trap = trap_Invalid_Decimal_Length; /* then trap for a count overflow */
|
|
|
|
else if (RA > 0) { /* otherwise if there are digits to process */
|
|
init_decimal (&source, Packed, data_checked, RB, RA); /* then set up the source digit accessor */
|
|
|
|
trap = convert_binary (&source, RC, RA); /* convert the decimal number to binary (RC,RA) */
|
|
|
|
if (trap == trap_None) /* if the source decimal was valid */
|
|
set_cca_decimal (&source); /* then set CCA on the decimal result */
|
|
}
|
|
|
|
decrement_stack (trap, 2, 3, 0); /* decrement the stack and trap if indicated */
|
|
break;
|
|
|
|
|
|
case 006: /* SLD (CCA, C, O; STUN, STOV, ARITH) */
|
|
case 007: /* NSLD (CCA, C, O; STUN, STOV, ARITH) */
|
|
SET_CARRY (FALSE); /* clear carry in anticipation of a good result */
|
|
|
|
/* fall through into SRD case */
|
|
|
|
case 010: /* SRD (CCA, O; STUN, STOV, ARITH) */
|
|
entry_status = STA; /* save the entry status for potential rollback */
|
|
|
|
X = X & MAX_COUNT_MASK; /* mask the shift count to the lower five bits */
|
|
|
|
if (SM + SR + 3 > Z) /* if there aren't three free words on the stack */
|
|
MICRO_ABORT (trap_Stack_Overflow); /* then trap for a stack overflow */
|
|
|
|
else if (RA > MAX_DIGITS || RC > MAX_DIGITS) /* otherwise if the source or target counts are too large */
|
|
trap = trap_Invalid_Decimal_Length; /* then trap for a count overflow */
|
|
|
|
else if (RA > 0 && RC > 0) { /* otherwise if there are digits to process */
|
|
init_decimal (&source, Packed, data_checked, RB, RA); /* so set up digit accessors */
|
|
init_decimal (&target, Packed, data_checked, RD, RC); /* for the source and target decimals */
|
|
|
|
trap = read_decimal (&source); /* read the source decimal number */
|
|
|
|
if (TRACING (cpu_dev, DEB_MOPND))
|
|
fprint_decimal_operand (&source, "source");
|
|
|
|
if (trap == trap_None) { /* if the source number is valid */
|
|
trap = shift_decimal (&target, &source, /* then shift the number as indicated */
|
|
(SHIFT_MODE) opcode); /* by the instruction opcode */
|
|
|
|
if (trap == trap_None) { /* if the shift succeeded */
|
|
write_decimal (&target, TRUE); /* then write the result to memory */
|
|
|
|
if (TRACING (cpu_dev, DEB_MOPND))
|
|
fprint_decimal_operand (&target, "target");
|
|
|
|
set_cca_decimal (&target); /* set CCA on the decimal result */
|
|
}
|
|
|
|
else /* otherwise the shift failed */
|
|
STA = STA & STATUS_C | entry_status; /* so restore the status but keep the carry bit */
|
|
}
|
|
}
|
|
|
|
decrement_stack (trap, 0, 2, 4); /* decrement the stack and trap if indicated */
|
|
break;
|
|
|
|
|
|
case 011: /* ADDD (CCA, O; STUN, STOV, ARITH) */
|
|
if (read_operands (&source, &target, &trap)) { /* read the decimal operands; if they are valid */
|
|
trap = add_decimal (&target, &source); /* then add them */
|
|
write_operand (&target); /* and write the result back */
|
|
}
|
|
|
|
decrement_stack (trap, 0, 2, 4); /* decrement the stack and trap if indicated */
|
|
break;
|
|
|
|
|
|
case 012: /* CMPD (CCC, O; STUN, STOV, ARITH) */
|
|
if (read_operands (&right, &left, &trap)) { /* read the decimal operands; if they are valid */
|
|
comparison = compare_decimal (&left, &right); /* then compare the operand magnitudes */
|
|
|
|
if (left.sign == Negative /* if the operand signs are the same */
|
|
&& right.sign == Negative /* and negative */
|
|
&& comparison != STATUS_CCE) /* and the values aren't equal */
|
|
comparison = comparison ^ STATUS_CCL; /* then flip the magnitude comparison */
|
|
|
|
else if (right.sign != left.sign /* otherwise if the signs are different */
|
|
&& (right.significant_index != NOT_SET /* and the comparison */
|
|
|| left.significant_index != NOT_SET)) /* is not +0 = -0 */
|
|
if (right.sign == Negative) /* then if the right operand is negative */
|
|
comparison = STATUS_CCG; /* then the left is greater (positive) */
|
|
else /* otherwise the right is positive */
|
|
comparison = STATUS_CCL; /* so the left is smaller (negative) */
|
|
|
|
STA = STA & ~STATUS_CC_MASK | comparison; /* set the condition code */
|
|
}
|
|
|
|
decrement_stack (trap, 0, 2, 4); /* decrement the stack and trap if indicated */
|
|
break;
|
|
|
|
|
|
case 013: /* SUBD (CCA, O; STUN, STOV, ARITH) */
|
|
if (read_operands (&source, &target, &trap)) { /* read the decimal operands; if they are valid */
|
|
trap = subtract_decimal (&target, &source); /* then subtract them */
|
|
write_operand (&target); /* and write the result back */
|
|
}
|
|
|
|
decrement_stack (trap, 0, 2, 4); /* decrement the stack and trap if indicated */
|
|
break;
|
|
|
|
|
|
case 014: /* MPYD (CCA, O; STUN, STOV, ARITH) */
|
|
if (read_operands (&source, &target, &trap)) { /* read the decimal operands; if they are valid */
|
|
trap = multiply_decimal (&target, &source); /* then multiply them */
|
|
write_operand (&target); /* and write the result back */
|
|
}
|
|
|
|
decrement_stack (trap, 0, 2, 4); /* decrement the stack and trap if indicated */
|
|
break;
|
|
|
|
|
|
default:
|
|
status = STOP_UNIMPL; /* the firmware extension instruction is unimplemented */
|
|
}
|
|
|
|
return status; /* return the execution status */
|
|
}
|
|
|
|
|
|
|
|
/* EIS local utility routine declarations */
|
|
|
|
|
|
/* Initialize a decimal accessor.
|
|
|
|
The supplied decimal accessor structure is initialized for the numeric
|
|
format, starting relative byte offset pointer, digit count, and type of
|
|
memory access desired. If checked accesses are requested, then the starting
|
|
and ending word addresses will be bounds-checked, and a Bounds Violation will
|
|
occur if the address range exceeds that permitted by the access.
|
|
|
|
On return, the decimal accessor is ready for use with the other decimal
|
|
access routines.
|
|
|
|
Decimal accessors may be used to sequentially read or write packed or
|
|
external decimal numbers from or to memory. Packed numbers store two BCD
|
|
digits per byte, except for the last byte, which contains the LSD and the
|
|
sign, and the first byte, which contains a single digit if the count of
|
|
digits is even. External numbers store one ASCII digit per byte. The read
|
|
and write routines handle the digit packing and unpacking automatically.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The underlying byte access routines assume that if the initial range or
|
|
starting address is checked, succeeding accesses need not be checked, and
|
|
vice versa. The implication is that if the access class passed to this
|
|
routine is checked, the routine might abort with a Bounds Violation, but
|
|
succeeding read or write accesses will not, and if the class is
|
|
unchecked, this routine will not abort but a succeeding access might.
|
|
*/
|
|
|
|
static void init_decimal (DIGIT_ACCESS *dap, DECIMAL_FORMAT format, ACCESS_CLASS class,
|
|
HP_WORD byte_offset, HP_WORD digit_count)
|
|
{
|
|
int zero;
|
|
uint32 byte_count;
|
|
|
|
dap->format = format; /* set the decimal number format */
|
|
|
|
if (format == Packed) { /* if a packed number is designated */
|
|
byte_count = digit_count / 2 + 1; /* then convert the digit count to a byte count */
|
|
dap->sign = Positive; /* set the sign of the zero value */
|
|
zero = 0; /* and initialize with a numeric zero */
|
|
}
|
|
|
|
else { /* otherwise an external number is designated */
|
|
byte_count = digit_count; /* so the byte count is the digit count */
|
|
dap->sign = Unsigned; /* set the sign of the zero value */
|
|
zero = (int) '0'; /* and initialize with a character zero */
|
|
}
|
|
|
|
dap->byte_offset = byte_offset; /* save the offset to the first byte to access */
|
|
|
|
mem_init_byte (&dap->bac, class, &dap->byte_offset, byte_count); /* set up a byte accessor for the digits */
|
|
|
|
dap->significant_index = NOT_SET; /* initialize the significant digit index */
|
|
dap->starting_index = MAX_DIGITS - digit_count; /* and save the index of the first valid digit */
|
|
dap->digit_count = digit_count; /* and the number of valid digits */
|
|
|
|
memset (dap->digits, zero, sizeof dap->digits); /* store zeros in the full digit array */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Read a decimal number from memory.
|
|
|
|
The decimal number indicated by the supplied decimal accessor is read from
|
|
memory into the accessor's digit array and checked for correctness. The
|
|
routine returns trap_Invalid_Decimal_Digit or trap_Invalid_ASCII_Digit if
|
|
invalid digit is encountered, depending on the accessor format. If all of
|
|
the digits are legal representations, trap_None is returned after the index
|
|
of the first significant digit in the number is determined. The index is
|
|
that of the first non-zero digit after the leading zeros. If the decimal
|
|
number is zero, the significant digit index will equal the maximum digit
|
|
count, i.e., will point just beyond the last digit.
|
|
|
|
Special handling is needed for the first byte of a packed decimal number if
|
|
the digit count is even. In this case, only the right-hand digit within the
|
|
byte is part of the number. Otherwise, all bytes contain two digits except
|
|
for the last, which contains a digit and the sign.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. For packed decimal numbers only, the microcode sets the condition code to
|
|
CCL or CCG, depending on the sign of the number, before checking digits
|
|
for validity. Consequently, if an invalid digit trap is taken, the
|
|
condition code has already been set. We follow that behavior here.
|
|
*/
|
|
|
|
static uint32 read_decimal (DIGIT_ACCESS *dap)
|
|
{
|
|
HP_BYTE byte, upper_digit, lower_digit;
|
|
uint32 byte_count, index;
|
|
uint32 digit_trap = trap_None, sign_trap = trap_None;
|
|
|
|
index = dap->starting_index; /* get the index of the first digit to store */
|
|
byte_count = dap->bac.length; /* and the number of bytes to read */
|
|
|
|
dap->significant_index = NOT_SET; /* initialize the index of the first significant digit */
|
|
|
|
if (dap->format == Packed) { /* if this is a packed decimal value */
|
|
if ((dap->digit_count & 1) == 0) { /* then if the digit count is even */
|
|
byte = mem_read_byte (&dap->bac); /* then read the byte containing the single MSD */
|
|
byte_count--; /* and drop the remaining count */
|
|
|
|
lower_digit = LOWER_HALF (byte); /* get the right digit from the byte */
|
|
|
|
if (lower_digit > 9) /* if the digit is invalid */
|
|
digit_trap = trap_Invalid_Decimal_Digit; /* then set up the trap */
|
|
else if (lower_digit > 0) /* otherwise if the digit is non-zero */
|
|
dap->significant_index = index; /* then it is the first significant digit */
|
|
|
|
dap->digits [index++] = lower_digit; /* save it as the first digit */
|
|
}
|
|
|
|
while (byte_count > 0) { /* for the remaining bytes */
|
|
byte = mem_read_byte (&dap->bac); /* read the next byte from memory */
|
|
byte_count--; /* and drop the remaining count */
|
|
|
|
upper_digit = UPPER_HALF (byte); /* split the byte */
|
|
lower_digit = LOWER_HALF (byte); /* into left and right digits */
|
|
|
|
if (upper_digit > 9) /* if the digit is invalid */
|
|
digit_trap = trap_Invalid_Decimal_Digit; /* then set up the trap */
|
|
if (upper_digit > 0 && dap->significant_index == NOT_SET) /* otherwise if it's non-zero and not yet indexed */
|
|
dap->significant_index = index; /* then save the first significant digit index */
|
|
|
|
dap->digits [index++] = upper_digit; /* save the left-hand digit */
|
|
|
|
if (byte_count == 0) /* if this is the last byte */
|
|
if (lower_digit == SIGN_MINUS) { /* then if a minus sign is present */
|
|
SET_CCL; /* then preset the condition code to "less than" */
|
|
dap->sign = Negative; /* and set the decimal sign negative */
|
|
}
|
|
|
|
else { /* otherwise */
|
|
SET_CCG; /* preset the condition code to "greater than" */
|
|
|
|
if (lower_digit == SIGN_UNSIGNED) /* if an unsigned indicator is present */
|
|
dap->sign = Unsigned; /* then the decimal is unsigned */
|
|
else /* otherwise a plus sign is assumed */
|
|
dap->sign = Positive; /* and the decimal is positive */
|
|
}
|
|
|
|
else { /* otherwise this is an intermediate byte */
|
|
if (lower_digit > 9) /* so if the digit is invalid */
|
|
digit_trap = trap_Invalid_Decimal_Digit; /* then set up the trap */
|
|
|
|
else if (lower_digit > 0 /* otherwise if it's non-zero */
|
|
&& dap->significant_index == NOT_SET) /* and not yet indexed */
|
|
dap->significant_index = index; /* then save the first significant digit index */
|
|
|
|
dap->digits [index++] = lower_digit; /* save the right-hand byte */
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (dap->format == External) /* otherwise it's an external decimal */
|
|
while (byte_count > 0) { /* for the remaining bytes */
|
|
byte = mem_read_byte (&dap->bac); /* read the byte from memory */
|
|
byte_count--; /* and drop the remaining count */
|
|
|
|
if (byte_count == 0 && byte != ' ') /* if this is a non-blank overpunched sign */
|
|
sign_trap = strip_overpunch (&byte, &dap->sign); /* then strip the overpunch and set the sign */
|
|
|
|
else if (byte < '0' && byte != ' ' || byte > '9') /* otherwise if the digit is not valid */
|
|
digit_trap = trap_Invalid_ASCII_Digit; /* then trap for the error */
|
|
|
|
if (byte > '0' && dap->significant_index == NOT_SET) /* if it's non-zero and not yet indexed */
|
|
dap->significant_index = index; /* the save the first significant digit index */
|
|
|
|
dap->digits [index++] = byte; /* save the byte */
|
|
}
|
|
|
|
if (digit_trap != trap_None) /* if a bad digit was seen */
|
|
return digit_trap; /* then return the trap code */
|
|
else /* otherwise */
|
|
return sign_trap; /* return success or a bad sign trap code */
|
|
}
|
|
|
|
|
|
/* Write a decimal number to memory.
|
|
|
|
The decimal number indicated by the supplied decimal accessor is written to
|
|
memory. Special handling is needed for the first byte of a packed decimal
|
|
number if the digit count is even. In this case, only the right-hand digit
|
|
within the byte is part of the number. Whether the most-significant digit is
|
|
merged with the left-hand four bits of the byte or whether those bits are
|
|
zeroed is determined by the supplied "merge_digits" parameter. The parameter
|
|
is ignored when writing numbers in external decimal format.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The CVAD instruction is the only one that does NOT merge the MSD into the
|
|
leading byte. It is also the only instruction that can write a negative
|
|
zero number.
|
|
|
|
2. Error aborts (e.g., from the CVDA instruction) may request writing fewer
|
|
digits than are indicated by the starting index by reducing the byte
|
|
accessor length. Therefore, we use the latter instead of the former to
|
|
determine the number of bytes to write.
|
|
*/
|
|
|
|
static void write_decimal (DIGIT_ACCESS *dap, t_bool merge_digits)
|
|
{
|
|
HP_BYTE byte, upper_digit, lower_digit;
|
|
uint32 byte_count, index;
|
|
|
|
index = dap->starting_index; /* get the index of the first digit to store */
|
|
byte_count = dap->bac.length; /* and the number of bytes to read */
|
|
|
|
if (byte_count == 0) /* if there are no bytes to write */
|
|
return; /* then quit now */
|
|
|
|
if (dap->format == Packed) { /* if this is a packed decimal value */
|
|
if (dap->significant_index == NOT_SET && merge_digits) /* then if the value is zero and merged */
|
|
dap->sign = Positive; /* then ensure that we write a positive zero */
|
|
|
|
if ((dap->digit_count & 1) == 0) { /* if the digit count is even */
|
|
if (merge_digits) { /* then if the MSD must be merged */
|
|
byte = mem_read_byte (&dap->bac); /* then get the current byte value */
|
|
|
|
mem_reset_byte (&dap->bac); /* reset the byte accessor back to its original location */
|
|
|
|
byte = TO_BYTE (UPPER_HALF (byte), /* merge the MSD with the existing value */
|
|
dap->digits [index++]); /* in the byte */
|
|
}
|
|
|
|
else /* otherwise merging is not required */
|
|
byte = dap->digits [index++]; /* so set the upper half of the byte to zero */
|
|
|
|
mem_write_byte (&dap->bac, byte); /* write the initial byte to memory */
|
|
byte_count--; /* and drop the remaining count */
|
|
}
|
|
|
|
while (byte_count > 0) { /* for the remaining bytes */
|
|
upper_digit = dap->digits [index++]; /* get the left-hand digit */
|
|
|
|
if (byte_count > 1) /* if this is an intermediate byte */
|
|
lower_digit = dap->digits [index++]; /* then get the right-hand digit */
|
|
else /* otherwise it's the last byte */
|
|
lower_digit = sign_digit [dap->sign]; /* so get the sign instead */
|
|
|
|
byte = TO_BYTE (upper_digit, lower_digit); /* merge the digits in the byte */
|
|
|
|
mem_write_byte (&dap->bac, byte); /* write the byte to memory */
|
|
byte_count--; /* and drop the remaining count */
|
|
}
|
|
}
|
|
|
|
else if (dap->format == External) /* otherwise it's an external decimal */
|
|
do { /* so for each digit */
|
|
byte = dap->digits [index++]; /* get the next digit */
|
|
|
|
if (byte_count == 1 /* if this is the last byte */
|
|
&& byte >= '0' && byte <= '9') /* and the digit is valid */
|
|
byte = overpunch [dap->sign] [byte - '0']; /* then get the overpunched sign */
|
|
|
|
mem_write_byte (&dap->bac, byte); /* write the byte to memory */
|
|
}
|
|
while (--byte_count > 0); /* continue until all bytes are written */
|
|
|
|
mem_update_byte (&dap->bac); /* write any partial final word if present */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Compare two decimal numbers.
|
|
|
|
This routine compares the magnitudes of two decimal numbers and returns a
|
|
condition code to indicate the result (cc = first < | = | > second). The
|
|
signs of the numbers are not considered.
|
|
|
|
If one number has more significant digits than the other, then it is larger
|
|
by definition, and vice versa. If both numbers have the same number of
|
|
significant digits, then the corresponding digits of each number are compared
|
|
until they differ or until all digits have been examined. If both numbers
|
|
have no significant digits, then both are zero values and so are equal.
|
|
|
|
The returned status code represents the comparison of the first operand to
|
|
the second operand.
|
|
*/
|
|
|
|
static HP_WORD compare_decimal (DIGIT_ACCESS *first, DIGIT_ACCESS *second)
|
|
{
|
|
uint32 index;
|
|
|
|
if (first->significant_index < second->significant_index) /* if the first has more significant digits */
|
|
return STATUS_CCG; /* than the second, then it is greater in value */
|
|
|
|
else if (first->significant_index > second->significant_index) /* otherwise if the first has fewer significant */
|
|
return STATUS_CCL; /* digits, then it is smaller in value */
|
|
|
|
else { /* otherwise they have the same significance */
|
|
index = first->significant_index; /* so they must be examined digit by digit */
|
|
|
|
while (index < MAX_DIGITS) { /* while digits remain */
|
|
if (first->digits [index] > second->digits [index]) /* if the first digit is greater */
|
|
return STATUS_CCG; /* then the first operand is greater */
|
|
|
|
else if (first->digits [index] < second->digits [index]) /* otherwise if the digit is smaller */
|
|
return STATUS_CCL; /* then the first operand is smaller */
|
|
|
|
index++; /* otherwise they are equal, so try the next pair */
|
|
}
|
|
|
|
return STATUS_CCE; /* all digits are equal, so the operands are equal */
|
|
}
|
|
}
|
|
|
|
|
|
/* Add two decimal numbers.
|
|
|
|
The sum of the two decimal operands is returned in the accessor of the first
|
|
operand (augend = augend + addend). If one operand is zero and the other is
|
|
not, then the non-zero operand is returned as the sum. Otherwise, the
|
|
operands are added digit-by-digit.
|
|
|
|
To ensure that the sum does not underflow (i.e., that the magnitude of the
|
|
sum is always greater than zero), the operands are compared. If the operand
|
|
signs are the then same, the result is the sum of the magnitudes. If the
|
|
signs are different, then the sum is the smaller value subtracted from the
|
|
larger value, and the result adopts the sign of the larger value. If the
|
|
magnitudes are equal and the signs are opposite, then the result is zero.
|
|
|
|
Addition or subtraction is performed digit-by-digit, with carries between
|
|
digits, until the all of the digits of the larger operand have been
|
|
processed.
|
|
|
|
A Decimal Overflow trap is returned if the result does not fit in the augend
|
|
operand.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. If the augend is zero, the addend digits must be copied to the augend
|
|
digit array. However, the ADDD and SUBD instructions allow the two
|
|
operands to overlap. Therefore, the "memmove" function must be used to
|
|
copy the digits instead of the "memcpy" function.
|
|
|
|
2. A borrow out of the last digit cannot occur because the operands are
|
|
reordered if necessary to ensure that the difference is always positive.
|
|
*/
|
|
|
|
static uint32 add_decimal (DIGIT_ACCESS *augend, DIGIT_ACCESS *addend)
|
|
{
|
|
DIGIT_ACCESS *op1, *op2;
|
|
NUMERIC_SIGN operator;
|
|
HP_WORD comparison;
|
|
HP_BYTE carry;
|
|
int32 result;
|
|
uint32 index, last;
|
|
|
|
if (addend->significant_index == NOT_SET) /* if the addend is zero */
|
|
return trap_None; /* then the augend value is the sum */
|
|
|
|
else if (augend->significant_index == NOT_SET) { /* otherwise if the augend is zero */
|
|
memmove (augend->digits, addend->digits, /* then copy the addend value */
|
|
sizeof augend->digits); /* into the augend digit array */
|
|
|
|
augend->sign = addend->sign; /* copy the addend sign */
|
|
augend->significant_index = addend->significant_index; /* and index of significant digits */
|
|
|
|
if (addend->significant_index < augend->starting_index) /* if the augend does not have enough room */
|
|
return trap_Decimal_Overflow; /* then an overflow occurs */
|
|
else /* otherwise */
|
|
return trap_None; /* the addend value is the sum */
|
|
}
|
|
|
|
else { /* otherwise neither value is zero */
|
|
comparison = compare_decimal (augend, addend); /* so compare the operand magnitudes */
|
|
|
|
if (comparison == STATUS_CCL) { /* if the augend is smaller than the addend */
|
|
op1 = addend; /* then swap the order */
|
|
op2 = augend; /* for the sum or difference */
|
|
}
|
|
|
|
else { /* otherwise the augend is larger than the addend */
|
|
op1 = augend; /* so keep the supplied order */
|
|
op2 = addend; /* for the sum or difference */
|
|
}
|
|
|
|
if (augend->sign == addend->sign) /* if the operand signs are the same */
|
|
operator = Positive; /* then sum the magnitudes */
|
|
|
|
else if (comparison == STATUS_CCE) { /* otherwise if the values are equal with different signs */
|
|
memset (augend->digits, 0, /* then the sum is zero */
|
|
sizeof augend->digits);
|
|
|
|
augend->sign = Positive; /* the result is positive */
|
|
augend->significant_index = NOT_SET; /* with no significant digits */
|
|
return trap_None; /* and no error */
|
|
}
|
|
|
|
else { /* otherwise the sum is determined */
|
|
operator = Negative; /* by subtracting the magnitudes */
|
|
augend->sign = op1->sign; /* and assuming the sign of the larger operand */
|
|
}
|
|
}
|
|
|
|
last = op1->significant_index; /* stop after processing the MSD of the larger value */
|
|
|
|
augend->significant_index = NOT_SET; /* reset the result significant digit index */
|
|
|
|
carry = 0; /* start with no carry */
|
|
index = MAX_DIGITS; /* and with the LSD and work forward */
|
|
|
|
do { /* sum the digits in sequence */
|
|
index--; /* move the index to the next digit */
|
|
|
|
if (operator == Positive) /* if we're summing */
|
|
result = op1->digits [index] + op2->digits [index] + carry; /* then add the digits and carry */
|
|
else /* otherwise */
|
|
result = op1->digits [index] - op2->digits [index] - carry; /* subtract the digits and borrow */
|
|
|
|
if (result > 9) { /* if a carry occurred */
|
|
result = LOWER_HALF (result + 6); /* then correct the digit */
|
|
carry = 1; /* and set the carry */
|
|
}
|
|
|
|
else if (result < 0) { /* otherwise if a borrow occurred */
|
|
result = result + 10; /* then correct the digit */
|
|
carry = 1; /* and set the borrow */
|
|
}
|
|
|
|
else /* otherwise */
|
|
carry = 0; /* neither carry nor borrow was generated */
|
|
|
|
if (result > 0 && index >= augend->starting_index) /* if a significant digit that will fit in the result */
|
|
augend->significant_index = index; /* then count it */
|
|
|
|
augend->digits [index] = (HP_BYTE) result; /* save the digit */
|
|
}
|
|
while (index > last); /* and continue until all significant digits processed */
|
|
|
|
if (carry > 0 && index > 0) { /* if a carry out of the last significant digit occurs */
|
|
augend->digits [--index] = carry; /* then store it in the next MSD */
|
|
carry = 0; /* and indicate that space was available for it */
|
|
|
|
if (index >= augend->starting_index) /* if the carry did not overflow the available space */
|
|
augend->significant_index = index; /* then it becomes the most significant digit */
|
|
}
|
|
|
|
if (carry > 0 || augend->starting_index > index) /* if there is insufficient room to contain the result */
|
|
return trap_Decimal_Overflow; /* then indicate an overflow */
|
|
else /* otherwise */
|
|
return trap_None; /* the addition succeeded */
|
|
}
|
|
|
|
|
|
/* Subtract two decimal numbers.
|
|
|
|
The difference of the two decimal operands is returned in the accessor of the
|
|
first operand (minuend = minuend - subtrahend). Subtraction is implemented
|
|
by negating the subtrahend and then adding the minuend.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. If the subtrahend is zero, negating produces a "negative zero." However,
|
|
the "add" routine handles this as positive zero, so we do not need to
|
|
worry about this condition.
|
|
*/
|
|
|
|
static uint32 subtract_decimal (DIGIT_ACCESS *minuend, DIGIT_ACCESS *subtrahend)
|
|
{
|
|
if (subtrahend->sign == Negative) /* invert the sign */
|
|
subtrahend->sign = Positive; /* of the subtrahend */
|
|
else
|
|
subtrahend->sign = Negative;
|
|
|
|
return add_decimal (minuend, subtrahend); /* add to obtain the difference */
|
|
}
|
|
|
|
|
|
/* Multiply two decimal numbers.
|
|
|
|
The product of the two decimal operands is returned in the accessor of the
|
|
first operand (multiplicand = multiplicand * multiplier). Conceptually, the
|
|
implementation is a 28 x 28 = 56-digit multiply with the lower 28 digits
|
|
retained. If either operand is zero, zero is returned as the product.
|
|
Otherwise, the product is obtained by long multiplication (digit-by-digit
|
|
multiplication and summation of the partial products) with the shorter of the
|
|
two operands selected as the multiplier to improve efficiency.
|
|
|
|
If the result would overflow 28 digits (the maximum allowed), the
|
|
multiplication is not attempted, and no result is returned. If the result
|
|
fits in 28 digits but not in the space available for the result, the
|
|
truncated result is returned. In both cases, a Decimal Overflow trap is
|
|
returned. Otherwise, the product is returned in the multiplicand accessor,
|
|
and no trap occurs.
|
|
|
|
The product is obtained by multiplying the significant digits of the
|
|
multiplicand by each significant digit of the multiplier and summing the
|
|
partial products. The sign of the product is positive if the operand signs
|
|
are the same and negative if they differ.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. An initial check is made to see if the product would exceed the maximum
|
|
number of significant digits permitted (28). If the sum of the numbers
|
|
of significant digits exceeds 29, the product will not fit. If the sum
|
|
is 28 or less, the product will fit. If the sum is 29, the product may
|
|
fit, depending on the values of the operands. In the last case, a carry
|
|
out of the 28th digit indicates that the product won't fit.
|
|
*/
|
|
|
|
static uint32 multiply_decimal (DIGIT_ACCESS *multiplicand, DIGIT_ACCESS *multiplier)
|
|
{
|
|
DIGIT_ACCESS *op1, *op2;
|
|
HP_WORD comparison;
|
|
HP_BYTE product [MAX_DIGITS];
|
|
uint32 index_1, index_2, index_p, start_1, start_2, digit, partial, carry;
|
|
|
|
if (multiplicand->significant_index == NOT_SET) /* if the multiplicand is zero */
|
|
return trap_None; /* then it already hold the product */
|
|
|
|
else if (multiplier->significant_index == NOT_SET) { /* otherwise if the multiplier is zero */
|
|
memset (multiplicand->digits, 0, /* then set the multiplicand value to zero */
|
|
sizeof multiplicand->digits); /* by clearing the digit array */
|
|
|
|
multiplicand->sign = Positive; /* the result is positive */
|
|
multiplicand->significant_index = NOT_SET; /* with no significant digits */
|
|
return trap_None; /* and no error */
|
|
}
|
|
|
|
else if (multiplicand->significant_index /* otherwise if the product would overflow */
|
|
+ multiplier->significant_index < MAX_DIGITS - 1) /* the maximum number of digits allowed */
|
|
return trap_Decimal_Overflow; /* then report it without trying */
|
|
|
|
else { /* otherwise neither value is zero */
|
|
comparison = compare_decimal (multiplicand, multiplier); /* so compare the operand magnitudes */
|
|
|
|
if (comparison == STATUS_CCL) { /* if the multiplicand is smaller than the multiplier */
|
|
op1 = multiplier; /* then swap the order */
|
|
op2 = multiplicand; /* to reduce the number of operations */
|
|
}
|
|
|
|
else { /* otherwise the multiplicand is larger than the multiplier */
|
|
op1 = multiplicand; /* so keep the supplied order */
|
|
op2 = multiplier; /* which is already optimal */
|
|
}
|
|
}
|
|
|
|
if (multiplicand->sign == multiplier->sign) /* if the operand signs are the same */
|
|
multiplicand->sign = Positive; /* then the result will be positive */
|
|
else /* otherwise */
|
|
multiplicand->sign = Negative; /* a negative value will result */
|
|
|
|
memset (product, 0, sizeof product); /* clear the product */
|
|
|
|
start_1 = op1->significant_index; /* get the indices of */
|
|
start_2 = op2->significant_index; /* the first non-zero digits in each operand */
|
|
|
|
index_2 = MAX_DIGITS; /* begin with the multiplier LSD and work toward the MSD */
|
|
|
|
do { /* form the partial products in sequence */
|
|
index_p = index_2; /* align the product sum with the multiplier digit */
|
|
carry = 0; /* and start with no initial carry */
|
|
|
|
digit = op2->digits [--index_2]; /* get the next multiplier digit */
|
|
|
|
if (digit > 0) { /* if the partial product will contribute to the sum */
|
|
index_1 = MAX_DIGITS; /* then start at the multiplicand LSD and work forward */
|
|
|
|
do { /* form the next partial product */
|
|
partial = product [--index_p] + carry /* from the sum of the current product and carry */
|
|
+ op1->digits [--index_1] * digit; /* and the product of the next two operand digits */
|
|
|
|
product [index_p] = partial % 10; /* save the new current product digit */
|
|
carry = partial / 10; /* and carry any overflow to the next digit */
|
|
}
|
|
while ((index_1 > start_1 || carry > 0) /* continue until the multiplicand is exhausted */
|
|
&& index_p > 0); /* or the product has no more room */
|
|
}
|
|
}
|
|
while (index_2 > start_2); /* continue until the multiplier is exhausted */
|
|
|
|
if (carry > 0) { /* if a carry out of the last digit occurred */
|
|
multiplicand->bac.length = 0; /* then skip writing back the result */
|
|
return trap_Decimal_Overflow; /* because it is larger than the maximum allowed */
|
|
}
|
|
|
|
else { /* otherwise */
|
|
multiplicand->significant_index = index_p; /* update the count of significant product digits */
|
|
|
|
memcpy (multiplicand->digits, product, /* copy the product digits */
|
|
sizeof multiplicand->digits); /* back into the result accessor */
|
|
|
|
if (multiplicand->significant_index /* if some significant digits will be lost */
|
|
< multiplicand->starting_index) /* because the result isn't large enough */
|
|
return trap_Decimal_Overflow; /* then signal an overflow */
|
|
else /* otherwise */
|
|
return trap_None; /* then correct product is returned */
|
|
}
|
|
}
|
|
|
|
|
|
/* Shift a decimal number.
|
|
|
|
The decimal number specified by the "source" accessor is shifted by the number
|
|
of digits specified by the value in the X register in the direction and mode
|
|
specified by the "shift" parameter and is returned in the "target" accessor.
|
|
Three shift modes are supported:
|
|
|
|
Shift Mode Action
|
|
----------- ---------------------------------------------------------
|
|
Right Shift digits to the right, zero fill on the left
|
|
Left Shift digits to the left, zero fill on the right
|
|
Normalizing Same as Left, except stop shifting if a significant digit
|
|
would be lost
|
|
|
|
For the Right shift mode, digits shifted off of the right end are lost. For
|
|
the Left shift mode, digits shifted off the left end are lost; if a lost
|
|
digit is significant (i.e., non-zero), the Carry flag is set in the Status
|
|
register. The Normalizing shift mode is identical to the Left mode, except
|
|
that shifting is stopped if a significant digit would be lost; the remaining
|
|
shift count is present in the X register on return. If no shifting can be
|
|
done without losing a significant digit (i.e., the number of significant
|
|
source digits is greater than the number of available target digits), Carry
|
|
is set, and a Decimal Overflow trap is returned. If the shift succeeds, no
|
|
trap is returned.
|
|
|
|
Entry is with the target value initialized to zero. If the source value is
|
|
zero, then shifting will not change the result, so the routine returns
|
|
immediately. Otherwise, shifting is performed by copying a selected set of
|
|
digits from the source to the target. This is accomplished by determining
|
|
the starting and ending indices in the source digit array and the starting
|
|
index in the target array.
|
|
|
|
First, the proposed shift is examined to determine if significant digits will
|
|
be lost due to an insufficiently large target. This would occur if a left
|
|
shift moved the most-significant digit outside of the available target space
|
|
or a right shift failed to move the most-significant digit into the target
|
|
space. In the case of a normal left or right shift, the source index is
|
|
advanced to the first digit to be transferred, and the target index is set to
|
|
the first target digit. For a normalizing left shift, the shift count is
|
|
reduced to ensure that target will start with the most-significant source
|
|
digit. The left shifts also set Carry status; the right shift simply
|
|
truncates the result.
|
|
|
|
If no significant digits would be lost, the source and target indices are set
|
|
as directed by the shift count and direction, and the index where the copy
|
|
should end is calculated. If the shift count is large enough to shift all
|
|
source digits off either end of the target, no digits are copied, and the
|
|
zero result is returned.
|
|
|
|
The routine converts an unsigned source into a positive target but otherwise
|
|
sets the target sign to that of the source. As the requisite digits are
|
|
copied, the significance of the target is computed, as losing significant
|
|
digits from the source will render a smaller significance in the target.
|
|
*/
|
|
|
|
static uint32 shift_decimal (DIGIT_ACCESS *target, DIGIT_ACCESS *source, SHIFT_MODE shift)
|
|
{
|
|
uint32 source_index, target_index, end_index;
|
|
|
|
if (source->significant_index == NOT_SET) /* if the source value is zero */
|
|
return trap_None; /* then the target value is also zero */
|
|
|
|
else if (shift == Right) {
|
|
if (source->significant_index + X < target->starting_index) { /* if significant digits will be lost */
|
|
source_index = target->starting_index - X; /* then start at the first non-truncated digit */
|
|
target_index = target->starting_index; /* and copy to the first target digit */
|
|
}
|
|
|
|
else { /* otherwise the leading digits will fit */
|
|
source_index = source->significant_index; /* so start at the first significant digit */
|
|
target_index = source_index + X; /* and target the desired shift location */
|
|
}
|
|
|
|
if (target_index < MAX_DIGITS) /* if there are target digits to move */
|
|
end_index = source_index + MAX_DIGITS - target_index; /* then set up the ending source index */
|
|
else /* otherwise the shift loses all digits */
|
|
source_index = MAX_DIGITS; /* so point beyond the source array */
|
|
}
|
|
|
|
else if (shift == Left) {
|
|
if (source->significant_index < target->starting_index + X) { /* if significant digits will be lost */
|
|
source_index = target->starting_index + X; /* then start at the first non-truncated digit */
|
|
target_index = target->starting_index; /* and copy to the first target digit */
|
|
|
|
SET_CARRY (TRUE); /* set Carry to indicate a significance loss */
|
|
}
|
|
|
|
else { /* otherwise all digits will fit */
|
|
source_index = source->significant_index; /* so start at the first significant digit */
|
|
target_index = source_index - X; /* and target the desired shift location */
|
|
}
|
|
|
|
end_index = MAX_DIGITS; /* set up the ending source index */
|
|
}
|
|
|
|
else {
|
|
if (source->significant_index < target->starting_index) { /* if shift cannot be done without losing significance */
|
|
SET_CARRY (TRUE); /* then set Carry status */
|
|
return trap_Decimal_Overflow; /* and trap for an overflow */
|
|
}
|
|
|
|
else /* otherwise the leading digit will fit */
|
|
source_index = source->significant_index; /* so start with the first significant digit */
|
|
|
|
if (X > source_index - target->starting_index) { /* if significant digits will be lost */
|
|
target_index = target->starting_index; /* then start the copy at the first target digit */
|
|
X = X - (source_index - target_index); /* and drop the shift count by the amount shifted */
|
|
|
|
SET_CARRY (TRUE); /* set Carry to indicate a significance loss */
|
|
}
|
|
|
|
else /* otherwise all source digits will fit */
|
|
target_index = source_index - X; /* so target the desired shift location */
|
|
|
|
end_index = MAX_DIGITS; /* set up the ending source index */
|
|
}
|
|
|
|
if (source_index >= MAX_DIGITS) /* if all digits will be shifted out of the target */
|
|
return trap_None; /* then the result is zero */
|
|
else if (source->sign == Unsigned) /* otherwise if the source is unsigned */
|
|
target->sign = Positive; /* then set the result positive */
|
|
else /* otherwise */
|
|
target->sign = source->sign; /* the result is the same sign as the source */
|
|
|
|
while (source_index < end_index) { /* while there are digits to move */
|
|
if (target->significant_index == NOT_SET /* if the significant digit count has not been set */
|
|
&& source->digits [source_index] > 0) /* and the next source digit is non-zero */
|
|
target->significant_index = target_index; /* then mark it as significant */
|
|
|
|
target->digits [target_index++] = source->digits [source_index++]; /* copy the digit to the result */
|
|
}
|
|
|
|
return trap_None; /* return success */
|
|
}
|
|
|
|
|
|
/* Convert between packed and external decimal.
|
|
|
|
The supplied source operand is converted to the format of the target operand.
|
|
If the target is in external decimal format, each packed decimal digit in the
|
|
source is converted to ASCII and stored in the corresponding location in the
|
|
target digit array. Bits 9 and 10 of the instruction indicate how the sign
|
|
is to be handled. If bit 9 (NABS_FLAG) is set, the target is unsigned. If
|
|
bit 10 (ABS_FLAG) is set, the target is negative if the source is negative,
|
|
otherwise it is unsigned. If neither bit is set, the target adopts the
|
|
source sign.
|
|
|
|
If the target is packed, external decimal source digits are converted to BCD
|
|
and stored in the target digit array. Leading source blanks are allowed and
|
|
are converted to zeros, but embedded blanks will cause a trap. The source
|
|
sign is decoded from the potentially overpunched LSD and set as the target
|
|
sign. Conversion stops if the target is filled; remaining source digits are
|
|
not checked for validity.
|
|
|
|
Invalid source digits will cause an Invalid Decimal Digit or Invalid ASCII
|
|
Digit trap, depending on the format. In addition, the partially converted
|
|
value is present in the target to the same extent as in the microcode. A
|
|
decimal to ASCII conversion proceeds from left to right and stops at the
|
|
point an invalid digit is encountered. Any good digits converted to that
|
|
point will be written to memory.
|
|
|
|
For an ASCII to decimal conversion, the check is executed from right to left,
|
|
and an Invalid ASCII Digit trap occurs if the check fails. If a trap occurs,
|
|
a partial decimal conversion may be done. The rules are a bit arcane, and
|
|
the diagnostic tests for expected results for bad source strings.
|
|
|
|
The sign is checked first, so if it is bad, nothing is written. Thereafter,
|
|
words are written as they are converted until an invalid character is seen,
|
|
whereupon an immediate trap is taken, and the word in which the bad character
|
|
would appear is not written. Because the decimal target may end in either
|
|
the upper or lower byte of the last word, an illegal character as the
|
|
next-to-last digit may or may not write a target word. If the target ends in
|
|
the upper byte, that word is written; if it ends in the lower byte, that word
|
|
is not written.
|
|
|
|
The situation is complicated by embedded blanks. Because processing occurs
|
|
from right to left, encountering one or more contiguous blanks may or may not
|
|
be illegal, depending on whether any additional digits appear to the left of
|
|
the blank(s). Working backward, the first space encountered is replaced by a
|
|
zero, and a flag is set to require all additional characters to be spaces
|
|
(i.e., it assumes that it is processing leading spaces). If a non-space
|
|
character is subsequently encountered, the trap is taken at that point, and
|
|
any prior blanks encountered are stored as valid zeros.
|
|
|
|
This means, for example, that an embedded space that would go in the MSD of a
|
|
word will write that word with a zero in the MSD and then trap on the NEXT
|
|
valid digit. So processing "1234 67" will write an "067F" word before
|
|
trapping, but processing "12345 7" or "1234-67" will write nothing.
|
|
|
|
As another example, if the ASCII string consists of a "1" followed by eight
|
|
blanks followed by the sign, and the target ends in the right-hand byte, a
|
|
word of four zeros and a word of three zeros plus a sign would be written
|
|
before the trap is taken. However, if the string contained a "1" followed by
|
|
two blanks followed by the sign, nothing would be written.
|
|
|
|
The diagnostic checks these three cases:
|
|
|
|
Step 463, source 7,"1234 67", result 3,"067F"
|
|
stored as: 067F
|
|
|
|
Step 464, source 9,"9=7654321", result 5,"54321F"
|
|
stored as: 5432 1FFF
|
|
|
|
Step 465, source 10,"0+2345678R", result 7,"3456789D"
|
|
stored as: 3456 789D
|
|
|
|
If all digits are valid, the routine returns no trap, and the converted value
|
|
is present in the target accessor.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Error handling for either conversion requires that the target decimal be
|
|
shortened, so that the resulting memory write matches that produced by
|
|
the microcode. Shortening a number involves resetting the byte accessor
|
|
length, the starting digit index, and the digit count. For a left-to-
|
|
right conversion, the leading digits are written first, so setting these
|
|
three values suffices. For a right-to-left conversion, in addition to
|
|
setting the previous three values, the first byte address, first byte
|
|
offset, and current byte offset must be advanced to the start of the
|
|
first valid digit, and the byte accessor must be set to pick up the new
|
|
values.
|
|
|
|
2. Because of the need to return partial results, entry may be made with
|
|
illegal digits present in the source accessor, so they must be detected
|
|
here. An invalid ASCII sign (overpunch) will have something other than
|
|
an ASCII digit in the last position, so no special handling is needed to
|
|
detect this.
|
|
|
|
3. On entry, at least one digit is to be converted, so special testing for a
|
|
zero count is not required.
|
|
*/
|
|
|
|
static uint32 convert_decimal (DIGIT_ACCESS *target, DIGIT_ACCESS *source)
|
|
{
|
|
HP_BYTE byte;
|
|
uint32 index, blank_index, bytes_skipped, byte_count, digit_count;
|
|
uint32 trap = trap_None;
|
|
|
|
if (target->format == Packed) {
|
|
index = MAX_DIGITS; /* work right-to-left */
|
|
blank_index = NOT_SET; /* initialize the blank index */
|
|
|
|
target->sign = source->sign; /* the value adopts the source sign */
|
|
target->significant_index = NOT_SET; /* recalculate the significant digit index */
|
|
|
|
while (index > source->starting_index /* while there are ASCII digits to convert */
|
|
&& index > target->starting_index) { /* and packed digits to fill */
|
|
byte = source->digits [--index]; /* get the next source character */
|
|
|
|
if (byte >= '0' && byte <= '9' /* if the character is numeric */
|
|
&& blank_index == NOT_SET) /* and blanks are not being skipped */
|
|
byte = byte - '0'; /* then convert to BCD */
|
|
|
|
else if (byte == ' ') { /* otherwise if it's a blank */
|
|
byte = 0; /* then fill with a zero */
|
|
|
|
if (blank_index == NOT_SET) /* if the blank index has not been set */
|
|
blank_index = index; /* then set it now */
|
|
}
|
|
|
|
else { /* otherwise */
|
|
trap = trap_Invalid_ASCII_Digit; /* the digit is invalid */
|
|
break; /* so quit at this point */
|
|
}
|
|
|
|
if (byte > 0) /* if the digit is significant */
|
|
target->significant_index = index; /* then set or reset the index */
|
|
|
|
target->digits [index] = byte; /* add the digit to the target */
|
|
}
|
|
|
|
if (trap != trap_None) { /* if a bad digit is present */
|
|
byte_count = (MAX_DIGITS - index) / 2; /* then get the count of good bytes including the sign */
|
|
|
|
if (byte_count == 0) { /* if all bytes are bad, i.e., a bad sign */
|
|
target->bac.length = 0; /* then skip the write */
|
|
target->significant_index = 0; /* but force CCG to match the microcode */
|
|
}
|
|
|
|
else { /* otherwise adjust for full word writes */
|
|
if (target->bac.initial_byte_offset + target->bac.length & 1) /* if the target ends on an even byte */
|
|
byte_count = byte_count - 1 | 1; /* then adjust the byte count */
|
|
else /* otherwise it ends on an odd byte */
|
|
byte_count = byte_count & ~1; /* so adjust accordingly */
|
|
|
|
bytes_skipped = target->bac.length - byte_count; /* get the number of bytes that will be skipped */
|
|
|
|
target->bac.length = byte_count; /* reset the number of bytes to write */
|
|
target->bac.first_byte_address += bytes_skipped; /* and move the byte address and offset */
|
|
target->bac.first_byte_offset += bytes_skipped; /* forward to the new starting byte */
|
|
|
|
digit_count = byte_count * 2 - 1; /* get the number of digits to write, excluding the sign */
|
|
|
|
target->byte_offset += bytes_skipped; /* move the working offset forward */
|
|
target->starting_index = MAX_DIGITS - digit_count; /* reset the starting index */
|
|
target->digit_count = digit_count; /* and the count of digits to write */
|
|
|
|
mem_set_byte (&target->bac); /* set the new write location in the target accessor */
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (target->format == External) {
|
|
if (CIR & NABS_FLAG /* if the request is for an unsigned result */
|
|
|| CIR & ABS_FLAG && source->sign != Negative) /* or unsigned unless negative */
|
|
target->sign = Unsigned; /* then reset the result sign */
|
|
else /* otherwise */
|
|
target->sign = source->sign; /* the target adopts the source sign */
|
|
|
|
index = source->starting_index; /* start with the first digit */
|
|
target->significant_index = source->significant_index; /* and set the significance index */
|
|
|
|
do /* convert packed decimal to external decimal */
|
|
if (source->digits [index] <= 9) /* if the source digit is valid */
|
|
target->digits [index] = source->digits [index] + '0'; /* then convert it to a character */
|
|
|
|
else { /* otherwise */
|
|
target->bac.length = index - source->starting_index; /* reset the operand length */
|
|
target->digit_count = target->bac.length; /* to the count of good digits */
|
|
target->sign = Unsigned; /* and omit the sign overpunch */
|
|
|
|
trap = trap_Invalid_Decimal_Digit; /* trap for the error */
|
|
break; /* and stop the conversion */
|
|
}
|
|
|
|
while (++index < MAX_DIGITS); /* loop until all digits are converted */
|
|
}
|
|
|
|
return trap; /* return the trap status */
|
|
}
|
|
|
|
|
|
/* Convert between binary and decimal number formats.
|
|
|
|
This routines converts a packed decimal number into its multi-word twos
|
|
complement binary equivalent or converts a binary number into its packed
|
|
decimal equivalent. The direction of the conversion is specified by the LSB
|
|
of the machine instruction in the CIR: 0 for binary-to-decimal and 1 for
|
|
decimal-to-binary.
|
|
|
|
On entry, the "decimal" parameter contains an initialized digit accessor that
|
|
is set up to provide (decimal-to-binary) or hold (binary-to-decimal) the
|
|
packed decimal number. The "address" and "count" parameters specify the
|
|
memory address of the multi-word array used as a corresponding target or
|
|
source for the binary number. The size of the array depends on the number of
|
|
digits in the packed decimal number, as follows:
|
|
|
|
Digits Words
|
|
------ -----
|
|
1-4 1
|
|
5-9 2
|
|
10-18 4
|
|
19-28 6
|
|
|
|
The binary number is a twos complement value with the most-significant word
|
|
first in the array.
|
|
|
|
On return, the converted binary number has been written to memory
|
|
(decimal-to-binary) or the converted decimal number is present in the digit
|
|
accessor. The function returns a trap value if an error occurred.
|
|
|
|
Conversion from decimal to binary proceeds from the LSD to the MSD of the
|
|
decimal value, forming a binary word from each group of four decimal digits
|
|
(resulting in a value from 0-9999), multiplying the multi-word binary value
|
|
by 10,000, and adding in the latest conversion. This process is repeated
|
|
until all of the significant digits in the source decimal are exhausted. The
|
|
multi-word multiplication is performed by doing 16 x 16 = 32 bit multiplies,
|
|
saving the lower 16 bits as the current product word, and using the upper 16
|
|
bits as a carry to the next product word.
|
|
|
|
After the value conversion is complete, the binary result is negated if the
|
|
source decimal sign is negative; this is done by a complement and increment
|
|
across the array words.
|
|
|
|
Conversion from binary to decimal begins by negating the binary number if it
|
|
is negative to obtain the absolute value. The number is then divided by
|
|
10,000, with the remainder forming the least significant four digits of the
|
|
decimal number. The division is repeated, obtaining each set of four digits
|
|
in turn, until the dividend is zero or the maximum number of decimal digits
|
|
is reached. If the limit is reached and the dividend is not yet zero, then a
|
|
decimal overflow trap is indicated.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The CVDB microcode checks for a target bounds violation before reading
|
|
the decimal number or writing any of the target words. The diagnostic
|
|
checks for this, so we must call "read_decimal" here instead of reading
|
|
the number before calling this routine. The diagnostic does not test for
|
|
negative numbers, overflows, or bad digits, so these were tested
|
|
independently.
|
|
|
|
2. The CVBD microcode checks the upper bound of the binary array against SM
|
|
and not SM + SR as is usually done; see page 172 of the microcode manual.
|
|
The simulation follows the microcode.
|
|
|
|
3. When converting from decimal to binary, the starting index into the digit
|
|
array must be adjusted to start on a four-digit boundary, so that each
|
|
group represents a division by 10,000.
|
|
*/
|
|
|
|
static uint32 convert_binary (DIGIT_ACCESS *decimal, HP_WORD address, HP_WORD count)
|
|
{
|
|
static const char *const binary_formats [] = { /* binary display formats, indexed by word count */
|
|
BOV_FORMAT "%s %d, %06o\n", /* 1 word format */
|
|
BOV_FORMAT "%s %d, %06o %06o\n", /* 2 word format */
|
|
BOV_FORMAT "%s %d, %06o %06o %06o\n", /* 3 word format */
|
|
BOV_FORMAT "%s %d, %06o %06o %06o %06o\n", /* 4 word format */
|
|
BOV_FORMAT "%s %d, %06o %06o %06o %06o %06o\n", /* 5 word format */
|
|
BOV_FORMAT "%s %d, %06o %06o %06o %06o %06o %06o\n" /* 6 word format */
|
|
};
|
|
|
|
HP_WORD binary [6], carry;
|
|
uint32 index, counter, digit, offset, end, word_count;
|
|
uint32 complement, remainder, accumulator, dividend, sum, partial;
|
|
uint32 trap = trap_None;
|
|
|
|
if (CIR & 1) { /* if this is a decimal-to-binary conversion */
|
|
if (count < 5) /* then determine */
|
|
word_count = 1; /* the number of */
|
|
else if (count < 10) /* binary words */
|
|
word_count = 2; /* needed to hold */
|
|
else if (count < 19) /* the decimal number */
|
|
word_count = 4; /* that is to be */
|
|
else /* converted */
|
|
word_count = 6;
|
|
|
|
offset = DB + address & LA_MASK; /* get the starting and ending */
|
|
end = offset + word_count - 1 & LA_MASK; /* memory offsets of the binary array */
|
|
|
|
if (NPRV && (offset < DL || end > SM)) /* if non-privileged and out of range */
|
|
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
|
|
else /* otherwise */
|
|
trap = read_decimal (decimal); /* read the source decimal number */
|
|
|
|
if (TRACING (cpu_dev, DEB_MOPND))
|
|
fprint_decimal_operand (decimal, "source");
|
|
|
|
if (trap == trap_None) { /* if the source decimal is valid */
|
|
memset (binary, 0, sizeof binary); /* then zero the target array */
|
|
|
|
if (decimal->significant_index != NOT_SET) { /* if the source decimal is not zero */
|
|
index = (decimal->significant_index / 4) * 4; /* then point at the first group of four digits */
|
|
|
|
do { /* convert groups of four digits to binary */
|
|
sum = 0; /* clear the group sum */
|
|
|
|
for (counter = 0; counter < 4; counter++) /* sum the next four */
|
|
sum = sum * 10 + decimal->digits [index++]; /* decimal digits */
|
|
|
|
carry = sum; /* set up the carry into the LSW */
|
|
counter = word_count; /* and start at the end of the array */
|
|
|
|
do { /* multiply the binary number by 10,000 */
|
|
partial = binary [--counter] * 10000 + carry; /* and add the sum */
|
|
|
|
binary [counter] = LOWER_WORD (partial);
|
|
carry = UPPER_WORD (partial);
|
|
}
|
|
while (counter > 0);
|
|
}
|
|
while (index < MAX_DIGITS); /* loop until all digits are converted */
|
|
|
|
if (decimal->sign == Negative) { /* if the decimal number is negative */
|
|
carry = 1; /* then negate the words */
|
|
counter = word_count; /* in the binary array */
|
|
|
|
do { /* perform a twos complement of the binary number */
|
|
complement = LOWER_WORD (~binary [--counter]) + carry;
|
|
binary [counter] = LOWER_WORD (complement);
|
|
carry = UPPER_WORD (complement);
|
|
}
|
|
while (counter > 0);
|
|
}
|
|
}
|
|
|
|
tprintf (cpu_dev, DEB_MOPND, binary_formats [word_count - 1],
|
|
DBANK, offset, address, " target", word_count,
|
|
binary [0], binary [1], binary [2],
|
|
binary [3], binary [4], binary [5]);
|
|
|
|
for (index = 0; index < word_count; index++) /* write the binary number to memory */
|
|
cpu_write_memory (data, offset++, binary [index]); /* with checking already done above */
|
|
}
|
|
}
|
|
|
|
else { /* otherwise this is a binary-to-decimal conversion */
|
|
offset = DB + address & LA_MASK; /* get the starting and ending */
|
|
end = offset + count - 1 & LA_MASK; /* memory offsets of the binary array */
|
|
|
|
if (NPRV && (offset < DL || end > SM)) /* if non-privileged and out of range */
|
|
MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */
|
|
|
|
for (index = 0; index < count; index++) /* load the binary array */
|
|
cpu_read_memory (data, offset + index, &binary [index]); /* with checking already done above */
|
|
|
|
tprintf (cpu_dev, DEB_MOPND, binary_formats [count - 1],
|
|
DBANK, offset, address, " source", count,
|
|
binary [0], binary [1], binary [2],
|
|
binary [3], binary [4], binary [5]);
|
|
|
|
if (binary [0] & D16_SIGN) { /* if the source binary number is negative */
|
|
decimal->sign = Negative; /* then set the target decimal sign */
|
|
carry = 1; /* and negate the words */
|
|
counter = count; /* in the array */
|
|
|
|
do { /* perform a twos complement of the binary number */
|
|
complement = LOWER_WORD (~binary [--counter]) + carry;
|
|
binary [counter] = LOWER_WORD (complement);
|
|
carry = UPPER_WORD (complement);
|
|
}
|
|
while (counter > 0);
|
|
}
|
|
|
|
else /* otherwise the binary number is positive */
|
|
decimal->sign = Positive; /* so set the decimal sign */
|
|
|
|
decimal->significant_index = NOT_SET; /* clear the significance counter for CCE detection */
|
|
index = MAX_DIGITS; /* and start the conversion from the right end */
|
|
|
|
do { /* convert the binary array to decimal by decades */
|
|
remainder = 0; /* clear the initial remainder */
|
|
accumulator = 0; /* and the zero accumulator */
|
|
|
|
for (counter = 0; counter < count; counter++) { /* divide the binary number by 10,000 */
|
|
dividend = TO_DWORD (remainder, binary [counter]);
|
|
|
|
remainder = dividend % 10000; /* divide the number by 10,000 */
|
|
binary [counter] = dividend / 10000; /* to isolate groups of four digits */
|
|
|
|
accumulator |= binary [counter]; /* accumulate to detect when the dividend is zero */
|
|
}
|
|
|
|
for (digit = 0; digit < 4; digit++) { /* split the remainder */
|
|
decimal->digits [--index] = remainder % 10; /* into four separate digits */
|
|
remainder = remainder / 10; /* and store in the decimal number */
|
|
|
|
if (decimal->digits [index] > 0) /* if the digit is non-zero */
|
|
decimal->significant_index = index; /* then (re)set the significance index */
|
|
}
|
|
}
|
|
while (index > 0 && accumulator > 0); /* loop until out of (significant) digits */
|
|
|
|
if (accumulator > 0) /* if more digits are present than will fit */
|
|
trap = trap_Decimal_Overflow; /* then set up for an overflow trap */
|
|
}
|
|
|
|
return trap;
|
|
}
|
|
|
|
|
|
/* Read a pair of decimal operands from memory.
|
|
|
|
The two decimal operands specified by the four top-of-stack values are read
|
|
into the supplied digit accessors. The first accessor is read from the
|
|
packed decimal number designated by RA (count) and RB (address) and the
|
|
second from the number designated by RC (count) and RD (address). If either
|
|
digit count is too large, the routine returns a Word Count Overflow trap. If
|
|
either number contains an invalid digit, the routine returns an Invalid
|
|
Decimal Digit trap. The function returns TRUE if the accessors were
|
|
populated with valid numbers and FALSE otherwise.
|
|
|
|
If operand tracing is enabled, the two operand values are printed on the
|
|
trace log before returning.
|
|
*/
|
|
|
|
static t_bool read_operands (DIGIT_ACCESS *first, DIGIT_ACCESS *second, uint32 *trap)
|
|
{
|
|
if (RA > MAX_DIGITS || RC > MAX_DIGITS) { /* if the operand digit counts are too large */
|
|
*trap = trap_Invalid_Decimal_Length; /* then trap for a count overflow */
|
|
return FALSE; /* and indicate read failure */
|
|
}
|
|
|
|
else if (RA == 0 || RC == 0) { /* otherwise if there are digits to process */
|
|
*trap = trap_None; /* then indicate read failure */
|
|
return FALSE; /* without a trap */
|
|
}
|
|
|
|
else { /* otherwise there are digits to process */
|
|
init_decimal (first, Packed, data_checked, RB, RA); /* so set up the digit accessors */
|
|
init_decimal (second, Packed, data_checked, RD, RC); /* for the decimal operands */
|
|
|
|
*trap = read_decimal (first); /* read the first decimal operand */
|
|
|
|
if (TRACING (cpu_dev, DEB_MOPND))
|
|
fprint_decimal_operand (first, "operand-1");
|
|
|
|
if (*trap == trap_None) { /* if the first decimal is valid */
|
|
*trap = read_decimal (second); /* then read the second decimal operand */
|
|
|
|
if (TRACING (cpu_dev, DEB_MOPND))
|
|
fprint_decimal_operand (second, "operand-2");
|
|
}
|
|
|
|
return (*trap == trap_None); /* return TRUE if both reads were good */
|
|
}
|
|
}
|
|
|
|
|
|
/* Write a decimal operand to memory.
|
|
|
|
The packed decimal number specified by the supplied digit accessor is written
|
|
to memory, and the condition code is set according to the value. The
|
|
accessor is reset before writing, in case it was used to read a decimal value
|
|
earlier (e.g., in-place update is being done). If operand tracing is
|
|
enabled, the operand value is printed on the trace log before returning.
|
|
*/
|
|
|
|
static void write_operand (DIGIT_ACCESS *operand)
|
|
{
|
|
mem_reset_byte (&operand->bac); /* reset the accessor in case it has been used */
|
|
write_decimal (operand, TRUE); /* and write the operand to memory */
|
|
|
|
if (TRACING (cpu_dev, DEB_MOPND))
|
|
fprint_decimal_operand (operand, "result");
|
|
|
|
set_cca_decimal (operand); /* set CCA on the decimal result */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Set Condition Code A for a decimal number.
|
|
|
|
The condition code in the status register is set to reflect the value passed
|
|
in the supplied digit accessor.
|
|
*/
|
|
|
|
static void set_cca_decimal (DIGIT_ACCESS *dap)
|
|
{
|
|
if (dap->significant_index == NOT_SET) /* if the number has no significant digits */
|
|
SET_CCE; /* then the value is zero */
|
|
else if (dap->sign == Negative) /* otherwise if the sign is negative */
|
|
SET_CCL; /* then the value is less than zero */
|
|
else /* otherwise */
|
|
SET_CCG; /* the value is greater than zero */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Decrement the stack and trap if enabled.
|
|
|
|
This routine is called with a trap value and either two or three stack
|
|
decrement values. If no trap is indicated or traps are disabled, the stack
|
|
is decremented as specified. If two values are specified, indicated by the
|
|
third decrement value being zero, the stack decrement bit is examined to
|
|
select either the first (0) or second (1) decrement value. If three values
|
|
are specified, then the stack decrement field (two bits) is examined, and the
|
|
field value determines the stack decrement value to use. For example:
|
|
|
|
Parameters CIR Test Decrements
|
|
---------- ---------- ----------
|
|
1, 3, 0 bit 11 -1, -3
|
|
0, 2, 0 bit 11 0, -2
|
|
0, 2, 4 bits 10-11 0, -2, -4
|
|
|
|
After the decrement is performed, if a trap is indicated, a microcode abort
|
|
is done. Otherwise, the overflow register is cleared, and the routine
|
|
returns.
|
|
*/
|
|
|
|
static void decrement_stack (uint32 trap, uint32 count_0, uint32 count_1, uint32 count_2)
|
|
{
|
|
uint32 count, decrement;
|
|
|
|
if (trap == trap_None || (STA & STATUS_T) == 0) { /* if the instruction succeeded or user traps are disabled */
|
|
if (count_2 == 0) /* then if only two choices are present */
|
|
if (CIR & EIS_SDEC_FLAG) /* then if the S-decrement flag is set */
|
|
decrement = count_1; /* then decrement by the second choice */
|
|
else /* otherwise */
|
|
decrement = count_0; /* then decrement by the first choice */
|
|
|
|
else switch (EIS_SDEC (CIR)) { /* otherwise select among the three choices */
|
|
|
|
case 0: /* if the S-decrement field is 00 */
|
|
decrement = count_0; /* then select the first choice */
|
|
break;
|
|
|
|
case 1: /* if the S-decrement field is 01 */
|
|
decrement = count_1; /* then select the second choice */
|
|
break;
|
|
|
|
case 2: /* if the S-decrement field is 10 */
|
|
default: /* or 11 (invalid) */
|
|
decrement = count_2; /* then select the third choice */
|
|
break;
|
|
}
|
|
|
|
if (decrement == 4) /* if four parameters are to be deleted */
|
|
SR = 0; /* then simply clear the stack counter */
|
|
|
|
else for (count = 0; count < decrement; count++) /* otherwise delete the number */
|
|
cpu_pop (); /* of items requested */
|
|
}
|
|
|
|
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 */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Strip the sign from an overpunched digit.
|
|
|
|
If the supplied character includes a valid overpunched sign, it is stripped
|
|
and returned separately, the character is set to the stripped digit, and
|
|
trap_None is returned to indicate success. If the character 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 (HP_BYTE *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 */
|
|
}
|
|
|
|
|
|
/* Format and print a decimal memory operand.
|
|
|
|
The decimal operand described by the decimal accessor is sent to the debug
|
|
trace log file. Operand tracing must be enabled when the routine is called.
|
|
|
|
On entry, "op" points at the decimal accessor describing the operand, and
|
|
"label" points to text used to label the operand.
|
|
|
|
The operand is printed in this format:
|
|
|
|
>>CPU opnd: 00.045177 000467 source 15,"314159265358979"
|
|
~~ ~~~~~~ ~~~~~~ ~~~~~~ ~~ ~~~~~~~~~~~~~~~~~
|
|
| | | | | |
|
|
| | | | | +-- 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_decimal_operand (DIGIT_ACCESS *op, char *label)
|
|
{
|
|
typedef char * (*OP_PRINT) (uint32 address, uint32 length);
|
|
|
|
OP_PRINT operand_printer;
|
|
|
|
if (op->format == Packed) /* if this is a packed decimal number */
|
|
operand_printer = fmt_bcd_operand; /* then use the BCD operand printer */
|
|
else /* otherwise */
|
|
operand_printer = fmt_byte_operand; /* use the character operand printer */
|
|
|
|
hp_trace (&cpu_dev, DEB_MOPND, BOV_FORMAT " %s %d,\"%s\"\n",
|
|
TO_BANK (op->bac.first_byte_address / 2),
|
|
TO_OFFSET (op->bac.first_byte_address / 2),
|
|
op->bac.first_byte_offset, label, op->digit_count,
|
|
operand_printer (op->bac.first_byte_address, op->digit_count));
|
|
|
|
return;
|
|
}
|