import * as vscode from "vscode"; import * as fs from "fs"; 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 { // 检查项目路径是否存在 if (!fs.existsSync(projectPath)) { result.errors.push(`项目路径不存在: ${projectPath}`); return result; } // 查找所有 Verilog 文件 (.v, .sv) const verilogFiles = 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 content = fs.readFileSync(file, "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 文件 */ function findVerilogFiles(dir: string): string[] { const verilogFiles: string[] = []; function searchDir(currentDir: string) { const files = fs.readdirSync(currentDir); for (const file of files) { const filePath = path.join(currentDir, file); const stat = fs.statSync(filePath); if (stat.isDirectory()) { // 跳过 node_modules 等目录 if (!file.startsWith(".") && file !== "node_modules") { searchDir(filePath); } } else if (stat.isFile()) { const ext = path.extname(file).toLowerCase(); if (ext === ".v" || ext === ".sv") { verilogFiles.push(filePath); } } } } searchDir(dir); return verilogFiles; } /** * 获取 iverilog 可执行文件路径 */ function getIverilogPath(extensionPath: string): string { 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 if (!fs.existsSync(iverilogBin)) { return "iverilog"; // 使用系统 PATH 中的 iverilog } return iverilogBin; } /** * 获取 vvp 可执行文件路径 */ function getVvpPath(extensionPath: string): string { 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 if (!fs.existsSync(vvpBin)) { return "vvp"; // 使用系统 PATH 中的 vvp } return vvpBin; } /** * 运行 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 = getIverilogPath(extensionPath); const vvpPath = 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: `iverilog 编译失败:\n${error.message}`, stderr: error.stderr, stdout: error.stdout, }; } // 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 vcdFiles = fs.readdirSync(projectPath).filter(file => file.endsWith('.vcd')); 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 { if (fs.existsSync(outputFile)) { fs.unlinkSync(outputFile); } } 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 = getIverilogPath(extensionPath); // 检查文件是否存在 if (!fs.existsSync(iverilogPath)) { return { available: false, message: `iverilog 不可用。未找到文件: ${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: `iverilog 可用: ${version}`, }; } catch (error: any) { return { available: false, message: `iverilog 执行失败: ${error.message}\n${error.stderr || ""}`, }; } }