4. 时钟定义与约束检查
时序签核的第一步不是跑STA,而是确认“时钟本身有没有被描述对”。Tempus的所有setup/hold计算都建立在时钟波形、传播路径和不确定性之上;一旦create_clock的周期、generated_clock的源关系、clock group的异步声明或latency/uncertainty的数值有偏差,后续所有slack都会失真。本节围绕“如何验证这些约束的正确性”展开,重点讲清每类约束在Tempus中应检查什么、用什么命令看、看到什么算通过。
一、先建立时钟约束的检查对象模型
时钟约束不是孤立的一条条SDC命令,而是一张有向关系图:主时钟(create_clock)是根,generated_clock挂在某个源时钟或源引脚上,clock group把时钟划分成同步/异步集合,latency描述时钟从源到寄存器的延迟,uncertainty描述抖动、偏斜和余量。Tempus读入SDC后,会把这些约束解析成内部的时钟树和传播关系。验证的本质,就是确认这张关系图与设计意图一致。
二、create_clock:先确认“根”对不对
create_clock定义的是时钟的源头波形。检查时重点看四件事:周期是否与设计规格一致、波形(duty cycle)是否正确、源对象(port或pin)是否指向真实时钟入口、时钟名是否与后续约束引用一致。在Tempus中执行report_clocks,会列出每个时钟的周期、波形边沿、源引脚和是否为generated。若发现某个时钟的源是内部pin而非顶层port,要确认这是有意为之(比如PLL输出后定义主时钟),还是误把生成时钟写成了主时钟。
一个常见错误是周期单位。SDC默认单位与库单位可能不同,若库是ns而约束按ps写,周期会差1000倍。检查方法是对比report_clocks中的周期数值与规格书,同时用report_units确认当前时间单位。另一个错误是波形写反,比如create_clock -period 10 -waveform {5 10},上升沿在5ns、下降沿在10ns,占空比50%但相位偏移了半个周期,会导致setup/hold关系整体错位。
三、generated_clock:验证源关系与分频比
generated_clock的正确性取决于两点:-source是否指向正确的源对象,以及分频/倍频/相移参数是否与电路实际一致。Tempus的report_clocks会显示generated时钟的master clock和源引脚。检查时沿着源引脚回溯:如果源是主时钟经过一个分频寄存器,那么-source应指向该寄存器的时钟引脚或主时钟本身,-divide_by或-edges要与分频逻辑匹配。
例如一个二分频时钟,源为主时钟clk_main(周期10ns),分频寄存器在clk_main上升沿翻转。正确写法是:
create_clock -name clk_main -period 10 [get_ports clk]
create_generated_clock -name clk_div2 -source [get_ports clk] \
-divide_by 2 [get_pins u_div/Q]
若误写成-divide_by 4,Tempus会按20ns周期计算,所有跨到该时钟域的路径slack都会偏乐观。验证方法是report_clocks -generated查看实际周期,并与仿真或规格对比。对于用-edges描述的复杂生成时钟,要确认边沿列表与波形图一致,特别是相移时钟的-invert或-edge_shift。
四、clock group:同步与异步的边界
clock group决定哪些时钟之间做时序分析、哪些不做。set_clock_groups -asynchronous告诉Tempus两组时钟互不检查,-exclusive表示同一时刻只有一个时钟有效。检查时用report_clock_groups或report_clocks -groups查看分组结果。关键判断是:被声明为异步的时钟之间,是否真的没有数据交互?如果两个时钟域之间有同步器,异步声明是合理的;如果存在直接组合路径,异步声明会掩盖真实的亚稳态风险。
另一个检查点是同一时钟是否被重复分组。若一个时钟同时出现在两个group中,Tempus可能报冲突或按优先级处理。用check_timing -verbose可以捕获这类约束冲突。对于-exclusive,还要确认多路选择器的选择信号是否被正确约束,否则Tempus可能对两个互斥时钟都做分析,产生虚假违例。
五、latency与uncertainty:数值是否反映真实余量
latency分源延迟(source latency)和网络延迟(network latency)。源延迟是时钟从源到芯片入口的延迟,网络延迟是入口到寄存器时钟引脚的延迟。在Tempus中,report_clock_properties会显示每个时钟的latency值。检查时注意:-source和-network是否用对,-late和-early是否分别覆盖setup和hold。若只写了一个值,Tempus会同时用于早晚,可能低估hold风险。
uncertainty是setup和hold的余量,通常包含jitter、skew和margin。检查时用report_clock_uncertainty或report_clock_properties -uncertainty查看实际生效值。常见错误是把setup uncertainty设得过大导致过度悲观,或把hold uncertainty设为零导致hold违例被隐藏。合理的做法是:setup uncertainty = jitter + skew + margin,hold uncertainty = jitter + skew,且两者都应与时钟频率和工艺节点匹配。
check_timing是约束检查的总入口。它不会直接告诉你“约束错了”,而是报告“未约束端点”“无时钟路径”“时钟未传播”等异常。把这些异常与report_clocks、report_clock_properties的结果交叉比对,才能定位是约束缺失还是约束错误。六、一套可复用的检查顺序
建议按以下顺序在Tempus中执行检查,每步都有明确的通过标准:
- 读入SDC后先跑
check_timing -verbose:确认没有“unconstrained endpoint”和“no clock”类警告。若存在,说明有时序端点未被任何时钟覆盖,需回到create_clock或generated_clock补充。 - 执行
report_clocks:核对每个时钟的周期、波形、源对象。通过标准是周期与规格一致、源对象为预期port或pin、generated时钟的master正确。 - 执行
report_clock_properties:查看latency和uncertainty的实际值。通过标准是早晚延迟分别合理、uncertainty非零且与规格匹配。 - 执行
report_clock_groups:确认异步/互斥分组与设计意图一致,没有时钟被遗漏或重复分组。 - 跑一次
report_timing -unconstrained:确认没有路径因为时钟约束缺失而未被分析。若报告为空,说明所有路径都已被时钟覆盖。
这五步做完,时钟约束的正确性就有了可追溯的证据链。后续做ECO或时序修复时,任何slack变化都可以回溯到这些约束是否被改动。时钟约束检查不是一次性动作,每次SDC更新、每次时钟结构变更后都应重新执行,确保Tempus看到的时钟关系始终与设计一致。