Fixed the LED (lamp) test, fixed Front Panel update on a machine RESET, and cleaned up the status update code a bit
This commit is contained in:
parent
afdefb9c4f
commit
57bceb4da9
7 changed files with 554 additions and 512 deletions
|
|
@ -744,6 +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.cpu.autoStart();
|
||||
}
|
||||
|
||||
|
|
@ -955,26 +956,31 @@ ComputerPDP11.prototype.powerOff = function(fSave, fShutdown)
|
|||
* allocated the Bus object ourselves, after all the other components were allocated, it ends
|
||||
* up near the end of Component's list of components. Hence the special case for this.bus below.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @this {ComputerPDP11}
|
||||
*/
|
||||
ComputerPDP11.prototype.reset = function()
|
||||
{
|
||||
if (this.bus && this.bus.reset) {
|
||||
/*
|
||||
* TODO: Why does WebStorm think that this.bus.type is undefined? The base class (Component)
|
||||
* constructor defines it.
|
||||
*/
|
||||
this.printMessage("Resetting " + this.bus.type);
|
||||
this.bus.reset();
|
||||
}
|
||||
if (this.cpu && this.cpu.reset) {
|
||||
this.printMessage("Resetting " + this.cpu.type);
|
||||
this.cpu.reset();
|
||||
}
|
||||
var aComponents = Component.getComponents(this.id);
|
||||
for (var iComponent = 0; iComponent < aComponents.length; iComponent++) {
|
||||
var component = aComponents[iComponent];
|
||||
if (component !== this && component !== this.bus && component.reset) {
|
||||
if (component !== this && component !== this.bus && component !== this.cpu && component.reset) {
|
||||
this.printMessage("Resetting " + component.type);
|
||||
component.reset();
|
||||
}
|
||||
}
|
||||
this.updateStatus(true);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1027,6 +1033,44 @@ ComputerPDP11.prototype.stop = function(ms, nCycles)
|
|||
this.updateStatus(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* updateStatus(fForce)
|
||||
*
|
||||
* TODO: Notify all (other) components with an updateStatus() method that the computer's state has changed.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* TODO: Consider alternate approaches to these largely register-oriented display updates. Ordinarily, we like to
|
||||
* separate logic from presentation, and currently the CPUState contains both, since it's the component that intimately
|
||||
* knows the names, number, sizes, etc, of all the active registers. The Panel component is the logical candidate,
|
||||
* but Panel is an optional component; it's often the case that only machines that include the Debugger also include
|
||||
* 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)
|
||||
{
|
||||
/*
|
||||
* 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
|
||||
* 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.
|
||||
*/
|
||||
if (this.cpu) this.cpu.updateStatus(fForce);
|
||||
if (this.panel) this.panel.updateStatus(fForce);
|
||||
};
|
||||
|
||||
/**
|
||||
* setBinding(sType, sBinding, control, sValue)
|
||||
*
|
||||
|
|
@ -1429,41 +1473,6 @@ ComputerPDP11.prototype.setFocus = function(fScroll)
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* updateStatus(fForce)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* TODO: Consider alternate approaches to these largely register-oriented display updates. Ordinarily, we like to
|
||||
* separate logic from presentation, and currently the CPUState contains both, since it's the component that intimately
|
||||
* knows the names, number, sizes, etc, of all the active registers. The Panel component is the logical candidate,
|
||||
* but Panel is an optional component; generally, only machines that include Debugger also include 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)
|
||||
{
|
||||
/*
|
||||
* 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
|
||||
* 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.
|
||||
*/
|
||||
if (this.cpu) this.cpu.updateStatus(fForce);
|
||||
if (this.panel) this.panel.updateStatus(fForce);
|
||||
};
|
||||
|
||||
/**
|
||||
* ComputerPDP11.init()
|
||||
*
|
||||
|
|
|
|||
|
|
@ -282,11 +282,7 @@ CPUPDP11.prototype.powerUp = function(data, fRepower)
|
|||
* is now responsible for managing a component's fPowered flag, not us.
|
||||
*
|
||||
* this.flags.powered = true;
|
||||
*
|
||||
* And we don't have to worry whether this.cmp is set, because powerUp() handlers are never called
|
||||
* before initBus() handlers.
|
||||
*/
|
||||
this.cmp.updateStatus();
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
@ -508,6 +504,17 @@ CPUPDP11.prototype.setBinding = function(sType, sBinding, control, sValue)
|
|||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* updateComputer(fForce)
|
||||
*
|
||||
* @this {CPUPDP11}
|
||||
* @param {boolean} [fForce]
|
||||
*/
|
||||
CPUPDP11.prototype.updateComputer = function(fForce)
|
||||
{
|
||||
if (this.cmp) this.cmp.updateStatus(fForce);
|
||||
};
|
||||
|
||||
/**
|
||||
* updateStatus(fForce)
|
||||
*
|
||||
|
|
@ -1094,7 +1101,7 @@ CPUPDP11.prototype.runCPU = function()
|
|||
if (this.nCyclesNextYield <= 0) {
|
||||
this.nCyclesNextYield += this.nCyclesPerYield;
|
||||
if (++this.nYieldsSinceStatusUpdate >= CPUPDP11.YIELDS_PER_STATUS) {
|
||||
if (this.cmp) this.cmp.updateStatus();
|
||||
this.updateComputer();
|
||||
this.nYieldsSinceStatusUpdate = 0;
|
||||
}
|
||||
break;
|
||||
|
|
@ -1209,7 +1216,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.
|
||||
*/
|
||||
if (this.cmp) this.cmp.updateStatus();
|
||||
this.updateComputer();
|
||||
};
|
||||
|
||||
if (NODE) module.exports = CPUPDP11;
|
||||
|
|
|
|||
|
|
@ -1413,9 +1413,8 @@ PDP11.opRESET = function(opCode)
|
|||
if (!(this.regPSW & PDP11.PSW.CMODE)) {
|
||||
this.bus.reset();
|
||||
this.resetRegs();
|
||||
// display.data = this.regsGen[0]; // TODO: Review
|
||||
}
|
||||
this.nStepCycles -= 667; // TODO: Review (but it's definitely a big number)
|
||||
this.nStepCycles -= 667; // TODO: Review (but it's definitely a big number)
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -63,12 +63,16 @@ function PanelPDP11(parmsPanel)
|
|||
this.fDisplayLiveRegs = true;
|
||||
|
||||
/*
|
||||
* regSwitches contains the Console (Front Panel) Switch Register, which is also available as a
|
||||
* read-only register at 177570 (but only the low 16 bits).
|
||||
* regSwitches contains the Front Panel (aka Console) 'SWITCH' register, which is also available
|
||||
* as a read-only register at 177570 (but only the low 16 bits).
|
||||
*
|
||||
* regAddr is an internal register containing the contents of the Front Panel's ADDRESS display,
|
||||
* and regData corresponds to the DATA display. They are updated by setAddr() and setData(),
|
||||
* regAddr is an internal register containing the contents of the Front Panel's 'ADDRESS' display,
|
||||
* and regData corresponds to the 'DATA' display. They are updated by setAddr() and setData(),
|
||||
* which in turn take care of calling setLEDArray().
|
||||
*
|
||||
* The state of ALL switches is maintained in this.switches, and likewise all LED states are
|
||||
* maintained in this.leds, but for convenience, we also mirror some of those states in dedicated
|
||||
* variables (eg, regSwitches for the 'SWITCH' register, fLEDTest for the 'TEST' switch, etc).
|
||||
*/
|
||||
this.regSwitches = 0;
|
||||
this.regAddr = this.regData = 0;
|
||||
|
|
@ -82,7 +86,7 @@ function PanelPDP11(parmsPanel)
|
|||
* register (regData) [the equivalent of selecting 'DISPLAY REGISTER'] except when data is being
|
||||
* examined or deposited [the equivalent of selecting 'DATA PATHS'].
|
||||
*/
|
||||
this.fLampTest = false; // lamp (LED) test in progress
|
||||
this.fLEDTest = false; // LED (lamp) test in progress
|
||||
this.fExamine = false; // true if the previously pressed switch was the 'EXAM' switch
|
||||
this.fDeposit = false; // true if the previously pressed switch was the 'DEP' switch
|
||||
this.nAddrSel = PanelPDP11.ADDRSEL.CONS_PHY;
|
||||
|
|
@ -107,11 +111,11 @@ function PanelPDP11(parmsPanel)
|
|||
*
|
||||
* initBus() will call displaySwitches() to ensure that every switch is the position represented below.
|
||||
*
|
||||
* NOTE: Not all switches have the same "process" criteria. For example, 'TEST' will perform a lamp test
|
||||
* when it is momentarily pressed "up", whereas 'LOAD [ADRS]' will load the ADDRESS register from the SWITCH
|
||||
* register when it is momentarily pressed "down".
|
||||
* NOTE: Not all switches have the same "process" criteria. For example, 'TEST' will perform a LED test
|
||||
* when it is momentarily pressed "up", whereas 'LOAD [ADRS]' will load the 'ADDRESS' register from the
|
||||
* 'SWITCH' register when it is momentarily pressed "down".
|
||||
*
|
||||
* This means that processLampTest(value) must act when value == 1 ("up"), whereas processLoadAddr(value)
|
||||
* This means that processLEDTest(value) must act when value == 1 ("up"), whereas processLoadAddr(value)
|
||||
* must act when value == 0 ("down"). You can infer all this from the table below, because the initial value
|
||||
* of any momentary switch is its "inactive" value, so the opposite is its "active" value.
|
||||
*/
|
||||
|
|
@ -123,7 +127,7 @@ function PanelPDP11(parmsPanel)
|
|||
'DEP': [0, true, false, this.processDeposit],
|
||||
'EXAM': [1, true, false, this.processExamine],
|
||||
'LOAD': [1, true, false, this.processLoadAddr],
|
||||
'TEST': [0, true, false, this.processLampTest]
|
||||
'TEST': [0, true, false, this.processLEDTest]
|
||||
};
|
||||
for (var i = 0; i < 22; i++) {
|
||||
this.switches['S'+i] = [0, false, false, this.processSwitchReg, i];
|
||||
|
|
@ -203,10 +207,18 @@ PanelPDP11.prototype.getSwitch = function(name)
|
|||
/**
|
||||
* reset()
|
||||
*
|
||||
* NOTE: Since we've registered our handler with the Bus component, we will be called twice whenever
|
||||
* the entire machine is reset: once when the Computer's reset() handler calls the Bus's reset() handler,
|
||||
* and again when the Computer's reset() handler calls us directly. Multiple resets should be harmless.
|
||||
*
|
||||
* @this {PanelPDP11}
|
||||
*/
|
||||
PanelPDP11.prototype.reset = function()
|
||||
{
|
||||
/*
|
||||
* Simulate a call to our stop() handler, to update the panel's 'ADDRESS' register with the current PC.
|
||||
*/
|
||||
this.stop();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -346,15 +358,9 @@ PanelPDP11.prototype.powerUp = function(data, fRepower)
|
|||
*/
|
||||
PanelPDP11.init();
|
||||
/*
|
||||
* TODO: Until we implement a restore() function, all we can do is reset(); however, that's
|
||||
* currently unnecessary, since we've added reset() to the addResetHandler() list.
|
||||
*
|
||||
* this.reset();
|
||||
*
|
||||
* In the meantime, simulate a call to our stop() handler, to update the panel's ADDRESS register
|
||||
* with the new PC.
|
||||
* TODO: Until we implement a restore() function, all we can do is reset()
|
||||
*/
|
||||
this.stop();
|
||||
this.reset();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
|
@ -491,8 +497,14 @@ PanelPDP11.prototype.pressSwitch = function(sBinding)
|
|||
*/
|
||||
if (sw[3]) sw[3].call(this, sw[0], sw[4]);
|
||||
|
||||
this.fDeposit = (sBinding == PanelPDP11.SWITCH.DEP);
|
||||
this.fExamine = (sBinding == PanelPDP11.SWITCH.EXAM);
|
||||
/*
|
||||
* This helps the next 'DEP' or 'EXAM' press determine if the previous press was the same,
|
||||
* while also ignoring any intervening 'STEP' presses (see processStep() for why we do that).
|
||||
*/
|
||||
if (sBinding != PanelPDP11.SWITCH.STEP) {
|
||||
this.fDeposit = (sBinding == PanelPDP11.SWITCH.DEP);
|
||||
this.fExamine = (sBinding == PanelPDP11.SWITCH.EXAM);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -562,8 +574,11 @@ PanelPDP11.prototype.processStart = function(value, index)
|
|||
* processStep(value, index)
|
||||
*
|
||||
* If value == 1 (our initial value), then the 'STEP' switch is set to "S INST" (step one instruction);
|
||||
* otherwise, it's set to "S BUS CYCLE" (step one bus cycle). Note that we don't actually support the
|
||||
* latter.
|
||||
* otherwise, it's set to "S BUS CYCLE" (step one bus cycle).
|
||||
*
|
||||
* However, since we can't currently support cycle-stepping, I've decided to change the meaning of this
|
||||
* switch: the normal ("up") position means that successive 'EXAM' and 'DEP' operations will first add 2
|
||||
* to the 'ADDRESS' register, while the opposite ("down") position means they will first subtract 2.
|
||||
*
|
||||
* @this {PanelPDP11}
|
||||
* @param {number} value
|
||||
|
|
@ -649,7 +664,7 @@ PanelPDP11.prototype.processContinue = function(value, index)
|
|||
}
|
||||
|
||||
/*
|
||||
* Simulate a call to our stop() handler, to update the panel's ADDRESS register with the new PC.
|
||||
* Simulate a call to our stop() handler, to update the panel's 'ADDRESS' register with the new PC.
|
||||
*/
|
||||
this.stop();
|
||||
|
||||
|
|
@ -658,8 +673,8 @@ PanelPDP11.prototype.processContinue = function(value, index)
|
|||
* updateStatus() handlers will be called, including ours.
|
||||
*
|
||||
* NOTE: If we used the Debugger's stepCPU() function, then that includes a call to updateStatus();
|
||||
* unfortunately, it will have happened BEFORE we called stop() to update the ADDRESS register, so we
|
||||
* still need to call it again.
|
||||
* 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();
|
||||
}
|
||||
|
|
@ -679,9 +694,7 @@ PanelPDP11.prototype.processContinue = function(value, index)
|
|||
PanelPDP11.prototype.processDeposit = function(value, index)
|
||||
{
|
||||
if (value && !this.cpu.isRunning()) {
|
||||
if (this.fDeposit) {
|
||||
this.setAddr(this.regAddr + 2);
|
||||
}
|
||||
if (this.fDeposit) this.advanceAddr();
|
||||
var w = this.setData(this.regSwitches);
|
||||
if (this.nAddrSel == PanelPDP11.ADDRSEL.CONS_PHY) {
|
||||
this.bus.setWordDirect(this.regAddr, w);
|
||||
|
|
@ -704,10 +717,8 @@ PanelPDP11.prototype.processDeposit = function(value, index)
|
|||
PanelPDP11.prototype.processExamine = function(value, index)
|
||||
{
|
||||
if (!value && !this.cpu.isRunning()) {
|
||||
if (this.fExamine) {
|
||||
this.setAddr(this.regAddr + 2);
|
||||
}
|
||||
var w;
|
||||
if (this.fExamine) this.advanceAddr();
|
||||
if (this.nAddrSel == PanelPDP11.ADDRSEL.CONS_PHY) {
|
||||
w = this.bus.getWordDirect(this.regAddr);
|
||||
} else {
|
||||
|
|
@ -735,15 +746,21 @@ PanelPDP11.prototype.processLoadAddr = function(value, index)
|
|||
};
|
||||
|
||||
/**
|
||||
* processLampTest(value, index)
|
||||
* processLEDTest(value, index)
|
||||
*
|
||||
* @this {PanelPDP11}
|
||||
* @param {number} value
|
||||
* @param {number} [index]
|
||||
*/
|
||||
PanelPDP11.prototype.processLampTest = function(value, index)
|
||||
PanelPDP11.prototype.processLEDTest = function(value, index)
|
||||
{
|
||||
this.displayLEDs(value || null);
|
||||
if (value) {
|
||||
this.fLEDTest = true;
|
||||
this.displayLEDs(true);
|
||||
} else {
|
||||
this.fLEDTest = false;
|
||||
this.displayLEDs();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -776,6 +793,20 @@ PanelPDP11.prototype.setAddr = function(value)
|
|||
return this.regAddr;
|
||||
};
|
||||
|
||||
/**
|
||||
* advanceAddr()
|
||||
*
|
||||
* @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;
|
||||
this.setLEDArray("A", this.regAddr, 22);
|
||||
return this.regAddr;
|
||||
};
|
||||
|
||||
/**
|
||||
* setData(value)
|
||||
*
|
||||
|
|
@ -801,7 +832,7 @@ PanelPDP11.prototype.setData = function(value)
|
|||
PanelPDP11.prototype.setLED = function(sBinding, value)
|
||||
{
|
||||
this.leds[sBinding] = value;
|
||||
if (!this.fLampTest) this.displayLED(sBinding, value);
|
||||
if (!this.fLEDTest) this.displayLED(sBinding, value);
|
||||
return value;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue