第二章:SDC约束编写——时钟与路径约束实战
各位同行,今天咱们聊聊SDC约束。说实话,我见过太多后端工程师在约束上栽跟头。有一次,一个同事的芯片在tapeout前三天发现时序违例,查到最后居然是create_clock写错了频率。嗯,这种低级错误,咱们得避免。
2.1 create_clock:时钟的“出生证明”
create_clock是SDC里最基础的命令。说白了,就是告诉工具:“嘿,这里有个时钟,频率是XXX”。我个人习惯在项目一开始就把所有主时钟定义清楚。
# 基本语法
create_clock -name clk_sys -period 10 [get_ports clk_in]
create_clock -name clk_io -period 20 [get_ports clk_io_pad]
这里有几个坑要注意:
- 时钟名要规范:我建议用“clk_模块名_频率”的格式,比如clk_core_500m。别用clk1、clk2这种,三个月后你自己都看不懂。
- 波形定义:默认50%占空比,但如果你用DDR接口,记得加-waveform参数。
- 虚拟时钟:做set_input_delay/set_output_delay时,经常需要虚拟时钟。我习惯加个注释,标明用途。
避坑指南:我曾经遇到一个案例,两个时钟域用了同一个时钟名,结果工具把跨时钟域路径当成了同步路径处理。嗯,那次的ECO改得我头大。
2.2 create_generated_clock:分频与倍频的“身份证”
生成时钟,就是从一个主时钟衍生出来的时钟。比如PLL出来的时钟,或者通过分频器得到的时钟。
# 二分频时钟
create_generated_clock -name clk_div2 -source [get_ports clk_in] \
-divide_by 2 [get_pins u_divider/clk_out]
# 倍频时钟(PLL输出)
create_generated_clock -name clk_pll_2x -source [get_pins u_pll/ref_clk] \
-multiply_by 2 [get_pins u_pll/clk_out]
我个人经验:生成时钟的-source一定要选对。很多人直接写主时钟端口,但如果你有多个分频路径,最好指定具体的源pin。
| 参数 | 说明 | 我的建议 |
|---|---|---|
| -divide_by | 分频系数 | 整数分频用这个,简单明了 |
| -multiply_by | 倍频系数 | PLL输出常用,注意相位关系 |
| -edges | 自定义边沿 | 复杂分频时用,比如三分频 |
小技巧:写完生成时钟后,用report_clock -skew检查一下。我每次都会跑这个命令,确保时钟树结构正确。
2.3 set_input_delay / set_output_delay:接口时序的“边界线”
这两个约束定义了芯片外部到内部,或者内部到外部的时序关系。说白了,就是告诉工具:“数据从外面进来时,相对于时钟是什么时候到的?”
# 输入延迟:数据在时钟沿前2ns到达
set_input_delay -clock clk_sys -max 2.0 [get_ports data_in*]
set_input_delay -clock clk_sys -min 0.5 [get_ports data_in*]
# 输出延迟:数据需要在时钟沿后1.5ns稳定
set_output_delay -clock clk_sys -max 1.5 [get_ports data_out*]
set_output_delay -clock clk_sys -min 0.3 [get_ports data_out*]
你想想看,如果输入延迟设大了,工具会拼命优化内部路径,导致面积和功耗飙升。设小了,又可能漏掉真正的违例。我一般会先拿一个保守值,然后根据后端结果逐步收紧。
注意:-max和-min都要设!我曾经见过只设-max的,结果hold违例全漏了。嗯,那次ECO花了整整一周。
2.4 set_false_path:那些“不用管”的路径
false path就是不需要时序检查的路径。比如异步复位、测试模式下的路径、跨时钟域同步器路径。
# 异步复位路径
set_false_path -from [get_ports rst_n]
# 跨时钟域路径(已用同步器处理)
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
# 测试模式路径
set_false_path -from [get_ports scan_enable]
我个人习惯:false path一定要加注释,说明为什么是false path。不然三个月后你回来看,根本想不起来当初为什么这么设。
避坑指南:我曾经犯过一个错误,把真正的同步路径设成了false path。结果芯片功能正常,但时序报告里一片绿,后来发现是约束把关键路径给“隐藏”了。从那以后,我每次设false path都会double-check。
2.5 set_multicycle_path:多周期路径的“特殊待遇”
有些路径不需要在一个时钟周期内完成。比如从慢速模块到快速模块的数据传输,或者某些计算密集型逻辑。
# 数据需要2个周期才能稳定
set_multicycle_path -setup 2 -from [get_pins u_slow/clk] -to [get_pins u_fast/data]
# 对应的hold也要调整
set_multicycle_path -hold 1 -from [get_pins u_slow/clk] -to [get_pins u_fast/data]
这里有个关键点:设了-setup 2之后,-hold要设1。为什么?因为hold检查默认在setup检查的前一个周期。如果你不调整hold,工具会检查一个不存在的路径。
| 场景 | setup值 | hold值 |
|---|---|---|
| 2周期路径 | 2 | 1 |
| 3周期路径 | 3 | 2 |
| N周期路径 | N | N-1 |
我的经验:多周期路径一定要和前端确认。我遇到过前端说“这个路径可以放宽”,结果后端设了多周期后,功能仿真就挂了。嗯,沟通很重要。
2.6 实战总结:一个完整的SDC示例
最后,我给大家一个完整的SDC片段。这是我个人常用的模板,你可以直接拿来改。
# ========== 时钟定义 ==========
create_clock -name clk_sys -period 10 [get_ports clk_in]
create_clock -name clk_io -period 20 [get_ports clk_io_pad]
# 生成时钟
create_generated_clock -name clk_core -source [get_ports clk_in] \
-divide_by 2 [get_pins u_pll/clk_core_out]
# ========== 接口约束 ==========
set_input_delay -clock clk_sys -max 2.0 [get_ports data_in*]
set_input_delay -clock clk_sys -min 0.5 [get_ports data_in*]
set_output_delay -clock clk_sys -max 1.5 [get_ports data_out*]
set_output_delay -clock clk_sys -min 0.3 [get_ports data_out*]
# ========== 例外路径 ==========
set_false_path -from [get_ports rst_n] # 异步复位
set_false_path -from [get_clocks clk_sys] -to [get_clocks clk_io] # 跨时钟域
# 多周期路径
set_multicycle_path -setup 2 -from [get_pins u_slow/clk] -to [get_pins u_fast/data]
set_multicycle_path -hold 1 -from [get_pins u_slow/clk] -to [get_pins u_fast/data]
好了,这一章的内容就到这里。记住,SDC约束是时序收敛的第一步。写错了,后面所有努力都白费。下一章咱们聊聊时钟树综合,那才是真正的“手艺活”。