/*********************************************************************** * 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 ************************************************************************ * JavaScript object definition for the B5500 Central Control module. ************************************************************************ * 2012-06-03 P.Kimpel * Original version, from thin air. ***********************************************************************/ /**************************************/ function B5500CentralControl() { /* Constructor for the Central Control module object */ /* Global system modules */ this.PA = null; // Processor A (PA) this.PB = null; // Processor B (PB) this.IO1 = null; // I/O unit 1 this.IO2 = null; // I/O unit 2 this.IO3 = null; // I/O unit 3 this.IO4 = null; // I/O unit 4 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 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]); 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.timer = null; // Reference to the RTC setTimeout id. this.loadTimer = null; // Reference to the load setTimeout id. this.tock.that = this; // Establish contexts for when called from setTimeout(). this.loadComplete.that = this; this.clear(); // Create and initialize the Central Control state } /**************************************/ /* Global constants */ B5500CentralControl.rtcTick = 1000/60; // Real-time clock period, milliseconds B5500CentralControl.pow2 = [ // powers of 2 from 0 to 52 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824, 2147483648, 4294967296, 8589934592, 17179869184, 34359738368, 68719476736, 137438953472, 274877906944, 549755813888, 1099511627776, 2199023255552, 4398046511104, 8796093022208, 17592186044416, 35184372088832, 70368744177664, 140737488355328, 281474976710656, 562949953421312, 1125899906842624, 2251799813685248, 4503599627370496]; B5500CentralControl.mask2 = [ // (2**n)-1 for n from 0 to 52 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535, 131071, 262143, 524287, 1048575, 2097151, 4194303, 8388607, 16777215, 33554431, 67108863, 134217727, 268435455, 536870911, 1073741823, 2147483647, 4294967295, 8589934591, 17179869183, 34359738367, 68719476735, 137438953471, 274877906943, 549755813887, 1099511627775, 2199023255551, 4398046511103, 8796093022207, 17592186044415, 35184372088831, 70368744177663, 140737488355327, 281474976710655, 562949953421311, 1125899906842623, 2251799813685247, 4503599627370495]; /**************************************/ B5500CentralControl.prototype.clear = function() { /* Initializes the system and starts the real-time clock */ if (this.timer) { clearTimeout(this.timer); } this.nextTimeStamp = new Date().getTime() + B5500CentralControl.rtcTick; this.timer = setTimeout(this.tock, B5500CentralControl.rtcTick); this.IAR = 0; // Interrupt address register this.TM = 0; // Real-time clock (6 bits, 60 ticks per second) this.CCI03F = 0; // Time interval interrupt this.CCI04F = 0; // I/O busy interrupt this.CCI05F = 0; // Keyboard request interrupt this.CCI06F = 0; // Printer 1 finished interrupt this.CCI07F = 0; // Printer 2 finished interrupt this.CCI08F = 0; // I/O unit 1 finished interrupt (RD in @14) this.CCI09F = 0; // I/O unit 2 finished interrupt (RD in @15) this.CCI10F = 0; // I/O unit 3 finished interrupt (RD in @16) this.CCI11F = 0; // I/O unit 4 finished interrupt (RD in @17) this.CCI12F = 0; // P2 busy interrupt this.CCI13F = 0; // Remote inquiry request interrupt this.CCI14F = 0; // Special interrupt #1 (not used) this.CCI15F = 0; // Disk file #1 read check finished this.CCI16F = 0; // Disk file #2 read check finished this.MCYF = 0; // Memory cycle FFs (one bit per M0..M7) this.PAXF = 0; // PA memory exchange select (M0..M7) this.PBXF = 0; // PB memory exchange select (M0..M7) this.I1XF = 0; // I/O unit 1 exchange select (M0..M7) this.I2XF = 0; // I/O unit 2 exchange select (M0..M7) this.I3XF = 0; // I/O unit 3 exchange select (M0..M7) this.I4XF = 0; // I/O unit 4 exchange select (M0..M7) this.AD1F = 0; // I/O unit 1 busy this.AD2F = 0; // I/O unit 2 busy this.AD3F = 0; // I/O unit 3 busy this.AD4F = 0; // I/O unit 4 busy this.LOFF = 0; // Load button pressed on console this.CTMF = 0; // Commence timing FF this.P2BF = 0; // Processor 2 busy FF this.HP2F = 1; // Halt processor 2 FF if (this.PA) { this.PA.clear(); } if (this.PB) { this.PB.clear(); } this.P1 = (this.PB1L ? this.PB : this.PA); this.P2 = (this.PB1L ? this.PA : this.PB); if (!this.P2) { this.P2BF = 1; // mark non-existent P2 as busy } } /**************************************/ B5500CentralControl.prototype.bit = function(word, bit) { /* Extracts and returns the specified bit from the word */ var e = 47-bit; var p; 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) { /* Extracts a bit field [start:width] from word and returns the field */ var ue = 48-start; // upper power exponent var le = ue-width; // lower power exponent var p; if (le > 0) { p = B5500CentralControl.pow2[le]; return ((word - word%p)/p) % B5500CentralControl.pow2[width]; } else { return word % B5500CentralControl.pow2[width]; } } /**************************************/ B5500CentralControl.prototype.fieldInsert = function(word, start, width, value) { /* Inserts a bit field into word.[start:width] and returns the updated word */ var ue = 48-start; // upper power exponent var le = ue-width; // lower power exponent var bpower = 1; // bottom portion power of 2 var bottom = 0; // unaffected bottom portion of word var top = 0; // unaffected top portion of word if (start > 0) { top = word - (word % B5500CentralControl.pow2[ue]); } if (le > 0) { bpower = B5500CentralControl.pow2[le]; 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; 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: this.PAXF = modMask; break; case PB: this.PBXF = modMask; break; case IO1: this.I1XF = modMask; break; case IO2: this.I2XF = modMask; break; case IO3: this.I3XF = modMask; break; case IO4: 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; // no .word value is returned in this case } else { acer.MPED = 0; acer.MAED = 0; acer.word = this.Memory[memMod][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; 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: this.PAXF = modMask; break; case this.PB: this.PBXF = modMask; break; case this.IO1: this.I1XF = modMask; break; case this.IO2: this.I2XF = modMask; break; case this.IO3: this.I3XF = modMask; break; case this.IO4: 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; } else { acer.MPED = 0; acer.MAED = 0; this.Memory[memMod][modAddr] = acer.word; } } /**************************************/ B5500CentralControl.prototype.signalInterrupt = function() { /* Called by all modules to signal that an interrupt has occurred and to invoke the interrupt prioritization mechanism. This will result in an updated vector address in the IAR. Can also be called to reprioritize any remaining interrupts after an interrupt is handled. If no interrupt condition exists, this.IAR is set to zero. */ var p1 = this.P1; var p2 = this.P2; this.IAR = p1.I & 0x01 ? 0x30 // @60: P1 memory parity error : p1.I & 0x02 ? 0x31 // @61: P1 invalid address error : this.CCI03F ? 0x12 // @22: Time interval : this.CCI04F ? 0x13 // @23: I/O busy : this.CCI05F ? 0x14 // @24: Keyboard request : this.CCI08F ? 0x17 // @27: I/O 1 finished : this.CCI09F ? 0x18 // @30: I/O 2 finished : this.CCI10F ? 0x19 // @31: I/O 3 finished : this.CCI11F ? 0x1A // @32: I/O 4 finished : this.CCI06F ? 0x15 // @25: Printer 1 finished : this.CCI07F ? 0x16 // @26: Printer 2 finished : this.CCI12F ? 0x1B // @33: P2 busy : this.CCI13F ? 0x1C // @34: Inquiry request : this.CCI14F ? 0x1D // @35: Special interrupt 1 : this.CCI15F ? 0x1E // @36: Disk file 1 read check finished : this.CCI16F ? 0x1F // @37: Disk file 2 read check finished : p1.I & 0x04 ? 0x32 // @62: P1 stack overflow : p1.I & 0xF0 ? (p1.I >>> 4) + 0x30 // @64-75: P1 syllable-dependent : p2.I & 0x01 ? 0x20 // @40: P2 memory parity error : p2.I & 0x02 ? 0x21 // @41: P2 invalid address error : 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() { /* Resets an interrupt based on the current setting of this.IAR, then reprioritizes any remaining interrupts, leaving the new vector address in this.IAR. */ var p1 = this.P1; var p2 = this.P2; switch (this.IAR) { case 0x12: // @22: Time interval this.CCI03F = 0; break; case 0x13: // @23: I/O busy this.CCI04F = 0; break; case 0x14: // @24: Keyboard request this.CCI05F = 0; break; case 0x15: // @25: Printer 1 finished this.CCI06F = 0; break; case 0x16: // @26: Printer 2 finished this.CCI07F = 0; break; case 0x17: // @27: I/O 1 finished this.CCI08F = 0; break; case 0x18: // @30: I/O 2 finished this.CCI09F = 0; break; case 0x19: // @31: I/O 3 finished this.CCI10F = 0; break; case 0x1A: // @32: I/O 4 finished this.CCI11F = 0; break; case 0x1B: // @33: P2 busy this.CCI12F = 0; break; case 0x1C: // @34: Inquiry request this.CCI13F = 0; break; case 0x1D: // @35: Special interrupt 1 this.CCI14F = 0; break; case 0x1E: // @36: Disk file 1 read check finished this.CCI15F = 0; break; case 0x1F: // @37: Disk file 2 read check finished this.CCI16F = 0; break; case 0x20: // @40: P2 memory parity error p2.I &= 0xFE; break; case 0x21: // @41: P2 invalid address error p2.I &= 0xFD; break; case 0x22: // @42: P2 stack overflow p2.I &= 0xFB; break; case 0x24: // @44-55: P2 syllable-dependent case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: p2.I &= 0x0F; break; case 0x30: // @60: P1 memory parity error p1.I &= 0xFE; break; case 0x31: // @61: P1 invalid address error p1.I &= 0xFD; break; case 0x32: // @62: P1 stack overflow p1.I &= 0x0B; break; case 0x34: // @64-75: P1 syllable-dependent case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: p1.I &= 0x0F; break; default: // no interrupt vector was set break; } this.signalInterrupt(); } /**************************************/ B5500CentralControl.prototype.tock = function tock() { /* Handles the 1/60th second real-time clock tick */ var that = tock.that; // capture the current closure context var thisTime = new Date().getTime(); if (that.TM < 63) { that.TM++; } else { that.TM = 0; 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)); } /**************************************/ B5500CentralControl.prototype.initiateP2 = function() { /* Called by P1 to initiate P2. Assumes that an INCW has been stored at memory location @10. If P2 is busy or not present, sets the P2 busy interrupt. Otherwise, loads the INCW into P2's A register and initiates the processor. */ var p2 = this.P2; if (!this.P2 || this.P2BF) { this.CCI12F = 1; // set P2 busy interrupt this.signalInterrupt(); } else { p2.M = 8; // Now have P2 pick up the INCW p2.access(0x04); // A = [M] p2.AROF = 1; p2.T = 0x849; // inject 4111=IP1 into P2's T register p2.TROF = 1; p2.NCSF = 0; // make sure P2 is in control state this.P2BF = 1; this.HP2F = 0; // Now start scheduling P2 on the Javascript thread p2.procTime = new Date().getTime()*1000; p2.scheduler = setTimeout(p2.schedule, 0); } } /**************************************/ B5500CentralControl.prototype.initiateIO = function() { /* Selects an I/O unit and initiates an I/O */ if (this.IO1) { this.AD1F = 1; IO1.initiate(); } else if (this.IO2) { this.AD2F = 1; IO2.initiate(); } else if (this.IO3) { this.AD3F = 1; IO3.initiate(); } else if (this.IO4) { this.AD4F = 1; IO4.initiate(); } else { this.CCI04F = 1; // set I/O busy interrupt this.signalInterrupt(); } } /**************************************/ B5500CentralControl.prototype.halt = function() { /* Halts the processors. Any in-process I/Os are allowed to complete */ if (this.PA && this.PA.busy) { this.PA.busy = false; this.PA.cycleLimit = 0; if (this.PA.scheduler) { clearTimeout(this.PA.scheduler); this.PA.scheduler = null; } } if (this.PB && this.PB.busy) { this.PB.busy = false; this.PB.cycleLimit = 0; if (this.PB.scheduler) { clearTimeout(this.PB.scheduler); this.PB.scheduler = null; } } if (this.loadTimer) { cancelTimeout(this.loadTimer); this.loadTimer = null; } } /**************************************/ B5500CentralControl.prototype.load = function() { /* Initiates a Load operation to start the system */ if ((this.PA && this.PA.busy) || (this.PB && this.PB.busy)) { this.clear(); if (this.P1) { this.LOFF = 1; if (this.IO1) { // !! not sure about I/O selection here IO1.initiateLoad(this.cardLoadSelect); this.loadComplete(); } } } } /**************************************/ B5500CentralControl.prototype.loadComplete = function loadComplete() { /* Monitors an initial load I/O operation for complete status. When complete, initiates P1 */ var that = loadComplete.that; // capture the current closure context if (!that.CCI08F) { that.loadTimer = setTimeout(that.loadComplete, 10); } 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.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); } }