import * as vscode from 'vscode'; import * as os from 'os'; import { AbsPath, IProgress, opeParam } from '../global'; import { HdlLangID } from '../global/enum'; import { MainOutput, ReportType } from '../global/outputChannel'; import * as common from './common'; import { hdlFile, hdlPath } from '../hdlFs'; import { HdlSymbol } from './util'; import { DoFastFileType } from '../global/lsp'; class HdlParam { private readonly topModules : Set = new Set(); private readonly srcTopModules : Set = new Set(); private readonly simTopModules : Set = new Set(); private readonly pathToHdlFiles : Map = new Map(); public readonly modules : Set = new Set(); private readonly unhandleInstances : Set = new Set(); public hasHdlFile(path: AbsPath): boolean { const moduleFile = this.getHdlFile(path); if (!moduleFile) { return false; } return true; } public getHdlFile(path: AbsPath): HdlFile | undefined { return this.pathToHdlFiles.get(path); } public getAllHdlFiles(): HdlFile[] { const hdlFiles = []; for (const [_, hdlFile] of this.pathToHdlFiles) { hdlFiles.push(hdlFile); } return hdlFiles; } /** * used only in initialization stage * @param hdlFile */ public setHdlFile(hdlFile: HdlFile) { const path = hdlFile.path; this.pathToHdlFiles.set(path, hdlFile); } /** * add a file by path and create context * @param path absolute path of the file to be added */ public async addHdlPath(path: AbsPath) { path = hdlPath.toSlash(path); await this.doHdlFast(path, 'common'); const hdlFile = this.getHdlFile(path); if (!hdlFile) { MainOutput.report('error happen when we attempt to add file by path: ' + path, ReportType.Error); } else { hdlFile.makeInstance(); // when a new file is added, retry the solution of dependency for (const hdlModule of hdlFile.getAllHdlModules()) { hdlModule.solveUnhandleInstance(); } } } public hasHdlModule(path: AbsPath | undefined, name: string): boolean { if (!path) { return false; } const hdlFile = this.getHdlFile(path); if (!hdlFile) { return false; } const hdlModule = hdlFile.getHdlModule(name); if (!hdlModule) { return false; } return true; } public getHdlModule(path: AbsPath, name: string): HdlModule | undefined { const hdlFile = this.getHdlFile(path); if (!hdlFile) { return undefined; } return hdlFile.getHdlModule(name); } public getAllHdlModules(): HdlModule[] { const hdlModules: HdlModule[] = []; this.modules.forEach(m => hdlModules.push(m)); return hdlModules; } public addHdlModule(hdlModule: HdlModule) { this.modules.add(hdlModule); } public addTopModule(hdlModule: HdlModule) { this.topModules.add(hdlModule); } public deleteTopModule(hdlModule: HdlModule) { this.topModules.delete(hdlModule); } public getAllTopModules(global :boolean = false): HdlModule[] { const topModules: HdlModule[] = []; if (global) { this.topModules.forEach(m => topModules.push(m)); } else { this.srcTopModules.forEach(m => topModules.push(m)); this.simTopModules.forEach(m => topModules.push(m)); } return topModules; } public isTopModule(path: AbsPath, name: string, global = false): boolean { const module = this.getHdlModule(path, name); if (!module) { return false; } if (global) { return this.topModules.has(module); } else { const sourceTopModule = this.selectTopModuleSourceByFileType(module); return sourceTopModule.has(module); } } public selectTopModuleSourceByFileType(hdlModule: HdlModule): Set { switch (hdlModule.file.type) { case common.HdlFileType.Src: return this.srcTopModules; case common.HdlFileType.Sim: return this.simTopModules; case common.HdlFileType.LocalLib: return this.srcTopModules; case common.HdlFileType.RemoteLib: return this.srcTopModules; default: return this.srcTopModules; } } /** * add module to top modules of a certain source (sim or src) * @param hdlModule */ public addTopModuleToSource(hdlModule: HdlModule) { const topModuleSource = this.selectTopModuleSourceByFileType(hdlModule); topModuleSource.add(hdlModule); } public deleteTopModuleToSource(hdlModule: HdlModule) { const topModuleSource = this.selectTopModuleSourceByFileType(hdlModule); topModuleSource.delete(hdlModule); } public getAllDependences(path: AbsPath, name: string): common.HdlDependence | undefined { const module = this.getHdlModule(path, name); if (!module) { return undefined; } const dependencies : common.HdlDependence = { current: [], include: [], others: [] }; for (const inst of module.getAllInstances()) { if (!inst.module) { continue; } const status = inst.instModPathStatus; if (status === common.InstModPathStatus.Current && inst.instModPath) { dependencies.current.push(inst.instModPath); } else if (status === common.InstModPathStatus.Include && inst.instModPath) { dependencies.include.push(inst.instModPath); } else if (status === common.InstModPathStatus.Others && inst.instModPath) { dependencies.others.push(inst.instModPath); } const instDependencies = this.getAllDependences(inst.module.path, inst.module.name); if (instDependencies) { dependencies.current.push(...instDependencies.current); dependencies.include.push(...instDependencies.include); dependencies.others.push(...instDependencies.others); } } return dependencies; } public getUnhandleInstanceNumber(): number { return this.unhandleInstances.size; } public getUnhandleInstanceByType(typeName: string): HdlInstance | undefined { for (const inst of this.unhandleInstances) { if (inst.type === typeName) { return inst; } } return undefined; } public addUnhandleInstance(inst: HdlInstance) { this.unhandleInstances.add(inst); } public deleteUnhandleInstance(inst: HdlInstance) { this.unhandleInstances.delete(inst); } /** * vlog -> HdlLangID.Verilog * svlog -> HdlLangID.SystemVerilog * vhdl -> HdlLangID.Vhdl * @param langID */ private alignLanguageId(langID: string) : HdlLangID { switch (langID) { case 'vhdl': return HdlLangID.Vhdl; case 'vlog': return HdlLangID.Verilog; case 'svlog': return HdlLangID.SystemVerilog; default: return HdlLangID.Unknown; } } private async doHdlFast(path: AbsPath, fileType: DoFastFileType) { try { const fast = await HdlSymbol.fast(path, fileType); if (fast) { const languageId = hdlFile.getLanguageId(path); new HdlFile(path, languageId, fast.macro, fast.content, fast.fileType); } } catch (error) { MainOutput.report('Error happen when parse ' + path, ReportType.Error); MainOutput.report('Reason: ' + error, ReportType.Error); } } public async initializeHdlFiles(hdlFiles: AbsPath[], progress: vscode.Progress) { const { t } = vscode.l10n; let count: number = 0; let fileNum = hdlFiles.length; const parallelChunk = Math.min(os.cpus().length, 32); console.log("use cpu: " + parallelChunk); const pools: { id: number, promise: Promise, path: string }[] = []; const reportTitle = t('info.progress.build-module-tree'); progress.report({ message: reportTitle + ` ${1}/${fileNum}`, increment: 0 }); async function consumePools() { for (const p of pools) { const increment = Math.floor(p.id / fileNum * 100); await p.promise; console.log("handle id " + p.id + ' increment: ' + increment); progress?.report({ message: reportTitle + ` ${p.id}/${fileNum}`, increment }); } pools.length = 0; } for (const path of hdlFiles) { count ++; console.log('send request: ' + path); const p = this.doHdlFast(path, 'common'); pools.push({ id: count, promise: p, path }); if (pools.length % parallelChunk === 0) { // 消费并发池 await consumePools(); } } if (pools.length > 0) { await consumePools(); } } public async initializeIPsPath(IPsPath: string[], progress: vscode.Progress) { const { t } = vscode.l10n; let count: number = 0; let fileNum = IPsPath.length; const parallelChunk = Math.min(os.cpus().length, 32); console.log("use cpu: " + parallelChunk); const pools: { id: number, promise: Promise, path: string }[] = []; const reportTitle = t('info.progress.build-ip-module-tree'); progress.report({ message: reportTitle + ` ${1}/${fileNum}`, increment: 0 }); async function consumePools() { for (const p of pools) { const increment = Math.floor(p.id / fileNum * 100); await p.promise; console.log("handle id " + p.id + ' increment: ' + increment); progress?.report({ message: reportTitle + ` ${p.id}/${fileNum}`, increment }); } pools.length = 0; } for (const path of IPsPath) { count ++; console.log('send request: ' + path); const p = this.doHdlFast(path, 'ip'); pools.push({ id: count, promise: p, path }); if (pools.length % parallelChunk === 0) { // 消费并发池 await consumePools(); } } if (pools.length > 0) { await consumePools(); } } public async makeAllInstance() { for (const hdlFile of this.getAllHdlFiles()) { hdlFile.makeInstance(); } } public getTopModulesByType(type: string): HdlModule[] { const hardware = opeParam.prjInfo.arch.hardware; if (hardware.sim === hardware.src) { return this.getAllTopModules(); } switch (type) { case common.HdlFileType.Src: return this.getSrcTopModules(); case common.HdlFileType.Sim: return this.getSimTopModules(); default: return []; } } public getSrcTopModules(): HdlModule[] { const srcTopModules = this.srcTopModules; if (!srcTopModules) { return []; } const moduleFiles = []; for (const module of srcTopModules) { moduleFiles.push(module); } return moduleFiles; } public getSimTopModules(): HdlModule[] { const simTopModules = this.simTopModules; if (!simTopModules) { return []; } const moduleFiles = []; for (const module of simTopModules) { moduleFiles.push(module); } return moduleFiles; } public deleteHdlFile(path: AbsPath) { path = hdlPath.toSlash(path); const moduleFile = this.getHdlFile(path); if (moduleFile) { for (const name of moduleFile.getAllModuleNames()) { moduleFile.deleteHdlModule(name); } this.pathToHdlFiles.delete(path); } } public async addHdlFile(path: AbsPath) { path = hdlPath.toSlash(path); await this.doHdlFast(path, 'common'); const moduleFile = this.getHdlFile(path); if (!moduleFile) { MainOutput.report('error happen when create moduleFile ' + path, ReportType.Warn); } else { moduleFile.makeInstance(); for (const module of moduleFile.getAllHdlModules()) { module.solveUnhandleInstance(); } } } public updateFast(path: string, fast: common.Fast) { const moduleFile = this.getHdlFile(path); if (moduleFile === undefined) { return; } // 1. update marco directly moduleFile.updateMacro(fast.macro); // 2. update modules one by one const uncheckedModuleNames = new Set(); for (const name of moduleFile.getAllModuleNames()) { uncheckedModuleNames.add(name); } for (const rawHdlModule of fast.content) { const moduleName = rawHdlModule.name; if (uncheckedModuleNames.has(moduleName)) { // match the same module, check then const originalModule = moduleFile.getHdlModule(moduleName); uncheckedModuleNames.delete(moduleName); originalModule?.update(rawHdlModule); } else { // no matched, create it const newModule = moduleFile.createHdlModule(rawHdlModule); newModule.makeNameToInstances(); newModule.solveUnhandleInstance(); } } // 3. delete module not visited yet for (const moduleName of uncheckedModuleNames) { moduleFile.deleteHdlModule(moduleName); } } }; const hdlParam = new HdlParam(); class HdlInstance { name: string; // name of the instance type: string; // type range: common.Range; // range of instance instModPath: AbsPath | undefined; // path of the definition instModPathStatus: common.InstModPathStatus; // status of the instance (current, include, others) instparams: common.InstRange; // range of params instports: common.InstRange; // range of ports parentMod: HdlModule; // HdlModule that the instance serves module: HdlModule | undefined; // module constructor(name: string, type: string, instModPath: string | undefined, instModPathStatus: common.InstModPathStatus, instparams: common.InstRange, instports: common.InstRange, range: common.Range, parentMod: HdlModule ) { this.name = name; this.type = type; this.parentMod = parentMod; this.instparams = instparams; this.instports = instports; this.instModPath = instModPath; this.instModPathStatus = instModPathStatus; this.range = range; this.module = undefined; // solve dependency this.locateHdlModule(); } public locateHdlModule() { const instModPath = this.instModPath; const instModName = this.type; if (instModPath) { this.module = hdlParam.getHdlModule(instModPath, instModName); // add refer for module this.module?.addGlobalReferedInstance(this); // if module and parent module share the same source (e.g both in src folder) if (this.isSameSource()) { this.module?.addLocalReferedInstance(this); } } } /** * judge if the instance is a cross source reference * e.g. this.module is from src, this.parentMod is from sim, then * isSameSource will return false, meaning that the instance is a cross source reference * * a cross source reference won't affect the top module reference of this.module, * meaning that a top module in one source can have its instance in other source */ public isSameSource(): boolean { const parentMod = this.parentMod; const instMod = this.module; if (instMod) { return parentMod.file.type === instMod.file.type; } return false; } /** * @description update Instance of each time * @param newInstance */ public update(newInstance: common.RawHdlInstance) { this.type = newInstance.type; this.range = newInstance.range; this.instparams = newInstance.instparams; this.instports = newInstance.instports; this.instModPath = this.parentMod.path; this.instModPathStatus = this.parentMod.solveInstModPathStatus(); } }; class HdlModule { file: HdlFile; name: string; range: common.Range; params: common.HdlModuleParam[]; ports: common.HdlModulePort[]; private rawInstances: common.RawHdlInstance[] | undefined; private nameToInstances: Map; private unhandleInstances: Set; private globalRefers: Set; private localRefers: Set; constructor(file: HdlFile, name: string, range: common.Range, params: common.HdlModuleParam[], ports: common.HdlModulePort[], instances: common.RawHdlInstance[]) { this.file = file; this.name = name; this.range = range; this.params = params ? params : []; this.ports = ports ? ports : []; this.rawInstances = instances; this.nameToInstances = new Map(); // add in hdlParam data structure // default both top module in top module and local top module (sim/src) hdlParam.addTopModule(this); hdlParam.addTopModuleToSource(this); hdlParam.addHdlModule(this); // log reference (its instance) // represents all the instance from this this.globalRefers = new Set(); // represents all the instance from this created in the same scope // scope: src or sim (lib belongs to src) // localRefers subset to refers this.localRefers = new Set(); // make unhandleInstances this.unhandleInstances = new Set(); } public get path() : string { return this.file.path; } public get languageId(): HdlLangID { return this.file.languageId; } public getInstance(name: string): HdlInstance | undefined { return this.nameToInstances.get(name); } public getAllInstances(): HdlInstance[] { const hdlInstances: HdlInstance[] = []; for (const inst of this.nameToInstances.values()) { hdlInstances.push(inst); } return hdlInstances; } public getInstanceNum(): number { return this.nameToInstances.size; } public createHdlInstance(rawHdlInstance: common.RawHdlInstance): HdlInstance { const instModName = rawHdlInstance.type; if (this.languageId === HdlLangID.Verilog || this.languageId === HdlLangID.SystemVerilog) { const searchResult = this.searchInstModPath(instModName); const hdlInstance = new HdlInstance(rawHdlInstance.name, rawHdlInstance.type, searchResult.path, searchResult.status, rawHdlInstance.instparams, rawHdlInstance.instports, rawHdlInstance.range, this); if (!searchResult.path) { hdlParam.addUnhandleInstance(hdlInstance); this.addUnhandleInstance(hdlInstance); } if (this.nameToInstances) { this.nameToInstances.set(rawHdlInstance.name, hdlInstance); } return hdlInstance; } else if (this.languageId === HdlLangID.Vhdl) { const hdlInstance = new HdlInstance(rawHdlInstance.name, rawHdlInstance.type, this.path, common.InstModPathStatus.Current, rawHdlInstance.instparams, rawHdlInstance.instports, rawHdlInstance.range, this); hdlInstance.module = this; if (this.nameToInstances) { this.nameToInstances.set(rawHdlInstance.name, hdlInstance); } return hdlInstance; } else { vscode.window.showErrorMessage(`Unknown Language :${this.languageId} exist in our core program`); const hdlInstance = new HdlInstance(rawHdlInstance.name, rawHdlInstance.type, this.path, common.InstModPathStatus.Unknown, rawHdlInstance.instparams, this.ports[0].range, rawHdlInstance.range, this); return hdlInstance; } } public makeNameToInstances() { if (this.rawInstances !== undefined) { this.nameToInstances.clear(); for (const inst of this.rawInstances) { this.createHdlInstance(inst); } // this.rawInstances = undefined; } else { MainOutput.report('call makeNameToInstances but this.rawInstances is undefined', ReportType.Warn); } } public deleteInstanceByName(name: string) { const inst = this.getInstance(name); this.deleteInstance(inst); } public deleteInstance(inst?: HdlInstance) { if (inst) { this.deleteUnhandleInstance(inst); hdlParam.deleteUnhandleInstance(inst); if (this.nameToInstances) { this.nameToInstances.delete(inst.name); } // delete reference from instance's instMod const instMod = inst.module; if (instMod) { instMod.deleteGlobalReferedInstance(inst); if (inst.isSameSource()) { instMod.deleteLocalReferedInstance(inst); } } } } /** * @description 计算出当前 instance 和父组件的 包含关系 * @param instModName instance 的名字 * @returns InstModPathSearchResult */ private searchInstModPath(instModName: string): common.InstModPathSearchResult { // search path of instance // priority: "current file" -> "included files" -> "other hdls in the project" // prepare for "other hdls in the project" const excludeFile = new Set([this.file]); // search all the modules in the current file for (const name of this.file.getAllModuleNames()) { if (instModName === name) { return {path : this.path, status: common.InstModPathStatus.Current}; } } // search included file for (const include of this.file.macro.includes) { const absIncludePath = hdlPath.rel2abs(this.path, include.path); const includeFile = hdlParam.getHdlFile(absIncludePath); if (includeFile) { excludeFile.add(includeFile); if (includeFile.hasHdlModule(instModName)) { return {path: includeFile.path, status: common.InstModPathStatus.Include}; } } } // search other files in the project for (const hdlFile of hdlParam.getAllHdlFiles()) { if (!excludeFile.has(hdlFile) && hdlFile.hasHdlModule(instModName)) { return {path: hdlFile.path, status: common.InstModPathStatus.Others}; } } return {path: '', status: common.InstModPathStatus.Unknown}; } public addUnhandleInstance(inst: HdlInstance) { this.unhandleInstances.add(inst); } public deleteUnhandleInstance(inst: HdlInstance) { this.unhandleInstances.delete(inst); } public getAllGlobalRefers(): Set { return this.globalRefers; } public getAllLocalRefers(): Set { return this.localRefers; } public addGlobalReferedInstance(inst: HdlInstance) { const globalRefers = this.globalRefers; globalRefers.add(inst); // it is refered in global scope, so delete this from top module if (globalRefers.size > 0) { hdlParam.deleteTopModule(this); } } public deleteGlobalReferedInstance(inst: HdlInstance) { const globalRefers = this.globalRefers; globalRefers.delete(inst); if (globalRefers.size === 0) { hdlParam.addTopModule(this); } } public addLocalReferedInstance(inst: HdlInstance) { const localRefers = this.localRefers; localRefers.add(inst); // it is refered in local scope, so delete this from top module if (localRefers.size > 0) { hdlParam.deleteTopModuleToSource(this); } } public deleteLocalReferedInstance(inst: HdlInstance) { const localRefers = this.localRefers; localRefers.delete(inst); if (localRefers.size === 0) { hdlParam.addTopModuleToSource(this); } } public solveInstModPathStatus(): common.InstModPathStatus { const inst = hdlParam.getUnhandleInstanceByType(this.name); if (!inst) { return common.InstModPathStatus.Unknown; } const userModule = inst.parentMod; if (userModule.path === this.path) { return common.InstModPathStatus.Current; } else { const userIncludePaths = userModule.file.macro.includes.map( include => hdlPath.rel2abs(userModule.path, include.path)); if (userIncludePaths.includes(this.path)) { return common.InstModPathStatus.Include; } else { return common.InstModPathStatus.Others; } } } public solveUnhandleInstance() { const inst = hdlParam.getUnhandleInstanceByType(this.name); if (inst) { const userModule = inst.parentMod; // match a inst with the same type name of the module // remove from unhandle list hdlParam.deleteUnhandleInstance(inst); userModule.deleteUnhandleInstance(inst); // assign instModPath inst.instModPath = this.path; // judge the type of instModPathStatus inst.instModPathStatus = this.solveInstModPathStatus(); // assign module in the instance inst.locateHdlModule(); } } public update(newModule: common.RawHdlModule) { this.ports = newModule.ports; this.params = newModule.params; this.range = newModule.range; // compare and make change to instance const uncheckedInstanceNames = new Set(); for (const inst of this.getAllInstances()) { uncheckedInstanceNames.add(inst.name); } for (const newInst of newModule.instances) { if (uncheckedInstanceNames.has(newInst.name)) { // match exist instance, compare and update const originalInstance = this.getInstance(newInst.name); originalInstance?.update(newInst); uncheckedInstanceNames.delete(newInst.name); } else { // unknown instance, create it this.createHdlInstance(newInst); } } // delete Instance that not visited for (const instName of uncheckedInstanceNames) { this.deleteInstanceByName(instName); } } }; class HdlFile { public path: string; public languageId: HdlLangID; public type: common.HdlFileType; public doFastType: DoFastFileType; public macro: common.Macro; private readonly nameToModule: Map; constructor(path: string, languageId: HdlLangID, macro: common.Macro, modules: common.RawHdlModule[], doFastType: DoFastFileType) { this.path = path; this.languageId = languageId; this.macro = macro; this.type = hdlFile.getHdlFileType(path); this.doFastType = doFastType; // add to global hdlParam hdlParam.setHdlFile(this); // make nameToModule this.nameToModule = new Map(); for (const rawHdlModule of modules) { this.createHdlModule(rawHdlModule); } } public createHdlModule(rawHdlModule: common.RawHdlModule): HdlModule { const module: HdlModule = new HdlModule(this, rawHdlModule.name, rawHdlModule.range, rawHdlModule.params, rawHdlModule.ports, rawHdlModule.instances); this.nameToModule.set(rawHdlModule.name, module); return module; } public hasHdlModule(name: string): boolean { return this.nameToModule.has(name); } public getHdlModule(name: string): HdlModule | undefined { return this.nameToModule.get(name); } public getAllModuleNames(): string[] { const names: string[] = []; for (const [name, _] of this.nameToModule) { names.push(name); } return names; } public getAllHdlModules(): HdlModule[] { const hdlModules: HdlModule[] = []; for (const hdlModule of this.nameToModule.values()) { hdlModules.push(hdlModule); } return hdlModules; } public deleteHdlModule(name: string) { const hdlModule = this.getHdlModule(name); if (hdlModule) { // delete child reference in the module which use this for (const childInst of hdlModule.getAllGlobalRefers()) { const userModule = childInst.parentMod; childInst.module = undefined; childInst.instModPath = undefined; childInst.instModPathStatus = common.InstModPathStatus.Unknown; hdlParam.addUnhandleInstance(childInst); userModule.addUnhandleInstance(childInst); } // delete all the instance in the module for (const inst of hdlModule.getAllInstances()) { hdlModule.deleteInstance(inst); } // delete any variables containing module hdlParam.deleteTopModule(hdlModule); hdlParam.deleteTopModuleToSource(hdlModule); hdlParam.modules.delete(hdlModule); this.nameToModule.delete(hdlModule.name); } } public makeInstance() { for (const module of this.getAllHdlModules()) { module.makeNameToInstances(); } } public updateMacro(macro: common.Macro) { this.macro = macro; } } export { hdlParam, HdlModule, HdlInstance, hdlFile };