| /* i1620_cpu.c: IBM 1620 CPU simulator | |
| Copyright (c) 2002-2008, 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. | |
| This CPU module incorporates code and comments from the 1620 simulator by | |
| Geoff Kuenning, with his permission. | |
| 28-May-06 RMS Fixed bug in cpu history Peter Schorn) | |
| 22-Sep-05 RMS Fixed declarations (Sterling Garwood) | |
| 16-Aug-05 RMS Fixed C++ declaration and cast problems | |
| 07-Nov-04 RMS Added instruction history | |
| 26-Mar-04 RMS Fixed warnings with -std=c99 | |
| 02-Nov-03 RMS Fixed bug in branch digit (Dave Babcock) | |
| 21-Aug-03 RMS Fixed bug in immediate index add (Michael Short) | |
| 25-Apr-03 RMS Changed t_addr to uint32 throughout | |
| 18-Oct-02 RMS Fixed bugs in invalid result testing (Hans Pufal) | |
| The simulated register state for the IBM 1620 is: | |
| 1620 sim comment | |
| IR1 [PC] program counter | |
| IR2 instruction register 2 (subroutine return address) | |
| OR1 [QAR] Q address | |
| OR2 [PAR] P address | |
| PR1 manual save address | |
| ind[0:99] indicators | |
| Additional internal registers OR3, PR2, and PR3 are not simulated. | |
| The IBM 1620 is a fixed instruction length, variable data length, decimal | |
| data system. Memory consists of 20000 - 60000 BCD digits, each containing | |
| four bits of data and a flag. There are no general registers; all | |
| instructions are memory to memory. | |
| The 1620 uses a fixed, 12 digit instruction format: | |
| oo ppppp qqqqq | |
| where | |
| oo = opcode | |
| ppppp = P (usually destination) address | |
| qqqqq = Q (usually source) address | |
| Immediate instructions use the qqqqq field as the second operand. | |
| The 1620 Model 1 uses table lookups for add and multiply; for that reason, | |
| it was nicknamed CADET (Can't Add, Doesn't Even Try). The Model 2 does | |
| adds in hardware and uses the add table memory for index registers. | |
| This routine is the instruction decode routine for the IBM 1620. | |
| 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 1620 has no interrupt structure. | |
| 3. Non-existent memory. On the 1620, all memory references | |
| are modulo the memory size. | |
| 4. Adding I/O devices. These modules must be modified: | |
| i1620_cpu.c add iodisp table entry | |
| i1620_sys.c add sim_devices table entry | |
| */ | |
| #include "i1620_defs.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_PC | |
| #define HIST_PC 0x40000000 | |
| #define HIST_MIN 64 | |
| #define HIST_MAX 65536 | |
| typedef struct { | |
| uint16 vld; | |
| uint16 pc; | |
| uint8 inst[INST_LEN]; | |
| } InstHistory; | |
| uint8 M[MAXMEMSIZE] = { 0 }; /* main memory */ | |
| uint32 saved_PC = 0; /* saved PC */ | |
| uint32 IR2 = 1; /* inst reg 2 */ | |
| uint32 PAR = 0; /* P address */ | |
| uint32 QAR = 0; /* Q address */ | |
| uint32 PR1 = 1; /* proc reg 1 */ | |
| uint32 iae = 1; /* ind addr enb */ | |
| uint32 idxe = 0; /* index enable */ | |
| uint32 idxb = 0; /* index band */ | |
| uint32 io_stop = 1; /* I/O stop */ | |
| uint32 ar_stop = 1; /* arith stop */ | |
| int32 ind_max = 16; /* iadr nest limit */ | |
| uint16 pcq[PCQ_SIZE] = { 0 }; /* PC queue */ | |
| int32 pcq_p = 0; /* PC queue ptr */ | |
| REG *pcq_r = NULL; /* PC queue reg ptr */ | |
| int32 hst_p = 0; /* history pointer */ | |
| int32 hst_lnt = 0; /* history length */ | |
| InstHistory *hst = NULL; /* instruction history */ | |
| uint8 ind[NUM_IND] = { 0 }; /* indicators */ | |
| 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_opt1 (UNIT *uptr, int32 val, char *cptr, void *desc); | |
| t_stat cpu_set_opt2 (UNIT *uptr, int32 val, char *cptr, void *desc); | |
| t_stat cpu_set_model (UNIT *uptr, int32 val, char *cptr, void *desc); | |
| t_stat cpu_set_size (UNIT *uptr, int32 val, char *cptr, void *desc); | |
| t_stat cpu_set_save (UNIT *uptr, int32 val, char *cptr, void *desc); | |
| t_stat cpu_set_table (UNIT *uptr, int32 val, char *cptr, void *desc); | |
| t_stat cpu_set_hist (UNIT *uptr, int32 val, char *cptr, void *desc); | |
| t_stat cpu_show_hist (FILE *st, UNIT *uptr, int32 val, void *desc); | |
| int32 get_2d (uint32 ad); | |
| t_stat get_addr (uint32 alast, int32 lnt, t_bool indexok, uint32 *addr); | |
| t_stat cvt_addr (uint32 alast, int32 lnt, t_bool signok, int32 *val); | |
| t_stat get_idx (uint32 aidx); | |
| t_stat xmt_field (uint32 d, uint32 s, uint32 skp); | |
| t_stat xmt_record (uint32 d, uint32 s, t_bool cpy); | |
| t_stat xmt_index (uint32 d, uint32 s); | |
| t_stat xmt_divd (uint32 d, uint32 s); | |
| t_stat xmt_tns (uint32 d, uint32 s); | |
| t_stat xmt_tnf (uint32 d, uint32 s); | |
| t_stat add_field (uint32 d, uint32 s, t_bool sub, t_bool sto, uint32 skp, int32 *sta); | |
| uint32 add_one_digit (uint32 dst, uint32 src, uint32 *cry); | |
| t_stat mul_field (uint32 mpc, uint32 mpy); | |
| t_stat mul_one_digit (uint32 mpyd, uint32 mpcp, uint32 prop, uint32 last); | |
| t_stat div_field (uint32 dvd, uint32 dvr, int32 *ez); | |
| t_stat div_one_digit (uint32 dvd, uint32 dvr, uint32 max, uint32 *quod, uint32 *quop); | |
| t_stat oct_to_dec (uint32 tbl, uint32 s); | |
| t_stat dec_to_oct (uint32 d, uint32 tbl, int32 *ez); | |
| t_stat or_field (uint32 d, uint32 s); | |
| t_stat and_field (uint32 d, uint32 s); | |
| t_stat xor_field (uint32 d, uint32 s); | |
| t_stat com_field (uint32 d, uint32 s); | |
| void upd_ind (void); | |
| extern t_stat tty (uint32 op, uint32 pa, uint32 f0, uint32 f1); | |
| extern t_stat ptp (uint32 op, uint32 pa, uint32 f0, uint32 f1); | |
| extern t_stat ptr (uint32 op, uint32 pa, uint32 f0, uint32 f1); | |
| extern t_stat cdp (uint32 op, uint32 pa, uint32 f0, uint32 f1); | |
| extern t_stat cdr (uint32 op, uint32 pa, uint32 f0, uint32 f1); | |
| extern t_stat dp (uint32 op, uint32 pa, uint32 f0, uint32 f1); | |
| extern t_stat lpt (uint32 op, uint32 pa, uint32 f0, uint32 f1); | |
| extern t_stat btp (uint32 op, uint32 pa, uint32 f0, uint32 f1); | |
| extern t_stat btr (uint32 op, uint32 pa, uint32 f0, uint32 f1); | |
| extern t_stat fp_add (uint32 d, uint32 s, t_bool sub); | |
| extern t_stat fp_mul (uint32 d, uint32 s); | |
| extern t_stat fp_div (uint32 d, uint32 s); | |
| extern t_stat fp_fsl (uint32 d, uint32 s); | |
| extern t_stat fp_fsr (uint32 d, uint32 s); | |
| /* 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+MI_STD, MAXMEMSIZE) }; | |
| REG cpu_reg[] = { | |
| { DRDATA (PC, saved_PC, 16), PV_LEFT }, | |
| { DRDATA (IR2, IR2, 16), PV_LEFT }, | |
| { DRDATA (PR1, PR1, 16), PV_LEFT }, | |
| { DRDATA (PAR, PAR, 16), PV_LEFT + REG_RO }, | |
| { DRDATA (QAR, QAR, 16), PV_LEFT + REG_RO }, | |
| { FLDATA (SW1, ind[IN_SW1], 0) }, | |
| { FLDATA (SW2, ind[IN_SW2], 0) }, | |
| { FLDATA (SW3, ind[IN_SW3], 0) }, | |
| { FLDATA (SW4, ind[IN_SW4], 0) }, | |
| { FLDATA (HP, ind[IN_HP], 0) }, | |
| { FLDATA (EZ, ind[IN_EZ], 0) }, | |
| { FLDATA (OVF, ind[IN_OVF], 0) }, | |
| { FLDATA (EXPCHK, ind[IN_EXPCHK], 0) }, | |
| { FLDATA (RDCHK, ind[IN_RDCHK], 0) }, | |
| { FLDATA (WRCHK, ind[IN_WRCHK], 0) }, | |
| { FLDATA (ARSTOP, ar_stop, 0) }, | |
| { FLDATA (IOSTOP, io_stop, 0) }, | |
| { BRDATA (IND, ind, 10, 1, NUM_IND) }, | |
| { FLDATA (IAE, iae, 0) }, | |
| { FLDATA (IDXE, idxe, 0) }, | |
| { FLDATA (IDXB, idxb, 0) }, | |
| { DRDATA (INDMAX, ind_max, 16), REG_NZ + PV_LEFT }, | |
| { BRDATA (PCQ, pcq, 10, 14, PCQ_SIZE), REG_RO+REG_CIRC }, | |
| { ORDATA (PCQP, pcq_p, 6), REG_HRO }, | |
| { ORDATA (WRU, sim_int_char, 8) }, | |
| { NULL } | |
| }; | |
| MTAB cpu_mod[] = { | |
| { IF_IA, IF_IA, "IA", "IA", &cpu_set_opt1 }, | |
| { IF_IA, 0, "no IA", "NOIA", &cpu_set_opt1 }, | |
| { IF_EDT, IF_EDT, "EDT", "EDT", &cpu_set_opt1 }, | |
| { IF_EDT, 0, "no EDT", "NOEDT", &cpu_set_opt1 }, | |
| { IF_DIV, IF_DIV, "DIV", "DIV", &cpu_set_opt1 }, | |
| { IF_DIV, 0, "no DIV", "NODIV", &cpu_set_opt1 }, | |
| { IF_FP, IF_FP, "FP", "FP", NULL }, | |
| { IF_FP, 0, "no FP", "NOFP", NULL }, | |
| { IF_BIN, IF_BIN, "BIN", "BIN", &cpu_set_opt2 }, | |
| { IF_BIN, 0, "no BIN", "NOBIN", &cpu_set_opt2 }, | |
| { IF_IDX, IF_IDX, "IDX", "IDX", &cpu_set_opt2 }, | |
| { IF_IDX, 0, "no IDX", "NOIDX", &cpu_set_opt2 }, | |
| { IF_MII, IF_MII, "Model 2", "MOD2", &cpu_set_model }, | |
| { IF_MII, 0, "Model 1", "MOD1", &cpu_set_model }, | |
| { UNIT_MSIZE, 20000, NULL, "20K", &cpu_set_size }, | |
| { UNIT_MSIZE, 40000, NULL, "40K", &cpu_set_size }, | |
| { UNIT_MSIZE, 60000, NULL, "60K", &cpu_set_size }, | |
| { UNIT_MSIZE, 0, NULL, "SAVE", &cpu_set_save }, | |
| { UNIT_MSIZE, 0, NULL, "TABLE", &cpu_set_table }, | |
| { MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "HISTORY", "HISTORY", | |
| &cpu_set_hist, &cpu_show_hist }, | |
| { 0 } | |
| }; | |
| DEVICE cpu_dev = { | |
| "CPU", &cpu_unit, cpu_reg, cpu_mod, | |
| 1, 10, 18, 1, 16, 5, | |
| &cpu_ex, &cpu_dep, &cpu_reset, | |
| NULL, NULL, NULL | |
| }; | |
| /* Instruction table */ | |
| const int32 op_table[100] = { | |
| 0, /* 0 */ | |
| IF_FP + IF_VPA + IF_VQA, /* FADD */ | |
| IF_FP + IF_VPA + IF_VQA, /* FSUB */ | |
| IF_FP + IF_VPA + IF_VQA, /* FMUL */ | |
| 0, | |
| IF_FP + IF_VPA + IF_VQA, /* FSL */ | |
| IF_FP + IF_MII + IF_VPA + IF_VQA, /* TFL */ | |
| IF_FP + IF_MII + IF_VPA + IF_VQA, /* BTFL */ | |
| IF_FP + IF_VPA + IF_VQA, /* FSR */ | |
| IF_FP + IF_VPA + IF_VQA, /* FDV */ | |
| IF_MII + IF_VPA + IF_IMM, /* 10: BTAM */ | |
| IF_VPA + IF_IMM, /* AM */ | |
| IF_VPA + IF_IMM, /* SM */ | |
| IF_VPA + IF_IMM, /* MM */ | |
| IF_VPA + IF_IMM, /* CM */ | |
| IF_VPA + IF_IMM, /* TDM */ | |
| IF_VPA + IF_IMM, /* TFM */ | |
| IF_VPA + IF_IMM, /* BTM */ | |
| IF_DIV + IF_VPA + IF_IMM, /* LDM */ | |
| IF_DIV + IF_VPA + IF_IMM, /* DM */ | |
| IF_MII + IF_VPA + IF_VQA, /* 20: BTA */ | |
| IF_VPA + IF_VQA, /* A */ | |
| IF_VPA + IF_VQA, /* S */ | |
| IF_VPA + IF_VQA, /* M */ | |
| IF_VPA + IF_VQA, /* C */ | |
| IF_VPA + IF_VQA, /* TD */ | |
| IF_VPA + IF_VQA, /* TF */ | |
| IF_VPA + IF_VQA, /* BT */ | |
| IF_DIV + IF_VPA + IF_VQA, /* LD */ | |
| IF_DIV + IF_VPA + IF_VQA, /* D */ | |
| IF_MII + IF_VPA + IF_VQA, /* 30: TRNM */ | |
| IF_VPA + IF_VQA, /* TR */ | |
| IF_VPA, /* SF */ | |
| IF_VPA, /* CF */ | |
| IF_VPA, /* K */ | |
| IF_VPA, /* DN */ | |
| IF_VPA, /* RN */ | |
| IF_VPA, /* RA */ | |
| IF_VPA, /* WN */ | |
| IF_VPA, /* WA */ | |
| 0, /* 40 */ | |
| 0, /* NOP */ | |
| 0, /* BB */ | |
| IF_VPA + IF_VQA, /* BD */ | |
| IF_VPA + IF_VQA, /* BNF */ | |
| IF_VPA + IF_VQA, /* BNR */ | |
| IF_VPA, /* BI */ | |
| IF_VPA, /* BNI */ | |
| 0, /* H */ | |
| IF_VPA, /* B */ | |
| 0, /* 50 */ | |
| 0, | |
| 0, | |
| 0, | |
| 0, | |
| IF_VPA + IF_VQA, /* BNG - disk sys */ | |
| 0, | |
| 0, | |
| 0, | |
| 0, | |
| IF_MII + IF_VPA, /* 60: BS */ | |
| IF_IDX + IF_VPA + IF_NQX, /* BX */ | |
| IF_IDX + IF_VPA + IF_IMM, /* BXM */ | |
| IF_IDX + IF_VPA + IF_NQX, /* BCX */ | |
| IF_IDX + IF_VPA + IF_IMM, /* BCXM */ | |
| IF_IDX + IF_VPA + IF_NQX, /* BLX */ | |
| IF_IDX + IF_VPA + IF_IMM, /* BLXM */ | |
| IF_IDX + IF_VPA + IF_NQX, /* BSX */ | |
| 0, | |
| 0, | |
| IF_IDX + IF_VPA + IF_VQA, /* 70: MA */ | |
| IF_EDT + IF_VPA + IF_VQA, /* MF */ | |
| IF_EDT + IF_VPA + IF_VQA, /* MF */ | |
| IF_EDT + IF_VPA + IF_VQA, /* TNF */ | |
| 0, | |
| 0, | |
| 0, | |
| 0, | |
| 0, | |
| 0, | |
| 0, /* 80 */ | |
| 0, | |
| 0, | |
| 0, | |
| 0, | |
| 0, | |
| 0, | |
| 0, | |
| 0, | |
| 0, | |
| IF_BIN + IF_VPA + IF_4QA, /* 90: BBT */ | |
| IF_BIN + IF_VPA + IF_4QA, /* BMK */ | |
| IF_BIN + IF_VPA + IF_VQA, /* ORF */ | |
| IF_BIN + IF_VPA + IF_VQA, /* ANDF */ | |
| IF_BIN + IF_VPA + IF_VQA, /* CPLF */ | |
| IF_BIN + IF_VPA + IF_VQA, /* EORF */ | |
| IF_BIN + IF_VPA + IF_VQA, /* OTD */ | |
| IF_BIN + IF_VPA + IF_VQA, /* DTO */ | |
| 0, | |
| 0 | |
| }; | |
| /* IO dispatch table */ | |
| t_stat (*iodisp[NUM_IO])(uint32 op, uint32 pa, uint32 f0, uint32 f1) = { | |
| NULL, &tty, &ptp, &ptr, &cdp, /* 00 - 09 */ | |
| &cdr, NULL, &dp, NULL, &lpt, | |
| NULL, NULL, NULL, NULL, NULL, /* 10 - 19 */ | |
| NULL, NULL, NULL, NULL, NULL, | |
| NULL, NULL, NULL, NULL, NULL, /* 20 - 29 */ | |
| NULL, NULL, NULL, NULL, NULL, | |
| NULL, NULL, &btp, &btr, NULL, /* 30 - 39 */ | |
| NULL, NULL, NULL, NULL, NULL, | |
| NULL, NULL, NULL, NULL, NULL, /* 40 - 49 */ | |
| NULL, NULL, NULL, NULL, NULL, | |
| NULL, NULL, NULL, NULL, NULL, /* 50 - 59 */ | |
| NULL, NULL, NULL, NULL, NULL, | |
| NULL, NULL, NULL, NULL, NULL, /* 60 - 69 */ | |
| NULL, NULL, NULL, NULL, NULL, | |
| NULL, NULL, NULL, NULL, NULL, /* 70 - 79 */ | |
| NULL, NULL, NULL, NULL, NULL, | |
| NULL, NULL, NULL, NULL, NULL, /* 80 - 89 */ | |
| NULL, NULL, NULL, NULL, NULL, | |
| NULL, NULL, NULL, NULL, NULL, /* 90 - 99 */ | |
| NULL, NULL, NULL, NULL, NULL | |
| }; | |
| /* Indicator table: -1 = illegal, +1 = resets when tested */ | |
| const int32 ind_table[NUM_IND] = { | |
| -1, 0, 0, 0, 0, -1, 1, 1, -1, 1, /* 00 - 09 */ | |
| -1, 0, 0, 0, 1, 1, 1, 1, -1, 0, /* 10 - 19 */ | |
| -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, /* 20 - 29 */ | |
| 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, /* 30 - 39 */ | |
| -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, /* 40 - 49 */ | |
| -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 50 - 59 */ | |
| -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 60 - 69 */ | |
| -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 70 - 79 */ | |
| -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80 - 89 */ | |
| -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 /* 90 - 99 */ | |
| }; | |
| /* Add table for 1620 Model 1 */ | |
| const uint8 std_add_table[ADD_TABLE_LEN] = { | |
| 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, | |
| 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, | |
| 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, | |
| 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, | |
| 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, | |
| 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, | |
| 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, | |
| 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, | |
| 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, | |
| 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 | |
| }; | |
| /* Add table for 1620 Model 2 ("hardware add") */ | |
| const uint8 sum_table[20] = { | |
| 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, | |
| 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19 | |
| }; | |
| /* Multiply table */ | |
| const uint8 std_mul_table[MUL_TABLE_LEN] = { | |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, | |
| 0, 0, 2, 0, 4, 0, 6, 0, 8, 0, | |
| 0, 0, 3, 0, 6, 0, 9, 0, 2, 1, | |
| 0, 0, 4, 0, 8, 0, 2, 1, 6, 1, | |
| 0, 0, 5, 0, 0, 1, 5, 1, 0, 2, | |
| 0, 0, 6, 0, 2, 1, 8, 1, 4, 2, | |
| 0, 0, 7, 0, 4, 1, 1, 2, 8, 2, | |
| 0, 0, 8, 0, 6, 1, 4, 2, 2, 3, | |
| 0, 0, 9, 0, 8, 1, 7, 2, 6, 3, | |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, | |
| 0, 1, 2, 1, 4, 1, 6, 1, 8, 1, | |
| 5, 1, 8, 1, 1, 2, 4, 2, 7, 2, | |
| 0, 2, 4, 2, 8, 2, 2, 3, 6, 3, | |
| 5, 2, 0, 3, 5, 3, 0, 4, 5, 4, | |
| 0, 3, 6, 3, 2, 4, 8, 4, 4, 5, | |
| 5, 3, 2, 4, 9, 4, 6, 5, 3, 6, | |
| 0, 4, 8, 4, 6, 5, 4, 6, 2, 7, | |
| 5, 4, 4, 5, 3, 6, 2, 7, 1, 8 | |
| }; | |
| #define BRANCH(x) PCQ_ENTRY; PC = (x) | |
| #define GET_IDXADDR(x) ((idxb? IDX_B: IDX_A) + ((x) * ADDR_LEN) + (ADDR_LEN - 1)) | |
| t_stat sim_instr (void) | |
| { | |
| uint32 PC, pla, qla, f0, f1; | |
| int32 i, t, idx, flags, sta, dev, op; | |
| t_stat reason; | |
| /* Restore saved state */ | |
| PC = saved_PC; | |
| if ((cpu_unit.flags & IF_IA) == 0) | |
| iae = 0; | |
| if ((cpu_unit.flags & IF_IDX) == 0) | |
| idxe = idxb = 0; | |
| upd_ind (); /* update indicators */ | |
| reason = 0; | |
| /* Main instruction fetch/decode loop */ | |
| while (reason == 0) { /* loop until halted */ | |
| saved_PC = PC; /* commit prev instr */ | |
| if (sim_interval <= 0) { /* check clock queue */ | |
| if ((reason = sim_process_event ())) | |
| break; | |
| } | |
| if (sim_brk_summ && sim_brk_test (PC, SWMASK ('E'))) { /* breakpoint? */ | |
| reason = STOP_IBKPT; /* stop simulation */ | |
| break; | |
| } | |
| sim_interval = sim_interval - 1; | |
| /* Instruction fetch and address decode */ | |
| if (PC & 1) { /* PC odd? */ | |
| reason = STOP_INVIAD; /* stop */ | |
| break; | |
| } | |
| op = get_2d (PC); /* get opcode */ | |
| if (op < 0) { /* invalid? */ | |
| reason = STOP_INVINS; | |
| break; | |
| } | |
| flags = op_table[op]; /* get op, flags */ | |
| if ((flags & ALLOPT) && /* need option? */ | |
| !(flags & ALLOPT & cpu_unit.flags)) { /* any set? */ | |
| reason = STOP_INVINS; /* no, error */ | |
| break; | |
| } | |
| pla = ADDR_A (PC, I_PL); /* P last addr */ | |
| qla = ADDR_A (PC, I_QL); /* Q last addr */ | |
| if (flags & IF_VPA) { /* need P? */ | |
| reason = get_addr (pla, 5, TRUE, &PAR); /* get P addr */ | |
| if (reason != SCPE_OK) /* stop if error */ | |
| break; | |
| } | |
| if (flags & (IF_VQA | IF_4QA | IF_NQX)) { /* need Q? */ | |
| reason = get_addr (qla, /* get Q addr */ | |
| ((flags & IF_4QA)? 4: 5), /* 4 or 5 digits */ | |
| ((flags & IF_NQX)? FALSE: TRUE), /* not or indexed */ | |
| &QAR); | |
| if (reason != SCPE_OK) { /* stop if invalid */ | |
| reason = reason + (STOP_INVQDG - STOP_INVPDG); | |
| break; | |
| } | |
| } | |
| else if (flags & IF_IMM) /* immediate? */ | |
| QAR = qla; | |
| if (hst_lnt) { /* history enabled? */ | |
| hst_p = (hst_p + 1); /* next entry */ | |
| if (hst_p >= hst_lnt) | |
| hst_p = 0; | |
| hst[hst_p].vld = 1; | |
| hst[hst_p].pc = PC; | |
| for (i = 0; i < INST_LEN; i++) | |
| hst[hst_p].inst[i] = M[(PC + i) % MEMSIZE]; | |
| } | |
| PC = PC + INST_LEN; /* advance PC */ | |
| switch (op) { /* case on op */ | |
| /* Transmit digit - P,Q are valid */ | |
| case OP_TD: | |
| case OP_TDM: | |
| M[PAR] = M[QAR] & (FLAG | DIGIT); /* move dig, flag */ | |
| break; | |
| /* Transmit field - P,Q are valid */ | |
| case OP_TF: | |
| case OP_TFM: | |
| reason = xmt_field (PAR, QAR, 1); /* xmit field */ | |
| break; | |
| /* Transmit record - P,Q are valid */ | |
| case OP_TR: | |
| reason = xmt_record (PAR, QAR, TRUE); /* xmit record */ | |
| break; | |
| /* Transmit record no record mark - P,Q are valid */ | |
| case OP_TRNM: | |
| reason = xmt_record (PAR, QAR, FALSE); /* xmit record but */ | |
| break; /* not rec mark */ | |
| /* Set flag - P is valid */ | |
| case OP_SF: | |
| M[PAR] = M[PAR] | FLAG; /* set flag on P */ | |
| break; | |
| /* Clear flag - P is valid */ | |
| case OP_CF: | |
| M[PAR] = M[PAR] & ~FLAG; /* clear flag on P */ | |
| break; | |
| /* Branch - P is valid */ | |
| case OP_B: | |
| BRANCH (PAR); /* branch to P */ | |
| break; | |
| /* Branch and transmit - P,Q are valid */ | |
| case OP_BT: | |
| case OP_BTM: | |
| reason = xmt_field (ADDR_S (PAR, 1), QAR, 1); /* xmit field to P-1 */ | |
| IR2 = PC; /* save PC */ | |
| BRANCH (PAR); /* branch to P */ | |
| break; | |
| /* Branch and transmit floating - P,Q are valid */ | |
| case OP_BTFL: | |
| reason = xmt_field (ADDR_S (PAR, 1), QAR, 3); /* skip 3 flags */ | |
| IR2 = PC; /* save PC */ | |
| BRANCH (PAR); /* branch to P */ | |
| break; | |
| /* Branch and transmit address - P,Q are valid */ | |
| case OP_BTA: | |
| case OP_BTAM: | |
| reason = xmt_field (ADDR_S (PAR, 1), QAR, 4); /* skip 4 flags */ | |
| IR2 = PC; /* save PC */ | |
| BRANCH (PAR); /* branch to P */ | |
| break; | |
| /* Branch back */ | |
| case OP_BB: | |
| if (PR1 != 1) { /* PR1 valid? */ | |
| BRANCH (PR1); /* return to PR1 */ | |
| PR1 = 1; /* invalidate */ | |
| } | |
| else if (IR2 != 1) { /* IR2 valid? */ | |
| BRANCH (IR2); /* return to IR2 */ | |
| IR2 = 1; /* invalidate */ | |
| } | |
| else reason = STOP_INVRTN; /* MAR check */ | |
| break; | |
| /* Branch on digit (not zero) - P,Q are valid */ | |
| case OP_BD: | |
| if ((M[QAR] & DIGIT) != 0) { /* digit != 0? */ | |
| BRANCH (PAR); /* branch */ | |
| } | |
| break; | |
| /* Branch no flag - P,Q are valid */ | |
| case OP_BNF: | |
| if ((M[QAR] & FLAG) == 0) { /* flag == 0? */ | |
| BRANCH (PAR); /* branch */ | |
| } | |
| break; | |
| /* Branch no record mark (8-2 not set) - P,Q are valid */ | |
| case OP_BNR: | |
| if ((M[QAR] & REC_MARK) != REC_MARK) { /* not rec mark? */ | |
| BRANCH (PAR); /* branch */ | |
| } | |
| break; | |
| /* Branch no group mark - P,Q are valid */ | |
| case OP_BNG: | |
| if ((M[QAR] & DIGIT) != GRP_MARK) { /* not grp mark? */ | |
| BRANCH (PAR); /* branch */ | |
| } | |
| break; | |
| /* Branch (no) indicator - P is valid */ | |
| case OP_BI: | |
| case OP_BNI: | |
| upd_ind (); /* update indicators */ | |
| t = get_2d (ADDR_A (saved_PC, I_BR)); /* get ind number */ | |
| if ((t < 0) || (ind_table[t] < 0)) { /* not valid? */ | |
| reason = STOP_INVIND; /* stop */ | |
| break; | |
| } | |
| if ((ind[t] != 0) ^ (op == OP_BNI)) { /* ind value correct? */ | |
| BRANCH (PAR); /* branch */ | |
| } | |
| if (ind_table[t] > 0) /* reset if needed */ | |
| ind[t] = 0; | |
| break; | |
| /* Add/subtract/compare - P,Q are valid */ | |
| case OP_A: | |
| case OP_AM: | |
| reason = add_field (PAR, QAR, FALSE, TRUE, 0, &sta); /* add, store */ | |
| if (sta == ADD_CARRY) /* cout => ovflo */ | |
| ind[IN_OVF] = 1; | |
| if (ar_stop && ind[IN_OVF]) | |
| reason = STOP_OVERFL; | |
| break; | |
| case OP_S: | |
| case OP_SM: | |
| reason = add_field (PAR, QAR, TRUE, TRUE, 0, &sta); /* sub, store */ | |
| if (sta == ADD_CARRY) /* cout => ovflo */ | |
| ind[IN_OVF] = 1; | |
| if (ar_stop && ind[IN_OVF]) | |
| reason = STOP_OVERFL; | |
| break; | |
| case OP_C: | |
| case OP_CM: | |
| reason = add_field (PAR, QAR, TRUE, FALSE, 0, &sta); /* sub, nostore */ | |
| if (sta == ADD_CARRY) /* cout => ovflo */ | |
| ind[IN_OVF] = 1; | |
| if (ar_stop && ind[IN_OVF]) | |
| reason = STOP_OVERFL; | |
| break; | |
| /* Multiply - P,Q are valid */ | |
| case OP_M: | |
| case OP_MM: | |
| reason = mul_field (PAR, QAR); /* multiply */ | |
| break; | |
| /* IO instructions - P is valid */ | |
| case OP_RA: | |
| case OP_WA: | |
| if ((PAR & 1) == 0) { /* P even? */ | |
| reason = STOP_INVEAD; /* stop */ | |
| break; | |
| } | |
| case OP_K: | |
| case OP_DN: | |
| case OP_RN: | |
| case OP_WN: | |
| dev = get_2d (ADDR_A (saved_PC, I_IO)); /* get IO dev */ | |
| f0 = M[ADDR_A (saved_PC, I_CTL)] & DIGIT; /* get function */ | |
| f1 = M[ADDR_A (saved_PC, I_CTL + 1)] & DIGIT; | |
| if ((dev < 0) || (iodisp[dev] == NULL)) /* undefined dev? */ | |
| reason = STOP_INVIO; /* stop */ | |
| else reason = iodisp[dev] (op, PAR, f0, f1); /* call device */ | |
| break; | |
| /* Divide special feature instructions */ | |
| case OP_LD: | |
| case OP_LDM: | |
| for (i = 0; i < PROD_AREA_LEN; i++) /* clear prod area */ | |
| M[PROD_AREA + i] = 0; | |
| t = M[QAR] & FLAG; /* save Q sign */ | |
| reason = xmt_divd (PAR, QAR); /* xmit dividend */ | |
| M[PROD_AREA + PROD_AREA_LEN - 1] |= t; /* set sign */ | |
| break; | |
| /* Divide - P,Q are valid */ | |
| case OP_D: | |
| case OP_DM: | |
| reason = div_field (PAR, QAR, &t); /* divide */ | |
| ind[IN_EZ] = t; /* set indicator */ | |
| if ((reason == STOP_OVERFL) && !ar_stop) /* ovflo stop? */ | |
| reason = SCPE_OK; /* no */ | |
| break; | |
| /* Edit special feature instructions */ | |
| /* Move flag - P,Q are valid */ | |
| case OP_MF: | |
| M[PAR] = (M[PAR] & ~FLAG) | (M[QAR] & FLAG); /* copy Q flag */ | |
| M[QAR] = M[QAR] & ~FLAG; /* clr Q flag */ | |
| break; | |
| /* Transmit numeric strip - P,Q are valid, P is source */ | |
| case OP_TNS: | |
| if ((PAR & 1) == 0) { /* P must be odd */ | |
| reason = STOP_INVEAD; | |
| break; | |
| } | |
| reason = xmt_tns (QAR, PAR); /* xmit and strip */ | |
| break; | |
| /* Transmit numeric fill - P,Q are valid */ | |
| case OP_TNF: | |
| if ((PAR & 1) == 0) { /* P must be odd */ | |
| reason = STOP_INVEAD; | |
| break; | |
| } | |
| reason = xmt_tnf (PAR, QAR); /* xmit and strip */ | |
| break; | |
| /* Index special feature instructions */ | |
| /* Move address - P,Q are valid */ | |
| case OP_MA: | |
| for (i = 0; i < ADDR_LEN; i++) { /* move 5 digits */ | |
| M[PAR] = (M[PAR] & FLAG) | (M[QAR] & DIGIT); | |
| MM (PAR); MM (QAR); | |
| } | |
| break; | |
| /* Branch load index - P,Q are valid, Q not indexed */ | |
| case OP_BLX: | |
| case OP_BLXM: | |
| idx = get_idx (ADDR_A (saved_PC, I_QL - 1)); /* get index */ | |
| if (idx < 0) { /* disabled? */ | |
| reason = STOP_INVIDX; /* stop */ | |
| break; | |
| } | |
| xmt_index (GET_IDXADDR (idx), QAR); /* copy Q to idx */ | |
| BRANCH (PAR); /* branch to P */ | |
| break; | |
| /* Branch store index - P,Q are valid, Q not indexed */ | |
| case OP_BSX: | |
| idx = get_idx (ADDR_A (saved_PC, I_QL - 1)); /* get index */ | |
| if (idx < 0) { /* disabled? */ | |
| reason = STOP_INVIDX; /* stop */ | |
| break; | |
| } | |
| xmt_index (QAR, GET_IDXADDR (idx)); /* copy idx to Q */ | |
| BRANCH (PAR); /* branch to P */ | |
| break; | |
| /* Branch and modify index - P,Q are valid, Q not indexed */ | |
| case OP_BX: | |
| idx = get_idx (ADDR_A (saved_PC, I_QL - 1)); /* get index */ | |
| if (idx < 0) { /* disabled? */ | |
| reason = STOP_INVIDX; /* stop */ | |
| break; | |
| } | |
| reason = add_field (GET_IDXADDR (idx), QAR, FALSE, TRUE, 0, &sta); | |
| if (ar_stop && ind[IN_OVF]) | |
| reason = STOP_OVERFL; | |
| BRANCH (PAR); /* branch to P */ | |
| break; | |
| case OP_BXM: | |
| idx = get_idx (ADDR_A (saved_PC, I_QL - 1)); /* get index */ | |
| if (idx < 0) { /* disabled? */ | |
| reason = STOP_INVIDX; /* stop */ | |
| break; | |
| } | |
| reason = add_field (GET_IDXADDR (idx), QAR, FALSE, TRUE, 3, &sta); | |
| if (ar_stop && ind[IN_OVF]) | |
| reason = STOP_OVERFL; | |
| BRANCH (PAR); /* branch to P */ | |
| break; | |
| /* Branch conditionally and modify index - P,Q are valid, Q not indexed */ | |
| case OP_BCX: | |
| idx = get_idx (ADDR_A (saved_PC, I_QL - 1)); /* get index */ | |
| if (idx < 0) { /* disabled? */ | |
| reason = STOP_INVIDX; /* stop */ | |
| break; | |
| } | |
| reason = add_field (GET_IDXADDR (idx), QAR, FALSE, TRUE, 0, &sta); | |
| if (ar_stop && ind[IN_OVF]) | |
| reason = STOP_OVERFL; | |
| if ((ind[IN_EZ] == 0) && (sta == ADD_NOCRY)) { /* ~z, ~c, ~sign chg? */ | |
| BRANCH (PAR); /* branch */ | |
| } | |
| break; | |
| case OP_BCXM: | |
| idx = get_idx (ADDR_A (saved_PC, I_QL - 1)); /* get index */ | |
| if (idx < 0) { /* disabled? */ | |
| reason = STOP_INVIDX; /* stop */ | |
| break; | |
| } | |
| reason = add_field (GET_IDXADDR (idx), QAR, FALSE, TRUE, 3, &sta); | |
| if (ar_stop && ind[IN_OVF]) | |
| reason = STOP_OVERFL; | |
| if ((ind[IN_EZ] == 0) && (sta == ADD_NOCRY)) { /* ~z, ~c, ~sign chg? */ | |
| BRANCH (PAR); /* branch */ | |
| } | |
| break; | |
| /* Branch and select - P is valid */ | |
| case OP_BS: | |
| t = M[ADDR_A (saved_PC, I_SEL)] & DIGIT; /* get select */ | |
| switch (t) { /* case on select */ | |
| case 0: | |
| idxe = idxb = 0; /* indexing off */ | |
| break; | |
| case 1: | |
| idxe = 1; idxb = 0; /* index band A */ | |
| break; | |
| case 2: | |
| idxe = idxb = 1; /* index band B */ | |
| break; | |
| case 8: | |
| iae = 0; /* indirect off */ | |
| break; | |
| case 9: | |
| iae = 1; /* indirect on */ | |
| break; | |
| default: | |
| reason = STOP_INVSEL; /* undefined */ | |
| break; | |
| } | |
| BRANCH (PAR); | |
| break; | |
| /* Binary special feature instructions */ | |
| /* Branch on bit - P,Q are valid, Q is 4d address */ | |
| case OP_BBT: | |
| t = M[ADDR_A (saved_PC, I_Q)]; /* get Q0 digit */ | |
| if (t & M[QAR] & DIGIT) { /* match to mem? */ | |
| BRANCH (PAR); /* branch */ | |
| } | |
| break; | |
| /* Branch on mask - P,Q are valid, Q is 4d address */ | |
| case OP_BMK: | |
| t = M[ADDR_A (saved_PC, I_Q)]; /* get Q0 digit */ | |
| if (((t ^ M[QAR]) & /* match to mem? */ | |
| ((t & FLAG)? (FLAG + DIGIT): DIGIT)) == 0) { | |
| BRANCH (PAR); /* branch */ | |
| } | |
| break; | |
| /* Or - P,Q are valid */ | |
| case OP_ORF: | |
| reason = or_field (PAR, QAR); /* OR fields */ | |
| break; | |
| /* AND - P,Q are valid */ | |
| case OP_ANDF: | |
| reason = and_field (PAR, QAR); /* AND fields */ | |
| break; | |
| /* Exclusive or - P,Q are valid */ | |
| case OP_EORF: | |
| reason = xor_field (PAR, QAR); /* XOR fields */ | |
| break; | |
| /* Complement - P,Q are valid */ | |
| case OP_CPLF: | |
| reason = com_field (PAR, QAR); /* COM field */ | |
| break; | |
| /* Octal to decimal - P,Q are valid */ | |
| case OP_OTD: | |
| reason = oct_to_dec (PAR, QAR); /* convert */ | |
| break; | |
| /* Decimal to octal - P,Q are valid */ | |
| case OP_DTO: | |
| reason = dec_to_oct (PAR, QAR, &t); /* convert */ | |
| ind[IN_EZ] = t; /* set indicator */ | |
| if (ar_stop && ind[IN_OVF]) | |
| reason = STOP_OVERFL; | |
| break; | |
| /* Floating point special feature instructions */ | |
| case OP_FADD: | |
| reason = fp_add (PAR, QAR, FALSE); /* add */ | |
| if (ar_stop && ind[IN_EXPCHK]) | |
| reason = STOP_EXPCHK; | |
| break; | |
| case OP_FSUB: | |
| reason = fp_add (PAR, QAR, TRUE); /* subtract */ | |
| if (ar_stop && ind[IN_EXPCHK]) | |
| reason = STOP_EXPCHK; | |
| break; | |
| case OP_FMUL: | |
| reason = fp_mul (PAR, QAR); /* multiply */ | |
| if (ar_stop && ind[IN_EXPCHK]) | |
| reason = STOP_EXPCHK; | |
| break; | |
| case OP_FDIV: | |
| reason = fp_div (PAR, QAR); /* divide */ | |
| if (ar_stop && ind[IN_OVF]) | |
| reason = STOP_FPDVZ; | |
| if (ar_stop && ind[IN_EXPCHK]) | |
| reason = STOP_EXPCHK; | |
| break; | |
| case OP_FSL: | |
| reason = fp_fsl (PAR, QAR); /* shift left */ | |
| break; | |
| case OP_FSR: | |
| reason = fp_fsr (PAR, QAR); /* shift right */ | |
| break; | |
| /* Halt */ | |
| case OP_H: | |
| saved_PC = PC; /* commit inst */ | |
| reason = STOP_HALT; /* stop */ | |
| break; | |
| /* NOP */ | |
| case OP_NOP: | |
| break; | |
| /* Invalid instruction code */ | |
| default: | |
| reason = STOP_INVINS; /* stop */ | |
| break; | |
| } /* end switch */ | |
| } /* end while */ | |
| /* Simulation halted */ | |
| pcq_r->qptr = pcq_p; /* update pc q ptr */ | |
| upd_ind (); | |
| return reason; | |
| } | |
| /* Utility routines */ | |
| /* Get 2 digit field | |
| Inputs: | |
| ad = address of high digit | |
| Outputs: | |
| val = field converted to binary | |
| -1 if bad digit | |
| */ | |
| int32 get_2d (uint32 ad) | |
| { | |
| int32 d, d1; | |
| d = M[ad] & DIGIT; /* get 1st digit */ | |
| d1 = M[ADDR_A (ad, 1)] & DIGIT; /* get 2nd digit */ | |
| if (BAD_DIGIT (d) || BAD_DIGIT (d1)) /* bad? error */ | |
| return -1; | |
| return ((d * 10) + d1); /* cvt to binary */ | |
| } | |
| /* Get address routine | |
| Inputs: | |
| alast = address of low digit | |
| lnt = length | |
| indexok = TRUE if indexing allowed | |
| &addr = pointer to address output | |
| Output: | |
| return = error status (in terms of P address) | |
| addr = address converted to binary | |
| Notes: | |
| - If indexing produces a negative result, the effective address is | |
| the 10's complement of the result | |
| - An address that exceeds memory produces a MAR check stop | |
| */ | |
| t_stat get_addr (uint32 alast, int32 lnt, t_bool indexok, uint32 *reta) | |
| { | |
| uint8 indir; | |
| int32 cnt, idx, idxa, idxv, addr; | |
| if (iae) /* init indirect */ | |
| indir = FLAG; | |
| else indir = 0; | |
| cnt = 0; /* count depth */ | |
| do { | |
| indir = indir & M[alast]; /* get indirect */ | |
| if (cvt_addr (alast, lnt, FALSE, &addr)) /* cvt addr to bin */ | |
| return STOP_INVPDG; /* bad? */ | |
| idx = get_idx (ADDR_S (alast, 1)); /* get index reg num */ | |
| if (indexok && (idx > 0)) { /* indexable? */ | |
| idxa = GET_IDXADDR (idx); /* get idx reg addr */ | |
| if (cvt_addr (idxa, ADDR_LEN, TRUE, &idxv)) /* cvt idx reg */ | |
| return STOP_INVPDG; | |
| addr = addr + idxv; /* add in index */ | |
| if (addr < 0) /* -? 10's comp */ | |
| addr = addr + 100000; | |
| } | |
| if (addr >= (int32) MEMSIZE) /* invalid addr? */ | |
| return STOP_INVPAD; | |
| alast = addr; /* new address */ | |
| lnt = ADDR_LEN; /* std len */ | |
| } while (indir && (cnt++ < ind_max)); | |
| if (cnt > ind_max) /* indir too deep? */ | |
| return STOP_INVPIA; | |
| *reta = addr; /* return address */ | |
| return SCPE_OK; | |
| } | |
| /* Convert address to binary | |
| Inputs: | |
| alast = address of low digit | |
| lnt = length | |
| signok = TRUE if signed | |
| val = address of output | |
| Outputs: | |
| status = 0 if ok, != 0 if error | |
| */ | |
| t_stat cvt_addr (uint32 alast, int32 lnt, t_bool signok, int32 *val) | |
| { | |
| int32 sign = 0, addr = 0, t; | |
| if (signok && (M[alast] & FLAG)) /* signed? */ | |
| sign = 1; | |
| alast = alast - lnt; /* find start */ | |
| do { | |
| PP (alast); /* incr mem addr */ | |
| t = M[alast] & DIGIT; /* get digit */ | |
| if (BAD_DIGIT (t)) /* bad? error */ | |
| return STOP_INVDIG; | |
| addr = (addr * 10) + t; /* cvt to bin */ | |
| } while (--lnt > 0); | |
| if (sign) /* minus? */ | |
| *val = -addr; | |
| else *val = addr; | |
| return SCPE_OK; | |
| } | |
| /* Get index register number | |
| Inputs: | |
| aidx = address of low digit | |
| Outputs: | |
| index = >0 if indexed | |
| =0 if not indexed | |
| <0 if indexing disabled | |
| */ | |
| t_stat get_idx (uint32 aidx) | |
| { | |
| int32 i, idx; | |
| if (idxe == 0) /* indexing off? */ | |
| return -1; | |
| for (i = idx = 0; i < 3; i++) { /* 3 flags worth */ | |
| if (M[aidx] & FLAG) /* test flag */ | |
| idx = idx | (1 << i); | |
| MM (aidx); /* next digit */ | |
| } | |
| return idx; | |
| } | |
| /* Update indicators routine */ | |
| void upd_ind (void) | |
| { | |
| ind[IN_HPEZ] = ind[IN_HP] | ind[IN_EZ]; /* HPEZ = HP | EZ */ | |
| ind[IN_DERR] = ind[IN_DACH] | ind[IN_DWLR] | ind[IN_DCYO]; | |
| ind[IN_ANYCHK] = ind[IN_RDCHK] | ind[IN_WRCHK] | /* ANYCHK = all chks */ | |
| ind[IN_MBREVEN] | ind[IN_MBRODD] | | |
| ind[IN_PRCHK] | ind[IN_DACH]; | |
| ind[IN_IXN] = ind[IN_IXA] = ind[IN_IXB] = 0; /* clr index indics */ | |
| if (!idxe) /* off? */ | |
| ind[IN_IXN] = 1; | |
| else if (!idxb) /* on, band A? */ | |
| ind[IN_IXA] = 1; | |
| else ind[IN_IXB] = 1; /* no, band B */ | |
| return; | |
| } | |
| /* Transmit routines */ | |
| /* Transmit field from 's' to 'd' - ignore first 'skp' flags */ | |
| t_stat xmt_field (uint32 d, uint32 s, uint32 skp) | |
| { | |
| uint32 cnt = 0; | |
| uint8 t; | |
| do { | |
| t = M[d] = M[s] & (FLAG | DIGIT); /* copy src to dst */ | |
| MM (d); /* decr mem addrs */ | |
| MM (s); | |
| if (cnt++ >= MEMSIZE) /* (stop runaway) */ | |
| return STOP_FWRAP; | |
| } while (((t & FLAG) == 0) || (cnt <= skp)); /* until flag */ | |
| return SCPE_OK; | |
| } | |
| /* Transmit record from 's' to 'd' - copy record mark if 'cpy' = TRUE */ | |
| t_stat xmt_record (uint32 d, uint32 s, t_bool cpy) | |
| { | |
| uint32 cnt = 0; | |
| while ((M[s] & REC_MARK) != REC_MARK) { /* until rec mark */ | |
| M[d] = M[s] & (FLAG | DIGIT); /* copy src to dst */ | |
| PP (d); /* incr mem addrs */ | |
| PP (s); | |
| if (cnt++ >= MEMSIZE) /* (stop runaway) */ | |
| return STOP_FWRAP; | |
| } | |
| if (cpy) /* copy rec mark */ | |
| M[d] = M[s] & (FLAG | DIGIT); | |
| return SCPE_OK; | |
| } | |
| /* Transmit index from 's' to 'd' - fixed five character field */ | |
| t_stat xmt_index (uint32 d, uint32 s) | |
| { | |
| int32 i; | |
| M[d] = M[s] & (FLAG | DIGIT); /* preserve sign */ | |
| MM (d); MM (s); /* decr mem addrs */ | |
| for (i = 0; i < ADDR_LEN - 2; i++) { /* copy 3 digits */ | |
| M[d] = M[s] & DIGIT; /* without flags */ | |
| MM (d); /* decr mem addrs */ | |
| MM (s); | |
| } | |
| M[d] = (M[s] & DIGIT) | FLAG; /* set flag on last */ | |
| return SCPE_OK; | |
| } | |
| /* Transmit dividend from 'd' to 's' - clear flag on first digit */ | |
| t_stat xmt_divd (uint32 d, uint32 s) | |
| { | |
| uint32 cnt = 0; | |
| M[d] = M[s] & DIGIT; /* first w/o flag */ | |
| do { | |
| MM (d); /* decr mem addrs */ | |
| MM (s); | |
| M[d] = M[s] & (FLAG | DIGIT); /* copy src to dst */ | |
| if (cnt++ >= MEMSIZE) /* (stop runaway) */ | |
| return STOP_FWRAP; | |
| } while ((M[d] & FLAG) == 0); /* until src flag */ | |
| return SCPE_OK; | |
| } | |
| /* Transmit numeric strip from 's' to 'd' - s is odd */ | |
| t_stat xmt_tns (uint32 d, uint32 s) | |
| { | |
| uint32 cnt = 0; | |
| uint8 t, z; | |
| t = M[s] & DIGIT; /* get units */ | |
| z = M[s - 1] & DIGIT; /* get zone */ | |
| if ((z == 1) || (z == 5) || ((z == 2) && (t == 0))) /* 1x, 5x, 20? */ | |
| M[d] = t | FLAG; /* set flag */ | |
| else M[d] = t; /* else clear flag */ | |
| do { | |
| MM (d); /* decr mem addrs */ | |
| s = ADDR_S (s, 2); | |
| t = M[d] & FLAG; /* save dst flag */ | |
| M[d] = M[s] & (FLAG | DIGIT); /* copy src to dst */ | |
| if (cnt >= MEMSIZE) /* (stop runaway) */ | |
| return STOP_FWRAP; | |
| cnt = cnt + 2; | |
| } while (t == 0); /* until dst flag */ | |
| M[d] = M[d] | FLAG; /* set flag at end */ | |
| return SCPE_OK; | |
| } | |
| /* Transmit numeric fill from 's' to 'd' - d is odd */ | |
| t_stat xmt_tnf (uint32 d, uint32 s) | |
| { | |
| uint32 cnt = 0; | |
| uint8 t; | |
| t = M[s]; /* get 1st digit */ | |
| M[d] = t & DIGIT; /* store */ | |
| M[d - 1] = (t & FLAG)? 5: 7; /* set sign from flag */ | |
| do { | |
| MM (s); /* decr mem addr */ | |
| d = ADDR_S (d, 2); | |
| t = M[s]; /* get src digit */ | |
| M[d] = t & DIGIT; /* move to dst, no flag */ | |
| M[d - 1] = 7; /* set zone */ | |
| if (cnt >= MEMSIZE) /* (stop runaway) */ | |
| return STOP_FWRAP; | |
| cnt = cnt + 2; | |
| } while ((t & FLAG) == 0); /* until src flag */ | |
| return SCPE_OK; | |
| } | |
| /* Add routine | |
| Inputs: | |
| d = destination field low (P) | |
| s = source field low (Q) | |
| sub = TRUE if subtracting | |
| sto = TRUE if storing | |
| skp = number of source field flags, beyond sign, to ignore | |
| Output: | |
| return = status | |
| sta = ADD_NOCRY: no carry out, no sign change | |
| ADD_SCHNG: sign change | |
| ADD_CARRY: carry out | |
| Reference Manual: "When the sum is zero, the sign of the P field | |
| is retained." | |
| */ | |
| t_stat add_field (uint32 d, uint32 s, t_bool sub, t_bool sto, uint32 skp, int32 *sta) | |
| { | |
| uint32 cry, src, dst, res, comp, dp, dsv; | |
| uint32 src_f = 0, cnt = 0, dst_f; | |
| *sta = ADD_NOCRY; /* assume no cry */ | |
| dsv = d; /* save dst */ | |
| comp = ((M[d] ^ M[s]) & FLAG) ^ (sub? FLAG: 0); /* set compl flag */ | |
| cry = 0; /* clr carry */ | |
| ind[IN_HP] = ((M[d] & FLAG) == 0); /* set sign from res */ | |
| ind[IN_EZ] = 1; /* assume zero */ | |
| dst = M[d] & DIGIT; /* 1st digits */ | |
| src = M[s] & DIGIT; | |
| if (BAD_DIGIT (dst) || BAD_DIGIT (src)) /* bad digit? */ | |
| return STOP_INVDIG; | |
| if (comp) /* complement? */ | |
| src = 10 - src; | |
| res = add_one_digit (dst, src, &cry); /* add */ | |
| if (sto) /* store */ | |
| M[d] = (M[d] & FLAG) | res; | |
| MM (d); MM (s); /* decr mem addrs */ | |
| do { | |
| dst = M[d] & DIGIT; /* get dst digit */ | |
| dst_f = M[d] & FLAG; /* get dst flag */ | |
| if (src_f) /* src done? src = 0 */ | |
| src = 0; | |
| else { | |
| src = M[s] & DIGIT; /* get src digit */ | |
| if (cnt >= skp) /* get src flag */ | |
| src_f = M[s] & FLAG; | |
| MM (s); /* decr src addr */ | |
| } | |
| if (BAD_DIGIT (dst) || BAD_DIGIT (src)) /* bad digit? */ | |
| return STOP_INVDIG; | |
| if (comp) /* complement? */ | |
| src = 9 - src; | |
| res = add_one_digit (dst, src, &cry); /* add */ | |
| if (sto) /* store */ | |
| M[d] = dst_f | res; | |
| MM (d); /* decr dst addr */ | |
| if (cnt++ >= MEMSIZE) /* (stop runaway) */ | |
| return STOP_FWRAP; | |
| } while (dst_f == 0); /* until dst done */ | |
| if (!src_f) /* !src done? ovf */ | |
| ind[IN_OVF] = 1; | |
| if (comp && !cry && !ind[IN_EZ]) { /* recomp needed? */ | |
| ind[IN_HP] = ind[IN_HP] ^ 1; /* flip indicator */ | |
| if (sto) { /* storing? */ | |
| for (cry = 1, dp = dsv; dp != d; ) { /* rescan */ | |
| dst = M[dp] & DIGIT; /* get dst digit */ | |
| res = add_one_digit (9 - dst, 0, &cry); /* "add" */ | |
| M[dp] = (M[dp] & FLAG) | res; /* store */ | |
| MM (dp); /* decr dst addr */ | |
| } | |
| M[dsv] = M[dsv] ^ FLAG; /* compl sign */ | |
| } | |
| *sta = ADD_SIGNC; /* sign changed */ | |
| return SCPE_OK; | |
| } /* end if recomp */ | |
| if (ind[IN_EZ]) /* res = 0? clr HP */ | |
| ind[IN_HP] = 0; | |
| if (!comp && cry) /* set status */ | |
| *sta = ADD_CARRY; | |
| return SCPE_OK; | |
| } | |
| /* Add one digit via table (Model 1) or "hardware" (Model 2) */ | |
| uint32 add_one_digit (uint32 dst, uint32 src, uint32 *cry) | |
| { | |
| uint32 res; | |
| if (*cry) src = src + 1; /* cry in? incr src */ | |
| if (src >= 10) { /* src > 10? */ | |
| src = src - 10; /* src -= 10 */ | |
| *cry = 1; /* carry out */ | |
| } | |
| else *cry = 0; /* else no carry */ | |
| if (cpu_unit.flags & IF_MII) /* Model 2? */ | |
| res = sum_table[dst + src]; /* "hardware" */ | |
| else res = M[ADD_TABLE + (dst * 10) + src]; /* table lookup */ | |
| if (res & FLAG) /* carry out? */ | |
| *cry = 1; | |
| if (res & DIGIT) /* nz? clr ind */ | |
| ind[IN_EZ] = 0; | |
| return res & DIGIT; | |
| } | |
| /* Multiply routine | |
| Inputs: | |
| mpc = multiplicand address | |
| mpy = multiplier address | |
| Outputs: | |
| return = status | |
| Reference manual: "A zero product may have a negative or positive sign, | |
| depending on the signs of the fields at the P and Q addresses." | |
| */ | |
| t_stat mul_field (uint32 mpc, uint32 mpy) | |
| { | |
| int32 i; | |
| uint32 pro; /* prod pointer */ | |
| uint32 mpyd, mpyf; /* mpy digit, flag */ | |
| uint32 cnt = 0; /* counter */ | |
| uint8 sign; /* final sign */ | |
| t_stat r; | |
| PR1 = 1; /* step on PR1 */ | |
| for (i = 0; i < PROD_AREA_LEN; i++) /* clr prod area */ | |
| M[PROD_AREA + i] = 0; | |
| sign = (M[mpc] & FLAG) ^ (M[mpy] & FLAG); /* get final sign */ | |
| ind[IN_HP] = (sign == 0); /* set indicators */ | |
| ind[IN_EZ] = 1; | |
| pro = PROD_AREA + PROD_AREA_LEN - 1; /* product ptr */ | |
| /* Loop on multiplier (mpy) and product (pro) digits */ | |
| do { | |
| mpyd = M[mpy] & DIGIT; /* multiplier digit */ | |
| mpyf = (M[mpy] & FLAG) && (cnt != 0); /* last digit flag */ | |
| if (BAD_DIGIT (mpyd)) /* bad? */ | |
| return STOP_INVDIG; | |
| r = mul_one_digit (mpyd, mpc, pro, mpyf); /* prod += mpc*mpy_dig */ | |
| if (r != SCPE_OK) /* error? */ | |
| return r; | |
| MM (mpy); /* decr mpyr, prod addrs */ | |
| MM (pro); | |
| if (cnt++ >= MEMSIZE) /* (stop runaway) */ | |
| return STOP_FWRAP; | |
| } while ((mpyf == 0) || (cnt <= 1)); /* until mpyr flag */ | |
| if (ind[IN_EZ]) /* res = 0? clr HP */ | |
| ind[IN_HP] = 0; | |
| M[PROD_AREA + PROD_AREA_LEN - 1] |= sign; /* set final sign */ | |
| return SCPE_OK; | |
| } | |
| /* Multiply step | |
| Inputs: | |
| mpyd = multiplier digit (tested valid) | |
| mpcp = multiplicand low address | |
| prop = product low address | |
| last = last iteration flag (set flag on high product) | |
| Outputs: | |
| prod += multiplicand * multiplier_digit | |
| return = status | |
| The multiply table address is constructed as follows: | |
| - double the multiplier digit | |
| - use the 10's digit of the doubled result, + 1, as the 100's digit | |
| of the table address | |
| - use the multiplicand digit as the 10's digit of the table address | |
| - use the unit digit of the doubled result as the unit digit of the | |
| table address | |
| EZ indicator is cleared if a non-zero digit is ever generated | |
| */ | |
| t_stat mul_one_digit (uint32 mpyd, uint32 mpcp, uint32 prop, uint32 last) | |
| { | |
| uint32 mpta, mptb; /* mult table */ | |
| uint32 mptd; /* mult table digit */ | |
| uint32 mpcd, mpcf; /* mpc digit, flag */ | |
| uint32 prwp; /* prod working ptr */ | |
| uint32 prod; /* product digit */ | |
| uint32 cry; /* carry */ | |
| uint32 mpcc, cryc; /* counters */ | |
| mptb = MUL_TABLE + ((mpyd <= 4)? (mpyd * 2): /* set mpy table 100's, */ | |
| (((mpyd - 5) * 2) + 100)); /* 1's digits */ | |
| /* Inner loop on multiplicand (mpcp) and product (prop) digits */ | |
| mpcc = 0; /* multiplicand ctr */ | |
| do { | |
| prwp = prop; /* product working ptr */ | |
| mpcd = M[mpcp] & DIGIT; /* multiplicand digit */ | |
| mpcf = M[mpcp] & FLAG; /* multiplicand flag */ | |
| if (BAD_DIGIT (mpcd)) /* bad? */ | |
| return STOP_INVDIG; | |
| mpta = mptb + (mpcd * 10); /* mpy table 10's digit */ | |
| cry = 0; /* init carry */ | |
| mptd = M[mpta] & DIGIT; /* mpy table digit */ | |
| if (BAD_DIGIT (mptd)) /* bad? */ | |
| return STOP_INVDIG; | |
| prod = M[prwp] & DIGIT; /* product digit */ | |
| if (BAD_DIGIT (prod)) /* bad? */ | |
| return STOP_INVDIG; | |
| M[prwp] = add_one_digit (prod, mptd, &cry); /* add mpy tbl to prod */ | |
| MM (prwp); /* decr working ptr */ | |
| mptd = M[mpta + 1] & DIGIT; /* mpy table digit */ | |
| if (BAD_DIGIT (mptd)) /* bad? */ | |
| return STOP_INVDIG; | |
| prod = M[prwp] & DIGIT; /* product digit */ | |
| if (BAD_DIGIT (prod)) /* bad? */ | |
| return STOP_INVDIG; | |
| M[prwp] = add_one_digit (prod, mptd, &cry); /* add mpy tbl to prod */ | |
| cryc = 0; /* (stop runaway) */ | |
| while (cry) { /* propagate carry */ | |
| MM (prwp); /* decr working ptr */ | |
| prod = M[prwp] & DIGIT; /* product digit */ | |
| if (BAD_DIGIT (prod)) /* bad? */ | |
| return STOP_INVDIG; | |
| M[prwp] = add_one_digit (prod, 0, &cry); /* add cry */ | |
| if (cryc++ > MEMSIZE) | |
| return STOP_FWRAP; | |
| } | |
| MM (mpcp); /* decr mpc, prod ptrs */ | |
| MM (prop); | |
| if (mpcc++ > MEMSIZE) | |
| return STOP_FWRAP; | |
| } while ((mpcf == 0) || (mpcc <= 1)); /* until mpcf flag */ | |
| if (last) /* flag high product */ | |
| M[prop] = M[prop] | FLAG; | |
| return SCPE_OK; | |
| } | |
| /* Divide routine - comments from Geoff Kuenning's 1620 simulator | |
| The destination of the divide is given by: | |
| 100 - <# digits in quotient> | |
| Which is more easily calculated as: | |
| 100 - <# digits in divisor> - <# digits in dividend> | |
| The quotient goes into 99 minus the divisor length. The | |
| remainder goes into 99. The load dividend instruction (above) | |
| should have specified a P address of 99 minus the size of the | |
| divisor. | |
| Note that this all implies that "dest" points to the *leftmost* | |
| digit of the dividend. | |
| After the division, the assumed decimal point will be as many | |
| positions to the left as there are digits in the divisor. In | |
| other words, a 4-digit divisor will produce 4 (assumed) decimal | |
| places. | |
| There are other ways to do these things. In particular, the | |
| load-dividend instruction doesn't have to specify the above | |
| formula; if it's done differently, then you don't have to get | |
| decimal places. This is not well-explained in the books I have. | |
| How to divide on a 1620: | |
| The dividend is the field at 99: | |
| 90 = _1234567890 | |
| The divisor is somewhere else in memory: | |
| _03 | |
| The divide operation specifies the left-most digit of the | |
| dividend as the place to begin trial subtractions: | |
| DM 90,3 | |
| The loop works as follows: | |
| 1. Call the left-most digit of the dividend "current_dividend". | |
| Call the location current_dividend - <divisor_length> | |
| "quotient_digit". | |
| 2. Clear the flag at current_dividend, and set one at | |
| quotient_digit. | |
| 88 = _001234567890, q_d = 88, c_d = 90 | |
| [Not actually done; divisor length controls subtract.] | |
| 3. Subtract the divisor from the field at current-dividend, | |
| using normal 1620 rules, except that signs are ignored. | |
| Continue these subtractions until either 10 subtractions | |
| have been done, or you get a negative result: | |
| 88 = _00_2234567890, q_d = 88, c_d = 90 | |
| 4. If 10 subtractions have been done, set the overflow | |
| indicator and abort. Otherwise, add the divisor back to | |
| correct for the oversubtraction: | |
| 88 = _001234567890, q_d = 88, c_d = 90 | |
| 5. Store the (net) number of subtractions in quotient_digit: | |
| 88 = _001234567890, q_d = 88, c_d = 90 | |
| 6. If this is not the first pass, clear the flag at | |
| quotient_digit. Increment quotient_digit and | |
| current_dividend, and set a flag at the new | |
| quotient_digit: | |
| 88 = _0_01234567890, q_d = 89, c_d = 91 | |
| [If first pass, set a flag at quotient digit.] | |
| 7. If current_dividend is not 100, repeat steps 3 through 7. | |
| 8. Set flags at 99 and quotient_digit - 1 according to the | |
| rules of algebra: the quotient's sign is the exclusive-or | |
| of the signs of the divisor and dividend, and the | |
| remainder has the sign of the dividend: | |
| 10 / 3 = 3 remainder 1 | |
| 10 / -3 = -3 remainder 1 | |
| -10 / 3 = -3 remainder -1 | |
| -10 / -3 = 3 remainder -1 | |
| This preserves the relationship dd = q * dv + r. | |
| Our example continues as follows for steps 3 through 7: | |
| 3. 88 = _0_00_334567890, q_d = 89, c_d = 91 | |
| 4. 88 = _0_00034567890 | |
| 5. 88 = _0_40034567890 | |
| 6. 88 = _04_0034567890, q_d = 90, c_d = 92 | |
| 3. 88 = _04_00_34567890 | |
| 4. 88 = _04_0004567890 | |
| 5. 88 = _04_1004567890 | |
| 6. 88 = _041_004567890, q_d = 91, c_d = 93 | |
| 3. 88 = _041_00_2567890 | |
| 4. 88 = _041_001567890 | |
| 5. 88 = _041_101567890 | |
| 6. 88 = _0411_01567890, q_d = 92, c_d = 94 | |
| 3. 88 = _0411_00_367890 | |
| 4. 88 = _0411_00067890 | |
| 5. 88 = _0411_50067890 | |
| 6. 88 = _04115_0067890, q_d = 93, c_d = 95 | |
| 3. 88 = _04115_00_37890 | |
| 4. 88 = _04115_0007890 | |
| 5. 88 = _04115_2007890 | |
| 6. 88 = _041152_007890, q_d = 94, c_d = 96 | |
| 3. 88 = _041152_00_2890 | |
| 4. 88 = _041152_001890 | |
| 5. 88 = _041152_201890 | |
| 6. 88 = _0411522_01890, q_d = 95, c_d = 97 | |
| 3. 88 = _0411522_00_390 | |
| 4. 88 = _0411522_00090 | |
| 5. 88 = _0411522_60090 | |
| 6. 88 = _04115226_0090, q_d = 96, c_d = 98 | |
| 3. 88 = _04115226_00_30 | |
| 4. 88 = _04115226_0000 | |
| 5. 88 = _04115226_3000 | |
| 6. 88 = _041152263_000, q_d = 97, c_d = 99 | |
| 3. 88 = _041152263_00_3 | |
| 4. 88 = _041152263_000 | |
| 5. 88 = _041152263_000 | |
| 6. 88 = _0411522630_00, q_d = 98, c_d = 100 | |
| In the actual code below, we elide several of these steps in | |
| various ways for convenience and efficiency. | |
| Note that the EZ indicator is NOT valid for divide, because it | |
| is cleared by any non-zero result in an intermediate add. The | |
| code maintains its own EZ indicator for the quotient. | |
| */ | |
| t_stat div_field (uint32 dvd, uint32 dvr, int32 *ez) | |
| { | |
| uint32 quop, quod, quos; /* quo ptr, dig, sign */ | |
| uint32 dvds; /* dvd sign */ | |
| t_bool first = TRUE; /* first pass */ | |
| t_stat r; | |
| dvds = (M[PROD_AREA + PROD_AREA_LEN - 1]) & FLAG; /* dividend sign */ | |
| quos = dvds ^ (M[dvr] & FLAG); /* quotient sign */ | |
| ind[IN_HP] = (quos == 0); /* set indicators */ | |
| *ez = 1; | |
| /* Loop on current dividend, high order digit at dvd */ | |
| do { | |
| r = div_one_digit (dvd, dvr, 10, &quod, &quop); /* dev quo digit */ | |
| if (r != SCPE_OK) /* error? */ | |
| return r; | |
| /* Store quotient digit and advance current dividend pointer */ | |
| if (first) { /* first pass? */ | |
| if (quod >= 10) { /* overflow? */ | |
| ind[IN_OVF] = 1; /* set indicator */ | |
| return STOP_OVERFL; /* stop */ | |
| } | |
| M[quop] = FLAG | quod; /* set flag on quo */ | |
| first = FALSE; | |
| } | |
| else M[quop] = quod; /* store quo digit */ | |
| if (quod) /* if nz, clr ind */ | |
| *ez = 0; | |
| PP (dvd); /* incr dvd ptr */ | |
| } while (dvd != (PROD_AREA + PROD_AREA_LEN)); /* until end prod */ | |
| /* Division done. Set signs of quo, rem, set flag on high order remainder */ | |
| if (*ez) /* res = 0? clr HP */ | |
| ind[IN_HP] = 0; | |
| M[PROD_AREA + PROD_AREA_LEN - 1] |= dvds; /* remainder sign */ | |
| M[quop] = M[quop] | quos; /* quotient sign */ | |
| PP (quop); /* high remainder */ | |
| M[quop] = M[quop] | FLAG; /* set flag */ | |
| return SCPE_OK; | |
| } | |
| /* Divide step | |
| Inputs: | |
| dvd = current dividend address (high digit) | |
| dvr = divisor address (low digit) | |
| max = max number of iterations before overflow | |
| &quod = address to store quotient digit | |
| &quop = address to store quotient pointer (can be NULL) | |
| Outputs: | |
| return = status | |
| Divide step calculates a quotient digit by repeatedly subtracting the | |
| divisor from the current dividend. The divisor's length controls the | |
| subtraction; dividend flags are ignored. | |
| */ | |
| t_stat div_one_digit (uint32 dvd, uint32 dvr, uint32 max, | |
| uint32 *quod, uint32 *quop) | |
| { | |
| uint32 dvrp, dvrd, dvrf; /* dvr ptr, dig, flag */ | |
| uint32 dvdp, dvdd; /* dvd ptr, dig */ | |
| uint32 qd, cry; /* quo dig, carry */ | |
| uint32 cnt; | |
| for (qd = 0; qd < max; qd++) { /* devel quo dig */ | |
| dvrp = dvr; /* divisor ptr */ | |
| dvdp = dvd; /* dividend ptr */ | |
| cnt = 0; | |
| cry = 1; /* carry in = 1 */ | |
| do { /* sub dvr fm dvd */ | |
| dvdd = M[dvdp] & DIGIT; /* dividend digit */ | |
| if (BAD_DIGIT (dvdd)) /* bad? */ | |
| return STOP_INVDIG; | |
| dvrd = M[dvrp] & DIGIT; /* divisor digit */ | |
| dvrf = M[dvrp] & FLAG; /* divisor flag */ | |
| if (BAD_DIGIT (dvrd)) /* bad? */ | |
| return STOP_INVDIG; | |
| M[dvdp] = add_one_digit (dvdd, 9 - dvrd, &cry); /* sub */ | |
| MM (dvdp); /* decr ptrs */ | |
| MM (dvrp); | |
| if (cnt++ >= MEMSIZE) /* (stop runaway) */ | |
| return STOP_FWRAP; | |
| } while ((dvrf == 0) || (cnt <= 1)); /* until dvr flag */ | |
| if (!cry) { /* !cry = borrow */ | |
| dvdd = M[dvdp] & DIGIT; /* borrow digit */ | |
| if (BAD_DIGIT (dvdd)) /* bad? */ | |
| return STOP_INVDIG; | |
| M[dvdp] = add_one_digit (dvdd, 9, &cry); /* sub */ | |
| } | |
| if (!cry) /* !cry = negative */ | |
| break; | |
| } | |
| /* Add back the divisor to correct for the negative result */ | |
| dvrp = dvr; /* divisor ptr */ | |
| dvdp = dvd; /* dividend ptr */ | |
| cnt = 0; | |
| cry = 0; /* carry in = 0 */ | |
| do { | |
| dvdd = M[dvdp] & DIGIT; /* dividend digit */ | |
| dvrd = M[dvrp] & DIGIT; /* divisor digit */ | |
| dvrf = M[dvrp] & FLAG; /* divisor flag */ | |
| M[dvdp] = add_one_digit (dvdd, dvrd, &cry); /* add */ | |
| MM (dvdp); /* decr ptrs */ | |
| MM (dvrp); | |
| cnt++; | |
| } while ((dvrf == 0) || (cnt <= 1)); /* until dvr flag */ | |
| if (cry) { /* carry out? */ | |
| dvdd = M[dvdp] & DIGIT; /* borrow digit */ | |
| M[dvdp] = add_one_digit (dvdd, 0, &cry); /* add */ | |
| } | |
| if (quop != NULL) /* set quo addr */ | |
| *quop = dvdp; | |
| *quod = qd; /* set quo digit */ | |
| return SCPE_OK; | |
| } | |
| /* Logical operation routines (and, or, xor, complement) | |
| Inputs: | |
| d = destination address | |
| s = source address | |
| Output: | |
| return = status | |
| Destination flags are preserved; EZ reflects the result. | |
| COM does not obey normal field length restrictions. | |
| */ | |
| t_stat or_field (uint32 d, uint32 s) | |
| { | |
| uint32 cnt = 0; | |
| int32 t; | |
| ind[IN_EZ] = 1; /* assume result zero */ | |
| do { | |
| t = M[s]; /* get src */ | |
| M[d] = (M[d] & FLAG) | ((M[d] | t) & 07); /* OR src to dst */ | |
| if (M[d] & DIGIT) /* nz dig? clr ind */ | |
| ind[IN_EZ] = 0; | |
| MM (d); /* decr pointers */ | |
| MM (s); | |
| if (cnt++ >= MEMSIZE) /* (stop runaway) */ | |
| return STOP_FWRAP; | |
| } while (((t & FLAG) == 0) || (cnt <= 1)); /* until src flag */ | |
| return SCPE_OK; | |
| } | |
| t_stat and_field (uint32 d, uint32 s) | |
| { | |
| uint32 cnt = 0; | |
| int32 t; | |
| ind[IN_EZ] = 1; /* assume result zero */ | |
| do { | |
| t = M[s]; /* get src */ | |
| M[d] = (M[d] & FLAG) | ((M[d] & t) & 07); /* AND src to dst */ | |
| if (M[d] & DIGIT) /* nz dig? clr ind */ | |
| ind[IN_EZ] = 0; | |
| MM (d); /* decr pointers */ | |
| MM (s); | |
| if (cnt++ >= MEMSIZE) /* (stop runaway) */ | |
| return STOP_FWRAP; | |
| } while (((t & FLAG) == 0) || (cnt <= 1)); /* until src flag */ | |
| return SCPE_OK; | |
| } | |
| t_stat xor_field (uint32 d, uint32 s) | |
| { | |
| uint32 cnt = 0; | |
| int32 t; | |
| ind[IN_EZ] = 1; /* assume result zero */ | |
| do { | |
| t = M[s]; /* get src */ | |
| M[d] = (M[d] & FLAG) | ((M[d] ^ t) & 07); /* XOR src to dst */ | |
| if (M[d] & DIGIT) /* nz dig? clr ind */ | |
| ind[IN_EZ] = 0; | |
| MM (d); /* decr pointers */ | |
| MM (s); | |
| if (cnt++ >= MEMSIZE) /* (stop runaway) */ | |
| return STOP_FWRAP; | |
| } while (((t & FLAG) == 0) || (cnt <= 1)); /* until src flag */ | |
| return SCPE_OK; | |
| } | |
| t_stat com_field (uint32 d, uint32 s) | |
| { | |
| uint32 cnt = 0; | |
| int32 t; | |
| ind[IN_EZ] = 1; /* assume result zero */ | |
| do { | |
| t = M[s]; /* get src */ | |
| M[d] = (t & FLAG) | ((t ^ 07) & 07); /* comp src to dst */ | |
| if (M[d] & DIGIT) /* nz dig? clr ind */ | |
| ind[IN_EZ] = 0; | |
| MM (d); /* decr pointers */ | |
| MM (s); | |
| if (cnt++ >= MEMSIZE) /* (stop runaway) */ | |
| return STOP_FWRAP; | |
| } while ((t & FLAG) == 0); /* until src flag */ | |
| return SCPE_OK; | |
| } | |
| /* Octal to decimal | |
| Inputs: | |
| tbl = conversion table address (low digit) | |
| s = source address | |
| Outputs: | |
| product area = converted source | |
| result = status | |
| OTD is a cousin of multiply. The octal digits in the source are | |
| multiplied by successive values in the conversion table, and the | |
| results are accumulated in the product area. Although the manual | |
| does not say, this code assumes that EZ and HP are affected. | |
| */ | |
| t_stat oct_to_dec (uint32 tbl, uint32 s) | |
| { | |
| uint32 cnt = 0, tblc; | |
| uint32 i, sd, sf, tf, sign; | |
| t_stat r; | |
| for (i = 0; i < PROD_AREA_LEN; i++) /* clr prod area */ | |
| M[PROD_AREA + i] = 0; | |
| sign = M[s] & FLAG; /* save sign */ | |
| ind[IN_EZ] = 1; /* set indicators */ | |
| ind[IN_HP] = (sign == 0); | |
| do { | |
| sd = M[s] & DIGIT; /* src digit */ | |
| sf = M[s] & FLAG; /* src flag */ | |
| r = mul_one_digit (sd, tbl, PROD_AREA + PROD_AREA_LEN - 1, sf); | |
| if (r != SCPE_OK) /* err? */ | |
| return r; | |
| MM (s); /* decr src addr */ | |
| MM (tbl); /* skip 1st tbl dig */ | |
| tblc = 0; /* count */ | |
| do { | |
| tf = M[tbl] & FLAG; /* get next */ | |
| MM (tbl); /* decr ptr */ | |
| if (tblc++ > MEMSIZE) | |
| return STOP_FWRAP; | |
| } while (tf == 0); /* until flag */ | |
| if (cnt++ >= MEMSIZE) /* (stop runaway) */ | |
| return STOP_FWRAP; | |
| } while (sf == 0); | |
| if (ind[IN_EZ]) /* res = 0? clr HP */ | |
| ind[IN_HP] = 0; | |
| M[PROD_AREA + PROD_AREA_LEN - 1] |= sign; /* set sign */ | |
| return SCPE_OK; | |
| } | |
| /* Decimal to octal | |
| Inputs: | |
| d = destination address | |
| tbl = conversion table address (low digit of highest power) | |
| &ez = address of soft EZ indicator | |
| product area = field to convert | |
| Outputs: | |
| return = status | |
| DTO is a cousin to divide. The number in the product area is repeatedly | |
| divided by successive values in the conversion table, and the quotient | |
| digits are stored in the destination. Although the manual does not say, | |
| this code assumes that EZ and HP are affected. | |
| */ | |
| t_stat dec_to_oct (uint32 d, uint32 tbl, int32 *ez) | |
| { | |
| uint32 sign, octd, t; | |
| t_bool first = TRUE; | |
| uint32 ctr = 0; | |
| t_stat r; | |
| sign = M[PROD_AREA + PROD_AREA_LEN - 1] & FLAG; /* input sign */ | |
| *ez = 1; /* set indicators */ | |
| ind[IN_HP] = (sign == 0); | |
| for ( ;; ) { | |
| r = div_one_digit (PROD_AREA + PROD_AREA_LEN - 1, /* divide */ | |
| tbl, 8, &octd, NULL); | |
| if (r != SCPE_OK) /* error? */ | |
| return r; | |
| if (first) { /* first pass? */ | |
| if (octd >= 8) { /* overflow? */ | |
| ind[IN_OVF] = 1; /* set indicator */ | |
| return SCPE_OK; /* stop */ | |
| } | |
| M[d] = FLAG | octd; /* set flag on quo */ | |
| first = FALSE; | |
| } | |
| else M[d] = octd; /* store quo digit */ | |
| if (octd) /* if nz, clr ind */ | |
| *ez = 0; | |
| PP (tbl); /* incr tbl addr */ | |
| if ((M[tbl] & REC_MARK) == REC_MARK) /* record mark? */ | |
| break; | |
| PP (tbl); /* skip flag */ | |
| if ((M[tbl] & REC_MARK) == REC_MARK) /* record mark? */ | |
| break; | |
| do { /* look for F, rec mk */ | |
| PP (tbl); | |
| t = M[tbl]; | |
| } while (((t & FLAG) == 0) && ((t & REC_MARK) != REC_MARK)); | |
| MM (tbl); /* step back one */ | |
| PP (d); /* incr quo addr */ | |
| if (ctr++ > MEMSIZE) /* (stop runaway) */ | |
| return STOP_FWRAP; | |
| } | |
| if (*ez) /* res = 0? clr HP */ | |
| ind[IN_HP] = 0; | |
| M[d] = M[d] | sign; /* set result sign */ | |
| return SCPE_OK; | |
| } | |
| /* Reset routine */ | |
| t_stat cpu_reset (DEVICE *dptr) | |
| { | |
| int32 i; | |
| static t_bool one_time = TRUE; | |
| PR1 = IR2 = 1; /* invalidate PR1,IR2 */ | |
| ind[0] = 0; | |
| for (i = IN_SW4 + 1; i < NUM_IND; i++) /* init indicators */ | |
| ind[i] = 0; | |
| if (cpu_unit.flags & IF_IA) /* indirect enabled? */ | |
| iae = 1; | |
| else iae = 0; | |
| idxe = idxb = 0; /* indexing off */ | |
| pcq_r = find_reg ("PCQ", NULL, dptr); /* init old PC queue */ | |
| if (pcq_r) | |
| pcq_r->qptr = 0; | |
| else return SCPE_IERR; | |
| sim_brk_types = sim_brk_dflt = SWMASK ('E'); /* init breakpoints */ | |
| upd_ind (); /* update indicators */ | |
| if (one_time) /* set default tables */ | |
| cpu_set_table (&cpu_unit, 1, NULL, NULL); | |
| one_time = FALSE; | |
| 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] & (FLAG | DIGIT); | |
| 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 & (FLAG | DIGIT); | |
| 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; | |
| return SCPE_OK; | |
| } | |
| /* Model change */ | |
| t_stat cpu_set_model (UNIT *uptr, int32 val, char *cptr, void *desc) | |
| { | |
| if (val) | |
| cpu_unit.flags = (cpu_unit.flags & (UNIT_SCP | UNIT_BCD | MII_OPT)) | | |
| IF_DIV | IF_IA | IF_EDT; | |
| else cpu_unit.flags = cpu_unit.flags & (UNIT_SCP | UNIT_BCD | MI_OPT); | |
| return SCPE_OK; | |
| } | |
| /* Set/clear Model 1 option */ | |
| t_stat cpu_set_opt1 (UNIT *uptr, int32 val, char *cptr, void *desc) | |
| { | |
| if (cpu_unit.flags & IF_MII) { | |
| printf ("Feature is standard on 1620 Model 2\n"); | |
| if (sim_log) | |
| fprintf (sim_log, "Feature is standard on 1620 Model 2\n"); | |
| return SCPE_NOFNC; | |
| } | |
| return SCPE_OK; | |
| } | |
| /* Set/clear Model 2 option */ | |
| t_stat cpu_set_opt2 (UNIT *uptr, int32 val, char *cptr, void *desc) | |
| { | |
| if (!(cpu_unit.flags & IF_MII)) { | |
| printf ("Feature is not available on 1620 Model 1\n"); | |
| if (sim_log) | |
| fprintf (sim_log, "Feature is not available on 1620 Model 1\n"); | |
| return SCPE_NOFNC; | |
| } | |
| return SCPE_OK; | |
| } | |
| /* Front panel save */ | |
| t_stat cpu_set_save (UNIT *uptr, int32 val, char *cptr, void *desc) | |
| { | |
| if (saved_PC & 1) | |
| return SCPE_NOFNC; | |
| PR1 = saved_PC; | |
| return SCPE_OK; | |
| } | |
| /* Set standard add/multiply tables */ | |
| t_stat cpu_set_table (UNIT *uptr, int32 val, char *cptr, void *desc) | |
| { | |
| int32 i; | |
| for (i = 0; i < MUL_TABLE_LEN; i++) /* set mul table */ | |
| M[MUL_TABLE + i] = std_mul_table[i]; | |
| if (((cpu_unit.flags & IF_MII) == 0) || val) { /* set add table */ | |
| for (i = 0; i < ADD_TABLE_LEN; i++) | |
| M[ADD_TABLE + i] = std_add_table[i]; | |
| } | |
| return SCPE_OK; | |
| } | |
| /* Set history */ | |
| t_stat cpu_set_hist (UNIT *uptr, int32 val, char *cptr, void *desc) | |
| { | |
| int32 i, lnt; | |
| t_stat r; | |
| if (cptr == NULL) { | |
| for (i = 0; i < hst_lnt; i++) | |
| hst[i].vld = 0; | |
| hst_p = 0; | |
| return SCPE_OK; | |
| } | |
| lnt = (int32) get_uint (cptr, 10, HIST_MAX, &r); | |
| if ((r != SCPE_OK) || (lnt && (lnt < HIST_MIN))) | |
| return SCPE_ARG; | |
| hst_p = 0; | |
| if (hst_lnt) { | |
| free (hst); | |
| hst_lnt = 0; | |
| hst = NULL; | |
| } | |
| if (lnt) { | |
| hst = (InstHistory *) calloc (lnt, sizeof (InstHistory)); | |
| if (hst == NULL) | |
| return SCPE_MEM; | |
| hst_lnt = lnt; | |
| } | |
| return SCPE_OK; | |
| } | |
| /* Show history */ | |
| t_stat cpu_show_hist (FILE *st, UNIT *uptr, int32 val, void *desc) | |
| { | |
| int32 i, k, di, lnt; | |
| char *cptr = (char *) desc; | |
| t_value sim_eval[INST_LEN]; | |
| t_stat r; | |
| InstHistory *h; | |
| extern t_stat fprint_sym (FILE *ofile, t_addr addr, t_value *val, | |
| UNIT *uptr, int32 sw); | |
| if (hst_lnt == 0) /* enabled? */ | |
| return SCPE_NOFNC; | |
| if (cptr) { | |
| lnt = (int32) get_uint (cptr, 10, hst_lnt, &r); | |
| if ((r != SCPE_OK) || (lnt == 0)) | |
| return SCPE_ARG; | |
| } | |
| else lnt = hst_lnt; | |
| di = hst_p - lnt; /* work forward */ | |
| if (di < 0) | |
| di = di + hst_lnt; | |
| fprintf (st, "PC IR\n\n"); | |
| for (k = 0; k < lnt; k++) { /* print specified */ | |
| h = &hst[(++di) % hst_lnt]; /* entry pointer */ | |
| if (h->vld) { /* instruction? */ | |
| fprintf (st, "%05d ", h->pc); | |
| for (i = 0; i < INST_LEN; i++) | |
| sim_eval[i] = h->inst[i]; | |
| if ((fprint_sym (st, h->pc, sim_eval, &cpu_unit, SWMASK ('M'))) > 0) { | |
| fprintf (st, "(undefined)"); | |
| for (i = 0; i < INST_LEN; i++) | |
| fprintf (st, "%02X", h->inst[i]); | |
| } | |
| fputc ('\n', st); /* end line */ | |
| } /* end else instruction */ | |
| } /* end for */ | |
| return SCPE_OK; | |
| } |