FPGA初学记录——数字时钟系统搭建(上)
野火征途Pro开发板教程——数码管动态展示拓展训练,数字时钟系统搭建
文章目录
前言
最近在学习FPGA,因为研究生需要用到,所用的开发板是野火的征途Pro,也是在跟着野火的教程学习,学到了数码管动态展示,看到拓展训练,是一个挺适合练手的题目,主要是利用各个模块,比较锻炼模块化设计的思路,感觉有必要记录下来,说不定以后会有用。
整个rtl代码网盘链接:
链接:https://pan.baidu.com/s/1HiunRrvUADUQ4vlM0RRmaA
提取码:0101
–来自百度网盘超级会员V4的分享
一、问题简述
1、更改数据生成模块,生成一个时钟(时、分、秒)显示在数码管上。
2、在1的基础上加入按键信号,可通过按键去设置时钟值。
本文先解决第一个问题
二、功能解析和源代码
1.功能解析
1、首先思路呢,学习完野火给的动态显示数码管之后,我们会明白如何在数码管上动态显示0-999999的数字,然后再看这个题目。会发现很多模块可以直接拿过来用,只需要更改data_gen这个模块,更改数据的产生方式就ok。
2、例程从0-99999的思路很简单,就是计时然后累加,但是数字时钟的思路会稍微复杂一些,这里就提一下我自己思考的一个思路:
a、参考例程的思路,把想要展示的数据用二进制表示出来,然后传入到一系列模块(下文统称为显示模块)中,就能把数据以10进制的方式展示出来,所以我们第一个想法是怎么把我们想要的数据用二进制表示出来。
b、0-999999,思路就是不停累加,而我们想要的数据比较特殊,将这个数据分为3个部分,时、分、秒,秒一直累加,一直到59归零,同时分加1,当分加到59的时候也归零,时加1,当时加到24的时候归零,从头开始,好,我们的主要思路就是这样定了。
c、得到了时、分、秒各自的数字,怎么把他们弄成一块呢,我的思路就是,最终的数据=时10000+分100+秒,当然了,这是10进制的,二进制也是一样的,但是二进制我们需要考虑的问题就是verilog没有直接的乘法,得自己写一个乘法器。
d、按照上述的说法,写一个基本的乘法器,然后加上亿点点细节,从乘法器然后往上推,就可以最后实现这个功能啦。
3、综上呢,本文主要分享数据生成data_gen代码和乘法器multiply代码(备注,为了更好了看到问题,所以缩短了数据更新时间,正常时钟的话是每1s变化,但代码里我设成了比较短,好像只有100ms还是10ms,之后改的话只需要改CNT_MAX就可以了)
2.源代码——简单乘法器
先把简单乘法器代码奉上:非最终代码里的内容,只是一个基础,最终乘法器代码是在该代码的基础上修改的
module multiply
(
input wire sys_clk,
input wire sys_rst_n,
input wire [19:0] data1,//被乘数
input wire [19:0] data2,//乘数
output wire [19:0] result
);
reg [4:0] num;//判断第几位
reg [19:0] data_reg;//存储中间变量
reg [19:0] sum;//结果
reg [19:0] data1_reg;
reg [19:0] data2_reg;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
num <= -1'd1;
else if(num == 5'd20)
num <= 5'd20;
else
num <= num + 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_reg <= 20'd0;
else if(data2[num] == 1'b0)
data_reg <= 20'd0;
else if(data2[num] == 1'b1)
data_reg <= data1 << (num);
else
data_reg <= data_reg;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sum <= 20'd0;
else if(num == 5'd20)
sum <= sum;
else
sum <= sum + data_reg;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data1_reg <= 20'd0;
else
data1_reg <= data1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data2_reg <= 20'd0;
else
data2_reg <= data2;
assign result = sum;
endmodule
乘法器的思路比较简单,简单来说就是:判断,移位,相加,可以直接手写几个二进制相乘的例子,仔细观察就可以发现规律,本人实在太懒,这里就不再花大篇内容阐述乘法器的思路了,以后有空了再说吧。然后就是想验证自己写的乘法器是否正确的话,可以写一个仿真验证代码的代码,缩短调试时间,观察波形和结果来验证,这个过程还是能学到很多的。
2.源代码——数字时钟的乘法器
同样先把代码奉上
module multiply
#(
parameter CNT_MAX = 25'd499_99_99, // 1000ms计数值
parameter DATA_CARRY_MAX = 20'd10 // 进位最大值
)
(
input wire sys_clk , // 时钟信号
input wire sys_rst_n , // 复位信号
input wire [19:0] data_multiplicand , // 被乘数
input wire [19:0] data_multiplier , // 乘数
output wire [19:0] result // 计算结果
);
/***乘法器变量***/
reg [4:0] num ; // 乘数的位的索引,因为乘数和被乘数都有20位,所以num设定为5位,最大值为31,大于20
reg [19:0] data_reg ; // 存储计算过程中移位的中间变量
reg [19:0] sum ; // 累加结果
/***针对数码管显示时间的进位变量***/
reg [19:0] data_carry ; // 进位数据
reg data_carry_flag ; // 进位的标志位
/***计时器变量***/
reg [24:0] cnt_100ms ; // 100ms计数器
reg cnt_flag ; // 100ms标志信号
// cnt_100ms:用50MHz时钟从0到4999_999计数即为100ms
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_100ms <= 25'd0;
else if(cnt_100ms == CNT_MAX)
cnt_100ms <= 25'd0;
else
cnt_100ms <= cnt_100ms + 1'b1;
// cnt_flag:每100ms产生一个标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_flag <= 1'b0;
else if(cnt_100ms == CNT_MAX - 1'b1)
cnt_flag <= 1'b1;
else
cnt_flag <= 1'b0;
// 最小单元显示的数据:0-60
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_carry <= 20'd0;
else if(data_carry == DATA_CARRY_MAX && cnt_flag == 1'b1)
data_carry <= 20'd0;
else if(cnt_flag == 1'b1)
data_carry <= data_carry + 1'b1;
else
data_carry <= data_carry;
// data_carry_flag——进位的标志位
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_carry_flag <= 1'b0;
else if(data_carry == DATA_CARRY_MAX && cnt_flag == 1'b1)
data_carry_flag <= 1'b1;
else
data_carry_flag <= 1'b0;
// num:每过一个时钟上升沿就累加,每次完成一次进位就清零
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
num <= -1'd1;
else if(num == 5'd20 && data_carry_flag == 1'b1)
num <= 5'd0;
else if(num == 5'd20)
num <= 5'd20;
else
num <= num + 1'b1;
// num:如果data_multiplier[num] == 1'b0,data_reg为0;如果data_multiplier[num] == 1'b1,data_reg为data_multiplicand左移num位
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_reg <= 20'd0;
else if(data_multiplier[num] == 1'b0)
data_reg <= 20'd0;
else if(data_multiplier[num] == 1'b1)
data_reg <= data_multiplicand << (num);
else
data_reg <= data_reg;
//每次移位之后对data_reg进行累加,并且在每一次进位的时候清零
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sum <= 20'd0;
else if(num == 20'd20 && data_carry_flag == 1'b1)
sum <= 20'd0;
else if(num == 20'd20)
sum <= sum;
else
sum <= sum + data_reg;
assign result = sum;
endmodule
最终乘法器的代码是在简单乘法器的基础上加了亿点点细节,这些细节真的是永远的痛,一开始学的时候我还觉得看波形没必要,直到经过这个题目之后,只能说:“真香”,很多看不到的东西可以通过仿真,看波形,直观的看出来,一开始我简单乘法器功能实现了,但不知道为什么一和数字时钟结合就出各种各样的问题,最后盯着波形一个个参数的看,结合代码分析问题,一步步把问题解决,也加了亿点点细节。
4.源代码——数据产生data_gen
同样先把代码奉上
module data_gen
#(
parameter CNT_MAX = 25'd49_99_99 , // 1000ms计数值
parameter DATA_MAX = 20'd999_999 , // 显示的最大值
parameter DATA_SECOND_MAX = 20'd59 , // 秒的最大值
parameter DATA_MINUTE_MAX = 20'd59 , // 分钟的最大值
parameter DATA_HOUR_MAX = 20'd24 // 小时的最大值
// parameter DATA_MAX1 = 18'd250_000
)
(
input wire sys_clk , // 系统时钟,频率50MHz
input wire sys_rst_n , // 复位信号,低电平有效
output wire [19:0] data , // 数码管要显示的值
output wire [5:0] point , // 小数点显示,高电平有效
output reg seg_en , // 数码管使能信号,高电平有效
output wire sign // 符号位,高电平显示负号
);
reg [24:0] cnt_100ms ; // 100ms计数器
reg cnt_flag ; // 100ms标志信号
reg [19:0] data_sencond ; // 秒的数据
reg [19:0] data_hour ; // 小时的数据
reg [19:0] hour_multiplicand ; // 小时的被乘数
reg [19:0] data_minute ; // 分钟的数据
reg [19:0] minute_multiplicand ; // 分钟的被乘数
reg sencond_minute_flag ; // 秒-分钟的标志位
reg minute_hour_flag ; // 分钟-小时的标志位
wire [19:0] result_minute ; // 计算的分钟的结果
wire [19:0] result_hour ; // 计算的小时的结果
// 不显示小数点以及负数
assign point = 6'b010_100 ;
assign sign = 1'b0 ;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
minute_multiplicand <= 20'd0;
else
minute_multiplicand <= 20'd100;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
hour_multiplicand <= 20'd0;
else
hour_multiplicand <= 20'd10000;
// cnt_100ms:用50MHz时钟从0到4999_999计数即为100ms
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_100ms <= 25'd0;
else if(cnt_100ms == CNT_MAX)
cnt_100ms <= 25'd0;
else
cnt_100ms <= cnt_100ms + 1'b1;
// cnt_flag:每100ms产生一个标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_flag <= 1'b0;
else if(cnt_100ms == CNT_MAX - 1'b1)
cnt_flag <= 1'b1;
else
cnt_flag <= 1'b0;
// 秒显示的数据:0-60
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_sencond <= 20'd0;
else if(data_sencond == DATA_SECOND_MAX && cnt_flag == 1'b1)
data_sencond <= 20'd0;
else if(cnt_flag == 1'b1)
data_sencond <= data_sencond + 1'b1;
else
data_sencond <= data_sencond;
//sencond_minute_flag:秒-分钟的标志位
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sencond_minute_flag <= 1'b0;
else if(data_sencond == DATA_SECOND_MAX && cnt_flag == 1'b1)
sencond_minute_flag <= 1'b1;
else
sencond_minute_flag <= 1'b0;
// 分钟显示的数据:0-60
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_minute <= 20'd0;
else if(data_minute == DATA_MINUTE_MAX && sencond_minute_flag == 1'b1)
data_minute <= 20'd0;
else if(sencond_minute_flag == 1'b1)
data_minute <= data_minute + 1'b1;
else
data_minute <= data_minute;
//minute_hour_flag:分钟-小时的标志位
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
minute_hour_flag <= 1'b0;
else if(data_minute == DATA_MINUTE_MAX && sencond_minute_flag == 1'b1)
minute_hour_flag <= 1'b1;
else
minute_hour_flag <= 1'b0;
// 小时显示的数据:0-24
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_hour <= 20'd0;
else if(data_hour == DATA_HOUR_MAX && minute_hour_flag == 1'b1)
data_hour <= 20'd0;
else if(minute_hour_flag == 1'b1)
data_hour <= data_hour + 1'b1;
else
data_hour <= data_hour;
// 数码管使能信号给高即可
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
seg_en <= 1'b0;
else
seg_en <= 1'b1;
assign data = result_hour + result_minute + data_sencond;
multiply
#(
.DATA_CARRY_MAX(DATA_MINUTE_MAX)
)
multiply_inst
(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.data_multiplier(data_minute),
.data_multiplicand(minute_multiplicand),
.result(result_minute)
);
multiply
#(
.DATA_CARRY_MAX(DATA_HOUR_MAX)
)
multiply_inst0
(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.data_multiplier(data_hour),
.data_multiplicand(hour_multiplicand),
.result(result_hour)
);
endmodule
代码思路就跟一开始的思路一样,先分时、分、秒,然后利用乘法器,然后再相加获得想要展示的数据。
三、总结
大体内容就是这样啦,因为不是很熟练,整个功能的全部实现花了比较多的时间,每个功能或许能单独实现,但是整合在一起总会出现一些小问题,不过问题解决了心情还是很好滴,代码格式得注意可读性,一开始代码太乱了,给自己看糊涂了,最后整理下代码,定义定义放一块,功能功能放一块,看起来舒服多了.
这道题的第一问总算完成了,很多细节都没有写出来,一方面是我懒,一方面是我还不清楚怎么组织起来会更有条理,之后把思路理清楚再说叭。这道题学到的最多的呢是看波形调试代码,还有写仿真代码,过程艰辛,不过还是搞出来啦,欧耶,接着搞第二个问题去了。