AIE微信合集
AIE(1)
對于Versal,我們從系統角度看,可將其分為3個Domain:AIE、PS和PL,如下圖所示。如果要運行一個AIE的應用,絕大多數情況下,這3個Domain我們都會用到,使其協同工作。這里我們僅關注AIE Domain,將介紹如何使用Vitis 2021.2創建AIE應用工程。
File->New->Application Project。
圖中標記1:simple_application將是我們在AIE上運行的工程。標記2:文件夾data,這里存放了仿真需用的輸入文件input.txt和輸出參考文件golden.txt。標記3:文件夾src,這里存放了設計需用的各種源文件,還包括一個子目錄標記4:Kernels,用于存放描述Kernel的源文件。
project.h創建了一個類simpleGraph。
實際上,仿真平臺與graph之間的連接關系可用下圖表述。
AIE(2) graph
自適應數據流 (Adaptive Data Flow, ADF) 計算圖是 AIE 應用的更高層次。這些計算圖是以 C++ 編寫的,包含節點和邊緣,其中節點表示計算內核函數和/或子計算圖,邊緣表示數據連接。所有 ADF 計算圖類定義都是預定義的數據流計算圖類 adf::graph 的子類,必須位于頭文件內。
AIE(3) kernel
需用注意的是多個Kernel可運行在同一個AIE上,但是一個Kernel不能運行在多個AIE上,這就要求設計者對函數進行合理的分割,以保證每個Kernel至多僅占用一個AIE。
AIE要求所有的Kernel都是void。
數據訪問機制
在這個案例中,Kernel訪問數據是通過window方式。另一種方式為stream。
基于window的數據訪問方式意味著Kernel直接從當前AIE的本地Memory(32KB)或與之相鄰的AIE的本地Memory讀取數據。只有當window寫滿時,Kernel才會將數據加載到AIE中。這意味著在第一次調用Kernel時會有一些延遲,因為填滿window需用一定的時間。但是,由于采用的是乒乓緩沖,當Kernel在運行的過程中,下一套數據就可以寫入Memory,這樣當再次調用Kernel時,數據已準備就緒了。
基于stream的數據訪問方式意味著Kernel直接從AIX-Stream接口讀取數據。在這種情況下,Kernel按采樣方式讀取數據。采用stream方式可能會造成上游Kernel反壓。這是因為下游Kernel處理數據還不夠快。也可能會造成下游Kernal停滯,這是因為上游Kernel不能更快的提供數據。
AIE(6)—用Vitis Analyzer查看AIE編譯結果
在Explorer窗口中,雙擊文件project.aiecompile_summary(位于目錄Emulation-AIE/Work),即可打開Vitis Analyzer。
或者只是$vitis_analyzer 打開project.aiecompile_summary
Graph視圖
點擊Vitis Analyzer左側控制面板的Graph即可呈現graph視圖,如下圖所示。從這個視圖中可以看到kernel的連接關系以及kernel之間的memory(對應圖中的buf)。從輸入到第一個kernel使用了雙緩存buf0和buf0d(double buffers,字母d的來歷),本質上就是乒乓操作。同樣地,從第二個kernel到輸出也使用了雙緩存buf2和buf2d。兩個kernel之間使用了單緩存buf1,這是因為這兩個kernel是按順序執行,不會同時訪問同一個memory。Graph視圖底部的表格顯示了具體kernel信息,例如first為kernel的實例化名字,simple為描述kernel功能的C++函數名,這個kernel運行在AIE上,AIE位于AIE陣列的第25列第0行。表格和Graph視圖是相互關聯的,例如在表格中選中first,Graph中的first就會以高亮方式顯示。
選擇上圖中紅色方框內的Tile View,則整個Graph視圖將顯示為下圖所示方式。從圖中可以看到first和second這兩個kernel位于AIE Tile [25, 0],這意味著這兩個kernel在同一個AIE上順序執行。還可以看到這兩個kernel訪問的memory位于AIE Tile[24,0]和[25,1],分別位于[25,0]的左側和上方,與[25,0]是相鄰位置,故不需要任何DMA。
AIE(7)—理解Runtime Ratio
增加runtime ratio使得每個kernel運行在不同的AIE Core上有可能提升graph吞吐率,但也增加了AIE的利用率。
降低runtime ratio有可能會降低AIE的利用率。這是因為只有當多個kernel確實可以運行在同一個AIE上時,工具才會將其映射到同一個AIE Core上。
AIE(8)—創建一個包含PL/PS/AIE的Vitis工程(1)
在此基礎上,可得到platform與graph的連接關系,如下圖所示。
Hardware link Application
那么AIE與PL之間究竟如何連接呢?這就要看HW Link部分。
生成XCLBIN之后,我們也可以看到生成該文件的Vivado工程,工程名為prj.xpr,具體路徑如下圖所示。
AIE(12)—AI Engine架構概覽
AI Engine陣列是由一系列的AI EngineTile構成。每個AI Engine Tile包含一個AI Engine,一個存儲單元和一個互連單元,如下圖所示。可以看到相鄰兩行AI Engine Tile的存儲單元與AI Engine的位置正好相反。
互連單元采用AIX4 Stream接口將數據在東西南北四個方向傳送。同時每個AI Engine Tile的存儲單元都包含一個DMA。每個DMA由一個獨立的S2MM和一個獨立的MM2S構成。前者用于將數據從Stream上取下來寫入到存儲單元,后者用于將存儲單元的數據上傳到Stream,如下圖所示。
AIE(17)—更新RTP(1)
AIE Kernel有時需要由外部提供參數更新kernel行為,此時就要用到RTP(Run-Time Parameter)。
AIE支持兩種類型的RTP,一種是異步(Asynchronous),通常由PS或其他AIE Kernel控制。“異步”意味著RTP可以隨時被更改。在每次Kernel被調用時,RTP都會被讀取,而不會進行任何同步處理。這種機制適合于參數不經常更新的場合,例如濾波器系數。另一種為同步RTP(Synchronous)。“同步”意味著只有當處理器將RTP傳遞給AIE Kernel后,該Kernel才能被觸發執行。無論是哪種類型,RTL都可以是標量(Scalar)或數組(Array)。
我們先看一個同步RTP。系統框圖如下圖所示。圖中sine為AIE Kernel,其中trigger為RTP,此處為標量。s2mm為HLS Kernel,最終通過HLS在PL側實現。
如前所述,RTP也可以是數組。我們看一下數組為RTP的一個例子。如下圖所示,HLS Kernel random_noise產生輸入數據傳遞給AIE Kernel fir24_sym。fir24_sym是一個濾波器,濾波器系數作為輸入參數由PS傳遞,其輸出經HLS Kernel s2mm寫入到外部存儲器。這里既用到了AIE API(對應AIE Kernel),又用到了OpenCL API(對應Host)。
AIE(19)—Packet Switching(1)
多個stream數據流可以共享一個物理通道,這個物理通道可以是PL到AIE也可以是AIE到PL。這樣的好處是節省了PL接口,尤其適用于低帶寬的場合。
本質上,packet switching使用了一對解復用器(de-multiplexer)和復用器(multiplexer)。前者將打包的數據流根據packet ID分配給不同的kernel,后者將來自于不同kernel上的數據流合并匯聚為一個數據流。為此,在ADF graph library中引入了pktsplit和pktmerge。pktsplit是一個1:n的解復用器,pktmerge是一個n:1的復用器。n最大值為32。
我們通過一個具體案例來體會一下packet switching的使用方法。這個例子包含4個AIE kernel,每個kernel的輸入/輸出均采用Window-based方式。4個Kernel的輸入數據分別來自于pktsplit解復用器的4個輸出,而4個Kernel的輸出數據則通過pktmerge復用器合并為一路輸出數據。需要注意的是將packet stream與window連接時connect里填的參數分別為pktstream和window,如下圖中紅色方框所示。
Packet sender由兩部分構成。第一部分用于生成packet header,第二部分則是將4路數據分時發送到PLIO stream上。本質上就是分時復用一個PLIO channel。如下圖所示。
再看packet receiver。Packet receiver收到的是一個打包好的數據流,因此,它需要根據packet header提供的信息將數據分發到不同的stream上。具體代碼如下圖所示。
無論是packet sender還是packet receiver,都需要獲知packet ID和stream的對應關系,這可由packet_ids_c.h文件獲取,如上述代碼中的深藍色方框所示。這個文件是在AIE編譯完之后生成的。因此,一旦AIE側發生改變就需要重新編譯生成此文件。