使用Verilog实现秒表计时器功能。

要求:

  • 数码管从0-20s计时,20s时自动归零
  • 动态驱动数码管
  • 有暂停按钮和归零按钮
  • 有按键消抖
  • 编写仿真文件,得到仿真波形图

原理:

  • 要实现计时功能,需要用到时钟信号,在时钟信号上升时计数器加一
  • 动态驱动数码管是指开发板上的数码管为共阴极,一次只可使一个数码管导通,但通过时钟信号可以让不同数码管不停切换显示,只要其切换频率大于人眼可见频率,即可视为多个数码管同时显示不同数字。
  • 使用100Hz频率时钟,若要使计时器每1s加一,需要对时钟信号分频,将100Hz信号分为1Hz信号。

CODE

采用多模块设计

  • count.v(顶层文件)
/*
工程名:count
文件名:count.v
*/
module count(CLK,RST,btn,seg,cat);
   input CLK,RST,btn;
   wire [4:0] Q;	//计数器
   wire [3:0] Q0;	//计数器个位
   wire [3:0] Q1;	//计数器十位
   output [6:0] seg;	//数码管
   output [7:0]cat;	//数码管编号
   reg [4:0] Q_TMP = 0;	//计数器前置
   reg pulse = 0;	//暂停信号
   wire btn_debounce;	//消抖后信号
   reg clk_out = 0;	//1HZ分频时钟
   reg [5:0] counter = 0; // 定义时钟计数器,用于计数时钟周期

   assign Q = Q_TMP;	//Q始终等于Q_TMP
   
   always @(posedge CLK) begin
   	counter <= counter + 1; // 时钟计数器加1
   	if (counter == 49) begin // 当时钟计数器达到49时,输出一个时钟周期
       clk_out <= ~clk_out; // 取反输出时钟信号
       counter <= 0; // 时钟计数器清零
   	end
   end
   
   always @(posedge clk_out or posedge RST) begin	//计数器控制
   	if (RST)	Q_TMP = 0;
   	else if (Q_TMP==19) 	Q_TMP = 0;
   	else if(pulse)	Q_TMP <= Q_TMP;	//pulse为1时暂停
   	else Q_TMP <= Q_TMP + 1;	//pulse为0时不暂停
   end

   always @(posedge btn_debounce) begin	//暂停控制
   	pulse <= ~pulse;	//按下按钮将使暂停控制器翻转;
   end

   switch v2(	//拆分器,将两位数Q拆分为个位Q0和十位Q1
   .Q(Q),
   .OQ0(Q0),
   .OQ1(Q1)
   );

   seg7 v1(	//动态驱动数码管
   .A0(Q0),
   .A1(Q1),
   .Y(seg),
   .clk(CLK),
   .cat(cat)
   );

   key_debounce v3(	//按键消抖
   .clk(CLK),
   .i_key(btn),
   .o_key(btn_debounce)
   );
endmodule
  • switch.v
/*
工程名:count
文件名:switch.v
 */
module switch(Q,OQ0,OQ1);	//拆分模块
	input [3:0]Q;	//输入两位数
	output reg[3:0]OQ0;	//个位数字
	output reg[3:0]OQ1;	//十位数字
	
	always@(*) begin
		if(Q<=9) begin
			OQ0 = Q;
			OQ1 = 0;
		end
		else begin
			OQ0 = Q - 10;
			OQ1 = 1;
		end
	end
endmodule
  • seg7.v
/*
工程名:count
文件名:seg7.v
 */
module seg7(A0,A1,Y,clk,cat);	//动态驱动数码管(当前驱动两位)
	input[3:0] A0;	//个位
	input [3:0]A1;	//十位
	output reg[7:0] cat;	//数码管使能端(置0启动)
	input clk;	//时钟信号
	output[6:0] Y;	//单个数码管信号
	reg [6:0] Y;
	always@(*)begin	//由于只用驱动两位,故只需使用时钟信号的上升沿和下降沿即可实现动态驱动(驱动三位及以上需增加中介reg变量)
		if(clk) begin
		case(A0)
		4'b0000 : Y=7'b1111110;//0
		4'b0001 : Y=7'b0110000;//1
		4'b0010 : Y=7'b1101101;//2
		4'b0011 : Y=7'b1111001;//3
		4'b0100 : Y=7'b0110011;//4
		4'b0101 : Y=7'b1011011;//5
		4'b0110 : Y=7'b1011111;//6
		4'b0111 : Y=7'b1110000;//7
		4'b1000 : Y=7'b1111111;//8
		4'b1001 : Y=7'b1111011;//9
		default : Y=7'b0000000;
		endcase
		cat = 8'b11111110;	//数码管0使能(个位)
		end
		else if(~clk) begin
			case(A1)
		4'b0000 : Y=7'b1111110;//0
		4'b0001 : Y=7'b0110000;//1
		4'b0010 : Y=7'b1101101;//2
		4'b0011 : Y=7'b1111001;//3
		4'b0100 : Y=7'b0110011;//4
		4'b0101 : Y=7'b1011011;//5
		4'b0110 : Y=7'b1011111;//6
		4'b0111 : Y=7'b1110000;//7
		4'b1000 : Y=7'b1111111;//8
		4'b1001 : Y=7'b1111011;//9
		default : Y=7'b0000000;
		endcase
		cat = 8'b11111101;	//数码管1使能(十位)
		end
	end
endmodule
  • key_debounce.v
/*
工程名:count
文件名:key_debounce.v
 */
module key_debounce(input clk,  i_key,output  o_key );	//按键消抖
	reg r_key_buf1, r_key_buf2;
	
	always@(negedge clk)begin
		r_key_buf2 <= r_key_buf1;
		r_key_buf1 <= i_key;
	end
	
	assign o_key = clk & r_key_buf1 & (~r_key_buf2);
endmodule

仿真

  • 仿真文件
`timescale 1ms / 1ms

module count_tb;

reg clk;
reg btn0;
reg reset;
wire [6:0] seg;
wire [7:0] cat;

// 实例化被测试模块
count uut (
	 .CLK(clk),
	 .RST(reset),
	 .btn(btn0),
	 .seg(seg),
	 .cat(cat)
);

	always#5 clk = ~clk;  // 100Hz时钟

// 初始化和模拟输入
initial begin
    // 初始化
	 clk = 0;
    btn0 = 0;
	 reset = 0;
	 #3000;
    reset = 1;
	 #20
    reset = 0;
	 #4000
	 
    // 模拟按钮按下
    #10;
    btn0 = 1;
    #20;
    btn0 = 0;//pulse

    #3000;
	 
	 #10 btn0 = 1;
	 #20 btn0 = 0;//continue

	 #3000;
	 
	 #10 btn0 = 1;
	 #40 btn0 = 0;//pulse
	 #3000;
    $stop;
end
endmodule
  • 仿真波形

    可以看出,已完美实现计时器功能,拥有暂停、归零功能,按钮已进行消抖。