【精品博文】详细解析基于FPGA的任意分频
赢一个双肩背包
有多难?
戳一下试试看!
→_→
【主题】:详细解析基于FPGA的任意分频
【作者】:LinCoding
【时间】:2016.11.28
对于FPGA而言,分频极其重要,不多说,直接看程序。
(源码出自CrazyBingo,尊重版权)
module precise_divider#( //DIVIDER_CNT = 42.94967296(100Mhz) × fo parameter DIVIDER_CNT = 32'd6597070 //9600 × 16 (100Mhz)// parameter DIVIDER_CNT = 32'd79164837 //115200 × 16 (100Mhz))( input clk, //global clock input rst_n, //global reset output divide_clk, output divide_clken);
第一部分是输入输出定义,要注意的是使用了parameter的用法,这样可以在顶层随意改动参数以适合所需设定的频率,这里的参数指的是步长,具体解释如下:
首先,如果系统时钟是100Mhz,且数据位宽是32位的话,用232去量化100Mhz,100Mhz/232≈0.023283hz,这就意味着,如果数据位宽是32位宽,以步长为1去计数,每记1个数所对应的频率约为0.023283hz,也就意味着,这样去分频的话,输出时钟的精度可以达到0.023283hz,如果我们要设定(9600*16)hz,则计数的步长就是:(9600*16)hz/0.023283hz=6597088,当然了,由于我在计算0.023283时用了约等,所以这里所算的数不准确,其实我们可以直接来算:(9600*16)hz/(100Mhz/232)=(9600*16)hz*42.94967296≈6597070,这样就准确多了,也就是程序中注释的公式。(从原理可知,这个方案所能达到的精度很高,但是所能得到的频率最高为100Mhz/2=50Mhz了,比如你想得到60.25Mhz,这个方案是不可以的)
//-----------------------------------//counterreg [31:0] cnt;always @ ( posedge clk or negedge rst_n )begin if ( ! rst_n ) cnt <= 32'd0; else cnt <= cnt + DIVIDER_CNT;end
第二部分就是计数器了,计数器位宽为32位宽,步长为以parameter定义的为准。这样的话,每计满232就自己归零了。
//-----------------------------------reg cnt_equal;always @ ( posedge clk or negedge rst_n )begin if ( ! rst_n ) cnt_equal <= 1'b0; else if ( cnt < 32'h7FFF_FFFF ) cnt_equal <= 1'b0; else cnt_equal <= 1'b1;end
第三部分是与232/2比较,以输出占空比为%50的方波。32'h7FFF_FFFF就是232/2
//-----------------------------------reg cnt_equal_r;always @ ( posedge clk or negedge rst_n )begin if ( ! rst_n ) cnt_equal_r <= 1'b0; else cnt_equal_r <= cnt_equal;endassign divide_clk = cnt_equal_r;assign divide_clken= ( ~cnt_equal_r & cnt_equal ) ? 1'b1 : 1'b0; //posedge capture
第四部分就是输出分频后的时钟和使能时钟了。这里有一点需要注意。
既然我们需要的是分频后的时钟,可为什么还需要divide_clken这个使能时钟呢?答案是FPGA不允许除全局时钟以外的时钟作为系统的驱动信号,也就是我们常常写的
always @ ( posedge clk or negedge rst_n ),这里的clk要么是全局时钟,要么是PLL生成的时钟,最好不要用计数器生成的时钟,那么,计数器分频得到的时钟就需要一个使能时钟(使能信号)了,我们可以这样来用:
always @ ( posedge clk or negedge rst_n )begin if ( ! rst_n ) begin ...... end else if ( divide_clken ) begin ...... endend
这样就OK了,因此我们需要在输出时钟的时候加一个输出时钟使能信号以供其它模块使用。所以,上述代码采用了一级D触发器打一拍,然后捕获了输出时钟的上升沿作为使能时钟以供其它模块使用!
最后输出divide_clk信号观察,可见9600hz*16=153600hz=153.6Khz,与设定完全一致,且精度很高!