1
0
mirror of https://github.com/pkimpel/retro-220.git synced 2026-01-11 23:52:46 +00:00
pkimpel.retro-220/webUI/B220MagTapeControl.js
Paul Kimpel 2665c6066e Release retro-220 emulator version 1.02a.
1. Apply corrections to emulator/B220Processor.js uncovered by the
paper-tape diagnostic routines acquired by Al Kossow:
    a. Integer divide: sign of R must be preserved on overflow,
       otherwise must be set to sign of the dividend (A register).
    b. Floating division: sign of R must be preserved, correct align-
       ment of operands in registers before starting division cycles,
       perform 11 division cycles instead of 10, reconstruct A and R
       registers if exponent overflow is detected during post-divide
       mantissa normalization.
2. Disable console keyboard after ADD button is pressed.
3. Correct alphanumeric character code translation used by the internal
memory dump routine to match that for console and paper-tape I/O.
4. Fix bug with backward magnetic tape positioning (MPB) for tape
blocks less than 20 words long.
2020-08-21 07:06:12 -07:00

1183 lines
49 KiB
JavaScript

/***********************************************************************
* retro-220/webUI B220MagTapeControl.js
************************************************************************
* Copyright (c) 2017, Paul Kimpel.
* Licensed under the MIT License, see
* http://www.opensource.org/licenses/mit-license.php
************************************************************************
* Burroughs 220 MagTape Control unit module.
************************************************************************
* 2017-07-09 P.Kimpel
* Original version, from retro-205 D205MagTapeControl.js.
***********************************************************************/
"use strict";
/**************************************/
function B220MagTapeControl(p) {
/* Constructor for the MagTapeControl object */
var left = 0; // control window left position
var top = 452; // control window top-from-bottom position
var u; // unit configuration object
var x; // unit index
this.config = p.config; // System configuration object
this.mnemonic = "MCU";
this.p = p; // B220Processor object
this.releaseProcessor = p.boundMagTapeComplete;
// Do not call this.clear() here -- call this.clearUnit() from onLoad instead
this.doc = null;
this.window = null;
B220Util.openPopup(window, "../webUI/B220MagTapeControl.html", this.mnemonic,
"location=no,scrollbars=no,resizable,width=712,height=144,top=" +
(screen.availHeight - top) + ",left=" + left,
this, B220MagTapeControl.prototype.magTapeOnLoad);
this.boundReleaseControl = B220MagTapeControl.prototype.releaseControl.bind(this);
this.boundCancelIO = B220MagTapeControl.prototype.cancelIO.bind(this);
this.boundTapeUnitFinished = B220MagTapeControl.prototype.tapeUnitFinished.bind(this);
this.boundFetchWord = B220MagTapeControl.prototype.fetchWord.bind(this);
this.boundStoreWord = B220MagTapeControl.prototype.storeWord.bind(this);
this.boundSwitch_Click = B220MagTapeControl.prototype.switch_Click.bind(this);
this.currentUnit = null; // stashed tape unit object
/* driveState is a status object passed to mag tape units that allows them
to report their status back to the control unit */
this.driveState = {
state: 0, // state/error code, see below
preface: 0, // preface/block length word
keyword: 0, // block keyword (first data word)
controlWord: 0, // block controlword (last data word)
startTime: 0, // start time for the operation (ms)
completionDelay: 0, // extra delay before drive is released (ms)
// State constants
driveNoError: 0, // operation successful
driveNotReady: 1, // drive not ready or rewind-lockout
driveBusy: 2, // drive busy
driveAtBOT: 3, // tape at physical BOT
driveAtEOT: 4, // tape at physical EOT
driveAtEOI: 5, // tape at end-of-information
driveHasControlWord: 6, // drive returned an EOT- or control-block control word
drivePrefaceCheck: 10, // invalid preface word
drivePrefaceMismatch: 12, // preface/instruction block-length mismatch
driveReadCheck: 13, // preface/tape block-length mismatch
driveInvalidBlockLength: 14, // invalid block length from instruction
driveMemoryError: 15, // memory address or parity error
driveNotEditedTape: 16, // attempt to initial-write over non-edited tape
driveUndefined: 99}; // undefined error
/* Set up the tape units from the system configuration. These can be any
combination of Tape Storage Units (DataReaders) and DataFiles. The indexes
into this array are physical unit numbers used internally -- unit designate
is set on the tape unit itself */
this.tapeUnit = [
null, // 0=not used
null, // tape unit A
null, // tape unit B
null, // tape unit C
null, // tape unit D
null, // tape unit E
null, // tape unit F
null, // tape unit G
null, // tape unit H
null, // tape unit I
null]; // tape unit J
for (x=1; x<this.tapeUnit.length; ++x) {
u = this.config.getNode("MagTape.units", x);
switch (u.type.substring(0, 2)) {
case "MT":
this.tapeUnit[x] = new B220MagTapeDrive(u.type, x, this, this.config);
break;
case "DF":
this.tapeUnit[x] = new B220DataFile(u.type, x, this, this.config);
break;
default:
this.tapeUnit[x] = null;
break;
} // switch u.type
} // for x
}
/**************************************/
B220MagTapeControl.prototype.$$ = function $$(e) {
return this.doc.getElementById(e);
};
/**************************************/
B220MagTapeControl.prototype.clear = function clear() {
/* Initializes (and if necessary, creates) the panel state */
this.C = 0; // C register (unit, block count, etc.)
this.T = 0; // T register
this.unitNr = 0; // current unit number from command
this.unitIndex = 0; // current index into this.tapeUnit[]
this.sField = 0; // starting digit position for field compare
this.LField = 0; // length of field compare
this.controlBusy = false; // control unit is busy with read/write/search
this.pendingCallee = null; // method to call for a pending operation
this.pendingArgs = null; // Arguments object for a pending operation
};
/**************************************/
B220MagTapeControl.prototype.clearMisc = function clearMisc() {
/* Resets this.regMisc and the individual lamps for that register */
var bitNr;
var m = this.regMisc;
m.update(0);
for (bitNr=m.bits-1; bitNr>= 0; --bitNr) {
m.lamps[bitNr].set(0);
}
};
/**************************************/
B220MagTapeControl.prototype.fetchWord = function fetchWord(initialFetch) {
/* Returns the next word from the Processor's memory, as addressed by the low
four digits of its C register */
var word = this.p.boundMagTapeSendWord(initialFetch);
if (this.p.digitCheckAlarm.value) {
this.p.setMagneticTapeCheck(1);
return -1;
} else {
return word;
}
};
/**************************************/
B220MagTapeControl.prototype.storeWord = function storeWord(initialStore, word) {
/* Stores the word value in the next word of the Processor's memory, as
addressed by the low four digits of its C register */
this.p.boundMagTapeReceiveWord(initialStore, word);
};
/**************************************/
B220MagTapeControl.prototype.reportStatus = function reportStatus(state) {
/* Sets bits in the MISC register to indicate various drive and control unit
status and error conditions */
switch (state) {
case this.driveState.driveNoError:
this.clearMisc();
break;
case this.driveState.driveNotReady:
this.TX2Lamp.set(1);
this.TX10Lamp.set(1);
break;
case this.driveState.drivePrefaceCheck:
this.p.setMagneticTapeCheck(1);
this.TPCLamp.set(1);
break;
case this.driveState.drivePrefaceMismatch:
this.p.setMagneticTapeCheck(1);
this.TCFLamp.set(1);
this.C = (this.C & 0x00FFFF) | 0xFF0000;
this.regC.update(this.C);
break;
case this.driveState.driveReadCheck:
this.p.setMagneticTapeCheck(1);
this.TYC1Lamp.set(1);
this.TYC2Lamp.set(1);
this.C = (this.C & 0xFFF00F) | 0x000F90;
this.regC.update(this.C);
break;
case this.driveState.driveInvalidBlockLength:
this.p.setMagneticTapeCheck(1);
this.TX2Lamp.set(1);
this.TX4Lamp.set(1);
this.C = (this.C & 0x000F0F) | 0xB010F0;
this.regC.update(this.C);
break;
case this.driveState.driveNotEditedTape:
this.p.setMagneticTapeCheck(1);
break;
} // switch code
};
/**************************************/
B220MagTapeControl.prototype.findDesignate = function findDesignate(u) {
/* Searches this.tapeUnit[] to find the internal index of the unit that is
designated as "u". If found, returns the internal index; if not found,
returns -1. If more than one ready unit with the same designate is found,
returns -2 */
var index = -1;
var unit;
var x;
for (x=this.tapeUnit.length-1; x>0; --x) {
unit = this.tapeUnit[x];
if (unit && unit.ready) {
if (unit.unitDesignate == u) {
if (index == -1) {
index = x;
} else {
index = -2;
break; // out of for loop
}
}
}
} // for x
return index;
};
/**************************************/
B220MagTapeControl.prototype.decrementBlockCount = function decrementBlockCount() {
/* Decrements the block-count digit in the C register. Returns true if the
remaining block count is non-zero, false otherwise */
var c1 = this.C; // will hold two high-order digits of C: uu0000
var c2 = c1%0x10000; // four low-order digits of C: bddd, b=block count
var result = true;
c1 -= c2;
if (c2 < 0x1000) {
c2 += 0x9000; // decrement 0x0ddd to 0x9ddd as count=0 => 10
} else {
c2 -= 0x1000; // decrement 0xddd..0x9ddd
if (c2 < 0x1000) {
result = false; // decremented 0x1ddd to 0x0ddd: no more blocks
}
}
this.C = c1+c2;
this.regC.update(this.C);
return result;
};
/**************************************/
B220MagTapeControl.prototype.determineBlockLength = function determineBlockLength(record) {
/* Determines the length of the next block to be read or written. If
"record" is true, the length is fetched from the next word in the Processor's
memory at the address in the Processor's C register. The length is converted
to binary and checked for valid values. Returns a negative value on error.
Note that the TCU always checks the kk digits from the instruction, even if
this is a record-mode operation and those digits do not determine block length */
var words = (this.C%0x1000 - this.C%0x10)/0x10; // kk digits from TCU C register
if (words > 0) {
words = (words >>> 4)*10 + words%0x10;
} else {
words = 100; // kk == 0 => 100
}
if (words < this.currentUnit.minBlockWords && words > 1) {
words = -1; // invalid kk digits in instruction
this.driveState.state = this.driveState.driveInvalidBlockLength;
} else if (record) {
words = this.fetchWord(true);
if (words < 0) { // memory fetch failed
this.driveState.state = this.driveState.driveMemoryError;
} else { // convert preface word to binary
words = ((words - words%0x100000000)/0x100000000)%0x100;
if (words > 0) {
words = (words >>> 4)*10 + words%0x10;
} else {
words = 100; // preface == 0 => 100
}
if (words < this.currentUnit.minBlockWords && words > 1) {
words = -1; // invalid preface read from memory
this.driveState.state = this.driveState.driveInvalidBlockLength;
}
}
}
return words;
};
/**************************************/
B220MagTapeControl.prototype.determineFieldCompare = function determineFieldCompare(bReg) {
/* Determines the field to be compared during search and scan operations.
Decodes the sL value in bReg to this.sField and this.LField, checks that
s<L, and if not, sets the Processor's Program Check alarm. Returns true if
sL is invalid */
var result = false; // return value
this.sField = (bReg%0x10000 - bReg%0x1000)/0x1000;
if (this.sField == 0) {
this.sField = 10;
}
this.LField = (bReg%0x1000 - bReg%0x100)/0x100;
if (this.LField == 0) {
this.LField = 10;
}
if (this.sField < this.LField) {
result = true;
this.p.setProgramCheck(1);
}
return result;
};
/**************************************/
B220MagTapeControl.prototype.compareKeywordField = function compareKeywordField(keyword) {
/* Compares "keyword" to the contents of the T register based on the partial-
word parameters this.sField and this.LField. Returns -1 if the keyword is
less than T, 0 if they are equal, and +1 if the keyword is greater than T */
var L = this.LField; // working copy of the field length
var adder = 0; // digit adder
var carry = 1; // decimal carry flag: 1 since we're subtracting
var equal = true; // equality flag: assume true initially
var kd = 0; // current keyword digit
var kw = keyword; // working copy of the keyword value
var s = this.sField; // working copy of starting digit number
var td = 0; // current T register digit
var tw = this.T; // working copy of the T register value
while (L > 0) {
kd = kw%0x10;
td = tw%0x10;
if (s < 10) {
++s; // just shift until s=10
} else {
--L;
adder = 9 - kd + td + carry;
if (adder < 10) {
carry = 0;
} else {
carry = 1;
adder -= 10
}
if (adder) {
equal = false;
}
}
kw = (kw-kd)/0x10; // shift both words right one digit
tw = (tw-td)/0x10;
} // while L
if (equal) {
return 0; // keyword equal T
} else if (carry) {
return -1; // keyword less than T
} else {
return 1; // keyword greater than T
}
};
/**************************************/
B220MagTapeControl.prototype.queuePendingOperation = function queuePendingOperation(callee, args) {
/* Queues a pending tape operation */
//console.log(this.mnemonic + " queuePendingOperation: " + args[0].toString(16));
if (this.pendingCallee !== null) {
throw new Error("More than one pending tape control operation");
}
this.pendingCallee = callee;
this.pendingArgs = args;
};
/**************************************/
B220MagTapeControl.prototype.dequeuePendingOperation = function dequeuePendingOperation() {
/* Dequeues and reinitiates a pending tape operation */
var args = this.pendingArgs; // pending Arguments object
var callee = this.pendingCallee; // pending method to call
this.pendingCallee = this.pendingArgs = null;
callee.apply(this, args);
};
/**************************************/
B220MagTapeControl.prototype.loadCommand = function loadCommand(dReg, callee, args) {
/* If the control unit or the tape unit addressed by the unit field in dReg
are currently busy, queues the args parameter (an Arguments object) in
this.pendingCallee and -Args, and returns false. If the control is idle but
the tape unit is not ready or not present, or two units have the same designate,
calls this.releaseProcessor and returns false. If the control and tape unit
are ready for their next operation, loads the contents of the processor's
D register passed to the operation routines into the T, C, and MISC registers.
Sets this.unitNr, and this.unitIndex from the digits in T. Sets this.
currentUnit to the current tape unit object. Then returns true to inidicate
the I/O can proceed */
var c; // scratch
var proceed = false; // return value: true => proceed with I/O
var t = dReg%0x10000000000; // temp to partition fields of Processor's D register
var ux; // internal unit index
//console.log(this.mnemonic + " loadCommand: " + dReg.toString(16));
if (this.controlBusy) {
this.queuePendingOperation(callee, args);
} else {
this.T = t;
this.regT.update(this.T);
this.unitNr = (t - t%0x1000000000)/0x1000000000;
t = (t - t%0x10000)/0x10000;
c = t%0x10; // low-order digit of op code
t = (t - t%0x100)/0x100; // control digits from instruction
this.C = this.unitNr*0x100000 + t*0x10 + c;
this.regC.update(this.C);
this.clearMisc();
this.unitIndex = ux = this.findDesignate(this.unitNr);
if (ux < 0) {
this.reportStatus(this.driveState.driveNotReady); // drive not ready, not present
this.queuePendingOperation(callee, args);
} else {
this.currentUnit = this.tapeUnit[ux];
if (this.currentUnit.busy || this.currentUnit.rewindLock) {
this.queuePendingOperation(callee, args);
} else {
proceed = true;
this.driveState.startTime = performance.now();
this.driveState.completionDelay = 0;
this.driveState.state = this.driveState.driveNoError;
}
}
}
return proceed;
};
/**************************************/
B220MagTapeControl.prototype.releaseControl = function releaseControl(param) {
/* Releases the busy status of the control. If an error is present, sets the
bits in the MISC register and the Processor's Magnetic Tape Check alarm, as
appropriate. If another operation is pending, initiates that operation.
Returns but does not use its parameter so that it can be used with
Promise.then() */
this.TFLamp.set(0);
this.TBLamp.set(0);
this.controlBusy = false;
if (this.driveState.state != this.driveState.driveNoError) {
this.currentUnit.releaseUnit(this.driveState);
this.reportStatus(this.driveState.state);
}
if (this.pendingCallee !== null) {
this.dequeuePendingOperation();
}
return param;
};
/**************************************/
B220MagTapeControl.prototype.cancelIO = function cancelIO(param) {
/* Terminates the current I/O operation by releasing tape unit, and tape
control unit. Returns but does not use its parameter so it can be used
with Promise.then() */
//this.releaseProcessor(false, 0); // disabled 2017-12-21
this.currentUnit.releaseUnit();
this.releaseControl();
return param;
};
/**************************************/
B220MagTapeControl.prototype.tapeUnitFinished = function tapeUnitFinished(param) {
/* Call-back function passed to tape unit methods to signal when the unit has
completed its asynchronous operation. Returns but does not use "param", so
that it can be used with Promise.then() */
if (!this.controlBusy) { // if the control unit is currently idle...
if (this.pendingCallee !== null) {
this.dequeuePendingOperation();
}
}
return param;
};
/**************************************/
B220MagTapeControl.prototype.switch_Click = function switch_Click(ev) {
/* Handle the click event for buttons and switches */
switch(ev.target.id) {
case "ClearBtn":
this.clearUnit();
break;
case "Misc_RightClear":
this.clearMisc();
break;
case "C_RightClear":
this.C = 0;
this.regC.update(0);
break;
case "T_RightClear":
this.T = 0;
this.regT.update(0);
break;
} // switch target.id
};
/**************************************/
B220MagTapeControl.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;
};
/**************************************/
B220MagTapeControl.prototype.magTapeOnLoad = function magTapeOnLoad(ev) {
/* Initializes the MagTape Control window and user interface */
var body;
var box;
var e;
var x;
this.doc = ev.target;
this.window = this.doc.defaultView;
body = this.$$("PanelSurface");
// Misc Register
this.regMisc = new PanelRegister(this.$$("MiscRegPanel"), 4*4, 4, "Misc_", " ");
this.regMisc.lamps[15].setCaption("MCL", true);
this.regMisc.lamps[14].setCaption("MC6", true);
this.TYC1Lamp = this.regMisc.lamps[13]; // Yozzle toggle 1
this.TYC1Lamp.setCaption("TYC", true);
this.TYC2Lamp = this.regMisc.lamps[12]; // Yozzle toggle 2
this.TYC2Lamp.setCaption("TYC", true);
this.TCFLamp = this.regMisc.lamps[10]; // Preface compare failure: not in this register on a 220
this.TCFLamp.setCaption("TCF", true);
this.TFLamp = this.regMisc.lamps[9]; // Tape forward: not in this register on a 220
this.TFLamp.setCaption("TF", true);
this.TBLamp = this.regMisc.lamps[8]; // Tape bacward: not in this register on a 220
this.TBLamp.setCaption("TB", true);
this.TPCLamp = this.regMisc.lamps[7]; // Preface check
this.TPCLamp.setCaption("TPC", true);
this.regMisc.lamps[6].setCaption("TSX", true);
this.regMisc.lamps[5].setCaption("1R6", true);
this.TX1Lamp = this.regMisc.lamps[4]; // TX register
this.TX1Lamp.setCaption("TX1", true);
this.TX10Lamp = this.regMisc.lamps[3];
this.TX10Lamp.setCaption("TX10", true);
this.TX8Lamp = this.regMisc.lamps[2];
this.TX8Lamp.setCaption("TX8", true);
this.TX4Lamp = this.regMisc.lamps[1];
this.TX4Lamp.setCaption("TX4", true);
this.TX2Lamp = this.regMisc.lamps[0];
this.TX2Lamp.setCaption("TX2", true);
// Full Registers
this.regC = new PanelRegister(this.$$("CRegPanel"), 6*4, 4, "C_", "C");
this.regT = new PanelRegister(this.$$("TRegPanel"), 10*4, 4, "T_", "T");
// Events
this.window.addEventListener("beforeunload", B220MagTapeControl.prototype.beforeUnload, false);
this.$$("ClearBtn").addEventListener("click", this.boundSwitch_Click, false);
this.regMisc.rightClearBar.addEventListener("click", this.boundSwitch_Click, false);
this.regC.rightClearBar.addEventListener("click", this.boundSwitch_Click, false);
this.regT.rightClearBar.addEventListener("click", this.boundSwitch_Click, false);
this.clearUnit();
};
/**************************************/
B220MagTapeControl.prototype.scan = function scan(dReg, bReg) {
/* Scans a tape unit for a block with a word matching the word at the
operand address in memory, which is stored in the TCU's T register. "bReg"
is the contents of the B register for a partial-word match, or 0 for a
full-word match. The index of the category word in the block to be compared
to the scan key is determined from dReg:41. This routine is used by MTC and
MFC */
var laneNr = 0; // lane number from TCU C register
var searchWord = 0; // target word to scan for
var wordIndex = 0; // index of category word
var scanForward = () => {
/* Handles a block after it has been scanned in a forward direction.
If the category word is not equal, continues scanning in a forward
direction; otherwise, repositions to allow the matching block to be read
next. If an EOT or control block is encountered, the control word from
that block is ignored, and the tape is then repositioned, ready to read
the EOT or control block. The drive forward scan stops in the erase gap
of the block containing the category or control word, so a reposition
backs up to allow reading the next block. A second reposition is needed
is needed to back up into the prior block, allowing the block just
scanned to be read next */
var compare = 0; // category word field comparison result
if (this.driveState.state == this.driveState.driveHasControlWord) {
// EOT or control block encountered: terminate the I/O
this.driveState.state = this.driveState.driveNoError;
this.TFLamp.set(0);
this.TBLamp.set(1);
this.currentUnit.reposition(this.driveState) // reposition into the EOT block
.then(this.currentUnit.boundReposition) // reposition again into prior block
.then(this.currentUnit.boundReleaseDelay) // allowing the EOT block to be read next
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundReleaseControl)
.catch(this.boundReleaseControl);
} else {
// Compare category word from tape with T register
compare = this.compareKeywordField(this.driveState.keyword);
if (compare != 0) { // if category word unequal, keep scanning
this.currentUnit.scanBlock(this.driveState, wordIndex)
.then(scanForward)
.catch(this.boundReleaseControl);
} else { // Keyword was equal: stop and reposition
this.TFLamp.set(0);
this.TBLamp.set(1);
this.currentUnit.reposition(this.driveState) // reposition into the matching block
.then(this.currentUnit.boundReposition) // reposition again into prior block
.then(this.currentUnit.boundReleaseDelay) // allowing the matching block to be read next
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundReleaseControl)
.catch(this.boundReleaseControl);
}
}
};
var scanFirstBlock = (driveState) => {
/* Closure to call u.scanBlock with additional parameters from Promise.then() */
this.currentUnit.scanBlock(this.driveState, wordIndex);
};
if (this.loadCommand(dReg, scan, arguments)) {
this.controlBusy = true;
this.driveState.completionDelay = 16;
// Check for field compare validity
if (this.determineFieldCompare(bReg)) {
this.releaseProcessor(false, 0);
this.releaseControl();
} else {
// Fetch the scan target word from memory
searchWord = this.fetchWord(false);
if (searchWord < 0) {
this.driveState.state = this.driveState.driveMemoryError;
this.releaseProcessor(false, 0);
this.releaseControl();
} else {
// Start the scan after changing lane, as appropriate
this.T = searchWord%0x10000000000;
this.regT.update(this.T);
this.releaseProcessor(false, 0);
this.TFLamp.set(1);
laneNr = ((this.C - this.C%0x100)/0x100)%2;
wordIndex = ((this.C - this.C%0x10)/0x10)%2;
if (wordIndex == 0) {
wordIndex = 10;
}
this.currentUnit.setLane(laneNr, this.driveState)
.then(this.currentUnit.boundStartUpForward)
.then(scanFirstBlock)
.then(scanForward)
.catch(this.boundReleaseControl);
}
}
}
};
/**************************************/
B220MagTapeControl.prototype.search = function search(dReg, bReg) {
/* Searches a tape unit for a block with a keyword matching the word at the
operand address in memory, which is stored in the TCU's T register. "bReg"
is the contents of the B register for a partial-word match, or 0 for a
full-word match. This routine is used by MTS and MFS */
var laneNr = 0; // lane number from TCU C register
var searchWord = 0; // target word to search for
var searchForward = () => {
/* Handles a block after it has been searched in a forward direction.
If the keyword is low, continues searching in a forward direction;
otherwise, reverses tape direction and initiates a backward search.
If an EOT block is encountered, the control word from that block is
ignored, and the tape is then repositioned, ready to read the EOT block.
The drive forward search stops in the block after the keyword, so a
reposition backs up to allow reading the block just searched */
var compare = 0; // keyword field comparison result
if (this.driveState.state == this.driveState.driveHasControlWord) {
// EOT block encountered: terminate the I/O
this.driveState.state = this.driveState.driveNoError;
this.TFLamp.set(0);
this.TBLamp.set(1);
this.currentUnit.reposition(this.driveState)
.then(this.currentUnit.boundReleaseDelay)
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundReleaseControl)
.catch(this.boundReleaseControl);
} else {
// Compare keyword from tape with T register
compare = this.compareKeywordField(this.driveState.keyword);
if (compare < 0) { // if keyword low, keep searching
this.currentUnit.searchForwardBlock(this.driveState)
.then(searchForward)
.catch(this.boundReleaseControl);
} else { // Keyword was high or equal: reverse direction
this.TFLamp.set(0);
this.TBLamp.set(1);
this.currentUnit.reverseDirection(this.driveState)
.then(this.currentUnit.boundSearchBackwardBlock)
.then(searchBackward)
.catch(this.boundReleaseControl);
}
}
};
var searchBackward = () => {
/* Handles a block after it has been searched in a backward direction.
If the keyword is high, continues searching in a backward direction.
If the keyword is low, reverses tape direction again and searches one
block in the forward direction. In this case, the result of the search
is ignored, leaving the tape positioned to read the next block, which
will be greater-than or equal-to the search target.
If the keyword is equal, the tape is already positioned in the prior
block, so we just quit, leaving the tape in position to read the block
with the equal key */
var compare = this.compareKeywordField(this.driveState.keyword);
if (compare > 0) { // keyword is high, continue searching...
this.currentUnit.searchBackwardBlock(this.driveState)
.then(searchBackward)
.catch(this.boundReleaseControl);
} else if (compare < 0) { // keyword is low, reverse direction, search one block, and quit
this.TBLamp.set(0);
this.TFLamp.set(1);
this.currentUnit.reverseDirection(this.driveState)
.then(this.currentUnit.boundSearchForwardBlock)
.then(this.currentUnit.boundReleaseDelay)
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundReleaseControl)
.catch(this.boundReleaseControl);
} else { // keyword equal, just quit with tape positioned in prior block
this.currentUnit.releaseDelay(this.driveState)
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundReleaseControl)
.catch(this.boundReleaseControl);
}
};
if (this.loadCommand(dReg, search, arguments)) {
this.controlBusy = true;
this.driveState.completionDelay = 16;
// Check for field compare validity
if (this.determineFieldCompare(bReg)) {
this.releaseProcessor(false, 0);
this.releaseControl();
} else {
// Fetch the search target word from memory
searchWord = this.fetchWord(false);
if (searchWord < 0) {
this.driveState.state = this.driveState.driveMemoryError;
this.releaseProcessor(false, 0);
this.releaseControl();
} else {
// Start the search after changing lane, as appropriate
this.T = searchWord%0x10000000000;
this.regT.update(this.T);
this.releaseProcessor(false, 0);
this.TFLamp.set(1);
laneNr = ((this.C - this.C%0x100)/0x100)%2;
this.currentUnit.setLane(laneNr, this.driveState)
.then(this.currentUnit.boundStartUpForward)
.then(this.currentUnit.boundSearchForwardBlock)
.then(searchForward)
.catch(this.boundReleaseControl);
}
}
}
};
/**************************************/
B220MagTapeControl.prototype.read = function read(dReg, record) {
/* Reads the number of blocks indicated in dReg. If "record" is true (MRR),
block lengths (preface words) are stored into the word in memory preceding
the data read from tape. This routine is used by MRD and MRR */
var controlEnabled = false; // true => control blocks will be recognized
var readBlock = () => {
/* Reads blocks on the designated unit until the block count is
exhausted or some error occurs. If an EOT block or control block
is encountered, the drive returns the control word from that block and
the I/O is terminated normally after passing the control word to the
Processor for action. The tape is repositioned, ready to read the next
block */
if (this.driveState.state == this.driveState.driveHasControlWord) {
this.driveState.state = this.driveState.driveNoError;
this.releaseProcessor(true, this.driveState.controlWord);
this.TFLamp.set(0);
this.TBLamp.set(1);
this.currentUnit.reposition(this.driveState)
.then(this.currentUnit.boundReleaseDelay)
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundReleaseControl)
.catch(this.boundReleaseControl);
} else if (this.decrementBlockCount()) {
this.currentUnit.readNextBlock(this.driveState, record, controlEnabled, this.boundStoreWord)
.then(readBlock)
.catch(this.boundCancelIO);
} else { // block count exhausted
this.releaseProcessor(false, 0);
this.TFLamp.set(0);
this.TBLamp.set(1);
this.currentUnit.reposition(this.driveState)
.then(this.currentUnit.boundReleaseDelay)
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundReleaseControl)
.catch(this.boundReleaseControl);
}
};
var readFirstBlock = (driveState) => {
/* Closure to call u.readNextBlock with additional parameters from Promise.then() */
this.currentUnit.readNextBlock(this.driveState, record, controlEnabled, this.boundStoreWord);
};
if (this.loadCommand(dReg, read, arguments)) {
this.controlBusy = true;
this.driveState.completionDelay = 18;
controlEnabled = (this.C%0x20 < 0x10); // low-order bit of instruction v-digit
this.TFLamp.set(1);
this.currentUnit.startUpForward(this.driveState)
.then(readFirstBlock)
.then(readBlock)
.catch(this.boundCancelIO);
}
};
/**************************************/
B220MagTapeControl.prototype.overwrite = function overwrite(dReg, record) {
/* Overwrites the number of blocks and of the size indicated in dReg. If
"record" is true (MOR), block lengths (preface words) are taken from the
word in memory preceding the data to be written. Otherwise, block lengths
are taken from the instruction control digits. This routine is used by
MOW and MOR */
var blocksLeft = true; // true => more blocks to process
var writeBlock = () => {
/* Overwrites blocks on the designated unit until the block count is
exhausted or some error occurs. If an EOT block with a preface mismatch
is encountered, the drive returns the control word from that block and
the I/O is terminated normally after passing the control word to the
Processor for action. The tape is repositioned, ready to read the next
block */
var words = 0;
if (this.driveState.state == this.driveState.driveHasControlWord) {
this.driveState.state = this.driveState.driveNoError;
this.releaseProcessor(true, this.driveState.controlWord);
this.TFLamp.set(0);
this.TBLamp.set(1);
this.currentUnit.reposition(this.driveState)
.then(this.currentUnit.boundReleaseDelay)
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundReleaseControl)
.catch(this.boundReleaseControl);
} else if (blocksLeft) {
words = this.determineBlockLength(record);
if (words < 0) {
this.cancelIO();
} else {
blocksLeft = this.decrementBlockCount();
this.currentUnit.overwriteBlock(this.driveState, record, words, this.boundFetchWord)
.then(writeBlock)
.catch(this.boundCancelIO);
}
} else { // block count exhausted
this.releaseProcessor(false, 0);
this.TFLamp.set(0);
this.TBLamp.set(1);
this.currentUnit.reposition(this.driveState)
.then(this.currentUnit.boundReleaseDelay)
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundReleaseControl)
.catch(this.boundReleaseControl);
}
};
if (this.loadCommand(dReg, overwrite, arguments)) {
this.controlBusy = true;
this.driveState.completionDelay = 18;
this.TFLamp.set(1);
this.currentUnit.startUpForward(this.driveState)
.then(writeBlock)
.catch(this.boundCancelIO);
}
};
/**************************************/
B220MagTapeControl.prototype.initialWrite = function initialWrite(dReg, record) {
/* Initial-writes the number of blocks and of the size indicated in dReg.
If "record" is true (MIR), block lengths (preface words) are taken from the
word in memory preceding the data to be written. Otherwise, block lengths
are taken from the instruction control digits. This routine is used by
MIW and MIR */
var blocksLeft = true; // true => more blocks to process
var writeBlock = () => {
/* Initial-writes blocks on the designated unit until the block count is
exhausted or some error occurs */
var words = 0;
if (blocksLeft) {
words = this.determineBlockLength(record);
if (words < 0) {
this.cancelIO();
} else {
blocksLeft = this.decrementBlockCount();
this.currentUnit.initialWriteBlock(this.driveState, record, words, this.boundFetchWord)
.then(writeBlock)
.catch(this.boundCancelIO);
}
} else { // block count exhausted
this.releaseProcessor(false, 0);
this.TFLamp.set(0); // direction actually changes after WriteFinalize
this.TBLamp.set(1); // but that's messy to do here...
this.currentUnit.initialWriteFinalize(this.driveState)
.then(this.currentUnit.boundReposition)
.then(this.currentUnit.boundReleaseDelay)
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundReleaseControl)
.catch(this.boundReleaseControl);
}
};
if (this.loadCommand(dReg, initialWrite, arguments)) {
this.controlBusy = true;
this.driveState.completionDelay = 20;
this.TFLamp.set(1);
this.currentUnit.startUpForward(this.driveState)
.then(writeBlock)
.catch(this.boundCancelIO);
}
};
/**************************************/
B220MagTapeControl.prototype.positionForward = function positionForward(dReg) {
/* Positions the tape forward the number of blocks indicated in dReg */
var spaceBlock = () => {
/* Spaces forward over blocks on the designated unit until the block
count is exhausted or some error (like end-of-tape) occurs */
if (this.decrementBlockCount()) {
this.currentUnit.spaceForwardBlock(this.driveState)
.then(spaceBlock)
.catch(this.boundReleaseControl);
} else { // block count exhausted
this.TFLamp.set(0);
this.TBLamp.set(1);
this.currentUnit.reposition(this.driveState)
.then(this.currentUnit.boundReleaseDelay)
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundReleaseControl)
.catch(this.boundReleaseControl);
}
};
if (this.loadCommand(dReg, positionForward, arguments)) {
this.controlBusy = true;
this.driveState.completionDelay = 16;
this.releaseProcessor(false, 0);
this.TFLamp.set(1);
this.currentUnit.startUpForward(this.driveState)
.then(this.currentUnit.boundSpaceForwardBlock)
.then(spaceBlock)
.catch(this.boundReleaseControl);
}
};
/**************************************/
B220MagTapeControl.prototype.positionBackward = function positionBackward(dReg) {
/* Positions the tape backward the number of blocks indicated in dReg */
var spaceBlock = () => {
/* Spaces backward over blocks on the designated unit until the block
count is exhausted or some error (like beginning-of-tape) occurs */
if (this.decrementBlockCount()) {
this.currentUnit.spaceBackwardBlock(this.driveState)
.then(spaceBlock)
.catch(this.boundReleaseControl);
} else { // block count exhausted
this.currentUnit.reposition(this.driveState)
.then(this.currentUnit.boundReleaseDelay)
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundReleaseControl)
.catch(this.boundReleaseControl);
}
};
if (this.loadCommand(dReg, positionBackward, arguments)) {
this.controlBusy = true;
this.driveState.completionDelay = 6;
this.releaseProcessor(false, 0);
this.TBLamp.set(1);
this.currentUnit.startUpBackward(this.driveState)
.then(this.currentUnit.boundSpaceBackwardBlock)
.then(spaceBlock)
.catch(this.boundReleaseControl);
}
};
/**************************************/
B220MagTapeControl.prototype.positionAtEnd = function positionAtEnd(dReg) {
/* Positions the tape to the end of recorded information (i.e., when a gap
longer than inter-block gap is detected. Leaves the tape at the end of the
prior recorded block */
var spaceBlock = () => {
/* Spaces over blocks on the designated unit until end-of-information is
detected or some error (like end-of-tape) occurs */
if (this.driveState.state != this.driveState.driveAtEOI) {
this.currentUnit.spaceEOIBlock(this.driveState)
.then(spaceBlock)
.catch(this.boundReleaseControl);
} else { // found end-of-information
this.driveState.state = this.driveState.driveNoError;
this.TFLamp.set(0);
this.TBLamp.set(1);
this.currentUnit.reposition(this.driveState)
.then(this.currentUnit.boundReleaseDelay)
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundReleaseControl)
.catch(this.boundReleaseControl);
}
};
if (this.loadCommand(dReg, positionAtEnd, arguments)) {
this.controlBusy = true;
this.driveState.completionDelay = 23;
this.releaseProcessor(false, 0);
this.TFLamp.set(1);
this.currentUnit.startUpForward(this.driveState)
.then(this.currentUnit.boundSpaceEOIBlock)
.then(spaceBlock)
.catch(this.boundReleaseControl);
}
};
/**************************************/
B220MagTapeControl.prototype.laneSelect = function laneSelect(dReg) {
/* Selects the tape lane of the designated unit. Returns an alarm if the
unit does not exist or is not ready */
var laneNr; // lane to select (0, 1)
if (this.loadCommand(dReg, laneSelect, arguments)) {
this.controlBusy = true;
laneNr = ((this.C - this.C%0x100)/0x100)%2;
this.driveState.completionDelay = 3;
this.fetchWord(true); // memory access for MTS/MFS not used by MLS
this.releaseProcessor(false, 0);
this.currentUnit.laneSelect(this.driveState, laneNr)
.then(this.currentUnit.boundReleaseDelay)
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundReleaseControl)
.catch(this.boundReleaseControl);
}
};
/**************************************/
B220MagTapeControl.prototype.rewind = function rewind(dReg) {
/* Initiates rewind of the designated unit. Returns an alarm if the unit
does not exist or is not ready */
var laneNr; // lane to select (0, 1)
var lockout; // lockout after rewind (0, 1)
if (this.loadCommand(dReg, rewind, arguments)) {
this.controlBusy = true;
laneNr = ((this.C - this.C%0x100)/0x100)%2;
lockout = ((this.C - this.C%0x10)/0x10)%2;
this.fetchWord(true); // memory access for MTS/MFS not used by MRW/MDA
this.releaseProcessor(false, 0);
this.TBLamp.set(1);
setCallback(this.mnemonic, this, 50, this.releaseControl, this.driveState);
this.currentUnit.rewind(this.driveState, laneNr, lockout)
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundTapeUnitFinished);
}
};
/**************************************/
B220MagTapeControl.prototype.testUnitReady = function testUnitReady(dReg) {
/* Interrogates status of the designated unit. Returns true if ready */
var result = false; // return value
var ux; // internal unit index
ux = ((dReg - dReg%0x1000000000)/0x1000000000)%0x10;
ux = this.findDesignate(ux);
if (ux >= 0) {
if (this.tapeUnit[ux].ready) {
if (!this.tapeUnit[ux].busy) {
result = true;
}
}
}
return result;
};
/**************************************/
B220MagTapeControl.prototype.testUnitAtEOT = function testUnitAtEOT(dReg) {
/* Interrogates status of the designated unit. Returns true if ready and at
end-of-tape */
var result = false; // return value
var ux; // internal unit index
ux = ((dReg - dReg%0x1000000000)/0x1000000000)%0x10;
ux = this.findDesignate(ux);
if (ux >= 0) {
if (this.tapeUnit[ux].ready) {
if (this.tapeUnit[ux].atEOT) {
result = true;
}
}
}
return result;
};
/**************************************/
B220MagTapeControl.prototype.clearUnit = function clearUnit() {
/* Clears the internal state of the control unit */
this.clear();
this.clearMisc();
this.regC.update(this.C);
this.regT.update(this.T);
};
/**************************************/
B220MagTapeControl.prototype.shutDown = function shutDown() {
/* Shuts down the panel */
var x;
if (this.tapeUnit) {
for (x=this.tapeUnit.length-1; x>=0; --x) {
if (this.tapeUnit[x]) {
this.tapeUnit[x].shutDown();
this.tapeUnit[x] = null;
}
}
}
this.window.removeEventListener("beforeunload", B220MagTapeControl.prototype.beforeUnload, false);
this.$$("ClearBtn").removeEventListener("click", this.boundSwitch_Click, false);
this.regMisc.rightClearBar.removeEventListener("click", this.boundSwitch_Click, false);
this.regC.rightClearBar.removeEventListener("click", this.boundSwitch_Click, false);
this.regT.rightClearBar.removeEventListener("click", this.boundSwitch_Click, false);
this.window.close();
};