feat: 支持多VCD文件生成功能

- iverilogRunner新增generateMultiVCD函数
- toolExecutor处理dumpModules参数
- api.ts扩展SimulationArgs接口
- messageArea支持多波形预览
This commit is contained in:
XiaoFeng
2026-01-10 16:45:39 +08:00
parent 5c19be22d3
commit 15a1de3a90
4 changed files with 299 additions and 26 deletions

View File

@ -8,7 +8,7 @@ import * as os from 'os';
import * as fs from 'fs'; import * as fs from 'fs';
import { readFileContent, readDirectory } from '../utils/readFiles'; import { readFileContent, readDirectory } from '../utils/readFiles';
import { createOrOverwriteFile } from '../utils/createFiles'; import { createOrOverwriteFile } from '../utils/createFiles';
import { generateVCD, checkIverilogAvailable } from '../utils/iverilogRunner'; import { generateVCD, checkIverilogAvailable, generateMultiVCD, DumpModule } from '../utils/iverilogRunner';
import { analyzeVcdFile } from '../utils/vcdParser'; import { analyzeVcdFile } from '../utils/vcdParser';
import { executeWaveformTrace, WaveformTraceArgs } from '../utils/waveformTracer'; import { executeWaveformTrace, WaveformTraceArgs } from '../utils/waveformTracer';
import { import {
@ -285,7 +285,30 @@ async function executeSimulation(
const projectPath = workspaceFolders[0].uri.fsPath; const projectPath = workspaceFolders[0].uri.fsPath;
// 调用现有的 generateVCD 函数 // 检查是否有 dumpModules 参数(多 VCD 模式)
if (args.dumpModules) {
const modules = parseDumpModules(args.dumpModules);
const vcdDir = args.vcdDir || 'vcd';
const result = await generateMultiVCD(
projectPath,
context.extensionPath,
args.tbPath,
modules,
vcdDir
);
if (result.success) {
const vcdList = result.vcdFiles
.map(f => `- ${f.moduleName}: ${f.success ? f.vcdPath : '失败 - ' + f.error}`)
.join('\n');
return `${result.message}\n\nVCD 文件列表:\n${vcdList}${result.stdout ? '\n\n仿真输出:' + result.stdout : ''}`;
} else {
throw new Error(result.message);
}
}
// 原有单 VCD 逻辑
const result = await generateVCD(projectPath, context.extensionPath); const result = await generateVCD(projectPath, context.extensionPath);
if (result.success) { if (result.success) {
@ -303,6 +326,17 @@ async function executeSimulation(
} }
} }
/**
* 解析 dumpModules 参数
* 格式name:path,name:path
*/
function parseDumpModules(dumpModules: string): DumpModule[] {
return dumpModules.split(',').map(item => {
const [name, modulePath] = item.trim().split(':');
return { name: name.trim(), path: modulePath.trim() };
});
}
/** /**
* 执行 waveform_summary 工具 * 执行 waveform_summary 工具
* 解析 VCD 文件并返回波形摘要 * 解析 VCD 文件并返回波形摘要

View File

@ -433,6 +433,10 @@ export interface SimulationArgs {
rtlPath: string; rtlPath: string;
tbPath: string; tbPath: string;
duration?: string; duration?: string;
/** 要dump的模块列表格式name:path,name:path */
dumpModules?: string;
/** VCD输出目录默认'vcd' */
vcdDir?: string;
} }
/** waveform_summary 工具参数 */ /** waveform_summary 工具参数 */

View File

@ -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 : '未知错误'}`
};
}
}

View File

@ -675,6 +675,29 @@ export function getMessageAreaScript(): string {
${getPlanCardScript()} ${getPlanCardScript()}
// 解析多 VCD 文件路径
function parseMultiVcdPaths(toolResult) {
if (!toolResult) return [];
const result = String(toolResult);
// 匹配 "- moduleName: path" 格式
const vcdListMatch = result.match(/VCD 文件列表:[\\s\\S]*?(?=\\n\\n|$)/);
if (!vcdListMatch) return [];
const paths = [];
const lineRegex = /- (\\w+): ([^\\n]+)/g;
let match;
while ((match = lineRegex.exec(vcdListMatch[0])) !== null) {
const name = match[1];
const pathOrError = match[2].trim();
// 跳过失败的条目
if (!pathOrError.startsWith('失败')) {
paths.push({ name: name + '.vcd', path: pathOrError });
}
}
return paths;
}
// 获取工具图标 // 获取工具图标
function getToolIcon(toolName) { function getToolIcon(toolName) {
const iconMap = { const iconMap = {
@ -1034,7 +1057,17 @@ export function getMessageAreaScript(): string {
// 如果是仿真工具且成功完成,尝试添加波形预览 // 如果是仿真工具且成功完成,尝试添加波形预览
if (segment.toolName === 'simulation' && segment.toolStatus === 'success') { if (segment.toolName === 'simulation' && segment.toolStatus === 'success') {
// 优先使用显式提供的路径,否则从结果文本中解析 // 尝试解析多个 VCD 文件(多 VCD 模式)
const vcdPaths = parseMultiVcdPaths(segment.toolResult);
if (vcdPaths.length > 0) {
// 多 VCD 模式:为每个文件创建预览
vcdPaths.forEach(vcdInfo => {
const waveformPreview = createWaveformPreview(vcdInfo.path, vcdInfo.name);
segmentDiv.appendChild(waveformPreview);
});
} else {
// 单 VCD 模式(兼容旧逻辑)
let vcdPath = segment.vcdFilePath; let vcdPath = segment.vcdFilePath;
if (!vcdPath && segment.toolResult) { if (!vcdPath && segment.toolResult) {
const match = String(segment.toolResult).match(/路径\s*:\s*(.+)/); const match = String(segment.toolResult).match(/路径\s*:\s*(.+)/);
@ -1049,6 +1082,7 @@ export function getMessageAreaScript(): string {
segmentDiv.appendChild(waveformPreview); segmentDiv.appendChild(waveformPreview);
} }
} }
}
// 添加折叠/展开事件监听 // 添加折叠/展开事件监听
if (shouldCollapse) { if (shouldCollapse) {
@ -1281,7 +1315,17 @@ export function getMessageAreaScript(): string {
// 如果是仿真工具且成功完成,尝试添加波形预览 // 如果是仿真工具且成功完成,尝试添加波形预览
if (segment.toolName === 'simulation' && segment.toolStatus === 'success') { if (segment.toolName === 'simulation' && segment.toolStatus === 'success') {
// 优先使用显式提供的路径,否则从结果文本中解析 // 尝试解析多个 VCD 文件(多 VCD 模式)
const vcdPaths = parseMultiVcdPaths(segment.toolResult);
if (vcdPaths.length > 0) {
// 多 VCD 模式:为每个文件创建预览
vcdPaths.forEach(vcdInfo => {
const waveformPreview = createWaveformPreview(vcdInfo.path, vcdInfo.name);
segmentDiv.appendChild(waveformPreview);
});
} else {
// 单 VCD 模式(兼容旧逻辑)
let vcdPath = segment.vcdFilePath; let vcdPath = segment.vcdFilePath;
if (!vcdPath && segment.toolResult) { if (!vcdPath && segment.toolResult) {
const match = String(segment.toolResult).match(/路径\s*:\s*(.+)/); const match = String(segment.toolResult).match(/路径\s*:\s*(.+)/);
@ -1296,6 +1340,7 @@ export function getMessageAreaScript(): string {
segmentDiv.appendChild(waveformPreview); segmentDiv.appendChild(waveformPreview);
} }
} }
}
// 添加折叠/展开事件监听 // 添加折叠/展开事件监听
if (shouldCollapse) { if (shouldCollapse) {