diff --git a/doc/compilers.txt b/doc/compilers.txt
index 286e4283..c6a0ce72 100644
--- a/doc/compilers.txt
+++ b/doc/compilers.txt
@@ -54,3 +54,9 @@ machines.
SWEET16 - Woz's tiny bytecode interpreter on the Apple ][ integer BASIC ROM.
Still emcumbered by Apple's copyright for the foreseeable future.
http://6502.org/source/interpreters/sweet16.htm
+
+https://github.com/EtchedPixels/FUZIX/wiki
+
+gcc6809 - need to check this out
+
+
diff --git a/spinner.gif b/images/spinner.gif
similarity index 100%
rename from spinner.gif
rename to images/spinner.gif
diff --git a/index.html b/index.html
index 8327e725..acf4260c 100644
--- a/index.html
+++ b/index.html
@@ -75,6 +75,18 @@ div.emulator {
background-color: #666;
margin-top: 20px auto 0;
}
+div.debugwindow {
+ position:absolute;
+ left:50%;
+ top:0;
+ width:50%;
+ background-color: #666;
+ color: #66ff66;
+ white-space: pre;
+ margin-top: 20px auto 0;
+ font-family: "Andale Mono", "Menlo", "Lucida Console", monospace;
+ font-size: 10pt;
+}
div.mem_info {
position: fixed;
left: 51%;
@@ -83,7 +95,7 @@ div.mem_info {
color: #66ff66;
white-space: pre;
padding: 20px;
- z-index: 2;
+ z-index: 12;
font-family: "Andale Mono", "Menlo", "Lucida Console", monospace;
font-size: 12pt;
}
@@ -226,7 +238,7 @@ div.bitmap_editor {
-
+
@@ -235,9 +247,13 @@ div.bitmap_editor {
-
-
+
@@ -249,8 +265,12 @@ div.bitmap_editor {
+
+
+
+
+
@@ -314,6 +334,7 @@ Dest: $0000/0
+
diff --git a/src/emu.js b/src/emu.js
index 7032c9d0..0d2b645c 100644
--- a/src/emu.js
+++ b/src/emu.js
@@ -825,11 +825,26 @@ function AddressDecoder(table, options) {
}
var BusProbe = function(bus) {
+ var active = false;
+ var callback;
+ this.activate = function(_callback) {
+ active = true;
+ callback = _callback;
+ }
+ this.deactivate = function() {
+ active = false;
+ callback = null;
+ }
this.read = function(a) {
- var val = bus.read(a);
- return val;
+ if (active) {
+ callback(a);
+ }
+ return bus.read(a);
}
this.write = function(a,v) {
- return bus.write(a,v);
+ if (active) {
+ callback(a,v);
+ }
+ bus.write(a,v);
}
}
diff --git a/src/platform/galaxian.js b/src/platform/galaxian.js
index d3b128d6..cf98249b 100644
--- a/src/platform/galaxian.js
+++ b/src/platform/galaxian.js
@@ -264,6 +264,9 @@ var GalaxianPlatform = function(mainElement, options) {
isContended: function() { return false; },
};
}
+ this.readMemory = function(a) {
+ return (a == 0x7000 || a == 0x7800) ? null : membus.read; // ignore watchdog
+ };
audio = new MasterAudio();
psg1 = new AY38910_Audio(audio);
psg2 = new AY38910_Audio(audio);
@@ -368,7 +371,7 @@ var GalaxianPlatform = function(mainElement, options) {
}
this.isRunning = function() {
- return timer.isRunning();
+ return timer && timer.isRunning();
}
this.pause = function() {
timer.stop();
diff --git a/src/platform/mw8080bw.js b/src/platform/mw8080bw.js
index b17791a6..3d6d1844 100644
--- a/src/platform/mw8080bw.js
+++ b/src/platform/mw8080bw.js
@@ -60,6 +60,7 @@ var Midway8080BWPlatform = function(mainElement) {
]),
isContended: function() { return false; },
};
+ this.readMemory = membus.read;
iobus = {
read: function(addr) {
addr &= 0x3;
@@ -177,7 +178,7 @@ var Midway8080BWPlatform = function(mainElement) {
}
this.isRunning = function() {
- return timer.isRunning();
+ return timer && timer.isRunning();
}
this.pause = function() {
timer.stop();
diff --git a/src/platform/sound_konami.js b/src/platform/sound_konami.js
index d8a6846e..c88d109d 100644
--- a/src/platform/sound_konami.js
+++ b/src/platform/sound_konami.js
@@ -122,7 +122,7 @@ var KonamiSoundPlatform = function(mainElement) {
}
this.isRunning = function() {
- return timer.isRunning();
+ return timer && timer.isRunning();
}
this.pause = function() {
timer.stop();
diff --git a/src/platform/sound_williams.js b/src/platform/sound_williams.js
index 149737d4..2f8d6fa6 100644
--- a/src/platform/sound_williams.js
+++ b/src/platform/sound_williams.js
@@ -137,7 +137,7 @@ var WilliamsSoundPlatform = function(mainElement) {
}
this.isRunning = function() {
- return timer.isRunning();
+ return timer && timer.isRunning();
}
this.pause = function() {
timer.stop();
diff --git a/src/platform/vcs.js b/src/platform/vcs.js
index 1459631d..bc6252c0 100644
--- a/src/platform/vcs.js
+++ b/src/platform/vcs.js
@@ -65,7 +65,7 @@ var VCSPlatform = function() {
return {x:xpos, y:ypos};
}
- this.isRunning = function() { return Javatari.room.console.isRunning(); }
+ this.isRunning = function() { return Javatari.room && Javatari.room.console.isRunning(); }
this.pause = function() { Javatari.room.console.pause(); }
this.resume = function() { Javatari.room.console.go(); }
this.step = function() { Javatari.room.console.debugSingleStepCPUClock(); }
diff --git a/src/platform/vector.js b/src/platform/vector.js
index f5791947..f18e633c 100644
--- a/src/platform/vector.js
+++ b/src/platform/vector.js
@@ -89,6 +89,7 @@ var AtariVectorPlatform = function(mainElement) {
], {gmask:0x7fff})
};
+ this.readMemory = membus.read;
cpu = self.newCPU(bus);
// create video/audio
video = new VectorVideo(mainElement,1024,1024);
@@ -131,7 +132,7 @@ var AtariVectorPlatform = function(mainElement) {
}
this.isRunning = function() {
- return timer.isRunning();
+ return timer && timer.isRunning();
}
this.pause = function() {
timer.stop();
@@ -275,7 +276,7 @@ var AtariColorVectorPlatform = function(mainElement) {
}
this.isRunning = function() {
- return timer.isRunning();
+ return timer && timer.isRunning();
}
this.pause = function() {
timer.stop();
@@ -401,7 +402,7 @@ var Z80ColorVectorPlatform = function(mainElement, proto) {
}
this.isRunning = function() {
- return timer.isRunning();
+ return timer && timer.isRunning();
}
this.pause = function() {
timer.stop();
diff --git a/src/platform/vicdual.js b/src/platform/vicdual.js
index ecacd921..86653939 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;
+ var video, audio, psg, timer, pixels, probe;
var inputs = [0xff, 0xff, 0xff, 0xff^0x8]; // most things active low
var palbank = 0;
@@ -108,6 +108,7 @@ var VicDualPlatform = function(mainElement) {
]),
isContended: function() { return false; },
};
+ this.readMemory = membus.read;
iobus = {
read: function(addr) {
return inputs[addr&3];
@@ -119,9 +120,10 @@ var VicDualPlatform = function(mainElement) {
if (addr & 0x40) { palbank = val & 3; }; // palette
}
};
+ probe = new BusProbe(membus);
cpu = window.Z80({
display: {},
- memory: membus,
+ memory: probe,
ioBus: iobus
});
video = new RasterVideo(mainElement,256,224,{rotate:-90});
@@ -199,7 +201,7 @@ var VicDualPlatform = function(mainElement) {
}
this.isRunning = function() {
- return timer.isRunning();
+ return timer && timer.isRunning();
}
this.pause = function() {
timer.stop();
@@ -223,6 +225,7 @@ var VicDualPlatform = function(mainElement) {
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 546fe689..dd4b4f8f 100644
--- a/src/platform/williams.js
+++ b/src/platform/williams.js
@@ -278,6 +278,7 @@ var WilliamsPlatform = function(mainElement, proto) {
read: memread_williams,
write: memwrite_williams,
};
+ this.readMemory = membus.read;
//var probebus = new BusProbe(membus);
var iobus = {
read: function(a) {return 0;},
@@ -384,7 +385,7 @@ var WilliamsPlatform = function(mainElement, proto) {
}
this.isRunning = function() {
- return timer.isRunning();
+ return timer && timer.isRunning();
}
this.pause = function() {
timer.stop();
diff --git a/src/ui.js b/src/ui.js
index 0b167e2e..38fdce53 100644
--- a/src/ui.js
+++ b/src/ui.js
@@ -115,12 +115,10 @@ var current_preset_index = -1; // TODO: use URL
var current_preset_id = null;
var assemblyfile = null;
var sourcefile = null;
-var pcvisits;
var trace_pending_at_pc;
var store;
var pendingWorkerMessages = 0;
var editor;
-
var disasmview = CodeMirror(document.getElementById('disassembly'), {
mode: 'z80',
theme: 'cobalt',
@@ -129,6 +127,9 @@ var disasmview = CodeMirror(document.getElementById('disassembly'), {
styleActiveLine: true
});
+var memoryview;
+var profileview;
+
function newEditor(mode) {
var isAsm = (mode != 'text/x-csrc');
editor = CodeMirror(document.getElementById('editor'), {
@@ -147,6 +148,11 @@ function newEditor(mode) {
setCode(editor.getValue());
}, 200);
});
+ editor.on('scroll', function(ed, changeobj) {
+ if (profileview) {
+ profileview.container.scrollTop = editor.getScrollInfo().top;
+ }
+ });
editor.setOption("mode", mode);
}
@@ -400,7 +406,8 @@ function setCompileOutput(data) {
platform.loadROM(getCurrentPresetTitle(), rom);
resume();
current_output = rom;
- pcvisits = {};
+ prof_reads = [];
+ prof_writes = [];
toolbar.removeClass("has-errors");
} catch (e) {
console.log(e); // TODO: show error
@@ -447,7 +454,6 @@ function setCompileOutput(data) {
worker.onmessage = function(e) {
toolbar.removeClass("is-busy");
$('#compile_spinner').css('visibility', 'hidden');
- // TODO: this doesn't completely work yet
if (pendingWorkerMessages > 1) {
pendingWorkerMessages = 0;
setCode(editor.getValue());
@@ -523,7 +529,6 @@ function setupBreakpoint() {
console.log("BREAKPOINT", hex(PC));
// TODO: switch to disasm
}
- pcvisits[PC] = pcvisits[PC] ? pcvisits[PC]+1 : 1;
showMemory(state);
updateDisassembly();
});
@@ -872,6 +877,136 @@ function _breakExpression() {
}
}
+function updateDebugWindows() {
+ if (platform.isRunning()) {
+ updateMemoryWindow();
+ updateProfileWindow();
+ }
+ 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) {
+ var div = $(e);
+ var lineno = div.attr('data-index') | 0;
+ var newtext = getProfileLine(lineno+1);
+ if (newtext) {
+ var oldtext = div.text();
+ if (oldtext != newtext)
+ div.text(newtext);
+ }
+ });
+ }
+}
+
+function updateMemoryWindow() {
+ if (memoryview) {
+ $("#memoryview").find('[data-index]').each(function(i,e) {
+ var div = $(e);
+ var offset = div.attr('data-index') * 16;
+ var oldtext = div.text();
+ var newtext = getMemoryLineAtOffset(offset);
+ if (oldtext != newtext)
+ div.text(newtext);
+ });
+ }
+}
+
+function getMemoryLineAtOffset(offset) {
+ var s = hex(offset,4) + ' ';
+ for (var i=0; i<16; i++) {
+ var read = platform.readMemory(offset+i);
+ if (i==8) s += ' ';
+ s += ' ' + (read>=0?hex(read,2):'??');
+ }
+ return s;
+}
+
+function getEditorLineHeight() {
+ return $("#editor").find(".CodeMirror-line").first().height();
+}
+
+function showMemoryWindow() {
+ memoryview = new VirtualList({
+ w:$("#emulator").width(),
+ h:$("#emulator").height(),
+ itemHeight: getEditorLineHeight(),
+ totalRows: 0x1000,
+ generatorFn: function(row) {
+ var s = getMemoryLineAtOffset(row * 16);
+ var div = document.createElement("div");
+ div.appendChild(document.createTextNode(s));
+ return div;
+ }
+ });
+ $("#memoryview").empty().append(memoryview.container);
+ updateMemoryWindow();
+ memoryview.scrollToItem(0x800); // TODO
+}
+
+function toggleMemoryWindow() {
+ if ($("#profileview").is(':visible')) toggleProfileWindow();
+ if ($("#memoryview").is(':visible')) {
+ memoryview = null;
+ $("#emulator").show();
+ $("#memoryview").hide();
+ } else {
+ showMemoryWindow();
+ $("#emulator").hide();
+ $("#memoryview").show();
+ }
+}
+
+function createProfileWindow() {
+ profileview = new VirtualList({
+ w:$("#emulator").width(),
+ h:$("#emulator").height(),
+ itemHeight: getEditorLineHeight(),
+ totalRows: editor.lineCount(),
+ generatorFn: function(row) {
+ var div = document.createElement("div");
+ div.appendChild(document.createTextNode("."));
+ return div;
+ }
+ });
+ $("#profileview").empty().append(profileview.container);
+ updateProfileWindow();
+}
+
+var prof_reads, prof_writes;
+
+function profileWindowCallback(a,v) {
+ if (v >= 0) {
+ prof_writes[a] = (prof_writes[a]|0)+1;
+ } else {
+ prof_reads[a] = (prof_reads[a]|0)+1;
+ }
+}
+
+function toggleProfileWindow() {
+ if ($("#memoryview").is(':visible')) toggleMemoryWindow();
+ if ($("#profileview").is(':visible')) {
+ profileview = null;
+ platform.getProbe().deactivate();
+ $("#emulator").show();
+ $("#profileview").hide();
+ } else {
+ createProfileWindow();
+ platform.getProbe().activate(profileWindowCallback);
+ $("#emulator").hide();
+ $("#profileview").show();
+ }
+}
+
function setupDebugControls(){
$("#dbg_reset").click(resetAndDebug);
$("#dbg_pause").click(pause);
@@ -883,6 +1018,12 @@ function setupDebugControls(){
if (platform_id == 'vcs') {
$("#dbg_timing").click(traceTiming).show();
}
+ else if (platform.readAddress) {
+ $("#dbg_memory").click(toggleMemoryWindow).show();
+ }
+ if (platform.getProbe) {
+ $("#dbg_profile").click(toggleProfileWindow).show();
+ }
if (platform.saveState) { // TODO: only show if listing or disasm available
$("#dbg_disasm").click(toggleDisassembly).show();
}
@@ -893,6 +1034,7 @@ function setupDebugControls(){
$("#item_reset_file").click(_resetPreset);
$("#item_debug_expr").click(_breakExpression);
$("#item_download_rom").click(_downloadROMImage);
+ updateDebugWindows();
}
function showWelcomeMessage() {
@@ -938,11 +1080,6 @@ function showWelcomeMessage() {
///////////////////////////////////////////////////
-function setupBitmapEditor() {
-}
-
-///////////////////////////////////////////////////
-
var qs = (function (a) {
if (!a || a == "")
return {};
@@ -975,7 +1112,6 @@ function startPlatform() {
// start platform and load file
preloadWorker(qs['file']);
setupDebugControls();
- setupBitmapEditor();
platform.start();
loadPreset(qs['file']);
updateSelector();
diff --git a/src/vlist.js b/src/vlist.js
new file mode 100644
index 00000000..086c948f
--- /dev/null
+++ b/src/vlist.js
@@ -0,0 +1,161 @@
+/**
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2013 Sergi Mansilla
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the 'Software'), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+'use strict';
+
+/**
+ * Creates a virtually-rendered scrollable list.
+ * @param {object} config
+ * @constructor
+ */
+function VirtualList(config) {
+ var width = (config && config.w + 'px') || '100%';
+ var height = (config && config.h + 'px') || '100%';
+ var itemHeight = this.itemHeight = config.itemHeight;
+
+ this.items = config.items;
+ this.generatorFn = config.generatorFn;
+ this.totalRows = config.totalRows || (config.items && config.items.length);
+
+ var scroller = VirtualList.createScroller(itemHeight * this.totalRows);
+ this.container = VirtualList.createContainer(width, height);
+ this.container.appendChild(scroller);
+
+ var screenItemsLen = Math.ceil(config.h / itemHeight);
+ // Cache 4 times the number of items that fit in the container viewport
+ this.cachedItemsLen = screenItemsLen * 3;
+ this._renderChunk(this.container, 0);
+
+ var self = this;
+ var lastRepaintY;
+ var maxBuffer = screenItemsLen * itemHeight;
+ var lastScrolled = 0;
+
+ // As soon as scrolling has stopped, this interval asynchronouslyremoves all
+ // the nodes that are not used anymore
+ this.rmNodeInterval = setInterval(function() {
+ if (Date.now() - lastScrolled > 100) {
+ var badNodes = document.querySelectorAll('[data-rm="1"]');
+ for (var i = 0, l = badNodes.length; i < l; i++) {
+ try {
+ self.container.removeChild(badNodes[i]);
+ } catch (e) {
+ //
+ }
+ }
+ }
+ }, 300);
+
+ function onScroll(e) {
+ var scrollTop = e.target.scrollTop; // Triggers reflow
+ if (!lastRepaintY || Math.abs(scrollTop - lastRepaintY) > maxBuffer) {
+ var first = parseInt(scrollTop / itemHeight) - screenItemsLen;
+ self._renderChunk(self.container, first < 0 ? 0 : first);
+ lastRepaintY = scrollTop;
+ }
+
+ lastScrolled = Date.now();
+ e.preventDefault && e.preventDefault();
+ }
+
+ this.container.addEventListener('scroll', onScroll);
+}
+
+VirtualList.prototype.createRow = function(i) {
+ var item;
+ if (this.generatorFn)
+ item = this.generatorFn(i);
+ else if (this.items) {
+ if (typeof this.items[i] === 'string') {
+ var itemText = document.createTextNode(this.items[i]);
+ item = document.createElement('div');
+ item.style.height = this.itemHeight + 'px';
+ item.appendChild(itemText);
+ } else {
+ item = this.items[i];
+ }
+ }
+
+ item.classList.add('vrow');
+ item.setAttribute('data-index', ''+i);
+ item.style.position = 'absolute';
+ item.style.top = (i * this.itemHeight) + 'px';
+ return item;
+};
+
+/**
+ * Renders a particular, consecutive chunk of the total rows in the list. To
+ * keep acceleration while scrolling, we mark the nodes that are candidate for
+ * deletion instead of deleting them right away, which would suddenly stop the
+ * acceleration. We delete them once scrolling has finished.
+ *
+ * @param {Node} node Parent node where we want to append the children chunk.
+ * @param {Number} from Starting position, i.e. first children index.
+ * @return {void}
+ */
+VirtualList.prototype._renderChunk = function(node, from) {
+ var finalItem = from + this.cachedItemsLen;
+ if (finalItem > this.totalRows)
+ finalItem = this.totalRows;
+
+ // Append all the new rows in a document fragment that we will later append to
+ // the parent node
+ var fragment = document.createDocumentFragment();
+ for (var i = from; i < finalItem; i++) {
+ fragment.appendChild(this.createRow(i));
+ }
+
+ // Hide and mark obsolete nodes for deletion.
+ for (var j = 1, l = node.childNodes.length; j < l; j++) {
+ node.childNodes[j].style.display = 'none';
+ node.childNodes[j].setAttribute('data-rm', '1');
+ }
+ node.appendChild(fragment);
+};
+
+VirtualList.createContainer = function(w, h) {
+ var c = document.createElement('div');
+ c.style.width = w;
+ c.style.height = h;
+ c.style.overflow = 'auto';
+ c.style.position = 'relative';
+ c.style.padding = 0;
+ c.style.border = '1px solid black';
+ return c;
+};
+
+VirtualList.createScroller = function(h) {
+ var scroller = document.createElement('div');
+ scroller.style.opacity = 0;
+ scroller.style.position = 'absolute';
+ scroller.style.top = 0;
+ scroller.style.left = 0;
+ scroller.style.width = '1px';
+ scroller.style.height = h + 'px';
+ return scroller;
+};
+
+VirtualList.prototype.scrollToItem = function(index) {
+ this.container.scrollTop = this.itemHeight * index;
+};