基于FPGA的简易棋钟设计
一、项目要求
自行设计一个棋钟,功能如下:
$a$. 棋类比赛中每步棋有时间限制。假设A、B两位棋手比赛,当A棋手落子后按下A键,此时B棋手的秒表开始倒计时,A棋手的秒表恢复为倒计时初始值;当B棋手落子后按下B键,A棋手的秒表开始倒计时,B棋手的秒表恢复为倒计时初始值。
$b$. 倒计时初始值可设置为10秒或30秒两档。
$c$. 当倒计时至5秒内时,蜂鸣器每秒响一下或led灯闪烁1下。
$d$. 当某选手超时判负时,蜂鸣器长响,led灯持续闪烁,数码管显示出获胜的棋手编号(A或B)。
$e$. 其他功能可自由发挥。
依据设计目标,提炼出以下几点要求:
- 时钟分频功能,控制计时、LED及蜂鸣器工作频率。
- 倒计时功能,满足双方的计时要求。
- LED闪烁功能,两种闪烁模式。
- 蜂鸣器鸣叫功能,两种鸣叫模式。
- 数码管显示功能,倒计时及获胜方显示。
- 按键消抖功能,对AB切换按钮消抖。
- 模式切换功能,在30s和10s倒计时功能间切换。
- 状态判断切换功能,倒计时状态、倒计时5s内、一方获胜等状态,并控制棋钟工作。
二、实现方案框图
依据上述要求,可将前六个功能模块化,最后的状态控制在顶层模块中完成。
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将切换到另一方倒计时。至此课程设计要求全部实现,感谢陈瑜老师的指导,祝愿陈老师身体安康,工作顺利。