化實戰(zhàn))
等了13個小時出一次bit文件結(jié)果上板一看時序違例還掛在那邊。改一行代碼重新跑又是十幾個小時。這種日子我過了大半年直到有一次項目節(jié)點實在扛不住才下定決心把編譯提速這件事徹底解決掉。這篇文章就是把我從“13小時”壓到“5小時”的完整過程、參數(shù)設(shè)置還有踩坑記錄全部捋一遍。如果你也在用Vivado或者Quartus做中大型FPGA工程每次編譯都要燒掉半天時間那這篇文章應(yīng)該能幫你把時間搶回來。先說清楚一個概念FPGA編譯時間不是由工程代碼量單獨決定的而是由“邏輯規(guī)模 約束復(fù)雜度 工具策略 機器性能”四個維度共同決定的。很多時候你覺得編譯慢是代碼太多其實可能是某個策略參數(shù)沒調(diào)對、某條約束寫得過于苛刻、或者增量編譯開關(guān)沒開。這些問題的排查成本遠低于重寫代碼但收益卻是立竿見影的。1. 先把賬算清楚編譯時間到底花在哪里1.1 綜合、布局、布線各階段耗時拆解FPGA編譯流程大體分三步綜合Synthesis、布局Placement、布線Routing外加打包Packing和時序收斂Timing Closure這兩個容易忽略的環(huán)節(jié)。我以前跑一個40萬LUT級別的視頻采集工程總耗時大概12小時50分鐘拆開看大概是階段耗時占比綜合1小時20分10%布局3小時23%布線6小時46%時序分析優(yōu)化迭代2小時15%其他IO規(guī)劃、bit生成30分4%看到?jīng)]有布線占了接近一半。這不是巧合布線階段工具需要嘗試海量的資源連接方案還要在時序約束和擁塞度之間反復(fù)權(quán)衡。很多人一上來就調(diào)綜合策略其實方向錯了——在工程規(guī)模不變的前提下真正的大頭在布局和布線。但有意思的是綜合階段的一些設(shè)置又會直接影響后續(xù)布局布線的壓力所以不能單純只優(yōu)化某一個環(huán)節(jié)。1.2 默認策略為什么“慢得離譜”Vivado和Quartus的默認策略都偏向“保守 通用”。默認情況下工具會把所有時序路徑都當作同等重要來處理對所有跨時鐘域路徑做完整分析對所有邏輯塊做均勻布局。這種做法的好處是適應(yīng)性廣、不容易出錯壞處就是時間開銷大。舉個例子一個工程里有大量跨時鐘域CDC路徑工具默認情況下會嘗試對每條CDC路徑做全部時序分析但其實很多跨時鐘域路徑本身就是異步的根本不需要嚴格收斂。你不告訴工具“這些路徑是異步的”它就老老實實花費大量時間在優(yōu)化這些意義不大的路徑上。這就像你讓一個保潔把整個園區(qū)每塊地磚都擦三遍哪怕有些地磚根本沒人踩——浪費時間但問題出在管理員的指令不夠清晰。2. 從13小時到5小時五個能立刻上手的提速手段2.1 增量編譯不要每次從頭再來增量編譯是FPGA編譯加速最直接、最省事的手段但很多人對它有個誤解以為只要開了增量模式就一定快。實際上增量編譯的原理是復(fù)用上一次布局布線的結(jié)果只對改動影響到的邏輯區(qū)域重新做布局布線。如果你改動的代碼影響面很大比如頂層模塊的接口變了幾十個信號那增量編譯的效果會大打折扣甚至可能比全量還慢因為工具還要額外做差異比對。我的使用經(jīng)驗是局部小改動開增量大改動或時序不收斂時老老實實全量。在Vivado里開啟增量編譯的方法是set_property strategy Performance_Explore [current_run] set_property STEPS.SYNTH_DESIGN.ARGS.DIRECTIVE PerformanceOptimized [current_run] set_property STEPS.PLACE_DESIGN.ARGS.DIRECTIVE ExtraTimingOpt [current_run] set_property STEPS.ROUTE_DESIGN.ARGS.DIRECTIVE Quick [current_run]以上是策略設(shè)置。增量編譯要在工程屬性中勾選“incremental synthesis”和“incremental placement/routing”并且第一次跑工程時必須保留上一次的Checkpoint.dcp文件。如果你之前的工程沒有保存Checkpoint那第一次增量編譯也快不了。2.2 分布式編譯讓多臺機器替你干活如果你們實驗室/公司有閑置的機器分布式編譯值得認真考慮。Vivado本身不支持跨機器并行綜合但你可以把“綜合”和“布局布線”拆開在兩臺機器上跑。具體做法是A機器跑綜合生成綜合后的Checkpointsynth.dcpB機器直接加載Checkpoint繼續(xù)做布局布線。這個方案實操時要留意版本一致性。Vivado 2020.1生成的dcp用2022.2打開大概率會報錯要么拒絕加載要么中途崩掉。所以分布式編譯的前提是所有機器裝同一個Vivado版本、同一個補丁級別最好還同步一下IP核版本。我試過用兩臺配置完全一致的服務(wù)器把綜合放到一臺、布局布線放到另一臺整個編譯周期能從13小時壓到9小時左右。不過前提是網(wǎng)絡(luò)傳輸dcp文件的耗時不能太長最好走千兆內(nèi)網(wǎng)否則文件傳輸時間會把收益吃掉。2.3 策略與探針把時間花在真正重要的地方Vivado的布局布線和布線策略里面有幾個關(guān)鍵選項直接影響耗時的分配Quick布線速度最快但容易導(dǎo)致時序惡化。Explore多輪迭代質(zhì)量高但耗時最長。Early Block Placement先做局部布局、再全局連線適合大型設(shè)計如果不開工具會混在一起做時間陡然上升。我自己的路線是這樣早期功能驗證階段用Quick每半天能出一次版本臨近時序收斂階段切到Explore雖然一次要跑七八小時但能顯著減少后續(xù)手動優(yōu)化時序的反復(fù)次數(shù)。Quartus這邊也有類似機制叫“布局布線努力級別”Placement Effort分別有Fast、Standard、Extra三種。功能調(diào)試階段用Fast正式出板前用StandardExtra級別我很少用因為它比較適合那種“資源已經(jīng)吃到95%以上、時序怎么也收不攏”的極端場景。2.4 時序約束清理少給工具添亂這一步是我自己踩坑最多的地方。很多FPGA工程師的約束文件是“復(fù)制粘貼”流派的產(chǎn)物——從舊工程復(fù)制過來改幾個引腳名根本沒考慮那些約束是否仍然適用。結(jié)果就是工程里堆了大量無效約束比如已經(jīng)廢棄的時鐘約束、特定路徑上的假路徑約束、或者相互矛盾的約束。這些約束雖然不會讓編譯報錯但會讓布局布線工具花大量時間去分析、嘗試滿足那些本來就不必要的目標。比如某個時鐘樹的約束頻率是100MHz但你實際跑40MHz中間有大量裕量工具卻依然按照100MHz去布局布線白白增加難度。我做過一次清理把一個工程里300多條約束精簡到160條編譯時間直接少了40分鐘時序結(jié)果反而變好了——因為工具聚焦在真正需要優(yōu)化的路徑上了。約束清理有一個比較穩(wěn)妥的步驟先用時序摘要報告把當前設(shè)計“真實存在的時鐘域”理清再對照約束文件里那些時鐘是否真的存在那些被約束的路徑是否真的在設(shè)計中。不存在的東西大膽刪。存疑的先不用急著刪用set_false_path和set_clock_groups標注成異步關(guān)系告訴工具“這段不用管”。2.5 進程與內(nèi)存調(diào)優(yōu)讓機器全力輸出FPGA編譯是CPU密集型也是內(nèi)存密集型操作。Vivado默認的內(nèi)存配置偏保守很多機器明明有64GB內(nèi)存Vivado默認只敢用一半不到。你自己可以根據(jù)工程大小做調(diào)整set_param general.maxThreads 8 set_param place.placeEffortLevel High set_param route.routerEffortLevel HighmaxThreads這個參數(shù)是控制Vivado使用的CPU線程數(shù)的默認往往只用到2~4個線程如果你機器是16核以上這個參數(shù)不改就是浪費硬件。我實測過一樣的工程線程數(shù)從4調(diào)到12布局階段能快30%左右。不過要注意線程數(shù)不是越高越好線程切來切去的開銷也會影響速度建議設(shè)成物理核數(shù)的70%左右。內(nèi)存方面32GB內(nèi)存跑30萬LUT以上的工程已經(jīng)有點吃緊了稍大一點的設(shè)計可能跑到一半報“內(nèi)存不足”然后整個工具直接崩掉。這種情況下還沒到調(diào)策略的時候先加內(nèi)存或者調(diào)大Vivado進程可用內(nèi)存上限再說。Linux下可以用ulimit調(diào)整Windows下改環(huán)境變量或者在啟動腳本里加-max_memory參數(shù)具體數(shù)值要看工程規(guī)模我自己的經(jīng)驗是預(yù)留出工程占用內(nèi)存的1.5倍以上才比較安全。3. 自動化編譯與CI集成把“等編譯”變成“看結(jié)果”3.1 用腳本串起完整構(gòu)建流程FPGA編譯提速不只是調(diào)工具參數(shù)流程自動化也能幫你省出大量“無效等待時間”。以前我的習慣是打開Vivado GUI點Flow → Synthesis → Implementation然后盯進度條發(fā)呆。后來我全部改成Tcl腳本批處理模式一鍵式跑完整流程open_project ./prj/project.xpr synth_design -top top -part xc7z045ffg900-2 write_checkpoint -force ./checkpoints/synth.dcp place_design phys_opt_design route_design write_bitstream -force ./output/top.bit close_project這里有個細節(jié)phys_opt_design是在布局后做物理優(yōu)化對時序收斂效果明顯但費時。如果工程處于早期驗證階段建議先注釋掉這行能省不少時間只有當最終時序收不攏或關(guān)鍵路徑有違例時才啟用它。腳本化之后我又把不同配置的編譯任務(wù)串成隊列用Makefile管理。比如compile_cmp: # 全量編譯 vivado -mode batch -source run_all.tcl compile_quick: # 快速編譯 vivado -mode batch -source run_quick.tcl這樣一次跑多個配置的工程不用每次手動改參數(shù)再點一次開始。更重要的是把編譯結(jié)果輸出到固定目錄后續(xù)上板調(diào)試、版本對比、歸檔都方便多了。3.2 夜間構(gòu)建與并行排隊的實踐有了腳本之后我一般會在下班前把“夜間構(gòu)建”跑起來。先跑一個全量編譯輸出bit文件再跑一個帶Quick策略的差異編譯用于第二天早上的快速迭代。早上到工位第一件事先看昨晚的編譯日志有沒有ERROR和CRITICAL WARNING沒問題就直接上板驗證有問題也能定位到具體是哪一行改動導(dǎo)致。并行排隊的實踐經(jīng)驗是不要讓兩個大型編譯同時跑在同一臺機器上。Vivado和Quartus的布局布線工具對CPU和內(nèi)存的占用都非?!鞍缘馈眱蓚€工程同時跑單個工程的耗時不是簡單變慢一兩倍而是可能慢到四五倍總吞吐反而下降。合理做法是一臺機器跑一個全量編譯另一臺機器跑另一個全量編譯中間留一點余量給日常仿真和編輯器。如果你只有一臺機器就用“時間切片”的方式白天跑Quick版本晚上跑Explore版本。4. 實戰(zhàn)對比同一個工程13小時和5小時的差異來源4.1 項目背景與原用時分布說這么多不如直接來一個實戰(zhàn)對比。我手頭這個工程是一個中等規(guī)模的圖像采集系統(tǒng)主控用的是Xilinx UltraScaleXCU50LUT消耗約38萬DSP用了400多個BRAM占用六成左右。工程本身包含2個PCIe硬核接口、4路MIPI CSI-2接收、一個圖像縮放模塊和一個DDR4控制器。工程結(jié)構(gòu)上大概有120個子模塊其中三分之一是IP核。原始狀態(tài)下我直接采用Vivado默認策略跑沒有開增量編譯沒有做約束清理線程數(shù)也是默認的編譯一次耗時12小時50分接近13小時。具體分布如下項目默認配置耗時綜合1小時20分布局3小時05分布線5小時40分時序分析/優(yōu)化1小時50分Bit輸出55分當時項目一周要出3~4個版本每個版本等13個小時那就是整整兩天時間耗在等編譯上。而且這還不是最痛苦的情況如果時序收斂失敗還得重新跑時間直接翻倍。4.2 調(diào)整后的配置與效果對比后來我按上面說的五個方向逐項調(diào)整最終一次編譯跑下來是5小時10分鐘。具體改動如下打開增量編譯保留上一次的Checkpoint。綜合策略從RuntimeOptimized改為PerformanceOptimized綜合時間多了20分鐘但布局布線省了將近2個小時。布局策略從默認的Explore降級為ExtraTimingOpt少跑一輪全局迭代。布線直接使用Quick策略等最終版本快凍結(jié)前再切換到Explore跑一次徹底收斂。清理了冗余約束把異步路徑用set_clock_groups隔離。線程數(shù)調(diào)整到8同時把進程內(nèi)存上限調(diào)高。整個編譯流程全部腳本化在Batch模式下執(zhí)行。調(diào)整后的分布如下項目優(yōu)化后耗時綜合1小時40分布局1小時20分布線1小時35分時序分析/優(yōu)化15分Bit輸出20分總計約5小時10分5小時10分對比原來的12小時50分省了將近八個小時。這個提升不是說某一個參數(shù)靈丹妙藥而是多個手段疊加出來的效果。其中趁力最大的是前三個綜合策略優(yōu)化、增量編譯、布線策略調(diào)整這三項加起來大約省了6個小時。有一個點需要說明Quick布線策略確實會讓時序質(zhì)量略微下降。我那個工程最終版本做全量時序簽核時關(guān)鍵路徑的WNS從Quick模式的-0.06ns改善到Explore模式的0.15ns。如果做量產(chǎn)版本還是要切換到Explore跑一次完整收斂成本是可以接受的。5. 常見問題排查這些坑我替你踩過了5.1 增量編譯不生效或變慢很多人開了增量編譯后發(fā)現(xiàn)速度不升反降。排查思路從這三個方面入手確認上一次編譯生成的Checkpoint還在且沒有被清理掉。Vivado的增量編譯依賴上一次的dcp如果之前跑過reset_run或delete_project增量信息就丟了。確認代碼改動范圍。如果某個模塊的接口大改或者頂層模塊的端口變了Vivado會認為“影響面太大”自動退化為全量編譯。這種情況打開日志會看到“cannot reuse previous netlist”之類的提示。確認沒有把整個工程目錄拷到另一臺機器上編譯。增量編譯的Checkpoint文件里存儲的路徑信息是絕對路徑換個目錄或換臺機器路徑不一樣工具找不到可復(fù)用的部分就會重新來一遍。5.2 線程數(shù)設(shè)了不生效有時候你在Tcl控制臺設(shè)置了maxThreads但新開的編譯任務(wù)又變回默認值。原因是這個設(shè)置是會話級的不是在工程級生效。每次啟動Vivado Batch模式它都會重新讀取默認配置。正確做法是在啟動之前設(shè)置環(huán)境變量或者把參數(shù)寫進啟動腳本# 在 start_synth.tcl 或 vivado_settings.tcl 開頭加入 set_param general.maxThreads 8或者直接在命令行傳參vivado -mode batch -source run_all.tcl -tclargs -max_threads 8還有一個比較容易踩的坑Windows系統(tǒng)的Vivado對線程支持不如Linux。我的實測數(shù)據(jù)是同樣是8線程Linux下的布局布線大概比Windows快15%左右。如果你有條件正式工程的編譯盡量放到Linux服務(wù)器上跑。5.3 布局布線工具崩潰或內(nèi)存不足工程跑到40萬LUT以上內(nèi)存不足是家常便飯。報錯insufficient memory或者直接彈一個“FATAL ERROR”生成日志里往往沒有任何有用信息。排查方法是先看系統(tǒng)內(nèi)存使用情況確認沒有其他大進程占用。然后確認Vivado的位數(shù)現(xiàn)在基本都是64位再看是否需要增大交換分區(qū)。更大的坑在于布局布線工具崩潰之后增量Checkpoint會損壞導(dǎo)致下一次編譯無法復(fù)用舊結(jié)果。這時候唯一的辦法是刪掉checkpoints目錄下所有文件重新跑全量。所以我的習慣是每一次全量編譯成功之后把生成的dcp文件壓縮備份一份萬一后續(xù)增量編繹失敗還能恢復(fù)到這個“干凈全量”狀態(tài)而不是從頭再來。5.4 約束文件刪了時序反而更差有朋友照著我的思路做了約束清理結(jié)果編譯速度上來是上來了但時序卻更差了。排查后發(fā)現(xiàn)原因是他把一些確實需要優(yōu)化的路徑上的約束也順手刪了。我建議不是沖上去刪而是先跑一個report_timing_summary把時序違例的路徑和冗余約束對一下。只有確定某條約束覆蓋的路徑完全不存在、或確實是異步路徑才刪得動手。刪完之后再用report_clock_interaction確認時鐘邊界已經(jīng)處理清楚。6. 工具選型與硬件搭配花多少錢買多少時間編譯提速這件事除了軟件調(diào)參硬件也是一個繞不開的變量。我自己前后用過三臺不同的機器跑同一個工程數(shù)據(jù)很有參考價值機器配置編譯耗時i7-9700K32GB內(nèi)存SATA SSD接近17小時R9 5950X64GB內(nèi)存NVMe SSD12小時50分雙路EPYC 7302128GB內(nèi)存NVMe SSD8小時45分雙路EPYC 7302 分布式綜合5小時10分并不是說一定要上頂級服務(wù)器才能干活但如果你常規(guī)工程在20萬LUT以上至少16核 64GB內(nèi)存 NVMe SSD是底線。機械硬盤跑大工程IO瓶頸比CPU更明顯尤其在綜合階段工具會生成海量中間文件磁盤讀寫速度直接拖后腿。我之前在SATA SSD上跑綜合階段有時比機械盤還慢后來換成NVMe后才緩解。另外AMD和Intel的CPU在多線程性能上差異不小如果你主要跑Synopsys、Cadence、Vivado這類EDA工具AMD的銳龍和EPYC系列性價比很高多核性能強價格又比Xeon低一大截。Intel那邊的Xeon金牌系列雖然穩(wěn)但同樣核心數(shù)價格翻倍。個人項目或者小團隊銳龍9或者線程撕裂者基本上夠用團隊項目二手的EPYC平臺性價比非??鋸?。內(nèi)存方面Vivado吃內(nèi)存的巔峰時刻是布線階段尤其是phys_opt_design啟用后內(nèi)存需求幾乎是成倍增長。64GB內(nèi)存跑40萬LUT以內(nèi)的工程基本夠用再往上堆邏輯資源的話128GB起步比較穩(wěn)。內(nèi)存頻率對編譯速度的影響也有但不是決定性的別為了追求高頻內(nèi)存浪費預(yù)算容量優(yōu)先級更高。7. 結(jié)合團隊協(xié)作的提速思路省下的時間不只是一個人的編譯提速這件事表面上是縮短了一個人的等待時間但如果團隊合作協(xié)議得好提升是全員的。我目前比較推薦的做法是專職編譯服務(wù)器 排隊腳本 自動歸檔版本。具體來說團隊共用一臺高配編譯服務(wù)器或者兩臺一主一備所有成員把工程同步到服務(wù)器上編譯。每個人提交編譯任務(wù)時腳本自動判斷當前是否有其他編譯任務(wù)在跑如果有排隊等待如果沒有立刻啟動編譯。編譯完成之后自動歸檔bit文件、rpt報告、dcp文件到統(tǒng)一的版本目錄按時間和分支命名。編譯結(jié)果通過一個簡單的Web頁面或者直接用rsync同步到大家都能訪問的目錄通知到對應(yīng)的人。這種方式的好處是你不用在自己本地電腦上等PT編譯期間本地電腦還能正常寫代碼、跑仿真。而且服務(wù)器統(tǒng)一配置編譯環(huán)境的差異性問題也減少了。我見過很多團隊每個人都在自己的電腦上跑編譯結(jié)果同一個工程在兩個同事的電腦上出來的時序結(jié)果居然不一樣排查半天發(fā)現(xiàn)是一個人的Vivado版本比另一個人新或者一個裝了補丁一個沒裝。統(tǒng)一編譯服務(wù)器之后這類問題基本消失。當然這個方案有個前提編譯服務(wù)器的算力要夠。否則多人排隊反而比各自編譯更慢。如果團隊規(guī)模比較小少于5人直接各自編譯也不是不行但最好統(tǒng)一Vivado版本和編譯參數(shù)。8. 寫在最后的幾條心得回到標題那個問題等13個小時和5個小時差的真的只是時間嗎我自己的體會是差的是一種敢于頻繁試錯的心態(tài)。編譯快你就敢大膽改架構(gòu)試方案敢多做幾次布局策略對比實驗敢在功能還沒完全確定的時候頻繁出版本驗證關(guān)鍵路徑。編譯慢你會不自覺地畏手畏腳生怕一次改動不完美就浪費掉一天的等待結(jié)果反而到了項目后期積累了一大堆邏輯問題改Bug的成本成倍上升。我在實際使用中還有一個習慣每次全量編譯成功之后把當時的dcp文件和編譯日志壓縮打包按日期歸檔。一是為了增量編譯兜底二是方便回溯——出了時序問題或者功能問題可以快速找到“這個版本之前到底做了什么改動”。有一次客戶反饋一個偶發(fā)問題我硬是靠著一個多月前的dcp文件恢復(fù)出當時的綜合網(wǎng)表逐條對比才發(fā)現(xiàn)是某個IP核升級導(dǎo)致的接口時序變了。沒有這個歸檔習慣那次排查的成本不知道要高出多少。如果你現(xiàn)在正在被FPGA編譯速度折磨我建議你動手的時候先從最便宜的事做起開增量編譯、清理冗余約束、改線程數(shù)。這三步不用花一分錢也不需要換機器就能感受到明顯差異。等這三步做完還不滿足再考慮換策略、上多機分布式、加內(nèi)存。成本高一些的項目放在后面做。最后分享一個小技巧Vivado的report_qor_suggestions命令會直接給你一些針對當前工程時序和擁塞的優(yōu)化建議雖然不能直接幫你提速但能告訴你當前工程到底是受限于布線擁塞、時序緊還是資源吃緊。搞清楚瓶頸在哪再來決定該加機器還是該改代碼就不會做無用功了。