)
做FPGA上的除法和做乘法真的不是一回事。乘法有DSP硬核一拍就能拿到結(jié)果除法呢你一拍、兩拍、三四拍都未必能算完而且綜合出來動不動就是一片LUT。我之前在一個圖像縮放項目里需要把像素坐標做歸一化處理隨手寫了一個組合邏輯除法器結(jié)果一跑實現(xiàn)LUT直接爆掉時序報告里飄紅一片。后來老老實實改用Vivado里的Divider Generator IP從Radix2到Fractional模式挨個試了一圈才把整個配置鏈路吃透。這篇文章就把我實際踩過坑之后總結(jié)的配置思路、原理細節(jié)和調(diào)試技巧完整寫出來如果你也在用Vivado做除法運算并且正被IP核的各個參數(shù)搞得頭暈?zāi)沁@篇應(yīng)該能幫你省下不少時間。先說清楚這個IP能干什么Divider Generator IP是Xilinx官方提供的除法器生成器只需要配置好被除數(shù)與除數(shù)的位寬、有符號還是無符號、采用哪種除法算法以及輸出余數(shù)還是小數(shù)它就能自動生成一個時序收斂、面積可控的除法器模塊。整個過程不需要你手工設(shè)計任何除法邏輯也避開了組合邏輯除法器的時序災(zāi)難。適合的人至少有以下幾類正在做定點運算的算法工程師、需要用坐標變換或比例運算的FPGA開發(fā)者、以及所有在Vivado里被除法器時序問題折磨過的同學(xué)。1. 為什么不用組合邏輯除法而是用IP核1.1 組合邏輯與IP核的本質(zhì)差別先用一句話把問題點透FPGA上的除法不是一個“一拍算完”的操作。加法器和乘法器都有專用硬件資源除法則沒有對應(yīng)的DSP硬核它本質(zhì)上是一個迭代過程每算出一位商都要做一次減法、移位和比較所以天然需要消耗多個時鐘周期。如果你用組合邏輯硬寫這串迭代邏輯會被展開成一條極長的組合鏈路路徑延遲會隨著被除數(shù)位寬線性增長跑到100MHz以上通常就開始時序違例。我自己用Verilog寫過16bit除以8bit的組合除法器綜合后在Artix-7上最好的結(jié)果也只有大概87MHz而且LUT占用高得離譜。換用Divider Generator IP的Radix2模式同樣的位寬自動生成PPA功耗、性能、面積均衡的電路運行到150MHz一點問題都沒有LUT消耗反而下降了一大截。這才理解為什么官方IP核值得優(yōu)先選擇因為它內(nèi)部使用了移位減法結(jié)構(gòu)和流水線寄存器的合理排布把迭代過程分布到多個周期里從根上把時序問題解決了。1.2 Divider Generator IP能替你解決哪些事這個IP核幫你封裝的東西遠不止“除法”這一步。第一它處理了輸入數(shù)據(jù)的符號擴展和位寬對齊尤其是在有符號模式下負數(shù)的二進制補碼處理如果手工寫很容易出錯第二它自動加入了合適的流水線級數(shù)你只需要在配置界面里選擇一個Latency總周期數(shù)內(nèi)部每一級寄存器的位置都由Xilinx幫你排好第三它還提供了AXI4-Stream接口的封裝版本帶tvalid/tready握手信號可以在數(shù)據(jù)流系統(tǒng)中直接接入省去了一大堆跨模塊同步邏輯。更重要的是這個IP在面對“被零除”或是“運算中間產(chǎn)生溢出”這類異常情況時輸出行為是確定的、可預(yù)測的。相比自己寫的除法邏輯這些邊緣情況其實是最容易翻車的點而IP核已經(jīng)在硬件上做了專門定義?;谶@些原因我后來的項目里凡是出現(xiàn)除法的地方一律走IP核不再手搓。2. 打開配置界面之前先想清楚三件事2.1 算法類型Radix2還是High RadixVivado的Divider Generator IP在“Algorithm Type”一欄提供了多個選項坐標上一般分成Radix2和High Radix兩大陣營。Radix2是最傳統(tǒng)的逐位除法算法每次迭代只會計算出1bit的商實現(xiàn)簡單資源占用少但延遲周期數(shù)偏大High Radix則包括Radix4、Radix8、Radix16一次迭代能算出2bit、3bit或4bit商延遲會縮短但代價是內(nèi)部查找表和判斷邏輯變多面積上升。實際選型時怎么權(quán)衡我的經(jīng)驗是如果被除數(shù)位寬在16bit以內(nèi)Radix2完全夠用延遲差那么幾個周期對整體系統(tǒng)影響不大如果被除數(shù)達到32bit甚至更高同時系統(tǒng)對延遲敏感那就毫不猶豫選High Radix的高基數(shù)模式。還有一個補充角度——資源緊張的項目優(yōu)先Radix2速度敏感的項目優(yōu)先Radix4以上。2.2 數(shù)據(jù)格式有符號還是無符號這里的“有符號”和“無符號”直接決定IP內(nèi)部使用原碼還是補碼運算。無符號場景最簡單所有輸入輸出都按二進制正整數(shù)處理有符號場景下被除數(shù)和除數(shù)都以二進制補碼形式進入輸出商也是補碼。最容易踩坑的是位寬擴展。無符號模式下輸入位寬就是你配置的位寬有符號模式下為了保留符號位被除數(shù)通常需要在實際數(shù)據(jù)位寬基礎(chǔ)上額外增加一位符號位。比如你要做的是兩個16bit有符號數(shù)相除配置界面里的Dividend Width最好填17而不是16否則計算結(jié)果很容易在正負邊界上出錯。這個點很多人都會忽略我一開始就直接用了16bit結(jié)果發(fā)現(xiàn)負數(shù)除法結(jié)果全部不對后來查手冊才明白是符號位處理的問題。2.3 輸出形式余數(shù)還是小數(shù)用過C語言取模的都知道整數(shù)除法帶一個余數(shù)。在FPGA的除法器里你可以選擇把余數(shù)作為輸出Remainder模式也可以選擇輸出帶小數(shù)位的結(jié)果Fractional模式兩者只能選一個。Remainder模式的輸出由兩個字段組成商和余數(shù)。商取整余數(shù)同符號這在做取模運算、循環(huán)隊列索引、校驗算法時非常有用。Fractional模式則會把余數(shù)繼續(xù)算下去輸出一個帶有小數(shù)位寬的定點數(shù)適用于PID控制、坐標歸一化、比例系數(shù)計算等場景。一個小提醒如果你只要一個浮點小數(shù)的整數(shù)近似不想輸出余數(shù)Fractional模式比Remainder模式更符合直覺因為后續(xù)不需要再做任何余數(shù)換算。3. Radix2模式配置實戰(zhàn)從界面參數(shù)到例化驗證3.1 界面入口與核心參數(shù)逐項拆解在Vivado里打開IP Catalog搜索“Divider Generator”即可找到。雙擊進入配置界面后建議把“Show Disabled Ports”勾上這樣能看到所有可選端口方便后續(xù)連接。界面上第一頁關(guān)心的是這幾個參數(shù)。Component NameIP核在工程里的實例名建議起一個能看懂的名字比如divider_u16_s16后面引用時找起來方便。Algorithm Type選擇Radix2。Divisor Width除數(shù)的位寬填實際輸入位寬即可注意有符號時要包含符號位。Dividend Width被除數(shù)的位寬有符號場景下記得加符號位。Remainder Type選擇Remainder這里控制輸出的是余數(shù)而非小數(shù)。Remainder Fractional Width只在Fractional模式時生效Remainder模式下是置灰狀態(tài)。Signed or Unsigned下拉選擇是否帶符號。這些參數(shù)全部設(shè)置好以后界面上會立即計算出一個Latency值。Radix2模式下這個值不是隨便生成的它大概等于內(nèi)部迭代級數(shù)加上輸入輸出寄存器的級數(shù)你可以把它理解為從輸入有效到輸出有效之間的固定時鐘周期數(shù)。拿到這個數(shù)值后后續(xù)寫代碼做握手等待時直接引用即可。3.2 延遲周期的計算邏輯關(guān)于Latency這個參數(shù)我想多說幾句因為很多人栽在這里。Radix2除法器的Latency不完全等于被除數(shù)位寬它還包含取整、符號擴展以及流水線輸出階段帶來的額外周期。Xilinx官方給出的計算公式大概思路是基本迭代周期取決于除數(shù)和被除數(shù)的相對位寬關(guān)系再加上輸入級和輸出級各若干拍。如果配置界面選擇“Automatic”工具會給出一個針對當前參數(shù)優(yōu)化后的周期數(shù)如果選擇“Manual”也可以手工加大延時代價是增加寄存器資源。實操中我一般直接采用Automatic生成的Latency值在驗證平臺上用計數(shù)器等待這個值之后再去采樣輸出數(shù)據(jù)。如果發(fā)現(xiàn)采樣點不對再往回倒退查數(shù)據(jù)對齊。手動調(diào)大延遲通常只在timing緊張插流水線寄存器后需要對齊路徑時才用得到。3.3 例化模板與Verilog連接方式配置完成后IP核會在工程里生成一個例化模板。右鍵IP核選擇“Open IP Example Design”官方會生成一個完整的測試工程。這里我貼一段精簡版的例化代碼方便理解信號連接關(guān)系我是按AXI4-Stream接口方式使用的divider_u16_s16 u_divider ( .aclk (clk ), // 輸入時鐘 .s_axis_dividend_tvalid (dividend_valid ), // 被除數(shù)有效 .s_axis_dividend_tdata (dividend_data ), // 被除數(shù)數(shù)據(jù) .s_axis_divisor_tvalid (divisor_valid ), // 除數(shù)有效 .s_axis_divisor_tdata (divisor_data ), // 除數(shù)數(shù)據(jù) .m_axis_dout_tvalid (result_valid ), // 輸出有效 .m_axis_dout_tdata (result_data ) // 商與余數(shù)拼接輸出 );需要注意AXI4-Stream接口下輸出總線m_axis_dout_tdata里通常同時打包了商和余數(shù)具體的位段劃分規(guī)則會隨IP版本不同略有差異最穩(wěn)妥的做法是查看IP核生成的例化文件里注釋中的位段說明或者打開仿真波形對照輸入輸出做一次對齊確認。我第一次用的時候默認商是低字節(jié)余數(shù)在高字節(jié)結(jié)果反了折騰了一個下午。3.4 仿真測試把Radix2模式的邊界情況全測一遍寫測試平臺時我建議至少覆蓋這些測試向量同號正數(shù)相除、同號負數(shù)相除、異號相除、最大正數(shù)除以1、最小負數(shù)除以1、任意數(shù)除以自身以及除數(shù)為0的情況。除數(shù)為0時輸出結(jié)果會有明確行為有的版本會拉高一個division_by_zero標志有的版本則將商置為全1。知道這個行為后后續(xù)在代碼里做異常保護就簡單得多。測試平臺上我習(xí)慣用一個計數(shù)器從拉高輸入有效信號開始計數(shù)計數(shù)到Latency值時檢查輸出有效信號。如果tvalid為高則讀回數(shù)據(jù)否則報錯。整個過程用$display打印關(guān)鍵數(shù)據(jù)方便追蹤。實測下來Radix2模式的輸出與C語言整數(shù)除法結(jié)果完全一致只要位寬配置正確誤差為零。4. Fractional模式深度解析輸出小數(shù)位的關(guān)鍵配置4.1 為什么需要Fractional模式很多算法場景里除法的結(jié)果不希望被截斷成整數(shù)比如PID控制器里的誤差比例項、圖像處理里的歸一化坐標、電機控制里的占空比換算。這些場景如果只保留整數(shù)部分整個控制精度會大打折扣。Radix2或High Radix配合Remainder模式雖然能拿到余數(shù)但余數(shù)還要你自己換算成小數(shù)麻煩且易錯。Fractional模式下IP核直接幫你把余數(shù)繼續(xù)迭代計算輸出一個定點格式的小數(shù)很大程度上簡化了后續(xù)處理。4.2 配置要點Remainder Type選擇Fractional在IP核配置界面里把“Remainder Type”從Remainder切換到Fractional隨后“Remainder Fractional Width”會變成可配置項。這個寬度就是小數(shù)的二進制位寬它直接決定小數(shù)的精度。比如Fractional Width設(shè)為8那么小數(shù)部分就有8bit精度換算成十進制精度大約是1/256也就是0.0039左右對于大多數(shù)電機控制和圖像處理場景已經(jīng)足夠。需要理解的是這里的輸出并不是IEEE754浮點數(shù)而是一種定點數(shù)表示。把商視作一個定點數(shù)整數(shù)部分占前若干位小數(shù)部分占Fractional Width位。假設(shè)Dividend Width是16Divisor Width是8Fractional Width是8那么輸出總位寬約等于整數(shù)商位寬加上8位小數(shù)位寬。使用的時候你把結(jié)果當成一個左移了8位的整數(shù)去理解后續(xù)做乘法或加法時要記得小數(shù)點對齊。4.3 一個坐標歸一化的實際案例拿我做過的一個例子來說圖像縮放模塊里需要把像素坐標從原始分辨率映射到目標分辨率計算公式是 dst_x src_x * src_width / dst_width。如果src_width是1920dst_width是1080直接整數(shù)除法會丟掉很多精度。我當時的做法是配置一個Fractional模式除法器Dividend Width設(shè)為22實際需要19bit表示坐標加上符號位Divisor Width設(shè)為12Fractional Width設(shè)為16。這樣每次算出來的結(jié)果直接就是帶16bit小數(shù)的定點值后面做乘法再右移16位就得到最終坐標整個過程只用了兩次乘法一次除法精度完全夠用而且時序穩(wěn)定跑在200MHz。換成我自己寫的組合邏輯除法器想都不敢想。4.4 Fractional模式與Radix2的性能差異同樣位寬下Fractional模式的延遲會比Remainder模式稍微大一些因為小數(shù)部分的額外迭代需要更多周期。具體延遲數(shù)值在配置界面會實時顯示建議把這一列數(shù)值記錄下來用于后續(xù)時序約束。資源方面小數(shù)位寬越大迭代級數(shù)越多LUT占用也會緩慢增加所以Fractional Width不是越大越好夠用就行。我的原則是如果小數(shù)部分精度需求在1/1000以內(nèi)8bit就夠如果要在1/100000附近則需要17bit以上。定這個位寬之前最好先做一次數(shù)學(xué)換算別盲目填大。5. 仿真驗證、調(diào)試技巧與常見錯誤排查5.1 輸出有效信號的正確等待方式在所有除法器模塊里最容易讓新手困惑的就是什么時候去取輸出數(shù)據(jù)。Divider Generator IP的輸出端有一個m_axis_dout_tvalid信號這個信號拉高一個周期意味著當前時鐘沿上m_axis_dout_tdata總線上的內(nèi)容是有效結(jié)果。不要在你輸入數(shù)據(jù)valid拉高后的下一拍就去采數(shù)據(jù)而應(yīng)該等待tvalid信號從低到高的跳變。實際操作中還有一種情況數(shù)據(jù)連續(xù)輸入時tvalid會連續(xù)拉高多個周期每個周期對應(yīng)一組輸出數(shù)據(jù)。此時可以直接把它當作數(shù)據(jù)有效的門控信號把結(jié)果和源數(shù)據(jù)流對齊。如果業(yè)務(wù)邏輯需要一個“運算完成”的中斷可以直接把tvalid引到中斷控制器里省得自己數(shù)延遲周期數(shù)。5.2 仿真波形里看到全X態(tài)怎么辦這是我在調(diào)試中遇到最多的問題之一。全X態(tài)通常出現(xiàn)的原因是輸入數(shù)據(jù)在有效信號拉高時還是未知態(tài)或者IP核的復(fù)位時序不對。Divider Generator IP有可選復(fù)位端口如果不接復(fù)位內(nèi)部寄存器上電后默認值是0問題不大但如果例化時端口懸空某些版本會導(dǎo)致仿真器識別為X態(tài)。解決方法是把復(fù)位端口啟用并在測試平臺初始化階段拉低復(fù)位至少一個時鐘周期后再釋放。另一個常見原因是輸入被除數(shù)或除數(shù)的位寬和實際連接數(shù)據(jù)不一致比如你把一個16bit端口接到了12bit的reg上Vivado綜合時可能報warning仿真時則會出現(xiàn)高bit位長度不匹配導(dǎo)致的X態(tài)。這種情況建議把連接信號的位寬顯式寫清楚不要依賴隱式截斷。5.3 時序違規(guī)實現(xiàn)階段紅字怎么辦如果時序報告里出現(xiàn)除法器相關(guān)路徑違規(guī)最優(yōu)先的解決思路不是去調(diào)整布局布線而是回到IP核配置界面把Latency調(diào)大或者手動打開“Additional Pipeline Stages”選項。增加流水級的作用是把關(guān)鍵路徑拆短雖然輸出延遲增大但時鐘頻率能顯著提升。如果調(diào)大延遲后timing還是紅的下一步檢查除法運算是否處在過長的組合邏輯鏈路中。比如除法器的輸入來自一個大位寬減法器輸出又接了一個大位寬乘法器這會形成三連楊組合邏輯鏈最終導(dǎo)致關(guān)鍵路徑過長。解決辦法是在除法器前后各加一組寄存器打斷鏈路確保任何組合邏輯路徑上都只出現(xiàn)一個較重的運算模塊。5.4 典型配置錯誤速查表錯誤現(xiàn)象直接原因解決辦法負數(shù)除法結(jié)果出錯有符號模式下位寬未加符號位Dividend Width和Divisor Width增加1bit輸出商與預(yù)期完全相反商和余數(shù)位段順序弄反查看IP例化模板注釋確認位段排列輸出一直為0輸入有效信號未正確拉高檢查s_axis_*_tvalid連接不能懸空tvalid信號一直不拉高Latency未到達或復(fù)位未釋放等待Latency周期確認復(fù)位時序?qū)崿F(xiàn)階段除法路徑時序紅延遲配置過小流水級不足調(diào)大Latency或增加Additional Pipeline Stages6. 資源占用對比與速度評估選錯模式會差多少6.1 不同模式的資源占用實測以一個Dividend Width 16、Divisor Width 8的除法器為例在Artix-7器件上做對比Radix2模式配合Remainder輸出LUT占用大約在200到300之間FF占用在100到200之間沒有使用DSP同樣的位寬切到Radix4LUT會增加到350到450FF基本持平如果選Radix16LUT可能直接翻倍到600以上。這說明如果資源緊張Radix2是更穩(wěn)妥的選擇。再來看High Radix帶來的性能收益。使用Radix4相比Radix2Latency通常能減少20%到30%Radix16則能減少40%以上。但提升的代價就是LUT用量上升明顯。如果你是那種邏輯資源幾乎用滿的項目優(yōu)先保資源選Radix2配足夠流水級如果是做高速接口類應(yīng)用邏輯資源還有富余那就用Radix4或Radix8不用追求極致Radix16。6.2 DSP數(shù)量的誤解與說明很多第一次使用Divider Generator IP的人會問除法器能不能像乘法器那樣用DSP實現(xiàn)答案是不能。除法器的核心迭代邏輯是減法加比較這種結(jié)構(gòu)不適合映射到乘法器專用的DSP48E片上硬件單元里因此整體實現(xiàn)完全依賴LUT和FF。在評估工程資源占用時不要為除法器預(yù)留DSP資源要預(yù)留的是邏輯資源和相應(yīng)的布線資源。如果你的設(shè)計里DSP資源非常緊張可以把所有乘法和除法放在同一個IP里單獨評估別混在綜合報告里看。綜合報告在資源占用表里會把LUT邏輯、LUTRAM、FF、DSP分開顯示除法的占用只會出現(xiàn)在LUT和FF兩列里如果你看到DSP列有數(shù)值那大概率是復(fù)用了其他運算模塊。6.3 多路除法復(fù)用思路當系統(tǒng)中有多路數(shù)據(jù)同時需要除法運算時不要草率地例化多個IP核那樣LUT會成倍上漲。更好的思路是采用時分復(fù)用配置一個位寬最大、延遲固定的除法器多路數(shù)據(jù)通過一個仲裁器輪流送入每路數(shù)據(jù)在輸出端等待對應(yīng)的Latency周期后取結(jié)果。這個方法能顯著降低邏輯資源占用代價是各路數(shù)據(jù)的運算吞吐率下降。我這里實際處理過一個8路并行的比例控制任務(wù)原本計劃例化8個除法器評估下來LUT要占4000多根本放不下。后來改成1個除法器加一個8路輪詢仲裁LUT消耗降到了600左右運算周期從原來的幾十拍變成兩三百拍但對于控制周期十幾微秒的系統(tǒng)來說完全夠用。這又是一個“資源與速度”之間的經(jīng)典取舍。7. 工程實現(xiàn)中的幾個隱藏技巧與個人經(jīng)驗7.1 復(fù)位策略與跨時鐘域處理Divider Generator IP的時鐘只有一個aclk因此只要保證所有輸入數(shù)據(jù)的時鐘域和aclk一致就不需要做額外的跨時鐘域處理。但也有例外如果你的上游模塊工作在另一個時鐘域輸入數(shù)據(jù)在進入除法器之前必須經(jīng)過異步FIFO或兩級寄存器同步。直接跨時鐘域送數(shù)據(jù)會導(dǎo)致采樣不確定最終除法結(jié)果出現(xiàn)偶發(fā)性錯誤排查起來極度痛苦。復(fù)位策略上我建議用到復(fù)位引腳而不是懸空。雖然IP核內(nèi)部結(jié)構(gòu)對復(fù)位要求不算苛刻但在仿真階段有復(fù)位信號控制會讓行為更清晰尤其是要測試“復(fù)位后首筆運算”這類場景時。復(fù)位釋放之后建議等上兩三個時鐘周期再送第一筆有效數(shù)據(jù)給內(nèi)部狀態(tài)機一個穩(wěn)定的啟動時間。7.2 與Vivado工程集成的細節(jié)把IP核加入工程后Vivado會自動生成對應(yīng)的.xci文件和所有相關(guān)仿真模型。完成后compile order會自動更新不需要手動添加任何文件。如果你的工程使用Tcl腳本自動化構(gòu)建也可以用命令方式生成IP核并設(shè)置參數(shù)這樣做的好處是方便版本管理和批量配置。下面是我常用的Tcl配置片段供參考create_ip -name div_gen -vendor xilinx.com -library ip -version 5.1 -module_name div_fixed set_property -dict [list \ CONFIG.algorithm_type {Radix2} \ CONFIG.dividend_width {16} \ CONFIG.divisor_width {8} \ CONFIG.remainder_type {Fractional} \ CONFIG.fractional_width {8} \ CONFIG.operand_sign {Unsigned} \ ] [get_ips div_fixed]使用Tcl腳本配置IP核的好處是以后換項目或換版本時只需要修改參數(shù)重新跑一次腳本就能得到結(jié)構(gòu)完全一致的除法器不會因為界面操作漏點某個選項導(dǎo)致前后行為不一致。7.3 從仿真到上板驗證的最后一步仿真正確不代表上板就一定能跑通至少要注意兩個問題第一IP核默認生成的是仿真模型綜合實現(xiàn)時會自動切換為網(wǎng)表實現(xiàn)兩者行為幾乎一致但個別情況下網(wǎng)表對復(fù)位釋放時序更敏感上板前最好在硬件環(huán)境里做一個最小驗證第二上板后如果發(fā)現(xiàn)輸出數(shù)據(jù)偶發(fā)異常優(yōu)先用ILA抓內(nèi)部信號重點看tvalid和tdata的對齊關(guān)系很多時候問題是出在數(shù)據(jù)源側(cè)而不是除法器本身。我個人的習(xí)慣是在每個用到除法器的模塊里都預(yù)留一組ILA探針平時不使能占資源極少但一旦出現(xiàn)問題時能直接抓波形分析。對比幾次之后你就會發(fā)現(xiàn)大量看似“除法器有問題”的現(xiàn)象最后其實都是輸入數(shù)據(jù)時序沒對齊、位寬沒匹配、或者復(fù)位不一致導(dǎo)致的真正除法器本身的故障非常少見。另外還想分享一個經(jīng)驗?zāi)玫絀P核后建議花半天時間把官網(wǎng)提供的Product Guide翻一遍重點看Timing Diagrams那一章。很多我上面列出的“坑”比如商和余數(shù)的位段順序、tvalid的精確拉高時刻文檔里其實都有明確說明只是平時沒人愿意靜下心看。你把這章消化掉之后用任何Xilinx的算術(shù)類IP都會順手很多。