FPGA入門學習Day1——設計一個DDS信號發生器

目錄

一、DDS簡介

(一)基本原理

(二)主要優勢

(三)與傳統技術的對比

二、FPGA存儲器

(一)ROM波形存儲器

(二)RAM隨機存取存儲器

(三)FIFO先進先出存儲器

三、自主設計過程

(一)相位累加器的設計

(二)波形存儲器ROM的設計

(三)設計實現

四、總結


一、DDS簡介

????????直接數字頻率合成(Direct Digital Frequency Synthesis,簡稱DDS)是一種通過數字技術生成高精度、高分辨率頻率信號的電子方法。它廣泛應用于通信、雷達、儀器測量和信號處理等領域。以下是其核心要點:

(一)基本原理

DDS的核心思想是利用數字控制的方式直接合成模擬信號,主要模塊包括:

  1. 相位累加器:根據輸入的頻率控制字(FTW)逐步累加相位值,生成連續的相位序列。
  2. 波形存儲器(ROM):存儲目標波形(如正弦波、方波)的幅度-相位對應表,將項為值轉換為數字幅度值。
  3. 數模轉換器(DAC):將數字幅度轉換為模擬信號。
  4. 低通濾波器(LPF):濾除DAC輸出的高頻雜散成分,輸出平滑的模擬信號。

(二)主要優勢

  1. 高頻率分辨率:輸出頻率的最小步進由相位累加器位數決定,可達毫赫茲(mHz)級精度。

  2. 快速頻率切換:通過改變頻率控制字,可在納秒級時間內切換頻率,無傳統鎖相環(PLL)的鎖定延遲。

  3. 相位連續性:頻率切換時相位連續,避免信號突變。

  4. 靈活波形生成:支持正弦波、三角波、方波等多種波形,通過修改ROM數據可自定義波形。

  5. 全數字控制:易于與微處理器或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:僅通過單一組地址線控制數據的寫入和讀取操作,讀寫操作無法同時進行;

端口名端口描述
dataRAM讀寫數據
addressRAM讀寫地址,讀地址寫地址共用同一個地址
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博客

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/78073.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/78073.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/78073.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SqlSugar與Entity Framework (EF)的SWOT分析

以下是基于 SWOT 分析法 對 SqlSugar 和 Entity Framework (EF) 的特性對比&#xff1a; SqlSugar 優勢 (Strengths) 高性能&#xff1a; SqlSugar 以輕量化設計著稱&#xff0c;執行速度更快&#xff0c;適合對性能要求較高的場景。在大數據量操作和復雜查詢中表現優異。 易…

學習記錄:DAY16

Maven 進階與前端實戰 前言 二輪考核的內容下來了&#xff0c;由整體項目構建轉為實現特定模塊的功能。對細節的要求更高了&#xff0c;而且有手搓線程池、手搓依賴注入等進階要求&#xff0c;又有得學力。嘻嘻&#xff0c;太簡單了&#xff0c;只要我手搓 Spring Boot 框架……

深度學習--卷積神經網絡調整學習率

文章目錄 前言一、學習率1、什么學習率2、什么是調整學習率3、目的 二、調整方法1、有序調整1&#xff09;有序調整StepLR(等間隔調整學習率)2&#xff09;有序調整MultiStepLR(多間隔調整學習率)3&#xff09;有序調整ExponentialLR (指數衰減調整學習率)4&#xff09;有序調整…

【消息隊列RocketMQ】四、RocketMQ 存儲機制與性能優化

一、RocketMQ 存儲機制詳解 1.1 存儲文件結構? RocketMQ 的存儲文件主要分布在store目錄下&#xff0c;該目錄是在broker.conf配置文件中通過storePathRootDir參數指定的&#xff0c;默認路徑為${user.home}/store 。主要包含以下幾種關鍵文件類型&#xff1a;? 1.1.1 Comm…

C++入門小館: 探尋vector類

嘿&#xff0c;各位技術潮人&#xff01;好久不見甚是想念。生活就像一場奇妙冒險&#xff0c;而編程就是那把超酷的萬能鑰匙。此刻&#xff0c;陽光灑在鍵盤上&#xff0c;靈感在指尖跳躍&#xff0c;讓我們拋開一切束縛&#xff0c;給平淡日子加點料&#xff0c;注入滿滿的pa…

CSS-跟隨圖片變化的背景色

CSS-跟隨圖片變化的背景色 獲取圖片的主要顏色并用于背景漸變需要安裝依賴 colorthief獲取圖片的主要顏色. 并丟給背景注意 getPalette并不是個異步方法 import styles from ./styles.less; import React, { useState } from react; import Colortheif from colorthief;cons…

RAGFlow:構建高效檢索增強生成流程的技術解析

引言 在當今信息爆炸的時代&#xff0c;如何從海量數據中快速準確地獲取所需信息并生成高質量內容已成為人工智能領域的重要挑戰。檢索增強生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;技術應運而生&#xff0c;它將信息檢索與大型語言模型&#xff08;L…

SpringBoot應用:MyBatis的select語句如何返回數組類型

在SpringBoot應用中&#xff0c;比如想返回一個表的主鍵id構成的Long型數組Long[]&#xff0c;需要在XxxMapper.xml文件中這樣定義select語句&#xff1a; <select id"selectIds" parameterType"int" resultType"Long">select id from sy…

【HFP】藍牙HFP協議來電處理機制解析

目錄 一、協議概述與技術背景 1.1 HFP協議演進 1.2 核心角色定義 1.3 關鍵技術指標 二、來電接入的核心交互流程 2.1 基礎流程概述&#xff1a;AG 的 RING 通知機制 2.2 HF 的響應&#xff1a;本地提醒與信令交互 三、帶內鈴聲&#xff08;In-Band Ring Tone&#xff0…

【每天一個知識點】如何解決大模型幻覺(hallucination)問題?

解決大模型幻覺&#xff08;hallucination&#xff09;問題&#xff0c;需要從模型架構、訓練方式、推理機制和后處理策略多方面協同優化。 &#x1f9e0; 1. 引入 RAG 框架&#xff08;Retrieval-Augmented Generation&#xff09; 思路&#xff1a; 模型生成前先檢索知識庫中…

基于STC89C52RC和8X8點陣屏、獨立按鍵的小游戲《打磚塊》

目錄 系列文章目錄前言一、效果展示二、原理分析三、各模塊代碼1、8X8點陣屏2、獨立按鍵3、定時器04、定時器1 四、主函數總結 系列文章目錄 前言 用的是普中A2開發板&#xff0c;外設有&#xff1a;8X8LED點陣屏、獨立按鍵。 【單片機】STC89C52RC 【頻率】12T11.0592MHz 效…

C++學習:六個月從基礎到就業——C++學習之旅:STL迭代器系統

C學習&#xff1a;六個月從基礎到就業——C學習之旅&#xff1a;STL迭代器系統 本文是我C學習之旅系列的第二十四篇技術文章&#xff0c;也是第二階段"C進階特性"的第二篇&#xff0c;主要介紹C STL迭代器系統。查看完整系列目錄了解更多內容。 引言 在上一篇文章中…

leetcode刷題——判斷對稱二叉樹(C語言版)

題目描述&#xff1a; 示例 1&#xff1a; 輸入&#xff1a;root [6,7,7,8,9,9,8] 輸出&#xff1a;true 解釋&#xff1a;從圖中可看出樹是軸對稱的。 示例 2&#xff1a; 輸入&#xff1a;root [1,2,2,null,3,null,3] 輸出&#xff1a;false 解釋&#xff1a;從圖中可看出最…

無法右鍵下載文檔?網頁PDF下載方法大全

適用場景&#xff1a;繞過付費限制/無法右鍵下載/動態加載PDF 方法1&#xff1a;瀏覽器原生下載&#xff08;成功率60%&#xff09; Chrome/Edge&#xff1a; 在PDF預覽頁點擊工具欄 ??下載圖標&#xff08;右上角&#xff09; 快捷鍵&#xff1a;CtrlS → 保存類型選PDF …

基于缺失數據的2024年山東省專項債發行報告

一、數據情況 本次報告選取了山東省財政局公開的2024年專項債數據,共計2723條,發行期數是從第1期到第58期,由于網絡原因,其中25期到32期,54到57期的數據有缺失,如下圖所示。 從上圖看出,一年52周,平均每周都有一期發布,因此持續做專項債的謀劃很重要,一定要持續謀劃…

Ubuntu數據連接訪問崩潰問題

目錄 一、分析問題 1、崩潰問題本地調試gdb調試&#xff1a; 二、解決問題 1. 停止 MySQL 服務 2. 卸載 MySQL 相關包 3. 刪除 MySQL 數據目錄 4. 清理依賴和緩存 5.重新安裝mysql數據庫 6.創建程序需要的數據庫 三、驗證 1、動態庫更新了 2、頭文件更新了 3、重新…

Linux系統編程 day10 接著線程(中期頭大,還要寫論文)

線程有點懵逼 線程之前函數回顧以及總結部分&#xff08;對不清楚的問題再思考&#xff09; 線程控制原語 進程控制原語 pthread_create(); fork(); pthread_self(); getpid(); pthread_exit(); exit(); pthread_join(); …

《潯川AI翻譯v6.1.0問題已修復公告》

《潯川AI翻譯v6.1.0問題已修復公告》 尊敬的潯川AI翻譯用戶&#xff1a; 感謝您對潯川AI翻譯的支持與反饋&#xff01;我們已針對 **v6.1.0** 版本中用戶反饋的多個問題進行了全面修復&#xff0c;并優化了系統穩定性。以下是本次修復的主要內容&#xff1a; 已修復問題 ?…

深入理解 java synchronized 關鍵字

&#x1f9d1; 博主簡介&#xff1a;CSDN博客專家&#xff0c;歷代文學網&#xff08;PC端可以訪問&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移動端可微信小程序搜索“歷代文學”&#xff09;總架構師&#xff0c;15年工作經驗&#xff0c;…

華三(H3C)與華為(Huawei)設備配置IPsec VPN的詳細說明,涵蓋配置流程、參數設置及常見問題處理

以下是針對華三&#xff08;H3C&#xff09;與華為&#xff08;Huawei&#xff09;設備配置IPsec VPN的詳細說明&#xff0c;涵蓋配置流程、參數設置及常見問題處理&#xff1a; 一、華三&#xff08;H3C&#xff09;設備IPsec VPN配置詳解 1. 配置流程 華三IPsec VPN配置主要…