import * as vscode from 'vscode'; import * as fs from 'fs'; import * as child_process from 'child_process'; import { hdlParam } from '../../hdlParser'; import { AbsPath, MainOutput, opeParam, ReportType } from '../../global'; import { hdlDir, hdlFile, hdlPath } from '../../hdlFs'; import { getSelectItem } from './instance'; import { ToolChainType } from '../../global/enum'; import { HdlModule } from '../../hdlParser/core'; interface SimulateConfig { mod : string, // 设置的顶层模块 clk : string, // 设置的主频信号 rst : string, // 设置的复位信号 end : string, // wave : string, // wave存放的路径 simulationHome : string, // sim运行的路径 gtkwavePath : string, // gtkwave安装路径 installPath : string // 第三方仿真工具的安装路径 iverilogPath: string vvpPath: string } class Simulate { regExp = { mod : /\/\/ @ sim.module : (?\w+)/, clk : /\/\/ @ sim.clk : (?\w+)/, rst : /\/\/ @ sim.rst : (?\w+)/, end : /#(?[0-9+])\s+\$(finish|stop)/, wave : /\$dumpfile\s*\(\s*\"(?.+)\"\s*\);/, }; xilinxLib = [ "xeclib", "unisims" ,"unimacro" ,"unifast" ,"retarget" ]; /** * @description 获取仿真的配置 * @param path 代码路径 * @param tool 仿真工具名 */ getConfig(path: AbsPath, tool: string): SimulateConfig | undefined { let simConfig: SimulateConfig = { mod : '', clk : '', // 设置的主频信号 rst : '', // 设置的复位信号 end : '', // wave : '', // wave存放的路径 simulationHome : '', // sim运行的路径 gtkwavePath : '', // gtkwave安装路径 installPath : '', // 第三方仿真工具的安装路径 iverilogPath: 'iverilog', // iverilog仿真器所在路径 vvpPath: 'vvp' // vvp解释器所在路径 }; let code = hdlFile.readFile(path); if (!code) { MainOutput.report('error when read ' + path, ReportType.Error, true); return; } for (const element in this.regExp) { const regGroup = code.match(this.regExp[element as keyof typeof this.regExp])?.groups; if (regGroup) { simConfig[element as keyof SimulateConfig] = regGroup[element]; } } const setting = vscode.workspace.getConfiguration(); // make simulation dir const defaultSimulationDir = hdlPath.join(opeParam.prjInfo.arch.prjPath, 'simulation', 'icarus'); simConfig.simulationHome = setting.get('function.simulate.simulationHome', ''); if (!fs.existsSync(simConfig.simulationHome)) { simConfig.simulationHome = defaultSimulationDir; } if (!hdlFile.isDir(simConfig.simulationHome)) { MainOutput.report('create dir ' + simConfig.simulationHome, ReportType.Info); hdlDir.mkdir(simConfig.simulationHome); } simConfig.gtkwavePath = setting.get('function.simulate.gtkwavePath', 'gtkwave'); if (simConfig.gtkwavePath !== '' && !hdlFile.isDir(simConfig.gtkwavePath)) { simConfig.gtkwavePath = 'gtkwave'; // 如果不存在则认为是加入了环境变量 } else { if (opeParam.os === 'win32') { simConfig.gtkwavePath = hdlPath.join(simConfig.gtkwavePath, 'gtkwave.exe'); } else { simConfig.gtkwavePath = hdlPath.join(simConfig.gtkwavePath, 'gtkwave'); } } simConfig.installPath = setting.get('function.simulate.icarus.installPath', ''); if (simConfig.installPath !== '' && !hdlFile.isDir(simConfig.installPath)) { MainOutput.report(`install path ${simConfig.installPath} is illegal`, ReportType.Error, true); return; } return simConfig; } /** * @description 获取自带仿真库的路径 * @param toolChain */ getSimLibArr(toolChain: ToolChainType): AbsPath[] { let libPath: AbsPath[] = []; const setting = vscode.workspace.getConfiguration(); // 获取xilinx的自带仿真库的路径 if (toolChain === ToolChainType.Xilinx) { const simLibPath = setting.get('function.simulate.xilinxLibPath', ''); if (!hdlFile.isDir(simLibPath)) { return []; } const glblPath = hdlPath.join(simLibPath, 'glbl.v'); libPath.push(glblPath); for (const element of this.xilinxLib) { const xilinxPath = hdlPath.join(simLibPath, element); libPath.push(xilinxPath); } } return libPath; } } /** * @description icarus 仿真类 * */ class IcarusSimulate extends Simulate { os: string; prjPath: AbsPath; toolChain: ToolChainType; simConfig: SimulateConfig | undefined; constructor() { super(); this.os = opeParam.os; this.prjPath = opeParam.prjInfo.arch.prjPath; this.toolChain = opeParam.prjInfo.toolChain; } /** * generate acutal iverlog simulation command * @param name name of top module * @param path path of the simulated file * @param dependences dependence that not specified in `include macro * @returns */ private getCommand(name: string, path: AbsPath, dependences: string[]): string | undefined { const simConfig = this.getConfig(path, 'iverilog'); if (!simConfig) { return; } this.simConfig = simConfig; const installPath = simConfig.installPath; if (this.os === 'win32') { simConfig.iverilogPath += '.exe'; simConfig.vvpPath += '.exe'; } if (hdlFile.isDir(installPath)) { simConfig.iverilogPath = hdlPath.join(installPath, simConfig.iverilogPath); simConfig.vvpPath = hdlPath.join(installPath, simConfig.vvpPath); } let dependenceArgs = ''; dependences.forEach(d => dependenceArgs += '"' + d + '" '); dependenceArgs = dependenceArgs.trim(); let thirdLibPath = ' '; this.getSimLibArr(this.toolChain).forEach(element => { if(!hdlFile.isDir(element)) { thirdLibPath += element + " "; } else { thirdLibPath += `-y ${element}`; } }); const iverilogPath = simConfig.iverilogPath; const argu = '-g2012'; const outVvpPath = '"' + hdlPath.join(simConfig.simulationHome, 'out.vvp') + '"'; const mainPath = '"' + path + '"'; const cmd = `${iverilogPath} ${argu} -o ${outVvpPath} -s ${name} ${mainPath} ${dependenceArgs} ${thirdLibPath}`; MainOutput.report(cmd, ReportType.Run); return cmd; } private execInTerminal(command: string, cwd: AbsPath) { // let vvp: vscode.Terminal; // const targetTerminals = vscode.window.terminals.filter(t => t.name === 'vvp'); // if (targetTerminals.length > 0) { // vvp = targetTerminals[0]; // } else { // vvp = vscode.window.createTerminal('vvp'); // } // let cmd = `${vvpPath} ${outVvpPath}`; // if (simConfig.wave !== '') { // let waveExtname = simConfig.wave.split('.'); // cmd += '-' + waveExtname[simConfig.wave.length - 1]; // } // vvp.show(true); // vvp.sendText(cmd); // if (simConfig.wave !== '') { // vvp.sendText(`${simConfig.gtkwavePath} ${simConfig.wave}`); // } else { // MainOutput.report('There is no wave image path in this testbench', ReportType.Error); // } } private execInOutput(command: string, cwd: AbsPath) { const simConfig = this.simConfig; if (!simConfig) { return; } child_process.exec(command, { cwd }, (error, stdout, stderr) => { if (error) { MainOutput.report('Error took place when run ' + command, ReportType.Error); MainOutput.report('Reason: ' + stderr, ReportType.Error); } else { MainOutput.report(stdout, ReportType.Info); const vvpOutFile = hdlPath.join(simConfig.simulationHome, 'out.vvp'); MainOutput.report("Create vvp to " + vvpOutFile, ReportType.Run); const outVvpPath = hdlPath.join(simConfig.simulationHome, 'out.vvp'); const vvpPath = simConfig.vvpPath; // run vvp to interrupt script const vvpCommand = `${vvpPath} ${outVvpPath}`; MainOutput.report(vvpCommand, ReportType.Run); child_process.exec(vvpCommand, { cwd }, (error, stdout, stderr) => { if (error) { MainOutput.report('Error took place when run ' + vvpCommand, ReportType.Error); MainOutput.report('Reason: ' + stderr, ReportType.Error); } else { MainOutput.report(stdout, ReportType.Info); } }); } }); } private exec(command: string, cwd: AbsPath) { const simConfig = this.simConfig; if (!simConfig) { MainOutput.report('this.simConfig is empty when exec'); return; } const runInTerminal = vscode.workspace.getConfiguration().get('function.simulate.runInTerminal'); if (runInTerminal) { this.execInTerminal(command, cwd); } else { this.execInOutput(command, cwd); } } private getAllOtherDependences(path: AbsPath, name: string): AbsPath[] { const deps = hdlParam.getAllDependences(path, name); if (deps) { return deps.others; } else { MainOutput.report('Fail to get dependences of path: ' + path + ' name: ' + name, ReportType.Warn); return []; } } private simulateByHdlModule(hdlModule: HdlModule) { const name = hdlModule.name; const path = hdlModule.path; if (!hdlParam.isTopModule(path, name, false)) { const warningMsg = name + ' in ' + path + ' is not top module'; MainOutput.report(warningMsg, ReportType.Warn, true); return; } const dependences = this.getAllOtherDependences(path, name); const simulationCommand = this.getCommand(name, path, dependences); if (simulationCommand) { const cwd = hdlPath.resolve(hdlModule.path, '..'); this.exec(simulationCommand, cwd); } else { const errorMsg = 'Fail to generate command'; MainOutput.report(errorMsg, ReportType.Error, true); return; } } public async simulateModule(hdlModule: HdlModule) { this.simulateByHdlModule(hdlModule); } public async simulateFile() { const editor = vscode.window.activeTextEditor; if (!editor) { return; } const uri = editor.document.uri; const path = hdlPath.toSlash(uri.fsPath); const currentFile = hdlParam.getHdlFile(path); if (!currentFile) { MainOutput.report('path ' + path + ' is not a hdlFile', ReportType.Error, true); return; } const items = getSelectItem(currentFile.getAllHdlModules()); if (items.length) { let selectModule: HdlModule; if (items.length === 1) { selectModule = items[0].module; } else { const select = await vscode.window.showQuickPick(items, {placeHolder: 'choose a top module'}); if (select) { selectModule = select.module; } else { return; } } this.simulateByHdlModule(selectModule); } } } const icarus = new IcarusSimulate(); namespace Icarus { export async function simulateModule(hdlModule: HdlModule) { await icarus.simulateModule(hdlModule); } export async function simulateFile() { await icarus.simulateFile(); } }; export { Icarus };