DDS发生器
DDS信号发生器是直接数字频率合成技术,采用直接数字频率合成(Direct Digital Synthesis,简称DDS)技术,把信号发生器的频率稳定度、准确度提高到与基准频率相同的水平,并且可以在很宽的频率范围内进行精细的频率调节。采用这种方法设计的信号源可工作于调制状态,可对输出电平进行调节,也可输出各种波形。
工作原理
相位累加 :在基准时钟的驱动下,频率控制字与累加器输出的累加相位数据相加,结果送至相位寄存器的数据输入端,相位寄存器将累加器在上一个时钟作用后产生的新相位数据反馈到累加器的输入端,使相位累加器进行线性相位累加,当累加满时产生一次溢出,完成一个周期性动作,溢出频率即为输出信号频率。
波形查找与转换 :相位寄存器的输出与相位控制字相加,结果作为正弦查找表的地址,查找表由 ROM 构成,存有一个完整周期正弦波的数字幅度信息,每个地址对应正弦波的一个相位点,把输入地址信息映射成正弦波幅度信号,输出到 D/A 转换器,经转换成模拟量形式信号,再通过低通滤波器衰减和滤除不需要的取样分量,输出频谱纯净的正弦波信号。
基本结构
DDS 主要由相位累加器、相位调制器、波形数据表以及 D/A 转换器构成。
相位累加器:相位寄存器同样由FPGA内部的寄存器资源构成,用于暂存相位累加器的输出结果。其位数与相位累加器相匹配,以便准确地反馈相位累加结果。
在相位累加器完成一次累加后,其输出结果会被送入相位寄存器。在下一个时钟周期,相位寄存器将存储的相位数据反馈到相位累加器的输入端,以实现连续的相位累加操作。
波形存储器(ROM):在FPGA中,波形存储器通常由只读存储器(ROM)实现。可利用FPGA内部的ROM资源,预先将波形的一个周期内的幅度信息以数字形式存储在ROM中。例如,对于正弦波,可以预先计算出一个周期内各个相位点的幅度值,并将其存储到ROM中。
相位寄存器的输出与相位控制字相加后,结果作为波形存储器的地址。FPGA根据该地址从ROM中读取预先存储的波形幅度值,并将其输出到数模转换器(DAC)。波形存储器的容量和精度决定了输出信号的精度和质量,容量越大,可存储的波形点数越多,输出信号的波形越平滑;存储精度越高,输出信号的幅度精度越高。
数模转换器(DAC):FPGA本身不直接包含数模转换器,通常需要外接DAC芯片。通过FPGA的I/O引脚与DAC芯片的接口相连,将数字波形数据传输给DAC进行数模转换。
FPGA按照一定的时序控制,将波形存储器输出的数字幅度信号通过I/O引脚发送到DAC芯片。DAC芯片根据接收到的数字信号,将其转换为对应的模拟信号。转换后的模拟信号通常是一个阶梯状的波形,需要经过后续的低通滤波处理。
低通滤波器(LPF):低通滤波器一般由外部的无源或有源滤波电路实现,FPGA主要负责控制和配置滤波器的相关参数。例如,通过FPGA的I/O引脚输出控制信号来调整滤波器的截止频率等参数。
DAC输出的阶梯状模拟信号包含丰富的高频分量,低通滤波器会滤除这些高频分量,使输出信号变得更加平滑,接近理想的正弦波或其他目标波形。
DFS信号发生器中,相位累加器是核心部件之一,它由N位加法器和N位寄存器组成。每当有一个时钟脉冲到来时,加法器就会把频率控制字和寄存器里当前存着的相位数据加起来,得出的结果又会送回寄存器的输入端。这样,在下一个时钟脉冲来的时候,加法器会再次把频率控制字和寄存器里的新数据相加。这个过程不断重复,相位累加器就在时钟信号的驱动下,持续地对频率控制字进行相加操作。每次相加后,累加器输出的数据就代表了合成信号的相位信息,而且相位累加器发生溢出的频率,就是DFS输出信号的频率。简单来说,相位累加器的作用就是在时钟的作用下,不断累加频率控制字,从而得到合成信号的相位数据,并且这个相位数据会被用作波形存储器的地址,去查出对应的波形采样值,实现从相位到幅度的转换,最后波形存储器输出的数据会被送到D/A转换器,转换成模拟信号输出。
特点
频率分辨率高:输出信号的频率与相位累加器的增量值有关,可以实现非常细微的频率调节。
波形种类丰富:能够生成正弦波、方波、三角波等多种波形,通过改变查找表中的数据即可快速切换。
频率稳定度和准确度高:频率稳定度和准确度可提高到与基准频率相同的水平。
快速转换时间:信号频率转换时间短。
可调制性:可工作于调制状态,对输出电平进行调节。
自己动手设计一个DDS信号发生器
思路
通过查阅相关资料,我们可以从以下几个步骤实现DDS信号发生器的设计:
一、系统设计
确定设计目标
输出信号类型:正弦波和方波。
频率范围:10Hz~5MHz。
最小频率分辨率:小于1kHz。
系统架构划分
相位累加器模块:负责生成相位信息。
波形查找表模块:存储正弦波和方波的波形数据。
数模转换器(DAC)接口模块:将数字信号转换为模拟信号。
控制模块:用于设置频率控制字、波形选择等参数。
时钟模块:提供系统时钟信号。
二、模块设计
相位累加器模块
选择合适的相位累加器位数(如32位)。
根据频率范围和分辨率要求,计算频率控制字的范围。
波形储存器模块
使用ROM IP核存储正弦波和方波的波形数据。
初始化查找表,填充正弦波和方波的数字样本。
控制模块
提供用户接口,用于设置频率控制字、选择波形类型(正弦波或方波)等参数。
将用户输入的参数传递给相位累加器和波形查找表模块。
三、仿真验证
使用ModelSim或其他仿真工具搭建仿真环境。将设计的各个模块集成到仿真环境中,设置仿真时钟信号和测试激励。
实现步骤
根据实验指导,我们可以根据以下结构设计以便理解:
所需正弦波生成
在仿真设计之前,我们需要通过MATLAB生成一个正弦波文件添加到我们的项目中,以便我们后续的操作。
clc, clear, close all
F1=1; %信号频率
Fs=10^2; %采样频率
P1=0; %信号初始相位
N=10^2; %采样点数
t=[0:1/Fs:(N-1)/Fs]; %采样时刻
ADC=2^7 - 1; %直流分量
A=2^7; %信号幅度
%生成正弦信号
s=A*sin(2*pi*F1*t + pi*P1/180) + ADC;
plot(s); %绘制图形
%创建 coe 文件
fild = fopen('sin_wave_100x8.coe','wt');
%写入 coe 文件头
%固定写法,表示写入的数据是 10 进制表示
fprintf(fild, '%s\n','memory_initialization_radix=10;');
%固定写法,下面开始写入数据
fprintf(fild, '%s\n\n','memory_initialization_vector =');
for i = 1:N
s2(i) = round(s(i)); %对小数四舍五入以取整
if s2(i) <0 %负 1 强制置零
s2(i) = 0
end
fprintf(fild, '%d',s2(i)); %数据写入
if i==N
fprintf(fild, '%s\n',';'); %最后一个数据用;
else
fprintf(fild,',\n'); % 其他数据用,
end
end
fclose(fild); % 写完了,关闭文件
F1 = 1; %信号频率
Fs = 10^2; %采样频率
P1 = 0; %信号初始相位
N = 10^2; %采样点数
t = [0:1/Fs:(N-1)/Fs]; %采样时刻
ADC = 2^7 - 1; %直流分量
A = 2^7; %信号幅度
%生成方波信号
s = A*square(2*pi*F1*t + pi*P1/180) + ADC;
plot(s); %绘制图形
%创建 coe 文件
fild = fopen('squ_wave_100x8.coe','wt');
%写入 coe 文件头
%固定写法,表示写入的数据是 10 进制表示
fprintf(fild, '%s\n','memory_initialization_radix=10;');
%固定写法,下面开始写入数据
fprintf(fild, '%s\n\n','memory_initialization_vector =');
for i = 1:N
s2(i) = round(s(i)); %对小数四舍五入以取整
if s2(i) <0 %负 1 强制置零
s2(i) = 0
end
fprintf(fild, '%d',s2(i)); %数据写入
if i==N
fprintf(fild, '%s\n',';'); %最后一个数据用分号
else
fprintf(fild,',\n'); % 其他数据用 ,
end
end
fclose(fild); % 写完,关闭文件
1、相位累加器模块
// 模块声明,名称为squwave
// CPi为时钟输入信号,RSTn为复位输入信号,Address为17位地址输入,Qsquare为12位输出寄存器
module squwave(CPi, RSTn, Address, Qsquare);
input CPi; // 时钟信号输入
input RSTn; // 复位信号输入(低电平有效)
input [16:0] Address; // 17位地址输入
output reg [11:0] Qsquare; // 12位输出寄存器
// 时序逻辑块,检测CPi的上升沿触发
always @(posedge CPi) begin
// 当复位信号RSTn为低电平时,Qsquare输出全0
if (!RSTn) begin
Qsquare = 12'h000; // 复位时输出12位全0
end
// 正常工作时,根据Address的值设置Qsquare的输出
else begin
// 如果Address小于等于17'h0FFFF(即十进制的131071),Qsquare输出全1
if (Address <= 17'h0FFFF) begin
Qsquare = 12'hFFF; // 输出12位全1
end
// 否则,Qsquare输出全0
else begin
Qsquare = 12'h000; // 输出12位全0
end
end
end
endmodule
该代码定义了一个名为squwave的数字电路模块,该模块的主要功能是根据输入地址Address的值来生成一个12位的输出信号Qsquare。模块接收一个时钟信号CPi和一个低电平有效的复位信号RSTn。当复位信号RSTn被激活(即处于低电平状态)时,输出Qsquare会被立即清零,以确保模块从一个已知的初始状态开始工作。
在正常工作状态下,模块会在每个时钟周期的上升沿检查Address输入的值。如果Address的值小于或等于0xFFFF(即十进制的65535),则输出Qsquare会被设置为全1(0xFFF),这通常表示一个高电平状态或激活状态。相反,如果Address的值大于0xFFFF,则输出Qsquare会被设置为全0(0x000),表示一个低电平状态或非激活状态。
2、ROM模块
// 定义一个名为rom_sine的模块,用于生成正弦波形的只读存储器(ROM)
module rom_sine(
input [10:0] address, // 11位地址输入,用于访问ROM中的波形数据
input clock, // 时钟信号输入,用于同步数据读取
output reg [11:0] q // 12位输出,用于输出ROM中存储的正弦波形数据
);
// 声明一个12位宽、2048个元素的存储器数组,用于存储正弦波形数据
reg [11:0] mem [0:2047];
// 初始化块,用于从MIF文件中读取正弦波形数据到存储器数组
initial begin
$readmemb("Sine1024.mif", mem); // 从Sine1024.mif文件中读取数据到mem数组
end
// 时序逻辑块,用于在时钟上升沿更新输出q的值
always @(posedge clock) begin
q <= mem[address]; // 根据输入地址,从存储器数组中读取对应的正弦波形数据,并赋值给输出q
end
endmodule
// 定义一个名为rom_square的模块,用于生成方波形的只读存储器(ROM)
module rom_square(
input [10:0] address, // 11位地址输入,用于访问ROM中的波形数据
input clock, // 时钟信号输入,用于同步数据读取
output reg [11:0] q // 12位输出,用于输出ROM中存储的方波形数据
);
// 声明一个12位宽、2048个元素的存储器数组,用于存储方波形数据
reg [11:0] mem [0:2047];
// 初始化块,用于从MIF文件中读取方波形数据到存储器数组
initial begin
$readmemb("Square1024.mif", mem); // 从Square1024.mif文件中读取数据到mem数组
end
// 时序逻辑块,用于在时钟上升沿更新输出q的值
always @(posedge clock) begin
q <= mem[address]; // 根据输入地址,从存储器数组中读取对应的方波形数据,并赋值给输出q
end
endmodule
这两个模块分别实现了正弦波和方波的ROM生成器。它们都使用了一个12位宽、2048个元素的存储器数组来存储波形数据,并在时钟信号的上升沿根据输入地址读取对应的波形数据并输出。初始化块使用readmemb系统任务从MIF文件中读取波形数据到存储器数组中。这种设计可以用于数字信号处理应用中,生成所需的波形信号。
3、顶层模块
module DDS_top (
input CLOCK_50, // 输入50MHz时钟信号
input RSTn, // 输入复位信号,低电平有效
input [1:0] WaveSel, // 输入波形选择信号,2位宽,用于选择输出的波形类型
input [12:0] K, // 输入频率控制字,13位宽,用于控制输出波形的频率
output reg [11:0] WaveValue, // 输出波形值,12位宽,输出实际的波形数据
wire [9:0] ROMaddr, // 定义一个10位宽的ROM地址信号
wire [16:0] Address, // 定义一个17位宽的地址信号
wire [11:0] Qsine, // 定义一个12位宽的正弦波形数据信号
wire [11:0] Qsquare,// 定义一个12位宽的方波形数据信号
output [0:0] LEDG, // 输出LED指示信号,1位宽,用于指示PLL锁定状态
output CLOCK_100 // 输出100MHz时钟信号
);
// 定义内部时钟信号CPi,等于输出的100MHz时钟
wire CPi = CLOCK_100;
// 实例化PLL模块,将50MHz时钟倍频至100MHz,并输出锁定指示LEDG
PLL100M_CP PLL100M_CP_inst (
.inclk0(CLOCK_50),
.c0(CLOCK_100),
.locked(LEDG[0])
);
// 实例化地址计数器模块,根据频率控制字K和内部时钟CPi生成ROM地址ROMaddr和地址Address
addr_cnt U0_instance (
.CPi(CPi),
.K(K),
.ROMaddr(ROMaddr),
.Address(Address)
);
// 实例化正弦波ROM模块,根据ROM地址ROMaddr和内部时钟CPi输出正弦波形数据Qsine
SineROM ROM_inst (
.address(ROMaddr),
.clock(CPi),
.q(Qsine)
);
// 实例化方波模块,根据地址Address和内部时钟CPi输出方波形数据Qsquare
squwave U1 (
.CPi(CPi),
.RSTn(RSTn),
.Address(Address),
.Qsquare(Qsquare)
);
// 根据波形选择信号WaveSel选择输出的波形类型
always @(posedge CPi) begin
case (WaveSel)
2'b01: WaveValue = Qsine; // 如果选择正弦波,则输出Qsine
2'b10: WaveValue = Qsquare; // 如果选择方波,则输出Qsquare
default: WaveValue = Qsine; // 默认输出正弦波
endcase
end
endmodule
部分Ip核配置
配置输出位宽、存储容量和存储器类型等: