HLS设计方法论 - 结构优化策略

简介: HLS设计方法论 - 结构优化策略

写在前面


本文参考赛灵思的官方手册UG1270,主要介绍了结构优化策略,以帮助我们更好的进行HLS的开发设计。

结构优化策略


C代码可以包含防止函数或循环以所需性能流水线化的描述。 这通常由 C 代码的结构或用于实现 PL 逻辑的默认逻辑结构暗示。 在某些情况下,这可能需要修改代码,但在大多数情况下,这些问题可以使用额外的优化指令来解决。

以下示例显示了使用优化指令来改进实现结构和流水线性能的情况。 在这个初始示例中,将 PIPELINE 指令添加到循环中以提高循环的性能。 此示例代码显示了在函数内部使用的循环。

#include "bottleneck.h"
dout_t bottleneck(...) {
  ...
  SUM_LOOP: for(i=3;i<N;i=i+4) {
  #pragma HLS PIPELINE
  sum += mem[i] + mem[i-1] + mem[i-2] + mem[i-3];
  }
  ...
}

当上面的代码编译到硬件中时,会出现以下消息作为输出:

INFO: [SCHED 61] Pipelining loop ‘SUM_LOOP’.

WARNING: [SCHED 69] Unable to schedule ‘load’ operation (‘mem_load_2’,

bottleneck.c:62) on array ‘mem’ due to limited memory ports.

INFO: [SCHED 61] Pipelining result: Target II: 1, Final II: 2, Depth: 3.

I

此示例中的问题是使用 PL 结构中的高效的块 RAM 资源来实现阵列。这导致开销小、高效的快速设计。 但Block RAM 的缺点是,与 DDR 或 SRAM 等其他存储器一样,它们的数据端口数量有限,通常最多两个。

在上面的代码中,需要来自 mem 的四个数据值来计算 sum 的值。 由于 mem 是一个数组并在只有两个数据端口的块 RAM 中实现,因此每个时钟周期只能读取(或写入)两个值。 使用这种配置,不可能在一个时钟周期内计算 sum 的值,从而消耗或产生 II 为 1 的数据(每个时钟处理一个数据样本)。

内存端口限制问题可以通过在内存数组上使用 ARRAY_PARTITION 指令来解决。 该指令将数组划分为更小的数组,通过提供更多的数据端口和允许更高性能的管道来改进数据结构。

使用下面显示的附加指令,数组 mem 被划分为两个双端口存储器,以便所有四个读取都可以在一个时钟周期内发生。 对阵列进行分区有多种选择。 在这种情况下,因子为 2 的循环分区确保第一个分区包含来自原始数组的元素 0、2、4 等,而第二个分区包含元素 1、3、5 等。因为分区确保有 现在有两个双端口块 RAM(总共有四个数据端口),这允许在单个时钟周期内读取元素 0、1、2 和 3。

#include "bottleneck.h"
dout_t bottleneck(...) {
#pragma HLS ARRAY_PARTITION variable=mem cyclic factor=2 dim=1
...
SUM_LOOP: for(i=3;i<N;i=i+4) {
#pragma HLS PIPELINE
sum += mem[i] + mem[i-1] + mem[i-2] + mem[i-3];
}
...
}

尝试流水线化循环和函数时可能会遇到其他此类问题。 下表列出了可能通过帮助减少数据结构中的瓶颈来解决这些问题的指令。

image.png

除了 ARRAY_PARTITION 指令外,阵列分区的配置还可用于自动对阵列进行分区。

流水线循环时,可能需要 DEPENDENCE 指令来删除隐含的依赖项。 这种依赖性由消息 SCHED-68 报告。

@W [SCHED-68] Target II not met due to carried dependence(s)

INLINE 指令删除函数边界。 这可用于将逻辑或循环向上一层层次结构。 通过将函数包含在其上方的函数中,将其逻辑流水线化可能更有效,将循环合并到它们上面的函数中,其中 DATAFLOW 优化可用于同时执行所有循环,而无需没有中间子函数调用的开销。 这可能会导致更高性能的设计。

对于无法使用所需的 II 流水线化循环的情况,可能需要 UNROLL 指令。 如果一个循环只能在 II = 4 的情况下进行流水线化,它会将系统中的其他循环和函数限制为 II = 4。在某些情况下,可能值得展开或部分展开循环以创建更多逻辑和消除潜在的瓶颈。 如果循环只能达到 II = 4,那么将循环展开 4 倍将创建可以并行处理循环的四次迭代并达到 II = 1 的逻辑。

减少延迟


当编译器完成最小化启动间隔 (II) 后,它会自动寻求最小化延迟。 下表中列出的优化指令可以帮助指定特定的延迟或通知编译器实现低于生成的延迟,即指示编译器满足延迟指令,即使它导致更高的 II。 这可能会导致性能较低的设计。

通常不需要延迟指令,因为大多数应用程序具有所需的吞吐量但没有所需的延迟。 当硬件功能与处理器集成时,处理器的延迟通常是系统中的限制因素。

如果循环和函数没有流水线化,吞吐量会受到延迟的限制,因为在当前任务完成之前,任务不会开始读取下一组输入。

image.png

循环优化指令可用于展平循环层次结构或将连续循环合并在一起。 延迟的好处是因为它通常在控制逻辑中花费一个时钟周期来进入和离开由循环创建的逻辑。 循环之间的转换次数越少,完成设计所需的时钟周期数就越少。

减少面积


在硬件中,实现逻辑功能所需的资源数量称为设计区域。 设计面积也指资源在固定尺寸的PL结构上使用的面积。 当硬件太大而无法在目标设备中实现时,以及当硬件功能占用可用区域的百分比非常高(> 90%)时,该区域很重要。 当尝试将硬件逻辑连接在一起时,这可能会导致困难,因为连接线本身需要资源。

在满足所需的性能目标(或 II)后,下一步可能是在保持相同性能的同时减少面积。 这一步可以是最佳的,因为如果硬件功能以所需的性能运行,并且在 PL 的剩余空间中不实施其他硬件功能,则通过减少面积没有任何好处。

最常见的区域优化是数据流内存通道的优化,以减少实现硬件功能所需的块 RAM 资源数量。 每个设备具有数量有限的 Block RAM 资源。

如果您使用了 DATAFLOW 优化并且编译器无法确定设计中的任务是否是流式数据,它会使用乒乓缓冲区实现数据流任务之间的内存通道。 这些需要两个块 RAM,每个块的大小为 N,其中 N 是要在任务之间传输的样本数(通常是在任务之间传递的数组的大小)。 如果设计是流水线式的,并且数据实际上是从一个任务流到下一个任务,并以顺序方式产生和消耗值,那么您可以通过使用 STREAM 指令来指定数组将在一个 使用简单 FIFO 的流式传输方式,您可以为其指定深度。 深度较小的 FIFO 使用寄存器实现,PL 结构有许多寄存器。

  • 对于以相同速率产生和消耗数据的任务,指定它们之间的数组以深度为 1 进行流式传输。
  • 对于将数据速率降低 X 到 1 倍的任务,在任务的输入处指定数组以 X 的深度进行流式传输。函数中在此之前的所有数组也应具有 X 的深度以确保 硬件功能不会因为 FIFO 已满而停止。
  • 对于将数据速率提高 1 到 Y 倍的任务,在任务的输出处指定数组以 Y 的深度进行流式传输。函数中此之后的所有数组也应具有 Y 的深度以确保 硬件功能不会因为 FIFO 已满而停止。

下表列出了在尝试最小化用于实现设计的资源时要考虑的其他指令。

image.png

ALLOCATION 和 RESOURCE 指令用于限制操作数量并选择使用哪些内核(硬件资源)来实现操作。 例如,您可以将函数或循环限制为仅使用一个乘法器,并指定它使用流水线乘法器来实现。

如果 ARRAY_PARITION 指令用于改进启动间隔,您可能需要考虑使用 ARRAY_RESHAPE 指令。 ARRAY_RESHAPE 优化执行与数组分区类似的任务,但是,重塑优化将通过分区创建的元素重新组合到具有更宽数据端口的单个块 RAM 中。 这可能会阻止所需块 RAM 资源数量的增加。

如果 C 代码包含一系列具有相似索引的循环,将循环与 LOOP_MERGE 指令合并可能会允许进行一些优化。 最后,如果流水线区域中的一段代码只需要以低于该区域其余部分的启动间隔运行,则使用 OCCURENCE 指令指示可以优化此逻辑以以较低的速率执行。

设计优化工作流程


在执行任何优化之前,建议在项目中创建一个新的构建配置。 使用不同的构建配置允许将一组结果与另一组结果进行比较。 除了标准的 Debug 和 Release 配置之外,还可以使用 Manage Build Configurations for Project 工具栏按钮在 Project Settings 窗口中创建具有更有用名称的自定义配置(例如 Opt_ver1 和 UnOpt_ver)。

不同的构建配置不仅可以比较结果,还可以比较用于实现 FPGA 的日志文件甚至输出 RTL 文件(RTL 文件只推荐给非常熟悉硬件设计的用户)。

高性能设计的基本优化策略是:

  • 创建初始或基线设计。
  • 流水线化循环和函数。 应用 DATAFLOW 优化以同时执行循环和函数。
  • 解决任何限制流水线的问题,例如数组瓶颈和循环依赖(使用 ARRAY_PARTITION 和 DEPENDENCE 指令)。

总之,目标是始终首先满足性能,然后再减少面积。 如果策略是用最少的资源创建设计,只需省略提高性能的步骤,尽管基线结果可能非常接近最小的设计。

在整个优化过程中,强烈建议在编译后查看控制台输出(或日志文件)。 当编译器无法达到优化的指定性能目标时,它会自动放宽目标(时钟频率除外)并创建具有可满足目标的设计。 查看编译日志文件和报告的输出以了解已执行的优化非常重要。

reference


  1. UG1270
目录
相关文章
|
4月前
|
SQL 安全 Java
探索软件测试的多维策略:从单元到集成,再到性能与安全
在软件开发生命周期中,测试是不可或缺的一环。本文将深入探讨软件测试的多维策略,从单元测试、集成测试到性能测试和安全测试等各个层面进行剖析。我们将通过具体的统计数据和案例分析,揭示不同测试策略的优势和应用场景。文章旨在为读者提供一个全面的测试框架,帮助他们构建更稳定、高效和安全的系统。
95 2
|
3月前
|
Web App开发 缓存 Shell
PWA离线优先策略:提升用户体验的关键步骤
Progressive Web Apps (PWA) 采用Service Worker与Cache API实现离线优先策略,确保无网时仍可访问网站内容。通过注册Service Worker、配置缓存策略及manifest文件,结合App Shell架构和WebSocket支持,创建出即便在离线或弱网环境中也能提供流畅体验的高度可用应用。测试和持续优化对于保证PWA性能至关重要。
58 6
|
3月前
|
机器学习/深度学习 分布式计算 前端开发
构建前端防腐策略问题之前端代码会随着技术引擎的迭代而腐烂的问题如何解决
构建前端防腐策略问题之前端代码会随着技术引擎的迭代而腐烂的问题如何解决
|
4月前
|
存储 缓存 监控
通用研发提效问题之动态调整干预能力,如何解决
通用研发提效问题之动态调整干预能力,如何解决
|
存储 SQL Web App开发
迭代技术方案设计文档规范
规范在团队管理中的意义无需多言,对于开发团队来说,技术方案的设计和执行无疑是日常工作中很重要的一块。编码一定要在思考清楚之后在开始,以免把问题带入线上,或者反复修改造时间、精力的浪费。
549 0
|
前端开发 Serverless 开发者
前端工程化的前端性能的性能优化方案的网络层面优化之资源优化
资源优化是一种非常重要的前端性能优化方案,因为它可以在不同的环境中提高网页的响应速度和可接受性。
91 1
|
前端开发 开发者
前端工程化的前端性能的性能优化方案的网络层面优化之压缩
压缩是一种非常重要的前端性能优化方案,因为它可以在不同的环境中提高网页的响应速度和可接受性。
79 0
|
分布式计算 关系型数据库 BI
KYLIN 建模设计学习总结(概念、空间优化、查询性能优化)
KYLIN 建模设计学习总结(概念、空间优化、查询性能优化)
140 0
|
消息中间件 存储 SQL
谈谈如何构建优化的流数据架构(上)
流处理最初是一种“特定群体”技术。但随着 SaaS、物联网和机器学习的快速发展,各行各业的组织现在都在试行或全面实施流分析。
谈谈如何构建优化的流数据架构(上)
|
存储 SQL 分布式计算
谈谈如何构建优化的流数据架构(下)
流处理最初是一种“特定群体”技术。但随着 SaaS、物联网和机器学习的快速发展,各行各业的组织现在都在试行或全面实施流分析。
谈谈如何构建优化的流数据架构(下)
下一篇
无影云桌面