Supoprt for tape images in the Absolute Loader format has been moved to the RAM component, so that those images can be preloaded into RAM in the same way that raw bootstrap images can; the RAM component now provides Absolute Loader image parsing as a service to other components that need it (specifically, the PC11 tape reader)

This commit is contained in:
Jeff Parsons 2016-10-19 11:10:38 -07:00 committed by Jeff Parsons
commit 13a1dedbc7
21 changed files with 800 additions and 693 deletions

View file

@ -66,8 +66,8 @@ function RAM8080(parmsRAM)
this.addrRAM = parmsRAM['addr'];
this.sizeRAM = parmsRAM['size'];
this.nFileLoad = parmsRAM['load'];
this.nFileExec = parmsRAM['exec'];
this.addrLoad = parmsRAM['load'];
this.addrExec = parmsRAM['exec'];
this.fInstalled = (!!this.sizeRAM); // 0 is the default value for 'size' when none is specified
this.fAllocated = false;
@ -197,8 +197,8 @@ RAM8080.prototype.doneLoad = function(sURL, sData, nErrorCode)
if (resource) {
this.abInit = resource.aBytes;
this.aSymbols = resource.aSymbols;
if (this.nFileLoad == null && resource.nLoad != null) this.nFileLoad = resource.nLoad;
if (this.nFileExec == null && resource.nExec != null) this.nFileExec = resource.nExec;
if (this.addrLoad == null) this.addrLoad = resource.addrLoad;
if (this.addrExec == null) this.addrExec = resource.addrExec;
} else {
this.sFilePath = null;
}
@ -232,19 +232,19 @@ RAM8080.prototype.initRAM = function()
if (!this.abInit || !this.bus) return;
var addr = this.addrRAM;
if (this.nFileLoad !== null) addr = this.nFileLoad;
if (this.addrLoad !== null) addr = this.addrLoad;
for (var i = 0; i < this.abInit.length; i++) {
this.bus.setByteDirect(addr + i, this.abInit[i]);
}
if (this.nFileExec !== null) {
if (this.addrExec !== null) {
/*
* Here's where we enable our "Fake CP/M" support, triggered by the user loading a "writable" ROM image
* at offset 0x100. Fake CP/M support works by installing HLT opcodes at well-known CP/M addresses
* (namely, 0x0000, which is the CP/M reset vector, and 0x0005, which is the CP/M system call vector) and
* then telling the CPU to call us whenever a HLT occurs, so we can check PC for one of these addresses.
*/
if (this.nFileExec == RAM8080.CPM.INIT) {
if (this.addrExec == RAM8080.CPM.INIT) {
for (i = 0; i < RAM8080.CPM.VECTORS.length; i++) {
this.bus.setByteDirect(RAM8080.CPM.VECTORS[i], CPUDef8080.OPCODE.HLT);
}
@ -255,7 +255,7 @@ RAM8080.prototype.initRAM = function()
};
}(this));
}
this.cpu.setReset(this.nFileExec);
this.cpu.setReset(this.addrExec);
}
/*

View file

@ -1278,7 +1278,7 @@ FDC.prototype.autoMount = function(fRemount)
if (this.configMount) {
for (var sDrive in this.configMount) {
var configDrive = this.configMount[sDrive];
var sDiskettePath = configDrive['path'];
var sDiskettePath = configDrive['path'] || "";
var sDisketteName = configDrive['name'] || this.findDiskette(sDiskettePath);
if (sDiskettePath && sDisketteName) {
/*

View file

@ -369,6 +369,13 @@ CPUStatePDP11.prototype.setReset = function(addr)
{
this.addrReset = addr;
this.setPC(addr);
if (this.dbg) {
/*
* TODO: Review the decision to always stop the CPU if the Debugger is loaded.
*/
this.stopCPU();
this.dbg.updateStatus();
}
};
/**

View file

@ -93,6 +93,7 @@ function DebuggerPDP11(parmsDbg)
/*
* Since this Debugger doesn't use replaceRegs(), we can use parentheses instead of braces.
*/
this.fInit = false;
this.fParens = true;
/*
@ -1217,6 +1218,7 @@ if (DEBUGGER) {
*/
DebuggerPDP11.prototype.init = function()
{
this.fInit = true;
this.println("Type ? for help with PDP11 Debugger commands");
this.updateStatus();
if (this.sInitCommands) {
@ -1367,6 +1369,8 @@ if (DEBUGGER) {
*/
DebuggerPDP11.prototype.updateStatus = function(fRegs)
{
if (!this.fInit) return;
if (fRegs === undefined) fRegs = true;
this.dbgAddrNextCode = this.newAddr(this.cpu.getPC());

View file

@ -220,6 +220,7 @@ PC11.prototype.initBus = function(cmp, bus, cpu, dbg)
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.ram = cmp.getMachineComponent("RAM");
var pc11 = this;
@ -310,7 +311,7 @@ PC11.prototype.autoMount = function(fRemount)
{
if (!fRemount) this.cAutoMount = 0;
if (this.configMount) {
var sTapePath = this.configMount['path'];
var sTapePath = this.configMount['path'] || "";
var sTapeName = this.configMount['name'] || this.findTape(sTapePath);
if (sTapePath && sTapeName) {
/*
@ -320,7 +321,11 @@ PC11.prototype.autoMount = function(fRemount)
this.setReady(false);
}
} else {
this.notice("Incorrect auto-mount settings for PC11 (" + JSON.stringify(this.configMount) + ")");
/*
* This likely happened because there was no autoMount setting (or it was overridden with an empty value),
* so just make sure the current selection is set to "None".
*/
this.displayTape();
}
}
return !!this.cAutoMount;
@ -360,7 +365,7 @@ PC11.prototype.loadSelectedTape = function(sTapeName, sTapePath, nTapeTarget, fi
sTapePath = window.prompt("Enter the URL of a remote tape image.", "") || "";
if (!sTapePath) return;
sTapeName = str.getBaseName(sTapePath);
if (DEBUG) this.println("Attempting to load " + sTapePath + " as \"" + sTapeName + "\"");
this.status("Attempting to load " + sTapePath + " as \"" + sTapeName + "\"");
this.sTapeSource = PC11.SOURCE.REMOTE;
}
else {
@ -395,7 +400,7 @@ PC11.prototype.loadTape = function(sTapeName, sTapePath, nTapeTarget, fAutoMount
this.notice("PC11 busy");
}
else {
// if (DEBUG) this.println("tape queued: " + sTapeName);
// this.status("tape queued: " + sTapeName);
if (fAutoMount) {
this.cAutoMount++;
if (this.messageEnabled()) this.printMessage("auto-loading tape: " + sTapeName);
@ -407,7 +412,7 @@ PC11.prototype.loadTape = function(sTapeName, sTapePath, nTapeTarget, fAutoMount
}
}
}
if (DEBUG && nResult) this.println(this.nTapeTarget == PC11.TARGET.READER? "tape attached" : "tape loaded");
if (nResult) this.status(this.nTapeTarget == PC11.TARGET.READER? "tape attached" : "tape loaded");
return nResult;
};
@ -496,7 +501,7 @@ PC11.prototype.doneLoad = function(sTapeName, sTapePath, nTapeTarget, sTapeData,
Component.addMachineResource(this.idMachine, sURL, sTapeData);
var resource = web.parseMemoryResource(sURL, sTapeData);
if (resource) {
this.parseTape(sTapeName, sTapePath, nTapeTarget, resource.aBytes);
this.parseTape(sTapeName, sTapePath, nTapeTarget, resource.aBytes, resource.addrLoad, resource.addrExec);
}
}
this.flags.busy = false;
@ -597,107 +602,50 @@ PC11.prototype.displayTape = function()
};
/**
* parseTape(sTapeName, sTapePath, nTapeTarget, aBytes)
* parseTape(sTapeName, sTapePath, nTapeTarget, aBytes, addrLoad, addrExec)
*
* @this {PC11}
* @param {string} sTapeName
* @param {string} sTapePath
* @param {number} nTapeTarget
* @param {Array|Uint8Array} aBytes
* @param {number|null} [addrLoad]
* @param {number|null} [addrExec]
*/
PC11.prototype.parseTape = function(sTapeName, sTapePath, nTapeTarget, aBytes)
PC11.prototype.parseTape = function(sTapeName, sTapePath, nTapeTarget, aBytes, addrLoad, addrExec)
{
this.sTapeName = sTapeName;
this.sTapePath = sTapePath;
this.nTapeTarget = nTapeTarget;
if (nTapeTarget == PC11.TARGET.MEMORY) {
/*
* Data on tapes is organized into blocks; each block begins with a 6-byte header:
* Use the RAM component's loadImage() service to do our dirty work. If the load succeeds, then
* depending on whether there was also exec address, either the CPU will be stopped or the PC wil be
* reset.
*
* 2-byte signature (0x01,0x00)
* 2-byte length (N + 6)
* 2-byte load address
* NOTE: Some tapes are not in the Absolute Loader format, so if the JSON-encoded tape resource file
* we downloaded didn't ALSO include a load address, the load will fail.
*
* 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), in which case stop.
*
* After the data bytes, there is a single checksum byte. The sum of all the bytes (including
* the 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.
* For example, the "Absolute Loader" tape is NOT itself in the Absolute Loader format. You just have
* to know that in order to load that tape, you must first load the appropriate "Bootstrap Loader" (which
* DOES include its own hard-coded load address), attach the "Absolute Loader" tape, and then run the
* "Bootstrap Loader".
*/
var off = 0;
var fError = false, cBlocks = 0;
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) {
if (DEBUG) this.println("invalid signature (" + str.toHexWord(w) + ") at offset " + str.toHexWord(offBlock));
if (!cBlocks) fError = true;
break;
}
if (off + 6 >= aBytes.length) {
if (DEBUG) this.println("invalid block at offset " + str.toHexWord(offBlock));
fError = true;
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 - 1) {
checksum += aBytes[off++] & 0xff;
len--;
}
if (len != 0 || off >= aBytes.length) {
if (DEBUG) this.println("insufficient data for block at offset " + str.toHexWord(offBlock));
fError = true;
break;
}
checksum += aBytes[off++] & 0xff;
if (checksum & 0xff) {
if (DEBUG) this.println("invalid checksum (" + str.toHexByte(checksum) + ") for block at offset " + str.toHexWord(offBlock));
fError = true;
break;
}
if (!cbData) {
if (!(addr & 0x1)) {
this.cpu.setPC(addr);
}
} else {
while (cbData--) {
this.cpu.setByteDirect(addr++, aBytes[offData++] & 0xff);
}
}
cBlocks++;
}
if (fError) {
if (!this.ram || !this.ram.loadImage(aBytes, addrLoad, addrExec)) {
this.sTapeName = "";
this.sTapePath = "";
this.sTapeSource = PC11.SOURCE.NONE;
this.nTapeTarget = PC11.TARGET.NONE;
if (DEBUG) this.println("error loading tape: " + sTapeName);
this.status("error loading tape: " + sTapeName);
return;
}
if (DEBUG) this.println("tape loaded: " + sTapeName);
this.status("tape loaded: " + sTapeName);
return;
}
this.iTapeData = 0;
this.aTapeData = aBytes;
if (DEBUG) this.println("tape attached: " + sTapeName);
this.status("tape attached: " + sTapeName);
};
/**
@ -715,7 +663,7 @@ PC11.prototype.unloadTape = function(fLoading)
* Avoid any unnecessary hysteresis regarding the display if this unload is merely a prelude to another load.
*/
if (!fLoading) {
if (DEBUG && this.nTapeTarget) this.println(this.nTapeTarget == PC11.TARGET.READER? "tape detached" : "tape unloaded");
if (this.nTapeTarget) this.status(this.nTapeTarget == PC11.TARGET.READER? "tape detached" : "tape unloaded");
this.sTapeSource = PC11.SOURCE.NONE;
this.nTapeTarget = PC11.TARGET.NONE;
this.displayTape();

View file

@ -69,8 +69,8 @@ function RAMPDP11(parmsRAM)
this.addrRAM = parmsRAM['addr'];
this.sizeRAM = parmsRAM['size'];
this.nFileLoad = parmsRAM['load'];
this.nFileExec = parmsRAM['exec'];
this.addrLoad = parmsRAM['load'];
this.addrExec = parmsRAM['exec'];
this.fInstalled = (!!this.sizeRAM); // 0 is the default value for 'size' when none is specified
this.fAllocated = false;
@ -175,8 +175,8 @@ RAMPDP11.prototype.doneLoad = function(sURL, sData, nErrorCode)
if (resource) {
this.abInit = resource.aBytes;
this.aSymbols = resource.aSymbols;
if (this.nFileLoad == null && resource.nLoad != null) this.nFileLoad = resource.nLoad;
if (this.nFileExec == null && resource.nExec != null) this.nFileExec = resource.nExec;
if (this.addrLoad == null) this.addrLoad = resource.addrLoad;
if (this.addrExec == null) this.addrExec = resource.addrExec;
} else {
this.sFilePath = null;
}
@ -194,6 +194,8 @@ RAMPDP11.prototype.doneLoad = function(sURL, sData, nErrorCode)
*/
RAMPDP11.prototype.initRAM = function()
{
if (!this.bus) return;
if (!this.fAllocated && this.sizeRAM) {
if (this.bus.addMemory(this.addrRAM, this.sizeRAM, MemoryPDP11.TYPE.RAM)) {
this.fAllocated = true;
@ -209,15 +211,7 @@ RAMPDP11.prototype.initRAM = function()
*/
if (!this.abInit || !this.bus) return;
var addr = this.addrRAM;
if (this.nFileLoad !== null) addr = this.nFileLoad;
for (var i = 0; i < this.abInit.length; i++) {
this.bus.setByteDirect(addr + i, this.abInit[i]);
}
if (this.nFileExec !== null) {
this.cpu.setReset(this.nFileExec);
}
this.loadImage(this.abInit, this.addrLoad, this.addrExec, this.addrRAM);
/*
* TODO: Consider an option to retain this data and give the user a way of restoring the initial contents.
@ -240,6 +234,108 @@ RAMPDP11.prototype.reset = function()
*/
};
/**
* loadImage(aBytes, addrLoad, addrExec, addrInit)
*
* @this {RAMPDP11}
* @param {Array|Uint8Array} aBytes
* @param {number|null} [addrLoad]
* @param {number|null} [addrExec]
* @param {number|null} [addrInit]
* @return {boolean} (true if loaded, false if not)
*/
RAMPDP11.prototype.loadImage = function(aBytes, addrLoad, addrExec, addrInit)
{
var fLoaded = false;
/*
* Data on tapes 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 first, followed by high byte), just like
* the PDP-11 does.
*
* 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.
*/
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) {
if (MAXDEBUG) this.println("invalid signature (" + str.toHexWord(w) + ") at offset " + str.toHexWord(offBlock));
break;
}
if (off + 6 >= aBytes.length) {
if (MAXDEBUG) this.println("invalid block at offset " + str.toHexWord(offBlock));
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) {
if (MAXDEBUG) this.println("insufficient data for block at offset " + str.toHexWord(offBlock));
break;
}
checksum += aBytes[off++] & 0xff;
if (checksum & 0xff) {
if (MAXDEBUG) this.println("invalid checksum (" + str.toHexByte(checksum) + ") for block at offset " + str.toHexWord(offBlock));
break;
}
if (!cbData) {
if (addr & 0x1) {
this.cpu.stopCPU();
} else {
this.cpu.setReset(addr);
}
} else {
while (cbData--) {
this.cpu.setByteDirect(addr++, aBytes[offData++] & 0xff);
}
}
fLoaded = true;
}
}
if (!fLoaded) {
if (addrLoad == null) addrLoad = addrInit;
if (addrLoad != null) {
for (var i = 0; i < aBytes.length; i++) {
this.cpu.setByteDirect(addrLoad + i, aBytes[i]);
}
if (addrExec != null) this.cpu.setReset(addrExec);
fLoaded = true;
}
}
return fLoaded;
};
/**
* RAMPDP11.init()
*

View file

@ -295,8 +295,8 @@ web.parseMemoryResource = function(sURL, sData)
var resource = {
aBytes: null,
aSymbols: null,
nLoad: null,
nExec: null
addrLoad: null,
addrExec: null
};
if (sData.charAt(0) == "[" || sData.charAt(0) == "{") {
@ -338,8 +338,8 @@ web.parseMemoryResource = function(sURL, sData)
data = eval("(" + sData + ")");
}
resource.nLoad = data['load'];
resource.nExec = data['exec'];
resource.addrLoad = data['load'];
resource.addrExec = data['exec'];
if (a = data['bytes']) {
resource.aBytes = a;