mirror of
https://github.com/pkimpel/retro-b5500.git
synced 2026-04-25 20:01:52 +00:00
Implement initial B5500DiskUnit and fix miscelleaneous other minor issues in I/O subsystem.
This commit is contained in:
@@ -42,7 +42,8 @@ function B5500CentralControl() {
|
||||
|
||||
// Instance variables and flags
|
||||
this.poweredUp = 0; // System power indicator
|
||||
this.unitStatusMask = 0; // Peripheral unit ready-status bitmask
|
||||
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
|
||||
@@ -115,8 +116,8 @@ B5500CentralControl.unitIndex = [
|
||||
|
||||
B5500CentralControl.unitSpecs = {
|
||||
SPO: {unitIndex: 22, designate: 30, unitClass: B5500SPOUnit},
|
||||
DKA: {unitIndex: 29, designate: 6, unitClass: null},
|
||||
DKB: {unitIndex: 28, designate: 12, unitClass: null},
|
||||
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},
|
||||
@@ -623,32 +624,29 @@ B5500CentralControl.prototype.interrogateUnitStatus = function() {
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500CentralControl.prototype.testUnitBusy = function(ioUnit, rw, unit) {
|
||||
/* Determines whether the unit designate "unit" is currently in use by any other
|
||||
I/O Unit than the one designated by "ioUnit". "rw" indicates whether the designate
|
||||
is for writing (0) or reading (1). Returns 0 if not busy */
|
||||
B5500CentralControl.prototype.testUnitReady = function(index) {
|
||||
/* Determines whether the unit index "index" is currently in ready status.
|
||||
Returns 1 if ready, 0 if not ready */
|
||||
|
||||
if (ioUnit != "1" && this.IO1 && this.IO1.REMF && this.AD1F && this.IO1.busyUnit == unit) {
|
||||
return 1;
|
||||
} else if (ioUnit != "2" && this.IO2 && this.IO2.REMF && this.AD2F && this.IO2.busyUnit == unit) {
|
||||
return 2;
|
||||
} else if (ioUnit != "3" && this.IO3 && this.IO3.REMF && this.AD3F && this.IO3.busyUnit == unit) {
|
||||
return 3;
|
||||
} else if (ioUnit != "4" && this.IO4 && this.IO4.REMF && this.AD4F && this.IO4.busyUnit == unit) {
|
||||
return 4;
|
||||
} else {
|
||||
return 0; // peripheral unit not in use by any other I/O Unit
|
||||
}
|
||||
return (index ? this.bit(this.unitStatusMask, index) : 0);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500CentralControl.prototype.testUnitReady = function(rw, unit) {
|
||||
/* Determines whether the unit designate "unit" is currently in ready status.
|
||||
"rw" indicates whether the designate is for writing (0) or reading (1).
|
||||
Returns 1 if ready, 0 if not ready */
|
||||
var index = B5500CentralControl.unitIndex[rw & 1][unit & 0x1F];
|
||||
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.unitStatusMask, index) : 0);
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
@@ -825,19 +823,38 @@ B5500CentralControl.prototype.configureSystem = function() {
|
||||
function makeSignal(cc, mnemonic) {
|
||||
switch (mnemonic) {
|
||||
case "SPO":
|
||||
return function() {cc.CCI05F = 1; cc.signalInterrupt()};
|
||||
return function() {
|
||||
cc.CCI05F = 1;
|
||||
cc.signalInterrupt();
|
||||
};
|
||||
break;
|
||||
case "LPA":
|
||||
return function() {cc.CCI06F = 1; cc.signalInterrupt()};
|
||||
return function() {
|
||||
cc.setUnitBusy(27, 0);
|
||||
cc.CCI06F = 1;
|
||||
cc.signalInterrupt();
|
||||
};
|
||||
break;
|
||||
case "LPB":
|
||||
return function() {cc.CCI07F = 1; cc.signalInterrupt()};
|
||||
return function() {
|
||||
cc.setUnitBusy(26, 0);
|
||||
cc.CCI07F = 1;
|
||||
cc.signalInterrupt();
|
||||
};
|
||||
break;
|
||||
case "DKA":
|
||||
return function() {cc.CCI15F = 1; cc.signalInterrupt()};
|
||||
return function() {
|
||||
cc.setUnitBusy(29, 0);
|
||||
cc.CCI15F = 1;
|
||||
cc.signalInterrupt();
|
||||
};
|
||||
break;
|
||||
case "DKB":
|
||||
return function() {cc.CCI16F = 1; cc.signalInterrupt()};
|
||||
return function() {
|
||||
cc.setUnitBusy(28, 0);
|
||||
cc.CCI16F = 1;
|
||||
cc.signalInterrupt();
|
||||
};
|
||||
break;
|
||||
default:
|
||||
return function() {};
|
||||
|
||||
@@ -45,7 +45,7 @@ function B5500IOUnit(ioUnitID, cc) {
|
||||
this.ioUnitID = ioUnitID; // I/O Unit ID ("1", "2", "3", or "4")
|
||||
this.cc = cc; // Reference back to Central Control module
|
||||
|
||||
this.scheduler = null; // Reference to current setTimeout id
|
||||
this.forkHandle = null; // Reference to current setTimeout id
|
||||
this.accessor = { // Memory access control block
|
||||
requestorID: ioUnitID, // Memory requestor ID
|
||||
addr: 0, // Memory address
|
||||
@@ -55,8 +55,6 @@ function B5500IOUnit(ioUnitID, cc) {
|
||||
MAED: 0 // Truthy if memory address/inhibit error
|
||||
};
|
||||
|
||||
this.schedule.that = this; // Establish context for when called from setTimeout()
|
||||
|
||||
// Establish a buffer for the peripheral modules to use during their I/O.
|
||||
// The size is sufficient for 63 disk sectors, rounded up to the next 8KB.
|
||||
this.bufferArea = new ArrayBuffer(16384);
|
||||
@@ -78,7 +76,7 @@ B5500IOUnit.BICtoANSI = [ // Index by 6-bit BIC to get 8-bit ANSI
|
||||
0x7C,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50, // 20-27, @40-47
|
||||
0x51,0x52,0x24,0x2A,0x2D,0x29,0x3B,0x7B, // 28-2F, @50-57
|
||||
0x20,0x2F,0x53,0x54,0x55,0x56,0x57,0x58, // 30-37, @60-67
|
||||
0x59,0x5A,0x2C,0x25,0x21,0x3D,0x5D,0x23]; // 38-3F, @70-77
|
||||
0x59,0x5A,0x2C,0x25,0x21,0x3D,0x5D,0x22]; // 38-3F, @70-77
|
||||
|
||||
B5500IOUnit.BICtoBCLANSI = [ // Index by 6-bit BIC to get 8-bit BCL-as-ANSI code
|
||||
0x23,0x31,0x32,0x33,0x34,0x35,0x36,0x37, // 00-07, @00-07
|
||||
@@ -88,7 +86,7 @@ B5500IOUnit.BICtoBCLANSI = [ // Index by 6-bit BIC to get 8-bit BCL-a
|
||||
0x24,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50, // 20-27, @40-47
|
||||
0x51,0x52,0x2A,0x2D,0x7C,0x29,0x3B,0x7B, // 28-2F, @50-57
|
||||
0x2B,0x41,0x42,0x43,0x44,0x45,0x46,0x47, // 30-37, @60-67
|
||||
0x48,0x49,0x5B,0x26,0x2E,0x28,0x3C,0X7E]; // 38-3F, @70-77
|
||||
0x48,0x49,0x5B,0x26,0x2E,0x28,0x3C,0x7E]; // 38-3F, @70-77
|
||||
|
||||
B5500IOUnit.ANSItoBIC = [ // Index by 8-bit ANSI to get 6-bit BIC (upcased, invalid=>"?")
|
||||
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 00-0F
|
||||
@@ -175,6 +173,10 @@ B5500IOUnit.prototype.clear = function() {
|
||||
this.totalCycles = 0; // Total cycles executed on this I/O Unit
|
||||
this.ioUnitTime = 0; // Total I/O Unit running time, based on cycles executed
|
||||
this.ioUnitSlack = 0; // Total I/O Unit throttling delay, milliseconds
|
||||
|
||||
if (this.forkHandle) {
|
||||
clearTimeout(this.forkHandle);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
@@ -538,7 +540,8 @@ B5500IOUnit.prototype.finish = function () {
|
||||
break;
|
||||
}
|
||||
|
||||
this.busyUnit = 0; // zero so CC won't think unit is busy
|
||||
this.busy = 0; // zero so CC won't think I/O unit is busy
|
||||
this.busyUnit = 0;
|
||||
this.cc.signalInterrupt();
|
||||
};
|
||||
|
||||
@@ -554,7 +557,8 @@ B5500IOUnit.prototype.makeFinish = function(f) {
|
||||
B5500IOUnit.prototype.finishGeneric = function(errorMask, length) {
|
||||
/* Handles a generic I/O finish when no word-count update or input data
|
||||
transfer is needed. Can also be used to apply common error mask posting
|
||||
at the end of specialized finish handlers */
|
||||
at the end of specialized finish handlers. Note that this turns off the
|
||||
busyUnit mask bit in CC */
|
||||
|
||||
if (errorMask & 0x01) {this.D32F = 1}
|
||||
if (errorMask & 0x02) {this.D31F = 1}
|
||||
@@ -563,26 +567,198 @@ B5500IOUnit.prototype.finishGeneric = function(errorMask, length) {
|
||||
if (errorMask & 0x10) {this.D28F = 1}
|
||||
if (errorMask & 0x20) {this.D27F = 1}
|
||||
if (errorMask & 0x40) {this.D26F = 1}
|
||||
this.cc.setUnitBusy(this.busyUnit, 0);
|
||||
this.finish();
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.finishSPORead = function(errorMask, length) {
|
||||
/* Handles I/O finish for a SPO keyboard input operation */
|
||||
var count;
|
||||
|
||||
count = this.storeBufferWithGM(length, 0, 1, 0x7FFF);
|
||||
this.storeBufferWithGM(length, 0, 1, 0x7FFF);
|
||||
this.finishGeneric(errorMask, length);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.initiate = function() {
|
||||
/* Initiates an I/O operation on this I/O Unit */
|
||||
B5500IOUnit.prototype.finishDiskRead = function(errorMask, length) {
|
||||
/* Handles I/O finish for a DFCU data read operation */
|
||||
var segWords = Math.floor((length+7)/8);
|
||||
var memWords = (this.D23F ? this.DwordCount : segWords);
|
||||
|
||||
if (segWords < memWords) {
|
||||
memWords = segWords;
|
||||
}
|
||||
this.storeBuffer(length, 0, this.D21F, memWords);
|
||||
this.finishGeneric(errorMask, length);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.initiateDiskIO = function(u) {
|
||||
/* Initiates an I/O to the Disk File Control Unit. The disk address is fetched from
|
||||
the first word of the memory area and converted to binary for the DFCU module. Read
|
||||
check and interrogate operations are determined from their respective IOD bits. If
|
||||
it's a read data operation, we request the specified number of segments from the disk
|
||||
and will sort out word count issues in finishDiskRead(). If it's a write data operation,
|
||||
we truncate or pad the data from memory as appropriate and request a write of the
|
||||
specified number of segments */
|
||||
var c; // address char
|
||||
var memWords; // number of memory words to transfer
|
||||
var p = 1; // address digit power
|
||||
var w; // current memory word value
|
||||
var x; // temp variable
|
||||
|
||||
var segAddr = 0; // disk segment address
|
||||
var segs = this.LP; // I/O size in segments
|
||||
var segWords = segs*30; // I/O size in words
|
||||
var segChars = segWords*8; // I/O size in characters
|
||||
|
||||
if (this.fetch(this.Daddress)) { // fetch the segment address from first buffer word
|
||||
this.finish();
|
||||
} else {
|
||||
this.Daddress++; // bump memory address past the seg address word
|
||||
w = this.W; // convert address word to binary
|
||||
for (x=0; x<7; x++) {
|
||||
c = w % 0x40; // get low-order six bits of word
|
||||
segAddr += (c % 0x10)*p; // isolate the numeric portion and accumulate
|
||||
w = (w-c)/0x40; // shift word right six bits
|
||||
p *= 10; // bump power for next digit
|
||||
}
|
||||
|
||||
if (this.D18F) { // mem inhibit => read check operation
|
||||
u.readCheck(this.makeFinish(this.finishGeneric), segChars, segAddr);
|
||||
} else if (this.D23F && this.DwordCount == 0) {
|
||||
if (this.D24F) { // read interrogate operation
|
||||
u.readInterrogate(this.makeFinish(this.finishGeneric), segAddr);
|
||||
} else { // write interrogate operation
|
||||
u.writeInterrogate(this.makeFinish(this.finishGeneric), segAddr);
|
||||
}
|
||||
} else if (this.D24F) { // it's a read data operation
|
||||
u.read(this.makeFinish(this.finishDiskRead), this.buffer, segChars, this.D21F, segAddr);
|
||||
} else { // it's a write data operation
|
||||
memWords = (this.D23F ? this.DwordCount : segWords);
|
||||
if (segWords <= memWords) { // transfer size is limited by number of segs
|
||||
this.fetchBuffer(this.D21F, segWords);
|
||||
} else { // transfer size is limited by word count
|
||||
x = this.fetchBuffer(this.D21F, memWords);
|
||||
c = (this.D21F ? 0x00 : 0x2B); // pad "0" if binary, " " if alpha (as BCL)
|
||||
while (x < segChars) { // pad remainder of buffer up to seg count
|
||||
this.buffer[x++] = c;
|
||||
}
|
||||
}
|
||||
u.write(this.makeFinish(this.finishGeneric), this.buffer, segChars, this.D21F, segAddr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.forkIO = function forkIO() {
|
||||
/* Asychrounously nitiates an I/O operation on this I/O Unit for a peripheral device */
|
||||
var addr; // memory address
|
||||
var chars; // I/O memory transfer length
|
||||
var index; // unit index
|
||||
var u; // peripheral unit object
|
||||
var x;
|
||||
var x; // temp number variable
|
||||
|
||||
this.forkHandle = null; // clear the setTimeout() handle
|
||||
|
||||
x = this.D; // explode the D-register into its fields
|
||||
this.Dunit = this.cc.fieldIsolate(x, 3, 5);
|
||||
this.DwordCount = this.cc.fieldIsolate(x, 8, 10);
|
||||
x = x % 0x40000000; // isolate low-order 30 bits
|
||||
this.D18F = (x >>> 29) & 1; // memory inhibit
|
||||
this.D21F = (x >>> 26) & 1; // mode
|
||||
this.D22F = (x >>> 25) & 1; // direction (for tapes)
|
||||
this.D23F = (x >>> 24) & 1; // use word counter
|
||||
this.D24F = (x >>> 23) & 1; // write/read
|
||||
this.LP = (x >>> 15) & 0x3F; // save control bits for disk, drum, and printer
|
||||
this.Daddress = x % 0x8000;
|
||||
|
||||
this.busyUnit = index = B5500CentralControl.unitIndex[this.D24F & 1][this.Dunit & 0x1F];
|
||||
if (this.cc.testUnitBusy(index)) {
|
||||
this.D32F = 1; // set unit busy error
|
||||
this.finish();
|
||||
} else if (!this.cc.testUnitReady(index)) {
|
||||
this.D30F = 1; // set unit not-ready error
|
||||
this.finish();
|
||||
} else {
|
||||
this.cc.setUnitBusy(index, 1);
|
||||
u = this.cc.unit[index];
|
||||
switch(this.Dunit) {
|
||||
// disk designates
|
||||
case 6:
|
||||
case 12:
|
||||
this.initiateDiskIO(u);
|
||||
break;
|
||||
|
||||
// printer designates
|
||||
case 22:
|
||||
case 26:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
break;
|
||||
|
||||
// datacom designate
|
||||
case 16:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
break;
|
||||
|
||||
// card #1 reader/punch
|
||||
case 10:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
break;
|
||||
|
||||
// card #2 reader
|
||||
case 14:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
break;
|
||||
|
||||
// SPO designate
|
||||
case 30:
|
||||
if (this.D24F) {
|
||||
u.read(this.makeFinish(this.finishSPORead), this.buffer, 0x7FFF, 0, 0);
|
||||
} else {
|
||||
chars = this.fetchBufferWithGM(1, 0x7FFF);
|
||||
u.write(this.makeFinish(this.finishGeneric), this.buffer, chars, 0, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
// magnetic tape designates
|
||||
case 1: case 3: case 5: case 7: case 9: case 11: case 13: case 15:
|
||||
case 17: case 19: case 21: case 23: case 25: case 27: case 29: case 31:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
break;
|
||||
|
||||
// drum designates
|
||||
case 4:
|
||||
case 8:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
break;
|
||||
|
||||
// paper tape designates
|
||||
case 18:
|
||||
case 20:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
break;
|
||||
|
||||
// illegal designates
|
||||
default:
|
||||
this.D30F = 1; // report invalid unit as not ready
|
||||
this.finish();
|
||||
break;
|
||||
} // switch this.Dunit
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.initiate = function() {
|
||||
/* Initiates an I/O operation on this I/O Unit. When P1 executes an IIO instruction,
|
||||
it calls the CentralControl.initiateIO() function, which selects an idle I/O Unit and
|
||||
calls this function for that unit. Thus, at entry we are still running on top of the
|
||||
processor. This routine merely fetches the IOD from memory and then schedules forkIO()
|
||||
to run asynchronously. Then we exit back through CC and into P1, thus allowing the
|
||||
actual I/O operation to run asynchronously from the processor. Of course, in a browser
|
||||
environment, all of the Javascript action occurs on one thread, so this allows us to
|
||||
multiplex what are supposed to be asynchronous operations on that thread */
|
||||
var that = this; // Establish object context for the callback
|
||||
|
||||
this.clearD();
|
||||
this.AOFF = 0;
|
||||
@@ -592,129 +768,13 @@ B5500IOUnit.prototype.initiate = function() {
|
||||
this.finish();
|
||||
} else {
|
||||
this.EXNF = 1;
|
||||
this.Daddress = addr = this.W % 0x8000;
|
||||
if (this.fetch(addr)) { // fetch the IOD from that address
|
||||
this.Daddress = this.W % 0x8000;
|
||||
if (this.fetch(this.Daddress)) {// fetch the IOD from that address
|
||||
this.finish();
|
||||
} else {
|
||||
this.D31F = 0; // reset the IOD-fetch error condition
|
||||
this.D = x = this.W; // explode the D-register into its fields
|
||||
this.Dunit = this.cc.fieldIsolate(x, 3, 5);
|
||||
this.DwordCount = this.cc.fieldIsolate(x, 8, 10);
|
||||
x = x % 0x40000000; // isolate low-order 30 bits
|
||||
this.D18F = (x >>> 29) & 1; // memory inhibit
|
||||
this.D21F = (x >>> 26) & 1; // mode
|
||||
this.D22F = (x >>> 25) & 1; // direction (for tapes)
|
||||
this.D23F = (x >>> 24) & 1; // use word counter
|
||||
this.D24F = (x >>> 23) & 1; // write/read
|
||||
this.LP = (x >>> 15) & 0x3F;// save control bits for drum and printer
|
||||
this.Daddress = x % 0x8000;
|
||||
|
||||
this.busyUnit = index = B5500CentralControl.unitIndex[this.D24F & 1][this.Dunit & 0x1F];
|
||||
if (this.cc.testUnitBusy(this.ioUnitID, this.busyUnit)) {
|
||||
this.D32F = 1; // set unit busy error
|
||||
this.finish();
|
||||
} else if (!this.cc.testUnitReady(this.D24F, this.Dunit)) {
|
||||
this.D30F = 1; // set unit not-ready error
|
||||
this.finish();
|
||||
} else {
|
||||
u = this.cc.unit[index];
|
||||
switch(this.Dunit) {
|
||||
// disk designates
|
||||
case 6:
|
||||
case 12:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
break;
|
||||
|
||||
// printer designates
|
||||
case 22:
|
||||
case 26:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
break;
|
||||
|
||||
// datacom designate
|
||||
case 16:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
break;
|
||||
|
||||
// card #1 reader/punch
|
||||
case 10:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
break;
|
||||
|
||||
// card #2 reader
|
||||
case 14:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
break;
|
||||
|
||||
// SPO designate
|
||||
case 30:
|
||||
if (this.D24F) {
|
||||
u.read(this.makeFinish(this.finishSPORead), this.buffer, 0x7FFF, 0, 0);
|
||||
} else {
|
||||
chars = this.fetchBufferWithGM(1, 0x7FFF);
|
||||
u.write(this.makeFinish(this.finishGeneric), this.buffer, chars, 0, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
// magnetic tape designates
|
||||
case 1: case 3: case 5: case 7: case 9: case 11: case 13: case 15:
|
||||
case 17: case 19: case 21: case 23: case 25: case 27: case 29: case 31:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
break;
|
||||
|
||||
// drum designates
|
||||
case 4:
|
||||
case 8:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
break;
|
||||
|
||||
// paper tape designates
|
||||
case 18:
|
||||
case 20:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
break;
|
||||
|
||||
// illegal designates
|
||||
default:
|
||||
this.D30F = 1; // report invalid unit as not ready
|
||||
this.finish();
|
||||
break;
|
||||
} // switch this.Dunit
|
||||
}
|
||||
this.D = this.W;
|
||||
this.forkHandle = setTimeout(function() {that.forkIO()}, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.run = function() {
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.schedule = function schedule() {
|
||||
/* Schedules the I/O Unit run time and attempts to throttle performance
|
||||
to approximate that of a real B5500. Well, at least we hope this will run
|
||||
fast enough that the performance will need to be throttled. It establishes
|
||||
a timeslice in terms of a number of I/O Unit "cycles" of 1 microsecond
|
||||
each and calls run() to execute at most that number of cycles. run()
|
||||
counts up cycles until it reaches this limit or some terminating event
|
||||
(such as a halt), then exits back here. If the I/O Unit remains active,
|
||||
this routine will reschedule itself for an appropriate later time, thereby
|
||||
throttling the performance and allowing other modules a chance at the
|
||||
Javascript execution thread. */
|
||||
var delayTime;
|
||||
var that = schedule.that;
|
||||
|
||||
that.scheduler = null;
|
||||
that.cycleLimit = B5500IOUnit.timeSlice;
|
||||
that.cycleCount = 0;
|
||||
|
||||
that.run();
|
||||
|
||||
that.totalCycles += that.cycleCount
|
||||
that.ioUnitTime += that.cycleCount;
|
||||
if (that.busy) {
|
||||
delayTime = that.ioUnitTime/1000 - new Date().getTime();
|
||||
that.ioUnitSlack += delayTime;
|
||||
that.scheduler = setTimeout(that.schedule, (delayTime < 0 ? 1 : delayTime));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ var B5500SystemConfiguration = {
|
||||
|
||||
units: {
|
||||
SPO: true, // SPO keyboard/printer
|
||||
DKA: false, // Disk File Control A
|
||||
DKA: true, // Disk File Control A
|
||||
DKB: false, // Disk File Control B
|
||||
CRA: false, // Card Reader A
|
||||
CRB: false, // Card Reader B
|
||||
|
||||
@@ -44,13 +44,6 @@
|
||||
*
|
||||
* To use, select the .bcd file using the file selection control on the page.
|
||||
* The script writes a log of activity to the web page.
|
||||
*
|
||||
* This version outputs the converted data by opening a browser window for
|
||||
* each file and inserting the converted text into a <textarea> element in
|
||||
* that window. From there you can copy the text and paste into another
|
||||
* program that can save the data to a local filesystem. This approach is
|
||||
* being used until we can figure out a better way to get data out of a
|
||||
* browser environment and into a local filesystem. Ugh.
|
||||
************************************************************************
|
||||
* 2012-12-29 P.Kimpel
|
||||
* Original version, from B5500LibMaintExtract.html.
|
||||
@@ -1824,8 +1817,8 @@ window.onload = function() {
|
||||
/**************************************/
|
||||
function openDatabase(name, version) {
|
||||
/* Attempts to open the disk subsystem database for the specified "name"
|
||||
and "version". Returns the IDB database object if successful, or null if
|
||||
unsuccessful */
|
||||
and "version". Stores the IDB database object in "disk" if successful, or
|
||||
stores null if unsuccessful */
|
||||
var db = null;
|
||||
var req;
|
||||
|
||||
@@ -1848,11 +1841,11 @@ window.onload = function() {
|
||||
$$("ColdstartBtn").disabled = false;
|
||||
$$("FileSelector").disabled = false;
|
||||
|
||||
dumpDisk(); // <<<<<<<<<<<<<< DEBUG <<<<<<<<<<<<<<<<<
|
||||
// dumpDisk(); // <<<<<<<<<<<<<< DEBUG <<<<<<<<<<<<<<<<<
|
||||
|
||||
disk.transaction("CONFIG").objectStore("CONFIG").get(0).onsuccess = function(ev) {
|
||||
config = ev.target.result;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
<meta http-equiv="Content-Style-Type" content="text/css">
|
||||
<link id=defaultStyleSheet rel=stylesheet type="text/css" href="B5500Console.css">
|
||||
|
||||
<script src="B5500SPOUnit.js"></script>
|
||||
<script src="/B5500/B5500DummyUnit.js"></script>
|
||||
<script src="/B5500/B5500SPOUnit.js"></script>
|
||||
<script src="/B5500/B5500DiskUnit.js"></script>
|
||||
|
||||
<script src="../emulator/B5500SystemConfiguration.js"></script>
|
||||
<script src="../emulator/B5500CentralControl.js"></script>
|
||||
<script src="../emulator/B5500Processor.js"></script>
|
||||
<script src="../emulator/B5500IOUnit.js"></script>
|
||||
<script src="/B5500EMU/B5500SystemConfiguration.js"></script>
|
||||
<script src="/B5500EMU/B5500CentralControl.js"></script>
|
||||
<script src="/B5500EMU/B5500Processor.js"></script>
|
||||
<script src="/B5500EMU/B5500IOUnit.js"></script>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
@@ -33,7 +35,7 @@ window.onload = function() {
|
||||
aNormal.className = "yellowButton";
|
||||
aControl.className = "yellowButton yellowLit";
|
||||
delay = Math.log(delay+1)*250;
|
||||
} else {
|
||||
} else {
|
||||
paState = 1;
|
||||
aNormal.className = "yellowButton yellowLit";
|
||||
aControl.className = "yellowButton";
|
||||
@@ -96,13 +98,13 @@ window.onload = function() {
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/***** window.onload() outer block *****/
|
||||
|
||||
|
||||
document.getElementById("PowerOnBtn").onclick = function() {
|
||||
return PowerOnBtn_Click();
|
||||
};
|
||||
|
||||
|
||||
document.getElementById("PowerOffBtn").onclick = function() {
|
||||
return PowerOffBtn_Click();
|
||||
};
|
||||
@@ -124,7 +126,7 @@ window.onload = function() {
|
||||
<button id=AControlBtn class=yellowButton>A CONTROL</button>
|
||||
<button id=BNormalBtn class=yellowButton>B NORMAL</button>
|
||||
<button id=BControlBtn class=yellowButton>B CONTROL</button>
|
||||
|
||||
|
||||
<button id=PowerOnBtn class=whiteButton>POWER<br>ON</button>
|
||||
<button id=PowerOffBtn class=blackButton>POWER OFF</button>
|
||||
|
||||
|
||||
470
webUI/B5500DiskUnit.js
Normal file
470
webUI/B5500DiskUnit.js
Normal file
@@ -0,0 +1,470 @@
|
||||
/***********************************************************************
|
||||
* retro-b5500/emulator B5500DiskUnit.js
|
||||
************************************************************************
|
||||
* Copyright (c) 2013, Nigel Williams and Paul Kimpel.
|
||||
* Licensed under the MIT License, see
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
************************************************************************
|
||||
* B5500 Disk File Control Unit module.
|
||||
*
|
||||
* Defines a peripheral unit type for the Disk File Control Unit used with
|
||||
* Burroughs Model I and Model IB Head-per-Track disk units. The DFCU is the
|
||||
* addressable unit to the B5500. This module manages the Electronic Units (EU)
|
||||
* and Storage Units (SU) that make up the physical disk storage facility.
|
||||
*
|
||||
* Physical storage in this implementation is provided by a W3C IndexedDB
|
||||
* database local to the browser in which the emulator is running. This database
|
||||
* must have been previously initialized; see tools/B5500ColdLoader.html.
|
||||
*
|
||||
* The database consists of a CONFIG object store and some number of EUn object
|
||||
* stores, where n is in 0..9. The CONFIG store contains an "eus" member that
|
||||
* specifies the total number of EU objects, plus an "EUn" member that specicies
|
||||
* the size of that EU in 240-character segments. There may be gaps in the EU
|
||||
* numbering.
|
||||
*
|
||||
* Within an EU, segments are represented in the database as 240-byte Uint8Array
|
||||
* objects, each with a database key corresponding to its numeric segment address.
|
||||
* The segments are an EU are not pre-allocated, but are created as they are
|
||||
* written to by IDB put() methods. When reading, any unallocated segments are
|
||||
* returned with their bytes set to binary zero, which will be translated to
|
||||
* BIC "?" by the IOU for both binary and alpha modes.
|
||||
*
|
||||
* At present, disk write lockout is not supported. When there are two DFCUs in
|
||||
* the system, the presence of a Disk File Exchange is assumed, allowing either
|
||||
* DFCU to access any EU. Thus this implementation is currently limited to a
|
||||
* maximum of 10 EUs (480 million characters).
|
||||
*
|
||||
* Note that all disk I/O is done in units of 240-character segments. The
|
||||
* interface with the I/O Unit uses lengths in terms of characters, however.
|
||||
* All lengths SHOULD be multiples of 240 characters; any other values will be
|
||||
* rounded up to the next multiple of 240. The I/O Unit is responsible for any
|
||||
* padding or truncation to account for differences between the segment count
|
||||
* and word count in the IOD. This implementation ignores binary vs. alpha mode
|
||||
* and assumes the IOU does any necessary translation.
|
||||
*
|
||||
* The starting disk segment address for an I/O is passed in the "control"
|
||||
* parameter to each of the I/O methods. This is an an alphanumeric value in
|
||||
* the B5500 memory and I/O Unit. The I/O unit translates this value to binary
|
||||
* for the "control" parameter. The low-order six decimal digits of the value
|
||||
* comprise the segment address within the EU. The seventh decimal digit is the
|
||||
* EU number. Any other portion of the value is ignored.
|
||||
*
|
||||
* The DFCU's "read check" operation is asynchronous with respect to the IOU, and
|
||||
* the I/O operation itself completes almost immediately. Because of this, any
|
||||
* error reporting for the read check is deferred until the next I/O operation
|
||||
* (typically an interrogate) against the unit. Therefore, the error mask is
|
||||
* cleared at the end of each disk I/O operation (except for read check) instead
|
||||
* of at the beginning, and new errors are OR-ed with any errors persisting from
|
||||
* the prior operation.
|
||||
*
|
||||
* This module attempts to simulate actual disk activity times by delaying the
|
||||
* finish() call by an amount of time computed from the numbers of sectors
|
||||
* requested and the Model I SU's average 96KC transfer rate, plus a random
|
||||
* distribution across its 40ms max rotational latency time.
|
||||
************************************************************************
|
||||
* 2013-01-19 P.Kimpel
|
||||
* Original version, cloned from B5500DummyUnit.js.
|
||||
***********************************************************************/
|
||||
"use strict";
|
||||
|
||||
/**************************************/
|
||||
function B5500DiskUnit(mnemonic, index, designate, statusChange, signal) {
|
||||
/* Constructor for the DiskUnit object */
|
||||
|
||||
this.mnemonic = mnemonic; // Unit mnemonic
|
||||
this.index = index; // Ready-mask bit number
|
||||
this.designate = designate; // IOD unit designate number
|
||||
this.statusChange = statusChange; // external function to call for ready-status change
|
||||
this.signal = signal; // external function to call for special signals (e.g,. SPO input request)
|
||||
|
||||
this.clear();
|
||||
}
|
||||
|
||||
B5500DiskUnit.prototype.dbName = "B5500DiskUnit"; // IndexedDB database name
|
||||
B5500DiskUnit.prototype.dbVersion = 1; // current IDB database version
|
||||
B5500DiskUnit.prototype.configName = "CONFIG"; // database configuration store name
|
||||
B5500DiskUnit.prototype.euPrefix = "EU"; // prefix for EU object store names
|
||||
B5500DiskUnit.prototype.charXferRate = 96000; // avg. transfer rate, characters/sec (Model I SU)
|
||||
B5500DiskUnit.prototype.maxLatency = 0.040; // max rotational latency, sec (Model I SU)
|
||||
|
||||
/**************************************/
|
||||
B5500DiskUnit.prototype.clear = function() {
|
||||
/* Initializes (and if necessary, creates) the processor state */
|
||||
|
||||
this.ready = false; // ready status
|
||||
this.busy = false; // busy status
|
||||
this.activeIOUnit = 0; // I/O unit currently using this device
|
||||
|
||||
this.errorMask = 0; // error mask for finish()
|
||||
this.finish = null; // external function to call for I/O completion
|
||||
this.startStamp = null; // I/O starting timestamp
|
||||
|
||||
this.config = null; // copy of CONFIG store contents
|
||||
this.disk = null; // the IDB database object
|
||||
|
||||
this.openDatabase(); // attempt to open the IDB database
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DiskUnit.genericDBError = function(ev) {
|
||||
/* Formats a generic alert when otherwise-unhandled database errors occur */
|
||||
var disk = ev.currentTarget.result;
|
||||
|
||||
this.errorMask |= 0x20; // set a generic disk-parity error
|
||||
alert("Database for \"" + this.mnemonic + "\" error: " + ev.target.result.error);
|
||||
this.finish(this.errorMask, 0);
|
||||
this.errorMask = 0;
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DiskUnit.prototype.copySegment = function(seg, buffer, offset) {
|
||||
/* Copies the bytes from a single segment Uint8Array object to "buffer" starting
|
||||
at "offset" for 240 bytes. If "seg" is undefined, copies zero bytes instead */
|
||||
var x;
|
||||
|
||||
if (seg) {
|
||||
for (x=0; x<240; x++) {
|
||||
buffer[offset+x] = seg[x];
|
||||
}
|
||||
} else {
|
||||
for (x=offset+239; x>=0; x--) {
|
||||
buffer[x] = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DiskUnit.prototype.openDatabase = function() {
|
||||
/* Attempts to open the disk subsystem database specified by this.dbName and
|
||||
this.dbVersion. If successful, sets this.disk to the IDB object and sets the
|
||||
DFCU to ready status */
|
||||
var req;
|
||||
var db = null;
|
||||
var that = this;
|
||||
|
||||
that.statusChange(0); // initially force DFCU status to not ready
|
||||
req = window.indexedDB.open(that.dbName, that.dbVersion);
|
||||
|
||||
req.onerror = function(ev) {
|
||||
alert("Cannot open " + that.mnemonic + " database: " + ev.target.error);
|
||||
};
|
||||
|
||||
req.onblocked = function(ev) {
|
||||
alert("Database.open is blocked -- cannot continue");
|
||||
};
|
||||
|
||||
req.onupgradeneeded = function(ev) {
|
||||
alert("Database requires version upgrade -- cannot continue");
|
||||
};
|
||||
|
||||
req.onsuccess = function(ev) {
|
||||
that.disk = ev.target.result; // save the object reference globally for later use
|
||||
that.disk.onerror = function(ev) {
|
||||
alert("Database for \"" + this.mnemonic + "\" open error: " + ev.target.result.error);
|
||||
};
|
||||
|
||||
that.disk.transaction("CONFIG").objectStore("CONFIG").get(0).onsuccess = function(ev) {
|
||||
that.config = ev.target.result;
|
||||
that.statusChange(1); // report the DFCU as ready to Central Control
|
||||
// Set up the generic error handler
|
||||
that.disk.onerror = function(ev) {
|
||||
that.genericIDBError(ev);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DiskUnit.prototype.read = function(finish, buffer, length, mode, control) {
|
||||
/* Initiates a read operation on the unit. "length" is in characters; segment address
|
||||
is in "control". "mode" is ignored (any translation would have been done by IOU) */
|
||||
var bx = 0; // current buffer offset
|
||||
var euSize; // max seg size for EU
|
||||
var finishTime; // predicted time of I/O completion, ms
|
||||
var range; // key range for multi-segment read
|
||||
var req; // IDB request object
|
||||
var that = this; // local object context
|
||||
var txn; // IDB transaction object
|
||||
|
||||
this.finish = finish; // for global error handler
|
||||
var segs = Math.floor((length+239)/240);
|
||||
var segAddr = control % 1000000; // starting seg address
|
||||
var euNumber = (control % 10000000 - segAddr)/1000000;
|
||||
var euName = this.euPrefix + euNumber;
|
||||
var endAddr = segAddr+segs-1; // ending seg address
|
||||
|
||||
euSize = this.config[euName];
|
||||
if (!euSize) { // EU does not exist
|
||||
finish(this.errorMask | 0x20, 0); // set D27F for EU not ready
|
||||
this.errorMask = 0;
|
||||
} else if (segAddr < 0) {
|
||||
finish(this.errorMask | 0x20, 0); // set D27F for invalid starting seg address
|
||||
this.errorMask = 0;
|
||||
} else {
|
||||
if (endAddr >= euSize) { // if read is past end of disk
|
||||
this.errorMask |= 0x20; // set D27F for invalid seg address
|
||||
segs = euSize-segAddr; // compute number of segs possible to read
|
||||
length = segs*240; // recompute length and ending seg address
|
||||
endAddr = euSize-1;
|
||||
}
|
||||
finishTime = new Date().getTime() +
|
||||
(Math.random()*this.maxLatency + segs*240/this.charXferRate)*1000;
|
||||
|
||||
// No length specified, so just finish the I/O
|
||||
if (segs < 1) {
|
||||
finish(this.errorMask, 0);
|
||||
this.errorMask = 0;
|
||||
|
||||
// A single-segment read
|
||||
} else if (segs < 2) {
|
||||
req = this.disk.transaction(euName).objectStore(euName).get(segAddr);
|
||||
req.onsuccess = function(ev) {
|
||||
that.copySegment(ev.target.result, buffer, 0);
|
||||
setTimeout(function() {
|
||||
finish(that.errorMask, length);
|
||||
that.errorMask = 0;
|
||||
}, finishTime - new Date().getTime());
|
||||
};
|
||||
|
||||
// A multi-segment read
|
||||
} else {
|
||||
range = window.IDBKeyRange.bound(segAddr, endAddr);
|
||||
txn = this.disk.transacation(euName);
|
||||
|
||||
req = txn.objectStore(euName).openCursor(range);
|
||||
req.onsuccess = function(ev) {
|
||||
var cursor = ev.target.result;
|
||||
|
||||
if (cursor) { // found a segment at some address in range
|
||||
// fill buffer with zeroes for any unallocated segments
|
||||
while (cursor.key > segAddr) {
|
||||
that.copySegment(null, buffer, bx);
|
||||
bx += 240;
|
||||
segAddr++;
|
||||
}
|
||||
// copy the segment data to the buffer and request next seg
|
||||
that.copySegment(cursor.value, buffer, bx);
|
||||
bx += 240;
|
||||
segAddr++;
|
||||
cursor.continue();
|
||||
} else { // at end of range
|
||||
// fill buffer with zeroes for any remaining segments in range
|
||||
while (endAddr > segAddr) {
|
||||
that.copySegment(null, buffer, bx);
|
||||
bx += 240;
|
||||
segAddr++;
|
||||
}
|
||||
setTimeout(function() {
|
||||
finish(that.errorMask, length);
|
||||
that.errorMask = 0;
|
||||
}, finishTime - new Date().getTime());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DiskUnit.prototype.space = function(finish, length, control) {
|
||||
/* Initiates a space operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DiskUnit.prototype.write = function(finish, buffer, length, mode, control) {
|
||||
/* Initiates a write operation on the unit. "length" is in characters; segment address
|
||||
is in "control". "mode" is ignored (any translation will done by the IOU) */
|
||||
var bx = 0; // current buffer offset
|
||||
var eu; // IDB object store for EU
|
||||
var euSize; // max seg size for EU
|
||||
var finishTime; // predicted time of I/O completion, ms
|
||||
var req; // IDB request object
|
||||
var that = this; // local object context
|
||||
var txn; // IDB transaction object
|
||||
|
||||
this.finish = finish; // for global error handler
|
||||
var segs = Math.floor((length+239)/240);
|
||||
var segAddr = control % 1000000; // starting seg address
|
||||
var euNumber = (control % 10000000 - segAddr)/1000000;
|
||||
var euName = this.euPrefix + euNumber;
|
||||
var endAddr = segAddr+segs-1; // ending seg address
|
||||
|
||||
euSize = this.config[euName];
|
||||
if (!euSize) { // EU does not exist
|
||||
finish(this.errorMask | 0x20, 0); // set D27F for EU not ready
|
||||
this.errorMask = 0;
|
||||
} else if (segAddr < 0) {
|
||||
finish(this.errorMask | 0x20, 0); // set D27F for invalid starting seg address
|
||||
this.errorMask = 0;
|
||||
} else {
|
||||
if (endAddr >= euSize) { // if read is past end of disk
|
||||
this.errorMask |= 0x20; // set D27F for invalid seg address
|
||||
segs = euSize-segAddr; // compute number of segs possible to read
|
||||
length = segs*240; // recompute length and ending seg address
|
||||
endAddr = euSize-1;
|
||||
}
|
||||
finishTime = new Date().getTime() +
|
||||
(Math.random()*this.maxLatency + segs*240/this.charXferRate)*1000;
|
||||
|
||||
// No length specified, so just finish the I/O
|
||||
if (segs < 1) {
|
||||
finish(this.errorMask, 0);
|
||||
this.errorMask = 0;
|
||||
|
||||
// Do the write
|
||||
} else {
|
||||
txn = this.disk.transaction(euName, "readwrite")
|
||||
txn.oncomplete = function(ev) {
|
||||
setTimeout(function() {
|
||||
finish(that.errorMask, length);
|
||||
that.errorMask = 0;
|
||||
}, finishTime - new Date().getTime());
|
||||
};
|
||||
eu = txn.objectStore(euName);
|
||||
for (; segAddr<=endAddr; segAddr++) {
|
||||
eu.put(buffer.subarray(bx, bx+240), segAddr);
|
||||
bx += 240;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DiskUnit.prototype.erase = function(finish, length) {
|
||||
/* Initiates an erase operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DiskUnit.prototype.rewind = function(finish) {
|
||||
/* Initiates a rewind operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DiskUnit.prototype.readCheck = function(finish, length, control) {
|
||||
/* Initiates a read check operation on the unit. "length" is in characters;
|
||||
segment address is in "control". "mode" is ignored. This is essentially a
|
||||
read without any data transfer to memory. Note that the errorMask is NOT
|
||||
zeroed at the end of the I/O -- it will be reported with the next I/O */
|
||||
var euSize; // max seg size for EU
|
||||
var finishTime; // predicted time of I/O completion, ms
|
||||
var range; // key range for multi-segment read
|
||||
var req; // IDB request object
|
||||
var that = this; // local object context
|
||||
var txn; // IDB transaction object
|
||||
|
||||
this.finish = finish; // for global error handler
|
||||
var segs = Math.floor((length+239)/240);
|
||||
var segAddr = control % 1000000; // starting seg address
|
||||
var euNumber = (control % 10000000 - segAddr)/1000000;
|
||||
var euName = this.euPrefix + euNumber;
|
||||
var endAddr = segAddr+segs-1; // ending seg address
|
||||
|
||||
this.errorMask = 0; // clear any prior error mask
|
||||
euSize = this.config[euName];
|
||||
if (!euSize) { // EU does not exist
|
||||
finish(this.errorMask | 0x20, 0); // set D27F for EU not ready
|
||||
// do NOT clear the error mask
|
||||
} else if (segAddr < 0) {
|
||||
finish(this.errorMask | 0x20, 0); // set D27F for invalid starting seg address
|
||||
// do NOT clear the error mask
|
||||
} else {
|
||||
if (endAddr >= euSize) { // if read is past end of disk
|
||||
this.errorMask |= 0x20; // set D27F for invalid seg address
|
||||
segs = euSize-segAddr; // compute number of segs possible to read
|
||||
length = segs*240; // recompute length and ending seg address
|
||||
endAddr = euSize-1;
|
||||
}
|
||||
finishTime = new Date().getTime() +
|
||||
(Math.random()*this.maxLatency + segs*240/this.charXferRate)*1000;
|
||||
|
||||
// No length specified, so just finish the I/O
|
||||
if (segs < 1) {
|
||||
finish(this.errorMask, 0);
|
||||
// do NOT clear the error mask
|
||||
|
||||
// A multi-segment read
|
||||
} else {
|
||||
range = window.IDBKeyRange.bound(segAddr, endAddr);
|
||||
txn = this.disk.transacation(euName);
|
||||
|
||||
req = txn.objectStore(euName).openCursor(range);
|
||||
req.onsuccess = function(ev) {
|
||||
var cursor = ev.target.result;
|
||||
|
||||
if (cursor) { // found a segment at some address in range
|
||||
cursor.continue();
|
||||
} else { // at end of range
|
||||
setTimeout(function() {
|
||||
finish(that.errorMask, length);
|
||||
// do NOT clear the error mask
|
||||
}, finishTime - new Date().getTime());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DiskUnit.prototype.readInterrogate = function(finish, control) {
|
||||
/* Initiates a read interrogate operation on the unit. This serves only to
|
||||
check the addresss for validity and to return any errorMask from a prior
|
||||
read check operation. This implementation assumes completion will be delayed
|
||||
by a random amount of time based on rotational latency for the EU to search for
|
||||
the address */
|
||||
var euSize; // max seg size for EU
|
||||
var segAddr = control % 1000000; // starting seg address
|
||||
var euNumber = (control % 10000000 - segAddr)/1000000;
|
||||
var euName = this.euPrefix + euNumber;
|
||||
|
||||
this.finish = finish; // for global error handler
|
||||
euSize = this.config[euName];
|
||||
if (!euSize) { // EU does not exist
|
||||
finish(this.errorMask | 0x20, 0); // set D27F for EU not ready
|
||||
this.errorMask = 0;
|
||||
} else {
|
||||
if (segAddr < 0 || segAddr >= euSize) { // if read is past end of disk
|
||||
this.errorMask |= 0x20; // set D27F for invalid seg address
|
||||
}
|
||||
setTimeout(function() {
|
||||
finish(that.errorMask, length);
|
||||
this.errorMask = 0;
|
||||
}, Math.random()*this.maxLatency*1000);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DiskUnit.prototype.writeInterrogate = function (finish, control) {
|
||||
/* Initiates a write interrogate operation on the unit. This serves only to
|
||||
check the addresss for validity and to return any errorMask from a prior
|
||||
read check operation. This implementation assumes completion will be delayed
|
||||
by a random amount of time based on rotational latency for the EU to search for
|
||||
the address */
|
||||
|
||||
/* Note: until disk write lockout is implemented, this operation is identical
|
||||
to readInterrogate() */
|
||||
|
||||
var euSize; // max seg size for EU
|
||||
var segAddr = control % 1000000; // starting seg address
|
||||
var euNumber = (control % 10000000 - segAddr)/1000000;
|
||||
var euName = this.euPrefix + euNumber;
|
||||
|
||||
this.finish = finish; // for global error handler
|
||||
euSize = this.config[euName];
|
||||
if (!euSize) { // EU does not exist
|
||||
finish(this.errorMask | 0x20, 0); // set D27F for EU not ready
|
||||
this.errorMask = 0;
|
||||
} else {
|
||||
if (segAddr < 0 || segAddr >= euSize) { // if read is past end of disk
|
||||
this.errorMask |= 0x20; // set D27F for invalid seg address
|
||||
}
|
||||
setTimeout(function() {
|
||||
finish(that.errorMask, length);
|
||||
this.errorMask = 0;
|
||||
}, Math.random()*this.maxLatency*1000);
|
||||
}
|
||||
};
|
||||
@@ -27,7 +27,6 @@ function B5500DummyUnit(mnemonic, index, designate, statusChange, signal) {
|
||||
this.designate = designate; // IOD unit designate number
|
||||
this.statusChange = statusChange; // external function to call for ready-status change
|
||||
this.signal = signal; // external function to call for special signals (e.g,. SPO input request)
|
||||
this.finish = null; // external function to call for I/O completion
|
||||
|
||||
this.clear();
|
||||
}
|
||||
@@ -39,6 +38,10 @@ B5500DummyUnit.prototype.clear = function() {
|
||||
this.ready = false; // ready status
|
||||
this.busy = false; // busy status
|
||||
this.activeIOUnit = 0; // I/O unit currently using this device
|
||||
|
||||
this.errorMask = 0; // error mask for finish()
|
||||
this.finish = null; // external function to call for I/O completion
|
||||
this.buffer = null; //
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
@@ -77,21 +80,21 @@ B5500DummyUnit.prototype.rewind = function(finish) {
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DummyUnit.prototype.readCheck = function(finish, length) {
|
||||
B5500DummyUnit.prototype.readCheck = function(finish, length, control) {
|
||||
/* Initiates a read check operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DummyUnit.prototype.readInterrogate = function(finish) {
|
||||
B5500DummyUnit.prototype.readInterrogate = function(finish, control) {
|
||||
/* Initiates a read interrogate operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DummyUnit.prototype.writeInterrogate = function (finish) {
|
||||
B5500DummyUnit.prototype.writeInterrogate = function (finish, control) {
|
||||
/* Initiates a write interrogate operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
|
||||
@@ -29,7 +29,6 @@ function B5500SPOUnit(mnemonic, unitIndex, designate, statusChange, signal) {
|
||||
this.designate = designate; // IOD unit designate number
|
||||
this.statusChange = statusChange; // external function to call for ready-status change
|
||||
this.signal = signal; // external function to call for special signals (e.g,. SPO input request)
|
||||
this.finish = null; // external function to call for I/O completion
|
||||
|
||||
this.clear();
|
||||
|
||||
@@ -76,15 +75,17 @@ B5500SPOUnit.prototype.clear = function() {
|
||||
this.ready = false; // ready status
|
||||
this.busy = false; // busy status
|
||||
this.activeIOUnit = 0; // I/O unit currently using this device
|
||||
this.spoState = this.spoNotReady; // Current state of SPO interface
|
||||
this.spoLocalRequested = false; // LOCAL button pressed while active
|
||||
this.errorMask = 0; // error mask for finish()
|
||||
|
||||
this.errorMask = 0; // error mask for finish()
|
||||
this.finish = null; // external function to call for I/O completion
|
||||
this.buffer = null;
|
||||
this.bufLength = 0;
|
||||
this.bufIndex = 0;
|
||||
this.printCol = 0;
|
||||
this.nextCharTime = 0;
|
||||
|
||||
this.spoState = this.spoNotReady; // Current state of SPO interface
|
||||
this.spoLocalRequested = false; // LOCAL button pressed while active
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
@@ -551,21 +552,21 @@ B5500SPOUnit.prototype.rewind = function(finish) {
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500SPOUnit.prototype.readCheck = function(finish, length) {
|
||||
B5500SPOUnit.prototype.readCheck = function(finish, length, control) {
|
||||
/* Initiates a read check operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500SPOUnit.prototype.readInterrogate = function(finish) {
|
||||
B5500SPOUnit.prototype.readInterrogate = function(finish, control) {
|
||||
/* Initiates a read interrogate operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500SPOUnit.prototype.writeInterrogate = function (finish) {
|
||||
B5500SPOUnit.prototype.writeInterrogate = function (finish, control) {
|
||||
/* Initiates a write interrogate operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
<meta http-equiv="Content-Style-Type" content="text/css">
|
||||
<link id=defaultStyleSheet rel=stylesheet type="text/css" href="B5500SyllableDebugger.css">
|
||||
|
||||
<script src="/B5500/B5500DummyUnit.js"></script>
|
||||
<script src="/B5500/B5500SPOUnit.js"></script>
|
||||
<script src="/B5500/B5500DiskUnit.js"></script>
|
||||
|
||||
<script src="/B5500EMU/B5500SystemConfiguration.js"></script>
|
||||
<script src="/B5500EMU/B5500CentralControl.js"></script>
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
<meta http-equiv="Content-Style-Type" content="text/css">
|
||||
<link id=defaultStyleSheet rel=stylesheet type="text/css" href="B5500DistributionAndDisplay.css">
|
||||
|
||||
<script src="../B5500SPOUnit.js"></script>
|
||||
<script src="/B5500/B5500DummyUnit.js"></script>
|
||||
<script src="/B5500/B5500SPOUnit.js"></script>
|
||||
<script src="/B5500/B5500DiskUnit.js"></script>
|
||||
|
||||
<script src="../../emulator/B5500SystemConfiguration.js"></script>
|
||||
<script src="../../emulator/B5500CentralControl.js"></script>
|
||||
<script src="../../emulator/B5500Processor.js"></script>
|
||||
<script src="../../emulator/B5500IOUnit.js"></script>
|
||||
<script src="/B5500EMU/emulator/B5500SystemConfiguration.js"></script>
|
||||
<script src="/B5500EMU/emulator/B5500CentralControl.js"></script>
|
||||
<script src="/B5500EMU/emulator/B5500Processor.js"></script>
|
||||
<script src="/B5500EMU/emulator/B5500IOUnit.js"></script>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
@@ -23,7 +25,7 @@ function setText(id, text) {
|
||||
/* Replaces the children of the node having id="id" with the "text" */
|
||||
var e = document.getElementById(id);
|
||||
var f;
|
||||
|
||||
|
||||
if (!e) {
|
||||
alert("Invalid node id \"" + id + "\"");
|
||||
} else {
|
||||
@@ -35,7 +37,7 @@ function setText(id, text) {
|
||||
}
|
||||
|
||||
function getOctal(id) {
|
||||
/* Obtains the .value from the element "id", parses it, and returns the
|
||||
/* Obtains the .value from the element "id", parses it, and returns the
|
||||
result as a B5500 numeric word. If the element text contains any of "-+eE."
|
||||
the text is parsed as a decimal integer or floating point number, otherwise
|
||||
it is parsed as an octal value */
|
||||
@@ -46,7 +48,7 @@ function getOctal(id) {
|
||||
var tv = 0; // sign of parsed value exponent
|
||||
var text = e.value; // text of element
|
||||
var v; // parsed value of element text
|
||||
|
||||
|
||||
if (text.search(/\S/) < 0) {
|
||||
text = "0";
|
||||
}
|
||||
@@ -62,7 +64,7 @@ function getOctal(id) {
|
||||
while (v < 0x1000000000) {
|
||||
v *= 8;
|
||||
ev--;
|
||||
}
|
||||
}
|
||||
while (v >= 0x8000000000) {
|
||||
v /= 8;
|
||||
ev++;
|
||||
@@ -81,7 +83,7 @@ function getOctal(id) {
|
||||
v = (((sv*2) + tv)*64 + ev%64)*0x8000000000 + mv; // to B5500 format
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isNaN(v)) {
|
||||
e.style.backgroundColor = "red";
|
||||
} else {
|
||||
@@ -93,7 +95,7 @@ function getOctal(id) {
|
||||
function putOctal(id, value) {
|
||||
/* Formats the "value" as octal and set the "id".value property with the result */
|
||||
var e = document.getElementById(id);
|
||||
|
||||
|
||||
e.value = value.toString(8);
|
||||
}
|
||||
|
||||
@@ -104,7 +106,7 @@ function putNumber(id, value) {
|
||||
var e = (value - m)/0x8000000000; // get the exponent and sign bits
|
||||
var s = (e & 0x80) >>> 7; // get the mantissa sign
|
||||
var t = (e & 0x40) >>> 6; // get the exponent sign
|
||||
|
||||
|
||||
e = (t ? -(e & 0x3F) : (e & 0x3F)); // get signed value of exponent
|
||||
setText(id, (Math.pow(8, e)*(s ? -m : m)).toPrecision(12));
|
||||
}
|
||||
@@ -115,7 +117,7 @@ function reg_onChange(ev) {
|
||||
var id = e.id;
|
||||
var result = true;
|
||||
var value;
|
||||
|
||||
|
||||
value = getOctal(id);
|
||||
if (isNaN(value)) {
|
||||
e.style.backgroundColor = "red";
|
||||
@@ -140,7 +142,7 @@ function stepIt() {
|
||||
title = opList.options[opList.selectedIndex].text
|
||||
value = opList.options[opList.selectedIndex].value;
|
||||
opcode = parseInt(value, 8);
|
||||
|
||||
|
||||
value = getOctal("AReg");
|
||||
if (!isNaN(value)) {
|
||||
cc.P1.A = value;
|
||||
@@ -151,10 +153,10 @@ function stepIt() {
|
||||
cc.P1.B = value;
|
||||
cc.P1.BROF = 1;
|
||||
setText("BRegOrig", value.toString(8));
|
||||
|
||||
|
||||
cc.P1.I = 0; // reset any interrupts
|
||||
cc.IAR = 0;
|
||||
|
||||
|
||||
cc.P1.X = 0;
|
||||
cc.P1.T = opcode;
|
||||
cc.P1.step();
|
||||
@@ -190,7 +192,7 @@ window.onload = function() {
|
||||
cc.P1.T = cc.fieldIsolate(cc.P1.P, 0, 12);
|
||||
cc.P1.TROF = 1;
|
||||
cc.P1.L = 1; // point to the next instruction
|
||||
|
||||
|
||||
cc.P1.NCSF = 1; // run test in normal state
|
||||
|
||||
}
|
||||
@@ -203,7 +205,7 @@ window.onload = function() {
|
||||
<div id=BurroughsLogo>
|
||||
<img id=BurroughsLogoImage src="../Burroughs-Logo-Neg.jpg">
|
||||
</div>
|
||||
<div id=B5500Logo>B 5500
|
||||
<div id=B5500Logo>B 5500
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -223,19 +225,19 @@ window.onload = function() {
|
||||
<input id=AReg name=AReg type=text class=number size=16 maxlength=16>
|
||||
<td id=ARegValue class=number>
|
||||
<td id=ARegOrig class=number>
|
||||
<tr>
|
||||
<tr>
|
||||
<td class=center>B
|
||||
<td class=number>
|
||||
<input id=BReg name=BReg type=text class=number size=16 maxlength=16>
|
||||
<td id=BRegValue class=number>
|
||||
<td id=BRegOrig class=number>
|
||||
<tr>
|
||||
<tr>
|
||||
<td class=center>X
|
||||
<td class=number>
|
||||
<input id=XReg name=XReg type=text class=number size=13 maxlength=13>
|
||||
<td id=XRegValue class=number>
|
||||
<td id=XRegOrig class=number>
|
||||
<tr>
|
||||
<tr>
|
||||
<td class=center>I
|
||||
<td class=number>
|
||||
<input id=IReg name=IReg type=text class=number size=3 maxlength=3>
|
||||
@@ -253,7 +255,7 @@ window.onload = function() {
|
||||
<input name=Remide type=button value="Rem Div" onclick="return testIt(6)">
|
||||
-->
|
||||
|
||||
Syllable:
|
||||
Syllable:
|
||||
<select id=OpList name=OpList>
|
||||
<option value="0055" selected>NOP : No Operation
|
||||
<option value="0101">ADD : Add
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
<meta http-equiv="Content-Style-Type" content="text/css">
|
||||
<link id=defaultStyleSheet rel=stylesheet type="text/css" href="B5500DistributionAndDisplay.css">
|
||||
|
||||
<script src="../B5500SPOUnit.js"></script>
|
||||
<script src="/B5500/B5500DummyUnit.js"></script>
|
||||
<script src="/B5500/B5500SPOUnit.js"></script>
|
||||
<script src="/B5500/B5500DiskUnit.js"></script>
|
||||
|
||||
<script src="../../emulator/B5500SystemConfiguration.js"></script>
|
||||
<script src="../../emulator/B5500CentralControl.js"></script>
|
||||
<script src="../../emulator/B5500Processor.js"></script>
|
||||
<script src="../../emulator/B5500IOUnit.js"></script>
|
||||
<script src="/B5500EMU/emulator/B5500SystemConfiguration.js"></script>
|
||||
<script src="/B5500EMU/emulator/B5500CentralControl.js"></script>
|
||||
<script src="/B5500EMU/emulator/B5500Processor.js"></script>
|
||||
<script src="/B5500EMU/emulator/B5500IOUnit.js"></script>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
Reference in New Issue
Block a user