一、籃球計分器的功能
按照籃球賽賽制進行設計,須具有24秒倒計時功能,十二分鐘計時功能,暫停功能,進球計分功能(1分,2分,3分)等,
二、具體實作方法
這個籃球計分器的實作采用top-down思想,具體分為6個模塊 分別為 控制模塊、計分模塊、計時模塊、24s倒計時模塊、按鍵消抖模塊、數碼管顯示模塊,
這個籃球計分器的輸入根據功能分析有以下幾個:系統時鐘、復位信號、暫停信號、加分信號(one、two、three)、控制給哪個隊伍加分的信號;輸出信號由于只有在數碼管上顯示,所以輸出信號就只有數碼管的段選與位選,
三、各個模塊的實作方式
(1)、top模塊
top模塊主要是要了解各個模塊之間的連接方式,具體如下圖所示:

根據上圖所示,top模塊代碼應該為:
module top(
input clk, //系統時鐘
input rst, //復位信號
input stop, //計時暫停 (撥碼按鍵)
input team, //選擇加分的隊伍 (撥碼按鍵)
input ctrl, //切換擋位 (按鍵)
input one, //加一分 (按鍵)
input two, //加兩分 (按鍵)
input three, //加三分 (按鍵)
output wire [3:0] sel, //數碼管位選
output wire [6:0] seg //數碼管段選
);
wire key_one;
wire key_two;
wire key_three;
wire [15:0] date1;
wire [15:0] date2;
wire [15:0] date3;
wire en;
wire key_ctrl;
wire [2:0] out_ctrl;
//計分模塊
xiaodou s0 (
.clk(clk),
.rst(rst),
.key_in(one),
.key_flag(key_one)
);
xiaodou s1 (
.clk(clk),
.rst(rst),
.key_in(two),
.key_flag(key_two)
);
xiaodou s2 (
.clk(clk),
.rst(rst),
.key_in(three),
.key_flag(key_three)
);
jifen s3 (
.clk(clk),
.rst(rst),
.key_one(key_one),
.key_two(key_two),
.key_three(key_three),
.team(team),
.date2(date2)
);
//計時模塊
jishi a0 (
.clk(clk),
.rst(rst),
.stop(stop),
.date1(date1)
);
//倒計時模塊
daojishi b0 (
.clk(clk),
.rst(rst),
.en(en),
.date3(date3)
);
//控制模塊
xiaodou c0 (
.clk(clk),
.rst(rst),
.key_in(ctrl),
.key_flag(key_ctrl)
);
ctrl c1(
.clk(clk),
.rst(rst),
.key_ctrl(key_ctrl),
.en(en),
.ctrl(out_ctrl)
);
//顯示模塊
xianshi d0 (
.clk(clk),
.rst(rst),
.ctrl(out_ctrl),
.date1(date1),
.date2(date2),
.date3(date3),
.sel(sel),
.seg(seg)
);
endmodule
(2)、控制模塊
控制模塊主要功能是切換擋位,本模塊他的輸入是有:系統時鐘、復位信號和經過按鍵消抖后的一個脈沖信號;其輸出是一個控制數碼管顯示的一個信號和倒計時模塊的使能信號en,由于我們要控制倒計時模塊何時開始倒計時,所以要引入這個使能信號,也就是按下ctrl按鈕切換到倒計時模塊時就開始倒計時,
具體代碼如下:
module ctrl(
input clk,
input rst,
input key_ctrl,
output reg en,
output reg [2:0] ctrl
);
reg [1:0] cnt;
always@(posedge clk or negedge rst) begin
if(rst==0)
cnt<=0;
else if(cnt=='d2&&key_ctrl == 1)
cnt<=0;
else if(key_ctrl == 1)
cnt<=cnt+1'd1;
end
always@(posedge clk or negedge rst) begin
if(rst==0) begin
ctrl<=0;
en<=0;
end
else if(cnt==0) begin
ctrl<=3'b000;
en <=0;
end
else if(cnt==1) begin
ctrl<=3'b001;
en<=0;
end
else if(cnt=='d2) begin
ctrl<=3'b010;
en<=1;
end
end
endmodule
(3)、計時模塊
計時模塊的主要功能是12分鐘的計時并將現在所及的時間轉化為16位的輸出信號(輸出信號為BCD碼前八位表示分鐘,后八位表示秒數), 計時模塊的輸入主要有:系統時鐘、復位信號和一個暫停信號(stop)輸出只有一個位寬為16位的資料信號;此模塊中的暫停信號(stop)我們可以在一秒鐘的計數器中加一個信號當stop拉高時停止記數,這樣就可以將其暫停;
在此模塊要注意如何將目前所及的時間轉化為BCD碼,在這里我使用的是BCD計數器,將分鐘和秒數分開,分別用兩個8位的資料來記錄它們的資料,最后將其合并;
具體代碼如下:
module jishi(
input clk,
input rst,
input stop,
output [15:0] date1
);
localparam TIME_1S='d49_999_999;
/* localparam TIME_1S='d4; */
reg [25:0] cnt_1s;
reg [7:0] dout_1;
reg [7:0] dout_2;
//一秒鐘的計數器
always@(posedge clk or negedge rst) begin
if(rst==0)
cnt_1s<='d0;
else if(cnt_1s == TIME_1S)
cnt_1s<='d0;
else if(stop==0)
cnt_1s<= cnt_1s + 1'd1;
else
cnt_1s<=cnt_1s;
end
//當前的秒數 并轉化為bcd碼
always@(posedge clk or negedge rst)begin
if(rst==0)
dout_2<=1'd0;
else if(dout_2[7:4]==4'b0101&&dout_2[3:0]==4'b1001&&cnt_1s == TIME_1S)
dout_2<=1'd0;
else if(dout_2[3:0]==4'b1001&&cnt_1s == TIME_1S) begin
dout_2[3:0]<=0;
dout_2[7:4]<=dout_2[7:4]+1'd1;
end
else if(cnt_1s == TIME_1S)
dout_2[3:0]<=dout_2[3:0]+1'b1;
end
//當前的分鐘數并轉化為bcd碼
always@(posedge clk or negedge rst)begin
if(rst==0)
dout_1<=1'd0;
else if(dout_1[7:4]==4'b0001&&dout_1[3:0]==4'b0010&&cnt_1s == TIME_1S)
dout_1<=1'd0;
else if(dout_1[3:0]==4'b1001&&cnt_1s == TIME_1S) begin
dout_1[3:0]<=0;
dout_1[7:4]<=dout_1[7:4]+1'd1;
end
else if(dout_2[7:4]==4'b0101&&dout_2[3:0]==4'b1001&&cnt_1s == TIME_1S)
dout_1[3:0]<=dout_1[3:0]+1'b1;
end
assign date1={dout_1[7:0],dout_2[7:0]};
endmodule
(4)、計分模塊
計分模塊的主要功能是計算并記錄目前兩隊的分數并將其轉化為16位的輸出信號;本模塊的輸入有:系統時鐘、復位信號和三個經過消抖后的計分信號(key_one、key_two、key_three)輸出只有一個位寬為16位的資料信號;
這里將資料轉化為BCD碼是要注意:由于我們不知道所加的分數是多少,所以有可能在加分后個位的分數大于9,所以此時BCD計數器在進位時要這要改寫:
else if(dout_1[3:0] >= 4'b1001) begin
dout_1[3:0] <= dout_1[3:0]-'d9;
dout_1[7:4] <= dout_1[7:4] + 1'b1;
end
此模塊代碼如下:
module jifen(
input clk, //系統時鐘
input rst, //復位信號
input key_one, //加一分
input key_two, //加兩分
input key_three, //加三分
input team, //控制給那個隊伍加分
output wire [15:0] date2 //輸出的資料
);
/* reg [6:0] score1; //目前該加幾分
reg [6:0] score2; //目前該加幾分 */
reg [7:0] dout_1;
reg [7:0] dout_2;
//計算加分并將其轉化為bcd碼
//第一隊
always@(posedge clk or negedge rst) begin
if(rst==0)
dout_1 <= 8'b00000000;
else if(dout_1[3:0] >= 4'b1001) begin
dout_1[3:0] <= dout_1[3:0]-'d9;
dout_1[7:4] <= dout_1[7:4] + 1'b1;
end
else if(key_one == 1'b1 && team == 0) begin
dout_1[7:4] <= dout_1[7:4];
dout_1[3:0] <= dout_1[3:0] + 1'b1;
end
else if(key_two == 1'b1 && team == 0) begin
dout_1[7:4] <= dout_1[7:4];
dout_1[3:0] <= dout_1[3:0] + 'd2;
end
else if(key_three == 1'b1 && team == 0) begin
dout_1[7:4] <= dout_1[7:4];
dout_1[3:0] <= dout_1[3:0] + 'd3;
end
end
//第二隊
always@(posedge clk or negedge rst) begin
if(rst==0)
dout_2 <= 8'b00000000;
else if(dout_2[3:0] >= 4'b1001)
begin
dout_2[3:0] <= dout_2[3:0]-'d9;
dout_2[7:4] <= dout_2[7:4] + 1'b1;
end
else if(key_one == 1'b1 && team == 1) begin
dout_2[7:4] <= dout_2[7:4];
dout_2[3:0] <= dout_2[3:0] + 1'b1;
end
else if(key_two == 1'b1 && team == 1) begin
dout_2[7:4] <= dout_2[7:4];
dout_2[3:0] <= dout_2[3:0] + 'd2;
end
else if(key_three == 1'b1 && team == 1) begin
dout_2[7:4] <= dout_2[7:4];
dout_2[3:0] <= dout_2[3:0] + 'd3;
end
end
//將bcd碼轉為將要賦值的資料
assign date2={dout_1[7:0],dout_2[7:0]};
endmodule
(5)、倒計時模塊
倒計時模塊的主要功能是24s的倒計時并將目前的時間轉化為BCD碼;本模塊的輸入是系統時鐘、復位信號和一個有控制模塊輸出的使能信號(用來控制何時開始倒計時);輸出是一個16位的資料信號(BCD碼)用來表示目前的時間;
本模塊中同樣采用BCD計數器將資料信號轉化為BCD碼,不過與之前不同的是采用減法記數;
本模塊代碼如下:
module daojishi(
input clk,//系統時鐘
input rst,//復位信號
input en,//判斷何時開始倒計時 由控制模塊輸入
output [15:0] date3 //輸出的資料信號
);
localparam TIME_001S='d499999;
/* localparam TIME_001S='d4; */
reg [7:0] dout_1;
reg [7:0] dout_2;
reg [18:0] cnt_001s;
always@(posedge clk or negedge rst) begin
if(rst==0)
cnt_001s<=0;
else if(en==0)
cnt_001s<=0;
else if(cnt_001s == TIME_001S)
cnt_001s<=0;
else
cnt_001s<=cnt_001s +1;
end
always@(posedge clk or negedge rst) begin
if(rst==0)
dout_2<=8'b0000_0000;
else if(en==0)
dout_2<=8'b0000_0000;
else if(dout_1==0&&dout_2==0&&cnt_001s == TIME_001S)
dout_2<=dout_2;
else if(dout_2==0&&cnt_001s == TIME_001S)
dout_2<=8'b1001_1001;
else if(dout_2[3:0]==0&&cnt_001s == TIME_001S) begin
dout_2[7:4]<=dout_2[7:4] - 1'b1;
dout_2[3:0]<=4'b1001;
end
else if(cnt_001s == TIME_001S)
dout_2[3:0]<=dout_2[3:0] - 1'b1;
end
always@(posedge clk or negedge rst) begin
if(rst==0)
dout_1<=8'b0010_0100;
else if(en==0)
dout_1<=8'b0010_0100;
else if(dout_1==0&&cnt_001s == TIME_001S)
dout_1<=dout_1;
else if(dout_1[3:0]==0&&cnt_001s == TIME_001S) begin
dout_1[7:4]<=dout_1[7:4] - 1'b1;
dout_1[3:0]<=4'b1001;
end
else if(dout_2==0&&cnt_001s == TIME_001S)
dout_1[3:0]<=dout_1[3:0] - 1'b1;
end
assign date3={dout_1[7:0],dout_2[7:0]};
endmodule
(6)、按鍵消抖模塊
按鍵消抖模塊的主要功能是將一個輸入的按鍵信號濾波轉化為一個脈沖信號;具體實作辦法可以參見小梅哥《零基礎輕松學習FPGA,小梅哥FPGA設計思想與驗證方法視頻教程》
具體代碼如下:
module xiaodou(
input clk,
input rst,
input key_in,
output reg key_flag //濾波后的信號(脈沖信號)
);
localparam
IDEL = 4'b0001,
FILTER0 = 4'b0010,
DOWN = 4'b0100,
FILTER1 = 4'b1000;
reg [3:0] state;
reg key_tem0;
reg key_tem1;
wire nedge;
wire pedge;
reg [19:0] cnt; //二十毫秒計數器
reg en_cnt; //計數器使能信號
reg cnt_full;//計數器記滿信號
//邊沿檢測
always@(posedge clk or negedge rst) begin
if(rst==0) begin
key_tem0<=0;
key_tem1<=0;
end
else begin
key_tem0 <= key_in;
key_tem1 <= key_tem0;
end
end
assign nedge = !key_tem0 & key_tem1; //下降沿
assign pedge = key_tem0 & (!key_tem1); //上升沿
//一段式狀態機
always@(posedge clk or negedge rst )begin
if(rst==0) begin
state <= IDEL;
en_cnt <= 1'b0;
key_flag<=1'd0;
end
else
case(state)
IDEL:
begin
key_flag<=1'b0;
if(nedge) begin
state <= FILTER0;
en_cnt <= 1'b1; //計數器記數
end
else
state <= IDEL;
end
FILTER0:
if(cnt_full) begin
state<= DOWN;
en_cnt<=1'b0;
key_flag<=1'b1;
end
else if(pedge) begin
state<= IDEL;
en_cnt <= 1'b0;
end
else
state<= FILTER0;
DOWN:
begin
key_flag<=1'b0;
if(pedge) begin
state<=FILTER1;
en_cnt<=1'b1;
end
else
state<= DOWN;
end
FILTER1:
if(cnt_full) begin
state<= IDEL;
en_cnt<=1'b0;
key_flag<=1'b0;
end
else if(nedge) begin
state<= DOWN;
en_cnt <= 1'b0;
end
else
state<= FILTER1;
default: begin
state<=IDEL;
en_cnt<=1'b0;
key_flag<=1'b0;
end
endcase
end
//二十毫秒計數器
always@(posedge clk or negedge rst) begin
if(rst==0)
cnt <= 20'd0;
else if(en_cnt)
cnt <= cnt + 1'b1;
else
cnt<= 20'd0;
end
always@(posedge clk or negedge rst) begin
if(rst==0)
cnt_full <= 1'b0;
else if(cnt == 'd999_999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
end
endmodule
(7)、數碼管顯示模塊
數碼管顯示模塊的主要功能是將輸入其中的16位資料信號通過動態掃描的方式輸出在數碼管上,本模塊的輸入有系統時鐘、復位信號、由控制模塊輸入的控制信號和由計時、計分、倒計時三個模塊輸出的資料信號,輸出也就是整個計分器的輸出:4位的數碼管位選sel和7位的數碼管段選seg;
本模塊通過譯碼器的方式來實作資料選擇:
always@(posedge clk or negedge rst) begin
if(rst==0)
disp_data<=0;
else
case(ctrl)
3'b000:disp_data<=date1;
3'b001:disp_data<=date2;
3'b010:disp_data<=date3;
default:disp_data<=0;
endcase
end
本模塊的代碼如下:
module xianshi(
input clk,
input rst,
input [2:0] ctrl,
input [15:0] date1,//資料信號
input [15:0] date2,//資料信號
input [15:0] date3,//資料信號
output reg [3:0] sel, // 數碼管位選(選擇當前要顯示的數碼管)
output reg [6:0] seg // 數碼管段選(選擇當前要顯示的內容)
);
reg [14:0] cnt_1; //分頻記數的計數器
reg clk_out; //分頻后的時鐘信號
/* reg [3:0] sel_r; //表示那個數碼管亮 */
reg [3:0] date_tmp;
reg [15:0] disp_data;
localparam TIME='d24999;
/* localparam TIME='d4; */
always@(posedge clk or negedge rst) begin
if(rst==0)
disp_data<=0;
else
case(ctrl)
3'b000:disp_data<=date1;
3'b001:disp_data<=date2;
3'b010:disp_data<=date3;
default:disp_data<=0;
endcase
end
//cnt_1的記數模塊
always@(posedge clk or negedge rst) begin
if(rst==0)
cnt_1 <= 15'd0;
else if(cnt_1 ==TIME)
cnt_1 <= 15'd0;
else
cnt_1 <= cnt_1+1'd1;
end
//時鐘分頻模塊
always@(posedge clk or negedge rst) begin
if(rst==0)
clk_out<=1'b0;
else if(cnt_1== TIME)
clk_out<=~clk_out;
else
clk_out<=clk_out;
end
//移位暫存器
always@(posedge clk_out or negedge rst) begin
if(rst==0)
sel<=4'b0001;
else if(sel==4'b1000)
sel<=4'b0001;
else
sel<=sel<<1;
/* if(rst==0)
sel_r<=4'b0001;
else
sel_r<={sel_r[2:0],sel_r[3]}; */
end
//四選一多路器
always@(*) begin
case(sel)
4'b0001 : date_tmp <= disp_data[3:0];
4'b0010 : date_tmp <= disp_data[7:4];
4'b0100 : date_tmp <= disp_data[11:8];
4'b1000 : date_tmp <= disp_data[15:12];
default:date_tmp<=4'b0000;
endcase
end
//字典(譯碼器)
always@(*) begin
case(date_tmp)
4'h0:seg<=7'b1000000;
4'h1:seg<=7'b1111001;
4'h2:seg<=7'b0100100;
4'h3:seg<=7'b0110000;
4'h4:seg<=7'b0011001;
4'h5:seg<=7'b0010010;
4'h6:seg<=7'b0000010;
4'h7:seg<=7'b1111000;
4'h8:seg<=7'b0000000;
4'h9:seg<=7'b0010000;
4'ha:seg<=7'b0001000;
4'hb:seg<=7'b0000011;
4'hc:seg<=7'b1000110;
4'hd:seg<=7'b0100001;
4'he:seg<=7'b0000110;
4'hf:seg<=7'b0001110;
endcase
end
//
endmodule
四、總結
本人是剛入門FPGA的一名在讀大學生,由于初學代碼也表現出冗長,凌亂等諸多問題,望見諒,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/341940.html
標籤:其他
