本科时期的一个课设,现在将他分享出来,写了很详细的文章,可以直接拿去使用:设计采用波形查找表和相位累加器的方法实现DDS,查找表的数据位宽为8位,采样点数为4096。波形产生范围是100Hz-20MHz,最小频率间隔为1Hz,用8个数码管显示频率,可产生正弦波、方波、三角波,波形用两个led显示,select信号控制,全灭为正弦波,一亮一灭为方波,全亮为锯齿波。为了使频率和相位便于调节,用3个拨码开关控制步进,用8个led显示状态。
基于FPGA的函数信号发生器设计
目录
摘要:本设计以EGO1开发板为核心部件,基于Xilinx Vivado平台,运用Verilog语言,采用波形查找表和相位累加器的方法实现了信号发生器的设计,总结概括了DDS(直接数字频率合成器)的原理、详细设计、仿真分析以及实验总结。DDS通过相位累加和波形数据查询实现信号合成,涉及奈奎斯特采样定理和调频原理。设计中考虑了频率与相位步进精度,利用高位相位累加器和正弦波采样点数实现高精度输出。仿真验证了设计的正确性,实验表明该DDS设计性能良好。
关键字:Vivado;Verilog;函数信号发生器;波形查找表;相位累加器
1 原理分析
DDS信号发生器采用直接数字频率合成(Direct Digital Synthesis,简称DDS)技术,把信号发生器的频率稳定度、准确度提高到与基准频率相同的水平,并且可以在很宽的频率范围内进行精细的频率调节。采用这种方法设计的信号源可工作于调制状态,可对输出电平进行调节,也可输出各种波形。DDS的设计思想完全是基于数值计算信号波形的抽样值来实现频率合成的。
DDS的工作原理是基于相位和幅度的对应关系,通过改变频率控制字来改变相位累加器的累加速度,然后在固定时钟的控制下取样,取样得到的相位值通过相位幅度转换得到与相位值对应的幅度序列,幅度序列通过数模转换得到模拟形式量化的正弦波输出。
1.1 DDS框图
DDS的结构主要由相位累加器、波形存储器、数模(D/A)转换器和低通滤波器等四个大的结构组成。其结构框图如下:
图1-1 DDS结构图
图中,相位累加器是由N位加法器与N位累加寄存器构成,它是DDS模块中一个极其重要的部分。在参考频率时钟的驱动下,DDS模块开始工作;当每来一个参考时钟时,累加器就把频率控制字FW与寄存器输出的值进行累加,将相加后的结果再输入到寄存器中,而累加寄存器就将在上一个参考时钟作用时产生的数据通过反馈的方式输送到累加器中。这样,在时钟的作用下,就可以不停的对频率控制字进行累加。此时,用相位累加器输出的数据作为地址在波形存储器中通过查找地址所对应的幅值表,就可以完成其从相位到幅值之间的转化。
1.2 DDS原理
1.2.1 DDS的工作原理
DDS技术是根据奈奎斯特取样定律,从连续信号的相位出发,将正弦信号取样,编码,量化,形成一个正弦函数表,储存在EPROM中,合成时,通过改变相位累加器的频率字来改变相位增量,也就是步长。相位增量的不同导致一个周期内取样点的不同,在时钟频率即采样频率不变的情况下,通过相位的改变来改变频率。
1.2.2 奈奎斯特采样定理
奈奎斯特采样定理讨论了对于频谱在信号的采样频率限制,所以也称为低通采样定理。假设存在一个最高频率为的带限信号,如果以不小于二倍于的采样频率对信号进行采样,即,得到,则原信号可以被采样数据串无失真的还原出来。
1.2.3 合成
合成,简单来说就是将我们的采样数据还原成模拟信号。还原的方式为:以一定的频率将采样数据依次输出。就可以还原波形。
假设,以100M的频率输出我们的1024个抽样数据,则将会得到一个频率为:
的正弦波。这就达到了最初的信号输出。
1.2.4 调频
调频的方案有两种:一种是改变时钟频率,将读取采样数据的速度变快或者变慢,这样就可以改变频率。这种方法对于当下很多开发板固定的晶振频率来说比较难以实现。
另一种方案就是减少输出的采样数据,输出的采样数据越少,频率便会越高。 在DDS模块中,输出频率的公式为:
输出信号的频率分辨率为:
从上两式可以看出,在参考信号与加法器或寄存器的位数给定时,信号最终的输出频率主要由频率控制字决定。故当频率控制字变化时,输出频率也跟着变化,从而可以实现调频的基本功能。
2 详细设计
图2-1 RTL视图
RTL视图如上所示,本设计采用波形查找表和相位累加器的方法实现DDS,查找表的数据位宽为8位,采样点数为4096。波形产生范围是100Hz-20MHz,最小频率间隔为1Hz,用8个数码管显示频率,可产生正弦波、方波、三角波,波形用两个led显示,select信号控制,全灭为正弦波,一亮一灭为方波,全亮为锯齿波。 为了使频率和相位便于调节,用3个拨码开关控制步进,用8个led显示状态。对应关系如下表:
sw[2:0] | 频率步进 | 相位步进 | led[7:0] |
---|---|---|---|
000 | 1Hz | 1度 | 0000_0001 |
001 | 10Hz | 5度 | 0000_0010 |
010 | 100Hz | 10度 | 0000_0100 |
011 | 1KHz | 30度 | 0000_1000 |
100 | 10KHz | 50度 | 0001_0000 |
101 | 100KHz | 90度 | 0010_0000 |
110 | 1MHz | 100度 | 0100_0000 |
111 | 10MHz | 180度 | 1000_0000 |
2.1 频率与相位步进设计
参数文件如下,其中包括计算相位控制字和频率控制字的公式。
//频率步进(下面是以时钟频率100M,相位控制字12位,频率控制字32位计算所得)
`define Fword_step0 32'd43 //频率步进1HZ
`define Fword_step1 32'd430 //频率步进10HZ
`define Fword_step2 32'd4_295 //频率步进100HZ
`define Fword_step3 32'd42_950 //频率步进1KHZ
`define Fword_step4 32'd429_497 //频率步进10KHZ
`define Fword_step5 32'd4_294_967 //频率步进100KHZ
`define Fword_step6 32'd42_949_673 //频率步进1MHZ
`define Fword_step7 32'd429_496_730 //频率步进10MHZ
//相位步进
`define Pword_step0 12'd11 //相位步进1度
`define Pword_step1 12'd57 //相位步进5度
`define Pword_step2 12'd114 //相位步进10度
`define Pword_step3 12'd341 //相位步进30度
`define Pword_step4 12'd569 //相位步进50度
`define Pword_step5 12'd1024 //相位步进90度
`define Pword_step6 12'd1138 //相位步进100度
`define Pword_step7 12'd2048 //相位步进180度
//计算公式
`define SYS_CLK 33'd100_000_000 //系统时钟频率
`define MAX_FWORD 33'd4294967296 //2的WORD_WIDTH次方
`define ROM_DEP 32'd4096 //2的ADDR_WIDTH次方
`define Fword_step0 integer(`MAX_FWORD/(`SYS_CLK/ 1.000_000_000_0)) //频率步进1HZ
`define Fword_step1 integer(`MAX_FWORD/(`SYS_CLK/ 10.000_000_000_0)) //频率步进10HZ
`define Fword_step2 integer(`MAX_FWORD/(`SYS_CLK/ 100.000_000_000_0)) //频率步进100HZ
`define Fword_step3 integer(`MAX_FWORD/(`SYS_CLK/ 1000.000_000_000_0)) //频率步进1KHZ
`define Fword_step4 integer(`MAX_FWORD/(`SYS_CLK/ 10000.000_000_000_0)) //频率步进10KHZ
`define Fword_step5 integer(`MAX_FWORD/(`SYS_CLK/ 100000.000_000_000_0)) //频率步进100KHZ
`define Fword_step6 integer(`MAX_FWORD/(`SYS_CLK/ 1000000.000_000_000_0)) //频率步进1MHZ
`define Fword_step7 integer(`MAX_FWORD/(`SYS_CLK/10000000.000_000_000_0)) //频率步进10MHZ
`define Pword_step0 integer(`ROM_DEP/(360/ 1.000_000_000_0)) //相位步进1度
`define Pword_step1 integer(`ROM_DEP/(360/ 5.000_000_000_0)) //相位步进5度
`define Pword_step2 integer(`ROM_DEP/(360/ 10.000_000_000_0)) //相位步进10度
`define Pword_step3 integer(`ROM_DEP/(360/ 30.000_000_000_0)) //相位步进30度
`define Pword_step4 integer(`ROM_DEP/(360/ 50.000_000_000_0)) //相位步进50度
`define Pword_step5 integer(`ROM_DEP/(360/ 90.000_000_000_0)) //相位步进90度
`define Pword_step6 integer(`ROM_DEP/(360/100.000_000_000_0)) //相位步进100度
`define Pword_step7 integer(`ROM_DEP/(360/180.000_000_000_0)) //相位步进180度
`define PWORD_WIDTH 4'd12 //相位控制字位宽
`define FWORD_WIDTH 6'd32 //频率控制字位宽
`define ADDR_WIDTH 4'd12 //ROM查找表深度位宽
`define DA_WIDTH 4'd8 //DA位宽
//`define KEY_CNT 21'd1_999_999 //按键计数值,实际
`define KEY_CNT 21'd200 //按键计数值,仿真时
`define Fword_START integer(`Fword_step2) //输出最小频率所对应的频率控制字
`define Fword_END integer(`Fword_step7*2) //输出最大频率所对应的频率控制字
`define Pword_START integer(0) //输出最小相位所对应的相位控制字
`define Pword_END integer(`Pword_step7*2) //输出最大相位所对应的相位控制字
参数文件说明:其核心就是频率控制,相位控制和相位累加。主要参数有:频率步进精度、相位步进精度、DAC的位数以及滤波器的设计。
2.1.1 频率步进精度
频率步进精度就决定了程序中相位累加器的位数(一般都是取24-32位),同时也要取决于FPGA的时钟(这里以100MHz为例)。
也就是说采用32位的相位累加器,在参考时钟为100MHz时可以达到0.02328Hz的精度。
2.1.2 相位步进精度
相位步进主要是调解输出波形的相位。这里就还涉及到采样深度(也就是对正弦波的采样点数,这里以4096为例)
根据不同的相位精度,求出X即可得相位控制字。如:相位步进5度,计算得X=56.88888。
2.1.3 涉及计算的主要公式
(1) 频率控制字
M为频率控制字,改变M的值即可改变输出频率,N为相位累加器的位数。
由于采样定理,所产生信号的频率不能超过时钟频率的一半,在实际运用中为了保证输出信号的质量,输出频率不超过参考时钟频率的三分之一,以避免混叠或被谐波落入有用输出频带内。相位累加器输出位并不全部加到查询表,而要截断。相位截断减小了查询表长度,但并不影响频率分辨率,对最终输出仅增加一个很小的相位噪声。DAC分辨率一般比查询表长度小2-4位。
(2) 相位控制字
X为ROM查找表的采样深度,p为相位,a为相位控制字。将计算结果加在相位累加器上便可得到相应相位的波形。
2.1.4 对参数文件进行详细解释
为了增加代码的可移植性,特意将设计里面所用到的所有参数全部列入para.v文件里面,可以参照上述代码和注释加以理解。相位控制字和频率控制字的计算公式全部写成宏参数的形式,不需要自己去算相位和频率控制字,只需要按照实际需求修改相位累加器位宽、ROM查找表深度位宽、时钟频率等参数,编译器会计算出相应步进参数,复制到相位累加器模块里面去。
2.2 相位累加设计
`include"para.v"
module DDS_phase_acc(
input clk ,
input rst_n ,
input EN ,//信号使能
input [`FWORD_WIDTH-1:0] Fword ,//频率控制字
input [`PWORD_WIDTH-1:0] Pword ,//相位控制字
output reg [`ADDR_WIDTH -1:0] Rom_Addr
);
reg [`FWORD_WIDTH-1:0] Fre_acc ;//相位累加器
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
Fre_acc <= 0;
else if(!EN)
Fre_acc <= 0;
else
Fre_acc <= Fre_acc + Fword;
end
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
Rom_Addr <= 0;
else if (!EN)
Rom_Addr <= 0;
else
Rom_Addr <= Fre_acc[`FWORD_WIDTH-1:`FWORD_WIDTH-`ADDR_WIDTH] + Pword;//取累加器中高ADDR_WIDTH位作为ROM查询地址
end
endmodule
3 仿真分析
3.1 仿真代码
`timescale 1ns / 1ns
`define clk_period 10
module DDS_tb();
reg sys_clk ;
reg sys_rst_n ;
reg [2:0] sw ;
reg [4:0] key ;
wire [7:0] led ;
wire [7:0] DA_data ;
wire DA_clk ;
wire [7:0] sseg_high ;
wire [3:0] an_high ;
wire [7:0] sseg_low ;
wire [3:0] an_low ;
wire [1:0] select ;
DDS i0(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.sw (sw ),
.key (key ),
.led (led ),
.select (select ),
.DA_data (DA_data ),
.DA_clk (DA_clk ),
.sseg_high (sseg_high ),
.an_high (an_high ),
.sseg_low (sseg_low ),
.an_low (an_low )
);
always #(`clk_period/2) sys_clk = ~sys_clk;
initial
begin
//初始化
sys_clk = 1'b0;
sys_rst_n = 1'b0; //复位
key = 5'b11111;
sw = 3'b000;
#(`clk_period*200)
sys_rst_n = 1'b1;
#(`clk_period*1100_000)//输出100HZ正弦波
sw = 3'b110; //将频率挡设置为1MHZ,相位挡100度
key[1] = 1'b0; //频率加1MHZ
#(`clk_period*300) //本应该延时大于20ms,但为了仿真方便,修改了按键计数器的值为200
key[1] = 1'b1;
#(`clk_period*210)
key[0] = 1'b0; //设置输出为方波
#(`clk_period*300)
key[0] = 1'b1;
#(`clk_period*210)
key[0] = 1'b0; //设置输出为锯齿波
#(`clk_period*300)
key[0] = 1'b1;
#(`clk_period*210)
key[0] = 1'b0; //设置输出为正弦波
#(`clk_period*300)
key[0] = 1'b1;
#(`clk_period*210)
sw = 3'b010; //将频率挡设置为100HZ,相位挡10度
key[2] = 1'b0; //频率减100HZ
#(`clk_period*300)
key[2] = 1'b1;
#(`clk_period*210)
sw = 3'b101; //相位步进为90度,频率步进为100KHz
key[3] = 1'b0; //相位加90度
#(`clk_period*300)
key[3] = 1'b1;
end
endmodule
3.2 仿真波形
由于功能较为复杂,一些功能不好做仿真,这里只挑选了核心的功能进行仿真。有的输出信号是多余的,只是为了仿真才添加,实际运用时只需保留引脚约束文件xdc里面的输出信号即可。仿真时需要将para.v参数文件里面的参数KEY_CNT改小,以便按键方便仿真,以下仿真KEY_CNT = 200。
默认情况下输出
默认输出频率应为100Hz,周期为10ns图中正弦波峰和相邻波谷时间值相差为(7.51-2.54)ns=5ns,等于半个周期,仿真结果正确。
图3-2
如图3-2信号key[1]出现下降沿,相当于频率加按键被触发,此时频率应该加一个当前档位的对应的步进。此时频率步进位1MHz,所以这之后频率应该变为1.0001MHz,对应周期为0.9999us。正弦波相邻波谷时间差为(11005.715-11004.715)us=1us。原因是在做相位累加器时是取频率控制字高12位作为查找表地址,查找表的深度不够大,做不到1Hz的步进,如果要做到可以增加查找表深度,但是会增加资源消耗。
图3-3
如图3-3信号key[0]出现下降沿,相当于波形选择按键被触发,此时波形变为方波,频率用前面的方法计算也是正确的,所以仿真结果正确。
图3-4
如图3-4信号key[0]再次出现下降沿,相当于波形选择按键被触发,此时波形变为锯齿波,频率用前面的方法计算也是正确的,所以仿真结果正确。
图3-5
如图3-5信号key[2]出现下降沿,相当于频率减按键被触发,此时频率应该减去此时的一个频率步进100Hz,频率用前面的方法计算也是正确的,所以仿真结果正确。
图3-6
如图3-6信号key[3]出现下降沿,相当于相位加按键被触发,此时波形相位应该加上一个此时的相位步进90度,在红色椭圆处看到相位发生了90度变化,所以仿真结果正确。
图3-7
如图3-7sw=0时,led=0000_0001,sw=2时led=0000_0100。仿真结果正确。
图3-8
如图3-8sseg_high[7:0]是数码管高四位的段选码,根据下面an_high[3:0]可以知道高四位显示的的位对应的段码。由图中标注可知此时显示数字应为00.000100MHz,即为100Hz,select值为00表示输出为正弦波。所以仿真结果正确。
图3-9
如图3-9方波select为01,正弦波为00,锯齿波为11,满足前面说的设计要求全灭为正弦波,一亮一灭为方波,全亮为锯齿波。所以仿真结果正确。
图3-10
如图3-10当前波形频率为1MHz,对应显示为01.000000MHz,仿真正确。
图3-11
如图3-11,使DAC0832输入寄存器工作在锁存状态,而DAC寄存器工作在直通状态,要求、都为低电平,DAC寄存器的锁存选端得不到有效电平而直通;输入寄存器控制信号处于高电平、处于低电平,当端来一个负脉冲时就可以完成一次转换。
本次设计的资源占用率情况如下图所示:
图3-12
图3-13
图3-14
图3-15
4 实验总结
面对电子技术日新月异的发展,利用EDA手段进行设计已成为不可阻挡的趋势。相对于传统至底向上的设计方式,自上而下的设计具有其显著的优越性。利用EDA设计软件辅助设计,方便快捷,减少了错误率的产生,缩短了产品的设计及上市周期,既减轻了设计工作量又满足了商业利益的需求。
该系统以EGO1开发板为核心部件,可利用Vivado软件编程实现了简易函数信号发生器设计。努力做到了线路简单、高性价比的特点,充分利用了软件编程,弥补了硬件元器件的不足。在设计过程当中,遇到了软件操作不熟练,程序编写不规范等诸多问题,通过对问题的总结分析得出,应用软件的主要功能必须熟练操作,才能提高工作效率,需要规范操作的地方必须严格按照使用说明操作,避免由于软件使用不当造成的错误产生。程序的编写格式必须规范,模块、端口以及信号变量的命名应当反映实际意义,缩进格式工整明了,方便阅读理解,这样有利于程序的编写,有利于分析调试,也有利于程序的重复使用。此次课题的设计已告一段落,在这次设计过程中需要用一些不曾学过的东西时,就要去有针对性地查找资料,然后加以吸收利用,以提高自己的应用能力,而且还能增长自己见识,补充最新的专业知识,学会了一些编程方面的常用算法。
作为一名电子专业的学生,我将会继续在新技术的道路上不断钻研、开拓进取。相信通过此次设计的锻炼,我对专业知识和技能的掌握将更加牢靠,在今后的工作和学习中,必将使我受益匪浅,取得应有的成绩。
参考文献
[1] 汤勇明,张圣清,陆佳华. 搭建你的数字积木[M]. 北京:清华大学出版社,2017.
[2] 梅雪松,袁玉卓,曾凯锋. FPGA自学笔记—设计与验证[M]. 北京:北京航空航天大学出版社,2017.
[3] https://wenku.baidu.com/view/bcb5fa05eff9aef8941e0680
[4] https://blog.csdn.net/qq_36854651/article/details/104433727?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-5
[5] https://blog.csdn.net/zf_suan/article/details/53039514
其中所需要波形可以由matlab生成,或者使用如下所示的波形数据生成器:
波形数据生成器
获取源码和工程可点击如下链接:https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzkxNjM0NDk2Nw==&action=getalbum&album_id=3473393253968314372#wechat_redirect