2017-11-11 19:45:32 +00:00
|
|
|
|
2019-10-26 01:55:50 +00:00
|
|
|
import { Platform, BasePlatform } from "../common/baseplatform";
|
|
|
|
import { PLATFORMS, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, getMousePos, KeyFlags } from "../common/emu";
|
|
|
|
import { SampleAudio } from "../common/audio";
|
2021-06-28 20:36:47 +00:00
|
|
|
import { safe_extend } from "../common/util";
|
2019-10-26 01:55:50 +00:00
|
|
|
import { WaveformView, WaveformProvider, WaveformMeta } from "../ide/waveform";
|
2021-06-28 20:36:47 +00:00
|
|
|
import { setFrameRateUI, loadScript } from "../ide/ui";
|
|
|
|
import { HDLUnit, isLogicType } from "../common/hdl/hdltypes";
|
|
|
|
import { HDLModuleJS } from "../common/hdl/hdlruntime";
|
2018-09-12 02:28:30 +00:00
|
|
|
|
|
|
|
declare var Split;
|
2018-08-16 23:19:20 +00:00
|
|
|
|
2021-06-28 20:36:47 +00:00
|
|
|
interface WaveformSignal extends WaveformMeta {
|
|
|
|
name: string;
|
|
|
|
}
|
|
|
|
|
2017-11-11 19:45:32 +00:00
|
|
|
var VERILOG_PRESETS = [
|
2017-11-15 00:12:52 +00:00
|
|
|
{id:'clock_divider.v', name:'Clock Divider'},
|
2018-12-15 16:10:32 +00:00
|
|
|
{id:'binary_counter.v', name:'Binary Counter'},
|
2017-11-15 00:12:52 +00:00
|
|
|
{id:'hvsync_generator.v', name:'Video Sync Generator'},
|
|
|
|
{id:'test_hvsync.v', name:'Test Pattern'},
|
2018-02-03 20:20:56 +00:00
|
|
|
{id:'7segment.v', name:'7-Segment Decoder'},
|
2018-02-21 19:32:11 +00:00
|
|
|
{id:'digits10.v', name:'Bitmapped Digits'},
|
2018-02-07 00:07:40 +00:00
|
|
|
{id:'scoreboard.v', name:'Scoreboard'},
|
2018-06-01 17:33:37 +00:00
|
|
|
{id:'ball_absolute.v', name:'Ball Motion (absolute position)'},
|
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'},
|
2018-10-05 18:02:46 +00:00
|
|
|
{id:'chardisplay.v', name:'RAM Text Display'},
|
2018-08-25 02:55:16 +00:00
|
|
|
{id:'switches.v', name:'Switch Inputs'},
|
|
|
|
{id:'paddles.v', name:'Paddle Inputs'},
|
2017-11-21 21:36:38 +00:00
|
|
|
{id:'sprite_bitmap.v', name:'Sprite Bitmaps'},
|
|
|
|
{id:'sprite_renderer.v', name:'Sprite Rendering'},
|
2018-06-04 02:01:40 +00:00
|
|
|
{id:'racing_game.v', name:'Racing Game'},
|
2018-02-03 20:20:56 +00:00
|
|
|
{id:'sprite_rotation.v', name:'Sprite Rotation'},
|
|
|
|
{id:'tank.v', name:'Tank Game'},
|
2018-02-21 19:32:11 +00:00
|
|
|
{id:'sound_generator.v', name:'Sound Generator'},
|
2018-02-14 01:04:52 +00:00
|
|
|
{id:'lfsr.v', name:'Linear Feedback Shift Register'},
|
|
|
|
{id:'starfield.v', name:'Scrolling Starfield'},
|
2021-05-07 14:50:04 +00:00
|
|
|
{id:'alu.v', name:'ALU'},
|
2018-02-21 19:32:11 +00:00
|
|
|
{id:'cpu8.v', name:'Simple 8-Bit CPU'},
|
|
|
|
{id:'racing_game_cpu.v', name:'Racing Game with CPU'},
|
2018-02-15 18:31:32 +00:00
|
|
|
{id:'framebuffer.v', name:'Frame Buffer'},
|
2018-02-27 04:48:36 +00:00
|
|
|
{id:'tile_renderer.v', name:'Tile Renderer'},
|
2018-02-21 19:32:11 +00:00
|
|
|
{id:'sprite_scanline_renderer.v', name:'Sprite Scanline Renderer'},
|
2018-02-28 03:35:42 +00:00
|
|
|
{id:'cpu16.v', name:'16-Bit CPU'},
|
2018-02-28 17:09:29 +00:00
|
|
|
{id:'cpu_platform.v', name:'CPU Platform'},
|
2018-06-11 17:01:09 +00:00
|
|
|
{id:'test2.asm', name:'16-bit ASM Game'},
|
2021-06-03 23:17:06 +00:00
|
|
|
{id:'cpu6502.v', name:'6502 CPU'},
|
2017-11-11 19:45:32 +00:00
|
|
|
];
|
|
|
|
|
2017-11-15 00:12:52 +00:00
|
|
|
var VERILOG_KEYCODE_MAP = makeKeycodeMap([
|
2019-06-07 13:45:30 +00:00
|
|
|
[Keys.LEFT, 0, 0x1],
|
|
|
|
[Keys.RIGHT, 0, 0x2],
|
|
|
|
[Keys.UP, 0, 0x4],
|
|
|
|
[Keys.DOWN, 0, 0x8],
|
|
|
|
[Keys.A, 0, 0x10],
|
|
|
|
[Keys.B, 0, 0x20],
|
2020-02-29 07:37:52 +00:00
|
|
|
[Keys.P2_LEFT, 1, 0x1],
|
|
|
|
[Keys.P2_RIGHT, 1, 0x2],
|
|
|
|
[Keys.P2_UP, 1, 0x4],
|
|
|
|
[Keys.P2_DOWN, 1, 0x8],
|
|
|
|
[Keys.P2_A, 1, 0x10],
|
|
|
|
[Keys.P2_B, 1, 0x20],
|
2019-06-07 13:45:30 +00:00
|
|
|
[Keys.START, 2, 0x1],
|
|
|
|
[Keys.P2_START, 2, 0x2],
|
|
|
|
[Keys.SELECT, 2, 0x4],
|
|
|
|
[Keys.P2_SELECT, 2, 0x8],
|
2017-12-04 21:40:10 +00:00
|
|
|
[Keys.VK_7, 2, 0x10],
|
2017-11-15 00:12:52 +00:00
|
|
|
]);
|
|
|
|
|
2021-06-23 16:27:22 +00:00
|
|
|
const TRACE_BUFFER_DWORDS = 0x40000;
|
|
|
|
|
2020-09-17 03:12:21 +00:00
|
|
|
const CYCLES_PER_FILL = 20;
|
|
|
|
|
2018-08-29 12:24:13 +00:00
|
|
|
// PLATFORM
|
|
|
|
|
2017-11-11 19:45:32 +00:00
|
|
|
var VerilogPlatform = function(mainElement, options) {
|
2018-08-29 12:24:13 +00:00
|
|
|
this.__proto__ = new (BasePlatform as any)();
|
|
|
|
|
2017-11-13 05:24:19 +00:00
|
|
|
var video, audio;
|
2019-06-07 17:03:30 +00:00
|
|
|
var poller;
|
2018-02-14 01:04:52 +00:00
|
|
|
var useAudio = false;
|
2018-02-27 04:48:36 +00:00
|
|
|
var videoWidth = 292;
|
|
|
|
var videoHeight = 256;
|
2018-02-21 19:32:11 +00:00
|
|
|
var maxVideoLines = 262+40; // vertical hold
|
2018-02-26 23:18:23 +00:00
|
|
|
var idata, timer, timerCallback;
|
2021-06-28 20:36:47 +00:00
|
|
|
var top : HDLModuleJS;
|
2017-11-11 19:45:32 +00:00
|
|
|
var gen;
|
2018-02-26 23:18:23 +00:00
|
|
|
var cyclesPerFrame = (256+23+7+23)*262; // 4857480/60 Hz
|
2018-08-29 12:24:13 +00:00
|
|
|
|
|
|
|
// control inputs
|
2017-12-04 21:40:10 +00:00
|
|
|
var switches = [0,0,0];
|
2019-08-29 02:04:02 +00:00
|
|
|
var keycode = 0;
|
2018-08-29 12:24:13 +00:00
|
|
|
|
|
|
|
// inspect feature
|
2017-11-21 16:15:08 +00:00
|
|
|
var inspect_obj, inspect_sym;
|
|
|
|
var inspect_data = new Uint32Array(videoWidth * videoHeight);
|
2018-08-29 12:24:13 +00:00
|
|
|
|
2018-09-12 02:28:30 +00:00
|
|
|
// for scope
|
2018-02-28 03:35:42 +00:00
|
|
|
var module_name;
|
2018-09-12 02:28:30 +00:00
|
|
|
//var trace_ports;
|
2018-02-26 23:18:23 +00:00
|
|
|
var trace_signals;
|
2017-11-28 02:08:19 +00:00
|
|
|
var trace_buffer;
|
|
|
|
var trace_index;
|
2017-11-11 19:45:32 +00:00
|
|
|
|
2018-08-29 12:24:13 +00:00
|
|
|
// for virtual CRT
|
|
|
|
var framex=0;
|
|
|
|
var framey=0;
|
|
|
|
var frameidx=0;
|
|
|
|
var framehsync=false;
|
|
|
|
var framevsync=false;
|
2017-11-11 19:45:32 +00:00
|
|
|
|
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,
|
2018-02-21 19:32:11 +00:00
|
|
|
0xff999999,
|
|
|
|
0xff9999ff,
|
|
|
|
0xff99ff99,
|
|
|
|
0xff99ffff,
|
|
|
|
0xffff9999,
|
|
|
|
0xffff99ff,
|
|
|
|
0xffffff99,
|
|
|
|
0xff666666,
|
2017-11-13 05:24:19 +00:00
|
|
|
];
|
|
|
|
|
2017-11-24 19:14:22 +00:00
|
|
|
var debugCond;
|
2018-08-29 12:24:13 +00:00
|
|
|
var frameRate = 0;
|
2017-11-24 19:14:22 +00:00
|
|
|
|
2017-11-15 00:12:52 +00:00
|
|
|
function vidtick() {
|
2021-06-28 20:36:47 +00:00
|
|
|
top.tick2();
|
2018-02-14 01:04:52 +00:00
|
|
|
if (useAudio)
|
|
|
|
audio.feedSample(gen.spkr*(1.0/255.0), 1);
|
2019-08-29 02:04:02 +00:00
|
|
|
if (keycode && keycode >= 128 && gen.keystrobe) // keystrobe = clear hi bit of key buffer
|
|
|
|
keycode = gen.keycode = keycode & 0x7f;
|
2018-02-14 01:04:52 +00:00
|
|
|
if (debugCond && debugCond())
|
|
|
|
debugCond = null;
|
2017-11-15 00:12:52 +00:00
|
|
|
}
|
|
|
|
|
2021-06-28 20:36:47 +00:00
|
|
|
function doreset() {
|
|
|
|
gen.reset = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
function unreset() {
|
|
|
|
if (gen.reset !== undefined) {
|
|
|
|
gen.reset = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-29 12:24:13 +00:00
|
|
|
// inner Platform class
|
|
|
|
|
2018-09-12 02:28:30 +00:00
|
|
|
class _VerilogPlatform extends BasePlatform implements WaveformProvider {
|
|
|
|
|
|
|
|
waveview : WaveformView;
|
|
|
|
wavediv : JQuery;
|
2018-11-23 18:29:11 +00:00
|
|
|
topdiv : JQuery;
|
2018-09-12 02:28:30 +00:00
|
|
|
split;
|
|
|
|
hasvideo : boolean;
|
2018-08-29 12:24:13 +00:00
|
|
|
|
|
|
|
getPresets() { return VERILOG_PRESETS; }
|
|
|
|
|
2019-01-27 19:47:03 +00:00
|
|
|
setVideoParams(width:number, height:number, clock:number) {
|
|
|
|
videoWidth = width;
|
|
|
|
videoHeight = height;
|
|
|
|
cyclesPerFrame = clock;
|
|
|
|
maxVideoLines = height+40;
|
|
|
|
}
|
|
|
|
|
2021-06-28 20:36:47 +00:00
|
|
|
async start() {
|
|
|
|
await loadScript('./gen/common/hdl/hdltypes.js');
|
|
|
|
await loadScript('./gen/common/hdl/hdlruntime.js');
|
2018-11-25 12:18:32 +00:00
|
|
|
video = new RasterVideo(mainElement,videoWidth,videoHeight,{overscan:true});
|
2018-08-29 12:24:13 +00:00
|
|
|
video.create();
|
2019-08-29 02:04:02 +00:00
|
|
|
poller = setKeyboardFromMap(video, switches, VERILOG_KEYCODE_MAP, (o,key,code,flags) => {
|
|
|
|
if (flags & KeyFlags.KeyPress) {
|
|
|
|
keycode = code | 0x80;
|
|
|
|
}
|
|
|
|
}, true); // true = always send function
|
2018-08-29 12:24:13 +00:00
|
|
|
var vcanvas = $(video.canvas);
|
|
|
|
idata = video.getFrameData();
|
|
|
|
timerCallback = () => {
|
2018-12-07 15:03:01 +00:00
|
|
|
if (!this.isRunning())
|
|
|
|
return;
|
2021-06-28 20:36:47 +00:00
|
|
|
if (gen && gen.switches != null) gen.switches = switches[0];
|
2018-08-29 12:24:13 +00:00
|
|
|
this.updateFrame();
|
|
|
|
};
|
|
|
|
this.setFrameRate(60);
|
2018-09-12 02:28:30 +00:00
|
|
|
// setup scope
|
2021-06-23 16:27:22 +00:00
|
|
|
trace_buffer = new Uint32Array(TRACE_BUFFER_DWORDS);
|
2018-09-13 00:54:25 +00:00
|
|
|
var overlay = $("#emuoverlay").show();
|
2018-11-23 18:29:11 +00:00
|
|
|
this.topdiv = $('<div class="emuspacer">').appendTo(overlay);
|
|
|
|
vcanvas.appendTo(this.topdiv);
|
2018-09-12 02:28:30 +00:00
|
|
|
this.wavediv = $('<div class="emuscope">').appendTo(overlay);
|
2018-11-23 18:29:11 +00:00
|
|
|
this.split = Split( [this.topdiv[0], this.wavediv[0]], {
|
2018-09-12 02:28:30 +00:00
|
|
|
minSize: [0,0],
|
|
|
|
sizes: [99,1],
|
|
|
|
direction: 'vertical',
|
|
|
|
gutterSize: 16,
|
|
|
|
onDrag: () => {
|
2018-12-07 15:03:01 +00:00
|
|
|
this.resize();
|
|
|
|
//if (this.waveview) this.waveview.recreate();
|
2018-11-26 11:12:45 +00:00
|
|
|
//vcanvas.css('position','relative');
|
|
|
|
//vcanvas.css('top', -this.wavediv.height()+'px');
|
2018-09-27 02:34:16 +00:00
|
|
|
},
|
2018-09-12 02:28:30 +00:00
|
|
|
});
|
|
|
|
// setup mouse events
|
2018-09-26 18:42:43 +00:00
|
|
|
video.setupMouseEvents();
|
2019-05-02 02:07:17 +00:00
|
|
|
// setup mouse click
|
|
|
|
video.vcanvas.click( (e) => {
|
|
|
|
if (!gen) return; // must have created emulator
|
|
|
|
if (!e.ctrlKey) {
|
2019-09-03 00:24:34 +00:00
|
|
|
//setFrameRateUI(60);
|
2019-05-02 02:07:17 +00:00
|
|
|
return; // ctrl key must be down
|
|
|
|
}
|
|
|
|
setFrameRateUI(1.0/2048);
|
|
|
|
var pos = getMousePos(video.canvas, e);
|
|
|
|
var new_y = Math.floor(pos.y);
|
|
|
|
var clock = 0;
|
|
|
|
while (framey != new_y || clock++ > 200000) {
|
|
|
|
this.setGenInputs();
|
|
|
|
this.updateVideoFrameCycles(1, true, false);
|
2021-06-28 20:36:47 +00:00
|
|
|
unreset();
|
2019-05-02 02:07:17 +00:00
|
|
|
}
|
|
|
|
});
|
2018-08-29 12:24:13 +00:00
|
|
|
}
|
|
|
|
|
2019-06-07 17:03:30 +00:00
|
|
|
// TODO: pollControls() { poller.poll(); }
|
|
|
|
|
2018-11-26 11:12:45 +00:00
|
|
|
resize() {
|
|
|
|
if (this.waveview) this.waveview.recreate();
|
|
|
|
}
|
|
|
|
|
2018-08-29 12:24:13 +00:00
|
|
|
setGenInputs() {
|
|
|
|
useAudio = (audio != null);
|
|
|
|
//TODO debugCond = this.getDebugCallback();
|
2021-06-28 20:36:47 +00:00
|
|
|
if (gen.switches_p1 != null) gen.switches_p1 = switches[0];
|
|
|
|
if (gen.switches_p2 != null) gen.switches_p2 = switches[1];
|
|
|
|
if (gen.switches_gen != null) gen.switches_gen = switches[2];
|
|
|
|
if (gen.keycode != null) gen.keycode = keycode;
|
2018-08-29 12:24:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
updateVideoFrame() {
|
2018-12-07 15:03:01 +00:00
|
|
|
//this.topdiv.show(); //show crt
|
2018-08-29 12:24:13 +00:00
|
|
|
this.setGenInputs();
|
|
|
|
var fps = this.getFrameRate();
|
|
|
|
// darken the previous frame?
|
2019-05-02 02:07:17 +00:00
|
|
|
var sync = fps > 45;
|
|
|
|
if (!sync) {
|
2018-08-29 12:24:13 +00:00
|
|
|
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
|
2018-09-12 02:28:30 +00:00
|
|
|
var trace = this.isScopeVisible();
|
2018-08-29 12:24:13 +00:00
|
|
|
this.updateVideoFrameCycles(cyclesPerFrame * fps/60 + 1, sync, trace);
|
2019-05-02 02:07:17 +00:00
|
|
|
if (fps < 0.25) {
|
|
|
|
idata[frameidx] = -1;
|
|
|
|
}
|
2018-08-29 12:24:13 +00:00
|
|
|
//this.restartDebugState();
|
2021-06-28 20:36:47 +00:00
|
|
|
unreset();
|
2018-08-29 12:24:13 +00:00
|
|
|
this.refreshVideoFrame();
|
2018-09-12 02:28:30 +00:00
|
|
|
// set scope offset
|
|
|
|
if (trace && this.waveview) {
|
2021-06-23 23:44:30 +00:00
|
|
|
this.waveview.setCurrentTime(Math.floor(trace_index/trace_signals.length));
|
2018-09-12 02:28:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
isScopeVisible() {
|
|
|
|
return this.split.getSizes()[1] > 2; // TODO?
|
2018-08-29 12:24:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: merge with prev func
|
2020-07-04 14:16:45 +00:00
|
|
|
advance(novideo : boolean) : number {
|
2018-08-29 12:24:13 +00:00
|
|
|
this.setGenInputs();
|
|
|
|
this.updateVideoFrameCycles(cyclesPerFrame, true, false);
|
2021-06-28 20:36:47 +00:00
|
|
|
unreset();
|
2018-08-29 12:24:13 +00:00
|
|
|
if (!novideo) {
|
|
|
|
this.refreshVideoFrame();
|
|
|
|
}
|
2020-07-04 14:16:45 +00:00
|
|
|
return cyclesPerFrame; //TODO?
|
2018-08-29 12:24:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
refreshVideoFrame() {
|
|
|
|
this.updateInspectionFrame();
|
2018-09-12 02:28:30 +00:00
|
|
|
video.updateFrame();
|
2018-08-29 12:24:13 +00:00
|
|
|
this.updateInspectionPostFrame();
|
|
|
|
}
|
2018-09-12 02:28:30 +00:00
|
|
|
|
|
|
|
refreshScopeOverlay() {
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
|
|
|
|
updateScopeFrame() {
|
|
|
|
this.split.setSizes([0,100]); // ensure scope visible
|
2018-12-07 15:03:01 +00:00
|
|
|
//this.topdiv.hide();// hide crt
|
2020-09-17 03:12:21 +00:00
|
|
|
var done = this.fillTraceBuffer(CYCLES_PER_FILL * trace_signals.length);
|
2018-09-12 02:28:30 +00:00
|
|
|
if (done)
|
|
|
|
this.pause(); // TODO?
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
|
|
|
|
updateScope() {
|
|
|
|
// create scope, if visible
|
|
|
|
if (this.isScopeVisible()) {
|
|
|
|
if (!this.waveview) {
|
2018-10-11 15:33:09 +00:00
|
|
|
this.waveview = new WaveformView(this.wavediv[0] as HTMLElement, this);
|
2018-09-12 02:28:30 +00:00
|
|
|
} else {
|
|
|
|
this.waveview.refresh();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-08-29 12:24:13 +00:00
|
|
|
|
|
|
|
updateFrame() {
|
|
|
|
if (!gen) return;
|
2018-09-12 02:28:30 +00:00
|
|
|
if (this.hasvideo)
|
2018-08-29 12:24:13 +00:00
|
|
|
this.updateVideoFrame();
|
|
|
|
else
|
|
|
|
this.updateScopeFrame();
|
2018-09-12 02:28:30 +00:00
|
|
|
this.updateScope();
|
2018-08-29 12:24:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
updateInspectionFrame() {
|
2018-02-14 01:04:52 +00:00
|
|
|
useAudio = false;
|
2017-11-21 16:15:08 +00:00
|
|
|
if (inspect_obj && inspect_sym) {
|
2018-02-21 19:32:11 +00:00
|
|
|
var COLOR_BIT_OFF = 0xffff6666;
|
|
|
|
var COLOR_BIT_ON = 0xffff9999;
|
2018-02-26 23:18:23 +00:00
|
|
|
var i = videoWidth;
|
|
|
|
for (var y=0; y<videoHeight-2; y++) {
|
2018-02-19 00:19:20 +00:00
|
|
|
for (var x=0; x<videoWidth; x++) {
|
|
|
|
var val = inspect_data[i];
|
|
|
|
idata[i++] = (val & 1) ? COLOR_BIT_ON : COLOR_BIT_OFF;
|
|
|
|
}
|
|
|
|
}
|
2017-11-21 16:15:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-29 12:24:13 +00:00
|
|
|
updateInspectionPostFrame() {
|
2017-11-21 16:15:08 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-12 02:28:30 +00:00
|
|
|
updateVideoFrameCycles(ncycles:number, sync:boolean, trace:boolean) {
|
2018-02-26 23:18:23 +00:00
|
|
|
ncycles |= 0;
|
|
|
|
var inspect = inspect_obj && inspect_sym;
|
2018-09-12 02:28:30 +00:00
|
|
|
var trace0 = trace_index;
|
2018-02-26 23:18:23 +00:00
|
|
|
while (ncycles--) {
|
2018-09-12 02:28:30 +00:00
|
|
|
if (trace) {
|
|
|
|
this.snapshotTrace();
|
|
|
|
if (trace_index == trace0) trace = false; // kill trace when wraps around
|
|
|
|
}
|
2018-02-26 23:18:23 +00:00
|
|
|
vidtick();
|
|
|
|
if (framex++ < videoWidth) {
|
|
|
|
if (framey < videoHeight) {
|
|
|
|
if (inspect) {
|
|
|
|
inspect_data[frameidx] = inspect_obj[inspect_sym];
|
|
|
|
}
|
2020-12-16 16:48:33 +00:00
|
|
|
let rgb = gen.rgb;
|
|
|
|
idata[frameidx] = rgb & 0x80000000 ? rgb : RGBLOOKUP[rgb & 15];
|
2018-02-26 23:18:23 +00:00
|
|
|
frameidx++;
|
|
|
|
}
|
|
|
|
} else if (!framehsync && gen.hsync) {
|
|
|
|
framehsync = true;
|
|
|
|
} else if ((framehsync && !gen.hsync) || framex > videoWidth*2) {
|
|
|
|
framehsync = false;
|
|
|
|
framex = 0;
|
|
|
|
framey++;
|
2021-06-28 20:36:47 +00:00
|
|
|
if (gen.hpaddle != null) gen.hpaddle = framey > video.paddle_x ? 1 : 0;
|
|
|
|
if (gen.vpaddle != null) gen.vpaddle = framey > video.paddle_y ? 1 : 0;
|
2018-02-26 23:18:23 +00:00
|
|
|
}
|
|
|
|
if (framey > maxVideoLines || gen.vsync) {
|
|
|
|
framevsync = true;
|
|
|
|
framey = 0;
|
2018-02-27 04:48:36 +00:00
|
|
|
framex = 0;
|
|
|
|
frameidx = 0;
|
2021-06-28 20:36:47 +00:00
|
|
|
if (gen.hpaddle != null) gen.hpaddle = 0;
|
|
|
|
if (gen.vpaddle != null) gen.vpaddle = 0;
|
2018-02-26 23:18:23 +00:00
|
|
|
} else {
|
2018-02-27 04:48:36 +00:00
|
|
|
var wasvsync = framevsync;
|
2018-02-26 23:18:23 +00:00
|
|
|
framevsync = false;
|
2018-08-29 12:24:13 +00:00
|
|
|
if (sync && wasvsync) {
|
|
|
|
this.updateRecorder();
|
|
|
|
return; // exit when vsync ends
|
|
|
|
}
|
2018-02-26 23:18:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-12 02:28:30 +00:00
|
|
|
snapshotTrace() {
|
|
|
|
var arr = trace_signals;
|
2018-02-26 23:18:23 +00:00
|
|
|
for (var i=0; i<arr.length; i++) {
|
|
|
|
var v = arr[i];
|
|
|
|
var z = gen[v.name];
|
2021-06-28 20:36:47 +00:00
|
|
|
trace_buffer[trace_index] = z+0;
|
2018-09-12 02:28:30 +00:00
|
|
|
trace_index++;
|
2018-02-26 23:18:23 +00:00
|
|
|
}
|
2018-09-12 02:28:30 +00:00
|
|
|
if (trace_index >= trace_buffer.length - arr.length)
|
|
|
|
trace_index = 0;
|
2018-02-26 23:18:23 +00:00
|
|
|
}
|
|
|
|
|
2018-09-12 02:28:30 +00:00
|
|
|
fillTraceBuffer(count:number) : boolean {
|
|
|
|
var max_index = Math.min(trace_buffer.length - trace_signals.length, trace_index + count);
|
2017-11-28 02:08:19 +00:00
|
|
|
while (trace_index < max_index) {
|
|
|
|
gen.clk ^= 1;
|
2021-06-28 20:36:47 +00:00
|
|
|
top.eval();
|
2018-09-12 02:28:30 +00:00
|
|
|
this.snapshotTrace();
|
|
|
|
if (trace_index == 0)
|
|
|
|
break;
|
2017-11-28 02:08:19 +00:00
|
|
|
}
|
2021-06-28 20:36:47 +00:00
|
|
|
unreset();
|
2018-09-12 02:28:30 +00:00
|
|
|
return (trace_index == 0);
|
2017-11-28 02:08:19 +00:00
|
|
|
}
|
2018-09-12 02:28:30 +00:00
|
|
|
|
|
|
|
getSignalMetadata() : WaveformMeta[] {
|
|
|
|
return trace_signals;
|
2018-02-26 23:18:23 +00:00
|
|
|
}
|
2018-09-12 02:28:30 +00:00
|
|
|
|
|
|
|
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;
|
2017-11-29 01:48:27 +00:00
|
|
|
}
|
2018-09-12 02:28:30 +00:00
|
|
|
return a;
|
2017-11-14 14:33:15 +00:00
|
|
|
}
|
|
|
|
|
2021-04-29 20:25:29 +00:00
|
|
|
setSignalValue(index:number, value:number) {
|
|
|
|
var meta = this.getSignalMetadata()[index];
|
|
|
|
gen[meta.label] = value;
|
|
|
|
this.reset();
|
|
|
|
}
|
|
|
|
|
2018-08-29 12:24:13 +00:00
|
|
|
printErrorCodeContext(e, code) {
|
2017-11-15 00:12:52 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-28 20:36:47 +00:00
|
|
|
loadROM(title:string, output:any) {
|
|
|
|
var unit = output as HDLUnit;
|
|
|
|
var topmod = unit.modules['TOP'];
|
|
|
|
if (unit.modules && topmod) {
|
|
|
|
{
|
|
|
|
// initialize top module and constant pool
|
|
|
|
top = new HDLModuleJS(topmod, unit.modules['@CONST-POOL@']);
|
|
|
|
top.init();
|
|
|
|
top.reset();
|
|
|
|
gen = top.state;
|
|
|
|
// create signal array
|
|
|
|
var signals : WaveformSignal[] = [];
|
|
|
|
for (var key in topmod.vardefs) {
|
|
|
|
var vardef = topmod.vardefs[key];
|
|
|
|
if (isLogicType(vardef.dtype)) {
|
|
|
|
signals.push({
|
|
|
|
name: key,
|
|
|
|
label: vardef.origName,
|
|
|
|
input: vardef.isInput,
|
|
|
|
output: vardef.isOutput,
|
|
|
|
len: vardef.dtype.left+1
|
|
|
|
});
|
|
|
|
}
|
2018-09-13 00:54:25 +00:00
|
|
|
}
|
2021-06-28 20:36:47 +00:00
|
|
|
trace_signals = signals;
|
|
|
|
trace_signals = trace_signals.filter((v) => { return !v.label.startsWith("__V"); }); // remove __Vclklast etc
|
2018-07-31 18:41:27 +00:00
|
|
|
trace_index = 0;
|
2021-06-28 20:36:47 +00:00
|
|
|
// reset
|
2018-07-31 18:41:27 +00:00
|
|
|
this.poweron();
|
2021-06-28 20:36:47 +00:00
|
|
|
// query output signals -- video or not?
|
2018-09-12 02:28:30 +00:00
|
|
|
this.hasvideo = gen.vsync !== undefined && gen.hsync !== undefined && gen.rgb !== undefined;
|
2018-09-12 19:26:27 +00:00
|
|
|
if (this.hasvideo) {
|
|
|
|
const IGNORE_SIGNALS = ['clk','reset'];
|
|
|
|
trace_signals = trace_signals.filter((v) => { return IGNORE_SIGNALS.indexOf(v.name)<0; }); // remove clk, reset
|
2018-09-18 18:09:51 +00:00
|
|
|
$("#speed_bar").show();
|
2020-09-23 17:00:47 +00:00
|
|
|
$("#run_bar").show();
|
|
|
|
$("#xtra_bar").show();
|
2018-09-18 18:09:51 +00:00
|
|
|
} else {
|
|
|
|
$("#speed_bar").hide();
|
2020-09-23 17:00:47 +00:00
|
|
|
$("#run_bar").hide();
|
|
|
|
$("#xtra_bar").hide();
|
2018-09-12 19:26:27 +00:00
|
|
|
}
|
2018-03-02 05:15:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// replace program ROM, if using the assembler
|
2021-06-28 20:36:47 +00:00
|
|
|
this.reset();
|
2018-03-02 05:15:33 +00:00
|
|
|
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
|
2021-06-28 20:36:47 +00:00
|
|
|
gen[output.program_rom_variable].set(output.program_rom);
|
2018-03-02 05:15:33 +00:00
|
|
|
} else {
|
|
|
|
alert("No program_rom variable found (" + output.program_rom_variable + ")");
|
|
|
|
}
|
2017-11-15 00:12:52 +00:00
|
|
|
}
|
2018-03-02 05:15:33 +00:00
|
|
|
// restart audio
|
2018-08-29 12:24:13 +00:00
|
|
|
this.restartAudio();
|
2018-09-12 02:28:30 +00:00
|
|
|
if (this.waveview) {
|
2019-04-22 14:09:33 +00:00
|
|
|
this.waveview.recreate();
|
2018-09-12 02:28:30 +00:00
|
|
|
}
|
2018-02-26 23:18:23 +00:00
|
|
|
}
|
2018-09-12 19:26:27 +00:00
|
|
|
|
2018-08-29 12:24:13 +00:00
|
|
|
restartAudio() {
|
2018-02-21 18:54:53 +00:00
|
|
|
// stop/start audio
|
2018-02-26 23:18:23 +00:00
|
|
|
var hasAudio = gen && gen.spkr !== undefined && frameRate > 1;
|
|
|
|
if (audio && !hasAudio) {
|
2018-02-21 18:54:53 +00:00
|
|
|
audio.stop();
|
|
|
|
audio = null;
|
2018-02-26 23:18:23 +00:00
|
|
|
} else if (!audio && hasAudio) {
|
2018-08-29 12:24:13 +00:00
|
|
|
audio = new SampleAudio(cyclesPerFrame * this.getFrameRate());
|
|
|
|
if (this.isRunning())
|
2018-02-21 18:54:53 +00:00
|
|
|
audio.start();
|
|
|
|
}
|
2017-11-11 19:45:32 +00:00
|
|
|
}
|
|
|
|
|
2018-08-29 12:24:13 +00:00
|
|
|
isRunning() {
|
2017-11-11 19:45:32 +00:00
|
|
|
return timer && timer.isRunning();
|
|
|
|
}
|
2018-08-29 12:24:13 +00:00
|
|
|
pause() {
|
2017-11-11 19:45:32 +00:00
|
|
|
timer.stop();
|
2018-02-21 18:54:53 +00:00
|
|
|
if (audio) audio.stop();
|
2017-11-11 19:45:32 +00:00
|
|
|
}
|
2018-08-29 12:24:13 +00:00
|
|
|
resume() {
|
2017-11-11 19:45:32 +00:00
|
|
|
timer.start();
|
2018-02-21 18:54:53 +00:00
|
|
|
if (audio) audio.start();
|
2017-11-11 19:45:32 +00:00
|
|
|
}
|
|
|
|
|
2021-06-28 20:36:47 +00:00
|
|
|
isBlocked() {
|
|
|
|
return top && top.finished;
|
|
|
|
}
|
|
|
|
isStopped() {
|
|
|
|
return top && top.stopped;
|
|
|
|
}
|
|
|
|
|
2018-08-29 12:24:13 +00:00
|
|
|
setFrameRate(rateHz) {
|
2018-02-26 23:18:23 +00:00
|
|
|
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;
|
|
|
|
}
|
2018-08-29 12:24:13 +00:00
|
|
|
this.restartAudio();
|
|
|
|
}
|
|
|
|
getFrameRate() { return frameRate; }
|
2018-02-26 23:18:23 +00:00
|
|
|
|
2018-08-29 12:24:13 +00:00
|
|
|
poweron() {
|
2021-06-28 20:36:47 +00:00
|
|
|
top.reset();
|
2017-11-21 16:15:08 +00:00
|
|
|
this.reset();
|
|
|
|
}
|
2018-08-29 12:24:13 +00:00
|
|
|
reset() {
|
2018-12-22 23:50:39 +00:00
|
|
|
if (!gen) return;
|
2021-06-28 20:36:47 +00:00
|
|
|
//top.reset(); // to avoid clobbering user inputs
|
|
|
|
doreset();
|
2018-09-12 02:28:30 +00:00
|
|
|
trace_index = 0;
|
2018-02-27 04:48:36 +00:00
|
|
|
if (trace_buffer) trace_buffer.fill(0);
|
2018-02-18 05:12:09 +00:00
|
|
|
if (video) video.setRotate(gen.rotate ? -90 : 0);
|
2018-09-14 13:10:41 +00:00
|
|
|
$("#verilog_bar").hide();
|
2018-10-10 01:49:29 +00:00
|
|
|
if (!this.hasvideo) this.resume(); // TODO?
|
2017-11-11 19:45:32 +00:00
|
|
|
}
|
2018-08-29 12:24:13 +00:00
|
|
|
tick() {
|
2021-06-28 20:36:47 +00:00
|
|
|
top.tick2();
|
2017-11-22 14:42:07 +00:00
|
|
|
}
|
2018-08-29 12:24:13 +00:00
|
|
|
getToolForFilename(fn) {
|
2020-12-16 16:48:33 +00:00
|
|
|
if (fn.endsWith(".asm")) return "jsasm";
|
|
|
|
else if (fn.endsWith(".ice")) return "silice";
|
|
|
|
else return "verilator";
|
2017-11-11 19:45:32 +00:00
|
|
|
}
|
2018-08-29 12:24:13 +00:00
|
|
|
getDefaultExtension() { return ".v"; };
|
2017-11-21 16:15:08 +00:00
|
|
|
|
2020-07-06 23:53:20 +00:00
|
|
|
inspect(name:string) : string {
|
2017-11-21 16:15:08 +00:00
|
|
|
if (!gen) return;
|
2020-12-16 16:48:33 +00:00
|
|
|
if (name) name = name.replace('.','_');
|
|
|
|
if (!name || !name.match(/^\w+$/)) {
|
|
|
|
inspect_obj = inspect_sym = null;
|
|
|
|
return;
|
|
|
|
}
|
2017-11-21 16:15:08 +00:00
|
|
|
var val = gen[name];
|
2021-06-28 20:36:47 +00:00
|
|
|
/* TODO
|
2017-11-21 16:15:08 +00:00
|
|
|
if (val === undefined && current_output.code) {
|
2020-12-16 16:48:33 +00:00
|
|
|
var re = new RegExp("(\\w+__DOT__(?:_[dcw]_)" + name + ")\\b", "gm");
|
2017-11-21 16:15:08 +00:00
|
|
|
var m = re.exec(current_output.code);
|
|
|
|
if (m) {
|
|
|
|
name = m[1];
|
|
|
|
val = gen[name];
|
|
|
|
}
|
|
|
|
}
|
2021-06-28 20:36:47 +00:00
|
|
|
*/
|
2018-03-19 01:11:11 +00:00
|
|
|
if (typeof(val) === 'number') {
|
2017-11-21 16:15:08 +00:00
|
|
|
inspect_obj = gen;
|
|
|
|
inspect_sym = name;
|
|
|
|
} else {
|
|
|
|
inspect_obj = inspect_sym = null;
|
|
|
|
}
|
|
|
|
}
|
2017-11-24 19:14:22 +00:00
|
|
|
|
|
|
|
// DEBUGGING
|
|
|
|
|
2020-07-08 01:56:44 +00:00
|
|
|
getDebugTree() {
|
2021-06-28 20:36:47 +00:00
|
|
|
return {
|
|
|
|
//ast: current_output,
|
|
|
|
runtime: top,
|
|
|
|
state: this.saveState().o
|
|
|
|
}
|
2020-07-08 01:56:44 +00:00
|
|
|
}
|
|
|
|
|
2018-08-29 12:24:13 +00:00
|
|
|
// TODO: bind() a function to avoid depot?
|
|
|
|
saveState() {
|
|
|
|
var state = {
|
2021-06-28 20:36:47 +00:00
|
|
|
// TODO: T:gen.ticks(),
|
2018-08-29 12:24:13 +00:00
|
|
|
o:safe_extend(true, {}, gen)
|
|
|
|
};
|
|
|
|
return state;
|
2017-11-24 19:14:22 +00:00
|
|
|
}
|
2018-08-29 12:24:13 +00:00
|
|
|
loadState(state) {
|
|
|
|
gen = safe_extend(true, gen, state.o);
|
2021-06-28 20:36:47 +00:00
|
|
|
// TODO: gen.setTicks(state.T);
|
2018-08-29 12:24:13 +00:00
|
|
|
//console.log(gen, state.o);
|
|
|
|
}
|
|
|
|
saveControlsState() {
|
|
|
|
return {
|
2018-09-26 18:42:43 +00:00
|
|
|
p1x: video.paddle_x,
|
|
|
|
p1y: video.paddle_y,
|
2018-08-29 12:24:13 +00:00
|
|
|
sw0: switches[0],
|
|
|
|
sw1: switches[1],
|
|
|
|
sw2: switches[2],
|
2019-08-29 02:04:02 +00:00
|
|
|
keycode: keycode
|
2018-08-29 12:24:13 +00:00
|
|
|
};
|
2017-11-24 19:14:22 +00:00
|
|
|
}
|
2018-08-29 12:24:13 +00:00
|
|
|
loadControlsState(state) {
|
2018-09-26 18:42:43 +00:00
|
|
|
video.paddle_x = state.p1x;
|
|
|
|
video.paddle_y = state.p1y;
|
2018-08-29 12:24:13 +00:00
|
|
|
switches[0] = state.sw0;
|
|
|
|
switches[1] = state.sw1;
|
|
|
|
switches[2] = state.sw2;
|
2019-08-29 02:04:02 +00:00
|
|
|
keycode = state.keycode;
|
2017-11-24 19:14:22 +00:00
|
|
|
}
|
2021-06-28 20:36:47 +00:00
|
|
|
getDownloadFile() {
|
|
|
|
return {
|
|
|
|
extension:".js",
|
|
|
|
blob: new Blob([top.getJSCode()], {type:"text/plain"})
|
|
|
|
};
|
|
|
|
}
|
2017-11-24 19:14:22 +00:00
|
|
|
|
2018-08-29 12:24:13 +00:00
|
|
|
} // end of inner class
|
|
|
|
return new _VerilogPlatform();
|
2017-11-11 19:45:32 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
////////////////
|
|
|
|
|
2019-01-27 19:47:03 +00:00
|
|
|
var VERILOG_VGA_PRESETS = [
|
2018-11-18 17:30:41 +00:00
|
|
|
{id:'hvsync_generator.v', name:'Video Sync Generator'},
|
|
|
|
{id:'test_hvsync.v', name:'Test Pattern'},
|
2019-01-27 20:29:37 +00:00
|
|
|
{id:'chardisplay.v', name:'RAM Text Display'},
|
|
|
|
{id:'starfield.v', name:'Scrolling Starfield'},
|
|
|
|
{id:'ball_paddle.v', name:'Brick Smash Game'},
|
2018-11-18 17:30:41 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
2019-01-27 19:47:03 +00:00
|
|
|
var VerilogVGAPlatform = function(mainElement, options) {
|
2018-11-18 17:30:41 +00:00
|
|
|
this.__proto__ = new (VerilogPlatform as any)(mainElement, options);
|
|
|
|
|
2019-01-27 19:47:03 +00:00
|
|
|
this.getPresets = function() { return VERILOG_VGA_PRESETS; }
|
|
|
|
|
|
|
|
this.setVideoParams(800-64, 520, 25000000);
|
2018-11-18 17:30:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
////////////////
|
|
|
|
|
2017-11-11 19:45:32 +00:00
|
|
|
PLATFORMS['verilog'] = VerilogPlatform;
|
2019-01-27 19:47:03 +00:00
|
|
|
PLATFORMS['verilog-vga'] = VerilogVGAPlatform;
|
2020-09-23 17:00:47 +00:00
|
|
|
PLATFORMS['verilog-test'] = VerilogPlatform;
|