diff --git a/emulator/B220Processor.js b/emulator/B220Processor.js index 0c9d358..14beace 100644 --- a/emulator/B220Processor.js +++ b/emulator/B220Processor.js @@ -78,8 +78,8 @@ function B220Processor(config, devices) { this.IB = new B220Processor.Register(11*4, this, true); // memory Input Buffer // Processor throttling control and timing statistics - this.execTime = 0; // estimated internal processor time, ms - this.execLimit = 0; // current time slice limit on this.execTime, ms + this.execClock = 0; // emulated internal processor clock, ms + this.execLimit = 0; // current time slice limit on this.execClock, ms this.opTime = 0; // estimated time for current instruction, ms this.procStart = 0; // Javascript time that the processor started running, ms this.procTime = 0; // total internal running time for processor, ms @@ -106,12 +106,6 @@ function B220Processor(config, devices) { // Control Console Lamps this.digitCheckAlarm = new B220Processor.FlipFlop(this, false); - this.programCheckAlarm = new B220Processor.FlipFlop(this, false); - this.storageAlarm = new B220Processor.FlipFlop(this, false); - this.magneticTapeAlarm = new B220Processor.FlipFlop(this, false); - this.paperTapeAlarm = new B220Processor.FlipFlop(this, false); - this.cardatronAlarm = new B220Processor.FlipFlop(this, false); - this.highSpeedPrinterAlarm =new B220Processor.FlipFlop(this, false); this.systemNotReady = new B220Processor.FlipFlop(this, false); this.computerNotReady = new B220Processor.FlipFlop(this, false); @@ -190,7 +184,7 @@ function B220Processor(config, devices) { // Right-Hand Maintenance Panel Registers & Flip-Flops this.AX = new B220Processor.Register(10, this, false); // A exponent register - this.BI = new B220Processor.Register( 8, this, false); // paper tape decoder + this.BI = new B220Processor.Register( 8, this, false); // paper tape buffer inverters this.DX = new B220Processor.Register( 8, this, false); // D exponent register this.PA = new B220Processor.Register( 8, this, false); // PA register @@ -198,10 +192,10 @@ function B220Processor(config, devices) { this.AST = new B220Processor.FlipFlop(this, false); // asynchronous toggle this.CCT = new B220Processor.FlipFlop(this, false); // ?? toggle this.CRT = new B220Processor.FlipFlop(this, false); // Cardatron alarm toggle - this.DPT = new B220Processor.FlipFlop(this, false); // ?? digit pulse toggle + this.DPT = new B220Processor.FlipFlop(this, false); // decimal point toggle (SPO) this.EWT = new B220Processor.FlipFlop(this, false); // end of word toggle this.EXT = new B220Processor.FlipFlop(this, false); // fetch(0)/execute(1) toggle - this.HAT = new B220Processor.FlipFlop(this, false); // ?? toggle + this.HAT = new B220Processor.FlipFlop(this, false); // high-speed printer alarm toggle this.HCT = new B220Processor.FlipFlop(this, false); // halt control toggle, for SOR, SOH, IOM this.HIT = new B220Processor.FlipFlop(this, false); // high comparison toggle this.MAT = new B220Processor.FlipFlop(this, false); // multiple access toggle @@ -227,8 +221,8 @@ function B220Processor(config, devices) { // Context-bound routines this.boundUpdateLampGlow = B220Processor.bindMethod(this, B220Processor.prototype.updateLampGlow); - this.boundConsoleOutputSignDigit = B220Processor.bindMethod(this, B220Processor.prototype.consoleOutputSignDigit); - this.boundConsoleOutputNumberDigit= B220Processor.bindMethod(this, B220Processor.prototype.consoleOutputNumberDigit); + this.boundConsoleOutputSign = B220Processor.bindMethod(this, B220Processor.prototype.consoleOutputSign); + this.boundConsoleOutputChar = B220Processor.bindMethod(this, B220Processor.prototype.consoleOutputChar); this.boundConsoleOutputFinished = B220Processor.bindMethod(this, B220Processor.prototype.consoleOutputFinished); this.boundConsoleInputDigit = B220Processor.bindMethod(this, B220Processor.prototype.consoleInputDigit); this.boundConsoleReceiveDigit = B220Processor.bindMethod(this, B220Processor.prototype.consoleReceiveDigit); @@ -255,7 +249,7 @@ function B220Processor(config, devices) { * Global Constants * ***********************************************************************/ -B220Processor.version = "0.00c"; +B220Processor.version = "0.00d"; B220Processor.tick = 1000/200000; // milliseconds per clock cycle (200KHz) B220Processor.cyclesPerMilli = 1/B220Processor.tick; @@ -500,11 +494,6 @@ B220Processor.prototype.clear = function clear() { // Control Console Lamps this.digitCheckAlarm.set(0); - this.programCheckAlarm.set(0); - this.storageAlarm.set(0); - this.magneticTapeAlarm.set(0); - this.paperTapeAlarm.set(0); - this.cardatronAlarm.set(0); this.systemNotReady.set(0); this.computerNotReady.set(0); @@ -587,8 +576,13 @@ B220Processor.prototype.updateGlow = function updateGlow(beta) { /* Updates the lamp glow for all registers and flip-flops in the system. Beta is a bias in the range (0,1). For normal update use 0; to freeze the current state in the lamps use 1 */ + var clocking = (this.execClock < 0); var gamma = (this.RUT.value ? beta || 0 : 1); + if (clocking) { + this.clockIn(); + } + // Primary Registers this.A.updateGlow(gamma); this.B.updateGlow(gamma); @@ -602,11 +596,6 @@ B220Processor.prototype.updateGlow = function updateGlow(beta) { // Control Console Lamps this.digitCheckAlarm.updateGlow(gamma); - this.programCheckAlarm.updateGlow(gamma); - this.storageAlarm.updateGlow(gamma); - this.magneticTapeAlarm.updateGlow(gamma); - this.paperTapeAlarm.updateGlow(gamma); - this.cardatronAlarm.updateGlow(gamma); this.systemNotReady.updateGlow(gamma); this.computerNotReady.updateGlow(gamma); @@ -640,35 +629,42 @@ B220Processor.prototype.updateGlow = function updateGlow(beta) { } // Right-Hand Maintenance Panel Registers & Flip-Flops + this.ALT.updateGlow(gamma); + this.MET.updateGlow(gamma); + this.TAT.updateGlow(gamma); + this.PAT.updateGlow(gamma); + this.CRT.updateGlow(gamma); + this.HAT.updateGlow(gamma); + this.EXT.updateGlow(gamma); this.OFT.updateGlow(gamma); this.RPT.updateGlow(gamma); this.RUT.updateGlow(gamma); + if (this.rightPanelOpen) { this.AX.updateGlow(gamma); this.BI.updateGlow(gamma); this.DX.updateGlow(gamma); this.PA.updateGlow(gamma); - this.ALT.updateGlow(gamma); this.AST.updateGlow(gamma); this.CCT.updateGlow(gamma); this.CRT.updateGlow(gamma); this.DPT.updateGlow(gamma); this.EWT.updateGlow(gamma); - this.HAT.updateGlow(gamma); this.HCT.updateGlow(gamma); this.HIT.updateGlow(gamma); this.MAT.updateGlow(gamma); - this.MET.updateGlow(gamma); this.MNT.updateGlow(gamma); - this.PAT.updateGlow(gamma); this.PRT.updateGlow(gamma); this.PZT.updateGlow(gamma); this.SST.updateGlow(gamma); - this.TAT.updateGlow(gamma); this.UET.updateGlow(gamma); } + + if (clocking) { + this.clockOut(); + } }; @@ -682,14 +678,14 @@ B220Processor.Register = function Register(bits, p, invisible) { the timing members. "invisible" should be true if the register does not have a visible presence in the UI -- this will inhibit computing average lamp glow values for the register. - Note that it is important to increment this.execTime in the caller AFTER + Note that it is important to increment this.execClock in the caller AFTER setting new values in registers and flip-flops. This allows the average intensity to be computed based on the amount of time a bit was actually in that state */ this.bits = bits; // number of bits in register this.visible = (invisible ? false : true); - this.lastExecTime = 0; // time register was last set + this.lastExecClock = 0; // time register was last set this.p = p; // processor instance this.value = 0; // binary value of register: read-only externally @@ -723,10 +719,10 @@ B220Processor.Register.prototype.checkFC = function checkFC() { /**************************************/ B220Processor.Register.prototype.updateGlow = function updateGlow(beta) { - /* Updates the lamp glow averages based on this.p.execTime. Note that the + /* Updates the lamp glow averages based on this.p.execClock. Note that the glow is always aged by at least one clock tick. Beta is a bias in the range (0,1). For normal update, use 0; to freeze the current state, use 1 */ - var alpha = Math.min(Math.max(this.p.execTime-this.lastExecTime, B220Processor.tick)/ + var alpha = Math.min(Math.max(this.p.execClock-this.lastExecClock, B220Processor.tick)/ B220Processor.maxGlowTime + beta, 1.0); var alpha1 = 1.0-alpha; var b = 0; @@ -747,7 +743,7 @@ B220Processor.Register.prototype.updateGlow = function updateGlow(beta) { } } - this.lastExecTime = this.p.execTime; + this.lastExecClock = this.p.execClock; }; /**************************************/ @@ -786,7 +782,7 @@ B220Processor.Register.prototype.getBit = function getBit(bitNr) { B220Processor.Register.prototype.setBit = function setBit(bitNr, value) { /* Set a bit on or off in the register. Returns the new register value. Note that the glow is always aged by at least one clock tick */ - var alpha = Math.min(Math.max(this.p.execTime-this.lastExecTime, B220Processor.tick)/ + var alpha = Math.min(Math.max(this.p.execClock-this.lastExecClock, B220Processor.tick)/ B220Processor.maxGlowTime, 1.0); var bit = value%2; @@ -808,7 +804,7 @@ B220Processor.Register.prototype.setBit = function setBit(bitNr, value) { B220Processor.Register.prototype.flipBit = function flipBit(bitNr) { /* Complements a bit in the register. Returns the new register value. Note that the glow is always aged by at least one clock tick */ - var alpha = Math.min(Math.max(this.p.execTime-this.lastExecTime, B220Processor.tick)/ + var alpha = Math.min(Math.max(this.p.execClock-this.lastExecClock, B220Processor.tick)/ B220Processor.maxGlowTime, 1.0); var bit; @@ -847,13 +843,13 @@ B220Processor.FlipFlop = function FlopFlop(p, invisible) { Processor object, used to access the timing members. "invisible" should be true if the FF does not have a visible presence in the UI -- this will inhibit computing the average lamp glow value for it. - Note that it is important to increment this.execTime in the caller AFTER + Note that it is important to increment this.execClock in the caller AFTER setting new values in registers and flip-flops. This allows the average intensity to be computed based on the amount of time a bit was actually in that state */ this.visible = (invisible ? false : true); - this.lastExecTime = 0; // time register was last set + this.lastExecClock = 0; // time register was last set this.p = p; // processor instance this.value = 0; // binary value of register: read-only externally this.glow = 0; // average lamp glow value @@ -865,15 +861,14 @@ B220Processor.FlipFlop.prototype.updateGlow = function updateGlow(beta) { always aged by at least one clock tick. Beta is a bias in the range (0,1). For normal update, use 0; to freeze the current state, use 1. Returns the new average */ - var alpha = Math.min(Math.max(this.p.execTime-this.lastExecTime, B220Processor.tick)/ + var alpha = Math.min(Math.max(this.p.execClock-this.lastExecClock, B220Processor.tick)/ B220Processor.maxGlowTime + beta, 1.0); - var v = (this.value ? 1 : 0); if (this.visible) { - this.glow = this.glow*(1.0-alpha) + v*alpha; + this.glow = this.glow*(1.0-alpha) + this.value*alpha; } - this.lastExecTime = this.p.execTime; + this.lastExecClock = this.p.execClock; return this.glow; }; @@ -882,7 +877,7 @@ B220Processor.FlipFlop.prototype.set = function set(value) { /* Set the value of the FF. Use this rather than setting the value member directly so that average lamp glow can be computed. Returns the new value */ - this.value = value; + this.value = (value ? 1 : 0); if (this.visible) { this.updateGlow(0); } @@ -894,7 +889,7 @@ B220Processor.FlipFlop.prototype.set = function set(value) { B220Processor.FlipFlop.prototype.flip = function flip() { /* Complement the value of the FF. Returns the new value */ - return this.set(this.value ? 0 : 1); + return this.set(1-this.value); }; @@ -902,7 +897,33 @@ B220Processor.FlipFlop.prototype.flip = function flip() { * Timing and Statistics Functions * ***********************************************************************/ -// TBD +/**************************************/ +B220Processor.prototype.clockOut = function clockOut() { + /* Turns off the accumulation of emulated time for the current instruction + during I/O. Once the processor regains control, it should call this.clockIn + to increment this.execClock by the amount of elapsed time the I/O was + running asynchronously */ + var stamp = performance.now(); + + while (this.execClock > 0) { + this.execClock -= stamp; + } + + return stamp; +}; + +/**************************************/ +B220Processor.prototype.clockIn = function clockIn() { + /* Turns on the accumulation of emulated time for the current instruction + after an elapsed period of asynchronous I/O has completed */ + var stamp = performance.now(); + + while (this.execClock < 0) { + this.execClock += stamp; + } + + return stamp; +}; /*********************************************************************** @@ -927,7 +948,6 @@ B220Processor.prototype.setProgramCheck = function setProgramCheck(value) { /* Sets the Program Check alarm */ if (!this.ALARMSW) { - this.programCheckAlarm.set(value); this.ALT.set(value); if (value) { this.RUT.set(0); @@ -940,7 +960,6 @@ B220Processor.prototype.setStorageCheck = function setStorageCheck(value) { /* Sets the Storage Check alarm */ if (!this.ALARMSW) { - this.storageCheckAlarm.set(value); this.MET.set(value); if (value) { this.RUT.set(0); @@ -954,7 +973,6 @@ B220Processor.prototype.setMagneticTapeCheck = function setMagneticTapeCheck(val /* Sets the Magnetic Tape Check alarm */ if (!this.ALARMSW) { - this.magneticTapeAlarm.set(value); this.TAT.set(value); if (value) { this.RUT.set(0); @@ -967,7 +985,6 @@ B220Processor.prototype.setCardatronCheck = function setCardatronCheck(value) { /* Sets the Cardatron Check alarm */ if (!this.ALARMSW) { - this.cardatronAlarm.set(value); this.CRT.set(value); if (value) { this.RUT.set(0); @@ -980,7 +997,6 @@ B220Processor.prototype.setPaperTapeCheck = function setPaperTapeCheck(value) { /* Sets the Paper Tape Check alarm */ if (!this.ALARMSW) { - this.paperTapeAlarm.set(value); this.PAT.set(value); if (value) { this.RUT.set(0); @@ -993,8 +1009,7 @@ B220Processor.prototype.setHighSpeedPrinterCheck = function setHighSpeedPrinterC /* Sets the Cardatron Check alarm */ if (!this.ALARMSW) { - this.highSpeedPrinterAlarm.set(value); - this.CRT.set(value); + this.HAT.set(value); if (value) { this.RUT.set(0); } @@ -1866,63 +1881,116 @@ B220Processor.prototype.keyboardAdd = function keyboardAdd() { }; /**************************************/ -B220Processor.prototype.consoleOutputSignDigit = function consoleOutputSignDigit() { - /* Outputs the sign digit for a PTW (03) command and sets up to output the - first number digit. If the Shift Counter is already at 19, terminates the - output operation */ +B220Processor.prototype.consoleOutputSign = function consoleOutputSign(printSign) { + /* Outputs the sign character for a SPO (09) command and sets up to output the + first number digit */ var d; - var w = this.A.value % 0x10000000000; + var w; - if (this.togPO1) { // if false, we've probably been cleared - d = (this.A.value - w)/0x10000000000; // get the digit - this.A.set(w*0x10 + d); // rotate A+sign left one - if (this.SHIFT == 0x19) { // if the shift counter is already 19, we're done - this.togOK = this.togPO1 = 0; // for display only - this.consoleOutputFinished(); + this.clockIn(); + if (this.AST.value) { // if false, we've probably been cleared + d = this.bcdAdd(this.CCONTROL, 0x990); // decrement word count + this.CCONTROL += d%0x1000 - this.CCONTROL%0x1000; + this.C.set((this.CCONTROL*0x100 + this.COP)*0x10000 + this.CADDR); + this.E.set(this.CADDR); + this.readMemory(); + if (this.MET.value) { // invalid address + this.ioComplete(); } else { - this.togOK = 1-this.togOK; // for dislay only - this.togPO2 = 1; // for display only - this.togDELAY = 0; // for display only - this.console.writeSignDigit(d, this.boundConsoleOutputNumberDigit); + this.D.set(this.IB.value); + this.opTime += 0.700; // 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 + this.DC.set(0x10); + this.DPT.set(this.CCONTROL%0x10 == 1 && this.COP == 0x09); + this.LT1.set(this.LEADINGZEROESSW); // use LT1 for leading-zero suppression (probably not accurate) + this.EWT.set(0); + this.PZT.set(d == 2 && !this.HOLDPZTZEROSW); + this.PA.set(0x80 + d); // translate numerically + this.clockOut(); + printSign(this.PA.value, this.boundConsoleOutputChar); } } }; /**************************************/ -B220Processor.prototype.consoleOutputNumberDigit = function consoleOutputNumberDigit() { - /* Outputs a numeric digit for a PTW (03) command and sets up to output the - next number digit. If the Shift Counter is already at 19, terminates the +B220Processor.prototype.consoleOutputChar = function consoleOutputChar(printChar) { + /* Outputs the next character code for a SPO (09) command and sets up to output the + next number digit. If the Shift Counter is already at 20, terminates the output operation and sends a Finish signal */ var d; - var w = this.A.value % 0x10000000000; + var w; - if (this.togPO1) { // if false, we've probably been cleared - this.togOK = 1-this.togOK; // for dislay only - this.togPO2 = 1-this.togPO2; // for display only - this.togDELAY = 1-this.togDELAY;// for display only - if (this.SHIFT == 0x19) { // if the shift counter is already 19, we're done - d = (this.CADDR - this.CADDR%0x1000)/0x1000; - this.console.writeFinish(d, this.boundConsoleOutputFinished); + this.clockIn(); + if (this.AST.value) { // if false, we've probably been cleared + if (this.EWT.value) { + if (this.CCONTROL%0x1000 < 0x10) { + this.clockOut(); + printChar(0x35, this.boundConsoleOutputFinished); + } else { + this.C.add(1); + this.CADDR = this.C.value%0x10000; + this.clockOut(); + printChar(0x35, this.boundConsoleOutputSign); + } + } else if (this.PZT.value) { + // Output alphabetically + 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.DC.add(0x02); + this.PA.set(d); + if (this.DC.value >= 0x20) { + this.EWT.set(1); + } + this.clockOut(); + printChar(d, this.boundConsoleOutputChar); } else { - d = (this.A.value - w)/0x10000000000; // get the digit - this.A.set(w*0x10 + d); // rotate A+sign left one - this.SHIFT = this.bcdAdd(this.SHIFT, 1); - this.console.writeNumberDigit(d, this.boundConsoleOutputNumberDigit); + // Output numerically + if (this.DPT.value && !this.LEADINGZEROESSW) { + // decimal point may be needed + d = this.CCONTROL >>> 12; + if (this.DC.value + d > 0x19) { + this.DPT.set(0); + this.LT1.set(0); // stop any zero-suppression + this.PA.set(0x03); // decimal point code + this.clockOut(); + printChar(0x03, this.boundConsoleOutputChar); + return; // early exit + } + } + + do { // suppress leading zeroes if necessary + 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.DC.add(0x01); + } while (d == 0 && this.LT1.value && this.DC.value < 0x20); + + this.LT1.set(0); + this.D.set(this.D.value); + d += 0x80; // translate numerically + this.PA.set(d); + if (this.DC.value >= 0x20) { + this.EWT.set(1); + } + this.clockOut(); + printChar(d, this.boundConsoleOutputChar); } } }; /**************************************/ B220Processor.prototype.consoleOutputFinished = function consoleOutputFinished() { - /* Handles the final cycle of an I/O operation and restores this.execTime */ + /* Handles the final cycle of console output */ - if (this.togOK || this.togPO1) { // if false, we've probably been cleared - this.togOK = 0; // for display only - this.togPO1 = this.togPO2 = 0; // for display only - this.togDELAY = 0; // for display only - this.stopIdle = 0; // turn IDLE lamp back off now that we're done - this.execTime += performance.now()*B220Processor.wordsPerMilli; - this.schedule(); + this.clockIn(); + if (this.AST.value) { // if false, we've probably been cleared + this.EWT.set(0); + this.ioComplete(); } }; @@ -2485,7 +2553,7 @@ B220Processor.prototype.fetch = function fetch() { this.EXT.set(1); } - this.execTime += 0.090; // fetch uniformly requires 90 us + this.execClock += 0.090; // fetch uniformly requires 90 us }; @@ -2493,18 +2561,6 @@ B220Processor.prototype.fetch = function fetch() { * Execute Module * ***********************************************************************/ -/**************************************/ -B220Processor.prototype.operationComplete = function operationComplete() { - /* Implements Operation Complete for the Execute cycle. If we're not locked - in Execute, switch to Fetch cycle next */ - - if (this.FETCHEXECUTELOCKSW != 1) { - this.EXT.set(0); - } - - this.execTime += this.opTime; -}; - /**************************************/ B220Processor.prototype.execute = function execute() { /* Implements the Execute cycle of the 220 processor. This is initiated @@ -2532,66 +2588,116 @@ B220Processor.prototype.execute = function execute() { case 0x00: //--------------------- HLT Halt this.RUT.set(0); this.opTime = 0.010; + this.operationComplete(); break; case 0x01: //--------------------- NOP No operation // do nothing this.opTime = 0.010; + this.operationComplete(); break; case 0x03: //--------------------- PRD Paper tape read this.setProgramCheck(1); + this.operationComplete(); break; case 0x04: //--------------------- PRB Paper tape read, branch this.setProgramCheck(1); + this.operationComplete(); break; case 0x05: //--------------------- PRI Paper tape read, inverse format this.setProgramCheck(1); + this.operationComplete(); break; case 0x06: //--------------------- PWR Paper tape write - this.setProgramCheck(1); + this.opTime = 0.185; // just a guess... + this.AST.set(1); + d = this.CCONTROL >>> 12; // get unit number + if (d == 0) { + d = 10; // xlate unit 0 to unit 10 + } + + this.clockOut(); + d = this.console.outputUnitSelect(d, this.boundConsoleOutputSign); + if (d < 0) { // no unit available -- set alarm and quit + this.clockIn(); + this.AST.set(0); + this.setPaperTapeCheck(1); + this.operationComplete(); + } + break; + + case 0x07: //--------------------- PWI Paper tape write interrogate, branch + d = this.CCONTROL >>> 12; // get unit number + if (d == 0) { + d = 10; // xlate unit 0 to unit 10 + } + d = this.console.outputUnitSelect(d, B220Processor.emptyFunction); + if (d < 0) { // if not ready, continue in sequence + this.opTime = 0.015; + } else { // if ready, branch to operand address + this.P.set(this.CADDR); + this.opTime = 0.035; + } + this.operationComplete(); break; case 0x08: //--------------------- KAD Keyboard add this.D.set(0); this.setStop(); + this.operationComplete(); break; case 0x09: //--------------------- SPO Supervisory print-out - this.setProgramCheck(1); + this.opTime = 0.185; // just a guess... + this.AST.set(1); + this.clockOut(); + d = this.console.outputUnitSelect(0, this.boundConsoleOutputSign); + if (d < 0) { // no unit available -- set alarm and quit + this.clockIn(); + this.AST.set(0); + this.setPaperTapeCheck(1); + this.operationComplete(); + } break; case 0x10: //--------------------- CAD/CAA Clear add/add absolute this.SUT.set(0); this.A.value = this.IB.value - this.IB.value%0x10000000000; // 0 with sign of IB this.integerAdd(this.CCONTROL % 0x10 == 1); + this.operationComplete(); break; case 0x11: //--------------------- CSU/CSA Clear subtract/subtract absolute this.SUT.set(1); this.A.value = this.IB.value - this.IB.value%0x10000000000; // 0 with sign of IB this.integerAdd(this.CCONTROL % 0x10 == 1); + this.operationComplete(); break; case 0x12: //--------------------- ADD/ADA Add/add absolute this.SUT.set(0); this.integerAdd(this.CCONTROL % 0x10 == 1); + this.operationComplete(); break; case 0x13: //--------------------- SUB/SUA Subtract/subtract absolute this.SUT.set(1); this.integerAdd(this.CCONTROL % 0x10 == 1); + this.operationComplete(); break; case 0x14: //--------------------- MUL Multiply this.integerMultiply(); + this.operationComplete(); break; case 0x15: //--------------------- DIV Divide this.integerDivide(); + this.operationComplete(); break; case 0x16: //--------------------- RND Round @@ -2611,93 +2717,115 @@ B220Processor.prototype.execute = function execute() { this.opTime += 0.060; // account for add cycle } this.R.set(0); // unconditionally clear R + this.operationComplete(); break; case 0x17: //--------------------- EXT Extract this.integerExtract(); + this.operationComplete(); break; case 0x18: //--------------------- CFA/CFR Compare field A/R this.setProgramCheck(1); + this.operationComplete(); break; case 0x19: //--------------------- ADL Add to location this.setProgramCheck(1); + this.operationComplete(); break; case 0x20: //--------------------- IBB Increase B, branch this.setProgramCheck(1); + this.operationComplete(); break; case 0x21: //--------------------- DBB Decrease B, branch this.setProgramCheck(1); + this.operationComplete(); break; case 0x22: //--------------------- FAD/FAA Floating add/add absolute this.SUT.set(0); this.floatingAdd(this.CCONTROL % 0x10 == 1); + this.operationComplete(); break; case 0x23: //--------------------- FSU/FSA Floating subtract/subtract absolute this.SUT.set(1); this.floatingAdd(this.CCONTROL % 0x10 == 1); + this.operationComplete(); break; case 0x24: //--------------------- FMU Floating multiply this.floatingMultiply(); + this.operationComplete(); break; case 0x25: //--------------------- FDV Floating divide this.floatingDivide(0); + this.operationComplete(); break; case 0x26: //--------------------- IFL Increase field location this.setProgramCheck(1); + this.operationComplete(); break; case 0x27: //--------------------- DFL Decrease field location this.setProgramCheck(1); + this.operationComplete(); break; case 0x28: //--------------------- DLB Decrease field location, load B this.setProgramCheck(1); + this.operationComplete(); break; case 0x29: //--------------------- RTF Record transfer this.setProgramCheck(1); + this.operationComplete(); break; case 0x30: //--------------------- BUN Branch, unconditionally this.P.set(this.CADDR); this.opTime = 0.035; + this.operationComplete(); break; case 0x31: //--------------------- BOF Branch, overflow this.setProgramCheck(1); + this.operationComplete(); break; case 0x32: //--------------------- BRP Branch, repeat this.setProgramCheck(1); + this.operationComplete(); break; case 0x33: //--------------------- BSA Branch, sign A this.setProgramCheck(1); + this.operationComplete(); break; case 0x34: //--------------------- BCH/BCL Branch, comparison high/low this.setProgramCheck(1); + this.operationComplete(); break; case 0x35: //--------------------- BCE/BCU Branch, comparison equal/unequal this.setProgramCheck(1); + this.operationComplete(); break; case 0x36: //--------------------- BFA Branch, field A this.setProgramCheck(1); + this.operationComplete(); break; case 0x37: //--------------------- BFR Branch, field R this.setProgramCheck(1); + this.operationComplete(); break; case 0x38: //--------------------- BCS Branch, control switch @@ -2707,128 +2835,155 @@ B220Processor.prototype.execute = function execute() { this.opTime += 0.020; this.P.set(this.CADDR); } + this.operationComplete(); break; case 0x39: //--------------------- SO*/IOM Set overflow remember/halt, Interrogate overflow mode this.setProgramCheck(1); + this.operationComplete(); break; case 0x40: //--------------------- ST* Store A/R/B this.setProgramCheck(1); + this.operationComplete(); break; case 0x41: //--------------------- LDR Load R this.setProgramCheck(1); + this.operationComplete(); break; case 0x42: //--------------------- LDB/LBC Load B/B complement this.setProgramCheck(1); + this.operationComplete(); break; case 0x43: //--------------------- LSA Load sign A this.setProgramCheck(1); + this.operationComplete(); break; case 0x44: //--------------------- STP Store P this.setProgramCheck(1); + this.operationComplete(); break; case 0x45: //--------------------- CL* Clear A/R/B this.setProgramCheck(1); + this.operationComplete(); break; case 0x46: //--------------------- CLL Clear location this.setProgramCheck(1); + this.operationComplete(); break; case 0x48: //--------------------- SR* Shift right A/A and R/A with sign this.setProgramCheck(1); + this.operationComplete(); break; case 0x49: //--------------------- SL* Shift left A/A and R/A with sign this.setProgramCheck(1); + this.operationComplete(); break; case 0x50: //--------------------- MT* Magnetic tape search/field search/lane select/rewind this.setProgramCheck(1); + this.operationComplete(); break; case 0x51: //--------------------- MTC/MFC Magnetic tape scan/field scan this.setProgramCheck(1); + this.operationComplete(); break; case 0x52: //--------------------- MRD Magnetic tape read this.setProgramCheck(1); + this.operationComplete(); break; case 0x53: //--------------------- MRR Magnetic tape read, record this.setProgramCheck(1); + this.operationComplete(); break; case 0x54: //--------------------- MIW Magnetic tape initial write this.setProgramCheck(1); + this.operationComplete(); break; case 0x55: //--------------------- MIR Magnetic tape initial write, record this.setProgramCheck(1); + this.operationComplete(); break; case 0x56: //--------------------- MOW Magnetic tape overwrite this.setProgramCheck(1); + this.operationComplete(); break; case 0x57: //--------------------- MOR Magnetic tape overwrite, record this.setProgramCheck(1); + this.operationComplete(); break; case 0x58: //--------------------- MPF/MPB Magnetic tape position forward/backward/at end this.setProgramCheck(1); + this.operationComplete(); break; case 0x59: //--------------------- MIB/MIE Magnetic tape interrogate, branch/end of tape, branch this.setProgramCheck(1); + this.operationComplete(); break; case 0x60: //--------------------- CRD Card read this.setProgramCheck(1); + this.operationComplete(); break; case 0x61: //--------------------- CWR Card write this.setProgramCheck(1); + this.operationComplete(); break; case 0x62: //--------------------- CRF Card read, format load this.setProgramCheck(1); + this.operationComplete(); break; case 0x63: //--------------------- CWF Card write, format load this.setProgramCheck(1); + this.operationComplete(); break; case 0x64: //--------------------- CRI Card read interrogate, branch this.setProgramCheck(1); + this.operationComplete(); break; case 0x65: //--------------------- CWI Card write interrogate, branch this.setProgramCheck(1); + this.operationComplete(); break; case 0x66: //--------------------- HPW High speed printer write this.setProgramCheck(1); + this.operationComplete(); break; case 0x67: //--------------------- HPI High speed printer interrogate, branch this.setProgramCheck(1); + this.operationComplete(); break; default: //--------------------- Invalid op code -- set Program Check alarm this.setProgramCheck(1); + this.operationComplete(); break; } // switch this.COP - // Operation Complete: - this.operationComplete(); - /*************************************************************************** @@ -3245,6 +3400,51 @@ B220Processor.prototype.execute = function execute() { * Processor Run Control * ***********************************************************************/ +/**************************************/ +B220Processor.prototype.operationComplete = function operationComplete() { + /* Implements Operation Complete for the Execute cycle. If we're not locked + in Execute, switch to Fetch cycle next */ + + this.execClock += this.opTime; + if (this.FETCHEXECUTELOCKSW != 1) { + this.EXT.set(0); // set to FETCH state + } + + if (this.ORDERCOMPLEMENTSW) { + this.C.flipBit(16); // complement low order bit of op code + this.COP ^= 0x01; + } + + if (!this.RUT.value) { // halted + this.stop(); + } else if (this.SST.value) { + this.stop(); // single-stepping + } else if (this.SONSW) { + if (this.STOCSW) { // check for post-execute S-to-C stop + if (this.SUNITSSW) { + if (this.C.value%0x10 == this.S.value%0x10) { + this.stop(); + } + } else if (this.C.value%0x10000 == this.S.value) { + this.stop(); + } + } + } +}; + +/**************************************/ +B220Processor.prototype.ioComplete = function operationComplete() { + /* Implements completion of the Execute cycle for an I/O instruction that + has been executing asynchronously */ + + this.operationComplete(); + this.AST.set(0); + this.procTime += this.execClock; + if (this.RUT.value) { + this.schedule(); + } +}; + /**************************************/ B220Processor.prototype.run = function run() { /* Main execution control loop for the processor. Called from this.schedule() @@ -3260,39 +3460,12 @@ B220Processor.prototype.run = function run() { this.schedule() to restart execution again once memory transfers have completed */ - this.execLimit = this.execTime + B220Processor.timeSlice; + this.execLimit = this.execClock + B220Processor.timeSlice; do { - switch (1) { - case this.AST.value: // doing I/O -- just exit - this.execLimit = 0; - break; - - case this.EXT.value: // enter execute cycle + if (this.EXT.value) { // enter EXECUTE cycle this.execute(); - if (this.ORDERCOMPLEMENTSW) { - this.C.flipBit(16); // complement low order bit of op code - this.COP ^= 0x01; - } - - if (!this.RUT.value) { // halted - this.stop(); - } else if (this.SST.value) { - this.stop(); // single-stepping - } else if (this.SONSW) { - if (this.STOCSW) { // check for post-execute S-to-C stop - if (this.SUNITSSW) { - if (this.C.value%0x10 == this.S.value%0x10) { - this.stop(); - } - } else if (this.C.value%0x10000 == this.S.value) { - this.stop(); - } - } - } - break; - - default: // enter fetch cycle + } else { // enter FETCH cycle if (this.SONSW) { // check for post-fetch S-to-P stop if (this.STOPSW) { // must check before P is incremented in fetch() if (this.SUNITSSW) { @@ -3311,7 +3484,7 @@ B220Processor.prototype.run = function run() { } break; } - } while (this.execTime < this.execLimit); + } while (this.execClock < this.execLimit && !this.AST.value); }; /**************************************/ @@ -3342,19 +3515,14 @@ B220Processor.prototype.schedule = function schedule() { this.procSlackAvg*B220Processor.slackAlpha1; } - // Accumulate internal processor run time if it's been idle due to I/O. - while (this.procTime < 0) { - this.procTime += stamp; - } + this.procTime -= this.execClock; // prepare to accumulate internal processor time // Execute the time slice. - runStamp = stamp; // starting clock time for time slice - this.procTime -= this.execTime; // prepare to accumulate internal processor time + runStamp = stamp; // starting clock time for time slice this.run(); stamp = performance.now(); - this.procTime += this.execTime; // accumulate internal processor time for the slice this.procRunAvg = (stamp - runStamp)*B220Processor.slackAlpha + this.procRunAvg*B220Processor.slackAlpha1; @@ -3362,13 +3530,13 @@ B220Processor.prototype.schedule = function schedule() { if (!this.RUT.value) { // Processor is stopped, just inhibit delay averaging on next call and exit. this.lastDelayStamp = 0; + this.procTime += this.execClock; // accumulate internal processor time for the slice } else if (this.AST.value) { - // Processor is still running, but idle during I/O. + // Processor is still running, but idle during I/O. Do not update this.procTime. this.lastDelayStamp = 0; - while (this.procTime >= 0) { - this.procTime -= stamp; // keep the internal processor timer running - } } else { + this.procTime += this.execClock; // accumulate internal processor time for the slice + // The processor is still running, so schedule next time slice after a // throttling delay. delayTime is the number of milliseconds the // processor is running ahead of real-world time. Web browsers have a @@ -3378,7 +3546,7 @@ B220Processor.prototype.schedule = function schedule() { // hope). If the delay is greater than the minimum, setCallback() will // reschedule us after that delay. - delayTime = this.execTime - stamp; + delayTime = this.execClock - stamp; this.delayRequested = delayTime; this.delayLastStamp = stamp; this.scheduler = setCallback(this.mnemonic, this, delayTime, this.schedule); @@ -3391,12 +3559,13 @@ B220Processor.prototype.start = function start() { var stamp = performance.now(); if (this.poweredOn && !this.RUT.value && !this.AST.value && - !this.programCheckAlarm.value && !this.storageAlarm.value && - !this.magneticTapeAlarm.value && !this.cardatronAlarm.value && - !this.paperTapeAlarm.value && !this.highSpeedPrinterAlarm.value && + !this.digitCheckAlarm.value && + !this.ALT.value && !this.MET.value && + !this.TAT.value && !this.CRT.value && + !this.PAT.value && !this.HAT.value && !this.systemNotReady.value && !this.computerNotReady.value) { this.procStart = stamp; - this.execTime = stamp; + this.execClock = stamp; this.delayLastStamp = 0; this.delayRequested = 0; this.RUT.set(1); @@ -3469,7 +3638,7 @@ B220Processor.prototype.setCycle = function setCycle(cycle) { if (this.poweredOn) { if (!this.RUT.value) { - this.EXT.set(cycle ? 1 : 0); + this.EXT.set(cycle); } } }; @@ -3489,20 +3658,19 @@ B220Processor.prototype.resetRunTimer = function resetRunTimer() { /**************************************/ B220Processor.prototype.resetTransfer = function resetTransfer() { - /* Performs a Reset and Transfer operation, storing P in address 0000/04 - and C in 0000/64. Then branches to address 0001 */ - var w; + /* Initiates a Reset and Transfer operation, storing P in address 0000/04 + and C in 0000/64, then branching to address 0001. Always active, even + when running */ if (this.poweredOn) { + this.E.set(0x0000); + this.readMemory(); + this.IB.add((this.C.value % 0x10000)*0x10000 + (this.P.value % 0x10000) - + this.IB.value % 0x100000000); + this.writeMemory(); + this.P.set(0x0001); + this.EXT.set(0); // set to Fetch cycle if (!this.RUT.value) { - this.E.set(0x0000); - w = this.readMemory(); - w += (this.C.value % 0x10000)*0x10000 + (this.P.value % 0x10000) - - w % 0x100000000; - this.IB.set(w); - this.writeMemory(); - this.P.set(0x0001); - this.EXT.set(0); // set to Fetch cycle this.start(); } } @@ -3584,18 +3752,16 @@ B220Processor.prototype.loadDefaultProgram = function loadDefaultProgram() { this.MM[ 10] = 0x0000360200 // BT6 200 this.MM[ 11] = 0x0000370220 // BT7 220 this.MM[ 12] = 0x0000206980 // CU 6980 branch to loop-6 entry point + *************************************************/ // Hello World - this.MM[ 20] = 0x0000070500; // PTWF 0500 line feed - this.MM[ 21] = 0x0000640027; // CAD 27 - this.MM[ 22] = 0x0000030410; // PTW 0410 - this.MM[ 23] = 0x0000070800; // PTWF 0800 space - this.MM[ 24] = 0x0000640028; // CAD 28 - this.MM[ 25] = 0x0000030410; // PTW 0410 - this.MM[ 26] = 0x0000089429; // HALT 9429 - this.MM[ 27] = 0x4845535356; // LIT "HELLO" - this.MM[ 28] = 0x6656595344; // LIT "WORLD" + this.MM[ 20] = 0x0030090022; // SPO 22 + this.MM[ 21] = 0x0000009999; // HLT 9999 + this.MM[ 22] = 0x21648455353; // LIT R'HELL' + this.MM[ 23] = 0x25600665659; // LIT 'O WOR' + this.MM[ 24] = 0x25344000016; // LIT 'LD 'R + /************************************************* // Tom Sawyer's "Square Roots 100" (Babylonian or Newton's method): this.MM[ 100] = 0x640139; // CAD 139 this.MM[ 101] = 0x120138; // ST 138 diff --git a/software/BALGOL/BALGOL-Generator.bacg b/software/BALGOL/BALGOL-Generator.bacg index df4e36a..4174499 100644 --- a/software/BALGOL/BALGOL-Generator.bacg +++ b/software/BALGOL/BALGOL-Generator.bacg @@ -220,7 +220,7 @@ SEQ PLAC ADDR WORD LABEL OPCODE OPERAND ' IS A 4 210 0155 0 4410 40 2449 STA TBL+1/44 211 0156 0 4204 27 2451 DFL TBL+3/42,4 212 0157 0 0000 30 0134 BUN COMMENT - 213 0158 0 0000 10 2449 SETSCAN.1 CRD TBL+1 + 213 0158 0 0000 10 2449 SETSCAN.1 CAD TBL+1 214 0159 1 4410 40 1677 -STA IA/44 215 0160 0 0000 30 0514 BUN RETURN 216 diff --git a/webUI/B220.html b/webUI/B220.html index d849242..8a496d6 100644 --- a/webUI/B220.html +++ b/webUI/B220.html @@ -30,11 +30,7 @@ - - - + + + + + diff --git a/webUI/B220Common.css b/webUI/B220Common.css index 9381422..838fcc6 100644 --- a/webUI/B220Common.css +++ b/webUI/B220Common.css @@ -308,7 +308,7 @@ DIV.blackControlKnobBottomCaption { DIV.panelSurface { position: absolute; - background-color: #E4DDCD; /* was #D8C5BC; putty #EDEAE8; */ + background-color: #D8C5BC; /* was #E4DDCD; putty #EDEAE8; */ color: black} DIV.panelRegister { diff --git a/webUI/B220ConsoleKeyboard.html b/webUI/B220ConsoleKeyboard.html index 0a50dcb..0697a3a 100644 --- a/webUI/B220ConsoleKeyboard.html +++ b/webUI/B220ConsoleKeyboard.html @@ -33,11 +33,11 @@ class=keyboardBtn>C - - - diff --git a/webUI/B220ConsoleKeyboard.js b/webUI/B220ConsoleKeyboard.js index ab30a39..4383ba1 100644 --- a/webUI/B220ConsoleKeyboard.js +++ b/webUI/B220ConsoleKeyboard.js @@ -23,7 +23,7 @@ function B220ConsoleKeyboard(p) { this.window = null; // window object, null if not displayed this.enabled = false; // true if keyboard is active - this.boundKeypress = B220Processor.bindMethod(this, B220ConsoleKeyboard.prototype.keypress); + this.boundKeypress = B220Util.bindMethod(this, B220ConsoleKeyboard.prototype.keypress); this.boundButton_Click = B220Util.bindMethod(this, B220ConsoleKeyboard.prototype.button_Click); this.boundKeyboard_OnLoad = B220Util.bindMethod(this, B220ConsoleKeyboard.prototype.keyboardOnLoad); this.boundKeyboard_Unload = B220Util.bindMethod(this, B220ConsoleKeyboard.prototype.keyboardUnload); @@ -150,14 +150,22 @@ B220ConsoleKeyboard.prototype.keypress = function keypress(ev) { this.animateClick(this.$$("EBtn")); this.p.keyboardAction(-3); break; - case 0x0D: // Enter key = EXAM + case 0x58: case 0x78: // "X", "x" this.animateClick(this.$$("ExamBtn")); this.p.keyboardAction(-4); break; + case 0x0D: // Enter key = ENT + this.animateClick(this.$$("EntBtn")); + this.p.keyboardAction(-5); + break; + case 0x53: case 0x73: // "S", "s" + this.animateClick(this.$$("StepBtn")); + this.p.keyboardAction(-6); + break; case 0: // Firefox reports only graphic charCodes for keypress if (ev.keyCode == 0x0D) { // check keyCode instead - this.animateClick(this.$$("ExamBtn")); - this.p.keyboardAction(-4); + this.animateClick(this.$$("EntBtn")); + this.p.keyboardAction(-5); } break; } // switch c @@ -175,7 +183,7 @@ B220ConsoleKeyboard.prototype.keyboardOpen = function keyboardOpen() { if (!this.window) { this.window = window.open("../webUI/B220ConsoleKeyboard.html", this.mnemonic, - "location=no,scrollbars=no,resizable,width=" + w + ",height=" + h + + "resizable,width=" + w + ",height=" + h + ",left=" + (screen.availWidth - w) + ",top=" + (screen.availHeight - h)); this.window.addEventListener("load", this.boundKeyboard_OnLoad, false); } diff --git a/webUI/B220ConsolePrinter.css b/webUI/B220ConsolePrinter.css new file mode 100644 index 0000000..9dcadd1 --- /dev/null +++ b/webUI/B220ConsolePrinter.css @@ -0,0 +1,294 @@ +/*********************************************************************** +* retro-220/webUI B220PaperTapePunch.css +************************************************************************ +* Copyright (c) 2017, Paul Kimpel. +* Licensed under the MIT License, see +* http://www.opensource.org/licenses/mit-license.php +************************************************************************ +* Burroughs 220 Console Printer Unit. +************************************************************************ +* 2017-03-17 P.Kimpel +* Original version, from retro-205 D205ConsoleOutput.css. +***********************************************************************/ + +#PrinterBody { + height: 100%; + min-height: 100%; + overflow: hidden; + padding: 0} + +#Printer { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: 4px; + border-radius: 16px} + +#LineFeedBtn { + left: 16px; + top: 8px; + box-shadow: 3px 3px 2px #999} +#LineFeedBtnCaption { + left: 48px; + top: 18px} + +#CarriageReturnBtn { + right: 16px; + top: 8px; + box-shadow: 3px 3px 2px #999} +#CarriageReturnBtnCaption { + right: 48px; + top: 18px} + +#OpenPanelBtn { + position: absolute; + top: 8px; + left: calc(50% - 50px); + width: 100px} + +#ClosePanelBtn { + position: absolute; + top: 8px; + left: calc(50% - 50px); + width: 100px} + +#PrinterPlaten { + position: absolute; + left: 16px; + right: 16px; + top: 48px; + bottom: 16px; + min-height: 64px; + min-width: 120px; + overflow-x: hidden; + overflow-y: scroll; + color: black; + background-color: white; + padding: 4px; + border: 1px solid gray} + +#Paper { + margin-left: 0; + margin-right: 0; + margin-top: 2000px; + margin-bottom: 0; + padding: 0} + +#EndOfPaper { + display: block; + margin: 0; + padding: 0; + opacity: 0} +#EndOfPaper.hidden { + display: none} + +#FormatControlsDiv { + position: absolute; + overflow: visible; + display: none; + left: calc(50% - 312px); + top: 48px; + z-index: 10; + height: 216px; + width: 624px; + border-radius: 8px; + border: 2px solid black; + box-shadow: 3px 3px 2px #999; + color: black; + background-color: #E4DDCD} + +#RemoteKnob { + position: absolute; + left: 36px; + top: 60px} +#RemoteKnobRemoteCaption { + left: 36px; + top: 48px; + width: 36px; + text-align: left} +#RemoteKnobLocalCaption { + left: 62px; + top: 48px; + width: 38px; + text-align: right} + +#FormatKnob { + position: absolute; + right: 36px; + top: 60px} +#FormatKnobCaption { + right: 36px; + top: 128px; + width: 64px} +#FormatKnobSpaceCaption { + right: 88px; + top: 48px; + text-align: right} +#FormatKnobTabCaption { + right: 36px; + top: 48px; + width: 64px} +#FormatKnobCarRetCaption { + right: 12px; + top: 48px; + text-align: right} + +#ZeroSuppressSwitch { + position: absolute; + left: 134px; + top: 80px; + width: 24px} +#ZeroSuppressOn { + left: 122px; + top: 66px; + width: 48px} +#ZeroSuppressOff { + left: 122px; + top: 110px; + width: 48px} + +#MapMemorySwitch { + position: absolute; + left: 184px; + top: 80px; + width: 24px} +#MapMemoryOn { + left: 172px; + top: 66px; + width: 48px} +#MapMemoryNormal { + left: 172px; + top: 110px; + width: 48px} + +#Columns { + position: absolute; + right: 356px; + top: 80px; + width: 16px} +#ColumnsCaption { + right: 338px; + top: 66px; + width: 60px} + +#TabStops { + position: absolute; + right: 124px; + top: 80px; + width: 220px} +#TabStopsCaption { + right: 124px; + top: 66px; + width: 220px} + +#UnitSwitch0 { + position: absolute; + left: 134px; + top: 160px; + width: 24px} +#UnitSwitch0Caption { + left: 122px; + top: 146px; + width: 48px} + +#UnitSwitch1 { + position: absolute; + left: 170px; + top: 160px; + width: 24px} +#UnitSwitch1Caption { + left: 158px; + top: 146px; + width: 48px} + +#UnitSwitch2 { + position: absolute; + left: 206px; + top: 160px; + width: 24px} +#UnitSwitch2Caption { + left: 194px; + top: 146px; + width: 48px} + +#UnitSwitch3 { + position: absolute; + left: 242px; + top: 160px; + width: 24px} +#UnitSwitch3Caption { + left: 230px; + top: 146px; + width: 48px} + +#UnitSwitch4 { + position: absolute; + left: 278px; + top: 160px; + width: 24px} +#UnitSwitch4Caption { + left: 266px; + top: 146px; + width: 48px} + +#UnitSwitch5 { + position: absolute; + left: 314px; + top: 160px; + width: 24px} +#UnitSwitch5Caption { + left: 302px; + top: 146px; + width: 48px} + +#UnitSwitch6 { + position: absolute; + left: 350px; + top: 160px; + width: 24px} +#UnitSwitch6Caption { + left: 338px; + top: 146px; + width: 48px} + +#UnitSwitch7 { + position: absolute; + left: 386px; + top: 160px; + width: 24px} +#UnitSwitch7Caption { + left: 374px; + top: 146px; + width: 48px} + +#UnitSwitch8 { + position: absolute; + left: 422px; + top: 160px; + width: 24px} +#UnitSwitch8Caption { + left: 410px; + top: 146px; + width: 48px} + +#UnitSwitch9 { + position: absolute; + left: 458px; + top: 160px; + width: 24px} +#UnitSwitch9Caption { + left: 446px; + top: 146px; + width: 48px} + +#UnitSwitch10 { + position: absolute; + left: 494px; + top: 160px; + width: 24px} +#UnitSwitch10Caption { + left: 482px; + top: 146px; + width: 48px} diff --git a/webUI/B220ConsolePrinter.html b/webUI/B220ConsolePrinter.html new file mode 100644 index 0000000..d63703d --- /dev/null +++ b/webUI/B220ConsolePrinter.html @@ -0,0 +1,81 @@ + + + + +retro-220 Emulator Console Printer + + + + + + + + + + +
+
+
LINE FEED
+ + + +
CAR. RET.
+
+ +
+
 
+
 
+
+ +
+ + +
REMOTE
+
LOCAL
+ +
ZERO
SUPPRESS
+
NORMAL
+ +
MAP
MEMORY
+
NORMAL
+ +
SPACE
+
TAB
+
CAR.RET.
+
FORMAT
+ +
COLUMNS
+ + +
TAB STOPS
+ + +
SPO
+
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
0
+
+
+ + + \ No newline at end of file diff --git a/webUI/B220ConsolePrinter.js b/webUI/B220ConsolePrinter.js new file mode 100644 index 0000000..d5cf81b --- /dev/null +++ b/webUI/B220ConsolePrinter.js @@ -0,0 +1,566 @@ +/*********************************************************************** +* retro-220/webUI B220ConsolePrinter.js +************************************************************************ +* Copyright (c) 2017, Paul Kimpel. +* Licensed under the MIT License, see +* http://www.opensource.org/licenses/mit-license.php +************************************************************************ +* Burroughs 220 Console Printer and +* High-Speed Paper Tape Punch devices. +************************************************************************ +* 2017-03-17 P.Kimpel +* Original version, from retro-205 D205ConsoleOutput.js. +***********************************************************************/ +"use strict"; + +/**************************************/ +function B220ConsolePrinter(mnemonic, unitIndex, config) { + /* Constructor for the Console Printer object */ + var top = unitIndex*32; + var left = unitIndex*32; + + this.config = config; // System configuration object + this.mnemonic = mnemonic; // Unit mnemonic + this.unitIndex = unitIndex; // Unit index into console output units + this.outTimer = 0; // output setCallback() token + + this.columns = 72; // right-margin auto-return position + this.format = 0; // 0=space, 1=tab, 2=carriage-return + this.nextCharTime = 0; // next time a character can be printed + this.mapMemory = 0; // map-memory switch setting + this.unitMask = 0; // unit selection mask + this.unitSwitch = new Array(11); // unit selection switch objects + this.tabStop = []; // 0-relative tab stop positions + this.zeroSuppress = 0; // zero-suppression switch setting + + this.boundButton_Click = B220Util.bindMethod(this, B220ConsolePrinter.prototype.button_Click); + this.boundText_OnChange = B220Util.bindMethod(this, B220ConsolePrinter.prototype.text_OnChange); + this.boundFlipSwitch = B220Util.bindMethod(this, B220ConsolePrinter.prototype.flipSwitch); + this.boundReceiveSign = B220Util.bindMethod(this, B220ConsolePrinter.prototype.receiveSign); + this.boundReceiveChar = B220Util.bindMethod(this, B220ConsolePrinter.prototype.receiveChar); + + this.clear(); + + // Create the printer window and onload event + this.doc = null; + this.paper = null; + this.printerEOP = null; + this.printerLine = 0; + this.printerCol = 0; + this.window = window.open("../webUI/B220ConsolePrinter.html", mnemonic, + "location=no,scrollbars=no,resizable,width=640,height=300," + + "left=" + left + ",top=" + top); + this.window.addEventListener("load", B220Util.bindMethod(this, + B220ConsolePrinter.prototype.printerOnLoad)); +} + +/**************************************/ +B220ConsolePrinter.offSwitchImage = "./resources/ToggleDown.png"; +B220ConsolePrinter.onSwitchImage = "./resources/ToggleUp.png"; + +B220ConsolePrinter.charsPerSecond = 10; // Printer speed +B220ConsolePrinter.charPeriod = 1000/B220ConsolePrinter.charsPerSecond; + // Inter-character period, ms +B220ConsolePrinter.pageSize = 66; // lines/page for form-feed +B220ConsolePrinter.maxScrollLines = 15000; + // Maximum amount of paper scrollback + +B220ConsolePrinter.codeXlate = [ // translate internal B220 code to ANSI + " ", "?", " ", ".", "\u00A4", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 00-0F + "&", "?", "?", "$", "*", "\f", "\n", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 10-1F + "-", "/", "?", ",", "%", "?", "\t", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 20-2F + "?", "?", "?", "#", "@", "!", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 30-3F + "?", "A", "B", "C", "D", "E", "F", "G", "H", "I", "?", "?", "?", "?", "?", "?", // 40-4F + "?", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "?", "?", "?", "?", "?", "?", // 50-5F + "?", "?", "S", "T", "U", "V", "W", "X", "Y", "Z", "?", "?", "?", "?", "?", "?", // 60-6F + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 70-7F + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "?", "?", "?", "?", "?", "?", // 80-8F + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?"]; // 90-9F + + +/**************************************/ +B220ConsolePrinter.prototype.clear = function clear() { + /* Initializes (and if necessary, creates) the SPO unit state */ + + this.ready = false; // ready status + this.busy = false; // busy status + + this.eowAction = 0; // 1 => End-of-Word action needed + this.suppressLZ = 0; // 1 => currently suppressing leading zeroes +}; + +/**************************************/ +B220ConsolePrinter.prototype.beforeUnload = function beforeUnload(ev) { + var msg = "Closing this window will make the device unusable.\n" + + "Suggest you stay on the page and minimize this window instead"; + + ev.preventDefault(); + ev.returnValue = msg; + return msg; +}; + +/**************************************/ +B220ConsolePrinter.prototype.$$ = function $$(e) { + return this.doc.getElementById(e); +}; + +/**************************************/ +B220ConsolePrinter.prototype.emptyPaper = function emptyPaper() { + /* Empties the printer output "paper" and initializes it for new output */ + + while (this.paper.firstChild) { + this.paper.removeChild(this.paper.firstChild); + } + this.paper.appendChild(this.doc.createTextNode("")); +}; + +/**************************************/ +B220ConsolePrinter.prototype.emptyLine = function emptyLine(text) { + /* Removes excess lines already output, then appends a new text node to the +
 element within the paper element. Note that "text" is an ANSI string */
+    var paper = this.paper;
+    var line = text || "";
+
+    while (paper.childNodes.length > B220ConsolePrinter.maxScrollLines) {
+        paper.removeChild(paper.firstChild);
+    }
+    paper.lastChild.nodeValue += "\n";     // newline
+    paper.appendChild(this.doc.createTextNode(line));
+    ++this.printerLine;
+    this.printerCol = line.length;
+    this.printerEOP.scrollIntoView();
+};
+
+/**************************************/
+B220ConsolePrinter.prototype.printChar = function printChar(code) {
+    /* Outputs the character "code" to the device */
+    var c = B220ConsolePrinter.codeXlate[code];
+    var line;
+    var len;
+
+    if (c != "?") {                     // some 220 codes just don't print
+        line = this.paper.lastChild.nodeValue;
+        len = line.length;
+        if (len < 1) {
+            this.paper.lastChild.nodeValue = c;
+            this.printerCol = 1;
+        } else if (len < this.columns) {
+            this.paper.lastChild.nodeValue = line + c;
+            ++this.printerCol;
+        } else {
+             this.emptyLine(c);
+        }
+    }
+};
+
+/**************************************/
+B220ConsolePrinter.prototype.printTab = function printTab() {
+    /* Simulates tabulation by outputting an appropriate number of spaces */
+    var tabCol;                         // tabulation column
+    var x;                              // scratch index
+
+    for (x=0; x this.printerCol) {
+            tabCol = this.tabStop[x];
+            break; // out of for loop
+        }
+    } // for x
+
+    if (this.columns < tabCol) {
+        this.emptyLine();                   // tab would overflow right margin
+    } else {
+        while (this.printerCol < tabCol) {
+            this.printChar(0x00);           // output a space
+        }
+    }
+};
+
+/**************************************/
+B220ConsolePrinter.prototype.printFormFeed = function printFormFeed() {
+    /* Simulates a form feed by outputting an appropriate number of blank lines */
+
+    this.printerLine %= B220ConsolePrinter.pageSize;
+    while (this.printerLine < B220ConsolePrinter.pageSize) {
+        this.emptyLine();
+    }
+};
+
+/**************************************/
+B220ConsolePrinter.prototype.resizeWindow = function resizeWindow(ev) {
+    /* Handles the window onresize event by scrolling the "paper" so it remains at the end */
+
+    this.printerEOP.scrollIntoView();
+};
+
+/**************************************/
+B220ConsolePrinter.prototype.copyPaper = function copyPaper(ev) {
+    /* Copies the text contents of the "paper" area of the device, opens a new
+    temporary window, and pastes that text into the window so it can be copied
+    or saved by the user */
+    var text = this.paper.textContent;
+    var title = "B220 " + this.mnemonic + " Text Snapshot";
+    var win = window.open("./B220FramePaper.html", "TTY-Snapshot",
+            "scrollbars,resizable,width=500,height=500");
+
+    win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
+    win.addEventListener("load", function() {
+        var doc;
+
+        doc = win.document;
+        doc.title = title;
+        doc.getElementById("Paper").textContent = text;
+    });
+
+    this.emptyPaper();
+    this.emptyLine();
+    ev.preventDefault();
+    ev.stopPropagation();
+};
+
+/**************************************/
+B220ConsolePrinter.prototype.button_Click = function button_Click(ev) {
+    /* Handler for button clicks */
+
+    switch (ev.target.id) {
+    case "LineFeedBtn":
+    case "CarriageReturnBtn":
+        if (!this.ready) {
+            this.emptyLine();
+        }
+        break;
+    case "OpenPanelBtn":
+        ev.target.disabled = true;
+        this.$$("FormatControlsDiv").style.display = "block";
+        break;
+    case "ClosePanelBtn":
+        this.$$("OpenPanelBtn").disabled = false;
+        this.$$("FormatControlsDiv").style.display = "none";
+        break;
+    } // switch ev.target.id
+
+    ev.preventDefault();
+    ev.stopPropagation();
+};
+
+/**************************************/
+B220ConsolePrinter.prototype.flipSwitch = function flipSwitch(ev) {
+    /* Handler for switch clicks */
+    var id = ev.target.id;
+    var prefs = this.config.getNode("ConsoleOutput.units", this.unitIndex);
+    var x;
+
+    switch (id) {
+    case "ZeroSuppressSwitch":
+        this.zeroSuppressSwitch.flip();
+        prefs.zeroSuppress = this.zeroSuppress = this.zeroSuppressSwitch.state;
+        break;
+    case "MapMemorySwitch":
+        this.mapMemorySwitch.flip();
+        prefs.mapMemory, this.mapMemory = this.mapMemorySwitch.state;
+        break;
+    case "RemoteKnob":
+        this.remoteKnob.step();
+        prefs.remote = this.remoteKnob.position;
+        this.ready = (this.remoteKnob.position != 0);
+        break;
+    case "FormatKnob":
+        this.formatKnob.step();
+        prefs.format = this.formatKnob.position;
+        this.format = this.formatKnob.position;
+        break;
+    default:
+        x = id.indexOf("UnitSwitch");
+        if (x == 0) {
+            x = parseInt(id.substring(10), 10);
+            if (!isNaN(x)) {
+                this.unitSwitch[x].flip();
+                this.unitMask ^= B220Processor.pow2[x];
+                prefs.unitMask = this.unitMask;
+            }
+        }
+        break;
+    }
+
+    this.config.putNode("ConsoleOutput.units", prefs, this.unitIndex);
+    ev.preventDefault();
+    ev.stopPropagation();
+};
+
+/**************************************/
+B220ConsolePrinter.prototype.text_OnChange = function text_OnChange(ev) {
+    /* Handler for text onchange events */
+    var prefs = this.config.getNode("ConsoleOutput.units", this.unitIndex);
+    var text = ev.target.value;
+    var v;
+
+    switch (ev.target.id) {
+    case "Columns":
+        v = parseInt(text, 10);
+        if (!isNaN(v)) {
+            this.columns = v;
+            ev.target.value = text = v.toFixed();
+            prefs.columns = v;
+        }
+        break;
+    case "TabStops":
+        v = this.parseTabStops(prefs.tabs || "", this.window);
+        if (v !== null) {
+            this.tabStop = v;
+            ev.target.value = text = v.join(",");
+            prefs.tabs = text;
+        }
+        break;
+    } // switch ev.target.id
+
+    this.config.putNode("ConsoleOutput.units", prefs, this.unitIndex);
+    ev.preventDefault();
+    ev.stopPropagation();
+};
+
+/**************************************/
+B220ConsolePrinter.prototype.parseTabStops = function parsetabStops(text, alertWin) {
+    /* Parses a comma-delimited list of 1-relative tab stops. If the list is parsed
+    successfully, returns an array of 0-relative tab stop positions; otherwise
+    returns null. An alert is displayed on the window for the first parsing or
+    out-of-sequence error */
+    var col;
+    var cols;
+    var copacetic = true;
+    var lastCol = 0;
+    var raw;
+    var x;
+    var tabStop = [];
+
+    if (text.search(/\S/) >= 0) {
+        cols = text.split(",");
+        for (x=0; x 0) {       // ignore empty fields
+                col = parseInt(raw, 10);
+                if (isNaN(col)) {
+                    copacetic = false;
+                    alertWin.alert("Tab stop #" + (x+1) + " (\"" + cols[x] + "\") is not numeric");
+                    break; // out of for loop
+                } else if (col <= lastCol) {
+                    copacetic = false;
+                    alertWin.alert("Tab stop #" + (x+1) + " (\"" + col + "\") is out of sequence");
+                    break; // out of for loop
+                } else {
+                    lastCol = col;
+                    tabStop.push(col-1);
+                }
+            }
+        } // for x
+    }
+
+    return (copacetic ? tabStop : null);
+};
+
+/**************************************/
+B220ConsolePrinter.prototype.printerOnLoad = function printerOnLoad() {
+    /* Initializes the Teletype printer window and user interface */
+    var body;
+    var id;
+    var mask;
+    var prefs = this.config.getNode("ConsoleOutput.units", this.unitIndex);
+    var tabStop;
+    var x;
+
+    this.doc = this.window.document;
+    this.doc.title = "retro-220 Printer - " + this.mnemonic;
+    this.paper = this.$$("Paper");
+    this.printerEOP = this.$$("EndOfPaper");
+    this.emptyPaper();
+    this.emptyLine();
+
+    body = this.$$("FormatControlsDiv");
+    this.remoteKnob = new BlackControlKnob(body, null, null, "RemoteKnob",
+        prefs.remote, [20, -20]);
+    this.ready = (prefs.remote != 0);
+
+    this.zeroSuppressSwitch = new ToggleSwitch(body, null, null, "ZeroSuppressSwitch",
+            B220ConsolePrinter.offSwitchImage, B220ConsolePrinter.onSwitchImage);
+    this.zeroSuppressSwitch.set(prefs.zeroSuppress);
+    this.zeroSuppress = this.zeroSuppressSwitch.state;
+    this.mapMemorySwitch = new ToggleSwitch(body, null, null, "MapMemorySwitch",
+            B220ConsolePrinter.offSwitchImage, B220ConsolePrinter.onSwitchImage);
+    this.mapMemorySwitch.set(prefs.mapMemory);
+    this.mapMemory = this.mapMemorySwitch.state;
+
+    mask = 0x001;
+    this.unitMask = prefs.unitMask;
+    for (x=0; x
+
+
+
+Burroughs 220 Device Frame Paper Page
+
+
+
+
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/webUI/B220Manifest.appcache b/webUI/B220Manifest.appcache index 9c3e1ca..1fae711 100644 --- a/webUI/B220Manifest.appcache +++ b/webUI/B220Manifest.appcache @@ -1,5 +1,5 @@ CACHE MANIFEST -# retro-220 emulator 0.00c, 2017-03-12 12:15 +# retro-220 emulator 0.00d, 2017-04-29 12:35 CACHE: ../emulator/B220Processor.js B220.css @@ -21,8 +21,9 @@ B220Common.css B220ConsoleKeyboard.css B220ConsoleKeyboard.html B220ConsoleKeyboard.js -#B220ConsoleOutput.css -#B220ConsoleOutput.js +B220ConsolePrinter.css +B220ConsolePrinter.html +B220ConsolePrinter.js B220ControlConsole.css B220ControlConsole.html B220ControlConsole.js @@ -30,8 +31,7 @@ B220ControlConsole.js #B220DataFile.html #B220DataFile.js #B220DiagMonitor.html -#B220Flexowriter.html -#B220FramePaper.html +B220FramePaper.html #B220MagTapeControl.css #B220MagTapeControl.html #B220MagTapeControl.js @@ -40,7 +40,9 @@ B220ControlConsole.js #B220MagTapeDrive.js #B220MagTapeLoadPanel.html B220PanelUtil.js -#B220PaperTapePunch.html +B220PaperTapePunch.css +B220PaperTapePunch.html +B220PaperTapePunch.js #B220PaperTapeReader.html B220SetCallback.js B220SystemConfig.css diff --git a/webUI/B220PanelUtil.js b/webUI/B220PanelUtil.js index a42e198..5b860ce 100644 --- a/webUI/B220PanelUtil.js +++ b/webUI/B220PanelUtil.js @@ -133,6 +133,7 @@ function NeonLampBox(parent, x, y, id, caption) { this.lamp = new NeonLamp(this.element, 3, 3, id + "_Lamp"); this.button = document.createElement("div"); + this.button.id = id + "_LampBtn"; this.button.className = NeonLampBox.lampButtonClass; this.button.textContent = caption; this.element.appendChild(this.button); @@ -582,6 +583,7 @@ function BlackControlKnob(parent, x, y, id, initial, positions) { BlackControlKnob.topCaptionClass = "blackControlKnobTopCaption"; BlackControlKnob.bottomCaptionClass = "blackControlKnobBottomCaption"; BlackControlKnob.className = "blackControlKnob1"; +BlackControlKnob.canvasColor = "transparent"; BlackControlKnob.size = 64; // width/height in pixels /**************************************/ @@ -614,7 +616,7 @@ BlackControlKnob.prototype.set = function set(position) { dc.save(); dc.translate(halfSize+0.5, halfSize+0.5); // move origin to the center - dc.fillStyle = "#246"; // fill in the panel background (aids antialiasing) + dc.fillStyle = BlackControlKnob.canvasColor;// fill in the panel background (aids antialiasing) dc.fillRect(-halfSize, -halfSize, BlackControlKnob.size, BlackControlKnob.size); silverSkirt = dc.createRadialGradient(0, 0, halfSize, 0, 0, quarterSize); @@ -639,7 +641,7 @@ BlackControlKnob.prototype.set = function set(position) { dc.save(); // draw the knob indicator dc.rotate(this.positions[this.position]*degrees); dc.beginPath(); - dc.moveTo(0, -halfSize); + dc.moveTo(0, 1-halfSize); dc.lineTo(-quarterSize/4, -halfSize+quarterSize/2); dc.lineTo(quarterSize/4, -halfSize+quarterSize/2); dc.closePath(); @@ -678,10 +680,10 @@ BlackControlKnob.prototype.setCaption = function setCaption(caption, atBottom) { e = document.createElement("div"); if (atBottom) { this.bottomCaptionDiv = e; - e.className = blackControlKnob.bottomCaptionClass; + e.className = BlackControlKnob.bottomCaptionClass; } else { this.topCaptionDiv = e; - e.className = blackControlKnob.topCaptionClass; + e.className = BlackControlKnob.topCaptionClass; } e.appendChild(document.createTextNode(caption)); this.element.appendChild(e); diff --git a/webUI/B220PaperTapePunch.css b/webUI/B220PaperTapePunch.css new file mode 100644 index 0000000..53b7ec9 --- /dev/null +++ b/webUI/B220PaperTapePunch.css @@ -0,0 +1,94 @@ +/*********************************************************************** +* retro-220/webUI B220PaperTapePunch.css +************************************************************************ +* Copyright (c) 2017, Paul Kimpel. +* Licensed under the MIT License, see +* http://www.opensource.org/licenses/mit-license.php +************************************************************************ +* Burroughs 220 Paper Tape Punch Unit. +************************************************************************ +* 2017-04-28 P.Kimpel +* Original version, from retro-205 D205ConsoleOutput.css. +***********************************************************************/ + +#PunchBody { + height: 100%; + min-height: 100%; + overflow: hidden; + padding: 0} + +#PaperTapePunch { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: 4px; + border-radius: 8px} + +#PunchTape { + position: absolute; + left: 8px; + right: 8px; + top: 60px; + bottom: 8px; + min-height: 32px; + min-width: 120px; + overflow-x: hidden; + overflow-y: scroll; + color: black; + background-color: white; + padding: 4px; + border: 1px solid gray} + +#Paper { + margin-left: 0; + margin-right: 0; + margin-top: 0; + margin-bottom: 0; + padding: 0} + +#EndOfPaper { + display: block; + margin: 0; + padding: 0; + opacity: 0} +#EndOfPaper.hidden { + display: none} + +#RemoteSwitch { + position: absolute; + width: 24px; + left: 18px; + top: 12px} +#RemoteSwitchOn { + width: 36px; + left: 12px; + top: 6px} +#RemoteSwitchOff { + width: 36px; + left: 12px; + top: 42px} + +#ReadyLamp { + position: absolute; + width: 24px; + left: 56px; + top: 14px} +#ReadyLampCaption { + width: 36px; + left: 52px; + top: 6px} + +#UnitDesignateKnob { + position: absolute; + text-align: center; + width: 64px; + right: 8px; + top: 18px; + color: white; + background-color: #333} +#UnitDesignateKnobCaption { + width: 64px; + right: 8px; + top: 6px} diff --git a/webUI/B220PaperTapePunch.html b/webUI/B220PaperTapePunch.html new file mode 100644 index 0000000..d8d0e28 --- /dev/null +++ b/webUI/B220PaperTapePunch.html @@ -0,0 +1,58 @@ + + + + +retro-220 Emulator Paper Tape Punch + + + + + + + + + + +
+
REMOTE
+
LOCAL
+ +
READY
+ +
UNIT DESIGNATE
+ + +
+
 
+
 
+
+
+ + + \ No newline at end of file diff --git a/webUI/B220PaperTapePunch.js b/webUI/B220PaperTapePunch.js new file mode 100644 index 0000000..9da56b1 --- /dev/null +++ b/webUI/B220PaperTapePunch.js @@ -0,0 +1,324 @@ +/*********************************************************************** +* retro-220/webUI B220PaperTapePunch.js +************************************************************************ +* Copyright (c) 2017, Paul Kimpel. +* Licensed under the MIT License, see +* http://www.opensource.org/licenses/mit-license.php +************************************************************************ +* Burroughs 220 High-Speed Paper Tape Punch device. +************************************************************************ +* 2017-04-28 P.Kimpel +* Original version, from retro-205 D205ConsoleOutput.js. +***********************************************************************/ +"use strict"; + +/**************************************/ +function B220PaperTapePunch(mnemonic, unitIndex, config) { + /* Constructor for the Console Paper Tape Punch object */ + var top = unitIndex*32; + var left = unitIndex*32; + + this.config = config; // System configuration object + this.mnemonic = mnemonic; // Unit mnemonic + this.unitIndex = unitIndex; // Unit index into console output units + this.outTimer = 0; // output setCallback() token + + this.nextCharTime = 0; // next time a character can be punched + this.unitMask = 0; // unit selection mask + this.unitSwitch = new Array(11); // unit selection switch objects + this.tabStop = []; // 0-relative tab stop positions + + this.boundFlipSwitch = B220Util.bindMethod(this, B220PaperTapePunch.prototype.flipSwitch); + this.boundReceiveSign = B220Util.bindMethod(this, B220PaperTapePunch.prototype.receiveSign); + this.boundReceiveChar = B220Util.bindMethod(this, B220PaperTapePunch.prototype.receiveChar); + + this.clear(); + + // Create the punch window and onload event + this.doc = null; + this.punchTape = null; + this.punchEOP = null; + this.window = window.open("../webUI/B220PaperTapePunch.html", mnemonic, + "location=no,scrollbars=no,resizable,width=240,height=160," + + "left=" + left + ",top=" + top); + this.window.addEventListener("load", B220Util.bindMethod(this, + B220PaperTapePunch.prototype.punchOnLoad)); +} + +/**************************************/ +B220PaperTapePunch.offSwitchImage = "./resources/ToggleDown.png"; +B220PaperTapePunch.onSwitchImage = "./resources/ToggleUp.png"; + +B220PaperTapePunch.charsPerSecond = 60; // Punch speed, characters/second +B220PaperTapePunch.charPeriod = 1000/B220PaperTapePunch.charsPerSecond; + // Inter-character period, ms +B220PaperTapePunch.maxScrollLines = 45000; + // Maximum amount of punch word scrollback + +B220PaperTapePunch.codeXlate = [ // translate internal B220 code to ANSI + // Note that ANSI new-line sequences are used for end-of-word characters, + // so B220 carriage-return translates to "|". To avoide space-expansion of + // tab characters, they are translated to "~". + " ", "?", " ", ".", "\u00A4", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 00-0F + "&", "?", "?", "$", "*", "\f", "|", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 10-1F + "-", "/", "?", ",", "%", "?", "~", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 20-2F + "?", "?", "?", "#", "@", "!", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 30-3F + "?", "A", "B", "C", "D", "E", "F", "G", "H", "I", "?", "?", "?", "?", "?", "?", // 40-4F + "?", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "?", "?", "?", "?", "?", "?", // 50-5F + "?", "?", "S", "T", "U", "V", "W", "X", "Y", "Z", "?", "?", "?", "?", "?", "?", // 60-6F + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 70-7F + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "?", "?", "?", "?", "?", "?", // 80-8F + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?"]; // 90-9F + + +/**************************************/ +B220PaperTapePunch.prototype.clear = function clear() { + /* Initializes (and if necessary, creates) the SPO unit state */ + + this.ready = false; // ready status + this.busy = false; // busy status +}; + +/**************************************/ +B220PaperTapePunch.prototype.beforeUnload = function beforeUnload(ev) { + var msg = "Closing this window will make the device unusable.\n" + + "Suggest you stay on the page and minimize this window instead"; + + ev.preventDefault(); + ev.returnValue = msg; + return msg; +}; + +/**************************************/ +B220PaperTapePunch.prototype.$$ = function $$(e) { + return this.doc.getElementById(e); +}; + +/**************************************/ +B220PaperTapePunch.prototype.punchEmptyPaper = function punchEmptyPaper() { + /* Empties the punch output "paper" and initializes it for new output */ + + while (this.punchTape.firstChild) { + this.punchTape.removeChild(this.punchTape.firstChild); + } + this.punchTape.appendChild(this.doc.createTextNode("")); +}; + +/**************************************/ +B220PaperTapePunch.prototype.punchEmptyLine = function punchEmptyLine(text) { + /* Removes excess lines already output, then appends a new text node to the +
 element within the paper element. Note that "text" is an ANSI string */
+    var paper = this.punchTape;
+    var line = text || "";
+
+    while (paper.childNodes.length > B220PaperTapePunch.maxScrollLines) {
+        paper.removeChild(paper.firstChild);
+    }
+    paper.lastChild.nodeValue += "\n";     // newline
+    paper.appendChild(this.doc.createTextNode(line));
+};
+
+/**************************************/
+B220PaperTapePunch.prototype.punchChar = function punchChar(code) {
+    /* Outputs the character "code" to the device */
+    var c = B220PaperTapePunch.codeXlate[code];
+    var line;
+    var len;
+
+    if (c != "?") {                     // some 220 codes just don't print
+        line = this.punchTape.lastChild.nodeValue;
+        len = line.length;
+        if (len < 1) {
+            this.punchTape.lastChild.nodeValue = c;
+        } else {
+            this.punchTape.lastChild.nodeValue = line + c;
+        }
+    }
+};
+
+/**************************************/
+B220PaperTapePunch.prototype.resizeWindow = function resizeWindow(ev) {
+    /* Handles the window onresize event by scrolling the "tape" so it remains at the end */
+
+    this.punchEOP.scrollIntoView();
+};
+
+/**************************************/
+B220PaperTapePunch.prototype.punchCopyTape = function punchCopyTape(ev) {
+    /* Copies the text contents of the "paper" area of the device, opens a new
+    temporary window, and pastes that text into the window so it can be copied
+    or saved by the user */
+    var text = this.punchTape.textContent;
+    var title = "B220 " + this.mnemonic + " Text Snapshot";
+    var win = window.open("./B220FramePaper.html", "PaperTape-Snapshot",
+            "scrollbars,resizable,width=500,height=500");
+
+    win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
+    win.addEventListener("load", function() {
+        var doc;
+
+        doc = win.document;
+        doc.title = title;
+        doc.getElementById("Paper").textContent = text;
+    });
+
+    this.punchEmptyPaper();
+    ev.preventDefault();
+    ev.stopPropagation();
+};
+
+/**************************************/
+B220PaperTapePunch.prototype.flipSwitch = function flipSwitch(ev) {
+    /* Handler for switch clicks */
+    var id = ev.target.id;
+    var prefs = this.config.getNode("ConsoleOutput.units", this.unitIndex);
+    var x;
+
+    switch (id) {
+    case "RemoteSwitch":
+        this.remoteSwitch.flip();
+        prefs.remote = this.remoteSwitch.state;
+        this.ready = (this.remoteSwitch.state != 0);
+        this.readyLamp.set(this.remoteSwitch.state);
+        break;
+    case "UnitDesignateKnob":
+        x = this.unitDesignateKnob.selectedIndex;
+        if (x < 0) {
+            x = this.unitDesignateKnob.length-1;
+            this.unitMask = 0;
+        } else {
+            this.unitMask = B220Processor.pow2[x];
+            prefs.unitMask = this.unitMask
+        }
+        break;
+    }
+
+    this.config.putNode("ConsoleOutput.units", prefs, this.unitIndex);
+    ev.preventDefault();
+    ev.stopPropagation();
+};
+
+/**************************************/
+B220PaperTapePunch.prototype.punchOnLoad = function punchOnLoad() {
+    /* Initializes the Paper Tape Punch window and user interface */
+    var body;
+    var id;
+    var mask;
+    var prefs = this.config.getNode("ConsoleOutput.units", this.unitIndex);
+    var x;
+
+    this.doc = this.window.document;
+    this.doc.title = "retro-220 Punch - " + this.mnemonic;
+    this.punchTape = this.$$("Paper");
+    this.punchEOP = this.$$("EndOfPaper");
+    this.punchEmptyPaper();
+
+    body = this.$$("PaperTapePunch")
+    this.remoteSwitch = new ToggleSwitch(body, null, null, "RemoteSwitch",
+            B220PaperTapePunch.offSwitchImage, B220PaperTapePunch.onSwitchImage);
+    this.remoteSwitch.set(prefs.remote);
+    this.ready = (this.remoteSwitch.state != 0);
+
+    this.readyLamp = new ColoredLamp(body, null, null, "ReadyLamp", "blueLamp lampCollar", "blueLit");
+    this.readyLamp.set(this.remoteSwitch.state);
+
+    this.unitDesignateKnob = this.$$("UnitDesignateKnob");
+    mask = 0x001;
+    this.unitMask = prefs.unitMask;
+    if (this.unitMask == 0) {
+        this.unitDesignateKnob.selectedIndex = this.unitDesignateKnob.length-1;
+    } else {
+        for (x=0; x
     
+
+ + + +
System Properties:
- + + Word Memory Size + +
Console Output:
+ +
+ + + + + + + + + + + + +
PositionTypeSPO12345678910FormatColsTabs
0 - - × 1000 Word Memory + + + + + + + + + + + + + + + + + + + + + + + + + + +   + +   +
1 - - - + + + + + + + + + + + + + + + + + + + + + + + + +   + +   + +
2 + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + +   + +
3 + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + +   + +
4 + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + +   + +
5 + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + +   + +
6 + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + +   + +
7 + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + +   + +
8 + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + +   + +
9 + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + +   + +
10 + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + +  
Cardatron Unit Selection:
@@ -446,11 +910,6 @@ - - -
- - diff --git a/webUI/B220SystemConfig.js b/webUI/B220SystemConfig.js index d5114cd..249abec 100644 --- a/webUI/B220SystemConfig.js +++ b/webUI/B220SystemConfig.js @@ -58,23 +58,35 @@ B220SystemConfig.prototype.createConfigData = function createConfigData() { memorySize: 5000, // 11-digit words ControlConsole: { - hasSPO: true, - poSuppressSwitch: 0, - skipSwitch: 0, - audibleAlarmSwitch: 0, - outputKnob: 2, - breakpointKnob: 0, - inputKnob: 1}, + PCS1SW: 0, // Program Control Switches 1-0 + PCS2SW: 0, + PCS3SW: 0, + PCS4SW: 0, + PCS5SW: 0, + PCW6SW: 0, + PCS7SW: 0, + PCS8SW: 0, + PCS9SW: 0, + PCS0SW: 0, + SONSW: 0, // S-register on + SUNITSSW: 0, // S-register units + STOCSW: 0, // S-to-C stop + STOPSW: 0}, // S-to-P stop - Flexowriter: { - zeroSuppressSwitch: 0, - tabSpaceSwitch: 2, - groupingCountersSwitch: 0, - autoStopSwitch: 0, - powerSwitch: 1, - wordsKnob: 0, - linesKnob: 0, - groupsKnob: 0}, + ConsoleOutput: { + units: [ + {type: "TTYA", zeroSuppress: 0, mapMemory: 0, unitMask: 0x001, remote: 1, format: 0, columns: 72, tabs: "9,17,25,33,41,49,57,65,73,81"}, + {type: "NONE"}, + {type: "NONE"}, + {type: "NONE"}, + {type: "NONE"}, + {type: "NONE"}, + {type: "NONE"}, + {type: "NONE"}, + {type: "NONE"}, + {type: "NONE"}, + {type: "NONE"} + ]}, Cardatron: { hasCardatron: true, @@ -108,54 +120,6 @@ B220SystemConfig.prototype.createConfigData = function createConfigData() { }; this.flushHandler(); - - // Convert old Supervisory Panel prefs - s = localStorage.getItem("retro-205-SupervisoryPanel-Prefs"); - if (s) { - try { - prefs = JSON.parse(s); - } finally { - // nothing - } - - for (pref in prefs) { - this.configData.SupervisoryPanel[pref] = prefs[pref]; - } - this.flushHandler(); - localStorage.removeItem("retro-205-SupervisoryPanel-Prefs"); - } - - // Convert old Control Console prefs - s = localStorage.getItem("retro-205-ControlConsole-Prefs"); - if (s) { - try { - prefs = JSON.parse(s); - } finally { - // nothing - } - - for (pref in prefs) { - this.configData.ControlConsole[pref] = prefs[pref]; - } - this.flushHandler(); - localStorage.removeItem("retro-205-ControlConsole-Prefs"); - } - - // Convert old Flexowriter prefs - s = localStorage.getItem("retro-205-Flexowriter-Prefs"); - if (s) { - try { - prefs = JSON.parse(s); - } finally { - // nothing - } - - for (pref in prefs) { - this.configData.Flexowriter[pref] = prefs[pref]; - } - this.flushHandler(); - localStorage.removeItem("retro-205-Flexowriter-Prefs"); - } }; /**************************************/ @@ -306,14 +270,31 @@ B220SystemConfig.prototype.setListValue = function setListValue(id, value) { B220SystemConfig.prototype.loadConfigDialog = function loadConfigDialog() { /* Loads the configuration UI window with the settings from this.configData */ var cd = this.configData; // local configuration reference + var mask; // unit mask bits var prefix; // unit id prefix var unit; // unit configuration object var x; // unit index + var y; // secondary index - this.$$("SystemMemorySize").value = Math.floor(cd.memorySize/1000).toString(); + // System Properties + this.setListValue("SystemMemorySize", cd.memorySize.toString()); - // Console units - this.$$("SPO").checked = cd.ControlConsole.hasSPO; + // Console Output units + for (x=0; x<=10; ++x) { + unit = cd.ConsoleOutput.units[x]; + prefix = "ConsoleOut" + x; + this.setListValue(prefix + "Type", unit.type); + mask = 0x001; + this.$$(prefix + "_SPO").checked = (unit.unitMask & mask ? true : false); + for (y=1; y<=10; ++y) { + mask <<= 1; + this.$$(prefix + "_" + y).checked = (unit.unitMask & mask ? true : false); + } // for y + + this.setListValue(prefix + "Format", unit.format); + this.$$(prefix + "Columns").textContent = (unit.columns ? unit.columns : 72); + this.$$(prefix + "Tabs").textContent = (unit.tabs ? unit.tabs : ""); + } // for x // Cardatron units for (x=1; x<=7; ++x) { @@ -363,9 +344,11 @@ B220SystemConfig.prototype.saveConfigDialog = function saveConfigDialog() { the updated configuration to localStorage */ var cd = this.configData; // local configuration reference var e; // local element reference + var mask; // unit mask var prefix; // unit id prefix var unit; // unit configuration object var x; // unit index + var y; // secondary index function getNumber(id, caption, min, max) { var n; @@ -382,16 +365,36 @@ B220SystemConfig.prototype.saveConfigDialog = function saveConfigDialog() { return n; } - x = getNumber.call(this, "SystemMemorySize", "Memory Size", 1, 10); - if (isNaN(x)) { - return; - } else { - cd.memorySize = x*1000; - } + // System Properties - // Console units + e = this.$$("SystemMemorySize"); + x = parseInt(e.options[e.selectedIndex], 10); + cd.memorySize = (isNaN(x) ? 5000 : x); - cd.ControlConsole.hasSPO = this.$$("SPO").checked; + // Console Output units + + for (x=0; x<=10; ++x) { + unit = cd.ConsoleOutput.units[x]; + prefix = "ConsoleOut" + x; + e = this.$$(prefix + "Type"); + unit.type = (e.selectedIndex < 0 ? "NONE" : e.options[e.selectedIndex].value); + mask = 0x001; + unit.unitMask = 0; + if (this.$$(prefix + "_SPO").checked) { + unit.unitMask |= mask; + } + for (y=1; y<=10; ++y) { + mask <<= 1; + if (this.$$(prefix + "_" + y).checked) { + unit.unitMask |= mask; + } + } // for y + + e = this.$$(prefix + "Format"); + unit.format = (e.selectedIndex < 0 ? "NONE" : e.options[e.selectedIndex].value); + unit.columns = (unit.columns ? unit.columns : 72); + unit.tabs = (unit.tabs ? unit.tabs : "1,9,17,25,33,41,49,57,65,73"); + } // for x // Cardatron units @@ -472,6 +475,11 @@ B220SystemConfig.prototype.openConfigUI = function openConfigUI() { B220Util.bindMethod(this, this.saveConfigDialog)); this.$$("CancelBtn").addEventListener("click", B220Util.bindMethod(this, function(ev) {this.window.close()})); + this.$$("DefaultsBtn").addEventListener("click", + B220Util.bindMethod(this, function(ev) { + this.createConfigData(); + this.loadConfigDialog(); + })); this.window.addEventListener("unload", B220Util.bindMethod(this, this.closeConfigUI), false); this.loadConfigDialog(); @@ -479,7 +487,7 @@ B220SystemConfig.prototype.openConfigUI = function openConfigUI() { this.doc = null; this.window = window.open("../webUI/B220SystemConfig.html", this.configStorageName, - "location=no,scrollbars,resizable,width=640,height=800"); + "location=no,scrollbars,resizable,width=800,height=800"); this.window.moveTo(screen.availWidth-this.window.outerWidth-40, (screen.availHeight-this.window.outerHeight)/2); this.window.focus(); diff --git a/webUI/B220Util.js b/webUI/B220Util.js index 3f92bb1..fada3a6 100644 --- a/webUI/B220Util.js +++ b/webUI/B220Util.js @@ -44,31 +44,22 @@ B220Util.$$ = function $$(e) { /**************************************/ B220Util.hasClass = function hasClass(e, name) { /* returns true if element "e" has class "name" in its class list */ - var classes = e.className; - if (!e) { - return false; - } else if (classes == name) { - return true; - } else { - return (classes.search("\\b" + name + "\\b") >= 0); - } + return e.classList.contains(name); }; /**************************************/ B220Util.addClass = function addClass(e, name) { /* Adds a class "name" to the element "e"s class list */ - if (!B220Util.hasClass(e, name)) { - e.className += (" " + name); - } + e.classList.add(name); }; /**************************************/ B220Util.removeClass = function removeClass(e, name) { /* Removes the class "name" from the element "e"s class list */ - e.className = e.className.replace(new RegExp("\\b" + name + "\\b\\s*", "g"), ""); + e.classList.remove(name); }; /**************************************/