1
0
mirror of https://github.com/pkimpel/retro-220.git synced 2026-04-10 22:51:52 +00:00

Release retro-220 emulator version 1.02a.

1. Apply corrections to emulator/B220Processor.js uncovered by the
paper-tape diagnostic routines acquired by Al Kossow:
    a. Integer divide: sign of R must be preserved on overflow,
       otherwise must be set to sign of the dividend (A register).
    b. Floating division: sign of R must be preserved, correct align-
       ment of operands in registers before starting division cycles,
       perform 11 division cycles instead of 10, reconstruct A and R
       registers if exponent overflow is detected during post-divide
       mantissa normalization.
2. Disable console keyboard after ADD button is pressed.
3. Correct alphanumeric character code translation used by the internal
memory dump routine to match that for console and paper-tape I/O.
4. Fix bug with backward magnetic tape positioning (MPB) for tape
blocks less than 20 words long.
This commit is contained in:
Paul Kimpel
2020-08-21 07:06:12 -07:00
parent d6377935e3
commit 2665c6066e
5 changed files with 53 additions and 63 deletions

View File

@@ -260,7 +260,7 @@ function B220Processor(config, devices) {
* Global Constants *
***********************************************************************/
B220Processor.version = "1.02";
B220Processor.version = "1.02a";
B220Processor.tick = 1000/200000; // milliseconds per clock cycle (200KHz)
B220Processor.cyclesPerMilli = 1/B220Processor.tick;
@@ -1497,6 +1497,8 @@ B220Processor.prototype.integerDivide = function integerDivide() {
var dSign; // sign of divisior
var rd; // current quotient (R) digit;
var rm = this.R.value%0x10000000000;// current quotient (R) mantissa (ignore sign)
var rSign = (this.R.value - rm)/0x10000000000;
// R register sign (restored later)
var sign; // local copy of sign toggle (sign of quotient)
var tSign = 1; // sign for timing count accumulation
var x; // digit counter
@@ -1531,7 +1533,7 @@ B220Processor.prototype.integerDivide = function integerDivide() {
if (this.bcdAdd(dm, am, 11, 1, 1) < 0x10000000000) {
this.OFT.set(1);
this.A.set(aSign*0x10000000000 + am);
this.R.set(aSign*0x10000000000 + rm);
this.R.set(rSign*0x10000000000 + rm);
this.D.set(this.IB.value);
this.opTime = 0.090;
} else {
@@ -1554,7 +1556,7 @@ B220Processor.prototype.integerDivide = function integerDivide() {
} // for x
this.A.set(sign*0x10000000000 + rm); // rotate final values in A & R
this.R.set(sign*0x10000000000 + am);
this.R.set(aSign*0x10000000000 + am);
this.D.set(dSign*0x10000000000 + dm);
this.opTime = 3.805 + 0.060*count;
}
@@ -2012,7 +2014,6 @@ B220Processor.prototype.floatingDivide = function floatingDivide() {
point dividend in the A & R registers by the floating-point divisor in the
D register, producing a 9- or 10-digit quotient in the A & R registers
and a 6- or 7-digit remainder in the low-order digits of the R register.
See the Floating Point Handbook for the gory details of the result format.
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 */
@@ -2025,7 +2026,9 @@ B220Processor.prototype.floatingDivide = function floatingDivide() {
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 rm = this.R.value%0x10000000000;// current quotient (R) mantissa (drop sign)
var rSign = (this.R.value-rm)/0x10000000000;
// R register sign (restore later)
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
@@ -2048,7 +2051,7 @@ B220Processor.prototype.floatingDivide = function floatingDivide() {
dm %= 0x100000000;
if (am < 0x10000000 && dm >= 0x10000000) {
this.A.set(0); // A is not normalized but D is, =quotient=0
this.A.set(0); // A is not normalized but D is, quotient=0
this.R.set(0);
} else if (dm < 0x10000000) {
this.OFT.set(1); // D is not normalized, overflow (div 0)
@@ -2071,11 +2074,11 @@ B220Processor.prototype.floatingDivide = function floatingDivide() {
sign = 0;
this.A.set(am);
} 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;
// Shift A+R 1 digit right (exponent adjustment occurs later
ad = am%0x10;
am = (am-ad)/0x10;
rd = rm%0x10;
rm = (rm-rd)/0x10 + ad*0x1000000000;
// 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
@@ -2083,12 +2086,12 @@ B220Processor.prototype.floatingDivide = function floatingDivide() {
// 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, but we don't have sufficient technical details to know for sure.
// 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) {
for (x=0; x<11; ++x) {
// Repeatedly subtract D from A until we would get underflow.
ad = 0;
ad = 0;
/********** DEBUG **********
console.log("FDV %2d Ax=%3s A=%11s R=%11s Dx=%2s D=%11s", x,
@@ -2109,21 +2112,21 @@ B220Processor.prototype.floatingDivide = function floatingDivide() {
rd = (rm - rm%0x1000000000)/0x1000000000;
rm = (rm%0x1000000000)*0x10 + ad;
// Shift into remainder except on last digit.
if (x < 9) {
if (x < 10) {
am = am*0x10 + rd;
}
tSign = -tSign;
} // for x
/********** DEBUG **********
/********** 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 and remainder for 10 digits to exchange registers
for (x=0; x<10; ++x) {
@@ -2132,7 +2135,7 @@ B220Processor.prototype.floatingDivide = function floatingDivide() {
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),
@@ -2144,6 +2147,8 @@ B220Processor.prototype.floatingDivide = function floatingDivide() {
if (am >=0x1000000000 && ax == 0x99) {
this.OFT.set(1);
this.A.set(am);
this.R.set(rSign*0x10000000000 + rm);
} else {
if (am < 0x1000000000) {
// Normalize one digit to the right
@@ -2158,7 +2163,7 @@ B220Processor.prototype.floatingDivide = function floatingDivide() {
ax = this.bcdAdd(ax, 1, 3);
}
/********** DEBUG **********
/********** 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),
@@ -2169,7 +2174,7 @@ B220Processor.prototype.floatingDivide = function floatingDivide() {
// Reconstruct the final product in the registers
this.A.set((sign*0x100 + ax)*0x100000000 + am);
this.R.set(sign*0x10000000000 + rm);
this.R.set(rSign*0x10000000000 + rm);
}
timing += 4.075 + 0.060*count;

View File

@@ -145,6 +145,7 @@ B220ConsoleKeyboard.prototype.keypress = function keypress(ev) {
break;
case 0x2B: // "+" = ADD
this.animateClick(this.$$("AddBtn"));
this.keyboardEnable(0);
this.p.keyboardAction(-1);
break;
case 0x43: case 0x63: // "C", "c"

View File

@@ -110,16 +110,17 @@ B220ControlConsole.offOrganSwitchImage = "./resources/Organ-Switch-Up.png"
B220ControlConsole.onOrganSwitchImage = "./resources/Organ-Switch-Down.png"
B220ControlConsole.codeXlate = [ // translate internal 220 code to ANSI
" ", "_", " ", ".", "\u00A4", "_", "_", "_", "_", "_", "!", "!", "!", "!", "!", "!", // 00-0F
"&", "_", "_", "$", "*", "^", "~", "_", "_", "_", "!", "!", "!", "!", "!", "!", // 10-1F
"-", "/", "_", ",", "%", "_", "|", "_", "_", "_", "!", "!", "!", "!", "!", "!", // 20-2F
"_", "_", "_", "#", "@", "\\", "_", "_", "_", "_", "!", "!", "!", "!", "!", "!", // 30-3F
"_", "A", "B", "C", "D", "E", "F", "G", "H", "I", "!", "!", "!", "!", "!", "!", // 40-4F
"_", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "!", "!", "!", "!", "!", "!", // 50-5F
"_", "_", "S", "T", "U", "V", "W", "X", "Y", "Z", "!", "!", "!", "!", "!", "!", // 60-6F
"_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "!", "!", "!", "!", "!", "!", // 70-7F
// 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
" ", "?", "_", ".", "\u00A4", "?", "?", "?", "?", "?", "!", "!", "!", "!", "!", "!", // 00-0F
"&", "?", "?", "$", "*", "^", "|", "?", "?", "?", "!", "!", "!", "!", "!", "!", // 10-1F
"-", "/", "?", ",", "%", "?", "~", "?", "?", "?", "!", "!", "!", "!", "!", "!", // 20-2F
"?", "?", "?", "#", "@", "\\", "?", "?", "?", "?", "!", "!", "!", "!", "!", "!", // 30-3F
"?", "A", "B", "C", "D", "E", "F", "G", "H", "I", "!", "!", "!", "!", "!", "!", // 40-4F
"?", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "!", "!", "!", "!", "!", "!", // 50-5F
"?", "?", "S", "T", "U", "V", "W", "X", "Y", "Z", "!", "!", "!", "!", "!", "!", // 60-6F
"?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "!", "!", "!", "!", "!", "!", // 70-7F
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "!", "!", "!", "!", "!", // 80-8F
"_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "!", "!", "!", "!", "!", "!", // 90-9F
"?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "!", "!", "!", "!", "!", "!", // 90-9F
"!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", // A0-AF
"!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", // B0-BF
"!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", "!", // C0-CF

View File

@@ -1008,7 +1008,8 @@ B220MagTapeControl.prototype.positionBackward = function positionBackward(dReg)
.then(spaceBlock)
.catch(this.boundReleaseControl);
} else { // block count exhausted
this.currentUnit.boundReleaseDelay(this.driveState)
this.currentUnit.reposition(this.driveState)
.then(this.currentUnit.boundReleaseDelay)
.then(this.currentUnit.boundReleaseUnit)
.then(this.boundReleaseControl)
.catch(this.boundReleaseControl);

View File

@@ -192,11 +192,11 @@ B220MagTapeDrive.prototype.maxTapeWords = Math.floor(B220MagTapeDrive.prototype.
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;
B220MagTapeDrive.prototype.repositionWords = 4;
// number of words to reposition back into the block after a turnaround
B220MagTapeDrive.prototype.startTime = 3;
// tape start time [ms]
B220MagTapeDrive.prototype.startWords = 6;
B220MagTapeDrive.prototype.startWords = 4;
// number of words traversed during tape start time
B220MagTapeDrive.prototype.stopTime = 3;
// tape stop time [ms]
@@ -1356,7 +1356,7 @@ B220MagTapeDrive.prototype.reverseDirection = function reverseDirection(driveSta
/**************************************/
B220MagTapeDrive.prototype.reposition = function reposition(driveState) {
/* Reverses tape direction after a forward tape operation and repositions
the head five words from the end of the prior block, giving room for
the head four words from the end of the prior block, giving room for
startup acceleration of the next forward operation. The "prior block" is
located by the first EOB (erase gap) or flaw marker word encountered when
moving in a backward direction. Returns a Promise that resolves when tape
@@ -2283,9 +2283,9 @@ B220MagTapeDrive.prototype.initialWriteBlock = function initialWriteBlock(driveS
/**************************************/
B220MagTapeDrive.prototype.spaceForwardBlock = function spaceForwardBlock(driveState) {
/* Positions tape one block in a forward direction. Leaves the block
positioned at the preface word, ready to space the next block or reposition
into the prior block at the end of the operation. Returns a Promise that
resolves after the block is spaced */
positioned at the next preface word, ready to space the next block or
reposition into the prior block at the end of the operation. Returns a
Promise that resolves after the block is spaced */
var spaceBlock = (resolve, reject) => {
/* Spaces forward over the next block. Blocks are counted as their
@@ -2318,11 +2318,11 @@ B220MagTapeDrive.prototype.spaceForwardBlock = function spaceForwardBlock(driveS
if (w == this.markerGap) {
++x;
} else {
state = 3;
state = 3; // found preface word
}
break;
case 3: // found preface: search for end of block (next erase-gap word)
case 3: // search for end of block (next erase-gap word)
if (w == this.markerEOB) {
++x;
state = 4;
@@ -2356,9 +2356,8 @@ B220MagTapeDrive.prototype.spaceForwardBlock = function spaceForwardBlock(driveS
/**************************************/
B220MagTapeDrive.prototype.spaceBackwardBlock = function spaceBackwardBlock(driveState) {
/* Positions tape one block in a backward direction. Leaves the block
positioned five words into the end of the prior block, as for a normal
reposition after a forward operation. Returns a Promise that resolves
after the block is spaced */
positioned before the preface word. Returns a Promise that resolves after
the block is spaced */
var spaceBlock = (resolve, reject) => {
/* Spaces backward over the current or prior block. Blocks are counted
@@ -2377,10 +2376,9 @@ B220MagTapeDrive.prototype.spaceBackwardBlock = function spaceBackwardBlock(driv
} else {
w = lane[x];
switch (state) {
case 1: // initial state: skip over flaw and magnetic EOT words
case 1: // initial state: skip over inter-block gap, flaw, and magnetic EOT words
if (w == this.markerGap) {
--x;
state = 2;
} else if (w == this.markerFlaw) {
--x;
} else if (w == this.markerMagEOT) {
@@ -2390,17 +2388,9 @@ B220MagTapeDrive.prototype.spaceBackwardBlock = function spaceBackwardBlock(driv
}
break;
case 2: // skip initial inter-block gap words
if (w == this.markerGap) {
--x;
} else {
state = 3;
}
break;
case 3: // search for start of block (first prior inter-block gap word)
if (w == this.markerGap) {
state = 4;
state = 4; // just passed the preface word
} else {
--x;
}
@@ -2410,16 +2400,8 @@ B220MagTapeDrive.prototype.spaceBackwardBlock = function spaceBackwardBlock(driv
if (w == this.markerGap) {
--x;
} else {
state = 5;
}
break;
case 5: // skip the prior block's erase-gap words
if (w == this.markerEOB) {
--x;
} else { // position into end of prior block, as usual
state = 0;
resolve(this.moveTapeTo(x-this.repositionWords, driveState));
resolve(this.moveTapeTo(x, driveState));
}
break;
} // switch state
@@ -2438,7 +2420,7 @@ B220MagTapeDrive.prototype.spaceBackwardBlock = function spaceBackwardBlock(driv
/**************************************/
B220MagTapeDrive.prototype.spaceEOIBlock = function spaceEOIBlock(driveState) {
/* Spaces forward one block on tape, detecting end-of-information if encountered
(i.e., gap longer and inter-block gap. Returns a Promise that resolves
(i.e., gap longer than inter-block gap. Returns a Promise that resolves
after the block is spaced or EOI is encountered */
var spaceBlock = (resolve, reject) => {