| /* i650_cpu.c: IBM 650 CPU simulator | |
| Copyright (c) 2018, Roberto Sancho | |
| 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 | |
| ROBERTO SANCHO 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. | |
| cpu IBM 650 central processor | |
| From Wikipedia: The IBM 650 Magnetic Drum Data-Processing Machine is one of | |
| IBM's early computers, and the worlds first mass-produced computer. It was | |
| announced in 1953 and in 1956 enhanced as the IBM 650 RAMAC with the | |
| addition of up to four disk storage units. Almost 2,000 systems were | |
| produced, the last in 1962. | |
| The 650 was a two-address, bi-quinary coded decimal computer (both data and | |
| addresses were decimal), with memory on a rotating magnetic drum. Character | |
| support was provided by the input/output units converting punched card | |
| alphabetical and special character encodings to/from a two-digit decimal | |
| code. | |
| Rotating drum memory provided 1,000, 2,000, or 4,000 words of memory (a | |
| signed 10-digit number or five characters per word) at addresses 0000 to | |
| 0999, 1999, or 3999 respectively. | |
| Instructions read from the drum went to a program register (in current | |
| terminology, an instruction register). Data read from the drum went through | |
| a 10-digit distributor. The 650 had a 20-digit accumulator, divided into | |
| 10-digit lower and upper accumulators with a common sign. Arithmetic was | |
| performed by a one-digit adder. The console (10 digit switches, one sign | |
| switch, and 10 bi-quinary display lights), distributor, lower and upper | |
| accumulators were all addressable; 8000, 8001, 8002, 8003 respectively. | |
| The 650 instructions consisted of a two-digit operation code, a four-digit | |
| data address and the four-digit address of the next instruction. The sign | |
| was ignored on the basic machine, but was used on machines with optional | |
| features. The base machine had 44 operation codes. Additional operation | |
| codes were provided for options, such as floating point, core storage, | |
| index registers and additional I/O devices. With all options installed, | |
| there were 97 operation codes. | |
| The programmer visible system state for the IBM 650 is: | |
| CSW <10:1> Console Switches | |
| ACC[0] <10:1> Lower Accumulator register | |
| ACC[1] <10:1> Upper Accumulator register | |
| DIST <10:1> Distributor | |
| OV<0:0> Overflow flag | |
| The 650 had one basic instuction format. | |
| Intructions are stores as 10 digits (0-9) words in drum memory | |
| 10 9 | 8 7 6 5 | 4 3 2 1 | 0 | |
| -----+---------+---------+----- | |
| op | Data | Instr | Sign | |
| code | Addr | Addr | |
| First two digits are opcodes | |
| digits 8-5 is data address referenced by opcode | |
| digits 4-1 is instruction address: address of next instruction | |
| Instruction support as described in BitSavers 22-6060-2_650_OperMan.pdf | |
| IBM 653 Storage Unit can be enabled as an option. This simulates the following | |
| - Immediate Access Storage (IAS) | |
| - Index registers | |
| - Floating Point support | |
| - Synchronizers 2 & 3 | |
| */ | |
| #include "i650_defs.h" | |
| #define UNIT_V_MSIZE (UNIT_V_UF + 0) | |
| #define UNIT_MSIZE (7 << UNIT_V_MSIZE) | |
| #define UNIT_V_CPUMODEL (UNIT_V_UF + 4) | |
| #define UNIT_MODEL (0x01 << UNIT_V_CPUMODEL) | |
| #define CPU_MODEL ((cpu_unit.flags >> UNIT_V_CPUMODEL) & 0x01) | |
| #define MODEL(x) (x << UNIT_V_CPUMODEL) | |
| #define MEMAMOUNT(x) (x << UNIT_V_MSIZE) | |
| #define OPTION_STOR (1 << (UNIT_V_CPUMODEL + 1)) | |
| #define OPTION_CNTRL (1 << (UNIT_V_CPUMODEL + 2)) | |
| #define OPTION_SOAPMNE (1 << (UNIT_V_CPUMODEL + 3)) | |
| #define OPTION_FAST (1 << (UNIT_V_CPUMODEL + 4)) | |
| 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, CONST char *cptr, void *desc); | |
| t_stat cpu_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); | |
| t_stat cpu_svc (UNIT *uptr); | |
| const char *cpu_description (DEVICE *dptr); | |
| // DRUM Memory | |
| t_int64 DRUM[MAXDRUMSIZE] = {0}; | |
| int DRUM_NegativeZeroFlag[MAXDRUMSIZE] = {0}; | |
| char DRUM_Symbolic_Buffer[MAXDRUMSIZE * 80] = {0}; // does not exists on real hw. Used to keep symbolic info | |
| // IO Synchronizer for card read-punch buffer | |
| t_int64 IOSync[10] = {0}; | |
| int IOSync_NegativeZeroFlag[10] = {0}; | |
| // IAS Memory | |
| t_int64 IAS[60] = {0}; | |
| int IAS_NegativeZeroFlag[60] = {0}; | |
| int IAS_TimingRing = 0; | |
| // interlock counters | |
| int InterLockCount[IL_array] = {0}; | |
| // address where rotating drum is currently positioned (0-49) | |
| int DrumAddr; | |
| // cpu registers | |
| uint16 IC; // Added register not part of cpu. Has addr of current intr in execution, just for displaying purposes. IBM 650 has no program counter | |
| uint16 PROP; // Added register not part of cpu. Has operation code of current intr in execution, just for scp scripting purposes. Contains the two higher digits of PR register | |
| t_int64 ACC[2]; /* lower, upper accumulator. 10 digits (=one word) each*/ | |
| t_int64 DIST; /* ditributor. 10 digits */ | |
| t_int64 CSW = 0; /* Console Switches, 10 digits */ | |
| t_int64 PR; /* Program Register: hold current instr in execution, 10 digits*/ | |
| uint16 AR; /* Address Register: address references to drum */ | |
| uint8 OV; /* Overflow flag */ | |
| uint8 CSWProgStop = 1; /* Console programmed stop switch */ | |
| uint8 CSWOverflowStop = 0; /* Console stop on overflow switch */ | |
| uint8 HalfCycle = 0; // set to 0 for normal run, =1 to execute I-Half-cycle, =2 to execute D-half-cycle | |
| int ProgStopFlag = 0; // set to 1 if programmed stop was the previous inst executed | |
| int AccNegativeZeroFlag = 0; // set to 1 if acc has a negative zero | |
| int DistNegativeZeroFlag = 0; // set to 1 if distributor has a negative zero | |
| int16 IR[3]; // Index registers. Are 4 digits as AR register, but signed | |
| /* CPU data structures | |
| cpu_dev CPU device descriptor | |
| cpu_unit CPU unit descriptor | |
| cpu_reg CPU register list | |
| cpu_mod CPU modifiers list | |
| */ | |
| UNIT cpu_unit = | |
| { UDATA(&cpu_svc, MEMAMOUNT(0)|MODEL(0x0), 1000), 10 }; | |
| REG cpu_reg[] = { | |
| {DRDATAD(IC, IC, 16, "Current Instruction"), REG_FIT|REG_RO}, | |
| {DRDATAD(PROP, PROP, 16, "Program Register Operation Code"), REG_FIT|REG_RO}, | |
| {HRDATAD(DIST, DIST, 64, "Distributor"), REG_VMIO|REG_FIT}, | |
| {HRDATAD(ACCLO, ACC[0], 64, "Lower Accumulator"), REG_VMIO|REG_FIT}, | |
| {HRDATAD(ACCUP, ACC[1], 64, "Upper Accumulator"), REG_VMIO|REG_FIT}, | |
| {HRDATAD(PR, PR, 64, "Program Register"), REG_VMIO|REG_FIT}, | |
| {DRDATAD(AR, AR, 16, "Address Register"), REG_FIT}, | |
| {ORDATAD(OV, OV, 1, "Overflow"), REG_FIT}, | |
| {HRDATAD(CSW, CSW, 64, "Console Switches"), REG_VMIO|REG_FIT}, | |
| {ORDATAD(CSWPS, CSWProgStop, 1, "Console Switch Program Stop"), REG_FIT}, | |
| {ORDATAD(CSWOS, CSWOverflowStop, 1, "Console Switch Overflow Stop"), REG_FIT}, | |
| {ORDATAD(HALF, HalfCycle, 2, "Half Cycle"), REG_FIT}, | |
| {NULL} | |
| }; | |
| MTAB cpu_mod[] = { | |
| {UNIT_MSIZE, MEMAMOUNT(0), "1K", "1K", &cpu_set_size}, | |
| {UNIT_MSIZE, MEMAMOUNT(1), "2K", "2K", &cpu_set_size}, | |
| {UNIT_MSIZE, MEMAMOUNT(2), "4K", "4K", &cpu_set_size}, | |
| {OPTION_STOR, 0, NULL, "NOSTORAGEUNIT", NULL}, | |
| {OPTION_STOR, OPTION_STOR, "Storage Unit", "STORAGEUNIT", NULL}, | |
| {OPTION_CNTRL, 0, NULL, "NOCNTRLUNIT", NULL}, | |
| {OPTION_CNTRL, OPTION_CNTRL, "Control Unit", "CNTRLUNIT", NULL}, | |
| {OPTION_SOAPMNE, 0, NULL, "DEFAULTMNE", NULL}, | |
| {OPTION_SOAPMNE, OPTION_SOAPMNE, "Using SOAP Mnemonics", "SOAPMNE", NULL}, | |
| {OPTION_FAST, 0, NULL, "REALTIME", NULL}, | |
| {OPTION_FAST, OPTION_FAST, "Fast Execution", "FAST", NULL}, | |
| {0} | |
| }; | |
| DEVICE cpu_dev = { | |
| "CPU", &cpu_unit, cpu_reg, cpu_mod, | |
| 1, 10, 16, 1, 10, 64, | |
| &cpu_ex, &cpu_dep, &cpu_reset, NULL, NULL, NULL, | |
| NULL, DEV_DEBUG, 0, dev_debug, | |
| NULL, NULL, &cpu_help, NULL, NULL, &cpu_description | |
| }; | |
| t_stat cpu_svc (UNIT *uptr) | |
| { | |
| // poll kbd to sense ^E to halt cpu execution. | |
| sim_activate_after (uptr, 300*1000); // poll each 300 msec | |
| sim_poll_kbd(); | |
| return SCPE_OK; | |
| } | |
| // return 0 if addr invalid, 1 if addr valid depending on allowed addrs given by ValidDA | |
| // set the ias TimingRing to AR is IAS is accessed | |
| int IsDrumAddrOk(int AR, int ValidDA) | |
| { | |
| // check if AR should be 9000 | |
| if ((STOR) && (ValidDA & vda_9000)) | |
| return (AR == 9000) ? 1:0; | |
| // Drum address | |
| if ((AR >= 0) && (AR < DRUMSIZE)) | |
| return (ValidDA & vda_D) ? 1:0; | |
| // cpu registers: acc (lo&hi), distibutor, console swithc reg: ok to check for Addr validity, ok to read, cannot write to it | |
| if ((AR >= 8000) && (AR <= 8003)) | |
| return (ValidDA & vda_A) ? 1:0; | |
| // index registers (ir) present if Storage Unit is enabled: ok to check for Addr validity, ok to read, cannot write to it | |
| if ((STOR) && (AR >= 8005) && (AR <= 8007)) | |
| return (ValidDA & vda_I) ? 1:0; | |
| // tape address present is tapes are enabled: ok to check for Addr validity, cannot read/write to it | |
| if ((CNTRL) && (AR >= 8010) && (AR <= 8015)) | |
| return (ValidDA & vda_T) ? 1:0; | |
| // inmediate access storage (ias) if Storage Unit is enabled: ok to check for Addr validity, ok to read/write | |
| if ((STOR) && (AR >= 9000) && (AR <= 9059)) { | |
| if (ValidDA & vda_S) { | |
| IAS_TimingRing = AR - 9000; // set Timing ring on address accesed | |
| return 1; | |
| } | |
| } | |
| // none of the above -> invalid address or address/mode combination | |
| return 0; | |
| } | |
| // return 0 if write addr invalid | |
| int WriteAddr(int AR, t_int64 d, int NegZero) | |
| { | |
| if (d) NegZero = 0; // sanity check on Minus Zero | |
| if ((STOR) && (AR >= 9000) && (AR <= 9059)) { // IAS is available at addr 9000-9059 | |
| IAS_TimingRing = AR - 9000; // not necessary as before any call to WriteAddr IsAddrOk is invoked. But ... just in case | |
| IAS[IAS_TimingRing] = d; | |
| IAS_NegativeZeroFlag[IAS_TimingRing] = NegZero; | |
| return 1; | |
| } else if ((AR >= 0) && (AR < DRUMSIZE) && (AR < MAXDRUMSIZE)) { | |
| if (d) NegZero = 0; // sanity check on Minus Zero | |
| DRUM[AR] = d; | |
| DRUM_NegativeZeroFlag[AR] = NegZero; | |
| return 1; | |
| } | |
| // none of the above -> invalid address or address/mode combination | |
| return 0; | |
| } | |
| // return 0 if read addr invalid | |
| int ReadAddr(int AR, t_int64 * d, int * NegZero) | |
| { | |
| int neg; | |
| // read from drum? | |
| if ((AR >= 0) && (AR < DRUMSIZE)) { | |
| *d = DRUM[AR]; | |
| neg = DRUM_NegativeZeroFlag[AR]; | |
| if (*d) DRUM_NegativeZeroFlag[AR] = 0; | |
| } else | |
| // read from cpu registers? | |
| if (AR == 8000) {*d = CSW; neg=0; } else | |
| if (AR == 8001) {*d = DIST; neg=DistNegativeZeroFlag; } else | |
| if (AR == 8002) {*d = ACC[0]; neg=AccNegativeZeroFlag; } else | |
| if (AR == 8003) {*d = ACC[1]; neg=AccNegativeZeroFlag; } else | |
| // read index registers (ir) ? | |
| if ((STOR) && (AR == 8005)) {*d = IR[0]; neg=0; } else | |
| if ((STOR) && (AR == 8006)) {*d = IR[1]; neg=0; } else | |
| if ((STOR) && (AR == 8007)) {*d = IR[2]; neg=0; } else | |
| // tape address ? | |
| if ((CNTRL) && (AR >= 8010) && (AR <= 8015)) { | |
| // cannot read/write to tape addresses | |
| return 0; | |
| } else | |
| // read inmediate access storage (ias)? | |
| if ((STOR) && (AR >= 9000) && (AR <= 9059)) { | |
| IAS_TimingRing = AR - 9000; | |
| *d = IAS[IAS_TimingRing]; | |
| neg = IAS_NegativeZeroFlag[IAS_TimingRing]; | |
| if (*d) IAS_NegativeZeroFlag[IAS_TimingRing] = 0; | |
| } else { | |
| // none of the above -> invalid address for read | |
| return 0; | |
| } | |
| if (*d) neg = 0; // sanity check on Minus Zero | |
| if (NegZero != NULL) *NegZero = neg; | |
| return 1; | |
| } | |
| // shift acc 1 digit. If direction > 0 to the left, if direction < 0 to the right. | |
| // Return digit going out of acc (with sign) | |
| int ShiftAcc(int direction) | |
| { | |
| t_int64 a0, a1; | |
| int neg = 0; | |
| int n, m; | |
| n = 0; | |
| a1 = ACC[1]; if (a1 < 0) {a1 = -a1; neg = 1;} | |
| a0 = ACC[0]; if (a0 < 0) {a0 = -a0; neg = 1;} | |
| if ((AccNegativeZeroFlag) && (ACC[0] == 0) && (ACC[1] == 0)) neg = 1; | |
| if (direction > 0) { // shift left | |
| n = Shift_Digits(&a1, 1); // n = Upper Acc high digit shifted out on the left | |
| m = Shift_Digits(&a0, 1); // m = intermediate digit that goes from one acc to the other | |
| a1 = a1 + (t_int64) m; | |
| } else if (direction < 0) { // shift right | |
| m = Shift_Digits(&a1, -1); // m = intermediate digit that goes from one acc to the other | |
| n = Shift_Digits(&a0, -1); // n = Lower Acc units digit shifted out on the right | |
| a0 = a0 + (t_int64) m * (1000000000L); | |
| } | |
| if (neg) {a1=-a1; a0=-a0; n=-n;} | |
| ACC[0] = a0; | |
| ACC[1] = a1; | |
| if ((neg == 1) && (a0 == 0) && (a1 == 0)) AccNegativeZeroFlag = 1; | |
| return n; | |
| } | |
| // float value format = mmmmmmmcc = 0.m x 10^(c-50) | |
| // mmmmmmm = mantissa | |
| // cc = modified characteristic (== exponent) | |
| // get modified characteristic of float d | |
| int GetExp(t_int64 d) | |
| { | |
| return (AbsWord(d) % 100); | |
| } | |
| // set modified characteristic of float d | |
| t_int64 SetExp(t_int64 d, int exp) | |
| { | |
| int neg = 0; | |
| if (d < 0) {neg=1; d=-d;} | |
| d = ((d / 100) * 100) + (exp % 100); | |
| if (neg) d=-d; | |
| return d; | |
| } | |
| // set result into ACC[1] and ACC[0] for float mult and division | |
| // get a 10 digits mantissa en ACC[1] | |
| // round to the 8th digit | |
| // add the modified characteristic (exp) | |
| // add sign, check for zero | |
| void MantissaRoundAndNormalizeToFloat(int * CpuStepsUsed, int neg, int exp) | |
| { | |
| // if high order digit of mantissa is zero, shift left once | |
| if (Get_HiDigit(ACC[1]) == 0) { | |
| ShiftAcc(1); | |
| *CpuStepsUsed = *CpuStepsUsed + 2; | |
| if (exp == 0) { | |
| OV = 1; | |
| } else { | |
| exp--; | |
| } | |
| } | |
| // round mantissa in ACC[1] to the 8th digit | |
| if (GetExp(ACC[1]) >= 50) { | |
| ACC[1] = ACC[1] + 100; | |
| if (ACC[1] >= D10) { | |
| ACC[1] = ACC[1] / 10; | |
| *CpuStepsUsed = *CpuStepsUsed + 2; | |
| if (exp == 99) { | |
| OV = 1; | |
| } else { | |
| exp++; | |
| } | |
| } | |
| } | |
| ACC[1] = SetExp(ACC[1], 0); | |
| // normalize mantissas | |
| while (( ACC[1] != 0) && (Get_HiDigit(ACC[1]) == 0)) { | |
| if (exp == 0) { | |
| OV = 1; | |
| break; // if zero, underflow | |
| } else { | |
| exp--; | |
| } | |
| ACC[1] = ACC[1] * 10; | |
| *CpuStepsUsed = *CpuStepsUsed + 2; | |
| } | |
| // set result | |
| if (exp < 0) {exp += 100; OV = 1;} | |
| if (exp > 99) {exp -= 100; OV = 1;} | |
| ACC[1] = neg * SetExp(ACC[1], exp); | |
| ACC[0] = 0; | |
| if ((ACC[1] / 100) == 0) ACC[1] = 0; // if mantissa is zero, all is zero | |
| AccNegativeZeroFlag = 0; | |
| } | |
| // add float to accumulator, set Overflow | |
| // return number of steps used | |
| int AddFloatToAcc(int bSubstractFlag, int bAbsFlag, int bNormalizeFlag) | |
| { | |
| int nSteps; | |
| int n, neg; | |
| t_int64 d; | |
| OV = 0; AccNegativeZeroFlag = 0; | |
| nSteps = 0; | |
| n = GetExp(ACC[1]) - GetExp(DIST); | |
| if (n == 0) { | |
| // no decimal aligning necessary. Mantissas ready to being added | |
| } else if ( n > 8) { | |
| DIST = ACC[1]; ACC[1] = 0; | |
| } else if ( n < -8) { | |
| ACC[1] = 0; | |
| } else { | |
| if (n < 0) { // if between -1 and -8 | |
| n = -n; // just remove sign on number of shifts to be done | |
| } else { // if between 1 and 8 | |
| d = ACC[1]; | |
| ACC[1] = DIST; DIST = d; // exchange distrib and upper acc | |
| nSteps += 2; | |
| } | |
| ACC[1] = SetExp(ACC[1], 0); // modified characteristic of upper set to zero | |
| while (n>0) { // shift right n digits | |
| ShiftAcc(-1); | |
| nSteps += 2; | |
| n--; | |
| } | |
| if (GetExp(ACC[1]) >= 50) { // should round? | |
| ACC[1] = ACC[1] + ((ACC[1] >= 0) ? 100:-100); | |
| } | |
| } | |
| d = DIST; | |
| if (bAbsFlag) if (d < 0) d = -d; | |
| if (bSubstractFlag) d = -d; | |
| if (((ACC[1] > 0) && (d < 0)) || ((ACC[1] < 0) && (d > 0))) nSteps += 4; | |
| ACC[1] = (ACC[1] / 100) + (d / 100); // add/substract mantissas (positions 10-3) | |
| n = GetExp(DIST); // get modified characteristic from dist | |
| if (ACC[1] < 0) { | |
| ACC[1] = -ACC[1]; neg=-1; | |
| } else { | |
| neg=1; | |
| } | |
| if (ACC[1] >= D8) { // overflow? | |
| if ((ACC[1] % 10) >= 5) { // should round? | |
| ACC[1] = ACC[1] / 10 + 1; // yes, shift right, keep extra 1 in high pos, add rounding | |
| nSteps += 4; | |
| } else { | |
| ACC[1] = ACC[1] / 10; // no, just shift | |
| } | |
| n++; // add 1 to dist modified characteristic | |
| if (n > 99) {OV = 1; n=0;} // overflow. Set modified characteristic to zero | |
| nSteps += 4; | |
| } | |
| if (ACC[1] == 0) { | |
| n = 0; // if mantissa is zero, mod. char is set to zero also | |
| bNormalizeFlag = 0; // must not normalize | |
| nSteps += 2; | |
| } | |
| ACC[1] = SetExp(neg * ACC[1] * 100, n); // insert modified characteristic of dist into upper acc | |
| ACC[0] = 0; // lower acc set to zero | |
| if (bNormalizeFlag) { | |
| while(Get_HiDigit(ACC[1]) == 0) { // while hi digit (digit 10) is zero -> normalize | |
| n = GetExp(ACC[1]); // get modified characteristic | |
| if (n == 0) { | |
| OV = 1; break; // if zero, underflow | |
| } | |
| n--; | |
| ACC[1] = SetExp(ACC[1]/100 * 1000, n); // left shift mantissa, set modified characteristic | |
| nSteps += 3; | |
| } | |
| } | |
| return nSteps; | |
| } | |
| int bAccNegComplement; // flag to signals acc has complemented a negative ass (== sign adjust) | |
| // needed to compute execution cycles taken by the intruction | |
| // add to accumulator, set Overflow | |
| void AddToAcc(t_int64 a1, t_int64 a0, int bSetOverflow) | |
| { | |
| OV = 0; AccNegativeZeroFlag = 0; | |
| bAccNegComplement = 0; | |
| ACC[0] += a0; | |
| ACC[1] += a1; | |
| // adjust carry from Lower ACC to Upper Acc | |
| if (ACC[0] >= D10) { ACC[0] -= D10; ACC[1]++; } | |
| if (ACC[0] <= -D10) { ACC[0] += D10; ACC[1]--; } | |
| // ajust sign | |
| if ((ACC[0] > 0) && (ACC[1] < 0)) { | |
| ACC[0] -= D10; ACC[1]++; | |
| bAccNegComplement = 1; | |
| } | |
| if ((ACC[0] < 0) && (ACC[1] > 0)) { | |
| ACC[0] += D10; ACC[1]--; | |
| bAccNegComplement = 1; | |
| } | |
| // check overflow | |
| if (bSetOverflow) { | |
| if ((ACC[1] >= D10) || (ACC[1] <= -D10)) { | |
| ACC[1] = ACC[1] % D10; | |
| OV=1; | |
| } | |
| } | |
| } | |
| t_int64 SetDA(t_int64 d, int DA) | |
| { | |
| int neg = 0; | |
| int op, nn, IA; | |
| if (DA < 0) DA=-DA; | |
| if (d < 0) {d=-d; neg=1;} | |
| // extract parts of word | |
| op = Shift_Digits(&d, 2); | |
| nn = Shift_Digits(&d, 4); // discard current DA | |
| IA = Shift_Digits(&d, 4); | |
| // rebuild word with new DA | |
| d = (t_int64) op * D8 + | |
| (t_int64) DA * D4 + | |
| (t_int64) IA; | |
| if (neg) d=-d; | |
| return d; | |
| } | |
| // set last 4 digits in d with IA contents | |
| t_int64 SetIA(t_int64 d, int IA) | |
| { | |
| int neg = 0; | |
| if (IA < 0) IA=-IA; | |
| if (d < 0) {d=-d; neg=1;} | |
| d = d - ( d % D4); | |
| d = d + (IA % D4); | |
| if (neg) d=-d; | |
| return d; | |
| } | |
| // set last 2 digits in d with IA contents | |
| t_int64 SetIA2(t_int64 d, int n) | |
| { | |
| int neg = 0; | |
| if (n < 0) n=-n; | |
| if (d < 0) {d=-d; neg=1;} | |
| d = d - ( d % 100); | |
| d = d + ( n % 100); | |
| if (neg) d=-d; | |
| return d; | |
| } | |
| // normalize to 4 digits, 10 complements | |
| void NormalizeAddr(int * addr) | |
| { | |
| while (*addr >= 10000) *addr -= 10000; | |
| while (*addr < 0) *addr += 10000; | |
| } | |
| // apply index register to a tagged address | |
| // removes tag, replace value with developed address | |
| // return 1 if address was tagged, and has been replaced by developed addr | |
| int ApplyIndexRegister(int * addr) | |
| { | |
| int n = 0; | |
| int norm = 0; | |
| // check for tag and untag | |
| if ((*addr >= 2000) && (*addr < 4000)) {n = 1; norm = 2000; } else | |
| if ((*addr >= 4000) && (*addr < 6000)) {n = 2; norm = 4000; } else | |
| if ((*addr >= 6000) && (*addr < 8000)) {n = 3; norm = 6000; } else | |
| if ((*addr >= 9200) && (*addr < 9260)) {n = 1; norm = 200; } else | |
| if ((*addr >= 9400) && (*addr < 9460)) {n = 2; norm = 400; } else | |
| if ((*addr >= 9600) && (*addr < 9660)) {n = 3; norm = 600; } else | |
| return 0; // address not tagged | |
| *addr = *addr + IR[n-1] - norm; | |
| NormalizeAddr(addr); | |
| return 1; | |
| } | |
| // opcode decode | |
| // input: prior to call DecodeOpcode PR cpu register must be loaded with the word to decode | |
| // output: decoded instruction as opcode, DA, IA parts | |
| // returns opname: points to opcode name or NULL if undef opcode | |
| CONST char * DecodeOpcode(t_int64 d, int * opcode, int * DA, int * IA) | |
| { | |
| CONST char * opname; | |
| *opcode = Shift_Digits(&d, 2); // current inste opcode | |
| *DA = Shift_Digits(&d, 4); // addr of data used by current instr | |
| *IA = Shift_Digits(&d, 4); // addr of next instr | |
| opname = (cpu_unit.flags & OPTION_SOAPMNE) ? base_ops[*opcode].name2 : base_ops[*opcode].name1; | |
| if (base_ops[*opcode].option == opStorUnit) { | |
| // opcode available if IBM 653 Storage Unit is present | |
| if (STOR == 0) return NULL; | |
| } else if (base_ops[*opcode].option == opCntrlUnit) { | |
| // opcode available if IBM 652 Control Unit is present | |
| if (CNTRL == 0) return NULL; | |
| } | |
| return opname; | |
| } | |
| // transfer (copy words) between IAS and DRUM | |
| // dir = "D->I" or "I->D" | |
| // bEOB = 1 -> End of IAS band terminated transfer | |
| // return number of words transfered | |
| int TransferIAS(CONST char * dir, int bEOB) | |
| { | |
| int n, f0, t0, f1, t1, ec; | |
| n = f0 = t0 = f1 = t1 = ec = 0; | |
| while (1) { | |
| if (dir[0] == 'D') { | |
| IAS[IAS_TimingRing] = DRUM[AR]; | |
| IAS_NegativeZeroFlag[IAS_TimingRing] = DRUM_NegativeZeroFlag[AR]; | |
| if (n==0) {f0=AR; t0=IAS_TimingRing;} | |
| f1=AR; t1=IAS_TimingRing; | |
| } else { | |
| DRUM[AR] = IAS[IAS_TimingRing]; | |
| DRUM_NegativeZeroFlag[AR] = IAS_NegativeZeroFlag[IAS_TimingRing]; | |
| if (n==0) {t0=AR; f0=IAS_TimingRing;} | |
| t1=AR; f1=IAS_TimingRing; | |
| } | |
| n++; | |
| if ((AR % 50) == 49) { ec = 0; break; } | |
| if (IAS_TimingRing == 9059) { ec = 1; break; } | |
| if ((bEOB) && ((IAS_TimingRing % 10) == 9)) { ec = 2; break; } | |
| AR++; IAS_TimingRing++; | |
| } | |
| sim_debug(DEBUG_DATA, &cpu_dev, " ... Copy %04d-%04d to %04d-%04d (%d words)\n", | |
| f0, f1, t0, t1, n); | |
| sim_debug(DEBUG_DATA, &cpu_dev, " ended by end of %s condition\n", | |
| (ec == 0) ? "Drum band" : (ec == 1) ? "IAS" : "IAS Block"); | |
| IAS_TimingRing = (IAS_TimingRing + 1) % 60; // incr timing ring at end of pch | |
| return n; | |
| } | |
| // opcode execution | |
| // input: opcode, DA (data address), DrumAddr (current word under the r/w heads. Needed to calculate time used on instr execution) | |
| // prior to call ExecOpcode DIST cpu register must be loaded with the needed data for inst execution | |
| // output: bBranchToDA: =1 if next inst must be taken from DA register instead of DA | |
| // CpuStepsUsed: number of steps (=word time) used on program execution | |
| t_stat ExecOpcode(int opcode, int DA, | |
| int * bBranchToDA, | |
| int DrumAddr, | |
| int * CpuStepsUsed) | |
| { | |
| t_stat reason = 0; | |
| t_int64 d; | |
| int i, n, neg; | |
| int bUsingIAS; | |
| *bBranchToDA = 0; | |
| *CpuStepsUsed = 0; | |
| switch(opcode) { | |
| case OP_NOOP : // No operation | |
| if ((IC == 0) && ((PR % D4) == 0)) reason = STOP_HALT; // if loop on NOOP on addr zero -> machine idle -> stop cpu | |
| break; | |
| case OP_STOP : // Stop if console switch is set to stop, otherwise continue as a NO-OP | |
| if (CSWProgStop) { | |
| reason = STOP_PROG; | |
| // stops has the consequence to prevent AR to be set with IA contents (to point to next instruction). | |
| // so must set a flag so next setp/go scp command will take next inst to execute from | |
| // IA field in PR reg instead of AR | |
| ProgStopFlag = 1; | |
| } | |
| break; | |
| // arithmetic | |
| case OP_RAL: // Reset and Add into Lower | |
| case OP_RSL: // Reset and Subtract into Lower | |
| case OP_RAABL: // Reset and Add Absolute into Lower | |
| case OP_RSABL: // Reset and Subtract Absolute into Lower | |
| d = DIST; | |
| if ((opcode == OP_RAABL) || (opcode == OP_RSABL)) d = AbsWord(d); | |
| if ((opcode == OP_RSL) || (opcode == OP_RSABL)) d = -d; | |
| OV = 0; AccNegativeZeroFlag = 0; | |
| ACC[1] = 0; | |
| ACC[0] = d; | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| // sequence chart for Add/Substract | |
| // (1) (0..49) (1) (0/1) (2) (0/2) (1) | |
| // Enable Search Data to Wait Dist to Complement Remove A | |
| // Dist Data dist for even Acc Neg Sum interlock | |
| // (1) (1) (1) (0..49) | |
| // Restart IA to AR Enable PR Search next | |
| // Signal Inst | |
| *CpuStepsUsed = 1+1+2+1 | |
| +(DrumAddr % 2); // using lower acc -> wait for even | |
| // no need to complement neg sum | |
| break; | |
| case OP_AL: // Add to Lower | |
| case OP_SL: // Subtract from Lower | |
| case OP_AABL: // Add Absolute to lower | |
| case OP_SABL: // Subtract Absolute from lower | |
| if ((opcode == OP_AL) && (ACC[1] == 0) && (ACC[0] == 0) && (AccNegativeZeroFlag) && | |
| (DIST == 0) && (DistNegativeZeroFlag)) { | |
| // special case as stated in Operation manual 22(22-6060-2_650_OperMan.pdf), page 95 | |
| // Acc result on minus zero if acc contains minus zero and AU or AL with a drum | |
| // location that contains minus zero | |
| OV=0; | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... ACC: 0000000000 0000000000- (Minus Zero), OV: 0\n"); | |
| // acc keeps the minus zero it already has | |
| break; | |
| } | |
| d = DIST; | |
| if ((opcode == OP_AABL) || (opcode == OP_SABL)) d = AbsWord(d); | |
| if ((opcode == OP_SL) || (opcode == OP_SABL)) d = -d; | |
| AddToAcc(0,d,1); | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| *CpuStepsUsed = 1+1+2+1 | |
| +(DrumAddr % 2) // using lower acc -> wait for even | |
| +(bAccNegComplement ? 2:0); // acc sign change -> need to complement neg sum (two steps) | |
| break; | |
| case OP_RAU: // Reset and Add into Upper | |
| case OP_RSU: // Reset and Subtract into Upper | |
| case OP_AU: // Add to Upper | |
| case OP_SU: // Substract from Upper | |
| if ((opcode == OP_AU) && (ACC[1] == 0) && (ACC[0] == 0) && (AccNegativeZeroFlag) && | |
| (DIST == 0) && (DistNegativeZeroFlag)) { | |
| // special case as stated in Operation manual 22(22-6060-2_650_OperMan.pdf), page 95 | |
| // Acc result on minus zero if acc contains minus zero and AU or AL with a drum | |
| // location that contains minus zero | |
| OV=0; | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... ACC: 0000000000 0000000000- (Minus Zero), OV: 0\n"); | |
| // acc keeps the minus zero it already has | |
| break; | |
| } | |
| d = DIST; | |
| if ((opcode == OP_RAU) || (opcode == OP_RSU)) ACC[1] = ACC[0] = 0; | |
| if ((opcode == OP_SU) || (opcode == OP_RSU)) d = -d; | |
| AddToAcc(d,0,1); | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| *CpuStepsUsed = 1+1+2+1 | |
| +((DrumAddr+1) % 2) // using upper acc -> wait for odd | |
| +(bAccNegComplement ? 2:0); // acc sign change -> need to complement neg sum (two steps) | |
| break; | |
| // Multiply/divide | |
| case OP_MULT: // Multiply | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... Mult ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... by DIST: %06d%04d%c\n", | |
| printfd); | |
| if ((ACC[1] == 0) && (ACC[0] == 1) && (DIST == 0) && (DistNegativeZeroFlag)) { | |
| // special case as stated in Operation manual 22(22-6060-2_650_OperMan.pdf), page 95 | |
| // Acc result on minus zero if a drum location that contains minus zero | |
| // is multiplied by +1 | |
| OV = 0; | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... Mult result ACC: 0000000000 0000000000- (Minus Zero), OV: 0\n"); | |
| // acc set to minus zero | |
| ACC[1] = ACC[0] = 0; | |
| AccNegativeZeroFlag = 1; | |
| break; | |
| } | |
| *CpuStepsUsed = 0; | |
| OV = 0; | |
| neg = (DIST < 0) ? 1:0; if (AccNegative) neg = 1-neg; | |
| d = AbsWord(DIST); | |
| ACC[0] = AbsWord(ACC[0]); | |
| ACC[1] = AbsWord(ACC[1]); | |
| for(i=0;i<10;i++) { | |
| n = ShiftAcc(1); | |
| *CpuStepsUsed = *CpuStepsUsed + 2; | |
| while (n-- > 0) { | |
| AddToAcc(0, d, 1); | |
| *CpuStepsUsed = *CpuStepsUsed + 18; | |
| if (OV) break; | |
| } | |
| if (OV) break; | |
| } | |
| if (neg) { | |
| ACC[0] = -ACC[0]; | |
| ACC[1] = -ACC[1]; | |
| } | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| // sequence chart for Multiply/Divide | |
| // (1) (0..49) (1) (0/1) (20..200) (1) | |
| // Enable Search Data to Wait Mult/Div Remove A | |
| // Dist Data dist for even loop interlock | |
| // (1) (1) (1) (0..49) | |
| // Restart IA to AR Enable PR Search next | |
| // Signal Inst | |
| *CpuStepsUsed = 1+1+1+1 | |
| +(DrumAddr % 2) // wait for even | |
| +*CpuStepsUsed; // i holds the number of loops done | |
| break; | |
| case OP_DIV: // Divide | |
| case OP_DIVRU: // Divide and reset upper accumulator | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... Div ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... by DIST: %06d%04d%c\n", | |
| printfd); | |
| if (DIST == 0) { | |
| OV = 1; | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "Divide By Zero -> OV set\n"); | |
| reason = STOP_OV; // divisor zero allways stops the machine | |
| } else if (AbsWord(DIST) <= AbsWord(ACC[1])) { | |
| OV = 1; | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "Quotient Overflow -> OV set and ERROR\n"); | |
| reason = STOP_OV; // quotient overfow allways stops the machine | |
| } else { | |
| *CpuStepsUsed = 0; | |
| OV = 0; | |
| neg = (DIST < 0) ? 1:0; if (AccNegative) neg = 1-neg; | |
| d = AbsWord(DIST); | |
| ACC[0] = AbsWord(ACC[0]); | |
| ACC[1] = AbsWord(ACC[1]); | |
| for(i=0;i<10;i++) { | |
| n = ShiftAcc(1); | |
| ACC[1] = ACC[1] + n * D10; | |
| *CpuStepsUsed = *CpuStepsUsed + 2; | |
| while (d <= ACC[1]) { | |
| AddToAcc(-d, 0, 0); | |
| *CpuStepsUsed = *CpuStepsUsed + 18; | |
| ACC[0]++; | |
| } | |
| } | |
| if (neg) { | |
| ACC[0] = -ACC[0]; | |
| ACC[1] = -ACC[1]; | |
| } | |
| if (opcode == OP_DIVRU) { | |
| ACC[1] = 0; | |
| } | |
| *CpuStepsUsed = 1+1+1+1 | |
| +(DrumAddr % 2) // wait for even | |
| +*CpuStepsUsed + 40; // i holds the number of loops done | |
| } | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... Div result ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| break; | |
| // shift | |
| case OP_SLT: // Shift Left | |
| case OP_SRT: // Shift Right | |
| case OP_SRD: // Shift Right and Round | |
| n = DA % 10; // number of digits to shift | |
| if (opcode == OP_SRD) if (n == 0) n=10; // SRD 0000 means 10 sifts. SRT/SLT 0000 means no shifts | |
| d = 0; | |
| while (n-- > 0) { | |
| d = ShiftAcc((opcode == OP_SLT) ? 1:-1); | |
| } | |
| if (opcode == OP_SRD) { | |
| if (d <= - 5) AddToAcc(0,-1,1); | |
| if (d >= 5) AddToAcc(0,+1,1); | |
| OV = 0; | |
| } | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| // sequence chart for shift | |
| // (1) (0/1) (2) (1) | |
| // Enable Wait Per Remove A | |
| // Sh count for even shift interlock | |
| // (0/1) (1) (1) (0..49) | |
| // Restart IA to AR Enable PR Search next | |
| // Signal Inst | |
| *CpuStepsUsed = 1+1+1 | |
| +(DrumAddr % 2) // wait for even | |
| + 2*(DA % 10) // number of shifts done | |
| + ((opcode == OP_SRD) ? 1:0); | |
| break; | |
| case OP_SCT : // Shift accumulator left and count | |
| n = 10 - DA % 10; // shift count (nine's complement of unit digit of DA) | |
| neg = AccNegative; // save acc sign | |
| ACC[0] = AbsWord(ACC[0]); | |
| ACC[1] = AbsWord(ACC[1]); | |
| if (n==10) n=0; | |
| if (ACC[1] == 0) { | |
| // upper acc is zero -> will have 10 or more shifts | |
| ACC[1] = ACC[0]; | |
| ACC[0] = 10; | |
| if (n) { | |
| OV = 1; // overflow because n <> 0 | |
| } else { | |
| if (Get_HiDigit(ACC[1]) == 0) OV = 1; // overflow because not just 10 shifts | |
| } | |
| } else if (Get_HiDigit(ACC[1]) != 0) { | |
| // no shift will be done | |
| ACC[0] = SetIA2(ACC[0], 0); // replace last two digits by 00 | |
| } else { | |
| while (Get_HiDigit(ACC[1]) == 0) { | |
| ShiftAcc(1); // shift left | |
| if (n==10) { | |
| OV = 1; | |
| break; | |
| } | |
| n++; | |
| } | |
| ACC[0] = SetIA2(ACC[0], n); // replace last two digits by 00 | |
| } | |
| AccNegativeZeroFlag = 0; | |
| if (neg) {ACC[0] = -ACC[0]; ACC[1] = -ACC[1]; } | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| *CpuStepsUsed = 1+1+1 | |
| +(DrumAddr % 2) // wait for even | |
| + 2*(DA % 10); // number of shifts done | |
| break; | |
| // load and store | |
| case OP_STL: // Store Lower in Mem | |
| case OP_STU: // Store Upper in Mem | |
| if ((ACC[0] == 0) && (ACC[1] == 0) && (AccNegativeZeroFlag)) { | |
| DistNegativeZeroFlag = 1; | |
| } else { | |
| DistNegativeZeroFlag = 0; | |
| } | |
| DIST = (opcode == OP_STU) ? ACC[1] : ACC[0]; | |
| // sequence chart for store | |
| // (1) (0/1) (1) (0..49) (1) (1) (1) | |
| // Enable Wait L/U acc Search Store IA to AR Enable PR | |
| // Dist for even to dist data data | |
| // or odd | |
| *CpuStepsUsed = 1+1+1+1+1+ | |
| + (((opcode == OP_STU) ? DrumAddr:DrumAddr+1) % 2); // wait for odd/even depending on STU/STL opcode | |
| break; | |
| case OP_STD: // store distributor | |
| *CpuStepsUsed = 1+1+1+1; | |
| break; | |
| case OP_STDA: // Store Lower Data Address | |
| n = ((ACC[0] / D4) % D4); // get data addr xxDDDDxxxx from lower Acc | |
| d = SetDA(DIST, n); // replace it in distributor | |
| if ((d == 0) && ((DIST < 0) || ( (DIST == 0) && (DistNegativeZeroFlag) ))) { | |
| // if dist results in zero but was negative or negative zero before replacing digits | |
| // then it is set to minus zero | |
| DistNegativeZeroFlag = 1; | |
| } else { | |
| DistNegativeZeroFlag = 0; | |
| } | |
| DIST = d; | |
| *CpuStepsUsed = 1+1+1+1 | |
| +(DrumAddr % 2); // wait for even | |
| break; | |
| case OP_STIA: // Store Lower Instruction Address | |
| n = (ACC[0] % D4); // get inst addr xxyyyyAAAA | |
| d = SetIA(DIST, n); // replace it in distributor | |
| if ((d == 0) && ((DIST < 0) || ( (DIST == 0) && (DistNegativeZeroFlag) ))) { | |
| // if dist results in zero but was negative or negative zero before replacing digits | |
| // then it is set to minus zero | |
| DistNegativeZeroFlag = 1; | |
| } else { | |
| DistNegativeZeroFlag = 0; | |
| } | |
| DIST = d; | |
| *CpuStepsUsed = 1+1+1+1 | |
| +(DrumAddr % 2); // wait for even | |
| break; | |
| case OP_LD: // Load Distributor | |
| *CpuStepsUsed = 1+1+1+1; | |
| break; | |
| case OP_TLU: // Table lookup | |
| { | |
| char s[6]; | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... Search DIST: %06d%04d%c '%s'\n", | |
| printfd, | |
| word_to_ascii(s, 1, 5, DIST)); | |
| bUsingIAS = (AR >= 9000) ? 1:0; | |
| if (bUsingIAS) { | |
| AR = DA; // if TLU is searching on IAS, search starts at given addr | |
| } else { | |
| AR = (DA / 50) * 50; // set AR to start of drum band based on DA | |
| } | |
| AR--; n=-1; | |
| while (1) { | |
| AR++; n++; | |
| if (0==IsDrumAddrOk(AR, vda_DS)) { | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "Invalid AR addr %d ERROR\n", AR); | |
| reason = STOP_ADDR; | |
| break; | |
| } | |
| if ((bUsingIAS == 0) && ((AR % 50) > 47)) continue; // skip addr 48 & 49 of band that cannot be used for tables | |
| ReadAddr(AR, &d, NULL); // read table argument | |
| if (AbsWord(d) >= AbsWord(DIST)) { | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... Found %04d: %06d%04d%c '%s'\n", | |
| AR, printfw(d,0), | |
| word_to_ascii(s, 1, 5, d)); | |
| break; // found | |
| } | |
| } | |
| // if tlu on ias, incr timing ring at end of instr execution | |
| if (bUsingIAS) IAS_TimingRing = (IAS_TimingRing + 1) % 60; | |
| // set the result as xxNNNNxxxx in lower acc | |
| ACC[0] = SetDA(ACC[0], DA+n); | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... Result ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| } | |
| *CpuStepsUsed = 1+1+1+1+1+1 | |
| +(DrumAddr % 2) // wait for even | |
| + n; // number of reads to find the argument searched for | |
| break; | |
| // branch | |
| case OP_BRD1: case OP_BRD2: case OP_BRD3: case OP_BRD4: case OP_BRD5: // Branch on 8 in distributor positions 1-10 | |
| case OP_BRD6: case OP_BRD7: case OP_BRD8: case OP_BRD9: case OP_BRD10: | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... Check DIST: %06d%04d%c\n", | |
| printfd); | |
| d = DIST; | |
| n = opcode - OP_BRD10; if (n == 0) n = 10; | |
| while (--n > 0) d = d / 10; | |
| d = d % 10; | |
| if (d == 8) { | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "Digit is %d -> Branch Taken\n", (int32) d); | |
| *bBranchToDA = 1; // IA (next instr addr) will be taken from DA. Branch taken | |
| } else if (d == 9) { | |
| // IA kept as already set. Branch not taken | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "Digit is %d -> Branch Not Taken\n", (int32) d); | |
| } else { | |
| // any other value for tested digit -> stop | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "Digit is %d -> Branch ERROR\n", (int32) d); | |
| reason = STOP_ERRO; | |
| break; | |
| } | |
| *CpuStepsUsed = 1+1 | |
| + ((*bBranchToDA) ? 1:0); // one extra step needed if branch taken | |
| break; | |
| case OP_BRNZU: // Branch on Non-Zero in Upper | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| if (ACC[1] != 0) { | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "Upper ACC not Zero -> Branch Taken\n"); | |
| *bBranchToDA = 1; | |
| } | |
| *CpuStepsUsed = 1+1 | |
| +(DrumAddr % 2) // wait for even | |
| + ((*bBranchToDA) ? 1:0); // one extra step needed if branch taken | |
| break; | |
| case OP_BRNZ: // Branch on Non-Zero | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| if ((ACC[1] != 0) || (ACC[0] != 0)) { | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "Not Zero -> Branch Taken\n"); | |
| *bBranchToDA = 1; | |
| } | |
| *CpuStepsUsed = 1 | |
| +((DrumAddr+1) % 2) // wait for odd | |
| + ((*bBranchToDA) ? 1:0); // one extra step needed if branch taken | |
| break; | |
| case OP_BRMIN: // Branch on Minus | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| if (AccNegative) { | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "Is Negative -> Branch Taken\n"); | |
| *bBranchToDA = 1; | |
| } | |
| *CpuStepsUsed = 1+1 | |
| + ((*bBranchToDA) ? 1:0); // one extra step needed if branch taken | |
| break; | |
| case OP_BROV: // Branch on Overflow | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... Check OV: %d\n", OV); | |
| if (OV) { | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "OV Set -> Branch Taken\n"); | |
| *bBranchToDA = 1; | |
| } | |
| *CpuStepsUsed = 1+1 | |
| + ((*bBranchToDA) ? 1:0); // one extra step needed if branch taken | |
| break; | |
| // Card I/O | |
| case OP_RD: // Read a card | |
| bUsingIAS = (AR >= 9000) ? 1:0; | |
| { | |
| char s[6]; | |
| if (bUsingIAS == 0) { | |
| AR = (DA / 50) * 50 + 1; // Drum Read Band is XX01 to XX10 or XX51 to XX60 | |
| } | |
| reason = cdr_cmd(&cdr_unit[1], IO_RDS, AR); | |
| if (reason == SCPE_NOCARDS) { | |
| reason = STOP_CARD; | |
| break; | |
| } else if (reason != SCPE_OK) { | |
| break; | |
| } | |
| // copy card data from IO Sync buffer to drum/ias | |
| for (i=0;i<10;i++) { | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... Read Card %04d: %06d%04d%c '%s'\n", | |
| AR+i, printfw(IOSync[i],IOSync_NegativeZeroFlag[i]), | |
| word_to_ascii(s, 1, 5, IOSync[i])); | |
| if (bUsingIAS == 0) { | |
| DRUM[AR + i] = IOSync[i]; | |
| DRUM_NegativeZeroFlag[AR + i] = IOSync_NegativeZeroFlag[i]; | |
| } else { | |
| n = AR - 9000 + i; | |
| IAS[n] = IOSync[i]; | |
| IAS_NegativeZeroFlag[n] = IOSync_NegativeZeroFlag[i]; | |
| if ((n % 10) == 9) break; // hit ias end of block, terminate read even if transfered less than 10 words | |
| } | |
| } | |
| if (bUsingIAS) IAS_TimingRing = DA; // is using ias, set timing ring on instr completition | |
| if (cdr_unit[1].u5 & URCSTA_LOAD) { | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... Is a LOAD Card\n"); | |
| *bBranchToDA = 1; // load card -> next instr is taken from DA | |
| } | |
| } | |
| // 300 msec read cycle, 270 available for computing | |
| *CpuStepsUsed = 312; // 30 msec div 0.096 msec word time; | |
| InterLockCount[IL_RD1] = 3120; // 300 msec for read card processing | |
| break; | |
| case OP_PCH: // Punch a card | |
| bUsingIAS = (AR >= 9000) ? 1:0; | |
| { | |
| char s[6]; | |
| if (bUsingIAS == 0) { | |
| AR = (DA / 50) * 50 + 27; // Drum Read Band is XX27 to XX36 or XX77 to XX86 | |
| } | |
| // clear IO Sync buffer | |
| for (i=0;i<10;i++) IOSync[i] = IOSync_NegativeZeroFlag[i] = 0; | |
| // copy card data to IO Sync buffer from drum/ias | |
| for (i=0;i<10;i++) { | |
| if (bUsingIAS == 0) { | |
| IOSync[i] = DRUM[AR + i]; | |
| IOSync_NegativeZeroFlag[i] = DRUM_NegativeZeroFlag[AR + i]; | |
| } else { | |
| n = AR - 9000 + i; | |
| IOSync[i] = IAS[n]; | |
| IOSync_NegativeZeroFlag[i] = IAS_NegativeZeroFlag[n]; | |
| IAS_TimingRing = i; | |
| if ((n % 10) == 9) break; // hit ias end of block, terminate even if transfered less than 10 words | |
| } | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... Punch Card %04d: %06d%04d%c '%s'\n", | |
| AR+i, printfw(IOSync[i],IOSync_NegativeZeroFlag[i]), | |
| word_to_ascii(s, 1, 5, IOSync[i])); | |
| } | |
| reason = cdp_cmd(&cdp_unit[1], IO_WRS,AR); | |
| if (reason == SCPE_NOCARDS) { | |
| reason = STOP_CARD; | |
| break; | |
| } else if (reason != SCPE_OK) { | |
| break; | |
| } | |
| if (bUsingIAS) IAS_TimingRing = (IAS_TimingRing + 1) % 60; // incr timing ring at end of pch | |
| } | |
| // 600 msec punch cycle, 565 available for computing | |
| *CpuStepsUsed = 365; // 35 msec div 0.096 msec word time; | |
| InterLockCount[IL_WR1] = 6250; // 600 msec for punch card processing | |
| break; | |
| // IAS - Immediate Access Storage | |
| case OP_SET: // Set IAS Timing Ring | |
| *CpuStepsUsed = 1+1+1; | |
| break; | |
| case OP_LDI: // Load IAS (from Drum) | |
| n = TransferIAS("D->I", 0); // transfer drum to ias, end of ias block does not terminate transfer | |
| *CpuStepsUsed = 1+1+1+n; | |
| break; | |
| case OP_STI: // Store IAS (to Drum) | |
| n = TransferIAS("I->D", 0); // transfer ias to drum, end of ias block does not terminate transfer | |
| *CpuStepsUsed = 1+1+1+n; | |
| break; | |
| case OP_LIB: // Load IAS Block (from Drum) | |
| n = TransferIAS("D->I", 1); // transfer drum to ias, end of ias block does not terminate transfer | |
| *CpuStepsUsed = 1+1+1+n; | |
| break; | |
| case OP_SIB: // Store IAS Block (to Drum) | |
| n = TransferIAS("I->D", 1); // transfer ias to drum, end of ias block does not terminate transfer | |
| *CpuStepsUsed = 1+1+1+n; | |
| break; | |
| // Index Register | |
| case OP_AXA: // Add/Substract [with reset] to IRA | |
| case OP_SXA: | |
| case OP_RAA: | |
| case OP_RSA: | |
| n = IR[0]; | |
| if ((opcode == OP_RAA) || (opcode == OP_RSA)) n = 0; | |
| if (DA >= 8000) { | |
| ReadAddr(DA, &d, NULL); | |
| i = (int) (d % D4); | |
| } else { | |
| i = DA; | |
| } | |
| n = n + (((opcode == OP_AXA) || (opcode == OP_RAA)) ? i : -i); | |
| NormalizeAddr(&n); | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... IRA: %04d\n", | |
| n); | |
| IR[0] = n; | |
| *CpuStepsUsed = 1+1+1; | |
| break; | |
| case OP_AXB: // Add/Substract [with reset] to IRB | |
| case OP_SXB: | |
| case OP_RAB: | |
| case OP_RSB: | |
| n = IR[1]; | |
| if ((opcode == OP_RAB) || (opcode == OP_RSB)) n = 0; | |
| if (DA >= 8000) { | |
| ReadAddr(DA, &d, NULL); | |
| i = (int) (d % D4); | |
| } else { | |
| i = DA; | |
| } | |
| n = n + (((opcode == OP_AXB) || (opcode == OP_RAB)) ? i : -i); | |
| NormalizeAddr(&n); | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... IRB: %04d\n", | |
| n); | |
| IR[1] = n; | |
| *CpuStepsUsed = 1+1+1; | |
| break; | |
| case OP_AXC: // Add/Substract [with reset] to IRC | |
| case OP_SXC: | |
| case OP_RAC: | |
| case OP_RSC: | |
| n = IR[2]; | |
| if ((opcode == OP_RAC) || (opcode == OP_RSC)) n = 0; | |
| if (DA >= 8000) { | |
| ReadAddr(DA, &d, NULL); | |
| i = (int) (d % D4); | |
| } else { | |
| i = DA; | |
| } | |
| n = n + (((opcode == OP_AXC) || (opcode == OP_RAC)) ? i : -i); | |
| NormalizeAddr(&n); | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... IRC: %04d\n", | |
| n); | |
| IR[2] = n; | |
| *CpuStepsUsed = 1+1+1; | |
| break; | |
| case OP_BMA: // Branch on IR Minus | |
| case OP_BMB: | |
| case OP_BMC: | |
| i = ((opcode == OP_BMA) ? 0 : (opcode == OP_BMB) ? 1 : 2); | |
| n = IR[i]; | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... IR%c: %04d\n", | |
| i+'A', n); | |
| if (n<0) { | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "Is Negative -> Branch Taken\n"); | |
| *bBranchToDA = 1; | |
| } | |
| *CpuStepsUsed = 1+1 | |
| + ((*bBranchToDA) ? 1:0); // one extra step needed if branch taken | |
| break; | |
| case OP_NZA: // Branch on IR Zero | |
| case OP_NZB: | |
| case OP_NZC: | |
| i = ((opcode == OP_NZA) ? 0 : (opcode == OP_NZB) ? 1 : 2); | |
| n = IR[i]; | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... IR%c: %04d\n", | |
| i+'A', n); | |
| if (n==0) { | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "Is Zero -> Branch Taken\n"); | |
| *bBranchToDA = 1; | |
| } | |
| *CpuStepsUsed = 1+1 | |
| + ((*bBranchToDA) ? 1:0); // one extra step needed if branch taken | |
| break; | |
| // floating point | |
| case OP_FAD: // FP Add | |
| case OP_UFA: // Unnormalized FP Add | |
| case OP_FSB: // FP Sub | |
| case OP_FAM: // FP Add Absolute value | |
| case OP_FSM: // FP Sub Absolute | |
| n = AddFloatToAcc((opcode == OP_FSB) || (opcode == OP_FSM), // subtract? | |
| (opcode == OP_FAM) || (opcode == OP_FSM), // absolute value? | |
| (opcode != OP_UFA) // normalize? | |
| ); | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... ACC: %06d%04d %06d%04d%c, OV: %d, DIST: %06d%04d%c\n", | |
| printfa, OV, printfd); | |
| *CpuStepsUsed = 1+1 | |
| +(DrumAddr % 2) // using upper acc -> wait for even | |
| +2+2+2+1 | |
| +n; // Float Add steps | |
| break; | |
| case OP_FMP: // Float Multiply | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... Mult ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... by DIST: %06d%04d%c\n", | |
| printfd); | |
| OV = 0; | |
| if (((ACC[1] / 100) == 0) || ((DIST / 100) == 0)) { | |
| // if any mantissa is zero -> multiply by zero -> result = 0 | |
| ACC[1] = ACC[0] = 0; | |
| } else { | |
| int exp = GetExp(DIST) + GetExp(ACC[1]) - 50; | |
| neg = (DIST < 0) ? -1:1; if (AccNegative) neg = -neg; | |
| ACC[1] = SetExp(AbsWord(ACC[1]), 0); | |
| d = SetExp(AbsWord(DIST), 0); | |
| // mult mantissas | |
| for(i=0;i<10;i++) { | |
| n = ShiftAcc(1); | |
| *CpuStepsUsed = *CpuStepsUsed + 2; | |
| while (n-- > 0) { | |
| AddToAcc(0, d, 1); | |
| *CpuStepsUsed = *CpuStepsUsed + 18; | |
| if (OV) break; | |
| } | |
| if (OV) break; | |
| } | |
| MantissaRoundAndNormalizeToFloat(CpuStepsUsed, neg, exp); | |
| } | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... FP Mult result ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| *CpuStepsUsed = 1+1+2+2+2+1+ *CpuStepsUsed | |
| +(DrumAddr % 2); // wait for even | |
| break; | |
| case OP_FDV: // Float Divide | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... Div ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... by DIST: %06d%04d%c\n", | |
| printfd); | |
| OV = 0; | |
| if ((DIST / 100) == 0) { // check mantissa for zero, not exponent | |
| OV = 1; | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "Divide By Zero -> OV set and ERROR\n"); | |
| reason = STOP_OV; // float divisor zero allways stops the machine | |
| } else if ((ACC[1] / 100) == 0) { | |
| // if dividend is zero -> result = 0 | |
| ACC[1] = ACC[0] = 0; | |
| } else { | |
| int exp = GetExp(ACC[1]) - GetExp(DIST) + 50; | |
| neg = (DIST < 0) ? -1:1; if (AccNegative) neg = -neg; | |
| ACC[1] = AbsWord(ACC[1]) / 100; | |
| d = AbsWord(DIST) / 100; | |
| // div mantissas | |
| for(i=0;;i++) { | |
| while (d <= ACC[1]) { | |
| AddToAcc(-d, 0, 0); | |
| *CpuStepsUsed = *CpuStepsUsed + 18; | |
| ACC[0] = ACC[0] + 10; // add to second position of lower | |
| } | |
| if (i > 8) break; | |
| if ((i == 8) && (Get_HiDigit(ACC[0]))) {exp++; break;} | |
| n = ShiftAcc(1); | |
| ACC[1] = ACC[1] + n * D10; // extra digit | |
| *CpuStepsUsed = *CpuStepsUsed + 2; | |
| } | |
| ACC[1] = ACC[0]; | |
| MantissaRoundAndNormalizeToFloat(CpuStepsUsed, neg, exp); | |
| } | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... FP Div result ACC: %06d%04d %06d%04d%c, OV: %d\n", | |
| printfa, | |
| OV); | |
| *CpuStepsUsed = 1+1+2+2+16+2+1+ *CpuStepsUsed | |
| +(DrumAddr % 2); // wait for even | |
| break; | |
| default: | |
| reason = STOP_UUO; | |
| break; | |
| } | |
| if ((reason == 0) && (OV) && (CSWOverflowStop)) reason = STOP_OV; | |
| return reason; | |
| } | |
| // return 1 if must wait for storage | |
| int WaitForStorage(int AR) | |
| { | |
| if ((AR >= 0) && (AR < DRUMSIZE)) { | |
| if ((AR % 50) != DrumAddr) return 1; // yes, must wait for drum | |
| } else if ((STOR) && (AR >= 9000) && (AR < 9060)) { | |
| if (InterLockCount[IL_IAS] > 0) return 1; // yes, IAS was interlocked. Must wait until interlock is released | |
| } | |
| return 0; | |
| } | |
| t_stat | |
| sim_instr(void) | |
| { | |
| t_stat reason; | |
| int opcode, halt_cpu; | |
| int bReadData, bWriteDrum, bBranchToDA; | |
| int instr_count = 0; /* Number of instructions to execute */ | |
| const char * opname; /* points to opcode name */ | |
| int IA = 0; // Instr Address: addr of next inst | |
| int DA = 0; // Data Address; addr of data to be used by current inst | |
| int MachineCycle, CpuStepsUsed, il, WaitForInterlock; | |
| /* How CPU execution is simulated | |
| A cpu instruction is executed in real hw in several steps. Some os these steps involves waiting for rotating | |
| drum to be positioned on requested addres (register AR). Other steps can involve waiting a Interlock to be released. | |
| The execution of a complete instruction is called a machine cycle | |
| User can select in real hw control panel to execute the instructions one by one. The execution is not done | |
| on full instruction (a full cycle), but rather in instruction half-cycles: I-Cycle and D-Cycle. | |
| During I-Cycle, the instruction is fetched from drum and decoded. During D-Cycle instruction is performed. | |
| The simulator models this using the concept of MachineCycles, that groups several steps on opcode execution | |
| SimH Real hw equivalent | |
| machine cycle half cycle | |
| 0 I-Cycle WAIT FOR INSTR: | |
| wait for drum to be positioned at address given by AR cpu register | |
| 1 I-Cycle FETCH INST: | |
| read the drum to get instr to PR register, | |
| decode PR as opcode, DA, IA, | |
| apply index tags if needed, write back to PR | |
| check if opcode must wait for interlock release | |
| check if opcode reads data from drum | |
| 2 D-Cycle WAIT FOR DATA READ: | |
| wait for interlock release if needed | |
| wait for drum to be positioned at AR address if decoded opcode reads data from drum | |
| 3 D-Cycle EXEC: | |
| get data from storage into DIST if needed | |
| set interlock if needed | |
| execute opcode operation | |
| 4 D-Cycle WAIT FOR DATA WRITE: | |
| wait opcode excution time | |
| wait for drum to be positioned at AR address if executed opcode writes data to drum | |
| 5 D-Cycle WRITEBACK: | |
| if executed opcode writes data to drum, write DIST to drum | |
| set AR=IA to read next instruction | |
| */ | |
| if (sim_step != 0) { | |
| instr_count = sim_step; | |
| sim_cancel_step(); | |
| } | |
| reason = halt_cpu = 0; | |
| MachineCycle = CpuStepsUsed = 0; | |
| DrumAddr = 0; | |
| CpuStepsUsed = 0; | |
| if ((ProgStopFlag) && | |
| // if last inst was a programmed stop, | |
| // and AR has not been changed (still contains the same value set by stop 01 opcode) | |
| // gets instr to execute from IA instead of AR. This is to simulate the D-Cycle on stop opcode resume | |
| ((PR / D8) == 01) && | |
| (AR == ((PR / D4) % D4))) { | |
| AR = (PR % D4); | |
| ProgStopFlag = 0; | |
| } | |
| WaitForInterlock = 0; // clear interlocks | |
| for (il=0;il<IL_array;il++) InterLockCount[il] = 0; | |
| sim_cancel (&cpu_unit); | |
| sim_activate (&cpu_unit, 1); | |
| while (reason == 0) { /* loop until halted */ | |
| if (sim_interval <= 0) { /* event queue? */ | |
| reason = sim_process_event(); | |
| if (reason == SCPE_STOP) { | |
| reason = 0; // if stop cpu requested, does not do it inmediatelly | |
| halt_cpu = 1; // signal it so cpu is halted on end of current intr execution cycle | |
| } | |
| if (reason != SCPE_OK) { | |
| break; | |
| } | |
| } | |
| // housekeeping at beggining of inst execution cycle | |
| if (MachineCycle == 0) { | |
| // save current instr addr in pseudoregister IC | |
| IC = AR; | |
| // init current instr opcode in pseudoregister PROP | |
| PROP = 0; | |
| /* Only check for break points during actual fetch */ | |
| if (sim_brk_summ && sim_brk_test(IC, SWMASK('E'))) { | |
| reason = STOP_IBKPT; | |
| break; | |
| } | |
| // only check for ^E on fetch | |
| if (halt_cpu) { | |
| reason = SCPE_STOP; | |
| break; | |
| } | |
| } | |
| /* Main instruction fetch/decode loop */ | |
| sim_interval -= 1; /* count down */ | |
| // simulate the rotating drum: incr current drum position | |
| DrumAddr = (DrumAddr+1) % 50; | |
| // if any interlock set, decrease it | |
| for (il=0;il<IL_array;il++) if (InterLockCount[il] > 0) InterLockCount[il]--; | |
| // decrease pending to execute step intruction count | |
| if (CpuStepsUsed > 0) CpuStepsUsed--; | |
| // WAIT FOR INSTR | |
| if (MachineCycle == 0) { | |
| if (HalfCycle == 2 ) { // if D-Half should start | |
| HalfCycle = 1; // bump half cycle to exec I-Half on next scp step | |
| instr_count = 1; // break at the end of D-half execution | |
| MachineCycle = 3; | |
| continue; | |
| } | |
| // should wait for storage to fetch inst? | |
| if (FAST == 0) if (WaitForStorage(AR)) continue; // yes | |
| // init inst execution | |
| CpuStepsUsed = 0; | |
| MachineCycle = 1; | |
| } | |
| // FETCH INST | |
| if (MachineCycle == 1) { | |
| // get current intruction from storage, save current instr addr in IC | |
| IC = AR; | |
| if (0==ReadAddr(AR, &PR, NULL)) { | |
| reason = STOP_ADDR; | |
| goto end_of_cycle; | |
| } | |
| // decode inst | |
| opname = DecodeOpcode(PR, &opcode, &DA, &IA); | |
| sim_debug(DEBUG_CMD, &cpu_dev, "Exec %04d: %02d %-6s %04d %04d %s%s\n", | |
| IC, opcode, (opname == NULL) ? "???":opname, DA, IA, | |
| ((AR >= MAXDRUMSIZE) || (DRUM_Symbolic_Buffer[AR * 80] == 0)) ? "" : " symb: ", | |
| (AR >= MAXDRUMSIZE) ? "" : &DRUM_Symbolic_Buffer[AR * 80]); | |
| PROP = (uint16) opcode; | |
| if (opname == NULL) { | |
| reason = STOP_UUO; | |
| goto end_of_cycle; | |
| } | |
| // if DA or IA tagged, modify DA or IA to remove tag and set the developed address in PR | |
| if (STOR) { | |
| int nIndexsApplied; | |
| nIndexsApplied = ApplyIndexRegister(&DA) + ApplyIndexRegister(&IA); | |
| if (nIndexsApplied > 0) { | |
| CpuStepsUsed += nIndexsApplied; | |
| PR = (t_int64) opcode * D8 + (t_int64) DA * D4 + (t_int64) IA; | |
| sim_debug(DEBUG_CMD, &cpu_dev, "Exec %04d: %02d %-6s %04d %04d %s\n", | |
| IC, opcode, (opname == NULL) ? "???":opname, DA, IA, | |
| " (developed addr)"); | |
| } | |
| } | |
| AR = DA; // allways trasnfer DA to AR even if drum will be not read. This is why | |
| // all opcodes must have a valid DA address even if not used to read drum (eg SRT 0003 to shift) | |
| // simulates the machine working on half cycles | |
| if (HalfCycle == 1) { // if I-Half finished, about to exec D-Half | |
| HalfCycle = 2; // bump half cycle to exec D-Half on next scp step | |
| reason = SCPE_STEP; // then break beacuse I-Half finished | |
| break; | |
| } | |
| bReadData = (base_ops[opcode].opRW & opReadDA) ? 1:0; | |
| // check if opcode should wait for and already set interlock | |
| WaitForInterlock = base_ops[opcode].opInterLock; | |
| MachineCycle = 2; | |
| } | |
| // WAIT FOR DATA READ | |
| if (MachineCycle == 2) { | |
| // should wait to exec the inst (the address untagging) ? | |
| if (FAST == 0) if (CpuStepsUsed > 0) continue; // yes | |
| // should wait for interlock release for opcode execution? | |
| if (WaitForInterlock) { | |
| if (FAST == 0) if (InterLockCount[WaitForInterlock] > 0) continue; // interlock makes execution wait | |
| InterLockCount[WaitForInterlock] = 0; // clear interlock | |
| WaitForInterlock = 0; | |
| } | |
| // should wait for storage to fetch data? | |
| if (bReadData) { | |
| if (FAST == 0) if (WaitForStorage(AR)) continue; // yes | |
| } | |
| MachineCycle = 3; | |
| } | |
| // EXEC | |
| if (MachineCycle == 3) { | |
| // decode again PR register to reload internal register DA, IA, AR again. Needed if we are executing half cycles | |
| opname = DecodeOpcode(PR, &opcode, &DA, &IA); | |
| AR = DA; | |
| if (opname == NULL) { | |
| reason = STOP_UUO; | |
| goto end_of_cycle; | |
| } | |
| // even if no data is fetched, DA addr must be a valid one for this opcode | |
| if (0==IsDrumAddrOk(AR, base_ops[opcode].validDA)) { | |
| sim_debug(DEBUG_DETAIL, &cpu_dev, "... %04d: Invalid addr ERROR\n", AR); | |
| reason = STOP_ADDR; | |
| goto end_of_cycle; | |
| } | |
| // get data from if needed | |
| bReadData = (base_ops[opcode].opRW & opReadDA) ? 1:0; | |
| if (bReadData) { | |
| ReadAddr(AR, &DIST, &DistNegativeZeroFlag); | |
| sim_debug(DEBUG_DATA, &cpu_dev, "... Read %04d: %06d%04d%c\n", | |
| AR, printfd); | |
| } | |
| bWriteDrum = (base_ops[opcode].opRW & opWriteDA) ? 1:0; | |
| reason = ExecOpcode(opcode, DA, | |
| &bBranchToDA, | |
| DrumAddr, &CpuStepsUsed); | |
| if (reason != 0) goto end_of_cycle; | |
| if (bBranchToDA) IA = DA; | |
| MachineCycle = 4; | |
| } | |
| // WAIT FOR DATA WRITE | |
| if (MachineCycle == 4) { | |
| // should wait to exec the inst (opcode execution) ? | |
| if (FAST == 0) if (CpuStepsUsed > 0) continue; // yes | |
| // should wait for storage to store data? | |
| if (bWriteDrum) { | |
| if (FAST == 0) if (WaitForStorage(AR)) continue; // yes | |
| } | |
| MachineCycle = 5; | |
| } | |
| // WRITEBACK | |
| if (MachineCycle == 5) { | |
| if (bWriteDrum) { | |
| sim_debug(DEBUG_DATA, &cpu_dev, "... Write %04d: %06d%04d%c\n", | |
| AR, printfd); | |
| if (0==WriteAddr(AR, DIST, DistNegativeZeroFlag)) { | |
| reason = STOP_ADDR; | |
| goto end_of_cycle; | |
| } | |
| } | |
| // set AR to point to next instr | |
| AR = IA; | |
| // no more machine cycles | |
| } | |
| end_of_cycle: | |
| if (instr_count != 0 && --instr_count == 0) { | |
| if (reason == 0) { | |
| IC = AR; | |
| // if cpu not stoped (just stepped) set IC so next inst to be executed is shown. | |
| // if cpu stopped because some error (reason != 0), does not advance IC so instr shown is offending one | |
| reason = SCPE_STEP; | |
| break; | |
| } | |
| } | |
| MachineCycle = 0; // ready to process to next instr | |
| } /* end while */ | |
| // flush 407 printout | |
| if ((cdp_unit[0].flags & UNIT_ATT) && (cdp_unit[0].fileref)) { | |
| fflush(cdp_unit[0].fileref); | |
| } | |
| /* Simulation halted */ | |
| return reason; | |
| } | |
| /* Reset routine */ | |
| t_stat | |
| cpu_reset(DEVICE * dptr) | |
| { | |
| ACC[0] = ACC[1] = DIST = 0; | |
| PR = AR = OV = 0; | |
| ProgStopFlag = 0; | |
| AccNegativeZeroFlag = 0; | |
| DistNegativeZeroFlag = 0; | |
| IC = 0; | |
| IAS_TimingRing = 0; | |
| IR[0] = IR[1] = IR[2] = 0; | |
| 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) | |
| { | |
| t_int64 d; | |
| int NegZero; | |
| t_value val; | |
| if (0==ReadAddr(addr, &d, &NegZero)) { | |
| return SCPE_NXM; | |
| } | |
| if (vptr != NULL) { | |
| if (NegZero) { | |
| val = NEGZERO_value; // val has this special value to represent -0 (minus zero == negative zero) | |
| } else { | |
| val = (t_value) d; | |
| } | |
| *vptr = val; | |
| } | |
| return SCPE_OK; | |
| } | |
| /* Memory deposit */ | |
| t_stat | |
| cpu_dep(t_value val, t_addr addr, UNIT * uptr, int32 sw) | |
| { | |
| t_int64 d; | |
| int NegZero; | |
| if (val == NEGZERO_value) { | |
| d = 0; | |
| NegZero = 1; | |
| } else { | |
| d = val; | |
| NegZero = 0; | |
| } | |
| if (0==WriteAddr(addr, d, NegZero)) { | |
| return SCPE_NXM; | |
| } | |
| return SCPE_OK; | |
| } | |
| t_stat | |
| cpu_set_size(UNIT * uptr, int32 val, CONST char *cptr, void *desc) | |
| { | |
| int mc = 0; | |
| uint32 i; | |
| int32 v; | |
| v = val >> UNIT_V_MSIZE; | |
| if (v == 0) {v = 1000;} else | |
| if (v == 1) {v = 2000;} else | |
| if (v == 2) {v = 4000;} else v = 0; | |
| if ((v <= 0) || (v > MAXDRUMSIZE)) | |
| return SCPE_ARG; | |
| if (v < 4000) { | |
| for (i = v; i < MAXDRUMSIZE; i++) { | |
| if ((DRUM[i] != 0) || (DRUM_NegativeZeroFlag[i] != 0)) { | |
| mc = 1; | |
| break; | |
| } | |
| } | |
| } | |
| if ((mc != 0) && (!get_yn("Really truncate memory [N]? ", FALSE))) | |
| return SCPE_OK; | |
| cpu_unit.flags &= ~UNIT_MSIZE; | |
| cpu_unit.flags |= val; | |
| cpu_unit.capac = 9990 + (v / 1000); | |
| for (i=0;i<MAXDRUMSIZE * 80;i++) | |
| DRUM_Symbolic_Buffer[i] = 0; // clear drum symbolic info | |
| for (i = DRUMSIZE; i < MAXDRUMSIZE; i++) | |
| DRUM[i] = DRUM_NegativeZeroFlag[i] = 0; | |
| for(i = 0; i < 60; i++) IAS[i] = IAS_NegativeZeroFlag[i] = 0; | |
| return SCPE_OK; | |
| } | |
| t_stat | |
| cpu_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) { | |
| fprintf (st, "These switches are recognized when examining or depositing in CPU memory:\r\n\r\n"); | |
| fprintf (st, " -c examine/deposit characters, 5 per word\r\n"); | |
| fprintf (st, " -m examine/deposit IBM 650 instructions\r\n\r\n"); | |
| fprintf (st, "The memory of the CPU can be set to 1000, 2000 or 4000 words.\r\n\r\n"); | |
| fprintf (st, " sim> SET CPU nK\r\n\r\n"); | |
| fprintf (st, " sim> SET CPU StorageUnit enables IBM 652 Storage Unit\n"); | |
| fprintf (st, " sim> SET CPU NoStorageUnit disables IBM 652 Storage Unit\n\n"); | |
| fprint_set_help(st, dptr); | |
| fprint_show_help(st, dptr); | |
| return SCPE_OK; | |
| } | |
| const char * cpu_description (DEVICE *dptr) { | |
| return "IBM 650 CPU"; | |
| } | |