feat: 支持多VCD文件生成功能
- iverilogRunner新增generateMultiVCD函数 - toolExecutor处理dumpModules参数 - api.ts扩展SimulationArgs接口 - messageArea支持多波形预览
This commit is contained in:
@ -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 文件并返回波形摘要
|
||||||
|
|||||||
@ -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 工具参数 */
|
||||||
|
|||||||
@ -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 : '未知错误'}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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,19 +1057,30 @@ export function getMessageAreaScript(): string {
|
|||||||
|
|
||||||
// 如果是仿真工具且成功完成,尝试添加波形预览
|
// 如果是仿真工具且成功完成,尝试添加波形预览
|
||||||
if (segment.toolName === 'simulation' && segment.toolStatus === 'success') {
|
if (segment.toolName === 'simulation' && segment.toolStatus === 'success') {
|
||||||
// 优先使用显式提供的路径,否则从结果文本中解析
|
// 尝试解析多个 VCD 文件(多 VCD 模式)
|
||||||
let vcdPath = segment.vcdFilePath;
|
const vcdPaths = parseMultiVcdPaths(segment.toolResult);
|
||||||
if (!vcdPath && segment.toolResult) {
|
|
||||||
const match = String(segment.toolResult).match(/路径\s*:\s*(.+)/);
|
|
||||||
if (match && match[1]) {
|
|
||||||
vcdPath = match[1].trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vcdPath) {
|
if (vcdPaths.length > 0) {
|
||||||
const fileName = segment.fileName || vcdPath.split(/[\\\\\/]/).pop() || 'waveform.vcd';
|
// 多 VCD 模式:为每个文件创建预览
|
||||||
const waveformPreview = createWaveformPreview(vcdPath, fileName);
|
vcdPaths.forEach(vcdInfo => {
|
||||||
segmentDiv.appendChild(waveformPreview);
|
const waveformPreview = createWaveformPreview(vcdInfo.path, vcdInfo.name);
|
||||||
|
segmentDiv.appendChild(waveformPreview);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 单 VCD 模式(兼容旧逻辑)
|
||||||
|
let vcdPath = segment.vcdFilePath;
|
||||||
|
if (!vcdPath && segment.toolResult) {
|
||||||
|
const match = String(segment.toolResult).match(/路径\s*:\s*(.+)/);
|
||||||
|
if (match && match[1]) {
|
||||||
|
vcdPath = match[1].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vcdPath) {
|
||||||
|
const fileName = segment.fileName || vcdPath.split(/[\\\\\/]/).pop() || 'waveform.vcd';
|
||||||
|
const waveformPreview = createWaveformPreview(vcdPath, fileName);
|
||||||
|
segmentDiv.appendChild(waveformPreview);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1281,19 +1315,30 @@ export function getMessageAreaScript(): string {
|
|||||||
|
|
||||||
// 如果是仿真工具且成功完成,尝试添加波形预览
|
// 如果是仿真工具且成功完成,尝试添加波形预览
|
||||||
if (segment.toolName === 'simulation' && segment.toolStatus === 'success') {
|
if (segment.toolName === 'simulation' && segment.toolStatus === 'success') {
|
||||||
// 优先使用显式提供的路径,否则从结果文本中解析
|
// 尝试解析多个 VCD 文件(多 VCD 模式)
|
||||||
let vcdPath = segment.vcdFilePath;
|
const vcdPaths = parseMultiVcdPaths(segment.toolResult);
|
||||||
if (!vcdPath && segment.toolResult) {
|
|
||||||
const match = String(segment.toolResult).match(/路径\s*:\s*(.+)/);
|
|
||||||
if (match && match[1]) {
|
|
||||||
vcdPath = match[1].trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vcdPath) {
|
if (vcdPaths.length > 0) {
|
||||||
const fileName = segment.fileName || vcdPath.split(/[\\\\\/]/).pop() || 'waveform.vcd';
|
// 多 VCD 模式:为每个文件创建预览
|
||||||
const waveformPreview = createWaveformPreview(vcdPath, fileName);
|
vcdPaths.forEach(vcdInfo => {
|
||||||
segmentDiv.appendChild(waveformPreview);
|
const waveformPreview = createWaveformPreview(vcdInfo.path, vcdInfo.name);
|
||||||
|
segmentDiv.appendChild(waveformPreview);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 单 VCD 模式(兼容旧逻辑)
|
||||||
|
let vcdPath = segment.vcdFilePath;
|
||||||
|
if (!vcdPath && segment.toolResult) {
|
||||||
|
const match = String(segment.toolResult).match(/路径\s*:\s*(.+)/);
|
||||||
|
if (match && match[1]) {
|
||||||
|
vcdPath = match[1].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vcdPath) {
|
||||||
|
const fileName = segment.fileName || vcdPath.split(/[\\\\\/]/).pop() || 'waveform.vcd';
|
||||||
|
const waveformPreview = createWaveformPreview(vcdPath, fileName);
|
||||||
|
segmentDiv.appendChild(waveformPreview);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user