Got rid of runState, implemented a solution for WAIT
This commit is contained in:
parent
98081c6a6d
commit
b3ba03d0a5
7 changed files with 358 additions and 338 deletions
|
|
@ -1161,8 +1161,8 @@ CPUPDP11.prototype.startCPU = function(fUpdateFocus)
|
|||
var controlRun = this.bindings["run"];
|
||||
if (controlRun) controlRun.textContent = "Halt";
|
||||
if (this.cmp) {
|
||||
this.cmp.start(this.msStartRun, this.getCycles());
|
||||
if (fUpdateFocus) this.cmp.updateFocus(true);
|
||||
this.cmp.start(this.msStartRun, this.getCycles());
|
||||
}
|
||||
this.updateCPU(true);
|
||||
setTimeout(this.onRunTimeout, 0);
|
||||
|
|
|
|||
|
|
@ -1097,7 +1097,6 @@ PDP11.opHALT = function(opCode)
|
|||
this.regErr |= PDP11.CPUERR.BADHALT;
|
||||
this.trap(PDP11.TRAP.BUS_ERROR, PDP11.REASON.HALT);
|
||||
} else {
|
||||
this.runState = 3;
|
||||
this.endBurst();
|
||||
}
|
||||
this.nStepCycles -= 1;
|
||||
|
|
@ -1605,7 +1604,27 @@ PDP11.opTSTB = function(opCode)
|
|||
*/
|
||||
PDP11.opWAIT = function(opCode)
|
||||
{
|
||||
this.checkInterruptDelay();
|
||||
/*
|
||||
* The original PDP-11 emulation code would actually stop emulating instructions now, relying on assorted
|
||||
* setTimeout() callbacks, setInterval() callbacks, device XHR (XMLHttpRequest) callbacks, etc, to eventually
|
||||
* call interrupt(), which would then transition the CPU's runState from 2 to 0 and kickstart emulate() again.
|
||||
*
|
||||
* That approach isn't compatible with PCjs emulators, which prefer to rely on the simulated CPU clock to
|
||||
* drive all simulated device updates. This means components should call the CPU's setTimer() function, which
|
||||
* invokes the provided callback when the number of CPU cycles that correspond to the requested number of
|
||||
* milliseconds have elapsed. This also gives us the ability to scale device response times as needed if the
|
||||
* user decides to crank up CPU speed, and to freeze them along with the CPU whenever the user halts the machine.
|
||||
*
|
||||
* However, the PCjs approach requires the CPU to continue running. One simple solution to this dilemma:
|
||||
*
|
||||
* 1) opWAIT() sets a new opFlags bit (OPFLAG.WAIT)
|
||||
* 2) When stepCPU() sees OPFLAG.WAIT, it checks for interrupts; if none, it rewinds the PC back to the WAIT
|
||||
*
|
||||
* NOTE: It's almost always a bad idea to add more checks to the inner stepCPU() loop, because every additional
|
||||
* check can have a measurable (negative) impact on performance. Which is why it's important to use opFlags bits
|
||||
* whenever possible, since we can test for multiple (up to 32) exceptional conditions with a single check.
|
||||
*/
|
||||
this.opFlags |= PDP11.OPFLAG.WAIT;
|
||||
this.nStepCycles -= 1;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -168,7 +168,6 @@ CPUStatePDP11.prototype.initRegs = function()
|
|||
this.opFlags = 0;
|
||||
this.cpuType = 70;
|
||||
this.trapPSW = -1;
|
||||
this.runState = 0; // 0=run, 1=step, 2=wait, 3=run
|
||||
this.loopRate = 9999; // instructions we can execute in 12ms
|
||||
this.resetRegs();
|
||||
};
|
||||
|
|
@ -568,9 +567,6 @@ CPUStatePDP11.prototype.requestHALT = function()
|
|||
* is active and both the second and third are waiting for one more instruction
|
||||
* execution to become active.
|
||||
*
|
||||
* If the current state is WAIT (runState === 2) then skip any delay and
|
||||
* go into RUN state.
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @param {number} delay
|
||||
* @param {number} priority
|
||||
|
|
@ -590,11 +586,6 @@ CPUStatePDP11.prototype.interrupt = function(delay, priority, vector, callback)
|
|||
}
|
||||
}
|
||||
if (delay >= 0) {
|
||||
if (this.runState === 2) { // if currently wait
|
||||
delay = 0;
|
||||
this.runState = 0;
|
||||
setTimeout(this.onRunTimeout, 0); // TODO: Review
|
||||
}
|
||||
i = this.interruptQueue.length; // queue in delay 'difference' order
|
||||
while (i-- > 0) {
|
||||
if (this.interruptQueue[i].delay > delay) {
|
||||
|
|
@ -613,24 +604,6 @@ CPUStatePDP11.prototype.interrupt = function(delay, priority, vector, callback)
|
|||
this.priorityReview = 2;
|
||||
};
|
||||
|
||||
/**
|
||||
* checkInterruptDelay()
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
*/
|
||||
CPUStatePDP11.prototype.checkInterruptDelay = function()
|
||||
{
|
||||
var delay = 0;
|
||||
for (var i = this.interruptQueue.length; --i >= 0;) {
|
||||
delay += this.interruptQueue[i].delay;
|
||||
this.interruptQueue[i].delay = 0;
|
||||
}
|
||||
if (!delay && this.runState === 0) {
|
||||
this.runState = 2; // wait
|
||||
this.endBurst();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* checkInterruptQueue()
|
||||
*
|
||||
|
|
@ -666,6 +639,7 @@ CPUStatePDP11.prototype.checkInterruptQueue = function()
|
|||
}
|
||||
}
|
||||
if (savePSW > (this.regPSW & 0xe0)) {
|
||||
this.opFlags &= ~PDP11.OPFLAG.WAIT;
|
||||
if (!interruptEvent) {
|
||||
this.trap(PDP11.TRAP.PIRQ, PDP11.REASON.INTERRUPT);
|
||||
} else {
|
||||
|
|
@ -750,7 +724,7 @@ CPUStatePDP11.prototype.setPSW = function(newPSW)
|
|||
*/
|
||||
CPUStatePDP11.prototype.updateNZFlags = function(result)
|
||||
{
|
||||
if (!(this.opFlags & PDP11.OPFLAG.SKIP_FLAGS)) {
|
||||
if (!(this.opFlags & PDP11.OPFLAG.NO_FLAGS)) {
|
||||
this.flagN = this.flagZ = result;
|
||||
this.flagV = 0;
|
||||
}
|
||||
|
|
@ -767,7 +741,7 @@ CPUStatePDP11.prototype.updateNZFlags = function(result)
|
|||
*/
|
||||
CPUStatePDP11.prototype.updateAllFlags = function(result, overflow)
|
||||
{
|
||||
if (!(this.opFlags & PDP11.OPFLAG.SKIP_FLAGS)) {
|
||||
if (!(this.opFlags & PDP11.OPFLAG.NO_FLAGS)) {
|
||||
this.flagN = this.flagZ = this.flagC = result;
|
||||
this.flagV = overflow || 0;
|
||||
}
|
||||
|
|
@ -783,7 +757,7 @@ CPUStatePDP11.prototype.updateAllFlags = function(result, overflow)
|
|||
*/
|
||||
CPUStatePDP11.prototype.updateAddFlags = function(result, src, dst)
|
||||
{
|
||||
if (!(this.opFlags & PDP11.OPFLAG.SKIP_FLAGS)) {
|
||||
if (!(this.opFlags & PDP11.OPFLAG.NO_FLAGS)) {
|
||||
this.flagN = this.flagZ = this.flagC = result;
|
||||
this.flagV = (src ^ result) & (dst ^ result);
|
||||
}
|
||||
|
|
@ -800,7 +774,7 @@ CPUStatePDP11.prototype.updateAddFlags = function(result, src, dst)
|
|||
*/
|
||||
CPUStatePDP11.prototype.updateDecFlags = function(result, dst)
|
||||
{
|
||||
if (!(this.opFlags & PDP11.OPFLAG.SKIP_FLAGS)) {
|
||||
if (!(this.opFlags & PDP11.OPFLAG.NO_FLAGS)) {
|
||||
this.flagN = this.flagZ = result;
|
||||
// Because src is always 1 (with a zero sign bit), it can be optimized out of this calculation
|
||||
this.flagV = (/* src ^ */ dst) & (dst ^ result);
|
||||
|
|
@ -818,7 +792,7 @@ CPUStatePDP11.prototype.updateDecFlags = function(result, dst)
|
|||
*/
|
||||
CPUStatePDP11.prototype.updateIncFlags = function(result, dst)
|
||||
{
|
||||
if (!(this.opFlags & PDP11.OPFLAG.SKIP_FLAGS)) {
|
||||
if (!(this.opFlags & PDP11.OPFLAG.NO_FLAGS)) {
|
||||
this.flagN = this.flagZ = result;
|
||||
// Because src is always 1 (with a zero sign bit), it can be optimized out of this calculation
|
||||
this.flagV = (/* src ^ */ result) & (dst ^ result);
|
||||
|
|
@ -833,10 +807,16 @@ CPUStatePDP11.prototype.updateIncFlags = function(result, dst)
|
|||
*/
|
||||
CPUStatePDP11.prototype.updateMulFlags = function(result)
|
||||
{
|
||||
this.flagN = result >> 16;
|
||||
this.flagZ = this.flagN | result;
|
||||
this.flagV = 0;
|
||||
this.flagC = (result < -32768 || result > 32767)? 0x10000 : 0;
|
||||
/*
|
||||
* NOTE: Technically, the MUL instruction doesn't need to worry about NO_FLAGS, because that instruction
|
||||
* doesn't write to the bus, and therefore can't modify the PSW directly. But it doesn't hurt to be consistent.
|
||||
*/
|
||||
if (!(this.opFlags & PDP11.OPFLAG.NO_FLAGS)) {
|
||||
this.flagN = result >> 16;
|
||||
this.flagZ = this.flagN | result;
|
||||
this.flagV = 0;
|
||||
this.flagC = (result < -32768 || result > 32767)? 0x10000 : 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -847,7 +827,7 @@ CPUStatePDP11.prototype.updateMulFlags = function(result)
|
|||
*/
|
||||
CPUStatePDP11.prototype.updateShiftFlags = function(result)
|
||||
{
|
||||
if (!(this.opFlags & PDP11.OPFLAG.SKIP_FLAGS)) {
|
||||
if (!(this.opFlags & PDP11.OPFLAG.NO_FLAGS)) {
|
||||
this.flagN = this.flagZ = this.flagC = result;
|
||||
this.flagV = this.flagN ^ (this.flagC >> 1);
|
||||
}
|
||||
|
|
@ -866,7 +846,7 @@ CPUStatePDP11.prototype.updateShiftFlags = function(result)
|
|||
*/
|
||||
CPUStatePDP11.prototype.updateSubFlags = function(result, src, dst)
|
||||
{
|
||||
if (!(this.opFlags & PDP11.OPFLAG.SKIP_FLAGS)) {
|
||||
if (!(this.opFlags & PDP11.OPFLAG.NO_FLAGS)) {
|
||||
this.flagN = this.flagZ = this.flagC = result;
|
||||
this.flagV = (src ^ dst) & (dst ^ result);
|
||||
}
|
||||
|
|
@ -1701,26 +1681,45 @@ CPUStatePDP11.prototype.stepCPU = function(nMinCycles)
|
|||
nDebugState = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for any pending traps.
|
||||
*
|
||||
* I've moved this TRAP_MASK check BEFORE we decode the next instruction instead
|
||||
* of immediately AFTER, because the last instruction may have thrown an exception,
|
||||
* kicking us out before we reach the bottom of this loop.
|
||||
*/
|
||||
if (this.opFlags & PDP11.OPFLAG.TRAP_MASK) {
|
||||
if (this.opFlags & PDP11.OPFLAG.TRAP_MMU) {
|
||||
this.trap(PDP11.TRAP.MMU_FAULT, PDP11.REASON.TRAPMMU); // MMU trap has priority
|
||||
} else {
|
||||
if (this.opFlags & PDP11.OPFLAG.TRAP_SP) {
|
||||
this.trap(PDP11.TRAP.BUS_ERROR, PDP11.REASON.TRAPSP); // then SP trap
|
||||
if (this.opFlags) {
|
||||
/*
|
||||
* Check for any pending traps.
|
||||
*
|
||||
* I've moved this TRAP_MASK check BEFORE we decode the next instruction instead
|
||||
* of immediately AFTER, because the last instruction may have thrown an exception,
|
||||
* kicking us out before we reach the bottom of this loop.
|
||||
*/
|
||||
if (this.opFlags & PDP11.OPFLAG.TRAP_MASK) {
|
||||
if (this.opFlags & PDP11.OPFLAG.TRAP_MMU) {
|
||||
this.trap(PDP11.TRAP.MMU_FAULT, PDP11.REASON.TRAPMMU); // MMU trap has priority
|
||||
} else {
|
||||
if (this.opFlags & PDP11.OPFLAG.TRAP_TF) {
|
||||
this.trap(PDP11.TRAP.BREAKPOINT, PDP11.REASON.TRAPTF); // and finally a TF trap
|
||||
if (this.opFlags & PDP11.OPFLAG.TRAP_SP) {
|
||||
this.trap(PDP11.TRAP.BUS_ERROR, PDP11.REASON.TRAPSP); // then SP trap
|
||||
} else {
|
||||
if (this.opFlags & PDP11.OPFLAG.TRAP_TF) {
|
||||
this.trap(PDP11.TRAP.BREAKPOINT, PDP11.REASON.TRAPTF); // and finally a TF trap
|
||||
}
|
||||
}
|
||||
}
|
||||
this.opFlags &= ~PDP11.OPFLAG.TRAP_MASK;
|
||||
}
|
||||
/*
|
||||
* If we're in WAIT state, see if any interrupts are ready to kick us out of that state.
|
||||
*/
|
||||
if (this.opFlags & PDP11.OPFLAG.WAIT) {
|
||||
/*
|
||||
* If checkInterruptQueue() found an interrupt, it will have dispatched it AND cleared
|
||||
* the WAIT flag; it won't return, so we're done.
|
||||
*/
|
||||
this.checkInterruptQueue();
|
||||
/*
|
||||
* Since checkInterruptQueue() returned, we need to rewind the PC to the WAIT instruction;
|
||||
* we're going to play it safe and turn off the WAIT flag, because assuming the WAIT instruction
|
||||
* is still there, it will automatically re-enable it. TODO: Assert that the WAIT is still there.
|
||||
*/
|
||||
this.opFlags &= ~PDP11.OPFLAG.WAIT;
|
||||
this.regsGen[7] = (this.regsGen[7] - 2) & 0xffff;
|
||||
}
|
||||
this.opFlags &= ~PDP11.OPFLAG.TRAP_MASK;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1737,10 +1736,10 @@ CPUStatePDP11.prototype.stepCPU = function(nMinCycles)
|
|||
}
|
||||
|
||||
/*
|
||||
* Snapshot the TF bit in opFlags, while simultaneously clearing all other opFlags;
|
||||
* Snapshot the TF bit in opFlags, while simultaneously clearing all other opFlags (except WAIT);
|
||||
* we'll check the TRAP_TF bit in opFlags when we come back around for another opcode.
|
||||
*/
|
||||
this.opFlags = this.regPSW & PDP11.PSW.TF;
|
||||
this.opFlags = (this.opFlags & PDP11.OPFLAG.WAIT) | (this.regPSW & PDP11.PSW.TF);
|
||||
|
||||
this.decode(this.getPCWord());
|
||||
|
||||
|
|
|
|||
|
|
@ -161,11 +161,12 @@ var PDP11 = {
|
|||
* Internal operation state flags
|
||||
*/
|
||||
OPFLAG: {
|
||||
WAIT: 0x01, // WAIT operation in progress
|
||||
TRAP_TF: 0x10, // aka PDP11.PSW.TF
|
||||
TRAP_MMU: 0x20,
|
||||
TRAP_SP: 0x40,
|
||||
TRAP_MASK: 0x70,
|
||||
SKIP_FLAGS: 0x80 // when set, all updateXXXFlags() functions must leave flags unchanged
|
||||
NO_FLAGS: 0x80 // set whenever the PSW is written directly, requiring all updateXXXFlags() functions to leave flags unchanged
|
||||
},
|
||||
/*
|
||||
* Opcode reg (opcode bits 2-0)
|
||||
|
|
|
|||
|
|
@ -352,7 +352,7 @@ DevicePDP11.prototype.writePSW = function(data, addr)
|
|||
{
|
||||
var maskDisallowed = PDP11.PSW.UNUSED | PDP11.PSW.TF;
|
||||
this.cpu.setPSW((data & ~maskDisallowed) | (this.cpu.getPSW() & maskDisallowed));
|
||||
this.cpu.opFlags |= PDP11.OPFLAG.SKIP_FLAGS;
|
||||
this.cpu.opFlags |= PDP11.OPFLAG.NO_FLAGS;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue