时序约束基础:什么是时序约束,SDC文件结构
各位好。咱们正式开始第2章。
上一章聊了LINCS项目里约束激增的麻烦。但在动手排查之前,你总得知道「约束」到底是什么东西。我个人觉得,很多工程师被激增搞到头大,根本原因不是工具报错多,而是对约束本身的理解还停留在「抄上一个项目的」水平。
这一章,我们把时序约束和SDC文件掰开揉碎。嗯,都是基础,但基础不牢,后面排查起来会非常痛苦。
什么是时序约束
说白了,时序约束就是你告诉EDA工具的一句话:「我这个设计要在多快的时钟下跑,哪些路径可以慢一点,哪些路径一点都不能慢。」
我有个比喻:你想象一下,你给一张芯片画好了所有走线的地图。时序约束就是那个限速牌——哪个路段必须跑300MHz,哪个路段可以慢悠悠走,哪个交叉口必须确保信号1ns内到达。没有这些限速牌,工具就像没有导航的司机,要么乱开,要么超速撞车。
时序约束的目标有三个:
- 保证芯片能在目标频率下稳定工作
- 告诉工具哪些路径是「假的」,不用管
- 让工具知道每条路径的时间预算,从而优化面积和功耗
你想想看,如果什么都不约束,工具默认所有路径都要在最短时间内走完。结果就是面积巨大、功耗爆炸、布线绕成麻花。后来我发现,很多人只会加一个create_clock就跑STA,然后报了一堆违例也不知道怎么调。其实就是没搞懂约束的真正作用。
避坑指南
我曾经在一个SoC项目中接手过别人的约束文件。打开一看,光时钟就定义了80多个,大部分还是无脑复制粘贴。后来发现,其中20多个时钟根本不存在于RTL中。这种冗余约束不仅拖慢运行时间,还会让工具多出大量假违例。嗯,从那时起,我养成了一个习惯:每写一句约束,都要问自己一句「这个约束到底在管哪条路径?」
SDC文件的结构
SDC是Synopsys Design Constraints的缩写。它是个文本文件,后缀一般是.sdc。你别以为它是Synopsys私有的东西——现在所有主流EDA工具都认它。
SDC的内容其实挺有规律的。我习惯把它分成几大块:
| 约束类别 | 主要命令 | 作用 |
|---|---|---|
| 时钟定义 | create_clock, create_generated_clock | 定义时钟周期、波形、源点 |
| 时钟组 | set_clock_groups | 告诉工具哪些时钟是异步的,不用分析 |
| 输入/输出延迟 | set_input_delay, set_output_delay | 定义端口到外部器件的时序窗口 |
| 异常路径 | set_false_path, set_multicycle_path | 跳过或放宽某些路径的分析 |
| 驱动/负载 | set_drive, set_load | 模拟端口的外部驱动能力和负载 |
| 线载模型(较少用) | set_wire_load_model | 早期工艺使用,现在很少见了 |
你看,其实就这么几类。我见过最乱的约束文件,就是把这些命令全部混在一起写,时钟定义、例外路径、IO延迟全堆在一堆,根本分不清哪句管什么。我个人习惯是按照上面这个表格顺序来组织SDC,每一类之间加注释分隔。
举个例子,一份结构清晰的SDC开头应该是这样的:
# ========================================
# Clock Definitions
# ========================================
create_clock -name sys_clk -period 10.000 [get_ports sys_clk]
create_generated_clock -name div_clk -divide_by 2 \
-source [get_ports sys_clk] [get_pins u_div/clk_out]
# ========================================
# Clock Groups
# ========================================
set_clock_groups -asynchronous \
-group {sys_clk} \
-group {div_clk}
# ========================================
# Input and Output Delays
# ========================================
set_input_delay -clock sys_clk -max 2.000 [get_ports data_in*]
set_output_delay -clock sys_clk -min 0.500 [get_ports data_out*]
# ========================================
# False Paths and Multicycle Paths
# ========================================
set_false_path -from [get_ports rst_n]
set_multicycle_path -setup 2 -from [get_clocks div_clk] -to [get_clocks sys_clk]
# ========================================
# Drive and Load
# ========================================
set_drive 1.0 [get_ports data_in*]
set_load 0.5 [get_ports data_out*]
你看,用注释隔开,结构一目了然。我每次接手新项目的SDC,第一件事就是先用这种格式帮它重构一遍。通常重构完,就能发现很多冗余或矛盾的约束。
常见误区
不要以为SDC文件越长越好。我见过一个设计只有500行RTL代码,SDC却有800行。你以为这样很齐全,其实里面有大量无用的set_false_path。嗯,如果你发现工程里SDC比RTL还长,就要警惕了——大概率是约束激增的早期信号。
SDC文件的核心命令详解
重点说几个最常用的命令。这些是我在日常排查中频繁打交道的。
1. create_clock
这是SDC里最重要的命令。定义了时钟的周期和波形。注意,它定义的是时钟源,而不是时钟树上的内部节点。通常加在port上。
create_clock -name clk_core -period 5.000 [get_ports clk_core_in]
这个命令说明clk_core_in这个端口进来的时钟周期是5ns,也就是200MHz。默认波形是50%占空比,上升沿在0时刻,下降沿在2.5ns时刻。
2. create_generated_clock
分频或倍频后的时钟。它需要指定源时钟和分频关系。
create_generated_clock -name clk_halved \
-divide_by 2 -source [get_ports clk_core_in] \
[get_pins u_divider/clk_out]
这里容易搞错的是-source参数。它指向的是源时钟所在的端口或引脚,而不是源时钟的时钟名。我见过有人莫名其妙写-source [get_clocks clk_core],结果工具报错找不到对象——因为get_clocks返回的是时钟对象,但-source需要的是物理节点。
3. set_clock_groups
这个命令能帮工具省下大量STA时间。如果两个时钟是异步的,直接告诉工具它们永远不会同步,路径不用分析。
set_clock_groups -asynchronous \
-group {clk_a clk_b} \
-group {clk_c}
意思是clk_a和clk_b属于同一组,它们与clk_c是异步的。不同组之间的路径,工具全部跳过。
4. set_false_path
这个最容易被滥用。正确的用法是:确定这条路径永远不会影响芯片功能——比如测试模式下的路径、复位信号、跨异步时钟域的安全路径。但我见过有人在SDC里写了几百条set_false_path,只为了掩盖STA违例。
我的经验
每一次使用set_false_path,都应该在注释里写清楚理由。比如这样:
# Reset is asynchronous, no need to check timing
set_false_path -from [get_ports rst_n]
如果以后有人来排查约束激增问题,看到注释就能知道这条约束到底是必要的还是多余的。
5. set_multicycle_path
当一条数据路径需要多个时钟周期才能稳定,就用这个命令。典型场景是握手协议或片内SRAM的读操作。
set_multicycle_path -setup 2 -from [get_pins u_controller/data_reg/CK] \
-to [get_pins u_ram/address]
这里-setup 2表示数据可以在2个时钟周期后到达。注意,工具默认还会同时调整hold检查,所以通常还要配合-hold参数。嗯,这个有点绕,后面专门讲。
SDC的执行顺序
工具读取SDC时,是按行从上往下执行的。写在前面的约束可能会影响后面的约束行为。举个例子:
create_clock -name clk_vco -period 2.000 [get_pins u_pll/clk_vco]
create_clock -name clk_final -period 4.000 [get_ports clk_final_in]
如果先写了pll的时钟,后写了端口的时钟,那么工具在处理后面这条命令时,已经知道前面有个200MHz的时钟存在。如果两条时钟有路径交叉,工具会按后来定义的时钟做分析。
我建议把时钟定义放在最前面。然后是时钟组、IO延迟、异常路径。这样执行顺序最安全。
一个小技巧
可以用report_clock命令来验证SDC里的时钟定义是否和预期一致。我一般在写完约束后,先跑一下report_clock,看看时钟数量、周期、源点有没有问题。这能发现大多数笔误。
本章小结
时序约束就是给工具的一张限速地图。SDC文件是这张地图的载体。它的结构其实很清晰:时钟定义→时钟组→IO延迟→异常路径→驱动/负载。只要按这个顺序组织,后面的排查工作就会轻松很多。
下一节我们会深入时钟约束的具体写法。但在此之前,我建议你把手里项目的SDC文件打开,按照今天的结构重构一遍。相信我,你会发现不少问题的苗头。