[FPGA基础应用]DDS正弦波发生器

首先分享两篇有关dds信号产生的文章。

一、

来源:http://www.digilent.com.cn/community/241.html

在开始DDS这个实验之前需要先解释一下DDS原理,摘了一段网上大神的解释:

基本原理框图:

191686382900.jpg
两个关键术语:
       a. 相位累加器:Phase = Phase + freq_ctrl,可以暂且理解为i = i + 1一样的东西。
       b. 频率控制字:freq_ctrl,这个东西的值直接影响输出信号的频率。
 

假设系统工作时钟(查表时钟)为150MHz,ROM表深度为4096,存储波形为1个周期(如正弦波每周期抽样量化为4096个点),也就是一个周期的波形由4096个采样点组成,意味着输出波形一个周期最多4096个采样点。比如Data输出10M的正弦波,输出的正弦波每周期只有15个采样点;而输出1M的正弦波,每周期将有150个采样点;我们也可以知道当输出频率小于等于36.621KHz时,输出波形每周期由4096个点构成。输出信号的每周期点越多,阶梯效过越不明显,经过低通滤波器后波形越好看。

如果freq_ctrl为1时,那么输出信号为150MHz/4096=36.621KHz,如果freq_ctrl为2时,那么输出信号为150MHz*2/4096=73.242KHz。因此当需要输出正弦波频率为fout MHz时,
 

Fout = 150MHz*freq_ctrl/4096,所以freq_ctrl = Fout*4096/150MHz。

如果上面的大家都理解了,那么恭喜你已经完全理解了DDS的核心部分。至于其他DDS相关的内如,比如频率分辨率(因为rom地址必须是整数,所以freq_ctrl必须是整数,所以上例的频率分辨率为),旁瓣抑制比(量化多1bit,多6db = 20lg2)……

   本次实验采用官方DDS IP核的形式进行,不同与前三个实验的地方是本次需要进行板上调试,开发过程当中这个也是除了功能仿真很常用的一种调试手段。

    功能简介:使用板上一个按键来控制不同输出频率间的切换,按下按键切换输出频率。

1、新建DDS_test工程,找到DDS ip核双击进入设置界面:

QQ截图20161210155247.png

注意,采用的时钟时100MHz的输入时钟,通道选择一个通道,操作模式选择标准,另外一种是栅格化方式有兴趣可以试一试。参数选择选择系统参数,这个比硬件参数不同地方就是可以设置信噪比、频率分辨率等参数。

2、实施界面可编程相位增量选择可编程,其他默认即可,之后点击OK:

QQ截图20161210155318.png

3、开始新建DDS_test进行代码编写:

 
  1. `timescale 1ns / 1ps
  2. //////////////////////////////////////////////////////////////////////////////////
  3. // Company:
  4. // Engineer:
  5. //
  6. // Create Date: 2016/12/10 15:59:44
  7. // Design Name:
  8. // Module Name: DDS_test
  9. // Project Name:
  10. // Target Devices:
  11. // Tool Versions:
  12. // Description:
  13. //
  14. // Dependencies:
  15. //
  16. // Revision:
  17. // Revision 0.01 - File Created
  18. // Additional Comments:
  19. //
  20. //////////////////////////////////////////////////////////////////////////////////
  21.  
  22.  
  23. module DDS_test(
  24. input clk,
  25. input rst_n,
  26.  
  27. input key1
  28. );
  29. reg [19:0] key1_cnt;
  30. reg dds_we;
  31.  
  32. reg [ 3:0] dds_freq;
  33. reg [31:0] dds_data;//DDS控制字
  34. wire[31:0] phase_data;
  35. wire dds_val;
  36. wire [15:0] sine;
  37. wire [15:0] cosine;
  38. always @(posedge clk or negedge rst_n)
  39. if(!rst_n)
  40. begin
  41. key1_cnt <= 20'd0;
  42. dds_we <= 1'b0;
  43. dds_freq <= 4'd0;
  44. end
  45. else
  46. begin
  47. if(key1 == 1'b0)
  48. begin
  49. key1_cnt <= 20'd0;
  50. end
  51. else if(key1 && (key1_cnt <= 20'd99999))//按键持续时间大于1ms
  52. begin
  53. key1_cnt <= key1_cnt + 20'd1;
  54. end
  55. else
  56. begin
  57. key1_cnt <= key1_cnt;//避免长时间按键处于1时自增
  58. end
  59.  
  60. if(key1 && (key1_cnt == 20'd99999))
  61. begin
  62. dds_freq <= dds_freq + 4'd1;
  63. dds_we <= 1'b1;
  64. end
  65. else
  66. begin
  67. dds_we <= 1'b0;
  68. end
  69. end
  70.  
  71. reg dds_we_reg;//控制字有效信号
  72.  
  73. always @(posedge clk or negedge rst_n)
  74. if(!rst_n)
  75. begin
  76. dds_we_reg <= 1'b0;
  77. dds_data <= 32'd0;
  78. end
  79. else
  80. begin
  81. dds_we_reg <= dds_we;
  82. case(dds_freq)
  83. 4'b0000: begin
  84. dds_data <= 32'd23;//1khz dds_data*2^32)/(100*1000000)
  85. end
  86. 4'b0001: begin
  87. dds_data <= 32'd116;//5khz dds_data*2^32)/(100*1000000)
  88. end
  89. 4'b0010: begin
  90. dds_data <= 32'd232;//10khz dds_data*2^32)/(100*1000000)
  91. end
  92. 4'b0011: begin
  93. dds_data <= 32'd1164;//50khz dds_data*2^32)/(100*1000000)
  94. end
  95. 4'b0100: begin
  96. dds_data <= 32'd2328;//100khz dds_data*2^32)/(100*1000000)
  97. end
  98. 4'b0101: begin
  99. dds_data <= 32'd11641;//500khz dds_data*2^32)/(100*1000000)
  100. end
  101. 4'b0110: begin
  102. dds_data <= 32'd23283;//1Mhz dds_data*2^32)/(100*1000000)
  103. end
  104. 4'b0111: begin
  105. dds_data <= 32'd46566;//2Mhz dds_data*2^32)/(100*1000000)
  106. end
  107. 4'b1000: begin
  108. dds_data <= 32'd93132;//4Mz dds_data*2^32)/(100*1000000)
  109. end
  110. 4'b1001: begin
  111. dds_data <= 32'd186264;//8mhz dds_data*2^32)/(100*1000000)
  112. end
  113. 4'b1010: begin
  114. dds_data <= 32'd232830;//10mhz dds_data*2^32)/(100*1000000)
  115. end
  116. 4'b1011: begin
  117. dds_data <= 32'd465661;//20mhz dds_data*2^32)/(100*1000000)
  118. end
  119. 4'b1100: begin
  120. dds_data <= 32'd931322;//40mhz dds_data*2^32)/(100*1000000)
  121. end
  122. 4'b1101: begin
  123. dds_data <= 32'd1164153;//50mhz dds_data*2^32)/(100*1000000)
  124. end
  125. 4'b1110: begin
  126. dds_data <= 32'd2328306; //100MHz: (dds_data*2^32)/(101*1000000)
  127. end
  128. 4'b1111: begin
  129. dds_data <= 32'd4656612; //200MHz: (dds_data*2^32)/(100*1000000)
  130. end
  131. default: begin
  132. dds_data <= 32'd23; //1KHz: (dds_data*2^32)/(100*1000000)
  133. end
  134. endcase
  135. end
  136. //----IP核接口例化---------------------------
  137. dds_compiler_0 dds_ip (
  138. .aclk(clk), // input wire aclk
  139. .s_axis_config_tvalid(dds_we_reg), // input wire s_axis_config_tvalid
  140. .s_axis_config_tdata(dds_data), // input wire [31 : 0] s_axis_config_tdata
  141. .m_axis_data_tvalid(dds_val), // output wire m_axis_data_tvalid
  142. .m_axis_data_tdata({sine,cosine}), // output wire [31 : 0] m_axis_data_tdata
  143. .m_axis_phase_tvalid(), // output wire m_axis_phase_tvalid
  144. .m_axis_phase_tdata(phase_data) // output wire [31 : 0] m_axis_phase_tdata
  145. );
  146.  
  147. //----在线调试----------------
  148. (* MARK_DEBUG="true" *) wire [11:0] cosine_w;
  149. (* MARK_DEBUG="true" *) wire [11:0] sine_w;
  150. (* MARK_DEBUG="true" *) wire dds_we_reg_w;
  151. (* MARK_DEBUG="true" *) wire [31:0] dds_data_w;
  152. (* MARK_DEBUG="true" *) wire [3:0]dds_freq_w;
  153.  
  154. assign cosine_w = cosine[11:0];
  155. assign sine_w = sine[11:0];
  156. assign dds_we_reg_w = dds_we_reg;
  157. assign dds_data_w = dds_data;
  158. assign dds_freq_w = dds_freq;
  159.  
  160. endmodule
  161. 4、新建仿真文件进行代码仿真:
  162. `timescale 1ns / 1ps
  163. //////////////////////////////////////////////////////////////////////////////////
  164. // Company:
  165. // Engineer:
  166. //
  167. // Create Date: 2016/12/10 16:22:40
  168. // Design Name:
  169. // Module Name: vtf_DDS_test
  170. // Project Name:
  171. // Target Devices:
  172. // Tool Versions:
  173. // Description:
  174. //
  175. // Dependencies:
  176. //
  177. // Revision:
  178. // Revision 0.01 - File Created
  179. // Additional Comments:
  180. //
  181. //////////////////////////////////////////////////////////////////////////////////
  182.  
  183.  
  184. module vtf_DDS_test();
  185. // Inputs
  186. reg clk;
  187. reg rst_n;
  188. reg key1;
  189.  
  190. DDS_test top(
  191. .clk(clk),
  192. .rst_n(rst_n),
  193.  
  194. .key1(key1)
  195.  
  196. );
  197. initial
  198. begin
  199. // Initialize Inputs
  200. clk = 0;
  201. rst_n = 0;
  202. key1 = 0;
  203. // Wait 100 ns for global reset to finish
  204. #100 rst_n = 1;//复位完成
  205. #100 key1 = 1;
  206. #1000020 key1 = 0;//1
  207. #50_000_00 key1 = 1;
  208. #1000020 key1 = 0;//2
  209. #50_000_00 key1 = 1;
  210. #1000020 key1 = 0;//3
  211. #50_000_00 key1 = 1;
  212. #1000020 key1 = 0;//4
  213. #50_000_00 key1 = 1;
  214. #1000020 key1 = 0;//5
  215. #50_000_00 key1 = 1;
  216. #1000020 key1 = 0;//6
  217. #50_000_00 key1 = 1;
  218. #1000020 key1 = 0;//7
  219. #50_000_00 key1 = 1;
  220. #1000020 key1 = 0;//8
  221. #50_000_00 key1 = 1;
  222. #1000020 key1 = 0;//9
  223. #50_000_00 key1 = 1;
  224. #1000020 key1 = 0;//10
  225. #50_000_00 key1 = 1;
  226. #1000020 key1 = 0;//11
  227. #50_000_00 key1 = 1;
  228. #1000020 key1 = 0;//12
  229. #50_000_00 key1 = 1;
  230. #1000020 key1 = 0;//13
  231. #50_000_00 key1 = 1;
  232. #1000020 key1 = 0;//14
  233. #50_000_00 key1 = 1;
  234. #1000020 key1 = 0;//15
  235. // Add stimulus here
  236. end
  237.  
  238. always #5 clk = ~clk;
  239. endmodule
  240.  

复制代码

5、仿真效果图,将输出设置为有符号十进制且信号为模拟信号进行显示,这样就可以看到输出的sin/cos信号为正弦波,注意,由于IP核设置SNR为72,所以有效位为12位,这样的话可以只监控bit0~11即可。由于自己电脑太渣就没能看到后面的波形,卡出翔来快,自己对电脑性能自信的话可以看一下

QQ截图20161210162800.png

6、接下来对引脚进行分配,由于板子上没有DA所以输出引脚就不用了,直接将时钟、复位和按键引脚定义一下就好。

7、进行板级调试前的设置:

QQ截图20161212083158.png

8、综合后点击如图set up debug:

QQ截图20161212084733.png

9、next,如下图所示添加需要添加的信号,输入信号之后回车即可出现所有匹配的信号,选中需要的添加就行。好友就是时钟域设置,这些具体的用法白的即可,太多不做介绍:

QQ截图20161212085234.png

10、设置监控深度和捕获控制,资源多的可以多用点,少的拉倒,完了点击OK即可:

QQ截图20161212085520.png

11、编译后生成.bit文件,选中bit和调试ltx文件之后下载就行:

QQ截图20161212093248.png

12、出现如图所示的界面,箭头打钩表示可以显示的窗口,自己试:

QQ截图20161212093539.png

13、没有设置触发条件,设置输出sine_w、cosine_w为模拟输出,且输出格式为有符号十进制。下图中标识1表示单次监控,标识2表示连续监控,前几次看到波形不是一个完整的正弦波,说明设置监控深度点数不够显示一个完整波形。

QQ截图20161212093851.png

QQ截图20161212094326.png

QQ截图20161212094339.png

总结:本次实验主要介绍vivado下DDSip核的简单实用流程,以及开发过程当中经常使用到的板级调试方法。

按惯例实验程序发上来,有需要的自取。

问一下版主啊,我这开发板为啥没有DDR3的驱动例程啊,按照论坛上的例程Arty - Getting Started with Microblaze Servers建立的工程显示

-----lwIP TCP echo server ------
TCP packets sent to port 6001 will be echoed back
link speed: 1000
DHCP Timeout
Configuring default IP of 192.168.1.10
Board IP: 192.168.1.10
Netmask : 255.255.255.0
Gateway : 192.168.1.1
TCP echo server started @ port 7

不知道相关技术人员能不能给出解答啊!

二、

来源https://www.cnblogs.com/christsong/p/5536995.html

前言:

DDS:直接数字频率合成,正弦波0-2pi周期内,相位到幅度是一一对应的(这里我们使用放大后的整数幅度)。

主要思路:

个人理解,FPGA不擅长直接做数字信号计算,那样太占用片上逻辑资源,所以需要事先建立 正弦波相位-幅度 表,然后在时钟下,通过相位累加并用相位作为地址索引来查询正弦波信号表。

正弦波相位-幅度 表:
存储的是量化的正弦波在一个周期的幅度信息(幅度的地址即相位)。
幅度的地址数目决定了相位量化的误差。
而存储每一个幅度的比特数决定了幅度的量化误差。
可以通过matlab以及Xilinx的IP核向导创建。

Verilog编写的DDS模块主要由三部分组成,

  • 相位累加器,用于决定输出信号频率的范围和精度;
  • 正弦函数模块,用于存储经量化和离散后的正弦函数的幅值;
  • 查表模块,对相位累加器的输出地址查表。

两种方法可以改变输出信号的频率:

  • 改变查表寻址的时钟频率,从而改变输出波形的频率。
  • 改变寻址的步长来改变输出信号的频率。
    步长即为相位增量。
    由累加器对相位增量进行累加,
    累加器的值作为查表地址。

相位累加器是 DDS 的核心所在,前面在低于时钟频率的任意频率生成(相位累加器)中我们已经进行了叙述。
正弦函数模块包含一个周期正弦波的数字幅度信息,每个地址对应正弦波中0-2pi范围的一个相位点。查表模块把输入的地址相位信息映射成正弦波幅度的数字量信号。相位寄存器每经过 2^N/K 个时钟后回到初始状态,相应地正弦查询表经过一个循环回到初始位置,输出一个正弦波。

输出正弦波周期为fo=fc* K/2^N ,最小分辨率为f=fc/2^N。(通过fc和K控制正弦波频率精度) 其中,N 为累加器位宽,K 为步长,fc 为时钟频率。计数模(最大值):M=2^N。

一般正弦波表幅度地址位宽与累加的查表地址位宽不同,按前者位宽取后者对应高位的位宽即可。(具体见实例)

先用matlab生成1024点的正弦波数据:

clc;clear;
N = 10;                     %储存单元地址线
depth=2^N;                 %存储单元;
widths=N;                    %数据宽度为8位;
index = linspace(0,pi*2,depth);              
sin_value = sin(index);                
sin_value = sin_value * (depth/2 -1);  %扩大正弦幅度值    
sin_value = fix((sin_value)+0.5);
plot(sin_value);
number = [0:depth];
fid=fopen('sin_table.coe','w+');
fprintf(fid,'memory_initialization_radix=10;\n');
fprintf(fid,'memory_initialization_vector=\n');
for i = 1 : depth - 1  
    fprintf(fid, '%d,\n', sin_value(i));
end
fprintf(fid, '%d;', sin_value(depth));
fclose(fid);

Verilog程序

1、adder.v文件,相位累加模块

`timescale 1ns/1ps
/***************************************
晶振频率 fc = 100MHz
输出频率 fo = 1kHz(根据需要可以设为任意值)
控制参数 K  = (fo*2^N)/fc = 42950
参数 N = 2^32,(32为计数器的位宽)
****************************************/
module PHASE_ADDER(
    input clk,
    input rst,
    output reg [31:0] cnt,
    output reg clk_out
    );

always @(posedge clk or posedge rst) 
    if(rst)
        cnt <= 0;
    else
        cnt <= cnt + 32'd42950;  //计数器步长K

always @(posedge clk or posedge rst)
    if(rst)
        clk_out <= 1'b0;
    else if(cnt < 32'h7FFF_FFFF)
        clk_out <= 1'b0;
    else
        clk_out <= 1'b1;

endmodule

2、dds_top.v顶层设计

`timescale 10ns /1ns

module dds_top(
    input rst,
    input clk,
    output signed [15:0] sine_o
    );

    wire [31:0] phase;   //32bit内部连接线,传递相位增量
    wire clk_out;
    wire [9:0] addr;   //10bit相位信息

    PHASE_ADDER U_PHASE_ADDER(
        .clk    (clk    ),
        .rst    (rst    ),
        .cnt    (phase  ),
        .clk_out(clk_out)
        );
     
    assign addr = phase[31:22];//addr 10bit
    
    DDS_Table U_DDS_Table(
        .clka(clk),    // input wire clka
        .addra(addr),  // input wire [9 : 0] addra
        .douta(sine_o)  // output wire [15 : 0] douta
        );
endmodule

3、仿真测试文件

`timescale 1ns/1ps

module TB;
    
    reg clk;
    reg rst;
    wire clk_out;
    dds_top U_dds_top(
        .clk    (clk    ),
        .rst    (rst    )
    );

    initial begin
        clk = 0;
        rst = 0;
        #4 rst = 1;
        #3 rst = 0;
    end

    always #5 clk = ~clk;
endmodule 

matlab生成正弦数据:

sin_table

vivado和Modelsim联合仿真结果:

仿真结果

三、几点思考

1.“如果freq_ctrl为1时,那么输出信号为150MHz/4096=36.621KHz,如果freq_ctrl为2时,那么输出信号为150MHz*2/4096=73.242KHz。因此当需要输出正弦波频率为fout MHz时,Fout = 150MHz*freq_ctrl/4096,所以freq_ctrl = Fout*4096/150MHz。”    --------文章1                                                          

“输出正弦波周期为fo=fc* K/2^N ,最小分辨率为f=fc/2^N。(通过fc和K控制正弦波频率精度) 其中,N 为累加器位宽,K 为步长,fc 为时钟频率。计数模(最大值):M=2^N。”  -------文章2

                                                                         

文章1这里面提到的freq_ctrl频率控制字在第二篇文章里应该就是K,同时也是其他文章里的相位增量。

ROM表深度为4096(每周期抽样量化为4096个点)在第二篇文章里面应该就是2^N,就是归一化后能表示的最大相位,2^N还原后就是2pi。

2.“如果freq_ctrl为1时,那么输出信号为150MHz/4096=36.621KHz”从这个表述里其实可以发现,将左右两边式子取倒数,其实就是周期之间的关系。当dds的系统工作时钟确定时,每两次采样的间隔时间则固定了;那么,用我们要产生的信号周期除以每两次采样的间隔时间就能得出一共采样多少点。相位最大值为2^N,用相位总量除以点数就能得出每两个点相隔的相位,即相位增量。由此就可以得出.K(相位增量)=2^N* fo/fc,最终推导出fo=fc* K/2^N。(推倒式子具体看手写图)

3.从fo=fc* K/2^N可以看出,因为K作为相位增量只能是整数,而且不能大于2^N(相位最大值),所以单个DDS产生的信号频率永远是小于fo的。如何突破频率限制,产生大于fo的频率,看下面的手写图。

4.

猜你喜欢

转载自blog.csdn.net/weixin_42229533/article/details/81982355