diff --git a/index.html b/index.html
index e33b65ba..182b6245 100644
--- a/index.html
+++ b/index.html
@@ -99,6 +99,10 @@ div.mem_info {
font-family: "Andale Mono", "Menlo", "Lucida Console", monospace;
font-size: 12pt;
}
+.seg_code { color: #ff9966; }
+.seg_data { color: #66ff66; }
+.seg_stack { color: #ffff66; }
+.seg_unknown { color: #cccccc; }
span.hilite {
color: #ff66ff;
}
diff --git a/src/emu.js b/src/emu.js
index 0522cbfc..4d02f2f6 100644
--- a/src/emu.js
+++ b/src/emu.js
@@ -441,21 +441,32 @@ function cpuStateToLongString_Z80(c) {
var BaseZ80Platform = function() {
+ var _cpu;
+ var probe;
+
window.buildZ80({
applyContention: false // TODO???
});
- // TODO: refactor w/ platforms
this.newCPU = function(membus, iobus) {
- return window.Z80({
+ probe = new BusProbe(membus);
+ _cpu = window.Z80({
display: {},
- memory: membus,
+ memory: probe,
ioBus: iobus
});
+ return _cpu;
}
+ this.getProbe = function() { return probe; }
+ this.getPC = function() { return _cpu.getPC(); }
+ this.getSP = function() { return _cpu.getSP(); }
+
// TODO: refactor other parts into here
this.runCPU = function(cpu, cycles) {
+ _cpu = cpu; // TODO?
+ if (this.wasBreakpointHit())
+ return 0;
var debugCond = this.getDebugCallback();
var targetTstates = cpu.getTstates() + cycles;
if (debugCond) { // || trace) {
diff --git a/src/platform/galaxian.js b/src/platform/galaxian.js
index cf98249b..9c826d93 100644
--- a/src/platform/galaxian.js
+++ b/src/platform/galaxian.js
@@ -264,8 +264,8 @@ var GalaxianPlatform = function(mainElement, options) {
isContended: function() { return false; },
};
}
- this.readMemory = function(a) {
- return (a == 0x7000 || a == 0x7800) ? null : membus.read; // ignore watchdog
+ this.readAddress = function(a) {
+ return (a == 0x7000 || a == 0x7800) ? null : membus.read(a); // ignore watchdog
};
audio = new MasterAudio();
psg1 = new AY38910_Audio(audio);
@@ -387,9 +387,6 @@ var GalaxianPlatform = function(mainElement, options) {
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
watchdog_counter = INITIAL_WATCHDOG;
}
- this.readAddress = function(addr) {
- return membus.read(addr); // TODO?
- }
}
var GalaxianScramblePlatform = function(mainElement) {
diff --git a/src/platform/mw8080bw.js b/src/platform/mw8080bw.js
index 3d6d1844..aa24eed7 100644
--- a/src/platform/mw8080bw.js
+++ b/src/platform/mw8080bw.js
@@ -13,6 +13,7 @@ var Midway8080BWPlatform = function(mainElement) {
this.__proto__ = new BaseZ80Platform();
var cpu, ram, membus, iobus, rom;
+ var probe;
var video, timer, pixels, displayPCs;
var inputs = [0xe,0x8,0x0];
var bitshift_offset = 0;
@@ -60,7 +61,7 @@ var Midway8080BWPlatform = function(mainElement) {
]),
isContended: function() { return false; },
};
- this.readMemory = membus.read;
+ this.readAddress = membus.read;
iobus = {
read: function(addr) {
addr &= 0x3;
@@ -96,11 +97,7 @@ var Midway8080BWPlatform = function(mainElement) {
}
}
};
- cpu = window.Z80({
- display: {},
- memory: membus,
- ioBus: iobus
- });
+ cpu = this.newCPU(membus, iobus);
video = new RasterVideo(mainElement,256,224,{rotate:-90});
video.create();
$(video.canvas).click(function(e) {
@@ -191,9 +188,6 @@ var Midway8080BWPlatform = function(mainElement) {
cpu.setTstates(0);
watchdog_counter = INITIAL_WATCHDOG;
}
- this.readAddress = function(addr) {
- return membus.read(addr);
- }
}
PLATFORMS['mw8080bw'] = Midway8080BWPlatform;
diff --git a/src/platform/sound_konami.js b/src/platform/sound_konami.js
index c88d109d..00d063f5 100644
--- a/src/platform/sound_konami.js
+++ b/src/platform/sound_konami.js
@@ -56,11 +56,8 @@ var KonamiSoundPlatform = function(mainElement) {
}
}
};
- cpu = window.Z80({
- display: {},
- memory: membus,
- ioBus: iobus
- });
+ this.readAddress = membus.read;
+ cpu = this.newCPU(membus, iobus);
psg = new PsgDeviceChannel();
master = new MasterChannel();
psg.setMode(PsgDeviceChannel.MODE_SIGNED);
@@ -136,9 +133,6 @@ var KonamiSoundPlatform = function(mainElement) {
cpu.reset();
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
}
- this.readAddress = function(addr) {
- return membus.read(addr); // TODO?
- }
}
PLATFORMS['sound_konami'] = KonamiSoundPlatform;
diff --git a/src/platform/sound_williams.js b/src/platform/sound_williams.js
index 2f8d6fa6..60a9ae02 100644
--- a/src/platform/sound_williams.js
+++ b/src/platform/sound_williams.js
@@ -79,11 +79,8 @@ var WilliamsSoundPlatform = function(mainElement) {
fillBuffer();
}
};
- cpu = window.Z80({
- display: {},
- memory: membus,
- ioBus: iobus
- });
+ this.readAddress = membus.read;
+ cpu = this.newCPU(membus, iobus);
audio = new SampleAudio(cpuFrequency / cpuAudioFactor);
audio.callback = function(lbuf) {
if (self.isRunning()) {
@@ -151,9 +148,6 @@ var WilliamsSoundPlatform = function(mainElement) {
cpu.reset();
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
}
- this.readAddress = function(addr) {
- return membus.read(addr); // TODO?
- }
}
PLATFORMS['sound_williams-z80'] = WilliamsSoundPlatform;
diff --git a/src/platform/vector.js b/src/platform/vector.js
index c134a557..577e92b6 100644
--- a/src/platform/vector.js
+++ b/src/platform/vector.js
@@ -90,7 +90,7 @@ var AtariVectorPlatform = function(mainElement) {
], {gmask:0x7fff})
};
- this.readMemory = membus.read;
+ this.readAddress = bus.read;
cpu = self.newCPU(bus);
// create video/audio
video = new VectorVideo(mainElement,1024,1024);
@@ -147,9 +147,6 @@ var AtariVectorPlatform = function(mainElement) {
this.clearDebug();
cpu.reset();
}
- this.readAddress = function(addr) {
- return bus.read(addr);
- }
this.loadState = function(state) {
cpu.loadState(state.c);
@@ -240,6 +237,7 @@ var AtariColorVectorPlatform = function(mainElement) {
])
};
+ this.readAddress = bus.read;
cpu = self.newCPU(bus);
// create video/audio
video = new VectorVideo(mainElement,1024,1024);
@@ -291,9 +289,6 @@ var AtariColorVectorPlatform = function(mainElement) {
this.clearDebug();
cpu.reset();
}
- this.readAddress = function(addr) {
- return bus.read(addr);
- }
this.loadState = function(state) {
cpu.loadState(state.c);
@@ -376,6 +371,7 @@ var Z80ColorVectorPlatform = function(mainElement, proto) {
])
};
+ this.readAddress = bus.read;
cpu = self.newCPU(bus);
// create video/audio
video = new VectorVideo(mainElement,1024,1024);
@@ -420,9 +416,6 @@ var Z80ColorVectorPlatform = function(mainElement, proto) {
switches[0xe] = 16;
cpu.reset();
}
- this.readAddress = function(addr) {
- return bus.read(addr);
- }
this.loadState = function(state) {
cpu.loadState(state.c);
diff --git a/src/platform/vicdual.js b/src/platform/vicdual.js
index f360ffae..ebb9c9fe 100644
--- a/src/platform/vicdual.js
+++ b/src/platform/vicdual.js
@@ -15,7 +15,7 @@ var VicDualPlatform = function(mainElement) {
this.__proto__ = new BaseZ80Platform();
var cpu, ram, membus, iobus, rom;
- var video, audio, psg, timer, pixels, probe;
+ var video, audio, psg, timer, pixels;
var inputs = [0xff, 0xff, 0xff, 0xff^0x8]; // most things active low
var palbank = 0;
@@ -108,7 +108,7 @@ var VicDualPlatform = function(mainElement) {
]),
isContended: function() { return false; },
};
- this.readMemory = membus.read;
+ this.readAddress = membus.read;
iobus = {
read: function(addr) {
return inputs[addr&3];
@@ -120,12 +120,7 @@ var VicDualPlatform = function(mainElement) {
if (addr & 0x40) { palbank = val & 3; }; // palette
}
};
- probe = new BusProbe(membus);
- cpu = window.Z80({
- display: {},
- memory: probe,
- ioBus: iobus
- });
+ cpu = this.newCPU(membus, iobus);
video = new RasterVideo(mainElement,256,224,{rotate:-90});
audio = new MasterAudio();
psg = new AY38910_Audio(audio);
@@ -154,14 +149,7 @@ var VicDualPlatform = function(mainElement) {
targetTstates += cpuCyclesPerLine;
if (sl == vblankStart) inputs[1] |= 0x8;
if (sl == vsyncEnd) inputs[1] &= ~0x8;
- if (debugCond) {
- while (cpu.getTstates() < targetTstates) {
- if (debugCond && debugCond()) { debugCond = null; }
- cpu.runFrame(cpu.getTstates() + 1);
- }
- } else {
- cpu.runFrame(targetTstates);
- }
+ self.runCPU(cpu, targetTstates - cpu.getTstates());
}
video.updateFrame();
self.restartDebugState();
@@ -216,16 +204,12 @@ var VicDualPlatform = function(mainElement) {
psg.reset();
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
}
- this.readAddress = function(addr) {
- return membus.read(addr & 0xffff); // TODO?
- }
this.setFrameStats = function(on) {
framestats = on ? {
palette: palette,
layers: {width:256, height:224, tiles:[]}
} : null;
}
- this.getProbe = function() { return probe; }
}
PLATFORMS['vicdual'] = VicDualPlatform;
diff --git a/src/platform/williams.js b/src/platform/williams.js
index dd4b4f8f..e4cc9f25 100644
--- a/src/platform/williams.js
+++ b/src/platform/williams.js
@@ -273,13 +273,12 @@ var WilliamsPlatform = function(mainElement, proto) {
nvram = new RAM(0x400);
// TODO: save in browser storage?
//displayPCs = new Uint16Array(new ArrayBuffer(0x9800*2));
- rom = padBytes(new lzgmini().decode(ROBOTRON_ROM).slice(0), 0xc001);
+ //rom = padBytes(new lzgmini().decode(ROBOTRON_ROM).slice(0), 0xc001);
membus = {
read: memread_williams,
write: memwrite_williams,
};
- this.readMemory = membus.read;
- //var probebus = new BusProbe(membus);
+ this.readAddress = membus.read;
var iobus = {
read: function(a) {return 0;},
write: function(a,v) {console.log(hex(a),hex(v));}
@@ -317,13 +316,11 @@ var WilliamsPlatform = function(mainElement, proto) {
cpu.requestInterrupt();
}
}
- if (!self.wasBreakpointHit())
- self.runCPU(cpu, cpuCyclesPerSection);
+ self.runCPU(cpu, cpuCyclesPerSection);
if (sl < 256) video.updateFrame(0, 0, 256-4-sl, 0, 4, 304);
}
// last 6 lines
- if (!self.wasBreakpointHit())
- self.runCPU(cpu, cpuCyclesPerSection*2);
+ self.runCPU(cpu, cpuCyclesPerSection*2);
if (screenNeedsRefresh) {
for (var i=0; i<0x9800; i++)
drawDisplayByte(i, ram.mem[i]);
@@ -400,9 +397,6 @@ var WilliamsPlatform = function(mainElement, proto) {
watchdog_counter = INITIAL_WATCHDOG;
banksel = 1;
}
- this.readAddress = function(addr) {
- return membus.read(addr);
- }
this.scaleCPUFrequency = function(scale) {
cpuScale = scale;
cpuFrequency *= scale;
diff --git a/src/ui.js b/src/ui.js
index a352aaed..4edc755f 100644
--- a/src/ui.js
+++ b/src/ui.js
@@ -110,11 +110,14 @@ var TOOL_TO_SOURCE_STYLE = {
}
var worker = new Worker("./src/worker/workermain.js");
-var current_output = null;
-var current_preset_index = -1; // TODO: use URL
-var current_preset_id = null;
-var assemblyfile = null;
-var sourcefile = null;
+var current_output;
+var current_preset_index = -1;
+var current_preset_id;
+var assemblyfile;
+var sourcefile;
+var symbolmap;
+var addr2symbol;
+var compparams;
var trace_pending_at_pc;
var store;
var pendingWorkerMessages = 0;
@@ -367,11 +370,23 @@ function arrayCompare(a,b) {
return true;
}
+function invertMap(m) {
+ var r = {};
+ if (m) {
+ for (var k in m) r[m[k]] = k;
+ }
+ return r;
+}
+
function setCompileOutput(data) {
sourcefile = new SourceFile(data.lines);
if (data.asmlines) {
assemblyfile = new SourceFile(data.asmlines, data.intermediate.listing);
}
+ symbolmap = data.symbolmap;
+ addr2symbol = invertMap(symbolmap);
+ addr2symbol[0x10000] = '__END__';
+ compparams = data.params;
// errors?
function addErrorMarker(line, msg) {
var div = document.createElement("div");
@@ -406,8 +421,7 @@ function setCompileOutput(data) {
platform.loadROM(getCurrentPresetTitle(), rom);
resume();
current_output = rom;
- prof_reads = [];
- prof_writes = [];
+ resetProfiler();
toolbar.removeClass("has-errors");
} catch (e) {
console.log(e); // TODO: show error
@@ -711,6 +725,16 @@ function _breakExpression() {
}
}
+function getSymbolAtAddress(a) {
+ if (addr2symbol[a]) return addr2symbol[a];
+ var i=0;
+ while (--a >= 0) {
+ i++;
+ if (addr2symbol[a]) return addr2symbol[a] + '+' + i;
+ }
+ return '';
+}
+
function updateDebugWindows() {
if (platform.isRunning()) {
updateMemoryWindow();
@@ -719,14 +743,6 @@ function updateDebugWindows() {
setTimeout(updateDebugWindows, 200);
}
-function getProfileLine(line) {
- var offset = sourcefile.line2offset[line];
- if (offset >= 0) {
- if (prof_reads[offset] > 0)
- return ""+prof_reads[offset];
- }
-}
-
function updateProfileWindow() {
if (profileview && sourcefile) {
$("#profileview").find('[data-index]').each(function(i,e) {
@@ -746,22 +762,41 @@ function updateMemoryWindow() {
if (memoryview) {
$("#memoryview").find('[data-index]').each(function(i,e) {
var div = $(e);
- var offset = div.attr('data-index') * 16;
+ var row = div.attr('data-index');
var oldtext = div.text();
- var newtext = getMemoryLineAtOffset(offset);
+ var newtext = getMemoryLineAt(row);
if (oldtext != newtext)
div.text(newtext);
});
}
}
-function getMemoryLineAtOffset(offset) {
+function getMemoryLineAt(row) {
+ var offset = row * 16;
+ var n1 = 0;
+ var n2 = 16;
+ var sym;
+ if (getDumpLines()) {
+ var dl = dumplines[row];
+ if (dl) {
+ offset = dl.a & 0xfff0;
+ n1 = dl.a - offset;
+ n2 = n1 + dl.l;
+ sym = dl.s;
+ } else {
+ return '.';
+ }
+ }
var s = hex(offset,4) + ' ';
- for (var i=0; i<16; i++) {
- var read = platform.readMemory(offset+i);
+ for (var i=0; i 8) s += ' ';
+ for (var i=n1; i=0?hex(read,2):'??');
}
+ for (var i=n2; i<16; i++) s += ' ';
+ if (sym) s += ' ' + sym;
return s;
}
@@ -769,6 +804,56 @@ function getEditorLineHeight() {
return $("#editor").find(".CodeMirror-line").first().height();
}
+function getDumpLineAt(line) {
+ var d = dumplines[line];
+ if (d) {
+ return d.a + " " + d.s;
+ }
+}
+
+var IGNORE_SYMS = {s__INITIALIZER:true, /* s__GSINIT:true, */ _color_prom:true};
+
+function getDumpLines() {
+ if (!dumplines && addr2symbol) {
+ dumplines = [];
+ var ofs = 0;
+ var sym;
+ for (var nextofs in addr2symbol) {
+ nextofs |= 0;
+ var nextsym = addr2symbol[nextofs];
+ if (sym) {
+ if (IGNORE_SYMS[sym]) {
+ ofs = nextofs;
+ } else {
+ while (ofs < nextofs) {
+ var ofs2 = (ofs + 16) & 0xffff0;
+ if (ofs2 > nextofs) ofs2 = nextofs;
+ //if (ofs < 1000) console.log(ofs, ofs2, nextofs, sym);
+ dumplines.push({a:ofs, l:ofs2-ofs, s:sym});
+ ofs = ofs2;
+ }
+ }
+ }
+ sym = nextsym;
+ }
+ }
+ return dumplines;
+}
+
+function getMemorySegment(a) {
+ if (!compparams) return 'unknown';
+ if (a >= compparams.data_start && a < compparams.data_start+compparams.data_size) {
+ if (platform.getSP && a >= platform.getSP() - 15)
+ return 'stack';
+ else
+ return 'data';
+ }
+ else if (a >= compparams.code_start && a < compparams.code_start+compparams.code_size)
+ return 'code';
+ else
+ return 'unknown';
+}
+
function showMemoryWindow() {
memoryview = new VirtualList({
w:$("#emulator").width(),
@@ -776,15 +861,19 @@ function showMemoryWindow() {
itemHeight: getEditorLineHeight(),
totalRows: 0x1000,
generatorFn: function(row) {
- var s = getMemoryLineAtOffset(row * 16);
+ var s = getMemoryLineAt(row);
var div = document.createElement("div");
+ if (dumplines) {
+ var dlr = dumplines[row];
+ if (dlr) div.classList.add('seg_' + getMemorySegment(dumplines[row].a));
+ }
div.appendChild(document.createTextNode(s));
return div;
}
});
$("#memoryview").empty().append(memoryview.container);
updateMemoryWindow();
- memoryview.scrollToItem(0x800); // TODO
+ memoryview.scrollToItem(0); // TODO
}
function toggleMemoryWindow() {
@@ -816,13 +905,59 @@ function createProfileWindow() {
updateProfileWindow();
}
+var pcdata = {};
var prof_reads, prof_writes;
+var dumplines;
+
+function resetProfiler() {
+ prof_reads = [];
+ prof_writes = [];
+ pcdata = [];
+ dumplines = null;
+}
function profileWindowCallback(a,v) {
- if (v >= 0) {
- prof_writes[a] = (prof_writes[a]|0)+1;
+ if (platform.getPC) {
+ var pc = platform.getPC();
+ var pcd = pcdata[pc];
+ if (!pcd) {
+ pcd = pcdata[pc] = {nv:1};
+ }
+ pcd.nv++;
+ if (a != pc) {
+ if (v >= 0) {
+ pcd.lastwa = a;
+ pcd.lastwv = v;
+ } else {
+ pcd.lastra = a;
+ pcd.lastrv = platform.readAddress(a);
+ }
+ }
} else {
- prof_reads[a] = (prof_reads[a]|0)+1;
+ // TODO
+ if (v >= 0) {
+ prof_writes[a] = (prof_writes[a]|0)+1;
+ } else {
+ prof_reads[a] = (prof_reads[a]|0)+1;
+ }
+ }
+}
+
+function getProfileLine(line) {
+ var offset = sourcefile.line2offset[line];
+ if (offset >= 0) {
+ var pcd = pcdata[offset];
+ if (pcd) {
+ var s = pcd.nv+"";
+ while (s.length < 8) { s = ' '+s; }
+ if (pcd.lastra >= 0) {
+ s += " read [" + hex(pcd.lastra,4) + "] == " + hex(pcd.lastrv,2);
+ }
+ if (pcd.lastwa >= 0) {
+ s += " write " + hex(pcd.lastwv,2) + " -> [" + hex(pcd.lastwa,4) + "]";
+ }
+ return s;
+ }
}
}
@@ -965,8 +1100,8 @@ function startPlatform() {
showWelcomeMessage();
// start platform and load file
preloadWorker(qs['file']);
- setupDebugControls();
platform.start();
+ setupDebugControls();
loadPreset(qs['file']);
updateSelector();
return true;
diff --git a/src/worker/workermain.js b/src/worker/workermain.js
index 0253d3c9..842690a8 100644
--- a/src/worker/workermain.js
+++ b/src/worker/workermain.js
@@ -6,42 +6,49 @@ var PLATFORM_PARAMS = {
code_size: 0x2000,
data_start: 0x2000,
data_size: 0x400,
+ stack_end: 0x2400,
},
'vicdual': {
code_start: 0x0,
code_size: 0x4020,
data_start: 0xe400,
data_size: 0x400,
+ stack_end: 0xe800,
},
'galaxian': {
code_start: 0x0,
code_size: 0x4000,
data_start: 0x4000,
data_size: 0x400,
+ stack_end: 0x4800,
},
'galaxian-scramble': {
code_start: 0x0,
code_size: 0x5020,
data_start: 0x4000,
data_size: 0x400,
+ stack_end: 0x4800,
},
'williams-z80': {
code_start: 0x0,
code_size: 0x9800,
data_start: 0x9800,
data_size: 0x2800,
+ stack_end: 0xc000,
},
'vector-z80color': {
code_start: 0x0,
code_size: 0x8000,
data_start: 0xe000,
data_size: 0x2000,
+ stack_end: 0x0,
},
'sound_williams-z80': {
code_start: 0x0,
code_size: 0x4000,
data_start: 0x4000,
data_size: 0x400,
+ stack_end: 0x8000,
},
};
@@ -685,22 +692,19 @@ function assemblelinkSDASZ80(code, platform) {
var asmlines = parseListing(lstout, /^\s*([0-9A-F]+)\s+([0-9A-F][0-9A-F r]*[0-9A-F])\s+\[([0-9 ]+)\]\s+(\d+) (.*)/i, 4, 1, 2, 5, 3);
var srclines = parseSourceLines(lstout, /^\s+\d+ ;:(\d+):/i, /^\s*([0-9A-F]{4})/i);
// parse symbol map
- /*
var symbolmap = {};
- console.log(mapout);
for (var s of mapout.split("\n")) {
var toks = s.split(" ");
- if (s[0] == 'DEF') {
- symbolmap[s[1]] = s[2];
+ if (toks[0] == 'DEF' && !toks[1].startsWith("A$main$")) {
+ symbolmap[toks[1]] = parseInt(toks[2], 16);
}
}
- */
return {
output:parseIHX(hexout, params.code_start, params.code_size),
lines:asmlines,
srclines:srclines,
errors:msvc_errors, // TODO?
- symbolmap:mapout,
+ symbolmap:symbolmap,
intermediate:{listing:rstout},
};
}
@@ -897,6 +901,7 @@ onmessage = function(e) {
var toolfn = TOOLS[e.data.tool];
if (!toolfn) throw "no tool named " + e.data.tool;
var result = toolfn(code, platform);
+ result.params = PLATFORM_PARAMS[platform];
if (result) {
postMessage(result);
}