在系统设计中,正确指定加速器与主机之间的数据传输模式至关重要。以下章节提供了有关设计各方面的更多详细信息。
全局存储器 I/O
VSC 支持在全局存储器与加速器之间采用两种传输模式,如 指南宏 中所述:
-
DATA_COPY
:拖入数据移动器,如有效设计的 RTL IP,它将在与全局存储器对接的 M_AXI 接口上自动执行突发和数据宽度操作等功能。在加速器侧,此接口支持顺序数据访问(与 Vitis HLSap_fifo
接口中相同)以及随机数据访问(与 Vitis HLSap_memory
接口中相同)。 -
ZERO_COPY
:将 M_AXI 接口从全局存储器平台端口连接到加速器
对于 compute()
函数接口的定义,建议考量下列注意事项:
- 如果数据大小(所有计算实参)过大,难以放入器件 DDR,请通过多次发送迭代将大型
alloc_buf
拆分为较小的计算区块。 - 一般建议使用
DATA_COPY
和SEQUENTIAL
访问模式,当加速器代码执行顺序输入数据访问时尤其如此。 - 如果加速器代码执行随机数据访问,并且无法将代码修改为顺序访问数据,那么请使用随机访问模式,前提是数据可放入器件 RAM(BRAM 或 URAM)。否则,请使用
ZERO_COPY
。
顺序访问模式
对于内核代码顺序访问的 PE 端口 data
,请使用 ACCESS_PATTERN(data,
SEQUENTIAL);
VSC 需获取运行时的数据大小。这可通过 DATA_COPY
宏来完成。例如,DATA_COPY(data, data[numData]);
data
和 numData
都必须作为实参传递给内核,即使内核不使用 numData
,也同样必须作为实参来提供。总体上,numData
可采用任意表达式,前提是可在运行时根据内核函数实参来对其进行求值。在加速器类声明,支持如下示例,但此示例可能并不实用:
DATA_COPY(data, data[m * log(n) + 5]);
...
static void PE_func(int n, int m, float* data);
对于输入和输出数据,DATA_COPY
的精确大小至关重要,必须与内核当前读取的数据量精确匹配。如果大小与设计不匹配,则可能存在功能问题,如:
- 如果内核读取的数据量超过应用代码提供的数据量,那么在运行时会发生挂起,或者
- 如果前一次内核调用未能读取之前的计算调用的所有数据,那么内核将读取垃圾数据
为防止并调试此类问题,您可使用 ZERO_COPY
来确保内核代码正确工作。
随机访问模式
如果内核必须以随机方式访问 compute()
函数实参 data
,请使用 ACCESS_PATTERN(data,
RANDOM);
因此,您必须确保数据能放入片上 FPGA 存储器。内核代码必须将数据声明为静态阵列,例如:
ACCESS_PATTERN(data, RANDOM);
...
static void PE_func(int n, int m, float data[64]);
如果数据大小采用随机方式来访问并且对于片上 FPGA 存储器而言,数据大小过大,那么应使用 ZERO_COPY(data)
。
ACCESS_PATTERN
宏与 ZERO_COPY
搭配使用。主机与全局存储器之间传输的数据单位量并非必须与 DATA_COPY
大小相同。它实际上是由 VPP_ACC::alloc_buf()
调用的大小 (size) 实参来确定的。此大小可大于每次内核计算所需的数据大小,例如,一次性为多个 compute()
调用发送数据时就是如此。因此,按如下方式即可轻松聚集 PCIe 数据传输用于执行 N 次 compute()
调用:
send_while ... { ...
clustered_buffer = acc::alloc_buf( N * size );
for (i = 0; i < N; i++) { ...
acc::compute(&clustered_buf[ i * size ] ...
}
- 为 N 次计算调用分配相应的数据缓冲器大小
- 对
compute()
进行 N 次调用,每次调用都索引到聚集的缓冲器中
计算有效载荷数据类型
compute()
数据类型还可判定全局存储器上的数据布局,因此将影响加速器性能。为了允许内核尽快访问数据,为 compute()
实参选择相应的数据类型至关重要。例如,如果内核正在处理整数,并且必须在每个时钟周期内处理一个整数 (HLS II = 1),那么接口即可使用整数阵列,例如:
static void compute ( int* A );
再举个例子,以下提供的两种编码样式在每次计算调用中添加 4 个整数。
|
|
内核以来自输入阵列的每 4 个整数为一组进行相加。左侧直接实现每个结果需要 4 个时钟周期,假定每次存储器访问需一个周期。但将全部 4 个整数都打包到单次全局存储器访问中更好,如右侧所示。因此,在此情况下建议:
- 使用 C 结构体将所有数据打包并传递给内核
- 使用
DATA_COPY(data, data[numData]);
确保提供的数据副本大小正确
还有些需要注意的关键要点:
- 使用
int* data[4]
将不会打包这些整数,生成的硬件与int* data;
相同 - 通常打包超过 64 个字节(对应 512 位 M_AXI 总线宽度)将无法改善性能,但也不会造成性能降级