AXI 突发传输 - 2023.2 简体中文

Vitis 高层次综合用户指南 (UG1399)

Document ID
UG1399
Release Date
2023-12-18
Version
2023.2 简体中文

突发传输概述

突发是一种最优化方式,它会尝试以智能方式聚集对 DDR 的存储器访问操作,以便尽可能提升吞吐量带宽和/或减小时延。突发是可对内核执行的诸多最优化操作之一。突发通常可以实现 4 到 5 倍的提升,而其他最优化(例如,访问拓宽或者确保不存在通过 DDR 存储器的依赖关系)甚至可提供更大的性能提升。通常 DDR 端口上存在争用(源于多个相互竞争的内核)时,适合使用突发。

Vitis HLS 工具支持自动突发推断,并且支持多种功能特性,例如,自动拓宽端口以适应自动突发访问。在某些情况下,如果自动突发访问失败,那么有效的解决方案是重写代码或者使用手动突发,如 使用手动突发 中所述。如果这些方法无效,那么另一个解决方案可能是在 AXI4 接口中通过 CACHE 编译指示或指令来使用高速缓存存储器。

AXI4 协议的突发功能特性能够通过在单个请求内在全局存储器上读取/写入多个数据块来提升加载存储功能的吞吐量。数据越大,吞吐量越高。这种指标的计算方式如下:((传输的字节数) * (内核频率)/(时间))。最大内核接口位宽为 512 位,如果内核编译为按 300 MHz 频率运行,那么理论上每个 DDR 存储器可达成 (512 * 300 MHz)/1 秒 = ~17 GB/s。

图 1. AXI 协议

上图显示 AXI 协议的工作方式。HLS 内核会发出读取请求(突发长度为 8),然后发出写入请求(突发长度为 8)。读取时延定义为发送读取请求突发直到内核接收到来自突发中的首个读取请求的数据之间所耗费的时间。同样,写入时延定义为发送写入突发中的最后一次写入操作的数据的时间与内核接收到写入确认的时间之间所耗用的时间。读取请求通常在写入请求入队并首次变为可用即开始发送,直至突发中的每次写入的数据变为可用为止。

提示: 此行为是由 syn.interface.m_axi_conservative_mode 选项来判定的(如 接口配置 中所述),该选项默认启用。

要了解突发传输的底层语义,请考量如下代码片段:

for(size_t i = 0; i < size; i++) {
    out[f(i)] = in[f(i)]);
}

Vitis HLS 会自动执行突发最优化,它以智能方式聚集来自用户代码的循环/函数内部的存储器访问,并对特定大小的全局存储器执行读/写。这些读/写会被转换为针对全局存储器的读取请求、写入请求和写入响应。根据存储器访问模式,Vitis HLS 会将这些读取和写入请求自动插入循环边界外部或者循环主体内部。根据这些请求的布局,Vitis HLS 可定义两种突发请求类型:顺序突发和流水打拍突发。

突发语义

对于给定内核,HLS 编译器会以多通道最优化(而非基于每个函数)的方式来实现突发分析最优化。仅对单个函数执行突发,不支持跨函数执行突发。在 综合汇总 报告中会提供突发最优化报告,并且还会报告错失的突发机会,以便帮助您改善突发最优化。

首先,HLS 编译器会在函数基本块中查找存储器访问,例如,函数内部一组连续的语句中的存储器访问。假定已满足突发的前置条件,这些基本块中推断的每次突发都被称为顺序突发。编译器将自动扫描基本块以将最长的连续访问构建为单一顺序突发。

随后,编译器会查找循环,并尝试推断哪些突发属于流水线突发。所谓流水线突发,是指跨循环迭代的连续读/写操作。编译器会尝试通过分析循环归纳变量和循环次数来推断突发长度。如果分析成功,编译器即可将每个循环迭代中的一连串读/写链接到单一长条流水线突发中。现如今,编译器会自动推断流水线突发或顺序突发,但无法请求特定类型的突发。需编写代码以使该工具推断流水线突发或顺序突发。

流水线突发

流水线突发通过在单个请求内读取或写入大量数据或最大量的数据来改善函数吞吐量。流水线突发的优势在于后续请求 (i+1) 无需等待当前请求 (i) 完成,因为读取请求、写入请求和写入响应均位于循环主体外部,并且流水线突发可以尽快执行请求,如以下代码示例所示。这样可以显著提升函数吞吐量,因为读/写整个循环边界所需的时间更短。

rb = ReadReq(i, size);
wb = WriteReq(i, size);
for(size_t i = 0; i < size; i++) { 
  Write(wb, i) = f(Read(rb, i)); 
}
WriteResp(wb);
图 2. 流水线突发

如果编译器可成功减少来自归纳变量 (i) 和循环次数 (size) 的突发长度,那么它将推断一个大型流水线突发,并且将 ReadReqWriteReqWriteResp 调用移至循环外部,如“流水线突发”代码示例所示。因此,所有循环迭代的读取请求都会组合为单个读取请求,所有写入请求也都会组合为单个写入请求。所有读取请求通常会立即发出,而写入请求仅在数据变为可用后才会发出。

在此情况下,每个循环迭代的读取请求和写入请求均可组合为一个读取或写入请求。但如有任何突发前置条件未能得到满足(如 突发传输的前置条件和限制 中所述),那么编译器将无法推断流水线突发,但是将改为尝试推断顺序突发,其中,ReadReqWriteRegWriteResp 与要进行突发最优化的读/写访问位于同一处,如“顺序突发”代码示例中所示。

顺序突发

顺序突发由大小较小的数据组成,其中读取请求、写入请求和写入响应均位于循环主体内部,如以下代码示例中所示。

for(size_t i = 0; i < size; i++) {
    rb = ReadReq(i, 1);
    wb = WriteReq(i, 1);
    Write(wb, i) = f(Read(rb, i));
    WriteResp(wb);
}

顺序突发的缺点在于后续请求 (i+1) 依赖于先前请求 (i) 完成后才能发出,因为它会等待读取请求、写入请求和写入响应完成。这将导致请求之间出现间隔,如下图所示。

图 3. 顺序突发

顺序突发效率不及流水线突发,因为它多次读取或写入少量数据,以补偿循环边界。虽然这将显著影响吞吐量,但是顺序突发仍好于无突发。如果您的代码未遵循 突发传输的前置条件和限制,那么 Vitis HLS 使用此突发方法。

提示: 突发请求的大小可以进一步分区为多个请求,大小由用户使用 INTERFACE 编译指示或指令的 max_read_burst_lengthmax_write_burst_length 来指定,如 控制 AXI4 突发行为的选项 中所述。