Cleaned up the run loop, less complicated now, and video updates can drive themselves thanks to the new CPU timer functions
This commit is contained in:
parent
97f8e87ca3
commit
244f0dba32
7 changed files with 451 additions and 495 deletions
|
|
@ -1471,17 +1471,17 @@ Computer8080.prototype.updateStatus = function(fForce)
|
|||
};
|
||||
|
||||
/**
|
||||
* updateVideo(n)
|
||||
* updateVideo(fForced)
|
||||
*
|
||||
* Any high-frequency updates should be performed here (avoid updating DOM elements).
|
||||
*
|
||||
* @this {Computer8080}
|
||||
* @param {number} n (where 0 <= n < VIDEO_UPDATES_PER_SECOND for a normal update, or -1 for a forced update)
|
||||
* @param {boolean} [fForced]
|
||||
*/
|
||||
Computer8080.prototype.updateVideo = function(n)
|
||||
Computer8080.prototype.updateVideo = function(fForced)
|
||||
{
|
||||
for (var i = 0; i < this.aVideo.length; i++) {
|
||||
this.aVideo[i].updateScreen(n);
|
||||
this.aVideo[i].updateScreen(fForced);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ if (NODE) {
|
|||
*
|
||||
* The CPU8080 class supports the following (parmsCPU) properties:
|
||||
*
|
||||
* cycles: the machine's base cycles per second; the CPUState8080 constructor will
|
||||
* provide us with a default (based on the CPU model) to use as a fallback.
|
||||
* cycles: the machine's base cycles per second; the CPUState8080 constructor
|
||||
* will provide us with a default (based on the CPU model) to use as a fallback.
|
||||
*
|
||||
* multiplier: base cycle multiplier; default is 1.
|
||||
*
|
||||
|
|
@ -56,8 +56,8 @@ if (NODE) {
|
|||
* checksum records; -1 if disabled. checksum records are a diagnostic aid
|
||||
* used to help compare one CPU run to another.
|
||||
*
|
||||
* csInterval: the number of cycles that runCPU() must execute before
|
||||
* generating a checksum record; -1 if disabled.
|
||||
* csInterval: the number of cycles that runCPU() must execute before generating
|
||||
* a checksum record; -1 if disabled.
|
||||
*
|
||||
* csStop: the number of cycles to stop generating checksum records.
|
||||
*
|
||||
|
|
@ -81,7 +81,6 @@ function CPU8080(parmsCPU, nCyclesDefault)
|
|||
|
||||
this.counts = {};
|
||||
this.counts.nCyclesPerSecond = nCycles;
|
||||
this.counts.nVideoUpdates = 0;
|
||||
|
||||
/*
|
||||
* nCyclesMultiplier replaces the old "speed" variable (0, 1, 2) and eliminates the need for
|
||||
|
|
@ -141,20 +140,15 @@ Component.subclass(CPU8080);
|
|||
* calcCycles(), which uses the nCyclesPerSecond passed to the constructor as a starting
|
||||
* point and computes the following variables:
|
||||
*
|
||||
* this.counts.nCyclesPerYield (this.counts.nCyclesPerSecond / CPU8080.YIELDS_PER_SECOND)
|
||||
* this.counts.nCyclesPerVideoUpdate (this.counts.nCyclesPerSecond / CPU8080.VIDEO_UPDATES_PER_SECOND)
|
||||
* this.counts.nCyclesPerStatusUpdate (this.counts.nCyclesPerSecond / CPU8080.STATUS_UPDATES_PER_SECOND)
|
||||
* this.counts.nCyclesPerYield: (this.counts.nCyclesPerSecond / CPU8080.YIELDS_PER_SECOND)
|
||||
*
|
||||
* The above variables are also multiplied by any cycle multiplier in effect, via setSpeed(),
|
||||
* and then they're used to initialize another set of variables for each runCPU() iteration:
|
||||
*
|
||||
* this.counts.nCyclesNextYield <= this.counts.nCyclesPerYield
|
||||
* this.counts.nCyclesNextVideoUpdate <= this.counts.nCyclesPerVideoUpdate
|
||||
* this.counts.nCyclesNextStatusUpdate <= this.counts.nCyclesPerStatusUpdate
|
||||
* this.counts.nCyclesNextYield: this.counts.nCyclesPerYield
|
||||
*/
|
||||
CPU8080.YIELDS_PER_SECOND = 30;
|
||||
CPU8080.VIDEO_UPDATES_PER_SECOND = 60;
|
||||
CPU8080.STATUS_UPDATES_PER_SECOND = 2;
|
||||
CPU8080.YIELDS_PER_SECOND = 30; // just a gut feeling for the MINIMUM number of yields per second
|
||||
CPU8080.YIELDS_PER_STATUS = 15; // every 15 yields (ie, twice per second), perform CPU status updates
|
||||
|
||||
CPU8080.BUTTONS = ["power", "reset"];
|
||||
|
||||
|
|
@ -178,12 +172,6 @@ CPU8080.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
if (control) this.cmp.setBinding(null, CPU8080.BUTTONS[i], control);
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to know the refresh rate (and corresponding interrupt rate, if any) of the Video component.
|
||||
*/
|
||||
var video = /** @type {Video8080} */ (cmp.getMachineComponent("Video"));
|
||||
this.refreshRate = video && video.getRefreshRate() || CPU8080.VIDEO_UPDATES_PER_SECOND;
|
||||
|
||||
/*
|
||||
* Attach the ChipSet component to the CPU so that it can be notified whenever the CPU stops and starts.
|
||||
*/
|
||||
|
|
@ -207,7 +195,6 @@ CPU8080.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
*/
|
||||
CPU8080.prototype.reset = function()
|
||||
{
|
||||
this.counts.nVideoUpdates = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -592,22 +579,12 @@ CPU8080.prototype.addCycles = function(nCycles, fEndStep)
|
|||
* calcCycles(fRecalc)
|
||||
*
|
||||
* Calculate the number of cycles to process for each "burst" of CPU activity. The size of a burst
|
||||
* is driven by the following values:
|
||||
* is driven by YIELDS_PER_SECOND (eg, 30).
|
||||
*
|
||||
* CPU8080.YIELDS_PER_SECOND (eg, 30)
|
||||
* CPU8080.VIDEO_UPDATES_PER_SECOND (eg, 60)
|
||||
* CPU8080.STATUS_UPDATES_PER_SECOND (eg, 5)
|
||||
*
|
||||
* The largest of the above values forces the size of the burst to its smallest value. Let's say that
|
||||
* largest value is 30. Assuming nCyclesPerSecond is 1,000,000, that results in bursts of 33,333 cycles.
|
||||
*
|
||||
* At the end of each burst, we subtract burst cycles from yield, video, and status cycle "threshold"
|
||||
* counters. Whenever the "next yield" cycle counter goes to (or below) zero, we compare elapsed time
|
||||
* to the time we expected the virtual hardware to take (eg, 1000ms/50 or 20ms), and if we still have time
|
||||
* remaining, we sleep the remaining time (or 0ms if there's no remaining time), and then restart runCPU().
|
||||
*
|
||||
* Similarly, whenever the "next video update" cycle counter goes to (or below) zero, we call updateVideo(),
|
||||
* and whenever the "next status update" cycle counter goes to (or below) zero, we call updateStatus().
|
||||
* At the end of each burst, we subtract burst cycles from the yield cycle "threshold" counter.
|
||||
* Whenever the "next yield" cycle counter goes to (or below) zero, we compare elapsed time to the time
|
||||
* we expected the virtual hardware to take (eg, 1000ms/50 or 20ms), and if we still have time remaining,
|
||||
* we sleep the remaining time (or 0ms if there's no remaining time), and then restart runCPU().
|
||||
*
|
||||
* @this {CPU8080}
|
||||
* @param {boolean} [fRecalc] is true if the caller wants to recalculate thresholds based on the most recent
|
||||
|
|
@ -616,14 +593,7 @@ CPU8080.prototype.addCycles = function(nCycles, fEndStep)
|
|||
CPU8080.prototype.calcCycles = function(fRecalc)
|
||||
{
|
||||
/*
|
||||
* Calculate the most cycles we're allowed to execute in a single "burst"
|
||||
*/
|
||||
var nMostUpdatesPerSecond = CPU8080.YIELDS_PER_SECOND;
|
||||
if (nMostUpdatesPerSecond < this.refreshRate) nMostUpdatesPerSecond = this.refreshRate;
|
||||
if (nMostUpdatesPerSecond < CPU8080.STATUS_UPDATES_PER_SECOND) nMostUpdatesPerSecond = CPU8080.STATUS_UPDATES_PER_SECOND;
|
||||
|
||||
/*
|
||||
* Calculate cycle "per" values for the yield, video update, and status update cycle counters
|
||||
* Calculate "per" yield values.
|
||||
*/
|
||||
var vMultiplier = 1;
|
||||
if (fRecalc) {
|
||||
|
|
@ -633,18 +603,13 @@ CPU8080.prototype.calcCycles = function(fRecalc)
|
|||
}
|
||||
|
||||
this.counts.msPerYield = Math.round(1000 / CPU8080.YIELDS_PER_SECOND);
|
||||
this.counts.nCyclesPerBurst = Math.floor(this.counts.nCyclesPerSecond / nMostUpdatesPerSecond * vMultiplier);
|
||||
this.counts.nCyclesPerYield = Math.floor(this.counts.nCyclesPerSecond / CPU8080.YIELDS_PER_SECOND * vMultiplier);
|
||||
this.counts.nCyclesPerVideoUpdate = Math.floor(this.counts.nCyclesPerSecond / this.refreshRate * vMultiplier);
|
||||
this.counts.nCyclesPerStatusUpdate = Math.floor(this.counts.nCyclesPerSecond / CPU8080.STATUS_UPDATES_PER_SECOND * vMultiplier);
|
||||
|
||||
/*
|
||||
* And initialize "next" yield, video update, and status update cycle "threshold" counters to those "per" values
|
||||
* And initialize "next" yield values to the "per" values.
|
||||
*/
|
||||
if (!fRecalc) {
|
||||
this.counts.nCyclesNextYield = this.counts.nCyclesPerYield;
|
||||
this.counts.nCyclesNextVideoUpdate = this.counts.nCyclesPerVideoUpdate;
|
||||
this.counts.nCyclesNextStatusUpdate = this.counts.nCyclesPerStatusUpdate;
|
||||
}
|
||||
this.counts.nCyclesRecalc = 0;
|
||||
};
|
||||
|
|
@ -718,6 +683,7 @@ CPU8080.prototype.getCyclesPerSecond = function()
|
|||
CPU8080.prototype.resetCycles = function()
|
||||
{
|
||||
this.counts.mhz = 0;
|
||||
this.counts.nYieldsSinceStatusUpdate = 0;
|
||||
this.nTotalCycles = this.nRunCycles = this.nBurstCycles = this.nStepCycles = 0;
|
||||
this.resetChecksum();
|
||||
this.setSpeed(1);
|
||||
|
|
@ -1000,26 +966,26 @@ CPU8080.prototype.setTimer = function(iTimer, ms)
|
|||
{
|
||||
var nCycles = -1;
|
||||
if (iTimer >= 0 && iTimer < this.aTimers.length) {
|
||||
nCycles = this.getCyclesMS(ms);
|
||||
nCycles = this.getMSCycles(ms);
|
||||
this.aTimers[iTimer][0] = nCycles;
|
||||
}
|
||||
return nCycles;
|
||||
};
|
||||
|
||||
/**
|
||||
* getCyclesMS(ms)
|
||||
* getMSCycles(ms)
|
||||
*
|
||||
* @this {CPU8080}
|
||||
* @param {number} ms
|
||||
* @return {number} number of corresponding cycles
|
||||
*/
|
||||
CPU8080.prototype.getCyclesMS = function(ms)
|
||||
CPU8080.prototype.getMSCycles = function(ms)
|
||||
{
|
||||
return (this.counts.nCyclesPerSecond * this.counts.nCyclesMultiplier) / 1000 * ms;
|
||||
};
|
||||
|
||||
/**
|
||||
* getCyclesBurst(nCycles)
|
||||
* getBurstCycles(nCycles)
|
||||
*
|
||||
* Used by runCPU() to get min(nCycles,[timer cycle counts])
|
||||
*
|
||||
|
|
@ -1027,7 +993,7 @@ CPU8080.prototype.getCyclesMS = function(ms)
|
|||
* @param {number} nCycles (number of cycles about to execute)
|
||||
* @return {number} (either nCycles or less if a timer needs to fire)
|
||||
*/
|
||||
CPU8080.prototype.getCyclesBurst = function(nCycles)
|
||||
CPU8080.prototype.getBurstCycles = function(nCycles)
|
||||
{
|
||||
for (var i = this.aTimers.length - 1; i >= 0; i--) {
|
||||
var timer = this.aTimers[i];
|
||||
|
|
@ -1083,18 +1049,15 @@ CPU8080.prototype.runCPU = function(fUpdateFocus)
|
|||
* recalculates the the maximum number of cycles for each burst if the nCyclesRecalc threshold has been reached.
|
||||
*/
|
||||
this.calcStartTime();
|
||||
|
||||
try {
|
||||
do {
|
||||
/*
|
||||
* nCyclesPerBurst is how many cycles we WANT to run on each iteration of stepCPU(), but it may run
|
||||
* significantly less (or slightly more, since we can't execute partial instructions).
|
||||
* nCyclesPerBurst is how many cycles we WANT to run on each iteration of stepCPU(), and may
|
||||
* be as HIGH as nCyclesPerYield, but it may be significantly less. getBurstCycles() will adjust
|
||||
* nCyclesPerBurst downward if any CPU timers need to fire during the next burst.
|
||||
*/
|
||||
var nCyclesPerBurst = (this.flags.fChecksum? 1 : this.counts.nCyclesPerBurst);
|
||||
|
||||
/*
|
||||
* Adjust nCyclesPerBurst if there are any CPU timers that need to fire within the current burst.
|
||||
*/
|
||||
nCyclesPerBurst = this.getCyclesBurst(nCyclesPerBurst);
|
||||
var nCyclesPerBurst = this.getBurstCycles(this.flags.fChecksum? 1 : this.counts.nCyclesPerYield);
|
||||
|
||||
/*
|
||||
* Execute the burst.
|
||||
|
|
@ -1119,22 +1082,13 @@ CPU8080.prototype.runCPU = function(fUpdateFocus)
|
|||
this.addCycles(0, true);
|
||||
this.updateChecksum(nCycles);
|
||||
|
||||
this.counts.nCyclesNextVideoUpdate -= nCycles;
|
||||
if (this.counts.nCyclesNextVideoUpdate <= 0) {
|
||||
this.counts.nCyclesNextVideoUpdate += this.counts.nCyclesPerVideoUpdate;
|
||||
if (this.cmp) this.cmp.updateVideo(this.counts.nVideoUpdates++);
|
||||
if (this.counts.nVideoUpdates > this.refreshRate) this.counts.nVideoUpdates = 0;
|
||||
}
|
||||
|
||||
this.counts.nCyclesNextStatusUpdate -= nCycles;
|
||||
if (this.counts.nCyclesNextStatusUpdate <= 0) {
|
||||
this.counts.nCyclesNextStatusUpdate += this.counts.nCyclesPerStatusUpdate;
|
||||
if (this.cmp) this.cmp.updateStatus();
|
||||
}
|
||||
|
||||
this.counts.nCyclesNextYield -= nCycles;
|
||||
if (this.counts.nCyclesNextYield <= 0) {
|
||||
this.counts.nCyclesNextYield += this.counts.nCyclesPerYield;
|
||||
if (++this.counts.nYieldsSinceStatusUpdate >= CPU8080.YIELDS_PER_STATUS) {
|
||||
if (this.cmp) this.cmp.updateStatus();
|
||||
this.counts.nYieldsSinceStatusUpdate = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} while (this.flags.fRunning);
|
||||
|
|
@ -1147,6 +1101,7 @@ CPU8080.prototype.runCPU = function(fUpdateFocus)
|
|||
this.setError(e.stack || e.message);
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(this.onRunTimeout, this.calcRemainingTime());
|
||||
};
|
||||
|
||||
|
|
@ -1235,7 +1190,7 @@ CPU8080.prototype.stopCPU = function(fComplete)
|
|||
CPU8080.prototype.updateCPU = function(fForce)
|
||||
{
|
||||
if (this.cmp) {
|
||||
this.cmp.updateVideo(-1);
|
||||
this.cmp.updateVideo(fForce);
|
||||
this.cmp.updateStatus(fForce);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3720,10 +3720,7 @@ if (DEBUGGER) {
|
|||
{
|
||||
if (DEBUG) {
|
||||
this.println("msPerYield: " + this.cpu.counts.msPerYield);
|
||||
this.println("nCyclesPerBurst: " + this.cpu.counts.nCyclesPerBurst);
|
||||
this.println("nCyclesPerYield: " + this.cpu.counts.nCyclesPerYield);
|
||||
this.println("nCyclesPerVideoUpdate: " + this.cpu.counts.nCyclesPerVideoUpdate);
|
||||
this.println("nCyclesPerStatusUpdate: " + this.cpu.counts.nCyclesPerStatusUpdate);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -922,11 +922,11 @@ Keyboard8080.prototype.isVT100TransmitterReady = function()
|
|||
{
|
||||
if (this.fVT100UARTBusy) {
|
||||
/*
|
||||
* NOTE: getCyclesMS(1.2731488) should work out to 3520 cycles for a CPU clocked at 361.69ns per cycle,
|
||||
* which is roughly 2.76Mhz. We could just hard-code 3520 instead of calling getCyclesMS(), but this helps
|
||||
* NOTE: getMSCycles(1.2731488) should work out to 3520 cycles for a CPU clocked at 361.69ns per cycle,
|
||||
* which is roughly 2.76Mhz. We could just hard-code 3520 instead of calling getMSCycles(), but this helps
|
||||
* maintain a reasonable blink rate for the cursor even when the user cranks up the CPU speed.
|
||||
*/
|
||||
if (this.cpu.getCycles() >= this.nVT100UARTCycleSnap + this.cpu.getCyclesMS(1.2731488)) {
|
||||
if (this.cpu.getCycles() >= this.nVT100UARTCycleSnap + this.cpu.getMSCycles(1.2731488)) {
|
||||
this.fVT100UARTBusy = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -430,6 +430,13 @@ Video8080.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
}
|
||||
}
|
||||
|
||||
var video = this;
|
||||
this.timerUpdateNext = this.cpu.addTimer(function() {
|
||||
video.updateScreen();
|
||||
});
|
||||
this.cpu.setTimer(this.timerUpdateNext, this.getRefreshTime());
|
||||
this.nUpdates = 0;
|
||||
|
||||
if (!this.sFontROM) this.setReady();
|
||||
};
|
||||
|
||||
|
|
@ -797,7 +804,7 @@ Video8080.prototype.updateScrollOffset = function(bScroll)
|
|||
* that update doesn't seem like a huge cause for concern.
|
||||
*/
|
||||
if (bScroll) {
|
||||
this.updateScreen(-1);
|
||||
this.updateScreen(true);
|
||||
} else {
|
||||
this.fSkipSingleCellUpdate = true;
|
||||
}
|
||||
|
|
@ -902,14 +909,14 @@ Video8080.prototype.setFocus = function()
|
|||
};
|
||||
|
||||
/**
|
||||
* getRefreshRate()
|
||||
* getRefreshTime()
|
||||
*
|
||||
* @this {Video8080}
|
||||
* @return {number}
|
||||
*/
|
||||
Video8080.prototype.getRefreshRate = function()
|
||||
Video8080.prototype.getRefreshTime = function()
|
||||
{
|
||||
return Math.max(this.rateRefresh, this.rateInterrupt);
|
||||
return 1000 / Math.max(this.rateRefresh, this.rateInterrupt);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1194,7 +1201,7 @@ Video8080.prototype.updateVT100 = function(fForced)
|
|||
};
|
||||
|
||||
/**
|
||||
* updateScreen(n)
|
||||
* updateScreen(fForced)
|
||||
*
|
||||
* Propagates the video buffer to the cell cache and updates the screen with any changes. Forced updates
|
||||
* are generally internal updates triggered by an I/O operation or other state change, while non-forced updates
|
||||
|
|
@ -1205,22 +1212,20 @@ Video8080.prototype.updateVT100 = function(fForced)
|
|||
* invalid value, we're assured that the next call to updateScreen() will redraw the entire (visible) video buffer.
|
||||
*
|
||||
* @this {Video8080}
|
||||
* @param {number} n (where 0 <= n < getRefreshRate() for a normal update, or -1 for a forced update)
|
||||
* @param {boolean} [fForced]
|
||||
*/
|
||||
Video8080.prototype.updateScreen = function(n)
|
||||
Video8080.prototype.updateScreen = function(fForced)
|
||||
{
|
||||
var fClean;
|
||||
var fUpdate = true;
|
||||
var fForced = true;
|
||||
|
||||
if (n >= 0) {
|
||||
fForced = false;
|
||||
if (!fForced) {
|
||||
if (this.rateInterrupt) {
|
||||
/*
|
||||
* TODO: Incorporate these hard-coded interrupt vector numbers into configuration blocks.
|
||||
*/
|
||||
if (this.rateInterrupt == 120) {
|
||||
if (!(n & 1)) {
|
||||
if (!(this.nUpdates & 1)) {
|
||||
/*
|
||||
* On even updates, call cpu.requestINTR(1), and also update our copy of the screen.
|
||||
*/
|
||||
|
|
@ -1248,13 +1253,15 @@ Video8080.prototype.updateScreen = function(n)
|
|||
fUpdate = false;
|
||||
}
|
||||
}
|
||||
this.cpu.setTimer(this.timerUpdateNext, this.getRefreshTime());
|
||||
this.nUpdates++;
|
||||
}
|
||||
|
||||
if (DEBUG && !fForced) {
|
||||
var nCycles = this.cpu.getCycles();
|
||||
this.nCyclesDelta = nCycles - this.nCyclesPrev;
|
||||
this.nCyclesPrev = nCycles;
|
||||
if (MAXDEBUG) this.printMessage("updateScreen(" + n + "): clean=" + fClean + ", update=" + fUpdate + ", cycles=" + this.nCyclesPrev + ", delta=" + this.nCyclesDelta);
|
||||
if (MAXDEBUG) this.printMessage("updateScreen(false): clean=" + fClean + ", update=" + fUpdate + ", cycles=" + this.nCyclesPrev + ", delta=" + this.nCyclesDelta);
|
||||
}
|
||||
|
||||
if (!fUpdate) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue