[FPGA]Verilog 60s秒表计时器(最大可计时间长达9min)
[FPGA]Verilog 60s秒表计时器
1.引述
这次的实验来自于本人本科课程数电结课时的自选题目。由于这次上传是后知后觉,学校已将小脚丫板子回收,所以在这篇文章中没法贴出代码结果的效果图了,但最终效果已经过测试,可放心食用。那么下面就贴上代码并略加讲解供大家参考。
2.分频模块
我们要实现一个秒表,自然要将实验板中的时钟脉冲clk分频为一个周期为1s的脉冲,已知小脚丫板子的晶振为12MHz。下面贴上分频模块的代码。
module divide # ( //parameter是verilog里参数定义 parameter WIDTH = 24, //计数器的位数,计数的最大值为 2**(WIDTH-1) parameter N = 12_000_000 //分频系数,请确保 N<2**(WIDTH-1),否则计数会溢出 ) ( input clk, //clk频率为12MHz input rst_n, //复位信号,低有效, output clkout //输出信号,可以连接到LED观察分频的时钟 ); reg [WIDTH-1:0] cnt_p,cnt_n; //cnt_p为上升沿触发时的计数器,cnt_n为下降沿触发时的计数器 reg clk_p,clk_n; //clk_p为上升沿触发时分频时钟,clk_n为下降沿触发时分频时钟 //上升沿触发时计数器的控制 always @(posedge clk or negedge rst_n) begin if(!rst_n) cnt_p <= 1'b0; else if(cnt_p == (N-1)) cnt_p <= 1'b0; else cnt_p <= cnt_p + 1'b1; //计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器 end //上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50% always @(posedge clk or negedge rst_n) begin if(!rst_n) clk_p <= 1'b0; else if(cnt_p < (N>>1)) //N>>1表示右移一位,相当于除以2取商 clk_p <= 1'b0; else clk_p <= 1'b1; //得到的分频时钟正周期比负周期多一个clk时钟 end //下降沿触发时计数器的控制 always @(negedge clk or negedge rst_n) begin if(!rst_n) cnt_n <= 1'b0; else if(cnt_n == (N-1)) cnt_n <= 1'b0; else cnt_n <= cnt_n + 1'b1; end //下降沿触发的分频时钟输出,和clk_p相差半个clk时钟 always @(negedge clk or negedge rst_n) begin if(!rst_n) clk_n <= 1'b0; else if(cnt_n < (N>>1)) clk_n <= 1'b0; else clk_n <= 1'b1; //得到的分频时钟正周期比负周期多一个clk时钟 end wire clk1 = clk; //当N=1时,直接输出clk wire clk2 = clk_p; //当N为偶数也就是N的最低位为0,N[0]=0,输出clk_p wire clk3 = clk_p & clk_n; //当N为奇数也就是N最低位为1,N[0]=1,输出clk_p&clk_n。正周期多所以是相与 assign clkout = (N==1)? clk1:(N[0]? clk3:clk2); //条件判断表达式 endmodule
3.八位数码管显示模块
小脚丫板子上有两个八位数码管显示,本实验中用来显示从00s到59s的显示。下面贴上数码管显示模块的代码。
module segment ( input wire [3:0] seg_data_1, //四位输入数据信号 input wire [3:0] seg_data_2, //四位输入数据信号 output wire [8:0] segment_led_1, //数码管1,MSB~LSB = SEG,DP,G,F,E,D,C,B,A output wire [8:0] segment_led_2 //数码管2,MSB~LSB = SEG,DP,G,F,E,D,C,B,A ); reg[8:0] seg [15:0]; //存储7段数码管译码数据 initial begin seg[0] = 9'h3f; // 0 seg[1] = 9'h06; // 1 seg[2] = 9'h5b; // 2 seg[3] = 9'h4f; // 3 seg[4] = 9'h66; // 4 seg[5] = 9'h6d; // 5 seg[6] = 9'h7d; // 6 seg[7] = 9'h07; // 7 seg[8] = 9'h7f; // 8 seg[9] = 9'h6f; // 9 seg[10]= 9'h77; // A seg[11]= 9'h7C; // b seg[12]= 9'h39; // C seg[13]= 9'h5e; // d seg[14]= 9'h79; // E seg[15]= 9'h71; // F end assign segment_led_1 = seg[seg_data_1]; assign segment_led_2 = seg[seg_data_2]; endmodule
4.功能讲解
在主模块中除了要例化上述的两个模块之外,还需给这个秒表添砖加瓦一下!标题中提到这是一个60s秒表,而我们数码管显示只从00到59,但最大计时量程却达到了9min,这是怎么办到的呢?这里我们就用到了小脚丫上的一排八位LED灯,每当计到59s时,下一秒数码管显示回到00,点亮八位LED灯中的一个,达到表示已计过了1min的作用。一共有八位LED灯,当八个灯都被点亮后,数码管还有一次从00到59的显示机会,这样我们就的得到了一个最大计时量程为9min的秒表啦!下面贴上八位LED灯显示部分的代码。
always@(posedge clk) if(cnt1==4'b0) LED[7:0]<=8'b11111111; else if(cnt1==4'b0001) LED[7:0]<=8'b11111110; else if(cnt1==4'b0010) LED[7:0]<=8'b11111100; else if(cnt1==4'b0011) LED[7:0]<=8'b11111000; else if(cnt1==4'b0100) LED[7:0]<=8'b11110000; else if(cnt1==4'b0101) LED[7:0]<=8'b11100000; else if(cnt1==4'b0110) LED[7:0]<=8'b11000000; else if(cnt1<=4'b0111) LED[7:0]<=8'b10000000; else if(cnt1<=4'b1000) LED[7:0]<=8'b00000000;
此外作为一个秒表自然就要有暂停和开始计时的功能(当然清零功能也是有哒!主模块中就用rst复位键来实现,这里不多赘述。)暂停和开始计时这里我就用同一个按键实现。小脚丫板子上还有两个RGB三色灯,既然有这么好的资源存在,我们就要物尽其用!按下开始计时键时,秒表开始计时,数码管显示开始变化,此处我们让RGB三色灯中的一个等亮绿灯,表示处于正常计时状态中;当再次按键开启键时,秒表暂停,我们让另一个RGB三色灯亮红色,表示处于暂停状态。下面贴上包含三色灯点亮的部分代码。
always @(posedge clk1h or negedge rst) //产生60进制计数器 begin //数码管显示要按照十进制的方式显示 if(!rst) begin cnt <= 8'h00; //复位初值显示00 cnt1<=4'b0; end else if(flag) begin G_LED2<=1'b0; R_LED1<=1'b1; if(cnt[3:0] == 4'd9) //个位满九? begin cnt[3:0] <= 4'd0; //个位清零 if(cnt[7:4] == 4'd5 ) //十位满五? begin cnt[7:4] <= 4'd0; //十位清零 cnt1<=cnt1+1; end else begin cnt[7:4] <= cnt[7:4] + 1'b1; //十位加一 cnt1<=cnt1; end end else cnt[3:0] <= cnt[3:0] + 1'b1; //个位加一 end else begin cnt <= cnt; G_LED2<=1'b1; R_LED1<=1'b0; end end
5.主模块
最后贴上主模块的代码,完成整个秒表的实现。
module counter60 ( input clk,rst, //时钟和复位输入 input key, //启动暂停按键 output wire [8:0] segment_led_1,segment_led_2, //数码管输出 output reg [7:0] LED, //八位LED灯 output reg R_LED1,G_LED2 //RGB三色灯,此处用红色表示处于暂停状态中,绿色表示处于正常计时中 ); wire clk1h; //1秒时钟 reg [7:0] cnt; //计时计数器 reg [3:0] cnt1; //分钟计数器 reg flag; //启动暂停标志 divide # //例化分频器产生1秒时钟信号 ( .WIDTH(24), .N(12_000_000) ) u1 ( .clk(clk), .rst_n(rst), .clkout(clk1h) ); segment u2 //例化数码管显示模块 ( .seg_data_1 (cnt[7:4]), //seg_data input .seg_data_2 (cnt[3:0]), //seg_data input .segment_led_1 (segment_led_1), //MSB~LSB = SEG,DP,G,F,E,D,C,B,A .segment_led_2 (segment_led_2) //MSB~LSB = SEG,DP,G,F,E,D,C,B,A ); always @(posedge clk or negedge rst) //产生标志信号 begin if(!rst) flag = 1'b0; else if(!key) begin flag = ~flag; end else begin flag = flag; end end always @(posedge clk1h or negedge rst) //产生60进制计数器 begin //数码管显示要按照十进制的方式显示 if(!rst) begin cnt <= 8'h00; //复位初值显示00 cnt1<=4'b0; end else if(flag) begin G_LED2<=1'b0; R_LED1<=1'b1; if(cnt[3:0] == 4'd9) //个位满九? begin cnt[3:0] <= 4'd0; //个位清零 if(cnt[7:4] == 4'd5 ) //十位满五? begin cnt[7:4] <= 4'd0; //十位清零 cnt1<=cnt1+1; end else begin cnt[7:4] <= cnt[7:4] + 1'b1; //十位加一 cnt1<=cnt1; end end else cnt[3:0] <= cnt[3:0] + 1'b1; //个位加一 end else begin cnt <= cnt; G_LED2<=1'b1; R_LED1<=1'b0; end end always@(posedge clk) if(cnt1==4'b0) LED[7:0]<=8'b11111111; else if(cnt1==4'b0001) LED[7:0]<=8'b11111110; else if(cnt1==4'b0010) LED[7:0]<=8'b11111100; else if(cnt1==4'b0011) LED[7:0]<=8'b11111000; else if(cnt1==4'b0100) LED[7:0]<=8'b11110000; else if(cnt1==4'b0101) LED[7:0]<=8'b11100000; else if(cnt1==4'b0110) LED[7:0]<=8'b11000000; else if(cnt1<=4'b0111) LED[7:0]<=8'b10000000; else if(cnt1<=4'b1000) LED[7:0]<=8'b00000000; endmodule module divide # ( //parameter是verilog里参数定义 parameter WIDTH = 24, //计数器的位数,计数的最大值为 2**(WIDTH-1) parameter N = 12_000_000 //分频系数,请确保 N<2**(WIDTH-1),否则计数会溢出 ) ( input clk, //clk频率为12MHz input rst_n, //复位信号,低有效, output clkout //输出信号,可以连接到LED观察分频的时钟 ); reg [WIDTH-1:0] cnt_p,cnt_n; //cnt_p为上升沿触发时的计数器,cnt_n为下降沿触发时的计数器 reg clk_p,clk_n; //clk_p为上升沿触发时分频时钟,clk_n为下降沿触发时分频时钟 //上升沿触发时计数器的控制 always @(posedge clk or negedge rst_n) begin if(!rst_n) cnt_p <= 1'b0; else if(cnt_p == (N-1)) cnt_p <= 1'b0; else cnt_p <= cnt_p + 1'b1; //计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器 end //上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50% always @(posedge clk or negedge rst_n) begin if(!rst_n) clk_p <= 1'b0; else if(cnt_p < (N>>1)) //N>>1表示右移一位,相当于除以2取商 clk_p <= 1'b0; else clk_p <= 1'b1; //得到的分频时钟正周期比负周期多一个clk时钟 end //下降沿触发时计数器的控制 always @(negedge clk or negedge rst_n) begin if(!rst_n) cnt_n <= 1'b0; else if(cnt_n == (N-1)) cnt_n <= 1'b0; else cnt_n <= cnt_n + 1'b1; end //下降沿触发的分频时钟输出,和clk_p相差半个clk时钟 always @(negedge clk or negedge rst_n) begin if(!rst_n) clk_n <= 1'b0; else if(cnt_n < (N>>1)) clk_n <= 1'b0; else clk_n <= 1'b1; //得到的分频时钟正周期比负周期多一个clk时钟 end wire clk1 = clk; //当N=1时,直接输出clk wire clk2 = clk_p; //当N为偶数也就是N的最低位为0,N[0]=0,输出clk_p wire clk3 = clk_p & clk_n; //当N为奇数也就是N最低位为1,N[0]=1,输出clk_p&clk_n。正周期多所以是相与 assign clkout = (N==1)? clk1:(N[0]? clk3:clk2); //条件判断表达式 endmodule module segment ( input wire [3:0] seg_data_1, //四位输入数据信号 input wire [3:0] seg_data_2, //四位输入数据信号 output wire [8:0] segment_led_1, //数码管1,MSB~LSB = SEG,DP,G,F,E,D,C,B,A output wire [8:0] segment_led_2 //数码管2,MSB~LSB = SEG,DP,G,F,E,D,C,B,A ); reg[8:0] seg [15:0]; //存储7段数码管译码数据 initial begin seg[0] = 9'h3f; // 0 seg[1] = 9'h06; // 1 seg[2] = 9'h5b; // 2 seg[3] = 9'h4f; // 3 seg[4] = 9'h66; // 4 seg[5] = 9'h6d; // 5 seg[6] = 9'h7d; // 6 seg[7] = 9'h07; // 7 seg[8] = 9'h7f; // 8 seg[9] = 9'h6f; // 9 seg[10]= 9'h77; // A seg[11]= 9'h7C; // b seg[12]= 9'h39; // C seg[13]= 9'h5e; // d seg[14]= 9'h79; // E seg[15]= 9'h71; // F end assign segment_led_1 = seg[seg_data_1]; assign segment_led_2 = seg[seg_data_2]; endmodule
6.总结
到这里整个秒表就完成啦。最后再次向读者们道歉,不能贴上实验效果图了。身边有实验板的读者们可以将代码烧录进板子观察现象。本人编程水平、时间有限,这篇文章到这里就要结束啦,欢迎广大读者评论留言,更欢迎大家指出本人的不足,希望能通过交流自身得到提高。最后感谢大家的耐心阅读!
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。