【代码】【Xilinx + Vivado + 数字时钟(时分秒) + LED指示】实践项目
最终效果(没图)
- 每1秒
sec
计数累加1次,59次之后清零; - 每1分钟
min
计数累加1次,59次之后清零; - 每1小时
hour
计数累加1次,23次之后清零; - LED1交替翻转,1秒翻转1次,所以1亮1灭是2秒;
- LED2交替翻转,1分钟翻转1次;
相关截图
过程记录
首先要申明一点,目前入门FPGA的阶段属于非常非常,所有工程代码、工程逻辑都可能存在错误,希望各位同学老师能够指出,同时也参考了大量的互联网上优质内容,在这里一并感谢,基于这个原因也决定将核心逻辑进行共享。请勿做一切商业用途。
ok,let’s go
首先是搞清楚思路,我们设计一个时钟,是在我们这个现实世界里边的时钟,滴答滴答1秒就是1秒,而不是FPGA规定的1秒。如何将FPGA里边的1秒,对应上现实世界滴答滴答1秒,关键在于配合FPGA的时钟和计数值二者。假如我们现在FPGA的时钟是50MHz(6次方),那么FPGA计数一次就是20ns
,我们需要计算50_000_000
次,才能够达到现实世界滴答滴答1秒的效果。注意,到这里两个1秒就同步上了,因为现实世界、FPGA都消耗了1秒,不管你是从哪一帧开始的,从这一帧开始匹配上之后,往后的每个1秒(只要FPGA不要断电 and 世界不要毁灭)理论上都能一直匹配上,这就是数字时钟能够显示现实世界时间的原理。当然FPGA并不知道当前北京时间是多少(可以加上时间校正功能),也并不知道1小时有60分钟,1分钟有60秒,这些逻辑都是依靠我们自己去实现的。
设计模块1:分频部分,配合FPGA的时钟和计数值,实现1秒的计时;
设计模块2:秒钟部分,实现秒钟的累加,数值达到59自动清零;
设计模块3:分钟部分,实现分钟的累加,数值达到59自动清零;
设计模块4:时钟部分,实现时钟的累计,数值达到23自动清零;
由于我们定义sec
、min
、hour
都是全局的,所以我们不需要额外的标志位,再去判断是否技术满足1分钟,或者计数满足1小时。只需要判断是否等于59即可,因为下个时钟沿才会下一段判断,而刚好下个时钟沿不就正好60了吗(上一刻赋值,下一刻判断,注意使用非阻塞赋值)。所以我们要做的就是维护这些计数值达到59/23的时候自动清零即可。
在每次秒钟累加的时候,翻转LED1,而在累加59秒钟,自动清零那一块的代码里边,翻转LED2。具体不展开,可以结合代码边调边看。
// 秒钟部分
always @ ( posedge clk_div or posedge rst)begin
if(rst)begin
sec <= 0;
led_out1 <= 1'b0;
led_out2 <= 1'b0;
end
else begin
if ( sec == 59 )begin
sec <= 0 ;
led_out2 <= ~led_out2;
end
else begin
led_out1 <= ~led_out1;
sec <= sec + 1 ;
end
end
end
还需要讲一下这个分频部分,这一块比较重要。
变量 | 含义 |
---|---|
clk_out1 | 经过PLL分频得到的50MHz时钟信号 |
cnt | FPGA自加计数的值 |
clk_div | FPGA累计满1秒给的信号(上升沿) |
sec | 秒钟计数值(59后清零) |
min | 分钟计数值(59后清零) |
hour | 时钟计数值(23后清零) |
parameter TIME_2HZ = 25_000_000 ;
parameter TIME_1HZ= 50_000_000 ;
//分频部分 50MHz - 1Hz/2Hz
always @ ( posedge clk_out1 or posedge rst)begin
if(rst)begin
cnt <= 0;
clk_div <= 0;
end
else begin
if ( cnt < TIME_2HZ - 1 )
begin
clk_div <= 0;
cnt <= cnt + 1;
end
else if ( cnt < TIME_1HZ - 1 )
begin
clk_div <= 1;
cnt <= cnt + 1 ;
end
else
cnt <=0 ;
end
end
另外由于板载自带的晶振源是200MHz,所以我们需要PLL分频把晶振源降低到50MHz,这一部分参考之前的那篇博客。如果你的晶振已经是50MHz,那么直接把clk_out1
换成sys_clk
(50MHz)即可。
上述博客地址:【已解决】FPGA系列Xilinx Artix7找不到SYS_CLK在哪里,如何输出50MHZ频率方波
逻辑是这样的,系统时钟现在是50MHz
,计数一次就是20ns
,我们可以计数累计50_000_000
就翻转一次clk_div
的状态;而这里用的方法稍微有些不同,相当于是一个500ms
高电平,一个500ms
低电平,当两个高电平之间相隔还是1秒整。但如果我们1秒翻转1次翻转一次clk_div
的状态,那么两次上升沿
的间隔就是2秒(1秒高电平1秒低电平)而不是1秒。
集合图像来看,假设我涂阴影的部分是1秒的自然时间,那么方案1就是维持500ms
的低电平,维持500ms
的高电平。而方案2就是维持1_000ms
也就是1秒的高电平,再维持1_000
ms的低电平。上述两种方案,哪种更加贴近我们的需求呢?显然是第一种方案,因为它能够维持每隔1秒,传递一个有效上升沿clk_div
,而方案2需要每隔2秒才能传递一个有效的高电平。
另外需要注意的一点是,每个1秒产生一个clk_div
信号,并且在秒钟、分钟、时钟的计时处理函数中,都将clk_div
的上升沿作为触发条件。看似没有什么问题,但其实也有更好的写法。理论上应该使用系统时钟(50MHz)作为触发条件,而不是自己分频的时钟,并且在系统时钟触发之后,再去判断clk_div
上升沿完成一系列操作。虽然目前看工程暂时没有什么问题,这里先mark一下看看有没有其他大佬指点一下。
搞定这几个主要逻辑之后,读后边的代码,应该就比较顺畅了。具体还有问题的话可以评论区留言一下。
代码工程
首先要申明一点,目前入门FPGA的阶段属于非常非常,所有工程代码、工程逻辑都可能存在错误,希望各位同学老师能够指出,同时也参考了大量的互联网上优质内容,在这里一并感谢,基于这个原因也决定将核心逻辑进行共享。请勿做一切商业用途。
另外本次工程项目也继续产用了层次化的设计,具体什么是层次化(仅个人理解),代码复用带来什么好处请看我另一篇博客,这里不再赘述。以下代码包含三个部分,第一部分是核心clock module的实现。第二部分是Top.v,也就是最后下载到板子上的逻辑,这部分调用
了clock module并且申明了一些端口的输出。第三部分是Testbench,同样是调用
了clock module,并且申明了相应端口的输出,不同的是这里也申明了sec
、min
、hour
三个端口的输出,毕竟我们要看的就是这三个呀!?!
我的另一篇博客:FPGA基于Vivado开发,设计顶层文件Top.v
完整的clock module代码,主要实现功能是在50MHz的晶振源下,通过时钟+计数值的配合,每隔1秒输出一个有效的上升沿clk_div
,并且进入具体逻辑的判断,包含计数值累加、计数值清零等操作。具体可以结合代码+仿真再看看。
`timescale 1ns / 1ns
//数字时钟模块
module clock1(
sec,
min,
hour,
rst,
sys_clk_p,
sys_clk_n,
clk_out1,
led_out1,
led_out2
);
input rst;
input sys_clk_p; // Differential input clock 200Mhz
input sys_clk_n; // Differential input clock 200Mhz
output clk_out1;
output [7:0] sec,min;
output [7:0] hour;
output led_out1;
output led_out2;
reg [7:0] sec=0;
reg [7:0] min=0;
reg [7:0] hour=0;
reg [32:0] cnt;
reg clk_div;
reg led_out1;
reg led_out2;
//parameter TIME_2HZ = 25 ; // 测试仿真使用
//parameter TIME_1HZ= 50 ;
parameter TIME_2HZ = 25_000_000 ;
parameter TIME_1HZ= 50_000_000 ;
//分频部分 50MHz - 1Hz
always @ ( posedge clk_out1 or posedge rst)begin
if(rst)begin
cnt <= 0;
clk_div <= 0;
end
else begin
if ( cnt < TIME_2HZ - 1 )
begin
clk_div <= 0;
cnt <= cnt + 1;
end
else if ( cnt < TIME_1HZ - 1 )
begin
clk_div <= 1;
cnt <= cnt + 1 ;
end
else
cnt <=0 ;
end
end
// 秒钟部分
always @ ( posedge clk_div or posedge rst)begin
if(rst)begin
sec <= 0;
led_out1 <= 1'b0;
led_out2 <= 1'b0;
end
else begin
if ( sec == 59 )begin
sec <= 0 ;
led_out2 <= ~led_out2;
end
else begin
led_out1 <= ~led_out1;
sec <= sec + 1 ;
end
end
end
//分钟部分
always @ ( posedge clk_div or posedge rst)begin
if(rst)begin
min <= 0;
end
else begin
if ( sec == 59 )begin
if (min ==59 )
min <= 0;
else
min <= min + 1 ;
end
end
end
// 时钟部分
always @ ( posedge clk_div or posedge rst)
begin
if(rst)begin
hour <= 0;
end
else begin
if ( sec == 59 && min ==59 )begin
if (hour == 23)
hour <= 0 ;
else
hour <= hour + 1;
end
end
end
endmodule
完整的顶层Top.v文件代码,常规的clock module初始化,PLL分频是因为我的板子是200MHz的,通过PLL分频为50MHz(之前的博客写了)。
`timescale 1ns / 1ps
module TOP(
input rst,
input sys_clk_p,
input sys_clk_n,
output clk_out1,
output led_out1,
output led_out2
);
//***********差分时钟50MHz *****************************************
wire sys_clk_p;
wire sys_clk_n;
wire rst;
wire clk_out1;
//***********数字时钟****************************************
wire led_out1;
wire led_out2;
//时钟模块初始化
clock1 clock(
.led_out1(led_out1),
.led_out2(led_out2),
.rst(rst),
.sys_clk_p(sys_clk_p ),
.sys_clk_n(sys_clk_n ),
.clk_out1(clk_out1 )
);
//PLL分频的代码
clk_wiz_0 clk_wiz_0
(
// Clock out ports
.clk_out1(clk_out1), // output clk_out1
// Status and control signals
.reset(rst),
//.locked(locked), // output locked
// Clock in ports
.clk_in1_p(sys_clk_p),
.clk_in1_n(sys_clk_n)); // input clk_in1_n
endmodule
然后就是仿真文件,也都比较常规,注意的是这里还申明了sec、min、hour三个输出,方便我们看他的波形。另外值得注意的点是,这里的数字时钟计数方式是十六进制,也就是说09、0a、0b、0c
……这样的计数方式,具体原因我也还没搞清楚。后续我将这个clock制作成为数字时钟并且显示的时候,另外做了一些处理,最终也能够正常显示成09、10、11、12
……这种形式,感兴趣的话可以翻翻那一篇博客。
`timescale 1ns / 1ns
module clock1_tb();
//***********??????*****************************************
reg sys_clk_p;
wire sys_clk_n;
reg rst;
wire clk_out1;
//***********??????*****************************************
wire [7:0] sec;
wire [7:0] min;
wire [7:0] hour;
wire led_out1;
wire led_out2;
//初始化系统时钟
initial begin
sys_clk_p = 1'b0;
rst <= 1'b0;
#2000
rst <= 1'b1;
#20000
rst <= 1'b0;
#2000000
rst <= 1'b1;
#20000
rst <= 1'b0;
end
parameter T = 5; //200MHz时钟周期为5ns
always #(T/2) sys_clk_p = ~ sys_clk_p;
assign sys_clk_n = ~ sys_clk_p;
//数字时钟初始化
clock1 clock(
.led_out1(led_out1),
.led_out2(led_out2),
.sec(sec),
.min(min),
.hour(hour),
.rst(rst),
.sys_clk_p (sys_clk_p ),
.sys_clk_n (sys_clk_n ),
.clk_out1 (clk_out1 )
);
//PLL时钟分频
clk_wiz_0 clk_wiz_0
(
// Clock out ports
.clk_out1(clk_out1), // output clk_out1
// Status and control signals
.reset(rst),
//.locked(locked), // output locked
// Clock in ports
.clk_in1_p(sys_clk_p),
.clk_in1_n(sys_clk_n)); // input clk_in1_n
endmodule
后续可以烧录板子具体看一看现象,引脚图上边已经给出了,但我们的引脚不会是一样的。另外因为我的时钟200MHz,所以会有sys_clk_p、sys_clk_n(二者差分)、clk_out1(最终的50MHz,其实可以不输出)。板子的复位是高电平有效,这一点也和传统复位可能有些区别,这一点大家注意一下。如果你是低电平复位建议写成rst_n
,我是高电平复位所以是rst
。
总结
- 通过时钟+计数值的配合,在FPGA环境下产生周期为1秒的上升沿
clk_div
; - 在此上升沿的基础之上,实现了秒钟、分钟、时钟的计时逻辑;
- 最终展示效果是LED1每隔1秒翻转一次(2秒完成一次闪烁),LED2每隔1分钟翻转一次。
参考
- FPGA Verilog 开发实战指南 – 基于 Xilinx Artix7,野火(非常推荐这本书和他们家的白嫖资料);
- 基于FPGA的数字时钟(使用vivado)
- 基于FPGA的简易时钟(含verilog源码)
以上。文章来源:https://www.toymoban.com/news/detail-767088.html
如果你觉得这篇文章对你有帮助,请为我点个赞谢谢🌹🌹
如果有遇到其他问题,也请在评论区留言🌹🌹文章来源地址https://www.toymoban.com/news/detail-767088.html
到了这里,关于【代码】Xilinx + Vivado + 数字时钟(时分秒) + LED指示的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!