Vitis HLS 的Tiny Tutorial部分代码分析

对例子中的语法等的一点记录

背景介绍

  • HLS (high-level synthesis): 高级综合, 用 C/C++为FPGA开发 RTL IP.
  • Vitis HLS: 在Vivado 2020版本中替代原先的Vivado HLS, 功能略有差异,在Vivado HLS中的例子直接在Vitis HLS上跑会报错。详细比较见Vivado HLS和Vitis HLS什么区别?
  • HLS中的pragma语法: HLS Pragmas

内容

  • #pragma HLS dataflow
    • 默认情况下,Vitis HLS工具试图最小化延迟并提高并发性。但是,数据依赖性可能会限制这一点。DATAFLOW优化使函数或循环中的操作能够在上一个函数或循环完成其所有操作之前开始操作。(能够分析更细)。
    • 也能够解决两个不依赖循环不并行情况(类似于封装进函数)。
void diamond(data_t vecIn[N], data_t vecOut[N])
{
  data_t c1[N], c2[N], c3[N], c4[N];
#pragma HLS dataflow
  funcA(vecIn, c1, c2);
  funcB(c1, c3);
  funcC(c2, c4);
  funcD(c3, c4, vecOut);
}
  • #pragma HLS pipeline rewind
    • 构造一个连续循环的流水线。两次循环之间没有暂停。
    • 不能包含条件分支 (if-else)
    • 只支持循环,不支持函数的流水线化。
  for (int i = 0; i < N; i++)
  {
#pragma HLS pipeline rewind
#pragma HLS unroll factor = 2
    data_t t = in[i] * 3;
    out1[i] = t;
    out2[i] = t;
  }
  • #pragma HLS unroll factor = 2
    • 将循环展开多少次
for(int i = 0; i < X; i++) {
  pragma HLS unroll factor=2
  a[i] = b[i] + c[i];
}

变成:

for(int i = 0; i < X; i += 2) {
  a[i] = b[i] + c[i];
  if (i+1 >= X) break;
  a[i+1] = b[i+1] + c[i+1];
}
  • #pragma HLS function_instantiate variable=<variable>
    • FUNCTION_INSTANTIATE编译指示用于为函数的每个实例创建唯一的RTL实现,从而可以根据函数调用对每个实例进行本地优化。因为:调用函数时,函数的某些输入可以是恒定值,并使用它来简化周围的控制结构并生成更小的更优化的功能块
    • variable是必需的参数,用于定义要用作常量的函数参数。
char foo(char inval, char incr) {
#pragma HLS INLINE OFF
#pragma HLS FUNCTION_INSTANTIATE variable=incr
 return inval + incr;
}

void top(char inval1, char inval2, char inval3,
 char *outval1, char *outval2, char *outval3)
{
 *outval1 = foo(inval1,   0);
 *outval2 = foo(inval2,   1);
 *outval3 = foo(inval3, 100);
}
  • 不完美的循环(imperfect loop):
    • 内层循环上限是一个变量。
    • 循环体并非全部在最里层循环中。
    • 所以没法展开,会造成额外的进出循环的时钟周期。
    LOOP_I:for(i=0; i < 20; i++){
        acc = 0;
        LOOP_J: for(j=0; j < 20; j++){
            acc += A[j] * j;
        }
        if (i%2 == 0)
            B[i] = acc / 20;
        else
            B[i] = 0;
    }
  • 将不完美循环改写成完美循环的trick:使用条件语句
    LOOP_I:for(i=0; i < 20; i++){
        LOOP_J: for(j=0; j < 20; j++){
            if(j==0) acc = 0;
            acc += A[j] * j;
            if(j==19) {
                if (i%2 == 0)
                    B[i] = acc / 20;
                else
                    B[i] = 0;
            }
        }
    }
  • 循环可以没有label,HLS会生成默认label。

  • 循环上限为变量的优化方法: 使用条件语句

  LOOP_X:for (x=0;x<N-1; x++) {
    if (x<width) {
      out_accum += A[x];
    }
  }
  • 可以利用两个独立的模块来压缩流水的II (用模版实现较为方便,需要配合上多路选择器)
//                    +--proc-->[ II=2 ]--+
//                   /                     \               
// in -->[demux II=1]                       [mux II=1]---> out
//                   \                     /
//                    +--proc-->[ II=2 ]--+

#include "example.h"

//--------------------------------------------
template <int ID>
void proc(stream<int> &in, stream<int> &out)
{
    for (int i = 0; i < 25; i++)
    {
#pragma HLS PIPELINE II=2
#pragma HLS LATENCY min=2 max=2
        int var;
        in.read(var);
        out.write(var);
    }
}

//--------------------------------------------
void mux(stream<int> (&inter)[2], stream<int> &mux_output)
{
    int mux_sel = 0;
    for (int i = 0; i < 50; i++)
    {
#pragma HLS PIPELINE II=1
        int var;
        inter[mux_sel].read(var);
        mux_output.write(var);
        mux_sel = (mux_sel == 0) ? (1) : (0);
    }
}

//--------------------------------------------
void demux(stream<int> &in, stream<int> (&inter)[2])
{
    int demux_sel = 0;
    for (int i = 0; i < 50; i++)
    {
#pragma HLS PIPELINE II=1

        int var;
        in.read(var);
        inter[demux_sel].write(var);
        demux_sel = (demux_sel == 0) ? 1 : 0;
    }
}

void example(stream<int> &in, stream<int> &out)
{
#pragma HLS DATAFLOW

    stream<int> inter[2];
    stream<int> mux_in[2];

#pragma HLS STREAM variable = inter depth = 16
#pragma HLS STREAM variable = mux_in depth = 16

    demux(in, inter);
    proc<0>(inter[0], mux_in[0]);
    proc<1>(inter[1], mux_in[1]);
    mux(mux_in, out);
}