目錄
一、DDS簡介
(一)基本原理
(二)主要優勢
(三)與傳統技術的對比
二、FPGA存儲器
(一)ROM波形存儲器
(二)RAM隨機存取存儲器
(三)FIFO先進先出存儲器
三、自主設計過程
(一)相位累加器的設計
(二)波形存儲器ROM的設計
(三)設計實現
四、總結
一、DDS簡介
????????直接數字頻率合成(Direct Digital Frequency Synthesis,簡稱DDS)是一種通過數字技術生成高精度、高分辨率頻率信號的電子方法。它廣泛應用于通信、雷達、儀器測量和信號處理等領域。以下是其核心要點:
(一)基本原理
DDS的核心思想是利用數字控制的方式直接合成模擬信號,主要模塊包括:
- 相位累加器:根據輸入的頻率控制字(FTW)逐步累加相位值,生成連續的相位序列。
- 波形存儲器(ROM):存儲目標波形(如正弦波、方波)的幅度-相位對應表,將項為值轉換為數字幅度值。
- 數模轉換器(DAC):將數字幅度轉換為模擬信號。
- 低通濾波器(LPF):濾除DAC輸出的高頻雜散成分,輸出平滑的模擬信號。
(二)主要優勢
-
高頻率分辨率:輸出頻率的最小步進由相位累加器位數決定,可達毫赫茲(mHz)級精度。
-
快速頻率切換:通過改變頻率控制字,可在納秒級時間內切換頻率,無傳統鎖相環(PLL)的鎖定延遲。
-
相位連續性:頻率切換時相位連續,避免信號突變。
-
靈活波形生成:支持正弦波、三角波、方波等多種波形,通過修改ROM數據可自定義波形。
-
全數字控制:易于與微處理器或FPGA集成,實現自動化頻率調制(如FSK、PSK)。
(三)與傳統技術的對比
-
DDS vs. PLL:DDS頻率切換更快、分辨率更高,但輸出頻率范圍較窄;PLL適合高頻但分辨率較低。
-
DDS vs. 模擬振蕩器:DDS頻率和相位可控性更優,但噪聲性能可能略遜。
二、FPGA存儲器
????????常見的FPGA存儲器有3種,RAM( 隨機訪問內存)ROM(只讀存儲器)FIFO(先入先出)這三種存儲器的區別如下:
- 其中RAM通常都是在掉電之后就丟失數據,ROM在系統停止供電的時候仍然可以保持數據
- 可以向RAM和ROM中的任意位置寫入數據,也可以讀取任意的位置的數據
- 而FIFO的數據先入先出,先進去的數據先出來,只能順序寫入數據,順序的讀出數據,其數據地址由內部讀寫指針自動加1完成,不能像普通存儲器那樣可以由地址線決定讀取或寫入某個指定的地址。
這三種存儲器的應用場合:
- RAM和ROM常用于存儲指令或者中間的數據
- FIFO常用于數據傳輸通道中用于緩存數據,避免數據丟失,如不同速率時鐘模塊間的數據傳輸就需要用到異步FIFO
(一)ROM波形存儲器
????????ROM(Read-Only Memory,只讀存儲器)是一種僅支持數據讀取操作的半導體存儲器,其存儲內容在寫入后不可修改。由于FPGA芯片本身不具備非易失性存儲單元,其ROM功能需通過特殊方式實現:利用FPGA內部的RAM資源構建ROM模塊,并通過加載數據文件在運行時對模塊進行初始化,從而模擬非易失存儲特性。
????????Altera(現Intel PSG)提供的ROM IP核主要分為兩類:單端口ROM僅配置單個地址讀取通道和對應的數據輸出端口,支持單向讀取操作;雙端口ROM則擴展為兩組獨立的地址/數據端口,可同時進行兩路并行讀取操作。兩種類型均保持ROM的只讀特性,核心差異體現在接口數量和并行讀取能力上。
?1、調用IP
(1)創建文件
我們單獨創建一個ROM文件來熟悉ROM調用IP,我們在Quartus中創建一個新項目命名為ROM。
(2)調用IP
在左側的IP Catalog搜索欄搜索“ROM”,然后選擇“ROM:1-PORT”。
接下來在最開始我們創建的文件夾中添加一個名為IP的文件夾,隨后我們將把IP文件保存在其中。?
?
(3)配置IP
- 第一部分是IP核的輸出數據位寬以及IP核的存儲容量
- 第二部分是存儲單元類型,默認即可
- 第三部分是選擇時鐘模式,單時鐘或者雙時鐘,我們這里選擇的是單時鐘。
隨后點擊next進入下一步配置。
這一步包括以下三個部分
- 第一部分是選擇輸出端口Q是否寄存。
- 第二部分是時鐘使能信號,通常默認不勾選。
- 第三部分是選擇是否創建已補復位信號“acir”和讀使能信號“rden”。這里兩項都不勾選。
這一步是將生成的MIF文件添加進去。本次采用matlab生成一個FPGA所需要的正弦波MIF文件,sin_wave_8x256.mif會生成在你的資源管理器中,把他添加到你的FPGA工程文件下
?MIF文件代碼:
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:Ns2(i) = round(s(i)); %對小數四舍五入以取整if s2(i) <0 %負 1 強制置零s2(i) = 0endfprintf(fild, '%d',s2(i)); %數據寫入if i==Nfprintf(fild, '%s\n',';'); %最后一個數據用;elsefprintf(fild,',\n'); % 其他數據用,end
end
fclose(fild); % 寫完了,關閉文件復卷機: 04-09 16:48:55
% 方波信號波形采集參考代碼(square_wave.m):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:Ns2(i) = round(s(i)); %對小數四舍五入以取整if s2(i) <0 %負 1 強制置零s2(i) = 0endfprintf(fild, '%d',s2(i)); %數據寫入if i==Nfprintf(fild, '%s\n',';'); %最后一個數據用分號elsefprintf(fild,',\n'); % 其他數據用 ,end
end
fclose(fild); % 寫完了,關閉文件
MATLAB生成的正弦信號波形圖:
?最后一步勾選如圖所示,生成例化文檔。
2、代碼
(1)單端口ROM
- 頂層模塊rom
module rom
(input wire sys_clk ,input wire sys_rst_n,output wire [7:0] rom_data
);wire [7:0] rom_addr;rom_ctrl rom_ctrl_inst
(.sys_clk (sys_clk),.sys_rst_n (sys_rst_n),.rom_addr (rom_addr)
);rom_sin rom_sin_inst (.address ( rom_addr ),.clock ( sys_clk ),.q ( rom_data ));endmodule
- ?rom_Ctrl模塊代碼:
module rom_ctrl
(input wire sys_clk ,input wire sys_rst_n ,output reg [7:0] rom_addr
);always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)rom_addr <= 8'd0;elserom_addr <= rom_addr + 1'b1;endmodule
- 測試代碼?
`timescale 1ns/1ns
module tb_rom();reg sys_clk;
reg sys_rst_n;wire rom_data;initialbeginsys_clk = 1'b0;sys_rst_n <= 1'b0;#30sys_rst_n <= 1'b1;endalways #10 sys_clk = ~sys_clk;rom rom_inst
(.sys_clk (sys_clk),.sys_rst_n (sys_rst_n),.rom_data (rom_data)
);endmodule
- 效果及其總結
????????由仿真波形數據得正弦波的周期是5120ns,而ROM存儲的一個完整正弦波周期也是5120ns。仿真結果正確。
(2)雙端口ROM
在原來的基礎,采用雙端口ROM輸出兩路信號,一路正弦波一路方波。步驟與上訴類似?
- 通過MATLAB生成兩路信號如下為MIF文件代碼:
clc; %清除命令行命令
clear all; %清除工作區變量,釋放內存空間
F1=1; %信號頻率
Fs=2^8; %采樣頻率
P1=0; %信號初始相位
N=2^8; %采樣點數
t=[0:1/Fs:(N-1)/Fs]; %采樣時刻
ADC=2^7 - 1; %直流分量
A=2^7; %信號幅度
s1=A*sin(2*pi*F1*t + pi*P1/180) + ADC; %正弦波信號
s2=A*square(2*pi*F1*t + pi*P1/180) + ADC; %方波信號
%創建mif文件
fild = fopen('wave_512x8.mif','wt');
%寫入mif文件頭
fprintf(fild, '%s\n','WIDTH=8;'); %位寬
fprintf(fild, '%s\n\n','DEPTH=512;'); %深度
fprintf(fild, '%s\n','ADDRESS_RADIX=UNS;'); %地址格式
fprintf(fild, '%s\n\n','DATA_RADIX=UNS;'); %數據格式
fprintf(fild, '%s\t','CONTENT'); %地址
fprintf(fild, '%s\n','BEGIN'); %開始
for j = 1:2for i = 1:Nif j == 1 %打印正弦信號數據s0(i) = round(s1(i)); %對小數四舍五入以取整fprintf(fild, '\t%g\t',i-1); %地址編碼endif j == 2 %打印方波信號數據s0(i) = round(s2(i)); %對小數四舍五入以取整fprintf(fild, '\t%g\t',i-1+N); %地址編碼endif s0(i) <0 %負1強制置零s0(i) = 0endfprintf(fild, '%s\t',':'); %冒號fprintf(fild, '%d',s0(i)); %數據寫入fprintf(fild, '%s\n',';'); %分號,換行end
end
fprintf(fild, '%s\n','END;'); %結束
fclose(fild);
- 頂層模塊rom
module rom
(input wire sys_clk ,input wire sys_rst_n ,output wire [7:0] rom_data ,output wire [7:0] sin_d ,output wire [7:0] squ_d
);wire [7:0] rom_addr;
wire [8:0] rom_sin_d;
wire [8:0] rom_squ_d;rom_ctrl rom_ctrl_inst
(.sys_clk (sys_clk),.sys_rst_n (sys_rst_n),.rom_addr (rom_addr),.rom_sin_d (rom_sin_d),.rom_squ_d (rom_squ_d)
);rom_double rom_double_inst ( //雙端口ROM.address_a ( rom_sin_d ),.address_b ( rom_squ_d ),.clock ( sys_clk ),.q_a ( sin_d ),.q_b ( squ_d ));rom_sin rom_sin_inst (.address ( rom_addr ),.clock ( sys_clk ),.q ( rom_data ));endmodule
- rom_ctrl模塊
module rom_ctrl
(input wire sys_clk ,input wire sys_rst_n ,output reg [7:0] rom_addr ,output reg [8:0] rom_sin_d ,output reg [8:0] rom_squ_d
);localparam SQU_Z = 9'd256;always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)rom_addr <= 8'd0;else rom_addr <= rom_addr + 1'b1;always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)rom_sin_d <= 9'd0;else if(rom_sin_d == 9'd255)rom_sin_d <= 9'd0;elserom_sin_d <= rom_addr + 1'b1;always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)rom_squ_d <= SQU_Z;elserom_squ_d <= rom_addr + SQU_Z;endmodule
- 測試代碼
`timescale 1ns/1ns
module tb_rom();reg sys_clk;
reg sys_rst_n;wire rom_data;
wire rom_sin_d;
wire rom_squ_d;initialbeginsys_clk = 1'b0;sys_rst_n <= 1'b0;#30sys_rst_n <= 1'b1;endalways #10 sys_clk = ~sys_clk;rom rom_inst
(.sys_clk (sys_clk),.sys_rst_n (sys_rst_n),.rom_data (rom_data),.sin_d (rom_sin_d),.squ_d (rom_squ_d)
);endmodule
- 效果及其總結
(二)RAM隨機存取存儲器
????????隨機存取存儲器(Random Access Memory,RAM)是一種能夠隨時從指定地址讀取數據或向指定地址寫入數據的存儲器件,其讀寫速度由時鐘頻率直接決定。RAM通常用于臨時存儲運行中的程序代碼、執行時產生的中間數據及運算結果,是計算機系統中實現高速數據存取的核心組件之一。在Altera(現為Intel FPGA)的硬件架構中,RAM的實現主要分為兩種類型:?
(1)單端口RAM:僅通過單一組地址線控制數據的寫入和讀取操作,讀寫操作無法同時進行;
端口名 | 端口描述 |
---|---|
data | RAM讀寫數據 |
address | RAM讀寫地址,讀地址寫地址共用同一個地址 |
wren | 寫使能信號,高電平有效 |
rden | 讀使能信號,高電平有效 |
clken | 時鐘使能信號 |
aclr | 復位信號,高電平有效 |
inclock/outclock | 單端口RAM支持雙時鐘模式和單時鐘模式 |
(2)雙端口RAM:配備兩組獨立的地址線,可分別控制寫入端口和讀取端口,允許同時進行讀寫操作,從而提升數據吞吐效率,適用于需要并行數據處理的場景(如緩存、實時信號處理等)。
????????兩種結構的核心差異在于端口資源的分配,雙端口RAM通過硬件級并行設計突破了單端口RAM的時序限制,但會占用更多的邏輯資源。
1、調用IP
(1)創建文件
我們單獨創建一個RAM文件來熟悉RAM調用IP,我們在Quartus中創建一個新項目命名為RAM。
(2)調用IP
在左側的IP Catalog搜索欄搜索“RAM”,然后選擇“RAM:1-PORT”。
接下來在最開始我們創建的文件夾中添加一個名為IP的文件夾,隨后我們將把IP文件保存在其中。
(3)配置IP
- 第一部分是IP核的輸出數據位寬
- 第二部分是IP核的存儲容量
- 第三部分是存儲單元類型,默認即可
- 第四部分是選擇時鐘模式,單時鐘或者雙時鐘,我們這里選擇的是單時鐘。
隨后點擊next進入下一步配置。
這一步包括以下三個部分
- 第一部分是選擇輸出端口Q是否寄存。
- 第二部分是時鐘使能信號,通常默認不勾選。
- 第三部分是選擇是否創建已補復位信號“acir”和讀使能信號“rden”。這里兩項都勾選。
隨后點擊next進入下一步配置。
這一步是配置某個地址寫入數據的同時讀取數據,通常默認“New data”即可,隨后點擊next進入下一步配置。
?這一步是配置RAM存儲器初始化參數包括以下兩個部分:
- 第一部分是確定是否配置初始化文件,根據需求可以自主選擇,此處默認不改。
- 第二部分是確定是否選擇允許系統存儲器內容編輯器采集和更新內容在與系統時鐘無關的情況下,默認不勾選。
最后一步是將inst文件勾選上點擊finish完成配置,隨后點擊Yes。
2、代碼
(1).v代碼
module ram_ip( input clk ,input rst_n ,input [5:0] address ,input [7:0] data ,input redn ,input wren ,output [7:0] q
);ram ram_inst (.aclr ( ~rst_n ),.address ( address ),.clock ( clk ),.data ( data ),.rden ( rden ),.wren ( wren ),.q ( q ));endmodule
(2)測試代碼?
這個測試代碼的意思就是將地址依次增加,并且每加一次寫進去一個數據。之后再從頭的地址讀數據。
`timescale 1ns/1nsmodule tb_ram();
reg tb_clk ;
reg tb_rst_n ;
reg [5:0] address ;
reg [7:0] data ;
reg rden ;
reg wren ;
wire [7:0] q ;parameter CYCLE = 20;ram_ip ram_ip_inst( /*input */.clk ( tb_clk ) ,/*input */.rst_n ( tb_rst_n ) ,/*input [5:0] */.address ( address ) ,/*input [7:0] */.data ( data ) ,/*input */.redn ( rden ) ,/*input */.wren ( wren ) ,/*output [7:0] */.q ( q )
);always #(CYCLE/2) tb_clk = ~tb_clk;integer i;
initial begintb_clk = 1'b1;tb_rst_n = 1'b1;#(CYCLE*2);tb_rst_n = 1'b0;address = 0;//復位賦初值data = 0;rden = 0;wren = 0;#(CYCLE*10);tb_rst_n = 1'b1;#(100*CYCLE);for (i=0;i<64 ;i=i+1 ) begin //寫入數據address = i;data = i+1;wren = 1'b1;#CYCLE;endwren = 0;#(CYCLE*10);for (i=10;i<32 ;i=i+1 ) begin //讀取數據address = i;rden = 1'b1;#CYCLE;endrden = 0;$stop;
endendmodule
不會仿真的朋友可以點擊如下鏈接跳轉去學習:FPGA入門學習Day0——狀態機相關內容解析HDLbits練習_boost峰值電流模控制的二進制碼狀態機用什么表征-CSDN博客
3、效果及其總結
????????在仿真過程中,當寫使能信號“wren”置為高電平時,系統會向指定地址的存儲單元中寫入對應的數據;而讀使能信號“rden”(或“redn”)置為高電平時,則會從該地址的存儲單元中讀取已存儲的數據。這兩個信號通過高低電平狀態分別獨立控制數據的寫入和讀取操作,確保存儲訪問的時序邏輯清晰且功能明確。
(三)FIFO先進先出存儲器
????????FIFO(先進先出存儲器)是一種基于順序存取原則的存儲器件,無外部地址線,數據按寫入順序依次讀出,主要用于數據緩沖、速率匹配及跨時鐘域異步數據交互。與RAM(支持隨機讀寫,用于程序及運行時數據存儲)和ROM(僅讀不可改)不同,FIFO專注于流式數據處理,無法通過地址線指定讀寫位置。
????????FIFO分為單時鐘(SCFIFO)和雙時鐘(DCFIFO)兩類。單時鐘FIFO所有操作由單一時鐘同步控制,適用于同時鐘域的數據緩存;雙時鐘FIFO的讀寫端口分別由獨立時鐘(wrclk和rdclk)驅動,進一步分為普通雙時鐘FIFO(讀寫位寬一致)和混合位寬雙時鐘FIFO(支持讀寫位寬轉換,如32位轉16位)。
1、調用IP
(1)創建文件
創建一個文件夾其內容分布如下:
- doc:存放結果、圖片等等
- ip :存放IP核
- prj :存放工程文件
- rtl :存放主要代碼
- tb :存放testbench代碼
(2)調用IP
在左側的IP Catalog搜索欄搜索“FIFO”,然后選擇“FIFO”。
將IP存入文件夾中IP內,點擊OK。
(3)配置IP
- 第一部分是FIFO的位寬
- 第二部分是FIFO的深度
- 第三部分是確定是否設置成雙時鐘FIFO。
隨后點擊next進入下一步配置。
?第二頁不用修改,保持默認即可。
?這一步將方框中框住的全打勾即可
這一步是配置FIFO的工作模式,具體配置分為如下兩個部分:
- 第一部分:FIFO工作模式分為正常模式和前顯模式。正常模式下讀使能后下一時鐘輸出數據;前顯模式下數據在讀請求前已有效,但空信號(empty)滯后寫操作兩周期,適用于需低延遲的實時處理場景,這里選此模式優化效率。
- 第二部分的配置選擇默認Auto即可
這一步把例化文件勾選上,可以看左邊的簡化圖,點擊finish完成配置。
2、代碼
(1).v代碼
module fifo_test( input wire wr_clk ,//寫時鐘input wire rd_clk ,//讀時鐘input wire rst_n ,input wire [7:0] wr_din ,//寫入fifo數據input wire wr_en ,//寫使能input wire rd_en ,//讀使能output reg [7:0] rd_dout ,//讀出的數據output reg rd_out_vld //讀有效信號
);//------------<參數說明>--------------------------------------------wire [7:0] wr_data ;
wire [7:0] q ;
wire wr_req ;//寫請求
wire rd_req ;//讀請求
wire wr_empty ;//寫空
wire wr_full ;//寫滿
wire rd_empty ;//讀空
wire rd_full ;//讀滿
wire [7:0] wr_usedw ;//在寫時鐘域下,FIFO 中剩余的數據量;
wire [7:0] rd_usedw ;//在讀時鐘域下,FIFO 中剩余的數據量。fifo fifo_inst (.data ( wr_data ),.rdclk ( rd_clk ),.rdreq ( rd_req ),.wrclk ( wr_clk ),.wrreq ( wr_req ),.q ( q ),.rdempty ( rd_empty ),.rdfull ( rd_full ),.rdusedw ( rd_usedw ),.wrempty ( wr_empty ),.wrfull ( wr_full ),.wrusedw ( wr_usedw ));
assign wr_data = wr_din;
assign wr_req = (wr_full == 1'b0)?wr_en:1'b0;
assign rd_req = (rd_empty == 1'b0)?rd_en:1'b0;always @(posedge rd_clk or negedge rst_n)beginif(!rst_n)beginrd_dout <= 0;endelse beginrd_dout <= q;end
end
always @(posedge rd_clk or negedge rst_n)beginif(!rst_n)beginrd_out_vld <= 1'b0;endelse beginrd_out_vld <= rd_req;end
endendmodule
(2)測試代碼:
`timescale 1 ns/1 ns
module tb_fifo();
//時鐘和復位reg wr_clk;reg rd_clk;reg rst_n ;
//輸入信號reg [7:0] wr_din;reg wr_en ;reg rd_en ;
//輸出信號wire rd_out_vld;wire [7:0] rd_dout;parameter WR_CYCLE = 20;//寫時鐘周期,單位為 ns,
parameter RD_CYCLE = 30;//讀時鐘周期,單位為 ns,
parameter RST_TIME = 3 ;//復位時間,此時表示復位 3 個時鐘周期的時間。
//待測試的模塊例化fifo_test fifo_test_inst( /*input wire */.wr_clk ( wr_clk ) ,//寫時鐘/*input wire */.rd_clk ( rd_clk ) ,//讀時鐘/*input wire */.rst_n ( rst_n ) ,/*input wire [7:0] */.wr_din ( wr_din ) ,//寫入fifo數據/*input wire */.wr_en ( wr_en ) ,//寫使能/*input wire */.rd_en ( rd_en ) ,//讀使能/*output reg [7:0] */.rd_dout ( rd_dout ) ,//讀出的數據/*output reg */.rd_out_vld ( rd_out_vld ) //讀有效信號
);integer i = 0;
//生成本地時鐘 50Minitial wr_clk = 0;always #(WR_CYCLE/2) wr_clk=~wr_clk;initial rd_clk = 0;always #(RD_CYCLE/2) rd_clk=~rd_clk;
//產生復位信號initial beginrst_n = 1;#2;rst_n = 0;#(WR_CYCLE*RST_TIME);rst_n = 1;end
//輸入信號賦值initial begin#1;wr_din = 0;//賦初值wr_en = 0;#(10*WR_CYCLE);for(i=0;i<500;i=i+1)begin//開始賦值wr_din = {$random};wr_en = {$random};#(1*WR_CYCLE);end#(100*WR_CYCLE);endinitial begin#1;rd_en = 0;//賦初值#(12*RD_CYCLE); for(i=0;i<500;i=i+1)begin//開始賦值rd_en = {$random};#(1*RD_CYCLE);end#(100*RD_CYCLE);$stop;end
endmodule
3、結果和總結
????????在數據流模式下,FIFO的操作表現為:當寫使能信號(wren)有效時,數據立即寫入隊列;讀使能信號(rden)有效時,數據同步從隊列讀出。由于數據在填滿前即被持續讀出,空和滿狀態信號未被觸發,表明隊列始終未達到存儲容量上限或下限。這種模式適用于持續流式數據傳輸場景。
三、自主設計過程
(一)相位累加器的設計
????????相位累加器在時鐘的驅動下,將輸入的頻率控制字轉換為地址并輸出,它決定著頻率的范圍和分辨率。本設計不使用相位調制器,相位累加器采用m=17位的二進制累加器和寄存器構成,其結果直接送到后面的存儲器。在Quartus中新建工程,新建.v文件取名為“addr_cnt”,代碼如下:
module addr_cnt(CPi,K,ROMaddr,Address);input CPi;input [12:0] K;output reg [9:0] ROMaddr;output reg [16:0] Address;always @(posedge CPi) beginAddress=Address+K;ROMaddr=Address[16:7];end
endmodule
保存后,右擊文件 →??Set as Top Level Entity,編譯運行后再次右鍵點擊 →?Create Symbol Files for Current File
模塊符號如下所示:?
(二)波形存儲器ROM的設計
1、方波模塊
????????由于方波的實現算法相對簡單,可以不用ROM表,直接用寄存器來保存方波的輸出值。方波只有高、低電平兩種狀態,因此只需要在一個周期的中間位置翻轉電平即可。其實現原理如下:由于相位累加器的值是線性累加的,因此地址值(Address)也是線性累加的,對地址值Address進行判斷,當地址值的最高位為0時,便將存儲波形幅值的存儲器的每一位賦值為1,否則賦值為0。具體源程序如下:
module squwave(CPi,RSTn,Address,Qsquare);input CPi;input RSTn;input [16:0] Address;output reg [11:0] Qsquare;always @(posedge CPi)if (!RSTn)Qsquare=12'h000; else beginif(Address<=17'h0FFFF)Qsquare=12'hFFF;else Qsquare=12'h000;end
endmodule
模塊符號如下所示:
2、正弦波
????????這個比較復雜,需要用IP核調用ROM存儲。但是直接IP核生成ROM并更改存儲數據太麻煩了,我們可以編寫一個C語言程序,生成存儲器的初始化文件Sine1024.mif。
C語言程序如下所示:
/*myMIF.c*/
#include <stdio.h>
#include <math.h>
#define PI 3.141592
#define DEPTH 1024
#define WIDTH 12
int main(void)
{int n,temp;float v;FILE *fp;fp=fopen("Sine1024.mif","w+");if(NULL==fp)printf("Can not creat file!\r\n");else{printf("File created successfully!\n");fprintf(fp,"DEPTH=%d;\n",DEPTH);fprintf(fp,"WIDTH=%d;\n",WIDTH);fprintf(fp,"ADDRESS_RADIX=HEX;\n");fprintf(fp,"DATA_RADIX=HEX;\n");fprintf(fp,"CONTENT\n");fprintf(fp,"BEGIN\n");for(n=0;n<DEPTH;n++){v=sin(2*PI*n/DEPTH);temp=(int)((v+1)*4095/2);fprintf(fp,"%04x : %03x;\n",n,temp);}fprintf(fp,"END;\n");fclose(fp);}}
配置ROM:
????????按照前面第二章的步驟進行配置單端口ROM,將IP核的輸出數據位寬以及IP核的存儲容量設置為12、1024。再將上述c語言生成的mif文件添加進去。
3、鎖相環倍頻電路
現在需要調用宏模塊定制一個100MHz的鎖相環模塊。其過程如下所示:
- 在IP Catalog搜索欄輸入APTPLL并雙擊。
?按照下圖進行設置即可
4、?頂層電路設計
將其設為頂層文件再編譯
module DDS_top (CLOCK_50,RSTn,WaveSel,K,
WaveValue,LEDG,CLOCK_100);input CLOCK_50;input RSTn;input [1:0] WaveSel;input [12:0] K;output reg [11:0] WaveValue;wire [9:0] ROMaddr/* synthesis keep */;wire [16:0] Address;wire [11:0] Qsine,Qsquare;output [0:0] LEDG;output CLOCK_100;wire CPi=CLOCK_100;PLL100M_CP PLL100M_CP_inst(.inclk0(CLOCK_50),.c0(CLOCK_100),.locked(LEDG[0]));addr_cnt U0_instance(CPi,K,ROMaddr,Address);SineROM ROM_inst(.address(ROMaddr),.clock(CPi),.q(Qsine));squwave U1(CPi,RSTn,Address,Qsquare);always @(posedge CPi)begincase(WaveSel)2'b01:WaveValue=Qsine;2'b10:WaveValue=Qsquare;default:WaveValue=Qsine;endcaseend
endmodule
?(三)設計實現
設計好DDS之后我們使用DE2-115開發板來實現,如下我們將著手在DE2-115開發板上進行操作
1、代碼介紹:
????????首先我們創建dds_top的頂層代碼,將下述的代碼寫入其中,此代碼:
module dds_top(input wire clk_50m, // 50MHz系統時鐘input wire rst_n, // 復位信號input wire [31:0] fcw, // 頻率控制字input wire wave_sel, // 波形選擇(0:正弦波, 1:方波)output wire [7:0] dac_out // 8位DAC輸出
);wire [7:0] phase; // 相位地址
wire [7:0] sine_data; // 正弦波數據
wire [7:0] square_data; // 方波數據// 相位累加器模塊
phase_accumulator u_phase_accum(.clk(clk_50m),.rst_n(rst_n),.fcw(fcw),.phase(phase)
);// 正弦波ROM表
sine_rom u_sine_rom(.address(phase),.clock(clk_50m),.q(sine_data)
);// 方波生成模塊
square_wave_gen u_square_gen(.phase(phase),.square_data(square_data)
);// 波形選擇輸出
assign dac_out = wave_sel ? square_data : sine_data;endmodule
2、模塊解讀:
模塊結構
-
輸入:50MHz時鐘(clk_50m)、低有效復位(rst_n)、32位頻率控制字(fcw)、波形選擇(wave_sel)
-
輸出:8位DAC數據(dac_out)
-
內部信號:8位相位地址(phase)、正弦波數據(sine_data)、方波數據(square_data
相位累加器
-
采用32位累加器,每個時鐘周期累加fcw值
-
取累加器高8位[31:24]作為相位地址(phase)
-
頻率分辨率:f_out = (fcw × 50MHz)/232 ≈ 0.0116Hz/FCW步進
正弦波生成
-
使用256深度ROM(sine_rom)
-
相位地址直接作為ROM查表地址
-
同步讀取設計,輸出延遲1個時鐘周期
方波生成
-
組合邏輯實現,無時鐘延遲
-
相位值≥128時輸出0xFF(高電平),否則0x00(低電平)
-
生成50%占空比方波,頻率與正弦波一致
輸出選擇
-
多路選擇器根據wave_sel選擇波形
-
正弦波存在1周期延遲,方波實時輸出
引腳配置如下所示:
?隨后打開波形仿真得到以下結果(不會波形仿真的同志可以閱讀如下博客進行學習:FPGA學習(一)數字邏輯與FPGA實現基基礎-CSDN博客):
四、總結
????????通過本次DDS信號發生器的設計與實現,我深入理解了直接數字頻率合成的核心原理,掌握了FPGA中ROM、RAM等存儲器的配置與調用技巧。在構建相位累加器、波形ROM表及方波生成模塊時,深刻體會到時序同步與模塊協同的重要性。實驗過程中,通過Matlab生成波形數據、Quartus調用IP核以及仿真驗證,進一步鞏固了理論與實踐的結合能力。盡管在波形切換的相位連續性優化上仍有提升空間,但成功實現了可調頻率的正弦波與方波輸出,為后續復雜信號合成奠定了扎實基礎。
參考博客:
FPGA基礎--【Altera】IP核(2)---RAM隨機存取存儲器_fpga板上ram-CSDN博客MATLAB生成FPGA ROM與雙端口ROM實現正弦波與方波存儲與讀取-CSDN博客