第二讲:HLS工具初探——Vivado HLS界面介绍与第一个工程

好,咱们正式开始动手了。这一讲,我带你把Vivado HLS这个工具摸个底。说白了,就是让你知道它长什么样、怎么用、跑起来是什么感觉。

我记得我第一次打开Vivado HLS时,看着满屏的英文菜单和按钮,说实话有点懵。但别怕,这东西用熟了你会发现,它比直接写Verilog要友好得多。

2.1 Vivado HLS界面概览

打开Vivado HLS后,你会看到几个主要区域。我习惯把它们分成三块:

  • 工程管理区(左侧):显示你的源文件、测试文件、解决方案等。有点像VS Code的资源管理器。
  • 信息输出区(底部):跑仿真、综合时的日志和警告都在这里。嗯,这里要养成随时看的习惯。
  • 主编辑区(中间):写代码、看报告的地方。

你可能会问:「这么多窗口,我该看哪个?」其实刚开始,你只需要关注两个按钮:C仿真和C综合。其他的后面慢慢熟悉。

小技巧:我个人习惯把界面布局调成「默认+加大字体」。Vivado HLS默认字体偏小,看久了眼睛累。在Tools → Options里可以调。

2.2 创建第一个HLS工程:Hello World

学任何语言,第一个程序都是Hello World。HLS也不例外。虽然它不输出到屏幕,但我们可以写一个简单的加法函数,跑通整个流程。

操作步骤很简单:

  1. 点击 Create New Project
  2. 给工程起个名字,比如 hello_hls
  3. 选择顶层函数(后面会讲这是什么意思)
  4. 添加源文件,然后直接Finish

好,现在我们来写代码。新建一个源文件,叫 hello.cpp:

#include <ap_int.h>

int add(int a, int b) {
    return a + b;
}

就这么简单?对,就这么简单。你可能会想:「这不就是C语言吗?」没错,HLS的输入就是C/C++。但注意,不是所有C代码都能综合成硬件。后面我会专门讲哪些能写、哪些不能写。

然后写个测试文件 hello_tb.cpp:

#include <iostream>
#include "hello.h"

int main() {
    int result = add(3, 5);
    if (result == 8) {
        std::cout << "Test passed!" << std::endl;
        return 0;
    } else {
        std::cout << "Test failed!" << std::endl;
        return 1;
    }
}
注意:测试文件里的 printf 或 cout 只在仿真时有效。综合成硬件后,这些打印语句会被忽略。我曾经犯过这个错——在综合后的代码里指望看到打印信息,结果啥也没有。

2.3 C仿真:先跑一遍软件

写完了代码,第一步不是综合,而是C仿真。说白了,就是用你的电脑CPU先跑一遍这个C程序,看看逻辑对不对。

点击工具栏上的绿色三角形按钮(C Simulation),Vivado HLS会调用GCC或VS编译器来编译运行你的测试文件。如果一切正常,你会看到控制台输出:

Test passed!

为什么要先做这一步?因为C仿真快啊!综合一次可能要几分钟甚至几小时,而C仿真几秒钟就出结果。我个人的习惯是:先仿真通过,再考虑综合。这样可以快速排除逻辑错误。

2.4 C综合:把C变成硬件

C仿真通过后,就可以点那个黄色的「小齿轮」按钮——C综合了。这一步,Vivado HLS会把你的C代码翻译成RTL(寄存器传输级)代码,也就是Verilog或VHDL。

综合过程中,你会看到进度条在跑。这时候可以喝杯咖啡,或者看看综合报告。综合完成后,工具会自动弹出综合报告窗口。

核心概念:C综合的本质是「调度」和「绑定」。调度决定每个操作在哪个时钟周期执行,绑定决定用哪种硬件资源(比如加法器、乘法器)来实现。这两个步骤决定了你最终电路的性能和面积。

2.5 理解综合报告

综合报告是HLS里最重要的东西。它告诉你:你的设计能跑多快、占多大面积、用了多少资源。

报告主要包含这几块:

报告项 含义 我的经验
Latency 从输入到输出需要的时钟周期数 如果latency太大,考虑加pipeline
II (Initiation Interval) 两次启动之间的最小间隔 II=1是理想状态,但很难做到
DSP48E 用了多少个DSP单元 乘法、乘加运算会消耗DSP
BRAM 用了多少块Block RAM 数组、查找表会消耗BRAM
FF/LUT 触发器和查找表 逻辑运算和状态机消耗这些

举个例子,刚才的加法函数综合后,报告大概会显示:

  • Latency = 2 个时钟周期(因为输入寄存和输出寄存各占一拍)
  • DSP = 0(加法可以用LUT实现)
  • FF/LUT = 几十个(非常小)

你可能会问:「为什么加法要2个周期?不是一拍就能算完吗?」嗯,这里有个细节:HLS默认会在输入和输出插入寄存器,以保证时序。如果你追求极致性能,可以用 #pragma HLS pipeline 来优化。这个我们后面会详细讲。

避坑指南:我曾经遇到一个项目,综合报告显示资源占用只有30%,但布局布线后却报资源不足。后来发现是数组被综合成了大量LUT,而不是BRAM。解决办法是加 #pragma HLS bind_storage 强制指定存储类型。所以,综合报告只是预估,最终要以布局布线结果为准。

2.6 本章知识体系

为了让你更直观地理解这一讲的内容,我画了一张流程图,展示HLS从代码到硬件的完整路径:

HLS开发流程(从C到RTL) C/C++ 源代码 hello.cpp + hello.h C 仿真 验证功能正确性 C 综合 调度 + 绑定 综合报告 Latency / II / 资源 RTL 代码 Verilog / VHDL 根据报告优化代码 虚线表示:根据综合报告反馈优化代码,形成迭代开发闭环

这张图清晰地展示了我们今天做的每一步:从写C代码,到C仿真验证,再到C综合生成RTL,最后通过综合报告来评估设计质量。注意那条虚线——它代表一个迭代循环。实际项目中,你很少能一次综合就满意,往往要反复调整代码、看报告、再优化。

2.7 小结

这一讲,我们完成了三件事:

  • 认识了Vivado HLS的界面布局
  • 创建了第一个HLS工程,写了一个加法函数
  • 跑通了C仿真和C综合,并学会了看综合报告

说实话,加法函数虽然简单,但它背后的流程——仿真、综合、报告分析——是后续所有复杂设计的基础。你把这个流程跑熟了,后面学pipeline、array partition、dataflow这些高级优化时,就不会手忙脚乱。

下一讲,我们会深入HLS的数据类型,看看 ap_int、ap_fixed 这些自定义类型到底怎么用,以及为什么它们比C语言自带的 int、float 更适合硬件设计。


可可精品资料库