Created PDP-10 skeleton
This commit is contained in:
parent
b1d16c979a
commit
ba7de38fbf
40 changed files with 12282 additions and 35 deletions
717
modules/pdp10/lib/bus.js
Normal file
717
modules/pdp10/lib/bus.js
Normal file
|
|
@ -0,0 +1,717 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP-10 Bus component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
|
||||
*
|
||||
* 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 modified copy of this work
|
||||
* and to display that copyright notice when the software starts running; see COPYRIGHT in
|
||||
* <http://pcjs.org/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 Usr = require("../../shared/lib/usrlib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var State = require("../../shared/lib/state");
|
||||
var PDP10 = require("./defines");
|
||||
var MemoryPDP10 = require("./memory");
|
||||
var MessagesPDP10 = require("./messages");
|
||||
}
|
||||
|
||||
/*
|
||||
* Data types used by scanMemory()
|
||||
*/
|
||||
|
||||
/**
|
||||
* This defines the BlockInfo bit fields used by scanMemory() when it creates the aBlocks array.
|
||||
*
|
||||
* @typedef {{
|
||||
* num: BitField,
|
||||
* count: BitField,
|
||||
* btmod: BitField,
|
||||
* type: BitField
|
||||
* }}
|
||||
*/
|
||||
var BlockInfoPDP10 = Usr.defineBitFields({num:20, count:8, btmod:1, type:3});
|
||||
|
||||
/**
|
||||
* BusInfoPDP10 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.<BlockInfoPDP10>
|
||||
* }}
|
||||
*/
|
||||
var BusInfoPDP10;
|
||||
|
||||
class BusPDP10 extends Component {
|
||||
/**
|
||||
* BusPDP10(parmsBus, cpu, dbg)
|
||||
*
|
||||
* The BusPDP10 component manages physical memory and I/O address spaces.
|
||||
*
|
||||
* The BusPDP10 component has no UI elements, so it does not require an init() handler,
|
||||
* but it still inherits from the Component class and must be allocated like any
|
||||
* other device component. It's currently allocated by the Computer's init() handler,
|
||||
* which then calls the initBus() method of all the other components.
|
||||
*
|
||||
* For memory beyond the simple needs of the ROM and RAM components (ie, memory-mapped
|
||||
* devices), the address space must still be allocated through the BusPDP10 component via
|
||||
* addMemory(). If the component needs something more than simple read/write storage,
|
||||
* it must provide a custom controller.
|
||||
*
|
||||
* @param {Object} parmsBus
|
||||
* @param {CPUStatePDP10} cpu
|
||||
* @param {DebuggerPDP10} dbg
|
||||
*/
|
||||
constructor(parmsBus, cpu, dbg)
|
||||
{
|
||||
super("Bus", parmsBus, MessagesPDP10.BUS);
|
||||
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
|
||||
/*
|
||||
* Supported values for nBusWidth: 18 (default). 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'] || 18;
|
||||
|
||||
/*
|
||||
* Compute all BusPDP10 memory block parameters now, based on the width of the bus.
|
||||
*
|
||||
* Note that all PCjs machines divide their address space into blocks, using a block size appropriate for
|
||||
* the machine's bus width. This allows us to efficiently allocate the entire address space, by reusing blocks
|
||||
* as appropriate, and to define to different address behaviors on a block-granular level.
|
||||
*/
|
||||
this.addrTotal = 1 << this.nBusWidth;
|
||||
this.nBusMask = (this.addrTotal - 1);
|
||||
this.nBlockSize = 16384;
|
||||
this.nBlockShift = Math.log2(this.nBlockSize); // ES6 ALERT (alternatively: Math.log(this.nBlockSize) / Math.LN2)
|
||||
this.nBlockLen = this.nBlockSize >> 2;
|
||||
this.nBlockLimit = this.nBlockSize - 1;
|
||||
this.nBlockTotal = (this.addrTotal / this.nBlockSize) | 0;
|
||||
this.nBlockMask = this.nBlockTotal - 1;
|
||||
this.assert(this.nBlockMask <= BlockInfoPDP10.num.mask);
|
||||
|
||||
/*
|
||||
* Define all the properties to be initialized by initMemory()
|
||||
*/
|
||||
this.aBusBlocks = [];
|
||||
|
||||
/*
|
||||
* We're ready to allocate empty Memory blocks to span the entire physical address space.
|
||||
*/
|
||||
this.initMemory();
|
||||
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* initMemory()
|
||||
*
|
||||
* Allocate enough (empty) Memory blocks to span the entire physical address space.
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
*/
|
||||
initMemory()
|
||||
{
|
||||
var block = new MemoryPDP10(this);
|
||||
block.copyBreakpoints(this.dbg);
|
||||
|
||||
this.aBusBlocks = new Array(this.nBlockTotal);
|
||||
for (var iBlock = 0; iBlock < this.nBlockTotal; iBlock++) {
|
||||
this.aBusBlocks[iBlock] = block;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
*/
|
||||
reset()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* getWidth()
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @return {number}
|
||||
*/
|
||||
getWidth()
|
||||
{
|
||||
return this.nBusWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {Object|null} data (always null because we supply no powerDown() handler)
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
powerUp(data, fRepower)
|
||||
{
|
||||
if (!fRepower) {
|
||||
if (!data) {
|
||||
this.reset();
|
||||
} else {
|
||||
if (!this.restore(data)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* powerDown(fSave, fShutdown)
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {boolean} [fSave]
|
||||
* @param {boolean} [fShutdown]
|
||||
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
|
||||
*/
|
||||
powerDown(fSave, fShutdown)
|
||||
{
|
||||
return fSave? this.save() : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @return {Object|null}
|
||||
*/
|
||||
save()
|
||||
{
|
||||
var state = new State(this);
|
||||
state.set(0, this.saveMemory());
|
||||
return state.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* restore(data)
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {Object} data
|
||||
* @return {boolean} true if restore successful, false if not
|
||||
*/
|
||||
restore(data)
|
||||
{
|
||||
return this.restoreMemory(data[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* addMemory(addr, size, type)
|
||||
*
|
||||
* Adds new Memory blocks to the specified address range. Any Memory blocks previously
|
||||
* added to that range must first be removed via removeMemory(); otherwise, you'll get
|
||||
* an allocation conflict error. This helps prevent address calculation errors, redundant
|
||||
* allocations, etc.
|
||||
*
|
||||
* We've relaxed some of the original requirements (ie, that addresses must start at a
|
||||
* block-granular address, or that sizes must be equal to exactly one or more blocks),
|
||||
* because machines with large block sizes can make it impossible to load certain ROMs at
|
||||
* their required addresses. Every allocation still allocates a whole number of blocks.
|
||||
*
|
||||
* Even so, BusPDP10 memory management does NOT provide a general-purpose heap. Most memory
|
||||
* allocations occur during machine initialization and never change. In particular, there
|
||||
* is NO support for removing partial-block allocations.
|
||||
*
|
||||
* Each Memory block keeps track of a start address (addr) and length (used), indicating
|
||||
* the used space within the block; any free space that precedes or follows that used space
|
||||
* can be allocated later, by simply extending the beginning or ending of the previously used
|
||||
* space. However, any holes that might have existed between the original allocation and an
|
||||
* extension are subsumed by the extension.
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {number} addr is the starting physical address of the request
|
||||
* @param {number} size of the request, in bytes
|
||||
* @param {number} type is one of the MemoryPDP10.TYPE constants
|
||||
* @return {boolean} true if successful, false if not
|
||||
*/
|
||||
addMemory(addr, size, type)
|
||||
{
|
||||
var addrNext = addr;
|
||||
var sizeLeft = size;
|
||||
var iBlock = addrNext >>> this.nBlockShift;
|
||||
|
||||
while (sizeLeft > 0 && iBlock < this.aBusBlocks.length) {
|
||||
|
||||
var block = this.aBusBlocks[iBlock];
|
||||
var addrBlock = iBlock * this.nBlockSize;
|
||||
var sizeBlock = this.nBlockSize - (addrNext - addrBlock);
|
||||
if (sizeBlock > sizeLeft) sizeBlock = sizeLeft;
|
||||
|
||||
if (block && block.size) {
|
||||
if (block.type == type) {
|
||||
/*
|
||||
* Where there is already a similar block with a non-zero size, we allow the allocation only if:
|
||||
*
|
||||
* 1) addrNext + sizeLeft <= block.addr (the request precedes the used portion of the current block), or
|
||||
* 2) addrNext >= block.addr + block.used (the request follows the used portion of the current block)
|
||||
*/
|
||||
if (addrNext + sizeLeft <= block.addr) {
|
||||
block.used += (block.addr - addrNext);
|
||||
block.addr = addrNext;
|
||||
return true;
|
||||
}
|
||||
if (addrNext >= block.addr + block.used) {
|
||||
var sizeAvail = block.size - (addrNext - addrBlock);
|
||||
if (sizeAvail > sizeLeft) sizeAvail = sizeLeft;
|
||||
block.used = addrNext - block.addr + sizeAvail;
|
||||
addrNext = addrBlock + this.nBlockSize;
|
||||
sizeLeft -= sizeAvail;
|
||||
iBlock++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return this.reportError(BusPDP10.ERROR.RANGE_INUSE, addrNext, sizeLeft);
|
||||
}
|
||||
|
||||
var blockNew = new MemoryPDP10(this, addrNext, sizeBlock, this.nBlockSize, type);
|
||||
blockNew.copyBreakpoints(this.dbg, block);
|
||||
this.aBusBlocks[iBlock++] = blockNew;
|
||||
|
||||
addrNext = addrBlock + this.nBlockSize;
|
||||
sizeLeft -= sizeBlock;
|
||||
}
|
||||
|
||||
if (sizeLeft <= 0) {
|
||||
this.status("Added " + (size >> 10) + "Kb " + MemoryPDP10.TYPE_NAMES[type] + " at " + Str.toOct(addr));
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.reportError(BusPDP10.ERROR.RANGE_INVALID, addr, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* cleanMemory(addr, size)
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {number} addr
|
||||
* @param {number} size
|
||||
* @return {boolean} true if all blocks were clean, false if dirty; all blocks are cleaned in the process
|
||||
*/
|
||||
cleanMemory(addr, size)
|
||||
{
|
||||
var fClean = true;
|
||||
var iBlock = addr >>> this.nBlockShift;
|
||||
while (size > 0 && iBlock < this.aBusBlocks.length) {
|
||||
if (this.aBusBlocks[iBlock].fDirty) {
|
||||
this.aBusBlocks[iBlock].fDirty = fClean = false;
|
||||
this.aBusBlocks[iBlock].fDirtyEver = true;
|
||||
}
|
||||
size -= this.nBlockSize;
|
||||
iBlock++;
|
||||
}
|
||||
return fClean;
|
||||
}
|
||||
|
||||
/**
|
||||
* zeroMemory(addr, size, pattern)
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {number} addr
|
||||
* @param {number} size
|
||||
* @param {number} [pattern]
|
||||
*/
|
||||
zeroMemory(addr, size, pattern)
|
||||
{
|
||||
var off = addr & this.nBlockLimit;
|
||||
var iBlock = addr >>> this.nBlockShift;
|
||||
while (size > 0 && iBlock < this.aBusBlocks.length) {
|
||||
this.aBusBlocks[iBlock].zero(off, size, pattern);
|
||||
size -= this.nBlockSize;
|
||||
iBlock++;
|
||||
off = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* scanMemory(info, addr, size)
|
||||
*
|
||||
* Returns a BusInfoPDP10 object for the specified address range.
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {BusInfoPDP10} [info] previous BusInfoPDP10, 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 {BusInfoPDP10} updated info (or new info if no previous info provided)
|
||||
*/
|
||||
scanMemory(info, addr, size)
|
||||
{
|
||||
if (addr == null) addr = 0;
|
||||
if (size == null) size = (this.addrTotal - addr) | 0;
|
||||
if (info == null) info = {cbTotal: 0, cBlocks: 0, aBlocks: []};
|
||||
|
||||
var iBlock = addr >>> this.nBlockShift;
|
||||
var iBlockMax = ((addr + size - 1) >>> this.nBlockShift);
|
||||
|
||||
info.cbTotal = 0;
|
||||
info.cBlocks = 0;
|
||||
while (iBlock <= iBlockMax) {
|
||||
var block = this.aBusBlocks[iBlock];
|
||||
info.cbTotal += block.size;
|
||||
if (block.size) {
|
||||
info.aBlocks.push(/** @type {BlockInfoPDP10} */ (Usr.initBitFields(BlockInfoPDP10, iBlock, 0, 0, block.type)));
|
||||
info.cBlocks++
|
||||
}
|
||||
iBlock++;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* removeMemory(addr, size)
|
||||
*
|
||||
* Replaces every block in the specified address range with empty Memory blocks that ignore all reads/writes.
|
||||
*
|
||||
* TODO: Update the removeMemory() interface to reflect the relaxed requirements of the addMemory() interface.
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {number} addr
|
||||
* @param {number} size
|
||||
* @return {boolean} true if successful, false if not
|
||||
*/
|
||||
removeMemory(addr, size)
|
||||
{
|
||||
if (!(addr & this.nBlockLimit) && size && !(size & this.nBlockLimit)) {
|
||||
var iBlock = addr >>> this.nBlockShift;
|
||||
while (size > 0) {
|
||||
var blockOld = this.aBusBlocks[iBlock];
|
||||
var blockNew = new MemoryPDP10(this, addr);
|
||||
blockNew.copyBreakpoints(this.dbg, blockOld);
|
||||
this.aBusBlocks[iBlock++] = blockNew;
|
||||
addr = iBlock * this.nBlockSize;
|
||||
size -= this.nBlockSize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return this.reportError(BusPDP10.ERROR.RANGE_INVALID, addr, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* getMemoryBlocks(addr, size)
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {number} addr is the starting physical address
|
||||
* @param {number} size of the request, in bytes
|
||||
* @return {Array} of Memory blocks
|
||||
*/
|
||||
getMemoryBlocks(addr, size)
|
||||
{
|
||||
var aBlocks = [];
|
||||
var iBlock = addr >>> this.nBlockShift;
|
||||
while (size > 0 && iBlock < this.aBusBlocks.length) {
|
||||
aBlocks.push(this.aBusBlocks[iBlock++]);
|
||||
size -= this.nBlockSize;
|
||||
}
|
||||
return aBlocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* setMemoryBlocks(addr, size, aBlocks, type)
|
||||
*
|
||||
* If no type is specified, then specified address range uses all the provided blocks as-is;
|
||||
* this form of setMemoryBlocks() is used for complete physical aliases.
|
||||
*
|
||||
* Otherwise, new blocks are allocated with the specified type; the underlying memory from the
|
||||
* provided blocks is still used, but the new blocks may have different access to that memory.
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {number} addr is the starting physical address
|
||||
* @param {number} size of the request, in bytes
|
||||
* @param {Array} aBlocks as returned by getMemoryBlocks()
|
||||
* @param {number} [type] is one of the MemoryPDP10.TYPE constants
|
||||
*/
|
||||
setMemoryBlocks(addr, size, aBlocks, type)
|
||||
{
|
||||
var i = 0;
|
||||
var iBlock = addr >>> this.nBlockShift;
|
||||
while (size > 0 && iBlock < this.aBusBlocks.length) {
|
||||
var block = aBlocks[i++];
|
||||
this.assert(block);
|
||||
if (!block) break;
|
||||
if (type !== undefined) {
|
||||
var blockNew = new MemoryPDP10(this, addr);
|
||||
blockNew.clone(block, type, this.dbg);
|
||||
block = blockNew;
|
||||
}
|
||||
this.aBusBlocks[iBlock++] = block;
|
||||
size -= this.nBlockSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getWord(addr)
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {number} addr is a physical address
|
||||
* @return {number} word (16-bit) value at that address
|
||||
*/
|
||||
getWord(addr)
|
||||
{
|
||||
var off = addr & this.nBlockLimit;
|
||||
var iBlock = (addr & this.nBusMask) >>> this.nBlockShift;
|
||||
return this.aBusBlocks[iBlock].readWord(off, addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* setWord(addr, w)
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {number} addr is a physical address
|
||||
* @param {number} w is the word (16-bit) value to write
|
||||
*/
|
||||
setWord(addr, w)
|
||||
{
|
||||
var off = addr & this.nBlockLimit;
|
||||
var iBlock = (addr & this.nBusMask) >>> this.nBlockShift;
|
||||
this.aBusBlocks[iBlock].writeWord(off, w, addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* getBlockDirect(addr)
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {number} addr is a physical address
|
||||
* @return {MemoryPDP10}
|
||||
*/
|
||||
getBlockDirect(addr)
|
||||
{
|
||||
return this.aBusBlocks[(addr & this.nBusMask) >>> this.nBlockShift];
|
||||
}
|
||||
|
||||
/**
|
||||
* getWordDirect(addr)
|
||||
*
|
||||
* This is used for device I/O and Debugger physical memory requests, not the CPU.
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {number} addr is a physical address
|
||||
* @return {number} word (16-bit) value at that address
|
||||
*/
|
||||
getWordDirect(addr)
|
||||
{
|
||||
var w;
|
||||
var off = addr & this.nBlockLimit;
|
||||
var block = this.getBlockDirect(addr);
|
||||
w = block.readWordDirect(off, addr);
|
||||
return w;
|
||||
}
|
||||
|
||||
/**
|
||||
* setWordDirect(addr, w)
|
||||
*
|
||||
* This is used for device I/O and Debugger physical memory requests, not the CPU.
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {number} addr is a physical address
|
||||
* @param {number} w is the word (16-bit) value to write (we truncate it to 16 bits to be safe)
|
||||
*/
|
||||
setWordDirect(addr, w)
|
||||
{
|
||||
var off = addr & this.nBlockLimit;
|
||||
var block = this.getBlockDirect(addr);
|
||||
block.writeWordDirect(off, w & 0xffff, addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* addMemBreak(addr, fWrite)
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {number} addr
|
||||
* @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
|
||||
*/
|
||||
addMemBreak(addr, fWrite)
|
||||
{
|
||||
if (DEBUGGER) {
|
||||
var iBlock = addr >>> this.nBlockShift;
|
||||
this.aBusBlocks[iBlock].addBreakpoint(addr & this.nBlockLimit, fWrite);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* removeMemBreak(addr, fWrite)
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {number} addr
|
||||
* @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
|
||||
*/
|
||||
removeMemBreak(addr, fWrite)
|
||||
{
|
||||
if (DEBUGGER) {
|
||||
var iBlock = addr >>> this.nBlockShift;
|
||||
this.aBusBlocks[iBlock].removeBreakpoint(addr & this.nBlockLimit, fWrite);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* saveMemory(fAll)
|
||||
*
|
||||
* The only memory blocks we save are those marked as dirty, but most likely all of RAM will have been marked dirty,
|
||||
* and even if our dirty-memory flags were as smart as our dirty-sector flags (ie, were set only when a write changed
|
||||
* what was already there), it's unlikely that would reduce the number of RAM blocks we must save/restore. At least
|
||||
* all the ROM blocks should be clean (except in the unlikely event that the Debugger was used to modify them).
|
||||
*
|
||||
* All dirty blocks will be stored in a single array, as pairs of block numbers and data arrays, like so:
|
||||
*
|
||||
* [iBlock0, [dw0, dw1, ...], iBlock1, [dw0, dw1, ...], ...]
|
||||
*
|
||||
* In a normal 4Kb block, there will be 1K DWORD values in the data array. Remember that each DWORD is a signed 32-bit
|
||||
* integer (because they are formed using bit-wise operator rather than floating-point math operators), so don't be
|
||||
* surprised to see negative numbers in the data.
|
||||
*
|
||||
* The above example assumes "uncompressed" data arrays. If we choose to use "compressed" data arrays, the data arrays
|
||||
* will look like:
|
||||
*
|
||||
* [count0, dw0, count1, dw1, ...]
|
||||
*
|
||||
* where each count indicates how many times the following DWORD value occurs. A data array length less than 1K indicates
|
||||
* that it's compressed, since we'll only store them in compressed form if they actually shrank, and we'll use State
|
||||
* helper methods compress() and decompress() to create and expand the compressed data arrays.
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {boolean} [fAll] (true to save all non-ROM memory blocks, regardless of their dirty flags)
|
||||
* @return {Array} a
|
||||
*/
|
||||
saveMemory(fAll)
|
||||
{
|
||||
var i = 0;
|
||||
var a = [];
|
||||
|
||||
for (var iBlock = 0; iBlock < this.nBlockTotal; iBlock++) {
|
||||
var block = this.aBusBlocks[iBlock];
|
||||
/*
|
||||
* We have to check both fDirty and fDirtyEver, because we may have called cleanMemory() on some of
|
||||
* the memory blocks (eg, video memory), and while cleanMemory() will clear a dirty block's fDirty flag,
|
||||
* it also sets the dirty block's fDirtyEver flag, which is left set for the lifetime of the machine.
|
||||
*/
|
||||
if (fAll && block.type != MemoryPDP10.TYPE.ROM || block.fDirty || block.fDirtyEver) {
|
||||
a[i++] = iBlock;
|
||||
a[i++] = State.compress(block.save());
|
||||
}
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* restoreMemory(a)
|
||||
*
|
||||
* This restores the contents of all Memory blocks; called by CPUState.restore().
|
||||
*
|
||||
* In theory, we ONLY have to save/restore block contents. Other block attributes,
|
||||
* like the type, the memory controller (if any), and the active memory access functions,
|
||||
* should already be restored, since every component (re)allocates all the memory blocks
|
||||
* it was using when it's restored. And since the CPU is guaranteed to be the last
|
||||
* component to be restored, all those blocks (and their attributes) should be in place now.
|
||||
*
|
||||
* See saveMemory() for more information on how the memory block contents are saved.
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {Array} a
|
||||
* @return {boolean} true if successful, false if not
|
||||
*/
|
||||
restoreMemory(a)
|
||||
{
|
||||
var i;
|
||||
for (i = 0; i < a.length - 1; i += 2) {
|
||||
var iBlock = a[i];
|
||||
var adw = a[i+1];
|
||||
if (adw && adw.length < this.nBlockLen) {
|
||||
adw = State.decompress(adw, this.nBlockLen);
|
||||
}
|
||||
var block = this.aBusBlocks[iBlock];
|
||||
if (!block || !block.restore(adw)) {
|
||||
/*
|
||||
* Either the block to restore hasn't been allocated, indicating a change in the machine
|
||||
* configuration since it was last saved (the most likely explanation) or there's some internal
|
||||
* inconsistency (eg, the block size is wrong).
|
||||
*/
|
||||
Component.error("Unable to restore memory block " + iBlock);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* getMemoryLimit(type)
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {number} type is one of the MemoryPDP10.TYPE constants
|
||||
* @return {number} (the limiting address of the specified memory type, zero if none)
|
||||
*/
|
||||
getMemoryLimit(type)
|
||||
{
|
||||
var addr = 0;
|
||||
for (var iBlock = 0; iBlock < this.aBusBlocks.length; iBlock++) {
|
||||
var block = this.aBusBlocks[iBlock];
|
||||
if (block.type == type) {
|
||||
addr = block.addr + block.used;
|
||||
}
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* reportError(errNum, addr, size, fQuiet)
|
||||
*
|
||||
* @this {BusPDP10}
|
||||
* @param {number} errNum
|
||||
* @param {number} addr
|
||||
* @param {number} size
|
||||
* @param {boolean} [fQuiet] (true if any error should be quietly logged)
|
||||
* @return {boolean} false
|
||||
*/
|
||||
reportError(errNum, addr, size, fQuiet)
|
||||
{
|
||||
var sError = "Memory block error (" + errNum + ": " + Str.toHex(addr) + "," + Str.toHex(size) + ")";
|
||||
if (fQuiet) {
|
||||
if (this.dbg) {
|
||||
this.dbg.message(sError);
|
||||
} else {
|
||||
this.log(sError);
|
||||
}
|
||||
} else {
|
||||
Component.error(sError);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
BusPDP10.ERROR = {
|
||||
RANGE_INUSE: 1,
|
||||
RANGE_INVALID: 2
|
||||
};
|
||||
|
||||
if (NODE) module.exports = BusPDP10;
|
||||
1640
modules/pdp10/lib/computer.js
Normal file
1640
modules/pdp10/lib/computer.js
Normal file
File diff suppressed because it is too large
Load diff
1274
modules/pdp10/lib/cpu.js
Normal file
1274
modules/pdp10/lib/cpu.js
Normal file
File diff suppressed because it is too large
Load diff
802
modules/pdp10/lib/cpustate.js
Normal file
802
modules/pdp10/lib/cpustate.js
Normal file
|
|
@ -0,0 +1,802 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP-10 CPU component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
|
||||
*
|
||||
* 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 modified copy of this work
|
||||
* and to display that copyright notice when the software starts running; see COPYRIGHT in
|
||||
* <http://pcjs.org/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 PDP10 = require("./defines");
|
||||
var BusPDP10 = require("./bus");
|
||||
var CPUPDP10 = require("./cpu");
|
||||
var MessagesPDP10 = require("./messages");
|
||||
var MemoryPDP10 = require("./memory");
|
||||
}
|
||||
|
||||
/*
|
||||
* Overview of Device Interrupt Support
|
||||
*
|
||||
* Originally, the CPU maintained a queue of requested interrupts. Entries in this queue recorded a device's
|
||||
* priority, vector, and delay (ie, a number of instructions to execute before dispatching the interrupt). This
|
||||
* queue would constantly grow and shrink as requests were issued and dispatched, and as long as there was something
|
||||
* in the queue, the CPU was constantly examining it.
|
||||
*
|
||||
* Now we are trying something more efficient. First, for devices that require delays (like the SerialPort's receiver
|
||||
* and transmitter buffer registers, which are supposed to "clock" the data in and out at a specific baud rate), the
|
||||
* CPU offers timer services that will "fire" a callback after a specified delay, which are much more efficient than
|
||||
* requiring the CPU to dive into an interrupt queue and decrement delay counts on every instruction.
|
||||
*
|
||||
* Second, devices that generate interrupts will allocate an IRQ object during initialization; we will no longer
|
||||
* be creating and destroying interrupt event objects and inserting/deleting them in a constantly changing queue.
|
||||
* Each IRQ contains properties that never change (eg, the vector and priority), along with a "next" pointer that's
|
||||
* only used when the IRQ is active.
|
||||
*
|
||||
* When a device decides it's time to interrupt (either at the end of some I/O operation or when a timer has fired),
|
||||
* it will simply set the IRQ, which basically means that the IRQ will be linked onto a list of active IRQs, in
|
||||
* priority order, so that when the CPU is ready to acknowledge interrupts, it need only check the top of the active
|
||||
* IRQ list.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* vector: number,
|
||||
* priority: number,
|
||||
* message: number,
|
||||
* name: (string|null),
|
||||
* next: (IRQ|null)
|
||||
* }}
|
||||
*/
|
||||
var IRQ;
|
||||
|
||||
/**
|
||||
* @class CPUStatePDP10
|
||||
* @unrestricted
|
||||
*/
|
||||
class CPUStatePDP10 extends CPUPDP10 {
|
||||
/**
|
||||
* CPUStatePDP10(parmsCPU)
|
||||
*
|
||||
* The CPUStatePDP10 class uses the following (parmsCPU) properties:
|
||||
*
|
||||
* model: a number (eg, 1001) that should match one of the PDP10.MODEL_* values
|
||||
* addrReset: 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.
|
||||
*
|
||||
* @param {Object} parmsCPU
|
||||
*/
|
||||
constructor(parmsCPU)
|
||||
{
|
||||
var nCyclesDefault = 0;
|
||||
var model = +parmsCPU['model'] || PDP10.MODEL_KA10;
|
||||
|
||||
switch(model) {
|
||||
case PDP10.MODEL_KA10:
|
||||
default:
|
||||
nCyclesDefault = 6666667;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* ES6 ALERT: Classes cannot access "this" until all superclasses have been initialized as well.
|
||||
*/
|
||||
super(parmsCPU, nCyclesDefault);
|
||||
|
||||
this.model = model;
|
||||
this.addrReset = +parmsCPU['addrReset'] || 0;
|
||||
|
||||
/** @type {IRQ|null} */
|
||||
this.irqNext = null; // the head of the active IRQ list, in priority order
|
||||
|
||||
/** @type {Array.<IRQ>} */
|
||||
this.aIRQs = []; // list of all IRQs, active or not (to be used for auto-configuration)
|
||||
|
||||
this.flags.complete = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* Called once the Bus has been initialized.
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {ComputerPDP10} cmp
|
||||
* @param {BusPDP10} bus
|
||||
* @param {CPUPDP10} cpu
|
||||
* @param {DebuggerPDP10} dbg
|
||||
*/
|
||||
initBus(cmp, bus, cpu, dbg)
|
||||
{
|
||||
super.initBus(cmp, bus, cpu, dbg);
|
||||
}
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
*/
|
||||
reset()
|
||||
{
|
||||
this.status("Model " + this.model);
|
||||
if (this.flags.running) this.stopCPU();
|
||||
this.initCPU();
|
||||
this.resetCycles();
|
||||
this.clearError(); // clear any fatal error/exception that setError() may have flagged
|
||||
super.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* initCPU()
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
*/
|
||||
initCPU()
|
||||
{
|
||||
this.regPC = this.pcLast = this.addrReset;
|
||||
|
||||
/*
|
||||
* This is queried and displayed by the Panel when it's not displaying its own ADDRESS register
|
||||
* (which takes precedence when, for example, you've manually halted the CPU and are independently
|
||||
* examining the contents of other addresses).
|
||||
*
|
||||
* We initialize it to the current PC.
|
||||
*/
|
||||
this.addrLast = this.regPC;
|
||||
|
||||
/*
|
||||
* opFlags contains various conditions that stepCPU() needs to be aware of.
|
||||
*/
|
||||
this.opFlags = 0;
|
||||
|
||||
this.setMemoryAccess();
|
||||
|
||||
this.resetIRQs();
|
||||
}
|
||||
|
||||
/**
|
||||
* setMemoryAccess()
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
*/
|
||||
setMemoryAccess()
|
||||
{
|
||||
this.readWord = this.readWordFromPhysical;
|
||||
this.writeWord = this.writeWordToPhysical;
|
||||
}
|
||||
|
||||
/**
|
||||
* setReset(addr, fStart, bUnit, addrStack)
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {number} addr
|
||||
* @param {boolean} [fStart] (true if a "startable" image was just loaded, false if not)
|
||||
* @param {number} [bUnit] (boot unit #)
|
||||
* @param {number} [addrStack]
|
||||
*/
|
||||
setReset(addr, fStart, bUnit, addrStack)
|
||||
{
|
||||
this.addrReset = addr;
|
||||
|
||||
this.setPC(addr);
|
||||
|
||||
if (fStart) {
|
||||
if (!this.flags.powered) {
|
||||
this.flags.autoStart = true;
|
||||
}
|
||||
else if (!this.flags.running) {
|
||||
this.startCPU();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.dbg && this.flags.powered) {
|
||||
/*
|
||||
* TODO: Review the decision to always stop the CPU if the Debugger is loaded. Note that
|
||||
* when stopCPU() stops a running CPU, the Debugger gets notified, so no need to notify it again.
|
||||
*
|
||||
* TODO: There are more serious problems to deal with if another component is slamming a new PC down
|
||||
* the CPU's throat (presumably while also dropping some new code into RAM) while the CPU is running;
|
||||
* we should probably force a complete reset, but for now, it's up to the user to hit the reset button
|
||||
* themselves.
|
||||
*/
|
||||
if (!this.stopCPU() && !this.cmp.flags.reset) {
|
||||
this.dbg.updateStatus();
|
||||
this.cmp.updateDisplays(-1);
|
||||
}
|
||||
}
|
||||
else if (fStart === false) {
|
||||
this.stopCPU();
|
||||
}
|
||||
}
|
||||
if (!this.isRunning() && this.panel) this.panel.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* getChecksum()
|
||||
*
|
||||
* TODO: Implement
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @return {number} a 32-bit summation of key elements of the current CPU state (used by the CPU checksum code)
|
||||
*/
|
||||
getChecksum()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @return {Object|null}
|
||||
*/
|
||||
save()
|
||||
{
|
||||
var state = new State(this);
|
||||
state.set(0, [
|
||||
this.regPC,
|
||||
this.pcLast,
|
||||
this.addrLast,
|
||||
this.opFlags
|
||||
]);
|
||||
state.set(1, []);
|
||||
state.set(2, [this.nTotalCycles, this.getSpeed(), this.flags.autoStart]);
|
||||
state.set(3, this.saveIRQs());
|
||||
state.set(4, this.saveTimers());
|
||||
return state.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* restore(data)
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {Object} data
|
||||
* @return {boolean} true if restore successful, false if not
|
||||
*/
|
||||
restore(data)
|
||||
{
|
||||
/*
|
||||
* ES6 ALERT: A handy destructuring assignment, which makes it easy to perform the inverse
|
||||
* of what save() does when it collects a bunch of object properties into an array.
|
||||
*/
|
||||
[
|
||||
this.regPC,
|
||||
this.pcLast,
|
||||
this.addrLast,
|
||||
this.opFlags
|
||||
] = data[0];
|
||||
|
||||
var a = data[2];
|
||||
this.nTotalCycles = a[0];
|
||||
this.setSpeed(a[1]);
|
||||
this.flags.autoStart = a[2];
|
||||
|
||||
this.restoreIRQs(data[3]);
|
||||
this.restoreTimers(data[4]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* getOpcode()
|
||||
*
|
||||
* NOTE: This function is nothing more than a convenience, and we fully expect it to be inlined at runtime.
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @return {number}
|
||||
*/
|
||||
getOpcode()
|
||||
{
|
||||
var pc = this.regPC;
|
||||
var opCode = this.readWord(pc);
|
||||
this.regPC = (pc + 1) % PDP10.ADDR_LIMIT;
|
||||
return opCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* advancePC(off)
|
||||
*
|
||||
* NOTE: This function is nothing more than a convenience, and we fully expect it to be inlined at runtime.
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {number} off
|
||||
* @return {number} (original PC)
|
||||
*/
|
||||
advancePC(off)
|
||||
{
|
||||
var pc = this.regPC;
|
||||
this.regPC = (pc + off) % PDP10.ADDR_LIMIT;
|
||||
return pc;
|
||||
}
|
||||
|
||||
/**
|
||||
* getPC()
|
||||
*
|
||||
* NOTE: This function is nothing more than a convenience, and we fully expect it to be inlined at runtime.
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @return {number}
|
||||
*/
|
||||
getPC()
|
||||
{
|
||||
return this.regPC;
|
||||
}
|
||||
|
||||
/**
|
||||
* getLastAddr()
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @return {number}
|
||||
*/
|
||||
getLastAddr()
|
||||
{
|
||||
return this.addrLast;
|
||||
}
|
||||
|
||||
/**
|
||||
* getLastPC()
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @return {number}
|
||||
*/
|
||||
getLastPC()
|
||||
{
|
||||
return this.pcLast;
|
||||
}
|
||||
|
||||
/**
|
||||
* setPC()
|
||||
*
|
||||
* NOTE: Unlike other PCjs emulators, such as PCx86, where all PC updates MUST go through the setPC()
|
||||
* function, this function is nothing more than a convenience, because in the PDP-11, the PC can be loaded
|
||||
* like any other general register. We fully expect this function to be inlined at runtime.
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {number} addr
|
||||
*/
|
||||
setPC(addr)
|
||||
{
|
||||
this.regPC = addr % PDP10.ADDR_LIMIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* addIRQ(vector, priority, message)
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {number} vector (-1 for floating vector)
|
||||
* @param {number} priority
|
||||
* @param {number} [message]
|
||||
* @return {IRQ}
|
||||
*/
|
||||
addIRQ(vector, priority, message)
|
||||
{
|
||||
var irq = {vector: vector, priority: priority, message: message || 0, name: null, next: null};
|
||||
this.aIRQs.push(irq);
|
||||
return irq;
|
||||
}
|
||||
|
||||
/**
|
||||
* insertIRQ(irq)
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {IRQ} irq
|
||||
*/
|
||||
insertIRQ(irq)
|
||||
{
|
||||
if (irq != this.irqNext) {
|
||||
var irqPrev = this.irqNext;
|
||||
if (!irqPrev || irqPrev.priority <= irq.priority) {
|
||||
irq.next = irqPrev;
|
||||
this.irqNext = irq;
|
||||
} else {
|
||||
do {
|
||||
var irqNext = irqPrev.next;
|
||||
if (!irqNext || irqNext.priority <= irq.priority) {
|
||||
irq.next = irqNext;
|
||||
irqPrev.next = irq;
|
||||
break;
|
||||
}
|
||||
irqPrev = irqNext;
|
||||
} while (irqPrev);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* See the writeXCSR() function for an explanation of why signalling an IRQ hardware interrupt
|
||||
* should be done using IRQ_DELAY rather than setting IRQ directly.
|
||||
*/
|
||||
this.opFlags |= PDP10.OPFLAG.IRQ_DELAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* removeIRQ(irq)
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {IRQ} irq
|
||||
*/
|
||||
removeIRQ(irq)
|
||||
{
|
||||
var irqPrev = this.irqNext;
|
||||
if (irqPrev == irq) {
|
||||
this.irqNext = irq.next;
|
||||
} else {
|
||||
while (irqPrev) {
|
||||
var irqNext = irqPrev.next;
|
||||
if (irqNext == irq) {
|
||||
irqPrev.next = irqNext.next;
|
||||
break;
|
||||
}
|
||||
irqPrev = irqNext;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* We could also set irq.next to null now, but strictly speaking, that shouldn't be necessary.
|
||||
*
|
||||
* Last but not least, if there's still an IRQ on the active IRQ list, we need to make sure IRQ_DELAY
|
||||
* is still set.
|
||||
*/
|
||||
if (this.irqNext) {
|
||||
this.opFlags |= PDP10.OPFLAG.IRQ_DELAY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* setIRQ(irq)
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {IRQ|null} irq
|
||||
*/
|
||||
setIRQ(irq)
|
||||
{
|
||||
if (irq) {
|
||||
this.insertIRQ(irq);
|
||||
if (irq.message && this.messageEnabled(irq.message | MessagesPDP10.INT)) {
|
||||
this.printMessage("setIRQ(vector=" + Str.toOct(irq.vector) + ",priority=" + irq.priority + ")", true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* clearIRQ(irq)
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {IRQ|null} irq
|
||||
*/
|
||||
clearIRQ(irq)
|
||||
{
|
||||
if (irq) {
|
||||
this.removeIRQ(irq);
|
||||
if (irq.message && this.messageEnabled(irq.message | MessagesPDP10.INT)) {
|
||||
this.printMessage("clearIRQ(vector=" + Str.toOct(irq.vector) + ",priority=" + irq.priority + ")", true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* findIRQ(vector)
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {number} vector
|
||||
* @return {IRQ|null}
|
||||
*/
|
||||
findIRQ(vector)
|
||||
{
|
||||
for (var i = 0; i < this.aIRQs.length; i++) {
|
||||
var irq = this.aIRQs[i];
|
||||
if (irq.vector === vector) return irq;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* checkIRQs(priority)
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {number} priority
|
||||
* @return {IRQ|null}
|
||||
*/
|
||||
checkIRQs(priority)
|
||||
{
|
||||
return (this.irqNext && this.irqNext.priority > priority)? this.irqNext : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* resetIRQs(priority)
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
*/
|
||||
resetIRQs()
|
||||
{
|
||||
this.irqNext = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* saveIRQs()
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @return {Array.<number>}
|
||||
*/
|
||||
saveIRQs()
|
||||
{
|
||||
var aIRQVectors = [];
|
||||
var irq = this.irqNext;
|
||||
while (irq) {
|
||||
aIRQVectors.push(irq.vector);
|
||||
irq = irq.next;
|
||||
}
|
||||
return aIRQVectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* restoreIRQs(aIRQVectors)
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {Array.<number>} aIRQVectors
|
||||
*/
|
||||
restoreIRQs(aIRQVectors)
|
||||
{
|
||||
for (var i = aIRQVectors.length - 1; i >= 0; i--) {
|
||||
var irq = this.findIRQ(aIRQVectors[i]);
|
||||
this.assert(irq != null);
|
||||
if (irq) {
|
||||
irq.next = this.irqNext;
|
||||
this.irqNext = irq;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* checkInterrupts()
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @return {boolean} true if an interrupt was dispatched, false if not
|
||||
*/
|
||||
checkInterrupts()
|
||||
{
|
||||
var fInterrupt = false;
|
||||
|
||||
if (this.opFlags & PDP10.OPFLAG.IRQ) {
|
||||
|
||||
// var vector = PDP10.TRAP.PIRQ;
|
||||
// var priority = (this.regPIR & PDP10.PSW.PRI) >> PDP10.PSW.SHIFT.PRI;
|
||||
//
|
||||
// var irq = this.checkIRQs(priority);
|
||||
// if (irq) {
|
||||
// vector = irq.vector;
|
||||
// priority = irq.priority;
|
||||
// }
|
||||
//
|
||||
// if (this.dispatchInterrupt(vector, priority)) {
|
||||
// if (irq) this.removeIRQ(irq);
|
||||
// fInterrupt = true;
|
||||
// }
|
||||
|
||||
if (!this.irqNext) {
|
||||
this.opFlags &= ~PDP10.OPFLAG.IRQ;
|
||||
}
|
||||
}
|
||||
else if (this.opFlags & PDP10.OPFLAG.IRQ_DELAY) {
|
||||
/*
|
||||
* We know that IRQ (bit 2) is clear, so since IRQ_DELAY (bit 0) is set, incrementing opFlags
|
||||
* will eventually transform IRQ_DELAY into IRQ, without affecting any other (higher) bits.
|
||||
*/
|
||||
this.opFlags++;
|
||||
}
|
||||
return fInterrupt;
|
||||
}
|
||||
|
||||
/**
|
||||
* dispatchInterrupt(vector, priority)
|
||||
*
|
||||
* TODO: The process of dispatching an interrupt MUST cost some cycles; either trap() needs to assess
|
||||
* that cost, or we do.
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {number} vector
|
||||
* @param {number} priority
|
||||
* @return {boolean} (true if dispatched, false if not)
|
||||
*/
|
||||
dispatchInterrupt(vector, priority)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* isWaiting()
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @return {boolean} (true if OPFLAG.WAIT is set, false otherwise)
|
||||
*/
|
||||
isWaiting()
|
||||
{
|
||||
return !!(this.opFlags & PDP10.OPFLAG.WAIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* readWordFromPhysical(addr)
|
||||
*
|
||||
* This is a handler set up by setMemoryAccess(). All calls should go through readWord().
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {number} addr
|
||||
* @return {number}
|
||||
*/
|
||||
readWordFromPhysical(addr)
|
||||
{
|
||||
return this.bus.getWord(this.addrLast = addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* writeWordToPhysical(addr, data)
|
||||
*
|
||||
* This is a handler set up by setMemoryAccess(). All calls should go through writeWord().
|
||||
*
|
||||
* @this {CPUStatePDP10}
|
||||
* @param {number} addr
|
||||
* @param {number} data
|
||||
*/
|
||||
writeWordToPhysical(addr, data)
|
||||
{
|
||||
this.bus.setWord(this.addrLast = addr, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {CPUStatePDP10}
|
||||
* @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).
|
||||
*/
|
||||
stepCPU(nMinCycles)
|
||||
{
|
||||
/*
|
||||
* The Debugger uses complete 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 complete, 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;
|
||||
|
||||
/*
|
||||
* nDebugCheck is 1 if we want the Debugger's checkInstruction() to check every instruction,
|
||||
* -1 if we want it to check just the first instruction, and 0 if there's no need for any checks.
|
||||
*/
|
||||
var nDebugCheck = (DEBUGGER && this.dbg)? (this.dbg.checksEnabled()? 1 : (this.flags.starting? -1 : 0)) : 0;
|
||||
|
||||
/*
|
||||
* nDebugState is needed only when nDebugCheck is non-zero; it is -1 if this is a single-step, 0 if
|
||||
* this is the start of a new run, and 1 if this is a continuation of a previous run. It is used by
|
||||
* checkInstruction() to determine if it should skip breakpoint checks and/or HALT instructions (ie,
|
||||
* if nDebugState is <= zero).
|
||||
*/
|
||||
var nDebugState = (!nMinCycles)? -1 : (this.flags.starting? 0 : 1);
|
||||
this.flags.starting = false; // we've moved beyond "starting" and have officially "started" now
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/*
|
||||
* And finally, move the nDebugCheck state to an OPFLAG bit, so that the loop need check only one variable.
|
||||
*/
|
||||
this.opFlags = (this.opFlags & ~PDP10.OPFLAG.DEBUGGER) | (nDebugCheck? PDP10.OPFLAG.DEBUGGER : 0);
|
||||
|
||||
do {
|
||||
if (this.opFlags) {
|
||||
/*
|
||||
* NOTE: We still check DEBUGGER to ensure that this code will be compiled out of existence in
|
||||
* non-DEBUGGER builds.
|
||||
*/
|
||||
if (DEBUGGER && (this.opFlags & PDP10.OPFLAG.DEBUGGER)) {
|
||||
if (this.dbg.checkInstruction(this.getPC(), nDebugState)) {
|
||||
this.stopCPU();
|
||||
break;
|
||||
}
|
||||
if (!++nDebugCheck) this.opFlags &= ~PDP10.OPFLAG.DEBUGGER;
|
||||
if (!nDebugState) nDebugState++;
|
||||
}
|
||||
/*
|
||||
* If we're in the IRQ or WAIT state, check for any pending interrupts.
|
||||
*
|
||||
* NOTE: It's no coincidence that we're checking this BEFORE any pending traps, because in rare
|
||||
* cases (including some presented by those pesky "TRAP TEST" diagnostics), the process of dispatching
|
||||
* an interrupt can trigger a TRAP_SP stack overflow condition, which must be dealt with BEFORE we
|
||||
* execute the first instruction of the interrupt handler.
|
||||
*/
|
||||
if ((this.opFlags & (PDP10.OPFLAG.IRQ_MASK | PDP10.OPFLAG.WAIT)) /* && nDebugState >= 0 */) {
|
||||
if (this.checkInterrupts()) {
|
||||
if ((this.opFlags & PDP10.OPFLAG.DEBUGGER) && this.dbg.checkInstruction(this.getPC(), nDebugState)) {
|
||||
this.stopCPU();
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* Since an interrupt was just dispatched, altering the normal flow of time and changing
|
||||
* the future as we knew it, let's break out immediately if we're single-stepping, so that
|
||||
* the Debugger gets to see the first instruction of the interrupt handler. NOTE: This
|
||||
* assumes that we've still commented out the nDebugState check above that used to bypass
|
||||
* checkInterrupts() when single-stepping.
|
||||
*/
|
||||
if (nDebugState < 0) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.opFlags &= PDP10.OPFLAG.PRESERVE;
|
||||
|
||||
var opCode = this.getOpcode();
|
||||
|
||||
// this.decode(opCode);
|
||||
|
||||
} while (this.nStepCycles > 0);
|
||||
|
||||
return (this.flags.complete? this.nBurstCycles - this.nStepCycles : (this.flags.complete === false? -1 : 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* CPUStatePDP10.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "cpu", extracting the
|
||||
* JSON-encoded parameters for the CPUStatePDP10 constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor (which in turn invokes the CPU constructor)
|
||||
* to create a CPUStatePDP10 component, and then binding any associated HTML controls to the
|
||||
* new component.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var aeCPUs = Component.getElementsByClass(document, PDP10.APPCLASS, "cpu");
|
||||
for (var iCPU = 0; iCPU < aeCPUs.length; iCPU++) {
|
||||
var eCPU = aeCPUs[iCPU];
|
||||
var parmsCPU = Component.getComponentParms(eCPU);
|
||||
var cpu = new CPUStatePDP10(parmsCPU);
|
||||
Component.bindComponentControls(cpu, eCPU, PDP10.APPCLASS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize every CPU module on the page
|
||||
*/
|
||||
Web.onInit(CPUStatePDP10.init);
|
||||
|
||||
if (NODE) module.exports = CPUStatePDP10;
|
||||
3558
modules/pdp10/lib/debugger.js
Normal file
3558
modules/pdp10/lib/debugger.js
Normal file
File diff suppressed because it is too large
Load diff
148
modules/pdp10/lib/defines.js
Normal file
148
modules/pdp10/lib/defines.js
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/**
|
||||
* @fileoverview PDP10-specific compile-time definitions.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
|
||||
*
|
||||
* 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 modified copy of this work
|
||||
* and to display that copyright notice when the software starts running; see COPYRIGHT in
|
||||
* <http://pcjs.org/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";
|
||||
|
||||
/**
|
||||
* @define {string}
|
||||
*/
|
||||
var APPCLASS = "pdp10"; // this @define is the default application class (eg, "pcx86", "c1pjs")
|
||||
|
||||
/**
|
||||
* APPNAME is used more for display purposes than anything else now. APPCLASS is what matters in terms
|
||||
* of folder and file names, CSS styles, etc.
|
||||
*
|
||||
* @define {string}
|
||||
*/
|
||||
var APPNAME = "PDPjs"; // this @define is the default application name (eg, "PCx86", "C1Pjs")
|
||||
|
||||
/**
|
||||
* WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
|
||||
* In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate. When it's *false*,
|
||||
* nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
|
||||
* "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
|
||||
* debugger.js from the compilation process altogether.
|
||||
*
|
||||
* However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
|
||||
* I would like to skip loading debugger.js altogether. When doing that, we must ALSO arrange for an additional file
|
||||
* (nodebugger.js) to be loaded immediately after this file, which *explicitly* overrides DEBUGGER with *false*.
|
||||
*
|
||||
* @define {boolean}
|
||||
*/
|
||||
var DEBUGGER = true; // this @define is overridden by the Closure Compiler to remove Debugger-related support
|
||||
|
||||
/*
|
||||
* Combine all the shared globals and machine-specific globals into one machine-specific global object,
|
||||
* which all machine components should start using; eg: "if (PDP10.DEBUG) ..." instead of "if (DEBUG) ...".
|
||||
*/
|
||||
var PDP10 = {
|
||||
APPCLASS: APPCLASS,
|
||||
APPNAME: APPNAME,
|
||||
APPVERSION: APPVERSION, // shared
|
||||
COMPILED: COMPILED, // shared
|
||||
CSSCLASS: CSSCLASS, // shared
|
||||
DEBUG: DEBUG, // shared
|
||||
DEBUGGER: DEBUGGER,
|
||||
MAXDEBUG: MAXDEBUG, // shared
|
||||
PRIVATE: PRIVATE, // shared
|
||||
SITEHOST: SITEHOST, // shared
|
||||
XMLVERSION: XMLVERSION, // shared
|
||||
|
||||
/*
|
||||
* CPU model numbers (supported)
|
||||
*
|
||||
* The 11/20 includes the 11/10, which is not identified separately because there was
|
||||
* nothing functionally different about it.
|
||||
*
|
||||
* The 11/40 added the MODE bits to the PSW (but only KERNEL=00 and USER=11) and 18-bit
|
||||
* addressing via an MMU; there was still only one register set.
|
||||
*
|
||||
* The 11/45 added REGSET bit to the PSW (to support a second register set), SUPER=01
|
||||
* mode to the existing KERNEL=00 and USER=11 modes, separate I/D spaces, and other MMU
|
||||
* extensions (eg, MMR1 and MMR3).
|
||||
*
|
||||
* The 11/70 added 22-bit addressing and corresponding extensions to the MMU.
|
||||
*/
|
||||
MODEL_KA10: 1001,
|
||||
|
||||
/*
|
||||
* This constant is used to mark points in the code where the physical address being returned
|
||||
* is invalid and should not be used.
|
||||
*
|
||||
* In a 32-bit CPU, -1 (ie, 0xffffffff) could actually be a valid address, so consider changing
|
||||
* ADDR_INVALID to NaN or null (which is also why all ADDR_INVALID tests should use strict equality
|
||||
* operators).
|
||||
*
|
||||
* The main reason I'm NOT using NaN or null now is my concern that, by mixing non-numbers
|
||||
* (specifically, values outside the range of signed 32-bit integers), performance may suffer.
|
||||
*
|
||||
* WARNING: Like many of the properties defined here, ADDR_INVALID is a common constant, which the
|
||||
* Closure Compiler will happily inline (with or without @const annotations; in fact, I've yet to
|
||||
* see a @const annotation EVER improve automatic inlining). However, if you don't make ABSOLUTELY
|
||||
* certain that this file is included BEFORE the first reference to any of these properties, that
|
||||
* automatic inlining will no longer occur.
|
||||
*/
|
||||
ADDR_INVALID: -1,
|
||||
ADDR_LIMIT: Math.pow(2, 18),
|
||||
DATA_INVALID: 0,
|
||||
DATA_LIMIT: Math.pow(2, 36),
|
||||
/*
|
||||
* Assorted common opcodes
|
||||
*/
|
||||
OPCODE: {
|
||||
HALT: 0o000000000000, // TODO: Resolve
|
||||
INVALID: 0o777777777777 // TODO: Resolve
|
||||
},
|
||||
/*
|
||||
* Internal operation state flags
|
||||
*/
|
||||
OPFLAG: {
|
||||
IRQ_DELAY: 0x0001, // incremented until it becomes IRQ
|
||||
IRQ: 0x0002, // time to call checkInterrupts()
|
||||
IRQ_MASK: 0x0003,
|
||||
DEBUGGER: 0x0004, // set if the Debugger wants to perform checks
|
||||
WAIT: 0x0008, // WAIT operation in progress
|
||||
PRESERVE: 0x000F, // OPFLAG bits to preserve prior to the next instruction
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Combine all the shared globals and machine-specific globals into one machine-specific global object,
|
||||
* which all machine components should start using; eg: "if (PDP10.DEBUGGER)" instead of "if (DEBUGGER)".
|
||||
*/
|
||||
PDP10.APPCLASS = APPCLASS;
|
||||
PDP10.APPNAME = APPNAME;
|
||||
PDP10.DEBUGGER = DEBUGGER;
|
||||
|
||||
if (NODE) {
|
||||
global.APPCLASS = APPCLASS;
|
||||
global.APPNAME = APPNAME;
|
||||
global.DEBUGGER = DEBUGGER;
|
||||
global.PDP10 = PDP10;
|
||||
module.exports = PDP10;
|
||||
}
|
||||
172
modules/pdp10/lib/device.js
Normal file
172
modules/pdp10/lib/device.js
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
/**
|
||||
* @fileoverview Implements PDP-10 device support.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
|
||||
*
|
||||
* 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 modified copy of this work
|
||||
* and to display that copyright notice when the software starts running; see COPYRIGHT in
|
||||
* <http://pcjs.org/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 PDP10 = require("./defines");
|
||||
var BusPDP10 = require("./bus");
|
||||
var MemoryPDP10 = require("./memory");
|
||||
var MessagesPDP10 = require("./messages");
|
||||
}
|
||||
|
||||
class DevicePDP10 extends Component {
|
||||
/**
|
||||
* DevicePDP10(parmsDevice)
|
||||
*
|
||||
* @param {Object} parmsDevice
|
||||
*/
|
||||
constructor(parmsDevice)
|
||||
{
|
||||
super("Device", parmsDevice, MessagesPDP10.DEVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {DevicePDP10}
|
||||
* @param {ComputerPDP10} cmp
|
||||
* @param {BusPDP10} bus
|
||||
* @param {CPUStatePDP10} cpu
|
||||
* @param {DebuggerPDP10} dbg
|
||||
*/
|
||||
initBus(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.bus = bus;
|
||||
this.cmp = cmp;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* @this {DevicePDP10}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
powerUp(data, fRepower)
|
||||
{
|
||||
if (!fRepower) {
|
||||
if (!data) {
|
||||
this.reset();
|
||||
} else {
|
||||
if (!this.restore(data)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* powerDown(fSave, fShutdown)
|
||||
*
|
||||
* @this {DevicePDP10}
|
||||
* @param {boolean} [fSave]
|
||||
* @param {boolean} [fShutdown]
|
||||
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
|
||||
*/
|
||||
powerDown(fSave, fShutdown)
|
||||
{
|
||||
return fSave? this.save() : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* @this {DevicePDP10}
|
||||
*/
|
||||
reset()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* This implements save support for the DevicePDP10 component.
|
||||
*
|
||||
* @this {DevicePDP10}
|
||||
* @return {Object}
|
||||
*/
|
||||
save()
|
||||
{
|
||||
var state = new State(this);
|
||||
return state.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* restore(data)
|
||||
*
|
||||
* This implements restore support for the DevicePDP10 component.
|
||||
*
|
||||
* @this {DevicePDP10}
|
||||
* @param {Object} data
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
restore(data)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* DevicePDP10.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "device", extracting the
|
||||
* JSON-encoded parameters for the DevicePDP10 constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a DevicePDP10 component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var aeDevice = Component.getElementsByClass(document, PDP10.APPCLASS, "device");
|
||||
for (var iDevice = 0; iDevice < aeDevice.length; iDevice++) {
|
||||
var device;
|
||||
var eDevice = aeDevice[iDevice];
|
||||
var parmsDevice = Component.getComponentParms(eDevice);
|
||||
switch(parmsDevice['type']) {
|
||||
case 'default':
|
||||
device = new DevicePDP10(parmsDevice);
|
||||
Component.bindComponentControls(device, eDevice, PDP10.APPCLASS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize all the DevicePDP10 modules on the page.
|
||||
*/
|
||||
Web.onInit(DevicePDP10.init);
|
||||
|
||||
if (NODE) module.exports = DevicePDP10;
|
||||
645
modules/pdp10/lib/memory.js
Normal file
645
modules/pdp10/lib/memory.js
Normal file
|
|
@ -0,0 +1,645 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP-10 Memory component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
|
||||
*
|
||||
* 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 modified copy of this work
|
||||
* and to display that copyright notice when the software starts running; see COPYRIGHT in
|
||||
* <http://pcjs.org/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 Component = require("../../shared/lib/component");
|
||||
var Int36 = require("../../shared/lib/int36");
|
||||
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.readBits = this.readBitsDirect = this.readNone;
|
||||
this.readWord = this.readWordDirect = this.readWordDefault;
|
||||
this.writeBits = this.writeBitsDirect = this.writeNone;
|
||||
this.writeWord = this.writeWordDirect = this.writeWordDefault;
|
||||
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.<number>|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.<number>|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]
|
||||
*/
|
||||
zero(off, len, pattern)
|
||||
{
|
||||
var i;
|
||||
off = off || 0;
|
||||
pattern = Int36.validate(pattern || 0);
|
||||
/*
|
||||
* NOTE: If len happens to be larger than the block, that's OK, because we also bounds-check the index.
|
||||
*/
|
||||
if (len === undefined) len = this.size;
|
||||
Component.assert(off >= 0 && off < this.size);
|
||||
for (i = off; len-- && i < this.size; i++) this.writeWordDirect(off, pattern, this.addr + off);
|
||||
}
|
||||
|
||||
/**
|
||||
* setAccess(afn, fDirect)
|
||||
*
|
||||
* The afn parameter should be a 4-entry function table containing two bits handlers and
|
||||
* two word 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 that fall
|
||||
* back to the bits handlers, and if one or both bits handlers are undefined, they default
|
||||
* to handlers that simply ignore the access.
|
||||
*
|
||||
* 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.<function()>} [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.<function()>} afn
|
||||
* @param {boolean} [fDirect]
|
||||
*/
|
||||
setReadAccess(afn, fDirect)
|
||||
{
|
||||
if (!fDirect || !this.cReadBreakpoints) {
|
||||
this.readBits = afn[0] || this.readNone;
|
||||
this.readWord = afn[2] || this.readWordDefault;
|
||||
}
|
||||
if (fDirect || fDirect === undefined) {
|
||||
this.readBitsDirect = afn[0] || this.readNone;
|
||||
this.readWordDirect = afn[2] || this.readWordDefault;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* setWriteAccess(afn, fDirect)
|
||||
*
|
||||
* @this {MemoryPDP10}
|
||||
* @param {Array.<function()>} afn
|
||||
* @param {boolean} [fDirect]
|
||||
*/
|
||||
setWriteAccess(afn, fDirect)
|
||||
{
|
||||
if (!fDirect || !this.cWriteBreakpoints) {
|
||||
this.writeBits = !this.fReadOnly && afn[1] || this.writeNone;
|
||||
this.writeWord = !this.fReadOnly && afn[3] || this.writeWordDefault;
|
||||
}
|
||||
if (fDirect || fDirect === undefined) {
|
||||
this.writeBitsDirect = afn[1] || this.writeNone;
|
||||
this.writeWordDirect = afn[3] || this.writeWordDefault;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* resetReadAccess()
|
||||
*
|
||||
* @this {MemoryPDP10}
|
||||
*/
|
||||
resetReadAccess()
|
||||
{
|
||||
this.readBits = this.readBitsDirect;
|
||||
this.readWord = this.readWordDirect;
|
||||
}
|
||||
|
||||
/**
|
||||
* resetWriteAccess()
|
||||
*
|
||||
* @this {MemoryPDP10}
|
||||
*/
|
||||
resetWriteAccess()
|
||||
{
|
||||
this.writeBits = this.fReadOnly? this.writeNone : this.writeBitsDirect;
|
||||
this.writeWord = this.fReadOnly? this.writeWordDefault : 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)
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* readWordDefault(off, addr)
|
||||
*
|
||||
* @this {MemoryPDP10}
|
||||
* @param {number} off
|
||||
* @param {number} addr
|
||||
* @return {number}
|
||||
*/
|
||||
readWordDefault(off, addr)
|
||||
{
|
||||
return this.readWord(off, addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* writeWordDefault(w, off, addr)
|
||||
*
|
||||
* @this {MemoryPDP10}
|
||||
* @param {number} w
|
||||
* @param {number} off
|
||||
* @param {number} addr
|
||||
*/
|
||||
writeWordDefault(w, off, addr)
|
||||
{
|
||||
this.writeWord(w, off, addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* readBitsMemory(offBits, lenBits, off, addr)
|
||||
*
|
||||
* @this {MemoryPDP10}
|
||||
* @param {number} offBits (the bit position of the right-most bit of the result, using modern bit numbering)
|
||||
* @param {number} lenBits
|
||||
* @param {number} off
|
||||
* @param {number} addr
|
||||
* @return {number}
|
||||
*/
|
||||
readBitsMemory(offBits, lenBits, off, addr)
|
||||
{
|
||||
var w = this.aw[off];
|
||||
if (offBits + lenBits <= 32) {
|
||||
w = (w >> offBits) & ((1 << lenBits) - 1);
|
||||
} else {
|
||||
w = Math.trunc(w / Math.pow(2, offBits)) % Math.pow(2, lenBits);
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
/**
|
||||
* readWordMemory(off, addr)
|
||||
*
|
||||
* @this {MemoryPDP10}
|
||||
* @param {number} off
|
||||
* @param {number} addr
|
||||
* @return {number}
|
||||
*/
|
||||
readWordMemory(off, addr)
|
||||
{
|
||||
return this.aw[off];
|
||||
}
|
||||
|
||||
/**
|
||||
* writeBitsMemory(offBits, lenBits, bits, off, addr)
|
||||
*
|
||||
* @this {MemoryPDP10}
|
||||
* @param {number} offBits (the bit position of the right-most bit of the result, using modern bit numbering)
|
||||
* @param {number} lenBits
|
||||
* @param {number} bits (only the right-most lenBits of bits are used, so this value doesn't need to be pre-masked)
|
||||
* @param {number} off
|
||||
* @param {number} addr
|
||||
*/
|
||||
writeBitsMemory(offBits, lenBits, bits, off, addr)
|
||||
{
|
||||
var w = this.aw[off];
|
||||
if (offBits + lenBits <= 32) {
|
||||
var bitsMask = ((1 << lenBits) - 1) << offBits;
|
||||
w = (w & ~bitsMask) | ((bits << offBits) & bitsMask);
|
||||
} else {
|
||||
var shiftBits = Math.pow(2, offBits);
|
||||
bits %= Math.pow(2, lenBits);
|
||||
var v = (w % Math.pow(2, offBits + lenBits));
|
||||
w = (w - v) + (bits * shiftBits) + (v % shiftBits);
|
||||
}
|
||||
if (this.aw[off] != w) {
|
||||
this.aw[off] = w;
|
||||
this.fDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* writeWordMemory(w, off, addr)
|
||||
*
|
||||
* @this {MemoryPDP10}
|
||||
* @param {number} w
|
||||
* @param {number} off
|
||||
* @param {number} addr
|
||||
*/
|
||||
writeWordMemory(w, off, addr)
|
||||
{
|
||||
this.aw[off] = w;
|
||||
this.fDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* readBitsChecked(offBits, lenBits, off, addr)
|
||||
*
|
||||
* @this {MemoryPDP10}
|
||||
* @param {number} offBits (the bit position of the right-most bit of the result, using modern bit numbering)
|
||||
* @param {number} lenBits
|
||||
* @param {number} off
|
||||
* @param {number} addr
|
||||
* @return {number}
|
||||
*/
|
||||
readBitsChecked(offBits, lenBits, off, addr)
|
||||
{
|
||||
if (DEBUGGER && this.dbg && this.addr != null) {
|
||||
this.dbg.checkMemoryRead(this.addr + off);
|
||||
}
|
||||
return this.readBitsDirect(offBits, lenBits, off, addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* writeBitsChecked(offBits, lenBits, bits, off, addr)
|
||||
*
|
||||
* @this {MemoryPDP10}
|
||||
* @param {number} offBits (the bit position of the right-most bit of the result, using modern bit numbering)
|
||||
* @param {number} lenBits
|
||||
* @param {number} bits
|
||||
* @param {number} off
|
||||
* @param {number} addr
|
||||
*/
|
||||
writeBitsChecked(offBits, lenBits, bits, off, addr)
|
||||
{
|
||||
if (DEBUGGER && this.dbg && this.addr != null) {
|
||||
this.dbg.checkMemoryWrite(this.addr + off);
|
||||
}
|
||||
if (this.fReadOnly) this.writeNone(bits, off, addr); else this.writeBitsDirect(offBits, lenBits, bits, 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 4 handlers (ie, 2 bits handlers and 2 word handlers) are undefined.
|
||||
*
|
||||
MemoryPDP10.afnNone = [
|
||||
MemoryPDP10.prototype.readNone,
|
||||
MemoryPDP10.prototype.writeNone,
|
||||
MemoryPDP10.prototype.readWordDefault,
|
||||
MemoryPDP10.prototype.writeWordDefault
|
||||
];
|
||||
*/
|
||||
MemoryPDP10.afnNone = [];
|
||||
|
||||
MemoryPDP10.afnMemory = [
|
||||
MemoryPDP10.prototype.readBitsMemory,
|
||||
MemoryPDP10.prototype.writeBitsMemory,
|
||||
MemoryPDP10.prototype.readWordMemory,
|
||||
MemoryPDP10.prototype.writeWordMemory
|
||||
];
|
||||
|
||||
MemoryPDP10.afnChecked = [
|
||||
MemoryPDP10.prototype.readBitsChecked,
|
||||
MemoryPDP10.prototype.writeBitsChecked,
|
||||
MemoryPDP10.prototype.readWordChecked,
|
||||
MemoryPDP10.prototype.writeWordChecked
|
||||
];
|
||||
|
||||
if (NODE) module.exports = MemoryPDP10;
|
||||
104
modules/pdp10/lib/messages.js
Normal file
104
modules/pdp10/lib/messages.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* @fileoverview Defines PDP-10 message categories.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
|
||||
*
|
||||
* 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 modified copy of this work
|
||||
* and to display that copyright notice when the software starts running; see COPYRIGHT in
|
||||
* <http://pcjs.org/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";
|
||||
|
||||
var MessagesPDP10 = {
|
||||
CPU: 0x00000001,
|
||||
TRAP: 0x00000002,
|
||||
FAULT: 0x00000004,
|
||||
INT: 0x00000008,
|
||||
BUS: 0x00000010,
|
||||
MEMORY: 0x00000020,
|
||||
MMU: 0x00000040,
|
||||
ROM: 0x00000080,
|
||||
DEVICE: 0x00000100,
|
||||
PANEL: 0x00000200,
|
||||
KEYBOARD: 0x00000400,
|
||||
KEYS: 0x00000800,
|
||||
PAPER: 0x00001000,
|
||||
READ: 0x00004000,
|
||||
WRITE: 0x00008000,
|
||||
SERIAL: 0x00100000,
|
||||
TIMER: 0x00200000,
|
||||
SPEAKER: 0x01000000,
|
||||
COMPUTER: 0x02000000,
|
||||
LOG: 0x10000000,
|
||||
WARN: 0x20000000,
|
||||
BUFFER: 0x40000000,
|
||||
HALT: 0x80000000|0
|
||||
};
|
||||
|
||||
/*
|
||||
* Message categories supported by the messageEnabled() function and other assorted message
|
||||
* functions. Each category has a corresponding bit value that can be combined (ie, OR'ed) as
|
||||
* needed. The Debugger's message command ("m") is used to turn message categories on and off,
|
||||
* like so:
|
||||
*
|
||||
* m port on
|
||||
* m port off
|
||||
* ...
|
||||
*
|
||||
* NOTE: The order of these categories can be rearranged, alphabetized, etc, as desired; just be
|
||||
* aware that changing the bit values could break saved Debugger states (not a huge concern, just
|
||||
* something to be aware of).
|
||||
*/
|
||||
MessagesPDP10.CATEGORIES = {
|
||||
"cpu": MessagesPDP10.CPU,
|
||||
"trap": MessagesPDP10.TRAP,
|
||||
"fault": MessagesPDP10.FAULT,
|
||||
"int": MessagesPDP10.INT,
|
||||
"bus": MessagesPDP10.BUS,
|
||||
"memory": MessagesPDP10.MEMORY,
|
||||
"mmu": MessagesPDP10.MMU,
|
||||
"rom": MessagesPDP10.ROM,
|
||||
"device": MessagesPDP10.DEVICE,
|
||||
"panel": MessagesPDP10.PANEL,
|
||||
"keyboard": MessagesPDP10.KEYBOARD, // "kbd" is also allowed as shorthand for "keyboard"; see doMessages()
|
||||
"key": MessagesPDP10.KEYS, // using "key" instead of "keys", since the latter is a method on JavasScript objects
|
||||
"paper": MessagesPDP10.PAPER,
|
||||
"read": MessagesPDP10.READ,
|
||||
"write": MessagesPDP10.WRITE,
|
||||
"serial": MessagesPDP10.SERIAL,
|
||||
"timer": MessagesPDP10.TIMER,
|
||||
"speaker": MessagesPDP10.SPEAKER,
|
||||
"computer": MessagesPDP10.COMPUTER,
|
||||
"log": MessagesPDP10.LOG,
|
||||
"warn": MessagesPDP10.WARN,
|
||||
/*
|
||||
* Now we turn to message actions rather than message types; for example, setting "halt"
|
||||
* on or off doesn't enable "halt" messages, but rather halts the CPU on any message above.
|
||||
*
|
||||
* Similarly, "m buffer on" turns on message buffering, deferring the display of all messages
|
||||
* until "m buffer off" is issued.
|
||||
*/
|
||||
"buffer": MessagesPDP10.BUFFER,
|
||||
"halt": MessagesPDP10.HALT
|
||||
};
|
||||
|
||||
if (NODE) module.exports = MessagesPDP10;
|
||||
43
modules/pdp10/lib/nodebugger.js
Normal file
43
modules/pdp10/lib/nodebugger.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* @fileoverview Compile-time definitions for Debugger-less configurations.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
|
||||
*
|
||||
* 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 modified copy of this work
|
||||
* and to display that copyright notice when the software starts running; see COPYRIGHT in
|
||||
* <http://pcjs.org/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";
|
||||
|
||||
/*
|
||||
* WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
|
||||
* In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate. When it's *false*,
|
||||
* nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
|
||||
* "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
|
||||
* debugger.js from the compilation process altogether.
|
||||
*
|
||||
* However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
|
||||
* I would still like to skip loading debugger.js altogether. To do that, we must arrange for this additional file,
|
||||
* nodebugger.js, to be loaded immediately after defines.js, *explicitly* overriding the previously defined value
|
||||
* of DEBUGGER with *false*.
|
||||
*/
|
||||
DEBUGGER = false;
|
||||
1266
modules/pdp10/lib/panel.js
Normal file
1266
modules/pdp10/lib/panel.js
Normal file
File diff suppressed because it is too large
Load diff
413
modules/pdp10/lib/ram.js
Normal file
413
modules/pdp10/lib/ram.js
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP-10 RAM component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
|
||||
*
|
||||
* 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 modified copy of this work
|
||||
* and to display that copyright notice when the software starts running; see COPYRIGHT in
|
||||
* <http://pcjs.org/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 DumpAPI = require("../../shared/lib/dumpapi");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var PDP10 = require("./defines");
|
||||
var MemoryPDP10 = require("./memory");
|
||||
var MessagesPDP10 = require("./messages");
|
||||
}
|
||||
|
||||
class RAMPDP10 extends Component {
|
||||
/**
|
||||
* RAMPDP10(parmsRAM)
|
||||
*
|
||||
* The RAMPDP10 component expects the following (parmsRAM) properties:
|
||||
*
|
||||
* addr: starting physical address of RAM (default is 0)
|
||||
* size: amount of RAM, in bytes (default is 0, which means defer to motherboard switch settings)
|
||||
* file: name of optional data file to load into RAM (default is "")
|
||||
* load: optional file load address (overrides any load address specified in the data file; default is null)
|
||||
* exec: optional file exec address (overrides any exec address specified in the data file; default is null)
|
||||
*
|
||||
* NOTE: We make a note of the specified size, but no memory is initially allocated for the RAM until the
|
||||
* Computer component calls powerUp().
|
||||
*
|
||||
* @param {Object} parmsRAM
|
||||
*/
|
||||
constructor(parmsRAM)
|
||||
{
|
||||
super("RAM", parmsRAM);
|
||||
|
||||
this.abInit = null;
|
||||
this.aSymbols = null;
|
||||
|
||||
this.addrRAM = +parmsRAM['addr'];
|
||||
this.sizeRAM = +parmsRAM['size'];
|
||||
|
||||
this.addrLoad = parmsRAM['load'];
|
||||
this.addrExec = parmsRAM['exec'];
|
||||
if (this.addrLoad != null) this.addrLoad = +this.addrLoad;
|
||||
if (this.addrExec != null) this.addrExec = +this.addrExec;
|
||||
|
||||
this.fInstalled = (!!this.sizeRAM); // 0 is the default value for 'size' when none is specified
|
||||
this.fAllocated = false;
|
||||
|
||||
this.sFilePath = parmsRAM['file'];
|
||||
this.sFileName = Str.getBaseName(this.sFilePath);
|
||||
|
||||
if (this.sFilePath) {
|
||||
var sFileURL = this.sFilePath;
|
||||
if (DEBUG) this.log('load("' + sFileURL + '")');
|
||||
/*
|
||||
* If the selected data file has a ".json" extension, then we assume it's pre-converted
|
||||
* JSON-encoded data, so we load it as-is; ditto for ROM files with a ".hex" extension.
|
||||
* Otherwise, we ask our server-side converter to return the file in a JSON-compatible format.
|
||||
*/
|
||||
var sFileExt = Str.getExtension(this.sFileName);
|
||||
if (sFileExt != DumpAPI.FORMAT.JSON && sFileExt != DumpAPI.FORMAT.HEX) {
|
||||
sFileURL = Web.getHost() + DumpAPI.ENDPOINT + '?' + DumpAPI.QUERY.FILE + '=' + this.sFilePath + '&' + DumpAPI.QUERY.FORMAT + '=' + DumpAPI.FORMAT.BYTES + '&' + DumpAPI.QUERY.DECIMAL + '=true';
|
||||
}
|
||||
var ram = this;
|
||||
Web.getResource(sFileURL, null, true, function doneLoad(sURL, sResponse, nErrorCode) {
|
||||
ram.finishLoad(sURL, sResponse, nErrorCode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {RAMPDP10}
|
||||
* @param {ComputerPDP10} cmp
|
||||
* @param {BusPDP10} bus
|
||||
* @param {CPUStatePDP10} cpu
|
||||
* @param {DebuggerPDP10} dbg
|
||||
*/
|
||||
initBus(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.initRAM();
|
||||
}
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* @this {RAMPDP10}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
powerUp(data, fRepower)
|
||||
{
|
||||
if (this.aSymbols) {
|
||||
if (this.dbg) {
|
||||
this.dbg.addSymbols(this.id, this.addrRAM, this.sizeRAM, this.aSymbols);
|
||||
}
|
||||
/*
|
||||
* Our only role in the handling of symbols is to hand them off to the Debugger at our
|
||||
* first opportunity. Now that we've done that, our copy of the symbols, if any, are toast.
|
||||
*/
|
||||
delete this.aSymbols;
|
||||
}
|
||||
if (!fRepower) {
|
||||
/*
|
||||
* Since we use the Bus to allocate all our memory, memory contents are already restored for us,
|
||||
* so we don't save any state, and therefore no state should be restored. Just do a reset().
|
||||
*/
|
||||
this.assert(!data);
|
||||
this.reset();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* powerDown(fSave, fShutdown)
|
||||
*
|
||||
* @this {RAMPDP10}
|
||||
* @param {boolean} [fSave]
|
||||
* @param {boolean} [fShutdown]
|
||||
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
|
||||
*/
|
||||
powerDown(fSave, fShutdown)
|
||||
{
|
||||
/*
|
||||
* The Computer powers down the CPU first, at which point CPUState state is saved,
|
||||
* which includes the Bus state, and since we use the Bus component to allocate all
|
||||
* our memory, memory contents are already saved for us, so we don't need the usual
|
||||
* save logic.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* finishLoad(sURL, sData, nErrorCode)
|
||||
*
|
||||
* @this {RAMPDP10}
|
||||
* @param {string} sURL
|
||||
* @param {string} sData
|
||||
* @param {number} nErrorCode (response from server if anything other than 200)
|
||||
*/
|
||||
finishLoad(sURL, sData, nErrorCode)
|
||||
{
|
||||
if (nErrorCode) {
|
||||
this.notice("Unable to load RAM resource (error " + nErrorCode + ": " + sURL + ")");
|
||||
this.sFilePath = null;
|
||||
}
|
||||
else {
|
||||
Component.addMachineResource(this.idMachine, sURL, sData);
|
||||
var resource = Web.parseMemoryResource(sURL, sData);
|
||||
if (resource) {
|
||||
this.abInit = resource.aBytes;
|
||||
this.aSymbols = resource.aSymbols;
|
||||
if (this.addrLoad == null) this.addrLoad = resource.addrLoad;
|
||||
if (this.addrExec == null) this.addrExec = resource.addrExec;
|
||||
} else {
|
||||
this.sFilePath = null;
|
||||
}
|
||||
}
|
||||
this.initRAM();
|
||||
}
|
||||
|
||||
/**
|
||||
* initRAM()
|
||||
*
|
||||
* This function is called by both initBus() and finishLoad(), but it cannot copy the initial data into place
|
||||
* until after initBus() has received the Bus component AND finishLoad() has received the data. When both those
|
||||
* criteria are satisfied, the component becomes "ready".
|
||||
*
|
||||
* @this {RAMPDP10}
|
||||
*/
|
||||
initRAM()
|
||||
{
|
||||
if (!this.bus) return;
|
||||
|
||||
if (!this.fAllocated && this.sizeRAM) {
|
||||
if (this.bus.addMemory(this.addrRAM, this.sizeRAM, MemoryPDP10.TYPE.RAM)) {
|
||||
this.fAllocated = true;
|
||||
} else {
|
||||
this.sizeRAM = 0; // don't bother trying again (it just results in redundant error messages)
|
||||
}
|
||||
}
|
||||
if (!this.isReady()) {
|
||||
if (!this.fAllocated) {
|
||||
Component.error("No RAM allocated");
|
||||
}
|
||||
else if (this.sFilePath) {
|
||||
/*
|
||||
* Too early...
|
||||
*/
|
||||
if (!this.abInit || !this.bus) return;
|
||||
|
||||
if (this.loadImage(this.abInit, this.addrLoad, this.addrExec, this.addrRAM)) {
|
||||
this.status('Loaded image "' + this.sFileName + '"');
|
||||
} else {
|
||||
this.notice('Error loading image "' + this.sFileName + '"');
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: We now retain this data, so that reset() can return the RAM to its predefined state.
|
||||
*
|
||||
* delete this.abInit;
|
||||
*/
|
||||
}
|
||||
this.setReady();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* @this {RAMPDP10}
|
||||
*/
|
||||
reset()
|
||||
{
|
||||
if (this.fAllocated) {
|
||||
/*
|
||||
* TODO: Add a configuration parameter for selecting the byte pattern on reset?
|
||||
* Note that when memory blocks are originally created, they are currently always
|
||||
* zero-initialized, so this would only affect resets.
|
||||
*/
|
||||
this.bus.zeroMemory(this.addrRAM, this.sizeRAM, 0);
|
||||
if (this.abInit) {
|
||||
this.loadImage(this.abInit, this.addrLoad, this.addrExec, this.addrRAM, !this.dbg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* loadImage(aBytes, addrLoad, addrExec, addrInit, fStart)
|
||||
*
|
||||
* If the array contains a PAPER tape image in the "Absolute Format," load it as specified
|
||||
* by the format; otherwise, load it as-is using the address(es) supplied.
|
||||
*
|
||||
* @this {RAMPDP10}
|
||||
* @param {Array|Uint8Array} aBytes
|
||||
* @param {number|null} [addrLoad]
|
||||
* @param {number|null} [addrExec] (this CAN override any starting address INSIDE the image)
|
||||
* @param {number|null} [addrInit]
|
||||
* @param {boolean} [fStart]
|
||||
* @return {boolean} (true if loaded, false if not)
|
||||
*/
|
||||
loadImage(aBytes, addrLoad, addrExec, addrInit, fStart)
|
||||
{
|
||||
var fStop = false;
|
||||
var fLoaded = false;
|
||||
/*
|
||||
* Data on tapes in the "Absolute Format" is organized into blocks; each block begins with
|
||||
* a 6-byte header:
|
||||
*
|
||||
* 2-byte signature (0x0001)
|
||||
* 2-byte block length (N + 6, because it includes the 6-byte header)
|
||||
* 2-byte load address
|
||||
*
|
||||
* followed by N data bytes. If N is zero, then the 2-byte load address is the exec address,
|
||||
* unless the address is odd (usually 1). DEC's Absolute Loader jumps to the exec address
|
||||
* in former case, halts in the latter.
|
||||
*
|
||||
* All values are stored "little endian" (low byte followed by high byte), just like the
|
||||
* PDP-11's memory architecture.
|
||||
*
|
||||
* After the data bytes, there is a single checksum byte. The 8-bit sum of all the bytes in
|
||||
* the block (including the header bytes and checksum byte) should be zero.
|
||||
*
|
||||
* ANOMALIES: Tape files don't always begin with a signature word, so I allow any number of
|
||||
* leading zeros before the first signature. Tape files don't always end cleanly either, so as
|
||||
* soon as I see an invalid signature, I break out of the loop without signalling an error, as
|
||||
* long as at least ONE block was successfully processed. In fact, it's possible that as
|
||||
* soon as a block with ZERO data bytes is encountered, processing is supposed to stop, but
|
||||
* I haven't examined enough tapes (or the Absolute Loader code) to know for sure.
|
||||
*/
|
||||
if (addrLoad == null) {
|
||||
var off = 0, fError = false;
|
||||
while (off < aBytes.length - 1) {
|
||||
var w = (aBytes[off] & 0xff) | ((aBytes[off+1] & 0xff) << 8);
|
||||
if (!w) { // ignore pairs of leading zeros
|
||||
off += 2;
|
||||
continue;
|
||||
}
|
||||
if (!(w & 0xff)) { // as well as single bytes of zero
|
||||
off++;
|
||||
continue;
|
||||
}
|
||||
var offBlock = off;
|
||||
if (w != 0x0001) {
|
||||
this.printMessage("invalid signature (" + Str.toHexWord(w) + ") at offset " + Str.toHexWord(offBlock), MessagesPDP10.PAPER);
|
||||
break;
|
||||
}
|
||||
if (off + 6 >= aBytes.length) {
|
||||
this.printMessage("invalid block at offset " + Str.toHexWord(offBlock), MessagesPDP10.PAPER);
|
||||
break;
|
||||
}
|
||||
off += 2;
|
||||
var checksum = w;
|
||||
var len = (aBytes[off++] & 0xff) | ((aBytes[off++] & 0xff) << 8);
|
||||
var addr = (aBytes[off++] & 0xff) | ((aBytes[off++] & 0xff) << 8);
|
||||
checksum += (len & 0xff) + (len >> 8) + (addr & 0xff) + (addr >> 8);
|
||||
var offData = off, cbData = len -= 6;
|
||||
while (len > 0 && off < aBytes.length) {
|
||||
checksum += aBytes[off++] & 0xff;
|
||||
len--;
|
||||
}
|
||||
if (len != 0 || off >= aBytes.length) {
|
||||
this.printMessage("insufficient data for block at offset " + Str.toHexWord(offBlock), MessagesPDP10.PAPER);
|
||||
break;
|
||||
}
|
||||
checksum += aBytes[off++] & 0xff;
|
||||
if (checksum & 0xff) {
|
||||
this.printMessage("invalid checksum (" + Str.toHexByte(checksum) + ") for block at offset " + Str.toHexWord(offBlock), MessagesPDP10.PAPER);
|
||||
break;
|
||||
}
|
||||
if (!cbData) {
|
||||
if (addr & 0x1) {
|
||||
fStop = true;
|
||||
} else {
|
||||
if (addrExec == null) addrExec = addr;
|
||||
}
|
||||
if (addrExec != null) this.printMessage("starting address: " + Str.toHexWord(addrExec), MessagesPDP10.PAPER);
|
||||
} else {
|
||||
this.printMessage("loading " + Str.toHexWord(cbData) + " bytes at " + Str.toHexWord(addr) + "-" + Str.toHexWord(addr + cbData), MessagesPDP10.PAPER);
|
||||
while (cbData--) {
|
||||
this.bus.setWordDirect(addr++, aBytes[offData++]);
|
||||
}
|
||||
}
|
||||
fLoaded = true;
|
||||
}
|
||||
}
|
||||
if (!fLoaded) {
|
||||
if (addrLoad == null) addrLoad = addrInit;
|
||||
if (addrLoad != null) {
|
||||
for (var i = 0; i < aBytes.length; i++) {
|
||||
this.bus.setWordDirect(addrLoad + i, aBytes[i]);
|
||||
}
|
||||
fLoaded = true;
|
||||
}
|
||||
}
|
||||
if (fLoaded) {
|
||||
/*
|
||||
* Set the start address to whatever the caller provided, or failing that, whatever start
|
||||
* address was specified inside the image.
|
||||
*
|
||||
* For example, the diagnostic "MAINDEC-11-D0AA-PB" doesn't include a start address inside the
|
||||
* image, but we know that the directions for that diagnostic say to "Start and Restart at 200",
|
||||
* so we have manually inserted an "exec":128 in the JSON containing the image.
|
||||
*/
|
||||
if (addrExec == null || fStop) {
|
||||
this.cpu.stopCPU();
|
||||
fStart = false;
|
||||
}
|
||||
if (addrExec != null) {
|
||||
this.cpu.setReset(addrExec, fStart);
|
||||
}
|
||||
}
|
||||
return fLoaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* RAMPDP10.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "ram", extracting the
|
||||
* JSON-encoded parameters for the RAMPDP10 constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a RAMPDP10 component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var aeRAM = Component.getElementsByClass(document, PDP10.APPCLASS, "ram");
|
||||
for (var iRAM = 0; iRAM < aeRAM.length; iRAM++) {
|
||||
var eRAM = aeRAM[iRAM];
|
||||
var parmsRAM = Component.getComponentParms(eRAM);
|
||||
var ram = new RAMPDP10(parmsRAM);
|
||||
Component.bindComponentControls(ram, eRAM, PDP10.APPCLASS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize all the RAMPDP10 modules on the page.
|
||||
*/
|
||||
Web.onInit(RAMPDP10.init);
|
||||
|
||||
if (NODE) module.exports = RAMPDP10;
|
||||
339
modules/pdp10/lib/rom.js
Normal file
339
modules/pdp10/lib/rom.js
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP-10 ROM component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
|
||||
*
|
||||
* 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 modified copy of this work
|
||||
* and to display that copyright notice when the software starts running; see COPYRIGHT in
|
||||
* <http://pcjs.org/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 DumpAPI = require("../../shared/lib/dumpapi");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var PDP10 = require("./defines");
|
||||
var BusPDP10 = require("./bus");
|
||||
var MemoryPDP10 = require("./memory");
|
||||
var MessagesPDP10 = require("./messages");
|
||||
}
|
||||
|
||||
class ROMPDP10 extends Component {
|
||||
/**
|
||||
* ROMPDP10(parmsROM)
|
||||
*
|
||||
* The ROMPDP10 component expects the following (parmsROM) properties:
|
||||
*
|
||||
* addr: physical address of ROM
|
||||
* size: amount of ROM, in bytes
|
||||
* alias: physical alias address (null if none)
|
||||
* file: name of ROM data file
|
||||
*
|
||||
* NOTE: The ROM data will not be copied into place until the Bus is ready (see initBus()) AND
|
||||
* the ROM data file has finished loading (see finishLoad()).
|
||||
*
|
||||
* Also, while the size parameter may seem redundant, I consider it useful to confirm that the ROM
|
||||
* you received is the ROM you expected.
|
||||
*
|
||||
* @param {Object} parmsROM
|
||||
*/
|
||||
constructor(parmsROM)
|
||||
{
|
||||
super("ROM", parmsROM, MessagesPDP10.ROM);
|
||||
|
||||
this.abInit = null;
|
||||
this.aSymbols = null;
|
||||
|
||||
this.addrROM = +parmsROM['addr'];
|
||||
this.sizeROM = +parmsROM['size'];
|
||||
this.fRetainROM = false;
|
||||
|
||||
/*
|
||||
* The new 'alias' property can now be EITHER a single physical address (like 'addr') OR an array of
|
||||
* physical addresses; eg:
|
||||
*
|
||||
* [0xf0000,0xffff0000,0xffff8000]
|
||||
*
|
||||
* We could have overloaded 'addr' to accomplish the same thing, but I think it's better to have any
|
||||
* aliased locations listed under a separate property.
|
||||
*
|
||||
* Most ROMs are not aliased, in which case the 'alias' property should have the default value of null.
|
||||
*/
|
||||
this.addrAlias = parmsROM['alias'];
|
||||
if (typeof this.addrAlias == "string") {
|
||||
this.addrAlias = eval(this.addrAlias);
|
||||
}
|
||||
|
||||
this.sFilePath = parmsROM['file'];
|
||||
this.sFileName = Str.getBaseName(this.sFilePath);
|
||||
|
||||
if (this.sFilePath) {
|
||||
var sFileURL = this.sFilePath;
|
||||
if (DEBUG) this.log('load("' + sFileURL + '")');
|
||||
/*
|
||||
* If the selected ROM file has a ".json" extension, then we assume it's pre-converted
|
||||
* JSON-encoded ROM data, so we load it as-is; ditto for ROM files with a ".hex" extension.
|
||||
* Otherwise, we ask our server-side ROM converter to return the file in a JSON-compatible format.
|
||||
*/
|
||||
var sFileExt = Str.getExtension(this.sFileName);
|
||||
if (sFileExt != DumpAPI.FORMAT.JSON && sFileExt != DumpAPI.FORMAT.HEX) {
|
||||
sFileURL = Web.getHost() + DumpAPI.ENDPOINT + '?' + DumpAPI.QUERY.FILE + '=' + this.sFilePath + '&' + DumpAPI.QUERY.FORMAT + '=' + DumpAPI.FORMAT.BYTES + '&' + DumpAPI.QUERY.DECIMAL + '=true';
|
||||
}
|
||||
var rom = this;
|
||||
Web.getResource(sFileURL, null, true, function doneLoad(sURL, sResponse, nErrorCode) {
|
||||
rom.finishLoad(sURL, sResponse, nErrorCode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {ROMPDP10}
|
||||
* @param {ComputerPDP10} cmp
|
||||
* @param {BusPDP10} bus
|
||||
* @param {CPUStatePDP10} cpu
|
||||
* @param {DebuggerPDP10} dbg
|
||||
*/
|
||||
initBus(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.initROM();
|
||||
}
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* @this {ROMPDP10}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
powerUp(data, fRepower)
|
||||
{
|
||||
if (this.aSymbols) {
|
||||
if (this.dbg) {
|
||||
this.dbg.addSymbols(this.id, this.addrROM, this.sizeROM, this.aSymbols);
|
||||
}
|
||||
/*
|
||||
* Our only role in the handling of symbols is to hand them off to the Debugger at our
|
||||
* first opportunity. Now that we've done that, our copy of the symbols, if any, are toast.
|
||||
*/
|
||||
delete this.aSymbols;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* powerDown(fSave, fShutdown)
|
||||
*
|
||||
* Since we have nothing to do on powerDown(), and no state to return, we could simply omit
|
||||
* this function. But it doesn't hurt anything, and maybe we'll use our state to save something
|
||||
* useful down the road, like user-defined symbols (ie, symbols that the Debugger may have
|
||||
* created, above and beyond those symbols we automatically loaded, if any, along with the ROM).
|
||||
*
|
||||
* @this {ROMPDP10}
|
||||
* @param {boolean} [fSave]
|
||||
* @param {boolean} [fShutdown]
|
||||
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
|
||||
*/
|
||||
powerDown(fSave, fShutdown)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* finishLoad(sURL, sData, nErrorCode)
|
||||
*
|
||||
* @this {ROMPDP10}
|
||||
* @param {string} sURL
|
||||
* @param {string} sData
|
||||
* @param {number} nErrorCode (response from server if anything other than 200)
|
||||
*/
|
||||
finishLoad(sURL, sData, nErrorCode)
|
||||
{
|
||||
if (nErrorCode) {
|
||||
this.notice("Unable to load ROM resource (error " + nErrorCode + ": " + sURL + ")");
|
||||
this.sFilePath = null;
|
||||
}
|
||||
else {
|
||||
Component.addMachineResource(this.idMachine, sURL, sData);
|
||||
var resource = Web.parseMemoryResource(sURL, sData);
|
||||
if (resource) {
|
||||
this.abInit = resource.aBytes;
|
||||
this.aSymbols = resource.aSymbols;
|
||||
} else {
|
||||
this.sFilePath = null;
|
||||
}
|
||||
}
|
||||
this.initROM();
|
||||
}
|
||||
|
||||
/**
|
||||
* initROM()
|
||||
*
|
||||
* This function is called by both initBus() and finishLoad(), but it cannot copy the initial data into place
|
||||
* until after initBus() has received the Bus component AND finishLoad() has received the data. When both those
|
||||
* criteria are satisfied, the component becomes "ready".
|
||||
*
|
||||
* @this {ROMPDP10}
|
||||
*/
|
||||
initROM()
|
||||
{
|
||||
if (!this.isReady()) {
|
||||
if (this.sFilePath) {
|
||||
/*
|
||||
* Too early...
|
||||
*/
|
||||
if (!this.abInit || !this.bus) return;
|
||||
|
||||
/*
|
||||
* If no explicit size was specified, then use whatever the actual size is.
|
||||
*/
|
||||
if (!this.sizeROM) {
|
||||
this.sizeROM = this.abInit.length;
|
||||
}
|
||||
if (this.abInit.length != this.sizeROM) {
|
||||
/*
|
||||
* Note that setError() sets the component's fError flag, which in turn prevents setReady() from
|
||||
* marking the component ready. TODO: Revisit this decision. On the one hand, it sounds like a
|
||||
* good idea to stop the machine in its tracks whenever a setError() occurs, but there may also be
|
||||
* times when we'd like to forge ahead anyway.
|
||||
*/
|
||||
this.setError("ROM size (" + Str.toHexLong(this.abInit.length) + ") does not match specified size (" + Str.toHexLong(this.sizeROM) + ")");
|
||||
}
|
||||
else if (this.addROM(this.addrROM)) {
|
||||
|
||||
var aliases = [];
|
||||
if (typeof this.addrAlias == "number") {
|
||||
aliases.push(this.addrAlias);
|
||||
} else if (this.addrAlias != null && this.addrAlias.length) {
|
||||
aliases = this.addrAlias;
|
||||
}
|
||||
for (var i = 0; i < aliases.length; i++) {
|
||||
this.cloneROM(aliases[i]);
|
||||
}
|
||||
/*
|
||||
* We used to hang onto the initial ROM data so that we could restore any bytes the CPU overwrote,
|
||||
* using memory write-notification handlers, but with the introduction of read-only memory blocks, that's
|
||||
* no longer necessary.
|
||||
*
|
||||
* TODO: Consider an option to retain the ROM data, and give the user some way of restoring ROMs.
|
||||
* That may be useful for "resumable" machines that save/restore all dirty block of memory, regardless
|
||||
* whether they're ROM or RAM. However, the only way to modify a machine's ROM is with the Debugger,
|
||||
* and Debugger users should know better.
|
||||
*/
|
||||
if (!this.fRetainROM) {
|
||||
delete this.abInit;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setReady();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* addROM(addr)
|
||||
*
|
||||
* @this {ROMPDP10}
|
||||
* @param {number} addr
|
||||
* @return {boolean}
|
||||
*/
|
||||
addROM(addr)
|
||||
{
|
||||
if (this.bus.addMemory(addr, this.sizeROM, MemoryPDP10.TYPE.ROM)) {
|
||||
if (DEBUG) this.log("addROM(): copying ROM to " + Str.toHexLong(addr) + " (" + Str.toHexLong(this.abInit.length) + " bytes)");
|
||||
var i;
|
||||
for (i = 0; i < this.abInit.length; i++) {
|
||||
this.bus.setWordDirect(addr + i, this.abInit[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't need to report an error here, because addMemory() already takes care of that.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* cloneROM(addr)
|
||||
*
|
||||
* For ROMs with one or more alias addresses, we used to call addROM() for each address. However,
|
||||
* that obviously wasted memory, since each alias was an independent copy, and if you used the
|
||||
* Debugger to edit the ROM in one location, the changes would not appear in the other location(s).
|
||||
*
|
||||
* Now that the Bus component provides low-level getMemoryBlocks() and setMemoryBlocks() methods
|
||||
* to manually get and set the blocks of any memory range, it is now possible to create true aliases.
|
||||
*
|
||||
* @this {ROMPDP10}
|
||||
* @param {number} addr
|
||||
*/
|
||||
cloneROM(addr)
|
||||
{
|
||||
var aBlocks = this.bus.getMemoryBlocks(this.addrROM, this.sizeROM);
|
||||
this.bus.setMemoryBlocks(addr, this.sizeROM, aBlocks);
|
||||
}
|
||||
|
||||
/**
|
||||
* ROMPDP10.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "rom", extracting the
|
||||
* JSON-encoded parameters for the ROMPDP10 constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a ROMPDP10 component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var aeROM = Component.getElementsByClass(document, PDP10.APPCLASS, "rom");
|
||||
for (var iROM = 0; iROM < aeROM.length; iROM++) {
|
||||
var eROM = aeROM[iROM];
|
||||
var parmsROM = Component.getComponentParms(eROM);
|
||||
var rom = new ROMPDP10(parmsROM);
|
||||
Component.bindComponentControls(rom, eROM, PDP10.APPCLASS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: There's currently no need for this component to have a reset() function, since
|
||||
* once the ROM data is loaded, it can't be changed, so there's nothing to reinitialize.
|
||||
*
|
||||
* OK, well, I take that back, because the Debugger, if installed, has the ability to modify
|
||||
* ROM contents, so in that case, having a reset() function that restores the original ROM data
|
||||
* might be useful; then again, it might not, depending on what you're trying to debug.
|
||||
*
|
||||
* If we do add reset(), then we'll want to change initROM() to hang onto the original
|
||||
* ROM data; currently, we release it after copying it into the read-only memory allocated
|
||||
* via bus.addMemory().
|
||||
*/
|
||||
|
||||
/*
|
||||
* Initialize all the ROMPDP10 modules on the page.
|
||||
*/
|
||||
Web.onInit(ROMPDP10.init);
|
||||
|
||||
if (NODE) module.exports = ROMPDP10;
|
||||
670
modules/pdp10/lib/serial.js
Normal file
670
modules/pdp10/lib/serial.js
Normal file
|
|
@ -0,0 +1,670 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP-10 SerialPort component
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
|
||||
*
|
||||
* 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 modified copy of this work
|
||||
* and to display that copyright notice when the software starts running; see COPYRIGHT in
|
||||
* <http://pcjs.org/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 Keys = require("../../shared/lib/keys");
|
||||
var State = require("../../shared/lib/state");
|
||||
var PDP10 = require("./defines");
|
||||
var MessagesPDP10 = require("./messages");
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the Closure Compiler treats ES6 classes as @struct rather than @dict by default,
|
||||
* it deters us from defining named properties on our components; eg:
|
||||
*
|
||||
* this['exports'] = {...}
|
||||
*
|
||||
* results in an error:
|
||||
*
|
||||
* Cannot do '[]' access on a struct
|
||||
*
|
||||
* So, in order to define 'exports', we must override the @struct assumption by annotating
|
||||
* the class as @unrestricted (or @dict). Note that this must be done both here and in the
|
||||
* Component class, because otherwise the Compiler won't allow us to *reference* the named
|
||||
* property either.
|
||||
*
|
||||
* TODO: Consider marking ALL our classes unrestricted, because otherwise it forces us to
|
||||
* define every single property the class uses in its constructor, which results in a fair
|
||||
* bit of redundant initialization, since many properties aren't (and don't need to be) fully
|
||||
* initialized until the appropriate init(), reset(), restore(), etc. function is called.
|
||||
*
|
||||
* The upside, however, may be that since the structure of the class is completely defined by
|
||||
* the constructor, JavaScript engines may be able to optimize and run more efficiently.
|
||||
*
|
||||
* @unrestricted
|
||||
*/
|
||||
class SerialPortPDP10 extends Component {
|
||||
/**
|
||||
* SerialPortPDP10(parmsSerial)
|
||||
*
|
||||
* The SerialPort component has the following component-specific (parmsSerial) properties:
|
||||
*
|
||||
* adapter: adapter number; 0 if not defined (the PCx86 SerialPort component uses this
|
||||
* value to set the device's internal COM number, which in turn determines other properties,
|
||||
* such as I/O ports and IRQ; for the PDP-10, this currently has no defined use)
|
||||
*
|
||||
* binding: name of a control (based on its "binding" attribute) to bind to this port's I/O
|
||||
*
|
||||
* tabSize: set to a non-zero number to convert tabs to spaces (applies only to output to
|
||||
* the above binding); default is 0 (no conversion)
|
||||
*
|
||||
* upperCase: if true, all received input is upper-cased; it is normally the responsibility
|
||||
* of the sending device to ensure this, but sometimes it's more convenient to enforce
|
||||
* on the receiving end.
|
||||
*
|
||||
* @param {Object} parmsSerial
|
||||
*/
|
||||
constructor(parmsSerial)
|
||||
{
|
||||
super("SerialPort", parmsSerial, MessagesPDP10.SERIAL);
|
||||
|
||||
this.iAdapter = +parmsSerial['adapter'];
|
||||
this.fUpperCase = parmsSerial['upperCase'];
|
||||
if (typeof this.fUpperCase == "string") this.fUpperCase = (this.fUpperCase == "true");
|
||||
/**
|
||||
* consoleOutput becomes a string that records serial port output if the 'binding' property is set to the
|
||||
* reserved name "console". Nothing is written to the console, however, until a linefeed (0x0A) is output
|
||||
* or the string length reaches a threshold (currently, 1024 characters).
|
||||
*
|
||||
* @type {string|null}
|
||||
*/
|
||||
this.consoleOutput = null;
|
||||
|
||||
/**
|
||||
* controlIOBuffer is a DOM element bound to the port (currently used for output only; see transmitByte()).
|
||||
*
|
||||
* Example: CTTY COM2
|
||||
*
|
||||
* The CTTY DOS command redirects all CON I/O to the specified serial port (eg, COM2), which it assumes is
|
||||
* connected to a serial terminal, and therefore anything it *transmits* via COM2 will be displayed by the
|
||||
* terminal. It further assumes that anything typed on such a terminal is NOT displayed, so as DOS *receives*
|
||||
* serial input, DOS *transmits* the appropriate characters back to the terminal via COM2.
|
||||
*
|
||||
* As a result, controlIOBuffer only needs to be updated by the transmitByte() function.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
this.controlIOBuffer = null;
|
||||
|
||||
/*
|
||||
* If controlIOBuffer is being used AND 'tabSize' is set, then we make an attempt to monitor the characters
|
||||
* being echoed via transmitByte(), maintain a logical column position, and convert any tabs into the appropriate
|
||||
* number of spaces.
|
||||
*
|
||||
* charBOL, if nonzero, is a character to automatically output at the beginning of every line. This probably
|
||||
* isn't generally useful; I use it internally to preformat serial output.
|
||||
*/
|
||||
this.tabSize = +parmsSerial['tabSize'];
|
||||
this.charBOL = +parmsSerial['charBOL'];
|
||||
this.iLogicalCol = 0;
|
||||
this.fNullModem = true;
|
||||
|
||||
this.abReceive = [];
|
||||
|
||||
var sBinding = parmsSerial['binding'];
|
||||
if (sBinding == "console") {
|
||||
this.consoleOutput = "";
|
||||
} else {
|
||||
/*
|
||||
* NOTE: If sBinding is not the name of a valid Control Panel DOM element, this call does nothing.
|
||||
*/
|
||||
Component.bindExternalControl(this, sBinding, SerialPortPDP10.sIOBuffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* No connection until initConnection() is called.
|
||||
*/
|
||||
this.sDataReceived = "";
|
||||
this.connection = this.sendData = this.updateStatus = null;
|
||||
|
||||
/*
|
||||
* Export all functions required by initConnection().
|
||||
*/
|
||||
this['exports'] = {
|
||||
'connect': this.initConnection,
|
||||
'receiveData': this.receiveData,
|
||||
'receiveStatus': this.receiveStatus,
|
||||
'setConnection': this.setConnection
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* setBinding(sType, sBinding, control, sValue)
|
||||
*
|
||||
* @this {SerialPortPDP10}
|
||||
* @param {string|null} sType is the type of the HTML control (eg, "button", "textarea", "register", "flag", "rled", etc)
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "buffer")
|
||||
* @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
|
||||
*/
|
||||
setBinding(sType, sBinding, control, sValue)
|
||||
{
|
||||
var serial = this;
|
||||
|
||||
switch (sBinding) {
|
||||
case SerialPortPDP10.sIOBuffer:
|
||||
this.bindings[sBinding] = this.controlIOBuffer = control;
|
||||
|
||||
/*
|
||||
* An onkeydown handler is required for certain keys that browsers tend to consume themselves;
|
||||
* for example, BACKSPACE is often defined as going back to the previous web page, and certain
|
||||
* CTRL keys are often used for browser shortcuts (usually on Windows-based browsers).
|
||||
*
|
||||
* NOTE: We don't bother with a keyUp handler, because for the most part, we're only intercepting
|
||||
* keys that require special treatment; in general, we're content with keyPress events.
|
||||
*/
|
||||
control.onkeydown = function onKeyDown(event) {
|
||||
event = event || window.event;
|
||||
var bASCII = 0;
|
||||
var keyCode = event.keyCode;
|
||||
/*
|
||||
* Perform the same remapping of BACKSPACE and DELETE that our VT100 emulation performs,
|
||||
* for PCjs-wide consistency; see the KEYMAP table in /modules/pc8080/lib/keyboard.js for
|
||||
* the rationale. Ditto for ALT-DELETE; see onKeyDown() in /modules/pc8080/lib/keyboard.js
|
||||
* for details.
|
||||
*
|
||||
* NOTE: keyDown (and keyUp) events supply us with KEYCODE values, which are NOT the same as
|
||||
* ASCII values, which is why we are comparing with KEYCODE values but assigning ASCII values,
|
||||
* because receiveData() requires ASCII values.
|
||||
*/
|
||||
if (keyCode == Keys.KEYCODE.BS) {
|
||||
bASCII = event.altKey? Keys.ASCII.CTRL_H : Keys.ASCII.DEL;
|
||||
}
|
||||
else if (keyCode == Keys.KEYCODE.DEL) {
|
||||
bASCII = Keys.ASCII.CTRL_H;
|
||||
}
|
||||
else if (event.ctrlKey && keyCode >= Keys.ASCII.A && keyCode <= Keys.ASCII.Z) {
|
||||
bASCII = keyCode - (Keys.ASCII.A - Keys.ASCII.CTRL_A);
|
||||
}
|
||||
if (bASCII) {
|
||||
if (event.preventDefault) event.preventDefault();
|
||||
serial.receiveData(bASCII);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
control.onkeypress = function onKeyPress(event) {
|
||||
/*
|
||||
* NOTE: Unlike keyDown events, keyPress events generally supply us with ASCII values,
|
||||
* despite the fact that, as above, they come to us via the keyCode property. Yes, it's
|
||||
* brilliant (or rather, the opposite of brilliant), but that's life.
|
||||
*/
|
||||
event = event || window.event;
|
||||
/*
|
||||
* Not sure why COMMAND-key combinations are coming through here (on Safari at least),
|
||||
* but in any case, let's make sure we don't act on them.
|
||||
*/
|
||||
if (!event.metaKey) {
|
||||
var bASCII = event.which || event.keyCode;
|
||||
/*
|
||||
* Perform the same remapping of ALT-ENTER (to LINE-FEED) that our VT100 emulation performs,
|
||||
* for PCjs-wide consistency; see onKeyDown() in /modules/pc8080/lib/keyboard.js for details.
|
||||
*/
|
||||
if (event.altKey) {
|
||||
if (bASCII == Keys.ASCII.CTRL_M) {
|
||||
bASCII = Keys.ASCII.CTRL_J;
|
||||
}
|
||||
}
|
||||
serial.receiveData(bASCII);
|
||||
/*
|
||||
* Since we're going to remove the "readonly" attribute from the <textarea> control
|
||||
* (so that the soft keyboard activates on iOS), instead of calling preventDefault() for
|
||||
* selected keys (eg, the SPACE key, whose default behavior is to scroll the page), we must
|
||||
* now call it for *all* keys, so that the keyCode isn't added to the control immediately,
|
||||
* on top of whatever the machine is echoing back, resulting in double characters.
|
||||
*/
|
||||
if (event.preventDefault) event.preventDefault();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
control.onpaste = function onKeyPress(event) {
|
||||
if (event.stopPropagation) event.stopPropagation();
|
||||
if (event.preventDefault) event.preventDefault();
|
||||
var clipboardData = event.clipboardData || window.clipboardData;
|
||||
if (clipboardData) {
|
||||
/*
|
||||
* NOTE: Multiple lines of pasted text will (at least on macOS) contain LFs instead of CRs;
|
||||
* this is dealt with in receiveData() whenever it receives a string of characters.
|
||||
*/
|
||||
serial.receiveData(clipboardData.getData('Text'));
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Now that we've added an onkeypress handler that calls preventDefault() for ALL keys, the control
|
||||
* itself no longer needs the "readonly" attribute; we primarily need to remove it for iOS browsers,
|
||||
* so that the soft keyboard will activate, but it shouldn't hurt to remove the attribute for all browsers.
|
||||
*/
|
||||
control.removeAttribute("readonly");
|
||||
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {SerialPortPDP10}
|
||||
* @param {ComputerPDP10} cmp
|
||||
* @param {BusPDP10} bus
|
||||
* @param {CPUStatePDP10} cpu
|
||||
* @param {DebuggerPDP10} dbg
|
||||
*/
|
||||
initBus(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.cmp = cmp;
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* initConnection(fNullModem)
|
||||
*
|
||||
* If a machine 'connection' parameter exists of the form "{sourcePort}->{targetMachine}.{targetPort}",
|
||||
* and "{sourcePort}" matches our idComponent, then look for a component with id "{targetMachine}.{targetPort}".
|
||||
*
|
||||
* If the target component is found, then verify that it has exported functions with the following names:
|
||||
*
|
||||
* receiveData(data): called when we have data to transmit; aliased internally to sendData(data)
|
||||
* receiveStatus(pins): called when our control signals have changed; aliased internally to updateStatus(pins)
|
||||
*
|
||||
* For now, we're not going to worry about communication in the other direction, because when the target component
|
||||
* performs its own initConnection(), it will find our receiveData() and receiveStatus() functions, at which point
|
||||
* communication in both directions should be established, and the circle of life complete.
|
||||
*
|
||||
* For added robustness, if the target machine initializes much more slowly than we do, and our connection attempt
|
||||
* fails, that's OK, because when it finally initializes, its initConnection() will call our initConnection();
|
||||
* if we've already initialized, no harm done.
|
||||
*
|
||||
* @this {SerialPortPDP10}
|
||||
* @param {boolean} [fNullModem] (caller's null-modem setting, to ensure our settings are in agreement)
|
||||
*/
|
||||
initConnection(fNullModem)
|
||||
{
|
||||
if (!this.connection) {
|
||||
var sConnection = this.cmp.getMachineParm("connection");
|
||||
if (sConnection) {
|
||||
var asParts = sConnection.split('->');
|
||||
if (asParts.length == 2) {
|
||||
var sSourceID = Str.trim(asParts[0]);
|
||||
if (sSourceID != this.idComponent) return; // this connection string is intended for another instance
|
||||
var sTargetID = Str.trim(asParts[1]);
|
||||
this.connection = Component.getComponentByID(sTargetID);
|
||||
if (this.connection) {
|
||||
var exports = this.connection['exports'];
|
||||
if (exports) {
|
||||
var fnConnect = exports['connect'];
|
||||
if (fnConnect) fnConnect.call(this.connection, this.fNullModem);
|
||||
this.sendData = exports['receiveData'];
|
||||
if (this.sendData) {
|
||||
this.fNullModem = fNullModem;
|
||||
this.updateStatus = exports['receiveStatus'];
|
||||
this.status("Connected " + this.idMachine + '.' + sSourceID + " to " + sTargetID);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Changed from notice() to status() because sometimes a connection fails simply because one of us is a laggard.
|
||||
*/
|
||||
this.status("Unable to establish connection: " + sConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* @this {SerialPortPDP10}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
powerUp(data, fRepower)
|
||||
{
|
||||
if (!fRepower) {
|
||||
|
||||
/*
|
||||
* This is as late as we can currently wait to make our first inter-machine connection attempt;
|
||||
* even so, the target machine's initialization process may still be ongoing, so any connection
|
||||
* may be not fully resolved until the target machine performs its own initConnection(), which will
|
||||
* in turn invoke our initConnection() again.
|
||||
*/
|
||||
this.initConnection(this.fNullModem);
|
||||
|
||||
if (!data) {
|
||||
this.reset();
|
||||
} else {
|
||||
if (!this.restore(data)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* powerDown(fSave, fShutdown)
|
||||
*
|
||||
* @this {SerialPortPDP10}
|
||||
* @param {boolean} [fSave]
|
||||
* @param {boolean} [fShutdown]
|
||||
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
|
||||
*/
|
||||
powerDown(fSave, fShutdown)
|
||||
{
|
||||
return fSave? this.save() : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* @this {SerialPortPDP10}
|
||||
*/
|
||||
reset()
|
||||
{
|
||||
this.initState();
|
||||
}
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* This implements save support for the SerialPort component.
|
||||
*
|
||||
* @this {SerialPortPDP10}
|
||||
* @return {Object}
|
||||
*/
|
||||
save()
|
||||
{
|
||||
var state = new State(this);
|
||||
state.set(0, this.saveRegisters());
|
||||
return state.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* restore(data)
|
||||
*
|
||||
* This implements restore support for the SerialPort component.
|
||||
*
|
||||
* @this {SerialPortPDP10}
|
||||
* @param {Object} data
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
restore(data)
|
||||
{
|
||||
return this.initState(data[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* initState(a)
|
||||
*
|
||||
* @this {SerialPortPDP10}
|
||||
* @param {Array} [a]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
initState(a)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* saveRegisters()
|
||||
*
|
||||
* Basically, the inverse of initState().
|
||||
*
|
||||
* @this {SerialPortPDP10}
|
||||
* @return {Array}
|
||||
*/
|
||||
saveRegisters()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* receiveData(data)
|
||||
*
|
||||
* This replaces the old sendRBR() function, which expected an Array of bytes. We still support that,
|
||||
* but in order to support connections with other SerialPort components (ie, the PC8080 SerialPort), we
|
||||
* have added support for numbers and strings as well.
|
||||
*
|
||||
* @this {SerialPortPDP10}
|
||||
* @param {number|string|Array} data
|
||||
* @return {boolean} true if received, false if not
|
||||
*/
|
||||
receiveData(data)
|
||||
{
|
||||
if (typeof data == "number") {
|
||||
this.abReceive.push(data);
|
||||
}
|
||||
else if (typeof data == "string") {
|
||||
var bASCII = 0, bASCIIPrev;
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
bASCIIPrev = bASCII;
|
||||
bASCII = data.charCodeAt(i);
|
||||
/*
|
||||
* NOTE: Multiple lines of pasted text will (at least on macOS) contain LFs instead of CRs;
|
||||
* we convert them to CRs below. Windows may do something different, but in the worst case,
|
||||
* even if we receive CR/LF pairs, this code should keep the CRs and lose the LFs.
|
||||
*/
|
||||
if (bASCII == Str.ASCII.LF) {
|
||||
if (bASCIIPrev == Str.ASCII.CR) continue;
|
||||
bASCII = Str.ASCII.CR;
|
||||
}
|
||||
this.abReceive.push(bASCII);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.abReceive = this.abReceive.concat(data);
|
||||
}
|
||||
|
||||
return true; // for now, return true regardless, since we're buffering everything anyway
|
||||
}
|
||||
|
||||
/**
|
||||
* receiveByte()
|
||||
*
|
||||
* @this {SerialPortPDP10}
|
||||
* @return {number} (0x00-0xff if byte available, -1 if not)
|
||||
*/
|
||||
receiveByte()
|
||||
{
|
||||
var b = -1;
|
||||
if (this.abReceive.length) {
|
||||
/*
|
||||
* Here, as elsewhere (eg, the PC11 component), even if I trusted all incoming data
|
||||
* to be byte values (which I don't), there's also the risk that it could be signed data
|
||||
* (eg, -128 to 127, instead of 0 to 255). Both risks are good reasons to always mask
|
||||
* the data assigned to RBUF with 0xff.
|
||||
*/
|
||||
b = this.abReceive.shift() & 0xff;
|
||||
this.printMessage("receiveByte(" + Str.toHexByte(b) + ")");
|
||||
if (this.fUpperCase) {
|
||||
/*
|
||||
* Automatically transform lower-case ASCII codes to upper-case; fUpperCase should
|
||||
* only be set when a terminal or some sort of pseudo-display is being used and we don't
|
||||
* trust it to have its CAPS-LOCK setting correct.
|
||||
*/
|
||||
if (b >= 0x61 && b < 0x7A) b -= 0x20;
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* receiveStatus(pins)
|
||||
*
|
||||
* @this {SerialPortPDP10}
|
||||
* @param {number} pins
|
||||
*/
|
||||
receiveStatus(pins)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* setConnection(component, fn)
|
||||
*
|
||||
* @this {SerialPortPDP10}
|
||||
* @param {Object|null} component
|
||||
* @param {function(number)} fn
|
||||
* @return {boolean}
|
||||
*/
|
||||
setConnection(component, fn)
|
||||
{
|
||||
if (!this.connection) {
|
||||
this.connection = component;
|
||||
this.sendData = fn;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* transmitByte(b)
|
||||
*
|
||||
* @this {SerialPortPDP10}
|
||||
* @param {number} b
|
||||
* @return {boolean} true if transmitted, false if not
|
||||
*/
|
||||
transmitByte(b)
|
||||
{
|
||||
var fTransmitted = false;
|
||||
|
||||
if (MAXDEBUG) this.printMessage("transmitByte(" + Str.toHexByte(b) + ")");
|
||||
|
||||
if (this.sendData) {
|
||||
if (this.sendData.call(this.connection, b)) {
|
||||
fTransmitted = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Why do DEC diagnostics like to output bytes with bit 7 set?
|
||||
*/
|
||||
b &= 0x7F;
|
||||
|
||||
if (this.controlIOBuffer) {
|
||||
if (b == 0x0D) {
|
||||
this.iLogicalCol = 0;
|
||||
}
|
||||
else if (b == 0x08) {
|
||||
this.controlIOBuffer.value = this.controlIOBuffer.value.slice(0, -1);
|
||||
/*
|
||||
* TODO: Back up the correct number of columns if the character erased was a tab.
|
||||
*/
|
||||
if (this.iLogicalCol > 0) this.iLogicalCol--;
|
||||
}
|
||||
else if (b) {
|
||||
/*
|
||||
* RT-11 outputs lots of NULL characters, at least after a "D 56=5015" (0x0A0D) command has
|
||||
* been issued, hence the "if (b)" check above.
|
||||
*
|
||||
* TODO: Also consider a check for Keys.ASCII.CTRL_C, because by default, RT-11 outputs "raw"
|
||||
* CTRL_C characters, which we capture below and render as <ETX>. RT-11 does this for other keys
|
||||
* as well, such as CTRL_K (<VT>) and CTRL_L (<FF>).
|
||||
*/
|
||||
var s = Str.toASCIICode(b); // formerly: String.fromCharCode(b);
|
||||
var nChars = s.length; // formerly: (b >= 0x20? 1 : 0);
|
||||
if (b < 0x20 && nChars == 1) nChars = 0;
|
||||
if (b == 0x09) {
|
||||
var tabSize = this.tabSize || 8;
|
||||
nChars = tabSize - (this.iLogicalCol % tabSize);
|
||||
if (this.tabSize) s = Str.pad("", nChars);
|
||||
}
|
||||
if (this.charBOL && !this.iLogicalCol && nChars) s = String.fromCharCode(this.charBOL) + s;
|
||||
this.controlIOBuffer.value += s;
|
||||
this.controlIOBuffer.scrollTop = this.controlIOBuffer.scrollHeight;
|
||||
this.iLogicalCol += nChars;
|
||||
}
|
||||
fTransmitted = true;
|
||||
}
|
||||
else if (this.consoleOutput != null) {
|
||||
if (b == 0x0A || this.consoleOutput.length >= 1024) {
|
||||
this.println(this.consoleOutput);
|
||||
this.consoleOutput = "";
|
||||
}
|
||||
if (b != 0x0A) {
|
||||
this.consoleOutput += String.fromCharCode(b);
|
||||
}
|
||||
fTransmitted = true;
|
||||
}
|
||||
|
||||
return fTransmitted;
|
||||
}
|
||||
|
||||
/**
|
||||
* SerialPortPDP10.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "serial", extracting the
|
||||
* JSON-encoded parameters for the SerialPort constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a SerialPort component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var aeSerial = Component.getElementsByClass(document, PDP10.APPCLASS, "serial");
|
||||
for (var iSerial = 0; iSerial < aeSerial.length; iSerial++) {
|
||||
var eSerial = aeSerial[iSerial];
|
||||
var parmsSerial = Component.getComponentParms(eSerial);
|
||||
var serial = new SerialPortPDP10(parmsSerial);
|
||||
Component.bindComponentControls(serial, eSerial, PDP10.APPCLASS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal name used for the I/O buffer control, if any, that we bind to the SerialPort.
|
||||
*
|
||||
* Alternatively, if SerialPort wants to use another component's control (eg, the Panel's
|
||||
* "print" control), it can specify the name of that control with the 'binding' property.
|
||||
*
|
||||
* For that binding to succeed, we also need to know the target component; for now, that's
|
||||
* been hard-coded to "Panel", in part because that's one of the few components we can rely
|
||||
* upon initializing before we do, but it would be a simple matter to include a component type
|
||||
* or ID as part of the 'binding' property as well, if we need more flexibility later.
|
||||
*/
|
||||
SerialPortPDP10.sIOBuffer = "buffer";
|
||||
|
||||
/*
|
||||
* Initialize every SerialPort module on the page.
|
||||
*/
|
||||
Web.onInit(SerialPortPDP10.init);
|
||||
|
||||
if (NODE) module.exports = SerialPortPDP10;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP11 Bus component.
|
||||
* @fileoverview Implements the PDP-11 Bus component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP11 Computer component.
|
||||
* @fileoverview Implements the PDP-11 Computer component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Controls the PDP11 CPU component.
|
||||
* @fileoverview Controls the PDP-11 CPU component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Implements PDP11 opcode handlers.
|
||||
* @fileoverview Implements PDP-11 opcode handlers.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP11 CPU component.
|
||||
* @fileoverview Implements the PDP-11 CPU component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP11 Debugger component.
|
||||
* @fileoverview Implements the PDP-11 Debugger component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview PDP11-specific compile-time definitions.
|
||||
* @fileoverview PDP-11 compile-time definitions.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Implements PDP11 device support.
|
||||
* @fileoverview Implements PDP-11 device support.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Implements a generic disk drive controller
|
||||
* @fileoverview Implements a generic disk drive controller.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP11 Keyboard component.
|
||||
* @fileoverview Implements the PDP-11 Keyboard component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP11 Memory component.
|
||||
* @fileoverview Implements the PDP-11 Memory component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
@ -738,8 +738,8 @@ class MemoryPDP11 {
|
|||
*
|
||||
* @this {MemoryPDP11}
|
||||
* @param {number} off
|
||||
* @param {number} addr
|
||||
* @param {number} b
|
||||
* @param {number} addr
|
||||
*/
|
||||
writeByteChecked(off, b, addr)
|
||||
{
|
||||
|
|
@ -754,8 +754,8 @@ class MemoryPDP11 {
|
|||
*
|
||||
* @this {MemoryPDP11}
|
||||
* @param {number} off
|
||||
* @param {number} addr
|
||||
* @param {number} w
|
||||
* @param {number} addr
|
||||
*/
|
||||
writeWordChecked(off, w, addr)
|
||||
{
|
||||
|
|
@ -859,8 +859,8 @@ class MemoryPDP11 {
|
|||
*
|
||||
* @this {MemoryPDP11}
|
||||
* @param {number} off
|
||||
* @param {number} addr
|
||||
* @param {number} b
|
||||
* @param {number} addr
|
||||
*/
|
||||
writeByteLE(off, b, addr)
|
||||
{
|
||||
|
|
@ -876,8 +876,8 @@ class MemoryPDP11 {
|
|||
*
|
||||
* @this {MemoryPDP11}
|
||||
* @param {number} off
|
||||
* @param {number} addr
|
||||
* @param {number} w
|
||||
* @param {number} addr
|
||||
*/
|
||||
writeWordBE(off, w, addr)
|
||||
{
|
||||
|
|
@ -893,8 +893,8 @@ class MemoryPDP11 {
|
|||
*
|
||||
* @this {MemoryPDP11}
|
||||
* @param {number} off
|
||||
* @param {number} addr
|
||||
* @param {number} w
|
||||
* @param {number} addr
|
||||
*/
|
||||
writeWordLE(off, w, addr)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Defines PDP11 message categories.
|
||||
* @fileoverview Defines PDP-11 message categories.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP11 Panel component.
|
||||
* @fileoverview Implements the PDP-11 Panel component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP11 RAM component.
|
||||
* @fileoverview Implements the PDP-11 RAM component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP11 ROM component.
|
||||
* @fileoverview Implements the PDP-11 ROM component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2017
|
||||
*
|
||||
|
|
|
|||
|
|
@ -657,11 +657,11 @@ class Debugger extends Component {
|
|||
* @param {boolean} [fStripLeadingZeros]
|
||||
* @return {string}
|
||||
*/
|
||||
toStrBase(n, nBytes, fStripLeadingZeros) {
|
||||
toStrBase(n, nBytes = 0, fStripLeadingZeros = false) {
|
||||
var s;
|
||||
switch(this.nBase) {
|
||||
case 8:
|
||||
s = Str.toOct(n, nBytes * 3 - (nBytes > 2? 1 : 0));
|
||||
s = Str.toOct(n, nBytes * 3 /* - (nBytes > 2? 1 : 0) */);
|
||||
break;
|
||||
case 10:
|
||||
s = n.toString();
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
var DEBUG = true;
|
||||
|
||||
/*
|
||||
From the "PDP-10 System Reference Manual", May 1968, p. 1-4:
|
||||
|
||||
|
|
@ -116,13 +114,13 @@ var DEBUG = true;
|
|||
events in previous instructions.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @class Int36
|
||||
* @property {number} value
|
||||
* @property {number|null} extended
|
||||
* @property {number|null} remainder
|
||||
* @property {number} error
|
||||
* @unrestricted
|
||||
*
|
||||
* The 'value' property stores the 36-bit value as an unsigned integer. When the value should be
|
||||
* interpreted as a signed quantity, subtract BIT36 whenever value > MAXPOS.
|
||||
|
|
@ -140,7 +138,6 @@ var DEBUG = true;
|
|||
* to the 'extended' portion as the "low order part" and the 'value' portion as the "high order part",
|
||||
* presumably because they number the left-most significant bit 0.
|
||||
*/
|
||||
|
||||
class Int36 {
|
||||
/**
|
||||
* Int36(obj, extended)
|
||||
|
|
@ -280,7 +277,7 @@ class Int36 {
|
|||
* @param {boolean} [fUnsigned] (default is signed for radix 10, unsigned for any other radix)
|
||||
* @return {string}
|
||||
*/
|
||||
toString(radix = 10, fUnsigned)
|
||||
toString(radix = 10, fUnsigned = false)
|
||||
{
|
||||
if (radix == 10) {
|
||||
return this.toDecimal(fUnsigned);
|
||||
|
|
|
|||
Loading…
Reference in a new issue