New improved I/O handler functions for the PDP-11

This commit is contained in:
Jeff 2016-09-21 12:44:25 -07:00 committed by Jeff Parsons
commit dfe367f53d
18 changed files with 847 additions and 1059 deletions

View file

@ -59,11 +59,6 @@ if (NODE) {
* addMemory(). If the component needs something more than simple read/write storage,
* it must provide a custom controller.
*
* All port (I/O) operations are defined by external handlers; they register with us,
* and we manage those registrations and provide support for I/O breakpoints, but the
* only default I/O behavior we provide is ignoring writes to any unregistered output
* ports and returning 0xff from any unregistered input ports.
*
* @constructor
* @extends Component
* @param {Object} parmsBus
@ -80,35 +75,16 @@ function Bus(parmsBus, cpu, dbg)
this.nBusWidth = parmsBus['busWidth'] || 16;
/*
* Compute all Bus memory block parameters, based on the width of the bus.
*
* Regarding blockTotal, we want to avoid using block overflow expressions like:
*
* iBlock < this.nBlockTotal? iBlock : 0
*
* As long as we know that blockTotal is a power of two (eg, 256 or 0x100, in the case of
* nBusWidth == 20 and blockSize == 4096), we can define blockMask as (blockTotal - 1) and
* rewrite the previous expression as:
*
* iBlock & this.nBlockMask
*
* Bus Property Old hard-coded values (when nBusWidth was always 20)
* ------------ ----------------------------------------------------
* this.nBusLimit 0xfffff
* this.nBusMask [same as busLimit]
* this.nBlockSize 4096
* this.nBlockLen (this.nBlockSize >> 2)
* this.nBlockShift 12
* this.nBlockLimit 0xfff
* this.nBlockTotal ((this.nBusLimit + this.nBlockSize) / this.nBlockSize) | 0
* this.nBlockMask (this.nBlockTotal - 1) [ie, 0xff]
*
* Note that we choose a nBlockShift value (and thus a physical memory block size) based on "buswidth":
* Compute all Bus6502 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
* the bus width. The following table summarizes our simplistic calculations.
*
* Bus Width Block Shift Block Size
* --------- ----------- ----------
* 16 bits (64Kb address space): 10 1Kb (64 maximum blocks)
* 18 bits (256Kb address space): 11 2Kb (128 maximum blocks)
* 20 bits (1Mb address space): 12 4Kb (256 maximum blocks)
* 22 bits (4Mb address space): 13 8Kb (512 maximum blocks)
* 24 bits (16Mb address space): 14 16Kb (1K maximum blocks)
* 32 bits (4Gb address space); 15 32Kb (128K maximum blocks)
*
@ -119,7 +95,9 @@ function Bus(parmsBus, cpu, dbg)
*/
this.addrTotal = Math.pow(2, this.nBusWidth);
this.nBusLimit = this.nBusMask = (this.addrTotal - 1) | 0;
this.nBlockShift = (this.nBusWidth <= 16)? 10 : ((this.nBusWidth <= 20)? 12 : (this.nBusWidth <= 24? 14 : 15));
this.nBlockShift = (this.nBusWidth >> 1) + 2;
if (this.nBlockShift < 10) this.nBlockShift = 10;
if (this.nBlockShift > 15) this.nBlockShift = 15;
this.nBlockSize = 1 << this.nBlockShift;
this.nBlockLen = this.nBlockSize >> 2;
this.nBlockLimit = this.nBlockSize - 1;
@ -127,39 +105,6 @@ function Bus(parmsBus, cpu, dbg)
this.nBlockMask = this.nBlockTotal - 1;
this.assert(this.nBlockMask <= Bus.BlockInfo.num.mask);
/*
* Lists of I/O notification functions: aPortInputNotify and aPortOutputNotify are arrays, indexed by
* port, of sub-arrays which contain:
*
* [0]: registered function to call for every I/O access
*
* The registered function is called with the port address, and if the access was triggered by the CPU,
* the instruction pointer (IP) at the point of access.
*
* WARNING: Unlike the (old) read and write memory notification functions, these support only one
* pair of input/output functions per port. A more sophisticated architecture could support a list
* of chained functions across multiple components, but I doubt that will be necessary here.
*
* UPDATE: The Debugger now piggy-backs on these arrays to indicate ports for which it wants notification
* of I/O. In those cases, the registered component/function elements may or may not be set, but the
* following additional element will be set:
*
* [1]: true to break on I/O, false to ignore I/O
*
* The false case is important if fPortInputBreakAll and/or fPortOutputBreakAll is set, because it allows the
* Debugger to selectively ignore specific ports.
*/
this.aPortInputNotify = [];
this.aPortOutputNotify = [];
this.fPortInputBreakAll = this.fPortOutputBreakAll = false;
/*
* By default, all I/O ports are 1 byte wide; ports that are wider must add themselves to one or both of
* these lists, using addPortInputWidth() and/or addPortOutputWidth().
*/
this.aPortInputWidth = [];
this.aPortOutputWidth = [];
/*
* Allocate empty Memory blocks to span the entire physical address space.
*/
@ -177,38 +122,6 @@ Bus.ERROR = {
REM_MEM_BADRANGE: 5
};
/**
* @typedef {number}
*/
var BlockInfo;
/**
* This defines the BlockInfo bit fields used by scanMemory() when it creates the aBlocks array.
*
* @typedef {{
* num: BitField,
* count: BitField,
* btmod: BitField,
* type: BitField
* }}
*/
Bus.BlockInfo = usr.defineBitFields({num:20, count:8, btmod:1, type:3});
/**
* BusInfo object definition (returned by scanMemory())
*
* cbTotal: total bytes allocated
* cBlocks: total Memory blocks allocated
* aBlocks: array of allocated Memory block numbers
*
* @typedef {{
* cbTotal: number,
* cBlocks: number,
* aBlocks: Array.<BlockInfo>
* }}
*/
var BusInfo;
/**
* initMemory()
*
@ -365,6 +278,42 @@ Bus.prototype.cleanMemory = function(addr, size)
return fClean;
};
/*
* Data types used by scanMemory()
*/
/**
* @typedef {number}
*/
var BlockInfo;
/**
* This defines the BlockInfo bit fields used by scanMemory() when it creates the aBlocks array.
*
* @typedef {{
* num: BitField,
* count: BitField,
* btmod: BitField,
* type: BitField
* }}
*/
Bus.BlockInfo = usr.defineBitFields({num:20, count:8, btmod:1, type:3});
/**
* BusInfo object definition (returned by scanMemory())
*
* cbTotal: total bytes allocated
* cBlocks: total Memory blocks allocated
* aBlocks: array of allocated Memory block numbers
*
* @typedef {{
* cbTotal: number,
* cBlocks: number,
* aBlocks: Array.<BlockInfo>
* }}
*/
var BusInfo;
/**
* scanMemory(info, addr, size)
*
@ -741,309 +690,6 @@ Bus.prototype.restoreMemory = function(a)
return true;
};
/**
* addPortInputBreak(port)
*
* @this {Bus}
* @param {number} [port]
* @return {boolean} true if break on port input enabled, false if disabled
*/
Bus.prototype.addPortInputBreak = function(port)
{
if (port === undefined) {
this.fPortInputBreakAll = !this.fPortInputBreakAll;
return this.fPortInputBreakAll;
}
if (this.aPortInputNotify[port] === undefined) {
this.aPortInputNotify[port] = [null, false];
}
this.aPortInputNotify[port][1] = !this.aPortInputNotify[port][1];
return this.aPortInputNotify[port][1];
};
/**
* addPortInputNotify(start, end, fn)
*
* Add a port input-notification handler to the list of such handlers.
*
* @this {Bus}
* @param {number} start port address
* @param {number} end port address
* @param {function(number,number)} fn is called with the port and IP values at the time of the input
*/
Bus.prototype.addPortInputNotify = function(start, end, fn)
{
if (fn !== undefined) {
for (var port = start; port <= end; port++) {
if (this.aPortInputNotify[port] !== undefined) {
Component.warning("Input port " + str.toHexWord(port) + " already registered");
continue;
}
this.aPortInputNotify[port] = [fn, false];
if (MAXDEBUG) this.log("addPortInputNotify(" + str.toHexWord(port) + ")");
}
}
};
/**
* addPortInputTable(component, table, offset)
*
* Add port input-notification handlers from the specified table (a batch version of addPortInputNotify)
*
* @this {Bus}
* @param {Component} component
* @param {Object} table
* @param {number} [offset] is an optional port offset
*/
Bus.prototype.addPortInputTable = function(component, table, offset)
{
if (offset === undefined) offset = 0;
for (var port in table) {
this.addPortInputNotify(+port + offset, +port + offset, table[port].bind(component));
}
};
/**
* addPortInputWidth(port, size)
*
* By default, all input ports are 1 byte wide; ports that are wider must call this function.
*
* @this {Bus}
* @param {number} port
* @param {number} size (1, 2 or 4)
*/
Bus.prototype.addPortInputWidth = function(port, size)
{
this.aPortInputWidth[port] = size;
};
/**
* checkPortInputNotify(port, size, addrIP)
*
* @this {Bus}
* @param {number} port
* @param {number} size (1, 2 or 4)
* @param {number} [addrIP] is the IP value at the time of the input
* @return {number} simulated port data
*
* NOTE: It seems that parts of the ROM BIOS (like the RS-232 probes around F000:E5D7 in the 5150 BIOS)
* assume that ports for non-existent hardware return 0xff rather than 0x00, hence my new default (0xff) below.
*/
Bus.prototype.checkPortInputNotify = function(port, size, addrIP)
{
var data = 0, shift = 0;
while (size > 0) {
var aNotify = this.aPortInputNotify[port];
var sizePort = this.aPortInputWidth[port] || 1;
var maskPort = (sizePort == 1? 0xff : (sizePort == 2? 0xffff : -1));
var dataPort = maskPort;
/*
* TODO: We need to decide what to do about 8-bit I/O to a 16-bit port (ditto for 16-bit I/O
* to a 32-bit port). We probably should pass the size through to the aNotify[0] handler,
* and let it decide what to do, but I don't feel like changing all the I/O handlers right now.
* The good news, at least, is that the 8-bit handlers would not have to do anything special.
* This assert will warn us if this is a pressing need.
*/
this.assert(size >= sizePort);
if (aNotify !== undefined) {
if (aNotify[0]) {
dataPort = aNotify[0](port, addrIP);
if (dataPort === undefined) {
dataPort = maskPort;
} else {
dataPort &= maskPort;
}
}
if (DEBUGGER && this.dbg && this.fPortInputBreakAll != aNotify[1]) {
this.dbg.checkPortInput(port, size, dataPort);
}
}
else {
if (DEBUGGER && this.dbg) {
this.dbg.messageIO(this, port, null, addrIP);
if (this.fPortInputBreakAll) this.dbg.checkPortInput(port, size, dataPort);
}
}
data |= dataPort << shift;
shift += (sizePort << 3);
port += sizePort;
size -= sizePort;
}
this.assert(!size);
return data;
};
/**
* removePortInputNotify(start, end)
*
* Remove port input-notification handler(s) (to be ENABLED later if needed)
*
* @this {Bus}
* @param {number} start address
* @param {number} end address
*
Bus.prototype.removePortInputNotify = function(start, end)
{
for (var port = start; port < end; port++) {
if (this.aPortInputNotify[port]) {
delete this.aPortInputNotify[port];
}
}
};
*/
/**
* addPortOutputBreak(port)
*
* @this {Bus}
* @param {number} [port]
* @return {boolean} true if break on port output enabled, false if disabled
*/
Bus.prototype.addPortOutputBreak = function(port)
{
if (port === undefined) {
this.fPortOutputBreakAll = !this.fPortOutputBreakAll;
return this.fPortOutputBreakAll;
}
if (this.aPortOutputNotify[port] === undefined) {
this.aPortOutputNotify[port] = [null, false];
}
this.aPortOutputNotify[port][1] = !this.aPortOutputNotify[port][1];
return this.aPortOutputNotify[port][1];
};
/**
* addPortOutputNotify(start, end, fn)
*
* Add a port output-notification handler to the list of such handlers.
*
* @this {Bus}
* @param {number} start port address
* @param {number} end port address
* @param {function(number,number)} fn is called with the port and IP values at the time of the output
*/
Bus.prototype.addPortOutputNotify = function(start, end, fn)
{
if (fn !== undefined) {
for (var port = start; port <= end; port++) {
if (this.aPortOutputNotify[port] !== undefined) {
Component.warning("Output port " + str.toHexWord(port) + " already registered");
continue;
}
this.aPortOutputNotify[port] = [fn, false];
if (MAXDEBUG) this.log("addPortOutputNotify(" + str.toHexWord(port) + ")");
}
}
};
/**
* addPortOutputTable(component, table, offset)
*
* Add port output-notification handlers from the specified table (a batch version of addPortOutputNotify)
*
* @this {Bus}
* @param {Component} component
* @param {Object} table
* @param {number} [offset] is an optional port offset
*/
Bus.prototype.addPortOutputTable = function(component, table, offset)
{
if (offset === undefined) offset = 0;
for (var port in table) {
this.addPortOutputNotify(+port + offset, +port + offset, table[port].bind(component));
}
};
/**
* addPortOutputWidth(port, size)
*
* By default, all output ports are 1 byte wide; ports that are wider must call this function.
*
* @this {Bus}
* @param {number} port
* @param {number} size (1, 2 or 4)
*/
Bus.prototype.addPortOutputWidth = function(port, size)
{
this.aPortOutputWidth[port] = size;
};
/**
* checkPortOutputNotify(port, size, data, addrIP)
*
* @this {Bus}
* @param {number} port
* @param {number} size
* @param {number} data
* @param {number} [addrIP] is the IP value at the time of the output
*/
Bus.prototype.checkPortOutputNotify = function(port, size, data, addrIP)
{
var shift = 0;
while (size > 0) {
var aNotify = this.aPortOutputNotify[port];
var sizePort = this.aPortOutputWidth[port] || 1;
var maskPort = (sizePort == 1? 0xff : (sizePort == 2? 0xffff : -1));
var dataPort = (data >>>= shift) & maskPort;
/*
* TODO: We need to decide what to do about 8-bit I/O to a 16-bit port (ditto for 16-bit I/O
* to a 32-bit port). We probably should pass the size through to the aNotify[0] handler,
* and let it decide what to do, but I don't feel like changing all the I/O handlers right now.
* The good news, at least, is that the 8-bit handlers would not have to do anything special.
* This assert will warn us if this is a pressing need.
*/
this.assert(size >= sizePort);
if (aNotify !== undefined) {
if (aNotify[0]) {
aNotify[0](port, dataPort, addrIP);
}
if (DEBUGGER && this.dbg && this.fPortOutputBreakAll != aNotify[1]) {
this.dbg.checkPortOutput(port, size, dataPort);
}
}
else {
if (DEBUGGER && this.dbg) {
this.dbg.messageIO(this, port, dataPort, addrIP);
if (this.fPortOutputBreakAll) this.dbg.checkPortOutput(port, size, dataPort);
}
}
shift += (sizePort << 3);
port += sizePort;
size -= sizePort;
}
this.assert(!size);
};
/**
* removePortOutputNotify(start, end)
*
* Remove port output-notification handler(s) (to be ENABLED later if needed)
*
* @this {Bus}
* @param {number} start address
* @param {number} end address
*
Bus.prototype.removePortOutputNotify = function(start, end)
{
for (var port = start; port < end; port++) {
if (this.aPortOutputNotify[port]) {
delete this.aPortOutputNotify[port];
}
}
};
*/
/**
* reportError(op, addr, size, fQuiet)
*

View file

@ -807,18 +807,45 @@ Memory.prototype = {
/*
* This is the effective definition of afnNone, but we need not fully define it, because setAccess()
* uses these defaults when any of the 6 handlers (ie, 3 read handlers and 3 write handlers) are undefined.
* uses these defaults when any of the 4 handlers (ie, 2 byte handlers and 2 short handlers) are undefined.
*
Memory.afnNone = [Memory.prototype.readNone, Memory.prototype.readShortDefault, Memory.prototype.writeNone, Memory.prototype.writeShortDefault];
Memory.afnNone = [
Memory.prototype.readNone,
Memory.prototype.writeNone,
Memory.prototype.readShortDefault,
Memory.prototype.writeShortDefault
];
*/
Memory.afnNone = [];
Memory.afnNone = [];
Memory.afnMemory = [Memory.prototype.readByteMemory, Memory.prototype.readShortMemory, Memory.prototype.writeByteMemory, Memory.prototype.writeShortMemory];
Memory.afnChecked = [Memory.prototype.readByteChecked, Memory.prototype.readShortChecked, Memory.prototype.writeByteChecked, Memory.prototype.writeShortChecked];
Memory.afnMemory = [
Memory.prototype.readByteMemory,
Memory.prototype.writeByteMemory,
Memory.prototype.readShortMemory,
Memory.prototype.writeShortMemory
];
Memory.afnChecked = [
Memory.prototype.readByteChecked,
Memory.prototype.writeByteChecked,
Memory.prototype.readShortChecked,
Memory.prototype.writeShortChecked
];
if (TYPEDARRAYS) {
Memory.afnArrayBE = [Memory.prototype.readByteBE, Memory.prototype.readShortBE, Memory.prototype.writeByteBE, Memory.prototype.writeShortBE];
Memory.afnArrayLE = [Memory.prototype.readByteLE, Memory.prototype.readShortLE, Memory.prototype.writeByteLE, Memory.prototype.writeShortLE];
Memory.afnArrayBE = [
Memory.prototype.readByteBE,
Memory.prototype.writeByteBE,
Memory.prototype.readShortBE,
Memory.prototype.writeShortBE
];
Memory.afnArrayLE = [
Memory.prototype.readByteLE,
Memory.prototype.writeByteLE,
Memory.prototype.readShortLE,
Memory.prototype.writeShortLE
];
}
if (NODE) module.exports = Memory;