FPGA几种常用的分频方法
时钟分频在实际项目中的使用率非常高,下面就总结几种笔者所知道的分频方式。这里讲的分频方式,占空比都为50%。
第一种不用自己写代码,直接调用官方的IP核或者原语,但是很多FPGA的PLL资源是有限的,因此一般只用于对使用比较多的时钟信号进行倍频分频。
第二种就是最常用的,也是最简单易懂的分频方式,这种方式是通过计数,计数完成就取反。以下是代码:
1 reg [15:0] r_pos_cnt; 2 reg [15:0] r_neg_cnt; 3 wire w_pos_end; 4 wire w_neg_end; 5 wire w_pos_clk; 6 wire w_neg_clk; 7 8 always@(posedge i_sys_clk) 9 begin 10 if(~i_sys_rstn | w_pos_end) 11 begin 12 r_pos_cnt <= 'd0; 13 end 14 else 15 begin 16 r_pos_cnt <= r_pos_cnt + 1'b1; 17 end 18 end 19 20 assign w_pos_end = (r_pos_cnt == DIV - 1); 21 assign w_pos_clk = (r_pos_cnt < DIV_HALF) ? 0 : 1; 22 23 always@(negedge i_sys_clk) 24 begin 25 if(~i_sys_rstn | w_neg_end) 26 begin 27 r_neg_cnt <= 'd0; 28 end 29 else 30 begin 31 r_neg_cnt <= r_neg_cnt + 1'b1; 32 end 33 end 34 35 assign w_neg_end = (r_neg_cnt == DIV - 1); 36 assign w_neg_clk = (r_neg_cnt < DIV_HALF) ? 0 : 1; 37 //Parity judgment 38 assign o_clk_div1 = DIV[0] ? (w_pos_clk & w_neg_clk) : w_pos_clk;
第三种类似第二种方式,只是方法灵活些,且只能分频2的倍数频率。4bit的计数器,每次上升沿累加1'b1,观察到bit0是2分频,bit1是4分频,bit2是8分频,bit3是16分频。代码如下:
1 reg [3:0] r_div_cnt; 2 3 always@(posedge i_sys_clk) 4 begin 5 if(~i_sys_rstn) 6 begin 7 r_div_cnt <= 'd0; 8 end 9 else 10 begin 11 r_div_cnt <= r_div_cnt + 1'b1; 12 end 13 end 14 //16 frequency divide 15 assign o_clk_div2 = r_div_cnt[3];
第四种方式可能比较少见,但是在很多地方应用广泛,比如芯片,IP核的信号发生器等。笔者在写IP核使用该方式较多,因为频率可以通过axi4_lite更改,对于低速接口如I2C,SPI,DDS等IP核来说,频率可以根据实际情况修改增加了模块的复用性和通用性。该方式的原理是根据频率分辨率来计算需要频率的步长。比如代码中系统时钟是100MHz,计数器为32bit,那么每bit对应的频率为:100_000_000/(2**32) = 0.02328,如果需要得到1MHz的频率则步进应该为:1_000_000/0.02328 = 42_949_673。那么步进累加器就会随着累加次数发生变化,最高位即想要的频率变化。不过这种方式产生的信号是会有偏差的,不像上面的几种方法能够产生准确的50%占空比信号,因为有分辨率的存在,这种方式产生的信号占空比和频率可能会有小小的误差。
1 reg [31:0] r_fre_sum; 2 3 always@(posedge i_sys_clk) 4 begin 5 if(~i_sys_rstn) 6 begin 7 r_fre_sum <= 31'd0; 8 end 9 else 10 begin 11 r_fre_sum <= r_fre_sum + i_fre_step; 12 end 13 end 14 15 assign o_clk_div3 = r_fre_sum[31];
以上便是笔者常用的几种分频方式,当然还有更多,更好的方法只是笔者知识有限,只能列出知道的这几种。
下面是完整的代码及仿真代码:
1 //************************************************************************** 2 // *** file name : clk_div.v 3 // *** version : 1.0 4 // *** Description : clk_div 5 // *** Blogs : https://www.cnblogs.com/WenGalois123/ 6 // *** Author : Galois_V 7 // *** Date : 2022.4.23 8 // *** Changes : Initial 9 //************************************************************************** 10 `timescale 1ns/1ps 11 12 module clk_div 13 #( 14 parameter DIV = 3 15 ) 16 ( 17 input i_sys_clk , 18 input i_sys_rstn , 19 20 input [31:0] i_fre_step , 21 output o_clk_div1 , 22 output o_clk_div2 , 23 output o_clk_div3 24 ); 25 localparam DIV_HALF = DIV >> 1; 26 27 /******************************************************************************\ 28 Method 1,DIV frequency divide 29 \******************************************************************************/ 30 reg [15:0] r_pos_cnt; 31 reg [15:0] r_neg_cnt; 32 wire w_pos_end; 33 wire w_neg_end; 34 wire w_pos_clk; 35 wire w_neg_clk; 36 37 always@(posedge i_sys_clk) 38 begin 39 if(~i_sys_rstn | w_pos_end) 40 begin 41 r_pos_cnt <= 'd0; 42 end 43 else 44 begin 45 r_pos_cnt <= r_pos_cnt + 1'b1; 46 end 47 end 48 49 assign w_pos_end = (r_pos_cnt == DIV - 1); 50 assign w_pos_clk = (r_pos_cnt < DIV_HALF) ? 0 : 1; 51 52 always@(negedge i_sys_clk) 53 begin 54 if(~i_sys_rstn | w_neg_end) 55 begin 56 r_neg_cnt <= 'd0; 57 end 58 else 59 begin 60 r_neg_cnt <= r_neg_cnt + 1'b1; 61 end 62 end 63 64 assign w_neg_end = (r_neg_cnt == DIV - 1); 65 assign w_neg_clk = (r_neg_cnt < DIV_HALF) ? 0 : 1; 66 //Parity judgment 67 assign o_clk_div1 = DIV[0] ? (w_pos_clk & w_neg_clk) : w_pos_clk; 68 69 /******************************************************************************\ 70 Method 2 71 \******************************************************************************/ 72 reg [3:0] r_div_cnt; 73 74 always@(posedge i_sys_clk) 75 begin 76 if(~i_sys_rstn) 77 begin 78 r_div_cnt <= 'd0; 79 end 80 else 81 begin 82 r_div_cnt <= r_div_cnt + 1'b1; 83 end 84 end 85 //16 frequency divide 86 assign o_clk_div2 = r_div_cnt[3]; 87 88 /******************************************************************************\ 89 Method 3 90 \******************************************************************************/ 91 reg [31:0] r_fre_sum; 92 93 always@(posedge i_sys_clk) 94 begin 95 if(~i_sys_rstn) 96 begin 97 r_fre_sum <= 31'd0; 98 end 99 else 100 begin 101 r_fre_sum <= r_fre_sum + i_fre_step; 102 end 103 end 104 105 assign o_clk_div3 = r_fre_sum[31]; 106 107 endmodule
tb:
1 //************************************************************************** 2 // *** file name : clk_div_tb.v 3 // *** version : 1.0 4 // *** Description : clk_div_tb 5 // *** Blogs : https://www.cnblogs.com/WenGalois123/ 6 // *** Author : Galois_V 7 // *** Date : 2022.4.23 8 // *** Changes : Initial 9 //************************************************************************** 10 `timescale 1ns/1ps 11 module clk_div_tb(); 12 13 14 reg clk; 15 reg rstn; 16 17 18 initial 19 begin 20 clk = 0; 21 rstn = 0; 22 #2000; 23 rstn = 1'b1; 24 end 25 26 always #5 clk = ~clk; 27 28 clk_div 29 #( 30 .DIV (5 ) 31 ) 32 u_clk_div 33 ( 34 .i_sys_clk (clk ), 35 .i_sys_rstn (rstn ), 36 .i_fre_step (32'd42949673),//1MHz 37 .o_clk_div1 ( ), 38 .o_clk_div2 ( ), 39 .o_clk_div3 ( ) 40 ); 41 42 endmodule