mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2025-01-10 16:29:48 +00:00
verilog: refactor, trace buffer, fast video update
This commit is contained in:
parent
4e97cd2eef
commit
c0d60edbad
@ -60,6 +60,9 @@ export class HDLModuleJS implements HDLModuleRunner {
|
||||
init() {
|
||||
}
|
||||
|
||||
dispose() {
|
||||
}
|
||||
|
||||
genFuncs(constants: {}) : VerilatorUnit {
|
||||
var funcs = Object.create(this.basefuncs);
|
||||
this.curconsts = constants;
|
||||
|
@ -9,6 +9,13 @@ export interface HDLModuleRunner {
|
||||
isStopped() : boolean;
|
||||
saveState() : {};
|
||||
loadState(state: {}) : void;
|
||||
dispose() : void;
|
||||
}
|
||||
|
||||
export interface HDLModuleTrace {
|
||||
trace: any;
|
||||
resetTrace() : void;
|
||||
nextTrace() : void;
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -2,14 +2,6 @@
|
||||
import { HDLBinop, HDLBlock, HDLConstant, HDLDataType, HDLExpr, HDLExtendop, HDLFuncCall, HDLModuleDef, HDLModuleRunner, HDLSourceLocation, HDLTriop, HDLUnop, HDLValue, HDLVariableDef, HDLVarRef, HDLWhileOp, isArrayItem, isArrayType, isBinop, isBlock, isConstExpr, isFuncCall, isLogicType, isTriop, isUnop, isVarDecl, isVarRef, isWhileop } from "./hdltypes";
|
||||
import binaryen = require('binaryen');
|
||||
|
||||
interface VerilatorUnit {
|
||||
_ctor_var_reset(state) : void;
|
||||
_eval_initial(state) : void;
|
||||
_eval_settle(state) : void;
|
||||
_eval(state) : void;
|
||||
_change_request(state) : boolean;
|
||||
}
|
||||
|
||||
const VERILATOR_UNIT_FUNCTIONS = [
|
||||
"_ctor_var_reset",
|
||||
"_eval_initial",
|
||||
@ -24,9 +16,14 @@ interface Options {
|
||||
resulttype?: number;
|
||||
}
|
||||
|
||||
const GLOBALOFS = 0;
|
||||
const MEMORY = "0";
|
||||
const GLOBAL = "$$GLOBAL";
|
||||
const CHANGEDET = "$$CHANGE";
|
||||
const MEMORY = "0";
|
||||
const TRACERECLEN = "$$treclen";
|
||||
const TRACEOFS = "$$tofs";
|
||||
const TRACEEND = "$$tend";
|
||||
const TRACEBUF = "$$tbuf";
|
||||
|
||||
///
|
||||
|
||||
@ -60,7 +57,15 @@ function getDataTypeSize(dt: HDLDataType) : number {
|
||||
}
|
||||
}
|
||||
|
||||
function getArrayElementSize(e: HDLExpr) : number {
|
||||
function getArrayElementSizeFromType(dtype: HDLDataType) : number {
|
||||
if (isArrayType(dtype)) {
|
||||
return getArrayElementSizeFromType(dtype.subtype);
|
||||
} else {
|
||||
return getDataTypeSize(dtype);
|
||||
}
|
||||
}
|
||||
|
||||
function getArrayElementSizeFromExpr(e: HDLExpr) : number {
|
||||
if (isVarRef(e) && isArrayType(e.dtype)) {
|
||||
return getDataTypeSize(e.dtype.subtype);
|
||||
} else if (isBinop(e) && isArrayType(e.dtype)) {
|
||||
@ -111,15 +116,18 @@ class Struct {
|
||||
|
||||
addVar(vardef: HDLVariableDef) {
|
||||
var size = getDataTypeSize(vardef.dtype);
|
||||
var rec = this.addEntry(vardef.name, getBinaryenType(size), size, vardef.dtype, false);
|
||||
var rec = this.addEntry(vardef.name, size, getBinaryenType(size), vardef.dtype, false);
|
||||
rec.init = vardef.initValue;
|
||||
if (vardef.constValue) throw new HDLError(vardef, `can't handle constants`);
|
||||
return rec;
|
||||
}
|
||||
|
||||
addEntry(name: string, itype: number, size: number, hdltype: HDLDataType, isParam: boolean) : StructRec {
|
||||
var align = getAlignmentForSize(size);
|
||||
alignTo(align: number) : void {
|
||||
while (this.len % align) this.len++;
|
||||
}
|
||||
|
||||
addEntry(name: string, size: number, itype?: number, hdltype?: HDLDataType, isParam?: boolean) : StructRec {
|
||||
this.alignTo(getAlignmentForSize(size));
|
||||
// pointers are 32 bits, so if size > 8 it's a pointer
|
||||
var rec : StructRec = {
|
||||
name: name,
|
||||
@ -165,46 +173,142 @@ export class HDLModuleWASM implements HDLModuleRunner {
|
||||
stopped: boolean;
|
||||
databuf: Buffer;
|
||||
data8: Uint8Array;
|
||||
data16: Uint16Array;
|
||||
data32: Uint32Array;
|
||||
state: any;
|
||||
statebytes: number;
|
||||
outputbytes: number;
|
||||
traceBufferSize: number = 0xff000;
|
||||
traceRecordSize: number;
|
||||
traceReadOffset: number;
|
||||
traceStartOffset: number;
|
||||
traceEndOffset: number;
|
||||
trace: any;
|
||||
|
||||
constructor(moddef: HDLModuleDef, constpool: HDLModuleDef) {
|
||||
this.hdlmod = moddef;
|
||||
this.constpool = constpool;
|
||||
this.bmod = new binaryen.Module();
|
||||
this.genTypes();
|
||||
var memsize = Math.ceil(this.globals.len / 65536) + 1;
|
||||
this.bmod.setMemory(memsize, memsize, MEMORY); // memory is in 64k chunks
|
||||
var membytes = this.globals.len;
|
||||
var memblks = Math.ceil(membytes / 65536) + 1;
|
||||
this.bmod.setMemory(memblks, memblks, MEMORY); // memory is in 64k chunks
|
||||
this.genFuncs();
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.genModule();
|
||||
this.genInitData();
|
||||
this.enableTracing();
|
||||
}
|
||||
|
||||
genTypes() {
|
||||
powercycle() {
|
||||
this.finished = false;
|
||||
this.stopped = false;
|
||||
(this.instance.exports as any)._ctor_var_reset(GLOBALOFS);
|
||||
(this.instance.exports as any)._eval_initial(GLOBALOFS);
|
||||
for (var i=0; i<100; i++) {
|
||||
(this.instance.exports as any)._eval_settle(GLOBALOFS);
|
||||
(this.instance.exports as any)._eval(GLOBALOFS);
|
||||
var Vchange = (this.instance.exports as any)._change_request(GLOBALOFS);
|
||||
if (!Vchange) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new HDLError(null, `model did not converge on reset()`)
|
||||
}
|
||||
|
||||
eval() {
|
||||
(this.instance.exports as any).eval(GLOBALOFS);
|
||||
}
|
||||
|
||||
tick() {
|
||||
// TODO: faster, save state
|
||||
this.state.clk ^= 1;
|
||||
(this.instance.exports as any).eval(GLOBALOFS);
|
||||
}
|
||||
|
||||
tick2(iters: number) {
|
||||
(this.instance.exports as any).tick2(GLOBALOFS, iters);
|
||||
}
|
||||
|
||||
isFinished() { return this.finished; }
|
||||
|
||||
isStopped() { return this.stopped; }
|
||||
|
||||
saveState() {
|
||||
return { o: this.data8.slice(0, this.statebytes) };
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
this.data8.set(state.o as Uint8Array);
|
||||
}
|
||||
|
||||
enableTracing() {
|
||||
this.traceStartOffset = this.globals.lookup(TRACEBUF).offset;
|
||||
this.traceEndOffset = this.traceStartOffset + this.traceBufferSize - this.outputbytes;
|
||||
this.state[TRACEEND] = this.traceEndOffset;
|
||||
this.state[TRACERECLEN] = this.outputbytes;
|
||||
this.resetTrace();
|
||||
//console.log(this.state[TRACEOFS], this.state[TRACERECLEN], this.state[TRACEEND]);
|
||||
this.trace = new Proxy({}, this.makeScopeProxy(() => { return this.traceReadOffset }));
|
||||
}
|
||||
|
||||
resetTrace() {
|
||||
this.traceReadOffset = this.traceStartOffset;
|
||||
this.state[TRACEOFS] = this.traceStartOffset;
|
||||
}
|
||||
|
||||
nextTrace() {
|
||||
this.traceReadOffset += this.outputbytes;
|
||||
if (this.traceReadOffset >= this.traceEndOffset)
|
||||
this.traceReadOffset = this.traceStartOffset;
|
||||
}
|
||||
|
||||
getTraceRecordSize() {
|
||||
return this.traceRecordSize;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this.bmod) {
|
||||
this.bmod.dispose();
|
||||
this.bmod = null;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
private genTypes() {
|
||||
// generate global variables
|
||||
var state = new Struct();
|
||||
this.globals = state;
|
||||
// TODO: sort globals by size
|
||||
// outputs are first
|
||||
for (const [varname, vardef] of Object.entries(this.hdlmod.vardefs)) {
|
||||
if (vardef.isOutput) state.addVar(vardef);
|
||||
}
|
||||
state.alignTo(8);
|
||||
this.outputbytes = state.len;
|
||||
// followed by inputs and internal vars
|
||||
for (const [varname, vardef] of Object.entries(this.hdlmod.vardefs)) {
|
||||
if (!vardef.isOutput) state.addVar(vardef);
|
||||
}
|
||||
state.alignTo(8);
|
||||
this.statebytes = state.len;
|
||||
// followed by constant pool
|
||||
for (const [varname, vardef] of Object.entries(this.constpool.vardefs)) {
|
||||
state.addVar(vardef);
|
||||
}
|
||||
this.globals = state;
|
||||
state.alignTo(8);
|
||||
// and now the trace buffer
|
||||
state.addEntry(TRACERECLEN, 4, binaryen.i32);
|
||||
state.addEntry(TRACEOFS, 4, binaryen.i32);
|
||||
state.addEntry(TRACEEND, 4, binaryen.i32);
|
||||
state.addEntry(TRACEBUF, this.traceBufferSize);
|
||||
this.traceRecordSize = this.outputbytes;
|
||||
}
|
||||
|
||||
genFuncs() {
|
||||
private genFuncs() {
|
||||
// function type (dsegptr)
|
||||
var fsig = binaryen.createType([binaryen.i32])
|
||||
for (var block of this.hdlmod.blocks) {
|
||||
@ -212,10 +316,10 @@ export class HDLModuleWASM implements HDLModuleRunner {
|
||||
var fnname = block.name;
|
||||
// find locals of function
|
||||
var fscope = new Struct();
|
||||
fscope.addEntry(GLOBAL, binaryen.i32, 4, null, true); // 1st param to function
|
||||
fscope.addEntry(GLOBAL, 4, binaryen.i32, null, true); // 1st param to function
|
||||
// add __req local if change_request function
|
||||
if (this.funcResult(block) == binaryen.i32) {
|
||||
fscope.addEntry(CHANGEDET, binaryen.i32, 1, null, false);
|
||||
fscope.addEntry(CHANGEDET, 1, binaryen.i32, null, false);
|
||||
}
|
||||
this.pushScope(fscope);
|
||||
block.exprs.forEach((e) => {
|
||||
@ -243,46 +347,71 @@ export class HDLModuleWASM implements HDLModuleRunner {
|
||||
throw new HDLError(this.bmod.emitText(), `could not validate wasm module`);
|
||||
}
|
||||
|
||||
async genModule() {
|
||||
private async genModule() {
|
||||
//console.log(this.bmod.emitText());
|
||||
var wasmData = this.bmod.emitBinary();
|
||||
var compiled = await WebAssembly.compile(wasmData);
|
||||
this.instance = await WebAssembly.instantiate(compiled, {});
|
||||
this.databuf = (this.instance.exports[MEMORY] as any).buffer;
|
||||
this.data8 = new Uint8Array(this.databuf);
|
||||
this.state = new Proxy({}, {
|
||||
// TODO
|
||||
this.data16 = new Uint16Array(this.databuf);
|
||||
this.data32 = new Uint32Array(this.databuf);
|
||||
// proxy object to access globals (starting from 0)
|
||||
this.state = new Proxy({}, this.makeScopeProxy(() => 0));
|
||||
}
|
||||
|
||||
private makeScopeProxy(basefn: () => number) {
|
||||
return {
|
||||
// TODO: more types, signed/unsigned
|
||||
get: (target, prop, receiver) => {
|
||||
var vref = this.globals.lookup(prop.toString());
|
||||
var base = basefn();
|
||||
if (vref !== undefined) {
|
||||
if (isLogicType(vref.type)) {
|
||||
return this.data8[vref.offset]; // TODO: other types
|
||||
} else if (isArrayType(vref.type)) {
|
||||
return new Uint8Array(this.databuf, vref.offset, vref.size); // TODO: other types
|
||||
} else
|
||||
throw new HDLError(vref, `can't get property ${prop.toString()}`);
|
||||
if (vref.type && isArrayType(vref.type)) {
|
||||
var elsize = getArrayElementSizeFromType(vref.type);
|
||||
if (elsize == 1) {
|
||||
return new Uint8Array(this.databuf, base + vref.offset, vref.size);
|
||||
} else if (elsize == 2) {
|
||||
return new Uint16Array(this.databuf, (base + vref.offset) >> 1, vref.size >> 1);
|
||||
} else if (elsize == 4) {
|
||||
return new Uint32Array(this.databuf, (base + vref.offset) >> 2, vref.size >> 2);
|
||||
}
|
||||
} else {
|
||||
if (vref.size == 1) {
|
||||
return this.data8[base + vref.offset];
|
||||
} else if (vref.size == 2) {
|
||||
return this.data16[(base + vref.offset) >> 1];
|
||||
} else if (vref.size == 4) {
|
||||
return this.data32[(base + vref.offset) >> 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
set: (obj, prop, value) => {
|
||||
var vref = this.globals.lookup(prop.toString());
|
||||
var base = basefn();
|
||||
if (vref !== undefined) {
|
||||
if (isLogicType(vref.type)) {
|
||||
this.data8[vref.offset] = value; // TODO: other types
|
||||
if (vref.size == 1) {
|
||||
this.data8[(base + vref.offset)] = value;
|
||||
return true;
|
||||
} else if (vref.size == 2) {
|
||||
this.data16[(base + vref.offset) >> 1] = value;
|
||||
return true;
|
||||
} else if (vref.size == 4) {
|
||||
this.data32[(base + vref.offset) >> 2] = value;
|
||||
return true;
|
||||
} else if (isArrayType(vref.type)) {
|
||||
throw new HDLError(vref, `can't set property ${prop.toString()}`);
|
||||
} else {
|
||||
return true;
|
||||
throw new HDLError(vref, `can't set property ${prop.toString()}`);
|
||||
}
|
||||
} else {
|
||||
return true; // silently fail
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
genInitData() {
|
||||
private genInitData() {
|
||||
for (var rec of this.globals.locals) {
|
||||
if (rec.init) {
|
||||
var arr = this.state[rec.name];
|
||||
@ -300,112 +429,111 @@ export class HDLModuleWASM implements HDLModuleRunner {
|
||||
}
|
||||
}
|
||||
|
||||
powercycle() {
|
||||
this.finished = false;
|
||||
this.stopped = false;
|
||||
(this.instance.exports as any)._ctor_var_reset(0);
|
||||
(this.instance.exports as any)._eval_initial(0);
|
||||
for (var i=0; i<100; i++) {
|
||||
(this.instance.exports as any)._eval_settle(0);
|
||||
(this.instance.exports as any)._eval(0);
|
||||
var Vchange = (this.instance.exports as any)._change_request(0);
|
||||
if (!Vchange) {
|
||||
return;
|
||||
}
|
||||
private addHelperFunctions() {
|
||||
this.addCopyTraceRecFunction();
|
||||
this.addEvalFunction();
|
||||
this.addTick2Function();
|
||||
// TODO: this.bmod.addFunctionImport("$$rand", "builtins", "$$rand", binaryen.createType([]), binaryen.i64);
|
||||
}
|
||||
|
||||
private addCopyTraceRecFunction() {
|
||||
const m = this.bmod;
|
||||
const o_TRACERECLEN = this.globals.lookup(TRACERECLEN).offset;
|
||||
const o_TRACEOFS = this.globals.lookup(TRACEOFS).offset;
|
||||
const o_TRACEEND = this.globals.lookup(TRACEEND).offset;
|
||||
const o_TRACEBUF = this.globals.lookup(TRACEBUF).offset;
|
||||
var i32 = binaryen.i32;
|
||||
var none = binaryen.none;
|
||||
m.addFunction("copyTraceRec",
|
||||
binaryen.createType([]),
|
||||
none,
|
||||
[i32, i32, i32], // src, len, dest
|
||||
m.block("@block", [
|
||||
// $0 = 0 (start of globals)
|
||||
m.local.set(0, m.i32.const(GLOBALOFS)),
|
||||
// don't use $0 as data seg offset, assume trace buffer offsets start @ 0
|
||||
// $1 = TRACERECLEN
|
||||
m.local.set(1, m.i32.load(0, 4, m.i32.const(o_TRACERECLEN))),
|
||||
// $2 = TRACEOFS
|
||||
m.local.set(2, m.i32.load(0, 4, m.i32.const(o_TRACEOFS))),
|
||||
// while ($1--) [$0]++ = [$2]++
|
||||
m.loop("@loop", m.block(null, [
|
||||
m.i64.store(0, 8, m.local.get(2, i32), m.i64.load(0, 8, m.local.get(0, i32))),
|
||||
m.local.set(0, m.i32.add(m.local.get(0, i32), m.i32.const(8))),
|
||||
m.local.set(2, m.i32.add(m.local.get(2, i32), m.i32.const(8))),
|
||||
m.local.set(1, m.i32.sub(m.local.get(1, i32), m.i32.const(8))),
|
||||
this.bmod.br_if("@loop", m.local.get(1, i32))
|
||||
])),
|
||||
// TRACEOFS += TRACERECLEN
|
||||
m.i32.store(0, 4, m.i32.const(o_TRACEOFS),
|
||||
m.i32.add(
|
||||
m.i32.load(0, 4, m.i32.const(o_TRACEOFS)),
|
||||
m.i32.load(0, 4, m.i32.const(o_TRACERECLEN))
|
||||
)
|
||||
),
|
||||
// break if TRACEOFS < TRACEEND
|
||||
m.br_if("@block", m.i32.lt_u(
|
||||
m.i32.load(0, 4, m.i32.const(o_TRACEOFS)),
|
||||
m.i32.load(0, 4, m.i32.const(o_TRACEEND))
|
||||
)),
|
||||
// TRACEOFS = @TRACEBUF
|
||||
m.i32.store(0, 4, m.i32.const(o_TRACEOFS), m.i32.const(o_TRACEBUF))
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
private addTick2Function() {
|
||||
const m = this.bmod;
|
||||
if (this.globals.lookup('clk')) {
|
||||
var l_dseg = m.local.get(0, binaryen.i32);
|
||||
var l_count = m.local.get(1, binaryen.i32);
|
||||
m.addFunction("tick2",
|
||||
binaryen.createType([binaryen.i32, binaryen.i32]),
|
||||
binaryen.none,
|
||||
[],
|
||||
m.loop("@loop", m.block(null, [
|
||||
this.makeSetVariableFunction("clk", 0),
|
||||
m.drop(m.call("eval", [l_dseg], binaryen.i32)),
|
||||
this.makeSetVariableFunction("clk", 1),
|
||||
m.drop(m.call("eval", [l_dseg], binaryen.i32)),
|
||||
// call copyTraceRec
|
||||
m.call("copyTraceRec", [], binaryen.none),
|
||||
// dec $1
|
||||
m.local.set(1, m.i32.sub(l_count, m.i32.const(1))),
|
||||
// goto @loop if $1
|
||||
m.br_if("@loop", l_count)
|
||||
]))
|
||||
);
|
||||
m.addFunctionExport("tick2", "tick2");
|
||||
} else {
|
||||
m.addFunctionExport("eval", "tick2");
|
||||
}
|
||||
throw new HDLError(null, `model did not converge on reset()`)
|
||||
}
|
||||
|
||||
eval() {
|
||||
(this.instance.exports as any).eval(0);
|
||||
}
|
||||
|
||||
tick() {
|
||||
// TODO: faster
|
||||
this.state.clk ^= 1;
|
||||
(this.instance.exports as any).eval(0);
|
||||
}
|
||||
|
||||
tick2(iters: number) {
|
||||
(this.instance.exports as any).tick2(0, iters);
|
||||
}
|
||||
|
||||
isFinished() { return this.finished; }
|
||||
|
||||
isStopped() { return this.stopped; }
|
||||
|
||||
saveState() {
|
||||
return { o: this.data8.slice(0, this.statebytes) };
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
this.data8.set(state.o as Uint8Array);
|
||||
}
|
||||
|
||||
test() {
|
||||
//console.log(this.globals);
|
||||
this.state.reset = 1;
|
||||
for (var i=0; i<50; i++) {
|
||||
this.tick();
|
||||
this.tick();
|
||||
if (i==5) this.state.reset = 0;
|
||||
}
|
||||
console.log(this.databuf);
|
||||
var t1 = new Date().getTime();
|
||||
var tickiters = 10000;
|
||||
var looplen = Math.round(100000000/tickiters);
|
||||
for (var i=0; i<looplen; i++) {
|
||||
this.tick2(tickiters);
|
||||
}
|
||||
var t2 = new Date().getTime();
|
||||
console.log('wasm:',t2-t1,'msec',i*tickiters,'iterations');
|
||||
console.log(this.databuf);
|
||||
}
|
||||
|
||||
addHelperFunctions() {
|
||||
private addEvalFunction() {
|
||||
this.bmod.addFunction("eval",
|
||||
binaryen.createType([binaryen.i32]), // (dataptr)
|
||||
binaryen.i32, // return # of iterations
|
||||
[], // no locals
|
||||
this.makeTickFunction(0)
|
||||
binaryen.createType([binaryen.i32]),
|
||||
binaryen.i32,
|
||||
[],
|
||||
this.makeTickFuncBody(0)
|
||||
);
|
||||
this.bmod.addFunctionExport("eval", "eval");
|
||||
|
||||
// TODO: what if there is no clk? (e.g. ALU)
|
||||
var l_dseg = this.bmod.local.get(0, binaryen.i32);
|
||||
var l_count = this.bmod.local.get(1, binaryen.i32);
|
||||
this.bmod.addFunction("tick2",
|
||||
binaryen.createType([binaryen.i32, binaryen.i32]), // (dataptr, iters)
|
||||
binaryen.none, // return nothing
|
||||
[], // no locals
|
||||
this.bmod.loop("@loop", this.bmod.block(null, [
|
||||
this.makeSetVariableFunction("clk", 0),
|
||||
this.bmod.drop(this.bmod.call("eval", [l_dseg], binaryen.i32)),
|
||||
this.makeSetVariableFunction("clk", 1),
|
||||
this.bmod.drop(this.bmod.call("eval", [l_dseg], binaryen.i32)),
|
||||
// dec $1
|
||||
this.bmod.local.set(1, this.bmod.i32.sub(l_count, this.bmod.i32.const(1))),
|
||||
// goto @loop if $1
|
||||
this.bmod.br_if("@loop", l_count)
|
||||
]))
|
||||
);
|
||||
this.bmod.addFunctionExport("tick2", "tick2");
|
||||
}
|
||||
|
||||
makeGetVariableFunction(name: string, value: number) {
|
||||
private makeGetVariableFunction(name: string, value: number) {
|
||||
var dtype = this.globals.lookup(name).type;
|
||||
var src : HDLVarRef = {refname:name, dtype:dtype};
|
||||
return this.e2w(src);
|
||||
}
|
||||
|
||||
makeSetVariableFunction(name: string, value: number) {
|
||||
private makeSetVariableFunction(name: string, value: number) {
|
||||
var dtype = this.globals.lookup(name).type;
|
||||
var dest : HDLVarRef = {refname:name, dtype:dtype};
|
||||
var src : HDLConstant = {cvalue:value, dtype:dtype};
|
||||
return this.assign2wasm(dest, src);
|
||||
}
|
||||
|
||||
makeTickFunction(count: number) {
|
||||
private makeTickFuncBody(count: number) {
|
||||
var dseg = this.bmod.local.get(0, binaryen.i32);
|
||||
if (count > 4)
|
||||
return this.bmod.i32.const(count);
|
||||
@ -413,27 +541,27 @@ export class HDLModuleWASM implements HDLModuleRunner {
|
||||
this.bmod.call("_eval", [dseg], binaryen.none),
|
||||
this.bmod.if(
|
||||
this.bmod.call("_change_request", [dseg], binaryen.i32),
|
||||
this.makeTickFunction(count+1),
|
||||
this.makeTickFuncBody(count+1),
|
||||
this.bmod.return(this.bmod.local.get(0, binaryen.i32))
|
||||
)
|
||||
], binaryen.i32)
|
||||
}
|
||||
|
||||
funcResult(func: HDLBlock) {
|
||||
private funcResult(func: HDLBlock) {
|
||||
// only _change functions return a result
|
||||
return func.name.startsWith("_change_request") ? binaryen.i32 : binaryen.none;
|
||||
}
|
||||
|
||||
pushScope(scope: Struct) {
|
||||
private pushScope(scope: Struct) {
|
||||
scope.parent = this.locals;
|
||||
this.locals = scope;
|
||||
}
|
||||
|
||||
popScope() {
|
||||
private popScope() {
|
||||
this.locals = this.locals.parent;
|
||||
}
|
||||
|
||||
i3264(dt: HDLDataType) {
|
||||
private i3264(dt: HDLDataType) {
|
||||
var size = getDataTypeSize(dt);
|
||||
var type = getBinaryenType(size);
|
||||
if (type == binaryen.i32) return this.bmod.i32;
|
||||
@ -441,11 +569,11 @@ export class HDLModuleWASM implements HDLModuleRunner {
|
||||
else throw new HDLError(null, `unknown type for i3264 ${type}`);
|
||||
}
|
||||
|
||||
dataptr() : number {
|
||||
private dataptr() : number {
|
||||
return this.bmod.local.get(0, binaryen.i32); // 1st param of function == data ptr
|
||||
}
|
||||
|
||||
e2w(e: HDLExpr, opts?:Options) : number {
|
||||
private e2w(e: HDLExpr, opts?:Options) : number {
|
||||
if (e == null) {
|
||||
return this.bmod.nop();
|
||||
} else if (isBlock(e)) {
|
||||
@ -531,7 +659,8 @@ export class HDLModuleWASM implements HDLModuleRunner {
|
||||
} else if (isBinop(dest)) {
|
||||
// TODO: array bounds
|
||||
var addr = this.address2wasm(dest);
|
||||
return this.storemem(addr, 0, getDataTypeSize(dest.dtype), value);
|
||||
var elsize = getArrayElementSizeFromExpr(dest.left);
|
||||
return this.storemem(addr, 0, elsize, value);
|
||||
}
|
||||
throw new HDLError([dest, src], `cannot complete assignment`);
|
||||
}
|
||||
@ -567,7 +696,7 @@ export class HDLModuleWASM implements HDLModuleRunner {
|
||||
address2wasm(e: HDLExpr) : number {
|
||||
if (isBinop(e) && e.op == 'arraysel') {
|
||||
var array = this.address2wasm(e.left);
|
||||
var elsize = getArrayElementSize(e.left);
|
||||
var elsize = getArrayElementSizeFromExpr(e.left);
|
||||
var index = this.e2w(e.right);
|
||||
return this.bmod.i32.add(
|
||||
array,
|
||||
|
@ -20,7 +20,23 @@ async function testWASM() {
|
||||
var bmod = new HDLModuleWASM(parser.modules[modname], parser.modules['@CONST-POOL@']);
|
||||
await bmod.init();
|
||||
bmod.powercycle();
|
||||
bmod.test();
|
||||
//console.log(this.globals);
|
||||
bmod.state.reset = 1;
|
||||
for (var i=0; i<10; i++) {
|
||||
bmod.tick2(1);
|
||||
if (i==5) bmod.state.reset = 0;
|
||||
bmod.nextTrace();
|
||||
}
|
||||
console.log(bmod.databuf);
|
||||
var t1 = new Date().getTime();
|
||||
var tickiters = 10000;
|
||||
var looplen = Math.round(100000000/tickiters);
|
||||
for (var i=0; i<looplen; i++) {
|
||||
bmod.tick2(tickiters);
|
||||
}
|
||||
var t2 = new Date().getTime();
|
||||
console.log('wasm:',t2-t1,'msec',i*tickiters,'iterations');
|
||||
console.log(bmod.databuf);
|
||||
}
|
||||
|
||||
async function testJS() {
|
||||
|
@ -5,7 +5,7 @@ import { SampleAudio } from "../common/audio";
|
||||
import { safe_extend } from "../common/util";
|
||||
import { WaveformView, WaveformProvider, WaveformMeta } from "../ide/waveform";
|
||||
import { setFrameRateUI, loadScript } from "../ide/ui";
|
||||
import { HDLModuleRunner, HDLUnit, isLogicType } from "../common/hdl/hdltypes";
|
||||
import { HDLModuleRunner, HDLModuleTrace, HDLUnit, isLogicType } from "../common/hdl/hdltypes";
|
||||
import { HDLModuleJS } from "../common/hdl/hdlruntime";
|
||||
import { HDLModuleWASM } from "../common/hdl/hdlwasm";
|
||||
|
||||
@ -78,13 +78,17 @@ const CYCLES_PER_FILL = 20;
|
||||
var VerilogPlatform = function(mainElement, options) {
|
||||
this.__proto__ = new (BasePlatform as any)();
|
||||
|
||||
var video, audio;
|
||||
var video : RasterVideo;
|
||||
var audio;
|
||||
var poller;
|
||||
var useAudio = false;
|
||||
var usePaddles = false;
|
||||
var videoWidth = 292;
|
||||
var videoHeight = 256;
|
||||
var maxVideoLines = 262+40; // vertical hold
|
||||
var idata, timer, timerCallback;
|
||||
var idata : Uint32Array;
|
||||
var timer : AnimationTimer;
|
||||
var timerCallback;
|
||||
var top : HDLModuleRunner;
|
||||
var cyclesPerFrame = (256+23+7+23)*262; // 4857480/60 Hz
|
||||
|
||||
@ -109,6 +113,7 @@ var VerilogPlatform = function(mainElement, options) {
|
||||
var frameidx=0;
|
||||
var framehsync=false;
|
||||
var framevsync=false;
|
||||
var scanlineCycles = 0;
|
||||
|
||||
var RGBLOOKUP = [
|
||||
0xff222222,
|
||||
@ -240,7 +245,8 @@ var VerilogPlatform = function(mainElement, options) {
|
||||
}
|
||||
|
||||
setGenInputs() {
|
||||
useAudio = (audio != null);
|
||||
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];
|
||||
@ -354,9 +360,16 @@ var VerilogPlatform = function(mainElement, options) {
|
||||
}
|
||||
}
|
||||
|
||||
updateVideoFrameCycles(ncycles:number, sync:boolean, trace:boolean) {
|
||||
updateVideoFrameCycles(ncycles:number, sync:boolean, trace:boolean) : void {
|
||||
ncycles |= 0;
|
||||
var inspect = inspect_obj && inspect_sym;
|
||||
// use fast trace buffer-based update?
|
||||
if (sync && !trace && top['trace'] != null) {
|
||||
this.updateVideoFrameFast((top as any) as HDLModuleTrace);
|
||||
this.updateRecorder();
|
||||
return;
|
||||
}
|
||||
// use slow update method
|
||||
var trace0 = trace_index;
|
||||
while (ncycles--) {
|
||||
if (trace) {
|
||||
@ -400,6 +413,59 @@ var VerilogPlatform = function(mainElement, options) {
|
||||
}
|
||||
}
|
||||
|
||||
// use trace buffer to update video
|
||||
updateVideoFrameFast(tmod: HDLModuleTrace) {
|
||||
var maxLineCycles = videoWidth < 300 ? 521 : 1009; // prime number so we eventually sync up
|
||||
if (!scanlineCycles) scanlineCycles = maxLineCycles;
|
||||
var nextlineCycles = scanlineCycles;
|
||||
// TODO: we can go faster if no paddle/sound
|
||||
frameidx = 0;
|
||||
var wasvsync = false;
|
||||
// iterate through a frame of scanlines + room for vsync
|
||||
for (framey=0; framey<videoHeight*2; framey++) {
|
||||
if (usePaddles) {
|
||||
top.state.hpaddle = framey > video.paddle_x ? 1 : 0;
|
||||
top.state.vpaddle = framey > video.paddle_y ? 1 : 0;
|
||||
}
|
||||
// generate frames in trace buffer
|
||||
top.tick2(nextlineCycles);
|
||||
// convert trace buffer to video/audio
|
||||
var n = 0;
|
||||
if (framey < videoHeight) {
|
||||
for (framex=0; framex<videoWidth; framex++) {
|
||||
var rgb = tmod.trace.rgb;
|
||||
idata[frameidx++] = rgb & 0x80000000 ? rgb : RGBLOOKUP[rgb & 15];
|
||||
if (useAudio) {
|
||||
audio.feedSample(tmod.trace.spkr * (1.0/255.0), 1);
|
||||
}
|
||||
tmod.nextTrace();
|
||||
}
|
||||
n += videoWidth;
|
||||
}
|
||||
// find hsync
|
||||
while (n < maxLineCycles && !tmod.trace.hsync) { tmod.nextTrace(); n++; }
|
||||
while (n < maxLineCycles && tmod.trace.hsync) { tmod.nextTrace(); n++; }
|
||||
// see if our scanline cycle count is stable
|
||||
if (n == scanlineCycles) {
|
||||
// scanline cycle count licked in, reset buffer to improve cache locality
|
||||
nextlineCycles = n;
|
||||
tmod.resetTrace();
|
||||
} else {
|
||||
// not in sync, set to prime # and we'll eventually sync
|
||||
nextlineCycles = maxLineCycles;
|
||||
scanlineCycles = n;
|
||||
}
|
||||
// exit when vsync starts and then stops
|
||||
if (tmod.trace.vsync) {
|
||||
wasvsync = true;
|
||||
} else if (wasvsync) {
|
||||
top.state.hpaddle = 0;
|
||||
top.state.vpaddle = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
snapshotTrace() {
|
||||
var arr = trace_signals;
|
||||
for (var i=0; i<arr.length; i++) {
|
||||
@ -473,6 +539,7 @@ var VerilogPlatform = function(mainElement, options) {
|
||||
var _top = new (useWASM ? HDLModuleWASM : HDLModuleJS)(topmod, unit.modules['@CONST-POOL@']);
|
||||
await _top.init();
|
||||
_top.powercycle();
|
||||
if (top) top.dispose();
|
||||
top = _top;
|
||||
// create signal array
|
||||
var signals : WaveformSignal[] = [];
|
||||
@ -510,10 +577,11 @@ var VerilogPlatform = function(mainElement, options) {
|
||||
}
|
||||
// replace program ROM, if using the assembler
|
||||
this.reset();
|
||||
// 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.program_rom_variable.length + " got " + 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 {
|
||||
|
@ -1,196 +0,0 @@
|
||||
|
||||
type V2JS_Var = {
|
||||
wordlen:number,
|
||||
name:string,
|
||||
len:number,
|
||||
ofs:number,
|
||||
arrdim?:number[],
|
||||
input:boolean,
|
||||
output:boolean,
|
||||
}
|
||||
|
||||
type V2JS_Code = {
|
||||
name:string,
|
||||
ports:V2JS_Var[],
|
||||
signals:V2JS_Var[],
|
||||
funcs:string[],
|
||||
}
|
||||
|
||||
type V2JS_Output = {
|
||||
output:{
|
||||
code:V2JS_Code,
|
||||
name:string,
|
||||
ports:V2JS_Var[],
|
||||
signals:V2JS_Var[],
|
||||
}
|
||||
}
|
||||
|
||||
function translateVerilatorOutputToJS(htext:string, cpptext:string) {
|
||||
|
||||
var moduleName : string;
|
||||
var symsName : string;
|
||||
|
||||
function parseDecls(text:string, arr:V2JS_Var[], name:string, bin?:boolean, bout?:boolean) {
|
||||
var re = new RegExp(name + "(\\d*)[(](\\w+),(\\d+),(\\d+)[)]", 'gm');
|
||||
var m;
|
||||
while ((m = re.exec(text))) {
|
||||
arr.push({
|
||||
wordlen:parseInt(m[1]),
|
||||
name:m[2],
|
||||
len:parseInt(m[3])+1,
|
||||
ofs:parseInt(m[4]),
|
||||
input:bin,
|
||||
output:bout,
|
||||
});
|
||||
}
|
||||
re = new RegExp(name + "(\\d*)[(](\\w+)\\[(\\d+)\\],(\\d+),(\\d+)[)]", 'gm');
|
||||
var m;
|
||||
while ((m = re.exec(text))) {
|
||||
arr.push({
|
||||
wordlen:parseInt(m[1]),
|
||||
name:m[2],
|
||||
arrdim:[parseInt(m[3])],
|
||||
len:parseInt(m[4])+1,
|
||||
ofs:parseInt(m[5]),
|
||||
input:bin,
|
||||
output:bout,
|
||||
});
|
||||
}
|
||||
re = new RegExp(name + "(\\d*)[(](\\w+)\\[(\\d+)\\]\\[(\\d+)\\],(\\d+),(\\d+)[)]", 'gm');
|
||||
var m;
|
||||
while ((m = re.exec(text))) {
|
||||
arr.push({
|
||||
wordlen:parseInt(m[1]),
|
||||
name:m[2],
|
||||
arrdim:[parseInt(m[3]), parseInt(m[4])],
|
||||
len:parseInt(m[5])+1,
|
||||
ofs:parseInt(m[6]),
|
||||
input:bin,
|
||||
output:bout,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function buildModule(o : V2JS_Code) : string {
|
||||
var m = '"use strict";\n';
|
||||
for (var i=0; i<o.ports.length; i++) {
|
||||
m += "\tthis." + o.ports[i].name + ";\n";
|
||||
}
|
||||
for (var i=0; i<o.signals.length; i++) {
|
||||
var sig = o.signals[i];
|
||||
if (sig.arrdim) {
|
||||
if (sig.arrdim.length == 1) {
|
||||
m += "\tvar " + sig.name + " = this." + sig.name + " = [];\n";
|
||||
} else if (sig.arrdim.length == 2) {
|
||||
m += "\tvar " + sig.name + " = this." + sig.name + " = [];\n";
|
||||
m += "\tfor(var i=0; i<" + sig.arrdim[0] + "; i++) { " + sig.name + "[i] = []; }\n";
|
||||
}
|
||||
} else {
|
||||
m += "\tthis." + sig.name + ";\n";
|
||||
}
|
||||
}
|
||||
for (var i=0; i<o.funcs.length; i++) {
|
||||
m += o.funcs[i];
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
function getStats(o : V2JS_Code) {
|
||||
var nmembits = 0;
|
||||
var nlines = 0;
|
||||
for (var sig of o.signals.concat(o.ports)) {
|
||||
var len = sig.len || 0;
|
||||
if (sig.arrdim)
|
||||
for (var n of sig.arrdim)
|
||||
len *= n;
|
||||
nmembits += len;
|
||||
}
|
||||
for (var fn of o.funcs) {
|
||||
nlines += fn.split('\n').length;
|
||||
}
|
||||
//console.log(nmembits,'bits',nlines,'lines');
|
||||
return {bits:nmembits, lines:nlines};
|
||||
}
|
||||
|
||||
function translateFunction(text : string) : string {
|
||||
text = text.trim();
|
||||
if (text.match(/VL_RAND_RESET_Q/))
|
||||
throw Error("Values longer than 32 bits are not supported");
|
||||
var funcname = text.match(/(\w+)/)[1];
|
||||
text = text.replace(symsName + "* __restrict ", "");
|
||||
text = text.replace(moduleName + "* __restrict vlTOPp VL_ATTR_UNUSED", "var vlTOPp");
|
||||
text = text.replace(/\bVL_DEBUG_IF\(([^]+?)\);\n/g,"/*VL_DEBUG_IF($1);*/\n");
|
||||
//text = text.replace(/\bVL_DEBUG_IF/g,"!__debug__?0:\n");
|
||||
text = text.replace(/\bVL_SIG(\d*)[(](\w+),(\d+),(\d+)[)]/g, 'var $2');
|
||||
text = text.replace(/\b->\b/g, ".");
|
||||
text = text.replace('VL_INLINE_OPT', '');
|
||||
text = text.replace(/[(]IData[)]/g, '');
|
||||
text = text.replace(/\b(0x[0-9a-f]+)U/gi, '$1');
|
||||
text = text.replace(/\b([0-9]+)U/gi, '$1');
|
||||
text = text.replace(/\bQData /g, 'var ');
|
||||
text = text.replace(/\bbool /g, '');
|
||||
text = text.replace(/\bint /g, 'var ');
|
||||
text = text.replace(/(\w+ = VL_RAND_RESET_)/g, 'this.$1'); // TODO?
|
||||
//text = text.replace(/^\s*(\w+ = \d+;)/gm, 'this.$1'); // TODO?
|
||||
//text = text.replace(/(\w+\[\w+\] = VL_RAND_RESET_I)/g, 'this.$1');
|
||||
text = text.replace(/^#/gm, '//#');
|
||||
text = text.replace(/VL_LIKELY/g, '!!');
|
||||
text = text.replace(/VL_UNLIKELY/g, '!!');
|
||||
// for memread
|
||||
text = text.replace(/VL_SIGW[(](\w+),(\d+),(\d+),(\d+)[)]/g, 'var $1 = new Uint32Array($4)');
|
||||
// convert VL_ULL() 64-bits into an array of two 32-bits
|
||||
text = text.replace(/VL_ULL[(]0x([0-9a-f]+?)([0-9a-f]{8})[)]/g, '[0x$2, 0x$1]');
|
||||
//[%0t] %Error: scoreboard.v:53: Assertion failed in %Nscoreboard_top.scoreboard_gen: reset 64 -935359306 Vscoreboard_top
|
||||
text = text.replace(/Verilated::(\w+)Error/g, 'console.log');
|
||||
text = text.replace(/vlSymsp.name[(][)]/g, '"'+moduleName+'"');
|
||||
return "function " + text + "\nthis." + funcname + " = " + funcname + ";\n";
|
||||
}
|
||||
|
||||
function translateStaticVars(text : string) : string {
|
||||
var s = "";
|
||||
var m;
|
||||
var re = /VL_ST_SIG(\d+)[(](\w+?)::(\w+).(\d+).,(\d+),(\d+)[)]/g;
|
||||
while (m = re.exec(text)) {
|
||||
s += "var " + m[3] + " = this." + m[3] + " = new Uint" + m[1] + "Array(" + m[4] + ");\n";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// parse header file
|
||||
moduleName = /VL_MODULE.(\w+)./.exec(htext)[1];
|
||||
symsName = moduleName + "__Syms";
|
||||
var ports = [];
|
||||
parseDecls(htext, ports, 'VL_IN', true, false);
|
||||
parseDecls(htext, ports, 'VL_OUT', false, true);
|
||||
var signals = [];
|
||||
parseDecls(htext, signals, 'VL_SIG');
|
||||
|
||||
// parse cpp file
|
||||
// split functions
|
||||
var re_fnsplit = new RegExp("(?:void|QData) " + moduleName + "::");
|
||||
var functexts = cpptext.split(re_fnsplit);
|
||||
var funcs = [];
|
||||
funcs.push(translateStaticVars(functexts[0]));
|
||||
for (var i=4; i<functexts.length; i++) {
|
||||
var fntxt = translateFunction(functexts[i]);
|
||||
funcs.push(fntxt);
|
||||
}
|
||||
|
||||
var modinput = {
|
||||
name:moduleName,
|
||||
ports:ports,
|
||||
signals:signals,
|
||||
funcs:funcs,
|
||||
};
|
||||
|
||||
return {
|
||||
output:{
|
||||
code:buildModule(modinput),
|
||||
name:moduleName,
|
||||
ports:ports,
|
||||
signals:signals,
|
||||
//stats:getStats(modinput),
|
||||
}
|
||||
};
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user