mirror of
https://github.com/pkimpel/retro-b5500.git
synced 2026-02-12 11:17:29 +00:00
954 lines
38 KiB
JavaScript
954 lines
38 KiB
JavaScript
/***********************************************************************
|
|
* 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
|
|
************************************************************************
|
|
* 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
|
|
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.P2 = null; // Reference for Processor 2 (slave) [PA or PB]
|
|
|
|
this.addressSpace = [ // Array of memory module address spaces (8 x 32KB each)
|
|
null, null, null, null, null, null, null, null];
|
|
this.memMod = [ // Array of memory module words as Float64s (8 x 4KW each)
|
|
null, null, null, null, null, null, null, null];
|
|
this.unit = [ // Array of peripheral units, indexed by ready-mask bit number
|
|
null, null, null, null, null, null, null, null,
|
|
null, null, null, null, null, null, null, null,
|
|
null, null, null, null, null, null, null, null,
|
|
null, null, null, null, null, null, null, null,
|
|
null, null, null, null, null, null, null, null,
|
|
null, null, null, null, null, null, null, null];
|
|
|
|
// Instance variables and flags
|
|
this.poweredUp = 0; // System power indicator
|
|
this.unitStatusMask = 0; // Peripheral unit ready-status bitmask
|
|
this.unitBusyMask = 0; // Peripheral unit busy-status bitmask
|
|
|
|
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() for timer tick
|
|
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.version = "0.01";
|
|
|
|
B5500CentralControl.rtcTick = 1000/60; // Real-time clock period, milliseconds
|
|
|
|
B5500CentralControl.pow2 = [ // powers of 2 from 0 to 52
|
|
0x1, 0x2, 0x4, 0x8,
|
|
0x10, 0x20, 0x40, 0x80,
|
|
0x100, 0x200, 0x400, 0x800,
|
|
0x1000, 0x2000, 0x4000, 0x8000,
|
|
0x10000, 0x20000, 0x40000, 0x80000,
|
|
0x100000, 0x200000, 0x400000, 0x800000,
|
|
0x1000000, 0x2000000, 0x4000000, 0x8000000,
|
|
0x10000000, 0x20000000, 0x40000000, 0x80000000,
|
|
0x100000000, 0x200000000, 0x400000000, 0x800000000,
|
|
0x1000000000, 0x2000000000, 0x4000000000, 0x8000000000,
|
|
0x10000000000, 0x20000000000, 0x40000000000, 0x80000000000,
|
|
0x100000000000, 0x200000000000, 0x400000000000, 0x800000000000,
|
|
0x1000000000000, 0x2000000000000, 0x4000000000000, 0x8000000000000,
|
|
0x10000000000000];
|
|
|
|
B5500CentralControl.mask2 = [ // (2**n)-1 For n From 0 to 52
|
|
0x0, 0x1, 0x3, 0x7,
|
|
0x0F, 0x1F, 0x3F, 0x7F,
|
|
0x0FF, 0x1FF, 0x3FF, 0x7FF,
|
|
0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF,
|
|
0x0FFFF, 0x1FFFF, 0x3FFFF, 0x7FFFF,
|
|
0x0FFFFF, 0x1FFFFF, 0x3FFFFF, 0x7FFFFF,
|
|
0x0FFFFFF, 0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF,
|
|
0x0FFFFFFF, 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF,
|
|
0x0FFFFFFFF, 0x1FFFFFFFF, 0x3FFFFFFFF, 0x7FFFFFFFF,
|
|
0x0FFFFFFFFF, 0x1FFFFFFFFF, 0x3FFFFFFFFF, 0x7FFFFFFFFF,
|
|
0x0FFFFFFFFFF, 0x1FFFFFFFFFF, 0x3FFFFFFFFFF, 0x7FFFFFFFFFF,
|
|
0x0FFFFFFFFFFF, 0x1FFFFFFFFFFF, 0x3FFFFFFFFFFF , 0x7FFFFFFFFFFF,
|
|
0x0FFFFFFFFFFFF, 0x1FFFFFFFFFFFF, 0x3FFFFFFFFFFFF, 0x7FFFFFFFFFFFF,
|
|
0x0FFFFFFFFFFFFF] ;
|
|
|
|
// The following two-dimensional array translates unit designates to a unique 1-relative
|
|
// peripheral unit index. This index is the same as the unit's ready-status bit number,
|
|
// which is why they are in the range 17..47. The [0] dimension determines the index
|
|
// when writing; the [1] dimension determines the index when reading. This approach
|
|
// is necessary since some unit designates map to two different devices depending
|
|
// on IOD.[24:1], e.g. designate 14=CPA/CRA (status bits 23/24).
|
|
|
|
B5500CentralControl.unitIndex = [
|
|
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
|
[null, 47,null, 46, 31, 45, 29, 44, 30, 43, 25, 42, 28, 41,null, 40,
|
|
17, 39, 21, 38, 18, 37, 27, 36,null, 35, 26, 34,null, 33, 22, 32],
|
|
[null, 47,null, 46, 31, 45, 29, 44, 30, 43, 24, 42, 28, 41, 23, 40,
|
|
17, 39, 20, 38, 19, 37,null, 36,null, 35,null, 34,null, 33, 22, 32]];
|
|
|
|
// The following object maps the unit mnemonics from B5500SystemConfiguration.units
|
|
// to the attributes needed to configure the CC unit[] array.
|
|
|
|
B5500CentralControl.unitSpecs = {
|
|
SPO: {unitIndex: 22, designate: 30, unitClass: B5500SPOUnit},
|
|
DKA: {unitIndex: 29, designate: 6, unitClass: B5500DiskUnit},
|
|
DKB: {unitIndex: 28, designate: 12, unitClass: B5500DiskUnit},
|
|
CRA: {unitIndex: 24, designate: 10, unitClass: null},
|
|
CRB: {unitIndex: 23, designate: 14, unitClass: null},
|
|
CPA: {unitIndex: 25, designate: 10, unitClass: null},
|
|
LPA: {unitIndex: 27, designate: 22, unitClass: null},
|
|
LPB: {unitIndex: 26, designate: 26, unitClass: null},
|
|
PRA: {unitIndex: 20, designate: 18, unitClass: null},
|
|
PRB: {unitIndex: 19, designate: 20, unitClass: null},
|
|
PPA: {unitIndex: 21, designate: 18, unitClass: null},
|
|
PPB: {unitIndex: 18, designate: 20, unitClass: null},
|
|
DCA: {unitIndex: 17, designate: 16, unitClass: null},
|
|
DRA: {unitIndex: 31, designate: 4, unitClass: null},
|
|
DRB: {unitIndex: 30, designate: 8, unitClass: null},
|
|
MTA: {unitIndex: 47, designate: 1, unitClass: null},
|
|
MTB: {unitIndex: 46, designate: 3, unitClass: null},
|
|
MTC: {unitIndex: 45, designate: 5, unitClass: null},
|
|
MTD: {unitIndex: 44, designate: 7, unitClass: null},
|
|
MTE: {unitIndex: 43, designate: 9, unitClass: null},
|
|
MTF: {unitIndex: 42, designate: 11, unitClass: null},
|
|
MTH: {unitIndex: 41, designate: 13, unitClass: null},
|
|
MTJ: {unitIndex: 40, designate: 15, unitClass: null},
|
|
MTK: {unitIndex: 39, designate: 17, unitClass: null},
|
|
MTL: {unitIndex: 38, designate: 19, unitClass: null},
|
|
MTM: {unitIndex: 37, designate: 21, unitClass: null},
|
|
MTN: {unitIndex: 36, designate: 23, unitClass: null},
|
|
MTP: {unitIndex: 35, designate: 25, unitClass: null},
|
|
MTR: {unitIndex: 34, designate: 27, unitClass: null},
|
|
MTS: {unitIndex: 33, designate: 29, unitClass: null},
|
|
MTT: {unitIndex: 32, designate: 31, unitClass: null}};
|
|
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.clear = function() {
|
|
/* Initializes (and if necessary, creates) 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; // word lower power exponent
|
|
var p; // bottom portion of word power of 2
|
|
|
|
if (e > 0) {
|
|
return ((word - word % (p = B5500CentralControl.pow2[e]))/p) % 2;
|
|
} else {
|
|
return word % 2;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.bitSet = function(word, bit) {
|
|
/* Sets the specified bit in word and returns the updated word */
|
|
var ue = 48-bit; // word upper power exponent
|
|
var le = ue-1; // word lower power exponent
|
|
var bpower = 1; // bottom portion of word power of 2
|
|
var bottom = // unaffected bottom portion of word
|
|
(le == 0 ? 0 : (word % (bpower = B5500CentralControl.pow2[le])));
|
|
var top = // unaffected top portion of word
|
|
(bit == 0 ? 0 : (word - (word % B5500CentralControl.pow2[ue])));
|
|
|
|
return bpower + top + bottom;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.bitReset = function(word, bit) {
|
|
/* Resets the specified bit in word and returns the updated word */
|
|
var ue = 48-bit; // word upper power exponent
|
|
var le = ue-1; // word lower power exponent
|
|
var bottom = // unaffected bottom portion of word
|
|
(le == 0 ? 0 : (word % B5500CentralControl.pow2[le]));
|
|
var top = // unaffected top portion of word
|
|
(bit == 0 ? 0 : (word - (word % B5500CentralControl.pow2[ue])));
|
|
|
|
return top + bottom;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.fieldIsolate = function(word, start, width) {
|
|
/* Extracts a bit field [start:width] from word and returns the field */
|
|
var le = 48-start-width; // lower power exponent
|
|
var p; // bottom portion of word power of 2
|
|
|
|
return (le == 0 ? word :
|
|
(word - word % (p = B5500CentralControl.pow2[le]))/p
|
|
) % B5500CentralControl.pow2[width];
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.fieldInsert = function(word, start, width, value) {
|
|
/* Inserts a bit field from the low-order bits of value ([48-width:width])
|
|
into word.[start:width] and returns the updated word */
|
|
var ue = 48-start; // word upper power exponent
|
|
var le = ue-width; // word lower power exponent
|
|
var bpower = 1; // bottom portion of word power of 2
|
|
var bottom = // unaffected bottom portion of word
|
|
(le == 0 ? 0 : (word % (bpower = B5500CentralControl.pow2[le])));
|
|
var top = // unaffected top portion of word
|
|
(ue == 0 ? 0 : (word - (word % B5500CentralControl.pow2[ue])));
|
|
|
|
return (value % B5500CentralControl.pow2[width])*bpower + top + bottom;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.fieldTransfer = function(word, wstart, width, value, vstart) {
|
|
/* Inserts a bit field from value.[vstart:width] into word.[wstart:width] and
|
|
returns the updated word */
|
|
var ue = 48-wstart; // word upper power exponent
|
|
var le = ue-width; // word lower power exponent
|
|
var ve = 48-vstart-width; // value lower power exponent
|
|
var vpower; // bottom port of value power of 2
|
|
var bpower = 1; // bottom portion of word power of 2
|
|
var bottom = // unaffected bottom portion of word
|
|
(le == 0 ? 0 : (word % (bpower = B5500CentralControl.pow2[le])));
|
|
var top = // unaffected top portion of word
|
|
(ue == 0 ? 0 : (word - (word % B5500CentralControl.pow2[ue])));
|
|
|
|
return ((ve == 0 ? value :
|
|
(value - value % (vpower = B5500CentralControl.pow2[ve]))/vpower
|
|
) % B5500CentralControl.pow2[width]
|
|
)*bpower + top + bottom;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.fetch = function(acc) {
|
|
/* Called by a 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 (acc.requestorID) {
|
|
case "A":
|
|
this.PAXF = modMask;
|
|
break;
|
|
case "B":
|
|
this.PBXF = modMask;
|
|
break;
|
|
case "1":
|
|
this.I1XF = modMask;
|
|
break;
|
|
case "2":
|
|
this.I2XF = modMask;
|
|
break;
|
|
case "3":
|
|
this.I3XF = modMask;
|
|
break;
|
|
case "4":
|
|
this.I4XF = modMask;
|
|
break;
|
|
}
|
|
|
|
// For now, we assume memory parity can never happen
|
|
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 {
|
|
acc.MPED = 0; // no parity error
|
|
acc.MAED = 0; // no address error
|
|
acc.word = this.memMod[modNr][modAddr];
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.store = function(acc) {
|
|
/* 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 (acc.requestorID) {
|
|
case "A":
|
|
this.PAXF = modMask;
|
|
break;
|
|
case "B":
|
|
this.PBXF = modMask;
|
|
break;
|
|
case "1":
|
|
this.I1XF = modMask;
|
|
break;
|
|
case "2":
|
|
this.I2XF = modMask;
|
|
break;
|
|
case "3":
|
|
this.I3XF = modMask;
|
|
break;
|
|
case "4":
|
|
this.I4XF = modMask;
|
|
break;
|
|
}
|
|
|
|
// For now, we assume memory parity can never happen
|
|
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 {
|
|
acc.MPED = 0; // no parity error
|
|
acc.MAED = 0; // no address error
|
|
this.memMod[modNr][modAddr] = acc.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.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 = this.P2) ?
|
|
( 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
|
|
)
|
|
: 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;
|
|
this.AD1F = 0; // make unit non-busy
|
|
break;
|
|
case 0x18: // @30: I/O 2 finished
|
|
this.CCI09F = 0;
|
|
this.AD2F = 0; // make unit non-busy
|
|
break;
|
|
case 0x19: // @31: I/O 3 finished
|
|
this.CCI10F = 0;
|
|
this.AD3F = 0; // make unit non-busy
|
|
break;
|
|
case 0x1A: // @32: I/O 4 finished
|
|
this.CCI11F = 0;
|
|
this.AD4F = 0; // make unit non-busy
|
|
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
|
|
if (p2) {p2.I &= 0xFE}
|
|
break;
|
|
case 0x21: // @41: P2 invalid address error
|
|
if (p2) {p2.I &= 0xFD}
|
|
break;
|
|
case 0x22: // @42: P2 stack overflow
|
|
if (p2) {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:
|
|
if (p2) {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 interval; // milliseconds to next 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;
|
|
/********** INHIBIT FOR NOW **********
|
|
that.CCI03F = 1; // set timer interrupt
|
|
that.signalInterrupt();
|
|
*************************************/
|
|
}
|
|
interval = (that.nextTimeStamp += B5500CentralControl.rtcTick) - thisTime;
|
|
that.timer = setTimeout(function() {that.tock()}, (interval < 0 ? 1 : interval));
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.haltP2 = function() {
|
|
/* Called by P1 to halt P2. storeForInterrupt() will set P2BF=0 */
|
|
|
|
this.HP2F = 1;
|
|
// We know P2 is not currently running on this thread, so save its registers
|
|
if (this.P2 && this.P2BF) {
|
|
this.P2.storeForInterrupt(0);
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
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. */
|
|
|
|
if (!this.P2 || this.P2BF) {
|
|
this.CCI12F = 1; // set P2 busy interrupt
|
|
this.signalInterrupt();
|
|
} else {
|
|
this.P2BF = 1;
|
|
this.HP2F = 0;
|
|
this.P2.initiateAsP2();
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.initiateIO = function() {
|
|
/* Selects an I/O unit and initiates an I/O */
|
|
|
|
if (this.IO1 && this.IO1.REMF && !this.AD1F) {
|
|
this.AD1F = 1;
|
|
this.IO1.initiate();
|
|
} else if (this.IO2 && this.IO2.REMF && !this.AD2F) {
|
|
this.AD2F = 1;
|
|
this.IO2.initiate();
|
|
} else if (this.IO3 && this.IO3.REMF && !this.AD3F) {
|
|
this.AD3F = 1;
|
|
this.IO3.initiate();
|
|
} else if (this.IO4 && this.IO4.REMF && !this.AD4F) {
|
|
this.AD4F = 1;
|
|
this.IO4.initiate();
|
|
} else {
|
|
this.CCI04F = 1; // set I/O busy interrupt
|
|
this.signalInterrupt();
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.interrogateIOChannel = function() {
|
|
/* Returns a value as for the processor TIO syllable indicating the first
|
|
available and non-busy I/O Unit */
|
|
|
|
if (this.IO1 && this.IO1.REMF && !this.AD1F) {
|
|
return 1;
|
|
} else if (this.IO2 && this.IO2.REMF && !this.AD2F) {
|
|
return 2;
|
|
} else if (this.IO3 && this.IO3.REMF && !this.AD3F) {
|
|
return 3;
|
|
} else if (this.IO4 && this.IO4.REMF && !this.AD4F) {
|
|
return 4;
|
|
} else {
|
|
return 0; // All I/O Units busy
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.interrogateUnitStatus = function() {
|
|
/* Returns a bitmask as for the processor TUS syllable indicating the
|
|
ready status of all peripheral units */
|
|
|
|
return this.unitStatusMask;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.testUnitReady = function(index) {
|
|
/* Determines whether the unit index "index" is currently in ready status.
|
|
Returns 1 if ready, 0 if not ready */
|
|
|
|
return (index ? this.bit(this.unitStatusMask, index) : 0);
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.testUnitBusy = function(index) {
|
|
/* Determines whether the unit index "index" is currently in use by any other
|
|
I/O Unit. Returns 1 if busy, 0 if not busy */
|
|
|
|
return (index ? this.bit(this.unitBusyMask, index) : 0);
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.setUnitBusy = function(index, busy) {
|
|
/* Sets or resets the unit-busy mask bit for unit index "index" */
|
|
|
|
if (index) {
|
|
this.unitBusyMask = (busy ? this.bitSet(this.unitBusyMask, index)
|
|
: this.bitReset(this.unitBusyMask, index));
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
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 = 0;
|
|
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 = 0;
|
|
this.PB.cycleLimit = 0;
|
|
if (this.PB.scheduler) {
|
|
clearTimeout(this.PB.scheduler);
|
|
this.PB.scheduler = null;
|
|
}
|
|
}
|
|
|
|
if (this.loadTimer) {
|
|
clearTimeout(this.loadTimer);
|
|
this.loadTimer = null;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.loadComplete = function loadComplete() {
|
|
/* Monitors an initial load I/O operation for complete status.
|
|
When complete, initiates P1 */
|
|
var completed = false; // true if some I/O Unit finished
|
|
var that = loadComplete.that; // capture the current closure context
|
|
|
|
if (that.CCI08F) { // I/O Unit 1 finished
|
|
completed = true;
|
|
that.CCI08F = 0;
|
|
that.AD1F = 0;
|
|
} else if (that.CCI09F) { // I/O Unit 2 finished
|
|
completed = true;
|
|
that.CCI09F = 0;
|
|
that.AD2F = 0;
|
|
} else if (that.CCI10F) { // I/O Unit 3 finished
|
|
completed = true;
|
|
that.CCI10F = 0;
|
|
that.AD3F = 0;
|
|
} else if (that.CCI11F) { // I/O Unit 4 finished
|
|
completed = true;
|
|
that.CCI11F = 0;
|
|
that.AD4F = 0;
|
|
} else { // Nothing finished yet (or there was an error)
|
|
that.loadTimer = setTimeout(that.loadComplete, 100);
|
|
}
|
|
|
|
if (completed) {
|
|
that.loadTimer = null;
|
|
that.LOFF = 0;
|
|
that.P1.preset(0x10); // start execution at C=@20
|
|
that.P1.start(); // let'er rip
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.load = function() {
|
|
/* Initiates a Load operation to start the system */
|
|
|
|
if (this.P1 && !this.P1.busy) {
|
|
this.clear();
|
|
this.LOFF = 1;
|
|
if (this.IO1 && this.IO1.REMF && !this.AD1F) {
|
|
this.AD1F = 1;
|
|
this.IO1.initiateLoad(this.cardLoadSelect);
|
|
this.loadComplete();
|
|
} else if (this.IO2 && this.IO2.REMF && !this.AD2F) {
|
|
this.AD2F = 1;
|
|
this.IO2.initiateLoad(this.cardLoadSelect);
|
|
this.loadComplete();
|
|
} else if (this.IO3 && this.IO3.REMF && !this.AD3F) {
|
|
this.AD3F = 1;
|
|
this.IO3.initiateLoad(this.cardLoadSelect);
|
|
this.loadComplete();
|
|
} else if (this.IO4 && this.IO4.REMF && !this.AD4F) {
|
|
this.AD4F = 1;
|
|
this.IO4.initiateLoad(this.cardLoadSelect);
|
|
this.loadComplete();
|
|
} else {
|
|
this.CCI04F = 1; // set I/O busy interrupt
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.loadTest = function(buf, loadAddr) {
|
|
/* Loads a test codestream into memory starting at B5500 word address
|
|
"loadAddr" from the ArrayBuffer "buf". Returns the number of B5500
|
|
words loaded into memory. Note that when loading an ESPOL "DISK" file,
|
|
the first executable location is @20, so you will typically want to load
|
|
to address 0 and call cc.runTest(0x10) [where 0x10 = @20]. This routine
|
|
should not be used to load ESPOL "DECK" files */
|
|
var addr = loadAddr; // starting B5500 memory address
|
|
var bytes = buf.byteLength;
|
|
var data = new DataView(buf); // use DataView() to avoid problems with littleendians.
|
|
var power = 0x10000000000;
|
|
var word = 0;
|
|
var x = 0;
|
|
|
|
function store(addr, word) {
|
|
/* Stores a 48-bit word at the specified B5500 address.
|
|
Invalid addresses and parity errors are ignored */
|
|
var modNr = addr >>> 12;
|
|
var modAddr = addr & 0x0FFF;
|
|
|
|
if (modNr < 8 && this.memMod[modNr]) {
|
|
this.memMod[modNr][modAddr] = word;
|
|
}
|
|
}
|
|
|
|
if (!this.poweredUp) {
|
|
throw "cc.loadTest: Cannot load with system powered off"
|
|
} else {
|
|
while (bytes > 6) {
|
|
word = data.getUint8(x)* 0x10000000000 +
|
|
data.getUint8(x+1)* 0x100000000 +
|
|
data.getUint8(x+2)* 0x1000000 +
|
|
data.getUint8(x+3)* 0x10000 +
|
|
data.getUint8(x+4)* 0x100 +
|
|
data.getUint8(x+5);
|
|
store.call(this, addr, word);
|
|
x += 6;
|
|
bytes -= 6;
|
|
if (++addr > 0x7FFF) {
|
|
break;
|
|
}
|
|
}
|
|
// Store any partial word that may be left
|
|
while (bytes > 0) {
|
|
word += data.getUint8(x, false)*power;
|
|
x++;
|
|
bytes--;
|
|
power /= 0x100;
|
|
}
|
|
store.call(this, addr, word);
|
|
}
|
|
return addr-loadAddr+1;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.runTest = function(runAddr) {
|
|
/* Executes a test program previously loaded by this.loadTest on processor
|
|
P1. "runAddr" is the B5500 word address at which execution will begin
|
|
(typically 0x10 [octal 20]) */
|
|
|
|
this.clear();
|
|
this.loadTimer = null;
|
|
this.LOFF = 0;
|
|
this.P1.preset(runAddr);
|
|
this.P1.start();
|
|
};
|
|
|
|
/**************************************/
|
|
B5500CentralControl.prototype.configureSystem = function() {
|
|
/* Establishes the hardware module configuration from the
|
|
B5500SystemConfiguration module */
|
|
var cfg = B5500SystemConfiguration;
|
|
var mnem;
|
|
var signal = null;
|
|
var specs;
|
|
var u;
|
|
var unitClass;
|
|
var x;
|
|
|
|
function makeChange(cc, maskBit) {
|
|
return function(ready) {
|
|
cc.unitStatusMask = (ready ? cc.bitSet(cc.unitStatusMask, maskBit)
|
|
: cc.bitReset(cc.unitStatusMask, maskBit));
|
|
};
|
|
}
|
|
|
|
function makeSignal(cc, mnemonic) {
|
|
switch (mnemonic) {
|
|
case "SPO":
|
|
return function() {
|
|
cc.CCI05F = 1;
|
|
cc.signalInterrupt();
|
|
};
|
|
break;
|
|
case "LPA":
|
|
return function() {
|
|
cc.setUnitBusy(27, 0);
|
|
cc.CCI06F = 1;
|
|
cc.signalInterrupt();
|
|
};
|
|
break;
|
|
case "LPB":
|
|
return function() {
|
|
cc.setUnitBusy(26, 0);
|
|
cc.CCI07F = 1;
|
|
cc.signalInterrupt();
|
|
};
|
|
break;
|
|
case "DKA":
|
|
return function() {
|
|
cc.setUnitBusy(29, 0);
|
|
cc.CCI15F = 1;
|
|
cc.signalInterrupt();
|
|
};
|
|
break;
|
|
case "DKB":
|
|
return function() {
|
|
cc.setUnitBusy(28, 0);
|
|
cc.CCI16F = 1;
|
|
cc.signalInterrupt();
|
|
};
|
|
break;
|
|
default:
|
|
return function() {};
|
|
break;
|
|
};
|
|
}
|
|
|
|
// ***** !! inhibit for now ***** // this.DD = new B5500DistributionAndDisplay(this);
|
|
|
|
// Configure the processors
|
|
if (cfg.PA) {this.PA = new B5500Processor("A", this)};
|
|
if (cfg.PB) {this.PB = new B5500Processor("B", this)};
|
|
|
|
// Determine P1/P2
|
|
this.PB1L = (cfg.PB1L ? 1 : 0);
|
|
|
|
// Configure the I/O Units
|
|
if (cfg.IO1) {this.IO1 = new B5500IOUnit("1", this)};
|
|
if (cfg.IO2) {this.IO2 = new B5500IOUnit("2", this)};
|
|
if (cfg.IO3) {this.IO3 = new B5500IOUnit("3", this)};
|
|
if (cfg.IO4) {this.IO4 = new B5500IOUnit("4", this)};
|
|
|
|
// Configure memory
|
|
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]);
|
|
}
|
|
}
|
|
|
|
// Configure the peripheral units
|
|
for (mnem in cfg.units) {
|
|
if (cfg.units[mnem]) {
|
|
specs = B5500CentralControl.unitSpecs[mnem];
|
|
if (specs) {
|
|
unitClass = specs.unitClass || B5500DummyUnit;
|
|
if (unitClass) {
|
|
u = new unitClass(mnem, specs.unitIndex, specs.designate,
|
|
makeChange(this, specs.unitIndex), makeSignal(this, mnem));
|
|
this.unit[specs.unitIndex] = u;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 = 1;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
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 = 0;
|
|
}
|
|
};
|