学习笔记——基于PLL分频计数的LED闪烁实例

一、PLL概述

  • PLL(Phase Locked Loop):为锁相回路或锁相环,用来统一整合时脉讯号,使内存能正确的存取资料。PLL用于振荡器中的反馈技术。许多电子设备要正常工作,通常需要外部的输入信号与内部的振荡信号同步,利用锁相环路就可以实现这个目的。
  • 时钟就是 FPGA 运行的心脏,它的每次跳动必须精准而毫无偏差(当然现实世界中不存 在所谓的毫无偏差,但是我们希望它的偏差越小越好)。一个 FPGA 工程中,不同的外设通常工作在不同的时钟频率下,所以一个时钟肯定满足不了需求;此外,有时候可能两个不同的模块共用一个时钟频率,但是由于他们运行在不同的工作环境和时序下,所以他们常常是同频不同相(相位),FPGA 里面定义的 PLL,不只有一个反馈调整功能,它还有倍频和分频等功能集成其中。可以说PLL 算是一个 FPGA 内部的时钟管理模块了。我之前也对PLL有点了解,也总结过博客,大家感兴趣可以参考https://blog.csdn.net/quanqueen/article/details/109693219在这个博客里面,我可能只是将PLL简单的理解成了用于振荡器的反馈技术,那么这次就让我们来看看PLL的更大的功能
  • PLL一个最主要的功能就是能够对输入的基准时钟信号进行一定范围内的分频或者倍频,从而产生多个输出时钟信号供芯片内部的各个功能模块使用。下图就是PLL的内部功能框图。
    在这里插入图片描述

二、功能介绍

  • FPGA内部的PLL是本次的重点,输出FPGA引脚上的25MHZ时钟,配置PLL使其输出4路分别为12.5MHZ、25MHZ、50MHZ和100MHZ的时钟信号,这4路时钟信号又分别驱动4个不同位宽的计数器不停地计数工作,这些计数器的最高位最终输出用于控制4个不同的灯亮灭。简单的来说就是,四个灯用四个不同的定时器控制,由于4哥时钟频率都有一定的倍数关系,所以也很容易通过调整合理的计数器位宽,达到4个LED闪烁一致的控制。简单的基于PLL分频计数的LED闪烁功能框图如下

在这里插入图片描述

三、新建IP核文件

  • 要想完成上述功能,那么我们首先想要学会如何配置一个PLL硬核IP,并将其集成到工程中。首先我们在工具栏,点击Tools选择MegaWizard
    Plug-In Manager

在这里插入图片描述

  • 选择“Creat a new custom megafunction variation”,然后点击 Next。
    在这里插入图片描述
    然后选择所需的IP核并进行一系列的设置
    ● 在“Select a megafunction from the list below”下面选择 IP 核为“I/O → ALTPLL”。
    ● 在“What device family will you be using”后面的下拉栏中选择我们所使用的器件系
    列为“Cyclone IV E”。
    ● 在“What type of output file do you want to create?”下面选择语言为“Verilog”。
    ● 在“What name do you want for the output file?”下面输入工程所在的路径,并且在
    最后面加上一个名称,这个名称是我们现在正在例化的 PLL 模块的名称,我们可以
    给他起名叫 pll_controller,然后点击 Next 进入下一个页面。这里它所在的路径,实
    际上是我们在工程文件夹 cy4ex8 下面创建的 ip_core 文件夹和其下的 pll 文件夹。
    在这里插入图片描述

四、PLL配置

上面的设置完成之后,点击NEXT就到了 PLL 的参数配置页面

● 在“What device speed grade will you be using?”后面选择“8”,即我们使用的器件
的速度等级。
● 在“What is the frequency of the inclk0 input?”后面选择“25MHz”,即我们输入到
该 PLL 的基准时钟频率。
在这里插入图片描述
Input/lock 页面中,如图 8.22 所示进行设置,接着点击 Next 进入下一个页面。
● 勾选“Create an ‘areset’ input to asynchronously reset the PLL”,即引出该 PLL 硬核的’areset’信号,这是该 PLL 硬核的异步复位信号,高电平有效。
● 勾选“Create ‘locked’ output”,即引出该 PLL 硬核的’locked’信号,该信号用于指示PLL 是否完成内部初始化,已经可以正常输出了高电平有效。
在这里插入图片描述
Bandwidth/SS、Clock Switchover 和 PLL Reconfiguration 页面不用设置,默认即可。直接进入 Ouput Clocks 页面,如图 8.23 所示,这里有 5 个可选的时钟输出通道,通过勾选对应
通道下方的 Use this clock 选项开启对应的时钟输出通道。可以在配置页面中设置输出时钟的
频率、相位和占空比。这里是 C0 通道的设置。
● 勾选“Use this clock”,表示使用该时钟输出信号。
● 输入“Enter output clock frequency”为“12.5MHz”,表示该通道输出的时钟频率为12.5MHz。
● 输入“Clock phase shift”为“0 deg”,表示该通道输出的时钟相位为0deg。
● 输入“Clock duty cycle(%)”为“50.00%”,表示该通道输出的时钟占空比为 50%。
在这里插入图片描述

和 C0 的配置一样,我们可以分别开启并且配置 C1、C2、C3,这些时钟虽然这个例程暂时用不上,但是后续的例程将会使用到。
● C1 的时钟频率为 25MHz,相位为 0deg,占空比为 50%。
在这里插入图片描述

● C2 的时钟频率为 50MHz,相位为 0deg,占空比为 50%。
在这里插入图片描述

● C3 的时钟频率为 100MHz,相位为 0deg,占空比为 50%。
在这里插入图片描述

  • 配置完成后,最后在 Summary 页面勾选上*_inst.v 文件,这是一个PLL
    例化的模板文件,一会我们可以在工程目录下找到这个文件,然后打开它,将它的代码复制到工程中,修改对应接口即可完成这个 IP 核的集成。

在这里插入图片描述

  • 点击 Finish 完成 PLL 的配置。工程中若弹出如图 8.25 所示的对话框,勾“Automatically add Quartus
    II IP Files to all projects”选项后,点击 Yes

在这里插入图片描述

  • 此时,我们可以来到 pll 文件夹下,打开 pll_controller_inst.v 文件,它是这个 PLL IP 核的例化模板。

在这里插入图片描述

  • 复制文件中的内容,将()内的信号名改为我们连接顶层 模块A 模块B 模块C 子模块A1 子模块A2 子模块C1 子模块C2 ………到这个模块的接口信号名就可以了。

在这里插入图片描述

五、模块化设计

1、概述

  • 模块化设计也是FPGA的一个重要的技巧,我记得在我们之前学习单片机的时候也学习过模块化编程,那么这两个之间有什么区别或者是相同之处呢,下面我们来了解一下,FPGA模块化设计。

  • 模块化设计可以使一个大型设计的分工协作、仿真测试更加容易,代码维护或升级更加便利。

  • 一般整个设计的顶层只做例化,不做逻辑。然后一个顶层下面会有模块 A、模块 B、模块 C 等等,模块 A/B/C 下又可以分多个子模块实现。如下模块层次设计图
    在这里插入图片描述

  • 这样,就可以将大规模复杂系统按照一定规则划分成若干模块,然后对每个模块进行设计输入、综合,并将实现结果约束在预先设置好的区域内,最后将所有模块的实现结果有机地组织起来,就能完成整个系统的设计。

  • 对于顶层模块的设计,首先需要完成顶层模块的设计输入与综合,为进行模块化设计实现阶段的第一步即初始预算阶段(Initial Budgeting Phase)做准备。

  • 对于子模块的设计,多个模块的设计者相对独立地并行完成各自子模块的设计输入与综合,为进行模块化设计实现阶段的第二步即子模块的激活模式实现(Active Module Implementation)做准备。

  • 模块化设计的实现步骤是整个模块化设计流程中最重要、最特殊的,它包含:
    (1)初始预算,本阶段是实现步骤的第一步,对整个模块化设计起着指导性的作用。在初始预算阶段,项目管理者需要为设计的整体进行位置布局,只有布局合理,才能够在最大程度上体现模块化设计的优势;反之,如果因布局不合理而在较后的阶段需要再次进行初始预算,则需要对整个实现步骤全面返工。
    (2)子模块的激活模式实现(Active Module Implementation),在该阶段,每个项目成员并行完成各自子模块的实现。
    (3) 模块的最后合并(Final Assembly),在该阶段项目管理者将顶层的实现结果和所有子模块的激活模式实现结果有机地组织起来,完成整个设计的实现步骤。

  • 模块划分的基本原则是,子模块功能相对独立,模块内部联系尽量紧密,而模块间的连接尽量简单。对于那些难以满足模块划分准则的具有强内部关联的复杂设计,并不适合采用模块化设计方法。

  • 上面的术语其实有些生硬,比如说激活模式、初始预算等我们都是没有一个准确的观念的,不过没关系,我们觉得我们只要明白这个模块化设计的好处以及用法然后通过之后的实例来慢慢了解就好啦。

2、实践

  • 实践是检验真理的唯一标准,那么我们就来实践一下模块化设计。如图所示,在这个设计中,我们的顶层模块是cy4.v相当于上图中的顶层,在之前没有模块化设计的实例中,我们的源代码文件只有一个cy4,也是就说,我们所需要的所有代码都写在这一个文件里面。当我们使用模块化设计之后,我们将会把各个不同的独立的功能逻辑代码分别写在不同的源文件中,然后通过“例化”的方式,将它们之间的接口互联起来。如此一来,cy4.v 文件里面其实几乎是没有具体的逻辑功能的,它只是做一些 基本的例化和互联,将它下面的 5个功能模块相关的接口信号都连接起来。如图 所示,它们还具体层级的关系,一目了然,非常易于查看和编辑管理。在这里插入图片描述

3、例化

  • 上面说到我们的顶层可以通过”例化“的方式,将各模块之间的接口互联起来。那么问题来了,”例化“是什么?为什么“例化”?这个模块化设计和单片机里的模块化编程其实是差不多的意思,我们进行模块化编程的时候,主函数里面一般不进行什么多余的操作,只是将各个操作连接起来,比如我的延时模块,我们在主函数里面使用的时候我们只需要写其名字即可,不需要准确的知道延时函数内部的代码操作等一系列内容,那么”例化“也如此。
  • 例化在FPGA中就是模块调用。对于一个FPGA工程,通常是由一个顶层模块与多个功能子模块组成,为了实现顶层模块与子模块的连接,需要进行模块间的例化(或说是调用)。
  • 在一个FPGA项目工程中,其输入、输出端口命名通常在设计前期就已确定下来,但会存在一些中间变量,一个工程可能会让不同的工程师在不同的时间段内共同完成,不同的人对于这些变量的命名会有所不同,故例化很有必要。

六、代码解析

  • 本次实例我们是使用模块化设计,那么我们代码解析的时候也就从各个模块的作用以及各个模块中的代码的解释说起。

1、cy4.v模块代码解析

上面我们指出了cy4.v是我们的顶层模块,他负责接口定义和模块例化,一般不会在这个模块中做任何的具体逻辑设计。
(1)接口部分
这个实例的接口只有时钟、复位、8个LDE信号,代码如下所示,与之前的一样,我们只需将输入输出罗列好即可。

module cy4( input ext_clk_25m, //外部输入 25MHz 时钟信号
input ext_rst_n, //外部输入复位信号,低电平有效
output[7:0] led //8 个 LED 指示灯接口
);

(2)声明信号类型

  • 这里需要声明5 个 wire 类型的信号,所有在不同模块间接口的信号,在它们的上级模块中都必须定义为 wire 类型,这里有 4个不同频率的时钟以及由 PLL 的 lock 信号引出的复位信号 sys_rst_n。
wire clk_12m5; //PLL 输出 12.5MHz 时钟
wire clk_25m; //PLL 输出 25MHz 时钟
wire clk_50m; //PLL 输出 50MHz 时钟
wire clk_100m; //PLL 输出 100MHz 时钟
wire sys_rst_n; //PLL 输出的 locked 信号,作为 FPGA 内部的复位信号,低电平复位,高
电平正常工作

(3)PLL例化

  • PLL 是我们配置的 IP 核模块,它需要在我们的代码中例化,如下所示:
//PLL 例化
pll_controller pll_controller_inst (
	.areset ( !ext_rst_n ),
	.inclk0 ( ext_clk_25m ),
	.c0 ( clk_12m5 ),
	.c1 ( clk_25m ),
	.c2 ( clk_50m ),
	.c3 ( clk_100m ),
	.locked ( sys_rst_n )
	);

(4)闪烁模块的例化

  • 最后 4 个 LED 闪烁控制模块的例化,它们的源码都是 led_controller.v 模块,但它们 的名称不一样,分别为 uut_led_controller_clk12m5、uut_led_controller_clk25m、 uut_led_controller_clk50m、uut_led_controller_clk100m。这样的定义方式最终实现效果不同于软件的函数调用,软件的函数调用只有一个函数,分时复用;而 FPGA 的这种代码例化却会实现 4 个完全一样的硬件逻辑。当然了,这4个模块还略有不同,就是两个名称中间的“#(n)”,n 有 23、24、25 和 26,这个是输入到 led_controller.v模块的一个参数,之后对这个参数有所解释。
//12.5MHz 时钟进行分频闪烁,计数器为 23 位
led_controller #(23) uut_led_controller_clk12m5(
	.clk(clk_12m5), //时钟信号
	.rst_n(sys_rst_n), //复位信号,低电平有效
	.sled(led[0]) //LED 指示灯接口
	);
//-------------------------------------
//25MHz 时钟进行分频闪烁,计数器为 24 位
led_controller #(24) uut_led_controller_clk25m(
	.clk(clk_25m), //时钟信号
	.rst_n(sys_rst_n), //复位信号,低电平有效
	.sled(led[1]) //LED 指示灯接口
	);	
//-------------------------------------
//25MHz 时钟进行分频闪烁,计数器为 25 位
led_controller #(25) uut_led_controller_clk50m(
	.clk(clk_50m), //时钟信号
	.rst_n(sys_rst_n), //复位信号,低电平有效
	.sled(led[2]) //LED 指示灯接口
	);
//-------------------------------------
//25MHz 时钟进行分频闪烁,计数器为 26 位
led_controller #(26) uut_led_controller_clk100m(
	.clk(clk_100m), //时钟信号
	.rst_n(sys_rst_n), //复位信号,低电平有效
	.sled(led[3]) //LED 指示灯接口
	);
//-------------------------------------
//高 4 位 LED 指示灯关闭
assign led[7:4] = 4'b1111;
endmodule

2、led_controller.v 模块代码解析

  • led_controller.v 模块代码如下,这里重点注意我们上面刚刚提到的输入参数。在代码中,有“parameter CNT_HIGH = 24;”这样的定义,若是例化这个模块的上层接口中不定 义“#(n)”,则表示“parameter CNT_HIGH= 24;”语句生效,若是定义的“#(n)”中的 n 值与代码中定义的 24不同,那么以n为最终值。
module led_controller(
	input clk, //时钟信号
	input rst_n, //复位信号,低电平有效
	output sled //LED 指示灯接口
	);
parameter CNT_HIGH = 24; //计数器最高位
//-------------------------------------
reg[(CNT_HIGH-1):0] cnt; //24 位计数器
//cnt 计数器进行循环计数
always @ (posedge clk or negedge rst_n)
	if(!rst_n) cnt <= 0;
	else cnt <= cnt+1'b1;
assign sled = cnt[CNT_HIGH-1];
endmodule

七、效果展示

这种闪烁的效果用图片是呈现不出来的,不过最后的结果就是D2、D3、D4、D5这4个LED完全同步地进行闪烁,这就证明了PLL输出的4个时钟相互之间所呈现的倍频的关系。

在这里插入图片描述

参考链接

https://blog.csdn.net/weixin_42470069/article/details/107421790?utm_term=%E6%A8%A1%E5%9D%97%E4%BE%8B%E5%8C%96%E6%98%AF%E4%BB%80%E4%B9%88%E6%84%8F%E6%80%9D&utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2allsobaiduweb~default-0-107421790&spm=3001.4430

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热爱生活的fuyao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值