From 15a1de3a908865946902883dad4ddb5d993548c9 Mon Sep 17 00:00:00 2001 From: XiaoFeng <117837368+Fzhiyu1@users.noreply.github.com> Date: Sat, 10 Jan 2026 16:45:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=A4=9AVCD=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=94=9F=E6=88=90=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - iverilogRunner新增generateMultiVCD函数 - toolExecutor处理dumpModules参数 - api.ts扩展SimulationArgs接口 - messageArea支持多波形预览 --- src/services/toolExecutor.ts | 38 ++++++- src/types/api.ts | 4 + src/utils/iverilogRunner.ts | 190 +++++++++++++++++++++++++++++++++++ src/views/messageArea.ts | 93 ++++++++++++----- 4 files changed, 299 insertions(+), 26 deletions(-) diff --git a/src/services/toolExecutor.ts b/src/services/toolExecutor.ts index 97c07b5..4569d61 100644 --- a/src/services/toolExecutor.ts +++ b/src/services/toolExecutor.ts @@ -8,7 +8,7 @@ import * as os from 'os'; import * as fs from 'fs'; import { readFileContent, readDirectory } from '../utils/readFiles'; 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 { executeWaveformTrace, WaveformTraceArgs } from '../utils/waveformTracer'; import { @@ -285,7 +285,30 @@ async function executeSimulation( 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); 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 工具 * 解析 VCD 文件并返回波形摘要 diff --git a/src/types/api.ts b/src/types/api.ts index 53d85f5..e432aee 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -433,6 +433,10 @@ export interface SimulationArgs { rtlPath: string; tbPath: string; duration?: string; + /** 要dump的模块列表,格式:name:path,name:path */ + dumpModules?: string; + /** VCD输出目录,默认'vcd' */ + vcdDir?: string; } /** waveform_summary 工具参数 */ diff --git a/src/utils/iverilogRunner.ts b/src/utils/iverilogRunner.ts index 84246e0..0c45298 100644 --- a/src/utils/iverilogRunner.ts +++ b/src/utils/iverilogRunner.ts @@ -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 { + 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 : '未知错误'}` + }; + } +} diff --git a/src/views/messageArea.ts b/src/views/messageArea.ts index 1345a22..843bd82 100644 --- a/src/views/messageArea.ts +++ b/src/views/messageArea.ts @@ -675,6 +675,29 @@ export function getMessageAreaScript(): string { ${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) { const iconMap = { @@ -1034,19 +1057,30 @@ export function getMessageAreaScript(): string { // 如果是仿真工具且成功完成,尝试添加波形预览 if (segment.toolName === 'simulation' && segment.toolStatus === 'success') { - // 优先使用显式提供的路径,否则从结果文本中解析 - let vcdPath = segment.vcdFilePath; - if (!vcdPath && segment.toolResult) { - const match = String(segment.toolResult).match(/路径\s*:\s*(.+)/); - if (match && match[1]) { - vcdPath = match[1].trim(); - } - } + // 尝试解析多个 VCD 文件(多 VCD 模式) + const vcdPaths = parseMultiVcdPaths(segment.toolResult); - if (vcdPath) { - const fileName = segment.fileName || vcdPath.split(/[\\\\\/]/).pop() || 'waveform.vcd'; - const waveformPreview = createWaveformPreview(vcdPath, fileName); - segmentDiv.appendChild(waveformPreview); + if (vcdPaths.length > 0) { + // 多 VCD 模式:为每个文件创建预览 + vcdPaths.forEach(vcdInfo => { + 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') { - // 优先使用显式提供的路径,否则从结果文本中解析 - let vcdPath = segment.vcdFilePath; - if (!vcdPath && segment.toolResult) { - const match = String(segment.toolResult).match(/路径\s*:\s*(.+)/); - if (match && match[1]) { - vcdPath = match[1].trim(); - } - } + // 尝试解析多个 VCD 文件(多 VCD 模式) + const vcdPaths = parseMultiVcdPaths(segment.toolResult); - if (vcdPath) { - const fileName = segment.fileName || vcdPath.split(/[\\\\\/]/).pop() || 'waveform.vcd'; - const waveformPreview = createWaveformPreview(vcdPath, fileName); - segmentDiv.appendChild(waveformPreview); + if (vcdPaths.length > 0) { + // 多 VCD 模式:为每个文件创建预览 + vcdPaths.forEach(vcdInfo => { + 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); + } } }