import binaryen = require('binaryen'); import { hasDataType, HDLBinop, HDLBlock, HDLConstant, HDLDataType, HDLDataTypeObject, HDLExpr, HDLExtendop, HDLFuncCall, HDLModuleDef, HDLModuleRunner, HDLSourceLocation, HDLSourceObject, HDLTriop, HDLUnop, HDLValue, HDLVariableDef, HDLVarRef, HDLWhileOp, isArrayItem, isArrayType, isBigConstExpr, isBinop, isBlock, isConstExpr, isFuncCall, isLogicType, isTriop, isUnop, isVarDecl, isVarRef, isWhileop } from "./hdltypes"; import { HDLError } from "./hdlruntime"; const VERILATOR_UNIT_FUNCTIONS = [ "_ctor_var_reset", "_eval_initial", "_eval_settle", "_eval", "_change_request" ]; interface Options { store?: boolean; funcblock?: HDLBlock; funcarg?: boolean; resulttype?: number; } const GLOBALOFS = 0; const MEMORY = "$$MEM"; const GLOBAL = "$$GLOBAL"; const CHANGEDET = "$$CHANGE"; const TRACERECLEN = "$$treclen"; const TRACEOFS = "$$tofs"; const TRACEEND = "$$tend"; const TRACEBUF = "$$tbuf"; /// 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)) { // TODO: additional padding for array? return (Math.abs(dt.high.cvalue - dt.low.cvalue) + 1) * getDataTypeSize(dt.subtype); } else { throw new HDLError(dt, `don't know data type`); } } function isReferenceType(dt: HDLDataType) : boolean { return getDataTypeSize(dt) > 8; } function getArrayElementSizeFromType(dtype: HDLDataType) : number { if (isArrayType(dtype)) { return getArrayElementSizeFromType(dtype.subtype); } else { return getDataTypeSize(dtype); } } function getArrayElementSizeFromExpr(e: HDLExpr) : number { if (hasDataType(e) && isArrayType(e.dtype)) { return getDataTypeSize(e.dtype.subtype); } else if (hasDataType(e) && isLogicType(e.dtype) && e.dtype.left > 63) { throw new HDLError(e, `elements > 64 bits not supported`); } throw new HDLError(e, `cannot figure out array element size`); } function getArrayValueSize(e: HDLExpr) : number { if (isVarRef(e)) { var dt = e.dtype; while (isArrayType(dt)) dt = dt.subtype; return getDataTypeSize(dt); } else if (isBinop(e) && e.op == 'arraysel') { return getArrayValueSize(e.left); } else if (isBinop(e) && e.op == 'wordsel') { return getArrayValueSize(e.left); } throw new HDLError(e, `cannot figure out array value size`); } function getAlignmentForSize(size) { if (size <= 1) return 1; else if (size <= 2) return 2; else if (size <= 4) return 4; else return 8; } function getBinaryenType(size: number) { if (size <= 4) return binaryen.i32; else if (size <= 8) return binaryen.i64; else return binaryen.none; } interface StructRec { name: string; type: HDLDataType; offset: number; size: number; itype: number; index: number; init: HDLBlock; constval: HDLConstant; } class Struct { parent : Struct; len : number = 0; vars : {[name: string] : StructRec} = {}; locals : StructRec[] = []; params : StructRec[] = []; addVar(vardef: HDLVariableDef) { var size = getDataTypeSize(vardef.dtype); var rec = this.addEntry(vardef.name, size, getBinaryenType(size), vardef.dtype, false); rec.init = vardef.initValue; rec.constval = vardef.constValue; return rec; } 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, type: hdltype, size: size, itype: itype, index: this.params.length + this.locals.length, offset: this.len, init: null, constval: null, } this.len += size; 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 implements HDLModuleRunner { bmod: binaryen.Module; instance: WebAssembly.Instance; hdlmod: HDLModuleDef; constpool: HDLModuleDef; globals: Struct; locals: Struct; finished: boolean; 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; getFileData = null; maxMemoryMB : number; constructor(moddef: HDLModuleDef, constpool: HDLModuleDef, maxMemoryMB?: number) { this.hdlmod = moddef; this.constpool = constpool; this.maxMemoryMB = maxMemoryMB || 16; this.genMemory(); this.genFuncs(); this.validate(); } async init() { await this.genModule(); this.genInitData(); this.enableTracing(); } 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() { this.state.clk ^= 1; this.eval(); } 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); } getGlobals() { var g = {}; for (const [varname, vardef] of Object.entries(this.hdlmod.vardefs)) { var o = g; var toks = varname.split('$'); for (var tok of toks.slice(0, -1)) { o[tok] = o[tok] || {}; o = o[tok]; } o[toks[toks.length-1]] = this.state[varname]; } return g; } enableTracing() { if (this.outputbytes == 0) throw new Error(`outputbytes == 0`); if (this.outputbytes % 8) throw new Error(`outputbytes must be 8-byte aligned`); if (this.traceBufferSize % 8) throw new Error(`trace buffer size must be 8-byte aligned`); 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; this.instance = null; this.databuf = null; this.data8 = null; this.data16 = null; this.data32 = null; } } // private genMemory() { this.bmod = new binaryen.Module(); this.genTypes(); var membytes = this.globals.len; if (membytes > this.maxMemoryMB*1024*1024) throw new HDLError(null, `cannot allocate ${membytes} bytes, limit is ${this.maxMemoryMB} MB`); var memblks = Math.ceil(membytes / 65536); this.bmod.setMemory(memblks, memblks, MEMORY); // memory is in 64k chunks } private genTypes() { // generate global variables var state = new Struct(); this.globals = state; // sort globals by output flag and size var vardefs = Object.values(this.hdlmod.vardefs); function getVarDefSortKey(vdef: HDLVariableDef) { var val = getDataTypeSize(vdef.dtype); // sort by size if (!vdef.isOutput) val += 1000000; // outputs are first in list return val; } vardefs.sort((a,b) => { return getVarDefSortKey(a) - getVarDefSortKey(b); }); // outputs are contiguous so we can copy them to the trace buffer // so we put them all first in the struct order for (var vardef of vardefs) { if (vardef.isOutput) state.addVar(vardef); } if (state.len == 0) state.addEntry("___", 1); // ensure as least 8 output bytes for trace buffer state.alignTo(8); this.outputbytes = state.len; // followed by inputs and internal vars (arrays after logical types) for (var vardef of vardefs) { if (!vardef.isOutput) state.addVar(vardef); } state.alignTo(8); this.statebytes = state.len; // followed by constant pool if (this.constpool) { for (const vardef of Object.values(this.constpool.vardefs)) { state.addVar(vardef); } } 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; } private genFuncs() { // function type (dsegptr) for (var block of this.hdlmod.blocks) { this.genFunction(block); } // export functions for (var fname of VERILATOR_UNIT_FUNCTIONS) { this.bmod.addFunctionExport(fname, fname); } // create helper functions this.addHelperFunctions(); // link imported functions this.addImportedFunctions(); } private validate() { // validate wasm module //console.log(this.bmod.emitText()); //this.bmod.optimize(); if (!this.bmod.validate()) { //console.log(this.bmod.emitText()); throw new HDLError(null, `could not validate wasm module`); } } private genFunction(block) { // TODO: cfuncs only var fnname = block.name; // find locals of function var fscope = new Struct(); fscope.addEntry(GLOBAL, 4, binaryen.i32, null, true); // 1st param to function // add __req local if change_request function if (this.funcResult(block.name) == binaryen.i32) { fscope.addEntry(CHANGEDET, 1, binaryen.i32, null, false); } this.pushScope(fscope); block.exprs.forEach((e) => { if (e && isVarDecl(e)) { // TODO: make local reference types, instead of promoting local arrays to global if (isReferenceType(e.dtype)) { this.globals.addVar(e); } else { 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.name); var fsig = binaryen.createType([binaryen.i32]); // pass dataptr() var fref = this.bmod.addFunction(fnname, fsig, fret, fscope.getLocals(), fbody); this.popScope(); } 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.getImportObject()); this.databuf = (this.instance.exports[MEMORY] as any).buffer; this.data8 = new Uint8Array(this.databuf); 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 (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>>1) + vref.offset, vref.size >> 1); } else if (elsize == 4) { return new Uint32Array(this.databuf, (base>>2) + vref.offset, 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 (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 { throw new HDLError(vref, `can't set property ${prop.toString()}`); } } else { return true; // silently fail } } } } private genInitData() { for (var rec of this.globals.locals) { if (rec.init) { var arr = this.state[rec.name]; if (!arr) throw new HDLError(rec, `no array to init`); for (let i=0; i { if (!this.finished) console.log('... Finished @', o); this.finished = true; }, $stop: (o) => { if (!this.stopped) console.log('... Stopped @', o); this.stopped = true; }, $time: (o) => BigInt(new Date().getTime()), // TODO: timescale $rand: (o) => (Math.random() * (65536 * 65536)) | 0, $readmem: (o,a,b) => this.$readmem(a, b) } } } private $readmem(p_filename, p_rom) { var fn = ''; for (var i=0; i<255; i++) { var charCode = this.data8[p_filename + i]; if (charCode == 0) break; fn = String.fromCharCode(charCode) + fn; } var filedata = this.getFileData && this.getFileData(fn); if (filedata == null) throw new HDLError(fn, `no file "${fn}" for $readmem`); if (typeof filedata !== 'string') throw new HDLError(fn, `file "${fn}" must be lines of hex or binary values`); var ishex = !fn.endsWith('.binary'); // TODO: hex should be attribute in xml var data = filedata.split('\n').filter(s => s !== '').map(s => parseInt(s, ishex ? 16 : 2)); 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.makeTickFuncBody(count+1), this.bmod.return(this.bmod.local.get(0, binaryen.i32)) ) ], binaryen.i32) } private funcResult(funcname: string) { // only _change functions return a result if (funcname.startsWith("_change_request")) return binaryen.i32; else if (funcname == '$time') return binaryen.i64; else if (funcname == '$rand') return binaryen.i32; else return binaryen.none; } private pushScope(scope: Struct) { scope.parent = this.locals; this.locals = scope; } private popScope() { this.locals = this.locals.parent; } private 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 new HDLError(dt, `data types > 64 bits not supported`); } private i3264rel(e: HDLBinop) { if (hasDataType(e.left) && hasDataType(e.right)) { var leftsize = getDataTypeSize(e.left.dtype); var rightsize = getDataTypeSize(e.right.dtype); // TODO: left size should equal right size, both can be > i32 though return leftsize > rightsize ? this.i3264(e.left.dtype) : this.i3264(e.right.dtype); } return this.i3264(e.dtype); } private dataptr() : number { return this.bmod.local.get(0, binaryen.i32); // 1st param of function == data ptr } private 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) || isBigConstExpr(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) { throw new HDLError(e, `no such method ${n}`) } return this[n](e, opts); } else { throw new HDLError(e, `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.name) : binaryen.none; // must have return value for change_request function if (ret == binaryen.i32) { stmts.push(this.bmod.return(this.bmod.local.get(this.locals.lookup(CHANGEDET).index, ret))); } // return block value for loop condition if (opts && opts.resulttype) { ret = binaryen.i32; } return this.bmod.block(e.name, stmts, ret); } funccall2wasm(e: HDLFuncCall, opts?:Options) : number { var args = [this.dataptr()]; for (var arg of e.args) { args.push(this.e2w(arg, {funcarg:true})); } var internal = e.funcname; if (e.funcname.startsWith('$')) { if ((e.funcname == '$stop' || e.funcname == '$finish') && e.$loc) { args = [this.bmod.i32.const(e.$loc.line)]; // line # of source code } internal += '_' + (args.length - 1); } var ret = this.funcResult(e.funcname); return this.bmod.call(internal, args, ret); } const2wasm(e: HDLConstant, opts: Options) : number { var size = getDataTypeSize(e.dtype); if (isLogicType(e.dtype)) { if (e.bigvalue != null) return this.i3264(e.dtype).const( Number(e.bigvalue & BigInt(0xffffffff)), Number(e.bigvalue >> BigInt(32))); else if (size <= 4) return this.bmod.i32.const(e.cvalue); else if (size <= 8) return this.bmod.i64.const(e.cvalue, e.cvalue >> 32); // TODO: bigint? else throw new HDLError(e, `constants > 64 bits not supported`) } else { throw new HDLError(e, `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 > 8 && opts && opts.funcarg) return this.address2wasm(e); // TODO: only applies to wordsel else return this.loadmem(e, this.dataptr(), global.offset, global.size); } throw new HDLError(e, `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) : number { 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) { return this.storemem(dest, this.dataptr(), global.offset, global.size, value); } } else if (isBinop(dest)) { var addr = this.address2wasm(dest); var elsize = dest.op == 'wordsel' ? getDataTypeSize(dest.dtype) : getArrayElementSizeFromExpr(dest.left); return this.storemem(dest, addr, 0, elsize, value); } throw new HDLError(dest, `cannot complete assignment`); } loadmem(e: HDLSourceObject, ptr, offset:number, size:number) : number { if (size == 1) { return this.bmod.i32.load8_u(offset, 1, ptr); } else if (size == 2) { return this.bmod.i32.load16_u(offset, 2, ptr); } else if (size == 4) { return this.bmod.i32.load(offset, 4, ptr); } else if (size == 8) { return this.bmod.i64.load(offset, 8, ptr); } else { throw new HDLError(e, `cannot load ${size} bytes (> 64 bits not supported)`) } } storemem(e: HDLSourceObject, ptr, offset:number, size:number, value) : number { if (size == 1) { return this.bmod.i32.store8(offset, 1, ptr, value); } else if (size == 2) { return this.bmod.i32.store16(offset, 2, ptr, value); } else if (size == 4) { return this.bmod.i32.store(offset, 4, ptr, value); } else if (size == 8) { return this.bmod.i64.store(offset, 8, ptr, value); } else { throw new HDLError(e, `cannot store ${size} bytes (> 64 bits not supported)`) } } address2wasm(e: HDLExpr) : number { if (isBinop(e) && (e.op == 'arraysel' || e.op == 'wordsel')) { var elsize = e.op == 'wordsel' ? getDataTypeSize(e.dtype) : getArrayElementSizeFromExpr(e.left); var array = this.address2wasm(e.left); var index = this.e2w(e.right); return this.bmod.i32.add( array, this.bmod.i32.mul(this.bmod.i32.const(elsize), index) ); } else if (isVarRef(e)) { var local = this.locals && this.locals.lookup(e.refname); var global = this.globals.lookup(e.refname); if (local != null) { throw new HDLError(e, `can't get array local address yet`); } else if (global != null) { return this.bmod.i32.const(global.offset); } } throw new HDLError(e, `cannot get address`); } // TODO: array bounds _arraysel2wasm(e: HDLBinop, opts:Options) : number { var addr = this.address2wasm(e); var elsize = getArrayValueSize(e); return this.loadmem(e, addr, 0, elsize); } _wordsel2wasm(e: HDLBinop, opts:Options) : number { return this._arraysel2wasm(e, opts); } _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)); } _condbound2wasm(e: HDLTriop, opts:Options) { return this.bmod.select(this.e2w(e.cond), this.e2w(e.left), this.e2w(e.right)); } _while2wasm(e: HDLWhileOp, opts:Options) { var l_block = this.label("@block"); var l_loop = this.label("@loop"); var block = []; if (e.precond) { block.push(this.e2w(e.precond)); } if (e.loopcond) { // TODO: detect constant while loop condition block.push(this.bmod.if( this.e2w(e.loopcond, {resulttype:binaryen.i32}), this.bmod.nop(), this.bmod.br(l_block) // exit loop )); } if (e.body) { block.push(this.e2w(e.body)); } if (e.inc) { block.push(this.e2w(e.inc)); } block.push(this.bmod.br(l_loop)); return this.bmod.loop(l_loop, this.bmod.block(l_block, block, binaryen.none)); } _ccast2wasm(e: HDLUnop, opts:Options) { if (hasDataType(e.left)) { return this.castexpr(this.e2w(e.left), e.left.dtype, e.dtype); } else throw new HDLError(e.left, `no data type for ccast`); } castexpr(val: number, tsrc: HDLDataType, tdst: HDLDataType) : number { if (isLogicType(tsrc) && isLogicType(tdst) && tsrc.right == 0 && tdst.right == 0) { if (tsrc.left == tdst.left) { return val; } else if (tsrc.left <= 31 && tdst.left <= 31) { return val; } // TODO: signed? else if (tsrc.left <= 31 && tdst.left == 63) { // 32 -> 64 return this.bmod.i64.extend_u(val); } else if (tsrc.left == 63 && tdst.left <= 31) { // 64 -> 32 return this.bmod.i32.wrap(val); } } throw new HDLError([tsrc, tdst], `cannot cast`); } _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)); } _negate2wasm(e: HDLUnop, opts:Options) { var inst = this.i3264(e.dtype); return inst.sub(inst.const(0,0), this.e2w(e.left, opts)); } _changedet2wasm(e: HDLBinop, opts:Options) { var req = this.locals.lookup(CHANGEDET); if (!req) throw new HDLError(e, `no changedet local`); var left = this.e2w(e.left); var right = this.e2w(e.right); let datainst = this.i3264(hasDataType(e.left) && e.left.dtype); return this.bmod.block(null, [ // if (left != right) req = 1; this.bmod.if( datainst.ne(left, right), this.bmod.local.set(req.index, this.bmod.i32.const(1)), this.bmod.nop(), ), // ${this.expr2js(e.right)} = ${this.expr2js(e.left)}` this.assign2wasm(e.right, e.left) ]); } _extend2wasm(e: HDLExtendop, opts:Options) { var value = this.e2w(e.left); var inst = this.i3264(e.dtype); if (this.bmod.getFeatures() & binaryen.Features.SignExt) { if (e.widthminv == 32 && e.width == 64) { return this.bmod.i64.extend_u(value); } } throw new HDLError(e, `cannot extend`); } _extends2wasm(e: HDLExtendop, opts:Options) { var value = this.e2w(e.left); var inst = this.i3264(e.dtype); if (this.bmod.getFeatures() & binaryen.Features.SignExt) { if (e.widthminv == 8) { return inst.extend8_s(value); } else if (e.widthminv == 16) { return inst.extend16_s(value); } else if (e.widthminv == 32 && e.width == 64) { return this.bmod.i64.extend32_s(value); } } var shift = inst.const(e.width - e.widthminv, 0); return inst.shr_s(inst.shl(value, shift), shift); } // TODO: i32/i64 _redxor2wasm(e: HDLUnop) { if (hasDataType(e.left)) { var left = this.e2w(e.left); var inst = this.i3264(e.left.dtype); var rtn = inst.and( inst.const(1, 0), inst.popcnt(left)); // (num_set_bits & 1) return rtn; //this.castexpr(rtn, e.dtype, e.left.dtype); } else throw new HDLError(e, ''); } binop(e: HDLBinop, f_op: (a:number, b:number) => number, upcastLeft?: boolean, upcastRight?: boolean) { var left = this.e2w(e.left); var right = this.e2w(e.right); if (hasDataType(e.left) && hasDataType(e.right)) { var lsize = getDataTypeSize(e.left.dtype); var rsize = getDataTypeSize(e.right.dtype); if (lsize < rsize && upcastLeft) left = this.castexpr(left, e.left.dtype, e.right.dtype); else if (rsize < lsize && upcastRight) right = this.castexpr(right, e.right.dtype, e.left.dtype); } var rtn = f_op(left, right); return rtn; } relop(e: HDLBinop, f_op : (a:number,b:number)=>number) { return f_op(this.e2w(e.left), this.e2w(e.right)); } _or2wasm(e: HDLBinop) { return this.binop(e, this.i3264(e.dtype).or); } _and2wasm(e: HDLBinop) { return this.binop(e, this.i3264(e.dtype).and); } _xor2wasm(e: HDLBinop) { return this.binop(e, this.i3264(e.dtype).xor); } _shiftl2wasm(e: HDLBinop) { return this.binop(e, this.i3264(e.dtype).shl, false, true); } _shiftr2wasm(e: HDLBinop) { return this.binop(e, this.i3264(e.dtype).shr_u, false, true); } _shiftrs2wasm(e: HDLBinop) { return this.binop(e, this.i3264(e.dtype).shr_s, false, true); } _add2wasm(e: HDLBinop) { return this.binop(e, this.i3264(e.dtype).add); } _sub2wasm(e: HDLBinop) { return this.binop(e, this.i3264(e.dtype).sub); } _mul2wasm(e: HDLBinop) { return this.binop(e, this.i3264(e.dtype).mul); } _muls2wasm(e: HDLBinop) { return this.binop(e, this.i3264(e.dtype).mul); // TODO: signed? } _moddiv2wasm(e: HDLBinop) { return this.binop(e, this.i3264(e.dtype).rem_u); } _div2wasm(e: HDLBinop) { return this.binop(e, this.i3264(e.dtype).div_u); } _moddivs2wasm(e: HDLBinop) { return this.binop(e, this.i3264(e.dtype).rem_s); } _divs2wasm(e: HDLBinop) { return this.binop(e, this.i3264(e.dtype).div_s); } // TODO: i32/i64 _eq2wasm(e: HDLBinop) { return this.relop(e, this.i3264rel(e).eq); } _neq2wasm(e: HDLBinop) { return this.relop(e, this.i3264rel(e).ne); } _lt2wasm(e: HDLBinop) { return this.relop(e, this.i3264rel(e).lt_u); } _gt2wasm(e: HDLBinop) { return this.relop(e, this.i3264rel(e).gt_u); } _lte2wasm(e: HDLBinop) { return this.relop(e, this.i3264rel(e).le_u); } _gte2wasm(e: HDLBinop) { return this.relop(e, this.i3264rel(e).ge_u); } _gts2wasm(e: HDLBinop) { return this.relop(e, this.i3264rel(e).gt_s); } _lts2wasm(e: HDLBinop) { return this.relop(e, this.i3264rel(e).lt_s); } _gtes2wasm(e: HDLBinop) { return this.relop(e, this.i3264rel(e).ge_s); } _ltes2wasm(e: HDLBinop) { return this.relop(e, this.i3264rel(e).le_s); } }