Merge branch 'next-release'
This commit is contained in:
commit
baac8d4ea8
221 changed files with 6475 additions and 2731 deletions
|
|
@ -4,7 +4,7 @@
|
|||
<title>C1Pjs</title>
|
||||
<excerpt>The following document describes the C1Pjs source code.</excerpt>
|
||||
<content>
|
||||
<machine id="c1psim" class="c1p" border="1" width="272px" pos="right" padleft="50px" padright="50px">
|
||||
<machine id="c1psim" class="c1p" border="1" width="272px" pos="right" padLeft="50px" padRight="50px">
|
||||
<computer id="c1p" name="Challenger 1P">
|
||||
<module type="cpu" refid="cpu6502" start="0x0000" end="0xffff"/>
|
||||
<module type="ram" refid="ram8K" start="0x0000" end="0x1fff"/>
|
||||
|
|
|
|||
|
|
@ -123,10 +123,10 @@
|
|||
<xsl:choose>
|
||||
<xsl:when test="@padding">padding:<xsl:value-of select="@padding"/>;</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:if test="@padtop">padding-top:<xsl:value-of select="@padtop"/>;</xsl:if>
|
||||
<xsl:if test="@padright">padding-right:<xsl:value-of select="@padright"/>;</xsl:if>
|
||||
<xsl:if test="@padbottom">padding-bottom:<xsl:value-of select="@padbottom"/>;</xsl:if>
|
||||
<xsl:if test="@padleft">padding-left:<xsl:value-of select="@padleft"/>;</xsl:if>
|
||||
<xsl:if test="@padTop">padding-top:<xsl:value-of select="@padTop"/>;</xsl:if>
|
||||
<xsl:if test="@padRight">padding-right:<xsl:value-of select="@padRight"/>;</xsl:if>
|
||||
<xsl:if test="@padBottom">padding-bottom:<xsl:value-of select="@padBottom"/>;</xsl:if>
|
||||
<xsl:if test="@padLeft">padding-left:<xsl:value-of select="@padLeft"/>;</xsl:if>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ var DumpAPI = require("../../shared/lib/dumpapi");
|
|||
*/
|
||||
function FileDump(sFormat, fComments, fDecimal, offDump, nWidthDump, sServerRoot)
|
||||
{
|
||||
this.addrLoad = null;
|
||||
this.fDebug = false;
|
||||
this.sFormat = (sFormat || DumpAPI.FORMAT.JSON);
|
||||
this.fJSONNative = (this.sFormat == DumpAPI.FORMAT.JSON && !fComments);
|
||||
|
|
@ -297,7 +298,6 @@ FileDump.prototype.parseListing = function(sListing)
|
|||
var ab = [];
|
||||
var matchLine;
|
||||
var re = /^( [0-9 ]{8}|)([0-7]{6})[: ]+([0-7]+)[ ]*([0-7']+|)[ ]+([0-7']+|)[ ]+(.*)$/gm;
|
||||
var addrStart = null;
|
||||
while (matchLine = re.exec(sListing)) {
|
||||
/*
|
||||
* matchLine[1]: line # (optional)
|
||||
|
|
@ -307,7 +307,7 @@ FileDump.prototype.parseListing = function(sListing)
|
|||
* matchLine[5]: 6-digit operand (with optional apostrophe) or 3-digit data
|
||||
*/
|
||||
var addrLine = parseInt(matchLine[2], 8);
|
||||
if (addrStart == null) addrStart = addrLine;
|
||||
if (this.addrLoad == null) this.addrLoad = addrLine;
|
||||
for (var i = 3, s; i <= 5 && (s = matchLine[i]); i++) {
|
||||
var data = parseInt(s.substr(0, 6), 8);
|
||||
if (s.length == 3) {
|
||||
|
|
@ -481,7 +481,17 @@ FileDump.prototype.dumpBuffer = function(sKey, buf, len, cbItem, offDump, nWidth
|
|||
offDump = offDump || this.offDump;
|
||||
nWidthDump = nWidthDump || this.nWidthDump;
|
||||
|
||||
var sDump = this.dumpLine(2, (sKey? '"' + sKey + '":' : "") + this.sJSONWhitespace + chOpen);
|
||||
var sDump = "";
|
||||
if (this.addrLoad) {
|
||||
/*
|
||||
* TODO: We need command-line overrides (eg, --load and --exec) to allow the user to specify the
|
||||
* correct load (and exec) addresses. For now, we're simply inferring that the first address parsed
|
||||
* in parseListing() is both the load and exec address.
|
||||
*/
|
||||
var sAddr = str.toHexWord(this.addrLoad) + (nBase == 8? " /*" + str.toOct(this.addrLoad, 6) + "*/" : "");
|
||||
sDump += this.dumpLine(2, '"load":' + sAddr + ',"exec":' + sAddr + ',');
|
||||
}
|
||||
sDump += this.dumpLine(2, (sKey? '"' + sKey + '":' : "") + this.sJSONWhitespace + chOpen);
|
||||
|
||||
var sLine = "";
|
||||
var sASCII = "";
|
||||
|
|
|
|||
|
|
@ -31,11 +31,12 @@
|
|||
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 State = require("../../shared/lib/state");
|
||||
var PC8080 = require("./defines");
|
||||
var CPUDef8080 = require("./cpudef");
|
||||
var Memory8080 = require("./memory");
|
||||
var ROM8080 = require("./rom");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -45,6 +46,9 @@ if (NODE) {
|
|||
*
|
||||
* 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().
|
||||
|
|
@ -57,14 +61,66 @@ function RAM8080(parmsRAM)
|
|||
{
|
||||
Component.call(this, "RAM", parmsRAM, RAM8080);
|
||||
|
||||
this.abInit = null;
|
||||
this.aSymbols = null;
|
||||
|
||||
this.addrRAM = parmsRAM['addr'];
|
||||
this.sizeRAM = parmsRAM['size'];
|
||||
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;
|
||||
|
||||
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(sURL, sResponse, nErrorCode) {
|
||||
ram.doneLoad(sURL, sResponse, nErrorCode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Component.subclass(RAM8080);
|
||||
|
||||
RAM8080.CPM = {
|
||||
BIOS: {
|
||||
VECTOR: 0x0000
|
||||
},
|
||||
BDOS: {
|
||||
VECTOR: 0x0005,
|
||||
FUNC: { // function number (specified in regC)
|
||||
RESET: 0x00,
|
||||
CON_READ: 0x01, // output: A = L = ASCII character
|
||||
CON_WRITE: 0x02, // input: E = ASCII character
|
||||
AUX_READ: 0x03, // output: A = L = ASCII character
|
||||
AUX_WRITE: 0x04, // input: E = ASCII character
|
||||
PRN_WRITE: 0x05, // input: E = ASCII character
|
||||
MEM_SIZE: 0x06, // output: base address of CCP (Console Command Processor), but which register? (perhaps moot if this was CP/M 1.3 only...)
|
||||
CON_IO: 0x06, // input: E = ASCII character (or 0xFF to return ASCII character in A)
|
||||
GET_IOBYTE: 0x07,
|
||||
SET_IOBYTE: 0x08,
|
||||
STR_WRITE: 0x09 // input: DE = address of string
|
||||
}
|
||||
},
|
||||
INIT: 0x100
|
||||
};
|
||||
|
||||
RAM8080.CPM.VECTORS = [RAM8080.CPM.BIOS.VECTOR, RAM8080.CPM.BDOS.VECTOR];
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
|
|
@ -79,7 +135,7 @@ RAM8080.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.setReady();
|
||||
this.initRAM();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -92,15 +148,12 @@ RAM8080.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
*/
|
||||
RAM8080.prototype.powerUp = function(data, fRepower)
|
||||
{
|
||||
if (!fRepower) {
|
||||
/*
|
||||
* The Computer powers up the CPU last, at which point CPUState state is restored,
|
||||
* which includes the Bus state, and since we use the Bus to allocate all our memory,
|
||||
* memory contents are already restored for us, so we don't need the usual restore
|
||||
* logic. We just need to call reset(), to allocate memory for the RAM.
|
||||
*/
|
||||
this.reset();
|
||||
}
|
||||
/*
|
||||
* The Computer powers up the CPU last, at which point CPUState state is restored,
|
||||
* which includes the Bus state, and since we use the Bus to allocate all our memory,
|
||||
* memory contents are already restored for us, so we don't need the usual restore
|
||||
* logic.
|
||||
*/
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
@ -120,7 +173,98 @@ RAM8080.prototype.powerDown = function(fSave, fShutdown)
|
|||
* our memory, memory contents are already saved for us, so we don't need the usual
|
||||
* save logic.
|
||||
*/
|
||||
return (fSave)? this.save() : true;
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* doneLoad(sURL, sData, nErrorCode)
|
||||
*
|
||||
* @this {RAM8080}
|
||||
* @param {string} sURL
|
||||
* @param {string} sData
|
||||
* @param {number} nErrorCode (response from server if anything other than 200)
|
||||
*/
|
||||
RAM8080.prototype.doneLoad = function(sURL, sData, nErrorCode)
|
||||
{
|
||||
if (nErrorCode) {
|
||||
this.notice("Unable to load RAM resource (error " + nErrorCode + ": " + sURL + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
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 doneLoad(), but it cannot copy the initial data into place
|
||||
* until after initBus() has received the Bus component AND doneLoad() has received the data. When both those
|
||||
* criteria are satisfied, the component becomes "ready".
|
||||
*
|
||||
* @this {RAM8080}
|
||||
*/
|
||||
RAM8080.prototype.initRAM = function()
|
||||
{
|
||||
if (!this.fAllocated && this.sizeRAM) {
|
||||
if (this.bus.addMemory(this.addrRAM, this.sizeRAM, Memory8080.TYPE.RAM)) {
|
||||
this.fAllocated = true;
|
||||
}
|
||||
}
|
||||
if (!this.isReady()) {
|
||||
if (!this.fAllocated) {
|
||||
Component.error("No RAM allocated");
|
||||
}
|
||||
else if (this.sFilePath) {
|
||||
/*
|
||||
* Too early...
|
||||
*/
|
||||
if (!this.abInit || !this.bus) return;
|
||||
|
||||
var addr = this.addrRAM;
|
||||
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.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.addrExec == RAM8080.CPM.INIT) {
|
||||
for (i = 0; i < RAM8080.CPM.VECTORS.length; i++) {
|
||||
this.bus.setByteDirect(RAM8080.CPM.VECTORS[i], CPUDef8080.OPCODE.HLT);
|
||||
}
|
||||
|
||||
this.cpu.addHaltCheck(function(rom) {
|
||||
return function(addr) {
|
||||
return rom.checkCPMVector(addr)
|
||||
};
|
||||
}(this));
|
||||
}
|
||||
this.cpu.setReset(this.addrExec);
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Consider an option to retain this data and give the user a way of restoring the initial contents.
|
||||
*/
|
||||
delete this.abInit;
|
||||
}
|
||||
this.setReady();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -130,41 +274,99 @@ RAM8080.prototype.powerDown = function(fSave, fShutdown)
|
|||
*/
|
||||
RAM8080.prototype.reset = function()
|
||||
{
|
||||
if (!this.fAllocated && this.sizeRAM) {
|
||||
if (this.bus.addMemory(this.addrRAM, this.sizeRAM, Memory8080.TYPE.RAM)) {
|
||||
this.fAllocated = true;
|
||||
/*
|
||||
* If you want to zero RAM on reset, then this would be a good place to do it.
|
||||
*/
|
||||
};
|
||||
|
||||
/**
|
||||
* checkCPMVector(addr)
|
||||
*
|
||||
* @this {RAM8080}
|
||||
* @param {number} addr (of the HLT opcode)
|
||||
* @return {boolean} true if special processing performed, false if not
|
||||
*/
|
||||
RAM8080.prototype.checkCPMVector = function(addr)
|
||||
{
|
||||
var i = RAM8080.CPM.VECTORS.indexOf(addr);
|
||||
if (i >= 0) {
|
||||
var fCPM = false;
|
||||
var cpu = this.cpu;
|
||||
var dbg = this.dbg;
|
||||
if (addr == RAM8080.CPM.BDOS.VECTOR) {
|
||||
fCPM = true;
|
||||
switch(cpu.regC) {
|
||||
case RAM8080.CPM.BDOS.FUNC.CON_WRITE:
|
||||
this.writeCPMString(this.getCPMChar(cpu.regE));
|
||||
break;
|
||||
case RAM8080.CPM.BDOS.FUNC.STR_WRITE:
|
||||
this.writeCPMString(this.getCPMString(cpu.getDE(), '$'));
|
||||
break;
|
||||
default:
|
||||
fCPM = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fCPM) {
|
||||
CPUDef8080.opRET.call(cpu); // for recognized calls, automatically return
|
||||
}
|
||||
else if (dbg) {
|
||||
this.println("\nCP/M vector " + str.toHexWord(addr));
|
||||
cpu.setPC(addr); // this is purely for the Debugger's benefit, to show the HLT
|
||||
dbg.stopCPU();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!this.fAllocated) {
|
||||
Component.error("No RAM allocated");
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* getCPMChar(ch)
|
||||
*
|
||||
* @this {RAM8080}
|
||||
* @param {number} ch
|
||||
* @return {string}
|
||||
*/
|
||||
RAM8080.prototype.getCPMChar = function(ch)
|
||||
{
|
||||
return String.fromCharCode(ch);
|
||||
};
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* This implements save support for the RAM8080 component.
|
||||
* getCPMString(addr, chEnd)
|
||||
*
|
||||
* @this {RAM8080}
|
||||
* @return {Object}
|
||||
* @param {number} addr (of a string)
|
||||
* @param {string|number} [chEnd] (terminating character, default is 0)
|
||||
* @return {string}
|
||||
*/
|
||||
RAM8080.prototype.save = function()
|
||||
RAM8080.prototype.getCPMString = function(addr, chEnd)
|
||||
{
|
||||
return null;
|
||||
var s = "";
|
||||
var cchMax = 255;
|
||||
var bEnd = chEnd && chEnd.length && chEnd.charCodeAt(0) || chEnd || 0;
|
||||
while (cchMax--) {
|
||||
var b = this.cpu.getByte(addr++);
|
||||
if (b == bEnd) break;
|
||||
s += String.fromCharCode(b);
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
/**
|
||||
* restore(data)
|
||||
*
|
||||
* This implements restore support for the RAM8080 component.
|
||||
* writeCPMString(s)
|
||||
*
|
||||
* @this {RAM8080}
|
||||
* @param {Object} data
|
||||
* @return {boolean} true if successful, false if failure
|
||||
* @param {string} s
|
||||
*/
|
||||
RAM8080.prototype.restore = function(data)
|
||||
RAM8080.prototype.writeCPMString = function(s)
|
||||
{
|
||||
return true;
|
||||
s = s.replace(/\r/g, '');
|
||||
if (this.controlPrint) {
|
||||
this.controlPrint.value += s;
|
||||
this.controlPrint.scrollTop = this.controlPrint.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ if (NODE) {
|
|||
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var PC8080 = require("./defines");
|
||||
var CPUDef8080 = require("./cpudef");
|
||||
var Memory8080 = require("./memory");
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +46,6 @@ if (NODE) {
|
|||
* size: amount of ROM, in bytes
|
||||
* alias: physical alias address (null if none)
|
||||
* file: name of ROM data file
|
||||
* writable: true to make ROM writable (default is false)
|
||||
*
|
||||
* 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 doneLoad()).
|
||||
|
|
@ -55,13 +53,6 @@ if (NODE) {
|
|||
* Also, while the size parameter may seem redundant, I consider it useful to confirm that the ROM you received
|
||||
* is the ROM you expected.
|
||||
*
|
||||
* Finally, while making ROM "writable" may seem a contradiction in terms, I want to be able to load selected
|
||||
* binary files into memory purely for testing purposes, and the RAM component has no "file" option, so the
|
||||
* simplest solution was to add the option to load binary files into memory as "writable" ROMs.
|
||||
*
|
||||
* Moreover, if a "writable" ROM is installed at addr 0x100, that triggers our "Fake CP/M" support, providing
|
||||
* a quick-and-dirty means of loading simple CP/M test binaries. See addROM() for details.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @param {Object} parmsROM
|
||||
|
|
@ -73,7 +64,6 @@ function ROM8080(parmsROM)
|
|||
this.abROM = null;
|
||||
this.addrROM = parmsROM['addr'];
|
||||
this.sizeROM = parmsROM['size'];
|
||||
this.fWritable = parmsROM['writable'];
|
||||
|
||||
/*
|
||||
* The new 'alias' property can now be EITHER a single physical address (like 'addr') OR an array of
|
||||
|
|
@ -112,30 +102,6 @@ function ROM8080(parmsROM)
|
|||
|
||||
Component.subclass(ROM8080);
|
||||
|
||||
ROM8080.CPM = {
|
||||
BIOS: {
|
||||
VECTOR: 0x0000
|
||||
},
|
||||
BDOS: {
|
||||
VECTOR: 0x0005,
|
||||
FUNC: { // function number (specified in regC)
|
||||
RESET: 0x00,
|
||||
CON_READ: 0x01, // output: A = L = ASCII character
|
||||
CON_WRITE: 0x02, // input: E = ASCII character
|
||||
AUX_READ: 0x03, // output: A = L = ASCII character
|
||||
AUX_WRITE: 0x04, // input: E = ASCII character
|
||||
PRN_WRITE: 0x05, // input: E = ASCII character
|
||||
MEM_SIZE: 0x06, // output: base address of CCP (Console Command Processor), but which register? (perhaps moot if this was CP/M 1.3 only...)
|
||||
CON_IO: 0x06, // input: E = ASCII character (or 0xFF to return ASCII character in A)
|
||||
GET_IOBYTE: 0x07,
|
||||
SET_IOBYTE: 0x08,
|
||||
STR_WRITE: 0x09 // input: DE = address of string
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ROM8080.CPM.VECTORS = [ROM8080.CPM.BIOS.VECTOR, ROM8080.CPM.BDOS.VECTOR];
|
||||
|
||||
/*
|
||||
* 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.
|
||||
|
|
@ -350,31 +316,12 @@ ROM8080.prototype.copyROM = function()
|
|||
*/
|
||||
ROM8080.prototype.addROM = function(addr)
|
||||
{
|
||||
if (this.bus.addMemory(addr, this.sizeROM, this.fWritable? Memory8080.TYPE.RAM : Memory8080.TYPE.ROM)) {
|
||||
if (this.bus.addMemory(addr, this.sizeROM, Memory8080.TYPE.ROM)) {
|
||||
if (DEBUG) this.log("addROM(): copying ROM to " + str.toHexLong(addr) + " (" + str.toHexLong(this.abROM.length) + " bytes)");
|
||||
var i;
|
||||
for (i = 0; i < this.abROM.length; i++) {
|
||||
this.bus.setByteDirect(addr + i, this.abROM[i]);
|
||||
}
|
||||
if (this.fWritable && addr == 0x100) {
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
for (i = 0; i < ROM8080.CPM.VECTORS.length; i++) {
|
||||
this.bus.setByteDirect(ROM8080.CPM.VECTORS[i], CPUDef8080.OPCODE.HLT);
|
||||
}
|
||||
|
||||
this.cpu.addHaltCheck(function(rom) {
|
||||
return function(addr) {
|
||||
return rom.checkCPMVector(addr)
|
||||
};
|
||||
}(this));
|
||||
|
||||
this.cpu.setReset(addr);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
|
|
@ -383,96 +330,6 @@ ROM8080.prototype.addROM = function(addr)
|
|||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* checkCPMVector(addr)
|
||||
*
|
||||
* @this {ROM8080}
|
||||
* @param {number} addr (of the HLT opcode)
|
||||
* @return {boolean} true if special processing performed, false if not
|
||||
*/
|
||||
ROM8080.prototype.checkCPMVector = function(addr)
|
||||
{
|
||||
var i = ROM8080.CPM.VECTORS.indexOf(addr);
|
||||
if (i >= 0) {
|
||||
var fCPM = false;
|
||||
var cpu = this.cpu;
|
||||
var dbg = this.dbg;
|
||||
if (addr == ROM8080.CPM.BDOS.VECTOR) {
|
||||
fCPM = true;
|
||||
switch(cpu.regC) {
|
||||
case ROM8080.CPM.BDOS.FUNC.CON_WRITE:
|
||||
this.writeCPMString(this.getCPMChar(cpu.regE));
|
||||
break;
|
||||
case ROM8080.CPM.BDOS.FUNC.STR_WRITE:
|
||||
this.writeCPMString(this.getCPMString(cpu.getDE(), '$'));
|
||||
break;
|
||||
default:
|
||||
fCPM = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fCPM) {
|
||||
CPUDef8080.opRET.call(cpu); // for recognized calls, automatically return
|
||||
}
|
||||
else if (dbg) {
|
||||
this.println("\nCP/M vector " + str.toHexWord(addr));
|
||||
cpu.setPC(addr); // this is purely for the Debugger's benefit, to show the HLT
|
||||
dbg.stopCPU();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* getCPMChar(ch)
|
||||
*
|
||||
* @this {ROM8080}
|
||||
* @param {number} ch
|
||||
* @return {string}
|
||||
*/
|
||||
ROM8080.prototype.getCPMChar = function(ch)
|
||||
{
|
||||
return String.fromCharCode(ch);
|
||||
};
|
||||
|
||||
/**
|
||||
* getCPMString(addr, chEnd)
|
||||
*
|
||||
* @this {ROM8080}
|
||||
* @param {number} addr (of a string)
|
||||
* @param {string|number} [chEnd] (terminating character, default is 0)
|
||||
* @return {string}
|
||||
*/
|
||||
ROM8080.prototype.getCPMString = function(addr, chEnd)
|
||||
{
|
||||
var s = "";
|
||||
var cchMax = 255;
|
||||
var bEnd = chEnd && chEnd.length && chEnd.charCodeAt(0) || chEnd || 0;
|
||||
while (cchMax--) {
|
||||
var b = this.cpu.getByte(addr++);
|
||||
if (b == bEnd) break;
|
||||
s += String.fromCharCode(b);
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
/**
|
||||
* writeCPMString(s)
|
||||
*
|
||||
* @this {ROM8080}
|
||||
* @param {string} s
|
||||
*/
|
||||
ROM8080.prototype.writeCPMString = function(s)
|
||||
{
|
||||
s = s.replace(/\r/g, '');
|
||||
if (this.controlPrint) {
|
||||
this.controlPrint.value += s;
|
||||
this.controlPrint.scrollTop = this.controlPrint.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* cloneROM(addr)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1094,7 +1094,6 @@ Disk.prototype.load = function(sDiskName, sDiskPath, file, fnNotify, controller)
|
|||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* build(buffer, fModified)
|
||||
*
|
||||
* Builds a disk image from an ArrayBuffer (eg, from a FileReader object), rather than from JSON-encoded data.
|
||||
|
|
@ -1217,24 +1216,6 @@ Disk.prototype.doneLoad = function(sURL, sDiskData, nErrorCode)
|
|||
}
|
||||
/*
|
||||
* The most likely source of any exception will be here, where we're parsing the disk data.
|
||||
*
|
||||
* TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to
|
||||
* eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in
|
||||
* IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks
|
||||
* (perhaps one track per chunk, using regular expressions) and then manually re-assemble it.
|
||||
*
|
||||
* However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")")
|
||||
* is a much easier fix. The only drawback is that we must first quote any unquoted property names
|
||||
* and remove any comments, because while eval() was cool with them, JSON.parse() is more particular;
|
||||
* the following RegExp replacements take care of those requirements.
|
||||
*
|
||||
* The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and
|
||||
* while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified),
|
||||
* I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement
|
||||
* for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect
|
||||
* any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so:
|
||||
*
|
||||
* ["unrecognized disk path: test.img"]
|
||||
*/
|
||||
var aDiskData;
|
||||
if (sDiskData.substr(0, 1) == "<") { // if the "data" begins with a "<"...
|
||||
|
|
@ -1249,6 +1230,25 @@ Disk.prototype.doneLoad = function(sURL, sDiskData, nErrorCode)
|
|||
*/
|
||||
aDiskData = ["Missing disk image: " + this.sDiskName];
|
||||
} else {
|
||||
/*
|
||||
* TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to
|
||||
* eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in
|
||||
* IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks
|
||||
* (perhaps one track per chunk, using regular expressions) and then manually re-assemble it.
|
||||
*
|
||||
* However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")")
|
||||
* is a much easier fix. The only drawback is that we must first quote any unquoted property names
|
||||
* and remove any comments, because while eval() was cool with them, JSON.parse() is more particular;
|
||||
* the following RegExp replacements take care of those requirements.
|
||||
*
|
||||
* The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and
|
||||
* while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified),
|
||||
* I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement
|
||||
* for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect
|
||||
* any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so:
|
||||
*
|
||||
* ["unrecognized disk path: test.img"]
|
||||
*/
|
||||
if (sDiskData.indexOf("0x") < 0 && sDiskData.substr(0, 2) != "[\"") {
|
||||
aDiskData = JSON.parse(sDiskData.replace(/([a-z]+):/gm, "\"$1\":").replace(/\/\/[^\n]*/gm, ""));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ if (NODE) {
|
|||
* component-specific property:
|
||||
*
|
||||
* autoMount: one or more JSON-encoded objects, each containing 'name' and 'path' properties
|
||||
*
|
||||
* sortBy: "name" to sort disks by name, "path" to sort by path, or "none" to leave as-is (default is "name")
|
||||
*
|
||||
* Regarding early diskette drives: the IBM PC Model 5150 originally shipped with single-sided drives,
|
||||
|
|
@ -659,10 +660,7 @@ FDC.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
bus.addPortOutputTable(this, FDC.aPortOutput);
|
||||
|
||||
this.addDiskette("None", "", true);
|
||||
|
||||
if (this.fLocalDisks) {
|
||||
this.addDiskette("Local Disk", "?");
|
||||
}
|
||||
if (this.fLocalDisks) this.addDiskette("Local Disk", "?");
|
||||
this.addDiskette("Remote Disk", "??");
|
||||
|
||||
if (!this.autoMount()) this.setReady();
|
||||
|
|
@ -1281,7 +1279,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) {
|
||||
/*
|
||||
|
|
@ -1535,6 +1533,7 @@ FDC.prototype.doneLoadDiskette = function onFDCLoadNotify(drive, disk, sDiskette
|
|||
/**
|
||||
* addDiskette(sName, sPath, fTop)
|
||||
*
|
||||
* @this {FDC}
|
||||
* @param {string} sName
|
||||
* @param {string} sPath
|
||||
* @param {boolean} [fTop] (default is bottom)
|
||||
|
|
@ -1563,6 +1562,7 @@ FDC.prototype.addDiskette = function(sName, sPath, fTop)
|
|||
* This is used to deal with mount requests (eg, autoMount) that supply a path without a name;
|
||||
* if we can find the path in the "listDisks" control, then we return the associated disk name.
|
||||
*
|
||||
* @this {FDC}
|
||||
* @param {string} sPath
|
||||
* @return {string|null}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -667,7 +667,7 @@ Memory.prototype = {
|
|||
*/
|
||||
readNone: function readNone(off, addr) {
|
||||
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.CPU | Messages.MEM) /* && !off */) {
|
||||
this.dbg.message("attempt to read invalid block %" + str.toHex(this.addr), true);
|
||||
this.dbg.message("attempt to read invalid block %" + str.toHex(addr), true);
|
||||
}
|
||||
return 0xff;
|
||||
},
|
||||
|
|
@ -681,7 +681,7 @@ Memory.prototype = {
|
|||
*/
|
||||
writeNone: function writeNone(off, v, addr) {
|
||||
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.CPU | Messages.MEM) /* && !off */) {
|
||||
this.dbg.message("attempt to write " + str.toHexWord(v) + " to invalid block %" + str.toHex(this.addr), true);
|
||||
this.dbg.message("attempt to write " + str.toHexWord(v) + " to invalid block %" + str.toHex(addr), true);
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ PDP-11 Machine Emulation Module (PDPjs)
|
|||
|
||||
Overview
|
||||
---
|
||||
PDPjs is our PDP-11 machine emulation module. The code is being adapted from
|
||||
PDPjs, our PDP-11 machine emulation module, is adapted from
|
||||
the JavaScript [PDP 11/70 Emulator (v1.4)](http://skn.noip.me/pdp11/pdp11.html) written by
|
||||
[Paul Nankervis](mailto:paulnank@hotmail.com).
|
||||
|
||||
See the list of available [PDP-11 Machines](/devices/pdp11/machine/), which includes a
|
||||
[PDP-11/70 with Front Panel](/devices/pdp11/machine/1170/panel/) and [PDP-11/70 with VT100 Terminal](/devices/pdp11/machine/1170/vt100/).
|
||||
[PDP-11/70 with Front Panel](/devices/pdp11/machine/1170/panel/debugger/) and [PDP-11/70 with VT100 Terminal](/devices/pdp11/machine/1170/vt100/debugger/).
|
||||
|
||||
PDPjs is currently comprised of the following non-shared components, as listed in
|
||||
[package.json](../../package.json) (see the *pdp11Files* property):
|
||||
|
|
@ -32,5 +32,6 @@ PDPjs is currently comprised of the following non-shared components, as listed i
|
|||
* [ram.js](/modules/pdp11/lib/ram.js)
|
||||
* [keyboard.js](/modules/pdp11/lib/keyboard.js)
|
||||
* [serialport.js](/modules/pdp11/lib/serialport.js)
|
||||
* [pc11.js](/modules/pdp11/lib/pc11.js)
|
||||
* [debugger.js](/modules/pdp11/lib/debugger.js)
|
||||
* [computer.js](/modules/pdp11/lib/computer.js)
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ function BusPDP11(parmsBus, cpu, dbg)
|
|||
*/
|
||||
this.aIOHandlers = [];
|
||||
this.fIOBreakAll = false;
|
||||
this.nDisableTraps = 0;
|
||||
this.nDisableFaults = 0;
|
||||
|
||||
/*
|
||||
* Array of RESET notification handlers registered by Device components.
|
||||
|
|
@ -556,9 +556,9 @@ BusPDP11.prototype.unknownAccess = function(addr, fByte, data)
|
|||
* TODO: For 22-bit machines, let's display addr as a 3-byte value (for a total of 9 octal digits)
|
||||
*/
|
||||
this.dbg.printMessage("warning: unknown I/O access (" + this.dbg.toStrBase(addr) + "," + this.dbg.toStrBase(data, fByte?1:2) + ")", true, true);
|
||||
if (this.dbg.stopInstruction()) return 0;
|
||||
this.dbg.stopInstruction();
|
||||
}
|
||||
if (!this.nDisableTraps) {
|
||||
if (!this.nDisableFaults) {
|
||||
this.cpu.trap(PDP11.TRAP.BUS_ERROR, addr);
|
||||
}
|
||||
return 0;
|
||||
|
|
@ -700,6 +700,25 @@ BusPDP11.prototype.cleanMemory = function(addr, size)
|
|||
return fClean;
|
||||
};
|
||||
|
||||
/**
|
||||
* zeroMemory(addr, size)
|
||||
*
|
||||
* @this {BusPDP11}
|
||||
* @param {number} addr
|
||||
* @param {number} size
|
||||
*/
|
||||
BusPDP11.prototype.zeroMemory = function(addr, size)
|
||||
{
|
||||
var off = addr & this.nBlockLimit;
|
||||
var iBlock = addr >>> this.nBlockShift;
|
||||
while (size > 0 && iBlock < this.aMemBlocks.length) {
|
||||
this.aMemBlocks[iBlock].zero(off, size);
|
||||
size -= this.nBlockSize;
|
||||
iBlock++;
|
||||
off = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Data types used by scanMemory()
|
||||
*/
|
||||
|
|
@ -906,9 +925,9 @@ BusPDP11.prototype.getByte = function(addr)
|
|||
*/
|
||||
BusPDP11.prototype.getByteDirect = function(addr)
|
||||
{
|
||||
this.nDisableTraps++;
|
||||
this.nDisableFaults++;
|
||||
var b = this.aMemBlocks[(addr & this.nBusMask) >>> this.nBlockShift].readByteDirect(addr & this.nBlockLimit, addr);
|
||||
this.nDisableTraps--;
|
||||
this.nDisableFaults--;
|
||||
return b;
|
||||
};
|
||||
|
||||
|
|
@ -943,13 +962,13 @@ BusPDP11.prototype.getWordDirect = function(addr)
|
|||
var w;
|
||||
var off = addr & this.nBlockLimit;
|
||||
var iBlock = (addr & this.nBusMask) >>> this.nBlockShift;
|
||||
this.nDisableTraps++;
|
||||
this.nDisableFaults++;
|
||||
if (!PDP11.WORDBUS && off == this.nBlockLimit) {
|
||||
w = this.aMemBlocks[iBlock++].readByteDirect(off, addr) | (this.aMemBlocks[iBlock & this.nBlockMask].readByteDirect(0, addr + 1) << 8);
|
||||
} else {
|
||||
w = this.aMemBlocks[iBlock].readWordDirect(off, addr);
|
||||
}
|
||||
this.nDisableTraps--;
|
||||
this.nDisableFaults--;
|
||||
return w;
|
||||
};
|
||||
|
||||
|
|
@ -977,9 +996,9 @@ BusPDP11.prototype.setByte = function(addr, b)
|
|||
*/
|
||||
BusPDP11.prototype.setByteDirect = function(addr, b)
|
||||
{
|
||||
this.nDisableTraps++;
|
||||
this.nDisableFaults++;
|
||||
this.aMemBlocks[(addr & this.nBusMask) >>> this.nBlockShift].writeByteDirect(addr & this.nBlockLimit, b & 0xff, addr);
|
||||
this.nDisableTraps--;
|
||||
this.nDisableFaults--;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1015,14 +1034,14 @@ BusPDP11.prototype.setWordDirect = function(addr, w)
|
|||
{
|
||||
var off = addr & this.nBlockLimit;
|
||||
var iBlock = (addr & this.nBusMask) >>> this.nBlockShift;
|
||||
this.nDisableTraps++;
|
||||
this.nDisableFaults++;
|
||||
if (!PDP11.WORDBUS && off == this.nBlockLimit) {
|
||||
this.aMemBlocks[iBlock++].writeByteDirect(off, w & 0xff, addr);
|
||||
this.aMemBlocks[iBlock & this.nBlockMask].writeByteDirect(0, (w >> 8) & 0xff, addr + 1);
|
||||
} else {
|
||||
this.aMemBlocks[iBlock].writeWordDirect(off, w & 0xffff, addr);
|
||||
}
|
||||
this.nDisableTraps--;
|
||||
this.nDisableFaults--;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1236,18 +1255,20 @@ BusPDP11.prototype.addResetHandler = function(fnReset)
|
|||
};
|
||||
|
||||
/**
|
||||
* fault(addr)
|
||||
* fault(addr, access)
|
||||
*
|
||||
* Memory interface for signaling alignment errors.
|
||||
* Memory interface for signaling alignment errors, invalid memory
|
||||
*
|
||||
* @this {BusPDP11}
|
||||
* @param {number} addr
|
||||
* @param {number} [access] (for diagnostic purposes only)
|
||||
*/
|
||||
BusPDP11.prototype.fault = function(addr)
|
||||
BusPDP11.prototype.fault = function(addr, access)
|
||||
{
|
||||
if (!this.nDisableTraps) {
|
||||
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(MessagesPDP11.WARN)) {
|
||||
this.dbg.printMessage("memory fault on address " + this.dbg.toStrBase(addr), true, true);
|
||||
if (!this.nDisableFaults) {
|
||||
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(MessagesPDP11.BUS)) {
|
||||
this.dbg.printMessage("memory fault (" + access + ") on address " + this.dbg.toStrBase(addr), true, true);
|
||||
this.dbg.stopInstruction();
|
||||
}
|
||||
this.cpu.trap(PDP11.TRAP.BUS_ERROR, addr);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1398,7 +1398,7 @@ ComputerPDP11.prototype.getMachineComponent = function(sType, componentPrev)
|
|||
};
|
||||
|
||||
/**
|
||||
* updateFocus(fScroll)
|
||||
* setFocus(fScroll)
|
||||
*
|
||||
* NOTE: When soft keyboard buttons call us to return focus to the machine (and away from the button),
|
||||
* the browser's default behavior is to scroll the element into view, which can be annoying, especially on iOS,
|
||||
|
|
@ -1407,8 +1407,9 @@ ComputerPDP11.prototype.getMachineComponent = function(sType, componentPrev)
|
|||
* @this {ComputerPDP11}
|
||||
* @param {boolean} [fScroll]
|
||||
*/
|
||||
ComputerPDP11.prototype.updateFocus = function(fScroll)
|
||||
ComputerPDP11.prototype.setFocus = function(fScroll)
|
||||
{
|
||||
if (this.controlPrint) this.controlPrint.focus();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -260,10 +260,10 @@ CPUPDP11.prototype.powerUp = function(data, fRepower)
|
|||
this.resetChecksum();
|
||||
}
|
||||
/*
|
||||
* Give the Debugger a chance to do/print something once we've powered up
|
||||
* Give the Debugger a chance to do/print something once we've powered up.
|
||||
*/
|
||||
if (DEBUGGER && this.dbg) {
|
||||
this.dbg.init();
|
||||
this.dbg.init(this.flags.autoStart);
|
||||
} else {
|
||||
/*
|
||||
* The Computer (this.cmp) knows if there's a Control Panel (this.cmp.panel), and the Control Panel
|
||||
|
|
@ -530,7 +530,7 @@ CPUPDP11.prototype.addCycles = function(nCycles, fEndStep)
|
|||
{
|
||||
this.nTotalCycles += nCycles;
|
||||
if (fEndStep) {
|
||||
this.nBurstCycles = this.nStepCycles = 0;
|
||||
this.nBurstCycles = this.nStepCycles = this.nSnapCycles = 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -643,7 +643,7 @@ CPUPDP11.prototype.resetCycles = function()
|
|||
{
|
||||
this.mhz = 0;
|
||||
this.nYieldsSinceStatusUpdate = 0;
|
||||
this.nTotalCycles = this.nRunCycles = this.nBurstCycles = this.nStepCycles = 0;
|
||||
this.nTotalCycles = this.nRunCycles = this.nBurstCycles = this.nStepCycles = this.nSnapCycles = 0;
|
||||
this.resetChecksum();
|
||||
this.setSpeed(1);
|
||||
};
|
||||
|
|
@ -722,7 +722,7 @@ CPUPDP11.prototype.setSpeed = function(nMultiplier, fUpdateFocus)
|
|||
if (controlSpeed) controlSpeed.textContent = sSpeed;
|
||||
this.println("target speed: " + sSpeed);
|
||||
}
|
||||
if (fUpdateFocus && this.cmp) this.cmp.updateFocus();
|
||||
if (fUpdateFocus && this.cmp) this.cmp.setFocus();
|
||||
}
|
||||
this.addCycles(this.nRunCycles);
|
||||
this.nRunCycles = 0;
|
||||
|
|
@ -1019,7 +1019,13 @@ CPUPDP11.prototype.updateTimers = function(nCycles)
|
|||
CPUPDP11.prototype.endBurst = function(fReset)
|
||||
{
|
||||
var nCycles = this.nBurstCycles -= this.nStepCycles;
|
||||
this.nStepCycles = 0;
|
||||
/*
|
||||
* In addition to zeroing nStepCycles, it's important that we also zero nSnapCycles, because if a CPU
|
||||
* burst is being ended after nStepCycles has been "snapped" (because a certain opcode has an unusual timing
|
||||
* calculation that must be based on a "snapped" cycle count rather the opcode's starting cycle count), we
|
||||
* could inadvertently undo the endBurst() if the original "snapped" value was used to update nStepCycles.
|
||||
*/
|
||||
this.nStepCycles = this.nSnapCycles = 0;
|
||||
if (fReset) this.nBurstCycles = 0;
|
||||
return nCycles;
|
||||
};
|
||||
|
|
@ -1130,7 +1136,7 @@ CPUPDP11.prototype.startCPU = function(fUpdateFocus)
|
|||
var controlRun = this.bindings["run"];
|
||||
if (controlRun) controlRun.textContent = "Halt";
|
||||
if (this.cmp) {
|
||||
if (fUpdateFocus) this.cmp.updateFocus(true);
|
||||
if (fUpdateFocus) this.cmp.setFocus(true);
|
||||
this.cmp.start(this.msStartRun, this.getCycles());
|
||||
}
|
||||
setTimeout(this.onRunTimeout, 0);
|
||||
|
|
@ -1190,7 +1196,6 @@ CPUPDP11.prototype.yieldCPU = function()
|
|||
{
|
||||
this.endBurst(); // this will break us out of stepCPU()
|
||||
this.nCyclesNextYield = 0; // this will break us out of runCPU(), once we break out of stepCPU()
|
||||
// if (DEBUG) this.nSnapCycles = this.nBurstCycles;
|
||||
/*
|
||||
* The Debugger calls yieldCPU() after every message() to ensure browser responsiveness, but it looks
|
||||
* odd for those messages to show CPU state changes if the Control Panel, Video display, etc, does not,
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ if (NODE) {
|
|||
*
|
||||
* For example, opADD() passes the helper function fnADD() to the appropriate update method. This
|
||||
* allows the update method to perform the entire read/modify/write operation, because the modify
|
||||
* step is performed internally via the fnXXX() helper function.
|
||||
* step is performed internally, via the fnXXX() helper function.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -827,7 +827,7 @@ PDP11.opBPL = function(opCode)
|
|||
*/
|
||||
PDP11.opBPT = function(opCode)
|
||||
{
|
||||
this.trap(PDP11.TRAP.BREAKPOINT, PDP11.REASON.BPT);
|
||||
this.trap(PDP11.TRAP.BPT, PDP11.REASON.BPT);
|
||||
this.nStepCycles -= (4 + 1);
|
||||
};
|
||||
|
||||
|
|
@ -1101,7 +1101,7 @@ PDP11.opDIV = function(opCode)
|
|||
*/
|
||||
PDP11.opEMT = function(opCode)
|
||||
{
|
||||
this.trap(PDP11.TRAP.EMULATOR, PDP11.REASON.EMT);
|
||||
this.trap(PDP11.TRAP.EMT, PDP11.REASON.EMT);
|
||||
this.nStepCycles -= (22 + 3);
|
||||
};
|
||||
|
||||
|
|
@ -1117,7 +1117,24 @@ PDP11.opHALT = function(opCode)
|
|||
this.regErr |= PDP11.CPUERR.BADHALT;
|
||||
this.trap(PDP11.TRAP.BUS_ERROR, PDP11.REASON.HALT);
|
||||
} else {
|
||||
this.stopCPU();
|
||||
if (!this.dbg) {
|
||||
/*
|
||||
* This will leave the PC exactly where it's supposed to be: at the address of the HALT + 2.
|
||||
*/
|
||||
this.stopCPU();
|
||||
} else {
|
||||
/*
|
||||
* When the Debugger is present, this call will rewind PC by 2 so that the HALT instruction is
|
||||
* displayed, making it clear why the processor stopped; the user could also use the "dh" command
|
||||
* to dump the Debugger's instruction history buffer to see why it stopped, assuming the history
|
||||
* buffer is enabled, but that's more work.
|
||||
*
|
||||
* Because rewinding is not normal CPU behavior, attempting to Run again (or use the Debugger's
|
||||
* "g" command) will cause an immediate HALT again; the work-around is simple: either set the PC to
|
||||
* a new address (eg, "r pc=pc+2") or single-step the HALT instruction ("t").
|
||||
*/
|
||||
this.dbg.stopInstruction();
|
||||
}
|
||||
}
|
||||
this.nStepCycles -= 7;
|
||||
};
|
||||
|
|
@ -1174,9 +1191,9 @@ PDP11.opJMP = function(opCode)
|
|||
* Since JMP and JSR opcodes have their own unique timings for the various dst modes, we must snapshot
|
||||
* nStepCycles before decoding the mode, and then use that to update nStepCycles.
|
||||
*/
|
||||
var nSnapCycles = this.nStepCycles;
|
||||
this.nSnapCycles = this.nStepCycles;
|
||||
this.setPC(this.readDstAddr(opCode));
|
||||
this.nStepCycles = nSnapCycles - PDP11.JMP_CYCLES[this.dstMode];
|
||||
this.nStepCycles = this.nSnapCycles - PDP11.JMP_CYCLES[this.dstMode];
|
||||
};
|
||||
|
||||
PDP11.JSR_CYCLES = [
|
||||
|
|
@ -1195,7 +1212,7 @@ PDP11.opJSR = function(opCode)
|
|||
* Since JMP and JSR opcodes have their own unique timings for the various dst modes, we must snapshot
|
||||
* nStepCycles before decoding the mode, and then use that to update nStepCycles.
|
||||
*/
|
||||
var nSnapCycles = this.nStepCycles;
|
||||
this.nSnapCycles = this.nStepCycles;
|
||||
/*
|
||||
* TODO: Determine whether or not the SRCMODE operand (regsGen[reg]) should be snapped BEFORE or AFTER we
|
||||
* decode the DSTMODE operand. Doing it AFTER seems a bit risky.
|
||||
|
|
@ -1205,7 +1222,7 @@ PDP11.opJSR = function(opCode)
|
|||
this.pushWord(this.regsGen[reg]);
|
||||
this.regsGen[reg] = this.getPC();
|
||||
this.setPC(addr);
|
||||
this.nStepCycles = nSnapCycles - PDP11.JSR_CYCLES[this.dstMode];
|
||||
this.nStepCycles = this.nSnapCycles - PDP11.JSR_CYCLES[this.dstMode];
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1270,9 +1287,9 @@ PDP11.opMOV = function(opCode)
|
|||
* nStepCycles after decoding the src mode, and then use that to update nStepCycles.
|
||||
*/
|
||||
var data = this.readSrcWord(opCode);
|
||||
var nSnapCycles = this.nStepCycles;
|
||||
this.nSnapCycles = this.nStepCycles;
|
||||
this.updateNZVFlags(this.writeDstWord(opCode, data));
|
||||
this.nStepCycles = nSnapCycles - PDP11.MOV_CYCLES[(this.srcMode? 8 : 0) + this.dstMode] + (this.dstReg == 7 && !this.dstMode? 2 : 0);
|
||||
this.nStepCycles = this.nSnapCycles - PDP11.MOV_CYCLES[(this.srcMode? 8 : 0) + this.dstMode] + (this.dstReg == 7 && !this.dstMode? 2 : 0);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1305,10 +1322,10 @@ PDP11.opMTPD = function(opCode)
|
|||
* nStepCycles before decoding the mode, and then use that to update nStepCycles.
|
||||
*/
|
||||
var data = this.popWord();
|
||||
var nSnapCycles = this.nStepCycles;
|
||||
this.nSnapCycles = this.nStepCycles;
|
||||
this.writeWordToPrevSpace(opCode, PDP11.ACCESS.DSPACE, data);
|
||||
this.updateNZVFlags(data);
|
||||
this.nStepCycles = nSnapCycles - PDP11.MTP_CYCLES[this.dstMode];
|
||||
this.nStepCycles = this.nSnapCycles - PDP11.MTP_CYCLES[this.dstMode];
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1324,10 +1341,10 @@ PDP11.opMTPI = function(opCode)
|
|||
* nStepCycles before decoding the mode, and then use that to update nStepCycles.
|
||||
*/
|
||||
var data = this.popWord();
|
||||
var nSnapCycles = this.nStepCycles;
|
||||
this.nSnapCycles = this.nStepCycles;
|
||||
this.writeWordToPrevSpace(opCode, PDP11.ACCESS.ISPACE, data);
|
||||
this.updateNZVFlags(data);
|
||||
this.nStepCycles = nSnapCycles - PDP11.MTP_CYCLES[this.dstMode];
|
||||
this.nStepCycles = this.nSnapCycles - PDP11.MTP_CYCLES[this.dstMode];
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1479,8 +1496,15 @@ PDP11.opRTS = function(opCode)
|
|||
}
|
||||
var src = this.popWord();
|
||||
var reg = opCode & PDP11.OPREG.MASK;
|
||||
this.setPC(this.regsGen[reg]);
|
||||
this.regsGen[reg] = src;
|
||||
/*
|
||||
* When the popular "RTS PC" form is used, we might as well eliminate the useless setting of PC to
|
||||
*/
|
||||
if (reg == PDP11.REG.PC) {
|
||||
this.setPC(src);
|
||||
} else {
|
||||
this.setPC(this.regsGen[reg]);
|
||||
this.regsGen[reg] = src;
|
||||
}
|
||||
this.nStepCycles -= (7 + 2);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ if (NODE) {
|
|||
* The CPUStatePDP11 class uses the following (parmsCPU) properties:
|
||||
*
|
||||
* model: a number (eg, 1170) that should match one of the PDP11.MODEL_* values
|
||||
* resetAddr: reset address (default is 0)
|
||||
* 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)
|
||||
|
|
@ -63,7 +63,7 @@ if (NODE) {
|
|||
function CPUStatePDP11(parmsCPU)
|
||||
{
|
||||
this.model = +parmsCPU['model'] || PDP11.MODEL_1170;
|
||||
this.resetAddr = parmsCPU['resetAddr'] || 0;
|
||||
this.addrReset = parmsCPU['addrReset'] || 0;
|
||||
|
||||
var nCyclesDefault = 0;
|
||||
switch(this.model) {
|
||||
|
|
@ -175,7 +175,7 @@ CPUStatePDP11.prototype.initRegs = function()
|
|||
this.flagN = 0x8000; // PSW N bit
|
||||
this.regPSW = 0x000f; // PSW other bits (TODO: What's the point of setting the flag bits here, too?)
|
||||
this.regsGen = [ // General R0 - R7
|
||||
0, 0, 0, 0, 0, 0, 0, this.resetAddr
|
||||
0, 0, 0, 0, 0, 0, 0, this.addrReset
|
||||
];
|
||||
this.regsAlt = [ // Alternate R0 - R5
|
||||
0, 0, 0, 0, 0, 0
|
||||
|
|
@ -359,6 +359,26 @@ CPUStatePDP11.prototype.setMMR3 = function(newMMR3)
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* setReset(addr, fReset)
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @param {number} addr
|
||||
* @param {boolean} [fReset] (true if called in the context of a complete reset, obviating the need to notify the Debugger)
|
||||
*/
|
||||
CPUStatePDP11.prototype.setReset = function(addr, fReset)
|
||||
{
|
||||
this.addrReset = addr;
|
||||
this.setPC(addr);
|
||||
if (!fReset && this.dbg) {
|
||||
/*
|
||||
* TODO: Review the decision to always stop the CPU if the Debugger is loaded.
|
||||
*/
|
||||
this.stopCPU();
|
||||
this.dbg.updateStatus();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* getChecksum()
|
||||
*
|
||||
|
|
@ -528,9 +548,47 @@ CPUStatePDP11.prototype.setNF = function()
|
|||
this.flagN = 0x8000;
|
||||
};
|
||||
|
||||
/**
|
||||
* getOpcode()
|
||||
*
|
||||
* TODO: Determine whether we can speed this up by *always* snapping PC into a shadow MMR2 register
|
||||
* and eliminating the ABORT test.
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @return {number}
|
||||
*/
|
||||
CPUStatePDP11.prototype.getOpcode = function()
|
||||
{
|
||||
var pc = this.regsGen[PDP11.REG.PC];
|
||||
if (!(this.regMMR0 & PDP11.MMR0.ABORT)) {
|
||||
this.regMMR1 = 0;
|
||||
this.regMMR2 = pc;
|
||||
}
|
||||
this.regsGen[PDP11.REG.PC] = (pc + 2) & 0xffff;
|
||||
return this.readWord(pc);
|
||||
};
|
||||
|
||||
/**
|
||||
* advancePC(off)
|
||||
*
|
||||
* NOTE: This function is nothing more than a convenience, and we fully expect it to be inlined at runtime.
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @param {number} off
|
||||
* @return {number} (original PC)
|
||||
*/
|
||||
CPUStatePDP11.prototype.advancePC = function(off)
|
||||
{
|
||||
var pc = this.regsGen[PDP11.REG.PC];
|
||||
this.regsGen[PDP11.REG.PC] = (pc + off) & 0xffff;
|
||||
return pc;
|
||||
};
|
||||
|
||||
/**
|
||||
* getPC()
|
||||
*
|
||||
* NOTE: This function is nothing more than a convenience, and we fully expect it to be inlined at runtime.
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @return {number}
|
||||
*/
|
||||
|
|
@ -548,38 +606,18 @@ CPUStatePDP11.prototype.getPC = function()
|
|||
CPUStatePDP11.prototype.getLastPC = function()
|
||||
{
|
||||
/*
|
||||
* As long as we're always snapping the PC before every opcode, we might as well use it....
|
||||
* As long as we're always snapping the PC into regMMR2 before every opcode, we might as well use it.
|
||||
*/
|
||||
return this.regMMR2;
|
||||
};
|
||||
|
||||
/**
|
||||
* getPCWord()
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @return {number}
|
||||
*/
|
||||
CPUStatePDP11.prototype.getPCWord = function()
|
||||
{
|
||||
var data = this.readWord(this.regsGen[PDP11.REG.PC]);
|
||||
this.advancePC(2);
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* advancePC(off)
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @param {number} off
|
||||
*/
|
||||
CPUStatePDP11.prototype.advancePC = function(off)
|
||||
{
|
||||
this.regsGen[PDP11.REG.PC] = (this.regsGen[PDP11.REG.PC] + off) & 0xffff;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 {CPUStatePDP11}
|
||||
* @param {number} addr
|
||||
*/
|
||||
|
|
@ -591,6 +629,8 @@ CPUStatePDP11.prototype.setPC = function(addr)
|
|||
/**
|
||||
* getSP()
|
||||
*
|
||||
* NOTE: This function is nothing more than a convenience, and we fully expect it to be inlined at runtime.
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @return {number}
|
||||
*/
|
||||
|
|
@ -602,6 +642,10 @@ CPUStatePDP11.prototype.getSP = function()
|
|||
/**
|
||||
* setSP()
|
||||
*
|
||||
* NOTE: Unlike other PCjs emulators, such as PCx86, where all SP updates MUST go through the setSP()
|
||||
* 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 {CPUStatePDP11}
|
||||
* @param {number} addr
|
||||
*/
|
||||
|
|
@ -1118,6 +1162,13 @@ CPUStatePDP11.prototype.trap = function(vector, reason)
|
|||
this.opFlags &= ~PDP11.OPFLAG.TRAP_MASK; // lose interest in traps after an abort
|
||||
this.trapPSW = -1; // reset flag that we have a trap within a trap
|
||||
|
||||
/*
|
||||
* These next properties are purely for bookkeeping purposes; see getTrapStatus()
|
||||
*/
|
||||
this.opFlags |= PDP11.OPFLAG.TRAP;
|
||||
this.trapVector = vector;
|
||||
this.trapReason = reason;
|
||||
|
||||
if (reason != PDP11.REASON.INTERRUPT) throw vector;
|
||||
};
|
||||
|
||||
|
|
@ -1148,6 +1199,17 @@ CPUStatePDP11.prototype.trapReturn = function()
|
|||
this.opFlags &= ~PDP11.OPFLAG.TRAP_TF;
|
||||
};
|
||||
|
||||
/**
|
||||
* getTrapStatus()
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @return {number}
|
||||
*/
|
||||
CPUStatePDP11.prototype.getTrapStatus = function()
|
||||
{
|
||||
return (this.opFlags & PDP11.OPFLAG.TRAP)? (this.trapVector | this.trapReason << 8) : 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* mapUnibus(unibusAddress)
|
||||
*
|
||||
|
|
@ -1297,7 +1359,7 @@ CPUStatePDP11.prototype.mapVirtualToPhysical = function(virtualAddress, accessFl
|
|||
if (!(this.regMMR0 & 0xe000)) {
|
||||
this.regMMR0 |= errorMask | (this.mmuLastMode << 5) | (this.mmuLastPage << 1);
|
||||
}
|
||||
this.trap(PDP11.TRAP.MMU_FAULT, PDP11.REASON.MAPERROR);
|
||||
this.trap(PDP11.TRAP.MMU, PDP11.REASON.MAPERROR);
|
||||
}
|
||||
if (!(this.regMMR0 & 0xf000)) {
|
||||
//if (physicalAddress < 017772200 || physicalAddress > 017777677) {
|
||||
|
|
@ -1501,7 +1563,7 @@ CPUStatePDP11.prototype.getAddrByMode = function(mode, reg, accessFlags)
|
|||
* Mode 6: d(R)
|
||||
*/
|
||||
case 6:
|
||||
virtualAddress = this.getPCWord();
|
||||
virtualAddress = this.readWord(this.advancePC(2));
|
||||
virtualAddress = ((virtualAddress + this.regsGen[reg]) & 0xffff) | addrDSpace;
|
||||
this.nStepCycles -= (4 + 2);
|
||||
return virtualAddress;
|
||||
|
|
@ -1510,7 +1572,7 @@ CPUStatePDP11.prototype.getAddrByMode = function(mode, reg, accessFlags)
|
|||
* Mode 7: @d(R)
|
||||
*/
|
||||
case 7:
|
||||
virtualAddress = this.getPCWord();
|
||||
virtualAddress = this.readWord(this.advancePC(2));
|
||||
virtualAddress = (virtualAddress + this.regsGen[reg]) & 0xffff;
|
||||
virtualAddress = this.readWord(virtualAddress | this.addrDSpace) | addrDSpace;
|
||||
this.nStepCycles -= (7 + 3);
|
||||
|
|
@ -2054,13 +2116,13 @@ CPUStatePDP11.prototype.stepCPU = function(nMinCycles)
|
|||
*/
|
||||
if (this.opFlags & PDP11.OPFLAG.TRAP_MASK) {
|
||||
if (this.opFlags & PDP11.OPFLAG.TRAP_MMU) {
|
||||
this.trap(PDP11.TRAP.MMU_FAULT, PDP11.REASON.TRAPMMU); // MMU trap has priority
|
||||
this.trap(PDP11.TRAP.MMU, PDP11.REASON.TRAPMMU); // MMU trap has priority
|
||||
} else {
|
||||
if (this.opFlags & PDP11.OPFLAG.TRAP_SP) {
|
||||
this.trap(PDP11.TRAP.BUS_ERROR, PDP11.REASON.TRAPSP); // then SP trap
|
||||
this.trap(PDP11.TRAP.BUS_ERROR, PDP11.REASON.TRAPSP); // then SP trap
|
||||
} else {
|
||||
if (this.opFlags & PDP11.OPFLAG.TRAP_TF) {
|
||||
this.trap(PDP11.TRAP.BREAKPOINT, PDP11.REASON.TRAPTF); // and finally a TF trap
|
||||
this.trap(PDP11.TRAP.BPT, PDP11.REASON.TRAPTF); // and finally a TF trap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2077,18 +2139,13 @@ CPUStatePDP11.prototype.stepCPU = function(nMinCycles)
|
|||
}
|
||||
}
|
||||
|
||||
if (!(this.regMMR0 & PDP11.MMR0.ABORT)) {
|
||||
this.regMMR1 = 0;
|
||||
this.regMMR2 = this.regsGen[7];
|
||||
}
|
||||
|
||||
/*
|
||||
* Snapshot the TF bit in opFlags, while clearing all other opFlags (except those in PRESERVE);
|
||||
* we'll check the TRAP_TF bit in opFlags when we come back around for another opcode.
|
||||
*/
|
||||
this.opFlags = (this.opFlags & PDP11.OPFLAG.PRESERVE) | (this.regPSW & PDP11.PSW.TF);
|
||||
|
||||
this.decode(this.getPCWord());
|
||||
this.decode(this.getOpcode());
|
||||
|
||||
} while (this.nStepCycles > 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/*
|
||||
|
|
@ -139,7 +140,7 @@ function DebuggerPDP11(parmsDbg)
|
|||
* The new "bn" command allows you to specify a number of instructions to execute and then stop;
|
||||
* "bn 0" disables any outstanding count.
|
||||
*/
|
||||
this.nBreakIns = 0;
|
||||
this.nBreakInstructions = 0;
|
||||
|
||||
/*
|
||||
* Execution history is allocated by historyInit() whenever checksEnabled() conditions change.
|
||||
|
|
@ -279,6 +280,7 @@ if (DEBUGGER) {
|
|||
DebuggerPDP11.OP_DSTOFF = 0x2000;
|
||||
DebuggerPDP11.OP_DSTNUM3 = 0x3000; // DST 3-bit number (ie, just the DSTREG field)
|
||||
DebuggerPDP11.OP_DSTNUM6 = 0x6000; // DST 6-bit number (ie, both the DSTREG and DSTMODE fields)
|
||||
DebuggerPDP11.OP_DSTNUM8 = 0x8000; // DST 8-bit number
|
||||
DebuggerPDP11.OP_OTHER = 0xF000;
|
||||
|
||||
/*
|
||||
|
|
@ -338,8 +340,8 @@ if (DEBUGGER) {
|
|||
0x8500: [DebuggerPDP11.OPS.BVS, DebuggerPDP11.OP_BRANCH],
|
||||
0x8600: [DebuggerPDP11.OPS.BCC, DebuggerPDP11.OP_BRANCH],
|
||||
0x8700: [DebuggerPDP11.OPS.BCS, DebuggerPDP11.OP_BRANCH],
|
||||
0x8800: [DebuggerPDP11.OPS.EMT], // 104000..104377
|
||||
0x8900: [DebuggerPDP11.OPS.TRAP] // 104400..104777
|
||||
0x8800: [DebuggerPDP11.OPS.EMT, DebuggerPDP11.OP_DSTNUM8], // 104000..104377
|
||||
0x8900: [DebuggerPDP11.OPS.TRAP, DebuggerPDP11.OP_DSTNUM8] // 104400..104777
|
||||
},
|
||||
0xFFC0: {
|
||||
0x0040: [DebuggerPDP11.OPS.JMP, DebuggerPDP11.OP_DST], // 0001DD
|
||||
|
|
@ -570,11 +572,11 @@ if (DEBUGGER) {
|
|||
};
|
||||
|
||||
/**
|
||||
* updateFocus()
|
||||
* setFocus()
|
||||
*
|
||||
* @this {DebuggerPDP11}
|
||||
*/
|
||||
DebuggerPDP11.prototype.updateFocus = function()
|
||||
DebuggerPDP11.prototype.setFocus = function()
|
||||
{
|
||||
if (this.controlDebug) this.controlDebug.focus();
|
||||
};
|
||||
|
|
@ -931,8 +933,8 @@ if (DEBUGGER) {
|
|||
{
|
||||
var sMore = "";
|
||||
var cHistory = 0;
|
||||
var iHistory = this.iOpcodeHistory;
|
||||
var aHistory = this.aOpcodeHistory;
|
||||
var iHistory = this.iInstructionHistory;
|
||||
var aHistory = this.aInstructionHistory;
|
||||
|
||||
if (aHistory.length) {
|
||||
var nPrev = +sPrev || this.nextHistory;
|
||||
|
|
@ -984,7 +986,7 @@ if (DEBUGGER) {
|
|||
*
|
||||
* If you re-enable this protection, be sure to re-enable the decrement below, too.
|
||||
*/
|
||||
while (nLines > 0 && iHistory != this.iOpcodeHistory) {
|
||||
while (nLines > 0 && iHistory != this.iInstructionHistory) {
|
||||
|
||||
var dbgAddr = aHistory[iHistory++];
|
||||
if (dbgAddr.addr == null) break;
|
||||
|
|
@ -997,10 +999,16 @@ if (DEBUGGER) {
|
|||
|
||||
var sComment = "history";
|
||||
var nSequence = nPrev--;
|
||||
|
||||
/*
|
||||
* TODO: Need to some UI to control whether cycle counts are displayed as part of the history.
|
||||
* It's currently disabled in checkInstruction(), so it's disable here, too.
|
||||
*
|
||||
if (DEBUG && dbgAddr.cycleCount != null) {
|
||||
sComment = "cycles";
|
||||
nSequence = dbgAddr.cycleCount;
|
||||
}
|
||||
*/
|
||||
|
||||
var sInstruction = this.getInstruction(dbgAddrNew, sComment, nSequence);
|
||||
|
||||
|
|
@ -1208,11 +1216,14 @@ if (DEBUGGER) {
|
|||
* init()
|
||||
*
|
||||
* @this {DebuggerPDP11}
|
||||
* @param {boolean} [fAutoStart]
|
||||
*/
|
||||
DebuggerPDP11.prototype.init = function()
|
||||
DebuggerPDP11.prototype.init = function(fAutoStart)
|
||||
{
|
||||
this.fInit = true;
|
||||
this.println("Type ? for help with PDP11 Debugger commands");
|
||||
this.updateStatus();
|
||||
if (!fAutoStart) this.setFocus();
|
||||
if (this.sInitCommands) {
|
||||
var sCmds = this.sInitCommands;
|
||||
this.sInitCommands = null;
|
||||
|
|
@ -1237,34 +1248,27 @@ if (DEBUGGER) {
|
|||
{
|
||||
var i;
|
||||
if (!this.checksEnabled()) {
|
||||
if (this.aOpcodeHistory && this.aOpcodeHistory.length && !fQuiet) {
|
||||
if (this.aInstructionHistory && this.aInstructionHistory.length && !fQuiet) {
|
||||
this.println("instruction history buffer freed");
|
||||
}
|
||||
this.iOpcodeHistory = 0;
|
||||
this.aOpcodeHistory = [];
|
||||
this.aaOpcodeCounts = [];
|
||||
this.iInstructionHistory = 0;
|
||||
this.aInstructionHistory = [];
|
||||
return;
|
||||
}
|
||||
if (!this.aOpcodeHistory || !this.aOpcodeHistory.length) {
|
||||
this.aOpcodeHistory = new Array(DebuggerPDP11.HISTORY_LIMIT);
|
||||
for (i = 0; i < this.aOpcodeHistory.length; i++) {
|
||||
if (!this.aInstructionHistory || !this.aInstructionHistory.length) {
|
||||
this.aInstructionHistory = new Array(DebuggerPDP11.HISTORY_LIMIT);
|
||||
for (i = 0; i < this.aInstructionHistory.length; i++) {
|
||||
/*
|
||||
* Preallocate dummy Addr (Array) objects in every history slot, so that
|
||||
* checkInstruction() doesn't need to call newAddr() on every slot update.
|
||||
*/
|
||||
this.aOpcodeHistory[i] = this.newAddr();
|
||||
this.aInstructionHistory[i] = this.newAddr();
|
||||
}
|
||||
this.iOpcodeHistory = 0;
|
||||
this.iInstructionHistory = 0;
|
||||
if (!fQuiet) {
|
||||
this.println("instruction history buffer allocated");
|
||||
}
|
||||
}
|
||||
if (!this.aaOpcodeCounts || !this.aaOpcodeCounts.length) {
|
||||
this.aaOpcodeCounts = new Array(256);
|
||||
for (i = 0; i < this.aaOpcodeCounts.length; i++) {
|
||||
this.aaOpcodeCounts[i] = [i, 0];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1316,7 +1320,7 @@ if (DEBUGGER) {
|
|||
this.nCycles += nCyclesStep;
|
||||
this.cpu.addCycles(nCyclesStep, true);
|
||||
this.cpu.updateChecksum(nCyclesStep);
|
||||
this.cOpcodes++;
|
||||
this.cInstructions++;
|
||||
}
|
||||
}
|
||||
catch(exception) {
|
||||
|
|
@ -1361,8 +1365,17 @@ if (DEBUGGER) {
|
|||
*/
|
||||
DebuggerPDP11.prototype.updateStatus = function(fRegs)
|
||||
{
|
||||
if (!this.fInit) return;
|
||||
|
||||
if (fRegs === undefined) fRegs = true;
|
||||
|
||||
var trapStatus = this.cpu.getTrapStatus();
|
||||
if (trapStatus) {
|
||||
var trapReason = trapStatus >> 8;
|
||||
var sReason = trapReason? (" (" + this.toStrBase(trapReason) + ")") : "";
|
||||
this.println("trapped to " + this.toStrBase(trapStatus & 0xff, 1) + sReason);
|
||||
}
|
||||
|
||||
this.dbgAddrNextCode = this.newAddr(this.cpu.getPC());
|
||||
/*
|
||||
* this.nStep used to be a simple boolean, but now it's 0 (or undefined)
|
||||
|
|
@ -1446,7 +1459,7 @@ if (DEBUGGER) {
|
|||
DebuggerPDP11.prototype.reset = function(fQuiet)
|
||||
{
|
||||
this.historyInit();
|
||||
this.cOpcodes = this.cOpcodesStart = 0;
|
||||
this.cInstructions = this.cInstructionsStart = 0;
|
||||
this.sMessagePrev = null;
|
||||
this.nCycles = 0;
|
||||
this.dbgAddrNextCode = this.newAddr(this.cpu.getPC());
|
||||
|
|
@ -1540,14 +1553,14 @@ if (DEBUGGER) {
|
|||
var nCyclesPerSecond = (msTotal > 0? Math.round(this.nCycles * 1000 / msTotal) : 0);
|
||||
sStopped += " (";
|
||||
if (this.checksEnabled()) {
|
||||
sStopped += this.cOpcodes + " opcodes, ";
|
||||
sStopped += this.cInstructions + " instructions, ";
|
||||
/*
|
||||
* $ops displays progress by calculating cOpcodes - cOpcodesStart, so before
|
||||
* zeroing cOpcodes, we should subtract cOpcodes from cOpcodesStart (since we're
|
||||
* effectively subtracting cOpcodes from cOpcodes as well).
|
||||
*/
|
||||
this.cOpcodesStart -= this.cOpcodes;
|
||||
this.cOpcodes = 0;
|
||||
this.cInstructionsStart -= this.cInstructions;
|
||||
this.cInstructions = 0;
|
||||
}
|
||||
sStopped += this.nCycles + " cycles, " + msTotal + " ms, " + nCyclesPerSecond + " hz)";
|
||||
} else {
|
||||
|
|
@ -1563,7 +1576,7 @@ if (DEBUGGER) {
|
|||
this.println(sStopped);
|
||||
}
|
||||
this.updateStatus(true);
|
||||
this.updateFocus();
|
||||
this.setFocus();
|
||||
this.clearTempBreakpoint(this.cpu.getPC());
|
||||
}
|
||||
};
|
||||
|
|
@ -1584,7 +1597,7 @@ if (DEBUGGER) {
|
|||
*/
|
||||
DebuggerPDP11.prototype.checksEnabled = function(fRelease)
|
||||
{
|
||||
return ((DEBUG && !fRelease)? true : (this.aBreakExec.length > 1 || !!this.nBreakIns));
|
||||
return ((DEBUG && !fRelease)? true : (this.aBreakExec.length > 1 || !!this.nBreakInstructions));
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1600,11 +1613,23 @@ if (DEBUGGER) {
|
|||
*/
|
||||
DebuggerPDP11.prototype.checkInstruction = function(addr, nState)
|
||||
{
|
||||
var opCode = -1;
|
||||
var cpu = this.cpu;
|
||||
|
||||
/*
|
||||
* Purely as a convenience, we're going to skip over a HALT opcode if the machine is just starting,
|
||||
* and pretend that the NEXT instruction is the first to be executed.
|
||||
*/
|
||||
if (nState == 0) {
|
||||
opCode = this.cpu.getWordDirect(addr);
|
||||
if (opCode == PDP11.OPCODE.HALT) {
|
||||
addr = this.cpu.advancePC(2);
|
||||
}
|
||||
}
|
||||
|
||||
if (nState > 0) {
|
||||
if (this.nBreakIns && !--this.nBreakIns) {
|
||||
return true;
|
||||
if (this.nBreakInstructions) {
|
||||
if (!--this.nBreakInstructions) return true;
|
||||
}
|
||||
if (this.checkBreakpoint(addr, 1, this.aBreakExec)) {
|
||||
return true;
|
||||
|
|
@ -1617,14 +1642,16 @@ if (DEBUGGER) {
|
|||
* adding/removing breakpoints, simply because it's breakpoints that trigger the call to checkInstruction();
|
||||
* well, OK, and a few other things now, like enabling MessagesPDP11.INT messages.
|
||||
*/
|
||||
if (nState >= 0 && this.aaOpcodeCounts.length) {
|
||||
this.cOpcodes++;
|
||||
var opCode = this.cpu.getWordDirect(addr);
|
||||
if (opCode != null) {
|
||||
var dbgAddr = this.aOpcodeHistory[this.iOpcodeHistory];
|
||||
this.setAddr(dbgAddr, cpu.getPC());
|
||||
if (DEBUG) dbgAddr.cycleCount = cpu.getCycles();
|
||||
if (++this.iOpcodeHistory == this.aOpcodeHistory.length) this.iOpcodeHistory = 0;
|
||||
if (nState >= 0 && this.aInstructionHistory.length) {
|
||||
this.cInstructions++;
|
||||
if (opCode < 0) {
|
||||
opCode = this.cpu.getWordDirect(addr);
|
||||
}
|
||||
if ((opCode & 0xffff) != PDP11.OPCODE.INVALID) {
|
||||
var dbgAddr = this.aInstructionHistory[this.iInstructionHistory];
|
||||
this.setAddr(dbgAddr, addr);
|
||||
// if (DEBUG) dbgAddr.cycleCount = cpu.getCycles();
|
||||
if (++this.iInstructionHistory == this.aInstructionHistory.length) this.iInstructionHistory = 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
|
@ -1644,7 +1671,12 @@ if (DEBUGGER) {
|
|||
if (cpu.isRunning()) {
|
||||
cpu.setPC(this.cpu.getLastPC());
|
||||
this.stopCPU();
|
||||
throw -1; // TODO: Review the appropriate-ness of throwing a bogus vector number in order to immediately stop the instruction
|
||||
/*
|
||||
* TODO: Review the appropriate-ness of throwing a bogus vector number in order to immediately stop
|
||||
* the instruction. It's handy, but it also means that we no longer actually return true, so callers
|
||||
* of either stopInstruction() or undefinedInstruction() may have unreachable code paths.
|
||||
*/
|
||||
throw -1;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
|
@ -2071,7 +2103,8 @@ if (DEBUGGER) {
|
|||
|
||||
/*
|
||||
* If getOperand() returns an Array rather than a string, then the first element is the original
|
||||
* operand, and the second element contains an alternate representation of the operand (eg, target address).
|
||||
* operand, and the second element contains an alternate representation of the operand (eg, target
|
||||
* address, memory contents, etc).
|
||||
*/
|
||||
if (typeof sOperand != "string") {
|
||||
sTarget = sOperand[1];
|
||||
|
|
@ -2114,6 +2147,10 @@ if (DEBUGGER) {
|
|||
* If getOperand() returns an Array rather than a string, then the first element is the original
|
||||
* operand, and the second element is a comment containing an alternate representation of the operand.
|
||||
*
|
||||
* TODO: For PC-relative addresses, we now return the effective address directly, rather than as the
|
||||
* second element of an Array; however, I still envision using Array return values to include current
|
||||
* memory operands, so support for such values is being left in place.
|
||||
*
|
||||
* @this {DebuggerPDP11}
|
||||
* @param {number} opCode
|
||||
* @param {number} opType
|
||||
|
|
@ -2139,13 +2176,17 @@ if (DEBUGGER) {
|
|||
sOperand = this.toStrBase(addr);
|
||||
}
|
||||
else if (opTypeOther == DebuggerPDP11.OP_DSTNUM3) {
|
||||
disp = (opCode & 0x7);
|
||||
disp = (opCode & 0x07);
|
||||
sOperand = this.toStrBase(disp, 1);
|
||||
}
|
||||
else if (opTypeOther == DebuggerPDP11.OP_DSTNUM6) {
|
||||
disp = (opCode & 0x3f);
|
||||
sOperand = this.toStrBase(disp, 1);
|
||||
}
|
||||
else if (opTypeOther == DebuggerPDP11.OP_DSTNUM8) {
|
||||
disp = (opCode & 0xff);
|
||||
sOperand = this.toStrBase(disp, 1);
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Isolate all OP_SRC or OP_DST bits from opcode in the opMode variable.
|
||||
|
|
@ -2205,9 +2246,20 @@ if (DEBUGGER) {
|
|||
sOperand = this.toStrBase(wIndex, 0, true) + '(' + this.getRegName(reg) + ')';
|
||||
if (reg == 7) {
|
||||
/*
|
||||
* When using R7 (aka PC), INDEX is known as RELATIVE
|
||||
* When using R7 (aka PC), INDEX is known as RELATIVE. However, instead of displaying
|
||||
* such an instruction like this:
|
||||
*
|
||||
* 016156: 010167 001300 MOV R1,1300(PC) ; @017462
|
||||
*
|
||||
* with the effective address display to the far right, let's display it like this instead:
|
||||
*
|
||||
* 016156: 010167 001300 MOV R1,017462
|
||||
*
|
||||
* because you can still clearly see PC-relative offset (eg, 001300) as part of the disassembly.
|
||||
*
|
||||
* sOperand = [sOperand, this.toStrBase((wIndex + dbgAddr.addr) & 0xffff)];
|
||||
*/
|
||||
sOperand = [sOperand, this.toStrBase((wIndex + dbgAddr.addr) & 0xffff)];
|
||||
sOperand = this.toStrBase((wIndex + dbgAddr.addr) & 0xffff);
|
||||
}
|
||||
break;
|
||||
case PDP11.OPMODE.INDEXD: // 0x7: INDEX DEFERRED
|
||||
|
|
@ -2215,9 +2267,12 @@ if (DEBUGGER) {
|
|||
sOperand = '@' + this.toStrBase(wIndex) + '(' + this.getRegName(reg) + ')';
|
||||
if (reg == 7) {
|
||||
/*
|
||||
* When using R7 (aka PC), INDEX DEFERRED is known as RELATIVE DEFERRED
|
||||
* When using R7 (aka PC), INDEX DEFERRED is known as RELATIVE DEFERRED. And for the same
|
||||
* reasons articulated above, we now display the effective address inline.
|
||||
*
|
||||
* sOperand = [sOperand, this.toStrBase((wIndex + dbgAddr.addr) & 0xffff)];
|
||||
*/
|
||||
sOperand = [sOperand, this.toStrBase((wIndex + dbgAddr.addr) & 0xffff)];
|
||||
sOperand = '@' + this.toStrBase((wIndex + dbgAddr.addr) & 0xffff);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
@ -2235,7 +2290,7 @@ if (DEBUGGER) {
|
|||
/**
|
||||
* parseInstruction(sOp, sOperand, addr)
|
||||
*
|
||||
* TODO: Unimplemented. See parseInstruction() in modules/c1pjs/lib/debugger.js for a working implementation.
|
||||
* TODO: Unimplemented. See parseInstruction() in modules/c1pjs/lib/debugger.js for a sample implementation.
|
||||
*
|
||||
* @this {DebuggerPDP11}
|
||||
* @param {string} sOp
|
||||
|
|
@ -2702,8 +2757,8 @@ if (DEBUGGER) {
|
|||
}
|
||||
|
||||
if (sParm == 'n') {
|
||||
this.nBreakIns = this.parseValue(sAddr);
|
||||
this.println("break after " + this.nBreakIns + " instruction(s)");
|
||||
this.nBreakInstructions = this.parseValue(sAddr);
|
||||
this.println("break after " + this.nBreakInstructions + " instruction(s)");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2881,9 +2936,12 @@ if (DEBUGGER) {
|
|||
* And while we used to always call getByte() and assemble them into words or dwords as appropriate, I've
|
||||
* changed the logic below to honor "dw" by calling getWord(), since the Bus interfaces have been updated
|
||||
* to prevent generating traps due to to Debugger access of unaligned memory and/or undefined IOPAGE addresses.
|
||||
*
|
||||
* Besides, it's nice for "db" and "dw" to generate the same Bus activity that typical byte and word reads do.
|
||||
*/
|
||||
for (i = (size == 4? 16 : this.nBase); i > 0 && nBytes > 0; i--) {
|
||||
var n, v = size == 2? this.getWord(dbgAddr, n = 2) : this.getByte(dbgAddr, n = 1);
|
||||
var n = 1;
|
||||
var v = size == 1? this.getByte(dbgAddr, n) : this.getWord(dbgAddr, (n = 2));
|
||||
data |= (v << (shift << 3));
|
||||
shift += n;
|
||||
if (shift == size) {
|
||||
|
|
@ -2892,7 +2950,7 @@ if (DEBUGGER) {
|
|||
data = shift = 0;
|
||||
}
|
||||
sChars += (v >= 32 && v < 128? String.fromCharCode(v) : '.');
|
||||
nBytes--;
|
||||
nBytes -= n;
|
||||
}
|
||||
if (sDump) sDump += '\n';
|
||||
sDump += sAddr + " " + sData + ((i == 0)? (' ' + sChars) : "");
|
||||
|
|
@ -3423,7 +3481,7 @@ if (DEBUGGER) {
|
|||
if (this.nStep) {
|
||||
this.setTempBreakpoint(dbgAddr);
|
||||
if (!this.startCPU()) {
|
||||
if (this.cmp) this.cmp.updateFocus();
|
||||
if (this.cmp) this.cmp.setFocus();
|
||||
this.nStep = 0;
|
||||
}
|
||||
/*
|
||||
|
|
@ -3658,16 +3716,16 @@ if (DEBUGGER) {
|
|||
};
|
||||
|
||||
/**
|
||||
* shiftArgs(asArgs)
|
||||
*
|
||||
* Used with any command (eg, "r") that allows but doesn't require whitespace between command and first argument.
|
||||
* splitArgs(sCmd)
|
||||
*
|
||||
* @this {DebuggerPDP11}
|
||||
* @param {Array.<string>} asArgs
|
||||
* @param {string} sCmd
|
||||
* @return {Array.<string>}
|
||||
*/
|
||||
DebuggerPDP11.prototype.shiftArgs = function(asArgs)
|
||||
DebuggerPDP11.prototype.splitArgs = function(sCmd)
|
||||
{
|
||||
var asArgs = sCmd.replace(/ +/g, ' ').split(' ');
|
||||
asArgs[0] = asArgs[0].toLowerCase();
|
||||
if (asArgs && asArgs.length) {
|
||||
var s0 = asArgs[0];
|
||||
var ch0 = s0.charAt(0);
|
||||
|
|
@ -3727,8 +3785,7 @@ if (DEBUGGER) {
|
|||
}
|
||||
|
||||
var fError = false;
|
||||
var asArgs = this.shiftArgs(sCmd.replace(/ +/g, ' ').split(' '));
|
||||
asArgs[0] = asArgs[0].toLowerCase();
|
||||
var asArgs = this.splitArgs(sCmd);
|
||||
|
||||
switch (asArgs[0].charAt(0)) {
|
||||
case 'a':
|
||||
|
|
|
|||
|
|
@ -185,6 +185,13 @@ var PDP11 = {
|
|||
CMODE: 14
|
||||
}
|
||||
},
|
||||
/*
|
||||
* Assorted common opcodes
|
||||
*/
|
||||
OPCODE: {
|
||||
HALT: 0x0000,
|
||||
INVALID: 0xFFFF // far from the only invalid opcode, just a KNOWN invalid opcode
|
||||
},
|
||||
/*
|
||||
* Internal operation state flags
|
||||
*/
|
||||
|
|
@ -192,6 +199,7 @@ var PDP11 = {
|
|||
INTQ_SPL: 0x01, // INTQ triggered by SPL
|
||||
INTQ: 0x02, // call checkInterrupts()
|
||||
WAIT: 0x04, // WAIT operation in progress
|
||||
TRAP: 0x08, // set if last operation was a trap (see trapLast for the vector, and trapReason for the reason)
|
||||
TRAP_TF: 0x10, // aka PDP11.PSW.TF
|
||||
TRAP_MMU: 0x20,
|
||||
TRAP_SP: 0x40,
|
||||
|
|
@ -241,15 +249,15 @@ var PDP11 = {
|
|||
*/
|
||||
TRAP: {
|
||||
UNDEFINED: 0x00, // 000 (reserved)
|
||||
BUS_ERROR: 0x04, // 004 illegal instructions, bus errors, stack limit, illegal internal address, microbreak
|
||||
BUS_ERROR: 0x04, // 004 illegal instruction, unaligned address, invalid memory, stack limit, microbreak
|
||||
RESERVED: 0x08, // 010 reserved instructions
|
||||
BREAKPOINT: 0x0C, // 014 BPT, breakpoint trap (trace)
|
||||
IOT: 0x10, // 020 IOT, input/output trap
|
||||
POWER_FAIL: 0x14, // 024 power fail
|
||||
EMULATOR: 0x18, // 030 EMT, emulator trap
|
||||
BPT: 0x0C, // 014 BPT: breakpoint trap (trace)
|
||||
IOT: 0x10, // 020 IOT: input/output trap
|
||||
PF: 0x14, // 024 power fail
|
||||
EMT: 0x18, // 030 EMT: emulator trap
|
||||
TRAP: 0x1C, // 034 TRAP instruction
|
||||
PIRQ: 0xA0, // 240 PIRQ, program interrupt request
|
||||
MMU_FAULT: 0xA8 // 250 MMU aborts and traps
|
||||
PIRQ: 0xA0, // 240 PIRQ: program interrupt request
|
||||
MMU: 0xA8 // 250 MMU: aborts and traps
|
||||
},
|
||||
/*
|
||||
* PDP-11 trap reasons (for diagnostic purposes only)
|
||||
|
|
@ -425,8 +433,10 @@ var PDP11 = {
|
|||
|
||||
LKS: 0o177546, // KW11-L Clock Status
|
||||
|
||||
PRS: 0o177550, // PC11/PR11 Reader Status Register
|
||||
PRB: 0o177552, // PC11/PR11 Reader Buffer Register
|
||||
PRS: 0o177550, // PC11 (and PR11) Reader Status Register
|
||||
PRB: 0o177552, // PC11 (and PR11) Reader Buffer Register
|
||||
PPS: 0o177554, // PC11 Punch Status Register
|
||||
PPB: 0o177556, // PC11 Punch Buffer Register
|
||||
|
||||
RCSR: 0o177560, // Display Terminal: Receiver Status Register
|
||||
RBUF: 0o177562, // Display Terminal: Receiver Data Buffer Register
|
||||
|
|
@ -511,6 +521,9 @@ var PDP11 = {
|
|||
PSW: 0o177776 // 777776 17777776 0x3FFFFE Processor Status Word
|
||||
},
|
||||
PC11: { // High Speed Reader & Punch (PR11 is a Reader-only unit)
|
||||
PRI: 4, // NOTE: reader has precedence over punch
|
||||
RVEC: 0o70, // reader vector
|
||||
PVEC: 0o74, // punch vector
|
||||
PRS: {
|
||||
RE: 0x0001, // Reader Enable (W/O)
|
||||
RIE: 0x0040, // Reader Interrupt Enable (allows the DONE and ERROR bits to trigger an interrupt)
|
||||
|
|
@ -518,18 +531,26 @@ var PDP11 = {
|
|||
BUSY: 0x0800, // Busy (R/O)
|
||||
ERROR: 0x8000, // Error (R/O)
|
||||
CLEAR: 0x08C0, // bits cleared on INIT
|
||||
WMASK: 0x0041 // bits writable
|
||||
RMASK: 0xFFFE, // bits readable (TODO: All I know for sure is that bit 0 is NOT readable; see readPRS())
|
||||
WMASK: 0x0041, // bits writable
|
||||
BAUD: 3600
|
||||
},
|
||||
PRB: {
|
||||
MASK: 0x00FF // Data
|
||||
}
|
||||
},
|
||||
PPS: {
|
||||
/*
|
||||
* TODO: Flesh this out if/when we add Paper Tape Punch support
|
||||
*/
|
||||
BAUD: 600
|
||||
},
|
||||
},
|
||||
DL11: { // Serial Line Interface (program compatible with the KL11 for control of console teleprinters)
|
||||
PRI: 4,
|
||||
RVEC: 0o60,
|
||||
XVEC: 0o64,
|
||||
RCSR: { // 177560
|
||||
RE: 0x0001, // Reader Enable (W/O) TODO: Determine if we really need to exclude this write-only bit from RMASK
|
||||
RE: 0x0001, // Reader Enable (W/O)
|
||||
DTR: 0x0002, // Data Terminal Ready (R/W)
|
||||
RTS: 0x0004, // Request To Send (R/W)
|
||||
STD: 0x0008, // Secondary Transmitted Data (R/W)
|
||||
|
|
@ -542,16 +563,16 @@ var PDP11 = {
|
|||
CTS: 0x2000, // Clear To Send (R/O)
|
||||
RI: 0x4000, // Ring Indicator (R/O)
|
||||
DSC: 0x8000, // Dataset Status Change (R/O)
|
||||
RMASK: 0xFFFF, // read mask
|
||||
WMASK: 0x006F // write mask
|
||||
RMASK: 0xFFFE, // bits readable (TODO: All I know for sure is that bit 0 is NOT readable; see readRCSR())
|
||||
WMASK: 0x006F, // bits writable
|
||||
BAUD: 9600
|
||||
},
|
||||
RBUF: { // 177562
|
||||
DATA: 0x00ff, // Received Data (R/O)
|
||||
PARITY: 0x1000, // Received Data Parity (R/O)
|
||||
FE: 0x2000, // Framing Error (R/O)
|
||||
OE: 0x4000, // Overrun Error (R/O)
|
||||
ERROR: 0x8000, // Error (R/O)
|
||||
DELAY: 1
|
||||
ERROR: 0x8000 // Error (R/O)
|
||||
},
|
||||
XCSR: { // 177564
|
||||
BREAK: 0x0001, // BREAK (R/W)
|
||||
|
|
@ -560,11 +581,10 @@ var PDP11 = {
|
|||
READY: 0x0080, // Transmitter Ready (R/O)
|
||||
RMASK: 0x00C5,
|
||||
WMASK: 0x0045,
|
||||
DELAY: 1
|
||||
BAUD: 9600
|
||||
},
|
||||
XBUF: { // 177566
|
||||
DATA: 0x00FF, // Transmitted Data (W/O) TODO: Determine why pdp11.js effectively defined this as 0x7F
|
||||
DELAY: 1
|
||||
DATA: 0x00FF // Transmitted Data (W/O) TODO: Determine why pdp11.js effectively defined this as 0x7F
|
||||
}
|
||||
},
|
||||
KW11: { // KW11-L Line Time Clock
|
||||
|
|
|
|||
|
|
@ -88,7 +88,6 @@ KeyboardPDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
this.cmp = cmp;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg; // NOTE: The "dbg" property must be set for the message functions to work
|
||||
this.chipset = null; // /** @type {ChipSetPDP11} */ (cmp.getMachineComponent("ChipSet"));
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ var littleEndian = (TYPEDARRAYS? (function() {
|
|||
*/
|
||||
function MemoryPDP11(bus, addr, used, size, type, controller)
|
||||
{
|
||||
var i;
|
||||
var a, i;
|
||||
this.bus = bus;
|
||||
this.id = (MemoryPDP11.idBlock += 2);
|
||||
this.adw = null;
|
||||
|
|
@ -127,7 +127,7 @@ function MemoryPDP11(bus, addr, used, size, type, controller)
|
|||
/*
|
||||
* For empty memory blocks, all we need to do is ensure all access functions are mapped to "none" handlers.
|
||||
*/
|
||||
if (!size) {
|
||||
if (!this.size) {
|
||||
this.setAccess();
|
||||
return;
|
||||
}
|
||||
|
|
@ -138,7 +138,7 @@ function MemoryPDP11(bus, addr, used, size, type, controller)
|
|||
*/
|
||||
if (controller) {
|
||||
this.controller = controller;
|
||||
var a = controller.getControllerBuffer(addr);
|
||||
a = controller.getControllerBuffer(addr);
|
||||
this.adw = a[0];
|
||||
this.offset = a[1];
|
||||
this.setAccess(controller.getControllerAccess());
|
||||
|
|
@ -154,20 +154,24 @@ function MemoryPDP11(bus, addr, used, size, type, controller)
|
|||
* mode; pseudo-random might be best, to help make any bugs reproducible.
|
||||
*/
|
||||
if (TYPEDARRAYS) {
|
||||
this.buffer = new ArrayBuffer(size);
|
||||
this.dv = new DataView(this.buffer, 0, size);
|
||||
this.buffer = new ArrayBuffer(this.size);
|
||||
this.dv = new DataView(this.buffer, 0, this.size);
|
||||
/*
|
||||
* If littleEndian is true, we can use ab[], aw[] and adw[] directly; well, we can use them
|
||||
* whenever the offset is a multiple of 1, 2 or 4, respectively. Otherwise, we must fallback to
|
||||
* dv.getUint8()/dv.setUint8(), dv.getUint16()/dv.setUint16() and dv.getInt32()/dv.setInt32().
|
||||
*/
|
||||
this.ab = new Uint8Array(this.buffer, 0, size);
|
||||
this.aw = new Uint16Array(this.buffer, 0, size >> 1);
|
||||
this.adw = new Int32Array(this.buffer, 0, size >> 2);
|
||||
this.ab = new Uint8Array(this.buffer, 0, this.size);
|
||||
this.aw = new Uint16Array(this.buffer, 0, this.size >> 1);
|
||||
this.adw = new Int32Array(this.buffer, 0, this.size >> 2);
|
||||
this.setAccess(littleEndian? MemoryPDP11.afnArrayLE : MemoryPDP11.afnArrayBE);
|
||||
} else {
|
||||
/*
|
||||
* NOTE: An ArrayBuffer is defined as being zero-initialized, but the elements of a new
|
||||
* Array are not, so this code path takes care of zero-initialization ourselves.
|
||||
*/
|
||||
if (BYTEARRAYS) {
|
||||
this.ab = new Array(size);
|
||||
a = this.ab = new Array(this.size);
|
||||
} else {
|
||||
/*
|
||||
* NOTE: This is the default mode of operation (!TYPEDARRAYS && !BYTEARRAYS), because it
|
||||
|
|
@ -175,9 +179,9 @@ function MemoryPDP11(bus, addr, used, size, type, controller)
|
|||
* come at twice the overhead of TYPEDARRAYS, it's increasingly likely that the JavaScript
|
||||
* runtime will notice that all we ever store are 32-bit values, and optimize accordingly.
|
||||
*/
|
||||
this.adw = new Array(size >> 2);
|
||||
for (i = 0; i < this.adw.length; i++) this.adw[i] = 0;
|
||||
a = this.adw = new Array(this.size >> 2);
|
||||
}
|
||||
for (i = 0; i < a.length; i++) a[i] = 0;
|
||||
this.setAccess(MemoryPDP11.afnMemory);
|
||||
}
|
||||
}
|
||||
|
|
@ -247,7 +251,7 @@ MemoryPDP11.prototype = {
|
|||
this.addr = addr;
|
||||
},
|
||||
/**
|
||||
* clone(mem, type)
|
||||
* 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.
|
||||
|
|
@ -381,6 +385,31 @@ MemoryPDP11.prototype = {
|
|||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* zero(off, len)
|
||||
*
|
||||
* Zeros the block. Supporting off and len parameters is probably overkill, and makes more
|
||||
* work in the non-TYPEDARRAY, non-BYTEARRAY case, because there all we have is an array of DWORDs,
|
||||
* but that's not the typical case.
|
||||
*
|
||||
* @this {MemoryPDP11}
|
||||
* @param {number} [off] (optional starting byte offset within block)
|
||||
* @param {number} [len] (optional maximum number of bytes; default is the entire block)
|
||||
*/
|
||||
zero: function(off, len) {
|
||||
var i;
|
||||
off = off || 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);
|
||||
if (TYPEDARRAYS || BYTEARRAYS) {
|
||||
for (i = off; len-- && i < this.ab.length; i++) this.ab[i] = 0;
|
||||
} else {
|
||||
for (i = off; len-- && i < this.size; i++) this.writeByteDirect(off, 0, this.addr + off);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* setAccess(afn, fDirect)
|
||||
*
|
||||
|
|
@ -558,9 +587,10 @@ MemoryPDP11.prototype = {
|
|||
*/
|
||||
readNone: function readNone(off, addr) {
|
||||
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(MessagesPDP11.MEMORY) /* && !off */) {
|
||||
this.dbg.printMessage("attempt to read invalid block %" + str.toHex(this.addr), true);
|
||||
this.dbg.stopCPU();
|
||||
this.dbg.printMessage("attempt to read invalid address " + this.dbg.toStrBase(addr), true);
|
||||
this.dbg.stopInstruction();
|
||||
}
|
||||
this.bus.fault(addr, PDP11.ACCESS.READ);
|
||||
return 0xff;
|
||||
},
|
||||
/**
|
||||
|
|
@ -573,9 +603,10 @@ MemoryPDP11.prototype = {
|
|||
*/
|
||||
writeNone: function writeNone(off, v, addr) {
|
||||
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(MessagesPDP11.MEMORY) /* && !off */) {
|
||||
this.dbg.printMessage("attempt to write " + str.toHexWord(v) + " to invalid block %" + str.toHex(this.addr), true);
|
||||
this.dbg.stopCPU();
|
||||
this.dbg.printMessage("attempt to write " + this.dbg.toStrBase(v) + " to invalid addresses " + this.dbg.toStrBase(addr), true);
|
||||
this.dbg.stopInstruction();
|
||||
}
|
||||
this.bus.fault(addr, PDP11.ACCESS.WRITE);
|
||||
},
|
||||
/**
|
||||
* readWordDefault(off, addr)
|
||||
|
|
@ -624,7 +655,7 @@ MemoryPDP11.prototype = {
|
|||
*/
|
||||
readWordMemory: function readWordMemory(off, addr) {
|
||||
if (PDP11.MEMFAULT && (off & 0x1)) {
|
||||
this.bus.fault(addr);
|
||||
this.bus.fault(addr, PDP11.ACCESS.READ_WORD);
|
||||
}
|
||||
if (BYTEARRAYS) {
|
||||
return this.ab[off] | (this.ab[off + 1] << 8);
|
||||
|
|
@ -668,7 +699,7 @@ MemoryPDP11.prototype = {
|
|||
*/
|
||||
writeWordMemory: function writeWordMemory(off, w, addr) {
|
||||
if (PDP11.MEMFAULT && (off & 0x1)) {
|
||||
this.bus.fault(addr);
|
||||
this.bus.fault(addr, PDP11.ACCESS.WRITE_WORD);
|
||||
}
|
||||
if (BYTEARRAYS) {
|
||||
this.ab[off] = (w & 0xff);
|
||||
|
|
@ -778,7 +809,7 @@ MemoryPDP11.prototype = {
|
|||
*/
|
||||
readWordBE: function readWordBE(off, addr) {
|
||||
if (PDP11.MEMFAULT && (off & 0x1)) {
|
||||
this.bus.fault(addr);
|
||||
this.bus.fault(addr, PDP11.ACCESS.READ_WORD);
|
||||
}
|
||||
return this.dv.getUint16(off, true);
|
||||
},
|
||||
|
|
@ -793,7 +824,7 @@ MemoryPDP11.prototype = {
|
|||
readWordLE: function readWordLE(off, addr) {
|
||||
var w;
|
||||
if (PDP11.MEMFAULT && (off & 0x1)) {
|
||||
this.bus.fault(addr);
|
||||
this.bus.fault(addr, PDP11.ACCESS.READ_WORD);
|
||||
}
|
||||
/*
|
||||
* TODO: For non-WORDBUS machines, it remains to be seen if there's any advantage to checking the offset
|
||||
|
|
@ -846,7 +877,7 @@ MemoryPDP11.prototype = {
|
|||
*/
|
||||
writeWordBE: function writeWordBE(off, w, addr) {
|
||||
if (PDP11.MEMFAULT && (off & 0x1)) {
|
||||
this.bus.fault(addr);
|
||||
this.bus.fault(addr, PDP11.ACCESS.WRITE_WORD);
|
||||
}
|
||||
this.dv.setUint16(off, w, true);
|
||||
this.fDirty = true;
|
||||
|
|
@ -861,7 +892,7 @@ MemoryPDP11.prototype = {
|
|||
*/
|
||||
writeWordLE: function writeWordLE(off, w, addr) {
|
||||
if (PDP11.MEMFAULT && (off & 0x1)) {
|
||||
this.bus.fault(addr);
|
||||
this.bus.fault(addr, PDP11.ACCESS.WRITE_WORD);
|
||||
}
|
||||
/*
|
||||
* TODO: For non-WORDBUS machines, it remains to be seen if there's any advantage to checking the offset
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Implements the PC11 High-Speed Paper Tape Reader/Punch
|
||||
* @fileoverview Implements the PDP-11 High-Speed Paper Tape Reader/Punch (eg, PC11)
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2016
|
||||
*
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
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 State = require("../../shared/lib/state");
|
||||
var PDP11 = require("./defines");
|
||||
|
|
@ -40,6 +41,24 @@ if (NODE) {
|
|||
/**
|
||||
* PC11(parms)
|
||||
*
|
||||
* The PC11 component has the following component-specific (parms) properties:
|
||||
*
|
||||
* autoMount: a JSON-encoded object containing 'name' and 'path' properties, describing a
|
||||
* tape resource to automatically attach at startup (only the "attach" operation is supported
|
||||
* for autoMount; if you want to "load" a tape image directly into RAM at startup, you must
|
||||
* ask the RAM component to do that).
|
||||
*
|
||||
* baudReceive: the default number of bits/second that the device should receive data at;
|
||||
* 0 means use the device default (PDP11.PC11.PRS.BAUD)
|
||||
*
|
||||
* baudTransmit: the default number of bits/second that the device should transmit data at;
|
||||
* 0 means use the device default (PDP11.PC11.PPS.BAUD); currently ignored, since punch
|
||||
* support isn't implemented yet.
|
||||
*
|
||||
* NOTE: Since the XSL file defines the 'baud' properties as numbers, not strings, there's no need to
|
||||
* use parseInt(), and as an added benefit, we don't need to worry about whether a hex or decimal format
|
||||
* was used.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @param {Object} parms
|
||||
|
|
@ -49,29 +68,172 @@ function PC11(parms)
|
|||
Component.call(this, "PC11", parms, PC11);
|
||||
|
||||
/*
|
||||
* TODO: Technically, the PC11 should have a timer that "clocks" data from the abReader buffer into the
|
||||
* PRB register at the appropriate rate (300 CPS for the high-speed version, 10 CPS for the low-speed version).
|
||||
* We record any 'autoMount' object now, but we no longer parse it until initBus(), because the
|
||||
* Computer's getMachineParm() service may have an override for us.
|
||||
*/
|
||||
this.prs = 0; // PRS register
|
||||
this.prb = 0; // PRB register
|
||||
this.iReader = 0; // buffer index
|
||||
this.abReader = []; // buffer for the PRB register
|
||||
this.configMount = parms['autoMount'] || null;
|
||||
this.cAutoMount = 0;
|
||||
this.nBaudReceive = parms['baudReceive'] || PDP11.PC11.PRS.BAUD;
|
||||
|
||||
this.prs = 0; // PRS register
|
||||
this.prb = 0; // PRB register
|
||||
this.iTapeData = 0; // buffer index
|
||||
this.aTapeData = []; // buffer for the PRB register
|
||||
this.sTapeSource = PC11.SOURCE.NONE;
|
||||
this.nTapeTarget = PC11.TARGET.NONE;
|
||||
this.sTapeName = this.sTapePath = "";
|
||||
this.nLastPercent = -1; // ensure the first displayProgress() displays something
|
||||
|
||||
/*
|
||||
* Support for local tape images is currently limited to desktop browsers with FileReader support;
|
||||
* when this flag is set, setBinding() allows local tape bindings and informs initBus() to update the
|
||||
* "listTapes" binding accordingly.
|
||||
*/
|
||||
this.fLocalTapes = (!web.isMobile() && window && 'FileReader' in window);
|
||||
}
|
||||
|
||||
Component.subclass(PC11);
|
||||
|
||||
/*
|
||||
* There's nothing super special about these values, except that NONE should be falsey and the others should not.
|
||||
*/
|
||||
PC11.SOURCE = {
|
||||
NONE: "",
|
||||
LOCAL: "?",
|
||||
REMOTE: "??"
|
||||
};
|
||||
|
||||
PC11.TARGET = {
|
||||
NONE: 0,
|
||||
READER: 1,
|
||||
MEMORY: 2
|
||||
};
|
||||
|
||||
PC11.BINDING = {
|
||||
READ_PROGRESS: "readProgress"
|
||||
};
|
||||
|
||||
PC11.CSSCLASS = {
|
||||
PROGRESS_BAR: PDP11.CSSCLASS + "-progress-bar"
|
||||
};
|
||||
|
||||
/**
|
||||
* setBinding(sType, sBinding, control, sValue)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @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 {string|null} sType is the type of the HTML control (eg, "button", "list", "text", etc)
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listTapes")
|
||||
* @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
|
||||
*/
|
||||
PC11.prototype.setBinding = function(sType, sBinding, control, sValue)
|
||||
{
|
||||
var pc11 = this;
|
||||
var nTapeTarget = PC11.TARGET.NONE;
|
||||
|
||||
switch (sBinding) {
|
||||
|
||||
case "listTapes":
|
||||
this.bindings[sBinding] = control;
|
||||
control.onchange = function onChangeListTapes(event) {
|
||||
var controlDesc = pc11.bindings["descTape"];
|
||||
var controlOption = control.options[control.selectedIndex];
|
||||
if (controlDesc && controlOption) {
|
||||
var dataValue = {};
|
||||
var sValue = controlOption.getAttribute("data-value");
|
||||
if (sValue) {
|
||||
try {
|
||||
dataValue = eval("(" + sValue + ")");
|
||||
} catch (e) {
|
||||
Component.error("PC11 option error: " + e.message);
|
||||
}
|
||||
}
|
||||
var sHTML = dataValue['desc'];
|
||||
if (sHTML === undefined) sHTML = "";
|
||||
var sHRef = dataValue['href'];
|
||||
if (sHRef !== undefined) sHTML = "<a href=\"" + sHRef + "\" target=\"_blank\">" + sHTML + "</a>";
|
||||
controlDesc.innerHTML = sHTML;
|
||||
}
|
||||
};
|
||||
return true;
|
||||
|
||||
case "descTape":
|
||||
this.bindings[sBinding] = control;
|
||||
return true;
|
||||
|
||||
/*
|
||||
* "loadTape" operation must do pretty much everything that the "attachTape" does, but whereas the attach
|
||||
* operation records the bytes in aTapeData, the load operation stuffs them directly into the machine's memory;
|
||||
* the former sets nTapeTarget to TARGET.READER, while the latter sets it to TARGET.MEMORY.
|
||||
*/
|
||||
case "loadTape":
|
||||
nTapeTarget = PC11.TARGET.MEMORY;
|
||||
/* falls through */
|
||||
|
||||
case "attachTape":
|
||||
if (!nTapeTarget) nTapeTarget = PC11.TARGET.READER;
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function onClickLoadTape(event) {
|
||||
var controlTapes = pc11.bindings["listTapes"];
|
||||
if (controlTapes) {
|
||||
var sTapeName = controlTapes.options[controlTapes.selectedIndex].text;
|
||||
var sTapePath = controlTapes.value;
|
||||
pc11.loadSelectedTape(sTapeName, sTapePath, nTapeTarget);
|
||||
}
|
||||
};
|
||||
return true;
|
||||
|
||||
case "mountTape":
|
||||
if (!this.fLocalTapes) {
|
||||
if (DEBUG) this.log("Local tape support not available");
|
||||
/*
|
||||
* We could also simply hide the control; eg:
|
||||
*
|
||||
* control.style.display = "none";
|
||||
*
|
||||
* but removing the control altogether seems better.
|
||||
*/
|
||||
control.parentNode.removeChild(/** @type {Node} */ (control));
|
||||
return false;
|
||||
}
|
||||
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
/*
|
||||
* Enable "Mount" button only if a file is actually selected
|
||||
*/
|
||||
control.addEventListener('change', function() {
|
||||
var fieldset = control.children[0];
|
||||
var files = fieldset.children[0].files;
|
||||
var submit = fieldset.children[1];
|
||||
submit.disabled = !files.length;
|
||||
});
|
||||
|
||||
control.onsubmit = function(event) {
|
||||
var file = event.currentTarget[1].files[0];
|
||||
if (file) {
|
||||
var sTapePath = file.name;
|
||||
var sTapeName = str.getBaseName(sTapePath, true);
|
||||
/*
|
||||
* TODO: Provide a way to mount tapes into MEMORY as well as READER.
|
||||
*/
|
||||
pc11.loadSelectedTape(sTapeName, sTapePath, PC11.TARGET.READER, file);
|
||||
}
|
||||
/*
|
||||
* Prevent reloading of web page after form submission
|
||||
*/
|
||||
return false;
|
||||
};
|
||||
return true;
|
||||
|
||||
case PC11.BINDING.READ_PROGRESS:
|
||||
this.bindings[sBinding] = control;
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
|
@ -90,10 +252,40 @@ 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;
|
||||
|
||||
this.configMount = this.cmp.getMachineParm('autoMount') || this.configMount;
|
||||
|
||||
if (this.configMount) {
|
||||
if (typeof this.configMount == "string") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be right here, where we're parsing
|
||||
* this JSON-encoded data.
|
||||
*/
|
||||
this.configMount = eval("(" + this.configMount + ")");
|
||||
} catch (e) {
|
||||
Component.error("PC11 auto-mount error: " + e.message + " (" + this.configMount + ")");
|
||||
this.configMount = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.triggerReaderInterrupt = this.cpu.addTrigger(PDP11.PC11.RVEC, PDP11.PC11.PRI);
|
||||
|
||||
this.timerReaderAdvance = this.cpu.addTimer(function readyReader() {
|
||||
pc11.advanceReader();
|
||||
});
|
||||
|
||||
bus.addIOTable(this, PC11.UNIBUS_IOTABLE);
|
||||
|
||||
this.setReady();
|
||||
this.addTape("None", PC11.SOURCE.NONE, true);
|
||||
if (this.fLocalTapes) this.addTape("Local Tape", PC11.SOURCE.LOCAL);
|
||||
this.addTape("Remote Tape", PC11.SOURCE.REMOTE);
|
||||
|
||||
if (!this.autoMount()) this.setReady();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -132,14 +324,409 @@ PC11.prototype.powerDown = function(fSave, fShutdown)
|
|||
/**
|
||||
* reset()
|
||||
*
|
||||
* TODO: Consider making our reset() handler ALSO restore the original attached tape, in much the same
|
||||
* way the RAM component now restores the original predefined memory or tape image after resetting the RAM.
|
||||
*
|
||||
* @this {PC11}
|
||||
*/
|
||||
PC11.prototype.reset = function()
|
||||
{
|
||||
this.prs &= ~PDP11.PC11.PRS.CLEAR;
|
||||
this.prb = 0;
|
||||
this.iReader = 0;
|
||||
this.abReader = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* autoMount(fRemount)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {boolean} [fRemount] is true if we're remounting all auto-mounted tapes
|
||||
* @return {boolean} true if one or more tape images are being auto-mounted, false if none
|
||||
*/
|
||||
PC11.prototype.autoMount = function(fRemount)
|
||||
{
|
||||
if (!fRemount) this.cAutoMount = 0;
|
||||
if (this.configMount) {
|
||||
var sTapePath = this.configMount['path'] || "";
|
||||
var sTapeName = this.configMount['name'] || this.findTape(sTapePath);
|
||||
if (sTapePath && sTapeName) {
|
||||
/*
|
||||
* TODO: Provide a way to autoMount tapes into MEMORY as well as READER.
|
||||
*/
|
||||
if (!this.loadTape(sTapeName, sTapePath, PC11.TARGET.READER, true) && fRemount) {
|
||||
this.setReady(false);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* loadSelectedTape(sTapeName, sTapePath, nTapeTarget, file)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {string} sTapeName
|
||||
* @param {string} sTapePath
|
||||
* @param {number} nTapeTarget
|
||||
* @param {File} [file] is set if there's an associated File object
|
||||
*/
|
||||
PC11.prototype.loadSelectedTape = function(sTapeName, sTapePath, nTapeTarget, file)
|
||||
{
|
||||
if (!sTapePath) {
|
||||
this.unloadTape(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sTapePath == PC11.SOURCE.LOCAL) {
|
||||
this.notice('Use "Choose File" and "Mount" to select and load a local tape.');
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the special PC11.SOURCE.REMOTE path is selected, then we want to prompt the user for a URL.
|
||||
* Oh, and make sure we pass an empty string as the 2nd parameter to prompt(), so that IE won't display
|
||||
* "undefined" -- because after all, undefined and "undefined" are EXACTLY the same thing, right?
|
||||
*
|
||||
* TODO: This is literally all I've done to support remote tape images. There's probably more
|
||||
* I should do, like dynamically updating "listTapes" to include new entries, and adding new entries
|
||||
* to the save/restore data.
|
||||
*/
|
||||
if (sTapePath == PC11.SOURCE.REMOTE) {
|
||||
sTapePath = window.prompt("Enter the URL of a remote tape image.", "") || "";
|
||||
if (!sTapePath) return;
|
||||
sTapeName = str.getBaseName(sTapePath);
|
||||
this.status("Attempting to load " + sTapePath + " as \"" + sTapeName + "\"");
|
||||
this.sTapeSource = PC11.SOURCE.REMOTE;
|
||||
}
|
||||
else {
|
||||
this.sTapeSource = sTapePath;
|
||||
}
|
||||
|
||||
this.loadTape(sTapeName, sTapePath, nTapeTarget, false, file);
|
||||
};
|
||||
|
||||
/**
|
||||
* loadTape(sTapeName, sTapePath, nTapeTarget, fAutoMount, file)
|
||||
*
|
||||
* NOTE: If sTapePath is already loaded, nothing needs to be done.
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {string} sTapeName
|
||||
* @param {string} sTapePath
|
||||
* @param {number} nTapeTarget
|
||||
* @param {boolean} [fAutoMount]
|
||||
* @param {File} [file] is set if there's an associated File object
|
||||
* @return {number} 1 if tape loaded, 0 if queued up (or busy), -1 if already loaded
|
||||
*/
|
||||
PC11.prototype.loadTape = function(sTapeName, sTapePath, nTapeTarget, fAutoMount, file)
|
||||
{
|
||||
var nResult = -1;
|
||||
if (this.sTapePath.toLowerCase() != sTapePath.toLowerCase() || this.nTapeTarget != nTapeTarget) {
|
||||
|
||||
nResult++;
|
||||
this.unloadTape(true);
|
||||
|
||||
if (this.flags.busy) {
|
||||
this.notice("PC11 busy");
|
||||
}
|
||||
else {
|
||||
// this.status("tape queued: " + sTapeName);
|
||||
if (fAutoMount) {
|
||||
this.cAutoMount++;
|
||||
if (this.messageEnabled()) this.printMessage("auto-loading tape: " + sTapeName);
|
||||
}
|
||||
if (this.load(sTapeName, sTapePath, nTapeTarget, file)) {
|
||||
nResult++;
|
||||
} else {
|
||||
this.flags.busy = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nResult) this.status(this.nTapeTarget == PC11.TARGET.READER? "tape attached" : "tape loaded");
|
||||
return nResult;
|
||||
};
|
||||
|
||||
/**
|
||||
* load(sTapeName, sTapePath, nTapeTarget, file)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {string} sTapeName
|
||||
* @param {string} sTapePath
|
||||
* @param {number} nTapeTarget
|
||||
* @param {File} [file] is set if there's an associated File object
|
||||
* @return {boolean} true if load completed (successfully or not), false if queued
|
||||
*/
|
||||
PC11.prototype.load = function(sTapeName, sTapePath, nTapeTarget, file)
|
||||
{
|
||||
var pc11 = this;
|
||||
var sTapeURL = sTapePath;
|
||||
|
||||
if (DEBUG) {
|
||||
var sMessage = 'load("' + sTapeName + '","' + sTapePath + '")';
|
||||
this.printMessage(sMessage);
|
||||
}
|
||||
|
||||
if (file) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
pc11.doneRead(sTapeName, sTapePath, nTapeTarget, reader.result);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there's an occurrence of API_ENDPOINT anywhere in the path, we assume we can use it as-is;
|
||||
* ie, that the user has already formed a URL of the type we use ourselves for unconverted tape images.
|
||||
*/
|
||||
if (sTapePath.indexOf(DumpAPI.ENDPOINT) < 0) {
|
||||
/*
|
||||
* If the selected tape image has a "json" extension, then we assume it's a pre-converted
|
||||
* JSON-encoded tape image, so we load it as-is; otherwise, we ask our server-side tape image
|
||||
* converter to return the corresponding JSON-encoded data.
|
||||
*/
|
||||
var sTapeExt = str.getExtension(sTapePath);
|
||||
if (sTapeExt == DumpAPI.FORMAT.JSON || sTapeExt == DumpAPI.FORMAT.JSON_GZ) {
|
||||
sTapeURL = encodeURI(sTapePath);
|
||||
} else {
|
||||
var sTapeParm = DumpAPI.QUERY.PATH;
|
||||
sTapeURL = web.getHost() + DumpAPI.ENDPOINT + '?' + sTapeParm + '=' + encodeURIComponent(sTapePath) + "&" + DumpAPI.QUERY.FORMAT + "=" + DumpAPI.FORMAT.JSON;
|
||||
}
|
||||
}
|
||||
|
||||
return !!web.getResource(sTapeURL, null, true, function(sURL, sResponse, nErrorCode) {
|
||||
pc11.doneLoad(sTapeName, sTapePath, nTapeTarget, sResponse, sURL, nErrorCode);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* doneLoad(sTapeName, sTapePath, sTapeData, nTapeTarget, sURL, nErrorCode)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {string} sTapeName
|
||||
* @param {string} sTapePath
|
||||
* @param {string} sTapeData
|
||||
* @param {number} nTapeTarget
|
||||
* @param {string} sURL
|
||||
* @param {number} nErrorCode (response from server if anything other than 200)
|
||||
*/
|
||||
PC11.prototype.doneLoad = function(sTapeName, sTapePath, nTapeTarget, sTapeData, sURL, nErrorCode)
|
||||
{
|
||||
var fPrintOnly = (nErrorCode < 0 && this.cmp && !this.cmp.flags.powered);
|
||||
|
||||
if (nErrorCode) {
|
||||
/*
|
||||
* This can happen for innocuous reasons, such as the user switching away too quickly, forcing
|
||||
* the request to be cancelled. And unfortunately, the browser cancels XMLHttpRequest requests
|
||||
* BEFORE it notifies any page event handlers, so if the Computer's being powered down, we won't know
|
||||
* that yet. For now, we rely on the lack of a specific error (nErrorCode < 0), and suppress the
|
||||
* notify() alert if there's no specific error AND the computer is not powered up yet.
|
||||
*/
|
||||
this.notice("Unable to load tape \"" + sTapeName + "\" (error " + nErrorCode + ": " + sURL + ")", fPrintOnly);
|
||||
}
|
||||
else {
|
||||
if (DEBUG && this.messageEnabled()) {
|
||||
this.printMessage('doneLoad("' + sTapePath + '")');
|
||||
}
|
||||
Component.addMachineResource(this.idMachine, sURL, sTapeData);
|
||||
var resource = web.parseMemoryResource(sURL, sTapeData);
|
||||
if (resource) {
|
||||
this.parseTape(sTapeName, sTapePath, nTapeTarget, resource.aBytes, resource.addrLoad, resource.addrExec);
|
||||
}
|
||||
}
|
||||
this.flags.busy = false;
|
||||
if (this.cAutoMount) {
|
||||
this.cAutoMount--;
|
||||
if (!this.cAutoMount) this.setReady();
|
||||
}
|
||||
this.displayTape();
|
||||
};
|
||||
|
||||
/**
|
||||
* doneRead(sTapeName, sTapePath, nTapeTarget, buffer)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {string} sTapeName
|
||||
* @param {string} sTapePath
|
||||
* @param {number} nTapeTarget
|
||||
* @param {?} buffer (we KNOW this is an ArrayBuffer, but we can't seem to convince the Closure Compiler)
|
||||
*/
|
||||
PC11.prototype.doneRead = function(sTapeName, sTapePath, nTapeTarget, buffer)
|
||||
{
|
||||
if (buffer) {
|
||||
var aBytes = new Uint8Array(buffer, 0, buffer.byteLength);
|
||||
this.parseTape(sTapeName, sTapePath, nTapeTarget, aBytes);
|
||||
this.sTapeSource = PC11.SOURCE.LOCAL;
|
||||
}
|
||||
this.displayTape();
|
||||
};
|
||||
|
||||
/**
|
||||
* addTape(sName, sPath, fTop)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {string} sName
|
||||
* @param {string} sPath
|
||||
* @param {boolean} [fTop] (default is bottom)
|
||||
*/
|
||||
PC11.prototype.addTape = function(sName, sPath, fTop)
|
||||
{
|
||||
var controlTapes = this.bindings["listTapes"];
|
||||
if (controlTapes && controlTapes.options) {
|
||||
for (var i = 0; i < controlTapes.options.length; i++) {
|
||||
if (controlTapes.options[i].value == sPath) return;
|
||||
}
|
||||
var controlOption = document.createElement("option");
|
||||
controlOption.text = sName;
|
||||
controlOption.value = sPath;
|
||||
if (fTop && controlTapes.childNodes[0]) {
|
||||
controlTapes.insertBefore(controlOption, controlTapes.childNodes[0]);
|
||||
} else {
|
||||
controlTapes.appendChild(controlOption);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* findTape(sPath)
|
||||
*
|
||||
* This is used to deal with mount requests (eg, autoMount) that supply a path without a name;
|
||||
* if we can find the path in the "listTapes" control, then we return the associated tape name.
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {string} sPath
|
||||
* @return {string|null}
|
||||
*/
|
||||
PC11.prototype.findTape = function(sPath)
|
||||
{
|
||||
var controlTapes = this.bindings["listTapes"];
|
||||
if (controlTapes && controlTapes.options) {
|
||||
for (var i = 0; i < controlTapes.options.length; i++) {
|
||||
var control = controlTapes.options[i];
|
||||
if (control.value == sPath) return control.text;
|
||||
}
|
||||
}
|
||||
return str.getBaseName(sPath, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* displayTape()
|
||||
*
|
||||
* @this {PC11}
|
||||
*/
|
||||
PC11.prototype.displayTape = function()
|
||||
{
|
||||
var controlTapes = this.bindings["listTapes"];
|
||||
if (controlTapes && controlTapes.options) {
|
||||
var sTargetPath = this.sTapeSource || this.sTapePath;
|
||||
for (var i = 0; i < controlTapes.options.length; i++) {
|
||||
if (controlTapes.options[i].value == sTargetPath) {
|
||||
if (controlTapes.selectedIndex != i) {
|
||||
controlTapes.selectedIndex = i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == controlTapes.options.length) controlTapes.selectedIndex = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* displayProgress(nPercent)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {number} nPercent
|
||||
*/
|
||||
PC11.prototype.displayProgress = function(nPercent)
|
||||
{
|
||||
nPercent |= 0;
|
||||
if (nPercent !== this.nLastPercent) {
|
||||
var control = this.bindings[PC11.BINDING.READ_PROGRESS];
|
||||
if (control) {
|
||||
var aeControls = Component.getElementsByClass(control, PC11.CSSCLASS.PROGRESS_BAR);
|
||||
var controlBar = aeControls && aeControls[0];
|
||||
if (controlBar && controlBar.style) {
|
||||
controlBar.style.width = nPercent + "%";
|
||||
}
|
||||
}
|
||||
this.nLastPercent = nPercent;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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, addrLoad, addrExec)
|
||||
{
|
||||
this.sTapeName = sTapeName;
|
||||
this.sTapePath = sTapePath;
|
||||
this.nTapeTarget = nTapeTarget;
|
||||
|
||||
if (nTapeTarget == PC11.TARGET.MEMORY) {
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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".
|
||||
*/
|
||||
if (!this.ram || !this.ram.loadImage(aBytes, addrLoad, addrExec)) {
|
||||
this.sTapeName = "";
|
||||
this.sTapePath = "";
|
||||
this.sTapeSource = PC11.SOURCE.NONE;
|
||||
this.nTapeTarget = PC11.TARGET.NONE;
|
||||
this.notice("No load address available for tape: " + sTapeName);
|
||||
return;
|
||||
}
|
||||
this.status("tape loaded: " + sTapeName);
|
||||
return;
|
||||
}
|
||||
this.iTapeData = 0;
|
||||
this.aTapeData = aBytes;
|
||||
this.status("tape attached: " + sTapeName);
|
||||
this.displayProgress(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* unloadTape(fLoading)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {boolean} [fLoading]
|
||||
*/
|
||||
PC11.prototype.unloadTape = function(fLoading)
|
||||
{
|
||||
if (this.sTapePath || fLoading === false) {
|
||||
this.sTapeName = "";
|
||||
this.sTapePath = "";
|
||||
/*
|
||||
* Avoid any unnecessary hysteresis regarding the display if this unload is merely a prelude to another load.
|
||||
*/
|
||||
if (!fLoading) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -170,16 +757,72 @@ PC11.prototype.restore = function(data)
|
|||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* getBaudTimeout(nBaud)
|
||||
*
|
||||
* Based on the selected baud rate (nBaud), convert that rate into a millisecond delay.
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {number} nBaud
|
||||
* @return {number} (number of milliseconds per byte)
|
||||
*/
|
||||
PC11.prototype.getBaudTimeout = function(nBaud)
|
||||
{
|
||||
/*
|
||||
* TODO: Do a better job computing this, based on actual numbers of start, stop and parity bits,
|
||||
* instead of hard-coding the total number of bits per byte to 10.
|
||||
*/
|
||||
var nBytesPerSecond = Math.round(nBaud / 10);
|
||||
return 1000 / nBytesPerSecond;
|
||||
};
|
||||
|
||||
/**
|
||||
* advanceReader()
|
||||
*
|
||||
* If the reader is enabled (RE is set) and there is no exceptional condition (ie, ERROR is set),
|
||||
* and if the buffer register is empty (DONE is clear), then if we have more data in our internal buffer,
|
||||
* store it in the buffer register, and optionally trigger an interrupt if device interrupts are enabled.
|
||||
*
|
||||
* @this {PC11}
|
||||
*/
|
||||
PC11.prototype.advanceReader = function()
|
||||
{
|
||||
if ((this.prs & (PDP11.PC11.PRS.RE | PDP11.PC11.PRS.ERROR)) == PDP11.PC11.PRS.RE) {
|
||||
if (!(this.prs & PDP11.PC11.PRS.DONE)) {
|
||||
if (this.iTapeData < this.aTapeData.length) {
|
||||
/*
|
||||
* Here, as elsewhere (eg, the DL11 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 PRB with 0xff.
|
||||
*/
|
||||
this.prb = this.aTapeData[this.iTapeData++] & 0xff;
|
||||
this.displayProgress(this.iTapeData / this.aTapeData.length * 100);
|
||||
this.prs |= PDP11.PC11.PRS.DONE;
|
||||
this.prs &= ~PDP11.PC11.PRS.BUSY;
|
||||
if (this.prs & PDP11.PC11.PRS.RIE) {
|
||||
this.cpu.setTrigger(this.triggerReaderInterrupt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* readPRS(addr)
|
||||
*
|
||||
* NOTE: We use the PRS RMASK to honor the "write-only" behavior of bit 0, the reader enable bit (RE), because
|
||||
* DEC's tiny Bootstrap Loader (/apps/pdp11/boot/bootstrap/BOOTSTRAP-16KB.lst) repeatedly enables the reader using
|
||||
* the INC instruction, which causes the PRS to be read, incremented, and written, so if bit 0 isn't always read
|
||||
* as zero, the INC instruction would clear RE instead of setting it.
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {number} addr (eg, PDP11.UNIBUS.PRS or 177550)
|
||||
* @return {number}
|
||||
*/
|
||||
PC11.prototype.readPRS = function(addr)
|
||||
{
|
||||
return this.prs;
|
||||
return this.prs & PDP11.PC11.PRS.RMASK; // RMASK honors the "write-only" nature of the RE bit by returning zero on reads
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -192,14 +835,28 @@ PC11.prototype.readPRS = function(addr)
|
|||
PC11.prototype.writePRS = function(data, addr)
|
||||
{
|
||||
if (data & PDP11.PC11.PRS.RE) {
|
||||
/*
|
||||
* From the 1976 Peripherals Handbook, p. 4-378:
|
||||
*
|
||||
* Set [RE] to allow the Reader to fetch one character. The setting of this bit clears Done,
|
||||
* sets Busy, and clears the Reader Buffer (PRB). Operation of this bit is disabled if Error = 1;
|
||||
* attempting to set it when Error = 1 will cause an immediate interrupt if Interrupt Enable = 1.
|
||||
*/
|
||||
if (this.prs & PDP11.PC11.PRS.ERROR) {
|
||||
data &= ~PDP11.PC11.PRS.RE;
|
||||
// if (this.prs & PDP11.PC11.PRS.RIE) {
|
||||
// TODO: Generate an interrupt
|
||||
// }
|
||||
if (this.prs & PDP11.PC11.PRS.RIE) {
|
||||
this.cpu.setTrigger(this.triggerReaderInterrupt);
|
||||
}
|
||||
} else {
|
||||
this.prs &= ~PDP11.PC11.PRS.DONE;
|
||||
this.prs |= PDP11.PC11.PRS.BUSY;
|
||||
this.prb = 0;
|
||||
/*
|
||||
* The PC11, by virtue of its "high speed", is supposed to deliver characters at 300 CPS, so
|
||||
* that's the rate we'll choose as well (ie, 1000ms / 300). As an aside, the original "low speed"
|
||||
* version of the reader ran at 10 CPS.
|
||||
*/
|
||||
this.cpu.setTimer(this.timerReaderAdvance, this.getBaudTimeout(this.nBaudReceive));
|
||||
}
|
||||
}
|
||||
this.prs = (this.prs & ~PDP11.PC11.PRS.WMASK) | (data & PDP11.PC11.PRS.WMASK);
|
||||
|
|
@ -214,6 +871,12 @@ PC11.prototype.writePRS = function(data, addr)
|
|||
*/
|
||||
PC11.prototype.readPRB = function(addr)
|
||||
{
|
||||
/*
|
||||
* I'm guessing that the DONE and BUSY bits always remain more-or-less inverses of each other. They definitely
|
||||
* start out that way when writePRS() sets the reader enable (RE) bit, and so that's how we treat them elsewhere, too.
|
||||
*/
|
||||
this.prs &= ~PDP11.PC11.PRS.DONE;
|
||||
this.prs |= PDP11.PC11.PRS.BUSY;
|
||||
return this.prb;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -35,11 +35,11 @@
|
|||
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 State = require("../../shared/lib/state");
|
||||
var PDP11 = require("./defines");
|
||||
var MemoryPDP11 = require("./memory");
|
||||
var ROMPDP11 = require("./rom");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -49,6 +49,9 @@ if (NODE) {
|
|||
*
|
||||
* 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().
|
||||
|
|
@ -61,10 +64,37 @@ function RAMPDP11(parmsRAM)
|
|||
{
|
||||
Component.call(this, "RAM", parmsRAM, RAMPDP11);
|
||||
|
||||
this.abInit = null;
|
||||
this.aSymbols = null;
|
||||
|
||||
this.addrRAM = parmsRAM['addr'];
|
||||
this.sizeRAM = parmsRAM['size'];
|
||||
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;
|
||||
|
||||
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(sURL, sResponse, nErrorCode) {
|
||||
ram.doneLoad(sURL, sResponse, nErrorCode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Component.subclass(RAMPDP11);
|
||||
|
|
@ -86,24 +116,6 @@ RAMPDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
this.initRAM();
|
||||
};
|
||||
|
||||
/**
|
||||
* initRAM()
|
||||
*
|
||||
* @this {RAMPDP11}
|
||||
*/
|
||||
RAMPDP11.prototype.initRAM = function()
|
||||
{
|
||||
if (!this.fAllocated && this.sizeRAM) {
|
||||
if (this.bus.addMemory(this.addrRAM, this.sizeRAM, MemoryPDP11.TYPE.RAM)) {
|
||||
this.fAllocated = true;
|
||||
}
|
||||
}
|
||||
if (!this.fAllocated) {
|
||||
Component.error("No RAM allocated");
|
||||
}
|
||||
this.setReady();
|
||||
};
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
|
|
@ -114,6 +126,16 @@ RAMPDP11.prototype.initRAM = function()
|
|||
*/
|
||||
RAMPDP11.prototype.powerUp = function(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;
|
||||
}
|
||||
/*
|
||||
* The Computer powers up the CPU last, at which point CPUState state is restored,
|
||||
* which includes the Bus state, and since we use the Bus to allocate all our memory,
|
||||
|
|
@ -142,6 +164,73 @@ RAMPDP11.prototype.powerDown = function(fSave, fShutdown)
|
|||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* doneLoad(sURL, sData, nErrorCode)
|
||||
*
|
||||
* @this {RAMPDP11}
|
||||
* @param {string} sURL
|
||||
* @param {string} sData
|
||||
* @param {number} nErrorCode (response from server if anything other than 200)
|
||||
*/
|
||||
RAMPDP11.prototype.doneLoad = function(sURL, sData, nErrorCode)
|
||||
{
|
||||
if (nErrorCode) {
|
||||
this.notice("Unable to load RAM resource (error " + nErrorCode + ": " + sURL + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
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 doneLoad(), but it cannot copy the initial data into place
|
||||
* until after initBus() has received the Bus component AND doneLoad() has received the data. When both those
|
||||
* criteria are satisfied, the component becomes "ready".
|
||||
*
|
||||
* @this {RAMPDP11}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (!this.isReady()) {
|
||||
if (!this.fAllocated) {
|
||||
Component.error("No RAM allocated");
|
||||
}
|
||||
else if (this.sFilePath) {
|
||||
/*
|
||||
* Too early...
|
||||
*/
|
||||
if (!this.abInit || !this.bus) return;
|
||||
this.loadImage(this.abInit, this.addrLoad, this.addrExec, this.addrRAM);
|
||||
/*
|
||||
* NOTE: We now retain this data, so that reset() can return the RAM to its predefined state.
|
||||
*
|
||||
* delete this.abInit;
|
||||
*/
|
||||
}
|
||||
this.setReady();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
|
|
@ -149,9 +238,117 @@ RAMPDP11.prototype.powerDown = function(fSave, fShutdown)
|
|||
*/
|
||||
RAMPDP11.prototype.reset = function()
|
||||
{
|
||||
if (this.fAllocated) {
|
||||
this.bus.zeroMemory(this.addrRAM, this.sizeRAM);
|
||||
if (this.abInit) {
|
||||
this.loadImage(this.abInit, this.addrLoad, this.addrExec, this.addrRAM, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* loadImage(aBytes, addrLoad, addrExec, addrInit, fReset)
|
||||
*
|
||||
* @this {RAMPDP11}
|
||||
* @param {Array|Uint8Array} aBytes
|
||||
* @param {number|null} [addrLoad]
|
||||
* @param {number|null} [addrExec]
|
||||
* @param {number|null} [addrInit]
|
||||
* @param {boolean} [fReset]
|
||||
* @return {boolean} (true if loaded, false if not)
|
||||
*/
|
||||
RAMPDP11.prototype.loadImage = function(aBytes, addrLoad, addrExec, addrInit, fReset)
|
||||
{
|
||||
var fLoaded = false;
|
||||
/*
|
||||
* If you want to zero RAM on reset, then this would be a good place to do it.
|
||||
* 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 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) {
|
||||
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, fReset);
|
||||
}
|
||||
} 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, fReset);
|
||||
fLoaded = true;
|
||||
}
|
||||
}
|
||||
return fLoaded;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ if (NODE) {
|
|||
* size: amount of ROM, in bytes
|
||||
* alias: physical alias address (null if none)
|
||||
* file: name of ROM data file
|
||||
* writable: true to make ROM writable (default is false)
|
||||
*
|
||||
* 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 doneLoad()).
|
||||
|
|
@ -58,10 +57,6 @@ if (NODE) {
|
|||
* Also, while the size parameter may seem redundant, I consider it useful to confirm that the ROM you received
|
||||
* is the ROM you expected.
|
||||
*
|
||||
* Finally, while making ROM "writable" may seem a contradiction in terms, I want to be able to load selected
|
||||
* binary files into memory purely for testing purposes, and the RAM component has no "file" option, so the
|
||||
* simplest solution was to add the option to load binary files into memory as "writable" ROMs.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @param {Object} parmsROM
|
||||
|
|
@ -70,10 +65,11 @@ function ROMPDP11(parmsROM)
|
|||
{
|
||||
Component.call(this, "ROM", parmsROM, ROMPDP11);
|
||||
|
||||
this.abROM = null;
|
||||
this.abInit = null;
|
||||
this.aSymbols = null;
|
||||
|
||||
this.addrROM = parmsROM['addr'];
|
||||
this.sizeROM = parmsROM['size'];
|
||||
this.fWritable = parmsROM['writable'];
|
||||
|
||||
/*
|
||||
* The new 'alias' property can now be EITHER a single physical address (like 'addr') OR an array of
|
||||
|
|
@ -184,87 +180,28 @@ ROMPDP11.prototype.powerDown = function(fSave, fShutdown)
|
|||
};
|
||||
|
||||
/**
|
||||
* doneLoad(sURL, sROMData, nErrorCode)
|
||||
* doneLoad(sURL, sData, nErrorCode)
|
||||
*
|
||||
* @this {ROMPDP11}
|
||||
* @param {string} sURL
|
||||
* @param {string} sROMData
|
||||
* @param {string} sData
|
||||
* @param {number} nErrorCode (response from server if anything other than 200)
|
||||
*/
|
||||
ROMPDP11.prototype.doneLoad = function(sURL, sROMData, nErrorCode)
|
||||
ROMPDP11.prototype.doneLoad = function(sURL, sData, nErrorCode)
|
||||
{
|
||||
if (nErrorCode) {
|
||||
this.notice("Unable to load system ROM (error " + nErrorCode + ": " + sURL + ")");
|
||||
this.notice("Unable to load ROM resource (error " + nErrorCode + ": " + sURL + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
Component.addMachineResource(this.idMachine, sURL, sROMData);
|
||||
Component.addMachineResource(this.idMachine, sURL, sData);
|
||||
|
||||
var i;
|
||||
if (sROMData.charAt(0) == "[" || sROMData.charAt(0) == "{") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be here: parsing the JSON-encoded ROM.
|
||||
*/
|
||||
var a, ib;
|
||||
var rom = eval("(" + sROMData + ")");
|
||||
|
||||
if (a = rom['bytes']) {
|
||||
this.abROM = a;
|
||||
}
|
||||
else if (a = rom['words']) {
|
||||
/*
|
||||
* Convert all WORDs into BYTEs, so that subsequent code only has to deal with abROM.
|
||||
*/
|
||||
this.abROM = new Array(a.length * 2);
|
||||
for (i = 0, ib = 0; i < a.length; i++) {
|
||||
this.abROM[ib++] = a[i] & 0xff;
|
||||
this.abROM[ib++] = (a[i] >> 8) & 0xff;
|
||||
this.assert(!(a[i] & ~0xffff));
|
||||
}
|
||||
}
|
||||
else if (a = rom['data']) {
|
||||
/*
|
||||
* Convert all DWORDs into BYTEs, so that subsequent code only has to deal with abROM.
|
||||
*/
|
||||
this.abROM = new Array(a.length * 4);
|
||||
for (i = 0, ib = 0; i < a.length; i++) {
|
||||
this.abROM[ib++] = a[i] & 0xff;
|
||||
this.abROM[ib++] = (a[i] >> 8) & 0xff;
|
||||
this.abROM[ib++] = (a[i] >> 16) & 0xff;
|
||||
this.abROM[ib++] = (a[i] >> 24) & 0xff;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.abROM = rom;
|
||||
}
|
||||
|
||||
this.aSymbols = rom['symbols'];
|
||||
|
||||
if (!this.abROM.length) {
|
||||
Component.error("Empty ROM: " + sURL);
|
||||
return;
|
||||
}
|
||||
else if (this.abROM.length == 1) {
|
||||
Component.error(this.abROM[0]);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
this.notice("ROM data error: " + e.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Parse the ROM data manually; we assume it's in "simplified" hex form (a series of hex byte-values
|
||||
* separated by whitespace).
|
||||
*/
|
||||
var sHexData = sROMData.replace(/\n/gm, " ").replace(/ +$/, "");
|
||||
var asHexData = sHexData.split(" ");
|
||||
this.abROM = new Array(asHexData.length);
|
||||
for (i = 0; i < asHexData.length; i++) {
|
||||
this.abROM[i] = str.parseInt(asHexData[i], 16);
|
||||
}
|
||||
var resource = web.parseMemoryResource(sURL, sData);
|
||||
if (resource) {
|
||||
this.abInit = resource.aBytes;
|
||||
this.aSymbols = resource.aSymbols;
|
||||
} else {
|
||||
this.sFilePath = null;
|
||||
}
|
||||
this.initROM();
|
||||
};
|
||||
|
|
@ -272,33 +209,35 @@ ROMPDP11.prototype.doneLoad = function(sURL, sROMData, nErrorCode)
|
|||
/**
|
||||
* initROM()
|
||||
*
|
||||
* This function is called by both initBus() and doneLoad(), but it cannot copy the the ROM data into place
|
||||
* until after initBus() has received the Bus component AND doneLoad() has received the abROM data. When both
|
||||
* those criteria are satisfied, the component becomes "ready".
|
||||
* This function is called by both initBus() and doneLoad(), but it cannot copy the initial data into place
|
||||
* until after initBus() has received the Bus component AND doneLoad() has received the data. When both those
|
||||
* criteria are satisfied, the component becomes "ready".
|
||||
*
|
||||
* @this {ROMPDP11}
|
||||
*/
|
||||
ROMPDP11.prototype.initROM = function()
|
||||
{
|
||||
if (!this.isReady()) {
|
||||
if (!this.sFilePath) {
|
||||
this.setReady();
|
||||
}
|
||||
else if (this.abROM && this.bus) {
|
||||
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.abROM.length;
|
||||
this.sizeROM = this.abInit.length;
|
||||
}
|
||||
if (this.abROM.length != this.sizeROM) {
|
||||
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.abROM.length) + ") does not match specified size (" + str.toHexLong(this.sizeROM) + ")");
|
||||
this.setError("ROM size (" + str.toHexLong(this.abInit.length) + ") does not match specified size (" + str.toHexLong(this.sizeROM) + ")");
|
||||
}
|
||||
else if (this.addROM(this.addrROM)) {
|
||||
|
||||
|
|
@ -312,7 +251,7 @@ ROMPDP11.prototype.initROM = function()
|
|||
this.cloneROM(aliases[i]);
|
||||
}
|
||||
/*
|
||||
* We used to hang onto the original ROM data so that we could restore any bytes the CPU overwrote,
|
||||
* 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.
|
||||
*
|
||||
|
|
@ -321,10 +260,10 @@ ROMPDP11.prototype.initROM = function()
|
|||
* 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.
|
||||
*/
|
||||
delete this.abROM;
|
||||
delete this.abInit;
|
||||
}
|
||||
this.setReady();
|
||||
}
|
||||
this.setReady();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -337,11 +276,11 @@ ROMPDP11.prototype.initROM = function()
|
|||
*/
|
||||
ROMPDP11.prototype.addROM = function(addr)
|
||||
{
|
||||
if (this.bus.addMemory(addr, this.sizeROM, this.fWritable? MemoryPDP11.TYPE.RAM : MemoryPDP11.TYPE.ROM)) {
|
||||
if (DEBUG) this.log("addROM(): copying ROM to " + str.toHexLong(addr) + " (" + str.toHexLong(this.abROM.length) + " bytes)");
|
||||
if (this.bus.addMemory(addr, this.sizeROM, MemoryPDP11.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.abROM.length; i++) {
|
||||
this.bus.setByteDirect(addr + i, this.abROM[i]);
|
||||
for (i = 0; i < this.abInit.length; i++) {
|
||||
this.bus.setByteDirect(addr + i, this.abInit[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview Implements the PDP11 SerialPort component.
|
||||
* @fileoverview Implements the PDP-11 SerialPort component (eg, DL11)
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @copyright © Jeff Parsons 2012-2016
|
||||
*
|
||||
|
|
@ -46,19 +46,28 @@ if (NODE) {
|
|||
*
|
||||
* The SerialPort component has the following component-specific (parmsSerial) properties:
|
||||
*
|
||||
* adapter: adapter number; 0 if not defined
|
||||
* 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-11, this currently has no defined use)
|
||||
*
|
||||
* baudReceive: the default number of bits/second that the device should receive data at;
|
||||
* 0 means use the device default (PDP11.DL11.RCSR.BAUD)
|
||||
*
|
||||
* baudTransmit: the default number of bits/second that the device should transmit data at;
|
||||
* 0 means use the device default (PDP11.DL11.XCSR.BAUD)
|
||||
*
|
||||
* 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)
|
||||
*
|
||||
* In the future, we may support 'port' and 'irq' properties that allow the machine to define a
|
||||
* non-standard serial port configuration, instead of only our pre-defined 'adapter' configurations.
|
||||
* 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.
|
||||
*
|
||||
* NOTE: Since the XSL file defines 'adapter' as a number, not a string, there's no need to use
|
||||
* parseInt(), and as an added benefit, we don't need to worry about whether a hex or decimal format
|
||||
* was used.
|
||||
* NOTE: Since the XSL file defines the 'adapter' and 'baud' properties as numbers, not strings,
|
||||
* there's no need to use parseInt(), and as an added benefit, we don't need to worry about whether
|
||||
* a hex or decimal format was used.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
|
|
@ -67,6 +76,9 @@ if (NODE) {
|
|||
function SerialPortPDP11(parmsSerial) {
|
||||
|
||||
this.iAdapter = parmsSerial['adapter'];
|
||||
this.nBaudReceive = parmsSerial['baudReceive'] || PDP11.DL11.RCSR.BAUD;
|
||||
this.nBaudTransmit = parmsSerial['baudTransmit'] || PDP11.DL11.XCSR.BAUD;
|
||||
this.fUpperCase = parmsSerial['upperCase'];
|
||||
|
||||
/**
|
||||
* consoleOutput becomes a string that records serial port output if the 'binding' property is set to the
|
||||
|
|
@ -265,7 +277,23 @@ SerialPortPDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
this.timerReceiveInterrupt = this.cpu.addTimer(function readyReceiver() {
|
||||
if (!(serial.rcsr & PDP11.DL11.RCSR.RD)) {
|
||||
if (serial.abReceive.length) {
|
||||
serial.rbuf = serial.abReceive.shift();
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
serial.rbuf = serial.abReceive.shift() & 0xff;
|
||||
if (serial.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 (serial.rbuf >= 0x61 && serial.rbuf < 0x7A) {
|
||||
serial.rbuf -= 0x20;
|
||||
}
|
||||
}
|
||||
serial.rcsr |= PDP11.DL11.RCSR.RD;
|
||||
if (serial.rcsr & PDP11.DL11.RCSR.RIE) {
|
||||
serial.cpu.setTrigger(serial.triggerReceiveInterrupt);
|
||||
|
|
@ -447,6 +475,25 @@ SerialPortPDP11.prototype.saveRegisters = function()
|
|||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* getBaudTimeout(nBaud)
|
||||
*
|
||||
* Based on the selected baud rate (nBaud), convert that rate into a millisecond delay.
|
||||
*
|
||||
* @this {SerialPortPDP11}
|
||||
* @param {number} nBaud
|
||||
* @return {number} (number of milliseconds per byte)
|
||||
*/
|
||||
SerialPortPDP11.prototype.getBaudTimeout = function(nBaud)
|
||||
{
|
||||
/*
|
||||
* TODO: Do a better job computing this, based on actual numbers of start, stop and parity bits,
|
||||
* instead of hard-coding the total number of bits per byte to 10.
|
||||
*/
|
||||
var nBytesPerSecond = Math.round(nBaud / 10);
|
||||
return 1000 / nBytesPerSecond;
|
||||
};
|
||||
|
||||
/**
|
||||
* receiveData(data)
|
||||
*
|
||||
|
|
@ -471,14 +518,10 @@ SerialPortPDP11.prototype.receiveData = function(data)
|
|||
else {
|
||||
this.abReceive = this.abReceive.concat(data);
|
||||
}
|
||||
this.cpu.setTimer(this.timerReceiveInterrupt, PDP11.DL11.RBUF.DELAY);
|
||||
this.cpu.setTimer(this.timerReceiveInterrupt, this.getBaudTimeout(this.nBaudReceive));
|
||||
return true; // for now, return true regardless, since we're buffering everything anyway
|
||||
};
|
||||
|
||||
SerialPortPDP11.prototype.advanceRBUF = function()
|
||||
{
|
||||
};
|
||||
|
||||
/**
|
||||
* transmitByte(b)
|
||||
*
|
||||
|
|
@ -574,7 +617,7 @@ SerialPortPDP11.prototype.readRBUF = function(addr)
|
|||
{
|
||||
this.rcsr &= ~PDP11.DL11.RCSR.RD;
|
||||
if (this.abReceive.length > 0) {
|
||||
this.cpu.setTimer(this.timerReceiveInterrupt, PDP11.DL11.RBUF.DELAY);
|
||||
this.cpu.setTimer(this.timerReceiveInterrupt, this.getBaudTimeout(this.nBaudReceive));
|
||||
}
|
||||
return this.rbuf;
|
||||
};
|
||||
|
|
@ -615,7 +658,7 @@ SerialPortPDP11.prototype.writeXCSR = function(data, addr)
|
|||
* If the device is READY, and IE is transitioning on, then request an interrupt.
|
||||
*/
|
||||
if ((this.xcsr & (PDP11.DL11.XCSR.READY | PDP11.DL11.XCSR.TIE)) == PDP11.DL11.XCSR.READY && (data & PDP11.DL11.XCSR.TIE)) {
|
||||
this.cpu.setTimer(this.timerTransmitInterrupt, PDP11.DL11.XCSR.DELAY);
|
||||
this.cpu.setTimer(this.timerTransmitInterrupt, this.getBaudTimeout(this.nBaudTransmit));
|
||||
}
|
||||
this.xcsr = (this.xcsr & ~PDP11.DL11.XCSR.WMASK) | (data & PDP11.DL11.XCSR.WMASK);
|
||||
};
|
||||
|
|
@ -645,7 +688,7 @@ SerialPortPDP11.prototype.writeXBUF = function(data, addr)
|
|||
if (data) {
|
||||
this.transmitByte(data);
|
||||
this.xcsr &= ~PDP11.DL11.XCSR.READY;
|
||||
this.cpu.setTimer(this.timerTransmitInterrupt, PDP11.DL11.XBUF.DELAY);
|
||||
this.cpu.setTimer(this.timerTransmitInterrupt, this.getBaudTimeout(this.nBaudTransmit));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -283,6 +283,129 @@ web.getResource = function(sURL, dataPost, fAsync, done)
|
|||
return response;
|
||||
};
|
||||
|
||||
/**
|
||||
* parseMemoryResource(sURL, sData)
|
||||
*
|
||||
* @param {string} sData
|
||||
* @return {Object|null} (resource)
|
||||
*/
|
||||
web.parseMemoryResource = function(sURL, sData)
|
||||
{
|
||||
var i;
|
||||
var resource = {
|
||||
aBytes: null,
|
||||
aSymbols: null,
|
||||
addrLoad: null,
|
||||
addrExec: null
|
||||
};
|
||||
|
||||
if (sData.charAt(0) == "[" || sData.charAt(0) == "{") {
|
||||
try {
|
||||
var a, ib, data;
|
||||
|
||||
if (sData.substr(0, 1) == "<") { // if the "data" begins with a "<"...
|
||||
/*
|
||||
* Early server configs reported an error (via the nErrorCode parameter) if a tape URL was invalid,
|
||||
* but more recent server configs now display a somewhat friendlier HTML error page. The downside,
|
||||
* however, is that the original error has been buried, and we've received "data" that isn't actually
|
||||
* tape data. So if the data we've received appears to be "HTML-like", we treat it as an error message.
|
||||
*/
|
||||
throw new Error(sData);
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to
|
||||
* eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in
|
||||
* IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks
|
||||
* (perhaps one track per chunk, using regular expressions) and then manually re-assemble it.
|
||||
*
|
||||
* However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")")
|
||||
* is a much easier fix. The only drawback is that we must first quote any unquoted property names
|
||||
* and remove any comments, because while eval() was cool with them, JSON.parse() is more particular;
|
||||
* the following RegExp replacements take care of those requirements.
|
||||
*
|
||||
* The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and
|
||||
* while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified),
|
||||
* I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement
|
||||
* for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect
|
||||
* any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so:
|
||||
*
|
||||
* ["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, ""));
|
||||
} else {
|
||||
data = eval("(" + sData + ")");
|
||||
}
|
||||
|
||||
resource.addrLoad = data['load'];
|
||||
resource.addrExec = data['exec'];
|
||||
|
||||
if (a = data['bytes']) {
|
||||
resource.aBytes = a;
|
||||
}
|
||||
else if (a = data['words']) {
|
||||
/*
|
||||
* Convert all words into bytes
|
||||
*/
|
||||
resource.aBytes = new Array(a.length * 2);
|
||||
for (i = 0, ib = 0; i < a.length; i++) {
|
||||
resource.aBytes[ib++] = a[i] & 0xff;
|
||||
resource.aBytes[ib++] = (a[i] >> 8) & 0xff;
|
||||
Component.assert(!(a[i] & ~0xffff));
|
||||
}
|
||||
}
|
||||
else if (a = data['data']) {
|
||||
/*
|
||||
* Convert all dwords (longs) into bytes
|
||||
*/
|
||||
resource.aBytes = new Array(a.length * 4);
|
||||
for (i = 0, ib = 0; i < a.length; i++) {
|
||||
resource.aBytes[ib++] = a[i] & 0xff;
|
||||
resource.aBytes[ib++] = (a[i] >> 8) & 0xff;
|
||||
resource.aBytes[ib++] = (a[i] >> 16) & 0xff;
|
||||
resource.aBytes[ib++] = (a[i] >> 24) & 0xff;
|
||||
}
|
||||
}
|
||||
else {
|
||||
resource.aBytes = data;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Parse the data manually; we assume it's a series of hex byte-values separated by whitespace.
|
||||
*/
|
||||
var ab = [];
|
||||
var sHexData = sData.replace(/\n/gm, " ").replace(/ +$/, "");
|
||||
var asHexData = sHexData.split(" ");
|
||||
for (i = 0; i < asHexData.length; i++) {
|
||||
var n = parseInt(asHexData[i], 16);
|
||||
if (isNaN(n)) {
|
||||
Component.error("Resource data error (" + sURL + "): invalid hex byte (" + asHexData[i] + ")");
|
||||
break;
|
||||
}
|
||||
ab.push(n & 0xff);
|
||||
}
|
||||
if (i == asHexData.length) resource.aBytes = ab;
|
||||
}
|
||||
return resource;
|
||||
};
|
||||
|
||||
/**
|
||||
* sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
<div class="common-top-left">
|
||||
<ul>
|
||||
<li><a href="/">PCjs</a></li>
|
||||
<li><a href="/apps/pcx86/">Demos</a></li>
|
||||
<li><a href="/apps/">Apps</a></li>
|
||||
<li><a href="/devices/">Devices</a></li>
|
||||
<li><a href="/disks/">Disks</a></li>
|
||||
<li><a href="/docs/">Docs</a></li>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
<div class="common-top-left">
|
||||
<ul>
|
||||
<li><a href="/">PCjs</a></li>
|
||||
<li><a href="/apps/pcx86/">Demos</a></li>
|
||||
<li><a href="/apps/">Apps</a></li>
|
||||
<li><a href="/devices/">Devices</a></li>
|
||||
<li><a href="/disks/">Disks</a></li>
|
||||
<li><a href="/docs/">Docs</a></li>
|
||||
|
|
|
|||
|
|
@ -55,6 +55,30 @@
|
|||
line-height: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.pcjs-progress {
|
||||
height: 20px;
|
||||
width: 300px;
|
||||
margin-top: 8px;
|
||||
border: 1px solid black;
|
||||
position: relative;
|
||||
}
|
||||
.pcjs-progress-bar {
|
||||
height: 20px;
|
||||
width: 0%; /* this will be updated pragmatically */
|
||||
background-color: gold;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
}
|
||||
.pcjs-progress-text {
|
||||
height: 20px;
|
||||
width: 300px;
|
||||
font-size: small;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
z-index: 1;
|
||||
}
|
||||
.pcjs-register {
|
||||
font-family: Monaco, "Lucida Console", monospace;
|
||||
font-size: small;
|
||||
|
|
@ -90,7 +114,7 @@
|
|||
}
|
||||
.pcjs-description, .pcjs-status {
|
||||
font-size: x-small;
|
||||
line-height: 2em;
|
||||
line-height: 2.8em;
|
||||
}
|
||||
.pcjs-key {
|
||||
border: 1px solid black;
|
||||
|
|
@ -175,4 +199,8 @@
|
|||
.pcjs-registers {
|
||||
width: 100% !important;
|
||||
}
|
||||
.pdp11-device {
|
||||
width: 100% !important;
|
||||
max-width: none !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,14 @@
|
|||
|
||||
<xsl:template name="componentScripts">
|
||||
<xsl:param name="component"/>
|
||||
<script type="text/javascript" src="/versions/{$APPCLASS}/{$APPVERSION}/{$component}.js"> </script>
|
||||
<xsl:choose>
|
||||
<xsl:when test="$APPNAME = 'PDPjs'">
|
||||
<script type="text/javascript" src="/versions/pdpjs/{$APPVERSION}/{$component}.js"> </script>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<script type="text/javascript" src="/versions/{$APPCLASS}/{$APPVERSION}/{$component}.js"> </script>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="componentIncludes">
|
||||
|
|
@ -182,10 +189,10 @@
|
|||
<xsl:choose>
|
||||
<xsl:when test="@padding">padding:<xsl:value-of select="@padding"/>;</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:if test="@padtop">padding-top:<xsl:value-of select="@padtop"/>;</xsl:if>
|
||||
<xsl:if test="@padright">padding-right:<xsl:value-of select="@padright"/>;</xsl:if>
|
||||
<xsl:if test="@padbottom">padding-bottom:<xsl:value-of select="@padbottom"/>;</xsl:if>
|
||||
<xsl:if test="@padleft">padding-left:<xsl:value-of select="@padleft"/>;</xsl:if>
|
||||
<xsl:if test="@padTop">padding-top:<xsl:value-of select="@padTop"/>;</xsl:if>
|
||||
<xsl:if test="@padRight">padding-right:<xsl:value-of select="@padRight"/>;</xsl:if>
|
||||
<xsl:if test="@padBottom">padding-bottom:<xsl:value-of select="@padBottom"/>;</xsl:if>
|
||||
<xsl:if test="@padLeft">padding-left:<xsl:value-of select="@padLeft"/>;</xsl:if>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
|
|
@ -318,10 +325,10 @@
|
|||
<xsl:choose>
|
||||
<xsl:when test="@padding">padding:<xsl:value-of select="@padding"/>;</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:if test="@padtop">padding-top:<xsl:value-of select="@padtop"/>;</xsl:if>
|
||||
<xsl:if test="@padright">padding-right:<xsl:value-of select="@padright"/>;</xsl:if>
|
||||
<xsl:if test="@padbottom">padding-bottom:<xsl:value-of select="@padbottom"/>;</xsl:if>
|
||||
<xsl:if test="@padleft">padding-left:<xsl:value-of select="@padleft"/>;</xsl:if>
|
||||
<xsl:if test="@padTop">padding-top:<xsl:value-of select="@padTop"/>;</xsl:if>
|
||||
<xsl:if test="@padRight">padding-right:<xsl:value-of select="@padRight"/>;</xsl:if>
|
||||
<xsl:if test="@padBottom">padding-bottom:<xsl:value-of select="@padBottom"/>;</xsl:if>
|
||||
<xsl:if test="@padLeft">padding-left:<xsl:value-of select="@padLeft"/>;</xsl:if>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
|
|
@ -330,6 +337,7 @@
|
|||
<xsl:when test="@pos = 'left'">float:left;</xsl:when>
|
||||
<xsl:when test="@pos = 'right'">float:right;</xsl:when>
|
||||
<xsl:when test="@pos = 'center'">margin:0 auto;</xsl:when>
|
||||
<xsl:when test="@pos = 'default'">clear:both;</xsl:when>
|
||||
<xsl:when test="@pos">position:<xsl:value-of select="@pos"/>;</xsl:when>
|
||||
<xsl:when test="$left != '' or $top != ''">position:relative;</xsl:when>
|
||||
<xsl:when test="@container">text-align:<xsl:value-of select="@container"/>;</xsl:when>
|
||||
|
|
@ -390,7 +398,7 @@
|
|||
</xsl:when>
|
||||
<xsl:when test="@type = 'list'">
|
||||
<select class="{$APPCLASS}-binding" style="{$border}{$width}{$height}{$fontsize}{$style}" data-value="{{{$type},{$binding}}}">
|
||||
<xsl:apply-templates select="disk|app|manifest" mode="component"/>
|
||||
<xsl:apply-templates select="disk|tape|app|manifest" mode="component"/>
|
||||
</select>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'text'">
|
||||
|
|
@ -416,6 +424,12 @@
|
|||
<xsl:when test="@type = 'led' or @type = 'rled'">
|
||||
<div class="{$APPCLASS}-binding {$CSSCLASS}-{@type}" data-value="{{{$type},{$binding}}}" style="display:inline-block;"><xsl:value-of select="."/></div>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'progress'">
|
||||
<div class="{$APPCLASS}-binding {$CSSCLASS}-{@type}" style="-webkit-user-select:none;{$border}{$width}{$height}{$fontsize}{$style}" data-value="{{{$type},{$binding},{$value}}}">
|
||||
<div class="{$CSSCLASS}-{@type}-text" style="{$width}"><xsl:apply-templates/></div>
|
||||
<div class="{$CSSCLASS}-{@type}-bar"></div>
|
||||
</div>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'separator'">
|
||||
<hr/>
|
||||
</xsl:when>
|
||||
|
|
@ -452,7 +466,38 @@
|
|||
</xsl:if>
|
||||
</xsl:if>
|
||||
</xsl:variable>
|
||||
<option value="{@path}" data-value="{{{$desc}}}"><xsl:if test="name"><xsl:value-of select="name"/></xsl:if><xsl:if test="not(name)"><xsl:value-of select="."/></xsl:if></option>
|
||||
<xsl:variable name="name">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@name"><xsl:value-of select="@name"/></xsl:when>
|
||||
<xsl:when test="name"><xsl:value-of select="name"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="."/></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<option value="{@path}" data-value="{{{$desc}}}"><xsl:value-of select="$name"/></option>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="tape[@ref]" mode="component">
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/tape" mode="component"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="tape[not(@ref)]" mode="component">
|
||||
<xsl:variable name="desc">
|
||||
<xsl:if test="@desc">
|
||||
<xsl:text>desc:'</xsl:text><xsl:value-of select="@desc"/><xsl:text>'</xsl:text>
|
||||
<xsl:if test="@href">
|
||||
<xsl:text>,href:'</xsl:text><xsl:value-of select="@href"/><xsl:text>'</xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="name">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@name"><xsl:value-of select="@name"/></xsl:when>
|
||||
<xsl:when test="name"><xsl:value-of select="name"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="."/></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<option value="{@path}" data-value="{{{$desc}}}"><xsl:value-of select="$name"/></option>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="app[@ref]" mode="component">
|
||||
|
|
@ -582,9 +627,9 @@
|
|||
<xsl:otherwise>null</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="resetAddr">
|
||||
<xsl:variable name="addrReset">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@resetAddr"><xsl:value-of select="@resetAddr"/></xsl:when>
|
||||
<xsl:when test="@addrReset"><xsl:value-of select="@addrReset"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
|
|
@ -612,7 +657,7 @@
|
|||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class" select="'cpu'"/>
|
||||
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>',stepping:'<xsl:value-of select="$stepping"/>',fpu:<xsl:value-of select="$fpu"/>,cycles:<xsl:value-of select="$cycles"/>,multiplier:<xsl:value-of select="$multiplier"/>,autoStart:<xsl:value-of select="$autoStart"/>,resetAddr:<xsl:value-of select="$resetAddr"/>,csStart:<xsl:value-of select="$csStart"/>,csInterval:<xsl:value-of select="$csInterval"/>,csStop:<xsl:value-of select="$csStop"/></xsl:with-param>
|
||||
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>',stepping:'<xsl:value-of select="$stepping"/>',fpu:<xsl:value-of select="$fpu"/>,cycles:<xsl:value-of select="$cycles"/>,multiplier:<xsl:value-of select="$multiplier"/>,autoStart:<xsl:value-of select="$autoStart"/>,addrReset:<xsl:value-of select="$addrReset"/>,csStart:<xsl:value-of select="$csStart"/>,csInterval:<xsl:value-of select="$csInterval"/>,csStop:<xsl:value-of select="$csStop"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
|
|
@ -709,21 +754,44 @@
|
|||
<xsl:template match="device[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/device"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
<xsl:apply-templates select="document($componentFile)/device">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="mount" select="@automount"/>
|
||||
</xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="device[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:param name="mount" select="''"/>
|
||||
<xsl:variable name="type">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@type"><xsl:value-of select="@type"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="baudReceive">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@baudReceive"><xsl:value-of select="@baudReceive"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="baudTransmit">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@baudTransmit"><xsl:value-of select="@baudTransmit"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="autoMount">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$mount != ''"><xsl:value-of select="$mount"/></xsl:when>
|
||||
<xsl:when test="@automount"><xsl:value-of select="@automount"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="@autoMount"/></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">device</xsl:with-param>
|
||||
<xsl:with-param name="parms">,type:'<xsl:value-of select="$type"/>'</xsl:with-param>
|
||||
<xsl:with-param name="parms">,type:'<xsl:value-of select="$type"/>',baudReceive:<xsl:value-of select="$baudReceive"/>,baudTransmit:<xsl:value-of select="$baudTransmit"/>,autoMount:'<xsl:value-of select="$autoMount"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
|
|
@ -789,6 +857,18 @@
|
|||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="baudReceive">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@baudReceive"><xsl:value-of select="@baudReceive"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="baudTransmit">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@baudTransmit"><xsl:value-of select="@baudTransmit"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="binding">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@binding"><xsl:value-of select="@binding"/></xsl:when>
|
||||
|
|
@ -809,10 +889,17 @@
|
|||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="upperCase">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@uppercase"><xsl:value-of select="@uppercase"/></xsl:when>
|
||||
<xsl:when test="@upperCase"><xsl:value-of select="@upperCase"/></xsl:when>
|
||||
<xsl:otherwise>false</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">serial</xsl:with-param>
|
||||
<xsl:with-param name="parms">,adapter:<xsl:value-of select="$adapter"/>,binding:'<xsl:value-of select="$binding"/>',tabSize:<xsl:value-of select="$tabSize"/>,charBOL:<xsl:value-of select="$charBOL"/></xsl:with-param>
|
||||
<xsl:with-param name="parms">,adapter:<xsl:value-of select="$adapter"/>,baudReceive:<xsl:value-of select="$baudReceive"/>,baudTransmit:<xsl:value-of select="$baudTransmit"/>,binding:'<xsl:value-of select="$binding"/>',tabSize:<xsl:value-of select="$tabSize"/>,charBOL:<xsl:value-of select="$charBOL"/>,upperCase:<xsl:value-of select="$upperCase"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
|
|
@ -849,10 +936,11 @@
|
|||
<xsl:template match="fdc[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:param name="mount" select="''"/>
|
||||
<xsl:variable name="automount">
|
||||
<xsl:variable name="autoMount">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$mount != ''"><xsl:value-of select="$mount"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="@automount"/></xsl:otherwise>
|
||||
<xsl:when test="@automount"><xsl:value-of select="@automount"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="@autoMount"/></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="sortBy">
|
||||
|
|
@ -864,7 +952,7 @@
|
|||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">fdc</xsl:with-param>
|
||||
<xsl:with-param name="parms">,autoMount:'<xsl:value-of select="$automount"/>',sortBy:'<xsl:value-of select="$sortBy"/>'</xsl:with-param>
|
||||
<xsl:with-param name="parms">,autoMount:'<xsl:value-of select="$autoMount"/>',sortBy:'<xsl:value-of select="$sortBy"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
|
|
@ -933,16 +1021,10 @@
|
|||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="writable">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@writable"><xsl:value-of select="@writable"/></xsl:when>
|
||||
<xsl:otherwise>false</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">rom</xsl:with-param>
|
||||
<xsl:with-param name="parms">,addr:<xsl:value-of select="$addr"/>,size:<xsl:value-of select="$size"/>,alias:<xsl:value-of select="$alias"/>,file:'<xsl:value-of select="$file"/>',notify:'<xsl:value-of select="$notify"/>',writable:<xsl:value-of select="$writable"/></xsl:with-param>
|
||||
<xsl:with-param name="parms">,addr:<xsl:value-of select="$addr"/>,size:<xsl:value-of select="$size"/>,alias:<xsl:value-of select="$alias"/>,file:'<xsl:value-of select="$file"/>',notify:'<xsl:value-of select="$notify"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
|
|
@ -966,6 +1048,24 @@
|
|||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="file">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@file"><xsl:value-of select="@file"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="load">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@load"><xsl:value-of select="@load"/></xsl:when>
|
||||
<xsl:otherwise>null</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="exec">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@exec"><xsl:value-of select="@exec"/></xsl:when>
|
||||
<xsl:otherwise>null</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="test">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@test"><xsl:value-of select="@test"/></xsl:when>
|
||||
|
|
@ -975,7 +1075,7 @@
|
|||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">ram</xsl:with-param>
|
||||
<xsl:with-param name="parms">,addr:<xsl:value-of select="$addr"/>,size:<xsl:value-of select="$size"/>,test:<xsl:value-of select="$test"/></xsl:with-param>
|
||||
<xsl:with-param name="parms">,addr:<xsl:value-of select="$addr"/>,size:<xsl:value-of select="$size"/>,file:'<xsl:value-of select="$file"/>',load:<xsl:value-of select="$load"/>,exec:<xsl:value-of select="$exec"/>,test:<xsl:value-of select="$test"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@
|
|||
<xsl:output doctype-system="about:legacy-compat" method="html"/>
|
||||
|
||||
<xsl:include href="common.xsl"/>
|
||||
<!-- There is no "shared" components.xsl, so we just pick one to eliminate IDE inspection warnings -->
|
||||
<xsl:include href="../../pcjs/templates/components.xsl"/>
|
||||
<xsl:include href="../../shared/templates/components.xsl"/>
|
||||
|
||||
<xsl:template match="/machine">
|
||||
<html lang="en">
|
||||
|
|
|
|||
Loading…
Reference in a new issue