Intel® FPGA SDK for OpenCL™ Pro Edition: 最佳实践实践指南

ID 683521
日期 9/26/2022
Public
文档目录

10.1.2. 使用单个内核来描述脉动阵列

使用英特尔 Stratix 10 OpenCL设计, Intel® 建议将脉动数组描述成单个内核,对处理单元(PE)使用一个函数,并使用完全展开的循环或嵌套循环来表示数组。

未优化的多内核脉动数组伪码(pseudocode):

// data distribution network over an array of channels

channel int c[ROWS][COLS]; 
channel int d[ROWS][COLS]; 

attribute((num_compute_units(ROWS,COLS)) 
kernel void PE() {
   // get data values from my neighbors 
   while(1){
      x = read_channel_intel(c[ROWS-1][COLS]); 
      y = read_channel_inel(d[ROWS][COLS-1]);
 
 
      // some code that uses x and y 
      ... 
      // send the same data values to the next neighbors 
      write_channel_intel(c[ROWS][COLS], x); 
      write_channel_intel(d[ROWS][COLS], y); 
   }
}

优化单内核伪码:

kernel void allPEs() {
   while(1){
      int c[ROWS], d[COLS]; 
   
      #pragma unroll 
      for (int i = 0; i < ROWS; i++) 
         #pragma unroll 
         for (int j = 0; j < COLS; j++) {
            PE(c[i], d[j]); 
         } 
      }
   } 
}
注: PE主体不是内核,而是成为函数调用PE()。展开循环会得到一个PE数组,每个PE都使用2D数组中FPGA的一部分。
根据数组的大小,对于 Intel® FPGA SDK for OpenCL™ Offline Compiler而言,在单个时钟周期内生成为一行或一列数组上所有PE分配相同值cd的硬件有一定挑战性。这样做也可能会导致fMAX降低。为了解决这个问题,请考虑使用__fpga_reg()函数指示离线编译器在每个新PE的cd上插入寄存器。 Intel® 建议您仅在知道FPGA上的PE在空间上彼此分离才使用__fpga_reg()函数。
注: __fpga_reg()内置函数是一项高级功能。离线编译器不提供应该在何处插入__fpga_reg()函数调用的相关指导。为了帮助确定是否适合插入__fpga_reg()函数调用,您可以通过实验量化附加寄存器可能对fMAX产生的影响,并检查英特尔 Quartus Prime编译报告。

使用__fpga_reg()函数优化的伪码:

kernel void allPEs() {
   int c[ROWS], d[COLS]; 
   
   while(1){
      #pragma unroll 
      for (int i = 0; i < ROWS; i++) 
         #pragma unroll 
         for (int j = 0; j < COLS; j++) {
            // compute and store outputs 
            PE(c[i], d[j]); 
            c[i] = __fpga_reg(c[i]); 
            d[j] = __fpga_reg(d[j]); 
         } 
      }
   } 
}

离线编译器展开循环后,在cd上的每个PE之前有一个寄存器,允许英特尔 Quartus Prime Pro Edition软件将PE分开放置。您也可以通过在代码中插入多个__fpga_reg()调用来添加多个寄存器。例如,调用__fpga_reg(__fpga_reg(x))在数据路径上添加两个寄存器。但是,内核中有过多__fpga_reg()调用会增加设计面积,并且随之产生的拥塞可能会导致fMAX降级。