計數器是非常基本的使用,沒有計數器就無法處理時序。我在學習時發現市面上有幾種不同的計數器寫法,非常有趣,在此記錄下來:
一、時序邏輯和組合邏輯徹底分開
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那些事兒》