diff --git a/emulator/B220Processor.js b/emulator/B220Processor.js
new file mode 100644
index 0000000..4c3683b
--- /dev/null
+++ b/emulator/B220Processor.js
@@ -0,0 +1,3422 @@
+/***********************************************************************
+* 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 205 Handbook
+* (Bulletin 3021, Burroughs Corporation, 1956).
+* Programming and Coding Manual, Datatron
+* (Bulletin 3040A, ElectroData Corporation, 1954).
+* Handbook of Operating Procedures for the Burroughs 205
+* (Bulletin 3034A, Burroughs Corporation, May 1960).
+*
+* available at:
+* http://bitsavers.org/pdf/burroughs/electrodata/205/
+*
+* also:
+*
+* TM4001 Datatron 205 Computer (Training Edition)
+* (Burroughs Corporation, December,1956).
+* Burroughs 205 Handbook: Floating Point Control Unit Model 36044
+* (Bulletin 3028, Burroughs Corporation 1957).
+* Engineering Description of the ElectroData Digital Computer, J. C. Alrich,
+* (IRE Transactions on Electronic Computers, vol EC-4, Number 1, March 1955).
+*
+* Datatron 205 word format:
+* 44 bits, encoded as binary-coded decimal (BCD); non-decimal codes are invalid
+* and cause the computer to stop with a Forbidden Combination (FC) alarm.
+* 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
+* 5 character codes
+* one instruction word
+*
+* Instruction word format:
+* Low-order 4 digits: operand address
+* Next-higher 2 digits: operation code
+* Next-higher 4 digits: breakpoint and special-function codes
+* 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 "word-times": 1/200-th
+* revolution of the memory drum, or about 84 µs at 3570rpm.
+*
+************************************************************************
+* 2017-01-01 P.Kimpel
+* Original version, cloned from a bit of retro-205 code.
+***********************************************************************/
+"use strict";
+
+/**************************************/
+function B220Processor(config, devices) {
+ /* Constructor for the 205 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.memoryDrum = new ArrayBuffer(4080*8); // Drum: 4080 64-bit FP words
+ this.MM = new Float64Array(this.memoryDrum, 0, 4000); // Main memory, 4000 words
+ this.L4 = new Float64Array(this.memoryDrum, 4000*8, 20); // 4000 loop, 20 words
+ this.L5 = new Float64Array(this.memoryDrum, 4020*8, 20); // 5000 loop, 20 words
+ this.L6 = new Float64Array(this.memoryDrum, 4040*8, 20); // 6000 loop, 20 words
+ this.L7 = new Float64Array(this.memoryDrum, 4060*8, 20); // 7000 loop, 20 words
+
+ // Supervisory Panel switches
+ this.sswLockNormal = 0; // Lock/Normal switch
+ this.sswStepContinuous = 0; // Step/Continuous switch
+ this.sswAudibleAlarm = 0; // Audible alarm
+
+ // Control Console switches
+ this.cswPOSuppress = 0; // Print-out suppress
+ this.cswSkip = 0; // Skip instruction
+ this.cswAudibleAlarm = 0; // Audible alarm
+ this.cswOutput = 0; // Output knob: 0=Off, 1=Page, 2=Tape (mapped from actual knob values)
+ this.cswInput = 0; // Input knob: 0=Mechanical reader, 1=Optical reader, 2=Keyboard
+ this.cswBreakpoint = 0; // Breakpoint knob: 0=Off, 1, 2, 4
+
+ // Mag-Tape Control Unit switch
+ this.tswSuppressB = 0; // Suppress B-register modification on input
+
+ // Context-bound routines
+ this.boundExecuteComplete = B220Processor.bindMethod(this, B220Processor.prototype.executeComplete);
+ 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);
+
+ // Processor throttling control
+ this.scheduler = 0; // Current setCallback token
+ this.procStart = 0; // Javascript time that the processor started running, ms
+ this.procTime = 0; // Total processor running time, ms
+
+ // External switches [used by EXC (71)]
+ this.externalSwitch = [0, 0, 0, 0, 0, 0, 0, 0];
+
+ // Register average-intensity accumulators
+ this.glowTimer = null;
+ this.toggleGlow = {
+ glowA: new Float64Array(44),
+ glowB: new Float64Array(16),
+ glowC: new Float64Array(40),
+ glowD: new Float64Array(44),
+ glowR: new Float64Array(40),
+ glowCtl: new Float64Array(40),
+ glowADDER: new Float64Array(4),
+ glowCT: new Float64Array(5),
+ glowTiming: 0,
+ glowOverflow: 0,
+ glowTWA: 0,
+ glow3IO: 0,
+ glowMAIN: 0,
+ glowRWM: 0,
+ glowRWL: 0,
+ glowWDBL: 0,
+ glowACTION: 0,
+ glowACCESS: 0,
+ glowLM: 0,
+ glowL4: 0,
+ glowL5: 0,
+ glowL6: 0,
+ glowL7: 0};
+
+ this.clear(); // Create and initialize the processor state
+
+ this.loadDefaultProgram(); // Preload a default program
+}
+
+/**************************************/
+
+/* Global constants */
+B220Processor.version = "0.00a";
+
+B220Processor.drumRPM = 3570; // memory drum speed, RPM
+B220Processor.trackSize = 200; // words per drum revolution
+B220Processor.loopSize = 20; // words per high-speed loop
+B220Processor.wordTime = 60000/B220Processor.drumRPM/B220Processor.trackSize;
+ // one word time, about 0.084 ms at 3570rpm (=> 142.8 KHz)
+B220Processor.wordsPerMilli = 1/B220Processor.wordTime;
+ // word times per millisecond
+B220Processor.neonPersistence = 1000/30;
+ // persistence of neon bulb glow [ms]
+B220Processor.maxGlowTime = B220Processor.neonPersistence*B220Processor.wordsPerMilli;
+ // panel bulb glow persistence [word-times]
+B220Processor.lampGlowInterval = 50; // background lamp sampling interval (ms)
+B220Processor.adderGlowAlpha = B220Processor.wordTime/12/B220Processor.neonPersistence;
+ // adder and carry toggle glow decay factor,
+ // based on one digit (1/12 word) time
+
+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.prototype.clear = function clear() {
+ /* Initializes (and if necessary, creates) the processor state */
+
+ this.clearControl();
+
+ // Registers
+ this.A = 0; // A register - accumulator
+ this.B = 0; // B register - index, loop control
+ this.C = 0; // C register - current operator, control counter
+ this.D = 0; // D register - input buffer from memory and I/O
+ this.R = 0; // R register - accumulator extension
+
+ this.COP = 0; // copy of C register op code (2 digits)
+ this.CADDR = 0; // copy of C register operand address (4 digits)
+ this.CCONTROL = 0; // copy of C register control address (4 digits)
+ this.CEXTRA = 0; // high-order four digits of instruction word + sign
+
+ // Adder registers
+ this.ADDER = 0; // The adder
+ this.CT = 0; // Carry toggles for the adder
+
+ // Operational toggles
+ this.togTiming = 1; // Timing toggle: 0=execute, 1=fetch
+ this.togCST = 1; // Computer Stop toggle (?)
+ this.cctContinuous = 0; // Console step(0) / continuous(1) toggle
+
+ // Halt/error toggles
+ this.stopOverflow = 0; // Halted due to overflow
+ this.stopSector = 0; // Halted due to sector alarm
+ this.stopForbidden = 0; // Halted due to forbidden combination
+ this.stopControl = 0; // Halted due to Stop operator (08) or overflow
+ this.stopBreakpoint = 0; // Halted due to breakpoint
+ this.stopIdle = 1; // Halted or in step mode
+
+ // Memory control toggles
+ this.memMAIN = 0; // Access is to main memory
+ this.memRWM = 0; // Read/write main memory
+ this.memRWL = 0; // Read/write loop memory
+ this.memWDBL = 0; // Word or block transfer
+ this.memACTION = 0; // Memory access ACTION toggle
+ this.memACCESS = 0; // Memory access active toggle
+ this.memLM = 0; // Loop/main access toggle
+ this.memL4 = 0; // 4000 loop access
+ this.memL5 = 0; // 5000 loop access
+ this.memL6 = 0; // 6000 loop access
+ this.memL7 = 0; // 7000 loop access
+
+ // Statistics timers
+ this.lastGlowTime = 0; // Last panel lamp intensity update time [word-times]
+ this.memoryStartTime = 0; // Start of last memory access [word-times]
+ this.memoryStopTime = 0; // End of last memory access [word-times]
+
+ this.setTimingToggle(0); // set to Execute initially
+ this.sampleLamps(1.0, 1.0); // initialize the lamp-glow averages
+
+ // 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();
+ }
+};
+
+/**************************************/
+B220Processor.prototype.clearControlToggles = function clearControlToggles() {
+ /* Clears the processor control toggles at end of an execution cycle.
+ The adder and carry toggles should also be cleared as part of this, but
+ they are often zero anyway, and we are only doing this for display purposes,
+ so leave them as is */
+
+ this.togADDER = // Adder toggle
+ this.togBTOAIN = 0; // B-to-A, Input toggle
+ this.togDPCTR = // Digit-pulse toggle: on during decimal-correct, off during complement
+ this.togDELTABDIV = // Delta-B, divide toggle
+ this.togCOMPL = // Complement toggle
+ this.togADDAB = // Add-A/add-B toggle
+ this.togCLEAR = // Clear toggle
+ this.togMULDIV = // Multiply-divide toggle
+ this.togSIGN = // Sign toggle
+ this.togCOUNT = // Count toggle
+ this.togDIVALARM = // Divide alarm
+ this.togSTEP = 0; // Step toggle
+};
+
+/**************************************/
+B220Processor.prototype.clearControl = function clearControl() {
+ /* Initializes (and if necessary, creates) the processor control registers
+ and toggles */
+
+ this.SHIFT = 0; // Shift counter
+ this.SHIFTCONTROL = 0; // Shift control register
+ this.SPECIAL = 0; // Special counter
+
+ // Toggles (flip-flops)
+ this.clearControlToggles(); // the standard set
+ this.togSTART = 0; // Input control: Start toggle
+ this.togTF = 0; // Input control: FINISH pulse toggle
+ this.togTC1 = 0; // Input control: clock pulse toggle (from input device)
+ this.togTC2 = 0; // Input control: clock pulse toggle (shift input digit to D sign)
+ this.togOK = 0; // Output control: OK toggle (output device ready for next digit)
+ this.togPO1 = 0; // Output control: PO1 toggle (actual print-out to begin)
+ this.togPO2 = 0; // Output control: PO2 toggle (on at start of digit output)
+ this.togDELAY = 0; // Output control: Delay toggle
+ this.togT0 = 0; // Central control: T0 toggle
+ this.togBKPT = 0; // Central control: breakpoint toggle
+ this.togZCT = 0; // Central control: zero check toggle
+ this.togASYNC = 0; // Central control: async toggle
+ this.togMT3P = 0; // Magnetic tape: 3P toggle
+ this.togMT1BV4 = 0; // Magnetic tape: 1BV4 toggle
+ this.togMT1BV5 = 0; // Magnetic tape: 1BV5 toggle
+
+ // Cardatron toggles
+ this.togTWA = 0; // Cardatron: TWA toggle
+ this.tog3IO = 0; // Cardatron: 3IO toggle
+
+ // I/O globals
+ this.kDigit = 0; // variant/format digits from upper part of instruction
+ this.selectedUnit = 0; // currently-selected unit number
+};
+
+/***********************************************************************
+* 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 */
+ var d;
+ var power = 1;
+ var result = 0;
+
+ while(v) {
+ d = v % 0x10;
+ 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;
+};
+
+
+/***********************************************************************
+* Timing and Statistics Functions *
+***********************************************************************/
+
+/**************************************/
+B220Processor.prototype.setTimingToggle = function setTimingToggle(cycle) {
+ /* Sets the timing toggle to the value of "cycle": 0=Execute, !0=Fetch */
+
+ this.togTiming = (cycle ? 1 : 0);
+};
+
+/**************************************/
+B220Processor.prototype.setOverflow = function setOverflow(overflow) {
+ /* Sets the overflow toggle to the value of "overflow" */
+
+ this.stopOverflow = (overflow ? 1 : 0);
+};
+
+/**************************************/
+B220Processor.prototype.feelTheGlow = function feelTheGlow(alpha, alpha1, glow, bits, r) {
+ /* Computes the running exponential average of lamp intensities for register
+ "r" in the low-order "bits" bits, using the "alpha" decay factor into the array
+ "glow". alpha1 must be 1.0 - alpha */
+ var b = 0;
+ var bit;
+
+ while (r) {
+ bit = r % 2;
+ r = (r-bit)/2;
+ glow[b] = glow[b]*alpha1 + bit*alpha;
+ ++b;
+ }
+
+ while (b < bits) {
+ glow[b] *= alpha1;
+ ++b;
+ }
+};
+
+/**************************************/
+B220Processor.prototype.updateAdderGlow = function updateAdderGlow(adder, ct) {
+ /* Computes the exponential running average of adder and carry toggle bit
+ intensities. This is called for every digit passing through the adder, and
+ serves only to compute a range of bit-by-bit intensities (0,1) for display */
+ var alpha = B220Processor.adderGlowAlpha;
+ var alpha1 = 1.0 - alpha;
+ var b = 0;
+ var bit;
+ var glowA = this.toggleGlow.glowADDER;
+ var glowC = this.toggleGlow.glowCT;
+
+ while (b < 4) {
+ bit = adder % 2;
+ adder = (adder-bit)/2;
+ glowA[b] = glowA[b]*alpha1 + bit*alpha;
+ ++b;
+ }
+
+ b = 0;
+ while (b < 5) {
+ bit = ct % 2;
+ ct = (ct-bit)/2;
+ glowC[b] = glowC[b]*alpha1 + bit*alpha;
+ ++b;
+ }
+};
+
+/**************************************/
+B220Processor.prototype.sampleLamps = function sampleLamps(alpha, memAlpha) {
+ /* Updates the lamp intensity arrays for all registers. "alpha" and "memAlpha"
+ must be in the range (0-,1) and indicate the relative significance for the current
+ register settings to the running exponential average algorithm */
+ var alpha1 = 1.0 - alpha;
+ var tg = this.toggleGlow;
+
+ tg.glowTiming = tg.glowTiming*alpha1 + this.togTiming*alpha;
+ tg.glowOverflow = tg.glowOverflow*alpha1 + this.stopOverflow*alpha;
+ tg.glowTWA = tg.glowTWA*alpha1 + this.togTWA*alpha;
+ tg.glow3IO = tg.glow3IO*alpha1 + this.tog3IO*alpha;
+
+ this.feelTheGlow(alpha, alpha1, tg.glowA, 44, this.A);
+ this.feelTheGlow(alpha, alpha1, tg.glowB, 16, this.B);
+ this.feelTheGlow(alpha, alpha1, tg.glowC, 40, this.C);
+ this.feelTheGlow(alpha, alpha1, tg.glowD, 44, this.D);
+ this.feelTheGlow(alpha, alpha1, tg.glowR, 40, this.R);
+
+ this.feelTheGlow(alpha, alpha1, tg.glowADDER, 4, this.ADDER);
+ this.feelTheGlow(alpha, alpha1, tg.glowCT, 5, this.CT);
+
+ this.feelTheGlow(alpha, alpha1, tg.glowCtl, 40, ((((((((((((((((((((((((((((
+ this.SPECIAL*2 +
+ this.togBTOAIN)*2 +
+ this.togADDER)*2 +
+ this.togDPCTR)*2 +
+ this.togDELTABDIV)*2 +
+ this.togCOMPL)*2 +
+ this.togADDAB)*2 +
+ this.togCLEAR)*2 +
+ this.togMULDIV)*2 +
+ this.togSIGN)*2 +
+ this.togCOUNT)*2 +
+ this.togDIVALARM)*2 +
+ this.togSTEP)*2 +
+ this.togSTART)*2 +
+ this.togTF)*2 +
+ this.togTC1)*2 +
+ this.togTC2)*2 +
+ this.togOK)*2 +
+ this.togPO1)*2 +
+ this.togPO2)*2 +
+ this.togDELAY)*2 +
+ this.togT0)*2 +
+ this.togBKPT)*2 +
+ this.togZCT)*2 +
+ this.togASYNC)*16 +
+ this.SHIFTCONTROL)*2 +
+ this.togMT3P)*2 +
+ this.togMT1BV4)*2 +
+ this.togMT1BV5)*32 +
+ this.SHIFT);
+
+ // Decay the memory toggles if no memory access is in progress
+ if (this.memoryStartTime == 0) {
+ alpha1 = 1.0 - memAlpha;
+ tg.glowMAIN = tg.glowMAIN*alpha1;
+ tg.glowRWM = tg.glowRWM*alpha1;
+ tg.glowRWL = tg.glowRWL*alpha1;
+ tg.glowWDBL = tg.glowWDBL*alpha1;
+ tg.glowACTION = tg.glowACTION*alpha1;
+ tg.glowACCESS = tg.glowACCESS*alpha1;
+ tg.glowLM = tg.glowLM*alpha1;
+ tg.glowL4 = tg.glowL4*alpha1;
+ tg.glowL5 = tg.glowL5*alpha1;
+ tg.glowL6 = tg.glowL6*alpha1;
+ tg.glowL7 = tg.glowL7*alpha1;
+
+ if (isNaN(tg.glowMAIN)) {debugger}
+ }
+};
+
+/**************************************/
+B220Processor.prototype.updateLampGlow = function updateLampGlow(drumTime) {
+ /* Computes an alpha factor based on the elapsed time since the last sampling,
+ then calls sampleLamps() to update the running averages. drumTime is the
+ current time in word-time units, 0.084ms. If drumTime is zero or undefined,
+ the current time is used */
+ var clock = drumTime || performance.now()*B220Processor.wordsPerMilli;
+ var alpha = Math.min((clock - this.lastGlowTime)/B220Processor.maxGlowTime, 1.0);
+ var memAlpha = Math.min((clock - this.memoryStopTime)/B220Processor.maxGlowTime, 1.0);
+
+ this.sampleLamps(alpha, memAlpha);
+ this.lastGlowTime = clock;
+};
+
+/**************************************/
+B220Processor.prototype.startMemoryTiming = function startMemoryTiming(drumTime) {
+ /* Starts the necessary timers for the memory toggles to aid in their
+ display on the panels. Note that "drumTime" is in units of word-times */
+
+ this.updateLampGlow(drumTime);
+ this.memoryStartTime = drumTime;
+};
+
+/**************************************/
+B220Processor.prototype.stopMemoryTiming = function stopMemoryTiming() {
+ /* Stops the active timers for the memory toggles to aid in their
+ display on the panels and reset the corresponding toggle */
+ var drumTime = performance.now()*B220Processor.wordsPerMilli;
+ var alpha = Math.min((drumTime - this.memoryStartTime)/B220Processor.maxGlowTime, 1.0);
+ var alpha1 = 1.0 - alpha;
+ var tg = this.toggleGlow;
+
+ tg.glowMAIN = tg.glowMAIN*alpha1 + this.memMAIN*alpha;
+ tg.glowRWM = tg.glowRWM*alpha1 + this.memRWM*alpha;
+ tg.glowRWL = tg.glowRWL*alpha1 + this.memRWL*alpha;
+ tg.glowWDBL = tg.glowWDBL*alpha1 + this.memWDBL*alpha;
+ tg.glowACTION = tg.glowACTION*alpha1 + this.memACTION*alpha;
+ tg.glowACCESS = tg.glowACCESS*alpha1 + this.memACCESS*alpha;
+ tg.glowLM = tg.glowLM*alpha1 + this.memLM*alpha;
+ tg.glowL4 = tg.glowL4*alpha1 + this.memL4*alpha;
+ tg.glowL5 = tg.glowL5*alpha1 + this.memL5*alpha;
+ tg.glowL6 = tg.glowL6*alpha1 + this.memL6*alpha;
+ tg.glowL7 = tg.glowL7*alpha1 + this.memL7*alpha;
+
+ if (isNaN(tg.glowMAIN)) {debugger}
+
+ this.memoryStopTime = drumTime;
+ this.memoryStartTime =
+ this.memMAIN =
+ this.memRWM =
+ this.memRWL =
+ this.memWDBL =
+ this.memACTION =
+ this.memACCESS =
+ this.memLM =
+ this.memL4 =
+ this.memL5 =
+ this.memL6 =
+ this.memL7 = 0;
+};
+
+
+/***********************************************************************
+* Bit and Field Manipulation Functions *
+***********************************************************************/
+
+/**************************************/
+B220Processor.prototype.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.prototype.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.prototype.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.prototype.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.prototype.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.prototype.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.prototype.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;
+};
+
+/***********************************************************************
+* The 205 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.togCOMPL will be set from
+ "complement" and this.CT is set from the final carry toggles of the addition.
+ Further, this.ADDER will still have a copy of the sign (11th) digit. Sets
+ the Forbidden-Combination stop if non-decimal digits are encountered, but
+ does not set the Overflow stop. */
+ 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 (CT 1)
+ var compl = complement || 0; // local copy of complement toggle
+ var ct = carry; // local copy of carry register (CT 1-16)
+ var dd; // current addend (d) digit;
+ var dm = d % 0x100000000000; // addend mantissa
+ var x; // digit counter
+
+ this.togADDER = 1; // for display only
+ this.togDPCTR = 1; // 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.stopForbidden = 1;
+ this.togCST = 1; // halt the processor
+ }
+
+ // add the digits plus carry, complementing as necessary
+ dd = dm % 0x10;
+ if (dd > 9) {
+ this.stopForbidden = 1;
+ this.togCST = 1; // halt the processor
+ }
+ if (compl) {
+ ad = 9-ad;
+ }
+ 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;
+ this.updateAdderGlow(adder, ct);
+
+ // 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
+ this.togCOMPL = compl;
+ this.CT = ct;
+ this.ADDER = adder;
+ 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 205, 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 205 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 205 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 205, 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.readMemoryFinish = function readMemoryFinish() {
+ /* Completes a read of the memory drum after an appropriate delay for drum
+ latency. Clears the memory control toggles and calls the current successor
+ function. Note: the word has already been stored in D by readMemory() */
+
+ this.scheduler = 0;
+ this.stopMemoryTiming();
+ this.successor();
+};
+
+/**************************************/
+B220Processor.prototype.readMemory = function readMemory(successor) {
+ /* Initiates a read of the memory drum at the BCD address specified by
+ C3-C6. The memory word is placed in the D register, and an appropriate
+ delay for drum latency, the successor function is called. Sets the memory
+ access toggles as necessary.
+ Note: the D register SHOULD be loaded after the latency delay, but we do
+ it here so that the word will show on the panels during the drum latency */
+ var addr; // binary target address, mod 8000
+ var cbCategory; // setCallback delay-averaging category
+ var drumTime = // current drum position [word-times]
+ performance.now()*B220Processor.wordsPerMilli;
+ var latency; // drum latency in word-times
+ var trackSize; // words/track in target band of drum
+
+ addr = B220Processor.bcdBinary(this.CADDR % 0x8000);
+ this.memACCESS = 1;
+ if (addr < 4000) {
+ trackSize = B220Processor.trackSize;
+ cbCategory = "MEMM";
+ this.memMAIN = this.memLM = 1;
+ this.D = this.MM[addr];
+ } else {
+ trackSize = B220Processor.loopSize;
+ cbCategory = "MEML";
+ if (addr < 5000) {
+ this.memL4 = 1;
+ this.D = this.L4[addr%trackSize];
+ } else if (addr < 6000) {
+ this.memL5 = 1;
+ this.D = this.L5[addr%trackSize];
+ } else if (addr < 7000) {
+ this.memL6 = 1;
+ this.D = this.L6[addr%trackSize];
+ } else {
+ this.memL7 = 1;
+ this.D = this.L7[addr%trackSize];
+ }
+ }
+
+ latency = (addr%trackSize - drumTime%trackSize + trackSize)%trackSize;
+ this.procTime += latency+1; // emulated time at end of drum access
+ this.memACTION = 1;
+ this.startMemoryTiming(drumTime);
+ this.successor = successor;
+ this.scheduler = setCallback(cbCategory, this,
+ (this.procTime-drumTime)/B220Processor.wordsPerMilli, this.readMemoryFinish);
+};
+
+/**************************************/
+B220Processor.prototype.writeMemoryFinish = function writeMemoryFinish(clearA) {
+ /* Completes a write of the memory drum after an appropriate delay for drum
+ latency. A has already been moved to the memory word, so just clears the
+ memory control toggles, and calls the current successor function */
+
+ this.scheduler = 0;
+ this.stopMemoryTiming();
+ if (clearA) {
+ this.A = 0;
+ }
+ this.successor();
+};
+
+/**************************************/
+B220Processor.prototype.writeMemory = function writeMemory(successor, clearA) {
+ /* Initiates a write of the memory drum at the BCD address specified by C3-C6.
+ After an appropriate delay for drum latency, the word in A is stored on the drum
+ and the successor function is called. Sets the memory access toggles as necessary.
+ If clearA is truthy, the A register is cleared at the completion of the write.
+ Note: the word should be stored after the latency delay, but we do it here
+ so that the word will show in the panels during the drum latency */
+ var addr; // binary target address, mod 8000
+ var cbCategory; // setCallback delay-averaging category
+ var drumTime = // current drum position [word-times]
+ performance.now()*B220Processor.wordsPerMilli;
+ var latency; // drum latency in word-times
+ var trackSize; // words/track in target band of drum
+
+ addr = B220Processor.bcdBinary(this.CADDR % 0x8000);
+ this.memACCESS = 1;
+ if (addr < 4000) {
+ trackSize = B220Processor.trackSize;
+ cbCategory = "MEMM";
+ this.memMAIN = this.memLM = this.memRWM = 1;
+ this.MM[addr] = this.A;
+ } else {
+ trackSize = B220Processor.loopSize;
+ cbCategory = "MEML";
+ this.memRWL = 1;
+ if (addr < 5000) {
+ this.memL4 = 1;
+ this.L4[addr%trackSize] = this.A;
+ } else if (addr < 6000) {
+ this.memL5 = 1;
+ this.L5[addr%trackSize] = this.A;
+ } else if (addr < 7000) {
+ this.memL6 = 1;
+ this.L6[addr%trackSize] = this.A;
+ } else {
+ this.memL7 = 1;
+ this.L7[addr%trackSize] = this.A;
+ }
+ }
+
+ latency = (addr%trackSize - drumTime%trackSize + trackSize)%trackSize;
+ this.procTime += latency+1; // emulated time at end of drum access
+ this.memACTION = 1;
+ this.startMemoryTiming(drumTime);
+ this.successor = successor;
+ this.scheduler = setCallback(cbCategory, this,
+ (this.procTime-drumTime)/B220Processor.wordsPerMilli, this.writeMemoryFinish, clearA);
+};
+
+/**************************************/
+B220Processor.prototype.blockFromLoop = function blockFromLoop(loop, successor) {
+ /* Copies 20 words from the designated loop to main memory at the BCD
+ address specified by C3-C6. After an appropriate delay for drum latency,
+ the successor function is called. Sets the memory access toggles as
+ necessary.
+ Note: the words SHOULD be copied at the END of the drum latency delay,
+ but we do it here, so that the updated registers will show on the panels
+ during the delay for drum latency */
+ var addr; // main binary address, mod 4000
+ var drumTime = // current drum position [word-times]
+ performance.now()*B220Processor.wordsPerMilli;
+ var latency; // drum latency in word-times
+ var loopMem; // reference to the loop memory array
+ var x; // iteration control
+
+ addr = B220Processor.bcdBinary(this.CADDR % 0x4000);
+ this.memACCESS = 1;
+ this.memMAIN = this.memLM = this.memRWM = this.memWDBL = 1;
+ switch (loop) {
+ case 4:
+ this.memL4 = 1;
+ loopMem = this.L4;
+ break;
+ case 5:
+ this.memL5 = 1;
+ loopMem = this.L5;
+ break;
+ case 6:
+ this.memL6 = 1;
+ loopMem = this.L6;
+ break;
+ case 7:
+ this.memL7 = 1;
+ loopMem = this.L7;
+ break;
+ } // switch loop
+
+ for (x=B220Processor.loopSize; x>0; --x) {
+ this.MM[addr] = loopMem[addr%B220Processor.loopSize];
+ addr = (addr+1) % 4000; // handle main memory address wraparound
+ }
+
+ /* According to TM4001, the memory control circuits added 200 to the C-register
+ address, but only if the blocking operation crossed a main-memory track boundary.
+ The Burroughs 205 Handbook of Operating Procedures (Bulletin 2034-A, Rev June 1960),
+ however, indicates that for systems equipped with magnetic tape, blocking operations
+ added 20 to the address field in C (see paragraph 3-19 on page 3-2). We implement
+ the magnetic tape variant */
+
+ this.CADDR = this.bcdAdd(this.CADDR, 0x20)%0x10000;
+ this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
+
+ latency = (addr%B220Processor.trackSize - drumTime%B220Processor.trackSize +
+ B220Processor.trackSize)%B220Processor.trackSize;
+ this.procTime += latency+B220Processor.loopSize; // emulated time at end of drum access
+ this.memACTION = 1;
+ this.startMemoryTiming(drumTime);
+ this.successor = successor;
+ this.scheduler = setCallback("MEMM", this,
+ (this.procTime-drumTime)/B220Processor.wordsPerMilli, this.writeMemoryFinish, false);
+};
+
+/**************************************/
+B220Processor.prototype.blockToLoop = function blockToLoop(loop, successor) {
+ /* Copies 20 words from the main memory at the BCD address specified by
+ C3-C6 to the designated loop. After an appropriate delay for drum latency,
+ the successor function is called. Sets the memory access toggles as
+ necessary.
+ Note 1: Knuth's MEASY assembler and the Shell assembler both use instructions
+ of the form BTn 8000. Normally, this would mean an effective address of 0000,
+ but considering the special use of addresses >= 8000 for magnetic tape
+ instructions, and the apparent intention of this use in both assemblers,
+ we conclude that BTn with the high-order bit of the address set causes 20
+ words of zeroes to be written to the designated loop.
+ Note 2: the words SHOULD be copied at the END of the drum latency delay,
+ but we do it here, so that the updated registers will show on the panels
+ during the delay for drum latency */
+ var addr; // main binary address, mod 4000
+ var drumTime = // current drum position [word-times]
+ performance.now()*B220Processor.wordsPerMilli;
+ var latency; // drum latency in word-times
+ var loopMem; // reference to the loop memory array
+ var x; // iteration control
+
+ addr = B220Processor.bcdBinary(this.CADDR);
+ this.memACCESS = 1;
+ this.memLM = this.memRWL = this.memWDBL = 1;
+ if (addr < 8000) {
+ addr %= 4000;
+ this.memMAIN = 1;
+ }
+ switch (loop) {
+ case 4:
+ this.memL4 = 1;
+ loopMem = this.L4;
+ break;
+ case 5:
+ this.memL5 = 1;
+ loopMem = this.L5;
+ break;
+ case 6:
+ this.memL6 = 1;
+ loopMem = this.L6;
+ break;
+ case 7:
+ this.memL7 = 1;
+ loopMem = this.L7;
+ break;
+ } // switch loop
+
+ if (addr >= 8000) {
+ for (x=B220Processor.loopSize; x>0; --x) {
+ loopMem[x] = 0;
+ }
+ } else {
+ for (x=B220Processor.loopSize; x>0; --x) {
+ loopMem[addr%B220Processor.loopSize] = this.MM[addr];
+ addr = (addr+1) % 4000; // handle main memory address wraparound
+ }
+ }
+
+ // See the comment on C address field adjustment at this point in blockFromLoop()
+ this.CADDR = this.bcdAdd(this.CADDR, 0x20)%0x10000;
+ this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
+
+ latency = (addr%B220Processor.trackSize - drumTime%B220Processor.trackSize +
+ B220Processor.trackSize)%B220Processor.trackSize;
+ this.procTime += latency+B220Processor.loopSize; // emulated time at end of drum access
+ this.memACTION = 1;
+ this.startMemoryTiming(drumTime);
+ this.successor = successor;
+ this.scheduler = setCallback("MEMM", this,
+ (this.procTime-drumTime)/B220Processor.wordsPerMilli, this.writeMemoryFinish, false);
+};
+
+/**************************************/
+B220Processor.prototype.searchMemoryFinish = function searchMemoryFinish() {
+ /* Handles completion of a search memory operation and resets the memory
+ toggle and timing state */
+
+ this.stopMemoryTiming();
+ this.executionComplete();
+};
+
+/**************************************/
+B220Processor.prototype.searchMemory = function searchMemory(high) {
+ /* Searches memory from the current operand address in this.CADDR for a
+ word equal to the value in the A register (high=false) or greater than or
+ equal to the value in the A register (high=true). Comparisons are based on
+ the absolute values of both the word in A and the word in memory, ignoring
+ the sign. If a match is found, load the memory word into the A register and
+ set the upper 4 digits of R to the memory address where found. If address
+ 3999 is reached and the word is still not found, terminate with Overflow set.
+
+ This instruction searches one word per word-time, so total execution time is
+ drum latency to the initial address plus the number of words searched.
+ This function is called after the initial memory word has been fetched into
+ the D register by executeWithOperand(), so the latency delay is already
+ accounted for. It will proceed to search up to 200 words (one drum track) at
+ a time, then if no match is found, perform a catch-up delay. A final partial
+ delay is performed at the end of the search.
+
+ Note: the MSH (85) and MSE (88) instructions apparently were a custom
+ modification in 1959 for a 205 to be delivered to the Eaton Manufacturing
+ Company. A description of these two instructions was found in the back of
+ the 205 Central Computer Handbook included with Donald Knuth's papers donated
+ to the Computer History Museum in Mountain View, California */
+ var addr; // main binary address, mod 4000
+ var aWord = this.A % 0x10000000000; // search target word
+ var drumTime; // current drum position [word-times]
+ var dWord; // result of comparison D:A
+ var found = false; // true if matching word found
+ var x = 0; // iteration control
+
+ if (!this.memACCESS) {
+ this.memACCESS = this.memACTION = this.memMAIN = this.memLM = 1;
+ this.startMemoryTiming(performance.now()*B220Processor.wordsPerMilli);
+ }
+
+ addr = B220Processor.bcdBinary(this.CADDR % 0x4000);
+ do {
+ dWord = this.bcdAdd(aWord, this.D % 0x10000000000, 1, 1); // effectively abs(D)-abs(A)
+ if (dWord == 0) {
+ found = true;
+ break; // out of do loop -- match equal
+ } else if (high && dWord < 0x10000000000) {
+ found = true;
+ break; // out of do loop -- match high
+ } else if (addr < 3999) {
+ this.D = this.MM[++addr];
+ } else {
+ this.setOverflow(1);
+ break; // out of do loop -- end of main memory
+ }
+ } while (++x < B220Processor.trackSize);
+
+ this.CADDR = B220Processor.binaryBCD(addr);
+ this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
+ if (found) {
+ this.A = this.D;
+ this.R = this.CADDR*0x1000000 + this.R%0x1000000;
+ this.successor = this.searchMemoryFinish;
+ } else if (this.stopOverflow) {
+ this.successor = this.searchMemoryFinish;
+ } else {
+ this.successor = searchMemory;
+ }
+
+ this.procTime += x;
+ drumTime = performance.now()*B220Processor.wordsPerMilli;
+ this.scheduler = setCallback("MEML", this,
+ (this.procTime-drumTime)/B220Processor.wordsPerMilli, this.successor, high);
+};
+
+/***********************************************************************
+* 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.executeComplete();
+ }
+};
+
+/**************************************/
+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.executeComplete();
+ }
+};
+
+/***********************************************************************
+* 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.executeComplete();
+ }
+};
+
+/**************************************/
+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.executeComplete();
+ } 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.executeComplete();
+ }
+};
+
+/**************************************/
+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, executeComplete()
+ 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.executeComplete();
+ }
+ } 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=0; --x) {
+ d = (w - w%0x1000000000)/0x1000000000;
+ w = (w%0x1000000000)*0x10;
+ switch (d%0x04) {
+ case 1: // open the switch
+ this.externalSwitch[x] = 0;
+ break;
+ case 2: // close the switch
+ this.externalSwitch[x] = 1;
+ break;
+ case 3: // complement the switch
+ this.externalSwitch[x] = 1 - (this.externalSwitch[x] % 0x01);
+ break;
+ } // switch
+ } // for x
+};
+
+
+/***********************************************************************
+* Fetch Module *
+***********************************************************************/
+
+/**************************************/
+B220Processor.prototype.transferDtoC = function transferDtoC() {
+ /* Implements the D-to-C portion of the fetch cycle. Shifts the operand
+ address in C3-C6 to C7-C10, incrementing that address by one. Shifts the
+ low-order six digits of D into C1-C6, applying B register modification if
+ the D sign bit is 1. Leaves D shifted six digits to the right.
+ Note that during B modification, overflow may occur from the operand address
+ into the opcode digits coming from D, which on the 205 could be either a
+ feature or a bug, depending on how well you were paying attention */
+ var addr = this.C % 0x100000000; // operand address digits from C
+ var ctl = addr % 0x10000; // control address digits from C
+
+ this.SHIFTCONTROL = 0x07; // for display only
+ this.togADDAB = 1; // for display only
+ this.togCLEAR = // determine D sign for B modification
+ 1-(((this.D - this.D%0x10000000000)/0x10000000000) & 0x01);
+
+ addr = (addr-ctl)/0x10000; // extract the actual address digits
+ this.CCONTROL = this.bcdAdd(addr, 0x0001) % 0x10000;// bump the control address modulo 10000
+
+ addr = this.D % 0x1000000; // get the opcode and address digits from D
+ this.CEXTRA = this.D = (this.D - addr)/0x1000000; // shift D right six digits & save what's left
+ if (this.togCLEAR) { // check for B modification
+ addr = this.bcdAdd(addr, 0); // if no B mod, add 0 (this is actually the way it worked)
+ } else {
+ addr = this.bcdAdd(addr, this.B) % 0x1000000; // otherwise, add B to the operand address
+ }
+
+ this.SHIFT = 0x19; // for display only
+ this.C = addr*0x10000 + this.CCONTROL; // put C back together
+ this.CADDR = addr % 0x10000;
+ this.COP = (addr - this.CADDR)/0x10000;
+};
+
+/**************************************/
+B220Processor.prototype.fetchComplete = function fetchComplete() {
+ /* Second phase of the Fetch cycle, called after the word is read from
+ memory into D */
+ var breakDigit; // breakpoint digit from D4
+
+ this.togSIGN = ((this.A - this.A%0x10000000000)/0x10000000000) & 0x01;
+ this.transferDtoC();
+ this.procTime += 4; // minimum alpha for all orders is 4
+ breakDigit = this.CEXTRA % 0x10;
+ if (this.cswBreakpoint & breakDigit) { // check for breakpoint stop
+ this.togBKPT = 1; // prepare to halt after this instruction
+ }
+
+ if (this.cswSkip && (breakDigit & 0x08)) {
+ if (!this.sswLockNormal) {
+ this.setTimingToggle(1); // stay in Fetch to skip this instruction
+ }
+ if (breakDigit & 0x01) {
+ this.togCST = 1; // halt the processor
+ }
+ }
+
+ // If we're not halted and either console has started in Continuous mode, continue
+ if (this.togCST || !(this.sswStepContinuous || this.cctContinuous)) {
+ this.stop();
+ } else if (this.togTiming) {
+ this.fetch(); // once more with feeling
+ } else {
+ this.execute(); // execute the instruction just fetched
+ }
+};
+
+/**************************************/
+B220Processor.prototype.fetch = function fetch() {
+ /* Implements the Fetch cycle of the 205 processor. This is initiated either
+ by pressing START on one of the consoles with the Timing Toggle=1 (Fetch),
+ or by the prior operation complete if the processor is in continuous mode */
+
+ this.CADDR = this.C % 0x100000000; // C operand and control addresses
+ this.CCONTROL = this.CADDR % 0x10000; // C control address
+ this.COP = (this.C - this.CADDR)/0x100000000; // C operation code
+ this.CADDR = (this.CADDR - this.CCONTROL)/0x10000;
+
+ this.togT0 = 0; // for display only, leave it off for fetch cycle
+ if (this.togSTART) {
+ if (this.tog3IO) {
+ this.cardatronInputWord(); // we're still executing a Cardatron input command
+ } else {
+ this.consoleInputDigit(); // we're still executing a Console input command
+ }
+ } else {
+ this.setTimingToggle(0); // next cycle will be Execute by default
+ this.SHIFT = 0x15; // for display only
+ this.SHIFTCONTROL = 0x05; // for display only
+ // shift control address into operand address and initiate read
+ this.CADDR = this.CCONTROL;
+ this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
+ this.readMemory(this.fetchComplete); // load D from the operand address
+ }
+};
+
+/***********************************************************************
+* Execute Module *
+***********************************************************************/
+
+/**************************************/
+B220Processor.prototype.executeComplete = function executeComplete() {
+ /* Implements Operation Complete (O.C.) for the Execute cycle. Determines
+ if there is a stop or alarm condition, otherwise determines whether to
+ do a Fetch or Execute cycle next. We should clear the control toggles here,
+ but leave them so they'll display during the fetch latency delay. They
+ will be cleared in the next call to execute() */
+
+ if (this.togBKPT) {
+ this.togCST = 1;
+ this.stopBreakpoint = 1; // turn on BKPT lamp
+ }
+
+ if (this.togCST) {
+ this.stop();
+ } else if (!(this.sswStepContinuous || this.cctContinuous)) {
+ this.stop();
+ } else if (this.togTiming) {
+ this.fetch(); // once more with feeling
+ } else {
+ this.execute(); // execute the instruction currently in C
+ }
+};
+
+/**************************************/
+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, this.bitFlip(this.D, 40));
+ this.D = 0;
+ break;
+
+ case 0x66: //---------------- CADA Clear and Add Absolute
+ this.procTime += 3;
+ this.A = this.bcdAdd(0, this.bitReset(this.D, 40));
+ this.D = 0;
+ break;
+
+ case 0x67: //---------------- CSUA Clear and Subtract Absolute
+ this.procTime += 3;
+ this.A = this.bcdAdd(0, this.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 = this.bitFlip(this.D, 40); // complement the D sign
+ this.integerAdd();
+ break;
+
+ case 0x76: //---------------- ADA Add Absolute
+ this.procTime += 3;
+ this.D = this.bitReset(this.D, 40); // clear the D sign
+ this.integerAdd();
+ break;
+
+ case 0x77: //---------------- SUA Subtract Absolute
+ this.procTime += 3;
+ this.D = this.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 executeComplete()
+ break;
+
+ // 0x86: //---------------- (no op)
+
+ case 0x87: //---------------- MSE Memory Search Equal (Eaton CER)
+ this.searchMemory(false);
+ return; // avoid the executeComplete()
+ 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
+
+ this.executeComplete();
+};
+
+/**************************************/
+B220Processor.prototype.execute = function execute() {
+ /* Implements the Execute cycle of the 205 processor. This is initiated either
+ by pressing START on one of the consoles with the Timing Toggle=0 (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 % 0x100000000; // C register operand and control addresses
+ this.CCONTROL = w % 0x10000; // C register control address
+ this.COP = (this.C - w)/0x100000000; // C register operation code
+ this.CADDR = (w - this.CCONTROL)/0x10000; // C register operand address
+
+ this.togZCT = 0; // for display only
+ this.togT0 = 1; // for display only, leave it on for execute cycle
+ if (!this.sswLockNormal) {
+ this.setTimingToggle(1); // next cycle will be Fetch by default
+ }
+
+ if ((this.COP & 0xF8) == 0x08) { // if STOP (08) operator or
+ this.togCST = 1; // halt the processor
+ this.stopControl = 1; // turn on CONTROL lamp
+ this.executeComplete();
+ } else if (this.stopOverflow && (this.COP & 0x08) == 0) { // overflow and op is not a CC
+ this.togCST = 1; // halt the processor
+ this.stopControl = 1; // turn on CONTROL lamp
+ this.executeComplete();
+ } else if (this.COP >= 0x60) { // if operator requires an operand
+ ++this.procTime; // minimum alpha for Class II is 5 word-times
+ this.clearControlToggles();
+ this.readMemory(this.executeWithOperand);
+ } else { // otherwise execute a non-operand instruction
+ this.clearControlToggles();
+ this.procTime += 3; // minimum Class I execution is 3 word-times
+
+ switch (this.COP) {
+ 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;
+ this.executeComplete();
+ break;
+
+ case 0x02: //---------------- STC Store and Clear A
+ this.procTime += 1;
+ this.writeMemory(this.executeComplete, true);
+ break;
+
+ case 0x03: //---------------- PTW Paper-tape/Flexowriter write
+ if (this.cswPOSuppress) {
+ this.executeComplete(); // ignore printout commands
+ } else if (this.cswOutput == 0) {
+ this.togCST = 1; // halt if Output switch is OFF
+ this.executeComplete();
+ } 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.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
+ }
+ this.executeComplete();
+ break;
+
+ // 0x05: //---------------- (no op)
+
+ case 0x06: //---------------- UA Unit Adjust
+ this.procTime += 2;
+ if (this.A % 2 == 0) {
+ ++this.A;
+ }
+ this.executeComplete();
+ break;
+
+ case 0x07: //---------------- PTWF Paper-tape Write Format
+ if (this.cswPOSuppress) {
+ this.executeComplete(); // ignore printout commands
+ } else if (this.cswOutput == 0) {
+ this.togCST = 1; // halt if Output switch is OFF
+ this.executeComplete();
+ } 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.executeComplete();
+ break;
+
+ case 0x12: //---------------- ST Store A
+ this.procTime += 1;
+ this.writeMemory(this.executeComplete, 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
+ this.executeComplete();
+ 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
+ this.executeComplete();
+ 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.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
+ }
+ this.executeComplete();
+ break;
+
+ case 0x16: //---------------- ADSC Add Special Counter to A
+ this.procTime += 3;
+ this.D = this.SPECIAL;
+ this.integerAdd();
+ this.executeComplete();
+ break;
+
+ case 0x17: //---------------- SUSC Subtract Special Counter from A
+ this.procTime += 3;
+ this.D = this.SPECIAL + 0x10000000000; // set to negative
+ this.integerAdd();
+ this.executeComplete();
+ 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.CCONTROL = this.CADDR; // copy address to control counter
+ this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
+ this.executeComplete();
+ 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;
+ this.executeComplete();
+ 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;
+ this.executeComplete();
+ 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
+ this.executeComplete();
+ 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.executeComplete);
+ 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;
+ }
+ this.executeComplete();
+ 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;
+ }
+ this.executeComplete();
+ 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.executeComplete);
+ 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.executeComplete);
+ 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;
+ this.executeComplete();
+ break;
+
+ case 0x33: //---------------- CR Clear R
+ this.procTime += 2;
+ this.SHIFTCONTROL = 0x04; // for display only
+ this.R = 0;
+ this.executeComplete();
+ 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.executeComplete);
+ 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
+ this.executeComplete();
+ } 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.executeComplete);
+ }
+ 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
+ this.executeComplete();
+ } 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.executeComplete);
+ }
+ break;
+
+ case 0x40: //---------------- MTR Magnetic Tape Read
+ if (!this.magTape) {
+ this.executeComplete();
+ } 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.executeComplete();
+ }
+ }
+ 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.executeComplete();
+ break;
+
+ // 0x43: //---------------- (no op)
+
+ case 0x44: //---------------- CDR Card Read (Cardatron)
+ this.D = 0;
+ if (!this.cardatron) {
+ this.executeComplete();
+ } 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.executeComplete();
+ break;
+
+ // 0x46-0x47: //---------------- (no op)
+
+ case 0x48: //---------------- CDRF Card Read Format
+ if (!this.cardatron) {
+ this.executeComplete();
+ } 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.executeComplete();
+ } 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.executeComplete();
+ }
+ }
+ 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
+ }
+ }
+ this.executeComplete();
+ break;
+
+ // 0x53: //---------------- (no op)
+
+ case 0x54: //---------------- CDW Card Write (Cardatron)
+ if (!this.cardatron) {
+ this.executeComplete();
+ } 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;
+ }
+ this.executeComplete();
+ break;
+
+ // 0x56-0x57: //---------------- (no op)
+
+ case 0x58: //---------------- CDWF Card Write Format
+ if (!this.cardatron) {
+ this.executeComplete();
+ } 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 -- no op)
+ this.executeComplete();
+ break;
+ } // switch this.COP
+ }
+};
+
+/**************************************/
+B220Processor.prototype.start = function start() {
+ /* Initiates the processor according to the current Timing Toggle state */
+ var drumTime = performance.now()*B220Processor.wordsPerMilli;
+
+ if (this.togCST && this.poweredOn) {
+ this.procStart = drumTime;
+ this.procTime = drumTime;
+ this.memoryStopTime = drumTime;
+
+ this.stopIdle = 0;
+ this.stopControl = 0;
+ this.stopBreakpoint = this.togBKPT = 0;
+ // stopOverflow, stopSector, and stopForbidden are not reset by the START button
+
+ this.togCST = 0;
+ if (this.togTiming && !this.sswLockNormal) {
+ this.fetch();
+ } else {
+ this.setTimingToggle(0);
+ this.execute();
+ }
+ }
+};
+
+/**************************************/
+B220Processor.prototype.stop = function stop() {
+ /* Stops running the processor on the Javascript thread */
+
+ if (this.poweredOn) {
+ this.togCST = 1;
+ this.cctContinuous = 0;
+ this.stopIdle = 1; // turn on the IDLE lamp
+ if (this.scheduler) {
+ clearCallback(this.scheduler);
+ this.scheduler = 0;
+ }
+ }
+};
+
+/**************************************/
+B220Processor.prototype.inputSetup = function inputSetup(unitNr) {
+ /* Called from the Cardatron Control Unit. If the Processor is stopped,
+ loads a CDR (44) instruction into C for unit "unitNr" */
+
+ if (this.togCST && this.poweredOn) {
+ this.CEXTRA = unitNr*0x10;
+ this.COP = 0x44;
+ this.CADDR = 0;
+ this.C = (this.COP*0x10000 + this.CADDR)*0x10000 + this.CCONTROL;
+ this.setTimingToggle(0);
+ }
+};
+
+/**************************************/
+B220Processor.prototype.powerUp = function powerUp() {
+ /* Powers up the system */
+
+ if (!this.poweredOn) {
+ this.clear();
+ this.poweredOn = 1;
+ this.console = this.devices.ControlConsole;
+ this.cardatron = this.devices.CardatronControl;
+ this.magTape = this.devices.MagTapeControl;
+ this.lastGlowTime = performance.now()*B220Processor.wordsPerMilli;
+ this.glowTimer = setInterval(this.boundUpdateLampGlow, B220Processor.lampGlowInterval);
+ }
+};
+
+/**************************************/
+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
+};
\ No newline at end of file
diff --git a/index.html b/index.html
index 01ef268..91fe71c 100644
--- a/index.html
+++ b/index.html
@@ -31,11 +31,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/webUI/B220ControlConsole.js b/webUI/B220ControlConsole.js
new file mode 100644
index 0000000..fc55def
--- /dev/null
+++ b/webUI/B220ControlConsole.js
@@ -0,0 +1,603 @@
+/***********************************************************************
+* retro-220/webUI B220ControlConsole.js
+************************************************************************
+* Copyright (c) 2017, Paul Kimpel.
+* Licensed under the MIT License, see
+* http://www.opensource.org/licenses/mit-license.php
+************************************************************************
+* Burroughs 220 Emulator Control Console object.
+************************************************************************
+* 2017-01-01 P.Kimpel
+* Original version, from D205SupervisoryPanel.js.
+***********************************************************************/
+"use strict";
+
+/**************************************/
+function B220ControlConsole(p, systemShutdown) {
+ /* Constructor for the ControlConsole object */
+ var h = 584;
+ var w = 1064;
+ var mnemonic = "ControlConsole";
+
+ this.config = p.config; // System Configuration object
+ this.intervalToken = 0; // setInterval() token for panel refresh
+ this.timerBase = performance.now(); // starting value of Interval Timer
+ this.timerValue = 0; // current value of Interval Timer
+ this.p = p; // B220Processor object
+ this.systemShutdown = systemShutdown; // system shut-down callback
+
+ this.boundLamp_Click = B220Util.bindMethod(this, B220ControlConsole.prototype.lamp_Click);
+ this.boundPowerBtn_Click = B220Util.bindMethod(this, B220ControlConsole.prototype.powerBtn_Click);
+ this.boundClear_Click = B220Util.bindMethod(this, B220ControlConsole.prototype.clear_Click);
+ this.boundFlipSwitch = B220Util.bindMethod(this, B220ControlConsole.prototype.flipSwitch);
+ this.boundStartBtn_Click = B220Util.bindMethod(this, B220ControlConsole.prototype.startBtn_Click);
+ this.boundResetTimer = B220Util.bindMethod(this, B220ControlConsole.prototype.resetTimer);
+ this.boundUpdatePanel = B220Util.bindMethod(this, B220ControlConsole.prototype.updatePanel);
+
+ this.doc = null;
+ this.window = window.open("../webUI/B220ControlConsole.html", mnemonic,
+ "location=no,scrollbars,resizable,width=" + w + ",height=" + h +
+ ",top=0,left=" + (screen.availWidth - w));
+ this.window.addEventListener("load",
+ B220Util.bindMethod(this, B220ControlConsole.prototype.consoleOnLoad));
+}
+
+/**************************************/
+B220ControlConsole.displayRefreshPeriod = 50; // milliseconds
+B220ControlConsole.offSwitchClass = "./resources/ToggleDown.png";
+B220ControlConsole.onSwitchClass = "./resources/ToggleUp.png";
+
+/**************************************/
+B220ControlConsole.prototype.$$ = function $$(e) {
+ return this.doc.getElementById(e);
+};
+
+/**************************************/
+B220ControlConsole.prototype.powerOnSystem = function powerOnSystem() {
+ /* Powers on the system */
+
+ if (!this.p.poweredOn) {
+ this.p.powerUp();
+ this.powerLamp.set(1);
+ this.window.focus();
+ if (!this.intervalToken) {
+ this.intervalToken = this.window.setInterval(this.boundUpdatePanel, B220ControlConsole.displayRefreshPeriod);
+ }
+ }
+};
+
+/**************************************/
+B220ControlConsole.prototype.powerOffSystem = function powerOffSystem() {
+ /* Powers off the system */
+
+ if (this.p.poweredOn) {
+ this.systemShutdown();
+ this.powerLamp.set(0);
+ if (this.intervalToken) { // if the display auto-update is running
+ this.window.clearInterval(this.intervalToken); // kill it
+ this.intervalToken = 0;
+ }
+ }
+};
+
+/**************************************/
+B220ControlConsole.prototype.beforeUnload = function beforeUnload(ev) {
+ var msg = "Closing this window will make the panel unusable.\n" +
+ "Suggest you stay on the page and minimize this window instead";
+
+ ev.preventDefault();
+ ev.returnValue = msg;
+ return msg;
+};
+
+/**************************************/
+B220ControlConsole.prototype.displayCallbackState = function displayCallbackState() {
+ /* Builds a table of outstanding callback state */
+ var cb;
+ var cbs;
+ var e;
+ var body = document.createElement("tbody");
+ var oldBody = this.$$("CallbackBody");
+ var row;
+ var state = getCallbackState(0x03);
+ var token;
+
+ cbs = state.delayDev;
+ for (token in cbs) {
+ row = document.createElement("tr");
+
+ e = document.createElement("td");
+ e.appendChild(document.createTextNode(token));
+ row.appendChild(e);
+
+ e = document.createElement("td");
+ e.appendChild(document.createTextNode((cbs[token]||0).toFixed(2)));
+ row.appendChild(e);
+
+ e = document.createElement("td");
+ e.colSpan = 2;
+ row.appendChild(e);
+ body.appendChild(row);
+ }
+
+ cbs = state.pendingCallbacks;
+ for (token in cbs) {
+ cb = cbs[token];
+ row = document.createElement("tr");
+
+ e = document.createElement("td");
+ e.appendChild(document.createTextNode(token.toString()));
+ row.appendChild(e);
+
+ e = document.createElement("td");
+ e.appendChild(document.createTextNode(cb.delay.toFixed(2)));
+ row.appendChild(e);
+
+ e = document.createElement("td");
+ e.appendChild(document.createTextNode((cb.context && cb.context.mnemonic) || "??"));
+ row.appendChild(e);
+
+ e = document.createElement("td");
+ e.appendChild(document.createTextNode((cb.args ? cb.args.length : 0).toString()));
+ row.appendChild(e);
+ body.appendChild(row);
+ }
+
+ body.id = oldBody.id;
+ oldBody.parentNode.replaceChild(body, oldBody);
+};
+
+/**************************************/
+B220ControlConsole.prototype.updatePanel = function updatePanel() {
+ /* Updates the panel from the current Processor state */
+ var eLevel;
+ var p = this.p; // local copy of Processor object
+ var stamp = performance.now();
+ var text;
+ var tg = p.toggleGlow;
+
+ // This needs to be done only if the Processor is in RUN status.
+ this.timerValue = stamp - this.timerBase;
+ text = (this.timerValue/1000 + 10000).toFixed(1);
+ this.intervalTimer.textContent = text.substring(text.length-6);
+
+ return; /////////////////// DEBUG ///////////////////////////////////////////////////////
+
+ eLevel = (p.stopIdle ? p.togTiming : tg.glowTiming);
+
+ this.regA.updateGlow(tg.glowA);
+ this.regB.updateGlow(tg.glowB);
+ this.regC.updateGlow(tg.glowC);
+ this.regD.updateGlow(tg.glowD);
+ this.regR.updateGlow(tg.glowR);
+ this.control.updateGlow(tg.glowCtl);
+
+ this.regAdder.updateGlow(tg.glowADDER);
+ this.regCarry.updateGlow(tg.glowCT);
+
+ this.cardatronTWA.set(tg.glowTWA);
+ this.cardatron3IO.set(tg.glow3IO);
+
+ this.overflowLamp.set(p.poweredOn && tg.glowOverflow);
+ this.sectorLamp.set(p.stopSector);
+ this.fcLamp.set(p.stopForbidden);
+ this.controlLamp.set(p.stopControl);
+ this.idleLamp.set(p.poweredOn && p.stopIdle);
+
+ this.executeLamp.set(p.poweredOn && (1-eLevel));
+ this.fetchLamp.set(p.poweredOn && eLevel);
+
+ this.mainLamp.set(tg.glowMAIN);
+ this.rwmLamp.set(tg.glowRWM);
+ this.rwlLamp.set(tg.glowRWL);
+ this.wdblLamp.set(tg.glowWDBL);
+ this.actLamp.set(tg.glowACTION);
+ this.accessLamp.set(tg.glowACCESS);
+ this.lmLamp.set(tg.glowLM);
+ this.l4Lamp.set(tg.glowL4);
+ this.l5Lamp.set(tg.glowL5);
+ this.l6Lamp.set(tg.glowL6);
+ this.l7Lamp.set(tg.glowL7);
+
+ /********** DEBUG **********
+ this.$$("ProcDelta").value = p.procSlackAvg.toFixed(2);
+ this.$$("LastLatency").value = p.delayDeltaAvg.toFixed(2);
+ this.displayCallbackState();
+ ***************************/
+};
+
+/**************************************/
+B220ControlConsole.prototype.lamp_Click = function lamp_Click(ev) {
+ /* Handles the click event within panels. Determines which lamp element was
+ clicked, flips the state of the corresponding toggle in the Processor, and
+ refreshes the lamp element */
+ var bit; // bit number extracted from the id
+ var id = ev.target.id; // id of the element clicked
+ var ix = id.indexOf("_"); // offset of the "_" delimiter in the id
+ var p = this.p; // local copy of processor object
+ var reg; // register prefix from id
+
+ if (p.poweredOn) {
+ if (ix < 0) {
+ reg = id;
+ bit = 0;
+ } else if (ix > 0) {
+ reg = id.substring(0, ix);
+ bit = parseInt(id.substring(ix+1));
+ if (isNaN(bit)) {
+ bit = 0;
+ }
+ }
+
+ switch (reg) {
+ case "A":
+ p.A = p.bitFlip(p.A, bit);
+ this.regA.update(p.A);
+ break;
+ case "B":
+ p.B = p.bitFlip(p.B, bit);
+ this.regB.update(p.B);
+ break;
+ case "C":
+ p.C = p.bitFlip(p.C, bit);
+ this.regC.update(p.C);
+ break;
+ case "D":
+ p.D = p.bitFlip(p.D, bit);
+ this.regD.update(p.D);
+ break;
+ case "R":
+ p.R = p.bitFlip(p.R, bit);
+ this.regR.update(p.R);
+ break;
+ case "ADD":
+ p.ADDER = p.bitFlip(p.ADDER, bit);
+ this.regAdder.update(p.ADDER);
+ break;
+ case "CT":
+ p.CT = p.bitFlip(p.CT, bit);
+ this.regCarry.update(p.CT);
+ break;
+ case "TWA":
+ p.togTWA ^= 1;
+ this.cardatronTWA.set(p.togTWA);
+ break;
+ case "3IO":
+ p.tog3IO ^= 1;
+ this.cardatron3IO.set(p.tog3IO);
+ break;
+ case "CTL":
+ switch (bit) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ p.SHIFT = p.bitFlip(p.SHIFT, bit);
+ break;
+ case 5:
+ p.togMT1BV5 ^= 1;
+ break;
+ case 6:
+ p.togMT1BV4 ^= 1;
+ break;
+ case 7:
+ p.togMT3P ^= 1;
+ break;
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ p.SHIFTCONTROL = p.bitFlip(p.SHIFTCONTROL, bit-8);
+ break;
+ case 12:
+ p.togASYNC ^= 1;
+ break;
+ case 13:
+ p.togZCT ^= 1;
+ break;
+ case 14:
+ p.togBKPT ^= 1;
+ break;
+ case 15:
+ p.togT0 ^= 1;
+ break;
+ case 16:
+ p.togDELAY ^= 1;
+ break;
+ case 17:
+ p.togPO2 ^= 1;
+ break;
+ case 18:
+ p.togPO1 ^= 1;
+ break;
+ case 19:
+ p.togOK ^= 1;
+ break;
+ case 20:
+ p.togTC2 ^= 1;
+ break;
+ case 21:
+ p.togTC1 ^= 1;
+ break;
+ case 22:
+ p.togTF ^= 1;
+ break;
+ case 23:
+ p.togSTART ^= 1;
+ break;
+ case 24:
+ p.togSTEP ^= 1;
+ break;
+ case 25:
+ p.togDIVALARM ^= 1;
+ break;
+ case 26:
+ p.togCOUNT ^= 1;
+ break;
+ case 27:
+ p.togSIGN ^= 1;
+ break;
+ case 28:
+ p.togMULDIV ^= 1;
+ break;
+ case 29:
+ p.togCLEAR ^= 1;
+ break;
+ case 30:
+ p.togPLUSAB ^= 1;
+ break;
+ case 31:
+ p.togCOMPL ^= 1;
+ break;
+ case 32:
+ p.togDELTABDIV ^= 1;
+ break;
+ case 33:
+ p.togDPCTR ^= 1;
+ break;
+ case 34:
+ p.togADDER ^= 1;
+ break;
+ case 35:
+ p.togBTOAIN ^= 1;
+ break;
+ case 36:
+ case 37:
+ case 38:
+ case 39:
+ p.SPECIAL = p.bitFlip(p.SPECIAL, bit-36);
+ break;
+ } // switch bit
+
+ break;
+ } // switch reg
+ }
+
+ ev.preventDefault();
+ ev.stopPropagation();
+ return false;
+};
+
+/**************************************/
+B220ControlConsole.prototype.clear_Click = function Clear_Click(ev) {
+ /* Event handler for the various clear/reset buttons on the panel */
+
+ if (this.p.poweredOn) {
+ switch (ev.target.id) {
+ case "ClearBtn":
+ this.p.clear();
+ break;
+ case "ClearARegBtn":
+ this.p.A = 0;
+ break;
+ case "ClearBRegBtn":
+ this.p.B = 0;
+ break;
+ case "ClearCRegBtn":
+ this.p.C = 0;
+ break;
+ case "ClearDRegBtn":
+ this.p.D = 0;
+ break;
+ case "ClearRRegBtn":
+ this.p.R = 0;
+ break;
+ case "ClearControlBtn":
+ this.p.clearControl();
+ break;
+ case "ResetOverflowBtn":
+ this.p.setOverflow(0);
+ break;
+ case "ResetSectorBtn":
+ this.p.stopSector = 0;
+ break;
+ case "ResetControlBtn":
+ this.p.stopControl = 0;
+ break;
+ case "ExecuteBtn":
+ this.p.setTimingToggle(0);
+ break;
+ case "FetchBtn":
+ this.p.setTimingToggle(1 - this.p.sswLockNormal);
+ break;
+ }
+ this.updatePanel();
+ }
+ ev.preventDefault();
+ return false;
+};
+
+/**************************************/
+B220ControlConsole.prototype.powerBtn_Click = function powerBtn_Click(ev) {
+ /* Handler for the START button: begins execution for the current cycle */
+
+ switch(ev.target.id) {
+ case "PowerOnBtn":
+ this.powerOnSystem();
+ break;
+ case "PowerOffBtn":
+ this.powerOffSystem();
+ break;
+ }
+ this.updatePanel();
+ ev.preventDefault();
+ return false;
+};
+
+/**************************************/
+B220ControlConsole.prototype.startBtn_Click = function startBtn_Click(ev) {
+ /* Handler for the START button: begins execution for the current cycle */
+
+ this.p.start();
+ this.timerBase = performance.now() - this.timerValue;
+ this.updatePanel();
+ ev.preventDefault();
+ return false;
+};
+
+/**************************************/
+B220ControlConsole.prototype.resetTimer = function resetTimer(ev) {
+ /* Resets the Interval Timer display to 0000.0 */
+
+ this.timerBase = performance.now();
+ this.timerValue = 0;
+};
+
+/**************************************/
+B220ControlConsole.prototype.flipSwitch = function flipSwitch(ev) {
+ /* Handler for switch & knob clicks */
+
+ switch (ev.target.id) {
+ case "AudibleAlarmSwitch":
+ this.audibleAlarmSwitch.flip();
+ this.config.putNode("ControlConsole.audibleAlarmSwitch",
+ this.p.sswAudibleAlarm = this.audibleAlarmSwitch.state);
+ break;
+ case "LockNormalSwitch":
+ this.lockNormalSwitch.flip();
+ this.config.putNode("ControlConsole.lockNormalSwitch",
+ this.p.sswLockNormal = this.lockNormalSwitch.state);
+ break;
+ case "StepContinuousSwitch":
+ this.stepContinuousSwitch.flip();
+ this.config.putNode("ControlConsole.stepContinuousSwitch",
+ this.p.sswStepContinuous = this.stepContinuousSwitch.state);
+ break;
+ case "PulseSourceSwitch": // non-functional, just turn it back off
+ this.pulseSourceSwitch.flip();
+ this.config.putNode("ControlConsole.pulseSourceSwitch", 0);
+ setCallback(null, this.pulseSourceSwitch, 250, this.pulseSourceSwitch.set, 0);
+ break;
+ case "WordContSwitch": // non-functional, just turn it back off
+ this.wordContSwitch.flip();
+ this.config.putNode("ControlConsole.wordContSwitch", 0);
+ setCallback(null, this.wordContSwitch, 250, this.wordContSwitch.set, 0);
+ break;
+ case "FrequencyKnob": // non-function knob -- just step it
+ this.frequencyKnob.step();
+ this.config.putNode("ControlConsole.frequencyKnob", this.frequencyKnob.position);
+ break;
+ }
+
+ this.updatePanel();
+ ev.preventDefault();
+ return false;
+};
+
+/**************************************/
+B220ControlConsole.prototype.consoleOnLoad = function consoleOnLoad() {
+ /* Initializes the Supervisory Panel window and user interface */
+ var body;
+ var prefs = this.config.getNode("ControlConsole");
+ var x;
+
+ this.doc = this.window.document;
+ body = this.$$("PanelSurface");
+
+ this.intervalTimer = this.$$("IntervalTimer");
+
+ // Main Registers
+
+ this.regA = new PanelRegister(this.$$("ARegPanel"), 44, 4, "A_", "A");
+ this.regB = new PanelRegister(this.$$("BRegPanel"), 16, 4, "B_", "B");
+ this.regC = new PanelRegister(this.$$("CRegPanel"), 40, 4, "C_", "C");
+ this.regD = new PanelRegister(this.$$("DRegPanel"), 44, 4, "D_", "D");
+ this.regE = new PanelRegister(this.$$("ERegPanel"), 16, 4, "E_", "E");
+ this.regR = new PanelRegister(this.$$("RRegPanel"), 44, 4, "R_", "R");
+ this.regP = new PanelRegister(this.$$("PRegPanel"), 16, 4, "P_", "P");
+ this.regS = new PanelRegister(this.$$("SRegPanel"), 16, 4, "S_", "S");
+
+ // Status Panels
+
+ this.digitCheckLamp = new ColoredLamp(body, null, null, "DigitCheckLamp", "redLamp lampCollar", "whiteLit");
+ this.powerLamp = new ColoredLamp(body, null, null, "PowerLamp", "greenLamp", "greenLit");
+
+ this.overflowLamp = new ColoredLamp(body, null, null, "OverflowLamp", "redLamp", "redLit");
+
+ this.sectorLamp = new ColoredLamp(body, null, null, "SectorLamp", "whiteLamp", "whiteLit");
+ this.controlLamp = new ColoredLamp(body, null, null, "ControlLamp", "orangeLamp", "orangeLit");
+ this.fcLamp = new ColoredLamp(body, null, null, "FCLamp", "whiteLamp", "whiteLit");
+ this.idleLamp = new ColoredLamp(body, null, null, "IdleLamp", "redLamp", "redLit");
+
+ this.executeLamp = new ColoredLamp(body, null, null, "ExecuteLamp", "whiteLamp", "whiteLit");
+ this.fetchLamp = new ColoredLamp(body, null, null, "FetchLamp", "whiteLamp", "whiteLit");
+
+ // Organ Switches
+
+ this.audibleAlarmSwitch = new ToggleSwitch(body, null, null, "AudibleAlarmSwitch",
+ B220ControlConsole.offSwitchClass, B220ControlConsole.onSwitchClass);
+ this.audibleAlarmSwitch.set(this.p.sswAudibleAlarm = prefs.audibleAlarmSwitch);
+ this.lockNormalSwitch = new ToggleSwitch(body, null, null, "LockNormalSwitch",
+ B220ControlConsole.offSwitchClass, B220ControlConsole.onSwitchClass);
+ this.lockNormalSwitch.set(this.p.sswLockNormal = prefs.lockNormalSwitch);
+ this.stepContinuousSwitch = new ToggleSwitch(body, null, null, "StepContinuousSwitch",
+ B220ControlConsole.offSwitchClass, B220ControlConsole.onSwitchClass);
+ this.stepContinuousSwitch.set(this.p.sswStepContinuous = prefs.stepContinuousSwitch);
+
+ // Events
+ this.$$("IntervalTimerResetBtn").addEventListener("click", this.boundResetTimer);
+ /*****
+ this.$$("ResetOverflowBtn").addEventListener("click", this.boundClear_Click);
+ this.$$("ResetSectorBtn").addEventListener("click", this.boundClear_Click);
+ this.$$("ResetControlBtn").addEventListener("click", this.boundClear_Click);
+ this.$$("ExecuteBtn").addEventListener("click", this.boundClear_Click);
+ this.$$("FetchBtn").addEventListener("click", this.boundClear_Click);
+ this.$$("PowerOnBtn").addEventListener("click", this.boundPowerBtn_Click);
+ *****/
+ this.$$("PowerOffBtn").addEventListener("click", this.boundPowerBtn_Click);
+
+ this.window.addEventListener("beforeunload", B220ControlConsole.prototype.beforeUnload);
+
+ /*****
+ this.$$("AudibleAlarmSwitch").addEventListener("click", this.boundFlipSwitch);
+ this.$$("LockNormalSwitch").addEventListener("click", this.boundFlipSwitch);
+ this.$$("StepContinuousSwitch").addEventListener("click", this.boundFlipSwitch);
+
+ this.$$("StartBtn").addEventListener("click", this.boundStartBtn_Click);
+
+ this.$$("ARegPanel").addEventListener("click", this.boundLamp_Click);
+ this.$$("BRegPanel").addEventListener("click", this.boundLamp_Click);
+ this.$$("CRegPanel").addEventListener("click", this.boundLamp_Click);
+ this.$$("DRegPanel").addEventListener("click", this.boundLamp_Click);
+ this.$$("RRegPanel").addEventListener("click", this.boundLamp_Click);
+ *****/
+
+ this.$$("EmulatorVersion").textContent = B220Processor.version;
+
+ // Power on the system by default...
+ setCallback(this.mnemonic, this, 1000, function powerOnTimer() {
+ this.powerOnSystem();
+ });
+};
+
+/**************************************/
+B220ControlConsole.prototype.shutDown = function shutDown() {
+ /* Shuts down the panel */
+
+ if (this.intervalToken) {
+ this.window.clearInterval(this.intervalToken);
+ }
+ this.window.removeEventListener("beforeunload", B220ControlConsole.prototype.beforeUnload);
+ this.window.close();
+};
\ No newline at end of file
diff --git a/webUI/B220Manifest.appcache b/webUI/B220Manifest.appcache
new file mode 100644
index 0000000..e9d1a39
--- /dev/null
+++ b/webUI/B220Manifest.appcache
@@ -0,0 +1,61 @@
+CACHE MANIFEST
+# retro-220 emulator 0.00a, 2016-12-29 10:30
+
+CACHE:
+../emulator/B220Processor.js
+B220.css
+B220.html
+B220.js
+#B220CardatronControl.css
+#B220CardatronControl.html
+#B220CardatronControl.js
+#B220CardatronInput.css
+#B220CardatronInput.html
+#B220CardatronInput.js
+#B220CardatronOutput.css
+#B220CardatronOutput.html
+#B220CardatronOutput.js
+#B220CardatronZeroSuppressPanel.html
+B220Common.css
+#B220ConsoleInput.css
+#B220ConsoleInput.js
+#B220ConsoleOutput.css
+#B220ConsoleOutput.js
+B220ControlConsole.css
+B220ControlConsole.html
+B220ControlConsole.js
+#B220DataFile.css
+#B220DataFile.html
+#B220DataFile.js
+#B220DiagMonitor.html
+#B220Flexowriter.html
+#B220FramePaper.html
+#B220MagTapeControl.css
+#B220MagTapeControl.html
+#B220MagTapeControl.js
+#B220MagTapeDrive.css
+#B220MagTapeDrive.html
+#B220MagTapeDrive.js
+#B220MagTapeLoadPanel.html
+B220PanelUtil.js
+#B220PaperTapePunch.html
+#B220PaperTapeReader.html
+B220SetCallback.js
+B220SystemConfig.css
+B220SystemConfig.html
+B220SystemConfig.js
+B220Util.js
+resources/B220-Site.jpg
+#resources/DataFileTapeHead.png
+resources/DejaVuSans-Bold-webfont.ttf
+resources/DejaVuSans-Bold-webfont.woff
+resources/DejaVuSans-webfont.ttf
+resources/DejaVuSans-webfont.woff
+resources/DejaVuSansMono-webfont.ttf
+resources/DejaVuSansMono-webfont.woff
+#resources/MagTapeReel.jpg
+resources/retro-220-Logo.png
+resources/retro-Logo.png
+resources/ToggleDown.png
+resources/ToggleMid.png
+resources/ToggleUp.png
diff --git a/webUI/B220PanelUtil.js b/webUI/B220PanelUtil.js
new file mode 100644
index 0000000..0aa39ed
--- /dev/null
+++ b/webUI/B220PanelUtil.js
@@ -0,0 +1,758 @@
+/***********************************************************************
+* retro-220/webUI B220PanelUtil.js
+************************************************************************
+* Copyright (c) 2017, Paul Kimpel.
+* Licensed under the MIT License, see
+* http://www.opensource.org/licenses/mit-license.php
+************************************************************************
+* JavaScript object definition for the Burroughs 220 Emulator Control
+* Panel utility constructors.
+************************************************************************
+* 2017-01-01 P.Kimpel
+* Original version, from retro-205 D205PanelUtil.js.
+***********************************************************************/
+
+/***********************************************************************
+* Panel Neon Lamp *
+***********************************************************************/
+function NeonLamp(parent, x, y, id) {
+ /* Constructor for the neon lamp objects used within panels. x & y are the
+ coordinates of the lamp within its containing element; id is the DOM id */
+
+ this.state = 0; // current lamp state, 0=off
+ this.topCaptionDiv = null; // optional top caption element
+ this.bottomCaptionDiv = null; // optional bottom caption element
+
+ // visible DOM element
+ this.element = document.createElement("div");
+ this.element.id = id;
+ this.element.className = NeonLamp.lampClass;
+ if (x !== null) {
+ this.element.style.left = x.toString() + "px";
+ }
+ if (y !== null) {
+ this.element.style.top = y.toString() + "px";
+ }
+
+ if (parent) {
+ parent.appendChild(this.element);
+ }
+}
+
+/**************************************/
+
+NeonLamp.topCaptionClass = "neonLampTopCaption";
+NeonLamp.bottomCaptionClass = "neonLampBottomCaption";
+NeonLamp.lampClass = "neonLamp";
+NeonLamp.litClass = "neonLamp neonLit";
+NeonLamp.lampLevels = 6;
+NeonLamp.levelClass = [ // css class names for the lamp levels
+ NeonLamp.lampClass,
+ NeonLamp.litClass + "1",
+ NeonLamp.litClass + "2",
+ NeonLamp.litClass + "3",
+ NeonLamp.litClass + "4",
+ NeonLamp.litClass + "5",
+ NeonLamp.litClass];
+
+/**************************************/
+NeonLamp.prototype.set = function set(state) {
+ /* Changes the visible state of the lamp according to the value of "state", 0-1 */
+ var newState = Math.max(Math.min(Math.round(state*NeonLamp.lampLevels + 0.4999), NeonLamp.lampLevels), 0);
+
+ if (this.state ^ newState) { // the state has changed
+ this.state = newState;
+ this.element.className = NeonLamp.levelClass[newState];
+ }
+};
+
+/**************************************/
+NeonLamp.prototype.flip = function flip() {
+ /* Complements the visible state of the lamp */
+
+ this.set(1.0 - this.state/NeonLamp.lampLevels);
+};
+
+/**************************************/
+NeonLamp.prototype.setCaption = function setCaption(caption, atBottom) {
+ /* Establishes an optional caption at the top or bottom of a single lamp.
+ Returns the caption element */
+ var e = (atBottom ? this.bottomCaptionDiv : this.topCaptionDiv);
+
+ if (e) {
+ e.textContent = caption;
+ } else {
+ e = document.createElement("div");
+ if (atBottom) {
+ this.bottomCaptionDiv = e;
+ e.className = NeonLamp.bottomCaptionClass;
+ } else {
+ this.topCaptionDiv = e;
+ e.className = NeonLamp.topCaptionClass;
+ }
+ e.appendChild(document.createTextNode(caption));
+ this.element.appendChild(e);
+ }
+ return e;
+};
+
+/***********************************************************************
+* Panel Neon Lamp Box *
+***********************************************************************/
+function NeonLampBox(parent, x, y, id, caption) {
+ /* Constructor for the neon lamp-in-a-box objects used within panels.
+ x & y are the coordinates of the box within its containing element;
+ id is the DOM id */
+
+ // visible DOM element
+ this.element = document.createElement("div");
+ this.element.id = id;
+ this.element.className = NeonLampBox.lampBoxClass;
+ if (x !== null) {
+ this.element.style.left = x.toString() + "px";
+ }
+ if (y !== null) {
+ this.element.style.top = y.toString() + "px";
+ }
+
+ this.lamp = new NeonLamp(this.element, 3, 3, id + "_Lamp");
+
+ this.button = document.createElement("div");
+ this.button.className = NeonLampBox.lampButtonClass;
+ this.button.textContent = caption;
+ this.element.appendChild(this.button);
+
+ if (parent) {
+ parent.appendChild(this.element);
+ }
+}
+
+/**************************************/
+
+NeonLampBox.lampBoxClass = "lampBox";
+NeonLampBox.lampButtonClass = "lampButton";
+
+/**************************************/
+NeonLampBox.prototype.set = function set(state) {
+ /* Changes the visible state of the lamp according to the value of "state", 0-1 */
+
+ this.lamp.set(state);
+};
+
+/**************************************/
+NeonLampBox.prototype.flip = function flip() {
+ /* Complements the visible state of the lamp */
+
+ this.lamp.flip();
+};
+
+/**************************************/
+NeonLampBox.prototype.setCaption = function setCaption(caption) {
+ /* Establishes an optional caption on the button for a single lamp */
+
+ this.button.textContent = caption;
+};
+
+
+/***********************************************************************
+* Panel Colored Lamp *
+***********************************************************************/
+function ColoredLamp(parent, x, y, id, offClass, onClass) {
+ /* Constructor for the colored lamp objects used within panels. x & y are
+ the coordinates of the lamp within its containing element; id is the DOM id */
+
+ this.state = 0; // current lamp state, 0=off
+ this.topCaptionDiv = null; // optional top caption element
+ this.bottomCaptionDiv = null; // optional bottom caption element
+ this.lampClass = offClass; // css styling for an "off" lamp
+ this.litClass = // css styling for an "on" lamp
+ offClass + " " + onClass;
+ this.levelClass = [ // css class names for the lamp levels
+ offClass,
+ this.litClass + "1",
+ this.litClass + "2",
+ this.litClass + "3",
+ this.litClass + "4",
+ this.litClass + "5",
+ this.litClass];
+
+ // visible DOM element
+ this.element = document.createElement("div");
+ this.element.id = id;
+ this.element.className = offClass;
+ if (x !== null) {
+ this.element.style.left = x.toString() + "px";
+ }
+ if (y !== null) {
+ this.element.style.top = y.toString() + "px";
+ }
+
+ if (parent) {
+ parent.appendChild(this.element);
+ }
+}
+
+/**************************************/
+
+ColoredLamp.lampLevels = 6;
+ColoredLamp.topCaptionClass = "coloredLampTopCaption";
+ColoredLamp.bottomCaptionClass = "coloredLampBottomCaption";
+
+/**************************************/
+ColoredLamp.prototype.set = function set(state) {
+ /* Changes the visible state of the lamp according to the value of "state", 0-1 */
+ var newState = Math.max(Math.min(Math.round(state*ColoredLamp.lampLevels + 0.4999), ColoredLamp.lampLevels), 0);
+
+ if (this.state != newState) { // the state has changed
+ this.state = newState;
+ this.element.className = this.levelClass[newState];
+ }
+};
+
+/**************************************/
+ColoredLamp.prototype.flip = function flip() {
+ /* Complements the visible state of the lamp */
+
+ this.set(ColoredLamp.lampLevels - this.state);
+};
+
+/**************************************/
+ColoredLamp.prototype.setCaption = function setCaption(caption, atBottom) {
+ /* Establishes an optional caption at the top or bottom of a single lamp.
+ Returns the caption element */
+ var e = (atBottom ? this.bottomCaptionDiv : this.topCaptionDiv);
+
+ if (e) {
+ e.textContent = caption;
+ } else {
+ e = document.createElement("div");
+ if (atBottom) {
+ this.bottomCaptionDiv = e;
+ e.className = ColoredLamp.bottomCaptionClass;
+ } else {
+ this.topCaptionDiv = e;
+ e.className = ColoredLamp.topCaptionClass;
+ }
+ e.appendChild(document.createTextNode(caption));
+ this.element.appendChild(e);
+ }
+ return e;
+};
+
+
+/***********************************************************************
+* Panel Toggle Switch *
+***********************************************************************/
+function ToggleSwitch(parent, x, y, id, offImage, onImage) {
+ /* Constructor for the toggle switch objects used within panels. x & y are
+ the coordinates of the switch within its containing element; id is the DOM id */
+
+ this.state = 0; // current switch state, 0=off
+ this.topCaptionDiv = null; // optional top caption element
+ this.bottomCaptionDiv = null; // optional bottom caption element
+ this.offImage = offImage; // image used for the off state
+ this.onImage = onImage; // image used for the on state
+
+ // visible DOM element
+ this.element = document.createElement("img");
+ this.element.id = id;
+ this.element.src = offImage;
+ if (x !== null) {
+ this.element.style.left = x.toString() + "px";
+ }
+ if (y !== null) {
+ this.element.style.top = y.toString() + "px";
+ }
+
+ if (parent) {
+ parent.appendChild(this.element);
+ }
+}
+
+/**************************************/
+
+ToggleSwitch.topCaptionClass = "toggleSwitchTopCaption";
+ToggleSwitch.bottomCaptionClass = "toggleSwitchBottomCaption";
+
+/**************************************/
+ToggleSwitch.prototype.set = function set(state) {
+ /* Changes the visible state of the switch according to the low-order
+ bit of "state" */
+ var newState = state & 1;
+
+ if (this.state ^ newState) { // the state has changed
+ this.state = newState;
+ this.element.src = (newState ? this.onImage : this.offImage);
+ }
+};
+
+/**************************************/
+ToggleSwitch.prototype.flip = function flip() {
+ /* Complements the visible state of the switch */
+ var newState = this.state ^ 1;
+
+ this.state = newState;
+ this.element.src = (newState ? this.onImage : this.offImage);
+};
+
+/**************************************/
+ToggleSwitch.prototype.setCaption = function setCaption(caption, atBottom) {
+ /* Establishes an optional caption at the top or bottom of a single switch.
+ Returns the caption element */
+ var e = (atBottom ? this.bottomCaptionDiv : this.topCaptionDiv);
+
+ if (e) {
+ e.textContent = caption;
+ } else {
+ e = document.createElement("div");
+ if (atBottom) {
+ this.bottomCaptionDiv = e;
+ e.className = ToggleSwitch.bottomCaptionClass;
+ } else {
+ this.topCaptionDiv = e;
+ e.className = ToggleSwitch.topCaptionClass;
+ }
+ e.appendChild(document.createTextNode(caption));
+ this.element.appendChild(e);
+ }
+ return e;
+};
+
+
+/***********************************************************************
+* Panel Three-way Toggle Switch *
+***********************************************************************/
+function ThreeWaySwitch(parent, x, y, id, offImage, onImage1, onImage2) {
+ /* Constructor for the three-way toggle switch objects used within panels.
+ x & y are the coordinates of the switch within its containing element;
+ id is the DOM id */
+
+ this.state = 0; // current switch state, 0=off
+ this.topCaptionDiv = null; // optional top caption element
+ this.bottomCaptionDiv = null; // optional bottom caption element
+ this.offImage = offImage; // image used for the off state
+ this.onImage1 = onImage1; // image used for the lower on state
+ this.onImage2 = onImage2; // image used for the upper on state
+
+ // visible DOM element
+ this.element = document.createElement("img");
+ this.element.id = id;
+ this.element.src = offImage;
+ if (x !== null) {
+ this.element.style.left = x.toString() + "px";
+ }
+ if (y !== null) {
+ this.element.style.top = y.toString() + "px";
+ }
+
+ if (parent) {
+ parent.appendChild(this.element);
+ }
+}
+
+/**************************************/
+
+ThreeWaySwitch.topCaptionClass = "ToggleSwitchTopCaption";
+ThreeWaySwitch.bottomCaptionClass = "ToggleSwitchBottomCaption";
+
+/**************************************/
+ThreeWaySwitch.prototype.set = function set(state) {
+ /* Changes the visible state of the switch according to the value
+ of "state" */
+
+ if (this.state != state) { // the state has changed
+ switch (state) {
+ case 1:
+ this.state = 1;
+ this.element.src = this.onImage1;
+ break;
+ case 2:
+ this.state = 2;
+ this.element.src = this.onImage2;
+ break;
+ default:
+ this.state = 0;
+ this.element.src = this.offImage;
+ break;
+ } // switch state
+ }
+};
+
+/**************************************/
+ThreeWaySwitch.prototype.flip = function flip() {
+ /* Increments the visible state of the switch */
+
+ this.set(this.state+1);
+};
+
+/**************************************/
+ThreeWaySwitch.prototype.setCaption = function setCaption(caption, atBottom) {
+ /* Establishes an optional caption at the top or bottom of a single switch.
+ Returns the caption element */
+ var e = (atBottom ? this.bottomCaptionDiv : this.topCaptionDiv);
+
+ if (e) {
+ e.textContent = caption;
+ } else {
+ e = document.createElement("div");
+ if (atBottom) {
+ this.bottomCaptionDiv = e;
+ e.className = ThreeWaySwitch.bottomCaptionClass;
+ } else {
+ this.topCaptionDiv = e;
+ e.className = ThreeWaySwitch.topCaptionClass;
+ }
+ e.appendChild(document.createTextNode(caption));
+ this.element.appendChild(e);
+ }
+ return e;
+};
+
+
+/***********************************************************************
+* Black Control Knob *
+***********************************************************************/
+function BlackControlKnob(parent, x, y, id, initial, positions) {
+ /* Constructor for the black control knob objects used within panels. x & y are
+ the coordinates of the knob within its containing element; id is the DOM id;
+ initial is the 0-relative index indicating the default position of the switch;
+ positions is an array indicating the angular position (in degrees, where 0
+ is straight up) of each of the knob's positions */
+
+ this.position = 0; // current knob position
+ this.direction = 1; // rotate knob clockwise(1), counter-clockwise(-1)
+ this.topCaptionDiv = null; // optional top caption element
+ this.bottomCaptionDiv = null; // optional bottom caption element
+ this.positions = positions; // array of knob position angles
+
+ // visible DOM element
+ this.element = document.createElement("canvas");
+ this.element.id = id;
+ this.element.width = BlackControlKnob.size;
+ this.element.height = BlackControlKnob.size;
+ this.element.className = BlackControlKnob.className;
+ if (x !== null) {
+ this.element.style.left = x.toString() + "px";
+ }
+ if (y !== null) {
+ this.element.style.top = y.toString() + "px";
+ }
+
+ if (parent) {
+ parent.appendChild(this.element);
+ }
+
+ this.set(initial); // set to its initial position
+}
+
+/**************************************/
+
+BlackControlKnob.topCaptionClass = "blackControlKnobTopCaption";
+BlackControlKnob.bottomCaptionClass = "blackControlKnobBottomCaption";
+BlackControlKnob.className = "blackControlKnob1";
+BlackControlKnob.size = 64; // width/height in pixels
+
+/**************************************/
+BlackControlKnob.prototype.set = function set(position) {
+ /* Changes the visible state of the knob according to the position index */
+ var dc = this.element.getContext("2d");
+ var degrees = Math.PI/180;
+ var fullCircle = 360*degrees;
+ var halfSize = Math.floor(BlackControlKnob.size/2);
+ var quarterSize = Math.floor(BlackControlKnob.size/4);
+ var silverSkirt;
+
+ if (position < 0) {
+ this.position = 0;
+ this.direction = 1;
+ } else if (position < this.positions.length) {
+ this.position = position;
+ } else {
+ this.position = this.positions.length-1;
+ this.direction = -1;
+ }
+
+ dc.save();
+ dc.translate(halfSize+0.5, halfSize+0.5); // move origin to the center
+
+ dc.fillStyle = "#246"; // fill in the panel background (aids antialiasing)
+ dc.fillRect(-halfSize, -halfSize, BlackControlKnob.size, BlackControlKnob.size);
+
+ silverSkirt = dc.createRadialGradient(0, 0, halfSize, 0, 0, quarterSize);
+ silverSkirt.addColorStop(0.5, "#FFF");
+ silverSkirt.addColorStop(1, "#CCC");
+
+ dc.beginPath(); // draw the outer skirt of the knob
+ dc.arc(0, 0, halfSize-1, 0, fullCircle, false);
+ dc.fillStyle = silverSkirt;
+ dc.fill();
+
+ dc.beginPath(); // draw the central knob
+ dc.arc(0, 0, quarterSize, 0, fullCircle, false);
+ dc.fillStyle = "#000";
+ dc.fill();
+
+ dc.beginPath(); // draw the inset on top of the knob
+ dc.arc(0, 0, quarterSize-4, 0, fullCircle, false);
+ dc.fillStyle = "#333";
+ dc.fill();
+
+ dc.save(); // draw the knob indicator
+ dc.rotate(this.positions[this.position]*degrees);
+ dc.beginPath();
+ dc.moveTo(0, -halfSize);
+ dc.lineTo(-quarterSize/4, -halfSize+quarterSize/2);
+ dc.lineTo(quarterSize/4, -halfSize+quarterSize/2);
+ dc.closePath();
+ dc.fillStyle = "#000";
+ dc.fill();
+ dc.restore(); // undo the rotation
+ dc.restore(); // undo the translation
+};
+
+/**************************************/
+BlackControlKnob.prototype.step = function step() {
+ /* Steps the knob to its next position. If it is at the last position, steps it
+ to the first position */
+ var position = this.position+this.direction;
+
+ if (position < 0) {
+ this.direction = 1;
+ this.set(1);
+ } else if (position < this.positions.length) {
+ this.set(position);
+ } else {
+ this.direction = -1;
+ this.set(this.positions.length-2);
+ }
+};
+
+/**************************************/
+BlackControlKnob.prototype.setCaption = function setCaption(caption, atBottom) {
+ /* Establishes an optional caption at the top or bottom of a single switch.
+ Returns the caption element */
+ var e = (atBottom ? this.bottomCaptionDiv : this.topCaptionDiv);
+
+ if (e) {
+ e.textContent = caption;
+ } else {
+ e = document.createElement("div");
+ if (atBottom) {
+ this.bottomCaptionDiv = e;
+ e.className = blackControlKnob.bottomCaptionClass;
+ } else {
+ this.topCaptionDiv = e;
+ e.className = blackControlKnob.topCaptionClass;
+ }
+ e.appendChild(document.createTextNode(caption));
+ this.element.appendChild(e);
+ }
+ return e;
+};
+
+
+/***********************************************************************
+* Panel Register *
+***********************************************************************/
+function PanelRegister(parent, bits, rows, idPrefix, caption) {
+ /* Constructor for the register objects used within panels:
+ parent:the DOM element (usually a
) within which the register will be built
+ bits: number of bits in register
+ rows: number of rows used to display the bit lamps
+ caption:optional caption displayed at the bottom of the register
+ */
+ var cols = Math.floor((bits+rows-1)/rows);
+ var b;
+ var cx;
+ var cy;
+ var e;
+
+ this.element = parent; // containing element for the panel
+ this.bits = bits; // number of bits in the register
+ this.caption = caption || ""; // panel caption
+ this.lastValue = 0; // prior register value
+ this.lamps = new Array(bits); // bit lamps
+
+ cx = cols*PanelRegister.hSpacing + PanelRegister.hOffset;
+ for (b=0; b>>= 1;
+ thisMask >>>= 1;
+ } else {
+ thisValue = thisMask = 0;
+ lastValue = lastMask = 0;
+ break; // out of inner while loop
+ }
+ }
+
+ bitBase += 30;
+ } while (thisValue || lastValue);
+ }
+};
+
+/**************************************/
+PanelRegister.prototype.updateGlow = function updateGlow(glow) {
+ /* Update the register lamps from the bitwise intensity values in "glow" */
+ var bitNr;
+
+ for (bitNr=this.bits-1; bitNr>=0; --bitNr) {
+ this.lamps[bitNr].set(glow[bitNr]);
+ }
+};
diff --git a/webUI/B220SetCallback.js b/webUI/B220SetCallback.js
new file mode 100644
index 0000000..bfca684
--- /dev/null
+++ b/webUI/B220SetCallback.js
@@ -0,0 +1,275 @@
+/***********************************************************************
+* retro-220/webUI B220SetCallback.js
+************************************************************************
+* Copyright (c) 2014,2017, Paul Kimpel.
+* Licensed under the MIT License, see
+* http://www.opensource.org/licenses/mit-license.php
+************************************************************************
+* B220 emulator universal function call-back module.
+*
+* Implements a combination setTimeout() and setImmediate() facility for the
+* B220 emulator web-based user interface. setCallback() is used the same way
+* that setTimeout() is used, except that for low values of the timeout parameter,
+* it merely yields control to any other pending events and timers before calling
+* the call-back function.
+*
+* This facility is needed because modern browsers implement a minimum delay
+* when calling setTimeout(). HTML5 specs require 4ms, but browsers vary in the
+* minimum they use, and their precision in activating the call-back function
+* once the actual delay is established varies even more. This module will use
+* setTimeout() if the requested delay time is above a certain threshold, and
+* a setImmediate()-like mechanism (based on window.postMessage) if the requested
+* delay is below that threshold.
+*
+* To help compensate for the fact that the call-back function may be called
+* sooner than requested, and that due either to other activity or to browser
+* limitations the delay may be longer than requested, the timing behavior of
+* setCallback() may be divided into "categories." For each category, a separate
+* record is kept of the current total deviation between the requested delay and
+* the actual delay. A portion of this deviation is then applied to the requested
+* delay on subsequent calls in an attempt to smooth out the differences. We are
+* going for good average behavior here, and some too-quick call-backs are better
+* than consistently too-long callbacks in this environment, so that I/Os can be
+* initiated and their finish detected in finer-grained time increments.
+*
+* The SetCallback mechanism defines three functions that become members of the
+* global (window) object:
+*
+* token = setCallback(category, context, delay, fcn[, arg])
+*
+* Requests that the function "fcn" be called after "delay" milliseconds.
+* The function will be called as a method of "context", passing a
+* single optional argument "arg". The call-back "fcn" may be called
+* earlier or later than the specified delay. The string "category" (which
+* may be empty, null, or undefined) defines the category under which the
+* average delay difference will be maintained. setCallBack returns a
+* numeric token identifying the call-back event, which can be used
+* with clearCallback(). Note that passing a string in lieu of a function
+* object is not permitted.
+*
+* clearCallBack(token)
+*
+* Cancels a pending call-back event, if in fact it is still pending.
+* The "token" parameter is a value returned from setCallback().
+*
+* object = getCallbackState(optionMask)
+*
+* This is a diagnostic function intended for use in monitoring the callback
+* mechanism. It returns an object that, depending upon bits set in its mask
+* parameter, contains copies of the lastTokenNr value, poolLength
+* value, current delayDev hash, pendingCallbacks hash, and pool array.
+* The optionMask parameter supports the following bit values:
+* bit 0x01: delayDev hash
+* bit 0x02: pendingCallbacks hash
+* bit 0x04: pool array
+* The lastTokenNr and poolLength values are always returned. If no mask
+* is supplied, no additional items are returned.
+*
+* This implementation has been inspired by Domenic Denicola's shim for the
+* setImmediate() API at https://github.com/NobleJS/setImmediate, and
+* David Baron's setZeroTimeout() implemenmentation described in his blog
+* at http://dbaron.org/log/20100309-faster-timeouts.
+*
+* I stole a little of their code, too.
+*
+************************************************************************
+* 2017-01-01 P.Kimpel
+* Original version, cloned from retro-205 emulator D205SetCallback.js.
+***********************************************************************/
+"use strict";
+
+(function (global) {
+ /* Define a closure for the setCallback() mechanism */
+ var delayDev = {NUL: 0}; // hash of delay time deviations by category
+ var minTimeout = 4; // minimum setTimeout() threshold, milliseconds
+ var lastTokenNr = 0; // last setCallback token return value
+ var pendingCallbacks = {}; // hash of pending callbacks, indexed by token as a string
+ var perf = global.performance; // cached window.performance object
+ var pool = []; // pool of reusable callback objects
+ var poolLength = 0; // length of active entries in pool
+ var secretPrefix = "retro-205.webUI." + Date.now().toString(16);
+
+ /**************************************/
+ function activateCallback(token) {
+ /* Activates a callback after its delay period has expired */
+ var category;
+ var endStamp = perf.now();
+ var thisCallback;
+ var tokenName = token.toString();
+
+ thisCallback = pendingCallbacks[tokenName];
+ if (thisCallback) {
+ delete pendingCallbacks[tokenName];
+ category = thisCallback.category;
+ if (category) {
+ delayDev[category] += endStamp - thisCallback.startStamp - thisCallback.delay;
+ }
+ try {
+ thisCallback.fcn.call(thisCallback.context, thisCallback.arg);
+ } catch (err) {
+ console.log("B220SetCallback.activateCallback: " + err.name + ", " + err.message);
+ }
+
+ thisCallback.context = null;
+ thisCallback.fcn = null;
+ thisCallback.arg = null;
+ pool[poolLength++] = thisCallback;
+ }
+ }
+
+ /**************************************/
+ function clearCallback(token) {
+ /* Disables a pending callback, if it still exists and is still pending */
+ var thisCallback;
+ var tokenName = token.toString();
+
+ thisCallback = pendingCallbacks[tokenName];
+ if (thisCallback) {
+ delete pendingCallbacks[tokenName];
+ if (thisCallback.isTimeout) {
+ if (thisCallback.cancelToken) {
+ global.clearTimeout(thisCallback.cancelToken);
+ }
+ }
+
+ thisCallback.context = null;
+ thisCallback.fcn = null;
+ thisCallback.arg = null;
+ pool[poolLength++] = thisCallback;
+ }
+ }
+
+ /**************************************/
+ function setCallback(category, context, callbackDelay, fcn, arg) {
+ /* Sets up and schedules a callback for function "fcn", called with context
+ "context", after a delay of "delay" ms. An optional "arg" value will be passed
+ to "fcn". If the delay is less than "minTimeout", a setImmediate-like mechanism
+ based on window.postsMessage() will be used; otherwise the environment's standard
+ setTimeout mechanism will be used */
+ var categoryName = (category || "NUL").toString();
+ var delay = callbackDelay || 0; // actual delay to be generated
+ var delayBias; // current amount of delay deviation
+ var ratio; // ratio of delay to delayBias
+ var thisCallback; // call-back object to be used
+ var token = ++lastTokenNr; // call-back token number
+ var tokenName = token.toString(); // call-back token ID
+
+ // Allocate a call-back object from the pool.
+ if (poolLength <= 0) {
+ thisCallback = {};
+ } else {
+ thisCallback = pool[--poolLength];
+ pool[poolLength] = null;
+ }
+
+ delayBias = delayDev[categoryName];
+ if (!delayBias) {
+ delayDev[categoryName] = 0; // bias was zero, or got a new one
+ } else {
+ ratio = delay/delayBias;
+ if (ratio > 1) {
+ delay -= delayBias;
+ delayDev[categoryName] = 0;
+ } else if (ratio > 0) {
+ delayDev[categoryName] -= delay;
+ delay = 0;
+ } else if (ratio < -1) {
+ delay += delayBias;
+ delayDev[categoryName] = 0;
+ } else {
+ delayDev[categoryName] += delay;
+ delay = 0;
+ }
+ }
+
+ // Fill in the call-back object and tank it in pendingCallbacks.
+ thisCallback.startStamp = perf.now();
+ thisCallback.category = categoryName;
+ thisCallback.context = context || this;
+ thisCallback.delay = delay;
+ thisCallback.fcn = fcn;
+ thisCallback.arg = arg;
+ pendingCallbacks[tokenName] = thisCallback;
+
+ // Decide whether to do a time wait or just a yield.
+ if (delay > minTimeout) {
+ thisCallback.isTimeout = true;
+ thisCallback.cancelToken = global.setTimeout(activateCallback, delay, token);
+ } else {
+ thisCallback.isTimeout = false;
+ thisCallback.cancelToken = 0;
+ global.postMessage(secretPrefix + tokenName, "*");
+ }
+
+ return token;
+ }
+
+ /**************************************/
+ function onMessage(ev) {
+ /* Handler for the global.onmessage event. Activates the callback */
+ var payload;
+
+ if (ev.source === global) {
+ payload = ev.data.toString();
+ if (payload.substring(0, secretPrefix.length) === secretPrefix) {
+ activateCallback(payload.substring(secretPrefix.length));
+ }
+ }
+ }
+
+ /**************************************/
+ function getCallbackState(optionMask) {
+ /* Diagnostic function. Returns an object that, depending upon bits in
+ the option mask, contains copies of the lastTokenNr value, poolLength
+ value, current delayDev hash, pendingCallbacks hash, and pool array.
+ bit 0x01: delayDev hash
+ bit 0x02: pendingCallbacks hash
+ bit 0x04: pool array
+ If no mask is supplied, no additional items are returned */
+ var e;
+ var mask = optionMask || 0;
+ var state = {
+ lastTokenNr: lastTokenNr,
+ poolLength: poolLength,
+ delayDev: {},
+ pendingCallbacks: {},
+ pool: []};
+
+ if (mask & 0x01) {
+ for (e in delayDev) {
+ state.delayDev[e] = delayDev[e];
+ }
+ }
+ if (mask & 0x02) {
+ for (e in pendingCallbacks) {
+ state.pendingCallbacks[e] = pendingCallbacks[e];
+ }
+ }
+ if (mask & 0x04) {
+ for (e=0; e
+
+
+
+retro-220 Emulator System Configuration
+
+
+
+
+
+
+
+
+
+
+
+
+ Burroughs 220 System Configuration
+
+
+
+
+
+
+
+
Console Unit Selection:
+
+
+
+
+
+
+
+
+
+
Cardatron Unit Selection:
+
+
+
+
+
Slot
Type
Algol Glyphs
Greenbar
Zero-Suppress Columns (set on device)
+
+
+
1
+
+
+
+
+
+
+
+
+
+
+
2
+
+
+
+
+
+
+
+
+
+
3
+
+
+
+
+
+
+
+
+
+
4
+
+
+
+
+
+
+
+
+
+
5
+
+
+
+
+
+
+
+
+
+
6
+
+
+
+
+
+
+
+
+
+
7
+
+
+
+
+
+
+
+
+
+
+
+
Magnetic Tape Unit Selection:
+
+
+
+
+
+
+
+ Suppress B-Register Modification
+
+
+
+
+
+
Unit
Type
Designate
Remote
Rewind-Ready
Not-Write
+
+
+
A
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webUI/B220SystemConfig.js b/webUI/B220SystemConfig.js
new file mode 100644
index 0000000..3955747
--- /dev/null
+++ b/webUI/B220SystemConfig.js
@@ -0,0 +1,475 @@
+/***********************************************************************
+* retro-220/webUI B220SystemConfig.js
+************************************************************************
+* Copyright (c) 2017, Paul Kimpel.
+* Licensed under the MIT License, see
+* http://www.opensource.org/licenses/mit-license.php
+************************************************************************
+* Burroughs 220 Emulator System Configuration management object.
+*
+* Defines the system configuration used internally by the emulator and the
+* methods used to manage that configuration data.
+*
+************************************************************************
+* 2017-01-01 P.Kimpel
+* Original version, from retro-205 webUI/D205SystemConfig.js.
+***********************************************************************/
+"use strict";
+
+/**************************************/
+function B220SystemConfig() {
+ /* Constructor for the SystemConfig configuration management object */
+ var s;
+
+ this.flushTimer = 0; // timer token for flushing configuration to localStorage
+ this.window = null; // configuration UI window object
+ this.alertWin = window; // current base window for alert/confirm/prompt
+
+ // Load or create the system configuration data
+ s = localStorage.getItem(this.configStorageName);
+ if (!s) {
+ this.createConfigData();
+ } else {
+ this.loadConfigData(s);
+ }
+}
+
+/**************************************/
+B220SystemConfig.prototype.configStorageName = "retro-220-Config";
+B220SystemConfig.prototype.configVersion = 1;
+B220SystemConfig.prototype.flushDelay = 60000; // flush timer setting, ms
+
+/**************************************/
+B220SystemConfig.prototype.$$ = function $$(id) {
+ return this.window.document.getElementById(id);
+}
+
+/**************************************/
+B220SystemConfig.prototype.createConfigData = function createConfigData() {
+ /* Creates and initializes a new configuration data object and stores it in
+ localStorage. If former state preference objects exist, these are merged into
+ the new data object and then deleted */
+ var pref;
+ var prefs;
+ var s;
+
+ this.configData = {
+ version: this.configVersion,
+
+ SupervisoryPanel: {
+ pulseSourceSwitch: 0,
+ wordContSwitch: 0,
+ frequencyKnob: 0,
+ audibleAlarmSwitch: 0,
+ lockNormalSwitch: 0,
+ stepContinuousSwitch: 0},
+
+ ControlConsole: {
+ hasFlexowriter: true,
+ hasPaperTapeReader: true,
+ hasPaperTapePunch: true,
+ poSuppressSwitch: 0,
+ skipSwitch: 0,
+ audibleAlarmSwitch: 0,
+ outputKnob: 2,
+ breakpointKnob: 0,
+ inputKnob: 1},
+
+ Flexowriter: {
+ zeroSuppressSwitch: 0,
+ tabSpaceSwitch: 2,
+ groupingCountersSwitch: 0,
+ autoStopSwitch: 0,
+ powerSwitch: 1,
+ wordsKnob: 0,
+ linesKnob: 0,
+ groupsKnob: 0},
+
+ Cardatron: {
+ hasCardatron: true,
+ units: [
+ null, // unit[0] not used
+ {type: "CR1", formatSelect: 0, formatCol: 1},
+ {type: "NONE"},
+ {type: "NONE"},
+ {type: "NONE"},
+ {type: "LP3", algolGlyphs: true, greenBar: true, zeroSuppressCols: ""},
+ {type: "CP2", algolGlyphs: true, greenBar: false, zeroSuppressCols: ""},
+ {type: "CP1", algolGlyphs: true, greenBar: false, zeroSuppressCols: ""}
+ ]},
+
+ MagTape: {
+ hasMagTape: true,
+ suppressBSwitch: false, // false => off => suppress B-register modification
+ units: [
+ null, // unit[0] not used
+ {type: "MTA", designate: 0, remoteSwitch: false, rewindReadySwitch: true, notWriteSwitch: false},
+ {type: "NONE"},
+ {type: "NONE"},
+ {type: "MTD", designate: 3, remoteSwitch: false, rewindReadySwitch: true, notWriteSwitch: false},
+ {type: "MTE", designate: 4, remoteSwitch: false, rewindReadySwitch: true, notWriteSwitch: false},
+ {type: "NONE"},
+ {type: "NONE"},
+ {type: "NONE"},
+ {type: "NONE"},
+ {type: "NONE"}
+ ]}
+ };
+
+ this.flushHandler();
+
+ // Convert old Supervisory Panel prefs
+ s = localStorage.getItem("retro-205-SupervisoryPanel-Prefs");
+ if (s) {
+ try {
+ prefs = JSON.parse(s);
+ } finally {
+ // nothing
+ }
+
+ for (pref in prefs) {
+ this.configData.SupervisoryPanel[pref] = prefs[pref];
+ }
+ this.flushHandler();
+ localStorage.removeItem("retro-205-SupervisoryPanel-Prefs");
+ }
+
+ // Convert old Control Console prefs
+ s = localStorage.getItem("retro-205-ControlConsole-Prefs");
+ if (s) {
+ try {
+ prefs = JSON.parse(s);
+ } finally {
+ // nothing
+ }
+
+ for (pref in prefs) {
+ this.configData.ControlConsole[pref] = prefs[pref];
+ }
+ this.flushHandler();
+ localStorage.removeItem("retro-205-ControlConsole-Prefs");
+ }
+
+ // Convert old Flexowriter prefs
+ s = localStorage.getItem("retro-205-Flexowriter-Prefs");
+ if (s) {
+ try {
+ prefs = JSON.parse(s);
+ } finally {
+ // nothing
+ }
+
+ for (pref in prefs) {
+ this.configData.Flexowriter[pref] = prefs[pref];
+ }
+ this.flushHandler();
+ localStorage.removeItem("retro-205-Flexowriter-Prefs");
+ }
+};
+
+/**************************************/
+B220SystemConfig.prototype.loadConfigData = function loadConfigData(jsonConfig) {
+ /* Attempts to parse the JSON configuration data string and store it in
+ this.configData. If the parse is unsuccessful, recreates the default configuration.
+ Applies any necessary updates to older configurations */
+
+ try {
+ this.configData = JSON.parse(jsonConfig);
+ } catch (e) {
+ alert("Could not parse system configuration data:\n" +
+ e.message + "\nReinitializing configuration");
+ this.createConfigData();
+ }
+
+ // Apply updates
+ if (this.getNode("Cardatron.hasCardatron") === undefined) {
+ this.putNode("Cardatron.hasCardatron", true);
+ }
+
+ if (this.getNode("MagTape.hasMagTape") === undefined) {
+ this.putNode("MagTape.hasMagTape", true);
+ }
+};
+
+/**************************************/
+B220SystemConfig.prototype.flushHandler = function flushHandler() {
+ /* Callback function for the flush timer. Stores the configuration data */
+
+ this.flushTimer = 0;
+ localStorage.setItem(this.configStorageName, JSON.stringify(this.configData));
+};
+
+/*************************************/
+B220SystemConfig.prototype.flush = function flush() {
+ /* If the current configuration data object has been modified, stores it to
+ localStorage and resets the flush timer */
+
+ if (this.flushTimer) {
+ clearCallback(this.flushTimer);
+ this.flushHandler();
+ }
+};
+
+/**************************************/
+B220SystemConfig.prototype.getNode = function getNode(nodeName, index) {
+ /* Retrieves a specified node of the configuration data object tree.
+ "nodeName" specifies the node using dotted-path format. A blank name
+ retrieves the entire tree. If the "index" parameter is specified, the final
+ node in the path is assumed to be an array, and "index" used to return
+ that element of the array. If a node does not exist, returns undefined */
+ var name;
+ var names;
+ var node = this.configData;
+ var top;
+ var x;
+
+ name = nodeName.trim();
+ if (name.length > 0) {
+ names = name.split(".");
+ top = names.length;
+ for (x=0; x 0) {
+ names = name.split(".");
+ top = names.length-1;
+ for (x=0; x list with the specified "id" to the
+ entry with the specified "value". If no such value exists, the list
+ selection is not changed */
+ var e = this.$$(id);
+ var opt;
+ var x;
+
+ if (e && e.tagName == "SELECT") {
+ opt = e.options;
+ for (x=0; x= 0);
+ }
+};
+
+/**************************************/
+B220Util.addClass = function addClass(e, name) {
+ /* Adds a class "name" to the element "e"s class list */
+
+ if (!B220Util.hasClass(e, name)) {
+ e.className += (" " + name);
+ }
+};
+
+/**************************************/
+B220Util.removeClass = function removeClass(e, name) {
+ /* Removes the class "name" from the element "e"s class list */
+
+ e.className = e.className.replace(new RegExp("\\b" + name + "\\b\\s*", "g"), "");
+};
+
+/**************************************/
+B220Util.bindMethod = function bindMethod(context, f) {
+ /* Returns a new function that binds the function "f" to the object "context" */
+
+ return function bindMethodAnon() {return f.apply(context, arguments)};
+};
+
+/**************************************/
+B220Util.deepCopy = function deepCopy(source, dest) {
+ /* Performs a deep copy of the object "source" into the object "dest".
+ If "dest" is null or undefined, simply returns a deep copy of "source".
+ Note that this routine clones the primitive Javascript types, basic
+ objects (hash tables), Arrays, Dates, RegExps, and Functions. Other
+ types may be supported by extending the switch statement. Also note
+ this is a static function.
+ Adapted (with thanks) from the "extend" routine by poster Kamarey on 2011-03-26 at
+ http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-an-object
+ */
+ var constr;
+ var copy;
+ var name;
+
+ if (source === null) {
+ return source;
+ } else if (!(source instanceof Object)) {
+ return source;
+ } else {
+ constr = source.constructor;
+ if (constr !== Object && constr !== Array) {
+ return source;
+ } else {
+ switch (constr) {
+ case String:
+ case Number:
+ case Boolean:
+ case Date:
+ case Function:
+ case RegExp:
+ copy = new constr(source);
+ break;
+ default:
+ copy = dest || new constr();
+ break;
+ }
+
+ for (name in source) {
+ copy[name] = deepCopy(source[name], null);
+ }
+
+ return copy;
+ }
+ }
+};
+
+/**************************************/
+B220Util.xlateToAlgolChar = function xlateToAlgolChar(c) {
+ /* Translates one BIC-as-ASCII Algol glyph character to Unicode */
+
+ return B220Util.xlateASCIIToAlgolGlyph[c] || "?";
+};
+
+/**************************************/
+B220Util.xlateASCIIToAlgol = function xlateASCIIToAlgol(text) {
+ /* Translates the BIC-as-ASCII characters in "text" to equivalent Unicode glyphs */
+
+ return text.replace(B220Util.xlateASCIIToAlgolRex, B220Util.xlateToAlgolChar);
+};
+
+/**************************************/
+B220Util.xlateToASCIIChar = function xlateToASCIIChar(c) {
+ /* Translates one Unicode Algol glyph to its BIC-as-ASCII equivalent */
+
+ return B220Util.xlateAlgolToASCIIGlyph[c] || "?";
+};
+
+/**************************************/
+B220Util.xlateAlgolToASCII = function xlateAlgolToASCII(text) {
+ /* Translates the Unicode characters in "text" equivalent BIC-as-ASCII glyphs */
+
+ return text.replace(B220Util.xlateAlgolToASCIIRex, B220Util.xlateToASCIIChar);
+};
+
+/**************************************/
+B220Util.xlateDOMTreeText = function xlateDOMTreeText(n, xlate) {
+ /* If Node "n" is a text node, translates its value using the "xlate"
+ function. For all other Node types, translates all subordinate text nodes */
+ var kid;
+
+ if (n.nodeType == Node.TEXT_NODE) {
+ n.nodeValue = xlate(n.nodeValue);
+ } else {
+ kid = n.firstChild;
+ while (kid) {
+ xlateDOMTreeText(kid, xlate);
+ kid = kid.nextSibling;
+ }
+ }
+};
diff --git a/webUI/resources/B220-Logo - White.jpg b/webUI/resources/B220-Logo - White.jpg
new file mode 100644
index 0000000..a128a50
Binary files /dev/null and b/webUI/resources/B220-Logo - White.jpg differ
diff --git a/webUI/resources/B220-Logo.jpg b/webUI/resources/B220-Logo.jpg
new file mode 100644
index 0000000..0178266
Binary files /dev/null and b/webUI/resources/B220-Logo.jpg differ
diff --git a/webUI/resources/B220-Site.jpg b/webUI/resources/B220-Site.jpg
new file mode 100644
index 0000000..572228d
Binary files /dev/null and b/webUI/resources/B220-Site.jpg differ
diff --git a/webUI/resources/Burroughs-Logo - White.jpg b/webUI/resources/Burroughs-Logo - White.jpg
new file mode 100644
index 0000000..4af4f8f
Binary files /dev/null and b/webUI/resources/Burroughs-Logo - White.jpg differ
diff --git a/webUI/resources/Burroughs-Logo-Neg.jpg b/webUI/resources/Burroughs-Logo-Neg.jpg
new file mode 100644
index 0000000..66085fa
Binary files /dev/null and b/webUI/resources/Burroughs-Logo-Neg.jpg differ
diff --git a/webUI/resources/Burroughs-Logo.jpg b/webUI/resources/Burroughs-Logo.jpg
new file mode 100644
index 0000000..a823fd9
Binary files /dev/null and b/webUI/resources/Burroughs-Logo.jpg differ
diff --git a/webUI/resources/Burroughs-Meatball.png b/webUI/resources/Burroughs-Meatball.png
new file mode 100644
index 0000000..691da47
Binary files /dev/null and b/webUI/resources/Burroughs-Meatball.png differ
diff --git a/webUI/resources/DataFileTapeHead.png b/webUI/resources/DataFileTapeHead.png
new file mode 100644
index 0000000..5e12dc2
Binary files /dev/null and b/webUI/resources/DataFileTapeHead.png differ
diff --git a/webUI/resources/MagTapeReel.jpg b/webUI/resources/MagTapeReel.jpg
new file mode 100644
index 0000000..3024483
Binary files /dev/null and b/webUI/resources/MagTapeReel.jpg differ
diff --git a/webUI/resources/ToggleDown.png b/webUI/resources/ToggleDown.png
new file mode 100644
index 0000000..8d4f1ed
Binary files /dev/null and b/webUI/resources/ToggleDown.png differ
diff --git a/webUI/resources/ToggleMid.png b/webUI/resources/ToggleMid.png
new file mode 100644
index 0000000..83f4532
Binary files /dev/null and b/webUI/resources/ToggleMid.png differ
diff --git a/webUI/resources/ToggleUp.png b/webUI/resources/ToggleUp.png
new file mode 100644
index 0000000..876751e
Binary files /dev/null and b/webUI/resources/ToggleUp.png differ
diff --git a/webUI/resources/retro-Logo.png b/webUI/resources/retro-Logo.png
new file mode 100644
index 0000000..1206a3c
Binary files /dev/null and b/webUI/resources/retro-Logo.png differ