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:
parent
03e4169df2
commit
bdb06e458f
9 changed files with 384 additions and 322 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue