3、时序约束入门:SDC约束文件结构、时钟定义与生成时钟、输入输出延迟约束、时序例外

各位好,我是老李。在芯片后端干了十几年,我见过太多因为时序约束没写对,导致流片回来跑不起来的惨案。今天咱们就聊聊SDC约束文件——这东西说白了,就是你告诉工具:我的芯片到底该怎么跑。

3.1 SDC文件长什么样?

SDC的全称是Synopsys Design Constraints。嗯,名字挺唬人,其实就是一个文本文件。工具读它的时候,一行一行解析,告诉综合工具或STA工具:哪些路径要检查,哪些可以放水。

我个人习惯把SDC分成几个区块:

  • 时钟定义:create_clock、create_generated_clock
  • 输入输出延迟:set_input_delay、set_output_delay
  • 时序例外:false_path、multicycle_path、max_delay等
  • 其他约束:set_clock_uncertainty、set_clock_transition等

你想想看,一个芯片里成千上万条路径,工具不可能每条都按最严格的标准去检查。SDC就是告诉它:哪些是重点,哪些可以睁一只眼闭一只眼。

核心原则:SDC约束必须完整、准确、可覆盖所有关键路径。少写一条,可能就漏掉一个时序违例。

3.2 时钟定义——芯片的“心跳”

时钟是芯片里最关键的信号。没有时钟,所有寄存器都像没睡醒一样。定义时钟用create_clock命令。

create_clock -name clk_sys -period 10 [get_ports clk_in]

这条命令的意思是:在端口clk_in上创建一个名为clk_sys的时钟,周期10ns,也就是100MHz。

我在项目中遇到过一件事:有个同事把时钟周期写成了10ns,但实际PLL输出是5ns。结果综合出来的电路根本跑不到200MHz,流片回来直接废了。嗯,时钟周期写错,是新手最容易犯的错。

3.2.1 生成时钟(Generated Clock)

芯片里经常有分频、倍频的情况。比如PLL输出一个200MHz时钟,然后内部再分频成100MHz。这时候就要用create_generated_clock

create_generated_clock -name clk_div2 -source [get_pins pll/clk_out] \
  -divide_by 2 [get_pins divider/clk_out]

这里要注意:-source指定的是源时钟的引脚,不是端口。我见过有人写成get_ports,结果工具报错半天找不到原因。

小技巧:生成时钟的源时钟,最好用get_pinsget_nets,不要用get_ports。因为源时钟可能经过了很多级缓冲,端口上的波形已经不准了。

3.3 输入输出延迟约束——芯片与外界的“握手”

芯片不是孤岛。它要和外部器件通信。输入延迟告诉工具:数据从外部到达芯片引脚,花了多少时间。输出延迟告诉工具:数据从芯片引脚到外部器件,需要多少时间。

3.3.1 输入延迟

set_input_delay -clock clk_sys -max 2.5 [get_ports data_in]

这条命令的意思是:数据data_in相对于时钟clk_sys,最大延迟是2.5ns。也就是说,时钟沿到来后,数据最晚2.5ns后才能稳定。

我曾经在一个项目中,输入延迟设得太小,结果工具以为数据到得很早,把路径优化得特别激进。流片回来发现,实际数据到得晚,setup违例了。嗯,这个坑我踩过。

3.3.2 输出延迟

set_output_delay -clock clk_sys -max 4.0 [get_ports data_out]

输出延迟的意思是:外部器件需要数据在时钟沿之后4.0ns内到达。所以芯片内部必须在时钟沿之后(周期-4.0ns)内把数据送出来。

注意:输入输出延迟的数值,通常来自芯片的数据手册。不要自己拍脑袋写。我曾经见过有人写set_input_delay -max 0,结果工具以为数据零延迟到达,把内部路径压得死死的,根本没法收敛。

3.4 时序例外——给工具“开绿灯”

不是所有路径都需要按一个时钟周期来检查。有些路径天生就慢,有些路径根本不需要检查。这时候就要用时序例外。

3.4.1 False Path(假路径)

假路径就是:这条路径永远不会被激活。比如跨时钟域的同步器路径,或者测试模式下的路径。

set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]

这条命令告诉工具:从clk_a到clk_b的所有路径,都不用检查时序。

我个人习惯:写false_path时,尽量用时钟域做范围,不要用具体的引脚。因为芯片迭代时,引脚名字可能会变,但时钟域名字一般不变。

避坑指南:我曾经在一个项目中,把异步FIFO的跨时钟路径设成了false_path。结果工具真的不检查了,但实际电路里那些路径是有时序要求的。后来我改用set_clock_groups -asynchronous,更安全。

3.4.2 Multicycle Path(多周期路径)

有些路径不需要在一个时钟周期内完成。比如从慢速模块到快速模块,或者某些计算需要多个周期才能稳定。

set_multicycle_path -setup 2 -from [get_pins reg_a/CK] -to [get_pins reg_b/D]

这条命令的意思是:从reg_a到reg_b的路径,允许2个时钟周期完成setup检查。hold检查默认会跟着调整,但有时需要单独设hold。

你想想看,如果一条路径需要2个周期才能稳定,你却按1个周期去约束,工具会拼命插缓冲器,面积和功耗都上去了。多周期路径就是告诉工具:别急,慢慢来。

经验之谈:设multicycle_path时,一定要同时考虑hold。默认情况下,setup设成N,hold会变成N-1。但有些工具行为不一样,我建议显式地写set_multicycle_path -hold N-1。

3.5 知识体系总览

下面这张图,是我自己总结的SDC约束知识体系。你可以把它当成一个检查清单,写SDC时对照着看,不容易漏项。

SDC约束文件知识体系 时钟定义 输入输出延迟 时序例外 其他约束 create_clock create_generated_clock set_clock_groups set_input_delay set_output_delay set_load / set_drive set_false_path set_multicycle_path set_max_delay / set_min_delay set_clock_uncertainty set_clock_transition set_ideal_network 核心原则:完整、准确、可覆盖所有关键路径 SDC约束是芯片后端设计的“宪法”,所有时序分析都基于它展开 💡 写SDC时,建议先写时钟,再写IO延迟,最后加例外

3.6 写在最后

SDC约束这东西,看着简单,但坑特别多。我见过有人把时钟周期写反,有人把false_path写成了整个芯片,还有人忘了写生成时钟。嗯,这些我都经历过。

我的建议是:每写完一条约束,都问自己三个问题——这条约束对吗?覆盖了哪些路径?有没有遗漏?养成这个习惯,你的时序收敛会顺利很多。

好了,这一章就到这里。下一章咱们聊聊更深入的东西。


公众号:蓝海资料掘金营,微信deep3321