您已回顾了 Alveo 加速器卡及其关键组件的一些基本认知,并已了解了 CPU 与 Alveo 卡之间的数据移动方式。您还接触到了创建 Vitis 应用的建议准则。本节将深入讲解使用 Vitis HLS 进行编码的关键概念相关主题。
将函数实参映射到硬件接口
Vitis HLS 会为 C/C++ 内核函数实参自动分配接口端口。这些函数实参的类型属于标量或指针/阵列。来自主机的参数直接写入加速器的寄存器。缓冲器保留在全局存储器外部,加速器对该全局存储器执行读取和写入。
标量类型的函数实参用于参数,指针或阵列类型的实参则用于访问全局存储器。Vitis HLS 将这些接口端口作为 AXI 协议来实现。如需了解有关此接口协议的更多信息,请参阅 AXI 简介。
加载 - 计算 - 存储
算法结构应设为加载 - 计算 - 存储,并在其间设置通信通道,如下所示。
-
load
函数负责将数据从器件存储器移入内核。此函数不执行任何数据处理,而是侧重于有效的数据传输,包括必要时的缓冲 (buffering) 和缓存 (caching)。 - 顾名思义,
compute
函数是完成所有处理的地方。在开发流程的这个阶段,计算函数的内部结构并不重要。 -
store
函数是 load 函数的镜像操作。它负责将数据移出内核,获取计算函数的结果,并将它们传输到内核外的全局存储器。
开发者对存储器访问的编码方式需最大程度减小全局存储器访问的开销,这意味着尽可能减少使用连续访问,以便能够推断突发。突发访问会隐藏存储器访问时延,并改善存储器带宽。
此外,往来全局存储器与内核之间的最大数据宽度为 512 位。为了最大程度提升数据传输速率,建议您完整使用此数据宽度。默认情况下,在 Vitis 内核流程中,Vitis HLS 工具会将内核接口端口自动上调至 512 位以改善突发访问。
创建满足性能目标的加载 - 计算 - 存储结构首先要在内核中设计数据流。需要考虑的因素包括:
- 数据如何从内核外部流入内核?
- 内核处理这些数据的速度有多快?
- 如何将处理后的数据写入内核的输出?
Load-Compute 和 Compute-Store 会通过串流通道进行通信。串流是一种数据传输形式,其中数据样本从第一个样本开始按顺序发送。串流不需要地址管理,可以使用 FIFO 实现。一旦有足够数据可供 compute 函数使用,就会开始计算。同样,一旦有数据可供 Store 函数使用,即可通过 AXI4 主接口将数据发送到 DDR。
任务级并行度
开发者需评估算法并判定可达成的任务级并行度。可在两个维度内启用此类型的并行度。
- 任务可以彼此重叠的方式来执行。换言之,Compute 函数能够基于数据可用性来启动,且无需先前函数首先完成。启用数据流之后,该工具将推断此类并行度。
- 任务本身可在给定时间内重启,这称为“传输事务时间间隔”。换言之,在上一次调用 compute 函数完全完成之前,即可重启该函数的下一次调用。Vitis 工具可以为任意循环的性能目标提供编译器指令。添加该指令时,编译器将自动执行必要的变换或组合变换,例如,阵列分区、嵌套循环展开或者循环流水打拍,以满足“目标时间间隔”的目标。
验证内核的功能正确性
使用 Vitis HLS 设计流程时,对功能错误的 C 语言代码进行综合,然后分析实现详细信息,以判定此函数功能与期望不符的原因,这个过程较为耗时。因此,高层次综合中的第一步应该是验证 C 语言函数是否正确,再生成 RTL 代码,然后使用精心编写的测试激励文件执行 C 语言仿真。准确编写测试激励文件有助于显著提升工作效率,因为 C 语言函数的执行速度比 RTL 仿真快数个量级。与调试 RTL 代码相比,在综合之前使用 C 语言来开发和确认算法要快得多。此 C 语言测试激励文件同样可用于运行 C/RTL 协同仿真,以便自动验证生成的 RTL 设计。