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。

避坑指南:多周期约束一定要同时设setup和hold。我见过有人只设setup,结果hold检查出错,工具报一堆违例。

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就有章可循了。

SDC约束知识体系 时钟定义 主时钟 create_clock 生成时钟 create_generated_clock 时钟组 set_clock_groups 虚拟时钟 create_clock -name 输入输出延迟 输入输出延迟 虚假路径 set_false_path 多周期路径 set_multicycle_path 最大/最小延迟 set_max/min_delay 目标:让工具准确分析时序,避免过约束或欠约束

3.7 常见错误与调试技巧

写SDC难免出错。我总结几个常见问题:

  • 时钟没定义就使用:比如在set_input_delay里引用了一个没定义的时钟。工具会报错,但有时候报错信息不明显。
  • 虚假路径设多了:导致真正的时序问题被掩盖。建议先跑一遍STA,看看哪些路径违例,再决定是否设虚假路径。
  • 多周期路径的hold没设:前面说过,hold要跟着调整。忘了设hold,工具会按默认规则检查,可能报一堆假违例。
  • 时钟组设错了:把同步时钟设成异步,或者反过来。同步时钟设成异步,工具不分析,可能漏掉问题。异步时钟设成同步,工具跑得慢,还报一堆假违例。
调试建议:先用report_clock检查时钟定义,再用report_timing看关键路径。如果违例很多,先检查时钟和延迟约束,别急着调电路。

好了,SDC约束就聊到这儿。记住,约束是写给工具看的,也是写给未来自己看的。写清楚、写规范,后面省心很多。

可可精品资料库