/** * VCD (Value Change Dump) 解析器 * 纯 TypeScript 实现,参照 VerilogCoder 项目格式 * * @deprecated 当前未使用,保留备用 * 目前使用 waveformTracer.ts 调用 Python 打包的 waveform_trace.exe * 未来可能用此文件替换 Python 实现 */ import * as fs from 'fs'; import * as path from 'path'; // ==================== 类型定义 ==================== /** 信号定义 */ export interface VcdSignal { name: string; // 完整路径名,如 "tb.top_module.data" shortName: string; // 短名,如 "data" symbolId: string; // VCD 符号 ID,如 "!", "#" width: number; // 位宽 varType: string; // 变量类型:wire, reg module: string; // 所属模块 } /** 时间-值对 */ export interface TimeValue { time: number; value: string; } /** 信号波形数据 */ export interface SignalWaveform { signal: VcdSignal; changes: TimeValue[]; } /** VCD 解析结果 */ export interface VcdData { date?: string; version?: string; timescale: string; endTime: number; signals: Map; // symbolId -> signal waveforms: Map; // symbolId -> changes } /** Mismatch 信息 */ export interface MismatchInfo { time: number; signal: string; dutValue: string; refValue: string; } // ==================== VCD 解析器 ==================== export class VcdParser { private signals: Map = new Map(); private waveforms: Map = new Map(); private scopeStack: string[] = []; private timescale: string = '1ns'; private currentTime: number = 0; private endTime: number = 0; private date?: string; private version?: string; /** * 解析 VCD 文件 */ parse(filePath: string): VcdData { const content = fs.readFileSync(filePath, 'utf-8'); return this.parseContent(content); } /** * 解析 VCD 内容 */ parseContent(content: string): VcdData { // 预处理:将多行指令合并成单行 const normalizedContent = this.normalizeVcdContent(content); const lines = normalizedContent.split('\n'); let inDefinitions = true; for (const rawLine of lines) { const line = rawLine.trim(); if (!line) continue; if (inDefinitions) { // 解析定义区 if (line.startsWith('$enddefinitions')) { inDefinitions = false; continue; } this.parseDefinition(line); } else { // 解析数据区 this.parseValueChange(line); } } return { date: this.date, version: this.version, timescale: this.timescale, endTime: this.endTime, signals: this.signals, waveforms: this.waveforms }; } private parseDefinition(line: string): void { if (line.startsWith('$date')) { this.date = this.extractValue(line); } else if (line.startsWith('$version')) { this.version = this.extractValue(line); } else if (line.startsWith('$timescale')) { this.timescale = this.extractValue(line) || '1ns'; } else if (line.startsWith('$scope')) { const match = line.match(/\$scope\s+\w+\s+(\S+)/); if (match) { this.scopeStack.push(match[1]); } } else if (line.startsWith('$upscope')) { this.scopeStack.pop(); } else if (line.startsWith('$var')) { this.parseVariable(line); } } private parseVariable(line: string): void { // $var wire 8 # data [7:0] $end // $var reg 1 ! clk $end const match = line.match(/\$var\s+(\w+)\s+(\d+)\s+(\S+)\s+(\S+)/); if (!match) return; const [, varType, widthStr, symbolId, name] = match; const width = parseInt(widthStr, 10); const module = this.scopeStack.join('.'); const fullName = module ? `${module}.${name}` : name; const signal: VcdSignal = { name: fullName, shortName: name.replace(/\[\d+:\d+\]/, ''), // 移除位宽标注 symbolId, width, varType, module }; this.signals.set(symbolId, signal); this.waveforms.set(symbolId, []); } private parseValueChange(line: string): void { if (line.startsWith('#')) { // 时间戳: #100 this.currentTime = parseInt(line.substring(1), 10); this.endTime = Math.max(this.endTime, this.currentTime); } else if (line.startsWith('b') || line.startsWith('B')) { // 多位值: b10101010 # const spaceIdx = line.indexOf(' '); if (spaceIdx > 0) { const value = line.substring(1, spaceIdx); const symbolId = line.substring(spaceIdx + 1).trim(); this.addChange(symbolId, value); } } else if (line.length >= 2 && !line.startsWith('$')) { // 单位值: 0! 或 1# 或 x$ const value = line[0]; const symbolId = line.substring(1).trim(); if (symbolId && this.signals.has(symbolId)) { this.addChange(symbolId, value); } } } private addChange(symbolId: string, value: string): void { const changes = this.waveforms.get(symbolId); if (changes) { changes.push({ time: this.currentTime, value }); } } private extractValue(line: string): string { // 提取 $xxx value $end 中的 value const match = line.match(/\$\w+\s+(.+?)\s*\$end/); return match ? match[1].trim() : ''; } /** * 预处理 VCD 内容,将多行指令合并成单行 */ private normalizeVcdContent(content: string): string { // 将多行 $xxx ... $end 合并成单行 return content.replace(/(\$\w+)\s*\n\s*([^\$]+?)\s*\n\s*(\$end)/g, '$1 $2 $3'); } } // ==================== 波形分析工具 ==================== /** * 二进制字符串转十六进制 */ export function binaryToHex(binary: string): string { if (binary === 'x' || binary === 'X' || binary.includes('x')) { return 'xx'; } if (binary === 'z' || binary === 'Z' || binary.includes('z')) { return 'zz'; } if (binary.length <= 1) { return binary; } // 补齐到 4 的倍数 const padded = binary.padStart(Math.ceil(binary.length / 4) * 4, '0'); let hex = ''; for (let i = 0; i < padded.length; i += 4) { hex += parseInt(padded.substring(i, i + 4), 2).toString(16); } return hex; } /** * 获取信号在指定时间的值 */ export function getValueAtTime( changes: TimeValue[], time: number ): string { let value = 'x'; for (const change of changes) { if (change.time <= time) { value = change.value; } else { break; } } return value; } /** * 查找 DUT 和 REF 信号的第一个 mismatch */ export function findFirstMismatch( vcdData: VcdData, dutSignals: string[], refSignals: string[] ): MismatchInfo | null { // 收集所有时间点 const allTimes = new Set(); for (const changes of vcdData.waveforms.values()) { for (const c of changes) { allTimes.add(c.time); } } const sortedTimes = Array.from(allTimes).sort((a, b) => a - b); // 按信号名匹配 DUT 和 REF for (const time of sortedTimes) { for (let i = 0; i < dutSignals.length; i++) { const dutSig = findSignalByName(vcdData, dutSignals[i]); const refSig = findSignalByName(vcdData, refSignals[i]); if (!dutSig || !refSig) continue; const dutChanges = vcdData.waveforms.get(dutSig.symbolId) || []; const refChanges = vcdData.waveforms.get(refSig.symbolId) || []; const dutVal = getValueAtTime(dutChanges, time); const refVal = getValueAtTime(refChanges, time); // 跳过未知值 if (dutVal.includes('x') || refVal.includes('x')) continue; if (dutVal !== refVal) { return { time, signal: dutSig.shortName, dutValue: binaryToHex(dutVal), refValue: binaryToHex(refVal) }; } } } return null; } /** * 按名称查找信号 */ function findSignalByName(vcdData: VcdData, name: string): VcdSignal | null { for (const signal of vcdData.signals.values()) { if (signal.name.endsWith(name) || signal.shortName === name) { return signal; } } return null; } /** * 生成波形表格(参照 VerilogCoder 格式) */ export function generateWaveformTable( vcdData: VcdData, signalNames: string[], startTime: number = 0, endTime?: number, windowSize: number = 20 ): string { const actualEndTime = endTime ?? vcdData.endTime; // 查找信号 const signals: VcdSignal[] = []; for (const name of signalNames) { const sig = findSignalByName(vcdData, name); if (sig) signals.push(sig); } if (signals.length === 0) { return '未找到指定信号'; } // 收集时间点 const times = new Set(); for (const sig of signals) { const changes = vcdData.waveforms.get(sig.symbolId) || []; for (const c of changes) { if (c.time >= startTime && c.time <= actualEndTime) { times.add(c.time); } } } let sortedTimes = Array.from(times).sort((a, b) => a - b); if (sortedTimes.length > windowSize) { sortedTimes = sortedTimes.slice(0, windowSize); } // 生成表头 const headers = ['time(ns)', ...signals.map(s => s.shortName)]; const colWidths = headers.map(h => Math.max(h.length, 8)); let table = '### Waveform Trace ###\n'; table += headers.map((h, i) => h.padEnd(colWidths[i])).join(' ') + '\n'; table += colWidths.map(w => '─'.repeat(w)).join('──') + '\n'; // 生成数据行 for (const time of sortedTimes) { const row = [time.toString()]; for (const sig of signals) { const changes = vcdData.waveforms.get(sig.symbolId) || []; const val = getValueAtTime(changes, time); row.push(binaryToHex(val)); } table += row.map((v, i) => v.padEnd(colWidths[i])).join(' ') + '\n'; } table += '### Waveform Trace End ###\n'; return table; } /** * 获取信号显示名称(模块.信号名[位宽]) */ function getSignalDisplayName(sig: VcdSignal): string { const moduleParts = sig.module.split('.'); const moduleShort = moduleParts[moduleParts.length - 1] || ''; const bitInfo = sig.width > 1 ? `[${sig.width - 1}:0]` : ''; return moduleShort ? `${moduleShort}.${sig.shortName}${bitInfo}` : `${sig.shortName}${bitInfo}`; } /** * 生成变化日志格式(只记录信号变化) */ export function generateChangeLog(vcdData: VcdData): string { // 筛选信号(排除 parameter) const signals: VcdSignal[] = []; for (const sig of vcdData.signals.values()) { if (sig.varType !== 'parameter') { signals.push(sig); } } // 收集所有时间点 const times = new Set(); for (const sig of signals) { const changes = vcdData.waveforms.get(sig.symbolId) || []; for (const c of changes) { times.add(c.time); } } const sortedTimes = Array.from(times).sort((a, b) => a - b); // 记录每个信号的上一个值 const lastValues = new Map(); for (const sig of signals) { lastValues.set(sig.symbolId, null); } let log = ''; for (const time of sortedTimes) { const changes: string[] = []; for (const sig of signals) { const waveform = vcdData.waveforms.get(sig.symbolId) || []; const currentVal = binaryToHex(getValueAtTime(waveform, time)); const lastVal = lastValues.get(sig.symbolId); if (lastVal === null) { changes.push(`${getSignalDisplayName(sig)}=${currentVal}`); } else if (currentVal !== lastVal) { changes.push(`${getSignalDisplayName(sig)} ${lastVal}→${currentVal}`); } lastValues.set(sig.symbolId, currentVal); } if (changes.length > 0) { log += `#${time}: ${changes.join(', ')}\n`; } } return log; } /** * 分析 VCD 文件(主入口) */ export function analyzeVcdFile( filePath: string, signalFilter?: string, checkpoint?: number ): string { // 解析 VCD const parser = new VcdParser(); const vcdData = parser.parse(filePath); // 解析信号过滤器 const signalNames = signalFilter ? signalFilter.split(',').map(s => s.trim()) : Array.from(vcdData.signals.values()).map(s => s.shortName); // 生成摘要 let result = `=== VCD 波形分析 ===\n`; result += `文件: ${path.basename(filePath)}\n`; result += `时间单位: ${vcdData.timescale}\n`; result += `仿真时长: 0 - ${vcdData.endTime}${vcdData.timescale}\n\n`; // 信号列表 result += `--- 信号列表 (${vcdData.signals.size} 个) ---\n`; let idx = 1; for (const sig of vcdData.signals.values()) { if (idx <= 10) { result += `${idx}. ${sig.shortName} (${sig.width}-bit, ${sig.varType})\n`; } idx++; } if (vcdData.signals.size > 10) { result += `... 还有 ${vcdData.signals.size - 10} 个信号\n`; } result += '\n'; // 变化日志 result += generateChangeLog(vcdData); return result; }