Verilog实现数字钟(带校时、按键消抖)并求教一个bug

Verilog实现数字钟以及一个bug的讨论

我现在是FPGA新手入门,数字钟大概是新手最常做的一个入门系统项目吧,所以也想分享以下我的方案,以及求教一个我无法解决的bug。
测试采用Altera的EP4CE6F17C8芯片开发板。

分频模块

module clk_div(cr,clk_50M,clk_1k,clk_5,clk_1);
  parameter N_1k=50_000;            //1kHz分频常数
  parameter N_5=1000_0000;          //500Hz分频常数
  parameter N_1=5000_0000;          //1Hz分频常数
  input cr,clk_50M;
  output reg clk_5,clk_1k,clk_1;
  reg [14:0]cnt_1k;
  reg [24:0]cnt_5;
  reg [24:0]cnt_1;

always @(posedge clk_50M or negedge cr)
begin
    if(~cr) begin
      clk_1<=0;
      clk_1k<=0;
      clk_5<=0;
      cnt_1<=0;
      cnt_1k<=0;
      cnt_5<=0;
    end
    else begin
//1kHz输出
    if(cnt_1k<(N_1k/2-1)) cnt_1k<=cnt_1k+1'b1;
	 else begin
	   cnt_1k<=0;
      clk_1k<=~clk_1k;
    end

//5Hz输出
    if(cnt_5<(N_5/2-1)) cnt_5<=cnt_5+1'b1;
	 else begin
      cnt_5<=0;
      clk_5<=~clk_5;
    end
 
//1Hz输出
    if(cnt_1<(N_1/2-1)) cnt_1<=cnt_1+1'b1;
	 else begin
      cnt_1<=0;
      clk_1<=~clk_1;
    end  
    end
end
endmodule

时钟模块

分、秒60进制采用10进制和6进制两个模块构成。

//10计数器
module cnt10(cp,cr,en,q);
    input cp,
	      cr,
		  en;
	output reg[3:0]q;
	always @(posedge cp or negedge cr) begin
		if(~cr) q<=4'b0000;         //cr置低清零
		else if(en) begin
			if(q>=9) q<=4'b0000;
			else q<=q+1;
		end
		else q<=q;
	end
endmodule

//6计数器
module cnt6(cp,cr,en,q);
    input cp,
	      cr,
		  en;
	output reg[3:0]q;
	always @(posedge cp or negedge cr) begin
		if(~cr) q<=4'b0000;         //cr置低清零
		else if(en) begin
			if(q>=5) q<=4'b0000;
			else q<=q+1;
		end
		else q<=q;
	end
endmodule

//60计数器
module cnt60(cp,cr,en,q);
  input cp,cr,en;
  output wire[7:0]q;                  //输出8BCD码
  wire ENP;                           //内部进位信号
  
  cnt10 u0(.cp(cp),.cr(cr),.en(en),.q(q[3:0]));  //引用模10计数器模块
  cnt6 u1(.cp(cp),.cr(cr),.en(ENP),.q(q[7:4]));
  assign ENP=(q[3:0]==4'b1001);
endmodule

//24计数器
module cnt24(cp,cr,en,q);
    input cp,cr,en;
    output reg[7:0]q;
 
always @(posedge cp or negedge cr)
    begin
//个位模10计数,十位模3计数,终态23
        if(~cr) q<=8'h00;                    //清零
        else if(~en) q<=q;                   //保持
        else if(q[7:4]==2&&q[3:0]>=3||q[7:4]>2||q[3:0]>9) q[7:0]<=8'h00;         //错误处理  
        else if(q[7:4]==2&&q[3:0]<3) begin q[7:4]<=q[7:4]; q[3:0]<=q[3:0]+1; end //20~23计数
        else if(q[3:0]==9) begin q[7:4]<=q[7:4]+1; q[3:0]<=4'b0000; end  //十位进位,各位清零
        else begin q[3:0]<=q[3:0]+1; q[7:4]<=q[7:4]; end                 //其他情况个位加一
    end
endmodule 

时钟顶层模块

module clock(
    input clk_1,clk_5,cr,en,
    input key_2,             //调分信号
    input key_3,             //调时信号
    output [7:0]hour,        //时信号
    output [7:0]min,         //分信号
    output [7:0]sec          //秒信号
);
    wire sec_min;               //-分进位信号
    wire min_hour;              //-时进位信号

//引用模块
cnt60 u_sec(.cp(clk_1),.cr(cr),.en(en),
            .q(sec));
cnt60 u_min(.cp(~sec_min),.cr(cr),.en(en),
            .q(min));
cnt24 u_hour(.cp(~min_hour),.cr(cr),.en(en),
             .q(hour));
assign sec_min=key_2?(sec==8'h59):clk_5;
assign min_hour=key_3?({min,sec}==16'h5959):clk_5;

endmodule

这里的clk_5是校时用的5Hz信号。key_2和key_3分别用于调校分和时,松开时接收上一级的进位信号,按下后接5Hz方波信号校时。
对clock模块仿真结果
注意这里用16进制格式显示的是23时59分处的进位情况,秒到达59后正常进位。

显示译码模块

//六位数显管,1ms频率扫描,使用1kHz频率
//此处为共阳极数码管,低电平有效
//片选信号低电平有效
module dig_display(cp,dig,sel,seg);
input cp;
input [23:0]dig;                     //输入24BCD码
output reg [5:0]sel;                 //选择六个数显管
output reg [7:0]seg;                 //输出8位译码(h为dp)
reg [2:0]cnt;
reg [3:0]dig_r;              //实际转换的4BCD码

initial cnt<=3'd0;
//片选,1ms刷新
always @(posedge cp)
begin
    if(cnt<6) cnt<=cnt+1;
    else cnt<=3'd0;
    case(cnt) 
      0:begin dig_r<=dig[3:0];sel<=6'b111110; end        //Q6,右一
      1:begin dig_r<=dig[7:4];sel<=6'b111101; end        //Q5,右二
      2:begin dig_r<=dig[11:8];sel<=6'b111011; end       //Q4,右三
      3:begin dig_r<=dig[15:12];sel<=6'b110111; end      //Q3,左三
      4:begin dig_r<=dig[19:16];sel<=6'b101111; end      //Q2,左二
      5:begin dig_r<=dig[23:20];sel<=6'b011111; end      //Q1,左一
      default:sel<=6'b111111;                            //默认下均不选中
    endcase
end

//译码
always @(dig_r)
begin
    case(dig_r)
      4'h0: seg=8'b1100_0000;        //abcdef
      4'h1: seg=8'b1111_1001;        //bc
      4'h2: seg=8'b1010_0100;        //abdeg
      4'h3: seg=8'b1011_0000;        //abcdg
      4'h4: seg=8'b1001_1001;        //bcfg
      4'h5: seg=8'b1001_0010;        //acdfg
      4'h6: seg=8'b1000_0010;        //acdefg
      4'h7: seg=8'b1111_1000;        //abc
      4'h8: seg=8'b1000_0000;        //all
      4'h9: seg=8'b1001_0000;        //abcdfg
      default:seg=8'b0111_1111;      //出现错误显示小数点
    endcase
end
endmodule

按键消抖模块

仅就完成一个数字钟而言,按键消抖不重要。但使用5Hz信号校时时,如果不进行按键消抖总是会导致跳动,于是还是加上了。
我的按键消抖方案是参考的下面这个链接按键消抖方案中的方案三来的,几乎完美实现了按键消抖,详细大家自己去看,我只做一点简单解释。

  1. 带自锁的原版方案
//源码来自:https://blog.csdn.net/zhoutaopower/article/details/103180042
module keyscan_sl(
    input clk,
    input cr,
    input key_in,
    output reg key_out
);

//上升沿/下降沿检测模块
reg key_in_0;           //按键抽样现态
reg key_in_1;           //按键上次抽样状态
always @(posedge clk or negedge cr)
begin
    if(~cr) begin
        key_in_0<=1'b1;
        key_in_1<=1'b1;
    end
    else begin
        key_in_0<=key_in;
        key_in_1<=key_in_0;
    end
end

//下降沿和上升沿的检测方案,记下
wire key_posedge;
wire key_negedge;
assign key_posedge=key_in_0 & (~key_in_1);      //上次检测为0,现态为1时key_posedge置1
assign key_negedge=key_in_1 & (~key_in_0);

//状态机
reg[2:0]state;           //三个状态
parameter IDLE = 3'b001;           
parameter SAMPLING = 3'b010;
parameter DOWN = 3'b100;
reg en_cnt;                 //计数使能
reg key_pressed;

always @(posedge clk or negedge cr)
begin
    if(~cr) begin
        state<=IDLE;
        en_cnt<=1'b0;
        key_pressed<=1'b0;
    end
    else begin
    case(state)
    IDLE:begin
        if(key_negedge) begin
            state<=SAMPLING;
            en_cnt<=1'b1;
            key_pressed<=1'b0;
        end
        else begin
            state<=IDLE;
            en_cnt<=1'b0;
            key_pressed<=1'b0;
        end
        end
    SAMPLING:begin
        if(key_posedge) begin
            state <=IDLE;
            en_cnt<=1'b0;
            key_pressed<=1'b0;
        end
        else begin 
            if(cnt_full) begin
                state<=DOWN;
                en_cnt<=1'b0;
                key_pressed<=1'b0;
            end
            else begin
                state<=SAMPLING;
                en_cnt<=1'b1;
                key_pressed<=1'b0;
            end
        end
        end
    DOWN:begin
        state<=IDLE;
        en_cnt<=1'b0;
        key_pressed<=1'b1;
        end
    default:begin
        state<=IDLE;
        en_cnt<=1'b0;
        key_pressed<=1'b0;
        end
    endcase
    end
end

//Counter
reg [19:0]cnt;
always @(posedge clk or negedge cr)
begin
    if(~cr) cnt<=20'h00000;
    else if(en_cnt) cnt<=cnt+1;
    else cnt<=20'h00000;
end

//Counter_full logic
parameter _20ms=100_0000;    //50M晶振T=20ns,计数1000000次即为20ms
reg cnt_full;
always @(posedge clk or negedge cr)
begin
    if(~cr) cnt_full<=1'b0;
    else if(cnt==_20ms) cnt_full<=1'b1;
    else cnt_full<=1'b0;
end

//保持按键输出状态(自锁)
always @(posedge clk or negedge cr)
begin
    if(~cr) key_out<=1'b1;
    else if(key_pressed) key_out<=~key_out;
end

endmodule

以上基本是原版copy下来的,当计数器满(20ms)后认为按键真实按下,置低电平并且按键松开后仍保持状态,类似于电气自锁,我的“暂停”按键使用的便是这个方案。但校时按键一般是按下校时,松开结束校时,所以需要一个没有自锁的,我根据这个方案改进了一个方案,基本思路就是将上面的三个状态复制了一遍用于检测上升沿(比较冗长,与主题无关不感兴趣请跳过):

  1. 取消自锁的改进方案
//仅当判定按键松开(key_loose=1)时才进行下降沿检测,当判定按键按下(key_pressed)才进行上升沿检测
//根据以下源码改进:https://blog.csdn.net/zhoutaopower/article/details/103180042

module keyscan(
    input clk,
    input cr,
    input key_in,
    output reg key_out
);

//上升沿/下降沿检测模块
reg key_in_0;           //按键抽样现态
reg key_in_1;           //按键上次抽样状态
always @(posedge clk or negedge cr)
begin
    if(~cr) begin
        key_in_0<=1'b1;
        key_in_1<=1'b1;
    end
    else begin
        key_in_0<=key_in;
        key_in_1<=key_in_0;
    end
end

//下降沿和上升沿的检测方案,记下
wire key_posedge;
wire key_negedge;
assign key_posedge=key_in_0 & (~key_in_1);      //上次检测为0,现态为1时key_posedge置1
assign key_negedge=key_in_1 & (~key_in_0);

//状态机
reg[2:0]state;           //三个状态
parameter IDLE = 3'b001;           
parameter SAMPLING = 3'b010;
parameter CONFIRM = 3'b100;
reg en_cnt;                 //计数使能
reg key_loose;              //按键松开
reg key_pressed;            //按键按下

always @(posedge clk or negedge cr)
begin
    if(~cr) begin
        state<=IDLE;
        en_cnt<=1'b0;
        key_loose<=1'b1;
        key_pressed<=1'b0;
    end
    else begin
        if(key_loose) begin          //按键松开,检测下降沿
            case(state)
                IDLE:begin
                    if(key_negedge) begin
                        state<=SAMPLING;
                        en_cnt<=1'b1;
                        key_pressed<=1'b0;
                        key_loose<=1'b1;
                    end
                    else begin
                        state<=IDLE;
                        en_cnt<=1'b0;
                        key_pressed<=1'b0;
                        key_loose<=1'b1;
                    end
                end
                SAMPLING:begin
                    if(key_posedge) begin
                        state <=IDLE;
                        en_cnt<=1'b0;
                        key_pressed<=1'b0;
                        key_loose<=1'b1;
                    end
                    else begin 
                        if(cnt_full) begin
                            state<=CONFIRM;
                            en_cnt<=1'b0;
                            key_pressed<=1'b0;
                            key_loose<=1'b1;
                        end
                        else begin
                            state<=SAMPLING;
                            en_cnt<=1'b1;
                            key_pressed<=1'b0;
                            key_loose<=1'b1;
                        end
                    end
                end
                CONFIRM:begin
                    state<=IDLE;
                    en_cnt<=1'b0;
                    key_pressed<=1'b1;
                    key_loose<=1'b0;
                end
                default:begin
                    state<=IDLE;
                    en_cnt<=1'b0;
                    key_pressed<=1'b0;
                    key_loose<=1'b1;
                end
            endcase
        end
        else if(key_pressed) begin        //按键按下,检测上升沿
            case(state)
                IDLE:begin
                    if(key_posedge) begin
                        state<=SAMPLING;
                        en_cnt<=1'b1;
                        key_pressed<=1'b1;
                        key_loose<=1'b0;
                    end
                    else begin
                        state<=IDLE;
                        en_cnt<=1'b0;
                        key_pressed<=1'b1;
                        key_loose<=1'b0;
                    end
                end
                SAMPLING:begin  
                    if(key_negedge) begin
                        state <=IDLE;
                        en_cnt<=1'b0;
                        key_pressed<=1'b1;
                        key_loose<=1'b0;
                    end
                    else begin
                        if(cnt_full) begin
                            state<=CONFIRM;
                            en_cnt<=1'b0;
                            key_pressed<=1'b1;
                            key_loose<=1'b0;
                        end
                        else begin
                            state<=SAMPLING;
                            en_cnt<=1'b1;
                            key_pressed<=1'b1;
                            key_loose<=1'b0;
                        end
                    end
                end
                CONFIRM:begin
                    state<=IDLE;
                    en_cnt<=1'b0;
                    key_pressed<=1'b0;
                    key_loose<=1'b1;
                end
                default:begin
                    state<=IDLE;
                    en_cnt<=1'b0;
                    key_pressed<=1'b1;
                    key_loose<=1'b0;
                end
            endcase
        end
	end
end

//Counter
reg [19:0]cnt;
always @(posedge clk or negedge cr)
begin
    if(~cr) cnt<=20'h00000;
    else if(en_cnt) cnt<=cnt+1;
    else cnt<=20'h00000;
end

//Counter_full logic
parameter _20ms=100_0000;    //50M晶振T=20ns,计数1000000次即为20ms
reg cnt_full;
always @(posedge clk or negedge cr)
begin
    if(~cr) cnt_full<=1'b0;
    else if(cnt==_20ms) cnt_full<=1'b1;
    else cnt_full<=1'b0;
end

always @(posedge clk or negedge cr)
begin
    if(~cr) key_out<=1'b0;
    else if(key_pressed) key_out<=1'b1;
    else if(key_loose) key_out<=1'b0;
end

endmodule

顶层模块

module display_clock(
    input cp,cr,key_1,
    input key_2,key_3,
    output [5:0]sel,
    output [7:0]seg
);

    wire clk_1;             //1Hz信号
    wire clk_1k;            //1kHz信号
    wire clk_5;             //5Hz调时专用信号
    wire key_min,key_sec;
    wire [7:0]hour;
    wire [7:0]min;
    wire [7:0]sec;

//引用模块
clk_div u0(.cr(cr),.clk_50M(cp),
           .clk_1k(clk_1k),.clk_5(clk_5),.clk_1(clk_1));
clock u1(.clk_1(clk_1),.clk_5(clk_5),
         .cr(cr),.en(key_en),
         .key_2(key_min),.key_3(key_sec),
         .hour(hour),.min(min),.sec(sec));
dig_display u2(.cp(clk_1k),.dig({hour,min,sec}),
               .sel(sel),.seg(seg));

//key_2、key_3不带自锁按键消抖
keyscan u3(.clk(cp),.cr(cr),.key_in(key_2),.key_out(key_min));
keyscan u4(.clk(cp),.cr(cr),.key_in(key_3),.key_out(key_sec));

//key_en带自锁
keyscan_sl u5(.clk(cp),.cr(cr),.key_in(key_1),.key_out(key_en));
endmodule

RTL结构图:

  1. cp:50MHz信号输入
  2. cr:复位信号输入(Key0)
  3. key_1:计数使能信号输入(Key1)
  4. key_2:分校时信号(Key2)
  5. key_3:时校时信号(Key3)
    RTL Viewer

以上,可以顺利在开发板上输出结果。但是!!存在一个令我感到诡异的

BUG

秒在59处可以正常进位,同时还会在49处进位,但正如上方仿真结果所示,整个clock的计数模块是没有问题的,并没有出现在49秒处异常进位的情况。如果排除clock模块的问题,其他模块感觉都不会影响计数系统。还是希望有大神可以发现问题出在哪里指点一下,谢谢!

  • 12
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
按键消抖是指在数字电路中,通过一定的逻辑设计手段,解决按键在按下或释放可能产生的抖动问题。抖动是指在按键按下或释放的瞬间,由于机械接触或其他因素的影响,按键可能会产生不稳定的信号变化,导致系统误触发或无法正确识别按键的状态。 在FPGA的VerilogHDL语言中,可以使用计数器来实现按键消抖一个常见的方法是使用两个计数器来判断按键持续的间,并根据持续间判断按键的状态。例如,在设计中可以设置一个计数器,当检测到按键按下,计数器开始计数,同一个计数器判断计数值是否达到一定阈值。如果计数值达到阈值,表示按键已稳定按下,可以进行相应的处理;如果计数值没有达到阈值,表示按键还在抖动状态,需要继续等待。 当然,也可以使用状态机来实现按键消抖。状态机可以根据按键的状态变化进行状态转移,从而判断按键的稳定状态。例如,可以定义状态机的初始状态为按键待机状态,当检测到按键按下,状态机切换到按下滤波状态,并等待一段间。如果在这段间内没有检测到按键的状态变化,表示按键已稳定按下,可以进行相应的处理;如果在这段间内检测到按键的状态变化,表示按键还在抖动状态,状态机回到初始态继续等待。 总之,按键消抖是通过逻辑设计手段解决按键抖动问题的一种方法,在FPGA的VerilogHDL语言中可以使用计数器或状态机来实现按键消抖。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值