Vivado_HLS 學習筆記4-嵌套的循環的優化


優化的原理

HLS會自動嘗試最小化循環的延遲. 除了這些自動的優化之外,directive文件負責

  • 執行並行任務; 例如相同函數的多次執行,以及相同循環的多次迭代. 要進行pipeline設計;
  • 重新設計數組(Block arrays),函數,循環和端口等的物理實現,改善數據的訪存;
  • 提供數據依賴的信息;

最終的優化手段是修改C源代碼以移除非必要的數據依賴.

參考ug871的第7章.

對有數據依賴的計算如何優化設計

void matrixmul(
      mat_a_t a[MAT_A_ROWS][MAT_A_COLS],
      mat_b_t b[MAT_B_ROWS][MAT_B_COLS],
      result_t res[MAT_A_ROWS][MAT_B_COLS])
{
  // Iterate over the rows of the A matrix
   Row: for(int i = 0; i < MAT_A_ROWS; i++) {
      // Iterate over the columns of the B matrix
      Col: for(int j = 0; j < MAT_B_COLS; j++) {
         res[i][j] = 0;
         // Do the inner product of a row of A and col of B, 
         // 顯然,內積的計算中,每次計算都需要讀取上一次的res[i][j]並更新本次的res[i][j].存在同一循環不同迭代間的數據依賴.
         Product: for(int k = 0; k < MAT_B_ROWS; k++) {
            res[i][j] += a[i][k] * b[k][j];
         }
      }
   }

}

解決步驟1-pipeline最底層loop,

  • 操作: 設置留空II以initiaion interval 為1
  • 結果: 當最底層的loop循環進行pipeline不能降低latency(運算一次數據需要的時鍾周期數)和Interval(下一次輸入數據需要等待的間隔,如果等於latency說明沒有pipeline)時,查看console看一下是否有藍色字體 **Unable to enforce a carried dependence constraint **,執行步驟2;

解決步驟2-pipeline上一層loop

  • 操作: 選擇其上一層loop進行pipeline,而不是最底層pipeline.
  • 如果發現並沒有獲得想要的結果,再次查看console的warnning!,發現Unable to schedule 'load' operation ('a_load_1', matrixmul.cpp:60) on array 'a' due to limited memory ports. Please consider using a memory core with more ports or partitioning the array 'a'.;

解決步驟3-數組分塊(並行,需要多個端口並行)或reshape(需要一個更寬的端口)

  • 操作1: 本例中,需要將數組a的[k]維度也就是第2維進行展開,數組 a -> Directive -> ARRAY_RESHAPE, dimension:2
  • 操作2: 本例中,需要對數組b的[k]維度也就是第1維進行展開;數組 a -> Directive -> ARRAY_RESHAPE, dimension:1
  • 結果: 能夠看到II已經是1,滿足了流水的設計要求;

解決步驟4-嘗試添加FIFO接口

  • 操作: 對輸入端口a,b,和輸出端口res設置-> Directive -> Interface ,ap_fifo
  • 結果: console顯示錯誤Port 'res' (matrixmul.cpp:48) of function 'matrixmul' cannot be set to a FIFO [SYNCHK 200-91] as it has both write (matrixmul.cpp:60:13) and read (matrixmul.cpp:60:13) operations.
  • 分析: 在57行和60行等,對res[0][0]進行了多次連續的寫操作,不滿足stream的狀況.這不是stream而是random access.
  • 結果: 這種情況不能使用FIFO!

解決步驟5-繼續優化!修改C代碼

在重寫C代碼之前,需要對數組a,b,的訪存地址進行分析:

  • 為了實現序列的流訪存,端口只能在圖中紅色高亮的部分訪存;
  • 對於數組a,b的其他藍色的讀數據口,需要使用內部緩存cached獲取;
  • 對於輸出res的其他端口,只能通過臨時變量進行緩存,最終在紅色時輸出.

因此,改寫后的代碼為:

void matrixmul(
      mat_a_t a[MAT_A_ROWS][MAT_A_COLS],
      mat_b_t b[MAT_B_ROWS][MAT_B_COLS],
      result_t res[MAT_A_ROWS][MAT_B_COLS])
{
#pragma HLS ARRAY_RESHAPE variable=b complete dim=1
#pragma HLS ARRAY_RESHAPE variable=a complete dim=2
#pragma HLS INTERFACE ap_fifo port=a
#pragma HLS INTERFACE ap_fifo port=b
#pragma HLS INTERFACE ap_fifo port=res
  mat_a_t a_row[MAT_A_ROWS];
  mat_b_t b_copy[MAT_B_ROWS][MAT_B_COLS];
  int tmp = 0;

  // Iterate over the rowa of the A matrix
  Row: for(int i = 0; i < MAT_A_ROWS; i++) {
    // Iterate over the columns of the B matrix
    Col: for(int j = 0; j < MAT_B_COLS; j++) {
#pragma HLS PIPELINE rewind
      // Do the inner product of a row of A and col of B
      tmp=0;
      // Cache each row (so it's only read once per function)
      if (j == 0)
        Cache_Row: for(int k = 0; k < MAT_A_ROWS; k++)
          a_row[k] = a[i][k];
      
       // Cache all cols (so they are only read once per function)
     if (i == 0)
            Cache_Col: for(int k = 0; k < MAT_B_ROWS; k++)
               b_copy[k][j] = b[k][j];

      Product: for(int k = 0; k < MAT_B_ROWS; k++) {
        tmp += a_row[k] * b_copy[k][j];
      }
      res[i][j] = tmp;
    }
  }
}

注意事項

如果沒有pipling loops,那么每計算一個數字,需要interval,計算tripcount次,就需要tripcount * interval;
pipling loops把循環的latency從$$Latency = iteration\ latency * tripcount $$ 變成了 $$Latency = iteration\ latency + (tripcount * interval)$$.

  • loop的latency是指循環執行完成一次需要的時鍾數;
  • iteration latency是指循環內部單次執行完一次需要的時鍾數;
  • tripcount是指循環執行的次數.
  • initiation interval是指本次數據輸入和下次數據輸入需要的間隔周期數.

注意: 對上層循環/模塊的pipeline將導致對所有模塊均進行pipeling,將展開所有循環.會極大地消耗資源.獲得更高的吞吐量.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM