diff --git a/emulator/B220Processor.js b/emulator/B220Processor.js index 9964ff4..865e66c 100644 --- a/emulator/B220Processor.js +++ b/emulator/B220Processor.js @@ -243,8 +243,8 @@ function B220Processor(config, devices) { this.boundCardatronReceiveWord = B220Processor.bindMethod(this, B220Processor.prototype.cardatronReceiveWord); this.boundMagTapeComplete = B220Processor.bindMethod(this, B220Processor.prototype.magTapeComplete); - this.boundMagTapeReceiveBlock = B220Processor.bindMethod(this, B220Processor.prototype.magTapeReceiveBlock); - this.boundMagTapeSendBlock = B220Processor.bindMethod(this, B220Processor.prototype.magTapeSendBlock); + this.boundMagTapeReceiveWord = B220Processor.bindMethod(this, B220Processor.prototype.magTapeReceiveWord); + this.boundMagTapeSendWord = B220Processor.bindMethod(this, B220Processor.prototype.magTapeSendWord); this.clear(); // Create and initialize the processor state @@ -256,7 +256,7 @@ function B220Processor(config, devices) { * Global Constants * ***********************************************************************/ -B220Processor.version = "0.03a"; +B220Processor.version = "0.03b"; B220Processor.tick = 1000/200000; // milliseconds per clock cycle (200KHz) B220Processor.cyclesPerMilli = 1/B220Processor.tick; @@ -1886,7 +1886,7 @@ B220Processor.prototype.compareField = function compareField() { if (s == 0) { s = 10; } - L = (this.CCONTROL >>> 8) & 0x0F; + L = (this.CCONTROL >>> 8)%0x10; if (L == 0) { L = 10; } @@ -2009,7 +2009,7 @@ B220Processor.prototype.increaseFieldLocation = function increaseFieldLocation() if (s == 0) { s = 10; } - L = (this.CCONTROL >>> 8) & 0x0F; + L = (this.CCONTROL >>> 8)%0x10; if (L == 0) { L = 10; } @@ -2092,7 +2092,7 @@ B220Processor.prototype.decreaseFieldLocation = function decreaseFieldLocation(l if (s == 0) { s = 10; } - L = (this.CCONTROL >>> 8) & 0x0F; + L = (this.CCONTROL >>> 8)%0x10; if (L == 0) { L = 10; } @@ -2173,7 +2173,7 @@ B220Processor.prototype.branchField = function branchField(regValue) { if (s == 0) { s = 10; } - L = (this.CCONTROL >>> 8) & 0x0F; + L = (this.CCONTROL >>> 8)%0x10; if (L == 0) { L = 10; } @@ -2265,7 +2265,7 @@ B220Processor.prototype.storeRegister = function storeRegister() { if (s == 0) { s = 10; } - L = (this.CCONTROL >>> 8) & 0x0F; + L = (this.CCONTROL >>> 8)%0x10; if (L == 0) { L = 10; } @@ -2433,7 +2433,7 @@ B220Processor.prototype.consoleOutputSign = function consoleOutputSign(printSign this.ioComplete(true); } else { this.D.set(this.IB.value); - this.opTime += 0.070; // estimate for memory access and rotation + this.execClock += 0.070; // estimate for memory access and rotation w = this.D.value%0x10000000000; d = (this.D.value - w)/0x10000000000; // get the sign digit this.D.set(w*0x10 + d); // rotate D+sign left one @@ -2470,7 +2470,7 @@ B220Processor.prototype.consoleOutputChar = function consoleOutputChar(printChar w = this.D.value % 0x1000000000; d = (this.D.value - w)/0x1000000000; // get next 2 digits this.D.set(w*0x100 + d); // rotate D+sign left by two - this.opTime += 0.060; // estimate for rotation + this.execClock += 0.060; // estimate for rotation this.DC.inc(); // increment DC for two digits this.DC.inc(); this.PA.set(d); @@ -2495,7 +2495,7 @@ B220Processor.prototype.consoleOutputChar = function consoleOutputChar(printChar w = this.D.value % 0x10000000000; d = (this.D.value - w)/0x10000000000; // get a digit this.D.value = w*0x10 + d; // rotate D+sign left by one - this.opTime += 0.065; // estimate for rotation + this.execClock += 0.065; // estimate for rotation this.DC.inc(); } while (d == 0 && this.LT1.value && this.DC.value < 0x20); @@ -2727,13 +2727,14 @@ B220Processor.prototype.cardatronOutputWord = function cardatronOutputWord() { } else if (this.MET.value) { // previous memory access error word = 0; } else { - this.opTime += 0.117; // time for full-word transfer word = this.readMemory(); // address in E was previously set if (this.MET.value) { word = 0; } else { this.E.dec(); // step down to next memory address } + + this.execClock += 0.117; // time for full-word transfer } return word; @@ -2771,7 +2772,6 @@ B220Processor.prototype.cardatronReceiveWord = function cardatronReceiveWord(wor // Memory error has occurred: just ignore further data from Cardatron } else { // Full word accumulated -- process it and initialize for the next word - this.opTime += 0.117; // time for full-word transfer this.D.set(word); word %= 0x10000000000; // strip the sign digit sign = (this.D.value - word)/0x10000000000; // get D-sign @@ -2811,8 +2811,8 @@ B220Processor.prototype.cardatronReceiveWord = function cardatronReceiveWord(wor default: // sign is 8, 9: store word with optional B mod if (!(this.rDigit & 0x08)) { // no B-register modification this.IB.set(this.D.value); - } else { // add B to low-order five digits of word - word = word - word%0x100000 + this.bcdAdd(word, this.B.value, 5); + } else { // add B to low-order four digits of word + word = word - word%0x100000 + this.bcdAdd(word, this.B.value, 4); this.C10.set(0); // reset carry toggle this.IB.set((sign%2)*0x10000000000 + word); } @@ -2822,6 +2822,8 @@ B220Processor.prototype.cardatronReceiveWord = function cardatronReceiveWord(wor } break; } // switch sign + + this.execClock += 0.117; // time for full-word transfer } return returnCode; @@ -2864,139 +2866,81 @@ B220Processor.prototype.magTapeComplete = function magTapeComplete(alarm, contro }; /**************************************/ -B220Processor.prototype.magTapeSendBlock = function magTapeSendBlock(buffer, words) { - /* Sends a block of data from memory to the tape control unit. "buffer" is an - array of words to receive the data to be written to tape. "words" is the number - of words to place in the buffer, starting at the current operand address in the - C register. Returns true if the processor has been cleared or a memory address - error occurs, and the I/O must be aborted */ - var result = false; // return value - var that = this; // local context - var x = 0; // buffer index - - //console.log("TSU " + this.selectedUnit + " W, Len " + words + - // ", ADDR=" + this.CADDR.toString(16)); +B220Processor.prototype.magTapeSendWord = function magTapeSendWord(initial) { + /* Sends the next of data from memory to the tape control unit, starting at + the current operand address in the C register. "initial" is true if this + call is the first to fetch words for a block. This causes the routine to + save the current operand address in the control digits of C. Returns + binary -1 if the processor has been cleared or a memory address error + occurs, and the I/O must be aborted. Returns the BCD memory word otherwise */ + var result; // return value if (!this.AST.value) { - result = true; + result = -1; // we've probably been cleared } else { - while (x < words) { - this.E.set(this.CADDR); - this.CADDR = this.bcdAdd(this.CADDR, 1, 4); - this.readMemory(); - if (this.MET.value) { // invalid address - result = true; - break; // out of do-loop - } else { - buffer[x] = this.IB.value; - ++x; - } + if (initial) { + this.clockIn(); + this.CCONTROL = this.CADDR; // copy C address into control digits + } + + this.E.set(this.CADDR); + this.CADDR = this.bcdAdd(this.CADDR, 1, 4); + this.C.set((this.CCONTROL*0x100 + this.COP)*0x10000 + this.CADDR); + this.readMemory(); + if (this.MET.value) { // invalid address + result = -1; + } else { + result = this.IB.value; + this.D.set(result); + this.execClock += 0.480; // time to transfer one word to tape } } - this.C.set(this.C.value - this.C.value%0x10000 + this.CADDR); return result; }; /**************************************/ -B220Processor.prototype.magTapeReceiveBlock = function magTapeReceiveBlock(block, lastBlock) { - /* Called by the tape control unit to store a block of 20 words. If "lastBlock" is - true, it indicates this is the last block and the I/O is finished. If "block" - is null, that indicates the I/O was aborted and the block must not be stored - in memory. The block is stored in one of the loops, as determined by the - togMT1BV4 and togMT1BV5 control toggles. Sign digit adjustment and B-register - modification take place at this time. If the C-register operand address is - less than 8000, the loop is then stored at the current operand address, which - is incremented by blockFromLoop(). If this is the last block, schedule() - is called after the loop is stored to terminate the read instruction. Since - tape block reads take 46 ms, they are much longer than any loop-to-memory - transfer, so this routine simply exits after the blockFromLoop is initiated, - and the then processor waits for the next block to arrive from the tape, by - which time the blockFromLoop will (should?) have completed. Returns true if - the processor has been cleared and the tape control unit should abort the I/O */ - var aborted = false; // return value - var loop; +B220Processor.prototype.magTapeReceiveWord = function magTapeReceiveWord(initial, word) { + /* Stores the next of data from the tape control unit to memory, starting at + the current operand address in the C register. "initial" is true if this + call is the first to store words for a block. This causes the routine to + save the current operand address in the control digits of C. Returns + binary -1 if the processor has been cleared or a memory address error + occurs, and the I/O must be aborted. Returns 0 otherwise */ + var result = 0; // return value var sign; // sign digit - var that = this; - var w; // scratch word - var x; // scratch index - function blockStoreComplete() { - if (lastBlock) { - if (that.togMT3P) { // if false, we've probably been cleared - that.A = that.D = 0; // for display only - that.togMT3P = 0; - that.togMT1BV4 = that.togMT1BV5 = 0; - that.schedule(); - } - } else { - // Flip the loop buffer toggles - that.togMT1BV5 = that.togMT1BV4; - that.togMT1BV4 = 1-that.togMT1BV4; - // Suspend time again during I/O - that.execTime -= 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; + if (!this.AST.value) { + result = -1; // we've probably been cleared } else { - this.execTime += 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 (initial) { + this.clockIn(); + this.CCONTROL = this.CADDR; // copy C address into control digits } - 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>> 12) & 0x0F; - switch (this.CCONTROL%0x10) { - case 0: case 1: case 2: case 3: // MTS/MFS: search or field search - this.setProgramCheck(true); // TEMP // - this.operationComplete(); - break; - case 4: case 5: case 6: case 7: // MLS: lane select - this.ioInitiate(); - this.magTape.laneSelect(this.D.value, this.boundMagTapeComplete); - break; - case 8: case 9: // MRW/MDA: rewind, with or without lockout - this.ioInitiate(); - this.magTape.rewind(this.D.value, this.boundMagTapeComplete); - break; - default: // should never happen - this.setProgramCheck(true); - this.operationComplete(); - break; - } // switch on operation variant + this.selectedUnit = (this.CCONTROL >>> 12)%0x10; + this.vDigit = this.CCONTROL%0x10; + this.ioInitiate(); + if (this.vDigit & 0x08) { // MRW/MDA: rewind, with or without lockout + this.magTape.rewind(this.D.value, this.boundMagTapeComplete, this.boundMagTapeSendWord); + } else if (this.vDigit & 0x04) { // MLS: lane select + this.magTape.laneSelect(this.D.value, this.boundMagTapeComplete, this.boundMagTapeSendWord); + } else { // MTS/MFS: search or field search + if (this.D.value%0x80000000000 < 0x40000000000) { // full-word search + this.magTape.search(this.D.value, this.boundMagTapeComplete, 0, this.boundMagTapeSendWord); + } else { // partial-word search based on sL in B + this.magTape.search(this.D.value, this.boundMagTapeComplete, this.B.value, this.boundMagTapeSendWord); + } + } } break; @@ -3681,13 +3620,29 @@ B220Processor.prototype.execute = function execute() { break; case 0x52: //--------------------- MRD Magnetic tape read - this.setProgramCheck(1); - this.operationComplete(); + this.opTime = 0.160; + if (!this.magTape) { + this.setMagneticTapeCheck(true); // no tape control + this.operationComplete(); + } else { + this.selectedUnit = (this.CCONTROL >>> 12)%0x10; + this.vDigit = this.CCONTROL%0x10; + this.ioInitiate(); + this.magTape.read(this.D.value, this.boundMagTapeComplete, false, this.boundMagTapeReceiveWord); + } break; case 0x53: //--------------------- MRR Magnetic tape read, record - this.setProgramCheck(1); - this.operationComplete(); + this.opTime = 0.160; + if (!this.magTape) { + this.setMagneticTapeCheck(true); // no tape control + this.operationComplete(); + } else { + this.selectedUnit = (this.CCONTROL >>> 12)%0x10; + this.vDigit = this.CCONTROL%0x10; + this.ioInitiate(); + this.magTape.read(this.D.value, this.boundMagTapeComplete, true, this.boundMagTapeReceiveWord); + } break; case 0x54: //--------------------- MIW Magnetic tape initial write @@ -3696,11 +3651,9 @@ B220Processor.prototype.execute = function execute() { this.setMagneticTapeCheck(true); // no tape control this.operationComplete(); } else { - this.selectedUnit = (this.CCONTROL >>> 12) & 0x0F; - this.CCONTROL = this.CADDR; // copy C address into control digits - this.C.set((this.CCONTROL*0x100 + this.COP)*0x10000 + this.CADDR); + this.selectedUnit = (this.CCONTROL >>> 12)%0x10; this.ioInitiate(); - this.magTape.initialWrite(this.D.value, this.boundMagTapeComplete, this.boundMagTapeSendBlock); + this.magTape.initialWrite(this.D.value, this.boundMagTapeComplete, false, this.boundMagTapeSendWord); } break; @@ -3710,22 +3663,34 @@ B220Processor.prototype.execute = function execute() { this.setMagneticTapeCheck(true); // no tape control this.operationComplete(); } else { - this.selectedUnit = (this.CCONTROL >>> 12) & 0x0F; - this.CCONTROL = this.CADDR; // copy C address into control digits - this.C.set((this.CCONTROL*0x100 + this.COP)*0x10000 + this.CADDR); + this.selectedUnit = (this.CCONTROL >>> 12)%0x10; this.ioInitiate(); - this.magTape.initialWriteRecord(this.D.value, this.boundMagTapeComplete, this.boundMagTapeSendBlock); + this.magTape.initialWrite(this.D.value, this.boundMagTapeComplete, true, this.boundMagTapeSendWord); } break; case 0x56: //--------------------- MOW Magnetic tape overwrite - this.setProgramCheck(1); - this.operationComplete(); + this.opTime = 0.160; + if (!this.magTape) { + this.setMagneticTapeCheck(true); // no tape control + this.operationComplete(); + } else { + this.selectedUnit = (this.CCONTROL >>> 12)%0x10; + this.ioInitiate(); + this.magTape.overwrite(this.D.value, this.boundMagTapeComplete, false, this.boundMagTapeSendWord); + } break; case 0x57: //--------------------- MOR Magnetic tape overwrite, record - this.setProgramCheck(1); - this.operationComplete(); + this.opTime = 0.160; + if (!this.magTape) { + this.setMagneticTapeCheck(true); // no tape control + this.operationComplete(); + } else { + this.selectedUnit = (this.CCONTROL >>> 12)%0x10; + this.ioInitiate(); + this.magTape.overwrite(this.D.value, this.boundMagTapeComplete, true, this.boundMagTapeSendWord); + } break; case 0x58: //--------------------- MPF/MPB/MIE Magnetic tape position forward/backward/at end @@ -3734,7 +3699,7 @@ B220Processor.prototype.execute = function execute() { this.setMagneticTapeCheck(true); // no tape control this.operationComplete(); } else { - this.selectedUnit = (this.CCONTROL >>> 12) & 0x0F; + this.selectedUnit = (this.CCONTROL >>> 12)%0x10; this.ioInitiate(); switch (this.CCONTROL%0x10) { case 1: // MPB: position tape backward @@ -3780,9 +3745,9 @@ B220Processor.prototype.execute = function execute() { this.setCardatronCheck(1); this.operationComplete(); } else { - this.selectedUnit = (this.CCONTROL >>> 12) & 0x0F; - this.rDigit = this.CCONTROL & 0x0F; - this.vDigit = (this.CCONTROL >>> 4) & 0x0F; + this.selectedUnit = (this.CCONTROL >>> 12)%0x10; + this.rDigit = this.CCONTROL%0x10; + this.vDigit = (this.CCONTROL >>> 4)%0x10; this.ioInitiate(); d = this.cardatron.inputInitiate(this.selectedUnit, this.rDigit, this.boundCardatronReceiveWord); if (d < 0) { // invalid unit @@ -3800,9 +3765,9 @@ B220Processor.prototype.execute = function execute() { this.setCardatronCheck(1); this.operationComplete(); } else { - this.selectedUnit = (this.CCONTROL >>> 12) & 0x0F; - this.rDigit = this.CCONTROL & 0x0F; - this.vDigit = (this.CCONTROL >>> 4) & 0x0F; + this.selectedUnit = (this.CCONTROL >>> 12)%0x10; + this.rDigit = this.CCONTROL%0x10; + this.vDigit = (this.CCONTROL >>> 4)%0x10; this.ioInitiate(); d = this.cardatron.outputInitiate(this.selectedUnit, this.rDigit, this.vDigit, this.boundCardatronOutputWord, this.boundCardatronOutputFinished); @@ -3821,8 +3786,8 @@ B220Processor.prototype.execute = function execute() { this.setCardatronCheck(1); this.operationComplete(); } else { - this.selectedUnit = (this.CCONTROL >>> 12) & 0x0F; - this.rDigit = this.CCONTROL & 0x0F; + this.selectedUnit = (this.CCONTROL >>> 12)%0x10; + this.rDigit = this.CCONTROL%0x10; this.ioInitiate(); d = this.cardatron.inputFormatInitiate(this.selectedUnit, this.rDigit, this.boundCardatronOutputWord, this.boundCardatronOutputFinished); @@ -3841,8 +3806,8 @@ B220Processor.prototype.execute = function execute() { this.setCardatronCheck(1); this.operationComplete(); } else { - this.selectedUnit = (this.CCONTROL >>> 12) & 0x0F; - this.rDigit = this.CCONTROL & 0x0F; + this.selectedUnit = (this.CCONTROL >>> 12)%0x10; + this.rDigit = this.CCONTROL%0x10; this.ioInitiate(); d = this.cardatron.outputFormatInitiate(this.selectedUnit, this.rDigit, this.boundCardatronOutputWord, this.boundCardatronOutputFinished); @@ -3860,7 +3825,7 @@ B220Processor.prototype.execute = function execute() { if (!this.cardatron) { this.setCardatronCheck(1); } else { - this.selectedUnit = (this.CCONTROL >>> 12) & 0x0F; + this.selectedUnit = (this.CCONTROL >>> 12)%0x10; d = this.cardatron.inputReadyInterrogate(this.selectedUnit); if (d < 0) { // invalid unit this.setCardatronCheck(1); @@ -3879,7 +3844,7 @@ B220Processor.prototype.execute = function execute() { if (!this.cardatron) { this.setCardatronCheck(1); } else { - this.selectedUnit = (this.CCONTROL >>> 12) & 0x0F; + this.selectedUnit = (this.CCONTROL >>> 12)%0x10; d = this.cardatron.outputReadyInterrogate(this.selectedUnit); if (d < 0) { // invalid unit this.setCardatronCheck(1); @@ -3906,63 +3871,6 @@ B220Processor.prototype.execute = function execute() { this.operationComplete(); break; } // switch this.COP - - /*************************************************************************** - - - switch (-1) { - - case 0x40: //---------------- MTR Magnetic Tape Read - if (!this.magTape) { - //this.schedule(); - } else { - this.selectedUnit = (this.CCONTROL >>> 4) & 0x0F; - d = (this.CCONTROL >>> 8) & 0xFF; // number of blocks - this.togMT3P = 1; - this.togMT1BV4 = d%2; // select initial loop buffer - this.togMT1BV5 = 1-this.togMT1BV4; - this.execTime -= performance.now()*B220Processor.wordsPerMilli; // mark time during I/O - if (this.magTape.read(this.selectedUnit, d, this.boundMagTapeReceiveBlock)) { - this.OFT.set(1); // control or tape unit busy/not-ready - this.togMT3P = this.togMT1BV4 = this.togMT1BV5 = 0; - //this.schedule(); - } - } - break; - - case 0x42: //---------------- MTS Magnetic Tape Search - if (this.magTape) { - this.selectedUnit = (this.CCONTROL >>> 4) & 0x0F; - d = (this.CCONTROL >>> 8) & 0xFF; // lane number - if (this.magTape.search(this.selectedUnit, d, this.CADDR)) { - this.OFT.set(1); // control or tape unit busy/not-ready - } - } - //this.schedule(); - break; - - case 0x50: //---------------- MTW Magnetic Tape Write - if (!this.magTape) { - //this.schedule(); - } else { - this.selectedUnit = (this.CCONTROL >>> 4) & 0x0F; - d = (this.CCONTROL >>> 8) & 0xFF; // number of blocks - this.togMT3P = 1; - this.togMT1BV4 = d%2; // select initial loop buffer - this.togMT1BV5 = 1-this.togMT1BV4; - this.execTime -= performance.now()*B220Processor.wordsPerMilli; // mark time during I/O - if (this.magTape.write(this.selectedUnit, d, this.boundMagTapeInitiateSend)) { - this.OFT.set(1); // control or tape unit busy/not-ready - this.togMT3P = this.togMT1BV4 = this.togMT1BV5 = 0; - //this.schedule(); - } - } - break; - - default: //---------------- (unimplemented instruction -- alarm) - break; - } // switch this.COP - ***************************************************************************/ }; @@ -4025,6 +3933,7 @@ B220Processor.prototype.ioInitiate = function ioInitiate() { /* Initiates asynchronous mode of the processor for I/O */ this.AST.set(1); + this.updateGlow(1); // update the console lamps this.execLimit = 0; // kill the run() loop }; @@ -4348,9 +4257,22 @@ B220Processor.prototype.loadDefaultProgram = function loadDefaultProgram() { this.MM[ 2] = 0x1000540000; // MIW 0,1,10,100 this.MM[ 3] = 0x1750540100; // MIW 100,1,7,50 this.MM[ 4] = 0x1500550079; // MIR 79,1,5,00 - this.MM[ 5] = 0x1101540200; // MIW 200,1,1,1 - this.MM[ 6] = 0x1009500000; // MDA 1 - this.MM[ 7] = 0x7777009999; // HLT 9999,7777 + this.MM[ 5] = 0x1101542000; // MIW 2000,1,1,1 // write control block + + this.MM[ 6] = 0x1008500000; // MRW 1 + this.MM[ 7] = 0x1000560000; // MOW 0,1,10,100 + this.MM[ 8] = 0x1750560100; // MOW 100,1,7,50 + this.MM[ 9] = 0x1500570079; // MOR 79,1,5,00 + this.MM[ 10] = 0x1101012000; // MOW 2000,1,1,1 // TEMP: changed to a NOP + + this.MM[ 11] = 0x1008500000; // MRW 1 + this.MM[ 12] = 0x1000523000; // MRD 3000,1,10,0 + this.MM[ 13] = 0x1700524000; // MRD 4000,1,7,0 + this.MM[ 14] = 0x1500534350; // MRR 4350,1,5,0 + this.MM[ 15] = 0x1100534800; // MRR 4800,1,1,0 // should be a control block + + this.MM[ 16] = 0x1009500000; // MDA 1 + this.MM[ 17] = 0x7777009999; // HLT 9999,7777 this.MM[ 79] = 0x1900000000; // preface for 19 words, 80-98 this.MM[ 99] = 0x4000000000; // preface for 40 words, 100-139 @@ -4358,6 +4280,10 @@ B220Processor.prototype.loadDefaultProgram = function loadDefaultProgram() { this.MM[ 199] = 0x9900000000; // preface for 99 words, 200-298 this.MM[ 299] = 0x0000000000; // preface for 100 words, 300-399 + this.MM[2000] = 0x9920012002; // end-of-tape control word + this.MM[2001] = 0x9999999999; // storage for end-of-tape block state + this.MM[2002] = 0x9999008421; // HLT: target for end-of-tape control branch + // Simple counter speed test this.MM[ 80] = 0x0000120082; // ADD 82 this.MM[ 81] = 0x0000300080; // BUN 80 diff --git a/webUI/B220MagTapeControl.js b/webUI/B220MagTapeControl.js index 69afba8..ccc67ec 100644 --- a/webUI/B220MagTapeControl.js +++ b/webUI/B220MagTapeControl.js @@ -35,17 +35,9 @@ function B220MagTapeControl(p) { this.boundControlFinished = B220Util.bindMethod(this, B220MagTapeControl.prototype.controlFinished); this.boundTapeUnitFinished = B220Util.bindMethod(this, B220MagTapeControl.prototype.tapeUnitFinished); - this.boundReadReceiveBlock = B220Util.bindMethod(this, B220MagTapeControl.prototype.readReceiveBlock); - this.boundWriteTerminate = B220Util.bindMethod(this, B220MagTapeControl.prototype.writeTerminate); - this.boundWriteSendBlock = B220Util.bindMethod(this, B220MagTapeControl.prototype.writeSendBlock); - this.boundWriteInitiate = B220Util.bindMethod(this, B220MagTapeControl.prototype.writeInitiate); - this.boundSearchComplete = B220Util.bindMethod(this, B220MagTapeControl.prototype.searchComplete); + this.boundSwitch_Click = B220Util.bindMethod(this, B220MagTapeControl.prototype.switch_Click); this.currentUnit = null; // stashed tape unit object - this.memoryBlockCallback = null; // stashed block-sending/receiving call-back function - this.memoryTerminateCallback = null;// stashed memory-sending terminate call-back function - this.tapeBlock = new Float64Array(101); - // block buffer for tape I/O /* Set up the tape units from the system configuration. These can be any combination of Tape Storage Units (DataReaders) and DataFiles. The indexes @@ -69,10 +61,10 @@ function B220MagTapeControl(p) { u = this.config.getNode("MagTape.units", x); switch (u.type.substring(0, 2)) { case "MT": - this.tapeUnit[x] = new B220MagTapeDrive(u.type, x, this.config); + this.tapeUnit[x] = new B220MagTapeDrive(u.type, x, this, this.config); break; case "DF": - this.tapeUnit[x] = new B220DataFile(u.type, x, this.config); + this.tapeUnit[x] = new B220DataFile(u.type, x, this, this.config); break; default: this.tapeUnit[x] = null; @@ -90,13 +82,11 @@ B220MagTapeControl.prototype.$$ = function $$(e) { B220MagTapeControl.prototype.clear = function clear() { /* Initializes (and if necessary, creates) the panel state */ - this.MISC = 0; // Miscellaneous control register - this.C = 0; // C register (block number, etc.) + 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.blockCount = 0; // number of blocks for current operation this.blockWords = 0; // number of words/block for current operation this.controlBusy = false; // control unit is busy with read/write/search @@ -144,6 +134,16 @@ B220MagTapeControl.prototype.queuePendingOperation = function queuePendingOperat 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, releaseProcessor, callee, args) { /* If the control unit or the tape unit addressed by the unit field in dReg @@ -153,8 +153,8 @@ B220MagTapeControl.prototype.loadCommand = function loadCommand(dReg, releasePro calls the releaseProcessor call-back 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, this.unitIndex, this.blockCount, and this.blockWords from - the digits in T. Then returns true */ + Sets this.unitNr, this.unitIndex, and this.blockWords from the digits in T. + Sets this.currentUnit to the current tape unit object. Then returns true */ var c; // scratch var t = dReg%0x10000000000; // scratch var ux; // internal unit index @@ -164,31 +164,33 @@ B220MagTapeControl.prototype.loadCommand = function loadCommand(dReg, releasePro if (this.controlBusy) { this.queuePendingOperation(callee, args); } else { - this.MISC = 0; this.T = 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.blockWords = t%0x100; - this.blockCount = ((t - this.blockWords)/0x100)%0x10; - if (this.blockWords == 0) { - this.blockWords = 100; - } else { + if (this.blockWords > 0) { this.blockWords = B220Processor.bcdBinary(this.blockWords); + } else { + this.blockWords = 100; } this.C = this.unitNr*0x100000 + t*0x10 + c; - this.regMisc.update(this.MISC); + this.clearMisc(); this.regC.update(this.C); this.regT.update(this.T); this.unitIndex = ux = this.findDesignate(this.unitNr); if (ux < 0) { + this.reportStatus(2); // drive not ready, not present setCallback(this.mnemonic, this, 0, releaseProcessor, true); - } else if (this.tapeUnit[ux].busy) { - this.queuePendingOperation(callee, args); } else { - result = true; + this.currentUnit = this.tapeUnit[ux]; + if (this.currentUnit.busy || this.currentUnit.rewindLock) { + this.queuePendingOperation(callee, args); + } else { + result = true; + } } } @@ -201,8 +203,6 @@ B220MagTapeControl.prototype.controlFinished = function controlFinished(alarm) { back to simulate the amount of time the control unit is busy with an I/O. If alarm is true, sets the Processor's Magnetic Tape Check alarm. If another operation is pending, initiates that operation */ - var args; // pending Arguments object - var callee; // pending method to call //console.log(this.mnemonic + " controlFinished: " + alarm + ", busy=" + this.controlBusy); if (alarm) { @@ -211,20 +211,19 @@ B220MagTapeControl.prototype.controlFinished = function controlFinished(alarm) { this.controlBusy = false; if (this.pendingCallee !== null) { - callee = this.pendingCallee; - args = this.pendingArgs; - this.pendingCallee = this.pendingArgs = null; - callee.apply(this, args); + this.dequeuePendingOperation(); } }; /**************************************/ -B220MagTapeControl.prototype.tapeUnitFinished = function tapeUnitFinished(alarm) { +B220MagTapeControl.prototype.tapeUnitFinished = function tapeUnitFinished() { /* Call-back function passed to tape unit methods to signal when the unit has completed its asynchronous operation */ if (!this.controlBusy) { // if the control unit is currently idle... - this.controlFinished(alarm); // initiate any pending operation + if (this.pendingCallee !== null) { + this.dequeuePendingOperation(); + } } }; @@ -252,10 +251,58 @@ B220MagTapeControl.prototype.decrementBlockCount = function decrementBlockCount( }; /**************************************/ -B220MagTapeControl.prototype.ClearBtn_onClick = function ClearBtn_onClick(ev) { - /* Handle the click event for the tape control CLEAR button */ +B220MagTapeControl.prototype.clearMisc = function clearMisc() { + /* Resets this.regMisc and the individual lamps for that register */ + var bitNr; + var m = this.regMisc; - this.clearUnit(); + m.update(0); + for (bitNr=m.bits-1; bitNr>= 0; --bitNr) { + m.lamps[bitNr].set(0); + } +}; + +/**************************************/ +B220MagTapeControl.prototype.reportStatus = function reportStatus(code) { + /* Sets bits in the MISC register to indicate various drive and control unit + status and error conditions */ + + switch (code) { + case 1: // report tape unit ready + this.TX2Lamp.set(0); + this.TX10Lamp.set(0); + break; + case 2: // report tape unit not ready + this.TX2Lamp.set(1); + this.TX10Lamp.set(1); + break; + case 4: // read check + this.TYC1Lamp.set(1); + this.TYC2Lamp.set(1); + break; + } // switch code +}; + +/**************************************/ +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 }; /**************************************/ @@ -304,276 +351,219 @@ B220MagTapeControl.prototype.magTapeOnLoad = function magTapeOnLoad() { this.TX2Lamp = this.regMisc.lamps[0]; this.TX2Lamp.setCaption("TX2", true); - // C Register - this.regC = new PanelRegister(this.$$("CRegPanel"), 6*4, 4, "C_", "C"); - - // T Register + // 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); - this.$$("ClearBtn").addEventListener("click", - B220Util.bindMethod(this, B220MagTapeControl.prototype.ClearBtn_onClick)); + 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.readTerminate = function readTerminate() { - /* Terminates the read operation, sets the control to not-busy, and signals - the processor we are finished with the I/O */ +B220MagTapeControl.prototype.search = function search(dReg, releaseProcessor, bReg, fetchWord) { + /* Searches a tape unit for a block with a keyWord matching the word at the + operand address in memory. "bReg is the contents of the B register for a + search, or 0 for a full-word search. This routine is used by MTS and MFS */ + var alarm = false; // error result + var blocksLeft = true; // true => more blocks to process + var searchWord; // target word to search for + var that = this; // local self-reference - this.controlBusy = false; - this.currentUnit.readTerminate(); -}; + function signalControl(controlWord) { + /* Call-back function to send the EOT or control word to the Processor + and release it for the next operation */ -/**************************************/ -B220MagTapeControl.prototype.readReceiveBlock = function readReceiveBlock(block, abortRead) { - /* Receives the next block read by the tape unit. Sends the block to the - processor for storage in memory, updates the block counter, and if not - finished, requests the next block from the tape. Termination is a little - tricky here, as readTerminate() must be called to release the drive before - the block is stored in memory (and p.executeComplete() called to advance to - the next instruction, but if the memory call-back tells us the processor - has been cleared, we must release the drive after the attempt to store the - block in memory. Mess with the sequencing below at your peril */ - var lastBlock; - var t = B220Processor.bcdBinary(this.T); + releaseProcessor(false, true, controlWord); + } - if (abortRead) { - this.readTerminate(); - this.memoryBlockCallback(null, true); - } else { - // Decrement the block counter in the T register: - t = (t + 990)%1000; // subtract 1 from the counter field without overflow - this.T = B220Processor.binaryBCD(t); - this.regT.update(this.T); + function blockReady(alarm, control, controlWord, readBlock, completed) { + /* Call-back function when the drive is ready to send the next block + of data, or when it has encountered an error such as EOT. "alarm" + indicates that an error has occurred and the operation is to be aborted. + "control" inidicates that an EOT or control block was encountered, and + "controlWord" is to be passed to the Processor for handling. Otherwise, + if there are more blocks to write, fetches the next block from the + Processor and calls the drive's "readBlock" function, Finally calls + "completed" to finish the operation */ - // If there are more blocks to read, request the next one - lastBlock = (t < 10); - if (lastBlock) { - this.readTerminate(); - abortRead = this.memoryBlockCallback(block, true); + if (alarm) { + setCallback(that.mnemonic, that, 0, releaseProcessor, true); + completed(true); // drive detected an error + } else if (control) { + setCallback(that.mnemonic, that, 0, signalControl, controlWord); + completed(false); + } else if (blocksLeft) { + blocksLeft = that.decrementBlockCount(); // set to false on last block + readBlock(storeWord, record, controlEnabled);// read the next block } else { - abortRead = this.memoryBlockCallback(block, false); - if (abortRead) { // processor was cleared - this.readTerminate(); - } else { // at least one block left to go - this.currentUnit.readBlock(this.boundReadReceiveBlock); - } + setCallback(that.mnemonic, that, 0, releaseProcessor, false); + completed(false); // normal termination + } + } + + if (this.loadCommand(dReg, releaseProcessor, search, arguments)) { + this.controlBusy = true; + searchWord = fetchWord(true); + if (searchWord < 0) { + alarm = true; + } else { + alarm = this.currentUnit.searchBlock(blockReady, this.boundControlFinished); + } + + if (alarm) { + setCallback(this.mnemonic, this, 0, releaseProcessor, alarm); + this.controlFinished(true); } } }; /**************************************/ -B220MagTapeControl.prototype.read = function read(unitNr, blocks, blockSender) { - /* Initiates a read on the designated unit. "blocks" is the number of blocks - to read in BCD; "blockSender" is the call-back function to send a block of data - to the processor. "terminator" is the call-back function to tell the Processor - the I/O is finished. Returns true if the control is still busy with another command - or the unit is busy, and does not do the read */ - var result = false; // return value - var unit; // tape unit object - var ux; // internal unit index +B220MagTapeControl.prototype.read = function read(dReg, releaseProcessor, record, storeWord) { + /* 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. "storeWord" is a function to store a word to the + Processor's memory. This routine is used by MRD and MRR */ + var alarm = false; // error result + var blocksLeft = true; // true => more blocks to process + var controlEnabled = false; // true => control blocks will be recognized + var that = this; // local self-reference - if (this.controlBusy) { - result = true; - } else { - this.C = unitNr; - this.regC.update(unitNr); - ux = this.findDesignate(unitNr); - if (ux < 0) { - result = true; + function signalControl(controlWord) { + /* Call-back function to send the EOT or control word to the Processor + and release it for the next operation */ + + releaseProcessor(false, true, controlWord); + } + + function blockReady(alarm, control, controlWord, readBlock, completed) { + /* Call-back function when the drive is ready to send the next block + of data, or when it has encountered an error such as EOT. "alarm" + indicates that an error has occurred and the operation is to be aborted. + "control" inidicates that an EOT or control block was encountered, and + "controlWord" is to be passed to the Processor for handling. Otherwise, + if there are more blocks to write, fetches the next block from the + Processor and calls the drive's "readBlock" function, Finally calls + "completed" to finish the operation */ + + if (alarm) { + setCallback(that.mnemonic, that, 0, releaseProcessor, true); + completed(true); // drive detected an error + } else if (control) { + setCallback(that.mnemonic, that, 0, signalControl, controlWord); + completed(false); + } else if (blocksLeft) { + blocksLeft = that.decrementBlockCount(); // set to false on last block + readBlock(storeWord, record, controlEnabled);// read the next block } else { - this.controlBusy = true; - this.currentUnit = unit = this.tapeUnit[ux]; - this.MISC = B220Processor.binaryBCD(unit.laneNr); - this.regMisc.update(this.MISC); - this.T = blocks*0x10 + unitNr; - this.regT.update(this.T); - this.memoryBlockCallback = blockSender; - result = unit.readInitiate(this.boundReadReceiveBlock); - if (result) { - this.controlBusy = false; - } + setCallback(that.mnemonic, that, 0, releaseProcessor, false); + completed(false); // normal termination } } - return result; -}; - -/**************************************/ -B220MagTapeControl.prototype.writeTerminate = function writeTerminate(abortWrite) { - /* Called by the drive after the last block is written to release the - control unit and terminate the I/O. Note that "abortWrite" is not used, but - exists for signature compatibility with writeSendBlock() */ - - this.recordLamp.set(0); - this.controlBusy = false; - this.memoryTerminateCallback(); -}; - -/**************************************/ -B220MagTapeControl.prototype.writeSendBlock = function writeSendBlock(abortWrite) { - /* Called by the tape drive when it is ready for the next block to be written. - Retrieves the next buffered block from the Processor and passes it to the drive. - Unless this is the last block to write, the drive will call this again after - tape motion is complete. Note that this.memoryBlockCallback() will return true - if the processor has been cleared and the I/O must be aborted */ - var aborted; // true if processor aborted the I/O - var lastBlock = abortWrite; // true if this will be the last block - var t = B220Processor.bcdBinary(this.T); - - // First, decrement the block counter in the T register: - t = (t + 990)%1000; // subtract 1 from the counter field without overflow - this.T = B220Processor.binaryBCD(t); - this.regT.update(this.T); - if (t < 10) { - lastBlock = true; - } - - aborted = this.memoryBlockCallback(this.tapeBlock, lastBlock); - if (abortWrite || aborted) { - this.writeTerminate(false); - } else if (lastBlock) { - this.currentUnit.writeBlock(this.tapeBlock, this.boundWriteTerminate, true); - } else { - this.currentUnit.writeBlock(this.tapeBlock, this.boundWriteSendBlock, false); - } -}; - -/**************************************/ -B220MagTapeControl.prototype.writeInitiate = function writeInitiate(blockReceiver, terminator) { - /* Call-back function called by the Processor once the initial block to be - written is buffered in one of the high-speed loops. Once this block is - buffered, the drive can start tape motion and begin writing to tape */ - - this.memoryBlockCallback = blockReceiver; - this.memoryTerminateCallback = terminator; - this.currentUnit.writeInitiate(this.boundWriteSendBlock); -}; - -/**************************************/ -B220MagTapeControl.prototype.write = function write(unitNr, blocks, receiveInitiate) { - /* Initiates a write on the designated unit. "blocks" is the number of blocks - to write in BCD; "receiveInitiate" is the call-back function to begin memory - transfer from the processor. Returns true if the control is still busy with - another command or the unit is busy, and does not do the write */ - var result = false; // return value - var unit; // tape unit object - var ux; // internal unit index - - if (this.controlBusy) { - result = true; - } else { - this.C = unitNr; - this.regC.update(unitNr); - ux = this.findDesignate(unitNr); - if (ux < 0) { - result = true; - } else { - this.controlBusy = true; - this.currentUnit = unit = this.tapeUnit[ux]; - this.MISC = B220Processor.binaryBCD(unit.laneNr); - this.regMisc.update(this.MISC); - this.T = blocks*0x10 + unitNr; - this.regT.update(this.T); - result = unit.writeReadyTest(); - if (result) { - this.controlBusy = false; - } else { - receiveInitiate(this.boundWriteInitiate); - } + if (this.loadCommand(dReg, releaseProcessor, read, arguments)) { + this.controlBusy = true; + controlEnabled = (this.blockWords%2 == 0); // low-order bit of v-digit + alarm = this.currentUnit.readBlock(blockReady, this.boundControlFinished); + if (alarm) { + setCallback(this.mnemonic, this, 0, releaseProcessor, alarm); + this.controlFinished(true); } } - - return result; }; /**************************************/ -B220MagTapeControl.prototype.searchComplete = function searchComplete(success) { - /* Resets the busy status at the completion of a search */ - var d; // scratch digit - - if (success) { - // rotate T one digit right at end of successful search - d = this.T % 0x10; - this.T = d*0x1000 + (this.T - d)/0x10; - this.regT.update(this.T); - } - - this.controlBusy = false; -}; - -/**************************************/ -B220MagTapeControl.prototype.search = function search(unitNr, laneNr, addr) { - /* Initiates a search on the designated unit. "laneNr" is the lane number in - BCD; "addr" is the number of the block to search for in BCD. The search - Takes place in the control unit and drive independently of the processor. - Returns true if the control is still busy with another command or the unit - is busy, and does not do the search */ - var block = B220Processor.bcdBinary(addr); - var lane = B220Processor.bcdBinary(laneNr); - var result = false; // return value - var unit; // tape unit object - var ux; // internal unit index - - if (this.controlBusy) { - result = true; - } else { - this.C = unitNr; - this.regC.update(unitNr); - this.MISC = laneNr; - this.regMisc.update(laneNr); - this.T = addr; - this.regT.update(addr); - ux = this.findDesignate(unitNr); - if (ux < 0) { - result = true; - } else { - this.controlBusy = true; - unit = this.tapeUnit[ux]; - result = unit.search(lane, block, this.boundSearchComplete, - this.boundDirectionLampSet, this.boundTestDisabled); - if (result) { - this.controlBusy = false; - } - } - } - - return result; -}; - -/**************************************/ -B220MagTapeControl.prototype.initialWrite = function initialWrite(dReg, releaseProcessor, fetchBlock) { - /* Initial-writes the number of blocks and of the size indicated in dReg. - This routine is used by MIW */ +B220MagTapeControl.prototype.overwrite = function overwrite(dReg, releaseProcessor, record, fetchWord) { + /* 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. "fetchWord" is a function to + read a word from the Processor's memory. This routine is used by MOW and MOR */ var alarm = false; // error result var blocksLeft = true; // true => more blocks to process var that = this; // local self-reference - var unit = null; // selected tape unit + var words; + + function signalControl(controlWord) { + /* Call-back function to send the EOT control word to the Processor + and release it for the next operation */ + + releaseProcessor(false, true, controlWord); + } + + function blockReady(alarm, control, controlWord, writeBlock, completed) { + /* Call-back function when the drive is ready to receive the next block + of data, or when it has encountered an error such as EOT. "alarm" + indicates that an error has occurred and the operation is to be aborted. + "control" inidicates that an EOT block with a preface mismatch occurred, + and "controlWord" is to be passed to the Processor for handling. + Otherwise, if there are more blocks to write, fetches the next block + from the Processor and calls the drive's "writeBlock" function, Finally + calls "completed" to finish the operation */ + + if (alarm) { + setCallback(that.mnemonic, that, 0, releaseProcessor, true); + completed(true); // drive detected an error + } else if (control) { + setCallback(that.mnemonic, that, 0, signalControl, controlWord); + completed(false); + } else if (blocksLeft) { + blocksLeft = that.decrementBlockCount(); // set to false on last block + writeBlock(fetchWord, words); // write the next block + } else { + setCallback(that.mnemonic, that, 0, releaseProcessor, false); + completed(false); // normal termination + } + } + + if (this.loadCommand(dReg, releaseProcessor, overwrite, arguments)) { + this.controlBusy = true; + if (this.blockWords < this.currentUnit.minBlockWords && this.blockWords > 1) { + alarm = true; // invalid block length + } else { + words = (record ? 0 : this.blockWords); + alarm = this.currentUnit.overwriteBlock(blockReady, this.boundControlFinished); + } + + if (alarm) { + setCallback(this.mnemonic, this, 0, releaseProcessor, alarm); + this.controlFinished(true); + } + } +}; + +/**************************************/ +B220MagTapeControl.prototype.initialWrite = function initialWrite(dReg, releaseProcessor, record, fetchWord) { + /* 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. fetchWord" is a function to + read a word from the Processor's memory. This routine is used by MIW and MIR */ + var alarm = false; // error result + var blocksLeft = true; // true => more blocks to process + var that = this; // local self-reference + var words; function blockReady(alarm, writeBlock, completed) { /* Call-back function when the drive is ready to receive the next block - of data, or when it has encountered an error such as EOT. If there are - more blocks to write, fetches the next block from the Processor and calls - the drive's "writeBlock" function, otherwise calls "completed" to finish - the operation */ + of data, or when it has encountered an error such as EOT. "alarm" + indicates that an error has occurred and the operation is to be aborted. + Otherwise, if there are more blocks to write, fetches the next block + from the Processor and calls the drive's "writeBlock" function. Finally + calls "completed" to finish the operation */ if (alarm) { setCallback(that.mnemonic, that, 0, releaseProcessor, true); completed(true); // drive detected an error } else if (blocksLeft) { blocksLeft = that.decrementBlockCount(); // set to false on last block - if (that.blockWords < unit.minBlockWords && that.blockWords > 1) { - completed(true); // invalid block size - } else if (fetchBlock(that.tapeBlock, that.blockWords)) { - completed(true); // Processor cleared or memory address error - } else { - writeBlock(that.tapeBlock, that.blockWords); // write next block - } + writeBlock(fetchWord, words); // write the next block } else { setCallback(that.mnemonic, that, 0, releaseProcessor, false); completed(false); // normal termination @@ -582,70 +572,16 @@ B220MagTapeControl.prototype.initialWrite = function initialWrite(dReg, releaseP if (this.loadCommand(dReg, releaseProcessor, initialWrite, arguments)) { this.controlBusy = true; - unit = this.tapeUnit[this.unitIndex]; - alarm = unit.initialWriteBlock(blockReady, this.boundControlFinished); - if (alarm) { - setCallback(this.mnemonic, this, 0, releaseProcessor, alarm); - controlFinished(true); - } - } -}; - -/**************************************/ -B220MagTapeControl.prototype.initialWriteRecord = function initialWriteRecord(dReg, releaseProcessor, fetchBlock) { - /* Initial-writes the number of blocks indicated in dReg. Block lengths - (preface words) are taken from the word in memory preceding the data to be - written. This routine is used by MIR */ - var alarm = false; // error result - var blocksLeft = true; // true => more blocks to process - var that = this; // local self-reference - var unit = null; // selected tape unit - - function blockReady(alarm, writeBlock, completed) { - /* Call-back function when the drive is ready to receive the next block - of data, or when it has encountered an error such as EOT. If there are - more blocks to write, fetches the next block and its size from the - Processor and calls the drive's "writeBlock" function, otherwise calls - "completed" to finish the operation */ - var words; // words to write for block - - if (alarm) { - setCallback(that.mnemonic, that, 0, releaseProcessor, true); - completed(true); // drive detected an error - } else if (blocksLeft) { - blocksLeft = that.decrementBlockCount(); // set to false on last block - if (fetchBlock(that.tapeBlock, 1)) { // fetch the preface word - completed(true); // Processor cleared or memory address error - } else { - words = that.tapeBlock[0]; // convert preface to binary - words = ((words - words%0x100000000)/0x100000000)%0x100; - if (words) { - words = (words >>> 4)*10 + words%0x10; - } else { - words = 100; // preface == 0 => 100 - } - - if (words < unit.minBlockWords && words > 1) { - completed(true); // invalid block size - } else if (fetchBlock(that.tapeBlock, words)) { - completed(true); // Processor cleared or memory address error - } else { - writeBlock(that.tapeBlock, words); // write next block - } - } + if (this.blockWords < this.currentUnit.minBlockWords && this.blockWords > 1) { + alarm = true; // invalid block length } else { - setCallback(that.mnemonic, that, 0, releaseProcessor, false); - completed(false); // normal termination + words = (record ? 0 : this.blockWords); + alarm = this.currentUnit.initialWriteBlock(blockReady, this.boundControlFinished); } - } - if (this.loadCommand(dReg, releaseProcessor, initialWriteRecord, arguments)) { - this.controlBusy = true; - unit = this.tapeUnit[this.unitIndex]; - alarm = unit.initialWriteBlock(blockReady, this.boundControlFinished); if (alarm) { setCallback(this.mnemonic, this, 0, releaseProcessor, alarm); - controlFinished(true); + this.controlFinished(true); } } }; @@ -655,7 +591,6 @@ B220MagTapeControl.prototype.positionForward = function positionForward(dReg, re /* Positions the tape forward the number of blocks indicated in dReg */ var alarm = false; // error result var that = this; // local self-reference - var unit = null; // selected tape unit function blockFinished(nextBlock, completed) { /* Call-back function when the drive has finished spacing one block @@ -671,8 +606,7 @@ B220MagTapeControl.prototype.positionForward = function positionForward(dReg, re if (this.loadCommand(dReg, releaseProcessor, positionForward, arguments)) { this.controlBusy = true; - unit = this.tapeUnit[this.unitIndex]; - alarm = unit.positionForward(blockFinished, this.boundControlFinished); + alarm = this.currentUnit.positionForward(blockFinished, this.boundControlFinished); setCallback(this.mnemonic, this, 0, releaseProcessor, alarm); } }; @@ -682,7 +616,6 @@ B220MagTapeControl.prototype.positionBackward = function positionBackward(dReg, /* Positions the tape backward the number of blocks indicated in dReg */ var alarm = false; // error result var that = this; // local self-reference - var unit = null; // selected tape unit function blockFinished(nextBlock, completed) { /* Call-back function when the drive has finished spacing one block @@ -698,8 +631,7 @@ B220MagTapeControl.prototype.positionBackward = function positionBackward(dReg, if (this.loadCommand(dReg, releaseProcessor, positionBackward, arguments)) { this.controlBusy = true; - unit = this.tapeUnit[this.unitIndex]; - alarm = unit.positionBackward(blockFinished, this.boundControlFinished); + alarm = this.currentUnit.positionBackward(blockFinished, this.boundControlFinished); setCallback(this.mnemonic, this, 0, releaseProcessor, alarm); } }; @@ -713,13 +645,13 @@ B220MagTapeControl.prototype.positionAtEnd = function positionAtEnd(dReg, releas if (this.loadCommand(dReg, releaseProcessor, positionAtEnd, arguments)) { this.controlBusy = true; - alarm = this.tapeUnit[this.unitIndex].positionAtEnd(this.boundControlFinished); + alarm = this.currentUnit.positionAtEnd(this.boundControlFinished); setCallback(this.mnemonic, this, 0, releaseProcessor, alarm); } }; /**************************************/ -B220MagTapeControl.prototype.laneSelect = function laneSelect(dReg, releaseProcessor) { +B220MagTapeControl.prototype.laneSelect = function laneSelect(dReg, releaseProcessor, fetchWord) { /* Selects the tape lane of the designated unit. Returns an alarm if the unit does not exist or is not ready */ var alarm = false; // error result @@ -728,13 +660,14 @@ B220MagTapeControl.prototype.laneSelect = function laneSelect(dReg, releaseProce if (this.loadCommand(dReg, releaseProcessor,laneSelect, arguments)) { this.controlBusy = true; laneNr = ((this.C - this.C%0x100)/0x100)%2; - alarm = this.tapeUnit[this.unitIndex].laneSelect(laneNr, this.boundControlFinished); + fetchWord(true); // memory access for MTS/MFS not used by MLS + alarm = this.currentUnit.laneSelect(laneNr, this.boundControlFinished); setCallback(this.mnemonic, this, 0, releaseProcessor, alarm); } }; /**************************************/ -B220MagTapeControl.prototype.rewind = function rewind(dReg, releaseProcessor) { +B220MagTapeControl.prototype.rewind = function rewind(dReg, releaseProcessor, fetchWord) { /* Initiates rewind of the designated unit. Returns an alarm if the unit does not exist or is not ready */ var alarm = false; // error result @@ -745,7 +678,8 @@ B220MagTapeControl.prototype.rewind = function rewind(dReg, releaseProcessor) { this.controlBusy = true; laneNr = ((this.C - this.C%0x100)/0x100)%2; lockout = ((this.C - this.C%0x10)/0x10)%2; - alarm = this.tapeUnit[this.unitIndex].rewind(laneNr, lockout, this.boundTapeUnitFinished); + fetchWord(true); // memory access for MTS/MFS not used by MRW/MDA + alarm = this.currentUnit.rewind(laneNr, lockout); setCallback(this.mnemonic, this, 50, this.controlFinished, false); setCallback(this.mnemonic, this, 0, releaseProcessor, alarm); } @@ -795,7 +729,7 @@ B220MagTapeControl.prototype.clearUnit = function clearUnit() { /* Clears the internal state of the control unit */ this.clear(); - this.regMisc.update(this.MISC); + this.clearMisc(); this.regC.update(this.C); this.regT.update(this.T); }; @@ -814,6 +748,10 @@ B220MagTapeControl.prototype.shutDown = function shutDown() { } } - this.window.removeEventListener("beforeunload", B220MagTapeControl.prototype.beforeUnload); + 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(); }; \ No newline at end of file diff --git a/webUI/B220MagTapeDrive.js b/webUI/B220MagTapeDrive.js index b884352..c068c3d 100644 --- a/webUI/B220MagTapeDrive.js +++ b/webUI/B220MagTapeDrive.js @@ -82,12 +82,13 @@ "use strict"; /**************************************/ -function B220MagTapeDrive(mnemonic, unitIndex, config) { +function B220MagTapeDrive(mnemonic, unitIndex, tcu, config) { /* Constructor for the MagTapeDrive object */ var y = ((mnemonic.charCodeAt(2) - "A".charCodeAt(0))*32) + 304; this.config = config; // System configuration object this.mnemonic = mnemonic; // Unit mnemonic + this.tcu = tcu; // Tape Control Unit object this.unitIndex = unitIndex; // Internal unit number this.unitDesignate = -1; // External unit number @@ -145,8 +146,8 @@ B220MagTapeDrive.prototype.startOfBlockWords = 4; // inter-block tape gap + preface [words] B220MagTapeDrive.prototype.endOfBlockWords = 2; // end-of-block + erase gap [words] -B220MagTapeDrive.prototype.repositionWords = B220MagTapeDrive.prototype.endOfBlockWords + 2; - // number of words to reposition backwards after a turnaround +B220MagTapeDrive.prototype.repositionWords = 2; + // number of words to reposition back into the block after a turnaround B220MagTapeDrive.prototype.startTime = 3; // tape start time [ms] B220MagTapeDrive.prototype.stopTime = 3; @@ -371,6 +372,8 @@ B220MagTapeDrive.prototype.setTapeUnloaded = function setTapeUnloaded() { this.imgLength = 0; this.imgTopWordNr = 0; this.busy = false; + this.rewindLock = false; + this.rwlLamp.set(0); this.setTapeReady(false); this.setAtBOT(false); this.setAtEOT(false); @@ -871,7 +874,7 @@ B220MagTapeDrive.prototype.unloadTape = function unloadTape() { }; /**************************************/ -B220MagTapeDrive.prototype.tapeRewind = function tapeRewind(laneNr, lockout, unitFinished) { +B220MagTapeDrive.prototype.tapeRewind = function tapeRewind(laneNr, lockout) { /* Rewinds the tape. Makes the drive not-ready and delays for an appropriate amount of time depending on how far up-tape we are. Readies the unit again when the rewind is complete unless lockout is truthy */ @@ -885,7 +888,10 @@ B220MagTapeDrive.prototype.tapeRewind = function tapeRewind(laneNr, lockout, uni this.rewindLock = (lockout ? true : false); this.rwlLamp.set(this.rewindLock ? 1 : 0); this.setTapeReady(!this.rewindLock); - this.releaseUnit(unitFinished, false); + this.releaseUnit(null, false); + if (this.ready) { + this.tcu.tapeUnitFinished(); + } } function rewindDelay() { @@ -939,9 +945,37 @@ B220MagTapeDrive.prototype.tapeReposition = function tapeReposition(successor) { words) to reach the end of the erase gap and start of the inter-block gap for the next block. this.repositionWords is sized to approximate that 2ms delay */ + var done = false; // completion flag + var lane = this.image[this.laneNr]; // image data for current lane + var state = 1; // FSA state variable + var x = this.imgIndex-1; // lane image word index - this.imgIndex -= this.repositionWords; - this.moveTape(-this.repositionWords*this.inchesPerWord, this.turnaroundTime, successor); + do { + if (x <= 0) { + done = true; + } else { + switch (state) { + case 1: // initial state: skip backwards until erase-gap + if (lane[x] == this.markerEOB) { + state = 2; + } else { + --x; + } + break; + case 2: // skip over erase-gap words + if (lane[x] == this.markerEOB) { + --x; + } else { + done = true; + } + break; + } // switch state + } + } while (!done); + + x = this.imgIndex - x + this.repositionWords; // words to reposition + this.imgIndex -= x; + this.moveTape(-x*this.inchesPerWord, this.turnaroundTime, successor); }; /**************************************/ @@ -1078,236 +1112,704 @@ B220MagTapeDrive.prototype.tapeDriveOnload = function tapeDriveOnload() { }; /**************************************/ -B220MagTapeDrive.prototype.readBlock = function readBlock(receiveBlock) { - /* Reads the next block from the tape. If at EOT, makes the drive not ready - and terminates the read. After delaying for tape motion, calls the receiveBlock - callback function to pass the block to the control unit and thence the processor */ - var wx = this.blockNr*this.blockWords; - - if (this.blockNr >= this.imgLength) { - this.blockNr = this.imgLength; - this.setTapeReady(false); - this.setAtEOT(true); - this.designatedLamp.set(0); - this.busy = false; - receiveBlock(null, true); // terminate the I/O - } else { - if (this.atBOT) { - this.setAtBOT(false); - } - ++this.blockNr; - this.moveTape(this.blockLength, this.blockLength/this.tapeSpeed, function readBlockComplete() { - receiveBlock(this.image[this.laneNr].subarray(wx, wx+this.blockWords), false); - }); - } -}; - -/**************************************/ -B220MagTapeDrive.prototype.readTerminate = function readTerminate() { - /* Terminates a read operation on the tape drive and makes it not-busy */ - - this.designatedLamp.set(0); - this.busy = false; -}; - -/**************************************/ -B220MagTapeDrive.prototype.readInitiate = function readInitiate(receiveBlock) { - /* Initiates a read operation on the unit. If the drive is busy or not ready, - returns true. Otherwise, delays for the start-up time of the drive and calls - readBlock() to read the next block and send it to the Processor */ - var result = false; - - if (this.busy) { - result = true; // report unit busy - } else if (!this.ready) { - result = true; // report unit not ready - } else { - this.busy = true; - this.designatedLamp.set(1); - setCallback(this.mnemonic, this, this.startTime, this.readBlock, receiveBlock); - } - //console.log(this.mnemonic + " read: result=" + result.toString()); - - return result; -}; - -/**************************************/ -B220MagTapeDrive.prototype.writeBlock = function writeBlock(block, sendBlock, lastBlock) { - /* Writes the next block to the tape. If at EOT, makes the drive not ready - and terminates the write. Calls the sendBlock callback function to request the - next block from the processor */ - var lane = this.image[this.laneNr]; - var wx = this.blockNr*this.blockWords; - var x; - - if (this.blockNr >= this.imgLength) { - this.blockNr = this.imgLength; - this.setTapeReady(false); - this.setAtEOT(true); - this.designatedLamp.set(0); - this.busy = false; - sendBlock(true); // terminate the I/O - } else { - if (this.atBOT) { - this.setAtBOT(false); - } - - // If the NOT WRITE switch is on, do not copy the block data to the tape image - if (!this.notWrite) { - this.imgWritten = true; - if (this.blockNr > this.imgTopWordNr) { - this.imgTopWordNr = this.blockNr; - } - for (x=0; x= this.imgLength) { - this.blockNr = this.imgLength; - this.setTapeReady(false); - this.setAtEOT(true); - } - complete(this.blockNr == targetBlock); - } - - function searchBackward() { - /* While the tape is beyond the target block, moves backward one block and - is called by moveTape to evaluate the new block position */ - - --this.blockNr; - if (this.atEOT) { - this.setAtEOT(false); - } - - if (testDisabled()) { - finishSearch.call(this); - } else if (this.blockNr > targetBlock) { - this.moveTape(-this.blockLength, delay, searchBackward); - } else { - finishSearch.call(this); - } - } - - function searchForward() { - /* Searches forward until the tape is beyond the target block, then - reverses direction and does a backward search */ - - ++this.blockNr; - if (this.atBOT) { - this.setAtBOT(false); - } - - if (testDisabled()) { - finishSearch.call(this); - } else if (this.blockNr >= this.imgLength) { - this.blockNr = this.imgLength; - finishSearch.call(this); - } else if (this.blockNr <= targetBlock) { - this.moveTape(this.blockLength, delay, searchForward); - } else { - // We have searched forward (or are already forward) of the target block. - // Now change direction and search backwards to the target. - lampSet(false); // indicate backward motion - this.moveTape(-this.blockLength, this.turnaroundTime, searchBackward); - } - } - - if (this.busy) { - result = true; // report unit busy - } else if (!this.ready) { - result = true; // report unit not ready - } else { - this.busy = true; - this.laneNr = laneNr & 0x01; // TSU uses only low-order lane bit - this.designatedLamp.set(1); - - // Begin by searching forward until we are past the target block - lampSet(true); // indicate forward motion - this.moveTape(this.blockLength, delay+this.startTime, searchForward); - } - - //console.log(this.mnemonic + " search: result=" + result.toString()); - if (result) { - complete(false); - } - return result; -}; - -/**************************************/ -B220MagTapeDrive.prototype.initialWriteBlock = function initialWriteBlock(blockReady, controlFinished) { - /* Writes one block on edited (blank) tape. "blockReady" is a function - to be called after the block is written. "releaseControl" is a function - to call after the control signals completion (see release). This routine - is used for both MIW and MIR, as block lengths are determined by the - tape control unit */ +B220MagTapeDrive.prototype.searchBlock = function searchBlock(blockReady, releaseControl, laneNr) { + /* Searches blocks on tape. "blockReady" is a function to be called after a + block is searched. "releaseControl" is a function to call after the control + signals completion (see release). "laneNr" is the lane on the tape to search. + This routine is used for MTS and MFS */ var imgLength = this.imgLength; // physical end of tape index - var lane = this.image[this.laneNr]; // image data for current lane + var keyWord = 0; // keyWord read from block + var lane; // image data for current lane var result = false; - var startIndex = this.imgIndex; // initial index into lane buffer + var startDelay = this.startTime; // delay for drive startup and lane change var that = this; function release() { /* Releases the unit and control */ - that.releaseUnit(controlFinished, false); + that.releaseUnit(releaseControl, false); + } + + function finish() { + /* Wraps up the positioning and delays before releasing the unit and control */ + var delay = 16 - that.startTime; + + setCallback(that.mnemonic, that, delay, release); + } + + function turnaround() { + /* Repositions the tape after the last block is written */ + + that.tapeReposition(finish); + } + + function signalControl() { + /* Returns to the tape control after encountering an end-of-tape block, + after the tape has been repositioned to read the EOT block */ + + blockReady(false, true, keyWord, null, null, turnaround); + } + + function repositionAndSignal() { + /* Repositions the tape image so that an end-of-tape block can be read + by the next operation. Then terminates the I/O */ + + that.tapeReposition(signalControl); + } + + function finalizeBlockForward() { + /* Returns the keyWord to the tape control after searching one block in + a forward direction */ + + blockReady(false, false, keyWord, searchBlockForward, searchBlockBackward, turnaround); + } + + function abort() { + /* Aborts the I/O due to some error */ + + blockReady(true, false, 0, null, null, release); + } + + function setEOT() { + /* Sets EOT status after positioning to end-of-tape. Does not release + the control unit */ + + that.setAtEOT(true); + that.releaseUnit(null, false); + } + + function advance (index, successor) { + /* Advances the tape after a block is passed, then calls the successor */ + var len = index - that.imgIndex; // number of words passed + var delay = len*that.millisPerWord; // amount of tape spin time + + that.imgIndex = index; + that.moveTape(len*that.inchesPerWord, delay, successor); + } + + function searchBlockForward() { + /* Reads the start of the next block in the tape image in a forward + direction to obtain its keyword */ + var count = 0; // word counter + var done = false; // completion flag + var state = 1; // FSA state variable + var words = 0; // number of words from preface + var w; // current image word + var x = that.imgIndex; // lane image word index + + if (!that.ready) { + done = true; // drive went non-ready + advance.call(that, x, abort); + } else { + do { + if (x >= imgLength) { + done = true; // at EOT: just exit and leave everybody hanging... + that.busy = false; + advance.call(that, x, setEOT); + } else { + w = lane[x]; + + switch (state) { + case 1: // initial state: skip over flaw and intra-block words + if (w == that.markerGap) { + state = 2; + } else { + ++x; + } + break; + + case 2: // skip over inter-block gap and magnetic EOT words + if (w == that.markerGap) { + ++x; + } else if (w == that.markerEOT) { + ++x; + } else if (w >= 0) { + state = 3; // found the preface + } else { + state = 1; // ignore this block + } + break; + + case 3: // read the preface + ++x; + words = w; + if (words < that.minBlockWords && words > 1) { + done = true; // preface check: invalid block size + advance.call(that, x, abort); + } else { + state = 4; // advance to the keyWord + } + break; + + case 4: // read the keyword + ++x; + if (w < 0) { // block was shorter than preface indicated, + state = 1; // so ignore it and look for next block + } else { + done = true; + keyWord = w; + if (words == 1) { // signal end-of-tape block + advance.call(that, x, repositionAndSignal); + } else { // return keyWord to control + advance.call(that, x, finalizeBlockForward); + } + } + break; + } // switch state + } + } while (!done); + } + } + + function firstBlock() { + /* Called after the startTime delay to signal the control unit we are + ready for the first block of data */ + + blockReady(false, false, 0, searchBlockForward, searchBlockBackward, release); + } + + if (this.busy || this.rewindLock) { + result = true; // unit busy or in RWL + } else if (!this.ready) { + result = true; // unit not ready + } else if (this.atEOT) { + result = true; // tape at EOT + } else { + this.busy = true; + this.designatedLamp.set(1); + this.setAtBOT(false); + if (laneNr != this.laneNr) { + this.laneNr = laneNr; + startDelay += 70; // additional time for lane change + } + + // Begin with a delay for start-up time + lane = this.image[this.laneNr]; + setCallback(this.mnemonic, this, startDelay, firstBlock); + } + + //console.log(this.mnemonic + " searchBlock result=" + result.toString()); + return result; +}; + +/**************************************/ +B220MagTapeDrive.prototype.readBlock = function readBlock(blockReady, releaseControl) { + /* Reads blocks on tape. "blockReady" is a function to be called after a + block is read. "releaseControl" is a function to call after the control + signals completion (see release). This routine is used for both MRD and MRR, + as block lengths are determined by the tape control unit */ + var controlWord = 0; // stashed control word for end-of-tape or control block + var imgLength = this.imgLength; // physical end of tape index + var lane = this.image[this.laneNr]; // image data for current lane + var result = false; + var that = this; + + function release() { + /* Releases the unit and control */ + + that.releaseUnit(releaseControl, false); + } + + function finish() { + /* Wraps up the positioning and delays before releasing the unit and control */ + var delay = 18 - that.startTime - that.repositionTime; + + setCallback(that.mnemonic, that, delay, release); + } + + function turnaround() { + /* Repositions the tape after the last block is written */ + + that.tapeReposition(finish); + } + + function signalControl() { + /* Returns to the tape control after encountering an end-of-tape block, + using the control word stashed by readBlockNext. Note that tape must be + positioned to read the block after the end-of-tape block, but we are + already past the first word in the block, so we don't reposition backwards + in this case -- just leave the tape where it is and normal pre-block + scanning will find the next block */ + + blockReady(false, true, controlWord, null, finish); + } + + function repositionAndSignal() { + /* Repositions the tape image so that the block following a control block + can be read by the next operation. Then terminates the I/O */ + + that.tapeReposition(signalControl); + } + + function finalizeBlock() { + /* Returns to the tape control after completion of one block */ + + blockReady(false, false, 0, readBlockNext, turnaround); + } + + function abort() { + /* Aborts the I/O due to some error */ + + blockReady(true, false, 0, null, release); + } + + function repositionAndAbort() { + /* Repositions the tape image so that the block causing the abort can be + read by the next operation. Then aborts the I/O */ + + that.tapeReposition(abort); + } + + function setEOT() { + /* Sets EOT status after positioning to end-of-tape. Does not release + the control unit */ + + that.setAtEOT(true); + that.releaseUnit(null, false); + } + + function advance (index, successor) { + /* Advances the tape after a block is passed, then calls the successor */ + var len = index - that.imgIndex; // number of words passed + var delay = len*that.millisPerWord; // amount of tape spin time + + that.imgIndex = index; + that.moveTape(len*that.inchesPerWord, delay, successor); + } + + function readBlockNext(storeWord, record, controlEnabled) { + /* Reads the next block in the tape image. "storeWord" is a call-back + to store the next word to the Processor's memory. If "record" is true, + the preface is stored in memory before the data. If "controlEnabled" is + true, control blocks are recognized */ + var controlBlock = false; // true if control block detected + var count = 0; // word counter + var done = false; // completion flag + var firstWord = true; // flag for initial memory fetch + var sign = 0; // sign digit of keyword + var state = 1; // FSA state variable + var words = 0; // number of words from preface + var w; // current image word + var x = that.imgIndex; // lane image word index + + if (!that.ready) { + done = true; // drive went non-ready + advance.call(that, x, abort); + } else { + do { + if (x >= imgLength) { + done = true; // at EOT: just exit and leave everybody hanging... + that.busy = false; + advance.call(that, x, setEOT); + } else { + w = lane[x]; + + switch (state) { + case 1: // initial state: skip over flaw and intra-block words + if (w == that.markerGap) { + state = 2; + } else { + ++x; + } + break; + + case 2: // skip over inter-block gap and magnetic EOT words + if (w == that.markerGap) { + ++x; + } else if (w == that.markerEOT) { + ++x; + } else if (w >= 0) { + state = 3; // found the preface + } else { + done = true; // not a formatted tape + advance.call(that, x, repositionAndAbort); + } + break; + + case 3: // read the preface and check for EOT block + ++x; + words = w; + if (words == 1) { + state = 6; // detected end-of-tape block + } else if (words < that.minBlockWords && words > 1) { + done = true; // preface check: invalid block size + advance.call(that, x, abort); + } else { + state = 4; // normal or control block + } + break; + + case 4: // read the keyword, detect control block, and store the preface if necessary + ++x; + if (w < 0) { + done = true; // block was shorter than preface indicated + advance.call(that, x-1, repositionAndAbort); + } else { + if (controlEnabled) { + sign = (w - w%0x10000000000)%0x10; + if (sign == 7) { + controlBlock = true; + // strip sign digit from keyword (not entirely sure this should be done) + w %= 0x10000000000; + } + } + + if (record) { // store the preface word (with keyword sign if a control block) + sign = ((sign*0x10 + (words%100 - words%10)/10)*0x10 + words%10)*0x100000000; + done = (storeWord(firstWord, sign) < 0); // true if memory error + firstWord = false; + } + + if (controlBlock) { + --words; // prevent storing the control word + } + + if (done) { // error storing preface word + advance.call(that, x, abort); + } else { + w = storeWord(firstWord, w); + if (w < 0) { + done = true; // keyword memory store failed + advance.call(that, x, abort); + } else { + firstWord = false; + ++count; + state = 5; + } + } + } + break; + + case 5: // read and store the remaining block words + if (count < words) { + if (w < 0) { + done = true; // block was shorter than preface indicated + advance.call(that, x-1, repositionAndAbort); + } else { + w = storeWord(firstWord, w); + if (w < 0) { + done = true; // memory store failed + advance.call(that, x, abort); + } else { + firstWord = false; + ++x; + ++count; + } + } + } else if (controlBlock) { + if (w < 0) { + done = true; // block was shorter than preface indicated + advance.call(that, x-1, repositionAndAbort); + } else { + controlWord = w; + ++x; + state = 7; // deal with the control word after EOB + } + } else { + state = 7; // check for proper EOB + } + break; + + case 6: // fetch the control word for an end-of-tape block and terminate + controlWord = w; + ++x; + done = true; // signal end-of-tape block + advance.call(that, x, signalControl); + break; + + case 7: // check for proper end-of-block and terminate + done = true; + if (w != that.markerEOB) { + advance.call(that, x, repositionAndAbort); + } else { // block was longer than preface indicated + if (controlBlock) { + advance.call(that, x+that.endOfBlockWords, repositionAndSignal); + } else { + advance.call(that, x+that.endOfBlockWords, finalizeBlock); + } + } + break; + } // switch state + } + } while (!done); + } + } + + function firstBlock() { + /* Called after the startTime delay to signal the control unit we are + ready for the first block of data */ + + blockReady(false, false, 0, readBlockNext, release); + } + + if (this.busy || this.rewindLock) { + result = true; // unit busy or in RWL + } else if (!this.ready) { + result = true; // unit not ready + } else if (this.atEOT) { + result = true; // tape at EOT + } else { + this.busy = true; + this.designatedLamp.set(1); + this.setAtBOT(false); + + // Begin with a delay for start-up time + setCallback(this.mnemonic, this, this.startTime, firstBlock); + } + + //console.log(this.mnemonic + " readBlock result=" + result.toString()); + return result; +}; + +/**************************************/ +B220MagTapeDrive.prototype.overwriteBlock = function overwriteBlock(blockReady, releaseControl) { + /* Overwrites blocks on tape. "blockReady" is a function to be called after + a block is overwritten. "releaseControl" is a function to call after the + control signals completion (see release). This routine is used for both MOW + and MOR, as block lengths are determined by the tape control unit */ + var controlWord = 0; // stashed control word for end-of-tape block + var imgLength = this.imgLength; // physical end of tape index + var lane = this.image[this.laneNr]; // image data for current lane + var result = false; + var that = this; + + function release() { + /* Releases the unit and control */ + + that.releaseUnit(releaseControl, false); + } + + function finish() { + /* Wraps up the positioning and delays before releasing the unit and control */ + var delay = 18 - that.startTime - that.repositionTime; + + setCallback(that.mnemonic, that, delay, release); + } + + function turnaround() { + /* Repositions the tape after the last block is written */ + + that.tapeReposition(finish); + } + + function signalControl() { + /* Returns to the tape control after encountering an end-of-tape block, + using the control word stashed by writeBlock. Note that tape must be + positioned to read the block after the end-of-tape block, but we are + already past the first word in the block, so we don't reposition backwards + in this case -- just leave the tape where it is and normal pre-block + scanning will find the next block */ + + blockReady(false, true, controlWord, null, finish); + } + + function finalizeBlock() { + /* Returns to the tape control after completion of one block */ + + blockReady(false, false, 0, writeBlock, turnaround); + } + + function abort() { + /* Aborts the I/O due to some error */ + + blockReady(true, false, 0, null, release); + } + + function repositionAndAbort() { + /* Repositions the tape image so that the block causing the abort can be + read by the next operation. Then aborts the I/O */ + + that.tapeReposition(abort); + } + + function setEOT() { + /* Sets EOT status after positioning to end-of-tape. Does not release + the control unit */ + + that.setAtEOT(true); + that.releaseUnit(null, false); + } + + function advance (index, successor) { + /* Advances the tape after a block is passed, then calls the successor */ + var len = index - that.imgIndex; // number of words passed + var delay = len*that.millisPerWord; // amount of tape spin time + + that.imgIndex = index; + if (index > that.imgTopWordNr) { + that.imgTopWordNr = index; + } + that.moveTape(len*that.inchesPerWord, delay, successor); + } + + function writeBlock(fetchWord, words) { + /* Overwrites the next block in the tape image. "fetchWord" is a call-back + to retrieve the next word from the Processor's memory. "words" is the + number of words in the block, 0 => "record" mode */ + var count = 0; // word counter + var done = false; // completion flag + var firstWord = true; // flag for initial memory fetch + var state = 1; // FSA state variable + var w; // current image word + var x = that.imgIndex; // lane image word index + + if (!that.ready) { + done = true; // drive went non-ready + advance.call(that, x, abort); + } else { + do { + if (x >= imgLength) { + done = true; // at EOT: just exit and leave everybody hanging... + that.busy = false; + advance.call(that, x, setEOT); + } else { + w = lane[x]; + + switch (state) { + case 1: // initial state: skip over flaw and intra-block words + if (w == that.markerGap) { + state = 2; + } else { + ++x; + } + break; + + case 2: // skip over inter-block gap and magnetic EOT words + if (w == that.markerGap) { + ++x; + } else if (w == that.markerEOT) { + ++x; + } else if (w >= 0) { + state = 3; // found the preface + } else { + done = true; // not a formatted tape + advance.call(that, x, abort); + } + break; + + case 3: // check the preface + ++x; + if (words <= 0) { // overwrite, record: fetch preface + words = fetchWord(firstWord); + if (words < 0) { // memory fetch failed + done = true; + } else { // convert preface word to binary + firstWord = false; + words = ((words - words%0x100000000)/0x100000000)%0x100; + if (words > 0) { + words = (words >>> 4)*10 + words%0x10; + } else { + words = 100; // preface == 0 => 100 + } + } + } + + if (done) { // preface fetch failed + advance.call(that, x, abort); + } else if (w < that.minBlockWords && w > 1) { + done = true; // preface check: invalid block size + advance.call(that, x, abort); + } else if (w == words) { + state = 4; // preface match: overwrite the block + } else if (w == 1) { + state = 5; // preface mismatch on end-of-tape block + } else { + done = true; // other preface mismatch + advance.call(that, x, repositionAndAbort); + } + break; + + case 4: // overwrite the block words + if (count < words) { + if (w < 0) { + done = true; // block was shorter than preface indicated + advance.call(that, x-1, repositionAndAbort); + } else { + w = fetchWord(firstWord); + if (w < 0) { + done = true; // memory fetch failed + advance.call(that, x, abort); + } else { + firstWord = false; + lane[x] = w; + ++x; + ++count; + } + } + } else if (count < that.minBlockWords) { + if (w < 0) { // block was shorter than preface indicated + done = true; + advance.call(that, x-1, repositionAndAbort); + } else { + lane[x] = 0; + ++x; + ++count; + } + } else if (w == that.markerEOB) { + done = true; // normal block termination + advance.call(that, x+that.endOfBlockWords, finalizeBlock); + } else { + done = true; // block was longer than preface indicated + advance.call(that, x, repositionAndAbort); + } + break; + + case 5: // fetch the control word for an end-of-tape preface mismatch and terminate + controlWord = w; + ++x; + done = true; // signal end-of-tape block + advance.call(that, x, signalControl); + break; + } // switch state + } + } while (!done); + } + } + + function firstBlock() { + /* Called after the startTime delay to signal the control unit we are + ready for the first block of data */ + + blockReady(false, false, 0, writeBlock, release); + } + + if (this.busy || this.rewindLock) { + result = true; // unit busy or in RWL + } else if (!this.ready) { + result = true; // unit not ready + } else if (this.notWrite) { + result = true; // tape in not-write status + } else if (this.atEOT) { + result = true; // tape at EOT + } else { + this.busy = true; + this.imgWritten = true; + this.designatedLamp.set(1); + this.setAtBOT(false); + + // Begin with a delay for start-up time + setCallback(this.mnemonic, this, this.startTime, firstBlock); + } + + //console.log(this.mnemonic + " overwriteBlock result=" + result.toString()); + return result; +}; + +/**************************************/ +B220MagTapeDrive.prototype.initialWriteBlock = function initialWriteBlock(blockReady, releaseControl) { + /* Writes blocks on edited (blank) tape. "blockReady" is a function to be + called after the block is written. "releaseControl" is a function to call + after the control signals completion (see release). This routine is used for + both MIW and MIR, as block lengths are determined by the tape control unit */ + var imgLength = this.imgLength; // physical end of tape index + var lane = this.image[this.laneNr]; // image data for current lane + var result = false; + var that = this; + + function release() { + /* Releases the unit and control */ + + that.releaseUnit(releaseControl, false); } function finish() { @@ -1381,24 +1883,29 @@ B220MagTapeDrive.prototype.initialWriteBlock = function initialWriteBlock(blockR that.moveTape(len*that.inchesPerWord, delay, successor); } - function writeBlock(block, words) { - /* Writes the next block from the words in "block" */ + function writeBlock(fetchWord, words) { + /* Initial-writes the next block in the tape image. "fetchWord" is a + call-back to retrieve the next word from the Processor's memory. + "words" is the number of words in the block, 0 => "record" mode */ var count = 0; // word counter var done = false; // completion flag + var firstWord = true; // flag for initial memory fetch var gapCount = 0; // count of consecutive inter-block gap words var state = 1; // FSA state variable + var w; // memory word var x = that.imgIndex; // lane image word index if (!that.ready) { - done = true; - advance.call(that, x, abort); // drive went non-ready + done = true; // drive went non-ready + advance.call(that, x, abort); } else { do { if (x >= imgLength) { - done = true; // at EOT: just exit and leave everybody hanging... + done = true; // at EOT: just exit and leave everybody hanging... that.busy = false; advance.call(that, x, setEOT); } else { + switch (state) { case 1: // initial state: skip over flaw and intra-block words if (lane[x] == that.markerGap) { @@ -1407,6 +1914,7 @@ B220MagTapeDrive.prototype.initialWriteBlock = function initialWriteBlock(blockR ++x; } break; + case 2: // count 3 consecutive inter-block gap words if (lane[x] == that.markerGap) { ++gapCount; @@ -1418,21 +1926,51 @@ B220MagTapeDrive.prototype.initialWriteBlock = function initialWriteBlock(blockR } else if (lane[x] == that.markerMagEOT) { ++x; } else { - done = true; - that.imgIndex = x; - advance.call(that, x, abort); // not an edited tape + done = true; // not an edited tape + advance.call(that, x, abort); } break; + case 3: // write the preface - lane[x] = words; - ++x; - state = 4; + if (words <= 0) { // initial-write, record: fetch preface + words = fetchWord(firstWord); + if (words < 0) { // memory fetch failed + done = true; + } else { // convert preface word to binary + firstWord = false; + words = ((words - words%0x100000000)/0x100000000)%0x100; + if (words > 0) { + words = (words >>> 4)*10 + words%0x10; + } else { + words = 100; // preface == 0 => 100 + } + } + } + + if (done) { // preface fetch failed + advance.call(that, x, abort); + } else if (words < that.minBlockWords && words > 1) { + done = true; // invalid block size + advance.call(that, x, abort); + } else { + lane[x] = words; + ++x; + state = 4; + } break; + case 4: // write the block words if (count < words) { - lane[x] = block[count]; - ++x; - ++count; + w = fetchWord(firstWord); + if (w < 0) { + done = true; // memory fetch failed + advance.call(that, x, abort); + } else { + firstWord = false; + lane[x] = w; + ++x; + ++count; + } } else if (count < that.minBlockWords) { lane[x] = 0; ++x; @@ -1442,6 +1980,7 @@ B220MagTapeDrive.prototype.initialWriteBlock = function initialWriteBlock(blockR state = 5; } break; + case 5: // write the erase gap if (count < that.endOfBlockWords) { lane[x] = that.markerEOB; @@ -1465,8 +2004,8 @@ B220MagTapeDrive.prototype.initialWriteBlock = function initialWriteBlock(blockR blockReady(false, writeBlock, release); } - if (this.busy) { - result = true; // unit busy + if (this.busy || this.rewindLock) { + result = true; // unit busy or in RWL } else if (!this.ready) { result = true; // unit not ready } else if (this.notWrite) { @@ -1489,10 +2028,9 @@ B220MagTapeDrive.prototype.initialWriteBlock = function initialWriteBlock(blockR /**************************************/ B220MagTapeDrive.prototype.positionForward = function positionForward(blockFinished, releaseControl) { - /* Initiates the positioning of tape in a forward direction and spaces the - first block. "blockFinished" is a function to be called after the block is - spaced. "releaseControl" is a function to call after the last block is spaced - (see spaceForward and turnaround) */ + /* Positions tape in a forward direction. "blockFinished" is a function to + be called after a block is spaced. "releaseControl" is a function to call + after the last block is spaced (see spaceForward and turnaround) */ var lane = this.image[this.laneNr]; // image data for current lane var result = false; var that = this; @@ -1552,6 +2090,7 @@ B220MagTapeDrive.prototype.positionForward = function positionForward(blockFinis that.releaseUnit(releaseControl, true); } else { w = lane[x]; + switch (state) { case 1: // initial state: skip over flaw and intra-block words if (w == that.markerGap) { @@ -1560,6 +2099,7 @@ B220MagTapeDrive.prototype.positionForward = function positionForward(blockFinis ++x; } break; + case 2: // skip inter-block gap words if (w != that.markerGap) { state = 3; @@ -1567,6 +2107,7 @@ B220MagTapeDrive.prototype.positionForward = function positionForward(blockFinis ++x; } break; + case 3: // search for end of block (next inter-block gap word) if (w != that.markerGap) { ++x; @@ -1581,8 +2122,8 @@ B220MagTapeDrive.prototype.positionForward = function positionForward(blockFinis } - if (this.busy) { - result = true; // unit busy + if (this.busy || this.rewindLock) { + result = true; // unit busy or in RWL } else if (!this.ready) { result = true; // unit not ready } else if (this.atEOT) { @@ -1602,10 +2143,9 @@ B220MagTapeDrive.prototype.positionForward = function positionForward(blockFinis /**************************************/ B220MagTapeDrive.prototype.positionBackward = function positionBackward(blockFinished, releaseControl) { - /* Initiates the positioning of tape in a backward direction and spaces the - first block. "blockFinished" is a function to be called after the block is - spaced. "releaseControl" is a function to call after the last block is spaced - (see spaceBackward and finish) */ + /* Positions tape in a backward direction. "blockFinished" is a function to + be called after a block is spaced. "releaseControl" is a function to call + after the last block is spaced (see spaceBackward and finish) */ var lane = this.image[this.laneNr]; // image data for current lane var result = false; var that = this; @@ -1643,7 +2183,6 @@ B220MagTapeDrive.prototype.positionBackward = function positionBackward(blockFin function spaceBackward(blockFinished) { /* Spaces over the current or prior block until the inter-block gap */ var done = false; // completion flag - var imgLength = that.imgLength; // physical end of tape index var state = 1; // FSA state variable var w; // current image word var x = that.imgIndex; // lane image word index @@ -1659,16 +2198,18 @@ B220MagTapeDrive.prototype.positionBackward = function positionBackward(blockFin that.releaseUnit(releaseControl, true); } else { w = lane[x]; + switch (state) { case 1: // initial state: skip over flaw words if (w == that.markerGap) { state = 2; - } else if (w != that.markerFlaw) { - state = 3; - } else { + } else if (w == that.markerFlaw) { --x; + } else { + state = 3; } break; + case 2: // skip initial inter-block gap words if (w != that.markerGap) { state = 3; @@ -1676,6 +2217,7 @@ B220MagTapeDrive.prototype.positionBackward = function positionBackward(blockFin --x; } break; + case 3: // search for start of block (first prior inter-block gap word) if (w != that.markerGap) { --x; @@ -1683,6 +2225,7 @@ B220MagTapeDrive.prototype.positionBackward = function positionBackward(blockFin state = 4; } break; + case 4: // skip this block's inter-block gap words if (w != that.markerGap) { done = true; @@ -1697,8 +2240,8 @@ B220MagTapeDrive.prototype.positionBackward = function positionBackward(blockFin } while (!done); } - if (this.busy) { - result = true; // unit busy + if (this.busy || this.rewindLock) { + result = true; // unit busy or in RWL } else if (!this.ready) { result = true; // unit not ready } else if (this.atBOT) { @@ -1781,28 +2324,32 @@ B220MagTapeDrive.prototype.positionAtEnd = function positionAtEnd(releaseControl that.releaseUnit(releaseControl, true); } else { w = lane[x]; + switch (state) { case 1: // initial state: skip over flaw words if (w == that.markerGap) { state = 2; - } else if (w != that.markerFlaw) { - state = 3; - } else { + } else if (w == that.markerFlaw) { ++x; + } else { + state = 3; } break; + case 2: // count inter-block gap words if (w != that.markerGap) { state = 3; } else { - ++x; - ++gapCount; if (gapCount > that.startOfBlockWords) { done = true; advance.call(that, x, turnaround); + } else { + ++x; + ++gapCount; } } break; + case 3: // search for end of block (next inter-block gap word) if (w != that.markerGap) { ++x; @@ -1817,8 +2364,8 @@ B220MagTapeDrive.prototype.positionAtEnd = function positionAtEnd(releaseControl } - if (this.busy) { - result = true; // unit busy + if (this.busy || this.rewindLock) { + result = true; // unit busy or in RWL } else if (!this.ready) { result = true; // unit not ready } else if (this.atEOT) { @@ -1844,8 +2391,8 @@ B220MagTapeDrive.prototype.laneSelect = function laneSelect(laneNr, releaseContr var lane = laneNr%2; var result = false; - if (this.busy) { - result = true; // unit busy + if (this.busy || this.rewindLock) { + result = true; // unit busy or in RWL } else if (!this.ready) { result = true; // unit not ready } else { @@ -1864,20 +2411,20 @@ B220MagTapeDrive.prototype.laneSelect = function laneSelect(laneNr, releaseContr }; /**************************************/ -B220MagTapeDrive.prototype.rewind = function rewind(laneNr, lockout, unitFinished) { +B220MagTapeDrive.prototype.rewind = function rewind(laneNr, lockout) { /* Initiates a rewind operation on the unit. If the drive is busy or not ready, returns true. Otherwise, makes the drive not-ready, delays for an appropriate amount of time depending on how far up-tape we are, then readies the unit again */ var result = false; - if (this.busy) { - result = true; // unit busy + if (this.busy || this.rewindLock) { + result = true; // unit busy or in RWL } else if (!this.ready) { result = true; // unit not ready } else { this.designatedLamp.set(1); - this.tapeRewind(laneNr, lockout, unitFinished); + this.tapeRewind(laneNr, lockout); } //console.log(this.mnemonic + " rewind: lane=" + laneNr + ", lockout=" + lockout + ", result=" + result.toString()); diff --git a/webUI/B220Manifest.appcache b/webUI/B220Manifest.appcache index b8f7909..3bffc5d 100644 --- a/webUI/B220Manifest.appcache +++ b/webUI/B220Manifest.appcache @@ -1,5 +1,5 @@ CACHE MANIFEST -# retro-220 emulator 0.03a, 2017-10-19 08:45 +# retro-220 emulator 0.03b, 2017-11-02 12:30 CACHE: ../emulator/B220Processor.js B220.css