第二十三章 PLL IP核实验

对应《正点原子 ZYNQ FPGA 开发指南》第二十三章(约 P518~P558),本章无外部硬件参与,输出时钟通过 J3 扩展口引出,用示波器测量验证。

本章在讲什么?(先看这个)

前 22 章里,FPGA 内所有逻辑都跑在同一个 50MHz 系统时钟上。但真实工程里经常需要别的频率:SDRAM 要 100MHz,VGA 要 25MHz,有时还要求两个时钟之间有固定的相位关系。这些时钟没法用 Verilog 代码"写"出来——计数器分频只能得到占空比难看、抖动大的低速时钟,倍频更是完全做不到。能干这活的是芯片里的硬件资源:锁相环(PLL)。

本章做三件事:

  1. 理解 PLL 的工作原理(分频、倍频、相位偏移是怎么实现的);
  2. 通过 Clocking Wizard 配置一个 PLL IP 核,输出 100MHz、100MHz(偏移180°)、50MHz、25MHz 四路时钟;
  3. 学习 Vivado 自带仿真器的用法,以及如何和 Modelsim 联合仿真(这是带 IP 核工程仿真的必修课)。

核心概念拆解

23.1 PLL IP 核简介

锁相环是个闭环反馈系统。 它由三个部件串成环:鉴频鉴相器(PFD)比较两个时钟的频率/相位差 → 环路滤波器(LF)把误差信号滤平滑 → 压控振荡器(VCO)根据电压输出振荡信号,再送回 PFD 继续比较。只要两个频率不等,环路就一直调整,直到"锁住"为止——这就是名字的由来。

图23.1.1 PLL工作原理图

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

图23.1.2 行为类比图

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

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

图23.1.3 PLL分频原理图

图23.1.4 PLL倍频原理图

几个必须记住的点:

  • 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.2.1 J3扩展口原理图

23.3 实验任务

输出 4 路时钟:100MHz、100MHz 相位偏移 180°、50MHz、25MHz,先仿真验证,再下载到板子上用示波器实测频率。

23.4 程序设计

PLL IP 核配置

新建 ip_clk_wiz 工程,在 Flow Navigator 里点开 IP Catalog,搜 "clock",双击 Clocking Wizard

图23.4.1 点击IP Catalog

图23.4.4 Customize IP窗口

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

图23.4.14 PLL IP核的各个参数配置页面

① Clocking Options 选项卡(输入侧):

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

图23.4.15 Clocking Options选项卡的设置

② Output Clocks 选项卡(输出侧,本章重点):

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

图23.4.16 Output Clocks选项卡的设置

③~⑥ 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 个时钟输出:

图23.4.23 模块框图与端口

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

图23.4.24 ip_clk_wiz模块波形图

编写 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 联合仿真。

图23.5.1 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。

图23.5.9 Vivado仿真界面

三个窗口的分工: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。频率全部正确。

图23.5.21 Vivado仿真波形

知识扩展——Vivado 的 5 种仿真模式:行为级仿真(功能仿真,必做)→ 综合后功能仿真 → 综合后时序仿真 → 实现后功能仿真 → 实现后时序仿真(带 SDF 延时标注,最接近真实但极耗时)。经验法则:行为级仿真必须做,后仿真只在排查疑难杂症时才用

Modelsim 联合仿真

想用 Modelsim 仿真带 IP 核的工程,需三步:

  1. 编译仿真库:在 Modelsim 安装目录建一个 vivado2020.2_lib 文件夹 → 打开 Vivado(不要打开工程)→ Tools → Compile Simulation Libraries,仿真器选 ModelSim Simulator,库路径指向刚建的文件夹,器件系列按需勾选(全选会编译很久),点 Compile,约 10~20 分钟。版本必须匹配(本书 Vivado 2020.2 + Modelsim 2020.4,可查 UG973);
  2. 关联 Modelsim:Tools → Settings → 3rd Party Simulators,指定 Modelsim 安装路径与仿真库路径;
  3. 工程级设置:打开工程 → 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 的第一种用法。