diff --git a/src/common/hdl/hdlruntime.ts b/src/common/hdl/hdlruntime.ts index c943f85c..8a02a505 100644 --- a/src/common/hdl/hdlruntime.ts +++ b/src/common/hdl/hdlruntime.ts @@ -1,6 +1,6 @@ -import { byteArrayToString } from "../util"; -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 { byteArrayToString, safe_extend } from "../util"; +import { HDLBinop, HDLBlock, HDLConstant, HDLDataType, HDLExpr, HDLExtendop, HDLFuncCall, HDLModuleDef, HDLModuleRunner, HDLTriop, HDLUnop, HDLValue, HDLVariableDef, HDLVarRef, isArrayItem, isArrayType, isBinop, isBlock, isConstExpr, isFuncCall, isLogicType, isTriop, isUnop, isVarDecl, isVarRef, isWhileop } from "./hdltypes"; interface VerilatorUnit { _ctor_var_reset(state) : void; @@ -10,7 +10,7 @@ interface VerilatorUnit { _change_request(state) : boolean; } -export class HDLModuleJS { +export class HDLModuleJS implements HDLModuleRunner { mod: HDLModuleDef; constpool: HDLModuleDef; @@ -22,14 +22,12 @@ export class HDLModuleJS { stopped: boolean = false; settleTime: number = 0; curconsts: {}; + constused: number; specfuncs: VerilatorUnit[] = []; constructor(mod: HDLModuleDef, constpool: HDLModuleDef) { this.mod = mod; this.constpool = constpool; - } - - init() { this.basefuncs = {} as any; this.state = {}; //new Object(this.funcs) as any; // set built-in functions @@ -39,10 +37,12 @@ export class HDLModuleJS { // generate functions this.basefuncs = this.genFuncs({}); this.curfuncs = this.basefuncs; - for (var i=0; i<2; i++) { + for (var i=0; i<16; i++) { this.specfuncs[i] = this.genFuncs({ - reset:(i&1), + //reset:(i&1), //clk:(i&2), + //CPU16$state:i + test_CPU16_top$cpu$state:i }); } // set initial state @@ -57,22 +57,30 @@ export class HDLModuleJS { } } + init() { + } + genFuncs(constants: {}) : VerilatorUnit { var funcs = Object.create(this.basefuncs); this.curconsts = constants; for (var block of this.mod.blocks) { this.locals = {}; + // if we have at least 1 constant value, check for it (set counter to zero) + this.constused = (Object.keys(this.curconsts).length == 0) ? 99999 : 0; var s = this.block2js(block); - try { - var funcname = block.name||'__anon'; - var funcbody = `'use strict'; function ${funcname}(o) { ${s} }; return ${funcname};`; - var func = new Function('', funcbody)(); - funcs[block.name] = func; - //console.log(funcbody); - } catch (e) { - console.log(funcbody); - throw e; + if (this.constused) { + try { + var funcname = block.name||'__anon'; + var funcbody = `'use strict'; function ${funcname}(o) { ${s} }; return ${funcname};`; + var func = new Function('', funcbody)(); + funcs[block.name] = func; + //console.log(funcbody); + } catch (e) { + console.log(funcbody); + throw e; + } } + //if (this.constused) console.log('FUNC',constants,funcname,this.constused); } return funcs; } @@ -88,14 +96,14 @@ export class HDLModuleJS { return s; } - reset() { + powercycle() { this.finished = false; this.stopped = false; this.basefuncs._ctor_var_reset(this.state); this.basefuncs._eval_initial(this.state); for (var i=0; i<100; i++) { this.basefuncs._eval_settle(this.state); - this.eval(); + this.basefuncs._eval(this.state); var Vchange = this.basefuncs._change_request(this.state); if (!Vchange) { this.settleTime = i; @@ -106,9 +114,14 @@ export class HDLModuleJS { } eval() { - //var clk = this.state.clk as number; + var clk = this.state.clk as number; var reset = this.state.reset as number; - this.curfuncs = this.specfuncs[reset]; + var state = this.state.test_CPU16_top$cpu$state as number; + var opcode = this.state.CPU$opcode as number; + var aluop = this.state.CPU$aluop as number; + var fi = state; + //this.curfuncs = this.specfuncs[fi & 0xff]; + this.curfuncs = this.basefuncs; for (var i=0; i<100; i++) { this.curfuncs._eval(this.state); var Vchange = this.curfuncs._change_request(this.state); @@ -125,15 +138,13 @@ export class HDLModuleJS { throw new Error(`model did not converge on eval()`) } - tick2() { - //var k1 = Object.keys(this.state).length; - // TODO - this.state.clk = 0; - this.eval(); - this.state.clk = 1; - this.eval(); - //var k2 = Object.keys(this.state).length; - //if (k2 != k1) console.log(k1, k2); + tick2(iters: number) { + while (iters-- > 0) { + this.state.clk = 0; + this.eval(); + this.state.clk = 1; + this.eval(); + } } defaultValue(dt: HDLDataType, vardef?: HDLVariableDef) : HDLValue { @@ -186,14 +197,15 @@ export class HDLModuleJS { return this.expr2js(block); } - expr2js(e: HDLExpr, options?:{cond?:boolean}) : string { + expr2js(e: HDLExpr, options?:{store?:boolean,cond?:boolean}) : string { if (e == null) { return "/*null*/"; // TODO } if (isVarRef(e)) { - if (this.curconsts[e.refname] != null) + if (this.curconsts[e.refname] != null && !(options||{}).store) { + this.constused++; return `${this.curconsts[e.refname]}`; - else if (this.locals[e.refname]) + } else if (this.locals[e.refname]) return `${e.refname}`; else return `o.${e.refname}`; @@ -233,7 +245,7 @@ export class HDLModuleJS { case 'assignpre': case 'assigndly': case 'assignpost': - return `${this.expr2js(e.right)} = ${this.expr2js(e.left)}`; + return `${this.expr2js(e.right, {store:true})} = ${this.expr2js(e.left)}`; case 'arraysel': case 'wordsel': return `${this.expr2js(e.left)}[${this.expr2js(e.right)}]`; @@ -303,11 +315,9 @@ export class HDLModuleJS { expr2reset(e: HDLExpr) { if (isVarRef(e)) { - // don't reset constant values - if (this.curconsts[e.refname] != null) - return `/* ${e.refname} */`; - // TODO: random values? - if (isLogicType(e.dtype)) { + if (this.curconsts[e.refname] != null) { + return `${e.refname}`; + } else if (isLogicType(e.dtype)) { return `${this.expr2js(e)} = 0`; } else if (isArrayType(e.dtype)) { if (isLogicType(e.dtype.subtype)) { @@ -380,6 +390,32 @@ export class HDLModuleJS { return null; } + isStopped() { return this.stopped; } + isFinished() { return this.finished; } + + tick() { + (this.state as any).clk ^= 1; + this.eval(); + } + + get(varname: string) { + return this.state[varname]; + } + + set(varname: string, value) { + if (varname in this.state) { + this.state[varname] = value; + } + } + + saveState() { + return safe_extend(true, {}, this.state); + } + + loadState(state) { + safe_extend(true, this.state, state); + } + } const OP2JS : {[key:string] : string} = { diff --git a/src/common/hdl/hdltypes.ts b/src/common/hdl/hdltypes.ts index 9186ffea..d3ef4d44 100644 --- a/src/common/hdl/hdltypes.ts +++ b/src/common/hdl/hdltypes.ts @@ -1,4 +1,18 @@ +export interface HDLModuleRunner { + state: any; // live state or proxy object + eval() : void; + tick() : void; + tick2(iters: number) : void; + powercycle() : void; + isFinished() : boolean; + isStopped() : boolean; + saveState() : {}; + loadState(state: {}) : void; +} + +/// + export interface HDLLogicType extends HDLSourceObject { left: number; right: number; diff --git a/src/common/hdl/hdlwasm.ts b/src/common/hdl/hdlwasm.ts index a3171ef7..91a28099 100644 --- a/src/common/hdl/hdlwasm.ts +++ b/src/common/hdl/hdlwasm.ts @@ -1,5 +1,5 @@ -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 { 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 { @@ -21,6 +21,7 @@ const VERILATOR_UNIT_FUNCTIONS = [ interface Options { store?: boolean; funcblock?: HDLBlock; + resulttype?: number; } const GLOBAL = "$$GLOBAL"; @@ -29,6 +30,16 @@ const MEMORY = "0"; /// +export class HDLError extends Error { + obj: any; + constructor(obj: any, msg: string) { + super(msg); + Object.setPrototypeOf(this, HDLError.prototype); + this.obj = obj; + if (obj) console.log(obj); + } +} + function getDataTypeSize(dt: HDLDataType) : number { if (isLogicType(dt)) { if (dt.left <= 7) @@ -42,14 +53,41 @@ function getDataTypeSize(dt: HDLDataType) : number { 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. + // TODO: additional padding for array? + return (Math.abs(dt.high.cvalue - dt.low.cvalue) + 1) * getDataTypeSize(dt.subtype); } else { - console.log(dt); - throw Error(`don't know data type`); + throw new HDLError(dt, `don't know data type`); } } +function getArrayElementSize(e: HDLExpr) : number { + if (isVarRef(e) && isArrayType(e.dtype)) { + return getDataTypeSize(e.dtype.subtype); + } else if (isBinop(e) && isArrayType(e.dtype)) { + return getDataTypeSize(e.dtype.subtype); + } + 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); + } + 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) { return size <= 4 || size > 8 ? binaryen.i32 : binaryen.i64 } @@ -61,6 +99,7 @@ interface StructRec { size: number; itype: number; index: number; + init: HDLBlock; } class Struct { @@ -72,10 +111,15 @@ class Struct { addVar(vardef: HDLVariableDef) { var size = getDataTypeSize(vardef.dtype); - return this.addEntry(vardef.name, getBinaryenType(size), size, vardef.dtype, false); + var rec = this.addEntry(vardef.name, getBinaryenType(size), 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); + while (this.len % align) this.len++; // pointers are 32 bits, so if size > 8 it's a pointer var rec : StructRec = { name: name, @@ -84,8 +128,9 @@ class Struct { itype: itype, index: this.params.length + this.locals.length, offset: this.len, + init: null, } - this.len += 8; //TODO: rec.size, alignment + this.len += size; if (rec.name != null) this.vars[rec.name] = rec; if (isParam) this.params.push(rec); else this.locals.push(rec); @@ -105,117 +150,228 @@ class Struct { } } -export class HDLModuleWASM { +/// + +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; + state: any; + statebytes: number; + outputbytes: number; 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? + var memsize = Math.ceil(this.globals.len / 65536) + 1; + this.bmod.setMemory(memsize, memsize, MEMORY); // memory is in 64k chunks this.genFuncs(); } + async init() { + await this.genModule(); + this.genInitData(); + } + genTypes() { + // generate global variables var state = new Struct(); + // outputs are first for (const [varname, vardef] of Object.entries(this.hdlmod.vardefs)) { + if (vardef.isOutput) state.addVar(vardef); + } + 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); + } + this.statebytes = state.len; + // followed by constant pool + for (const [varname, vardef] of Object.entries(this.constpool.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`); + // 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 new HDLError(this.bmod.emitText(), `could not validate wasm module`); + } + + async genModule() { //console.log(this.bmod.emitText()); - this.test(); + 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 + get: (target, prop, receiver) => { + var vref = this.globals.lookup(prop.toString()); + 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()}`); + } + return undefined; + }, + set: (obj, prop, value) => { + var vref = this.globals.lookup(prop.toString()); + if (vref !== undefined) { + if (isLogicType(vref.type)) { + this.data8[vref.offset] = value; // TODO: other types + return true; + } else if (isArrayType(vref.type)) { + throw new HDLError(vref, `can't set property ${prop.toString()}`); + } else { + return true; + } + } else { + return true; // silently fail + } + } + }); + } + + 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 this.e2w(stmt)); var ret = opts && opts.funcblock ? this.funcResult(opts.funcblock) : binaryen.none; - if (ret == binaryen.i32) { // must have return value + // 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); } @@ -328,10 +494,9 @@ export class HDLModuleWASM { if (size <= 4) return this.bmod.i32.const(e.cvalue); else - throw new Error(`constants > 32 bits not supported`) + throw new HDLError(e, `constants > 32 bits not supported`) } else { - console.log(e); - throw new Error(`non-logic constants not supported`) + throw new HDLError(e, `non-logic constants not supported`) } } @@ -342,17 +507,9 @@ export class HDLModuleWASM { 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()); - } + return this.loadmem(this.dataptr(), global.offset, global.size); } - throw new Error(`cannot lookup variable ${e.refname}`) + throw new HDLError(e, `cannot lookup variable ${e.refname}`) } local2wasm(e: HDLVariableDef, opts:Options) : number { @@ -361,7 +518,7 @@ export class HDLModuleWASM { return this.bmod.nop(); // TODO } - assign2wasm(dest: HDLExpr, src: HDLExpr) { + assign2wasm(dest: HDLExpr, src: HDLExpr) : number { var value = this.e2w(src); if (isVarRef(dest)) { var local = this.locals && this.locals.lookup(dest.refname); @@ -369,19 +526,70 @@ export class HDLModuleWASM { 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); - } + return this.storemem(this.dataptr(), global.offset, global.size, value); + } + } else if (isBinop(dest)) { + // TODO: array bounds + var addr = this.address2wasm(dest); + return this.storemem(addr, 0, getDataTypeSize(dest.dtype), value); + } + throw new HDLError([dest, src], `cannot complete assignment`); + } + + loadmem(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(null, `bad size ${size}`) + } + } + + storemem(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(null, `bad size ${size}`) + } + } + + address2wasm(e: HDLExpr) : number { + if (isBinop(e) && e.op == 'arraysel') { + var array = this.address2wasm(e.left); + var elsize = getArrayElementSize(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); } } - console.log(dest, src); - throw new Error(`cannot complete assignment`); + 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(addr, 0, elsize); } _assign2wasm(e: HDLBinop, opts:Options) { @@ -406,6 +614,27 @@ export class HDLModuleWASM { _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 block = []; + if (e.precond) { + block.push(this.e2w(e.precond)); + } + if (e.loopcond) { + block.push(this.bmod.br_if("@block", this.e2w(e.loopcond, {resulttype:binaryen.i32}))); + } + if (e.body) { + block.push(this.e2w(e.body)); + } + if (e.inc) { + block.push(this.e2w(e.inc)); + } + block.push(this.bmod.br("@loop")); + return this.bmod.loop("@loop", this.bmod.block("@block", block, binaryen.none)); + } _ccast2wasm(e: HDLUnop, opts:Options) { return this.e2w(e.left, opts); @@ -422,9 +651,13 @@ export class HDLModuleWASM { 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 Error(`no changedet local`); + if (!req) throw new HDLError(e, `no changedet local`); var left = this.e2w(e.left); var right = this.e2w(e.right); return this.bmod.block(null, [ @@ -439,6 +672,21 @@ export class HDLModuleWASM { this.assign2wasm(e.right, e.left) ]); } + _extends2wasm(e: HDLExtendop, opts:Options) { + var value = this.e2w(e.left); + var inst = this.i3264(e.dtype); + /* + 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); + } else */ { + var shift = this.bmod.i32.const(e.width - e.widthminv); + return inst.shr_s(inst.shl(value, shift), shift); + } + } _or2wasm(e: HDLBinop, opts:Options) { return this.i3264(e.dtype).or(this.e2w(e.left), this.e2w(e.right)); @@ -481,5 +729,11 @@ export class HDLModuleWASM { _gte2wasm(e: HDLBinop, opts:Options) { return this.i3264(e.dtype).ge_u(this.e2w(e.left), this.e2w(e.right)); } + _gts2wasm(e: HDLBinop, opts:Options) { + return this.i3264(e.dtype).gt_s(this.e2w(e.left), this.e2w(e.right)); + } + _lts2wasm(e: HDLBinop, opts:Options) { + return this.i3264(e.dtype).lt_s(this.e2w(e.left), this.e2w(e.right)); + } } diff --git a/src/common/hdl/vxmltest.ts b/src/common/hdl/vxmltest.ts index 1e160739..95804fe0 100644 --- a/src/common/hdl/vxmltest.ts +++ b/src/common/hdl/vxmltest.ts @@ -14,26 +14,33 @@ try { throw e; } console.log(parser); -var modname = process.argv[3]; -if (1 && modname) { +var modname = 'TOP'; //process.argv[3]; + +async function testWASM() { var bmod = new HDLModuleWASM(parser.modules[modname], parser.modules['@CONST-POOL@']); - bmod.init(); + await bmod.init(); + bmod.powercycle(); + bmod.test(); } -if (1 && modname) { + +async function testJS() { var mod = new HDLModuleJS(parser.modules[modname], parser.modules['@CONST-POOL@']); mod.init(); console.log(mod.getJSCode()); - mod.reset(); + mod.powercycle(); var t1 = new Date().getTime(); for (var i=0; i<100000000; i++) { - mod.tick2(); + mod.tick2(1); } mod.state.reset = 1; for (var j=0; j<10000000; j++) { - mod.tick2(); + mod.tick2(1); } var t2 = new Date().getTime(); console.log(mod.state); console.log('js:',t2-t1, 'msec', i, 'iterations', i/1000/(t2-t1), 'MHz') //console.log(emitter); } + +testWASM().then(testJS); + diff --git a/src/platform/verilog.ts b/src/platform/verilog.ts index 2cda18a5..e624e218 100644 --- a/src/platform/verilog.ts +++ b/src/platform/verilog.ts @@ -5,8 +5,9 @@ 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 { HDLUnit, isLogicType } from "../common/hdl/hdltypes"; +import { HDLModuleRunner, HDLUnit, isLogicType } from "../common/hdl/hdltypes"; import { HDLModuleJS } from "../common/hdl/hdlruntime"; +import { HDLModuleWASM } from "../common/hdl/hdlwasm"; declare var Split; @@ -84,8 +85,7 @@ var VerilogPlatform = function(mainElement, options) { var videoHeight = 256; var maxVideoLines = 262+40; // vertical hold var idata, timer, timerCallback; - var top : HDLModuleJS; - var gen; + var top : HDLModuleRunner; var cyclesPerFrame = (256+23+7+23)*262; // 4857480/60 Hz // control inputs @@ -133,23 +133,25 @@ var VerilogPlatform = function(mainElement, options) { var frameRate = 0; function vidtick() { - top.tick2(); - if (useAudio) - audio.feedSample(gen.spkr*(1.0/255.0), 1); - if (keycode && keycode >= 128 && gen.keystrobe) // keystrobe = clear hi bit of key buffer - keycode = gen.keycode = keycode & 0x7f; - if (debugCond && debugCond()) + top.tick2(1); + if (useAudio) { + audio.feedSample(top.state.spkr * (1.0/255.0), 1); + } + if (keycode && keycode >= 128 && top.state.keystrobe) { // keystrobe = clear hi bit of key buffer + keycode = keycode & 0x7f; + top.state.keycode = keycode; + } + if (debugCond && debugCond()) { debugCond = null; + } } function doreset() { - gen.reset = 1; + top.state.reset = 1; } function unreset() { - if (gen.reset !== undefined) { - gen.reset = 0; - } + top.state.reset = 0; } // inner Platform class @@ -174,6 +176,8 @@ var VerilogPlatform = function(mainElement, options) { async start() { await loadScript('./gen/common/hdl/hdltypes.js'); await loadScript('./gen/common/hdl/hdlruntime.js'); + await loadScript('./gen/common/hdl/hdlwasm.js'); + await loadScript('./node_modules/binaryen/index.js'); // TODO: path? video = new RasterVideo(mainElement,videoWidth,videoHeight,{overscan:true}); video.create(); poller = setKeyboardFromMap(video, switches, VERILOG_KEYCODE_MAP, (o,key,code,flags) => { @@ -186,7 +190,7 @@ var VerilogPlatform = function(mainElement, options) { timerCallback = () => { if (!this.isRunning()) return; - if (gen && gen.switches != null) gen.switches = switches[0]; + if (top) top.state.switches = switches[0]; this.updateFrame(); }; this.setFrameRate(60); @@ -212,7 +216,7 @@ var VerilogPlatform = function(mainElement, options) { video.setupMouseEvents(); // setup mouse click video.vcanvas.click( (e) => { - if (!gen) return; // must have created emulator + if (!top) return; // must have created emulator if (!e.ctrlKey) { //setFrameRateUI(60); return; // ctrl key must be down @@ -238,10 +242,10 @@ var VerilogPlatform = function(mainElement, options) { setGenInputs() { useAudio = (audio != null); //TODO debugCond = this.getDebugCallback(); - 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; + top.state.switches_p1 = switches[0]; + top.state.switches_p2 = switches[1]; + top.state.switches_gen = switches[2]; + top.state.keycode = keycode; } updateVideoFrame() { @@ -316,7 +320,7 @@ var VerilogPlatform = function(mainElement, options) { } updateFrame() { - if (!gen) return; + if (!top) return; if (this.hasvideo) this.updateVideoFrame(); else @@ -365,26 +369,26 @@ var VerilogPlatform = function(mainElement, options) { if (inspect) { inspect_data[frameidx] = inspect_obj[inspect_sym]; } - let rgb = gen.rgb; + let rgb = top.state.rgb; idata[frameidx] = rgb & 0x80000000 ? rgb : RGBLOOKUP[rgb & 15]; frameidx++; } - } else if (!framehsync && gen.hsync) { + } else if (!framehsync && top.state.hsync) { framehsync = true; - } else if ((framehsync && !gen.hsync) || framex > videoWidth*2) { + } else if ((framehsync && !top.state.hsync) || framex > videoWidth*2) { framehsync = false; framex = 0; framey++; - if (gen.hpaddle != null) gen.hpaddle = framey > video.paddle_x ? 1 : 0; - if (gen.vpaddle != null) gen.vpaddle = framey > video.paddle_y ? 1 : 0; + top.state.hpaddle = framey > video.paddle_x ? 1 : 0; + top.state.vpaddle = framey > video.paddle_y ? 1 : 0; } - if (framey > maxVideoLines || gen.vsync) { + if (framey > maxVideoLines || top.state.vsync) { framevsync = true; framey = 0; framex = 0; frameidx = 0; - if (gen.hpaddle != null) gen.hpaddle = 0; - if (gen.vpaddle != null) gen.vpaddle = 0; + top.state.hpaddle = 0; + top.state.vpaddle = 0; } else { var wasvsync = framevsync; framevsync = false; @@ -400,7 +404,7 @@ var VerilogPlatform = function(mainElement, options) { var arr = trace_signals; for (var i=0; i { return IGNORE_SIGNALS.indexOf(v.name)<0; }); // remove clk, reset @@ -507,11 +511,11 @@ var VerilogPlatform = function(mainElement, options) { // replace program ROM, if using the assembler this.reset(); 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); + 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); else - gen[output.program_rom_variable].set(output.program_rom); + top.state[output.program_rom_variable].set(output.program_rom); } else { alert("No program_rom variable found (" + output.program_rom_variable + ")"); } @@ -525,7 +529,7 @@ var VerilogPlatform = function(mainElement, options) { restartAudio() { // stop/start audio - var hasAudio = gen && gen.spkr !== undefined && frameRate > 1; + var hasAudio = top && top.state.spkr != null && frameRate > 1; if (audio && !hasAudio) { audio.stop(); audio = null; @@ -549,10 +553,10 @@ var VerilogPlatform = function(mainElement, options) { } isBlocked() { - return top && top.finished; + return top && top.isFinished(); } isStopped() { - return top && top.stopped; + return top && top.isStopped(); } setFrameRate(rateHz) { @@ -573,21 +577,21 @@ var VerilogPlatform = function(mainElement, options) { getFrameRate() { return frameRate; } poweron() { - top.reset(); + top.powercycle(); this.reset(); } reset() { - if (!gen) return; + if (!top) return; //top.reset(); // to avoid clobbering user inputs doreset(); trace_index = 0; if (trace_buffer) trace_buffer.fill(0); - if (video) video.setRotate(gen.rotate ? -90 : 0); + if (video) video.setRotate(top.state.rotate ? -90 : 0); $("#verilog_bar").hide(); if (!this.hasvideo) this.resume(); // TODO? } tick() { - top.tick2(); + top.tick2(1); } getToolForFilename(fn) { if (fn.endsWith(".asm")) return "jsasm"; @@ -597,13 +601,13 @@ var VerilogPlatform = function(mainElement, options) { getDefaultExtension() { return ".v"; }; inspect(name:string) : string { - if (!gen) return; + if (!top) return; if (name) name = name.replace('.','_'); if (!name || !name.match(/^\w+$/)) { inspect_obj = inspect_sym = null; return; } - var val = gen[name]; + var val = top.state[name]; /* TODO if (val === undefined && current_output.code) { var re = new RegExp("(\\w+__DOT__(?:_[dcw]_)" + name + ")\\b", "gm"); @@ -615,7 +619,7 @@ var VerilogPlatform = function(mainElement, options) { } */ if (typeof(val) === 'number') { - inspect_obj = gen; + inspect_obj = top.state; inspect_sym = name; } else { inspect_obj = inspect_sym = null; @@ -634,17 +638,13 @@ var VerilogPlatform = function(mainElement, options) { // TODO: bind() a function to avoid depot? saveState() { - var state = { - // TODO: T:gen.ticks(), - o:safe_extend(true, {}, gen) - }; - return state; + return {o: top.saveState()}; } + loadState(state) { - gen = safe_extend(true, gen, state.o); - // TODO: gen.setTicks(state.T); - //console.log(gen, state.o); + top.loadState(state.o); } + saveControlsState() { return { p1x: video.paddle_x, @@ -664,10 +664,13 @@ var VerilogPlatform = function(mainElement, options) { keycode = state.keycode; } getDownloadFile() { - return { - extension:".js", - blob: new Blob([top.getJSCode()], {type:"text/plain"}) - }; + // TODO: WASM code too? + if (top instanceof HDLModuleJS) { + return { + extension:".js", + blob: new Blob([top.getJSCode()], {type:"text/plain"}) + }; + } } } // end of inner class