Added support for PDP-10 RAM images

This commit is contained in:
Jeff Parsons 2017-02-26 20:37:16 -08:00 committed by Jeff Parsons
commit aecd943c52
23 changed files with 583 additions and 632 deletions

View file

@ -46,7 +46,7 @@ var DumpAPI = require("../../shared/lib/dumpapi");
* what the loadFile() function already does.
*
* @constructor
* @param {string|undefined} sFormat should be one of "json"|"data"|"hex"|"bytes"|"rom" (see the FORMAT constants)
* @param {string|undefined} sFormat should be one of "json"|"longs"|"hex"|"bytes"|"rom" (see the FORMAT constants)
* @param {boolean|string|undefined} fComments enables comments and other readability enhancements in the JSON output
* @param {boolean|string|undefined} fDecimal forces decimal output if not undefined
* @param {number|string|undefined} offDump
@ -82,7 +82,7 @@ function FileDump(sFormat, fComments, fDecimal, offDump, nWidthDump, sServerRoot
FileDump.sAPIURL = "http://www.pcjs.org" + DumpAPI.ENDPOINT;
FileDump.sCopyright = COPYRIGHT;
FileDump.sNotice = FileDump.sAPIURL + " " + FileDump.sCopyright;
FileDump.sUsage = "Usage: " + FileDump.sAPIURL + "?" + DumpAPI.QUERY.FILE + "=({path}|{URL})&" + DumpAPI.QUERY.FORMAT + "=(json|data|hex|octal|bytes|words|rom)";
FileDump.sUsage = "Usage: " + FileDump.sAPIURL + "?" + DumpAPI.QUERY.FILE + "=({path}|{URL})&" + DumpAPI.QUERY.FORMAT + "=(json|longs|hex|octal|bytes|words|rom)";
FileDump.asBadExts = [
"js", "log"
@ -99,7 +99,7 @@ FileDump.asBadExts = [
*
* Usage
* ---
* filedump --file=({path}|{URL}) [--merge=({path}|{url})] [--format=(json|data|hex|octal|bytes|words|rom)]
* filedump --file=({path}|{URL}) [--merge=({path}|{url})] [--format=(json|longs|hex|octal|bytes|words|rom)]
* [--comments] [--decimal] [--offset={number}] [--width={number}] [--load={number}] [--exec={number}]
* [--output={path}] [--overwrite]
*
@ -146,7 +146,7 @@ FileDump.CLI = function()
var args = proc.getArgs();
if (!args.argc) {
console.log("usage: filedump --file=({path}|{URL}) [--merge=({path}|{url})] [--format=(json|data|hex|octal|bytes|words|rom)] [--comments] [--decimal] [--offset={number}] [--width={number}] [--output={path}] [--overwrite]");
console.log("usage: filedump --file=({path}|{URL}) [--merge=({path}|{url})] [--format=(json|longs|hex|octal|bytes|words|rom)] [--comments] [--decimal] [--offset={number}] [--width={number}] [--output={path}] [--overwrite]");
return;
}
@ -752,7 +752,7 @@ FileDump.prototype.buildJSON = function()
else if (this.sFormat == DumpAPI.FORMAT.OCTAL || this.sFormat == DumpAPI.FORMAT.WORDS) {
this.json += this.dumpBuffer(this.sFormat, this.buf, this.buf.length, 2);
} else {
this.json += this.dumpBuffer("data", this.buf, this.buf.length, 4);
this.json += this.dumpBuffer(DumpAPI.FORMAT.LONGS, this.buf, this.buf.length, 4);
}
}
};

View file

@ -344,10 +344,10 @@ class CPUStatePDP10 extends CPUPDP10 {
}
/*
* Bits 0-22 (I,X,Y) contain what we call a "reference address" (R), which is used to calculate the
* "effective address" (E). To determine E from R, we must extract I, X, and Y from R, set E to Y,
* then add [X] to E if X is non-zero. If I is zero, then we're done; otherwise, we must set R to [E]
* and repeat the process.
* Bits 0-22 (I,X,Y) contain what we call a "reference address" (R), which is used to calculate an
* 18-bit "effective address" (E). To determine E from R, we must extract I, X, and Y from R, set E
* to Y, then add [X] to E if X is non-zero. If I is zero, then we're done; otherwise, we must set R
* to [E] and repeat the process.
*
* However, we don't actually repeat the process immediately; we need to treat each indirection as a
* separate decoding step, to ensure that the emulator can "breathe" periodically. So instead, we

View file

@ -59,7 +59,7 @@ class RAMPDP10 extends Component {
{
super("RAM", parmsRAM);
this.abInit = null;
this.aData = null;
this.aSymbols = null;
this.addrRAM = +parmsRAM['addr'];
@ -71,7 +71,7 @@ class RAMPDP10 extends Component {
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.fAllocated = this.fReset = false;
this.sFilePath = parmsRAM['file'];
this.sFileName = Str.getBaseName(this.sFilePath);
@ -180,7 +180,7 @@ class RAMPDP10 extends Component {
Component.addMachineResource(this.idMachine, sURL, sData);
var resource = Web.parseMemoryResource(sURL, sData);
if (resource) {
this.abInit = resource.aBytes;
this.aData = resource.aData;
this.aSymbols = resource.aSymbols;
if (this.addrLoad == null) this.addrLoad = resource.addrLoad;
if (this.addrExec == null) this.addrExec = resource.addrExec;
@ -219,9 +219,9 @@ class RAMPDP10 extends Component {
/*
* Too early...
*/
if (!this.abInit || !this.bus) return;
if (!this.aData) return;
if (this.loadImage(this.abInit, this.addrLoad, this.addrExec, this.addrRAM)) {
if (this.loadImage(this.aData, this.addrLoad, this.addrExec, this.addrRAM)) {
this.status('Loaded image "' + this.sFileName + '"');
} else {
this.notice('Error loading image "' + this.sFileName + '"');
@ -230,9 +230,10 @@ class RAMPDP10 extends Component {
/*
* NOTE: We now retain this data, so that reset() can return the RAM to its predefined state.
*
* delete this.abInit;
* delete this.aData;
*/
}
this.fReset = true;
this.setReady();
}
}
@ -244,126 +245,46 @@ class RAMPDP10 extends Component {
*/
reset()
{
if (this.fAllocated) {
if (this.fAllocated && !this.fReset) {
/*
* 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);
if (this.aData) {
this.loadImage(this.aData, this.addrLoad, this.addrExec, this.addrRAM, !this.dbg);
}
}
this.fReset = false;
}
/**
* loadImage(aBytes, addrLoad, addrExec, addrInit, fStart)
* loadImage(aData, 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 {Array} aData
* @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)
loadImage(aData, 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;
}
addrLoad = addrInit;
}
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 (addrLoad != null) {
for (var i = 0; i < aData.length; i++) {
this.bus.setWordDirect(addrLoad + i, aData[i]);
}
fLoaded = true;
}
if (fLoaded) {
/*
@ -374,7 +295,7 @@ class RAMPDP10 extends Component {
* 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) {
if (addrExec == null) {
this.cpu.stopCPU();
fStart = false;
}

View file

@ -76,7 +76,7 @@ class RAMPDP11 extends Component {
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.fAllocated = this.fReset = false;
this.sFilePath = parmsRAM['file'];
this.sFileName = Str.getBaseName(this.sFilePath);
@ -224,7 +224,7 @@ class RAMPDP11 extends Component {
/*
* Too early...
*/
if (!this.abInit || !this.bus) return;
if (!this.abInit) return;
if (this.loadImage(this.abInit, this.addrLoad, this.addrExec, this.addrRAM)) {
this.status('Loaded image "' + this.sFileName + '"');
@ -238,6 +238,7 @@ class RAMPDP11 extends Component {
* delete this.abInit;
*/
}
this.fReset = true;
this.setReady();
}
}
@ -249,7 +250,7 @@ class RAMPDP11 extends Component {
*/
reset()
{
if (this.fAllocated) {
if (this.fAllocated && !this.fReset) {
/*
* TODO: Add a configuration parameter for selecting the byte pattern on reset?
* Note that when memory blocks are originally created, they are currently always
@ -260,6 +261,7 @@ class RAMPDP11 extends Component {
this.loadImage(this.abInit, this.addrLoad, this.addrExec, this.addrRAM, !this.dbg);
}
}
this.fReset = false;
}
/**

View file

@ -70,6 +70,7 @@ var DumpAPI = {
OCTAL: "octal", // displays data as octal words
BYTES: "bytes", // displays data as hex bytes; normally used only when comments are enabled
WORDS: "words", // displays data as hex words; normally used only when comments are enabled
LONGS: "longs", // displays data as dwords
IMG: "img", // returns the raw disk data (ie, using a Buffer object) (DiskDump only)
ROM: "rom" // returns the raw file data (ie, using a Buffer object) (FileDump only)
}

View file

@ -268,6 +268,23 @@ class Web {
/**
* parseMemoryResource(sURL, sData)
*
* This converts a variety of JSON-style data streams into an Object with the following properties:
*
* aBytes
* aSymbols
* addrLoad
* addrExec
*
* If the source data contains a 'bytes' array, it's passed through to 'aBytes'; alternatively, if
* it contains a 'words' array, the values are converted from 16-bit to 8-bit and stored in 'aBytes',
* and if it contains a 'longs' array, the values are converted from 32-bit longs into bytes and
* stored in 'aBytes'.
*
* Alternatively, if the source data contains a 'data' array, we simply pass that through to the output
* object as:
*
* aData
*
* @param {string} sURL
* @param {string} sData
* @return {Object|null} (resource)
@ -315,8 +332,8 @@ class Web {
*
* ["unrecognized disk path: test.img"]
*/
if (sData.indexOf("0x") < 0 && sData.substr(0, 2) != "[\"") {
data = JSON.parse(sData.replace(/([a-z]+):/gm, "\"$1\":").replace(/\/\/[^\n]*/gm, ""));
if (sData.indexOf("0x") < 0 && sData.indexOf("0o") < 0 && sData.substr(0, 2) != '["') {
data = JSON.parse(sData.replace(/([a-z]+):/gm, '"$1":').replace(/\/\/[^\n]*/gm, ""));
} else {
data = eval("(" + sData + ")");
}
@ -338,7 +355,7 @@ class Web {
Component.assert(!(a[i] & ~0xffff));
}
}
else if (a = data['data']) {
else if (a = data['longs']) {
/*
* Convert all dwords (longs) into bytes
*/
@ -350,20 +367,25 @@ class Web {
resource.aBytes[ib++] = (a[i] >> 24) & 0xff;
}
}
else if (a = data['data']) {
resource.aData = a;
}
else {
resource.aBytes = data;
}
if (resource.aBytes) {
if (!resource.aBytes.length) {
Component.error("Empty resource: " + sURL);
resource = null;
}
else if (resource.aBytes.length == 1) {
Component.error(resource.aBytes[0]);
resource = null;
}
}
resource.aSymbols = data['symbols'];
if (!resource.aBytes.length) {
Component.error("Empty resource: " + sURL);
resource = null;
}
else if (resource.aBytes.length == 1) {
Component.error(resource.aBytes[0]);
resource = null;
}
} catch (e) {
Component.error("Resource data error (" + sURL + "): " + e.message);
resource = null;