• 计数器(1):Verilog常用写法


      计数器是非常基本的使用,没有计数器就无法处理时序。我在学习时发现市面上有几种不同的计数器写法,非常有趣,在此记录下来:

    一、时序逻辑和组合逻辑彻底分开

    1.代码

     1 //======================================================================
     2 // --- 名称 : Count_1
     3 // --- 作者 : xianyu_FPGA
     4 // --- 日期 : 2018-12-10
     5 // --- 描述 : 模10计数器,0到10循环累加
     6 //======================================================================
     7 
     8 module Count_1
     9 (
    10     input               clk                 ,
    11     input               rst_n               ,
    12     output reg [ 3:0]   cnt
    13 );
    14 
    15 //----------------------------------------------------------------------
    16 //--   组合电路
    17 //----------------------------------------------------------------------
    18 reg [ 3:0]              cnt_n                ;
    19 
    20 always @(*)begin
    21     if(cnt == 4'd9)
    22         cnt_n = 4'd0; 
    23     else
    24         cnt_n = cnt + 1'b1;
    25 end
    26 
    27 //----------------------------------------------------------------------
    28 //--   时序电路
    29 //----------------------------------------------------------------------
    30 always @(posedge clk or negedge rst_n)begin
    31     if(!rst_n)
    32         cnt <= 4'b0;
    33     else
    34         cnt <= cnt_n;
    35 end
    36 
    37 endmodule
    38 
    39 /*
    40 //----------------------------------------------------------------------
    41 //--   组合电路也可以这样写
    42 //----------------------------------------------------------------------
    43 wire [ 3:0]             cnt_n               ;
    44 
    45 assign cnt_n = (cnt==4'd9)? 4'd0 : cnt+1'b1;
    46 
    47 */

    2.写法1的RTL视图

    3.写法2的RTL视图

    二、最常见的写法

    1.代码

     1 //======================================================================
     2 // --- 名称 : Count_2
     3 // --- 作者 : xianyu_FPGA
     4 // --- 日期 : 2018-12-10
     5 // --- 描述 : 模10计数器,0到10循环累加
     6 //======================================================================
     7 
     8 module Count_2
     9 (
    10     input               clk                 ,
    11     input               rst_n               ,
    12     output reg [ 3:0]   cnt
    13 );
    14 
    15 always @(posedge clk or negedge rst_n)begin
    16     if(!rst_n)
    17         cnt <= 4'd0;
    18     else if(cnt==4'd9)
    19         cnt <= 4'd0;
    20     else
    21         cnt <= cnt + 1'b1;
    22 end
    23 
    24 
    25 
    26 endmodule

    2.RTL视图

    三.代码片段写法

    1.代码

     1 //======================================================================
     2 // --- 名称 : Count_3
     3 // --- 作者 : xianyu_FPGA
     4 // --- 日期 : 2018-12-10
     5 // --- 描述 : 模10计数器,0到10循环累加
     6 //======================================================================
     7 
     8 module Count_3
     9 //---------------------<端口声明>---------------------------------------
    10 (
    11 input                   clk                 ,
    12 input                   rst_n               ,
    13 output reg [ 3:0]       cnt
    14 );
    15 //---------------------<信号定义>---------------------------------------
    16 wire                    add_cnt             ;
    17 wire                    end_cnt             ;
    18 
    19 //----------------------------------------------------------------------
    20 //--   0-9计数
    21 //----------------------------------------------------------------------
    22 always @(posedge clk or negedge rst_n)begin
    23     if(!rst_n)
    24         cnt <= 'd0;
    25     else if(add_cnt)begin
    26         if(end_cnt)
    27             cnt <= 'd0;
    28         else
    29             cnt <= cnt + 1'b1;
    30     end
    31     else
    32         cnt <= cnt;
    33 end
    34 
    35 assign add_cnt = 1;
    36 assign end_cnt = add_cnt && cnt==10-1;
    37 
    38 
    39 
    40 
    41 endmodule

    2.RTL视图

    四、自减计数器(较少用到)

    1.代码

     1 //======================================================================
     2 // --- 名称 : Count_4
     3 // --- 作者 : xianyu_FPGA
     4 // --- 日期 : 2018-12-19
     5 // --- 描述 : 模10自减计数器,10到0循环累减
     6 //======================================================================
     7 
     8 module Count_4
     9 //---------------------<端口声明>---------------------------------------
    10 (
    11 input                   clk                 ,
    12 input                   rst_n               ,
    13 output reg [ 3:0]       cnt
    14 );
    15 //---------------------<参数定义>---------------------------------------
    16 parameter CNT_MAX       = 10                ,
    17 
    18 //----------------------------------------------------------------------
    19 //--   10到0循环累减
    20 //----------------------------------------------------------------------
    21 always @(posedge clk or negedge rst_n)begin
    22     if(!rst_n) begin
    23         cnt <= 0;
    24     end
    25     else if(cnt==0) begin
    26         cnt <= CNT_MAX;
    27     end
    28     else begin
    29         cnt <= cnt - 1;
    30     end
    31  end
    32 
    33 
    34 endmodule

    2.RTL视图

    3.仿真波形

    五、新学到的一种非常简洁的计数器

      本以为计数器就是这样了,近来学习开源骚客《SDRAM那些事儿》系列教程,又发现一种新的写法,对于特定功能的实现上非常简洁。

    要求:

      现在对 OV5640 摄像头进行上电控制,由数据手册得到上电控制的时序图如下所示,用Verilog代码实现其波形。

    1、代码片段法

      代码片段法还是比较好用的,我平时用的最多,要实现这个时序图,我肯定会这样写:

      1 module power_ctrl
      2 //========================< 端口 >==========================================
      3 (
      4 //system --------------------------------------------
      5 input   wire                clk                     , // 50MHz
      6 input   wire                rst_n                   ,       
      7 //ov5640 --------------------------------------------
      8 output  reg                 ov5640_pwdn             , // ov5640上电
      9 output  reg                 ov5640_rst_n            , // ov5640复位
     10 output  reg                 power_done                // power_ctrl全面有效,SCCB可以开始工作
     11 );
     12 //========================< 参数 >==========================================
     13 localparam T2_6MS           = 30_0000               ; // T2>5ms
     14 localparam T3_2MS           = 10_0000               ; // T3>1ms
     15 localparam T4_21MS          = 105_0000              ; // T4>20ms
     16 //========================< 信号 >==========================================
     17 reg     [18:0]              cnt_6ms                 ;
     18 wire                        add_cnt_6ms             ;
     19 wire                        end_cnt_6ms             ;
     20 reg     [16:0]              cnt_2ms                 ;
     21 wire                        add_cnt_2ms             ;
     22 wire                        end_cnt_2ms             ;
     23 reg     [20:0]              cnt_21ms                ;
     24 wire                        add_cnt_21ms            ;
     25 wire                        end_cnt_21ms            ;      
     26 
     27 //==========================================================================
     28 //==    ov5640_pwdn
     29 //==========================================================================
     30 always @(posedge clk or negedge rst_n) begin
     31     if(!rst_n)
     32         cnt_6ms <= 'd0;
     33     else if(add_cnt_6ms) begin
     34         if(end_cnt_6ms)
     35             cnt_6ms <= 'd0;
     36         else
     37             cnt_6ms <= cnt_6ms + 1;
     38     end
     39 end
     40 
     41 assign add_cnt_6ms = ov5640_pwdn == 1'b1;
     42 assign end_cnt_6ms = add_cnt_6ms && cnt_6ms== T2_6MS-1;
     43 
     44 always @(posedge clk or negedge rst_n) begin
     45     if(!rst_n) begin
     46         ov5640_pwdn <= 1'b1;
     47     end
     48     else if(end_cnt_6ms) begin
     49         ov5640_pwdn <= 1'b0;
     50     end
     51 end
     52 
     53 //==========================================================================
     54 //==    ov5640_rst_n
     55 //==========================================================================
     56 always @(posedge clk or negedge rst_n) begin
     57     if(!rst_n)
     58         cnt_2ms <= 'd0;
     59     else if(add_cnt_2ms) begin
     60         if(end_cnt_2ms)
     61             cnt_2ms <= 'd0;
     62         else
     63             cnt_2ms <= cnt_2ms + 1'b1;
     64     end
     65 end
     66 
     67 assign add_cnt_2ms = ov5640_rst_n == 1'b0 && ov5640_pwdn == 1'b0;
     68 assign end_cnt_2ms = add_cnt_2ms && cnt_2ms== T3_2MS-1;
     69 
     70 always @(posedge clk or negedge rst_n) begin
     71     if(!rst_n) begin
     72         ov5640_rst_n <= 1'b0;
     73     end
     74     else if(end_cnt_2ms) begin
     75         ov5640_rst_n <= 1'b1;
     76     end
     77 end
     78 
     79 //==========================================================================
     80 //==    power_done
     81 //==========================================================================
     82 always @(posedge clk or negedge rst_n)begin
     83     if(!rst_n)
     84         cnt_21ms <= 'd0;
     85     else if(add_cnt_21ms) begin
     86         if(end_cnt_21ms)
     87             cnt_21ms <= 'd0;
     88         else
     89             cnt_21ms <= cnt_21ms + 1'b1;
     90     end
     91 end
     92 
     93 assign add_cnt_21ms = power_done == 1'b0 && ov5640_rst_n == 1'b1;
     94 assign end_cnt_21ms = add_cnt_21ms && cnt_21ms== T4_21MS-1;
     95 
     96 always @(posedge clk or negedge rst_n) begin
     97     if(!rst_n) begin
     98         power_done <= 1'b0;
     99     end
    100     else if(end_cnt_21ms) begin
    101         power_done <= 1'b1;
    102     end
    103 end
    104 
    105 
    106 
    107 endmodule

      可以看到代码还是不复杂的,条理也比较清晰,仿真后得到如下波形,和时序图一致,设计正确。

    2、简洁计数器(by 开源骚客)

      开源骚客实现这段时序时也是采用计数器,可是代码却非常简洁!他的写法如下:

     1 module power_ctrl
     2 //========================< 端口 >==========================================
     3 (
     4 //system --------------------------------------------
     5 input   wire                clk                     , // 50MHz
     6 input   wire                rst_n                   ,       
     7 //ov5640 --------------------------------------------
     8 output  wire                ov5640_pwdn             , // ov5640上电
     9 output  wire                ov5640_rst_n            , // ov5640复位
    10 output  wire                power_done                // power_ctrl全面有效,SCCB可以开始工作
    11 );
    12 //========================< 参数 >==========================================
    13 localparam T2_6MS           = 30_0000               ; // T2>5ms
    14 localparam T3_2MS           = 10_0000               ; // T3>1ms
    15 localparam T4_21MS          = 105_0000              ; // T4>20ms
    16 //========================< 信号 >==========================================
    17 reg     [18:0]              cnt_6ms                 ;       
    18 reg     [16:0]              cnt_2ms                 ;       
    19 reg     [20:0]              cnt_21ms                ;       
    20 
    21 //==========================================================================
    22 //==    ov5640_pwdn
    23 //==========================================================================
    24 always @(posedge clk or negedge rst_n) begin
    25     if(!rst_n) begin
    26         cnt_6ms <= 'd0;
    27     end
    28     else if(ov5640_pwdn == 1'b1) begin
    29         cnt_6ms <= cnt_6ms + 1'b1;
    30     end
    31 end
    32 
    33 assign ov5640_pwdn = (cnt_6ms >= T2_6MS) ? 1'b0 : 1'b1;
    34 
    35 //==========================================================================
    36 //==    ov5640_rst_n
    37 //==========================================================================
    38 always  @(posedge clk or negedge rst_n) begin
    39     if(!rst_n) begin
    40         cnt_2ms <= 'd0;
    41     end
    42     else if(ov5640_rst_n == 1'b0 && ov5640_pwdn == 1'b0) begin
    43         cnt_2ms <= cnt_2ms + 1'b1;
    44     end
    45 end
    46 
    47 assign ov5640_rst_n = (cnt_2ms >= T3_2MS) ? 1'b1 : 1'b0;
    48 
    49 //==========================================================================
    50 //==    power_done
    51 //==========================================================================
    52 always  @(posedge clk or negedge rst_n) begin
    53     if(!rst_n) begin
    54         cnt_21ms <= 'd0;
    55     end
    56     else if(power_done == 1'b0 && ov5640_rst_n == 1'b1) begin
    57         cnt_21ms <= cnt_21ms + 1'b1;
    58     end
    59 end
    60 
    61 
    62 assign power_done = (cnt_21ms >= T4_21MS) ? 1'b1 : 1'b0;
    63 
    64 
    65 
    66 endmodule

      可以看到,代码非常简洁且条理清晰,比我自己的写法要省很多代码。同样对其仿真得到如下波形,和时序图一致,也设计正确。

      由两个仿真波形图可以发现,我自己的写法是计数器计满了就清0,大多时候计数器也确实是这样用的。而开源骚客的计数器是计数器和信号配合产生,计满了就保持,写法非常简洁!在设计简单时序时,这种计数器思路非常受用。看来计数器虽然简单,但是里面包含的学问可真不少啊。

    参考资料:

    [1]锆石科技FPGA教程

    [2]小梅哥FPGA教程

    [3]明德扬FPGA教程

    [4]开源骚客《SDRAM那些事儿》

  • 相关阅读:
    Django准备知识-web应用、http协议、web框架、Django简介
    Django
    MySQL(基本语句)
    jsvascript === 和==的区别
    控制input只能输入1-200范围的数字
    删除数组中指定的某个元素
    微信授权登陆绑定
    通过GZ代替document.getElementById()
    判断浏览器版本
    截取逗号后面所有字符
  • 原文地址:https://www.cnblogs.com/xianyufpga/p/10908740.html
Copyright © 2020-2023  润新知