目录
1.实验描述
实现60s计数器的数码显示,当计数为60后回到1又循环,过程中可以通过按键控制暂停与继续。
key:按键控制计数开始或暂停
rst:清零
2.系统模块框图
系统模块主要包含四个模块:
1.数据生成模块:每过1s在原数据基础上加1,并且在等到按键按下的高电平控制数据的计数与暂停。
2.按键消抖模块:检测按键是否按下,按下则产生一个脉冲的高电平。
3.二进制转BCD码模块:将数据生成模块中的数据进行转换为BCD码,提供给数码驱动模块。
4.数码驱动模块:得到BCD码后,每过1ms驱动一个数码管位并显示内容。
3.软件程序
3.1数据生成模块
`timescale 1ns / 1ns
module top_seg
(
input sys_clk,
input sys_rst_n,
input key ,
output [5:0] sel, //数码管位选信号
output [7:0] seg //数码管段选信号
);
wire [7:0] data; //数码管显示的数值
data_gen data_gen_inst( //数据产生模块,每过0.1s数据自加一
//@2seg_data_inst ?= u_seg_data
.sys_clk (sys_clk) ,
.sys_rst_n (sys_rst_n) ,
.key (key) ,
.data (data)
);
seg_dynamic seg_dynamic_inst( //数码管动态显示模块
.sys_clk (sys_clk) ,
.sys_rst_n (sys_rst_n) ,
.data (data) ,
.sel (sel) ,
.seg (seg)
);
endmodule
3.2按键消抖模块
`timescale 1ns/1ns
module key_filter
#(
parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(
input wire sys_clk , //系统时钟50Mhz
input wire sys_rst_n , //全局复位
input wire key_in , //按键输入信号
output reg key_flag //key_flag为1时表示消抖后检测到按键被按下
//key_flag为0时表示没有检测到按键被按下
);
//reg define
reg [19:0] cnt_20ms ; //计数器
//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms <= 20'b0;
else if(cnt_20ms == CNT_MAX && key_in == 1'b0)
cnt_20ms <= cnt_20ms;
else if(key_in == 1'b1) //按键没有按下,不计时
cnt_20ms <= 20'b0;
else
cnt_20ms <= cnt_20ms + 1'b1;
//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_flag <= 1'b0;
else if(cnt_20ms == CNT_MAX - 1'b1)
key_flag <= 1'b1;
else
key_flag <= 1'b0;
endmodule
3.3二进制转BCD码模块
`timescale 1ns/1ns
module bcd_8421
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [7:0] data , //输入需要转换的数据
output reg [3:0] unit , //个位BCD码
output reg [3:0] ten , //十位BCD码
output reg [3:0] hun , //百位BCD码
output reg [3:0] tho , //千位BCD码
output reg [3:0] t_tho , //万位BCD码
output reg [3:0] h_hun //十万位BCD码
);
reg [4:0] cnt_shift ; //移位判断计数器 移动次数由十转为二进制的位数决定
reg [43:0] data_shift ; //移位判断数据 寄存器 存放BCD码与二进制码
reg shift_flag ; //移位判断标志信号 用于控制移位判断的先后顺序
//cnt_shift:从0到21循环计数(当计为20时进行判断移位,21时则是取BCD码数据)
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_shift <= 5'd0;
else if((cnt_shift == 5'd21) && (shift_flag == 1'b1))
cnt_shift <= 5'd0;
else if(shift_flag == 1'b1)
cnt_shift <= cnt_shift + 1'b1;
else
cnt_shift <= cnt_shift;
//data_shift:计数器为0时赋初值,计数器为1~20时进行移位判断操作
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_shift <= 44'b0;
else if(cnt_shift == 5'd0) //计数器为0时赋初值
data_shift <= {24'b0,data};
else if((cnt_shift <= 20) && (shift_flag == 1'b0)) //<=为小于等于,先判断再移位
begin
data_shift[23:20] <= (data_shift[23:20] > 4) ? (data_shift[23:20] + 2'd3) : (data_shift[23:20]);
data_shift[27:24] <= (data_shift[27:24] > 4) ? (data_shift[27:24] + 2'd3) : (data_shift[27:24]);
data_shift[31:28] <= (data_shift[31:28] > 4) ? (data_shift[31:28] + 2'd3) : (data_shift[31:28]);
data_shift[35:32] <= (data_shift[35:32] > 4) ? (data_shift[35:32] + 2'd3) : (data_shift[35:32]);
data_shift[39:36] <= (data_shift[39:36] > 4) ? (data_shift[39:36] + 2'd3) : (data_shift[39:36]);
data_shift[43:40] <= (data_shift[43:40] > 4) ? (data_shift[43:40] + 2'd3) : (data_shift[43:40]);
end
else if((cnt_shift <= 20) && (shift_flag == 1'b1))
data_shift <= data_shift << 1;
else
data_shift <= data_shift;
//shift_flag:移位判断标志信号,低电平判断 高电平移位
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
shift_flag <= 1'b0;
else
shift_flag <= ~shift_flag;
//当计数器等于20时,移位判断操作完成,对各个位数的BCD码进行赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
unit <= 4'b0;
ten <= 4'b0;
hun <= 4'b0;
tho <= 4'b0;
t_tho <= 4'b0;
h_hun <= 4'b0;
end
else if(cnt_shift == 5'd21)
begin
unit <= data_shift[23:20];
ten <= data_shift[27:24];
hun <= data_shift[31:28];
tho <= data_shift[35:32];
t_tho <= data_shift[39:36];
h_hun <= data_shift[43:40];
end
endmodule
3.4数码驱动模块
`timescale 1ns/1ns
module seg_dynamic
#(
parameter CNT_MAX = 16'd49_999 //数码管刷新 时间计数最大值 1ms
)
(
input wire sys_rst_n ,
input wire sys_clk ,
input wire [7:0] data , //数码管要显示的值
output reg [5:0] sel , //数码管位选信号
output reg [7:0] seg //数码管段选信号
);
//wire define
wire [3:0] unit ; //个位数
wire [3:0] ten ; //十位数
wire [3:0] hun ; //百位数
wire [3:0] tho ; //千位数
wire [3:0] t_tho ; //万位数
wire [3:0] h_hun ; //十万位数
//reg define
reg [23:0] data_reg ; //待显示数据 寄存器
reg [15:0] cnt_1ms ; //1ms计数器
reg flag_1ms ; //1ms标志信号
reg [2:0] cnt_sel ; //数码管位选计数器
reg [5:0] sel_reg ; //位选信号
reg [3:0] data_disp ; //当前数码管显示的数据
//cnt_1ms:1ms计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1ms <= 16'd0;
else if(cnt_1ms == CNT_MAX)
cnt_1ms <= 16'd0;
else
cnt_1ms <= cnt_1ms + 1'b1;
//flag_1ms:1ms标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
flag_1ms <= 1'b0;
else if(cnt_1ms == CNT_MAX - 1'b1)
flag_1ms <= 1'b1; // unit 为BCD码,可以总效果4'bXXXX
else // ten 为BCD码,可以总效果4'bXXXX
flag_1ms <= 1'b0; // hun 为BCD码,可以总效果4'bXXXX
// tho 为BCD码,可以总效果4'bXXXX
//data_reg:控制数码管显示数据 // t_tho 为BCD码,可以总效果4'bXXXX
always@(posedge sys_clk or negedge sys_rst_n) // h_hun 为BCD码,可以总效果4'bXXXX
if(sys_rst_n == 1'b0)
data_reg <= 24'b0;
//若显示的十进制数的 十万 位为非零数据则六个数码管全显示
else if(h_hun) //h_hun非空即数据有六位
data_reg <= {h_hun,t_tho,tho,hun,ten,unit};
//若显示的十进制数的 万 位为非零数据则值显示在5个数码管上
else if(t_tho)
data_reg <= {4'd11,t_tho,tho,hun,ten,unit}; //4'd11(第六位)我们定义为不显示
//若显示的十进制数的 千 位为非零数据则值显示4个数码管
else if(tho)
data_reg <= {4'd11,4'd11,tho,hun,ten,unit};
//若显示的十进制数的 百 位为非零数据则值显示3个数码管
else if(hun)
data_reg <= {4'd11,4'd11,4'd11,hun,ten,unit};
//若显示的十进制数的 十 位为非零数据则值显示2个数码管
else if(ten)
data_reg <= {4'd11,4'd11,4'd11,4'd11,ten,unit};
//若上面都不满足都只显示 个 位数码管
else
data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd11,unit};
//cnt_sel:标记0-5ms所对应显示的BCD码
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_sel <= 3'd0;
else if((cnt_sel == 3'd5) && (flag_1ms == 1'b1))
cnt_sel <= 3'd0;
else if(flag_1ms == 1'b1)
cnt_sel <= cnt_sel + 1'b1;
else
cnt_sel <= cnt_sel;
//数码管位选信号寄存器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sel_reg <= 6'b000_000;
else if((cnt_sel == 3'd0) && (flag_1ms == 1'b1))
sel_reg <= 6'b000_001;
else if(flag_1ms == 1'b1)
sel_reg <= sel_reg << 1;
else
sel_reg <= sel_reg;
//控制数码管的位选信号,使六个数码管轮流显示
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_disp <= 4'b0;
else if(flag_1ms == 1'b1)
case(cnt_sel)
3'd0: data_disp <= data_reg[3:0] ; //给第1个数码管赋个位值
3'd1: data_disp <= data_reg[7:4] ; //给第2个数码管赋十位值
3'd2: data_disp <= data_reg[11:8] ; //给第3个数码管赋百位值
3'd3: data_disp <= data_reg[15:12]; //给第4个数码管赋千位值
3'd4: data_disp <= data_reg[19:16]; //给第5个数码管赋万位值
3'd5: data_disp <= data_reg[23:20]; //给第6个数码管赋十万位值
default:data_disp <= 4'b0 ;
endcase
else
data_disp <= data_disp;
//控制数码管段选信号,显示数字
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
seg <= 8'b1111_1111;
else
case(data_disp)
4'd0 : seg <= 8'b1100_0000; //显示数字0
4'd1 : seg <= 8'b1111_1001; //显示数字1
4'd2 : seg <= 8'b1010_0100; //显示数字2
4'd3 : seg <= 8'b1011_0000; //显示数字3
4'd4 : seg <= 8'b1001_1001; //显示数字4
4'd5 : seg <= 8'b1001_0010; //显示数字5
4'd6 : seg <= 8'b1000_0010; //显示数字6
4'd7 : seg <= 8'b1111_1000; //显示数字7
4'd8 : seg <= 8'b1000_0000; //显示数字8
4'd9 : seg <= 8'b1001_0000; //显示数字9
default:seg <= 8'b1111_1111;
endcase
//sel:数码管位选信号赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sel <= 6'b000_000;
else
sel <= sel_reg;
bcd_8421 bcd_8421_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低电平有效
.data (data ), //输入需要转换的数据
.unit (unit ), //个位BCD码
.ten (ten ), //十位BCD码
.hun (hun ), //百位BCD码
.tho (tho ), //千位BCD码
.t_tho (t_tho ), //万位BCD码
.h_hun (h_hun ) //十万位BCD码
);
endmodule
3.5测试程序
`timescale 1ns/1ns
module tb_top_seg();
reg sys_clk ;
reg sys_rst_n ;
reg key ;
wire [5:0] sel ;
wire [7:0] seg ;
initial
begin
sys_clk = 1'b0;
sys_rst_n <= 1'b0;
key <= 1'b1;
#20;
sys_rst_n <= 1'b1;
#60;
key <= 1'b0; //按键第一次拉低
#500;
key <= 1'b1;
#80000
key <= 1'b0; //按键第二次拉低
#500;
key <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
defparam top_seg_inst.data_gen_inst.KEY_CNT = 20'd11;
defparam top_seg_inst.data_gen_inst.CNT_MAX_1S = 26'd49;
defparam top_seg_inst.seg_dynamic_inst.CNT_MAX = 16'd20;
top_seg top_seg_inst
(
.sys_clk (sys_clk ) ,
.sys_rst_n (sys_rst_n ) ,
.key (key ) ,
.sel (sel ) ,
.seg (seg )
);
endmodule
烧入板子后可以成功实现相关功能,欢迎评论区讨论!