3. 约束文件基础:SDC语法入门,创建时钟、生成时钟、虚拟时钟
各位做FPGA或IC设计的朋友,咱们今天聊聊约束文件。说白了,SDC(Synopsys Design Constraints)就是告诉工具你的设计长什么样、想跑多快的一张“说明书”。没有它,工具就像蒙着眼睛走路——时序乱得一塌糊涂。
我个人习惯,拿到一个新项目,第一件事不是看RTL代码,而是先把时钟约束搭起来。为什么?因为时钟是时序分析的“心脏”,时钟没定好,后面所有分析都是白搭。
3.1 创建时钟:create_clock
创建时钟是最基本的操作。语法很简单:
create_clock -name clk_sys -period 10.0 [get_ports clk_in]
这条命令的意思是:在端口 clk_in 上创建一个名为 clk_sys 的时钟,周期10ns(也就是100MHz)。
嗯,这里要注意几个细节:
- 时钟名:最好和信号名区分开。我习惯加个
_clk后缀,比如sys_clk、dma_clk,这样一眼就能看出是时钟。 - 周期单位:默认是纳秒(ns)。如果你用皮秒(ps),要加
-unit ps参数。 - 占空比:默认50%。如果时钟不是50%占空比,用
-waveform指定上升沿和下降沿时间。
重要:创建时钟时,-name 和 [get_ports] 缺一不可。我见过有人只写 create_clock -period 10 [get_ports clk],结果工具自动生成一个叫 clk 的时钟名,后面查起来特别费劲。
举个例子,一个带相位偏移的时钟:
create_clock -name clk_90 -period 10.0 -waveform {2.5 7.5} [get_ports clk_in]
这个时钟的上升沿在2.5ns,下降沿在7.5ns,相当于相位偏移了90度。
3.2 生成时钟:create_generated_clock
生成时钟,说白了就是由主时钟分频、倍频或相移得到的时钟。比如PLL输出的时钟,或者计数器分频出来的时钟。
语法示例:
create_generated_clock -name clk_div2 -source [get_ports clk_in] -divide_by 2 [get_pins u_pll/clk_out]
这条命令的意思是:在 u_pll/clk_out 这个引脚上,生成一个相对于 clk_in 二分频的时钟,叫 clk_div2。
我在项目中遇到过一个问题:有人直接用 create_clock 去定义PLL输出时钟,结果工具把主时钟和生成时钟当成两个独立的时钟源,时序分析完全乱套。记住,生成时钟必须用 create_generated_clock,这样才能建立和主时钟的相位关系。
常用的参数:
| 参数 | 说明 | 示例 |
|---|---|---|
-divide_by |
分频系数 | -divide_by 2 表示二分频 |
-multiply_by |
倍频系数 | -multiply_by 4 表示四倍频 |
-invert |
反相时钟 | -invert 表示输出时钟反相 |
-edges |
自定义边沿关系 | -edges {1 3 5} 指定边沿位置 |
小技巧:对于复杂的PLL配置,我建议先用 report_clock 查看工具自动生成的时钟,再手动补充约束。这样不容易漏掉。
再来看一个带边沿关系的例子:
create_generated_clock -name clk_odd -source [get_ports clk_in] \
-edges {1 3 5} -edge_shift {0 2.5 0} [get_pins u_div/clk_out]
这个时钟的边沿位置是:第1个边沿(上升沿)不移位,第3个边沿(下降沿)延迟2.5ns,第5个边沿(下一个上升沿)不移位。说白了,就是做了一个占空比可调的分频时钟。
3.3 虚拟时钟:create_clock -name virt_clk
虚拟时钟,听名字就知道——它不是真实存在的时钟。它不连到任何端口或引脚上,只用于约束输入输出延迟。
为什么要用虚拟时钟?你想想看,当数据从外部芯片进来时,外部芯片有自己的时钟域。这个时钟可能和FPGA内部的时钟不同频、不同相。这时候就需要一个虚拟时钟来模拟外部时钟。
语法:
create_clock -name virt_clk -period 10.0
注意,这里没有 [get_ports] 或 [get_pins],因为虚拟时钟不绑定任何物理节点。
使用场景:
- 输入延迟约束:外部芯片在虚拟时钟的上升沿发送数据。
- 输出延迟约束:FPGA在虚拟时钟的上升沿将数据送出给外部芯片。
举个例子:
# 创建虚拟时钟,模拟外部芯片的时钟
create_clock -name ext_clk -period 8.0
# 输入延迟:外部芯片在 ext_clk 上升沿后 2ns 输出数据
set_input_delay -clock ext_clk -max 2.0 [get_ports data_in]
set_input_delay -clock ext_clk -min 0.5 [get_ports data_in]
避坑指南:我曾经犯过一个错误——把虚拟时钟和真实时钟用同一个名字。结果工具把虚拟时钟和真实时钟当成同一个时钟,时序分析完全乱套。记住,虚拟时钟的名字要独一无二,最好加个 _virt 后缀。
3.4 知识体系总览
下面这张图,把三种时钟的关系和用法串起来了:
3.5 实战建议
最后,分享几个我在项目里总结出来的经验:
- 先写时钟,再写其他约束。时钟是时序分析的根基,时钟没定好,IO延迟、false path这些都没意义。
- 用
report_clock检查。写完约束后,跑一下report_clock,看看工具识别出了哪些时钟。我经常发现有人漏了生成时钟。 - 虚拟时钟的名字要规范。我习惯用
virt_前缀,比如virt_adc_clk、virt_dac_clk,这样一眼就能看出是虚拟时钟。 - 不要滥用
create_clock。对于PLL输出、MMCM输出,一定要用create_generated_clock。否则工具会把它们当成独立时钟,时序分析结果完全不对。
一句话总结:创建时钟定周期,生成时钟定关系,虚拟时钟定边界。三种时钟各司其职,缺一不可。
好了,关于SDC时钟约束的基础就聊到这里。记住,约束文件不是写给别人看的,是写给工具看的。写清楚、写规范,工具才能帮你把时序做好。