基于basys2用verilog设计多功能数字钟(重写)
话不多说先上图
前言
自从学习FPGA以来,唯一做过的完整系统就是基于basys2得多功能数字表。记得当时做的时候也没少头疼,最后用时间磨出来了一个不是很完整的小系统,当时还是产生了满满的成就感。现在回头看来,先不说功能实现的如何,首先代码书写满是不规范,其中犯得最多的一个问题就是把verilog当C来写。所以,我决定趁着寒假自由支配的时间比较多,决定重写多功能数字时钟,算是对我大二第一学期以来对verilog的学习做一个总结。
首先,重写后的工程不仅在功能上做了一些优化,而且占用片内的资源也相对来说少了一些。话不多说先上图。
原来工程的资源占用情况
重写后资源占用情况(少了一点点,呵呵)
一、摘要
多功能数字表,有数字钟、秒表(有暂停清零功能)、闹钟(可设置)、外设蜂鸣器(当闹钟达到设定时间的时候蜂鸣器响应)。第一次写的代码主要是代码书写不规范,可读性和可移植性不强,一个always块里经常给多个寄存器赋值,或者多个if...else语句嵌套,这次重写在占用资源上得到优化,功能更加完善。
二、设计思想
主要有七个模块构成:数字钟计数、秒表计数、按键驱动、数码管显示、蜂鸣器、闹钟设置、中央数据处理中心。下面是rtl原理图。在设计闹钟、时钟、以及秒表共享数码管显示模块的时候,由于每个数码管是单独显示的所以闹钟、时钟、秒表设置模块的一共12个输出我还没有想到更好的办法处理,就专门写了一个cpu模块将各个功能模块对应使用拨码开关什么时候数码管应该显示的信号。感觉这种写法cpu模块的连线有点多。所以以后有机会再回头看看怎么优化一下。
在写时钟计数器时为了使时钟信号的准确,我采用数据流型信号,来作为时间的进位出发标志,代码如下,这种写法,即可以节省寄存器资源占用,也可以在将信号作为边沿检测使用时避免时钟偏移(skew)。
1 parameter TIME = 26'd49999999; 2 //parameter TIME = 26'd49;//仿真专用 3 4 reg [25:0] cnt;//分频一秒时钟信号计数器 5 reg [5:0] cnt_s;//秒计数器 6 wire flag_second;//秒59 7 wire flag_minute_one;//秒59,分个9 8 wire flag_minute_ten;//秒59,分个9,分十5 9 wire flag_hour_one1;//秒59,分个9,分十5,时个9 10 wire flag_hour_one2;//秒59,分个9,分十5,时个3 11 wire flag_hour_ten;//秒59,分个9,分十5,时个3,时十2 12 13 //分频数一秒信号 14 always @(posedge mclk or negedge rst_n) 15 begin 16 if(!rst_n) 17 cnt <= 26'b0; 18 else if(cnt == TIME) 19 cnt <= 26'b0; 20 else 21 cnt <= cnt + 1'b1; 22 end 23 24 //秒计数 25 always @(posedge mclk or negedge rst_n) 26 begin 27 if(!rst_n) 28 cnt_s <= 6'b0; 29 else if(cnt_s == 59 && cnt == TIME) 30 cnt_s <= 6'b0; 31 else if(cnt == TIME) 32 cnt_s <= cnt_s + 1'b1; 33 else 34 cnt_s <= cnt_s; 35 end 36 assign flag_second = (cnt_s == 59 && cnt == TIME)? 1'b1:1'b0; 37 38 //分钟个位计数 39 always @(posedge mclk or negedge rst_n) 40 begin 41 if(!rst_n) 42 minute_one <= 4'b0; 43 else if(minute_one == 9 && flag_second) 44 minute_one <= 4'b0; 45 else if(flag_second || key_en[0] && !clock) 46 minute_one <= minute_one + 1'b1; 47 else 48 minute_one <= minute_one; 49 end 50 assign flag_minute_one = (minute_one == 9 && flag_second)?1'b1:1'b0; 51 52 //分钟十位计数 53 always @(posedge mclk or negedge rst_n) 54 begin 55 if(!rst_n) 56 minute_ten <= 3'b0; 57 else if((minute_ten == 5 && flag_minute_one) || minute_ten == 6) 58 minute_ten <= 3'b0; 59 else if(flag_minute_one || key_en[1] && !clock) 60 minute_ten <= minute_ten + 1'b1; 61 else 62 minute_ten <= minute_ten; 63 end 64 assign flag_minute_ten = (minute_ten == 5 && flag_minute_one)?1'b1:1'b0; 65 66 //小时的个位计数 67 always @(posedge mclk or negedge rst_n) 68 begin 69 if(!rst_n) 70 hour_one <= 4'b0; 71 else if(hour_one == 9 && flag_minute_ten || flag_hour_ten) 72 hour_one <= 4'b0; 73 else if(flag_minute_ten || key_en[2] && !clock) 74 hour_one <= hour_one + 1'b1; 75 else 76 hour_one <= hour_one; 77 end 78 assign flag_hour_one1 = (hour_one == 9 && flag_minute_ten)?1'b1:1'b0; 79 assign flag_hour_one2 = (hour_one == 3 && flag_minute_ten)?1'b1:1'b0; 80 81 //小时的十位计数 82 always @(posedge mclk or negedge rst_n) 83 begin 84 if(!rst_n) 85 hour_ten <= 3'b0; 86 else if(hour_ten == 2 && flag_hour_one2) 87 hour_ten <= 3'b0; 88 else if(flag_hour_one1 || key_en[3] && !clock) 89 hour_ten <= hour_ten + 1'b1; 90 else 91 hour_ten <= hour_ten; 92 end 93 assign flag_hour_ten = (hour_ten == 2 && flag_hour_one2)?1'b1:1'b0;
按键消抖模块,我采用的是软件消抖,当按键按下后有一段不稳定时期,所以在检测到按键按下后计数器开始计数,当计数器记到一定数值,按键才会响应,当按键松开,计数器停止计数,所以在可以将那一段不稳定时期避免开。代码如下,可以根据所需要的按键个数自定义位宽!
1 parameter DURATION = 600; 2 3 reg[10:0] cnt; 4 5 always @(posedge mclk or negedge rst_n) 6 begin 7 if(!rst_n) 8 cnt <= 11'b0; 9 else if(key[0] == 1)begin 10 if(cnt == DURATION) 11 cnt <= cnt; 12 else 13 cnt <= cnt + 1'b1; 14 end 15 else 16 cnt <= 11'b0; 17 end 18 assign key_en[0] = (cnt == DURATION -1)?1'b1:1'b0;
蜂鸣器模块,当时钟计数到的时间与预先设置的时间相同时,蜂鸣器响应,发出救护车的声音。蜂鸣器驱动救护之音方法与呼吸灯类似,使用pwm信号脉宽调制,当pwm信号越来越高占空比高的时候,蜂鸣器声音越来越大,反之蜂鸣器声音越来越小。本例采用的无源蜂鸣器(有源蜂鸣器只需要通电便可直接响应),代码才考如下:
1 module beep( 2 input mclk, 3 input rst_n, 4 input ring, 5 //时钟 6 input [2:0] a1, 7 input [3:0] a2, 8 input [2:0] a3, 9 input [3:0] a4, 10 //闹钟 , 11 input [2:0] b1, 12 input [3:0] b2, 13 input [2:0] b3, 14 input [3:0] b4, 15 output reg beep 16 ); 17 18 parameter H_s = 25'd249999, 19 L_s = 25'd31249; 20 21 reg [24:0] cnt_T; 22 reg [15:0] cnt; 23 reg flag;//闹钟标志 24 25 always @(posedge mclk or negedge rst_n) 26 begin 27 if(!rst_n) 28 flag <= 1'b0; 29 else if(a1 == b1 && a2 == b2 && a3 == b3 && a4 == b4) 30 flag <= 1'b1; 31 else 32 flag <= 1'b0; 33 end 34 35 always @(posedge mclk or negedge rst_n) 36 begin 37 if(!rst_n) 38 cnt_T <= 25'b0; 39 else 40 cnt_T <= cnt_T + 1'b1; 41 end 42 43 always @(posedge mclk or negedge rst_n) 44 begin 45 if(!rst_n) 46 begin 47 cnt <= L_s; 48 beep <= 1'b0; 49 end 50 else if(cnt == 0 && flag && ring) 51 begin 52 cnt <= cnt_T[24]?H_s:L_s; 53 beep <= ~beep; 54 end 55 else 56 cnt <= cnt - 1'b1; 57 end 58 59 endmodule
最后附上数码管显示模块,该模块没有复杂的地方会写译码器就应该能看懂
1 module seven_seg_display( 2 input mclk, 3 input rst_n, 4 input [2:0] hour_ten, 5 input [3:0] hour_one, 6 input [2:0] minute_ten, 7 input [3:0] minute_one, 8 output reg [7:0] out, 9 output reg [3:0] an//所有的数码管的使能端 10 ); 11 12 wire [3:0] aen;//数码管使能信号 13 reg [1:0] s;//数码管显示选择 14 reg [18:0] cnt;//数码管扫描时钟计数 15 16 parameter CLK190 = 18'd263157; 17 18 19 always @(posedge mclk or negedge rst_n) 20 begin 21 if(!rst_n) 22 cnt <= 18'b0; 23 else if(cnt == CLK190 - 1'b1) 24 cnt <= 18'b0; 25 else 26 cnt <= cnt + 1'b1; 27 end 28 29 //灯的状态没5.2毫秒刷新一次 30 always @(posedge mclk or negedge rst_n) 31 begin 32 if(!rst_n) 33 s <= 2'b0; 34 else if(cnt == CLK190 - 1'b1) 35 s <= s + 1'b1; 36 end 37 38 assign aen = 4'b1111; 39 always @(*) 40 begin 41 an <= 4'b1111; 42 if(aen[s] == 1) 43 an[s] <= 1'b0; 44 end 45 46 reg [7:0] out0; 47 //小时的十位显示 48 always @(posedge mclk or negedge rst_n) 49 begin 50 if(!rst_n) 51 out0 <= 8'b0000_0011; 52 else 53 begin 54 case(hour_ten) 55 0: out0 <= 8'b0000_0011; 56 1: out0 <= 8'b1001_1111; 57 2: out0 <= 8'b0010_0101; 58 default: out0 <= 8'b0000_0011; 59 endcase 60 end 61 end 62 63 reg [7:0] out1; 64 //小时的个位显示 65 always @(posedge mclk or negedge rst_n) 66 begin 67 if(!rst_n) 68 out1 <= 8'b0000_0010; 69 else 70 begin 71 case(hour_one) 72 0: out1 <= 8'b0000_0010; 73 1: out1 <= 8'b1001_1110; 74 2: out1 <= 8'b0010_0100; 75 3: out1 <= 8'b0000_1100; 76 4: out1 <= 8'b1001_1000; 77 5: out1 <= 8'b0100_1000; 78 6: out1 <= 8'b0100_0000; 79 7: out1 <= 8'b0001_1110; 80 8: out1 <= 8'b0000_0000; 81 9: out1 <= 8'b0000_1000; 82 default: out1 <= 8'b0000_0010; 83 endcase 84 end 85 end 86 87 reg [7:0] out2; 88 //分钟的十位显示 89 always @(posedge mclk or negedge rst_n) 90 begin 91 if(!rst_n) 92 out2 <= 8'b0000_0011; 93 else 94 begin 95 case(minute_ten) 96 0: out2 <= 8'b0000_0011; 97 1: out2 <= 8'b1001_1111; 98 2: out2 <= 8'b0010_0101; 99 3: out2 <= 8'b0000_1101; 100 4: out2 <= 8'b1001_1001; 101 5: out2 <= 8'b0100_1001; 102 6: out2 <= 8'b0100_0001; 103 7: out2 <= 8'b0001_1111; 104 8: out2 <= 8'b0000_0001; 105 9: out2 <= 8'b0000_1001; 106 default: out2 <= 8'b0000_0011; 107 endcase 108 end 109 end 110 111 reg [7:0] out3; 112 //分钟的个位显示 113 always @(posedge mclk or negedge rst_n) 114 begin 115 if(!rst_n) 116 out3 <= 8'b0000_0011; 117 else 118 begin 119 case(minute_one) 120 0: out3 <= 8'b0000_0011; 121 1: out3 <= 8'b1001_1111; 122 2: out3 <= 8'b0010_0101; 123 3: out3 <= 8'b0000_1101; 124 4: out3 <= 8'b1001_1001; 125 5: out3 <= 8'b0100_1001; 126 6: out3 <= 8'b0100_0001; 127 7: out3 <= 8'b0001_1111; 128 8: out3 <= 8'b0000_0001; 129 9: out3 <= 8'b0000_1001; 130 default: out3 <= 8'b0000_0011; 131 endcase 132 end 133 end 134 135 always @(posedge mclk or negedge rst_n) 136 begin 137 if(!rst_n) 138 out <= 8'b0000_0011; 139 else 140 case(s) 141 0: out <= out3; 142 1: out <= out2; 143 2: out <= out1; 144 3: out <= out0; 145 default: out <= 8'b0000_0011; 146 endcase 147 end 148 149 endmodule
-----------------------------------------我是华丽的分隔线----------------------------------------------------------------
今天无意之中看到了另一种设置多功能数字表的思想,废话少说,下面做以笔记。
我原来的设计,在秒分频过后,当记到59秒时,将分的个位加1,当分的个位记到9时将分的十位加1,当分的十个位为59时,将时的个位加1,以此类推。我使用的basys2开发板只有四个LED数码管,所以只能显示分时,所以有四个输出端口minute_one,minute_ten,hour_one,hour_ten。完成显示。
下面我引出另一种设计思路
将秒计数到59,分进行进位,将分计数到59,小时进行进位,最后小时计数到23。然而一个时钟的个位和十位是分别显示的,所以还要做一个接口转换,把2位十进制数的个位和十位分离开。具体方法就是除以10,商是十位的值,余是个位的值。
如图所示:
三、实验总结
这里我就我不再次总结了,把以前的实验报告复制过来看看,现在看起来以前的总结报告,有些写的真是不堪入目,
-----------------------------------------我是分隔线---------------------------------------------------------------------
工作总结
第二期我们小队选择多功能数字时钟一题,针对basys2开发板使用Verilog HDL语言编译,完成该工程。题目要求多功能数字中有时钟(可校时)、秒表(可清零、暂停)功能,我们在原有题目完成的基础上添加了闹钟功能,在完成任务过程中可谓是一路心酸加苦逼,但最后还是终于圆满完成!下面我将简述我们完成工作过程中具体遇到的一些问题。
心得与体会
在设计之初,由于心中对整个工程的思考不够,没有一个具体的模块化概念,还只是单单把每个功能当成一个题目去做,导致给后面的工作埋下了定时炸弹。后来在慢慢的进度到后面的时候,才有了将各个模块联系起来的思想,在设计一个模块的时候就必须将其他想加入的模块提前考虑好,给下一步工程就要留下能加入引用的空间。而且小组合作就必须每个人的代码风格要类似,每个人写自己的模块,要不然最后模块整合的时候会有很大的麻烦。
在这次任务中我们没有体现出小组合作的优势,很伤心的是我们组最开始的两个组员因为忙双双离我而去,他们由于事情安排不开而且上一期的任务还没有完成,所以前面一半的工程我都独自一个人完成。记得那个周六我从早上九点多来到实验室,除了中间吃饭一直坐在这写代码,一直写到晚上十点多才离开,到第二天也是同样,只不过下午五点多就回去休息了。正是我这两天沉下心来做才让我们的任务有了很大的进展。最后我的两个组员离开,我感到有些伤心,但工程还得继续,我已经做好独立完成的准备,还好最后又有两个人分到我的小队,这让我又看到了希望,我的新队员给了我很多的灵感,所以我决定在原有的题目基础上加入闹钟的功能,但是我独立完成的那一部分还有一个致命的问题没有解决,就是硬件开发的基础问题——按键消抖。
从接触开始我就感觉按键消抖比较抽象,再加上对于放在我们这个数字钟的题目,按键不由的跟时钟扯到了一起,消抖的代码没有问题,但是因为这个题目的特殊,消抖后必须和时钟扯上关系,所以在这个问题上耗费了我很多的时间,上周从周一到周四四天里一下课我就来实验室,将代码改了一遍又一遍,改回来又改回去,到周四的时候我已经体会到绝望的感觉。周五终于解决了这个问题。到这里我们的工程就取得了一个大前进。
我其他两个组员编写了秒表计数的模块,然后在周五下午我就立马将模块整合,到此便完成了题目的所有要求。闹钟功能必须要又可以设置闹钟的功能而按键只有四个已经用过了,要是再在原来的基础上改动有可能别的模块出问题。周日的工作很顺利几乎没用多少时间,在原有的代码基础上增加个模块就实现了闹钟功能,至此我们的任务顺利完成。
------------------------------------------------------------------------------
从开始接触fpga以来已经有6个月了,我的路还是很漫长,就像bingo曾经说的,我没资格放弃。现在寒假还有不到二十天结束,不知道我又能学多少,我不认为自己势必比别人聪明的人,但我相信通过自己的努力,会慢慢变得更好。最后献上一个图,每天多努力一点点和少努力一点点的差距就是这么大。
转载请注明出处:NingHeChuan(宁河川)
个人微信订阅号:NingHeChuan
如果你想及时收到个人撰写的博文推送,可以扫描左边二维码(或者长按识别二维码)关注个人微信订阅号
知乎ID:NingHeChuan
微博ID:NingHeChuan