2017-11-11 19:45:32 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
var VERILOG_PRESETS = [
|
2017-11-15 00:12:52 +00:00
|
|
|
{id:'clock_divider.v', name:'Clock Divider'},
|
|
|
|
{id:'hvsync_generator.v', name:'Video Sync Generator'},
|
|
|
|
{id:'test_hvsync.v', name:'Test Pattern'},
|
|
|
|
{id:'lfsr.v', name:'Linear Feedback Shift Register'},
|
2017-11-18 19:51:25 +00:00
|
|
|
{id:'digits10.v', name:'Digits'},
|
2017-11-18 00:40:44 +00:00
|
|
|
{id:'ball_slip_counter.v', name:'Ball Motion (slipping counter)'},
|
|
|
|
{id:'ball_paddle.v', name:'Brick Smash Game'},
|
2017-11-21 21:36:38 +00:00
|
|
|
{id:'sprite_bitmap.v', name:'Sprite Bitmaps'},
|
|
|
|
{id:'sprite_renderer.v', name:'Sprite Rendering'},
|
|
|
|
{id:'sprite_multiple.v', name:'Multiple Sprites'},
|
2017-11-11 19:45:32 +00:00
|
|
|
];
|
|
|
|
|
2017-11-15 00:12:52 +00:00
|
|
|
var VERILOG_KEYCODE_MAP = makeKeycodeMap([
|
|
|
|
[Keys.VK_LEFT, 0, 0x1],
|
|
|
|
[Keys.VK_RIGHT, 0, 0x2],
|
|
|
|
[Keys.VK_UP, 0, 0x4],
|
|
|
|
[Keys.VK_DOWN, 0, 0x8],
|
|
|
|
[Keys.VK_SPACE, 0, 0x10],
|
|
|
|
[Keys.VK_SHIFT, 0, 0x20],
|
|
|
|
[Keys.VK_1, 0, 0x40],
|
|
|
|
[Keys.VK_2, 0, 0x80],
|
|
|
|
[Keys.VK_5, 0, 0x100],
|
|
|
|
[Keys.VK_6, 0, 0x200],
|
|
|
|
[Keys.VK_7, 0, 0x400],
|
|
|
|
]);
|
|
|
|
|
2017-11-17 21:01:07 +00:00
|
|
|
var VL_UL = function(x) { return x; }
|
|
|
|
|
|
|
|
/// Return true if data[bit] set
|
|
|
|
var VL_BITISSET_I = this.VL_BITISSET_I = function(data,bit) { return (data & (VL_UL(1)<<VL_BITBIT_I(bit))); }
|
|
|
|
|
|
|
|
var VL_EXTENDSIGN_I = this.VL_EXTENDSIGN_I = function(lbits, lhs) { return (-((lhs)&(VL_UL(1)<<(lbits-1)))); }
|
|
|
|
|
|
|
|
var VL_EXTEND_II = this.VL_EXTEND_II = function(obits,lbits,lhs) { return lhs; }
|
|
|
|
|
|
|
|
var VL_EXTENDS_II = this.VL_EXTENDS_II = function(x,lbits,lhs) {
|
|
|
|
return VL_EXTENDSIGN_I(lbits,lhs) | lhs;
|
|
|
|
}
|
|
|
|
|
|
|
|
var VL_EXTEND_II = this.VL_EXTEND_II = function(obits,lbits,lhs) { return lhs; }
|
|
|
|
|
|
|
|
var VL_NEGATE_I = this.VL_NEGATE_I = function(x) { return -x; }
|
|
|
|
|
|
|
|
var VL_LTS_III = this.VL_LTS_III = function(x,lbits,y,lhs,rhs) {
|
|
|
|
return VL_EXTENDS_II(x,lbits,lhs) < VL_EXTENDS_II(x,lbits,rhs); }
|
|
|
|
|
|
|
|
var VL_GTS_III = this.VL_GTS_III = function(x,lbits,y,lhs,rhs) {
|
|
|
|
return VL_EXTENDS_II(x,lbits,lhs) > VL_EXTENDS_II(x,lbits,rhs); }
|
|
|
|
|
|
|
|
var VL_LTES_III = this.VL_LTES_III = function(x,lbits,y,lhs,rhs) {
|
|
|
|
return VL_EXTENDS_II(x,lbits,lhs) <= VL_EXTENDS_II(x,lbits,rhs); }
|
|
|
|
|
|
|
|
var VL_GTES_III = this.VL_GTES_III = function(x,lbits,y,lhs,rhs) {
|
|
|
|
return VL_EXTENDS_II(x,lbits,lhs) >= VL_EXTENDS_II(x,lbits,rhs); }
|
|
|
|
|
|
|
|
//
|
|
|
|
|
2017-11-11 19:45:32 +00:00
|
|
|
function VerilatorBase() {
|
2017-11-17 21:01:07 +00:00
|
|
|
|
|
|
|
var VL_RAND_RESET_I = this.VL_RAND_RESET_I = function(bits) { return Math.floor(Math.random() * (1<<bits)); }
|
|
|
|
|
|
|
|
//
|
2017-11-11 19:45:32 +00:00
|
|
|
|
2017-11-13 05:24:19 +00:00
|
|
|
function vl_fatal(msg) {
|
|
|
|
console.log(msg);
|
|
|
|
}
|
|
|
|
|
2017-11-18 00:40:44 +00:00
|
|
|
var RESET_TICKS = 1000;
|
|
|
|
|
2017-11-13 05:24:19 +00:00
|
|
|
this.reset2 = function() {
|
2017-11-17 21:01:07 +00:00
|
|
|
if (this.reset !== undefined) {
|
2017-11-18 19:51:25 +00:00
|
|
|
this.reset = 0;
|
|
|
|
this.tick2();
|
2017-11-13 05:24:19 +00:00
|
|
|
this.reset = 1;
|
2017-11-18 00:40:44 +00:00
|
|
|
for (var i=0; i<RESET_TICKS; i++)
|
2017-11-13 05:24:19 +00:00
|
|
|
this.tick2();
|
|
|
|
this.reset = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.tick2 = function() {
|
|
|
|
this.clk = 0;
|
|
|
|
this.eval();
|
|
|
|
this.clk = 1;
|
2017-11-11 19:45:32 +00:00
|
|
|
this.eval();
|
|
|
|
}
|
|
|
|
|
|
|
|
var vlSymsp = {TOPp:this};
|
|
|
|
var maxVclockLoop = 1;
|
|
|
|
|
|
|
|
this.eval = function() {
|
|
|
|
vlSymsp.TOPp = this;
|
|
|
|
// Initialize
|
|
|
|
if (!vlSymsp.__Vm_didInit)
|
|
|
|
this._eval_initial_loop(vlSymsp);
|
|
|
|
// Evaluate till stable
|
|
|
|
//VL_DEBUG_IF(VL_PRINTF("\n----TOP Evaluate Vmain::eval\n"); );
|
|
|
|
var __VclockLoop = 0;
|
|
|
|
var __Vchange=1;
|
|
|
|
while (__Vchange) {
|
|
|
|
//VL_DEBUG_IF(VL_PRINTF(" Clock loop\n"););
|
|
|
|
vlSymsp.__Vm_activity = true;
|
|
|
|
this._eval(vlSymsp);
|
|
|
|
__Vchange = this._change_request(vlSymsp);
|
2017-11-13 05:24:19 +00:00
|
|
|
if (++__VclockLoop > 100) { vl_fatal("Verilated model didn't converge"); }
|
2017-11-11 19:45:32 +00:00
|
|
|
}
|
|
|
|
if (__VclockLoop > maxVclockLoop) {
|
|
|
|
maxVclockLoop = __VclockLoop;
|
|
|
|
console.log("Graph took " + maxVclockLoop + " iterations to stabilize");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this._eval_initial_loop = function(vlSymsp) {
|
|
|
|
vlSymsp.__Vm_didInit = true;
|
|
|
|
this._eval_initial(vlSymsp);
|
|
|
|
vlSymsp.__Vm_activity = true;
|
|
|
|
var __VclockLoop = 0;
|
|
|
|
var __Vchange=1;
|
|
|
|
while (__Vchange) {
|
|
|
|
this._eval_settle(vlSymsp);
|
|
|
|
this._eval(vlSymsp);
|
|
|
|
__Vchange = this._change_request(vlSymsp);
|
2017-11-13 05:24:19 +00:00
|
|
|
if (++__VclockLoop > 100) { vl_fatal("Verilated model didn't DC converge"); }
|
2017-11-11 19:45:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var VerilogPlatform = function(mainElement, options) {
|
|
|
|
var self = this;
|
2017-11-13 05:24:19 +00:00
|
|
|
var video, audio;
|
2017-11-15 00:12:52 +00:00
|
|
|
var videoWidth = 288;
|
|
|
|
var videoHeight = 248;
|
2017-11-11 19:45:32 +00:00
|
|
|
var idata, timer;
|
|
|
|
var gen;
|
|
|
|
var frameRate = 60;
|
2017-11-15 00:12:52 +00:00
|
|
|
var AUDIO_FREQ = 15750;
|
2017-11-14 14:33:15 +00:00
|
|
|
var current_output;
|
2017-11-15 00:12:52 +00:00
|
|
|
var paddle_x = 0;
|
|
|
|
var paddle_y = 0;
|
|
|
|
var switches = [0];
|
2017-11-21 16:15:08 +00:00
|
|
|
var inspect_obj, inspect_sym;
|
|
|
|
var inspect_data = new Uint32Array(videoWidth * videoHeight);
|
2017-11-11 19:45:32 +00:00
|
|
|
|
|
|
|
this.getPresets = function() { return VERILOG_PRESETS; }
|
|
|
|
|
2017-11-13 05:24:19 +00:00
|
|
|
var RGBLOOKUP = [
|
2017-11-21 16:15:08 +00:00
|
|
|
0xff222222,
|
|
|
|
0xff2222ff,
|
|
|
|
0xff22ff22,
|
|
|
|
0xff22ffff,
|
|
|
|
0xffff2222,
|
|
|
|
0xffff22ff,
|
|
|
|
0xffffff22,
|
2017-11-13 05:24:19 +00:00
|
|
|
0xffffffff,
|
|
|
|
];
|
|
|
|
|
2017-11-15 00:12:52 +00:00
|
|
|
function vidtick() {
|
|
|
|
gen.tick2();
|
|
|
|
audio.addSingleSample(0+gen.spkr); // TODO: sync with audio freq
|
|
|
|
}
|
|
|
|
|
2017-11-21 16:15:08 +00:00
|
|
|
function updateInspectionFrame() {
|
|
|
|
if (inspect_obj && inspect_sym) {
|
|
|
|
var COLOR_BIT_OFF = 0xffff3333;
|
|
|
|
var COLOR_BIT_ON = 0xffffffff;
|
|
|
|
for (var y=0; y<videoHeight; y++) {
|
|
|
|
var val = inspect_data[y * videoWidth];
|
|
|
|
var i = y * videoWidth + 16;
|
|
|
|
do {
|
|
|
|
idata[i] = (val & 1) ? COLOR_BIT_ON : COLOR_BIT_OFF;
|
|
|
|
i -= 2;
|
|
|
|
val >>= 1;
|
|
|
|
} while (val != 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateInspectionPostFrame() {
|
|
|
|
if (inspect_obj && inspect_sym) {
|
|
|
|
var ctx = video.getContext();
|
|
|
|
var val = inspect_data[inspect_data.length-1];
|
|
|
|
ctx.fillStyle = "black";
|
|
|
|
ctx.fillRect(18, videoHeight-8, 30, 8);
|
|
|
|
ctx.fillStyle = "white";
|
|
|
|
ctx.fillText(val.toString(10), 20, videoHeight-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-14 14:33:15 +00:00
|
|
|
function updateVideoFrame() {
|
2017-11-21 16:15:08 +00:00
|
|
|
var i=4; // TODO, start @ 0?
|
|
|
|
var trace=inspect_obj && inspect_sym;
|
2017-11-14 14:33:15 +00:00
|
|
|
for (var y=0; y<videoHeight; y++) {
|
2017-11-15 00:12:52 +00:00
|
|
|
gen.hpaddle = y > paddle_x ? 1 : 0;
|
|
|
|
gen.vpaddle = y > paddle_y ? 1 : 0;
|
2017-11-14 14:33:15 +00:00
|
|
|
for (var x=0; x<videoWidth; x++) {
|
2017-11-15 00:12:52 +00:00
|
|
|
vidtick();
|
2017-11-21 16:15:08 +00:00
|
|
|
if (trace) {
|
|
|
|
inspect_data[i] = inspect_obj[inspect_sym];
|
|
|
|
}
|
2017-11-14 14:33:15 +00:00
|
|
|
idata[i++] = RGBLOOKUP[gen.rgb];
|
|
|
|
}
|
|
|
|
var z=0;
|
2017-11-15 00:12:52 +00:00
|
|
|
while (!gen.hsync && z++<videoWidth) vidtick();
|
2017-11-21 16:15:08 +00:00
|
|
|
while (gen.hsync && z++<videoWidth) vidtick();
|
2017-11-14 14:33:15 +00:00
|
|
|
}
|
|
|
|
var z=0;
|
2017-11-15 00:12:52 +00:00
|
|
|
while (!gen.vsync && z++<videoWidth*80) vidtick();
|
2017-11-21 16:15:08 +00:00
|
|
|
while (gen.vsync && z++<videoWidth*80) vidtick();
|
|
|
|
updateInspectionFrame();
|
2017-11-14 14:33:15 +00:00
|
|
|
video.updateFrame();
|
2017-11-21 16:15:08 +00:00
|
|
|
updateInspectionPostFrame();
|
2017-11-14 14:33:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var yposlist = [];
|
2017-11-15 00:12:52 +00:00
|
|
|
var lasty = [];
|
2017-11-14 14:33:15 +00:00
|
|
|
|
|
|
|
function updateScopeFrame() {
|
|
|
|
var arr = current_output.ports;
|
|
|
|
if (!arr) return;
|
|
|
|
for (var i=0; i<idata.length; i++) {
|
|
|
|
if (idata[i])
|
|
|
|
idata[i] = 0; //<<= 1;
|
|
|
|
}
|
2017-11-21 16:15:08 +00:00
|
|
|
var COLOR_SIGNAL = 0xff22ff22;
|
|
|
|
var COLOR_BORDER = 0xff662222;
|
|
|
|
var COLOR_TRANS_SIGNAL = 0xff226622;
|
2017-11-14 14:33:15 +00:00
|
|
|
for (var x=0; x<videoWidth; x++) {
|
|
|
|
gen.clk ^= 1;
|
|
|
|
gen.eval();
|
|
|
|
var yb = 8;
|
|
|
|
var y1 = 0;
|
|
|
|
for (var i=0; i<arr.length; i++) {
|
|
|
|
var v = arr[i];
|
|
|
|
var lo = 0; // TODO? v.ofs?
|
|
|
|
var hi = v.len ? ((2 << v.len)-1) : 1;
|
|
|
|
var ys = hi>1 ? v.len*2+8 : 8;
|
|
|
|
var y2 = y1+ys;
|
|
|
|
var z = gen[v.name];
|
2017-11-15 00:12:52 +00:00
|
|
|
var y = Math.round(y2 - ys*((z-lo)/hi));
|
2017-11-14 14:33:15 +00:00
|
|
|
yposlist[i] = y2;
|
2017-11-15 00:12:52 +00:00
|
|
|
var ly = lasty[i];
|
|
|
|
if (x > 0 && ly != y) {
|
|
|
|
var dir = ly < y ? 1 : -1;
|
|
|
|
while ((ly += dir) != y && ly >= y1 && ly <= y2) {
|
|
|
|
idata[x + ly*videoWidth] = COLOR_TRANS_SIGNAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lasty[i] = y;
|
2017-11-14 14:33:15 +00:00
|
|
|
//idata[x + y1*videoWidth] = COLOR_BORDER;
|
|
|
|
//idata[x + y2*videoWidth] = COLOR_BORDER;
|
2017-11-15 00:12:52 +00:00
|
|
|
idata[x + y*videoWidth] = COLOR_SIGNAL;
|
2017-11-14 14:33:15 +00:00
|
|
|
y1 += ys+yb;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
video.updateFrame();
|
|
|
|
// draw labels
|
|
|
|
var ctx = video.getContext();
|
|
|
|
for (var i=0; i<arr.length; i++) {
|
|
|
|
var v = arr[i];
|
2017-11-21 16:15:08 +00:00
|
|
|
ctx.fillStyle = v.name == inspect_sym ? "yellow" : "white";
|
2017-11-14 14:33:15 +00:00
|
|
|
ctx.fillText(v.name, 1, yposlist[i]);
|
2017-11-15 00:12:52 +00:00
|
|
|
//ctx.textAlign = 'right';
|
|
|
|
//ctx.fillText(""+gen[v.name], videoWidth-1, yposlist[i]);
|
2017-11-14 14:33:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-17 21:01:07 +00:00
|
|
|
function clamp(minv,maxv,v) {
|
|
|
|
return (v < minv) ? minv : (v > maxv) ? maxv : v;
|
|
|
|
}
|
|
|
|
|
2017-11-11 19:45:32 +00:00
|
|
|
this.start = function() {
|
|
|
|
// TODO
|
|
|
|
video = new RasterVideo(mainElement,videoWidth,videoHeight);
|
|
|
|
video.create();
|
2017-11-21 16:15:08 +00:00
|
|
|
var ctx = video.getContext();
|
|
|
|
ctx.font = "8px TinyFont";
|
|
|
|
ctx.fillStyle = "white";
|
|
|
|
ctx.textAlign = "left";
|
2017-11-15 00:12:52 +00:00
|
|
|
setKeyboardFromMap(video, switches, VERILOG_KEYCODE_MAP);
|
|
|
|
$(video.canvas).mousemove(function(e) {
|
2017-11-20 01:32:58 +00:00
|
|
|
paddle_x = clamp(8,240,Math.floor(e.offsetX * video.canvas.width / $(video.canvas).width() - 20));
|
|
|
|
paddle_y = clamp(8,240,Math.floor(e.offsetY * video.canvas.height / $(video.canvas).height() - 20));
|
2017-11-15 00:12:52 +00:00
|
|
|
});
|
2017-11-13 05:24:19 +00:00
|
|
|
audio = new SampleAudio(AUDIO_FREQ);
|
2017-11-11 19:45:32 +00:00
|
|
|
idata = video.getFrameData();
|
|
|
|
// TODO: 15.7 kHz?
|
|
|
|
timer = new AnimationTimer(frameRate, function() {
|
|
|
|
if (!self.isRunning())
|
|
|
|
return;
|
2017-11-15 00:12:52 +00:00
|
|
|
gen.switches = switches[0];
|
2017-11-14 14:33:15 +00:00
|
|
|
if (gen.vsync !== undefined && gen.hsync !== undefined && gen.rgb !== undefined)
|
|
|
|
updateVideoFrame();
|
|
|
|
else
|
|
|
|
updateScopeFrame();
|
2017-11-11 19:45:32 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-11-15 00:12:52 +00:00
|
|
|
function printErrorCodeContext(e, code) {
|
|
|
|
if (e.lineNumber && e.message) {
|
|
|
|
var lines = code.split('\n');
|
|
|
|
var s = e.message + '\n';
|
|
|
|
for (var i=0; i<lines.length; i++) {
|
|
|
|
if (i > e.lineNumber-5 && i < e.lineNumber+5) {
|
|
|
|
s += lines[i] + '\n';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
console.log(s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-14 14:33:15 +00:00
|
|
|
this.loadROM = function(title, output) {
|
2017-11-15 00:12:52 +00:00
|
|
|
var mod;
|
|
|
|
try {
|
|
|
|
mod = new Function('base', output.code);
|
|
|
|
} catch (e) {
|
|
|
|
printErrorCodeContext(e, output.code);
|
|
|
|
throw e;
|
|
|
|
}
|
2017-11-11 19:45:32 +00:00
|
|
|
var base = new VerilatorBase();
|
|
|
|
gen = new mod(base);
|
|
|
|
gen.__proto__ = base;
|
2017-11-14 14:33:15 +00:00
|
|
|
current_output = output;
|
2017-11-21 16:15:08 +00:00
|
|
|
this.poweron();
|
2017-11-11 19:45:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this.isRunning = function() {
|
|
|
|
return timer && timer.isRunning();
|
|
|
|
}
|
|
|
|
this.pause = function() {
|
|
|
|
timer.stop();
|
2017-11-20 01:32:58 +00:00
|
|
|
if (gen.spkr !== undefined) audio.stop();
|
2017-11-11 19:45:32 +00:00
|
|
|
}
|
|
|
|
this.resume = function() {
|
|
|
|
timer.start();
|
2017-11-20 01:32:58 +00:00
|
|
|
if (gen.spkr !== undefined) audio.start();
|
2017-11-11 19:45:32 +00:00
|
|
|
}
|
|
|
|
|
2017-11-21 16:15:08 +00:00
|
|
|
this.poweron = function() {
|
2017-11-11 19:45:32 +00:00
|
|
|
gen._ctor_var_reset();
|
2017-11-21 16:15:08 +00:00
|
|
|
this.reset();
|
|
|
|
}
|
|
|
|
this.reset = function() {
|
2017-11-13 05:24:19 +00:00
|
|
|
gen.reset2();
|
2017-11-11 19:45:32 +00:00
|
|
|
}
|
|
|
|
this.getToolForFilename = function(fn) {
|
|
|
|
return "verilator";
|
|
|
|
}
|
|
|
|
this.getDefaultExtension = function() { return ".v"; };
|
2017-11-21 16:15:08 +00:00
|
|
|
|
|
|
|
this.inspect = function(name) {
|
|
|
|
if (!gen) return;
|
|
|
|
if (name && !name.match(/^\w+$/)) return;
|
|
|
|
var val = gen[name];
|
|
|
|
if (val === undefined && current_output.code) {
|
|
|
|
var re = new RegExp("(\\w+__DOT__" + name + ")\\b", "gm");
|
|
|
|
var m = re.exec(current_output.code);
|
|
|
|
if (m) {
|
|
|
|
name = m[1];
|
|
|
|
val = gen[name];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (val !== undefined) {
|
|
|
|
inspect_obj = gen;
|
|
|
|
inspect_sym = name;
|
|
|
|
} else {
|
|
|
|
inspect_obj = inspect_sym = null;
|
|
|
|
}
|
|
|
|
}
|
2017-11-11 19:45:32 +00:00
|
|
|
};
|
|
|
|
|
2017-11-20 01:32:58 +00:00
|
|
|
function traceTiming() {
|
|
|
|
// TODO: merge with main setCode(text)
|
|
|
|
var text = editor.getValue();
|
|
|
|
worker.postMessage({
|
|
|
|
code:text,
|
|
|
|
dependencies:loadFileDependencies(text),
|
|
|
|
platform:platform_id,
|
|
|
|
tool:'yosys'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-11-11 19:45:32 +00:00
|
|
|
////////////////
|
|
|
|
|
|
|
|
PLATFORMS['verilog'] = VerilogPlatform;
|