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会隐藏真实的跨时钟域问题。

SDC时钟约束关系图 主时钟 clk_core 5ns 生成时钟 clk_div2 10ns 生成时钟 clk_pll 1.25ns -divide_by 2 -edges {1 3 5} 虚拟时钟 vclk_ddr 2.5ns I/O端口 dq[*] set_input_delay set_clock_groups -asynchronous Group1: clk_core, clk_div2 Group2: clk_usb Group3: clk_ddr false false 主时钟 生成时钟 虚拟时钟 时钟组

五、案例驱动:一个多时钟域SoC的约束编写

假设一个SoC包含:50MHz参考时钟clk_ref(20ns),经PLL倍频至400MHzclk_core(2.5ns),再经4分频得到100MHzclk_peri(10ns);另有独立USB时钟clk_usb(30ns)和DDR虚拟时钟vclk_ddr(2.5ns)。约束编写顺序如下:

  1. 定义主时钟clk_ref与clk_usb;
  2. 用create_generated_clock从clk_ref派生clk_core与clk_peri;
  3. 定义虚拟时钟vclk_ddr并约束DDR I/O;
  4. 用set_clock_groups将clk_ref/clk_core/clk_peri归为一组,clk_usb与vclk_ddr各自成组,切断异步路径;
  5. 对同步但相位不同的时钟(如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]
检查要点:读入SDC后,用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的质量直接决定时序收敛的起点,建议将约束文件纳入版本管理,并在每次时钟结构变更后同步更新。