/** * @fileoverview Implements the PDP-10 Memory component. * @author Jeff Parsons * @copyright © Jeff Parsons 2012-2017 * * This file is part of PCjs, a computer emulation software project at . * * 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 . * * You are required to include the above copyright notice in every modified copy of this work * and to display that copyright notice when the software starts running; see COPYRIGHT in * . * * 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 Component = require("../../shared/lib/component"); var PDP10 = require("./defines"); var MessagesPDP10 = require("./messages"); } /** * @class DataView * @property {function(number,boolean):number} getUint8 * @property {function(number,number,boolean)} setUint8 * @property {function(number,boolean):number} getUint16 * @property {function(number,number,boolean)} setUint16 * @property {function(number,boolean):number} getInt32 * @property {function(number,number,boolean)} setInt32 */ class MemoryPDP10 { /** * MemoryPDP10(bus, addr, used, size, type) * * The Bus component allocates Memory objects so that each has a memory buffer with a * block-granular starting address and an address range equal to bus.nBlockSize; however, * the size of any given Memory object's underlying buffer can be either zero or bus.nBlockSize; * memory read/write functions for empty (buffer-less) blocks are mapped to readNone/writeNone. * * The Bus allocates empty blocks for the entire address space during initialization, so that * any reads/writes to undefined addresses will have no effect. Later, the ROM and RAM * components will ask the Bus to allocate memory for specific ranges, and the Bus will allocate * as many new blockSize Memory objects as the ranges require. Partial Memory blocks could * also be supported in theory, but in practice, they're not. * * WARNING: Since Memory blocks are low-level objects that have no UI requirements, they * do not inherit from the Component class, so if you want to use any Component class methods, * such as Component.assert(), use the corresponding Debugger methods instead (assuming a debugger * is available). * * @param {BusPDP10} bus * @param {number|null} [addr] of lowest used address in block * @param {number} [used] portion of block in words (0 for none) * @param {number} [size] of block's buffer in words (0 for none) * @param {number} [type] is one of the MemoryPDP10.TYPE constants (default is MemoryPDP10.TYPE.NONE) */ constructor(bus, addr, used, size, type) { var a, i; this.bus = bus; this.id = (MemoryPDP10.idBlock += 2); this.adw = null; this.offset = 0; this.addr = addr; this.used = used; this.size = size || 0; this.type = type || MemoryPDP10.TYPE.NONE; this.fReadOnly = (type == MemoryPDP10.TYPE.ROM); this.dbg = null; this.readWord = this.readWordDirect = this.readNone; this.writeWord = this.writeWordDirect = this.writeNone; this.cReadBreakpoints = this.cWriteBreakpoints = 0; this.copyBreakpoints(); // initialize the block's Debugger info; the caller will reinitialize /* * TODO: Study the impact of dirty block tracking. The original purposes were to allow saveMemory() * to save only dirty blocks, and to enable the Video component to quickly detect changes to the video buffer. * * However, a quick test with dirty block tracking disabled didn't yield a noticeable improvement in performance, * so I think the overhead of our block-based architecture is swamping the impact of these micro-updates. */ this.fDirty = this.fDirtyEver = false; /* * For empty memory blocks, all we need to do is ensure all access functions are mapped to "none" handlers. */ if (!this.size) { this.setAccess(); return; } /* * This is the normal case: allocate a buffer that provides a word of data per address; * no controller is required because our default memory access functions (see afnMemory) * know how to deal with this simple 1-1 mapping of addresses to words. * * TODO: Consider initializing the memory array to random (or pseudo-random) values in DEBUG * mode; pseudo-random might be best, to help make any bugs reproducible. */ a = this.aw = new Array(this.size); for (i = 0; i < a.length; i++) a[i] = 0; this.setAccess(MemoryPDP10.afnMemory); } /** * init(addr) * * Quick reinitializer when reusing a Memory block. * * @this {MemoryPDP10} * @param {number} addr */ init(addr) { this.addr = addr; } /** * clone(mem, type, dbg) * * Converts the current Memory block (this) into a clone of the given Memory block (mem), * and optionally overrides the current block's type with the specified type. * * @this {MemoryPDP10} * @param {MemoryPDP10} mem * @param {number} [type] * @param {DebuggerPDP10} [dbg] */ clone(mem, type, dbg) { /* * Original memory block IDs are even; cloned memory block IDs are odd; * the original ID of the current block is lost, but that's OK, since it was presumably * produced merely to become a clone. */ this.id = mem.id | 0x1; this.used = mem.used; this.size = mem.size; if (type) { this.type = type; this.fReadOnly = (type == MemoryPDP10.TYPE.ROM); } this.aw = mem.aw; this.setAccess(MemoryPDP10.afnMemory); this.copyBreakpoints(dbg, mem); } /** * save() * * This gets the contents of a Memory block as an array of numeric values; used by Bus.saveMemory(), * which in turn is called by CPUState.save(). * * @this {MemoryPDP10} * @return {Array.|null} */ save() { return this.aw; } /** * restore(aw) * * This restores the contents of a Memory block from an array of numeric values; used by Bus.restoreMemory(), * which is called by CPUState.restore(), after all other components have been restored and thus all Memory * blocks have been allocated by their respective components. * * @this {MemoryPDP10} * @param {Array.|null} aw * @return {boolean} true if successful, false if block size mismatch */ restore(aw) { if (aw && this.size == aw.length) { this.aw = aw; this.fDirty = true; return true; } return false; } /** * zero(off, len, pattern) * * @this {MemoryPDP10} * @param {number} [off] (optional starting word offset within block) * @param {number} [len] (optional maximum number of words; default is the entire block) * @param {number} [pattern] (default is zero) */ zero(off, len, pattern = 0) { /* * NOTE: If len is larger than the block, that's OK, because we also bounds-check the index. */ off = off || 0; if (len === undefined) len = this.size; Component.assert(off >= 0 && off < this.size); /* * Although it's expected that most callers will supply unsigned 36-bit values, we're nice about * converting any signed values to their unsigned (two's complement) counterpart, provided they are * within the acceptable range. Any values outside that range will be dealt with afterward. */ if (pattern < 0 && pattern >= -PDP10.INT_LIMIT) { pattern += PDP10.WORD_LIMIT; } pattern = Math.trunc(Math.abs(pattern)) % PDP10.WORD_LIMIT; for (var i = off; len-- && i < this.size; i++) this.writeWordDirect(pattern, off, this.addr + off); } /** * setAccess(afn, fDirect) * * The afn parameter should be a 2-entry function table containing word read and write handlers. * See the static afnMemory table for an example. * * If no function table is specified, a default is selected based on the Memory type; similarly, * any undefined entries in the table are filled with default handlers. * * fDirect indicates that both the default AND the direct handlers should be updated. Direct * handlers normally match the default handlers, except when "checked" handlers are installed; * this allows "checked" handlers to know where to dispatch the call after performing checks. * Examples of checks are read/write breakpoints, but it's really up to the Debugger to decide * what the check consists of. * * @this {MemoryPDP10} * @param {Array.} [afn] function table * @param {boolean} [fDirect] (true to update direct access functions as well; default is true) */ setAccess(afn, fDirect) { if (!afn) { Component.assert(this.type == MemoryPDP10.TYPE.NONE); afn = MemoryPDP10.afnNone; } this.setReadAccess(afn, fDirect); this.setWriteAccess(afn, fDirect); } /** * setReadAccess(afn, fDirect) * * @this {MemoryPDP10} * @param {Array.} afn * @param {boolean} [fDirect] */ setReadAccess(afn, fDirect) { if (!fDirect || !this.cReadBreakpoints) { this.readWord = afn[0] || this.readNone; } if (fDirect || fDirect === undefined) { this.readWordDirect = afn[0] || this.readNone; } } /** * setWriteAccess(afn, fDirect) * * @this {MemoryPDP10} * @param {Array.} afn * @param {boolean} [fDirect] */ setWriteAccess(afn, fDirect) { if (!fDirect || !this.cWriteBreakpoints) { this.writeWord = !this.fReadOnly && afn[1] || this.writeNone; } if (fDirect || fDirect === undefined) { this.writeWordDirect = afn[1] || this.writeNone; } } /** * resetReadAccess() * * @this {MemoryPDP10} */ resetReadAccess() { this.readWord = this.readWordDirect; } /** * resetWriteAccess() * * @this {MemoryPDP10} */ resetWriteAccess() { this.writeWord = this.fReadOnly? this.writeNone : this.writeWordDirect; } /** * printAddr(sMessage) * * @this {MemoryPDP10} * @param {string} sMessage */ printAddr(sMessage) { if (DEBUG && this.dbg && this.dbg.messageEnabled(MessagesPDP10.MEMORY)) { this.dbg.printMessage(sMessage + ' ' + (this.addr != null? ('@' + this.dbg.toStrBase(this.addr)) : '#' + this.id), true); } } /** * addBreakpoint(off, fWrite) * * @this {MemoryPDP10} * @param {number} off * @param {boolean} fWrite */ addBreakpoint(off, fWrite) { if (!fWrite) { if (this.cReadBreakpoints++ === 0) { this.setReadAccess(MemoryPDP10.afnChecked, false); } if (DEBUG) this.printAddr("read breakpoint added to memory block"); } else { if (this.cWriteBreakpoints++ === 0) { this.setWriteAccess(MemoryPDP10.afnChecked, false); } if (DEBUG) this.printAddr("write breakpoint added to memory block"); } } /** * removeBreakpoint(off, fWrite) * * @this {MemoryPDP10} * @param {number} off * @param {boolean} fWrite */ removeBreakpoint(off, fWrite) { if (!fWrite) { if (--this.cReadBreakpoints === 0) { this.resetReadAccess(); if (DEBUG) this.printAddr("all read breakpoints removed from memory block"); } Component.assert(this.cReadBreakpoints >= 0); } else { if (--this.cWriteBreakpoints === 0) { this.resetWriteAccess(); if (DEBUG) this.printAddr("all write breakpoints removed from memory block"); } Component.assert(this.cWriteBreakpoints >= 0); } } /** * copyBreakpoints(dbg, mem) * * @this {MemoryPDP10} * @param {DebuggerPDP10} [dbg] * @param {MemoryPDP10} [mem] (outgoing MemoryPDP10 block to copy breakpoints from, if any) */ copyBreakpoints(dbg, mem) { this.dbg = dbg; this.cReadBreakpoints = this.cWriteBreakpoints = 0; if (mem) { if ((this.cReadBreakpoints = mem.cReadBreakpoints)) { this.setReadAccess(MemoryPDP10.afnChecked, false); } if ((this.cWriteBreakpoints = mem.cWriteBreakpoints)) { this.setWriteAccess(MemoryPDP10.afnChecked, false); } } } /** * readNone(off, addr) * * @this {MemoryPDP10} * @param {number} off * @param {number} addr * @return {number} */ readNone(off, addr) { if (DEBUGGER && this.dbg && this.dbg.messageEnabled(MessagesPDP10.MEMORY) /* && !off */) { this.dbg.printMessage("attempt to read invalid address " + this.dbg.toStrBase(addr), true); } this.bus.fault(addr); return PDP10.WORD_INVALID; } /** * writeNone(v, off, addr) * * @this {MemoryPDP10} * @param {number} v * @param {number} off * @param {number} addr */ writeNone(v, off, addr) { if (DEBUGGER && this.dbg && this.dbg.messageEnabled(MessagesPDP10.MEMORY) /* && !off */) { this.dbg.printMessage("attempt to write " + this.dbg.toStrBase(v) + " to invalid addresses " + this.dbg.toStrBase(addr), true); } this.bus.fault(addr); } /** * readWordMemory(off, addr) * * @this {MemoryPDP10} * @param {number} off * @param {number} addr * @return {number} */ readWordMemory(off, addr) { var w = this.aw[off]; Component.assert(w >= 0 && w < PDP10.WORD_LIMIT); return w; } /** * writeWordMemory(w, off, addr) * * @this {MemoryPDP10} * @param {number} w * @param {number} off * @param {number} addr */ writeWordMemory(w, off, addr) { Component.assert(w >= 0 && w < PDP10.WORD_LIMIT); if (this.aw[off] != w) { this.aw[off] = w; this.fDirty = true; } } /** * readWordChecked(off, addr) * * @this {MemoryPDP10} * @param {number} off * @param {number} addr * @return {number} */ readWordChecked(off, addr) { if (DEBUGGER && this.dbg && this.addr != null) { this.dbg.checkMemoryRead(this.addr + off, 2); } return this.readWordDirect(off, addr); } /** * writeWordChecked(w, off, addr) * * @this {MemoryPDP10} * @param {number} w * @param {number} off * @param {number} addr */ writeWordChecked(w, off, addr) { if (DEBUGGER && this.dbg && this.addr != null) { this.dbg.checkMemoryWrite(this.addr + off, 2) } if (this.fReadOnly) this.writeNone(w, off, addr); else this.writeWordDirect(w, off, addr); } } /* * Basic memory types * * RAM is the most conventional memory type, providing full read/write capability. ROM is equally * conventional, except that the fReadOnly property is set. ROM can be written using the Bus setWordDirect() * interface (which in turn uses the Memory writeWordDirect() interface), allowing the ROM component to * initialize its own memory. */ MemoryPDP10.TYPE = { NONE: 0, RAM: 1, ROM: 2 }; MemoryPDP10.TYPE_COLORS = ["black", "blue", "green"]; MemoryPDP10.TYPE_NAMES = ["NONE", "RAM", "ROM"]; /* * Last used block ID (used for debugging only) */ MemoryPDP10.idBlock = 0; /* * This is the effective definition of afnNone, but we need not fully define it, because setAccess() * uses these defaults when any of the handlers are undefined. * MemoryPDP10.afnNone = [ MemoryPDP10.prototype.readNone, MemoryPDP10.prototype.writeNone ]; */ MemoryPDP10.afnNone = []; MemoryPDP10.afnMemory = [ MemoryPDP10.prototype.readWordMemory, MemoryPDP10.prototype.writeWordMemory ]; MemoryPDP10.afnChecked = [ MemoryPDP10.prototype.readWordChecked, MemoryPDP10.prototype.writeWordChecked ]; if (NODE) module.exports = MemoryPDP10;