import * as vscode from "vscode"; import * as path from "path"; import { spawn } from "child_process"; import { promisify } from "util"; // 使用 spawn 来执行命令,正确处理路径空格 function execCommand( command: string, args: string[], options: { cwd: string; env?: any }, ): Promise<{ stdout: string; stderr: string }> { return new Promise((resolve, reject) => { // 在 Windows 上,如果路径包含空格,不使用 shell,直接用 spawn // spawn 在不使用 shell 时会正确处理带空格的路径 const child = spawn(command, args, { cwd: options.cwd, env: options.env || process.env, windowsHide: true, shell: false, // 不使用 shell,让 spawn 直接执行 }); let stdout = ""; let stderr = ""; // 在 Windows 上使用 GBK 编码解码输出 const encoding = process.platform === "win32" ? "gbk" : "utf8"; child.stdout.on("data", (data) => { try { // 尝试使用 iconv-lite 解码(如果可用) const iconv = require("iconv-lite"); stdout += iconv.decode(data, encoding); } catch { // 如果 iconv-lite 不可用,使用默认解码 stdout += data.toString("utf8"); } }); child.stderr.on("data", (data) => { try { const iconv = require("iconv-lite"); stderr += iconv.decode(data, encoding); } catch { stderr += data.toString("utf8"); } }); child.on("close", (code) => { if (code === 0) { resolve({ stdout, stderr }); } else { const error: any = new Error(`Command failed with exit code ${code}`); error.stdout = stdout; error.stderr = stderr; error.code = code; reject(error); } }); child.on("error", (error) => { reject(error); }); }); } /** * Verilog 项目文件检查结果 */ export interface VerilogProjectCheck { isComplete: boolean; hasTopModule: boolean; hasTestbench: boolean; topModuleFile?: string; testbenchFile?: string; allVerilogFiles: string[]; missingFiles: string[]; errors: string[]; } /** * VCD 生成结果 */ export interface VCDGenerationResult { success: boolean; vcdFilePath?: string; outputPath?: string; message: string; stdout?: string; stderr?: string; } /** * 检查项目中的 Verilog 文件完整性 */ export async function checkVerilogProject( projectPath: string, ): Promise { const result: VerilogProjectCheck = { isComplete: false, hasTopModule: false, hasTestbench: false, allVerilogFiles: [], missingFiles: [], errors: [], }; try { // 检查项目路径是否存在 const projectUri = vscode.Uri.file(projectPath); try { await vscode.workspace.fs.stat(projectUri); } catch (error) { result.errors.push(`项目路径不存在: ${projectPath}`); return result; } // 查找所有 Verilog 文件 (.v, .sv) const verilogFiles = await findVerilogFiles(projectPath); result.allVerilogFiles = verilogFiles; if (verilogFiles.length === 0) { result.errors.push("项目中没有找到 Verilog 文件 (.v 或 .sv)"); return result; } // 分析文件内容,查找 top module 和 testbench for (const file of verilogFiles) { const fileUri = vscode.Uri.file(file); const contentBytes = await vscode.workspace.fs.readFile(fileUri); const content = Buffer.from(contentBytes).toString("utf-8"); const fileName = path.basename(file).toLowerCase(); // 检查是否是 testbench 文件 if ( fileName.includes("tb") || fileName.includes("test") || content.includes("$dumpfile") || content.includes("$dumpvars") ) { result.hasTestbench = true; result.testbenchFile = file; } // 检查是否包含 module 定义(可能是 top module) if (content.match(/module\s+\w+/)) { if (!result.hasTopModule && !fileName.includes("tb")) { result.hasTopModule = true; result.topModuleFile = file; } } } // 检查完整性 if (!result.hasTopModule) { result.missingFiles.push("缺少顶层模块文件"); } if (!result.hasTestbench) { result.missingFiles.push("缺少 testbench 文件"); } result.isComplete = result.hasTopModule && result.hasTestbench; return result; } catch (error) { result.errors.push( `检查项目时出错: ${error instanceof Error ? error.message : "未知错误"}`, ); return result; } } /** * 递归查找目录下所有 Verilog 文件 */ async function findVerilogFiles(dir: string): Promise { const verilogFiles: string[] = []; async function searchDir(currentDir: string) { const dirUri = vscode.Uri.file(currentDir); const entries = await vscode.workspace.fs.readDirectory(dirUri); for (const [fileName, fileType] of entries) { const filePath = path.join(currentDir, fileName); if (fileType === vscode.FileType.Directory) { // 跳过 node_modules 等目录 if (!fileName.startsWith(".") && fileName !== "node_modules") { await searchDir(filePath); } } else if (fileType === vscode.FileType.File) { const ext = path.extname(fileName).toLowerCase(); if (ext === ".v" || ext === ".sv") { verilogFiles.push(filePath); } } } } await searchDir(dir); return verilogFiles; } /** * 获取 iverilog 可执行文件路径 */ async function getIverilogPath(extensionPath: string): Promise { const platform = process.platform; let iverilogBin = ""; if (platform === "win32") { iverilogBin = path.join( extensionPath, "tools", "iverilog", "bin", "iverilog.exe", ); } else if (platform === "darwin") { iverilogBin = path.join( extensionPath, "tools", "iverilog", "bin", "iverilog", ); } else { // Linux iverilogBin = path.join( extensionPath, "tools", "iverilog", "bin", "iverilog", ); } // 如果插件包中没有,尝试使用系统安装的 iverilog const iverilogUri = vscode.Uri.file(iverilogBin); try { await vscode.workspace.fs.stat(iverilogUri); return iverilogBin; } catch { return "iverilog"; // 使用系统 PATH 中的 iverilog } } /** * 获取 vvp 可执行文件路径 */ async function getVvpPath(extensionPath: string): Promise { const platform = process.platform; let vvpBin = ""; if (platform === "win32") { vvpBin = path.join(extensionPath, "tools", "iverilog", "bin", "vvp.exe"); } else if (platform === "darwin") { vvpBin = path.join(extensionPath, "tools", "iverilog", "bin", "vvp"); } else { // Linux vvpBin = path.join(extensionPath, "tools", "iverilog", "bin", "vvp"); } // 如果插件包中没有,尝试使用系统安装的 vvp const vvpUri = vscode.Uri.file(vvpBin); try { await vscode.workspace.fs.stat(vvpUri); return vvpBin; } catch { return "vvp"; // 使用系统 PATH 中的 vvp } } /** * 运行 iverilog 生成 VCD 文件 */ export async function generateVCD( projectPath: string, extensionPath: string, ): Promise { try { // 1. 检查项目完整性 const projectCheck = await checkVerilogProject(projectPath); if (!projectCheck.isComplete) { return { success: false, message: `项目文件不完整:\n${projectCheck.missingFiles.join("\n")}\n${projectCheck.errors.join("\n")}`, }; } // 2. 获取 iverilog 和 vvp 路径 const iverilogPath = await getIverilogPath(extensionPath); const vvpPath = await getVvpPath(extensionPath); // 3. 准备输出路径 const outputDir = projectPath; const outputFile = path.join(outputDir, "simulation.vvp"); // 4. 设置环境变量,确保 iverilog 能找到库文件 const libPath = path.join(extensionPath, "tools", "iverilog", "lib"); const env = { ...process.env, IVERILOG_ROOT: path.join(extensionPath, "tools", "iverilog"), }; // 5. 构建 iverilog 编译参数 const compileArgs = ["-o", outputFile, ...projectCheck.allVerilogFiles]; console.log("执行编译命令:", iverilogPath, compileArgs.join(" ")); console.log("IVERILOG_ROOT:", env.IVERILOG_ROOT); // 6. 执行编译 let compileResult; try { compileResult = await execCommand(iverilogPath, compileArgs, { cwd: projectPath, env: env, }); } catch (error: any) { return { success: false, message: `IC Coder编译器编译失败:\n${error.message}`, stderr: error.stderr, stdout: error.stdout, }; } // 6.5. 删除 shebang 行(修复 Windows 上的 vvp 解析错误) try { const fs = require("fs"); const vvpContent = fs.readFileSync(outputFile, "utf8"); const lines = vvpContent.split("\n"); if (lines.length > 0 && lines[0].startsWith("#!")) { const cleanedContent = lines.slice(1).join("\n"); fs.writeFileSync(outputFile, cleanedContent, "utf8"); console.log("已删除 .vvp 文件的 shebang 行"); } } catch (error) { console.warn("删除 shebang 失败,继续执行:", error); } // 7. 执行仿真生成 VCD const simArgs = [outputFile]; console.log("执行仿真命令:", vvpPath, simArgs.join(" ")); let simResult; try { simResult = await execCommand(vvpPath, simArgs, { cwd: projectPath, env: env, }); } catch (error: any) { return { success: false, message: `VVP 仿真失败:\n${error.message}`, stderr: error.stderr, stdout: error.stdout, }; } // 8. 查找生成的 VCD 文件 const projectUri = vscode.Uri.file(projectPath); const entries = await vscode.workspace.fs.readDirectory(projectUri); const vcdFiles = entries .filter( ([fileName, fileType]) => fileType === vscode.FileType.File && fileName.endsWith(".vcd"), ) .map(([fileName]) => fileName); if (vcdFiles.length === 0) { return { success: false, message: "VCD 文件未生成。请确保 testbench 中包含 $dumpfile 和 $dumpvars 语句。", stdout: simResult.stdout, }; } // 使用找到的第一个 VCD 文件 const vcdFile = path.join(projectPath, vcdFiles[0]); // 9. 清理中间文件 try { const outputUri = vscode.Uri.file(outputFile); await vscode.workspace.fs.stat(outputUri); await vscode.workspace.fs.delete(outputUri); } catch (error) { // 忽略清理错误 } return { success: true, vcdFilePath: vcdFile, outputPath: projectPath, message: `VCD 文件生成成功!\n路径: ${vcdFile}`, stdout: simResult.stdout, }; } catch (error) { return { success: false, message: `生成 VCD 文件时出错: ${error instanceof Error ? error.message : "未知错误"}`, }; } } /** * 检查 iverilog 是否可用 */ export async function checkIverilogAvailable( extensionPath: string, ): Promise<{ available: boolean; version?: string; message: string }> { try { const iverilogPath = await getIverilogPath(extensionPath); // 检查文件是否存在 const iverilogUri = vscode.Uri.file(iverilogPath); try { await vscode.workspace.fs.stat(iverilogUri); } catch (error) { return { available: false, message: `IC Coder编译器不可用。未找到文件: ${iverilogPath}`, }; } // 设置环境变量 const env = { ...process.env, IVERILOG_ROOT: path.join(extensionPath, "tools", "iverilog"), }; const result = await execCommand(iverilogPath, ["-V"], { cwd: path.dirname(iverilogPath), env: env, }); const version = result.stdout.trim().split("\n")[0]; return { available: true, version: version, message: `IC Coder编译器可用: ${version}`, }; } catch (error: any) { return { available: false, message: `IC Coder编译器执行失败: ${error.message}\n${error.stderr || ""}`, }; } } /** * 要 dump 的模块定义 */ export interface DumpModule { name: string; // 模块名(用于 VCD 文件名和宏名) path: string; // 实例路径(如 dut.u_tx) } /** * 多 VCD 生成结果 */ export interface MultiVCDResult { success: boolean; vcdFiles: Array<{ moduleName: string; vcdPath: string; success: boolean; error?: string; }>; message: string; stdout?: string; } /** * 在 testbench 中注入条件编译代码 * 将原有的 $dumpfile/$dumpvars 替换为条件编译版本 */ function injectConditionalDump( tbContent: string, dumpModules: DumpModule[], vcdDir: string, ): string { // 匹配 $dumpfile 和 $dumpvars 语句(可能跨多行) const dumpPattern = /(\$dumpfile\s*\([^)]+\)\s*;[\s\S]*?\$dumpvars\s*\([^)]+\)\s*;)/g; // 生成条件编译代码 const conditionalCode = generateConditionalDumpCode(dumpModules, vcdDir); // 替换原有的 dump 语句 const modified = tbContent.replace(dumpPattern, conditionalCode); // 如果没有找到匹配,尝试单独匹配 $dumpfile if (modified === tbContent) { const singleDumpPattern = /\$dumpfile\s*\([^)]+\)\s*;/g; return tbContent.replace(singleDumpPattern, conditionalCode); } return modified; } /** * 生成条件编译的 dump 代码 */ function generateConditionalDumpCode( dumpModules: DumpModule[], vcdDir: string, ): string { if (dumpModules.length === 0) { return '$dumpfile("output.vcd");\n $dumpvars(0, dut);'; } const lines: string[] = []; dumpModules.forEach((module, index) => { const macroName = `DUMP_${module.name.toUpperCase()}`; const vcdPath = `${vcdDir}/${module.name}.vcd`; const directive = index === 0 ? "`ifdef" : "`elsif"; lines.push(`${directive} ${macroName}`); lines.push(` $dumpfile("${vcdPath}");`); lines.push(` $dumpvars(1, ${module.path});`); }); // 添加默认分支(使用第一个模块) lines.push("`else"); lines.push(` $dumpfile("${vcdDir}/${dumpModules[0].name}.vcd");`); lines.push(` $dumpvars(1, ${dumpModules[0].path});`); lines.push("`endif"); return lines.join("\n"); } /** * 生成多个 VCD 文件(为不同子模块) */ export async function generateMultiVCD( projectPath: string, extensionPath: string, tbPath: string, dumpModules: DumpModule[], vcdDir: string = "vcd", ): Promise { const results: MultiVCDResult["vcdFiles"] = []; let allStdout = ""; try { // 1. 创建 vcd 目录 const vcdDirPath = path.join(projectPath, vcdDir); const vcdDirUri = vscode.Uri.file(vcdDirPath); try { await vscode.workspace.fs.createDirectory(vcdDirUri); } catch { // 目录可能已存在 } // 2. 读取原始 testbench const tbFullPath = path.isAbsolute(tbPath) ? tbPath : path.join(projectPath, tbPath); const tbUri = vscode.Uri.file(tbFullPath); const tbBytes = await vscode.workspace.fs.readFile(tbUri); const originalTb = Buffer.from(tbBytes).toString("utf-8"); // 3. 注入条件编译代码 const modifiedTb = injectConditionalDump(originalTb, dumpModules, vcdDir); await vscode.workspace.fs.writeFile( tbUri, Buffer.from(modifiedTb, "utf-8"), ); console.log("[generateMultiVCD] Testbench 已修改,开始多次仿真..."); // 4. 获取工具路径 const iverilogPath = await getIverilogPath(extensionPath); const vvpPath = await getVvpPath(extensionPath); const env = { ...process.env, IVERILOG_ROOT: path.join(extensionPath, "tools", "iverilog"), }; // 5. 获取所有 Verilog 文件 const projectCheck = await checkVerilogProject(projectPath); const outputFile = path.join(projectPath, "simulation.vvp"); // 6. 循环执行仿真 for (const module of dumpModules) { const macroName = `DUMP_${module.name.toUpperCase()}`; const vcdPath = path.join(vcdDirPath, `${module.name}.vcd`); console.log(`[generateMultiVCD] 仿真模块: ${module.name} (${macroName})`); try { // 编译(带宏定义) const compileArgs = [ `-D${macroName}`, "-o", outputFile, ...projectCheck.allVerilogFiles, ]; await execCommand(iverilogPath, compileArgs, { cwd: projectPath, env }); // 仿真 const simResult = await execCommand(vvpPath, [outputFile], { cwd: projectPath, env, }); allStdout += `\n[${module.name}] ${simResult.stdout}`; results.push({ moduleName: module.name, vcdPath: vcdPath, success: true, }); } catch (error: any) { console.error( `[generateMultiVCD] 模块 ${module.name} 仿真失败:`, error.message, ); results.push({ moduleName: module.name, vcdPath: vcdPath, success: false, error: error.message, }); // 继续执行其他模块 } } // 7. 清理中间文件 try { await vscode.workspace.fs.delete(vscode.Uri.file(outputFile)); } catch { // 忽略 } const successCount = results.filter((r) => r.success).length; return { success: successCount > 0, vcdFiles: results, message: `生成完成:${successCount}/${dumpModules.length} 个 VCD 文件成功`, stdout: allStdout, }; } catch (error) { return { success: false, vcdFiles: results, message: `生成多 VCD 文件失败: ${error instanceof Error ? error.message : "未知错误"}`, }; } }