Corundum是一個基于FPGA的開源NIC原型平臺,用于高達100Gbps及更高的網(wǎng)絡(luò)接口開發(fā)。
Corundum平臺包括一些用于實現(xiàn)實時,高線速操作的核心功能,包括:高性能數(shù)據(jù)路徑,10G/25G/100G以太網(wǎng)MAC,PCIe gen3,自定義PCIe DMA引擎以及本機高精確的IEEE 1588 PTP時間戳。
背景
我們基于Corundum這個靈活且強大的平臺開發(fā)了一款網(wǎng)絡(luò)加速器。如圖所示,我們的設(shè)計(位于APP黃色部分)需要和原生數(shù)據(jù)流量共用一套DMA IF和主機HOST進行通信。紫色數(shù)據(jù)路徑為用于DMA操作的自定義分段內(nèi)存接口,連接著Corundum的高性能DMA引擎和分段存儲器。
在我們的設(shè)計中,需要頻繁的進行DMA讀寫操作。DMA操作大致有兩種類型,一種是小數(shù)據(jù)包的DMA,長度在16B-64B之間,內(nèi)容為硬件和主機通信的描述符,存放于Desc fetch和Cpl write模塊內(nèi)的描述符分段存儲器;一種是大數(shù)據(jù)包的DMA,長度大于1kB,內(nèi)容為傳輸數(shù)據(jù),存放于APP下的數(shù)據(jù)分段存儲器。
我們的設(shè)計通過了大量仿真,確認開發(fā)設(shè)計功能無誤,隨后開始上板測試。
問題
我們的PCIe鏈路速率gen3 * 8,PCIe理論帶寬為64G。在上板時出現(xiàn)了很奇怪的現(xiàn)象。
每次測試發(fā)送小數(shù)量級的請求時(<10000次,每次代表一次小數(shù)據(jù)包的DMA和一次大數(shù)據(jù)包的DMA),估算PCIe帶寬在25G-35G左右。這種測試情況下不會發(fā)生錯誤,測試順利通過。
當測試請求數(shù)量進一步增大時(>10萬次),預(yù)估PCIe帶寬在35G~40G。此時測試就會發(fā)生問題,整個系統(tǒng)癱瘓。
定位
進一步定位問題,主機和FPGA還可以通過MMIO進行寄存器訪問,但是DMA已經(jīng)完全卡死。
在DMA IF下設(shè)置計數(shù)器用于統(tǒng)計DMA Read請求的個數(shù)和響應(yīng):
if( s_axis_read_desc_valid && s_axis_read_desc_ready ) begin
dma_read_req_cnt <= dma_read_req_cnt + 1'b1;
end
if( m_axis_read_desc_status_valid ) begin
dma_read_status_cnt <= dma_read_status_cnt + 1'b1;
end
上板debug信號,每次測試癱瘓后請求的個數(shù)總是大于響應(yīng)的個數(shù)。
在Corundum中,DMA引擎是這樣工作的:
DMA引擎接收來自用戶的DMA Read請求,然后將DMA Read請求轉(zhuǎn)換為PCIe Non-Posted 類型的mem read請求TLP包,然后等待mem read的Cpld返回報文,解析Cpld報文,將數(shù)據(jù)通過自定義分段內(nèi)存接口存儲至各級模塊下的分段存儲器中,存儲完成后將回復(fù)DMA Read響應(yīng)至用戶。
結(jié)合對DMA 引擎的理解,可以推測有以下幾個可能導(dǎo)致響應(yīng)個數(shù)不夠
- PCIe IP錯誤導(dǎo)致rd tlp或cpld丟失;
- DMA引擎因返回cpld攜帶錯誤而丟棄cpld;
- DMA引擎因PCIe流控發(fā)生不可預(yù)知的錯誤,隨機丟棄數(shù)據(jù)包;
- 其他原因;
分析原因:
PCIe IP為Xilinx UltraScale+ Integrated Block(PCIE4C) for PCI Express,成熟的商用IP經(jīng)過了大規(guī)模驗證和使用一般不會出現(xiàn)問題,可以暫且排除因素1。
抓取相關(guān)的錯誤信號(rx_cpl_tlp_error
、status_error_cor
、status_error_uncor
),結(jié)果表明PCIe并未發(fā)生可糾正/不可糾正的錯誤類型,表示通信雙方鏈路正常,并沒有因為錯誤而丟棄報文導(dǎo)致響應(yīng)的數(shù)量不夠,可以排除因素2。
首先Corundum是一款100Gbps速率的網(wǎng)卡設(shè)計,經(jīng)過了大量驗證和上板測試;其次測試的PCIe理論帶寬為64Gbps。而此時的測試帶寬35G~40G遠遠不足s設(shè)計帶寬或是鏈路帶寬上限,應(yīng)該不會觸發(fā)流控機制。這里因素三也需要被排除。
求助Alex Forencich
最了解自己設(shè)計的一定是作者本人。我隨后在Zulip上求助了作者,希望他可以幫我想一想導(dǎo)致此問題其他有可能的因素。
Alex很熱心,對每個問題都會做出回復(fù)。
他表示這種問題很難定位,需要精確的定位“案發(fā)現(xiàn)場”才有可能定位問題。
我可以確保每次DMA操作的地址和長度都是合理的,并且由于mem read tlp為Non-Posted類型,因此需要等待一定的PCIe鏈路時延才能得到返回報文。這兩項特質(zhì)決定了很難定位至發(fā)生錯誤的DMA操作,無法恢復(fù)到錯誤發(fā)生的那一時刻。
他告訴了我一種定位的手段:
設(shè)置足夠深的ILA緩沖,用計數(shù)器擴展以檢測掛起:基本上,在每個周期遞增,但如果計數(shù)一致或有響應(yīng),則重置;然后將該計數(shù)器輸入ILA;并觸發(fā)各種計數(shù)器值。注意:觸發(fā)器位置靠近緩沖器末端。
基于此思路,我做出了以下設(shè)置:
設(shè)置ILA深度至8192,并且調(diào)整每次DMA Read的長度為4K,確保能夠在ILA深度內(nèi)抓到完整的DMA操作。
dma_read_cycle_cnt
作為需要觸發(fā)的計數(shù)/時器,每收到一個DMA Read請求開始隨時鐘周期自增,收到DMA Read請求響應(yīng)清零;dma_read_cycle_max_cnt
用來記錄正常情況下Non-Posted完成的最大計數(shù)值。將觸發(fā)條件設(shè)置為dma_read_cycle_cnt > dma_read_cycle_max_cnt,這樣就可以觸發(fā)到模塊出錯的時刻。調(diào)整dma_read_cycle_max_cnt觸發(fā)條件,逐步逼近出錯時刻。
if(m_axis_read_desc_status_valid)begin
dma_read_cycle_cnt <= 32'b0;
end else if(s_axis_read_desc_valid && s_axis_read_desc_ready)begin
dma_read_cycle_cnt <= 32'b1;
end else if(dma_read_cycle_cnt >= 1'b1)begin
dma_read_cycle_cnt <= dma_read_cycle_cnt + 1'b1;
end else begin
dma_read_cycle_cnt <= dma_read_cycle_cnt;
end
if(m_axis_read_desc_status_valid && (dma_read_cycle_cnt[23:0] > dma_read_cycle_max_cnt))begin
dma_read_cycle_max_cnt <= dma_read_cycle_cnt;
end else begin
dma_read_cycle_max_cnt <= dma_read_cycle_max_cnt;
end
同時,在ILA內(nèi)抓取一些相關(guān)的重要信號:基本的握手信號和相關(guān)流控信號。
外加統(tǒng)計read tlp的請求數(shù)據(jù)長度累計和返回cpld的數(shù)據(jù)長度累計。
定位“案發(fā)現(xiàn)場”
按照觸發(fā)設(shè)置,終于在ILA中捕獲到“案發(fā)現(xiàn)場”。
某個時刻,握手信號rx_cpl_tlp_ready不再置1,但rx_cpl_tlp_valid還存在。表明DMA引擎出現(xiàn)了問題,無法再接收返回的cpld報文。
也就是從此刻開始,DMA Read請求的個數(shù)在一直增加,但是DMA Read響應(yīng)的個數(shù)卻不再增長。
定位到了出錯時刻,此時便可以對模塊內(nèi)的重點信號展開分析。
原因分析
前文已經(jīng)去除掉了面向HOST的種種因素,因此最有可能出問題的地方只能是面向用戶的用于DMA操作的自定義分段內(nèi)存接口。
這里的分段內(nèi)存接口是Corundum獨特的體系結(jié)構(gòu)的一種,對于PCIe上的高性能DMA,Corundum使用自定義分段存儲器接口。該接口被分成最大512位的段,并且整體寬度是PCIe硬IP內(nèi)核的AXI流接口寬度的兩倍。例如,將PCIe Gen 3 x16與PCIe硬核中的512位AXI流接口一起使用的設(shè)計將使用1024位分段接口,該接口分成2個段,每個段512位。與使用單個AXI接口相比,該接口提供了改進的“阻抗匹配”,從而消除了DMA引擎中的對齊和互連邏輯中的仲裁,從而消除了背壓,從而提高了PCIe鏈路利用率。具體地說,該接口保證DMA接口可以在每個時鐘周期執(zhí)行全寬度,未對齊的讀取或?qū)懭?。此外,使用簡單的雙端口RAM(專用于在單個方向上移動的流量)消除了讀寫路徑之間的爭用。
在自定義分段內(nèi)存接口中,使用ram_wr_cmd_sel
路由和復(fù)用?;趩为毜倪x擇信號而不是通過地址解碼進行路由的。此功能消除了分配地址的需要,并允許使用可參數(shù)化的互連組件,這些組件以最少的配置適當?shù)芈酚刹僮鳌?/p>
output wire [RAM_SEG_COUNT*RAM_SEL_WIDTH-1:0] ram_wr_cmd_sel,
output wire [RAM_SEG_COUNT*RAM_SEG_BE_WIDTH-1:0] ram_wr_cmd_be,
output wire [RAM_SEG_COUNT*RAM_SEG_ADDR_WIDTH-1:0] ram_wr_cmd_addr,
output wire [RAM_SEG_COUNT*RAM_SEG_DATA_WIDTH-1:0] ram_wr_cmd_data,
output wire [RAM_SEG_COUNT-1:0] ram_wr_cmd_valid,
input wire [RAM_SEG_COUNT-1:0] ram_wr_cmd_ready,
input wire [RAM_SEG_COUNT-1:0] ram_wr_done,
DMA Read引擎使用分段內(nèi)存的寫入接口,將獲取的cpld數(shù)據(jù)寫入分段存儲器中。使用ram_wr_cmd_sel
決定寫入哪個分段存儲器,使用ram_wr_cmd_addr
決定寫入分段存儲器的地址,使用ram_wr_done
判斷寫入操作完成。
同樣為分段內(nèi)存接口設(shè)置計數(shù)器,用于統(tǒng)計分段內(nèi)存接口的寫入和寫入完成操作。
if(ram_wr_cmd_valid[0] && ram_wr_cmd_ready[0])begin
dpram_wr_cmd_cnt0 <= dpram_wr_cmd_cnt0 + 1'b1;
end
if(ram_wr_cmd_valid[1] && ram_wr_cmd_ready[1])begin
dpram_wr_cmd_cnt1 <= dpram_wr_cmd_cnt1 + 1'b1;
end
if(ram_wr_done[0])begin
dpram_wr_done_cnt0 <= dpram_wr_done_cnt0 + 1'b1;
end
if(ram_wr_done[1])begin
dpram_wr_done_cnt1 <= dpram_wr_done_cnt1 + 1'b1;
end
問題就出現(xiàn)在這里:
在“案發(fā)時刻”之后,分段內(nèi)存接口處不再有寫入完成信號ram_wr_done
??梢宰C明問題就出在這里,不再有內(nèi)存寫入完成信號,導(dǎo)致DMA IF遲遲無法確認數(shù)據(jù)操作完成,進而導(dǎo)致無法接收新的Cpld報文數(shù)據(jù)(防止數(shù)據(jù)覆蓋)。
進一步分析“案發(fā)時刻”之前分段內(nèi)存接口的所有波形,終于在某個時刻找到了異樣。在此時刻,分段存儲器的地址不再是線性增加,發(fā)生了跳變,執(zhí)行完0地址的寫入之后再恢復(fù)正常。
與DMA Read操作關(guān)聯(lián)起來,并且充分考慮到Non-Posted操作的PCIe鏈路時延,從“案發(fā)時刻”之前的13次DMA Read刪選出出錯的那一次DMA Read操作。
首先進行了一個4kB的大DMA Read操作;
隨后便是一個64B的小DMA Read操作:
所以按理來說,分段內(nèi)存應(yīng)該先執(zhí)行完4kB的數(shù)據(jù)寫入操作之后,再進行64B的數(shù)據(jù)寫入操作。
但抓取到的波形卻顯示數(shù)據(jù)寫入操作地址發(fā)生了跳變,0x5fc -> 0x000 -> 0x5fd。
而這正是觸發(fā)測試錯誤的條件 :兩次mem rd tlp的cpld碰巧發(fā)生了交織,或者說發(fā)生了亂序。小數(shù)據(jù)包的cpld超越了部分大數(shù)據(jù)包的cpld,先一步通過PCIe鏈路傳送至硬件。這種完成報文之間的亂序恰巧是PCIe協(xié)議所允許的,用于保證生產(chǎn)者/消費者模型的正確運轉(zhuǎn),防止死鎖的發(fā)生。
如果完成報文與之前的完成報文的Transaction ID不同,該報文可以超越之前的完成報文;如果相同,則不能超越。這里恰巧是兩個連續(xù)的不同DMA Read操作,Transaction ID的tag必不相同,所以亂序是會發(fā)生的。
在仿真中恢復(fù)案發(fā)現(xiàn)場
在仿真中,將“案發(fā)時刻”前后的波形文件csv導(dǎo)入仿真環(huán)境,或者直接使用force-release強制激勵即可恢復(fù)案發(fā)現(xiàn)場。
通過在仿真中檢查,很快便定位到是最近一級的dma_mux模塊出現(xiàn)了問題,它會在這種情況下丟棄1-2個ram_wr_done
。這有可能是每個分段存儲器的寫入路徑時延不同,導(dǎo)致done信號同時到達mux模塊或者是mux模塊因為被占用而無法處理某一個分段存儲器的寫入完成,導(dǎo)致done信號丟失。
修改BUG
隨后詢問了Alex我的想法是否正確:
他表示編寫mux時使用fifo和計數(shù)器來捕獲所有的done脈沖信號,并按正確的順序轉(zhuǎn)發(fā),但似乎存在一個bug。
隨后也證明確實是dma_ram_demux_wr
這個模塊存在問題:
Alex隨后進行了BUG修改。對輸入dma_ram_demux_wr
的完成的done信號加入雙向確認機制,確保不會遺漏任何done信號。
將修改完的代碼進行了仿真測試,隨后也通過了FPGA上板測試。
總結(jié)
錯誤的觸發(fā)條件: 兩次mem rd tlp的cpld發(fā)生亂序。小數(shù)據(jù)包的cpld超越了部分大數(shù)據(jù)包的cpld,先一步通過PCIe鏈路傳送至硬件。
錯誤原因: dma_ram_demux_wr
因為ram_wr_done
輸入擁塞而丟失了分段內(nèi)存接口的寫入完成信號ram_wr_done
。
進一步分析: 為什么Corundum跑到100G帶寬下都不會出現(xiàn)此問題?
Corundum下DMA的自定義分段內(nèi)存接口拓撲如圖所示:
在Corundum的架構(gòu)下,DMA IF 到所有分段存儲器的寫入路徑延遲是一致的,因此即使發(fā)生了cpld亂序,dma_ram_demux_wr
也不會因為ram_wr_done
輸入擁塞而丟失done個數(shù)。
在我們的設(shè)計中,在APP下仍會進行若干級的DMA MUX將DMA引擎進一步復(fù)用。
這樣就會導(dǎo)致存在兩個分段存儲器的寫入路徑延遲不一致,進一步導(dǎo)致在dma_ram_demux_wr
寫入完成接口處發(fā)生爭用導(dǎo)致ram_wr_done
丟失。
補充: 修改完此BUG之后,每級dma_if_mux
會引入一拍額外的時延(用于雙向確認),因此會對DMA引擎的性能造成一定影響。為消除此影響,可以將dma_if_pcie_rd
模塊下的參數(shù)STATUS_FIFO_ADDR_WIDTH
和OUTPUT_FIFO_ADDR_WIDTH
由5擴大為6,使用更多資源來抵消額外的時延影響。
評論
查看更多