2、SDC约束基础:SDC文件结构、时钟定义、生成时钟与时钟组
各位同学,咱们今天聊聊SDC约束。说实话,我刚入行那会儿,觉得SDC就是个麻烦事——不就是告诉工具时钟跑多快嘛?后来吃过亏才明白,约束写不好,后端跑出来的结果全是坑。
SDC,全称Synopsys Design Constraints,是业界通用的时序约束格式。Siemens EDA的工具也完全支持它。你想想看,一个芯片里成百上千条路径,工具怎么知道哪些路径重要?全靠SDC告诉它。
2.1 SDC文件结构
一个规范的SDC文件,我习惯把它分成三大块:
- 时钟定义:告诉工具时钟长什么样——频率、占空比、抖动
- IO约束:输入输出端口相对于时钟的时序关系
- 例外约束:false path、multicycle path这些特殊路径
我个人习惯把SDC文件按模块拆分,顶层只做时钟定义和IO约束,子模块的例外约束放在各自目录下。这样后期调试时,找问题快得多。
2.2 时钟定义:create_clock
时钟是时序分析的基准。没有时钟,工具根本不知道什么时候该采样数据。
最基本的时钟定义长这样:
create_clock -name clk_sys -period 10.0 [get_ports clk_in]
这条命令的意思是:在端口clk_in上创建一个名为clk_sys的时钟,周期10ns,也就是100MHz。
嗯,这里要注意——-name参数不是必须的,但我强烈建议加上。为什么?因为工具会自动生成一个名字,那名字又长又难记,你后面写约束时根本不想用它。
时钟还可以定义占空比和边沿:
create_clock -name clk_ddr -period 5.0 \
-waveform {0.0 2.5} [get_ports clk_ddr_in]
-waveform后面跟两个值:上升沿时间和下降沿时间。上面这个例子,上升沿在0ns,下降沿在2.5ns,占空比50%。
-waveform的默认值是{0 period/2},也就是50%占空比。如果你的时钟不是50%,一定要显式指定。
我在项目中遇到过一件事:有个同事定义时钟时没写-name,结果工具自动生成了clk_in作为时钟名。后来他写生成时钟时引用了这个名字,但端口名和时钟名混在一起,查了半天才找到问题。从那以后,我每个时钟都手动命名。
2.3 生成时钟:create_generated_clock
生成时钟,说白了就是由主时钟分频、倍频或相移得到的时钟。比如PLL输出的时钟,或者寄存器分频后的时钟。
基本语法:
create_generated_clock -name clk_div2 \
-source [get_ports clk_in] \
-divide_by 2 \
[get_pins u_divider/Q]
这条命令的意思是:在u_divider/Q这个引脚上,创建一个名为clk_div2的生成时钟,它由clk_in二分频得到。
常用的参数有:
| 参数 | 说明 | 示例 |
|---|---|---|
| -divide_by | 分频系数 | -divide_by 2 |
| -multiply_by | 倍频系数 | -multiply_by 2 |
| -invert | 反相 | -invert |
| -edges | 自定义边沿 | -edges {1 3 5} |
| -edge_shift | 边沿偏移 | -edge_shift {0 0.5 0} |
这里有个坑,我曾经踩过——-source参数指定的是主时钟的源点,不是主时钟的名字。很多人写成-source [get_clocks clk_in],这是错的。应该写成-source [get_ports clk_in]或者-source [get_pins ...]。
-source必须指向物理节点(端口或引脚),不能指向时钟对象。这是新手最容易犯的错误之一。
再举个复杂点的例子——DDR时钟,需要产生相移90度的时钟:
create_generated_clock -name clk_ddr_90 \
-source [get_ports clk_in] \
-edges {1 2 3} \
-edge_shift {0 1.25 0} \
[get_pins u_pll/clk_out]
这个例子中,-edges {1 2 3}表示继承主时钟的第1、2、3个边沿,-edge_shift给第二个边沿偏移了1.25ns,也就是90度相移。
2.4 时钟组:set_clock_groups
时钟组的作用,说白了就是告诉工具:这些时钟之间不需要做时序分析。最常见的场景是异步时钟域。
基本语法:
set_clock_groups -asynchronous \
-group [get_clocks clk_a] \
-group [get_clocks clk_b]
这条命令的意思是:clk_a和clk_b是异步时钟,它们之间的路径不需要分析。
除了-asynchronous,还有-physically_exclusive和-logically_exclusive:
- -asynchronous:异步时钟,频率和相位都没关系
- -physically_exclusive:物理上互斥,比如两个时钟不会同时出现在芯片上
- -logically_exclusive:逻辑上互斥,比如MUX选择的两个时钟
我个人习惯,对于真正的异步时钟域,一定用-asynchronous。对于MUX选择的时钟,用-logically_exclusive。别混用,否则工具可能会漏掉一些该分析的路径。
-group中。如果你想把多个时钟放在一组,用空格隔开就行:set_clock_groups -asynchronous -group {clk_a clk_b} -group {clk_c}
我曾经在一个项目里看到有人这么写:
set_clock_groups -asynchronous \
-group [get_clocks clk_a] \
-group [get_clocks clk_a]
同一个时钟出现在两个组里,工具直接报错。嗯,这种低级错误其实很容易犯,特别是项目后期时钟定义改来改去的时候。
2.5 实战建议
最后,我给大家几个实战中的小建议:
- 时钟命名要规范:我习惯用
clk_前缀,后面跟功能名和频率,比如clk_core_500m - 生成时钟一定要加
-master吗? 不一定。但如果你有多个主时钟到达同一个生成点,就必须用-master指定用哪个 - 时钟组要写注释:说明为什么这些时钟是异步的,方便后人理解
- 写完约束后跑一下
report_clock:看看时钟定义是否符合预期,这一步能发现很多低级错误
好了,SDC约束基础就讲到这里。下一章咱们聊聊IO约束和时序例外,那才是真正考验功力的地方。