第二十三章 PLL IP核实验
对应《正点原子 ZYNQ FPGA 开发指南》第二十三章(约 P518~P558),本章无外部硬件参与,输出时钟通过 J3 扩展口引出,用示波器测量验证。
本章在讲什么?(先看这个)
前 22 章里,FPGA 内所有逻辑都跑在同一个 50MHz 系统时钟上。但真实工程里经常需要别的频率:SDRAM 要 100MHz,VGA 要 25MHz,有时还要求两个时钟之间有固定的相位关系。这些时钟没法用 Verilog 代码"写"出来——计数器分频只能得到占空比难看、抖动大的低速时钟,倍频更是完全做不到。能干这活的是芯片里的硬件资源:锁相环(PLL)。
本章做三件事:
- 理解 PLL 的工作原理(分频、倍频、相位偏移是怎么实现的);
- 通过 Clocking Wizard 配置一个 PLL IP 核,输出 100MHz、100MHz(偏移180°)、50MHz、25MHz 四路时钟;
- 学习 Vivado 自带仿真器的用法,以及如何和 Modelsim 联合仿真(这是带 IP 核工程仿真的必修课)。
核心概念拆解
23.1 PLL IP 核简介
锁相环是个闭环反馈系统。 它由三个部件串成环:鉴频鉴相器(PFD)比较两个时钟的频率/相位差 → 环路滤波器(LF)把误差信号滤平滑 → 压控振荡器(VCO)根据电压输出振荡信号,再送回 PFD 继续比较。只要两个频率不等,环路就一直调整,直到"锁住"为止——这就是名字的由来。

如果觉得抽象,可以用开车类比:道路方向是参考时钟,行驶方向是输出时钟,人眼是 PFD(负责发现偏差),大脑是 LF(负责判断怎么修正),方向盘是 VCO(负责执行修正)。反复"发现偏差→修正→再发现",最终车走的方向就和路一致了。

分频:在 PFD 前面插一个前置分频器,参考时钟先除以 N 再比较,环路就把输出频率锁定到 ref_clk/N。
倍频:在反馈路径上插一个后置分频器,输出时钟除以 M 后再去比较,环路就把输出锁定到 ref_clk×M。


几个必须记住的点:
- CMT 与 MMCM/PLL 的关系:7 系列器件的时钟管理单元 CMT = 1 个 MMCM + 1 个 PLL。xc7z020 有 4 个 CMT,xc7z010 有 2 个。MMCM 是"PLL + 数字动态调相"的混合体,功能更强但占面积更大;单纯做分频/倍频/固定相移,两者都行。Clocking Wizard 里默认选的就是 MMCM。
- 频率范围有硬限制:xc7z020 的 PLL 输入范围 19~800MHz,输出范围 6.25~800MHz,且一个 MMCM 最多输出 7 路时钟。超出范围 Vivado 会直接报错或给出 Actual ≠ Requested 的结果。
- Xilinx 的 PLL 是模拟锁相环:稳定度高、锁定快,但极端温度/磁场下可能失锁——所以 IP 核才会输出一个
locked信号告诉你"时钟已稳定"。
23.2 硬件设计
本章没有按键、没有 LED,四路时钟从底板 J3 扩展口引出,用示波器测量。J3 扩展口的 IO 直连 FPGA 引脚,注意两点:J3 与 LCD 接口引脚复用(用 J3 就不能同时用 LCD);J4 扩展口只有 7020 核心板才能用,所以实验统一选 J3。

23.3 实验任务
输出 4 路时钟:100MHz、100MHz 相位偏移 180°、50MHz、25MHz,先仿真验证,再下载到板子上用示波器实测频率。
23.4 程序设计
PLL IP 核配置
新建 ip_clk_wiz 工程,在 Flow Navigator 里点开 IP Catalog,搜 "clock",双击 Clocking Wizard:


配置窗口分四块:工具栏(Documentation/IP Location/恢复默认)、IP 展示窗口(实时显示引脚)、命名栏、参数配置页面。配置页面共 6 个选项卡:

① Clocking Options 选项卡(输入侧):
- Primitive:选 MMCM 还是 PLL,保持默认 MMCM;
- Input Frequency:输入主时钟设为 50MHz(板上晶振频率);
- Source:输入时钟来源。系统时钟从晶振经单端引脚进来,选 "Single ended clock capable pin"。如果是差分时钟选 Differential,如果输入来自全局时钟网络(比如另一个 PLL 的输出)则选 Global buffer;
- Input Jitter:输入抖动,保持默认。

② Output Clocks 选项卡(输出侧,本章重点):
- 勾选前 4 路 Output Clock,Requested 频率分别填 100、100、50、25(MHz),Actual 列会显示实际能精确做到的频率;
- 第二路 100MHz 的 Phase 设为 180 度;
- Duty cycle 保持 50%;Drives 保持 BUFG(全局缓冲器,时钟要挂全局时钟网络就必须用它);
- 保持启用 reset(高电平有效)和 locked 输出。

③~⑥ Port Renaming / MMCM Setting / Summary:MMCM Setting 是 Vivado 自动算出的最优内部参数(VCO 频率、各分频系数),不要手改;Summary 核对无误后点 OK → Generate 生成 IP。
生成后 Design Runs 的 Out-of-Context 一栏会出现 clk_wiz_0_synth_1——IP 核在做独立综合(OOC),与顶层综合并行进行,此时就可以同步开始写代码了。
模块设计
模块 ip_clk_wiz 只有 2 个输入(系统时钟、复位)和 4 个时钟输出:

波形上要注意 locked 信号:locked 拉高之前,四路输出时钟是不稳定的(可能恒高、恒低或乱飘),只有 locked 为高后才输出稳定时钟。所以实际工程中常用 locked 参与系统复位释放。

编写 RTL 代码
IP 核的例化模板不用手写:Source 窗口 → IP Sources → clk_wiz_0 → Instantiation Template 下的 clk_wiz.veo,直接复制里面的例化框架改连线即可。完整顶层代码:
module ip_clk_wiz(
input sys_clk , //系统时钟
input sys_rst_n , //系统复位,低电平有效
//输出时钟
output clk_100m , //100Mhz 时钟频率
output clk_100m_180deg, //100Mhz 时钟频率,相位偏移180度
output clk_50m , //50Mhz 时钟频率
output clk_25m //25Mhz 时钟频率
);
//wire define
wire locked;
//------------------------------------------------
//PLL IP 核的例化
clk_wiz_0 clk_wiz_0
(
// Clock out ports
.clk_out1 (clk_100m ), // output clk_out1
.clk_out2 (clk_100m_180deg), // output clk_out2
.clk_out3 (clk_50m ), // output clk_out3
.clk_out4 (clk_25m ), // output clk_out4
// Status and control signals
.reset (~sys_rst_n ), // input reset
.locked (locked ), // output locked
// Clock in ports
.clk_in1 (sys_clk ) // input clk_in1
);
endmodule
唯一容易出错的细节:IP 核配置时保持默认的高电平复位,而板上 sys_rst_n 是低电平有效,所以例化时必须写 ~sys_rst_n 取反,否则板上复位一按 PLL 反而工作、松开反而复位。
23.5 仿真验证
Modelsim 手动仿真会报错
用 Modelsim 直接仿真这个工程会报错——因为缺少 IP 核的仿真库文件。两条出路:① 用 Vivado 自带仿真器;② 配置 Vivado 与 Modelsim 联合仿真。

TestBench 非常简单,只有时钟和复位两个激励:
`timescale 1ns / 1ps
module tb_ip_clk_wiz();
parameter CLK_PERIOD = 20; //时钟周期 20ns
reg sys_clk;
reg sys_rst_n;
wire clk_100m;
wire clk_100m_180deg;
wire clk_50m;
wire clk_25m;
//信号初始化
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#200
sys_rst_n = 1'b1;
end
//产生时钟
always #(CLK_PERIOD/2) sys_clk = ~sys_clk;
ip_clk_wiz u_ip_clk_wiz(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.clk_100m (clk_100m ),
.clk_100m_180deg (clk_100m_180deg),
.clk_50m (clk_50m ),
.clk_25m (clk_25m )
);
endmodule
Vivado 自带仿真器的使用
把 TB 添加到 Simulation Sources(注意不要勾选 "Scan and add RTL include files" 和 "Copy sources into project",避免工程目录混乱),然后 Run Simulation → Run Behavioral Simulation。

三个窗口的分工:Scope 显示设计层次,Objects 显示当前层次里的所有 reg/wire,右键对象可 "Add to Wave Window" 加入波形窗口。仿真器默认只跑 1000ns,且手动加入的信号(如 locked)当时没有波形——所以要看完整波形需要点 Restart 清零,再 Run For 10us,最后 Zoom Fit 全览。记得点保存按钮把波形配置存成 .wcfg 文件(注意它只存配置不存波形数据),下次仿真就不用重新摆信号了。
仿真结果:locked 拉高后,clk_100m 与 clk_100m_180deg 周期都是 10ns,后者相对系统时钟偏移 180°(相当于取反);clk_50m 周期 20ns;clk_25m 周期 40ns。频率全部正确。

知识扩展——Vivado 的 5 种仿真模式:行为级仿真(功能仿真,必做)→ 综合后功能仿真 → 综合后时序仿真 → 实现后功能仿真 → 实现后时序仿真(带 SDF 延时标注,最接近真实但极耗时)。经验法则:行为级仿真必须做,后仿真只在排查疑难杂症时才用。
Modelsim 联合仿真
想用 Modelsim 仿真带 IP 核的工程,需三步:
- 编译仿真库:在 Modelsim 安装目录建一个
vivado2020.2_lib文件夹 → 打开 Vivado(不要打开工程)→ Tools → Compile Simulation Libraries,仿真器选 ModelSim Simulator,库路径指向刚建的文件夹,器件系列按需勾选(全选会编译很久),点 Compile,约 10~20 分钟。版本必须匹配(本书 Vivado 2020.2 + Modelsim 2020.4,可查 UG973); - 关联 Modelsim:Tools → Settings → 3rd Party Simulators,指定 Modelsim 安装路径与仿真库路径;
- 工程级设置:打开工程 → Settings → Simulation → Target simulator 选 ModelSim Simulation,并指定库路径。之后点 Run Behavioral Simulation,Vivado 会自动拉起 Modelsim 完成编译和仿真。
两者波形结果完全一致,但体验有别:Vivado 自带仿真器方便快捷,可它仅限 Vivado 内使用,且大工程仿真慢、易卡死;Modelsim 通用、速度快。简单工程用自带仿真器图省事,正经项目建议以 Modelsim 为主。
23.6 下载验证
引脚分配(四路时钟从 J3 扩展口引出):
| 信号名 | 方向 | 管脚 | 端口说明 | 电平标准 |
|---|---|---|---|---|
| sys_clk | input | U18 | 系统时钟,50MHz | LVCMOS33 |
| sys_rst_n | input | N16 | 系统复位按键,低电平有效 | LVCMOS33 |
| clk_100m | output | U20 | 100MHz时钟 | LVCMOS33 |
| clk_100m_180deg | output | T20 | 100MHz时钟,相位偏移180° | LVCMOS33 |
| clk_50m | output | N18 | 50MHz时钟 | LVCMOS33 |
| clk_25m | output | P19 | 25MHz时钟 | LVCMOS33 |
对应的 XDC 约束:
create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN U20 IOSTANDARD LVCMOS33} [get_ports clk_100m]
set_property -dict {PACKAGE_PIN T20 IOSTANDARD LVCMOS33} [get_ports clk_100m_180deg]
set_property -dict {PACKAGE_PIN N18 IOSTANDARD LVCMOS33} [get_ports clk_50m]
set_property -dict {PACKAGE_PIN P19 IOSTANDARD LVCMOS33} [get_ports clk_25m]
生成比特流下载后,用示波器依次测 J3 扩展口的 U20、T20、N18、P19 四个引脚。实测 P19 上频率为 25MHz,与仿真一致。
初学者容易踩的坑
- 复位极性忘记取反。Clocking Wizard 的 reset 默认高电平有效,板上按键是低电平有效,例化时漏写
~sys_rst_n会导致复位逻辑完全反掉。要么取反接入,要么在 IP 配置里把 Reset Type 改成 Active Low。 - locked 之前的时钟不可用。上电后 PLL 需要一段时间锁定,locked 拉高前输出时钟不稳定。跨时钟域逻辑或全局复位释放应参考 locked 信号(第 26 章 FIFO 实验里就用了
rst_n = sys_rst_n & locked)。 - Modelsim 直接仿真带 IP 的工程必报错,这不是代码写错了,是缺仿真库。先编译仿真库并做好关联,或者先用 Vivado 自带仿真器验证功能。
- Output Freq 只填 Requested 列,Actual 列是 Vivado 实际能做到的频率。填一个除不尽的频率(如 33MHz)时 Actual 与 Requested 会有偏差,Vivado 也会给警告——这是正常现象,但要清楚你的系统实际跑在 Actual 频率上。
- 添加仿真文件时不要勾选 "Copy sources into project",否则 Vivado 会把文件复制到另一个目录,后续改代码容易改到"没被工程使用的那份"。
- 示波器测出的是"三角波"不代表时钟错了。25MHz 方波对示波器带宽/采样率要求不低,采样率不够时方波显示成近似三角波,用示波器的频率测量功能读频率即可。
小结 & 下一步
本章第一次接触"IP 核"这种 Vivado 自带的可配置模块,重点是 Clocking Wizard 的配置流程(输入频率/来源 → 输出路数/频率/相位 → 驱动类型 BUFG → reset/locked)和例化方法,这两个套路在后续 RAM、FIFO 等 IP 上几乎原样复用。同时掌握了 Vivado 自带仿真器的完整操作链(添加 TB → Run Behavioral Simulation → Restart/Run For/Zoom Fit → 保存 wcfg)和 Modelsim 联合仿真的三步配置。
下一步进入第二十四章:单端口 RAM IP 核实验,开始学习 FPGA 内部存储资源 BRAM 的第一种用法。