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_regsrc2_invalid_reg/R 的路径逻辑级数很少,但是 Net Delay 占主要部分,所以通过在 FR32_CAPTURE 内部增加本地寄存器 lck_cap_r,可以有效缩短布线路径,改善 setup 时序。

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