写在前面
本文参考UG998的第六章,介绍了以控制为中心的算法。它还包括示例和最佳实践建议,以最大限度地提高HLS生成的实现的性能。
概念
以控制为中心的算法是一种可以在任务执行期间根据系统级因素进行更改的算法。以计算为中心的算法在任务期间对所有输入数据值应用相同的操作,而以控制为中心的算法则根据当前输入端口状态确定其操作。
用 C/C++ 表达控制
循环
循环是用于表达迭代计算的基本编程结构。 与所有编译器一样,HLS 允许将循环表示为 for 循环、while 循环和 do-while 循环。 使用 Vivado HLS 编译的所有类型的应用程序都支持此构造。 循环对于在 C/C++ 中捕获计算密集型算法至关重要。
上图显示了一个 for 循环示例以及 Vivado HLS 编译的效果。 它说明了 Vivado HLS 编译如何生成计算和控制逻辑作为单个 FPGA 实现的一部分。 Vivado HLS 编译器不区分控制和计算语言结构。 对于图中的代码,HLS 为循环中的数学运算生成流水线数据路径。 这种实现通过在循环迭代内和循环迭代之间并行化计算来减少执行延迟。 除了这个逻辑,Vivado HLS 实现还嵌入了循环控制器逻辑。 循环控制器逻辑决定了硬件被执行多少次来计算 y 的值。
条件语句
条件语句在 C 和 C++ 中通常表示为 if-else 语句。 在硬件实现中,这导致基于触发值在两个结果或两个执行路径之间进行选择。 这种有用的构造允许设计者在变量或函数级别对算法进行控制。 HLS 编译器完全支持这两个用例。
上图图显示了 if-else 语句的示例,其中 if 语句在算法中的两个不同函数之间进行选择。 Vivado HLS 编译器生成的实现为 function_a 和 function_b 分配 FPGA 资源。 这两个硬件电路并行运行并平衡以在同一时钟周期内生成结果。 原始源代码中的条件触发器用于在两个计算结果之间进行选择。
Case 语句
Case 语句根据输入变量的值定义程序中的特定操作或事件序列。 尽管此构造可用于以计算为中心的算法,但它在以控制为中心的应用程序中更为普遍,其中系统级别的更改直接影响模块执行。 此外,在大多数使用模型中,case 语句明确定义了从一个程序控制区域到另一个程序控制区域的转换。
上图显示了一个示例 case 语句和使用 Vivado HLS 编译的结果。编译器将 case 语句转换为硬件有限状态机 (FSM)。FSM 的数组表示状态之间的转换并对应于代码示例中的情况转换。FSM 中的每个状态还包括程序控制区域内的计算逻辑。
控制系统分类
在使用特定的代码构造去设计以控件为中心的应用程序之后,设计者面临的下一个决策是运行应用程序的平台。在过去,处理器常常被选为最合适的平台。正如Zynq®-7000 SoC所展示的那样,在许多使用案例中,处理器仍然是最佳选择。然而,HLS编译器消除了状态机优化和复杂性问题,这是在FPGA结构中实现控制算法的瓶颈。设计者可以选择在处理器上运行相同的控制算法,或者在FPGA结构中作为HLS生成的客户控制器运行相同的控制算法。这些选项之间的选择取决于算法响应时间要求和FPGA结构资源的消耗。
表中显示了按外部事件响应时间分类的控制算法。
对于需要非常慢的响应时间的设计,最好的实现选择是处理器。 这种选择为将计算中心的算法编译到 FPGA 架构中提供了更多空间。 下图显示了一个控制响应时间非常慢的系统示例。
对于需要中等速度级别的设计,如慢速或中速类别所示,实现选择可以是更多处理器或 FPGA 架构中的自定义逻辑。 在这些情况下,控制算法具有必须作为硬件模块实现的关键功能。 对于这些类型的系统,硬件协处理器的目的是弥补控制处理器中的通信延迟或处理速度的不足。 下图显示了一个需要硬件协处理单元的系统示例。
最后一类以控制为中心的应用程序是快速响应时间类。 此类别是指需要比处理器可以提供的响应时间和计算吞吐量更高的控制应用程序。 自从引入 HLS 编译器以来,属于这一类别的算法范围已经扩大。 例如,HLS 编译器越来越多地用于为 Zynq-7000 SoC 生成处理器加速器模块。
UDP 数据包处理
用户数据报协议 (UDP) 是一种用于计算机网络应用程序的无状态数据传输协议。 该协议不保证数据包传送,也不处理丢失数据包恢复。 相反,它在有线或无线信道上尽可能快地传输数据包。 该协议可实现的数据速率使其成为互联网电话、视频流和其他应用的标准,在这些应用中,数据速率比接收传输中的每个数据包更重要。
尽管该协议不跟踪数据包的传递和状态,但它仍然是一个以控制为中心的应用程序。UDP数据包处理器的控制方面包括:
- 以线路传输速率解析传入数据包;
- 响应来自网络的控制数据包;
- 格式化传输数据包;
- 处理传输通道中断。
这些所有控制方面都导致了图中所示的复杂状态机。
在引入HLS编译器之前,这种复杂控制级别始终针对处理器,即使以牺牲性能为代价。选择这种实现方式的主要原因是难以在手动设计流程中有效地表达和平衡这种大小的FSM。
UDP数据包处理FSM是一个复杂的互联状态网络。每个状态处理数据包处理的不同阶段。除了状态之间的复杂交互之外,每个状态都可能被系统级事件中断。这些事件可能会触发来自应用程序的状态信息请求,或者重新配置下一个数据包的处理方式。与以计算为中心的应用程序不同,没有为数据包处理定义好的任务大小。必须分析每个数据包,这意味着只要设备通电,任务的持续时间是无限的。UDP处理FSM的实现从顶级函数签名开始。
上图显示了UDP数据包处理的顶级函数的声明,他的目标是使用HLS编译器实现FPGA。在该函数中,阵列用于对该模块与系统其余部分之间的物理通信缓冲区进行建模。还需要注意的是,使用volatile关键字来标记不是数组的每个函数变量。如上面的状态机图所示,该控制器必须能够在执行的任何阶段处理来自系统的中断。这个需求的问题是C和C++中指定的函数变量行为。
在C和C++中,函数变量被采样并存储在函数调用空间内的本地副本中,当函数调用被发出时。这意味着,除了可能在多个内存空间中存储相同的变量外,C/C++程序在下一次函数调用之前不会检测到变量值的变化。volatile关键字是这个问题的语言解决方案。 嵌入式软件开发人员熟悉这种结构,它通知C/C++编译器变量可以在函数调用期间更改值。因此,每次在代码中使用volatile变量时,都必须直接从函数端口访问它。尽管此语言构造修复了数据访问问题,但它不会删除变量的内部副本。
const限定符解决了跨内存空间的潜在数据重复问题。 将此限定符应用于函数端口时,编译器将避免在函数内存空间中创建变量的本地副本。相反,读或写操作直接发生在变量端口上。在硬件中,const volatile限定符的使用允许系统在任务期间对外部输入作出反应,并减少反应的延迟。
下图显示了封装UDP控制FSM主处理的代码。
UDP控制FSM的执行分为初始化和正常执行阶段。一旦FPGA实现完成复位,初始化阶段即开始。在此阶段,将状态标志设置为默认值,并从内存加载块的媒体访问控制(MAC)地址。MAC地址是唯一的网络标识符,动态主机配置协议(DHCP)地址分配到该标识符上。
UDP控制器可以广播其地址后,它开始处理网络控制数据包,以请求并向网络注册internet协议(IP)地址。在网络中正确注册控制器后,它将切换到正常操作模式并开始生成UDP数据包。除了特定的功能外,这段代码还演示了如何在一个以控制为中心的应用程序中组合控制和计算编码元素。
图中的代码显示了基于两个执行阶段的单级控制层次结构。在实践中,以控制为中心的应用程序比本示例更复杂,并且呈现出分层控制结构。HLS与其他硬件软件编译器的主要区别之一是能够以与处理器相同的方式捕获控制层次结构。
上图显示了HLS编译器如何表示层次控制的示例。此图是servlet函数的一部分。servlet函数在初始化后控制UDP控制器的所有操作阶段。如该代码所示,模块与系统级信号持续交互,以确定下一个操作。此外,这种编码风格维护嵌套的case语句和处理器代码典型的计算函数的混合。这有助于捕获C/C++中的功能,并有助于将代码从处理器迁移到FPGA。
以控制为中心的应用程序,如UDP处理器,可以使用HLS编译器在FPGA上编译和实现。因此,实现这类代码的决策被简化为控制代码需求与应用程序中所有其他功能需求之间的资源权衡。通过使用HLS编译器开发整个应用程序,用户可以确定设计中以控制和数据为中心的功能在不同性能点需要多少资源。HLS编译器生成多个假设场景的能力允许探索设计变量,例如吞吐量、面积和延迟。
reference
- UG998