正点原子 发表于 2021-1-27 10:56:29

【正点原子FPGA连载】第十六章IP核之PLL实验

1)实验平台:正点原子超越者FPGA开发板
2)章节摘自【正点原子】超越者之FPGA开发指南
3)购买链接:https://item.taobao.com/item.htm?&id=631660290421
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-chaoyuezhe.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流群:905624739





第十六章IP核之PLL实验

Spartan-6 FPGA芯片提供了非常丰富的时钟资源,它由四类时钟资源互连线,两种时钟资源网络组成。这四类连接线分别是全局时钟输入(GCLK)、全局时钟多路复用器(BUFG、BUFPLL)、I/O时钟缓冲器(BUFIO2、BUFIO2_2CLK、BUFPLL)、水平时钟路由缓冲器(BUFH);两种时钟网络分别是全局时钟网络和I/O区域时钟网络。这些互联线具有高速、低抖动的特点,非常适合于传输高频信号、最大量减小时钟抖动并且可以和DCM、PLL等实现连接。DCM和PLL是我们最常用的时钟资源管理器,通过对他们的调用可以实现对时钟的分频、倍频功能、消抖、抗扭斜等功能,得到相对平稳的高质量时钟信号。
本章我们将详细的讲解如何使用Xilinx Spartan-6的PLL IP核。本章包括以下几个部分:
1616.1简介
16.2实验任务
16.3硬件设计
16.4程序设计
16.5下载验证


16.1简介
在Spartan-6中时钟资源模块称之为CMT,它是由两个DCM和一个PLL组成的。
DCM即数字时钟管理器(DCMs),它为Spartan-6提供了先进的时钟功能。并且DCM是将时钟功能直接集成到全局时钟网络中去的。因此在高性能、高频等应用中,DCM解决了以下常见的时钟问题:
1. 消除设备内部或外部组件的时钟偏差,以改善整体系统性能并消除时钟分配延迟;
2. 通过改变时钟周期的固定分量或增量对时钟信号进行相移;
3. 对输入时钟进行分频或倍频来产生全新的时钟,全新的时钟频率是基于输入时钟频率的静态或动态参数的乘法和除法混合而成;
4. 确保输出时钟标准和稳定,可以调整占空比(常用50%)、镜像、转发或重新缓冲时钟信号等等,可以将输入时钟去歪斜并转换为其他I / O标准。
例如,使用转发和转换功能,可以将LVTTL时钟输入到LVDS;时钟输入抖动滤波;自由运行振荡器;扩频时钟产生等等功能。
PLL即锁相环,它的主要用途是作为广泛频率范围的频率合成器,以及作为与DCMs结合的外部或内部时钟的抖动滤波器。如下图所示:

图 16.1.1 PLL结构图
从上图中我们可以看到:首先是参考时钟和反馈时钟输入(Clock Pin通常反馈时钟都是使用PLL或者DCM自己产生,进行自反馈),这两个时钟可以来自IBUFG、 BUFG、 IBUF、PLL或者DCM,进来的时钟都会有一个可编程计数器D。然后时钟经过相位频率检测器(PFD),这一步主要用来比较参考时钟和反馈时钟的频率和相位,生成一个比例信号。该信号驱动电荷泵(CP)和环路滤波器(LF)产生一个电压控制振荡器(VCO)的参考电压。PFD产生上升或下降信号到电荷泵和环路滤波器以确定VCO是否应以更高或更低的频率运行。当VCO的频率过高时,PFD激活一个下降信号,导致控制电压降低,降低VCO工作频率,当VCO的频率太低时,信号会增加电压。 VCO产生八个输出相位,每个输出阶段可以选择作为输出计数器的参考时钟。如下图所示:

图 16.1.2 PLL工作原理图1

图 16.1.3 PLL工作原理图
在开发设计过程中会经常用到PLL,甚至可以说只要是稍微复杂点的工程都一定有它。PLL的主要用途是用于时钟网络去歪斜、当作合成器、抖动滤波器和产生一个零延迟缓冲时钟。这里需要注意的是当你想产生一个零延迟输出时钟时就需要用到ODDR2(ODDR2是IO资源,可以去参考官方文档UG831)如下图所示:

图 16.1.4 产生单端零延迟输出时钟
上图是产生单端时钟信号,如果想产生差分时钟信号则如下图所示:

图 16.1.5产生差分零延迟缓冲时钟
可能刚看到这两张图有点懵,其实仔细看上文对PLL的描述就会发现这两张图其实不难理解,我们先分析一下单端时钟信号怎么产生的如图 16.1.4所示:首先参考时钟(CLKIN)和反馈时钟(CLKFB_IN)进入PLL(在进入PLL之前参考时钟先进入IBUFG缓冲,反馈时钟进入IBUF缓冲),到达PLL后经过处理(处理主要指分频、倍频、消抖、滤波等),处理完成后输出(CLKFBOUT),这个时候我们想零延迟通过IO输出,就要调用IO的ODDR资源。CLKFBOUT输出的信号先进入BUFG,之后进入ODDR2,再由OBUF输出,这样我们在FPGA对应信号引脚上就可以用示波器测到时钟信号了。差分信号其实是类似的,唯一的区别就是单端时钟的OBUF缓冲器变成了差分时钟的OBUFDS缓冲器,其他的过程都一样。
看到这读者可能就会问了,那我怎们调用这些资源呢?比如外部时钟进来后我们怎么把它接入IBUFG,又怎么让它进入PLL,然后再怎么把处理好的时钟信号放入ODDR2呢?其实最简单的就是用原语,用原语定义(差分的原语和单端不同,本章节只讲解单端,差分的原语请参考官方文档UG832)IBUFG、ODDR2,但是PLL就不用原语了,我们会创建一个IP核,然后例化IP核。最后的OBUF是不需要定义的,IO资源已经自动帮我们接上了。那原语长什么样呢?不着急,软件设计章节会跟大家讲解,我们继续往下看。
在上文提到过PLL是可以与DCMs相结合的,那他们是如何结合的呢?Xilinx提供了三种使用方案:
第一种:DCM Driving PLL      

图 16.1.6 DCM Driving PLL
从上图我们可以看到参考时钟首先是进入DCM然后由CLK90输出用来作为PLL的参考时钟,这种方式就叫做DCM驱动PLL模式。
第二种:PLL Driving DCM

图 16.1.7 PLL Driving DCM
从上图我们可以看到参考时钟首先是进入PLL然后由CLKOUT0输出用来作为DCM的参考时钟,这种方式就叫做PLL驱动DCM模式。
第三种:PLL to PLL Connection

图 16.1.8 PLL to PLL Connection
从上图中我们可以看到是两个PLL级联,参考时钟进入第一个PLL在通过BUFG路由到第二个PLL,这里需要注意的是两个PLL级联的时钟信号范围是共用的,不能无限倍频也不能无限分频。
这里或许有人要问了,为什么要提供这三种方案呢,其实和DCM与PLL本身的特点有关,其中DCM主要的特点是相位调整,PLL的特点是消抖去扭斜。这就造成了为什么出现这样三种组合,比如说我需要的时钟对相位没什么太大的要求,我们就选第一种方案,如果对相位有要求就选第二种方案,还有的可能对输出时钟的抖动要求非常高,那就选第三种,最大限度的消除抖动和扭斜。所以根据不同的设计需求我们选择不同的设计方案。
16.2实验任务
本章的实验任务是通过调用PLL IP核,对输入时钟(晶振时钟)进行分频、倍频并输出出去。
16.3硬件设计
本章实验将PLL IP核产生的4个时钟100MHz、100MHz_180deg 、50MHz和25MHz,连接到开发板的J2扩展口IO上,分别是第B0_L37_N、B0_L37_P、B0_L48_P和B0_L50_P号引脚,扩展口原理图如下图所示:

图 16.3.1 扩展口引脚图
本实验中,各端口信号的管脚分配如下表所示:
表 16.3.1 IP核之PLL实验管脚分配

对应的引脚约束语句如下:
NET sys_clk                     TNM_NET = sys_clk_pin;
TIMESPEC TS_sys_clk_pin = PERIOD sys_clk_pin 50000 kHz;

NET sys_clk                     LOC = N8| IOSTANDARD = "LVCMOS33";
NET sys_rst_n                     LOC = G16 | IOSTANDARD = "LVCMOS33";

NET CLK_OUT1                      LOC = H16 | IOSTANDARD = "LVCMOS33";
NET CLK_OUT2                      LOC = H15 | IOSTANDARD = "LVCMOS33";
NET CLK_OUT3                      LOC = P15 | IOSTANDARD = "LVCMOS33";
NET CLK_OUT4                      LOC = R14 | IOSTANDARD = "LVCMOS33";
16.4程序设计
我们先创建好一个空的工程,工程命名为ip_pll,然后我们在这个空的的工程中添加PLL IP核,如下图所示:

图 16.4.1 添加IP核
我们将添加的IP核命名为pll然后设置好路径,点击“Next>”进入IP核选择界面,如下图所示:

图 16.4.2 创建PLL IP核
直接在Search IP Catalog搜索栏中输入Clocking就可以直接找到PLL IP核,选中IP核点“Next>”进入Summary界面,如下图所示:

图 16.4.3 Summary界面
在Summary界面我们可以看到路径信息等,如果没有问题就直接点击“Finish”进入IP核参数配置界面如下图所示:

图 16.4.4 IP核参数配置
在参数配置第一页我们主要是设置输入时钟频率(参考时钟),我们这里采用晶振时钟作为输入时钟,所以设置为50MHZ,后面的Source框就是设置我们的Buffer缓冲,这里选择No buffer,因为在上文已经跟大家说了我们要用原语来调用IBUFG,所以这边就没必要再设置了。之后我们点击“Next>”进入参数配置第二页,如下图所示:


图 16.4.5 IP核参数配置
参数配置第二页主要是配置输出时钟的参数,我们需要输出四路时钟(一个PLL最多输出六路)所以在第一个方框中再勾选三路时钟输出,第二个方框是设置时钟频率,第三个方框是设置相位差,这里我们把CLK_OUT2设置180度相位差,第四个方框是设置占空比(我们默认50%占空比),第五个方框是BUFG设置我们保持默认(在上文中我提到过PLL处理过时钟信号后在进入ODDR2之前会有个BUFG如图 16.1.4所示,这里就是把这个BUFG勾选上)。设置好输出时钟参数后我们点击“Next>”进入参数配置第三页,如下图所示:

图 16.4.6 IP核参数配置
这一页主要是设置复位引脚(RESET)和时钟锁存信号(LOCKED),其中时钟锁存信号的作用是指示PLL输出的时钟信号是否稳定,当LOCKED拉高代表PLL输出时钟稳定,反之则表示输出不稳定或者错误。设置好后点击“Next>”进入参数配置第四页,如下图所示:

图 16.4.7 IP核参数配置
参数配置第四页不需要作任何修改保持默认即可,直接点击“Next>”进入参数配置第五页,如下图所示:

图 16.4.8 IP核参数配置
参数配置第五页,主要是对IP引脚名称的修改,这里可以不作修改保持默认名称即可,点击“Next>”进入参数配置第六页,如下图所示:


图 16.4.9 IP核参数配置
参数配置第六页是Summary界面,显示将要生成哪些文件,直接点击“Generate”生成PLL IP核。生成成功后会在工程中显示我们刚刚创建的PLL IP核,如下图所示:

图 16.4.10 PLL IP核创建成功
现在我们就可以创建一个设计输入源文件来例化刚刚创建的PLL IP核了,先创建一个空白源文件,如下图所示:

图 16.4.11创建源文件
如上图所示将创建的源文件命名为ip_pll,路径放在工程的rtl文件夹下,然后一路点击“Next>”,这样空白源文件就创建好了,如下图所示:

图 16.4.12 空白源文件
如上图所示,因为我的ISE和Notepad++建立了关联,所以创建好源文件后会,自动进入Notepad++界面(如何关联请参考软件篇),现在我们来例化PLL IP核,如下图所示:



图 16.4.13 生成例化模板
如上图所示,先选中IP核,然后双击View HDL Instantiation Template,ISE会自动帮你生成例化模板,如下图所示:

图 16.4.14 例化模板
如上图所示将红色方框中的例化模板复制到源文件中去,如下图所示:

图 16.4.15 例化PLL
如上图所示:将模板中的instance_name改成你想要的名字(建议改成u_+模块名),接下来我们就来调用原语把上文中提到的IBUFG和ODDR2调用上,下面是完整的代码:
1   module ip_pll(
2       inputsys_clk   ,
3       inputsys_rst_n ,
4      
5       output CLK_OUT1,
6       output CLK_OUT2,
7       output CLK_OUT3,
8       output CLK_OUT4,
9       output LOCKED
10      
11      );
12
13wiresys_clk_pll;
14
15wireCLK_OUT1_pll ;
16wireCLK_OUT2_pll ;
17wireCLK_OUT3_pll ;
18wireCLK_OUT4_pll ;
19
20pll u_pll
21   (// Clock in ports
22      .CLK_IN1(sys_clk_pll),      // IN
23      // Clock out ports
24      .CLK_OUT1(CLK_OUT1_pll),   // OUT
25      .CLK_OUT2(CLK_OUT2_pll),   // OUT
26      .CLK_OUT3(CLK_OUT3_pll),   // OUT
27      .CLK_OUT4(CLK_OUT4_pll),   // OUT
28      // Status and control signals
29      .RESET(~sys_rst_n),// IN
30      .LOCKED(LOCKED));      // OUT   
31      
32      ODDR2 #(
33      .DDR_ALIGNMENT("NONE"), // Sets output alignment to "NONE", "C0" or "C1"
34      .INIT(1'b0),    // Sets initial state of the Q output to 1'b0 or 1'b1
35      .SRTYPE("SYNC") // Specifies "SYNC" or "ASYNC" set/reset
36   ) ODDR2_inst_1 (
37      .Q(CLK_OUT1),   // 1-bit DDR output data
38      .C0(CLK_OUT1_pll),   // 1-bit clock input
39      .C1(~CLK_OUT1_pll),   // 1-bit clock input
40      .CE(1'b1), // 1-bit clock enable input
41      .D0(1'b1), // 1-bit data input (associated with C0)
42      .D1(1'b0), // 1-bit data input (associated with C1)
43      .R(1'b0),   // 1-bit reset input
44      .S(1'b0)    // 1-bit set input
45   );   
46   
47    ODDR2 #(
48      .DDR_ALIGNMENT("NONE"), // Sets output alignment to "NONE", "C0" or "C1"
49      .INIT(1'b0),    // Sets initial state of the Q output to 1'b0 or 1'b1
50      .SRTYPE("SYNC") // Specifies "SYNC" or "ASYNC" set/reset
51   ) ODDR2_inst_2 (
52      .Q(CLK_OUT2),   // 1-bit DDR output data
53      .C0(CLK_OUT2_pll),   // 1-bit clock input
54      .C1(~CLK_OUT2_pll),   // 1-bit clock input
55      .CE(1'b1), // 1-bit clock enable input
56      .D0(1'b1), // 1-bit data input (associated with C0)
57      .D1(1'b0), // 1-bit data input (associated with C1)
58      .R(1'b0),   // 1-bit reset input
59      .S(1'b0)    // 1-bit set input
60   );   
61
62ODDR2 #(
63      .DDR_ALIGNMENT("NONE"), // Sets output alignment to "NONE", "C0" or "C1"
64      .INIT(1'b0),    // Sets initial state of the Q output to 1'b0 or 1'b1
65      .SRTYPE("SYNC") // Specifies "SYNC" or "ASYNC" set/reset
66   ) ODDR2_inst_3 (
67      .Q(CLK_OUT3),   // 1-bit DDR output data
68      .C0(CLK_OUT3_pll),   // 1-bit clock input
69      .C1(~CLK_OUT3_pll),   // 1-bit clock input
70      .CE(1'b1), // 1-bit clock enable input
71      .D0(1'b1), // 1-bit data input (associated with C0)
72      .D1(1'b0), // 1-bit data input (associated with C1)
73      .R(1'b0),   // 1-bit reset input
74      .S(1'b0)    // 1-bit set input
75   );   
76
77ODDR2 #(
78      .DDR_ALIGNMENT("NONE"), // Sets output alignment to "NONE", "C0" or "C1"
79      .INIT(1'b0),    // Sets initial state of the Q output to 1'b0 or 1'b1
80      .SRTYPE("SYNC") // Specifies "SYNC" or "ASYNC" set/reset
81   ) ODDR2_inst_4 (
82      .Q(CLK_OUT4),   // 1-bit DDR output data
83      .C0(CLK_OUT4_pll),   // 1-bit clock input
84      .C1(~CLK_OUT4_pll),   // 1-bit clock input
85      .CE(1'b1), // 1-bit clock enable input
86      .D0(1'b1), // 1-bit data input (associated with C0)
87      .D1(1'b0), // 1-bit data input (associated with C1)
88      .R(1'b0),   // 1-bit reset input
89      .S(1'b0)    // 1-bit set input
90   );   
91   
92    IBUFG #(
93      .IOSTANDARD("DEFAULT")
94   ) IBUFG_inst (
95      .O(sys_clk_pll), // Clock buffer output
96      .I(sys_clk)// Clock buffer input (connect directly to top-level port)
97   );
98   
99   endmodule
上面的代码可以看到除了例化一个PLL IP核之外我调用了一个IBUFG原语,四个ODDR2原语分别对应图 16.1.4中的IBUFG和ODDR2,至于原语为什么是这么使用的请参考官方文档UG381,这篇文档有详细的介绍,我在这里就不在花费大量篇幅介绍了。
代码写好并且综合通过后我们就可以使用Modelsim对代码进行仿真了。首先我们来编写一个仿真文件(Testbench文件),如下所示:
1 `timescale 1ns / 1ps
2
3 module pll_tb;
4
5   // Inputs
6   reg sys_clk;
7   reg sys_rst_n;
8
9   // Outputs
10wire CLK_OUT1;
11wire CLK_OUT2;
12wire CLK_OUT3;
13wire CLK_OUT4;
14    wire LOCKED;
15// Instantiate the Unit Under Test (UUT)
16ip_pll uut (
17      .sys_clk(sys_clk),
18      .sys_rst_n(sys_rst_n),
19      .CLK_OUT1(CLK_OUT1),
20      .CLK_OUT2(CLK_OUT2),
21      .CLK_OUT3(CLK_OUT3),
22      .CLK_OUT4(CLK_OUT4),
23      .LOCKED(LOCKED)
24);
25
26initial begin
27      // Initialize Inputs
28      sys_clk = 0;
29      sys_rst_n = 0;
30
31      // Wait 100 ns for global reset to finish
32      #100;
33      sys_rst_n = 1;
34      // Add stimulus here
35
36end
37   always #10 sys_clk=~sys_clk;
38
39   
40 endmodule
写好仿真文件我们回到ISE点击联合仿真按钮,如下图所示:

图 16.4.16 使用Modelsim仿真
仿真波形如下图所示:

图 16.4.17 仿真波形
上图的波形中我们的参考时钟是sys_clk,它是50MHZ的标准时钟,我们以它为参考,可以看到CLK_OUT1是100MHZ时钟,CLK_OUT2跟CLK_OUT1相差180度相位,CLK_OUT3是50MHZ时钟,CLK_OUT4是25MHZ时钟,跟我们预期的目标是完全一致的,说明我们仿真时完全没有问题的。
仿真没问题后就可以添加引脚约束文件了,新建一个时序约束文件(UCF文件),如下图所示:

图 16.4.18 新建引脚约束文件
如上图所示给引脚约束文件命名为pll,并将路径放在prj文件夹下,然后一路点击“Next>”就可以创建一个新的UCF约束文件(不会创建UCF文件的请参考软件篇),在这个空白文件中输入约束语句,如下图所示:

图 16.4.19 引脚约束
写好约束语句后点击保存就可以生成bit流文件进行下板验证了。
16.5下载验证
首先我们将下载器与超越者开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后连接开发板的电源,并打开电源开关。
回到ISE界面,我们将生成好的bit流文件下载到开发板中去,然后用示波器去抓取一路信号看看是否符合预期目标,我们将示波器探针接到R14引脚,示波器测量到的波形如下图所示:

图 16.5.1 25MHZ测量波形
可以看到示波器显示抓取到的信号为25MHZ,再测另外三路信号,发现都是符合预期的,这就说明本次PLL IP核实验成功了。
页: [1]
查看完整版本: 【正点原子FPGA连载】第十六章IP核之PLL实验