import { HDLError } from "./hdlruntime"; import { HDLAlwaysBlock, HDLArrayItem, HDLBinop, HDLBlock, HDLConstant, HDLDataType, HDLDataTypeObject, HDLExpr, HDLExtendop, HDLFile, HDLFuncCall, HDLHierarchyDef, HDLInstanceDef, HDLLogicType, HDLModuleDef, HDLNativeType, HDLPort, HDLSensItem, HDLSourceLocation, HDLSourceObject, HDLTriop, HDLUnit, HDLUnop, HDLUnpackArray, HDLValue, HDLVariableDef, HDLVarRef, HDLWhileOp, isArrayType, isBinop, isBlock, isConstExpr, isFuncCall, isLogicType, isTriop, isUnop, isVarDecl, isVarRef } from "./hdltypes"; /** * Whaa? * * Each hierarchy takes (uint32[] -> uint32[]) * - convert to/from js object * - JS or WASM * - Fixed-size packets * - state is another uint32[] * Find optimal packing of bits * Find clocks * Find pivots (reset, state) concat them together * Dependency cycles * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer */ export class CompileError extends Error implements HDLSourceObject { $loc: HDLSourceLocation; constructor($loc: HDLSourceLocation, msg: string) { super(msg); this.$loc = $loc; Object.setPrototypeOf(this, CompileError.prototype); } } interface XMLNode { type: string; text: string | null; children: XMLNode[]; attrs: { [id: string]: string }; obj: any; } type XMLVisitFunction = (node: XMLNode) => any; function escapeXML(s: string): string { if (s.indexOf('&') >= 0) { return s.replace(/'/g, "'") .replace(/"/g, '"') .replace(/>/g, '>') .replace(/</g, '<') .replace(/&/g, '&'); } else { return s; } } function parseXMLPoorly(s: string, openfn?: XMLVisitFunction, closefn?: XMLVisitFunction): XMLNode { const tag_re = /[<]([/]?)([?a-z_-]+)([^>]*)[>]+|(\s*[^<]+)/gi; const attr_re = /\s*(\w+)="(.*?)"\s*/gi; var fm: RegExpMatchArray; var stack: XMLNode[] = []; var top: XMLNode; function closetop() { top = stack.pop(); if (top == null || top.type != ident) throw new CompileError(null, "mismatch close tag: " + ident); if (closefn) { top.obj = closefn(top); } if (stack.length == 0) throw new CompileError(null, "close tag without open: " + ident); stack[stack.length - 1].children.push(top); } function parseattrs(as: string): { [id: string]: string } { var am; var attrs = {}; if (as != null) { while (am = attr_re.exec(as)) { attrs[am[1]] = escapeXML(am[2]); } } return attrs; } while (fm = tag_re.exec(s)) { var [_m0, close, ident, attrs, content] = fm; //console.log(stack.length, close, ident, attrs, content); if (close) { closetop(); } else if (ident) { var node = { type: ident, text: null, children: [], attrs: parseattrs(attrs), obj: null }; stack.push(node); if (attrs) { parseattrs(attrs); } if (openfn) { node.obj = openfn(node); } if (attrs && attrs.endsWith('/')) closetop(); } else if (content != null) { if (stack.length == 0) throw new CompileError(null, "content without element"); var txt = escapeXML(content as string).trim(); if (txt.length) stack[stack.length - 1].text = txt; } } if (stack.length != 1) throw new CompileError(null, "tag not closed"); if (stack[0].type != '?xml') throw new CompileError(null, "?xml needs to be first element"); return top; } export class VerilogXMLParser implements HDLUnit { files: { [id: string]: HDLFile } = {}; dtypes: { [id: string]: HDLDataType } = {}; modules: { [id: string]: HDLModuleDef } = {}; hierarchies: { [id: string]: HDLHierarchyDef } = {}; cur_node : XMLNode; cur_module : HDLModuleDef; cur_loc : HDLSourceLocation; cur_loc_str : string; cur_deferred = []; constructor() { // TODO: other types? this.dtypes['QData'] = {left:63, right:0}; this.dtypes['IData'] = {left:31, right:0}; this.dtypes['SData'] = {left:15, right:0}; this.dtypes['CData'] = {left:7, right:0}; this.dtypes['byte'] = {left:7, right:0}; this.dtypes['shortint'] = {left:15, right:0}; this.dtypes['int'] = {left:31, right:0}; this.dtypes['integer'] = {left:31, right:0}; this.dtypes['longint'] = {left:63, right:0}; this.dtypes['time'] = {left:63, right:0}; } defer(fn: () => void) { this.cur_deferred.unshift(fn); } defer2(fn: () => void) { this.cur_deferred.push(fn); } run_deferred() { this.cur_deferred.forEach((fn) => fn()); this.cur_deferred = []; } name2js(s: string) { if (s == null) throw new CompileError(this.cur_loc, `no name`); return s.replace(/[^a-z0-9_]/gi, '$'); } findChildren(node: XMLNode, type: string, required: boolean) : XMLNode[] { var arr = node.children.filter((n) => n.type == type); if (arr.length == 0 && required) throw new CompileError(this.cur_loc, `no child of type ${type}`); return arr; } parseSourceLocation(node: XMLNode): HDLSourceLocation { var loc = node.attrs['loc']; if (loc) { if (loc == this.cur_loc_str) { return this.cur_loc; // cache last parsed $loc object } else { var [fileid, line, col, end_line, end_col] = loc.split(','); var $loc = { hdlfile: this.files[fileid], path: this.files[fileid].filename, line: parseInt(line), start: parseInt(col)-1, end_line: parseInt(end_line), end: parseInt(end_col)-1, } this.cur_loc = $loc; this.cur_loc_str = loc; return $loc; } } else { return null; } } open_module(node: XMLNode) { var module: HDLModuleDef = { $loc: this.parseSourceLocation(node), name: node.attrs['name'], origName: node.attrs['origName'], blocks: [], instances: [], vardefs: {}, } if (this.cur_module) throw new CompileError(this.cur_loc, `nested modules not supported`); this.cur_module = module; return module; } deferDataType(node: XMLNode, def: HDLDataTypeObject) { var dtype_id = node.attrs['dtype_id']; if (dtype_id != null) { this.defer(() => { def.dtype = this.dtypes[dtype_id]; if (!def.dtype) { throw new CompileError(this.cur_loc, `Unknown data type ${dtype_id} for ${node.type}`); } }) } } parseConstValue(s: string) : number | bigint { const re_const = /(\d+)'([s]?)h([0-9a-f]+)/i; var m = re_const.exec(s); if (m) { var numstr = m[3]; if (numstr.length <= 8) return parseInt(numstr, 16); else return BigInt('0x' + numstr); } else { throw new CompileError(this.cur_loc, `could not parse constant "${s}"`); } } resolveVar(s: string, mod: HDLModuleDef) : HDLVariableDef { var def = mod.vardefs[s]; if (def == null) throw new CompileError(this.cur_loc, `could not resolve variable "${s}"`); return def; } resolveModule(s: string) : HDLModuleDef { var mod = this.modules[s]; if (mod == null) throw new CompileError(this.cur_loc, `could not resolve module "${s}"`); return mod; } // visit_verilator_xml(node: XMLNode) { } visit_package(node: XMLNode) { // TODO? } visit_module(node: XMLNode) { this.findChildren(node, 'var', false).forEach((n) => { if (isVarDecl(n.obj)) { this.cur_module.vardefs[n.obj.name] = n.obj; } }) this.modules[this.cur_module.name] = this.cur_module; this.cur_module = null; } visit_var(node: XMLNode) : HDLVariableDef { var name = node.attrs['name']; name = this.name2js(name); var vardef: HDLVariableDef = { $loc: this.parseSourceLocation(node), name: name, origName: node.attrs['origName'], isInput: node.attrs['dir'] == 'input', isOutput: node.attrs['dir'] == 'output', isParam: node.attrs['param'] == 'true', dtype: null, } this.deferDataType(node, vardef); var const_nodes = this.findChildren(node, 'const', false); if (const_nodes.length) { vardef.constValue = const_nodes[0].obj; } var init_nodes = this.findChildren(node, 'initarray', false); if (init_nodes.length) { vardef.initValue = init_nodes[0].obj; } return vardef; } visit_const(node: XMLNode) : HDLConstant { var name = node.attrs['name']; var cvalue = this.parseConstValue(name); var constdef: HDLConstant = { $loc: this.parseSourceLocation(node), dtype: null, cvalue: typeof cvalue === 'number' ? cvalue : null, bigvalue: typeof cvalue === 'bigint' ? cvalue : null, } this.deferDataType(node, constdef); return constdef; } visit_varref(node: XMLNode) : HDLVarRef { var name = node.attrs['name']; name = this.name2js(name); var varref: HDLVarRef = { $loc: this.parseSourceLocation(node), dtype: null, refname: name } this.deferDataType(node, varref); var mod = this.cur_module; /* this.defer2(() => { varref.vardef = this.resolveVar(name, mod); }); */ return varref; } visit_sentree(node: XMLNode) { // TODO } visit_always(node: XMLNode) : HDLAlwaysBlock { // TODO var sentree : HDLSensItem[]; var expr : HDLExpr; if (node.children.length == 2) { sentree = node.children[0].obj as HDLSensItem[]; expr = node.children[1].obj as HDLExpr; // TODO: check sentree } else { sentree = null; expr = node.children[0].obj as HDLExpr; } var always: HDLAlwaysBlock = { $loc: this.parseSourceLocation(node), blocktype: node.type, name: null, senlist: sentree, exprs: [expr], }; this.cur_module.blocks.push(always); return always; } visit_begin(node: XMLNode) : HDLBlock { var exprs = []; node.children.forEach((n) => exprs.push(n.obj)); return { $loc: this.parseSourceLocation(node), blocktype: node.type, name: node.attrs['name'], exprs: exprs, } } visit_initarray(node: XMLNode) : HDLBlock { return this.visit_begin(node); } visit_inititem(node: XMLNode) : HDLArrayItem { this.expectChildren(node, 1, 1); return { index: parseInt(node.attrs['index']), expr: node.children[0].obj } } visit_cfunc(node: XMLNode) : HDLBlock { if (this.cur_module == null) { // TODO? //console.log('no module open, skipping', node); return; } var block = this.visit_begin(node); block.exprs = []; node.children.forEach((n) => block.exprs.push(n.obj)); this.cur_module.blocks.push(block); return block; } visit_cuse(node: XMLNode) { // TODO? } visit_instance(node: XMLNode) : HDLInstanceDef { var instance : HDLInstanceDef = { $loc: this.parseSourceLocation(node), name: node.attrs['name'], origName: node.attrs['origName'], ports: [], module: null, } node.children.forEach((child) => { instance.ports.push(child.obj); }) this.cur_module.instances.push(instance); this.defer(() => { instance.module = this.resolveModule(node.attrs['defName']); }) return instance; } visit_iface(node: XMLNode) { throw new CompileError(this.cur_loc, `interfaces not supported`); } visit_intfref(node: XMLNode) { throw new CompileError(this.cur_loc, `interfaces not supported`); } visit_port(node: XMLNode) : HDLPort { this.expectChildren(node, 1, 1); var varref: HDLPort = { $loc: this.parseSourceLocation(node), name: node.attrs['name'], expr: node.children[0].obj } return varref; } visit_netlist(node: XMLNode) { } visit_files(node: XMLNode) { } visit_module_files(node: XMLNode) { node.children.forEach((n) => { if (n.obj) { var file = this.files[(n.obj as HDLFile).id]; if (file) file.isModule = true; } }); } visit_file(node: XMLNode) { return this.visit_file_or_module(node, false); } // TODO visit_scope(node: XMLNode) { } visit_topscope(node: XMLNode) { } visit_file_or_module(node: XMLNode, isModule: boolean) : HDLFile { var file : HDLFile = { id: node.attrs['id'], filename: node.attrs['filename'], isModule: isModule, } this.files[file.id] = file; return file; } visit_cells(node: XMLNode) { this.expectChildren(node, 1, 9999); var hier = node.children[0].obj as HDLHierarchyDef; if (hier != null) { var hiername = hier.name; this.hierarchies[hiername] = hier; } } visit_cell(node: XMLNode) : HDLHierarchyDef { var hier = { $loc: this.parseSourceLocation(node), name: node.attrs['name'], module: null, parent: null, children: node.children.map((n) => n.obj), } if (node.children.length > 0) throw new CompileError(this.cur_loc, `multiple non-flattened modules not yet supported`); node.children.forEach((n) => (n.obj as HDLHierarchyDef).parent = hier); this.defer(() => { hier.module = this.resolveModule(node.attrs['submodname']); }) return hier; } visit_basicdtype(node: XMLNode): HDLDataType { let id = node.attrs['id']; var dtype: HDLDataType; var dtypename = node.attrs['name']; switch (dtypename) { case 'logic': case 'integer': // TODO? case 'bit': let dlogic: HDLLogicType = { $loc: this.parseSourceLocation(node), left: parseInt(node.attrs['left'] || "0"), right: parseInt(node.attrs['right'] || "0"), } dtype = dlogic; break; case 'string': let dstring: HDLNativeType = { $loc: this.parseSourceLocation(node), jstype: 'string' } dtype = dstring; break; default: dtype = this.dtypes[dtypename]; if (dtype == null) { throw new CompileError(this.cur_loc, `unknown data type ${dtypename}`); } } this.dtypes[id] = dtype; return dtype; } visit_refdtype(node: XMLNode) { } visit_enumdtype(node: XMLNode) { } visit_enumitem(node: XMLNode) { } visit_packarraydtype(node: XMLNode): HDLDataType { // TODO: packed? return this.visit_unpackarraydtype(node); } visit_memberdtype(node: XMLNode) { throw new CompileError(null, `structs not supported`); } visit_constdtype(node: XMLNode) { // TODO? throw new CompileError(null, `constant data types not supported`); } visit_paramtypedtype(node: XMLNode) { // TODO? throw new CompileError(null, `constant data types not supported`); } visit_unpackarraydtype(node: XMLNode): HDLDataType { let id = node.attrs['id']; let sub_dtype_id = node.attrs['sub_dtype_id']; let range = node.children[0].obj as HDLBinop; if (isConstExpr(range.left) && isConstExpr(range.right)) { var dtype: HDLUnpackArray = { $loc: this.parseSourceLocation(node), subtype: null, low: range.left, high: range.right, } this.dtypes[id] = dtype; this.defer(() => { dtype.subtype = this.dtypes[sub_dtype_id]; if (!dtype.subtype) throw new CompileError(this.cur_loc, `Unknown data type ${sub_dtype_id} for array`); }) return dtype; } else { throw new CompileError(this.cur_loc, `could not parse constant exprs in array`) } } visit_senitem(node: XMLNode) : HDLSensItem { var edgeType = node.attrs['edgeType']; if (edgeType != "POS" && edgeType != "NEG") throw new CompileError(this.cur_loc, "POS/NEG required") return { $loc: this.parseSourceLocation(node), edgeType: edgeType, expr: node.obj } } visit_text(node: XMLNode) { } visit_cstmt(node: XMLNode) { } visit_cfile(node: XMLNode) { } visit_typetable(node: XMLNode) { } visit_constpool(node: XMLNode) { } visit_comment(node: XMLNode) { } expectChildren(node: XMLNode, low: number, high: number) { if (node.children.length < low || node.children.length > high) throw new CompileError(this.cur_loc, `expected between ${low} and ${high} children`); } __visit_unop(node: XMLNode) : HDLUnop { this.expectChildren(node, 1, 1); var expr: HDLUnop = { $loc: this.parseSourceLocation(node), op: node.type, dtype: null, left: node.children[0].obj as HDLExpr, } this.deferDataType(node, expr); return expr; } visit_extend(node: XMLNode) : HDLUnop { var unop = this.__visit_unop(node) as HDLExtendop; unop.width = parseInt(node.attrs['width']); unop.widthminv = parseInt(node.attrs['widthminv']); if (unop.width != 32) throw new CompileError(this.cur_loc, `extends width ${unop.width} != 32`) return unop; } visit_extends(node: XMLNode) : HDLUnop { return this.visit_extend(node); } __visit_binop(node: XMLNode) : HDLBinop { this.expectChildren(node, 2, 2); var expr: HDLBinop = { $loc: this.parseSourceLocation(node), op: node.type, dtype: null, left: node.children[0].obj as HDLExpr, right: node.children[1].obj as HDLExpr, } this.deferDataType(node, expr); return expr; } visit_if(node: XMLNode) : HDLTriop { this.expectChildren(node, 2, 3); var expr: HDLTriop = { $loc: this.parseSourceLocation(node), op: 'if', dtype: null, cond: node.children[0].obj as HDLExpr, left: node.children[1].obj as HDLExpr, right: node.children[2] && node.children[2].obj as HDLExpr, } return expr; } // while and for loops visit_while(node: XMLNode) : HDLWhileOp { this.expectChildren(node, 2, 4); var expr: HDLWhileOp = { $loc: this.parseSourceLocation(node), op: 'while', dtype: null, precond: node.children[0].obj as HDLExpr, loopcond: node.children[1].obj as HDLExpr, body: node.children[2] && node.children[2].obj as HDLExpr, inc: node.children[3] && node.children[3].obj as HDLExpr, } return expr; } __visit_triop(node: XMLNode) : HDLBinop { this.expectChildren(node, 3, 3); var expr: HDLTriop = { $loc: this.parseSourceLocation(node), op: node.type, dtype: null, cond: node.children[0].obj as HDLExpr, left: node.children[1].obj as HDLExpr, right: node.children[2].obj as HDLExpr, } this.deferDataType(node, expr); return expr; } __visit_func(node: XMLNode) : HDLFuncCall { var expr = { $loc: this.parseSourceLocation(node), dtype: null, funcname: node.attrs['func'] || ('$' + node.type), args: node.children.map(n => n.obj as HDLExpr) } this.deferDataType(node, expr); return expr; } visit_not(node: XMLNode) { return this.__visit_unop(node); } visit_negate(node: XMLNode) { return this.__visit_unop(node); } visit_redand(node: XMLNode) { return this.__visit_unop(node); } visit_redor(node: XMLNode) { return this.__visit_unop(node); } visit_redxor(node: XMLNode) { return this.__visit_unop(node); } visit_initial(node: XMLNode) { return this.__visit_unop(node); } visit_ccast(node: XMLNode) { return this.__visit_unop(node); } visit_creset(node: XMLNode) { return this.__visit_unop(node); } visit_creturn(node: XMLNode) { return this.__visit_unop(node); } visit_contassign(node: XMLNode) { return this.__visit_binop(node); } visit_assigndly(node: XMLNode) { return this.__visit_binop(node); } visit_assignpre(node: XMLNode) { return this.__visit_binop(node); } visit_assignpost(node: XMLNode) { return this.__visit_binop(node); } visit_assign(node: XMLNode) { return this.__visit_binop(node); } visit_arraysel(node: XMLNode) { return this.__visit_binop(node); } visit_wordsel(node: XMLNode) { return this.__visit_binop(node); } visit_eq(node: XMLNode) { return this.__visit_binop(node); } visit_neq(node: XMLNode) { return this.__visit_binop(node); } visit_lte(node: XMLNode) { return this.__visit_binop(node); } visit_gte(node: XMLNode) { return this.__visit_binop(node); } visit_lt(node: XMLNode) { return this.__visit_binop(node); } visit_gt(node: XMLNode) { return this.__visit_binop(node); } visit_and(node: XMLNode) { return this.__visit_binop(node); } visit_or(node: XMLNode) { return this.__visit_binop(node); } visit_xor(node: XMLNode) { return this.__visit_binop(node); } visit_add(node: XMLNode) { return this.__visit_binop(node); } visit_sub(node: XMLNode) { return this.__visit_binop(node); } visit_concat(node: XMLNode) { return this.__visit_binop(node); } // TODO? visit_shiftl(node: XMLNode) { return this.__visit_binop(node); } visit_shiftr(node: XMLNode) { return this.__visit_binop(node); } visit_shiftrs(node: XMLNode) { return this.__visit_binop(node); } visit_mul(node: XMLNode) { return this.__visit_binop(node); } visit_div(node: XMLNode) { return this.__visit_binop(node); } visit_moddiv(node: XMLNode) { return this.__visit_binop(node); } visit_muls(node: XMLNode) { return this.__visit_binop(node); } visit_divs(node: XMLNode) { return this.__visit_binop(node); } visit_moddivs(node: XMLNode) { return this.__visit_binop(node); } visit_gts(node: XMLNode) { return this.__visit_binop(node); } visit_lts(node: XMLNode) { return this.__visit_binop(node); } visit_gtes(node: XMLNode) { return this.__visit_binop(node); } visit_ltes(node: XMLNode) { return this.__visit_binop(node); } // TODO: more? visit_range(node: XMLNode) { return this.__visit_binop(node); } visit_cond(node: XMLNode) { return this.__visit_triop(node); } visit_condbound(node: XMLNode) { return this.__visit_triop(node); } visit_sel(node: XMLNode) { return this.__visit_triop(node); } visit_changedet(node: XMLNode) : HDLBinop { if (node.children.length == 0) return null; //{ op: "changedet", dtype:null, left:null, right:null } else return this.__visit_binop(node); } visit_ccall(node: XMLNode) { return this.__visit_func(node); } visit_finish(node: XMLNode) { return this.__visit_func(node); } visit_stop(node: XMLNode) { return this.__visit_func(node); } visit_rand(node: XMLNode) { return this.__visit_func(node); } visit_time(node: XMLNode) { return this.__visit_func(node); } visit_display(node: XMLNode) { return null; } visit_sformatf(node: XMLNode) { return null; } visit_scopename(node: XMLNode) { return null; } visit_readmem(node: XMLNode) { return this.__visit_func(node); } // xml_open(node: XMLNode) { this.cur_node = node; var method = this[`open_${node.type}`]; if (method) { return method.bind(this)(node); } } xml_close(node: XMLNode) { this.cur_node = node; var method = this[`visit_${node.type}`]; if (method) { return method.bind(this)(node); } else { throw new CompileError(this.cur_loc, `no visitor for ${node.type}`) } } parse(xmls: string) { parseXMLPoorly(xmls, this.xml_open.bind(this), this.xml_close.bind(this)); this.cur_node = null; this.run_deferred(); } }