From ba55a3532537e2eb969aeaef4d33f4fab35ba56a Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Tue, 11 Sep 2018 22:28:30 -0400 Subject: [PATCH] new scope module for verilog --- css/ui.css | 23 +++ doc/notes.txt | 1 + index.html | 5 + lib/mousetrap.min.js | 11 ++ lib/split.min.js | 2 + src/platform/verilog.ts | 313 +++++++++++++++------------------------- src/waveform.ts | 231 +++++++++++++++++++++++++++++ testwave.html | 241 +++++++++++++++++++++++++++++++ 8 files changed, 630 insertions(+), 197 deletions(-) create mode 100644 lib/mousetrap.min.js create mode 100644 lib/split.min.js create mode 100644 src/waveform.ts create mode 100644 testwave.html diff --git a/css/ui.css b/css/ui.css index edc0a2c5..1a6aba35 100644 --- a/css/ui.css +++ b/css/ui.css @@ -215,10 +215,22 @@ div.emulator { left:50%; top:0; width:50%; + height:100%; background-color: #666; margin-top: 20px auto 0; +} +div.emuoverlay { + position:absolute; + left:0; + top:0; + width:100%; height:100%; } +div.emuscope { + background-color: #333; +} +div.emuspacer { +} /* has to be here b/c renders differently after first load if in inline style */ .emuvideo { border-radius:20px; @@ -300,3 +312,14 @@ div.replaydiv { margin-left: 1em; margin-right: 1em; } +.gutter { + background-color: #555; + background-repeat: no-repeat; + background-position: 50%; +} +.gutter.gutter-vertical { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=') +} +.gutter.gutter-horizontal { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==') +} \ No newline at end of file diff --git a/doc/notes.txt b/doc/notes.txt index 3cd054be..191cbab5 100644 --- a/doc/notes.txt +++ b/doc/notes.txt @@ -67,6 +67,7 @@ TODO: - update memory browser window if view before 1st compile, update symbols - spinner disappears sometimes (and compiles even when not spinning...) (undo?) - z80 illegal opcode kills platform +- quantify verilog "graph iterations" WEB WORKER FORMAT diff --git a/index.html b/index.html index f4a7a21b..7b91f479 100644 --- a/index.html +++ b/index.html @@ -182,6 +182,8 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
+
+
@@ -288,6 +290,8 @@ if (window.location.host.endsWith('8bitworkshop.com')) { + + + diff --git a/lib/mousetrap.min.js b/lib/mousetrap.min.js new file mode 100644 index 00000000..105ae55b --- /dev/null +++ b/lib/mousetrap.min.js @@ -0,0 +1,11 @@ +/* mousetrap v1.6.2 craig.is/killing/mice */ +(function(p,t,h){function u(a,b,d){a.addEventListener?a.addEventListener(b,d,!1):a.attachEvent("on"+b,d)}function y(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return m[a.which]?m[a.which]:q[a.which]?q[a.which]:String.fromCharCode(a.which).toLowerCase()}function E(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function v(a){return"shift"==a||"ctrl"==a||"alt"==a|| +"meta"==a}function z(a,b){var d,e=[];var c=a;"+"===c?c=["+"]:(c=c.replace(/\+{2}/g,"+plus"),c=c.split("+"));for(d=0;dh||m.hasOwnProperty(h)&&(n[m[h]]=h)}d=n[c]?"keydown":"keypress"}"keypress"==d&&e.length&&(d="keydown");return{key:k,modifiers:e,action:d}}function C(a,b){return null===a||a===t?!1:a===b?!0:C(a.parentNode,b)}function e(a){function b(a){a= +a||{};var b=!1,l;for(l in n)a[l]?b=!0:n[l]=0;b||(w=!1)}function d(a,b,r,g,F,e){var l,D=[],h=r.type;if(!f._callbacks[a])return[];"keyup"==h&&v(a)&&(b=[a]);for(l=0;l":".","?":"/","|":"\\"},A={option:"alt",command:"meta","return":"enter", +escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},n;for(h=1;20>h;++h)m[111+h]="f"+h;for(h=0;9>=h;++h)m[h+96]=h.toString();e.prototype.bind=function(a,b,d){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,d);return this};e.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};e.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};e.prototype.reset=function(){this._callbacks={}; +this._directMap={};return this};e.prototype.stopCallback=function(a,b){return-1<(" "+b.className+" ").indexOf(" mousetrap ")||C(b,this.target)?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};e.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};e.addKeycodes=function(a){for(var b in a)a.hasOwnProperty(b)&&(m[b]=a[b]);n=null};e.init=function(){var a=e(t),b;for(b in a)"_"!==b.charAt(0)&&(e[b]=function(b){return function(){return a[b].apply(a, +arguments)}}(b))};e.init();p.Mousetrap=e;"undefined"!==typeof module&&module.exports&&(module.exports=e);"function"===typeof define&&define.amd&&define(function(){return e})}})("undefined"!==typeof window?window:null,"undefined"!==typeof window?document:null); diff --git a/lib/split.min.js b/lib/split.min.js new file mode 100644 index 00000000..652da169 --- /dev/null +++ b/lib/split.min.js @@ -0,0 +1,2 @@ +/*! Split.js - v1.3.5 */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Split=t()}(this,function(){"use strict";var e=window,t=e.document,n="addEventListener",i="removeEventListener",r="getBoundingClientRect",s=function(){return!1},o=e.attachEvent&&!e[n],a=["","-webkit-","-moz-","-o-"].filter(function(e){var n=t.createElement("div");return n.style.cssText="width:"+e+"calc(9px)",!!n.style.length}).shift()+"calc",l=function(e){return"string"==typeof e||e instanceof String?t.querySelector(e):e};return function(u,c){function z(e,t,n){var i=A(y,t,n);Object.keys(i).forEach(function(t){return e.style[t]=i[t]})}function h(e,t){var n=B(y,t);Object.keys(n).forEach(function(t){return e.style[t]=n[t]})}function f(e){var t=E[this.a],n=E[this.b],i=t.size+n.size;t.size=e/this.size*i,n.size=i-e/this.size*i,z(t.element,t.size,this.aGutterSize),z(n.element,n.size,this.bGutterSize)}function m(e){var t;this.dragging&&((t="touches"in e?e.touches[0][b]-this.start:e[b]-this.start)<=E[this.a].minSize+M+this.aGutterSize?t=E[this.a].minSize+this.aGutterSize:t>=this.size-(E[this.b].minSize+M+this.bGutterSize)&&(t=this.size-(E[this.b].minSize+this.bGutterSize)),f.call(this,t),c.onDrag&&c.onDrag())}function g(){var e=E[this.a].element,t=E[this.b].element;this.size=e[r]()[y]+t[r]()[y]+this.aGutterSize+this.bGutterSize,this.start=e[r]()[G]}function d(){var t=this,n=E[t.a].element,r=E[t.b].element;t.dragging&&c.onDragEnd&&c.onDragEnd(),t.dragging=!1,e[i]("mouseup",t.stop),e[i]("touchend",t.stop),e[i]("touchcancel",t.stop),t.parent[i]("mousemove",t.move),t.parent[i]("touchmove",t.move),delete t.stop,delete t.move,n[i]("selectstart",s),n[i]("dragstart",s),r[i]("selectstart",s),r[i]("dragstart",s),n.style.userSelect="",n.style.webkitUserSelect="",n.style.MozUserSelect="",n.style.pointerEvents="",r.style.userSelect="",r.style.webkitUserSelect="",r.style.MozUserSelect="",r.style.pointerEvents="",t.gutter.style.cursor="",t.parent.style.cursor=""}function S(t){var i=this,r=E[i.a].element,o=E[i.b].element;!i.dragging&&c.onDragStart&&c.onDragStart(),t.preventDefault(),i.dragging=!0,i.move=m.bind(i),i.stop=d.bind(i),e[n]("mouseup",i.stop),e[n]("touchend",i.stop),e[n]("touchcancel",i.stop),i.parent[n]("mousemove",i.move),i.parent[n]("touchmove",i.move),r[n]("selectstart",s),r[n]("dragstart",s),o[n]("selectstart",s),o[n]("dragstart",s),r.style.userSelect="none",r.style.webkitUserSelect="none",r.style.MozUserSelect="none",r.style.pointerEvents="none",o.style.userSelect="none",o.style.webkitUserSelect="none",o.style.MozUserSelect="none",o.style.pointerEvents="none",i.gutter.style.cursor=j,i.parent.style.cursor=j,g.call(i)}function v(e){e.forEach(function(t,n){if(n>0){var i=F[n-1],r=E[i.a],s=E[i.b];r.size=e[n-1],s.size=t,z(r.element,r.size,i.aGutterSize),z(s.element,s.size,i.bGutterSize)}})}function p(){F.forEach(function(e){e.parent.removeChild(e.gutter),E[e.a].element.style[y]="",E[e.b].element.style[y]=""})}void 0===c&&(c={});var y,b,G,E,w=l(u[0]).parentNode,D=e.getComputedStyle(w).flexDirection,U=c.sizes||u.map(function(){return 100/u.length}),k=void 0!==c.minSize?c.minSize:100,x=Array.isArray(k)?k:u.map(function(){return k}),L=void 0!==c.gutterSize?c.gutterSize:10,M=void 0!==c.snapOffset?c.snapOffset:30,O=c.direction||"horizontal",j=c.cursor||("horizontal"===O?"ew-resize":"ns-resize"),C=c.gutter||function(e,n){var i=t.createElement("div");return i.className="gutter gutter-"+n,i},A=c.elementStyle||function(e,t,n){var i={};return"string"==typeof t||t instanceof String?i[e]=t:i[e]=o?t+"%":a+"("+t+"% - "+n+"px)",i},B=c.gutterStyle||function(e,t){return n={},n[e]=t+"px",n;var n};"horizontal"===O?(y="width","clientWidth",b="clientX",G="left","paddingLeft"):"vertical"===O&&(y="height","clientHeight",b="clientY",G="top","paddingTop");var F=[];return E=u.map(function(e,t){var i,s={element:l(e),size:U[t],minSize:x[t]};if(t>0&&(i={a:t-1,b:t,dragging:!1,isFirst:1===t,isLast:t===u.length-1,direction:O,parent:w},i.aGutterSize=L,i.bGutterSize=L,i.isFirst&&(i.aGutterSize=L/2),i.isLast&&(i.bGutterSize=L/2),"row-reverse"===D||"column-reverse"===D)){var a=i.a;i.a=i.b,i.b=a}if(!o&&t>0){var c=C(t,O);h(c,L),c[n]("mousedown",S.bind(i)),c[n]("touchstart",S.bind(i)),w.insertBefore(c,s.element),i.gutter=c}0===t||t===u.length-1?z(s.element,s.size,L/2):z(s.element,s.size,L);var f=s.element[r]()[y];return f0&&F.push(i),s}),o?{setSizes:v,destroy:p}:{setSizes:v,getSizes:function(){return E.map(function(e){return e.size})},collapse:function(e){if(e===F.length){var t=F[e-1];g.call(t),o||f.call(t,t.size-t.bGutterSize)}else{var n=F[e];g.call(n),o||f.call(n,n.aGutterSize)}},destroy:p}}}); diff --git a/src/platform/verilog.ts b/src/platform/verilog.ts index 16a022c0..a2103248 100644 --- a/src/platform/verilog.ts +++ b/src/platform/verilog.ts @@ -4,6 +4,9 @@ import { Platform, BasePlatform } from "../baseplatform"; import { PLATFORMS, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap } from "../emu"; import { SampleAudio } from "../audio"; import { safe_extend } from "../util"; +import { WaveformView, WaveformProvider, WaveformMeta } from "../waveform"; + +declare var Split; var VERILOG_PRESETS = [ {id:'clock_divider.v', name:'Clock Divider'}, @@ -224,28 +227,12 @@ var VerilogPlatform = function(mainElement, options) { var inspect_obj, inspect_sym; var inspect_data = new Uint32Array(videoWidth * videoHeight); - // for scope - var scope_time_x = 0; // scope cursor - var scope_x_offset = 0; - var scope_y_offset = 0; - var scope_index_offset = 0; - var scope_max_y = 0; - var scope_y_top = 0; - var scope_a = 0; // used for transitions - var scopeWidth = videoWidth; - var scopeHeight = videoHeight; - var scopeImageData; - var sdata; // scope data + // for scope var module_name; - var yposlist = []; - var lasty = []; - var lastval = []; - var trace_ports; + //var trace_ports; var trace_signals; var trace_buffer; var trace_index; - var mouse_pressed; - var dirty = false; // for virtual CRT var framex=0; @@ -308,7 +295,12 @@ var VerilogPlatform = function(mainElement, options) { // inner Platform class - class _VerilogPlatform extends BasePlatform { + class _VerilogPlatform extends BasePlatform implements WaveformProvider { + + waveview : WaveformView; + wavediv : JQuery; + split; + hasvideo : boolean; getPresets() { return VERILOG_PRESETS; } @@ -321,37 +313,6 @@ var VerilogPlatform = function(mainElement, options) { ctx.textAlign = "left"; setKeyboardFromMap(video, switches, VERILOG_KEYCODE_MAP); var vcanvas = $(video.canvas); - vcanvas.mousemove( (e) => { - var new_x = Math.floor(e.offsetX * video.canvas.width / vcanvas.width() - 20); - var new_y = Math.floor(e.offsetY * video.canvas.height / vcanvas.height() - 20); - if (mouse_pressed) { - scope_y_offset = clamp(Math.min(0,-scope_max_y+videoHeight), 0, scope_y_offset + new_y - paddle_y); - scope_time_x = Math.floor(e.offsetX * video.canvas.width / vcanvas.width() - 16); - dirty = true; - this.refreshFrame(); - } - paddle_x = clamp(8, 240, new_x); - paddle_y = clamp(8, 240, new_y); - }); - vcanvas.mousedown( (e) => { - scope_time_x = Math.floor(e.offsetX * video.canvas.width / vcanvas.width() - 16); - mouse_pressed = true; - //if (e.target.setCapture) e.target.setCapture(); // TODO: pointer capture - dirty = true; - this.refreshFrame(); - }); - vcanvas.mouseup( (e) => { - mouse_pressed = false; - //if (e.target.setCapture) e.target.releaseCapture(); // TODO: pointer capture - dirty = true; - this.refreshFrame(); - }); - vcanvas.keydown( (e) => { - switch (e.keyCode) { - case 37: scope_time_x--; dirty=true; this.refreshFrame(); break; - case 39: scope_time_x++; dirty=true; this.refreshFrame(); break; - } - }); idata = video.getFrameData(); timerCallback = () => { if (!this.isRunning()) @@ -359,8 +320,30 @@ var VerilogPlatform = function(mainElement, options) { gen.switches = switches[0]; this.updateFrame(); }; - trace_buffer = new Uint32Array(0x10000); this.setFrameRate(60); + // setup scope + trace_buffer = new Uint32Array(0x20000); + var overlay = $("#emuoverlay"); + var topdiv = $('
').appendTo(overlay); + this.wavediv = $('
').appendTo(overlay); + this.split = Split( [topdiv[0], this.wavediv[0]], { + minSize: [0,0], + sizes: [99,1], + direction: 'vertical', + gutterSize: 16, + onDrag: () => { + if (this.waveview) this.waveview.recreate(); + }, + }); + // setup mouse events + topdiv.mousemove( (e) => { + var x = e.pageX - vcanvas.offset().left; + var y = e.pageY - vcanvas.offset().top; + var new_x = Math.floor(x * video.canvas.width / vcanvas.width() - 20); + var new_y = Math.floor(y * video.canvas.height / vcanvas.height() - 20); + paddle_x = clamp(8, 240, new_x); + paddle_y = clamp(8, 240, new_y); + }); } setGenInputs() { @@ -382,12 +365,19 @@ var VerilogPlatform = function(mainElement, options) { } // paint into frame, synched with vsync if full speed var sync = fps > 45; - var trace = fps < 0.02; + var trace = this.isScopeVisible(); this.updateVideoFrameCycles(cyclesPerFrame * fps/60 + 1, sync, trace); - //if (trace) displayTraceBuffer(); //this.restartDebugState(); gen.__unreset(); this.refreshVideoFrame(); + // set scope offset + if (trace && this.waveview) { + this.waveview.setEndTime(Math.floor(trace_index/trace_signals.length)); + } + } + + isScopeVisible() { + return this.split.getSizes()[1] > 2; // TODO? } // TODO: merge with prev func @@ -402,47 +392,40 @@ var VerilogPlatform = function(mainElement, options) { refreshVideoFrame() { this.updateInspectionFrame(); - this.updateAnimateScope(); + video.updateFrame(); this.updateInspectionPostFrame(); } + + refreshScopeOverlay() { + // TODO + } + + updateScopeFrame() { + this.split.setSizes([0,100]); // ensure scope visible + var done = this.fillTraceBuffer(32 * trace_signals.length); // TODO: const + if (done) + this.pause(); // TODO? + // TODO + } + + updateScope() { + // create scope, if visible + if (this.isScopeVisible()) { + if (!this.waveview) { + this.waveview = new WaveformView(this.wavediv[0], this); + } else { + this.waveview.refresh(); + } + } + } updateFrame() { if (!gen) return; - if (gen.vsync !== undefined && gen.hsync !== undefined && gen.rgb !== undefined) + if (this.hasvideo) this.updateVideoFrame(); else this.updateScopeFrame(); - } - - refreshFrame() { - if (!gen) return; - if (gen.vsync !== undefined && gen.hsync !== undefined && gen.rgb !== undefined) - this.refreshVideoFrame(); - else - this.refreshScopeOverlay(trace_ports); - } - - updateAnimateScope() { - var fps = this.getFrameRate(); - var trace = fps < 0.02; - var ctx = video.getContext(); - if (scope_a > 0.01) { - ctx.fillStyle = "black"; - ctx.fillRect(0, 0, videoWidth, videoHeight); - var vidyoffset = Math.round(scope_a*(-framey+videoHeight/6)); - video.updateFrame(0, vidyoffset, 0, 0, videoWidth, videoHeight); - ctx.fillStyle = "white"; - ctx.fillRect(framex, framey+vidyoffset, 1, 1); - scope_index_offset = (trace_index - trace_signals.length*scopeWidth + trace_buffer.length) % trace_buffer.length; - scope_x_offset = 0; - this.refreshScopeOverlay(trace_signals); - } else { - video.updateFrame(); - scope_index_offset = 0; - } - // smooth transition - scope_a = scope_a * 0.9 + (trace?1.0:0.0) * 0.1; - scope_y_top = (1 - scope_a*0.7) * videoHeight - (1 - scope_a) * scope_y_offset; + this.updateScope(); } updateInspectionFrame() { @@ -471,12 +454,15 @@ var VerilogPlatform = function(mainElement, options) { } } - updateVideoFrameCycles(ncycles, sync, trace) { + updateVideoFrameCycles(ncycles:number, sync:boolean, trace:boolean) { ncycles |= 0; var inspect = inspect_obj && inspect_sym; + var trace0 = trace_index; while (ncycles--) { - if (trace) - this.snapshotTrace(true); + if (trace) { + this.snapshotTrace(); + if (trace_index == trace0) trace = false; // kill trace when wraps around + } vidtick(); if (framex++ < videoWidth) { if (framey < videoHeight) { @@ -513,123 +499,50 @@ var VerilogPlatform = function(mainElement, options) { } } - displayTraceBuffer() { - var skip = trace_signals.length; - var src = trace_index; - for (var dest=0; dest= trace_buffer.length) trace_index = 0; + if (typeof(z) === 'number') + trace_buffer[trace_index] = z; + trace_index++; } + if (trace_index >= trace_buffer.length - arr.length) + trace_index = 0; } - fillTraceBuffer(count) { - var max_index = Math.min(trace_buffer.length - trace_ports.length, trace_index + count); + fillTraceBuffer(count:number) : boolean { + var max_index = Math.min(trace_buffer.length - trace_signals.length, trace_index + count); while (trace_index < max_index) { gen.clk ^= 1; gen.eval(); - this.snapshotTrace(false); - dirty = true; + this.snapshotTrace(); + if (trace_index == 0) + break; } gen.__unreset(); + return (trace_index == 0); } - - updateScopeFrame() { - this.fillTraceBuffer(Math.floor(videoWidth/4) * trace_ports.length); - if (!dirty) return; - dirty = false; - scope_y_top = 0; - this.refreshScopeOverlay(trace_ports); + + getSignalMetadata() : WaveformMeta[] { + return trace_signals; } - - refreshScopeOverlay(arr) { - if (!sdata) { - scopeImageData = video.getContext().createImageData(scopeWidth,scopeHeight); - sdata = new Uint32Array(scopeImageData.data.buffer); - } - var COLOR_BLACK = 0xff000000; - var COLOR_SIGNAL = 0xff22ff22; - var COLOR_BORDER = 0xff662222; - var COLOR_TRANS_SIGNAL = 0xff226622; - var COLOR_BLIP_SIGNAL = 0xff226622; - sdata.fill(0xff000000); - var jstart = scope_x_offset * arr.length + scope_index_offset; - var j = jstart; - for (var x=0; x1 ? v.len*2+8 : 8; - var y2 = y1+ys; - var z = trace_buffer[j++]; - if (j >= trace_buffer.length) j = 0; - var y = Math.round(y2 - ys*((z-lo)/hi)); - yposlist[i] = y2 + scope_y_top; - var ly = lasty[i]; - if (x > 0 && ly != y) { - var dir = ly < y ? 1 : -1; - while ((ly += dir) != y && ly >= y1 && ly <= y2) { - sdata[x + ly * scopeWidth] = COLOR_TRANS_SIGNAL; - } - } - sdata[x + y * scopeWidth] = lastval[i]==z ? COLOR_SIGNAL : COLOR_BLIP_SIGNAL; - lasty[i] = y; - lastval[i] = z; - y1 += ys+yb; - } - } - scope_max_y = y1 - scope_y_offset; - video.getContext().putImageData(scopeImageData, 0, scope_y_top); - // draw labels - var ctx = video.getContext(); - for (var i=0; i videoHeight) continue; - var v = arr[i]; - var name = v.name; - ctx.fillStyle = name == inspect_sym ? "yellow" : "white"; - name = name.replace(/__DOT__/g,'.'); - name = name.replace(module_name+'.',''); - ctx.textAlign = 'left'; - ctx.fillStyle = "white"; - shadowText(ctx, name, 1, yposlist[i]); - if (scope_time_x > 0) { - ctx.textAlign = 'right'; - var value = (arr.length * scope_time_x + i + jstart) % trace_buffer.length; - shadowText(ctx, ""+trace_buffer[value], videoWidth-1, yp); - } - } - // draw scope line & label - if (scope_time_x > 0) { - ctx.fillStyle = "cyan"; - shadowText(ctx, ""+(scope_time_x+scope_x_offset), - (scope_time_x>10)?(scope_time_x-2):(scope_time_x+20), videoHeight-2); - ctx.fillRect(scope_time_x, 0, 1, 4000); - } - // scroll left/right - if (scope_time_x >= videoWidth && scope_x_offset < (trace_buffer.length / arr.length) - videoWidth) { - scope_x_offset += 1 + (scope_time_x - videoWidth); - dirty = true; - } - else if (scope_time_x < 0 && scope_x_offset > 0) { - scope_x_offset = Math.max(0, scope_x_offset + scope_time_x); - dirty = true; + + getSignalData(index:number, start:number, len:number) : number[] { + // TODO: not efficient + var skip = this.getSignalMetadata().length; + var last = trace_buffer.length - trace_signals.length; // TODO: refactor, and not correct + var wrap = this.hasvideo; // TODO? + var a = []; + index += skip * start; + while (index < last && a.length < len) { + a.push(trace_buffer[index]); + index += skip; + if (wrap && index >= last) // TODO: what if starts with index==last + index = 0; } + return a; } printErrorCodeContext(e, code) { @@ -664,11 +577,14 @@ var VerilogPlatform = function(mainElement, options) { gen.__proto__ = base; current_output = output; module_name = output.name ? output.name.substr(1) : "top"; - trace_ports = current_output.ports; - trace_signals = current_output.ports.concat(current_output.signals); + //trace_ports = current_output.ports; + trace_signals = current_output.ports.concat(current_output.signals); // combine ports + signals + trace_signals = trace_signals.filter((v) => { return !v.name.startsWith("__V"); }); // remove __Vclklast etc trace_index = 0; // power on module this.poweron(); + // query output + this.hasvideo = gen.vsync !== undefined && gen.hsync !== undefined && gen.rgb !== undefined; } } // replace program ROM, if using the assembler @@ -685,6 +601,11 @@ var VerilogPlatform = function(mainElement, options) { } // restart audio this.restartAudio(); + // destroy scope + if (this.waveview) { + this.waveview.destroy(); + this.waveview = null; + } } restartAudio() { @@ -735,9 +656,8 @@ var VerilogPlatform = function(mainElement, options) { } reset() { gen.__reset(); - trace_index = scope_x_offset = 0; + trace_index = 0; if (trace_buffer) trace_buffer.fill(0); - dirty = true; if (video) video.setRotate(gen.rotate ? -90 : 0); } tick() { @@ -769,7 +689,6 @@ var VerilogPlatform = function(mainElement, options) { } else { inspect_obj = inspect_sym = null; } - dirty = true; } // DEBUGGING diff --git a/src/waveform.ts b/src/waveform.ts new file mode 100644 index 00000000..65a934d5 --- /dev/null +++ b/src/waveform.ts @@ -0,0 +1,231 @@ + +declare var VirtualList; +declare var Mousetrap; + +export interface WaveformMeta { + name : string; + len : number; +} + +export interface WaveformProvider { + getSignalMetadata() : WaveformMeta[]; + getSignalData(index:number, start:number, len:number) : number[]; +} + +export class WaveformView { + parent : HTMLElement; + wfp : WaveformProvider; + wavelist; + meta : WaveformMeta[]; + lines : HTMLCanvasElement[] = []; + zoom : number = 8; + t0 : number = 0; + tsel : number = -1; + pageWidth : number; + clocksPerPage : number; + clockMax : number; + + constructor(parent:HTMLElement, wfp:WaveformProvider) { + this.parent = parent; + this.wfp = wfp; + this.recreate(); + } + + wtimer; + recreate() { + clearTimeout(this.wtimer); + this.wtimer = setTimeout(() => { + this.destroy(); + // create new thing + this._recreate(); + }, 0); + } + + destroy() { + // remove old thing + if (this.wavelist) { + $(this.parent).empty(); + } + } + + _recreate() { + this.meta = this.wfp.getSignalMetadata(); + if (!this.meta) return; + var width = this.pageWidth = $(this.parent).width(); + var rowHeight = 40; // TODO + this.clocksPerPage = Math.floor(this.pageWidth/this.zoom) - 1; + this.clockMax = 0; + this.wavelist = new VirtualList({ + w: width, + h: $(this.parent).height(), + itemHeight: rowHeight, + totalRows: this.meta.length, + generatorFn: (row : number) => { + var s = this.meta[row].name; + var linediv = document.createElement("div"); + var canvas = document.createElement("canvas"); + canvas.width = width - 4; + canvas.height = rowHeight; + linediv.appendChild(canvas); //document.createTextNode(s)); + linediv.classList.add('waverow'); + this.lines[row] = canvas; + this.refreshRow(row); + return linediv; + } + }); + var wlc = this.wavelist.container; + wlc.tabIndex = -1; // make it focusable + //wlc.style = "overflow-x: hidden"; // TODO? + $(this.parent).append(wlc); + var down = false; + var selfn = (e) => { + this.setSelTime(e.offsetX / this.zoom + this.t0); + }; + $(wlc).mousedown( (e) => { + down = true; + selfn(e); + //if (e['pointerId']) e.target.setPointerCapture(e['pointerId']); + }); + $(wlc).mousemove( (e) => { + if (down) selfn(e); + }); + $(wlc).mouseup( (e) => { + down = false; + //if (e['pointerId']) e.target.releasePointerCapture(e['pointerId']); + }); + Mousetrap(wlc).bind('+', (e,combo) => { + this.setZoom(this.zoom * 2); + }); + Mousetrap(wlc).bind('-', (e,combo) => { + this.setZoom(this.zoom / 2); + }); + Mousetrap(wlc).bind('left', (e,combo) => { + this.setSelTime(this.tsel - 1); + }); + Mousetrap(wlc).bind('right', (e,combo) => { + this.setSelTime(this.tsel + 1); + }); + Mousetrap(wlc).bind('ctrl+left', (e,combo) => { + this.setSelTime(this.tsel - this.clocksPerPage/4); + }); + Mousetrap(wlc).bind('ctrl+right', (e,combo) => { + this.setSelTime(this.tsel + this.clocksPerPage/4); + }); + $(window).resize(() => { + this.recreate(); + }); // TODO: remove? + // assign buttons + $("#scope_go_start").click(() => { + this.setOrgTime(0); + }); + $("#scope_go_end").click(() => { + // TODO + }); + $("#scope_go_fwd").click(() => { + this.setOrgTime(this.t0 + this.clocksPerPage/4); + }); + $("#scope_go_back").click(() => { + this.setOrgTime(this.t0 - this.clocksPerPage/4); + }); + } + + roundT(t : number) { + t = Math.round(t); + t = Math.max(0, t); // make sure >= 0 + t = Math.min(this.clockMax + this.clocksPerPage/2, t); // make sure <= end + return t; + } + + setOrgTime(t : number) { + this.t0 = this.roundT(t); + this.refresh(); + } + + setEndTime(t : number) { + this.setOrgTime(t - this.clocksPerPage); + } + + setSelTime(t : number) { + t = this.roundT(t); + if (t > this.t0 + this.clocksPerPage) + this.t0 += this.clocksPerPage / 4; + if (t < this.t0) + this.t0 -= this.clocksPerPage / 4; + this.tsel = t; + this.setOrgTime(this.t0); + } + + setZoom(zoom : number) { + this.zoom = Math.max(1, zoom); + this.clocksPerPage = Math.ceil(this.pageWidth/this.zoom); // TODO: refactor into other one + this.refresh(); + } + + refresh() { + if (!this.meta) this.recreate(); + if (!this.meta) return; + for (var i=0; i0) + ctx.lineTo(x,y); + y = b + (1.0 - data[i]/yrange) * h2; + if (!isclk) x += this.zoom*(1/8); + if (i==0) + ctx.moveTo(x,y); + else + ctx.lineTo(x,y); + if (isclk) + x += this.zoom; + else + x += this.zoom*(7/8); + } + ctx.stroke(); + // draw selection thingie + if (this.tsel >= this.t0) { + ctx.strokeStyle = ctx.fillStyle = "#ff66ff"; + ctx.beginPath(); + x = (this.tsel - this.t0)*this.zoom + this.zoom/2; + ctx.moveTo(x, 0); + ctx.lineTo(x, h); + ctx.stroke(); + // print value + var val = data[this.tsel - this.t0]; + ctx.textAlign = 'right'; + if (val !== undefined) { + ctx.fillText(val.toString(), w-b*2, h); + } + } + // draw labels + ctx.fillStyle = "white"; + ctx.textAlign = "left"; + var name = meta.name; + name = name.replace(/__DOT__/g, "."); // make nicer name + ctx.fillText(name, 5, h-b); + } +} + diff --git a/testwave.html b/testwave.html new file mode 100644 index 00000000..9c49a968 --- /dev/null +++ b/testwave.html @@ -0,0 +1,241 @@ + + + +8bitworkshop IDE + + + + + + +
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +