Vitis HLS 学习笔记--HLS眼中的完美循环嵌套

目录

[1. 简介](#1. 简介)

[2. 示例](#2. 示例)

[2.1 不完美循环](#2.1 不完美循环)

[2.2 完美循环](#2.2 完美循环)

[2.3 HLS 眼中的循环](#2.3 HLS 眼中的循环)

[3. 总结](#3. 总结)


1. 简介

在处理嵌套循环时(HDL或HLS工具中),优化循环结构对于实现最佳性能至关重要。嵌套循环的性能优化直接影响着计算的时延和资源利用率。创建完美嵌套的循环意味着循环结构被设计得紧凑、高效,以最大程度地利用硬件资源和并行计算能力。

在硬件描述或高级综合中,循环的每个细节都被转换为硬件电路或流水线阶段。因此,创建完美嵌套循环的目标是确保循环体内的计算被最有效地映射到硬件逻辑中,并且循环的边界和计算步骤都能够被静态分析和优化。这种优化可以带来多方面的好处,包括更低的时延、更高的时钟频率、更少的资源占用和更高的吞吐量。

通过创建完美嵌套的循环,可以确保最内层循环包含所有计算逻辑,并且循环之间没有逻辑依赖或数据依赖,从而允许并行执行和流水线处理。此外,循环边界应该是常量,以便编译器在优化时能够静态确定迭代次数,从而更好地利用硬件资源。最终,这些优化可以使得硬件实现具有更高的性能、更低的时延,并且能够更好地适应特定的应用需求。

以下介绍三种典型的循环及其特点。

完美循环嵌套:

  • 仅限最内层循环才包含循环主体内容。
  • 在循环语句之间不指定任何逻辑。
  • 所有循环边界均为常量。

半完美循环嵌套:

  • 仅限最内层循环才包含循环主体内容。
  • 在循环语句之间不指定任何逻辑。
  • 最外层的循环边界可采用变量。

非完美循环嵌套:

  • 内层循环具有变量边界。
  • 循环主体未完全包含在内层循环内。

2. 示例

2.1 不完美循环

cpp 复制代码
void loop_imperfect(int A[20], int B[20]) {

    int i, j; // 定义循环变量i和j
    int acc;  // 定义累加器变量acc

    // 外层循环遍历数组B的每个元素
    for (i = 0; i < 20; i++) {
        acc = 0; // 在计算每个B[i]之前,将累加器归零

        // 内层循环遍历数组A,计算加权和
        for (j = 0; j < 20; j++) {
            acc += A[j] * j; // 将A[j]与其索引j相乘,并累加到acc
        }

        // 根据索引i的奇偶性决定B[i]的值
        if (i % 2 == 0)
            B[i] = acc / 20; // 如果i是偶数,B[i]为acc除以20的结果
        else
            B[i] = 0; // 如果i是奇数,B[i]设为0
    }
}

如果按照纯软件代码的标准, 这段代码有很大的问题:

  • 内层循环对于每个Bi都重新计算了加权和,即使Bi被设为0。这意味着有一半的计算是不必要的。
  • 由于j的范围是固定的,所以acc的计算可以在循环外部进行一次,然后根据需要分配给Bi

但是在Vitis HLS的角度,以上两点都不是问题,因为代码最终对应的是硬件的连接。事实上,以上代码的问题是:循环主体未完全包含在内层循环内。

2.2完美循环

cpp 复制代码
void loop_perfect(int A[20], int B[20]) {
    int i, j; // 定义循环变量i和j
    int acc;  // 定义累加器变量acc

    // 外层循环遍历数组B的每个元素
    for (i = 0; i < 20; i++) {
        // 内层循环遍历数组A,计算加权和
        for (j = 0; j < 20; j++) {

            if (j == 0) acc = 0; // 当j为0时,累加变量归零

            acc += A[j] * j; // 将A[j]与其索引j相乘,并累加到acc

            // 当内层循环到达最后一个元素时,根据i的奇偶输出B
            if (j == 19) {
                if (i % 2 == 0)
                    B[i] = acc / 20; // 如果i是偶数,B[i]为acc除以20的结果
                else
                    B[i] = 0; B[i] = 0; // 如果i是奇数,B[i]设为0
            }
        }
    }
}

相对于2.1的"改进点":

  • 累加器归零(if (j == 0) acc = 0;)的操作被移动到了内层循环的开始。
  • 条件判断(if (j == 19))被放置在内层循环的末尾。

如果按照纯软件代码的标准, 以上代码的问题更大了:

以上两个判断在每次内层循环时都会执行,这是不必要的,因为它只需要在内层循环开始或结束时执行一次!

2.3 HLS 眼中的循环

在 HLS 工具的眼中,完美循环嵌套是指循环结构被优化以便于硬件实现,从而实现最佳的性能和最低的资源使用。

原因如下:

  • **最内层循环包含所有计算:**所有的计算逻辑都应该包含在最内层循环中,这样可以最大化并行性和流水线效率。
  • **循环边界是常量:**这允许HLS工具在编译时确定循环的迭代次数,从而更好地优化生成的硬件。
  • **没有循环间的逻辑依赖:**循环之间不应该有数据依赖或逻辑依赖,这样可以避免不必要的延迟,并允许循环独立地并行执行。
  • **循环展开和流水线:**HLS工具可以自动展开循环并应用流水线技术,以提高并行性和吞吐量。
  • **数据访问模式是规则的:**数据访问应该是可预测的,以便于HLS工具进行有效的内存访问优化。

因为 HLS 会用到如下两个优化指令:

cpp 复制代码
#pragma HLS UNROLL // 指示HLS工具完全展开这个循环
#pragma HLS PIPELINE // 指示HLS工具流水线化这个循环

第一个指令,会让内层循环完全展开,即内层循环在一个周期内完成。

第二个指令,内层循环可流水线拆分,20个周期内完成。

所以,第二段代码实现了"最内层循环包含所有计算",是HLS眼中的完美循环。

3. 总结

本文分享了处理嵌套循环时的重要性以及创建完美嵌套循环的关键原则。优化循环结构对于实现最佳性能至关重要,完美循环嵌套的设计能够最大程度地利用硬件资源和并行计算能力,从而提高性能并降低时延。

相关推荐
志尊宝6 分钟前
Vue3 零基础每日笔记(047):动态路由与路由参数——:id 传参、query 传参、props 解耦
前端·javascript·vue.js·笔记·html5
鹿角片ljp12 分钟前
从 Kimi Cyber Reasoning 学习网络安全推理数据集:从 Reasoning SFT 到安全 Agent 数据设计
数据结构·算法
笨鸟先飞的橘猫14 分钟前
系统设计复盘2026-09-18
学习
威嵌神州24 分钟前
VxWorks6.9 BSP 适配:飞腾 D2000/FT2000/4 平台、驱动开发
驱动开发·嵌入式硬件·fpga开发·系统架构
.YM.Z30 分钟前
C++——【继承】的底层逻辑与【多态】实现机制
c++·多态·继承
汉克老师1 小时前
GESP2026年9月认证C++三级( 第一部分选择题(1~7题)精讲
c++·gesp·小学生·学c++编程
沪上企服通1 小时前
旧账系统的信创迁移工程:从 MySQL/Oracle 到国产库、国密与电子会计档案闭环
数据库·笔记·数据库架构
传奇开心果编程1 小时前
【Rust入门练中学】 第1课:从零开始
开发语言·学习·rust
residual_fan1 小时前
航空发动机故障诊断专用智能体(三):基于对比学习的时序特征区分方法
人工智能·算法·数据挖掘·数据分析
高亦真1 小时前
今天是学习嵌入式的第39天
linux·学习·算法