天天看點

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

http://www.tuicool.com/articles/eQ7nEn

最終到了HLS部分。HLS是High Level Synthesis的縮寫,是一種能夠将進階程式設計語言C,C++。SystemC綜合為RTL代碼的工具。

生産力的發展推動了設計模式。在電子技術0基礎階段,人們關注的是RLC電路。通過建立微分方程求解電路響應。

門級電路是對RLC的初步封裝,人們進而採用布爾代數、卡諾圖進行電路設計與分析。之後随着內建電路進一步發展。門電路能夠內建為寄存器、觸發器、ROM等宏單元。設計工具也變得更為高度子產品化。算法級别的電路設計,則一直沒有特别好的工具,直到出現了HLS。

HLS能夠将算法直接映射為RTL電路,實作了高層次綜合。從這個層面上講,System Generator也是一種高層次綜合工具。由于它将matlab算法描寫叙述綜合為RTL代碼。假設今後機器學習、人工智能獲得重大突破,也許會出現将人類自然語言綜合為RTL代碼的工具,不知我們能否見證它的面世。

HLS的學習資源能夠參考 ​​http://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      
76
75
40
66
18      

9800 

15846 

16555 

23124 

22939 

執行matlab腳本之後,生成三個檔案:A.h。b.h。c.h,這些是作為HLS程式的輸入資料和參考結果。以下我們用HLS工具實作上述矩陣X向量的功能。第一步,執行Vivado HLS。

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

選擇第一項,Create New Project,建立新projectMatrixMultiply

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

輸入路徑和project名之後,點Next。

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

加入頂層子產品檔案。這裡我們Top Functions輸入MatrixMultiply,然後New File...,建立一個.c檔案。命名為MatrixMultiply.c(字尾不要省略!

),然後點Next

加入頂層檔案測試腳本。這裡New一個檔案TestMatrixMultiply.c(字尾不要省略!

),然後Add前面用Matlab生成的A.h。b.h,c.h,例如以下圖所看到的:

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

點Next,選擇解決方式配置,例如以下圖所看到的

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

其餘保持預設,僅僅改動Part Selection部分,改為ZedBoard。

改完後。Finish就可以進入主界面,例如以下圖所看到的

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

能夠看出。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内容改為:

#include <stdio.h> typedef int data_type; #define N 5

const data_type MatrixA[] = { #include "A.h" }; const data_type Vector_b[] = { #include "b.h" }; const data_type MatlabResult_c[] = { #include "c.h" };

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]);  } }

首先進行C語言仿真驗證,點這個button:

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

結果例如以下:

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

從C仿真輸出看到,仿真結果與matlab計算結果一緻,說明我們編寫的C程式MatrixMultiply是正确的。

接下來進行綜合。按C仿真後面那個三角形button,得到結果例如以下:

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

注意到,計算延遲為186個時鐘周期。

這是未經過優化的版本号,記為版本号1。

為了提高FPGA并行計算性能,我們接下來對它進行優化。

打開MatrixMultiply.c,點Directives頁面,能夠看到我們能夠優化的對象。

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

注意到矩陣和向量相乘是雙層for循環結構。我們先展開最内層for循環,過程例如以下:

右鍵點選最内側循環,右鍵,然後Insert Directive...

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

彈出對話框例如以下,Directives選擇UNROLL,OK就可以。後面全部都保持預設。

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

再次綜合後,結果例如以下

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

可見效果很明顯,延遲縮短到51個時鐘周期。

用相同方法,展開外層循環。綜合後結果例如以下:

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

計算延遲又減少了1/3。!

但是代價呢?細心的你可能發現占用資源情況發生了較大變化,DSP48E1由最初的4個變為8個後來又成為76個。!

FPGA設計中,延遲的減少,即速度提高,必定會導緻面積的增大!

循環展開是優化的一個角度,還有一個角度是從資源出發進行優化。

我們打開Analysis視圖。例如以下所看到的:

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

從分析視圖能夠看出各個子產品的執行順序,進而為優化提供更為明白的指引。我們發現AA_load導緻了延遲,假設全部AA的值都能一次性并行取出,勢必會加快計算效率!

回到Synthetic視圖,為AA添加Directives:

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

選擇Resources,再點Cores後面的方框,進入Vivado HLS core選擇對話框

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

按上圖進行選擇。使用ROM是由于在計算矩陣和向量相乘時,AA為常數。确認。

仍然選擇AA。添加Directives,例如以下圖:

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

選擇數組分解,mode選擇全然complete,綜合後結果例如以下圖:

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

延遲進一步減少,已經降到11個時鐘周期了!。!是否已經達到極限了呢???

答案是否定的。我們進入Analysis視圖。看一下還有哪些地方能夠優化的。經過對照發現bb也須要分解,于是依照上面的方法對bb進行資源優化,也用ROM-2P類型,也做全分解,再次綜合,結果例如以下:

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

發現延遲進一步減少到8個時鐘周期了。。!

老師。能不能再給力點?

能夠的!!

我們進入分析視圖,發現cc這個回寫的步驟堵塞了總體流程,于是我們将cc也進行上述資源優化。僅僅隻是資源類型要變為RAM_2P,由于它是須要寫入的。

綜合結果:

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

總體延遲已經減少到6個clk周期了!!!

再看Analysis視圖:

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

延遲已經被壓縮到極限了。

。。

老師。還能再給力點嘛?

答案是能夠的。!

我們前面的全部運算都是基于整形數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試一下。結果例如以下:

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

看來結果是正确的(當然也不排除數位不夠,溢出後的結果相減也是0,須要你自己決定數值位寬)

綜合一下。結果例如以下:

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

延遲縮短了一半,DSP48E1降低到原來的1/4!!

!!!。!!!!

。!!!

。!

!。!。!!!。。!!!

!!

。。!!。!!

我和我的小夥伴們都震驚了!

。!!

。。。!

!!。!

!!!!。。!

!。!。!

!!!!!

。!!。。

再看Analysis視圖

ZED-Board從入門到精通系列(八)——Vivado HLS實作FIR濾波器

能夠發現我們的資源使用率已經達到極緻,時序已經壓縮到無以複加,實作了全并行計算。系統時鐘全然能夠達到100MHz。延遲僅3CLK。約30ns,相比matlab。得到約數百倍加速(matlab進行矩陣——向量相乘時採用浮點計算)。

通過本文實驗,能夠發現利用Vivado HLS實作從最初的C串行實作到全并行實作的步步優化。總結一下優化步驟:

(1)粗優化(循環展開、子函數内聯)

(2)訪存優化(塊存儲分散化、多port存取)

(3)精優化(數值位寬優化、流水線優化)

(4)總線化(利用AXI4、AXI-Stream總線接口,減少總體訪存需求)

利用HLS能夠将原來的C算法高速部署到FPGA上,降低直接進行硬體程式設計的工作量。在非常多情況下,優化手段能夠和CUDA進行類比,互相借鑒。CUDA事實上更接近軟體接口,而HLS更接近硬體程式設計接口,也許今後兩者會在新的層次上融合為統一架構語言。