Merge branch 'next-release'
This commit is contained in:
commit
ff88be1924
28 changed files with 2360 additions and 1892 deletions
|
|
@ -69,7 +69,34 @@ function Keyboard8080(parmsKbd)
|
|||
|
||||
Component.subclass(Keyboard8080);
|
||||
|
||||
Keyboard8080.MINPRESSTIME = 50; // minimum milliseconds to wait before auto-releasing keys
|
||||
/*
|
||||
* Now that we want to keep track of the physical (and simulated) state of modifier keys, I've
|
||||
* grabbed a copy of the same bit definitions used by /modules/pcx86/lib/keyboard.js, since it's
|
||||
* only important that we have a set of unique values; what the values are isn't critical.
|
||||
*/
|
||||
Keyboard8080.STATE = {
|
||||
RSHIFT: 0x0001,
|
||||
SHIFT: 0x0002,
|
||||
SHIFTS: 0x0003,
|
||||
RCTRL: 0x0004, // 101-key keyboard only
|
||||
CTRL: 0x0008,
|
||||
CTRLS: 0x000C,
|
||||
RALT: 0x0010, // 101-key keyboard only
|
||||
ALT: 0x0020,
|
||||
ALTS: 0x0030,
|
||||
RCMD: 0x0040, // 101-key keyboard only
|
||||
CMD: 0x0080, // 101-key keyboard only
|
||||
CMDS: 0x00C0,
|
||||
ALL_RIGHT: 0x0055, // RSHIFT | RCTRL | RALT | RCMD
|
||||
ALL_SHIFT: 0x00FF, // SHIFT | RSHIFT | CTRL | RCTRL | ALT | RALT | CMD | RCMD
|
||||
INSERT: 0x0100, // TODO: Placeholder (we currently have no notion of any "insert" states)
|
||||
CAPS_LOCK: 0x0200,
|
||||
NUM_LOCK: 0x0400,
|
||||
SCROLL_LOCK: 0x0800,
|
||||
ALL_LOCKS: 0x0E00 // CAPS_LOCK | NUM_LOCK | SCROLL_LOCK
|
||||
};
|
||||
|
||||
Keyboard8080.MINPRESSTIME = 50; // minimum milliseconds to wait before auto-releasing keys
|
||||
|
||||
/**
|
||||
* Alternate keyCode mappings to support popular "WASD"-style directional-key mappings.
|
||||
|
|
@ -255,7 +282,8 @@ Keyboard8080.VT100.LEDCODES = {
|
|||
'l1': Keyboard8080.VT100.STATUS.LED1,
|
||||
'locked': Keyboard8080.VT100.STATUS.LOCKED,
|
||||
'local': Keyboard8080.VT100.STATUS.LOCAL,
|
||||
'online': ~Keyboard8080.VT100.STATUS.LOCAL
|
||||
'online': ~Keyboard8080.VT100.STATUS.LOCAL,
|
||||
'caps-lock':Keyboard8080.STATE.CAPS_LOCK
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -322,6 +350,12 @@ Keyboard8080.prototype.setBinding = function(sHTMLType, sBinding, control, sValu
|
|||
control.onkeyup = function onKeyUp(event) {
|
||||
return kbd.onKeyDown(event, false);
|
||||
};
|
||||
control.onkeypress = function onKeyPress(event) {
|
||||
return kbd.onKeyPress(event);
|
||||
};
|
||||
control.onpaste = function onKeyPaste(event) {
|
||||
return kbd.onPaste(event);
|
||||
};
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
|
@ -392,6 +426,8 @@ Keyboard8080.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
|
||||
this.chipset = /** @type {ChipSet8080} */ (cmp.getMachineComponent("ChipSet"));
|
||||
|
||||
this.serial = /** @type {SerialPort8080} */ (cmp.getMachineComponent("SerialPort"));
|
||||
|
||||
bus.addPortInputTable(this, this.config.portsInput);
|
||||
bus.addPortOutputTable(this, this.config.portsOutput);
|
||||
};
|
||||
|
|
@ -458,6 +494,13 @@ Keyboard8080.prototype.reset = function()
|
|||
*/
|
||||
this.aKeysActive = [];
|
||||
|
||||
/*
|
||||
* The current (assumed) physical (and simulated) states of the various shift/lock keys.
|
||||
*
|
||||
* TODO: Determine how (or whether) we can query the browser's initial shift/lock key states.
|
||||
*/
|
||||
this.bitsState = 0;
|
||||
|
||||
if (this.config.INIT && !this.restore(this.config.INIT)) {
|
||||
this.notice("reset error");
|
||||
}
|
||||
|
|
@ -515,39 +558,86 @@ Keyboard8080.prototype.restore = function(data)
|
|||
};
|
||||
|
||||
/**
|
||||
* setLED(control, f)
|
||||
* setLED(control, f, color)
|
||||
*
|
||||
* TODO: Add support for user-definable LED colors
|
||||
*
|
||||
* @this {Keyboard8080}
|
||||
* @param {Object} control is an HTML control DOM object
|
||||
* @param {boolean} f is true if the LED represented by control should be "on", false if "off"
|
||||
* @param {boolean|number} f is true if the LED represented by control should be "on", false if "off"
|
||||
* @param {number} color (ie, 0xff0000 for RED, or 0x00ff00 for GREEN)
|
||||
*/
|
||||
Keyboard8080.prototype.setLED = function(control, f)
|
||||
Keyboard8080.prototype.setLED = function(control, f, color)
|
||||
{
|
||||
/*
|
||||
* TODO: Add support for user-definable LED colors
|
||||
*/
|
||||
control.style.backgroundColor = (f? "#ff0000" : "#000000");
|
||||
control.style.backgroundColor = (f? ('#' + str.toHex(color, 6)) : "#000000");
|
||||
};
|
||||
|
||||
/**
|
||||
* updateLEDs(bLEDs)
|
||||
*
|
||||
* @this {Keyboard8080}
|
||||
* @param {number} bLEDs
|
||||
* @param {number} [bLEDs]
|
||||
*/
|
||||
Keyboard8080.prototype.updateLEDs = function(bLEDs)
|
||||
{
|
||||
this.bLEDs = bLEDs;
|
||||
var id, control;
|
||||
if (bLEDs != null) {
|
||||
this.bLEDs = bLEDs;
|
||||
} else {
|
||||
bLEDs = this.bLEDs;
|
||||
}
|
||||
for (var sBinding in this.config.LEDCODES) {
|
||||
var id = "led-" + sBinding;
|
||||
var control = this.bindings[id];
|
||||
id = "led-" + sBinding;
|
||||
control = this.bindings[id];
|
||||
if (control) {
|
||||
var bitLED = this.config.LEDCODES[sBinding];
|
||||
var fOn = !!(bLEDs & bitLED);
|
||||
if (bitLED & (bitLED-1)) {
|
||||
fOn = !(bLEDs & ~bitLED);
|
||||
}
|
||||
this.setLED(control, fOn);
|
||||
this.setLED(control, fOn, 0xff0000);
|
||||
}
|
||||
}
|
||||
id = "led-caps-lock";
|
||||
control = this.bindings[id];
|
||||
if (control) {
|
||||
this.setLED(control, (this.bitsState & Keyboard8080.STATE.CAPS_LOCK), 0x00ff00);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* checkModifierKeys(softCode, fDown, fRight)
|
||||
*
|
||||
* @this {Keyboard8080}
|
||||
* @param {number|string} softCode (ie, either a keycode or string ID)
|
||||
* @param {boolean} fDown (true if key going down, false if key going up)
|
||||
* @param {boolean} fRight (true if key is on the right, false if not or unknown or n/a)
|
||||
*/
|
||||
Keyboard8080.prototype.checkModifierKeys = function(softCode, fDown, fRight)
|
||||
{
|
||||
var bit = 0;
|
||||
switch(softCode) {
|
||||
case Keys.KEYCODE.SHIFT:
|
||||
bit = fRight? Keyboard8080.STATE.RSHIFT : Keyboard8080.STATE.SHIFT;
|
||||
break;
|
||||
case Keys.KEYCODE.CTRL:
|
||||
bit = fRight? Keyboard8080.STATE.RCTRL : Keyboard8080.STATE.CTRL;
|
||||
break;
|
||||
case Keys.KEYCODE.ALT:
|
||||
bit = fRight? Keyboard8080.STATE.RALT : Keyboard8080.STATE.ALT;
|
||||
break;
|
||||
case Keys.KEYCODE.CMD:
|
||||
bit = fRight? Keyboard8080.STATE.RCMD : Keyboard8080.STATE.CMD;
|
||||
break;
|
||||
case Keys.KEYCODE.CAPS_LOCK:
|
||||
bit = Keyboard8080.STATE.CAPS_LOCK;
|
||||
break;
|
||||
}
|
||||
if (bit) {
|
||||
if (fDown) {
|
||||
this.bitsState |= bit;
|
||||
} else {
|
||||
this.bitsState &= ~bit;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -555,7 +645,7 @@ Keyboard8080.prototype.updateLEDs = function(bLEDs)
|
|||
/**
|
||||
* getSoftCode(keyCode)
|
||||
*
|
||||
* Returns a number if the keyCode exists in the KEYMAP, or a string if the keyCode has a soft-code string.
|
||||
* Returns a number if the keyCode exists in the KEYMAP, or a string if the keyCode has a string ID.
|
||||
*
|
||||
* @this {Keyboard8080}
|
||||
* @return {string|number|null}
|
||||
|
|
@ -589,17 +679,95 @@ Keyboard8080.prototype.onKeyDown = function(event, fDown)
|
|||
var softCode = this.getSoftCode(keyCode);
|
||||
|
||||
if (softCode) {
|
||||
fPass = this.onSoftKeyDown(softCode, fDown);
|
||||
event.preventDefault();
|
||||
/*
|
||||
* We now keep track of any physical keyboard modifier keys that also exist on the simulated
|
||||
* keyboard; in the case of the VT100, that means CTRL and SHIFT. This will make it possible
|
||||
* for a new pair of services to eventually be implemented: simulateKeysDown() and simulateKeysUp().
|
||||
*/
|
||||
this.checkModifierKeys(softCode, fDown, event.location == Keys.LOCATION.RIGHT);
|
||||
|
||||
if (!event.metaKey) {
|
||||
fPass = this.onSoftKeyDown(softCode, fDown);
|
||||
/*
|
||||
* As onKeyPress() explains, the only key presses we're interested in are letters, which provide
|
||||
* an important clue regarding the CAPS-LOCK state. For all other keys, we call preventDefault(),
|
||||
* which "suppresses" the keyPress event.
|
||||
*/
|
||||
if (!(softCode >= Keys.ASCII.A && softCode <= Keys.ASCII.Z)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!COMPILED && this.messageEnabled(Messages8080.KEYS)) {
|
||||
this.printMessage("onKey" + (fDown? "Down" : "Up") + "(" + keyCode + "): softCode=" + softCode + ", pass=" + (fPass? "true" : "false"), true);
|
||||
this.printMessage("onKey" + (fDown? "Down" : "Up") + "(" + keyCode + "): softCode=" + softCode + ", pass=" + fPass, true);
|
||||
}
|
||||
|
||||
return fPass;
|
||||
};
|
||||
|
||||
/**
|
||||
* onKeyPress(event)
|
||||
*
|
||||
* For now, our only interest in keyPress events is letters, as a means of detecting the CAPS-LOCK state.
|
||||
*
|
||||
* @this {Keyboard8080}
|
||||
* @param {Object} event
|
||||
* @return {boolean} true to pass the event along, false to consume it
|
||||
*/
|
||||
Keyboard8080.prototype.onKeyPress = function(event)
|
||||
{
|
||||
var keyCode = event.keyCode;
|
||||
if (keyCode >= Keys.ASCII.A && keyCode <= Keys.ASCII.Z) {
|
||||
if (!(this.bitsState & (Keyboard8080.STATE.SHIFTS | Keyboard8080.STATE.CAPS_LOCK))) {
|
||||
this.bitsState |= Keyboard8080.STATE.CAPS_LOCK;
|
||||
this.onSoftKeyDown(Keys.KEYCODE.CAPS_LOCK, true);
|
||||
this.updateLEDs();
|
||||
}
|
||||
}
|
||||
else if (keyCode >= Keys.ASCII.a && keyCode <= Keys.ASCII.z) {
|
||||
if (this.bitsState & Keyboard8080.STATE.CAPS_LOCK) {
|
||||
this.bitsState &= ~Keyboard8080.STATE.CAPS_LOCK;
|
||||
this.onSoftKeyDown(Keys.KEYCODE.CAPS_LOCK, false);
|
||||
this.updateLEDs();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* onPaste(event)
|
||||
*
|
||||
* @this {Keyboard8080}
|
||||
* @param {Object} event
|
||||
* @return {boolean} true to pass the event along, false to consume it
|
||||
*/
|
||||
Keyboard8080.prototype.onPaste = function(event) {
|
||||
/*
|
||||
* TODO: In a perfect world, we would have implemented simulateKeysDown() and simulateKeysUp(),
|
||||
* which would transform any given text into the appropriate keystrokes. But for now, we're going
|
||||
* to leapfrog all that and try invoking the SerialPort's sendData() function, which if available,
|
||||
* is nothing more than a call into a connected machine's receiveData() function.
|
||||
*
|
||||
* Besides, paste functionality doesn't seem to be consistently implemented across all browsers
|
||||
* (partly out of security concerns, apparently) so it may not make sense to expend much more
|
||||
* effort on this right now. If you want to paste a lot of text into a machine, you're better off
|
||||
* pasting into a machine that's been configured to use a textarea as part of its Control Panel.
|
||||
* A visible textarea seems to have less issues than the hidden textarea overlaid on top of our
|
||||
* Video display.
|
||||
*/
|
||||
if (this.serial && this.serial.sendData) {
|
||||
if (event.stopPropagation) event.stopPropagation();
|
||||
if (event.preventDefault) event.preventDefault();
|
||||
var clipboardData = event.clipboardData || window.clipboardData;
|
||||
if (clipboardData) {
|
||||
this.serial.transmitData(clipboardData.getData('Text'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* indexOfSoftKey(softCode)
|
||||
*
|
||||
|
|
@ -609,35 +777,36 @@ Keyboard8080.prototype.onKeyDown = function(event, fDown)
|
|||
*/
|
||||
Keyboard8080.prototype.indexOfSoftKey = function(softCode)
|
||||
{
|
||||
var i;
|
||||
for (i = 0; i < this.aKeysActive.length; i++) {
|
||||
for (var i = 0; i < this.aKeysActive.length; i++) {
|
||||
if (this.aKeysActive[i].softCode == softCode) return i;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* onSoftKeyDown(softCode, fDown)
|
||||
* onSoftKeyDown(softCode, fDown, fAutoRelease)
|
||||
*
|
||||
* @this {Keyboard8080}
|
||||
* @param {number|string} softCode
|
||||
* @param {boolean} fDown is true for a down event, false for up
|
||||
* @param {boolean} [fAutoRelease] is true only if we know we want the key to auto-release
|
||||
* @return {boolean} true to pass the event along, false to consume it
|
||||
*/
|
||||
Keyboard8080.prototype.onSoftKeyDown = function(softCode, fDown)
|
||||
Keyboard8080.prototype.onSoftKeyDown = function(softCode, fDown, fAutoRelease)
|
||||
{
|
||||
var i = this.indexOfSoftKey(softCode);
|
||||
if (fDown) {
|
||||
// this.println(softCode + " down");
|
||||
fAutoRelease = fAutoRelease || false;
|
||||
if (i < 0) {
|
||||
this.aKeysActive.push({
|
||||
softCode: softCode,
|
||||
msDown: Date.now(),
|
||||
fAutoRelease: false
|
||||
fAutoRelease: fAutoRelease
|
||||
});
|
||||
} else {
|
||||
this.aKeysActive[i].msDown = Date.now();
|
||||
this.aKeysActive[i].fAutoRelease = false;
|
||||
this.aKeysActive[i].fAutoRelease = fAutoRelease;
|
||||
}
|
||||
} else if (i >= 0) {
|
||||
// this.println(softCode + " up");
|
||||
|
|
|
|||
|
|
@ -751,7 +751,7 @@ SerialPort8080.prototype.transmitByte = function(b)
|
|||
};
|
||||
|
||||
/**
|
||||
* transmitData()
|
||||
* transmitData(sData)
|
||||
*
|
||||
* Helper for clocking transmitted data at the expected XMIT_RATE.
|
||||
*
|
||||
|
|
@ -759,10 +759,16 @@ SerialPort8080.prototype.transmitByte = function(b)
|
|||
* set XMIT_READY (and XMIT_EMPTY), which signals the firmware that another byte can be transmitted.
|
||||
*
|
||||
* @this {SerialPort8080}
|
||||
* @param {string} [sData]
|
||||
* @return {boolean} true if successful, false if not
|
||||
*/
|
||||
SerialPort8080.prototype.transmitData = function()
|
||||
SerialPort8080.prototype.transmitData = function(sData)
|
||||
{
|
||||
this.bStatus |= (SerialPort8080.UART8251.STATUS.XMIT_READY | SerialPort8080.UART8251.STATUS.XMIT_EMPTY);
|
||||
if (sData) {
|
||||
return this.sendData? this.sendData.call(this.connection, sData) : false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ function Video8080(parmsVideo, canvas, context, textarea, container)
|
|||
if ('on' + sEvent in document) {
|
||||
var onFullScreenChange = function() {
|
||||
var fFullScreen = (document['fullscreenElement'] || document['msFullscreenElement'] || document['mozFullScreenElement'] || document['webkitFullscreenElement']);
|
||||
video.notifyFullScreen(fFullScreen? true : false);
|
||||
video.notifyFullScreen(!!fFullScreen);
|
||||
};
|
||||
document.addEventListener(sEvent, onFullScreenChange, false);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -331,22 +331,23 @@ Keyboard.SCANCODE = {
|
|||
Keyboard.STATE = {
|
||||
RSHIFT: 0x0001,
|
||||
SHIFT: 0x0002,
|
||||
SHIFTS: 0x0003,
|
||||
RCTRL: 0x0004, // 101-key keyboard only
|
||||
CTRL: 0x0008,
|
||||
CTRLS: 0x000c,
|
||||
CTRLS: 0x000C,
|
||||
RALT: 0x0010, // 101-key keyboard only
|
||||
ALT: 0x0020,
|
||||
ALTS: 0x0030,
|
||||
RCMD: 0x0040, // 101-key keyboard only
|
||||
CMD: 0x0080, // 101-key keyboard only
|
||||
CMDS: 0x00c0,
|
||||
CMDS: 0x00C0,
|
||||
ALL_RIGHT: 0x0055, // RSHIFT | RCTRL | RALT | RCMD
|
||||
ALL_SHIFT: 0x00ff, // SHIFT | RSHIFT | CTRL | RCTRL | ALT | RALT | CMD | RCMD
|
||||
ALL_SHIFT: 0x00FF, // SHIFT | RSHIFT | CTRL | RCTRL | ALT | RALT | CMD | RCMD
|
||||
INSERT: 0x0100, // TODO: Placeholder (we currently have no notion of any "insert" states)
|
||||
CAPS_LOCK: 0x0200,
|
||||
NUM_LOCK: 0x0400,
|
||||
SCROLL_LOCK: 0x0800,
|
||||
ALL_LOCKS: 0x0e00 // CAPS_LOCK | NUM_LOCK | SCROLL_LOCK
|
||||
ALL_LOCKS: 0x0E00 // CAPS_LOCK | NUM_LOCK | SCROLL_LOCK
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -919,10 +919,10 @@ CPUPDP11.prototype.calcRemainingTime = function()
|
|||
*
|
||||
* Why not use JavaScript's setTimeout() instead? Good question. For a good answer, see setTimer() below.
|
||||
*
|
||||
* TODO: Consider making the addTimer() and setTimer() interfaces more like the addTrigger() and setTrigger()
|
||||
* TODO: Consider making the addTimer() and setTimer() interfaces more like the addIRQ() and setIRQ()
|
||||
* interfaces (which return the underlying object instead of an array index) and maintaining a separate list
|
||||
* of active timers, in order of highest to lowest cycle countdown values, as this could speed up getBurstCycles()
|
||||
* and updateTimers() functions ever so slightly.
|
||||
* of active timers, in order of highest to lowest cycle countdown values, as this could speed up
|
||||
* getBurstCycles() and updateTimers() functions ever so slightly.
|
||||
*
|
||||
* @this {CPUPDP11}
|
||||
* @param {function()} callBack
|
||||
|
|
|
|||
|
|
@ -1738,7 +1738,8 @@ PDP11.opSPL = function(opCode)
|
|||
}
|
||||
if (!(this.regPSW & PDP11.PSW.CMODE)) {
|
||||
this.regPSW = (this.regPSW & ~(PDP11.PSW.UNUSED | PDP11.PSW.PRI)) | ((opCode & 0x7) << PDP11.PSW.SHIFT.PRI);
|
||||
this.opFlags |= PDP11.OPFLAG.INTQ_DELAY;
|
||||
this.opFlags |= PDP11.OPFLAG.IRQ_DELAY;
|
||||
this.opFlags &= ~PDP11.OPFLAG.IRQ;
|
||||
}
|
||||
this.nStepCycles -= (4 + 1);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -96,15 +96,15 @@ Component.subclass(CPUStatePDP11, CPUPDP11);
|
|||
* CPU offers timer services that will "fire" a callback after a specified delay, which are much more efficient than
|
||||
* requiring the CPU to dive into an interrupt queue and decrement delay counts on every instruction.
|
||||
*
|
||||
* Second, devices that generate interrupts will allocate a trigger object during initialization; we will no longer
|
||||
* be creating and destroying interrupt event objects and inserting/deleting them in a constantly changing queue. Each
|
||||
* trigger contains properties that never change (eg, the vector and priority), along with a "next" pointer that's
|
||||
* only used when the trigger is active.
|
||||
* Second, devices that generate interrupts will allocate an IRQ object during initialization; we will no longer
|
||||
* be creating and destroying interrupt event objects and inserting/deleting them in a constantly changing queue.
|
||||
* Each IRQ contains properties that never change (eg, the vector and priority), along with a "next" pointer that's
|
||||
* only used when the IRQ is active.
|
||||
*
|
||||
* When a device decides it's time to interrupt (either at the end of some I/O operation or when a timer has "fired"),
|
||||
* it will simply "pull the trigger", which basically means that its trigger will be linked onto a list of active
|
||||
* triggers, and in priority order, so that when the CPU is ready to acknowledge interrupts, it need only check the
|
||||
* top of the active trigger list.
|
||||
* When a device decides it's time to interrupt (either at the end of some I/O operation or when a timer has fired),
|
||||
* it will simply set the IRQ, which basically means that the IRQ will be linked onto a list of active IRQs, in
|
||||
* priority order, so that when the CPU is ready to acknowledge interrupts, it need only check the top of the active
|
||||
* IRQ list.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -112,10 +112,10 @@ Component.subclass(CPUStatePDP11, CPUPDP11);
|
|||
* vector: number,
|
||||
* priority: number,
|
||||
* message: number,
|
||||
* next: (Trigger|null)
|
||||
* }} Trigger
|
||||
* next: (IRQ|null)
|
||||
* }} IRQ
|
||||
*/
|
||||
var Trigger;
|
||||
var IRQ;
|
||||
|
||||
/**
|
||||
* initProcessor()
|
||||
|
|
@ -148,12 +148,12 @@ CPUStatePDP11.prototype.initProcessor = function()
|
|||
|
||||
this.nDisableTraps = 0;
|
||||
|
||||
/** @type {Trigger|null} */
|
||||
this.triggerNext = null; // the head of the active triggers list, in priority order
|
||||
/** @type {IRQ|null} */
|
||||
this.irqNext = null; // the head of the active IRQ list, in priority order
|
||||
|
||||
if (DEBUG) {
|
||||
/** @type {Array.<Trigger>} */
|
||||
this.aTriggers = []; // list of all triggers, active or not (just for debugging)
|
||||
/** @type {Array.<IRQ>} */
|
||||
this.aIRQs = []; // list of all IRQs, active or not (just for debugging)
|
||||
}
|
||||
|
||||
this.flags.complete = false;
|
||||
|
|
@ -274,7 +274,7 @@ CPUStatePDP11.prototype.resetMMU = function()
|
|||
this.lastAddr = 0; // this is queried by the Panel when it's not using its own ADDRESS register
|
||||
this.lastOp = 0; // stores the PC and any auto-incs or auto-decs from the last opcode; used to update MMR1 and MMR2
|
||||
|
||||
this.resetTriggers();
|
||||
this.resetIRQs();
|
||||
|
||||
if (this.bus) this.setMemoryAccess();
|
||||
};
|
||||
|
|
@ -768,130 +768,138 @@ CPUStatePDP11.prototype.setSP = function(addr)
|
|||
};
|
||||
|
||||
/**
|
||||
* addTrigger(vector, priority, message)
|
||||
* addIRQ(vector, priority, message)
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @param {number} vector
|
||||
* @param {number} priority
|
||||
* @param {number} [message]
|
||||
* @return {Trigger}
|
||||
* @return {IRQ}
|
||||
*/
|
||||
CPUStatePDP11.prototype.addTrigger = function(vector, priority, message)
|
||||
CPUStatePDP11.prototype.addIRQ = function(vector, priority, message)
|
||||
{
|
||||
var trigger = {vector: vector, priority: priority, message: message || 0, next: null};
|
||||
if (DEBUG) this.aTriggers.push(trigger);
|
||||
return trigger;
|
||||
var irq = {vector: vector, priority: priority, message: message || 0, next: null};
|
||||
if (DEBUG) {
|
||||
irq.name = PDP11.VECTORS[vector];
|
||||
this.aIRQs.push(irq);
|
||||
}
|
||||
return irq;
|
||||
};
|
||||
|
||||
/**
|
||||
* insertTrigger(trigger)
|
||||
* insertIRQ(irq)
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @param {Trigger} trigger
|
||||
* @param {IRQ} irq
|
||||
*/
|
||||
CPUStatePDP11.prototype.insertTrigger = function(trigger)
|
||||
CPUStatePDP11.prototype.insertIRQ = function(irq)
|
||||
{
|
||||
if (trigger != this.triggerNext) {
|
||||
var triggerPrev = this.triggerNext;
|
||||
if (!triggerPrev || triggerPrev.priority <= trigger.priority) {
|
||||
trigger.next = triggerPrev;
|
||||
this.triggerNext = trigger;
|
||||
if (irq != this.irqNext) {
|
||||
var irqPrev = this.irqNext;
|
||||
if (!irqPrev || irqPrev.priority <= irq.priority) {
|
||||
irq.next = irqPrev;
|
||||
this.irqNext = irq;
|
||||
} else {
|
||||
do {
|
||||
var triggerNext = triggerPrev.next;
|
||||
if (!triggerNext || triggerNext.priority <= trigger.priority) {
|
||||
trigger.next = triggerNext;
|
||||
triggerPrev.next = trigger;
|
||||
var irqNext = irqPrev.next;
|
||||
if (!irqNext || irqNext.priority <= irq.priority) {
|
||||
irq.next = irqNext;
|
||||
irqPrev.next = irq;
|
||||
break;
|
||||
}
|
||||
triggerPrev = triggerNext;
|
||||
} while (triggerPrev);
|
||||
irqPrev = irqNext;
|
||||
} while (irqPrev);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* See the writeXCSR() function for an explanation of why signalling an IRQ hardware interrupt
|
||||
* should be done using IRQ_DELAY rather than setting IRQ directly.
|
||||
*/
|
||||
this.opFlags |= PDP11.OPFLAG.IRQ_DELAY;
|
||||
};
|
||||
|
||||
/**
|
||||
* removeTrigger(trigger)
|
||||
* removeIRQ(irq)
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @param {Trigger} trigger
|
||||
* @param {IRQ} irq
|
||||
*/
|
||||
CPUStatePDP11.prototype.removeTrigger = function(trigger)
|
||||
CPUStatePDP11.prototype.removeIRQ = function(irq)
|
||||
{
|
||||
var triggerPrev = this.triggerNext;
|
||||
if (triggerPrev == trigger) {
|
||||
this.triggerNext = trigger.next;
|
||||
var irqPrev = this.irqNext;
|
||||
if (irqPrev == irq) {
|
||||
this.irqNext = irq.next;
|
||||
} else {
|
||||
while (triggerPrev) {
|
||||
var triggerNext = triggerPrev.next;
|
||||
if (triggerNext == trigger) {
|
||||
triggerPrev.next = triggerNext.next;
|
||||
while (irqPrev) {
|
||||
var irqNext = irqPrev.next;
|
||||
if (irqNext == irq) {
|
||||
irqPrev.next = irqNext.next;
|
||||
break;
|
||||
}
|
||||
triggerPrev = triggerNext;
|
||||
irqPrev = irqNext;
|
||||
}
|
||||
}
|
||||
// We could also set trigger.next to null now, but strictly speaking, that shouldn't be necessary.
|
||||
};
|
||||
|
||||
/**
|
||||
* setTrigger(trigger)
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @param {Trigger} trigger
|
||||
* @return {boolean} (true if interrupt dispatched, false if not)
|
||||
*/
|
||||
CPUStatePDP11.prototype.setTrigger = function(trigger)
|
||||
{
|
||||
this.insertTrigger(trigger);
|
||||
|
||||
/*
|
||||
* See the writeXCSR() function for an explanation of why signalling an INTQ hardware interrupt condition
|
||||
* should be done using INTQ_DELAY rather than setting INTQ directly.
|
||||
* We could also set irq.next to null now, but strictly speaking, that shouldn't be necessary.
|
||||
*
|
||||
* Last but not least, if there's still an IRQ on the active IRQ list, we need to make sure IRQ_DELAY
|
||||
* is still set.
|
||||
*/
|
||||
this.opFlags |= PDP11.OPFLAG.INTQ_DELAY;
|
||||
|
||||
if (trigger.message && this.messageEnabled(trigger.message | MessagesPDP11.INT)) {
|
||||
this.printMessage("setInterrupt(vector=" + str.toOct(trigger.vector) + ",priority=" + trigger.priority + ")", true, true);
|
||||
if (this.irqNext) {
|
||||
this.opFlags |= PDP11.OPFLAG.IRQ_DELAY;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* clearTrigger(trigger)
|
||||
* setIRQ(irq)
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @param {Trigger} trigger
|
||||
* @param {IRQ} irq
|
||||
*/
|
||||
CPUStatePDP11.prototype.clearTrigger = function(trigger)
|
||||
CPUStatePDP11.prototype.setIRQ = function(irq)
|
||||
{
|
||||
this.removeTrigger(trigger);
|
||||
this.insertIRQ(irq);
|
||||
|
||||
if (trigger.message && this.messageEnabled(trigger.message | MessagesPDP11.INT)) {
|
||||
this.printMessage("clearInterrupt(vector=" + str.toOct(trigger.vector) + ",priority=" + trigger.priority + ")", true, true);
|
||||
if (irq.message && this.messageEnabled(irq.message | MessagesPDP11.INT)) {
|
||||
this.printMessage("setIRQ(vector=" + str.toOct(irq.vector) + ",priority=" + irq.priority + ")", true, true);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* checkTriggers(priority)
|
||||
* clearIRQ(irq)
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @param {IRQ} irq
|
||||
*/
|
||||
CPUStatePDP11.prototype.clearIRQ = function(irq)
|
||||
{
|
||||
this.removeIRQ(irq);
|
||||
|
||||
if (irq.message && this.messageEnabled(irq.message | MessagesPDP11.INT)) {
|
||||
this.printMessage("clearIRQ(vector=" + str.toOct(irq.vector) + ",priority=" + irq.priority + ")", true, true);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* checkIRQs(priority)
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @param {number} priority
|
||||
* @return {Trigger|null}
|
||||
* @return {IRQ|null}
|
||||
*/
|
||||
CPUStatePDP11.prototype.checkTriggers = function(priority)
|
||||
CPUStatePDP11.prototype.checkIRQs = function(priority)
|
||||
{
|
||||
return (this.triggerNext && this.triggerNext.priority > priority)? this.triggerNext : null;
|
||||
return (this.irqNext && this.irqNext.priority > priority)? this.irqNext : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* resetTriggers(priority)
|
||||
* resetIRQs(priority)
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
*/
|
||||
CPUStatePDP11.prototype.resetTriggers = function()
|
||||
CPUStatePDP11.prototype.resetIRQs = function()
|
||||
{
|
||||
this.triggerNext = null;
|
||||
this.irqNext = null;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -904,27 +912,30 @@ CPUStatePDP11.prototype.checkInterrupts = function()
|
|||
{
|
||||
var fInterrupt = false;
|
||||
|
||||
if (this.opFlags & PDP11.OPFLAG.INTQ) {
|
||||
this.opFlags &= ~PDP11.OPFLAG.INTQ;
|
||||
if (this.opFlags & PDP11.OPFLAG.IRQ) {
|
||||
|
||||
var vector = PDP11.TRAP.PIRQ;
|
||||
var priority = (this.regPIR & PDP11.PSW.PRI) >> PDP11.PSW.SHIFT.PRI;
|
||||
|
||||
var trigger = this.checkTriggers(priority);
|
||||
if (trigger) {
|
||||
vector = trigger.vector;
|
||||
priority = trigger.priority;
|
||||
var irq = this.checkIRQs(priority);
|
||||
if (irq) {
|
||||
vector = irq.vector;
|
||||
priority = irq.priority;
|
||||
}
|
||||
|
||||
if (this.dispatchInterrupt(vector, priority)) {
|
||||
if (trigger) this.removeTrigger(trigger);
|
||||
if (irq) this.removeIRQ(irq);
|
||||
fInterrupt = true;
|
||||
}
|
||||
|
||||
if (!this.irqNext && !this.regPIR) {
|
||||
this.opFlags &= ~PDP11.OPFLAG.IRQ;
|
||||
}
|
||||
}
|
||||
else if (this.opFlags & PDP11.OPFLAG.INTQ_DELAY) {
|
||||
else if (this.opFlags & PDP11.OPFLAG.IRQ_DELAY) {
|
||||
/*
|
||||
* We know that INTQ (bit 1) is clear, so since INTQ_DELAY (bit 0) is set, incrementing opFlags
|
||||
* will transform INTQ_DELAY into INTQ, without affecting any other (higher) bits.
|
||||
* We know that IRQ (bit 2) is clear, so since IRQ_DELAY (bit 0) is set, incrementing opFlags
|
||||
* will eventually transform IRQ_DELAY into IRQ, without affecting any other (higher) bits.
|
||||
*/
|
||||
this.opFlags++;
|
||||
}
|
||||
|
|
@ -1087,20 +1098,12 @@ CPUStatePDP11.prototype.setPSW = function(newPSW)
|
|||
this.regPSW = newPSW;
|
||||
|
||||
/*
|
||||
* Trigger (no pun intended) a call to checkInterrupts(), just in case.
|
||||
*
|
||||
* TODO: I think this is overdone; if you set a breakpoint on checkInterrupts(), you'll see that a significant
|
||||
* percentage of calls do nothing. For example, you'll usually see a spurious checkInterrupts() immediately after
|
||||
* an interrupt has been dispatched, because it's dispatched via trap(), and trap() calls setPSW().
|
||||
*
|
||||
* I mean, sure, it's POSSIBLE that the new PSW loaded by trap() actually set a lower priority, allowing a lower
|
||||
* priority interrupt to immediately be acknowledged. But perhaps we should be a bit more rigorous here.
|
||||
*
|
||||
* For example, we could avoid setting INTQ unless 1) there's actually an active interrupt trigger (ie, triggerNext
|
||||
* is not null) or 2) an optional fCheckInterrupts flag is passed to us, because the caller has some knowledge
|
||||
* that priority could be changing. Just throwing out some ideas....
|
||||
* Trigger a call to checkInterrupts(), just in case. If there's an active IRQ, then setting
|
||||
* OPFLAG.IRQ is a no-brainer, but even if not, we set IRQ_DELAY in case the priority was lowered
|
||||
* enough to permit a programmed interrupt (via regPIR).
|
||||
*/
|
||||
this.opFlags |= PDP11.OPFLAG.INTQ;
|
||||
this.opFlags &= ~PDP11.OPFLAG.IRQ;
|
||||
this.opFlags |= (this.irqNext? PDP11.OPFLAG.IRQ : PDP11.OPFLAG.IRQ_DELAY);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1144,15 +1147,15 @@ CPUStatePDP11.prototype.getPIR = function()
|
|||
*/
|
||||
CPUStatePDP11.prototype.setPIR = function(newPIR)
|
||||
{
|
||||
newPIR &= 0xfe00;
|
||||
newPIR &= PDP11.PIR.BITS;
|
||||
if (newPIR) {
|
||||
var i = newPIR >> 9;
|
||||
var bits = newPIR >> PDP11.PIR.SHIFT.BITS;
|
||||
do {
|
||||
newPIR += 0x22;
|
||||
} while (i >>= 1);
|
||||
newPIR += PDP11.PIR.PIA_INC;
|
||||
} while (bits >>= 1);
|
||||
this.opFlags |= PDP11.OPFLAG.IRQ_DELAY;
|
||||
}
|
||||
this.regPIR = newPIR;
|
||||
this.opFlags |= PDP11.OPFLAG.INTQ;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1397,7 +1400,7 @@ CPUStatePDP11.prototype.trap = function(vector, flag, reason)
|
|||
*
|
||||
* Well, OK, we're also supposed to "lose interest" in the TF flag, too; otherwise, DEC tests fail.
|
||||
*
|
||||
* Finally, setPSW() likes to always set INTQ, to force a check of hardware interrupts prior to
|
||||
* Finally, setPSW() likes to always set IRQ, to force a check of hardware interrupts prior to
|
||||
* the next instruction, just in case the PSW priority was lowered. However, there are "TRAP TEST"
|
||||
* tests like this one:
|
||||
*
|
||||
|
|
@ -1414,16 +1417,16 @@ CPUStatePDP11.prototype.trap = function(vector, flag, reason)
|
|||
*
|
||||
* where, after "TRAP 000" has executed, a hardware interrupt will be acknowledged, and instead of
|
||||
* executing the IOT, we'll execute the HALT and fail the test. We avoid that by relying on the same
|
||||
* trick that the SPL instruction uses: setting INTQ_DELAY instead of INTQ, which effectively delays
|
||||
* INTQ detection for one instruction, which is just long enough to allow the diagnostic to pass.
|
||||
* trick that the SPL instruction uses: setting IRQ_DELAY instead of IRQ, which effectively delays
|
||||
* IRQ detection for one instruction, which is just long enough to allow the diagnostic to pass.
|
||||
*/
|
||||
this.opFlags &= ~(flag | PDP11.OPFLAG.TRAP_TF | PDP11.OPFLAG.INTQ);
|
||||
this.opFlags |= PDP11.OPFLAG.INTQ_DELAY | PDP11.OPFLAG.TRAP;
|
||||
this.opFlags &= ~(flag | PDP11.OPFLAG.TRAP_TF | PDP11.OPFLAG.IRQ_MASK);
|
||||
this.opFlags |= PDP11.OPFLAG.IRQ_DELAY | PDP11.OPFLAG.TRAP_LAST;
|
||||
|
||||
this.trapPSW = -1; // reset flag that we have a trap within a trap
|
||||
|
||||
/*
|
||||
* These next properties (in conjunction with setting PDP11.OPFLAG.TRAP) are purely an aid for the Debugger;
|
||||
* These next properties (in conjunction with setting PDP11.OPFLAG.TRAP_LAST) are purely an aid for the Debugger;
|
||||
* see getTrapStatus().
|
||||
*/
|
||||
this.trapVector = vector;
|
||||
|
|
@ -1467,7 +1470,7 @@ CPUStatePDP11.prototype.trapReturn = function()
|
|||
*/
|
||||
CPUStatePDP11.prototype.getTrapStatus = function()
|
||||
{
|
||||
return (this.opFlags & PDP11.OPFLAG.TRAP)? (this.trapVector | this.trapReason << 8) : 0;
|
||||
return (this.opFlags & PDP11.OPFLAG.TRAP_LAST)? (this.trapVector | this.trapReason << 8) : 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1980,8 +1983,23 @@ CPUStatePDP11.prototype.checkStackLimit1145 = function(access, step, addr)
|
|||
/*
|
||||
* NOTE: The 11/70 CPU Instruction Exerciser does NOT expect reads to trigger a stack overflow,
|
||||
* so we check the access parameter.
|
||||
*
|
||||
* Moreover, TEST 40 of diagnostic EKBBF0 executes this instruction:
|
||||
*
|
||||
* R0=177777 R1=032435 R2=152110 R3=000024 R4=153352 R5=001164
|
||||
* SP=177776 PC=020632 PS=000350 IR=000000 SL=000377 T0 N1 Z0 V0 C0
|
||||
* 020632: 005016 CLR @SP ;cycles=7
|
||||
*
|
||||
* expecting a RED stack overflow trap. Yes, using *any* addresses in the IOPAGE for the stack isn't
|
||||
* a good idea, but who said it was illegal? For now, we're going to restrict overflows to the highest
|
||||
* address tested by the diagnostic (0xFFFE, aka the PSW), by making that address negative.
|
||||
*/
|
||||
if (addr >= 0xFFFE) addr |= ~0xFFFF;
|
||||
if ((access & PDP11.ACCESS.WRITE) && addr <= this.regSL) {
|
||||
/*
|
||||
* regSL can never fall below 0xFF, so this subtraction can never go negative, so this comparison
|
||||
* is always safe.
|
||||
*/
|
||||
if (addr <= this.regSL - 32) {
|
||||
this.trap(PDP11.TRAP.BUS, 0, PDP11.REASON.RED);
|
||||
} else {
|
||||
|
|
@ -2557,18 +2575,16 @@ CPUStatePDP11.prototype.stepCPU = function(nMinCycles)
|
|||
if (!nDebugState) nDebugState++;
|
||||
nDebugCheck++;
|
||||
}
|
||||
|
||||
if (this.opFlags) {
|
||||
|
||||
/*
|
||||
* If we're in the INTQ or WAIT state, check for any pending interrupts.
|
||||
* If we're in the IRQ 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_MASK | PDP11.OPFLAG.WAIT)) /* && nDebugState >= 0 */) {
|
||||
if ((this.opFlags & (PDP11.OPFLAG.IRQ_MASK | PDP11.OPFLAG.WAIT)) /* && nDebugState >= 0 */) {
|
||||
if (this.checkInterrupts()) {
|
||||
if (DEBUGGER && nDebugCheck && this.dbg.checkInstruction(this.getPC(), nDebugState)) {
|
||||
this.stopCPU();
|
||||
|
|
@ -2584,7 +2600,6 @@ CPUStatePDP11.prototype.stepCPU = function(nMinCycles)
|
|||
if (nDebugState < 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Next, check for any pending traps (which, as noted above, must be done after checkInterrupts()).
|
||||
*
|
||||
|
|
@ -2601,8 +2616,9 @@ CPUStatePDP11.prototype.stepCPU = function(nMinCycles)
|
|||
if (nDebugState < 0) break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.assert(!this.irqNext && !this.regPIR);
|
||||
}
|
||||
|
||||
/*
|
||||
* Snapshot the TF bit in opFlags, while clearing all other opFlags (except those in PRESERVE);
|
||||
* we'll check the TRAP_TF bit in opFlags when we come back around for another opcode.
|
||||
|
|
|
|||
|
|
@ -1262,14 +1262,14 @@ if (DEBUGGER) {
|
|||
sMessage += " @" + this.toStrAddr(this.newAddr(this.cpu.getLastPC()));
|
||||
}
|
||||
|
||||
if (this.sMessagePrev && sMessage == this.sMessagePrev) return;
|
||||
this.sMessagePrev = sMessage;
|
||||
|
||||
if (this.bitsMessage & MessagesPDP11.BUFFER) {
|
||||
this.aMessageBuffer.push(sMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.sMessagePrev && sMessage == this.sMessagePrev) return;
|
||||
this.sMessagePrev = sMessage;
|
||||
|
||||
var fRunning;
|
||||
if ((this.bitsMessage & MessagesPDP11.HALT) && this.cpu && (fRunning = this.cpu.isRunning()) || this.isBusy(true)) {
|
||||
this.stopCPU();
|
||||
|
|
@ -3378,8 +3378,9 @@ if (DEBUGGER) {
|
|||
this.bitsMessage &= ~bitsMessage;
|
||||
fCriteria = false;
|
||||
if (bitsMessage == MessagesPDP11.BUFFER) {
|
||||
for (var i = 0; i < this.aMessageBuffer.length; i++) {
|
||||
this.println(this.aMessageBuffer[i]);
|
||||
var i = this.aMessageBuffer.length >= 1000? this.aMessageBuffer.length - 1000 : 0;
|
||||
while (i < this.aMessageBuffer.length) {
|
||||
this.println(this.aMessageBuffer[i++]);
|
||||
}
|
||||
this.aMessageBuffer = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ var PDP11 = {
|
|||
MASK: 0x3
|
||||
},
|
||||
/*
|
||||
* Processor Status Word definitions (stored in regPSW)
|
||||
* Processor Status Word (stored in regPSW) at 177776
|
||||
*/
|
||||
PSW: {
|
||||
CF: 0x0001, // bit 0 (000001) Carry Flag
|
||||
|
|
@ -185,6 +185,19 @@ var PDP11 = {
|
|||
CMODE: 14
|
||||
}
|
||||
},
|
||||
/*
|
||||
* Program Interrupt Register (stored in regPIR) at 177772
|
||||
*
|
||||
* The PIA bits at 5-7 are designed to align with PRI bits 5-7 in the PSW.
|
||||
*/
|
||||
PIR: {
|
||||
BITS: 0xFE00, // bits 9-15 correspond to interrupt requests 1-7
|
||||
PIA: 0x00EE, // the PIA bits contain two copies of the corresponding interrupt request priority
|
||||
PIA_INC: 0x0022, // both sets of PIA bits can be incremented with this constant
|
||||
SHIFT: {
|
||||
BITS: 9
|
||||
}
|
||||
},
|
||||
/*
|
||||
* PDP-11 trap vectors
|
||||
*/
|
||||
|
|
@ -248,17 +261,17 @@ var PDP11 = {
|
|||
* Internal operation state flags
|
||||
*/
|
||||
OPFLAG: {
|
||||
INTQ_DELAY: 0x01, // set INTQ on next check (set by SPL and traps)
|
||||
INTQ: 0x02, // call checkInterrupts()
|
||||
INTQ_MASK: 0x03,
|
||||
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 (WARNING: do not change this bit, or you will likely break opRTI())
|
||||
TRAP_SP: 0x20, // set for a deferred BUS 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
|
||||
IRQ_DELAY: 0x0001, // incremented until it becomes IRQ (set by SPL and traps)
|
||||
IRQ: 0x0002, // time to call checkInterrupts()
|
||||
IRQ_MASK: 0x0003,
|
||||
WAIT: 0x0008, // WAIT operation in progress
|
||||
PRESERVE: 0x000F, // OPFLAG bits to preserve prior to the next instruction
|
||||
TRAP_TF: 0x0010, // aka PDP11.PSW.TF (WARNING: do not change this bit, or you will likely break opRTI())
|
||||
TRAP_SP: 0x0020, // set for a deferred BUS trap (due to a "yellow" stack overflow condition)
|
||||
TRAP_MMU: 0x0040,
|
||||
TRAP_MASK: 0x0070,
|
||||
TRAP_LAST: 0x0080, // set if last operation was a trap (see trapLast for the vector, and trapReason for the reason)
|
||||
NO_FLAGS: 0x0100 // set whenever the PSW is written directly, requiring all updateXXXFlags() functions to leave flags unchanged
|
||||
},
|
||||
/*
|
||||
* Opcode reg (opcode bits 2-0)
|
||||
|
|
@ -492,10 +505,10 @@ var PDP11 = {
|
|||
PPS: 0o177554, // PC11 Punch Status Register
|
||||
PPB: 0o177556, // PC11 Punch Buffer Register
|
||||
|
||||
RCSR: 0o177560, // DL11 Display Terminal: Receiver Status Register
|
||||
RBUF: 0o177562, // DL11 Display Terminal: Receiver Data Buffer Register
|
||||
XCSR: 0o177564, // DL11 Display Terminal: Transmitter Status Register
|
||||
XBUF: 0o177566, // DL11 Display Terminal: Transmitter Data Buffer Register
|
||||
RCSR: 0o177560, // DL11 Receiver Status Register
|
||||
RBUF: 0o177562, // DL11 Receiver Data Buffer Register
|
||||
XCSR: 0o177564, // DL11 Transmitter Status Register
|
||||
XBUF: 0o177566, // DL11 Transmitter Data Buffer Register
|
||||
|
||||
CNSW: 0o177570, // Console (Front Panel) Switch/Display Register
|
||||
|
||||
|
|
@ -578,9 +591,9 @@ var PDP11 = {
|
|||
},
|
||||
DL11: { // Serial Line Interface (program compatible with the KL11 for control of console teleprinters)
|
||||
PRI: 4,
|
||||
RVEC: 0o60,
|
||||
XVEC: 0o64,
|
||||
RCSR: { // 177560
|
||||
RVEC: 0o060,
|
||||
XVEC: 0o064,
|
||||
RCSR: { // 177560: DL11 Receiver Status Register
|
||||
RE: 0x0001, // Reader Enable (W/O)
|
||||
DTR: 0x0002, // Data Terminal Ready (R/W)
|
||||
RTS: 0x0004, // Request To Send (R/W)
|
||||
|
|
@ -598,14 +611,14 @@ var PDP11 = {
|
|||
WMASK: 0x006F, // bits writable
|
||||
BAUD: 9600
|
||||
},
|
||||
RBUF: { // 177562
|
||||
RBUF: { // 177562: DL11 Receiver Data Buffer Register
|
||||
DATA: 0x00ff, // Received Data (R/O)
|
||||
PARITY: 0x1000, // Received Data Parity (R/O)
|
||||
FE: 0x2000, // Framing Error (R/O)
|
||||
OE: 0x4000, // Overrun Error (R/O)
|
||||
ERROR: 0x8000 // Error (R/O)
|
||||
},
|
||||
XCSR: { // 177564
|
||||
XCSR: { // 177564: DL11 Transmitter Status Register
|
||||
BREAK: 0x0001, // BREAK (R/W)
|
||||
MAINT: 0x0004, // Maintenance (R/W)
|
||||
TIE: 0x0040, // Transmitter Interrupt Enable (R/W)
|
||||
|
|
@ -614,7 +627,7 @@ var PDP11 = {
|
|||
WMASK: 0x0045,
|
||||
BAUD: 9600
|
||||
},
|
||||
XBUF: { // 177566
|
||||
XBUF: { // 177566: DL11 Transmitter Data Buffer Register
|
||||
DATA: 0x00FF // Transmitted Data (W/O) TODO: Determine why pdp11.js effectively defined this as 0x7F
|
||||
}
|
||||
},
|
||||
|
|
@ -622,7 +635,7 @@ var PDP11 = {
|
|||
PRI: 6,
|
||||
VEC: 0o100,
|
||||
DELAY: 0,
|
||||
LKS: {
|
||||
LKS: { // 177546: KW11-L Clock Status
|
||||
IE: 0x0040, // Interrupt Enable
|
||||
MON: 0x0080, // Monitor
|
||||
MASK: 0x00C0 // these are the only bits that can read or written
|
||||
|
|
@ -630,9 +643,9 @@ var PDP11 = {
|
|||
},
|
||||
PC11: { // High Speed Reader & Punch (PR11 is a Reader-only unit)
|
||||
PRI: 4, // NOTE: reader has precedence over punch
|
||||
RVEC: 0o70, // reader vector
|
||||
PVEC: 0o74, // punch vector
|
||||
PRS: {
|
||||
RVEC: 0o070, // reader vector
|
||||
PVEC: 0o074, // punch vector
|
||||
PRS: { // 177550: PC11 (and PR11) Reader Status Register
|
||||
RE: 0x0001, // Reader Enable (W/O)
|
||||
RIE: 0x0040, // Reader Interrupt Enable (allows the DONE and ERROR bits to trigger an interrupt)
|
||||
DONE: 0x0080, // Done (R/O)
|
||||
|
|
@ -643,15 +656,20 @@ var PDP11 = {
|
|||
WMASK: 0x0041, // bits writable
|
||||
BAUD: 3600
|
||||
},
|
||||
PRB: {
|
||||
PRB: { // 177552: PC11 (and PR11) Reader Buffer Register
|
||||
MASK: 0x00FF // Data
|
||||
},
|
||||
PPS: {
|
||||
PPS: { // 177554: PC11 Punch Status Register
|
||||
/*
|
||||
* TODO: Flesh this out if/when we add Paper Tape Punch support
|
||||
*/
|
||||
BAUD: 600
|
||||
},
|
||||
PPB: { // 177556: PC11 Punch Buffer Register
|
||||
/*
|
||||
* TODO: Flesh this out if/when we add Paper Tape Punch support
|
||||
*/
|
||||
}
|
||||
},
|
||||
RK11: { // RK11 Disk Controller
|
||||
PRI: 5,
|
||||
|
|
@ -826,6 +844,15 @@ var PDP11 = {
|
|||
RDATA: 0b1100, // Read Data
|
||||
RDNC: 0b1110 // Read Data without Header Check
|
||||
}
|
||||
},
|
||||
VECTORS: {
|
||||
0o060: "DL11.RCSR",
|
||||
0o064: "DL11.XCSR",
|
||||
0o070: "PC11.PRS",
|
||||
0o074: "PC11.PPS",
|
||||
0o100: "KW11",
|
||||
0o160: "RL11",
|
||||
0o220: "RK11"
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ DevicePDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
device.interruptKW11();
|
||||
});
|
||||
|
||||
this.kw11.trigger = cpu.addTrigger(PDP11.KW11.VEC, PDP11.KW11.PRI, MessagesPDP11.KW11);
|
||||
this.kw11.irq = cpu.addIRQ(PDP11.KW11.VEC, PDP11.KW11.PRI, MessagesPDP11.KW11);
|
||||
|
||||
bus.addIOTable(this, DevicePDP11.UNIBUS_IOTABLE);
|
||||
bus.addResetHandler(this.reset.bind(this));
|
||||
|
|
@ -176,7 +176,7 @@ DevicePDP11.prototype.interruptKW11 = function()
|
|||
{
|
||||
this.kw11.lks |= PDP11.KW11.LKS.MON;
|
||||
if (this.kw11.lks & PDP11.KW11.LKS.IE) {
|
||||
this.cpu.setTrigger(this.kw11.trigger);
|
||||
this.cpu.setIRQ(this.kw11.irq);
|
||||
}
|
||||
if (this.cmp) this.cmp.updateDisplays(1);
|
||||
this.cpu.setTimer(this.kw11.timer, 1000/60);
|
||||
|
|
@ -214,6 +214,7 @@ DevicePDP11.prototype.writeLKS = function(data, addr)
|
|||
* I think that was wrong, and that all a write should do is mask off all the other (non-writable) bits.
|
||||
*/
|
||||
this.kw11.lks = data & PDP11.KW11.LKS.MASK;
|
||||
if (!(this.kw11.lks & PDP11.KW11.LKS.IE)) this.cpu.clearIRQ(this.kw11.irq);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -280,9 +280,9 @@ PC11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
}
|
||||
}
|
||||
|
||||
this.triggerReaderInterrupt = this.cpu.addTrigger(PDP11.PC11.RVEC, PDP11.PC11.PRI, MessagesPDP11.PC11);
|
||||
this.irqReader = this.cpu.addIRQ(PDP11.PC11.RVEC, PDP11.PC11.PRI, MessagesPDP11.PC11);
|
||||
|
||||
this.timerReaderAdvance = this.cpu.addTimer(function readyReader() {
|
||||
this.timerReader = this.cpu.addTimer(function readyReader() {
|
||||
pc11.advanceReader();
|
||||
});
|
||||
|
||||
|
|
@ -820,7 +820,7 @@ PC11.prototype.advanceReader = function()
|
|||
this.prs |= PDP11.PC11.PRS.DONE;
|
||||
this.prs &= ~PDP11.PC11.PRS.BUSY;
|
||||
if (this.prs & PDP11.PC11.PRS.RIE) {
|
||||
this.cpu.setTrigger(this.triggerReaderInterrupt);
|
||||
this.cpu.setIRQ(this.irqReader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -864,7 +864,7 @@ PC11.prototype.writePRS = function(data, addr)
|
|||
if (this.prs & PDP11.PC11.PRS.ERROR) {
|
||||
data &= ~PDP11.PC11.PRS.RE;
|
||||
if (this.prs & PDP11.PC11.PRS.RIE) {
|
||||
this.cpu.setTrigger(this.triggerReaderInterrupt);
|
||||
this.cpu.setIRQ(this.irqReader);
|
||||
}
|
||||
} else {
|
||||
this.prs &= ~PDP11.PC11.PRS.DONE;
|
||||
|
|
@ -875,7 +875,7 @@ PC11.prototype.writePRS = function(data, addr)
|
|||
* that's the rate we'll choose as well (ie, 1000ms / 300). As an aside, the original "low speed"
|
||||
* version of the reader ran at 10 CPS.
|
||||
*/
|
||||
this.cpu.setTimer(this.timerReaderAdvance, this.getBaudTimeout(this.nBaudReceive));
|
||||
this.cpu.setTimer(this.timerReader, this.getBaudTimeout(this.nBaudReceive));
|
||||
}
|
||||
}
|
||||
this.prs = (this.prs & ~PDP11.PC11.PRS.WMASK) | (data & PDP11.PC11.PRS.WMASK);
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ RK11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
*/
|
||||
this.initController();
|
||||
|
||||
this.triggerInterrupt = this.cpu.addTrigger(PDP11.RK11.VEC, PDP11.RK11.PRI, MessagesPDP11.RK11);
|
||||
this.irq = this.cpu.addIRQ(PDP11.RK11.VEC, PDP11.RK11.PRI, MessagesPDP11.RK11);
|
||||
|
||||
bus.addIOTable(this, RK11.UNIBUS_IOTABLE);
|
||||
bus.addResetHandler(this.reset.bind(this));
|
||||
|
|
@ -950,9 +950,7 @@ RK11.prototype.processCommand = function()
|
|||
if (fInterrupt) {
|
||||
this.csr &= ~PDP11.RK11.RKCS.GO;
|
||||
this.csr |= PDP11.RK11.RKCS.CRDY;
|
||||
if (this.csr & PDP11.RK11.RKCS.IE) {
|
||||
this.cpu.setTrigger(this.triggerInterrupt);
|
||||
}
|
||||
if (this.csr & PDP11.RK11.RKCS.IE) this.cpu.setIRQ(this.irq);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ RL11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
*/
|
||||
this.initController();
|
||||
|
||||
this.triggerInterrupt = this.cpu.addTrigger(PDP11.RL11.VEC, PDP11.RL11.PRI, MessagesPDP11.RL11);
|
||||
this.irq = this.cpu.addIRQ(PDP11.RL11.VEC, PDP11.RL11.PRI, MessagesPDP11.RL11);
|
||||
|
||||
bus.addIOTable(this, RL11.UNIBUS_IOTABLE);
|
||||
bus.addResetHandler(this.reset.bind(this));
|
||||
|
|
@ -965,9 +965,7 @@ RL11.prototype.processCommand = function()
|
|||
|
||||
if (fInterrupt) {
|
||||
this.csr |= PDP11.RL11.RLCS.DRDY | PDP11.RL11.RLCS.CRDY;
|
||||
if (this.csr & PDP11.RL11.RLCS.IE) {
|
||||
this.cpu.setTrigger(this.triggerInterrupt);
|
||||
}
|
||||
if (this.csr & PDP11.RL11.RLCS.IE) this.cpu.setIRQ(this.irq);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -284,42 +284,29 @@ SerialPortPDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
|
||||
var serial = this;
|
||||
|
||||
this.triggerReceiveInterrupt = this.cpu.addTrigger(PDP11.DL11.RVEC, PDP11.DL11.PRI, MessagesPDP11.DL11);
|
||||
this.irqReceiver = this.cpu.addIRQ(PDP11.DL11.RVEC, PDP11.DL11.PRI, MessagesPDP11.DL11);
|
||||
|
||||
this.timerReceiveInterrupt = this.cpu.addTimer(function readyReceiver() {
|
||||
if (!(serial.rcsr & PDP11.DL11.RCSR.RD)) {
|
||||
if (serial.abReceive.length) {
|
||||
/*
|
||||
* Here, as elsewhere (eg, the PC11 component), even if I trusted all incoming data
|
||||
* to be byte values (which I don't), there's also the risk that it could be signed data
|
||||
* (eg, -128 to 127, instead of 0 to 255). Both risks are good reasons to always mask
|
||||
* the data assigned to RBUF with 0xff.
|
||||
*/
|
||||
serial.rbuf = serial.abReceive.shift() & 0xff;
|
||||
if (serial.fUpperCase) {
|
||||
/*
|
||||
* Automatically transform lower-case ASCII codes to upper-case; fUpperCase should
|
||||
* only be set when a terminal or some sort of pseudo-display is being used and we don't
|
||||
* trust it to have its CAPS-LOCK setting correct.
|
||||
*/
|
||||
if (serial.rbuf >= 0x61 && serial.rbuf < 0x7A) {
|
||||
serial.rbuf -= 0x20;
|
||||
}
|
||||
}
|
||||
var b = serial.receiveByte();
|
||||
if (b >= 0) {
|
||||
serial.rbuf = b;
|
||||
if (!(serial.rcsr & PDP11.DL11.RCSR.RD)) {
|
||||
serial.rcsr |= PDP11.DL11.RCSR.RD;
|
||||
if (serial.rcsr & PDP11.DL11.RCSR.RIE) {
|
||||
serial.cpu.setTrigger(serial.triggerReceiveInterrupt);
|
||||
}
|
||||
} else {
|
||||
serial.rbuf |= PDP11.DL11.RBUF.OE | PDP11.DL11.RBUF.ERROR;
|
||||
}
|
||||
if (serial.rcsr & PDP11.DL11.RCSR.RIE) {
|
||||
cpu.setIRQ(serial.irqReceiver);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.triggerTransmitInterrupt = this.cpu.addTrigger(PDP11.DL11.XVEC, PDP11.DL11.PRI, MessagesPDP11.DL11);
|
||||
this.irqTransmitter = this.cpu.addIRQ(PDP11.DL11.XVEC, PDP11.DL11.PRI, MessagesPDP11.DL11);
|
||||
|
||||
this.timerTransmitInterrupt = this.cpu.addTimer(function readyTransmitter() {
|
||||
serial.xcsr |= PDP11.DL11.XCSR.READY;
|
||||
if (serial.xcsr & PDP11.DL11.XCSR.TIE) {
|
||||
serial.cpu.setTrigger(serial.triggerTransmitInterrupt);
|
||||
cpu.setIRQ(serial.irqTransmitter);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -544,10 +531,43 @@ SerialPortPDP11.prototype.receiveData = function(data)
|
|||
else {
|
||||
this.abReceive = this.abReceive.concat(data);
|
||||
}
|
||||
|
||||
this.cpu.setTimer(this.timerReceiveInterrupt, this.getBaudTimeout(this.nBaudReceive));
|
||||
|
||||
return true; // for now, return true regardless, since we're buffering everything anyway
|
||||
};
|
||||
|
||||
/**
|
||||
* receiveByte()
|
||||
*
|
||||
* @this {SerialPortPDP11}
|
||||
* @return {number} (0x00-0xff if byte available, -1 if not)
|
||||
*/
|
||||
SerialPortPDP11.prototype.receiveByte = function()
|
||||
{
|
||||
var b = -1;
|
||||
if (this.abReceive.length) {
|
||||
/*
|
||||
* Here, as elsewhere (eg, the PC11 component), even if I trusted all incoming data
|
||||
* to be byte values (which I don't), there's also the risk that it could be signed data
|
||||
* (eg, -128 to 127, instead of 0 to 255). Both risks are good reasons to always mask
|
||||
* the data assigned to RBUF with 0xff.
|
||||
*/
|
||||
b = this.abReceive.shift() & 0xff;
|
||||
this.printMessage("receiveByte(" + str.toHexByte(b) + ")");
|
||||
if (this.fUpperCase) {
|
||||
/*
|
||||
* Automatically transform lower-case ASCII codes to upper-case; fUpperCase should
|
||||
* only be set when a terminal or some sort of pseudo-display is being used and we don't
|
||||
* trust it to have its CAPS-LOCK setting correct.
|
||||
*/
|
||||
if (b >= 0x61 && b < 0x7A) b -= 0x20;
|
||||
}
|
||||
this.cpu.setTimer(this.timerReceiveInterrupt, this.getBaudTimeout(this.nBaudReceive));
|
||||
}
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* transmitByte(b)
|
||||
*
|
||||
|
|
@ -618,6 +638,13 @@ SerialPortPDP11.prototype.transmitByte = function(b)
|
|||
fTransmitted = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: When debugging issues involving the SerialPort, such as debugging code between a pair of
|
||||
* transmitted bytes, you can pass 0 instead of getBaudTimeout() to setTimer() to minimize the amount
|
||||
* of time spent waiting for XCSR.READY to be set again.
|
||||
*/
|
||||
this.cpu.setTimer(this.timerTransmitInterrupt, this.getBaudTimeout(this.nBaudTransmit));
|
||||
|
||||
return fTransmitted;
|
||||
};
|
||||
|
||||
|
|
@ -655,9 +682,6 @@ SerialPortPDP11.prototype.writeRCSR = function(data, addr)
|
|||
SerialPortPDP11.prototype.readRBUF = function(addr)
|
||||
{
|
||||
this.rcsr &= ~PDP11.DL11.RCSR.RD;
|
||||
if (this.abReceive.length > 0) {
|
||||
this.cpu.setTimer(this.timerReceiveInterrupt, this.getBaudTimeout(this.nBaudReceive));
|
||||
}
|
||||
return this.rbuf;
|
||||
};
|
||||
|
||||
|
|
@ -696,16 +720,17 @@ SerialPortPDP11.prototype.writeXCSR = function(data, addr)
|
|||
/*
|
||||
* If the device is READY, and TIE is being set, then request a hardware interrupt.
|
||||
*
|
||||
* Conversely, if TIE is being cleared, remove the request; this satisfies a test in MAINDEC TEST 15,
|
||||
* which appears to clear, set, and clear the Transmitter Interrupt Enable (TIE) bit in rapid succession,
|
||||
* with the expectation that NO interrupt will be generated. However, this fix also requires a
|
||||
* complementary change in setTrigger(), to request hardware interrupts with INTQ_DELAY rather than INTQ.
|
||||
* Conversely, if TIE is being cleared, remove the request; this resolves a problem within
|
||||
* MAINDEC TEST 15, where the Transmitter Interrupt Enable (TIE) bit is cleared, set, and cleared
|
||||
* in rapid succession, with the expectation that NO interrupt will be generated. Note that
|
||||
* this fix also requires a complementary change in setIRQ(), to request hardware interrupts with
|
||||
* IRQ_DELAY rather than IRQ.
|
||||
*/
|
||||
if (this.xcsr & PDP11.DL11.XCSR.READY) {
|
||||
if (data & PDP11.DL11.XCSR.TIE) {
|
||||
this.cpu.setTrigger(this.triggerTransmitInterrupt);
|
||||
this.cpu.setIRQ(this.irqTransmitter);
|
||||
} else {
|
||||
this.cpu.clearTrigger(this.triggerTransmitInterrupt);
|
||||
this.cpu.clearIRQ(this.irqTransmitter);
|
||||
}
|
||||
}
|
||||
this.xcsr = (this.xcsr & ~PDP11.DL11.XCSR.WMASK) | (data & PDP11.DL11.XCSR.WMASK);
|
||||
|
|
@ -732,15 +757,8 @@ SerialPortPDP11.prototype.readXBUF = function(addr)
|
|||
*/
|
||||
SerialPortPDP11.prototype.writeXBUF = function(data, addr)
|
||||
{
|
||||
data &= PDP11.DL11.XBUF.DATA;
|
||||
this.transmitByte(data);
|
||||
this.transmitByte(data & PDP11.DL11.XBUF.DATA);
|
||||
this.xcsr &= ~PDP11.DL11.XCSR.READY;
|
||||
/*
|
||||
* NOTE: When debugging issues involving the SerialPort, such as debugging code between a pair of
|
||||
* transmitted bytes, you can pass 0 instead of getBaudTimeout() to setTimer() to minimize the amount
|
||||
* of time spent waiting for XCSR.READY to be set again.
|
||||
*/
|
||||
this.cpu.setTimer(this.timerTransmitInterrupt, this.getBaudTimeout(this.nBaudTransmit));
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
|||
Loading…
Reference in a new issue