Verilog各类分频器设计详解

Verilog各类分频器设计详解

分频器是时序电路的基本器件,它的功能是对系统时钟或其他时钟进行分频产生所需要的时钟信号。分频有两种方式:一是通过HDL语言建模产生所需要的时钟信号,二是利用开发工具的PLL进行分频。前者分频灵活,需编写代码实现;后者使用场景受限,因为有的低端FPGA没有PLL,但PLL的分频效果更好,而且在进行小数分频时也比较容易实现。本文首先尝试用HDL语言建模方式设计各种类型的分频器,最后给大家简单介绍一下PLL的使用。如有不足之处还望大家批评指正。

偶数分频器

我们先从最简单的偶数分频器切入,慢慢分析各种分频器的实现。

若要实现二分频,则只需要在原时钟的上升沿进行输出时钟状态的翻转即可,如下图所示

2分频.png

若实现四分频呢?则需要一个计数器,每次在原时钟的上升沿计数,当计数器记到2个上升沿时输出时钟状态进行翻转,如下图所示

4分频.png

现在对一般情况进行分析,对时钟进行N分频,N为偶数;则计数器每次在原时钟的上升沿计数,计数器的范围为0-(N-1),我们可以在0-(N-1)这N个数中分出两个范围选择输出时钟的状态,如当cnt在0-M范围时输出时钟为低电平,当cnt在(M+1)-(N-1)范围时输出时钟为高电平,则我们可以动态调整输出时钟的占空比,输出时钟的占空比为(N-M-1)/N;

8分频.png

以8分频为例,则N=8,取M=3,则此时输出的时钟应当是50%占空比,如下图所示

8分频.png

若取M=1,则此时输出时钟的占空比应当是75%,如下图所示

8分频75占空比.png

偶数分频器代码

// 偶数分频器示例,可调占空比
module clk_div_even(
	input wire clk,                 //系统时钟
	input wire rst_n,               //异步低电平复位
	input wire [7:0] clkperiod,     //分频系数,N(偶数)分频时clkperiod=N
	input wire [7:0] clklow,        //低电平占用系统时钟的周期数
                                    //占空比为50%时clklow=N/2
    output reg clk_out				//输出时钟
    );
 
reg [7:0] cnt;
 
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cnt <= 8'b0;
        clk_out <= 1'b0;
    end
    else begin
        if(cnt == clklow - 1'b1) begin
            clk_out <= 1'b1;
            cnt <= cnt + 8'd1;
        end
        else if(cnt == clkperiod - 1'b1) begin
            clk_out <= 1'b0;
            cnt <= 8'd0;
        end
		else begin
            cnt <= cnt + 8'd1;
		end
    end
end
 
endmodule

奇数分频器

我们先以三分频模块切入,分析奇数分频器的思想:整体思路是产生两路上升沿和下降沿触发信号,然后对这两路信号进行操作得到最终分频时钟。

当分频系数N为奇数时,使用一个计数器在0~(N-1)循环进行计数,控制输出(N-1)/2个高电平,(N+1)/2个低电平,称为clk_1;然后将此clk_1电平信号延迟半个周期称为clk_2,最后输出clk_out = clk_1 | clk_2,即为占空比为50%的奇数分频器;另一种思路是产生输出(N+1)/2个高电平,(N-1)/2个低电平的clk_1,输出的分频时钟为clk_out = clk_1 & clk_2

clk_out = clk_1 & clk_2为例,将clk_1延时半个时钟周期的方法有两种,法1是直接使用下降沿的锁存器对clk_1锁存得到clk_2,法2得到clk_2的原理与clk_1相同,不过是在下降沿检测。

3分频.png

若采用法一,则我们用一个下降沿触发的D触发器锁存clk_1的结果clk_2,然后将clk_1clk_2信号做逻辑“或”就得到了占空比50%的分频时钟信号clk_out

结合上面的偶数分频器,如果N为偶数,则clk_1就是我们所需要的分频结果,如果N为奇数,则clk_1 & clk_2就是我们所需的分频结果,所以我们可以把偶数分频和奇数分频结合,实现N分频器设计,其中N为正整数;通过N[0]选择输出,N[0]=1为奇数分频,N[0]=0为偶数分频。

5分频结果如下:

integer_div5_out.png

6分频结果如下:

integer_div6_out.png

正整数分频器代码

module clk_div_integer #(
    parameter N = 6,        //N分频,N为整数
    parameter WIDTH = 3     //计数器位宽
) (
    input wire clk,
    input wire rstn,
    output wire clk_out
);

    reg [WIDTH-1:0] cnt;
    reg clk_1,clk_2;
    wire clk_odd;

    always @(posedge clk or negedge rstn) begin
        if(~rstn) begin
            cnt <= 0;
        end
        else begin
            if(cnt == N-1) begin
                cnt <= 0;
            end
            else begin
                cnt <= cnt + 1'b1;
            end
        end
    end

    always @(posedge clk or negedge rstn) begin
        if(~rstn) begin
            clk_1 <= 1'b0;
        end
        else begin
            if(cnt == ((N-1) >> 1)) begin
                clk_1 <= 1'b1;
            end
            else if(cnt == (N-1))begin
                clk_1 <= 1'b0;
            end
            else begin
                clk_1 <= clk_1;
            end
        end
    end

    //法1:在时钟下降沿锁存clk_1得到clk_2
    always @(negedge clk or negedge rstn) begin
        if(~rstn) begin
            clk_2 <= 1'b0;
        end
        else begin
            clk_2 <= clk_1;
        end
    end

    //法2:clk2与clk_1产生的方式相同,差别是clk_2是下降沿触发
    // always @(negedge clk or negedge rstn) begin
    //     if(~rstn) begin
    //         clk_2 <= 1'b0;
    //     end
    //     else begin
    //         if(cnt == ((N-1) >> 1)) begin
    //             clk_2 <= 1'b0;
    //         end
    //         else if(cnt == (N-1)) begin
    //             clk_2 <= 1'b1;
    //         end
    //         else begin
    //             clk_2 <= clk_2;
    //         end
    //     end
    // end

    assign clk_odd = clk_1 | clk_2;
    assign clk_out = N[0] ? clk_odd : clk_1;
    
endmodule

半整数分频器

1、占空比非50%

网上比较多的分频思路是:半整数分频多出来的那半个周期为高电平,其余为低电平。以5.5分频为例,以原时钟的半周期为单位,可以分频输出1高10低。原理是用计数器循环记数0~10即11个周期,控制输出clk_1前6周期高电平,后5周期低电平,然后再使用该计数器得到一下降沿触发的5低6高的输出clk_2,最后输出clk_out = clk_1 & clk_2。波形图如下图所示

5.5分频.png

现在我们虽然得到了5.5分频后的信号,但占空比不是很理想,是否可以在其基础上进行改进来实现占空比近似50%的分频信号呢?

2、占空比近似50%

由上面的波形图我们可以看到,如果clkxclkycnt的其它状态(稍微偏大的值)进行状态翻转的话,可能输出的clk_out = clk_1 & clk_2就能达到近似50%的占空比。在尝试后发现,clkxcnt等于N+M2N时进行状态翻转,clkycnt等于NM时进行状态翻转,此时输出的clk_out = clk_1 & clk_2近似50%占空比。其中N为不超过分频系数的最大整数,如5.5分频时N=5,当N为奇数时M=(N-1)/2,当N为偶数时M=(N+1)/2

占空比近似50%的4.5分频的波形图如下所示

4.5分频.png

半整数分频器代码

`timescale 1ns/1ns
module clk_div_half #(
    parameter N = 5             //5.5分频时N=5,以此类推
) (
    input wire clk,
    input wire rstn,
    output wire clk_div
);

    //localparam M = 0;                                           //占空比非50%,高电平仅有半周期
    localparam M = N[0] ? ((N - 1) >> 1) : ((N + 1) >> 1);        //占空比近似50%
    reg [31:0] cnt;
    reg clkx,clky;

    always @(posedge clk or negedge rstn) begin
       if(~rstn) begin
           cnt <= 32'd0;
       end
       else if(cnt == (N<<1)) begin
           cnt <= 32'd0;
       end
       else begin
           cnt <= cnt + 1'b1;
       end
    end

    always @(posedge clk or negedge rstn) begin
        if(~rstn) begin
            clkx <= 1'b0;
        end
        else if(cnt == N + M) begin
            clkx <= 1'b0;
        end
        else if(cnt == (N << 1))begin
            clkx <= 1'b1;
        end
    end

    always @(negedge clk or negedge rstn) begin
        if(~rstn) begin
            clky <= 1'b0;
        end
        else if(cnt == N) begin
            clky <= 1'b1;
        end
        else if(cnt == M) begin
            clky <= 1'b0;
        end
    end

    assign clk_div = clkx & clky;
    
endmodule

小数分频器

以8.7分频为例来分析小数分频器的设计。因为无法用计数器表示0.7这种数字,所以我们用一个等效的概念来实现8.7分频,原时钟87个周期的总时间等于分频后的时钟10个周期的总时间。

因为8.7分频在8分频和9分频之间,所以我们用8分频和9分频来组合生成8.7分频的时钟。可以列方程组,设8分频共x个周期,9分频共y个周期,则

  • x+y=10 (1)
  • 8*x + 9 *y = 87 (2)

解得x=3,y=7。

即通过3次8分频和7次9分频可得到8.7分频。但如果是按序先输出3个8分频再输出7次9分频的时钟用处不大,我们还得乱序使其均匀输出,不然会造成时钟频率均匀性不好,相位抖动大的问题

此处我们介绍脉冲删除小数分频,该方法相对比较简单。什么意思呢?就是说我在87个输入时钟里删掉77个时钟周期,这样不就输出了10个时钟周期了吗?也就实现了8.7分频,那么该怎么删呢?查阅论文后得到结论:

  1. 设置寄存器cnt位宽自定,初始值为0;
  2. clk的上升沿cnt=cnt+分母,并判断cnt是否大于分子,若大于分子则在下一周期减去分子;
  3. cnt小于分子时,输出脉冲信号为0,cnt大于分子时,输出脉冲信号为1;

说起来比较乱,我们以7/3分频为例来看

时钟序号cnt输出脉冲
030
160
291
3(12->)50
481
5(11->)40
6(7->)01

从表中可以看到每7个周期输出3个脉冲,刚好满足分频要求。

小数分频器代码

`timescale 1ns/1ns
module clk_div_decimal #(
    parameter fraction = 16'd87,       //分频的分子
    parameter denominator = 16'd10,    //分频的分母
    parameter cnt_width = 8            //计数器的位宽
) (
    input wire clk,
    input wire rstn,
    output reg clk_out
);

    reg [cnt_width-1:0] cnt;
    always @(posedge clk or negedge rstn) begin
        if(~rstn) begin
            cnt <= 0;
            clk_out <= 1'b0;
        end
        else if(cnt < fraction) begin
            cnt <= cnt + denominator;
            clk_out <= 1'b0;
        end
        else begin
            cnt <= cnt + denominator - fraction;
            clk_out <= 1'b1;
        end
    end
    
endmodule

8.7分频结果如下

8.7分频结果.jpg

3.25分频结果如下

3.25分频.jpg

PLL分频

这里就给大家简单介绍一下如何在Vivado中对PLL进行例化。

首先打开vivado,新建一个RTL项目,点击Flow Navigator窗口中的IP Catalog,在search处搜索自己想要的IP核的名字,例如输入clock就会找到Clocking Wizard这个IP核,如下图所示

pll_1.jpg

双击Clocking Wizard 这个IP核,就能弹出配置窗口;我们简单配置一些信息,输入时钟为100MHz,分频输出两个时钟,一个是30MHz,一个是18MHz,查看分频效果;

pll_2.jpg

pll_3.jpg

点击Generate,生成IP核,然后在source窗口就会出现一个文件

pll_4.jpg

pll_5.jpg

下面对这个生成的IP进行例化,测试分频效果

pll_6.jpg

将这个例化模块的例程添加到自己的顶层仿真代码中,查看分频结果,由下图可见Xilinx提供的PLL分频效果真的厉害,其中一个是10/3分频,一个是50/9分频,占空比均为50%。

pll_7.jpg


本次的博客就讲到这里,其中PLL的使用讲的很浅显,大家要想深入学习PLL的IP使用的话可以学习官方文档。
若文章中存在任何错误或不足欢迎大家指正,欢迎大家在博客下方留言交流。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日拱一卒_未来可期

若复习顺利望有闲钱的同学支持下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值