一、设计指标(都是坤坤指定的,我直接贴)
二、计时器原理:
三、Verilog实现
十进制计数器:
六进制计数器:
三、六十进制计数器:
4、计时器:
1Hz-toc" style="margin-left:40px;">6.时钟分频模块:50MHz->1Hz
7.数字管动态扫描模块:
8、BCD码译码模块:
显示模块:-toc" style="margin-left:40px;">9、LED显示模块:
10、顶层模块:
四、FPGA实现
1.管脚约束
2、实验效果
一、设计指标(同样的都是鲲鲲指定的,我直接贴过来)
在选择Basys 2/3型开发板,使用Verilog语言描述,计时器的设计,功能要求如下:
- 用Verilog语言描述,功能电路设计;
- 用数字管做计时器,四个数字管
- 开始计时和复位拨码开关(无约束)
- 两种工作模式
- 可从0开始计时,从00:00开始计时。
- 也可以自己设置倒数计时,比如从05:00到00:00的倒数计时。
- 假设输入晶振50MHz;
- 完成计时器的设计、前仿和后仿。
分(十位) |
分(个位) |
秒(十位) |
秒(个位) |
二、计时器原理:
笔者在网上找到了现有的方案,认为60进制计数器是由六进制计数器和十进制计数器建造的,然后两个60进制计数器在1Hz时钟下连接称为分秒计时器的思路比较清晰,所以作者的设计是按照这个思路来的。
系统框图如下:
具体来说,首先构建十进制计数器和六进制计数器,然后将十进制计数器作为六进制计数器的个位,六进制计数器作为十进制计数器。每个60进制计数器都有使能端和复位端。控制秒的60进制计数器的进位信号连接到控制分的60进制计数器的使能端。每个60进制计数器中的10进制计数器和6进制计数器输出的数字为4位BCD代码,这样我们就可以用我们之前解释的通用数字管显示模块显示,高两位显示0~59,低两位为秒显示0~59;
三、Verilog实现
十进制计数器:
常用的计数控制
module counter_10( input clk, input rst_n, input en,//使能 output reg [3:0]dout, output co//进位 ); //输出BCD码 always@(posedge clk or negedge rst_n) begin if(!rst_n) dout <= 4'd0; else if(en) begin if(dout == 4'd9) dout <= 4'd0; else dout <= dout 4'd1; end else dout <= dout; end //当dout = 1001时产生进位 assign co = dout[0] & dout[3]; endmodule
六进制计数器:
同十进制计数器的思路
module counter_6( input clk, input rst_n, input en,//使能 output reg [3:0]dout, output co//进位 ); //输出BCD码 always@(posedge clk or negedge rst_n) begin if(!rst_n) dout <= 4'd0; else if(en) begin if(dout == 4'd5) dout <= 4'd0; ele
dout <= dout + 4'd1;
end
else
dout <= dout;
end
//当dout = 0101时产生进位
assign co = dout[0] & dout[2];
endmodule
3、六十进制计数器:
例化十进制计数器和六进制计数器,其中的进位控制逻辑需要主义
module counter_60(
input clk,
input rst_n,
input en,//使能
output [7:0]dout,
output co//进位
);
//用一个十进制计数器和一个六进制计数器来构成60进制计数器
//声明内部信号线
wire co_10_1;//模10计数器的进位输出信号
wire co_10;//模6计数器的选通信号
wire co_6;//模6计数器的进位输出信号
wire [3:0]dout_10,dout_6;
//模块实例化
//个位模十
counter_10 c_10(
.clk(clk),
.rst_n(rst_n),
.en(en),
.dout(dout_10),
.co(co_10_1));
//十位模六
counter_6 c_6(
.clk(clk),
.rst_n(rst_n),
.en(co_10),
.dout(dout_6),
.co(co_6));
//产生进位信号与八位输出
assign co_10 = co_10_1 & en;
assign co = co_10 & co_6;
assign dout = {dout_6,dout_10};
endmodule
4、计时器:
在计时器中例化两个六十进制计数器
module Timer(
input clk,
input rst_n,
input en,
output [7:0]min_out,
output [7:0]sec_out
);
//用两个模60计数器作为分、秒的信号
//声明内部信号线
wire co_sec_1,co_sec;
//模块实例化
counter_60 c_60_sec(
.clk(clk),
.rst_n(rst_n),
.en(en),
.dout(sec_out),
.co(co_sec_1));
counter_60 c_60_min(
.clk(clk),
.rst_n(rst_n),
.en(co_sec),
.dout(min_out));
//产生进位
assign co_sec = co_sec_1 & en;
endmodule
5、时钟分频:50MHz->1KHz
将板子50MHz时钟分频称为1KHz时钟,用于数码管动态扫描
module clk_divider_1KHz(
input clk,
input rst_n,
output reg clk_div
);
//输入板子50MHz时钟,输出1KHz时钟
//计数分频:50000次
reg [15:0]cnt = 16'd0;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
cnt <= 16'd0;
end
else if(cnt == 16'd50000)
begin
cnt <= 16'd0;
end
else
begin
cnt <= cnt + 16'd1;
end
end
//输出时钟控制
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
clk_div <= 1'b0;
end
else if(cnt <= 16'd24999)
begin
clk_div <= 1'b1;
end
else
begin
clk_div <= 1'b0;
end
end
endmodule
6、时钟分频模块:50MHz->1Hz
将板子50MHz时钟分频为1Hz时钟,用于计时器计数
module clk_divider_1Hz(
input clk,
input rst_n,
output reg clk_div
);
//输入板子50MHz时钟,输出1KHz时钟
//计数分频:50000次
reg [25:0]cnt = 26'd0;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
cnt <= 26'd0;
end
else if(cnt == 26'd50_000_000)
begin
cnt <= 26'd0;
end
else
begin
cnt <= cnt + 26'd1;
end
end
//输出时钟控制
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
clk_div <= 1'b0;
end
else if(cnt <= 26'd24_999_999)
begin
clk_div <= 1'b1;
end
else
begin
clk_div <= 1'b0;
end
end
endmodule
7、数码管动态扫描模块:
输入1KHz时钟进行动态扫描,通过扫描计数器的状态选择相应的位并输出相应的段选码
module Display(
input clk_div,
input rst_n,
input [7:0]Out_number_1,
input [7:0]Out_number_2,
input [7:0]Out_number_3,
input [7:0]Out_number_4,
output reg [7:0]duan_code,
output reg [3:0]wei_code
);
//将输入的十六进制代码转换为共阳极数码管段选码动态扫描输出
reg [2:0]sel_cnt;//扫描计数器
//动态扫描
//扫描计数器控制位选
always@(posedge clk_div or negedge rst_n)
begin
if(!rst_n)
begin
sel_cnt <= 3'd0;
end
else if(sel_cnt == 3'd4)
begin
sel_cnt <= 3'd0;
end
else
begin
sel_cnt <= sel_cnt + 3'd1;
end
end
//位选与段选对应
always@(posedge clk_div or negedge rst_n)
begin
if(!rst_n)
begin
wei_code <= 4'b0000;
duan_code <= 8'hff;
end
else
begin
case(sel_cnt)
3'd0:
begin
wei_code <= 4'b0001;
duan_code <= Out_number_1;
end
3'd1:
begin
wei_code <= 4'b0010;
duan_code <= Out_number_2;
end
3'd2:
begin
wei_code <= 4'b0100;
duan_code <= Out_number_3;
end
3'd3:
begin
wei_code <= 4'b1000;
duan_code <= Out_number_4;
end
default:
begin
wei_code <= 4'b0000;
duan_code <= 8'hff;
end
endcase
end
end
endmodule
8、BCD码译码模块:
将计数器输出的BCD码转化为共阳极数码管的段选码,用于动态扫描显示
module wei_encoder(
input [3:0]hex_number,
output reg [7:0]display_code
);
//将输入的十六进制代码转换为共阳极数码管段选编码并输出
always@(*)
begin
case(hex_number)
4'b0000:display_code = 8'hc0;//0
4'b0001:display_code = 8'hf9;//1
4'b0010:display_code = 8'ha4;//2
4'b0011:display_code = 8'hb0;//3
4'b0100:display_code = 8'h99;//4
4'b0101:display_code = 8'h92;//5
4'b0110:display_code = 8'h82;//6
4'b0111:display_code = 8'hf8;//7
4'b1000:display_code = 8'h80;//8
4'b1001:display_code = 8'h90;//9
4'b1010:display_code = 8'h88;//A
4'b1011:display_code = 8'h83;//B
4'b1100:display_code = 8'hc6;//C
4'b1101:display_code = 8'ha1;//D
4'b1110:display_code = 8'h86;//E
4'b1111:display_code = 8'h8e;//F
default:display_code = 8'hff;//无
endcase
end
endmodule
9、LED显示模块:
突发奇想想用8个LED实现一个流水灯,计时一秒灯位移一次
module led_light(
input clk,//1Hz时钟
input rst_n,
output reg [7:0]led
);
//初始化
initial
begin
led = 8'b0000_0001;
end
//循环位移
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
led <= 8'h00;
else
begin
led[7:0] <= {led[6:0],led[7]};
end
end
endmodule
10、顶层模块:
module top(
input clk,
input rst_n,
input en,
output [3:0]wei_code,
output [7:0]duan_code,
output [7:0]led_display
);
//声明内部信号
wire [7:0]sec_out;//八位BCD数:秒
wire [7:0]min_out;//八位BCD数:分
wire clk_div_1000;//系统50MHz时钟->1KHz
wire clk_div_1;//系统50MHz时钟->1Hz
wire [7:0]output_number_1;//数码管右一段选码
wire [7:0]output_number_2;//数码管右二段选码
wire [7:0]output_number_3;//数码管右三段选码
wire [7:0]output_number_4;//数码管右四段选码
//模块实例化
//时钟分频,用于数码管扫描
clk_divider_1KHz div_1(
.clk(clk),
.rst_n(rst_n),
.clk_div(clk_div_1000)
);
//时钟分频,用于计数器计数
clk_divider_1Hz div_2(
.clk(clk),
.rst_n(rst_n),
.clk_div(clk_div_1)
);
//计时器:用于产生分、秒的压缩BCD码用于数码管扫描输出
Timer timer(
.clk(clk_div_1),
.rst_n(rst_n),
.en(en),
.min_out(min_out),
.sec_out(sec_out)
);
//将二进制数转换位段选码,用于数码管扫描输出
wei_encoder encoder_1(
.hex_number(sec_out[3:0]),
.display_code(output_number_1)
);
wei_encoder encoder_2(
.hex_number(sec_out[7:4]),
.display_code(output_number_2)
);
wei_encoder encoder_3(
.hex_number(min_out[3:0]),
.display_code(output_number_3)
);
wei_encoder encoder_4(
.hex_number(min_out[7:4]),
.display_code(output_number_4)
);
//数码管动态扫描模块,将段选码输出
Display display(
.clk_div(clk_div_1000),
.rst_n(rst_n),
.Out_number_1(output_number_1),
.Out_number_2(output_number_2),
.Out_number_3(output_number_3),
.Out_number_4(output_number_4),
.duan_code(duan_code),
.wei_code(wei_code)
);
//led流水灯,每一秒移动一次
led_light led(
.clk(clk_div_1),//1Hz时钟
.rst_n(rst_n),
.led(led_display)
);
endmodule
四、FPGA实现
1.管脚约束
根据开发板手册绑定管脚,管脚约束如下:
NET "led_display[7]" LOC = G1;
NET "led_display[6]" LOC = P4;
NET "led_display[5]" LOC = N4;
NET "led_display[4]" LOC = N5;
NET "led_display[3]" LOC = P6;
NET "led_display[2]" LOC = P7;
NET "led_display[1]" LOC = M11;
NET "led_display[0]" LOC = M5;
NET "wei_code[0]" LOC = F12;
NET "wei_code[1]" LOC = J12;
NET "wei_code[2]" LOC = M13;
NET "wei_code[3]" LOC = K14;
NET "duan_code[7]" LOC = N13;
NET "duan_code[6]" LOC = M12;
NET "duan_code[5]" LOC = L13;
NET "duan_code[4]" LOC = P12;
NET "duan_code[3]" LOC = N11;
NET "duan_code[2]" LOC = N14;
NET "duan_code[1]" LOC = H12;
NET "duan_code[0]" LOC = L14;
NET "clk" LOC = B8;
NET "en" LOC = P11;
NET "rst_n" LOC = N3;
2、实验效果
见视频(后续上传)
FPGA计时器