数字逻辑与计算机设计实验 FPGA数字钟(Verilog)

改自wolai笔记FPGA数字钟(Verilog)

项目源代码已上传至github:houhuawei23/DDCA_2022

目录

  • 实验 9 FPGA数字钟

        • 实验分析:

        • 实现思路:

        • 硬件支持:

      • 硬件描述语言代码编写:

        • 1 顶层模块

        • 2 时钟分频,(正/倒)计时器模块

        • 3 输入处理模块in_out.v

        • 5 24小时时钟,计时,秒表模块

        • 6 闹钟

        • 7 时间设置

实验 9 FPGA数字钟

请使用SystemVerilog/Verilog实现一个数字钟。
要求:
(1)能够显示时分秒;
(2)能够设置开始时间;
(3)使用你自己的7段数码管显示译码电路实现;
(4)可以使用动态显示方法实现;
(5)依据实现的其他附加功能,酌情加分:秒表、倒计时、闹钟、…
(6)需要在Basys3 FPGA开发板上实现,并通过验收

实验分析:

该实验主要考察对使用硬件设计语言(HDL)进行编程设计,锻炼硬件设计能力。

实现思路:

采用模块化设计的思想,将整个数字钟分解为若干功能模块:

  1. 端口映射:约束文件(代码端口映射到硬件端口)

  2. 处理按键输入的io模块:消抖,判断按下

  3. 特定功能模块:时钟分频,24小时时钟,正/倒计时,秒表,闹钟,秒表,时间设置

  4. 显示模块:译码,选择输出

硬件支持:

FPGA开发板:Artix-7 Basys3 from Xilinx

学习步骤:

  1. 阅读手册,了解结构与功能:
    Basys 3 Reference Manual - Digilent Reference

  2. 若需要更详细的信息,则阅读所用模块的原理图
    Basys 3 Schematic - Digilent

数字逻辑与计算机设计实验 FPGA数字钟(Verilog)_第1张图片
数字逻辑与计算机设计实验 FPGA数字钟(Verilog)_第2张图片
数字逻辑与计算机设计实验 FPGA数字钟(Verilog)_第3张图片

硬件描述语言代码编写:

1 顶层模块

输入:板载时钟CLK,各种按键

输出:按键LED灯信号,数码管12位信号(4位用于位置选择,7位用于7段数码管显示,1位用于小数点显示)

内部:

1 分线接线,设置缓冲区等

2 例化各种子模块

3 选择数码管输出

  • digital_clk.v

    module digital_clk(
            input CLK,
            input BTNU, BTND, BTNL, BTNR, BTNC,
            input SW0, SW1, SW2, SW3, SW4, SW5, SW6,
            input SW13, SW14, SW15,
            output reg[11:0] display_out,
            output LD0, LD1, LD2, LD3, LD4, LD5, LD6,
            output LD13, LD14, LD15
        );  
    //时钟分频
    wire ms_clk;
    wire clock_clk; //1Hz  
    wire scan_clk; //1kHZ  
    wire Reset, reset, set;
    //按键接线
    wire btnu_down, btnd_down, btnl_down, btnr_down, btnc_down;
    wire sw0, sw1, sw2, sw3, sw4, sw5, sw6;
    wire sw13, sw14, sw15;
    //各模块数码管输出区
    wire [11:0]clock_seg ; 
    wire [11:0]countup_seg;
    wire [11:0]countdown_seg;
    wire [11:0]alarm_seg;
    wire [11:0]reset_seg;
    wire [11:0]sw_seg;
    //时间设置接线
    wire [3:0] sethour1;
    wire [3:0] sethour2;
    wire [3:0] setminute1;
    wire [3:0] setminute2;
    wire [3:0] setsecond1;
    wire [3:0] setsecond2;
    
    wire [3:0] hour1;  
    wire [3:0] hour2;  
    wire [3:0] minute1;
    wire [3:0] minute2;
    wire [3:0] second1;
    wire [3:0] second2;
    //wire [5:0] setbit;
    wire beep;
    assign Reset = sw0;
    assign reset = sw13;
    assign set = sw1;
    //LD
    assign LD0 = sw0;
    assign LD1 = sw1;
    assign LD2 = sw2;
    assign LD3 = sw3;
    assign LD4 = sw4;
    assign LD5 = sw5;
    assign LD6 = sw6;
    assign LD13 =sw13;
    assign LD14 = sw14;
    assign LD15 = sw15;
    
    div_n32p #(42950) myscan_clk( //输出1000Hz扫描时钟
            .CLK(CLK), 
            .reset(Reset),
            .clk(scan_clk)
    );
    div_n32p #(43) myclock_clk( //输出1Hz计时时钟
            .CLK(CLK), 
            .reset(Reset),
            .clk(clock_clk)
    );
    div_n32p #(2577) myms_clk(
            .CLK(CLK), 
            .reset(Reset),
            .clk(ms_clk)
    );
    
    // io模块  
    in_out myio(
        .CLK(CLK), 
        .BTNU(BTNU), 
        .BTND(BTND), 
        .BTNL(BTNL), 
        .BTNR(BTNR), 
        .BTNC(BTNC),
        .SW0(SW0), 
        .SW1(SW1), 
        .SW2(SW2), 
        .SW3(SW3),
        .SW4(SW4), 
        .SW5(SW5),
        .SW6(SW6),
        .SW13(SW13),
        .SW14(SW14),
        .SW15(SW15),
        .btnu_down(btnu_down), 
        .btnd_down(btnd_down), 
        .btnl_down(btnl_down), 
        .btnr_down(btnr_down), 
        .btnc_down(btnc_down),
        .sw0(sw0), 
        .sw1(sw1), 
        .sw2(sw2), 
        .sw3(sw3),
        .sw4(sw4), 
        .sw5(sw5),
        .sw6(sw6),
        .sw13(sw13),
        .sw14(sw14),
        .sw15(sw15)
    );
    // 时钟模块
    clock myclock(
        .CLK(CLK),
        .clock_clk(clock_clk), //1Hz  
        .scan_clk(scan_clk), //1kHZ   
        .Reset(Reset), 
        .reset(reset && sw2),
        .set(set && sw2),
        .start_pause(btnc_down && sw2),
        .display_h(sw15),
    
        .sethour1(sethour1),  
        .sethour2(sethour2),  
        .setminute1(setminute1),
        .setminute2(setminute2),
        .setsecond1(setsecond1),
        .setsecond2(setsecond2),
        
        .hour1  (hour1),
        .hour2  (hour2),
        .minute1(minute1),
        .minute2(minute2),
        .second1(second1),
        .second2(second2),
    
        .clock_seg(clock_seg[7:0]),//8
        .seln(clock_seg[11:8])//4
    );
    //正计时模块
    countup mycountup(
        .CLK(CLK),
        .clock_clk(clock_clk), //1Hz
        .scan_clk(scan_clk), //1kHZ
        .Reset(Reset), 
        .reset(reset && sw3),
        .set(set && sw3),
        .display_h(sw15),
        .start_pause(btnc_down && sw3),
        .sethour1(sethour1),  
        .sethour2(sethour2),  
        .setminute1(setminute1),
        .setminute2(setminute2),
        .setsecond1(setsecond1),
        .setsecond2(setsecond2),
        
        .clock_seg(countup_seg[7:0]),//8
        .seln(countup_seg[11:8])//4
    );
    //倒计时模块
    countdown mycountdown(
        .CLK(CLK),  
        .clock_clk(clock_clk), //1Hz
        .scan_clk(scan_clk), //1kHZ
        .Reset(Reset), 
        .reset(reset && sw4),
        .set(set && sw4),
        .display_h(sw15),
        .start_pause(btnc_down && sw4),
        
        .sethour1(sethour1),  
        .sethour2(sethour2),  
        .setminute1(setminute1),
        .setminute2(setminute2),
        .setsecond1(setsecond1),
        .setsecond2(setsecond2),
        
        .clock_seg(countdown_seg[7:0]),//8
        .seln(countdown_seg[11:8])//4
    
    );
    //闹钟模块
    alarm myalarm(
        .CLK(CLK),
        .clock_clk(clock_clk), //1Hz
        .scan_clk(scan_clk), //1kHZ
        .Reset(Reset), 
        .reset(reset && sw5),
        .set(set && sw5),
        .start(sw14),
        .display_h(sw15),
        .hour1(hour1),
        .hour2(hour2),
        .minute1(minute1),
        .minute2(minute2),
        .second1(second1),
        .second2(second2),
        .sethour1(sethour1),  
        .sethour2(sethour2),  
        .setminute1(setminute1),
        .setminute2(setminute2),
        .setsecond1(setsecond1),
        .setsecond2(setsecond2),
        
        .clock_seg(alarm_seg[7:0]),//8
        .seln(alarm_seg[11:8]),//4
        .beep(beep)
    );
    //秒表
    stopwatch mystopwatch(
        .CLK(CLK),       
        .ms_clk(ms_clk),    
        .scan_clk(scan_clk),  
        .Reset(Reset),//全局复
        .reset(reset && sw6),//功能复
        .start_pause(btnc_down && sw6),
        .display_h(display_h), 
        .clock_seg(sw_seg[7:0]),//8
        .seln(sw_seg[11:8])//4
    );
    
    button_set_time myset( 
        .CLK(CLK), 
        .Reset(Reset),
    //    .reset(1'b0),
        .reset(reset && set),
        .set(set),
        .btnu_down(btnu_down), 
        .btnd_down(btnd_down), 
        .btnl_down(btnl_down), 
        .btnr_down(btnr_down), 
        .btnc_down(btnc_down),
    
        //out
        .sethour1(sethour1), 
        .sethour2(sethour2), 
        .setminute1(setminute1),
        .setminute2(setminute2),
        .setsecond1(setsecond1),
        .setsecond2(setsecond2));
    reset_module myreset(
        .CLK(CLK),
        .Reset(Reset),
        .scan_clk(scan_clk),
        .clock_clk(clock_clk),
        .clock_seg(reset_seg[7:0]), 
        .seln(reset_seg[11:8]));
    
    
    //选择输出
    always@(posedge CLK, negedge Reset)begin
        if(~Reset) display_out <= reset_seg;
        else if(sw2) begin//时钟
            if(beep && sw14)display_out <=alarm_seg;//闹钟响了
            else display_out <= clock_seg;
        end
        else if(sw3)display_out <=countup_seg;  //正计时
        else if(sw4)display_out <=countdown_seg;//倒计时
        else if(sw5)display_out <=alarm_seg;    //闹钟
        else if(sw6)display_out <=sw_seg;       //秒表
        else display_out <= reset_seg;          
    end
    endmodule
    
2 时钟分频,(正/倒)计时器模块

时钟分频

将板载输入100MHz分频为所需频率:

1000Hz用于扫描

1Hz用于时钟计时

60Hz用于秒表计时

由分频公式 f = p 2 N f 0 f=\frac{p}{2^{N}} f_{0} f=2Npf0,得 p = 2 N f f 0 p=\frac{2^{N} f}{f_{0}} p=f02Nf。采用32位计数器即可求得对应分频器的加数 p p p

  • div_n32p.v

    module div_n32p #(parameter p = 43)(//1Hz
        input CLK, reset,
        output clk);
    reg [31:0] counter;
    always@(posedge CLK, negedge reset)begin
        if(!reset) counter<=32'b0;
        else begin
            counter = counter + p;
        end
    end
    assign clk = counter[31];
    endmodule
    

1位计时器

通过输入的进位计时增加,满量程后进位。带有设置时间和开始/暂停功能。

  • counter.v

    module counter #(parameter base = 10)(
                input CLK,
                input cin,
                input reset,
                input set,
                input start_pause,
                input [3:0] scount,
                output reg[3:0] count,
                output reg cout
        );
        
    reg [2:0] cin_reg;
    wire cin_p;
    reg mode;//1开始 0暂停
    
    always@(posedge CLK, negedge reset)begin
        if(!reset) mode<=1'b0;
        else if(start_pause) mode<=~mode;
        else begin
            mode<=mode;
        end
    end
    
    always@(posedge CLK, negedge reset)begin
        if(!reset) begin
            cin_reg <=3'b0;
        end
        else begin 
            cin_reg <={cin_reg[1:0], cin};
        end
    end
    assign cin_p = ~cin_reg[2] && cin_reg[1];
    
    always@(posedge CLK, negedge reset)begin
        if(!reset) begin //复位
            count <= 4'b0;
            cout <= 0;
        end
        else if(set==1'b1)begin//设置时间
            count <= scount;
            cout <=0;
        end
        else begin//自增
            if(count >= base)begin
                cout <= 1;
                count <=4'b0;
            end
            else if(cin_p && mode==1'b1) begin
                count <=count+1;
                cout<=0;
            end
            else begin
                count<=count;
                cout<=0;
            end
        end
    end
    endmodule
    

1位倒计时器

与计时器相仿,减一个数等价于加上其补码,-1→+15

  • decounter.v

    `timescale 1ns / 1ps
    
    module decounter #(parameter base = 10)(
        input CLK,
        input cin,
        input reset,
        input set,
        input start_pause,
        input [3:0] scount,
        
        output reg[3:0] count,
        output reg cout);
        
    reg [2:0] cin_reg;
    wire cin_p;
    reg mode;//1开始 0暂停
    
    always@(posedge CLK, negedge reset)begin
        if(!reset) mode<=1'b0;
        else if(start_pause) mode<=~mode;
        else begin
            mode<=mode;
        end
    end
    
    always@(posedge CLK, negedge reset)begin
        if(!reset) begin
            cin_reg <=3'b0;
        end
        else begin 
            cin_reg <={cin_reg[1:0], cin};
        end
    end
    assign cin_p = ~cin_reg[2] && cin_reg[1];
    
    always@(posedge CLK, negedge reset)begin
        if(!reset) begin //复位
            count <= 4'b0;
            cout <= 0;
        end
        else if(set==1'b1)begin//设置时间
            count <= scount;
        end
        else begin//自增
            if(cin_p && mode==1'b1) begin
                if(count !=0 ) count <=count + 4'd15;
                else begin
                    count <=base-1;
                    cout <= 1;
                end
            end
            else begin
                count<=count;
                cout<=0;
            end
        end
    end
    
    endmodule
    
3 输入处理模块in_out.v

输入:未处理的按键接线

输出:处理后按键接线

  • 按键消抖

  • 判断Button按下

按键消抖

由于按键输入可能存在的抖动与对板载时钟的异步输入,所以要进行消抖与同步处理。

这里采用了简单的串联触发器构成的同步器,通过三级或更多级触发器,使得最终采样到的信号为亚稳态的概率尽可能小,即输出趋于稳定。

数字逻辑与计算机设计实验 FPGA数字钟(Verilog)_第4张图片

  • module debounce

    module debounce(
            input CLK,
            input key_in,
            output key_out
    );
    reg delay1;
    reg delay2;
    reg delay3;
    always@(posedge CLK)begin
            delay1 <= key_in;
            delay2 <= delay1;
            delay3 <= delay2;
    end
    assign key_out = delay3;
    endmodule
    

判断Button按下

按下,电位由低变高(或相反),存在一个上升沿,能不能用posedge检测上升沿来检测按键按下?

本着老师所说的控制信号与其他信号分开处理的要求,这里采取了连续采样寄存多次Button信号,通过判断临近两位是否出现相反的电位,来确定是否出现了上升沿。(相当于又做了一次同步??,感觉这里处理的不简练)

  • btn_down

    wire btnu;
    reg [2:0] btnu_reg;
    assign btnu_down = ~btnu_reg[2]&(btnu_reg[1]);
    always@(posedge CLK)begin
       btnu_reg<= {btnu_reg[1:0], btnu};
    end
    
    
5 24小时时钟,计时,秒表模块

输入:
各时钟与复位信号:CLK,clock_clk,scan_clk,Reset
设置时间,开始/暂停,高位显示信号

输出:
设置时间值,输出时间值,数码管信号

  • 例化counter,采用逐级进位

    counter #(6) s1_counter(
                .CLK(CLK),
                .reset(Reset&&~reset),
                .set(set),
                .start_pause(start_pause),
                .cin(cout[0]),
                .scount(setsecond1),
                .count(second1),
                .cout(cout[1])
    );
    
  • 扫描

    reg [2:0] sel; 
    always@(posedge scan_clk, negedge Reset)begin
        if(!Reset) sel<=0;
        else if(sel==3) sel<=0;
        else sel <= sel+1;
    end  
    
    always@(posedge scan_clk, negedge Reset)begin
        if(~Reset || full)begin
           {seln, clock_seg} <= 12'b1101_0000001_1;
        end
        else if(~display_h)begin
            case(sel)      
                3'd0:{seln, clock_seg} <= {4'b0111, encoder(minute1), 1'b1};
                3'd1:{seln, clock_seg} <= {4'b1011, encoder(minute2), dot };
                3'd2:{seln, clock_seg} <= {4'b1101, encoder(second1), 1'b1};
                3'd3:{seln, clock_seg} <= {4'b1110, encoder(second2), dot };
                default:{seln, clock_seg} <= 12'b1011_0000000_0;
            endcase
        end
        else begin
            case(sel)      
                3'd0:{seln, clock_seg} <= {4'b0111, encoder(hour1),   1'b1};
                3'd1:{seln, clock_seg} <= {4'b1011, encoder(hour2),   dot };
                3'd2:{seln, clock_seg} <= {4'b1101, encoder(minute1), 1'b1};
                3'd3:{seln, clock_seg} <= {4'b1110, encoder(minute2), dot };
                default:{seln, clock_seg} <= 12'b0111_0000000_0;
            endcase
        end
    end
    
    
  • 译码器(函数)

    function [6:0]encoder;
        input [3:0] dec;
        begin
            case(dec)
               4'd0: encoder = 7'b0000_001;
               4'd1: encoder = 7'b1001_111;
               4'd2: encoder = 7'b0010_010;
               4'd3: encoder = 7'b0000_110;
               4'd4: encoder = 7'b1001_100;
               4'd5: encoder = 7'b0100_100;
               4'd6: encoder = 7'b0100_000;
               4'd7: encoder = 7'b0001_111;
               4'd8: encoder = 7'b0000_000;
           4'd9: encoder = 7'b0000_100;
           default: encoder = 7'b1000_000;
          endcase
        end
    endfunction
    

正倒计时模块:正计时与24小时时钟模块相同,倒计时采用decounter即可

秒表:接入ms_clk即可,其他与时钟相同

6 闹钟

输入同上,另增一闹钟开关输入

输出另增一beep输出,用于响铃

思路:记录所设置的闹钟时间,与时钟时间比较,判断是否触发响铃。

  • 响铃逻辑beep

    output reg beep;
    always@(posedge clock_clk, negedge Reset)begin
        if(~Reset)begin                    
           count <= 4'b0;
           pre <= 1'b0;
           count <=4'b0;
        end  
        else if(onclk && start)begin //到时间
                pre <=1;
                count<=0;
                beep <=1;
        end
        else if(pre==1 && count <4'd5)begin
            count<= count+1;
        end
        else if(pre==1 && count>=4'd5)begin
            pre<=0;
            count<=0;
            beep<=0;
        end
        else begin
            pre<=pre;
            count<=count;
            beep<=beep;
        end
    end
    
7 时间设置

输入:各控制信号,按键信号

输出:所设置的时间

维护reg [5:0] setbit;通过左右按键调整设置哪一位,上下按键增减数值。

按键功能:

SW15显示高位
SW14闹钟开关
SW13功能归零reset高有效
SW6秒表
SW5闹钟
SW4倒计时
SW3正计时
SW2时钟
SW1设置时间
SW0全局复位

实验结果
数字逻辑与计算机设计实验 FPGA数字钟(Verilog)_第5张图片

按键去抖动:

FPGA中的按键消抖_Super-fei的博客-CSDN博客_fpga按键消抖

Verilog——FPGA按键去抖操作_Footprints明轩的博客-CSDN博客

你可能感兴趣的:(数字逻辑与计算机设计,fpga开发)