3. Kria时钟资源与约束
Kria SOM 的时钟体系与纯 FPGA 开发板最大的区别在于:PS(Processing System)本身就是整个系统的主时钟源之一,PL 侧的时钟往往不是从外部晶振直接引入,而是由 PS 通过 PL 时钟输出端口“喂”给可编程逻辑。因此,理解 Kria 的时钟资源,核心是理清三条链路:PS 内部 PLL 产生的时钟如何输出到 PL、PL 侧如何用 Clocking Wizard 生成所需频率、以及这些时钟在 XDC 中如何被正确约束。三者缺一不可,任何一环出错,时序分析都会给出错误结论。
PS 时钟输出:PL 时钟的来源
在 Kria K26 SOM 上,PS 侧共有四路 PL 时钟输出,通常记为 pl_clk0~pl_clk3。它们由 PS 内部的 IOPLL、RPLL 等时钟源分频得到,频率在 Vivado 的 Zynq UltraScale+ MPSoC IP 配置界面中设定。以常见的 KV260 载板设计为例,典型配置是:pl_clk0 设为 100 MHz,作为 PL 侧逻辑的主时钟;pl_clk1 设为 200 MHz,供高速接口使用;pl_clk2、pl_clk3 可关闭或留作备用。
需要特别注意的是,PS 输出的 PL 时钟默认是自由运行的,只要 PS 上电并完成初始化,这些时钟就会持续输出,与 PL 侧逻辑是否复位无关。这意味着 PL 逻辑在复位释放前就可能已经收到时钟沿,设计时必须保证复位信号与时钟域的配合正确。此外,PS 输出的时钟在 PL 侧进入 BUFG 之前,其抖动和偏斜特性由 PS 内部 PLL 决定,通常质量较好,但若频率较高(如超过 250 MHz),建议在 PL 侧再经过 MMCM 做一次清理和相移调整。
PL 时钟向导:从输入时钟到目标频率
PL 侧最常用的时钟生成工具是 Clocking Wizard IP。它的输入可以来自 PS 的 pl_clk0,也可以来自外部差分晶振。以 100 MHz 输入、需要生成 150 MHz 和 50 MHz 两路时钟为例,配置过程如下:
- 在 Vivado 中例化 Clocking Wizard,
Primitive选择 MMCM 或 PLL。Kria 的 PL 侧推荐优先使用 MMCM,因为其相移和抖动过滤能力更强。 - 输入时钟频率填 100 MHz,来源选
Global buffer(若来自 PS 的 pl_clk0,实际已经过 BUFG)。 - 在 Output Clocks 页勾选
clk_out1和clk_out2,分别请求 150 MHz 和 50 MHz。 - Vivado 会自动计算 VCO 频率和分频系数。例如 MMCM 的 VCO 范围通常为 600~1200 MHz,工具可能选择 VCO=1200 MHz,则 clk_out1 分频比为 1200/150=8,clk_out2 为 1200/50=24。
- 检查
Locked信号,它表示 MMCM 输出已稳定,应作为 PL 逻辑复位的释放条件之一。
这里有一个常见误区:很多人以为只要在 Wizard 里填了目标频率,输出就一定是精确值。实际上 MMCM 的输出频率是 VCO 频率除以整数分频比,因此实际频率可能与请求值有微小偏差。Vivado 会在配置界面显示 Actual 频率,必须核对该值是否满足下游逻辑的容差要求。例如请求 150 MHz,实际可能是 150.000 MHz 或 149.998 MHz,前者可接受,后者若用于高速串行接口则需重新评估。
MMCM 与 PLL 的选择与配置要点
MMCM 和 PLL 都能完成倍频、分频和相移,但二者定位不同。PLL 结构简单、功耗低、抖动较小,适合对时钟质量要求高但不需要精细相移的场合;MMCM 在 PLL 基础上增加了小数分频和更灵活的相移能力,适合需要多路不同频率、不同相位时钟的复杂设计。在 Kria 的 PL 侧,如果只需要一路或两路时钟且频率关系简单,PLL 足够;如果需要多路时钟、需要 90° 或 180° 相移、或者需要动态重配置,则应选 MMCM。
配置 MMCM 时有三个参数必须关注:CLKFBOUT_MULT_F(反馈倍频系数)、DIVCLK_DIVIDE(输入分频系数)和 CLKOUTx_DIVIDE(输出分频系数)。它们共同决定 VCO 频率:VCO = 输入频率 × MULT_F / DIVCLK_DIVIDE。VCO 必须落在器件规定的范围内,否则 MMCM 无法锁定。此外,CLKOUTx_PHASE 用于设置相移,单位是度,实际分辨率受 VCO 周期限制,不能任意取值。
XDC 时钟约束编写
XDC 约束是时钟设计的“最后一公里”。即使 PS 和 Wizard 配置正确,如果 XDC 没有正确描述时钟,Vivado 的时序分析仍然会基于错误假设。对于 PS 输出的 PL 时钟,Vivado 在综合后会自动推断出部分时钟,但自动推断并不总是可靠,尤其是当 pl_clk0 经过 BUFG 后驱动多个模块时。因此,推荐显式编写约束。
# PS 输出到 PL 的时钟约束
create_clock -name pl_clk0 -period 10.000 [get_ports pl_clk0]
create_clock -name pl_clk1 -period 5.000 [get_ports pl_clk1]
# MMCM 输出时钟约束(若 Vivado 未自动推断)
create_generated_clock -name clk_150m \
-source [get_pins clk_wiz_0/inst/mmcm_adv_inst/CLKIN1] \
-divide_by 8 -multiply_by 1 \
[get_pins clk_wiz_0/inst/mmcm_adv_inst/CLKOUT0]
# 异步时钟域声明
set_clock_groups -asynchronous \
-group [get_clocks pl_clk0] \
-group [get_clocks clk_150m]
这段约束中,create_clock 定义了 PS 输出时钟的周期,create_generated_clock 描述了 MMCM 输出与输入的关系,set_clock_groups 则声明了两个时钟域之间的异步关系。如果两个时钟域之间确实存在数据交互,不能简单用 set_clock_groups 一关了之,而应改用 set_max_delay 或 set_false_path 配合同步器结构,并在代码中实现正确的 CDC 处理。
常见错误:对 PS 输出的 pl_clk0 既写了 create_clock,又让 Vivado 自动推断,导致同一时钟被定义两次,时序报告出现冲突。解决方法是检查 report_clocks 输出,确认每个时钟只有一条定义。
最后需要强调的是,XDC 约束不是“写完就完事”的文档,而是需要与时序报告反复对照的验证依据。每次修改时钟配置后,都应运行 report_clocks 确认时钟定义无冲突,运行 report_timing_summary 确认所有时钟域内的建立/保持时间满足要求,并对跨时钟域路径单独检查是否已正确声明异步或已插入同步器。只有这三步都通过,时钟设计才算真正闭环。