简介:FPGA XC7A35T实现IP核之MMCM-PLL驱动(Verilog HDL实现)资源包,围绕Xilinx Artix-7系列FPGA的时钟管理模块设计,面向需要掌握MMCM与PLL配置、调用及其Verilog HDL驱动开发的硬件工程师与学习者。整套资料共306个文件,压缩包约1.77MB,涵盖核心源码、约束文件、Vivado工程配置、综合实现报告以及仿真与构建脚本,能够直观还原从IP配置到布局布线的完整过程。已有234人学习下载。通过该工程,读者可对照MMCM的分频倍频、相位偏移与PLL环路参数设置,理解时钟信号生成、仿真验证流程与硬件调试方法,并可直接用于XC7A35T开发板或参考迁移到其他Artix-7设计,具有较高的工程参考价值与实践指导意义。
1. 拿到XC7A35T之后,时钟才是第一道门槛
拿到一块XC7A35T开发板,流水灯和按键消抖很快就能跑通,但一旦开始做ADC采集、DDR读写或者LVDS接口,很多人会被同一件事卡住:板载晶振只有50MHz,而系统需要100MHz主时钟、200MHz采样时钟、90度相移的随路时钟,甚至还要一路25MHz给PHY。这时候你得靠MMCM-PLL把一路参考时钟变换成多路不同频率、不同相位的时钟。MMCM-PLL是Xilinx 7系列中负责时钟生成与对齐的核心资源,XC7A35T内部有5个CMT,每个CMT里各有一个MMCM和一个PLL,而Vivado里的Clocking Wizard IP核正是访问这些资源的入口。这篇文章按“选型、配置、Verilog HDL驱动、仿真与ILA验证、进阶应用”的顺序,把MMCM-PLL从IP核创建到上板调试的完整链路讲清楚。内容同时覆盖FPGA入门阶段最容易踩的坑和熟手也需要留意的边界条件,照着做就能在Vivado里复现。
2. MMCM与PLL怎么选:从XC7A35T的时钟需求反推IP核配置
2.1 结构差异决定了使用场景
XC7A35T内部有5个CMT,每个CMT由一个MMCM和一个PLL组成。MMCM的全称是Mixed-Mode Clock Manager,本质上是增强型锁相环;PLL则是基础的整数分频锁相环。很多工程习惯把两者混着用,只有输出路数不够了才想起MMCM,这个顺序是反的。合理的做法是先列出需求:需要几路输出、是否需要分数分频、是否要在运行中调相位,再决定用哪个。
MMCM相比PLL多三个能力:动态相移、分数分频、更多输出。动态相移意味着系统运行期间可以通过PSCLK、PSEN这类引脚微调输出相位;分数分频让M分频和输出分频支持0.125的小数步进,例如从100MHz得到125MHz这种非整数比;输出方面MMCM提供7路CLKOUT,PLL只有3路。PLL剩下的优势主要是抖动略低、功耗略小,实现也更简洁。
| 特性 | MMCM(MMCME2_ADV) | PLL(PLLE2_ADV) |
|---|---|---|
| 输出时钟路数 | 7路(CLKOUT0~6) | 3路(CLKOUT0~2) |
| 动态相移 | 支持 | 不支持 |
| 分数分频 | 支持(0.125步进) | 仅整数 |
| 输入频率范围 | 更宽 | 较窄 |
| 锁定时间 | 约几十到几百微秒 | 略快 |
| 典型用途 | 多路/相位/分数时钟生成 | 单路低抖动参考时钟 |
前面提到“XC7A35T有5个CMT”,这里再补充一下它的内部结构:每个CMT中MMCM和PLL共享输入时钟网络,但输出互不干扰。你在Clocking Wizard里选MMCM,工具实际例化的是MMCME2_ADV原语;选PLL则对应PLLE2_ADV原语。两者不能同时使用同一个CMT里的全部资源,规划多路时钟时要避免挨着放导致局部布线拥挤。
2.2 用三个问题确定选型
判断选MMCM还是PLL,我一般问三个问题。第一,是否只要一路固定频率的时钟?PLL够用,把MMCM留给复杂场景。第二,输出是否超过三路?超过三路只能选MMCM。第三,是否需要分数频率或动态相位?这两点PLL完全不具备,直接锁MMCM。
举一个XC7A35T上的典型例子:一个数据采集系统需要系统时钟100MHz、高速采样时钟200MHz、DDR随路时钟200MHz且移相90度,同时还要给低速串口提供25MHz时钟。四路输出、其中一路带相移,这个需求只能落在MMCM上。反过来,如果只是给一颗外部PHY提供单路参考时钟,频率固定且对抖动敏感,用PLL更合适,把MMCM资源让出来。
做TDC直方图这类对时序精度敏感的项目时,还要注意一点:MMCM本身会引入一定抖动,级联两级之后抖动会叠加,直方图展宽可能就来自时钟源而非进位链。这里的选择原则不是“用更好的MMCM”,而是尽量让系统在单级MMCM下完成所有时钟变换,减少不必要的级联。
2.3 记住VCO范围和BUFG这两个边界
MMCM内部把输入时钟先做D分频,再和反馈时钟比较,压控振荡器跑在一个乘了M倍的频率上,输出端各自除以O。核心约束是Fvco = Fclkin × M / D,并且Fvco必须落在器件手册规定区间内。以XC7A35T的-1速度等级为例,这个范围大约是600MHz到1200MHz,具体数值要查UG472中对应速度等级的表格。
在Clocking Wizard里,M/D/O这三个参数默认由工具自动计算,但不表示可以完全不看。自动算出的结果有时会让某个输出频率存在极小误差,因为MMCM的分数分频是0.125步进,例如要生成一个非标准频率,工具只能在最近的可取值里挑一个。手工干预时需要保证改动后的Fvco仍在VCO范围内,否则综合时直接报错。
另一条边界是BUFG。MMCM/PLL的输出默认插入BUFG驱动全局时钟网络,但XC7A35T的BUFG总量在32个左右。如果工程里还有外部引脚时钟、以太网参考钟、PCIe时钟,就需要规划哪些输出走BUFG、哪些走BUFR或BUFH。把不重要的低速时钟也塞进全局网络,综合后期很容易报出布线拥塞。
3. 用Clocking Wizard生成MMCM-PLL IP核:配置步骤与参数表
3.1 Vivado里创建一个Clocking Wizard
Vivado对MMCM-PLL的封装就是Clocking Wizard这个IP核。打开工程后在左侧Flow Navigator里点IP Catalog,搜索“Clocking Wizard”,双击进入配置界面。界面第一项是Component Name,建议命名为clk_wiz_0,后续在源码里例化、在约束里引用都比较直观。
在Clocking Options页面,输入时钟频率填50,与板载晶振一致;Source类型选Single ended clock capable pin;如果晶振是差分输出,选Differential clock capable pin。页面下方有reset和locked两个信号选项,默认勾上,这样生成的IP核会带reset输入和locked输出。
Output Clocks页面是配置重点:点Add Output Clock一次添加一路输出。以50MHz输入为例,通常会配四路:100MHz系统时钟、200MHz采样时钟、200MHz相移90度的DDR随路时钟、25MHz低速接口时钟。每路都能独立填频率、相位、占空比。相位设置粒度受VCO周期限制,实际最小可设值为VCO周期1/8对应的输出相位移,工具界面里常见0度、90度、180度、270度这种组合都能正常生成。
3.2 输出参数与自动分频的关系
下表是一套常用配置,输入为50MHz单端:
| 输出名称 | 输出频率 | 相位 | 占空比 | 用途示例 |
|---|---|---|---|---|
| clk_out1 | 100 MHz | 0.0° | 50% | 系统逻辑时钟 |
| clk_out2 | 200 MHz | 0.0° | 50% | ADC采样时钟 |
| clk_out3 | 200 MHz | 90.0° | 50% | DDR随路时钟 |
| clk_out4 | 25 MHz | 0.0° | 50% | PHY管理接口 |
配置完这几路,切到MMCM/PLL Settings页能看到工具自动推算出的CLKFBOUT_MULT_F、CLKOUT0_DIVIDE_F等参数。以50MHz进、200MHz出为例,一套常见取值是M=20、D=1,VCO=1000MHz,再对CLKOUT各位做分频。这个VCO频率落在Artix-7允许范围内,工具通常不会报警。
提示:如果频率关系比较特殊,比如100MHz输入要出125MHz,工具会把输出除法设成小数。每次生成IP后顺手看一眼VCO频率是否接近上限,比等到上板后排查要省事得多。
输出驱动资源默认是BUFG,在Output Clocks页面底部可以改成BUFR、BUFH或BUFIO。对只供局部逻辑使用的时钟,改成BUFH能省下全局资源;对需要跨时钟域到远端的信号,保持BUFG更稳。
3.3 生成IP核后的文件与端口
点Generate生成后,Vivado会在ip目录里形成一组文件,其中clk_wiz_0.v是供顶层例化的包装模块,clk_wiz_0_clk_wiz.v内部才真正例化MMCME2_ADV原语,后者值得打开读一遍,能看到工具替你做了哪些BUFG插入和反馈连接。顶层例化时以生成的模板为准,典型写法如下:
clk_wiz_0 u_clk_wiz_0 ( // Clock in ports .clk_in1 (clk_50m), // 板载50MHz参考时钟 // Clock out ports .clk_out1 (clk_100m), // 100MHz 系统时钟 .clk_out2 (clk_200m), // 200MHz 采样时钟 .clk_out3 (clk_200m_90), // 200MHz 90°相移时钟 .clk_out4 (clk_25m), // 25MHz 低速时钟 // Status and control signals .reset (~rst_n), // 高有效复位 .locked (clk_locked) // 锁定指示,高有效 );Clocking Wizard的reset是高有效,外部复位通常写成低有效rst_n,所以这里取反后接入。clk_in1直接接板级晶振引脚,工具会在内部自动插入IBUFG和BUFG。locked信号在输出时钟频率达到设定值后会拉高,它是下游复位释放、状态机启动的重要依赖。
生成目录里还会有clk_wiz_0.xdc和VHDL封装clk_wiz_0.vho。注意这个xdc里并没有对输入时钟的约束,输入时钟周期需要自己在顶层XDC里声明,否则Vivado会把无约束输入当虚拟时钟对待,时序报告没有意义。这个点很容易漏,后面第4章的约束部分会给出写法。
4. Verilog HDL驱动MMCM-PLL:例化、复位与顶层集成
4.1 一个完整的驱动模块
把IP核例化到顶层只是第一步,一个可靠的MMCM-PLL驱动模块至少还要处理三件事:复位信号的电平转换、locked信号的同步释放、以及复位释放后对下游逻辑的启动控制。下面这个模块可以直接用在XC7A35T工程里:
module mmcm_drv_top ( input wire clk_50m, // 板级 50MHz 晶振输入 input wire rst_n, // 外部低有效复位 output wire clk_100m, // 100MHz 系统时钟 output wire clk_200m, // 200MHz 采样时钟 output wire clk_200m_90, // 200MHz 90°相移时钟 output wire clk_25m, // 25MHz 低速时钟 output wire clk_locked, // MMCM 锁定指示 output wire sys_rst_n // 同步后的系统复位,低有效 ); // 例化 Clocking Wizard 生成的 IP 核 clk_wiz_0 u_clk_wiz_0 ( .clk_in1 (clk_50m), .clk_out1 (clk_100m), .clk_out2 (clk_200m), .clk_out3 (clk_200m_90), .clk_out4 (clk_25m), .reset (~rst_n), .locked (clk_locked) ); // 异步复位、同步释放:locked 参与释放判断 reg [1:0] rst_sync; always @(posedge clk_100m or negedge rst_n) begin if (!rst_n) rst_sync <= 2'b00; else begin rst_sync[0] <= clk_locked; rst_sync[1] <= rst_sync[0]; end end assign sys_rst_n = rst_sync[1]; endmodule先看例化部分,IP核端口和顶层一一对应,没有额外逻辑。关键在第14行的复位同步块:rst_sync两个触发器构成经典两级同步器,rst_sync[0]跟随clk_locked,rst_sync[1]再拍一拍作为系统复位信号输出。sys_rst_n只在clk_100m上升沿变化,并且外部rst_n已经稳定,下游逻辑拿到的是干净的复位释放时序。
不能直接把clk_locked接到sys_rst_n,原因是上电瞬间MMCM还没锁定,locked可能经历一段不确定的低电平抖动;如果系统里同时有按键复位和看门狗复位,它们和locked没有同步关系,直接组合会产生亚稳态。
4.2 关于复位的两个经验
第一个经验是和复位极性较劲。Clocking Wizard IP核的reset端口是高有效,习惯低有效复位的人在例化时很容易忘记取反。一旦忘掉,IP核会一直处于复位状态,输出没有时钟,locked永远为低。排查这类问题时先看reset脚电平,往往比反复改分频参数更高效。
第二个经验是sys_rst_n不要接回IP的reset。如果复位同步逻辑把sys_rst_n又送回clk_wiz_0的reset端口,就会形成循环依赖:MMCM等待复位释放,复位释放依赖locked,locked要等复位释放后才拉高,系统直接卡死。正确做法是只把外部rst_n取反后接IP核,locked只进同步逻辑,不再往回绕。
4.3 顶层约束:输入时钟必须显式声明
驱动模块写完后打开XDC文件,先对板级时钟做约束:
create_clock -name clk_50m -period 20.000 -waveform {0 10.000} [get_ports clk_50m]period=20.000对应50MHz时钟周期;waveform指定0到10ns为高电平,占空比50%。clk_50m是顶层端口名,必须和Verilog里的命名一致。保存约束后,Vivado综合实现时会自动从MMCM的输出推导出generated clock,不需要对clk_100m、clk_200m再手工创建时钟。
如果板载时钟引脚不是全局时钟引脚,或者输入来自普通IO,综合时可能报CLOCK_DEDICATED_ROUTE错误。这种场景的常见做法是加一段约束绕过专用引脚规则:
set_property CLOCK_DEDICATED_ROUTE ANY_CMT_COLUMN [get_nets clk_50m_IBUF_BUFG]这里的网表名以综合后实际网络为准,先跑一次综合再回来填。除非排错到最后一步,否则不要用FORCE,它会把时钟路径上的其他问题也一起掩盖。
5. 仿真与ILA验证:MMCM-PLL输出的频率和相位对不对
5.1 用最小testbench验证频率
MMCM-PLL的仿真模型在Vivado自带仿真器里直接就能跑,不需要另外准备仿真库。下面这个testbench以50MHz激励,重点统计200us内clk_100m的上升沿次数:
`timescale 1ns/1ps module tb_mmcm_drv; reg clk_50m = 1'b0; reg rst_n = 1'b0; wire clk_100m, clk_200m, clk_200m_90, clk_25m; wire clk_locked, sys_rst_n; always #10 clk_50m = ~clk_50m; // 50MHz, 周期20ns reg [31:0] cnt_100m = 32'd0; always @(posedge clk_100m) cnt_100m <= cnt_100m + 1; mmcm_drv_top dut ( .clk_50m (clk_50m), .rst_n (rst_n), .clk_100m (clk_100m), .clk_200m (clk_200m), .clk_200m_90 (clk_200m_90), .clk_25m (clk_25m), .clk_locked (clk_locked), .sys_rst_n (sys_rst_n) ); initial begin #100; rst_n = 1'b1; wait (clk_locked); #200000; // 等待200us $display("cnt_100m = %0d, expect 20000", cnt_100m); $display("locked = %0b", clk_locked); $finish; end endmodulealways #10生成20ns周期的时钟,对应50MHz输入。计数器挂在clk_100m的上升沿,200us内如果频率正确应该数到20000。如果比这个值少很多,先看locked是否一直为低;如果locked正常但数量不对,重点检查Clocking Wizard里输入频率是否填错,工程里所有时钟模块的输入定义必须和板载晶振严格一致。
相位验证可以在仿真里比较clk_200m和clk_200m_90的上升沿时间差。200MHz下一周期5ns,90度相移对应1.25ns,仿真精度1ns时能看出一个整刻度。用数值比较替代肉眼观察波形,这个习惯放进回归脚本之后很有价值。
5.2 上板看信号:ILA探针的接法与触发
仿真通过不代表上板一定通过,板上调试离不开ILA。Vivado的ILA IP核在这里的用法是:新建一个ILA,设置Probe端口数量和数据位宽,采样深度一般取1024或4096,采样时钟建议用clk_50m而不是clk_100m。原因是ILA要观察MMCM从未锁定到锁定的完整过程,如果用MMCM自己生成的clk_100m当采样时钟,locked拉高前的波形全部看不到。
探针接线建议覆盖三组信号:clk_locked(1位)、sys_rst_n(1位)、以及clk_100m输出时钟。触发条件设为clk_locked上升沿,这样上板后按一次复位,ILA能抓下从复位释放到锁定的完整时序。把ILA例化插入顶层后重新综合实现,打开Hardware Manager连接开发板、下载bitstream,在ILA触发器窗口选好触发条件,再按板上复位键就能看到locked拉高的相对时刻。
这里提醒一个常见误区:ILA不要挂太多。一个工程里放三四个ILA,每个采样深度4096,Block RAM会被探针存储占掉很多。XC7A35T的Block RAM一共约50块(36Kb each),几个ILA就能让资源报告明显变化。调试结束后记得把ILA移除再跑最终实现,这是规范流程。
5.3 三类典型现象的排查顺序
第一类,locked一直为低且reset电平正常。打开Clocking Wizard看M/D/O参数是否让VCO超出范围。对XC7A35T的-1速度等级,VCO应保持在大约600MHz到1200MHz区间,超范围时工具生成阶段就会给出CRITICAL WARNING,综合日志里搜warning能快速定位。
第二类,locked拉高但没有输出时钟。重点检查输出时钟是否被误接到普通逻辑引脚又被其他信号占用,或者BUFG资源耗尽。Vivado的Report Utilization里搜BUFG一项能看到占用情况。
第三类,仿真正常但上板后频率整体偏差。先测输入晶振实际频率,再看约束里create_clock的period是否写错。实际项目中遇到过原理图标注50MHz、板上焊成48MHz晶振的情况,改焊或者改约束都比反复调MMCM参数更实际。
6. 进阶:动态相移、时钟切换与引脚输出时钟的约束技巧
6.1 动态相移的握手时序
运行中需要微调相位时,要在Clocking Wizard的Output Clocks页面勾选Dynamic Phase Shift,生成后IP核会多出psclk、psen、psincdec、psdone四个端口。每次调整的流程是:psclk提供稳定时钟,psen拉高一个psclk周期,同时psincdec给出方向,1为相位增加、0为减小,之后等待psdone拉高表示本次调整完成,再发起下一次。最小相移步进是一个VCO周期对应的1/8,具体数值查器件手册里的Phase Shift表格,不要假设一次脉冲对应固定角度。
6.2 用BUFGMUX做无毛刺时钟切换
不要通过复位MMCM再重新锁定的方式切换时钟源,锁定期间输出时钟会中断,下游状态机全乱。需要冗余切换时,常见做法是用BUFGMUX原语在两路已锁定时钟之间选择:
BUFGMUX u_bufgmux ( .O (clk_sel), .I0(clk_a), .I1(clk_b), .S (sel) );S为低时选择I0,S为高时选择I1。BUFGMUX内部会等当前选中时钟电平回到低点再切换,从而避免毛刺,这是普通逻辑无法替代的。
6.3 从引脚输出时钟时的最后一公里
MMCM-PLL的输出如果要送到板外,比如作为ADC采样时钟,不要把clk_200m直接约束到普通IO引脚。Vivado会报CLOCK_DEDICATED_ROUTE违规,因为普通IO没有直达时钟网络的专用路径。推荐先例化ODDR原语复制时钟:
ODDR #(.DDR_CLK_EDGE("SAME_EDGE")) u_oddr_out ( .Q (clk_200m_pad), .C (clk_200m), .CE(1'b1), .D1(1'b1), .D2(1'b0), .R (1'b0), .S (1'b0) );D1固定1、D2固定0时,ODDR的输出Q就是C的复制时钟,驱动能力比直接拉线更好。然后对输出引脚做IO电平约束,并在XDC里对相关网络声明:
set_property CLOCK_DEDICATED_ROUTE ANY_CMT_COLUMN [get_nets clk_200m_OBUF]重新跑综合实现后,在Device视图里展开CMT区域,能看到clk_200m从MMCM输出端经过BUFG进入ODDR再到OBUF,整条路径清晰可见。如果发现ODDR被综合器优化掉,说明时钟被误当成普通逻辑路径处理,需要回到约束里确认I/O标准与D2端常量是否生效。
本文还有配套的精品资源,点击获取