写在前面(重要,必读):本次设计只是提供一个任意小数分频的思路,最后设计出来的并不适用于对时序要求很高的设计。
在设计任意小数分频中,如果只是使用FPGA逻辑资源 (即输入时钟clk_in,计数器,线网资源),不使用包括PLL锁相环等在内的外部资源和其他用于分频的外部电路,最终设计得到的时钟抖动都很大,就是高电平持续时间不一样,低电平持续时间也不一样。
比如说,要设计一个5.3分频的时钟,假设源时钟周期为10ns,那么最后设计出来的时钟一定不可能有任何一个周期为53ns(就是说不可能有任何两个上升沿之间的时间差为53ns)。只能说输出时钟clk_out 10个周期的时间持续为530ns,具体哪些周期是50ns,哪些是60ns,就看自己设计方式了
所以如果需要对时钟要求高的小数分频,最好使用PLL以及其他电路来得到所需时钟。
如果是需要N.5分频的话,可以看下面这个文章
小数分频之N.5分频原理及其实现(占空比非常接近0.5)
一、小数分频原理
假设clk_in的周期为10ns。
设计一个5.3分频的小数分频,上面也说了,不使用倍频等方式的话,不可能设计出每个周期都是53ns的时钟。大多数小数分频都是指一段时间内,平均周期达到53ns。那肯定有的周期小于60ns,有的大于50ns。当然,也可以一些周期10ns,一些周期100ns,只是时钟抖动有点大,也不符合小数分频设计的原则。
小数分频两个设计原则:时钟抖动小;占空比接近50%。
怎样减小时钟抖动呢?
假设clk_in的周期为10ns。
减小时钟抖动就是让每个周期持续时间的差尽量小,比如设计5.3分频,设计不出53ns,那就只能退而求次,让周期靠近53ns,如果全是50ns或者60ns,那肯定连平均周期都达不到53ns了,所以我们需要50ns和60ns交替出现的方式来设计。
为了不会让时钟抖动很大,所以目前大多设计都采用m分频和m+1分频交叉的方式,这种方式大多被称为双模前置小数分频
1、双模前置小数分频原理:
小数分频也是分数分频,假设现在要设计一个36/8的小数分频,其实就是在36个clk_in的时间内输出8个周期的clk_out
36 / 8 = 4 (余数不计)
所以我们需要用4分频和5分频交叉,那4分频和5分频分别有多少个呢?
令4分频的周期要a个,5分频的周期要b个
总共需要输出8个周期,所以a+b = 8;
同时,我们要在36个clk_in内输出,所以 4a+5b = 36
解出来 a = 4,b = 4,就是说输出的8个周期内,我们需要输出4个4分频,4个5分频。
具体怎么输出,有几种方式:
1、先输出4个4分频,再输出4个5分频;
2、将4个4分频,再输出均匀的插入到4个5分频中输出;
总体来说,第二种的时钟抖动会更小,但是设计出来的时钟占空比大多与50%相差很大,大多数不是低电平为一个脉冲就是高电平为一个脉冲。
我看到华为有一个专利,可以让占空比接近50%,但是实现很难。
2、脉冲删除法
还有一种小数分频的设计方法,脉冲删除法,就是通过计数,删除一些不需要的脉冲,这种设计出来的和双模前置法的第二种输出很像,但是占空比也和50%相差较大。这个论坛上也有一些博客说这个的,不过不太多。
基于以上,我通过一天左右时间的研究,在双模前置法的基础上设计并实现了一种占空比为50%的小数分频方法:波形拼接
二、波形拼接
1、波形拼接原理
为了让输出波形能够有更低的抖动,我们需要m分频和m+1分频交替,这里我们注意到m分频和m+1分频必然一个是偶数分频,一个是奇数分频。
还有一点更重要的是:不管是偶数分频还是奇数分频,他们一整个周期的长度总是一个clk_in周期的整数倍,这样我们的后续设计有了可能:因为在一个时钟的上升沿和下降沿进行操作时,总是很难把握。现在我们只需要在上升沿或者下降沿进行操作,这样难度大大降低。
目前有两个思路,一种稍微简单的,已经程序仿真验证了的,思路如下:(思路有点复杂,不难,就是很多细节,不好写,所以就放个大致思路,细节就不写了)
还是假设设计 36/8的小数分频
需要输出4个4分频,4个5分频
对m和m+1分别进行奇数分频和偶数分频(或者偶数分频和奇数分频)得到clk_o,和clk_j。我们需要它们的起始相位一样,这样我们就可以在上升沿或者下降沿对它们进行操作。
我们先输出所有次数的奇数分频(假如输出4个周期的奇数分频),然后再下一个要输出波形的时候,再输出从头开始的偶数分频(这里的从头开始的意思就是,当奇数分频的低电平结束,要上升之前,把偶数分频的高电平接在奇数分频后面)。具体原理如下:
上面的图不是36/8的分频图,只是解释波形拼接
如上图,先输出1-4个奇数分频,然后在5来之前,直接从6开始输出四个偶数分频,再然后,在9到来之前,再从1输出重复上述操作,这样就可以在clk_out的8个周期内输出36个clk_in,50%占空比。
得到了占空比为50% 的小数分频,这是目前已经实现了。
第二种思路是第一种的改进思路:就是让4个4分频和4个5分频均匀输出,这个实现就是稍微复杂一点
三、功能仿真效果截图
1、(13/5)
2、(53/10)
3、(36/8)
其他的小数分频,我就没有一一仿真了,最主要是vivado的自带仿真软件是真的难用(但是谁叫它更方便呢),modelsim还没关联(主要是懒得添加环境变量)
还有就是,我有个仿真用quartus 和modelsim仿真出来的是复合我预期的,但是用vivado自带的就完全不对,可能是我设计的问题
其他小数就自己下载程序仿真吧
程序代码
`timescale 1ns / 1ps
/*
思路如下:假设是53/10的分频,那么需要7次5分频,3次6分频。
1、我们先输出7/3=2个5分频,再进行波形拼接,输出1个6分频,这样我们需要cnt计数2*5+6 = 16,置1;
重复以上操作,直到我们输出了6个5分频,3个6分频
2、具体顺序如下:2(5分频) 1(6分频) 2(5分频) 1(6分频)2(5分频) 1(6分频)
3、这时候,我们还差一个5分频,如果继续重复1而不加限制的话,那就会又输出2个5分频和1个6分频,最终导致波形错误
4、这时候我们就要加上限制条件,使用另一个计数器cnt_1,从1计数到53,限制条件就是:
当cnt_1 == 53,就让cnt置1,重新开始,这样我们就只用输出最后一个5分频,然后又重复1.
5、因为我默认是让奇数分频先输出,这个在奇数分频次数大于等于偶数分频是没关系,但是,
当奇数分频次数小于偶数分频次数的时候,一、我们就需要调整输出顺序,即先输出偶数分频,再输出奇数分频,这种较麻烦;
二、我们采用先输出一个奇数,在输出偶数
*/
module divider_anypoint_5 #(
//parameter M = 53,
//parameter N = 10,
parameter a = 7, // 进行奇数分频的次数
parameter b = 3 // 进行偶数分频的次数
)
(
input clk_in,
input rst_n,
input [7:0] M,
input [7:0] N,
input [7:0] M_N_1, // 奇数
input [7:0] M_N_2, // 偶数
output reg [7:0] cnt,
output reg [7:0] cnt_1, // 保证计数到M时,能够重头开始
output clk_m,
output clk_m_1,
output reg en,
output reg en_1,
output [7:0] a_b,
output clk_out
);
reg [7:0] cnt_a; // 用于计算m分频次数
reg [7:0] cnt_b; // 用于计算m+1分频次数
//wire [7:0] a_b ;
//assign a_b = a/b;
//wire clk_out_1;
//wire clk_out_2;
//wire en_g;
//wire en_1_g;
wire [7:0] cnt_a_b; // 用于将m+1分频均匀插入到m分频中
wire [7:0] cnt_b_a; // 用于将m+1分频均匀插入到m分频中
assign cnt_a_b = (a>=b)?(a/b)*M_N_1:M_N_1; // a>=b先计数两个5(奇数)分频,a<b先计数两个(偶数)分频
assign cnt_b_a = (a>=b)?M_N_2:(b/a)*M_N_2; // b>a
//wire clk_m; // m分频时钟
//wire clk_m_1; // m+1分频时钟
//wire en; // 使能分频信号
assign clk_out = (en_1 == 1 )?clk_m:clk_m_1; // a>=b先输出clk_m奇数分频;a<b先输出clk_m_1偶数分频
always@(posedge clk_in or negedge rst_n) begin
if(!rst_n)
en <= 1'b0;
//else if(cnt_1 == M-1)
// en <= 1'b0;
else if(cnt ==(cnt_a_b-1))
en <= 1'b1;
//else if(cnt ==(cnt_a_b+M_N_2)) 这是专用于奇数分频次数大于等于偶数分频次数
else if(cnt ==(cnt_a_b+cnt_b_a))
en <= 1'b0;
else
en <= en ;
end
always@(negedge clk_in or negedge rst_n) begin
if(!rst_n)
en_1 <= 1'b1;
//else if(cnt_1 == M-1)
// en_1 <= 1'b1;
else if(cnt ==(cnt_a_b-1))
en_1 <= 1'b0;
//else if(cnt ==(cnt_a_b+M_N_2))
else if(cnt ==(cnt_a_b+cnt_b_a))
en_1 <= 1'b1;
else
en_1 <= en_1 ;
end
always@(posedge clk_in or negedge rst_n) begin
if(!rst_n)
cnt <= 8'd0;
//else if((cnt == cnt_a_b+M_N_2) || (cnt_1 == M)) // cnt_1 == M 保证计数到53会重新开始(假设53/10)
else if((cnt == cnt_a_b+cnt_b_a) || (cnt_1 == M)) // cnt_1 == M 保证计数到53会重新开始(假设53/10)
cnt <= 8'd1;
else
cnt <= cnt + 1'b1;
end
always@(posedge clk_in or negedge rst_n) begin
if(!rst_n)
cnt_1 <= 8'd0;
else if(cnt_1 == M)
cnt_1 <= 8'd1;
else
cnt_1 <= cnt_1 + 1'b1;
end
//如果m为奇数,则进行奇数分频
divider_odd
divider_odd(
.M_N (M_N_1),
.sys_clk (clk_in),
.sys_rst_n (rst_n && en_1),
.div_clk (clk_m)
);
//如果m为偶数,则进行偶数分频
divider_even
divider_even(
.sys_clk (clk_in),
.M_N (M_N_2),
.sys_rst_n (rst_n && en),
.div_clk (clk_m_1)
);
endmodule
`timescale 1ns / 1ps
/********** 任意偶数分频器************/
module divider_even #(parameter DIV = 4)(
input sys_clk,
input sys_rst_n,
input [7:0] M_N, // 取代DIV了
output reg div_clk // 任意偶数数分频输出时钟
);
reg [7:0] cnt;
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt <= 0;
else if(cnt == M_N-1)
cnt <= 0;
else
cnt <= cnt + 1;
end
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
div_clk <= 1'b0;
else if((cnt == 0) || (cnt == (M_N/2)))
div_clk <= ~div_clk;
else
div_clk <= div_clk;
end
endmodule
/********** 任意奇数分频器************/
module divider_odd #(parameter DIV = 3)(
input sys_clk,
input sys_rst_n,
input [7:0] M_N, // 取代DIV了
input en,
output reg clk1,
output reg clk2,
output div_clk
);
reg [7:0] cnt1;
reg [7:0] cnt2;
//reg clk1;
//reg clk2;
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt1 <= 4'd0;
else if(cnt1 == M_N-1)
cnt1 <= 4'd0;
else
cnt1 <= cnt1 + 1'b1;
end
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
clk1 <= 1'd0;
else if(cnt1 == 0)
clk1 <= ~clk1;
else if(cnt1 == (M_N-1)/2)
clk1 <= ~clk1;
else
clk1 <= clk1;
end
always@(negedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt2 <= 4'd0;
else if(cnt2 == M_N-1)
cnt2 <= 4'd0;
else
cnt2 <= cnt2 + 1'b1;
end
always@(negedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
clk2 <= 1'd0;
else if(cnt2 == 0)
clk2 <= ~clk2;
else if(cnt2 == (M_N-1)/2)
clk2 <= ~clk2;
else
clk2 <= clk2;
end
assign div_clk = clk1 | clk2;
endmodule
仿真程序
`timescale 1ns/1ns
`define clk_period 20
module divider_anypoint_5_tb();
//parameter M = 53;
//parameter N = 10;
/* a 和 b一定要自己算出来 .这改变这四个就可以了 */
parameter a = 9; // 进行m分频的次数,这个手算
parameter b = 4; // a为算出来奇数分频的个数,b为偶数分频的个数
wire [7:0] M = 35; // 用这个方便知道M/N是奇数还是偶数,分子
wire [7:0] N = 13; // 分母
reg clk;
reg rst_n;
wire [7:0] M_N_1;
wire [7:0] M_N_2;
wire [7:0] M_N;
assign M_N = M/N; // 判断M/N是奇数还是偶数
assign M_N_1 = (M_N[0] == 1)? (M_N): (M_N+1); // 如果M/N为奇数
assign M_N_2 = (M_N[0] == 1)? (M_N+1): (M_N); // 如果M/N为奇数
wire clk_out;
wire [7:0] cnt;
wire [7:0] cnt_1;
wire clk_m;
wire clk_m_1;
wire en;
wire en_1;
wire [7:0] a_b;
initial clk = 0;
always #(`clk_period/2) clk = ~clk;
initial begin
rst_n = 1;
#20;
rst_n = 0;
#30;
rst_n = 1;
//$stop;
end
divider_anypoint_5 #(
.a(a),
.b(b)
)
divider_anypoint_5(
.clk_in (clk),
.rst_n (rst_n),
.cnt (cnt),
.cnt_1 (cnt_1),
.M (M),
.N (N),
.clk_m (clk_m),
.clk_m_1 (clk_m_1),
.en (en),
.en_1 (en_1),
.M_N_1 (M_N_1),
.M_N_2 (M_N_2),
//.a_b (a_b),
.clk_out (clk_out )
);
endmodule
改进版链接:小数分频之任意小数分频(二)(占空比50%,时钟抖动较小)
0 C币程序下载链接:任意小数分频(占空比50%)