时序约束入门:create_clock、set_input_delay、set_output_delay、set_max_delay/set_min_delay
时序约束,说白了就是给芯片设计定规矩。你告诉工具:信号什么时候来,什么时候走,中间能等多久。没有规矩,工具就乱来,最后流片回来跑不动,哭都来不及。
我个人习惯,拿到一个新设计,第一件事就是先把时钟约束好。时钟是芯片的脉搏,脉搏乱了,什么都白搭。今天咱们就把最基础的四个约束讲透。
1. create_clock:给芯片定心跳
时钟约束是所有约束的根基。你想想看,没有时钟,工具怎么知道信号什么时候该到?
基本语法很简单:
create_clock -name clk_sys -period 10 [get_ports clk]
这里 -period 10 表示周期10ns,也就是100MHz。我刚开始做设计时,总觉得周期设大一点就安全,结果有一次设了20ns,工具报的时序全绿,流片回来却跑不到50MHz。后来才发现,约束太松,工具优化不积极,实际性能就上不去。
几个关键参数:
- -name:给时钟起个名字,方便后面引用
- -period:时钟周期,单位ns
- [get_ports clk]:指定时钟从哪个端口进来
重要提醒:时钟名字不要乱起。我见过有人把两个时钟都叫clk,结果工具分不清,时序分析全乱套。每个时钟必须有唯一的名字。
如果时钟有抖动(jitter),可以用 -jitter 参数:
create_clock -name clk_sys -period 10 -jitter 0.2 [get_ports clk]
抖动值一般从芯片手册里查。我建议留点余量,比如手册写0.15ns,你可以设0.2ns。为什么?因为实际PCB走线、电源噪声都会增加抖动,留点余量不吃亏。
2. set_input_delay:管好进来的信号
输入延迟,就是信号从芯片外部到内部寄存器之间的时间。这个时间包括PCB走线、外部器件的输出延迟等。
语法长这样:
set_input_delay -clock clk_sys -max 2.5 [get_ports data_in]
-max 表示最大延迟,-min 表示最小延迟。为什么要分最大最小?因为建立时间检查看最大路径,保持时间检查看最小路径。
我曾经在一个项目中,只设了最大延迟没设最小延迟,结果保持时间违例一大堆。后来花了三天才排查出来,原来是输入信号来得太快,内部寄存器来不及保持数据。
我的经验:输入延迟的值怎么定?看外部器件的datasheet。比如外部芯片输出延迟是1.5ns,PCB走线延迟0.5ns,那最大延迟就设2.0ns。最小延迟一般设0.5ns左右,留点余量。
如果输入信号是总线,可以这样写:
set_input_delay -clock clk_sys -max 2.5 [get_ports {data_in[0] data_in[1] data_in[2]}]
或者用通配符:
set_input_delay -clock clk_sys -max 2.5 [get_ports data_in[*]]
3. set_output_delay:管好出去的信号
输出延迟和输入延迟是对称的。它表示信号从内部寄存器到芯片输出端口的时间。
set_output_delay -clock clk_sys -max 3.0 [get_ports data_out]
这里3.0ns的意思是:数据从内部寄存器出来,经过组合逻辑、输出缓冲、PCB走线,到外部器件的时间不能超过3.0ns。
嗯,这里要注意:输出延迟是相对于时钟沿的。如果外部器件是上升沿采样,那输出延迟就是相对于上升沿。如果是下降沿,那就得用 -clock_fall 参数。
避坑指南:我曾经犯过一个错,把输出延迟设得太小,结果工具为了满足约束,拼命加buffer,面积大了30%,功耗也上去了。后来发现,其实外部器件的建立时间要求没那么严,放宽0.5ns就全解决了。
输出延迟的典型值怎么算?举个例子:外部芯片的建立时间要求是1.0ns,PCB走线延迟0.5ns,那输出延迟就设1.5ns。保持时间类似,看外部芯片的保持时间要求。
4. set_max_delay / set_min_delay:特殊路径的紧箍咒
这两个约束是给特殊路径用的。比如异步信号、跨时钟域路径、或者一些不要求严格时序的路径。
基本用法:
set_max_delay 5.0 -from [get_pins reg_a/CK] -to [get_pins reg_b/D]
set_min_delay 1.0 -from [get_pins reg_a/CK] -to [get_pins reg_b/D]
这里的意思是:从reg_a的时钟引脚到reg_b的数据引脚,最大延迟5.0ns,最小延迟1.0ns。
什么时候用这个?我举个例子:
- 异步复位释放:复位信号不需要像时钟那么严格,但也不能太慢,设个最大延迟就行
- 测试模式路径:测试模式下,有些路径可以放宽要求
- 跨时钟域路径:如果两个时钟域之间用了同步器,那路径延迟可以设大一点
核心要点:set_max_delay/set_min_delay 会覆盖时钟约束。如果你设了set_max_delay 5.0,那工具就不管时钟周期是多少,只保证这条路径在5.0ns以内。所以用的时候要小心,别把正常的时钟路径给覆盖了。
我个人习惯,用这两个约束时,一定要加 -from 和 -to 限定范围。不加范围的话,整个设计的所有路径都会被覆盖,那后果...你想想看,所有路径都按5.0ns约束,时钟周期10ns的路径就浪费了。
知识体系总览
下面这张图,把四个约束的关系画清楚了:
这张图把四个约束的关系讲清楚了。时钟约束是基础,输入输出约束管着芯片边界,特殊约束处理例外情况。所有约束最终都归结到建立时间和保持时间检查。
我的建议:刚开始学约束,别贪多。先把create_clock和set_input_delay/set_output_delay练熟。这三个用好了,90%的时序问题都能搞定。set_max_delay/set_min_delay是进阶内容,等遇到特殊路径再学也不迟。
最后说一句,约束文件写完后,一定要跑一遍STA看看有没有unconstraint的路径。我见过有人写了1000行约束,结果漏了一个时钟,工具默默地把那条路径当成了false path,流片回来才发现。嗯,这种坑,踩过一次就记住了。