2. 时钟定义与约束基础
时序验证的起点不是跑工具,而是把设计者脑中的时钟意图翻译成工具能读懂的约束。SDC(Synopsys Design Constraints)就是这门"翻译语言"。它基于Tcl语法,用一组标准命令描述时钟从哪里来、经过什么变换、彼此之间是什么关系。本节聚焦三类最基础也最关键的时钟对象:主时钟、生成时钟和虚拟时钟,并掌握 create_clock、create_generated_clock、set_clock_groups 三条命令的用法。
一、主时钟:时序分析的绝对原点
主时钟(master clock)是时钟树的根,通常定义在芯片的时钟输入端口或某个内部振荡器输出上。它规定了时钟的周期、占空比和边沿位置,是后续所有时序计算的基准。命令格式为:
create_clock -name <clock_name> -period <period> \
[-waveform {rise_edge fall_edge}] [source_objects]
其中 -period 单位由库决定,通常为纳秒。若省略 -waveform,默认上升沿在 0、下降沿在周期一半处,即 50% 占空比。例如一个 200MHz 的输入时钟接在端口 clk_in 上:
create_clock -name sys_clk -period 5.0 -waveform {0 2.5} [get_ports clk_in]
这条约束告诉工具:clk_in 端口上有一个周期 5ns、上升沿在 0ns、下降沿在 2.5ns 的时钟,命名为 sys_clk。注意 -name 是给时钟起的逻辑名,后续 get_clocks 用它引用;source_objects 是时钟的物理来源。若一个端口上要定义多个时钟(如多路复用输入),可多次执行 create_clock,工具会自动识别为同一源上的多个时钟。
二、生成时钟:描述时钟的变换关系
实际SoC中,绝大多数时钟不是直接来自端口,而是由PLL、分频器、时钟门控或时钟切换电路产生的。这些时钟与主时钟存在确定的相位、频率或边沿关系,用 create_generated_clock 描述:
create_generated_clock -name <gen_name> -source <master_pin> \
[-divide_by N | -multiply_by N | -edges {e1 e2 e3}] \
[-invert] <generated_pin>
关键参数是 -source,它指向主时钟所经过的源引脚(通常是主时钟定义点或上游时钟引脚)。工具据此自动推导生成时钟的波形。例如一个二分频器,输入来自 sys_clk,输出引脚为 div_clk:
create_generated_clock -name clk_div2 -source [get_pins u_div/CK] \
-divide_by 2 [get_pins u_div/Q]
工具会算出 clk_div2 周期为 10ns,上升沿对齐源时钟的上升沿。若分频器是上升沿触发,输出在源时钟上升沿翻转,则生成时钟的上升沿实际出现在源时钟的下降沿位置——此时需要用 -edges 或 -invert 精确描述边沿映射,否则会引入半个周期的相位误差。
对于PLL,通常用 -multiply_by 和 -divide_by 组合描述倍频比,或用 -edges 直接指定输出时钟的三个边沿在源时钟周期内的位置。生成时钟的 -source 必须指向一个已有时钟的引脚,形成"主时钟→生成时钟"的链式关系,工具才能正确传播时钟延迟和不确定性。
三、虚拟时钟:没有物理源的外部参考
虚拟时钟(virtual clock)是一种没有物理来源的时钟,定义时不给 source_objects:
create_clock -name v_clk -period 8.0
它主要用于两类场景:一是约束输入/输出端口的外部器件时序,此时外部器件的时钟并不在芯片内部,但需要作为参考;二是为某些异步接口提供参考时钟,配合 set_input_delay/set_output_delay 使用。虚拟时钟不驱动任何内部寄存器,只参与端口时序计算,因此不会出现在时钟树综合中。
四、时钟组:声明异步关系
当多个时钟之间没有确定的相位关系时,必须用 set_clock_groups 声明它们为异步组,否则工具会尝试在它们之间做时序分析,产生大量虚假违例:
set_clock_groups -asynchronous \
-group {sys_clk clk_div2} \
-group {v_clk usb_clk}
同一组内的时钟仍会互相分析,不同组之间的路径被切断。除了 -asynchronous,还有 -logically_exclusive(逻辑互斥,如多路选择器的两个输入)和 -physically_exclusive(物理互斥,如测试时钟与功能时钟)。选择正确的类型很重要:异步组用于真正无相位关系的时钟,互斥组用于同一时刻只有一个有效的时钟。
五、命令使用中的常见判断
实际写约束时,几个判断点值得留意。第一,create_clock 的 -name 建议显式指定,否则工具会用端口名或引脚名自动命名,后续引用容易混淆。第二,生成时钟的 -source 必须指向时钟路径上的引脚,而不是端口名;如果源引脚上还没有时钟,工具会报错。第三,set_clock_groups 中的时钟名必须与 create_clock/create_generated_clock 的 -name 一致,且每个时钟只能出现在一个组里,重复出现会导致约束冲突。
另一个容易忽略的点是时钟的传播方式。默认情况下,create_clock 定义的时钟是理想时钟(ideal),即没有延迟和抖动;实际时钟树综合后,需要用 set_propagated_clock 让工具计算真实插入延迟。虚拟时钟始终是理想时钟,因为它没有物理路径。理解这一点,才能正确解读时序报告中的建立/保持余量。
report_clocks 可以列出所有已定义时钟及其属性;用 check_timing 可以检查是否存在未约束的时钟或端口。在写完全部时钟约束后,先跑这两条命令确认时钟拓扑完整,再进入时序分析,能避免大量无效调试。把这三类时钟和三条命令串起来看:主时钟提供基准,生成时钟描述变换,虚拟时钟补充外部参考,时钟组声明异步边界。它们共同构成SDC时钟约束的骨架。后续的输入输出延迟、时钟不确定性、多周期路径等约束,都建立在这个骨架之上。骨架搭错,后面所有分析都会偏离真实意图。