声明:这篇博客是在充分参考前人成果的基础上写成的,如有侵权,请联系我作进一步处理。此外,这是我第一次写博客,描述不准确之处敬请指出。如有疑问,可在下方留言。祝好。
随着数字技术在仪器仪表和通信系统中的广泛使用,可从参考频率源产生多个频率的数字控制方法诞生了,即直接数字频率合成DDS(Direct Digital Frequency Synthesis)[1]。DDS可根据需要产生指定频率的波形信号,如正弦波、三角波、锯齿波、方波等,其结构框图如下图所示。
如上图所示,通过串口发送频率控制字、幅值控制字、波形选择,就能在DAC处得到相应的波形信号。其具体过程如下:首先,给定的频率控制字作为累加值,每当时钟信号1有效时,相位累加器就进行一次累加;然后,对相位累加器的输出值进行截位操作,并将截位的结果赋给相位寄存器;相位寄存器的输入值作为地址值读取事先存储在查找表上的波形信息,其输出进入幅值放大器并结合给定的幅值控制字调整波形幅值,最后将信号输入到DAC进行输出,得到对应的指定频率和幅值的波形。简而言之,就是事先将不同波形存储在不同的ROM上,通过改变频率控制字来控制波形频率,实现变频的功能,再通过幅值控制器进行相应增益处理,可实现调幅功能。
DDS的数学过程比较简单,主要的几个参数为输入时钟频率fc,输出频率fo,相位累加器位宽n,频率控制字K,幅值控制字A。输出信号的频率计算公式如下[2]:
fo=fc* K/2^n
记DDS输出最小频率,即分辨率为vf,则其表达式为[3]:
vf=fc/2^n
记DDS输出最大频率为fmax,它由奈奎斯特定理决定,即fmax=fc/2。实际中考虑有其它影响因素,一般有不超过时钟频率的40%。
记输出为y(t),以Waveform表示所选波形,则最终波形表达式如下:
y(t)=AWaveform(2πfot)
下面分别介绍相位累加器(包含相位寄存器部分内容)、查找表、幅值控制器的实现方法,并给出关键代码语句。
相位累加器[4]
module PHASE_ADDER
(//input
clk,rst,fre_value,
//output
cnt
);
input clk;
input rst;
input [15:0] fre_value;//频率控制字
output [15:0] cnt;
wire [15:0] fre_value;
reg [15:0] cnt; //相位累加寄存器
always @(posedge clk or posedge rst)
if(rst)
cnt <= 0;
else
cnt <= cnt + fre_value; //计数器步长K
endmodule
查找表
module DDS_Table
(//input
clk,addr,
//output
dout_sin,dout_triangel,dout_saw,sin_Test,dout_square
);
input clk;
input [9:0] addr;
output [9:0] dout_sin;
output [9:0] dout_triangel;
output [9:0] dout_saw;
output [9:0] sin_Test;
output [9:0] dout_square;
wire [9:0] sin;
wire [9:0] triangel;
wire [9:0] saw;
wire [9:0] square;
assign dout_sin = sin;
assign dout_triangel = triangel;
assign dout_saw = saw;
assign sin_Test = sin;
assign dout_square =square;
ROM_IP_CORE04 Sin
(//input
.clka (clk ), // input clka
.addra (addr), // input [9 : 0] addra
//output
.douta (sin ) // output [9 : 0] douta
); //addr作为地址值,读取存储在ROM里的正弦波幅度信息
ROM_IP_CORE09 Triangel
(//input
.clka (clk ), // input clka
.addra (addr ), // input [9 : 0] addra
//output
.douta (triangel ) // output [9 : 0] douta
);//addr作为地址值,读取存储在ROM里的三角波幅度信息
ROM_IP_CORE06 Saw
(//input
.clka (clk ), // input clka
.addra (addr ), // input [9 : 0] addra
//output
.douta (saw ) // output [9 : 0] douta
);//addr作为地址值,读取存储在ROM里的锯齿波幅度信息
ROM_IP_CORE12 Square
(//input
.clka (clk ), // input clka
.addra (addr ), // input [9 : 0] addra
//output
.douta (square ) // output [9 : 0] douta
);//addr作为地址值,读取存储在ROM里的方波幅度信息
endmodule
程序运行时,事先需要把波形存储在ROM中,具体做法如下[5]:
1)首先生成几种波形的coe文件。
以正弦波为例,coe文件可用MATLAB生成:
x=linspace(0,2*pi,1024);%一个周期采样点取1024个点,取多少个点由ROM地址线位宽决定,N为地址线可采样2^N个点。
y1=sin(x);
y2=ceil(y2*511);
%生成sin函数coe文件
fid = fopen('sin.coe','wt');
fprintf(fid,'MEMORY_INITIALIZATION_RADIX=10;\n');
fprintf(fid,'MEMORY_INITIALIZATION_VECTOR=\n');//这两句是生成coe文件所需要的
for i = 1:1:2^10
fprintf(fid,'%16.0f',y2(i));
if i==2^10
fprintf(fid,';');
else
fprintf(fid,',');
end
if i%15==0
fprintf(fid,'\n');
end
end
fclose(fid);
2)调用ROM的IP核时,把存储了波形信息的coe文件加载到ROM中,如下图所示:
幅值放大器
module Amplifier
(//input
gain_o,sin_o,
triangel_o,saw_o,square_o,
//output
sin_f,triangel_f,saw_f,square_f
);
input [3 :0] gain_o;
input [9:0] sin_o;
input [9:0] triangel_o;
input [9:0] saw_o;
input [9:0] square_o;
output[13:0] sin_f;
output[13:0] triangel_f;
output[13:0] saw_f;
output[13:0] square_f;
wire [13:0] sin;
wire [13:0] triangel;
wire [13:0] saw;
wire [13:0] square;
assign sin_f =sin;
assign triangel_f =triangel;
assign saw_f =saw;
assign square_f =square;
Multiplier Multiplier_sin
(
.a(sin_o), // input [9 : 0] a
.b(gain_o),// input [3 : 0] b
.p(sin) // output [13: 0] p
);
Multiplier Multiplier_triangel
(
.a(triangel_o), // input [9 : 0] a
.b(gain_o), // input [3 : 0] b
.p(triangel) // output [13: 0] p
);
Multiplier Multiplier_saw
(
.a(saw_o), // input [9 : 0] a
.b(gain_o), // input [3 : 0] b
.p(saw) // output [13: 0] p
);
Multiplier Multiplier_square
(
.a(square_o), // input [9 : 0] a
.b(gain_o), // input [3 : 0] b
.p(square) // output [13: 0] p
);
endmodule
顶层文件
module dds_top
(
input rst,
input gclk,
input [15:0] fre_value,
input signed [15:0] gain_o,
output signed [13:0] sin_ok,
output signed [13:0] triangel_ok,
output signed [13:0] saw_ok,
output signed [ 9:0] sin_Test,
output signed [13:0] square_ok
);
wire [15:0] phase; //32bit内部连接线,传递相位增量
wire [ 9:0] addr; //10bit相位信息
wire [ 3:0] gain;
wire clk_4_096;
wire CLK_4_096;
assign gain=gain_o[3:0];
ClockingWizard Clock
(//input
.gclk (gclk),
//output
.clk_4_096 (clk_4_096),
.RESET (),
.LOCKED ()
);
assign CLK_4_096=clk_4_096;
PHASE_ADDER PHASE_ADDER_Sin
(//input
.clk (CLK_4_096),
.rst (rst ),
.fre_value (fre_value),
//output
.cnt (phase )
);
assign addr = phase[15:6];//addr 10bit
wire [9:0] sin_o;
wire [9:0] triangel_o;
wire [9:0] saw_o;
wire [9:0] square_o;
DDS_Table U_DDS_Table
(//input
.clk (CLK_4_096), // input wire clka
.addr (addr ), // input wire [9 : 0 ] addra
//output
.dout_sin (sin_o), // output wire [13 : 0] dout_sin
.dout_triangel(triangel_o), // output wire [13 : 0] dout_tri
.dout_saw (saw_o), // output wire [13 : 0] dout_saw
.sin_Test (sin_Test),
.dout_square (square_o) // output wire [13 : 0] dout_square
);
Amplifier Amplifier
(//input
.gain_o (gain),
.sin_o (sin_o), // output wire [13 : 0] dout_sin
.triangel_o (triangel_o ), // output wire [13 : 0] dout_tri
.saw_o (saw_o ), // output wire [13 : 0] dout_saw
.square_o (square_o), // output wire [13 : 0] dout_square
//output
.sin_f (sin_ok),
.triangel_f (triangel_ok),
.saw_f (saw_ok),
.square_f (square_ok)
);
endmodule
至此,只需再通过DAC,即可输出所需的波形信号。
在ISE中调用Modelsim进行波形仿真,由于工程中调用了IP核,所以在Modelsim中需要额外对几个.v文件进行编译,具体是哪几个可以在Modelsim中的transcript中得知,寻找这些文件的路径大概是ISE_DS\ISE\verilog\src,编译好需要的文件后,得出结果如下图所示:
[1]: 直接数字频率合成(DDS)基本原理 MT-085 指南
[2]: https://www.cnblogs.com/christsong/p/5506127.html
[3]: 基于 FPGA 的幅值可调信号发生器设计 张有志, 张 鹍
[4]:https://www.cnblogs.com/christsong/p/5506127.html
[5]:https://blog.csdn.net/qq_30866297/article/details/52330140?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522158444860819724848348300%2522%252C%2522scm%2522%253A%252220140713.130056874…%2522%257D&request_id=158444860819724848348300&biz_id=0&utm_source=distribute.pc_search_result.none-task