3. 时序约束入门:创建时钟、设置输入延迟、设置输出延迟
时序约束这东西,说白了就是告诉工具你的芯片想跑多快、数据什么时候来、什么时候走。我刚入行那会儿觉得这玩意儿就是走个过场,直到有一次综合出来的网表时序惨不忍睹,才发现约束没写对。嗯,从那以后我再也不敢小看这一步了。
3.1 创建时钟——一切时序分析的起点
时钟是时序分析的脉搏。没有时钟,工具根本不知道拿什么当参考。你想想看,所有寄存器的数据传递都是靠时钟沿来触发的,所以第一步就是把时钟定义清楚。
创建时钟用 create_clock 命令。我个人习惯在 SDC 文件的开头就把所有主时钟定义好,这样后面设置输入输出延迟时心里有底。
# 创建一个50MHz的时钟,占空比50%
create_clock -name clk -period 20.0 [get_ports clk]
这里 -period 20.0 表示周期是20ns,对应50MHz。注意单位是纳秒,别写成毫秒,那可就闹笑话了。
关键点:时钟源必须是顶层端口(port),不能是内部节点。如果你需要定义内部时钟,得用 create_generated_clock。
我在项目中遇到过一种情况:芯片有多个时钟域,比如CPU核跑500MHz,外设总线只跑100MHz。这时候就得分别创建两个时钟,并且用 set_clock_groups 告诉工具它们之间不需要做时序检查,否则工具会傻乎乎地去分析跨时钟域路径,浪费大量时间。
# 创建两个异步时钟
create_clock -name cpu_clk -period 2.0 [get_ports cpu_clk]
create_clock -name bus_clk -period 10.0 [get_ports bus_clk]
# 声明异步时钟组
set_clock_groups -asynchronous -group {cpu_clk} -group {bus_clk}
小技巧:如果你不确定时钟树上的抖动(jitter)和延迟(latency),可以先不设,工具会用默认值。但做 signoff 时一定要加上实际值,否则流片回来可能跑不到目标频率。
3.2 设置输入延迟——告诉工具数据从哪来
输入延迟描述的是外部器件把数据送到芯片引脚的时间。说白了,就是数据相对于时钟沿是提前到还是晚到。
设置输入延迟用 set_input_delay 命令。我记得第一次用这个命令时,搞不清楚 -max 和 -min 的区别。后来才明白:-max 对应建立时间检查(setup),-min 对应保持时间检查(hold)。
# 假设外部器件在时钟上升沿后5ns输出数据
# 那么输入延迟就是5ns(相对于时钟沿)
set_input_delay -clock clk -max 5.0 [get_ports data_in]
set_input_delay -clock clk -min 2.0 [get_ports data_in]
这里 -max 5.0 表示数据最晚在时钟沿后5ns到达,-min 2.0 表示数据最早在时钟沿后2ns到达。为什么要有两个值?因为实际电路中,路径延迟有 PVT 变化,最快和最慢差很多。
避坑指南:我曾经犯过一个错误——只设了 -max 没设 -min。结果保持时间检查时工具用了默认值0,导致 hold 违例没被发现。后来 ECO 改版才修掉,多花了一周时间。所以 -max 和 -min 一定要成对出现。
输入延迟的值怎么定?通常看外部器件的 datasheet。比如一个 SPI 从机,数据在时钟沿后 3~7ns 输出,那你就设 -min 3.0 和 -max 7.0。如果拿不准,可以留点余量,比如加个 0.5ns 的 margin。
3.3 设置输出延迟——告诉工具数据往哪去
输出延迟和输入延迟是对称的。它描述的是芯片内部数据从时钟沿开始,到出现在输出引脚上,需要多长时间被外部器件正确采样。
设置输出延迟用 set_output_delay。你想想看,外部器件也有自己的建立时间和保持时间要求,所以输出延迟本质上是在模拟外部器件的时序窗口。
# 假设外部器件要求数据在时钟沿前2ns稳定
# 那么输出延迟就是2ns(相对于时钟沿)
set_output_delay -clock clk -max 2.0 [get_ports data_out]
set_output_delay -clock clk -min 1.0 [get_ports data_out]
这里 -max 2.0 表示数据必须在时钟沿前2ns到达输出引脚(满足外部器件的建立时间),-min 1.0 表示数据必须在时钟沿后至少保持1ns(满足外部器件的保持时间)。
核心逻辑:输出延迟的值 = 外部器件的建立/保持时间 + 板级走线延迟。如果板级走线很短,可以忽略,直接取外部器件的时序参数。
我在项目中遇到过一种情况:输出延迟设得太紧,导致内部逻辑无法满足时序。后来和系统工程师一聊,发现外部器件的建立时间其实没那么苛刻,放宽了0.5ns后,内部路径就收敛了。所以有时候别死磕,多和上下游沟通一下。
3.4 知识体系一览
下面这张图把本章的核心逻辑串起来了。你一看就明白:时钟是基准,输入延迟管外部到内部,输出延迟管内部到外部。
3.5 实战要点总结
说了这么多,我帮你把关键点捋一捋:
- 创建时钟:用
create_clock定义主时钟,注意时钟源必须是端口。多时钟域用set_clock_groups隔离。 - 输入延迟:用
set_input_delay描述外部数据到达时间。-max管 setup,-min管 hold,两个都要设。 - 输出延迟:用
set_output_delay描述外部器件采样要求。值来自 datasheet,别忘了加板级走线延迟。
个人经验:我建议你在写约束时,先画一张简单的时序图,把时钟沿、数据窗口、延迟值都标出来。这样不容易出错。尤其是刚接触时序约束的工程师,画图比直接写命令靠谱得多。
嗯,这一章的内容就到这里。记住:约束写对了,时序分析才有意义。否则工具再强大,也是巧妇难为无米之炊。