前言:
DDS:直接數字頻率合成,正弦波0-2pi周期內,相位到幅度是一一對應的(這里我們使用放大后的整數幅度)。
主要思路:
個人理解,FPGA不擅長直接做數字信號計算,那樣太占用片上邏輯資源,所以需要事先建立 正弦波相位-幅度 表,然后在時鍾下,通過相位累加並用相位作為地址索引來查詢正弦波信號表。
正弦波相位-幅度 表:
存儲的是量化的正弦波在一個周期的幅度信息(幅度的地址即相位)。
幅度的地址數目決定了相位量化的誤差。
而存儲每一個幅度的比特數決定了幅度的量化誤差。
可以通過matlab以及Xilinx的IP核向導創建。
Verilog編寫的DDS模塊主要由三部分組成,
- 相位累加器,用於決定輸出信號頻率的范圍和精度;
- 正弦函數模塊,用於存儲經量化和離散后的正弦函數的幅值;
- 查表模塊,對相位累加器的輸出地址查表。
兩種方法可以改變輸出信號的頻率:
- 改變查表尋址的時鍾頻率,從而改變輸出波形的頻率。
- 改變尋址的步長來改變輸出信號的頻率。
步長即為相位增量。
由累加器對相位增量進行累加,
累加器的值作為查表地址。
相位累加器是 DDS 的核心所在,前面在低於時鍾頻率的任意頻率生成(相位累加器)中我們已經進行了敘述。
正弦函數模塊包含一個周期正弦波的數字幅度信息,每個地址對應正弦波中0-2pi范圍的一個相位點。查表模塊把輸入的地址相位信息映射成正弦波幅度的數字量信號。相位寄存器每經過 2^N/K 個時鍾后回到初始狀態,相應地正弦查詢表經過一個循環回到初始位置,輸出一個正弦波。
輸出正弦波周期為fo=fc* K/2^N ,最小分辨率為f=fc/2^N。(通過fc和K控制正弦波頻率精度) 其中,N 為累加器位寬,K 為步長,fc 為時鍾頻率。計數模(最大值):M=2^N。
一般正弦波表幅度地址位寬與累加的查表地址位寬不同,按前者位寬取后者對應高位的位寬即可。(具體見實例)
先用matlab生成1024點的正弦波數據:
clc;clear;
N = 10; %儲存單元地址線
depth=2^N; %存儲單元;
widths=N; %數據寬度為8位;
index = linspace(0,pi*2,depth);
sin_value = sin(index);
sin_value = sin_value * (depth/2 -1); %擴大正弦幅度值
sin_value = fix((sin_value)+0.5);
plot(sin_value);
number = [0:depth];
fid=fopen('sin_table.coe','w+');
fprintf(fid,'memory_initialization_radix=10;\n');
fprintf(fid,'memory_initialization_vector=\n');
for i = 1 : depth - 1
fprintf(fid, '%d,\n', sin_value(i));
end
fprintf(fid, '%d;', sin_value(depth));
fclose(fid);
Verilog程序
1、adder.v文件,相位累加模塊
`timescale 1ns/1ps
/***************************************
晶振頻率 fc = 100MHz
輸出頻率 fo = 1kHz(根據需要可以設為任意值)
控制參數 K = (fo*2^N)/fc = 42950
參數 N = 2^32,(32為計數器的位寬)
****************************************/
module PHASE_ADDER(
input clk,
input rst,
output reg [31:0] cnt,
output reg clk_out
);
always @(posedge clk or posedge rst)
if(rst)
cnt <= 0;
else
cnt <= cnt + 32'd42950; //計數器步長K
always @(posedge clk or posedge rst)
if(rst)
clk_out <= 1'b0;
else if(cnt < 32'h7FFF_FFFF)
clk_out <= 1'b0;
else
clk_out <= 1'b1;
endmodule
2、dds_top.v頂層設計
`timescale 10ns /1ns
module dds_top(
input rst,
input clk,
output signed [15:0] sine_o
);
wire [31:0] phase; //32bit內部連接線,傳遞相位增量
wire clk_out;
wire [9:0] addr; //10bit相位信息
PHASE_ADDER U_PHASE_ADDER(
.clk (clk ),
.rst (rst ),
.cnt (phase ),
.clk_out(clk_out)
);
assign addr = phase[31:22];//addr 10bit
DDS_Table U_DDS_Table(
.clka(clk), // input wire clka
.addra(addr), // input wire [9 : 0] addra
.douta(sine_o) // output wire [15 : 0] douta
);
endmodule
3、仿真測試文件
`timescale 1ns/1ps
module TB;
reg clk;
reg rst;
wire clk_out;
dds_top U_dds_top(
.clk (clk ),
.rst (rst )
);
initial begin
clk = 0;
rst = 0;
#4 rst = 1;
#3 rst = 0;
end
always #5 clk = ~clk;
endmodule