09 HLS基础

在数字信号处理过程中,很多算法使用c/C++实现这里使用HLs工具进行把c/C++的算法转化为RTL硬件。

Vitis HLS创建工程

  1. 打开创建工程向导
  2. 输入工程名称,选择存储路径
  3. 设置顶层函数名称,添加或创建设计文件,这些步骤可以跳过,待工程创建完成后在设置或添、创建设计文件
  4. 添加或创建测试激励文件,这些步骤可以跳过,待工程创建完成后在添、创建测试激励文件
  5. 选择器件

在工程中添加文件

  1. 打开创建设计文件的向导

  2. 选择存储路径,输入文件名,点击保存

  3. 在以同样的方式创建"fir_filter.h"文件

  4. 打开创建测试激励文件的向导

  5. 选择存储路径,输入文件名,点击保存

  6. 编辑"rx_ask_fir.h"文件,内容如下,这是一个FIR滤波器,系数采用matlab的滤波器设计工具生成

c 复制代码
#ifndef _FIR_FILTER_H_
#define _FIR_FILTER_H_

#include <stdlib.h>
#include <ap_int.h>

//FIR滤波器系数中小数位数
#define COEF_Q      10
//FIR滤波器阶数
#define FIR_BL      64
//FIR滤波器系数,可以使用matlab生成
#define FIR_COEF      {1, 4, 2, 3, 3, 3, 3, 2, 1, -1, -3, -5, -8, -10, -12, -13, -13, -12, -10, -6, 0, 7, 15, 25, 36, 46, 57, 66, 75, 82, 87, 89, 89, 87, 82, 75, 66, 57, 46, 36, 25, 15, 7, 0, -6, -10, -12, -13, -13, -12, -10, -8, -5, -3, -1, 1, 2, 3, 3, 3, 3, 2, 4, 1}

//FIR滤波器
void fir_filter(const ap_int<12> *in, ap_int<12> *out);

#endif //_FIR_FILTER_H_
  1. 编辑"rx_ask_fir.cpp"文件,内容如下:
c 复制代码
#include "fir_filter.h"

//滤波器系数,对应公式中的h(0)~h(n)
static const ap_int<8> coef[FIR_BL] = FIR_COEF;
//状态,state[0]延迟1拍(对应公式中的x(n)),state[1]延迟2拍对应公式中的x(n-1)),state[2]延迟3拍对应公式中的x(n-2))......
static ap_int<12> state[FIR_BL] = {0};

void fir_filter(const ap_int<12> *in, ap_int<12> *out)
{
#pragma HLS INTERFACE mode=axis register_mode=both port=out register
#pragma HLS INTERFACE mode=axis register_mode=both port=in register
#pragma HLS INTERFACE mode=ap_ctrl_none port=return
#pragma HLS PIPELINE II=1
    ap_int<32> sum = 0;

    //延时打拍,将数据往高位方向移动一个节拍
    for(int loop = FIR_BL-1; loop > 0; loop--)
    {
        state[loop] = state[loop-1];
    }
    state[0] = *in;

    //卷积
	for(int loop = 0; loop < FIR_BL; loop++)
    {
    	sum += state[loop] * coef[loop];
    }

    *out = sum >> COEF_Q;
}
  1. 编辑"test_fir_filter.cpp"文件,文件内容如下:
c 复制代码
#include <math.h>
#include "fir_filter.h"

#define AM      50

int main(void)
{
	ap_int<12> signal0;
    ap_int<12> signal1;
    ap_int<12> signal2;
    ap_int<12> output;
    FILE  *fp;
    const int  SAMPLES = 1024;
    double f1 = 1000000.0;
    double f2 = 2500000.0;
    double fs = 40000000;

    fp=fopen("output.csv","w");

    for(int loop = 0; loop < SAMPLES; loop++)
    {
        //2*pi*f*t; t=loop/fs
        //2*pi*f*loop/fs
        signal0 = (ap_int<12>)(AM * sin(2 * M_PI * f1 * loop / fs));
        signal1 = (ap_int<12>)(AM * sin(2 * M_PI * f2 * loop / fs));
        signal2 = signal0 + signal1;
        fir_filter(&signal2, &output);

        fprintf(stdout,"output[%d] = %d\r\n", loop, (int)output);
        fprintf(fp,"%d,%d,%d,%d\r\n", (int)signal0, (int)signal1, (int)signal2, (int)output);
    }

    fclose(fp);

    return 0;
}

添加头文件路径

此工程所有文件均放在工程根目录下,因此无需添加头文件路径,不过这里还是介绍一下如何添加头文件路径,以便后面能管理复杂工程。

  1. 打开工程属性配置页面
  2. 添加头文件,注意GUN C和GNU C++均需要设置

debug C/C++

  1. 打开debug界面
  2. 通过CTRL+B编译工程或者按如下方法编译工程
  3. 打开debug设置界面
  4. 选择默认的Debug配置,然后启动Debug
  5. 在调试界面可以提供下面图片所示按键控制程序执行
  6. 退出Debug界面

进行C/C++仿真

  1. 启动C仿真
  2. 进行仿真配置,保持默认即可
  3. 仿真结果输出

进行RTL仿真

  1. 为顶层函数添加PIPELINE属性

  2. 添加IP核控制接口属性

  3. 添加in变量的接口属性

  4. 以同样的方法将out变量接口设置为AXIS属性
  5. 打开顶层函数设置页面,如创建工程时指定了,且不需要更换顶层函数可以忽略此步
  6. 选择顶层函数,如创建工程时指定了,且不需要更换顶层函数可以忽略此步
  7. 生成RTL文件
  8. 生成配置保持默认即可
  9. 打开RTL仿真配置界面
  10. 选择ModelSim,并设置ModelSim仿真库,然后启动仿真
  11. 仿真完成后会在console中输出"Finished C/RTL cosimulation.",并在工程的solution1\sim\verilog目录生成一个.wlf的波形文件
  12. 用ModelSim打开波形文件

导出IP核

  1. 打开IP导出配置页面
  2. 配置并导出IP,配置保持默认即可
  3. "ERROR: IMPL 213-28 Failed to generate IP."错误的处理
    错误是Vitis的一个bug,通过安装补丁即可解决,补丁下载路径和安装指定路径如下:
    https://adaptivesupport.amd.com/s/article/76960?language=en_US
    有时候AMD官网无法正常下载,这里我将其上传至码云了,可以通过码云下载码云下载
    下载好后在vivado2021.2的安装目录解压y2k22_patch-1.2.zip文件,解压完成后如下所示:

    然后再安装目录下代开cmd命令行,如下图所示:

    在cmd中执行命令"Vivado\2021.2\tps\win64\python-3.8.3\python.exe y2k22_patch\patch.py",执行结果如下图所示
  4. 补丁安装完成后重启VITIS,然后再重新执行步骤1和步骤2便可导出IP核,导出的IP在下列目录中,接下来可以按照使用自定义IP核的步骤再Vivado使用此IP核。

HLS-C特有数据类型

HLS-C特有数据类型定义在<ap_cint.h>文件中,实现有些任意宽度变量的功能,如int12表示12位有符号整型,uint12 表示12位无符号整型,同时还提供了有些操作变量指定位的函数,如apint_get_bit(Val, Bit)、apint_set_bit(Val, Bit, Repl)、apint_get_range(Val, Hi, Lo)、apint_set_range(Val, Hi, Lo, Repl)等。

注意:从2021版本开始不支持<ap_cint.h>文件,同时赛灵思官方也建议使用C++开发

HLS-C++特有数据类型

HLS-C特有数据类型定义在<ap_int.h>、<ap_fixed.h>、<hls_stream.h>文件中。

  1. <ap_int.h>
c 复制代码
<ap_int.h>提供定义任意宽度整型变量的功能,可以用于替换<ap_cint.h>中的功能。
		有符号定义格式为"ap_int<位宽> 变量名",如"ap_int<12> a"表示定义12位有符号整型变量a。
		无符号定义格式为"ap_uint<位宽> 变量名",如"ap_uint<12> b"表示定义12位无符号整型变量b。
  1. <ap_fixed.h>
c 复制代码
<ap_fixed.h>提供了定点数相关功能,有符号定义格式为"ap_fixed<W, I, Q, O, N> 变量名",无符号定义格式为"ap_ufixed<W, I, Q, O, N> 变量名" ,其中特殊字段含义如下。
W: 变量宽度
I:整数位宽(对于有符号数包括符号位)。
Q:量化模式
​	​	AP_RND:向正无穷舍入
​	​	AP_RND_MIN_INF:向负无穷舍入
​	​	AP_RND_INF:是以上两种模式的结合,会把正数向正无穷舍入,负数向负无穷舍入
​	​	AP_RND_ZERO:向0进行舍入
​	​	AP_RND_CONV:进行舍入取决于需要截取掉的最低位,截取掉的最低位为1则将此位累加到截取位之后的结果上
​	​	AP_TRN(默认):截取模式,将截取的位直接删除
​	​	AP_TRN_ZERO:正数值和AP_TRN一样,而负数值是AP_RND_ZERO模式
O:溢出模式
​	​	AP_SAT:超出位宽决定的最大值或者最小值则用最大值或最小值替代
​	​	AP_SAT_ZERO:超出位宽决定的最大值或者最小值则设置为0
​	​	AP_SAT_SYM:这个可以理解AP_SAT的对称模式,如8位的有符号数表示最大范围是+127到-128,当输入-128这时候就会被替换为-127,无符号则约束到0~最大值范围
​	​	AP_WRAP N=0(默认):超出位宽部分直接截掉(截掉高位),若截掉后最高位为1还则认为是负数,如8bit的385被截断后为-127,8bit的129被截断后为-127
​	​	AP_WRAP N>0:超出位宽部分直接截掉,但是不会改变符号位(截掉高位)
如ap_fixed<16, 8>表示定点数总宽度16位,其中整数占8位,小数占7位,符号占1位,量化模式为AP_TRN_ZERO,溢出模式均为AP_WRAP N=0

常用HLS 编译指示

HLS 工具提供各种编译指示,用于最优化设计、降低时延、提升吞吐量性能,以及减少生成的 RTL 代码的面积和器件资源利用率。这些编译指示可直接添加到内核源代码中,下表是HLS支持的编译指示,这里只介绍几个常用的编译指示。

  1. pragma HLS interface
    用于指定输入输出的接口类型,如"#pragma HLS INTERFACE mode=ap_ctrl_none port=return"表示模块的控制接口为"ap_ctrl_none",如"#pragma HLS INTERFACE mode=axis register_mode=both port=in register"表示"in"变量的接口类型为AXIS。
  2. pragma HLS pipeline
    用于指定函数的启动时间间隔,即每隔多久可以处理一个新的输入,如"#pragma HLS PIPELINE II=1"表示每个时钟周期可以处理一个新的输入。