729
技術社區[雲棲]
ZED Board從入門到精通係列(八)——Vivado HLS實現矩陣相乘
終於到了HLS部分。HLS是High Level Synthesis的縮寫,是一種可以將高級程序設計語言C,C++,SystemC綜合為RTL代碼的工具。
生產力的發展推動了設計模式。在電子技術初級階段,人們關注的是RLC電路,通過建立微分方程求解電路響應。門級電路是對RLC的初步封裝,人們進而采用布爾代數、卡諾圖進行電路設計與分析。之後隨著集成電路進一步發展,門電路可以集成為寄存器、觸發器、ROM等宏單元,設計工具也變得更為高度模塊化。算法級別的電路設計,則一直沒有特別好的工具,直到出現了HLS。HLS可以將算法直接映射為RTL電路,實現了高層次綜合。從這個層麵上講,System Generator也是一種高層次綜合工具,因為它將matlab算法描述綜合為RTL代碼。如果今後機器學習、人工智能獲得重大突破,或許會出現將人類自然語言綜合為RTL代碼的工具,不知我們是否能見證它的麵世。
HLS的學習資源可以參考https://xilinx.eetrend.com/article/5096。本節給出較為通用的矩陣與向量相乘例子,從全串行到全並行進行了一步步優化實現。
矩陣實驗室Matlab是比較常用的數學仿真軟件。本博主用的是R2013a版本。為了驗證矩陣向量相乘正確性,我們先用matlab生成測試矩陣和向量,並利用matlab計算結果。代碼如下:
clear; clc; close all; N = 5; A = randi([1,100],N,N); b = randi(100,N,1); c = A*b; KKK_SaveToCHeaderFile(A,'A.h'); KKK_SaveToCHeaderFile(b,'b.h'); KKK_SaveToCHeaderFile(c,'c.h');
這裏給出的是A*b = c的簡單例子,A為5X5矩陣,b為5X1向量,結果c為5X1向量。其中KKK_SaveToCHeaderFile()是將矩陣、向量保存為C語言數組的子函數,定義如下:
function [] = KKK_SaveToCHeaderFile(var,fn) fid = fopen(fn,'w'); var = reshape(var.',1,[]); fprintf(fid,'%d,\r\n',var); fclose(fid);
給出測試例程中,A如下:
82 10 16 15 66 91 28 98 43 4 13 55 96 92 85 92 96 49 80 94 64 97 81 96 68
b如下:
76 75 40 66 18
得到的c如下:
9800
15846
16555
23124
22939
運行matlab腳本之後,生成三個文件:A.h,b.h,c.h,這些是作為HLS程序的輸入數據和參考結果。下麵我們用HLS工具實現上述矩陣X向量的功能。第一步,運行Vivado HLS。
選擇第一項,Create New Project,建立新工程MatrixMultiply
輸入路徑和工程名之後,點Next。
添加頂層模塊文件。這裏我們Top Functions輸入MatrixMultiply,然後New File...,新建一個.c文件,命名為MatrixMultiply.c(後綴不要省略!),然後點Next
添加頂層文件測試腳本。這裏New一個文件TestMatrixMultiply.c(後綴不要省略!),然後Add前麵用Matlab生成的A.h,b.h,c.h,如下圖所示:
點Next,選擇解決方案配置,如下圖所示
其餘保持默認,隻修改Part Selection部分,改為ZedBoard。改完後,Finish即可進入主界麵,如下圖所示
可以看出,Vivado HLS界麵很像很像Xilinx SDK,不同的是前者負責PL部分開發,後者負責PS軟件編寫,定位不同決定了二者今後的路必然走向分歧。
將MatrixMultiply.c內容改為:
typedef int data_type; #define N 5 void MatrixMultiply(data_type AA[N*N],data_type bb[N],data_type cc[N]) { int i,j; for(i = 0;i<N;i++) { data_type sum = 0; for(j = 0;j<N;j++) { sum += AA[i*N+j]*bb[j]; } cc[i] = sum; } }
將TestMatrixMultiply.c內容改為:
<p>#include <stdio.h> typedef int data_type; #define N 5</p><p>const data_type MatrixA[] = { #include "A.h" }; const data_type Vector_b[] = { #include "b.h" }; const data_type MatlabResult_c[] = { #include "c.h" };</p><p>data_type HLS_Result_c[N] = {0}; void CheckResult(data_type * matlab_result,data_type * your_result); int main(void) { printf("Checking Results:\r\n"); MatrixMultiply(MatrixA,Vector_b,HLS_Result_c); CheckResult(MatlabResult_c,HLS_Result_c); return 0; } void CheckResult(data_type * matlab_result,data_type * your_result) { int i; for(i = 0;i<N;i++) { printf("Idx %d: Error = %d \r\n",i,matlab_result[i]-your_result[i]); } } </p>
首先進行C語言仿真驗證,點這個按鈕:
結果如下:
從C仿真輸出看到,仿真結果與matlab計算結果一致,說明我們編寫的C程序MatrixMultiply是正確的。
接下來進行綜合,按C仿真後麵那個三角形按鈕,得到結果如下:
注意到,計算延遲為186個時鍾周期。這是未經過優化的版本,記為版本1。
為了提高FPGA並行計算性能,我們接下來對它進行優化。
打開MatrixMultiply.c,點Directives頁麵,可以看到我們可以優化的對象。
注意到矩陣和向量相乘是雙層for循環結構。我們先展開最內層for循環,步驟如下:
右鍵點擊最內側循環,右鍵,然後Insert Directive...
彈出對話框如下,Directives選擇UNROLL,OK即可,後麵所有都保持默認。
再次綜合後,結果如下
可見效果非常明顯,延遲縮短到51個時鍾周期。
用同樣方法,展開外層循環,綜合後結果如下:
計算延遲又降低了1/3!!!
可是代價呢?細心的你可能發現占用資源情況發生了較大變化,DSP48E1由最初的4個變為8個後來又成為76個!!!
FPGA設計中,延遲的降低,即速度提高,必然會導致麵積的增大!
循環展開是優化的一個角度,另一個角度是從資源出發進行優化。我們打開Analysis視圖,如下所示:
從分析視圖可以看出各個模塊的運行順序,從而為優化提供更為明確的指引。我們發現AA_load導致了延遲,如果所有AA的值都能一次性並行取出,勢必會加快計算效率!
回到Synthetic視圖,為AA增加Directives:
選擇Resources,再點Cores後麵的方框,進入Vivado HLS core選擇對話框
按上圖進行選擇。使用ROM是因為在計算矩陣和向量相乘時,AA為常數。確認。
仍然選擇AA,增加Directives,如下圖:
選擇數組分解,mode選擇完全complete,綜合後結果如下圖:
延遲進一步降低,已經降到11個時鍾周期了!!!是否已經達到極限了呢???
答案是否定的。我們進入Analysis視圖,看一下還有哪些地方可以優化的。經過對比發現bb也需要分解,於是按照上麵的方法對bb進行資源優化,也用ROM-2P類型,也做全分解,再次綜合,結果如下:
發現延遲進一步降低到8個時鍾周期了!!!
老師,能不能再給力點?
可以的!!!!
我們進入分析視圖,發現cc這個回寫的步驟阻塞了整體流程,於是我們將cc也進行上述資源優化,隻不過資源類型要變為RAM_2P,因為它是需要寫入的。綜合結果:
整體延遲已經降低到6個clk周期了!!!
再看Analysis視圖:
延遲已經被壓縮到極限了。。。。
老師,還能再給力點嘛?
答案是可以的!!!!
我們前麵的所有運算都是基於整形數int,如果將數值精度降低,將大大節省資源。
注意現在DSP48E1需要100個!
看我們如何將資源再降下來。這就需要借助“任意精度”數據類型了。
HLS中除了C中定義的char,shrot,int,long,long long 之外,還有任意bit長度的int類型。我們將代碼開頭的data_type定義改為:
#include <ap_cint.h> typedef uint15 data_type;
由於matlab生成的隨機數在1~100以內,乘積範圍不會超過10000,於是取15bit就能滿足要求。
首先驗證下結果的正確性,用C Simulation試一下。結果如下:
看來結果是正確的(當然也不排除數位不夠,溢出後的結果相減也是0,需要你自己決定數值位寬)
綜合一下,結果如下:
延遲縮短了一半,DSP48E1減少到原來的1/4!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
我和我的小夥伴們都震驚了!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
再看Analysis視圖
可以發現我們的資源利用率已經達到極致,時序已經壓縮到無以複加,實現了全並行計算,係統時鍾完全可以達到100MHz,延遲僅3CLK,約30ns,相比matlab,得到約數百倍加速(matlab進行矩陣——向量相乘時采用浮點計算)。
通過本文實驗,可以發現利用Vivado HLS實現從最初的C串行實現到全並行實現的步步優化,總結一下優化步驟:
(1)粗優化(循環展開、子函數內聯)
(2)訪存優化(塊存儲分散化、多端口存取)
(3)精優化(數值位寬優化、流水線優化)
(4)總線化(利用AXI4、AXI-Stream總線接口,降低整體訪存需求)
利用HLS可以將原來的C算法快速部署到FPGA上,減少直接進行硬件編程的工作量。在很多情況下,優化手段可以和CUDA進行類比,相互借鑒。CUDA其實更接近軟件接口,而HLS更接近硬件編程接口,或許今後兩者會在新的層次上融合為統一架構語言。
最後更新:2017-04-03 12:54:51