Fixed TRAP TEST diagnostic that expected a stack overflow trap to be acknowledged immediately after a hardware interrupt was dispatched

This commit is contained in:
Jeff 2016-11-11 14:56:01 -08:00 committed by Jeff Parsons
commit bdb06e458f
9 changed files with 384 additions and 322 deletions

View file

@ -812,6 +812,9 @@ CPUStatePDP11.prototype.checkInterrupts = function()
/**
* dispatchInterrupt(vector, priority)
*
* TODO: The process of dispatching an interrupt MUST cost some cycles; either trap() needs to assess
* that cost, or we do.
*
* @this {CPUStatePDP11}
* @param {number} vector
* @param {number} priority
@ -1161,19 +1164,26 @@ CPUStatePDP11.prototype.trap = function(vector, flag, reason)
{
if (DEBUG && this.dbg) {
if (this.messageEnabled(MessagesPDP11.TRAP)) {
this.printMessage("trap to vector " + this.dbg.toStrBase(vector, 0, true) + (reason? " (" + this.dbg.toStrBase(reason) + ")" : ""), MessagesPDP11.TRAP, true);
var sReason = reason < 0? PDP11.REASONS[-reason] : this.dbg.toStrBase(reason);
this.printMessage("trap to vector " + this.dbg.toStrBase(vector, 1) + " (" + sReason + ")", MessagesPDP11.TRAP, true);
}
}
if (this.nDisableTraps) return;
var doubleTrap = false;
if (this.trapPSW < 0) {
this.trapPSW = this.getPSW();
} else if (!this.mmuMode) {
/*
* Double-fault (nested trap) condition detected.
*/
vector = 4;
doubleTrap = true;
/*
* The next two lines used to be deferred until after the setPSW() below, but
* I'm not seeing any dependencies on these registers, so I'm consolidating the code.
*/
this.regErr |= PDP11.CPUERR.RED;
this.regsGen[6] = 4;
}
if (!(this.regMMR0 & PDP11.MMR0.ABORT)) {
@ -1193,19 +1203,14 @@ CPUStatePDP11.prototype.trap = function(vector, flag, reason)
*/
this.setPSW((newPSW & ~PDP11.PSW.PMODE) | ((this.trapPSW >> 2) & PDP11.PSW.PMODE));
if (doubleTrap) {
this.regErr |= PDP11.CPUERR.RED;
this.regsGen[6] = 4;
}
this.pushWord(this.trapPSW);
this.pushWord(this.regsGen[7]);
this.setPC(newPC);
/*
* Since DEC's "TRAP TEST" triggers a RESERVED (instruction) trap with the stack deliberately
* set too low, and expects the stack overflow trap to be "sprung" immediately afterward, we only
* want to "lose interest" in TRAP flags that were set on entry.
* DEC's "TRAP TEST" triggers a RESERVED trap with an invalid opcode and the stack deliberately
* set too low, and expects the stack overflow trap to be "sprung" immediately afterward, so we
* only want to "lose interest" in the TRAP flag(s) that were set on entry, not ALL of them.
*
* this.opFlags &= ~PDP11.OPFLAG.TRAP_MASK; // lose interest in traps after an abort
*/
@ -1426,7 +1431,7 @@ CPUStatePDP11.prototype.mapVirtualToPhysical = function(virtualAddress, accessFl
// aborts and traps: log FIRST trap and MOST RECENT abort
this.mmuPDR[this.mmuMode][page] = pdr;
if ((physicalAddress !== 0x3fff7a) || this.mmuMode) { // MMR0 is 017777572
if ((physicalAddress !== 0x3fff7a) || this.mmuMode) {// MMR0 is 017777572
this.mmuLastMode = this.mmuMode;
this.mmuLastPage = page;
}
@ -1434,7 +1439,7 @@ CPUStatePDP11.prototype.mapVirtualToPhysical = function(virtualAddress, accessFl
var fTrap = false;
if (errorMask) {
if (errorMask & 0xe000) {
if (this.trapPSW >= 0) errorMask |= 0x80; // Instruction complete
if (this.trapPSW >= 0) errorMask |= 0x80; // Instruction complete
if (!(this.regMMR0 & 0xe000)) {
this.regMMR0 |= errorMask | (this.mmuLastMode << 5) | (this.mmuLastPage << 1);
}
@ -1443,14 +1448,14 @@ CPUStatePDP11.prototype.mapVirtualToPhysical = function(virtualAddress, accessFl
if (!(this.regMMR0 & 0xf000)) {
//if (physicalAddress < 017772200 || physicalAddress > 017777677) {
if (physicalAddress < 0x3ff480 || physicalAddress > 0x3fffbf) {
this.regMMR0 |= 0x1000; // MMU trap flag
this.regMMR0 |= 0x1000; // MMU trap flag
if (this.regMMR0 & 0x0200) {
this.opFlags |= PDP11.OPFLAG.TRAP_MMU;
}
}
}
if (fTrap) { // don't trap until the end, because it throws an exception
this.trap(PDP11.TRAP.MMU, 0, PDP11.REASON.MAPERROR);
this.trap(PDP11.TRAP.MMU, 0, PDP11.REASON.ERROR);
}
}
return physicalAddress;
@ -1516,18 +1521,30 @@ CPUStatePDP11.prototype.pushWord = function(data)
/**
* checkStackLimit(mode, addr)
*
* The special "mode 0" case used by pushWord() ignores addr <= 4, because pushWord() is used by trap(),
* and if the trap() was generated by a RED error (below) or generated a RED error itself, then we need to
* avoid triggering another (nested) RED error.
*
* TODO: pushWord() is not used exclusively by trap(); it's also used by a few instructions, like opJSR(),
* so we might need to do some additional factoring of this function's logic.
*
* @this {CPUStatePDP11}
* @param {number} mode (ie, the addressing mode; 0 if push)
* @param {number} mode (ie, the addressing mode, or 0 if pushWord() is checking)
* @param {number} addr
*/
CPUStatePDP11.prototype.checkStackLimit = function(mode, addr)
{
if (!this.mmuMode && !(this.opFlags & PDP11.OPFLAG.TRAP_SP)) {
if (addr <= this.regSL && (mode || addr > 4) || mode && addr >= 0xfffe) {
if (this.model >= PDP11.MODEL_1145 && (addr <= this.regSL - 32 || mode == 1 && addr >= 0xfffe)) {
if (!this.mmuMode) {
/*
* NOTE: I've removed the tests below for addr >= 0xFFFE, which were ported from the original code,
* because while it's definitely a bad physical stack address, I'm not sure it rises to the level of
* a trap, and this code is already expensive enough as it is.
*/
if (addr <= this.regSL && (mode || addr > 4) /* || mode && addr >= 0xFFFE */) {
if (this.model >= PDP11.MODEL_1145 && (addr <= this.regSL - 32 /* || mode == 1 && addr >= 0xFFFE */)) {
this.regErr |= PDP11.CPUERR.RED;
this.regsGen[6] = 4;
this.trap(PDP11.TRAP.BUS_ERROR, 0, PDP11.REASON.STACKMODE1);
this.trap(PDP11.TRAP.BUS_ERROR, 0, PDP11.REASON.STACK);
} else {
/*
* On older machines (eg, the PDP-11/20), the instruction is always allowed to complete,
@ -1593,11 +1610,12 @@ CPUStatePDP11.prototype.getAddrByMode = function(mode, reg, accessFlags)
* Mode 0: Registers don't have a virtual address, so trap.
*
* NOTE: Most instruction code paths never call getAddrByMode() when the mode is zero;
* JMP and JSR instructions are exceptions, but that's OK, because those are documented to
* "cause an 'illegal' instruction" condition", which presumably means a BUS_ERROR trap.
* JMP and JSR instructions are exceptions, but that's OK, because those are documented as
* ILLEGAL instructions which produce a BUS_ERROR trap (as opposed to UNDEFINED instructions
* that cause a RESERVED trap).
*/
case 0:
this.trap(PDP11.TRAP.BUS_ERROR, 0, PDP11.REASON.NOREGADDR);
this.trap(PDP11.TRAP.BUS_ERROR, 0, PDP11.REASON.ILLEGAL);
return 0;
/*
@ -2218,6 +2236,26 @@ CPUStatePDP11.prototype.stepCPU = function(nMinCycles)
}
if (this.opFlags) {
/*
* If we're in the INTQ or WAIT state, check for any pending interrupts.
*
* NOTE: It's no coincidence that we're checking this BEFORE any pending traps, because in rare
* cases (including some presented by those pesky "TRAP TEST" diagnostics), the process of dispatching
* an interrupt can trigger a TRAP_SP stack overflow condition, which must be dealt with BEFORE we
* execute the first instruction of the interrupt handler.
*/
if ((this.opFlags & (PDP11.OPFLAG.INTQ_SPL | PDP11.OPFLAG.INTQ | PDP11.OPFLAG.WAIT)) /* && nDebugState >= 0 */) {
if (this.checkInterrupts()) {
/*
* Since an interrupt was just dispatched, altering the normal flow of time and changing
* the future as we knew it, let's break out immediately if we're single-stepping, so that
* the Debugger gets to see the first instruction of the interrupt handler. NOTE: This
* assumes that we've still commented out the nDebugState check above that used to bypass
* checkInterrupts() when single-stepping.
*/
if (nDebugState < 0) break;
}
}
/*
* Check for any pending traps.
*
@ -2225,33 +2263,36 @@ CPUStatePDP11.prototype.stepCPU = function(nMinCycles)
* just in case the last instruction threw an exception that kicked us out before we reached the bottom
* of the stepCPU() loop.
*
* Note: I've swapped the order (priority) of the TF and SP traps, based on the PDP-11/20 Handbook.
* TODO: Determine if 1) the 11/20 Handbook was wrong, or 2) the 11/70 really has different priorities.
* NOTE: The following code processes these "deferred" traps in priority order. Unfortunately, that
* order seems to have changed since the 11/20. For reference, here's the priority list for the 11/70:
*
* 1. HALT (Instruction, Switch, or Command)
* 2. MMU Faults
* 3. Parity Errors
* 4. Bus Errors (which includes stack overflow traps)
* 5. Floating Point Traps
* 6. TRAP Instruction
* 7. TRACE Trap
* 8. OVFL Trap
* 9. Power Fail Trap
* 10. Console Bus Request (Front Panel Operation)
* 11. PIR 7, BR 7, PIR 6, BR 6, PIR 5, BR 5, PIR 4, BR 4, PIR 3, BR 3, PIR 2, PIR 1
* 12. WAIT Loop
*
* TODO: Determine 1) if the 11/20 Handbook was wrong, or 2) if the 11/70 really has different priorities.
*/
if (this.opFlags & PDP11.OPFLAG.TRAP_MASK) {
if (this.opFlags & PDP11.OPFLAG.TRAP_MMU) {
this.trap(PDP11.TRAP.MMU, PDP11.OPFLAG.TRAP_MMU, PDP11.REASON.TRAPMMU);
}
else if (this.opFlags & PDP11.OPFLAG.TRAP_TF) {
this.trap(PDP11.TRAP.BPT, PDP11.OPFLAG.TRAP_TF, PDP11.REASON.TRAPTF);
this.trap(PDP11.TRAP.MMU, PDP11.OPFLAG.TRAP_MMU, PDP11.REASON.TRAP);
if (nDebugState < 0) break;
}
else if (this.opFlags & PDP11.OPFLAG.TRAP_SP) {
this.trap(PDP11.TRAP.BUS_ERROR, PDP11.OPFLAG.TRAP_SP, PDP11.REASON.TRAPSP);
this.trap(PDP11.TRAP.BUS_ERROR, PDP11.OPFLAG.TRAP_SP, PDP11.REASON.STACK);
if (nDebugState < 0) break;
}
}
/*
* If we're in the INTQ or WAIT state, see if any interrupts can kick us out of that state.
*
* By also requiring nMinCycles to be non-zero before checking the interrupt queue, we avoid
* interrupting the natural flow of instructions whenever the Debugger is stepping through code.
*/
if ((this.opFlags & (PDP11.OPFLAG.INTQ_SPL | PDP11.OPFLAG.INTQ | PDP11.OPFLAG.WAIT)) /*&& nMinCycles*/) {
if (this.checkInterrupts()) {
/*
* Since an interrupt was just dispatched, altering the normal flow of time and changing
* the future as we knew it, let's break out immediately if we're single-stepping, so that
* the Debugger gets to see the first instruction of the interrupt handler.
*/
else /* if (this.opFlags & PDP11.OPFLAG.TRAP_TF) */ {
this.assert(this.opFlags & PDP11.OPFLAG.TRAP_TF);
this.trap(PDP11.TRAP.BPT, PDP11.OPFLAG.TRAP_TF, PDP11.REASON.TRACE);
if (nDebugState < 0) break;
}
}