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

@ -1395,7 +1395,7 @@ BusPDP11.prototype.fault = function(addr, err, access)
this.fFault = true;
if (!this.nDisableFaults) {
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(MessagesPDP11.FAULT)) {
this.dbg.printMessage("memory fault (" + access + ") on address " + this.dbg.toStrBase(addr), true, true);
this.dbg.printMessage("memory fault (" + access + ") on " + this.dbg.toStrBase(addr), true, true);
}
if (err) this.cpu.regErr |= err;
this.cpu.trap(PDP11.TRAP.BUS_ERROR, 0, addr);

View file

@ -827,7 +827,7 @@ PDP11.opBPL = function(opCode)
*/
PDP11.opBPT = function(opCode)
{
this.trap(PDP11.TRAP.BPT, 0, PDP11.REASON.BPT);
this.trap(PDP11.TRAP.BPT, 0, PDP11.REASON.TRAP);
this.nStepCycles -= (4 + 1);
};
@ -1101,7 +1101,7 @@ PDP11.opDIV = function(opCode)
*/
PDP11.opEMT = function(opCode)
{
this.trap(PDP11.TRAP.EMT, 0, PDP11.REASON.EMT);
this.trap(PDP11.TRAP.EMT, 0, PDP11.REASON.TRAP);
this.nStepCycles -= (22 + 3);
};
@ -1174,7 +1174,7 @@ PDP11.opINCB = function(opCode)
*/
PDP11.opIOT = function(opCode)
{
this.trap(PDP11.TRAP.IOT, 0, PDP11.REASON.IOT);
this.trap(PDP11.TRAP.IOT, 0, PDP11.REASON.TRAP);
this.nStepCycles -= (22 + 3);
};
@ -1294,7 +1294,7 @@ PDP11.opMFPT = function(opCode)
/*
* TODO: Review
*/
this.trap(PDP11.TRAP.RESERVED, 0, PDP11.REASON.RESERVED);
this.trap(PDP11.TRAP.RESERVED, 0, PDP11.REASON.TRAP);
};
PDP11.MOV_CYCLES = [
@ -1826,7 +1826,7 @@ PDP11.opUndefined = function(opCode)
if (DEBUGGER && this.dbg) {
if (this.dbg.undefinedInstruction(opCode)) return;
}
this.trap(PDP11.TRAP.RESERVED, 0, PDP11.REASON.RESERVED);
this.trap(PDP11.TRAP.RESERVED, 0, PDP11.REASON.TRAP);
};
/**

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;
}
}

View file

@ -1399,9 +1399,9 @@ if (DEBUGGER) {
var trapStatus = this.cpu.getTrapStatus();
if (trapStatus) {
var trapReason = trapStatus >> 8;
var sReason = trapReason? (" (" + trapReason + ")") : "";
this.println("trapped to " + this.toStrBase(trapStatus & 0xff, 1) + sReason);
var reason = trapStatus >> 8;
var sReason = reason < 0? PDP11.REASONS[-reason] : this.toStrBase(reason);
this.println("trapped to " + this.toStrBase(trapStatus & 0xff, 1) + " (" + sReason + ")");
}
this.dbgAddrNextCode = this.newAddr(this.cpu.getPC());

View file

@ -202,8 +202,8 @@ var PDP11 = {
WAIT: 0x04, // WAIT operation in progress
TRAP: 0x08, // set if last operation was a trap (see trapLast for the vector, and trapReason for the reason)
TRAP_TF: 0x10, // aka PDP11.PSW.TF
TRAP_MMU: 0x20,
TRAP_SP: 0x40, // set for a deferred BUS_ERROR trap (due to a "yellow" stack overflow condition)
TRAP_SP: 0x20, // set for a deferred BUS_ERROR trap (due to a "yellow" stack overflow condition)
TRAP_MMU: 0x40,
TRAP_MASK: 0x70,
NO_FLAGS: 0x80, // set whenever the PSW is written directly, requiring all updateXXXFlags() functions to leave flags unchanged
PRESERVE: 0x07 // OPFLAG bits to preserve prior to the next instruction
@ -261,28 +261,28 @@ var PDP11 = {
MMU: 0xA8 // 250 MMU: aborts and traps
},
/*
* PDP-11 trap reasons (for diagnostic purposes only)
* PDP-11 trap reasons (largely for diagnostic purposes only)
*/
REASON: {
BPT: -1,
EMT: -2,
HALT: -3,
IOT: -4,
TRAP: -5,
RESERVED: -6,
TRAPMMU: -7,
TRAPSP: -8,
TRAPTF: -9,
ODDMEMADDR: -10,
NOMEMORY: -11,
ODDMMUADDR: -12,
MAPERROR: -13,
PUSHERROR: -14,
NOREGADDR: -15,
STACKMODE1: -16,
STACKERROR: -17,
INTERRUPT: -18
UNKNOWN: 0,
TRAP: -1,
HALT: -2,
INTERRUPT: -3,
TRACE: -4,
STACK: -5,
ILLEGAL: -6,
ERROR: -7,
},
REASONS: [
"UNKNOWN",
"TRAP",
"HALT",
"INTERRUPT",
"TRACE",
"STACK",
"ILLEGAL",
"ERROR"
],
/*
* Internal memory access flags
*/