:FPGA IP核配置與仿真全解析)
寫多通道FIR濾波器很多人一開始就被“多通道”三個字嚇住了。其實說穿了就是把好幾路信號同時丟進同一個濾波器讓它們在同一個FPGA工程里各自完成濾波任務。我在剛開始用Xilinx FIR IP核的時候也是抱著數(shù)據(jù)手冊啃了三天最后發(fā)現(xiàn)真正難的不是IP核本身而是對通道、時鐘、數(shù)據(jù)排列的理解。這篇文章就按我自己的上手路徑來寫從需求分析講到參數(shù)配置再到仿真驗證和常見坑位排查盡量讓一個之前完全沒碰過FIR IP核的讀者也能把多通道濾波器跑起來。1. 動手之前先把多通道FIR的賬算清楚1.1 多通道FIR要解決什么問題FIR濾波器就是有限脈沖響應濾波器它對輸入信號做加權求和每個輸出點等于最近N個輸入點與N個濾波器系數(shù)的乘積之和。這個N就是抽頭數(shù)也叫Taps。FIR因為線性相位特性好、結(jié)構穩(wěn)定在通信、音頻、雷達、生物電信號處理里幾乎是繞不開的基礎模塊。那“多通道”是什么場景呢。最典型的是多通道音頻比如一個48kHz采樣率的音頻系統(tǒng)同時處理8路甚至32路聲道每路都要過均衡或分頻濾波器。還有多通道振動監(jiān)測幾十個加速度傳感器同時采數(shù)據(jù)每個通道要實時濾除工頻干擾。雷達和聲吶里更常見幾十上百個陣元通道每個通道都要做匹配濾波或波束形成的預處理。這些場景下如果你給每個通道都例化一個獨立的FIR模塊資源很快就爆了。正確做法是復用一個濾波計算單元通過時分復用的方式挨個處理每個通道的數(shù)據(jù)這正是Xilinx FIR Compiler IP核多通道模式的核心思想。1.2 三種實現(xiàn)路線對比與選型實現(xiàn)多通道FIR大致有三條路純手寫Verilog、用FIR Compiler IP核、用Vitis HLS做高層次綜合。純手寫Verilog的優(yōu)點是可控性最強想怎么優(yōu)化就怎么優(yōu)化但缺點也明顯FIR涉及到延遲線、系數(shù)存儲、乘法累加器、多通道狀態(tài)管理寫起來繁瑣不說時序優(yōu)化全靠經(jīng)驗。多通道模式下還要自己設計通道輪詢邏輯忙活一個星期寫出來的代碼性能和穩(wěn)定性還不一定比得上IP核。用Vitis HLS的優(yōu)點是開發(fā)效率高用C/C描述濾波器算法并且很容易做定點化仿真。但HLS生成的RTL代碼在資源利用率和時序收斂上有時不如手工優(yōu)化的IP核而且多通道高吞吐場景下HLS默認生成的流水線結(jié)構也需要反復調(diào)pragma才能達到預期性能。FIR Compiler IP核則是Xilinx專門針對FIR濾波優(yōu)化過的硬核IP它內(nèi)部使用DSP48硬核資源做乘加運算支持靈活的通道數(shù)、多相結(jié)構、可配置的系數(shù)重載還能自動處理飽和與舍入。對絕大多數(shù)量產(chǎn)項目來說這是性價比最高的選擇。我自己的項目基本都選IP核只有做ASIC驗證的原型RTL才會考慮手寫FIR因為那套代碼后續(xù)要移植到其他工藝庫IP核的RTL沒法直接用。1.3 核心公式與參數(shù)預算多通道FIR設計里最關鍵的一個公式是數(shù)據(jù)率預算。假設單通道采樣率為Fs通道數(shù)為Nch那么IP核輸入端的數(shù)據(jù)率就是Nch × Fs。以48kHz采樣、32通道為例輸入數(shù)據(jù)率就是1.536MHz也就是每秒鐘要送入153.6萬個采樣點。如果你的主時鐘是100MHz那每個采樣點對應大約65個時鐘周期這個余量非常充足IP核內(nèi)部的乘加單元完全忙得過來。但如果你處理的是高速ADC數(shù)據(jù)比如250MSPS采樣率、8通道并行采集那輸入數(shù)據(jù)率就高達2GSPS100MHz主時鐘根本扛不住這時必須讓多通道數(shù)據(jù)在更高時鐘域下并行輸入。FIR Compiler里有一個概念叫硬件過采樣率Hardware Oversampling Ratio簡稱HIL公式是HIL Fclk / (Nch × Fs)它表示每處理一個通道采樣點有多少個時鐘周期可用。IP核會依據(jù)這個數(shù)值自動決定內(nèi)部是單MAC串行結(jié)構還是多MAC并行結(jié)構。HIL越大需要的DSP48越少HIL小于抽頭數(shù)時IP核會復制乘法器來保證每個周期能完成足夠的乘加運算。所以在配置IP核之前先把這個賬算清楚主時鐘、采樣率、通道數(shù)、抽頭數(shù)四個數(shù)一擺資源量級和時鐘可行性大概就有數(shù)了。這也決定了后面IP核配置界面里幾個關鍵參數(shù)該填什么。2. FIR Compiler IP核參數(shù)配置詳解2.1 濾波器類型與系數(shù)加載在Vivado里打開IP Catalog搜索“FIR Compiler”雙擊進入配置界面。第一步是選擇濾波器類型最常見的是Single Rate也就是輸入輸出速率相同適合大多數(shù)普通濾波場景。如果你的系統(tǒng)里需要改變采樣率比如先把信號從48kHz插值到96kHz那就選Interpolation如果要從96kHz抽取到48kHz選Decimation。選抽取或插值之后IP核會自動把濾波器按多相結(jié)構展開這并不需要你自己手寫任何多相分解邏輯。我記得第一次看到這個功能時還挺意外后來仔細看文檔才發(fā)現(xiàn)多相濾波在硬件上就是原型濾波器系數(shù)按相位重新分組FIR Compiler把這個工作完全自動化了。接下來是濾波器系數(shù)。配置界面里有兩種加載方式一種是直接在Coefficient Vector表格里手動填數(shù)值適合調(diào)試時隨便填幾個系數(shù)驗證通路另一種是加載外部coe文件適合從MATLAB或Python算好系數(shù)后導進來。我強烈建議用coe文件因為手填系數(shù)很容易填錯位而且后期修改濾波器指標時直接改文件重新生成IP核就行。coe文件的格式有嚴格要求我再三提醒身邊同事文件第一行通常是注釋用分號開頭然后指定radix和coefficient_width最后是coefdata后面跟一串系數(shù)系數(shù)之間用逗號分隔整個文件以分號結(jié)尾。radix支持2、10、16比如radix16就是十六進制radix10就是十進制。很多人第一次寫coe文件忘記末尾分號導致IP核加載失敗這個錯誤很低級但真的一搜一大把。2.2 多通道數(shù)據(jù)接口與時序解析多通道配置的核心參數(shù)是Number of Channels和Input Sample Frequency。你填了通道數(shù)之后IP核會自動計算輸出數(shù)據(jù)率并顯示在Summary里。很多新手看到數(shù)據(jù)接口只有一組AXI4-Stream輸入輸出就會疑惑多通道的數(shù)據(jù)怎么塞進一根總線答案就是時分復用。在相鄰兩個采樣周期之間tvalid拉高的那一段時間里每個時鐘周期送一個通道的數(shù)據(jù)按通道0、通道1、通道2……順序依次排隊進入IP核。假設配置了4通道、每通道數(shù)據(jù)位寬16bit那么tdata總線位寬就是16bit不是64bit。輸入側(cè)需要用計數(shù)器控制多路選擇器在每個采樣周期內(nèi)把4個通道的數(shù)據(jù)按順序送上總線。輸出側(cè)同理tvalid有效后會按順序依次吐出通道0到通道3的濾波結(jié)果。有的Vivado版本里Data Interface可以選Independent Bus模式那時tdata位寬會變成Nch×16bit總線上不同位段對應不同通道數(shù)據(jù)并行送入這個模式適合輸入數(shù)據(jù)率很高、要求并行輸入的場合。具體選哪種看你系統(tǒng)架構是并行還是串行采樣。時序上最容易出問題的點在于tready。AXI4-Stream協(xié)議有握手機制只有當tvalid和tready同時為高時數(shù)據(jù)才算真正被接受。FIR Compiler在內(nèi)部處理不過來時會拉低tready進行反壓如果你的上游邏輯無視tready硬往里面塞數(shù)據(jù)那就會丟數(shù)據(jù)。多通道模式下丟一個數(shù)據(jù)后果是整個通道序列從此錯位濾波結(jié)果一塌糊涂而且非常難查。所以寫外部邏輯時務必把tready納入使能條件。2.3 多相濾波與抽取插值實現(xiàn)既然聊到多相濾波就稍微展開一點。FIR濾波器每輸出一個點要做N次乘加如果先濾波再抽取M點意味著有M-1個計算結(jié)果本來就該丟掉白白浪費了計算量。多相分解的思路是把長度為N的原型濾波器系數(shù)重新排列成M個子濾波器每個子濾波器只保留每隔M個取一個的系數(shù)。濾波時輸入信號按M倍抽取分別進入對應子濾波器每個子濾波器的計算結(jié)果直接就是抽取后的輸出計算量從N次乘加降為N/M次乘加省了整整數(shù)倍。FIR Compiler在處理Decimation和Interpolation配置時內(nèi)部就是按照多相結(jié)構來例化資源。你在配置界面上看到的選項只是抽取倍數(shù)或插值倍數(shù)背后的系數(shù)重排和相位選擇全被IP核吞掉了。所以如果你在工程里看到別人說“用Vivado FIR核做多相濾波”他大概率說的就是用這種抽取/插值模式而不是自己去寫多相分解代碼。有一點要留意抽取模式下輸出數(shù)據(jù)的速率是Fs/M下游模塊的時鐘和有效信號要按這個速率來設計否則會出現(xiàn)數(shù)據(jù)斷續(xù)。2.4 數(shù)據(jù)位寬、量化與飽和機制位寬是另一個決定成敗的細節(jié)。FIR IP核有兩個輸入端位寬要設置輸入數(shù)據(jù)位寬和系數(shù)位寬。常見的配置是輸入16bit、系數(shù)16bit輸出位寬則可以自己選。理想的輸出位寬應該等于輸入位寬加系數(shù)位寬再加log2(Nch×Taps)這么一位余量這樣才不會有溢出的風險。但工程上輸出位寬往往不能無限大后面還有模塊要用這個數(shù)據(jù)。于是IP核提供了多種量化模式Full Precision、Truncate LSBs、Non Convergent Rounding、Convergent Rounding等以及飽和模式Saturate和Wraparound。Full Precision最安全輸出位寬很寬不會丟失信息但位寬會大到下游難以接受。Truncate LSBs最簡單粗暴直接砍掉低位會引入直流偏置和量化噪聲。Convergent Rounding是四舍五入到偶數(shù)統(tǒng)計特性比單純截斷好在高精度場景里更推薦。飽和模式則決定數(shù)據(jù)超出范圍時是鉗位到最大值還是按二進制自然回繞。對音頻信號回繞會產(chǎn)生刺耳的爆音所以一般選Saturate對通信基帶信號有時為了算法分析方便反而選Wraparound避免飽和引入非線性。這些參數(shù)沒有絕對的對錯完全取決于后續(xù)模塊想看到什么樣的數(shù)據(jù)。我的建議是先用Full Precision摸清真實動態(tài)范圍再根據(jù)實測波形決定是否截斷和飽和不要一上來就憑感覺設。3. 完整實操從創(chuàng)建工程到仿真波形全綠3.1 用Python生成系數(shù)并導出coe文件系數(shù)設計推薦用Python的scipy或MATLAB。我用Python比較多就以Python為例。假設需求是48kHz采樣、128階低通、截止頻率8kHz、Hamming窗那代碼大致長這樣from scipy.signal import firwin import numpy as np fs 48000.0 cutoff 8000.0 numtaps 128 b firwin(numtaps, cutoff, fsfs, windowhamming) # 歸一化到16bit有符號定點數(shù) Q 15 b_scaled np.round(b * (2**Q - 1)) b_int b_scaled.astype(np.int16) # 導出Xilinx coe文件十六進制補碼格式 with open(fir_lpf.coe, w) as f: f.write(; Xilinx FIR Compiler coefficient file\n) f.write(radix16;\n) f.write(coefficient_width16;\n) f.write(coefdata\n) for i, c in enumerate(b_int): if c 0: hex_str format(c 0xFFFF, 04X) else: hex_str format(c, 04X) if i len(b_int) - 1: f.write(hex_str ,\n) else: f.write(hex_str ;\n)注意負數(shù)要按補碼格式寫入也就是c與0xFFFF做按位與。生成完成后可以用一個簡單的正弦疊加信號去驗證系數(shù)設計是否滿足指標直接在Python里調(diào)用np.convolve對比濾波前后的頻譜即可。3.2 Vivado中配置并例化FIR Compiler在Vivado IP Catalog里搜索FIR Compiler打開后按以下步驟配置Filter Options頁里把Filter Type選為Single RateNumber of Channels填實際通道數(shù)比如4。Sample Rate填每通道的采樣頻率比如48000Hz。在Coefficient File里加載剛才生成的fir_lpf.coe確認一下抽頭數(shù)自動識別為128。如果系數(shù)文件格式有問題這里會報錯或者顯示0個系數(shù)看到這種情況先回頭檢查文件末尾分號和小數(shù)點格式。Implementation Details頁里設置輸入數(shù)據(jù)位寬16bit、有符號數(shù)系數(shù)位寬16bit量化模式選Convergent Rounding飽和模式選Saturate。輸出位寬可以暫時保持自動計算的結(jié)果。時鐘頻率默認按Vivado工程的約束自動推斷也可以手動填一個期望值比如100MHz。配置完成后點擊OK在IP Sources里選中這個IP核右鍵選擇Generate Output Products等綜合生成完成后再打開Instantiation Template就能看到標準的例化模板。多通道模式下例化代碼并不復雜我用的最多的是AXI4-Stream接口fir_compiler_0 u_fir ( .aclk (clk), .s_axis_data_tvalid (s_axis_tvalid), .s_axis_data_tready (s_axis_tready), .s_axis_data_tdata (s_axis_tdata), .m_axis_data_tvalid (m_axis_tvalid), .m_axis_data_tdata (m_axis_tdata) );如果你的IP核版本支持獨立總線模式的輸入接口接口信號會根據(jù)配置生成多組數(shù)據(jù)位段這時候例化模板的位寬定義會差很多所以每次生成完IP核之后務必先看例化模板再連線。3.3 Testbench設計與多通道激勵生成Testbench的要點是嚴格按照AXI4-Stream握手時序產(chǎn)生多通道數(shù)據(jù)。采樣周期用周期信號表示每個采樣周期內(nèi)產(chǎn)生Nch個通道數(shù)據(jù)。下面是一個4通道16bit數(shù)據(jù)的Testbench片段timescale 1ns / 1ps module tb_fir; reg clk; reg rst_n; reg [15:0] ch_data [0:3]; reg [3:0] ch_index; reg sample_pulse; wire s_axis_tvalid; wire s_axis_tready; wire [15:0] s_axis_tdata; wire m_axis_tvalid; wire [15:0] m_axis_tdata; // 采樣脈沖每100個時鐘產(chǎn)生一次 always (posedge clk) begin if (sample_counter 99) begin sample_pulse 1b1; sample_counter 0; end else begin sample_pulse 1b0; sample_counter sample_counter 1; end end // 在每個采樣脈沖后的4個時鐘依次送4個通道數(shù)據(jù) always (posedge clk or negedge rst_n) begin if (!rst_n) begin ch_index 0; end else if (sample_pulse) begin ch_index 0; end else if (s_axis_tvalid s_axis_tready) begin if (ch_index 3) ch_index 0; else ch_index ch_index 1; end end assign s_axis_tvalid (sample_pulse || ch_index ! 0) !ch_done; assign s_axis_tdata ch_data[ch_index]; endmodule這里我把每個通道的激勵預存到一個數(shù)組里實際工程中可以從ROM讀取數(shù)據(jù)文件也可以直接生成正弦波掃頻信號。仿真的第一個目標不是看頻譜而是看通路是否打通。首選驗證方法是沖激響應法給通道0送一個單位沖激其余通道送0觀察輸出波形是否和coe文件中的系數(shù)完全一致。如果輸出序列和系數(shù)一一對應說明通路沒問題。然后再給每個通道分別送沖激就能確認通道之間沒有串擾。3.4 結(jié)果對比與延遲校準FIR濾波器是有固定群延遲的。對一個N階線性相位FIR群延遲是(N-1)/2個采樣周期。用128抽頭系數(shù)時輸出相對輸入延遲63.5個采樣點這就是為什么仿真里拿輸入正弦和輸出正弦直接對比相位會對不上。多通道模式下這個延遲對每個通道都是一樣的所以通道間相對延遲是0。驗證濾波結(jié)果是否正確的標準做法是把輸入激勵數(shù)據(jù)存成txt文件在Python或MATLAB里用相同系數(shù)做一次定點仿真然后把Vivado仿真導出的輸出數(shù)據(jù)與軟件仿真的結(jié)果做逐點對比。允許的誤差只有舍入造成的1到2個LSB。如果誤差偏大先檢查IP核的量化模式是否與軟件模型一致再看看輸入數(shù)據(jù)是否無意中經(jīng)過了符號位擴展。實測中我發(fā)現(xiàn)很多對不上的情況都出在Testbench里數(shù)據(jù)位寬沒有擴展到16bit符號位就接到了tdata總線上。4. 高頻問題排查與優(yōu)化實錄4.1 多通道FIR常見問題速查表我把實際項目中踩過的坑和同行常問的問題整理成一張表方便直接按圖索驥?,F(xiàn)象可能原因排查方式處理建議輸出一直是0系數(shù)文件沒有正確加載或tvalid從未拉高檢查IP核Summary里的Taps數(shù)量仿真里看s_axis_tvalid是否按采樣周期脈沖確認coe文件路徑和格式改用手動填系數(shù)驗證通路輸出波形放大或縮小系數(shù)量化后增益偏移或截斷模式引入直流對比coe文件系數(shù)和設計系數(shù)的增益系數(shù)歸一化時留足余量輸出端做增益補償通道間數(shù)據(jù)錯位輸入數(shù)據(jù)沒有嚴格按通道順序排列用通道0單位沖激檢查輸出對應關系重寫輸入側(cè)通道輪詢邏輯重點檢查tready握手高電平飽和削波輸出位寬不夠或飽和模式選擇不當觀察輸出是否被鉗位在最大值加寬輸出位寬或改用Wraparound模式數(shù)據(jù)率上不去時鐘頻率不夠通道數(shù)太多計算HIL和抽頭數(shù)看DSP資源利用率提高主時鐘或用獨立總線模式并行輸入時序收斂失敗IP核內(nèi)部乘法器級數(shù)太長查看時序報告定位關鍵路徑在IP核配置里提高多相并行度或增加輸出流水級4.2 三個典型案例復盤第一個案例輸出全0。那次是同事誤把coe文件只寫了注釋和radix行系數(shù)數(shù)據(jù)全丟了IP核顯示Taps數(shù)量為0但配置頁面不報錯。排查了一天才發(fā)現(xiàn)是coe文件末尾分號被中文輸入法打成了全角符號。從那以后我寫了個腳本加載coe文件后立即打印系數(shù)個數(shù)和第一個系數(shù)值確認無誤再繼續(xù)。第二個案例通道錯位。現(xiàn)象是4通道輸入正弦波通道0的頻譜出現(xiàn)在通道2的輸出上。查到最后是上游邏輯在tready為低時仍然推進了通道計數(shù)器導致后續(xù)所有通道的數(shù)據(jù)整體前移。修復方法很簡單只有tvalid和tready同時有效才遞增通道計數(shù)。但這個bug隱蔽在仿真波形里很難發(fā)現(xiàn)因為波形看起來始終有數(shù)據(jù)只是對應關系不對。第三個案例時序違例。設計是64通道、128抽頭、采樣率48kHz主時鐘100MHz按計算HIL約32.5理論上一個MAC足夠。但實際綜合后DSP使用量非常大查了IP核配置才發(fā)現(xiàn)時鐘頻率誤填成500MHzIP核按照HIL6.5自動復制了大量MAC單元浪費了資源。把時鐘頻率改成真實的100MHz并重新生成IP核后DSP使用量下降接近5倍。4.3 性能優(yōu)化與資源權衡多通道FIR的性能優(yōu)化歸根結(jié)底是HIL和DSP資源之間的權衡。如果HIL遠大于抽頭數(shù)IP核會串行復用乘加器資源開銷小如果HIL小于等于抽頭數(shù)IP核必須并行展開乘加器DSP開銷直線上升。設計階段我會先用公式粗算一遍再在IP核配置頁面里看Resource Utilization預估兩者交叉驗證。對超多通道場景還有幾個進階優(yōu)化思路。一是把多通道數(shù)據(jù)拆成兩路并行分別送兩個FIR Compiler實例每個實例處理一半通道相當于用面積換吞吐。二是利用多相抽取結(jié)構降低有效數(shù)據(jù)率在濾波前先做CIC抽取把帶寬降下來再進FIR精濾這在通信中頻信號處理里很常見。三是在FPGA里用多個時鐘域ADC采集端用高速率時鐘域FIR核心用中速率時鐘域中間用異步FIFO橋接這樣能把通道數(shù)和數(shù)據(jù)率解耦。另外提醒一句Vivado的IP核在不同版本之間的接口時序和配置界面偶爾有細微差異。網(wǎng)上經(jīng)常搜到xilinx sdk 2015.4之類的老版本教程很多配置路徑在現(xiàn)在的新版本里已經(jīng)變了。遇到界面對不上的情況直接用當前版本的IP核數(shù)據(jù)手冊和例化模板為準不要硬套老界面截圖。老版本工程遷移到新版Vivado時FIR Compiler IP核一般會自動升級但升級后務必重新跑一遍仿真對比輸出比特流確認系數(shù)加載和時序沒有變化。5. 最后再聊一點個人經(jīng)驗多通道FIR設計做到最后真正的難點已經(jīng)不在FIR本身了。IP核把算法細節(jié)封得嚴嚴實實你需要擔心的反而是數(shù)據(jù)怎么按時鐘編排、握手怎么處理、位寬怎么截斷。我個人最大的體會是上手FIR Compiler的第一步不是打開Vivado而是先在紙上把通道數(shù)、采樣率、主時鐘、抽頭數(shù)這四個數(shù)寫清楚然后算HIL看資源預估。這一步省下的調(diào)試時間遠比想象中多。調(diào)試時也建議先做最簡單的單通道沖激響應驗證再逐步擴展到多通道。很多人一上來就填滿所有通道跑復雜信號波形一旦不對根本無從定位。把主機邏輯和IP核解耦測試確保每一步通路都對了再接起來這個習慣能幫你少走很多彎路。如果你現(xiàn)在正被某個多通道FIR的問題折磨回過頭去檢查通道輪詢邏輯和tready握手大概率問題就出在那里。