FPGA时钟分频的几种方法
1.定义分频参数
localparam CLK_DIVIDE = 4'd10 ; // 时钟分频系数
reg [ 3:0] clk_cnt ; // 时钟分频计数器
reg dri_clk ; // 数码管的驱动时钟
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
clk_cnt <= 4'd0;
dri_clk <= 1'b1;
end
else if(clk_cnt == CLK_DIVIDE/2 - 1'd1) begin
clk_cnt <= 4'd0;
dri_clk <= ~dri_clk;
end
else begin
clk_cnt <= clk_cnt + 1'b1;
dri_clk <= dri_clk;
end
end
2.直接计算cnt计数值
reg [11:0] div_cnt ; //分频计数器
reg div_clk ; //分频时钟
时钟分频,50Mhz/(2*(3124+1))=8khz,T=0.125ms
always @(posedge sys_clk or negedge sys_rst_n ) begin
if (!sys_rst_n) begin
div_cnt <= 12'd0;
div_clk <= 1'b0;
end
else if(div_cnt == 12'd3124) begin
div_cnt <= 12'd0;
div_clk <= ~div_clk;
end
else
div_cnt = div_cnt + 12'b1;
end
cnt的计算是用sysclk除以需要的频率,得到cnt_total,cnt_total除以2再减1就是cnt的值。
assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd3;// 模块驱动时钟的分频系数
//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0) begin
dri_clk <= 1'b1;
clk_cnt <= 10'd0;
end
else if(clk_cnt == clk_divide - 1'd1) begin
clk_cnt <= 10'd0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
右移3位,这个程序中相对于I2C_FREQ时钟频率*8,但是少了上面除以2的操作,所以最后是四倍I2C_FREQ。因为我们操作的是时钟取反需要计数到的值,和我们平时接触到的直接改变数字大小是反着的
3.奇数分频
上面的1和2适合偶数分频,奇数分频采用下面方法
//7分频,奇数分频都可以类似这么做,只需要改div1和div2的参数。
//div1为奇数分频除2的整数部分,div2为分频系数减1。
//采用上升延和下降延分别触发不同波形,最后叠加的方式产生奇数分频。
module odd_div(clk, clk1x, rst, clk1xpose, clk1xnege, coutpose, coutnege);
input clk;
input rst;
output clk1x;
output clk1xpose;
output clk1xnege;
output[2:0] coutpose;
output[2:0] coutnege;
reg clk1xpose;
reg clk1xnege;
reg[2:0] coutpose;
reg[2:0] coutnege;
parameter div1 = 3 , div2 = 6; // div1 = 7 / 2, div2 = 7 - 1
assign clk1x = clk1xpose | clk1xnege;
always@(posedge clk or negedge rst)
begin
if(!rst)
clk1xpose <= 0;
else if(coutpose == div1)
clk1xpose <= ~clk1xpose;
else if(coutpose == div2)
clk1xpose <= ~clk1xpose;
else
clk1xpose <= clk1xpose;
end
always@(negedge clk or negedge rst)
begin
if(!rst)
clk1xnege <= 0;
else if(coutnege == div1)
clk1xnege <= ~clk1xnege;
else if(coutnege == div2)
clk1xnege <= ~clk1xnege;
else
clk1xnege <= clk1xnege;
end
always@(posedge clk or negedge rst)
begin
if(!rst)
coutpose <= 0;
else if(coutpose < div2)
coutpose <= coutpose + 1;
else
coutpose <= 0;
end
always@(negedge clk or negedge rst)
begin
if(!rst)
coutnege <= 0;
else if(coutnege < div2)
coutnege <= coutnege + 1;
else
coutnege <= 0;
end
endmodule
采用计数的方法分频,理论上看起来没什么问题,但实际应用中,时钟偏移有点严重,所以最好还是用PLL,但对时钟要求不是很精确时可以用计数的方法。下图是程序的时序仿真波形。
4.PLL
5.奇偶分频通用程序
//N是分频系数,WIDTH要大于等于能够表示N的位数
module clk_divide
#(
parameter WIDTH = 3,
parameter N = 5
)(
//INPUT
input clk,
input rst_n,
//OUTPUT
output clkout
);
//********************
//REGS
reg [WIDTH-1:0] cnt_p,cnt_n;
reg clk_p,clk_n;
assign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p;
//Sequential logic style
always @ (posedge clk)
begin
if(!rst_n)
cnt_p<=0;
else if (cnt_p==(N-1))
cnt_p<=0;
else cnt_p<=cnt_p+1;
end
always @ (negedge clk)
begin
if(!rst_n)
cnt_n<=0;
else if (cnt_n==(N-1))
cnt_n<=0;
else cnt_n<=cnt_n+1;
end
always @ (posedge clk)
begin
if(!rst_n)
clk_p<=0;
else if (cnt_p<(N>>1))
clk_p<=0;
else
clk_p<=1;
end
always @ (negedge clk)
begin
if(!rst_n)
clk_n<=0;
else if (cnt_n<(N>>1))
clk_n<=0;
else
clk_n<=1;
end
endmodule