mirror of
https://github.com/pkimpel/retro-220.git
synced 2026-01-13 15:18:24 +00:00
3603 lines
155 KiB
JavaScript
3603 lines
155 KiB
JavaScript
/***********************************************************************
|
|
* retro-220/emulator B220Processor.js
|
|
************************************************************************
|
|
* Copyright (c) 2017, Paul Kimpel.
|
|
* Licensed under the MIT License, see
|
|
* http://www.opensource.org/licenses/mit-license.php
|
|
************************************************************************
|
|
* Burroughs 220 Emulator Processor (CPU) module.
|
|
*
|
|
* Instance variables in all caps generally refer to register or flip-flop (FF)
|
|
* entities in the processor hardware. See the following documents:
|
|
*
|
|
* Burroughs 220 Operational Characterists Manual
|
|
* (Bulletin 5020A, Burroughs Corporation, revised August 1960).
|
|
* Handbook of Operating Procedures for the Burroughs 220
|
|
* (Bulletin 5023, Burroughs Corporation, November 1959).
|
|
* Burroughs 220 Schematics
|
|
* (Technical Manual 4053-1, Burroughs Corporation, December 1958).
|
|
* Datatron 220 Schematics, Section I [CPU.pdf]
|
|
* (Technical Manual 4053, Burroughs Corporation, December 1958).
|
|
*
|
|
* available at:
|
|
* http://bitsavers.org/pdf/burroughs/electrodata/220/
|
|
*
|
|
* also:
|
|
*
|
|
* An Introduction to Coding the Burroughs 220
|
|
* (Bulletin 5019, Burroughs Corporation, December, 1958).
|
|
*
|
|
* Burroughs 220 word format:
|
|
* 44 bits, encoded as binary-coded decimal (BCD); non-decimal codes are
|
|
* invalid and cause the computer to stop with a Digit Check alarm, also known
|
|
* as a Forbidden Combination (FC).
|
|
*
|
|
* High-order 4 bits are the "sign digit":
|
|
* Low-order bit of this digit is the actual sign.
|
|
* Higher-order bits are used in some I/O operations.
|
|
* Remaining 40 bits are the value as:
|
|
* 10 decimal digits as a fractional mantissa, with the decimal point
|
|
* between the sign and high-order (10th) digits
|
|
* a floating point value with the first two digits as the exponent (biased
|
|
* by 50) followed by a fractional 8-digit mantissa
|
|
* 5 two-digit character codes
|
|
* one instruction word
|
|
*
|
|
* Instruction word format:
|
|
* Low-order 4 digits: operand address
|
|
* Next-higher 2 digits: operation code
|
|
* Next-higher 4 digits: control and variant digits used by some instructions
|
|
* Sign digit: odd value indicates the B register is to be added to the
|
|
* operand address prior to execution.
|
|
*
|
|
* Processor timing is maintained internally in units of milliseconds.
|
|
*
|
|
************************************************************************
|
|
* 2017-01-01 P.Kimpel
|
|
* Original version, cloned from retro-205 emulator/D205Processor.js.
|
|
***********************************************************************/
|
|
"use strict";
|
|
|
|
/**************************************/
|
|
function B220Processor(config, devices) {
|
|
/* Constructor for the 220 Processor module object */
|
|
|
|
// Emulator control
|
|
this.cardatron = null; // reference to Cardatron Control Unit
|
|
this.config = config; // reference to SystemConfig object
|
|
this.console = null; // reference to Control Console for I/O
|
|
this.devices = devices; // hash of I/O device objects
|
|
this.ioCallback = null; // current I/O interface callback function
|
|
this.magTape = null; // reference to Magnetic Tape Control Unit
|
|
this.poweredOn = 0; // system is powered on and initialized
|
|
this.successor = null; // current delayed-action successor function
|
|
|
|
// Memory
|
|
this.memorySize = config.getNode("memorySize"); // memory size, words
|
|
this.MM = new Float64Array(this.memorySize); // main memory, 11-digit words
|
|
this.IB = new B220Processor.Register(11*4, this, true); // memory Input Buffer
|
|
|
|
// Processor throttling control and timing statistics
|
|
this.procStart = 0; // Javascript time that the processor started running, ms
|
|
this.realTime = 0; // estimated real time, ms
|
|
this.runLimit = 0; // current time slice limit on this.realTime
|
|
this.procTime = 0; // total internal running time for processor, ms
|
|
this.scheduler = 0; // current setCallback token
|
|
this.timerTime = 0; // elapsed run-time timer value, ms
|
|
|
|
this.procSlack = 0; // total processor throttling delay, ms
|
|
this.procSlackAvg = 0; // average slack time per time slice, ms
|
|
this.procRunAvg = 0; // average elapsed time per time slice, ms
|
|
|
|
this.delayDeltaAvg = 0; // average difference between requested and actual setCallback() delays, ms
|
|
this.delayLastStamp = 0; // timestamp of last setCallback() delay, ms
|
|
this.delayRequested = 0; // last requested setCallback() delay, ms
|
|
|
|
// Primary Registers
|
|
this.A = new B220Processor.Register(11*4, this, false);
|
|
this.B = new B220Processor.Register( 4*4, this, false);
|
|
this.C = new B220Processor.Register(10*4, this, false);
|
|
this.D = new B220Processor.Register(11*4, this, false);
|
|
this.E = new B220Processor.Register( 4*4, this, false);
|
|
this.P = new B220Processor.Register( 4*4, this, false);
|
|
this.R = new B220Processor.Register(11*4, this, false);
|
|
this.S = new B220Processor.Register( 4*4, this, false);
|
|
|
|
// Control Console Lamps
|
|
this.digitCheckAlarm = new B220Processor.FlipFlop(this, false);
|
|
this.programCheckAlarm = new B220Processor.FlipFlop(this, false);
|
|
this.storageAlarm = new B220Processor.FlipFlop(this, false);
|
|
this.magneticTapeAlarm = new B220Processor.FlipFlop(this, false);
|
|
this.paperTapeAlarm = new B220Processor.FlipFlop(this, false);
|
|
this.cardatronAlarm = new B220Processor.FlipFlop(this, false);
|
|
|
|
this.systemNotReady = new B220Processor.FlipFlop(this, false);
|
|
this.computerNotReady = new B220Processor.FlipFlop(this, false);
|
|
this.computerNotReady.set(1); // initial state after power-up
|
|
|
|
// Control Console Switches
|
|
this.PC1SW = 0; // program control switches 1-10
|
|
this.PC2SW = 0;
|
|
this.PC3SW = 0;
|
|
this.PC4SW = 0;
|
|
this.PC5SW = 0;
|
|
this.PC6SW = 0;
|
|
this.PC7SW = 0;
|
|
this.PC8SW = 0;
|
|
this.PC9SW = 0;
|
|
this.PC0SW = 0;
|
|
|
|
this.SONSW = 0; // S "On" switch
|
|
this.SUNITSSW = 0; // S units switch
|
|
this.STOCSW = 0; // S to C switch
|
|
this.STOPSW = 0; // S to P switch
|
|
|
|
// Left-Hand Maintenance Panel Switches
|
|
this.HOLDPZTZEROSW = 0;
|
|
this.LEADINGZEROESSW = 0;
|
|
this.PAPERTAPESUMSW = 0;
|
|
this.ORDERCOMPLEMENTSW = 0;
|
|
this.MEMORYLOCKOUTSW = 0;
|
|
this.DCLOCKOUTSW = 0;
|
|
this.SPDPHOLDSW = 0;
|
|
this.HOLDSEQUENCE1SW = 0;
|
|
this.HOLDSEQUENCE2SW = 0;
|
|
this.HOLDSEQUENCE4SW = 0;
|
|
this.HOLDSEQUENCE8SW = 0;
|
|
|
|
// Left-Hand Maintenance Panel Registers & Flip-Flops
|
|
this.CI = new B220Processor.Register(5, this, false); // carry inverters
|
|
this.DC = new B220Processor.Register(6, this, false); // digit counter (modulo 20)
|
|
this.SC = new B220Processor.Register(4, this, false); // sequence counter
|
|
this.SI = new B220Processor.Register(4, this, false); // sum inverters
|
|
this.X = new B220Processor.Register(4, this, false); // adder X (augend) input
|
|
this.Y = new B220Processor.Register(4, this, false); // adder Y (addend) input
|
|
this.Z = new B220Processor.Register(4, this, false); // decimal sum inverters, adder output
|
|
|
|
this.C10 = new B220Processor.FlipFlop(this, false); // decimal carry toggle
|
|
this.DST = new B220Processor.FlipFlop(this, false); // D-sign toggle
|
|
this.LT1 = new B220Processor.FlipFlop(this, false); // logical toggle 1
|
|
this.LT2 = new B220Processor.FlipFlop(this, false); // logical toggle 2
|
|
this.LT3 = new B220Processor.FlipFlop(this, false); // logical toggle 3
|
|
this.SCI = new B220Processor.FlipFlop(this, false); // sequence counter inverter
|
|
this.SGT = new B220Processor.FlipFlop(this, false); // sign toggle
|
|
this.SUT = new B220Processor.FlipFlop(this, false); // subtract toggle
|
|
this.TBT = new B220Processor.FlipFlop(this, false); // tape busy toggle
|
|
this.TCT = new B220Processor.FlipFlop(this, false); // tape clock toggle
|
|
this.TPT = new B220Processor.FlipFlop(this, false); // tape pulse toggle
|
|
this.TWT = new B220Processor.FlipFlop(this, false); // tape write toggle
|
|
|
|
// Right-Hand Maintenance Panel Switches
|
|
this.MULTIPLEACCESSSW = 0;
|
|
this.V1V2V3COUNTSW = 0;
|
|
this.AUDIBLEALARMSW = 0;
|
|
this.PCOUNTSW = 0;
|
|
this.DIGITCHECKSW = 0;
|
|
this.ALARMSW = 0;
|
|
this.ADCOUNTSW = 0;
|
|
this.IDLEALARMSW = 0;
|
|
this.FREQUENCYSELECTSW = 0;
|
|
this.SINGLEPULSESW = 0;
|
|
this.FETCHEXECUTELOCKSW = 0;
|
|
|
|
// Right-Hand Maintenance Panel Registers & Flip-Flops
|
|
this.AX = new B220Processor.Register(10, this, false); // AX register
|
|
this.BI = new B220Processor.Register( 8, this, false); // paper tape decoder
|
|
this.DX = new B220Processor.Register( 8, this, false); // DX register
|
|
this.PA = new B220Processor.Register( 8, this, false); // PA register
|
|
|
|
this.ALT = new B220Processor.FlipFlop(this, false); // program check alarm toggle
|
|
this.AST = new B220Processor.FlipFlop(this, false); // asynchronous toggle
|
|
this.CCT = new B220Processor.FlipFlop(this, false); // ?? toggle
|
|
this.CRT = new B220Processor.FlipFlop(this, false); // Cardatron alarm toggle
|
|
this.DPT = new B220Processor.FlipFlop(this, false); // ?? digit pulse toggle
|
|
this.EWT = new B220Processor.FlipFlop(this, false); // end of word toggle
|
|
this.EXT = new B220Processor.FlipFlop(this, false); // fetch(0)/execute(1) toggle
|
|
this.HAT = new B220Processor.FlipFlop(this, false); // ?? toggle
|
|
this.HCT = new B220Processor.FlipFlop(this, false); // halt control toggle, for SOR, SOH, IOM (I think)
|
|
this.HIT = new B220Processor.FlipFlop(this, false); // high comparison toggle
|
|
this.MAT = new B220Processor.FlipFlop(this, false); // multiple access toggle
|
|
this.MET = new B220Processor.FlipFlop(this, false); // memory (storage) alarm toggle
|
|
this.MNT = new B220Processor.FlipFlop(this, false); // manual toggle
|
|
this.OFT = new B220Processor.FlipFlop(this, false); // overflow toggle
|
|
this.PAT = new B220Processor.FlipFlop(this, false); // paper tape alarm toggle
|
|
this.PRT = new B220Processor.FlipFlop(this, false); // paper tape read toggle
|
|
this.PZT = new B220Processor.FlipFlop(this, false); // paper tape zone toggle
|
|
this.RPT = new B220Processor.FlipFlop(this, false); // repeat toggle
|
|
this.RUT = new B220Processor.FlipFlop(this, false); // run toggle
|
|
this.SST = new B220Processor.FlipFlop(this, false); // single-step toggle
|
|
this.TAT = new B220Processor.FlipFlop(this, false); // magnetic tape alarm toggle
|
|
this.UET = new B220Processor.FlipFlop(this, false); // unequal comparison toggle (HIT=UET=0 => off)
|
|
|
|
// Mag-Tape Control Unit switch
|
|
this.tswSuppressB = 0; // Suppress B-register modification on input
|
|
|
|
// Context-bound routines
|
|
this.boundUpdateLampGlow = B220Processor.bindMethod(this, B220Processor.prototype.updateLampGlow);
|
|
|
|
this.boundConsoleOutputSignDigit = B220Processor.bindMethod(this, B220Processor.prototype.consoleOutputSignDigit);
|
|
this.boundConsoleOutputNumberDigit= B220Processor.bindMethod(this, B220Processor.prototype.consoleOutputNumberDigit);
|
|
this.boundConsoleOutputFinished = B220Processor.bindMethod(this, B220Processor.prototype.consoleOutputFinished);
|
|
this.boundConsoleInputDigit = B220Processor.bindMethod(this, B220Processor.prototype.consoleInputDigit);
|
|
this.boundConsoleReceiveDigit = B220Processor.bindMethod(this, B220Processor.prototype.consoleReceiveDigit);
|
|
this.boundConsoleReceiveSingleDigit = B220Processor.bindMethod(this, B220Processor.prototype.consoleReceiveSingleDigit);
|
|
|
|
this.boundCardatronOutputWordReady = B220Processor.bindMethod(this, B220Processor.prototype.cardatronOutputWordReady);
|
|
this.boundCardatronOutputWord= B220Processor.bindMethod(this, B220Processor.prototype.cardatronOutputWord);
|
|
this.boundCardatronOutputFinished = B220Processor.bindMethod(this, B220Processor.prototype.cardatronOutputFinished);
|
|
this.boundCardatronInputWord = B220Processor.bindMethod(this, B220Processor.prototype.cardatronInputWord);
|
|
this.boundCardatronReceiveWord = B220Processor.bindMethod(this, B220Processor.prototype.cardatronReceiveWord);
|
|
|
|
this.boundMagTapeReceiveBlock = B220Processor.bindMethod(this, B220Processor.prototype.magTapeReceiveBlock);
|
|
this.boundMagTapeInitiateSend = B220Processor.bindMethod(this, B220Processor.prototype.magTapeInitiateSend);
|
|
this.boundMagTapeSendBlock = B220Processor.bindMethod(this, B220Processor.prototype.magTapeSendBlock);
|
|
this.boundMagTapeTerminateSend = B220Processor.bindMethod(this, B220Processor.prototype.magTapeTerminateSend);
|
|
|
|
this.clear(); // Create and initialize the processor state
|
|
|
|
//this.loadDefaultProgram(); // Preload a default program
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* Global Constants *
|
|
***********************************************************************/
|
|
|
|
B220Processor.version = "0.00b";
|
|
|
|
B220Processor.tick = 0.005; // milliseconds per clock cycle (5MHz)
|
|
B220Processor.cyclesPerMilli = 1/B220Processor.tick;
|
|
// clock cycles per millisecond (200 => 5MHz)
|
|
B220Processor.timeSlice = 10; // maximum processor time slice, ms
|
|
B220Processor.delayAlpha = 0.001; // decay factor for exponential weighted average delay
|
|
B220Processor.delayAlpha1 = 1-B220Processor.delayAlpha;
|
|
B220Processor.slackAlpha = 0.0001; // decay factor for exponential weighted average slack
|
|
B220Processor.slackAlpha1 = 1-B220Processor.slackAlpha;
|
|
|
|
B220Processor.neonPersistence = 1000/30;
|
|
// persistence of neon bulb glow [ms]
|
|
B220Processor.maxGlowTime = B220Processor.neonPersistence;
|
|
// panel bulb glow persistence [ms]
|
|
B220Processor.lampGlowInterval = 50; // background lamp sampling interval (ms)
|
|
B220Processor.adderGlowAlpha = B220Processor.neonPersistence/12;
|
|
// adder and carry toggle glow decay factor,
|
|
// based on one digit (1/12 word) time [ms]
|
|
|
|
B220Processor.pow2 = [ // powers of 2 from 0 to 52
|
|
0x1, 0x2, 0x4, 0x8,
|
|
0x10, 0x20, 0x40, 0x80,
|
|
0x100, 0x200, 0x400, 0x800,
|
|
0x1000, 0x2000, 0x4000, 0x8000,
|
|
0x10000, 0x20000, 0x40000, 0x80000,
|
|
0x100000, 0x200000, 0x400000, 0x800000,
|
|
0x1000000, 0x2000000, 0x4000000, 0x8000000,
|
|
0x10000000, 0x20000000, 0x40000000, 0x80000000,
|
|
0x100000000, 0x200000000, 0x400000000, 0x800000000,
|
|
0x1000000000, 0x2000000000, 0x4000000000, 0x8000000000,
|
|
0x10000000000, 0x20000000000, 0x40000000000, 0x80000000000,
|
|
0x100000000000, 0x200000000000, 0x400000000000, 0x800000000000,
|
|
0x1000000000000, 0x2000000000000, 0x4000000000000, 0x8000000000000,
|
|
0x10000000000000];
|
|
|
|
B220Processor.mask2 = [ // (2**n)-1 for n from 0 to 52
|
|
0x0, 0x1, 0x3, 0x7,
|
|
0x0F, 0x1F, 0x3F, 0x7F,
|
|
0x0FF, 0x1FF, 0x3FF, 0x7FF,
|
|
0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF,
|
|
0x0FFFF, 0x1FFFF, 0x3FFFF, 0x7FFFF,
|
|
0x0FFFFF, 0x1FFFFF, 0x3FFFFF, 0x7FFFFF,
|
|
0x0FFFFFF, 0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF,
|
|
0x0FFFFFFF, 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF,
|
|
0x0FFFFFFFF, 0x1FFFFFFFF, 0x3FFFFFFFF, 0x7FFFFFFFF,
|
|
0x0FFFFFFFFF, 0x1FFFFFFFFF, 0x3FFFFFFFFF, 0x7FFFFFFFFF,
|
|
0x0FFFFFFFFFF, 0x1FFFFFFFFFF, 0x3FFFFFFFFFF, 0x7FFFFFFFFFF,
|
|
0x0FFFFFFFFFFF, 0x1FFFFFFFFFFF, 0x3FFFFFFFFFFF , 0x7FFFFFFFFFFF,
|
|
0x0FFFFFFFFFFFF, 0x1FFFFFFFFFFFF, 0x3FFFFFFFFFFFF, 0x7FFFFFFFFFFFF,
|
|
0x0FFFFFFFFFFFFF] ;
|
|
|
|
B220Processor.operandRequired = [ // true if op code requires a memory operand
|
|
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
|
0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, // 00-0F
|
|
1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 01-1F
|
|
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 02-2F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 03-3F
|
|
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 04-4F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 05-5F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 06-6F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 07-7F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 08-8F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // 09-9F
|
|
|
|
|
|
/***********************************************************************
|
|
* Utility Functions *
|
|
***********************************************************************/
|
|
|
|
/**************************************/
|
|
B220Processor.bindMethod = function bindMethod(context, f) {
|
|
/* Returns a new function that binds the function "f" to the object "context".
|
|
Note that this is a static constructor property function, NOT an instance
|
|
method of the CC object */
|
|
|
|
return function bindMethodAnon() {return f.apply(context, arguments)};
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.bcdBinary = function bcdBinary(v) {
|
|
/* Converts the BCD value "v" to a binary number and returns it. If the
|
|
BCD value is not decimal, returns NaN instead */
|
|
var d;
|
|
var power = 1;
|
|
var result = 0;
|
|
|
|
while(v) {
|
|
d = v % 0x10;
|
|
if (d > 9) {
|
|
result = Number.NaN;
|
|
break;
|
|
} else {
|
|
result += d*power;
|
|
power *= 10;
|
|
v = (v-d)/0x10;
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.binaryBCD = function binaryBCD(v) {
|
|
/* Converts the binary value "v" to a BCD number and returns it */
|
|
var d;
|
|
var power = 1;
|
|
var result = 0;
|
|
|
|
while(v) {
|
|
d = v % 10;
|
|
result += d*power;
|
|
power *= 0x10;
|
|
v = (v-d)/10;
|
|
}
|
|
return result;
|
|
};
|
|
|
|
|
|
/***********************************************************************
|
|
* Bit and Field Manipulation Functions *
|
|
***********************************************************************/
|
|
|
|
/**************************************/
|
|
B220Processor.bitTest = function bitTest(word, bit) {
|
|
/* Extracts and returns the specified bit from the word */
|
|
var p; // bottom portion of word power of 2
|
|
|
|
if (bit > 0) {
|
|
return ((word - word % (p = B220Processor.pow2[bit]))/p) % 2;
|
|
} else {
|
|
return word % 2;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.bitSet = function bitSet(word, bit) {
|
|
/* Sets the specified bit in word and returns the updated word */
|
|
var ue = bit+1; // word upper power exponent
|
|
var bpower = // bottom portion of word power of 2
|
|
B220Processor.pow2[bit];
|
|
var bottom = // unaffected bottom portion of word
|
|
(bit <= 0 ? 0 : (word % bpower));
|
|
var top = // unaffected top portion of word
|
|
word - (word % B220Processor.pow2[ue]);
|
|
|
|
return bpower + top + bottom;
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.bitReset = function bitReset(word, bit) {
|
|
/* Resets the specified bit in word and returns the updated word */
|
|
var ue = bit+1; // word upper power exponent
|
|
var bottom = // unaffected bottom portion of word
|
|
(bit <= 0 ? 0 : (word % B220Processor.pow2[bit]));
|
|
var top = // unaffected top portion of word
|
|
word - (word % B220Processor.pow2[ue]);
|
|
|
|
return top + bottom;
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.bitFlip = function bitFlip(word, bit) {
|
|
/* Complements the specified bit in word and returns the updated word */
|
|
var ue = bit+1; // word upper power exponent
|
|
var bpower = // bottom portion of word power of 2
|
|
B220Processor.pow2[bit];
|
|
var bottom = // unaffected bottom portion of word
|
|
(bit <= 0 ? 0 : (word % bpower));
|
|
var middle = // bottom portion of word starting with affected bit
|
|
word % B220Processor.pow2[ue];
|
|
var top = word - middle; // unaffected top portion of word
|
|
|
|
if (middle >= bpower) { // if the affected bit is a one
|
|
return top + bottom; // return the result with it set to zero
|
|
} else { // otherwise
|
|
return bpower + top + bottom; // return the result with it set to one
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.fieldIsolate = function fieldIsolate(word, start, width) {
|
|
/* Extracts a bit field [start:width] from word and returns the field */
|
|
var le = start-width+1; // lower power exponent
|
|
var p; // bottom portion of word power of 2
|
|
|
|
return (le <= 0 ? word :
|
|
(word - word % (p = B220Processor.pow2[le]))/p
|
|
) % B220Processor.pow2[width];
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.fieldInsert = function fieldInsert(word, start, width, value) {
|
|
/* Inserts a bit field from the low-order bits of value ([48-width:width])
|
|
into word.[start:width] and returns the updated word */
|
|
var ue = start+1; // word upper power exponent
|
|
var le = ue-width; // word lower power exponent
|
|
var bpower = // bottom portion of word power of 2
|
|
B220Processor.pow2[le];
|
|
var bottom = // unaffected bottom portion of word
|
|
(le <= 0 ? 0 : (word % bpower));
|
|
var top = // unaffected top portion of word
|
|
(ue <= 0 ? 0 : (word - (word % B220Processor.pow2[ue])));
|
|
|
|
return (value % B220Processor.pow2[width])*bpower + top + bottom;
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.fieldTransfer = function fieldTransfer(word, wstart, width, value, vstart) {
|
|
/* Inserts a bit field from value.[vstart:width] into word.[wstart:width] and
|
|
returns the updated word */
|
|
var ue = wstart+1; // word upper power exponent
|
|
var le = ue-width; // word lower power exponent
|
|
var ve = vstart-width+1; // value lower power exponent
|
|
var vpower; // bottom port of value power of 2
|
|
var bpower = // bottom portion of word power of 2
|
|
B220Processor.pow2[le];
|
|
var bottom = // unaffected bottom portion of word
|
|
(le <= 0 ? 0 : (word % bpower));
|
|
var top = // unaffected top portion of word
|
|
(ue <= 0 ? 0 : (word - (word % B220Processor.pow2[ue])));
|
|
|
|
return ((ve <= 0 ? value :
|
|
(value - value % (vpower = B220Processor.pow2[ve]))/vpower
|
|
) % B220Processor.pow2[width]
|
|
)*bpower + top + bottom;
|
|
};
|
|
|
|
|
|
/***********************************************************************
|
|
* System Clear *
|
|
***********************************************************************/
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.clear = function clear() {
|
|
/* Initializes (and if necessary, creates) the processor state */
|
|
|
|
this.clearControl();
|
|
|
|
// Primary Registers
|
|
this.A.set(0);
|
|
this.B.set(0);
|
|
this.C.set(0);
|
|
this.D.set(0);
|
|
this.E.set(0);
|
|
this.P.set(0);
|
|
this.R.set(0);
|
|
this.S.set(0);
|
|
this.IB.set(0);
|
|
|
|
// Control Console Lamps
|
|
this.digitCheckAlarm.set(0);
|
|
this.programCheckAlarm.set(0);
|
|
this.storageAlarm.set(0);
|
|
this.magneticTapeAlarm.set(0);
|
|
this.paperTapeAlarm.set(0);
|
|
this.cardatronAlarm.set(0);
|
|
|
|
this.systemNotReady.set(0);
|
|
this.computerNotReady.set(0);
|
|
|
|
// Left-Hand Maintenance Panel Registers & Flip-Flops
|
|
this.CI.set(0);
|
|
this.DC.set(0);
|
|
this.SC.set(0);
|
|
this.SI.set(0);
|
|
this.X.set(0);
|
|
this.Y.set(0);
|
|
this.Z.set(0);
|
|
|
|
this.C10.set(0);
|
|
this.DST.set(0);
|
|
this.LT1.set(0);
|
|
this.LT2.set(0);
|
|
this.LT3.set(0);
|
|
this.SCI.set(0);
|
|
this.SGT.set(0);
|
|
this.SUT.set(0);
|
|
this.TBT.set(0);
|
|
this.TCT.set(0);
|
|
this.TPT.set(0);
|
|
this.TWT.set(0);
|
|
|
|
// Right-Hand Maintenance Panel Registers & Flip-Flops
|
|
this.AX.set(0);
|
|
this.BI.set(0);
|
|
this.DX.set(0);
|
|
this.PA.set(0);
|
|
|
|
this.ALT.set(0);
|
|
this.AST.set(0);
|
|
this.CCT.set(0);
|
|
this.CRT.set(0);
|
|
this.DPT.set(0);
|
|
this.EWT.set(0);
|
|
this.EXT.set(this.FETCHEXECUTELOCKSW == 2 ? 1 : 0);
|
|
this.HAT.set(0);
|
|
this.HCT.set(0);
|
|
this.HIT.set(0);
|
|
this.MAT.set(0);
|
|
this.MET.set(0);
|
|
this.MNT.set(0);
|
|
this.OFT.set(0);
|
|
this.PAT.set(0);
|
|
this.PRT.set(0);
|
|
this.PZT.set(0);
|
|
this.RPT.set(0);
|
|
this.RUT.set(0);
|
|
this.SST.set(0);
|
|
this.TAT.set(0);
|
|
this.UET.set(0);
|
|
|
|
this.CCONTROL = 0; // copy of C register control digits (4 digits)
|
|
this.COP = 0; // copy of C register op code (2 digits)
|
|
this.CADDR = 0; // copy of C register operand address (4 digits)
|
|
|
|
// Kill any pending action that may be in process
|
|
if (this.scheduler) {
|
|
clearCallback(this.scheduler);
|
|
this.scheduler = 0;
|
|
}
|
|
|
|
// Clear Cardatron Control Unit
|
|
if (this.cardatron) {
|
|
this.cardatron.clear();
|
|
}
|
|
};
|
|
|
|
|
|
/***********************************************************************
|
|
* Generic Register Class *
|
|
***********************************************************************/
|
|
|
|
B220Processor.Register = function Register(bits, p, invisible) {
|
|
/* Constructor for the generic Register class. Defines a binary register
|
|
of "bits" bits. "p" is a reference to the Processor object, used to access
|
|
the timing members. "invisible" should be true if the register does not
|
|
have a visible presence in the UI -- this will inhibit computing average
|
|
lamp glow values for the register.
|
|
Note that it is important to increment this.realTime AFTER setting new values
|
|
in registers and flip-flops. This allows the average intensity to be computed
|
|
based on the amount of time a bit was actually in that state */
|
|
|
|
this.bits = bits; // number of bits in register
|
|
this.visible = (invisible ? false : true);
|
|
this.lastRealTime = 0; // time register was last set
|
|
this.p = p; // processor instance
|
|
this.value = 0; // binary value of register: read-only externally
|
|
|
|
this.glow = new Float64Array(bits); // average lamp glow values
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.Register.prototype.checkFC = function checkFC() {
|
|
/* Checks the register for a Forbidden Combination (hex A-F) digit. If at
|
|
least one exists, sets the Digit Check alarm and returns true. The bit mask
|
|
operations are done 28 bits at a time to avoid problems with the 32-bit
|
|
2s-complement arithmetic used by Javascript for bit operations */
|
|
var v1 = this.value; // high-order digits (eventually)
|
|
var v2 = v1%0x10000000; // low-order 7 digits
|
|
|
|
v1 = (v1-v2)/0x10000000;
|
|
|
|
if ( (((v1 & 0x8888888) >>> 3) & (((v1 & 0x4444444) >>> 2) | ((v1 & 0x2222222) >>> 1))) |
|
|
(((v2 & 0x8888888) >>> 3) & (((v2 & 0x4444444) >>> 2) | ((v2 & 0x2222222) >>> 1))) ) {
|
|
this.setDigitCheck(1);
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.Register.prototype.set = function set(value) {
|
|
/* Set a binary value into the register. Use this rather than setting
|
|
the value member directly so that average lamp glow can be computed.
|
|
Returns the new value. Note that the glow is always aged by at least one
|
|
clock tick */
|
|
var alpha = Math.max(Math.max(this.p.realTime-this.lastRealTime, B220Processor.tick)/
|
|
B220Processor.maxGlowTime, 1.0);
|
|
var alpha1 = 1.0-alpha;
|
|
var b = 0;
|
|
var bit;
|
|
var v = this.value;
|
|
|
|
// Update the lamp glow for the former state.
|
|
if (this.visible) {
|
|
while (v) {
|
|
bit = v % 2;
|
|
v = (v-bit)/2;
|
|
this.glow[b] = this.glow[b]*alpha1 + bit*alpha;
|
|
++b;
|
|
}
|
|
|
|
while (b < this.bits) {
|
|
this.glow[b] *= alpha1;
|
|
++b;
|
|
}
|
|
}
|
|
|
|
// Set the new state.
|
|
this.value = value;
|
|
this.lastRealTime = this.p.realTime;
|
|
this.checkFC(value);
|
|
return this.value;
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.Register.prototype.getDigit = function setBit(digitNr) {
|
|
/* Returns the value of a 4-bit digit in the register. Digits are numbered
|
|
from 0 starting at the low end (not the way the 220 numbers them) */
|
|
|
|
return B220Processor.fieldIsolate(this.value, digitNr*4-1, 4);
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.Register.prototype.getBit = function setBit(bitNr) {
|
|
/* Returns the value of a bit in the register */
|
|
|
|
return (bitNr < this.bits ? B220Processor.bitTest(this.value, bitNr) : 0);
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.Register.prototype.setBit = function setBit(bitNr, value) {
|
|
/* Set a bit on or off in the register. Returns the new register value.
|
|
Note that the glow is always aged by at least one clock tick */
|
|
var alpha = Math.max(Math.max(this.p.realTime-this.lastRealTime, B220Processor.tick)/
|
|
B220Processor.maxGlowTime, 1.0);
|
|
var bit = value%2;
|
|
|
|
if (bitNr < this.bits) {
|
|
// Update the lamp glow for the former state.
|
|
if (this.visible) {
|
|
this.glow[bitNr] = this.glow[bitNr]*(1.0-alpha) + bit*alpha;
|
|
|
|
// Set the new state.
|
|
this.value = (bit ? B220Processor.setBit(this.value, bitNr) : B220Processor.resetBit(this.value, bitNr));
|
|
}
|
|
|
|
this.checkFC(value);
|
|
return this.value;
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.Register.prototype.flipBit = function setBit(bitNr) {
|
|
/* Complements a bit in the register. Returns the new register value. Note
|
|
that the glow is always aged by at least one clock tick */
|
|
var alpha = Math.max(Math.max(this.p.realTime-this.lastRealTime, B220Processor.tick)/
|
|
B220Processor.maxGlowTime, 1.0);
|
|
var bit;
|
|
|
|
if (bitNr < this.bits) {
|
|
bit = 1 - B220Processor.bitTest(this.value, bitNr);
|
|
|
|
// Update the lamp glow for the former state.
|
|
if (this.visible) {
|
|
this.glow[bitNr] = this.glow[bitNr]*(1.0-alpha) + bit*alpha;
|
|
|
|
// Set the new state.
|
|
this.value = B220Processor.flipBit(this.value, bitNr);
|
|
}
|
|
|
|
this.checkFC(value);
|
|
return this.value;
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.Register.prototype.add = function add(addend) {
|
|
/* Adds "addend" to the current register value withoug regard to sign,
|
|
discarding any overflow beyond the number of bits defined for the register.
|
|
Returns the new register value. NOTE THAT THE ADDEND IS IN BCD, NOT BINARY */
|
|
|
|
return this.set(this.bcdAdd(this.value, addend) % B220Processor.pow2[this.bits]);
|
|
};
|
|
|
|
|
|
/***********************************************************************
|
|
* Generic Flip-Flop Class *
|
|
***********************************************************************/
|
|
|
|
B220Processor.FlipFlop = function FlopFlop(p, invisible) {
|
|
/* Constructor for the generaic FlipFlop class. "p" is a reference to the
|
|
Processor object, used to access the timing members. "invisible" should be
|
|
true if the FF does not have a visible presence in the UI -- this will
|
|
inhibit computing the average lamp glow value for it.
|
|
Note that it is important to increment this.realTime AFTER setting new values
|
|
in registers and flip-flops. This allows the average intensity to be computed
|
|
based on the amount of time a bit was actually in that state */
|
|
|
|
this.visible = (invisible ? false : true);
|
|
this.lastRealTime = 0; // time register was last set
|
|
this.p = p; // processor instance
|
|
this.value = 0; // binary value of register: read-only externally
|
|
this.glow = 0; // average lamp glow value
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.FlipFlop.prototype.set = function set(value) {
|
|
/* Set the value of the FF. Use this rather than setting the value member
|
|
directly so that average lamp glow can be computed. Returns the new value.
|
|
Note that the glow is always aged by at least one clock tick */
|
|
var alpha = Math.max(Math.max(this.p.realTime-this.lastRealTime, B220Processor.tick)/
|
|
B220Processor.maxGlowTime, 1.0);
|
|
var v = (value ? 1 : 0);
|
|
|
|
// Update the lamp glow for the former state.
|
|
if (this.visible) {
|
|
this.glow = this.glow*(1.0-alpha) + v*alpha;
|
|
}
|
|
|
|
// Set the new state.
|
|
this.value = v;
|
|
this.lastRealTime = this.p.realTime;
|
|
return this.value;
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.FlipFlop.prototype.flip = function flip() {
|
|
/* Complement the value of the FF. Returns the new value */
|
|
|
|
return this.set(this.value ? 0 : 1);
|
|
};
|
|
|
|
|
|
/***********************************************************************
|
|
* Timing and Statistics Functions *
|
|
***********************************************************************/
|
|
|
|
// TBD
|
|
|
|
|
|
/***********************************************************************
|
|
* System Alarms *
|
|
***********************************************************************/
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.setDigitCheck = function setDigitCheck(value) {
|
|
/* Sets the Digit Check alarm */
|
|
|
|
if (!this.ALARMSW && !this.DIGITCHECKSW) {
|
|
this.digitCheckAlarm.set(value);
|
|
if (value) {
|
|
this.RUT.set(0);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.setProgramCheck = function setProgramCheck(value) {
|
|
/* Sets the Program Check alarm */
|
|
|
|
if (!this.ALARMSW) {
|
|
this.programCheckAlarm.set(value);
|
|
this.ALT.set(value);
|
|
if (value) {
|
|
this.RUT.set(0);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.setStorageCheck = function setStorageCheck(value) {
|
|
/* Sets the Storage Check alarm */
|
|
|
|
if (!this.ALARMSW) {
|
|
this.storageCheckAlarm.set(value);
|
|
this.MET.set(value);
|
|
if (value) {
|
|
this.RUT.set(0);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.setMagneticTapeCheck = function setMagneticTapeCheck(value) {
|
|
/* Sets the Magnetic Tape Check alarm */
|
|
|
|
if (!this.ALARMSW) {
|
|
this.magneticTapeAlarm.set(value);
|
|
this.TAT.set(value);
|
|
if (value) {
|
|
this.RUT.set(0);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.setPaperTapeCheck = function setPaperTapeCheck(value) {
|
|
/* Sets the Paper Tape Check alarm */
|
|
|
|
if (!this.ALARMSW) {
|
|
this.paperTapeAlarm.set(value);
|
|
this.PAT.set(value);
|
|
if (value) {
|
|
this.RUT.set(0);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.setCardatronCheck = function setCardatronCheck(value) {
|
|
/* Sets the Cardatron Check alarm */
|
|
|
|
if (!this.ALARMSW) {
|
|
this.cardatronAlarm.set(value);
|
|
this.CRT.set(value);
|
|
if (value) {
|
|
this.RUT.set(0);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/***********************************************************************
|
|
* The 220 Adder and Arithmetic Operations *
|
|
***********************************************************************/
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.bcdAdd = function bcdAdd(a, d, complement, initialCarry) {
|
|
/* Performs an unsigned, BCD addition of "a" and "d", producing an 11-digit
|
|
BCD result. On input, "complement" indicates whether 9s-complement addition
|
|
should be performed; "initialCarry" indicates whether an initial carry of 1
|
|
should be applied to the adder. On output, this.CI is set from the final
|
|
carry toggles of the addition. Further, this.Z will still have a copy of the
|
|
sign (11th) digit. Sets the Program Check alarm if non-decimal digits are
|
|
encountered, but does not set the Overflow toggle */
|
|
var ad; // current augend (a) digit;
|
|
var adder; // local copy of adder digit
|
|
var am = a % 0x100000000000; // augend mantissa
|
|
var carry = (initialCarry || 0) & 1;// local copy of carry toggle (CI1, CAT)
|
|
var compl = complement || 0; // local copy of complement toggle
|
|
var ct = carry; // local copy of carry register (CI1-16)
|
|
var dd; // current addend (d) digit;
|
|
var dm = d % 0x100000000000; // addend mantissa
|
|
var x; // digit counter
|
|
|
|
this.DC.set(0x09); // 20-11: for display only
|
|
|
|
// Loop through the 11 digits including sign digits
|
|
for (x=0; x<11; ++x) {
|
|
// shift low-order augend digit right into the adder
|
|
ad = am % 0x10;
|
|
am = (am - ad)/0x10;
|
|
if (ad > 9) {
|
|
this.setDigitCheck(1);
|
|
} else if (compl) {
|
|
ad = 9-ad;
|
|
}
|
|
|
|
// Add the digits plus carry, complementing as necessary
|
|
dd = dm % 0x10;
|
|
if (dd > 9) {
|
|
this.setDigitCheck(1);
|
|
}
|
|
|
|
adder = ad + dd + carry;
|
|
|
|
// Decimal-correct the adder
|
|
if (adder < 10) {
|
|
carry = 0;
|
|
} else {
|
|
adder -= 10;
|
|
carry = 1;
|
|
}
|
|
|
|
// Compute the carry toggle register (just for display)
|
|
ct = (((ad & dd) | (ad & ct) | (dd & ct)) << 1) + carry;
|
|
|
|
// Update the visible registers (for display only)
|
|
this.X.set(ad);
|
|
this.Y.set(dd);
|
|
this.Z.set(adder);
|
|
this.C10.set(carry);
|
|
this.CI.set(ct);
|
|
this.SI.set(0x0F - ct); // just a guess as to the sum inverters
|
|
|
|
// rotate the adder into the sign digit
|
|
am += adder*0x10000000000;
|
|
// shift the addend right to the next digit
|
|
dm = (dm - dd)/0x10;
|
|
} // for x
|
|
|
|
// Set toggles for display purposes and return the result
|
|
return am;
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.integerAdd = function integerAdd() {
|
|
/* Algebraically add the addend (D) to the augend (A), returning the result
|
|
in A and clearing D. All values are BCD with the sign in the 11th digit
|
|
position. Sets the Overflow and Forbidden-Combination stops as necessary */
|
|
var am = this.A % 0x10000000000; // augend mantissa
|
|
var aSign = ((this.A - am)/0x10000000000);
|
|
var compl; // complement addition required
|
|
var dm = this.D % 0x10000000000; // addend mantissa
|
|
var dSign = ((this.D - dm)/0x10000000000);
|
|
var sign = dSign & 0x01; // local copy of sign toggle
|
|
|
|
this.togADDER = 1; // for display only
|
|
this.togDPCTR = 1; // for display only
|
|
compl = (aSign^dSign) & 0x01;
|
|
am = this.bcdAdd(am, dm, compl, compl);
|
|
|
|
// Now examine the resulting sign (still in the adder) to see if we have overflow
|
|
// or need to recomplement the result
|
|
switch (this.ADDER) {
|
|
case 0:
|
|
am += sign*0x10000000000;
|
|
break;
|
|
case 1:
|
|
am += (sign-1)*0x10000000000;
|
|
this.setOverflow(1);
|
|
break;
|
|
default: // sign is 9
|
|
// reverse the sign toggle and recomplement the result (virtually adding to the zeroed dm)
|
|
sign = 1-sign;
|
|
am = this.bcdAdd(am, 0, 1, 1);
|
|
// after recomplementing, set the correct sign (adder still contains sign of result)
|
|
am += (sign - this.ADDER)*0x10000000000;
|
|
this.procTime += 2;
|
|
break;
|
|
} // switch this.ADDER
|
|
|
|
// Set toggles for display purposes and return the result
|
|
this.togSIGN = sign;
|
|
this.A = am;
|
|
this.D = 0;
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.integerExtract = function integerExtract() {
|
|
/* "Extract" digits from A according to the digit pattern in D.
|
|
This is actually a weird form of add. In the simple case, the extract
|
|
pattern consists of a string of ones and zeroes. The value digits will be
|
|
retained where the corresponding pattern digits are 1 and will be zeroed
|
|
where the corresponding pattern digits are zero.
|
|
In the general case, if the pattern digit is even, that digit replaces the
|
|
corresponding digit in the value. If the pattern digit is odd, then that
|
|
digit minus one is added to the corresponding value digit. In this latter
|
|
case, carries from digit to digit occur normally, and thus a generalized
|
|
extract can result in overflow.
|
|
Sets the Overflow and Forbidden-Combination stops as necessary */
|
|
var ad; // current value (A) digit;
|
|
var adder = 0; // local copy of adder digit
|
|
var am = this.A % 0x10000000000; // value mantissa
|
|
var aSign = ((this.A - am)/0x10000000000) & 0x01;
|
|
var carry; // local copy of carry toggle (CT 1)
|
|
var ct; // local copy of carry register (CT 1-16)
|
|
var dd; // current pattern (D) digit;
|
|
var dm = this.D; // pattern mantissa
|
|
var dSign = ((this.D - this.D%0x10000000000)/0x10000000000);
|
|
var sign = aSign & dSign & 0x01; // local copy of sign toggle
|
|
var x; // digit counter
|
|
|
|
this.togCLEAR = 1; // for display only
|
|
this.togADDER = 1; // for display only
|
|
this.togDPCTR = 1; // for display only
|
|
ct = carry = 0;
|
|
|
|
// Loop through the 11 digits including signs (which were set to zero in am and dm)
|
|
for (x=0; x<11; ++x) {
|
|
// shift low-order value digit right into the adder
|
|
ad = am % 0x10;
|
|
am = (am - ad)/0x10;
|
|
if (ad > 9) {
|
|
this.stopForbidden = 1;
|
|
this.togCST = 1; // halt the processor
|
|
}
|
|
|
|
// add the digits plus carry, complementing as necessary
|
|
dd = dm % 0x10;
|
|
// shift the pattern right to the next digit
|
|
dm = (dm - dd)/0x10;
|
|
if (dd > 9) {
|
|
this.stopForbidden = 1;
|
|
this.togCST = 1; // halt the processor
|
|
}
|
|
if (dd & 0x01) { // if extract digit is odd
|
|
dd &= 0x0E; // force extract digit to even (so adder=ad+dd-1)
|
|
} else { // otherwise, if it's even
|
|
ad = 0; // clear A digit so D digit will replace it (adder=dd)
|
|
}
|
|
adder = ad + dd + carry;
|
|
|
|
// decimal-correct the adder
|
|
if (adder < 10) {
|
|
carry = 0;
|
|
} else {
|
|
adder -= 10;
|
|
carry = 1;
|
|
}
|
|
|
|
// compute the carry toggle register (just for display)
|
|
ct = (((ad & dd) | (ad & ct) | (dd & ct)) << 1) + carry;
|
|
// rotate the adder into the sign digit
|
|
am += adder*0x10000000000;
|
|
} // for x
|
|
|
|
// Now examine the resulting sign (still in the adder) to see if we have overflow
|
|
if (adder == 0) {
|
|
am += sign*0x10000000000;
|
|
} else if (adder == 1) {
|
|
am += (sign-1)*0x10000000000;
|
|
this.setOverflow(1);
|
|
}
|
|
|
|
// Set toggles for display purposes and return the result
|
|
this.togCOMPL = 0;
|
|
this.togSIGN = sign;
|
|
this.CT = ct;
|
|
this.ADDER = adder;
|
|
this.A = am;
|
|
this.D = 0;
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.integerMultiply = function integerMultiply(roundOff) {
|
|
/* Algebraically multiply the D register by the A register, producing a
|
|
20-digit product in A and R. All values are BCD with the sign in the 11th digit
|
|
position. If roundOff is truthy, the product in the A register is rounded and
|
|
the R register is cleared. Sets Forbidden-Combination stop as necessary.
|
|
Overflow is not possible */
|
|
var ad; // current product (A) digit;
|
|
var am = this.A % 0x10000000000; // product (A) mantissa
|
|
var aSign = ((this.A - am)/0x10000000000) & 0x01;
|
|
var count = 0; // count of word-times consumed
|
|
var dm = this.D % 0x10000000000; // multiplicand mantissa
|
|
var dSign = ((this.D - dm)/0x10000000000) & 0x01;
|
|
var rc; // dup of rd for add counting
|
|
var rd; // current multipler (R) digit;
|
|
var rm = am; // current multiplier (R) mantissa
|
|
var sign = aSign ^ dSign; // local copy of sign toggle (sign of product)
|
|
var x; // digit counter
|
|
|
|
this.togMULDIV = 1; // for display only
|
|
this.SHIFT = 0x09; // for display only
|
|
this.SHIFTCONTROL = 0x0C; // for display only
|
|
am = 0; // clear the local product (A) mantissa
|
|
|
|
// We now have the multiplicand in D (dm), the multiplier in R (rm), and an
|
|
// initial product of zero in A (am). Go through a classic multiply cycle,
|
|
// doing repeated addition based on each multipler digit, and between digits
|
|
// shifting the product (in am and rm) one place to the right. After 10 digits,
|
|
// we're done.
|
|
|
|
this.togCOMPL = 0;
|
|
for (x=0; x<10; ++x) {
|
|
rc = rd = rm % 0x10;
|
|
count += rc;
|
|
while (rc > 0) {
|
|
am = this.bcdAdd(am, dm, 0, 0);
|
|
--rc;
|
|
} // while rd
|
|
|
|
ad = am % 0x10;
|
|
am = (am-ad)/0x10;
|
|
rm = (rm-rd)/0x10 + ad*0x1000000000;
|
|
} // for x
|
|
|
|
if (roundOff) {
|
|
++count;
|
|
if (rm >= 0x5000000000) {
|
|
am = this.bcdAdd(am, 0x01, 0, 0);
|
|
}
|
|
this.R = this.D = 0;
|
|
}
|
|
|
|
this.procTime += 13 + count*2;
|
|
this.SPECIAL = 0x09; // for display only
|
|
this.togSIGN = sign; // for display only
|
|
this.A = sign*0x10000000000 + am;
|
|
this.R = rm;
|
|
this.D = 0;
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.integerDivide = function integerDivide() {
|
|
/* Algebraically divide the A & R registers by the D register, producing a
|
|
signed 10-digit quotient in A and the remainder in R. All values are BCD
|
|
with the sign in the 11th digit position. Sets Forbidden-Combination stop
|
|
as necessary. If the magnitude of the divisor (D) is less or equal to the
|
|
magnitude of the dividend (A), the Overflow stop it set and division
|
|
terminates, unconditionally setting A & R to zero */
|
|
var am = this.A % 0x10000000000; // current remainder (A) mantissa
|
|
var aSign = ((this.A - am)/0x10000000000) & 0x01;
|
|
var count = 0; // count of word-times consumed
|
|
var dm = this.D % 0x10000000000; // divisor mantissa
|
|
var dSign = ((this.D - dm)/0x10000000000) & 0x01;
|
|
var rd; // current quotient (R) digit;
|
|
var rm = this.R; // current quotient (R) mantissa
|
|
var sign = aSign ^ dSign; // local copy of sign toggle (sign of quotient)
|
|
var x; // digit counter
|
|
|
|
this.togMULDIV = 1; // for display only
|
|
this.togDELTABDIV = 1; // for display only
|
|
this.togDIVALARM = 1; // for display only
|
|
this.SPECIAL = 0x09; // for display only: state at end unless overflow is set
|
|
|
|
// We now have the divisor in D (dm) and the dividend in A (am) & R (rm).
|
|
// The value in am will become the remainder; the value in rm will become
|
|
// the quotient. Go through a classic long-division cycle, repeatedly
|
|
// subtracting the divisor from the dividend, counting subtractions until
|
|
// underflow occurs, and shifting the divisor left one digit.
|
|
|
|
for (x=0; x<10; ++x) {
|
|
// First, shift A & R to the left one digit, with A1 shifting to ASGN
|
|
rd = (rm - rm%0x1000000000)/0x1000000000;
|
|
rm = (rm%0x1000000000)*0x10;
|
|
am = am*0x10 + rd;
|
|
|
|
// Now repeatedly subtract D from A until we would get underflow.
|
|
// Unlike the 220, we don't do one subtraction too many.
|
|
rd = 0;
|
|
while (am >= dm && rd < 10) {
|
|
am = this.bcdAdd(dm, am, 1, 1);
|
|
++rd;
|
|
++count;
|
|
}
|
|
|
|
// Check for overflow (dividend > divisor).
|
|
if (rd < 10) {
|
|
rm += rd; // accumulate the quotient digit
|
|
this.togDIVALARM = 0;
|
|
} else {
|
|
this.setOverflow(1);
|
|
this.SPECIAL = 0x0F; // for display only
|
|
am = rm = 0;
|
|
break; // out of for loop
|
|
}
|
|
} // for x
|
|
|
|
this.SHIFTCONTROL = 0x0E; // for display only
|
|
this.SHIFT = 0x09; // for display only
|
|
this.togSIGN = sign; // for display only
|
|
this.togSTEP = 1; // for display only
|
|
this.A = sign*0x10000000000 + rm;
|
|
this.R = am;
|
|
this.D = 0;
|
|
if (this.stopOverflow) {
|
|
this.procTime += 24;
|
|
} else {
|
|
this.procTime += 52 + count*2;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.floatingAdd = function floatingAdd() {
|
|
/* Algebraically add the floating-point addend (D) to the floating-point
|
|
augend (A), placing the result in A and clearing D. The R register is not
|
|
affected. All values are BCD with the sign in the 11th digit position.
|
|
The floating exponent is in the first two digit positions, biased by 50.
|
|
Sets the Overflow and Forbidden-Combination stops as necessary */
|
|
var ae; // augend exponent (binary)
|
|
var am = this.A % 0x10000000000; // augend mantissa (BCD)
|
|
var aSign = ((this.A - am)/0x10000000000) & 0x01;
|
|
var compl; // complement addition required
|
|
var d; // scratch digit;
|
|
var de; // addend exponent (binary)
|
|
var dm = this.D % 0x10000000000; // addend mantissa (BCD)
|
|
var dSign = ((this.D - dm)/0x10000000000) & 0x01;
|
|
var sign = dSign; // local copy of sign toggle
|
|
|
|
this.togADDER = 1; // for display only
|
|
this.togDPCTR = 1; // for display only
|
|
|
|
ae = (am - am%0x100000000)/0x100000000;
|
|
am %= 0x100000000;
|
|
de = (dm - dm%0x100000000)/0x100000000;
|
|
dm %= 0x100000000;
|
|
|
|
// If the exponents are unequal, normalize the larger and scale the smaller
|
|
// until they are in alignment, or one mantissa becomes zero.
|
|
if (am == 0) {
|
|
ae = de;
|
|
} else if (dm == 0) {
|
|
de = ae;
|
|
} else if (ae > de) {
|
|
// Normalize A
|
|
while (ae > de && am < 0x10000000) {
|
|
am *= 0x10; // shift left
|
|
ae = this.bcdAdd(1, ae, 1, 1); // --ae
|
|
}
|
|
// Scale D until its exponent matches or the mantissa goes to zero.
|
|
while (ae > de && dm > 0) {
|
|
d = dm % 0x10;
|
|
dm = (dm - d)/0x10; // shift right
|
|
de = this.bcdAdd(1, de, 0, 0); // ++de
|
|
}
|
|
} else if (ae < de) {
|
|
// Normalize D
|
|
while (ae < de && dm < 0x10000000) {
|
|
dm *= 0x10; // shift left
|
|
de = this.bcdAdd(1, de, 1, 1); // --de
|
|
}
|
|
// Scale A until its exponent matches or the mantissa goes to zero.
|
|
while (ae < de && am > 0) {
|
|
d = am % 0x10;
|
|
am = (am - d)/0x10; // shift right
|
|
ae = this.bcdAdd(1, ae, 0, 0); // ++ae
|
|
}
|
|
}
|
|
|
|
compl = (aSign^dSign);
|
|
am = this.bcdAdd(am, dm, compl, compl);
|
|
|
|
// Now examine the resulting sign (still in the adder) to see if we
|
|
// need to recomplement the result.
|
|
if (this.ADDER) {
|
|
// Reverse the sign toggle and recomplement the result (virtually adding to the zeroed dm).
|
|
sign = 1-sign;
|
|
am = this.bcdAdd(am, 0, 1, 1);
|
|
this.procTime += 2;
|
|
}
|
|
|
|
// Normalize or scale the result as necessary
|
|
this.SPECIAL = 0; // for display only
|
|
if (am >= 0x100000000) {
|
|
// Mantissa overflow: add/subtract can produce at most one digit of
|
|
// overflow, so shift right and increment the exponent, checking for
|
|
// overflow in the exponent.
|
|
if (ae < 0x99) {
|
|
++this.SPECIAL; // for display only
|
|
d = am % 0x10;
|
|
am = (am - d)/0x10; // shift right
|
|
ae = this.bcdAdd(1, ae, 0, 0); // ++ae
|
|
} else {
|
|
// A scaling shift would overflow the exponent, so set the overflow
|
|
// alarm and leave the mantissa as it was from the add, without the
|
|
// exponent inserted back into it. Since the A register gets reassembled
|
|
// below, we need to set up the mantissa and exponent so the reconstruct
|
|
// will effectively do nothing.
|
|
this.setOverflow(1);
|
|
sign = 0; // per 220 FP Handbook
|
|
ae = (am - am%0x100000000)/0x100000000;
|
|
am %= 0x100000000;
|
|
}
|
|
} else if (am > 0) {
|
|
// Normalize the result as necessary
|
|
while (am < 0x10000000) {
|
|
if (ae > 0) {
|
|
++this.SPECIAL; // for display only
|
|
am *= 0x10; // shift left
|
|
ae = this.bcdAdd(1, ae, 1, 1); // --ae
|
|
} else {
|
|
// Exponent underflow: set R and the reconstructed A to zero.
|
|
am = ae = sign = 0;
|
|
this.R = 0;
|
|
break;
|
|
}
|
|
}
|
|
} else { // mantissa is zero
|
|
ae = 0; // per example in 220 FP Handbook
|
|
}
|
|
|
|
// Set toggles for display purposes and set the result.
|
|
this.togSIGN = sign;
|
|
this.A = (sign*0x100 + ae)*0x100000000 + am;
|
|
this.D = 0;
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.floatingMultiply = function floatingMultiply() {
|
|
/* Algebraically multiply the floating-point multiplicand in the D register
|
|
by the floating-point multiplier in the A register, producing an 18-digit
|
|
product (16 mantissa + 2 exponent) in A and R. All values are BCD with the
|
|
sign in the 11th digit position. The floating exponent is in the first two
|
|
digit positions, biased by 50. Sets the Forbidden-Combination stop as
|
|
necessary */
|
|
var ad; // current product (A) digit;
|
|
var ae; // product/multiplier (A) exponent
|
|
var am = this.A % 0x10000000000; // product (A) mantissa
|
|
var aSign = ((this.A - am)/0x10000000000) & 0x01;
|
|
var count = 0; // count of word-times consumed
|
|
var de; // multiplicand exponent
|
|
var dm = this.D % 0x10000000000; // multiplicand mantissa
|
|
var dSign = ((this.D - dm)/0x10000000000) & 0x01;
|
|
var rc; // dup of rd for add counting
|
|
var rd; // current multipler (R) digit;
|
|
var rm; // current multiplier (R) mantissa
|
|
var sign = aSign ^ dSign; // local copy of sign toggle (sign of product)
|
|
var x; // digit counter
|
|
|
|
this.togMULDIV = 1; // for display only
|
|
this.SPECIAL = 0; // for display only
|
|
ae = (am - am%0x100000000)/0x100000000;
|
|
am %= 0x100000000;
|
|
de = (dm - dm%0x100000000)/0x100000000;
|
|
dm %= 0x100000000;
|
|
|
|
if (am == 0) {
|
|
this.A = this.R = 0;
|
|
} else if (dm == 0) {
|
|
this.A = this.R = 0;
|
|
} else {
|
|
ae = this.bcdAdd(ae, de);
|
|
if (ae >= 0x150) {
|
|
this.setOverflow(1);
|
|
sign = 0;
|
|
this.A = am;
|
|
this.R = 0;
|
|
} else {
|
|
rm = am; // move the multiplier to R
|
|
am = 0; // clear the local product (A) mantissa
|
|
this.SHIFT = 0x09; // for display only
|
|
this.SHIFTCONTROL = 0x0C; // for display only
|
|
|
|
// We now have the multiplicand in D (dm), the multiplier in R (rm), and an
|
|
// initial product of zero in A (am). Go through a classic multiply cycle,
|
|
// doing repeated addition based on each multipler digit, and between digits
|
|
// shifting the product (in am and rm) one place to the right. After 8 digits,
|
|
// we're done, except for normalization.
|
|
|
|
this.togCOMPL = 0;
|
|
for (x=0; x<8; ++x) {
|
|
rc = rd = rm % 0x10;
|
|
count += rc;
|
|
while (rc > 0) {
|
|
am = this.bcdAdd(am, dm, 0, 0);
|
|
--rc;
|
|
} // while rd
|
|
|
|
ad = am % 0x10;
|
|
am = (am-ad)/0x10;
|
|
rm = (rm-rd)/0x10 + ad*0x1000000000;
|
|
} // for x
|
|
|
|
// Check for exponent underflow
|
|
if (ae < 0x50) {
|
|
this.A = this.R = 0; // underflow
|
|
} else {
|
|
// Subtract the exponent bias and normalize the result as necessary.
|
|
ae = this.bcdAdd(0x50, ae, 1, 1);
|
|
while (am < 0x10000000) {
|
|
if (ae <= 0) {
|
|
// Exponent underflow: set R and the reconstructed A to zero.
|
|
am = ae = sign = 0;
|
|
this.R = 0;
|
|
break;
|
|
} else {
|
|
++this.SPECIAL; // for display only
|
|
rd = (rm - rm%0x1000000000)/0x1000000000;
|
|
rm = (rm % 0x1000000000)*0x10;
|
|
am = am*0x10 + rd; // shift left
|
|
ae = this.bcdAdd(1, ae, 1, 1); // --ae
|
|
}
|
|
}
|
|
|
|
this.A = (sign*0x100 + ae)*0x100000000 + am;
|
|
this.R = rm;
|
|
}
|
|
|
|
this.procTime += 13 + count*2;
|
|
}
|
|
}
|
|
|
|
this.togSIGN = sign; // for display only
|
|
this.D = 0;
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.floatingDivide = function floatingDivide() {
|
|
/* Algebraically divide the 18-digit (16 mantissa + 2 exponent) floating-
|
|
point dividend in the A & R registers by the floating-point divisor in the
|
|
D register, producing a 9- or 10-digit quotient in the A & R registers
|
|
and a 6- or 7-digit remainder in the low-order digits of the R register.
|
|
See the Floating Point Handbook for the gory details of the result format.
|
|
All values are BCD with the sign in the 11th digit position. The floating
|
|
exponent is in the first two digit positions, biased by 50. Sets the
|
|
Forbidden-Combination stop as necessary */
|
|
var ae; // dividend/quotient exponent
|
|
var am = this.A % 0x10000000000; // current remainder (A) mantissa
|
|
var aSign = ((this.A - am)/0x10000000000) & 0x01;
|
|
var count = 0; // count of word-times consumed
|
|
var de; // divisor exponent
|
|
var dm = this.D % 0x10000000000; // divisor mantissa
|
|
var dSign = ((this.D - dm)/0x10000000000) & 0x01;
|
|
var rd; // current quotient (R) digit;
|
|
var rm = this.R; // current quotient (R) mantissa
|
|
var sign = aSign ^ dSign; // local copy of sign toggle (sign of quotient)
|
|
var x; // digit counter
|
|
|
|
this.togMULDIV = 1; // for display only
|
|
this.togDELTABDIV = 1; // for display only
|
|
this.togDIVALARM = 0; // for display only
|
|
this.SPECIAL = 0; // for display only
|
|
ae = (am - am%0x100000000)/0x100000000;
|
|
am %= 0x100000000;
|
|
de = (dm - dm%0x100000000)/0x100000000;
|
|
dm %= 0x100000000;
|
|
|
|
// Normalize A & R
|
|
while (am && am < 0x10000000) {
|
|
if (ae <= 0) {
|
|
am = 0; // exponent underflow
|
|
} else {
|
|
rd = (rm - rm%0x1000000000)/0x1000000000;
|
|
rm = (rm % 0x1000000000)*0x10;
|
|
am = am*0x10 + rd; // shift left
|
|
ae = this.bcdAdd(1, ae, 1, 1); // --ae
|
|
}
|
|
}
|
|
|
|
// Normalize D
|
|
while (dm && dm < 0x10000000) {
|
|
if (de <= 0) {
|
|
dm = 0; // exponent underflow
|
|
} else {
|
|
dm *= 0x10; // shift left
|
|
de = this.bcdAdd(1, de, 1, 1); // --de
|
|
}
|
|
}
|
|
|
|
// Check for zero operands and commence the division
|
|
if (am == 0) {
|
|
this.A = this.R = sign = 0; // dividend is zero so result is zero
|
|
} else if (dm == 0) {
|
|
this.A = this.R = sign = 0; // divide by zero
|
|
this.togDIVALARM = 1; // for display only
|
|
this.setOverflow(1);
|
|
} else {
|
|
// Add the exponent bias to the dividend exponent and check for underflow
|
|
ae = this.bcdAdd(ae, 0x50);
|
|
if (ae < de) {
|
|
// Exponents differ by more than 50 -- underflow
|
|
this.A = this.R = sign = 0;
|
|
} else {
|
|
// If dividend >= divisor, scale the exponent by 1
|
|
if (am >= dm) {
|
|
ae = this.bcdAdd(ae, 1);
|
|
}
|
|
// Subtract the exponents and check for overflow
|
|
ae = this.bcdAdd(de, ae, 1, 1);
|
|
if (ae > 0x99) {
|
|
this.setOverflow(1);
|
|
sign = 0;
|
|
this.A = am;
|
|
this.R = rm;
|
|
} else {
|
|
// We now have the divisor in D (dm) and the dividend in A (am) & R (rm).
|
|
// The value in am will become the remainder; the value in rm will become
|
|
// the quotient. Go through a classic long-division cycle, repeatedly
|
|
// subtracting the divisor from the dividend, counting subtractions until
|
|
// underflow occurs, and shifting the divisor left one digit.
|
|
|
|
for (x=0; x<10; ++x) {
|
|
// Repeatedly subtract D from A until we would get underflow.
|
|
// Unlike the 220, we don't do one subtraction too many.
|
|
rd = 0;
|
|
while (am >= dm) {
|
|
am = this.bcdAdd(dm, am, 1, 1);
|
|
++rd;
|
|
++count;
|
|
}
|
|
|
|
// Shift A & R to the left one digit, accumulating the quotient digit in R
|
|
rm = rm*0x10 + rd;
|
|
rd = (rm - rm%0x10000000000)/0x10000000000;
|
|
rm %= 0x10000000000;
|
|
if (x < 9) {
|
|
am = am*0x10 + rd; // shift into remainder except on last digit
|
|
}
|
|
} // for x
|
|
|
|
// Rotate the quotient from R into A for 8 digits or until it's normalized
|
|
for (x=0; x<8 || am%0x100000000 < 0x10000000; ++x) {
|
|
++this.SPECIAL; // for display only
|
|
rd = (am - am%0x1000000000)/0x1000000000;
|
|
rm = rm*0x10 + rd;
|
|
rd = (rm - rm%0x10000000000)/0x10000000000;
|
|
rm %= 0x10000000000;
|
|
am = (am%0x1000000000)*0x10 + rd;
|
|
}
|
|
|
|
this.SHIFTCONTROL = 0x0E; // for display only
|
|
this.SHIFT = 0x09; // for display only
|
|
this.togSTEP = 1; // for display only
|
|
|
|
this.A = (sign*0x100 + ae)*0x100000000 + am%0x100000000;
|
|
this.R = rm;
|
|
}
|
|
|
|
if (this.stopOverflow) {
|
|
this.procTime += 24;
|
|
} else {
|
|
this.procTime += 52 + count*2;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.togSIGN = sign; // for display only
|
|
this.D = 0;
|
|
};
|
|
|
|
|
|
/***********************************************************************
|
|
* Memory Access *
|
|
***********************************************************************/
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.readMemory = function readMemory() {
|
|
/* Reads the contents of one word of memory into the IB register from the
|
|
address in the E register. Sets the Storage Check alarm if the address is
|
|
not valid. Returns the word fetched, or the current value of IB if invalid
|
|
address */
|
|
var addr = B220Processor.bcdBinary(this.E.value);
|
|
|
|
if (isNaN(addr)) {
|
|
this.setStorageCheck(1);
|
|
return this.IB.value;
|
|
} else if (addr >= this.memorySize) {
|
|
this.setStorageCheck(1);
|
|
return this.IB.value;
|
|
} else if (this.MEMORYLOCKOUTSW) {
|
|
return this.IB.set(this.D.value);
|
|
} else {
|
|
return this.IB.set(this.MM[addr]);
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.writeMemory = function writeMemory() {
|
|
/* Stores one word of memory from the IB register to the address in the E
|
|
register. Sets the Storage Check alarm if the address is not valid */
|
|
var addr = B220Processor.bcdBinary(this.E.value);
|
|
|
|
if (isNaN(addr)) {
|
|
this.setStorageCheck(1);
|
|
} else if (addr >= this.memorySize) {
|
|
this.setStorageCheck(1);
|
|
} else if (this.MEMORYLOCKOUTSW) {
|
|
this.D.set(this.IB.value);
|
|
} else {
|
|
this.MM[addr] = this.IB.value;
|
|
}
|
|
};
|
|
|
|
|
|
/***********************************************************************
|
|
* Console I/O Module *
|
|
***********************************************************************/
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.consoleOutputSignDigit = function consoleOutputSignDigit() {
|
|
/* Outputs the sign digit for a PTW (03) command and sets up to output the
|
|
first number digit. If the Shift Counter is already at 19, terminates the
|
|
output operation */
|
|
var d;
|
|
var w = this.A % 0x10000000000;
|
|
|
|
if (this.togPO1) { // if false, we've probably been cleared
|
|
d = (this.A - w)/0x10000000000; // get the digit
|
|
this.A = w*0x10 + d; // rotate A+sign left one
|
|
if (this.SHIFT == 0x19) { // if the shift counter is already 19, we're done
|
|
this.togOK = this.togPO1 = 0; // for display only
|
|
this.consoleOutputFinished();
|
|
} else {
|
|
this.togOK = 1-this.togOK; // for dislay only
|
|
this.togPO2 = 1; // for display only
|
|
this.togDELAY = 0; // for display only
|
|
this.console.writeSignDigit(d, this.boundConsoleOutputNumberDigit);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.consoleOutputNumberDigit = function consoleOutputNumberDigit() {
|
|
/* Outputs a numeric digit for a PTW (03) command and sets up to output the
|
|
next number digit. If the Shift Counter is already at 19, terminates the
|
|
output operation and sends a Finish signal */
|
|
var d;
|
|
var w = this.A % 0x10000000000;
|
|
|
|
if (this.togPO1) { // if false, we've probably been cleared
|
|
this.togOK = 1-this.togOK; // for dislay only
|
|
this.togPO2 = 1-this.togPO2; // for display only
|
|
this.togDELAY = 1-this.togDELAY;// for display only
|
|
if (this.SHIFT == 0x19) { // if the shift counter is already 19, we're done
|
|
d = (this.CADDR - this.CADDR%0x1000)/0x1000;
|
|
this.console.writeFinish(d, this.boundConsoleOutputFinished);
|
|
} else {
|
|
d = (this.A - w)/0x10000000000; // get the digit
|
|
this.A = w*0x10 + d; // rotate A+sign left one
|
|
this.SHIFT = this.bcdAdd(this.SHIFT, 1);
|
|
this.console.writeNumberDigit(d, this.boundConsoleOutputNumberDigit);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.consoleOutputFinished = function consoleOutputFinished() {
|
|
/* Handles the final cycle of an I/O operation and restores this.procTime */
|
|
|
|
if (this.togOK || this.togPO1) { // if false, we've probably been cleared
|
|
this.togOK = 0; // for display only
|
|
this.togPO1 = this.togPO2 = 0; // for display only
|
|
this.togDELAY = 0; // for display only
|
|
this.stopIdle = 0; // turn IDLE lamp back off now that we're done
|
|
this.procTime += performance.now()*B220Processor.wordsPerMilli;
|
|
this.schedule();
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.consoleInputDigit = function consoleInputDigit() {
|
|
// Solicits the next input digit from the Control Console */
|
|
|
|
this.togTF = 0; // for display only, reset finish pulse
|
|
this.procTime -= performance.now()*B220Processor.wordsPerMilli; // mark time during I/O
|
|
this.console.readDigit(this.boundConsoleReceiveDigit);
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.consoleReceiveDigit = function consoleReceiveDigit(digit) {
|
|
/* Handles an input digit coming from the Control Console keyboard or
|
|
paper-tape reader. Negative values indicate a finish pulse; otherwise
|
|
the digit is data read from the device. Data digits are rotated into
|
|
the D register; finish pulses are handled according to the sign digit
|
|
in the D register */
|
|
var sign; // register sign digit
|
|
var word; // register word less sign
|
|
|
|
this.procTime += performance.now()*B220Processor.wordsPerMilli; // restore time after I/O
|
|
if (digit >= 0) {
|
|
this.togTC1 = 1-this.togTC1; // for display only
|
|
this.togTC2 = 1-this.togTC2; // for display only
|
|
this.D = (this.D % 0x10000000000)*0x10 + digit;
|
|
this.consoleInputDigit();
|
|
} else {
|
|
this.togTF = 1;
|
|
this.togTC1 = this.togTC2 = 0; // for display only
|
|
word = this.D%0x10000000000;
|
|
sign = (this.D - word)/0x10000000000; // get D-sign
|
|
|
|
if (sign & 0x04) {
|
|
// D-sign is 4, 5, 6, 7: execute a "tape control" command
|
|
this.procTime += 2;
|
|
this.togTF = 0; // for display only
|
|
this.togSTART = 1-((sign >>> 1) & 0x01); // whether to continue in input mode
|
|
this.setTimingToggle(0); // Execute mode
|
|
this.togCOUNT = 1;
|
|
this.togBTOAIN = 0;
|
|
this.togADDAB = 1; // for display only
|
|
this.togADDER = 1; // for display only
|
|
this.togDPCTR = 1; // for display only
|
|
this.togCLEAR = 1-(sign & 0x01);
|
|
this.togSIGN = ((this.A - this.A%0x10000000000)/0x10000000000) & 0x01; // display only
|
|
sign &= 0x08;
|
|
|
|
// Increment the destination address (except on the first word)
|
|
this.SHIFTCONTROL = 0x01; // for display only
|
|
this.SHIFT = 0x13; // for display only
|
|
if (this.togCOUNT) {
|
|
this.CCONTROL = this.bcdAdd(this.CADDR, 1)%0x10000;
|
|
}
|
|
|
|
this.CEXTRA = (this.D - word%0x1000000)/0x1000000;
|
|
this.kDigit = (this.CEXTRA >>> 8) & 0x0F;
|
|
// do not set this.selectedUnit from the word -- keep the same unit
|
|
|
|
// Shift D5-D10 into C1-C6, modify by B as necessary, and execute
|
|
this.D = sign*0x100000 + (word - word%0x1000000)/0x1000000;
|
|
if (this.togCLEAR) {
|
|
word = this.bcdAdd(word%0x1000000, 0);
|
|
} else {
|
|
word = this.bcdAdd(word%0x1000000, this.B) % 0x1000000;
|
|
}
|
|
this.SHIFT = 0x19; // for display only
|
|
this.C = word*0x10000 + this.CCONTROL; // put C back together
|
|
this.CADDR = word % 0x10000;
|
|
this.COP = (word - this.CADDR)/0x10000;
|
|
this.execute();
|
|
} else {
|
|
// D-sign is 0, 1, 2, 3: store word, possibly modified by B
|
|
this.procTime += 3;
|
|
this.setTimingToggle(1); // Fetch mode
|
|
this.togCOUNT = this.togBTOAIN;
|
|
this.togBTOAIN = 1;
|
|
this.togADDAB = 1; // for display only
|
|
this.togADDER = 1; // for display only
|
|
this.togDPCTR = 1; // for display only
|
|
this.togCLEAR = 1-((sign >>> 1) & 0x01);
|
|
this.togSIGN = sign & 0x01;
|
|
|
|
// Increment the destination address (except on the first word)
|
|
this.SHIFTCONTROL = 0x01; // for display only
|
|
this.SHIFT = 0x15; // for display only
|
|
if (this.togCOUNT) {
|
|
this.CADDR = this.bcdAdd(this.CADDR, 1)%0x10000;
|
|
}
|
|
this.CCONTROL = this.CADDR;
|
|
this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
|
|
|
|
// Modify the word by B as necessary and store it
|
|
this.D = (sign & 0x0C)*0x10000000000 + word;
|
|
if (this.togCLEAR) {
|
|
this.A = this.bcdAdd(this.D, 0);
|
|
} else {
|
|
this.A = this.bcdAdd(this.D, this.B);
|
|
}
|
|
|
|
this.D = 0;
|
|
word = this.A % 0x10000000000;
|
|
sign = (((this.A - word)/0x10000000000) & 0x0E) | (sign & 0x01);
|
|
this.A = sign*0x10000000000 + word;
|
|
this.writeMemory(this.boundConsoleInputDigit, false);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.consoleReceiveSingleDigit = function consoleReceiveSingleDigit(digit) {
|
|
/* Handles a single input digit coming from the Control Console keyboard
|
|
or paper-tape reader, as in the case of Digit Add (10). Negative values
|
|
indicate a finish pulse, which is ignored, and causes another digit to be
|
|
solicited from the Console; otherwise the digit is (virtually) moved to
|
|
the D register and then (actually) added to the A register */
|
|
var sign; // register sign digit
|
|
var word; // register word less sign
|
|
|
|
if (digit < 0) { // ignore finish pulse and just re-solicit
|
|
this.console.readDigit(this.boundConsoleReceiveSingleDigit);
|
|
} else {
|
|
this.procTime += performance.now()*B220Processor.wordsPerMilli + 4; // restore time after I/O
|
|
this.togSTART = 0;
|
|
this.D = digit;
|
|
this.integerAdd();
|
|
this.schedule();
|
|
}
|
|
};
|
|
|
|
/***********************************************************************
|
|
* Cardatron I/O Module *
|
|
***********************************************************************/
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.cardatronOutputWordReady = function cardatronOutputWordReady() {
|
|
/* Successor function for readMemory that sets up the next word of output
|
|
and calls the current ioCallback function to output that word */
|
|
|
|
if (this.tog3IO) { // if false, we've probably been cleared
|
|
this.SHIFT = 0x09; // for display only
|
|
this.A = this.D; // move D with the memory word to A
|
|
this.togTWA = 1; // for display only
|
|
this.procTime -= performance.now()*B220Processor.wordsPerMilli;
|
|
this.ioCallback(this.A, this.boundCardatronOutputWord, this.boundCardatronOutputFinished);
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.cardatronOutputWord = function cardatronOutputWord(receiver) {
|
|
/* Initiates a read of the next word from memory for output to the
|
|
Cardatron Control Unit */
|
|
|
|
this.procTime += performance.now()*B220Processor.wordsPerMilli;
|
|
if (this.tog3IO) { // if false, we've probably been cleared
|
|
// Increment the source address (except on the first word)
|
|
this.SHIFTCONTROL = 0x01; // for display only
|
|
this.SHIFT = 0x15; // for display only
|
|
if (this.togCOUNT) {
|
|
this.CADDR = this.bcdAdd(this.CADDR, 1)%0x10000;
|
|
} else {
|
|
this.togCOUNT = 1;
|
|
}
|
|
this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
|
|
this.ioCallback = receiver;
|
|
this.readMemory(this.boundCardatronOutputWordReady);
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.cardatronOutputFinished = function cardatronOutputFinished() {
|
|
/* Handles the final cycle of an I/O operation and restores this.procTime */
|
|
|
|
if (this.tog3IO) { // if false, we've probably been cleared
|
|
this.tog3IO = 0;
|
|
this.togTWA = 0; // for display only
|
|
this.procTime += performance.now()*B220Processor.wordsPerMilli;
|
|
this.schedule();
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.cardatronInputWord = function cardatronInputWord() {
|
|
// Solicits the next input word from the Cardatron Control Unit */
|
|
|
|
this.togTF = 0; // for display only, reset finish pulse
|
|
this.togTWA = 1; // for display only
|
|
this.procTime -= performance.now()*B220Processor.wordsPerMilli; // mark time during I/O
|
|
this.cardatron.inputWord(this.selectedUnit, this.boundCardatronReceiveWord);
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.cardatronReceiveWord = function cardatronReceiveWord(word) {
|
|
/* Handles a word coming from the Cardatron input unit. Negative values for
|
|
the word indicate the last word was previously sent and the I/O is finished.
|
|
The word is stored into the D register and is handled according to the sign
|
|
digit in the D register. Any partial word received at the end of the
|
|
I/O is abandoned */
|
|
var sign; // D-register sign digit
|
|
|
|
this.procTime += performance.now()*B220Processor.wordsPerMilli; // restore time after I/O
|
|
if (word < 0) {
|
|
// Last word received -- finished with the I/O
|
|
this.tog3IO = 0;
|
|
this.togSTART = 0;
|
|
this.togTF = 0; // for display only
|
|
this.togTWA = 0; // for display only
|
|
this.D = word-900000000000; // remove the finished signal; for display only, not stored
|
|
this.schedule();
|
|
} else {
|
|
// Full word accumulated -- process it and initialize for the next word
|
|
this.SHIFT = 0x19; // for display only
|
|
this.togTF = 1; // for display only
|
|
this.D = word;
|
|
word %= 0x10000000000; // strip the sign digit
|
|
sign = (this.D - word)/0x10000000000; // get D-sign
|
|
|
|
if (sign & 0x04) {
|
|
// D-sign is 4, 5, 6, 7: execute the word as an instruction
|
|
this.procTime += 2;
|
|
this.togTF = 0; // for display only
|
|
this.togSTART = 1-((sign >>> 1) & 0x01); // whether to continue in input mode
|
|
this.setTimingToggle(0); // Execute mode
|
|
this.togCOUNT = 0;
|
|
this.togBTOAIN = 0;
|
|
this.togADDAB = 1; // for display only
|
|
this.togADDER = 1; // for display only
|
|
this.togDPCTR = 1; // for display only
|
|
this.togCLEAR = ((this.kDigit & 0x08) ? 1 : 1-(sign & 0x01));
|
|
this.togSIGN = ((this.A - this.A%0x10000000000)/0x10000000000) & 0x01; // display only
|
|
|
|
this.CEXTRA = (this.D - word%0x1000000)/0x1000000;
|
|
this.kDigit = (this.CEXTRA >>> 8) & 0x0F;
|
|
// do not set this.selectedUnit from the word -- keep the same unit
|
|
|
|
// Shift D5-D10 into C1-C6, modify by B as necessary, and execute
|
|
if (this.togCLEAR) {
|
|
word = this.bcdAdd(word%0x1000000, 0);
|
|
} else {
|
|
word = this.bcdAdd(word%0x1000000, this.B) % 0x1000000;
|
|
}
|
|
this.C = word*0x10000 + this.CCONTROL; // put C back together
|
|
this.CADDR = word % 0x10000;
|
|
this.COP = (word - this.CADDR)/0x10000;
|
|
if (sign & 0x02) { // sign-6 or -7
|
|
this.tog3IO = 0;
|
|
this.togTF = 0; // for display only
|
|
this.cardatron.inputStop(this.selectedUnit);
|
|
this.execute();
|
|
} else { // sign-4 or -5
|
|
/* It's not exactly clear what should happen at this point. The
|
|
documentation states that a sign-4 or -5 word coming from a Cardatron
|
|
input unit can only contain a CDR (44) instruction, which is sensible,
|
|
since sign-4/5 words are generally used to change the destination memory
|
|
address for the data transfer, and the Cardatron presumably still had
|
|
words to transfer. What it doesn't say is what happened if the sign-4/5
|
|
word contained something else. My guess is that either the Processor
|
|
ignored any other op code and proceeded as if it had been a CDR, or more
|
|
likely, things went to hell in a handbasket. The latter is a little
|
|
difficult to emulate, especially since we don't know which hell or
|
|
handbasket might be involved, so we'll assume the former, and just
|
|
continue requesting words from the Cardatron */
|
|
this.SHIFT = 0x09; // reset shift counter for next word
|
|
this.D = 0; // clear D to prepare for next word
|
|
this.cardatronInputWord(); // request the next word
|
|
}
|
|
} else {
|
|
// D-sign is 0, 1, 2, 3, 8, 9: store word, possibly modified by B
|
|
this.procTime += 3;
|
|
this.setTimingToggle(1); // Fetch mode
|
|
this.togCOUNT = this.togBTOAIN;
|
|
this.togBTOAIN = 1;
|
|
this.togADDAB = 1; // for display only
|
|
this.togADDER = 1; // for display only
|
|
this.togDPCTR = 1; // for display only
|
|
this.togSIGN = sign & 0x01;
|
|
if (this.kDigit & 0x08) {
|
|
this.togCLEAR = 1;
|
|
} else {
|
|
this.togCLEAR = 1-((sign >>> 1) & 0x01);
|
|
sign &= 0x0D;
|
|
}
|
|
|
|
// Increment the destination address (except on the first word)
|
|
this.SHIFTCONTROL = 0x01; // for display only
|
|
this.SHIFT = 0x15; // for display only
|
|
if (this.togCOUNT) {
|
|
this.CADDR = this.bcdAdd(this.CADDR, 1)%0x10000;
|
|
}
|
|
this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
|
|
|
|
// Modify the word by B as necessary and store it
|
|
if (this.togCLEAR) {
|
|
word = this.bcdAdd(word, 0);
|
|
} else {
|
|
word = this.bcdAdd(word, this.B);
|
|
}
|
|
|
|
this.A = sign*0x10000000000 + word%0x10000000000;
|
|
this.SHIFT = 0x09; // reset shift counter for next word
|
|
this.D = 0; // clear D and request the next word after storing this one
|
|
this.writeMemory(this.boundCardatronInputWord, false);
|
|
}
|
|
}
|
|
};
|
|
|
|
/***********************************************************************
|
|
* Magnetic Tape I/O Module *
|
|
***********************************************************************/
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.magTapeInitiateSend = function magTapeInitiateSend(writeInitiate) {
|
|
/* Performs the initial memory block-to-loop operation after the tape control
|
|
unit has determined the drive is ready and not busy. Once the initial loop is
|
|
loaded, calls "writeInitiate" to start tape motion, which in turn will cause
|
|
the control to call this.magTapeSendBlock to pass the loop data to the drive
|
|
and initiate loading of the alternate loop buffer */
|
|
var that = this;
|
|
|
|
if (this.togMT3P) { // if false, we've probably been cleared
|
|
if (this.CADDR >= 0x8000) {
|
|
writeInitiate(this.boundMagTapeSendBlock, this.boundMagTapeTerminateSend);
|
|
} else {
|
|
this.procTime += performance.now()*B220Processor.wordsPerMilli; // restore time after I/O
|
|
this.blockToLoop((this.togMT1BV4 ? 4 : 5), function initialBlockComplete() {
|
|
that.procTime -= performance.now()*B220Processor.wordsPerMilli; // suspend time during I/O
|
|
writeInitiate(that.boundMagTapeSendBlock, that.boundMagTapeTerminateSend);
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.magTapeSendBlock = function magTapeSendBlock(lastBlock) {
|
|
/* Sends a block of data from a loop buffer to the tape control unit and
|
|
initiates the load of the alternate loop buffer. this.togMT1BV4 and
|
|
this.togMT1BV5 control alternation of the loop buffers. "lastBlock" indicates
|
|
this will be the last block requested by the control unit and no further
|
|
blocks should be buffered. If the C-register address is 8000 or higher, the
|
|
loop is not loaded from main memory, and the current contents of the loop
|
|
are written to tape. Since tape block writes take 46 ms, they are much
|
|
longer than any memory-to-loop transfer, so this routine simply exits after
|
|
the next blockToLoop is initiated, and the processor then waits for the tape
|
|
control unit to request the next block, by which time the blockToLoop will
|
|
have completed. Returns null if the processor has been cleared and the I/O
|
|
must be aborted */
|
|
var loop;
|
|
var that = this;
|
|
|
|
function blockFetchComplete() {
|
|
that.procTime -= performance.now()*B220Processor.wordsPerMilli; // suspend time again during I/O
|
|
}
|
|
|
|
//console.log("TSU " + this.selectedUnit + " W, L" + (this.togMT1BV4 ? 4 : 5) +
|
|
// ", ADDR=" + this.CADDR.toString(16) +
|
|
// " : " + block[0].toString(16) + ", " + block[19].toString(16));
|
|
|
|
if (!this.togMT3P) {
|
|
loop = null;
|
|
} else {
|
|
// Select the appropriate loop to send data to the drive
|
|
if (this.togMT1BV4) {
|
|
loop = this.L4;
|
|
this.toggleGlow.glowL4 = 1; // turn on the lamp and let normal decay work
|
|
} else {
|
|
loop = this.L5;
|
|
this.toggleGlow.glowL5 = 1;
|
|
}
|
|
|
|
if (!lastBlock) {
|
|
this.procTime += performance.now()*B220Processor.wordsPerMilli; // restore time after I/O
|
|
// Flip the loop-buffer toggles
|
|
this.togMT1BV5 = this.togMT1BV4;
|
|
this.togMT1BV4 = 1-this.togMT1BV4;
|
|
// Block the loop buffer from main memory if appropriate
|
|
if (this.CADDR < 0x8000) {
|
|
this.blockToLoop((this.togMT1BV4 ? 4 : 5), blockFetchComplete);
|
|
} else {
|
|
blockFetchComplete();
|
|
}
|
|
}
|
|
|
|
this.A = loop[loop.length-1]; // for display only
|
|
this.D = 0; // for display only
|
|
}
|
|
|
|
return loop; // give the loop data to the control unit
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.magTapeTerminateSend = function magTapeTerminateSend() {
|
|
/* Called by the tape control unit after the last block has been completely
|
|
written to tape. Terminates the write instruction */
|
|
|
|
if (this.togMT3P) { // if false, we've probably been cleared
|
|
this.togMT3P = 0;
|
|
this.togMT1BV4 = this.togMT1BV5 = 0;
|
|
this.procTime += performance.now()*B220Processor.wordsPerMilli; // restore time after I/O
|
|
this.schedule();
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.magTapeReceiveBlock = function magTapeReceiveBlock(block, lastBlock) {
|
|
/* Called by the tape control unit to store a block of 20 words. If "lastBlock" is
|
|
true, it indicates this is the last block and the I/O is finished. If "block"
|
|
is null, that indicates the I/O was aborted and the block must not be stored
|
|
in memory. The block is stored in one of the loops, as determined by the
|
|
togMT1BV4 and togMT1BV5 control toggles. Sign digit adjustment and B-register
|
|
modification take place at this time. If the C-register operand address is
|
|
less than 8000, the loop is then stored at the current operand address, which
|
|
is incremented by blockFromLoop(). If this is the last block, schedule()
|
|
is called after the loop is stored to terminate the read instruction. Since
|
|
tape block reads take 46 ms, they are much longer than any loop-to-memory
|
|
transfer, so this routine simply exits after the blockFromLoop is initiated,
|
|
and the then processor waits for the next block to arrive from the tape, by
|
|
which time the blockFromLoop will (should?) have completed. Returns true if
|
|
the processor has been cleared and the tape control unit should abort the I/O */
|
|
var aborted = false; // return value
|
|
var loop;
|
|
var sign; // sign digit
|
|
var that = this;
|
|
var w; // scratch word
|
|
var x; // scratch index
|
|
|
|
function blockStoreComplete() {
|
|
if (lastBlock) {
|
|
if (that.togMT3P) { // if false, we've probably been cleared
|
|
that.A = that.D = 0; // for display only
|
|
that.togMT3P = 0;
|
|
that.togMT1BV4 = that.togMT1BV5 = 0;
|
|
that.schedule();
|
|
}
|
|
} else {
|
|
// Flip the loop buffer toggles
|
|
that.togMT1BV5 = that.togMT1BV4;
|
|
that.togMT1BV4 = 1-that.togMT1BV4;
|
|
// Suspend time again during I/O
|
|
that.procTime -= performance.now()*B220Processor.wordsPerMilli;
|
|
}
|
|
}
|
|
|
|
//console.log("TSU " + this.selectedUnit + " R, L" + (this.togMT1BV4 ? 4 : 5) +
|
|
// ", ADDR=" + this.CADDR.toString(16) +
|
|
// " : " + block[0].toString(16) + ", " + block[19].toString(16));
|
|
|
|
if (!this.togMT3P) { // if false, we've probably been cleared
|
|
aborted = true;
|
|
} else {
|
|
this.procTime += performance.now()*B220Processor.wordsPerMilli; // restore time after I/O
|
|
// Select the appropriate loop to receive data from the drive
|
|
if (this.togMT1BV4) {
|
|
loop = this.L4;
|
|
this.toggleGlow.glowL4 = 1; // turn on the lamp and let normal decay work
|
|
} else {
|
|
loop = this.L5;
|
|
this.toggleGlow.glowL5 = 1;
|
|
}
|
|
|
|
if (!block) { // control unit aborted the I/O
|
|
blockStoreComplete();
|
|
} else {
|
|
// Copy the tape block data to the appropriate high-speed loop
|
|
for (x=0; x<loop.length; ++x) {
|
|
this.D = w = block[x]; // D for display only
|
|
if (w < 0x20000000000) {
|
|
this.togCLEAR = 1; // no B modification
|
|
} else {
|
|
// Adjust sign digit and do B modification as necessary
|
|
sign = ((w - w%0x10000000000)/0x10000000000) % 0x08; // low-order 3 bits only
|
|
if (this.tswSuppressB) {
|
|
this.togCLEAR = 1; // no B modification
|
|
} else {
|
|
this.togCLEAR = ((sign & 0x02) ? 0 : 1);
|
|
sign &= 0x01;
|
|
}
|
|
|
|
w = sign*0x10000000000 + w%0x10000000000;
|
|
}
|
|
|
|
if (this.togCLEAR) {
|
|
w = this.bcdAdd(w, 0);
|
|
} else {
|
|
w = this.bcdAdd(w, this.B);
|
|
}
|
|
|
|
loop[x] = w;
|
|
} // for x
|
|
|
|
this.A = w; // for display only
|
|
|
|
// Block the loop buffer to main memory if appropriate
|
|
if (this.CADDR < 0x8000) {
|
|
this.blockFromLoop((this.togMT1BV4 ? 4 : 5), blockStoreComplete);
|
|
} else {
|
|
blockStoreComplete();
|
|
}
|
|
}
|
|
}
|
|
|
|
return aborted;
|
|
};
|
|
|
|
|
|
/***********************************************************************
|
|
* Fetch Module *
|
|
***********************************************************************/
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.fetch = function fetch() {
|
|
/* Implements the Fetch cycle of the 220 processor. This is initiated either
|
|
by pressing START on one of the consoles with EXT=0 (Fetch), during I/O when
|
|
a control word (sign>3) is received from a peripheral device, or by the
|
|
prior Operation Complete if the processor is in continuous mode */
|
|
var dSign; // sign bit of IB register
|
|
var word; // instruction word
|
|
|
|
if (this.AST.value) { // if doing I/O
|
|
word = this.IB.value;
|
|
} else { // if doing normal fetch
|
|
this.E.set(this.P.value);
|
|
word = this.readMemory();
|
|
}
|
|
|
|
if (!this.MET.value) {
|
|
// if we're not locked in Fetch, switch to Execute phase next.
|
|
if (this.FETCHEXECUTELOCKSW != 1) {
|
|
this.EXT.set(1);
|
|
}
|
|
|
|
dSign = ((word - word%0x10000000000)/0x10000000000) & 0x01;
|
|
this.DST.set(dSign);
|
|
// (should set IB sign bit 1=0 here, but to reduce overhead we don't bother)
|
|
|
|
this.CADDR = word%0x10000; // C address
|
|
this.COP = (word%0x1000000 - this.CADDR)/0x10000; // C op code
|
|
this.CCONTROL = (word%10000000000 - word%1000000)/0x1000000; // C control digits
|
|
if (dSign) {
|
|
this.CADDR = this.bcdAdd(this.CADDR, this.B.value) % 0x10000;
|
|
this.C10.set(0); // reset carry toggle
|
|
this.C.set((this.CCONTROL*100 + this.COP)*10000 + this.CADDR);
|
|
} else {
|
|
this.C.set(word%0x10000000000);
|
|
}
|
|
|
|
this.D.set(word); // D contains a copy of memory word
|
|
if (!this.AST.value && !this.PCOUNTSW) {
|
|
this.P.add(0x01); // if not doing I/O, bump the program counter
|
|
}
|
|
|
|
this.realTime += 0.090; // fetch uniformly requires 90 us
|
|
}
|
|
};
|
|
|
|
|
|
/***********************************************************************
|
|
* Execute Module *
|
|
***********************************************************************/
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.executeWithOperand = function executeWithOperand() {
|
|
/* Executes an instruction that requires an operand, after that operand
|
|
has been read from memory into the D register */
|
|
|
|
switch (this.COP) {
|
|
case 0x60: //---------------- M Multiply
|
|
this.integerMultiply(false);
|
|
break;
|
|
|
|
case 0x61: //---------------- Div Divide
|
|
this.integerDivide();
|
|
break;
|
|
|
|
// 0x62: //---------------- (no op)
|
|
break;
|
|
|
|
case 0x63: //---------------- EX Extract
|
|
this.procTime += 3;
|
|
this.integerExtract();
|
|
break;
|
|
|
|
case 0x64: //---------------- CAD Clear and Add A
|
|
this.procTime += 3;
|
|
this.A = this.bcdAdd(0, this.D);
|
|
this.D = 0;
|
|
break;
|
|
|
|
case 0x65: //---------------- CSU Clear and Subtract A
|
|
this.procTime += 3;
|
|
// Complement the D sign -- any sign overflow will be ignored by integerAdd
|
|
this.A = this.bcdAdd(0, B220Processor.bitFlip(this.D, 40));
|
|
this.D = 0;
|
|
break;
|
|
|
|
case 0x66: //---------------- CADA Clear and Add Absolute
|
|
this.procTime += 3;
|
|
this.A = this.bcdAdd(0, B220Processor.bitReset(this.D, 40));
|
|
this.D = 0;
|
|
break;
|
|
|
|
case 0x67: //---------------- CSUA Clear and Subtract Absolute
|
|
this.procTime += 3;
|
|
this.A = this.bcdAdd(0, B220Processor.bitSet(this.D, 40));
|
|
this.D = 0;
|
|
break;
|
|
|
|
// 0x68-0x69: //---------------- (no op)
|
|
|
|
case 0x70: //---------------- MRO Multiply and Round
|
|
this.integerMultiply(true);
|
|
break;
|
|
|
|
case 0x71: //---------------- EXC External Control
|
|
this.setExternalSwitches();
|
|
break;
|
|
|
|
case 0x72: //---------------- SB Set B
|
|
this.procTime += 3;
|
|
this.SHIFT = 0x15; // for display only
|
|
this.SHIFTCONTROL = 0; // for display only
|
|
this.togADDAB = 1; // for display only
|
|
this.togCLEAR = 1; // for display only
|
|
this.DELTABDIVT = 1; // for display only
|
|
this.B = this.bcdAdd(0, this.D % 0x10000);
|
|
this.togSIGN = ((this.A - this.A%0x10000000000)/0x10000000000) & 0x01; // for display only
|
|
this.D = 0;
|
|
break;
|
|
|
|
case 0x73: //---------------- OSGD Overflow on Sign Difference
|
|
this.procTime += 2;
|
|
this.togSIGN = ((this.A - this.A%0x10000000000)/0x10000000000) & 0x01; // for display, mostly
|
|
this.setOverflow(this.togSIGN ^
|
|
(((this.D - this.D%0x10000000000)/0x10000000000) & 0x01));
|
|
this.D = 0;
|
|
break;
|
|
|
|
case 0x74: //---------------- AD Add
|
|
this.procTime += 3;
|
|
this.integerAdd();
|
|
break;
|
|
|
|
case 0x75: //---------------- SU Subtract
|
|
this.procTime += 3;
|
|
this.D = B220Processor.bitFlip(this.D, 40); // complement the D sign
|
|
this.integerAdd();
|
|
break;
|
|
|
|
case 0x76: //---------------- ADA Add Absolute
|
|
this.procTime += 3;
|
|
this.D = B220Processor.bitReset(this.D, 40); // clear the D sign
|
|
this.integerAdd();
|
|
break;
|
|
|
|
case 0x77: //---------------- SUA Subtract Absolute
|
|
this.procTime += 3;
|
|
this.D = B220Processor.bitSet(this.D, 40); // set the D sign
|
|
this.integerAdd();
|
|
break;
|
|
|
|
// 0x78-0x79: //---------------- (no op)
|
|
|
|
case 0x80: //---------------- FAD Floating Add
|
|
this.procTime += 4;
|
|
this.floatingAdd();
|
|
break;
|
|
|
|
case 0x81: //---------------- FSU Floating Subtract
|
|
this.procTime += 4;
|
|
// Complement the D sign -- any sign overflow will be ignored by floatingAdd.
|
|
this.D += 0x10000000000;
|
|
this.floatingAdd();
|
|
break;
|
|
|
|
case 0x82: //---------------- FM Floating Multiply
|
|
this.procTime += 3;
|
|
this.floatingMultiply();
|
|
break;
|
|
|
|
case 0x83: //---------------- FDIV Floating Divide
|
|
this.procTime += 3;
|
|
this.floatingDivide();
|
|
break;
|
|
|
|
// 0x84: //---------------- (no op)
|
|
|
|
case 0x85: //---------------- MSH Memory Search High (Eaton CER)
|
|
this.searchMemory(true);
|
|
return; // avoid the schedule()
|
|
break;
|
|
|
|
// 0x86: //---------------- (no op)
|
|
|
|
case 0x87: //---------------- MSE Memory Search Equal (Eaton CER)
|
|
this.searchMemory(false);
|
|
return; // avoid the schedule()
|
|
break;
|
|
|
|
// 0x88-0x89: //---------------- (no op)
|
|
|
|
case 0x90: //---------------- FAA Floating Add Absolute
|
|
this.procTime += 4;
|
|
this.D %= 0x10000000000; // clear the D-sign digit
|
|
this.floatingAdd();
|
|
break;
|
|
|
|
case 0x91: //---------------- FSA Floating Subtract Absolute
|
|
this.procTime += 4;
|
|
this.D = this.D%0x10000000000 + 0x10000000000; // set the D-sign
|
|
this.floatingAdd();
|
|
break;
|
|
|
|
case 0x92: //---------------- FMA Floating Multiply Absolute
|
|
this.procTime += 3;
|
|
this.D %= 0x10000000000; // clear the D-sign digit
|
|
this.floatingMultiply();
|
|
break;
|
|
|
|
case 0x93: //---------------- FDA Floating Divide Absolute
|
|
this.procTime += 3;
|
|
this.D %= 0x10000000000; // clear the D-sign digit
|
|
this.floatingDivide();
|
|
break;
|
|
|
|
// 0x94-0x99: //---------------- (no op)
|
|
|
|
} // switch this.COP
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.execute = function execute() {
|
|
/* Implements the Execute cycle of the 220 processor. This is initiated either
|
|
by pressing START on one of the consoles with the EXT=1 (Execute),
|
|
or by the prior operation complete if the processor is in continuous mode */
|
|
var d; // scratch digit
|
|
var w; // scratch word
|
|
var x; // scratch variable or counter
|
|
|
|
w = this.C;
|
|
this.CCONTROL = (w - w%0x1000000)/0x1000000; // C register control digits
|
|
this.COP = (w%0x1000000 - w%0x10000)/0x10000; // C register operation code
|
|
this.CADDR = w%0x10000; // C register operand address
|
|
|
|
// If we're not locked in Execute, switch to Fetch phase next.
|
|
if (this.FETCHEXECUTELOCKSW != 1) {
|
|
this.EXT.set(0);
|
|
}
|
|
|
|
if (this.OFT.value && this.HCT.value && this.COP != 0x31) {
|
|
this.stop(); // if overflow and SOH and instruction is not BOF, stop
|
|
return;
|
|
}
|
|
|
|
if (B220Processor.operandRequired[this.COP]) {
|
|
this.E.set(this.CADDR);
|
|
this.readMemory();
|
|
if (this.MET.value) { // invalid address
|
|
this.setStorageCheck(1);
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (this.COP) {
|
|
case 0x00: //--------------------- HLT Halt
|
|
this.RUT.set(0);
|
|
this.realTime += 0.100;
|
|
break;
|
|
|
|
case 0x01: //--------------------- NOP No operation
|
|
// do nothing
|
|
this.realTime += 0.100;
|
|
break;
|
|
|
|
case 0x03: //--------------------- PRD Paper tape read
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x04: //--------------------- PRB Paper tape read, branch
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x05: //--------------------- PRI Paper tape read, inverse format
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x06: //--------------------- PWR Paper tape write
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x08: //--------------------- KAD Keyboard add
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x09: //--------------------- SPO Supervisory print-out
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x10: //--------------------- CAD/CAA Clear add/add absolute
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x11: //--------------------- CSU/CSA Clear subtract/subtract absolute
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x12: //--------------------- ADD/ADA Add/add absolute
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x13: //--------------------- SUB/SUA Subtract/subtract absolute
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x14: //--------------------- MUL Multiply
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x15: //--------------------- DIV Divide
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x16: //--------------------- RND Round
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x17: //--------------------- EXT Extract
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x18: //--------------------- CFA/CFR Compare field A/R
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x19: //--------------------- ADL Add to location
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x20: //--------------------- IBB Increase B, branch
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x21: //--------------------- DBB Decrease B, branch
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x22: //--------------------- FAD/FAA Floating add/add absolute
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x23: //--------------------- FSU/FSA Floating subtract/subtract absolute
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x24: //--------------------- FMU Floating multiply
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x25: //--------------------- FDV Floating divide
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x26: //--------------------- IFL Increase field location
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x27: //--------------------- DFL Decrease field location
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x28: //--------------------- DLB Decrease field location, load B
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x29: //--------------------- RTF Record transfer
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x30: //--------------------- BUN Branch, unconditionally
|
|
this.P.set(this.CADDR);
|
|
break;
|
|
|
|
case 0x31: //--------------------- BOF Branch, overflow
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x32: //--------------------- BRP Branch, repeat
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x33: //--------------------- BSA Branch, sign A
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x34: //--------------------- BCH/BCL Branch, comparison high/low
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x35: //--------------------- BCE/BCU Branch, comparison equal/unequal
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x36: //--------------------- BFA Branch, field A
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x37: //--------------------- BFR Branch, field R
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x38: //--------------------- BCS Branch, control switch
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x39: //--------------------- SO*/IOM Set overflow remember/halt, Interrogate overflow mode
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x40: //--------------------- ST* Store A/R/B
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x41: //--------------------- LDR Load R
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x42: //--------------------- LDB/LBC Load B/B complement
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x43: //--------------------- LSA Load sign A
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x44: //--------------------- STP Store P
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x45: //--------------------- CL* Clear A/R/B
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x46: //--------------------- CLL Clear location
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x48: //--------------------- SR* Shift right A/A and R/A with sign
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x49: //--------------------- SL* Shift left A/A and R/A with sign
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x50: //--------------------- MT* Magnetic tape search/field search/lane select/rewind
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x51: //--------------------- MTC/MFC Magnetic tape scan/field scan
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x52: //--------------------- MRD Magnetic tape read
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x53: //--------------------- MRR Magnetic tape read, record
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x54: //--------------------- MIW Magnetic tape initial write
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x55: //--------------------- MIR Magnetic tape initial write, record
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x56: //--------------------- MOW Magnetic tape overwrite
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x57: //--------------------- MOR Magnetic tape overwrite, record
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x58: //--------------------- MPF/MPB Magnetic tape position forward/backward/at end
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x59: //--------------------- MIB/MIE Magnetic tape interrogate, branch/end of tape, branch
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x60: //--------------------- CRD Card read
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x61: //--------------------- CWR Card write
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x62: //--------------------- CRF Card read, format load
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x63: //--------------------- CWF Card write, format load
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x64: //--------------------- CRI Card read interrogate, branch
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x65: //--------------------- CWI Card write interrogate, branch
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x66: //--------------------- HPW High speed printer write
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
case 0x67: //--------------------- HPI High speed printer interrogate, branch
|
|
this.setProgramCheck(1);
|
|
break;
|
|
|
|
default: //--------------------- Invalid op code -- set Program Check alarm
|
|
this.setProgramCheck(1);
|
|
break;
|
|
} // switch this.COP
|
|
|
|
return; ///////////////// for now //////////////////
|
|
|
|
|
|
switch (-1) {
|
|
case 0x00: //---------------- PTR Paper-tape/keyboard read
|
|
this.D = 0;
|
|
this.togSTART = 1;
|
|
this.consoleInputDigit();
|
|
break;
|
|
|
|
case 0x01: //---------------- CIRA Circulate A
|
|
x = B220Processor.bcdBinary(this.CADDR % 0x20);
|
|
this.procTime += x+8;
|
|
x = 19-x;
|
|
this.SHIFT = B220Processor.binaryBCD(x); // for display only
|
|
this.togDELAY = 1; // for display only
|
|
w = this.A;
|
|
for (; x<=19; ++x) {
|
|
d = (w - w%0x10000000000)/0x10000000000;
|
|
w = (w%0x10000000000)*0x10 + d;
|
|
}
|
|
this.A = w;
|
|
break;
|
|
|
|
case 0x02: //---------------- STC Store and Clear A
|
|
this.procTime += 1;
|
|
break;
|
|
|
|
case 0x03: //---------------- PTW Paper-tape/Flexowriter write
|
|
if (this.cswPOSuppress) {
|
|
//this.schedule(); // ignore printout commands
|
|
} else if (this.cswOutput == 0) {
|
|
this.togCST = 1; // halt if Output switch is OFF
|
|
//this.schedule();
|
|
} else {
|
|
this.togPO1 = 1; // for display only
|
|
this.SHIFT = this.bcdAdd(this.CADDR%0x20, 0x19, 1, 1); // 19-n
|
|
this.procTime -= performance.now()*B220Processor.wordsPerMilli; // mark time during I/O
|
|
this.stopIdle = 1; // turn IDLE lamp on in case Output Knob is OFF
|
|
d = (this.CADDR%0x1000 - this.CADDR%0x100)/0x100;
|
|
if (d) { // if C8 is not zero, output it first as the format digit
|
|
this.togOK = this.togDELAY = 1; // for display only
|
|
this.console.writeFormatDigit(d, this.boundConsoleOutputSignDigit);
|
|
} else {
|
|
this.consoleOutputSignDigit();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x04: //---------------- CNZ Change on Non-Zero
|
|
this.procTime += 3;
|
|
this.togZCT = 1; // for display only
|
|
this.D = 0;
|
|
this.integerAdd(); // clears the sign digit, among other things
|
|
if (this.A) {
|
|
this.setTimingToggle(0); // stay in Execute
|
|
this.setOverflow(1); // set overflow
|
|
this.COP = 0x28; // make into a CC
|
|
this.C = (this.CCONTROL*0x100 + this.COP)*0x10000 + this.CADDR;
|
|
}
|
|
break;
|
|
|
|
// 0x05: //---------------- (no op)
|
|
|
|
case 0x06: //---------------- UA Unit Adjust
|
|
this.procTime += 2;
|
|
if (this.A % 2 == 0) {
|
|
++this.A;
|
|
}
|
|
break;
|
|
|
|
case 0x07: //---------------- PTWF Paper-tape Write Format
|
|
if (this.cswPOSuppress) {
|
|
//this.schedule(); // ignore printout commands
|
|
} else if (this.cswOutput == 0) {
|
|
this.togCST = 1; // halt if Output switch is OFF
|
|
//this.schedule();
|
|
} else {
|
|
this.procTime -= performance.now()*B220Processor.wordsPerMilli; // mark time during I/O
|
|
this.stopIdle = 1; // turn IDLE lamp on in case Output Knob is OFF
|
|
d = (this.CADDR%0x1000 - this.CADDR%0x100)/0x100;
|
|
if (d) { // if C8 is not zero, output it as the format digit
|
|
this.togOK = this.togDELAY = 1;
|
|
this.console.writeFormatDigit(d, this.boundConsoleOutputFinished);
|
|
} else {
|
|
this.consoleOutputFinished();
|
|
}
|
|
}
|
|
break;
|
|
|
|
// 0x08: //---------------- HALT [was handled above]
|
|
|
|
// 0x09: //---------------- (no op)
|
|
|
|
case 0x10: //---------------- DAD Digit Add from keyboard to A
|
|
this.togSTART = 1;
|
|
this.procTime -= performance.now()*B220Processor.wordsPerMilli; // mark time during I/O
|
|
this.console.readDigit(this.boundConsoleReceiveSingleDigit);
|
|
break;
|
|
|
|
case 0x11: //---------------- BA B to A transfer
|
|
this.procTime += 3;
|
|
this.togBTOAIN = 1; // for display only
|
|
this.togADDAB = 1; // for display only
|
|
this.togDELTABDIV = 1; // for display only
|
|
this.SHIFT = 0x15; // for display only
|
|
this.A = this.bcdAdd(0, this.B);
|
|
//this.schedule();
|
|
break;
|
|
|
|
case 0x12: //---------------- ST Store A
|
|
this.procTime += 1;
|
|
this.writeMemory(this.schedule, false);
|
|
break;
|
|
|
|
case 0x13: //---------------- SR Shift Right A & R
|
|
x = B220Processor.bcdBinary(this.CADDR % 0x20);
|
|
this.procTime += (x < 12 ? 2 : 3);
|
|
x = 19-x;
|
|
this.SHIFT = B220Processor.binaryBCD(x); // for display only
|
|
this.SHIFTCONTROL = 0x04; // for display only
|
|
w = this.A % 0x10000000000; // A sign is not affected
|
|
for (; x<19; ++x) {
|
|
d = w % 0x10;
|
|
w = (w-d)/0x10;
|
|
this.R = (this.R - this.R % 0x10)/0x10 + d*0x1000000000;
|
|
}
|
|
this.A += w - this.A%0x10000000000; // restore the sign
|
|
break;
|
|
|
|
case 0x14: //---------------- SL Shift Left A & R
|
|
x = B220Processor.bcdBinary(this.CADDR % 0x20);
|
|
this.procTime += (x < 9 ? 3 : 2);
|
|
this.SHIFT = B220Processor.binaryBCD(x); // for display only
|
|
this.SHIFTCONTROL = 0x06; // for display only
|
|
w = this.A % 0x10000000000; // A sign is not affected
|
|
for (; x<=19; ++x) {
|
|
d = this.R % 0x10;
|
|
this.R = (this.R - d)/0x10;
|
|
d = (w += d*0x10000000000)%0x10;
|
|
w = (w-d)/0x10;
|
|
this.R += d*0x1000000000;
|
|
}
|
|
this.A += w - this.A%0x10000000000; // restore the sign
|
|
break;
|
|
|
|
case 0x15: //---------------- NOR Normalize A & R
|
|
this.togZCT = 1; // for display only
|
|
this.SHIFTCONTROL = 0x02; // for display only
|
|
w = this.A % 0x10000000000; // A sign is not affected
|
|
x = 0;
|
|
do {
|
|
d = (w - w%0x1000000000)/0x1000000000;
|
|
if (d) {
|
|
break; // out of do loop
|
|
} else {
|
|
++x; // count the shifts
|
|
d = this.R % 0x1000000000; // shift A and R left
|
|
w = w*0x10 + (this.R - d)/0x1000000000;
|
|
this.R = d*0x10;
|
|
}
|
|
} while (x < 10);
|
|
this.A += w - this.A%0x10000000000; // restore the sign
|
|
this.procTime += (x+1)*2;
|
|
|
|
this.SPECIAL = x; // the result
|
|
this.SHIFTCONTROL |= 0x04; // for display only
|
|
this.SHIFT = 0x19; // for display only
|
|
if (x < 10) {
|
|
} else {
|
|
this.setTimingToggle(0); // stay in Execute
|
|
this.setOverflow(1); // set overflow
|
|
this.COP = 0x28; // make into a CC
|
|
this.C = (this.CCONTROL*0x100 + this.COP)*0x10000 + this.CADDR;
|
|
}
|
|
break;
|
|
|
|
case 0x16: //---------------- ADSC Add Special Counter to A
|
|
this.procTime += 3;
|
|
this.D = this.SPECIAL;
|
|
this.integerAdd();
|
|
break;
|
|
|
|
case 0x17: //---------------- SUSC Subtract Special Counter from A
|
|
this.procTime += 3;
|
|
this.D = this.SPECIAL + 0x10000000000; // set to negative
|
|
this.integerAdd();
|
|
break;
|
|
|
|
// 0x18-0x19: //---------------- (no op)
|
|
|
|
case 0x20: //---------------- CU Change Unconditionally
|
|
this.procTime += 2;
|
|
this.SHIFT = 0x15; // for display only
|
|
this.SHIFTCONTROL = 0x07; // for display only
|
|
this.P = this.CADDR; // copy address to control counter
|
|
break;
|
|
|
|
case 0x21: //---------------- CUR Change Unconditionally, Record
|
|
this.procTime += 2;
|
|
this.SHIFT = 0x15; // for display only
|
|
this.SHIFTCONTROL = 0x07; // for display only
|
|
this.R = this.CCONTROL*0x1000000; // save current control counter
|
|
this.CCONTROL = this.CADDR; // copy address to control counter
|
|
this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
|
|
break;
|
|
|
|
case 0x22: //---------------- DB Decrease B and Change on Negative
|
|
this.procTime += 3;
|
|
this.togADDAB = 1; // for display only
|
|
this.togDELTABDIV = 1; // for display only
|
|
this.togZCT = 1; // for display only
|
|
this.SHIFT = 0x15; // for display only
|
|
if (this.B == 0) {
|
|
this.B = 0x9999;
|
|
} else {
|
|
this.B = this.bcdAdd(this.B, 0x9999) % 0x10000; // add -1
|
|
this.setTimingToggle(0); // stay in Execute
|
|
this.setOverflow(1); // set overflow
|
|
this.COP = 0x28; // make into a CC
|
|
this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
|
|
}
|
|
this.togSIGN = ((this.A - this.A%0x10000000000)/0x10000000000) & 0x01; // display only
|
|
this.D = 0;
|
|
break;
|
|
|
|
case 0x23: //---------------- RO Round A, Clear R
|
|
this.procTime += 3;
|
|
this.togSIGN = ((this.A - this.A%0x10000000000)/0x10000000000) & 0x01;
|
|
// Add round-off (as the carry bit) to absolute value of A.
|
|
this.A = this.bcdAdd(this.A%0x10000000000, 0, 0, (this.R < 0x5000000000 ? 0 : 1));
|
|
if (this.A >= 0x10000000000) {
|
|
this.setOverflow(1); // overflow occurred
|
|
this.A -= 0x10000000000; // remove the overflow bit
|
|
}
|
|
this.A += this.togSIGN*0x10000000000; // restore the sign bit in A
|
|
this.D = this.R = 0; // clear D & R
|
|
break;
|
|
|
|
case 0x24: //---------------- BF4 Block from 4000 Loop
|
|
case 0x25: //---------------- BF5 Block from 5000 loop
|
|
case 0x26: //---------------- BF6 Block from 6000 loop
|
|
case 0x27: //---------------- BF7 Block from 7000 loop
|
|
this.procTime +=2;
|
|
this.blockFromLoop(this.COP-0x20, this.schedule);
|
|
break;
|
|
|
|
case 0x28: //---------------- CC Change Conditionally
|
|
if (!this.stopOverflow) { // check if branch should occur
|
|
this.procTime += 1;
|
|
this.SHIFTCONTROL = 0x04; // no -- set for display only
|
|
} else {
|
|
this.procTime += 2;
|
|
this.setOverflow(0); // reset overflow and do the branch
|
|
this.SHIFT = 0x15; // for display only
|
|
this.SHIFTCONTROL = 0x07; // for display only
|
|
this.CCONTROL = this.CADDR; // copy address to control counter
|
|
this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
|
|
}
|
|
break;
|
|
|
|
case 0x29: //---------------- CCR Change Conditionally, Record
|
|
if (!this.stopOverflow) { // check if branch should occur
|
|
this.procTime += 1;
|
|
this.SHIFTCONTROL = 0x04; // for display only
|
|
} else {
|
|
this.procTime += 2;
|
|
this.setOverflow(0); // reset overflow and do the branch
|
|
this.SHIFT = 0x15; // for display only
|
|
this.SHIFTCONTROL = 0x07; // for display only
|
|
this.R = this.CCONTROL*0x1000000; // save current control counter
|
|
this.CCONTROL = this.CADDR; // copy address to control counter
|
|
this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
|
|
}
|
|
break;
|
|
|
|
case 0x30: //---------------- CUB Change Unconditionally, Block to 7000 Loop
|
|
this.procTime += 4;
|
|
this.SHIFT = 0.15; // for display only
|
|
this.SHIFTCONTROL = 0x0F; // for display only
|
|
this.CCONTROL = this.CADDR%0x100 + 0x7000; // set control to loop-7 address
|
|
this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
|
|
this.blockToLoop(7, this.schedule);
|
|
break;
|
|
|
|
case 0x31: //---------------- CUBR Change Unconditionally, Block to 7000 Loop, Record
|
|
this.procTime += 4;
|
|
this.SHIFT = 0.15; // for display only
|
|
this.SHIFTCONTROL = 0x0F; // for display only
|
|
this.R = this.CCONTROL*0x1000000; // save current control counter
|
|
this.CCONTROL = this.CADDR%0x100 + 0x7000; // set control to loop-7 address
|
|
this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
|
|
this.blockToLoop(7, this.schedule);
|
|
break;
|
|
|
|
case 0x32: //---------------- IB Increase B
|
|
this.procTime += 3;
|
|
this.togADDAB = 1; // for display only
|
|
this.togDELTABDIV = 1; // for display only
|
|
this.togZCT = 1; // for display only
|
|
this.SHIFT = 0x15; // for display only
|
|
this.B = this.bcdAdd(this.B, 1) % 0x10000; // discard any overflow in B
|
|
this.togSIGN = ((this.A - this.A%0x10000000000)/0x10000000000) & 0x01; // display only
|
|
this.D = 0;
|
|
break;
|
|
|
|
case 0x33: //---------------- CR Clear R
|
|
this.procTime += 2;
|
|
this.SHIFTCONTROL = 0x04; // for display only
|
|
this.R = 0;
|
|
break;
|
|
|
|
case 0x34: //---------------- BT4 Block to 4000 Loop
|
|
case 0x35: //---------------- BT5 Block to 5000 Loop
|
|
case 0x36: //---------------- BT6 Block to 6000 Loop
|
|
case 0x37: //---------------- BT7 Block to 7000 Loop
|
|
this.procTime +=2;
|
|
this.blockToLoop(this.COP-0x30, this.schedule);
|
|
break;
|
|
|
|
case 0x38: //---------------- CCB Change Conditionally, Block to 7000 Loop
|
|
if (!this.stopOverflow) { // check if branch should occur
|
|
this.procTime += 3;
|
|
this.SHIFTCONTROL = 0x04; // for display only
|
|
} else {
|
|
this.procTime += 4;
|
|
this.setOverflow(0); // reset overflow and do the branch
|
|
this.SHIFT = 0x15; // for display only
|
|
this.SHIFTCONTROL = 0x0F; // for display only
|
|
this.CCONTROL = this.CADDR%0x100 + 0x7000; // set control to loop-7 address
|
|
this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
|
|
this.blockToLoop(7, this.schedule);
|
|
}
|
|
break;
|
|
|
|
case 0x39: //---------------- CCBR Change Conditionally, Block to 7000 Loop, Record
|
|
if (!this.stopOverflow) { // check if branch should occur
|
|
this.procTime += 3;
|
|
this.SHIFTCONTROL = 0x04; // for display only
|
|
} else {
|
|
this.procTime += 4;
|
|
this.setOverflow(0); // reset overflow and do the branch
|
|
this.SHIFT = 0x15; // for display only
|
|
this.SHIFTCONTROL = 0x0F; // for display only
|
|
this.R = this.CCONTROL*0x1000000; // save current control counter
|
|
this.CCONTROL = this.CADDR%0x100 + 0x7000; // set control to loop-7 address
|
|
this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
|
|
this.blockToLoop(7, this.schedule);
|
|
}
|
|
break;
|
|
|
|
case 0x40: //---------------- MTR Magnetic Tape Read
|
|
if (!this.magTape) {
|
|
//this.schedule();
|
|
} else {
|
|
this.selectedUnit = (this.CEXTRA >>> 4) & 0x0F;
|
|
d = (this.CEXTRA >>> 8) & 0xFF; // number of blocks
|
|
this.togMT3P = 1;
|
|
this.togMT1BV4 = d & 0x01; // select initial loop buffer
|
|
this.togMT1BV5 = 1-this.togMT1BV4;
|
|
this.procTime -= performance.now()*B220Processor.wordsPerMilli; // mark time during I/O
|
|
if (this.magTape.read(this.selectedUnit, d, this.boundMagTapeReceiveBlock)) {
|
|
this.setOverflow(1); // control or tape unit busy/not-ready
|
|
this.togMT3P = this.togMT1BV4 = this.togMT1BV5 = 0;
|
|
//this.schedule();
|
|
}
|
|
}
|
|
break;
|
|
|
|
// 0x41: //---------------- (no op)
|
|
|
|
case 0x42: //---------------- MTS Magnetic Tape Search
|
|
if (this.magTape) {
|
|
this.selectedUnit = (this.CEXTRA >>> 4) & 0x0F;
|
|
d = (this.CEXTRA >>> 8) & 0xFF; // lane number
|
|
if (this.magTape.search(this.selectedUnit, d, this.CADDR)) {
|
|
this.setOverflow(1); // control or tape unit busy/not-ready
|
|
}
|
|
}
|
|
//this.schedule();
|
|
break;
|
|
|
|
// 0x43: //---------------- (no op)
|
|
|
|
case 0x44: //---------------- CDR Card Read (Cardatron)
|
|
this.D = 0;
|
|
if (!this.cardatron) {
|
|
//this.schedule();
|
|
} else {
|
|
this.tog3IO = 1;
|
|
this.kDigit = (this.CEXTRA >>> 8) & 0x0F;
|
|
this.selectedUnit = (this.CEXTRA >>> 4) & 0x07;
|
|
this.SHIFT = 0x08; // prepare to receive 11 digits
|
|
this.procTime -= performance.now()*B220Processor.wordsPerMilli; // mark time during I/O
|
|
this.cardatron.inputInitiate(this.selectedUnit, this.kDigit, this.boundCardatronReceiveWord);
|
|
}
|
|
break;
|
|
|
|
case 0x45: //---------------- CDRI Card Read Interrogate
|
|
this.selectedUnit = (this.CEXTRA >>> 4) & 0x07;
|
|
if (this.cardatron && this.cardatron.inputReadyInterrogate(this.selectedUnit)) {
|
|
this.R = this.CCONTROL*0x1000000;
|
|
this.setTimingToggle(0); // stay in Execute
|
|
this.setOverflow(1); // set overflow
|
|
this.COP = 0x28; // make into a CC
|
|
this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
|
|
}
|
|
//this.schedule();
|
|
break;
|
|
|
|
// 0x46-0x47: //---------------- (no op)
|
|
|
|
case 0x48: //---------------- CDRF Card Read Format
|
|
if (!this.cardatron) {
|
|
//this.schedule();
|
|
} else {
|
|
this.tog3IO = 1;
|
|
this.kDigit = (this.CEXTRA >>> 8) & 0x0F;
|
|
this.selectedUnit = (this.CEXTRA >>> 4) & 0x07;
|
|
this.SHIFT = 0x19; // start at beginning of a word
|
|
this.procTime -= performance.now()*B220Processor.wordsPerMilli; // mark time during I/O
|
|
this.cardatron.inputFormatInitiate(this.selectedUnit, this.kDigit,
|
|
this.boundCardatronOutputWord, this.boundCardatronOutputFinished);
|
|
}
|
|
break;
|
|
|
|
// 0x49: //---------------- (no op)
|
|
|
|
case 0x50: //---------------- MTW Magnetic Tape Write
|
|
if (!this.magTape) {
|
|
//this.schedule();
|
|
} else {
|
|
this.selectedUnit = (this.CEXTRA >>> 4) & 0x0F;
|
|
d = (this.CEXTRA >>> 8) & 0xFF; // number of blocks
|
|
this.togMT3P = 1;
|
|
this.togMT1BV4 = d & 0x01; // select initial loop buffer
|
|
this.togMT1BV5 = 1-this.togMT1BV4;
|
|
this.procTime -= performance.now()*B220Processor.wordsPerMilli; // mark time during I/O
|
|
if (this.magTape.write(this.selectedUnit, d, this.boundMagTapeInitiateSend)) {
|
|
this.setOverflow(1); // control or tape unit busy/not-ready
|
|
this.togMT3P = this.togMT1BV4 = this.togMT1BV5 = 0;
|
|
//this.schedule();
|
|
}
|
|
}
|
|
break;
|
|
|
|
// 0x51: //---------------- (no op)
|
|
|
|
case 0x52: //---------------- MTRW Magnetic Tape Rewind
|
|
if (this.magTape) {
|
|
this.selectedUnit = (this.CEXTRA >>> 4) & 0x0F;
|
|
if (this.magTape.rewind(this.selectedUnit)) {
|
|
this.setOverflow(1); // control or tape unit busy/not-ready
|
|
}
|
|
}
|
|
break;
|
|
|
|
// 0x53: //---------------- (no op)
|
|
|
|
case 0x54: //---------------- CDW Card Write (Cardatron)
|
|
if (!this.cardatron) {
|
|
//this.schedule();
|
|
} else {
|
|
this.tog3IO = 1;
|
|
this.kDigit = (this.CEXTRA >>> 8) & 0x0F;
|
|
this.selectedUnit = (this.CEXTRA >>> 4) & 0x07;
|
|
this.SHIFT = 0x19; // start at beginning of a word
|
|
this.procTime -= performance.now()*B220Processor.wordsPerMilli; // mark time during I/O
|
|
this.cardatron.outputInitiate(this.selectedUnit, this.kDigit, (this.CEXTRA >>> 12) & 0x0F,
|
|
this.boundCardatronOutputWord, this.boundCardatronOutputFinished);
|
|
}
|
|
break;
|
|
|
|
case 0x55: //---------------- CDWI Card Write Interrogate
|
|
this.selectedUnit = (this.CEXTRA >>> 4) & 0x07;
|
|
if (this.cardatron && this.cardatron.outputReadyInterrogate(this.selectedUnit)) {
|
|
this.R = this.CCONTROL*0x1000000;
|
|
this.setTimingToggle(0); // stay in Execute
|
|
this.setOverflow(1); // set overflow
|
|
this.COP = 0x28; // make into a CC
|
|
this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
|
|
}
|
|
break;
|
|
|
|
// 0x56-0x57: //---------------- (no op)
|
|
|
|
case 0x58: //---------------- CDWF Card Write Format
|
|
if (!this.cardatron) {
|
|
//this.schedule();
|
|
} else {
|
|
this.tog3IO = 1;
|
|
this.kDigit = (this.CEXTRA >>> 8) & 0x0F;
|
|
this.selectedUnit = (this.CEXTRA >>> 4) & 0x07;
|
|
this.SHIFT = 0x19; // start at beginning of a word
|
|
this.procTime -= performance.now()*B220Processor.wordsPerMilli; // mark time during I/O
|
|
this.cardatron.outputFormatInitiate(this.selectedUnit, this.kDigit,
|
|
this.boundCardatronOutputWord, this.boundCardatronOutputFinished);
|
|
}
|
|
break;
|
|
|
|
// 0x59: //---------------- (no op)
|
|
|
|
default: //---------------- (unimplemented instruction -- alarm)
|
|
this.setProgramCheck(1);
|
|
break;
|
|
} // switch this.COP
|
|
}
|
|
};
|
|
|
|
|
|
/***********************************************************************
|
|
* Processor Run Control *
|
|
***********************************************************************/
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.run = function run() {
|
|
/* Main execution control loop for the processor. Called from this.schedule()
|
|
to initiate a time slice. Will continue fetch/execute phases until the time
|
|
slice expires, a stop condition is detected, or AST (asynchronous toggle)
|
|
is set indicating the processor has been suspended during an I/O. This
|
|
routine effectively implements Operation Complete (O.C.) for the Fetch and
|
|
Execute cycles, although it is more of a "ready for next operation" function,
|
|
determining if there is a stop condition, or whether to do a Fetch or
|
|
Execute cycle next. The fetch() and execute() methods exit back here, and
|
|
in most cases we simply step to the next phase/instruction. In the case of
|
|
asynchronous operation, however, we simply exit, and the I/O interface will
|
|
call this.schedule() to restart execution again once memory transfers have
|
|
completed */
|
|
|
|
this.runLimit = this.realTime + B220Processor.timeSlice;
|
|
|
|
do {
|
|
switch (true) {
|
|
case this.AST.value: // doing I/O -- just exit
|
|
this.runLimit = 0;
|
|
break;
|
|
|
|
case !this.RUT.value: // halted
|
|
case this.SST.value: // single-stepped
|
|
this.stop(); // sets this.runLimit = zero, kills loop
|
|
break;
|
|
|
|
case this.EXT.value: // enter execute phase
|
|
this.execute();
|
|
if (this.ORDERCOMPLEMENTSW) {
|
|
this.C.flipBit(16); // complement low order bit of op code
|
|
this.COP ^= 0x01;
|
|
}
|
|
|
|
if (this.SONSW) { // check for post-execute S-to-C stop
|
|
if (this.STOCSW) {
|
|
if (this.SUNITSSW) {
|
|
if (this.C.value%0x10 == this.S%0x10) {
|
|
this.RUT.set(0);
|
|
}
|
|
} else if (this.C.value%0x1000 == this.S%0x1000) {
|
|
this.RUT.set(0);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default: // enter fetch phase
|
|
if (this.SONSW) { // check for post-fetch S-to-P stop
|
|
if (this.STOPSW) { // must check before P is incremented in fetch()
|
|
if (this.SUNITSSW) {
|
|
if (this.P.value%0x10 == this.S%0x10) {
|
|
this.RUT.set(0);
|
|
}
|
|
} else if (this.C.value == this.S) {
|
|
this.RUT.set(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.fetch();
|
|
break;
|
|
}
|
|
} while (this.realTime < this.runLimit);
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.schedule = function schedule() {
|
|
/* Schedules the next processor time slice and attempts to throttle
|
|
performance to approximate that of a real B220. It establishes a time slice
|
|
in terms of a number milliseconds each and calls run() to execute for at
|
|
most that amount of time. run() counts up instruction times until it reaches
|
|
this limit or some terminating event (such as a stop), then exits back here.
|
|
If the processor remains active, this routine will reschedule itself after
|
|
an appropriate delay, thereby throttling the performance and allowing other
|
|
modules to share the single Javascript execution thread */
|
|
var delayTime; // delay from/until next run() for this processor, ms
|
|
var runStamp; // real-world time at start of time slice, ms
|
|
var stamp = performance.now(); // ending time for the delay and the run() call, ms
|
|
|
|
this.scheduler = 0;
|
|
|
|
// If run() has been called by a throttling delay, compute the delay stats.
|
|
if (this.lastDelayStamp > 0) {
|
|
delayTime = stamp - this.delayLastStamp;
|
|
this.procSlack += delayTime;
|
|
|
|
// Compute the exponential weighted average of scheduling delay.
|
|
this.delayDeltaAvg = (delayTime - this.delayRequested)*B220Processor.delayAlpha +
|
|
this.delayDeltaAvg*B220Processor.delayAlpha1;
|
|
this.procSlackAvg = delayTime*B220Processor.slackAlpha +
|
|
this.procSlackAvg*B220Processor.slackAlpha1;
|
|
}
|
|
|
|
// Accumulate internal processor run time if it's been idle due to I/O.
|
|
while (this.procTime < 0) {
|
|
this.procTime += stamp;
|
|
}
|
|
|
|
// Execute the time slice.
|
|
runStamp = stamp; // starting clock time for time slice
|
|
this.procTime -= this.realTime; // prepare to accumulate internal processor time
|
|
|
|
this.run();
|
|
|
|
stamp = performance.now();
|
|
this.procTime += this.realTime; // accumulate internal processor time for the slice
|
|
this.procRunAvg = (stamp - runStamp)*B220Processor.slackAlpha +
|
|
this.procRunAvg*B220Processor.slackAlpha1;
|
|
|
|
// Determine what to do next.
|
|
if (!this.RUT.value) {
|
|
// Processor is stopped, just inhibit delay averaging on next call and exit.
|
|
this.lastDelayStamp = 0;
|
|
} else if (this.AST.value) {
|
|
// Processor is still running, but idle during I/O.
|
|
this.lastDelayStamp = 0;
|
|
while (this.procTime >= 0) {
|
|
this.procTime -= stamp; // keep the internal processor timer running
|
|
}
|
|
} else {
|
|
// Processor is still running, so schedule next time slice after a throttling delay.
|
|
// delayTime is the number of milliseconds the processor is running ahead of
|
|
// real-world time. Web browsers have a certain minimum setTimeout() delay. If the
|
|
// delay is less than our estimate of that minimum, setCallback will yield to
|
|
// the event loop but otherwise continue (real time should eventually catch up --
|
|
// we hope). If the delay is greater than the minimum, setCallback will reschedule
|
|
// us after that delay.
|
|
|
|
delayTime = this.realTime - stamp;
|
|
this.delayRequested = delayTime;
|
|
this.delayLastStamp = stamp;
|
|
this.scheduler = setCallback(this.mnemonic, this, delayTime, this.schedule);
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.start = function start() {
|
|
/* Initiates a time slice for the processor according to the EXT state */
|
|
var stamp = performance.now();
|
|
|
|
if (this.poweredOn && !this.RUT.value && !this.AST.value &&
|
|
!this.systemNotReady.value && !this.computerNotReady.value) {
|
|
this.procStart = stamp;
|
|
this.realTime = stamp;
|
|
this.delayLastStamp = stamp;
|
|
this.delayRequested = 0;
|
|
this.RUT.set(1);
|
|
|
|
// Start the timers
|
|
while (this.runTime >= 0) {
|
|
this.runTime -= stamp;
|
|
}
|
|
while (this.timerTime >= 0) {
|
|
this.timerTime -= stamp;
|
|
}
|
|
|
|
this.schedule();
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.stop = function stop() {
|
|
/* Stops running the processor on the Javascript thread */
|
|
|
|
if (this.poweredOn) {
|
|
this.runLimit = 0; // kill the time slice
|
|
this.SST.set(0);
|
|
this.RUT.set(0);
|
|
|
|
// Stop the timers
|
|
while (this.runTime < 0) {
|
|
this.runTime += stamp;
|
|
}
|
|
while (this.timerTime < 0) {
|
|
this.timerTime += stamp;
|
|
}
|
|
|
|
if (this.scheduler) {
|
|
clearCallback(this.scheduler);
|
|
this.scheduler = 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.step = function step() {
|
|
/* Single-steps the processor. This will execute the next Fetch or Execute
|
|
phase only, then stop the processor */
|
|
|
|
if (this.poweredOn) {
|
|
this.SST.set(1);
|
|
this.start();
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.resetRunTimer = function resetRunTimer() {
|
|
/* Resets the elapsed run-time timer to zero */
|
|
|
|
if (this.timerTime < 0) { // it's running, adjust its bias
|
|
this.timerTime = -performance.now();
|
|
} else { // it's stopped, just zero it
|
|
this.timerTime = 0;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.inputSetup = function inputSetup(unitNr) {
|
|
/* Called from the Cardatron Control Unit. If the Processor is stopped,
|
|
loads a CDR (60) instruction into C for unit "unitNr" and sets Execute phase */
|
|
|
|
if (this.poweredOn && !this.RUT.value) {
|
|
this.CONTROL = unitNr*0x1000; // recognize control words, no lockout
|
|
this.COP = 0x60; // CDR
|
|
this.CADDR = 0;
|
|
this.C = (this.CCONTROL*0x100 + this.COP)*0x10000 + this.CADDR;
|
|
this.EXT.set(1);
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.powerUp = function powerUp() {
|
|
/* Powers up the system */
|
|
|
|
if (!this.poweredOn) {
|
|
this.clear();
|
|
this.poweredOn = 1;
|
|
this.runTime = this.timerTime = 0;
|
|
this.procSlack = this.procSlackAvg = this.procRunAvg = 0;
|
|
this.delayDeltaAvg = this.delayRequested = 0;
|
|
|
|
this.console = this.devices.ControlConsole;
|
|
this.cardatron = this.devices.CardatronControl;
|
|
this.magTape = this.devices.MagTapeControl;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.powerDown = function powerDown() {
|
|
/* Powers down the system */
|
|
|
|
if (this.poweredOn) {
|
|
this.stop();
|
|
this.clear();
|
|
this.poweredOn = 0;
|
|
|
|
this.cardatron = null;
|
|
this.console = null;
|
|
this.magTape = null;
|
|
if (this.glowTimer) {
|
|
clearInterval(this.glowTimer);
|
|
this.glowTimer = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B220Processor.prototype.loadDefaultProgram = function loadDefaultProgram() {
|
|
/* Loads a set of default demo programs to the memory drum */
|
|
|
|
// Simple counter speed test
|
|
this.MM[ 0] = 0x0000740002; // ADD 2
|
|
this.MM[ 1] = 0x0000200000; // CU 0
|
|
this.MM[ 2] = 0x0000000001; // LIT 1
|
|
|
|
// Bootstrap to loop-based square-roots test
|
|
this.MM[ 10] = 0x0000360200 // BT6 200
|
|
this.MM[ 11] = 0x0000370220 // BT7 220
|
|
this.MM[ 12] = 0x0000206980 // CU 6980 branch to loop-6 entry point
|
|
|
|
// Hello World
|
|
this.MM[ 20] = 0x0000070500; // PTWF 0500 line feed
|
|
this.MM[ 21] = 0x0000640027; // CAD 27
|
|
this.MM[ 22] = 0x0000030410; // PTW 0410
|
|
this.MM[ 23] = 0x0000070800; // PTWF 0800 space
|
|
this.MM[ 24] = 0x0000640028; // CAD 28
|
|
this.MM[ 25] = 0x0000030410; // PTW 0410
|
|
this.MM[ 26] = 0x0000089429; // HALT 9429
|
|
this.MM[ 27] = 0x4845535356; // LIT "HELLO"
|
|
this.MM[ 28] = 0x6656595344; // LIT "WORLD"
|
|
|
|
// Tom Sawyer's "Square Roots 100" (Babylonian or Newton's method):
|
|
this.MM[ 100] = 0x640139; // CAD 139
|
|
this.MM[ 101] = 0x120138; // ST 138
|
|
this.MM[ 102] = 0x640139; // CAD 139
|
|
this.MM[ 103] = 0x130005; // SR 5
|
|
this.MM[ 104] = 0x610138; // DIV 138
|
|
this.MM[ 105] = 0x120137; // ST 137
|
|
this.MM[ 106] = 0x750138; // SUB 138
|
|
this.MM[ 107] = 0x120136; // ST 136
|
|
this.MM[ 108] = 0x660136; // CADA 136
|
|
this.MM[ 109] = 0x750135; // SUB 135
|
|
this.MM[ 110] = 0x730135; // OSGD 135
|
|
this.MM[ 111] = 0x280118; // CC 118
|
|
this.MM[ 112] = 0x640138; // CAD 138
|
|
this.MM[ 113] = 0x740137; // ADD 137
|
|
this.MM[ 114] = 0x130005; // SR 5
|
|
this.MM[ 115] = 0x610134; // DIV 134
|
|
this.MM[ 116] = 0x120138; // ST 138
|
|
this.MM[ 117] = 0x200102; // CU 102
|
|
this.MM[ 118] = 0x640139; // CAD 139
|
|
this.MM[ 119] = 0x030505; // PTW 0505
|
|
this.MM[ 120] = 0x640137; // CAD 137
|
|
this.MM[ 121] = 0x030810; // PTW 0810
|
|
this.MM[ 122] = 0x640139; // CAD 139
|
|
this.MM[ 123] = 0x740133; // ADD 133
|
|
this.MM[ 124] = 0x120139; // ST 139
|
|
this.MM[ 125] = 0x200102; // CU 102
|
|
this.MM[ 126] = 0;
|
|
this.MM[ 127] = 0;
|
|
this.MM[ 128] = 0;
|
|
this.MM[ 129] = 0;
|
|
this.MM[ 130] = 0;
|
|
this.MM[ 131] = 0;
|
|
this.MM[ 132] = 0;
|
|
this.MM[ 133] = 0x100000;
|
|
this.MM[ 134] = 0x200000;
|
|
this.MM[ 135] = 0x10;
|
|
this.MM[ 136] = 0;
|
|
this.MM[ 137] = 0;
|
|
this.MM[ 138] = 0;
|
|
this.MM[ 139] = 0x200000;
|
|
|
|
// "Square Roots 100" running from the loops with R cleared for division:
|
|
// Block for the 6980 loop
|
|
this.MM[ 200] = 0x0000647039; // CAD 7039
|
|
this.MM[ 201] = 0x0000127038; // ST 7038
|
|
this.MM[ 202] = 0x0000647039; // CAD 7039
|
|
this.MM[ 203] = 0x0000330000; // CR
|
|
this.MM[ 204] = 0x0000130005; // SR 5
|
|
this.MM[ 205] = 0x0000617038; // DIV 7038
|
|
this.MM[ 206] = 0x0000127037; // ST 7037
|
|
this.MM[ 207] = 0x0000757038; // SUB 7038
|
|
this.MM[ 208] = 0x0000127036; // ST 7036
|
|
this.MM[ 209] = 0x0000667036; // CADA 7036
|
|
this.MM[ 210] = 0x0000757035; // SUB 7035
|
|
this.MM[ 211] = 0x0000737035; // OSGD 7035
|
|
this.MM[ 212] = 0x0000287000; // CC 7000
|
|
this.MM[ 213] = 0x0000647038; // CAD 7038
|
|
this.MM[ 214] = 0x0000747037; // ADD 7037
|
|
this.MM[ 215] = 0x0000330000; // CR
|
|
this.MM[ 216] = 0x0000130005; // SR 5
|
|
this.MM[ 217] = 0x0000617034; // DIV 7034
|
|
this.MM[ 218] = 0x0000127038; // ST 7038
|
|
this.MM[ 219] = 0x0000206982; // CU 6982
|
|
// Block for the 7000 loop
|
|
this.MM[ 220] = 0x0000647039; // CAD 7039
|
|
this.MM[ 221] = 0x0000030505; // PTW 0505
|
|
this.MM[ 222] = 0x0000647037; // CAD 7037
|
|
this.MM[ 223] = 0x0000030810; // PTW 0810
|
|
this.MM[ 224] = 0x0000647039; // CAD 7039
|
|
this.MM[ 225] = 0x0000747033; // ADD 7033
|
|
this.MM[ 226] = 0x0000127039; // ST 7039
|
|
this.MM[ 227] = 0x0000206982; // CU 6982
|
|
this.MM[ 228] = 0;
|
|
this.MM[ 229] = 0;
|
|
this.MM[ 230] = 0;
|
|
this.MM[ 231] = 0;
|
|
this.MM[ 232] = 0;
|
|
this.MM[ 233] = 0x0000100000;
|
|
this.MM[ 234] = 0x0000200000;
|
|
this.MM[ 235] = 0x0000000010;
|
|
this.MM[ 236] = 0;
|
|
this.MM[ 237] = 0;
|
|
this.MM[ 238] = 0;
|
|
this.MM[ 239] = 0x0000200000;
|
|
|
|
// "Square Roots 100" adapted for floating-point and relative precision:
|
|
this.MM[ 300] = 0x0000640339; // CAD 339 load initial argument
|
|
this.MM[ 301] = 0x0000120338; // ST 338 store as initial upper bound
|
|
this.MM[ 302] = 0x0000640339; // CAD 339 start of loop: load current argument
|
|
this.MM[ 303] = 0x0000330000; // CR clear R
|
|
this.MM[ 304] = 0x0000830338; // FDIV 338 divide argument by upper bound
|
|
this.MM[ 305] = 0x0000120337; // ST 337 store as current result
|
|
this.MM[ 306] = 0x0000830338; // FDIV 338 ratio to upper bound
|
|
this.MM[ 307] = 0x0000120336; // ST 336 store as current precision
|
|
this.MM[ 308] = 0x0000660335; // CADA 335 load target precision
|
|
this.MM[ 309] = 0x0000810336; // FSU 336 subtract current precision
|
|
this.MM[ 310] = 0x0000730335; // OSGD 335 if current precision > target precision
|
|
this.MM[ 311] = 0x0000280318; // CC 318 we're done -- jump out to print
|
|
this.MM[ 312] = 0x0000640338; // CAD 338 load current upper bound
|
|
this.MM[ 313] = 0x0000800337; // FAD 337 add current result
|
|
this.MM[ 314] = 0x0000330000; // CR clear R
|
|
this.MM[ 315] = 0x0000830334; // FDIV 334 divide by 2.0 to get new upper bound
|
|
this.MM[ 316] = 0x0000120338; // ST 338 store new upper bound
|
|
this.MM[ 317] = 0x0000200302; // CU 302 do another iteration
|
|
this.MM[ 318] = 0x0001640339; // CAD 339
|
|
this.MM[ 319] = 0x0000030510; // PTW 0510
|
|
this.MM[ 320] = 0x0000640337; // CAD 337
|
|
this.MM[ 321] = 0x0000030810; // PTW 0810
|
|
this.MM[ 322] = 0x0000640339; // CAD 339 load argument value
|
|
this.MM[ 323] = 0x0000800333; // FAD 333 add 1 to argument value
|
|
this.MM[ 324] = 0x0000120339; // ST 339
|
|
this.MM[ 325] = 0x0000200301; // CU 301 start sqrt for next argument value
|
|
this.MM[ 326] = 0;
|
|
this.MM[ 327] = 0;
|
|
this.MM[ 328] = 0;
|
|
this.MM[ 329] = 0;
|
|
this.MM[ 330] = 0;
|
|
this.MM[ 331] = 0;
|
|
this.MM[ 332] = 0;
|
|
this.MM[ 333] = 0x05110000000; // 1.0 literal: argument increment
|
|
this.MM[ 334] = 0x05120000000; // 2.0 literal
|
|
this.MM[ 335] = 0x05099999990; // 0.99999990 literal: target precision
|
|
this.MM[ 336] = 0; // current precision
|
|
this.MM[ 337] = 0; // current sqrt result
|
|
this.MM[ 338] = 0; // current upper bound on result
|
|
this.MM[ 339] = 0x05120000000; // 2.0 sqrt argument
|
|
|
|
// Counter speed test in 4000 loop
|
|
this.L4[ 0] = 0x0000744002; // ADD 4002 -- start of counter speed test
|
|
this.L4[ 1] = 0x0000204000; // CU 4000
|
|
this.L4[ 2] = 0x0000000001; // LIT 1
|
|
}; |