功能说明
本文实现一个采用同步计数,具有暂停以及用11位控制字进行时分秒置位功能的24h数字时钟。
问题分析及模块实现
数字时钟由时分秒三部分组成,为此需要设计分频器、模24的计数器和模60的计数器,此外为了在多位共阴极数码管上显示,还需要设计数码管的驱动电路,最后,为实现全置位的功能,设计了11位控制字来对复位进行控制。
模24计数器
一般模24计数器只需要5位二进制状态码即可实现,但考虑到数字时钟应用的特殊性,即需要两个数码管来显示,其中每一个数码管的显示信号均需要4位二进制数译码得到。因此,这里采用8位二进制状态码来设计,高4位为十位,低4位为个位。
//8421BCD码计数器,模24
module counter24(clk, rst_n, en, dout,set,set_flag);
input clk, rst_n, en;
input set_flag;
input[7:0] set;
output[7:0] dout;
reg[7:0] dout;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) //复位信号有效时,置位
if(set_flag)
dout <= set[7:0];
else
dout<=dout;
else if(en == 1'b0) //计数使能无效时,输出不变
dout <= dout;
else if( (dout[7:4] == 4'b0010)&&(dout[3:0] == 4'b0011) ) //计数达到23时,输出清零
dout <= 8'b00000000;
else if(dout[3:0] == 4'b1001) //低位达到9时,低位清零,高位加1
begin
dout[3:0] <= 4'b0000;
dout[7:4] <= dout[7:4] + 1'b1;
end
else //上述情况都没有发生,则高位不变,低位加1
begin
dout[7:4] <= dout[7:4];
dout[3:0] <= dout[3:0] + 1'b1;
end
end
endmodule
值得说明的是这里为了后续暂停、进位以及自定义置位等功能的实现,在每个计数器中都加入了使能信号en、置位标识set_flag、置位信号set输入和进位信号co输出。
模60计数器
这里所设计的模60计数器由一个模6计数器和一个模10计数器组成,其中模6计数器的4位状态输出表示十位,模10计数器的4位状态输出表示个位,两者组合产生模60计数器的8位输出。
模6计数器
//模6计数器模块
module counter6(clk, rst_n, en, dout, co,set,set_flag);
input clk, rst_n, en;
input set_flag;
input [3:0]set;
output[3:0] dout;
reg [3:0] dout;
output co;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
if(set_flag)
dout <= set[3:0];
else
dout<=dout; //系统置位
else if(en)
if(dout == 4'b0101) //计数值达到5时,计数器清零
dout <= 4'b0000;
else
dout <= dout + 1'b1; //否则,计数器加1
else
dout <= dout;
end
assign co = dout[0]&dout[2]; //当计数达到5(4'b1001)时,进位为1,计数值为其他,都没有进位
endmodule
模10计数器
//模10计数器模块
module counter10(clk, rst_n, en, dout, co,set,set_flag);
input clk, rst_n, en;
input set_flag;
input[3:0] set;
output[3:0] dout;
reg [3:0] dout;
output co;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n )
if(set_flag)
dout <= set[3:0];
else
dout<=dout; //系统复位,计数器置为8(展示进位)
else if(en)
if(dout == 4'b1001) //计数值达到9时,计数器清零
dout <= 4'b0000;
else
dout <= dout + 1'b1; //否则,计数器加1
else
dout <= dout;
end
assign co = dout[0]&dout[3]; //当计数达到5(4'b1001)时,进位为1,计数值为其他,都没有进位
endmodule
模60计数器
//模60计数器的Verilog HDL设计
module counter60(clk, rst_n, en, dout, co,set,set_flag);
input clk, rst_n, en;
input set_flag;
input[7:0] set;
output[7:0] dout;
output co;
wire co10_1, co10, co6;
wire[3:0] dout10, dout6;
counter10 inst_counter10(.clk(clk), .rst_n(rst_n), .en(en), .dout(dout10), .co(co10_1),.set(set[3:0]),.set_flag(set_flag)); //模10计数器的进位为co10_1
and u3(co10,en,co10_1); //co10_1与en的与为co10
counter6 inst_counter6(.clk(clk), .rst_n(rst_n), .en(co10), .dout(dout6), .co(co6),.set(set[7:4]),.set_flag(set_flag)); //co10_1与en的与为co10,作为模6计数器的使能信号
and u4(co, co10, co6); //模6计数器的进位和模6的使能信号co10的与作为模60计数器的进位
assign dout = {dout6,dout10}; //模60计数器的输出,高位为模6计数器的输出,低位为模10计数器的输出,读法是8421BCD码读法
endmodule
此处说明一点,当两个计数器连接在一起的时候,低位计数器的进位信号与低位计数器的使能信号做与运算得到高位计数器的使能信号。此时clk信号都是统一直接连接在各计数器的clk端上的,故所实现的计数器是同步计数器。
数码管驱动电路
由于数码管的显示频率与数字时钟频率不同,故在其内部需要实现一个分频,来产生数码管的片选信号。实现的原理是将一个18位二进制计数器的高3位状态译码,作为片选。值得一提的是这里有6个数码管,而高3位可以对8个数码管进行片选,所以在计数过程中还应该实现一个状态的跳转。
//数码管显示模块
module digital_tube_display(
input clk,
input rst_n,
input [3:0] hex0, //第一个数码管显示的数字
input [3:0] hex1,
input [3:0] hex2,
input [3:0] hex3,
input [3:0] hex4,
input [3:0] hex5,
output reg [5:0] an, //数码管选择
output reg [7:0] sseg //共阴极数码管信号
);
reg [3:0] hex_in; //进入的数字
localparam N = 17;//数码管分频
reg [N-1:0] regN;
always@(posedge clk)
begin
regN <= regN + 1'b1;
if (regN[N-1:N-3] == 3'b110) regN[N-1:N-3] <= 3'b000;//跳过110,111的情况
end
always@ *
begin
case(regN[N-1:N-3])//使用分频信号的高三位作为数码管选择信号
3'b000:begin
an = 6'b111110; //选中第1个数码管
hex_in = hex0; //数码管显示的数字由hex_in控制,显示hex0输入的数字;
sseg[7] =0;
end
3'b001:begin
an = 6'b111101; //选中第2个数码管,以此类推
hex_in = hex1;
sseg[7] =0;
end
3'b010:begin
an = 6'b111011;
hex_in = hex2;
sseg[7] =1;//这里需要设置小数点
end
3'b011: begin
an = 6'b110111;
hex_in = hex3;
sseg[7] =0;
end
3'b100: begin
an = 6'b101111;
hex_in = hex4;
sseg[7] =1;//这里需要设置小数点
end
3'b101: begin
an = 6'b011111;
hex_in = hex5;
sseg[7] =0;
end
default:begin
an = 6'b111111;
hex_in = 0;
sseg[7] =0;
end
endcase
end
always@ (*)
begin
case(hex_in)
4'h0: sseg[6:0] = 7'b1111110; //共阴极数码管
4'h1: sseg[6:0] = 7'b0110000;
4'h2: sseg[6:0] = 7'b1101101;
4'h3: sseg[6:0] = 7'b1111001;
4'h4: sseg[6:0] = 7'b0110011;
4'h5: sseg[6:0] = 7'b1011011;
4'h6: sseg[6:0] = 7'b1011111;
4'h7: sseg[6:0] = 7'b1110000;
4'h8: sseg[6:0] = 7'b1111111;
4'h9: sseg[6:0] = 7'b1111011;
4'ha: sseg[6:0] = 7'b1110111;
4'hb: sseg[6:0] = 7'b0011111;
4'hc: sseg[6:0] = 7'b1001110;
4'hd: sseg[6:0] = 7'b0111101;
4'he: sseg[6:0] = 7'b1001111;
4'hf: sseg[6:0] = 7'b1000111;
endcase
end
endmodule
11位控制位说明
D10 | D9 | D8 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|---|---|
hour | min | sec | H3 | H2 | H1 | H0 | L3 | L2 | L1 | L0 |
hour:小时置位使能信号,=1时对小时置位,=0时不对小时置位。
min:分钟置位使能信号,=1时对分钟置位,=0时不对分钟置位。
sec:秒置位使能信号,=1时对秒置位,=0时不对秒置位。
H3~0:高4位置位信号。
L3~0:低4位置位信号。
注意当hour、min、sec同时为1时,时分秒会同时置为HL上表示的数,可以用来同步置0.
分频器
//分频
module demultiplication(clk,clk_out);
input clk;
output clk_out;
reg clk_out;
localparam Num=22;
reg [Num-1:0] count;
always @ (posedge clk) begin
count <= count + 1'b1;
end
always @ (posedge clk) begin
if(count == 1)begin
clk_out <= ~clk_out;
end
else begin
clk_out<=clk_out;
end
end
endmodule
数字时钟
//数字时钟
module digital_clock(
input clk,
input rst_n,
input en,
input[10:0] setting,
output [7:0] hour,
output [7:0] min,
output [7:0] sec
);
//用两个模60计数器作为分秒信号
//用一个模24计数器作为小时信号
//计数器之间进位用co信号与使能信号与来得到
wire co_sec1,co_sec,co_min,co_min1;
counter60 inst_sec(.clk(clk), .rst_n(rst_n), .en(en), .dout(sec), .co(co_sec1),.set_flag(setting[8]),.set(setting[7:0]));
and inst_and_sec(co_sec,en,co_sec1);
counter60 inst_min(.clk(clk), .rst_n(rst_n), .en(co_sec), .dout(min), .co(co_min1),.set_flag(setting[9]),.set(setting[7:0]));
and inst_and_min(co_min,co_sec,co_min1);
counter24 inst_hour(.clk(clk), .rst_n(rst_n), .en(co_min), .dout(hour),.set_flag(setting[10]),.set(setting[7:0]));
endmodule
其中,en为使能,为0时时钟暂停。setting为11位控制位输入。当rst_n有一个负脉冲时根据控制位信息对时钟置位,为1时时钟正常工作。
顶层文件实现
module TOP(
input clk,
input rst_n,
input en,
input[10:0] setting, //11位控制位
//S10为小时置位使能
//S9为分钟置位使能
//S8为秒置位使能
//S7~S4为十位数字
//S3~S0为个位数字
output [5:0] an, //数码管选择
output [7:0] sseg //数码管显示
);
wire [7:0] hour;
wire [7:0] min;
wire [7:0] sec;
wire clk_p;//clk_p为数字时钟所使用的时钟分频信号
//时钟分频
demultiplication inst_demultiplication(
.clk(clk),
.clk_out(clk_p)
);
digital_clock inst_digital_clock(
.clk(clk_p),
.rst_n(rst_n),
.en(en),
.setting(setting),
.hour(hour),
.min(min),
.sec(sec)
);
wire [3:0] hex0; //第一个数码管显示的数字,以此类推
wire [3:0] hex1;
wire [3:0] hex2;
wire [3:0] hex3;
wire [3:0] hex4;
wire [3:0] hex5;
//sec
assign hex0 = sec[3:0];
assign hex1 = sec[7:4];
//min
assign hex2 = min[3:0];
assign hex3 = min[7:4];
//hour
assign hex4 = hour[3:0];
assign hex5 = hour[7:4];
//数码管显示,注意数码管和时钟使用的是不同的分频时钟信号
digital_tube_display inst_digital_tube_display(
.clk(clk),
.rst_n(rst_n),
.hex0(hex0),
.hex1(hex1),
.hex2(hex2),
.hex3(hex3),
.hex4(hex4),
.hex5(hex5),
.an(an),
.sseg(sseg)
);
endmodule
代码中clk为系统自带时钟。此处的分频是写死的,可以根据实际需要去改分频器和驱动电路里面的localparam。频率太快会使得显示不正常,注意。
写在后面
学校数电实验总设计的代码,顺便放了上来,写代码的过程中参考了网上一些大佬的代码和思路,某些功能的实现可能会比较相似,给有需要的同学做一个参考吧。
不点个赞吗?
要工程文件可以评论留邮箱