import { HDLAlwaysBlock, HDLArrayItem, HDLBinop, HDLBlock, HDLConstant, HDLDataType, HDLDataTypeObject, HDLExpr, HDLExtendop, HDLFile, HDLFuncCall, HDLHierarchyDef, HDLInstanceDef, HDLLogicType, HDLModuleDef, HDLNativeType, HDLPort, HDLSensItem, HDLSourceLocation, 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 */ 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.type != ident) throw Error("mismatch close tag: " + ident); if (closefn) { top.obj = closefn(top); } 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) { var txt = escapeXML(content as string).trim(); if (txt.length) stack[stack.length - 1].text = txt; } } if (stack.length != 1) throw Error("tag not closed"); if (stack[0].type != '?xml') throw Error("?xml needs to be first element"); return top; } export class CompileError extends Error { $loc : HDLSourceLocation; constructor(msg: string, loc: HDLSourceLocation) { super(msg); Object.setPrototypeOf(this, CompileError.prototype); this.$loc = loc; } } 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_deferred = []; constructor() { // TODO: other types this.dtypes['IData'] = {left:31, 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) { 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 Error(`no child of type ${type}`); return arr; } parseSourceLocation(node: XMLNode): HDLSourceLocation { var loc = node.attrs['loc']; if (loc) { var [fileid, line, col, end_line, end_col] = loc.split(','); return { file: this.files[fileid], line: parseInt(line), col: parseInt(col), end_line: parseInt(line), end_col: parseInt(col), } } 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: {}, } 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) { console.log(node); throw Error(`Unknown data type ${dtype_id} for ${node.type}`); } }) } } parseConstValue(s: string) : number { const re_const = /(\d+)'([s]?)h([0-9a-f]+)/i; var m = re_const.exec(s); if (m) { return parseInt(m[3], 16); } else { throw Error(`could not parse constant "${s}"`); } } resolveVar(s: string, mod: HDLModuleDef) : HDLVariableDef { var def = mod.vardefs[s]; if (def == null) throw Error(`could not resolve variable "${s}"`); return def; } resolveModule(s: string) : HDLModuleDef { var mod = this.modules[s]; if (mod == null) throw Error(`could not resolve module "${s}"`); return mod; } // visit_verilator_xml(node: XMLNode) { } 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 constdef: HDLConstant = { $loc: this.parseSourceLocation(node), dtype: null, cvalue: this.parseConstValue(name) } 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 { if (node.children.length != 1) throw Error('expected 1 children'); return { index: parseInt(node.attrs['index']), expr: node.children[0].obj } } visit_cfunc(node: XMLNode) : HDLBlock { 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_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_port(node: XMLNode) : HDLPort { if (node.children.length != 1) throw Error('expected 1 children'); 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) => this.files[(n.obj as HDLFile).id].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) { var hier = node.children[0].obj as HDLHierarchyDef; 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), } 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) { console.log(node); throw Error(`unknown data type ${dtypename}`); } } this.dtypes[id] = dtype; return dtype; } 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 Error(`Unknown data type ${sub_dtype_id} for array`); }) return dtype; } else { throw Error(`could not parse constant exprs in array`) } } visit_senitem(node: XMLNode) : HDLSensItem { var edgeType = node.attrs['edgeType']; if (edgeType != "POS" && edgeType != "NEG") throw Error("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_unop(node: XMLNode) : HDLUnop { if (node.children.length != 1) throw Error('expected 1 children'); 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_extends(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 Error(`extends width ${unop.width} != 32`) return unop; } __visit_binop(node: XMLNode) : HDLBinop { if (node.children.length != 2) throw Error('expected 2 children'); 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 { if (node.children.length < 2 || node.children.length > 3) throw Error('expected 2 or 3 children'); 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 { if (node.children.length < 2 || node.children.length > 4) throw Error('expected 2-4 children'); 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 { if (node.children.length != 3) throw Error('expected 2 children'); 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 { return { $loc: this.parseSourceLocation(node), funcname: node.attrs['func'] || ('$' + node.type), args: node.children.map(n => n.obj as HDLExpr) } } 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_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 this.__visit_func(node); } visit_sformatf(node: XMLNode) { return this.visit_begin(node); } 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 { console.log(node); throw Error(`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(); } }