Compare commits
2 Commits
5c19be22d3
...
4a7af49fea
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a7af49fea | |||
| 15a1de3a90 |
@ -92,6 +92,7 @@ export class DialogSession {
|
||||
private toolContext: ToolExecutorContext;
|
||||
private accumulatedText = '';
|
||||
private isActive = false;
|
||||
private hasCompleted = false; // 标记是否已收到 complete 事件
|
||||
private segments: MessageSegment[] = [];
|
||||
private currentTextSegment: MessageSegment | null = null;
|
||||
|
||||
@ -331,6 +332,7 @@ export class DialogSession {
|
||||
}
|
||||
|
||||
this.isActive = true;
|
||||
this.hasCompleted = false; // 重置完成标志
|
||||
this.accumulatedText = '';
|
||||
this.segments = [];
|
||||
this.currentTextSegment = null;
|
||||
@ -686,6 +688,7 @@ export class DialogSession {
|
||||
|
||||
onComplete: (data) => {
|
||||
this.isActive = false;
|
||||
this.hasCompleted = true; // 标记已收到 complete 事件
|
||||
this.finalizeTextSegment();
|
||||
|
||||
// 追踪 AI 消息(用于后端重启后恢复)
|
||||
@ -807,6 +810,15 @@ export class DialogSession {
|
||||
|
||||
onClose: () => {
|
||||
console.log('[DialogSession] SSE 连接已关闭');
|
||||
// 如果没有收到 complete 事件,需要补充完成逻辑
|
||||
if (!this.hasCompleted && this.isActive) {
|
||||
console.log('[DialogSession] 未收到 complete 事件,补充完成处理');
|
||||
this.finalizeTextSegment();
|
||||
if (this.accumulatedText) {
|
||||
historyManager.trackAiMessage(this.accumulatedText);
|
||||
}
|
||||
callbacks.onComplete?.(this.segments);
|
||||
}
|
||||
this.isActive = false;
|
||||
}
|
||||
};
|
||||
|
||||
@ -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 文件并返回波形摘要
|
||||
|
||||
@ -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 工具参数 */
|
||||
|
||||
@ -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()}
|
||||
|
||||
// 解析多 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user