Files
IC-Coder-Plugin/src/utils/iverilogRunner.ts
Roe-xin 22b9a0ed13 feat:接入iverilog工具
- 将iverilog可以随着插件的下载而下载
- 用户输入自然语言就可以控制生成对应的VCD文件
2025-12-15 11:09:03 +08:00

401 lines
10 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 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<VerilogProjectCheck> {
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<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 = 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 || ""}`,
};
}
}