Merged next-release

This commit is contained in:
Jeff Parsons 2016-08-23 22:50:46 -07:00
commit bd5c32795c
274 changed files with 11029 additions and 1395 deletions

View file

@ -12,7 +12,7 @@ Overview
PC8080 is our 8080-based machine emulation module. The code is derived from [PCx86](/modules/pcx86/).
See the list of available [PC8080 Machines](/devices/pc8080/machine/), which includes
[Space Invaders (1978)](/devices/pc8080/machine/invaders/) and the [DEC VT100](/devices/pc8080/machine/vt100/).
[Space Invaders (1978)](/devices/pc8080/machine/invaders/) and the [DEC VT100 Terminal](/devices/pc8080/machine/vt100/).
PC8080 is comprised of the following non-shared components, as listed in [package.json](../../package.json)
(see the *pc8080Files* property):

View file

@ -172,25 +172,10 @@ ChipSet8080.SI1978 = {
* One of the many chips in the VT100 is an 8224, which operates at 24.8832MHz. That frequency is divided by 9
* to yield a 361.69ns clock period for the 8080 CPU, which means (in theory) that the CPU is running at 2.76Mhz.
*
* Hence the CPU component in the VT100's machine.xml SHOULD be defined as:
* Hence the CPU component in the VT100's machine.xml should be defined as:
*
* <cpu id="cpu8080" model="8080" cycles="2764800"/>
*
* where 2764800 = 24883200 / 9. Unfortunately, the VT100 ROM decrements a countdown value in memory to determine
* cursor blink rate, and if we use 2764800 cycles per second, the cursor blinks MUCH too fast. It's surprising that
* the VT100 doesn't rely on vertical retrace interrupts for blink rate. Perhaps the designers were concerned about
* consistency across 60Hz and 50Hz display modes, although that seems like a minor concern, considering that the
* alternative means the ROM is now tied to a specific CPU operating frequency. However, short of rewriting portions
* of the ROM, we have to deal with it.
*
* And we deal with it by lowering cycles per second to 1000000 (1Mhz). I'm guessing that in a real VT100, the 8080
* gets bogged down by other factors (eg, the Video Processor's DMA requests), but we don't simulate the hardware to
* that level of detail, so the easiest solution is to lower the effective clock speed.
*
* NOTE: If you've noticed that the VT100 cursor blinks unevenly, you're right, and it's by design: the ROM uses a
* countdown value for the cursor's "on" state that is twice as large as that for the cursor's "off" state, so it's
* "on" twice as long as it's "off".
*
* WARNING: The choice of clock speed has an effect on other simulated VT100 circuits; see the DC011 Timing Chip
* discussion below, along with the getVT100LBA() function.
*
@ -259,12 +244,12 @@ ChipSet8080.VT100 = {
*
* On p. 4-56, the DC011 Block Diagram shows 8 outputs labeled LBA0 through LBA7. From p. 4-61:
*
* Several of the LBAs are used as general purpose clocks in the VT100. LBA 3 and LBA 4 are used to generate
* Several of the LBAs are used as general purpose clocks in the VT100. LBA3 and LBA4 are used to generate
* timing for the keyboard. These signals satisfy the keyboard's requirement of two square-waves, one twice the
* frequency of the other, even though every 16th transition is delayed (the second stage of the horizontal
* counter divides by 17, not 16). LBA 7 is used by the nonvolatile RAM.
* counter divides by 17, not 16). LBA7 is used by the nonvolatile RAM.
*
* And on p. 4-62, timings are provided for the LBA0 through LBA7 when the VT100 is in 80-column mode; in particular:
* And on p. 4-62, timings are provided for the LBA0 through LBA7; in particular:
*
* LBA6: 16.82353us (when LBA6 is low, for a period of 33.64706us)
* LBA7: 31.77778us (when LBA7 is high, for a period of 63.55556us)
@ -501,32 +486,47 @@ ChipSet8080.VT100.INIT = [
0, 0, 0, 0,
[
/*
* The following table contains the data we use to initialize all (100) words of NVR (Non-Volatile RAM).
* According the DEC Technical Manual:
* The following array contains the data we use to initialize all (100) words of NVR (Non-Volatile RAM).
*
* I used to initialize every word to 0x3ff, as if the NVR had been freshly erased, but that causes the
* firmware to (attempt to) beep and then display an error code (2). As the DEC Technical Manual says:
*
* If the NVR fails, the bell sounds several times to inform the operator, and then default settings
* stored in the ROM allow the terminal to work.
*
* However, this behavior may be limited to only certain kinds of "failures", because if I deliberately
* stuff an invalid value in the table, there is indeed an attempt to beep (though sound isn't working yet),
* an error code (2) is displayed in the top-left corner of the screen, but the NVR still appears to contain
* all the same (invalid) data we started with. Perhaps all they meant to say is that the RAM copy of NVR
* settings is reset, not the NVR itself.
* but I think what they meant to say is that default settings are stored in the RAM copy of NVR. So then
* I went into SET-UP, pressed SHIFT-S to save those settings back to NVR, and then used the PC8080 debugger
* "d nvr" command to dump the NVR contents. The results are below.
*
* Using a VT100 configuration with the Debugger attached, use the NVR dumper command ("d nvr") to see how
* other SET-UP changes affect the NVR (after you've saved your changes using SHIFT-S, of course).
* The first dump actually contains only two modifications to the factory defaults: enabling ONLINE instead
* of LOCAL operation, and turning ANSI support ON. The second dump is unmodified (the TRUE factory defaults).
*
* NVR Notes
* ---------
* After enabling smooth scrolling, the word in row 5, col 4 changes from 2E50 to 2ED0, and the final word
* changes from 2E51 to 2E71 (I'm guessing that the final word is an NVR checksum).
* By making selective changes, you can discern where the bits for certain features are stored. For example,
* smooth-scrolling is apparently controlled by bit 7 of the word at offset 0x2B (and is ON by default in
* the factory settings). And it's likely that the word at offset 0x32 (ie, the last word that's not zero)
* is the NVR checksum.
*/
0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80,
0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80,
0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80,
0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E00,
0x2E08, 0x2E8E, 0x2E00, 0x2ED0, 0x2E30, 0x2E40, 0x2E20, 0x2E00, 0x2EE0, 0x2EE0,
0x2E71, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x2E08, 0x2E8E, 0x2E00, 0x2ED0, 0x2E70, 0x2E00, 0x2E20, 0x2E00, 0x2EE0, 0x2EE0,
0x2E7D, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
],
[
/*
* The TRUE factory defaults (not currently used for anything; they're just here for reference, wasting space....)
*/
0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80,
0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80,
0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80,
0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E80, 0x2E00,
0x2E08, 0x2E8E, 0x2E20, 0x2ED0, 0x2E50, 0x2E00, 0x2E20, 0x2E00, 0x2EE0, 0x2EE0,
0x2E69, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
@ -943,10 +943,11 @@ ChipSet8080.prototype.doNVRCommand = function()
*/
ChipSet8080.prototype.inVT100Flags = function(port, addrFrom)
{
var b = this.bFlags;
/*
* The NVR_CLK bit is driven by LBA7 (ie, bit 7 from Line Buffer Address generation); see the DC011 discussion above.
*/
var b = this.bFlags;
b &= ~ChipSet8080.VT100.FLAGS.NVR_CLK;
if (this.getVT100LBA(7)) {
b |= ChipSet8080.VT100.FLAGS.NVR_CLK;
@ -954,18 +955,22 @@ ChipSet8080.prototype.inVT100Flags = function(port, addrFrom)
this.doNVRCommand();
}
}
b &= ~ChipSet8080.VT100.FLAGS.NVR_DATA;
if (this.bNVROut) {
b |= ChipSet8080.VT100.FLAGS.NVR_DATA;
}
b &= ~ChipSet8080.VT100.FLAGS.KBD_XMIT;
if (this.kbd && this.kbd.isTransmitterReady()) {
if (this.kbd && this.kbd.isVT100TransmitterReady()) {
b |= ChipSet8080.VT100.FLAGS.KBD_XMIT;
}
b &= ~ChipSet8080.VT100.FLAGS.UART_XMIT;
if (this.serial && this.serial.isTransmitterReady()) {
b |= ChipSet8080.VT100.FLAGS.UART_XMIT;
}
this.bFlags = b;
this.printMessageIO(port, null, addrFrom, "FLAGS", b);
return b;
@ -1030,7 +1035,7 @@ ChipSet8080.prototype.outVT100DC012 = function(port, b, addrFrom)
this.bDC012Blink = ~this.bDC012Blink;
break;
case 0x1:
// TODO: Clear vertical frequency interrupt
// TODO: Clear vertical frequency interrupt?
break;
case 0x2:
case 0x3:

View file

@ -1404,7 +1404,8 @@ Computer8080.prototype.getMachineComponent = function(sType, componentPrev)
* updateFocus(fScroll)
*
* NOTE: When soft keyboard buttons call us to return focus to the machine (and away from the button),
* the scroll feature has annoying effect on iOS, so we no longer do it by default (fScroll must be true).
* the browser's default behavior is to scroll the element into view, which can be annoying, especially on iOS,
* where the display is more constrained, so we no longer do it by default (fScroll must be true).
*
* @this {Computer8080}
* @param {boolean} [fScroll]
@ -1418,15 +1419,17 @@ Computer8080.prototype.updateFocus = function(fScroll)
* is to ensure that keyboard input is fielded properly.
*/
var x = 0, y = 0;
if (fScroll && window) {
if (!fScroll && window) {
x = window.scrollX;
y = window.scrollY;
}
/*
* TODO: We need a mechanism to determine the "active" display, instead of hard-coding this to aVideo[0].
*/
this.aVideo[0].setFocus();
if (fScroll && window) {
if (!fScroll && window) {
window.scrollTo(x, y);
}
}
@ -1468,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);
}
};

View file

@ -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.
*
@ -79,21 +79,20 @@ function CPU8080(parmsCPU, nCyclesDefault)
var nMultiplier = parmsCPU['multiplier'] || 1;
this.aCounts = {};
this.aCounts.nCyclesPerSecond = nCycles;
this.aCounts.nVideoUpdates = 0;
this.counts = {};
this.counts.nCyclesPerSecond = nCycles;
/*
* nCyclesMultiplier replaces the old "speed" variable (0, 1, 2) and eliminates the need for
* the constants (SPEED_SLOW, SPEED_FAST and SPEED_MAX). The UI simply doubles the multiplier
* until we've exceeded the host's speed limit and then starts the multiplier over at 1.
*/
this.aCounts.nCyclesMultiplier = nMultiplier;
this.aCounts.mhzDefault = Math.round(this.aCounts.nCyclesPerSecond / 10000) / 100;
this.counts.nCyclesMultiplier = nMultiplier;
this.counts.mhzDefault = Math.round(this.counts.nCyclesPerSecond / 10000) / 100;
/*
* TODO: Take care of this with an initial setSpeed() call instead?
*/
this.aCounts.mhzTarget = this.aCounts.mhzDefault * this.aCounts.nCyclesMultiplier;
this.counts.mhzTarget = this.counts.mhzDefault * this.counts.nCyclesMultiplier;
/*
* We add a number of flags to the set initialized by Component
@ -117,10 +116,10 @@ function CPU8080(parmsCPU, nCyclesDefault)
* and call resetChecksum().
*/
this.flags.fChecksum = false;
this.aCounts.nChecksum = this.aCounts.nCyclesChecksumNext = 0;
this.aCounts.nCyclesChecksumStart = parmsCPU["csStart"];
this.aCounts.nCyclesChecksumInterval = parmsCPU["csInterval"];
this.aCounts.nCyclesChecksumStop = parmsCPU["csStop"];
this.counts.nChecksum = this.counts.nCyclesChecksumNext = 0;
this.counts.nCyclesChecksumStart = parmsCPU["csStart"];
this.counts.nCyclesChecksumInterval = parmsCPU["csInterval"];
this.counts.nCyclesChecksumStop = parmsCPU["csStop"];
/*
* Array of countdown timers managed by addTimer() and setTimer().
@ -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.aCounts.nCyclesPerYield (this.aCounts.nCyclesPerSecond / CPU8080.YIELDS_PER_SECOND)
* this.aCounts.nCyclesPerVideoUpdate (this.aCounts.nCyclesPerSecond / CPU8080.VIDEO_UPDATES_PER_SECOND)
* this.aCounts.nCyclesPerStatusUpdate (this.aCounts.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.aCounts.nCyclesNextYield <= this.aCounts.nCyclesPerYield
* this.aCounts.nCyclesNextVideoUpdate <= this.aCounts.nCyclesPerVideoUpdate
* this.aCounts.nCyclesNextStatusUpdate <= this.aCounts.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.aCounts.nVideoUpdates = 0;
};
/**
@ -375,16 +362,16 @@ CPU8080.prototype.getChecksum = function()
*/
CPU8080.prototype.resetChecksum = function()
{
if (this.aCounts.nCyclesChecksumStart === undefined) this.aCounts.nCyclesChecksumStart = 0;
if (this.aCounts.nCyclesChecksumInterval === undefined) this.aCounts.nCyclesChecksumInterval = -1;
if (this.aCounts.nCyclesChecksumStop === undefined) this.aCounts.nCyclesChecksumStop = -1;
this.flags.fChecksum = (this.aCounts.nCyclesChecksumStart >= 0 && this.aCounts.nCyclesChecksumInterval > 0);
if (this.counts.nCyclesChecksumStart === undefined) this.counts.nCyclesChecksumStart = 0;
if (this.counts.nCyclesChecksumInterval === undefined) this.counts.nCyclesChecksumInterval = -1;
if (this.counts.nCyclesChecksumStop === undefined) this.counts.nCyclesChecksumStop = -1;
this.flags.fChecksum = (this.counts.nCyclesChecksumStart >= 0 && this.counts.nCyclesChecksumInterval > 0);
if (this.flags.fChecksum) {
this.aCounts.nChecksum = 0;
this.aCounts.nCyclesChecksumNext = this.aCounts.nCyclesChecksumStart - this.nTotalCycles;
this.counts.nChecksum = 0;
this.counts.nCyclesChecksumNext = this.counts.nCyclesChecksumStart - this.nTotalCycles;
/*
* this.aCounts.nCyclesChecksumNext = this.aCounts.nCyclesChecksumStart + this.aCounts.nCyclesChecksumInterval -
* (this.nTotalCycles % this.aCounts.nCyclesChecksumInterval);
* this.counts.nCyclesChecksumNext = this.counts.nCyclesChecksumStart + this.counts.nCyclesChecksumInterval -
* (this.nTotalCycles % this.counts.nCyclesChecksumInterval);
*/
return true;
}
@ -409,15 +396,15 @@ CPU8080.prototype.updateChecksum = function(nCycles)
* Get a 32-bit summation of the current CPU state and add it to our running 32-bit checksum
*/
var fDisplay = false;
this.aCounts.nChecksum = (this.aCounts.nChecksum + this.getChecksum())|0;
this.aCounts.nCyclesChecksumNext -= nCycles;
if (this.aCounts.nCyclesChecksumNext <= 0) {
this.aCounts.nCyclesChecksumNext += this.aCounts.nCyclesChecksumInterval;
this.counts.nChecksum = (this.counts.nChecksum + this.getChecksum())|0;
this.counts.nCyclesChecksumNext -= nCycles;
if (this.counts.nCyclesChecksumNext <= 0) {
this.counts.nCyclesChecksumNext += this.counts.nCyclesChecksumInterval;
fDisplay = true;
}
if (this.aCounts.nCyclesChecksumStop >= 0) {
if (this.aCounts.nCyclesChecksumStop <= this.getCycles()) {
this.aCounts.nCyclesChecksumInterval = this.aCounts.nCyclesChecksumStop = -1;
if (this.counts.nCyclesChecksumStop >= 0) {
if (this.counts.nCyclesChecksumStop <= this.getCycles()) {
this.counts.nCyclesChecksumInterval = this.counts.nCyclesChecksumStop = -1;
this.resetChecksum();
this.stopCPU();
fDisplay = true;
@ -438,7 +425,7 @@ CPU8080.prototype.updateChecksum = function(nCycles)
*/
CPU8080.prototype.displayChecksum = function()
{
this.println(this.getCycles() + " cycles: " + "checksum=" + str.toHex(this.aCounts.nChecksum));
this.println(this.getCycles() + " cycles: " + "checksum=" + str.toHex(this.counts.nChecksum));
};
/**
@ -506,10 +493,15 @@ CPU8080.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
this.bindings[sBinding] = control;
control.onclick = function onClickRun() {
if (!cpu.cmp || !cpu.cmp.checkPower()) return;
/*
* We no longer pass true to these runCPU()/stopCPU() calls, on the theory that if the "run"
* control is visible, then the computer is probably sufficiently visible as well; the problem
* with setting fUpdateFocus to true is that it can jerk the web page around in annoying ways.
*/
if (!cpu.flags.fRunning)
cpu.runCPU(true);
cpu.runCPU();
else
cpu.stopCPU(true);
cpu.stopCPU();
};
fBound = true;
break;
@ -522,7 +514,7 @@ CPU8080.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
case "setSpeed":
this.bindings[sBinding] = control;
control.onclick = function onClickSetSpeed() {
cpu.setSpeed(cpu.aCounts.nCyclesMultiplier << 1, true);
cpu.setSpeed(cpu.counts.nCyclesMultiplier << 1, true);
};
control.textContent = this.getSpeedTarget();
fBound = true;
@ -587,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
@ -611,37 +593,25 @@ 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) {
if (this.aCounts.nCyclesMultiplier > 1 && this.aCounts.mhz) {
vMultiplier = (this.aCounts.mhz / this.aCounts.mhzDefault);
if (this.counts.nCyclesMultiplier > 1 && this.counts.mhz) {
vMultiplier = (this.counts.mhz / this.counts.mhzDefault);
}
}
this.aCounts.msPerYield = Math.round(1000 / CPU8080.YIELDS_PER_SECOND);
this.aCounts.nCyclesPerBurst = Math.floor(this.aCounts.nCyclesPerSecond / nMostUpdatesPerSecond * vMultiplier);
this.aCounts.nCyclesPerYield = Math.floor(this.aCounts.nCyclesPerSecond / CPU8080.YIELDS_PER_SECOND * vMultiplier);
this.aCounts.nCyclesPerVideoUpdate = Math.floor(this.aCounts.nCyclesPerSecond / this.refreshRate * vMultiplier);
this.aCounts.nCyclesPerStatusUpdate = Math.floor(this.aCounts.nCyclesPerSecond / CPU8080.STATUS_UPDATES_PER_SECOND * vMultiplier);
this.counts.msPerYield = Math.round(1000 / CPU8080.YIELDS_PER_SECOND);
this.counts.nCyclesPerYield = Math.floor(this.counts.nCyclesPerSecond / CPU8080.YIELDS_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.aCounts.nCyclesNextYield = this.aCounts.nCyclesPerYield;
this.aCounts.nCyclesNextVideoUpdate = this.aCounts.nCyclesPerVideoUpdate;
this.aCounts.nCyclesNextStatusUpdate = this.aCounts.nCyclesPerStatusUpdate;
this.counts.nCyclesNextYield = this.counts.nCyclesPerYield;
}
this.aCounts.nCyclesRecalc = 0;
this.counts.nCyclesRecalc = 0;
};
/**
@ -664,11 +634,11 @@ CPU8080.prototype.calcCycles = function(fRecalc)
CPU8080.prototype.getCycles = function(fScaled)
{
var nCycles = this.nTotalCycles + this.nRunCycles + this.nBurstCycles - this.nStepCycles;
if (fScaled && this.aCounts.nCyclesMultiplier > 1 && this.aCounts.mhz > this.aCounts.mhzDefault) {
if (fScaled && this.counts.nCyclesMultiplier > 1 && this.counts.mhz > this.counts.mhzDefault) {
/*
* We could scale the current cycle count by the current effective speed (this.aCounts.mhz); eg:
* We could scale the current cycle count by the current effective speed (this.counts.mhz); eg:
*
* nCycles = Math.round(nCycles / (this.aCounts.mhz / this.aCounts.mhzDefault));
* nCycles = Math.round(nCycles / (this.counts.mhz / this.counts.mhzDefault));
*
* but that speed will fluctuate somewhat: large fluctuations at first, but increasingly smaller
* fluctuations after each burst of instructions that runCPU() executes.
@ -683,7 +653,7 @@ CPU8080.prototype.getCycles = function(fScaled)
* interface allows any value, as does the CPU "multiplier" parmsCPU property (from the machine's
* XML file).
*/
nCycles = Math.round(nCycles / this.aCounts.nCyclesMultiplier);
nCycles = Math.round(nCycles / this.counts.nCyclesMultiplier);
}
return nCycles;
};
@ -698,7 +668,7 @@ CPU8080.prototype.getCycles = function(fScaled)
*/
CPU8080.prototype.getCyclesPerSecond = function()
{
return this.aCounts.nCyclesPerSecond;
return this.counts.nCyclesPerSecond;
};
/**
@ -712,7 +682,8 @@ CPU8080.prototype.getCyclesPerSecond = function()
*/
CPU8080.prototype.resetCycles = function()
{
this.aCounts.mhz = 0;
this.counts.mhz = 0;
this.counts.nYieldsSinceStatusUpdate = 0;
this.nTotalCycles = this.nRunCycles = this.nBurstCycles = this.nStepCycles = 0;
this.resetChecksum();
this.setSpeed(1);
@ -726,7 +697,7 @@ CPU8080.prototype.resetCycles = function()
*/
CPU8080.prototype.getSpeed = function()
{
return this.aCounts.nCyclesMultiplier;
return this.counts.nCyclesMultiplier;
};
/**
@ -740,7 +711,7 @@ CPU8080.prototype.getSpeedCurrent = function()
/*
* TODO: Has toFixed() been "fixed" in all browsers (eg, IE) to return a rounded value now?
*/
return ((this.flags.fRunning && this.aCounts.mhz)? (this.aCounts.mhz.toFixed(2) + "Mhz") : "Stopped");
return ((this.flags.fRunning && this.counts.mhz)? (this.counts.mhz.toFixed(2) + "Mhz") : "Stopped");
};
/**
@ -754,7 +725,7 @@ CPU8080.prototype.getSpeedTarget = function()
/*
* TODO: Has toFixed() been "fixed" in all browsers (eg, IE) to return a rounded value now?
*/
return this.aCounts.mhzTarget.toFixed(2) + "Mhz";
return this.counts.mhzTarget.toFixed(2) + "Mhz";
};
/**
@ -778,15 +749,15 @@ CPU8080.prototype.setSpeed = function(nMultiplier, fUpdateFocus)
/*
* If we haven't reached 80% (0.8) of the current target speed, revert to a multiplier of one (1).
*/
if (this.aCounts.mhz / this.aCounts.mhzTarget < 0.8) {
if (this.counts.mhz / this.counts.mhzTarget < 0.8) {
nMultiplier = 1;
} else {
fSuccess = true;
}
this.aCounts.nCyclesMultiplier = nMultiplier;
var mhz = this.aCounts.mhzDefault * this.aCounts.nCyclesMultiplier;
if (this.aCounts.mhzTarget != mhz) {
this.aCounts.mhzTarget = mhz;
this.counts.nCyclesMultiplier = nMultiplier;
var mhz = this.counts.mhzDefault * this.counts.nCyclesMultiplier;
if (this.counts.mhzTarget != mhz) {
this.counts.mhzTarget = mhz;
var sSpeed = this.getSpeedTarget();
var controlSpeed = this.bindings["setSpeed"];
if (controlSpeed) controlSpeed.textContent = sSpeed;
@ -796,8 +767,8 @@ CPU8080.prototype.setSpeed = function(nMultiplier, fUpdateFocus)
}
this.addCycles(this.nRunCycles);
this.nRunCycles = 0;
this.aCounts.msStartRun = usr.getTime();
this.aCounts.msEndThisRun = 0;
this.counts.msStartRun = usr.getTime();
this.counts.msEndThisRun = 0;
this.calcCycles();
return fSuccess;
};
@ -812,7 +783,7 @@ CPU8080.prototype.setSpeed = function(nMultiplier, fUpdateFocus)
CPU8080.prototype.calcSpeed = function(nCycles, msElapsed)
{
if (msElapsed) {
this.aCounts.mhz = Math.round(nCycles / (msElapsed * 10)) / 100;
this.counts.mhz = Math.round(nCycles / (msElapsed * 10)) / 100;
if (msElapsed >= 86400000) {
this.nTotalCycles = 0;
this.setSpeed(); // reset all counters once per day so that we never have to worry about overflow
@ -827,11 +798,11 @@ CPU8080.prototype.calcSpeed = function(nCycles, msElapsed)
*/
CPU8080.prototype.calcStartTime = function()
{
if (this.aCounts.nCyclesRecalc >= this.aCounts.nCyclesPerSecond) {
if (this.counts.nCyclesRecalc >= this.counts.nCyclesPerSecond) {
this.calcCycles(true);
}
this.aCounts.nCyclesThisRun = 0;
this.aCounts.msStartThisRun = usr.getTime();
this.counts.nCyclesThisRun = 0;
this.counts.msStartThisRun = usr.getTime();
/*
* Try to detect situations where the browser may have throttled us, such as when the user switches
@ -858,19 +829,19 @@ CPU8080.prototype.calcStartTime = function()
* to hit its target speed, since you would expect any instruction that displays a message to be an
* EXTREMELY slow instruction.
*/
if (this.aCounts.msEndThisRun) {
var msDelta = this.aCounts.msStartThisRun - this.aCounts.msEndThisRun;
if (msDelta > this.aCounts.msPerYield) {
if (this.counts.msEndThisRun) {
var msDelta = this.counts.msStartThisRun - this.counts.msEndThisRun;
if (msDelta > this.counts.msPerYield) {
if (MAXDEBUG) this.println("large time delay: " + msDelta + "ms");
this.aCounts.msStartRun += msDelta;
this.counts.msStartRun += msDelta;
/*
* Bumping msStartRun forward should NEVER cause it to exceed msStartThisRun; however, just
* in case, I make absolutely sure it cannot happen, since doing so could result in negative
* speed calculations.
*/
this.assert(this.aCounts.msStartRun <= this.aCounts.msStartThisRun);
if (this.aCounts.msStartRun > this.aCounts.msStartThisRun) {
this.aCounts.msStartRun = this.aCounts.msStartThisRun;
this.assert(this.counts.msStartRun <= this.counts.msStartThisRun);
if (this.counts.msStartRun > this.counts.msStartThisRun) {
this.counts.msStartRun = this.counts.msStartThisRun;
}
}
}
@ -884,47 +855,47 @@ CPU8080.prototype.calcStartTime = function()
*/
CPU8080.prototype.calcRemainingTime = function()
{
this.aCounts.msEndThisRun = usr.getTime();
this.counts.msEndThisRun = usr.getTime();
var msYield = this.aCounts.msPerYield;
if (this.aCounts.nCyclesThisRun) {
var msYield = this.counts.msPerYield;
if (this.counts.nCyclesThisRun) {
/*
* Normally, we would assume we executed a full quota of work over msPerYield, but since the CPU
* now has the option of calling yieldCPU(), that might not be true. If nCyclesThisRun is correct, then
* the ratio of nCyclesThisRun/nCyclesPerYield should represent the percentage of work we performed,
* and so applying that percentage to msPerYield should give us a better estimate of work vs. time.
*/
msYield = Math.round(msYield * this.aCounts.nCyclesThisRun / this.aCounts.nCyclesPerYield);
msYield = Math.round(msYield * this.counts.nCyclesThisRun / this.counts.nCyclesPerYield);
}
var msElapsedThisRun = this.aCounts.msEndThisRun - this.aCounts.msStartThisRun;
var msElapsedThisRun = this.counts.msEndThisRun - this.counts.msStartThisRun;
var msRemainsThisRun = msYield - msElapsedThisRun;
/*
* We could pass only "this run" results to calcSpeed():
*
* nCycles = this.aCounts.nCyclesThisRun;
* nCycles = this.counts.nCyclesThisRun;
* msElapsed = msElapsedThisRun;
*
* but it seems preferable to use longer time periods and hopefully get a more accurate speed.
*
* Also, if msRemainsThisRun >= 0 && this.aCounts.nCyclesMultiplier == 1, we could pass these results instead:
* Also, if msRemainsThisRun >= 0 && this.counts.nCyclesMultiplier == 1, we could pass these results instead:
*
* nCycles = this.aCounts.nCyclesThisRun;
* msElapsed = this.aCounts.msPerYield;
* nCycles = this.counts.nCyclesThisRun;
* msElapsed = this.counts.msPerYield;
*
* to insure that we display a smooth, constant N Mhz. But for now, I prefer seeing any fluctuations.
*/
var nCycles = this.nRunCycles;
var msElapsed = this.aCounts.msEndThisRun - this.aCounts.msStartRun;
var msElapsed = this.counts.msEndThisRun - this.counts.msStartRun;
if (MAXDEBUG && msRemainsThisRun < 0 && this.aCounts.nCyclesMultiplier > 1) {
if (MAXDEBUG && msRemainsThisRun < 0 && this.counts.nCyclesMultiplier > 1) {
this.println("warning: updates @" + msElapsedThisRun + "ms (prefer " + Math.round(msYield) + "ms)");
}
this.calcSpeed(nCycles, msElapsed);
if (msRemainsThisRun < 0 || this.aCounts.mhz < this.aCounts.mhzTarget) {
if (msRemainsThisRun < 0 || this.counts.mhz < this.counts.mhzTarget) {
/*
* If the last burst took MORE time than we allotted (ie, it's taking more than 1 second to simulate
* nCyclesPerSecond), all we can do is yield for as little time as possible (ie, 0ms) and hope that the
@ -937,13 +908,13 @@ CPU8080.prototype.calcRemainingTime = function()
* Last but not least, update nCyclesRecalc, so that when runCPU() starts up again and calls calcStartTime(),
* it'll be ready to decide if calcCycles() should be called again.
*/
this.aCounts.nCyclesRecalc += this.aCounts.nCyclesThisRun;
this.counts.nCyclesRecalc += this.counts.nCyclesThisRun;
if (DEBUG && this.messageEnabled(Messages8080.LOG) && msRemainsThisRun) {
this.log("calcRemainingTime: " + msRemainsThisRun + "ms to sleep after " + this.aCounts.msEndThisRun + "ms");
this.log("calcRemainingTime: " + msRemainsThisRun + "ms to sleep after " + this.counts.msEndThisRun + "ms");
}
this.aCounts.msEndThisRun += msRemainsThisRun;
this.counts.msEndThisRun += msRemainsThisRun;
return msRemainsThisRun;
};
@ -980,9 +951,9 @@ CPU8080.prototype.addTimer = function(callBack)
* This is preferred over JavaScript's setTimeout(), because all our timers are effectively paused when
* the CPU is paused (eg, when the Debugger halts execution). Moreover, setTimeout() handlers only run after
* runCPU() yields, which is far too granular for some components (eg, when the SerialPort tries to simulate
* receiver interrupts at 9600 baud).
* interrupts at 9600 baud).
*
* Ideally, the only function that would use setTimeout() is runCPU(), while the rest of the components would
* Ideally, the only function that would use setTimeout() is runCPU(), while the rest of the components
* use setTimer(); however, due to legacy code (ie, code that predates these functions) and/or laziness,
* that's currently not the case. TODO: Fix.
*
@ -995,24 +966,36 @@ CPU8080.prototype.setTimer = function(iTimer, ms)
{
var nCycles = -1;
if (iTimer >= 0 && iTimer < this.aTimers.length) {
nCycles = (this.aCounts.nCyclesPerSecond * this.aCounts.nCyclesMultiplier) / 1000 * ms;
nCycles = this.getMSCycles(ms);
this.aTimers[iTimer][0] = nCycles;
}
return nCycles;
};
/**
* getTimerBurst(nCycles)
* getMSCycles(ms)
*
* Used by runCPU() to either accept or shorten the current burst if any timers need to fire soon.
* @this {CPU8080}
* @param {number} ms
* @return {number} number of corresponding cycles
*/
CPU8080.prototype.getMSCycles = function(ms)
{
return (this.counts.nCyclesPerSecond * this.counts.nCyclesMultiplier) / 1000 * ms;
};
/**
* 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.getTimerBurst = function(nCycles)
CPU8080.prototype.getBurstCycles = function(nCycles)
{
for (var i = 0; i < this.aTimers.length; i++) {
for (var i = this.aTimers.length - 1; i >= 0; i--) {
var timer = this.aTimers[i];
if (timer[0] < 0) continue;
if (nCycles > timer[0]) {
@ -1034,7 +1017,7 @@ CPU8080.prototype.getTimerBurst = function(nCycles)
*/
CPU8080.prototype.updateTimers = function(nCycles)
{
for (var i = 0; i < this.aTimers.length; i++) {
for (var i = this.aTimers.length - 1; i >= 0; i--) {
var timer = this.aTimers[i];
if (timer[0] < 0) continue;
timer[0] -= nCycles;
@ -1066,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.aCounts.nCyclesPerBurst);
/*
* Adjust nCyclesPerBurst if there are any CPU timers that need to fire within the current burst.
*/
nCyclesPerBurst = this.getTimerBurst(nCyclesPerBurst);
var nCyclesPerBurst = this.getBurstCycles(this.flags.fChecksum? 1 : this.counts.nCyclesPerYield);
/*
* Execute the burst.
@ -1097,27 +1077,18 @@ CPU8080.prototype.runCPU = function(fUpdateFocus)
/*
* Add nCycles to nCyclesThisRun, as well as nRunCycles (the cycle count since the CPU first started).
*/
this.aCounts.nCyclesThisRun += nCycles;
this.counts.nCyclesThisRun += nCycles;
this.nRunCycles += nCycles;
this.addCycles(0, true);
this.updateChecksum(nCycles);
this.aCounts.nCyclesNextVideoUpdate -= nCycles;
if (this.aCounts.nCyclesNextVideoUpdate <= 0) {
this.aCounts.nCyclesNextVideoUpdate += this.aCounts.nCyclesPerVideoUpdate;
if (this.cmp) this.cmp.updateVideo(this.aCounts.nVideoUpdates++);
if (this.aCounts.nVideoUpdates > this.refreshRate) this.aCounts.nVideoUpdates = 0;
}
this.aCounts.nCyclesNextStatusUpdate -= nCycles;
if (this.aCounts.nCyclesNextStatusUpdate <= 0) {
this.aCounts.nCyclesNextStatusUpdate += this.aCounts.nCyclesPerStatusUpdate;
if (this.cmp) this.cmp.updateStatus();
}
this.aCounts.nCyclesNextYield -= nCycles;
if (this.aCounts.nCyclesNextYield <= 0) {
this.aCounts.nCyclesNextYield += this.aCounts.nCyclesPerYield;
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);
@ -1130,6 +1101,7 @@ CPU8080.prototype.runCPU = function(fUpdateFocus)
this.setError(e.stack || e.message);
return;
}
setTimeout(this.onRunTimeout, this.calcRemainingTime());
};
@ -1150,7 +1122,7 @@ CPU8080.prototype.startCPU = function(fUpdateFocus)
* threshold counter.
*/
this.setSpeed();
if (this.cmp) this.cmp.start(this.aCounts.msStartRun, this.getCycles());
if (this.cmp) this.cmp.start(this.counts.msStartRun, this.getCycles());
this.flags.fRunning = true;
this.flags.fStarting = true;
if (this.chipset) this.chipset.start();
@ -1218,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);
}
};
@ -1233,7 +1205,7 @@ CPU8080.prototype.updateCPU = function(fForce)
*/
CPU8080.prototype.yieldCPU = function()
{
this.aCounts.nCyclesNextYield = 0; // this will break us out of runCPU(), once we break out of stepCPU()
this.counts.nCyclesNextYield = 0; // this will break us out of runCPU(), once we break out of stepCPU()
this.nBurstCycles -= this.nStepCycles;
this.nStepCycles = 0; // this will break us out of stepCPU()
// if (DEBUG) this.nSnapCycles = this.nBurstCycles;

View file

@ -1287,11 +1287,6 @@ CPUDef8080.opMOVML = function()
*/
CPUDef8080.opHLT = function()
{
/*
* The CPU is never REALLY halted by a HLT instruction; instead, by setting X86.INTFLAG.HALT,
* we are signalling to stepCPU() that it's free to end the current burst AND that it should not
* execute any more instructions until checkINTR() indicates a hardware interrupt is requested.
*/
var addr = this.getPC() - 1;
/*
@ -1304,9 +1299,15 @@ CPUDef8080.opHLT = function()
}
}
this.intFlags |= CPUDef8080.INTFLAG.HALT;
this.nStepCycles -= 7;
/*
* The CPU is never REALLY halted by a HLT instruction; instead, we call requestHALT(), which
* signals to stepCPU() that it should end the current burst AND that it should not execute any
* more instructions until checkINTR() indicates a hardware interrupt has been requested.
*/
this.requestHALT();
/*
* If a Debugger is present and the HALT message category is enabled, then we REALLY halt the CPU,
* on the theory that whoever's using the Debugger would like to see HLTs.
@ -2823,8 +2824,9 @@ CPUDef8080.opJM = function()
*/
CPUDef8080.opEI = function()
{
this.setIF();var w = this.getHL();
this.setIF();
this.nStepCycles -= 4;
this.checkINTR();
};
/**

View file

@ -934,21 +934,38 @@ CPUState8080.prototype.pushWord = function(w)
* checkINTR()
*
* @this {CPUState8080}
* @return {boolean} true if h/w interrupt has just been acknowledged, false if not
* @return {boolean} true if execution may proceed, false if not
*/
CPUState8080.prototype.checkINTR = function()
{
if ((this.intFlags & CPUDef8080.INTFLAG.INTR) && this.getIF()) {
for (var nLevel = 0; nLevel < 8; nLevel++) {
if (this.intFlags & (1 << nLevel)) break;
/*
* If the Debugger is single-stepping, this.nStepCycles will always be zero, which we take
* advantage of here to avoid processing interrupts. The Debugger will have to issue a "g"
* command (or "p" command on a call instruction) if you want interrupts to be processed.
*/
if (this.nStepCycles) {
if ((this.intFlags & CPUDef8080.INTFLAG.INTR) && this.getIF()) {
for (var nLevel = 0; nLevel < 8; nLevel++) {
if (this.intFlags & (1 << nLevel)) break;
}
this.clearINTR(nLevel);
this.clearIF();
this.intFlags &= ~CPUDef8080.INTFLAG.HALT;
this.aOps[CPUDef8080.OPCODE.RST0 | (nLevel << 3)].call(this);
}
this.clearINTR(nLevel);
this.clearIF();
this.intFlags &= ~CPUDef8080.INTFLAG.HALT;
this.aOps[CPUDef8080.OPCODE.RST0 | (nLevel << 3)].call(this);
return true;
}
return false;
if (this.intFlags & CPUDef8080.INTFLAG.HALT) {
/*
* As discussed in opHLT(), the CPU is never REALLY halted by a HLT instruction; instead, opHLT()
* calls requestHALT(), which sets INTFLAG.HALT and signals to stepCPU() that it's free to end the
* current burst AND that it should not execute any more instructions until checkINTR() indicates
* that a hardware interrupt has been requested.
*/
this.nBurstCycles -= this.nStepCycles;
this.nStepCycles = 0;
return false;
}
return true;
};
/**
@ -968,14 +985,28 @@ CPUState8080.prototype.clearINTR = function(nLevel)
this.intFlags &= ~bitsClear;
};
/**
* requestHALT()
*
* @this {CPUState8080}
*/
CPUState8080.prototype.requestHALT = function()
{
this.intFlags |= CPUDef8080.INTFLAG.HALT;
this.nBurstCycles -= this.nStepCycles;
this.nStepCycles = 0;
};
/**
* requestINTR(nLevel)
*
* Request the corresponding interrupt level.
*
* Each interrupt level (0-7) has its own intFlags bit (0-7). If one or more of those bits are set,
* and the Interrupt Flag (IF) is also set, indicating that interrupts are enabled, then checkINTR()
* chooses one of those bits, clears it, clears IF, and executes the corresponding RST opcode.
* Each interrupt level (0-7) has its own intFlags bit (0-7). If the Interrupt Flag (IF) is also
* set, then we know that checkINTR() will want to issue the interrupt, so we end the current burst
* by setting nStepCycles to zero. But before we do, we subtract nStepCycles from nBurstCycles,
* so that the calculation of how many cycles were actually executed on this burst is correct.
*
* @this {CPUState8080}
* @param {number} nLevel (0-7)
@ -983,6 +1014,10 @@ CPUState8080.prototype.clearINTR = function(nLevel)
CPUState8080.prototype.requestINTR = function(nLevel)
{
this.intFlags |= (1 << nLevel);
if (this.getIF()) {
this.nBurstCycles -= this.nStepCycles;
this.nStepCycles = 0;
}
};
/**
@ -1099,63 +1134,22 @@ CPUState8080.prototype.stepCPU = function(nMinCycles)
*/
this.nBurstCycles = this.nStepCycles = nMinCycles;
do {
if (this.intFlags) {
/*
* We no longer call checkINTR() if the Debugger is single-stepping; you'll have to let the
* CPU run with a "g" (or a "p" on a call instruction) if you want interrupts to be processed.
*/
if (nMinCycles) {
/*
* NOTE: If checkINTR() returns true, it also clears INTFLAG.HALT, so we don't have to worry
* about the INTFLAG.HALT code below triggering.
*/
this.checkINTR();
/*
* If the Debugger is running, consider some new notification mechanism(s) regarding interrupt
* dispatches; the following code no longer applies, due to changes above.
*
* if (!nMinCycles && this.checkINTR()) {
* this.assert(DEBUGGER); // nMinCycles of zero should be generated ONLY by the Debugger
* if (DEBUGGER) {
* this.println("interrupt dispatched");
* break;
* }
* }
*/
/*
* NOTE: If checkINTR() returns false, INTFLAG.HALT must be set, so no instructions should be executed.
*/
if (this.checkINTR()) {
do {
if (DEBUGGER && fDebugCheck) {
if (this.dbg.checkInstruction(this.regPC, nDebugState)) {
this.stopCPU();
break;
}
nDebugState = 1;
}
if (this.intFlags & CPUDef8080.INTFLAG.HALT) {
/*
* As discussed in opHLT(), the CPU is never REALLY halted by a HLT instruction; instead,
* opHLT() sets CPUDef8080.INTFLAG.HALT, signalling to us that we're free to end the current burst
* AND that we should not execute any more instructions until checkINTR() indicates a hardware
* interrupt has been requested.
*
* One downside to this approach is that it *might* appear to the careful observer that we
* executed a full complement of instructions during bursts where CPUDef8080.INTFLAG.HALT was set,
* when in fact we did not. However, the steady advance of the overall cycle count, and thus
* the steady series calls to stepCPU(), is needed to ensure that timer updates, video updates,
* etc, all continue to occur at the expected rates.
*
* If necessary, we can add another bookkeeping cycle counter (eg, one that keeps tracks of the
* number of cycles during which we did not actually execute any instructions).
*/
this.nStepCycles = 0;
break;
}
}
this.aOps[this.getPCByte()].call(this);
if (DEBUGGER && fDebugCheck) {
if (this.dbg.checkInstruction(this.regPC, nDebugState)) {
this.stopCPU();
break;
}
nDebugState = 1;
}
this.aOps[this.getPCByte()].call(this);
} while (this.nStepCycles > 0);
} while (this.nStepCycles > 0);
}
return (this.flags.fComplete? this.nBurstCycles - this.nStepCycles : (this.flags.fComplete === undefined? 0 : -1));
};

View file

@ -2465,7 +2465,7 @@ if (DEBUGGER) {
sLine += (nSequence != null? '=' + nSequence.toString() : "");
} else {
var nCycles = this.cpu.getCycles();
sLine += "cycles=" + nCycles.toString() + " cs=" + str.toHex(this.cpu.aCounts.nChecksum);
sLine += "cycles=" + nCycles.toString() + " cs=" + str.toHex(this.cpu.counts.nChecksum);
}
}
return sLine;
@ -3719,11 +3719,8 @@ if (DEBUGGER) {
Debugger8080.prototype.doInfo = function(asArgs)
{
if (DEBUG) {
this.println("msPerYield: " + this.cpu.aCounts.msPerYield);
this.println("nCyclesPerBurst: " + this.cpu.aCounts.nCyclesPerBurst);
this.println("nCyclesPerYield: " + this.cpu.aCounts.nCyclesPerYield);
this.println("nCyclesPerVideoUpdate: " + this.cpu.aCounts.nCyclesPerVideoUpdate);
this.println("nCyclesPerStatusUpdate: " + this.cpu.aCounts.nCyclesPerStatusUpdate);
this.println("msPerYield: " + this.cpu.counts.msPerYield);
this.println("nCyclesPerYield: " + this.cpu.counts.nCyclesPerYield);
return true;
}
return false;
@ -3974,13 +3971,13 @@ if (DEBUGGER) {
if (asArgs[3] !== undefined) nCycles = +asArgs[3]; // warning: decimal instead of hex conversion
switch (asArgs[2]) {
case "int":
this.cpu.aCounts.nCyclesChecksumInterval = nCycles;
this.cpu.counts.nCyclesChecksumInterval = nCycles;
break;
case "start":
this.cpu.aCounts.nCyclesChecksumStart = nCycles;
this.cpu.counts.nCyclesChecksumStart = nCycles;
break;
case "stop":
this.cpu.aCounts.nCyclesChecksumStop = nCycles;
this.cpu.counts.nCyclesChecksumStop = nCycles;
break;
default:
this.println("unknown cs option");

View file

@ -494,6 +494,11 @@ Keyboard8080.prototype.setBinding = function(sHTMLType, sBinding, control, sValu
this.bindings[id] = control;
var fnDown = function(kbd, softCode) {
return function onKeyboardBindingDown(event) {
/*
* iOS Usability Improvement: Calling preventDefault() prevents rapid clicks from
* also being (mis)interpreted as a desire to "zoom" in on the machine.
*/
event.preventDefault();
kbd.onSoftKeyDown(softCode, true);
/*
* I'm assuming we only need to give focus back on the "up" event...
@ -507,8 +512,12 @@ Keyboard8080.prototype.setBinding = function(sHTMLType, sBinding, control, sValu
kbd.onSoftKeyDown(softCode, false);
/*
* Give focus back to the machine (since clicking the button takes focus away).
*
* if (kbd.cmp) kbd.cmp.updateFocus();
*
* iOS Usability Improvement: NOT calling updateFocus() keeps the soft keyboard down
* (assuming it was already down).
*/
if (kbd.cmp) kbd.cmp.updateFocus();
};
}(this, this.config.SOFTCODES[sBinding]);
if ('ontouchstart' in window) {
@ -580,9 +589,11 @@ Keyboard8080.prototype.powerDown = function(fSave, fShutdown)
Keyboard8080.VT100.INIT = [
[
Keyboard8080.VT100.STATUS.INIT, // bVT100Status
Keyboard8080.VT100.ADDRESS.INIT, // bVT100Address
-1 // iKeyNext
Keyboard8080.VT100.STATUS.INIT, // bVT100Status
Keyboard8080.VT100.ADDRESS.INIT, // bVT100Address
false, // fVT100UARTBusy
0, // nVT100UARTCycleSnap
-1 // iKeyNext
]
];
@ -625,7 +636,7 @@ Keyboard8080.prototype.save = function()
case Keyboard8080.SI1978.MODEL:
break;
case Keyboard8080.VT100.MODEL:
state.set(0, [this.bVT100Status, this.bVT100Address, -1]);
state.set(0, [this.bVT100Status, this.bVT100Address, this.fVT100UARTBusy, this.nVT100UARTCycleSnap, -1]);
break;
}
return state.data();
@ -652,7 +663,9 @@ Keyboard8080.prototype.restore = function(data)
this.bVT100Status = a[0];
this.updateLEDs(this.bVT100Status & Keyboard8080.VT100.STATUS.LEDS);
this.bVT100Address = a[1];
this.iKeyNext = a[2];
this.fVT100UARTBusy = a[2];
this.nVT100UARTCycleSnap = a[3];
this.iKeyNext = a[4];
return true;
}
}
@ -871,17 +884,53 @@ Keyboard8080.prototype.checkSoftKeysToRelease = function()
};
/**
* isTransmitterReady()
* isVT100TransmitterReady()
*
* Called whenever a ChipSet circuit needs the Keyboard UART's transmitter status.
* Currently, we have no busy conditions (our virtual keyboard transmitter is infinitely fast).
* Called whenever the VT100 ChipSet circuit needs the Keyboard UART's transmitter status.
*
* From p. 4-32 of the VT100 Technical Manual (July 1982):
*
* The operating clock for the keyboard interface comes from an address line in the video processor (LBA4).
* This signal has an average period of 7.945 microseconds. Each data byte is transmitted with one start bit
* and one stop bit, and each bit lasts 16 clock periods. The total time for each data byte is 160 times 7.945
* or 1.27 milliseconds. Each time the Transmit Buffer Empty flag on the terminal's UART gets set (when the
* current byte is being transmitted), the microprocessor loads another byte into the transmit buffer. In this
* way, the stream of status bytes to the keyboard is continuous.
*
* We used to always return true (after all, what's wrong with an infinitely fast UART?), but unfortunately,
* the VT100 firmware relies on the UART's slow transmission speed to drive cursor blink rate. We have several
* options:
*
* 1) Snapshot the CPU cycle count each time a byte is transmitted (see outVT100UARTStatus()) and then every
* time this is polled, see if the cycle count has exceeded the snapshot value by the necessary threshold;
* if we assume 361.69ns per CPU cycle, there are 22 CPU cycles for every 1 LBA4 cycle, and since transmission
* time is supposed to last for 160 LBA4 cycles, the threshold is 22*160 CPU cycles, or 3520 cycles.
*
* 2) Set a CPU timer using the new setTimer() interface, which can be passed the number of milliseconds to
* wait before firing (in this case, roughly 1.27ms).
*
* 3) Call the ChipSet's getVT100LBA(4) function for the state of the simulated LBA4, and count 160 LBA4
* transitions; however, that would be the worst solution, because there's no guarantee that the firmware's
* UART polling will occur regularly and/or frequently enough for us to catch every LBA4 transition.
*
* I'm going with solution #1 because it's less overhead.
*
* @this {Keyboard8080}
* @return {boolean} (true if ready, false if not)
*/
Keyboard8080.prototype.isTransmitterReady = function()
Keyboard8080.prototype.isVT100TransmitterReady = function()
{
return true;
if (this.fVT100UARTBusy) {
/*
* 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.getMSCycles(1.2731488)) {
this.fVT100UARTBusy = false;
}
}
return !this.fVT100UARTBusy;
};
/**
@ -944,6 +993,8 @@ Keyboard8080.prototype.outVT100UARTStatus = function(port, b, addrFrom)
{
this.printMessageIO(port, b, addrFrom, "KBDUART.STATUS");
this.bVT100Status = b;
this.fVT100UARTBusy = true;
this.nVT100UARTCycleSnap = this.cpu.getCycles();
this.updateLEDs(b & Keyboard8080.VT100.STATUS.LEDS);
if (b & Keyboard8080.VT100.STATUS.START) {
this.iKeyNext = 0;

View file

@ -369,8 +369,12 @@ SerialPort8080.prototype.setBinding = function(sHTMLType, sBinding, control, sVa
serial.receiveData(sValue);
/*
* Give focus back to the machine (since clicking the button takes focus away).
*
* if (serial.cmp) serial.cmp.updateFocus();
*
* iOS Usability Improvement: NOT calling updateFocus() keeps the soft keyboard down
* (assuming it was already down).
*/
if (serial.cmp) serial.cmp.updateFocus();
return true;
};
return true;
@ -380,6 +384,58 @@ SerialPort8080.prototype.setBinding = function(sHTMLType, sBinding, control, sVa
return false;
};
/**
* echoByte(b)
*
* @this {SerialPort8080}
* @param {number} b
* @return {boolean} true if echo, false if not
*/
SerialPort8080.prototype.echoByte = function(b)
{
var fEchoed = false;
if (this.controlIOBuffer) {
if (b == 0x08) {
this.controlIOBuffer.value = this.controlIOBuffer.value.slice(0, -1);
/*
* TODO: Back up the correct number of columns if the character erased was a tab.
*/
if (this.iLogicalCol > 0) this.iLogicalCol--;
}
else {
var s = str.toASCIICode(b);
var nChars = s.length;
if (b == 0x09) {
var tabSize = this.tabSize || 8;
nChars = tabSize - (this.iLogicalCol % tabSize);
if (this.tabSize) s = str.pad("", nChars);
}
else if (b == 0x0D) {
this.iLogicalCol = nChars = 0;
s = "\n";
}
if (this.charBOL && !this.iLogicalCol && nChars) s = String.fromCharCode(this.charBOL) + s;
this.controlIOBuffer.value += s;
this.controlIOBuffer.scrollTop = this.controlIOBuffer.scrollHeight;
this.iLogicalCol += nChars;
}
fEchoed = true;
}
else if (this.consoleOutput != null) {
if (b == 0x0A || this.consoleOutput.length >= 1024) {
this.println(this.consoleOutput);
this.consoleOutput = "";
}
if (b != 0x0A) {
this.consoleOutput += String.fromCharCode(b);
}
fEchoed = true;
}
return fEchoed;
};
/**
* initBus(cmp, bus, cpu, dbg)
*
@ -616,6 +672,7 @@ SerialPort8080.prototype.getBaudTimeout = function(maskRate)
*/
SerialPort8080.prototype.receiveByte = function(b)
{
if (MAXDEBUG) this.echoByte(b);
this.printMessage("receiveByte(" + str.toHexByte(b) + "), status=" + str.toHexByte(this.bStatus));
if (!this.fAutoStop && !(this.bStatus & SerialPort8080.UART8251.STATUS.RECV_FULL)) {
this.bDataIn = b;
@ -689,41 +746,7 @@ SerialPort8080.prototype.transmitByte = function(b)
}
}
if (this.controlIOBuffer) {
if (b == 0x08) {
this.controlIOBuffer.value = this.controlIOBuffer.value.slice(0, -1);
/*
* TODO: Back up the correct number of columns if the character erased was a tab.
*/
if (this.iLogicalCol > 0) this.iLogicalCol--;
}
else {
var s = str.toASCIICode(b);
var nChars = s.length;
if (b == 0x09) {
var tabSize = this.tabSize || 8;
nChars = tabSize - (this.iLogicalCol % tabSize);
if (this.tabSize) s = str.pad("", nChars);
}
else if (b == 0x0D) {
this.iLogicalCol = nChars = 0;
s = "\n";
}
if (this.charBOL && !this.iLogicalCol && nChars) s = String.fromCharCode(this.charBOL) + s;
this.controlIOBuffer.value += s;
this.controlIOBuffer.scrollTop = this.controlIOBuffer.scrollHeight;
this.iLogicalCol += nChars;
}
fTransmitted = true;
}
else if (this.consoleOutput != null) {
if (b == 0x0A || this.consoleOutput.length >= 1024) {
this.println(this.consoleOutput);
this.consoleOutput = "";
}
if (b != 0x0A) {
this.consoleOutput += String.fromCharCode(b);
}
if (this.echoByte(b)) {
fTransmitted = true;
}
@ -804,7 +827,7 @@ SerialPort8080.prototype.outData = function(port, bOut, addrFrom)
this.bStatus &= ~(SerialPort8080.UART8251.STATUS.XMIT_READY | SerialPort8080.UART8251.STATUS.XMIT_EMPTY);
/*
* If we're transmitting to a virtual device that has no measurable delay, this code may clear XMIT_READY
* too quickly.
* too quickly:
*
* if (this.transmitByte(bOut)) {
* this.bStatus |= (SerialPort8080.UART8251.STATUS.XMIT_READY | SerialPort8080.UART8251.STATUS.XMIT_EMPTY);

View file

@ -249,8 +249,6 @@ function Video8080(parmsVideo, canvas, context, textarea, container)
}
this.ledBindings = {};
if (DEBUG) this.nCyclesPrev = 0;
}
Component.subclass(Video8080);
@ -430,6 +428,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();
};
@ -618,9 +623,10 @@ Video8080.prototype.powerUp = function(data, fRepower)
* Because the VT100 frame buffer can be located anywhere in RAM (above 0x2000), we must defer this
* test code until the powerUp() notification handler is called, when all RAM has (hopefully) been allocated.
*
* TODO: Remove this display test code once the VT100 is fully operational.
* NOTE: The following test screen was useful for early testing, but a *real* VT100 doesn't display a test screen,
* so this code is no longer enabled by default. Remove MAXDEBUG if you want to see it again.
*/
if (this.nFormat == Video8080.FORMAT.VT100) {
if (MAXDEBUG && this.nFormat == Video8080.FORMAT.VT100) {
/*
* Build a test screen in the VT100 frame buffer; we'll mimic the "SET-UP A" screen, since it uses
* all the font variations. The process involves iterating over 0-based row numbers -2 (or -5 if 50Hz
@ -796,7 +802,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;
}
@ -901,14 +907,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);
};
/**
@ -1137,16 +1143,6 @@ Video8080.prototype.updateVT100 = function(fForced)
this.assert(font < 0 || iCell === this.nCellCache);
if (MAXDEBUG && !fForced) {
var nSeconds = Date.now() / 1000;
if ((nSeconds|0) != (this.nUpdateSeconds|0)) {
this.nUpdateNumber = 0;
}
this.nUpdateNumber++;
this.nUpdateSeconds = nSeconds;
this.printMessage("updateVT100(): update #" + this.nUpdateNumber + " at " +this.nUpdateSeconds + " corner=" + str.toHexByte(this.aCellCache[1]) + " cycles=" + this.nCyclesPrev + " delta=" + this.nCyclesDelta);
}
if (!fForced && this.fSkipSingleCellUpdate && cUpdated == 1) {
/*
* We're going to blow off this update, since it comes on the heels of a smooth-scroll that *may*
@ -1193,7 +1189,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
@ -1204,22 +1200,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.
*/
@ -1247,13 +1241,8 @@ Video8080.prototype.updateScreen = function(n)
fUpdate = false;
}
}
}
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);
this.cpu.setTimer(this.timerUpdateNext, this.getRefreshTime());
this.nUpdates++;
}
if (!fUpdate) {

View file

@ -37,6 +37,7 @@ if (NODE) {
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Interrupts = require("./interrupts");
var Messages = require("./messages");
var X86 = require("./x86");

View file

@ -68,6 +68,7 @@ if (NODE) {
var ReportAPI = require("../../shared/lib/reportapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var Bus = require("./bus");
}

View file

@ -677,59 +677,6 @@ if (DEBUGGER) {
Debugger.TYPE_80387 = Debugger.TYPE_80386;
Debugger.TYPE_CPU_SHIFT = 14;
/*
* Message categories supported by the messageEnabled() function and other assorted message
* functions. Each category has a corresponding bit value that can be combined (ie, OR'ed) as
* needed. The Debugger's message command ("m") is used to turn message categories on and off,
* like so:
*
* m port on
* m port off
* ...
*
* NOTE: The order of these categories can be rearranged, alphabetized, etc, as desired; just be
* aware that changing the bit values could break saved Debugger states (not a huge concern, just
* something to be aware of).
*/
Debugger.MESSAGES = {
"cpu": Messages.CPU,
"seg": Messages.SEG,
"desc": Messages.DESC,
"tss": Messages.TSS,
"int": Messages.INT,
"fault": Messages.FAULT,
"bus": Messages.BUS,
"mem": Messages.MEM,
"port": Messages.PORT,
"dma": Messages.DMA,
"pic": Messages.PIC,
"timer": Messages.TIMER,
"cmos": Messages.CMOS,
"rtc": Messages.RTC,
"8042": Messages.C8042,
"chipset": Messages.CHIPSET, // ie, anything else in ChipSet besides DMA, PIC, TIMER, CMOS, RTC and 8042
"keyboard": Messages.KEYBOARD, // "kbd" is also allowed as shorthand for "keyboard"; see doMessages()
"key": Messages.KEYS, // using "key" instead of "keys", since the latter is a method on JavasScript objects
"video": Messages.VIDEO,
"fdc": Messages.FDC,
"hdc": Messages.HDC,
"disk": Messages.DISK,
"parallel": Messages.PARALLEL,
"serial": Messages.SERIAL,
"mouse": Messages.MOUSE,
"speaker": Messages.SPEAKER,
"computer": Messages.COMPUTER,
"dos": Messages.DOS,
"data": Messages.DATA,
"log": Messages.LOG,
"warn": Messages.WARN,
/*
* Now we turn to message actions rather than message types; for example, setting "halt"
* on or off doesn't enable "halt" messages, but rather halts the CPU on any message above.
*/
"halt": Messages.HALT
};
Debugger.HISTORY_LIMIT = DEBUG? 100000 : 1000;
/*
@ -1772,21 +1719,21 @@ if (DEBUGGER) {
case Interrupts.WINDBG.KRNLVARS: // 0x005A
/*
* BX = version number of this data (0x3A0)
* DX:CX points to:
* WORD hGlobalHeap ****
* WORD pGlobalHeap ****
* WORD hExeHead ****
* WORD hExeSweep
* WORD topPDB
* WORD headPDB
* WORD topsizePDB
* WORD headTDB ****
* WORD curTDB ****
* WORD loadTDB
* WORD LockTDB
* WORD SelTableLen ****
* DWORD SelTableStart ****
* BX = version number of this data (0x3A0)
* DX:CX points to:
* WORD hGlobalHeap ****
* WORD pGlobalHeap ****
* WORD hExeHead ****
* WORD hExeSweep
* WORD topPDB
* WORD headPDB
* WORD topsizePDB
* WORD headTDB ****
* WORD curTDB ****
* WORD loadTDB
* WORD LockTDB
* WORD SelTableLen ****
* DWORD SelTableStart ****
*/
break;
@ -2008,7 +1955,7 @@ if (DEBUGGER) {
/*
* AL == segment type:
* 0x80 device driver code seg
* 0x81 device driver data seg
* 0x81 device driver data seg
* ES:DI -> D386_Device_Params structure (see addSectionInfo() for details)
*/
this.addSectionInfo(this.newAddr(DI, ES), !(AL & 0x1), !!this.fWinDbgRM);
@ -3410,9 +3357,9 @@ if (DEBUGGER) {
*/
var aEnable = this.parseCommand(sEnable.replace("keys","key").replace("kbd","keyboard"), false, '|');
if (aEnable.length) {
for (var m in Debugger.MESSAGES) {
for (var m in Messages.CATEGORIES) {
if (usr.indexOf(aEnable, m) >= 0) {
this.bitsMessage |= Debugger.MESSAGES[m];
this.bitsMessage |= Messages.CATEGORIES[m];
this.println(m + " messages enabled");
}
}
@ -3430,8 +3377,8 @@ if (DEBUGGER) {
*/
Debugger.prototype.messageDump = function(bitMessage, fnDumper)
{
for (var m in Debugger.MESSAGES) {
if (bitMessage == Debugger.MESSAGES[m]) {
for (var m in Messages.CATEGORIES) {
if (bitMessage == Messages.CATEGORIES[m]) {
this.afnDumpers[m] = fnDumper;
return true;
}
@ -6404,7 +6351,7 @@ if (DEBUGGER) {
if (sAddr == '?') {
var sDumpers = "";
for (m in Debugger.MESSAGES) {
for (m in Messages.CATEGORIES) {
if (this.afnDumpers[m]) {
if (sDumpers) sDumpers += ',';
sDumpers = sDumpers + m;
@ -6487,7 +6434,7 @@ if (DEBUGGER) {
this.doLoad(asArgs);
return;
}
for (m in Debugger.MESSAGES) {
for (m in Messages.CATEGORIES) {
if (asArgs[1] == m) {
var fnDumper = this.afnDumpers[m];
if (fnDumper) {
@ -7007,9 +6954,9 @@ if (DEBUGGER) {
*/
if (sCategory == "keys") sCategory = "key";
if (sCategory == "kbd") sCategory = "keyboard";
for (m in Debugger.MESSAGES) {
for (m in Messages.CATEGORIES) {
if (sCategory == m) {
bitsMessage = Debugger.MESSAGES[m];
bitsMessage = Messages.CATEGORIES[m];
fCriteria = !!(this.bitsMessage & bitsMessage);
break;
}
@ -7036,9 +6983,9 @@ if (DEBUGGER) {
*/
var n = 0;
var sCategories = "";
for (m in Debugger.MESSAGES) {
for (m in Messages.CATEGORIES) {
if (!sCategory || sCategory == m) {
var bitMessage = Debugger.MESSAGES[m];
var bitMessage = Messages.CATEGORIES[m];
var fEnabled = !!(this.bitsMessage & bitMessage);
if (fCriteria !== null && fCriteria != fEnabled) continue;
if (sCategories) sCategories += ',';

View file

@ -37,6 +37,7 @@ if (NODE) {
var DiskAPI = require("../../shared/lib/diskapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var ChipSet = require("./chipset");
var Disk = require("./disk");

View file

@ -37,6 +37,7 @@ if (NODE) {
var DiskAPI = require("../../shared/lib/diskapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Interrupts = require("./interrupts");
var Messages = require("./messages");
var ChipSet = require("./chipset");

View file

@ -36,6 +36,7 @@ if (NODE) {
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var ChipSet = require("./chipset");
var CPU = require("./cpu");

View file

@ -82,4 +82,57 @@ var Messages = {
HALT: 0x80000000|0
};
/*
* Message categories supported by the messageEnabled() function and other assorted message
* functions. Each category has a corresponding bit value that can be combined (ie, OR'ed) as
* needed. The Debugger's message command ("m") is used to turn message categories on and off,
* like so:
*
* m port on
* m port off
* ...
*
* NOTE: The order of these categories can be rearranged, alphabetized, etc, as desired; just be
* aware that changing the bit values could break saved Debugger states (not a huge concern, just
* something to be aware of).
*/
Messages.CATEGORIES = {
"cpu": Messages.CPU,
"seg": Messages.SEG,
"desc": Messages.DESC,
"tss": Messages.TSS,
"int": Messages.INT,
"fault": Messages.FAULT,
"bus": Messages.BUS,
"mem": Messages.MEM,
"port": Messages.PORT,
"dma": Messages.DMA,
"pic": Messages.PIC,
"timer": Messages.TIMER,
"cmos": Messages.CMOS,
"rtc": Messages.RTC,
"8042": Messages.C8042,
"chipset": Messages.CHIPSET, // ie, anything else in ChipSet besides DMA, PIC, TIMER, CMOS, RTC and 8042
"keyboard": Messages.KEYBOARD, // "kbd" is also allowed as shorthand for "keyboard"; see doMessages()
"key": Messages.KEYS, // using "key" instead of "keys", since the latter is a method on JavasScript objects
"video": Messages.VIDEO,
"fdc": Messages.FDC,
"hdc": Messages.HDC,
"disk": Messages.DISK,
"parallel": Messages.PARALLEL,
"serial": Messages.SERIAL,
"mouse": Messages.MOUSE,
"speaker": Messages.SPEAKER,
"computer": Messages.COMPUTER,
"dos": Messages.DOS,
"data": Messages.DATA,
"log": Messages.LOG,
"warn": Messages.WARN,
/*
* Now we turn to message actions rather than message types; for example, setting "halt"
* on or off doesn't enable "halt" messages, but rather halts the CPU on any message above.
*/
"halt": Messages.HALT
};
if (NODE) module.exports = Messages;

View file

@ -36,6 +36,7 @@ if (NODE) {
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var SerialPort = require("./serialport");
}

View file

@ -36,6 +36,7 @@ if (NODE) {
var usr = require("../../shared/lib/usrlib");
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var PCX86 = require("./defines");
var Bus = require("./bus");
var Memory = require("./memory");
var X86 = require("./x86");

View file

@ -36,6 +36,7 @@ if (NODE) {
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var ChipSet = require("./chipset");
}

View file

@ -36,6 +36,7 @@ if (NODE) {
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Memory = require("./memory");
var ROM = require("./rom");
}

View file

@ -36,6 +36,7 @@ if (NODE) {
var web = require("../../shared/lib/weblib");
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var PCX86 = require("./defines");
var Memory = require("./memory");
}

View file

@ -36,6 +36,7 @@ if (NODE) {
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var ChipSet = require("./chipset");
}

View file

@ -37,6 +37,7 @@ if (NODE) {
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Memory = require("./memory");
var Messages = require("./messages");
var ChipSet = require("./chipset");
@ -496,16 +497,16 @@ Video.TRAPALL = true; // monitor all I/O by default (not just deltas)
*
* Also, for quick reference, IBM VGA register values for the standard VGA modes (from http://www.pcjs.org/blog/2015/06/01/):
*
* INT 0x10 Mode Requested: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x0D 0x0E 0x10 0x12 0x13
* INT 0x10 Mode Requested: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x0D 0x0E 0x10 0x12 0x13
*
* BIOSMODE: 0x01 0x01 0x03 0x03 0x04 0x04 0x06 0x0D 0x0E 0x10 0x12 0x13
* CRTC[0x00]: HTOTAL 0x2D 0x2D 0x5F 0x5F 0x2D 0x2D 0x5F 0x2D 0x5F 0x5F 0x5F 0x5F
* CRTC[0x01]: HDISP_END 0x27 0x27 0x4F 0x4F 0x27 0x27 0x4F 0x27 0x4F 0x4F 0x4F 0x4F
* CRTC[0x02]: HBLANK_START 0x28 0x28 0x50 0x50 0x28 0x28 0x50 0x28 0x50 0x50 0x50 0x50
* CRTC[0x03]: HBLANK_END 0x90 0x90 0x82 0x82 0x90 0x90 0x82 0x90 0x82 0x82 0x82 0x82
* CRTC[0x04]: HRETRACE_START 0x2B 0x2B 0x55 0x55 0x2B 0x2B 0x54 0x2B 0x54 0x54 0x54 0x54
* CRTC[0x05]: HRETRACE_END 0xA0 0xA0 0x81 0x81 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80
* CRTC[0x06]: VTOTAL 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0x0B 0xBF
* CRTC[0x00]: HTOTAL 0x2D 0x2D 0x5F 0x5F 0x2D 0x2D 0x5F 0x2D 0x5F 0x5F 0x5F 0x5F
* CRTC[0x01]: HDISP_END 0x27 0x27 0x4F 0x4F 0x27 0x27 0x4F 0x27 0x4F 0x4F 0x4F 0x4F
* CRTC[0x02]: HBLANK_START 0x28 0x28 0x50 0x50 0x28 0x28 0x50 0x28 0x50 0x50 0x50 0x50
* CRTC[0x03]: HBLANK_END 0x90 0x90 0x82 0x82 0x90 0x90 0x82 0x90 0x82 0x82 0x82 0x82
* CRTC[0x04]: HRETRACE_START 0x2B 0x2B 0x55 0x55 0x2B 0x2B 0x54 0x2B 0x54 0x54 0x54 0x54
* CRTC[0x05]: HRETRACE_END 0xA0 0xA0 0x81 0x81 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80
* CRTC[0x06]: VTOTAL 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0x0B 0xBF
* CRTC[0x07]: OVERFLOW 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x3E 0x1F
* CRTC[0x08]: PRESET_ROW 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
* CRTC[0x09]: MAX_SCAN 0x4F 0x4F 0x4F 0x4F 0xC1 0xC1 0xC1 0xC0 0xC0 0x40 0x40 0x41
@ -515,13 +516,13 @@ Video.TRAPALL = true; // monitor all I/O by default (not just deltas)
* CRTC[0x0D]: START_ADDR_LO 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
* CRTC[0x0E]: CURSOR_ADDR_HI 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x00
* CRTC[0x0F]: CURSOR_ADDR_LO 0x19 0x19 0x41 0x41 0x19 0x19 0x41 0x19 0x41 0x41 0xE1 0xA2
* CRTC[0x10]: VRETRACE_START 0x9C 0x9C 0x9C 0x9C 0x9C 0x9C 0x9C 0x9C 0x9C 0x83 0xEA 0x9C
* CRTC[0x11]: VRETRACE_END 0x8E 0x8E 0x8E 0x8E 0x8E 0x8E 0x8E 0x8E 0x8E 0x85 0x8C 0x8E
* CRTC[0x12]: VDISP_END 0x8F 0x8F 0x8F 0x8F 0x8F 0x8F 0x8F 0x8F 0x8F 0x5D 0xDF 0x8F
* CRTC[0x10]: VRETRACE_START 0x9C 0x9C 0x9C 0x9C 0x9C 0x9C 0x9C 0x9C 0x9C 0x83 0xEA 0x9C
* CRTC[0x11]: VRETRACE_END 0x8E 0x8E 0x8E 0x8E 0x8E 0x8E 0x8E 0x8E 0x8E 0x85 0x8C 0x8E
* CRTC[0x12]: VDISP_END 0x8F 0x8F 0x8F 0x8F 0x8F 0x8F 0x8F 0x8F 0x8F 0x5D 0xDF 0x8F
* CRTC[0x13]: OFFSET 0x14 0x14 0x28 0x28 0x14 0x14 0x28 0x14 0x28 0x28 0x28 0x28
* CRTC[0x14]: UNDERLINE 0x1F 0x1F 0x1F 0x1F 0x00 0x00 0x00 0x00 0x00 0x0F 0x00 0x40
* CRTC[0x15]: VBLANK_START 0x96 0x96 0x96 0x96 0x96 0x96 0x96 0x96 0x96 0x63 0xE7 0x96
* CRTC[0x16]: VBLANK_END 0xB9 0xB9 0xB9 0xB9 0xB9 0xB9 0xB9 0xB9 0xB9 0xBA 0x04 0xB9
* CRTC[0x15]: VBLANK_START 0x96 0x96 0x96 0x96 0x96 0x96 0x96 0x96 0x96 0x63 0xE7 0x96
* CRTC[0x16]: VBLANK_END 0xB9 0xB9 0xB9 0xB9 0xB9 0xB9 0xB9 0xB9 0xB9 0xBA 0x04 0xB9
* CRTC[0x17]: MODE_CTRL 0xA3 0xA3 0xA3 0xA3 0xA2 0xA2 0xC2 0xE3 0xE3 0xE3 0xE3 0xA3
* CRTC[0x18]: LINE_COMPARE 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
* GRC[0x00]: SRESET 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
@ -557,7 +558,7 @@ Video.TRAPALL = true; // monitor all I/O by default (not just deltas)
* ATC[0x10]: MODE 0x0C 0x0C 0x0C 0x0C 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x41
* ATC[0x11]: OVERSCAN 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
* ATC[0x12]: PLANES 0x0F 0x0F 0x0F 0x0F 0x03 0x03 0x01 0x0F 0x0F 0x0F 0x0F 0x0F
* ATC[0x13]: HPAN 0x08 0x08 0x08 0x08 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
* ATC[0x13]: HPAN 0x08 0x08 0x08 0x08 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
*
* TODO: Build a similar table for the IBM EGA, and then work on rationalizing the mode detection logic in checkMode().
*/
@ -6304,22 +6305,22 @@ Video.prototype.inStatus0 = function(port, addrFrom)
* thing again with 5 more arrays, expecting the 4th byte in all 5 arrays to match SWSENSE, and being
* very unhappy if they don't:
*
* db 0x14,0x14,0x14,0x10
* db 0x2D,0x14,0x14,0x00
* db 0x14,0x2D,0x14,0x00
* db 0x14,0x14,0x2D,0x00
* db 0x2D,0x2D,0x2D,0x00
* db 0x14,0x14,0x14,0x10
* db 0x2D,0x14,0x14,0x00
* db 0x14,0x2D,0x14,0x00
* db 0x14,0x14,0x2D,0x00
* db 0x2D,0x2D,0x2D,0x00
*
* I ensure much happiness by setting SWSENSE unless any of the three 6-bit DAC values contain 0x2D.
*
* This hard-coded behavior assumes a color monitor. If you really want to simulate a monochrome monitor,
* then the 1st array (above) must mismatch, and a different set of arrays must all match:
*
* db 0x04,0x12,0x04,0x10
* db 0x1E,0x12,0x04,0x00
* db 0x04,0x2D,0x04,0x00
* db 0x04,0x16,0x15,0x00
* db 0x00,0x00,0x00,0x10
* db 0x04,0x12,0x04,0x10
* db 0x1E,0x12,0x04,0x00
* db 0x04,0x2D,0x04,0x00
* db 0x04,0x16,0x15,0x00
* db 0x00,0x00,0x00,0x10
*
* In other words, for a monochrome monitor, set SWSENSE only when DAC register #0 matches the first and last
* sets of values.

View file

@ -36,6 +36,7 @@ if (NODE) {
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var Memory = require("./memory");
var CPU = require("./cpu");

View file

@ -39,6 +39,7 @@ if (NODE) {
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var X86 = require("./x86");
}