計數器(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那些事兒》

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM