2022-02-21 15:35:52 +00:00
|
|
|
"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' },
|
|
|
|
{ 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 (Silice)' },
|
|
|
|
{ id: 'copperbars.ice', name: 'Animated Bars (Silice)' },
|
|
|
|
{ id: 'rototexture.ice', name: 'Rotating Texture (Silice)' },
|
|
|
|
//{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) => {
|
2022-09-14 02:55:50 +00:00
|
|
|
if (flags & emu_1.KeyFlags.KeyDown) {
|
2022-02-21 15:35:52 +00:00
|
|
|
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 = $('<div class="emuspacer">').appendTo(overlay);
|
|
|
|
vcanvas.appendTo(this.topdiv);
|
|
|
|
this.wavediv = $('<div class="emuscope">').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;
|
|
|
|
}
|
2022-09-16 04:59:21 +00:00
|
|
|
showHelp() {
|
|
|
|
return "https://8bitworkshop.com/docs/platforms/verilog/";
|
2022-02-21 15:35:52 +00:00
|
|
|
}
|
|
|
|
} // 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
|