HomeArchiveBlog


Original contents are licensed under CC BY-NC 4.0. All rights reserved © 2026 Kai.
Back to Archives
Quick Start: Using v++ from the Command Line

This guide provides step-by-step instructions on how to use v++ compiler from the command line to compile and link your hardware designs for Xilinx FPGAs.

Tue Dec 09 2025
Sat Jan 03 2026
Vitis HLSSystem GenerationCommand LineTutorials
On this page
  • Quick Start: Using v++ from the Command Line
    • 环境准备
    • 一些通用参数
    • 编译一个 HLS 设计
    • 仿真 HLS 设计
    • 链接生成完整系统
      • 链接配置文件
      • 执行链接
    • 打包启动映像

Quick Start: Using v++ from the Command Line

Vitis 是为数不多我个人觉得图形界面还不如命令行方便的工具之一, 并且通常大规模的综合和链接需要在服务器上跑, 用图形化界面相对不太方便. 好消息是新版本的 Vitis, 自从大概 2021.x 开始就提供了 v++ 编译器作为各种任务的统一入口, 比如 HLS 综合, 链接, 打包等. 这篇文章旨在快速上手 v++ 命令行工具, 从只综合一个基础 HLS 设计, 到生成最终的启动映像.

v++ 有三类主要的工作模式, -c : 编译, -l : 链接, -p : 打包启动映像. 这三大模式下面又有一些子模式, 比如 -c 下面可以指定 --mode=hls|aie 来编译 HLS 设计或者 AI Engine 设计 (对于部分 Versal 设备). 再通过一些配置参数可以配置某个工作模式的具体参数, 例如 --hls.syn.file 指定 HLS 源文件. 下面我们通过一些例子来看如何使用 v++ .

环境准备

首先需要安装好 Vitis 工具链, 并且配置好环境变量. 近几年的 Vitis 版本都大差不差. 以 Vitis 2025.2 为例,

source /opt/Xilinx/Vitis/2025.2/settings64.sh

演示的过程中使用 KV260 作为目标设备, 原因是 Vitis 已经提供好一个 KV260 的基础平台文方便链接. 对于非 Xilinx 官方评估板需要自己搭建基础平台, 关于搭建过程可以参考这篇文章. 如果只是想跑 HLS 综合, 那么不需要关心基础平台的事情.

一些通用参数

v++ 有一些通用参数, 适用于所有工作模式. 这里列出一些常用的.

  • --work_dir: 指定 v++ 的工作目录, 中间文件和日志文件都会保存在这个目录下面.
  • --report_dir: 指定报告文件的输出目录, 例如综合报告, 时序报告等.
  • --log_dir: 指定日志文件的输出目录.

编译一个 HLS 设计

假设我们有一个很简单的 HLS 设计, 它接受两路内存输入, 做加法然后输出到内存. 代码如下所示, 保存在 hls_add.cpp 文件中.

void hls_add(const int* in1, const int* in2, int* out, int n) {
    #pragma HLS interface m_axi port=in1 offset=slave bundle=gmem0 depth=128
    #pragma HLS interface m_axi port=in2 offset=slave bundle=gmem1 depth=128
    #pragma HLS interface m_axi port=out offset=slave bundle=gmem2 depth=128
    #pragma HLS interface s_axilite port=in1 bundle=control
    #pragma HLS interface s_axilite port=in2 bundle=control
    #pragma HLS interface s_axilite port=out bundle=control
    #pragma HLS interface s_axilite port=n bundle=control
    #pragma HLS interface s_axilite port=return bundle=control
    #pragma HLS interface ap_ctrl_hs port=return

    for (int i = 0; i < n; i++) {
        #pragma HLS pipeline II=1
        out[i] = in1[i] + in2[i];
    }
}

我们可以使用下面的 v++ 命令来综合这个设计.

v++ -c --mode=hls \
    -f $XILINX_VITIS/base_platforms/xilinx_kv260_base_202520_1/xilinx_kv260_base_202520_1.xpfm \
    --hls.clock 250MHz \
    --hls.syn.file hls_add.cpp \
    --hls.syn.top hls_add \
    --hls.flow_target vitis \
    --hls.package.output.format xo \
    --hls.package.output.file $PWD/hls_add.xo | tee hls_synth.log

对于自定义开发板又没有基础平台的情况, 可以把 -f 参数换成 --part 加上目标设备的核心型号, 比如 --part xcve2302-sfva784-1lp-e-S , 这个型号名称可以在 Vivado 或者 Vitis 的设备列表里面找到, 必须是完整的, 带上速度等级和封装型号的.

命令里面有一些必须的参数:

  • -c --mode=hls: 指定工作模式为编译 HLS 设计.
  • -f <platform>: 指定基础平台文件, 这里使用 Vitis 自带的 KV260 基础平台, 如果你在使用自定义设备, 比如小梅哥的 ACZ7015, 它的核心型号是 XC7Z015CLG485-2, 那么可以使用 --part xc7z015clg485-2 来替代这个参数.
  • --hls.clock 250MHz: 指定设计的目标频率.
  • --hls.syn.file: 指定 HLS 源文件. 如果你的设计有多个源文件, 可以多次使用这个参数, 例如
v++ ... \
    --hls.syn.file=file1.cpp \
    --hls.syn.file=file2.cpp
  • --hls.syn.top: 指定 HLS 设计的顶层函数名称.
  • --hls.flow_target, --hls.package.output.format: 这两个参数是绑定的, 根据工作流进行选择. 如果你只是想要 Vitis 输出一个打包好的 IP 给 Vivado, 然后在 Vivado 里面 Block Design 手动连线, 那么应该选择
v++ ... \
    --hls.flow_target=vivado \
    --hls.package.output.format=ip_catalog \
    --hls.package.output.file=$PWD/hls_add_ip.zip

如果你想要在 Vitis 里面完成综合还有整个系统的链接, 那么应该选择一开始的例子中的参数, 生成 .xo 文件. 默认情况生成 Vivado 工作流的 IP 包.

  • --hls.package.output.file: 指定输出文件路径, 对于 Vivado 工作流输出的是一个 .zip 文件, 对于 Vitis 工作流输出的是一个 .xo 文件, 后缀名不要弄错了.

接着是一些可选的参数

  • --hls.syn.cflags: 传递给 HLS 综合器的额外参数, 例如 --hls.syn.cflags="-O2 -g" 可以指定优化等级和调试信息, -I../include 可以添加额外的头文件搜索路径. 这个参数是传给 v++ 的 clang 前端的, 所以和 C++ 编译器能传递的参数基本一致.

  • --hls.syn.file_cflags: 这个参数和上面的参数类似, 但是它是针对某个特定的源文件传递编译参数. 例如 ../src.cpp,-Wno-unknown-pragmas 可以给 src.cpp 这个文件传递 -Wno-unknown-pragmas 编译标志.

v++ ... \
    --hls.syn.cflags="-O2 -g -I../include" \
    --hls.syn.file_cflags="../src.cpp,-Wno-unknown-pragmas"

如果你的工作流是 Vivado 工作流, 那么到这里 Vitis 部分就结束了, 你可以把生成的 IP 包导入到 Vivado 里面进行后续的设计了. 如果你的工作流是 Vitis 工作流, 那么接下来我们需要链接生成完整的系统.

仿真 HLS 设计

仿真其实不是 v++ 的功能, 它需要用到 vitis-run, 主要的命令如下

vitis-run --mode=hls --csim \
    --hls.clock 250MHz \
    --hls.syn.file hls_add.cpp \
    --hls.syn.top hls_add \
    --hls.tb.file hls_add_tb.cpp \
    --hls.csim.O true \
    --hls.csim.argv "arg1 arg2 arg3" \

也就是将 v++ 换成 vitis-run, 并且加上 --csim 参数来指定仿真模式. --hls.tb.file 用来指定测试平台文件, 如果有多个测试平台文件可以重复指定. --hls.csim.O 用来指定仿真优化等级, true 表示开启优化, 通常能加速大设计的仿真 (默认关闭). --hls.csim.argv 用来传递命令行参数给测试平台, 也就是测试的主函数 main.

如果要进行 C-RTL Co-sim, 那么将上面指令中的所有 csim 字样换成 cosim 就行了, 其它参数一致.

关于仿真的更多高级功能, 可以参考 Vitis 文档中的这部分内容.

链接生成完整系统

要能链接一个完整系统, 需要几个组件: 一些 HLS 设计生成的 IP .xo 文件, 一些 AIE 设计生成的 libadf.a 文件 (如果有), 一个基础平台 .xpfm 文件, 以及一个描述系统接口连接关系的配置文件 .cfg .

链接配置文件

我们仍然使用以上的 HLS 设计作为例子, 在这个例子中参数 in1 , in2 , out 都是通过 AXI-Full 接口连接到外部内存的, 而 n 是通过 AXI-Lite 接口连接到 PS 端. 我们需要创建一个配置文件 system.cfg 来描述这些连接关系, 内容如下所示.

[connectivity]
nk = hls_add:1:hls_add_0
sp = hls_add_0.in1:HP0
sp = hls_add_0.in2:HP1
sp = hls_add_0.out:HP2

第一行 nk 表示要创建的 IP 的数量和命名, 格式是

nk = <kernel_name>:<num_instances>:<instance_name1>,<instance_name2>,...

第一个参数是内核名称, 第二个参数是要创建的内核实例数量, 第三个参数是每个实例的名称. 这里我们创建了一个名为 hls_add 的内核, 只有一个实例, 名称为 hls_add_0 . 如果要创建多个, 看起来就是这样的

nk = hls_add:2:hls_add_0,hls_add_1

同样的, 这个参数可以被多次使用, 来创建多个不同的内核实例.

接下来的 sp 行表示系统端口和内核端口的连接关系, 格式是

sp = <instance_name>.<kernel_argument_name>:<sp_tag[min:max]>

这里 instance_name 是上面定义的内核实例名称, kernel_argument_name 是内核函数的参数名称, sp_tag 是系统端口的标签. 什么是系统端口? 这些是基础平台定义好的, 用于 PS-PL 交互的总线端口. 例如在 KV260 上, DDR 控制器被集成在 PS 里面, PL 通过 PS 开放的 AXI HP/HPC 端口发起内存访问请求. 那为了让 HLS 设计访问 DDR 内存, 就需要把 HLS 设计的 AXI-Full 接口连接到 PS 的 AXI HP/HPC 端口上. 端口的名称可以通过以下命令查看

platforminfo /path/to/platform.xpfm

找到其中的 Memory Information 部分, 例如对于 KV260, 输出如下所示.

==================
Memory Information
==================
  Bus SP Tag: HP0
  Bus SP Tag: HP1
  Bus SP Tag: HP2
  Bus SP Tag: HP3
  Bus SP Tag: HPC0
  Bus SP Tag: HPC1
  Bus SP Tag: LPD

这表明我可以通过 HP0 , HP1 , HP2 , HP3 , HPC0 , HPC1 这些端口来让 PL 访问 DDR 内存. 那我就只要挑选其中的几个端口来连接 HLS 设计的 AXI-Full 接口就行了(可以重复使用). 而 LPD 端口是用来管理 s_axilite 控制端口的, 在 Vitis 链接过程中并不需要手动连接, Vitis 会自动处理.

这是 Zynq 的情况, 它的内存控制器集成在 PS 里面. 对于 Versal 或者 Alveo 卡会复杂得多, 首先是 DDR 控制器是作为 PL 端的 IP 核存在的 (可以有好几个), 其次有些设备上有 HBM 控制器. 但主要的思路仍然类似, 先通过 platforminfo 命令查看系统端口标签, 然后在配置文件里面把内核端口连接到合适的系统端口上. 比如 vek280 , 可以看到输出如下所示.

==================
Memory Information
==================
  Bus SP Tag: LPDDR1
  Bus SP Tag: LPDDR2

这表明 vek280 上有两个 DDR 控制器, 分别对应 LPDDR1 和 LPDDR2 这两个系统端口标签. 那么如果 HLS 设计需要访问 DDR 内存, 就可以把 AXI-Full 接口连接到 LPDDR1 或者 LPDDR2 上.

如果某个端口是数组, 例如

void kernel(int* in[2], int* out);

那么在配置文件里面可以通过指定索引来连接, 例如

sp = kernel_0.in[0]:HP0
sp = kernel_0.in[1]:HP1

在这个例子中没有涉及到 stream_connect 参数, 这个专门用来配置内核之间的 AXI-Stream 连接的, 而不用于连接系统端口. 假设我们有这两个内核

void producer(hls::stream<int>& out) {
    #pragma HLS interface axis port=out
    #pragma HLS interface s_axilite port=return bundle=control
    for (int i = 0; i < 1024; i++) {
        #pragma HLS pipeline II=1
        out.write(i);
    }
}

void consumer(hls::stream<int>& in, int* out) {
    #pragma HLS interface axis port=in
    #pragma HLS interface m_axi port=out offset=slave bundle=gmem0 depth=128
    #pragma HLS interface s_axilite port=out bundle=control
    #pragma HLS interface s_axilite port=return bundle=control
    for (int i = 0; i < 1024; i++) {
        #pragma HLS pipeline II=1
        out[i] = in.read();
    }
}

为了将这两个内核连接起来, 我们需要在配置文件中添加如下内容.

[connectivity]
nk = producer:1:producer_0
nk = consumer:1:consumer_0
stream_connect = producer_0.out:consumer_0.in

Vitis 还另外提供了一个 connect 参数, 允许绕过 v++ 的连接检查, 强制连接任意端口. 这属于不到万不得已不应该使用的做法, 如果出现这种需求, 推荐直接导出 Vivado IP, 然后在 Vivado 里面手动连线.

执行链接

有了配置文件之后就可以执行链接了, 核心命令如下

v++ -l -t hw \
    -f $XILINX_VITIS/base_platforms/xilinx_kv260_base_202520_1/xilinx_kv260_base_202520_1.xpfm \
    --config /path/to/system.cfg \
    --clock.default_freqhz 200000000 \
    -o $PWD/hls_add.xclbin \
    $PWD/hls_add.xo | tee link.log

主要参数说明如下:

  • -l -t hw: 指定工作模式为链接硬件设计.
  • -f <platform>: 指定基础平台文件, 和编译阶段一样.
  • --config /path/to/system.cfg: 指定上面创建的链接配置文件.
  • --clock.default_freqhz 200000000: 指定系统默认时钟频率, 这里设置为 200MHz. 也就是所有的 PL 端 IP 核的默认时钟频率都是 200MHz, 可以通过 --freqhz=<freq>:<cu_name>.<pin_name> 来为特定的 IP 时钟端口指定不同的频率.
  • -o $PWD/hls_add.xclbin: 指定输出的启动映像文件路径. 对于 Zynq(ZynqMP) 设备, 生成的文件是 .xclbin 格式, 可以直接使用; 对于 Versal 设备, 生成的是 .xsa 文件, 必须经过 v++ -p 打包成最终的启动映像之后才能使用.

接下来是另外的一些可选参数, 但通常都推荐加上.

  • --optimize 2: 启用优化等级 2, 相对平衡综合质量和综合时间. 如果想要更高的综合质量可以使用等级 3.
  • --vivado.synth.jobs, --vivado.impl.jobs: 分别指定 Vivado 综合和实现阶段使用的并行线程数. 不建议开的非常高, 即使你的线程数很多. 因为 Out-of-Context 并行综合的时候会吃非常多的内存, 很容易爆内存. 对于 64G 内存的机器开 8-12 都是可以接受的.
  • --save-temps: 保存中间文件, 最主要是保存 Vivado 工程文件 .xpr 文件, 你可以在链接完成后用 Vivado 图形化界面打开这个工程文件, 查看综合和实现结果.
  • --temp_dir /path/to/temp/dir: 指定临时文件目录.

最后是 --profile 参数, 这个参数是用来加入一些探针, 通过 XRT 运行时做性能分析的. 例如 --profile.exec all:all 会监控所有内核的执行时间, --profile.data all:all:all 会监控所有内核的内存传输时间和数据量. 具体使用方法参考 Vitis 文档, 需配合 xrt.ini 使用. 注意, 插入探针会影响时序表现, 影响程度取决于探针的种类和数量.

因此最终一个完整的链接命令是这样的

v++ -l -t hw \
    -f $XILINX_VITIS/base_platforms/xilinx_kv260_base_202520_1/xilinx_kv260_base_202520_1.xpfm \
    --config /path/to/system.cfg \
    --clock.default_freqhz 200000000 \
    -o $PWD/hls_add.xclbin \
    --optimize 2 \
    --vivado.synth.jobs 8 \
    --vivado.impl.jobs 8 \
    --save-temps \
    --temp_dir /path/to/temp/dir \
    -o $PWD/hls_add.xclbin | tee link.log

打包启动映像

对于 Zynq(ZynqMP) 设备, 上面的链接步骤已经生成了最终的 .xclbin 文件, 这一步可以省略了. 但对于 Versal 设备, 上一步仅仅生成 .xsa 文件, 还需要打包生成最终的 .xclbin 才能被 XRT 识别和加载.

下面是一个打包的例子.

v++ -p -t hw \
    -f $XILINX_VITIS/base_platforms/xilinx_vck190_base_202520_1/xilinx_vck190_base_202520_1.xpfm \
    -o $PWD/hls_add.xclbin \
    $PWD/hls_add.xsa | tee package.log

这是最简单的打包命令, 只需要传入基础平台, 前一步链接得到的 .xsa 文件, 以及输出路径就行了. 这样会只打包出 .xclbin 文件, 不包含任何系统镜像.

如果需要打包带 Petalinux 镜像的启动映像, 具体细节不在这里介绍, 但给出一个参考.

v++ -p -t hw \
    -f $XILINX_VITIS/base_platforms/xilinx_vck190_base_202520_1/xilinx_vck190_base_202520_1.xpfm \
    --package.rootfs /path/to/rootfs.ext4 \
    --package.kernel_image /path/to/Image \
    --package.boot_mode sd \
    --package.image_format ext4 \
    --package.defer_aie_run \
    --package.out_dir /path/to/output/dir \
    --package.sd_file /path/to/host.exe \
    -o $PWD/hls_add.xclbin \
    $PWD/hls_add.xsa | tee package.log

最后, 建议将以上所有命令放到一个 Makefile 里面, 定义不同的目标, 比如 hls, link, package 等, 方便快速调用.