diff --git a/devices/pc/bios/compaq/deskpro386/README.md b/devices/pc/bios/compaq/deskpro386/README.md index 3268f9450..cc7a11a99 100644 --- a/devices/pc/bios/compaq/deskpro386/README.md +++ b/devices/pc/bios/compaq/deskpro386/README.md @@ -33,11 +33,11 @@ ROMs for their earliest 80386-based systems. cd 1988-01-28 filedump --file=109592-001.hex --merge=109591-001.hex --output=1988-01-28.json -For a more human-readable dump, use this command: +For a more human-readable dump, use the `--comments` option: - filedump --file=109592-001.hex --merge=109591-001.hex --comments + filedump --file=109592-001.hex --merge=109591-001.hex --output=1988-01-28.dump --comments -And for those who prefer a binary file, the FileDump API can be used to recreate binary data from JSON data: +And for those who want a binary file, the FileDump API can be used to recreate binary data from JSON data: > [http://www.pcjs.org/api/v1/dump?file=http://www.pcjs.org/devices/pc/bios/compaq/deskpro386/1988-01-28/1988-01-28.json&format=rom](http://www.pcjs.org/api/v1/dump?file=http://www.pcjs.org/devices/pc/bios/compaq/deskpro386/1988-01-28/1988-01-28.json&format=rom) @@ -48,6 +48,8 @@ from the two 16Kb BIN files provided by [Al Kossow](http://www.vintage-computer. filedump --file=private/109592-005.U11.bin --merge=private/109591-005.U13.bin --output=private/1989-04-14.rom --format=rom filedump --file=private/1989-04-14.rom --output=1989-04-14.json +Reading the ROMs +--- The *.hex* files for the 1988-01-28 DeskPro ROM were produced by running [eeprom_read](http://github.com/phooky/PROM/blob/master/tools/eeprom_read/eeprom_read.pde) on a [chipKIT Uno32](http://www.digilentinc.com/Products/Detail.cfm?NavPath=2,892,893&Prod=CHIPKIT-UNO32) Arduino-compatible prototyping board, and capturing the serial port output on my MacBook Pro -- as outlined in diff --git a/modules/pcjs/lib/bus.js b/modules/pcjs/lib/bus.js index de658ea38..ad0e42e47 100644 --- a/modules/pcjs/lib/bus.js +++ b/modules/pcjs/lib/bus.js @@ -34,29 +34,13 @@ if (typeof module !== 'undefined') { var str = require("../../shared/lib/strlib"); + var usr = require("../../shared/lib/usrlib"); var Component = require("../../shared/lib/component"); var Memory = require("./memory"); var Messages = require("./messages"); var State = require("./state"); } -/** - * BackTrack objects have the following properties: - * - * obj: a reference to the source object (eg, ROM object, Sector object) - * off: the offset within the source object that this object refers to - * slot: the slot (+1) in abtObjects which this object currently occupies - * refs: the number of memory references, as recorded by writeBackTrack() - * - * @typedef {{ - * obj: Object, - * off: number, - * slot: number, - * refs: number - * }} - */ -var BackTrack; - /** * Bus(cpu, dbg) * @@ -96,32 +80,32 @@ function Bus(parmsBus, cpu, dbg) this.nBusWidth = parmsBus['buswidth'] || 20; /* - * Compute all the Bus memory block addressing values that we rely on, based on the width of the bus. + * Compute all Bus memory block parameters, based on the width of the bus. * - * Regarding this.blockTotal, we want to avoid address-overflow-detection expressions like: + * Regarding blockTotal, we want to avoid using block overflow expressions like: * * iBlock < this.blockTotal? iBlock : 0 * - * and as long as we know that this.blockTotal is a power-of-two (eg, 256 or 0x100, in the case - * of nBusWidth == 20), we can define this.blockMask as (this.blockTotal - 1) and rewrite the previous - * expression as: + * 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.blockMask * - * While we *could* say that we mask addresses with this.busMask to simulate "A20 wrap", the simple - * fact is it relieves us from bounds-checking every aMemBlocks index. Address wrapping at the 1Mb - * boundary (ie, the A20 address line) is something we'll have to deal with more carefully on the 80286. + * Similarly, we mask addresses with busMask to enforce "A20 wrap" on 20-bit busses. + * For larger busses, A20 wrap can be simulated by either clearing bit 20 of busMask or by + * changing all the block entries for the 2nd megabyte to match those in the 1st megabyte. * - * New property Old property Old hard-coded values (when nBusWidth was always 20) - * ------------ ------------ ---------------------------------------------------- - * this.busLimit Bus.ADDR.LIMIT 0xfffff - * this.busMask N/A N/A - * this.blockSize Bus.BLOCK.SIZE 4096 - * this.blockLen Bus.BLOCK.LEN (this.blockSize >> 2) - * this.blockShift Bus.BLOCK.SHIFT 12 - * this.blockLimit Bus.BLOCK.LIMIT 0xfff - * this.blockTotal Bus.BLOCK.TOTAL ((this.busLimit + this.blockSize) / this.blockSize) | 0 - * this.blockMask Bus.BLOCK.MASK (this.blockTotal - 1) (ie, 0xff) + * Bus Property Old hard-coded values (when nBusWidth was always 20) + * ------------ ---------------------------------------------------- + * this.busLimit 0xfffff + * this.busMask N/A + * this.blockSize 4096 + * this.blockLen (this.blockSize >> 2) + * this.blockShift 12 + * this.blockLimit 0xfff + * this.blockTotal ((this.busLimit + this.blockSize) / this.blockSize) | 0 + * this.blockMask (this.blockTotal - 1) (ie, 0xff) * * Note that we choose a blockShift value (and thus a physical memory block size) based on "buswidth": * @@ -136,18 +120,17 @@ function Bus(parmsBus, cpu, dbg) * requirements. Your choices, for the moment, are either to ensure the allocations are performed in * order, or to choose smaller blockShift values (at the expense of a generating a larger block array). * - * Be aware that this is strictly a physical memory implementation detail, which should have no bearing - * on segment or page granularity of any future virtual memory implementation. + * However, if PAGEBLOCKS is set, then for a bus width of 32 bits, the block size is fixed at 4Kb. */ this.addrTotal = Math.pow(2, this.nBusWidth); this.busLimit = this.busMask = (this.addrTotal - 1) | 0; - this.blockShift = (this.nBusWidth <= 20? 12 : (this.nBusWidth <= 24? 14 : 15)); + this.blockShift = (PAGEBLOCKS && this.nBusWidth == 32 || this.nBusWidth <= 20)? 12 : (this.nBusWidth <= 24? 14 : 15); this.blockSize = 1 << this.blockShift; this.blockLen = this.blockSize >> 2; this.blockLimit = this.blockSize - 1; this.blockTotal = (this.addrTotal / this.blockSize) | 0; this.blockMask = this.blockTotal - 1; - this.assert(this.blockMask <= Bus.BLOCK.NUM_MASK); + this.assert(this.blockMask <= Bus.BlockInfo.num.mask); /* * Lists of I/O notification functions: aPortInputNotify and aPortOutputNotify are arrays, indexed by @@ -194,6 +177,23 @@ function Bus(parmsBus, cpu, dbg) Component.subclass(Bus); if (BACKTRACK) { + /** + * BackTrack object definition + * + * obj: reference to the source object (eg, ROM object, Sector object) + * off: the offset within the source object that this object refers to + * slot: the slot (+1) in abtObjects which this object currently occupies + * refs: the number of memory references, as recorded by writeBackTrack() + * + * @typedef {{ + * obj: Object, + * off: number, + * slot: number, + * refs: number + * }} + */ + var BackTrack; + /* * BackTrack indexes are 31-bit values, where bits 0-8 store an object offset (0-511) and bits 16-30 store * an object number (1-32767). Object number 0 is reserved for dynamic data (ie, data created independent @@ -245,20 +245,37 @@ if (BACKTRACK) { }; } -/* - * scanMemory() records block numbers in bits 0-16, a BackTrack "mod" bit in bit 17, and a block type at bit 28; - * the bits reserved for a count are not used. +/** + * @typedef {number} */ -Bus.BLOCK = { - NUM_SHIFT: 0, - NUM_MASK: 0x1ffff, - BTMOD_SHIFT: 17, - BTMOD_MASK: 0x1, - COUNT_SHIFT: 18, - COUNT_MASK: 0x03ff, - TYPE_SHIFT: 28, - TYPE_MASK: 0x7 -}; +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. + * }} + */ +var BusInfo; /** * initMemory() @@ -269,12 +286,10 @@ Bus.BLOCK = { */ Bus.prototype.initMemory = function() { + var block = new Memory(); this.aMemBlocks = new Array(this.blockTotal); for (var iBlock = 0; iBlock < this.blockTotal; iBlock++) { - var addr = iBlock * this.blockSize; - var block = this.aMemBlocks[iBlock] = new Memory(addr); - - if (DEBUGGER) block.setDebugInfo(this.cpu, this.dbg, this.blockSize); + this.aMemBlocks[iBlock] = block; } this.cpu.initMemory(this.aMemBlocks, this.blockShift, this.blockLimit, this.blockMask); this.cpu.setAddressMask(this.busMask); @@ -352,6 +367,7 @@ Bus.prototype.addMemory = function(addr, size, type, controller) var block = this.aMemBlocks[iBlock]; var addrBlock = iBlock * this.blockSize; var sizeBlock = size > this.blockSize? this.blockSize : size; + if (block && block.size) { if (block.type == type && block.controller == controller) { /* @@ -378,7 +394,7 @@ Bus.prototype.addMemory = function(addr, size, type, controller) return this.reportError(1, addr, size); } block = this.aMemBlocks[iBlock++] = new Memory(addr, sizeBlock, this.blockSize, type, controller); - if (DEBUGGER) block.setDebugInfo(this.cpu, this.dbg, this.blockSize); + if (DEBUGGER && this.dbg) block.setDebugInfo(this.cpu, this.dbg, addr, this.blockSize); size -= sizeBlock; addr = addrBlock + this.blockSize; } @@ -412,49 +428,38 @@ Bus.prototype.cleanMemory = function(addr, size) }; /** - * scanMemory(stats, addr, size) + * scanMemory(info, addr, size) * - * Returns a Stats object for the specified address range with the following properties: - * - * cbTotal: total bytes allocated - * cBlocks: total Memory blocks allocated - * aBlocks: array of allocated Memory block numbers - * - * aBlocks is preallocated to its maximum size, so don't rely on its length; at any given moment, - * only the first cBlocks entries will be valid. + * Returns a BusInfo object for the specified address range. * * @this {Bus} - * @param {Object} [stats] previous stats, if any + * @param {Object} [info] previous BusInfo, if any * @param {number} [addr] starting address of range (0 if none provided) * @param {number} [size] size of range, in bytes (up to end of address space if none provided) - * @return {Object} updated stats (or new stats if no previous stats provided) + * @return {Object} updated info (or new info if no previous info provided) */ -Bus.prototype.scanMemory = function(stats, addr, size) +Bus.prototype.scanMemory = function(info, addr, size) { if (addr == null) addr = 0; if (size == null) size = (this.addrTotal - addr) | 0; - if (stats == null) stats = {cbTotal: 0, cBlocks: 0, aBlocks: new Array(this.blockTotal)}; + if (info == null) info = {cbTotal: 0, cBlocks: 0, aBlocks: []}; var iBlock = addr >>> this.blockShift; var iBlockMax = ((addr + size - 1) >>> this.blockShift); - stats.cbTotal = 0; - stats.cBlocks = 0; + info.cbTotal = 0; + info.cBlocks = 0; while (iBlock <= iBlockMax) { var block = this.aMemBlocks[iBlock]; - stats.cbTotal += block.size; + info.cbTotal += block.size; if (block.size) { - var nBlock = iBlock; - nBlock |= (block.type << Bus.BLOCK.TYPE_SHIFT); - if (BACKTRACK) { - var fMod = block.modBackTrack(false); - if (fMod) nBlock |= (1 << Bus.BLOCK.BTMOD_SHIFT); - } - stats.aBlocks[stats.cBlocks++] = nBlock; + var btmod = (BACKTRACK && block.modBackTrack(false)? 1 : 0); + info.aBlocks.push(usr.initBitFields(Bus.BlockInfo, iBlock, 0, btmod, block.type)); + info.cBlocks++ } iBlock++; } - return stats; + return info; }; /** @@ -574,7 +579,7 @@ Bus.prototype.removeMemory = function(addr, size) while (size > 0) { addr = iBlock * this.blockSize; var block = this.aMemBlocks[iBlock++] = new Memory(addr); - if (DEBUGGER) block.setDebugInfo(this.cpu, this.dbg, this.blockSize); + if (DEBUGGER && this.dbg) block.setDebugInfo(this.cpu, this.dbg, addr, this.blockSize); size -= this.blockSize; } return true; @@ -626,7 +631,7 @@ Bus.prototype.setMemoryBlocks = function(addr, size, aBlocks, type) if (!block) break; if (type !== undefined) { var blockNew = new Memory(addr); - if (DEBUGGER) blockNew.setDebugInfo(this.cpu, this.dbg, this.blockSize); + if (DEBUGGER && this.dbg) blockNew.setDebugInfo(this.cpu, this.dbg, addr, this.blockSize); blockNew.clone(block, type); block = blockNew; } diff --git a/modules/pcjs/lib/defines.js b/modules/pcjs/lib/defines.js index c42d2abf0..671ef0dd2 100644 --- a/modules/pcjs/lib/defines.js +++ b/modules/pcjs/lib/defines.js @@ -82,16 +82,16 @@ var TYPEDARRAYS = (typeof ArrayBuffer !== 'undefined'); /** * @define {boolean} * - * Enables backtracking (disabled in compiled versions). Backtracking is a mechanism that allows us to tag - * every byte of incoming data and follow the flow of that data. + * BACKTRACK enables backtracking (disabled in compiled versions). Backtracking is a mechanism that allows + * us to tag every byte of incoming data and follow the flow of that data. */ var BACKTRACK = !COMPILED; /** * @define {boolean} * - * Enables instruction sampling (a work-in-progress). This was used briefly as an internal debugging aid, to - * periodically record LIP values in a fixed-length sampling buffer, halting execution once the sampling buffer + * SAMPLER enables instruction sampling (a work-in-progress). This was used briefly as an internal debugging aid, + * to periodically record LIP values in a fixed-length sampling buffer, halting execution once the sampling buffer * was full, and then compare those sampled LIP values to corresponding LIP values on subsequent runs, to look * for deviations. In theory, every run is supposed to be absolutely identical, even if you interrupt execution * with the Debugger or enable/disable different sets of messages, but in practice, that's hard to guarantee. @@ -101,9 +101,9 @@ var SAMPLER = false; /** * @define {boolean} * - * Enables support for known 8086 bugs. It's turned off by default, because 1) it adds overhead, and 2) it's - * hard to imagine any software actually being dependent on any of the bugs covered by this (eg, the failure to - * properly restart string instructions with multiple prefixes, or the failure to inhibit hardware interrupts + * BUGS_8086 enables support for known 8086 bugs. It's turned off by default, because 1) it adds overhead, and + * 2) it's hard to imagine any software actually being dependent on any of the bugs covered by this (eg, the failure + * to properly restart string instructions with multiple prefixes, or the failure to inhibit hardware interrupts * following SS segment loads). */ var BUGS_8086 = false; @@ -111,19 +111,33 @@ var BUGS_8086 = false; /** * @define {boolean} * - * Enables 80386 support. My preference continues to be one "binary" that supports all implemented CPUs, but + * I386 enables 80386 support. My preference continues to be one "binary" that supports all implemented CPUs, but * I'm providing this to enable a slimmed-down binary, at least until 80386 support is actually finished; at the - * moment, there's just a lot of scaffolding that bloats the compiled version without adding real functionality. + * moment, there's just a lot of scaffolding that bloats the compiled version without adding any real functionality. */ var I386 = true; /** * @define {boolean} * - * Enables Compaq DeskPro 386 support. + * COMPAQ386 enables Compaq DeskPro 386 support. */ var COMPAQ386 = true; +/** + * @define {boolean} + * + * PAGEBLOCKS enables 80386 paging support with assistance from the Bus component. This affects how the Bus component + * defines physical memory parameters for a 32-bit bus. With the 8086 and 80286 processors, the Bus component was free + * to choose any block size for physical memory allocations that made sense for the bus width (eg, 4Kb blocks for a + * 20-bit bus, or 16Kb blocks for 24-bit bus). + * + * However, for the 80386 processor, it makes more sense to choose a block size that matches the page size (ie, 4Kb), + * because then we have the option of altering the address-to-memory mapping for any block to match whatever page table + * mapping is in effect for that address, if any, without requiring another layer of address translation. + */ +var PAGEBLOCKS = I386; + if (typeof module !== 'undefined') { global.PCJSCLASS = PCJSCLASS; global.DEBUGGER = DEBUGGER; diff --git a/modules/pcjs/lib/memory.js b/modules/pcjs/lib/memory.js index bb21cc89b..73102c774 100644 --- a/modules/pcjs/lib/memory.js +++ b/modules/pcjs/lib/memory.js @@ -104,7 +104,7 @@ var littleEndian = (TYPEDARRAYS? (function() { * is available). * * @constructor - * @param {number} addr of lowest used address in block + * @param {number} [addr] of lowest used address in block * @param {number} [used] portion of block in bytes (0 for none); must be a multiple of 4 * @param {number} [size] of block's buffer in bytes (0 for none); must be a multiple of 4 * @param {number} [type] is one of the Memory.TYPE constants (default is Memory.TYPE.NONE) @@ -432,19 +432,21 @@ Memory.prototype = { this.writeLong = this.fReadOnly? this.writeNone : this.writeLongDirect; }, /** - * setDebugInfo(cpu, dbg, size) + * setDebugInfo(cpu, dbg, addr, size) * * @this {Memory} * @param {X86CPU|Component} cpu * @param {Debugger|Component} dbg + * @param {number} addr of block * @param {number} size of block */ - setDebugInfo: function(cpu, dbg, size) { + setDebugInfo: function(cpu, dbg, addr, size) { if (DEBUGGER) { this.cpu = cpu; this.dbg = dbg; this.cReadBreakpoints = this.cWriteBreakpoints = 0; - if (this.dbg) this.dbg.redoBreakpoints(this.addr, size); + Component.assert(this.dbg); + this.dbg.redoBreakpoints(addr, size); } }, /** @@ -455,7 +457,7 @@ Memory.prototype = { * @param {boolean} fWrite */ addBreakpoint: function(off, fWrite) { - if (DEBUGGER) { + if (DEBUGGER && this.dbg) { if (!fWrite) { if (this.cReadBreakpoints++ === 0) { this.setReadAccess(Memory.afnChecked); @@ -478,7 +480,7 @@ Memory.prototype = { * @param {boolean} fWrite */ removeBreakpoint: function(off, fWrite) { - if (DEBUGGER) { + if (DEBUGGER && this.dbg) { if (!fWrite) { if (--this.cReadBreakpoints === 0) { this.resetReadAccess(); @@ -515,7 +517,7 @@ Memory.prototype = { * @return {number} */ readNone: function readNone(off) { - if (DEBUGGER && this.dbg.messageEnabled(Messages.MEM) /* && !off */) { + if (DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.MEM) /* && !off */) { this.dbg.message("attempt to read invalid block %" + str.toHex(this.addr) + " from " + this.dbg.hexOffset(this.cpu.getIP(), this.cpu.getCS())); } return 0xff; @@ -529,7 +531,7 @@ Memory.prototype = { */ writeNone: function writeNone(off, v) { - if (DEBUGGER && this.dbg.messageEnabled(Messages.MEM) /* && !off */) { + if (DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.MEM) /* && !off */) { this.dbg.message("attempt to write " + str.toHexWord(v) + " to invalid block %" + str.toHex(this.addr), true); } }, @@ -736,7 +738,7 @@ Memory.prototype = { */ readByteChecked: function readByteChecked(off) { - if (DEBUGGER) this.dbg.checkMemoryRead(this.addr + off); + if (DEBUGGER && this.dbg) this.dbg.checkMemoryRead(this.addr + off); return this.readByteDirect(off); }, /** @@ -748,7 +750,7 @@ Memory.prototype = { */ readShortChecked: function readShortChecked(off) { - if (DEBUGGER) { + if (DEBUGGER && this.dbg) { this.dbg.checkMemoryRead(this.addr + off) || this.dbg.checkMemoryRead(this.addr + off + 1); } @@ -763,7 +765,7 @@ Memory.prototype = { */ readLongChecked: function readLongChecked(off) { - if (DEBUGGER) { + if (DEBUGGER && this.dbg) { this.dbg.checkMemoryRead(this.addr + off) || this.dbg.checkMemoryRead(this.addr + off + 1) || this.dbg.checkMemoryRead(this.addr + off + 2) || @@ -780,7 +782,7 @@ Memory.prototype = { */ writeByteChecked: function writeByteChecked(off, b) { - if (DEBUGGER) this.dbg.checkMemoryWrite(this.addr + off); + if (DEBUGGER && this.dbg) this.dbg.checkMemoryWrite(this.addr + off); this.writeByteDirect(off, b); }, /** @@ -792,7 +794,7 @@ Memory.prototype = { */ writeShortChecked: function writeShortChecked(off, w) { - if (DEBUGGER) { + if (DEBUGGER && this.dbg) { this.dbg.checkMemoryWrite(this.addr + off) || this.dbg.checkMemoryWrite(this.addr + off + 1); } @@ -807,7 +809,7 @@ Memory.prototype = { */ writeLongChecked: function writeLongChecked(off, l) { - if (DEBUGGER) { + if (DEBUGGER && this.dbg) { this.dbg.checkMemoryWrite(this.addr + off) || this.dbg.checkMemoryWrite(this.addr + off + 1) || this.dbg.checkMemoryWrite(this.addr + off + 2) || diff --git a/modules/pcjs/lib/panel.js b/modules/pcjs/lib/panel.js index f87d47dde..f645648d9 100644 --- a/modules/pcjs/lib/panel.js +++ b/modules/pcjs/lib/panel.js @@ -34,6 +34,7 @@ if (typeof module !== 'undefined') { var str = require("../../shared/lib/strlib"); + var usr = require("../../shared/lib/usrlib"); var web = require("../../shared/lib/weblib"); var Component = require("../../shared/lib/component"); var Bus = require("./bus"); @@ -59,7 +60,7 @@ function Panel(parmsPanel) this.fMouseDown = false; this.xMouse = this.yMouse = -1; if (BACKTRACK) { - this.stats = null; + this.busInfo = null; this.fBackTrack = false; } } @@ -477,19 +478,17 @@ Panel.prototype.updateMouse = function(event, fDown) */ Panel.prototype.findAddress = function(x, y) { - if (x < Panel.LIVEMEM.CX && this.stats && this.stats.aRects) { + if (x < Panel.LIVEMEM.CX && this.busInfo && this.busInfo.aRects) { var i, rect; - for (i = 0; i < this.stats.aRects.length; i++) { - rect = this.stats.aRects[i]; + for (i = 0; i < this.busInfo.aRects.length; i++) { + rect = this.busInfo.aRects[i]; if (rect.contains(x, y)) { x -= rect.x; y -= rect.y; - var nRegion = this.stats.aRegions[i]; - var iBlock = this.stats.aBlocks[nRegion & Bus.BLOCK.NUM_MASK] & Bus.BLOCK.NUM_MASK; - var cBlocks = (nRegion >> Bus.BLOCK.COUNT_SHIFT) & Bus.BLOCK.COUNT_MASK; - var type = (nRegion >> Bus.BLOCK.TYPE_SHIFT) & Bus.BLOCK.TYPE_MASK; + var region = this.busInfo.aRegions[i]; + var iBlock = usr.getBitField(Bus.BlockInfo.num, this.busInfo.aBlocks[region.iBlock]); var addr = iBlock * this.bus.blockSize; - var addrLimit = (iBlock + cBlocks) * this.bus.blockSize - 1; + var addrLimit = (iBlock + region.cBlocks) * this.bus.blockSize - 1; /* * If you want memory to be arranged "vertically" instead of "horizontally", do this: @@ -502,7 +501,7 @@ Panel.prototype.findAddress = function(x, y) addr |= 0; if (addr > addrLimit) addr = addrLimit; - if (MAXDEBUG) this.log("Panel.findAddress(" + x + "," + y + ") found type " + Memory.TYPE.NAMES[type] + ", address %" + str.toHex(addr)); + if (MAXDEBUG) this.log("Panel.findAddress(" + x + "," + y + ") found type " + Memory.TYPE.NAMES[region.type] + ", address %" + str.toHex(addr)); return addr; } } @@ -525,19 +524,19 @@ Panel.prototype.updateAnimation = function() if (this.fBackTrack) { if (DEBUG) this.log("begin scanMemory()"); - this.stats = this.bus.scanMemory(this.stats); + this.busInfo = this.bus.scanMemory(this.busInfo); /* * Calculate the pixel-to-memory-address ratio */ - this.ratioMemoryToPixels = (this.stats.cBlocks * this.bus.blockSize) / (Panel.LIVEMEM.CX * Panel.LIVEMEM.CY); + this.ratioMemoryToPixels = (this.busInfo.cBlocks * this.bus.blockSize) / (Panel.LIVEMEM.CX * Panel.LIVEMEM.CY); /* - * Update the Stats object with region information (cRegions and aRegions); return true if region + * Update the BusInfo object with region information (cRegions and aRegions); return true if region * information has changed since the last call. */ if (this.findRegions()) { /* * For each region, I choose a slice of the LiveMem canvas and record the corresponding rectangle - * within an aRects array (parallel to the aRegions array) in the Stats object. + * within an aRects array (parallel to the aRegions array) in the BusInfo object. * * I don't need a sophisticated Treemap algorithm, because at this level, the data is not hierarchical. * subDivide() makes a simple horizontal or vertical slicing decision based on the ratio of region blocks @@ -545,33 +544,34 @@ Panel.prototype.updateAnimation = function() */ var i, rect; var rectAvail = new Rectangle(0, 0, this.canvasLiveMem.width, this.canvasLiveMem.height); - this.stats.aRects = []; - var cBlocksRemaining = this.stats.cBlocks; - for (i = 0; i < this.stats.cRegions; i++) { - var cBlocksRegion = (this.stats.aRegions[i] >> Bus.BLOCK.COUNT_SHIFT) & Bus.BLOCK.COUNT_MASK; - this.stats.aRects.push(rect = rectAvail.subDivide(cBlocksRegion, cBlocksRemaining, !i)); + this.busInfo.aRects = []; + var cBlocksRemaining = this.busInfo.cBlocks; + + for (i = 0; i < this.busInfo.cRegions; i++) { + var cBlocksRegion = this.busInfo.aRegions[i].cBlocks; + this.busInfo.aRects.push(rect = rectAvail.subDivide(cBlocksRegion, cBlocksRemaining, !i)); if (MAXDEBUG) this.log("region " + i + " rectangle: (" + rect.x + "," + rect.y + " " + rect.cx + "," + rect.cy + ")"); cBlocksRemaining -= cBlocksRegion; } + /* * Assert that not only did all the specified regions account for all the specified blocks, but also that * the series of subDivide() calls exhausted the original rectangle to one of either zero width or zero height. */ this.assert(!cBlocksRemaining && (!rectAvail.cx || !rectAvail.cy)); + /* * Now draw all the rectangles produced by the series of subDivide() calls. */ - for (i = 0; i < this.stats.aRects.length; i++) { - var nRegion = this.stats.aRegions[i]; - var type = (nRegion >> Bus.BLOCK.TYPE_SHIFT) & Bus.BLOCK.TYPE_MASK; - var cBlocks = (nRegion >> Bus.BLOCK.COUNT_SHIFT) & Bus.BLOCK.COUNT_MASK; - rect = this.stats.aRects[i]; - rect.drawWith(this.contextLiveMem, Memory.TYPE.COLORS[type]); + for (i = 0; i < this.busInfo.aRects.length; i++) { + var region = this.busInfo.aRegions[i]; + rect = this.busInfo.aRects[i]; + rect.drawWith(this.contextLiveMem, Memory.TYPE.COLORS[region.type]); this.centerPen(rect); - this.centerText(Memory.TYPE.NAMES[type] + " (" + (((cBlocks * this.bus.blockSize) / 1024) | 0) + "Kb)"); + this.centerText(Memory.TYPE.NAMES[region.type] + " (" + (((region.cBlocks * this.bus.blockSize) / 1024) | 0) + "Kb)"); } } - if (DEBUG) this.log("end scanMemory(): total bytes: " + this.stats.cbTotal + ", total blocks: " + this.stats.cBlocks + ", total regions: " + this.stats.cRegions); + if (DEBUG) this.log("end scanMemory(): total bytes: " + this.busInfo.cbTotal + ", total blocks: " + this.busInfo.cBlocks + ", total regions: " + this.busInfo.cRegions); } else { this.drawText("This space intentionally left blank"); } @@ -599,10 +599,10 @@ Panel.prototype.updateStatus = function() /** * findRegions() * - * This takes the Stats object produced by scanMemory() and adds the following: + * This takes the BusInfo object produced by scanMemory() and adds the following: * * cRegions: number of contiguous memory regions - * aRegions: array of aBlocks indexes (bits 0-14) combined with block counts (bits 16-27) and block types (bits 28-31) + * aRegions: array of aBlocks [index, count, type] objects * * It calls addRegion() for each discrete region (set of contiguous blocks with the same type) that it finds. * @@ -612,30 +612,49 @@ Panel.prototype.updateStatus = function() Panel.prototype.findRegions = function() { var checksum = 0; - this.stats.cRegions = 0; - if (!this.stats.aRegions) this.stats.aRegions = []; + this.busInfo.cRegions = 0; + if (!this.busInfo.aRegions) this.busInfo.aRegions = []; + var typeRegion = -1, iBlockRegion = 0, addrRegion = 0, nBlockPrev = -1; - for (var iBlock = 0; iBlock < this.stats.cBlocks; iBlock++) { - var nBlock = this.stats.aBlocks[iBlock]; - var type = nBlock >>> Bus.BLOCK.TYPE_SHIFT; - var nBlockCurr = (nBlock & Bus.BLOCK.NUM_MASK); - if (type != typeRegion || nBlockCurr != nBlockPrev + 1) { + + for (var iBlock = 0; iBlock < this.busInfo.cBlocks; iBlock++) { + var blockInfo = this.busInfo.aBlocks[iBlock]; + var typeBlock = usr.getBitField(Bus.BlockInfo.type, blockInfo); + var nBlockCurr = usr.getBitField(Bus.BlockInfo.num, blockInfo); + if (typeBlock != typeRegion || nBlockCurr != nBlockPrev + 1) { var cBlocks = iBlock - iBlockRegion; if (cBlocks) { checksum += this.addRegion(addrRegion, iBlockRegion, cBlocks, typeRegion); } - typeRegion = type; + typeRegion = typeBlock; iBlockRegion = iBlock; - addrRegion = (nBlock & Bus.BLOCK.NUM_MASK) << this.bus.blockShift; + addrRegion = nBlockCurr << this.bus.blockShift; } nBlockPrev = nBlockCurr; } + checksum += this.addRegion(addrRegion, iBlockRegion, iBlock - iBlockRegion, typeRegion); - var fChanged = (this.stats.checksumRegions != checksum); - this.stats.checksumRegions = checksum; + + var fChanged = (this.busInfo.checksumRegions != checksum); + this.busInfo.checksumRegions = checksum; return fChanged; }; +/** + * Region object definition + * + * iBlock: starting block number + * cBlocks: number of blocks spanned by region + * type: type of all blocks in the region (see Memory.TYPE.*) + * + * @typedef {{ + * iBlock: number, + * cBlocks: number, + * type: number + * }} + */ +var Region; + /** * addRegion(addr, iBlock, cBlocks, type) * @@ -644,13 +663,13 @@ Panel.prototype.findRegions = function() * @param {number} iBlock * @param {number} cBlocks * @param {number} type - * @return {number} region data added + * @return {number} bitfield containing the above values (used for checksum) */ Panel.prototype.addRegion = function(addr, iBlock, cBlocks, type) { - if (DEBUG) this.log("region " + this.stats.cRegions + " (addr " + str.toHexLong(addr) + ", type " + Memory.TYPE.NAMES[type] + ") contains " + cBlocks + " blocks"); - this.assert(iBlock <= Bus.BLOCK.NUM_MASK && cBlocks <= Bus.BLOCK.COUNT_MASK && type <= Bus.BLOCK.TYPE_MASK); - return this.stats.aRegions[this.stats.cRegions++] = (iBlock | (cBlocks << Bus.BLOCK.COUNT_SHIFT) | (type << Bus.BLOCK.TYPE_SHIFT)); + if (DEBUG) this.log("region " + this.busInfo.cRegions + " (addr " + str.toHexLong(addr) + ", type " + Memory.TYPE.NAMES[type] + ") contains " + cBlocks + " blocks"); + this.busInfo.aRegions[this.busInfo.cRegions++] = {iBlock: iBlock, cBlocks: cBlocks, type: type}; + return usr.initBitFields(Bus.BlockInfo, iBlock, cBlocks, 0, type); }; /** diff --git a/modules/shared/lib/usrlib.js b/modules/shared/lib/usrlib.js index c98ebe1c8..ebe11bb08 100644 --- a/modules/shared/lib/usrlib.js +++ b/modules/shared/lib/usrlib.js @@ -34,28 +34,6 @@ var usr = {}; -/** - * indexOf(a, t, i) - * - * @param {Array} a - * @param {*} t - * @param {number} [i] - * @returns {number} - */ -usr.indexOf = function(a, t, i) -{ - if (Array.prototype.indexOf) { - return a.indexOf(t, i); - } - i = i || 0; - if (i < 0) i += a.length; - if (i < 0) i = 0; - for (var n = a.length; i < n; i++) { - if (i in a && a[i] === t) return i; - } - return -1; -}; - /** * binarySearch(a, v, fnCompare) * @@ -188,45 +166,154 @@ usr.formatDate = function(sFormat, date) { for (var i = 0; i < sFormat.length; i++) { var ch; switch((ch = sFormat.charAt(i))) { - case 'a': - sDate += (iHour < 12? "am" : "pm"); - break; - case 'd': - sDate += ('0' + iDay).slice(-2); - break; - case 'g': - sDate += (!iHour? 12 : (iHour > 12? iHour - 12 : iHour)); - break; - case 'i': - sDate += ('0' + date.getMinutes()).slice(-2); - break; - case 'j': - sDate += iDay; - break; - case 'l': - sDate += usr.asDays[date.getDay()]; - break; - case 'm': - sDate += ('0' + iMonth).slice(-2); - break; - case 's': - sDate += ('0' + date.getSeconds()).slice(-2); - break; - case 'F': - sDate += usr.asMonths[iMonth - 1]; - break; - case 'H': - sDate += ('0' + iHour).slice(-2); - break; - case 'Y': - sDate += date.getFullYear(); - break; - default: - sDate += ch; - break; + case 'a': + sDate += (iHour < 12? "am" : "pm"); + break; + case 'd': + sDate += ('0' + iDay).slice(-2); + break; + case 'g': + sDate += (!iHour? 12 : (iHour > 12? iHour - 12 : iHour)); + break; + case 'i': + sDate += ('0' + date.getMinutes()).slice(-2); + break; + case 'j': + sDate += iDay; + break; + case 'l': + sDate += usr.asDays[date.getDay()]; + break; + case 'm': + sDate += ('0' + iMonth).slice(-2); + break; + case 's': + sDate += ('0' + date.getSeconds()).slice(-2); + break; + case 'F': + sDate += usr.asMonths[iMonth - 1]; + break; + case 'H': + sDate += ('0' + iHour).slice(-2); + break; + case 'Y': + sDate += date.getFullYear(); + break; + default: + sDate += ch; + break; } } return sDate; }; +/** + * @typedef {{ + * mask: number, + * shift: number + * }} + */ +var BitField; + +/** + * @typedef {Object.} + */ +var BitFields; + +/** + * defineBitFields(bfs) + * + * Prepares a bit field definition for use with getBitField() and setBitField(); eg: + * + * var bfs = usr.defineBitFields({num:20, count:8, btmod:1, type:3}); + * + * The above defines a set of bit fields containg four fields: num (bits 0-19), count (bits 20-27), btmod (bit 28), and type (bits 29-31). + * + * usr.setBitField(bfs.num, n, 1); + * + * The above set bit field "bfs.num" in numeric variable "n" to the value 1. + * + * @param {Object} bfs + * @return {*} (technically, we transform the bfs object into a BitFields object, but the Closure Compiler won't let us specify that) + */ +usr.defineBitFields = function(bfs) +{ + var bit = 0; + for (var f in bfs) { + var width = bfs[f]; + var mask = ((1 << width) - 1) << bit; + bfs[f] = {mask: mask, shift: bit}; + bit += width; + } + // Component.assert(bit <= 32); + return bfs; +}; + +/** + * initBitFields(bfs, ...) + * + * @param {BitFields} bfs + * @param {...number} var_args + * @return {number} a value containing all supplied bit fields + */ +usr.initBitFields = function(bfs, var_args) +{ + var v = 0, i = 1; + for (var f in bfs) { + if (i >= arguments.length) break; + v = usr.setBitField(bfs[f], v, arguments[i++]); + } + return v; +}; + +/** + * getBitField(bf, v) + * + * @param {BitField} bf + * @param {number} v is a value containing bit fields + * @return {number} the value of the bit field in v defined by bf + */ +usr.getBitField = function(bf, v) +{ + return (v & bf.mask) >> bf.shift; +}; + +/** + * setBitField(bf, v, n) + * + * @param {BitField} bf + * @param {number} v is a value containing bit fields + * @param {number} n is a value to store in v in the bit field defined by bf + * @return {number} updated v + */ +usr.setBitField = function(bf, v, n) +{ + // Component.assert(!(n & ~(bf.mask >>> bf.shift))); + return (v & ~bf.mask) | ((n << bf.shift) & bf.mask); +}; + +/** + * indexOf(a, t, i) + * + * Use this instead of Array.prototype.indexOf() if you can't be sure the browser supports it. + * + * @param {Array} a + * @param {*} t + * @param {number} [i] + * @returns {number} + */ +usr.indexOf = function(a, t, i) +{ + if (Array.prototype.indexOf) { + return a.indexOf(t, i); + } + i = i || 0; + if (i < 0) i += a.length; + if (i < 0) i = 0; + for (var n = a.length; i < n; i++) { + if (i in a && a[i] === t) return i; + } + return -1; +}; + if (typeof module !== 'undefined') module.exports = usr;