FPGA教程系列-Vivado IP核Clock Wizard核解析及测试
PLL简介
锁相环作为一种反馈控制电路,其特点是利用外部输入的参考信号来控制环路内部震荡信号的频率和相位。因为锁相环可以实现输出信号频率对输入信号频率的自动跟踪,所以锁相环通常用于闭环跟踪电路。锁相环在工作的过程中,当输出信号的频率与输入信号的频率相等时,输出电压与输入电压保持固定的相位差值,即输出电压与输入电压的相位被锁住,这就是锁相环名称的由来。
Vivado中的生成锁相环的IP核为Clocking Wizard:

下面简单的解释一下这个IP核:

-
Clock Monitor: 用来监控时钟是否停止、故障和频率变化。一般不用。
-
Primitive: 选项用于选择是使用MMCM还是PLL来完成时钟需求。
-
Clocking Features: 用来设置时钟的特征,包括Frequency Synthesis(频率合成)、Minimize Power(最小化功率)、Phase Alignment(相位校准)、Dynamic Reconfig(动态重配置)、Safe Clock Startup(安全时钟启动)等,其中Spread Spectrum(扩频)和Dynamic Phase Shift(动态相移)是使用MMCM时才能够设置的特征,这里保持默认的设置即可,感兴趣的也可以勾选其它选项来观察会有什么不同效果。
-
Jitter Optimization: 用于抖动优化,可选Balanced(平衡)、Minimize Output Jitter(最小化输出抖动)或Maximize Input Jitter filtering(最大化输入抖动滤波)等优化方式。
-
Dynamic Reconfig Interface Options: 用于选择动态重构接口,只有在设置时钟的特征时勾选动态重构选项后方可进行配置。
-
Input Clock Information: 下的表格用于设置输入时钟的信息,其中:
-
Input Clock(输入时钟): Primary(主要,即主时钟)是必要的,Secondary(次要,即副时钟)是可选是否使用的,若使用了副时钟则会引入一个时钟选择信号(clk_in_sel),需要注意的是主副时钟不是同时生效的,可以通过控制clk_in_sel的高低电平来选择使用哪一个时钟,当clk_in_sel为1时选择主时钟,当clk_in_sel为0时选择副时钟。
-
Port Name(端口名称): 可以对输入时钟的端口进行命名。
-
Input Frequency(输入频率): 可以设置输入信号的时钟频率,单位为MHz,主时钟可配置的输入时钟范围(19MHz ~800MHz)可以在其后面的方块中进行查看;副时钟可配置的时钟输入范围会随着主时钟的频率而有所改变,具体范围同样可以在其后面的方块中进行查看。
-
Jitter Options(抖动选项): 有UI(百分比)和PSS(皮秒)两种表示单位可选。
-
Input Jitter(输入抖动): 为设置时钟上升沿和下降沿的时间,例如输入时钟为50MHz,Jitter Options选择UI,Input Jitter输入0.01,表示上升沿和下降沿的时间不超过0.2ns(20ns * 1%),若此时将UI改为PSS,则0.01会自动变成200(0.2ns = 200ps)。
-
Source(来源): 有四种选项:
- Single ended clock capable pin(支持单端时钟引脚): 当输入的时钟来自于单端时钟引脚时,需要选择这个。
- Differential clock capable pin(支持差分时钟引脚): 当输入的时钟来自于差分时钟引脚时,需要选择这个。
- Global buffer(全局缓冲器): 输入时钟只要在全局时钟网络上,就需要选择这个。例如前一个PLL IP核的输出时钟接到后一个PLL IP核的输入时,只要前一个PLL输出的时钟不是"No buffer"类型即可。
- No buffer(无缓冲器): 输入时钟必须经过全局时钟缓冲器(BUFG),才可以选择这个。例如前一个PLL IP核的输出时钟接到后一个PLL IP核的输入时,前一个PLL输出的时钟必须为BUFG或者BUFGCE类型才可以。
-

Output 选项卡: 各参数含义如下:
-
Output Clock表格: 用于设置输出时钟的路数(一个PLL IP核最多可输出六路不同频率的时钟信号)及参数,其中:
-
Output Clock: 为设置输出时钟的路数。
-
Port Name: 为设置时钟的名字。
-
Output Freq(MHz): 为设置输出时钟的频率,需要注意的是PLL IP核的时钟输出范围为6.25MHz ~800MHz,但这个范围是一个整体范围,根据驱动器类型的选择不同,其所支持的最大输出频率也会有所差异。
-
Phase (degrees): 为设置时钟的相位偏移。
-
Duty cycle: 为占空比,正常情况下如果没有特殊要求的话,占空比一般都是设置为50%。
-
Drives: 为驱动器类型,有五种驱动器类型可选:
- BUFG: 是全局缓冲器,如果时钟信号要走全局时钟网络,必须通过BUFG来驱动,BUFG可以驱动所有的CLB,RAM,IOB。
- BUFH: 是区域水平缓冲器,BUFH可以驱动其水平区域内的所有CLB,RAM,IOB。
- BUFGCE: 是带有时钟使能端的全局缓冲器,它有一个输入端I、一个使能端CE和一个输出端O。只有当BUFGCE的使能端CE有效(高电平)时,BUFGCE才有输出。
- BUFHCE: 是带有时钟使能端的区域水平缓冲器,用法与BUFGCE类似。
- No buffer: 即无缓冲器,当输出时钟无需挂在全局时钟网络上时,可以选择无缓冲区。
-
Max Freq of buffer: 为缓冲器的最大频率,例如我们选取的BUFG缓冲器支持的最大输出频率为628.141MHz。
-
-
USE CLOCK SEQUENCING(使用时钟排序): 当在第一个选项卡上启用安全时钟启动功能时,Use Clock Sequence表处于活动状态,可用于配置每个已启用时钟的序列号。在此模式下,只允许BUFGCE作为时钟输出的驱动程序。
-
Clocking Feedback(时钟反馈): 用于设置时钟信号的来源是片上还是片外,是自动控制还是用户控制,当自动控制片外的时钟时,还需要配置时钟信号的传递方式是单端还是差分。
-
Enable Optional Inputs/Outputs for MMCM/PLL(启用MMCM/PLL的可选输入/输出): 其中reset(复位)和power down(休眠)为输入信号,locked(锁定)、clkfbstopped(指示信号,表示反馈时钟是否丢失)和input_clk_stopped(指示信号,表示所选输入时钟不再切换)为输出信号。
-
Reset Type(复位类型): 用于设置复位信号是高电平有效还是低电平有效,这里我们可以保持默认的高电平有效。

- Port Renaming 选项卡: 主要是对一些控制信号(复位信号以外的信号)的重命名。在上一个选项卡中我们启用了锁定信号locked,因此这里我们只看到了locked这一个可以重命名的信号,因为默认的名称已经可以让我们一眼看出该信号的含义,所以无需重命名,保持默认即可。

- MMCM Settings 选项卡: 展示了对整个PLL的最终配置参数,这些参数都是由Vivado根据之前用户输入的时钟需求来自动配置的,Vivado已经对参数进行了最优的配置,在绝大多数情况下都不需要用户对它们进行更改,也不建议更改,所以这一步保持默认即可。
设置输出4 个不同频率或相位的时钟,四个时钟分别为一个倍频时钟(100MHz),一个倍频后相位偏移180 度的时钟(100MHz),一个与系统时钟相同的时钟(50MHz)和一个分频时钟(25MHz),并在Vivado 中进行仿真以验证结果。
这里还是继续用Testbench调用生成好的IP核。
verilog
`timescale 1ns / 1ps
module tb_clk_wiz_0;
// =========================================================================
// 1. 参数定义
// =========================================================================
// 系统时钟周期 (50MHz -> 20ns)
localparam CLK_IN_PERIOD_NS = 20;
localparam CLK_IN_HALF_PERIOD_NS = CLK_IN_PERIOD_NS / 2;
// 期望的输出时钟周期
localparam CLK_OUT1_PERIOD_NS = 10; // 100MHz
localparam CLK_OUT2_PERIOD_NS = 10; // 100MHz
localparam CLK_OUT3_PERIOD_NS = 20; // 50MHz
localparam CLK_OUT4_PERIOD_NS = 40; // 25MHz
// 仿真运行时间
localparam SIM_DURATION_NS = 10000; // 10us,足够观察锁定和多个时钟周期
// =========================================================================
// 2. Testbench 信号声明
// =========================================================================
// 输入到 DUT (Device Under Test) 的信号
reg clk_in1;
reg reset;
// 从 DUT 输出的信号
wire clk_out1;
wire clk_out2;
wire clk_out3;
wire clk_out4;
wire locked;
// =========================================================================
// 3. DUT (Device Under Test) 实例化
// =========================================================================
clk_wiz_0 instance_name (
// Clock out ports
.clk_out1(clk_out1), // output clk_out1 (100MHz)
.clk_out2(clk_out2), // output clk_out2 (100MHz, 180deg phase)
.clk_out3(clk_out3), // output clk_out3 (50MHz)
.clk_out4(clk_out4), // output clk_out4 (25MHz)
// Status and control signals
.reset(reset), // input reset
.locked(locked), // output locked
// Clock in ports
.clk_in1(clk_in1) // input clk_in1 (50MHz)
);
// =========================================================================
// 4. 时钟和复位信号生成
// =========================================================================
// 生成 50MHz 的输入时钟
initial begin
clk_in1 = 0;
forever #(CLK_IN_HALF_PERIOD_NS) clk_in1 = ~clk_in1;
end
// 生成复位信号
initial begin
// 在仿真开始时,复位信号有效
reset = 1;
// 保持复位几个时钟周期,确保 IP 核能正确复位
#(100); // 100ns
// 释放复位
reset = 0;
$display("--- Reset Released at %0t ns ---", $time);
end
// =========================================================================
// 5. 验证逻辑
// =========================================================================
// --- 5.1 基本监控 ---
// 使用 $monitor 可以在任何一个信号变化时打印其值,方便调试
initial begin
$monitor("Time=%0t ns | locked=%b | clk_in1=%b | clk_out1=%b | clk_out2=%b | clk_out3=%b | clk_out4=%b",
$time, locked, clk_in1, clk_out1, clk_out2, clk_out3, clk_out4);
end
// --- 5.2 locked 信号检查 ---
initial begin
wait(locked);
$display("*** Locked signal asserted at %0t ns. Clocks should be stable now. ***", $time);
end
// --- 5.3 频率验证 ---
// 验证 clk_out1 (100MHz)
reg [63:0] prev_time_out1 = 0;
always @(posedge clk_out1) begin
if (prev_time_out1 != 0) begin
real measured_period = $time - prev_time_out1;
if (measured_period == CLK_OUT1_PERIOD_NS) begin
$display("[PASS] clk_out1 frequency is correct. Measured Period: %0.2f ns", measured_period);
end else begin
$error("[FAIL] clk_out1 frequency is incorrect! Expected: %0d ns, Measured: %0.2f ns", CLK_OUT1_PERIOD_NS, measured_period);
end
end
prev_time_out1 = $time;
end
// 验证 clk_out2 (100MHz)
reg [63:0] prev_time_out2 = 0;
always @(posedge clk_out2) begin
if (prev_time_out2 != 0) begin
real measured_period = $time - prev_time_out2;
if (measured_period == CLK_OUT2_PERIOD_NS) begin
$display("[PASS] clk_out2 frequency is correct. Measured Period: %0.2f ns", measured_period);
end else begin
$error("[FAIL] clk_out2 frequency is incorrect! Expected: %0d ns, Measured: %0.2f ns", CLK_OUT2_PERIOD_NS, measured_period);
end
end
prev_time_out2 = $time;
end
// 验证 clk_out3 (50MHz)
reg [63:0] prev_time_out3 = 0;
always @(posedge clk_out3) begin
if (prev_time_out3 != 0) begin
real measured_period = $time - prev_time_out3;
if (measured_period == CLK_OUT3_PERIOD_NS) begin
$display("[PASS] clk_out3 frequency is correct. Measured Period: %0.2f ns", measured_period);
end else begin
$error("[FAIL] clk_out3 frequency is incorrect! Expected: %0d ns, Measured: %0.2f ns", CLK_OUT3_PERIOD_NS, measured_period);
end
end
prev_time_out3 = $time;
end
// 验证 clk_out4 (25MHz)
reg [63:0] prev_time_out4 = 0;
always @(posedge clk_out4) begin
if (prev_time_out4 != 0) begin
real measured_period = $time - prev_time_out4;
if (measured_period == CLK_OUT4_PERIOD_NS) begin
$display("[PASS] clk_out4 frequency is correct. Measured Period: %0.2f ns", measured_period);
end else begin
$error("[FAIL] clk_out4 frequency is incorrect! Expected: %0d ns, Measured: %0.2f ns", CLK_OUT4_PERIOD_NS, measured_period);
end
end
prev_time_out4 = $time;
end
// --- 5.4 相位验证 ---
// 验证 clk_out1 和 clk_out2 是否反相 (180度)
always @(clk_out1 or clk_out2) begin
// 在 locked 信号有效之后才开始检查
if (locked) begin
// 使用 === 进行严格比较,可以处理 X 态
if (clk_out1 === clk_out2) begin
$error("[FAIL] Phase Error: clk_out1 and clk_out2 are IN PHASE at time %0t. They should be inverted.", $time);
end
end
end
// =========================================================================
// 6. 仿真控制
// =========================================================================
initial begin
// 等待足够长的时间让所有测试完成
#(SIM_DURATION_NS);
$display("--- Simulation Finished at %0t ns ---", $time);
$finish;
end
endmodule
仿真结果:

可以看到按照既定的需求,产生了相应的时钟。