1
0
mirror of https://github.com/pkimpel/retro-b5500.git synced 2026-04-25 20:01:52 +00:00

1. Release emulator version 0.08.

2. Change time-slicing algorithm in Processor and implement Denicola's setImmediate() shim.
3. Find and fix normalization/rounding bugs in Processor.singlePrecisionAdd and .singlePrecisionMultiply.
4. Rework Processor.doublePrecisionAdd, but it's still not right.
5. Move read interval timer details from Processor to CentralControl.
6. Implement more centralized process halt in CentralControl and Processor.
7. CardReader: 
   a. Fix bug when adding decks to input hopper if previous deck did not end with a line delimiter.
   b. Allow "?" characters as valid if not in column 1.
   c. "Upgrade" from 800 to 1400 cards/minute.
8. DummyLinePrinter:
   a. Attempt to implement double-click as a way to clear the output window (but doesn't work yet).
   b. "Upgrade" from 800 to 1040 lines per minute (B329 model).
9. SPOUnit:
   a. Tweak paper scrolling behavior.
   b. Rework handling of ESC/BS/Enter keys; allow Input Request while SPO is busy with output.
10. Set AUTOPRNT option by default in B5500ColdLoader.
This commit is contained in:
paul.kimpel@digm.com
2013-07-01 03:25:49 +00:00
parent 8f672a01df
commit 0cb2022b63
11 changed files with 558 additions and 239 deletions

View File

@@ -61,7 +61,7 @@ function B5500CentralControl() {
/**************************************/
/* Global constants */
B5500CentralControl.version = "0.07";
B5500CentralControl.version = "0.08";
B5500CentralControl.memCycles = 4; // assume 4 µs memory cycle time (the other option was 6 µs)
B5500CentralControl.rtcTick = 1000/60; // Real-time clock period, milliseconds
@@ -573,6 +573,14 @@ B5500CentralControl.prototype.tock = function tock() {
this.timer = setTimeout(this.boundTock, (interval < 1 ? 1 : interval));
};
/**************************************/
B5500CentralControl.prototype.readTimer = function readTimer() {
/* Returns the value of the 1/60th second timer */
var thisTime = new Date().getTime();
return this.CCI03F*64 + this.TM;
};
/**************************************/
B5500CentralControl.prototype.haltP2 = function haltP2() {
/* Called by P1 to halt P2. storeForInterrupt() will set P2BF=0 */
@@ -724,21 +732,11 @@ B5500CentralControl.prototype.halt = function halt() {
}
if (this.PA && this.PA.busy) {
this.PA.busy = 0;
this.PA.cycleLimit = 0;
if (this.PA.scheduler) {
clearTimeout(this.PA.scheduler);
this.PA.scheduler = null;
}
this.PA.halt();
}
if (this.PB && this.PB.busy) {
this.PB.busy = 0;
this.PB.cycleLimit = 0;
if (this.PB.scheduler) {
clearTimeout(this.PB.scheduler);
this.PB.scheduler = null;
}
this.PB.halt();
}
};

View File

@@ -52,7 +52,9 @@ function B5500Processor(procID, cc) {
/**************************************/
B5500Processor.timeSlice = 16000; // Standard run() timeslice in clocks (about 16ms, we hope)
B5500Processor.timeSlice = 4000; // this.run() timeslice, clocks
B5500Processor.minDelay = 4; // minimum schedule() delay, ms
B5500Processor.maxDelay = 20; // maximum schedule() delay, ms
B5500Processor.collation = [ // index by BIC to get collation value
53, 54, 55, 56, 57, 58, 59, 60, // @00: 0 1 2 3 4 5 6 7
@@ -108,13 +110,13 @@ B5500Processor.prototype.clear = function clear() {
this.US14X = 0; // STOP OPERATOR switch
this.busy = 0; // Processor is running, not idle or halted
this.cycleCount = 0; // Current cycle count for this.run()
this.cycleLimit = 0; // Cycle limit for this.run()
this.totalCycles = 0; // Total cycles executed on this processor
this.procStart = 0; // Javascript time that the processor started running, ms
this.procTime = 0; // Total processor running time, ms
this.procTime = 0.001; // Total processor running time, ms
this.procSlack = 0; // Total processor throttling delay, ms
this.busy = 0; // Processor is running, not idle or halted
};
/**************************************/
@@ -1284,17 +1286,9 @@ B5500Processor.prototype.storeForInterrupt = function storeForInterrupt(forTest)
if (this === this.cc.P1) {
this.T = 0x89; // inject 0211=ITI into T register
} else {
this.T = 0; // idle the processor
this.TROF = 0;
this.PROF = 0;
this.busy = 0;
this.cycleLimit = 0; // exit this.run()
this.stop(); // idle the processor
this.cc.HP2F = 1;
this.cc.P2BF = 0; // tell P1 we've stopped
if (this.scheduler) {
clearTimeout(this.scheduler);
this.scheduler = null;
}
}
this.CWMF = 0;
} else if (forTest) {
@@ -1341,12 +1335,33 @@ B5500Processor.prototype.preset = function preset(runAddr) {
/**************************************/
B5500Processor.prototype.start = function start() {
/* Initiates the processor by scheduling it on the Javascript thread */
var stamp = new Date().getTime();
this.busy = 1;
this.procStart = new Date().getTime();
this.procStart = stamp;
this.procTime -= stamp;
this.scheduler = setTimeout(this.boundSchedule, 0);
};
/**************************************/
B5500Processor.prototype.stop = function stop() {
/* Stops running the processor on the Javascript thread */
var stamp = new Date().getTime();
this.T = 0;
this.TROF = 0;
this.PROF = 0;
this.busy = 0;
this.cycleLimit = 0; // exit this.run()
if (this.scheduler) {
clearTimeout(this.scheduler);
this.scheduler = null;
}
while (this.procTime < 0) {
this.procTime += stamp;
}
};
/**************************************/
B5500Processor.prototype.initiate = function initiate(forTest) {
/* Initiates the processor from interrupt control words stored in the
@@ -1540,15 +1555,22 @@ B5500Processor.prototype.singlePrecisionCompare = function singlePrecisionCompar
B5500Processor.prototype.singlePrecisionAdd = function singlePrecisionAdd(adding) {
/* Adds the contents of the A register to the B register, leaving the result
in B and invalidating A. If "adding" is not true, the sign of A is complemented
to accomplish subtraction instead of addition */
to accomplish subtraction instead of addition.
The B5500 did this by complement arithmetic, exchanging operands as necessary,
and maintaining a bunch of Q-register flags to keep it all straight. This
routine takes a more straightforward approach, doing algebraic arithmetic on
the A and B mantissas and maintaining separate extensions (X registers) for
scaling A and B. Only one register will be scaled, so the other extension will
always be zero */
var d = 0; // the guard (rounding) digit
var ea; // signed exponent of A
var eb; // signed exponent of B
var ma; // absolute mantissa of A*8
var mb; // absolute mantissa of B*8
var ma; // absolute mantissa of A
var mb; // absolute mantissa of B
var sa; // mantissa sign of A (0=positive)
var sb; // mantissa sign of B (ditto)
var xx = 0; // local copy of X
var xa = 0; // extension to A for scaling (pseudo X)
var xb = 0; // extension to B for scaling (pseudo X)
this.cycleCount += 4; // estimate some general overhead
this.adjustABFull();
@@ -1564,9 +1586,9 @@ B5500Processor.prototype.singlePrecisionAdd = function singlePrecisionAdd(adding
}
} else if (mb == 0 && adding) { // otherwise, if B is zero and we're adding,
this.B = this.A % 0x800000000000; // result is A with flag bit reset
} else { // rats, we actually have to do this
} else { // rats, we actually have to do this...
ea = (this.A - ma)/0x8000000000;
sa = ((ea >>> 7) & 0x01);
sa = (adding ? (ea >>> 7) & 0x01 : 1-((ea >>> 7) & 0x01));
ea = (ea & 0x40 ? -(ea & 0x3F) : (ea & 0x3F));
eb = (this.B - mb)/0x8000000000;
@@ -1586,13 +1608,13 @@ B5500Processor.prototype.singlePrecisionAdd = function singlePrecisionAdd(adding
while (ea != eb) {
this.cycleCount++;
d = mb % 8;
mb = (mb - d)/8; // shift right into X
xx = (xx - xx%8)/8 + d*0x1000000000;
mb = (mb - d)/8; // shift right into extension
xb = (xb - xb%8)/8 + d*0x1000000000;
if (mb) {
eb++;
} else {
eb = ea; // if B=0, result will have exponent of A
// should we clear X at this point to prevent rounding of A?
xb = 0; // prevent rounding of result
}
}
} else if (ea < eb) {
@@ -1606,40 +1628,56 @@ B5500Processor.prototype.singlePrecisionAdd = function singlePrecisionAdd(adding
while (eb != ea) {
this.cycleCount++;
d = ma % 8;
ma = (ma -d)/8; // shift right into X
xx = (xx - xx%8)/8 + d*0x1000000000;
ma = (ma - d)/8; // shift right into extension
xa = (xa - xa%8)/8 + d*0x1000000000;
if (ma) {
ea++;
} else {
ea = eb; // if A=0, kill the scaling loop
// should we clear X at this point to prevent rounding of B?
xa = 0; // prevent rounding of result
}
}
}
// At this point, the exponents are aligned (or one of the mantissas
// is zero), so do the actual 39-bit addition
mb = (sb ? -mb : mb) + (sa ^ (adding ? 0 : 1) ? -ma : ma);
// is zero), so do the actual 39-bit additions of mantissas and extensions
if (mb == 0) {
this.B = 0;
} else {
// Determine the resulting sign
if (mb >= 0) {
sb = 0;
} else {
sb = 1;
mb = -mb;
xb = (sb ? -xb : xb) + (sa ? -xa : xa); // compute the extension
if (xb < 0) {
xb += 0x8000000000; // adjust for underflow in the extension
mb += (sb ? 1 : -1); // adjust B for borrow into extension
} else if (xb > 0x8000000000) {
xb -= 0x8000000000; // adjust for overflow in the extension
mb += (sb? -1 : 1); // adjust B for carry from extension
}
mb = (sb ? -mb : mb) + (sa ? -ma : ma); // compute the mantissa
if (mb == 0) { // if the mantissa is zero...
this.B = 0; // the whole result is zero, and we're done
} else { // otherwise, determine the resulting sign
if (mb > 0) { // if positive...
sb = 0; // reset the B sign bit
} else { // if negative...
sb = 1; // set the B sign bit
mb = -mb; // negate the B mantissa
if (xb) { // if non-zero octades have been shifted into X (and ONLY if... learned THAT the hard way...)
xb = 0x8000000000 - xb; // negate the extension in X
mb--; // and adjust for borrow into X
}
}
// Normalize and round as necessary
if (mb < 0x1000000000 && xx >= 0x800000000) { // Normalization can be required for subtract
this.cycleCount++;
d = (xx - xx%0x1000000000)/0x1000000000; // get the rounding digit from X
xx = (xx%0x1000000000)*8; // shift B and X left together
mb = mb*8 + d;
eb--;
d = (xx - xx%0x1000000000)/0x1000000000; // get the next rounding digit from X
if (mb < 0x1000000000) { // Normalization can be required for subtract
if (xb < 0x800000000) { // if first two octades in X < @04 then
d = 0; // no rounding will take place
} else {
this.cycleCount++;
d = (xb - xb%0x1000000000)/0x1000000000; // get the rounding digit from X
xb = (xb%0x1000000000)*8; // shift B and X left together
mb = mb*8 + d;
eb--;
d = (xb - xb%0x1000000000)/0x1000000000; // get the next rounding digit from X
}
} else if (mb >= 0x8000000000) { // Scaling can be required for add
this.cycleCount++;
d = mb % 8; // get the rounding digit from B
@@ -1665,10 +1703,10 @@ B5500Processor.prototype.singlePrecisionAdd = function singlePrecisionAdd(adding
this.cc.signalInterrupt();
}
} else if (eb < 0) {
eb = (-eb) | 0x40; // set the exponent sign bit
eb = (-eb) | 0x40; // set the exponent sign bit
}
this.X = xx; // for display purposes only
this.X = xb; // for display purposes only
this.B = (sb*128 + eb)*0x8000000000 + mb; // Final Answer
}
}
@@ -1685,7 +1723,7 @@ B5500Processor.prototype.singlePrecisionMultiply = function singlePrecisionMulti
var ma; // absolute mantissa of A
var mb; // absolute mantissa of B
var mx = 0; // local copy of X for product extension
var n = 0; // local copy of N (octade counter)
var n; // local copy of N (octade counter)
var sa; // mantissa sign of A (0=positive)
var sb; // mantissa sign of B (ditto)
var xx; // local copy of X for multiplier
@@ -1734,7 +1772,7 @@ B5500Processor.prototype.singlePrecisionMultiply = function singlePrecisionMulti
mb = 0; // initialize high-order part of product
// Now we step through the 13 octades of the multiplier, developing the product
do {
for (n=0; n<13; n++) {
d = xx % 8; // extract the current multiplier digit
xx = (xx - d)/8; // shift the multiplier right one octade
@@ -1748,7 +1786,7 @@ B5500Processor.prototype.singlePrecisionMultiply = function singlePrecisionMulti
d = mb % 8; // get the low-order octade of partial product in B
mb = (mb - d)/8; // shift B right one octade
mx = mx/8 + d*0x1000000000; // shift B octade into high-order end of extension
} while (++n < 13);
} // for (n)
// Normalize the result
if (this.Q & 0x10 && mb == 0) { // if it's integer multiply (Q05F) with integer result
@@ -1770,7 +1808,7 @@ B5500Processor.prototype.singlePrecisionMultiply = function singlePrecisionMulti
this.Q &= ~(0x10); // reset Q05F
this.A = 0; // required by specs due to the way rounding addition worked
if (xx >= 0x4000000000) { // if high-order bit of remaining extension is 1
if (mx >= 0x4000000000) { // if high-order bit of remaining extension is 1
this.Q |= 0x01; // set Q01F (for display purposes only)
if (mb < 0x7FFFFFFFFF) { // if the rounding would not cause overflow
this.cycleCount++;
@@ -1788,14 +1826,16 @@ B5500Processor.prototype.singlePrecisionMultiply = function singlePrecisionMulti
this.I = (this.I & 0x0F) | 0xB0; // set I05/6/8: exponent-overflow
this.cc.signalInterrupt();
}
} else if (eb < -63) {
eb = ((-eb) % 64) | 0x40; // mod the exponent and set its sign
if (this.NCSF) {
this.I = (this.I & 0x0F) | 0xA0; // set I06/8: exponent-underflow
this.cc.signalInterrupt();
}
} else if (eb < 0) {
eb = (-eb) | 0x40; // set the exponent sign bit
if (eb >= -63) {
eb = (-eb) | 0x40; // set the exponent sign bit
} else {
eb = ((-eb) % 64) | 0x40; // mod the exponent and set its sign
if (this.NCSF) {
this.I = (this.I & 0x0F) | 0xA0;// set I06/8: exponent-underflow
this.cc.signalInterrupt();
}
}
}
this.B = (sb*128 + eb)*0x8000000000 + mb; // Final Answer
@@ -1894,14 +1934,16 @@ B5500Processor.prototype.singlePrecisionDivide = function singlePrecisionDivide(
this.I = (this.I & 0x0F) | 0xB0; // set I05/6/8: exponent-overflow
this.cc.signalInterrupt();
}
} else if (eb < -63) {
eb = ((-eb) % 64) | 0x40; // mod the exponent and set its sign
if (this.NCSF) {
this.I = (this.I & 0x0F) | 0xA0; // set I06/8: exponent-underflow
this.cc.signalInterrupt();
}
} else if (eb < 0) {
eb = (-eb) | 0x40; // set the exponent sign bit
if (eb >= -63) {
eb = (-eb) | 0x40; // set the exponent sign bit
} else {
eb = ((-eb) % 64) | 0x40; // mod the exponent and set its sign
if (this.NCSF) {
this.I = (this.I & 0x0F) | 0xA0;// set I06/8: exponent-underflow
this.cc.signalInterrupt();
}
}
}
this.B = (sb*128 + eb)*0x8000000000 + xx; // Final Answer
@@ -2123,12 +2165,11 @@ B5500Processor.prototype.doublePrecisionAdd = function doublePrecisionAdd(adding
precision contents of the top two words in the memory stack, leaving the result
in A and B. If "adding" is not true, the sign of A is complemented to accomplish
subtraction instead of addition */
var carry = 0; // overflow carry flag
var d = 0; // shifting digit between registers
var d; // shifting digit between registers
var ea; // signed exponent of A
var eb; // signed exponent of B
var ma; // absolute mantissa of A*8
var mb; // absolute mantissa of B*8
var ma; // absolute mantissa of A
var mb; // absolute mantissa of B
var sa; // mantissa sign of A (0=positive)
var sb; // mantissa sign of B (ditto)
var xa; // extended mantissa for A
@@ -2141,7 +2182,7 @@ B5500Processor.prototype.doublePrecisionAdd = function doublePrecisionAdd(adding
ma = this.A % 0x8000000000; // extract the A mantissa
xa = this.B % 0x8000000000; // extract the A mantissa extension
ea = (this.A - ma)/0x8000000000;
sa = (ea >>> 7) & 0x01;
sa = (adding ? (ea >>> 7) & 0x01 : 1-((ea >>> 7) & 0x01));
ea = (ea & 0x40 ? -(ea & 0x3F) : (ea & 0x3F));
this.AROF = this.BROF = 0; // empty the TOS registers
@@ -2210,78 +2251,49 @@ B5500Processor.prototype.doublePrecisionAdd = function doublePrecisionAdd(adding
}
// At this point, the exponents are aligned (or one of the mantissas
// is zero), so do the actual 78-bit addition as two 40-bit, signed, twos-
// complement halves. Note that computing the twos-complement of the
// extension requires a borrow from the high-order part, so the borrow
// is taken from the 40-bit twos-complement base (i.e., using 0xFFFFFFFFFF
// instead of 0x10000000000).
if (sb) { // if B negative, compute B 2s complement
this.cycleCount += 2;
xb = 0x8000000000 - xb;
mb = 0xFFFFFFFFFF - mb;
}
if (sa ^ (adding ? 0 : 1)) { // if A negative XOR subtracting, compute A 2s complement
this.cycleCount += 2;
xa = 0x8000000000 - xa;
ma = 0xFFFFFFFFFF - ma;
// is zero), so do the actual 78-bit addition.
xb = (sb ? -xb : xb) + (sa ? -xa : xa); // compute the extension
if (xb < 0) {
xb += 0x8000000000; // adjust for underflow in the extension
mb += (sb ? 1 : -1); // adjust B for borrow into extension
} else if (xb > 0x8000000000) {
xb -= 0x8000000000; // adjust for overflow in the extension
mb += (sb? -1 : 1); // adjust B for carry from extension
}
xb += xa; // add the extension parts
if (xb >= 0x8000000000) { // deal with carry out of extension part
mb++; // into high-order part
xb %= 0x8000000000;
}
mb += ma; // add the high-order parts
// Check for overflow: if the result occupies more than 40 bits, we know
// that overflow occurred; otherwise if both internal signs were positive
// and we have a twos-complement negative result, overflow occurred; otherwise
// if both internal signs were negative and we have a positive result,
// overflow occurred. Set the carry flag and adjust the result as necessary.
if (mb >= 0x10000000000) { // if result overflowed 40 bits
carry = 1; // set the carry flag
mb -= 0x8000000000; // and adjust result for the overflow
} else if (sb == (sa ^ (adding ? 0 : 1))) { // if the signs of the internal addition are the same
if (sb && mb < 0x8000000000) { // if signs were negative and result is positive
carry = 1; // overflow occurred: set carry flag
mb += 0x8000000000; // and adjust result for the overflow
} else if (!sb && mb >= 0x8000000000) { // if signs were positive and result is negative
carry = 1; // overflow occurred: set the carry flag
mb -= 0x8000000000; // and adjust result for the overflow
mb = (sb ? -mb : mb) + (sa ? -ma : ma); // compute the mantissa
if (mb > 0) { // if positive...
sb = 0; // reset the B sign bit
} else { // if negative...
sb = 1; // set the B sign bit
mb = -mb; // negate the B mantissa
if (xb) { // if the extension is non-zero
xb = 0x8000000000 - xb; // negate the extension
mb--; // and adjust for borrow into the extension
}
}
// Determine the resulting sign and decomplement as necessary
if (mb < 0x8000000000) {
sb = 0; // it's positive
} else {
sb = 1; // it's negative
this.cycleCount++;
xb = 0x8000000000 - xb;
mb = 0xFFFFFFFFFF - mb;
}
// Scale or normalize as necessary
if (carry) { // overflow occurred, so scale it in
if (mb >= 0x8000000000) { // If overflowed 39 bits, scale result
this.cycleCount++;
d = mb % 8; // get the shift digit from high-order part
mb = (mb - d)/8 + 0x1000000000; // shift right and insert the overflow bit
xb = (xb - xb%8)/8 + d*0x1000000000; // shift the extension and insert the shift digit
d = mb % 8; // get the rounding digit from B
mb = (mb - d)/8; // shift mantissa right due to overflow
xb = (xb - xb%8)/8 + d*0x1000000000; // shift extension right and insert mantissa digit
eb++;
} else {
while (mb < 0x1000000000 && mb & xb) { // Normalize
} else { // Otherwise, normalize as necessary
while (mb < 0x1000000000 && mb && xb) {
this.cycleCount++;
d = (xb - xb%0x1000000000)/0x1000000000;// get the rounding digit from X
xb = (xb%0x1000000000)*8; // shift B and X left together
d = (xb - xb%0x1000000000)/0x1000000000; // get the high-order digit from the extension
xb = (xb%0x1000000000)*8; // shift B and X left together
mb = mb*8 + d;
eb--;
}
}
if (mb == 0 && xb == 0) {
this.A = this.B = 0;
} else {
if (mb == 0 && xb == 0) { // if the mantissa is zero...
this.A = this.B = 0; // the whole result is zero, and we're done
} else { // otherwise, determine the resulting sign
// Check for exponent over/underflow
if (eb > 63) {
eb %= 64;
@@ -2289,14 +2301,16 @@ B5500Processor.prototype.doublePrecisionAdd = function doublePrecisionAdd(adding
this.I = (this.I & 0x0F) | 0xB0; // set I05/6/8: exponent-overflow
this.cc.signalInterrupt();
}
} else if (eb < -63) {
eb = ((-eb) % 64) | 0x40; // mod the exponent and set its sign
if (this.NCSF) {
this.I = (this.I & 0x0F) | 0xA0; // set I06/8: exponent-underflow
this.cc.signalInterrupt();
}
} else if (eb < 0) {
eb = (-eb) | 0x40; // set the exponent sign bit
if (eb >= -63) {
eb = (-eb) | 0x40; // set the exponent sign bit
} else {
eb = ((-eb) % 64) | 0x40; // mod the exponent and set its sign
if (this.NCSF) {
this.I = (this.I & 0x0F) | 0xA0;// set I06/8: exponent-underflow
this.cc.signalInterrupt();
}
}
}
this.X = xb; // for display purposes only
@@ -3672,7 +3686,7 @@ B5500Processor.prototype.run = function run() {
case 0x04: // 0411: RTR=Read Timer
if (!this.NCSF) { // control-state only
this.adjustAEmpty();
this.A = this.cc.CCI03F*64 + this.cc.TM;
this.A = this.cc.readTimer();
this.AROF = 1;
}
break;
@@ -4561,10 +4575,9 @@ B5500Processor.prototype.schedule = function schedule() {
this routine will reschedule itself after an appropriate delay, thereby
throttling the performance and allowing other modules a chance at the
Javascript execution thread */
var clockOff; // ending time for this run() call, ms
var delayTime; // delay until next run() for this processor, ms
var elapsedTime; // elapsed real time for this run() call, ms
var startTime = new Date().getTime(); // starting time for this run() call, ms
var stopTime; // ending time for this run() call, ms
var runTime = this.procTime; // real-world processor running time, ms
this.scheduler = null;
this.cycleLimit = B5500Processor.timeSlice;
@@ -4572,23 +4585,34 @@ B5500Processor.prototype.schedule = function schedule() {
this.run(); // execute syllables for the timeslice
stopTime = new Date().getTime();
elapsedTime = stopTime - startTime;
this.procTime += elapsedTime;
clockOff = new Date().getTime();
while (runTime < 0) {
runTime += clockOff;
}
this.totalCycles += this.cycleCount;
if (this.busy) {
delayTime = this.cycleCount/1000 - elapsedTime;
this.procSlack += delayTime;
this.scheduler = setTimeout(this.boundSchedule, (delayTime > 0 ? delayTime : 1));
// delayTime is the number of milliseconds the processor is running ahead of
// real-world time. Web browsers have a certain minimum delay. If the delay
// is less than that, we yield to the event loop, but otherwise continue (real
// time should eventually catch up -- we hope)
delayTime = this.totalCycles/1000 - runTime;
if (delayTime < B5500Processor.minDelay) {
setImmediate(this.boundSchedule); // just execute pending events
} else {
if (delayTime > B5500Processor.maxDelay) {
delayTime = B5500Processor.maxDelay;
}
this.procSlack += delayTime;
this.scheduler = setTimeout(this.boundSchedule, delayTime);
}
}
};
/**************************************/
B5500Processor.prototype.step = function step() {
/* Single-steps the processor. Normally this will cause one instruction to
be executed, but note that in case of an interrupt, one or two injected
instructions (e.g., SFI followed by ITI or char-mode CRF followed by lots of
things) could also be executed */
be executed, but note that in the case of an interrupt or char-mode CRF, one
or two injected instructions (e.g., SFI followed by ITI) could also be executed */
this.cycleLimit = 1;
this.cycleCount = 0;

View File

@@ -186,7 +186,7 @@ B5500CardPunch.prototype.punchOnload = function punchOnload() {
this.stacker1Frame = this.$$("CPStacker1Frame");
this.stacker1Frame.contentDocument.head.innerHTML += "<style>" +
"BODY {background-color: #F0DCB0; margin: 2px} " +
"BODY {background-color: #F7E7CE; margin: 2px} " +
"PRE {margin: 0; font-size: 9pt; font-family: Lucida Sans Typewriter, Courier New, Courier, monospace}" +
"</style>";
this.stacker1 = this.doc.createElement("pre");
@@ -196,7 +196,7 @@ B5500CardPunch.prototype.punchOnload = function punchOnload() {
this.stacker2Frame = this.$$("CPStacker2Frame");
this.stacker2Frame.contentDocument.head.innerHTML += "<style>" +
"BODY {background-color: #F0DCB0; margin: 2px} " +
"BODY {background-color: #F7E7CE; margin: 2px} " +
"PRE {margin: 0; font-size: 9pt; font-family: Lucida Sans Typewriter, Courier New, Courier, monospace}" +
"</style>";
this.stacker2 = this.doc.createElement("pre");

View File

@@ -46,7 +46,7 @@ function B5500CardReader(mnemonic, unitIndex, designate, statusChange, signal) {
B5500CardReader.prototype.eolRex = /([^\n\r\f]*)((:?\r[\n\f]?)|\n|\f)?/g;
B5500CardReader.prototype.cardsPerMinute = 800;
B5500CardReader.prototype.cardsPerMinute = 1400; // B129 card reader
B5500CardReader.prototype.cardFilter = [ // Filter ASCII character values to valid BIC ones
0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F, // 00-0F
@@ -193,10 +193,19 @@ B5500CardReader.prototype.fileSelector_onChange = function fileSelector_onChange
function fileLoader_onLoad(ev) {
/* Handle the onload event for a Text FileReader */
if (that.bufIndex < that.bufLength) {
that.buffer = that.buffer.substring(that.bufIndex) + ev.target.result;
} else {
if (that.bufIndex >= that.bufLength) {
that.buffer = ev.target.result;
} else {
switch (that.buffer.charAt(that.buffer.length-1)) {
case "\r":
case "\n":
case "\f":
break; // do nothing -- the last card has a delimiter
default:
that.buffer += "\n"; // so the next deck starts on a new line
break;
}
that.buffer = that.buffer.substring(that.bufIndex) + ev.target.result;
}
that.bufIndex = 0;
@@ -215,6 +224,7 @@ B5500CardReader.prototype.readCardAlpha = function readCardAlpha(buffer, length)
image as necessary to the I/O buffer length. Invalid BCL characters are
translated to ASCII "?" and the invalid character bit is set in the errorMask.
Returns the raw card image as a string */
var c; // current character
var card; // card image
var cardLength; // length of card image
var match; // result of eolRex.exec()
@@ -233,7 +243,12 @@ B5500CardReader.prototype.readCardAlpha = function readCardAlpha(buffer, length)
cardLength = length;
}
for (x=0; x<cardLength; x++) {
if ((buffer[x] = this.cardFilter[card.charCodeAt(x) & 0x7F]) == 0x3F) { // intentional assignment
c = card.charCodeAt(x);
if (c == 0x3F && x > 0) { // an actual "?"
buffer[x] = 0x3F;
} else if (c > 0x7F) { // Unicode R Us -- NOT!
this.errorMask |= 0x08;
} else if ((buffer[x] = this.cardFilter[c]) == 0x3F) { // intentional assignment
this.errorMask |= 0x08;
}
}
@@ -296,7 +311,7 @@ B5500CardReader.prototype.readerOnload = function readerOnload() {
this.outHopperFrame = this.$$("CROutHopperFrame");
this.outHopperFrame.contentDocument.head.innerHTML += "<style>" +
"BODY {background-color: #F0DCB0; margin: 2px} " +
"BODY {background-color: #F7E7CE; margin: 2px} " +
"PRE {margin: 0; font-size: 9pt; font-family: Lucida Sans Typewriter, Courier New, Courier, monospace}" +
"</style>";
this.outHopper = this.doc.createElement("pre");

View File

@@ -1742,7 +1742,7 @@ window.onload = function() {
// 41: initialize date @ H/L
// 40: initialize time @ H/L
// 39: use only one breakout tape
// 38: automatically print pbt
pow2[47-36] + // 38: automatically print pbt
pow2[47-37] + // 37: clear write ready status @ terminal
pow2[47-36] + // 36: write disc. code on terminal
pow2[47-35] + // 35: type when compiler files open & close

View File

@@ -7,6 +7,8 @@
<meta http-equiv="Content-Style-Type" content="text/css">
<link id=defaultStyleSheet rel=stylesheet type="text/css" href="B5500Console.css">
<script src="./setImmediate.js"></script>
<script src="./B5500DummyUnit.js"></script>
<script src="./B5500SPOUnit.js"></script>
<script src="./B5500DiskUnit.js"></script>
@@ -175,8 +177,11 @@ window.addEventListener("load", function() {
aControl.className = "yellowButton yellowLit";
}
}
et = new Date().getTime() - pa.procStart;
procRate.innerHTML = (pa.procTime/et*100).toFixed(1) + "%";
et = pa.procTime;
while (et < 0) {
et += new Date().getTime();
}
procRate.innerHTML = (pa.totalCycles/1000/et*100).toFixed(1) + "%";
procSlack.innerHTML = (pa.procSlack/et*100).toFixed(1) + "%";
}
}

View File

@@ -43,7 +43,7 @@ function B5500DummyPrinter(mnemonic, unitIndex, designate, statusChange, signal)
that.printerOnload();
}, false);
}
B5500DummyPrinter.prototype.linesPerMinute = 800; // Printer speed
B5500DummyPrinter.prototype.linesPerMinute = 1040; // B329 line printer
B5500DummyPrinter.maxScrollLines = 150000; // Maximum printer scrollback (about a box of paper)
@@ -60,10 +60,36 @@ B5500DummyPrinter.prototype.clear = function clear() {
this.busy = false; // busy status
this.activeIOUnit = 0; // I/O unit currently using this device
this.lastClickStamp = 0; // last click time for double-click detection
this.errorMask = 0; // error mask for finish()
this.finish = null; // external function to call for I/O completion
};
/**************************************/
B5500SPOUnit.prototype.tearPaper = function tearPaper(ev) {
/* Handles an event to clear the "paper" from the printer */
if (confirm("Do you want to clear the \"paper\" from the printer?")) {
while (this.paper.firstChild) {
this.paper.removeChild(this.paper.firstChild);
}
}
};
/**************************************/
B5500DummyPrinter.prototype.appendLine = function appendLine(text) {
/* Removes excess lines already printed, then appends a new <pre> element
to the <iframe>, creating an empty text node inside the new element */
var count = this.paper.childNodes.length;
var line = this.doc.createTextNode(text || "");
while (count-- > this.maxScrollLines) {
this.paper.removeChild(this.paper.firstChild);
}
this.paper.appendChild(line);
};
/**************************************/
B5500DummyPrinter.prototype.printerOnload = function printerOnload() {
/* Initializes the line printer window and user interface */
@@ -79,22 +105,21 @@ B5500DummyPrinter.prototype.printerOnload = function printerOnload() {
this.window.moveTo(40, 40);
this.window.resizeTo(1000, screen.availHeight*0.80);
this.window.addEventListener("click", function(ev) {
var stamp = new Date().getTime();
// a kludge, but this is the DUMMY printer....
if (stamp - this.lastClickStamp < 500) {
that.ripPaper(ev);
that.lastClickStamp = 0; // no triple-clicking allowed
} else {
that.lastClickStamp = stamp;
}
}, false);
this.statusChange(1);
};
/**************************************/
B5500DummyPrinter.prototype.appendLine = function appendLine(text) {
/* Removes excess lines already printed, then appends a new <pre> element
to the <iframe>, creating an empty text node inside the new element */
var count = this.paper.childNodes.length;
var line = this.doc.createTextNode(text || "");
while (count-- > this.maxScrollLines) {
this.paper.removeChild(this.paper.firstChild);
}
this.paper.appendChild(line);
};
/**************************************/
B5500DummyPrinter.prototype.read = function read(finish, buffer, length, mode, control) {
/* Initiates a read operation on the unit */

View File

@@ -24,7 +24,7 @@ PRE {
DIV#SPODiv {
position: relative;
width: 800px;
background-color: #F0D0A0;
background-color: #F7E7CE;
border-radius: 32px;
padding: 2em;
text-align: left}

View File

@@ -187,6 +187,7 @@ B5500SPOUnit.prototype.appendEmptyLine = function appendEmptyLine() {
while (count-- > this.maxScrollLines) {
this.paper.removeChild(this.paper.firstChild);
}
this.endOfPaper.scrollIntoView();
this.paper.appendChild(line);
};
@@ -216,7 +217,6 @@ B5500SPOUnit.prototype.printChar = function printChar(c) {
if (len < 1) {
line.nodeValue = String.fromCharCode(c);
that.endOfPaper.scrollIntoView();
} else if (len < 72) {
line.nodeValue += String.fromCharCode(c);
} else {
@@ -247,7 +247,7 @@ B5500SPOUnit.prototype.outputChar = function outputChar() {
that.printCol = 72;
setTimeout(that.outputChar, delay);
}
} else if (that.printCol == 72) { // delay to fake the output of a new-line
} else if (that.printCol == 72) { // delay to fake the output of a carriage-return
that.printCol++;
setTimeout(that.outputChar, delay+that.charPeriod);
} else { // actually output the CR/LF
@@ -355,44 +355,49 @@ B5500SPOUnit.prototype.keyDown = function keyDown(ev) {
} else {
nextTime = stamp + this.charPeriod;
}
this.nextCharTime = nextTime;
if (this.spoState == this.spoRemote) {
if (c == 27) {
if (that.spoState == that.spoRemote) {
that.signal();
} else if (that.spoState == that.spoOutput) {
that.signal();
}
switch (c) {
case 27: // ESC
switch (this.spoState) {
case this.spoRemote:
case this.spoOutput:
this.signal();
result = false;
}
} else if (this.spoState == this.spoInput) {
switch (c) {
case 27: // ESC
break;
case this.spoInput:
this.cancelInput();
result = false;
break;
case 8: // Backspace
}
break;
case 8: // Backspace
switch (this.spoState) {
case this.spoInput:
case this.spoLocal:
setTimeout(this.backspaceChar, nextTime-stamp);
this.nextCharTime = nextTime;
result = false;
break;
case 13: // Enter
}
break;
case 13: // Enter
switch (this.spoState) {
case this.spoInput:
this.terminateInput();
this.nextCharTime = nextTime;
result = false;
break
case this.spoLocal:
setTimeout(function() {
that.appendEmptyLine();
}, nextTime-stamp+this.charPeriod);
this.nextCharTime = nextTime;
result = false;
break;
}
} else if (this.spoState == this.spoLocal) {
switch (c) {
case 8: // Backspace
setTimeout(that.backspaceChar, nextTime-stamp);
result = false;
break;
case 13: // Enter
setTimeout(function() {that.appendEmptyLine()}, nextTime-stamp+this.charPeriod);
result = false;
break;
}
break;
}
if (!result) {
ev.preventDefault();
}
@@ -432,7 +437,7 @@ B5500SPOUnit.prototype.spoOnload = function spoOnload() {
this.paper.appendChild(this.doc.createTextNode(""));
this.$$("SPOUT").contentDocument.body.appendChild(this.paper);
this.endOfPaper = this.doc.createElement("div");
//this.endOfPaper.appendChild(this.doc.createTextNode("\xA0"));
this.endOfPaper.appendChild(this.doc.createTextNode("\xA0"));
this.$$("SPOUT").contentDocument.body.appendChild(this.endOfPaper);
this.$$("SPOUT").contentDocument.head.innerHTML += "<style>" +
"BODY {background-color: #FFE} " +
@@ -444,43 +449,43 @@ B5500SPOUnit.prototype.spoOnload = function spoOnload() {
this.window.moveTo(0, screen.availHeight-this.window.outerHeight);
this.window.focus();
this.$$("SPORemoteBtn").onclick = function() {
this.$$("SPORemoteBtn").addEventListener("click", function() {
that.setRemote();
};
}, false);
this.$$("SPOPowerBtn").onclick = function() {
this.$$("SPOPowerBtn").addEventListener("click", function() {
that.window.alert("Don't DO THAT");
};
}, false);
this.$$("SPOLocalBtn").onclick = function() {
this.$$("SPOLocalBtn").addEventListener("click", function() {
that.setLocal();
};
}, false);
this.$$("SPOInputRequestBtn").onclick = function() {
this.$$("SPOInputRequestBtn").addEventListener("click", function() {
if (that.spoState == that.spoRemote) {
that.signal();
} else if (that.spoState == that.spoOutput) {
that.signal();
}
};
}, false);
this.$$("SPOErrorBtn").onclick = function() {
this.$$("SPOErrorBtn").addEventListener("click", function() {
that.cancelInput();
};
}, false);
this.$$("SPOEndOfMessageBtn").onclick = function() {
this.$$("SPOEndOfMessageBtn").addEventListener("click", function() {
that.terminateInput();
};
}, false);
this.window.onkeypress = function(ev) {
this.window.addEventListener("keypress", function(ev) {
if (ev.keyCode == 191) {ev.preventDefault()};
that.keyPress(ev);
};
}, false);
this.window.onkeydown = function(ev) {
this.window.addEventListener("keydown", function(ev) {
if (ev.keyCode == 191) {ev.preventDefault()};
that.keyDown(ev);
};
}, false);
for (x=0; x<32; x++) {
this.appendEmptyLine();

View File

@@ -7,17 +7,19 @@
<meta http-equiv="Content-Style-Type" content="text/css">
<link id=defaultStyleSheet rel=stylesheet type="text/css" href="B5500SyllableDebugger.css">
<script src="/B5500/webUI/B5500DummyUnit.js"></script>
<script src="/B5500/webUI/B5500SPOUnit.js"></script>
<script src="/B5500/webUI/B5500DiskUnit.js"></script>
<script src="/B5500/webUI/B5500CardReader.js"></script>
<script src="/B5500/webUI/B5500CardPunch.js"></script>
<script src="/B5500/webUI/B5500DummyPrinter.js"></script>
<script src="./setImmediate.js"></script>
<script src="/B5500/emulator/B5500SystemConfiguration.js"></script>
<script src="/B5500/emulator/B5500CentralControl.js"></script>
<script src="/B5500/emulator/B5500Processor.js"></script>
<script src="/B5500/emulator/B5500IOUnit.js"></script>
<script src="./B5500DummyUnit.js"></script>
<script src="./B5500SPOUnit.js"></script>
<script src="./B5500DiskUnit.js"></script>
<script src="./B5500CardReader.js"></script>
<script src="./B5500CardPunch.js"></script>
<script src="./B5500DummyPrinter.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>
"use strict";
@@ -880,6 +882,7 @@ function hardwareLoad_onClick(ev) {
cc.P1.clear();
cc.cardLoadSelect = $$("CardLoadSelect").checked;
cc.load(true);
displaySystemState();
}
function checkBrowser() {

244
webUI/setImmediate.js Normal file
View File

@@ -0,0 +1,244 @@
// Domenic Denicola's implementation of the proposed setImmediate() API.
// Downloaded 2013-06-30 from https://github.com/NobleJS/setImmediate.
/* License:
Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, and Domenic Denicola
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
(function (global, undefined) {
"use strict";
var tasks = (function () {
function Task(handler, args) {
this.handler = handler;
this.args = args;
}
Task.prototype.run = function () {
// See steps in section 5 of the spec.
if (typeof this.handler === "function") {
// Choice of `thisArg` is not in the setImmediate spec; `undefined` is in the setTimeout spec though:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html
this.handler.apply(undefined, this.args);
} else {
var scriptSource = "" + this.handler;
/*jshint evil: true */
eval(scriptSource);
}
};
var nextHandle = 1; // Spec says greater than zero
var tasksByHandle = {};
var currentlyRunningATask = false;
return {
addFromSetImmediateArguments: function (args) {
var handler = args[0];
var argsToHandle = Array.prototype.slice.call(args, 1);
var task = new Task(handler, argsToHandle);
var thisHandle = nextHandle++;
tasksByHandle[thisHandle] = task;
return thisHandle;
},
runIfPresent: function (handle) {
// From the spec: "Wait until any invocations of this algorithm started before this one have completed."
// So if we're currently running a task, we'll need to delay this invocation.
if (!currentlyRunningATask) {
var task = tasksByHandle[handle];
if (task) {
currentlyRunningATask = true;
try {
task.run();
} finally {
delete tasksByHandle[handle];
currentlyRunningATask = false;
}
}
} else {
// Delay by doing a setTimeout. setImmediate was tried instead, but in Firefox 7 it generated a
// "too much recursion" error.
global.setTimeout(function () {
tasks.runIfPresent(handle);
}, 0);
}
},
remove: function (handle) {
delete tasksByHandle[handle];
}
};
}());
function canUseNextTick() {
// Don't get fooled by e.g. browserify environments.
return typeof process === "object" &&
Object.prototype.toString.call(process) === "[object process]";
}
function canUseMessageChannel() {
return !!global.MessageChannel;
}
function canUsePostMessage() {
// The test against `importScripts` prevents this implementation from being installed inside a web worker,
// where `global.postMessage` means something completely different and can't be used for this purpose.
if (!global.postMessage || global.importScripts) {
return false;
}
var postMessageIsAsynchronous = true;
var oldOnMessage = global.onmessage;
global.onmessage = function () {
postMessageIsAsynchronous = false;
};
global.postMessage("", "*");
global.onmessage = oldOnMessage;
return postMessageIsAsynchronous;
}
function canUseReadyStateChange() {
return "document" in global && "onreadystatechange" in global.document.createElement("script");
}
function installNextTickImplementation(attachTo) {
attachTo.setImmediate = function () {
var handle = tasks.addFromSetImmediateArguments(arguments);
process.nextTick(function () {
tasks.runIfPresent(handle);
});
return handle;
};
}
function installMessageChannelImplementation(attachTo) {
var channel = new global.MessageChannel();
channel.port1.onmessage = function (event) {
var handle = event.data;
tasks.runIfPresent(handle);
};
attachTo.setImmediate = function () {
var handle = tasks.addFromSetImmediateArguments(arguments);
channel.port2.postMessage(handle);
return handle;
};
}
function installPostMessageImplementation(attachTo) {
// Installs an event handler on `global` for the `message` event: see
// * https://developer.mozilla.org/en/DOM/window.postMessage
// * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages
var MESSAGE_PREFIX = "com.bn.NobleJS.setImmediate" + Math.random();
function isStringAndStartsWith(string, putativeStart) {
return typeof string === "string" && string.substring(0, putativeStart.length) === putativeStart;
}
function onGlobalMessage(event) {
// This will catch all incoming messages (even from other windows!), so we need to try reasonably hard to
// avoid letting anyone else trick us into firing off. We test the origin is still this window, and that a
// (randomly generated) unpredictable identifying prefix is present.
if (event.source === global && isStringAndStartsWith(event.data, MESSAGE_PREFIX)) {
var handle = event.data.substring(MESSAGE_PREFIX.length);
tasks.runIfPresent(handle);
}
}
if (global.addEventListener) {
global.addEventListener("message", onGlobalMessage, false);
} else {
global.attachEvent("onmessage", onGlobalMessage);
}
attachTo.setImmediate = function () {
var handle = tasks.addFromSetImmediateArguments(arguments);
// Make `global` post a message to itself with the handle and identifying prefix, thus asynchronously
// invoking our onGlobalMessage listener above.
global.postMessage(MESSAGE_PREFIX + handle, "*");
return handle;
};
}
function installReadyStateChangeImplementation(attachTo) {
attachTo.setImmediate = function () {
var handle = tasks.addFromSetImmediateArguments(arguments);
// Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
// into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
var scriptEl = global.document.createElement("script");
scriptEl.onreadystatechange = function () {
tasks.runIfPresent(handle);
scriptEl.onreadystatechange = null;
scriptEl.parentNode.removeChild(scriptEl);
scriptEl = null;
};
global.document.documentElement.appendChild(scriptEl);
return handle;
};
}
function installSetTimeoutImplementation(attachTo) {
attachTo.setImmediate = function () {
var handle = tasks.addFromSetImmediateArguments(arguments);
global.setTimeout(function () {
tasks.runIfPresent(handle);
}, 0);
return handle;
};
}
if (!global.setImmediate) {
// If supported, we should attach to the prototype of global, since that is where setTimeout et al. live.
var attachTo = typeof Object.getPrototypeOf === "function" && "setTimeout" in Object.getPrototypeOf(global) ?
Object.getPrototypeOf(global)
: global;
if (canUseNextTick()) {
// For Node.js before 0.9
installNextTickImplementation(attachTo);
} else if (canUsePostMessage()) {
// For non-IE10 modern browsers
installPostMessageImplementation(attachTo);
} else if (canUseMessageChannel()) {
// For web workers, where supported
installMessageChannelImplementation(attachTo);
} else if (canUseReadyStateChange()) {
// For IE 68
installReadyStateChangeImplementation(attachTo);
} else {
// For older browsers
installSetTimeoutImplementation(attachTo);
}
attachTo.clearImmediate = tasks.remove;
}
}(typeof global === "object" && global ? global : this));