import { HDLBinop, HDLBlock, HDLConstant, HDLDataType, HDLExpr, HDLExtendop, HDLFuncCall, HDLModuleDef, HDLTriop, HDLUnop, HDLValue, HDLVariableDef, HDLVarRef, 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", "_eval_settle", "_eval", "_change_request" ]; interface Options { store?: boolean; funcblock?: HDLBlock; } const GLOBAL = "$$GLOBAL"; const CHANGEDET = "$$CHANGE"; const MEMORY = "0"; /// function getDataTypeSize(dt: HDLDataType) : number { if (isLogicType(dt)) { if (dt.left <= 7) return 1; else if (dt.left <= 15) return 2; else if (dt.left <= 31) return 4; else if (dt.left <= 63) return 8; else return (dt.left >> 6) * 8 + 8; // 64-bit words } else if (isArrayType(dt)) { return (dt.high.cvalue - dt.low.cvalue + 1) * getDataTypeSize(dt.subtype); //return (asValue(dt.high) - asValue(dt.low) + 1) * dt. } else { console.log(dt); throw Error(`don't know data type`); } } function getBinaryenType(size: number) { return size <= 4 || size > 8 ? binaryen.i32 : binaryen.i64 } interface StructRec { name: string; type: HDLDataType; offset: number; size: number; itype: number; index: number; } class Struct { parent : Struct; len : number = 0; vars : {[name: string] : StructRec} = {}; locals : StructRec[] = []; params : StructRec[] = []; addVar(vardef: HDLVariableDef) { var size = getDataTypeSize(vardef.dtype); return this.addEntry(vardef.name, getBinaryenType(size), size, vardef.dtype, false); } addEntry(name: string, itype: number, size: number, hdltype: HDLDataType, isParam: boolean) : StructRec { // pointers are 32 bits, so if size > 8 it's a pointer var rec : StructRec = { name: name, type: hdltype, size: size, itype: itype, index: this.params.length + this.locals.length, offset: this.len, } this.len += 8; //TODO: rec.size, alignment if (rec.name != null) this.vars[rec.name] = rec; if (isParam) this.params.push(rec); else this.locals.push(rec); return rec; } getLocals() { var vars = []; for (const rec of this.locals) { vars.push(rec.itype); } return vars; } lookup(name: string) : StructRec { return this.vars[name]; } } export class HDLModuleWASM { bmod: binaryen.Module; hdlmod: HDLModuleDef; constpool: HDLModuleDef; globals: Struct; locals: Struct; constructor(moddef: HDLModuleDef, constpool: HDLModuleDef) { this.hdlmod = moddef; this.constpool = constpool; } init() { this.bmod = new binaryen.Module(); this.genTypes(); this.bmod.setMemory(this.globals.len, this.globals.len, MEMORY); // TODO? this.genFuncs(); } genTypes() { var state = new Struct(); for (const [varname, vardef] of Object.entries(this.hdlmod.vardefs)) { state.addVar(vardef); } this.globals = state; } genFuncs() { // function type (dsegptr) var fsig = binaryen.createType([binaryen.i32]) for (var block of this.hdlmod.blocks) { // TODO: cfuncs only var fnname = block.name; // find locals of function var fscope = new Struct(); fscope.addEntry(GLOBAL, binaryen.i32, 4, 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); } this.pushScope(fscope); block.exprs.forEach((e) => { if (e && isVarDecl(e)) { fscope.addVar(e); } }) // create function body var fbody = this.block2wasm(block, {funcblock:block}); //var fbody = this.bmod.return(this.bmod.i32.const(0)); var fret = this.funcResult(block); var fref = this.bmod.addFunction(fnname, fsig, fret, fscope.getLocals(), fbody); this.popScope(); } // export functions for (var fname of VERILATOR_UNIT_FUNCTIONS) { this.bmod.addFunctionExport(fname, fname); } // create helper functions this.addHelperFunctions(); // create wasm module console.log(this.bmod.emitText()); //this.bmod.optimize(); if (!this.bmod.validate()) throw Error(`could not validate wasm module`); //console.log(this.bmod.emitText()); this.test(); } test() { var wasmData = this.bmod.emitBinary(); var compiled = new WebAssembly.Module(wasmData); var instance = new WebAssembly.Instance(compiled, {}); var mem = (instance.exports[MEMORY] as any).buffer; (instance.exports as any)._ctor_var_reset(0); (instance.exports as any)._eval_initial(0); (instance.exports as any)._eval_settle(0); var data = new Uint8Array(mem); var o_clk = this.globals.lookup('clk').offset; var o_reset = this.globals.lookup('reset').offset data[o_reset] = 1; //new Uint8Array(mem)[this.globals.lookup('reset').offset] = 0; //new Uint8Array(mem)[this.globals.lookup('enable').offset] = 1; for (var i=0; i<20; i++) { data[o_clk] = 0; (instance.exports as any).tick(0); data[o_clk] = 1; (instance.exports as any).tick(0); if (i==5) new Uint8Array(mem)[this.globals.lookup('reset').offset] = 0; } console.log(mem); var t1 = new Date().getTime(); var tickiters = 10000; var looplen = Math.round(100000000/tickiters); for (var i=0; i 4) return this.bmod.i32.const(count); return this.bmod.block(null, [ this.bmod.call("_eval", [dseg], binaryen.none), this.bmod.if( this.bmod.call("_change_request", [dseg], binaryen.i32), this.makeTickFunction(count+1), this.bmod.return(this.bmod.local.get(0, binaryen.i32)) ) ], binaryen.i32) } funcResult(func: HDLBlock) { // only _change functions return a result return func.name.startsWith("_change_request") ? binaryen.i32 : binaryen.none; } pushScope(scope: Struct) { scope.parent = this.locals; this.locals = scope; } popScope() { this.locals = this.locals.parent; } i3264(dt: HDLDataType) { var size = getDataTypeSize(dt); var type = getBinaryenType(size); if (type == binaryen.i32) return this.bmod.i32; else if (type == binaryen.i64) return this.bmod.i64; else throw Error(); } dataptr() : number { return this.bmod.local.get(0, binaryen.i32); // 1st param of function == data ptr } e2w(e: HDLExpr, opts?:Options) : number { if (e == null) { return this.bmod.nop(); } else if (isBlock(e)) { return this.block2wasm(e, opts); } else if (isVarDecl(e)) { return this.local2wasm(e, opts); } else if (isVarRef(e)) { return this.varref2wasm(e, opts); } else if (isConstExpr(e)) { return this.const2wasm(e, opts); } else if (isFuncCall(e)) { return this.funccall2wasm(e, opts); } else if (isUnop(e) || isBinop(e) || isTriop(e) || isWhileop(e)) { var n = `_${e.op}2wasm`; var fn = this[n]; if (fn == null) { console.log(e); throw Error(`no such method ${n}`) } return this[n](e, opts); } else { console.log('expr', e); throw Error(`could not translate expr`) } } block2wasm(e: HDLBlock, opts?:Options) : number { var stmts = e.exprs.map((stmt) => this.e2w(stmt)); var ret = opts && opts.funcblock ? this.funcResult(opts.funcblock) : binaryen.none; if (ret == binaryen.i32) { // must have return value stmts.push(this.bmod.return(this.bmod.local.get(this.locals.lookup(CHANGEDET).index, ret))); } return this.bmod.block(e.name, stmts, ret); } funccall2wasm(e: HDLFuncCall, opts?:Options) : number { var args = [this.dataptr()]; var ret = e.funcname.startsWith("_change_request") ? binaryen.i32 : binaryen.none; return this.bmod.call(e.funcname, args, ret); } const2wasm(e: HDLConstant, opts: Options) : number { var size = getDataTypeSize(e.dtype); if (isLogicType(e.dtype)) { if (size <= 4) return this.bmod.i32.const(e.cvalue); else throw new Error(`constants > 32 bits not supported`) } else { console.log(e); throw new Error(`non-logic constants not supported`) } } varref2wasm(e: HDLVarRef, opts: Options) : number { if (opts && opts.store) throw Error(`cannot store here`); var local = this.locals && this.locals.lookup(e.refname); var global = this.globals.lookup(e.refname); if (local != null) { return this.bmod.local.get(local.index, local.itype); } else if (global != null) { if (global.size == 1) { return this.bmod.i32.load8_u(global.offset, 1, this.dataptr()); } else if (global.size == 2) { return this.bmod.i32.load16_u(global.offset, 2, this.dataptr()); } else if (global.size == 4) { return this.bmod.i32.load(global.offset, 4, this.dataptr()); } else if (global.size == 8) { return this.bmod.i64.load(global.offset, 8, this.dataptr()); } } throw new Error(`cannot lookup variable ${e.refname}`) } local2wasm(e: HDLVariableDef, opts:Options) : number { var local = this.locals.lookup(e.name); if (local == null) throw Error(`no local for ${e.name}`) return this.bmod.nop(); // TODO } assign2wasm(dest: HDLExpr, src: HDLExpr) { var value = this.e2w(src); if (isVarRef(dest)) { var local = this.locals && this.locals.lookup(dest.refname); var global = this.globals.lookup(dest.refname); if (local != null) { return this.bmod.local.set(local.index, value); } else if (global != null) { if (global.size == 1) { return this.bmod.i32.store8(global.offset, 1, this.dataptr(), value); } else if (global.size == 2) { return this.bmod.i32.store16(global.offset, 2, this.dataptr(), value); } else if (global.size == 4) { return this.bmod.i32.store(global.offset, 4, this.dataptr(), value); } else if (global.size == 8) { return this.bmod.i64.store(global.offset, 8, this.dataptr(), value); } } } console.log(dest, src); throw new Error(`cannot complete assignment`); } _assign2wasm(e: HDLBinop, opts:Options) { return this.assign2wasm(e.right, e.left); } _assignpre2wasm(e: HDLBinop, opts:Options) { return this._assign2wasm(e, opts); } _assigndly2wasm(e: HDLBinop, opts:Options) { return this._assign2wasm(e, opts); } _assignpost2wasm(e: HDLBinop, opts:Options) { return this._assign2wasm(e, opts); } _contassign2wasm(e: HDLBinop, opts:Options) { return this._assign2wasm(e, opts); } _if2wasm(e: HDLTriop, opts:Options) { return this.bmod.if(this.e2w(e.cond), this.e2w(e.left), this.e2w(e.right)); } _cond2wasm(e: HDLTriop, opts:Options) { return this.bmod.select(this.e2w(e.cond), this.e2w(e.left), this.e2w(e.right)); } _ccast2wasm(e: HDLUnop, opts:Options) { return this.e2w(e.left, opts); } _creset2wasm(e: HDLUnop, opts:Options) { // TODO return this.e2w(e.left, opts); return this.bmod.nop(); } _creturn2wasm(e: HDLUnop, opts:Options) { return this.bmod.return(this.e2w(e.left, opts)); } _not2wasm(e: HDLUnop, opts:Options) { var inst = this.i3264(e.dtype); return inst.xor(inst.const(-1, -1), this.e2w(e.left, opts)); } _changedet2wasm(e: HDLBinop, opts:Options) { var req = this.locals.lookup(CHANGEDET); if (!req) throw Error(`no changedet local`); var left = this.e2w(e.left); var right = this.e2w(e.right); return this.bmod.block(null, [ // $$req |= (${this.expr2js(e.left)} ^ ${this.expr2js(e.right)}) this.bmod.local.set(req.index, this.bmod.i32.or( this.bmod.local.get(req.index, req.itype), this.bmod.i32.xor(left, right) // TODO: i64? ) ), // ${this.expr2js(e.right)} = ${this.expr2js(e.left)}` this.assign2wasm(e.right, e.left) ]); } _or2wasm(e: HDLBinop, opts:Options) { return this.i3264(e.dtype).or(this.e2w(e.left), this.e2w(e.right)); } _and2wasm(e: HDLBinop, opts:Options) { return this.i3264(e.dtype).and(this.e2w(e.left), this.e2w(e.right)); } _xor2wasm(e: HDLBinop, opts:Options) { return this.i3264(e.dtype).xor(this.e2w(e.left), this.e2w(e.right)); } _shiftl2wasm(e: HDLBinop, opts:Options) { return this.i3264(e.dtype).shl(this.e2w(e.left), this.e2w(e.right)); } _shiftr2wasm(e: HDLBinop, opts:Options) { // TODO: signed? return this.i3264(e.dtype).shr_u(this.e2w(e.left), this.e2w(e.right)); } _add2wasm(e: HDLBinop, opts:Options) { return this.i3264(e.dtype).add(this.e2w(e.left), this.e2w(e.right)); } _sub2wasm(e: HDLBinop, opts:Options) { return this.i3264(e.dtype).sub(this.e2w(e.left), this.e2w(e.right)); } _eq2wasm(e: HDLBinop, opts:Options) { return this.i3264(e.dtype).eq(this.e2w(e.left), this.e2w(e.right)); } _neq2wasm(e: HDLBinop, opts:Options) { return this.i3264(e.dtype).ne(this.e2w(e.left), this.e2w(e.right)); } _lt2wasm(e: HDLBinop, opts:Options) { return this.i3264(e.dtype).lt_u(this.e2w(e.left), this.e2w(e.right)); } _gt2wasm(e: HDLBinop, opts:Options) { return this.i3264(e.dtype).gt_u(this.e2w(e.left), this.e2w(e.right)); } _lte2wasm(e: HDLBinop, opts:Options) { return this.i3264(e.dtype).le_u(this.e2w(e.left), this.e2w(e.right)); } _gte2wasm(e: HDLBinop, opts:Options) { return this.i3264(e.dtype).ge_u(this.e2w(e.left), this.e2w(e.right)); } }