From e4ff49bade8d9d54ed49591085037dc9576b920e Mon Sep 17 00:00:00 2001 From: XiaoFeng <117837368+Fzhiyu1@users.noreply.github.com> Date: Mon, 5 Jan 2026 18:29:49 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E6=B7=BB=E5=8A=A0=20vcdParser.ts=20(?= =?UTF-8?q?=E6=9C=AA=E4=BD=BF=E7=94=A8=EF=BC=8C=E4=BF=9D=E7=95=99=E5=A4=87?= =?UTF-8?q?=E7=94=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 纯 TypeScript 实现的 VCD 解析器,当前未使用。 目前使用 waveformTracer.ts 调用 Python 打包的 exe。 --- src/utils/vcdParser.ts | 467 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 467 insertions(+) create mode 100644 src/utils/vcdParser.ts diff --git a/src/utils/vcdParser.ts b/src/utils/vcdParser.ts new file mode 100644 index 0000000..eead1fd --- /dev/null +++ b/src/utils/vcdParser.ts @@ -0,0 +1,467 @@ +/** + * 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; +}