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
相关产品推荐
相关产品推荐

