Cleaned up the PDP-11 updateDisplay(s) code, and removed the update hack inside the WAIT instruction; display updates now occur in sync with the machine's 60Hz clock

This commit is contained in:
Jeff 2016-11-04 13:35:20 -07:00 committed by Jeff Parsons
commit a7a44a8288
9 changed files with 485 additions and 460 deletions

View file

@ -744,7 +744,7 @@ ComputerPDP11.prototype.donePowerOn = function(aParms)
* TODO: Do we not care about the return value here? (ie, is checking fRestoreError sufficient)?
*/
this.powerRestore(this.cpu, stateComputer, fRepower, fRestore);
this.updateStatus();
this.updateDisplays();
this.cpu.autoStart();
}
@ -958,7 +958,7 @@ ComputerPDP11.prototype.powerOff = function(fSave, fShutdown)
*
* Ditto for the CPU, in part because if the Front Panel resets before the CPU, it will end up
* snapping/displaying the PC as of the last instruction executed, before the CPU resets the PC,
* causing the Front Panel to display a stale address when we call updateStatus() at the end.
* causing the Front Panel to display a stale address when we call updateDisplays() at the end.
*
* @this {ComputerPDP11}
*/
@ -980,7 +980,7 @@ ComputerPDP11.prototype.reset = function()
component.reset();
}
}
this.updateStatus(true);
this.updateDisplays(-1);
};
/**
@ -1005,7 +1005,7 @@ ComputerPDP11.prototype.start = function(ms, nCycles)
component.start(ms, nCycles);
}
}
this.updateStatus(true);
this.updateDisplays(-1);
};
/**
@ -1030,19 +1030,20 @@ ComputerPDP11.prototype.stop = function(ms, nCycles)
component.stop(ms, nCycles);
}
}
this.updateStatus(true);
this.updateDisplays(-1);
};
/**
* updateStatus(fForce)
* updateDisplays(nUpdate)
*
* TODO: Notify all (other) components with an updateStatus() method that the computer's state has changed.
* TODO: Notify all components with an updateDisplay() method that the computer's state has changed (not
* just the hard-coded ones below).
*
* If any DOM controls were bound to the CPU, then we need to call its updateStatus() handler; if there are no
* such bindings, then cpu.updateStatus() does nothing.
* If any DOM controls were bound to the CPU, then we need to call its updateDisplay() handler; if there are no
* such bindings, then cpu.updateDisplay() does nothing.
*
* Similarly, if there's a Panel, then we need to call its updateStatus() handler, in case it created its own canvas
* and implemented its own register display (eg, dumpRegisters()); if not, then panel.updateStatus() also does nothing.
* Similarly, if there's a Panel, then we need to call its updateDisplay() handler, in case it created its own canvas
* and implemented its own register display (eg, dumpRegisters()); if not, then panel.updateDisplay() also does nothing.
*
* In practice, there will *either* be a Panel with a custom canvas *or* a set of DOM controls bound to the CPU *or*
* neither. In theory, there could be BOTH, but that would be unusual.
@ -1054,21 +1055,20 @@ ComputerPDP11.prototype.stop = function(ms, nCycles)
* Panel.
*
* @this {ComputerPDP11}
* @param {boolean} [fForce] (true will display registers even if the CPU is running and "live" registers are not enabled)
*/
ComputerPDP11.prototype.updateStatus = function(fForce)
* @param {number} [nUpdate] (1 for periodic, -1 for forced, 0 or undefined otherwise)
*/
ComputerPDP11.prototype.updateDisplays = function(nUpdate)
{
/*
* fForce is generally set to true whenever the CPU is transitioning to/from a running state, in which case
* cpu.updateStatus() will definitely want to hide/show register contents; however, at other times, when the
* nUpdate is generally set to -1 whenever the CPU is transitioning to/from a running state, in which case
* cpu.updateDisplay() will definitely want to hide/show register contents; however, at other times, when the
* CPU is running, constantly updating the DOM controls too frequently can adversely impact overall performance.
*
* So fForce serves as a hint to help cpu.updateStatus() make a more informed decision. panel.updateStatus()
* currently doesn't care, on the theory that canvas updates should be significantly faster than DOM updates,
* but we still pass fForce on.
* nUpdate will also be -1 whenever the Debugger has modified the state of the machine, implying that we're
* not sure what, if anything, actually changed.
*/
if (this.cpu) this.cpu.updateStatus(fForce);
if (this.panel) this.panel.updateStatus(fForce);
if (this.cpu) this.cpu.updateDisplay(nUpdate);
if (this.panel) this.panel.updateDisplay(nUpdate);
};
/**

View file

@ -505,26 +505,28 @@ CPUPDP11.prototype.setBinding = function(sType, sBinding, control, sValue)
};
/**
* updateComputer(fForce)
* updateDisplays(nUpdate)
*
* Simpler wrapper around the Computer's updateDisplays() method.
*
* @this {CPUPDP11}
* @param {boolean} [fForce]
* @param {number} [nUpdate] (1 for periodic, -1 for forced, 0 or undefined otherwise)
*/
CPUPDP11.prototype.updateComputer = function(fForce)
CPUPDP11.prototype.updateDisplays = function(nUpdate)
{
if (this.cmp) this.cmp.updateStatus(fForce);
if (this.cmp) this.cmp.updateDisplays(nUpdate);
};
/**
* updateStatus(fForce)
* updateDisplay(nUpdate)
*
* Some of the CPU bindings provide feedback and therefore need to be updated periodically. This is called
* via the Computer's updateStatus() handler several times per second; see YIELDS_PER_STATUS.
* Some of the CPU bindings provide feedback and therefore need to be updated periodically.
* However, this should be called via the Computer's updateDisplays() interface, not directly.
*
* @this {CPUPDP11}
* @param {boolean} [fForce]
* @param {number} [nUpdate] (1 for periodic, -1 for forced, 0 or undefined otherwise)
*/
CPUPDP11.prototype.updateStatus = function(fForce)
CPUPDP11.prototype.updateDisplay = function(nUpdate)
{
var controlSpeed = this.bindings["speed"];
if (controlSpeed) controlSpeed.textContent = this.getSpeedCurrent();
@ -1101,7 +1103,7 @@ CPUPDP11.prototype.runCPU = function()
if (this.nCyclesNextYield <= 0) {
this.nCyclesNextYield += this.nCyclesPerYield;
if (++this.nYieldsSinceStatusUpdate >= CPUPDP11.YIELDS_PER_STATUS) {
this.updateComputer();
this.updateDisplays();
this.nYieldsSinceStatusUpdate = 0;
}
break;
@ -1216,7 +1218,7 @@ CPUPDP11.prototype.yieldCPU = function()
* odd for those messages to show CPU state changes if the Control Panel, Video display, etc, does not,
* so I've added this call to try to keep things looking synchronized.
*/
this.updateComputer();
this.updateDisplays();
};
if (NODE) module.exports = CPUPDP11;

View file

@ -1754,13 +1754,13 @@ PDP11.opWAIT = function(opCode)
* NOTE: It's almost always a bad idea to add more checks to the inner stepCPU() loop, because every additional
* check can have a measurable (negative) impact on performance. Which is why it's important to use opFlags bits
* whenever possible, since we can test for multiple (up to 32) exceptional conditions with a single check.
*
* Finally, we used to update the machine's displays whenever transitioning to the WAIT state. However,
* it makes more sense to decouple display updates from specific instructions and rely on timers instead;
* the PDP-11 KW11 (60Hz Line Clock) timer is the perfect candidate. See device.js.
*
* if (!(this.opFlags & PDP11.OPFLAG.WAIT) && this.cmp) this.cmp.updateDisplays();
*/
if (!(this.opFlags & PDP11.OPFLAG.WAIT)) {
/*
* Since here we're actually transitioning to WAIT, let's update the Panel's LEDs (well, OK, among other things).
*/
if (this.cmp) this.cmp.updateStatus();
}
this.opFlags |= PDP11.OPFLAG.WAIT;
this.advancePC(-2);
this.nStepCycles -= 3;

View file

@ -464,8 +464,8 @@ if (DEBUGGER) {
DebuggerPDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.bus = bus;
this.cpu = cpu;
this.cmp = cmp;
this.cpu = cpu;
this.panel = cmp.panel;
/*
@ -677,7 +677,7 @@ if (DEBUGGER) {
this.cpu.setByteDirect(addr, b);
}
if (inc) this.incAddr(dbgAddr, inc);
this.cmp.updateStatus(true); // force a computer status update if, say, video memory was the target
this.cmp.updateDisplays(-1);
}
};
@ -699,7 +699,7 @@ if (DEBUGGER) {
this.cpu.setWordDirect(addr, w);
}
if (inc) this.incAddr(dbgAddr, inc);
this.cmp.updateStatus(true); // force a computer status update if, say, video memory was the target
this.cmp.updateDisplays(-1);
}
};
@ -1313,15 +1313,15 @@ if (DEBUGGER) {
};
/**
* stepCPU(nCycles, fRegs, fUpdateStatus)
* stepCPU(nCycles, fRegs, fUpdateDisplays)
*
* @this {DebuggerPDP11}
* @param {number} nCycles (0 for one instruction without checking breakpoints)
* @param {boolean} [fRegs] is true to display registers after step (default is false)
* @param {boolean} [fUpdateStatus] is false to disable Computer status updates (default is true)
* @param {boolean} [fUpdateDisplays] is false to disable Computer display updates (default is true)
* @return {boolean}
*/
DebuggerPDP11.prototype.stepCPU = function(nCycles, fRegs, fUpdateStatus)
DebuggerPDP11.prototype.stepCPU = function(nCycles, fRegs, fUpdateDisplays)
{
if (!this.checkCPU()) return false;
@ -1363,15 +1363,11 @@ if (DEBUGGER) {
/*
* Because we called cpu.stepCPU() and not cpu.startCPU(), we must nudge the Computer's update code,
* and then update our own state. Normally, the only time fUpdateStatus will be false is when doTrace()
* is calling us in a loop, in which case it will perform its own updateStatus() when it's done.
* and then update our own state. Normally, the only time fUpdateDisplays will be false is when doTrace()
* is calling us in a loop, in which case it will perform its own updateDisplays() when it's done.
*/
if (fUpdateStatus !== false) {
/*
* Make an effort to keep any Front Panel in sync with us.
*/
if (this.panel && this.panel.stop) this.panel.stop();
this.cmp.updateStatus();
if (fUpdateDisplays !== false) {
this.cmp.updateDisplays(-1);
}
this.updateStatus(fRegs || false);
@ -2729,7 +2725,7 @@ if (DEBUGGER) {
if (asArgs[2] === undefined) {
this.println("begin assemble at " + this.toStrAddr(dbgAddr));
this.fAssemble = true;
this.cmp.updateStatus();
this.cmp.updateDisplays();
return;
}
@ -3438,7 +3434,7 @@ if (DEBUGGER) {
this.println("unknown register: " + sReg);
return;
}
this.cmp.updateStatus();
this.cmp.updateDisplays();
this.println("updated registers:");
}
@ -3682,13 +3678,13 @@ if (DEBUGGER) {
},
function onCountStepComplete() {
/*
* We explicitly called stepCPU() with fUpdateStatus === false, because repeatedly
* calling updateStatus() can be very slow, especially if a Control Panel is present
* with displayLiveRegs enabled, so once the repeat count has been exhausted, we must
* perform a final updateStatus().
* We explicitly called stepCPU() with fUpdateDisplays set to false, because repeatedly
* calling updateDisplays() can be very slow, especially if a Control Panel is present with
* displayLiveRegs enabled, so once the repeat count has been exhausted, we must perform
* a final updateDisplays().
*/
if (dbg.panel && dbg.panel.stop) dbg.panel.stop();
dbg.cmp.updateStatus();
dbg.cmp.updateDisplays();
dbg.setBusy(false);
}
);

View file

@ -569,7 +569,7 @@ var PDP11 = {
DATA: 0x00FF // Transmitted Data (W/O) TODO: Determine why pdp11.js effectively defined this as 0x7F
}
},
KW11: { // KW11-L Line Time Clock
KW11: { // KW11-L Line Time Clock (60Hz; well, OK, or 50Hz, if you're in the UK, I suppose...)
PRI: 6,
VEC: 0o100,
DELAY: 0,

View file

@ -60,7 +60,7 @@ function DevicePDP11(parmsDevice)
{
Component.call(this, "Device", parmsDevice, DevicePDP11, MessagesPDP11.DEVICE);
this.kw11 = { // LW11 registers
this.kw11 = { // KW11 registers
csr: 0,
timer: -1 // initBus() will initialize this timer ID
};
@ -115,6 +115,7 @@ DevicePDP11.M9312 = [
DevicePDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.bus = bus;
this.cmp = cmp;
this.cpu = cpu;
this.dbg = dbg;
@ -153,6 +154,7 @@ DevicePDP11.prototype.kw11_interrupt = function()
this.cpu.setTrigger(this.kw11.trigger);
this.cpu.setTimer(this.kw11.timer, 1000/60);
}
if (this.cmp) this.cmp.updateDisplays(1);
};
/**

View file

@ -60,6 +60,8 @@ function PanelPDP11(parmsPanel)
* TODO: Add some UI for fDisplayLiveRegs (either an XML property, or a UI checkbox, or both).
*/
this.cLiveRegs = 0;
this.nPeriodicCount = 0;
this.nPeriodicLimit = 60;
this.fDisplayLiveRegs = true;
/*
@ -672,14 +674,14 @@ PanelPDP11.prototype.processContinue = function(value, index)
this.stop();
/*
* Going through the normal channels (ie, the Computer's updateStatus() interface) ensures that ALL
* updateStatus() handlers will be called, including ours.
* Going through the normal channels (ie, the Computer's updateDisplays() interface) ensures that
* ALL updateDisplay() handlers will be called, including ours.
*
* NOTE: If we used the Debugger's stepCPU() function, then that includes a call to updateStatus();
* NOTE: If we used the Debugger's stepCPU() function, then that includes a call to updateDisplay();
* unfortunately, it will have happened BEFORE we called stop() to update the 'ADDRESS' register, so
* we still need to call it again.
*/
if (this.cmp) this.cmp.updateStatus();
if (this.cmp) this.cmp.updateDisplays();
}
else {
this.cpu.startCPU();
@ -803,13 +805,26 @@ PanelPDP11.prototype.setAddr = function(value)
/**
* advanceAddr()
*
* This should also take care of the following Front Panel behaviors when the accessing the general-purpose
* registers:
*
* 1) ADDRESS display incremented by 1 (instead of 2)
* 2) The STEP after the last register is 177700, such that the addresses are looped
*
* A third behavior is NOT emulated: preventing the ADDRESS from stepping to the first General Register (177700)
* from 177676.
*
* @this {PanelPDP11}
* @return {number}
*/
PanelPDP11.prototype.advanceAddr = function()
{
var inc = this.getSwitch(PanelPDP11.SWITCH.STEP)? 2 : -2;
this.regAddr = (this.regAddr + inc) & this.bus.nBusMask;
var nRegs = this.cpu.model < PDP11.MODEL_1145? 8 : 16;
var fGenRegs = (this.regAddr >= PDP11.UNIBUS.R0SET0 /*177700*/ && this.regAddr < PDP11.UNIBUS.R0SET0 + nRegs);
var inc = fGenRegs? 1 : 2;
var mask = fGenRegs? 0xf : this.bus.nBusMask;
if (!this.getSwitch(PanelPDP11.SWITCH.STEP)) inc = -inc;
this.regAddr = (this.regAddr & ~mask) | ((this.regAddr + inc) & mask);
this.setLEDArray("A", this.regAddr, 22);
return this.regAddr;
};
@ -906,26 +921,36 @@ PanelPDP11.prototype.stop = function(ms, nCycles)
};
/**
* updateStatus(fForce)
* updateDisplay(nUpdate)
*
* Called by the Computer component at appropriate intervals to update any register displays, LEDs, etc.
* Called by the Computer component at intervals to update registers, LEDs, etc.
*
* @this {PanelPDP11}
* @param {boolean} [fForce] (true will display registers even if the CPU is running and "live" registers are not enabled)
* @param {number} [nUpdate] (< 0 for forced, > 0 for periodic, undefined otherwise)
*/
PanelPDP11.prototype.updateStatus = function(fForce)
PanelPDP11.prototype.updateDisplay = function(nUpdate)
{
if (this.cLiveRegs) {
if (fForce || !this.cpu.isRunning() || this.fDisplayLiveRegs) {
for (var i = 0; i < this.cpu.regsGen.length; i++) {
this.displayValue('R'+i, this.cpu.regsGen[i]);
if (nUpdate < 0 || !this.cpu.isRunning() || this.fDisplayLiveRegs) {
/*
* We arbitrarily separate the display elements into two categories: cheap and expensive.
*
* LEDs are considered cheap, register displays are not. So we'll skip the latter if this
* is a periodic update AND our periodic update counter hasn't reached the periodic update limit.
*/
if (!(nUpdate > 0 && (this.nPeriodicCount += nUpdate) < this.nPeriodicLimit)) {
for (var i = 0; i < this.cpu.regsGen.length; i++) {
this.displayValue('R'+i, this.cpu.regsGen[i]);
}
var regPSW = this.cpu.getPSW();
this.displayValue("PS", regPSW);
this.displayValue("NF", (regPSW & PDP11.PSW.NF)? 1 : 0, 1);
this.displayValue("ZF", (regPSW & PDP11.PSW.ZF)? 1 : 0, 1);
this.displayValue("VF", (regPSW & PDP11.PSW.VF)? 1 : 0, 1);
this.displayValue("CF", (regPSW & PDP11.PSW.CF)? 1 : 0, 1);
this.nPeriodicCount = 0;
}
var regPSW = this.cpu.getPSW();
this.displayValue("PS", regPSW);
this.displayValue("NF", (regPSW & PDP11.PSW.NF)? 1 : 0, 1);
this.displayValue("ZF", (regPSW & PDP11.PSW.ZF)? 1 : 0, 1);
this.displayValue("VF", (regPSW & PDP11.PSW.VF)? 1 : 0, 1);
this.displayValue("CF", (regPSW & PDP11.PSW.CF)? 1 : 0, 1);
this.setLEDArray("D", this.regData, 16);
this.setLEDArray("A", this.regAddr, 22);
/*