基于FPGA的简易棋钟设计

warning: 这篇文章距离上次修改已过817天,其中的内容可能已经有所变动。

一、项目要求

自行设计一个棋钟,功能如下:
$a$. 棋类比赛中每步棋有时间限制。假设A、B两位棋手比赛,当A棋手落子后按下A键,此时B棋手的秒表开始倒计时,A棋手的秒表恢复为倒计时初始值;当B棋手落子后按下B键,A棋手的秒表开始倒计时,B棋手的秒表恢复为倒计时初始值。
$b$. 倒计时初始值可设置为10秒或30秒两档。
$c$. 当倒计时至5秒内时,蜂鸣器每秒响一下或led灯闪烁1下。
$d$. 当某选手超时判负时,蜂鸣器长响,led灯持续闪烁,数码管显示出获胜的棋手编号(A或B)。
$e$. 其他功能可自由发挥。

依据设计目标,提炼出以下几点要求:

  1. 时钟分频功能,控制计时、LED及蜂鸣器工作频率。
  2. 倒计时功能,满足双方的计时要求。
  3. LED闪烁功能,两种闪烁模式。
  4. 蜂鸣器鸣叫功能,两种鸣叫模式。
  5. 数码管显示功能,倒计时及获胜方显示。
  6. 按键消抖功能,对AB切换按钮消抖。
  7. 模式切换功能,在30s和10s倒计时功能间切换。
  8. 状态判断切换功能,倒计时状态、倒计时5s内、一方获胜等状态,并控制棋钟工作。

二、实现方案框图

依据上述要求,可将前六个功能模块化,最后的状态控制在顶层模块中完成。

info:本设计基于电子科技大学数字电路课程组FPGA教学开发板(Xilinx xc7a35tftg256-1芯片) 如下图


2.1 分频器模块

分频器输入50MHz时钟,输出1Hz、5Hz、10Hz时钟,分别控制LED每秒闪烁、LED快速闪烁、倒计时0.1s精度,有一个时钟输入端(clk)、三个时钟输出端(clk_1hz、clk_5hz、clk_10hz)。

2.2 倒计时器模块

有三进制和十进制两种倒计器,即可实现30s和10s两种倒计时功能,倒计时器有时钟输入端(clk)、异步清零端(re)、使能端(EN)、倒计时结束标志输入端(done)、借位输出端(CO)、计数输出端(Q[3:0]),对于三进制倒计器还有一个模式输入端(model),用于切换两种倒计时模式。

2.3 LED模块

LED模块有1Hz、5Hz两个时钟输入端、状态输入端(state)、5s倒计时判断输入端(time_5s)、LED输出端(led)。

2.4 蜂鸣器模块

蜂鸣器模块大体同LED模块,但因FPGA板载为无源蜂鸣器,需输入高频时钟,故内部有分频功能,共有50MHz、1Hz两个时钟输入端、状态输入端(state)、5s倒计时判断输入端(time_5s)、蜂鸣器输出端(beep)。

2.5 数码管显示模块

采用六位数码管动态显示,内置分频功能,有一个时钟输入端(clk)、状态输入端(state)、胜方输入端(winner)、六个数据输入端(disp_data_0~5[3:0])、段码输出端(seg[7:0])、位码输出端(dig[5:0])。

2.6 按键消抖模块

按键消抖模块通过寄存器延时移位的方式对按键消抖,内置分频功能,共有一个时钟输入端(clk)、一个按键输入端(btn_in)、一个按键输出端(btn_out)。

其余功能在顶层模块中完成,这里不画框图。

总电路框图

三、Verilog源代码

3.1 clk_div.v

//分频器
module clk_div(
    input clk,
    output reg clk_1hz=0,   //倒计时5s时  LED和蜂鸣器每秒工作
    output reg clk_5hz=0,         //一方超时 LED快速闪烁
    output reg clk_10hz=0     //倒计时计时  0.1s
);
    reg [21:0] count_0=0;
    reg [22:0] count_1=0;
    reg [2:0] count_2=0;
    
    always@(posedge clk)
    begin
        if(count_0==2499999)    //5000000分频 10hz
        begin
            count_0<=0;
            clk_10hz<=~clk_10hz;
        end
        else
            count_0<=count_0+1;
    end
    
    always@(posedge clk)
    begin
        if(count_1==4999999)    //10000000分频 5hz
        begin
            count_1<=0;
            clk_5hz<=~clk_5hz;
        end
        else
            count_1<=count_1+1;
    end
    
    always@(posedge clk_10hz)
    begin
        if(count_2==4)    //1hz
        begin
            count_2<=0;
            clk_1hz<=~clk_1hz;
        end
        else
            count_2<=count_2+1;
    end
endmodule

3.2 countdown_3.v

//三进制倒计时器
module countdown_3(
    input clk,
    input re,      //异步复位端
    input EN,       //使能端
    input model,   //模式切换按键  0:3s倒计时  1:不计时
    input done,     //倒计时结束
    output CO,       //借位输出
    output reg [3:0] Q
    );

    always @(posedge clk or negedge re or negedge model)
    begin
        if(model)
            Q<=0;
         else if(re)    //复位端高有效
            Q<=2;
        else if(done)   //结束 高有效
            Q<=0;
        else if (EN)    //使能有效
               begin
                   if(Q==0)
                       Q<=2;
                   else
                       Q<=Q-1;
               end
    end
    assign CO=((EN&&(Q==0)) ? 1:0)|model;   //当计数到0且使能有效 或工作模式为1 借位为1
endmodule

3.3 countdown_10.v

//十进制倒计时器
module countdown_10 (
    input clk,
    input re,      //异步复位端
    input EN,       //使能端
    input done,     //倒计时结束
    output CO,       //借位输出
    output reg [3:0] Q
    );
    
    always @(posedge clk or negedge re)
    begin
        if(re)    //复位端高有效
            Q<=9;
        else if(done)   //结束 高有效
            Q<=0;
        else if (EN)    //使能有效
               begin
                   if(Q==0)
                       Q<=9;
                   else
                       Q<=Q-1;
               end
    end
    assign CO=(EN&&(Q==0)) ? 1:0;   //当计数到0且使能有效 借位为1
endmodule

3.4 led_shine.v

//LED闪烁模块
module led_shine(
    input clk_1hz,
    input clk_5hz,
    input state,      //工作状态 0:倒计内      1:倒计时结束 
    input time_5s,  //是否在5s倒计时内
    output reg led
    );
    always@(state,time_5s)
    begin
        if(state)
            led=clk_5hz;      //倒计时结束
        else if(time_5s)
            led=clk_1hz;    //倒计时5s内
        else
            led=0;     
    end
endmodule

3.5 buzzer.v

//蜂鸣器模块
module buzzer(
    input clk,
    input clk_1hz,
    input state,      //工作状态 0:倒计内      1:倒计时结束 
    input time_5s,  //是否在5s倒计时内
    output reg beep
    );
    
    reg [14:0] count=0;
    reg clk_1khz=0;     //蜂鸣器工作频率 
    always@(posedge clk)
    begin
        if(count==24999)    //50000分频 1khz
        begin
            count<=0;
            clk_1khz<=~clk_1khz;
        end
        else
            count<=count+1;
    end
    
    always@(state,time_5s)
    begin
        if(state)
            beep=clk_1khz;      //倒计时结束
        else if(time_5s)
            beep=clk_1hz&clk_1khz;      //倒计时5s内
        else
            beep=0;     
    end
endmodule

3.6 dynamic_led6.v

//六位动态显示电路
module dynamic_led6(
    input [3:0] disp_data_0,        //6个数码管显示的数据
    input [3:0] disp_data_1,
    input [3:0] disp_data_2,
    input [3:0] disp_data_3,
    input [3:0] disp_data_4,
    input [3:0] disp_data_5,
    input state,        //工作模式  0:倒计时模式  1:显示胜利者
    input winner,       //胜利者  0:a胜利    1:b胜利
    input clk,      //时钟输入
    output reg [7:0] seg,    //段码输出
    output reg [5:0] dig    // 位码输出
    );
    
    //分频  
    integer count=0;  //分频计数
    reg clk_div =0;        //分频后的时钟
    always @(posedge clk)
        begin
            if(count==24999)    //50000分频 输出时钟脉冲 5kHz
                begin
                    clk_div<=~clk_div;
                    count<=0;
                end
            else
                count<=count+1;
        end
        
 //计数器及译码器  控制位码
 reg [2:0] num=0;     //六位数码管  三位二进制即可表示
 always @(posedge clk_div)
    begin
        if(num>=5)
            num<=0;
        else
            num<=num+1;
    end
 always @(num)      //动态显示关键代码 位码译码
    begin
        case(num)
        0:dig=6'b111110;    //从右至左刷新
        1:dig=6'b111101;
        2:dig=6'b111011;
        3:dig=6'b110111;
        4:dig=6'b101111;
        5:dig=6'b011111;
        default : dig=6'b111111;
        endcase
    end

//数据选择器,确定要显示的数据
reg [3:0] disp_data;
always @(num,state)
    begin
        if(~state)      //倒计时模式 正常倒计时
            case(num)       //显示倒计时器数据
            0:disp_data=disp_data_0;
            1:disp_data=disp_data_1;
            2:disp_data=disp_data_2;
            3:disp_data=disp_data_3;
            4:disp_data=disp_data_4;
            5:disp_data=disp_data_5;
            default : disp_data=0;
            endcase
        else if(~winner)            // a胜 显示 '--aa--'
                    case(num)       //对不同的位显示不同的数据
                    0:disp_data=0;
                    1:disp_data=0;
                    2:disp_data=1;
                    3:disp_data=1;
                    4:disp_data=0;
                    5:disp_data=0;
                    default : disp_data=0;
                    endcase
               else             //b胜利 显示'--bb--'
                    case(num)       //对不同的位显示不同的数据
                    0:disp_data=0;
                    1:disp_data=0;
                    2:disp_data=2;
                    3:disp_data=2;
                    4:disp_data=0;
                    5:disp_data=0;
                    default : disp_data=0;
                    endcase
    end

//七段码显示译码器  这里直接采用实验十的代码
always@(disp_data,num,state)
    begin
        if(~state)       //倒计时模式
        begin
                if(num==1||num==4)  //带小数点的数
                case(disp_data)
                    4'b0000:seg=8'b11111101;    //0~9的段码
                    4'b0001:seg=8'b01100001;
                    4'b0010:seg=8'b11011011;
                    4'b0011:seg=8'b11110011;
                    4'b0100:seg=8'b01100111;
                    4'b0101:seg=8'b10110111;
                    4'b0110:seg=8'b10111111;    //这里修改一下 6
                    4'b0111:seg=8'b11100001;
                    4'b1000:seg=8'b11111111;
                    4'b1001:seg=8'b11100111;
                    default: seg=8'b00000001;
                endcase
                else
                case(disp_data)
                    4'b0000:seg=8'b11111100;    //0~9的段码
                    4'b0001:seg=8'b01100000;
                    4'b0010:seg=8'b11011010;
                    4'b0011:seg=8'b11110010;
                    4'b0100:seg=8'b01100110;
                    4'b0101:seg=8'b10110110;
                    4'b0110:seg=8'b10111110;
                    4'b0111:seg=8'b11100000;
                    4'b1000:seg=8'b11111110;
                    4'b1001:seg=8'b11100110;
                    default: seg=8'b00000000;
                endcase
        end
        else        //一方判负
            case(disp_data)
            4'b0000:seg=8'b00000010;    //显示'-'
            4'b0001:seg=8'b11111010;    //显示'a'
            4'b0010:seg=8'b00111110;    //显示'b'
            default: seg=8'b11111110;
            endcase
    end
endmodule

3.7 top_module.v

module top_module(
    input clk,      //时钟输入
    input col0,     //A方按钮
    input col1,     //B方按钮
    input model,    //模式切换按键  0:30s倒计时  1:10秒倒计时
    output led,     //指示LED
    output beep,  //指示蜂鸣器
    output [3:0] row,   //锁定最后一行
    output [7:0] seg,   //段码
    output [5:0] dig    //位码
);
    assign row[3:0]=0001;   //锁定第四行
    wire key0,key1;         //消抖后按键信号
    reg EN0=0, EN1=1;   //按下后的使能信号      初始为b方
    wire done0,done1;   //倒计时完成后的信号
    wire clk_1hz,clk_5hz,clk_10hz;  //分频器输出的时钟信号
    wire [3:0] Q0,Q1,Q2,Q3,Q4,Q5;   //倒计时器的计数结果
    wire CO0,CO1,CO2,CO3,CO4,CO5;   //倒计时器的借位
    wire state;     //进行状态 0:倒计时模式 1:某方获胜
    reg time_5s=1;    //倒计时5s时 0:未到5s内 1:5s内
    reg winner=0;     //获胜方 0:A   1:B
    assign done0=CO3&CO4&CO5;
    assign done1=CO0&CO1&CO2;
    assign state=done0|done1;       //双方任一倒计时结束 分出胜负
    
    always@(done0,done1)
    begin
        if(done0==1)        //A倒计时结束 B获胜
            winner=1;
        else if(done1==1)       //B倒计时结束 A获胜
            winner=0;
        else
            winner=1'bx;
    end
    
    always@(Q1,Q2,Q4,Q5)
    begin
        if((5>=Q1 && Q2==0)|(5>=Q4 && Q5==0))      //任意一方倒计时到5s内
            time_5s=1;
        else
            time_5s=0;
    end
    
    //分频器模块
    clk_div m1(.clk(clk),.clk_1hz(clk_1hz),.clk_5hz(clk_5hz),.clk_10hz(clk_10hz));
    //按键消抖模块
    key_deboucing m2(.btn_in(col0),.clk(clk),.btn_out(key0));
    key_deboucing m3(.btn_in(col1),.clk(clk),.btn_out(key1));
    always@(posedge key0|key1)  //按键消抖时钟沿转换为电平 两个按键功能相同
    begin
        EN0<=~EN0;
        EN1<=~EN1;
    end
    //倒计时模块
    countdown_10 m4(.clk(clk_10hz),.re(EN0),.EN(EN1),.done(done1),.CO(CO0),.Q(Q0));
    countdown_10 m5(.clk(clk_10hz),.re(EN0),.EN(CO0),.done(done1),.CO(CO1),.Q(Q1));
    countdown_3 m6(.clk(clk_10hz),.re(EN0),.EN(CO1),.model(model),.done(done1),.CO(CO2),.Q(Q2));
    countdown_10 m7(.clk(clk_10hz),.re(EN1),.EN(EN0),.done(done0),.CO(CO3),.Q(Q3));
    countdown_10 m8(.clk(clk_10hz),.re(EN1),.EN(CO3),.done(done0),.CO(CO4),.Q(Q4));
    countdown_3 m9(.clk(clk_10hz),.re(EN1),.EN(CO4),.model(model),.done(done0),.CO(CO5),.Q(Q5));
    //动态显示模块
    dynamic_led6 m10(
        .disp_data_0(Q0),
        .disp_data_1(Q1),
        .disp_data_2(Q2),
        .disp_data_3(Q3),
        .disp_data_4(Q4),
        .disp_data_5(Q5),
        .state(state),
        .winner(winner),
        .clk(clk),
        .seg(seg),
        .dig(dig)
        );
    //LED模块
    led_shine m11(.clk_1hz(clk_1hz),.clk_5hz(clk_5hz),.state(state),.time_5s(time_5s),.led(led));
    //蜂鸣器
    buzzer m12(.clk(clk),.clk_1hz(clk_1hz),.state(state),.time_5s(time_5s),.beep(beep));
endmodule

3.8 约束文件

#约束文件
#时钟输入
set_property PACKAGE_PIN D4 [get_ports {clk}]
set_property IOSTANDARD LVCMOS33 [get_ports {clk}]
#按键约束
#模式选择开关
set_property PACKAGE_PIN T9 [get_ports {model}]
set_property IOSTANDARD LVCMOS33 [get_ports {model}]
#行约束
set_property PACKAGE_PIN K3 [get_ports {row[0]}]
set_property PACKAGE_PIN M6 [get_ports {row[1]}]
set_property PACKAGE_PIN P10 [get_ports {row[2]}]
set_property PACKAGE_PIN R10 [get_ports {row[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {row[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {row[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {row[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {row[3]}]
#指定key1
set_property PACKAGE_PIN R12 [get_ports {col0}]
set_property IOSTANDARD LVCMOS33 [get_ports {col0}]
#指定key0
set_property PACKAGE_PIN T12 [get_ports {col1}]
set_property IOSTANDARD LVCMOS33 [get_ports {col1}]
#下拉列线至低电平
set_property PULLDOWN true [get_ports {col0}]
set_property PULLDOWN true [get_ports {col1}]
#LED
set_property PACKAGE_PIN P9 [get_ports {led}]
set_property IOSTANDARD LVCMOS33 [get_ports {led}]
#蜂鸣器
set_property PACKAGE_PIN L2 [get_ports {beep}]
set_property IOSTANDARD LVCMOS33 [get_ports {beep}]

#数码管约束
#位码引脚
set_property PACKAGE_PIN G12 [get_ports {dig[0]}]
set_property PACKAGE_PIN H13 [get_ports {dig[1]}]
set_property PACKAGE_PIN M12 [get_ports {dig[2]}]
set_property PACKAGE_PIN N13 [get_ports {dig[3]}]
set_property PACKAGE_PIN N14 [get_ports {dig[4]}]
set_property PACKAGE_PIN N11 [get_ports {dig[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {dig[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {dig[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {dig[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {dig[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {dig[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {dig[5]}]
#段码引脚
set_property PACKAGE_PIN L13 [get_ports {seg[0]}]
set_property PACKAGE_PIN M14 [get_ports {seg[1]}]
set_property PACKAGE_PIN P13 [get_ports {seg[2]}]
set_property PACKAGE_PIN K12 [get_ports {seg[3]}]
set_property PACKAGE_PIN K13 [get_ports {seg[4]}]
set_property PACKAGE_PIN L14 [get_ports {seg[5]}]
set_property PACKAGE_PIN N12 [get_ports {seg[6]}]
set_property PACKAGE_PIN P11 [get_ports {seg[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[7]}]

四、RTL分析截图

五、实现情况描述

  将拨码开关T9置为低电平,下载好bit流文件后,将从右方三位数码管开始倒计时(B方),倒计时30s,左方(A方)显示29.9;当右方倒计时至5s内时LED P9每秒闪烁一次,蜂鸣器每秒鸣叫;当右方倒计时结束,LED快速闪烁,蜂鸣器长响,数码管显示”--aa--”,A获胜。
  按下key1键,左方开始倒计时,右方显示29.9,倒计时到5s内,LED每秒闪烁一次,蜂鸣器每秒鸣叫;当左方倒计时结束,LED快速闪烁,蜂鸣器长响,数码管显示”--bb--”,b获胜;在左/右方倒计时未结束时,按下key1/key0将切换到另一方倒计时。
  将拨码开关T9置为高电平时,即可切换到10s倒计时模式,左/右方开始倒计时的时候,另一方显示09.9,倒计时五秒内,倒计时到5s内,LED每秒闪烁一次,蜂鸣器每秒鸣叫;当左/右方倒计时结束,LED快速闪烁,蜂鸣器长响,显示”--bb--”或者”--aa--”; 在左/右方倒计时未结束时,按下key1/key0将切换到另一方倒计时。至此课程设计要求全部实现,感谢陈瑜老师的指导,祝愿陈老师身体安康,工作顺利。

添加新评论