Vivado HLS高效设计实践

Vivado HLS在高效系统设计中的评估

摘要

高级综合 (HLS) 工具被誉为弥合设计生产率差距的最有前景的方法之一,尤其适用于可重构系统。这些工具在可能带来性能和/或硅片成本代价的同时,提高了设计人员的生产率,尽管设计人员在降低这两者方面可以发挥重要作用。目前,设计人员面临的主要挑战之一是如何高效地使用厂商定义的方法论以及 HLS 工具的设计指南。因此,本文旨在帮助设计人员充分利用并优化采用官方 HLS 方法论,以实现三种基础算法,这些算法广泛应用于各类视频和图像处理应用中。其中一种是排序算法,其余两种是用于遍历树/图数据结构的算法。本文研究的对象是一款广受欢迎的 HLS 工具——即 Vivado HLS,作者的经验与分析有助于更高效地使用该工具集,相比基于数据手册的简单方法,可显著提升设计生产率;为完整性起见,所有生成的可综合模型的性能也一并提供。

关键词 :高层次综合;可重构计算;现场可编程门阵列;数据结构

一、引言

高层次综合(HLS)目前是电子设计自动化(EDA)领域中一个快速发展的方向,近年来已提出了三十多种不同的工具[1]。这一趋势主要源于对快速且可靠的设计平台的迫切需求,该平台能够从用C、C++或基于C的变体(如 SystemC)描述的高层模型出发,生成其低层对应实现,并可在不同技术中实现,例如现场可编程门阵列(FPGAs)或专用集成电路(ASICs)。

此类技术有助于克服在寄存器传输级(RTL)[2]直接工作时所面临的挑战。精通VHDL或Verilog的程序员非常少,因此非专家必须达到高度的专业化水平才能高效地进行模型开发。此外,VHDL和Verilog的功能不如高级语言强大,导致源代码显著冗长,这增加了编码错误的可能性,并使得针对修改优化设计的过程变得耗时。

因此,高层次综合工具因其擅长基于高层模型生成生产级的 RTL模型而迅速流行起来。其中最受欢迎的高层次综合工具之一是Vivado HLS,它是Vivado设计套件[3]的重要组成部分。输入模型使用C或C++编写,输出则生成周期精确或非定时的 VHDL/Verilog RTL模型(以及SystemC)。此外,它还提供自动测试平台生成,它通过使用协同仿真来促进行为和功能验证。

尽管如此,使用Vivado HLS仍然是一个存在必须考虑的问题的过程。这些问题有时偏离了官方供应商提供的指南,而这正是本文的主要关注点。文中揭示了作者在三种关键的图像和视频处理算法中遇到的许多意外问题,即归并排序排序算法[4]和两种图遍历算法:深度优先搜索(DFS)和广度优先搜索(BFS)[5]。

此外,这些算法已在三个测试用例的背景下被用于评估工具在完整应用中的表现,即归并排序用于计算肯德尔相关系数 [6],,深度优先搜索用于迷宫求解算法,广度优先搜索用于图割[7]算法。

第二节讨论了所选算法的高层描述。第三节包含改进和补充官方Vivado HLS流程和指南的信息。第四节包含了作者认为最关键的典型HLS流程步骤,最后在第五节中给出了结论。

II. 高层次综合实现

本节讨论了实现过程中的重要方面,同时列出了一些实际的代码片段。设计过程中的一个重要环节是利用 Vivado HLS支持的关键增强功能,这些功能以指令的形式体现。这些指令在生成RTL模型时指导工具如何处理特定的代码段。本文考虑和/或使用的指令包括数据流、流水线、展开、数组分割、内联、循环展平以及接口[8]。

A. 深度优先搜索 & 迷宫求解器

深度优先搜索(DFS)是一种用于遍历图的方法,其基于从特定节点到图中所有其他节点寻找所有可能路径。随后,主深度优先搜索函数是递归的,由于递归无法直接映射到硬件,因此该算法已被修改为循环形式函数,该函数以流式方式输入图的边,并将起始顶点的ID作为稳定值。类似地,它以流式方式输出被访问顶点的顺序。

在输入端和输出端进行数据流传输时,使用了先进先出队列,并在此上下文中采用了 interface 指令。相同的指令也被用于定义输入

const int 节点数 = 4000;
const int 节点顺序 = 40;

函数 运行深度优先搜索(int * 起始节点, int * 目标节点, int * 已访问节点, int 首个节点) 
{
#pragma HLS 接口 ap fifo 起始节点=
#pragma HLS 接口 ap fifo 端口=目标节点
#pragma HLS INTERFACE ap fifo port=已访问节点 
#pragma HLS 接口 ap stable port=首个节点

int 临时访问数组[节点数]; 
边数组[节点数 * 节点阶数];

int i, j;

运行DFS标签0:for(i = 0; i < 节点数; i++)
{ 
    #pragma HLS UNROLL factor=2
    #pragma HLS 管线化
    临时访问数组[i]=-1; 
}

运行dfs label1:for(i = 0; i < 无边; i++) 
{ 
    #pragma HLS UNROLL factor=2
    #pragma HLS 管线化 
    边数组[i].边权重 = 0; 
    边数组[i].起始ID=起始ID[i]; 
    边数组[i].ID = 0;
    边数组[i].目标ID=目标ID[i];
}

深度优先搜索(边数组, 临时访问数组, 起始ID, 节点数, 边数);

运行深度优先搜索 label2:for(i = 0; i < 节点数; i++)
{ 
    #pragma HLS UNROLL factor=2
    #pragma HLS 管线化
}

第一个顶点稳定后,深度优先搜索的顶层函数包含三个 for 循环,用于初始化输入数据结构并将结果传输到输出流。为此,已使用指令unroll和pipeline来并行映射每个初始化循环。

此外,DFS 算法本身是在一个函数中实现的,该函数包含一个用于遍历图的while循环,以及两个用于遍历图中节点及其各节点边的for循环。前者由于图的深度未预先定义,因此无法通过指令进行优化;但后两个for循环可以展开和流水线化,因为它们具有上限,即节点连接数及其相邻节点数。此外,由于这两个循环属于同一代码层级,还可以使用指令flatten对其进行展平。

此外,图的表示采用了用于图的边的静态关联列表。由于这些结构在空间和时间复杂度方面具有优势,顶点使用了静态表。最后,为了构建实际的图并调用深度优先搜索函数,在顶层模型中添加了额外的代码,如上面的代码段所示。

B. 广度优先搜索 & 图割

广度优先搜索(BFS)最初被发明用于寻找走出迷宫的最短路径,现被应用于图割[7]算法,该算法源于图论。图割在计算机视觉中被广泛使用,以通过应用max-flow min-cut 定理来解决能量最小化问题。计算最大流的一种非常流行的算法是BK算法。

/* 处理邻居 */
for (a0=节点数[i].first arc; a0; a0=arcs[a0].next arc) 
{
    j= arcs[a0].head 节点; 
    if ( !节点数[j].is sink && (a=节点数[j].parent arc) ) 
    {
        if (arcs[arcs[a0].sister arc ].r cap)set active(j);
        if (a!=TERMINAL && a!=ORPHAN && arcs[a].head 节点==i)
        {
            ; 
            set orphan rear(j); // 将 j 添加到收养列表的末尾
        }
    }
}

[10]在本研究中使用。此页面上的第二个代码段展示了图的广度优先遍历。这里,函数set active()跟踪已访问节点,而函数set orphan rear()将一个节点添加到列表中以进行进一步处理。

图割是全局优化方法,这意味着为了获得全局解,图内部需要进行某种communication。这限制了在硬件上的并行化可能性,因为它会带来显著的I/O开销。因此,所采用的策略是将现有的图问题分解为更小的子图。

这使得在提高片上并行化水平的同时,能够扩展到更大的问题规模,而这些问题原本会对片上内存提出更高需求。图分割包括两个主要阶段:首先,在分割处的数据会在两个子图中被复制,并且权重减半,同时通过一定的边界来定义重叠子图节点的数量;其次,通过使用特定的对偶变量,强制两个子图在所有交叉边上具有相同的权重。

随后,已开发出一个顶层引擎,用于包含所有独立的子图并执行数据加载。在数据通信方面,通过仲裁器定义了一个数据总线,该仲裁器将数据加载到一组先进先出队列中,范围从1到N,其中N是BK子图的总数。

图割实现的关键高层模块包括i)图结构定义,ii)多核实例化,iii)节点信息提取,以及iv)算法执行的启动。

在此实现中,由于不同核心之间的互连关系,无法实现自动并行化,因此未使用任何指令,且已将使用的最大核心数设置为16。

C. 归并排序 & 肯德尔相关系数

归并排序是一种基于分治和比较的数组排序算法。首先,它将数组分成两半;其次,递归地对这两半进行排序;最后,将结果合并。一种使用归并排序等排序算法的应用是肯德尔相关系数。具体而言,肯德尔tau (τ)等级相关系数是衡量两个变量之间依赖性强弱的指标。随后,人们提出了多种计算肯德尔系数的算法,其中最流行的一种是基于归并排序的由奈特在[11]中提出并在本工作中采用的方法。

在此实现中,数组已存储在块状随机存取存储器中,该存储器提供两个读写端口。展开超过2会导致时钟频率降低,硬件运行变慢

//分解输入
for(i= 0; i < len/2; i++) 
{
    #pragma HLS 管线化
    buf1[i]= arr1[i]
    buf2[i]= arr1[i+len/2]
    bufc1[i]= arr2[i]
    bufc2[i]= arr2[i+len/2]
}

//归并排序段

swapCount+=归并排序( buf1,temp1,bufc1,tempc1,len/2);
swapCount+=归并排序( buf2,temp2,bufc2,tempc2,len/2);

//合并已排序数组

for(i= 0; i < len/2; i++) 
{
    #pragma HLS 管线化
    arr1[i]=buf1[i]
    arr1[i+len/2]=buf2[i]
    arr2[i]=bufc1[i]
    arr2[i+len/2]=bufc2[i]
}

swapCount+=merge(arr1,bufs,arr2,bufs2,0,len/2,len);

设计,此外还能提高资源利用率。本页顶部展示了综合后的高层模型的顶层函数。该函数包含三个主要任务,即i)将输入分解为两个部分,ii)调用两次归并排序函数,以及iii)合并它们的结果。

Kendall相关性归并排序函数在对一个数组进行排序的同时,重新排列第二个数组。在本研究中,采用了非递归实现,因为Vivado HLS不支持递归方法。为此,使用了两个迭代函数,并结合了pipeline和unroll(两倍因子)指令以提升性能。

III. 工具评估

在本节中,我们介绍了关于Vivado HLS从等效的高层表示生成低层RTL模型能力的观察结果和实际注意事项。

A. 算法为中心的评估

RTL模型生成以及仿真过程基于以下特性:所使用的数据集、目标硬件和所采用的CPU。DFS迷宫求解器算法使用一个30x30的迷宫谜题,具有相同的起始位置和目标位置;硬件实现基于Virtex‐7 XC7VX415T FPGA,而软件执行则在2.5GHz主频、4GB内存的Intel i5上进行。

广度优先搜索算法在硬件实现中使用了16K个节点和64K条边,硬件实现基于Virtex‐7 XC7VX330T FPGA,软件执行在3GHz的Intel i5上进行。最后,归并排序算法基于2x10K输入时间序列,其硬件和软件执行分别针对 Virtex‐7 XC7V690T FPGA和3.2GHz的Intel i5。本工作所使用的Vivado HLS版本为2014.2。

1) 深度优先搜索 & 迷宫求解器

为了使生成的RTL代码能够被其他工具轻松移植或仿真,设计人员必须关注设计的输入/输出。特别是,标量输入必须在启动信号激活前至少一个时钟周期内设置为有效值。这指的是生成的RTL模块的输入/输出端口之一,例如ap_start,该端口本质上被激活以从空闲状态转移到处理状态。例如,顶层函数的标量参数已通过接口指令以ap_stable模式传递。ap_stable模式表示设计在整个处理周期内将相应的输入端口视为稳定状态。

此外,使用指令interface与FIFO接口意味着FIFO模块控制信号(即signal_empty_n())会控制整个仿真过程,因此在数据进入设计后应将其设置为逻辑“1”,因为逻辑“0”将导致仿真停止。

该特定实现的吞吐量需求极低,因此输入/输出问题并未影响系统性能,其性能与初始软件方案非常接近。换句话说,尽管将输入/输出接口更改为类FIFO结构可略微提升设计性能,但并未测得显著加速,否则其延迟可能比软件更长。具体而言,pipeline 指令带来的性能提升较小,这主要是由于DFS算法的基于控制的特性及其数据依赖所致。

然而,内存输入/输出技术的映射效果似乎不如输入/输出FIFO流场景,尤其是在数据被流式传输并存储在FPGA内部的情况下。如果在存在数据依赖的代码或循环次数未预定的循环上应用pipeline指令,可能会降低设计性能。此外,dataflow指令在可高度并行化代码的情况下可能带来非常好的效果,但在我们的控制导向算法中并未奏效。最后,unroll指令带来的性能改进有限。

这里的主要优势在于,使用高层次综合(HLS),工作在几天内就完成了,这大大缩短了设计实现时间。

2) 广度优先搜索 & 图割

与此实现相关的关键观察是关于在Vivado HLS中如何启动并行核心的。

// 这是不允许的

图 图 核心[32]; //图是一个对象(一个硬件核心)

for (int i = 0; i < 32; i++) 图核心[i].set trcap(i, trcap);

此处显示的编码风格无法被工具处理,因为不能显式引用每个核心,例如在循环中,尤其是在参数化的情况下。因此,解决此问题的一个可靠方法是实现一个包装函数,该函数为多个核心实现一个参数化控制器,从而能够被Vivado HLS高效且正确地处理。

随后,表一给出了BK算法实现的延迟和加速比结果。需要注意的是,这些结果并非由不同的高层次综合指令所决定,因为该特定算法没有有效的加速方法;相反,结果会根据并行化的程度而变化。因此,并行核心数量越多,延迟越小,硬件延迟越低,相对于无法实现任何并行化的软件实现而言,所获得的加速比就越大,因此软件实现只能达到一个延迟值。

表 I. 广度优先搜索 & BK算法性能结果

配置 / 并行化级别 总计节点 延迟(毫秒) HW 延迟(毫秒) SW 加速比
单核 16K 12.17 6.1 0.5x
8核 16K 1.79 6.1 3.3x
16核 16K 0.95 6.1 6.2x
3) 归并排序 & 肯德尔相关系数

计算肯德尔相关系数的算法基于两次归并排序算法的调用。通过将数组拆分为更小的数组,对它们进行排序并合并已排序的数组,可以实现并行化。然而,速度提升并不显著,特别是对于小数组,因为合并操作必须按顺序执行。

因此,该算法基于归并排序对第二个数组进行排序时所需的交换次数来实现,而第一个数组必须在计算交换次数之前完成排序。结果是,每次调用该算法都需要两次归并排序调用。

并行归并排序通过将输入的部分数据复制到新的数组中来实现,以便工具能够使用独立的块RAM来实现这些数组,从而避免产生依赖关系。在部分数组排序完成后,它会与其相邻的数组进行合并。这使得最终的设计能够并行排序,否则将需要在输入的块状随机存取存储器上使用大型多路复用器,导致时钟频率降低或只能进行串行操作。

表 II. 归并排序 & Kendall相关性性能结果

时间 (毫秒) 加速比 (无指令) 加速比 (有指令)
软件 / 硬件 (i5@3.1GHz) 4.6 1x -
无指令 17.8 0.26倍 1x
流水线 12 0.38倍 1.48倍
流水线 + 展开 10.1 0.46x 1.76x
并行归并排序 4 6.1 0.75x 2.9x
并行归并排序 16 3.2 1.4x 5.6x

表 II 展示了硬件实现在不同(指令)配置下的运行时结果。首先给出了软件运行时间,随后是无指令的实现。接着引入指令,并展示了并行归并排序实现的结果。进一步并行化带来的性能提升较小,且几乎达到了 FPGA 的 BRAM 资源的极限,从而导致时钟频率降低。这是因为每个归并排序核心都需要自己的 BRAM 分配。最后,加速比也是输入大小的函数,因为较小的数据集允许部署更多的并行归并排序核心,从而减少延迟。

B. 通用评估

一个初步的观察是,高层级C模型在C/RTL协仿真器上进行仿真,根据我们的测量,这能够对所需周期数产生准确的估计。因此,这确保了生成的RTL模型确实是周期精确的。

根据开发过程所花费的时间,可以明确支持使用Vivado HLS的一个主要根本因素是能够显著缩短实现时间。这适用于所有三个案例研究,且系统越复杂,开发加速比越高。

另一方面,Vivado HLS对SystemC描述的模型支持较弱,同时在math.h函数的支持方面也有限。例如,pow函数不被支持,大多数对数运算也不受支持,因此这些操作必须手动开发。

此外,Vivado HLS不能保证没有依赖关系的函数调用会并行实现。这个问题可以使用array partition directive来解决,但该方法在某种程度上“不稳定”,因为其可能导致工具流程因尚不完全明确的原因而崩溃。

就C综合阶段结束时提供的延迟估计而言,据观察其有时偏差显著,因此不可靠。另一方面,pipeline和unroll指令配合使用效果非常好,特别是当unroll与一个较小值的因子结合使用时。

随后,对于在多个独立数据集上执行相同算术运算的应用,强烈建议设计者尝试使用unroll和pipeline指令。这两个指令已被证实能够提高设计的并行性,并有助于生成高效的 RTL模型。

另一方面,具有较低固有并行性的应用程序需要更多的人工干预。这类应用程序类似于深度优先搜索和广度优先搜索,即更多涉及内存访问而非计算问题的问题。

这种场景可以作为一个更广泛的问题来处理,将其分解为多个较小的问题,然后并行运行。因此,可以将一个手动编写、可重复使用的小型设计核心定义为C++对象,然后手动实例化多次,使每个实例能够处理其对应的数据集。

最后,表III对所使用的Vivado HLS指令进行了简要说明。

表 III. Vivado HLS指令概览

名称 评论
PIPELINE 整体非常高效
UNROLL 数组 PARTITION 非常高效 – 实现了良好的加速比
可能会减慢设计速度
DATAFLOW 在没有依赖关系的情况下运行良好
INLINE 高效但会产生硬件开销
INTERFACE 始终用于输入/输出接口
LOOP FLATTEN 在增加面积开销的情况下效果良好

IV. 提出的HLS流程

到目前为止的观察结果与典型的高层次综合(HLS)流程相结合,以构建一种高效的设计方法。图1展示了HLS流程的标准步骤,以及作者认为非常重要的步骤(用红色虚线标出)。算法的高层描述最初以C/C++模型的形式进行修改,以消除非HLS兼容的问题指针等元素,并由HLS工具的指令进行补充。这产生了一个可进行处理的模型,即预综合模型。

随后,该工具生成RTL等效模型,并将其与测试平台文件结合进行协同仿真。在协同仿真中,可以验证RTL设计的功能,并研究其性能特征,例如延迟、资源利用率、时钟速度等。

所有这些都以报告文件的形式呈现,设计人员可据此考虑对目标算法的高层模型进行可能需要的修改。因此,该过程将重复进行,直到满足所有目标条件为止,然后便可将RTL模型用于转换、映射及布局布线工具中进行比特流生成。关于关键流程步骤,首先,设计人员必须设置高层模型的输入/输出,以确保生成的RTL模型具有可移植性。此外,必须仔细研究高层模型,若缺乏固有并行性,则应通过手动将算法分解为更小的独立单元,使其能够并行运行,从而对模型进行修改。

出于相同原因,数组划分指令可用于高层模型。此外,如果在循环中需要明确使用核心数量,则必须通过使用类似于前述的包装函数来实现。

指令 unroll 和 pipeline 对综合过程非常有益,因为它们有助于提高并行性和流水线效率,尤其是在多次使用算术运算且针对多组独立数据的情况下。

最后,Vivado HLS 缺乏任何重要的数学函数,最终必须手动编码实现。此外,协同仿真之前的任何延迟估计都被发现不可靠。

V. 结论

本文概述了作者在三个从高层到低层算法转换的案例中使用Vivado HLS工具时所积累的提炼的知识。这有助于评估官方指南,并提取出需要采取何种措施的有用结论。

感兴趣的三种不同计算算法是深度优先搜索、广度优先搜索和归并排序,这些算法因其在众多图像和视频处理系统中的广泛应用和受欢迎程度而被选中。

因此,根据我们的实际观察,如果算法本身具有并行性,则可以由高层次综合工具(如Vivado HLS)轻松高效地进行综合,而无需大量人工干预。对于不易并行化的数据处理方案,我们提出一种可手动实现的分治法,以实现对高层功能描述的高效综合。

在三个案例研究中的两个案例中,实现的模块比在多GHz的先进CPU上的软件执行更快。此外,在所有情况下,大多数可用的Vivado HLS指令都得到了利用,并且其中大部分被证明是高效的。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值