pcjs/modules/pdp11/lib/cpustate.js

2593 lines
114 KiB
JavaScript

/**
* @fileoverview Implements the PDP11 CPU component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2016-Sep-03
*
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
*
* It has been adapted from the JavaScript PDP 11/70 Emulator v1.3 written by Paul Nankervis
* (paulnank@hotmail.com) as of August 2016 from http://skn.noip.me/pdp11/pdp11.html. This code
* may be used freely provided the original author name is acknowledged in any modified source code.
*
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see COPYRIGHT in /modules/shared/lib/defines.js).
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of PCjs
* for purposes of the GNU General Public License, and the author does not claim any copyright
* as to their contents.
*/
"use strict";
if (NODE) {
var str = require("../../shared/lib/strlib");
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PDP11 = require("./defines");
var BusPDP11 = require("./bus");
var CPUPDP11 = require("./cpu");
var MessagesPDP11 = require("./messages");
var MemoryPDP11 = require("./memory");
}
/**
* CPUStatePDP11(parmsCPU)
*
* The CPUStatePDP11 class uses the following (parmsCPU) properties:
*
* model: a number (eg, 1170) that should match one of the PDP11.MODEL_* values
* resetAddr: reset address (default is 0)
*
* This extends the CPU class and passes any remaining parmsCPU properties to the CPU class
* constructor, along with a default speed (cycles per second) based on the specified (or default)
* CPU model number.
*
* @constructor
* @extends CPUPDP11
* @param {Object} parmsCPU
*/
function CPUStatePDP11(parmsCPU)
{
this.model = +parmsCPU['model'] || PDP11.MODEL_1170;
this.resetAddr = parmsCPU['resetAddr'] || 0;
var nCyclesDefault = 0;
switch(this.model) {
case PDP11.MODEL_1170:
default:
nCyclesDefault = 1000000;
break;
}
CPUPDP11.call(this, parmsCPU, nCyclesDefault);
/*
* If there are no live registers to display, then updateStatus() can skip a bit....
*/
this.cLiveRegs = 0;
/*
* Initialize processor operation to match the requested model
*/
this.initProcessor();
}
Component.subclass(CPUStatePDP11, CPUPDP11);
/**
* initProcessor()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.initProcessor = function()
{
this.decode = PDP11.op1170.bind(this);
this.initRegs();
this.flags.complete = this.flags.debugCheck = false;
};
/**
* reset()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.reset = function()
{
if (this.flags.running) this.stopCPU();
this.resetRegs();
this.resetCycles();
this.clearError(); // clear any fatal error/exception that setError() may have flagged
this.parent.reset.call(this);
};
/**
* initRegs()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.initRegs = function()
{
/*
* TODO: Verify the initial state of all PDP-11 flags (are they documented?)
*/
this.flagC = 0x10000; // PSW C bit
this.flagV = 0x8000; // PSW V bit
this.flagZ = 0xffff; // ~ PSW Z bit (TODO: Why is Z clear instead of set like all other flags?)
this.flagN = 0x8000; // PSW N bit
this.regPSW = 0xf; // PSW other bits (TODO: What's the point of setting the flag bits here, too?)
this.regOp = -1; // current opcode
this.regsGen = [ // General R0 - R7
0, 0, 0, 0, 0, 0, 0, this.resetAddr
];
this.regsAlt = [ // Alternate R0 - R5
0, 0, 0, 0, 0, 0
];
this.regsAltStack = [ // Alternate R6 stack pointers (kernel, super, illegal, user)
0, 0, 0, 0
];
this.memory = []; // Main memory (words)
this.interruptQueue = []; // List of interrupts pending
this.pir = 0;
this.runState = 0; // 0=run, 1=step, 2=wait, 3=run
this.loopRate = 9999; // instructions we can execute in 12ms
this.priorityReview = 2; // flag to mark if we need to check priority change
this.stackLimit = 0xff;
this.opFlags = 0;
this.trapPSW = -1;
this.CPU_Error = 0;
this.cpuType = 70;
this.MMR0 = 0;
this.MMR1 = 0;
this.MMR2 = 0;
this.MMR3 = 0;
this.mmuMode = 0; // current memory management mode (0=kernel,1=super,2=undefined,3=user)
this.mmuEnable = 0; // MMU enabled for PDP11.ACCESS.READ or PDP11.ACCESS.WRITE
this.mmuLastMode = 0;
this.mmuLastPage = 0;
this.mmuLastVirtual = 0;
this.mmuMask = [ // mask to control I&D access for each mode
0x7, 0x7, 0x7, 0x7
];
this.mmuMap = [ // memory management register by mode - 16 PDR (8 I then 8 D descriptors) followed by 16 PAR (I/D addresses)
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // kernel
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // super
[0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // illegal mode 2 requires illegal PDRs
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // user
];
this.unibusMap = [ // 32 unibus map registers
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
];
this.controlReg = [ // various control registers we don't really care about
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
];
};
/**
* resetRegs()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.resetRegs = function()
{
this.stackLimit = 0xff;
this.CPU_Error = 0;
this.interruptQueue = [];
this.pir = 0;
this.MMR0 = this.MMR1 = this.MMR2 = this.MMR3 = this.mmuFrozen = this.mmuEnable = 0;
this.mmuMask[0] = this.mmuMask[1] = this.mmuMask[3] = 0x7;
this.mmuLastMode = 0;
};
/**
* getChecksum()
*
* @this {CPUStatePDP11}
* @return {number} a 32-bit summation of key elements of the current CPU state (used by the CPU checksum code)
*/
CPUStatePDP11.prototype.getChecksum = function()
{
return 0; // TODO: Implement
};
/**
* save()
*
* This implements save support for the CPUStatePDP11 component.
*
* @this {CPUStatePDP11}
* @return {Object|null}
*/
CPUStatePDP11.prototype.save = function()
{
var state = new State(this);
state.set(0, []);
state.set(1, [this.intFlags, this.nTotalCycles, this.getSpeed()]);
state.set(2, this.bus.saveMemory());
return state.data();
};
/**
* restore(data)
*
* This implements restore support for the CPUStatePDP11 component.
*
* @this {CPUStatePDP11}
* @param {Object} data
* @return {boolean} true if restore successful, false if not
*/
CPUStatePDP11.prototype.restore = function(data)
{
var a = data[1];
this.intFlags = a[0];
this.nTotalCycles = a[1];
this.setSpeed(a[3]);
return this.bus.restoreMemory(data[2]);
};
/**
* setBinding(sHTMLType, sBinding, control, sValue)
*
* @this {CPUStatePDP11}
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "AX")
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
* @param {string} [sValue] optional data value
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
CPUStatePDP11.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
{
var fBound = false;
switch (sBinding) {
case "R0":
case "R1":
case "R2":
case "R3":
case "R4":
case "R5":
case "R6":
case "R7":
case "NF":
case "ZF":
case "VF":
case "CF":
case "PS":
this.bindings[sBinding] = control;
this.cLiveRegs++;
fBound = true;
break;
default:
fBound = this.parent.setBinding.call(this, sHTMLType, sBinding, control);
break;
}
return fBound;
};
/**
* updateStatus(fForce)
*
* This provides periodic Control Panel updates (a few times per second; see YIELDS_PER_STATUS).
* this is where we take care of any DOM updates (eg, register values) while the CPU is running.
*
* @this {CPUStatePDP11}
* @param {boolean} [fForce] (true will display registers even if the CPU is running and "live" registers are not enabled)
*/
CPUStatePDP11.prototype.updateStatus = function(fForce)
{
if (this.cLiveRegs) {
if (fForce || !this.flags.running || this.flags.displayLiveRegs) {
for (var i = 0; i < this.regsGen.length; i++) {
this.displayValue('R'+i, this.regsGen[i]);
}
var regPSW = this.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);
}
}
var controlSpeed = this.bindings["speed"];
if (controlSpeed) controlSpeed.textContent = this.getSpeedCurrent();
};
/**
* clearCF()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.clearCF = function()
{
this.flagC = 0;
};
/**
* getCF()
*
* @this {CPUStatePDP11}
* @return {number} 0 or PDP11.PSW.CF
*/
CPUStatePDP11.prototype.getCF = function()
{
return (this.flagC & 0x10000)? PDP11.PSW.CF: 0;
};
/**
* setCF()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.setCF = function()
{
this.flagC = 0x10000;
};
/**
* clearVF()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.clearVF = function()
{
this.flagV = 0;
};
/**
* getVF()
*
* @this {CPUStatePDP11}
* @return {number} 0 or PDP11.PSW.VF
*/
CPUStatePDP11.prototype.getVF = function()
{
return (this.flagV & 0x8000)? PDP11.PSW.VF: 0;
};
/**
* setVF()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.setVF = function()
{
this.flagV = 0x8000;
};
/**
* clearZF()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.clearZF = function()
{
this.flagZ = 1;
};
/**
* getZF()
*
* @this {CPUStatePDP11}
* @return {number} 0 or PDP11.PSW.ZF
*/
CPUStatePDP11.prototype.getZF = function()
{
return (this.flagZ & 0xffff)? 0 : PDP11.PSW.ZF;
};
/**
* setZF()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.setZF = function()
{
this.flagZ = 0;
};
/**
* clearNF()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.clearNF = function()
{
this.flagN = 0;
};
/**
* getNF()
*
* @this {CPUStatePDP11}
* @return {number} 0 or PDP11.PSW.NF
*/
CPUStatePDP11.prototype.getNF = function()
{
return (this.flagN & 0x8000)? PDP11.PSW.NF : 0;
};
/**
* setNF()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.setNF = function()
{
this.flagN = 0x8000;
};
/**
* getPC()
*
* @this {CPUStatePDP11}
* @return {number}
*/
CPUStatePDP11.prototype.getPC = function()
{
return this.regsGen[7];
};
/**
* setPC()
*
* @this {CPUStatePDP11}
* @param {number} addr
*/
CPUStatePDP11.prototype.setPC = function(addr)
{
this.regsGen[7] = addr;
};
/**
* getSP()
*
* @this {CPUStatePDP11}
* @return {number}
*/
CPUStatePDP11.prototype.getSP = function()
{
return this.regsGen[6];
};
/**
* checkINTR()
*
* @this {CPUStatePDP11}
* @return {boolean} true if execution may proceed, false if not
*/
CPUStatePDP11.prototype.checkINTR = function()
{
if (this.intFlags & PDP11.INTFLAG.HALT) {
/*
* As discussed in opHLT(), the CPU is never REALLY halted by a HLT instruction; instead, opHLT()
* calls requestHALT(), which sets INTFLAG.HALT and signals to stepCPU() that it's free to end the
* current burst AND that it should not execute any more instructions until checkINTR() indicates
* that a hardware interrupt has been requested.
*/
this.endBurst();
return false;
}
return true;
};
/**
* clearINTR(nLevel)
*
* Clear the corresponding interrupt level.
*
* nLevel can either be a valid interrupt level (0-7), or -1 to clear all pending interrupts
* (eg, in the event of a system-wide reset).
*
* @this {CPUStatePDP11}
* @param {number} nLevel (0-7, or -1 for all)
*/
CPUStatePDP11.prototype.clearINTR = function(nLevel)
{
var bitsClear = nLevel < 0? 0xff : (1 << nLevel);
this.intFlags &= ~bitsClear;
};
/**
* requestHALT()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.requestHALT = function()
{
this.intFlags |= PDP11.INTFLAG.HALT;
this.endBurst();
};
/**
* interrupt(delay, priority, vector, callback)
*
* Interrupts are stored in a queue in delay order with the delay expressed as
* a difference. For example if the delays were 0, 1, 0 then the first entry
* is active and both the second and third are waiting for one more instruction
* execution to become active.
*
* If the current state is WAIT (runState === 2) then skip any delay and
* go into RUN state.
*
* @this {CPUStatePDP11}
* @param {number} delay
* @param {number} priority
* @param {number} vector
* @param {function()} [callback]
*/
CPUStatePDP11.prototype.interrupt = function(delay, priority, vector, callback)
{
var i = this.interruptQueue.length;
while (i-- > 0) {
if (this.interruptQueue[i].vector === vector) {
if (i > 0) {
this.interruptQueue[i - 1].delay += this.interruptQueue[i].delay;
}
this.interruptQueue.splice(i, 1);
break;
}
}
if (delay >= 0) {
if (this.runState === 2) { // if currently wait
delay = 0;
this.runState = 0;
setTimeout(this.onRunTimeout, 0); // TODO: Review
}
i = this.interruptQueue.length; // queue in delay 'difference' order
while (i-- > 0) {
if (this.interruptQueue[i].delay > delay) {
this.interruptQueue[i].delay -= delay;
break;
}
delay -= this.interruptQueue[i].delay;
}
this.interruptQueue.splice(i + 1, 0, {
"delay": delay,
"priority": priority & 0xe0,
"vector": vector,
"callback": callback
});
}
this.priorityReview = 2;
};
/**
* getPSW()
*
* @this {CPUStatePDP11}
* @return {number}
*/
CPUStatePDP11.prototype.getPSW = function()
{
/*
* I'm not sure why this function can't simply be written as:
*
* return (this.PSW & ~PDP11.PSW.FLAGS) | (this.getNF() | this.getZF() | this.getVF() | this.getCF());
*
* but for now, I'm keeping the same masking logic as pdp11.js.
*/
var mask = PDP11.PSW.CMODE | PDP11.PSW.PMODE | PDP11.PSW.REGSET | PDP11.PSW.PRI | PDP11.PSW.TF;
return this.PSW = (this.PSW & mask) | this.getNF() | this.getZF() | this.getVF() | this.getCF();
};
/**
* setPSW(newPSW)
*
* This updates the CPU Processor Status Word. The PSW should generally be written through
* this routine so that changes can be tracked properly, for example the correct register set,
* the current memory management mode, etc. An exception is SPL which writes the priority directly.
* Note that that N, Z, V, and C flags are actually stored separately for performance reasons.
*
* PSW 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
* CMODE PMODE RS -------- PRIORITY T N Z V C
*
* @this {CPUStatePDP11}
* @param {number} newPSW
*/
CPUStatePDP11.prototype.setPSW = function(newPSW)
{
this.flagN = newPSW << 12;
this.flagZ = (~newPSW) & 4;
this.flagV = newPSW << 14;
this.flagC = newPSW << 16;
if ((newPSW ^ this.PSW) & PDP11.PSW.REGSET) {
/*
* Swap register sets
*/
for (var i = this.regsAlt.length; --i >= 0;) {
var tmp = this.regsGen[i];
this.regsGen[i] = this.regsAlt[i];
this.regsAlt[i] = tmp;
}
}
this.mmuMode = (newPSW >> PDP11.PSW.SHIFT.CMODE) & PDP11.MODE.MASK;
var oldMode = (this.PSW >> PDP11.PSW.SHIFT.CMODE) & PDP11.MODE.MASK;
if (this.mmuMode != oldMode) {
/*
* Swap stack pointers
*/
this.regsAltStack[oldMode] = this.regsGen[6];
this.regsGen[6] = this.regsAltStack[this.mmuMode];
}
/*
* Trigger check of priority levels
*/
this.priorityReview = 2;
this.PSW = newPSW;
};
/**
* updateNZCFlags(result)
*
* @this {CPUStatePDP11}
* @param {number} result
*/
CPUStatePDP11.prototype.updateNZCFlags = function(result)
{
if (!(this.opFlags & PDP11.OPFLAG.SKIP_FLAGS)) {
this.flagN = this.flagZ = this.flagC = result;
this.flagV = 0;
}
};
/**
* updateNZFlags(result)
*
* @this {CPUStatePDP11}
* @param {number} result
*/
CPUStatePDP11.prototype.updateNZFlags = function(result)
{
if (!(this.opFlags & PDP11.OPFLAG.SKIP_FLAGS)) {
this.flagN = this.flagZ = result;
this.flagV = 0;
}
};
/**
* updateAddFlags(result, src, dst)
*
* @this {CPUStatePDP11}
* @param {number} result (dst + src)
* @param {number} src
* @param {number} dst
*/
CPUStatePDP11.prototype.updateAddFlags = function(result, src, dst)
{
if (!(this.opFlags & PDP11.OPFLAG.SKIP_FLAGS)) {
this.flagN = this.flagZ = this.flagC = result;
this.flagV = (src ^ result) & (dst ^ result);
}
};
/**
* updateSubFlags(result, src, dst)
*
* NOTE: CMP operations calculate (src - dst) rather than (dst - src), so when they call updateSubFlags(),
* they must reverse the order of the src and dst parameters.
*
* @this {CPUStatePDP11}
* @param {number} result (dst - src)
* @param {number} src
* @param {number} dst
*/
CPUStatePDP11.prototype.updateSubFlags = function(result, src, dst)
{
if (!(this.opFlags & PDP11.OPFLAG.SKIP_FLAGS)) {
this.flagN = this.flagZ = this.flagC = result;
this.flagV = (src ^ dst) & (dst ^ result);
}
};
/**
* panic(reason)
*
* TODO: Something.
*
* @this {CPUStatePDP11}
* @param {number} reason
*/
CPUStatePDP11.prototype.panic = function(reason)
{
console.log("panic(" + reason + ")");
};
/**
* trap(vector, reason)
*
* trap() handles all the trap/abort functions. It reads the trap vector from kernel
* D space, changes mode to reflect the new PSW and PC, and then pushes the old PSW and
* PC onto the new mode stack.
*
* @this {CPUStatePDP11}
* @param {number} vector
* @param {number} reason
*/
CPUStatePDP11.prototype.trap = function(vector, reason)
{
var newPC, newPSW, doubleTrap = 0;
if (this.trapPSW < 0) {
this.trapPSW = this.getPSW();
} else {
if (!this.mmuMode) {
vector = 4;
doubleTrap = 1;
}
}
//LOG_INSTRUCTION(vector, 11, "-trap-");
if (!(this.MMR0 & 0xe000)) {
this.MMR1 = 0xf6f6;
this.MMR2 = vector;
}
this.mmuMode = 0; // read from kernel D space
if ((newPC = this.readWordByVirtual(vector | 0x10000)) >= 0) {
if ((newPSW = this.readWordByVirtual(((vector + 2) & 0xffff) | 0x10000)) >= 0) {
this.setPSW((newPSW & 0xcfff) | ((this.trapPSW >> 2) & 0x3000)); // set new this.PSW with previous mode
if (doubleTrap) {
this.CPU_Error |= 4;
this.regsGen[6] = 4;
}
if (this.pushWord(this.trapPSW) >= 0 && this.pushWord(this.regsGen[7]) >= 0) {
this.regsGen[7] = newPC;
}
}
}
this.opFlags &= ~PDP11.OPFLAG.TRAP_MASK; // lose interest in traps after an abort
this.trapPSW = -1; // reset flag that we have a trap within a trap
throw vector | (reason << 8);
};
/**
* mapUnibus(unibusAddress)
*
* @this {CPUStatePDP11}
* @param {number} unibusAddress
* @return {number}
*/
CPUStatePDP11.prototype.mapUnibus = function(unibusAddress)
{
var idx = (unibusAddress >> 13) & 0x1f;
if (idx < 31) {
if (this.MMR3 & 0x20) {
unibusAddress = (this.unibusMap[idx] + (unibusAddress & 0x1ffe)) & 0x3ffffe;
if (unibusAddress >= BusPDP11.IOPAGE_UNIBUS && unibusAddress < BusPDP11.IOPAGE_22BIT) this.panic(898);
}
} else {
unibusAddress |= BusPDP11.IOPAGE_22BIT;
}
return unibusAddress;
};
/**
* mapVirtualToPhysical(virtualAddress, accessFlags)
*
* mapVirtualToPhysical() does memory management. It converts a 17 bit I/D
* virtual address to a 22 bit physical address (Note: the eight pseudo addresses
* for handling registers are NOT known at this level - those exist only for higher
* level functions). A real PDP 11/70 memory management unit can be enabled separately
* for read and write for diagnostic purposes. This is handled here by having by having
* an enable mask (mmuEnable) which is tested against the operation access mask
* (accessFlags). If there is no match then the virtual address is simply mapped
* as a 16 bit physical address with the upper page going to the IO address space.
* Significant access mask values used are PDP11.ACCESS.READ and PDP11.ACCESS.WRITE
*
* As an aside it turns out that it is the memory management unit that does odd address
* and non-existent memory trapping: who knew? :-) I thought these would have been
* handled at access time.
*
* When doing mapping, mmuMode is used to decide what address space is to be
* used. 0 = kernel, 1 = supervisor, 2 = illegal, 3 = user. Normally, mmuMode is
* set by the setPSW() function but there are exceptions for instructions which
* move data between address spaces (MFPD, MFPI, MTPD, and MTPI) and trap(). These will
* modify mmuMode outside of setPSW() and then restore it again if all worked. If
* however something happens to cause a trap then no restore is done as setPSW()
* will have been invoked as part of the trap, which will resynchronize mmuMode
*
* mmuMask[mmuMode] is used to control whether I/D space is active or not for
* each address space. If separate I/D mapping is enabled then this mask will contain
* 0xF to retain the I/D bit - or if not it will contain 0x7 to mask out the I/D bit.
*
* mmuMap[mmuMode] contains the memory map. Each entry has 32 values; 16 PDRs
* (8 I space pages and 8 D space pages) followed by 16 PARs
*
* A PDP 11/70 is different to other PDP 11's in that the highest 18 bit space (017000000
* & above) maps directly to UNIBUS space - including low memory. This doesn't appear to
* be particularly useful as it restricts maximum system memory - although it does appear
* to allow software testing of the unibus map. This feature also appears to confuse some
* OSes which test consecutive memory locations to find maximum memory - and on a full
* memory system find themselves accessing low memory again at high addresses.
*
* 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 MMR0
* nonr leng read trap unus unus ena mnt cmp -mode- i/d --page-- enable
*
* @this {CPUStatePDP11}
* @param {number} virtualAddress
* @param {number} accessFlags
* @return {number}
*/
CPUStatePDP11.prototype.mapVirtualToPhysical = function(virtualAddress, accessFlags)
{
var page, pdr, physicalAddress, errorMask = 0;
//if (virtualAddress & ~0x1ffff) this.panic(89); // check VA range
//if (!accessFlags) this.panic(93); // must have PDP11.ACCESS.READ or PDP11.ACCESS.WRITE
if (!(accessFlags & this.mmuEnable)) {
physicalAddress = virtualAddress & 0xffff; // virtual address without MMU is 16 bit (no I&D)
this.mmuLastVirtual = physicalAddress;
if (physicalAddress >= BusPDP11.IOPAGE_VIRT) {
physicalAddress |= BusPDP11.IOPAGE_22BIT;
} else { // no max_memory check in 16 bit mode
if ((physicalAddress & 1) && !(accessFlags & PDP11.ACCESS.BYTE)) {
this.CPU_Error |= 0x40;
this.trap(PDP11.TRAP.BUS_ERROR, 22);
}
}
return physicalAddress;
} else {
this.mmuLastVirtual = virtualAddress;
page = (virtualAddress >> 13) & this.mmuMask[this.mmuMode];
pdr = this.mmuMap[this.mmuMode][page];
physicalAddress = ((this.mmuMap[this.mmuMode][page + 16] << 6) + (virtualAddress & 0x1fff)) & 0x3fffff;
if (this.MMR3 & 0x10) { // if 22 bit MM mode
if (physicalAddress >= BusPDP11.IOPAGE_UNIBUS && physicalAddress < BusPDP11.IOPAGE_22BIT) {
physicalAddress = this.mapUnibus(physicalAddress & 0x3ffff); // 18bit unibus space
}
} else {
physicalAddress &= 0x3ffff; // truncate if only 18 bit mapping
if (physicalAddress >= BusPDP11.IOPAGE_18BIT) physicalAddress |= BusPDP11.IOPAGE_22BIT;
}
if (physicalAddress < BusPDP11.IOPAGE_UNIBUS) {
if (physicalAddress >= BusPDP11.MAX_MEMORY) {
this.CPU_Error |= 0x20;
this.trap(PDP11.TRAP.BUS_ERROR, 24); // KB11-EM does this after ABORT handling - KB11-CM before
}
if ((physicalAddress & 1) && !(accessFlags & PDP11.ACCESS.BYTE)) {
this.CPU_Error |= 0x40;
this.trap(PDP11.TRAP.BUS_ERROR, 26);
}
}
switch (pdr & 0x7) {
case 1: // read-only with trap
errorMask = 0x1000; // MMU trap
/* falls through */
case 2: // read-only
pdr |= 0x80; // Set A bit
if (accessFlags & PDP11.ACCESS.WRITE) {
errorMask = 0x2000; // read-only abort
}
break;
case 4: // read-write with read-write trap
errorMask = 0x1000; // MMU trap
/* falls through */
case 5: // read-write with write trap
if (accessFlags & PDP11.ACCESS.WRITE) {
errorMask = 0x1000; // MMU trap
}
/* falls through */
case 6: // read-write: set A & W bits
pdr |= ((accessFlags & PDP11.ACCESS.WRITE) ? 0xc0 : 0x80);
break;
default:
errorMask = 0x8000; // non-resident abort
break;
}
if ((pdr & 0x7f08) !== 0x7f00) { // skip checking most common case (hopefully)
if (pdr & 0x8) { // expand downwards
if (pdr & 0x7f00) {
if ((virtualAddress & 0x1fc0) < ((pdr >> 2) & 0x1fc0)) {
errorMask |= 0x4000; // page length error abort
}
}
} else { // expand upwards
if ((virtualAddress & 0x1fc0) > ((pdr >> 2) & 0x1fc0)) {
errorMask |= 0x4000; // page length error abort
}
}
}
// aborts and traps: log FIRST trap and MOST RECENT abort
this.mmuMap[this.mmuMode][page] = pdr;
if ((physicalAddress !== 0x3fff7a) || this.mmuMode) { // MMR0 is 017777572
this.mmuLastMode = this.mmuMode;
this.mmuLastPage = page;
}
if (errorMask) {
if (errorMask & 0xe000) {
if (this.trapPSW >= 0) errorMask |= 0x80; // Instruction complete
if (!(this.MMR0 & 0xe000)) {
this.MMR0 |= errorMask | (this.mmuLastMode << 5) | (this.mmuLastPage << 1);
}
this.trap(PDP11.TRAP.MMU_FAULT, 28);
}
if (!(this.MMR0 & 0xf000)) {
//if (physicalAddress < 017772200 || physicalAddress > 017777677) {
if (physicalAddress < 0x3ff480 || physicalAddress > 0x3fffbf) {
this.MMR0 |= 0x1000; // MMU trap flag
if (this.MMR0 & 0x0200) {
this.opFlags |= PDP11.OPFLAG.TRAP_MMU;
}
}
}
}
return physicalAddress;
}
};
/**
* readWordByAddr(physicalAddress)
*
* @this {CPUStatePDP11}
* @param {number} physicalAddress
* @return {number}
*/
CPUStatePDP11.prototype.readWordByAddr = function(physicalAddress)
{
if (physicalAddress >= BusPDP11.MAX_ADDRESS) {
return this.regsGen[physicalAddress - BusPDP11.MAX_ADDRESS];
} else {
// if (physicalAddress >= BusPDP11.IOPAGE_UNIBUS) {
// return this.bus.access_iopage(physicalAddress, -1, 0);
// } else {
if (physicalAddress >= 0) {
return this.bus.getShort(physicalAddress);
}
// }
}
return physicalAddress;
};
/**
* writeWordByAddr(physicalAddress, data)
*
* @this {CPUStatePDP11}
* @param {number} physicalAddress
* @param {number} data
* @return {number}
*/
CPUStatePDP11.prototype.writeWordByAddr = function(physicalAddress, data)
{
data &= 0xffff;
if (physicalAddress >= BusPDP11.MAX_ADDRESS) {
return (this.regsGen[physicalAddress - BusPDP11.MAX_ADDRESS] = data);
} else {
// if (physicalAddress >= BusPDP11.IOPAGE_UNIBUS) {
// return this.bus.access_iopage(physicalAddress, data, 0);
// } else {
if (physicalAddress >= 0) {
this.bus.setShort(physicalAddress, data);
return data;
}
// }
}
return physicalAddress;
};
/**
* readByteByAddr(physicalAddress)
*
* @this {CPUStatePDP11}
* @param {number} physicalAddress
* @return {number}
*/
CPUStatePDP11.prototype.readByteByAddr = function(physicalAddress)
{
var result;
if (physicalAddress >= BusPDP11.MAX_ADDRESS) {
return (this.regsGen[physicalAddress - BusPDP11.MAX_ADDRESS] & 0xff);
} else {
// if (physicalAddress >= BusPDP11.IOPAGE_UNIBUS) {
// return this.bus.access_iopage(physicalAddress, -1, 1);
// } else {
if (physicalAddress >= 0) {
return this.bus.getByte(physicalAddress);
}
// }
}
return physicalAddress;
};
/**
* writeByteByAddr(physicalAddress, data)
*
* @this {CPUStatePDP11}
* @param {number} physicalAddress
* @param {number} data
* @return {number}
*/
CPUStatePDP11.prototype.writeByteByAddr = function(physicalAddress, data)
{
data &= 0xff;
if (physicalAddress >= BusPDP11.MAX_ADDRESS) {
return (this.regsGen[physicalAddress - BusPDP11.MAX_ADDRESS] = (this.regsGen[physicalAddress - BusPDP11.MAX_ADDRESS] & 0xff00) | data);
} else {
// if (physicalAddress >= BusPDP11.IOPAGE_UNIBUS) {
// return this.bus.access_iopage(physicalAddress, data, 1);
// } else {
if (physicalAddress >= 0) {
this.bus.setByte(physicalAddress, data);
return data;
}
// }
}
return physicalAddress;
};
/**
* readWordByVirtual(virtualAddress)
*
* @this {CPUStatePDP11}
* @param {number} virtualAddress (input address is 17 bit (I&D))
*/
CPUStatePDP11.prototype.readWordByVirtual = function(virtualAddress)
{
return this.readWordByAddr(this.mapVirtualToPhysical(virtualAddress, PDP11.ACCESS.READ));
};
/**
* popWord()
*
* @this {CPUStatePDP11}
* @return {number}
*/
CPUStatePDP11.prototype.popWord = function()
{
var result = this.readWordByVirtual(this.regsGen[6] | 0x10000);
if (result >= 0) {
this.regsGen[6] = (this.regsGen[6] + 2) & 0xffff;
}
return result;
};
/**
* pushWord(data)
*
* @this {CPUStatePDP11}
* @param {number} data
* @return {number}
*/
CPUStatePDP11.prototype.pushWord = function(data)
{
var physicalAddress, virtualAddress;
this.regsGen[6] = virtualAddress = (this.regsGen[6] - 2) & 0xffff; // BSD needs SP updated before any fault :-(
if (!(this.MMR0 & 0xe000)) {
this.MMR1 = (this.MMR1 << 8) | 0xf6;
}
if ((!this.mmuMode) && virtualAddress <= this.stackLimit && virtualAddress > 4) {
if (virtualAddress <= this.stackLimit - 32) {
this.CPU_Error |= 4; // Red stack
this.regsGen[6] = 4;
this.trap(PDP11.TRAP.BUS_ERROR, 32);
} else {
this.CPU_Error |= 8; // Yellow
this.opFlags |= 4;
}
}
if ((physicalAddress = this.mapVirtualToPhysical(virtualAddress | 0x10000, PDP11.ACCESS.WRITE)) >= 0) {
return this.writeWordByAddr(physicalAddress, data);
}
return physicalAddress;
};
/**
* getVirtualByMode(addressMode, accessFlags)
*
* getVirtualByMode() maps a six bit operand to a 17 bit I/D virtual address space.
*
* Instruction operands are six bits in length - three bits for the mode and three
* for the register. The 17th I/D bit in the resulting virtual address represents
* whether the reference is to Instruction space or Data space - which depends on
* combination of the mode and whether the register is the Program Counter (register 7).
*
* The eight modes are:-
* 0 R no valid virtual address
* 1 (R) operand from I/D depending if R = 7
* 2 (R)+ operand from I/D depending if R = 7
* 3 @(R)+ address from I/D depending if R = 7 and operand from D space
* 4 -(R) operand from I/D depending if R = 7
* 5 @-(R) address from I/D depending if R = 7 and operand from D space
* 6 x(R) x from I space but operand from D space
* 7 @x(R) x from I space but address and operand from D space
*
* Also need to keep MMR1 updated as this stores which registers have been
* incremented and decremented so that the OS can reset and restart an instruction
* if a page fault occurs.
*
* @this {CPUStatePDP11}
* @param {number} addressMode
* @param {number} accessFlags
* @return {number}
*/
CPUStatePDP11.prototype.getVirtualByMode = function(addressMode, accessFlags)
{
var virtualAddress, stepSize, reg = addressMode & 7;
switch ((addressMode >> 3) & 7) {
case 0: // Mode 0: Registers don't have a virtual address so trap!
this.trap(PDP11.TRAP.BUS_ERROR, 34); // trap for invalid virtual address
break;
case 1: // Mode 1: (R)
if (reg === 6 && (!this.mmuMode) && (accessFlags & PDP11.ACCESS.WRITE) &&
(this.regsGen[6] <= this.stackLimit || this.regsGen[6] >= 0xfffe)) {
if (this.regsGen[6] <= this.stackLimit - 32 || this.regsGen[6] >= 0xfffe) {
this.CPU_Error |= 4; // Red stack
this.regsGen[6] = 4;
this.trap(PDP11.TRAP.BUS_ERROR, 36);
} else {
this.CPU_Error |= 8; // Yellow
this.opFlags |= PDP11.OPFLAG.TRAP_SP;
}
}
return (reg === 7 ? this.regsGen[reg] : (this.regsGen[reg] | 0x10000));
case 2: // Mode 2: (R)+
stepSize = 2;
virtualAddress = this.regsGen[reg];
if (reg !== 7) {
virtualAddress |= 0x10000;
if (reg < 6 && (accessFlags & PDP11.ACCESS.BYTE)) {
stepSize = 1;
}
}
break;
case 3: // Mode 3: @(R)+
stepSize = 2;
virtualAddress = this.regsGen[reg];
if (reg !== 7) virtualAddress |= 0x10000;
if ((virtualAddress = this.readWordByVirtual(virtualAddress)) < 0) {
return virtualAddress;
}
// if (reg === 7) LOG_ADDRESS(virtualAddress); // @#n not operational
virtualAddress |= 0x10000;
break;
case 4: // Mode 4: -(R)
stepSize = -2;
if (reg < 6 && (accessFlags & PDP11.ACCESS.BYTE)) stepSize = -1;
virtualAddress = (this.regsGen[reg] + stepSize) & 0xffff;
if (reg !== 7) {
virtualAddress |= 0x10000;
}
break;
case 5: // Mode 5: @-(R)
stepSize = -2;
virtualAddress = (this.regsGen[reg] - 2) & 0xffff;
if (reg !== 7) virtualAddress |= 0x10000;
if ((virtualAddress = this.readWordByVirtual(virtualAddress)) < 0) {
return virtualAddress;
}
virtualAddress |= 0x10000;
break;
case 6: // Mode 6: d(R)
if ((virtualAddress = this.readWordByVirtual(this.regsGen[7])) < 0) {
return virtualAddress;
}
this.regsGen[7] = (this.regsGen[7] + 2) & 0xffff;
if (reg < 7) {
//LOG_ADDRESS(virtualAddress);
virtualAddress = (virtualAddress + this.regsGen[reg]) & 0xffff;
} else {
virtualAddress = (virtualAddress + this.regsGen[reg]) & 0xffff;
//LOG_ADDRESS(virtualAddress);
}
return virtualAddress | 0x10000;
case 7: // Mode 7: @d(R)
if ((virtualAddress = this.readWordByVirtual(this.regsGen[7])) < 0) {
return virtualAddress;
}
this.regsGen[7] = (this.regsGen[7] + 2) & 0xffff;
if (reg < 7) {
//LOG_ADDRESS(virtualAddress);
virtualAddress = (virtualAddress + this.regsGen[reg]) & 0xffff;
} else {
virtualAddress = (virtualAddress + this.regsGen[reg]) & 0xffff;
//LOG_ADDRESS(virtualAddress);
}
if ((virtualAddress = this.readWordByVirtual(virtualAddress | 0x10000)) < 0) {
return virtualAddress;
}
return virtualAddress | 0x10000; // @x
}
this.regsGen[reg] = (this.regsGen[reg] + stepSize) & 0xffff;
if (!(this.MMR0 & 0xe000)) {
this.MMR1 = (this.MMR1 << 8) | ((stepSize << 3) & 0xf8) | reg;
}
if (reg === 6 && (!this.mmuMode) && (accessFlags & PDP11.ACCESS.WRITE) && stepSize <= 0 &&
(this.regsGen[6] <= this.stackLimit || this.regsGen[6] >= 0xfffe)) {
if (this.regsGen[6] <= this.stackLimit - 32) {
this.CPU_Error |= 4; // Red stack
this.regsGen[6] = 4;
this.trap(PDP11.TRAP.BUS_ERROR, 38);
} else {
this.CPU_Error |= 8; // Yellow
this.opFlags |= PDP11.OPFLAG.TRAP_SP;
}
}
return virtualAddress;
};
/**
* getAddrByMode(addressMode, accessFlags)
*
* @this {CPUStatePDP11}
* @param {number} addressMode
* @param {number} accessFlags
* @return {number}
*/
CPUStatePDP11.prototype.getAddrByMode = function(addressMode, accessFlags)
{
var result;
if (!(addressMode & 0x38)) {
result = BusPDP11.MAX_ADDRESS + (addressMode & 7); // Registers have special addresses above maximum address
} else {
var addr = this.getVirtualByMode(addressMode, accessFlags);
result = this.mapVirtualToPhysical(addr, accessFlags);
}
return result;
};
/**
* readWordByMode(addressMode)
*
* @this {CPUStatePDP11}
* @param {number} addressMode
* @return {number}
*/
CPUStatePDP11.prototype.readWordByMode = function(addressMode)
{
var result;
if (!(addressMode & PDP11.OPMODE.MASK)) {
result = this.regsGen[addressMode & PDP11.OPREG.MASK];
} else {
var addr = this.getAddrByMode(addressMode, PDP11.ACCESS.READ);
result = this.readWordByAddr(addr);
}
return result;
};
/**
* readByteByMode(addressMode)
*
* @this {CPUStatePDP11}
* @param {number} addressMode
* @return {number}
*/
CPUStatePDP11.prototype.readByteByMode = function(addressMode)
{
var result;
if (!(addressMode & 0x38)) {
result = this.regsGen[addressMode & 7] & 0xff;
} else {
var addr = this.getAddrByMode(addressMode, PDP11.ACCESS.READ_BYTE);
result = this.readByteByAddr(addr);
}
return result;
};
/**
* updateWordByMode(addressMode, src, fnOp)
*
* Used whenever the dst operand (as described by addressMode) DOES need to be read before writing.
*
* @this {CPUStatePDP11}
* @param {number} addressMode
* @param {number} src
* @param {function(number,number)} fnOp
* @return {number}
*/
CPUStatePDP11.prototype.updateWordByMode = function(addressMode, src, fnOp)
{
var data;
if (!(addressMode & PDP11.OPMODE.MASK)) {
var reg = addressMode & PDP11.OPREG.MASK;
this.regsGen[reg] = (data = fnOp.call(this, src, this.regsGen[reg]) & 0xffff);
} else {
var addr = this.getAddrByMode(addressMode, PDP11.ACCESS.UPDATE);
this.writeWordByAddr(addr, (data = fnOp.call(this, src, this.readWordByAddr(addr))));
}
return data;
};
/**
* updateByteByMode(addressMode, src, fnOp)
*
* Used whenever the dst operand (as described by addressMode) DOES need to be read before writing.
*
* @this {CPUStatePDP11}
* @param {number} addressMode
* @param {number} src
* @param {function(number,number)} fnOp
* @return {number}
*/
CPUStatePDP11.prototype.updateByteByMode = function(addressMode, src, fnOp)
{
var data;
if (!(addressMode & PDP11.OPMODE.MASK)) {
var reg = addressMode & PDP11.OPREG.MASK;
this.regsGen[reg] = (this.regsGen[reg] & 0xff00) | ((data = fnOp.call(this, src, this.regsGen[reg]) & 0xff));
} else {
var addr = this.getAddrByMode(addressMode, PDP11.ACCESS.UPDATE_BYTE);
this.writeByteByAddr(addr, (data = fnOp.call(this, src, this.readByteByAddr(addr))));
}
return data;
};
/**
* writeWordByMode(addressMode, data)
*
* Used whenever the dst operand (as described by addressMode) does NOT need to be read before writing.
*
* @this {CPUStatePDP11}
* @param {number} addressMode
* @param {number} data
* @return {number}
*/
CPUStatePDP11.prototype.writeWordByMode = function(addressMode, data)
{
if (!(addressMode & PDP11.OPMODE.MASK)) {
this.regsGen[addressMode & PDP11.OPREG.MASK] = data & 0xffff;
} else {
this.writeWordByAddr(this.getAddrByMode(addressMode, PDP11.ACCESS.WRITE), data);
}
return data;
};
/**
* writeByteByMode(addressMode, data, writeFlags)
*
* Used whenever the dst operand (as described by addressMode) does NOT need to be read before writing.
*
* @this {CPUStatePDP11}
* @param {number} addressMode
* @param {number} data
* @param {number} [writeFlags]
* @return {number}
*/
CPUStatePDP11.prototype.writeByteByMode = function(addressMode, data, writeFlags)
{
if (!(addressMode & PDP11.OPMODE.MASK)) {
var reg = addressMode & PDP11.OPREG.MASK;
if (!writeFlags) {
this.regsGen[reg] = (this.regsGen[reg] & ~0xff) | (data & 0xff);
} else if (writeFlags & PDP11.WRITE.ZERO) {
this.regsGen[reg] &= ~0xff;
} else {
this.assert(writeFlags & PDP11.WRITE.SIGNEXT);
this.regsGen[reg] = ((data << 24) >> 24) & 0xffff;
}
} else {
this.writeByteByAddr(this.getAddrByMode(addressMode, PDP11.ACCESS.WRITE_BYTE), data);
}
return data;
};
/**
* branch(PC, instruction)
*
* @this {CPUStatePDP11}
* @param {number} PC
* @param {number} instruction
* @return {number}
*/
CPUStatePDP11.prototype.branch = function(PC, instruction)
{
if (instruction & 0x80) { /*0200*/
instruction |= 0xff00;
} else {
instruction &= 0xff;
}
return ((instruction << 1) + PC) & 0xffff;
};
/**
* stepCPU(nMinCycles)
*
* NOTE: Single-stepping should not be confused with the Trap flag; single-stepping is a Debugger
* operation that's completely independent of Trap status. The CPU can go in and out of Trap mode,
* in and out of h/w interrupt service routines (ISRs), etc, but from the Debugger's perspective,
* they're all one continuous stream of instructions that can be stepped or run at will. Moreover,
* stepping vs. running should never change the behavior of the simulation.
*
* @this {CPUStatePDP11}
* @param {number} nMinCycles (0 implies a single-step, and therefore breakpoints should be ignored)
* @return {number} of cycles executed; 0 indicates a pre-execution condition (ie, an execution breakpoint
* was hit), -1 indicates a post-execution condition (eg, a read or write breakpoint was hit), and a positive
* number indicates successful completion of that many cycles (which should always be >= nMinCycles).
*/
CPUStatePDP11.prototype.stepCPU = function(nMinCycles)
{
/*
* The Debugger uses fComplete to determine if the instruction completed (true) or was interrupted
* by a breakpoint or some other exceptional condition (false). NOTE: this does NOT include JavaScript
* exceptions, which stepCPU() expects the caller to catch using its own exception handler.
*
* The CPU relies on the use of stopCPU() rather than fComplete, because the CPU never single-steps
* (ie, nMinCycles is always some large number), whereas the Debugger does. And conversely, when the
* Debugger is single-stepping (even when performing multiple single-steps), fRunning is never set,
* so stopCPU() would have no effect as far as the Debugger is concerned.
*/
this.flags.complete = true;
/*
* fDebugCheck is true if we need to "check" every instruction with the Debugger.
*/
var fDebugCheck = this.flags.debugCheck = (DEBUGGER && this.dbg && this.dbg.checksEnabled());
/*
* nDebugState is checked only when fDebugCheck is true, and its sole purpose is to tell the first call
* to checkInstruction() that it can skip breakpoint checks, and that will be true ONLY when fStarting is
* true OR nMinCycles is zero (the latter means the Debugger is single-stepping).
*
* Once we snap fStarting, we clear it, because technically, we've moved beyond "starting" and have
* officially "started" now.
*/
var nDebugState = (!nMinCycles)? -1 : (this.flags.starting? 0 : 1);
this.flags.starting = false;
/*
* We move the minimum cycle count to nStepCycles (the number of cycles left to step), so that other
* functions have the ability to force that number to zero (eg, stopCPU()), and thus we don't have to check
* any other criteria to determine whether we should continue stepping or not.
*/
this.nBurstCycles = this.nStepCycles = nMinCycles;
/*
* NOTE: If checkINTR() returns false, INTFLAG.HALT must be set, so no instructions should be executed.
*/
if (this.checkINTR()) {
do {
if (DEBUGGER && fDebugCheck) {
if (this.dbg.checkInstruction(this.getPC(), nDebugState)) {
this.stopCPU();
break;
}
nDebugState = 1;
}
var opCode,
src,
dst,
dstAddr,
result,
virtualAddress, savePSW, reg, i, j;
/*
* Check for any pending traps.
*
* I've moved this TRAP_MASK check BEFORE we decode the next instruction instead
* of immediately AFTER, because the last instruction may have thrown an exception,
* kicking us out before we reach the bottom of this loop.
*/
if (this.opFlags & PDP11.OPFLAG.TRAP_MASK) {
if (this.opFlags & PDP11.OPFLAG.TRAP_MMU) {
this.trap(PDP11.TRAP.MMU_FAULT, 52); // MMU trap has priority
} else {
if (this.opFlags & PDP11.OPFLAG.TRAP_SP) {
this.trap(PDP11.TRAP.BUS_ERROR, 54); // then SP trap
} else {
if (this.opFlags & PDP11.OPFLAG.TRAP_TF) {
this.trap(PDP11.TRAP.BREAKPOINT, 56); // and finally a TF trap
}
}
}
this.opFlags &= ~PDP11.OPFLAG.TRAP_MASK;
}
if (this.priorityReview) { // if nothing has changed don't check priority
if (this.priorityReview === 1) {
this.priorityReview = 2; // SPL delay
} else {
this.priorityReview = 0;
j = -1;
savePSW = this.pir & 0xe0;
if ((i = this.interruptQueue.length) > 0) {
while (i-- > 0) {
if (this.interruptQueue[i].delay > 0) {
this.interruptQueue[i].delay--;
this.priorityReview = 2;
break; // Decrement only one delay 'difference' per cycle
}
//if (typeof this.interruptQueue[i].callback !== "undefined") {
if (this.interruptQueue[i].callback) {
if (!this.interruptQueue[i].callback()) {
this.interruptQueue.splice(i, 1);
j--;
continue;
}
//delete
this.interruptQueue[i].callback = null;
}
if (this.interruptQueue[i].priority > savePSW) {
savePSW = this.interruptQueue[i].priority;
j = i;
}
}
}
if (savePSW > (this.PSW & 0xe0)) {
if (j < 0) {
this.trap(PDP11.TRAP.PIRQ, 42);
} else {
this.trap(this.interruptQueue[j].vector, 44);
this.interruptQueue.splice(j, 1);
}
}
}
}
if (!(this.MMR0 & 0xe000)) {
this.MMR1 = 0;
this.MMR2 = this.regsGen[7];
}
/*
* Snapshot the TF bit in opFlags, while simultaneously clearing all other opFlags;
* we'll check the TRAP_TF bit in opFlags when we come back around for another opcode.
*/
this.opFlags = this.PSW & PDP11.PSW.TF;
/*
* TODO: Determine (later) if this.regOp is a useful (internal) register to maintain;
* perhaps it would alleviate lots of opCode parameter-passing. In the meantime, we use
* it to detect if our new decode() function has processed (ie, "consumed") the opcode,
* by setting it to -1. If it has, then we can skip the older opcode decode logic below.
*/
this.regOp = opCode = this.readWordByVirtual(this.regsGen[7]);
if (opCode >= 0) this.regsGen[7] = (this.regsGen[7] + 2) & 0xffff;
this.decode(opCode);
if (this.regOp < 0) {
switch (opCode & 0xF000) /*0170000*/ { // Double operand instructions xxSSDD
case 0x1000: /*0010000*/ // MOV 01SSDD
//LOG_INSTRUCTION(opCode, 2, "MOV");
if ((src = this.readWordByMode(opCode >> 6)) >= 0) {
if (!(opCode & 0x38)) {
this.regsGen[opCode & 7] = src;
this.flagN = this.flagZ = src;
this.flagV = 0;
} else {
if ((dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.WRITE)) >= 0) {
if (this.writeWordByAddr(dstAddr, src) >= 0) {
this.flagN = this.flagZ = src;
this.flagV = 0;
}
}
}
}
break;
case 0x2000: /*0020000*/ // CMP 02SSDD
//LOG_INSTRUCTION(opCode, 2, "CMP");
if ((src = this.readWordByMode(opCode >> 6)) >= 0) {
if ((dst = this.readWordByMode(opCode)) >= 0) {
result = src - dst;
this.flagN = this.flagZ = this.flagC = result;
this.flagV = (src ^ dst) & (src ^ result);
}
}
break;
case 0x3000: /*0030000*/ // BIT 03SSDD
//LOG_INSTRUCTION(opCode, 2, "BIT");
if ((src = this.readWordByMode(opCode >> 6)) >= 0) {
if ((dst = this.readWordByMode(opCode)) >= 0) {
this.flagN = this.flagZ = src & dst;
this.flagV = 0;
}
}
break;
case 0x4000: /*0040000*/ // BIC 04SSDD
//LOG_INSTRUCTION(opCode, 2, "BIC");
if ((src = this.readWordByMode(opCode >> 6)) >= 0) {
if (!(opCode & 0x38)) {
result = this.regsGen[opCode & 7] &= ~src;
this.flagN = this.flagZ = result;
this.flagV = 0;
} else {
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >= 0) {
result = dst & ~src;
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagN = this.flagZ = result;
this.flagV = 0;
}
}
}
}
break;
case 0x5000: /*0050000*/ // BIS 05SSDD
//LOG_INSTRUCTION(opCode, 2, "BIS");
if ((src = this.readWordByMode(opCode >> 6)) >= 0) {
if (!(opCode & 0x38)) {
result = this.regsGen[opCode & 7] |= src;
this.flagN = this.flagZ = result;
this.flagV = 0;
} else {
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >= 0) {
result = dst | src;
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagN = this.flagZ = result;
this.flagV = 0;
}
}
}
}
break;
case 0x6000: /*0060000*/ // ADD 06SSDD
//LOG_INSTRUCTION(opCode, 2, "ADD");
if ((src = this.readWordByMode(opCode >> 6)) >= 0) {
if (!(opCode & 0x38)) {
reg = opCode & 7;
dst = this.regsGen[reg];
this.regsGen[reg] = (result = src + dst) & 0xffff;
this.flagN = this.flagZ = this.flagC = result;
this.flagV = (src ^ result) & (dst ^ result);
} else {
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >= 0) {
result = src + dst;
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagN = this.flagZ = this.flagC = result;
this.flagV = (src ^ result) & (dst ^ result);
}
}
}
}
break;
case 0x9000: /*0110000*/ // MOVB 11SSDD
//LOG_INSTRUCTION(opCode, 2, "MOVB");
if ((src = this.readByteByMode(opCode >> 6)) >= 0) {
if (!(opCode & 0x38)) {
if (src & 0x80) /*0200*/ src |= 0xff00; // movb sign extends register to word size
this.regsGen[opCode & 7] = src;
this.flagN = this.flagZ = src;
this.flagV = 0;
} else {
if ((dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.WRITE_BYTE)) >= 0) { // write byte
if (this.writeByteByAddr(dstAddr, src) >= 0) {
this.flagN = this.flagZ = src << 8;
this.flagV = 0;
}
}
}
}
break;
case 0xA000: /*0120000*/ // CMPB 12SSDD
//LOG_INSTRUCTION(opCode, 2, "CMPB");
if ((src = this.readByteByMode(opCode >> 6)) >= 0) {
if ((dst = this.readByteByMode(opCode)) >= 0) {
result = src - dst;
this.flagN = this.flagZ = this.flagC = result << 8;
this.flagV = ((src ^ dst) & (src ^ result)) << 8;
}
}
break;
case 0xB000: /*0130000*/ // BITB 13SSDD
//LOG_INSTRUCTION(opCode, 2, "BITB");
if ((src = this.readByteByMode(opCode >> 6)) >= 0) {
if ((dst = this.readByteByMode(opCode)) >= 0) {
this.flagN = this.flagZ = (src & dst) << 8;
this.flagV = 0;
}
}
break;
case 0xC000: /*0140000*/ // BICB 14SSDD
//LOG_INSTRUCTION(opCode, 2, "BICB");
if ((src = this.readByteByMode(opCode >> 6)) >= 0) {
if ((dst = this.readByteByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE_BYTE))) >= 0) {
result = dst & ~src;
if (this.writeByteByAddr(dstAddr, result) >= 0) {
this.flagN = this.flagZ = result << 8;
this.flagV = 0;
}
}
}
break;
case 0xD000: /*0150000*/ // BISB 15SSDD
//LOG_INSTRUCTION(opCode, 2, "BISB");
if ((src = this.readByteByMode(opCode >> 6)) >= 0) {
if ((dst = this.readByteByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE_BYTE))) >= 0) {
result = dst | src;
if (this.writeByteByAddr(dstAddr, result) >= 0) {
this.flagN = this.flagZ = result << 8;
this.flagV = 0;
}
}
}
break;
case 0xE000: /*0160000*/ // SUB 16SSDD
//LOG_INSTRUCTION(opCode, 2, "SUB");
if ((src = this.readWordByMode(opCode >> 6)) >= 0) {
if (!(opCode & 0x38)) {
reg = opCode & 7;
dst = this.regsGen[reg];
this.regsGen[reg] = (result = dst - src) & 0xffff;
this.flagN = this.flagZ = this.flagC = result;
this.flagV = (src ^ dst) & (dst ^ result);
} else {
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >= 0) {
result = dst - src;
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagN = this.flagZ = this.flagC = result;
this.flagV = (src ^ dst) & (dst ^ result);
}
}
}
}
break;
default:
switch (opCode & 0xFE00) /*0177000*/ { // Misc instructions xxRDD
case 0x800: /*04000*/ // JSR 004RDD
//LOG_INSTRUCTION(opCode, 3, "JSR");
if ((virtualAddress = this.getVirtualByMode(opCode, 0)) >= 0) {
reg = (opCode >> 6) & 7;
if (this.pushWord(this.regsGen[reg]) >= 0) {
this.regsGen[reg] = this.regsGen[7];
this.regsGen[7] = virtualAddress & 0xffff;
}
}
break;
case 0x7000: /*0070000*/ // MUL 070RSS
//LOG_INSTRUCTION(opCode, 3, "MUL");
if ((src = this.readWordByMode(opCode)) >= 0) {
reg = (opCode >> 6) & 7;
if (src & 0x8000) src |= ~0xffff;
dst = this.regsGen[reg];
if (dst & 0x8000) dst |= ~0xffff;
result = ~~(src * dst);
this.regsGen[reg] = (result >> 16) & 0xffff;
this.regsGen[reg | 1] = result & 0xffff;
this.flagN = result >> 16;
this.flagZ = this.flagN | result;
this.flagC = this.flagV = 0;
if (result < -32768 || result > 32767) this.flagC = 0x10000;
}
break;
case 0x7200: /*0071000*/ // DIV 071RSS
//LOG_INSTRUCTION(opCode, 3, "DIV");
if ((src = this.readWordByMode(opCode)) >= 0) {
if (!src) {
this.flagN = 0; // NZVC
this.flagZ = 0;
this.flagV = 0x8000;
this.flagC = 0x10000; // divide by zero
} else {
reg = (opCode >> 6) & 7;
dst = (this.regsGen[reg] << 16) | this.regsGen[reg | 1];
this.flagC = this.flagV = 0;
if (src & 0x8000) src |= ~0xffff;
result = ~~(dst / src);
if (result >= -32768 && result <= 32767) {
this.regsGen[reg] = result & 0xffff;
this.regsGen[reg | 1] = (dst - (result * src)) & 0xffff;
this.flagZ = (result >> 16) | result;
this.flagN = result >> 16;
} else {
this.flagV = 0x8000; // overflow - following are indeterminate
this.flagZ = (result >> 15) | result; // dodgy
this.flagN = dst >> 16; // just as dodgy
if (src === -1 && this.regsGen[reg] ===
0xfffe) this.regsGen[reg] = this.regsGen[reg | 1] = 1; // etc
}
}
}
break;
case 0x7400: /*072000*/ // ASH 072RSS
//LOG_INSTRUCTION(opCode, 3, "ASH");
if ((src = this.readWordByMode(opCode)) >= 0) {
reg = (opCode >> 6) & 7;
result = this.regsGen[reg];
if (result & 0x8000) result |= 0xffff0000;
this.flagC = this.flagV = 0;
src &= 0x3F; /*077*/
if (src & 0x20) /*040*/ { // shift right
src = 64 - src;
if (src > 16) src = 16;
this.flagC = result << (17 - src);
result = result >> src;
} else {
if (src) {
if (src > 16) {
this.flagV = result;
result = 0;
} else {
result = result << src;
this.flagC = result;
dst = (result >> 15) & 0xffff; // check successive sign bits
if (dst && dst !== 0xffff) this.flagV = 0x8000;
}
}
}
this.regsGen[reg] = result & 0xffff;
this.flagN = this.flagZ = result;
}
break;
case 0x7600: /*073000*/ // ASHC 073RSS
//LOG_INSTRUCTION(opCode, 3, "ASHC");
if ((src = this.readWordByMode(opCode)) >= 0) {
reg = (opCode >> 6) & 7;
dst = (this.regsGen[reg] << 16) | this.regsGen[reg | 1];
this.flagC = this.flagV = 0;
src &= 0x3F; /*077*/
if (src & 0x20) /*040*/ {
src = 64 - src;
if (src > 32) src = 32;
result = dst >> (src - 1);
this.flagC = result << 16;
result >>= 1;
if (dst & 0x80000000) result |= 0xffffffff << (32 - src);
} else {
if (src) { // shift left
result = dst << (src - 1);
this.flagC = result >> 15;
result <<= 1;
if (src > 32) src = 32;
dst = dst >> (32 - src);
if (dst) {
dst |= (0xffffffff << src) & 0xffffffff;
if (dst !== 0xffffffff) this.flagV = 0x8000;
}
} else {
result = dst;
}
}
this.regsGen[reg] = (result >> 16) & 0xffff;
this.regsGen[reg | 1] = result & 0xffff;
this.flagN = result >> 16;
this.flagZ = result >> 16 | result;
}
break;
case 0x7800: /*0074000*/ // XOR 074RSS
//LOG_INSTRUCTION(opCode, 3, "XOR");
if (!(opCode & 0x38)) {
dst = this.regsGen[opCode & 7] ^= this.regsGen[(opCode >> 6) & 7];
this.flagN = this.flagZ = dst;
this.flagV = 0;
} else {
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >= 0) {
dst ^= this.regsGen[(opCode >> 6) & 7];
if (this.writeWordByAddr(dstAddr, dst) >= 0) {
this.flagN = this.flagZ = dst;
this.flagV = 0;
}
}
}
break;
case 0x7E00: /*0077000*/ // SOB 077Rnn
//LOG_INSTRUCTION(opCode, 5, "SOB");
reg = (opCode >> 6) & 7;
if ((this.regsGen[reg] = ((this.regsGen[reg] - 1) & 0xffff))) {
this.regsGen[7] = (this.regsGen[7] - ((opCode & 0x3F) /*077*/ << 1)) & 0xffff;
}
break;
default:
switch (opCode & 0xFF00) /*0177400*/ { // Program control instructions & traps
case 0x100: /*0000400*/ // BR
//LOG_INSTRUCTION(opCode, 4, "BR");
this.regsGen[7] = this.branch(this.regsGen[7], opCode);
break;
case 0x200: /*0001000*/ // BNE
//LOG_INSTRUCTION(opCode, 4, "BNE");
if (this.flagZ & 0xffff) this.regsGen[7] = this.branch(this.regsGen[7], opCode);
break;
case 0x300: /*0001400*/ // BEQ
//LOG_INSTRUCTION(opCode, 4, "BEQ");
if (!(this.flagZ & 0xffff)) this.regsGen[7] = this.branch(this.regsGen[7], opCode);
break;
case 0x400: /*0002000*/ // BGE
//LOG_INSTRUCTION(opCode, 4, "BGE");
if ((this.flagN & 0x8000) ===
(this.flagV & 0x8000)) this.regsGen[7] = this.branch(this.regsGen[7], opCode);
break;
case 0x500: /*0002400*/ // BLT
//LOG_INSTRUCTION(opCode, 4, "BLT");
if ((this.flagN & 0x8000) !==
(this.flagV & 0x8000)) this.regsGen[7] = this.branch(this.regsGen[7], opCode);
break;
case 0x600: /*0003000*/ // BGT
//LOG_INSTRUCTION(opCode, 4, "BGT");
if ((this.flagZ & 0xffff) && ((this.flagN & 0x8000) ===
(this.flagV & 0x8000))) this.regsGen[7] = this.branch(this.regsGen[7], opCode);
break;
case 0x700: /*0003400*/ // BLE
//LOG_INSTRUCTION(opCode, 4, "BLE");
if (!(this.flagZ & 0xffff) || ((this.flagN & 0x8000) !==
(this.flagV & 0x8000))) this.regsGen[7] = this.branch(this.regsGen[7], opCode);
break;
case 0x8000: /*0100000*/ // BPL
//LOG_INSTRUCTION(opCode, 4, "BPL");
if (!(this.flagN & 0x8000)) this.regsGen[7] = this.branch(this.regsGen[7], opCode);
break;
case 0x8200: /*0101000*/ // BHI
//LOG_INSTRUCTION(opCode, 4, "BHI");
if (!(this.flagC & 0x10000) &&
(this.flagZ & 0xffff)) this.regsGen[7] = this.branch(this.regsGen[7], opCode);
break;
case 0x8100: /*0100400*/ // BMI
//LOG_INSTRUCTION(opCode, 4, "BMI");
if ((this.flagN & 0x8000)) this.regsGen[7] = this.branch(this.regsGen[7], opCode);
break;
case 0x8300: /*0101400*/ // BLOS
//LOG_INSTRUCTION(opCode, 4, "BLOS");
if ((this.flagC & 0x10000) ||
!(this.flagZ & 0xffff)) this.regsGen[7] = this.branch(this.regsGen[7], opCode);
break;
case 0x8400: /*0102000*/ // BVC
//LOG_INSTRUCTION(opCode, 4, "BVC");
if (!(this.flagV & 0x8000)) this.regsGen[7] = this.branch(this.regsGen[7], opCode);
break;
case 0x8500: /*0102400*/ // BVS
//LOG_INSTRUCTION(opCode, 4, "BVS");
if ((this.flagV & 0x8000)) this.regsGen[7] = this.branch(this.regsGen[7], opCode);
break;
case 0x8600: /*0103000*/ // BCC
//LOG_INSTRUCTION(opCode, 4, "BCC");
if (!(this.flagC &
0x10000)) this.regsGen[7] = this.branch(this.regsGen[7], opCode);
break;
case 0x8700: /*0103400*/ // BCS
//LOG_INSTRUCTION(opCode, 4, "BCS");
if (this.flagC & 0x10000) this.regsGen[7] = this.branch(this.regsGen[7], opCode);
break;
case 0x8800: /*0104000*/ // EMT 104000 -> 104377
//LOG_INSTRUCTION(opCode, 7, "EMT");
this.trap(PDP11.TRAP.EMULATOR, /*030*/ 2);
break;
case 0x8900: /*0104400*/ // TRAP 104400 -> 104777
//LOG_INSTRUCTION(opCode, 7, "TRAP");
this.trap(PDP11.TRAP.TRAP, /*034*/ 4);
break;
default:
switch (opCode & 0xFFC0) /*0177700*/ { // Single operand instructions xxxxDD
case 0x40: /*0000100*/ // JMP 0001DD
//LOG_INSTRUCTION(opCode, 1, "JMP");
if ((virtualAddress = this.getVirtualByMode(opCode, 0)) >= 0) {
this.regsGen[7] = virtualAddress & 0xffff;
}
break;
case 0xC0: /*0000300*/ // SWAB 0003DD
//LOG_INSTRUCTION(opCode, 1, "SWAB");
if (!(opCode & 0x38)) {
reg = opCode & 7;
dst = this.regsGen[reg];
this.regsGen[reg] = ((dst << 8) | (dst >> 8)) & 0xffff;
this.flagN = this.flagZ = dst & 0xff00;
this.flagV = this.flagC = 0;
} else {
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >=
0) {
result = (dst << 8) | (dst >> 8);
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagN = this.flagZ = dst & 0xff00;
this.flagV = this.flagC = 0;
}
}
}
break;
case 0xA00: /*0005000*/ // CLR 0050DD
//LOG_INSTRUCTION(opCode, 1, "CLR");
if (!(opCode & 0x38)) {
this.regsGen[opCode & 7] = 0;
this.flagN = this.flagC = this.flagV = this.flagZ = 0;
} else {
if ((dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.WRITE)) >= 0) { // write word
if (this.writeWordByAddr(dstAddr, 0) >= 0) {
this.flagN = this.flagC = this.flagV = this.flagZ = 0;
}
}
}
break;
case 0xA40: /*0005100*/ // COM 0051DD
//LOG_INSTRUCTION(opCode, 1, "COM");
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >=
0) {
result = ~dst;
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagN = this.flagZ = result;
this.flagC = 0x10000;
this.flagV = 0;
}
}
break;
case 0xA80: /*0005200*/ // INC 0052DD
//LOG_INSTRUCTION(opCode, 1, "INC");
if (!(opCode & 0x38)) {
reg = opCode & 7;
dst = this.regsGen[reg];
result = dst + 1;
this.regsGen[reg] = result & 0xffff;
this.flagN = this.flagZ = result;
this.flagV = result & (result ^ dst);
} else {
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >=
0) {
result = dst + 1;
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagN = this.flagZ = result;
this.flagV = result & (result ^ dst);
}
}
}
break;
case 0xAC0: /*0005300*/ // DEC 0053DD
//LOG_INSTRUCTION(opCode, 1, "DEC");
if (!(opCode & 0x38)) {
reg = opCode & 7;
dst = this.regsGen[reg];
result = dst - 1;
this.regsGen[reg] = result & 0xffff;
this.flagN = this.flagZ = result;
this.flagV = (result ^ dst) & dst;
} else {
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >=
0) {
result = dst - 1;
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagN = this.flagZ = result;
this.flagV = (result ^ dst) & dst;
}
}
}
break;
case 0xB00: /*0005400*/ // NEG 0054DD
//LOG_INSTRUCTION(opCode, 1, "NEG");
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >=
0) {
result = -dst;
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagC = this.flagN = this.flagZ = result;
this.flagV = result & dst;
}
}
break;
case 0xB40: /*0005500*/ // ADC 0055DD
//LOG_INSTRUCTION(opCode, 1, "ADC");
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >=
0) {
result = dst + ((this.flagC >> 16) & 1);
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagC = this.flagN = this.flagZ = result;
this.flagV = result & (result ^ dst);
}
}
break;
case 0xB80: /*0005600*/ // SBC 0056DD
//LOG_INSTRUCTION(opCode, 1, "SBC");
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >=
0) {
result = dst - ((this.flagC >> 16) & 1);
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagC = this.flagN = this.flagZ = result;
this.flagV = (result ^ dst) & dst;
}
}
break;
case 0xBC0: /*0005700*/ // TST 0057DD
//LOG_INSTRUCTION(opCode, 1, "TST");
if ((dst = this.readWordByMode(opCode)) >= 0) {
this.flagN = this.flagZ = dst;
this.flagC = this.flagV = 0;
}
break;
case 0xC00: /*0006000*/ // ROR 0060DD
//LOG_INSTRUCTION(opCode, 1, "ROR");
if (!(opCode & 0x38)) {
reg = opCode & 7;
dst = this.regsGen[reg];
result = ((this.flagC & 0x10000) | dst) >> 1;
this.regsGen[reg] = result & 0xffff;
this.flagC = (dst << 16);
this.flagN = this.flagZ = result;
this.flagV = result ^ (this.flagC >> 1);
} else {
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >=
0) {
result = ((this.flagC & 0x10000) | dst) >> 1;
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagC = (dst << 16);
this.flagN = this.flagZ = result;
this.flagV = result ^ (this.flagC >> 1);
}
}
}
break;
case 0xC40: /*0006100*/ // ROL 0061DD
//LOG_INSTRUCTION(opCode, 1, "ROL");
if (!(opCode & 0x38)) {
reg = opCode & 7;
dst = this.regsGen[reg];
result = (dst << 1) | ((this.flagC >> 16) & 1);
this.regsGen[reg] = result & 0xffff;
this.flagC = this.flagN = this.flagZ = result;
this.flagV = result ^ dst;
} else {
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >=
0) {
result = (dst << 1) | ((this.flagC >> 16) & 1);
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagC = this.flagN = this.flagZ = result;
this.flagV = result ^ dst;
}
}
}
break;
case 0xC80: /*0006200*/ // ASR 0062DD
//LOG_INSTRUCTION(opCode, 1, "ASR");
if (!(opCode & 0x38)) {
reg = opCode & 7;
dst = this.regsGen[reg];
result = (dst & 0x8000) | (dst >> 1);
this.regsGen[reg] = result & 0xffff;
this.flagC = dst << 16;
this.flagN = this.flagZ = result;
this.flagV = this.flagN ^ (this.flagC >> 1);
} else {
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >=
0) {
result = (dst & 0x8000) | (dst >> 1);
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagC = dst << 16;
this.flagN = this.flagZ = result;
this.flagV = this.flagN ^ (this.flagC >> 1);
}
}
}
break;
case 0xCC0: /*0006300*/ // ASL 0063DD
//LOG_INSTRUCTION(opCode, 1, "ASL");
if (!(opCode & 0x38)) {
reg = opCode & 7;
dst = this.regsGen[reg];
result = dst << 1;
this.regsGen[reg] = result & 0xffff;
this.flagC = this.flagN = this.flagZ = result;
this.flagV = result ^ dst;
} else {
if ((dst = this.readWordByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE))) >=
0) {
result = dst << 1;
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagC = this.flagN = this.flagZ = result;
this.flagV = result ^ dst;
}
}
}
break;
case 0xD00: /*0006400*/ // MARK 0064nn
//LOG_INSTRUCTION(opCode, 8, "MARK");
virtualAddress = (this.regsGen[7] + ((opCode & 0x3F) /*077*/ << 1)) & 0xffff;
if ((src = this.readWordByVirtual(virtualAddress | 0x10000)) >= 0) {
this.regsGen[7] = this.regsGen[5];
this.regsGen[5] = src;
this.regsGen[6] = (virtualAddress + 2) & 0xffff;
}
break;
case 0xD40: /*0006500*/ // MFPI 0065SS
//LOG_INSTRUCTION(opCode, 1, "MFPI");
if (!(opCode & 0x38)) {
reg = opCode & 7;
if (6 !== reg || ((this.PSW >> 2) & 0x3000) === (this.PSW & 0x3000)) {
src = this.regsGen[reg];
} else {
src = this.regsAltStack[(this.PSW >> 12) & 3];
}
if (this.pushWord(src) >= 0) {
this.flagN = this.flagZ = src;
this.flagV = 0;
}
} else {
if ((virtualAddress = this.getVirtualByMode(opCode, 0)) >= 0) {
if ((this.PSW & 0xf000) !== 0xf000) virtualAddress &= 0xffff;
this.mmuMode = (this.PSW >> 12) & 3;
if ((src = this.readWordByVirtual(virtualAddress)) >= 0) {
this.mmuMode = (this.PSW >> 14) & 3;
if (this.pushWord(src) >= 0) {
this.flagN = this.flagZ = src;
this.flagV = 0;
}
}
}
}
break;
case 0xD80: /*0006600*/ // MTPI 0066DD
//LOG_INSTRUCTION(opCode, 1, "MTPI");
if ((dst = this.popWord()) >= 0) {
if (!(this.MMR0 & 0xe000)) this.MMR1 = 0x16; /*026*/
if (!(opCode & 0x38)) {
reg = opCode & 7;
if (6 !== reg || ((this.PSW >> 2) & 0x3000) === (this.PSW & 0x3000)) {
this.regsGen[reg] = dst;
} else {
this.regsAltStack[(this.PSW >> 12) & 3] = dst;
}
this.flagN = this.flagZ = dst;
this.flagV = 0;
} else {
if ((virtualAddress = this.getVirtualByMode(opCode, 0)) >= 0) {
virtualAddress &= 0xffff;
this.mmuMode = (this.PSW >> 12) & 3;
if ((dstAddr = this.mapVirtualToPhysical(virtualAddress, PDP11.ACCESS.WRITE)) >= 0) {
this.mmuMode = (this.PSW >> 14) & 3;
if (this.writeWordByAddr(dstAddr, dst) >= 0) {
this.flagN = this.flagZ = dst;
this.flagV = 0;
}
}
}
}
}
break;
case 0xDC0: /*0006700*/ // SXT 0067DD
//LOG_INSTRUCTION(opCode, 1, "SXT");
if ((dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.WRITE)) >= 0) { // write word
result = -((this.flagN >> 15) & 1);
if (this.writeWordByAddr(dstAddr, result) >= 0) {
this.flagZ = result;
this.flagV = 0;
}
}
break;
case 0x8A00: /*0105000*/ // CLRB 1050DD
//LOG_INSTRUCTION(opCode, 1, "CLRB");
if (!(opCode & 0x38)) {
this.regsGen[opCode & 7] &= 0xff00;
this.flagN = this.flagC = this.flagV = this.flagZ = 0;
} else {
if ((dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.WRITE_BYTE)) >= 0) { // write byte
if (this.writeByteByAddr(dstAddr, 0) >= 0) {
this.flagN = this.flagC = this.flagV = this.flagZ = 0;
}
}
}
break;
case 0x8A40: /*0105100*/ // COMB 1051DD
//LOG_INSTRUCTION(opCode, 1, "COMB");
if ((dst = this.readByteByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE_BYTE))) >=
0) {
result = ~dst;
if (this.writeByteByAddr(dstAddr, result) >= 0) {
this.flagN = this.flagZ = result << 8;
this.flagC = 0x10000;
this.flagV = 0;
}
}
break;
case 0x8A80: /*0105200*/ // INCB 1052DD
//LOG_INSTRUCTION(opCode, 1, "INCB");
if ((dst = this.readByteByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE_BYTE))) >=
0) {
result = dst + 1;
if (this.writeByteByAddr(dstAddr, result) >= 0) {
this.flagN = this.flagZ = result << 8;
this.flagV = (result & (result ^ dst)) << 8;
}
}
break;
case 0x8AC0: /*0105300*/ // DECB 1053DD
//LOG_INSTRUCTION(opCode, 1, "DECB");
if ((dst = this.readByteByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE_BYTE))) >=
0) {
result = dst - 1;
if (this.writeByteByAddr(dstAddr, result) >= 0) {
this.flagN = this.flagZ = result << 8;
this.flagV = ((result ^ dst) & dst) << 8;
}
}
break;
case 0x8B00: /*0105400*/ // NEGB 1054DD
//LOG_INSTRUCTION(opCode, 1, "NEGB");
if ((dst = this.readByteByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE_BYTE))) >=
0) {
result = -dst;
if (this.writeByteByAddr(dstAddr, result) >= 0) {
this.flagC = this.flagN = this.flagZ = result << 8;
this.flagV = (result & dst) << 8;
}
}
break;
case 0x8B40: /*0105500*/ // ADCB 01055DD
//LOG_INSTRUCTION(opCode, 1, "ADCB");
if ((dst = this.readByteByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE_BYTE))) >=
0) {
result = dst + ((this.flagC >> 16) & 1);
if (this.writeByteByAddr(dstAddr, result) >= 0) {
this.flagN = this.flagZ = this.flagC = result << 8;
this.flagV = (result & (result ^ dst)) << 8;
}
}
break;
case 0x8B80: /*0105600*/ // SBCB 01056DD
//LOG_INSTRUCTION(opCode, 1, "SBCB");
if ((dst = this.readByteByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE_BYTE))) >=
0) {
result = dst - ((this.flagC >> 16) & 1);
if (this.writeByteByAddr(dstAddr, result) >= 0) {
this.flagN = this.flagZ = this.flagC = result << 8;
this.flagV = ((result ^ dst) & dst) << 8;
}
}
break;
case 0x8BC0: /*0105700*/ // TSTB 1057DD
//LOG_INSTRUCTION(opCode, 1, "TSTB");
if ((dst = this.readByteByMode(opCode)) >= 0) {
this.flagN = this.flagZ = dst << 8;
this.flagC = this.flagV = 0;
}
break;
case 0x8C00: /*0106000*/ // RORB 1060DD
//LOG_INSTRUCTION(opCode, 1, "RORB");
if ((dst = this.readByteByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE_BYTE))) >=
0) {
result = (((this.flagC & 0x10000) >> 8) | dst) >> 1;
if (this.writeByteByAddr(dstAddr, result) >= 0) {
this.flagC = (dst << 16);
this.flagN = this.flagZ = (result << 8);
this.flagV = this.flagN ^ (this.flagC >> 1);
}
}
break;
case 0x8C40: /*0106100*/ // ROLB 1061DD
//LOG_INSTRUCTION(opCode, 1, "ROLB");
if ((dst = this.readByteByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE_BYTE))) >=
0) {
result = (dst << 1) | ((this.flagC >> 16) & 1);
if (this.writeByteByAddr(dstAddr, result) >= 0) {
this.flagC = this.flagN = this.flagZ = result << 8;
this.flagV = (result ^ dst) << 8;
}
}
break;
case 0x8C80: /*0106200*/ // ASRB 1062DD
//LOG_INSTRUCTION(opCode, 1, "ASRB");
if ((dst = this.readByteByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE_BYTE))) >=
0) {
result = (dst & 0x80) | (dst >> 1);
if (this.writeByteByAddr(dstAddr, result) >= 0) {
this.flagC = dst << 16;
this.flagN = this.flagZ = result << 8;
this.flagV = this.flagN ^ (this.flagC >> 1);
}
}
break;
case 0x8CC0: /*0106300*/ // ASLB 1063DD
//LOG_INSTRUCTION(opCode, 1, "ASLB");
if ((dst = this.readByteByAddr(dstAddr = this.getAddrByMode(opCode, PDP11.ACCESS.UPDATE_BYTE))) >=
0) {
result = dst << 1;
if (this.writeByteByAddr(dstAddr, result) >= 0) {
this.flagC = this.flagN = this.flagZ = result << 8;
this.flagV = (result ^ dst) << 8;
}
}
break;
//case 0106400: // MTPS 1064SS
// //LOG_INSTRUCTION(opCode, 1, "MTPS");
// if ((src = this.readByteByMode(instruction)) >= 0) {
// this.setPSW((this.PSW & 0xff00) | (src & 0xef));
// } // Temporary PDP 11/34A
// break;
case 0x8D40: /*0106500*/ // MFPD 1065DD
//LOG_INSTRUCTION(opCode, 1, "MFPD");
if (!(opCode & 0x38)) {
reg = opCode & 7;
if (6 !== reg || ((this.PSW >> 2) & 0x3000) === (this.PSW & 0x3000)) {
src = this.regsGen[reg];
} else {
src = this.regsAltStack[(this.PSW >> 12) & 3];
}
if (this.pushWord(src) >= 0) {
this.flagN = this.flagZ = src;
this.flagV = 0;
}
} else {
if ((virtualAddress = this.getVirtualByMode(opCode, 0)) >= 0) {
this.mmuMode = (this.PSW >> 12) & 3;
if ((src = this.readWordByVirtual(virtualAddress | 0x10000)) >= 0) {
this.mmuMode = (this.PSW >> 14) & 3;
if (this.pushWord(src) >= 0) {
this.flagN = this.flagZ = src;
this.flagV = 0;
}
}
}
}
break;
case 0x8D80: /*0106600*/ // MTPD 1066DD
//LOG_INSTRUCTION(opCode, 1, "MTPD");
if ((dst = this.popWord()) >= 0) {
if (!(this.MMR0 & 0xe000)) this.MMR1 = 0x16; /*026*/
if (!(opCode & 0x38)) {
reg = opCode & 7;
if (6 !== reg || ((this.PSW >> 2) & 0x3000) === (this.PSW & 0x3000)) {
this.regsGen[reg] = dst;
} else {
this.regsAltStack[(this.PSW >> 12) & 3] = dst;
}
this.flagN = this.flagZ = dst;
this.flagV = 0;
} else {
if ((virtualAddress = this.getVirtualByMode(opCode, 0)) >= 0) {
this.mmuMode = (this.PSW >> 12) & 3;
if ((dstAddr = this.mapVirtualToPhysical(virtualAddress |
0x10000, PDP11.ACCESS.WRITE)) >= 0) {
this.mmuMode = (this.PSW >> 14) & 3;
if (this.writeWordByAddr(dstAddr, dst) >= 0) {
this.flagN = this.flagZ = dst;
this.flagV = 0;
}
}
}
}
}
break;
//case 0106700: // MTFS 1064SS
// //LOG_INSTRUCTION(opCode, 1, "MFPS");
// src = this.getPSW() & 0xff;
// if (instruction & 0x38) {
// if ((dstAddr = this.getAddrByMode(instruction, PDP11.ACCESS.WRITE_BYTE)) >= 0) { // write byte
// if (this.writeByteByAddr(dstAddr, src) >= 0) {
// this.flagN = this.flagZ = src << 8;
// this.flagV = 0;
// }
// }
// } else {
// if (src & 0200) src |= 0xff00;
// this.regsGen[instruction & 7] = src;
// this.flagN = this.flagZ = src << 8;
// this.flagV = 0;
// } // Temporary PDP 11/34A
// break;
default:
switch (opCode & 0xFFF8) /*0177770*/ { // Single register instructions xxxxxR (and CC)
case 0x80: /*0000200*/ // RTS 00020R
//LOG_INSTRUCTION(opCode, 6, "RTS");
if ((src = this.popWord()) >= 0) {
reg = opCode & 7;
this.regsGen[7] = this.regsGen[reg];
this.regsGen[reg] = src;
}
break;
case 0x98: /*0000230*/ // SPL 00023N
//LOG_INSTRUCTION(opCode, 9, "SPL");
if (!(this.PSW & 0xc000)) {
this.PSW = (this.PSW & 0xf81f) | ((opCode & 7) << 5);
this.priorityReview = 1;
}
break;
case 0xA0: /*0000240*/ // CLR CC 00024M Part 1 without N
case 0xA8: /*0000250*/ // CLR CC 00025M Part 2 with N
if (opCode & 1) this.clearCF(); // CLC
if (opCode & 2) this.clearVF(); // CLV
if (opCode & 4) this.clearZF(); // CLZ
if (opCode & 8) this.clearNF(); // CLN
break;
case 0xB0: /*0000260*/ // SET CC 00026M Part 1 without N
case 0xB8: /*0000270*/ // SET CC 00026M Part 2 with N
if (opCode & 1) this.setCF(); // SEC
if (opCode & 2) this.setVF(); // SEV
if (opCode & 4) this.setZF(); // SEZ
if (opCode & 8) this.setNF(); // SEN
break;
default: // Misc instructions (decode ALL remaining bits) xxxxxx
switch (opCode) {
case 0x0: /*0000000*/ // HALT 000000
//LOG_INSTRUCTION(opCode, 0, "HALT");
if (0xc000 & this.PSW) {
this.CPU_Error |= 0x80; /*0200*/
this.trap(PDP11.TRAP.BUS_ERROR, 46);
} else {
this.runState = 3; // halt
this.endBurst();
//LOG_PRINT();
console.log("HALT at " + this.regsGen[7].toString(8));
}
break;
case 0x1: /*0000001*/ // WAIT 000001
//LOG_INSTRUCTION(opCode, 0, "WAIT");
j = 0;
if ((i = this.interruptQueue.length) > 0) {
while (i-- > 0) {
j += this.interruptQueue[i].delay;
this.interruptQueue[i].delay = 0;
}
}
if (j === 0 && this.runState === 0) {
this.runState = 2; // wait
this.endBurst();
}
break;
case 0x3: /*0000003*/ // BPT 000003
//LOG_INSTRUCTION(opCode, 0, "BPT");
this.trap(PDP11.TRAP.BREAKPOINT, /*014*/ 6);
break;
case 0x4: /*0000004*/ // IOT 000004
//LOG_INSTRUCTION(opCode, 0, "IOT");
this.trap(PDP11.TRAP.IOT, /*020*/ 8);
break;
case 0x5: /*0000005*/ // RESET 000005
//LOG_INSTRUCTION(opCode, 0, "RESET");
if (!(this.PSW & 0xc000)) {
this.resetRegs();
this.bus.reset();
// display.data = this.regsGen[0]; // TODO: Review
}
break;
case 0x2: /*0000002*/ // RTI 000002
case 0x6: /*0000006*/ // RTT 000006
//LOG_INSTRUCTION(opCode, 0, "RTT");
dstAddr = this.regsGen[6];
if ((virtualAddress = this.readWordByVirtual(dstAddr | 0x10000)) >= 0) {
dstAddr = (dstAddr + 2) & 0xffff;
if ((savePSW = this.readWordByVirtual(dstAddr | 0x10000)) >= 0) {
this.regsGen[6] = (dstAddr + 2) & 0xffff;
savePSW &= 0xf8ff;
if (this.PSW & 0xc000) { // user / super restrictions
// keep SPL and allow lower only for modes and register set
savePSW = (savePSW & 0xf81f) | (this.PSW & 0xf8e0);
}
this.regsGen[7] = virtualAddress;
this.setPSW(savePSW);
this.opFlags &= ~PDP11.OPFLAG.TRAP_TF;
if (opCode === 2) { // RTI enables immediate trace
this.opFlags |= (this.PSW & PDP11.PSW.TF);
}
}
}
break;
//case 0000007: // MFPT 000007
// //LOG_INSTRUCTION(opCode, 0, "MFPT");
// this.regsGen[0] = 1;
// break; // Exists on pdp 11/44 & KB11-EM
default: // We don't know this instruction
//LOG_INSTRUCTION(opCode, 11, "-unknown-");
this.trap(PDP11.TRAP.RESERVED, /*010*/ 48); // reserved instruction
break;
}
}
}
}
}
}
}
} while (this.nStepCycles > 0);
}
return (this.flags.complete? this.nBurstCycles - this.nStepCycles : (this.flags.complete === undefined? 0 : -1));
};
/**
* CPUStatePDP11.init()
*
* This function operates on every HTML element of class "cpu", extracting the
* JSON-encoded parameters for the CPUStatePDP11 constructor from the element's "data-value"
* attribute, invoking the constructor (which in turn invokes the CPU constructor)
* to create a CPUStatePDP11 component, and then binding any associated HTML controls to the
* new component.
*/
CPUStatePDP11.init = function()
{
var aeCPUs = Component.getElementsByClass(document, PDP11.APPCLASS, "cpu");
for (var iCPU = 0; iCPU < aeCPUs.length; iCPU++) {
var eCPU = aeCPUs[iCPU];
var parmsCPU = Component.getComponentParms(eCPU);
var cpu = new CPUStatePDP11(parmsCPU);
Component.bindComponentControls(cpu, eCPU, PDP11.APPCLASS);
}
};
/*
* Initialize every CPU module on the page
*/
web.onInit(CPUStatePDP11.init);
if (NODE) module.exports = CPUStatePDP11;