DDS的fpga实现
DDS的原理可以查找其他资料,本次主要采用matlab生成载波数据,通过存储在ROM中对数据进行读取。
1、先附上matlab代码生成的载波数据
clc;
clear all;
close all;
Nbit=16;%量化位数
Ndata= 4096;
N_data=0:Ndata-1;
sintheta=sin(pi*2/4/4096*(N_data));
plot(N_data,sintheta);
title('y=sin(\theta)');
xlabel('\theta');
ylabel('sin');
Nor_sin= sintheta/max(abs(sintheta)); % 对数据进行归一化
sinlianghua=round(Nor_sin*2^(Nbit-1)-1);%量化后注意首尾是否溢出
sinlianghua(1)=0;%首位负数-1改成0.
Fix_Nor2=dec2bin(sinlianghua,Nbit);
%% 将处理后数据存储在.coe文件中
fid = fopen('sintheta.coe','w');
fprintf(fid,'MEMORY_INITIALIZATION_RADIX = 2;\r\n');
fprintf(fid,'MEMORY_INITIALIZATION_VECTOR =\r\n');
for i=1:Ndata
fprintf(fid, '%s\r\n', Fix_Nor2(i,:));
end
fclose(fid);
可以看出,为了节省寄存器资源,根据正弦函数的对称性,只存储sin函数的四分之一周期。
上图是取得的需要存储的数据波形。
接下来就是关键的,需要在FPGA中进行取数以及对地址和数据的转换。先附上FPGA程序如下。首先定义控制字以及相位位宽,同时定义好输出DA的位宽(DA的位宽根据实际选用DA器件的位数来定)。共存储4096个数据,因此相位累加器phase_reg(高12位为rom地址) 在第一象限的时候,地址到为4095,此时phase_reg高2bit为00;同时如果相位再次累加,则DDS进入第二象限,phase_reg高2bit为01,此时,取数据时需要在rom中反向取数,因此,取数地址为4095-phase_reg的高12位(注意此时为4095减去地址),完成反向取数。同理在第三象限时,phase_reg高2bit为10,地址从0到4095取数,但是数据在第三象限,因此输出数据cos_out取反,同理第四象限数据取反,rom中反向取数。
module nco_gen
#( parameter rom_capacity_width=12,//存储coe个数位宽4096
parameter coe_capacity=4095,//coe_capacity=2^rom_capacity_width-1
parameter fw_freq_width=32,//频率字位宽//相位累加位宽
parameter outwave_width=16//输出波形位宽
)
(
input sysclk,
input rstn,
input en,
input [fw_freq_width-1:0] fw_freq,//频率字位宽
output signed [outwave_width-1:0] cos_wave//输出波形位宽
);
reg [rom_capacity_width-1:0] phase_addr;//4096个数据
reg [fw_freq_width-1:0] phase_reg;//相位累加位宽,最高2位用于象限判断
reg signed[outwave_width-1:0] cos_out;
wire signed[outwave_width-1:0] nco_out;
always@(posedge sysclk or negedge rstn)
begin
if(!rstn)
phase_reg <= 0;
else
begin
if(en)
phase_reg <= phase_reg + fw_freq; //溢出循环
else
phase_reg <= 0; //溢出循环
end
end
always@(posedge sysclk or negedge rstn)
begin
if(!rstn)
phase_addr <= 0;
else
begin
if(phase_reg[fw_freq_width-2])//正反向来回循环取地址
phase_addr <= coe_capacity-phase_reg[fw_freq_width-3:fw_freq_width-14]; //溢出循环
else
phase_addr <= phase_reg[fw_freq_width-3:fw_freq_width-14]; //溢出循环
end
end
always@(posedge sysclk or negedge rstn)
begin
if(!rstn)
cos_out<=0;
else begin
case(phase_reg[fw_freq_width-1:fw_freq_width-2])
2'b00: cos_out<=nco_out;
2'b01: cos_out<=nco_out;
2'b10: cos_out<=0-nco_out;
2'b11: cos_out<=0-nco_out;
endcase
end
end
rom_ncogen u_NCO_COS (
.clka(sysclk), // input clka
.addra(phase_addr), // input [11 : 0] addra
.douta(nco_out) // output [15 : 0] douta
);
assign cos_wave=cos_out;
endmodule