DDS 全称 Direct Digital Synthesizer(直接数字合成),是从相位出发,直接采用数字技术产生波形的一种频率合成技术。基本模型如上图所示,主要由时钟频率源fclk、相位累加器、波形存储器、及后级数模转换器(DAC)、低通滤波器(LPF)组成。频率控制字M和相位控制字分别控制DDS输出正(余)弦波的频率和相位。每来一个时钟脉冲,相位寄存器以步长M递增。相位寄存器的输出与相位控制字相加,其结果作为正(余)弦查找表的地址。正(余)弦查找表的数据存放在ROM中,内部存有一个周期的正弦波信号的数字幅度信息,每个查找表的地址对应于正弦波中 0°~360°范围内的一个相位点。查找表把输入的址信息映射成正(余)弦波的数字幅度信号,同时输出到数模转换器DAC的输入端,DAC 输出的模拟信号经过低通滤波器 (LPF),可得到一个频谱纯净的正(余)弦波。
更详细的DDS原理、优缺点可以参考论文“基于FPGA的多波形信号源研究和仿真_王宁.”。
DDS输出频率的计算公式:Fo=Fs*K/M;Fo为输出频率,Fs为参考时钟(也可以认为是DAC时钟),M为波形rom的深度,K为频率控制字。当K为1时的Fo即为频率分辨率。
Quartus II 12.1
ModelSim-Altera 12.1
MATLAB(生成波形数据)
设计一个系统:串口接收频率、相位控制字,控制的DAC输出波形(正弦波、三角波、锯齿波、方波、直流)。串口通信程序使用本人博客“FPGA基础设计(三):UART串口通信”中的内容。
设计中取DAC输出时钟为50MHz,波形存储深度为512点(取信号的一个周期),用matlab生成mif格式的文件分别存储正弦波、方波、三角波、锯齿波的数据。顶层模块的程序如下所示:
module UART_DDS
(
input sys_clk,
input rst_n, //低电平有效复位按键
input [7:0] c0,c1,c2,c3,c4,c5,
//DAC,8bit位宽
output daclk,
output [7:0] dadata,
//串口接收
input uart_rx
);
//module1
//************ DAC时钟管理 *************
wire clk_da;
assign clk_da = sys_clk; //DAC采样速率等于系统时钟,50MHz
assign daclk = clk_da;
//module2
//************ 串口时钟管理 *************
wire clk_uart; //波特率的16倍时钟,串口时钟
clkdiv clkdiv_inst //分频产生串口时钟
(
.clk50(sys_clk), //50MHz系统时钟输入
.clkout(clk_uart) //分频产生串口时钟
);
//module3
//************ 串口数据接收 *************
wire rdsig;
wire [7:0] rxdata;
reg [7:0] ctrli;
reg [7:0] ctrl_data [5:0]; //控制命令字, 0-波形,1-2频率,3直流,4-5初相
wire [8:0]phase_i;
uartrx uartrx_inst (
.clk (clk_uart), //16倍波特率的时钟
.rx (uart_rx), //串口接收
.dataout (rxdata), //uart 接收到的数据,一个字节
.rdsig (rdsig), //uart 接收到数据有效
.frameerror ()
);
//module4
//************ 存储命令控制字 *************
//帧格式,0xFF,D0波形,D1频率高8bit,D2频率低8bit,D3直流,D4初相高8bit,D5初相低8bit
//使用串口时注释掉下面一段,将上面一段的注释取消;进行DDS的仿真时,注释上一段
/* 使用串口时
always @ (negedge rdsig)
if (rxdata == 8'hFF) ctrli <= 0; //0xFF为帧头
else begin
ctrl_data[ctrli] <= rxdata;
ctrli <= ctrli + 1'b1;
end
*/
// 使用仿真时
always @ (posedge clk_da)
begin
ctrl_data[0] <= c0;
ctrl_data[1] <= c1;
ctrl_data[2] <= c2;
ctrl_data[3] <= c3;
ctrl_data[4] <= c4;
ctrl_data[5] <= c5;
end
//module5
//************ DAC输出波形管理 *************
reg [8:0] rom_addr; //范围 0-511
reg [7:0] dadata_reg;
reg [15:0] freq,freq_h,freq_l;
wire [7:0] sin_data, triangle_data, sawtooth_data, square_data;
reg [7:0] dc_data;
assign dadata = dadata_reg;
always @ (negedge clk_da or negedge rst_n)
if (!rst_n) dadata_reg <= 'd0;
else
case (ctrl_data[0]) //根据波形控制字选择不同的波形数据输出
8'h00: dadata_reg <= sin_data;
8'h01: dadata_reg <= sin_data; //正弦波
8'h02: dadata_reg <= triangle_data; //三角波
8'h03: dadata_reg <= sawtooth_data; //锯齿波
8'h04: dadata_reg <= dc_data; //直流
8'h05: dadata_reg <= square_data; //方波
default: dadata_reg <= sin_data;
endcase
//module6
//************ 拼接获取初相控制字 *************
//相位分辨率 f=360°*K/N;
// K为相位控制字;N为相位累加器长度,此处为512(9bit位宽)
reg [15:0] phase,phase_h,phase_l;
always @ (negedge clk_da or negedge rst_n)
if (!rst_n) begin
phase_h <= 'd0; //初相高8bit
phase_l <= 'd0; //初相低8bit
phase <= 'd0; //组合为16bit的初相控制字
end
else begin
phase_h <= ctrl_data[4] << 8; //初相高8bit
phase_l <= ctrl_data[5]; //初相低8bit
phase <= phase_h | phase_l; //组合为16bit的初相控制字
end
//module7
//************ 拼接获取频率控制字 *************
//频率分辨率 f=CLK*K/N; CLK为DAC时钟,此处为50MHz;
// K为频率控制字;N为相位累加器长度,此处为512(9bit位宽)
always @ (negedge clk_da or negedge rst_n)
if (!rst_n) begin
freq_h <= 'd0; //频率高8bit
freq_l <= 'd0; //频率低8bit
freq <= 'd0; //组合为16bit的频率控制字
end
else begin
freq_h <= ctrl_data[1] << 8; //频率高8bit
freq_l <= ctrl_data[2]; //频率低8bit
freq <= freq_h | freq_l; //组合为16bit的频率控制字
end
//module8
//************ 相位累加器 *************
wire [8:0] rom_addr_phase;
always @ (negedge clk_da or negedge rst_n)
if (!rst_n) rom_addr <= 'd0;
else if (rom_addr + freq >= 'd512) rom_addr <= rom_addr + freq - 'd512;
else rom_addr <= rom_addr + freq;
assign rom_addr_phase = rom_addr + phase; //相位累加器输出加上初相控制字
//module8
//************ 输出直流电压 *************
always @ (negedge clk_da or negedge rst_n)
if (!rst_n)
dc_data <= 'd0;
else
dc_data <= ctrl_data[3];
//module9
//************ 1-Port ROM存储波形数据 *************
rom1 rom1_inst ( //正弦波
.address ( rom_addr_phase ),
.clock ( clk_da ),
.q ( sin_data )
);
rom2 rom2_inst ( //锯齿波
.address ( rom_addr_phase ),
.clock ( clk_da ),
.q ( sawtooth_data )
);
rom3 rom3_inst ( //三角波
.address ( rom_addr_phase ),
.clock ( clk_da ),
.q ( triangle_data )
);
rom4 rom4_inst ( //方波
.address ( rom_addr_phase ),
.clock ( clk_da ),
.q ( square_data )
);
endmodule
完整的工程奉上(含testbench,已在开发板上验证):https://download.csdn.net/download/fpgadesigner/10438885
仿真时博主遇到了问题,ModelSim中没有输出的DAC波形数据。考虑应该是存储波形数据的ROM IP核出了问题,网上搜索后发现想要仿真最方便的应该是将存储的mif文件转换为hex文件。在Quartus II中点击“File-Open”打开“.mif”格式的文件,再点击“File-Save as”选择“.hex”格式保存。重新将hex文件导入ROM中综合,再次仿真问题解决。
其实并不是仿真ROM IP核时不支持mif文件,不过用hex文件操作最简便。仿真结果如下:
传统DDS原理的好处是分辨率可以设置的很高,只要增加相位累加器的位数并截取高位赋值给DAC输出数据即可。不过在设计中最直观的缺点恐怕就是频率分辨率很难做到整数。
比如在上面的设计中,频率分辨率fclk=50MHz/512,显然不是整数,一个解决方法是将ROM的存储深度改为500个点,这样频率分辨率便是100kHz。根据公式,很明显,增加ROM的存储深度或者减小时钟都可以增加频率分辨率。
针对传统DDS原理的缺陷,现在很多人提出了改进,比如“基于FPGA的信号发生器研究蔡历鑫”这篇论文中提出了可编程的相位累加器模值和利用CORDIC算法;“基于FPGA的直接数字频率合成器的设计彭昭”这篇论文提出了循环相位累加器的方法,频率调整不仅非常小且保持为整数,不过该方法每个信号周期取点数少,只适合用于正弦波信号的生成。
对FPGA设计的DDS信号发生器感兴趣的同学可以结合博主的工程,网上下载文中提到的这几篇论文,相信不难理解。