3. SDC约束编写实战:时钟定义、输入输出延迟、虚假路径、多周期路径
各位好,我是老李。今天咱们聊聊SDC约束。说实话,这玩意儿看着简单,但坑特别多。我见过不少项目,RTL仿真跑得飞起,一到后端就崩,十有八九是约束没写好。
SDC,全称Synopsys Design Constraints。说白了,就是告诉工具:你的设计要跑多快、信号什么时候来、哪些路径不用管。嗯,咱们一个一个说。
3.1 时钟定义:一切的基础
时钟是时序分析的基准。时钟定义错了,后面全白搭。我个人习惯,拿到设计先看时钟结构,再动手写约束。
3.1.1 主时钟定义
主时钟通常来自PLL或外部晶振。用create_clock命令定义。举个例子:
create_clock -name clk_sys -period 10.0 [get_ports clk_in]
这里-period 10.0表示周期10ns,对应100MHz。[get_ports clk_in]指定时钟源端口。
clk_前缀加功能名,比如clk_cpu、clk_ddr。后期查问题方便。
3.1.2 生成时钟
分频时钟、门控时钟都属于生成时钟。用create_generated_clock定义。比如:
create_generated_clock -name clk_div2 -source [get_ports clk_in] \
-divide_by 2 [get_pins u_div/clk_out]
这里-source指定源时钟,-divide_by 2表示二分频。注意,生成时钟的源必须是已定义的时钟。
3.1.3 时钟组与异步时钟
不同时钟域之间,如果没做同步处理,必须设为异步。用set_clock_groups:
set_clock_groups -asynchronous \
-group {clk_sys clk_div2} \
-group {clk_ddr}
这样工具就不会分析跨时钟域的路径,省时省力。
3.2 输入输出延迟:告诉工具信号什么时候来
芯片不是孤岛。输入信号从外部来,输出信号要送到外部去。工具需要知道这些信号相对于时钟的时序关系。
3.2.1 输入延迟
用set_input_delay定义。比如:
set_input_delay -clock clk_sys -max 2.0 [get_ports data_in]
这表示data_in在时钟沿后最多2ns到达。为什么是2ns?因为外部器件有输出延迟,加上PCB走线延迟,加起来就是输入延迟。
我在项目中遇到过一个问题:输入延迟设得太紧,工具拼命优化,结果面积暴涨。后来放宽了0.5ns,面积降了15%,时序还满足。所以,输入延迟要实事求是,别为了安全故意设得很小。
3.2.2 输出延迟
类似地,用set_output_delay:
set_output_delay -clock clk_sys -max 3.0 [get_ports data_out]
这表示data_out在时钟沿前必须稳定3ns。为什么是3ns?因为外部器件有建立时间要求,加上PCB走线延迟。
3.3 虚假路径:别让工具白费力气
有些路径,功能上永远不会被激活。比如测试模式下的路径、复位路径、静态配置信号。这些路径不需要时序分析,设为虚假路径。
用set_false_path:
set_false_path -from [get_clocks clk_test] -to [get_clocks clk_sys]
set_false_path -through [get_pins u_scan/scan_en]
嗯,这里要注意:虚假路径设多了,可能漏掉真正的时序问题。设少了,工具跑得慢,还可能报一堆假违例。
我曾经犯过一个错:把某个跨时钟域的路径设成虚假路径,结果流片回来发现数据偶尔出错。后来查出来,那个路径其实有同步器,但同步器没做好,数据还是可能出错。所以,虚假路径只用于确定不会激活的路径,别偷懒。
3.4 多周期路径:给慢信号多点时间
有些路径,数据不需要在一个时钟周期内到达。比如从慢速外设读数据、或者经过复杂组合逻辑的路径。这时候用多周期约束。
用set_multicycle_path:
set_multicycle_path -setup 2 -from [get_pins u_reg1/Q] -to [get_pins u_reg2/D]
set_multicycle_path -hold 1 -from [get_pins u_reg1/Q] -to [get_pins u_reg2/D]
这里-setup 2表示数据需要2个时钟周期才能到达。-hold 1表示保持检查相对于启动沿偏移1个周期。为什么hold要设1?因为setup设了2,hold默认会跟着变,但实际hold检查应该用原始沿,所以设1。
3.5 实战案例:一个完整的SDC文件
说了这么多,咱们看个完整的例子。假设有个设计,包含系统时钟、DDR接口、测试模式。
# 时钟定义
create_clock -name clk_sys -period 10.0 [get_ports clk_in]
create_clock -name clk_ddr -period 5.0 [get_ports clk_ddr_in]
# 生成时钟
create_generated_clock -name clk_sys_div2 -source [get_ports clk_in] \
-divide_by 2 [get_pins u_pll/clk_out]
# 异步时钟组
set_clock_groups -asynchronous \
-group {clk_sys clk_sys_div2} \
-group {clk_ddr}
# 输入延迟
set_input_delay -clock clk_sys -max 2.5 [get_ports data_in]
set_input_delay -clock clk_sys -min 0.5 [get_ports data_in]
# 输出延迟
set_output_delay -clock clk_ddr -max 1.5 [get_ports ddr_dq]
set_output_delay -clock clk_ddr -min 0.2 [get_ports ddr_dq]
# 虚假路径
set_false_path -from [get_clocks clk_test] -to [get_clocks clk_sys]
set_false_path -through [get_pins u_scan/scan_en]
# 多周期路径
set_multicycle_path -setup 2 -from [get_pins u_slow/q] -to [get_pins u_fast/d]
set_multicycle_path -hold 1 -from [get_pins u_slow/q] -to [get_pins u_fast/d]
这个例子涵盖了大部分常见场景。实际项目中,SDC文件可能几百行,但核心逻辑就这些。
3.6 知识体系图
下面这张图总结了SDC约束的核心逻辑。你看,时钟定义是根,输入输出延迟是枝叶,虚假路径和多周期路径是修剪工具。理解了这张图,写SDC就有章可循了。
3.7 常见错误与调试技巧
写SDC难免出错。我总结几个常见问题:
- 时钟没定义就使用:比如在
set_input_delay里引用了一个没定义的时钟。工具会报错,但有时候报错信息不明显。 - 虚假路径设多了:导致真正的时序问题被掩盖。建议先跑一遍STA,看看哪些路径违例,再决定是否设虚假路径。
- 多周期路径的hold没设:前面说过,hold要跟着调整。忘了设hold,工具会按默认规则检查,可能报一堆假违例。
- 时钟组设错了:把同步时钟设成异步,或者反过来。同步时钟设成异步,工具不分析,可能漏掉问题。异步时钟设成同步,工具跑得慢,还报一堆假违例。
report_clock检查时钟定义,再用report_timing看关键路径。如果违例很多,先检查时钟和延迟约束,别急着调电路。
好了,SDC约束就聊到这儿。记住,约束是写给工具看的,也是写给未来自己看的。写清楚、写规范,后面省心很多。
可可精品资料库