3. 时序约束入门:主时钟约束、生成时钟约束、输入延迟约束、输出延迟约束

各位同学,咱们今天聊聊时序约束。说实话,我刚入行那会儿,觉得约束就是随便写几行代码,让工具别报错就行。结果呢?第一次做高速接口项目,板子调了三天三夜,信号就是不稳定。后来才发现,是约束没写对。

时序约束这东西,说白了就是告诉工具:你的设计要在什么样的时钟环境下工作,数据什么时候来,什么时候走。工具知道了这些,才能帮你把路径优化好。今天咱们就把最基础的四种约束讲透。

3.1 主时钟约束(create_clock)

主时钟约束是所有约束的起点。你想想看,没有时钟,时序分析就无从谈起。我习惯把主时钟想象成一个心跳,它决定了整个系统的节奏。

语法很简单,但参数要搞明白:

create_clock -name sys_clk -period 10.000 [get_ports clk_in]

这里 -period 10.000 表示周期是10ns,也就是100MHz。注意单位是纳秒,别写成毫秒,我见过有人犯这种低级错误。

有时候时钟不是50%占空比,比如DDR接口需要25%占空比的时钟。这时候用 -waveform 参数:

create_clock -name ddr_clk -period 5.000 -waveform {0.000 1.250} [get_ports ddr_clk_p]

这个波形参数的意思是:上升沿在0ns,下降沿在1.25ns。说白了就是高电平只占1/4周期。

我的习惯:给每个时钟起个有意义的名字,别用clk1、clk2这种。我在项目中吃过亏,后来用sys_clk、ddr_clk、pcie_clk这种命名,一眼就能看出是哪个域。

3.2 生成时钟约束(create_generated_clock)

生成时钟,就是由主时钟通过PLL或分频电路产生的时钟。工具不会自动识别这些时钟的关系,你得手动告诉它。

最常见的场景是PLL输出:

create_generated_clock -name pll_out -source [get_ports clk_in] \
  -divide_by 2 [get_pins pll/clk_out]

这里 -source 指定源时钟,-divide_by 2 表示二分频。如果是倍频,用 -multiply_by。

还有一种情况是分频时钟,比如用寄存器做二分频:

create_generated_clock -name div2_clk -source [get_ports clk_in] \
  -edges {1 3 5} [get_pins div_reg/Q]

-edges 参数指定了生成时钟的边沿对应源时钟的哪些边沿。1、3、5表示源时钟的第1、3、5个边沿。这个写法比较绕,我建议你直接用 -divide_by 和 -multiply_by,更直观。

我曾经踩过的坑:生成时钟的源时钟一定要指定正确。有一次我忘了写 -source,工具默认用了最近的时钟,结果时序分析全乱了。后来花了半天才排查出来。

3.3 输入延迟约束(set_input_delay)

输入延迟约束,是告诉工具数据从外部芯片到达FPGA引脚需要多长时间。这个时间包括PCB走线延迟、外部芯片的时钟到输出延迟(Tco)等。

语法是这样的:

set_input_delay -clock sys_clk -max 2.500 [get_ports data_in]

-max 表示最大延迟,-min 表示最小延迟。为什么要有最大最小?因为芯片的延迟有工艺偏差,你得考虑最坏情况。

实际项目中,输入延迟的计算公式是:

输入延迟 = 外部芯片Tco + PCB走线延迟

举个例子,外部芯片的Tco是1.5ns,PCB走线延迟0.8ns,那输入延迟就是2.3ns。我一般会留0.2ns的余量,写成2.5ns。

关键点:输入延迟是相对于时钟边沿的。如果数据在时钟上升沿之后到达,延迟是正值;如果数据在时钟上升沿之前到达,延迟是负值。嗯,负延迟的情况很少见,但DDR接口中确实存在。

3.4 输出延迟约束(set_output_delay)

输出延迟约束和输入延迟是对称的。它告诉工具:数据从FPGA输出后,外部芯片需要多长时间才能正确采样。

语法:

set_output_delay -clock sys_clk -max 3.000 [get_ports data_out]

输出延迟的计算公式:

输出延迟 = 外部芯片建立时间 + PCB走线延迟

比如外部芯片的建立时间是2ns,PCB走线延迟0.8ns,那输出延迟就是2.8ns。我习惯写成3ns,留点余量。

这里有个容易混淆的地方:输出延迟是相对于FPGA内部的时钟边沿,而不是外部时钟。你想想看,FPGA在时钟上升沿输出数据,数据经过PCB到达外部芯片,外部芯片要在下一个时钟上升沿采样。这个时间差就是输出延迟要约束的。

我的建议:刚开始做约束时,先别追求完美。把主时钟和生成时钟写对,输入输出延迟给个大概值,让工具先跑一遍。看到时序报告后,再根据实际情况调整。我每次都是这样迭代优化的。

3.5 四种约束的配合使用

这四种约束不是孤立的,它们共同构成了完整的时序约束体系。我画个简单的流程:

  1. 先定义所有主时钟(create_clock)
  2. 再定义所有生成时钟(create_generated_clock)
  3. 然后定义输入延迟(set_input_delay)
  4. 最后定义输出延迟(set_output_delay)

顺序很重要。你想想看,没有时钟,输入输出延迟就无从谈起。所以时钟约束必须放在最前面。

实际项目中,我还会加一些辅助约束,比如set_clock_uncertainty(时钟不确定性)、set_false_path(伪路径)等。但今天咱们先把这四种基础约束吃透。

最后提醒一句:约束文件写完后,一定要看时序报告。我见过太多人写完约束就不管了,结果工具报了一堆违例也不知道。记住,约束是写给工具看的,但最终是给人看的。

好了,今天的内容就到这里。下节课咱们聊聊如何分析时序报告,以及常见的违例怎么修。有什么问题,欢迎课后交流。