From 89c3209f09be106d5c60611e48b8b306b37e40de Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Tue, 18 Apr 2017 21:18:53 -0400 Subject: [PATCH] started on profile/memory views --- doc/compilers.txt | 6 ++ spinner.gif => images/spinner.gif | Bin index.html | 33 ++++-- src/emu.js | 21 +++- src/platform/galaxian.js | 5 +- src/platform/mw8080bw.js | 3 +- src/platform/sound_konami.js | 2 +- src/platform/sound_williams.js | 2 +- src/platform/vcs.js | 2 +- src/platform/vector.js | 7 +- src/platform/vicdual.js | 9 +- src/platform/williams.js | 3 +- src/ui.js | 158 +++++++++++++++++++++++++++-- src/vlist.js | 161 ++++++++++++++++++++++++++++++ 14 files changed, 380 insertions(+), 32 deletions(-) rename spinner.gif => images/spinner.gif (100%) create mode 100644 src/vlist.js 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; +};