fpga_ila 软逻辑分析仪调试指南

什么是 fpga_ila?

fpga_ila 是一个纯 Verilog 实现的跨厂商软逻辑分析仪,位于 /home/user/work/fpga_ila。与厂商特定的 IP 核(Xilinx ILA、Altera SignalTap)不同,fpga_ila 不使用任何专有原语。它能综合到任何支持标准 Block RAM 和自由运行时钟的 FPGA 上。该项目提供:

架构


┌──────────────────────────────────────────────────────┐
│                   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(位于 rtl/ila_hub_top.v):

debug-ila 技能工作流

debug-ila 技能可自动化整个插桩过程。以下是完整工作流:

第一步:定义信号 (signals.json)

创建一个 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

该技能执行以下操作:

第三步:综合


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 触发调试方法论

单 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

检查清单:单 bit 触发未触发


   grep "trigger_bit" build/ila_instrumentation.log
   

   // 防优化:以一种综合器无法移除的方式使用信号
   (* DONT_TOUCH = "TRUE" *) wire [N-1:0] probe_sig;
   assign probe_sig = {signal1, signal2, ...};
   

   "trigger": {
       "signal": "awvalid",
       "type": "posedge",
       "bit": 0
   }
   

   verilator --lint-only -I rtl/ rtl/fpga_top.v 2>&1 | grep -i "not found"
   

已排除的原因(已验证)

根据之前的调试记录,以下已被排除为单 bit 触发失败的原因:

插桩日志分析

插桩日志(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)

如果触发未生效,检查以下日志条目:


   cat rtl/ila_wrapper_axi_write_master.v | grep "awvalid"
   

soft_ila_top + ila_hub_top 集成模式

最小化集成示例

这是 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 配置

同时探测多个模块时:


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)
);

故障排查:常见问题

问题:"触发后 UART 未收到任何数据"

原因与解决方法:

   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]
   

   ls -la /dev/serial/by-id/
   dmesg | grep -i "usb.*tty"
   

问题:"复位后立即触发"

原因: 复位后探测数据总线全为零,电平 0 触发默认为真。解决方案: 改用上升沿触发,或在 ILA 包装模块中添加触发使能延迟计数器。

问题:"捕获开始时丢失采样点"

原因: ILA 缓冲区是环形的,但 capture.py 在 FPGA 烧录之后才开始监听。解决方案: 使用触发位置功能捕获预触发采样点。在 signals.json 中将 trigger_position 设置为 0.5(50% 预触发,50% 后触发):

"trigger": {
    "signal": "awvalid",
    "type": "posedge",
    "position": 0.5
}

问题:"综合失败:未找到端口"

原因: signals.json 中的信号名称与 RTL 端口或内部线网名称不匹配。解决方案: 使用 verilator --lintiverilog 检查:

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

关键选项:

run_all.sh 自检

/home/user/work/fpga_ila/run_all.sh 脚本运行端到端验证:

#!/bin/bash
cd /home/user/work/fpga_ila
./run_all.sh

它执行以下操作:

run_all.sh 通过意味着 ILA 核心逻辑在任何 FPGA 综合之前是正确的。如果 run_all.sh 通过但硬件捕获失败,则问题出在时序约束、引脚分配或信号连接上(而非 ILA 逻辑本身)。