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_r、data_buf_r、data_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 的路径时,优先从布线、扇出和控制信号本地化入手,而不是只盯着组合逻辑级数。

浙公网安备 33010602011771号