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 知识体系总览

下面这张图,把三种时钟的关系和用法串起来了:

SDC时钟约束知识体系 创建时钟 create_clock 绑定物理端口/引脚 定义周期、占空比 可指定波形偏移 示例: create_clock -name sys_clk -period 10.0 [get_ports clk] 生成时钟 create_generated_clock 基于主时钟生成 分频、倍频、相移 保持相位关系 示例: create_generated_clock -divide_by 2 -source [get_ports clk] 虚拟时钟 create_clock(无端口) 不绑定物理节点 模拟外部时钟 用于IO延迟约束 示例: create_clock -name ext_clk -period 8.0 作为源 独立 核心原则:每个时钟域至少有一个主时钟,生成时钟必须关联主时钟,虚拟时钟用于IO边界

3.5 实战建议

最后,分享几个我在项目里总结出来的经验:

  1. 先写时钟,再写其他约束。时钟是时序分析的根基,时钟没定好,IO延迟、false path这些都没意义。
  2. 用 report_clock 检查。写完约束后,跑一下 report_clock,看看工具识别出了哪些时钟。我经常发现有人漏了生成时钟。
  3. 虚拟时钟的名字要规范。我习惯用 virt_ 前缀,比如 virt_adc_clk、virt_dac_clk,这样一眼就能看出是虚拟时钟。
  4. 不要滥用 create_clock。对于PLL输出、MMCM输出,一定要用 create_generated_clock。否则工具会把它们当成独立时钟,时序分析结果完全不对。

一句话总结:创建时钟定周期,生成时钟定关系,虚拟时钟定边界。三种时钟各司其职,缺一不可。

好了,关于SDC时钟约束的基础就聊到这里。记住,约束文件不是写给别人看的,是写给工具看的。写清楚、写规范,工具才能帮你把时序做好。

可可精品资料库