|
作者: Xilinx公司資深DSP專家 王宏強 Xilinx公司高級FAE 徐堅 鄧濤 Xilinx公司工具與方法學高級專家 徐天容 在數字信號處理領域,如自適應濾波、DPD系數計算、MIMO Decoder 等,常常需要矩陣解方程運算以獲得其系數,因此需對矩陣進行求逆運算。然而,由于直接對矩陣求逆會導致龐大的運算量,所以在實際工程中往往需要先將矩陣分解成幾個特殊矩陣(正規正交矩陣或上、下三角矩陣以求其逆矩陣需要更小的運算量)的乘積。目前,QRD矩陣分解法是求一般矩陣全部特征值的最有效且廣泛應用的方法之一。它是將矩陣分解成一個正規正交矩陣Q與上三角形矩陣R,稱為QRD矩陣分解。 由于浮點具有更大的數據動態范圍,所以在眾多多算法中具有只需要一種數據類型的優勢,所以很多QRD矩陣分解是基于浮點數據類型的。不過在通信應用中,更多的場景還是復數類型。因此,隨著通信技術的日益發展,算法的復雜度越來越高,QRD矩陣的維度也越來越大。如果是用傳統的手寫RTL,浮點復數超大維度QRD矩陣分解的FPGA實現將變得非常復雜,需要很長的時間來編寫RTL代碼、仿真和進行驗證等工作,使得開發效率不是很高。 本文將介紹如何使用Xilinx Vivado HLS(Vivado 高層次綜合)工具實現浮點復數QRD矩陣分解并提升開發效率。使用Vivado HLS可以快速、高效地基于FPGA實現各種矩陣分解算法,降低開發者對算法FPGA的實現難度。其中包括:
Vivado HLS工具介紹 Vivado HLS 是Xilinx公司2010 年收購 AutoESL 以后重新打造的高層次綜合工具,它可以讓用戶通過添加適當的directives(制導語句) 和 constrains(約束), 將其C/C++/System C代碼直接轉換成 FPGA RTL( Verilog, VHDL, System C )代碼。讓用戶可以在算法開發環境而非通常的硬件開發環境中只需專注于算法規格和算法的 C 實現,Vivado HLS 工具會自動考慮 FPGA 微觀實現架構,并可生成可綜合的FPGA RTL代碼。如圖1所示。
圖1 Vivado HLS設計介紹 Vivado HLS FPGA設計流程: 首先用 C/C++/System C 將算法實現,并編寫 C testbench 驗證 C 的功能,確保其功能正確; 然后就可以通過 Vivado HLS 工具進行 C 綜合,將 C 轉換成 RTL; 接著做 C/RTL 的協同仿真(Co-simulation),以保證生成的RTL代碼功能與C的功能完全一致。 最后 Vivado HLS 生成的 RTL 代碼可直接用于 Xilinx 設計開發環境下做系統集成、仿真和生成bit文件。 如圖2所示。
圖2 Vivado HLS設計流程 QRD矩陣分解算法 QRD矩陣分解是將一個矩陣A分解成Q與R相乘, A=QR 其中R是個上三角矩陣, Q是個正交矩陣, QTQ = 1 QT 是Q的轉置共軛矩陣 QRD矩陣分解 Vivado HLS 實現C++代碼構架 QRD矩陣分解C++實現代碼的頂層模塊是qrd_engine.cpp, 它調用cal_core.cpp(核心計算函數), coef_cal.cpp(系數計算函數),以及浮點加、減、乘、除法等子函數來實現。 struct cf_t { float re; float im; }; void qrd_engine ( cf_t in_u[(R_DIM+Y_DIM)/DIV_NUM][DIV_NUM], cf_t pd_err_in, float lamda, float lamda_sqrt, float diag[R_DIM], cf_t r[R_DIM][X_DIM], cf_t p[R_DIM] ) { #pragma HLS ARRAY_PARTITION variable=in_u complete dim=2 #pragma HLS ARRAY_PARTITION variable=r complete dim=2 //注:這里對數組加入完全分割directive,目的是提高數據的并行帶寬,從而獲得并行計算。 #pragma HLS RESOURCE variable=in_u core=RAM3S //注:這里對數組加入RAM3S directive,目的是控制生成的BRAM的延遲。 for (i=0;i #pragma HLS PIPELINE II =124 #pragma HLS latency max = 123 //注:這里加入II(interval是指下一個新的可以輸入QRD模塊的數據與前一個數據之間所間隔的時鐘周期數)及latency directives (延遲制導語句),目的是控制這個模塊在指定的延遲節拍范圍內完成所有的計算。 coef_calc(lamda_sqrt,lamda, cal_core(u_tmp, r_tmp, s_n, i, j, k, c_o, lamda_sqrtxc_o, lamda_sqrtxs_o, s_conj_o, &in_u_w2, &r【i][r_addr]); } } void coef_calc ( float lamda_sqrt, float lamda, float r_diag, cf_t u_diag, cf_t pd_err_in, cf_t *s, cf_t *s_conj, cf_t *lamda_sqrtxs, float *c, float *lamda_sqrtxc, float *diag, cf_t *p_o, cf_t *pd_err ) void calc_core ( cf_t in_u, cf_t r, int s_n, int i, unsigned char j, unsigned char k, float c_o, float lamda_sqrtxc_o, cf_t lamda_sqrtxs_o, cf_t s_conj_o, cf_t* u_ret, cf_t* r_ret ) QRD矩陣分解的Vivado HLS設計實現與優化 C模塊劃分 Vivado HLS生成的RTL代碼在默認情況下保留原有c代碼的層次結構。在構建c代碼層次時,可以采用由上至下、自下而上相結合的模塊劃分方式。 將單精度浮點基本運算如加、減、乘、除、平方根等寫成最底層的子函數,以便添加directives來指導工具優化方向。 float hfmult ( float in1, float in2 ) { #pragma HLS PIPELINE // 注:將hfmult函數流水化設計,以獲得更好的時序性能 #pragma HLS RESOURCE variable=return core=FMul_fulldsp latency=9 // 注:制定hfmult的延遲為9個時鐘周期,以便hfmult內部實現有充分的時鐘節拍流水,特別是有足夠的時鐘節拍分配給DSP48內部做流水處理 float out; out = in1 * in2; } 類似的設計方法及思路也同樣適用于浮點加,減等運算。 提高C轉換成FPGA RTL 實現的并行度 由于計算時間、速度的要求,往往需要提高運算的并行度,需要對qrd_engine中in_u及r數組(HLS綜合成FPGA的BRAM或分布式RAM)加數組分割directive,這樣數據才可以并行進入后面的并行處理單元。 #pragma HLS ARRAY_PARTITION variable=in_u complete dim=2 #pragma HLS ARRAY_PARTITION variable=r complete dim=2 對數組memory地址讀寫設計優化 C語言中的數組通常會被綜合成FPGA的存儲memory,這樣就會有地址的讀寫。但為了達到更好的時序性能,可以盡量減少對memory的讀寫,從而簡化所生成RTL代碼中的mux。 寫法1,定義成,in_u[i/DIV_UNM][i%DIV_NUM],對下面的運算,將產生兩次memory讀寫。 calc_coef(lamda_sqrt, in_u[i/DIV_UNM][i%DIV_NUM], …); for(j=0; j in_u_read = in_u[u_addr][k]; } 寫法2,定義成變量in_u_pre, 通過 if(u_addr == (i+1)/DIV_NUM && k==(i+1)%DIV_NUM)判斷來實現同樣的功能,這樣只需要一次memory讀寫,從而獲得更好的II性能,當然也提升了時序性能。 calc_coef(lamda_sqrt, in_u_pre, …); for(j=0; j in_u_read = in_u[u_addr][k]; if(u_addr == (i+1)/DIV_NUM && ==(i+1)%DIV_NUM) in_u_pre = in_u_read; } 有時為了使in_u綜合出的RAM時序更好,也可以對in_u綜合的RAM加resource directive來控制其stage, 比如為3 stage,這樣生成的RAM, 在輸入,輸出都會打一拍register。 #pragma HLS RESOURCE variable=in_u core=RAM3S 浮點運算加法級聯與加法樹 由于浮點運算的精度與其運算的先后順序有直接關系,在Vivado HLS中,為了保證生成的RTL代碼與C中的精度一樣, Viado-HLS一般不會改變代碼中中浮點運算的順序。有時為降低浮點運算的latency,使用并行加法樹將比串行級聯加法來獲得更有效。 浮點串行加法級聯,這樣寫法是有嚴格先后順序的,Vivado HLS將其生成串行實現的RTL代碼,其latency為35個時鐘周期。 void fp_adder_cascade (float *r, float a, float b, float c, float d) { #pragma HLS PIPELINE *r = a + b + c + d; } 如果用戶確定上面的浮點加順序調整對精度的影響在誤差范圍內, 那么可以采用并行加法樹,這樣 Vivado HLS 將其生成并行加法RTL代碼實現,其latency為降為23個時鐘周期。 void fp_adder_tree (float *r, float a, float b, float c, float d) { #pragma HLS PIPELINE float e, f; e = a + b; f = c + d; *r = e + f; } 對浮點乘法使用的DSP48的優化 同樣我們也可以通過設置充足的latency directive給DSP48,這樣有足夠的時鐘節拍給到DSP48內部打拍register。 如果對上述的單精度浮點乘法hfmult latency設置為3,這樣分配到每個DSP48 內部只有2級latency, 那么綜合總合出來代碼DSP48內部的A_reg 或 P_reg不會打一拍, 這樣將會大大降低時序性能。 derive_core fmul_der -base FMul_maxdsp -latency 3 -fixed set_directive_resource -core fmul_der hfmult out
或者:
為了達到較好的時序性能,對上述的單精度浮點乘法hfmult latency至少設置為4,這樣分配到每個DSP48 內部只有3級latency, 那么綜合總合出來代碼DSP48內部的A_reg 或 P_reg都會各打一拍。 derive_core fmul_der -base FMul_maxdsp -latency 4 -fixed set_directive_resource -core fmul_der hfmult out
對Shifter Register的優化 利用Vivado HLS 2014.4的Shift Register RTL Attribute,有5種類型SRL來控制不同的shifter register組合:srl, srl_reg, reg_srl, reg_srl_reg。 比如在Vivado HLS生成的RTL代碼中加(* srl_style “reg_srl_reg” *),將綜合成在SRL之前、之后分別用一個register來實現。如圖所示:
Vivado HLS QRD矩陣分解設計結果 本文中QRD矩陣分解的大小是128x128的單精度復數浮點,我們使用Xilinx Vivado HLS 2014.4版本,將其生成的RTL在Xilinx Virtex-7 FPGA上實現。 延遲(latency)性能 本文所述的設計,在Xilinx Vivado HLS 2014.4中達到的延遲15237個時鐘周期。 Interval(吞吐率的倒數)性能 在Xilinx Vivado HLS中,interval是指下一個新的可以輸入QRD模塊的數據與前一個數據之間所間隔的時鐘周期數,也可以理解為吞吐率的倒數。這里Interval的單位是時鐘周期。 本文所述的設計,在Xilinx Vivado HLS 2014.4 中達到的Interval是15238個時鐘周期。 時序(timing)性能 使用Xilinx Vivado HLS 2014.4在Xilinx Virtex-7器件中實現單個128x128單精度浮點復數QRD分解模塊,時鐘頻率達到了350 MHz (2.85ns)。 處理時間性能 QRD矩陣分解的處理時間就是所用的延遲x時鐘周期,所以是:15237 x 2.85 = 43.4 uS。 FPGA資源 將Xilinx Vivado HLS 2014.4生成的RTL代碼,在Vivado 2014.4中綜合,實現,使用的資源如下表。這個資源使用率只占Xilinx Virtex-7 7V485T的4%,使用Vivado HLS 2014.4高效地實現了QRD矩陣分解。 Slice 4048 LUT 11882 FF 12495 DSP 109 BRAM 0 SRL 314 總結 使用Xilinx Vivado HLS工具,可以讓算法工程師、FPGA工程師及軟件工程師快速基于FPGA實現典型的數字信號處理算法。本文以浮點復數QRD矩陣分解為例,介紹了如何使用 Vivado HLS 快速、高效地基于FPGA實現,并降低開發者對算法的FPGA實現難度。 Vivado HLS可以幫助設計者大幅提升生產力。 使用 Vivado HLS 開發效率比手寫RTL實現快5-10倍,其FPGA資源效率與手寫RTL接近,且C/C++仿真驗證比傳統FPGA RTL要快100倍。。 |