FPGA学习:DDS信号发生器
第一次写博客,多多见谅。
DDS是直接数字式频率吧合成器(Direct Digital Synthesizer)的英文缩写,是一项关键的数字化技术。
DDS的基本结构主要由相位累加器、相位调制器、波形数据表ROM、D/A转换器等四大结构组成,其中较多设计还会在数模转换器之后增加一个低通滤波器。
系统时钟CLK为整个系统的工作时钟,频率为f_clk;频率输入字F_WORD,一般为整数,数字的大小控制着输出信号频率的大小,输出数字越大则输出的信号频率越大,反之,则输出的频率越小。相位输出字为P_WORD,为整数,数值大小控制输出信号的相位偏移,主要用于相位的信号调制,输出的信号为CLK_OUT,频率为f_out。
在图中所展示的四大结构中,相位累加器是整个DDS的核心,在这里完成相位累加,生成相位码。相位累加器的频率输入字为K,表示相位增量,其位宽为N,满足等式K=2^N*f_out/f_clk。其在输入相位累加器之前,在系统时钟同步下做数据寄存,数据改变时不会干扰相位累加器的正常工作。
相位调制器接收相位累加器输出的相位码, 在这里加上一个相位偏移值P,主要用于信号的相位调制,如应用于通信方面的相移键控等,不使用此部分时可以去掉,或者将其设为一个常数输入,同样相位字输入也要做寄存。
波形数据表ROM中存有一个完整周期的正弦波信号。假设波形数据ROM的地址位宽为12位,存储数据位宽为8位,即ROM有2^12 = 4096个存储空间,每个存储空间可存储1字节数据。将一个周期的正弦波信号,沿横轴等间隔采样212 = 4096次,每次采集的信号幅度用1字节数据表示,最大值为255,最小值为0。将4096次采样结果按顺序写入ROM的4096个存储单元,一个完整周期正弦波的数字幅度信号写入了波形数据表ROM中。波形数据表ROM以相位调制器传入的相位码为ROM读地址,将地址对应存储单元中的电压幅值数字量输出。
输出信号CLK_OUT的信号频率fOUT = K * fCLK / 2^N。当K = 1时,可得DDS最小分辨率为:fOUT = fCLK / 2N,此时输出信号频率最低。根据采样定理,K的最大值应小于2N/2。
所以,问题来了,相位累加器得到的相位码是如何进行寻址的呢?
对于N位的相位累加器,它对应的相位累加值为2N,如果正弦ROM中存储单元的个数也是2N的话,这个问题就很好解决,但是这对ROM的对存储容量的要求较高。在实际操作中,我们使用相位累加值的高几位对ROM进行寻址,也就是说并不是每个系统时钟都对ROM进行数据读取,而是多个时钟读取一次,因为这样能保证相位累加器溢出时,从正弦ROM表中取出正好一个正弦周期的样点。
因此,相位累加器每计数2^N次,对应一个正弦周期。而相位累加器1秒钟计数f_clk次,在k=1时,DDS输出的时钟频率就是频率分辨率。频率控制字K增加时,相位累加器溢出的频率增加,对应DDS输出信号clk_out频率变为K倍的DDS频率分辨率。
设:ROM存储单元个数为4096,每个存储数据用8位二进制表示。即,ROM地址线宽度为12,数据线宽度为8;相位累加器位宽N = 32。
根据上述条件可以知道,相位调制器位宽M = 12,那么根据DDS原理。那么在相位调制器中与相位控制字进行累加时,应用相位累加器的高12位累加。而相位累加器的低20位只与频率控制字累加。
我们以频率控制字K = 1为例,相位累加器的低20位一直会加1,直到低20位溢出向高12位进位,此时ROM为0,也就是说,ROM的0地址中的数据被读了220次,继续下去,ROM中的4096个点,每个点都将会被读220次,最终输出的波形频率应该是参考时钟频率的1 / 220,周期被扩大了220 倍。同样当频率控制字为100时,相位累加器的低20位一直会加100,那么,相位累加器的低20位溢出的时间比上面会快100倍,则ROM中的每个点相比于上面会少读100次,所以最终输出频率是上述的10倍。
为什么地址是由相位控制字加频率控制字高12位得到的?
1、本次实验使用的rom是宽度为14,深度为2^12 = 4096的数据,所以相位控制字根据rom的深度选择了12位宽
2、为什么ROM宽度是14,深度不取2^14?FPGA资源不够,没有这么多的寄存器存取这么多的数据
3、地址 = 相位 + 频率更迭,而相位宽度为12位,频率的宽度比相位多,所以频率控制字取高几位是由相位控制字的宽度决定的
4、取频率控制字高12位是如何完成频率变换的?
举例:
2^1 = 2’b10
2^2 = 3’b100
2^3 = 4’b1000
…
2^19 = 20’b1000_0000_0000_0000_0000
2^20 = 21’b1_0000_0000_0000_0000_0000
2^21 = 22’b10_0000_0000_0000_0000_0000
f = 1/T,N = 32
频率控制字为:2^20
fword_acc[31:0] + 2^20 相当于 (fword_acc[31:20] + 1)此时就是按照地址+1的速度寻址,假如Fclk = 50MHz(系统时钟),Tclk = 20ns,输出波形的周期就为:To = 20ns * 4096
频率控制字为:2^19
fword_acc[31:0] + (2^19 + 2^19) 相当于 (fword_acc[31:20] + 1),也就是要加两次频率控制字,才能实现一次地址+1,Tclk = 20ns,输出完整波形就要输出2次*4096个数据,输出的波形周期为:To = 20ns * (2 * 4096)
频率控制字为:2^21
fword_acc[31:0] + (2^21) 相当于 (fword_add[31:20] + 2’b10),加一次频率控制字,实现一次地址+2,Tclk = 20ns,因为是跳过一位地址取的数据,所以数据量减半,输出完整波形只需要输出4096/2个数据,输出的波形周期为:To = 20ns * (4096/2)
代码相关
DDS模块,目前只用了正弦波进行测试,当然,余弦波也是类似的哈。
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/08/07 20:31:52
// Design Name:
// Module Name: DDS_SIN_COS
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module DDS_SIN_COS(
sys_clk,
sys_rst,
clk_out,
signal_out
);
input sys_clk,sys_rst;
output clk_out;
output [11:0] signal_out;
wire [31:0]F_word;//频率控制字,可以对频率进行控制
wire [11:0]P_word;//相位控制字
assign F_word =32'd85899;
assign P_word = 12'b0;
wire [11:0] wave_data;
reg [11:0] wave_data_r;
reg [31:0] Fcnt;
wire [11:0] rom_addr;
assign signal_out = wave_data_r;
always@(posedge sys_clk or negedge sys_rst)
begin
if(!sys_rst)
Fcnt <= 32'd0;
else
Fcnt <= Fcnt + F_word;
end
assign rom_addr = Fcnt[31:20]+P_word;
assign clk_out = sys_clk;
rom_sin uut
(
.clka(clk_out), // input wire clka
//.wea(wea), // input wire [0 : 0] wea
.addra(rom_addr), // input wire [10 : 0] addra
//.dina(dina), // input wire [15 : 0] dina
.douta(wave_data) // output wire [15 : 0] douta
);
always @(posedge clk_out)
begin
wave_data_r <= wave_data;
end
endmodule
testbench:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/08/07 20:37:52
// Design Name:
// Module Name: tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module tb(
);
reg clk;
reg reset_n;
wire [11:0] sine_out;
initial
begin
clk=0;
reset_n=1;
#20 reset_n=0;
#20 reset_n=1;
end
always
begin
#10 clk=~clk;//需要获得50M时钟,周期20ps,所以延时为10
end
DDS_SIN_COS dds_uut(
.sys_clk(clk),
.sys_rst(reset_n),
//.clk_out(),//这个dac时钟是为了配合外部dac模块进行的输出,测试时不需要
.signal_out(sine_out)
);
endmodule
涉及ROM等ip核调用就不过多解释了,可以在网上查找波形ceo文件生成的方式。
引用来源:FPGA之DDS信号发生器(个人学习参考) - 快乐气氛组阿宇 - 博客园 (cnblogs.com)
第1篇:基于ROM的直接数字式频率合成器(DDS)的FPGA实现-CSDN博客
直接数字式频率合成法原理(DDS)_直接数字频率合成dds-CSDN博客
用FPGA实现dds的方案详解(保姆级入门教学)(VIVADO18.3、quartus13.1)_fpga dds-CSDN博客
用FPGA实现dds的方案详解(保姆级入门教学)(VIVADO18.3、quartus13.1)_fpga dds-CSDN博客
多谢这些博主的知识。