本文内容:基于 FPGA 实现数字时钟,如果后续有时间可以添加一些额外的功能,比如设置时间、闹钟等等
中间的基础篇和进阶篇主要训练数码管的灵活应用,如果熟悉了并完全掌握的话,可以更加熟练的实现数字时钟
SEL 信号
DIG 信号
module display(
input clk ,
input rst_n ,
output [7:0] DIG ,
output [5:0] SEL
);
assign SEL = 6'b111_101;
assign DIG = 8'b1010_0100;
endmodule
方法一:普通版
module display #(parameter MS_1 = 17'd100_000)(
input clk ,
input rst_n ,
output reg [7:0] DIG ,
output reg [5:0] SEL
);
// 参数定义
parameter SEL_MAX = 3'd6 ; // 数码管位数
// 信号定义
reg [ 2:0] SEL_num ; // SEL序号选择
reg [16:0] cnt_flicker ; // 数码管闪烁频率计数器
// 逻辑实现
// 闪烁频率计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_flicker <= 17'd0;
end
else if (cnt_flicker >= MS_1 - 17'd1) begin
cnt_flicker <= 17'd0;
end
else begin
cnt_flicker <= cnt_flicker + 17'd1;
end
end
// SEL序号选择
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
SEL_num <= 3'd0;
end
else if (cnt_flicker >= MS_1 - 17'd1) begin
if (SEL_num >= SEL_MAX - 3'd1) begin
SEL_num <= 3'd0;
end
else begin
SEL_num <= SEL_num + 3'd1;
end
end
else begin
SEL_num <= SEL_num;
end
end
// SEL信号输出
always @(*) begin
case (SEL_num)
3'd0 : SEL = 6'b111_110;
3'd1 : SEL = 6'b111_101;
3'd2 : SEL = 6'b111_011;
3'd3 : SEL = 6'b110_111;
3'd4 : SEL = 6'b101_111;
3'd5 : SEL = 6'b011_111;
default: SEL = 6'b111_111;
endcase
end
// DIG信号输出
always @(*) begin
case (SEL_num)
3'd0 : DIG = 8'b1111_1001;
3'd1 : DIG = 8'b0010_0100;
3'd2 : DIG = 8'b1011_0000;
3'd3 : DIG = 8'b0001_1001;
3'd4 : DIG = 8'b1001_0010;
3'd5 : DIG = 8'b1000_0010;
default: DIG = 8'b1111_1111;
endcase
end
endmodule
方法二:拼接运算版
module display #(parameter MS_1 = 17'd100_000)(
input clk ,
input rst_n ,
output reg [7:0] DIG ,
output reg [5:0] SEL
);
// 信号定义
reg [16:0] cnt_flicker ; // 数码管闪烁频率计数器
wire [ 0:0] SEL_change ;
// 逻辑实现
// 闪烁频率计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_flicker <= 17'd0;
end
else if (SEL_change) begin
cnt_flicker <= 17'd0;
end
else begin
cnt_flicker <= cnt_flicker + 17'd1;
end
end
assign SEL_change = cnt_flicker >= MS_1 - 17'd1 ? 1'b1 : 1'b0;
// SEL信号输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
SEL <= 6'b111_110;
end
else if (SEL_change) begin
SEL <= {SEL[4:0], SEL[5]};
end
else begin
SEL <= SEL;
end
end
// DIG信号输出
always @(*) begin
case (SEL)
6'b111_110 : DIG = 8'b1111_1001;
6'b111_101 : DIG = 8'b0010_0100;
6'b111_011 : DIG = 8'b1011_0000;
6'b110_111 : DIG = 8'b0001_1001;
6'b101_111 : DIG = 8'b1001_0010;
6'b011_111 : DIG = 8'b1000_0010;
default: DIG = 8'b1111_1111;
endcase
end
endmodule
实现目标:让 6 位数码管从右往左滑动显示 0 - 9,当显示 9 时,后面连续显示 “-”,直到 9 消失,又从 0 开始滑动
module dig_demo #(parameter MS_1 = 17'd100_000,
MS_200 = 25'd2500_0000)(
input clk , // 50MHz时钟
input rst_n , // 复位信号
output reg [ 7:0] DIG , // 输出DIG
output reg [ 5:0] SEL // 输出SEL
);
// 信号定义
reg [16:0] cnt_flicker ; // SEL刷新频率计数器
wire [ 0:0] end_cnt_flicker ; // cnt_flicker停止计数信号
reg [24:0] cnt_200ms ; // 200ms计数器
wire [ 0:0] end_cnt_200ms ; // cnt_200ms停止计数使能信号
reg [ 3:0] cnt_16state ; // 16 个状态计数器
wire [ 0:0] end_cnt_16state ; // 结束计时
reg [ 2:0] SEL_now ; // SEL现态
wire [ 4:0] cnt_16state_and_SEL_now ; // cnt_16state + SEL_now
reg [ 1:0] DIG_now_status ; // DIG现态所处的情况
reg [ 3:0] DIG_now ; // DIG现态
// 逻辑实现
// SEL刷新频率计数器
/*
每过10_0000个时钟周期刷新SEL值
*/
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_flicker <= 17'd0;
end
else if (end_cnt_flicker) begin
cnt_flicker <= 17'd0;
end
else begin
cnt_flicker <= cnt_flicker + 17'd1;
end
end
assign end_cnt_flicker = cnt_flicker >= MS_1 - 17'd1 ? 1'b1 : 1'b0;
// SEL现态
/*
当cnt_flicker计数到最大值10_0000个时钟周期后
SEL就从第n位跳到第n+1位
当跳到第5位后,就回到第0位
*/
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
SEL_now <= 'd0;
end
else if (end_cnt_flicker) begin
if (SEL_now >= 'd5) begin
SEL_now <= 'd0;
end
else begin
SEL_now <= SEL_now + 'd1;
end
end
else begin
SEL_now <= SEL_now;
end
end
// SEL信号输出
/*
根据SEL_now选择SEL输出二进制
*/
always @(*) begin
case (SEL_now)
3'd0 : SEL = 6'b111_110;
3'd1 : SEL = 6'b111_101;
3'd2 : SEL = 6'b111_011;
3'd3 : SEL = 6'b110_111;
3'd4 : SEL = 6'b101_111;
3'd5 : SEL = 6'b011_111;
default: SEL = 6'b111_111;
endcase
end
// cnt_200ms
/*
计数200ms
每过200ms,数码管就向左滑动一下
*/
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_200ms <= 'd0;
end
else if (end_cnt_200ms) begin
cnt_200ms <= 'd0;
end
else begin
cnt_200ms <= cnt_200ms + 'd1;
end
end
assign end_cnt_200ms = cnt_200ms >= MS_200 - 'd1 ? 1'b1 : 1'b0;
// cnt_16state
/*
数码管滑动分为16个状态
每个状态持续200ms
*/
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_16state <= 'd0;
end
else if (end_cnt_200ms) begin
if (end_cnt_16state) begin
cnt_16state <= 'd0;
end
else begin
cnt_16state <= cnt_16state + 'd1;
end
end
else begin
cnt_16state <= cnt_16state;
end
end
assign end_cnt_16state = cnt_16state >= 4'd15 ? 1'b1 : 1'b0;
// 计算当前16个状态中的一个状态值与SEL现在位数的和
assign cnt_16state_and_SEL_now = cnt_16state + SEL_now;
// DIG现态值判断
/*
根据上面计算出的和
判断当前数码管处于3个状态中的哪一个状态
0:数码管每一位显示数字
1:数码管前面显示数字,后面显示"-"
2:数码管前面显示"-",后面显示数字
*/
always @(*) begin
if (cnt_16state_and_SEL_now > 9 && cnt_16state_and_SEL_now < 16) begin
DIG_now_status <= 'd1;
end
else if (cnt_16state_and_SEL_now >= 16) begin
DIG_now_status <= 'd2;
end
else begin
DIG_now_status <= 'd0;
end
end
// DIG现态
/*
根据前面状态的判断计算出当前SEL所对应的DIG的值
*/
always @(*) begin
if (DIG_now_status == 'd0) begin
DIG_now = cnt_16state_and_SEL_now;
end
else if (DIG_now_status == 'd1) begin
DIG_now = 'd10;
end
else if (DIG_now_status == 'd2) begin
DIG_now = cnt_16state_and_SEL_now - 'd16;
end
else begin
DIG_now = DIG_now;
end
end
// DIG对应数字输出
always @(*) begin
case (DIG_now)
4'd0 : DIG = 8'b1100_0000;
4'd1 : DIG = 8'b1111_1001;
4'd2 : DIG = 8'b1010_0100;
4'd3 : DIG = 8'b1011_0000;
4'd4 : DIG = 8'b1001_1001;
4'd5 : DIG = 8'b1001_0010;
4'd6 : DIG = 8'b1000_0010;
4'd7 : DIG = 8'b1111_1000;
4'd8 : DIG = 8'b1000_0000;
4'd9 : DIG = 8'b1001_0000;
4'd10 : DIG = 8'b1011_1111;
default : DIG = 8'b1111_1111;
endcase
end
endmodule
数码管滑动显示
需求分析:
原理讲解:
FPGA多功能数字时钟