ARTICLE DETAIL

资讯详情

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

FPGA编译时间优化实战:从13小时到5小时的完整方案

FPGA编译时间优化实战:从13小时到5小时的完整方案 干FPGA这行的等编译应该是躲不掉的必修课。我之前维护过一个大工程纯逻辑接近几十万LUT加上PCIe、DDR、图像处理通路Zynq平台上跑一回Vivado综合实现编译时间稳定在13小时左右。也就是说早上到公司点一下运行下班前能不能拿到bit文件还看运气。中间要是发现时序违例或者跑出问题要改一行代码当天基本就废了整个团队都卡在流水线上干等。后来我花了几周时间把工程配置、约束、编译流程整体捋了一遍硬是把单次编译压缩到了5小时以内最快的增量编译甚至能做到1小时级别。这个优化过程涉及的东西比较杂但每一条都是实打实跑过的写出来给被编译时间折磨的朋友做个参考。先说清楚编译加速不是玄学它有一套明确的判断路径。你得先搞清楚时间耗在哪儿瓶颈在CPU还是存储器约束是不是在拖后腿模块划分是不是让工具做了大量无用功。这篇文章我会按实际处理的顺序来聊先诊断时间分布再讲基础配置和多线程然后是增量编译和OOC模块化最后是Pblock和时序约束这些更深入的提速手段。整个过程适合Vivado用户也适用于Intel Quartus里大部分思路只是命令名称略有差异。1. 先说结论为什么你的工程要编译13小时1.1 编译时间都花在哪了综合、实现各占多少很多人的第一反应是编译慢是因为综合很慢。实际上我测过很多次综合往往只占整个流程的20%-30%真正的大头在布局布线。拿我之前那个工程举例Vivado日志里各阶段耗时大概是这么分布的综合synth_design用了2小时出头逻辑综合把RTL转成网表这个过程也吃CPU但相比后面的布局布线压力小得多。接着是逻辑优化和物理综合大概1小时。然后布局place_design和布线route_design才是重头戏两者加起来接近8小时尤其是布线经常跑到5小时以上。最后生成比特流write_bitstream也要一两分钟到十几分钟具体看FPGA型号和配置。整体看我13小时里有超过60%的时间都花在了布局布线阶段。所以优化方向很明确要么让布局布线阶段跑得更快要么减少布局布线的规模要么干脆不做全量布局布线。后面介绍的所有方法本质上都是围绕这三件事展开的。1.2 真正拖慢编译时间的五个隐性因素很多工程编译慢不是机器配置不够而是工程设计本身给工具添了太多麻烦。我复盘时总结了五个最容易被忽视的因素每个都可能导致编译时间成倍上涨。第一个是模块层级和边界过于零碎。RTL代码里动不动就几十个子模块而且每个模块都单独设置了(* keep_hierarchy yes *)强制保留工具就没法跨模块合并逻辑等效门数量被放大布局布线的搜索空间也跟着变大。我见过不少工程师为了让模块化更清晰把层次保留打开结果编译时间直接翻倍。第二个是时序约束缺失或者过约束。约束文件里set_false_path没写好set_multicycle_path没指定或者把不同时钟域之间本应该异步的路径硬约束成同步关系导致布局布线器以为每条路径都是关键路径于是拼命去做时序收敛。这种情况下工具不是花时间把布局做合理而是耗费大量迭代去尝试满足一个根本不需要满足的时序要求。约束好不好直接决定route能不能快速收敛。第三个是资源利用率太高。一个工程如果LUT利用率超过了85%布线阶段会变得极其困难因为可布线资源变少拥挤度上升工具要么绕很远的路要么干脆布线失败后反复重试。同样规模的工程LUT从70%提升到90%布线时间可能从3小时变成8小时。第四个是伪路径和异步处理标注得不彻底。比如跨时钟域信号用的是同步器而没有通过约束告诉工具“这是异步的”工具会默认按同步逻辑去做时序分析引入大量无意义的时序路径浪费布局布线的时间。第五个才是物理硬件层面的问题。工程放在机械硬盘上读写IPC每次中间文件交换都要排队我自己实测同一个工程如果从机械硬盘换到NVMe SSD综合和实现阶段能省下接近20%的时间。还有内存不足导致系统使用swap交换那速度更是灾难。2. 从13小时到5小时最基础的机器与流程优化2.1 先别碰代码把Vivado线程和存储配置调好如果你现在工程编译超慢第一件要做的事不是改代码而是看你的机器配置和Vivado设置。Vivado默认情况下虽然会自动识别CPU核数但很多操作不会把所有核都用满特别是布线阶段的多线程支持是有限的。我在编译前会在Tcl Console里强制设置set_param general.maxThreads 16 set_param place.maxThreads 16 set_param route.maxThreads 16如果机器是8核16线程或者更多这条命令能很大程度上把多核吃满。注意place和route的maxThreads不是越高越好我试过把route线程从8开到32反而因为线程间同步开销变大速度没有明显的线性提升反而偶尔不稳定。比较稳妥的是和物理核数保持一致比如物理核8就设8如果开了超线程可以适当尝试16。然后检查工程存放位置。Vivado在整个编译过程中会产生大量中间文件尤其是布局布线的DCP文件文件体积动不动就是几个GB。工程放在机械硬盘上文件读写会成为严重瓶颈。我当时的做法是把工程整体复制到本地NVMe SSD并把Vivado的临时目录也挪过去setenv XILINX_TMPDIR /nvme_fast/tmp同时确认系统的pagefile或者swap分区有足够空间。在我的工程里Vivado峰值内存占用到过40GB如果你只有16GB内存那系统早就在疯狂换页了。建议物理内存至少是工程峰值内存的1.5倍以上不然CPU再多也发挥不出来。2.2 增量编译是见效最快的“白嫖”方案如果你已经确认机器和线程没问题下一步就是开启增量编译。Vivado的增量综合和增量实现简单说就是在设计改动很小的时候尽量复用上一次的布局布线结果只重新处理变更的部分。实际操作时需要在工程设置里勾选“Incremental Implementation”或者在综合时指定之前的检查点文件read_checkpoint -incremental ./dcp/top_routed.dcp增量实现的前提是你得先完整跑过一次编译并生成checkpoint。之后如果只是改了部分逻辑Vivado会复用大部分已有布局布线结果只对受影响区域重新实现。我那个工程在改动少数模块后全量编译13小时增量编译常常能压到2到3小时有时候只改了一点点文本甚至1小时就能出结果。这里有个非常关键的坑增量编译的加速效果依赖前一次布局布线的中点数据。如果约束大改、器件型号变了、资源利用率显著变化增量复用就可能失败工具会在日志里报出“incremental compile is discarded”之类的警告这时候它会退化成全量编译时间又回到13小时。所以增量编译适合迭代调试期不适合作为板上最终版本的唯一依靠关键版本还是要全量跑一遍确认时序。2.3 把设计拆成模块OOC综合与复用比增量编译更高阶的手段是OOCOut-of-Context综合也就是把某些模块单独拿出来综合成网表甚至单独布局布线生成模块级的DCP然后在顶层编译时把已完成的DCP直接读入。这样顶层每次编译都不需要再从RTL开始综合这些模块省下不少时间更重要的是保证了模块内部时序的稳定性。我当时的做法是把图像处理链路里的几个大模块比如缩放模块、滤波模块、以及PCIe的收发部分分别创建成OOC工程单独综合生成dcp。顶层采用Vivado中自顶向下的工程管理模式但把运行策略改成OOC对指定模块单独运行综合。顶层综合时会自动使用OOC生成的dcp不用我再手动read_checkpoint。OOC的好处不只是缩短编译时间它还能让内部时序路径提前收敛。你不需要每次都把整个大设计全部跑一遍才知道某个模块有没有问题只要模块级时序通过了顶层只需要关注模块间的时序连接。这样顶层布线时压力也小很多route时间明显缩短。3. 深度优化把大工程按“豆腐”切开3.1 用Pblock给布局布线划定“格子”当工程规模很大时布局布线器需要处理的候选位置非常多搜索空间巨大。Pblock的作用是你在物理上给某个逻辑模块划一块区域让工具只能在这个区域内摆放逻辑。这跟物流仓储一样不规定货架位置的仓库小件物品可能散落到很远的地方找起来费时费力划好区域后工具就不用全局到处找位置布线的走线长度和拥塞度都大幅下降。我当时给DDR控制器、图像缩放模块、PCIe硬核逻辑各画了一个Pblockcreate_pblock p_block_ddr_ctrl add_cells_to_pblock p_block_ddr_ctrl [get_cells inst_ddr_ctrl] resize_pblock p_block_ddr_ctrl -add {SLICE_X40Y100 SLICE_X70Y200}位置不能随便拍脑袋要参考Vivado布局后的utilization报告。我给每一个Pblock设定的原则是面积比该模块实际所需资源多出20%-30%留够布线的通量。太小了会导致工具在区域内摆不下反而死命向外挤太大了又起不到限制作用。Pblock对编译时间的影响我实测下来有很直观的数据。在没做Pblock时布线阶段经常在几个拥塞区域反复绕线耗时400多分钟划了Pblock之后相同区域的布线时间降到200分钟以内而且时序违例也变少了。因为这个工程里很多高频模块本来就该靠在一起物理上的聚集天然减少了线延迟。3.2 约束梳理是隐藏的加速器很多团队的约束文件是长年累月堆出来的中间经过多个人维护里面经常存在大量重复约束和过时路径。我在优化编译时间时花了两周专门梳理约束结果对提速的帮助比预期大得多。最重要的一个动作是明确异步时钟域。工程里有多个时钟像DDR的UI时钟、PCIe参考时钟、图像时钟它们之间很多本来就是异步的。我在约束里用了set_clock_groups把所有异步时钟分组set_clock_groups -asynchronous \ -group [get_clocks -include_generated_clocks clk_pcie] \ -group [get_clocks -include_generated_clocks clk_ddr] \ -group [get_clocks -include_generated_clocks clk_pixel]加上这个约束之后工具就不会去分析这些跨异步时钟域的路径时序路径数量锐减布局布线阶段的压力也小很多。当然前提是RTL里真的做了同步处理比如用两级触发器同步或者FIFO隔离如果没有处理不能直接set_clock_groups否则就是掩盖问题了。另一个关键动作是补充set_input_delay和set_output_delay让接口路径有明确的定时窗口。你可能会觉得这跟编译速度无关但实际上接口路径定义越清晰工具就越不容易纠结。尤其是在包含外部ADC、HDMI、PCIe接口的设计里I/O约束缺失会导致工具默认用比较保守的方式处理接口寄存器布局浪费大量优化时间。3.3 资源利用率为什么必须压到85%以下关于资源利用率我自己有过一次惨痛教训。某个版本为了堆功能LUT和FF利用率分别到了92%和88%综合一跑完布局布线直接卡死route阶段跑了6个小时还没结果最后强行停止一看日志全是congestion问题和布线失败的警告。后来我通过资源优化把利用率压到80%左右布线时间降到3小时内。资源利用率影响编译时间的原理并不复杂。布局工具把逻辑放到SLICE里时如果可用SLICE紧张它需要做大量启发式尝试来避免拥挤布线工具更惨有限的布线通道塞大量信号绕线方案复杂度和冲突成倍增长。建议在综合完后先看一眼资源报告如果任何一类资源超了85%先别急着跑实现回头优化一下设计结构可能更划算。优化资源利用率的方向包括用DSP48代替纯LUT乘加、把大数组从LUTRAM挪到BRAM、合并多路复用器、调整运算位宽。这些改的是RTL但对编译速度的影响却是立竿见影的因为它们直接减少了布局布线面临的问题规模。4. 性价比极高的实操配置一个工程从13h到5h的真实参数表4.1 我当时的工程规模与问题现状先交代一下当时那个项目的规模方便你对号入座。主控芯片是Zynq UltraScale系列某型号工程里包含PCIe根端口、 DDR4控制器、MIPI CSI-2接收、图像缩放与滤波链路、 HDMI输出以及一堆GPIO和AXI外设。综合后资源情况大概是LUT约25万FF约30万BRAM 300多个DSP 200多个。工程落在机械硬盘上Vivado版本是2019.1机器是双路至强20核40线程内存64GB。最初的编译时间表格大致是这样的阶段优化前耗时优化后耗时综合synth_design2h10m1h05m逻辑优化opt_design0h55m0h35m布局place_design3h05m1h20m布线route_design5h50m1h40m写比特流write_bitstream0h20m0h12m总耗时约13h约5h综合时间下降主要有两个原因一是启用了16线程二是把工程挪到了NVMe SSD上减少了IO等待。布局布线时间大幅下降主要是靠OOC修改和Pblock约束加上资源利用率从88%降到了82%异步时钟约束也理清了。4.2 具体实施步骤一个可复用的优化清单从决定优化到拿到稳定5小时结果我按下面步骤走的每一步都有明确的可验证节点。第一步拷贝工程到NVMe SSD并设置XILINX_TMPDIR到SSD临时目录。这一步完成以后可以跑一次全量编译记录基线时间确认磁盘读写不再是瓶颈。我看了一下任务管理器编译期间磁盘占用从之前100%降到30%左右。第二步在Vivado的Tcl console里设置线程数。不要只设置general.maxThreads还要单独设置place和route的线程数。调整完之后再次跑全量编译看综合和place阶段的耗时有没有改善。比较理想的情况是总耗时从13小时降到11小时左右。第三步梳理时序约束。重点处理set_clock_groups、set_false_path和set_multicycle_path。跑一次全量编译如果工具提示“No timing requirements”的路径数量明显减少route时间也会跟着压缩。这一步结束以后总耗时大概能让place加route从9小时降到6小时内。第四步做OOC综合。先把几个大模块提出来单独生成dcp其他模块保持全局综合跑一次全量编译确认顶层集成没有问题。如果工程里有模块可以复用之前编译好的dcp顶层每次节省1到2小时很正常。第五步根据第一次跑出的布局结果分析拥塞区域针对热点模块加Pblock。加上以后如果Pblock内有资源挤爆工具会产生严重拥塞警告需要调整框的大小。这一步是最后一块拼图能让布线降到2小时以内。4.3 一个可以抄作业的Tcl脚本片段我把自己用来跑全量编译的Tcl脚本节选出来工程结构大体是src目录放RTLxdc目录放约束dcp目录放OOC产出物。set_param general.maxThreads 16 set_param place.maxThreads 16 set_param route.maxThreads 16 read_verilog [glob ./src/*.v] read_ip [glob ./ip/*.xci] read_xdc [glob ./xdc/*.xdc] set_property strategy Performance_ExtraTimingOpt [current_run] synth_design -top top -part xczu7ev-ffvc1156-2-e \ -mode out_of_context # 如果某个模块已有OOC dcp可以在这里替代 set_property is_implementation_modified 1 [current_run] opt_design place_design phys_opt_design route_design write_checkpoint -force ./dcp/top_routed.dcp write_bitstream -force ./output/top.bit这里的strategy我选的是Performance_ExtraTimingOpt这是Vivado里相对均衡的优化策略兼顾时序和编译时间。如果你只关心能不能快速跑出bit可以选Flow_RuntimeOptimized会把编译时间再压一截但工程时序余量可能会变差。我一般只在中间迭代时用Flow_RuntimeOptimized最终版本还是会切回性能模式。5. 编译慢真正出问题时的排查实录5.1 从日志里定位真正的瓶颈阶段如果优化做完后发现编译还是慢你要学会从Vivado日志里看时间缺口。Vivado会打印每个命令的启动和结束时间比如在日志里搜“Start Routing Design”和“Finished Routing Design”两个时间戳之间就是布线阶段耗掉的时间。如果你发现综合阶段占了一半以上时间那说明RTL库综合压力大可以考虑OOC拆分如果place阶段异常长往往是资源碎片化或者Pblock有问题如果route阶段时间长大部分是拥塞和时序约束问题。还有一种情况是工具卡住了日志长时间不更新表面看起来像死机实际上可能是在做超大规模的时序更新。这时候可以用Vivado的report_qor_suggestions它会建议一些可选的改善设置。我记得有一次route跑了7个小时就是被一个convergence模式下重复做时序优化拖住的后面加了set_param route.EnableTimingDrivenPlaceMoves 0虽然牺牲了一点时序收敛能力但换来了明显的速度提升。5.2 高频问题与解法速查表我把实践中遇到最多的问题整理成表方便你排查时对照。现象可能原因解决办法route一直跑不完日志出现congestion warningLUT/FF利用率过高或Pblock区域过小压资源利用率调整Pblock大小检查拥塞区域综合阶段很慢CPU利用率低磁盘IO瓶颈或输入文件太大导致解析慢工程放到SSD必要时用增量综合增量编译不生效日志提示discard设计改动太大或前一次的dcp不是最新版本全量编译一次确认checkpoint是最新并放在固定路径加入多线程后速度反而下降核数设置超过物理核线程切换开销大按物理核数设置不开太多place阶段出现很多overlap错误Pblock太小或资源类型不匹配查看Pblock的面积占用扩大或者分开建Pblockroute结束但时序大幅违例约束里漏掉异步路径工具在无意义路径上浪费迭代梳理set_clock_groups和false path内存占用过高导致系统卡死机器内存小于Vivado峰值需求加内存或者用更小的模块级编译不要一次全量跑这个表里的前两条是绝大多数编译慢问题的核心。如果你对表里的东西没有明确方向可以先跑一次report_utilization和report_congestion把“体检报告”拿到手再做判断。5.3 从13小时到5小时的真实复盘最后聊聊我踩过的坑也算是个阶段性复盘。这个项目优化完以后团队的整体迭代节奏完全变了。以前一天只能编译一次任何一个问题都要等第二天才能验证现在上午改完代码中午就能拿到bit下午还能再调一轮。尤其是板级调试阶段这种差距直接决定了项目能不能按计划走。有一个小技巧值得单独说如果只是验证功能逻辑可以先把时序约束暂时放宽用Flow_RuntimeOptimized策略跑一版不带时序收敛要求的bit快速上手调试。等逻辑功能稳定了再用完整约束跑性能优化版这样可以避免把等待编译的时间浪费在功能验证上。不过要记住这种快速版bit不能用来做正式的性能测试不然会给你虚假的信心。说到底FPGA编译加速最怕的是盲目跟风和乱调参数。我见过有人一上来就改一堆Vivado高级选项结果编译时间没变短反而出了很多奇怪的行为。更可靠的做法是先记录原始基线然后每次只改一个变量观察它对编译阶段耗时的影响再决定要不要保留。我自己就是用这种方式逐步找到最优解的前期花了一点时间去布设监控和记录后面每次优化都有明确的数据支撑而不是靠感觉。
返回列表