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