這是數位訊號處理系列的第一篇,以簡單的數字混頻為例,介紹在FPGA程序設計中很重要的二進位原碼、補碼;有符號數、無符號數的問題。本文不是像課本那樣介紹這些基礎概念,而是介紹很實際的設計方法。
藉助於數字混頻這個設計,本文還會介紹用途非常廣泛的Altera公司Quartus中的NCO IP核、Xilinx公司Vivado中的DDS Compiler IP核的具體使用方法。
混頻就是把兩個不同的頻率信號混合,得到第三個頻率。在模擬電路中經常見到的就是把接收機接收到的高頻信號,經過混頻變成中頻信號,再進行中頻放大,以提高接收機的靈敏度。
數字電路中最簡單的混頻便是兩個信號做乘法,可以得到它們的和頻信號與差頻信號。數字混頻在通信的調製、解調、DUC(數字上變頻)、DDC(數字下變頻)等系統中應用廣泛。通常把其中一個信號稱為本振信號(local oscillator),另一個信號稱為混頻器的輸入信號。
本文的程序設計參考自杜勇老師的書《數字濾波器的MATLAB與FPGA實現-Altera/Verilog版》,對其中部分設計細節做了修改。杜勇老師的這個系列共有三本書,很推薦大家購買學習。不過可能由於篇幅有限,杜勇老師在書中對設計的一些細節和思想沒有做詳細介紹,博主在本文中和大家討論討論。
程序設計系統時鐘5MHz,625kHz的輸入信號與625kHz的本振信號做混頻,根據混頻原理會得到1.25MHz的和頻信號與0Hz(直流),將直流濾除掉得到1.25MHz的有效信號。
設計的頂層模塊接口如下所示:
module Mixer( input clk, input rst_n, input [9:0] din, output [9:0] s_oc, output out_valid, output [19:0] dout );
程序中首先生成本振信號。Quartus和Vivado中都提供了類似功能的IP核:Vivado中叫DDS(Direct Digital Synthesizers)Compiler;Quartus中叫NCO(Numerically controlled oscillators)。下面以實例化NCO為例,具體的設計方法在下文講解。
wire [9:0] oc_sin;oc oc( .phi_inc_i (16'd8192), .clk (clk), .reset_n (rst_n), .clken (1'b1), .fsin_o (oc_sin), .out_valid (out_valid) );
接下來用乘法進行混頻。我們都知道計算機中有帶符號數signed和無符號數unsigned,還知道計算機經常以二進位補碼的形式的表示帶符號數。
在FPGA設計中,不管是Altera還是Xilinx,它們的IP核幾乎都是採用二進位補碼帶符號數,也有很多的ADC、DAC晶片的數據接口也採用的是二進位補碼。因此,在設計中,我們要清楚什麼時候用什麼數值表示法。
比如NCO的輸出為帶符號數二進位補碼,假設混頻的輸入信號也是帶符號數二進位補碼,則在整個混頻程序設計中都要保持這個數值表示方法,否則就會出錯。
在下面的方法1中,再定義一個帶符號的寄存器將輸入信號轉換為帶符號數是很有必要的:「wire signed [9:0] din_s = din;」。如果不這樣做,直接使用乘法運算符「*」,會被綜合為無符號數乘法,得到的就是錯誤的結果。
當然也可以用方法2,乘法器IP核可以選擇計算方式是「signed」還是「unsigned」,將乘法器設置為signed也可以完成正確的計算。
wire signed [19:0] mult; mult1 mult1_inst ( .clock ( clk ), .dataa ( din ), .datab ( oc_sin ), .result ( mult ) );
接下來濾除混頻後的直流信號。由於乘法的運算結果為帶符號數,接下來的計算使用到的寄存器都應申明為signed。
需要強調的是,signed和unsigned的申明只是告訴設計的運算如何看待這個寄存器中的數,並不能改變寄存器的值。比如「11」這個值,如果申明為signed,運算將其視作-1,如果申明為unsigned,運算將其視作3。因為有符號數和無符號數的運算法則是不一樣的,所以錯誤的申明會導致結果計算錯誤。
reg signed [19:0] m1,m2,m3,m4,m5,m6,m7;// 5MHz/625kHz=8; 緩存連續8個點的值always @ (posedge clk or negedge rst_n) if (!rst_n) begin m1 <= 20'd0; m2 <= 20'd0; m3 <= 20'd0; m4 <= 20'd0; m5 <= 20'd0; m6 <= 20'd0; m7 <= 20'd0; end else begin m1 <= mult; m2 <= m1; m3 <= m2; m4 <= m3; m5 <= m4; m6 <= m5; m7 <= m6; endwire signed [22:0] madd = mult+m1+m2+m3+m4+m5+m6+m7; //一個周期wire signed [19:0] mean = madd[22:3]; //舍掉低3bit,相當於除8,得到wire signed [19:0] mt = mult - mean; //濾除直流分量assign dout = mt;assign s_oc = oc_sin; endmodule
上面濾除直流分量的方法並不通用,由於5Mhz的系統時鐘是625kHz信號的8倍,所以連續8個點的平均值便是直流分量。不過程序設計思路還是可以學習,比如依次移位緩存數據、截高位做除法這些小技巧。
完整的Quartus工程(含testbench仿真)可以在這裡下載:https://download.csdn.net/download/fpgadesigner/10447557
該testbench的編寫方法在「Testbench編寫指南(一)文件的讀寫操作」中: https://blog.csdn.net/fpgadesigner/article/details/80470972
在Quartus中打開該IP核,配置界面如下。後面的Quartus版本中將IP核集成到了qsys中,配置界面略有不同,但設置的參數之類的基本一樣。
設定好相位累加器精度、角度解析度、幅度精度、系統時鐘和輸出信號頻率,便可以得到一個相位增量值(phase increment value),在實例化NCO模塊時傳入的便是這個值。實際的輸出頻率和設定的頻率會存在一定誤差,下方便展示了輸出信號的頻域和時域圖形。總體來說設置比較簡單。
如果需要仿真,在生成IP核前一定要在「Set up simulation」中選中「Generate Simulation Model」和「Generate netlist」,如下圖所示。否則在導入ModelSim時會失敗,無法進行仿真。
在Vivado中打開DDS Compiler IP核,配置界面如下:
這個IP核的配置選項更豐富,提供的功能也更強大。我這裡只介紹下本設計用到的功能,其餘具體信息可以參考Xilinx官方文檔pg141。
同樣設定系統時鐘,Parameter Selection選擇「System Parameters」,這種設計方式可以直接設置無雜散動態範圍、頻率解析度、輸出頻率等系統級的參數,和Quartus的NCO IP核很像。另外一種「Hardware Parameters」設計方式需要自己設定輸出數據和相移的位寬,輸出頻率、相位偏移等值需要自己計算對應的二進位數值。這兩種設計方式向不同需求的設計者提供。
pg141中給出了總線位寬與系統參數之間的轉換關係公式。
相位增量和相位偏移都可以設置為可編程的「Programmable」和「Streaming」方式,本設計只需要產生625kHz固定頻率的本振信號,設置為「Fixed」即可(所需資源少)。在「Summary」中可以看到整個DDS系統的詳細信息。
需要提醒的是系統最終的實際信號位寬和總線接口位寬並不一致。IP核的位寬只會是8的倍數,多餘的位數會移符號為填充,如下圖所示。
更直觀的感受,看一個DDS Compiler IP核的仿真:
可以看到相移雖然有16bit的位寬,但是有效的只有低10bit,高位都是符號為。為了更好的觀察相位值,新建一個「virtual bus「,將低10bit加到bus中,如下圖所示:
可以清楚的看到相位和幅度之間的關係。
很多系統中需要sin和cos信號(如解調系統中的本振信號),在DDS中設置為「Sine and Cosine」輸出時,sin和cos信號會共用數據總線,sin使用高字節,cos使用低字節,格式如下:
如果需要生成帶有可調初始相位(也叫相位偏移Phase Offset)的信號,在DDS中將「Phase Offset Programmability」設置為「Streaming」,IP核埠會增加一個PHASE輸入通道,該通道數據總線的有效位寬與設置的頻率解析度(Frequency Resolution)有關,可以在Summary界面中看到位寬(Phase Width)。該數據總線與360°相位之間線性對應。比如Phase Width為16Bits,則0對應0°,FFFF對應360°,7FFF對應180°,以此類推。
NCO和DDS是經常用到的IP核,在後面的「FPGA數位訊號處理「系列介紹的其它系統中,也會經常出現,因此需要熟悉掌握這兩個IP核的使用。