feat: 支持多VCD文件生成功能
- iverilogRunner新增generateMultiVCD函数 - toolExecutor处理dumpModules参数 - api.ts扩展SimulationArgs接口 - messageArea支持多波形预览
This commit is contained in:
@ -413,3 +413,193 @@ export async function checkIverilogAvailable(
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 要 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 : '未知错误'}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user