
1. 项目概述为什么PL端需要调用PS端的时钟在Zynq系列SoC的开发中一个非常经典且高频的需求就是让可编程逻辑PL端去使用处理系统PS端的时钟。乍一听这似乎是个简单的连线问题但实际操作过的人都知道这里面藏着不少门道稍不注意就会导致时序违例、功能异常甚至整个系统不稳定。我最初接触这个需求时也踩过不少坑比如明明在Vivado里约束了时钟但实现后时序报告一片红或者PL逻辑的运行频率远低于预期。简单来说这个项目的核心就是打通PS和PL之间的时钟通道。Zynq的PS部分内置了丰富的时钟资源包括锁相环PLL、时钟管理单元等可以产生多种频率、低抖动的时钟信号。而PL端的时钟网络虽然灵活但其全局时钟资源BUFG是有限的且从外部引脚引入的时钟可能会带来额外的抖动和偏移。直接使用PS提供的时钟好处显而易见一是简化了硬件设计无需额外的晶振和时钟芯片二是PS的时钟管理更精细可以通过软件动态配置频率为PL提供灵活的时钟源三是PS与PL之间的时钟路径是芯片内部走线抖动和延迟相对可控有助于提升系统时序性能。那么谁需要关注这个内容呢如果你是FPGA逻辑工程师正在设计依赖PS控制或交互的PL模块如果你是嵌入式软件工程师需要为PL配置特定的工作时钟或者你是系统架构师在规划Zynq芯片的时钟架构那么理解并掌握PL调用PS时钟的方法就是一项必备技能。接下来我将从设计思路、具体实现、实操细节到问题排查完整地拆解这个过程。2. 整体设计思路与方案选型在动手写代码或画框图之前理清设计思路至关重要。PL调用PS时钟绝不是简单地在Zynq Processing System IP核上勾选一个时钟输出那么简单。你需要像一个系统时钟架构师一样思考权衡各种方案的利弊。2.1 核心需求解析我们要的到底是什么首先得明确PL端使用PS时钟具体是用于什么不同的应用场景对时钟的要求天差地别。场景一低速控制与状态同步。例如PL端的一个状态机需要与PS端的设备驱动程序保持粗略同步或者一个简单的GPIO扩展模块工作。这时对时钟的抖动、偏斜要求不高频率通常在几十MHz到100MHz左右。PS提供的FCLKFabric Clock就非常合适。场景二高速数据通路。例如通过AXI Stream接口进行高速数据流传输或者实现一个需要与DDR内存控制器频率匹配的DMA引擎。这时需要低抖动、高稳定性的时钟频率可能达到150MHz以上。PS的PLL输出时钟如FCLK_CLK0经过精心约束后可以胜任。场景三特定外设接口时钟。例如为PL端实现的USB、Ethernet MAC或SDIO控制器提供参考时钟。这时时钟的频率、占空比、抖动可能有行业标准要求。需要检查PS的时钟生成单元Clock Generation Unit, CGU是否能产生符合要求的时钟或者是否需要使用PL端的MMCM/PLL进行二次处理。关键决策点你需要评估PL逻辑所需时钟的频率、相位关系、抖动容限并与PS所能提供的时钟能力进行匹配。PS最多可以提供4个FCLKFCLK_CLK0~FCLK_CLK3它们源自PS内部的PLL频率可通过软件配置。这是最直接的选择。2.2 方案对比直达、缓冲与再处理确定了使用FCLK后具体怎么接到PL端逻辑里又有几种典型方案直连方案在Block Design中将Zynq PS IP的FCLK_CLKx端口直接引出作为顶层模块的输入端口然后直接接到PL逻辑的时钟端口上。优点最简单无需额外逻辑。缺点与风险Vivado综合工具可能会因为时钟路径未经过全局时钟缓冲器BUFG而报出严重警告CRITICAL WARNING时序性能难以保障时钟偏斜大。这是一个常见的坑强烈不推荐在生产设计中使用。全局缓冲方案推荐将PS输出的时钟信号先通过一个PL侧的全局时钟缓冲器BUFG再分配给PL逻辑使用。优点利用了FPGA专用的、低偏斜的全局时钟网络能驱动大片区域的逻辑时序性能最优。实现在Verilog/VHDL中实例化一个BUFG原语或者在Block Design中使用Utility Buffer IP核选择BUFG类型。这是绝大多数情况下的标准做法。时钟处理方案如果PS输出的时钟频率不符合要求或者需要生成相关时钟如分频、倍频、相位偏移则需要先经过PL端的时钟管理单元MMCM或PLL。流程PS FCLK → BUFG → MMCM/PLL IP核 → 生成所需时钟 → 再通过BUFG分发。应用场景PL逻辑需要多个不同频率且相位相关的时钟需要对PS时钟进行去抖动或频率综合。我的选型心得对于大多数控制和中速数据应用“全局缓冲方案”是最佳起点。它平衡了复杂度和性能。只有在确有必要时才引入MMCM/PLL因为它们会消耗额外的资源并增加时钟链路的抖动。2.3 硬件与软件的分工这是一个软硬协同的设计必须明确界限硬件Vivado职责在Block Design中使能并引出PS的时钟输出端口。在PL侧设计正确的时钟缓冲和分配网络。创建正确的时钟约束XDC告诉工具时钟信号的特性频率、不确定性等。软件Vitis/SDK职责在Bootloader或应用程序中配置PS的时钟生成单元CGU设置PLL参数从而输出特定频率的FCLK。可能需要在初始化阶段控制时钟的开启/关闭。一个关键联动硬件约束文件中定义的时钟频率必须与软件实际配置输出的频率严格一致。如果硬件约束写的是100MHz而软件只配置出了50MHz那么静态时序分析STA的结果将毫无意义系统可能无法在高频下工作。反之如果软件配置了150MHz而硬件约束只写了100MHz则工具不会按照150MHz的标准去优化时序很可能导致建立/保持时间违例。3. 核心细节解析与实操要点理解了整体框架我们来深入每个环节的魔鬼细节。这些细节往往是成功与失败的分水岭。3.1 PS端时钟源配置与硬件设计首先在Vivado中搭建硬件环境。3.1.1 Zynq PS IP核配置双击打开Zynq Processing System IP核配置路径通常为Clock Configuration-PL Fabric Clocks。使能时钟勾选你需要使用的FCLK例如FCLK_CLK0。设置初始频率这里可以设置一个“预期”的频率比如100.000 MHz。请注意这个值主要影响Vivado后续的时序分析约束的自动生成。它并不是最终运行时的频率最终频率由软件配置决定。但为了前后一致建议这里就设置为软件计划配置的目标频率。时钟缓冲类型通常保持默认的“BUFG”建议即可。这提示你需要在PL端使用BUFG。3.1.2 在Block Design中连接配置好后在Zynq IP核的图表上会出现FCLK_CLK0等输出端口。你需要将其引出到外部端口右键点击该端口选择“Make External”。这会在你的顶层设计Wrapper中创建一个输入时钟端口比如FCLK_CLK0_0。重要步骤你需要为这个端口加上正确的I/O标准约束。虽然它是内部信号但作为顶层端口需要定义其电气属性。通常通过XDC文件添加set_property -dict {PACKAGE_PIN 某个未用的PL引脚 IOSTANDARD LVCMOS33} [get_ports FCLK_CLK0_0]。注意这里的PACKAGE_PIN约束实际上不会被用于布局布线因为该时钟来自PS并非从真实引脚输入。但这个约束是Vivado流程所要求的你可以将其指向一个保留或不用的引脚或者使用-quiet选项忽略无法放置的警告。更规范的做法是使用set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets FCLK_CLK0_0]来告知工具这是一个特殊的时钟路径。3.2 PL端时钟网络设计这是确保时钟质量的关键。3.2.1 实例化全局时钟缓冲器BUFG绝对不要让PS来的时钟直接驱动寄存器。必须在顶层模块或时钟分配模块中实例化BUFG。Verilog示例wire fclk0_from_ps; // 连接来自PS顶层端口的线网 wire fclk0_bufg; // 经过BUFG后的时钟 IBUFG u_ibufg_fclk0 ( .I(fclk0_from_ps), // 输入 .O(fclk0_bufg) // 输出 ); // 注意对于Zynq PS输出的时钟有时工具推荐使用BUFG而非IBUFG因为其来源已是内部。更通用的做法是使用BUFGCTRL原语或Utility Buffer IP。稳妥起见使用Utility Buffer IP并选择BUFG类型。推荐使用Utility Buffer IP在Block Design中添加“Utility Buffer” IP核将其类型设置为“BUFG”将src_clk连接到PS的FCLK_CLK0端口将dest_out引出到你的逻辑或作为外部端口供其他模块使用。这种方法更直观且由Vivado自动处理原语映射。3.2.2 时钟约束XDC的编写这是最核心也最容易出错的部分。你需要手动或辅助创建约束。创建时钟约束约束必须施加在BUFG的输出端即真正的时钟网络起点而不是PS的输入端口。# 假设BUFG输出的网络名称为 fclk0_bufg create_clock -name clk_pl_100m -period 10.000 [get_nets fclk0_bufg] # -period 10.000 表示100MHz (周期10ns)设置时钟不确定性由于时钟源自PS的PLL经过芯片内部路径存在一定的抖动。需要为其设置一个合理的set_clock_uncertainty。set_clock_uncertainty -setup 0.150 [get_clocks clk_pl_100m] set_clock_uncertainty -hold 0.100 [get_clocks clk_pl_100m]这里的0.150ns和0.100ns是经验值具体需要参考Zynq器件的数据手册中关于PS输出时钟抖动的规格。设置不确定性相当于给时序分析增加了余量使设计更稳健。虚假路径约束如果你的PL逻辑与PS之间没有直接的同步数据路径例如仅通过AXI-Lite进行寄存器配置而AXI总线有自己的时钟域交叉电路那么可能需要将PS到PL的某些路径设置为虚假路径以避免不必要的时序报错。但这需要谨慎评估。3.3 PS端软件配置详解硬件设计完成后在Vitis中需要通过软件来“激活”这个时钟。3.3.1 使用Xilinx驱动库xil_io.h, xparameters.h在Vitis中创建裸机Baremetal或FreeRTOS应用后通常需要操作PS的时钟控制器寄存器。最安全的方式是使用Xilinx提供的API或参考BSP中的示例。查找时钟配置函数在Vitis生成的BSP工程中找到xparameters_ps.h和xil_io.h。时钟配置的底层驱动可能封装在xpseudo_asm.h或特定例程中。更简单的方法是参考Vivado导出的硬件平台xsa文件在Vitis中提供的示例工程如“Peripheral Tests”或“Clock Generator Example”。配置PLLPS的FCLK通常由某个PLL如ARM PLL或DDR PLL分频而来。你需要计算并设置PLL的分频器、倍频器参数。Xilinx SDK/Vitis有一个图形化的时钟配置工具在platform.spr文件内双击Zynq IP可以生成配置代码片段。关键代码流程#include xparameters.h #include xil_io.h // 假设FCLK0源自ARM PLL我们需要将其配置为100MHz // 1. 解锁SLCRSLCR寄存器写保护 Xil_Out32(XSLCR_UNLOCK, XSLCR_UNLOCK_MAGIC); // 2. 配置ARM PLL设置倍频、分频等此处参数需根据时钟架构计算 Xil_Out32(ARM_PLL_CTRL_REG, calculated_value); // 等待PLL锁定 while(!(Xil_In32(ARM_PLL_STATUS_REG) LOCK_MASK)); // 3. 配置FCLK0的分频器位于FPGA0_CLK_CTRL寄存器 Xil_Out32(FPGA0_CLK_CTRL, div_sel_value | ENABLE_MASK); // 4. 重新锁定SLCR Xil_Out32(XSLCR_LOCK, 0);重要提示直接操作寄存器风险高强烈建议使用Xilinx的Xil_Clock库如果可用或直接复制BSP示例中的完整时钟初始化函数。3.3.2 验证时钟输出配置完成后如何验证PS确实输出了100MHz的时钟软件方法可以读取时钟控制器的状态寄存器确认使能位和分频值。硬件方法推荐在PL端设计一个简单的时钟计数器通过AXI-Lite接口将计数值读回PS在串口打印。例如用100MHz时钟驱动一个32位计数器每秒计数值应增加100,000,000。观察打印值可以粗略判断时钟频率是否正确。或者更直接的方法是使用示波器或逻辑分析仪测量连接到PL端引脚的时钟信号如果你在硬件设计中将其引出了的话。4. 完整实现流程与核心环节让我们串联起所有步骤走一遍从零开始的完整实现流程。4.1 步骤一Vivado硬件工程创建与IP配置创建新工程选择对应的Zynq器件。创建Block Design添加Zynq Processing System IP核。运行“Block Automation”应用默认配置连接DDR和固定IO。双击Zynq IP进行定制在Clock Configuration-PL Fabric Clocks中使能FCLK_CLK0设置其频率为100 MHz。确认其他配置如DDR时钟、UART等符合你的板卡要求。在Diagram视图将Zynq IP的FCLK_CLK0端口右键“Make External”端口名自动生成如FCLK_CLK0_0。添加“Utility Buffer” IP核。双击配置Component Name设为bufg_fclk0Buffer Type选择“BUFG”。将其src_clk端口连接到FCLK_CLK0_0端口所在的接口会自动连接。将其dest_out端口也“Make External”命名为clk_100m_pl。这个clk_100m_pl就是我们将要用于PL逻辑的、经过全局缓冲的干净时钟。添加你的自定义PL逻辑模块例如一个AXI4-Lite从机测试模块并将其时钟输入连接到bufg_fclk0的dest_out端口或通过中间信号连接。运行“Connection Automation”让Vivado自动连接AXI接口和复位信号。验证设计生成HDL Wrapper。进入“Sources”窗口右键点击顶层Wrapper文件通常是design_1_wrapper选择“Create Constraint File”创建一个XDC文件。4.2 步骤二编写关键时钟约束打开新创建的XDC文件输入以下内容############################################### ## 主时钟定义约束BUFG输出的时钟网络 ############################################### # 首先需要找到BUFG输出端口的完整层次化网络名。 # 一个可靠的方法是在综合后的“Synthesized Design”中打开“Schematic” # 找到BUFG实例查看其输出端口O连接的网络名称。 # 假设网络名为 design_1_i/bufg_fclk0_0/O set clk_net [get_nets design_1_i/bufg_fclk0_0/O] # 创建时钟约束100MHz对应周期10ns create_clock -name clk_100m_pl -period 10.000 $clk_net # 设置时钟不确定性增加时序余量 set_clock_uncertainty -setup 0.150 [get_clocks clk_100m_pl] set_clock_uncertainty -hold 0.100 [get_clocks clk_100m_pl] # 如果该时钟还用于驱动I/O输出例如产生一个参考时钟给外部芯片 # 可能需要设置输出延迟约束。 # set_output_delay -clock clk_100m_pl -max 2.000 [get_ports my_output_port] # set_output_delay -clock clk_100m_pl -min -1.000 [get_ports my_output_port] # 对于来自PS的时钟输入端口可以设置一个虚拟的引脚约束以避免警告 # 或者使用 dedicated route 属性。 # set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets FCLK_CLK0_0]保存XDC文件。4.3 步骤三生成比特流与导出硬件平台运行综合Synthesis、实现Implementation和生成比特流Generate Bitstream。关键检查点在实现后的“Implemented Design”中打开“Timing Summary”。检查“Unconstrained Paths”是否为0或已合理约束。检查“clk_100m_pl”的建立时间Setup和保持时间Hold的裕量Slack是否为正。如果为负红色需要分析原因可能是逻辑路径太长、时钟不确定性设置不足或者实际物理路径延迟过大。比特流生成成功后在菜单栏选择File - Export - Export Hardware...。勾选“Include bitstream”导出XSA文件。这个文件包含了硬件描述和PL配置信息。4.4 步骤四Vitis软件工程开发与时钟配置打开Vitis创建新的平台工程Platform Project导入上一步导出的XSA文件。创建应用工程Application Project选择刚才创建的平台模板可以选择“Empty Application”。我们需要编写时钟初始化代码。最简单的方法是借鉴Vitis BSP中的例子。在平台工程的psu_init.c和psu_init.h文件中通常已经包含了完整的PS端初始化函数其中就有时钟配置。应用工程的主函数main()在执行用户代码前会调用psu_init()。因此通常我们不需要在应用中重复配置时钟除非需要动态改变频率。验证时钟频率在应用中我们可以通过读取寄存器或设计一个PL测试逻辑来验证。创建一个简单的PL计数器模块由clk_100m_pl驱动并通过AXI-Lite接口暴露一个32位的计数器寄存器。在PS端的C代码中定期读取这个计数器计算差值从而推算出实际时钟频率。// 伪代码示例 #include xil_io.h #include xparameters.h // 包含计数器模块的基地址 #define TEST_COUNTER_BASE XPAR_MY_COUNTER_0_S00_AXI_BASEADDR #define COUNTER_REG_OFFSET 0x00 // 计数器寄存器偏移 int main() { u32 cnt1, cnt2; float freq; cnt1 Xil_In32(TEST_COUNTER_BASE COUNTER_REG_OFFSET); usleep(1000000); // 延迟1秒 cnt2 Xil_In32(TEST_COUNTER_BASE COUNTER_REG_OFFSET); freq (float)(cnt2 - cnt1) / 1.0; // 差值即为1秒内的时钟周期数 xil_printf(Measured PL clock frequency: %f MHz\r\n, freq / 1000000.0); return 0; }编译软件工程将其与硬件比特流一起生成可启动的镜像文件BOOT.BIN。5. 常见问题、调试技巧与避坑指南即使按照流程操作也难免遇到问题。下面是我在多次项目中总结的“血泪教训”。5.1 时序违例Setup/Hold Violation这是最常见的问题表现为实现后的时序报告中出现红色负的Slack。原因1时钟约束错误或不完整。排查检查XDC中create_clock命令指定的时钟网络名称是否正确。必须指向BUFG之后的网络而不是PS的输入端口。使用get_nets或get_pins命令在Tcl Console中验证路径。解决修正约束目标。使用report_clocks命令查看已定义的时钟属性。原因2时钟不确定性Uncertainty设置过小。排查PS内部PLL和时钟分布网络存在固有抖动。如果set_clock_uncertainty的值小于实际抖动时序分析就会过于乐观。解决参考器件数据手册的“Clock Jitter”规格适当增加-setup和-hold的不确定性值。对于100-200MHz级别的时钟从0.15ns开始尝试是合理的。原因3逻辑路径延迟过大。排查查看违例路径的详细报告report_timing。可能是组合逻辑过于复杂级数太多或者信号需要穿越很长的物理距离。解决对逻辑进行流水线切割插入寄存器或者使用register_duplication、max_fanout等综合属性控制扇出或者尝试不同的布局策略如PhysOpt。原因4跨时钟域路径未约束。排查如果PL逻辑还使用了其他时钟如来自GTX的RX恢复时钟与PS时钟域之间有数据交换但没有正确的约束set_clock_groups或set_false_path工具会尝试分析这些不可能满足时序的路径。解决使用set_clock_groups -asynchronous将不同源的时钟组设置为异步或对特定路径设置set_false_path。5.2 PS时钟无输出或频率不对PL逻辑不工作或者计数器测出的频率与预期不符。原因1PS时钟未在软件中使能或配置错误。排查确认Vitis应用程序中的初始化代码是否包含了PS时钟配置。检查psu_init.c或你自己的配置代码中对应FCLK的使能位和分频器是否设置正确。解决使用SDK/Vitis的调试器单步跟踪时钟配置寄存器的写入过程或者直接读取这些寄存器的值进行验证。确保在配置PLL后等待了足够的锁定时间。原因2硬件连接或引脚约束干扰。排查如果在顶层将PS时钟端口分配给了实际物理引脚并且该引脚被其他电路拉低或冲突可能导致时钟信号异常。解决对于仅用于内部PL的PS时钟无需分配真实引脚。如果Vivado要求I/O约束可以将其分配到一个未连接的引脚如PACKAGE_PIN留空或指定一个NC引脚并忽略相关的布局布线警告。更好的方法是使用set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets FCLK_CLK0_0]来关闭专用时钟路由检查需谨慎理解其含义。原因3测量方法有误。排查软件计数器法受中断延迟、读取误差影响。示波器测量时探头负载可能影响高速时钟信号。解决使用更高精度的测量方法如使用PL内的硬件性能计数器如Xilinx的AXI Performance Monitor IP或者将时钟分频后输出到LED观察闪烁频率进行粗略判断。5.3 资源与功耗考虑BUFG资源有限每个Zynq器件可用的全局时钟缓冲器BUFG数量是固定的例如Zynq-7000通常有32个。每个PS输出的FCLK使用一个BUFGPL端其他时钟源如GTX恢复时钟、外部差分时钟也会消耗BUFG。在复杂设计中需规划BUFG的使用避免资源耗尽。可以使用report_clock_networks命令查看BUFG使用情况。动态功耗PS的PLL和时钟网络运行时会产生功耗。如果PL部分不需要一直工作可以考虑通过软件动态关闭Gate对应的FCLK输出以节省功耗。这需要软件配合在PS时钟控制器中操作相应的使能位。时钟域交叉CDC当PL逻辑同时使用PS时钟和其他时钟时它们之间的信号传递必须进行正确的CDC处理如使用双触发器同步器、异步FIFO。这是另一个复杂的话题但必须在设计初期就规划好否则会导致亚稳态和数据错误。5.4 一个实用的调试技巧使用ILA抓取时钟信号当怀疑时钟信号有问题时最直接的硬件调试手段是使用集成逻辑分析仪ILA。在Vivado的Block Design中添加ILA IP核。将ILA的时钟探测端口clk连接到一个稳定的参考时钟例如PS输出的另一个已知好的FCLK或者外部晶振时钟。将ILA的数据探测端口probe连接到你想观察的PS时钟网络例如BUFG之前的FCLK_CLK0_0或之后的clk_100m_pl。设置触发条件如上升沿重新生成比特流并下载。在Vivado Hardware Manager中连接设备触发并查看波形。你可以直观地看到时钟是否存在、频率是否正常、是否有毛刺。这是验证硬件层面时钟行为的最有力工具。最后我的个人体会是Zynq的PS-PL时钟交互是一个“软硬兼施”的典型。它要求开发者既理解PS的时钟架构和软件配置流程又掌握FPGA的时钟约束和时序分析原理。成功的关键在于严谨约束文件的一行命令、软件配置的一个参数都必须与硬件设计意图精确匹配。每次修改时钟相关配置后重新运行完整的实现流程并仔细审视时序报告这个习惯能帮你避开大多数深坑。