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:
parent
93c6ce7236
commit
a7a44a8288
9 changed files with 485 additions and 460 deletions
|
|
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
/*
|
||||
|
|
|
|||
Loading…
Reference in a new issue