FPGA之DDS信號發生器(個人學習參考)


DDS是直接數字式頻率合成器(Direct Digital Synthesizer)的英文縮寫,是一項關鍵的數字化技術
 
DDS的基本結構主要由相位累加器、相位調制器、波形數據表ROM、D/A轉換器等四大結構組成,其中較多設計還會在數模轉換器之后增加一個低通濾波器。DDS結構示意圖,具體見圖 33-1
 
圖 33-1中是不含低通濾波器的DDS結構示意圖,圖中主要包括相位累加器、相位調制器、波形存儲器、數模(D/A)轉換器等四大結構
系統時鍾CLK為整個系統的工作時鍾,頻率為fCLK;頻率字輸入F_WORD,一般為整數,數值大小控制輸出信號的頻率大小,數值越大輸出信號頻率越高,反之,輸出信號頻率越低,后文中用K表示;相位字輸入P_WORD,為整數,數值大小控制輸出信號的相位偏移,主要用於相位的信號調制,后文用P表示;設輸出信號為CLK_OUT,頻率為fOUT
圖中所展示的四大結構中,相位累加器是整個DDS的核心,在這里完成相位累加,生成相位碼。相位累加器的輸入為頻率字輸入K,表示相位增量,設其位寬為N,滿足等式K = 2N * fOUT / fCLK 。其在輸入相位累加器之前,在系統時鍾同步下做數據寄存,數據改變時不會干擾相位累加器的正常工作。
相位調制器接收相位累加器輸出的相位碼, 在這里加上一個相位偏移值P,主要用於信號的相位調制,如應用於通信方面的相移鍵控等,不使用此部分時可以去掉,或者將其設為一個常數輸入,同樣相位字輸入也要做寄存。
波形數據表ROM中存有一個完整周期的正弦波信號。假設波形數據ROM的地址位寬為12位,存儲數據位寬為8位,即ROM有 212 = 4096 個存儲空間,每個存儲空間可存儲1字節數據。將一個周期的正弦波信號,沿橫軸等間隔采樣 212 = 4096 次,每次采集的信號幅度用1字節數據表示,最大值為255,最小值為0。將4096次采樣結果按順序寫入ROM的4096個存儲單元,一個完整周期正弦波的數字幅度信號寫入了波形數據表ROM中。波形數據表ROM以相位調制器傳入的相位碼為ROM讀地址,將地址對應存儲單元中的電壓幅值數字量輸出。

輸出信號CLK_OUT的信號頻率fOUT = K * fCLK / 2N。當K = 1時,可得DDS最小分辨率為:fOUT = fCLK / 2N,此時輸出信號頻率最低。根據采樣定理,K的最大值應小於2N/ 2
講到這里,讀者會心存疑慮,相位累加器得到的相位碼是如何實現ROM尋址的呢?
對於N位的相位累加器,它對應的相位累加值為2N,如果正弦ROM中存儲單元的個數也是2N的話,這個問題就很好解決,但是這對ROM的對存儲容量的要求較高。在實際操作中,我們使用相位累加值的高幾位對ROM進行尋址,也就是說並不是每個系統時鍾都對ROM進行數據讀取,而是多個時鍾讀取一次,因為這樣能保證相位累加器溢出時,從正弦ROM表中取出正好一個正弦周期的樣點。
因此,相位累加器每計數2N次,對應一個正弦周期。而相位累加器1秒鍾計數fCLK次,在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倍。
  • 如圖,一個量化的32點的正弦波,也就是說一個ROM里存了32個這樣的數據,每次讀出一個數據要1ms,分別讀出1,2,3...30,31,32,共32個點,讀取完整的正弦波需要1ms * 32 = 32ms的時間

該正弦波參數為

  • 周期T = 1ms * 32 = 32ms,
  • 頻率為 f = 1/T = 1/(1ms * (32/1))

  • 在讀出一個數據時間不變(1ms)的情況下,想要讓讀出的正弦波頻率增加一倍,那就要間隔讀取,分別讀出2,4,6,8,10...28,30,32,此時只需要讀16個點

那么讀出完整正弦波的參數為

  • 周期T = 1ms * 16 = 16ms
  • 頻率f = 1/T = 1/(1ms * 16) = 1/(1ms * (32/2))

  • 想要讀出的正弦波頻率減少一倍,那就要插值讀取,分別讀出0.5,1,1.5,2,2.5,3...30.5,31,31.5,32,此時要讀64個點

讀出正弦波的參數為

  • 周期T = 1ms * 64 = 64ms
  • 頻率f = 1/T = 1/(1ms * 64) = 1/(1ms * (32/0.5))

這里,1ms即為Tclk,fclk = 1/Tclk = 1/1ms;32 = 2^5即為N=5,而32除以的數(1,2,0.5)即為頻率控制字fword,那么fo = (fclk * fword)/(2^N)

通常,FPGA並不擅長浮點運算,第三種情況,上式的(32/0.5)是很難實現的,因此在正弦波周期一樣的情況下,將精度N調高一位,N=6,(2^5 * 2)/(0.5 * 2),此時fword就不用為0.5,而是1

相位控制字pword的參數解釋,如果從x軸為8的數據開始取,那么相當於正弦波相移了90°,pword = 8,這就是相位控制字


 

為什么地址是由相位控制字加頻率控制字高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)

 
 
設計文件.v
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2022/01/10 15:31:44
// Design Name:
// Module Name: DDS
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////


module DDS(
clk ,
ce ,
we,
data,
reset,
sine,
cose
);

//參數定義
parameter DATA_W = 32;
parameter DATA_W1 = 16;

//輸入信號定義
input clk ;
input reset ;
input [DATA_W-1:0] data;
input we;
input ce;

//輸出信號定義
output[DATA_W1-1:0] sine ;
output[DATA_W1-1:0] cose ;


//中間信號定義
reg [DATA_W-1:0] ADD_A ;
reg [DATA_W-1:0] ADD_B ;
reg [DATA_W1-1:0] cose_DR ;
reg [DATA_W1-1:0] sine_DR ;

wire [DATA_W-1:0] data;
wire [DATA_W1-1:0] cose_D;
wire [DATA_W1-1:0] sine_D;
wire [9:0] ROM_A;


assign cose=cose_DR;
assign sine=sine_DR;
assign ROM_A=ADD_B[31:22]; //這里比較關鍵,需要明白為什么是高位開始想加

//時序邏輯寫法
always@(posedge clk or negedge reset)begin
if(reset)begin
ADD_A <=0;
end
else if(we) begin
ADD_A <=data;
end
end

always@(posedge clk or negedge reset)begin
if(reset)begin
ADD_B <=0;
end
else if(ce) begin
ADD_B <=ADD_B + ADD_A;
end
end

always@(posedge clk or negedge reset)begin
if(reset)begin
cose_DR <=0;
end
else if(ce) begin
cose_DR <=cose_D;
end
end

always@(posedge clk or negedge reset)begin
if(reset)begin
sine_DR <=0;
end
else if(ce) begin
sine_DR <=sine_D;
end
end

cos u1 (
.clka(clk), // input wire clka
.addra(ROM_A), // input wire [9 : 0] addra
.douta(cose_D) // output wire [15 : 0] douta
);

sine u2 (
.clka(clk), // input wire clka
.addra(ROM_A), // input wire [9 : 0] addra
.douta(sine_D) // output wire [15 : 0] douta
);
endmodule

  

 

 
 
 
仿真tb文件
 
 
 
 
 
 
 
 
 
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2022/01/10 15:57:17
// Design Name:
// Module Name: dds_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module dds_tb();

//時鍾和復位
reg clk ;
reg rst_n;
reg we;
reg ce;
//uut的輸入信號
reg[31:0] data ;

 

//uut的輸出信號
wire[15:0] cos;
wire[15:0] sin;


//時鍾周期,單位為ns,可在此修改時鍾周期。
parameter CYCLE = 20;

//復位時間,此時表示復位3個時鍾周期的時間。
parameter RST_TIME = 3 ;

//待測試的模塊例化
DDS uut(
.clk (clk ),
.reset (rst_n ),
.we (we ),
.ce (ce ),
.data (data ),
.sine (sin ),
.cose (cos )

);


//生成本地時鍾50M
initial begin
clk = 0;
forever
#(CYCLE/2)
clk=~clk;
end

//產生復位信號
initial begin
rst_n = 0;
#2;
rst_n = 1;
#(CYCLE*RST_TIME);
rst_n = 0;
end

//輸入信號din0賦值方式
initial begin
#1;
//賦初值
we = 0;
#(5*CYCLE);
//開始賦值
we = 1;

end

//輸入信號din1賦值方式
initial begin
#1;
//賦初值
ce = 0;
#(10*CYCLE);
//開始賦值
ce=1;


end

initial begin
#1;
//賦初值
data = 4194304;
end

endmodule

  

 

 
 
 
 
仿真結果
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM