Skip to content
This repository has been archived by the owner on Dec 24, 2020. It is now read-only.

Commit

Permalink
Cleaned up the run loop, less complicated now, and video updates can …
Browse files Browse the repository at this point in the history
…drive themselves thanks to the new CPU timer functions
  • Loading branch information
jeffpar committed Aug 24, 2016
1 parent b0cd597 commit c677b6c
Show file tree
Hide file tree
Showing 7 changed files with 451 additions and 495 deletions.
8 changes: 4 additions & 4 deletions modules/pc8080/lib/computer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};

Expand Down
109 changes: 32 additions & 77 deletions modules/pc8080/lib/cpu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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.
*
Expand All @@ -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
Expand Down Expand Up @@ -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"];

Expand All @@ -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.
*/
Expand All @@ -207,7 +195,6 @@ CPU8080.prototype.initBus = function(cmp, bus, cpu, dbg)
*/
CPU8080.prototype.reset = function()
{
this.counts.nVideoUpdates = 0;
};

/**
Expand Down Expand Up @@ -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:
*
* 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.
* is driven by YIELDS_PER_SECOND (eg, 30).
*
* 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
Expand All @@ -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) {
Expand All @@ -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;
};
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -1000,34 +966,34 @@ 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])
*
* @this {CPU8080}
* @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];
Expand Down Expand Up @@ -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.
Expand All @@ -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);
Expand All @@ -1147,6 +1101,7 @@ CPU8080.prototype.runCPU = function(fUpdateFocus)
this.setError(e.stack || e.message);
return;
}

setTimeout(this.onRunTimeout, this.calcRemainingTime());
};

Expand Down Expand Up @@ -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);
}
};
Expand Down
3 changes: 0 additions & 3 deletions modules/pc8080/lib/debugger.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions modules/pc8080/lib/keyboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
31 changes: 19 additions & 12 deletions modules/pc8080/lib/video.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
};

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
};

/**
Expand Down Expand Up @@ -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
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit c677b6c

Please sign in to comment.