仿真全过,上板却随机出错?FPGA 时序约束漏写的真实后果

前言

FPGA 代码仿真正常,并不代表硬件能够稳定运行。时序约束一旦漏写,工具可能不分析关键路径,也不会针对这些路径进行正确优化。本文结合时钟、输入输出、衍生时钟、跨时钟域和多周期路径,讲清漏写约束的真实后果,并给出可直接使用的 XDC 示例和排错方法。


1. 一个很常见的现象:仿真没有问题,上板偶尔出错

很多 FPGA 新人都遇到过这种情况:

  • RTL 仿真完全正常;

  • 综合、实现都能完成;

  • 下载到开发板后,大部分时间可以运行;

  • 温度升高、换一块板或者提高时钟频率后开始报错;

  • 加一个 ILA,错误反而消失;

  • 修改一行无关代码,原来的问题突然出现。

这类问题经常被归因于:

开发板不稳定 芯片批次有差异 电源有噪声 综合器优化有问题

实际检查后,却发现真正的原因是:

时序约束没有写完整,工具根本没有正确分析某些关键路径。

时序约束漏写后,工程不一定会报错,也不一定无法生成比特流。

最危险的情况恰恰是:

实现成功 WNS 看起来正常 上板也偶尔能工作

但实际上,部分路径从来没有经过完整的静态时序分析。


2. 时序约束到底在约束什么

先看一段最普通的同步逻辑:

always_ff @(posedge clk) begin data_reg1 <= data_in; data_reg2 <= data_reg1 + offset; end

data_reg1data_reg2之间存在一条寄存器到寄存器的时序路径:

data_reg1/Q ↓ 组合逻辑 ↓ data_reg2/D

数据需要在一个时钟周期内完成下面的过程:

源寄存器输出 → 经过组合逻辑和布线 → 到达目标寄存器 → 满足目标寄存器建立时间

可以简化理解为:

数据路径延迟 < 可用时钟时间

更完整一些:

Tclk_to_q + Tlogic + Troute + Tsetup < Tperiod + Tskew - Tuncertainty

其中:

参数含义
Tclk_to_q源寄存器时钟到输出的延迟
Tlogic组合逻辑延迟
TrouteFPGA 内部布线延迟
Tsetup目标寄存器建立时间
Tperiod时钟周期
Tskew时钟到达源、目标寄存器的偏差
Tuncertainty时钟抖动和不确定性预算

实现工具只有知道时钟周期,才能判断这条路径是否满足要求。

例如系统实际运行在 100 MHz:

时钟周期 = 10 ns

如果数据路径延迟为 12 ns,那么它显然无法在一个周期内稳定传输。

但如果没有创建这个时钟,工具可能不知道这条路径需要在 10 ns 内完成。

结果不是“工具自动帮你猜一个时钟”,而可能是:

该路径未被完整约束 该路径没有参与正常时序收敛 该路径的实现结果无法作为签核依据

3. 漏写约束不等于一定马上出错

这里要先纠正一个常见误解。

时序约束漏写后,并不代表电路一定不能运行。

假设某条路径实际延迟只有 3 ns,系统时钟周期为 10 ns。

即使没有写约束,这条路径在当前布局布线结果下,仍然可能满足时序。

因此工程可能暂时正常。

问题在于:

你不知道它是否满足,也无法保证下一次实现后仍然满足。

重新综合后,工具可能改变:

  • 逻辑映射;

  • 寄存器位置;

  • 布线路径;

  • 资源共享方式;

  • 扇出优化方式;

  • RAM、DSP 和寄存器之间的位置关系。

原来 3 ns 的路径,下一次可能变成 9 ns,也可能变成 11 ns。

没有约束时,工具没有义务优先优化这条路径。

所以漏写约束的真正后果不是“百分之百立即失败”,而是:

设计结果失去可验证性

一、漏写时钟约束

4. 最严重的漏项:没有创建主时钟

假设顶层时钟端口为:

input logic sys_clk;

系统实际时钟频率为 100 MHz。

在 Vivado 的 XDC 中,应至少写入:

create_clock -name sys_clk \ -period 10.000 \ [get_ports sys_clk]

其中:

100 MHz = 10 ns

如果这条约束没有写,可能出现以下后果:

  1. 寄存器之间的同步路径没有被正确分析;

  2. 工具无法计算可靠的建立时间和保持时间裕量;

  3. 布局布线阶段不会按照真实频率优化;

  4. 时序报告中的 WNS、TNS 不能代表完整设计;

  5. 工程可能存在大量未约束路径。


5. 为什么没有报时序违例,反而更加危险

假设存在一条延迟为 12 ns 的路径。

正确约束为 100 MHz

要求时间:10 ns 实际延迟:12 ns 时序裕量:约 -2 ns

工具会报告时序违例:

WNS = -2 ns

没有时钟约束

工具不知道目标周期是 10 ns。

这时可能出现:

没有对应的有效时序要求 路径被列入未约束路径 报告中没有正常的负裕量

所以:

没有时序违例,不等于时序已经满足。

必须先确认所有需要分析的路径都已经被约束。


6. 时钟周期写错同样危险

系统实际运行频率为 100 MHz,但约束写成了:

create_clock -period 20.000 [get_ports sys_clk]

20 ns 对应 50 MHz。

如果某条路径延迟为 15 ns:

按照错误约束:15 ns < 20 ns,时序通过 按照真实频率:15 ns > 10 ns,时序失败

这种情况比完全没有约束更加隐蔽。

因为报告中可能显示:

WNS 为正 Timing constraints are met

但报告检查的是错误目标。

因此创建时钟后,还要检查:

report_clocks

确认每个时钟的:

  • 名称;

  • 周期;

  • 波形;

  • 来源;

  • 是否为衍生时钟。


二、漏写输入输出延迟

7. FPGA 内部时序通过,不代表板级接口一定通过

很多新人只约束了 FPGA 的系统时钟:

create_clock -period 10.000 [get_ports sys_clk]

然后看到内部时序通过,就认为整个系统没有问题。

实际上,一个完整的输入路径是:

外部器件寄存器 ↓ 外部器件 Clock-to-Out ↓ PCB 数据线 ↓ FPGA 输入引脚 ↓ FPGA 内部布线 ↓ FPGA 采样寄存器

输出路径则是:

FPGA 输出寄存器 ↓ FPGA 内部布线 ↓ FPGA 输出引脚 ↓ PCB 数据线 ↓ 外部器件采样寄存器

如果没有写set_input_delayset_output_delay,工具只知道 FPGA 内部的延迟,不知道外部器件和 PCB 消耗了多少时间。


8. 漏写输入延迟会有什么后果

假设 FPGA 接收一个外部 ADC 输出的数据。

ADC 在时钟沿之后,需要 3 ns 才能输出稳定数据:

ADC Clock-to-Out = 3 ns

PCB 又消耗了 1 ns:

PCB 数据延迟 = 1 ns

那么数据到达 FPGA 引脚时,已经消耗了约 4 ns。

如果系统周期为 10 ns,FPGA 内部真正可以使用的时间不是完整的 10 ns,而大约只剩:

10 ns - 4 ns = 6 ns

如果没有输入延迟约束,工具可能按照更宽松的内部条件布局。

最终出现:

FPGA 内部路径看起来满足 但整个板级输入路径不满足

9. 输入延迟约束示例

下面数值仅用于说明格式,真实数值必须根据外部器件数据手册和 PCB 时延计算。

create_clock -name sys_clk \ -period 10.000 \ [get_ports sys_clk] set_input_delay -clock sys_clk \ -max 4.000 \ [get_ports data_in[*]] set_input_delay -clock sys_clk \ -min 1.000 \ [get_ports data_in[*]]

这里:

-max:用于建立时间分析 -min:用于保持时间分析

不能只写-max而完全忽略-min

否则建立时间可能被分析了,但保持时间的板级条件并不完整。


10. 输出延迟约束示例

假设 FPGA 输出数据给外部器件:

set_output_delay -clock sys_clk \ -max 3.000 \ [get_ports data_out[*]] set_output_delay -clock sys_clk \ -min -0.500 \ [get_ports data_out[*]]

输出延迟中的-min有时可能是负值,这与外部器件保持时间、时钟偏差和 PCB 延迟有关。

不要因为看到负数就直接认为约束写错。

真实项目中应根据以下参数计算:

  • 外部器件建立时间;

  • 外部器件保持时间;

  • 数据线 PCB 延迟;

  • 时钟线 PCB 延迟;

  • 时钟相位关系;

  • 设计裕量。

不能直接复制其他工程的输入输出延迟数值。


11. 漏写 I/O 约束的典型现象

输入输出约束漏写后,常见现象包括:

  • 低频运行正常,高频运行错误;

  • 同一份程序在不同开发板上表现不同;

  • 数据偶尔错一位;

  • ADC、DAC、DDR、SPI 或并行总线偶发错误;

  • 改变温度后误码率增加;

  • 加入 ILA 后问题发生变化;

  • FPGA 内部时序全绿,但接口仍不稳定。

这些现象不是因为静态时序分析无效,而是因为:

你只分析了系统的一部分

三、漏写衍生时钟约束

12. 什么是衍生时钟

衍生时钟是由已有时钟经过下面这些结构产生的时钟:

  • PLL;

  • MMCM;

  • 时钟分频器;

  • 时钟缓冲器;

  • 时钟选择器;

  • 逻辑分频寄存器;

  • DDR 时钟结构。

例如:

sys_clk = 100 MHz clk_div2 = 50 MHz

如果clk_div2被当作真正的时钟使用:

always_ff @(posedge clk_div2) begin data_reg <= data_in; end

工具必须知道:

  • clk_div2来源于哪个时钟;

  • 分频比例是多少;

  • 两个时钟之间的相位关系;

  • 时钟边沿如何对应。


13. 衍生时钟漏写后的后果

部分 FPGA 专用时钟资源,例如某些 PLL、MMCM 输出,工具可能能够自动传播或推导时钟。

但是不能假设所有衍生时钟都能自动识别。

尤其是使用普通逻辑产生的分频时钟:

always_ff @(posedge sys_clk) begin clk_div2 <= ~clk_div2; end

如果没有正确创建衍生时钟,可能出现:

  1. clk_div2时钟域内的路径未正确分析;

  2. sys_clkclk_div2之间的路径关系错误;

  3. 工具将真实相关的两个时钟当成无关时钟;

  4. 工具无法正确计算跨时钟边沿的建立和保持时间;

  5. 该时钟网络可能使用普通布线,产生较大偏斜。


14. 衍生时钟约束示例

例如,一个逻辑产生的二分频时钟:

create_generated_clock -name clk_div2 \ -source [get_ports sys_clk] \ -divide_by 2 \ [get_pins u_clk_div/clk_div2_reg/Q]

这里的层次路径必须与综合后的实际对象一致。

可以使用以下命令检查对象是否匹配:

get_ports sys_clk get_pins u_clk_div/clk_div2_reg/Q report_clocks

如果get_pins返回空对象,说明约束没有匹配到实际网表节点。

一条没有匹配到对象的约束,等于没有生效。


15. 能用时钟使能时,不要轻易生成逻辑时钟

下面这种写法会产生一个新的逻辑时钟:

logic clk_div2; always_ff @(posedge sys_clk) begin clk_div2 <= ~clk_div2; end always_ff @(posedge clk_div2) begin data_reg <= data_in; end

更推荐使用时钟使能:

logic div2_en; always_ff @(posedge sys_clk) begin div2_en <= ~div2_en; if (div2_en) data_reg <= data_in; end

这样整个逻辑仍然位于同一个sys_clk时钟域中。

优点包括:

  • 不需要额外创建逻辑时钟;

  • 避免普通布线作为时钟网络;

  • 降低时钟偏斜;

  • 简化时序分析;

  • 减少跨时钟域问题。


四、漏写跨时钟域关系

16. 两个异步时钟之间不能按照普通同步路径分析

假设设计中存在两个完全独立的时钟:

sys_clk:100 MHz adc_clk:74.25 MHz

它们来自不同晶振,彼此之间没有固定相位关系。

此时两个时钟域之间属于异步跨时钟域。

如果没有声明异步关系,工具可能尝试在两个时钟之间寻找某个边沿关系,并报告大量难以收敛的时序违例。

例如:

sys_clk 域寄存器 ↓ 跨时钟域信号 ↓ adc_clk 域寄存器

对于普通双触发器同步器,第一级触发器本来就可能发生亚稳态。

这类路径不能按照普通同步数据路径的方式进行时序收敛。


17. 异步时钟组约束示例

对于确定完全异步的两个时钟域,可以使用:

set_clock_groups -asynchronous \ -group [get_clocks sys_clk] \ -group [get_clocks adc_clk]

这条约束表示:

不对两个时钟组之间执行常规建立时间和保持时间分析

但是必须注意:

写了异步时钟组约束,不代表跨时钟域设计自动安全。

它只是在告诉静态时序分析工具:

不要使用普通同步路径规则分析这些路径

真正的 CDC 安全仍然需要 RTL 结构保证,例如:

  • 单比特信号使用两级同步器;

  • 脉冲信号使用脉冲展宽、握手或 toggle 同步;

  • 多比特数据使用握手、异步 FIFO 或稳定窗口;

  • 计数指针使用 Gray 码;

  • 复位释放在各时钟域同步完成。


18. 漏写异步关系的后果与漏写普通时钟不同

漏写普通时钟约束通常会导致:

关键同步路径没有被正确分析

漏写异步时钟关系通常会导致:

工具分析了本来不应该直接收敛的路径

表现为:

  • 出现大量跨时钟域负裕量;

  • 工具把时间浪费在无法正常收敛的异步路径上;

  • 真正需要优化的同步路径受到影响;

  • 开发人员为了消除报错,错误地修改正常逻辑;

  • 时序报告被大量无意义违例淹没。

不过,不要看到跨时钟域违例就直接全部设置为 false path。

应该先检查:

这条路径是否有正确的 CDC 结构

如果跨时钟域结构本身不安全,设置 false path 只能隐藏问题,不能解决问题。


五、漏写多周期路径约束

19. 什么是多周期路径

默认情况下,静态时序分析通常认为数据需要在一个时钟周期内完成传输。

例如:

第 N 个时钟沿:源寄存器发送数据 第 N+1 个时钟沿:目标寄存器采样数据

但有些设计明确允许数据经过多个周期后再采样。

例如:

第 N 个时钟沿:开始运算 第 N+1 个时钟沿:中间计算 第 N+2 个时钟沿:结果被目标寄存器采样

这时可能需要多周期路径约束。

例如允许两个周期完成:

set_multicycle_path 2 -setup \ -from [get_cells source_reg*] \ -to [get_cells destination_reg*] set_multicycle_path 1 -hold \ -from [get_cells source_reg*] \ -to [get_cells destination_reg*]

通常设置 N 周期建立时间时,还要配套设置 N-1 周期保持时间。


20. 漏写多周期约束会发生什么

多周期路径漏写后,工具仍然按照单周期路径分析。

结果可能是:

实际允许 20 ns 工具只允许 10 ns

这种漏写一般不会直接导致芯片功能错误,前提是 RTL 结构确实保证目标寄存器只在正确周期采样。

它更可能导致:

  • 出现不真实的时序违例;

  • 工具对该路径进行过度优化;

  • 使用更多 LUT 和寄存器;

  • 布局布线难度增加;

  • 其他关键路径的资源被挤占;

  • 时序收敛时间变长。

需要注意:

多周期约束只改变工具的分析要求,不会自动改变 RTL 的采样行为。

如果目标寄存器实际上每个周期都会采样,仅仅添加多周期约束,就可能把真实错误隐藏掉。


六、漏写路径例外约束

21. 什么是 false path

有些物理上存在的连接,不需要按照普通同步路径分析。

例如:

  • 两个完全异步时钟域之间的同步器路径;

  • 某些静态配置寄存器到工作逻辑的路径;

  • 只在复位或初始化期间变化的控制信号;

  • 不会在正常运行状态下被采样的测试路径。

可以使用类似约束:

set_false_path \ -from [get_cells config_reg*] \ -to [get_cells work_reg*]

set_false_path是非常强的约束。

它的含义不是:

这条路径比较宽松

而是:

完全不对这条路径执行正常时序分析

22. 漏写 false path 的后果

如果一条路径确实不需要分析,但没有设置 false path,可能出现:

  • 不真实的时序违例;

  • 工具浪费资源优化无关路径;

  • 报告中出现大量噪声;

  • 真正的关键违例被掩盖;

  • 时序收敛困难。

这种漏写通常不会直接造成硬件故障。

但是工程人员可能为了修复这些“假违例”,做出错误修改。


23. false path 不能用来消除看不懂的报错

下面这种做法非常危险:

看到时序违例 → 不分析路径来源 → 直接 set_false_path → 报告变绿

报告变绿不代表设计变安全。

它只代表工具不再检查这条路径。

在添加 false path 前,至少要回答三个问题:

  1. 为什么这条路径不需要满足普通时序?

  2. 目标寄存器是否可能在数据变化期间采样?

  3. RTL 中是否存在同步器、握手或其他安全结构?

如果无法明确回答,就不应该直接切断时序分析。


七、异步复位约束漏写

24. 异步复位不仅有拉低,还有释放

很多设计使用异步低电平复位:

always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) data_reg <= '0; else data_reg <= data_next; end

异步复位可以在没有时钟的情况下立即拉低寄存器。

但是复位释放如果刚好发生在时钟沿附近,可能违反:

  • Recovery time;

  • Removal time。

可以把它们理解为异步控制信号相对于时钟边沿的建立和保持要求。

如果复位直接异步释放到大量寄存器,不同寄存器可能在不同周期退出复位。

结果可能是:

  • 状态机进入非法状态;

  • 计数器各位不一致;

  • FIFO 指针不同步;

  • 模块之间复位状态不一致;

  • 上电后偶发启动失败。


25. 推荐异步拉低、同步释放

常见做法如下:

logic [1:0] rst_sync; logic local_rst_n; always_ff @(posedge clk or negedge global_rst_n) begin if (!global_rst_n) rst_sync <= 2'b00; else rst_sync <= {rst_sync[0], 1'b1}; end assign local_rst_n = rst_sync[1];

时序示意:

global_rst_n 0──────────────1──────────── clk ↑ ↑ ↑ ↑ ↑ ↑ ↑ rst_sync[0] 0──────────────0───1──────── rst_sync[1] 0──────────────────0───1──── local_rst_n 0──────────────────0───1────

这样:

  • 复位拉低是异步的;

  • 复位释放经过本地时钟同步;

  • 不同寄存器更容易在统一时钟边界退出复位。

仅仅通过约束隐藏复位路径,不能替代正确的复位结构。


八、漏写约束为什么会影响布局布线

26. 时序约束不只是生成报告

很多新人认为时序约束只是在实现结束后检查一下时序。

实际上,约束会参与整个实现过程。

工具会根据时序要求决定:

  • 哪些寄存器需要靠近;

  • 哪些组合逻辑需要重构;

  • 哪些网络需要降低扇出;

  • 哪些路径优先使用快速布线资源;

  • 是否进行寄存器复制;

  • 是否进行逻辑重定时;

  • DSP 和 RAM 周围逻辑如何摆放;

  • 哪些路径是关键路径。

如果一条路径没有约束,工具可能不会把它当作关键路径。

例如:

寄存器 A 到寄存器 B

在有约束时,工具可能将它们放得很近:

[A][组合逻辑][B]

在没有约束时,可能被放得很远:

[A]────────大量布线资源────────[B]

FPGA 中很多高频路径的主要延迟并不是 LUT 运算,而是布线延迟。

因此漏写约束会直接影响最终物理实现结果。


27. 为什么加一个 ILA 后问题反而消失

加入 ILA 后,工程会发生很多变化:

  • 新增调试网络;

  • 增加扇出;

  • 改变寄存器位置;

  • 改变布局布线;

  • 关键路径重新分配;

  • 部分信号被保留,不能继续优化。

因此同一条未约束路径可能从:

原实现:11 ns

变成:

加入 ILA 后:8 ns

问题暂时消失。

也可能反过来,原本正常的路径因为加入 ILA 后变得更差。

这说明问题与实现结果有关,并不说明 ILA 修复了逻辑。

当出现下面这种现象时,应优先怀疑时序和 CDC:

增加 ILA 后问题变化 修改无关代码后问题变化 重新实现后问题变化 不同优化策略下问题变化

九、一个最小 XDC 约束框架

下面给出一个基础约束框架。

真实工程需要根据硬件接口继续补充。

############################################################################### # 1. 主时钟 ############################################################################### create_clock -name sys_clk \ -period 10.000 \ -waveform {0.000 5.000} \ [get_ports sys_clk] ############################################################################### # 2. 输入延迟 # 数值仅为示例,必须根据外部器件和 PCB 参数计算 ############################################################################### set_input_delay -clock sys_clk \ -max 4.000 \ [get_ports data_in[*]] set_input_delay -clock sys_clk \ -min 1.000 \ [get_ports data_in[*]] ############################################################################### # 3. 输出延迟 # 数值仅为示例,必须根据外部器件和 PCB 参数计算 ############################################################################### set_output_delay -clock sys_clk \ -max 3.000 \ [get_ports data_out[*]] set_output_delay -clock sys_clk \ -min -0.500 \ [get_ports data_out[*]] ############################################################################### # 4. 衍生时钟 ############################################################################### create_generated_clock -name clk_div2 \ -source [get_ports sys_clk] \ -divide_by 2 \ [get_pins u_clk_div/clk_div2_reg/Q] ############################################################################### # 5. 异步时钟组 ############################################################################### set_clock_groups -asynchronous \ -group [get_clocks sys_clk] \ -group [get_clocks adc_clk] ############################################################################### # 6. 多周期路径 ############################################################################### set_multicycle_path 2 -setup \ -from [get_cells source_reg*] \ -to [get_cells destination_reg*] set_multicycle_path 1 -hold \ -from [get_cells source_reg*] \ -to [get_cells destination_reg*]

不要直接把这个模板完整复制到自己的工程中。

正确做法是:

根据设计结构逐条添加 每添加一条,就确认对象是否匹配 每添加一条,就理解它改变了哪些时序分析

十、怎么检查约束是否漏写

28. 第一步:检查时钟是否正确

Vivado 中可以执行:

report_clocks

重点检查:

  • 主时钟是否存在;

  • 时钟周期是否正确;

  • 衍生时钟是否存在;

  • 时钟名称是否重复;

  • 时钟来源是否正确;

  • 波形是否正确;

  • 是否存在意外时钟。

不能只看 XDC 文件里有没有create_clock

要看它是否真正匹配到了设计对象。


29. 第二步:执行check_timing

check_timing

该命令可以帮助检查:

  • 未定义时钟的寄存器;

  • 缺少输入延迟的端口;

  • 缺少输出延迟的端口;

  • 常量时钟;

  • 多时钟驱动;

  • 未约束端点;

  • 组合环路;

  • 时序约束异常。

对于新人来说,check_timing比只看 WNS 更重要。

因为 WNS 只反映已经参与分析的路径。

check_timing可以帮助发现哪些路径没有正常进入分析。


30. 第三步:检查未约束路径

可以生成时序汇总报告:

report_timing_summary -report_unconstrained

重点查看:

Unconstrained Paths

理想情况下,需要签核的同步路径不应该存在未解释的未约束项。

注意,这里不是要求所有端口和所有路径必须机械地约束。

有些路径可能确实是:

  • 静态信号;

  • 配置输入;

  • 异步复位;

  • 调试接口;

  • 不参与正常功能的测试端口。

但是每一类未约束路径都应该能够解释。

不能采用:

不知道为什么未约束,但工程能生成比特流

这样的处理方式。


31. 第四步:检查时序例外

report_exceptions

重点检查:

  • false path 是否过多;

  • 多周期路径是否匹配到正确对象;

  • 是否有空约束;

  • 是否有被其他约束覆盖的例外;

  • 是否把大范围路径错误切断;

  • 异步时钟组是否覆盖了不该覆盖的时钟。

尤其要警惕这种约束:

set_false_path -from [all_clocks] -to [all_clocks]

它几乎会把所有时钟间路径全部切断。

报告可能非常干净,但设计已经失去时序检查意义。


32. 第五步:检查 CDC

Vivado 中可以使用:

report_cdc

重点检查:

  • 单比特信号是否使用同步器;

  • 是否存在组合逻辑后直接跨时钟域;

  • 多比特总线是否逐位同步;

  • 同步器第一级输出是否被多处使用;

  • 异步复位是否安全释放;

  • Gray 码总线是否符合结构要求;

  • 是否存在未识别的 CDC 路径。

CDC 报告和时序报告不能互相替代。

时序报告通过 ≠ CDC 安全 CDC 结构正确 ≠ 同步路径时序通过

两者都需要检查。


十一、常见错误写法

33. 错误一:只创建一个主时钟就认为约束完成

错误认识:

XDC 中有 create_clock 所以整个工程已经被约束

实际还需要检查:

  • 是否有其他时钟输入;

  • 是否有衍生时钟;

  • 是否有异步时钟域;

  • 是否有输入延迟;

  • 是否有输出延迟;

  • 是否有特殊路径;

  • 是否有未约束端点。


34. 错误二:直接复制开发板例程的约束

开发板例程可能只包含:

set_property PACKAGE_PIN ... set_property IOSTANDARD ...

这些主要是引脚和电气属性约束。

例如:

set_property PACKAGE_PIN W5 [get_ports sys_clk] set_property IOSTANDARD LVCMOS33 [get_ports sys_clk]

它们并没有描述时钟周期。

还必须创建时钟:

create_clock -period 10.000 [get_ports sys_clk]

引脚约束和时序约束不是一回事。


35. 错误三:看到时序违例就设置 false path

错误处理过程:

出现负裕量 → 加 false path → 报告通过

正确处理过程应该是:

确认起点和终点 → 确认两个寄存器属于哪个时钟域 → 确认这条路径是否真实需要传输数据 → 检查 RTL 结构 → 判断属于同步、异步、多周期还是无效路径 → 选择正确约束

36. 错误四:只检查建立时间,不检查保持时间

时序分析至少包括:

Setup:数据是否到得太晚 Hold:数据是否变化得太早

建立时间通过,不代表保持时间一定通过。

特别是在以下情况下,应重点检查保持时间:

  • 输入输出接口;

  • 时钟相位调整;

  • PLL/MMCM 相移;

  • 源同步接口;

  • 多周期路径;

  • 不同时钟树之间的路径;

  • 较大时钟偏斜。


37. 错误五:认为 RTL 仿真能够发现时序问题

RTL 仿真主要验证逻辑功能。

RTL 中普通赋值通常不会包含真实的:

  • LUT 延迟;

  • 布线延迟;

  • 时钟偏斜;

  • 建立时间;

  • 保持时间;

  • 时钟抖动;

  • 亚稳态。

例如下面的逻辑在 RTL 仿真中可以正常工作:

always_ff @(posedge clk) begin result <= a + b + c + d + e + f; end

但综合后可能形成很长的组合路径。

RTL 仿真只会认为:

时钟沿到来 → 计算表达式 → 更新寄存器

它不会自动告诉你真实硬件能否在 5 ns 或 10 ns 内完成。

静态时序分析才是判断 FPGA 同步路径能否稳定运行的主要方法。


十二、工程排错示例

38. 现象:100 MHz 偶发错误,80 MHz 正常

建议按以下顺序检查:

第一步:确认时钟约束

report_clocks

确认周期是否为:

10 ns

而不是:

12.5 ns 20 ns 或根本没有创建

第二步:检查未约束路径

check_timing report_timing_summary -report_unconstrained

第三步:查看最差路径

report_timing -delay_type max -max_paths 20

重点查看:

  • 起点寄存器;

  • 终点寄存器;

  • 组合逻辑级数;

  • 布线延迟占比;

  • 时钟域;

  • 时序要求;

  • 实际延迟;

  • 裕量。

第四步:检查 CDC

如果错误数据来自另一个时钟域:

report_cdc

第五步:检查 I/O 延迟

如果错误发生在 ADC、DAC、摄像头或外部总线接口,确认:

set_input_delay set_output_delay

是否完整。


39. 现象:每次重新生成比特流,错误位置都不一样

优先怀疑:

  • 未约束路径;

  • 边界时序路径;

  • 不安全的 CDC;

  • 异步复位释放;

  • 组合逻辑毛刺;

  • 逻辑生成时钟;

  • 高扇出控制信号。

因为重新实现会改变布局布线。

如果逻辑功能完全确定,但错误随实现结果变化,通常要优先检查物理时序问题,而不是继续盯着算法代码。


40. 现象:时序报告全绿,但硬件仍然错误

按下面顺序排查:

  1. 所有时钟是否正确创建;

  2. 时钟周期是否与真实硬件一致;

  3. 是否存在未约束路径;

  4. 输入输出延迟是否完整;

  5. 时序例外是否切断过多路径;

  6. CDC 结构是否正确;

  7. 异步复位是否同步释放;

  8. 是否存在假多周期路径;

  9. 时钟是否走专用时钟资源;

  10. 外部器件时序参数是否使用正确工作模式下的数据。

报告全绿,只能证明:

工具检查到的、被正确约束的路径满足当前约束

它不能证明漏掉的路径也满足要求。


十三、时序约束检查清单

在生成最终比特流前,建议逐项确认。

时钟部分

□ 所有主时钟均已创建 □ 时钟周期与真实硬件频率一致 □ 时钟波形和占空比正确 □ 衍生时钟已正确识别 □ 逻辑分频时钟已处理 □ 异步时钟关系已明确

输入输出部分

□ 同步输入接口具有 input delay □ 同步输出接口具有 output delay □ 同时设置了 max 和 min □ 数值来自器件手册和 PCB 参数 □ 没有直接复制其他工程的延迟数值

时序例外部分

□ 每条 false path 都有明确原因 □ 多周期路径与 RTL 采样行为一致 □ 多周期 setup 和 hold 配套设置 □ 没有使用大范围约束隐藏违例 □ 约束对象确实匹配到网表节点

报告部分

□ report_clocks 结果正确 □ check_timing 没有未解释问题 □ 未约束路径均已确认 □ 建立时间满足要求 □ 保持时间满足要求 □ CDC 报告没有高风险结构 □ 时序例外已检查

十四、最后总结

时序约束漏写的后果,可以分为三类。

第一类:真实问题没有被发现

例如:

  • 主时钟漏写;

  • 时钟周期写错;

  • 衍生时钟漏写;

  • 输入输出延迟漏写。

这类问题最危险。

工具可能没有检查到真实的时序违例,而硬件会在特定条件下出错。


第二类:工具检查了不应该检查的路径

例如:

  • 异步时钟关系漏写;

  • false path 漏写;

  • 多周期路径漏写。

这类问题通常会制造大量不真实的时序违例,浪费实现资源和调试时间。


第三类:报告看起来正常,但结论无效

如果设计中存在未约束路径,那么:

WNS 为正 TNS 为 0 Timing constraints are met

也不能直接证明设计已经完成时序收敛。

最终必须确认:

所有需要工作的路径,都按照真实硬件条件参与了分析

可以记住下面四句话:

没有报错,不等于没有问题。 时序通过,不等于约束完整。 约束写了,不等于约束生效。 报告全绿,不等于硬件一定稳定。

一个可以签核的 FPGA 工程,至少要做到:

  1. 时钟定义正确;

  2. 输入输出接口约束完整;

  3. 衍生时钟关系清楚;

  4. CDC 结构安全;

  5. 时序例外有明确依据;

  6. 没有未解释的未约束路径;

  7. 建立时间和保持时间都满足要求。

时序约束不是工程最后补上的一份文件。

它本身就是 FPGA 设计的一部分。

#FPGA #Verilog #System #Verilog #FIFO 异步FIFO #同步FIFO #Gray码 #CDC #跨时钟域 #数字电路