FPGA學習(五)——DDS信號發生器設計
目錄
- FPGA學習(五)——DDS信號發生器設計
- 一、FPGA開發中常用IP核——ROM/RAM/FIFO
- 1、ROM簡介
- 2、ROM文件的設置
- (1)直接編輯法
- (2)用C語言等軟件生成初始化文件
- 3、ROM IP核配置調用
- 二、DDS信號發生器設計
- 1、相位累加器設計
- 2、波形存儲器ROM設計
- (1)方波模塊
- (2)正弦波形存儲器模塊
- 3、鎖相環倍頻電路設計
- 4、頂層電路設計
- 5、最終實現
- 6、仿真波形
一、FPGA開發中常用IP核——ROM/RAM/FIFO
常見的FPGA存儲器有3種,RAM(隨機訪問內存)ROM(只讀存儲器)FIFO(先入先出)
這三種存儲器的區別如下:
其中RAM通常都是在掉電之后就丟失數據,ROM在系統停止供電的時候仍然可以保持數據
可以向RAM和ROM中的任意位置寫入數據,也可以讀取任意的位置的數據
而FIFO的數據先入先出,先進去的數據先出來,只能順序寫入數據,順序的讀出數據,其數據地址由內部讀寫指針自動加1完成,不能像普通存儲器那樣可以由地址線決定讀取或寫入某個指定的地址。
這三種存儲器的應用場合:
RAM和ROM常用于存儲指令或者中間的數據
FIFO常用于數據傳輸通道中用于緩存數據,避免數據丟失,如不同速率時鐘模塊間的數據傳輸就需要用到異步FIFO
1、ROM簡介
? ROM 是只讀存儲器(Read-Only Memory)的簡稱,是一種只能讀出事先所存數據的固態半導體存儲器。其特性是一旦儲存資料就無法再將之改變或刪除,且資料不會因為電源關閉而消失。而事 實上在 FPGA 中通過 IP 核生成的 ROM 或 RAM(RAM 將在下一節為大家講解)調用的都是FPGA 內部的 RAM 資源,掉電內容都會丟失(這也很容易解釋,FPGA 芯片內部本來就沒有掉電非易失存儲器單元)。用 IP 核生成的 ROM 模塊只是提前添加了數據文件(.coe 格式)(.mif/.hex格式),在 FPGA 運行時通過數據文件給 ROM 模塊初始化,才使得 ROM 模塊像個“真正”的掉電非易失存儲器;也正是這個原因,ROM 模塊的內容必須提前在數據文件中寫死,無法在電路中修改。
最簡單的使用有效時鐘CLKA、有效地址ADDRA和有效使能EA,就可以輸出DOUTA
單端口ROM:只提供一個獨立的地址端口核一個讀數據端口
雙端口ROM:有兩個地址端口,和兩個讀數據端口
2、ROM文件的設置
(1)直接編輯法
在Edit找到Custom Fill Cells
設置完成后,將文件保存,然后用編輯器打開看一下
(2)用C語言等軟件生成初始化文件
當需要初始化的存儲單元變多時,上述手工輸入數據的方法就不太實用了。在了解mif文件的格式的基礎上,可以自己編寫C語言程序或者使用MATLAB程序自動生成mif文件。
以下是產生128X8位正弦波形數據的C語言源程序 :
#include<stdio.h>
#include<math.h>
#define PI 3.141592
#define DEPTH 128 //數據深度,即存儲單元的個數
#define WIDTH 8 //存儲單元的寬度
int main(void)
{
int n, temp;
float v;
FILE *fp;
/*建立名為Data_sine.mif的新文件,允許寫入數據*/
fp= fopen("Data_sine.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++)
{
/*周期為128個點的正弦波*/
v= sin(2*PI*n/DEPTH);
/*將-1~1之間的正弦波的值擴展到0~255之間*/
temp=(int)((v+1)*255/2);//v+1將數值平移到lO~2之間
/*以十六進制形式輸出地址和數據*/
fprintf(fp, "%x\t:\t%x;\n",n, temp);
}
fprintf(fp,"END;\n");
fclose(fp);
}
}
編輯上述代碼后生成myimf.exe文件,在資源管理器中雙擊運行exe文件,生成Data_sine.mif文件
3、ROM IP核配置調用
在Tools->IP Catalog->搜索ROM
1-PORT單端口,2-PORT雙端口,此次以單端口為例
How wide should the ‘q’ output bus be?
How many x-bit words of memory?
配置ROM的位寬和深度
What should the memory block type be?
使用什么類型的內存
在官方資料中,有提及什么類型的芯片適合用什么類型的內存
寄存器時鐘和復位信號的設置,設置輸出端q直接輸出,不通過寄存器,設置時鐘使能端(clken)
Create one dock enable signal for each dock signal. Note: All registered ports are controlled by the enable signal(s)
是否創建一個時鐘使能信號
Create an 'adr’asynchronous dear for the registered ports
是否創建一個清零信號
存儲器初始化,并指明初始化ROM的數據文件
使用mif文件對ROM進行初始化,文件名為黑色則為添加成功,紅色則為失敗
ROM子模塊參數設置完成后,為了測試其功能,新建一個頂層文件進行功能測試。代碼如下:
module Sine_Signal(q_sig,address_sig,clock_sig,clken_sig);output [7:0] q_sig;output [7:0] address_sig;input clken_sig;input clock_sig;IPROM IPROM_inst (.address ( address_sig ),.clken ( clken_sig ),.clock ( clock_sig ),.q ( q_sig ));endmodule
注意實例化部分應該與生成的_inst.v文件內容一致。
仿真波形如下:
二、DDS信號發生器設計
DDS是直接數字式頻率合成器(Direct Digital Synthesizer)的英文縮寫,是一項關鍵的數字化技術。與傳統的頻率合成器相比,DDS具有低成本、低功耗、高分辨率和快速轉換時間等優點,廣泛使用在電信與電子儀器領域,是實現設備全數字化的一個關鍵技術。作為設計人員,我們習慣稱它為信號發生器,一般用它產生正弦、鋸齒、方波等不同波形或不同頻率的信號波形,在電子設計和測試中得到廣泛應用。
DDS的基本結構主要由相位累加器、相位調制器、波形數據表ROM、D/A轉換器等四大結構組成,其中較多設計還會在數模轉換器之后增加一個低通濾波器。
1、相位累加器設計
//=====相位累加器和數據鎖存器=====
module addr_cnt(CPi,K,ROMaddr, Address); input CPi; //系統基準時鐘(100MHz) input [12:0] K; //13位頻率控制字 output reg [9:0] ROMaddr; //10位ROM地址 output reg [16:0] Address; //17位相位累加器地址信號
always @(posedge CPi)
begin Address = Address + K; ROMaddr = Address[16:7];
end endmodule
2、波形存儲器ROM設計
(1)方波模塊
由于方波的實現算法相對簡單,可以不用ROM表,直接用寄存器來保存方波的輸出值。方波只有高、低電平兩種狀態,因此只需要在一個周期的中間位置翻轉電平即可。其實現原理如下:由于相位累加器的值是線性累加的,因此地址值(Address)也是線性累加的,對地址值Address進行判斷,當地址值的最高位為0時,便將存儲波形幅值的存儲器的每一位賦值為1,否則賦值為0。具體源程序如下:
//=====方波產生模塊:squwave. v ======
module squwave(CPi,RSTn, Address, Qsquare); input CPi; //系統基準時鐘(100MHz)input RSTn;//同步清零input [16:0]Address;//17位地址輸入信號output reg[11:0] Qsquare; //輸出方波信號,12位
always @(posedge CPi)
if(!RSTn) Qsquare=12'h000;//同步清零
else beginif(Address<=17'hOFFFF)Qsquare=12'hFFF;//輸出高電平else Qsquare=12'h000;//輸出低電平
end
endmodule
(2)正弦波形存儲器模塊
#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;/*建立文件名為Sine1024.mif的新文件,允許寫入數據,對文件名沒有特殊要求,但擴展名必須為.mif*/fp=fopen("'Sine1024. mif", "w+");if(NULL== fp)printf(" Can not cr eat file!r\n");else{ printf(" File created successfully!\n");/*生成文件頭,注意不要忘了“;” */fprintf(fp, "DEPTH =%od;\n",DEPTH);fprintf(fp, "WIDTH =%od;\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++) {/*周期為1024個點的正弦波*/v= sin(2*PI*n/DEPTH);/*將-1~1之間的正弦波的值擴展到0~4095之間*/temp=(int)((v+1)*4095/2);//v+1將數值平移到0~2之間/*以十六進制輸出地址和數據*/fprintf(fp, "%x\t:t%x;\n",n, temp);} fprintf(fp, "END;\n");fclose(fp);//關閉文件}
}
3、鎖相環倍頻電路設計
使用QuartusPrime軟件調用宏模塊定制一個100MHz的鎖相環模塊。其過程是:選擇Tool一MegaWizardPlug-InManager命令,啟動MegaWizard工具,選擇左欄I/O項目下的ALTPLL(嵌入式鎖相環),定制一個名稱為PLL100M_CP的時鐘模塊,該模塊的輸入inclk0為50MHz時鐘信號,輸出c0為100MHz的脈沖信號,占空比為50%,帶有相位鎖定指示輸出端locked,模塊符號如圖8.7.9所示。
4、頂層電路設計
將上述各個模塊逐個級聯起來就可以得到波形產生器的頂層模塊,其代碼如下:
//=DDS的頂層模塊:DDS top.V =
module DDS top(CLOCK 50, RSTn, WaveSel,K,WaveValue, LEDG, CLOCK 100);
input CLOCK 5O;
input RSTn;
input [1:0] WaveSel;
input [12:0] K;
output reg [1l:0] WaveValue;
wire [9:0] ROMaddr;
wire [16:0]Address;
wire[11:0]Qsine,Qquar;
output [0:0]LEDG;
output CLOCK_100;
wire CPi = CLOCK_100;
PLL100M_CP PLL100M_CP_inst (.inclk0(CLOCK 50 );
//50MHz時鐘輸入.c0(CLOCK_100),
//100MHz時鐘輸出.locked (LEDG[O])
);
addr_cnt U0_instance(CPi,K,ROMaddr, Address);
SineROM ROM_inst (.adres (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;endcase
end
endmodule
5、最終實現
使用DE2-115開發板來驗證上述設計。用析出的50MHz晶振作為時鐘輸入,用KEY3控制方波清零,用SW12~SW0設置頻率控制字,SW7、SW6用來選擇輸出波形的各類,用LEDGe作為 PLL的相位鎖定指示。為方便引腳分配,新建一個頂層文件:
module DE2_115_DDS_top(CLOCK_50,KEY,SW,GPIO_0,LEDG);input CLOCK_50;input [3:3] KEY;input [17:0] SW;output [12:0] GPIO_0;output [0:0] LEDG;wire CLOCK_100;assign GPIO_0[12]=CLOCK_100;wire RSTn=KEY[3];wire[1:0] WaveSel=SW[17:16];wire [12:0] K=SW[12:0];wire [11:0] WaveValue;assign GPIO_0[11:0]=WaveValue;DDS_top DE2(CLOCK_50,RSTn,WaveSel,K,WaveValue,LEDG,CLOCK_100);
endmodule