FPGA 时序问题记录:远端控制信号导致 Setup Violation
1. 问题现象
Vivado Timing Report 中出现 setup 违例,例如:
Slack : -0.293ns
Levels : 1
Fanout : 7
Logic Delay: 0.281ns
Net Delay : 1.565ns
路径类似:
u_FR32_CTRL/LCK_reg/C
-> LUT
-> u_FR32_CAPTURE/src2_invalid_reg/R
或者:
xpm_cdc_array_single/syncstages_ff_reg[x][y]
-> 某些寄存器 CE/R
这类路径的特点是:
逻辑级数不深,但是 Net Delay 很大
说明问题主要不是组合逻辑复杂,而是控制信号从一个模块跨层级、跨区域拉到另一个模块,布线太长,导致时序不收敛。
2. 问题类型总结
类型一:远端控制信号直接控制 R / CE
典型写法:
always @(posedge CLK_REF) begin
if (!RST_N) begin
src2_invalid <= 16'd0;
end else if (LCK) begin
src2_invalid <= dr_invalid_rs[15:0];
end else begin
src2_invalid <= 16'd0;
end
end
综合后可能变成:
LCK -> src2_invalid_reg/R
或者:
LCK -> src2_invalid_reg/CE
如果 LCK 来自很远的模块,路径就容易炸。
类型二:CDC 输出直接大扇出
例如:
xpm_cdc_array_single 输出
-> 多个模块 / 多个寄存器 CE
如果 CDC 后的信号直接扇出到很多地方,容易出现:
Fanout 高
Net Delay 高
Slack 负
尤其是 250MHz、300MHz 这种高速时钟域,2ns~4ns 周期下布线延迟非常敏感。
类型三:跨模块控制信号直接参与高速逻辑
例如:
FR32_CTRL 产生 LCK
FR32_CAPTURE 使用 LCK
两个模块虽然是同一个时钟,但物理位置可能很远。
如果 LCK 直接参与 FR32_CAPTURE 内部寄存器控制,就会形成长路径。
3. 解决方法
方法一:控制信号本地打一拍
把远端控制信号先在目标模块内寄存一次。
修改前:
always @(posedge CLK_REF) begin
if (!RST_N) begin
src0_invalid <= 16'd0;
src1_invalid <= 16'd0;
src2_invalid <= 16'd0;
src3_invalid <= 16'd0;
end else if (LCK) begin
src0_invalid <= dl_invalid_rs[15:0];
src1_invalid <= dl_invalid_rs[31:16];
src2_invalid <= dr_invalid_rs[15:0];
src3_invalid <= dr_invalid_rs[31:16];
end else begin
src0_invalid <= 16'd0;
src1_invalid <= 16'd0;
src2_invalid <= 16'd0;
src3_invalid <= 16'd0;
end
end
修改后:
reg lck_cap_r; // 目标模块本地LCK
always @(posedge CLK_REF) begin
if (!RST_N) begin
lck_cap_r <= 1'b0;
end else begin
lck_cap_r <= LCK;
end
end
always @(posedge CLK_REF) begin
if (!RST_N) begin
src0_invalid <= 16'd0;
src1_invalid <= 16'd0;
src2_invalid <= 16'd0;
src3_invalid <= 16'd0;
end else begin
src0_invalid <= lck_cap_r ? dl_invalid_rs[15:0] : 16'd0;
src1_invalid <= lck_cap_r ? dl_invalid_rs[31:16] : 16'd0;
src2_invalid <= lck_cap_r ? dr_invalid_rs[15:0] : 16'd0;
src3_invalid <= lck_cap_r ? dr_invalid_rs[31:16] : 16'd0;
end
end
这样原路径:
远端 LCK_reg -> src*_invalid_reg
会变成:
远端 LCK_reg -> lck_cap_r
lck_cap_r -> src*_invalid_reg
第一段只有一个目的寄存器,第二段在目标模块本地,布线会短很多。
方法二:不要让远端信号直接进 R / CE
对于高速逻辑,尽量避免这种结构:
远端控制信号 -> 寄存器 R
远端控制信号 -> 寄存器 CE
更推荐把控制放到 D 端逻辑里,并且控制信号先本地寄存。
推荐:
always @(posedge clk_i) begin
if (!rst_n_i) begin
data_r <= 16'd0;
end else begin
data_r <= local_en_r ? data_next_w : 16'd0;
end
end
不推荐远端 enable 直接控制大量寄存器 CE。
方法三:降低扇出,复制控制寄存器
如果一个控制信号要控制很多寄存器,可以按功能区域复制。
reg lck_left_r; // 左路本地LCK
reg lck_right_r; // 右路本地LCK
always @(posedge CLK_REF) begin
if (!RST_N) begin
lck_left_r <= 1'b0;
lck_right_r <= 1'b0;
end else begin
lck_left_r <= LCK;
lck_right_r <= LCK;
end
end
然后:
src0_invalid <= lck_left_r ? dl_invalid_rs[15:0] : 16'd0;
src1_invalid <= lck_left_r ? dl_invalid_rs[31:16] : 16'd0;
src2_invalid <= lck_right_r ? dr_invalid_rs[15:0] : 16'd0;
src3_invalid <= lck_right_r ? dr_invalid_rs[31:16] : 16'd0;
这样可以减少单个控制信号的扇出和布线压力。
方法四:CDC 输出不要直接用
CDC 后的信号进入目标时钟域后,最好再根据功能打一拍或复制。
reg safe_ctrl_local_r; // CDC后本地控制
always @(posedge clk_dst_i) begin
if (!rst_dst_n_i) begin
safe_ctrl_local_r <= 1'b0;
end else begin
safe_ctrl_local_r <= safe_ctrl_cdc_w;
end
end
如果 CDC 输出直接驱动大量逻辑,容易出现高扇出和长布线。
4. 判断依据
看到下面这种情况,优先怀疑是布线问题:
Levels 很少
Logic Delay 很小
Net Delay 很大
Fanout 偏高
例如:
Levels : 1
Logic Delay: 0.281ns
Net Delay : 1.565ns
这说明组合逻辑本身不复杂,主要是信号拉得太远。
5. 不建议优先使用 false path
如果 source clock 和 destination clock 是同一个时钟:
Source Clock : CLK_250M
Destination Clock : CLK_250M
那它就是真实时序路径,不应该随便加:
set_false_path
只有在确认这个信号本来就是慢速配置、允许多拍生效时,才考虑 multicycle:
set_multicycle_path 2 -setup -from [get_cells *LCK_reg*] -to [get_cells *src2_invalid_reg*]
set_multicycle_path 1 -hold -from [get_cells *LCK_reg*] -to [get_cells *src2_invalid_reg*]
但工程上优先还是改 RTL。
6. 经验结论
这类时序问题可以简单归纳为:
远端控制信号 + 高速时钟 + R/CE 控制端 + 较大扇出 = 容易时序违例
优先解决思路:
1. 控制信号进入目标模块后先打一拍
2. 不让远端信号直接控制 R / CE
3. 按区域复制控制寄存器,降低 fanout
4. CDC 输出不要直接驱动大量逻辑
5. 约束作为最后手段,不要一上来 false path
本次问题中,LCK_reg 到 src2_invalid_reg/R 的路径逻辑级数很少,但是 Net Delay 占主要部分,所以通过在 FR32_CAPTURE 内部增加本地寄存器 lck_cap_r,可以有效缩短布线路径,改善 setup 时序。

浙公网安备 33010602011771号