401 lines
10 KiB
TypeScript
401 lines
10 KiB
TypeScript
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 || ""}`,
|
||
};
|
||
}
|
||
}
|