Updated PC8080 and PDPjs to support RAM images, eliminating the need for the old "writable ROM" kludge; RAM images can include their own load and exec addresses as part of the JSON file, but the RAM component can provide explicit overrides (if no load address is specified either way, the default load address is the starting RAM address)

This commit is contained in:
Jeff Parsons 2016-10-17 11:01:32 -07:00 committed by Jeff Parsons
commit 980c2ac5ab
64 changed files with 1479 additions and 1154 deletions

View file

@ -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.nFileLoad = parmsRAM['load'];
this.nFileExec = 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.nFileLoad == null && resource.nLoad != null) this.nFileLoad = resource.nLoad;
if (this.nFileExec == null && resource.nExec != null) this.nFileExec = resource.nExec;
} 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.nFileLoad !== null) addr = this.nFileLoad;
for (var i = 0; i < this.abInit.length; i++) {
this.bus.setByteDirect(addr + i, this.abInit[i]);
}
if (this.nFileExec !== null) {
/*
* Here's where we enable our "Fake CP/M" support, triggered by the user loading a "writable" ROM image
* at offset 0x100. Fake CP/M support works by installing HLT opcodes at well-known CP/M addresses
* (namely, 0x0000, which is the CP/M reset vector, and 0x0005, which is the CP/M system call vector) and
* then telling the CPU to call us whenever a HLT occurs, so we can check PC for one of these addresses.
*/
if (this.nFileExec == RAM8080.CPM.INIT) {
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.nFileExec);
}
/*
* 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;
}
};
/**

View file

@ -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,7 @@ 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 +103,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 +317,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 +331,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)
*

View file

@ -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,18 @@ CPUStatePDP11.prototype.setMMR3 = function(newMMR3)
}
};
/**
* setReset(addr)
*
* @this {CPUStatePDP11}
* @param {number} addr
*/
CPUStatePDP11.prototype.setReset = function(addr)
{
this.addrReset = addr;
this.setPC(addr);
};
/**
* getChecksum()
*

View file

@ -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.nFileLoad = parmsRAM['load'];
this.nFileExec = 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)
*
@ -142,6 +154,80 @@ 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.nFileLoad == null && resource.nLoad != null) this.nFileLoad = resource.nLoad;
if (this.nFileExec == null && resource.nExec != null) this.nFileExec = resource.nExec;
} 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.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;
var addr = this.addrRAM;
if (this.nFileLoad !== null) addr = this.nFileLoad;
for (var i = 0; i < this.abInit.length; i++) {
this.bus.setByteDirect(addr + i, this.abInit[i]);
}
if (this.nFileExec !== null) {
this.cpu.setReset(this.nFileExec);
}
/*
* TODO: Consider an option to retain this data and give the user a way of restoring the initial contents.
*/
delete this.abInit;
}
this.setReady();
}
};
/**
* reset()
*

View file

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

View file

@ -283,6 +283,90 @@ 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,
nLoad: null,
nExec: null
};
if (sData.charAt(0) == "[" || sData.charAt(0) == "{") {
try {
var a, ib;
var data = eval("(" + sData + ")");
resource.nLoad = data['load'];
resource.nExec = 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: " + e.message);
resource = null;
}
}
else {
/*
* Parse the data manually; we'll assume it's in "simplified" hex form
* (a series of hex byte-values separated by whitespace).
*/
var sHexData = sData.replace(/\n/gm, " ").replace(/ +$/, "");
var asHexData = sHexData.split(" ");
resource.aBytes = new Array(asHexData.length);
for (i = 0; i < asHexData.length; i++) {
resource.aBytes[i] = parseInt(asHexData[i], 16);
Component.assert(!isNaN(resource.aBytes[i]));
}
}
return resource;
};
/**
* sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName)
*

View file

@ -582,9 +582,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 +612,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>
@ -933,16 +933,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 +960,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 +987,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>