• 从计数器到分频电路(完结)


      本文介绍常见的电路——计数器,然后我们由计数器电路讲解到分频电路。

    一、计数器

      (1)计数器代码

      计数器,顾名思义就是在时钟的节拍下进行计数,一个简单的N位计数器的代码如下所示,这个计数器从0计数到2^N - 1(共计数了2^N个数,也就是N位计数器):

     1 module count#(parameter N=8)(
     2 input clk,
     3 input clear,
     4 output[N-1:0] cnt_Q
     5 );
     6 reg[N-1:0] cnt;
     7 assign cnt_Q = cnt;
     8 
     9 always@(posedge clk)
    10   if(clear)
    11     cnt <= 'h0;      //同步清 0,高电平有效
    12   else
    13     cnt <= cnt+1'b1; //加法计数
    14 
    15 endmodule

    上述描述的计数器通过 clear 信号清除计数值,然后下一周期开始加 1 计数;当计数器计到能够存储的最大数值时, 例如本例为 8 个 1,即 8'hff 就会自动回到 0,然后开始下一轮计数。

    综合得带的电路如下所示:

      (2)计数器改进

      如果想要实现 0~k 范围内计数,其中k ≠ 2^N ,可以将 always 语句修改为:

    always@(posedge clk)
        if(clear)
            cnt <=  'h0;    //同步清 0,高电平有效
        else if(cnt==K)
            cnt <= 'h0;
        else
            cnt <= cnt+1'b1; //减法计数            

      

      前面是累加计数,下面是一个既可以递增也能递减,且具备初始值装载和复位的计数器,代码如下所示:

     1 module updown_count#(parameter N=8)(
     2     input clk,
     3     input clear,
     4     input load,
     5     input up_down,
     6     input [N-1:0] preset_D,
     7     output[N-1:0] cnt_Q
     8 );
     9 reg[N-1:0] cnt;
    10 assign cnt_Q = cnt;
    11 
    12 always@(posedge clk)
    13   if(clear)
    14     cnt <= 'h0;      //同步清 0,高电平有效
    15   else if(load)
    16     cnt <= preset_D; //同步预置
    17   else if(up_down)
    18     cnt <= cnt+1;    //加法计数
    19   else
    20     cnt <= cnt-1;    //减法计数
    21 
    22 endmodule

    二、计数器的用途

      (1)基本的计数功能与分频

      计数器的基本功能顾名思义就是计数了,用来计数,产生某个信号等等。利用这个功能,可以实现信号的分频,具体会在后面的分频电路中进行描述。

      (2)看门狗

      计数器其实就可以设计成看门狗。在初始状态时,看门狗电路首先装载一个大数;当状态机或者程序开始运行后,看门狗开始倒计数。如果状态机或程序运行正常,每隔一段时间应发出指令或信号让看门狗重新装载一个大的初始值,并再次开始倒计数。如果看门狗减到 0 就认为程序或状态机没有正常工作,就需要强制整个系统复位。

      上面的第二处改进的计数器电路描述就是一个看门狗电路,只要加上 cnt==0 作为看门复位状态即可;而 load 信号则是状态机或软件给出的喂狗动作。

      (3)特殊的有限状态机

      当状态机要求没有那么严格的时候,这个时候就可以用计数器的计数值当做状态机的状态,计数增加或者减少就是改变状态。

     

    三、分频电路

      (1)简单的计数器

      计数器实质是对输入的驱动时钟进行计数,所以计数器在某种意义上讲,等同于对时钟进行分频。例如一个最大计数长度为N=2^n(从0计数到N-1)的计数器,也就是寄存器位数位n,那么寄存器最高位的输出为N=2^n分频,次高位为N/2分频...例如下面的代码:

     1 module test#(parameter N=3)(
     2 input clk,
     3 input rst_n,
     4 output clk_div
     5 );
     6 
     7 reg [N-1:0] div_reg        ;//分频计数器
     8 always @(posedge clk or negedge rst_n)
     9     if (rst_n == 1'b0 )
    10         div_reg    <= 0 ;
    11     else 
    12         div_reg    <= div_reg + 1'b1 ;
    13 
    14 assign clk_div = div_reg[N-1] ;
    15 
    16 
    17 endmodule

     该代码描述的将一个3位的计数器最高位输出,也就是计数长度为8(计数从0~7)波形如下所示:

        

    可以看到最高位的输出为输入时钟的8分频。

      当N不是2的整数次幂时,即N≠2^n时,从0计数到N-1,其最高位作为时钟输出(占空比不一定为 1:1)是输入时钟的1/N,也就是N分频。我们来举个例子,比如最大计数长度为5的计数器,即从0计数到4后又返回0,那么需要定义一个三位的寄存器。寄存器的计数过程为:

      000-001-010-011-100-000-001-010-011-100-000-001-010-011-100-000-001-010-011-100······

    我们取最高位,得到的信号变化就是:

      0-0-0-0-1-0-0-0-0-0-1-0-0-0-0-1-0-0-0-0-1···

     代码如下所示:

     1 module test#(parameter N=3)(
     2 input clk,
     3 input rst_n,
     4 output clk_div
     5 );
     6 
     7 reg [N-1:0] div_reg        ;//分频计数器
     8 always @(posedge clk or negedge rst_n)
     9     if (rst_n == 1'b0 )
    10         div_reg    <= 0 ;
    11     else if(div_reg == 3'd4)//从0计数到4,然后返回到0,5分频
    12         div_reg    <= 0;
    13     else
    14         div_reg    <= div_reg + 1'b1 ;
    15 
    16 assign clk_div = div_reg[N-1] ;
    17 
    18 
    19 endmodule

    仿真波形如下所示:

          

    由此可以看到,每一个分频后的时钟周期=5倍原来的时钟周期,因此是5分频。

      那么这个情况是不是也可以包含第一种情况呢?我们那设置为8分频看看,即前面的3'd4改成3'd7,得到的仿真波形如下所示:

          

    可以看到,计数器的最高位输出也是输入频率的1/N。

     因此我们得到结论一个最大计数长度为N(从0计数到N-1)的计数器,其最高位的输出,是输入频率的N分频

      通常 ASIC 和 FPGA 中,时钟都是全局信号,都需要通过 PLL 处理才能使用,但某些简易场合,采用计数器输出时钟也是能够使用的,只是需要注意时序约束。

      (2)偶数倍分频(占空比50%)

       偶数分频,也就是2分频、4分频、6分频...这个还是比较简单的,N(N当然是2的倍数)分频,那么计数到N/2-1,然后时钟翻转

     例如N=6时,代码如下所示:

     1 module test#(parameter N=6)(
     2 input clk,
     3 input rst_n,
     4 output clk_div
     5 );
     6 reg div_reg ;
     7 reg [N-1:0] div_cnt        ;//分频计数器
     8 always @(posedge clk or negedge rst_n)
     9     if (rst_n == 1'b0 )begin
    10         div_cnt    <= 0 ;
    11         div_reg    <= 0 ;
    12     end 
    13     else if(div_cnt == (N/2 - 1))begin
    14         div_cnt    <= 0;
    15         div_reg    <= ~div_reg ;
    16     end 
    17     else
    18         div_cnt    <= div_cnt + 1'b1 ;
    19 
    20 assign clk_div = div_reg ;

    仿真波形如下所示:

          

    当N=2的仿真波形如下所示:

            

      (3)奇数倍分频

        ①占空比接近50%

       对于占空比不是50%的计数分频,我们可以直接用上面的计数器方法,这里就不说了,我们介绍其他接近50%的占空比的方法,比如下面使用的状态机分频:

          

    上图的状态机除了用一般的状态机设计方式之外,我们也可以用简单的计数器实现,这种方法如下所示:

      假设时钟分频是N,则设置一个计数器,计数长度是N(即从0计数到N-1),然后在计数器为计数到(N-1)/2的时候,翻转一下分频时钟信号;在计数器计数到为N-1的时候,再翻转一下时钟。

    代码如下所示:

     1 module test#(parameter N=3)(//N分频,这里是3分频
     2 input clk,
     3 input rst_n,
     4 output clk_div
     5 );
     6 
     7 reg [N-1:0] div_cnt        ;//分频计数器
     8 reg div_reg ;
     9 always @(posedge clk or negedge rst_n)begin
    10     if (rst_n == 1'b0 )begin
    11         div_cnt    <= 0 ;
    12         div_reg        <= 1 ;
    13     end else if (div_cnt == (N-1)/2)begin//计数到(N-1)/2,进行翻转和继续计数
    14         div_reg        <= ~div_reg;
    15         div_cnt    <= div_cnt + 1'b1 ;
    16     end else if ( div_cnt == (N-1) )begin//计数到N-1,进行清零和翻转
    17         div_cnt        <= 0 ;
    18         div_reg        <= ~div_reg;
    19     end else 
    20         div_cnt    <= div_cnt + 1'b1 ;
    21         
    22 end         
    23 assign clk_div = (N == 1)?clk:div_reg ;//注意这里
    24 
    25 
    26 endmodule

    代码中我们需要注意,在N= 1的情况,也就是不分频的情况。仿真电路如下图所示:

    3分频,N = 3:

          

    5分频,N= 5 :

             

    不分频,即N=1的仿真如下所示:

          

        ②占空比50%

          产生具有50%占空比的奇数分频时钟的算法如下所示,假设N分频(N是计数):

      设置一个计数长度为N的上升沿计数器,和一个信号寄存器;信号寄存器在上升沿计数器为(N-1)/2的时候进行翻转,然后再在计数到N-1的时候进行翻转(这里相当于得到一个N分频信号A)。

      再设置一个计数长度为N的下降沿计数器,和另一个信号寄存器;信号寄存器在下降沿计数器为(N-1)/2的时候进行翻转,然后再在计数到N-1的时候进行翻转(这里相当于得到一个N分频信号B)。

      将A和B相或就可以得到占空比50%的奇数分频信号;代码实现如下:

     1 module test#(parameter N=5)(//N分频
     2 input clk,
     3 input rst_n,
     4 output clk_div
     5 );
     6 
     7 reg sig_r ;//定义一个上升沿翻转的信号
     8 reg sig_f ;//定义一个下降沿翻转的信号
     9 reg [N-1:0]    cnt_r;//上升沿计数器
    10 reg [N-1:0]    cnt_f;//下降沿计数器
    11 
    12 wire clk_f ;
    13 assign clk_f = ~clk ;//用来触发下降沿计数器的时钟
    14                     //由于同时使用上升沿和下降沿触发器不好,因此我们为同一边沿,都使用上升沿触发
    15                     //只不过是将时钟进行反向
    16                     
    17 always @(posedge clk or negedge rst_n)begin//上升沿计数
    18     if(rst_n == 1'b0)begin
    19         sig_r    <= 0 ;
    20         cnt_r    <= 0 ;
    21     end else if( cnt_r == (N-1)/2 )begin
    22         sig_r    <= ~sig_r ;
    23         cnt_r    <= cnt_r + 1 ;
    24     end else if ( cnt_r == (N-1) )begin
    25         sig_r    <= ~sig_r ;
    26         cnt_r    <= 0 ;
    27     end else 
    28         cnt_r    <= cnt_r + 1 ;
    29 end                     
    30 
    31 always @(posedge clk_f or negedge rst_n)begin//下降沿计数
    32     if(rst_n == 1'b0)begin
    33         sig_f    <= 0 ;
    34         cnt_f    <= 0 ;
    35     end else if( cnt_f == (N-1)/2 )begin
    36         sig_f    <= ~sig_f ;
    37         cnt_f    <= cnt_f + 1 ;
    38     end else if ( cnt_f == (N-1) )begin
    39         sig_f    <= ~sig_f ;
    40         cnt_f    <= 0 ;
    41     end else 
    42         cnt_f    <= cnt_f + 1 ;
    43 end 
    44 
    45 assign clk_div = sig_f || sig_r ;
    46                     
    47 endmodule

    仿真波形如下所示:

    3分频:

      

    5分频:

        

      (4)任意整数倍分频(接近50%)

      在前面中,我们知道了一个最大计数长度为N(从0计数到N-1)的计数器,其最高位的输出,是输入频率的N分频,因此最简单的任意分频电路就是设计一个计数器,然后最高位输出就是分频的频率了。虽然这这种方法很简单,但是很显然,这种方法的占空比是很糟糕的。因此我们要用其他的方法,也就是用其他的组合方式。

       ①占空比接近50%任意整数分频

      这种方法是取自偶数分频和奇数分频里面的接近50%占空比,实现的代码如下所示:

     1 module test #( parameter cfactor= 5)(
     2   input clk,
     3   input rst_n,
     4   output clk_div
     5 );
     6 reg clk_loc;
     7 //reg [15:0] cnt;//allowed maximum clock division factor is 65536
     8 reg [7:0] cnt;//allowed maximum clock division factor is 256
     9 
    10 assign clk_div = (cfactor==1)? clk : clk_loc;
    11 //assign clk_div = ((rst==1) || (cfactor==1))? clk : clk_loc;
    12 
    13 always@(posedge clk or negedge rst_n)
    14   if(!rst_n)begin
    15     cnt <= 'd0;
    16     clk_loc = 1;
    17   end
    18   else begin
    19     cnt <= cnt + 1'b1;
    20     if(cnt==cfactor/2-1)
    21       clk_loc = 0;
    22     else if(cnt==cfactor-1) begin
    23       cnt <= 'd0;
    24       clk_loc = 1;
    25     end
    26   end
    27 
    28 endmodule

    2分频的仿真图,如下所示:

        

    5分频的仿真波形如下所示:

        

        ②占空比50%的任意整数分频(重点)

        这种方法是取自偶数分频和奇数分频都是50%占空比的组合,代码如下所示:

     1 module test#(parameter N=1)(//N分频
     2 input clk,
     3 input rst_n,
     4 output clk_div
     5 );
     6 
     7 //奇数分频
     8 reg sig_r ;//定义一个上升沿翻转的信号
     9 reg sig_f ;//定义一个下降沿翻转的信号
    10 reg [N-1:0]    cnt_r;//上升沿计数器
    11 reg [N-1:0]    cnt_f;//下降沿计数器
    12 
    13 wire clk_f ;
    14 assign clk_f = ~clk ;//用来触发下降沿计数器的时钟
    15                     //由于同时使用上升沿和下降沿触发器不好,因此我们为同一边沿,都使用上升沿触发
    16                     //只不过是将时钟进行反向
    17                     
    18 always @(posedge clk or negedge rst_n)begin//上升沿计数
    19     if(rst_n == 1'b0)begin
    20         sig_r    <= 0 ;
    21         cnt_r    <= 0 ;
    22     end 
    23     else begin
    24         cnt_r    <= cnt_r + 1 ;
    25         if( cnt_r == (N-1)/2 )begin
    26             sig_r    <= ~sig_r ;
    27         end else if ( cnt_r == (N-1) )begin
    28             sig_r    <= ~sig_r ;
    29             cnt_r    <= 0 ;
    30         end 
    31     end 
    32 end                     
    33 
    34 always @(posedge clk_f or negedge rst_n)begin//下降沿计数
    35     if(rst_n == 1'b0)begin
    36         sig_f    <= 0 ;
    37         cnt_f    <= 0 ;
    38     end 
    39     else begin
    40         cnt_f    <= cnt_f + 1 ;
    41         if( cnt_f == (N-1)/2 )begin
    42             sig_f    <= ~sig_f ;
    43         end else if ( cnt_f == (N-1) )begin
    44             sig_f    <= ~sig_f ;
    45             cnt_f    <= 0 ;
    46         end 
    47     end     
    48 end 
    49 
    50 //偶数分频
    51 reg div_reg ;
    52 reg [N-1:0] div_cnt        ;//分频计数器
    53 always @(posedge clk or negedge rst_n)begin
    54     if (rst_n == 1'b0 )begin
    55         div_cnt    <= 0 ;
    56         div_reg    <= 0 ;
    57     end 
    58     else begin
    59         div_cnt    <= div_cnt + 1'b1 ;
    60         if(div_cnt == (N/2 - 1))begin
    61             div_cnt    <= 0;
    62             div_reg    <= ~div_reg ;
    63         end 
    64     end
    65 end        
    66 assign clk_div = (N == 1)?clk:
    67                 ( N%2 == 1)?(sig_f || sig_r ): div_reg;//这里用来输出分频值。对2的取余操作是综合的
    68                     
    69 endmodule 

    仿真波形如下所示:

    5分频:

        

    6分频:

          

       

     总结:本文介绍了计数器及其功能,主要是介绍了作为分频器的功能。对于分频器,如下所示:

              

  • 相关阅读:
    权限设计 【数据库和代码】 GO
    sql读取指定字符前的字符 GO
    C#编码建议 GO
    网页鼠标提示 GO
    ASP.NET设置ie打印两法 GO
    正则表达式入门教程 GO
    一个初学者对ArrayAdapter的简单理解
    泛型的简单理解
    SQL Server死锁详解
    .NET代理模式
  • 原文地址:https://www.cnblogs.com/IClearner/p/7208871.html
Copyright © 2020-2023  润新知