"use strict"; import { Platform, BasePlatform } from "../baseplatform"; import { PLATFORMS, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap } from "../emu"; import { SampleAudio } from "../audio"; import { safe_extend, clamp } from "../util"; import { WaveformView, WaveformProvider, WaveformMeta } from "../waveform"; declare var Split; var VERILOG_PRESETS = [ {id:'clock_divider.v', name:'Clock Divider'}, {id:'binary_counter.v', name:'Binary Counter'}, {id:'hvsync_generator.v', name:'Video Sync Generator'}, {id:'test_hvsync.v', name:'Test Pattern'}, {id:'7segment.v', name:'7-Segment Decoder'}, {id:'digits10.v', name:'Bitmapped Digits'}, {id:'scoreboard.v', name:'Scoreboard'}, {id:'ball_absolute.v', name:'Ball Motion (absolute position)'}, {id:'ball_slip_counter.v', name:'Ball Motion (slipping counter)'}, {id:'ball_paddle.v', name:'Brick Smash Game'}, {id:'chardisplay.v', name:'RAM Text Display'}, {id:'switches.v', name:'Switch Inputs'}, {id:'paddles.v', name:'Paddle Inputs'}, {id:'sprite_bitmap.v', name:'Sprite Bitmaps'}, {id:'sprite_renderer.v', name:'Sprite Rendering'}, {id:'racing_game.v', name:'Racing Game'}, {id:'sprite_rotation.v', name:'Sprite Rotation'}, {id:'tank.v', name:'Tank Game'}, {id:'sound_generator.v', name:'Sound Generator'}, {id:'lfsr.v', name:'Linear Feedback Shift Register'}, {id:'starfield.v', name:'Scrolling Starfield'}, {id:'cpu8.v', name:'Simple 8-Bit CPU'}, {id:'racing_game_cpu.v', name:'Racing Game with CPU'}, {id:'framebuffer.v', name:'Frame Buffer'}, {id:'tile_renderer.v', name:'Tile Renderer'}, {id:'sprite_scanline_renderer.v', name:'Sprite Scanline Renderer'}, {id:'cpu16.v', name:'16-Bit CPU'}, {id:'cpu_platform.v', name:'CPU Platform'}, {id:'test2.asm', name:'16-bit ASM Game'}, ]; 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_A, 1, 0x1], [Keys.VK_D, 1, 0x2], [Keys.VK_W, 1, 0x4], [Keys.VK_S, 1, 0x8], [Keys.VK_Z, 1, 0x10], [Keys.VK_X, 1, 0x20], [Keys.VK_1, 2, 0x1], [Keys.VK_2, 2, 0x2], [Keys.VK_5, 2, 0x4], [Keys.VK_6, 2, 0x8], [Keys.VK_7, 2, 0x10], ]); // SIMULATOR STUFF (should be global) export var vl_finished = false; export var vl_stopped = false; export function VL_UL(x) { return x|0; } export function VL_ULL(x) { return x|0; } export function VL_TIME_Q() { return (new Date().getTime())|0; } /// Return true if data[bit] set export function VL_BITISSET_I(data,bit) { return (data & (VL_UL(1)< VL_EXTENDS_II(x,lbits,rhs)) ? 1 : 0; } export function VL_LTES_III(x,lbits,y,lhs,rhs) { return (VL_EXTENDS_II(x,lbits,lhs) <= VL_EXTENDS_II(x,lbits,rhs)) ? 1 : 0; } export function VL_GTES_III(x,lbits,y,lhs,rhs) { return (VL_EXTENDS_II(x,lbits,lhs) >= VL_EXTENDS_II(x,lbits,rhs)) ? 1 : 0; } export function VL_MODDIV_III(lbits,lhs,rhs) { return (((rhs)==0)?0:(lhs)%(rhs)); } export function VL_MODDIVS_III(lbits,lhs,rhs) { return (((rhs)==0)?0:(lhs)%(rhs)); } export function VL_REDXOR_32(r) { r=(r^(r>>1)); r=(r^(r>>2)); r=(r^(r>>4)); r=(r^(r>>8)); r=(r^(r>>16)); return r; } export var VL_WRITEF = console.log; // TODO: $write export function vl_finish(filename,lineno,hier) { console.log("Finished at " + filename + ":" + lineno, hier); vl_finished = true; } export function vl_stop(filename,lineno,hier) { console.log("Stopped at " + filename + ":" + lineno, hier); vl_stopped = true; } export function VL_RAND_RESET_I(bits) { return 0 | Math.floor(Math.random() * (1< 100) { this.vl_fatal("Verilated model didn't converge"); } } if (__VclockLoop > this.maxVclockLoop) { this.maxVclockLoop = __VclockLoop; if (this.maxVclockLoop > 1) { console.log("Graph took " + this.maxVclockLoop + " iterations to stabilize"); $("#verilog_bar").show(); $("#settle_label").text(this.maxVclockLoop+""); } } this.totalTicks++; } _eval_initial_loop(vlSymsp) { vlSymsp.TOPp = this; 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); if (++__VclockLoop > 100) { this.vl_fatal("Verilated model didn't DC converge"); } } } } // PLATFORM var VerilogPlatform = function(mainElement, options) { this.__proto__ = new (BasePlatform as any)(); var video, audio; var useAudio = false; var videoWidth = 292; var videoHeight = 256; var maxVideoLines = 262+40; // vertical hold var idata, timer, timerCallback; var gen; var cyclesPerFrame = (256+23+7+23)*262; // 4857480/60 Hz var current_output; // control inputs var switches = [0,0,0]; // inspect feature var inspect_obj, inspect_sym; var inspect_data = new Uint32Array(videoWidth * videoHeight); // for scope var module_name; //var trace_ports; var trace_signals; var trace_buffer; var trace_index; // for virtual CRT var framex=0; var framey=0; var frameidx=0; var framehsync=false; var framevsync=false; var RGBLOOKUP = [ 0xff222222, 0xff2222ff, 0xff22ff22, 0xff22ffff, 0xffff2222, 0xffff22ff, 0xffffff22, 0xffffffff, 0xff999999, 0xff9999ff, 0xff99ff99, 0xff99ffff, 0xffff9999, 0xffff99ff, 0xffffff99, 0xff666666, ]; var debugCond; var frameRate = 0; function vidtick() { gen.tick2(); if (useAudio) audio.feedSample(gen.spkr*(1.0/255.0), 1); if (debugCond && debugCond()) debugCond = null; } function shadowText(ctx, txt, x, y) { ctx.shadowColor = "black"; ctx.shadowBlur = 0; ctx.shadowOffsetY = -1; ctx.shadowOffsetX = 0; ctx.fillText(txt, x, y); ctx.shadowOffsetY = 1; ctx.shadowOffsetX = 0; ctx.fillText(txt, x, y); ctx.shadowOffsetY = 0; ctx.shadowOffsetX = -1; ctx.fillText(txt, x, y); ctx.shadowOffsetY = 0; ctx.shadowOffsetX = 1; ctx.fillText(txt, x, y); ctx.shadowOffsetX = 0; } // inner Platform class class _VerilogPlatform extends BasePlatform implements WaveformProvider { waveview : WaveformView; wavediv : JQuery; topdiv : JQuery; split; hasvideo : boolean; getPresets() { return VERILOG_PRESETS; } setVideoParams(width:number, height:number, clock:number) { videoWidth = width; videoHeight = height; cyclesPerFrame = clock; maxVideoLines = height+40; } start() { video = new RasterVideo(mainElement,videoWidth,videoHeight,{overscan:true}); video.create(); var ctx = video.getContext(); ctx.font = "8px TinyFont"; ctx.fillStyle = "white"; ctx.textAlign = "left"; setKeyboardFromMap(video, switches, VERILOG_KEYCODE_MAP); var vcanvas = $(video.canvas); idata = video.getFrameData(); timerCallback = () => { if (!this.isRunning()) return; gen.switches = switches[0]; this.updateFrame(); }; this.setFrameRate(60); // setup scope trace_buffer = new Uint32Array(0x20000); var overlay = $("#emuoverlay").show(); this.topdiv = $('
').appendTo(overlay); vcanvas.appendTo(this.topdiv); this.wavediv = $('
').appendTo(overlay); this.split = Split( [this.topdiv[0], this.wavediv[0]], { minSize: [0,0], sizes: [99,1], direction: 'vertical', gutterSize: 16, onDrag: () => { this.resize(); //if (this.waveview) this.waveview.recreate(); //vcanvas.css('position','relative'); //vcanvas.css('top', -this.wavediv.height()+'px'); }, }); // setup mouse events video.setupMouseEvents(); } resize() { if (this.waveview) this.waveview.recreate(); } setGenInputs() { useAudio = (audio != null); //TODO debugCond = this.getDebugCallback(); gen.switches_p1 = switches[0]; gen.switches_p2 = switches[1]; gen.switches_gen = switches[2]; } updateVideoFrame() { //this.topdiv.show(); //show crt this.setGenInputs(); var fps = this.getFrameRate(); // darken the previous frame? if (fps < 45) { var mask = fps > 5 ? 0xe7ffffff : 0x7fdddddd; for (var i=0; i 45; var trace = this.isScopeVisible(); this.updateVideoFrameCycles(cyclesPerFrame * fps/60 + 1, sync, trace); //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 advance(novideo : boolean) { this.setGenInputs(); this.updateVideoFrameCycles(cyclesPerFrame, true, false); gen.__unreset(); if (!novideo) { this.refreshVideoFrame(); } } refreshVideoFrame() { this.updateInspectionFrame(); video.updateFrame(); this.updateInspectionPostFrame(); } refreshScopeOverlay() { // TODO } updateScopeFrame() { this.split.setSizes([0,100]); // ensure scope visible //this.topdiv.hide();// hide crt 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] as HTMLElement, this); } else { this.waveview.refresh(); } } } updateFrame() { if (!gen) return; if (this.hasvideo) this.updateVideoFrame(); else this.updateScopeFrame(); this.updateScope(); } updateInspectionFrame() { useAudio = false; if (inspect_obj && inspect_sym) { var COLOR_BIT_OFF = 0xffff6666; var COLOR_BIT_ON = 0xffff9999; var i = videoWidth; for (var y=0; y videoWidth*2) { framehsync = false; framex = 0; framey++; gen.hpaddle = framey > video.paddle_x ? 1 : 0; gen.vpaddle = framey > video.paddle_y ? 1 : 0; } if (framey > maxVideoLines || gen.vsync) { framevsync = true; framey = 0; framex = 0; frameidx = 0; gen.hpaddle = 0; gen.vpaddle = 0; } else { var wasvsync = framevsync; framevsync = false; if (sync && wasvsync) { this.updateRecorder(); return; // exit when vsync ends } } } } snapshotTrace() { var arr = trace_signals; for (var i=0; i= trace_buffer.length - arr.length) trace_index = 0; } 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(); if (trace_index == 0) break; } gen.__unreset(); return (trace_index == 0); } getSignalMetadata() : WaveformMeta[] { return trace_signals; } 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) { if (e.lineNumber && e.message) { var lines = code.split('\n'); var s = e.message + '\n'; for (var i=0; i e.lineNumber-5 && i < e.lineNumber+5) { s += lines[i] + '\n'; } } console.log(s); } } loadROM(title, output) { var mod; if (output.code) { // is code identical? if (current_output && current_output.code == output.code) { } else { try { mod = new Function('base', output.code); } catch (e) { this.printErrorCodeContext(e, output.code); throw e; } // compile Verilog code var base = new (VerilatorBase as any)(); gen = new mod(); //$.extend(gen, base); 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); // combine ports + signals trace_signals = trace_signals.filter((v) => { return !v.name.startsWith("__V"); }); // remove __Vclklast etc for (var v of trace_signals) { v.label = v.name.replace(/__DOT__/g, "."); // make nicer name } trace_index = 0; // power on module this.poweron(); // query output this.hasvideo = gen.vsync !== undefined && gen.hsync !== undefined && gen.rgb !== undefined; if (this.hasvideo) { const IGNORE_SIGNALS = ['clk','reset']; trace_signals = trace_signals.filter((v) => { return IGNORE_SIGNALS.indexOf(v.name)<0; }); // remove clk, reset $("#speed_bar").show(); } else { $("#speed_bar").hide(); } } } // replace program ROM, if using the assembler if (output.program_rom && output.program_rom_variable) { if (gen[output.program_rom_variable]) { if (gen[output.program_rom_variable].length != output.program_rom.length) alert("ROM size mismatch -- expected " + gen[output.program_rom_variable].length + " got " + output.program_rom.length); else gen[output.program_rom_variable] = output.program_rom; } else { alert("No program_rom variable found (" + output.program_rom_variable + ")"); } this.reset(); } // restart audio this.restartAudio(); // destroy scope if (this.waveview) { this.waveview.destroy(); this.waveview = null; } } restartAudio() { // stop/start audio var hasAudio = gen && gen.spkr !== undefined && frameRate > 1; if (audio && !hasAudio) { audio.stop(); audio = null; } else if (!audio && hasAudio) { audio = new SampleAudio(cyclesPerFrame * this.getFrameRate()); if (this.isRunning()) audio.start(); } } isRunning() { return timer && timer.isRunning(); } pause() { timer.stop(); if (audio) audio.stop(); } resume() { timer.start(); if (audio) audio.start(); } setFrameRate(rateHz) { frameRate = rateHz; var fps = Math.min(60, rateHz*cyclesPerFrame); if (!timer || timer.frameRate != fps) { var running = this.isRunning(); if (timer) timer.stop(); timer = new AnimationTimer(fps, timerCallback); if (running) timer.start(); } if (audio) { audio.stop(); audio = null; } this.restartAudio(); } getFrameRate() { return frameRate; } poweron() { gen._ctor_var_reset(); this.reset(); } reset() { if (!gen) return; gen.__reset(); trace_index = 0; if (trace_buffer) trace_buffer.fill(0); if (video) video.setRotate(gen.rotate ? -90 : 0); $("#verilog_bar").hide(); if (!this.hasvideo) this.resume(); // TODO? } tick() { gen.tick2(); } getToolForFilename(fn) { if (fn.endsWith("asm")) return "jsasm"; else return "verilator"; } getDefaultExtension() { return ".v"; }; inspect(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 (typeof(val) === 'number') { inspect_obj = gen; inspect_sym = name; } else { inspect_obj = inspect_sym = null; } } // DEBUGGING // TODO: bind() a function to avoid depot? saveState() { var state = { T:gen.ticks(), o:safe_extend(true, {}, gen) }; state.o.TOPp = null; return state; } loadState(state) { gen = safe_extend(true, gen, state.o); gen.setTicks(state.T); gen.TOPp = gen; //console.log(gen, state.o); } saveControlsState() { return { p1x: video.paddle_x, p1y: video.paddle_y, sw0: switches[0], sw1: switches[1], sw2: switches[2], }; } loadControlsState(state) { video.paddle_x = state.p1x; video.paddle_y = state.p1y; switches[0] = state.sw0; switches[1] = state.sw1; switches[2] = state.sw2; } } // end of inner class return new _VerilogPlatform(); }; //////////////// var VERILOG_VGA_PRESETS = [ {id:'hvsync_generator.v', name:'Video Sync Generator'}, {id:'test_hvsync.v', name:'Test Pattern'}, {id:'chardisplay.v', name:'RAM Text Display'}, {id:'starfield.v', name:'Scrolling Starfield'}, {id:'ball_paddle.v', name:'Brick Smash Game'}, ]; var VerilogVGAPlatform = function(mainElement, options) { this.__proto__ = new (VerilogPlatform as any)(mainElement, options); this.getPresets = function() { return VERILOG_VGA_PRESETS; } this.setVideoParams(800-64, 520, 25000000); } //////////////// PLATFORMS['verilog'] = VerilogPlatform; PLATFORMS['verilog-vga'] = VerilogVGAPlatform;