ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从UG871入手,掌握Vivado HLS的C到RTL设计与优化实战

从UG871入手,掌握Vivado HLS的C到RTL设计与优化实战 简介《Vivado HLS教程 UG871中文版》是一份由Xilinx官方发布的高层次综合入门指南面向FPGA工程师、Zynq开发者和有一定C/C基础、希望加速硬件设计流程的读者。内容以UG871实验为主线覆盖高级综合项目创建、Tcl命令接口、解决方案设计优化、C验证与调试以及块级I/O协议综合等关键环节有助于把算法级描述快速转换为HDL显著缩短设计周期。资源为单份PDF文档约20.08MB便于电脑端阅读、打印和按章节检索文档共三百余页步骤清晰配有教程设计说明与实验结论适合边学边练。目前该资源已有341人学习适合需要系统掌握Vivado HLS流程或准备Zynq开发项目的中高级读者收藏使用。1. UG871 不是一本只需“看”的文档从 C 到 RTL 的第一次握手UG871 是 Xilinx 官方《Vivado Design Suite Tutorial: High-Level Synthesis》的文档编号也是学 Vivado HLS 时被问得最多的一份资料。它和普通手册最大的区别在于整份文档由 lab 驱动每个章节对应一个能综合的 C 工程你在图形界面或 Tcl 控制台里把工程完整跑一遍比读十遍原理都管用。所谓“中文版”官方从未发布过网上流传的大多是社区翻译或整理的学习笔记版本对应 2014 到 2016 年的工具链界面截图和现在 2022.2 的差距不小。这不算缺点反而说明一个问题HLS 的核心概念十年来几乎没有变过——循环流水、数组拆分、数据流、接口协议。把旧例程在 2020.2 以上版本里重新综合照样能跑。这篇博文就按装环境、跑例程、调 pragma、导出 IP、排错的顺序把 UG871 从书架变成你上手硬件加速的起点。2. 搭建 Vivado HLS 环境版本选择、license 与 UG871 示例工程装 Vivado HLS 这件事大部分时间不是花在下载上而是花在“用哪个版本打开 UG871 例程”上。UG871 的截图基于 2014 到 2016 年的工具那时候启动命令还叫vivado_hls2019.2 之后 HLS 工具被并入 Vitis 统一安装器命令变成vitis_hls菜单布局也改了。如果你照着老截图找不到对应按钮不用怀疑自己直接改用 Tcl 脚本跑例程反而比 GUI 更稳。2.1 装哪个版本vivado_hls到vitis_hls的兼容边界Vivado 版本选型可以用一个简单标准不要只看界面新旧先确认你手上的 Tcl 脚本能不能被当前工具吃进去。下表是常见选择版本段HLS 入口命令与 UG871 截图差异实用建议2018.3 及之前vivado_hls基本一致适合完全对照文档点 GUI但系统兼容性差2019.2 到 2021.2vitis_hls多数保留vivado_hls软链接有差异核心菜单还在当前资料最多、踩坑记录最全的区间2022.2 及之后vitis_hls界面改动大工程结构不变新机器建议装这个Tcl 脚本兼容性良好UG871 里的 open_project、add_files、csynth_design 这些核心 Tcl 命令在 2022.2 里依然可以直接执行。界面变了不代表脚本失效所以我建议你从第一天就习惯用 Tcl 而不是鼠标。鼠标操作适合第一次熟悉流程之后的迭代优化全部走脚本否则改一个参数就要重新点十几层菜单效率差距非常明显。2.2 license 报错与 WinPcap 失败的排查顺序Vivado 安装过程中的三个高频问题里有两个和 license、依赖驱动相关你大概率会遇到其中之一。第一个是 license 报错形式通常是一串编号常见的有 2037 这类出现时先检查系统时间是否和当前时间一致再打开 Vivado License Manager 看 HostID 和你机器网卡 MAC 是否匹配。绑定浮动 license 的服务器如果换了 IP或者防火墙挡了 2100 端口也会出现同样的问题。把这三项按时间、MAC、端口顺序排查基本能定位。第二个是 Windows 下 WinPcap 安装失败。Vivado 安装器在勾选 Cable Drivers 时会调用 WinPcap 安装包这个组件只在连接 JTAG 下载器时才需要装不上不影响 HLS 综合和仿真。处理方式是单独下载 WinPcap 安装包手动安装或者右键以管理员身份运行安装器后再装一次。第三个是 GUI 闪退多见于 2020.2 之后的版本在 Windows 上启动时崩溃常见做法是删除%APPDATA%\Xilinx下的缓存目录再重新启动如果还闪退就干脆用命令行vitis_hls -i进入交互式 Tcl 环境绕开 GUI 渲染。2.3 UG871 配套例程拿到手之后先看目录UG871 的例程不随安装包自动带全需要到 Xilinx 文档页面找到对应版本在 Design Files 区域下载 zip 包。解压后常见的目录组织是源文件、测试文件和文档各占一块以 FIR 例程为例你通常会看到类似下面的结构ug871_labs/ ├── fir.c ├── fir.h └── fir_test.c这三个文件的分工要一开始就分清fir.c是要被综合成硬件的 C 函数fir.h是接口声明fir_test.c是给 C 仿真用的测试平台。很多人第一次跑 UG871 报错就是因为把 testbench 文件不加区分地全部add_files进去结果测试代码也被当成硬件来源综合出一堆莫名其妙的逻辑。正确做法是源文件用add_files测试文件用add_files -tb单独添加这样 HLS 才知道哪部分是硬件、哪部分是仿真环境。目录里一般没有预生成的 solution 目录需要你自己建这也是为什么先用 Tcl 脚本跑通流程比在 GUI 里摸索更高效。3. 用 Tcl 脚本跑通 UG871 FIR 例程从 C 仿真到 RTL 协同仿真在 UG871 的例程里FIR 是最常被拿来当第一个动手项目的工程因为它结构完整有输入数组、有系数数组、有累加循环既能演示 C 仿真又能演示优化 pragma 的效果。下面这套 Tcl 脚本就是跑通 FIR 例程的最小流程我一般会把它保存成run.tcl放在工程根目录每次改完 C 代码后直接在命令行执行。3.1 从 open_project 到 export_design 的最小工程脚本open_project fir_prj -reset add_files fir.c add_files -tb fir_test.c set_top fir open_solution sol1 -reset set_part xc7z020clg400-1 create_clock -period 10 csim_design csynth_design cosim_design -trace_level all export_design -format ip_catalog这段脚本的逻辑是按“工程 → 文件 → 顶层函数 → 方案 → 约束 → 仿真 → 综合 → 协同仿真 → 导出”的顺序推进的。open_project打开或新建工程-reset表示如果同名工程已存在就清掉重来避免旧文件干扰。add_files添加硬件源文件add_files -tb单独挂 testbench。set_top fir指定顶层函数名HLS 只会把 FIFO 或者 AXI 接口做到这个函数的边界上。open_solution对应 GUI 里的 Solution一个工程可以挂多个 solution用来对比不同优化策略-reset同样是清空重跑。set_part指定目标器件create_clock -period 10是 10ns 周期也就是 100MHz 的约束。后面的四行是核心流程csim_design先跑 C 仿真确认算法行为正确csynth_design做逻辑综合生成 RTL 和一份资源时序报告cosim_design把综合出的 RTL 和 C 测试平台放到一起做协同仿真验证 RTL 行为是否和 C 一致export_design导出成 Vivado 能用的 IP。需要特别注意的是cosim_design必须在csynth_design之后执行因为它要先有综合出的 RTL 才能跑。如果你只跑到csynth_design就去看 RTL看到的只是未经验证的中间产物。3.2 C 仿真与 C/RTL 协同仿真的边界C 仿真看起来和普通 C 程序没什么区别但它的判定机制有一个隐藏约定testbench 的main函数返回 0 表示测试通过返回非 0 表示失败。UG871 例程里的fir_test.c通常会用输入数据跑一遍滤波再和预先算好的期望值比对误差超过阈值就返回错误码。我经常看到有人把 testbench 写成只打印结果、不检查对错这样的 C 仿真跑一百遍都发现不了问题。cosim_design和 C 仿真的区别在于它会把综合后的 RTL 加载到仿真器里跑所以速度慢得多但能验证两件事接口时序是否正确、比特级行为是否和 C 一致。-trace_level all参数会导出全部信号的波形排错时可以在 Vivado 波形窗口里看到 ap_start、ap_done、数据总线的每一拍变化。常见的失败场景是 C 仿真通过但协同仿真挂掉原因往往是浮点数比较精度不一致或测试代码里用了printf打印浮点结果而后缀比较逻辑。对策是把 C 模型的比对容差放宽硬件里浮点累加顺序不同会导致最后几位不一致。3.3 综合报告看什么latency、Interval 和资源表csynth_design跑完后报告默认生成在solution1/syn/report/目录下文件名类似fir_csynth.rpt新版本里还会生成同名 XML 文件可以用浏览器打开查看结构化的报告。报告里几个指标直接决定你这个设计能不能用报告项英文名说明总延迟Latency一次完整调用需要多少个时钟周期启动间隔Interval相邻两次调用之间至少间隔多少周期迭代间隔Initiation Interval循环流水里启动下一次迭代所需周期数资源占用FF / LUT / BRAM / DSP面积成本直接决定能不能放进目标器件资源里 DSP 是最容易被忽略的如果 C 代码里乘法多HLS 会默认把每个乘法映射到一个 DSP48数量超了就会自动拆成 LUT 实现时序和面积都会变化。除了数字表格还要学会用 Schedule Viewer它在综合报告的 Viewer 面板里能把循环每一拍的调度画成时间线。如果看到某些操作被画成红色连线说明存在依赖冲突这就是后面第四章要讲的优化点。另外提醒一句综合报告里的功耗估算是粗粒度的做精确功耗分析要等导出 IP 后在 Vivado 里跑 open implemented design 再 Analyze Power。4. UG871 后半本的核心PIPELINE、UNROLL、ARRAY_PARTITION 与 DATAFLOWUG871 前半本教你如何把 C 变成 RTL后半本教你怎么把 RTL 变快。这四类 pragma 几乎是所有 HLS 优化的基础FIR 例程正好能覆盖其中的大部分场景。理解它们不能只背语法关键是弄清每个 pragma 在改什么资源、换什么性能。4.1 PIPELINE 的 II、真依赖和 schedule viewer先看一个典型的累加循环每轮迭代把输入乘系数后累加到一个变量上。如果直接综合HLS 默认是一轮迭代算完再算下一轮1024 次迭代就是 1024 个乘加延迟累加在一起。加上 PIPELINE 后迭代之间可以在时间上重叠但有一个硬约束由于累加变量acc每一轮都要读上一轮的结果这个循环携带依赖决定了迭代间隔不可能小于乘加链的延迟。int acc 0; for (int i 0; i 1024; i) { acc data[i] * coeff[i % 8]; } out acc;对这个循环加#pragma HLS PIPELINE II1综合时大概率会告诉你无法达到目标 II实际会退到 2 或 3。原因是乘法器加加法器链的延迟在那个器件频率下超过一个周期。解决办法有两个方向一是把 II 放宽到 2代价是吞吐率减半二是把累加拆成多个独立累加器最后再合并但这样会引入额外的加法树延迟。具体选择取决于你的瓶颈是吞吐还是延迟。判断依据就看 Schedule Viewer 里红色依赖线跨越了几拍那样比猜准确得多。4.2 ARRAY_PARTITION 的 block、cyclic、complete 三种切分方式数组在 C 里是内存综合后默认映射成 BRAM 或分布式 RAM而 RAM 的读写端口有限。当循环展开或流水后一个周期要访问多个数组元素RAM 端口就会成为瓶颈。ARRAY_PARTITION 解决的就是这个问题它的三种切分方式对应三种访问模式类型切分逻辑适合的访问模式资源代价block按连续区间切成两块顺序读入前后半段分别处理低子块仍是 RAMcyclic按步长轮转分配元素FIR 多相滤波那种交叉取系数的场景低到中complete全部拆成独立寄存器数组很小且需要全并行访问高只适合小数组#pragma HLS ARRAY_PARTITION variablecoeff dim1 cyclic factor4这条 pragma 把coeff数组按步长 4 切成四个子块一次循环里最多可以同时访问 4 个不同系数。在 FIR 的多相滤波实现里系数本来就按相位分组用 cyclic 切完之后四个子滤波器可以各自并行读自己的系数不必互相等 RAM 端口。complete适合长度不超过几十的数组比如 8 个 FIR 系数直接全部展开成寄存器读写延迟最低。4.3 UNROLL factor用面积换并行度UNROLL 和 PIPELINE 经常被放到一起说但作用完全不同。PIPELINE 让迭代在时间上重叠UNROLL 是复制循环体让同一拍执行多份迭代。二者配合时UNROLL 提供并行度PIPELINE 保证这些并行迭代能流水起来。#pragma HLS UNROLL factor2 for (int j 0; j 8; j) { acc data[i j] * coeff[j]; }factor2 表示一次迭代同时算 j 和 j1 两个输入循环次数从 8 减到 4。代价是乘法器和加法器各多一份DSP 占用翻倍。这个 pragma 有个容易被忽略的限制循环边界必须是常量。如果边界来自函数参数或 volatile 变量UNROLL 会直接报错需要先想办法把边界变成编译期常量。UG871 里的做法通常是先把循环改成固定上限再在循环内判断是否越过实际边界。4.4 DATAFLOW 什么时候加什么时候白加DATAFLOW 是用来做任务级流水的它作用在函数之间而不是循环内部。考虑一个典型的两级处理流程第一个函数对数据做滤波结果存入中间数组第二个函数对中间数组做缩放输出到最终结果。不加 DATAFLOW 时HLS 会先完整跑完第一个函数再开始第二个函数加上之后第一个函数还在处理第 N 个数据时第二个函数就可以开始消费第 N-1 个数据。#pragma HLS DATAFLOW void top(int *in, int *out) { int tmp[1024]; filter(in, tmp); scale(tmp, out); }DATAFLOW 生效条件很苛刻生产者和消费者之间只能通过 FIFO 或数组单向传递数据不能存在生产者读回消费者写的全局变量也不能有跨任务的循环依赖。如果两个任务之间共享一个全局数组并且双方都读写HLS 会在日志里报 dependence 相关警告此时 DATAFLOW 会被静默禁用或插入乒乓缓冲资源翻倍但 Interval 没降。4.4.1 加了 DATAFLOW 性能不升反降的检查点遇到这种问题按下面顺序排查。先看综合日志里有没有 “dataflow” 警告只要有 dependence 警告说明数据流方向没被编译器接受直接去掉 pragma 反而更干净。再看任务粒度如果每个任务内部本身就是串行的数据流带来的重叠收益会被任务切换开销抵消此时应该先把内部循环 PIPELINE再考虑 DATAFLOW。最后检查中间数组大小太小的数组会被实现成 FIFO但 FIFO 深度不够会导致 producer 长时间阻塞Interval 反而增大常见做法是把 FIFO 深度显式指定到 16 或 32再观察 Interval 变化。5. 导出 IP 到 Vivado 集成从 Block Design 到实现变红的一次定位UG871 的最后几个 lab 一定会把 HLS 生成的 IP 放进 Vivado 工程里跑通整个流程。这一步最常见的体验是“综合都过了为什么 implement design 变红”。这里的关键是要分清问题出在哪个阶段以及它是不是 HLS 阶段埋下的隐患。5.1 export_design 的格式选择与 Block Design 接线export_design -format ip_catalog执行完后产物在solution1/impl/目录下是一个标准的 Vivado IP 压缩包。在 Vivado 里打开 Block Design用 Add IP 搜索你设置的顶层函数名就能把它拖进画布。如果顶层接口是 AXI4-Lite 或 AXI Stream需要到 Address Editor 里给接口分配地址如果是自定义的 ap_vld、ap_ack 这类接口就得手动把 start、done、ready 信号连到你的控制逻辑上。HLS 默认会为顶层函数生成 ap_start 和 ap_done 握手信号如果不需要控制可以在 C 代码里用#pragma HLS INTERFACE modeap_ctrl_none去掉这套握手让 IP 一上电就持续工作。5.2 实现阶段变红按 DRC、接口悬空、时序的顺序查实现变红不要一上来就点 Reset Run先打开 Messages 面板定位错误级别。第一条要查的是 DRC 报告这类错误带编号例如 RTSTAT-2 相关报错通常指向时钟资源或跨时钟域约束问题和 HLS 导出的 IP 关系不大多半是 Block Design 里时钟连接没做对。查完 DRC 再看接口HLS IP 的输入端口如果没有连接在实现时会被固定到 0表现为上板后功能完全不工作但仿真全对这种问题在 Vivado 的 Schematic 里一拖就能看到悬空引脚。最后才是时序违规用 report_timing_summary 看是 IP 内部路径还是跨 IP 路径如果 HLS 那边已经 create_clock 10ns而 Block Design 里用的时钟是 200MHz那就完全是约束不一致造成的回到时钟配置里统一频率即可。按照 DRC、接口、时序这个顺序查大部分变红问题都能在五分钟内定位。5.3 握手信号不稳打两拍再给 HLS IP上板时经常遇到一种现象仿真全过下载后偶尔工作、偶尔不工作。这时候重点检查 ap_start 信号的来源。如果它由外部按键或异步逻辑产生脉冲宽度和相位都不确定HLS IP 内部在 ap_clk 上升沿采样时可能采到亚稳态导致启动状态机异常。常见做法是给 ap_start 加两级同步reg ap_start_q1, ap_start_q2; always (posedge ap_clk or negedge ap_rst_n) begin if (!ap_rst_n) begin ap_start_q1 1b0; ap_start_q2 1b0; end else begin ap_start_q1 ap_start; ap_start_q2 ap_start_q1; end end这段代码把外部异步信号打两拍输出ap_start_q2作为 HLS IP 的启动电平。注意打拍后的信号会比原信号晚两个时钟周期如果外部逻辑同时也在等待 ap_done 反馈就要把确认时序也相应调整。打完拍之后如果还有偶发不工作的情况回到 HLS 的 Schedule Viewer 里看关键路径上乘法器占了几拍如果恰好卡在时序边界上就调整create_clock的周期或对乘法链路加#pragma HLS LATENCY强制多拍完成用一拍的代价换稳定时序是压掉这类偶发问题最直接的办法。本文还有配套的精品资源点击获取
返回列表