FPGA初学记录——数字时钟系统搭建(上)

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

代码思路就跟一开始的思路一样,先分时、分、秒,然后利用乘法器,然后再相加获得想要展示的数据。

三、总结

大体内容就是这样啦,因为不是很熟练,整个功能的全部实现花了比较多的时间,每个功能或许能单独实现,但是整合在一起总会出现一些小问题,不过问题解决了心情还是很好滴,代码格式得注意可读性,一开始代码太乱了,给自己看糊涂了,最后整理下代码,定义定义放一块,功能功能放一块,看起来舒服多了.
这道题的第一问总算完成了,很多细节都没有写出来,一方面是我懒,一方面是我还不清楚怎么组织起来会更有条理,之后把思路理清楚再说叭。这道题学到的最多的呢是看波形调试代码,还有写仿真代码,过程艰辛,不过还是搞出来啦,欧耶,接着搞第二个问题去了。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值