3. 时钟约束与SDC实战
SDC(Synopsys Design Constraints)是连接前端设计与后端实现的时序契约。在Cadence Innovus、Genus等工具中,SDC通过read_sdc或source命令读入,最终转化为时序引擎可识别的时钟树、路径例外与延迟假设。本节围绕四类核心约束对象——主时钟、生成时钟、虚拟时钟、时钟组——展开,并给出可直接复用的约束片段与案例。
一、主时钟:时序分析的起点
主时钟(create_clock)定义时钟源进入芯片的端口或内部根节点,是STA的时基。约束时必须明确周期、波形(上升/下降沿)与作用对象。例如一个200MHz输入时钟,周期5ns,占空比50%,上升沿在0ns:
create_clock -name clk_core -period 5.0 \
-waveform {0.0 2.5} [get_ports clk_in]
若时钟经过PLL或分频器,不应在输出端重复create_clock,而应使用create_generated_clock。主时钟的-waveform决定时钟沿位置,直接影响建立/保持检查的参考边沿。对于差分输入,通常约束到P端,N端由工具自动处理;若工具未识别,可对N端设置set_clock_uncertainty或使用create_clock配合-add。
二、生成时钟:追踪时钟树上的派生关系
生成时钟(create_generated_clock)描述由主时钟经分频、倍频、门控或PLL产生的时钟。关键参数是-source(源时钟对象)与-divide_by/-multiply_by或-edges。例如主时钟5ns经2分频得到10ns时钟:
create_generated_clock -name clk_div2 \
-source [get_ports clk_in] -divide_by 2 \
[get_pins u_div/Q]
对于PLL,若输出频率为输入的4倍且存在相移,可用-edges精确描述:
create_generated_clock -name clk_pll \
-source [get_pins u_pll/REFCLK] \
-edges {1 3 5} [get_pins u_pll/CLKOUT]
生成时钟的源必须指向已定义的主时钟或另一个生成时钟,形成时钟树。若源对象错误,工具会报“no clock found”或产生错误的时序路径。门控时钟需用-combinational标记,使工具理解使能逻辑对时钟沿的影响。
三、虚拟时钟:为I/O路径提供外部参考
虚拟时钟(create_clock不指定对象,或指定到不存在的端口)用于描述芯片外部器件的时钟,常用于input/output delay约束。例如DDR接口的外部时钟周期2.5ns,但该时钟不进入芯片:
create_clock -name vclk_ddr -period 2.5
set_input_delay -clock vclk_ddr -max 1.2 [get_ports dq[*]]
set_output_delay -clock vclk_ddr -min 0.8 [get_ports dq[*]]
虚拟时钟没有物理对象,因此不会参与时钟树综合,但会作为I/O路径的参考。若忘记定义虚拟时钟,工具会使用默认时钟或报错,导致I/O时序检查失效。
四、时钟组:切断异步时钟间的虚假路径
默认情况下,STA会检查所有时钟域之间的路径。对于异步时钟,这种检查没有意义且会掩盖真实问题。使用set_clock_groups可声明时钟域关系:
set_clock_groups -asynchronous \
-group {clk_core clk_div2} \
-group {clk_usb} \
-group {clk_ddr}
同一组内的时钟仍会互相检查(如clk_core与clk_div2有同步关系),组间路径被设为false path。若两个时钟有确定的相位关系但频率不同,应使用-physically_exclusive或-logically_exclusive,而非asynchronous。错误使用asynchronous会隐藏真实的跨时钟域问题。
五、案例驱动:一个多时钟域SoC的约束编写
假设一个SoC包含:50MHz参考时钟clk_ref(20ns),经PLL倍频至400MHzclk_core(2.5ns),再经4分频得到100MHzclk_peri(10ns);另有独立USB时钟clk_usb(30ns)和DDR虚拟时钟vclk_ddr(2.5ns)。约束编写顺序如下:
- 定义主时钟
clk_ref与clk_usb; - 用
create_generated_clock从clk_ref派生clk_core与clk_peri; - 定义虚拟时钟
vclk_ddr并约束DDR I/O; - 用
set_clock_groups将clk_ref/clk_core/clk_peri归为一组,clk_usb与vclk_ddr各自成组,切断异步路径; - 对同步但相位不同的时钟(如
clk_core与clk_peri)保留默认检查,必要时用set_clock_uncertainty增加余量。
完整约束片段示例:
# 主时钟
create_clock -name clk_ref -period 20.0 [get_ports clk_ref]
create_clock -name clk_usb -period 30.0 [get_ports clk_usb]
# 生成时钟
create_generated_clock -name clk_core \
-source [get_ports clk_ref] -multiply_by 8 \
[get_pins u_pll/CLKOUT]
create_generated_clock -name clk_peri \
-source [get_pins u_pll/CLKOUT] -divide_by 4 \
[get_pins u_div/Q]
# 虚拟时钟与I/O
create_clock -name vclk_ddr -period 2.5
set_input_delay -clock vclk_ddr -max 1.0 [get_ports dq[*]]
set_output_delay -clock vclk_ddr -max 1.0 [get_ports dq[*]]
# 时钟组
set_clock_groups -asynchronous \
-group {clk_ref clk_core clk_peri} \
-group {clk_usb} \
-group {vclk_ddr}
# 时钟不确定性
set_clock_uncertainty -setup 0.15 [get_clocks clk_core]
set_clock_uncertainty -hold 0.05 [get_clocks clk_core]
report_clocks确认时钟树结构,用report_clock_groups查看组间关系,用check_timing检查未约束路径。若出现“unconstrained endpoint”,优先检查生成时钟的-source是否指向已定义时钟。
create_clock会导致时钟覆盖;生成时钟的-source写成端口而非引脚可能找不到源;set_clock_groups中遗漏虚拟时钟会使I/O路径仍被检查。建议每次修改后重新运行check_timing并对比report_timing的路径数量变化。
约束编写完成后,应在Genus/Innovus中执行read_sdc并观察日志中的warning与error。对于跨时钟域路径,若确实需要检查,可改用set_max_delay配合set_false_path做精细控制,而非简单归入asynchronous组。SDC的质量直接决定时序收敛的起点,建议将约束文件纳入版本管理,并在每次时钟结构变更后同步更新。