栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【Vitis Accel】3 - HLS Kernel 示例代码分析(1/2)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【Vitis Accel】3 - HLS Kernel 示例代码分析(1/2)

目录
  • 前言
  • 代码分析:Kernel - vadd.cpp
    • 1. 代码结构
    • 2. 关键词解释
    • 3. 代码逐段分析
      • 3.1 参数定义
      • 3.2 整体结构
      • 3.3 load_input
      • 3.4 compute_add
      • 3.5 store_result
      • 3.6 vadd kernel


前言

前一篇文章大致介绍了 Vitis 开发环境,那么接下来通过实现向量加法的简单 Hello World 进一步了解 Vitis 的 Host 和 Kernel 结构。

工程文件:Github - Hello World (HLS C/C++ Kernel)


代码分析:Kernel - vadd.cpp 1. 代码结构

代码结构:load / compute / store

2. 关键词解释
  • load / store:尽可能高效地完成数据在 Kernel 和 Host 之间的流动
  • compute:表现为一系列运算函数(compute functions),数据通过 HLS streams 传递,包含一组嵌套的循环。
  • HLS steams:hls::stream在运算函数之间传递数据,具有阻塞(blocking)逻辑的特征,可以在函数之间同步变量值
  • Dataflow pragma:指示 compiler 使能任务级(task-level)流水,是并行、流水执行 load / compute / store 的必要步骤
  • vector 数据类型:hls::vector,vector 的长度由 I/O 端口的宽度决定。将计算单元的宽度设置成与 I/O 端口的宽度相同可以更高效的使用资源。
3. 代码逐段分析 3.1 参数定义
// vdd.cpp line 61-70
// Includes
#include 
#include 
#include "assert.h"

#define MEMORY_DWIDTH 512
#define SIZEOF_WORD 4
#define NUM_WORDS ((MEMORY_DWIDTH) / (8 * SIZEOF_WORD))

#define DATA_SIZE 4096

// TRIPCOUNT identifier
const int c_size = DATA_SIZE;
  • 可以在 Includes中看到我们将要使用的两种数据类型hls::stream和hls::vector。前者在运算函数之间阻塞地传递数据,后者主要用于 I/O 端口。

  • 在计算NUM_WORDS时,由于一个 kernel 端口的最大值为 512 bits,因此有

    NUM_WORDS = 512 bits / (8 bits per byte * 4 byte per word)

  • DATA_SIZE规定了我们实际需要运算的数据长度,一般远长于 I/O 端口宽度。在本例中,我们待求和的数据是DATA_SIZE个 int 型数据

// host.cpp line 29
size_t vector_size_bytes = sizeof(int) * DATA_SIZE;
  • 最后一句对tripcount的设置与 HLS pragma loop_tripcount 的设置有关。语法为

    #pragma HLS loop_tripcount min= max= avg=
    

    min, max, avg 分别对应循环迭代的最小、最大和平均次数。

    从系列的第一篇文章,我们知道,Vitis HLS 工具计算循环的总延迟(latency),即执行循环所有迭代的时钟周期数。而循环延迟是循环迭代次数,即tripcount的函数。

    tripcount可能是一个常数值,也可能取决于循环表达式中使用的变量值,或者取决于循环内使用的控制语句。 在某些情况下,HLS 工具无法确定循环迭代次数,因此也无法得到延迟,例如:tripcount是循环的输入参数,或者是由动态操作计算求得的变量等。

    考虑到上述情况,LOOP_TRIPCOUNT pragma 允许指定循环的最小、最大和平均迭代次数,使工具可以分析循环延迟如何影响最终报告中的总设计延迟,确定适当的设计优化。

3.2 整体结构

先跳到文件的最后,看整个数据流 dataflow 的定义

//vadd.cpp line 136-141
#pragma HLS dataflow
    load_input(in1, in1_stream, vSize);
    load_input(in2, in2_stream, vSize);
    compute_add(in1_stream, in2_stream, out_stream, vSize);
    store_result(out, out_stream, vSize);

对比之前我们看到的框图,整个流程就非常清晰了。

那么接下来我们的工作,就是具体实现这三个函数

3.3 load_input

代码如下

// vadd.cpp line 75-83
static void load_input(hls::vector* in,
                       hls::stream >& inStream,
                       int vSize) {
mem_rd:
    for (int i = 0; i < vSize; i++) {
#pragma HLS LOOP_TRIPCOUNT min = c_size max = c_size
        inStream << in[i];
    }
}

函数主要完成的功能就是,从global memory中读取操作数,存入stream,以在 kernel 上完成运算操作。可以看到,函数的第一个输入参数即待读取操作数的地址,第二个就是inStream,而vsize由操作数的位宽以及 I/O 端口位宽决定。最终instream将存有完整的源操作数。

3.4 compute_add
static void compute_add(hls::stream >& in1_stream,
                        hls::stream >& in2_stream,
                        hls::stream >& out_stream,
                        int vSize) {
// The kernel is operating with vector of NUM_WORDS integers. The + operator performs
// an element-wise add, resulting in NUM_WORDS parallel additions.
execute:
    for (int i = 0; i < vSize; i++) {
#pragma HLS LOOP_TRIPCOUNT min = c_size max = c_size
        out_stream << (in1_stream.read() + in2_stream.read());
    }
}

函数主要完成的功能就是,读取两个操作数的值,相加,输出。这里的相加由NUM_WORDS(即 vector 长度)个并行的 element-wise 加法完成,即第一个 vector 的第 k 个元素与第二个 vector 的第 k 个元素对应相加,写入结果的第 k 个元素。

3.5 store_result
//vadd.cpp line 98-106
static void store_result(hls::vector* out,
                         hls::stream >& out_stream,
                         int vSize) {
mem_wr:
    for (int i = 0; i < vSize; i++) {
#pragma HLS LOOP_TRIPCOUNT min = c_size max = c_size
        out[i] = out_stream.read();
    }
}

与 load_input类似,这里就不加赘述了。

3.6 vadd kernel
//vadd.cpp line 108-143
extern "C" {



void vadd(hls::vector* in1,
          hls::vector* in2,
          hls::vector* out,
          int size) {
          
#pragma HLS INTERFACE m_axi port = in1 bundle = gmem0
#pragma HLS INTERFACE m_axi port = in2 bundle = gmem1
#pragma HLS INTERFACE m_axi port = out bundle = gmem0

    static hls::stream > in1_stream("input_stream_1");
    static hls::stream > in2_stream("input_stream_2");
    static hls::stream > out_stream("output_stream");

    // Since NUM_WORDS values are processed
    // in parallel per loop iteration, the for loop only needs to iterate 'size / NUM_WORDS' times.
    assert(size % NUM_WORDS == 0);
    int vSize = size / NUM_WORDS;
    
#pragma HLS dataflow
    load_input(in1, in1_stream, vSize);
    load_input(in2, in2_stream, vSize);
    compute_add(in1_stream, in2_stream, out_stream, vSize);
    store_result(out, out_stream, vSize);
}
}
  • 首先是输入参数,三个操作数的地址,以及操作数的位宽

  • 接着是 HLS INTERFACE pragma,

    #pragma HLS INTERFACE m_axi port = in1 bundle = gmem0
    #pragma HLS INTERFACE m_axi port = in2 bundle = gmem1
    #pragma HLS INTERFACE m_axi port = out bundle = gmem0 
    

    这里,Vitis 内核有一个 s_axilite 接口,用于 Host 对 Kernel 的配置。 所有 global memory 访问参数都与 m_axi(AXI Master)相关联。

    根据需求,也可以创建多个接口。 例如,当需要同时访问 global memory 时,用户可以创建多个 m_axi(AXI Master),连接到不同的参数。

  • 接下来是对 stream 的定义,

    static hls::stream > in1_stream("input_stream_1");
    static hls::stream > in2_stream("input_stream_2");
    static hls::stream > out_stream("output_stream");
    

    通常存储在数组中的数据以顺序方式消耗或产生。在这里,更有效的通信机制是使用由 STREAM pragma 指定的流数据,即使用 FIFO 而不是 RAM。

  • 由于NUM_WORDS个数在每次循环迭代中并行计算,因此 for 循环只需要迭代 size/NUM_WORDS 次。

  • 最后就是我们之前提过的,将 kernel 中的向量加法分为 4 个子任务,使用 Dataflow 并发执行。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/298317.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号