mirror of
https://github.com/pkimpel/retro-220.git
synced 2026-04-08 22:21:08 +00:00
Release retro-220 emulator version 1.02:
1. Redesign and correct implementation of the "normalization limiter digit" in floating add/subtract. 2. Correct final value of A register when floating-overflow occurs in floating multiply. 3. Correct final value of A register when D register is not normalized for floating divide. 4. Correct register setup and post-divide normalization for floating divide. 5. Implement double-click handler on Cardatron reader and printer background panel areas to temporarily toggle their speeds by 1000 cards/lines per minute. This is intended only for testing, not for regular use. 6. Implement double-click handler on Console Burroughs logo to toggle lamp intensity averaging on and off temporarily. 7. Implement validation of magnetic tape image files during loading; reject image files that do not pass. 8. Minor changes to comments and cosmetics for multiple other scripts.
This commit is contained in:
@@ -260,7 +260,7 @@ function B220Processor(config, devices) {
|
||||
* Global Constants *
|
||||
***********************************************************************/
|
||||
|
||||
B220Processor.version = "1.01";
|
||||
B220Processor.version = "1.02";
|
||||
|
||||
B220Processor.tick = 1000/200000; // milliseconds per clock cycle (200KHz)
|
||||
B220Processor.cyclesPerMilli = 1/B220Processor.tick;
|
||||
@@ -1577,10 +1577,11 @@ B220Processor.prototype.floatingAdd = function floatingAdd(absolute) {
|
||||
var dx; // addend exponent (binary)
|
||||
var dm; // addend mantissa (BCD)
|
||||
var dSign; // addend sign
|
||||
var limiter = (this.CCONTROL - this.CCONTROL%0x1000)/0x1000; // normalizing limiter
|
||||
var limiter = 0; // normalizing limiter
|
||||
var shifts = 0; // number of scaling/normalization shifts done
|
||||
var sign; // local copy of sign toggle
|
||||
var timing = 0.125; // minimum instruction timing
|
||||
var zeroed = false; // true if either operand normalizes to zero
|
||||
|
||||
this.E.set(this.CADDR);
|
||||
this.readMemory();
|
||||
@@ -1605,35 +1606,34 @@ B220Processor.prototype.floatingAdd = function floatingAdd(absolute) {
|
||||
|
||||
// Scale D until its exponent matches or the mantissa goes to zero.
|
||||
while (ax > dx) {
|
||||
if (++shifts < 8) {
|
||||
timing += 0.010;
|
||||
dx = this.bcdAdd(1, dx, 2, 0, 0); // ++dx
|
||||
d = dm % 0x10;
|
||||
dm = (dm - d)/0x10; // shift right
|
||||
} else {
|
||||
timing += 0.010;
|
||||
dx = this.bcdAdd(1, dx, 2, 0, 0); // ++dx
|
||||
d = dm % 0x10;
|
||||
dm = (dm - d)/0x10; // shift D right
|
||||
if (dm == 0) {
|
||||
zeroed = true;
|
||||
sign = aSign; // result is value in A
|
||||
limiter = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Scale A until its exponent matches or the mantissa goes to zero.
|
||||
while (ax < dx) {
|
||||
if (++shifts < 8) {
|
||||
timing += 0.010;
|
||||
ax = this.bcdAdd(1, ax, 3, 0, 0); // ++ax
|
||||
d = am % 0x10;
|
||||
am = (am - d)/0x10; // shift right
|
||||
} else {
|
||||
timing += 0.010;
|
||||
ax = this.bcdAdd(1, ax, 3, 0, 0); // ++ax
|
||||
d = am % 0x10;
|
||||
am = (am - d)/0x10; // shift A right
|
||||
if (am == 0) {
|
||||
zeroed = true;
|
||||
am = dm; // result is value in D with adjusted sign
|
||||
ax = dx;
|
||||
limiter = 0;
|
||||
dSign = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the mantissas
|
||||
if (shifts < 8) {
|
||||
if (!zeroed) {
|
||||
compl = (aSign^sign);
|
||||
am = this.bcdAdd(am, dm, 11, compl, compl);
|
||||
|
||||
@@ -1645,58 +1645,57 @@ B220Processor.prototype.floatingAdd = function floatingAdd(absolute) {
|
||||
am = this.bcdAdd(am, 0, 11, 1, 1);
|
||||
timing += 0.060;
|
||||
}
|
||||
}
|
||||
|
||||
dm = dSign = 0; // Set D to its strange result value
|
||||
dx = 0x10;
|
||||
dm = dSign = 0; // Set D to its strange result value
|
||||
dx = 0x10;
|
||||
|
||||
// Normalize or scale the result as necessary
|
||||
if (am >= 0x100000000) {
|
||||
// Mantissa overflow: add/subtract can produce at most one digit of
|
||||
// overflow, so scale by shifting right and incrementing the exponent,
|
||||
// checking for overflow in the exponent.
|
||||
limiter = 0;
|
||||
if (ax < 0x99) {
|
||||
timing += 0.005;
|
||||
ax = this.bcdAdd(1, ax, 3, 0, 0); // ++ax
|
||||
d = am % 0x10;
|
||||
am = (am - d)/0x10; // shift right
|
||||
} else {
|
||||
// A scaling shift would overflow the exponent, so set the overflow
|
||||
// toggle and leave the mantissa as it was from the add, without the
|
||||
// exponent inserted back into it. Since the A register gets reassembled
|
||||
// below, we need to set up the mantissa and exponent so the reconstruct
|
||||
// will effectively do nothing.
|
||||
this.OFT.set(1);
|
||||
sign = ax = dx = limiter = 0;
|
||||
}
|
||||
} else if (am == 0) { // mantissa is zero
|
||||
ax = sign = limiter = 0;
|
||||
timing += 0.065;
|
||||
} else { // normalize the result as necessary
|
||||
shifts = 0;
|
||||
while (am < 0x10000000) {
|
||||
if (ax > 0) {
|
||||
++shifts;
|
||||
timing += 0.010;
|
||||
ax = this.bcdAdd(1, ax, 3, 1, 1); // --ax
|
||||
am *= 0x10; // shift left
|
||||
// Normalize or scale the result as necessary
|
||||
if (am >= 0x100000000) {
|
||||
// Mantissa overflow: add/subtract can produce at most one digit of
|
||||
// overflow, so scale by shifting right and incrementing the exponent,
|
||||
// checking for overflow in the exponent.
|
||||
if (ax < 0x99) {
|
||||
timing += 0.005;
|
||||
ax = this.bcdAdd(1, ax, 3, 0, 0); // ++ax
|
||||
d = am % 0x10;
|
||||
am = (am - d)/0x10; // shift A right
|
||||
} else {
|
||||
// Exponent underflow: set the reconstructed A to zero.
|
||||
am = ax = sign = 0;
|
||||
break;
|
||||
// A scaling shift would overflow the exponent, so set the overflow
|
||||
// toggle and leave the mantissa as it was from the add, without the
|
||||
// exponent inserted back into it. Since the A register gets reassembled
|
||||
// below, we need to set up the mantissa and exponent so the reconstruct
|
||||
// will effectively do nothing.
|
||||
this.OFT.set(1);
|
||||
sign = ax = dx = 0;
|
||||
}
|
||||
} else if (am == 0) { // mantissa is zero => result is zero
|
||||
ax = sign = 0;
|
||||
timing += 0.065;
|
||||
} else { // normalize the result as necessary
|
||||
while (am < 0x10000000) {
|
||||
if (ax > 0 && shifts < 8) {
|
||||
++shifts;
|
||||
timing += 0.010;
|
||||
ax = this.bcdAdd(1, ax, 3, 1, 1); // --ax
|
||||
am *= 0x10; // shift left
|
||||
} else {
|
||||
// Exponent underflow: set the reconstructed A to zero.
|
||||
am = ax = sign = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine whether normalizing shifts exceed the limiter value
|
||||
if (limiter > 0) {
|
||||
if (limiter >= 8) {
|
||||
limiter = 0;
|
||||
} else if (shifts > limiter) {
|
||||
limiter = 10 - (shifts-limiter);
|
||||
this.SST.set(1); // limiter exceeded: set Single-Step
|
||||
} else {
|
||||
limiter = 0;
|
||||
// Determine whether normalizing shifts exceed the limiter value
|
||||
limiter = (this.CCONTROL - this.CCONTROL%0x1000)/0x1000;
|
||||
if (limiter > 0) {
|
||||
if (limiter >= 8) {
|
||||
limiter = 0;
|
||||
} else if (shifts > limiter) {
|
||||
limiter = 10 - (shifts-limiter);
|
||||
this.SST.set(1); // limiter exceeded: set Single-Step
|
||||
} else {
|
||||
limiter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1944,7 +1943,7 @@ B220Processor.prototype.floatingMultiply = function floatingMultiply() {
|
||||
timing += 0.080;
|
||||
if (x >= 0x150) { // exponent overflow
|
||||
this.OFT.set(1);
|
||||
this.A.set(0);
|
||||
this.A.set(am);
|
||||
this.R.set(rm);
|
||||
} else if (x < 0x50) { // exponent underflow
|
||||
this.A.set(0);
|
||||
@@ -2017,19 +2016,20 @@ B220Processor.prototype.floatingDivide = function floatingDivide() {
|
||||
All values are BCD with the sign in the 11th digit position. The floating
|
||||
exponent is in the first two digit positions, biased by 50. Sets the
|
||||
Digit Check alarm as necessary */
|
||||
var ax; // dividend/quotient exponent
|
||||
var ad = 0; // current remainder (A) digit
|
||||
var ax = 0; // dividend/quotient exponent
|
||||
var am = this.A.value % 0x10000000000; // current remainder (A) mantissa
|
||||
var aSign = ((this.A.value - am)/0x10000000000)%2;
|
||||
var count = 0; // count of word-times consumed
|
||||
var dx; // divisor exponent
|
||||
var dm; // divisor mantissa
|
||||
var dSign; // divisor sign
|
||||
var rd; // current quotient (R) digit;
|
||||
var dx = 0; // divisor exponent
|
||||
var dm = 0; // divisor mantissa
|
||||
var dSign = 0; // divisor sign
|
||||
var rd = 0; // current quotient (R) digit;
|
||||
var rm = this.R.value%0x10000000000;// current quotient (R) mantissa (ignore sign)
|
||||
var sign; // local copy of sign toggle (sign of quotient)
|
||||
var sign = 0; // local copy of sign toggle (sign of quotient)
|
||||
var timing = 0.085; // minimum instruction timing
|
||||
var tSign = 1; // sign for timing count accumulation
|
||||
var x; // digit counter
|
||||
var x = 0; // digit counter
|
||||
|
||||
this.E.set(this.CADDR);
|
||||
this.readMemory();
|
||||
@@ -2052,7 +2052,7 @@ B220Processor.prototype.floatingDivide = function floatingDivide() {
|
||||
this.R.set(0);
|
||||
} else if (dm < 0x10000000) {
|
||||
this.OFT.set(1); // D is not normalized, overflow (div 0)
|
||||
this.A.set(am);
|
||||
this.A.set(ax*0x100000000 + am);
|
||||
} else {
|
||||
// Add the exponent bias to the dividend exponent and check for underflow
|
||||
ax = this.bcdAdd(ax, 0x50, 3);
|
||||
@@ -2060,61 +2060,118 @@ B220Processor.prototype.floatingDivide = function floatingDivide() {
|
||||
if (ax < dx) {
|
||||
// Exponents differ by more than 50 -- underflow
|
||||
sign = 0;
|
||||
ax = this.bcdAdd(dx, ax, 3, 1, 1);
|
||||
this.A.set(0);
|
||||
this.R.set(0);
|
||||
} else {
|
||||
// If dividend >= divisor, scale the exponent by 1
|
||||
if (am >= dm) {
|
||||
ax = this.bcdAdd(ax, 1, 3);
|
||||
}
|
||||
// Subtract the exponents and check for overflow
|
||||
ax = this.bcdAdd(dx, ax, 3, 1, 1);
|
||||
if (ax > 0x99) {
|
||||
this.OFT.set(1);
|
||||
sign = 0;
|
||||
this.A.set(am);
|
||||
this.R.set(rm);
|
||||
} else {
|
||||
// Shift A+R, D left 2 into high-order digits
|
||||
dm *= 0x100;
|
||||
rd = (rm - rm%0x100000000)/0x100000000;
|
||||
rm = (rm%0x100000000)*0x100;
|
||||
am = am*0x100 + rd;
|
||||
|
||||
// We now have the divisor in D (dm) and the dividend in A (am) & R (rm).
|
||||
// The value in am will become the remainder; the value in rm will become
|
||||
// the quotient. Go through a classic long-division cycle, repeatedly
|
||||
// subtracting the divisor from the dividend, counting subtractions until
|
||||
// underflow occurs, and shifting the divisor left one digit.
|
||||
// The 220 probably did not work quite the way that it has been mechanized
|
||||
// below, which is close to the way the 205 emulator works.
|
||||
// below, but we don't have sufficient technical details to know for sure.
|
||||
// The following is adapted from the 205 implementation.
|
||||
|
||||
for (x=0; x<10; ++x) {
|
||||
// Repeatedly subtract D from A until we would get underflow.
|
||||
rd = 0;
|
||||
ad = 0;
|
||||
|
||||
/********** DEBUG **********
|
||||
console.log("FDV %2d Ax=%3s A=%11s R=%11s Dx=%2s D=%11s", x,
|
||||
(ax+0x1000).toString(16).substring(1),
|
||||
(am+0x100000000000).toString(16).substring(1),
|
||||
(rm+0x100000000000).toString(16).substring(1),
|
||||
(dx+0x1000).toString(16).substring(1),
|
||||
(dm+0x100000000000).toString(16).substring(1));
|
||||
***************************/
|
||||
|
||||
while (am >= dm) {
|
||||
am = this.bcdAdd(dm, am, 11, 1, 1);
|
||||
++rd;
|
||||
++ad;
|
||||
count += tSign;
|
||||
}
|
||||
|
||||
// Shift A & R to the left one digit, accumulating the quotient digit in R
|
||||
rm = rm*0x10 + rd;
|
||||
rd = (rm - rm%0x10000000000)/0x10000000000;
|
||||
rm %= 0x10000000000;
|
||||
rd = (rm - rm%0x1000000000)/0x1000000000;
|
||||
rm = (rm%0x1000000000)*0x10 + ad;
|
||||
// Shift into remainder except on last digit.
|
||||
if (x < 9) {
|
||||
am = am*0x10 + rd; // shift into remainder except on last digit
|
||||
am = am*0x10 + rd;
|
||||
}
|
||||
|
||||
tSign = -tSign;
|
||||
} // for x
|
||||
|
||||
/********** DEBUG **********
|
||||
console.log("FDV %2d Ax=%3s A=%11s R=%11s Dx=%2s D=%11s", x,
|
||||
(ax+0x1000).toString(16).substring(1),
|
||||
(am+0x100000000000).toString(16).substring(1),
|
||||
(rm+0x100000000000).toString(16).substring(1),
|
||||
(dx+0x1000).toString(16).substring(1),
|
||||
(dm+0x100000000000).toString(16).substring(1));
|
||||
***************************/
|
||||
|
||||
// Rotate the quotient from R into A for 8 digits or until it's normalized
|
||||
for (x=0; x<8 || am < 0x10000000; ++x) {
|
||||
rd = (am - am%0x1000000000)/0x1000000000;
|
||||
rm = rm*0x10 + rd;
|
||||
rd = (rm - rm%0x10000000000)/0x10000000000;
|
||||
rm %= 0x10000000000;
|
||||
am = (am%0x10000000)*0x10 + rd;
|
||||
// Rotate the quotient and remainder for 10 digits to exchange registers
|
||||
for (x=0; x<10; ++x) {
|
||||
ad = am%0x10;
|
||||
rd = rm%0x10;
|
||||
rm = (rm - rd)/0x10 + ad*0x1000000000;
|
||||
am = (am - ad)/0x10 + rd*0x1000000000;
|
||||
}
|
||||
|
||||
/********** DEBUG **********
|
||||
console.log("FDV %2d Ax=%3s A=%11s R=%11s Dx=%2s D=%11s", 98,
|
||||
(ax+0x1000).toString(16).substring(1),
|
||||
(am+0x100000000000).toString(16).substring(1),
|
||||
(rm+0x100000000000).toString(16).substring(1),
|
||||
(dx+0x1000).toString(16).substring(1),
|
||||
(dm+0x100000000000).toString(16).substring(1));
|
||||
***************************/
|
||||
|
||||
if (am >=0x1000000000 && ax == 0x99) {
|
||||
this.OFT.set(1);
|
||||
} else {
|
||||
if (am < 0x1000000000) {
|
||||
// Normalize one digit to the right
|
||||
ad = am%0x10;
|
||||
am = (am - ad)/0x10;
|
||||
rm = (rm - rm%0x10)/0x10 + ad*0x1000000000;
|
||||
} else {
|
||||
// Normalize two digits to the right and adjust exponent
|
||||
ad = am%0x100;
|
||||
am = (am - ad)/0x100;
|
||||
rm = (rm - rm%0x100)/0x100 + ad*0x100000000;
|
||||
ax = this.bcdAdd(ax, 1, 3);
|
||||
}
|
||||
|
||||
/********** DEBUG **********
|
||||
console.log("FDV %2d Ax=%3s A=%11s R=%11s Dx=%2s D=%11s", 99,
|
||||
(ax+0x1000).toString(16).substring(1),
|
||||
(am+0x100000000000).toString(16).substring(1),
|
||||
(rm+0x100000000000).toString(16).substring(1),
|
||||
(dx+0x1000).toString(16).substring(1),
|
||||
(dm+0x100000000000).toString(16).substring(1));
|
||||
***************************/
|
||||
|
||||
// Reconstruct the final product in the registers
|
||||
this.A.set((sign*0x100 + ax)*0x100000000 + am);
|
||||
this.R.set(sign*0x10000000000 + rm);
|
||||
}
|
||||
|
||||
// Reconstruct the final product in the registers
|
||||
this.A.set((sign*0x100 + ax)*0x100000000 + am);
|
||||
this.R.set(sign*0x10000000000 + rm);
|
||||
timing += 4.075 + 0.060*count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
</button>
|
||||
|
||||
<button id=ConfigureBtn>
|
||||
Configure System
|
||||
Configure the System
|
||||
</button>
|
||||
|
||||
<div id=PageFooter>
|
||||
|
||||
@@ -563,6 +563,18 @@ B220CardatronInput.prototype.finishCardRead = function finishCardRead() {
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B220CardatronInput.prototype.CIDiv_dblClick = function CIDiv_dblClick(ev) {
|
||||
/* Handle the double-click event for the background panel. If the card
|
||||
reader is ready, toggles the speed by 1000 operations per minute. This is
|
||||
not intended for regular use, but as a way to speed up the 220 during long
|
||||
emulator runs */
|
||||
|
||||
if (this.ready) {
|
||||
this.linesPerMinute += (this.linesPerMinute > 1000 ? -1000 : +1000);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B220CardatronInput.prototype.initiateCardRead = function initiateCardRead() {
|
||||
/* Initiates the read of the next card into the buffer drum */
|
||||
@@ -645,6 +657,8 @@ B220CardatronInput.prototype.readerOnLoad = function readerOnLoad(ev) {
|
||||
B220CardatronInput.prototype.ClearBtn_onClick.bind(this), false);
|
||||
this.hopperBar.addEventListener("click",
|
||||
B220CardatronInput.prototype.CIHopperBar_onClick.bind(this), false);
|
||||
this.$$("CIDiv").addEventListener("dblclick",
|
||||
B220CardatronInput.prototype.CIDiv_dblClick.bind(this), false);
|
||||
|
||||
this.window.resizeBy(de.scrollWidth - this.window.innerWidth + 4, // kludge for right-padding/margin
|
||||
de.scrollHeight - this.window.innerHeight);
|
||||
|
||||
@@ -705,6 +705,18 @@ B220CardatronOutput.prototype.COSetZSBtn_onClick = function COSetZSBtn_onClick(e
|
||||
this, zsOnload);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B220CardatronOutput.prototype.CODiv_dblClick = function CODiv_dblClick(ev) {
|
||||
/* Handle the double-click event for the background panel. If the printer/
|
||||
punch is ready, toggles the speed by 1000 operations per minute. This is
|
||||
not intended for regular use, but as a way to speed up the 220 during long
|
||||
listings and debugging runs */
|
||||
|
||||
if (this.ready) {
|
||||
this.linesPerMinute += (this.linesPerMinute > 1000 ? -1000 : +1000);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B220CardatronOutput.prototype.beforeUnload = function beforeUnload(ev) {
|
||||
var msg = "Closing this window will make the device unusable.\n" +
|
||||
@@ -790,6 +802,8 @@ B220CardatronOutput.prototype.deviceOnLoad = function deviceOnLoad(ev) {
|
||||
B220CardatronOutput.prototype.COSetZSBtn_onClick.bind(this), false);
|
||||
this.$$("ClearBtn").addEventListener("click",
|
||||
B220CardatronOutput.prototype.ClearBtn_onClick.bind(this), false);
|
||||
this.$$("CODiv").addEventListener("dblclick",
|
||||
B220CardatronOutput.prototype.CODiv_dblClick.bind(this), false);
|
||||
|
||||
if (!this.isPrinter) {
|
||||
this.$$("COEndOfSupplyBtn").innerHTML = "OUT OF<br>CARDS";
|
||||
|
||||
@@ -557,8 +557,8 @@ B220ConsolePrinter.prototype.receiveSign = function receiveSign(char, successor)
|
||||
this.nextCharTime = stamp;
|
||||
}
|
||||
|
||||
setCallback(this.mnemonic, this, this.nextCharTime-stamp+delay, successor, this.boundReceiveChar);
|
||||
this.nextCharTime += delay;
|
||||
setCallback(this.mnemonic, this, this.nextCharTime-stamp, successor, this.boundReceiveChar);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
@@ -620,8 +620,8 @@ B220ConsolePrinter.prototype.receiveChar = function receiveChar(char, successor)
|
||||
break;
|
||||
} // switch char
|
||||
|
||||
setCallback(this.mnemonic, this, this.nextCharTime-stamp+delay, successor, nextReceiver);
|
||||
this.nextCharTime += delay;
|
||||
setCallback(this.mnemonic, this, this.nextCharTime-stamp, successor, nextReceiver);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
|
||||
@@ -1016,6 +1016,16 @@ B220ControlConsole.prototype.consoleOnLoad = function consoleOnLoad(ev) {
|
||||
this.$$("IntervalTimerResetBtn").addEventListener("click", this.boundResetTimer, false);
|
||||
this.$$("PowerOffBtn").addEventListener("dblclick", this.boundPowerBtn_Click, false);
|
||||
|
||||
/******** DEBUG ***** Toggle neon lamp glow averaging **********************/
|
||||
this.$$("BurroughsLogo").addEventListener("dblclick", function toggleGlow(ev) {
|
||||
if (B220Processor.maxGlowTime >= B220Processor.neonPersistence) {
|
||||
B220Processor.maxGlowTime = 1.0e-8;
|
||||
} else {
|
||||
B220Processor.maxGlowTime = B220Processor.neonPersistence;
|
||||
}
|
||||
}, false);
|
||||
/******** END DEBUG ********************************************************/
|
||||
|
||||
this.window.addEventListener("beforeunload", B220ControlConsole.prototype.beforeUnload);
|
||||
|
||||
this.$$("EmulatorVersion").textContent = B220Processor.version;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
function B220MagTapeControl(p) {
|
||||
/* Constructor for the MagTapeControl object */
|
||||
var left = 0; // control window left position
|
||||
var top = 432; // control window top-from-bottom position
|
||||
var top = 452; // control window top-from-bottom position
|
||||
var u; // unit configuration object
|
||||
var x; // unit index
|
||||
|
||||
|
||||
@@ -56,11 +56,14 @@
|
||||
* return/line-feed pair.
|
||||
*
|
||||
* The first field on the line contains one or two integers, formatted as "L" or
|
||||
* "R*L". L represents the lane number. Only its low-order bit (0/1) is used.
|
||||
* "R*L". L represents the lane number. Only its low-order bit (0/1) is significant.
|
||||
* R is a repeat factor used to compress the size of tape image files. It
|
||||
* indicates the number of copies of this block that exist consecutively at this
|
||||
* point in the tape image. If R and its delimiting "*" are not present, R is
|
||||
* assumed to be one. If R is not present, the "*" must also be not present.
|
||||
* assumed to be one. If R is not present, the "*" must also not be present.
|
||||
* If R is less than one or greter than 45570 (the maximum number of blocks on
|
||||
* one lane of a 3500-foot reel of tape), it is considered invalid and aborts
|
||||
* the load.
|
||||
*
|
||||
* The second field on a line is the preface word indicating the length of the
|
||||
* data. A block length of 100 may be represented as either 0 or 100. Values
|
||||
@@ -74,6 +77,11 @@
|
||||
* digit of the word. Spaces may precede or follow the digits of a field, but may
|
||||
* not appear within the digits or between any leading "-" and the first digit.
|
||||
*
|
||||
* If any of the repeat factor, lane number, preface word, or block data words
|
||||
* is not a valid integer, it is considered invalid and aborts the block. If the
|
||||
* tape image fills more than 729165 words on a lane (the maximum amount for a
|
||||
* 3500-foot reel), the load is aborted at that point.
|
||||
*
|
||||
* Note that with this representation, it is possible that the word count in
|
||||
* the preface may not match the actual number of words on the rest of the line.
|
||||
* While this might be useful at some point to allow construction of invalid
|
||||
@@ -167,8 +175,6 @@ B220MagTapeDrive.prototype.inchesPerWord = 12/B220MagTapeDrive.prototype.density
|
||||
B220MagTapeDrive.prototype.millisPerWord = B220MagTapeDrive.prototype.inchesPerWord/B220MagTapeDrive.prototype.tapeSpeed;
|
||||
B220MagTapeDrive.prototype.maxTapeInches = 3500*12;
|
||||
// length of a standard reel of tape [inches]
|
||||
B220MagTapeDrive.prototype.maxTapeWords = Math.floor(B220MagTapeDrive.prototype.maxTapeInches*B220MagTapeDrive.prototype.density/12);
|
||||
// max words on a tape (12 digits/word)
|
||||
B220MagTapeDrive.prototype.minBlockWords = 10;
|
||||
// min words in a physical block
|
||||
B220MagTapeDrive.prototype.maxBlockWords = 100;
|
||||
@@ -181,6 +187,11 @@ B220MagTapeDrive.prototype.startOfBlockWords = 4;
|
||||
// inter-block tape gap + preface [words]
|
||||
B220MagTapeDrive.prototype.endOfBlockWords = 2;
|
||||
// end-of-block + erase gap [words]
|
||||
B220MagTapeDrive.prototype.maxTapeWords = Math.floor(B220MagTapeDrive.prototype.maxTapeInches*B220MagTapeDrive.prototype.density/12);
|
||||
// max words on a tape (12 digits/word)
|
||||
B220MagTapeDrive.prototype.maxTapeBlocks = Math.floor(B220MagTapeDrive.prototype.maxTapeWords/
|
||||
(B220MagTapeDrive.prototype.minBlockWords+B220MagTapeDrive.prototype.startOfBlockWords+B220MagTapeDrive.prototype.endOfBlockWords));
|
||||
// max possible blocks on a tape lane
|
||||
B220MagTapeDrive.prototype.repositionWords = 5;
|
||||
// number of words to reposition back into the block after a turnaround
|
||||
B220MagTapeDrive.prototype.startTime = 3;
|
||||
@@ -684,37 +695,46 @@ B220MagTapeDrive.prototype.loadTape = function loadTape() {
|
||||
/* Event handler for tape image file onLoad. Loads a text image as
|
||||
comma-delimited decimal word values. No end-of-tape block is written
|
||||
unless it is present in the text image */
|
||||
var blockWords; // words in current tape block
|
||||
var chunk; // ANSI text of current chunk
|
||||
var chunkLength; // length of current ASCII chunk
|
||||
var blockNr = 0; // current tape block number
|
||||
var blockWords = 0; // words in current tape block
|
||||
var chunk = ""; // ANSI text of current chunk
|
||||
var chunkLength = 0; // length of current ASCII chunk
|
||||
var buf = ev.target.result; // ANSI tape image buffer
|
||||
var bufLength = buf.length; // length of ANSI tape image buffer
|
||||
var dups; // repeat factor for consecutive blocks
|
||||
var dups = 0; // repeat factor for consecutive blocks
|
||||
var eolRex = /([^\n\r\f]*)((:?\r[\n\f]?)|\n|\f)?/g;
|
||||
var index = 0; // char index into tape image buffer for next chunk
|
||||
var lane; // current tape lane image
|
||||
var lane = 0; // current tape lane image
|
||||
var lx = [0,0]; // word indexes for each lane
|
||||
var match; // result of eolRex.exec()
|
||||
var preface; // preface word: block length in words
|
||||
var repeatRex = /\s*(\d+)\s*\*\s*/; // regex to detect and parse repeat factor
|
||||
var tx; // char index into ANSI chunk text
|
||||
var wx; // word index within current block
|
||||
var match = null; // result of eolRex.exec()
|
||||
var numericRex = /^-?\d+\s*$/; // regex to detect valid integer number
|
||||
var preface = 0; // preface word: block length in words
|
||||
var repeatRex = /^\s*(\d+)\s*\*\s*/; // regex to detect and parse repeat factor
|
||||
var tx = 0; // char index into ANSI chunk text
|
||||
var w = 0; // current word value
|
||||
var wx = 0; // word index within current block
|
||||
|
||||
function abortLoad(msg, blockNr, chunk) {
|
||||
/* Displays an alert for a fatal load error */
|
||||
|
||||
mt.window.alert("Abort load: " + msg + " @ block " + blockNr +
|
||||
"\n\"" + chunk + "\"");
|
||||
}
|
||||
|
||||
function parseRepeatFactor() {
|
||||
/* Parses the repeat factor, if any, from the first field on the
|
||||
line and returns its value. If there is no repeat factor, returns 1.
|
||||
If the repeat factor is not a valid integer, returns NaN.
|
||||
Leaves "tx" (the index into the line) pointing to the lane number */
|
||||
var match; // result of regex match
|
||||
var v; // parsed numeric value
|
||||
var v = 1; // parsed numeric value, default to 1
|
||||
|
||||
match = repeatRex.exec(chunk);
|
||||
if (!match) {
|
||||
v = 1; // default if no repeat present
|
||||
} else {
|
||||
match = chunk.match(repeatRex);
|
||||
if (match) {
|
||||
tx += match[0].length;
|
||||
v = parseInt(match[1], 10);
|
||||
if (isNaN(v)) {
|
||||
v = 1; // default if repeat is non-numeric
|
||||
if (match[1].search(numericRex) == 0) {
|
||||
v = parseInt(match[1], 10);
|
||||
} else {
|
||||
v = NaN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,10 +743,10 @@ B220MagTapeDrive.prototype.loadTape = function loadTape() {
|
||||
|
||||
function parseWord(radix) {
|
||||
/* Parses the next word from the chunk text and returns its value as
|
||||
determined by "radix" */
|
||||
var cx; // offset to next comma
|
||||
var text; // text of parsed word
|
||||
var v; // parsed numeric value
|
||||
determined by "radix". If the comma-delimited word is not a valid
|
||||
integer, returns NaN */
|
||||
var cx = 0; // offset to next comma
|
||||
var text = ""; // text of parsed word
|
||||
var w = 0; // result BCD word
|
||||
|
||||
if (tx < chunkLength) {
|
||||
@@ -736,16 +756,20 @@ B220MagTapeDrive.prototype.loadTape = function loadTape() {
|
||||
}
|
||||
text = chunk.substring(tx, cx).trim();
|
||||
if (text.length > 0) {
|
||||
v = parseInt(text, radix);
|
||||
if (!isNaN(v)) {
|
||||
if (v > 0) {
|
||||
w = v % 0x100000000000;
|
||||
} else if (v < 0) {
|
||||
// The number was specified as negative: if the
|
||||
// sign bit is not already set, then set it.
|
||||
w = (-v) % 0x100000000000;
|
||||
if (w % 0x20000000000 < 0x10000000000) {
|
||||
w += 0x10000000000;
|
||||
if (text.search(numericRex) != 0) {
|
||||
w = NaN;
|
||||
} else {
|
||||
w = parseInt(text, radix);
|
||||
if (!isNaN(w)) {
|
||||
if (w > 0) {
|
||||
w %= 0x100000000000;
|
||||
} else if (w < 0) {
|
||||
// The number was specified as negative: if the
|
||||
// sign bit is not already set, then set it.
|
||||
w = (-w) % 0x100000000000;
|
||||
if (w % 0x20000000000 < 0x10000000000) {
|
||||
w += 0x10000000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -769,10 +793,25 @@ B220MagTapeDrive.prototype.loadTape = function loadTape() {
|
||||
chunkLength = chunk.length;
|
||||
if (chunkLength > 0) { // ignore empty lines
|
||||
tx = 0;
|
||||
++blockNr;
|
||||
dups = parseRepeatFactor(); // get the repeat factor, if any
|
||||
mt.laneNr = parseWord(10)%2; // get the lane number
|
||||
preface = parseWord(10); // get the preface word as decimal
|
||||
if (preface > 100) { // limit blocks to 100 words
|
||||
if (isNaN(dups) || dups < 1 || dups > mt.maxTapeBlocks) {
|
||||
abortLoad("invalid repeat factor", blockNr, chunk);
|
||||
break;
|
||||
}
|
||||
|
||||
w = parseWord(10); // get the lane number
|
||||
if (isNaN(w) || w < 0) {
|
||||
abortLoad("invalid lane number", blockNr, chunk);
|
||||
break;
|
||||
}
|
||||
|
||||
mt.laneNr = w%2;
|
||||
preface = parseWord(10); // get the preface word as binary
|
||||
if (isNaN(preface)) {
|
||||
abortLoad("invalid preface/length word", blockNr, chunk);
|
||||
break;
|
||||
} else if (preface > 100) { // limit blocks to 100 words
|
||||
preface = 100;
|
||||
} else if (preface < 1) { // if block length <= 0, make it 100
|
||||
preface = 100;
|
||||
@@ -785,7 +824,13 @@ B220MagTapeDrive.prototype.loadTape = function loadTape() {
|
||||
writeBlockStart(preface);
|
||||
wx = 0; // load data words from tape image
|
||||
while (tx < chunkLength && wx < preface) {
|
||||
lane[mt.imgIndex+wx] = parseWord(16);
|
||||
w = parseWord(16);
|
||||
if (isNaN(w)) {
|
||||
abortLoad("invalid tape block word[" + wx + "]", blockNr, chunk);
|
||||
break;
|
||||
}
|
||||
|
||||
lane[mt.imgIndex+wx] = w;
|
||||
++wx;
|
||||
} // while tx:wx
|
||||
|
||||
@@ -799,13 +844,18 @@ B220MagTapeDrive.prototype.loadTape = function loadTape() {
|
||||
|
||||
wx = lx[mt.laneNr]; // starting offset of block
|
||||
blockWords = mt.imgIndex - wx; // total words in block, including overhead
|
||||
while (dups > 1) { // repeat the block as necessary
|
||||
--dups;
|
||||
while (dups > 1 && mt.imgIndex < mt.maxTapeWords) {
|
||||
--dups; // repeat the block as necessary
|
||||
++blockNr;
|
||||
lane.copyWithin(mt.imgIndex, wx, wx+blockWords);
|
||||
mt.imgIndex += blockWords;
|
||||
} // while dups
|
||||
|
||||
lx[mt.laneNr] = mt.imgIndex; // save current offset for this lane
|
||||
if (mt.imgIndex > mt.maxTapeWords) {
|
||||
abortLoad("maximum tape capacity exceeded", blockNr, chunk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (index < bufLength);
|
||||
|
||||
@@ -364,7 +364,7 @@ function ThreeWaySwitch(parent, x, y, id, offImage, onImage1, onImage2) {
|
||||
x & y are the coordinates of the switch within its containing element;
|
||||
id is the DOM id */
|
||||
|
||||
this.state = 0; // current switch state, 0=off, 1=down, 2=up)
|
||||
this.state = 0; // current switch state, 0=off, 1=down, 2=up
|
||||
this.topCaptionDiv = null; // optional top caption element
|
||||
this.bottomCaptionDiv = null; // optional bottom caption element
|
||||
this.offImage = offImage; // image used for the off state
|
||||
|
||||
@@ -287,8 +287,8 @@ B220PaperTapePunch.prototype.receiveSign = function receiveSign(char, successor)
|
||||
this.nextCharTime = stamp;
|
||||
}
|
||||
|
||||
setCallback(this.mnemonic, this, this.nextCharTime-stamp+delay, successor, this.boundReceiveChar);
|
||||
this.nextCharTime += delay;
|
||||
setCallback(this.mnemonic, this, this.nextCharTime-stamp, successor, this.boundReceiveChar);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
@@ -311,8 +311,8 @@ B220PaperTapePunch.prototype.receiveChar = function receiveChar(char, successor)
|
||||
break;
|
||||
} // switch char
|
||||
|
||||
setCallback(this.mnemonic, this, this.nextCharTime-stamp+delay, successor, nextReceiver);
|
||||
this.nextCharTime += delay;
|
||||
setCallback(this.mnemonic, this, this.nextCharTime-stamp, successor, nextReceiver);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
|
||||
Reference in New Issue
Block a user