閱讀729 返回首頁    go 技術社區[雲棲]


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

  上一篇:go 博客搬到CSDN了,以後就老實的呆在這兒吧~~
  下一篇:go UML之類圖