3. HLS工具链入门:典型HLS工具(Vivado HLS/Vitis HLS)安装与工作流程

好,咱们今天聊聊HLS工具链。说白了,就是怎么把C/C++代码变成硬件电路的那一套家伙事儿。

我刚开始接触HLS那会儿,心里也犯嘀咕:这玩意儿靠谱吗?后来在几个项目里摸爬滚打,才慢慢摸清了门道。今天我就把Vivado HLS和Vitis HLS的安装流程、工作套路,掰开了揉碎了讲给你听。

3.1 工具概览:Vivado HLS vs Vitis HLS

先搞清楚这两个工具的关系。Vivado HLS是Xilinx早期的HLS工具,后来被整合进了Vitis统一开发平台。说白了,Vitis HLS就是Vivado HLS的升级版。

特性 Vivado HLS Vitis HLS
推出时间 2012年左右 2019年
集成环境 独立GUI Vitis统一平台
语言支持 C/C++/SystemC C/C++/SystemC
优化指令 #pragma HLS #pragma HLS + 新特性
调试能力 基础波形查看 增强的协同仿真

我个人习惯用Vitis HLS,毕竟新工具对C++17的支持更好,调试起来也顺手。但如果你还在维护老项目,Vivado HLS也完全够用。

3.2 安装流程:别踩这些坑

安装这事儿,看着简单,其实门道不少。我曾经在一台Ubuntu 18.04上折腾了整整一下午,就为了搞定一个库依赖问题。

注意:Vitis HLS对操作系统有明确要求。Ubuntu 20.04 LTS和CentOS 7.4是官方推荐。别用太新的系统,比如Ubuntu 22.04,有些库不兼容。

3.2.1 下载与解压

去Xilinx官网下载Vitis统一安装包。注意,这个包很大,大概20GB左右。下载完解压:

tar -xzvf Xilinx_Vitis_2023.1_0522_1443.tar.gz
cd Xilinx_Vitis_2023.1_0522_1443

3.2.2 安装依赖库

这一步最容易出问题。我建议你提前装好这些库:

sudo apt-get update
sudo apt-get install libncurses5 libncurses5-dev libncursesw5
sudo apt-get install libtinfo5 libtinfo-dev
sudo apt-get install libc6-i386 lib32stdc++6 lib32gcc-s1

嗯,这里要注意,不同Ubuntu版本可能缺不同的库。如果安装过程中报错,别慌,看错误信息缺什么就装什么。

3.2.3 运行安装脚本

./xsetup

图形化界面会弹出来。选择Vitis HLS组件就行,不用全装。全装的话,硬盘空间至少留100GB。

小技巧:安装路径不要有中文和空格。我习惯放在 /opt/Xilinx/ 下,方便多用户使用。

3.3 工作流程:从C到RTL的奇幻之旅

安装好了,咱们看看HLS的工作流程。说白了就三步:写代码、加约束、看结果。

核心流程:C/C++源码 → 综合(Synthesis) → 调度(Scheduling) → 绑定(Binding) → RTL输出

我画了张图,帮你理清这个流程:

HLS工具链工作流程 C/C++ 源码 综合 (Synthesis) 调度 (Scheduling) 绑定 RTL 输出 优化指令 测试激励 (Testbench) 图3-1:HLS工具链核心工作流程 优化指令:PIPELINE、UNROLL、ARRAY_PARTITION、DATAFLOW 等

3.4 实战:跑通第一个HLS工程

光说不练假把式。咱们写个简单的FIR滤波器,看看整个流程怎么走。

3.4.1 编写C代码

// fir.c
#include <ap_int.h>
#include <hls_stream.h>

#define N 11

void fir(hls::stream<ap_int<16>> &in,
         hls::stream<ap_int<16>> &out,
         const ap_int<16> coeff[N]) {
#pragma HLS INTERFACE axis port=in
#pragma HLS INTERFACE axis port=out
#pragma HLS INTERFACE s_axilite port=coeff

    static ap_int<16> shift_reg[N];
#pragma HLS ARRAY_PARTITION variable=shift_reg complete dim=1

    ap_int<16> acc = 0;
    ap_int<16> data_in;

    in.read(data_in);

    for (int i = N-1; i > 0; i--) {
#pragma HLS UNROLL
        shift_reg[i] = shift_reg[i-1];
    }
    shift_reg[0] = data_in;

    for (int i = 0; i < N; i++) {
#pragma HLS PIPELINE
        acc += shift_reg[i] * coeff[i];
    }

    out.write(acc);
}

这段代码里,我用了几个关键的pragma指令。你想想看,#pragma HLS PIPELINE 就是告诉工具:这个循环要流水线化,每个时钟周期处理一次乘法累加。

3.4.2 编写测试激励

// tb_fir.cpp
#include <iostream>
#include <hls_stream.h>
#include <ap_int.h>

int main() {
    hls::stream<ap_int<16>> in, out;
    ap_int<16> coeff[N] = {1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1};

    // 输入测试数据
    for (int i = 0; i < 100; i++) {
        in.write(i);
        fir(in, out, coeff);
        std::cout << "out[" << i << "] = " << out.read() << std::endl;
    }

    return 0;
}

3.4.3 运行综合

在Vitis HLS GUI里,新建工程,添加fir.c和tb_fir.cpp。然后点击「C Synthesis」按钮。工具会开始综合,生成调度结果和RTL代码。

经验之谈:综合完成后,一定要看「Performance & Resource Estimates」报告。那里会告诉你延迟(Latency)、间隔(Interval)和资源使用情况。我曾经有个项目,综合报告显示LUT用了120%,赶紧回去改代码。

3.5 常见问题与避坑指南

做HLS开发,有些坑是绕不开的。我把自己踩过的坑列出来,你遇到了直接对照着查。

  • 指针别名问题:C代码里如果用了指针,HLS工具可能无法判断它们是否指向同一块内存。解决办法是用 #pragma HLS INTERFACE 明确指定接口类型。
  • 循环边界不确定:HLS要求循环边界在编译时确定。如果用了变量作为循环边界,工具会报错。我建议用常量或者 #pragma HLS LOOP_TRIPCOUNT 给出估计值。
  • 数组综合成BRAM还是LUT:默认情况下,小数组会综合成LUT,大数组会综合成BRAM。如果你想强制用BRAM,可以用 #pragma HLS RESOURCE variable=my_array core=RAM_1P
特别注意:HLS综合出来的RTL代码,时序可能不满足要求。这时候别急着改C代码,先看看调度报告里的关键路径。我曾经有个项目,就是因为一个乘法器放在了关键路径上,导致频率上不去。后来用 #pragma HLS LATENCY 指定了乘法器的延迟,问题就解决了。

3.6 小结

嗯,今天的内容就这些。Vivado HLS和Vitis HLS的安装其实不难,关键是理解工作流程。从C代码到RTL,中间经历了综合、调度、绑定三个阶段。每个阶段都有对应的优化指令,用好了事半功倍。

我个人觉得,HLS工具链最妙的地方在于:你可以用软件思维写硬件,但又能通过优化指令控制硬件的细节。说白了,就是「既要又要」——既要开发效率,又要硬件性能。

好了,去装个Vitis HLS试试吧。有问题随时回来翻翻这篇笔记。

专注资料整理