Created PDP-10 skeleton

This commit is contained in:
Jeff Parsons 2017-02-19 15:22:58 -08:00 committed by Jeff Parsons
commit ba7de38fbf
40 changed files with 12282 additions and 35 deletions

717
modules/pdp10/lib/bus.js Normal file
View 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;

File diff suppressed because it is too large Load diff

1274
modules/pdp10/lib/cpu.js Normal file

File diff suppressed because it is too large Load diff

View 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;

File diff suppressed because it is too large Load diff

View 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
View 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
View 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;

View 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;

View 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

File diff suppressed because it is too large Load diff

413
modules/pdp10/lib/ram.js Normal file
View 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
View 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
View 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;

View file

@ -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
*

View file

@ -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
*

View file

@ -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
*

View file

@ -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
*

View file

@ -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
*

View file

@ -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
*

View file

@ -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
*

View file

@ -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
*

View file

@ -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
*

View file

@ -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
*

View file

@ -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)
{

View file

@ -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
*

View file

@ -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
*

View file

@ -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
*

View file

@ -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
*

View file

@ -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();

View file

@ -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);