赞
踩
sec
计数累加1次,59次之后清零;min
计数累加1次,59次之后清零;hour
计数累加1次,23次之后清零;首先要申明一点,目前入门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)即可。
逻辑是这样的,系统时钟现在是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
。
clk_div
;以上。
如果你觉得这篇文章对你有帮助,请为我点个赞谢谢
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。