fpga_ila 是一个纯 Verilog 实现的跨厂商软逻辑分析仪,位于 /home/user/work/fpga_ila。与厂商特定的 IP 核(Xilinx ILA、Altera SignalTap)不同,fpga_ila 不使用任何专有原语。它能综合到任何支持标准 Block RAM 和自由运行时钟的 FPGA 上。该项目提供:
soft_ila_top 模块(触发逻辑 + 采样缓冲区)ila_hub_top 模块(多 ILA 聚合与数据读出)debug-ila 技能,一键完成调试插桩run_all.sh 自检回归测试
┌──────────────────────────────────────────────────────┐
│ FPGA 内部 │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 用户 RTL │ │ 用户 RTL │ │ 用户 RTL │ │
│ │ (DUT) │ │ 模块 2 │ │ 模块 3 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ 探测 │ 探测 │ 探测 │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │soft_ila │ │soft_ila │ │soft_ila │ │
│ │_top #0 │ │_top #1 │ │_top #2 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └───────────────┬───────────────┘ │
│ │ trigger_out / data │
│ ▼ │
│ ┌─────────────────┐ │
│ │ ila_hub_top │ │
│ │ (聚合器 + │ │
│ │ 读出状态机) │ │
│ └────────┬────────┘ │
│ │ UART / JTAG / AXIS │
└───────────────────────┼───────────────────────────────┘
│
▼
┌─────────────────┐
│ 主机 PC │
│ (capture.py / │
│ GTKWave) │
└─────────────────┘
soft_ila_top(位于 rtl/soft_ila_top.v):
ila_hub_top 输出 triggered 标志和采样数据总线ila_hub_top(位于 rtl/ila_hub_top.v):
soft_ila_top 实例debug-ila 技能可自动化整个插桩过程。以下是完整工作流:
创建一个 JSON 文件描述要观察的信号:
{
"module": "axi_write_master",
"source_file": "rtl/axi_write_master.v",
"top_module": "fpga_top",
"top_file": "rtl/fpga_top.v",
"num_samples": 2048,
"signals": [
{"name": "awvalid", "width": 1},
{"name": "awready", "width": 1},
{"name": "awaddr", "width": 32},
{"name": "wdata", "width": 64},
{"name": "wstrb", "width": 8},
{"name": "wlast", "width": 1},
{"name": "bvalid", "width": 1},
{"name": "bready", "width": 1},
{"name": "bresp", "width": 2},
{"name": "state", "width": 4}
]
}
cd /home/user/work/FPGA_Prj/Project/WebServer
debug-ila --signals signals.json
该技能执行以下操作:
signals.json,计算总探测位宽和 ILA 参数fpga_top.v)中实例化 ila_hub_topila_hub_top 的输入端口build/ila_instrumentation.log
cd /home/user/work/FPGA_Prj/Project/WebServer
source /home/user/tools/scripts/vivado_env.sh
vivado -mode batch -source build.tcl
# 烧录比特流
vivado -mode batch -source program.tcl
# 在主机 PC 上启动捕获
cd /home/user/work/fpga_ila/sw
python3 capture.py --port /dev/ttyUSB0 --baud 3000000 --samples 2048 \
--output capture.fst --signals signals.json
gtkwave capture.fst
单 bit 触发是 fpga_ila 中最容易出问题的部分。以下是系统性的调试方法。
// soft_ila_top.v 内部
always @(posedge clk) begin
if (!triggered) begin
// 将采样数据写入环形缓冲区
sample_buf[write_ptr] <= probe_data;
write_ptr <= write_ptr + 1;
// 触发检测:单 bit 边沿/电平
case (trigger_type)
2'b00: hit <= (probe_data[trigger_bit] == trigger_level); // 电平触发
2'b01: hit <= (probe_data[trigger_bit] && !probe_data_prev[trigger_bit]); // 上升沿
2'b10: hit <= (!probe_data[trigger_bit] && probe_data_prev[trigger_bit]); // 下降沿
2'b11: hit <= (probe_data[trigger_bit] != probe_data_prev[trigger_bit]); // 任意边沿
endcase
if (hit) begin
triggered <= 1;
samples_after_trigger <= 0;
end
end
// ... 触发后捕获逻辑
end
trigger_bit 索引从零开始。如果信号在 bit 位置 0 但选择了索引 5,则触发观察的是错误的 bit。验证方法:
grep "trigger_bit" build/ila_instrumentation.log
clk 上采样。如果触发信号在另一个时钟域(例如 clk_200M 而 ILA 使用 clk_50M),则会丢失窄脉冲。解决方案:同步触发信号,或在另一个时钟上实例化第二个 ILA。
// 防优化:以一种综合器无法移除的方式使用信号
(* DONT_TOUCH = "TRUE" *) wire [N-1:0] probe_sig;
assign probe_sig = {signal1, signal2, ...};
2'b00)。如果需要边沿触发,更新 signals.json:
"trigger": {
"signal": "awvalid",
"type": "posedge",
"bit": 0
}
.)在 signals.json 和实际 RTL 层次结构之间必须匹配。使用 verilator --lint 确认:
verilator --lint-only -I rtl/ rtl/fpga_top.v 2>&1 | grep -i "not found"
根据之前的调试记录,以下已被排除为单 bit 触发失败的原因:
clk 属于同一时钟域)插桩日志(build/ila_instrumentation.log)包含关键的调试信息。需要检查的关键部分:
=== ILA 插桩报告 ===
日期: 2026-07-24 15:30:00
模块: axi_write_master
信号映射:
[0] awvalid (1 位)
[1] awready (1 位)
[33:2] awaddr (32 位)
[97:34] wdata (64 位)
[105:98] wstrb (8 位)
... (继续)
总探测位宽: 114 位
ILA 实例数: 1
触发: bit[0] (awvalid) 类型=posedge
生成的文件:
- rtl/ila_wrapper_axi_write_master.v
- rtl/fpga_top.v (已修改: 添加了 ila_hub_top)
如果触发未生效,检查以下日志条目:
awvalid 列为 [0] 但你指定了 trigger_bit: 1,ILA 将观察错误的线。DATA_WIDTH 参数,高位将被静默截断。soft_ila_top 的默认位宽为 128;更宽的探测需要调整参数。rtl/ila_wrapper_*.v 确认探测线已连接:
cat rtl/ila_wrapper_axi_write_master.v | grep "awvalid"
这是 debug-ila 技能遵循的模式。你也可以手动操作以获得完全控制:
// 在顶层模块中(例如 fpga_top.v)
// 1. 包含 ILA 库文件
`include "rtl/soft_ila_top.v"
`include "rtl/ila_hub_top.v"
module fpga_top (
input wire clk,
input wire rst_n,
// ... 用户端口
);
// 2. 声明 ILA 探测线
wire [113:0] ila_probe_0;
wire ila_trigger_0;
// 3. 实例化 ILA 包装模块(自动生成)
ila_wrapper_axi_write_master u_ila_wrapper_0 (
.clk (clk),
.rst_n (rst_n),
.awvalid (u_axi_master.awvalid), // 层次化访问
.awready (u_axi_master.awready),
.awaddr (u_axi_master.awaddr),
.wdata (u_axi_master.wdata),
.wstrb (u_axi_master.wstrb),
.wlast (u_axi_master.wlast),
.bvalid (u_axi_master.bvalid),
.bready (u_axi_master.bready),
.bresp (u_axi_master.bresp),
.state (u_axi_master.state),
// ILA 输出
.ila_data (ila_probe_0),
.ila_trigger (ila_trigger_0)
);
// 4. 实例化 Hub
ila_hub_top #(
.NUM_ILAS (1),
.UART_BAUD (3000000),
.CLK_FREQ_HZ (50_000_000)
) u_ila_hub (
.clk (clk),
.rst_n (rst_n),
.ila_data_0 (ila_probe_0),
.ila_trigger_0(ila_trigger_0),
// 未用端口接地(仅使用 1 个 ILA)
.ila_data_1 (115'd0),
.ila_trigger_1(1'b0),
// ... 为 2..15 重复
.uart_tx (o_ila_uart_tx)
);
endmodule
同时探测多个模块时:
ila_hub_top #(
.NUM_ILAS(3) // 三个 ILA:AXI 主设备、AXI 从设备、DDR 控制器
) u_ila_hub (
.clk (clk),
.rst_n (rst_n),
.ila_data_0 (ila_probe_axi_master),
.ila_trigger_0 (ila_trigger_axi_master),
.ila_data_1 (ila_probe_axi_slave),
.ila_trigger_1 (ila_trigger_axi_slave),
.ila_data_2 (ila_probe_ddr_ctrl),
.ila_trigger_2 (ila_trigger_ddr_ctrl),
// 其余端口接地
// ...
.uart_tx (o_ila_uart_tx)
);
ila_hub_top 中的波特率参数与 capture.py --baud 一致:
ila_hub_top #(.UART_BAUD(3000000)) // 必须与 capture.py --baud 3000000 匹配
set_property PACKAGE_PIN Y9 [get_ports o_ila_uart_tx]
set_property IOSTANDARD LVCMOS33 [get_ports o_ila_uart_tx]
/dev/ttyUSB* 设备错误。 列出 USB 串口设备:
ls -la /dev/serial/by-id/
dmesg | grep -i "usb.*tty"
capture.py 在 FPGA 烧录之后才开始监听。解决方案: 使用触发位置功能捕获预触发采样点。在 signals.json 中将 trigger_position 设置为 0.5(50% 预触发,50% 后触发):
"trigger": {
"signal": "awvalid",
"type": "posedge",
"position": 0.5
}
signals.json 中的信号名称与 RTL 端口或内部线网名称不匹配。解决方案: 使用 verilator --lint 或 iverilog 检查:
iverilog -g2012 -I rtl/ -o /dev/null rtl/fpga_top.v 2>&1 | head -20
capture.py 脚本读取 UART 数据并将其转换为 FST 波形:
python3 capture.py \
--port /dev/ttyUSB0 \
--baud 3000000 \
--samples 2048 \
--output capture.fst \
--signals signals.json \
--timeout 30
关键选项:
--port:串口设备路径--baud:必须与 FPGA 中的 UART_BAUD 参数匹配--samples:要捕获的采样点数量(<= ILA 缓冲区深度)--output:输出波形文件(支持 .fst、.vcd)--signals:插桩时使用的 signals.json 路径--timeout:等待触发的超时秒数/home/user/work/fpga_ila/run_all.sh 脚本运行端到端验证:
#!/bin/bash
cd /home/user/work/fpga_ila
./run_all.sh
它执行以下操作:
soft_ila_top 进行独立仿真ila_hub_top 进行仿真run_all.sh 通过意味着 ILA 核心逻辑在任何 FPGA 综合之前是正确的。如果 run_all.sh 通过但硬件捕获失败,则问题出在时序约束、引脚分配或信号连接上(而非 ILA 逻辑本身)。