FPGA 时序问题记录:高扇出复位导致 Setup Violation

1. 问题现象

在 Vivado Timing Report 中看到 setup 违例:

Slack      : 负值
Levels     : 1
Logic Delay: 很小
Net Delay  : 很大
Fanout     : 很高

典型路径类似:

同步复位寄存器
    -> 全局/模块复位信号
    -> 大量寄存器的 R 端

这种路径的特点是:逻辑级数很少,但布线延迟很大
说明问题主要不是组合逻辑复杂,而是复位信号扇出太大、跨区域布线太长。


2. 问题原因

很多设计中会把一个同步后的复位信号直接分发给整个模块:

always @(posedge clk_i) begin
    if (rst_i) begin
        data_cnt_r   <= 16'd0;
        data_buf_r   <= 32'd0;
        pixel_data_r <= 24'd0;
        valid_o      <= 1'b0;
        done_o       <= 1'b0;
    end else begin
        // 正常逻辑
    end
end

如果模块内部寄存器很多,rst_i 会驱动大量寄存器的复位端:

rst_i -> reg0/R
rst_i -> reg1/R
rst_i -> reg2/R
...

这样会导致:

Fanout 高
Net Delay 高
Setup 违例

高速时钟下尤其明显,例如 250MHz 时钟周期只有 4ns,复位网络布线稍微长一点就容易不收敛。


3. 解决思路

核心思路是:

减少全局复位扇出
复位信号本地化
数据寄存器尽量不接全局 reset

方法一:在子模块内部复制本地复位

不要让顶层同步复位直接驱动整个大模块。
可以在模块内部按功能区域复制几份本地复位。

reg rst_core_r;      // 核心逻辑复位
reg rst_send_r;      // 发送逻辑复位
reg rst_calc_r;      // 计算逻辑复位
reg rst_mem_r;       // 存储逻辑复位

always @(posedge clk_i) begin
    rst_core_r <= rst_i;
    rst_send_r <= rst_core_r;
    rst_calc_r <= rst_core_r;
    rst_mem_r  <= rst_core_r;
end

改完后路径从:

rst_i -> 几百个寄存器

变成:

rst_i -> 几个本地复位寄存器
本地复位寄存器 -> 各自区域寄存器

这样可以明显降低单个复位信号的扇出和布线压力。


方法二:大量数据寄存器不接全局复位

并不是所有寄存器都必须复位。
状态机、valid、done、last 这类控制信号建议复位;
数据缓存、计数器、中间计算结果,可以在空闲状态初始化。

修改前:

always @(posedge clk_i) begin
    if (rst_i) begin
        data_cnt_r <= 16'd0;
        data_buf_r <= 32'd0;
        data_o     <= 8'd0;
        valid_o    <= 1'b0;
    end else begin
        // 正常逻辑
    end
end

修改后:

always @(posedge clk_i) begin
    if (rst_i) begin
        valid_o <= 1'b0;
    end else begin
        case (state_r)
            ST_IDLE: begin
                data_cnt_r <= 16'd0;
                data_buf_r <= 32'd0;
                data_o     <= 8'd0;
                valid_o    <= 1'b0;
            end

            ST_WORK: begin
                // 正常逻辑
            end

            default: begin
                valid_o <= 1'b0;
            end
        endcase
    end
end

这样 data_cnt_rdata_buf_rdata_o 不再由全局复位直接控制,复位扇出会下降。


方法三:按区域拆分复位

如果模块较大,可以按功能拆成几类:

控制状态机复位
发送通路复位
接收通路复位
缓存/RAM控制复位
计算通路复位

示例:

reg rst_ctrl_r;      // 控制区复位
reg rst_tx_r;        // 发送区复位
reg rst_rx_r;        // 接收区复位

always @(posedge clk_i) begin
    rst_ctrl_r <= rst_i;
    rst_tx_r   <= rst_i;
    rst_rx_r   <= rst_i;
end

不同逻辑使用不同的本地复位:

always @(posedge clk_i) begin
    if (rst_ctrl_r) begin
        state_r <= ST_IDLE;
    end else begin
        state_r <= state_next;
    end
end
always @(posedge clk_i) begin
    if (rst_tx_r) begin
        tx_valid_o <= 1'b0;
    end else begin
        // 发送逻辑
    end
end

这样比一个 rst_i 控制全模块更容易收敛。


4. 哪些寄存器建议保留 reset

建议保留全局或局部 reset 的:

状态机 state
valid
ready 控制寄存器
done 脉冲
last 脉冲
中断标志
关键配置状态
跨模块握手控制

这些信号上电乱跳可能会触发错误行为,所以建议复位到安全状态。


5. 哪些寄存器可以放到 IDLE 初始化

可以考虑不接全局 reset 的:

数据缓存寄存器
像素/采样数据寄存器
普通计数器
地址计数器
行列号计数器
中间计算结果
临时 buffer

这些信号只要在状态机开始工作前被初始化即可,不一定需要全局 reset。


6. 判断这类问题的方法

看到 timing report 中:

Levels 很少
Logic Delay 很小
Net Delay 很大
Fanout 很高

基本可以优先怀疑:

复位信号高扇出
控制信号高扇出
远距离跨模块控制

如果路径终点是寄存器的:

R 端
CE 端

更要注意,因为这类控制端容易形成高扇出控制网络。


7. 不建议直接 false path

如果 source clock 和 destination clock 是同一个时钟,这就是实际同步路径,不能随便加:

set_false_path

否则只是把问题隐藏了,并没有真正解决时序。

更推荐优先修改 RTL:

本地复位
复位复制
减少 reset 寄存器数量
拆分控制信号扇出

8. 总结

这类问题可以概括为:

高速时钟 + 高扇出复位/控制信号 + 跨区域布线 = setup 违例

解决原则:

1. 复位信号进入大模块后先本地化
2. 按功能区域复制复位
3. 数据通路寄存器尽量不接全局 reset
4. 状态机和关键控制信号保留 reset
5. 不要优先使用 false path 掩盖问题

工程中遇到 Net Delay 明显大于 Logic Delay 的路径时,优先从布线、扇出和控制信号本地化入手,而不是只盯着组合逻辑级数。

posted @ 2026-06-29 13:56  Oppenic  阅读(39)  评论(0)    收藏  举报