Fixed lost 8080 interrupts, and added new CPU-driven setTimer() functionality
This commit is contained in:
parent
12dad7291c
commit
99023617fd
6 changed files with 562 additions and 447 deletions
|
|
@ -122,6 +122,11 @@ function CPU(parmsCPU, nCyclesDefault)
|
|||
this.aCounts.nCyclesChecksumInterval = parmsCPU["csInterval"];
|
||||
this.aCounts.nCyclesChecksumStop = parmsCPU["csStop"];
|
||||
|
||||
/*
|
||||
* Array of countdown timers managed by addTimer() and setTimer().
|
||||
*/
|
||||
this.aTimers = [];
|
||||
|
||||
this.onRunTimeout = this.runCPU.bind(this); // function onRunTimeout() { cpu.runCPU(); };
|
||||
|
||||
this.setReady();
|
||||
|
|
@ -536,6 +541,9 @@ CPU.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
|||
* in anticipation of the timer requiring an update sooner than the normal nCyclesPerYield
|
||||
* period in runCPU() would normally provide.
|
||||
*
|
||||
* NOTE: In this context, "timer" refers to a timer chip (eg, an Intel 8253) being emulated by
|
||||
* by the ChipSet component, not the timers managed by the CPU (eg, addTimer(), setTimer(), etc).
|
||||
*
|
||||
* @this {CPU}
|
||||
* @param {number} nCycles is the target number of cycles to drop the current burst to
|
||||
* @return {boolean}
|
||||
|
|
@ -754,14 +762,14 @@ CPU.prototype.getSpeedTarget = function()
|
|||
*
|
||||
* NOTE: This used to return the target speed, in mhz, but no callers appear to care at this point.
|
||||
*
|
||||
* @desc Whenever the speed is changed, the running cycle count and corresponding start time must be reset,
|
||||
* so that the next effective speed calculation obtains sensible results. In fact, when runCPU() initially calls
|
||||
* setSpeed() with no parameters, that's all this function does (it doesn't change the current speed setting).
|
||||
*
|
||||
* @this {CPU}
|
||||
* @param {number} [nMultiplier] is the new proposed multiplier (reverts to 1 if the target was too high)
|
||||
* @param {boolean} [fUpdateFocus] is true to update Computer focus
|
||||
* @return {boolean} true if successful, false if not
|
||||
*
|
||||
* @desc Whenever the speed is changed, the running cycle count and corresponding start time must be reset,
|
||||
* so that the next effective speed calculation obtains sensible results. In fact, when runCPU() initially calls
|
||||
* setSpeed() with no parameters, that's all this function does (it doesn't change the current speed setting).
|
||||
*/
|
||||
CPU.prototype.setSpeed = function(nMultiplier, fUpdateFocus)
|
||||
{
|
||||
|
|
@ -939,6 +947,104 @@ CPU.prototype.calcRemainingTime = function()
|
|||
return msRemainsThisRun;
|
||||
};
|
||||
|
||||
/**
|
||||
* addTimer(callBack)
|
||||
*
|
||||
* Components that want to have timers that periodically fire after some number of milliseconds call
|
||||
* addTimer() to create the timer, and then setTimer() every time they want to arm it. There is currently
|
||||
* no removeTimer() because these are generally used for the entire lifetime of a component.
|
||||
*
|
||||
* Internally, each timer entry is a preallocated Array with two entries: a cycle countdown in element [0]
|
||||
* and a callback function in element [1]. A timer is initially dormant; dormant timers have a countdown
|
||||
* value of -1 (although any negative number will suffice) and active timers have a non-negative value.
|
||||
*
|
||||
* Why not use JavaScript's setTimeout() instead? Good question. For a good answer, see setTimer() below.
|
||||
*
|
||||
* @this {CPU}
|
||||
* @param {function()} callBack
|
||||
* @return {number} timer index
|
||||
*/
|
||||
CPU.prototype.addTimer = function(callBack)
|
||||
{
|
||||
var iTimer = this.aTimers.length;
|
||||
this.aTimers.push([-1, callBack]);
|
||||
return iTimer;
|
||||
};
|
||||
|
||||
/**
|
||||
* setTimer(iTimer, ms)
|
||||
*
|
||||
* Using the timer index from a previous addTimer() call, this sets that timer to fire after the
|
||||
* specified number of milliseconds.
|
||||
*
|
||||
* 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).
|
||||
*
|
||||
* Ideally, the only function that would use setTimeout() is runCPU(), while the rest of the components would
|
||||
* use setTimer(); however, due to legacy code (ie, code that predates these functions) and/or laziness,
|
||||
* that's currently not the case. TODO: Fix.
|
||||
*
|
||||
* @this {CPU}
|
||||
* @param {number} iTimer
|
||||
* @param {number} ms (converted into a cycle countdown internally)
|
||||
* @return {number} (number of cycles used to arm timer, or -1 if error)
|
||||
*/
|
||||
CPU.prototype.setTimer = function(iTimer, ms)
|
||||
{
|
||||
var nCycles = -1;
|
||||
if (iTimer >= 0 && iTimer < this.aTimers.length) {
|
||||
nCycles = (this.aCounts.nCyclesPerSecond * this.aCounts.nCyclesMultiplier) / 1000 * ms;
|
||||
this.aTimers[iTimer][0] = nCycles;
|
||||
}
|
||||
return nCycles;
|
||||
};
|
||||
|
||||
/**
|
||||
* getTimerBurst(nCycles)
|
||||
*
|
||||
* Used by runCPU() to either accept or shorten the current burst if any timers need to fire soon.
|
||||
*
|
||||
* @this {CPU}
|
||||
* @param {number} nCycles (number of cycles about to execute)
|
||||
* @return {number} (either nCycles or less if a timer needs to fire)
|
||||
*/
|
||||
CPU.prototype.getTimerBurst = function(nCycles)
|
||||
{
|
||||
for (var i = 0; i < this.aTimers.length; i++) {
|
||||
var timer = this.aTimers[i];
|
||||
if (timer[0] < 0) continue;
|
||||
if (nCycles > timer[0]) {
|
||||
nCycles = timer[0];
|
||||
}
|
||||
}
|
||||
return nCycles;
|
||||
};
|
||||
|
||||
/**
|
||||
* updateTimers(nCycles)
|
||||
*
|
||||
* Used by runCPU() to reduce all active timer countdown values by the number of cycles just executed;
|
||||
* this is the function that actually "fires" any timer(s) whose countdown has reached (or dropped below)
|
||||
* zero, invoking their callback function.
|
||||
*
|
||||
* @this {CPU}
|
||||
* @param {number} nCycles (number of cycles actually executed)
|
||||
*/
|
||||
CPU.prototype.updateTimers = function(nCycles)
|
||||
{
|
||||
for (var i = 0; i < this.aTimers.length; i++) {
|
||||
var timer = this.aTimers[i];
|
||||
if (timer[0] < 0) continue;
|
||||
timer[0] -= nCycles;
|
||||
if (timer[0] <= 0) {
|
||||
timer[0] = -1; // zero is technically an "active" value, so ensure the timer is dormant now
|
||||
timer[1](); // safe to invoke the callback function now
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* runCPU(fUpdateFocus)
|
||||
*
|
||||
|
|
@ -962,22 +1068,37 @@ CPU.prototype.runCPU = function(fUpdateFocus)
|
|||
this.calcStartTime();
|
||||
try {
|
||||
do {
|
||||
var nCyclesPerBurst = (this.flags.fChecksum? 1 : this.aCounts.nCyclesPerBurst);
|
||||
|
||||
/*
|
||||
* 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).
|
||||
*/
|
||||
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);
|
||||
|
||||
/*
|
||||
* Execute the burst.
|
||||
*/
|
||||
this.stepCPU(nCyclesPerBurst);
|
||||
|
||||
/*
|
||||
* nBurstCycles, less any remaining nStepCycles, is how many cycles stepCPU() ACTUALLY ran (nCycles).
|
||||
* We add that to nCyclesThisRun, as well as nRunCycles, which is the cycle count since the CPU first
|
||||
* started running.
|
||||
* nCycles is how many cycles stepCPU() actually ran (nBurstCycles less any remaining nStepCycles).
|
||||
*/
|
||||
var nCycles = this.nBurstCycles - this.nStepCycles;
|
||||
this.nRunCycles += nCycles;
|
||||
|
||||
/*
|
||||
* Update any/all timers, firing those whose cycle countdowns have reached (or dropped below) zero.
|
||||
*/
|
||||
this.updateTimers(nCycles);
|
||||
|
||||
/*
|
||||
* Add nCycles to nCyclesThisRun, as well as nRunCycles (the cycle count since the CPU first started).
|
||||
*/
|
||||
this.aCounts.nCyclesThisRun += nCycles;
|
||||
this.nRunCycles += nCycles;
|
||||
this.addCycles(0, true);
|
||||
this.updateChecksum(nCycles);
|
||||
|
||||
|
|
|
|||
|
|
@ -60,17 +60,17 @@ var CPUDef = {
|
|||
* Processor Status flag definitions (stored in regPS)
|
||||
*/
|
||||
PS: {
|
||||
CF: 0x0001, // bit 0: Carry flag
|
||||
CF: 0x0001, // bit 0: Carry Flag
|
||||
BIT1: 0x0002, // bit 1: reserved, always set
|
||||
PF: 0x0004, // bit 2: Parity flag
|
||||
PF: 0x0004, // bit 2: Parity Flag
|
||||
BIT3: 0x0008, // bit 3: reserved, always clear
|
||||
AF: 0x0010, // bit 4: Auxiliary Carry flag
|
||||
AF: 0x0010, // bit 4: Auxiliary Carry Flag
|
||||
BIT5: 0x0020, // bit 5: reserved, always clear
|
||||
ZF: 0x0040, // bit 6: Zero flag
|
||||
SF: 0x0080, // bit 7: Sign flag
|
||||
ZF: 0x0040, // bit 6: Zero Flag
|
||||
SF: 0x0080, // bit 7: Sign Flag
|
||||
ALL: 0x00D5, // all "arithmetic" flags (CF, PF, AF, ZF, SF)
|
||||
MASK: 0x00FF, //
|
||||
IF: 0x0200 // bit 9: Interrupt flag (set if interrupts enabled; for internal use only)
|
||||
IF: 0x0200 // bit 9: Interrupt Flag (set if interrupts enabled; Intel calls this the INTE bit)
|
||||
},
|
||||
PARITY: [ // 256-byte array with a 1 wherever the number of set bits of the array index is EVEN
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
|
|
@ -94,10 +94,9 @@ var CPUDef = {
|
|||
* Interrupt-related flags (stored in intFlags)
|
||||
*/
|
||||
INTFLAG: {
|
||||
NONE: 0x00,
|
||||
INTL: 0x07, // last interrupt level requested
|
||||
INTR: 0x08, // set if interrupt has been requested
|
||||
HALT: 0x10 // halt requested; see opHLT()
|
||||
NONE: 0x0000,
|
||||
INTR: 0x00ff, // mask for 8 bits, representing interrupt levels 0-7
|
||||
HALT: 0x0100 // halt requested; see opHLT()
|
||||
},
|
||||
/*
|
||||
* Opcode definitions
|
||||
|
|
|
|||
|
|
@ -938,47 +938,50 @@ CPUState.prototype.pushWord = function(w)
|
|||
CPUState.prototype.checkINTR = function()
|
||||
{
|
||||
if ((this.intFlags & CPUDef.INTFLAG.INTR) && this.getIF()) {
|
||||
var bRST = CPUDef.OPCODE.RST0 | ((this.intFlags & CPUDef.INTFLAG.INTL) << 3);
|
||||
this.intFlags &= ~CPUDef.INTFLAG.HALT;
|
||||
this.clearINTR();
|
||||
for (var nLevel = 0; nLevel < 8; nLevel++) {
|
||||
if (this.intFlags & (1 << nLevel)) break;
|
||||
}
|
||||
this.clearINTR(nLevel);
|
||||
this.clearIF();
|
||||
this.aOps[bRST].call(this);
|
||||
this.intFlags &= ~CPUDef.INTFLAG.HALT;
|
||||
this.aOps[CPUDef.OPCODE.RST0 | (nLevel << 3)].call(this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* clearINTR()
|
||||
* clearINTR(nLevel)
|
||||
*
|
||||
* Clear the corresponding interrupt level.
|
||||
*
|
||||
* nLevel can either be a valid interrupt level (0-7), or -1 to clear all pending interrupts
|
||||
* (eg, in the event of a system-wide reset).
|
||||
*
|
||||
* @this {CPUState}
|
||||
* @param {number} nLevel (0-7, or -1 for all)
|
||||
*/
|
||||
CPUState.prototype.clearINTR = function()
|
||||
CPUState.prototype.clearINTR = function(nLevel)
|
||||
{
|
||||
this.intFlags &= ~(CPUDef.INTFLAG.INTL | CPUDef.INTFLAG.INTR);
|
||||
var bitsClear = nLevel < 0? 0xff : (1 << nLevel);
|
||||
this.intFlags &= ~bitsClear;
|
||||
};
|
||||
|
||||
/**
|
||||
* requestINTR(nLevel)
|
||||
*
|
||||
* This is called by any component that wants to request a h/w interrupt.
|
||||
* Request the corresponding interrupt level.
|
||||
*
|
||||
* NOTE: We allow INTR to be set regardless of the current state of interrupt flag (IF), on the theory
|
||||
* that if/when the CPU briefly turns interrupts off, it shouldn't lose the last h/w interrupt requested.
|
||||
* So instead of ignoring INTR here, checkINTR() ignores INTR as long as the interrupt flag (IF) is clear.
|
||||
*
|
||||
* The downside is that, as long as the CPU has interrupts disabled, an active INTR state will slow stepCPU()
|
||||
* down slightly. We could avoid that by introducing a two-stage interrupt tracking system, where a separate
|
||||
* variable keeps track of the last interrupt requested whenever the interrupt flag (IF) is clear, and when
|
||||
* setIF() finally occurs, that interrupt is propagated to intFlags. But for now, we're going to assume that
|
||||
* scenario is rare.
|
||||
* 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.
|
||||
*
|
||||
* @this {CPUState}
|
||||
* @param {number} nLevel (0-7)
|
||||
*/
|
||||
CPUState.prototype.requestINTR = function(nLevel)
|
||||
{
|
||||
this.intFlags = (this.intFlags & ~CPUDef.INTFLAG.INTL) | nLevel | CPUDef.INTFLAG.INTR;
|
||||
this.intFlags |= (1 << nLevel);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -115,15 +115,6 @@ function SerialPort(parmsSerial) {
|
|||
Component.bindExternalControl(this, sBinding, SerialPort.sIOBuffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Define a setTimeout() function that receiveData() can use when there's more data to receive.
|
||||
*/
|
||||
this.fnCheckDataReceived = function(serial) {
|
||||
return function() {
|
||||
serial.receiveData();
|
||||
}
|
||||
}(this);
|
||||
|
||||
/*
|
||||
* No connection until initBus() invokes initConnection().
|
||||
*/
|
||||
|
|
@ -368,10 +359,20 @@ SerialPort.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
|
||||
var serial = this;
|
||||
this.timerReceiveData = this.cpu.addTimer(function() {
|
||||
serial.printMessage("timerReceiveData()");
|
||||
serial.receiveData()
|
||||
});
|
||||
|
||||
this.chipset = /** @type {ChipSet} */ (cmp.getMachineComponent("ChipSet"));
|
||||
|
||||
bus.addPortInputTable(this, SerialPort.aPortInput, this.portBase);
|
||||
bus.addPortOutputTable(this, SerialPort.aPortOutput, this.portBase);
|
||||
|
||||
this.initConnection();
|
||||
|
||||
this.setReady();
|
||||
};
|
||||
|
||||
|
|
@ -555,9 +556,11 @@ SerialPort.prototype.getBaudTimeout = function(maskRate)
|
|||
*/
|
||||
SerialPort.prototype.receiveByte = function(b)
|
||||
{
|
||||
this.printMessage("receiveByte(" + str.toHexByte(b) + "): " + str.toHexByte(this.bStatus));
|
||||
if (!(this.bStatus & SerialPort.UART8251.STATUS.RECV_FULL)) {
|
||||
this.bDataIn = b;
|
||||
this.bStatus |= SerialPort.UART8251.STATUS.RECV_FULL;
|
||||
this.printMessage("receiveByte(" + str.toHexByte(b) + "): " + str.toHexByte(this.bStatus) + " (requesting interrupt)");
|
||||
this.cpu.requestINTR(this.nIRQ);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -575,21 +578,8 @@ SerialPort.prototype.receiveData = function()
|
|||
if (this.receiveByte(this.sDataReceived.charCodeAt(0))) {
|
||||
this.sDataReceived = this.sDataReceived.substr(1);
|
||||
}
|
||||
/*
|
||||
* TODO: If data has become undeliverable for some reason (eg, the Debugger has paused execution),
|
||||
* we should stop setting timeouts, and add one or more notification mechanisms to kickstart it again.
|
||||
*/
|
||||
if (this.sDataReceived) {
|
||||
/*
|
||||
* TODO: setTimeout() is a less-than-ideal solution, because it's too slow; timeouts won't fire until
|
||||
* the end of a CPU burst. So instead of calculating a number of milliseconds, we should calculate a
|
||||
* number of CPU cycles, and create a CPU notification mechanism that calls us back after that many cycles
|
||||
* have elapsed (and which will automatically shorten the current CPU burst as needed).
|
||||
*
|
||||
* This will also solve the other issue noted above, because if the CPU has been halted, it won't be
|
||||
* generating any notifications either.
|
||||
*/
|
||||
setTimeout(this.fnCheckDataReceived, this.getBaudTimeout(SerialPort.UART8251.BAUDRATES.RECV_RATE));
|
||||
if (this.sDataReceived && this.cpu) {
|
||||
this.cpu.setTimer(this.timerReceiveData, this.getBaudTimeout(SerialPort.UART8251.BAUDRATES.RECV_RATE));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue