Files
IC-Coder-Plugin/src/utils/iverilogRunner.ts
Roe-xin 7cde4fa138 refactor: 优化代码格式和用户提示
- 统一代码格式化(Prettier)
- 将 iverilog 相关错误提示改为 'IC Coder编译器'
- 优化后端服务错误提示为 '当前访问人数过多,请稍后重试'
- 修复代码风格一致性问题
2026-03-09 11:10:56 +08:00

655 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<VerilogProjectCheck> {
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<string[]> {
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<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
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<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
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<VCDGenerationResult> {
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<MultiVCDResult> {
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 : "未知错误"}`,
};
}
}