You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Verilog仿真、ILA调试正常但上板FPGA运行异常排查

问题根因

加ILA正常、移除ILA后功能异常是FPGA开发中非常典型的代码不规范导致的未定义行为:ILA插入时会给探测信号增加额外寄存器打拍、布线延迟,刚好掩盖了逻辑竞争和综合优化带来的问题,移除ILA后问题就会暴露。你的代码存在三处明确的写法错误:

  • 时序逻辑写法严重不规范:uart_send模块中,你在同一个时钟沿触发的always块里,对bit_cnt、baud_cnt两个寄存器做了多次独立的非阻塞赋值。Verilog语法中,同一always块内同一时钟沿对同一寄存器的多次非阻塞赋值,只有最后一次赋值会生效,前面的逻辑会被综合器直接优化掉,直接导致波特率计数、数据位计数的运行逻辑完全偏离设计预期。
  • 控制信号存在竞争冒险:顶层模块中send_flag的拉高、拉低逻辑写在同一个always块里,拉低条件依赖的tx_done是子模块反馈的信号,没有做同步打拍处理。没有ILA带来的额外布线延迟时,两个赋值路径的延迟差会导致send_flag刚拉高就被误拉低,直接打断UART发送流程,把tx信号锁死在低电平。
  • 发送状态机缺少状态锁存:计数逻辑没有和发送请求做绑定,只要时钟沿到来,不管有没有发送请求,baud_cnt和bit_cnt都会持续累加,等到真正触发发送时,bit计数器的数值是随机的,会直接跳转到错误的发送状态。

修复方案

遵循“一个寄存器只在一个always块内赋值”的Verilog编码规范,新增发送使能锁存逻辑、对反馈信号做打拍同步消除竞争,修正后的完整代码如下:

module uart_send(
    input clk,
    input send_flag,
    input reset,
    input [7:0]data,
    input [2:0]baud_set,
    output reg tx_done,
    output reg tx
    );
    
    parameter CLK_FREQ = 50000000;
    
    reg [15:0] baud_cnt;
    reg [15:0] baud;
    reg [3:0] bit_cnt;
    reg send_en; // 锁存发送状态,避免send_flag提前撤销打断发送

    // 波特率选择组合逻辑
    always@(*)
    begin
        case(baud_set)
            0: baud = CLK_FREQ/9600;
            1: baud = CLK_FREQ/115200;
            default: baud = CLK_FREQ/115200;
        endcase        
    end
    
    // 发送使能锁存逻辑
    always@(posedge clk or negedge reset) begin
        if(!reset)
            send_en <= 1'b0;
        else if(send_flag)
            send_en <= 1'b1;
        else if(bit_cnt == 4'd10 && baud_cnt == baud-1)
            send_en <= 1'b0;
    end

    // 波特率、数据位计数逻辑
    always@(posedge clk or negedge reset) begin   
        if(!reset) begin
            baud_cnt <= 16'd0;
            bit_cnt <= 4'd0;
        end
        else if(send_en) begin
            if(baud_cnt == baud-1) begin
                baud_cnt <= 16'd0;
                bit_cnt <= (bit_cnt == 4'd10) ? 4'd0 : bit_cnt + 1'b1;
            end
            else
                baud_cnt <= baud_cnt + 1'b1;
        end
        else begin
            baud_cnt <= 16'd0;
            bit_cnt <= 4'd0;
        end
    end

    // 数据移位、发送完成标志逻辑
    always@(posedge clk or negedge reset) begin
        if(!reset) begin
            tx <= 1'b1;
            tx_done <= 1'b0;
        end
        else if(send_en) begin
            tx_done <= 1'b0;
            case(bit_cnt)
                0: tx <= 1'b0; // 起始位
                1: tx <= data[0];
                2: tx <= data[1];
                3: tx <= data[2];
                4: tx <= data[3];
                5: tx <= data[4];
                6: tx <= data[5];
                7: tx <= data[6];
                8: tx <= data[7];
                9: tx <= 1'b1; // 停止位
                10: begin
                    tx <= 1'b1;
                    tx_done <= 1'b1;
                end
                default: tx <= 1'b1;
            endcase
        end
        else begin
            tx <= 1'b1;
            tx_done <= 1'b0;
        end
    end
endmodule

module uart_tx_test(
    input clk,
    input reset,
    output data_out,
    output reg led
    );
    
    reg send_flag;
    reg [7:0] data;
    wire tx_done;
    
    reg [19:0] div_cnt;
    reg [23:0] led_cnt;
    reg tx_done_d0; // 打拍做边沿检测,消除组合逻辑竞争
    
    uart_send u1(
        .clk(clk),
        .send_flag(send_flag),
        .reset(reset),
        .baud_set(3'b001),
        .data(data),
        .tx(data_out),
        .tx_done(tx_done)
    );
    
    always@(posedge clk or negedge reset) begin
        if(!reset) begin
            div_cnt <= 20'd0;
            led_cnt <= 24'd0;
            send_flag <= 1'b0;
            data <= 8'd0;
            led <= 1'b0;
            tx_done_d0 <= 1'b0;
        end
        else begin
            tx_done_d0 <= tx_done;
            // 检测tx_done上升沿拉低send_flag,避免竞争
            if(tx_done && !tx_done_d0) begin
                send_flag <= 1'b0;
            end
            // 定时触发发送
            if(div_cnt == 20'd499999) begin
                div_cnt <= 20'd0;
                data <= data + 1'b1;
                send_flag <= 1'b1;
            end
            else begin
                div_cnt <= div_cnt + 1'b1;
            end    
            // LED运行指示逻辑
            if(led_cnt == 24'd9999999) begin
                led_cnt <= 24'd0;
                led <= ~led;
            end  
            else begin
                led_cnt <= led_cnt + 1'b1;
            end    
        end
    end
endmodule

为什么插入ILA时功能正常

ILA IP核工作时,会把所有被探测的信号标记为“不可优化”,同时给这些信号增加额外的触发器打拍、插入固定的布线延迟:

  • 原本会被综合器优化掉的重复赋值逻辑,因为信号被ILA设置了保留属性,没有被删除
  • 原本存在竞争的send_flag信号,因为路径延迟增加,刚好撑过了状态机的锁存窗口,不会被误拉低
    相当于ILA无意中给问题代码打了“补丁”,但代码本身的逻辑问题始终存在,这类“调试器依赖”问题本质都是编码不规范导致的,和芯片、工具版本无关。

内容的提问来源于stack exchange,提问作者SONGYJ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 13:01:20