一天一个设计实例-任意分频器设计
(一)分频器相关原理1.DDS原理任意分频原理起源于DDS(Direct Digital Synthesizer,直接频率合成法)的原理,DDS是重要的频率合成方法,在波形发生器中占有举足轻重的地位。DDS是一种从相位概念出发,直接合成所需要的波形的频率合成技术,其实质是以基准频率源(系统时钟)对相位进行等间隔采样。对于正余弦类型的频率合成,DDS基本上等效于NCO加上DA实现。DDS由相位累加器和波形存储器、数模转换器(DAC)以及模拟低通滤波器(LPF)三部分组成,实现原理与NCO完全一致,只是存储的ROM由NCO替代为需要发生的波形文件,并通过数模转换器转换为模拟信号,最后由具有内插作用的LPF将其平滑转化为连续的正弦波形以输出。一个典型的DDS硬件结构如图1‑75所示
图1‑75 典型的DDS硬件结构2.任意分频发生器的实现直接频率合成法通过相位累加原理,实现了通过步长可配置的任意波形输出。根据这个原理,通过对相位累加的地址进行处理,可以直接计算得到相应的频率。这一原理应用于Verilog HDL中,理论上也能实现任意频率的分频电路。设计实现的框图如图1‑76所示。
图1‑76 相位累加原理的实现框图假定FPGA基准时钟为100MHz,即基准时钟为:fc=100x106Hz同时规定计数器的位数N2=32,K为频率控制字,则相位累加后输出的最大频率与最小频率分别为:
21(.)变换公式,可以计算得到固定基准频率下每增/减1 Hz的频率控制字K的大小,如下所示:
22(.)由此公式可知,每增、减1 Hz,K的步进为42.949 672 96。另外,与前面的分析一致,最大的频率、最小频率(频率分辨率)分别为:
23(.)根据以上公式可以得到固定基准时钟下的任意频率发生器,其精度为0.0232831Hz。不过相位累加器得到的只是cnt累加的结果,为了得到频率,还需要对相位累加值cnt进行一定的处理,即采用类似于AD9850内DDS核的比较器,来实现方波à频率的转换。3.实验验证假定FPGA基准时钟为100MHz,即基准时钟为:fc=100x106Hz当使用UART时,波特率BPS=115200 bps。(一)直接分频法一个位的周期 = 1 / bps= 1/ 115200= 0.000086805555555555555555555555555556传输一位数据占用 0.000086805555555555555555555555555556s 时间。如果是一帧 11 位的数据,就需要0.000086805555555555556 x 11 = 0.00095486111111111111111111那么一秒钟内可以传输1 / 0.00095486111111111111111111 = 1047.27272727272727272727394591741047.2727272727272727272 个帧数据。如果用 100Mhz 的时钟频率去量化的话:( 1/115200 ) / (1/100E+6) = 8.68E-6 / 20E-9= 868.05555555555555555555555555556≈868计数上限为整数,将此值反馈代入输出频率计算式,如下:
24(.)现在要将基准时钟进行751分频(特殊才具有说明意义),产生的频率为:
25(.)由该公式可知,结果与预期的数据相差了近 10 Hz。不管这是否在波特率的允许误差范围内,用FPGA分频得到误差如此之大的频率误差简直让人无法接受。(二)任意分频发生器实现“任意频率发生器”方法:首先计算频率控制字,如下所示:
26(.)由于在FPGA中不能进行浮点运算,所以K取整数6597070。将此值反馈代入输出频率计算式,如下图所示:
27(.)最终输出的频率所产生的误差为:
28(.)由公式课件,尽管取K为整数,但最终产生的频率误差也在小数点后10位,这已经是一个非常理想的结果了。而UART转串口芯片一般允许一定的误差,如CH340 TXD的允许波特率误差小于0.3%,RXD的允许波特率误差不小于2%。因此,在可允许的范围内还有很大的余量。按照上述分析进行分频器的设计:(1)新建precise_divider的模块,其模块框图如图1‑77所示,端口定义如表1‑32所示。
图1‑77 任意分频模块框图表1‑32 任意分频模块端口列表端口名位宽输入/输出说明clk1Input全局时钟信号rst_n1Input全局复位信号divide_clk1Output分频时钟信号divide_clken1Output使能时钟信号(2)相位计数器-32位K步计数器,如下所示:代码1‑6 32位K步计数器1.//------------------------------------------------------ 2.//RTL1: Precise fractional frequency for uart bps clock 3.reg [31:0] cnt; 4.always@(posedge clk or negedge rst_n) 5.begin 6. if(!rst_n) 7. cnt <= 0; 8. else 9. cnt <= cnt + DEVIDE_CNT; 10.end如上所示,在全局时钟驱使下,进行K步计数,cnt可以理解为0~232-1的地址。类似于AD9850,可以通过计数器来实现跳跃寻址。其中DEVICE_CNT是一个在接口中经过宏定义的16倍波特率(9600x16)的分频参数,方便文件在例化时可以直接修改分频参数。(3)合成频率:方波的生成在上一步完成0~232-1寻址后,需要对地址进行比较、划分,得到一个方波信号。这类似于AD9850内部的比较器,通过输出的正弦波于门限电压做比较,得到一定频率下的方波。这里设计的Verilog HDL代码如下:代码1‑7 方波的生成1.//------------------------------------------------------ 2.//RTL2: Equal division of the Frequency division clock 3.reg cnt_equal; 4.always@(posedge clk or negedge rst_n) 5.begin 6. if(!rst_n) 7. cnt_equal <= 0; 8. else if(cnt < 32'h7FFF_FFFF) 9. cnt_equal <= 0; 10. else 11. cnt_equal <= 1; 12.end32’h7FFF_FFFF为232-1的中点,因此它可以作为“门限电压”,来实现合成频率后的方波输出。(4)分频时钟使能信号的生成。在FPGA中,除了全局时钟外,不允许用其他门控时钟来驱动电路,不然不易于综合电路,而且也无法保证电路的稳定性。因此,为了便于后续模块的调用,需要生成使能时钟信号。这里采用 “边沿检测技术”。代码如下:代码1‑8 分频时钟使能信号的生成1.//------------------------------------------------------ 2.//RTL3: Generate enable clock for clock 3.reg cnt_equal_r; 4.always@(posedge clk or negedge rst_n) 5.begin 6. if(!rst_n) 7. cnt_equal_r <= 0; 8. else 9. cnt_equal_r <= cnt_equal; 10.end 11.assign divide_clken = (~cnt_equal_r & cnt_equal) ? 1'b1 : 1'b0; 12.assign divide_clk = cnt_equal_r;这个分频器在后续使用中会慢慢验证,在此不会进验证(其实本人已经验证过了),有兴趣的可以自己编写程序进行验证。完整的代码及调用方式如下代码1‑9 完整的任意分频器设计代码1.//****************************************************************************// 2.//# @Author: 碎碎思 3.//# @Date: 2017-04-22 16:50:30 4.//# @Last Modified by: zlk 5.//# @WeChat Official Account: OpenFPGA 6.//# @Last Modified time: 2017-04-22 09:19:50 7.//# Description: 8.//# @Modification History: 2017-04-22 09:19:50 9.//# Date By Version Change Description: 10.//# ========================================================================= # 11.//# 2017-04-22 09:19:50 12.//# ========================================================================= # 13.//# | | # 14.//# | OpenFPGA | # 15.//****************************************************************************// 16.`timescale 1ns/1ns 17.module precise_divider 18.#( 19. //DEVIDE_CNT = 42.94967296 * fo 20.// parameter DEVIDE_CNT = 32'd175921860 //256000bps * 16 21.// parameter DEVIDE_CNT = 32'd87960930 //128000bps * 16 22.// parameter DEVIDE_CNT = 32'd79164837 //115200bps * 16 23. parameter DEVIDE_CNT = 32'd6597070 //9600bps * 16 24.) 25.( 26. //global clock 27. input clk, 28. input rst_n, 29. 30. //user interface 31. output divide_clk, 32. output divide_clken 33.); 34. 35.//------------------------------------------------------ 36.//RTL1: Precise fractional frequency for uart bps clock 37.reg [31:0] cnt; 38.always@(posedge clk or negedge rst_n) 39.begin 40. if(!rst_n) 41. cnt <= 0; 42. else 43. cnt <= cnt + DEVIDE_CNT; 44.end 45. 46.//------------------------------------------------------ 47.//RTL2: Equal division of the Frequency division clock 48.reg cnt_equal; 49.always@(posedge clk or negedge rst_n) 50.begin 51. if(!rst_n) 52. cnt_equal <= 0; 53. else if(cnt < 32'h7FFF_FFFF) 54. cnt_equal <= 0; 55. else 56. cnt_equal <= 1; 57.end 58. 59.//------------------------------------------------------ 60.//RTL3: Generate enable clock for clock 61.reg cnt_equal_r; 62.always@(posedge clk or negedge rst_n) 63.begin 64. if(!rst_n) 65. cnt_equal_r <= 0; 66. else 67. cnt_equal_r <= cnt_equal; 68.end 69.assign divide_clken = (~cnt_equal_r & cnt_equal) ? 1'b1 : 1'b0; 70.assign divide_clk = cnt_equal_r; 71. 72. 73.endmodule代码1‑10 调用完整的任意分频器设计代码示例1.wire divide_clken; 2.precise_divider 3.#( 4. //DEVIDE_CNT = 42.94967296 * fo 5. 6.// .DEVIDE_CNT (32'd175921860) //256000bps * 16 7.// .DEVIDE_CNT (32'd87960930) //128000bps * 16 8.// .DEVIDE_CNT (32'd79164837) //115200bps * 16 9. .DEVIDE_CNT (32'd6597070) //9600bps * 16 10.) 11.u_precise_divider 12.( 13. //global 14. .clk (clk_ref), //100MHz clock 15. .rst_n (sys_rst_n), //global reset 16. 17. //user interface 18. .divide_clk (divide_clk), 19. .divide_clken (divide_clken) 20.);