feat:接入iverilog工具

- 将iverilog可以随着插件的下载而下载
- 用户输入自然语言就可以控制生成对应的VCD文件
This commit is contained in:
Roe-xin
2025-12-15 11:09:03 +08:00
parent 94225a3525
commit 22b9a0ed13
71 changed files with 2020 additions and 87 deletions

View File

@ -41,7 +41,7 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
(message) => {
switch (message.command) {
case "sendMessage":
handleUserMessage(panel, message.text);
handleUserMessage(panel, message.text, context.extensionPath);
break;
case "readFile":
handleReadFile(panel, message.filePath);

400
src/utils/iverilogRunner.ts Normal file
View File

@ -0,0 +1,400 @@
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 || ""}`,
};
}
}

View File

@ -8,16 +8,28 @@ import {
renameFile,
replaceFile,
} from "./createFiles";
import {
generateVCD,
checkVerilogProject,
checkIverilogAvailable,
} from "./iverilogRunner";
/**
* 处理用户消息
*/
export async function handleUserMessage(
panel: vscode.WebviewPanel,
text: string
text: string,
extensionPath?: string
) {
console.log("收到用户消息:", text);
// 检查是否是 VCD 生成命令
if (isVCDGenerationCommand(text)) {
await handleVCDGeneration(panel, extensionPath || "");
return;
}
// 检查是否是文件操作命令
const fileOperation = parseFileOperation(text);
@ -466,3 +478,161 @@ export function insertCodeToEditor(code: string) {
vscode.window.showWarningMessage("请先打开一个编辑器");
}
}
/**
* 检查是否是 VCD 生成命令
*/
function isVCDGenerationCommand(text: string): boolean {
const lowerText = text.toLowerCase().trim();
// 匹配各种 VCD 生成命令
const vcdPatterns = [
/生成\s*vcd/,
/创建\s*vcd/,
/运行\s*仿真/,
/执行\s*仿真/,
/iverilog/,
/生成\s*波形/,
/仿真\s*生成/,
];
return vcdPatterns.some((pattern) => pattern.test(lowerText));
}
/**
* 处理 VCD 生成请求
*/
async function handleVCDGeneration(
panel: vscode.WebviewPanel,
extensionPath: string
) {
try {
// 获取当前工作区路径
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
panel.webview.postMessage({
command: "receiveMessage",
text: "❌ 请先打开一个工作区文件夹",
});
vscode.window.showErrorMessage("请先打开一个工作区文件夹");
return;
}
const projectPath = workspaceFolders[0].uri.fsPath;
// 发送开始消息
panel.webview.postMessage({
command: "receiveMessage",
text: "🔍 正在检查项目文件...",
});
// 1. 检查 iverilog 是否可用
const iverilogCheck = await checkIverilogAvailable(extensionPath);
if (!iverilogCheck.available) {
panel.webview.postMessage({
command: "receiveMessage",
text: `${iverilogCheck.message}\n\n请参考插件文档安装 iverilog 工具。`,
});
vscode.window.showErrorMessage(iverilogCheck.message);
return;
}
// 2. 检查项目文件完整性
const projectCheck = await checkVerilogProject(projectPath);
if (!projectCheck.isComplete) {
let errorMsg = "❌ 项目文件不完整:\n\n";
if (projectCheck.allVerilogFiles.length === 0) {
errorMsg += "• 未找到任何 Verilog 文件 (.v 或 .sv)\n";
} else {
errorMsg += `• 找到 ${projectCheck.allVerilogFiles.length} 个 Verilog 文件\n`;
if (!projectCheck.hasTopModule) {
errorMsg += "• ❌ 缺少顶层模块文件\n";
} else {
errorMsg += `• ✅ 顶层模块: ${projectCheck.topModuleFile}\n`;
}
if (!projectCheck.hasTestbench) {
errorMsg += "• ❌ 缺少 testbench 文件\n";
errorMsg += "\n提示: testbench 文件应包含 $dumpfile 和 $dumpvars 语句来生成 VCD 文件。\n";
} else {
errorMsg += `• ✅ Testbench: ${projectCheck.testbenchFile}\n`;
}
}
if (projectCheck.errors.length > 0) {
errorMsg += "\n错误信息:\n" + projectCheck.errors.join("\n");
}
panel.webview.postMessage({
command: "receiveMessage",
text: errorMsg,
});
vscode.window.showWarningMessage("项目文件不完整,无法生成 VCD");
return;
}
// 3. 显示项目检查结果
panel.webview.postMessage({
command: "receiveMessage",
text: `✅ 项目检查通过!\n\n找到 ${projectCheck.allVerilogFiles.length} 个 Verilog 文件\n• 顶层模块: ${projectCheck.topModuleFile}\n• Testbench: ${projectCheck.testbenchFile}\n\n🚀 开始编译和仿真...`,
});
// 4. 生成 VCD 文件
const result = await generateVCD(projectPath, extensionPath);
if (result.success) {
let successMsg = `${result.message}`;
if (result.stdout) {
successMsg += `\n\n仿真输出:\n${result.stdout}`;
}
panel.webview.postMessage({
command: "receiveMessage",
text: successMsg,
});
vscode.window.showInformationMessage(
`VCD 文件生成成功: ${result.vcdFilePath}`,
"打开文件"
).then((selection) => {
if (selection === "打开文件" && result.vcdFilePath) {
vscode.workspace.openTextDocument(result.vcdFilePath).then((doc) => {
vscode.window.showTextDocument(doc);
});
}
});
} else {
let errorMsg = `${result.message}`;
if (result.stderr) {
errorMsg += `\n\n错误输出:\n${result.stderr}`;
}
if (result.stdout) {
errorMsg += `\n\n标准输出:\n${result.stdout}`;
}
panel.webview.postMessage({
command: "receiveMessage",
text: errorMsg,
});
vscode.window.showErrorMessage("VCD 文件生成失败");
}
} catch (error) {
const errorMsg = `❌ 生成 VCD 文件时出错: ${
error instanceof Error ? error.message : "未知错误"
}`;
panel.webview.postMessage({
command: "receiveMessage",
text: errorMsg,
});
vscode.window.showErrorMessage(errorMsg);
}
}