Switching to new interrupt triggers

This commit is contained in:
Jeff Parsons 2016-10-12 16:40:47 -07:00 • committed by Jeff Parsons
commit 58d358b51e
7 changed files with 534 additions and 454 deletions

View file

@ -83,15 +83,37 @@ function CPUStatePDP11(parmsCPU)
Component.subclass(CPUStatePDP11, CPUPDP11);
/*
* Overview of Device Interrupt Support
*
* Originally, the CPU maintained a queue of requested interrupts. Entries in this queue recorded a device's
* priority, vector, and delay (ie, a number of instructions to process before issuing the interrupt). This queue
* would constantly grow and shrink as requests were issued and dispatched, and as long as there was something
* in the queue, the CPU was constantly examining it.
*
* Now we are trying something simpler. First, the CPU offers timer services to any device that wants a callback
* after a specified delay, which are much more efficient than requiring the CPU to dive into an interrupt queue and
* look for (and decrement) delay counts on every instruction.
*
* Second, when a device decides it's time to interrupt (either at the end of some operation or when its delay timer
* has fired), it will simply request the interrupt, and CPU priority permitting, the interrupt will be dispatched.
* If CPU priority is NOT permitting, then the interrupt will remain pending until the priority drops. This means
* that the CPU need not check for any pending interrupts until/unless the CPU priority changes.
*
* All this will be managed with "triggers". Every device that wants to interrupt must register via addTrigger().
* This will add a Trigger object to an array, and each object will contain the (self) assigned vector, the priority,
* the interrupt request status, and a next pointer.
*/
/**
* @typedef {{
* delay: number,
* priority: number,
* vector: number,
* callback: (function()|null|undefined)
* }} InterruptEvent
* priority: number,
* request: number,
* next: (Trigger|null)
* }} Trigger
*/
var InterruptEvent;
var Trigger;
/**
* initProcessor()
@ -101,7 +123,15 @@ var InterruptEvent;
CPUStatePDP11.prototype.initProcessor = function()
{
this.decode = PDP11.op1170.bind(this);
/** @type {Array.<Trigger>} */
this.aTriggers = [];
/** @type {Trigger|null} */
this.triggerNext = null;
this.initRegs();
this.flags.complete = this.flags.debugCheck = false;
};
@ -169,7 +199,7 @@ CPUStatePDP11.prototype.initRegs = function()
this.regMB = 0;
/*
* opFlags contains various triggers that stepCPU() needs to be aware of
* opFlags contains various conditions that stepCPU() needs to be aware of
*/
this.opFlags = 0;
@ -208,8 +238,6 @@ CPUStatePDP11.prototype.resetRegs = function()
this.mmuMask = 0x3ffff;
this.mmuMemorySize = BusPDP11.IOPAGE_18BIT;
/** @type {Array.<InterruptEvent>} */
this.interruptQueue = [];
this.opFlags |= PDP11.OPFLAG.INTQ;
if (this.bus) this.setMemoryAccess();
@ -562,77 +590,131 @@ CPUStatePDP11.prototype.setSP = function(addr)
};
/**
* interrupt(delay, priority, vector, callback)
*
* Interrupts are stored in a queue in delay order with the delay expressed as
* a difference. For example if the delays were 0, 1, 0 then the first entry
* is active and both the second and third are waiting for one more instruction
* execution to become active.
* addTrigger(vector, priority)
*
* @this {CPUStatePDP11}
* @param {number} delay
* @param {number} priority
* @param {number} vector
* @param {function()} [callback]
* @param {number} priority
* @return {Trigger}
*/
CPUStatePDP11.prototype.interrupt = function(delay, priority, vector, callback)
CPUStatePDP11.prototype.addTrigger = function(vector, priority)
{
var i = this.interruptQueue.length;
while (i-- > 0) {
if (this.interruptQueue[i].vector === vector) {
if (i > 0) {
this.interruptQueue[i - 1].delay += this.interruptQueue[i].delay;
}
this.interruptQueue.splice(i, 1);
break;
}
}
if (delay >= 0) {
i = this.interruptQueue.length; // queue in delay 'difference' order
while (i-- > 0) {
if (this.interruptQueue[i].delay > delay) {
this.interruptQueue[i].delay -= delay;
break;
}
delay -= this.interruptQueue[i].delay;
}
/*
* NOTE regarding a Google Closure Compiler "bug": if an InterruptEvent is inserted into the
* interruptQueue with the named properties below, they will never be seen by the rest of the
* code, because the compiler renames all other property references EXCEPT these.
*
* this.interruptQueue.splice(i + 1, 0, {
* "delay": delay,
* "priority": (priority << 5) & 0xe0,
* "vector": vector,
* "callback": callback
* });
*
* Perhaps if the inlined object had been explicitly @typed, that wouldn't have happened, but
* I'm playing it safe now: I've taken the object out-of-line, removed the quoted property names,
* and explicitly typed it. The compiler re-inlines the object with correctly renamed properties.
*/
/** @type {InterruptEvent} */
var interruptEvent = {
delay: delay,
priority: (priority << 5) & 0xe0,
vector: vector,
callback: callback
};
this.interruptQueue.splice(i + 1, 0, interruptEvent);
}
this.opFlags |= PDP11.OPFLAG.INTQ;
var trigger = {vector: vector, priority: priority, request: -1, next: null};
this.aTriggers.push(trigger);
return trigger;
};
/**
* checkInterruptQueue()
* checkTriggers()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.checkInterruptQueue = function()
CPUStatePDP11.prototype.checkTriggers = function()
{
if (this.triggerNext && this.setTrigger(this.triggerNext)) {
this.removeTrigger(this.triggerNext);
}
};
/**
* insertTrigger(trigger)
*
* @this {CPUStatePDP11}
* @param {Trigger} trigger
*/
CPUStatePDP11.prototype.insertTrigger = function(trigger)
{
if (trigger != this.triggerNext) {
var triggerPrev = this.triggerNext;
if (!triggerPrev || triggerPrev.priority <= trigger.priority) {
trigger.next = triggerPrev;
this.triggerNext = trigger;
} else {
do {
var triggerNext = triggerPrev.next;
if (!triggerNext || triggerNext.priority <= trigger.priority) {
trigger.next = triggerNext;
triggerPrev.next = trigger;
break;
}
triggerPrev = triggerNext;
} while (true);
}
}
};
/**
* removeTrigger(trigger)
*
* @this {CPUStatePDP11}
* @param {Trigger} trigger
*/
CPUStatePDP11.prototype.removeTrigger = function(trigger)
{
var triggerPrev = this.triggerNext;
if (triggerPrev == trigger) {
this.triggerNext = trigger.next;
} else {
do {
var triggerNext = triggerPrev.next;
if (triggerNext == trigger) {
triggerPrev.next = triggerNext.next;
break;
}
triggerPrev = triggerNext;
} while (true);
}
};
/**
* setTrigger(trigger)
*
* @this {CPUStatePDP11}
* @param {Trigger|null} trigger
* @return {boolean} (true if interrupt dispatched, false if not)
*/
CPUStatePDP11.prototype.setTrigger = function(trigger)
{
if (!this.dispatchInterrupt(trigger.vector, trigger.priority)) {
this.insertTrigger(trigger);
return false;
}
return true;
};
/**
* dispatchInterrupt(vector, priority)
*
* @this {CPUStatePDP11}
* @param {number} vector
* @param {number} priority
* @return {boolean} (true if dispatched, false if not)
*/
CPUStatePDP11.prototype.dispatchInterrupt = function(vector, priority)
{
var priorityCPU = (this.regPSW & PDP11.PSW.PRI) >> PDP11.PSW.SHIFT.PRI;
if (priority > priorityCPU) {
if (this.opFlags & PDP11.OPFLAG.WAIT) {
this.advancePC(2);
this.opFlags &= ~PDP11.OPFLAG.WAIT;
}
this.trap(vector, PDP11.REASON.INTERRUPT);
return true;
}
return false;
};
/**
* checkInterrupts()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.checkInterrupts = function()
{
if (this.opFlags & PDP11.OPFLAG.INTQ) {
this.opFlags &= ~PDP11.OPFLAG.INTQ;
this.checkTriggers();
/*
var interruptEvent = null;
var savePSW = this.regPIR & 0xe0;
for (var i = this.interruptQueue.length; --i >= 0;) {
@ -666,6 +748,7 @@ CPUStatePDP11.prototype.checkInterruptQueue = function()
this.trap(interruptEvent.vector, PDP11.REASON.INTERRUPT);
}
}
*/
}
else if (this.opFlags & PDP11.OPFLAG.INTQ_SPL) {
/*
@ -735,7 +818,7 @@ CPUStatePDP11.prototype.setPSW = function(newPSW)
this.regsGen[6] = this.regsAltStack[this.mmuMode];
}
/*
* Trigger a call to checkInterruptQueue()
* Trigger a call to checkInterrupts()
*/
this.opFlags |= PDP11.OPFLAG.INTQ;
this.regPSW = newPSW;
@ -1017,7 +1100,7 @@ CPUStatePDP11.prototype.trap = function(vector, reason)
}
}
throw vector;
if (reason != PDP11.REASON.INTERRUPT) throw vector;
};
/**
@ -1891,7 +1974,7 @@ CPUStatePDP11.prototype.stepCPU = function(nMinCycles)
* 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*/) {
this.checkInterruptQueue();
this.checkInterrupts();
}
}