絡(luò)時(shí)序優(yōu)化:從XDC約束到RTL寄存器復(fù)制的完整實(shí)戰(zhàn))
做FPGA設(shè)計(jì)久了你會(huì)發(fā)現(xiàn)一個(gè)特別有意思的現(xiàn)象同一段RTL不同的人做的約束策略不一樣出來(lái)的時(shí)序結(jié)果可能天差地別。有一回我排查一個(gè)圖像縮放工程的關(guān)鍵路徑查來(lái)查去最后發(fā)現(xiàn)罪魁禍?zhǔn)拙褪且粋€(gè)扇出fanout超過(guò)500的使能信號(hào)。當(dāng)時(shí)我就在Vivado里寫(xiě)了一條set_property MAX_FANOUT的XDC約束但綜合、實(shí)現(xiàn)跑完時(shí)序沒(méi)改善多少問(wèn)題根本沒(méi)解決。后來(lái)才搞清楚扇出約束這事兒遠(yuǎn)不是寫(xiě)一行約束那么簡(jiǎn)單它牽扯到綜合策略、實(shí)現(xiàn)階段的物理優(yōu)化、代碼里寄存器的組織方式甚至復(fù)位信號(hào)的專門(mén)處理。這篇文章就把我從XDC約束寫(xiě)到代碼優(yōu)化的完整排查經(jīng)驗(yàn)梳理一遍把扇出約束這個(gè)專題講透。1. 扇出到底指什么一個(gè)信號(hào)驅(qū)動(dòng)到底有多難1.1 先搞清楚工具里的Fanout是怎么統(tǒng)計(jì)的扇出的字面意思很直白就是一個(gè)寄存器輸出端信號(hào)所驅(qū)動(dòng)的負(fù)載單元數(shù)量。但FPGA里負(fù)載不完全等于LUT端口它還包括DSP、BRAM、進(jìn)位鏈、甚至IOB等資源。比如一個(gè)寄存器輸出同時(shí)接到了20個(gè)LUT的輸入、5個(gè)DSP的CE端口、還有8個(gè)觸發(fā)器復(fù)位端那這個(gè)網(wǎng)絡(luò)的扇出就是33。在Vivado里看扇出最直接的方式是打開(kāi)綜合后的原理圖Schematic選中任意一根網(wǎng)絡(luò)屬性面板里就會(huì)顯示Fanout值或者用Tcl命令查詢get_property FANOUT [get_nets valid_global]不過(guò)要注意綜合階段的扇出統(tǒng)計(jì)和布局布線后的扇出統(tǒng)計(jì)不完全一致。綜合工具看到的邏輯連接關(guān)系是理想化的而布局布線后由于物理優(yōu)化phys_opt_design可能復(fù)制寄存器、重映射邏輯網(wǎng)絡(luò)的扇出會(huì)有變化。所以排查時(shí)序問(wèn)題時(shí)應(yīng)該以實(shí)現(xiàn)后的報(bào)告為準(zhǔn)尤其是report_design_analysis里列出的高扇出網(wǎng)絡(luò)High Fanout Nets。1.2 高扇出為什么會(huì)讓時(shí)序爆炸扇出高為什么會(huì)影響時(shí)序我用一句話概括驅(qū)動(dòng)一個(gè)負(fù)載和驅(qū)動(dòng)五百個(gè)負(fù)載信號(hào)翻轉(zhuǎn)時(shí)需要充放電的電容差了兩個(gè)數(shù)量級(jí)延遲自然就上去了。放到FPGA內(nèi)部看這個(gè)過(guò)程會(huì)更具體。一個(gè)寄存器的輸出Q要送到下一級(jí)寄存器的D端口信號(hào)從Q端出來(lái)后會(huì)先走一段可控的布線資源經(jīng)過(guò)可編程開(kāi)關(guān)矩陣Switch Matrix中轉(zhuǎn)再逐級(jí)扇出到各個(gè)LUT的輸入。扇出越高需要經(jīng)過(guò)的開(kāi)關(guān)節(jié)點(diǎn)越多RC寄生效應(yīng)越明顯網(wǎng)絡(luò)延遲Net Delay就會(huì)變大。這個(gè)Net Delay在時(shí)序報(bào)告里體現(xiàn)得很直觀——路徑延遲從幾百皮秒漲到幾納秒配合組合邏輯延遲直接吃掉整個(gè)時(shí)鐘周期的預(yù)算。另外高扇出還會(huì)帶來(lái)一個(gè)隱蔽問(wèn)題叫偏移Skew。信號(hào)驅(qū)動(dòng)多個(gè)負(fù)載時(shí)由于各負(fù)載的布線路徑長(zhǎng)度不一致信號(hào)到達(dá)各自負(fù)載的時(shí)間會(huì)有差異。對(duì)數(shù)據(jù)信號(hào)來(lái)說(shuō)這個(gè)Skew會(huì)讓后一級(jí)的建立時(shí)間裕量變差對(duì)復(fù)位信號(hào)來(lái)說(shuō)Skew會(huì)讓不同寄存器的復(fù)位釋放時(shí)刻不一致嚴(yán)重時(shí)直接導(dǎo)致功能錯(cuò)誤。我個(gè)人的經(jīng)驗(yàn)閾值是這樣的普通數(shù)據(jù)信號(hào)扇出超過(guò)100就該留心使能類信號(hào)、復(fù)位信號(hào)、模式配置信號(hào)只要上200就建議主動(dòng)干預(yù)。Vivado默認(rèn)把扇出超過(guò)1000的網(wǎng)絡(luò)定義成High-Fanout Net但如果真想等項(xiàng)目跑到那一步再去優(yōu)化時(shí)序早就飛了。2. XDC扇出約束的正確寫(xiě)法set_property MAX_FANOUT的語(yǔ)法與生效范圍2.1 約束語(yǔ)法和作用對(duì)象XDC扇出約束的核心命令就一條語(yǔ)法非常簡(jiǎn)潔set_property MAX_FANOUT 50 [get_nets valid_global]這里MAX_FANOUT指定的數(shù)值是工具允許保留的最大扇出數(shù)等于告訴綜合器和實(shí)現(xiàn)工具這個(gè)網(wǎng)絡(luò)驅(qū)動(dòng)超過(guò)50個(gè)負(fù)載時(shí)你就要想辦法復(fù)制源端寄存器把負(fù)載拆開(kāi)。這個(gè)屬性的作用對(duì)象比較靈活可以是網(wǎng)絡(luò)net、單元引腳cell pin、甚至是頂層端口port。日常用得最多的是網(wǎng)絡(luò)和引腳。比如約束某個(gè)寄存器輸出引腳的扇出set_property MAX_FANOUT 20 [get_pins u_ctrl_sync/en_ff_reg/C]注意這里的引腳指的是驅(qū)動(dòng)管的輸出引腳不是負(fù)載端的輸入引腳。如果手滑選成了負(fù)載端的輸入引腳這條約束基本不會(huì)生效因?yàn)楣ぞ邚?fù)制寄存器時(shí)關(guān)心的是源端驅(qū)動(dòng)能力。2.2 綜合階段和實(shí)現(xiàn)階段的約束行為差異很多人以為寫(xiě)了XDC約束綜合和實(shí)現(xiàn)都會(huì)自動(dòng)遵守。但實(shí)際Vivado處理MAX_FANOUT的方式有階段差異綜合階段Vivado綜合器Vivado Synthesis會(huì)把MAX_FANOUT當(dāng)作邏輯復(fù)制的參考依據(jù)。綜合時(shí)遇到高扇出網(wǎng)絡(luò)如果設(shè)置了MAX_FANOUT工具會(huì)自動(dòng)復(fù)制源端寄存器生成多份邏輯相同的寄存器來(lái)分擔(dān)負(fù)載。實(shí)現(xiàn)階段布局布線前Vivado會(huì)對(duì)整個(gè)設(shè)計(jì)做時(shí)序預(yù)估。這里的物理優(yōu)化phys_opt_design也會(huì)根據(jù)MAX_FANOUT做進(jìn)一步的寄存器復(fù)制或合并。但物理優(yōu)化更關(guān)注實(shí)際布局的擁塞程度和時(shí)序瓶頸它可能會(huì)把綜合階段復(fù)制出來(lái)的寄存器重新合并——如果它判斷復(fù)制反而會(huì)導(dǎo)致布局更擁擠、路徑更長(zhǎng)。布局布線后布線階段基本不再做寄存器復(fù)制。這個(gè)階段再大量調(diào)整邏輯結(jié)構(gòu)已經(jīng)不現(xiàn)實(shí)了所以扇出約束必須在布局前完成布局。這個(gè)階段差異直接影響了你排查問(wèn)題的思路。比如綜合后打開(kāi)原理圖扇出已經(jīng)變小了但實(shí)現(xiàn)完一查又變回去了那大概率是物理優(yōu)化重新合并了這些復(fù)制寄存器的結(jié)果。2.3 一個(gè)典型的約束配置案例假設(shè)工程里有一個(gè)時(shí)鐘使能信號(hào)ce_mult驅(qū)動(dòng)了96個(gè)乘法器的CE端口時(shí)序報(bào)告顯示這條路徑是瓶頸。我的常見(jiàn)做法是分兩步走第一步先在XDC里添加MAX_FANOUT約束set_property MAX_FANOUT 32 [get_nets ce_mult]第二步在綜合設(shè)置里確認(rèn)沒(méi)有強(qiáng)制關(guān)閉高扇出優(yōu)化。如果是用綜合策略Synthesis Strategy默認(rèn)的Flow_PerfOptimized_high高扇出復(fù)制默認(rèn)開(kāi)啟如果用了某些追求面積的策略工具可能會(huì)優(yōu)先省寄存器而不主動(dòng)復(fù)制。接著跑綜合看綜合網(wǎng)表里該網(wǎng)絡(luò)的扇出是否降到了32以內(nèi)。如果降下來(lái)了說(shuō)明綜合階段約束生效如果沒(méi)降需要進(jìn)入下一章的排查流程。3. 寫(xiě)了約束卻沒(méi)效果的常見(jiàn)原因約束失效的完整排查鏈路3.1 排查鏈路第一步確認(rèn)約束有沒(méi)有作用到目標(biāo)對(duì)象上我自己踩過(guò)的一個(gè)坑是在XDC里寫(xiě)了set_property MAX_FANOUT 50 [get_nets valid_global]但綜合后打開(kāi)原理圖一看valid_global網(wǎng)絡(luò)還是三百多負(fù)載。排查第一步就是檢查get_nets到底選中了什么對(duì)象。綜合器在處理RTL時(shí)會(huì)給很多中間信號(hào)自動(dòng)生成新名字原始RTL里的信號(hào)名可能被加了后綴比如valid_global_reg_0_0。這個(gè)時(shí)候你用get_nets valid_global可能選中的是一個(gè)中間層次的名字而驅(qū)動(dòng)負(fù)載的實(shí)際網(wǎng)絡(luò)名已經(jīng)變了。解決辦法是用通配符set_property MAX_FANOUT 50 [get_nets -hierarchical *valid_global*]-hierarchical選項(xiàng)會(huì)遞歸尋找所有層級(jí)的網(wǎng)絡(luò)通配符*能匹配中間生成的名稱。加了這個(gè)選項(xiàng)之后大概率能選中真實(shí)的目標(biāo)網(wǎng)絡(luò)。3.2 排查鏈路第二步區(qū)分綜合階段和實(shí)現(xiàn)階段的布局行為還有一種情況綜合階段約束確實(shí)生效了原理圖里網(wǎng)絡(luò)扇出變成了50以內(nèi)但實(shí)現(xiàn)完查看最終布線結(jié)果扇出又漲回去了。原因就是我在第2章提到的物理優(yōu)化重新合并了寄存器。這時(shí)候你需要查實(shí)現(xiàn)日志搜索關(guān)鍵詞replicating register或者register duplication。Vivado的phys_opt_design在判斷某個(gè)寄存器復(fù)制后對(duì)時(shí)序無(wú)益或讓布線擁塞惡化時(shí)會(huì)撤銷綜合階段的復(fù)制。常見(jiàn)觸發(fā)條件是復(fù)制出的寄存器分散太遠(yuǎn)導(dǎo)致源端到各副本的路徑變長(zhǎng)或者布局資源緊張副本沒(méi)有合適位置放置。面對(duì)這種情況我的建議是不要只依賴MAX_FANOUT約束改用下一章的代碼優(yōu)化手段把寄存器復(fù)制落實(shí)到RTL層面。3.3 排查鏈路第三步檢查OOC模塊綜合的XDC加載情況這條經(jīng)驗(yàn)主要針對(duì)使用Out-Of-ContextOOC方式綜合子模塊的工程。OOC模式單獨(dú)綜合子模塊時(shí)主工程的XDC約束默認(rèn)是不加載進(jìn)來(lái)的。這意味著你在頂層X(jué)DC里寫(xiě)的MAX_FANOUT約束對(duì)OOC子模塊內(nèi)部網(wǎng)絡(luò)根本不生效。如果子模塊內(nèi)部確實(shí)有高扇出網(wǎng)絡(luò)需要在子模塊的XDC文件里單獨(dú)設(shè)置約束或者在綜合設(shè)置中將該模塊的XDC文件加入。Vivado的OOC綜合有一個(gè)選項(xiàng)叫-include_optimization或者在子模塊的約束文件里直接加同一條MAX_FANOUT屬性。這一步非常容易漏我見(jiàn)過(guò)不少團(tuán)隊(duì)因?yàn)镺OC子模塊的扇出約束缺失導(dǎo)致綜合結(jié)果和全工程實(shí)現(xiàn)結(jié)果不一致。排查這條鏈路時(shí)一個(gè)直接的手段是在綜合后打開(kāi)子模塊原理圖檢查目標(biāo)網(wǎng)絡(luò)的扇出。如果約束沒(méi)加載就該去子模塊的XDC里補(bǔ)上。4. 代碼層面的扇出優(yōu)化手段從源頭拆解高扇出網(wǎng)絡(luò)4.1 手動(dòng)復(fù)制寄存器最直接的拆分方式很多人把扇出優(yōu)化的希望全寄托在工具的自動(dòng)復(fù)制上但工具畢竟是工具它不會(huì)理解設(shè)計(jì)的功能意圖復(fù)制的時(shí)機(jī)、位置也不一定理想。到項(xiàng)目后期遇到反復(fù)無(wú)法收斂的扇出問(wèn)題我的做法往往是回到RTL手動(dòng)復(fù)制寄存器??匆粋€(gè)例子原始代碼里有一個(gè)全局使能信號(hào)always (posedge clk) begin if (~rst_n) begin valid_ff 1b0; end else begin valid_ff valid_src; end end // valid_ff 輸出驅(qū)動(dòng)了200多個(gè)運(yùn)算單元的使能端口要把它拆成4份用generate循環(huán)可以少寫(xiě)很多重復(fù)代碼(* KEEP TRUE *) reg [3:0] valid_ff; genvar i; generate for (i 0; i 4; i i 1) begin : valid_copy_gen always (posedge clk) begin if (~rst_n) begin valid_ff[i] 1b0; end else begin valid_ff[i] valid_src; end end end endgenerate復(fù)制出來(lái)后每個(gè)valid_ff[i]只驅(qū)動(dòng)四分之一負(fù)載扇出直接從200降到50左右。需要注意兩點(diǎn)第一(* KEEP TRUE *)屬性告訴綜合器保留這組寄存器不要因?yàn)檫壿嫷葍r(jià)又把它合并回去。我見(jiàn)過(guò)不寫(xiě)這個(gè)屬性綜合器自作主張把4份寄存器合并成1份扇出問(wèn)題原樣復(fù)現(xiàn)。第二復(fù)制出的多份寄存器之間會(huì)存在時(shí)鐘Skew但對(duì)使能類控制信號(hào)來(lái)說(shuō)幾百皮秒的偏差通常不影響功能。如果是對(duì)時(shí)序敏感的跨時(shí)鐘域信號(hào)不建議這樣簡(jiǎn)單復(fù)制需要另行設(shè)計(jì)。手動(dòng)復(fù)制寄存器在物理上還有額外好處你可以在RTL里就有意識(shí)地規(guī)劃寄存器擺放位置。比如最好把4份寄存器分散在運(yùn)算陣列的不同象限這樣到各自負(fù)載的布線會(huì)比較短。工具復(fù)制的時(shí)候不一定能完美做到這一點(diǎn)。4.2 復(fù)位信號(hào)的扇出處理一套專門(mén)的打法復(fù)位信號(hào)是FPGA里最容易出現(xiàn)超高扇出的網(wǎng)絡(luò)沒(méi)有之一。芯片上電后一個(gè)全局復(fù)位要同時(shí)驅(qū)動(dòng)幾萬(wàn)個(gè)觸發(fā)器這種扇出用普通的MAX_FANOUT約束根本處理不了——你不可能讓工具復(fù)制幾十萬(wàn)份復(fù)位寄存器那會(huì)讓資源爆炸。處理復(fù)位信號(hào)的頭號(hào)選擇是用全局時(shí)鐘資源。Vivado里全局復(fù)位信號(hào)通常會(huì)建議走BUFG網(wǎng)絡(luò)或者直接映射到專用復(fù)位引腳這樣可以借助全局布線網(wǎng)絡(luò)的強(qiáng)驅(qū)動(dòng)能力大幅降低復(fù)位網(wǎng)絡(luò)的延遲。XDC里的寫(xiě)法通常是set_property MAX_FANOUT 200000 [get_nets rst_n]注意這里是故意給一個(gè)非常大的值意思是不希望綜合器對(duì)這個(gè)復(fù)位網(wǎng)絡(luò)做復(fù)制讓它走全局資源。因?yàn)槿謴?fù)位信號(hào)的負(fù)載本來(lái)就有幾萬(wàn)、十幾萬(wàn)如果約束值設(shè)得太小綜合器可能嘗試復(fù)制一堆寄存器結(jié)果布局資源被浪費(fèi)時(shí)序反而更差。對(duì)于局部復(fù)位情況又不同。一個(gè)模塊內(nèi)部的模塊級(jí)復(fù)位信號(hào)扇出一般在上千左右。這種場(chǎng)景我建議做一個(gè)復(fù)位同步器把全局復(fù)位轉(zhuǎn)換為兩級(jí)同步后的局部復(fù)位再進(jìn)模塊。復(fù)位同步器的標(biāo)準(zhǔn)結(jié)構(gòu)是reg [1:0] rst_sync; always (posedge clk) begin if (~rst_async_n) begin rst_sync 2b00; end else begin rst_sync {rst_sync[0], 1b1}; end end assign rst_sync_n rst_sync[1];這個(gè)同步器既解決了異步復(fù)位的亞穩(wěn)態(tài)問(wèn)題又給內(nèi)部邏輯提供了一個(gè)干凈的同步復(fù)位信號(hào)。配合上模塊的局部使用扇出范圍還是可以控制的。關(guān)于復(fù)位信號(hào)與扇出還有個(gè)常見(jiàn)困惑既然復(fù)位不能高扇出那能不能干脆不做全局復(fù)位全靠上電初始值我的建議是功能模塊可以依賴初始值但控制通路里的狀態(tài)機(jī)、FIFO指針、握手信號(hào)這些地方還是老老實(shí)實(shí)上復(fù)位信號(hào)不然仿真和上電后行為會(huì)很難一致。4.3 邏輯重構(gòu)與使能鏈路的優(yōu)化思路除了寄存器復(fù)制和復(fù)位處理代碼層面的邏輯重構(gòu)也是降低扇出的有力手段。高扇出信號(hào)不一定非得被直接復(fù)制如果能在邏輯上把廣播變成局部共享效果會(huì)更好。最常見(jiàn)的一種重構(gòu)是拆分條件表達(dá)式。假設(shè)有一個(gè)模式配置信號(hào)mode_cfg它的每一位都被幾十個(gè)模塊共同用來(lái)控制運(yùn)算模式。如果每個(gè)模塊都去讀取mode_cfg扇出必然很高。一種做法是在每個(gè)子模塊的入口處把mode_cfg打一拍存成局部寄存器always (posedge clk) begin if (mode_valid) begin mode_cfg_local mode_cfg; end end這樣一來(lái)頂層的mode_cfg只需要驅(qū)動(dòng)每個(gè)子模塊入口的一兩個(gè)寄存器而不是每一路組合邏輯。子模塊內(nèi)部使用打拍后的mode_cfg_local扇出就被限制在局部范圍內(nèi)。這個(gè)手法的本質(zhì)是讓配置信息的傳播路徑結(jié)構(gòu)化而不是讓它像洪水一樣漫到整個(gè)設(shè)計(jì)里。另一種重構(gòu)思路針對(duì)多路選擇器。當(dāng)一個(gè)選擇信號(hào)控制幾十個(gè)MUX時(shí)扇出同樣爆炸。優(yōu)化方向是把這種一刀切的選擇信號(hào)改成先局部寄存再逐級(jí)傳遞的形式。比如在通道化數(shù)據(jù)鏈路中可以把使能信號(hào)與數(shù)據(jù)流同步移位讓每一級(jí)處理單元不再共享同一個(gè)長(zhǎng)距離使能信號(hào)而是使用靠近自己的延遲后的使能副本。雖然代價(jià)是多了一些移位寄存器但在時(shí)序收斂面前這點(diǎn)資源開(kāi)銷完全值得。還有一個(gè)細(xì)節(jié)值得留意扇出優(yōu)化時(shí)不要腦子一熱把負(fù)載數(shù)壓得太低。有些工程師上來(lái)就寫(xiě)MAX_FANOUT 10結(jié)果綜合器復(fù)制了上百份寄存器布局資源、布線資源全線告急時(shí)序不升反降。經(jīng)驗(yàn)上把扇出控制在30到50之間是比較穩(wěn)妥的區(qū)間除非你的布局結(jié)構(gòu)天然支撐更低的扇出。5. 一個(gè)圖像處理工程的扇出修復(fù)實(shí)戰(zhàn)從時(shí)序違例到收斂5.1 問(wèn)題現(xiàn)場(chǎng)一個(gè)300負(fù)載的使能信號(hào)去年做一個(gè)圖像縮放加速模塊時(shí)鐘跑到200MHz綜合和實(shí)現(xiàn)都能過(guò)但時(shí)序收斂不干凈。Vivado時(shí)序報(bào)告里WNS最差負(fù)裕量是-0.42ns關(guān)鍵是這個(gè)負(fù)裕量對(duì)應(yīng)的路徑重復(fù)出現(xiàn)在好幾條路徑上顯示的都是同一個(gè)網(wǎng)絡(luò)valid_global。打開(kāi)report_design_analysis找到高扇出網(wǎng)絡(luò)列表valid_global赫然掛著300多個(gè)負(fù)載。它的驅(qū)動(dòng)源是圖像處理流水線里的數(shù)據(jù)有效信號(hào)這個(gè)信號(hào)同時(shí)控制整條流水線200多個(gè)乘加單元的CE端口外加若干狀態(tài)機(jī)的跳變條件。路徑上組合邏輯本身不長(zhǎng)但Net Delay因?yàn)樯瘸鎏蟊焕搅?.8ns加上源端寄存器的Clk-to-Q和目的端的建立時(shí)間一個(gè)周期下來(lái)剛好差了口氣。5.2 第一次嘗試XDC約束為什么沒(méi)有達(dá)到預(yù)期我先按照常規(guī)思路在頂層X(jué)DC里加了約束set_property MAX_FANOUT 50 [get_nets valid_global]跑完綜合打開(kāi)網(wǎng)表原理圖檢查扇出確實(shí)降到了50左右綜合器復(fù)制出了6份valid信號(hào)寄存器。但繼續(xù)跑布局布線實(shí)現(xiàn)后的時(shí)序報(bào)告顯示W(wǎng)NS仍然是負(fù)的雖然不是-0.42ns那么差但也只到了-0.2ns左右。再次實(shí)現(xiàn)后檢查網(wǎng)絡(luò)扇出發(fā)現(xiàn)綜合階段復(fù)制出的6份寄存器的布局情況很不理想——其中3份被擺到了離負(fù)載很遠(yuǎn)的地方布線的繞線增加了凈延遲的改善遠(yuǎn)不如預(yù)期。我觀察到實(shí)現(xiàn)日志里還出現(xiàn)了merge register的記錄意味著物理優(yōu)化認(rèn)為部分復(fù)制不劃算又把它們合并了。這其實(shí)暴露了純靠XDC約束的短板工具自動(dòng)復(fù)制但它不會(huì)站在功能結(jié)構(gòu)的角度做最優(yōu)布局。它復(fù)制的邏輯可能覆蓋到了不合理的層級(jí)也可能因?yàn)橘Y源擺放導(dǎo)致副本間的布線互相干擾。5.3 第二次嘗試RTL手動(dòng)復(fù)制與結(jié)構(gòu)重構(gòu)被這次失敗刺激之后我決定回到RTL做手動(dòng)復(fù)制徹底控制寄存器的數(shù)量和位置。具體動(dòng)作分三步第一步把RTL里唯一的valid_ff拆成4份用generate循環(huán)生成并用(* KEEP TRUE *)屬性鎖住。4份正好對(duì)應(yīng)流水線運(yùn)算陣列的四個(gè)象限。第二步在RTL里用(* DONT_TOUCH TRUE *)或者(* KEEP_HIERARCHY TRUE *)把復(fù)制邏輯和負(fù)載邏輯包在同一個(gè)子模塊里防止綜合器做跨層優(yōu)化時(shí)又把它們合并。第三步寫(xiě)了一條placement約束把這4份寄存器手工鎖定到四個(gè)象限的空白區(qū)域。Vivado允許用set_property LOC或Pblock來(lái)做位置約束我這里用Pblock把4份寄存器分別圈住create_pblock valid_copy_0 add_cells_to_pblock [get_pblocks valid_copy_0] [get_cells {u_scale_core/valid_copy_gen[0].valid_ff_reg[*]}] resize_pblock [get_pblocks valid_copy_0] -add {SLICE_X20Y40 SLICE_X30Y70}雖然寫(xiě)Pblock需要一定后端經(jīng)驗(yàn)但這一步是讓扇出優(yōu)化真正落實(shí)到物理層面的關(guān)鍵。改完代碼、加上位置約束后重新跑綜合和實(shí)現(xiàn)結(jié)果如下表優(yōu)化階段扇出數(shù)WNSNet Delay (關(guān)鍵路徑)初始狀態(tài)320-0.42ns1.82ns僅XDC約束50綜合后/ 128實(shí)現(xiàn)后-0.21ns1.46nsRTL復(fù)制位置約束320.08ns0.94ns扇出從320降到32關(guān)鍵路徑的Net Delay從1.82ns降到0.94nsWNS由負(fù)轉(zhuǎn)正。整體實(shí)現(xiàn)后的資源占用率只增加了約1.5%完全可接受。5.4 修復(fù)后的復(fù)盤(pán)與經(jīng)驗(yàn)沉淀這個(gè)項(xiàng)目結(jié)束之后我總結(jié)出了幾條處理扇出問(wèn)題的核心原則先看路徑構(gòu)成再動(dòng)手。如果時(shí)序報(bào)告里Net Delay只占很小的比例組合邏輯Delay是主導(dǎo)那扇出不是瓶頸不必費(fèi)勁去優(yōu)化。如果Net Delay占了一半以上且對(duì)應(yīng)網(wǎng)絡(luò)負(fù)載數(shù)量巨大再考慮扇出優(yōu)化。工具自動(dòng)復(fù)制是備選而不是首選。XDC的MAX_FANOUT可以快速試水但效果不穩(wěn)定因?yàn)樗焕斫庠O(shè)計(jì)的布局意圖。對(duì)關(guān)鍵的時(shí)序路徑手動(dòng)RTL復(fù)制配合位置約束才是可控的手段。要防綜合器好心辦壞事。邏輯等價(jià)寄存器的大規(guī)模復(fù)制是綜合器的合法優(yōu)化但對(duì)我們來(lái)說(shuō)往往是噪聲。關(guān)鍵信號(hào)靠KEEP、DONT_TOUCH鎖住能讓綜合器的自由度縮小到可控范圍歸檔報(bào)告也更容易解釋。復(fù)位信號(hào)另外對(duì)待。全局復(fù)位不要設(shè)小的MAX_FANOUT值走全局時(shí)鐘資源才是正解局部復(fù)位置務(wù)必同步不要圖省事從全局復(fù)位直接拉線。我現(xiàn)在做新項(xiàng)目時(shí)已經(jīng)把扇出檢查前置到了RTL Review階段。綜合跑完第一版第一件事就是用report_design_analysis批量看高扇出網(wǎng)絡(luò)列表超過(guò)200扇出的網(wǎng)絡(luò)全部過(guò)一遍確定是主動(dòng)放任還是動(dòng)手優(yōu)化。提前干預(yù)一小時(shí)比項(xiàng)目后期反復(fù)跑實(shí)現(xiàn)省一整天這個(gè)賬非常劃算。最后再分享一個(gè)小技巧在Vivado里選中一個(gè)高扇出網(wǎng)絡(luò)按F9可以高亮所有負(fù)載這時(shí)圖形界面會(huì)非常直觀地展示這些負(fù)載在芯片上的分散程度。如果負(fù)載密密麻麻擠成一片布局問(wèn)題不大如果負(fù)載散布在芯片四角那無(wú)論怎么復(fù)制寄存器布線長(zhǎng)度都很難壓下來(lái)這時(shí)候要優(yōu)先考慮邏輯重構(gòu)把信號(hào)傳播路徑拉近而不是純粹靠復(fù)制解決問(wèn)題。