1
0
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:
Paul Kimpel
2020-05-06 17:24:47 -07:00
parent bf226cd990
commit 1893b8a2c0
10 changed files with 290 additions and 145 deletions

View File

@@ -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;
}
}

View File

@@ -82,7 +82,7 @@
</button>
&nbsp;&nbsp;&nbsp;
<button id=ConfigureBtn>
Configure System
Configure the System
</button>
<div id=PageFooter>

View File

@@ -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);

View File

@@ -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";

View File

@@ -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);
};
/**************************************/

View File

@@ -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;

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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);
};
/**************************************/