EDA数字钟(二)

文章目录

  • 前言
  • 一、设计内容
  • 二、模块结构
  • 三、代码编写
    • 1.顶层模块Digclk
    • 2.消抖模块Filter
    • 3.移位输入模块Set_time
    • 4.计数模块Count
    • 5.计数器分频模块Div_cnt
    • 6.计时模块Time_cnt
    • 7.整点报时模块H_alarm
    • 8.扫描模块Scan
    • 9.译码模块Convert
  • 四、测试文件
  • 五、波形仿真
  • 总结


前言

重写EDA数字钟,尽量保持原有功能不变,修改各模块之间的关系以及模块内部逻辑,使程序更加简洁、符合电路规范。


一、设计内容

设计功能与实现方式和上一篇保持一致:
1.具有“秒”、“分”、“时”计时的功能,小时计数器按24小时制;
2.具有校时功能,能对“分”和“时”进行调整;
3.具有手动输入设置定时闹钟的功能,亮1分钟;
4.可实现时钟复位功能:00:00:00;
5.报整点:几点亮几下。

二、模块结构

主要有两个比较大的改动:
(1)将n进制计数器模块改为同步置零的无进位计数器;
(2)增加了消抖模块,使按扭输入更加稳定。
EDA数字钟(二)_第1张图片

三、代码编写

1.顶层模块Digclk

按键的优先级从上到下递减
移位、增加通过消抖模块输入
复位与置数按键均为异步
移位与增加按键用于校时与闹钟数据的输入
拨码用于控制显示内容(校时/闹钟/时分/分秒/不显示)
显示拨码优先级从上到下递减(不包括计时使能拨码)
由于只有四个数码管,所以时分/分秒分开显示
秒计数用一个LED显示
另用2个LED代表整点报时和定时闹钟

module Digclk(
    input clk,//20ns脉冲
    input rst_n,//复位按键BTN0
    input set,//置数按键BTN1
    input move,//移位按键BTN2
    input inc,//增加按键BTN3
    input set_en,//校时拨码SW7
    input alm_en,//闹钟拨码SW6
    input h_m_en,//时分显示拨码SW5
    input m_s_en,//分秒显示拨码SW4
    input cnt_en,//计时使能拨码SW3
    output [3:0] loc,//数码管位置
    output [7:0] pin,//数码管引脚电平
    output h_alm,//整点报时LD2
    output s_alm,//定时闹钟LD1
    output sec);//秒计时LD0

    //消抖部分
    wire move_f, inc_f;//消抖后的按钮信号
    Filter u0_filter(
        .clk(clk),
        .rst_n(rst_n),
        .botton(move),
        .botton_f(move_f));
    Filter u1_filter(
        .clk(clk),
        .rst_n(rst_n),
        .botton(inc),
        .botton_f(inc_f));//消抖模块

    //校时部分
    reg [5:0] set_n;//校时信号
    wire [15:0] set_num;//校时部分数据
    reg [23:0] set_time;//校时整体数据
    wire [3:0] set_loc;//指针位置
    Set_time u0_set_time(
        .rst_n(rst_n),
        .en(set_en),
        .move(move_f),
        .inc(inc_f),
        .set_num(set_num),
        .set_loc(set_loc));//移位输入模块
    always @(set, set_en, alm_en, h_m_en, m_s_en) begin//判断所需修改的位
        if(set) set_n = 6'b000000;
        else if(set_en || alm_en) set_n = 6'b000000;
        else if(h_m_en) set_n = 6'b111100;
        else if(m_s_en) set_n = 6'b001111;
        else set_n = 6'b000000;
    end
    always @(h_m_en, set_num) begin//用于和计时部分连接
        if(h_m_en)
            set_time = {set_num, 8'b00000000};
        else set_time = {8'b00000000, set_num};
    end

    //计时部分
    wire clk_cnt;//计时时钟
    wire [23:0] cnt_num;//计时数据
    wire [5:0] co;//进位信号
    Div_cnt u_div_cnt(
        .clk(clk),
        .rst_n(rst_n),
        .clk_cnt(clk_cnt));//分频模块
    Time_cnt u_time_cnt(
        .clk(clk_cnt),
        .rst_n(rst_n),
        .en(cnt_en),
        .set_n(set_n),
        .set_num(set_time),
        .cnt(cnt_num),
        .co(co));//计时模块
    assign sec = cnt_num % 2;

    //整点报时
    H_alarm u_h_alarm(
        .clk(clk_cnt),
        .rst_n(rst_n),
        .en(co[3] & co[2] & co[1] & co[0]),//同步进位使能需要将前级所有进位相与
        .num(cnt_num[23:16]),
        .led_out(h_alm));//整点报时模块

    //闹钟部分
    wire [15:0] alm_num;//闹钟数据
    wire [3:0] alm_loc;//指针位置
    Set_time u1_set_time(
        .rst_n(rst_n),
        .en(!set_en && alm_en),
        .move(move_f),
        .inc(inc_f),
        .set_num(alm_num),
        .set_loc(alm_loc));
    assign s_alm = (cnt_num[23:8] == alm_num) && cnt_en;//闹钟响一分钟

    //显示部分
    Scan u_scan(.clk(clk), .rst_n(rst_n), .loc(loc));//扫描模块
    reg [15:0] show_num;//待取数字
    reg [3:0] show_dot;//小数点代表指针位置
    always @(*) begin//取数(校时/闹钟/计时)
        if(set_en) begin
            show_num = set_num;
            show_dot = set_loc;
        end
        else if(alm_en) begin
            show_num = alm_num;
            show_dot = alm_loc;
        end
        else if(h_m_en) begin 
            show_num = cnt_num[23:8];
            show_dot = 4'b0000;
        end
        else if(m_s_en) begin
            show_num = cnt_num[15:0];
            show_dot = 4'b0000;
        end
        else begin
            show_num = 0;
            show_dot = 4'b0000;
        end            
    end
    reg [4:0] con_num;//待译码数字,最后一位为小数点
    always @(loc, show_num, show_dot) begin//取数(41case(loc)
            4'b0111: con_num = {show_num[15:12], show_dot[3]};
            4'b1011: con_num = {show_num[11:8], show_dot[2]};
            4'b1101: con_num = {show_num[7:4], show_dot[1]};
            4'b1110: con_num = {show_num[3:0], show_dot[0]};
            default: con_num = 5'bxxxxx;//避免锁存
        endcase
    end
    Convert u_convert(.num(con_num), .pin(pin));//译码模块
    
endmodule

2.消抖模块Filter

按下按键(下跳沿)一段时间后发送一个时钟周期的负脉冲
此期间不接收按键信号
延时500ms
counter_end = 50M * 延时时间 / 2 - 1

module Filter(
    input clk,
    input rst_n,
    input botton,
    output botton_f);
    
    //parameter CNT_END = 12 499 999;//下载时延时500ms
    parameter CNT_END = 4;//仿真时延时100ns
    reg [23:0] counter;
    reg dly1, dly2;//延时标志位

    //dly1输出
    always @(posedge clk, negedge rst_n) begin
        if(!rst_n) dly1 <= 0;
        else if(!botton) dly1 <= 1;
        else if(counter >= CNT_END) dly1 <= 0;
    end

    //延时计数
    always @(posedge clk, negedge rst_n) begin
        if(!rst_n) counter <= 0;
        else if(dly1) counter <= counter + 1;
        else counter <= 0;
    end

    //dly2输出
    always @(posedge clk, negedge rst_n) begin
        if(!rst_n) dly2 <= 0;
        else dly2 <= dly1;
    end

    assign botton_f = dly1 || !dly2;//控制输出一个时钟的负脉冲

endmodule

3.移位输入模块Set_time

由4个无进位计数器子模块储存输入内容
set_loc用于记录操作哪一位计数器
set_num用于记录校时的值
apply按键每按下一次,指针向右移一位;
inc按键每按下一次,当前计数器获得一个cp,即值加1

module Set_time(
    input rst_n,
    input en,
    input move,
    input inc,
    output [15:0] set_num,
    output [3:0] set_loc);

    reg [3:0]loc;//指针位置
    //循环移位,时序逻辑
    always @(negedge rst_n, negedge move) begin
        if(!rst_n) loc <= 4'b1000;
        else if(!move && en) loc <= {loc[0], loc[3:1]};
    end
    assign set_loc = loc;

    //计数器子模块
    Count u0_count(
        .clk(inc),
        .rst_n(rst_n),
        .en(loc[0] && en),
        .set_0(set_num[3:0]==9),
        .set_n(1'b0),
        .set_num(4'b0000),
        .cnt(set_num[3:0]));
    Count u1_count(
        .clk(inc),
        .rst_n(rst_n),
        .en(loc[1] && en),
        .set_0(set_num[7:4]==9),
        .set_n(1'b0),
        .set_num(4'b0000),
        .cnt(set_num[7:4]));
    Count u2_count(
        .clk(inc),
        .rst_n(rst_n),
        .en(loc[2] && en),
        .set_0(set_num[11:8]==9),
        .set_n(1'b0),
        .set_num(4'b0000),
        .cnt(set_num[11:8]));
    Count u3_count(
        .clk(inc),
        .rst_n(rst_n),
        .en(loc[3] && en),
        .set_0(set_num[15:12]==9),
        .set_n(1'b0),
        .set_num(4'b0000),
        .cnt(set_num[15:12]));

endmodule

4.计数模块Count

16进制计数器
同步置零在外部电路完成
包含异步置数功能

module Count(
    input clk,//时钟
    input rst_n,//异步复位
    input en,//使能
    input set_0,//同步置0
    input set_n,//异步置数
    input [3:0] set_num,//置数内容
    output reg [3:0] cnt);//计数值

    always @(posedge clk, negedge rst_n, posedge set_n) begin
        if(!rst_n) cnt <= 0;
        else if(set_n) cnt <= set_num;
        else if(set_0 && en) cnt <= 0;
        else if(en) cnt <= cnt + 1;
    end

endmodule

5.计数器分频模块Div_cnt

系统时钟50MHz
仿真时输出1MHz
下载时输出1Hz
上升沿触发计数,保证reset之后的第一次计数
CNT_END=50M/(2x计数频率)

module Div_cnt (
    input clk,
    input rst_n,
    output reg clk_cnt);

    reg [24:0] counter;
    parameter [24:0] CNT_END = 24;//仿真时计数25
    //parameter [24:0] CNT_END = 24 999 999;//下载时计数25M

    always @(posedge clk, negedge rst_n) begin
        if (!rst_n) begin
            counter <= 0;
            clk_cnt <= 1;//上升沿触发计数
        end
        else if (counter == CNT_END) begin
            counter <= 0;
            clk_cnt <= !clk_cnt;
        end
        else counter <= counter + 1;
    end

endmodule

6.计时模块Time_cnt

6个无进位计数器表示时、分、秒
计数器进位通过外部连线完成
含异步清零、置数功能
为防止校时值超过进制,进位信号用>=代替==

module Time_cnt(
    input clk,
    input rst_n,
    input en,
    input [5:0] set_n,
    input [23:0] set_num,
    output [23:0] cnt,
    output [5:0] co);

    wire [5:0] enable;
    //计数器子模块
    //秒个位
    Count u0_count(
        .clk(clk),
        .rst_n(rst_n),
        .en(enable[0]),
        .set_0(co[0]),
        .set_n(set_n[0]),
        .set_num(set_num[3:0]),
        .cnt(cnt[3:0]));
    assign enable[0] = en;
    assign co[0] = (cnt[3:0] >= 9);
    //秒十位
    Count u1_count(
        .clk(clk),
        .rst_n(rst_n),
        .en(enable[1]),
        .set_0(co[1]),
        .set_n(set_n[1]),
        .set_num(set_num[7:4]),
        .cnt(cnt[7:4]));
    assign enable[1] = enable[0] && co[0];
    assign co[1] = (cnt[7:4] >= 5);
    //分个位
    Count u2_count(
        .clk(clk),
        .rst_n(rst_n),
        .en(enable[2]),
        .set_0(co[2]),
        .set_n(set_n[2]),
        .set_num(set_num[11:8]),
        .cnt(cnt[11:8]));
    assign enable[2] = enable[1] && co[1];
    assign co[2] = (cnt[11:8] >= 9);
    //分十位
    Count u3_count(
        .clk(clk),
        .rst_n(rst_n),
        .en(enable[3]),
        .set_0(co[3]),
        .set_n(set_n[3]),
        .set_num(set_num[15:12]),
        .cnt(cnt[15:12]));
    assign enable[3] = enable[2] && co[2];
    assign co[3] = (cnt[15:12] >= 5);
    //时个位
    Count u4_count(
        .clk(clk),
        .rst_n(rst_n),
        .en(enable[4]),
        .set_0(co[4]),
        .set_n(set_n[4]),
        .set_num(set_num[19:16]),
        .cnt(cnt[19:16]));
    assign enable[4] = enable[3] && co[3];
    assign co[4] = (cnt[19:16] >= 9) || co[5];
    //时十位
    Count u5_count(
        .clk(clk),
        .rst_n(rst_n),
        .en(enable[5]),
        .set_0(co[5]),
        .set_n(set_n[5]),
        .set_num(set_num[23:20]),
        .cnt(cnt[23:20]));
    assign enable[5] = enable[4] && co[4];
    assign co[5] = (cnt[23:20] >= 2) && (cnt[19:16] >= 3);

endmodule

7.整点报时模块H_alarm

几点LED闪几下
0点闪24下
共阳极LED,低电平点亮
参考消抖模块,24时flag在第47次后拉低,而counter会计到48
cnt_end = 2 * (num[7:4] * 10 + num[3:0] + 1) - 1 - 1;

module H_alarm(
    input clk,
    input rst_n,
    input en,
    input [7:0] num,
    output led_out);

    reg [5:0] counter;//最多计到47
    reg [5:0] cnt_end;//将BCD码转为十进制
    reg flag;//运行标志位

    always @(posedge clk, negedge rst_n) begin//参考消抖模块
        if(!rst_n) begin
            flag <= 0;
            cnt_end <= 0;
        end
        else if(en) begin
            flag <= 1;
            cnt_end <= 2 * (num[7:4] * 10 + num[3:0]);
        end
        else if(counter >= cnt_end) begin
            flag <= 0;
            cnt_end <= 0;
        end
    end

    always @(posedge clk, negedge rst_n) begin
        if(!rst_n) counter <= 0;
        else if(flag) counter <= counter + 1;
        else counter <= 0;
    end

    assign led_out = counter % 2;

endmodule

8.扫描模块Scan

扫描分频,系统时钟50MHz
仿真时2.5MHz(每计两次数扫描5轮)
下载时100Hz(扫描一轮100Hz)
数码管选择,为0表示被选中
电平触发扫描
CNT_END=50M/(扫描数x扫描频率)-1

module Scan(
    input clk,
    input rst_n,
    output reg [3:0] loc);
    //扫描分频
    reg [16:0] counter = 0;//最多计到124 999
    reg [1:0] flag = 0;//选择标志
    parameter [16:0] CNT_END = 4;//仿真时计数5
    //parameter [16:0] CNT_END = 124 999;//下载时计数125 000

    always @(posedge clk, negedge rst_n) begin
        if (!rst_n) begin
            counter <= 0;
            flag <= 0;
        end
        if (counter == CNT_END) begin
            counter <= 0;
            flag <= flag + 1;
        end
        else counter <= counter + 1;
    end
    //数码管选择
    always @(flag) begin
        case(flag)
            0: loc = 4'b1110;
            1: loc = 4'b1101;
            2: loc = 4'b1011;
            3: loc = 4'b0111;
            default loc = 4'bxxxx;
        endcase
    end

endmodule

9.译码模块Convert

将取到的数转换为数码管对应的电平
组合逻辑
低电平有效

module Convert(
    input [4:0] num,
    output reg [7:0] pin);

    always @(num) begin
	    case(num[4:1])
			0: pin[7:1] = 8'b0000001;
			1: pin[7:1] = 8'b1001111;
			2: pin[7:1] = 8'b0010010;
			3: pin[7:1] = 8'b0000110;
			4: pin[7:1] = 8'b1001100;
			5: pin[7:1] = 8'b0100100;
			6: pin[7:1] = 8'b0100000;
			7: pin[7:1] = 8'b0001111;
			8: pin[7:1] = 8'b0000000;
			9: pin[7:1] = 8'b0000100;
			default: pin = 8'b0110000;//其他状态显示E
        endcase
        pin[0] = ~num[0];//小数点
	end

endmodule

四、测试文件

采用function与repeat组合的方式模拟按键,简化了激励的施加。

`timescale 1ns/1ns
module Test_Top;
	// Inputs
	reg clk;
	reg rst_n;
	reg set;
	reg move;
	reg inc;
	reg set_en;
	reg alm_en;
	reg h_m_en;
	reg m_s_en;
	reg cnt_en;

	// Outputs
	wire [3:0] loc;
	wire [7:0] pin;
	wire h_alm;
	wire s_alm;
	wire sec;

    //模拟按键按下
    task SET;
        begin
	        #200 set = 0;
            #50 set = 1;
        end
    endtask
    task MOVE;
        begin
	        #200 move = 0;
            #50 move = 1;
        end
    endtask
    task INC;
        begin
	        #200 inc = 0;
            #50 inc = 1;
        end
    endtask

	// Instantiate the Unit Under Test (UUT)
	Digclk uut (
		.clk(clk), 
		.rst_n(rst_n), 	
		.set(set), 
		.move(move), 
		.inc(inc), 
		.set_en(set_en), 
		.alm_en(alm_en), 
		.h_m_en(h_m_en), 
		.m_s_en(m_s_en),
		.cnt_en(cnt_en),
		.loc(loc),
		.pin(pin),
		.h_alm(h_alm),
		.s_alm(s_alm),
		.sec(sec)
	);
	
	//时钟周期20ns
	parameter PERIOD = 20;
	
	//时钟信号
    initial clk = 0;
	always #(PERIOD/2) clk = ~clk;
    
    //初始化
	initial begin
		// Initialize Inputs
		rst_n = 1;
		set = 1;
		move = 1;
		inc = 1;
		set_en = 0;
		alm_en = 0;
		h_m_en = 0;
		m_s_en = 0;
		cnt_en = 0;
		
		// Wait 200 ns for global rst_n to finish
		#100 rst_n = 0;
        #100 rst_n = 1;
		
		// Add stimulus here
//////////////////////////////////////////////使能拨码测试
		//开始计数
		#100 cnt_en = 1;
		//显示分秒
		#1000 m_s_en = 1;
		//显示时分
		#1000 h_m_en = 1;
		//闹钟模式
		#100 alm_en = 1;
//////////////////////////////////////////////闹钟测试
		//闹钟置数1200,闹钟指针1000
		INC;//1
		MOVE;
		repeat(2) INC;//2
//////////////////////////////////////////////校时测试
		//时分置数1100,校时指针1000
        //置数模式
		#100 set_en = 1;
        INC;//1
        MOVE;
        INC;//1
		#100 set_en = 0;
        #100 alm_en = 0;
		SET;
		//分秒置数5949,校时指针0100
		#100 set_en = 1;
		repeat(3) MOVE;
		repeat(4) INC;//5
		MOVE;
		repeat(8) INC;//9
		MOVE;
		repeat(4) INC;//4
		MOVE;			
		repeat(9) INC;//9
		#100 set_en = 0;
        #100 h_m_en = 0;
		SET;
//////////////////////////////////////////////计时使能拨码测试
		//停止计时
		#100000 cnt_en = 0;
//////////////////////////////////////////////清零测试
		//清零
		#1000 rst_n = 0;
        #100 rst_n = 1;
//////////////////////////////////////////////进位测试
		//分秒置数5949,校时指针1000
        #100 set_en = 1;
		repeat(5) INC;//5
		MOVE;
		repeat(9) INC;//9
		MOVE;
		repeat(4) INC;//4
		MOVE;			
		repeat(9) INC;//9
        #100 set_en = 0;
		SET;
		//时分置数2359,校时指针0001
		#100 set_en = 1;
        MOVE;
		repeat(7) INC;//2
		MOVE;
		repeat(4) INC;//3
		MOVE;
		INC;//5
		#100 set_en = 0;
        #100 m_s_en = 0;
		INC;//空加(当set_en为0时,inc按键不应起作用)
        SET;//空置数(当h_m_en与m_s_en为0时,set按键不应起作用)
		#100 h_m_en = 1;
        SET;//时分置数
		//恢复计时
		#100 cnt_en = 1;
//////////////////////////////////////////////防抖测试
        #100 set_en = 1;//校时指针0010
        #100 inc = 0;//触发
        #10 inc = 1;
        #30 inc = 0;//忽略
        #50 inc = 1;
        #70 inc = 0;//触发
        #90 inc = 1;
	end

endmodule

五、波形仿真

变量名、测试内容与上一篇基本相似,不进行过多叙述。
1、变量名称说明
变量名称有所不同,但位置保持完全一致。
EDA数字钟(二)_第2张图片
2、使能拨码测试
具体解释见上一篇,下同。
EDA数字钟(二)_第3张图片
3、闹钟置数测试
此处闹钟直接置数,不需要SET,和上一篇有所不同(因为我重写的时候忘了原来的逻辑)。
EDA数字钟(二)_第4张图片
4、时钟置数测试
注意:FPGA的按键是低电平有效(虽然我不知道原来的程序为什么也能用)。
EDA数字钟(二)_第5张图片
5、清零测试
EDA数字钟(二)_第6张图片
6、计时使能拨码测试
EDA数字钟(二)_第7张图片
7、进位测试
EDA数字钟(二)_第8张图片
8、防抖测试
EDA数字钟(二)_第9张图片


总结

修改了部分的变量名称,但顶层模块的输入和输出基本保持不变,由于程序只进行了简单的仿真,不排除在实际使用中可能会出现问题。
I might be just begining,
I might be near the end.

你可能感兴趣的:(Verilog,fpga开发)