Improved IOPAGE management

This commit is contained in:
Jeff Parsons 2016-10-08 11:09:14 -07:00 committed by Jeff Parsons
commit ae4fbeb489
7 changed files with 469 additions and 422 deletions

View file

@ -42,7 +42,7 @@ if (NODE) {
}
/**
* BusPDP11(cpu, dbg)
* BusPDP11(parmsBus, cpu, dbg)
*
* The BusPDP11 component manages physical memory and I/O address spaces.
*
@ -69,8 +69,18 @@ function BusPDP11(parmsBus, cpu, dbg)
this.cpu = cpu;
this.dbg = dbg;
/*
* Supported values for nBusWidth are 16 (default), 18, and 22. This represents the maximum size
* of the bus for the life of the machine, regardless what memory management mode the CPU has enabled.
*/
this.nBusWidth = parmsBus['busWidth'] || 16;
/*
* This controls the location of the IOPAGE (ie, at the top of 16-bit, 18-bit, or 22-bit address range).
* It is managed by setIOPageRange(). initMemory() establishes the default (16).
*/
this.nIOPageRange = 0; // zero means no IOPAGE access (yet)
/*
* Compute all BusPDP11 memory block parameters, based on the width of the bus. The entire
* address space is divided into blocks, using a block size that is (hopefully) appropriate to
@ -130,10 +140,26 @@ function BusPDP11(parmsBus, cpu, dbg)
*/
this.aIOHandlers = [];
this.fIOBreakAll = false;
/*
* Array of RESET notification handlers registered by Device components.
*/
this.afnReset = [];
/*
* Allocate empty Memory blocks to span the entire physical address space.
* Before we can add any memory blocks that declare our component as a custom memory controller,
* we must initialize the array that the getControllerAccess() method supplies to the Memory component.
*/
this.afnIOPage = [
BusPDP11.IOController.readByte,
BusPDP11.IOController.writeByte,
BusPDP11.IOController.readWord,
BusPDP11.IOController.writeWord
];
/*
* We're ready to allocate empty Memory blocks to span the entire physical address space, including the
* initial location of the IOPAGE.
*/
this.initMemory();
@ -142,7 +168,7 @@ function BusPDP11(parmsBus, cpu, dbg)
Component.subclass(BusPDP11);
BusPDP11.IOPAGE_VIRT = 0xE000; /*000160000*/
BusPDP11.IOPAGE_16BIT = 0xE000; /*000160000*/ // eg, PDP-11/20
BusPDP11.IOPAGE_18BIT = 0x3E000; /*000760000*/ // eg, PDP-11/45
BusPDP11.IOPAGE_UNIBUS = 0x3C0000; /*017000000*/
BusPDP11.IOPAGE_22BIT = 0x3FE000; /*017760000*/ // eg, PDP-11/70
@ -365,18 +391,31 @@ BusPDP11.prototype.initMemory = function()
for (var iBlock = 0; iBlock < this.nBlockTotal; iBlock++) {
this.aMemBlocks[iBlock] = block;
}
this.afnIOPage = new Array(4);
this.afnIOPage[0] = BusPDP11.IOController.readByte;
this.afnIOPage[1] = BusPDP11.IOController.writeByte;
this.afnIOPage[2] = BusPDP11.IOController.readWord;
this.afnIOPage[3] = BusPDP11.IOController.writeWord;
/*
* Map IOPAGE at the top of the address space (as determined by nBusWidth), as well as at IOPAGE_VIRT
* (which is the only place that 16-bit code can reach the IOPAGE when memory management is not enabled).
*/
var addr = this.addrTotal - BusPDP11.IOPAGE_LENGTH;
this.addMemory(addr, BusPDP11.IOPAGE_LENGTH, MemoryPDP11.TYPE.CONTROLLER, this);
this.addMemory(BusPDP11.IOPAGE_VIRT, BusPDP11.IOPAGE_LENGTH, MemoryPDP11.TYPE.CONTROLLER, this);
this.setIOPageRange(16);
};
/**
* setIOPageRange(nRange)
*
* We can define the IOPAGE address range with a single number, because the size of the IOPAGE is fixed at 8Kb.
* The bottom of the range is (2 ^ nRange) - IOPAGE_LENGTH, and the top is (2 ^ nRange) - 1.
*
* @this {BusPDP11}
* @param {number} nRange (16, 18 or 22)
*/
BusPDP11.prototype.setIOPageRange = function(nRange)
{
if (nRange != this.nIOPageRange) {
var addr;
if (this.nIOPageRange) {
addr = (1 << this.nIOPageRange) - BusPDP11.IOPAGE_LENGTH;
if (!this.removeMemory(addr, BusPDP11.IOPAGE_LENGTH)) return;
this.nIOPageRange = 0;
}
addr = (1 << nRange) - BusPDP11.IOPAGE_LENGTH;
if (!this.addMemory(addr, BusPDP11.IOPAGE_LENGTH, MemoryPDP11.TYPE.CONTROLLER, this)) return;
this.nIOPageRange = nRange;
}
};
/**
@ -390,6 +429,9 @@ BusPDP11.prototype.initMemory = function()
*/
BusPDP11.prototype.getControllerBuffer = function(addr)
{
/*
* No buffer is required; all accesses go to a registered I/O handler or the bit-bucket.
*/
return [null, 0];
};

View file

@ -197,11 +197,6 @@ CPUPDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
if (control) this.cmp.setBinding(null, CPUPDP11.BUTTONS[i], control);
}
/*
* Attach the ChipSet component to the CPU so that it can be notified whenever the CPU stops and starts.
*/
this.chipset = null; // /** @type {ChipSetPDP11} */ (cmp.getMachineComponent("ChipSet"));
/*
* We've already saved the parmsCPU 'autoStart' setting, but there may be a machine (or URL) override.
*/
@ -210,12 +205,27 @@ CPUPDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
this.flags.autoStart = (sAutoStart == "true"? true : (sAutoStart == "false"? false : !!sAutoStart));
}
this.initBusComplete();
this.setReady();
};
/**
* initBusComplete()
*
* Stub for Bus finalization (overridden by the CPUStatePDP11 component).
*
* @this {CPUPDP11}
*/
CPUPDP11.prototype.initBusComplete = function()
{
};
/**
* reset()
*
* Stub for reset notification (overridden by the CPUStatePDP11 component).
*
* @this {CPUPDP11}
*/
CPUPDP11.prototype.reset = function()
@ -225,7 +235,7 @@ CPUPDP11.prototype.reset = function()
/**
* save()
*
* This is a placeholder for save support (overridden by the CPUStatePDP11 component).
* Stub for save support (overridden by the CPUStatePDP11 component).
*
* @this {CPUPDP11}
* @return {Object|null}
@ -238,7 +248,7 @@ CPUPDP11.prototype.save = function()
/**
* restore(data)
*
* This is a placeholder for restore support (overridden by the CPUStatePDP11 component).
* Stub for restore support (overridden by the CPUStatePDP11 component).
*
* @this {CPUPDP11}
* @param {Object} data
@ -552,40 +562,6 @@ CPUPDP11.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
return fBound;
};
/**
* setBurstCycles(nCycles)
*
* This function is used by the ChipSet component whenever a very low timer count is set,
* in anticipation of the timer requiring an update sooner than the normal nCyclesPerYield
* period in runCPU() would normally provide.
*
* NOTE: In this context, "timer" refers to a timer chip (eg, an Intel 8253) being emulated by
* by the ChipSet component, not the timers managed by the CPU (eg, addTimer(), setTimer(), etc).
*
* @this {CPUPDP11}
* @param {number} nCycles is the target number of cycles to drop the current burst to
* @return {boolean}
*/
CPUPDP11.prototype.setBurstCycles = function(nCycles)
{
if (this.flags.running) {
var nDelta = this.nStepCycles - nCycles;
/*
* NOTE: If nDelta is negative, we will actually be increasing nStepCycles and nBurstCycles.
* Which is OK, but if we're also taking snapshots of the cycle counts, to make sure that instruction
* costs are being properly assessed, then we need to update nSnapCycles as well.
*
* TODO: If the delta is negative, we could simply ignore the request, but we must first carefully
* consider the impact on the ChipSet timers, if any.
*/
// if (DEBUG) this.nSnapCycles -= nDelta;
this.nStepCycles -= nDelta;
this.nBurstCycles -= nDelta;
return true;
}
return false;
};
/**
* addCycles(nCycles, fEndStep)
*
@ -1183,7 +1159,6 @@ CPUPDP11.prototype.startCPU = function(fUpdateFocus)
this.setSpeed();
this.flags.running = true;
this.flags.starting = true;
if (this.chipset) this.chipset.start();
var controlRun = this.bindings["run"];
if (controlRun) controlRun.textContent = "Halt";
if (this.cmp) {
@ -1227,7 +1202,6 @@ CPUPDP11.prototype.stopCPU = function(fComplete)
this.addCycles(this.nRunCycles);
this.nRunCycles = 0;
this.flags.running = false;
if (this.chipset) this.chipset.stop();
var controlRun = this.bindings["run"];
if (controlRun) controlRun.textContent = "Run";
if (this.cmp) {

View file

@ -98,6 +98,17 @@ Component.subclass(CPUStatePDP11, CPUPDP11);
*/
var InterruptEvent;
/**
* initBusComplete()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.initBusComplete = function()
{
this.assert(this.bus);
this.setMemoryAccess();
};
/**
* initProcessor()
*
@ -216,11 +227,12 @@ CPUStatePDP11.prototype.resetRegs = function()
/** @type {Array.<InterruptEvent>} */
this.interruptQueue = [];
this.opFlags |= PDP11.OPFLAG.INTQ;
this.initMemoryAccess();
if (this.bus) this.setMemoryAccess();
};
/**
* initMemoryAccess()
* setMemoryAccess()
*
* Define handlers and DSPACE setting appropriate for the current MMU mode, in order to eliminate
* unnecessary calls to mapVirtualToPhysical().
@ -231,18 +243,20 @@ CPUStatePDP11.prototype.resetRegs = function()
*
* @this {CPUStatePDP11}
*/
CPUStatePDP11.prototype.initMemoryAccess = function()
CPUStatePDP11.prototype.setMemoryAccess = function()
{
if (this.mmuEnable) {
this.addrDSpace = PDP11.ACCESS.DSPACE;
this.getAddrByMode = this.getVirtualAddrByMode;
this.readWordFromAddr = this.readWordFromVirtual;
this.writeWordToAddr = this.writeWordToVirtual;
this.bus.setIOPageRange((this.regMMR3 & PDP11.MMR3.MMU_22BIT)? 22 : 18);
} else {
this.addrDSpace = 0;
this.getAddrByMode = this.getPhysicalAddrByMode;
this.readWordFromAddr = this.readWordFromPhysical;
this.writeWordToAddr = this.writeWordToPhysical;
this.bus.setIOPageRange(16);
}
};
@ -265,19 +279,23 @@ CPUStatePDP11.prototype.getMMR0 = function()
*/
CPUStatePDP11.prototype.setMMR0 = function(newMMR0)
{
this.regMMR0 = newMMR0 &= 0xf381;
this.mmuLastMode = (newMMR0 >> 5) & 3;
this.mmuLastPage = (newMMR0 >> 1) & 0xf;
if (newMMR0 & 0x101) {
if (newMMR0 & 0x1) {
this.mmuEnable = PDP11.ACCESS.READ | PDP11.ACCESS.WRITE;
} else {
this.mmuEnable = PDP11.ACCESS.WRITE;
newMMR0 &= 0xf381;
if (this.regMMR0 != newMMR0) {
this.regMMR0 = newMMR0;
this.mmuLastMode = (newMMR0 >> 5) & 3;
this.mmuLastPage = (newMMR0 >> 1) & 0xf;
var mmuEnable = 0;
if (newMMR0 & 0x101) {
mmuEnable = PDP11.ACCESS.WRITE;
if (newMMR0 & 0x1) {
mmuEnable |= PDP11.ACCESS.READ;
}
}
if (this.mmuEnable != mmuEnable) {
this.mmuEnable = mmuEnable;
this.setMemoryAccess();
}
} else {
this.mmuEnable = 0;
}
this.initMemoryAccess();
};
/**
@ -309,13 +327,16 @@ CPUStatePDP11.prototype.setMMR3 = function(newMMR3)
if (this.cpuType !== 70) {
newMMR3 &= ~(PDP11.MMR3.MMU_22BIT | PDP11.MMR3.UNIBUS_MAP);
}
this.regMMR3 = newMMR3;
if (this.regMMR3 & PDP11.MMR3.MMU_22BIT) {
this.mmuMask = 0x3fffff;
this.mmuMemorySize = BusPDP11.MAX_MEMORY;
} else {
this.mmuMask = 0x3ffff;
this.mmuMemorySize = BusPDP11.IOPAGE_18BIT;
if (this.regMMR3 != newMMR3) {
this.regMMR3 = newMMR3;
if (newMMR3 & PDP11.MMR3.MMU_22BIT) {
this.mmuMask = 0x3fffff;
this.mmuMemorySize = BusPDP11.MAX_MEMORY;
} else {
this.mmuMask = 0x3ffff;
this.mmuMemorySize = BusPDP11.IOPAGE_18BIT;
}
this.setMemoryAccess();
}
};
@ -1515,7 +1536,7 @@ CPUStatePDP11.prototype.getAddress = function(mode, reg, accessFlags)
/**
* getPhysicalAddrByMode(mode, reg, accessFlags)
*
* This is a handler set up by initMemoryAccess(). All calls should go through getAddrByMode().
* This is a handler set up by setMemoryAccess(). All calls should go through getAddrByMode().
*
* @this {CPUStatePDP11}
* @param {number} mode
@ -1531,7 +1552,7 @@ CPUStatePDP11.prototype.getPhysicalAddrByMode = function(mode, reg, accessFlags)
/**
* getVirtualAddrByMode(mode, reg, accessFlags)
*
* This is a handler set up by initMemoryAccess(). All calls should go through getAddrByMode().
* This is a handler set up by setMemoryAccess(). All calls should go through getAddrByMode().
*
* @this {CPUStatePDP11}
* @param {number} mode
@ -1547,7 +1568,7 @@ CPUStatePDP11.prototype.getVirtualAddrByMode = function(mode, reg, accessFlags)
/**
* readWordFromPhysical(physicalAddress)
*
* This is a handler set up by initMemoryAccess(). All calls should go through readWordFromAddr().
* This is a handler set up by setMemoryAccess(). All calls should go through readWordFromAddr().
*
* @this {CPUStatePDP11}
* @param {number} physicalAddress
@ -1561,7 +1582,7 @@ CPUStatePDP11.prototype.readWordFromPhysical = function(physicalAddress)
/**
* readWordFromVirtual(virtualAddress)
*
* This is a handler set up by initMemoryAccess(). All calls should go through readWordFromAddr().
* This is a handler set up by setMemoryAccess(). All calls should go through readWordFromAddr().
*
* @this {CPUStatePDP11}
* @param {number} virtualAddress (input address is 17 bit (I&D))
@ -1575,7 +1596,7 @@ CPUStatePDP11.prototype.readWordFromVirtual = function(virtualAddress)
/**
* writeWordToPhysical(physicalAddress, data)
*
* This is a handler set up by initMemoryAccess(). All calls should go through writeWordToAddr().
* This is a handler set up by setMemoryAccess(). All calls should go through writeWordToAddr().
*
* @this {CPUStatePDP11}
* @param {number} physicalAddress
@ -1589,7 +1610,7 @@ CPUStatePDP11.prototype.writeWordToPhysical = function(physicalAddress, data)
/**
* writeWordToVirtual(virtualAddress, data)
*
* This is a handler set up by initMemoryAccess(). All calls should go through writeWordToAddr().
* This is a handler set up by setMemoryAccess(). All calls should go through writeWordToAddr().
*
* @this {CPUStatePDP11}
* @param {number} virtualAddress (input address is 17 bit (I&D))
@ -1659,7 +1680,7 @@ CPUStatePDP11.prototype.writeWordToPrevSpace = function(opCode, accessFlags, dat
* TODO: Consider replacing the following code with writeWordToAddr(), by adding optional mmuMode
* parameters for each of the discrete mapVirtualToPhysical() and bus.setWord() operations, because
* as it stands, this is the only remaining call to mapVirtualToPhysical() outside of our
* initMemoryAccess() handlers.
* setMemoryAccess() handlers.
*/
this.mmuMode = (this.regPSW >> 12) & 3;
addr = this.mapVirtualToPhysical(addr | (accessFlags & PDP11.ACCESS.DSPACE), PDP11.ACCESS.WRITE);

View file

@ -79,16 +79,24 @@ var BYTEARRAYS = false;
var TYPEDARRAYS = (typeof ArrayBuffer !== 'undefined');
/**
* WORDBUS forces the Bus and Memory interfaces to assume even addresses when accessing words. Since PDPjs inherited
* its Bus component from PCx86, it originally supported both aligned and unaligned word accesses by default, but since
* the PDP-11 requires aligned (even) word addresses, we can turn off support for unaligned accesses and get some
* performance gains.
* MEMFAULT forces the Memory interfaces to signal a CPU fault when a word is accessed using an odd (unaligned) address.
*
* When WORDBUS is true, the Bus and Memory components simply ignore the low bit of word addresses, because it is the
* CPU, not the Bus, that's responsible for validating addresses and generating the appropriate traps.
* Since PDPjs inherited its Bus component from PCx86, it included support for both aligned and unaligned word accesses
* by default. However, the PDP-11 adds a wrinkle: when an odd address is used to access a memory word, a BUS_ERROR trap
* must be generated. Note that odd IOPAGE word accesses are fine; this only affects the Memory component.
*
* Don't worry that the source code looks MORE complicated rather than LESS with the additional WORDBUS checks, because
* the Closure Compiler eliminates those checks and throws away the (unreachable) code blocks that deal with unaligned
* When the MMU is enabled, these checks may also be performed at a higher level, eliminating the need for them at the
* physical memory level.
*/
var MEMFAULT = true;
/**
* WORDBUS turns off support for unaligned memory words. Whereas MEMFAULT necessarily slows down memory word accesses
* slightly, WORDBUS is able to speed them up slightly, by assuming that all word accesses (which didn't fault) must be
* aligned. This affects all word accesses, even IOPAGE accesses, because it also eliminates cross-block boundary checks.
*
* Don't worry that the source code looks MORE complicated rather than LESS with the additional MEMFAULT and WORDBUS checks,
* because the Closure Compiler eliminates those checks and throws away the (unreachable) code blocks that deal with unaligned
* accesses.
*/
var WORDBUS = true;
@ -109,6 +117,7 @@ var PDP11 = {
MAXDEBUG: MAXDEBUG, // shared
PRIVATE: PRIVATE, // shared
TYPEDARRAYS:TYPEDARRAYS,
MEMFAULT: MEMFAULT,
WORDBUS: WORDBUS,
SITEHOST: SITEHOST, // shared
XMLVERSION: XMLVERSION, // shared

View file

@ -623,7 +623,7 @@ MemoryPDP11.prototype = {
* @return {number}
*/
readWordMemory: function readWordMemory(off, addr) {
if (PDP11.WORDBUS && (off & 0x1)) {
if (PDP11.MEMFAULT && (off & 0x1)) {
this.cpu.fault(addr);
}
if (BYTEARRAYS) {
@ -667,7 +667,7 @@ MemoryPDP11.prototype = {
* @param {number} addr
*/
writeWordMemory: function writeWordMemory(off, w, addr) {
if (PDP11.WORDBUS && (off & 0x1)) {
if (PDP11.MEMFAULT && (off & 0x1)) {
this.cpu.fault(addr);
}
if (BYTEARRAYS) {
@ -777,7 +777,7 @@ MemoryPDP11.prototype = {
* @return {number}
*/
readWordBE: function readWordBE(off, addr) {
if (PDP11.WORDBUS && (off & 0x1)) {
if (PDP11.MEMFAULT && (off & 0x1)) {
this.cpu.fault(addr);
}
return this.dv.getUint16(off, true);
@ -791,15 +791,15 @@ MemoryPDP11.prototype = {
* @return {number}
*/
readWordLE: function readWordLE(off, addr) {
var w;
if (PDP11.MEMFAULT && (off & 0x1)) {
this.cpu.fault(addr);
}
/*
* TODO: For non-WORDBUS machines, it remains to be seen if there's any advantage to checking the offset
* for an aligned read vs. always reading the bytes separately.
*/
var w;
if (PDP11.WORDBUS && (off & 0x1)) {
this.cpu.fault(addr);
}
if (!(off & 0x1)) {
if (PDP11.WORDBUS || !(off & 0x1)) {
w = this.aw[off >> 1];
} else {
w = this.ab[off] | (this.ab[off+1] << 8);
@ -845,7 +845,7 @@ MemoryPDP11.prototype = {
* @param {number} w
*/
writeWordBE: function writeWordBE(off, w, addr) {
if (PDP11.WORDBUS && (off & 0x1)) {
if (PDP11.MEMFAULT && (off & 0x1)) {
this.cpu.fault(addr);
}
this.dv.setUint16(off, w, true);
@ -860,14 +860,14 @@ MemoryPDP11.prototype = {
* @param {number} w
*/
writeWordLE: function writeWordLE(off, w, addr) {
if (PDP11.MEMFAULT && (off & 0x1)) {
this.cpu.fault(addr);
}
/*
* TODO: For non-WORDBUS machines, it remains to be seen if there's any advantage to checking the offset
* for an aligned write vs. always writing the bytes separately.
*/
if (PDP11.WORDBUS && (off & 0x1)) {
this.cpu.fault(addr);
}
if (!(off & 0x1)) {
if (PDP11.WORDBUS || !(off & 0x1)) {
this.aw[off >> 1] = w;
} else {
this.ab[off] = w;