FPGA例程(5):时钟(clock)分频倍频(PLL/MMCM)实验--vivado行为级仿真、综合后仿真和实现后仿真说明
《FPGA经典例程及解读--基于xilinx K325T平台》系列导航
本专栏主要针对与想学习FPGA的同学,从基础的点灯到之后的复杂功能实战例程,从入门到进阶,通过这些例程的学习和了解,希望可以帮助你从一个FPGA小白进阶到FPGA中级阶段,能够处理工作中大多数的FPGA使用场景。
本篇是该系列的第五篇内容
上一篇:FPGA例程(4):按键消抖实验-ZEEKLOG博客
下一篇:FPGA例程(6):UART串口通讯协议解析-ZEEKLOG博客
1 引言
很多初学者会遇到一个问题,我们硬件的输入时钟只有100MHz,但是我们内部需要使用200MHz或者50MHz这样的时钟,我们该怎么办呢?其实在FPGA内部集成了PLL或者MMCM,不同的厂商叫法可能不同,但是功能类似,通过PLL(MMCM)可以分频和倍频,产生很多其它的时钟,本实现通过调用xilinx的clock wizard的IP核来学习PLL(MMCM)的使用方法。
2 硬件环境
开发环境使用vivado2019.1

开发板使用米联客的MK7160FA

3 实验原理
PLL,即锁相环,是FPGA中的重要资源。由于一个复杂的FPGA系统往往需要多个不同频率、相位的时钟信号,所以,一个FPGA芯片中PLL的数量是衡量FPGA芯片能力的重要指标。
7系列的FPGA使用了专用的全局(Global)和区域(Regional)IO和时钟资源来管理设计中各种的时钟需求。Clock Management Tiles(CMT)提供了时钟合成(clock frequency synthesis)、倾斜矫正(deskew)、过滤抖动(jitter filtering)功能。
每个CMTs包含一个MMCM(mixed-mode clock manager)和一个PLL。如下同所示,CMT的输入可以是BUFR、IBUFG、BUFG、GT、BUFH、本地布线(不推荐使用),输出需要接到BUFG或者BUFH后再使用

3.1 混合模式时钟管理器(MMCM)
MMCM用于在与给定输入时钟有设定的相位和频率关系的情况下,生成不同的时钟信号。MMCM提供了广泛而强大的时钟管理功能,MMCM内部的功能框图如下图所示:

3.2 数字锁相环PLL
锁相环主要用于频率综合。使用一个PLL可以从一个输入时钟信号生成多个时钟信号。PLL内部的功能框图如下图所示:

4 建立工程
按照FPGA例程(1):LED流水灯实验--vivado工程创建、编译及下载bit_vivado创建例程-ZEEKLOG博客
中的步骤,创建一个名为PLL_test的工程。
点击IP Catalog-->选择Clocking Wizard
clocking Wizard的具体说明详见:FPGA基础知识(十五):Xilinx Clocking Wizard IP核完全指南--从基础到高级应用-ZEEKLOG博客

我们的输入时钟为100MHz,我们内部需要使用200MHz和50MHz的时钟


其它参数的配置详见:FPGA基础知识(十五):Xilinx Clocking Wizard IP核完全指南--从基础到高级应用-ZEEKLOG博客
我们点击OK-->Generate生成IP核

生成的IP核如下,展开可以看到IP核的TOP层,之后我们在工程中就例化的是这个TOP层。

我们新建一个PLL_test.v文件,例化module clk_wiz_0
大家注意到,程序的最后有一段产生clk_div的程序,即将100MHz进行2分频产生50MHz,故clk_div的频率与clk_50M是一样的,只是生成的方式不同,方便大家看一下这两种方式的区别。
module PLL_test( input sys_clk, //100MHz output clk_200M, output clk_50M, output clk_div ); parameter RST_DELAY = 8'd128; reg [7:0] rst_cnt; always @(posedge sys_clk) begin if(rst_cnt < RST_DELAY)begin rst_cnt <= rst_cnt + 1'd1; end else if(rst_cnt == RST_DELAY) begin rst_cnt <= rst_cnt; end else begin rst_cnt <=0; end end reg rst_n; always @(posedge sys_clk) begin if(rst_cnt == RST_DELAY) begin rst_n <= 1; end else begin rst_n <= 0; end end //---------------------------------------------------- wire sys_rstn; //wire clk_200M; //wire clk_50M; clk_wiz_0 clk_wiz_0( .clk_200M(clk_200M), .clk_50M(clk_50M), .resetn(rst_n), .locked(sys_rstn), .clk_in1(sys_clk) ); //----------------------------------------------------- reg clk_div_c; //wire clk_div; always @(posedge sys_clk or negedge rst_n ) begin if (!rst_n) begin clk_div_c <= 1'b0; end else begin clk_div_c <= ~clk_div_c; end end BUFG BUFG_inst ( .O(clk_div), // 1-bit output: Clock output .I(clk_div_c) // 1-bit input: Clock input ); endmodule按照FPGA例程(1):LED流水灯实验--vivado工程创建、编译及下载bit_vivado创建例程-ZEEKLOG博客
中的步骤,添加我们的约束文件,如下:
## ----------- clock ----------------------- create_clock -period 10.000 -name sys_clk [get_ports sys_clk] set_property PACKAGE_PIN AA3 [get_ports sys_clk] set_property IOSTANDARD LVCMOS15 [get_ports sys_clk] ## ------------ led pin ----------------------------- set_property PACKAGE_PIN G14 [get_ports {clk_200M}] set_property PACKAGE_PIN H14 [get_ports {clk_50M}] set_property PACKAGE_PIN J10 [get_ports {clk_div}] set_property IOSTANDARD LVCMOS33 [get_ports {clk_200M}] set_property IOSTANDARD LVCMOS33 [get_ports {clk_50M}] set_property IOSTANDARD LVCMOS33 [get_ports {clk_div}] ## ========================== SPI X4 ================================= set_property CFGBVS VCCO [current_design] set_property CONFIG_VOLTAGE 3.3 [current_design] set_property BITSTREAM.GENERAL.COMPRESS true [current_design] set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design] set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design] set_property BITSTREAM.CONFIG.SPI_FALL_EDGE Yes [current_design]5 程序仿真
我们点击Add Sources-->选择Add or create simulation sources添加以下仿真激励文件。

如果我们有现成的文件,选择Add Files,选择文件路径即可
这里我们没有现成的,就点击Create File--->输入仿真激励文件名称--->OK---->Finish

就会在Simulation Sources下面产生以下tb_PLL_test.v的文件,我们开始编写测试激励:
输入时钟是100MHz,对应周期为10ns,故5ns我们执行一次反转
module tb_PLL_test(); reg sys_clk; initial begin sys_clk = 0; end always begin #5 sys_clk = ~sys_clk; end wire clk_200M,clk_50M,clk_div; PLL_test DUT( .sys_clk(sys_clk), //100MHz .clk_200M(clk_200M), .clk_50M(clk_50M), .clk_div(clk_div) ); endmodule5.1 行为级仿真
点击Run Simulation ,选择Run Behavioral Simulation

选择例化的名称,可以在Objects中看到相关的信号,右键选择Add to Wave Window,就可以将信号加入观测窗口的

添加好之后,设定仿真的时间,比如10us,点击Run for 10us(三角形+T的图标)就可以进行仿真了,跑完10us会自动停下
如果不知道设置多长时间可以直接点击Run All(三角形图标),等跑一下自己手动暂停即可。

仿真结果如下:
输出的clk_200m对应的周期为5ns,即200mhz时钟周期
输出的clk_50m对应的周期为20ns,即50mhz时钟周期
输出的clk_div对应的周期亦为20ns,即50mhz时钟周期
这些时钟均与sys_clk上升沿对齐

5.2 综合时序仿真
执行完Run Synthesis之后,点击Run Simulation ,选择Run Post-Synthesis Timing Simulation

通过仿真我们可以看到综合后仿真加入了延迟,会更加接近实际中芯片的运行场景
首先输入的sys_clk与输出clk_200M和clk_50M之间存在相位偏移

clk_200m与clk_50m之间也存在相位偏移,但是偏移非常小

输入的sys_clk与输出的clk_div存在相位偏移,且相位偏移要大于其与clk_50M之间的偏移

5.3 布局布线后时序仿真
完成Run Implementation之后,点击Run Simulation ,选择Run Post-Implementation Timing Simulation

通过仿真可以看出,通过布局布线之后加入了延迟,比综合后的仿真更接近于真实的情况,时序之间的相位延迟也更大一些。


6 总结
一般来说,程序的功能是否正常,我们可以先查看行为级仿真,当程序出现一些未预料的bug时,综合后仿真和实现后仿真能很好的帮助我们定位bug,尤其是一些因为不当的设计被优化掉的信号,从综合后仿真和实现后仿真能明显的看出来。