目錄
1. 簡介
2. 代碼解析
2.1 HLS kernel代碼
2.2 查看接口報告
2.3 TestBench
2.4 Dataflow 報告
3. Takeaways
4. 總結
1. 簡介
本文是對《Hardware Acceleration Tutorials: FIFO Sizing for Performance and Avoiding Deadlocks》實驗內容的詳細解釋。
首先需要了解,鑒于數據流優化具有動態特性,且不同并行任務的執行速度各不相同,設置不當的數據流通道可能會引發性能下降或死鎖。
數據流通道有兩種:FIFO 和 PIPO。可以由工具推斷,或者用戶自行創建。
FIFO:
- Streams (including hls::streams and streamed arrays),用戶創建。
- Scalar propagation FIFOs,工具推斷。
- Streams of blocks,用戶創建。
每個通道都有自己的握手信號。因為:
- 它們的讀寫操作是被調度的。
- 它們的讀/寫信號分別由流水線控制或有限狀態機(FSM)單獨驅動。
- 它們的full_n/empty_n信號直接使流水線的單個迭代或FSM的狀態暫停。
PIPO:
- PIPO,用戶創建。
- Task Level FIFOs (TLF),工具推斷。
- Input and output ports to the upper level,用戶創建。
其中,Task Level FIFOs (TLF) 是標量(scalar)FIFO,其連接到生產者的“done”握手信號以進行寫入,并連接到消費者的“start”握手信號以進行讀取。這些類型的FIFO是由工具自動推斷的。由于底層同步機制的緣故,它們被視為類PIPO。
這些通道應該被視為“使用ap_ctrl_chain握手的通道”,因為:
- 它們的寫入和讀取操作不會被調度。它們隱式地與進程的“done”握手或“start”握手相關聯。
- 它們的寫入和讀取信號分別連接到ap_done和ap_ready。
- 它們的full_n和empty_n分別連接到ap_continue和ap_start。
總結一下,在分析存儲深度、性能、死鎖等方面,真正需要關系的是:
- 通道是否擁有自己的握手機制(FIFOs)。它們的訪問在其進程執行期間被分散開來。例如,你可以在管道的第一個II之外讀取一個FIFO,或者甚至在數據流網絡的最后一個過程中讀取。
- 通道是否通過ap_ctrl_chain進行握手(PIPOs)。它們的讀取必須在管道的第一個II中,或者在數據流網絡的第一個“層級”的過程中進行,類似地,它們的寫入必須在最后一個II或在最后一個“層級”中進行。
- 另一個區別基于一次操作中傳輸的數據量,這對于資源分析比對性能分析更為重要:對于PIPOs來說是數組,對于流來說是塊流、標量流,對于標量傳播FIFOs和任務級FIFOs來說是標量。
2. 代碼解析
2.1 HLS kernel代碼
#include "example.h"void example(hls::stream<int>& A, hls::stream<int>& B){
#pragma HLS dataflow
#pragma HLS INTERFACE ap_fifo port=A
#pragma HLS INTERFACE ap_fifo port=Bhls::stream<int> data_channel1;hls::stream<int> data_channel2;proc_1(A, data_channel1, data_channel2);proc_2(data_channel1, data_channel2, B);
}void proc_1(hls::stream<int>& A, hls::stream<int>& B, hls::stream<int>& C){
#pragma HLS dataflowhls::stream<int> data_channel1;hls::stream<int> data_channel2;proc_1_1(A, data_channel1, data_channel2);proc_1_2(B, C, data_channel1, data_channel2);
}void proc_1_1(hls::stream<int>& A, hls::stream<int>& data_channel1, hls::stream<int>& data_channel2){int i;int tmp;for(i = 0; i < 10; i++){tmp = A.read();data_channel1.write(tmp); }for(i = 0; i < 10; i++){data_channel2.write(tmp); }
}void proc_1_2(hls::stream<int>& B, hls::stream<int>& C, hls::stream<int>& data_channel1, hls::stream<int>& data_channel2){int i;int tmp;for(i = 0; i < 10; i++){tmp = data_channel2.read() + data_channel1.read();B.write(tmp);}for(i = 0; i < 10; i++){C.write(tmp); }
}void proc_2(hls::stream<int>& A, hls::stream<int>& B, hls::stream<int>& C){
#pragma HLS dataflowhls::stream<int> data_channel1;hls::stream<int> data_channel2;proc_2_1(A, B, data_channel1, data_channel2);proc_2_2(C, data_channel1, data_channel2);
}void proc_2_1(hls::stream<int>& A, hls::stream<int>& B, hls::stream<int>& data_channel1, hls::stream<int>& data_channel2){int i;int tmp;for(i = 0; i < 10; i++){tmp = A.read() + B.read();data_channel1.write(tmp); }for(i = 0; i < 10; i++){data_channel2.write(tmp); }
}void proc_2_2(hls::stream<int>& C, hls::stream<int>& data_channel1, hls::stream<int>& data_channel2){int i;int tmp;for(i = 0; i < 10; i++){tmp = data_channel2.read() + data_channel1.read();C.write(tmp);}
}
與原示例相比,去掉了“&”符號。
#pragma HLS INTERFACE ap_fifo port=&A
#pragma HLS INTERFACE ap_fifo port=&B
以上 kernel 的功能框圖:
2.2 查看接口報告
對于頂層文件,可以查看 example_csynth.rpt,觀察頂層接口:
================================================================
== Interface
================================================================
* Summary:
+-----------+-----+-----+------------+--------------+--------------+
| RTL Ports | Dir | Bits| Protocol | Source Object| C Type |
+-----------+-----+-----+------------+--------------+--------------+
|A_dout | in| 32| ap_fifo| A| pointer|
|A_empty_n | in| 1| ap_fifo| A| pointer|
|A_read | out| 1| ap_fifo| A| pointer|
|B_din | out| 32| ap_fifo| B| pointer|
|B_full_n | in| 1| ap_fifo| B| pointer|
|B_write | out| 1| ap_fifo| B| pointer|
|ap_clk | in| 1| ap_ctrl_hs| example| return value|
|ap_rst | in| 1| ap_ctrl_hs| example| return value|
|ap_start | in| 1| ap_ctrl_hs| example| return value|
|ap_done | out| 1| ap_ctrl_hs| example| return value|
|ap_ready | out| 1| ap_ctrl_hs| example| return value|
|ap_idle | out| 1| ap_ctrl_hs| example| return value|
+-----------+-----+-----+------------+--------------+--------------+
?針對 Dataflow 區域的每個函數體,均有對應的 Interface:
================================================================
== Interface
================================================================
* Summary:
+-------------------------------+-----+-----+------------+----------------+--------------+
| RTL Ports | Dir | Bits| Protocol | Source Object | C Type |
+-------------------------------+-----+-----+------------+----------------+--------------+
|ap_clk | in| 1| ap_ctrl_hs| proc_1_1| return value|
|ap_rst | in| 1| ap_ctrl_hs| proc_1_1| return value|
|ap_start | in| 1| ap_ctrl_hs| proc_1_1| return value|
|start_full_n | in| 1| ap_ctrl_hs| proc_1_1| return value|
|ap_done | out| 1| ap_ctrl_hs| proc_1_1| return value|
|ap_continue | in| 1| ap_ctrl_hs| proc_1_1| return value|
|ap_idle | out| 1| ap_ctrl_hs| proc_1_1| return value|
|ap_ready | out| 1| ap_ctrl_hs| proc_1_1| return value|
|start_out | out| 1| ap_ctrl_hs| proc_1_1| return value|
|start_write | out| 1| ap_ctrl_hs| proc_1_1| return value|
|A_dout | in| 32| ap_fifo| A| pointer|
|A_empty_n | in| 1| ap_fifo| A| pointer|
|A_read | out| 1| ap_fifo| A| pointer|
|data_channel12_din | out| 32| ap_fifo| data_channel12| pointer|
|data_channel12_num_data_valid | in| 2| ap_fifo| data_channel12| pointer|
|data_channel12_fifo_cap | in| 2| ap_fifo| data_channel12| pointer|
|data_channel12_full_n | in| 1| ap_fifo| data_channel12| pointer|
|data_channel12_write | out| 1| ap_fifo| data_channel12| pointer|
|data_channel23_din | out| 32| ap_fifo| data_channel23| pointer|
|data_channel23_num_data_valid | in| 2| ap_fifo| data_channel23| pointer|
|data_channel23_fifo_cap | in| 2| ap_fifo| data_channel23| pointer|
|data_channel23_full_n | in| 1| ap_fifo| data_channel23| pointer|
|data_channel23_write | out| 1| ap_fifo| data_channel23| pointer|
+-------------------------------+-----+-----+------------+----------------+--------------+
上述報告 Source??Object 所在列:
- A - >?hls::stream<int>& A, C type 為 pointer
- data_channel12 ->?hls::stream<int>& data_channel1, C type 為 pointer
- data_channel23 ->?hls::stream<int>& data_channel2, C type 為 pointer
?
void proc_1_1(hls::stream<int>& A, hls::stream<int>& data_channel1, hls::stream<int>& data_channel2){int i;int tmp;for(i = 0; i < 10; i++){tmp = A.read();data_channel1.write(tmp);}for(i = 0; i < 10; i++){data_channel2.write(tmp);}
}
2.3 TestBench
#include <stdio.h>
#include "hls_stream.h"#define SIZE 10
extern void example(hls::stream<int>& A, hls::stream<int>& B);int main()
{int i;hls::stream<int> A;hls::stream<int> B;int time = 0;for (time = 0 ; time < 4; time ++) {for(i=0; i < SIZE; i++){A << (i + time);}example(A,B);}return 0;
}
2.4 Dataflow 報告
運行 C Synthesis 后,可以查看 Dataflow 報告,如下圖,沒有問題。
在運行 C/RTL Cosimulation 后,同樣在 Dataflow 報告中可以看到錯誤。
3. Takeaways
總結而言,Dataflow查看器實現了以下吞吐量分析任務:
圖表展示了 DATAFLOW 區域的整體拓撲結構,并顯示了在 DATAFLOW 區域中任務之間用于通信的通道類型(FIFO/PIPO)。通過分析每個通道和進程,可以有效地解決死鎖或由于FIFO大小不當導致的吞吐量不足等問題。
協同仿真數據通過在仿真過程中跟蹤FIFO的最大使用量,為解決FIFO大小設置問題提供了參考依據,從而幫助用戶調整FIFO大小。此外,運行協同仿真時的自動死鎖檢測功能能夠突出顯示涉及死鎖的進程和通道,使用戶能夠快速定位并解決這些問題。
除了 FIFO 大小的調整,協同仿真后的數據還能按每個進程和通道報告因等待輸入或輸出而導致的停滯時間。這些圖表幫助用戶理解并解決這些問題,同時管理通道大小以滿足慢速生產者與快速消費者之間的需求,或者相反。此外,圖表還揭示了在DATAFLOW區域中途讀取輸入如何影響整體性能,這是一個常見的場景,可能會對性能產生重大影響。
4. 總結
在數據流優化中,通道類型、握手機制、FIFO大小和死鎖避免都是關鍵因素。通過Dataflow查看器和協同仿真數據,您可以有效地優化設計,提高性能并避免潛在問題。