The paradigm of using a "weak" linker reference to find what was previously the vm_init_routine() doesn't work reliably on all compile environments supported by the simulators. This has been reported in #794 and it came up again in #862. This change assures that it will not come up again AND it reliably solves the problem with Visual Studio compilers (and linker) that randomly chooses whether to have the desired effect or not. Of the 82 simulators which are currently part of simh, only these three used the sim_vm_init() interface, so removing it had relatively minor impact.
2143 lines
93 KiB
C
2143 lines
93 KiB
C
/* 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 world’s 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
|
||
|
||
Memory Map
|
||
|
||
0000-1999 Drum Locations (0000-3999 on Model4)
|
||
2000-3999 Location indexed with IRA
|
||
4000-5999 Location indexed with IRB
|
||
6000-7999 Location indexed with IRC
|
||
8000 Console Switch Register
|
||
8001 Distributor Register
|
||
8002 Lower Accumulator Register
|
||
8003 Upper Accumulator Register
|
||
8005 Index Register A (IRA)
|
||
8006 Index Register B (IRB)
|
||
8007 Index Register C (IRC)
|
||
9000-9059 IAS Storage
|
||
9200-9259 Location indexed with IRA
|
||
9400-9459 Location indexed with IRB
|
||
9600-9659 Location indexed with IRC
|
||
|
||
|
||
*/
|
||
|
||
#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))
|
||
#define OPTION_TLE (1 << (UNIT_V_CPUMODEL + 5))
|
||
#define OPTION_1DSKARM (1 << (UNIT_V_CPUMODEL + 6))
|
||
|
||
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
|
||
char IAS_Symbolic_Buffer[60 * 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[8] = {0};
|
||
|
||
// address where rotating drum is currently positioned (0-49)
|
||
int DrumAddr;
|
||
|
||
// increment umber of word counts elapsed from starting of simulator -> this is the global time measurement
|
||
t_int64 GlobalWordTimeCount=1;
|
||
|
||
|
||
// 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},
|
||
{OPTION_TLE, 0, NULL, "NOTLE", NULL},
|
||
{OPTION_TLE, OPTION_TLE, "Table Lookup on Equal", "TLE", NULL},
|
||
{OPTION_1DSKARM, 0, NULL, "NOTLE", NULL},
|
||
{OPTION_1DSKARM, OPTION_1DSKARM, "Enable 1 ARM RAMAC", "1DSKARM", 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 switch 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;
|
||
|
||
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)
|
||
{
|
||
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, int bAllowNegativeValue)
|
||
{
|
||
while (*addr >= 10000) *addr -= 10000;
|
||
if (bAllowNegativeValue) {
|
||
while (*addr <= -10000) *addr += 10000;
|
||
} else {
|
||
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;
|
||
|
||
// check for tag and untag
|
||
if ((*addr >= 2000) && (*addr < 4000)) {n = 1; *addr -= 2000; } else
|
||
if ((*addr >= 4000) && (*addr < 6000)) {n = 2; *addr -= 4000; } else
|
||
if ((*addr >= 6000) && (*addr < 8000)) {n = 3; *addr -= 6000; } else
|
||
if ((*addr >= 9200) && (*addr < 9400)) {n = 1; *addr -= 200; } else
|
||
if ((*addr >= 9400) && (*addr < 9600)) {n = 2; *addr -= 400; } else
|
||
if ((*addr >= 9600) && (*addr < 9800)) {n = 3; *addr -= 600; } else
|
||
return 0; // address not tagged
|
||
|
||
*addr = *addr + IR[n-1];
|
||
NormalizeAddr(addr, 0);
|
||
|
||
return 1;
|
||
}
|
||
|
||
// apply index register to a tagged address for Model 4
|
||
// removes tag, replace value with developed address
|
||
// return 1 if address was tagged, and has been replaced by developed addr
|
||
int ApplyIndexRegisterModel4(int * DA, int * IA)
|
||
{
|
||
int n, tagDA, tagIA, nIndexApplied;
|
||
|
||
tagDA = tagIA = 0;
|
||
|
||
nIndexApplied = 0;
|
||
if ((*DA >= 9200) && (*DA < 9800)) {
|
||
nIndexApplied += ApplyIndexRegister(DA);
|
||
if ((*IA >= 9200) && (*IA < 9800)) {
|
||
nIndexApplied += ApplyIndexRegister(IA);
|
||
}
|
||
return nIndexApplied;
|
||
}
|
||
if ((*DA >= 4000) && (*DA < 8000)) {
|
||
*DA -= 4000; // remove tag on DA address
|
||
tagIA = 1;
|
||
}
|
||
if ((*IA >= 4000) && (*IA < 8000)) {
|
||
*IA -= 4000; // remove tag on IA address
|
||
tagDA = 1;
|
||
} else if ( ((*IA >= 8800) && (*IA < 8900)) || ((*IA >= 9800) && (*IA < 9900)) ) {
|
||
*IA -= 800; // remove tag on IA address
|
||
tagDA = 1;
|
||
}
|
||
|
||
n = tagDA + 2 * tagIA;
|
||
if (n) {
|
||
*DA = *DA + IR[n-1];
|
||
NormalizeAddr(DA, 0);
|
||
nIndexApplied++;
|
||
}
|
||
if ((*IA >= 9200) && (*IA < 9800)) {
|
||
nIndexApplied += ApplyIndexRegister(IA);
|
||
}
|
||
|
||
return nIndexApplied;
|
||
}
|
||
|
||
// 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;
|
||
int opt;
|
||
|
||
*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;
|
||
opt = base_ops[*opcode].option; // cpu option needed to have the opcode available
|
||
if (opt == opStorUnit) {
|
||
// opcode available if IBM 653 Storage Unit is present
|
||
if (STOR == 0) return NULL;
|
||
} else if (opt == opCntrlUnit) {
|
||
// opcode available if IBM 652 Control Unit is present
|
||
if (CNTRL == 0) return NULL;
|
||
} else if (opt == opTLE) {
|
||
// opcode available if Table LookUo Feature is present
|
||
if ((cpu_unit.flags & OPTION_TLE) == 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, ZeroNeg;
|
||
t_int64 d;
|
||
char s[6];
|
||
|
||
n = f0 = t0 = f1 = t1 = ec = 0;
|
||
while (1) {
|
||
if (dir[0] == 'D') {
|
||
// copy drum to ias
|
||
d = IAS[IAS_TimingRing] = DRUM[AR];
|
||
ZeroNeg = IAS_NegativeZeroFlag[IAS_TimingRing] = DRUM_NegativeZeroFlag[AR];
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... DRUM %04d to IAS %04d: %06d%04d%c '%s'\n",
|
||
AR, IAS_TimingRing+9000, printfw(d,ZeroNeg),
|
||
word_to_ascii(s, 1, 5, d));
|
||
if (n==0) {f0=AR; t0=IAS_TimingRing+9000;}
|
||
f1=AR; t1=IAS_TimingRing+9000;
|
||
// copy symbolic info from drum to ias (so code copies to ias to be executed faster
|
||
// keeps its symbolic info)
|
||
memset(&IAS_Symbolic_Buffer[IAS_TimingRing * 80], 0, 80); // clear ias symbolic info
|
||
sim_strlcpy(&IAS_Symbolic_Buffer[IAS_TimingRing * 80],
|
||
&DRUM_Symbolic_Buffer[AR * 80], 80);
|
||
} else {
|
||
// copy ias to drum
|
||
d = DRUM[AR] = IAS[IAS_TimingRing];
|
||
ZeroNeg = DRUM_NegativeZeroFlag[AR] = IAS_NegativeZeroFlag[IAS_TimingRing];
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... IAS %04d to DRUM %04d: %06d%04d%c '%s'\n",
|
||
IAS_TimingRing+9000, AR, printfw(d,ZeroNeg),
|
||
word_to_ascii(s, 1, 5, d));
|
||
if (n==0) {t0=AR; f0=IAS_TimingRing+9000;}
|
||
t1=AR; f1=IAS_TimingRing+9000;
|
||
}
|
||
n++;
|
||
if ((AR % 50) == 49) { ec = 0; break; }
|
||
if (IAS_TimingRing == 59) { 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 transfer
|
||
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, SvOV;
|
||
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;
|
||
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
|
||
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
|
||
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
|
||
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;
|
||
SvOV=OV; 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];
|
||
}
|
||
if (SvOV==1) OV=1; // if overflow was set at beginning of opcode execution, keeps its state
|
||
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);
|
||
SvOV=OV;
|
||
if (DIST == 0) {
|
||
OV = 1;
|
||
sim_debug(DEBUG_EXP, &cpu_dev, "Divide By Zero -> OV set and ERROR\n");
|
||
reason = STOP_OV; // divisor zero allways stops the machine
|
||
} else if (AbsWord(DIST) <= AbsWord(ACC[1])) {
|
||
OV = 1;
|
||
sim_debug(DEBUG_EXP, &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
|
||
}
|
||
if (SvOV==1) OV=1; // if overflow was set at beginning of opcode execution, keeps its state
|
||
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,0);
|
||
if (d >= 5) AddToAcc(0,+1,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 = DA % 10;
|
||
if (n>0) n=10-n; // shift count (ten's complement of unit digit of DA, or zero if digit is zero)
|
||
neg = AccNegative; // save acc sign
|
||
ACC[0] = AbsWord(ACC[0]);
|
||
ACC[1] = AbsWord(ACC[1]);
|
||
i=0;
|
||
if (Get_HiDigit(ACC[1]) > 0) {
|
||
// no shift, two low orfer digits replaced by zero
|
||
ACC[0] = SetIA2(ACC[0], 0); // replace last two digits by 00
|
||
} else {
|
||
while (Get_HiDigit(ACC[1]) == 0) {
|
||
if (n==10) {
|
||
OV = 1;
|
||
break;
|
||
}
|
||
ShiftAcc(1); // shift left
|
||
i++; // number of shift
|
||
n++; // count
|
||
}
|
||
ACC[0] = SetIA2(ACC[0], n); // replace last two digits by count n
|
||
}
|
||
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*i; // 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_TLE: // Table lookup on equal
|
||
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_EXP, &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 ( (opcode == OP_TLU) ?
|
||
(AbsWord(d) >= AbsWord(DIST)) :
|
||
(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 = AbsWord(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_EXP, &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
|
||
// BOV resets overflow
|
||
OV=0;
|
||
break;
|
||
// Card I/O
|
||
case OP_RD: // Read a card
|
||
case OP_RD2:
|
||
case OP_RD3:
|
||
case OP_RC1:
|
||
case OP_RC2:
|
||
case OP_RC3:
|
||
bUsingIAS = (AR >= 9000) ? 1:0;
|
||
{
|
||
char s[6];
|
||
int nUnit, area, nIL;
|
||
|
||
if ((opcode == OP_RD2) || (opcode == OP_RC2)) {
|
||
nUnit = 2; nIL = IL_RD23; area = 13;
|
||
} else if ((opcode == OP_RD3) || (opcode == OP_RC3)) {
|
||
nUnit = 3; nIL = IL_RD23; area = 13;
|
||
} else {
|
||
nUnit = 1; nIL = IL_RD1; area = 1;
|
||
}
|
||
|
||
if (bUsingIAS == 0) {
|
||
AR = (DA / 50) * 50 + area; // Drum Read Band is XX01 to XX10 or XX51 to XX60
|
||
}
|
||
|
||
reason = cdr_cmd(&cdr_unit[nUnit], 0, AR);
|
||
if (reason == SCPE_NOCARDS) {
|
||
reason = STOP_IO;
|
||
break;
|
||
} else if (reason != SCPE_OK) {
|
||
break;
|
||
}
|
||
// copy card data from IO Sync buffer to drum/ias
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... Read Card Unit CDR%d\n", nUnit);
|
||
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 = msec_to_wordtime(30); // 30 msec div 0.096 msec word time;
|
||
InterLockCount[nIL] = msec_to_wordtime(300); // set interlock 300 msec for card read processing
|
||
}
|
||
break;
|
||
case OP_PCH: // Punch a card
|
||
case OP_WR2:
|
||
case OP_WR3:
|
||
bUsingIAS = (AR >= 9000) ? 1:0;
|
||
{
|
||
char s[6];
|
||
int nUnit, area, nIL;
|
||
|
||
if (opcode == OP_WR2) {
|
||
nUnit = 2; nIL = IL_WR23; area = 39;
|
||
} else if (opcode == OP_WR3) {
|
||
nUnit = 3; nIL = IL_WR23; area = 39;
|
||
} else {
|
||
nUnit = 1; nIL = IL_RD1; area = 27;
|
||
}
|
||
|
||
if (bUsingIAS == 0) {
|
||
AR = (DA / 50) * 50 + area; // 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
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... Punch Card Unit CDP%d\n", nUnit);
|
||
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 = n;
|
||
}
|
||
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]));
|
||
if (bUsingIAS) {
|
||
// punching from IAS. If hit ias end of block, terminate even
|
||
// if transfered less than 10 words (rest of words were filled with zeroes)
|
||
if ((n % 10) == 9) break;
|
||
}
|
||
}
|
||
|
||
reason = cdp_cmd(&cdp_unit[nUnit], 0,AR);
|
||
if (reason == SCPE_NOCARDS) {
|
||
reason = STOP_IO;
|
||
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 = msec_to_wordtime(35); // 35 msec div 0.096 msec word time;
|
||
InterLockCount[nIL] = msec_to_wordtime(600); // set interlock 600 msec for card punch 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 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 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);
|
||
DIST=d; DistNegativeZeroFlag=0;
|
||
sim_debug(DEBUG_DATA, &cpu_dev, "... Read %04d: %06d%04d%c\n", DA, printfd);
|
||
i = (int) (d % D4);
|
||
} else {
|
||
i = DA;
|
||
}
|
||
n = n + (((opcode == OP_AXA) || (opcode == OP_RAA)) ? i : -i);
|
||
NormalizeAddr(&n, 1);
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... IRA: %04d%c\n",
|
||
abs(n), n<0?'-':'+');
|
||
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);
|
||
DIST=d; DistNegativeZeroFlag=0;
|
||
sim_debug(DEBUG_DATA, &cpu_dev, "... Read %04d: %06d%04d%c\n", DA, printfd);
|
||
i = (int) (d % D4);
|
||
} else {
|
||
i = DA;
|
||
}
|
||
n = n + (((opcode == OP_AXB) || (opcode == OP_RAB)) ? i : -i);
|
||
NormalizeAddr(&n, 1);
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... IRB: %04d%c\n",
|
||
abs(n), n<0?'-':'+');
|
||
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);
|
||
DIST=d; DistNegativeZeroFlag=0;
|
||
sim_debug(DEBUG_DATA, &cpu_dev, "... Read %04d: %06d%04d%c\n", DA, printfd);
|
||
i = (int) (d % D4);
|
||
} else {
|
||
i = DA;
|
||
}
|
||
n = n + (((opcode == OP_AXC) || (opcode == OP_RAC)) ? i : -i);
|
||
NormalizeAddr(&n, 1);
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... IRC: %04d%c\n",
|
||
abs(n), n<0?'-':'+');
|
||
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%c\n",
|
||
i+'A', abs(n), n<0?'-':'+');
|
||
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%c\n",
|
||
i+'A', abs(n), n<0?'-':'+');
|
||
if (n!=0) {
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "Is Non 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);
|
||
SvOV=OV; 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);
|
||
}
|
||
if (SvOV==1) OV=1; // if overflow was set at beginning of opcode execution, keeps its state
|
||
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);
|
||
SvOV=OV; 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);
|
||
}
|
||
if (SvOV==1) OV=1; // if overflow was set at beginning of opcode execution, keeps its state
|
||
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;
|
||
// tape opcodes
|
||
case OP_RTC: // Read Tape Check
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... Tape %d read check\n", DA % 10);
|
||
goto tape_opcode;
|
||
case OP_RTA: // Read Tape Alphanumeric
|
||
case OP_RTN: // Read Tape Numeric
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... Tape %d read at IAS: %04d\n", DA % 10, IAS_TimingRing + 9000);
|
||
goto tape_opcode;
|
||
case OP_WTN: // Write Tape Numeric
|
||
case OP_WTA: // Write Tape Alphabetic
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... Tape %d write from IAS: %04d\n", DA % 10, IAS_TimingRing + 9000);
|
||
goto tape_opcode;
|
||
case OP_WTM: // Write Tape Mark
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... Tape %d write tape mark\n", DA % 10);
|
||
goto tape_opcode;
|
||
case OP_BST: // BackStep Tape
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... Tape %d backspace record\n", DA % 10);
|
||
goto tape_opcode;
|
||
case OP_RWD: // rewind
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... Tape %d rewind\n", DA % 10);
|
||
tape_opcode:
|
||
n = (DA % 10);
|
||
if ((n < 0) || (n > 5)) {
|
||
sim_debug(DEBUG_EXP, &cpu_dev, "Invalid Tape addr %d ERROR\n", AR);
|
||
reason = STOP_ADDR;
|
||
break;
|
||
}
|
||
reason = mt_cmd(&mt_unit[n], opcode, FAST);
|
||
if (reason == SCPE_OK) {
|
||
// tape command terminated
|
||
} else if (reason == SCPE_OK_INPROGRESS) {
|
||
// tape command in progress.
|
||
// Set interlock on Control Unit. Will be removed by mt_svr when tape operation terminates
|
||
InterLockCount[IL_Tape] = msec_to_wordtime(5*60*1000);
|
||
// Set interlock on IAS if read/write from/to IAS. Will be removed by mt_svr when tape operation terminates
|
||
if ((opcode == OP_RTN) || (opcode == OP_RTA) || (opcode == OP_WTN) || (opcode == OP_WTA)){
|
||
InterLockCount[IL_IAS] = msec_to_wordtime(5*60*1000); ;
|
||
}
|
||
reason = SCPE_OK;
|
||
} else {
|
||
// other reason are unexpected errors and terminates the opcode execution
|
||
break;
|
||
}
|
||
*CpuStepsUsed = 1+1+1+1+1;
|
||
break;
|
||
case OP_NTS: // Branch on No Tape Signal
|
||
case OP_NEF: // Branch on No End of File
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... Tape Signal is %s\n", TapeIndicatorStr[LastTapeIndicator]);
|
||
if ((opcode == OP_NTS) && (LastTapeIndicator == 0)) {
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "No Tape Signal -> Branch Taken\n");
|
||
*bBranchToDA = 1;
|
||
}
|
||
if ((opcode == OP_NEF) && (LastTapeIndicator != MT_IND_EOF)) {
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "No End of File -> Branch Taken\n");
|
||
*bBranchToDA = 1;
|
||
}
|
||
*CpuStepsUsed = 1+1
|
||
+ ((*bBranchToDA) ? 1:0); // one extra step needed if branch taken
|
||
break;
|
||
// disk opcodes
|
||
case OP_SDS: // seek
|
||
case OP_RDS: // seek
|
||
case OP_WDS: // seek
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... DIST: %06d%04d%c\n", printfd);
|
||
n = abs((int)(DIST % D8)) % 1000000; // ramac operation address
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "... RAMAC %s on Unit %d, Disk %d, Track %d, Arm %d started\n",
|
||
(opcode == OP_SDS) ? "SEEK" : (opcode == OP_RDS) ? "READ" : "WRITE",
|
||
i=(n / 100000) % 10, // unit
|
||
(n / 1000) % 100, // disk
|
||
(n / 10) % 100, // track
|
||
neg=(n % 10) // arm
|
||
);
|
||
if (neg > 2) {
|
||
sim_debug(DEBUG_EXP, &cpu_dev, "Arm out of range (should be 0..2)\n");
|
||
reason = STOP_IO; // selected arm or unit out of range
|
||
}
|
||
if (i > 3) {
|
||
sim_debug(DEBUG_EXP, &cpu_dev, "Unit out of range (should be 0..3)\n");
|
||
reason = STOP_IO; // selected arm or unit out of range
|
||
}
|
||
if (cpu_unit.flags & OPTION_1DSKARM) {
|
||
// if 1 arm per disk enabled, alisase all disck comands to be executed on arm 0
|
||
n = (n / 10)*10;
|
||
}
|
||
reason = dsk_cmd(opcode, n, FAST);
|
||
if (reason == SCPE_OK) {
|
||
// disk command terminated
|
||
} else if (reason == SCPE_OK_INPROGRESS) {
|
||
// disk command in progress.
|
||
// Set interlock on Ramac Disk Control Unit. Will be removed by dsk_svr when disk operation terminates
|
||
InterLockCount[IL_RamacUnit] = msec_to_wordtime(75);
|
||
// Set interlock on IAS if read/write from/to IAS. Will be removed by dsk_svr when disk operation terminates
|
||
if ((opcode == OP_RDS) || (opcode == OP_WDS)){
|
||
InterLockCount[IL_IAS] = msec_to_wordtime(5*60*1000); ;
|
||
}
|
||
reason = SCPE_OK;
|
||
} else {
|
||
// other reason are unexpected errors and terminates the opcode execution
|
||
break;
|
||
}
|
||
*CpuStepsUsed = 1+1+1+1+1;
|
||
break;
|
||
default:
|
||
reason = STOP_UUO;
|
||
break;
|
||
}
|
||
if ((reason == 0) && (OV) && (CSWOverflowStop)) reason = STOP_OV;
|
||
|
||
return reason;
|
||
}
|
||
|
||
// return 2 if must wait for drum rotation, return 1 if must wait for IAS interlock release
|
||
int WaitForStorage(int AR)
|
||
{
|
||
if ((AR >= 0) && (AR < DRUMSIZE)) {
|
||
if ((AR % 50) != DrumAddr) return 2; // 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;
|
||
}
|
||
|
||
// return 1 if must wait for interlock release
|
||
int WaitForInterlock(int nInterlock)
|
||
{
|
||
int n, arm;
|
||
|
||
// handle combined interlocks
|
||
if (nInterlock == IL_Tape_and_Unit_and_IAS) {
|
||
if (WaitForInterlock(IL_IAS)) return 1;
|
||
if (WaitForInterlock(IL_Tape)) return 1;
|
||
if (WaitForInterlock(-1)) return 1; // check for interlock on tape unit
|
||
return 0;
|
||
} else if (nInterlock == IL_Tape_and_Unit) {
|
||
if (WaitForInterlock(IL_Tape)) return 1;
|
||
if (WaitForInterlock(-1)) return 1; // check for interlock on tape unit
|
||
return 0;
|
||
} else if (nInterlock == IL_RamacUnit_and_Arm_and_IAS) {
|
||
if (WaitForInterlock(IL_IAS)) return 1;
|
||
if (WaitForInterlock(IL_RamacUnit)) return 1;
|
||
if (WaitForInterlock(-2)) return 1; // check for interlock on disk unit
|
||
return 0;
|
||
} else if (nInterlock == IL_RamacUnit_and_Arm) {
|
||
if (WaitForInterlock(IL_RamacUnit)) return 1;
|
||
if (WaitForInterlock(-2)) return 1; // check for interlock on ramac disk unit arm
|
||
return 0;
|
||
}
|
||
// handle interlock on tape unit
|
||
if (nInterlock == -1) {
|
||
// get tape unit referenced by current opcode from intruction DA
|
||
n = (PR / D4) % 10;
|
||
if ((n < 0) || (n > 5)) return 0; // invalid tape addr -> no interlock wait
|
||
return mt_ready(n) ? 0:1; // if tape ready -> return 0 -> no need to wait for tape unit
|
||
}
|
||
// handle interlock on disk unit arm
|
||
if (nInterlock == -2) {
|
||
// get disk unit and arm referenced by current DIST (distributor) value
|
||
n = abs((int)(DIST % D8));
|
||
arm = n % 10;
|
||
n = n / 100000;
|
||
if ((arm > 2) || (n > 3)) return 0; // invalid arm/disk unit -> no interlock wait
|
||
if (cpu_unit.flags & OPTION_1DSKARM) {
|
||
// if 1 arm per disk enabled, alisase all disck comands to be executed on arm 0
|
||
arm=0;
|
||
}
|
||
return dsk_ready(n, arm) ? 0:1; // if disk unit arm ready -> return 0 -> no need to wait
|
||
}
|
||
|
||
// handle single interlock
|
||
return InterLockCount[nInterlock];
|
||
}
|
||
|
||
t_stat
|
||
sim_instr(void)
|
||
{
|
||
t_stat reason;
|
||
int opcode, halt_cpu_requested;
|
||
int bReadData, bWriteDrum, bBranchToDA;
|
||
int instr_count = 0; /* Number of instructions to execute */
|
||
const char * opname; /* points to opcode name */
|
||
char * Symbolic_Buffer;
|
||
|
||
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, nInterlock, bInterLockWaitMsg, bFastMode;
|
||
|
||
/* 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_requested = 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;
|
||
}
|
||
|
||
nInterlock = 0; // clear interlocks
|
||
memset(&InterLockCount[0], 0, sizeof(InterLockCount));
|
||
bInterLockWaitMsg = 0;
|
||
|
||
sim_cancel (&cpu_unit);
|
||
sim_activate (&cpu_unit, 1);
|
||
|
||
bFastMode = FAST;
|
||
|
||
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_requested = 1; // signal it so cpu is halted on end of current intr execution cycle
|
||
bFastMode = 1; // also set fast mode to avoid wait on Interlocks, thus finishing the current inst asap
|
||
}
|
||
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 and on interlock wait
|
||
if (halt_cpu_requested) {
|
||
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;
|
||
|
||
// increment umber of word counts elapsed from starting of simulator -> this is the global time measurement
|
||
GlobalWordTimeCount++;
|
||
|
||
// if any interlock set, decrease it
|
||
for (il=0;il < sizeof(InterLockCount)/ sizeof(InterLockCount[0]) ;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 (bFastMode == 0) {
|
||
il=WaitForStorage(AR);
|
||
if ((il==1) && (bInterLockWaitMsg == 0)) {
|
||
bInterLockWaitMsg = 1;
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "Wait for interlock on IAS to fetch opcode at %04d\n", AR);
|
||
}
|
||
if (il>0) continue; // yes, wait for storage to fetch inst
|
||
}
|
||
// 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);
|
||
// get symbolic info if any
|
||
if ((AR < MAXDRUMSIZE) && (DRUM_Symbolic_Buffer[AR * 80] > 0)) {
|
||
// drum symb info
|
||
Symbolic_Buffer = &DRUM_Symbolic_Buffer[AR * 80];
|
||
} else if ((AR >= 9000) && (AR < 9060)) {
|
||
// ias symb info
|
||
Symbolic_Buffer = &IAS_Symbolic_Buffer[(AR - 9000) * 80];
|
||
} else {
|
||
Symbolic_Buffer = 0;
|
||
}
|
||
sim_debug(DEBUG_CMD, &cpu_dev, "Exec %04d: %02d %-6s %04d %04d %s%s\n",
|
||
IC, opcode, (opname == NULL) ? "???":opname, DA, IA,
|
||
(Symbolic_Buffer) ? " symb: ": "",
|
||
(Symbolic_Buffer) ? Symbolic_Buffer : "");
|
||
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;
|
||
if (DRUM4K) {
|
||
nIndexsApplied = ApplyIndexRegisterModel4(&DA, &IA);
|
||
} else {
|
||
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
|
||
nInterlock = base_ops[opcode].opInterLock;
|
||
bInterLockWaitMsg = 0;
|
||
|
||
MachineCycle = 2;
|
||
}
|
||
// WAIT FOR DATA READ
|
||
if (MachineCycle == 2) {
|
||
// should wait before exec the inst (time for address untagging) ?
|
||
if (bFastMode == 0) if (CpuStepsUsed > 0) continue; // yes
|
||
// should wait for interlock release for opcode execution?
|
||
if (nInterlock) {
|
||
if (bFastMode == 0) if (WaitForInterlock(nInterlock)) {
|
||
if (bInterLockWaitMsg == 0) {
|
||
bInterLockWaitMsg = 1;
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "Wait for interlock on %s\n",
|
||
(nInterlock==IL_RD1) ? "RD1" :
|
||
(nInterlock==IL_WR1) ? "WR1" :
|
||
(nInterlock==IL_RD23) ? "RD23" :
|
||
(nInterlock==IL_WR23) ? "WR23" :
|
||
(nInterlock==IL_IAS) ? "IAS" :
|
||
(nInterlock==IL_Tape) ? "TCI" :
|
||
(nInterlock==IL_Tape_and_Unit_and_IAS) ? "IAS+TCI+Tape Unit ready" :
|
||
(nInterlock==IL_Tape_and_Unit) ? "TCI+Tape Unit ready" :
|
||
(nInterlock==IL_RamacUnit) ? "RAMAC Unit" :
|
||
(nInterlock==IL_RamacUnit_and_Arm) ? "RAMAC Unit+Arm" :
|
||
(nInterlock==IL_RamacUnit_and_Arm_and_IAS) ? "IAS+RAMAC Unit+Arm" :
|
||
"???");
|
||
}
|
||
continue; // yes, wait for interlock
|
||
}
|
||
}
|
||
// should wait for storage to fetch data?
|
||
if (bReadData) {
|
||
if (bFastMode == 0) {
|
||
il=WaitForStorage(AR);
|
||
if ((il==1) && (bInterLockWaitMsg == 0)) {
|
||
bInterLockWaitMsg = 1;
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "Wait for interlock on IAS to read at %04d\n",AR);
|
||
}
|
||
if (il>0) continue; // yes, wait for drum rotation/IAS ready
|
||
}
|
||
}
|
||
|
||
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 (bFastMode == 0) if (CpuStepsUsed > 0) continue; // yes
|
||
// should wait for storage to store data?
|
||
if (bWriteDrum) {
|
||
if (bFastMode == 0) {
|
||
il=WaitForStorage(AR);
|
||
if ((il==1) && (bInterLockWaitMsg == 0)) {
|
||
bInterLockWaitMsg = 1;
|
||
sim_debug(DEBUG_DETAIL, &cpu_dev, "Wait for interlock on IAS to write at %04d\n", AR);
|
||
}
|
||
if (il>0) 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;
|
||
}
|
||
}
|
||
// ready to process to next instr
|
||
MachineCycle = 0;
|
||
|
||
// reset the message for interlock wait
|
||
bInterLockWaitMsg = 0;
|
||
|
||
} /* 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');
|
||
|
||
vm_init ();
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
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";
|
||
}
|
||
|