feat: 集成 waveform_trace 波形调试工具
新增功能: - waveformTracer.ts: 调用 waveform_trace.exe 的工具实现 - toolExecutor.ts: 添加 waveform_trace 工具分发 - types/api.ts: 添加 WaveformTraceArgs 类型定义 工具源码 (tools/waveform_trace/src/): - AST 解析 + BFS 信号追踪 - VCD 波形解析 - 修复通用 testbench 支持 配置文件: - .gitignore: 排除 exe 和打包产物 - .vscodeignore: 发布时排除源码 - build.bat/build.sh: 打包脚本
This commit is contained in:
11
.gitignore
vendored
11
.gitignore
vendored
@ -3,3 +3,14 @@ dist
|
|||||||
node_modules
|
node_modules
|
||||||
.vscode-test/
|
.vscode-test/
|
||||||
*.vsix
|
*.vsix
|
||||||
|
|
||||||
|
# waveform_trace 打包产物(exe 太大,通过 Release 发布)
|
||||||
|
tools/waveform_trace/bin/
|
||||||
|
tools/waveform_trace/src/build/
|
||||||
|
tools/waveform_trace/src/dist/
|
||||||
|
tools/waveform_trace/src/*.spec
|
||||||
|
|
||||||
|
# Python 缓存
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
|||||||
29
.vscodeignore
Normal file
29
.vscodeignore
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# 排除开发文件
|
||||||
|
.vscode/**
|
||||||
|
.git/**
|
||||||
|
.gitignore
|
||||||
|
node_modules/**
|
||||||
|
src/**
|
||||||
|
**/*.ts
|
||||||
|
**/*.map
|
||||||
|
|
||||||
|
# 排除测试文件
|
||||||
|
test/**
|
||||||
|
**/*.test.js
|
||||||
|
|
||||||
|
# 排除文档
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
|
||||||
|
# 排除 waveform_trace Python 源码(只保留 exe)
|
||||||
|
tools/waveform_trace/src/**
|
||||||
|
tools/waveform_trace/build/**
|
||||||
|
tools/waveform_trace/dist/**
|
||||||
|
tools/waveform_trace/build.bat
|
||||||
|
tools/waveform_trace/build.sh
|
||||||
|
|
||||||
|
# 排除打包临时文件
|
||||||
|
**/__pycache__/**
|
||||||
|
**/*.pyc
|
||||||
|
**/*.pyo
|
||||||
|
**/*.spec
|
||||||
@ -9,6 +9,8 @@ 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 } from '../utils/iverilogRunner';
|
||||||
|
import { analyzeVcdFile } from '../utils/vcdParser';
|
||||||
|
import { executeWaveformTrace, WaveformTraceArgs } from '../utils/waveformTracer';
|
||||||
import {
|
import {
|
||||||
submitToolResult,
|
submitToolResult,
|
||||||
createSuccessResult,
|
createSuccessResult,
|
||||||
@ -79,6 +81,9 @@ export async function executeToolCall(
|
|||||||
case 'waveform_summary':
|
case 'waveform_summary':
|
||||||
resultText = await executeWaveformSummary(args as unknown as WaveformSummaryArgs);
|
resultText = await executeWaveformSummary(args as unknown as WaveformSummaryArgs);
|
||||||
break;
|
break;
|
||||||
|
case 'waveform_trace':
|
||||||
|
resultText = await executeWaveformTrace(args as unknown as WaveformTraceArgs, context);
|
||||||
|
break;
|
||||||
case 'knowledge_save':
|
case 'knowledge_save':
|
||||||
resultText = await executeKnowledgeSave(args as unknown as KnowledgeSaveArgs);
|
resultText = await executeKnowledgeSave(args as unknown as KnowledgeSaveArgs);
|
||||||
break;
|
break;
|
||||||
@ -300,12 +305,36 @@ async function executeSimulation(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行 waveform_summary 工具
|
* 执行 waveform_summary 工具
|
||||||
* TODO: 实现 VCD 波形分析
|
* 解析 VCD 文件并返回波形摘要
|
||||||
*/
|
*/
|
||||||
async function executeWaveformSummary(args: WaveformSummaryArgs): Promise<string> {
|
async function executeWaveformSummary(args: WaveformSummaryArgs): Promise<string> {
|
||||||
// TODO: 使用 vcdrom/vcd-stream 解析 VCD 文件
|
const { vcdPath, signals, checkpoints } = args;
|
||||||
// 目前返回一个占位响应
|
|
||||||
return `波形分析功能暂未实现。\n请求参数:\n- VCD文件: ${args.vcdPath}\n- 信号: ${args.signals}\n- 检查点: ${args.checkpoints || '无'}`;
|
// 获取工作区路径
|
||||||
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||||
|
if (!workspaceFolders || workspaceFolders.length === 0) {
|
||||||
|
throw new Error('请先打开一个工作区');
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspacePath = workspaceFolders[0].uri.fsPath;
|
||||||
|
|
||||||
|
// 解析 VCD 文件路径(支持相对路径)
|
||||||
|
const absolutePath = path.isAbsolute(vcdPath)
|
||||||
|
? vcdPath
|
||||||
|
: path.join(workspacePath, vcdPath);
|
||||||
|
|
||||||
|
// 检查文件是否存在
|
||||||
|
if (!fs.existsSync(absolutePath)) {
|
||||||
|
throw new Error(`VCD 文件不存在: ${vcdPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析检查点时间
|
||||||
|
const checkpoint = checkpoints ? parseInt(checkpoints, 10) : undefined;
|
||||||
|
|
||||||
|
// 调用 VCD 解析器
|
||||||
|
const result = analyzeVcdFile(absolutePath, signals, checkpoint);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -309,6 +309,7 @@ export type ToolName =
|
|||||||
| 'syntax_check'
|
| 'syntax_check'
|
||||||
| 'simulation'
|
| 'simulation'
|
||||||
| 'waveform_summary'
|
| 'waveform_summary'
|
||||||
|
| 'waveform_trace'
|
||||||
| 'knowledge_save'
|
| 'knowledge_save'
|
||||||
| 'knowledge_load';
|
| 'knowledge_load';
|
||||||
|
|
||||||
@ -354,6 +355,18 @@ export interface WaveformSummaryArgs {
|
|||||||
checkpoints?: string;
|
checkpoints?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** waveform_trace 工具参数 */
|
||||||
|
export interface WaveformTraceArgs {
|
||||||
|
/** Verilog 源文件路径(相对于项目根目录) */
|
||||||
|
verilogPath: string;
|
||||||
|
/** VCD 波形文件路径(相对于项目根目录) */
|
||||||
|
vcdPath: string;
|
||||||
|
/** 仿真工具的输出字符串(包含 mismatch 信息) */
|
||||||
|
simOutput: string;
|
||||||
|
/** BFS 回溯层数,默认 2 */
|
||||||
|
traceLevel?: number;
|
||||||
|
}
|
||||||
|
|
||||||
/** knowledge_save 工具参数 */
|
/** knowledge_save 工具参数 */
|
||||||
export interface KnowledgeSaveArgs {
|
export interface KnowledgeSaveArgs {
|
||||||
/** 知识图谱 JSON 数据 */
|
/** 知识图谱 JSON 数据 */
|
||||||
@ -374,5 +387,6 @@ export type ToolArgs =
|
|||||||
| SyntaxCheckArgs
|
| SyntaxCheckArgs
|
||||||
| SimulationArgs
|
| SimulationArgs
|
||||||
| WaveformSummaryArgs
|
| WaveformSummaryArgs
|
||||||
|
| WaveformTraceArgs
|
||||||
| KnowledgeSaveArgs
|
| KnowledgeSaveArgs
|
||||||
| KnowledgeLoadArgs;
|
| KnowledgeLoadArgs;
|
||||||
|
|||||||
145
src/utils/waveformTracer.ts
Normal file
145
src/utils/waveformTracer.ts
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/**
|
||||||
|
* 波形追踪工具
|
||||||
|
* 调用 PyInstaller 打包的 waveform_trace 可执行文件
|
||||||
|
*/
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 波形追踪参数
|
||||||
|
*/
|
||||||
|
export interface WaveformTraceArgs {
|
||||||
|
/** Verilog 源文件路径(相对于项目根目录) */
|
||||||
|
verilogPath: string;
|
||||||
|
/** VCD 波形文件路径(相对于项目根目录) */
|
||||||
|
vcdPath: string;
|
||||||
|
/** 仿真工具的输出字符串(包含 mismatch 信息) */
|
||||||
|
simOutput: string;
|
||||||
|
/** BFS 回溯层数,默认 2 */
|
||||||
|
traceLevel?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行波形追踪
|
||||||
|
* @param args 追踪参数
|
||||||
|
* @param context 执行上下文
|
||||||
|
* @returns 追踪结果字符串
|
||||||
|
*/
|
||||||
|
export async function executeWaveformTrace(
|
||||||
|
args: WaveformTraceArgs,
|
||||||
|
context: { extensionPath: string }
|
||||||
|
): Promise<string> {
|
||||||
|
// 获取可执行文件路径
|
||||||
|
const tracerPath = getWaveformTracerPath(context.extensionPath);
|
||||||
|
|
||||||
|
// 检查可执行文件是否存在
|
||||||
|
if (!fs.existsSync(tracerPath)) {
|
||||||
|
throw new Error(
|
||||||
|
`waveform_trace 工具未安装: ${tracerPath}\n` +
|
||||||
|
'请确保插件包含 tools/waveform_trace/bin/ 目录'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取工作区路径
|
||||||
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||||
|
if (!workspaceFolders || workspaceFolders.length === 0) {
|
||||||
|
throw new Error('请先打开一个工作区');
|
||||||
|
}
|
||||||
|
const workspacePath = workspaceFolders[0].uri.fsPath;
|
||||||
|
|
||||||
|
// 解析路径(支持相对路径)
|
||||||
|
const verilogAbsPath = path.isAbsolute(args.verilogPath)
|
||||||
|
? args.verilogPath
|
||||||
|
: path.join(workspacePath, args.verilogPath);
|
||||||
|
const vcdAbsPath = path.isAbsolute(args.vcdPath)
|
||||||
|
? args.vcdPath
|
||||||
|
: path.join(workspacePath, args.vcdPath);
|
||||||
|
|
||||||
|
// 验证文件存在
|
||||||
|
if (!fs.existsSync(verilogAbsPath)) {
|
||||||
|
throw new Error(`Verilog 文件不存在: ${args.verilogPath}`);
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(vcdAbsPath)) {
|
||||||
|
throw new Error(`VCD 文件不存在: ${args.vcdPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用可执行文件
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const child = spawn(tracerPath, [
|
||||||
|
'--verilog', verilogAbsPath,
|
||||||
|
'--vcd', vcdAbsPath,
|
||||||
|
'--sim-output', args.simOutput,
|
||||||
|
'--trace-level', String(args.traceLevel || 2),
|
||||||
|
'--output-format', 'text'
|
||||||
|
], {
|
||||||
|
windowsHide: true,
|
||||||
|
cwd: workspacePath,
|
||||||
|
shell: false
|
||||||
|
});
|
||||||
|
|
||||||
|
let stdout = '';
|
||||||
|
let stderr = '';
|
||||||
|
|
||||||
|
child.stdout.on('data', (data: Buffer) => {
|
||||||
|
stdout += data.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.on('data', (data: Buffer) => {
|
||||||
|
stderr += data.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('close', (code: number | null) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve(stdout);
|
||||||
|
} else {
|
||||||
|
reject(new Error(
|
||||||
|
`waveform_trace 执行失败 (code=${code}):\n${stderr || stdout}`
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('error', (error: Error) => {
|
||||||
|
reject(new Error(`waveform_trace 启动失败: ${error.message}`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 waveform_trace 可执行文件路径
|
||||||
|
*/
|
||||||
|
function getWaveformTracerPath(extensionPath: string): string {
|
||||||
|
const platform = process.platform;
|
||||||
|
let binName = 'waveform_trace';
|
||||||
|
|
||||||
|
if (platform === 'win32') {
|
||||||
|
binName = 'waveform_trace.exe';
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.join(extensionPath, 'tools', 'waveform_trace', 'bin', binName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查 waveform_trace 工具是否可用
|
||||||
|
*/
|
||||||
|
export function checkWaveformTraceAvailable(extensionPath: string): {
|
||||||
|
available: boolean;
|
||||||
|
message: string;
|
||||||
|
path?: string;
|
||||||
|
} {
|
||||||
|
const tracerPath = getWaveformTracerPath(extensionPath);
|
||||||
|
|
||||||
|
if (fs.existsSync(tracerPath)) {
|
||||||
|
return {
|
||||||
|
available: true,
|
||||||
|
message: 'waveform_trace 工具可用',
|
||||||
|
path: tracerPath
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
available: false,
|
||||||
|
message: `waveform_trace 工具未找到: ${tracerPath}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
42
tools/waveform_trace/build.bat
Normal file
42
tools/waveform_trace/build.bat
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
@echo off
|
||||||
|
REM waveform_trace 打包脚本 (Windows)
|
||||||
|
REM 用法: build.bat
|
||||||
|
|
||||||
|
echo ========================================
|
||||||
|
echo waveform_trace 打包脚本
|
||||||
|
echo ========================================
|
||||||
|
|
||||||
|
cd /d "%~dp0src"
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo [1/3] 安装依赖...
|
||||||
|
pip install -r requirements.txt
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo 错误: 依赖安装失败
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo [2/3] 清理旧文件...
|
||||||
|
if exist build rmdir /s /q build
|
||||||
|
if exist dist rmdir /s /q dist
|
||||||
|
if exist waveform_trace.spec del waveform_trace.spec
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo [3/3] PyInstaller 打包...
|
||||||
|
pyinstaller --onefile --name waveform_trace --collect-all pyverilog waveform_trace_cli.py
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo 错误: 打包失败
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo [4/4] 复制到 bin 目录...
|
||||||
|
if not exist "..\bin" mkdir "..\bin"
|
||||||
|
copy /y "dist\waveform_trace.exe" "..\bin\"
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ========================================
|
||||||
|
echo 打包完成!
|
||||||
|
echo 输出: tools/waveform_trace/bin/waveform_trace.exe
|
||||||
|
echo ========================================
|
||||||
35
tools/waveform_trace/build.sh
Normal file
35
tools/waveform_trace/build.sh
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# waveform_trace 打包脚本 (Linux/macOS)
|
||||||
|
# 用法: ./build.sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo " waveform_trace 打包脚本"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR/src"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[1/4] 安装依赖..."
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[2/4] 清理旧文件..."
|
||||||
|
rm -rf build dist *.spec
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[3/4] PyInstaller 打包..."
|
||||||
|
pyinstaller --onefile --name waveform_trace --collect-all pyverilog waveform_trace_cli.py
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[4/4] 复制到 bin 目录..."
|
||||||
|
mkdir -p ../bin
|
||||||
|
cp dist/waveform_trace ../bin/
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
|
echo " 打包完成!"
|
||||||
|
echo " 输出: tools/waveform_trace/bin/waveform_trace"
|
||||||
|
echo "========================================"
|
||||||
115
tools/waveform_trace/src/README.md
Normal file
115
tools/waveform_trace/src/README.md
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
# AST 波形调试核心代码
|
||||||
|
|
||||||
|
## 文件说明
|
||||||
|
|
||||||
|
| 文件 | 作用 | 核心函数 | TS重写需要 |
|
||||||
|
|------|------|----------|------------|
|
||||||
|
| `ast_node.py` | AST节点定义,遍历建图 | `toplogic_tree_traverse()` | ✅ 已完成 |
|
||||||
|
| `graph_builder.py` | 入口函数,调用解析器 | `generate_top_logic_graph()` | ✅ 已完成 |
|
||||||
|
| `debug_graph_analyzer.py` | BFS回溯控制信号 | `get_k_control_signals()` | ⚠️ 需重写 |
|
||||||
|
| `vcd_waveform_analyzer.py` | VCD波形文件解析 | `parse_mismatch()`, `get_tabular()` | ⚠️ 需重写 |
|
||||||
|
| `waveform_trace_tool.py` | 完整追踪工具封装 | `waveform_trace_tool()` | ⚠️ 需重写 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 调用流程
|
||||||
|
|
||||||
|
```
|
||||||
|
Verilog代码文件
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ graph_builder.py │
|
||||||
|
│ generate_top_logic_graph(filelist) │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ PyVerilog.parse() → AST │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ast.toplogic_tree_traverse() │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ NetworkX 有向图(信号依赖图) │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ debug_graph_analyzer.py │
|
||||||
|
│ DebugGraph.get_k_control_signals() │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ BFS回溯K层,找到控制信号链 │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ vcd_waveform_analyzer.py │
|
||||||
|
│ parse_mismatch() + get_tabular() │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ 提取相关信号的波形表 │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心代码位置
|
||||||
|
|
||||||
|
### 1. AST遍历建图 (ast_node.py:32-137)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def toplogic_tree_traverse(self, network_G, rvalue=False, lvalue=False, offset=0):
|
||||||
|
"""
|
||||||
|
递归遍历AST,提取信号依赖关系,填充到NetworkX图中
|
||||||
|
|
||||||
|
关键逻辑:
|
||||||
|
1. 识别 Rvalue(右值)和 Lvalue(左值)
|
||||||
|
2. 递归收集子节点的信号
|
||||||
|
3. 建立边:右值信号 → 左值信号(控制关系)
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 图构建入口 (graph_builder.py:89-99)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def generate_top_logic_graph(filelist: list[str]):
|
||||||
|
# 1. PyVerilog解析Verilog代码
|
||||||
|
ast, directives = parse(filelist, preprocess_include=[], preprocess_define=[])
|
||||||
|
# 2. 遍历AST,构建信号依赖图
|
||||||
|
return create_graph_from_ast(ast, display=False, display_signal_only=False)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. BFS回溯 (debug_graph_analyzer.py:20-66)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_k_control_signals(self, target_signals: list[str], k: int, signal_only: bool = False):
|
||||||
|
"""
|
||||||
|
从出错信号出发,BFS回溯K层,找到所有控制信号
|
||||||
|
|
||||||
|
输入:target_signals = ['out'] # 出错的信号
|
||||||
|
输出:control_signals = {'out': (10,10), 'state': (5,8), 'clk': (1,1)}
|
||||||
|
signal_level_tracer = [['clk->state', 'reset->state'], ['state->out']]
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 依赖库
|
||||||
|
|
||||||
|
```
|
||||||
|
pyverilog # Verilog解析,生成AST
|
||||||
|
networkx # 图数据结构
|
||||||
|
pandas # 波形数据处理(可选)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 如果要用JavaScript重写
|
||||||
|
|
||||||
|
需要重写的核心逻辑:
|
||||||
|
|
||||||
|
1. **Verilog解析器** → 用 ANTLR4 + Verilog.g4 或 tree-sitter-verilog
|
||||||
|
2. **AST遍历建图** → 约100行,参考 ast_node.py:32-137
|
||||||
|
3. **BFS回溯** → 约70行,参考 debug_graph_analyzer.py
|
||||||
|
|
||||||
|
总计约 **200行核心逻辑**(不含解析器)
|
||||||
455
tools/waveform_trace/src/TS_REWRITE_SPEC.md
Normal file
455
tools/waveform_trace/src/TS_REWRITE_SPEC.md
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
# AST波形调试工具 - TypeScript重写规范
|
||||||
|
|
||||||
|
## 一、项目背景
|
||||||
|
|
||||||
|
将Python实现的Verilog AST波形调试工具重写为TypeScript,用于VSCode插件。
|
||||||
|
|
||||||
|
**已完成部分**:
|
||||||
|
- ✅ Verilog AST解析(生成JSON格式的信号依赖图)
|
||||||
|
- ✅ 图结构定义
|
||||||
|
|
||||||
|
**待重写部分**:
|
||||||
|
- ⚠️ BFS信号回溯
|
||||||
|
- ⚠️ VCD波形解析
|
||||||
|
- ⚠️ 仿真输出解析
|
||||||
|
- ⚠️ 工具整合封装
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、数据结构定义
|
||||||
|
|
||||||
|
### 2.1 AST图结构(已完成)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ASTNode {
|
||||||
|
id: string;
|
||||||
|
attributes: {
|
||||||
|
lines: [number, number]; // [起始行, 结束行]
|
||||||
|
type: string; // Input/Output/Reg/Wire/Always/Assign等
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ASTEdge {
|
||||||
|
from: string; // 控制信号
|
||||||
|
to: string; // 被控制信号
|
||||||
|
attributes: {
|
||||||
|
lines: [number, number];
|
||||||
|
type: string; // Always/Assign/IfStatement等
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ASTGraph {
|
||||||
|
metadata: {
|
||||||
|
moduleName: string;
|
||||||
|
nodeCount: number;
|
||||||
|
edgeCount: number;
|
||||||
|
generatedAt: string;
|
||||||
|
};
|
||||||
|
nodes: ASTNode[];
|
||||||
|
edges: ASTEdge[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 追踪结果结构
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface TraceResult {
|
||||||
|
controlSignals: Map<string, [number, number]>; // 信号名 -> 代码行号
|
||||||
|
signalLevelTracer: string[][]; // 每层的控制关系链
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 波形数据结构
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface WaveformData {
|
||||||
|
time: number; // 时间点(ns)
|
||||||
|
signals: {
|
||||||
|
[signalName: string]: string; // 信号名 -> 值(十六进制)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MismatchInfo {
|
||||||
|
signals: string[]; // 出错的信号列表
|
||||||
|
firstMismatchTime: number; // 第一次出错的时间
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、需要重写的模块
|
||||||
|
|
||||||
|
### 3.1 BFS信号回溯模块
|
||||||
|
|
||||||
|
**源文件**: `debug_graph_analyzer.py`
|
||||||
|
**代码行数**: ~70行
|
||||||
|
**第三方依赖**: 无
|
||||||
|
|
||||||
|
#### 功能描述
|
||||||
|
从出错信号出发,BFS反向遍历图,找到所有控制该信号的上游信号。
|
||||||
|
|
||||||
|
#### 输入输出
|
||||||
|
```typescript
|
||||||
|
// 输入
|
||||||
|
graph: ASTGraph // AST图(JSON格式)
|
||||||
|
targetSignals: string[] // 出错的信号列表,如 ['count', 'overflow']
|
||||||
|
k: number // 回溯层数
|
||||||
|
signalOnly: boolean // 是否只返回信号节点(过滤Always/Assign等)
|
||||||
|
|
||||||
|
// 输出
|
||||||
|
TraceResult {
|
||||||
|
controlSignals: Map<string, [number, number]>,
|
||||||
|
signalLevelTracer: string[][]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 核心算法(伪代码)
|
||||||
|
```
|
||||||
|
1. 构建前驱映射(反向边)
|
||||||
|
for each edge in graph.edges:
|
||||||
|
predecessorMap[edge.to].push(edge.from)
|
||||||
|
|
||||||
|
2. 初始化BFS队列
|
||||||
|
for each signal in targetSignals:
|
||||||
|
queue.push([signal, signal])
|
||||||
|
controlSignals.set(signal, node.lines)
|
||||||
|
|
||||||
|
3. BFS遍历K层
|
||||||
|
for level = 0 to k:
|
||||||
|
while queue not empty:
|
||||||
|
[curSignal, controlledSignal] = queue.pop()
|
||||||
|
记录关系: curSignal -> controlledSignal
|
||||||
|
|
||||||
|
for each predecessor of curSignal:
|
||||||
|
if not visited and not filtered:
|
||||||
|
queue.push([predecessor, curSignal])
|
||||||
|
|
||||||
|
记录本层关系到 signalLevelTracer
|
||||||
|
|
||||||
|
4. 返回结果
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 过滤规则
|
||||||
|
```typescript
|
||||||
|
// 需要过滤的节点类型
|
||||||
|
const FILTERED_TYPES = ['Parameter', 'Localparam'];
|
||||||
|
|
||||||
|
// signalOnly=true时,还需要过滤以下前缀
|
||||||
|
const FILTERED_PREFIXES = ['Always', 'Assign', 'Module', 'IntConst'];
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 仿真输出解析模块
|
||||||
|
|
||||||
|
**源文件**: `vcd_waveform_analyzer.py` 中的 `parse_mismatch()`
|
||||||
|
**代码行数**: ~20行
|
||||||
|
**第三方依赖**: 无
|
||||||
|
|
||||||
|
#### 功能描述
|
||||||
|
解析仿真工具的输出文本,提取出错信号名和出错时间。
|
||||||
|
|
||||||
|
#### 输入输出
|
||||||
|
```typescript
|
||||||
|
// 输入
|
||||||
|
testOutput: string // 仿真工具的输出文本
|
||||||
|
|
||||||
|
// 输出
|
||||||
|
MismatchInfo {
|
||||||
|
signals: string[], // 出错信号列表
|
||||||
|
firstMismatchTime: number // 第一次出错时间(ns)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 解析规则
|
||||||
|
```typescript
|
||||||
|
// 需要匹配的格式
|
||||||
|
// "First mismatch occurred at time 100. Output 'count' ..."
|
||||||
|
|
||||||
|
const pattern = /First mismatch occurred at time (\d+).*Output '(\w+)'/g;
|
||||||
|
|
||||||
|
// 提取所有匹配
|
||||||
|
// 返回信号列表和最小时间戳
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
```
|
||||||
|
输入:
|
||||||
|
"First mismatch occurred at time 100. Output 'count' expected 0001, got 0000
|
||||||
|
First mismatch occurred at time 150. Output 'overflow' expected 1, got 0"
|
||||||
|
|
||||||
|
输出:
|
||||||
|
{
|
||||||
|
signals: ['count', 'overflow'],
|
||||||
|
firstMismatchTime: 100
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.3 VCD波形解析模块
|
||||||
|
|
||||||
|
**源文件**: `vcd_waveform_analyzer.py` 中的 `get_tabular()` 和 `tabular_via_dataframe()`
|
||||||
|
**代码行数**: ~150行
|
||||||
|
**第三方依赖**: Python版用了 `vcdvcd`, `pandas`, `numpy`
|
||||||
|
|
||||||
|
#### 功能描述
|
||||||
|
读取VCD(Value Change Dump)波形文件,提取指定信号的波形值,生成表格。
|
||||||
|
|
||||||
|
#### VCD文件格式简介
|
||||||
|
```vcd
|
||||||
|
$timescale 1ns $end
|
||||||
|
$scope module tb $end
|
||||||
|
$var wire 1 ! clk $end
|
||||||
|
$var wire 8 " count [7:0] $end
|
||||||
|
$upscope $end
|
||||||
|
$enddefinitions $end
|
||||||
|
#0
|
||||||
|
b0 "
|
||||||
|
1!
|
||||||
|
#5
|
||||||
|
0!
|
||||||
|
#10
|
||||||
|
1!
|
||||||
|
b00000001 "
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 输入输出
|
||||||
|
```typescript
|
||||||
|
// 输入
|
||||||
|
vcdPath: string // VCD文件路径
|
||||||
|
signalsToTrace: string[] // 需要提取的信号列表
|
||||||
|
offset: number // 时间偏移(从哪个时间点开始)
|
||||||
|
windowSize: number // 窗口大小(提取多少个时间点)
|
||||||
|
|
||||||
|
// 输出
|
||||||
|
string // 格式化的波形表格字符串
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 输出格式示例
|
||||||
|
```
|
||||||
|
### First mismatched signals time(ns) Trace ###
|
||||||
|
time(ns) clk reset count_ref count_dut
|
||||||
|
0 1 1 00 00
|
||||||
|
5 0 1 00 00
|
||||||
|
10 1 0 00 00
|
||||||
|
15 0 0 00 00
|
||||||
|
20 1 0 01 00 <- mismatch
|
||||||
|
### First mismatched signals time(ns) End ###
|
||||||
|
```
|
||||||
|
|
||||||
|
#### TS实现建议
|
||||||
|
1. **方案A**: 找现有的JS VCD解析库
|
||||||
|
- 搜索: `npm vcd parser`, `vcd-stream`, `wavedrom`
|
||||||
|
|
||||||
|
2. **方案B**: 自己实现简单的VCD解析器
|
||||||
|
- VCD格式相对简单,核心是解析变量定义和时间变化
|
||||||
|
- 约100-150行代码
|
||||||
|
|
||||||
|
#### VCD解析核心逻辑
|
||||||
|
```typescript
|
||||||
|
class VCDParser {
|
||||||
|
signals: Map<string, Signal>; // 信号定义
|
||||||
|
timeValues: Map<number, Map<string, string>>; // 时间 -> 信号值
|
||||||
|
|
||||||
|
parse(vcdContent: string): void {
|
||||||
|
// 1. 解析头部($var定义)
|
||||||
|
// 2. 解析数据部分(#时间 和 值变化)
|
||||||
|
}
|
||||||
|
|
||||||
|
getSignalValues(signalName: string, startTime: number, endTime: number): WaveformData[] {
|
||||||
|
// 提取指定信号在时间范围内的值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.4 工具整合封装模块
|
||||||
|
|
||||||
|
**源文件**: `waveform_trace_tool.py`
|
||||||
|
**代码行数**: ~150行
|
||||||
|
**第三方依赖**: 依赖上面所有模块
|
||||||
|
|
||||||
|
#### 功能描述
|
||||||
|
整合所有模块,提供统一的调试接口。
|
||||||
|
|
||||||
|
#### 输入输出
|
||||||
|
```typescript
|
||||||
|
// 输入
|
||||||
|
verilogFilePath: string // Verilog文件路径
|
||||||
|
vcdFilePath: string // VCD波形文件路径
|
||||||
|
simulationOutput: string // 仿真输出文本
|
||||||
|
traceLevel: number // 回溯层数
|
||||||
|
|
||||||
|
// 输出
|
||||||
|
string // 完整的调试报告
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 调试报告格式
|
||||||
|
```
|
||||||
|
[Signal Traces] Backtrace control signal relations.
|
||||||
|
clk->count
|
||||||
|
reset->count
|
||||||
|
-count->state
|
||||||
|
--state->out (*last output port level)
|
||||||
|
|
||||||
|
[Signal Waveform]:
|
||||||
|
<signal>_ref 是期望值(golden)
|
||||||
|
<signal>_dut 是实际输出
|
||||||
|
[Traced Signals]: out, state, count, clk, reset
|
||||||
|
|
||||||
|
[Table Waveform in hexadecimal format]
|
||||||
|
time(ns) clk reset count_ref count_dut
|
||||||
|
...
|
||||||
|
|
||||||
|
[Verilog of DUT]:
|
||||||
|
```verilog
|
||||||
|
module counter(...);
|
||||||
|
...
|
||||||
|
endmodule
|
||||||
|
```
|
||||||
|
|
||||||
|
[Hint] ...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、调用流程图
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ waveform_trace_tool() │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ 1. 检查文件是否存在 │
|
||||||
|
│ ├── verilogFilePath │
|
||||||
|
│ └── vcdFilePath │
|
||||||
|
│ │
|
||||||
|
│ 2. 加载AST图(已有JSON) │
|
||||||
|
│ └── graph = loadASTGraph(verilogFilePath) │
|
||||||
|
│ │
|
||||||
|
│ 3. 解析仿真输出,获取出错信号 │
|
||||||
|
│ └── mismatchInfo = parseMismatch(simulationOutput) │
|
||||||
|
│ ├── signals: ['count', 'overflow'] │
|
||||||
|
│ └── firstMismatchTime: 100 │
|
||||||
|
│ │
|
||||||
|
│ 4. BFS回溯,找到控制信号链 │
|
||||||
|
│ └── traceResult = getKControlSignals(graph, signals, k) │
|
||||||
|
│ ├── controlSignals: Map<信号名, 行号> │
|
||||||
|
│ └── signalLevelTracer: [['clk->count'], ...] │
|
||||||
|
│ │
|
||||||
|
│ 5. 读取VCD波形,提取相关信号的值 │
|
||||||
|
│ └── waveformTable = getTabular(vcdPath, signals, offset) │
|
||||||
|
│ │
|
||||||
|
│ 6. 读取Verilog源码 │
|
||||||
|
│ └── verilogCode = readFile(verilogFilePath) │
|
||||||
|
│ │
|
||||||
|
│ 7. 组装调试报告 │
|
||||||
|
│ └── return formatReport(traceResult, waveformTable, code) │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、参考实现
|
||||||
|
|
||||||
|
### 5.1 Python源文件位置
|
||||||
|
|
||||||
|
```
|
||||||
|
ast_debug_core/
|
||||||
|
├── ast_node.py # AST节点定义(参考32-137行)
|
||||||
|
├── graph_builder.py # 图构建入口
|
||||||
|
├── debug_graph_analyzer.py # BFS回溯(完整文件,约70行)
|
||||||
|
├── vcd_waveform_analyzer.py # VCD解析(参考89-285行)
|
||||||
|
└── waveform_trace_tool.py # 工具封装(完整文件,约180行)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 关键函数对照表
|
||||||
|
|
||||||
|
| Python函数 | 位置 | TS函数名建议 |
|
||||||
|
|------------|------|--------------|
|
||||||
|
| `get_k_control_signals()` | debug_graph_analyzer.py:20 | `getKControlSignals()` |
|
||||||
|
| `parse_mismatch()` | vcd_waveform_analyzer.py:244 | `parseMismatch()` |
|
||||||
|
| `get_tabular()` | vcd_waveform_analyzer.py:264 | `getTabular()` |
|
||||||
|
| `tabular_via_dataframe()` | vcd_waveform_analyzer.py:95 | `generateWaveformTable()` |
|
||||||
|
| `waveform_trace_tool()` | waveform_trace_tool.py:63 | `waveformTraceTool()` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、测试用例
|
||||||
|
|
||||||
|
### 6.1 BFS回溯测试
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 输入
|
||||||
|
const graph: ASTGraph = /* 加载 counter_ast_graph.json */;
|
||||||
|
const targetSignals = ['count'];
|
||||||
|
const k = 2;
|
||||||
|
|
||||||
|
// 期望输出
|
||||||
|
const expected = {
|
||||||
|
controlSignals: new Map([
|
||||||
|
['count', [6, 6]],
|
||||||
|
['next_count', [10, 10]],
|
||||||
|
['reset', [4, 4]],
|
||||||
|
['clk', [3, 3]],
|
||||||
|
['enable', [5, 5]]
|
||||||
|
]),
|
||||||
|
signalLevelTracer: [
|
||||||
|
['count->count'],
|
||||||
|
['next_count->count', 'reset->count', 'clk->count'],
|
||||||
|
['enable->next_count', 'count->next_count']
|
||||||
|
]
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 仿真输出解析测试
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 输入
|
||||||
|
const testOutput = `
|
||||||
|
Mismatches: 2
|
||||||
|
First mismatch occurred at time 100. Output 'count' expected 0001, got 0000
|
||||||
|
First mismatch occurred at time 150. Output 'overflow' expected 1, got 0
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 期望输出
|
||||||
|
const expected = {
|
||||||
|
signals: ['count', 'overflow'],
|
||||||
|
firstMismatchTime: 100
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、注意事项
|
||||||
|
|
||||||
|
1. **无第三方依赖要求**
|
||||||
|
- BFS回溯和仿真解析完全可以用原生TS实现
|
||||||
|
- VCD解析可以自己实现或找现有库
|
||||||
|
|
||||||
|
2. **性能考虑**
|
||||||
|
- 图遍历使用Map而非Object,提高查找效率
|
||||||
|
- VCD文件可能很大,考虑流式解析
|
||||||
|
|
||||||
|
3. **错误处理**
|
||||||
|
- 文件不存在时返回友好错误信息
|
||||||
|
- 信号不在图中时跳过而非报错
|
||||||
|
|
||||||
|
4. **兼容性**
|
||||||
|
- 信号名可能包含方括号,如 `count[7:0]`
|
||||||
|
- 时间单位统一为ns
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、交付物
|
||||||
|
|
||||||
|
1. `debugGraphAnalyzer.ts` - BFS回溯模块
|
||||||
|
2. `simulationParser.ts` - 仿真输出解析模块
|
||||||
|
3. `vcdParser.ts` - VCD波形解析模块
|
||||||
|
4. `waveformTraceTool.ts` - 工具整合封装
|
||||||
|
5. `types.ts` - 类型定义
|
||||||
|
6. 单元测试文件
|
||||||
1403
tools/waveform_trace/src/ast_node.py
Normal file
1403
tools/waveform_trace/src/ast_node.py
Normal file
File diff suppressed because it is too large
Load Diff
70
tools/waveform_trace/src/debug_graph_analyzer.py
Normal file
70
tools/waveform_trace/src/debug_graph_analyzer.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#
|
||||||
|
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# Author : Chia-Tung (Mark) Ho, NVIDIA
|
||||||
|
#
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import re
|
||||||
|
from collections import deque
|
||||||
|
from graph_builder import generate_top_logic_graph
|
||||||
|
|
||||||
|
# use class
|
||||||
|
class DebugGraph:
|
||||||
|
|
||||||
|
def __init__(self, verilog_filelist: list[str]):
|
||||||
|
self.filelist = verilog_filelist
|
||||||
|
self.graph = generate_top_logic_graph(verilog_filelist)
|
||||||
|
# print(list(self.graph.nodes(data=True)))
|
||||||
|
|
||||||
|
def get_k_control_signals(self, target_signals: list[str], k:int, signal_only: bool=False) -> list[str]:
|
||||||
|
|
||||||
|
control_signals = {}
|
||||||
|
signal_level_tracer = []
|
||||||
|
# queue
|
||||||
|
q = deque()
|
||||||
|
tmp_q = deque()
|
||||||
|
|
||||||
|
for signal in target_signals:
|
||||||
|
# store (predecessors, controlled signal)
|
||||||
|
q.append((signal, signal))
|
||||||
|
control_signals[signal] = self.graph.nodes[signal]['lines']
|
||||||
|
|
||||||
|
# BFS
|
||||||
|
for l in range (k + 1):
|
||||||
|
# traverse l layers
|
||||||
|
tmp_q.clear()
|
||||||
|
level_signal_control_rels = []
|
||||||
|
while len(q) > 0:
|
||||||
|
cur_signal = q.popleft()
|
||||||
|
level_signal_control_rels.append(cur_signal[0] + "->" + cur_signal[1])
|
||||||
|
if cur_signal[0] not in control_signals:
|
||||||
|
if self.graph.has_edge(cur_signal[0], cur_signal[1]):
|
||||||
|
# must be the control signals through the edge
|
||||||
|
control_signals[cur_signal[0]] = self.graph[cur_signal[0]][cur_signal[1]]['lines']
|
||||||
|
else:
|
||||||
|
print("[Error] Edge not found! - ", cur_signal)
|
||||||
|
# find the predecessors
|
||||||
|
controls = self.graph.predecessors(cur_signal[0])
|
||||||
|
for c in controls:
|
||||||
|
if c in control_signals:
|
||||||
|
continue
|
||||||
|
# exclude the parameter
|
||||||
|
if 'type' in self.graph.nodes[c] and self.graph.nodes[c]['type'] in ["Parameter", "Localparam"]:
|
||||||
|
continue
|
||||||
|
if signal_only and (re.match('^Always', c) or re.match('^Assign', c) or re.match('^Module', c) or re.match('^IntConst', c)):
|
||||||
|
continue
|
||||||
|
# store (predecessors, controlled signal)
|
||||||
|
tmp_q.append((c, cur_signal[0]))
|
||||||
|
# swap the q
|
||||||
|
assert(len(q) == 0)
|
||||||
|
print(tmp_q)
|
||||||
|
q = copy.deepcopy(tmp_q)
|
||||||
|
# record the signal relations
|
||||||
|
signal_level_tracer.append(level_signal_control_rels)
|
||||||
|
|
||||||
|
return control_signals, signal_level_tracer
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
debug_graph_tracer = DebugGraph(["/home/scratch.chiatungh_nvresearch/hardware-agent-marco/hardware_agent/examples/verilog_testcases/fsm_serialdata.v"])
|
||||||
|
print(debug_graph_tracer.get_k_control_signals(['out_byte', 'done'], k=3, signal_only=True))
|
||||||
144
tools/waveform_trace/src/graph_builder.py
Normal file
144
tools/waveform_trace/src/graph_builder.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
#
|
||||||
|
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# Author : Chia-Tung (Mark) Ho, NVIDIA
|
||||||
|
#
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
# 优先使用本地修改过的 pyverilog(包含 toplogic_tree_traverse 方法)
|
||||||
|
_local_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
sys.path.insert(0, _local_path)
|
||||||
|
|
||||||
|
from pyverilog.vparser.parser import parse
|
||||||
|
from io import StringIO
|
||||||
|
import networkx as nx
|
||||||
|
# importing matplotlib.pyplot
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import re
|
||||||
|
|
||||||
|
# create graph from ast str
|
||||||
|
# directed graph from networkX
|
||||||
|
def create_graph_from_ast(ast, display=False, display_signal_only=False):
|
||||||
|
graph = nx.DiGraph()
|
||||||
|
ast.toplogic_tree_traverse(network_G=graph, rvalue=False, lvalue=False)
|
||||||
|
if not display and not display_signal_only:
|
||||||
|
return graph
|
||||||
|
# Print out nodes with attributes
|
||||||
|
nodes_to_display = []
|
||||||
|
edges_to_display = []
|
||||||
|
print("Nodes:")
|
||||||
|
for node, attrs in graph.nodes(data=True):
|
||||||
|
if display_signal_only and (not re.match("^Assign", node) and not re.match("^Always", node) and not re.match("^Module", node)):
|
||||||
|
nodes_to_display.append(node)
|
||||||
|
print(f"Node {node}: {attrs}")
|
||||||
|
|
||||||
|
# Print out edges with attributes
|
||||||
|
print("\nEdges:")
|
||||||
|
for src, dst, attrs in graph.edges(data=True):
|
||||||
|
if display_signal_only and src in nodes_to_display and dst in nodes_to_display:
|
||||||
|
edges_to_display.append((src, dst))
|
||||||
|
print(f"Edge {src} to {dst}: {attrs}")
|
||||||
|
|
||||||
|
# displaying graphs
|
||||||
|
plt.figure(figsize=(18, 16)) # Set the figure size
|
||||||
|
pos = nx.spring_layout(graph, k=1.0)
|
||||||
|
if display_signal_only:
|
||||||
|
subgraph = graph.subgraph(nodes_to_display)
|
||||||
|
# subgraph.add_edges_from(edges_to_display)
|
||||||
|
else:
|
||||||
|
subgraph = graph
|
||||||
|
|
||||||
|
nx.draw_networkx(subgraph, pos, with_labels=True) # Draw the graph without labels
|
||||||
|
|
||||||
|
# Add node labels
|
||||||
|
# node_labels = nx.get_node_attributes(graph, 'label')
|
||||||
|
# nx.draw_networkx_labels(graph, pos, labels=node_labels)
|
||||||
|
|
||||||
|
# edge labels
|
||||||
|
edge_labels = nx.get_edge_attributes(subgraph, 'lines')
|
||||||
|
nx.draw_networkx_edge_labels(
|
||||||
|
subgraph, pos,
|
||||||
|
edge_labels=edge_labels,
|
||||||
|
font_color='blue'
|
||||||
|
)
|
||||||
|
# plt.axis('off')
|
||||||
|
plt.show()
|
||||||
|
return graph
|
||||||
|
|
||||||
|
def get_ast_structure_str(ast):
|
||||||
|
normal_stdout = sys.stdout
|
||||||
|
# put the string output to a string buffer
|
||||||
|
result = StringIO()
|
||||||
|
sys.stdout = result
|
||||||
|
|
||||||
|
# traverse the ast
|
||||||
|
ast.show(buf=sys.stdout)
|
||||||
|
|
||||||
|
# Redirect std output to the normal mode
|
||||||
|
sys.stdout = normal_stdout
|
||||||
|
|
||||||
|
# Get the result out
|
||||||
|
ast_str = result.getvalue()
|
||||||
|
# print('ast str = ', ast_str, '\n ast end')
|
||||||
|
return ast_str
|
||||||
|
|
||||||
|
def generate_top_logic_graph(filelist: list[str]):
|
||||||
|
for f in filelist:
|
||||||
|
if not os.path.exists(f):
|
||||||
|
raise IOError("file not found: " + f)
|
||||||
|
|
||||||
|
ast, directives = parse(filelist,
|
||||||
|
preprocess_include=[],
|
||||||
|
preprocess_define=[])
|
||||||
|
|
||||||
|
# ast_str = get_ast_structure_str(ast)
|
||||||
|
return create_graph_from_ast(ast, display=False, display_signal_only=False)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
INFO = "Verilog code parser"
|
||||||
|
VERSION = pyverilog.__version__
|
||||||
|
USAGE = "Usage: python example_parser.py file ..."
|
||||||
|
|
||||||
|
def showVersion():
|
||||||
|
print(INFO)
|
||||||
|
print(VERSION)
|
||||||
|
print(USAGE)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
optparser = OptionParser()
|
||||||
|
optparser.add_option("-v", "--version", action="store_true", dest="showversion",
|
||||||
|
default=False, help="Show the version")
|
||||||
|
optparser.add_option("-I", "--include", dest="include", action="append",
|
||||||
|
default=[], help="Include path")
|
||||||
|
optparser.add_option("-D", dest="define", action="append",
|
||||||
|
default=[], help="Macro Definition")
|
||||||
|
(options, args) = optparser.parse_args()
|
||||||
|
|
||||||
|
filelist = args
|
||||||
|
# print(filelist)
|
||||||
|
if options.showversion:
|
||||||
|
showVersion()
|
||||||
|
|
||||||
|
for f in filelist:
|
||||||
|
if not os.path.exists(f):
|
||||||
|
raise IOError("file not found: " + f)
|
||||||
|
|
||||||
|
if len(filelist) == 0:
|
||||||
|
showVersion()
|
||||||
|
|
||||||
|
ast, directives = parse(filelist,
|
||||||
|
preprocess_include=options.include,
|
||||||
|
preprocess_define=options.define)
|
||||||
|
|
||||||
|
# ast_str = get_ast_structure_str(ast)
|
||||||
|
create_graph_from_ast(ast, display_signal_only=True, display=True)
|
||||||
|
ast.show(attrnames=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
39241
tools/waveform_trace/src/parser.out
Normal file
39241
tools/waveform_trace/src/parser.out
Normal file
File diff suppressed because it is too large
Load Diff
443
tools/waveform_trace/src/parsetab.py
Normal file
443
tools/waveform_trace/src/parsetab.py
Normal file
File diff suppressed because one or more lines are too long
8
tools/waveform_trace/src/pyverilog/Makefile
Normal file
8
tools/waveform_trace/src/pyverilog/Makefile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
make clean -C ./utils
|
||||||
|
make clean -C ./vparser
|
||||||
|
make clean -C ./dataflow
|
||||||
|
make clean -C ./controlflow
|
||||||
|
make clean -C ./ast_code_generator
|
||||||
|
rm -rf *.pyc __pycache__ *.out parsetab.py *.html
|
||||||
1
tools/waveform_trace/src/pyverilog/VERSION
Normal file
1
tools/waveform_trace/src/pyverilog/VERSION
Normal file
@ -0,0 +1 @@
|
|||||||
|
1.3.0
|
||||||
7
tools/waveform_trace/src/pyverilog/__init__.py
Normal file
7
tools/waveform_trace/src/pyverilog/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
with open(os.path.join(os.path.dirname(__file__), "VERSION")) as f:
|
||||||
|
__version__ = f.read().splitlines()[0]
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -rf *.pyc __pycache__ parsetab.py *.out
|
||||||
1030
tools/waveform_trace/src/pyverilog/ast_code_generator/codegen.py
Normal file
1030
tools/waveform_trace/src/pyverilog/ast_code_generator/codegen.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,104 @@
|
|||||||
|
Source
|
||||||
|
Description
|
||||||
|
ModuleDef
|
||||||
|
Paramlist
|
||||||
|
Portlist
|
||||||
|
Port
|
||||||
|
Width
|
||||||
|
Length
|
||||||
|
Dimensions
|
||||||
|
Identifier
|
||||||
|
Value
|
||||||
|
Constant
|
||||||
|
IntConst
|
||||||
|
FloatConst
|
||||||
|
StringConst
|
||||||
|
Variable
|
||||||
|
Input
|
||||||
|
Output
|
||||||
|
Inout
|
||||||
|
Tri
|
||||||
|
Wire
|
||||||
|
Reg
|
||||||
|
Integer
|
||||||
|
Real
|
||||||
|
Genvar
|
||||||
|
Ioport
|
||||||
|
Parameter
|
||||||
|
Localparam
|
||||||
|
Decl
|
||||||
|
Concat
|
||||||
|
LConcat
|
||||||
|
Repeat
|
||||||
|
Partselect
|
||||||
|
Pointer
|
||||||
|
Lvalue
|
||||||
|
Rvalue
|
||||||
|
Operator
|
||||||
|
UnaryOperator
|
||||||
|
Uminus
|
||||||
|
Ulnot
|
||||||
|
Unot
|
||||||
|
Uand
|
||||||
|
Unand
|
||||||
|
Uor
|
||||||
|
Unor
|
||||||
|
Uxor
|
||||||
|
Uxnor
|
||||||
|
Power
|
||||||
|
Times
|
||||||
|
Divide
|
||||||
|
Mod
|
||||||
|
Plus
|
||||||
|
Minus
|
||||||
|
Sll
|
||||||
|
Srl
|
||||||
|
Sra
|
||||||
|
LessThan
|
||||||
|
GreaterThan
|
||||||
|
LessEq
|
||||||
|
GreaterEq
|
||||||
|
Eq
|
||||||
|
NotEq
|
||||||
|
Eql
|
||||||
|
NotEql
|
||||||
|
And
|
||||||
|
Xor
|
||||||
|
Xnor
|
||||||
|
Or
|
||||||
|
Land
|
||||||
|
Lor
|
||||||
|
Cond
|
||||||
|
Assign
|
||||||
|
Always
|
||||||
|
SensList
|
||||||
|
Sens
|
||||||
|
Substitution
|
||||||
|
BlockingSubstitution
|
||||||
|
NonblockingSubstitution
|
||||||
|
IfStatement
|
||||||
|
ForStatement
|
||||||
|
WhileStatement
|
||||||
|
CaseStatement
|
||||||
|
Case
|
||||||
|
Block
|
||||||
|
Initial
|
||||||
|
WaitStatement
|
||||||
|
ForeverStatement
|
||||||
|
DelayStatement
|
||||||
|
InstanceList
|
||||||
|
Instance
|
||||||
|
ParamArg
|
||||||
|
PortArg
|
||||||
|
Function
|
||||||
|
FunctionCall
|
||||||
|
Task
|
||||||
|
GenerateStatement
|
||||||
|
SystemCall
|
||||||
|
IdentifierScopeLabel
|
||||||
|
IdentifierScope
|
||||||
|
Pragma
|
||||||
|
PragmaEntry
|
||||||
|
Disable
|
||||||
|
ParallelBlock
|
||||||
|
SingleStatement
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
always @({{ sens_list }}) {{ statement }}
|
||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
assign {{ left }} = {{ right }};
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
begin{% if scope != '' %} : {{ scope }}{% endif %}
|
||||||
|
{%- for statement in statements %}
|
||||||
|
{{ statement }}
|
||||||
|
{%- endfor %}
|
||||||
|
end
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{% if ldelay != '' %}{{ ldelay }} {% endif %}{{ left }} = {% if rdelay != '' %}{{ rdelay }} {% endif %}{{ right }};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{{ cond }}: {{ statement }}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
case({{ comp }})
|
||||||
|
{%- for case in caselist %}
|
||||||
|
{{ case }}
|
||||||
|
{%- endfor %}
|
||||||
|
endcase
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
casex({{ comp }})
|
||||||
|
{%- for case in caselist %}
|
||||||
|
{{ case }}
|
||||||
|
{%- endfor %}
|
||||||
|
endcase
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{ {% for item in items %}{{ item }}{% if loop.index < len_items %}, {% endif %}{% endfor %} }
|
||||||
@ -0,0 +1 @@
|
|||||||
|
(({{ cond }})? {{ true_value }} : {{ false_value }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{{ value }}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
{%- for item in items %}{{ item }}
|
||||||
|
{%- endfor %}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
#{{ delay }}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{% for definition in definitions %}
|
||||||
|
{{ definition }}
|
||||||
|
{% endfor %}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
diable {{ name }}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
@({{ senslist }});
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{{ value }}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
forever {{ statement }}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
for({{ pre }} {{ cond }}; {{ post }}) {{ statement }}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
function {{ retwidth }} {{ name }};
|
||||||
|
{%- for s in statement %}
|
||||||
|
{{ s }}
|
||||||
|
{%- endfor %}
|
||||||
|
endfunction
|
||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{{ name }}({% for arg in args %}{{ arg }}{% if loop.index < len_args %}, {% endif %}{% endfor %})
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
generate {% for item in items %}{{ item }}{% endfor %}
|
||||||
|
endgenerate
|
||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
genvar {{ name }};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{{ scope }}{{ name }}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{% for scope in scopes %}{{ scope }}{% endfor %}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{{ name }}{%- if loop != '' %}[{{ loop }}]{%- endif %}.
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
if({{ cond }}) {{ true_statement }}
|
||||||
|
{%- if true_statement[-1] != ' ' and true_statement[-1] != '\n' %} {% endif -%}
|
||||||
|
{%- if true_statement.count('\n') == 0 and false_statement != '' %}
|
||||||
|
{% endif -%}
|
||||||
|
{%- if false_statement != '' %}else {{ false_statement }}{% endif -%}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
initial {{ statement }}
|
||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
inout {% if signed %}signed {% endif %}{% if width != '' %}{{ width }} {% endif %}{{ name }}{% if dimensions != '' %} {{ dimensions }}{% endif %};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
input {% if signed %}signed {% endif %}{% if width != '' %}{{ width }} {% endif %}{{ name }}{% if dimensions != '' %} {{ dimensions }}{% endif %};
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{{ name }}{{ array }}
|
||||||
|
({% for port in portlist %}
|
||||||
|
{{ port }}{%- if loop.index < len_portlist -%}, {%- endif -%}
|
||||||
|
{% endfor %}
|
||||||
|
)
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
{{ module }}
|
||||||
|
{%- if len_parameterlist > 0 %}
|
||||||
|
#({% for param in parameterlist %}
|
||||||
|
{{ param }}{%- if loop.index < len_parameterlist -%},
|
||||||
|
{%- endif -%}{% endfor %}
|
||||||
|
)
|
||||||
|
{%- endif %}
|
||||||
|
{%- for instance in instances %}
|
||||||
|
{{ instance }}{%- if loop.index < len_instances -%},
|
||||||
|
{%- endif -%}{%- endfor -%};
|
||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{{ value }}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
integer {{ name }};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{{ first }} {% if second != '' %}{{ second }} {% endif %}{% if signed %}signed {% endif %}{% if width != '' %}{{ width }} {% endif %}{{ name }}{% if dimensions != '' %} {{ dimensions }}{% endif %}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{ {% for item in items %}{{ item }}{% if loop.index < len_items %}, {% endif %}{% endfor %} }
|
||||||
@ -0,0 +1 @@
|
|||||||
|
[{{ msb }}:{{ lsb }}]
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
localparam {% if signed %}signed {% endif %}{% if width != '' %}{{ width }} {% endif %}{{ name }} = {{ value }};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{{ var }}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
module {{ modulename }}{% if paramlist != '' %} #
|
||||||
|
(
|
||||||
|
{{ paramlist }}
|
||||||
|
)
|
||||||
|
{%- endif %}
|
||||||
|
(
|
||||||
|
{{ portlist }}
|
||||||
|
);
|
||||||
|
|
||||||
|
{% for item in items %}{{ item }}
|
||||||
|
{% endfor %}
|
||||||
|
endmodule
|
||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{% if ldelay != '' %}{{ ldelay }} {% endif %}{{ left }} <= {% if rdelay != '' %}{{ rdelay }} {% endif %}{{ right }};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
output {% if signed %}signed {% endif %}{% if width != '' %}{{ width }} {% endif %}{{ name }}{% if dimensions != '' %} {{ dimensions }}{% endif %};
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
fork{% if scope != '' %} : {{ scope }}{% endif %}
|
||||||
|
{%- for statement in statements %}
|
||||||
|
{{ statement }}
|
||||||
|
{%- endfor %}
|
||||||
|
join
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{%- if paramname != '' -%}.{{ paramname }}({{ argname }}){%- else -%}{{ argname }}{%- endif -%}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
parameter {% if signed %}signed {% endif %}{% if width != '' %}{{ width }} {% endif %}{{ name }} = {{ value }};
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
{% for param in params %}{{ param }}{% if loop.index < len_params %},
|
||||||
|
{% endif %}{% endfor %}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{{ var }}[{{ msb }}:{{ lsb }}]
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{{ var }}[{{ ptr }}]
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{{ name }}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{%- if portname != '' -%}.{{ portname }}({{ argname }}){%- else -%}{{ argname }}{%- endif -%}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
{% for port in ports %}{{ port }}{% if loop.index < len_ports %},
|
||||||
|
{% endif %}{% endfor %}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
@ -0,0 +1 @@
|
|||||||
|
(* {{ entry }} *)
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{{ name }}{% if value != '' %} = {{ value }}{% endif %}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
real {{ name }};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
reg {% if signed %}signed {% endif %}{% if width != '' %}{{ width }} {% endif %}{{ name }}{% if dimensions != '' %} {{ dimensions }}{% endif %};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{ {{ times }}{{ value }} }
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{{ var }}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{% if type != '' %}{{ type }} {% endif %}{{ sig }}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{% for item in items %}{{ item }}{% if loop.index < len_items %} or {% endif %}{% endfor %}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{{ statement }};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
({{ left }} {{ op }} {{ right }})
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user