3. 时序约束基础
在 Design Compiler 的综合流程中,时钟约束是所有时序约束的起点。综合工具本身并不知道设计里哪根线是时钟、时钟周期是多少、时钟从哪来、到各触发器之间会经历怎样的延迟与抖动。这些信息必须由设计者通过 SDC 命令显式描述。只有时钟被正确定义,setup、hold、max_delay、multicycle_path 等约束才有意义。本节围绕时钟的定义、传播与不确定性展开,重点掌握 create_clock、create_generated_clock、set_clock_latency、set_clock_uncertainty、set_clock_transition 五条命令的语义与配合方式。
一、主时钟:create_clock 定义时序基准
create_clock 用于定义主时钟(primary clock),它是整个时序分析的参考原点。命令的基本形式为:
create_clock -name CLK -period 10 -waveform {0 5} [get_ports clk]
其中 -period 指定时钟周期,单位由 set_units 决定,默认纳秒;-waveform 给出上升沿与下降沿时刻,{0 5} 表示占空比 50%。若省略 -waveform,工具默认上升沿在 0、下降沿在周期一半处。-name 是时钟的逻辑名,后续所有约束都通过这个名字引用它,因此命名应清晰且唯一。
需要特别区分“时钟源”和“时钟定义对象”。当时钟从芯片外部输入时,通常约束在输入端口上,例如 [get_ports clk];当时钟由内部 PLL 或分频器产生时,主时钟可能定义在某个内部引脚或端口上。若一个端口上存在多个时钟(如多路复用时钟输入),可以多次 create_clock 并分别命名,工具会按条件分析。对于纯内部产生的时钟,如果它没有外部参考,也可以直接定义为主时钟,但更推荐用生成时钟描述其与源时钟的关系。
二、生成时钟:create_generated_clock 描述派生关系
实际设计中大量时钟并非直接来自端口,而是由分频器、倍频器、时钟门控或 PLL 产生。这类时钟应使用 create_generated_clock 定义,它必须绑定一个源时钟,从而让工具知道两者之间的相位与频率关系。例如一个二分频时钟:
create_clock -name CLK -period 10 [get_ports clk]
create_generated_clock -name CLK_DIV2 -source [get_ports clk] \
-divide_by 2 [get_pins div_reg/Q]
这里 -source 指向源时钟所在的端口或引脚,-divide_by 2 表示输出频率为源时钟的一半,即周期 20ns。除了 -divide_by,还可用 -multiply_by 表示倍频,用 -edges 精确指定边沿对应关系,用 -invert 表示反相。生成时钟的关键价值在于:工具会自动继承源时钟的周期、波形和不确定性,并据此推导出源时钟到生成时钟之间的时序路径。如果错误地把分频时钟再定义成一个独立主时钟,工具会认为两者无关,跨时钟域路径将失去正确的建立/保持检查。
三、时钟传播:latency、uncertainty 与 transition
理想时钟模型假设时钟沿同时到达所有触发器,且边沿瞬间完成跳变。真实芯片中,时钟从源点经过时钟树到达各触发器,存在插入延迟(latency)、边沿退化和抖动。综合阶段还没有真实的时钟树,因此需要用约束来“预描述”这些效应。
set_clock_latency 描述时钟源到触发器时钟引脚之间的延迟。它分为 source latency 和 network latency:source latency 表示时钟从外部源到芯片时钟定义点的延迟,network latency 表示从定义点到各触发器时钟引脚的片上时钟树延迟。例如:
set_clock_latency -source 1.5 [get_clocks CLK]
set_clock_latency 0.8 [get_clocks CLK]
前者告诉工具时钟源到芯片有 1.5ns 延迟,后者表示片上时钟树预计有 0.8ns 延迟。综合阶段通常只给一个估计值,布局布线后再用实际时钟树信息更新。
set_clock_uncertainty 用于为时钟边沿预留裕量,主要覆盖抖动(jitter)、时钟树偏斜(skew)以及建模误差。它分为 setup 和 hold 两类:
set_clock_uncertainty -setup 0.15 [get_clocks CLK]
set_clock_uncertainty -hold 0.05 [get_clocks CLK]
setup uncertainty 会从数据路径可用时间中扣除,使建立检查更严格;hold uncertainty 会加到保持要求上,使保持检查更严格。综合阶段 skew 尚未确定,通常把预估 skew 与 jitter 合并成一个 uncertainty 值。
set_clock_transition 描述时钟边沿的上升/下降时间。理想时钟的 transition 为 0,但真实时钟边沿有斜率,会影响触发器时钟引脚上的延迟计算和功耗。例如:
set_clock_transition -rise 0.12 [get_clocks CLK]
set_clock_transition -fall 0.10 [get_clocks CLK]
该约束只作用于时钟网络,不影响普通数据信号。综合时若没有实际时钟树,可先给一个合理估计,待时钟树综合后再由工具自动更新。
四、理想时钟模型与综合阶段的取舍
综合阶段默认使用理想时钟模型:时钟沿同时到达所有触发器,transition 为 0,latency 为 0,uncertainty 为 0。这种模型便于快速评估逻辑深度,但与真实芯片差距较大。因此实际项目中通常不会完全依赖默认值,而是主动设置一组“预估约束”:给主时钟设置合理的 source latency 和 network latency,给 setup/hold 设置 uncertainty,给时钟设置 transition。这样综合出来的结果更接近后端实际情况,减少后期时序收敛的反复。
需要注意的是,set_clock_latency 与 set_clock_uncertainty 的作用对象是时钟,而不是数据路径。它们会同时影响该时钟域内所有触发器的建立和保持检查。若设计中存在多个时钟,应分别对每个时钟设置,避免用 -all_clocks 一刀切导致某些时钟约束过紧或过松。
五、常见错误与检查方法
时钟约束最容易出现的问题有三类。第一,主时钟定义在错误的端口或引脚上,导致工具找不到时钟源,相关路径全部报 unconstrained。第二,生成时钟没有绑定 -source,或绑定到了错误的源时钟,导致跨时钟域路径被当作同步路径分析,产生虚假的时序违例。第三,latency 和 uncertainty 设置过大或过小,前者使综合过度优化、面积和功耗上升,后者使时序过于乐观、后端无法收敛。
检查时钟约束是否合理,可以在 DC 中使用 report_clock 查看所有时钟的名称、周期、波形和来源;用 report_clock -skew 查看时钟偏斜估计;用 check_timing 检查是否存在未约束的时钟端点或未定义的时钟。对于生成时钟,还可以用 report_clock -generated 确认其源时钟和分频/倍频关系是否正确。综合前花几分钟核对这些信息,往往能避免后期大量返工。
create_clock 定义主时钟,再 create_generated_clock 描述派生时钟,然后统一设置 latency、uncertainty 和 transition,最后再添加输入输出延迟、多周期路径等其它约束。顺序清晰,后续排查也更方便。