mirror of
https://github.com/pkimpel/retro-b5500.git
synced 2026-05-03 06:39:44 +00:00
1. Release emulator version 0.13 (finally).
2. Implement new setCallback() mechanism to wrap setTimeout() and setImmediate(). 2. Minor change to P2 management; remove context-bound callbacks. 3. Reduce window size and font size for peripheral UIs. 4. Implement <label> for file names on ColdLoader Load table. 5. Correct placement and color of buttons on Console and SPO. 6. Minor wiki updates for UI changes. 7. Rework SPO implementation to eliminate ".that" properties, implement setCallback(), and fix long-standing bugs that caused flaky operation.
This commit is contained in:
@@ -50,7 +50,7 @@ function B5500CentralControl() {
|
||||
this.cardLoadSelect = 0; // 0=> load from disk/drum; 1=> load from cards
|
||||
|
||||
this.nextTimeStamp = 0; // Next actual Date.getTime() for timer tick
|
||||
this.timer = null; // Reference to the RTC setTimeout id.
|
||||
this.timer = null; // Reference to the RTC setCallback id.
|
||||
|
||||
// Establish contexts for asynchronously-called methods
|
||||
this.boundTock = B5500CentralControl.bindMethod(this.tock, this);
|
||||
@@ -61,11 +61,10 @@ function B5500CentralControl() {
|
||||
/**************************************/
|
||||
/* Global constants */
|
||||
|
||||
B5500CentralControl.version = "0.12";
|
||||
B5500CentralControl.version = "0.13";
|
||||
|
||||
B5500CentralControl.memReadCycles = 2; // assume 2 µs memory read cycle time (the other option was 3 µs)
|
||||
B5500CentralControl.memWriteCycles = 4; // assume 4 µs memory write cycle time (the other option was 6 µs)
|
||||
B5500CentralControl.minDelay = 4; // minimum setTimeout() delay, ms
|
||||
B5500CentralControl.rtcTick = 1000/60; // Real-time clock period, milliseconds
|
||||
|
||||
B5500CentralControl.pow2 = [ // powers of 2 from 0 to 52
|
||||
@@ -105,7 +104,7 @@ B5500CentralControl.mask2 = [ // (2**n)-1 For n From 0 to 52
|
||||
// which is why they are in the range 17..47. The [0] dimension determines the index
|
||||
// when writing; the [1] dimension determines the index when reading. This approach
|
||||
// is necessary since some unit designates map to two different devices depending
|
||||
// on IOD.[24:1], e.g. designate 14=CPA/CRA (status bits 23/24).
|
||||
// on the read bit in IOD.[24:1], e.g. designate 14=CPA/CRA (status bits 23/24).
|
||||
|
||||
B5500CentralControl.unitIndex = [
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
||||
@@ -166,7 +165,7 @@ B5500CentralControl.prototype.clear = function clear() {
|
||||
real-time clock */
|
||||
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
clearCallback(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
@@ -572,12 +571,7 @@ B5500CentralControl.prototype.tock = function tock() {
|
||||
}
|
||||
}
|
||||
interval = (this.nextTimeStamp += B5500CentralControl.rtcTick) - thisTime;
|
||||
if (interval >= B5500CentralControl.minDelay) {
|
||||
this.timer = setTimeout(this.boundTock, interval);
|
||||
} else {
|
||||
this.timer = null;
|
||||
setImmediate(this.boundTock);
|
||||
}
|
||||
this.timer = setCallback(this.boundTock, this, interval);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
@@ -734,7 +728,7 @@ B5500CentralControl.prototype.halt = function halt() {
|
||||
/* Halts the processors. Any in-process I/Os are allowed to complete */
|
||||
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
clearCallback(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
@@ -1014,13 +1008,12 @@ B5500CentralControl.prototype.powerOn = function powerOn() {
|
||||
B5500CentralControl.prototype.powerOff = function powerOff() {
|
||||
/* Powers down the system and deallocates the hardware modules.
|
||||
Redundant power-offs are ignored. */
|
||||
var that = this;
|
||||
|
||||
function shutDown() {
|
||||
var x;
|
||||
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
clearCallback(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
@@ -1050,8 +1043,6 @@ B5500CentralControl.prototype.powerOff = function powerOff() {
|
||||
if (this.poweredUp) {
|
||||
this.halt();
|
||||
// Wait a little while for I/Os, etc., to finish
|
||||
setTimeout(function powerOffAnon() {
|
||||
shutDown.call(that);
|
||||
}, 1000)
|
||||
setCallback(shutDown, this, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -45,7 +45,7 @@ function B5500IOUnit(ioUnitID, cc) {
|
||||
this.ioUnitID = ioUnitID; // I/O Unit ID ("1", "2", "3", or "4")
|
||||
this.cc = cc; // Reference back to Central Control module
|
||||
|
||||
this.forkHandle = null; // Reference to current setImmediate id
|
||||
this.forkHandle = null; // Reference to current setCallback id
|
||||
this.accessor = { // Memory access control block
|
||||
requestorID: ioUnitID, // Memory requestor ID
|
||||
addr: 0, // Memory address
|
||||
@@ -181,7 +181,7 @@ B5500IOUnit.prototype.clear = function clear() {
|
||||
this.ioUnitSlack = 0; // Total I/O Unit throttling delay, milliseconds
|
||||
|
||||
if (this.forkHandle) {
|
||||
clearImmediate(this.forkHandle);
|
||||
clearCallback(this.forkHandle);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -733,7 +733,7 @@ B5500IOUnit.prototype.forkIO = function forkIO() {
|
||||
var u; // peripheral unit object
|
||||
var x; // temp number variable
|
||||
|
||||
this.forkHandle = null; // clear the setImmediate() handle
|
||||
this.forkHandle = null; // clear the setCallback() handle
|
||||
|
||||
x = this.D; // explode the D-register into its fields
|
||||
this.Dunit = (x%0x200000000000 - x%0x10000000000)/0x10000000000; // [3:5]
|
||||
@@ -863,7 +863,7 @@ B5500IOUnit.prototype.initiate = function initiate() {
|
||||
} else {
|
||||
this.D31F = 0; // reset the IOD-fetch error condition
|
||||
this.D = this.W;
|
||||
this.forkHandle = setImmediate(this.boundForkIO);
|
||||
this.forkHandle = setCallback(this.boundForkIO, this, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -34,8 +34,7 @@ function B5500Processor(procID, cc) {
|
||||
|
||||
this.processorID = procID; // Processor ID ("A" or "B")
|
||||
this.cc = cc; // Reference back to Central Control module
|
||||
this.schedImmediate = null; // Reference to current setImmediate token
|
||||
this.schedTimeout = null; // Reference to current setTimeout token
|
||||
this.scheduler = null; // Reference to current setCallback token
|
||||
this.accessor = { // Memory access control block
|
||||
requestorID: procID, // Memory requestor ID
|
||||
addr: 0, // Memory address
|
||||
@@ -45,18 +44,16 @@ function B5500Processor(procID, cc) {
|
||||
MAED: 0 // Truthy if memory address/inhibit error
|
||||
};
|
||||
|
||||
// Establish context for asynchronously-called methods
|
||||
this.boundSchedule = B5500CentralControl.bindMethod(this.schedule, this);
|
||||
|
||||
this.clear(); // Create and initialize the processor state
|
||||
|
||||
this.delayDeltaAvg = 0; // Average difference between requested and actual setTimeout() delays, ms
|
||||
this.delayLastStamp = 0; // Timestamp of last setTimeout() delay, ms
|
||||
this.delayRequested = 0; // Last requested setTimeout() delay, ms
|
||||
this.delayDeltaAvg = 0; // Average difference between requested and actual setCallback() delays, ms
|
||||
this.delayLastStamp = 0; // Timestamp of last setCallback() delay, ms
|
||||
this.delayRequested = 0; // Last requested setCallback() delay, ms
|
||||
}
|
||||
|
||||
/**************************************/
|
||||
|
||||
B5500Processor.cyclesPerMilli = 1000; // clock cycles per millisecond (1000 = 1.0 MHz)
|
||||
B5500Processor.timeSlice = 4000; // this.run() timeslice, clocks
|
||||
B5500Processor.delaySamples = 1000; // this.delayDeltaAvg sampling average basis
|
||||
|
||||
@@ -1294,7 +1291,6 @@ B5500Processor.prototype.storeForInterrupt = function storeForInterrupt(forTest)
|
||||
this.T = 0x89; // inject 0211=ITI into T register
|
||||
} else {
|
||||
this.stop(); // idle the processor
|
||||
this.cc.HP2F = 1;
|
||||
this.cc.P2BF = 0; // tell P1 we've stopped
|
||||
}
|
||||
this.CWMF = 0;
|
||||
@@ -1311,7 +1307,6 @@ B5500Processor.prototype.storeForInterrupt = function storeForInterrupt(forTest)
|
||||
this.V = 0;
|
||||
} else {
|
||||
this.stop(); // idle the processor
|
||||
this.cc.HP2F = 1;
|
||||
this.cc.P2BF = 0; // tell P1 we've stopped
|
||||
}
|
||||
}
|
||||
@@ -1341,7 +1336,7 @@ B5500Processor.prototype.start = function start() {
|
||||
this.procTime -= stamp;
|
||||
this.delayLastStamp = stamp;
|
||||
this.delayRequested = 0;
|
||||
this.schedImmediate = setImmediate(this.boundSchedule, 1);
|
||||
this.scheduler = setCallback(this.schedule, this, 0);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
@@ -1354,13 +1349,9 @@ B5500Processor.prototype.stop = function stop() {
|
||||
this.PROF = 0;
|
||||
this.busy = 0;
|
||||
this.cycleLimit = 0; // exit this.run()
|
||||
if (this.schedImmediate) {
|
||||
clearImmediate(this.schedImmediate);
|
||||
this.schedImmediate = null;
|
||||
}
|
||||
if (this.schedTimeout) {
|
||||
clearTimeout(this.schedTimeout);
|
||||
this.schedTimeout = null;
|
||||
if (this.scheduler) {
|
||||
clearCallback(this.scheduler);
|
||||
this.scheduler = null;
|
||||
}
|
||||
while (this.procTime < 0) {
|
||||
this.procTime += stamp;
|
||||
@@ -1463,7 +1454,9 @@ B5500Processor.prototype.initiate = function initiate(forTest) {
|
||||
|
||||
this.T = this.cc.fieldIsolate(this.P, this.L*12, 12);
|
||||
this.TROF = 1;
|
||||
if (forTest) {
|
||||
if (!forTest) {
|
||||
this.NCSF = 1;
|
||||
} else {
|
||||
this.NCSF = (this.TM >>> 4) & 0x01;
|
||||
this.CCCF = (this.TM >>> 5) & 0x01;
|
||||
this.MWOF = (this.TM >>> 6) & 0x01;
|
||||
@@ -1472,9 +1465,6 @@ B5500Processor.prototype.initiate = function initiate(forTest) {
|
||||
if (!this.CCCF) {
|
||||
this.TM |= 0x80;
|
||||
}
|
||||
} else {
|
||||
this.NCSF = 1;
|
||||
this.busy = 1;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1483,12 +1473,12 @@ B5500Processor.prototype.initiateAsP2 = function initiateAsP2() {
|
||||
/* Called from Central Control to initiate the processor as P2. Fetches the
|
||||
INCW from @10 and calls initiate() */
|
||||
|
||||
this.NCSF = 0; // make sure P2 is in control state to execute the IP1 & access low mem
|
||||
this.M = 0x08; // address of the INCW
|
||||
this.loadBviaM(); // B = [M]
|
||||
this.AROF = 0; // make sure A is invalid
|
||||
this.T = 0x849; // inject 4111=IP1 into P2's T register
|
||||
this.TROF = 1;
|
||||
this.NCSF = 0; // make sure P2 is in control state to execute the IP1
|
||||
|
||||
// Now start scheduling P2 on the Javascript thread
|
||||
this.start();
|
||||
@@ -3024,7 +3014,7 @@ B5500Processor.prototype.run = function run() {
|
||||
switch (variant) {
|
||||
case 0x14: // 2411: ZPI=Conditional Halt
|
||||
if (this.US14X) { // STOP OPERATOR switch on
|
||||
this.busy = 0;
|
||||
this.stop();
|
||||
this.cycleLimit = 0; // exit this.run()
|
||||
}
|
||||
break;
|
||||
@@ -3745,12 +3735,13 @@ B5500Processor.prototype.run = function run() {
|
||||
case 0x12: // 2211: HP2=Halt Processor 2
|
||||
if (!this.NCSF) { // control-state only
|
||||
this.cc.haltP2();
|
||||
this.cycleLimit = 0; // give P2 a chance to clean up
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x14: // 2411: ZPI=Conditional Halt
|
||||
if (this.US14X) { // STOP OPERATOR switch on
|
||||
this.busy = 0;
|
||||
this.stop();
|
||||
this.cycleLimit = 0; // exit this.run()
|
||||
}
|
||||
break;
|
||||
@@ -4582,7 +4573,7 @@ B5500Processor.prototype.run = function run() {
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500Processor.prototype.schedule = function schedule(which) {
|
||||
B5500Processor.prototype.schedule = function schedule() {
|
||||
/* Schedules the processor running time and attempts to throttle performance
|
||||
to approximate that of a real B5500 -- well, at least we hope this will run
|
||||
fast enough that the performance will need to be throttled. It establishes
|
||||
@@ -4597,17 +4588,11 @@ B5500Processor.prototype.schedule = function schedule(which) {
|
||||
var delayTime; // delay from/until next run() for this processor, ms
|
||||
var runTime; // real-world processor running time, ms
|
||||
|
||||
if (which) {
|
||||
this.schedImmediate = null;
|
||||
} else {
|
||||
this.schedTimeout = null;
|
||||
}
|
||||
this.scheduler = null;
|
||||
delayTime = clockOff - this.delayLastStamp;
|
||||
this.procSlack += delayTime;
|
||||
if (this.delayRequested) {
|
||||
this.delayDeltaAvg = (this.delayDeltaAvg*(B5500Processor.delaySamples-1) +
|
||||
delayTime - this.delayRequested)/B5500Processor.delaySamples;
|
||||
}
|
||||
this.delayDeltaAvg = (this.delayDeltaAvg*(B5500Processor.delaySamples-1) +
|
||||
delayTime - this.delayRequested)/B5500Processor.delaySamples;
|
||||
|
||||
if (this.busy) {
|
||||
this.cycleLimit = B5500Processor.timeSlice;
|
||||
@@ -4625,20 +4610,15 @@ B5500Processor.prototype.schedule = function schedule(which) {
|
||||
runTime += clockOff;
|
||||
}
|
||||
|
||||
delayTime = this.totalCycles/1000 - runTime;
|
||||
delayTime = this.totalCycles/B5500Processor.cyclesPerMilli - runTime;
|
||||
// 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 our estimate of that minimum, we yield to the event loop but
|
||||
// otherwise continue (real time should eventually catch up -- we hope). If the
|
||||
// real-world time. Web browsers have a certain minimum setTimeout() delay. If the
|
||||
// delay is less than our estimate of that minimum, we yield to the event loop
|
||||
// but otherwise continue (real time should eventually catch up -- we hope). If the
|
||||
// delay is greater than the minimum, we reschedule ourselves after that delay.
|
||||
|
||||
if (delayTime < B5500CentralControl.minDelay) {
|
||||
this.delayRequested = 0;
|
||||
this.schedImmediate = setImmediate(this.boundSchedule, 1); // just yield to the event loop
|
||||
} else {
|
||||
this.delayRequested = delayTime;
|
||||
this.schedTimeout = setTimeout(this.boundSchedule, delayTime, 0);
|
||||
}
|
||||
this.delayRequested = delayTime;
|
||||
this.scheduler = setCallback(this.schedule, this, delayTime);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user