"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const baseplatform_1 = require("../common/baseplatform"); const emu_1 = require("../common/emu"); const audio_1 = require("../common/audio"); const waveform_1 = require("../ide/waveform"); const hdltypes_1 = require("../common/hdl/hdltypes"); const hdlruntime_1 = require("../common/hdl/hdlruntime"); const hdlwasm_1 = require("../common/hdl/hdlwasm"); const Split = require("split.js"); 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: 'alu.v', name: 'ALU', category: 'CPU' }, { 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' }, { id: 'cpu6502.v', name: '6502 CPU' }, { id: 'test_pattern.ice', name: 'Test Pattern', category: 'Silice' }, { id: 'copperbars.ice', name: 'Animated Bars' }, { id: 'rototexture.ice', name: 'Rotating Texture' }, //{id:'life.ice', name:'Conway\'s Life (Silice)'}, ]; var VERILOG_KEYCODE_MAP = (0, emu_1.makeKeycodeMap)([ [emu_1.Keys.LEFT, 0, 0x1], [emu_1.Keys.RIGHT, 0, 0x2], [emu_1.Keys.UP, 0, 0x4], [emu_1.Keys.DOWN, 0, 0x8], [emu_1.Keys.A, 0, 0x10], [emu_1.Keys.B, 0, 0x20], [emu_1.Keys.P2_LEFT, 1, 0x1], [emu_1.Keys.P2_RIGHT, 1, 0x2], [emu_1.Keys.P2_UP, 1, 0x4], [emu_1.Keys.P2_DOWN, 1, 0x8], [emu_1.Keys.P2_A, 1, 0x10], [emu_1.Keys.P2_B, 1, 0x20], [emu_1.Keys.START, 2, 0x1], [emu_1.Keys.P2_START, 2, 0x2], [emu_1.Keys.SELECT, 2, 0x4], [emu_1.Keys.P2_SELECT, 2, 0x8], [emu_1.Keys.VK_7, 2, 0x10], ]); const TRACE_BUFFER_DWORDS = 0x40000; const CYCLES_PER_FILL = 20; const SHOW_INTERNAL_SIGNALS = false; // TODO: make this a config value // PLATFORM var VerilogPlatform = function (mainElement, options) { this.__proto__ = new baseplatform_1.BasePlatform(); var video; var audio; var poller; var useAudio = false; var usePaddles = false; var videoWidth = 292; var videoHeight = 256; var maxVideoLines = 262 + 40; // vertical hold var idata; var timer; var timerCallback; var top; var cyclesPerFrame = (256 + 23 + 7 + 23) * 262; // 4857480/60 Hz // control inputs var switches = [0, 0, 0]; var keycode = 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 scanlineCycles = 0; 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() { top.tick2(1); if (useAudio) { audio.feedSample(top.state.spkr, 1); } resetKbdStrobe(); if (debugCond && debugCond()) { debugCond = null; } } function resetKbdStrobe() { if (keycode && keycode >= 128 && top.state.keystrobe) { // keystrobe = clear hi bit of key buffer keycode = keycode & 0x7f; top.state.keycode = keycode; } } // inner Platform class class _VerilogPlatform extends baseplatform_1.BasePlatform { getPresets() { return VERILOG_PRESETS; } setVideoParams(width, height, clock) { videoWidth = width; videoHeight = height; cyclesPerFrame = clock; maxVideoLines = height + 40; } async start() { //await loadScript('./lib/binaryen.js'); // TODO: remove video = new emu_1.RasterVideo(mainElement, videoWidth, videoHeight, { overscan: true }); video.create(); poller = (0, emu_1.setKeyboardFromMap)(video, switches, VERILOG_KEYCODE_MAP, (o, key, code, flags) => { if (flags & emu_1.KeyFlags.KeyDown) { keycode = code | 0x80; } }, true); // true = always send function var vcanvas = $(video.canvas); idata = video.getFrameData(); timerCallback = () => { if (!this.isRunning()) return; if (top) top.state.switches = switches[0]; this.updateFrame(); }; this.setFrameRate(60); // setup scope trace_buffer = new Uint32Array(TRACE_BUFFER_DWORDS); 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(); } // TODO: pollControls() { poller.poll(); } resize() { if (this.waveview) this.waveview.recreate(); } setGenInputs() { useAudio = audio != null && top.state.spkr != null; usePaddles = top.state.hpaddle != null || top.state.vpaddle != null; //TODO debugCond = this.getDebugCallback(); top.state.switches_p1 = switches[0]; top.state.switches_p2 = switches[1]; top.state.switches_gen = switches[2]; top.state.keycode = keycode; } updateVideoFrame() { //this.topdiv.show(); //show crt this.setGenInputs(); var fps = this.getFrameRate(); // darken the previous frame? var sync = fps > 45; if (!sync) { var mask = fps > 5 ? 0xe7ffffff : 0x7fdddddd; for (var i = 0; i < idata.length; i++) idata[i] &= mask; } // paint into frame, synched with vsync if full speed var trace = this.isScopeVisible(); this.updateVideoFrameCycles(Math.ceil(cyclesPerFrame * fps / 60), sync, trace); if (fps < 0.25) { idata[frameidx] = -1; } //this.restartDebugState(); this.refreshVideoFrame(); // set scope offset if (trace && this.waveview) { this.waveview.setCurrentTime(Math.floor(trace_index / trace_signals.length)); } } isScopeVisible() { return this.split.getSizes()[1] > 2; // TODO? } // TODO: merge with prev func advance(novideo) { this.setGenInputs(); this.updateVideoFrameCycles(cyclesPerFrame, true, false); if (!novideo) { this.refreshVideoFrame(); } if (this.isBlocked()) { this.pause(); } return cyclesPerFrame; //TODO? } 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(CYCLES_PER_FILL * trace_signals.length); if (done) this.pause(); // TODO? // TODO } updateScope() { // create scope, if visible if (this.isScopeVisible()) { if (!this.waveview) { this.waveview = new waveform_1.WaveformView(this.wavediv[0], this); } else { this.waveview.refresh(); } } } updateFrame() { if (!top) 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 < videoHeight - 2; y++) { for (var x = 0; x < videoWidth; x++) { var val = inspect_data[i]; idata[i++] = (val & 1) ? COLOR_BIT_ON : COLOR_BIT_OFF; } } } } 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, 100, 8); ctx.fillStyle = "white"; ctx.fillText(val.toString(10) + " $" + val.toString(16), 20, videoHeight - 1); } } updateVideoFrameCycles(ncycles, sync, trace) { ncycles |= 0; var inspect = inspect_obj != null && inspect_sym != null; // use fast trace buffer-based update? if (sync && !trace && !inspect && top.trace != null && scanlineCycles > 0) { this.updateVideoFrameFast(top); this.updateRecorder(); return; } // use slow cycle-by-cycle version (needed on 1st frame to set scanlineCycles anyway) if (!sync) scanlineCycles = 0; var trace0 = trace_index; while (ncycles--) { if (trace) { this.snapshotTrace(); if (trace_index == trace0) trace = false; // kill trace when wraps around } vidtick(); if (framex++ < videoWidth) { if (framey < videoHeight) { if (inspect) { inspect_data[frameidx] = inspect_obj[inspect_sym]; } let rgb = top.state.rgb; idata[frameidx] = rgb & 0x80000000 ? rgb : RGBLOOKUP[rgb & 15]; frameidx++; } } else if (!framehsync && top.state.hsync) { framehsync = true; } else if ((framehsync && !top.state.hsync) || framex > videoWidth * 2) { if (sync && framehsync) scanlineCycles = framex; // set cycles/scanline for fast update function framehsync = false; framex = 0; framey++; top.state.hpaddle = framey > video.paddle_x ? 1 : 0; top.state.vpaddle = framey > video.paddle_y ? 1 : 0; } if (framey > maxVideoLines || top.state.vsync) { framevsync = true; framey = 0; framex = 0; frameidx = 0; top.state.hpaddle = 0; top.state.vpaddle = 0; } else { var wasvsync = framevsync; framevsync = false; if (sync && wasvsync) { this.updateRecorder(); return; // exit when vsync ends } } } } tick2(cycles) { // if a key is pressed, check for strobe after every cycle if (keycode >= 128) { while (cycles-- > 0) { top.tick2(1); resetKbdStrobe(); } } else { top.tick2(cycles); } } // use trace buffer to update video updateVideoFrameFast(tmod) { if (scanlineCycles <= 0) throw new Error(`scanlineCycles must be > 0`); var maxLineCycles = 1009; // prime number so we eventually sync up var nextlineCycles = scanlineCycles || maxLineCycles; frameidx = 0; // audio feed function spkr() { if (useAudio) audio.feedSample(tmod.trace.spkr, 1); } // iterate through a frame of scanlines + room for vsync for (framey = 0; framey < videoHeight * 2; framey++) { if (usePaddles && framey < videoHeight) { top.state.hpaddle = framey > video.paddle_x ? 1 : 0; top.state.vpaddle = framey > video.paddle_y ? 1 : 0; } // generate frames in trace buffer if (nextlineCycles > 0) { this.tick2(nextlineCycles); } // convert trace buffer to video/audio var n = 0; // draw scanline visible pixels if (framey < videoHeight) { for (framex = 0; framex < videoWidth; framex++) { var rgb = tmod.trace.rgb; //if (tmod.trace.hsync) rgb ^= Math.random() * 15; idata[frameidx++] = rgb & 0x80000000 ? rgb : RGBLOOKUP[rgb & 15]; spkr(); tmod.nextTrace(); } n += videoWidth; } // find hsync var hsyncStart = 0, hsyncEnd = 0; while (n < nextlineCycles) { if (tmod.trace.hsync) { if (!hsyncStart) hsyncStart = n; hsyncEnd = n; } else if (hsyncEnd) { break; } spkr(); tmod.nextTrace(); n++; } // see if our scanline cycle count is stable (can't read tmod.trace after end of line) if (hsyncStart < hsyncEnd && hsyncEnd == nextlineCycles - 1) { // scanline cycle count locked in, reset buffer to improve cache locality nextlineCycles = scanlineCycles; } else if (hsyncEnd > 0) { // our cycle count is not in sync with scanline // say our scanline lasts 100 cycles // we just read 300 cycles, and hsync ended at 80 // we'll toss the extra cycles in the buffer // next scanline should end @ (80 + 100*N) cycles // could be 180, 280, 380 ... // so we should read 100*N - 300 cycles where N > 2 // TODO: determine scanlineCycles here instead of letting slow loop do it let newCycles = scanlineCycles * 2 - ((nextlineCycles - n) % scanlineCycles); //console.log('scanline', framey, scanlineCycles, nextlineCycles, n, hsyncStart, hsyncEnd, newCycles); nextlineCycles = newCycles; } else { nextlineCycles = maxLineCycles; } tmod.resetTrace(); // exit when vsync starts and then stops if (tmod.trace.vsync) { framevsync = true; top.state.hpaddle = 0; top.state.vpaddle = 0; framex = framey = frameidx = 0; } else if (framevsync) { framevsync = false; break; } } } snapshotTrace() { var arr = trace_signals; for (var i = 0; i < arr.length; i++) { var v = arr[i]; var z = top.state[v.name]; trace_buffer[trace_index] = z + 0; trace_index++; } if (trace_index >= trace_buffer.length - arr.length) trace_index = 0; } fillTraceBuffer(count) { var max_index = Math.min(trace_buffer.length - trace_signals.length, trace_index + count); while (trace_index < max_index) { this.snapshotTrace(); if (!top.isStopped() && !top.isFinished()) { top.tick(); } if (trace_index == 0) break; } top.state.reset = 0; // need to de-assert reset when using no-video mode return (trace_index == 0); } getSignalMetadata() { return trace_signals; } getSignalData(index, start, len) { // 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; } setSignalValue(index, value) { var meta = this.getSignalMetadata()[index]; top.state[meta.label] = value; this.reset(); } 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); } } dispose() { if (top) { top.dispose(); top = null; } } async loadROM(title, output) { var unit = output; var topmod = unit.modules['TOP']; if (unit.modules && topmod) { { // initialize top module and constant pool var useWASM = true; var topcons = useWASM ? hdlwasm_1.HDLModuleWASM : hdlruntime_1.HDLModuleJS; var _top = new topcons(topmod, unit.modules['@CONST-POOL@']); _top.getFileData = this.sourceFileFetch; await _top.init(); this.dispose(); top = _top; // create signal array var signals = []; for (var key in topmod.vardefs) { var vardef = topmod.vardefs[key]; if ((0, hdltypes_1.isLogicType)(vardef.dtype)) { signals.push({ name: key, label: vardef.origName, input: vardef.isInput, output: vardef.isOutput, len: vardef.dtype.left + 1 }); } } trace_signals = signals; if (!SHOW_INTERNAL_SIGNALS) { trace_signals = trace_signals.filter((v) => { return !v.label.startsWith("__V"); }); // remove __Vclklast etc } trace_index = 0; // reset if (top instanceof hdlwasm_1.HDLModuleWASM) { top.randomizeOnReset = true; } // query output signals -- video or not? this.hasvideo = top.state.vsync != null && top.state.hsync != null && top.state.rgb != null; if (this.hasvideo) { const IGNORE_SIGNALS = ['clk', 'reset']; trace_signals = trace_signals.filter((v) => { return IGNORE_SIGNALS.indexOf(v.name) < 0; }); // remove clk, reset this.showVideoControls(); } else { this.hideVideoControls(); } } } // randomize values top.powercycle(); // replace program ROM, if using the assembler // TODO: fix this, it ain't good if (output.program_rom && output.program_rom_variable) { if (top.state[output.program_rom_variable]) { if (top.state[output.program_rom_variable].length != output.program_rom.length) alert("ROM size mismatch -- expected " + top.state[output.program_rom_variable].length + " got " + output.program_rom.length); else top.state[output.program_rom_variable].set(output.program_rom); } else { alert("No program_rom variable found (" + output.program_rom_variable + ")"); } } // restart audio this.restartAudio(); if (this.waveview) { this.waveview.recreate(); } // assert reset pin, wait 100 cycles if using video this.reset(); } showVideoControls() { $("#speed_bar").show(); $("#run_bar").show(); $("#dbg_record").show(); } hideVideoControls() { $("#speed_bar").hide(); $("#run_bar").hide(); $("#dbg_record").hide(); } restartAudio() { // stop/start audio var hasAudio = top && top.state.spkr != null && frameRate > 1; if (audio && !hasAudio) { audio.stop(); audio = null; } else if (!audio && hasAudio) { audio = new audio_1.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(); } isBlocked() { return top && top.isFinished(); } isStopped() { return top && top.isStopped(); } 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 emu_1.AnimationTimer(fps, timerCallback); if (running) timer.start(); } if (audio) { audio.stop(); audio = null; } this.restartAudio(); } getFrameRate() { return frameRate; } reset() { if (!top) return; // TODO: how do we avoid clobbering user-modified signals? trace_index = 0; if (trace_buffer) trace_buffer.fill(0); if (video) video.setRotate(top.state.rotate ? -90 : 0); $("#verilog_bar").hide(); if (this.hasvideo) { top.state.reset = 1; top.tick2(100); top.state.reset = 0; } else { top.state.reset = 1; // reset will be de-asserted later this.resume(); // TODO? } } tick() { if (!top) return; top.tick2(1); } getToolForFilename(fn) { if (fn.endsWith(".asm")) return "jsasm"; else if (fn.endsWith(".ice")) return "silice"; else return "verilator"; } getDefaultExtension() { return ".v"; } ; inspect(name) { if (!top) return; // check for valid identifier if (!name || !name.match(/^\w+$/)) { inspect_obj = inspect_sym = null; return; } // search for partial name var val; for (let key in top.state) { if (key == name || key.endsWith("$" + name)) { name = key; val = top.state[name]; } } // did we find a number? if (typeof (val) === 'number') { inspect_obj = top.state; inspect_sym = name; } else { inspect_obj = inspect_sym = null; } } // DEBUGGING getDebugTree() { return { runtime: top, state: top && top.getGlobals() }; } saveState() { return { o: top && top.saveState() }; } loadState(state) { if (state.o) top.loadState(state.o); } saveControlsState() { return { p1x: video.paddle_x, p1y: video.paddle_y, sw0: switches[0], sw1: switches[1], sw2: switches[2], keycode: keycode }; } loadControlsState(state) { video.paddle_x = state.p1x; video.paddle_y = state.p1y; switches[0] = state.sw0; switches[1] = state.sw1; switches[2] = state.sw2; keycode = state.keycode; } getDownloadFile() { if (top instanceof hdlruntime_1.HDLModuleJS) { return { extension: ".js", blob: new Blob([top.getJSCode()], { type: "text/plain" }) }; } else if (top instanceof hdlwasm_1.HDLModuleWASM) { return { extension: ".wat", blob: new Blob([top.bmod.emitText()], { type: "text/plain" }) }; } } getHDLModuleRunner() { return top; } showHelp() { return "https://8bitworkshop.com/docs/platforms/verilog/"; } } // 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(mainElement, options); this.getPresets = function () { return VERILOG_VGA_PRESETS; }; this.setVideoParams(800 - 64, 520, 25000000); }; //////////////// emu_1.PLATFORMS['verilog'] = VerilogPlatform; emu_1.PLATFORMS['verilog-vga'] = VerilogVGAPlatform; emu_1.PLATFORMS['verilog-test'] = VerilogPlatform; //# sourceMappingURL=verilog.js.map