From 2bc5332a56e862a4ab9a34343ccf29c207c0d84d Mon Sep 17 00:00:00 2001 From: paul Date: Sat, 30 Jun 2012 23:33:41 +0000 Subject: [PATCH] Implement first crude test harness for ConfigureSystem, CentralControl, and Processor using webUI/B5500Console.html. --- emulator/B5500CentralControl.js | 217 ++++++++++++++++-------- emulator/B5500DistributionAndDisplay.js | 6 +- emulator/B5500Processor.js | 119 +++++++------ emulator/B5500SystemConfiguration.js | 43 +++++ webUI/B5500Console.html | 40 ++++- 5 files changed, 301 insertions(+), 124 deletions(-) create mode 100644 emulator/B5500SystemConfiguration.js diff --git a/emulator/B5500CentralControl.js b/emulator/B5500CentralControl.js index 9b969d7..9484212 100644 --- a/emulator/B5500CentralControl.js +++ b/emulator/B5500CentralControl.js @@ -2,20 +2,22 @@ * retro-b5500/emulator B5500CentralControl.js ************************************************************************ * Copyright (c) 2012, Nigel Williams and Paul Kimpel. -* Licensed under the MIT License, see http://www.opensource.org/licenses/mit-license.php +* Licensed under the MIT License, +* see http://www.opensource.org/licenses/mit-license.php ************************************************************************ -* JavaScript object definition for the B5500 Central Control module. +* B5500 Central Control module. ************************************************************************ * 2012-06-03 P.Kimpel * Original version, from thin air. ***********************************************************************/ +"use strict"; /**************************************/ function B5500CentralControl() { /* Constructor for the Central Control module object */ /* Global system modules */ - + this.DD = null; // Distribution & Display unit this.PA = null; // Processor A (PA) this.PB = null; // Processor B (PB) this.IO1 = null; // I/O unit 1 @@ -26,17 +28,16 @@ function B5500CentralControl() { this.P1 = null; // Reference for Processor 1 (control) [PA or PB] this.P1 = null; // Reference for Processor 2 (slave) [PA or PB] - this.AddressSpace = []; // Array of memory module address spaces (8 x 32KB each) - this.Memory = []; // Array of memory module words as Float64s (8 x 4KW each) + this.AddressSpace = new Array(8); // Array of memory module address spaces (8 x 32KB each) + this.MemMod = new Array(8); // Array of memory module words as Float64s (8 x 4KW each) - // This memory instantiation should be done in configuration, but here's the idea... - this.AddressSpace[0] = new ArrayBuffer(32768); - this.Memory[0] = new Float64Array(this.AddressSpace[0]); + // Instance variables and flags + this.poweredUp = false; // System power indicator this.PB1L = 0; // 0=> PA is P1, 1=> PB is P1 this.cardLoadSelect = 0; // 0=> load from disk/drum; 1=> load from cards - this.nextTimeStamp = 0; // Next actual Date.getTime() expected + this.nextTimeStamp = 0; // Next actual Date.getTime() for timer tick this.timer = null; // Reference to the RTC setTimeout id. this.loadTimer = null; // Reference to the load setTimeout id. @@ -85,7 +86,8 @@ B5500CentralControl.mask2 = [ // (2**n)-1 for n from 0 to 52 /**************************************/ B5500CentralControl.prototype.clear = function() { - /* Initializes the system and starts the real-time clock */ + /* Initializes (and if necessary, creates) the system and starts the + real-time clock */ if (this.timer) { clearTimeout(this.timer); @@ -141,7 +143,7 @@ B5500CentralControl.prototype.clear = function() { if (!this.P2) { this.P2BF = 1; // mark non-existent P2 as busy } -} +}; /**************************************/ B5500CentralControl.prototype.bit = function(word, bit) { @@ -149,27 +151,27 @@ B5500CentralControl.prototype.bit = function(word, bit) { var e = 47-bit; var p; - if (e > 0( { + if (e > 0) { p = B5500CentralControl.pow2[e]; return ((word - word%p)/p) % 2; } else { return word % 2; } -} +}; /**************************************/ B5500CentralControl.prototype.bitSet = function(word, bit) { /* Sets the specified bit in word and returns the updated word */ return this.fieldInsert(word, bit, 1, 1); -} +}; /**************************************/ B5500CentralControl.prototype.bitReset = function(word, bit) { /* Resets the specified bit in word and returns the updated word */ return this.fieldInsert(word, bit, 1, 0); -} +}; /**************************************/ B5500CentralControl.prototype.fieldIsolate = function(word, start, width) { @@ -184,7 +186,7 @@ B5500CentralControl.prototype.fieldIsolate = function(word, start, width) { } else { return word % B5500CentralControl.pow2[width]; } -} +}; /**************************************/ B5500CentralControl.prototype.fieldInsert = function(word, start, width, value) { @@ -203,94 +205,95 @@ B5500CentralControl.prototype.fieldInsert = function(word, start, width, value) bottom = word % bpower; } return (value % B5500CentralControl.pow2[width])*bpower + top + bottom; -} +}; /**************************************/ -B5500CentralControl.prototype.fetch = function(r) { - /* Called by requestor module "r" to fetch a word from memory. */ - var acer = r.accessor; - var addr = acer.addr; +B5500CentralControl.prototype.fetch = function(acc) { + /* Called by requestor module passing accessor object "acc" to fetch a + word from memory. */ + var addr = acc.addr; var modNr = addr >>> 12; var modAddr = addr & 0x0FFF; var modMask = 1 << modNr; this.MCYF |= modMask; // !! need to figure out when to turn this off for display purposes // (odd/even addresses? fetch vs. store? XOR the mask?) - switch (r) { - case PA: + switch (acc.requestorID) { + case "A": this.PAXF = modMask; break; - case PB: + case "B": this.PBXF = modMask; break; - case IO1: + case "1": this.I1XF = modMask; break; - case IO2: + case "2": this.I2XF = modMask; break; - case IO3: + case "3": this.I3XF = modMask; break; - case IO4: + case "4": this.I4XF = modMask; break; } // For now, we assume memory parity can never happen - if (acer.MAIL || !this.Memory[modNr]) { - acer.MPED = 0; - acer.MAED = 1; + if (acc.MAIL || !this.MemMod[modNr]) { + acc.MPED = 0; // no memory parity error + acc.MAED = 1; // memory address error // no .word value is returned in this case } else { - acer.MPED = 0; - acer.MAED = 0; - acer.word = this.Memory[memMod][modAddr]; + acc.MPED = 0; // no parity error + acc.MAED = 0; // no address error + acc.word = this.MemMod[modNr][modAddr]; } -} +}; /**************************************/ B5500CentralControl.prototype.store = function(r, addr, word) { - /* Called by requestor module "r" to store a word into memory. */ - var acer = r.accessor - var addr = acer.addr; + /* Called by requestor module passing accessor object "acc" to store a + word into memory. */ + var addr = acc.addr; var modNr = addr >>> 12; var modAddr = addr & 0x0FFF; var modMask = 1 << modNr; this.MCYF |= modMask; // !! need to figure out when to turn this off for display purposes // (odd/even addresses? fetch vs. store? XOR the mask?) - switch (r) { - case this.PA: + switch (acc.requestorID) { + case "A": this.PAXF = modMask; break; - case this.PB: + case "B": this.PBXF = modMask; break; - case this.IO1: + case "1": this.I1XF = modMask; break; - case this.IO2: + case "2": this.I2XF = modMask; break; - case this.IO3: + case "3": this.I3XF = modMask; break; - case this.IO4: + case "4": this.I4XF = modMask; break; } // For now, we assume memory parity can never happen - if (acer.MAIL || !this.Memory[modNr]) { - acer.MPED = 0; - acer.MAED = 1; + if (acc.MAIL || !this.MemMod[modNr]) { + acc.MPED = 0; // no memory parity error + acc.MAED = 1; // memory address error + // no word is stored in this case } else { - acer.MPED = 0; - acer.MAED = 0; - this.Memory[memMod][modAddr] = acer.word; + acc.MPED = 0; // no parity error + acc.MAED = 0; // no address error + this.MemMod[modNr][modAddr] = acc.word; } -} +}; /**************************************/ B5500CentralControl.prototype.signalInterrupt = function() { @@ -325,7 +328,7 @@ B5500CentralControl.prototype.signalInterrupt = function() { : p2.I & 0x04 ? 0x22 // @42: P2 stack overflow : p2.I & 0xF0 ? (p2.I >>> 4) + 0x20 // @44-55: P2 syllable-dependent : 0; // no interrupt set -} +}; /**************************************/ B5500CentralControl.prototype.clearInterrupt = function() { @@ -427,11 +430,12 @@ B5500CentralControl.prototype.clearInterrupt = function() { break; } this.signalInterrupt(); -} +}; /**************************************/ B5500CentralControl.prototype.tock = function tock() { /* Handles the 1/60th second real-time clock tick */ + var interval; // milliseconds to next tick var that = tock.that; // capture the current closure context var thisTime = new Date().getTime(); @@ -442,10 +446,9 @@ B5500CentralControl.prototype.tock = function tock() { that.CCI03F = 1; // set timer interrupt // inhibit for now // that.signalInterrupt(); } - that.nextTimeStamp += B5500CentralControl.rtcTick; - that.timer = setTimeout(function() {that.tock()}, - (that.nextTimeStamp < thisTime ? 0 : that.nextTimeStamp-thisTime)); -} + interval = (that.nextTimeStamp += B5500CentralControl.rtcTick) - thisTime; + that.timer = setTimeout(function() {that.tock()}, (interval < 0 ? 1 : interval)); +}; /**************************************/ B5500CentralControl.prototype.initiateP2 = function() { @@ -472,7 +475,7 @@ B5500CentralControl.prototype.initiateP2 = function() { p2.procTime = new Date().getTime()*1000; p2.scheduler = setTimeout(p2.schedule, 0); } -} +}; /**************************************/ B5500CentralControl.prototype.initiateIO = function() { @@ -480,21 +483,21 @@ B5500CentralControl.prototype.initiateIO = function() { if (this.IO1) { this.AD1F = 1; - IO1.initiate(); + this.IO1.initiate(); } else if (this.IO2) { this.AD2F = 1; - IO2.initiate(); + this.IO2.initiate(); } else if (this.IO3) { this.AD3F = 1; - IO3.initiate(); + this.IO3.initiate(); } else if (this.IO4) { this.AD4F = 1; - IO4.initiate(); + this.IO4.initiate(); } else { this.CCI04F = 1; // set I/O busy interrupt this.signalInterrupt(); } -} +}; /**************************************/ B5500CentralControl.prototype.halt = function() { @@ -519,10 +522,10 @@ B5500CentralControl.prototype.halt = function() { } if (this.loadTimer) { - cancelTimeout(this.loadTimer); + clearTimeout(this.loadTimer); this.loadTimer = null; } -} +}; /**************************************/ B5500CentralControl.prototype.load = function() { @@ -533,12 +536,12 @@ B5500CentralControl.prototype.load = function() { if (this.P1) { this.LOFF = 1; if (this.IO1) { // !! not sure about I/O selection here - IO1.initiateLoad(this.cardLoadSelect); + this.IO1.initiateLoad(this.cardLoadSelect); this.loadComplete(); } } } -} +}; /**************************************/ B5500CentralControl.prototype.loadComplete = function loadComplete() { @@ -547,18 +550,90 @@ B5500CentralControl.prototype.loadComplete = function loadComplete() { var that = loadComplete.that; // capture the current closure context if (!that.CCI08F) { - that.loadTimer = setTimeout(that.loadComplete, 10); + that.loadTimer = setTimeout(that.loadComplete, 100); } else { that.loadTimer = null that.LOFF = 0; that.P1.C = 0x10; // execute from address @20 that.P1.L = 0; that.P1.access(0x30); // P = [C] - that.P1.T = Math.floor(that.P / 0x1000000000) % 0x1000; + that.P1.T = that.fieldIsolate(that.P, 0, 12); that.P1.TROF = 1; // Now start scheduling P1 on the Javascript thread that.P1.procTime = new Date().getTime()*1000; that.P1.scheduler = setTimeout(that.P1.schedule, 0); } -} \ No newline at end of file +}; + +/**************************************/ +B5500CentralControl.prototype.configureSystem = function() { + /* Establishes the hardware module configuration from the + B5500SystemConfiguration module */ + var cfg = B5500SystemConfiguration; + var x; + + // !! inhibit for now // this.DD = new B5500DistributionAndDisplay(); + + if (cfg.PA) {this.PA = new B5500Processor("A")}; + if (cfg.PB) {this.PB = new B5500Processor("B")}; + + this.PB1L = (cfg.PB1L ? 1 : 0); + + /*** enable once I/O exists *** + if (cfg.IO1) {this.IO1 = new B5500IOUnit("1")}; + if (cfg.IO2) {this.IO2 = new B5500IOUnit("2")}; + if (cfg.IO3) {this.IO3 = new B5500IOUnit("3")}; + if (cfg.IO4) {this.IO4 = new B5500IOUnit("4")}; + ***/ + + for (x=0; x<8; x++) { + if (cfg.MemMod[x]) { + this.AddressSpace[x] = new ArrayBuffer(32768); // 4K B5500 words @ 8 bytes each + this.MemMod[x] = new Float64Array(this.AddressSpace[x]); + } + } + + // Peripheral unit configuration should take place here once we have it. +}; + +/**************************************/ +B5500CentralControl.prototype.powerOn = function() { + /* Powers up the system and establishes the hardware module configuration. + Redundant power-ons are ignored. */ + + if (!this.poweredUp) { + this.configureSystem(); + this.poweredUp = true; + } +}; + +/**************************************/ +B5500CentralControl.prototype.powerOff = function() { + /* Powers down the system and deallocates the hardware modules. + Redundant power-offs are ignored. */ + var x; + + if (this.poweredUp) { + this.halt(); + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + + // Deallocate the system modules + this.P1 = this.P2 = null; + this.PA = null; + this.PB = null; + this.IO1 = null; + this.IO2 = null; + this.IO3 = null; + this.IO4 = null; + for (x=0; x<8; x++) { + this.MemMod[x] = null; + this.AddressSpace[x] = null; + } + + this.poweredUp = false; + } +}; diff --git a/emulator/B5500DistributionAndDisplay.js b/emulator/B5500DistributionAndDisplay.js index 45518d5..19aa22c 100644 --- a/emulator/B5500DistributionAndDisplay.js +++ b/emulator/B5500DistributionAndDisplay.js @@ -9,7 +9,9 @@ * 2012-06-16 P.Kimpel * Original version, from thin air. ***********************************************************************/ +"use strict"; +/**************************************/ function B5500DistributionAndDisplay() { /* Constructor for the Distribution And Display module object */ @@ -32,7 +34,7 @@ B5500DistributionAndDisplay.prototype.refreshPeriod = 50; // milliseconds /**************************************/ B5500DistributionAndDisplay.prototype.clear = function() { - /* Initializes the system and starts the real-time clock */ + /* Initializes the displays and starts the refresh timer */ if (this.timer) { clearTimeout(this.timer); @@ -69,7 +71,7 @@ B5500DistributionAndDisplay.prototype.updateDisplay = function updateDisplay() { // Schedule ourself for the next refresh period that.nextRefresh += that.refreshPeriod; delayTime = that.nextRefresh - thisTime; - that.timer = setTimeout(that.updateDisplay, (delayTime < 0 ? 0 : delayTime); + that.timer = setTimeout(that.updateDisplay, (delayTime < 0 ? 1 : delayTime); } \ No newline at end of file diff --git a/emulator/B5500Processor.js b/emulator/B5500Processor.js index a1cd8a2..50d178f 100644 --- a/emulator/B5500Processor.js +++ b/emulator/B5500Processor.js @@ -2,7 +2,7 @@ * retro-b5500/emulator B5500Processor.js ************************************************************************ * Copyright (c) 2012, Nigel Williams and Paul Kimpel. -* Licensed under the MIT License, see +* Licensed under the MIT License, see * http://www.opensource.org/licenses/mit-license.php * * Instance variables in all caps generally refer to register or flip-flop (FF) @@ -14,14 +14,16 @@ * 2012-06-03 P.Kimpel * Original version, from thin air. ***********************************************************************/ -"use strict"; +"use strict"; /**************************************/ -function B5500Processor() { +function B5500Processor(procID) { /* Constructor for the Processor module object */ + this.processorID = procID; // Processor ID ("A" or "B") this.scheduler = null; // Reference to current setTimeout id this.accessor = { // Memory access control block + requestorID: procID, // Memory requestor ID addr: 0, // Memory address word: 0, // 48-bit data word MAIL: 0, // Truthy if attempt to access @000-@777 in normal state @@ -91,88 +93,89 @@ B5500Processor.prototype.clear = function() { B5500Processor.prototype.access = function(eValue) { /* Access memory based on the E register. If the processor is in normal state, it cannot access the first 512 words of memory => invalid address */ + var acc = this.accessor; // get a local reference to the accessor object this.E = eValue; // Just to show the world what's happening switch (eValue) { case 0x02: // A = [S] - this.accessor.addr = this.S; - this.accessor.MAIL = (this.S < 0x0200 && this.NCSF); - cc.fetch(this); - this.A = this.accessor.word; + acc.addr = this.S; + acc.MAIL = (this.S < 0x0200 && this.NCSF); + cc.fetch(acc); + this.A = acc.word; this.AROF = 1; break; case 0x03: // B = [S] - this.accessor.addr = this.S; - this.accessor.MAIL = (this.S < 0x0200 && this.NCSF); - cc.fetch(this); - this.B = this.accessor.word; + acc.addr = this.S; + acc.MAIL = (this.S < 0x0200 && this.NCSF); + cc.fetch(acc); + this.B = acc.word; this.BROF = 1; break; case 0x04: // A = [M] - this.accessor.addr = this.M; - this.accessor.MAIL = (this.M < 0x0200 && this.NCSF); - cc.fetch(this); - this.A = this.accessor.word; + acc.addr = this.M; + acc.MAIL = (this.M < 0x0200 && this.NCSF); + cc.fetch(acc); + this.A = acc.word; this.AROF = 1; break; case 0x05: // B = [M] - this.accessor.addr = this.M; - this.accessor.MAIL = (this.M < 0x0200 && this.NCSF); - cc.fetch(this); - this.B = this.accessor.word; + acc.addr = this.M; + acc.MAIL = (this.M < 0x0200 && this.NCSF); + cc.fetch(acc); + this.B = acc.word; this.BROF = 1; break; case 0x06: // M = [M].[18:15] - this.accessor.addr = this.M; - this.accessor.MAIL = (this.M < 0x0200 && this.NCSF); - cc.fetch(this); - this.M = ((this.accessor.word % 0x40000000) >>> 15) & 0x7FFF; + acc.addr = this.M; + acc.MAIL = (this.M < 0x0200 && this.NCSF); + cc.fetch(acc); + this.M = ((acc.word % 0x40000000) >>> 15) & 0x7FFF; break; case 0x0A: // [S] = A - this.accessor.addr = this.S; - this.accessor.MAIL = (this.S < 0x0200 && this.NCSF); - this.accessor.word = this.A; - cc.store(this); + acc.addr = this.S; + acc.MAIL = (this.S < 0x0200 && this.NCSF); + acc.word = this.A; + cc.store(acc); break; case 0x0B: // [S] = B - this.accessor.addr = this.S; - this.accessor.MAIL = (this.S < 0x0200 && this.NCSF); - this.accessor.word = this.B; - cc.store(this); + acc.addr = this.S; + acc.MAIL = (this.S < 0x0200 && this.NCSF); + acc.word = this.B; + cc.store(acc); break; case 0x0C: // [M] = A - this.accessor.addr = this.M; - this.accessor.MAIL = (this.M < 0x0200 && this.NCSF); - this.accessor.word = this.A; - cc.store(this); + acc.addr = this.M; + acc.MAIL = (this.M < 0x0200 && this.NCSF); + acc.word = this.A; + cc.store(acc); break; case 0x0D: // [M] = B - this.accessor.addr = this.M; - this.accessor.MAIL = (this.M < 0x0200 && this.NCSF); - this.accessor.word = this.B; - cc.store(this); + acc.addr = this.M; + acc.MAIL = (this.M < 0x0200 && this.NCSF); + acc.word = this.B; + cc.store(acc); break; case 0x30: // P = [C] - this.accessor.addr = this.C; - this.accessor.MAIL = (this.C < 0x0200 && this.NCSF); - cc.fetch(this); - this.P = this.accessor.word; + acc.addr = this.C; + acc.MAIL = (this.C < 0x0200 && this.NCSF); + cc.fetch(acc); + this.P = acc.word; this.PROF = 1; break; default: - throw "Invalid E register value: " + eValue.toString(2); + throw "Invalid E-register value: " + eValue.toString(2); break; } this.cycleCount += 6; // assume 6 us memory cycle time (the other option is 4 usec) - if (this.accessor.MAED) { + if (acc.MAED) { this.I |= 0x02; // set I02F - memory address/inhibit error if (this.NCSF || this !== cc.P1) { cc.signalInterrupt(); } else { this.busy = false; // P1 invalid address in control state stops the proc } - } else if (this.accessor.MPED) { + } else if (acc.MPED) { this.I |= 0x01; // set I01F - memory parity error if (this.NCSF || this !== cc.P1) { cc.signalInterrupt(); @@ -647,7 +650,7 @@ B5500Processor.prototype.buildMSCW = function() { } /**************************************/ -B5500Processor.prototype.enterSubroutine(descriptorCall) { +B5500Processor.prototype.enterSubroutine = function(descriptorCall) { /* Enters a subroutine via the present program descriptor in A as part of an OPDC or DESC syllable. Also handles accidental entry */ var aw = this.A; // local copy of word in A reg @@ -1514,17 +1517,21 @@ B5500Processor.prototype.run = function() { case 0: this.T = ((this.P - this.P % 0x1000000000) / 0x1000000000) % 0x1000; this.L++; + break; case 1: this.T = ((this.P - this.P % 0x1000000) / 0x1000000) % 0x1000; this.L++; + break; case 2: this.T = ((this.P - this.P % 0x1000) / 0x1000) % 0x1000; this.L++; + break; case 3: this.T = this.P % 0x1000; this.L = 0; this.C++; this.access(0x30); // P = [C] + break; } } } while ((this.cycleCount += 2) < this.cycleLimit && this.busy); @@ -1556,6 +1563,22 @@ B5500Processor.prototype.schedule = function schedule() { if (that.busy) { delayTime = that.procTime/1000 - new Date().getTime(); that.scheduleSlack += delayTime; - that.scheduler = setTimeout(that.schedule, (delayTime < 0 ? 0 : delayTime)); + that.scheduler = setTimeout(that.schedule, (delayTime < 0 ? 1 : delayTime)); } }; + +/**************************************/ +B5500Processor.prototype.step = function() { + /* Single-steps the processor. Normally this will cause one instruction to + be executed, but note that in case of an interrupt, one or two injected + instructions (e.g., SFI followed by ITI) could also be executed. */ + + this.cycleLimit = 1; + this.cycleCount = 0; + + this.run(); + + this.totalCycles += this.cycleCount + this.procTime += this.cycleCount; +}; + diff --git a/emulator/B5500SystemConfiguration.js b/emulator/B5500SystemConfiguration.js new file mode 100644 index 0000000..580da2d --- /dev/null +++ b/emulator/B5500SystemConfiguration.js @@ -0,0 +1,43 @@ +/*********************************************************************** +* retro-b5500/emulator B5500SystemConfiguration.js +************************************************************************ +* Copyright (c) 2012, Nigel Williams and Paul Kimpel. +* Licensed under the MIT License, +* see http://www.opensource.org/licenses/mit-license.php +************************************************************************ +* B5500 System Configuration module. +* +* This is presently a static Javascript object describing the hardware +* modules and peripherals attached to the system. +************************************************************************ +* 2012-06-30 P.Kimpel +* Original version, from thin air. +***********************************************************************/ +"use strict"; + +var B5500SystemConfiguration = { + + PA: true, // Processor A available + PB: false, // Processor B available + + PB1L: false, // PA is P1 (false) | PB is P1 (true) + + IO1: true, // I/O Unit 1 available + IO2: true, // I/O Unit 2 available + IO3: false, // I/O Unit 3 available + IO4: false, // I/O Unit 4 available + + MemMod: [ + true, // Memory module 0 available (4KW) + true, // Memory module 1 available (4KW) + false, // Memory module 2 available (4KW) + false, // Memory module 3 available (4KW) + false, // Memory module 4 available (4KW) + false, // Memory module 5 available (4KW) + false, // Memory module 6 available (4KW) + false // Memory module 7 available (4KW) + ], + + // Peripheral configuration should go here. Somehow. Someday. + Dummy: null +}; diff --git a/webUI/B5500Console.html b/webUI/B5500Console.html index f7201be..810f86a 100644 --- a/webUI/B5500Console.html +++ b/webUI/B5500Console.html @@ -1,4 +1,4 @@ - + B5500 Emulator Operator Console @@ -6,9 +6,43 @@ + + + + @@ -47,7 +81,7 @@ window.onload = function() { B CONTROL -
+
POWER ON