/* i1401_cpu.c: IBM 1401 CPU simulator | |
Copyright (c) 1993-2003, Robert M. Supnik | |
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 | |
ROBERT M SUPNIK 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 Robert M Supnik shall not | |
be used in advertising or otherwise to promote the sale, use or other dealings | |
in this Software without prior written authorization from Robert M Supnik. | |
12-Jul-03 RMS Moved ASCII/BCD tables to included file | |
Revised fetch to model hardware | |
Removed length checking in fetch phase | |
16-Mar-03 RMS Fixed mnemonic, instruction lengths, and reverse | |
scan length check bug for MCS | |
Fixed MCE bug, BS off by 1 if zero suppress | |
Fixed chaining bug, D lost if return to SCP | |
Fixed H branch, branch occurs after continue | |
Added check for invalid 8 character MCW, LCA | |
03-Jun-03 RMS Added 1311 support | |
22-May-02 RMS Added multiply and divide | |
30-Dec-01 RMS Added old PC queue | |
30-Nov-01 RMS Added extended SET/SHOW support | |
10-Aug-01 RMS Removed register in declarations | |
07-Dec-00 RMS Fixed bugs found by Charles Owen | |
-- 4,7 char NOPs are legal | |
-- 1 char B is chained BCE | |
-- MCE moves whole char after first | |
14-Apr-99 RMS Changed t_addr to unsigned | |
The register state for the IBM 1401 is: | |
IS I storage address register (PC) | |
AS A storage address register (address of first operand) | |
BS B storage address register (address of second operand) | |
ind[0:63] indicators | |
SSA sense switch A | |
IOCHK I/O check | |
PRCHK process check | |
The IBM 1401 is a variable instruction length, decimal data system. | |
Memory consists of 4000, 8000, 12000, or 16000 BCD characters, each | |
containing six bits of data and a word mark. There are no general | |
registers; all instructions are memory to memory, using explicit | |
addresses or an address pointer from a prior instruction. | |
BCD numeric data consists of the low four bits of a character (DIGIT), | |
encoded as X, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, X, X, X, X, X. The high | |
two bits (ZONE) encode the sign of the data as +, +, -, +. Character | |
data uses all six bits of a character. Numeric and character fields are | |
delimited by a word mark. Fields are typically processed in descending | |
address order (low-order data to high-order data). | |
The 1401 encodes a decimal address, and an index register number, in | |
three characters: | |
character zone digit | |
addr + 0 <1:0> of thousands hundreds | |
addr + 1 index register # tens | |
addr + 2 <3:2> of thousands ones | |
Normally the digit values 0, 11, 12, 13, 14, 15 are illegal in addresses. | |
However, in indexing, digits are passed through the adder, and illegal | |
values are normalized to legal counterparts. | |
The 1401 has six instruction formats: | |
op A and B addresses, if any, from AS and BS | |
op d A and B addresses, if any, from AS and BS | |
op aaa B address, if any, from BS | |
op aaa d B address, if any, from BS | |
op aaa bbb | |
op aaa bbb d | |
where aaa is the A address, bbb is the B address, and d is a modifier. | |
The opcode has word mark set; all other characters have word mark clear. | |
*/ | |
/* This routine is the instruction decode routine for the IBM 1401. | |
It is called from the simulator control program to execute | |
instructions in simulated memory, starting at the simulated PC. | |
It runs until 'reason' is set non-zero. | |
General notes: | |
1. Reasons to stop. The simulator can be stopped by: | |
HALT instruction | |
breakpoint encountered | |
illegal addresses or instruction formats | |
I/O error in I/O simulator | |
2. Interrupts. The 1401 has no interrupt structure. | |
3. Non-existent memory. On the 1401, references to non-existent | |
memory halt the processor. | |
4. Adding I/O devices. These modules must be modified: | |
i1401_cpu.c add device dispatching code to iodisp | |
i1401_sys.c add sim_devices table entry | |
*/ | |
#include "i1401_defs.h" | |
#include "i1401_dat.h" | |
#define PCQ_SIZE 64 /* must be 2**n */ | |
#define PCQ_MASK (PCQ_SIZE - 1) | |
#define PCQ_ENTRY pcq[pcq_p = (pcq_p - 1) & PCQ_MASK] = saved_IS | |
/* These macros validate addresses. If an addresses error is detected, | |
they return an error status to the caller. These macros should only | |
be used in a routine that returns a t_stat value. | |
*/ | |
#define MM(x) x = x - 1; \ | |
if (x < 0) { \ | |
x = BA + MAXMEMSIZE - 1; \ | |
reason = STOP_WRAP; \ | |
break; } | |
#define PP(x) x = x + 1; \ | |
if (ADDR_ERR (x)) { \ | |
x = BA + (x % MAXMEMSIZE); \ | |
reason = STOP_WRAP; \ | |
break; } | |
#define BRANCH if (ADDR_ERR (AS)) { \ | |
reason = STOP_INVBR; \ | |
break; } \ | |
if (cpu_unit.flags & XSA) BS = IS; \ | |
else BS = BA + 0; \ | |
PCQ_ENTRY; \ | |
IS = AS; | |
uint8 M[MAXMEMSIZE] = { 0 }; /* main memory */ | |
int32 saved_IS = 0; /* saved IS */ | |
int32 AS = 0; /* AS */ | |
int32 BS = 0; /* BS */ | |
int32 D = 0; /* modifier */ | |
int32 as_err = 0, bs_err = 0; /* error flags */ | |
int32 hb_pend = 0; /* halt br pending */ | |
uint16 pcq[PCQ_SIZE] = { 0 }; /* PC queue */ | |
int32 pcq_p = 0; /* PC queue ptr */ | |
REG *pcq_r = NULL; /* PC queue reg ptr */ | |
int32 ind[64] = { 0 }; /* indicators */ | |
int32 ssa = 1; /* sense switch A */ | |
int32 prchk = 0; /* process check stop */ | |
int32 iochk = 0; /* I/O check stop */ | |
extern int32 sim_int_char; | |
extern int32 sim_brk_types, sim_brk_dflt, sim_brk_summ; /* breakpoint info */ | |
t_stat cpu_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw); | |
t_stat cpu_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw); | |
t_stat cpu_reset (DEVICE *dptr); | |
t_stat cpu_set_size (UNIT *uptr, int32 val, char *cptr, void *desc); | |
int32 store_addr_h (int32 addr); | |
int32 store_addr_t (int32 addr); | |
int32 store_addr_u (int32 addr); | |
int32 div_add (int32 ap, int32 bp, int32 aend); | |
int32 div_sub (int32 ap, int32 bp, int32 aend); | |
void div_sign (int32 dvrc, int32 dvdc, int32 qp, int32 rp); | |
t_stat iomod (int32 ilnt, int32 mod, const int32 *tptr); | |
t_stat iodisp (int32 dev, int32 unit, int32 flag, int32 mod); | |
extern t_stat read_card (int32 ilnt, int32 mod); | |
extern t_stat punch_card (int32 ilnt, int32 mod); | |
extern t_stat select_stack (int32 mod); | |
extern t_stat carriage_control (int32 mod); | |
extern t_stat write_line (int32 ilnt, int32 mod); | |
extern t_stat inq_io (int32 flag, int32 mod); | |
extern t_stat mt_io (int32 unit, int32 flag, int32 mod); | |
extern t_stat dp_io (int32 fnc, int32 flag, int32 mod); | |
extern t_stat mt_func (int32 unit, int32 mod); | |
extern t_stat sim_activate (UNIT *uptr, int32 delay); | |
/* CPU data structures | |
cpu_dev CPU device descriptor | |
cpu_unit CPU unit descriptor | |
cpu_reg CPU register list | |
cpu_mod CPU modifier list | |
*/ | |
UNIT cpu_unit = { UDATA (NULL, UNIT_FIX + UNIT_BCD + STDOPT, | |
MAXMEMSIZE) }; | |
REG cpu_reg[] = { | |
{ DRDATA (IS, saved_IS, 14), PV_LEFT }, | |
{ DRDATA (AS, AS, 14), PV_LEFT }, | |
{ DRDATA (BS, BS, 14), PV_LEFT }, | |
{ FLDATA (ASERR, as_err, 0) }, | |
{ FLDATA (BSERR, bs_err, 0) }, | |
{ ORDATA (D, D, 7) }, | |
{ FLDATA (SSA, ssa, 0) }, | |
{ FLDATA (SSB, ind[IN_SSB], 0) }, | |
{ FLDATA (SSC, ind[IN_SSC], 0) }, | |
{ FLDATA (SSD, ind[IN_SSD], 0) }, | |
{ FLDATA (SSE, ind[IN_SSE], 0) }, | |
{ FLDATA (SSF, ind[IN_SSF], 0) }, | |
{ FLDATA (SSG, ind[IN_SSG], 0) }, | |
{ FLDATA (EQU, ind[IN_EQU], 0) }, | |
{ FLDATA (UNEQ, ind[IN_UNQ], 0) }, | |
{ FLDATA (HIGH, ind[IN_HGH], 0) }, | |
{ FLDATA (LOW, ind[IN_LOW], 0) }, | |
{ FLDATA (OVF, ind[IN_OVF], 0) }, | |
{ FLDATA (IOCHK, iochk, 0) }, | |
{ FLDATA (PRCHK, prchk, 0) }, | |
{ FLDATA (HBPEND, hb_pend, 0) }, | |
{ BRDATA (ISQ, pcq, 10, 14, PCQ_SIZE), REG_RO+REG_CIRC }, | |
{ DRDATA (ISQP, pcq_p, 6), REG_HRO }, | |
{ ORDATA (WRU, sim_int_char, 8) }, | |
{ NULL } }; | |
MTAB cpu_mod[] = { | |
{ XSA, XSA, "XSA", "XSA", NULL }, | |
{ XSA, 0, "no XSA", "NOXSA", NULL }, | |
{ HLE, HLE, "HLE", "HLE", NULL }, | |
{ HLE, 0, "no HLE", "NOHLE", NULL }, | |
{ BBE, BBE, "BBE", "BBE", NULL }, | |
{ BBE, 0, "no BBE", "NOBBE", NULL }, | |
{ MA, MA, "MA", 0, NULL }, | |
{ MA, 0, "no MA", 0, NULL }, | |
{ MR, MR, "MR", "MR", NULL }, | |
{ MR, 0, "no MR", "NOMR", NULL }, | |
{ EPE, EPE, "EPE", "EPE", NULL }, | |
{ EPE, 0, "no EPE", "NOEPE", NULL }, | |
{ MDV, MDV, "MDV", "MDV", NULL }, | |
{ MDV, 0, "no MDV", "NOMDV", NULL }, | |
{ UNIT_MSIZE, 4000, NULL, "4K", &cpu_set_size }, | |
{ UNIT_MSIZE, 8000, NULL, "8K", &cpu_set_size }, | |
{ UNIT_MSIZE, 12000, NULL, "12K", &cpu_set_size }, | |
{ UNIT_MSIZE, 16000, NULL, "16K", &cpu_set_size }, | |
{ 0 } }; | |
DEVICE cpu_dev = { | |
"CPU", &cpu_unit, cpu_reg, cpu_mod, | |
1, 10, 14, 1, 8, 7, | |
&cpu_ex, &cpu_dep, &cpu_reset, | |
NULL, NULL, NULL }; | |
/* Tables */ | |
/* Opcode table - length, dispatch, and option flags. This table is also | |
used by the symbolic input routine to validate instruction lengths */ | |
const int32 op_table[64] = { | |
0, /* 00: illegal */ | |
L1 | L2 | L4 | L5, /* read */ | |
L1 | L2 | L4 | L5, /* write */ | |
L1 | L2 | L4 | L5, /* write and read */ | |
L1 | L2 | L4 | L5, /* punch */ | |
L1 | L4, /* read and punch */ | |
L1 | L2 | L4 | L5, /* write and read */ | |
L1 | L2 | L4 | L5, /* write, read, punch */ | |
L1, /* 10: read feed */ | |
L1, /* punch feed */ | |
0, /* illegal */ | |
L1 | L4 | L7 | AREQ | BREQ | MA, /* modify address */ | |
L1 | L4 | L7 | AREQ | BREQ | MDV, /* multiply */ | |
0, /* illegal */ | |
0, /* illegal */ | |
0, /* illegal */ | |
0, /* 20: illegal */ | |
L1 | L4 | L7 | BREQ | NOWM, /* clear storage */ | |
L1 | L4 | L7 | AREQ | BREQ, /* subtract */ | |
0, /* illegal */ | |
L5 | IO, /* magtape */ | |
L1 | L8 | BREQ, /* branch wm or zone */ | |
L1 | L8 | BREQ | BBE, /* branch if bit eq */ | |
0, /* illegal */ | |
L1 | L4 | L7 | AREQ | BREQ, /* 30: move zones */ | |
L1 | L4 | L7 | AREQ | BREQ, /* move supress zero */ | |
0, /* illegal */ | |
L1 | L4 | L7 | AREQ | BREQ | NOWM, /* set word mark */ | |
L1 | L4 | L7 | AREQ | BREQ | MDV, /* divide */ | |
0, /* illegal */ | |
0, /* illegal */ | |
0, /* illegal */ | |
0, /* 40: illegal */ | |
0, /* illegal */ | |
L2 | L5, /* select stacker */ | |
L1 | L4 | L7 | L8 | BREQ | MLS | IO, /* load */ | |
L1 | L4 | L7 | L8 | BREQ | MLS | IO, /* move */ | |
HNOP | L1 | L2 | L4 | L5 | L7 | L8, /* nop */ | |
0, /* illegal */ | |
L1 | L4 | L7 | AREQ | BREQ | MR, /* move to record */ | |
L1 | L4 | AREQ | MLS, /* 50: store A addr */ | |
0, /* illegal */ | |
L1 | L4 | L7 | AREQ | BREQ, /* zero and subtract */ | |
0, /* illegal */ | |
0, /* illegal */ | |
0, /* illegal */ | |
0, /* illegal */ | |
0, /* illegal */ | |
0, /* 60: illegal */ | |
L1 | L4 | L7 | AREQ | BREQ, /* add */ | |
L1 | L4 | L5 | L8, /* branch */ | |
L1 | L4 | L7 | AREQ | BREQ, /* compare */ | |
L1 | L4 | L7 | AREQ | BREQ, /* move numeric */ | |
L1 | L4 | L7 | AREQ | BREQ, /* move char edit */ | |
L2 | L5, /* carriage control */ | |
0, /* illegal */ | |
L1 | L4 | L7 | AREQ | MLS, /* 70: store B addr */ | |
0, /* illegal */ | |
L1 | L4 | L7 | AREQ | BREQ, /* zero and add */ | |
HNOP | L1 | L2 | L4 | L5 | L7 | L8, /* halt */ | |
L1 | L4 | L7 | AREQ | BREQ, /* clear word mark */ | |
0, /* illegal */ | |
0, /* illegal */ | |
0 }; /* illegal */ | |
const int32 len_table[9] = { 0, L1, L2, 0, L4, L5, 0, L7, L8 }; | |
/* Address character conversion tables. Illegal characters are marked by | |
the flag BA but also contain the post-adder value for indexing */ | |
const int32 hun_table[64] = { | |
BA+000, 100, 200, 300, 400, 500, 600, 700, | |
800, 900, 000, BA+300, BA+400, BA+500, BA+600, BA+700, | |
BA+1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, | |
1800, 1900, 1000, BA+1300, BA+1400, BA+1500, BA+1600, BA+1700, | |
BA+2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, | |
2800, 2900, 2000, BA+2300, BA+2400, BA+2500, BA+2600, BA+2700, | |
BA+3000, 3100, 3200, 3300, 3400, 3500, 3600, 3700, | |
3800, 3900, 3000, BA+3300, BA+3400, BA+3500, BA+3600, BA+3700 }; | |
const int32 ten_table[64] = { | |
BA+00, 10, 20, 30, 40, 50, 60, 70, | |
80, 90, 00, BA+30, BA+40, BA+50, BA+60, BA+70, | |
X1+00, X1+10, X1+20, X1+30, X1+40, X1+50, X1+60, X1+70, | |
X1+80, X1+90, X1+00, X1+30, X1+40, X1+50, X1+60, X1+70, | |
X2+00, X2+10, X2+20, X2+30, X2+40, X2+50, X2+60, X2+70, | |
X2+80, X2+90, X2+00, X2+30, X2+40, X2+50, X2+60, X2+70, | |
X3+00, X3+10, X3+20, X3+30, X3+40, X3+50, X3+60, X3+70, | |
X3+80, X3+90, X3+00, X3+30, X3+40, X3+50, X3+60, X3+70 }; | |
const int32 one_table[64] = { | |
BA+0, 1, 2, 3, 4, 5, 6, 7, | |
8, 9, 0, BA+3, BA+4, BA+5, BA+6, BA+7, | |
BA+4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, | |
4008, 4009, 4000, BA+4003, BA+4004, BA+4005, BA+4006, BA+4007, | |
BA+8000, 8001, 8002, 8003, 8004, 8005, 8006, 8007, | |
8008, 8009, 8000, BA+8003, BA+8004, BA+8005, BA+8006, BA+8007, | |
BA+12000, 12001, 12002, 12003, 12004, 12005, 12006, 12007, | |
12008, 12009, 12000, BA+12003, BA+12004, BA+12005, BA+12006, BA+12007 }; | |
const int32 bin_to_bcd[16] = { | |
10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; | |
const int32 bcd_to_bin[16] = { | |
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 3, 4, 5, 6, 7 }; | |
/* Indicator resets - a 1 marks an indicator that resets when tested */ | |
static const int32 ind_table[64] = { | |
0, 0, 0, 0, 0, 0, 0, 0, /* 00 - 07 */ | |
0, 0, 0, 0, 0, 0, 0, 0, /* 10 - 17 */ | |
0, 0, 0, 0, 0, 0, 0, 0, /* 20 - 27 */ | |
0, 1, 1, 0, 1, 0, 0, 0, /* 30 - 37 */ | |
0, 0, 1, 0, 0, 0, 0, 0, /* 40 - 47 */ | |
0, 0, 1, 0, 1, 0, 0, 0, /* 50 - 57 */ | |
0, 0, 0, 0, 0, 0, 0, 0, /* 60 - 67 */ | |
0, 0, 1, 0, 0, 0, 0, 0 }; /* 70 - 77 */ | |
/* Character collation table for compare with HLE option */ | |
static const int32 col_table[64] = { | |
000, 067, 070, 071, 072, 073, 074, 075, | |
076, 077, 066, 024, 025, 026, 027, 030, | |
023, 015, 056, 057, 060, 061, 062, 063, | |
064, 065, 055, 016, 017, 020, 021, 022, | |
014, 044, 045, 046, 047, 050, 051, 052, | |
053, 054, 043, 007, 010, 011, 012, 013, | |
006, 032, 033, 034, 035, 036, 037, 040, | |
041, 042, 031, 001, 002, 003, 004, 005 }; | |
/* Summing table for two decimal digits, converted back to BCD | |
Also used for multiplying two decimal digits, converted back to BCD, | |
with carry forward | |
*/ | |
static const int32 sum_table[100] = { | |
BCD_ZERO, BCD_ONE, BCD_TWO, BCD_THREE, BCD_FOUR, | |
BCD_FIVE, BCD_SIX, BCD_SEVEN, BCD_EIGHT, BCD_NINE, | |
BCD_ZERO, BCD_ONE, BCD_TWO, BCD_THREE, BCD_FOUR, | |
BCD_FIVE, BCD_SIX, BCD_SEVEN, BCD_EIGHT, BCD_NINE, | |
BCD_ZERO, BCD_ONE, BCD_TWO, BCD_THREE, BCD_FOUR, | |
BCD_FIVE, BCD_SIX, BCD_SEVEN, BCD_EIGHT, BCD_NINE, | |
BCD_ZERO, BCD_ONE, BCD_TWO, BCD_THREE, BCD_FOUR, | |
BCD_FIVE, BCD_SIX, BCD_SEVEN, BCD_EIGHT, BCD_NINE, | |
BCD_ZERO, BCD_ONE, BCD_TWO, BCD_THREE, BCD_FOUR, | |
BCD_FIVE, BCD_SIX, BCD_SEVEN, BCD_EIGHT, BCD_NINE, | |
BCD_ZERO, BCD_ONE, BCD_TWO, BCD_THREE, BCD_FOUR, | |
BCD_FIVE, BCD_SIX, BCD_SEVEN, BCD_EIGHT, BCD_NINE, | |
BCD_ZERO, BCD_ONE, BCD_TWO, BCD_THREE, BCD_FOUR, | |
BCD_FIVE, BCD_SIX, BCD_SEVEN, BCD_EIGHT, BCD_NINE, | |
BCD_ZERO, BCD_ONE, BCD_TWO, BCD_THREE, BCD_FOUR, | |
BCD_FIVE, BCD_SIX, BCD_SEVEN, BCD_EIGHT, BCD_NINE, | |
BCD_ZERO, BCD_ONE, BCD_TWO, BCD_THREE, BCD_FOUR, | |
BCD_FIVE, BCD_SIX, BCD_SEVEN, BCD_EIGHT, BCD_NINE, | |
BCD_ZERO, BCD_ONE, BCD_TWO, BCD_THREE, BCD_FOUR, | |
BCD_FIVE, BCD_SIX, BCD_SEVEN, BCD_EIGHT, BCD_NINE }; | |
static const int32 cry_table[100] = { | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, | |
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, | |
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, | |
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, | |
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, | |
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, | |
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, | |
9, 9, 9, 9, 9, 9, 9, 9, 9, 9 }; | |
/* Legal modifier tables */ | |
static const int32 w_mod[] = { BCD_S, BCD_SQUARE, -1 }; | |
static const int32 ss_mod[] = { 1, 2, 4, 8, -1 }; | |
static const int32 mtf_mod[] = { BCD_B, BCD_E, BCD_M, BCD_R, BCD_U, -1 }; | |
t_stat sim_instr (void) | |
{ | |
extern int32 sim_interval; | |
int32 IS, ilnt, flags; | |
int32 op, xa, t, wm, ioind, dev, unit; | |
int32 a, b, i, asave, bsave; | |
int32 carry, lowprd, sign, ps; | |
int32 quo, ahigh, qs; | |
int32 qzero, qawm, qbody, qsign, qdollar, qaster, qdecimal; | |
t_stat reason, r1, r2; | |
/* Restore saved state */ | |
IS = saved_IS; | |
if (as_err) AS = AS | BA; /* flag bad addresses */ | |
if (bs_err) BS = BS | BA; | |
as_err = bs_err = 0; /* reset error flags */ | |
reason = 0; | |
/* Main instruction fetch/decode loop */ | |
while (reason == 0) { /* loop until halted */ | |
if (hb_pend) { /* halt br pending? */ | |
hb_pend = 0; /* clear flag */ | |
BRANCH; } /* execute branch */ | |
saved_IS = IS; /* commit prev instr */ | |
if (sim_interval <= 0) { /* check clock queue */ | |
if (reason = sim_process_event ()) break; } | |
if (sim_brk_summ && sim_brk_test (IS, SWMASK ('E'))) { /* breakpoint? */ | |
reason = STOP_IBKPT; /* stop simulation */ | |
break; } | |
sim_interval = sim_interval - 1; | |
/* Instruction fetch - 1401 fetch works as follows: | |
- Each character fetched enters the B register. This register is not | |
visible; the variable t represents the B register. | |
- Except for the first and last cycles, each character fetched enters | |
the A register. This register is not visible; the variable D represents | |
the A register, because this is the instruction modifier for 2, 5, and 8 | |
character instructions. | |
- At the start of the second cycle (first address character), the A-address | |
register and, for most instructions, the B-address register, are cleared | |
to blanks. The simulator represents addresses in binary and creates the | |
effect of blanks (address is bad) if less than three A-address characters | |
are found. Further, the simulator accumulates only the A-address, and | |
replicates it to the B-address at the appropriate point. | |
- At the start of the fifth cycle (fourth address character), the B-address | |
register is cleared to blanks. Again, the simulator creates the effect of | |
blanks (address is bad) if less than three B-address characters are found. | |
The 1401 does explicitly check for valid instruction lengths. Most 2,3,5,6 | |
character instructions will be invalid because the A-address or B-address | |
(or both) are invalid. | |
*/ | |
if ((M[IS] & WM) == 0) { /* I-Op: WM under op? */ | |
reason = STOP_NOWM; /* no, error */ | |
break; } | |
op = M[IS] & CHAR; /* get opcode */ | |
flags = op_table[op]; /* get op flags */ | |
if ((flags == 0) || (flags & ALLOPT & ~cpu_unit.flags)) { | |
reason = STOP_NXI; /* illegal inst? */ | |
break; } | |
if (op == OP_SAR) BS = AS; /* SAR? save ASTAR */ | |
PP (IS); | |
if ((t = M[IS]) & WM) goto CHECK_LENGTH; /* I-1: WM? 1 char inst */ | |
D = ioind = t; /* could be D char, % */ | |
AS = hun_table[t]; /* could be A addr */ | |
PP (IS); /* if %xy, BA is set */ | |
if ((t = M[IS]) & WM) { /* I-2: WM? 2 char inst */ | |
AS = AS | BA; /* ASTAR bad */ | |
if (!(flags & MLS)) BS = AS; | |
goto CHECK_LENGTH; } | |
D = dev = t; /* could be D char, dev */ | |
AS = AS + ten_table[t]; /* build A addr */ | |
PP (IS); | |
if ((t = M[IS]) & WM) { /* I-3: WM? 3 char inst */ | |
AS = AS | BA; /* ASTAR bad */ | |
if (!(flags & MLS)) BS = AS; | |
goto CHECK_LENGTH; } | |
D = unit = t; /* could be D char, unit */ | |
if (unit == BCD_ZERO) unit = 0; /* convert unit to binary */ | |
AS = AS + one_table[t]; /* finish A addr */ | |
xa = (AS >> V_INDEX) & M_INDEX; /* get index reg */ | |
if (xa && (ioind != BCD_PERCNT) && (cpu_unit.flags & XSA)) { /* indexed? */ | |
AS = AS + hun_table[M[xa] & CHAR] + ten_table[M[xa + 1] & CHAR] + | |
one_table[M[xa + 2] & CHAR]; | |
AS = (AS & INDEXMASK) % MAXMEMSIZE; } | |
if (!(flags & MLS)) BS = AS; /* not MLS? B = A */ | |
PP (IS); | |
if ((t = M[IS]) & WM) goto CHECK_LENGTH; /* I-4: WM? 4 char inst */ | |
if ((op == OP_B) && (t == BCD_BLANK)) goto CHECK_LENGTH; /* BR + space? */ | |
D = t; /* could be D char */ | |
BS = hun_table[t]; /* could be B addr */ | |
PP (IS); | |
if ((t = M[IS]) & WM) { /* I-5: WM? 5 char inst */ | |
BS = BS | BA; /* BSTAR bad */ | |
goto CHECK_LENGTH; } | |
D = t; /* could be D char */ | |
BS = BS + ten_table[t]; /* build B addr */ | |
PP (IS); | |
if ((t = M[IS]) & WM) { /* I-6: WM? 6 char inst */ | |
BS = BS | BA; /* BSTAR bad */ | |
goto CHECK_LENGTH; } | |
D = t; /* could be D char */ | |
BS = BS + one_table[t]; /* finish B addr */ | |
xa = (BS >> V_INDEX) & M_INDEX; /* get index reg */ | |
if (xa && (cpu_unit.flags & XSA)) { /* indexed? */ | |
BS = BS + hun_table[M[xa] & CHAR] + ten_table[M[xa + 1] & CHAR] + | |
one_table[M[xa + 2] & CHAR]; | |
BS = (BS & INDEXMASK) % MAXMEMSIZE; } | |
PP (IS); | |
if (flags & NOWM) goto CHECK_LENGTH; /* I-7: SWM? done */ | |
if ((t = M[IS]) & WM) goto CHECK_LENGTH; /* WM? 7 char inst */ | |
D = t; /* last char is D */ | |
for (;;) { /* I-8: repeats until WM */ | |
if ((t = M[IS]) & WM) break; /* WM? done */ | |
D = t; /* last char is D */ | |
PP (IS); } | |
CHECK_LENGTH: | |
ilnt = IS - saved_IS; /* get lnt */ | |
//if (((flags & len_table [(ilnt <= 8)? ilnt: 8]) == 0) && /* valid lnt? */ | |
// ((flags & HNOP) == 0)) reason = STOP_INVL; | |
if ((flags & BREQ) && ADDR_ERR (BS)) reason = STOP_INVB; /* valid A? */ | |
if ((flags & AREQ) && ADDR_ERR (AS)) reason = STOP_INVA; /* valid B? */ | |
if (reason) break; /* error in fetch? */ | |
switch (op) { /* case on opcode */ | |
/* Move/load character instructions A check B check | |
MCW copy A to B, preserving B WM, here fetch | |
until either A or B WM | |
LCA copy A to B, overwriting B WM, here fetch | |
until A WM | |
Instruction lengths: | |
1 chained A and B | |
2,3 invalid A-address | |
4 chained B address | |
5,6 invalid B-address | |
7 normal | |
8+ normal + modifier | |
*/ | |
case OP_MCW: /* move char */ | |
if ((ilnt >= 4) && (ioind == BCD_PERCNT)) { /* I/O form? */ | |
reason = iodisp (dev, unit, MD_NORM, D); /* dispatch I/O */ | |
break; } | |
if (ADDR_ERR (AS)) { /* check A addr */ | |
reason = STOP_INVA; | |
break; } | |
do { | |
wm = M[AS] | M[BS]; | |
M[BS] = (M[BS] & WM) | (M[AS] & CHAR); /* move char */ | |
MM (AS); MM (BS); } /* decr pointers */ | |
while ((wm & WM) == 0); /* stop on A,B WM */ | |
break; | |
case OP_LCA: /* load char */ | |
if ((ilnt >= 4) && (ioind == BCD_PERCNT)) { /* I/O form? */ | |
reason = iodisp (dev, unit, MD_WM, D); | |
break; } | |
if (ADDR_ERR (AS)) { /* check A addr */ | |
reason = STOP_INVA; | |
break; } | |
do { | |
wm = M[BS] = M[AS]; /* move char + wmark */ | |
MM (AS); MM (BS); } /* decr pointers */ | |
while ((wm & WM) == 0); /* stop on A WM */ | |
break; | |
/* Other move instructions A check B check | |
MCM copy A to B, preserving B WM, fetch fetch | |
until record or group mark | |
MCS copy A to B, clearing B WM, until A WM; fetch fetch | |
reverse scan and suppress leading zeroes | |
MN copy A char digit to B char digit, fetch fetch | |
preserving B zone and WM | |
MZ copy A char zone to B char zone, fetch fetch | |
preserving B digit and WM | |
Instruction lengths: | |
1 chained | |
2,3 invalid A-address | |
4 self (B-address = A-address) | |
5,6 invalid B-address | |
7 normal | |
8+ normal + ignored modifier | |
*/ | |
case OP_MCM: /* move to rec/group */ | |
do { | |
t = M[AS]; | |
M[BS] = (M[BS] & WM) | (M[AS] & CHAR); /* move char */ | |
PP (AS); PP (BS); } /* incr pointers */ | |
while (((t & CHAR) != BCD_RECMRK) && (t != (BCD_GRPMRK + WM))); | |
break; | |
case OP_MCS: /* move suppress zero */ | |
bsave = BS; /* save B start */ | |
qzero = 1; /* set suppress */ | |
do { | |
wm = M[AS]; | |
M[BS] = M[AS] & ((BS != bsave)? CHAR: DIGIT); /* copy char */ | |
MM (AS); MM (BS); } /* decr pointers */ | |
while ((wm & WM) == 0); /* stop on A WM */ | |
if (reason) break; /* addr err? stop */ | |
do { | |
PP (BS); /* adv B */ | |
t = M[BS]; /* get B, cant be WM */ | |
if ((t == BCD_ZERO) || (t == BCD_COMMA)) { | |
if (qzero) M[BS] = 0; } | |
else if ((t == BCD_BLANK) || (t == BCD_MINUS)) ; | |
else if (((t == BCD_DECIMAL) && (cpu_unit.flags & EPE)) || | |
(t <= BCD_NINE)) qzero = 0; | |
else qzero = 1; } | |
while (BS < bsave); | |
PP (BS); /* BS end is B+1 */ | |
break; | |
case OP_MN: /* move numeric */ | |
M[BS] = (M[BS] & ~DIGIT) | (M[AS] & DIGIT); /* move digit */ | |
MM (AS); MM (BS); /* decr pointers */ | |
break; | |
case OP_MZ: /* move zone */ | |
M[BS] = (M[BS] & ~ZONE) | (M[AS] & ZONE); /* move high bits */ | |
MM (AS); MM (BS); /* decr pointers */ | |
break; | |
/* Branch instruction A check B check | |
Instruction lengths: | |
1 branch if B char equals d, chained if branch here | |
2,3 invalid B-address if branch here | |
4 unconditional branch if branch | |
5 branch if indicator[d] is set if branch | |
6 invalid B-address if branch here | |
7 branch if B char equals d, if branch here | |
d is last character of B-address | |
8 branch if B char equals d if branch here | |
*/ | |
case OP_B: /* branch */ | |
if (ilnt == 4) { BRANCH; } /* uncond branch? */ | |
else if (ilnt == 5) { /* branch on ind? */ | |
if (ind[D]) { BRANCH; } /* test indicator */ | |
if (ind_table[D]) ind[D] = 0; } /* reset if needed */ | |
else { /* branch char eq */ | |
if (ADDR_ERR (BS)) { /* validate B addr */ | |
reason = STOP_INVB; | |
break; } | |
if ((M[BS] & CHAR) == D) { BRANCH; } /* char equal? */ | |
else { MM (BS); } } | |
break; | |
/* Other branch instructions A check B check | |
BWZ branch if (d<0>: B char WM) if branch here | |
(d<1>: B char zone = d zone) | |
BBE branch if B char & d non-zero if branch here | |
Instruction lengths: | |
1 chained | |
2,3 invalid A-address and B-address | |
4 self (B-address = A-address, d = last character of A-address) | |
5,6 invalid B-address | |
7 normal, d = last character of B-address | |
8+ normal | |
*/ | |
case OP_BWZ: /* branch wm or zone */ | |
if (((D & 1) && (M[BS] & WM)) || /* d1? test wm */ | |
((D & 2) && ((M[BS] & ZONE) == (D & ZONE)))) /* d2? test zone */ | |
{ BRANCH; } | |
else { MM (BS); } /* decr pointer */ | |
break; | |
case OP_BBE: /* branch if bit eq */ | |
if (M[BS] & D & CHAR) { BRANCH; } /* any bits set? */ | |
else { MM (BS); } /* decr pointer */ | |
break; | |
/* Arithmetic instructions A check B check | |
ZA move A to B, normalizing A sign, fetch fetch | |
preserving B WM, until B WM | |
ZS move A to B, complementing A sign, fetch fetch | |
preserving B WM, until B WM | |
A add A to B fetch fetch | |
S subtract A from B fetch fetch | |
C compare A to B fetch fetch | |
Instruction lengths: | |
1 chained | |
2,3 invalid A-address | |
4 self (B-address = A-address) | |
5,6 invalid B-address | |
7 normal | |
8+ normal + ignored modifier | |
*/ | |
case OP_ZA: case OP_ZS: /* zero and add/sub */ | |
a = i = 0; /* clear flags */ | |
do { | |
if (a & WM) wm = M[BS] = (M[BS] & WM) | BCD_ZERO; | |
else { | |
a = M[AS]; /* get A char */ | |
t = (a & CHAR)? bin_to_bcd[a & DIGIT]: 0; | |
wm = M[BS] = (M[BS] & WM) | t; /* move digit */ | |
MM (AS); } | |
if (i == 0) i = M[BS] = M[BS] | | |
((((a & ZONE) == BBIT) ^ (op == OP_ZS))? BBIT: ZONE); | |
MM (BS); } | |
while ((wm & WM) == 0); /* stop on B WM */ | |
break; | |
case OP_A: case OP_S: /* add/sub */ | |
bsave = BS; /* save sign pos */ | |
a = M[AS]; /* get A digit/sign */ | |
b = M[BS]; /* get B digit/sign */ | |
MM (AS); | |
qsign = ((a & ZONE) == BBIT) ^ ((b & ZONE) == BBIT) ^ (op == OP_S); | |
t = bcd_to_bin[a & DIGIT]; /* get A binary */ | |
t = bcd_to_bin[b & DIGIT] + (qsign? 10 - t: t); /* sum A + B */ | |
carry = (t >= 10); /* get carry */ | |
b = (b & ~DIGIT) | sum_table[t]; /* get result */ | |
if (qsign && ((b & BBIT) == 0)) b = b | ZONE; /* normalize sign */ | |
M[BS] = b; /* store result */ | |
MM (BS); | |
if (b & WM) { /* b wm? done */ | |
if (qsign && (carry == 0)) M[bsave] = /* compl, no carry? */ | |
WM + ((b & ZONE) ^ ABIT) + sum_table[10 - t]; | |
break; } | |
do { | |
if (a & WM) a = WM; /* A WM? char = 0 */ | |
else { | |
a = M[AS]; /* else get A */ | |
MM (AS); } | |
b = M[BS]; /* get B */ | |
t = bcd_to_bin[a & DIGIT]; /* get A binary */ | |
t = bcd_to_bin[b & DIGIT] + (qsign? 9 - t: t) + carry; | |
carry = (t >= 10); /* get carry */ | |
if ((b & WM) && (qsign == 0)) { /* last, no recomp? */ | |
M[BS] = WM + sum_table[t] + /* zone add */ | |
(((a & ZONE) + b + (carry? ABIT: 0)) & ZONE); | |
ind[IN_OVF] = carry; } /* ovflo if carry */ | |
else M[BS] = (b & WM) + sum_table[t]; /* normal add */ | |
MM (BS); } | |
while ((b & WM) == 0); /* stop on B WM */ | |
if (reason) break; /* address err? */ | |
if (qsign && (carry == 0)) { /* recompl, no carry? */ | |
M[bsave] = M[bsave] ^ ABIT; /* XOR sign */ | |
for (carry = 1; bsave != BS; --bsave) { /* rescan */ | |
t = 9 - bcd_to_bin[M[bsave] & DIGIT] + carry; | |
carry = (t >= 10); | |
M[bsave] = (M[bsave] & ~DIGIT) | sum_table[t]; } } | |
break; | |
case OP_C: /* compare */ | |
if (ilnt != 1) { /* if not chained */ | |
ind[IN_EQU] = 1; /* clear indicators */ | |
ind[IN_UNQ] = ind[IN_HGH] = ind[IN_LOW] = 0; } | |
do { | |
a = M[AS]; /* get characters */ | |
b = M[BS]; | |
wm = a | b; /* get word marks */ | |
if ((a & CHAR) != (b & CHAR)) { /* unequal? */ | |
ind[IN_EQU] = 0; /* set indicators */ | |
ind[IN_UNQ] = 1; | |
ind[IN_HGH] = col_table[b & CHAR] > col_table [a & CHAR]; | |
ind[IN_LOW] = ind[IN_HGH] ^ 1; } | |
MM (AS); MM (BS); } /* decr pointers */ | |
while ((wm & WM) == 0); /* stop on A, B WM */ | |
if ((a & WM) && !(b & WM)) { /* short A field? */ | |
ind[IN_EQU] = ind[IN_LOW] = 0; | |
ind[IN_UNQ] = ind[IN_HGH] = 1; } | |
if (!(cpu_unit.flags & HLE)) /* no HLE? */ | |
ind[IN_EQU] = ind[IN_LOW] = ind[IN_HGH] = 0; | |
break; | |
/* I/O instructions A check B check | |
R read a card if branch | |
W write to line printer if branch | |
WR write and read if branch | |
P punch a card if branch | |
RP read and punch if branch | |
WP : write and punch if branch | |
WRP write read and punch if branch | |
RF read feed (nop) | |
PF punch feed (nop) | |
SS select stacker if branch | |
CC carriage control if branch | |
Instruction lengths: | |
1 normal | |
2,3 normal, with modifier | |
4 branch; modifier, if any, is last character of branch address | |
5 branch + modifier | |
6+ normal, with modifier | |
*/ | |
case OP_R: /* read */ | |
if (reason = iomod (ilnt, D, NULL)) break; /* valid modifier? */ | |
reason = read_card (ilnt, D); /* read card */ | |
BS = CDR_BUF + CDR_WIDTH; | |
if ((ilnt == 4) || (ilnt == 5)) { BRANCH; } /* check for branch */ | |
break; | |
case OP_W: /* write */ | |
if (reason = iomod (ilnt, D, w_mod)) break; /* valid modifier? */ | |
reason = write_line (ilnt, D); /* print line */ | |
BS = LPT_BUF + LPT_WIDTH; | |
if ((ilnt == 4) || (ilnt == 5)) { BRANCH; } /* check for branch */ | |
break; | |
case OP_P: /* punch */ | |
if (reason = iomod (ilnt, D, NULL)) break; /* valid modifier? */ | |
reason = punch_card (ilnt, D); /* punch card */ | |
BS = CDP_BUF + CDP_WIDTH; | |
if ((ilnt == 4) || (ilnt == 5)) { BRANCH; } /* check for branch */ | |
break; | |
case OP_WR: /* write and read */ | |
if (reason = iomod (ilnt, D, w_mod)) break; /* valid modifier? */ | |
reason = write_line (ilnt, D); /* print line */ | |
r1 = read_card (ilnt, D); /* read card */ | |
BS = CDR_BUF + CDR_WIDTH; | |
if ((ilnt == 4) || (ilnt == 5)) { BRANCH; } /* check for branch */ | |
if (reason == SCPE_OK) reason = r1; /* merge errors */ | |
break; | |
case OP_WP: /* write and punch */ | |
if (reason = iomod (ilnt, D, w_mod)) break; /* valid modifier? */ | |
reason = write_line (ilnt, D); /* print line */ | |
r1 = punch_card (ilnt, D); /* punch card */ | |
BS = CDP_BUF + CDP_WIDTH; | |
if ((ilnt == 4) || (ilnt == 5)) { BRANCH; } /* check for branch */ | |
if (reason == SCPE_OK) reason = r1; /* merge errors */ | |
break; | |
case OP_RP: /* read and punch */ | |
if (reason = iomod (ilnt, D, NULL)) break; /* valid modifier? */ | |
reason = read_card (ilnt, D); /* read card */ | |
r1 = punch_card (ilnt, D); /* punch card */ | |
BS = CDP_BUF + CDP_WIDTH; | |
if ((ilnt == 4) || (ilnt == 5)) { BRANCH; } /* check for branch */ | |
if (reason == SCPE_OK) reason = r1; /* merge errors */ | |
break; | |
case OP_WRP: /* write, read, punch */ | |
if (reason = iomod (ilnt, D, w_mod)) break; /* valid modifier? */ | |
reason = write_line (ilnt, D); /* print line */ | |
r1 = read_card (ilnt, D); /* read card */ | |
r2 = punch_card (ilnt, D); /* punch card */ | |
BS = CDP_BUF + CDP_WIDTH; | |
if ((ilnt == 4) || (ilnt == 5)) { BRANCH; } /* check for branch */ | |
if (reason == SCPE_OK) reason = (r1 == SCPE_OK)? r2: r1; | |
break; | |
case OP_SS: /* select stacker */ | |
if (reason = iomod (ilnt, D, ss_mod)) break; /* valid modifier? */ | |
if (reason = select_stack (D)) break; /* sel stack, error? */ | |
if ((ilnt == 4) || (ilnt == 5)) { BRANCH; } /* check for branch */ | |
break; | |
case OP_CC: /* carriage control */ | |
if (reason = carriage_control (D)) break; /* car ctrl, error? */ | |
if ((ilnt == 4) || (ilnt == 5)) { BRANCH; } /* check for branch */ | |
break; | |
/* MTF - magtape functions - must be at least 4 characters | |
Instruction lengths: | |
1-3 invalid I/O address | |
4 normal, d-character is unit | |
5 normal | |
6+ normal, d-character is last character | |
*/ | |
case OP_MTF: /* magtape function */ | |
if (ilnt < 4) reason = STOP_INVL; /* too short? */ | |
else if (ioind != BCD_PERCNT) reason = STOP_INVA; | |
else if (reason = iomod (ilnt, D, mtf_mod)) break; /* valid modifier? */ | |
reason = mt_func (unit, D); /* mt func, error? */ | |
break; /* can't branch */ | |
case OP_RF: case OP_PF: /* read, punch feed */ | |
break; /* nop's */ | |
/* Move character and edit | |
Control flags | |
qsign sign of A field (0 = +, 1 = minus) | |
qawm A field WM seen and processed | |
qzero zero suppression enabled | |
qbody in body (copying A field characters) | |
qdollar EPE only; $ seen in body | |
qaster EPE only; * seen in body | |
qdecimal EPE only; . seen on first rescan | |
MCE operates in one to three scans, the first of which has three phases | |
1 right to left qbody = 0, qawm = 0 => right status | |
qbody = 1, qawm = 0 => body | |
qbody = 0, qawm = 1 => left status | |
2 left to right | |
3 right to left, extended print end only | |
The first A field character is masked to its digit part, all others | |
are copied intact | |
Instruction lengths: | |
1 chained | |
2,3 invalid A-address | |
4 self (B-address = A-address) | |
5,6 invalid B-address | |
7 normal | |
8+ normal + ignored modifier | |
*/ | |
case OP_MCE: /* edit */ | |
a = M[AS]; /* get A char */ | |
b = M[BS]; /* get B char */ | |
if (a & WM) { /* one char A field? */ | |
reason = STOP_MCE1; | |
break; } | |
if (b & WM) { /* one char B field? */ | |
reason = STOP_MCE2; | |
break; } | |
t = a & DIGIT; MM (AS); /* get A digit */ | |
qsign = ((a & ZONE) == BBIT); /* get A field sign */ | |
qawm = qzero = qbody = 0; /* clear other flags */ | |
qdollar = qaster = qdecimal = 0; /* clear EPE flags */ | |
/* Edit pass 1 - from right to left, under B field control | |
* in status or !epe, skip B; else, set qaster, repl with A | |
$ in status or !epe, skip B; else, set qdollar, repl with A | |
0 in right status or body, if !qzero, set A WM; set qzero, repl with A | |
else, if !qzero, skip B; else, if (!B WM) set B WM | |
blank in right status or body, repl with A; else, skip B | |
C,R,- in status, blank B; else, skip B | |
, in status, blank B, else, skip B | |
& blank B | |
*/ | |
do { | |
b = M[BS]; /* get B char */ | |
M[BS] = M[BS] & ~WM; /* clr WM */ | |
switch (b & CHAR) { /* case on B char */ | |
case BCD_ASTER: /* * */ | |
if (!qbody || qdollar || !(cpu_unit.flags & EPE)) break; | |
qaster = 1; /* flag */ | |
goto A_CYCLE; /* take A cycle */ | |
case BCD_DOLLAR: /* $ */ | |
if (!qbody || qaster || !(cpu_unit.flags & EPE)) break; | |
qdollar = 1; /* flag */ | |
goto A_CYCLE; /* take A cycle */ | |
case BCD_ZERO: /* 0 */ | |
if (qawm && !qzero && !(b & WM)) { | |
M[BS] = BCD_ZERO + WM; /* mark with WM */ | |
qzero = 1; /* flag supress */ | |
break; } | |
if (!qzero) t = t | WM; /* first? set WM */ | |
qzero = 1; /* flag supress */ | |
/* fall through */ | |
case BCD_BLANK: /* blank */ | |
if (qawm) break; /* any A left? */ | |
A_CYCLE: | |
M[BS] = t; /* copy char */ | |
if (a & WM) { /* end of A field? */ | |
qbody = 0; /* end body */ | |
qawm = 1; } | |
else { | |
qbody = 1; /* in body */ | |
a = M[AS]; MM (AS); /* next A */ | |
t = a & CHAR; } | |
break; | |
case BCD_C: case BCD_R: case BCD_MINUS: /* C, R, - */ | |
if (!qsign && !qbody) M[BS] = BCD_BLANK; | |
break; | |
case BCD_COMMA: /* , */ | |
if (!qbody) M[BS] = BCD_BLANK; /* bl if status */ | |
break; | |
case BCD_AMPER: /* & */ | |
M[BS] = BCD_BLANK; /* blank B field */ | |
break; } /* end switch */ | |
MM (BS); } /* decr B pointer */ | |
while ((b & WM) == 0); /* stop on B WM */ | |
if (reason) break; /* address err? */ | |
if (!qawm || !qzero) { /* rescan? */ | |
if (qdollar) reason = STOP_MCE3; /* error if $ */ | |
break; } | |
/* Edit pass 2 - from left to right, supressing zeroes */ | |
do { | |
b = M[++BS]; /* get B char */ | |
switch (b & CHAR) { /* case on B char */ | |
case BCD_ONE: case BCD_TWO: case BCD_THREE: | |
case BCD_FOUR: case BCD_FIVE: case BCD_SIX: | |
case BCD_SEVEN: case BCD_EIGHT: case BCD_NINE: | |
qzero = 0; /* turn off supr */ | |
break; | |
case BCD_ZERO: case BCD_COMMA: /* 0 or , */ | |
if (qzero && !qdecimal) /* if supr, blank */ | |
M[BS] = qaster? BCD_ASTER: BCD_BLANK; | |
break; | |
case BCD_BLANK: /* blank */ | |
if (qaster) M[BS] = BCD_ASTER; /* if EPE *, repl */ | |
break; | |
case BCD_DECIMAL: /* . */ | |
if (qzero && (cpu_unit.flags & EPE)) | |
qdecimal = 1; /* flag for EPE */ | |
case BCD_PERCNT: case BCD_WM: case BCD_BS: | |
case BCD_TS: case BCD_MINUS: | |
break; /* ignore */ | |
default: /* other */ | |
qzero = 1; /* restart supr */ | |
break; } } /* end case, do */ | |
while ((b & WM) == 0); | |
M[BS] = M[BS] & ~WM; /* clear B WM */ | |
if (!qdollar && !(qdecimal && qzero)) { /* rescan again? */ | |
BS++; /* BS = addr WM + 1 */ | |
break; } | |
if (qdecimal && qzero) qdollar = 0; /* no digits? clr $ */ | |
/* Edit pass 3 (extended print only) - from right to left */ | |
for (;; ) { /* until chars */ | |
b = M[BS]; /* get B char */ | |
if ((b == BCD_BLANK) && qdollar) { /* blank & flt $? */ | |
M[BS] = BCD_DOLLAR; /* insert $ */ | |
break; } /* exit for */ | |
if (b == BCD_DECIMAL) { /* decimal? */ | |
M[BS] = qaster? BCD_ASTER: BCD_BLANK; | |
break; } /* exit for */ | |
if ((b == BCD_ZERO) && !qdollar) /* 0 & ~flt $ */ | |
M[BS] = qaster? BCD_ASTER: BCD_BLANK; | |
BS--; } /* end for */ | |
break; /* done at last! */ | |
/* Multiply. Comments from the PDP-10 based simulator by Len Fehskens. | |
Multiply, with variable length operands, is necessarily done the same | |
way you do it with paper and pencil, except that partial products are | |
added into the incomplete final product as they are computed, rather | |
than at the end. The 1401 multiplier format allows the product to | |
be developed in place, without scratch storage. | |
The A field contains the multiplicand, length LD. The B field must be | |
LD + 1 + length of multiplier. Locate the low order multiplier digit, | |
and at the same time zero out the product field. Then compute the sign | |
of the result. | |
Instruction lengths: | |
1 chained | |
2,3 invalid A-address | |
4 self (B-address = A-address) | |
5,6 invalid B-address | |
7 normal | |
8+ normal + ignored modifier | |
*/ | |
case OP_MUL: | |
asave = AS; bsave = lowprd = BS; /* save AS, BS */ | |
do { | |
a = M[AS]; /* get mpcd char */ | |
M[BS] = BCD_ZERO; /* zero prod */ | |
MM (AS); MM (BS); } /* decr pointers */ | |
while ((a & WM) == 0); /* until A WM */ | |
if (reason) break; /* address err? */ | |
M[BS] = BCD_ZERO; /* zero hi prod */ | |
MM (BS); /* addr low mpyr */ | |
sign = ((M[asave] & ZONE) == BBIT) ^ ((M[BS] & ZONE) == BBIT); | |
/* Outer loop on multiplier (BS) and product digits (ps), | |
inner loop on multiplicand digits (AS). | |
AS and ps cannot produce an address error. | |
*/ | |
do { | |
ps = bsave; /* ptr to prod */ | |
AS = asave; /* ptr to mpcd */ | |
carry = 0; /* init carry */ | |
b = M[BS]; /* get mpyr char */ | |
do { | |
a = M[AS]; /* get mpcd char */ | |
t = (bcd_to_bin[a & DIGIT] * /* mpyr * mpcd */ | |
bcd_to_bin[b & DIGIT]) + /* + c + partial prod */ | |
carry + bcd_to_bin[M[ps] & DIGIT]; | |
carry = cry_table[t]; | |
M[ps] = (M[ps] & WM) | sum_table[t]; | |
MM (AS); ps--; } | |
while ((a & WM) == 0); /* until mpcd done */ | |
M[BS] = (M[BS] & WM) | BCD_ZERO; /* zero mpyr just used */ | |
t = bcd_to_bin[M[ps] & DIGIT] + carry; /* add carry to prod */ | |
M[ps] = (M[ps] & WM) | sum_table[t]; /* store */ | |
bsave--; /* adv prod ptr */ | |
MM (BS); } /* adv mpyr ptr */ | |
while ((b & WM) == 0); /* until mpyr done */ | |
M[lowprd] = M[lowprd] | ZONE; /* assume + */ | |
if (sign) M[lowprd] = M[lowprd] & ~ABIT; /* if minus, B only */ | |
break; | |
/* Divide. Comments from the PDP-10 based simulator by Len Fehskens. | |
Divide is done, like multiply, pretty much the same way you do it with | |
pencil and paper; successive subtraction of the divisor from a substring | |
of the dividend while counting up the corresponding quotient digit. | |
Let LS be the length of the divisor, LD the length of the dividend: | |
- AS points to the low order divisor digit. | |
- BS points to the high order dividend digit. | |
- The low order dividend digit is identified by sign (zone) bits. | |
- To the left of the dividend is a zero field of length LS + 1. | |
The low quotient is at low dividend - LS - 1. As BS points to the | |
high dividend, the low dividend is at BS + LD - 1, so the low | |
quotient is at BS + LD - LS - 2. The longest possible quotient is | |
LD - LS + 1, so the first possible non-zero quotient bit will be | |
found as BS - 2. | |
This pointer calculation assumes that the divisor has no leading zeroes. | |
For each leading zero, the start of the quotient will be one position | |
further left. | |
Start by locating the high order non-zero digit of the divisor. This | |
also tests for a divide by zero. | |
Instruction lengths: | |
1 chained | |
2,3 invalid A-address | |
4 self (B-address = A-address) | |
5,6 invalid B-address | |
7 normal | |
8+ normal + ignored modifier | |
*/ | |
case OP_DIV: | |
asave = AS; ahigh = -1; | |
do { | |
a = M[AS]; /* get dvr char */ | |
if ((a & CHAR) != BCD_ZERO) ahigh = AS; /* mark non-zero */ | |
MM (AS); } | |
while ((a & WM) == 0); | |
if (reason) break; /* address err? */ | |
if (ahigh < 0) { /* div? by zero */ | |
ind[IN_OVF] = 1; /* set ovf indic */ | |
qs = bsave = BS; /* quo, dividend */ | |
do { | |
b = M[bsave]; /* find end divd */ | |
PP (bsave); } /* marked by zone */ | |
while ((b & ZONE) == 0); | |
if (reason) break; /* address err? */ | |
if (ADDR_ERR (qs)) { /* address err? */ | |
reason = STOP_WRAP; /* address wrap? */ | |
break; } | |
div_sign (M[asave], b, qs - 1, bsave - 1); /* set signs */ | |
BS = (BS - 2) - (asave - (AS + 1)); /* final bs */ | |
break; } | |
bsave = BS + (asave - ahigh); /* end subdivd */ | |
qs = (BS - 2) - (ahigh - (AS + 1)); /* quo start */ | |
/* Divide loop - done with subroutines to keep the code clean. | |
In the loop, | |
asave = low order divisor | |
bsave = low order subdividend | |
qs = current quotient digit | |
*/ | |
do { | |
quo = 0; /* clear quo digit */ | |
if (ADDR_ERR (qs) || ADDR_ERR (bsave)) { | |
reason = STOP_WRAP; /* address wrap? */ | |
break; } | |
b = M[bsave]; /* save low divd */ | |
do { | |
t = div_sub (asave, bsave, ahigh); /* subtract */ | |
quo++; } /* incr quo digit */ | |
while (t == 0); /* until borrow */ | |
div_add (asave, bsave, ahigh); quo--; /* restore */ | |
M[qs] = (M[qs] & WM) | sum_table[quo]; /* store quo digit */ | |
bsave++; qs++; } /* adv divd, quo */ | |
while ((b & ZONE) == 0); /* until B sign */ | |
if (reason) break; /* address err? */ | |
/* At this point, | |
AS = high order divisor - 1 | |
asave = unit position of divisor | |
b = unit character of dividend | |
bsave = unit position of remainder + 1 | |
qs = unit position of quotient + 1 | |
*/ | |
div_sign (M[asave], b, qs - 1, bsave - 1); /* set signs */ | |
BS = qs - 2; /* BS = quo 10's pos */ | |
break; | |
/* Word mark instructions A check B check | |
SWM set WM on A char and B char fetch fetch | |
CWM clear WM on A char and B char fetch fetch | |
Instruction lengths: | |
1 chained | |
2,3 invalid A-address | |
4 one operand (B-address = A-address) | |
5,6 invalid B-address | |
7 two operands (SWM cannot be longer than 7) | |
8+ two operands + ignored modifier | |
*/ | |
case OP_SWM: /* set word mark */ | |
M[BS] = M[BS] | WM; /* set A field mark */ | |
M[AS] = M[AS] | WM; /* set B field mark */ | |
MM (AS); MM (BS); /* decr pointers */ | |
break; | |
case OP_CWM: /* clear word mark */ | |
M[BS] = M[BS] & ~WM; /* clear A field mark */ | |
M[AS] = M[AS] & ~WM; /* clear B field mark */ | |
MM (AS); MM (BS); /* decr pointers */ | |
break; | |
/* Clear storage instruction A check B check | |
CS clear from B down to nearest hundreds if branch fetch | |
address | |
Instruction lengths: | |
1 chained | |
2,3 invalid A-address and B-address | |
4 one operand (B-address = A-address) | |
5,6 invalid B-address | |
7 branch | |
8+ one operand, branch ignored | |
*/ | |
case OP_CS: /* clear storage */ | |
t = (BS / 100) * 100; /* lower bound */ | |
while (BS >= t) M[BS--] = 0; /* clear region */ | |
if (BS < 0) BS = BS + MEMSIZE; /* wrap if needed */ | |
if (ilnt == 7) { BRANCH; } /* branch variant? */ | |
break; | |
/* Modify address instruction A check B check | |
MA add A addr and B addr, store at B addr fetch fetch | |
Instruction lengths: | |
1 chained | |
2,3 invalid A-address and B-address | |
4 self (B-address = A-address) | |
5,6 invalid B-address | |
7 normal | |
8+ normal + ignored modifier | |
*/ | |
case OP_MA: /* modify address */ | |
a = one_table[M[AS] & CHAR]; MM (AS); /* get A address */ | |
a = a + ten_table[M[AS] & CHAR]; MM (AS); | |
a = a + hun_table[M[AS] & CHAR]; MM (AS); | |
b = one_table[M[BS] & CHAR]; MM (BS); /* get B address */ | |
b = b + ten_table[M[BS] & CHAR]; MM (BS); | |
b = b + hun_table[M[BS] & CHAR]; MM (BS); | |
t = ((a + b) & INDEXMASK) % MAXMEMSIZE; /* compute sum */ | |
M[BS + 3] = (M[BS + 3] & WM) | store_addr_u (t); | |
M[BS + 2] = (M[BS + 2] & (WM + ZONE)) | store_addr_t (t); | |
M[BS + 1] = (M[BS + 1] & WM) | store_addr_h (t); | |
if (((a % 4000) + (b % 4000)) >= 4000) BS = BS + 2; /* carry? */ | |
break; | |
/* Store address instructions A-check B-check | |
SAR store A* at A addr fetch | |
SBR store B* at A addr fetch | |
Instruction lengths: | |
1 chained | |
2,3 invalid A-address | |
4 normal | |
5+ B-address overwritten from instruction; | |
invalid address ignored | |
*/ | |
case OP_SAR: case OP_SBR: /* store A, B reg */ | |
M[AS] = (M[AS] & WM) | store_addr_u (BS); MM (AS); | |
M[AS] = (M[AS] & WM) | store_addr_t (BS); MM (AS); | |
M[AS] = (M[AS] & WM) | store_addr_h (BS); MM (AS); | |
break; | |
/* NOP - no validity checking, all instructions length ok */ | |
case OP_NOP: /* nop */ | |
break; | |
/* HALT - unless length = 4 (branch), no validity checking; all lengths ok */ | |
case OP_H: /* halt */ | |
if (ilnt == 4) hb_pend = 1; /* set pending branch */ | |
reason = STOP_HALT; /* stop simulator */ | |
saved_IS = IS; /* commit instruction */ | |
break; | |
default: | |
reason = STOP_NXI; /* unimplemented */ | |
break; } /* end switch */ | |
} /* end while */ | |
/* Simulation halted */ | |
as_err = ADDR_ERR (AS); /* get addr err flags */ | |
bs_err = ADDR_ERR (BS); | |
AS = AS & ADDRMASK; /* clean addresses */ | |
BS = BS & ADDRMASK; | |
pcq_r->qptr = pcq_p; /* update pc q ptr */ | |
return reason; | |
} /* end sim_instr */ | |
/* store addr_x - convert address to BCD character in x position | |
Inputs: | |
addr = address to convert | |
Outputs: | |
char = converted address character | |
*/ | |
int32 store_addr_h (int32 addr) | |
{ | |
int32 thous; | |
thous = (addr / 1000) & 03; | |
return bin_to_bcd[(addr % 1000) / 100] | (thous << V_ZONE); | |
} | |
int32 store_addr_t (int32 addr) | |
{ | |
return bin_to_bcd[(addr % 100) / 10]; | |
} | |
int32 store_addr_u (int32 addr) | |
{ | |
int32 thous; | |
thous = (addr / 1000) & 014; | |
return bin_to_bcd[addr % 10] | (thous << (V_ZONE - 2)); | |
} | |
/* div_add - add string for divide */ | |
int32 div_add (int32 ap, int32 bp, int32 aend) | |
{ | |
int32 a, b, c, r; | |
c = 0; /* init carry */ | |
do { a = M[ap]; b = M[bp]; /* get operands */ | |
r = bcd_to_bin[b & DIGIT] + /* sum digits + c */ | |
bcd_to_bin[a & DIGIT] + c; | |
c = (r >= 10); /* set carry out */ | |
M[bp] = sum_table[r]; /* store result */ | |
ap--; bp--; } | |
while (ap >= aend); | |
return c; | |
} | |
/* div_sub - substract string for divide */ | |
int32 div_sub (int32 ap, int32 bp, int32 aend) | |
{ | |
int32 a, b, c, r; | |
c = 0; /* init borrow */ | |
do { a = M[ap]; b = M[bp]; /* get operands */ | |
r = bcd_to_bin[b & DIGIT] - /* a - b - borrow */ | |
bcd_to_bin[a & DIGIT] - c; | |
c = (r < 0); /* set borrow out */ | |
M[bp] = sum_table[r + 10]; /* store result */ | |
ap--; bp--; } | |
while (ap >= aend); | |
b = M[bp] & CHAR; /* borrow position */ | |
if (b != BCD_ZERO) { /* non-zero? */ | |
r = bcd_to_bin[b & DIGIT] - c; /* subtract borrow */ | |
M[bp] = sum_table[r]; /* store result */ | |
return 0; } /* subtract worked */ | |
return c; /* return borrow */ | |
} | |
/* div_sign - set signs for divide */ | |
void div_sign (int32 dvrc, int32 dvdc, int32 qp, int32 rp) | |
{ | |
int32 sign = dvrc & ZONE; /* divisor sign */ | |
M[rp] = M[rp] | ZONE; /* assume rem pos */ | |
if (sign == BBIT) M[rp] = M[rp] & ~ABIT; /* if dvr -, rem - */ | |
M[qp] = M[qp] | ZONE; /* assume quo + */ | |
if (((dvdc & ZONE) == BBIT) ^ (sign == BBIT)) /* dvr,dvd diff? */ | |
M[qp] = M[qp] & ~ABIT; /* make quo - */ | |
return; | |
} | |
/* iomod - check on I/O modifiers | |
Inputs: | |
ilnt = instruction length | |
mod = modifier character | |
tptr = pointer to table of modifiers, end is -1 | |
Output: | |
status = SCPE_OK if ok, STOP_INVM if invalid | |
*/ | |
t_stat iomod (int32 ilnt, int32 mod, const int32 *tptr) | |
{ | |
if ((ilnt != 2) && (ilnt != 5) && (ilnt < 8)) return SCPE_OK; | |
if (tptr == NULL) return STOP_INVM; | |
do { if (mod == *tptr++) return SCPE_OK; } | |
while (*tptr >= 0); | |
return STOP_INVM; | |
} | |
/* iodisp - dispatch load or move to I/O routine | |
Inputs: | |
dev = device number | |
unit = unit number | |
flag = move (MD_NORM) vs load (MD_WM) | |
mod = modifier | |
*/ | |
t_stat iodisp (int32 dev, int32 unit, int32 flag, int32 mod) | |
{ | |
if (dev == IO_INQ) return inq_io (flag, mod); /* inq terminal? */ | |
if (dev == IO_DP) return dp_io (unit, flag, mod); /* disk pack? */ | |
if (dev == IO_MT) return mt_io (unit, flag, mod); /* magtape? */ | |
if (dev == IO_MTB) { /* binary? */ | |
if (flag == MD_WM) return STOP_INVM; /* invalid */ | |
return mt_io (unit, MD_BIN, mod); } | |
return STOP_NXD; /* not implemented */ | |
} | |
/* Reset routine */ | |
t_stat cpu_reset (DEVICE *dptr) | |
{ | |
int32 i; | |
for (i = 0; i < 64; i++) ind[i] = 0; /* clr indicators */ | |
ind[IN_UNC] = 1; /* ind[0] always on */ | |
AS = 0; as_err = 1; /* clear AS */ | |
BS = 0; bs_err = 1; /* clear BS */ | |
D = 0; /* clear D */ | |
hb_pend = 0; /* no halt br */ | |
pcq_r = find_reg ("ISQ", NULL, dptr); | |
if (pcq_r) pcq_r->qptr = 0; | |
else return SCPE_IERR; | |
sim_brk_types = sim_brk_dflt = SWMASK ('E'); | |
return SCPE_OK; | |
} | |
/* Memory examine */ | |
t_stat cpu_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw) | |
{ | |
if (addr >= MEMSIZE) return SCPE_NXM; | |
if (vptr != NULL) *vptr = M[addr] & (WM + CHAR); | |
return SCPE_OK; | |
} | |
/* Memory deposit */ | |
t_stat cpu_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw) | |
{ | |
if (addr >= MEMSIZE) return SCPE_NXM; | |
M[addr] = val & (WM + CHAR); | |
return SCPE_OK; | |
} | |
/* Memory size change */ | |
t_stat cpu_set_size (UNIT *uptr, int32 val, char *cptr, void *desc) | |
{ | |
int32 mc = 0; | |
uint32 i; | |
if ((val <= 0) || (val > MAXMEMSIZE) || ((val % 1000) != 0)) | |
return SCPE_ARG; | |
for (i = val; i < MEMSIZE; i++) mc = mc | M[i]; | |
if ((mc != 0) && (!get_yn ("Really truncate memory [N]?", FALSE))) | |
return SCPE_OK; | |
MEMSIZE = val; | |
for (i = MEMSIZE; i < MAXMEMSIZE; i++) M[i] = 0; | |
if (MEMSIZE > 4000) cpu_unit.flags = cpu_unit.flags | MA; | |
else cpu_unit.flags = cpu_unit.flags & ~MA; | |
return SCPE_OK; | |
} |