PCIe學習(一):PCIe基礎及生成PIO例程分析

原文鏈接:https://blog.csdn.net/cllovexyh/article/details/79828833

版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。

簡介
    學習PCIe有一段時間了,這裏將這段時間的學習做一個總結。由於手裏沒有包含PCIe的板子,因此所做的也就是盡力將XILINX提供的實例工程中的關鍵模塊進行分析,包括 PIO_RX_ENGINE.v,PIO_TX_ENGINE.v,PIO_EP_MEM_ACCESS.v ,希望對和我一樣的初學者有所幫助。
    軟件:VIVADO2017.4
第一步:PCIe基礎知識
    PCIe協議比較複雜,XILINX官方提供了相關文檔(pg054),此外也有不少好的中文學習資料(PCIe入門,PCIe體系結構導讀)。文章結尾會上傳部分學習資料,有需要的同學可以下載。
    PCIe 規範對於設備的設計採用分層的結構,有事務層、數據鏈路層和物理層組成,各層有都分爲發送和接收兩功能塊。總線的物理鏈路如下圖所示。

這裏寫圖片描述

    PCIe總線層次關係如下。

這裏寫圖片描述

    在設備的發送部分,首先根據來自設備核和應用程序的信息,在事務層形成事務層包(TLP),儲存在發送緩衝器裏,等待推向下層;在數據鏈路層,在 TLP 包上再串接一些附加信息,這些信息是對方接收 TLP 包時進行錯誤檢查要用到的;在物理層,對 TLP 包進行編碼,佔用鏈路中的可用通道,從查封發送器發送出去。
    層次結構及數據傳輸(摘自PCIe入門 )。

這裏寫圖片描述

    事務層包(TLP),數據鏈路層包(DLLP),物理層(PLP)產生於各自所在層,最後通過電或光等介質和另一方通訊。其中數據鏈路層包(DLLP),物理層(PLP)的包平常不需要關心,在 IP 核中封裝好了。在 FPGA 上做 PCIe 的功能,變成完成事務層包(TLP)的處理。

這裏寫圖片描述

    TLP 有三部分組成,幀頭、數據、摘要(或者稱ECRC)。TLP 頭標長 3 或者 4 個 DW,格式和內容隨事物類型變化;數據端爲 TLP 幀頭定義下的數據段,如果該 TLP 不攜帶數據,那該段爲空。 Digest段(Optional)是基於頭標、數據字段計算出來的 CRC,成爲 ECRC,一般 Digest 段有 IP 核填充。所以,PCIe 的處理在用戶層表現爲處理 TLP 中頭標和數據段。
    如下圖所示,Byte0~Byte11是頭標(3DW,1DW=4Byte),Byte12~Byte23數據(多個DW),Byte24~Byte27是摘要。

這裏寫圖片描述

    1.Fmt 和 Type 字段確認當前 TLP 使用的總線事務,TLP 頭的大小是由 3 個雙字還是 4 個雙字組成,當前 TLP 是否包含有效負載。Fmt+Type配置信息如下

這裏寫圖片描述

這裏寫圖片描述
    2.TC 字段表示當前 TLP 的傳送類型,PCIe 總線規定了 8 種傳輸類型,分別爲TC0~TC7,缺省值爲 TC0,該字段與 PCIe 的 QoS 相關。
    3.Attr 字段由 3 位組成,其中第 2 位表示該 TLP 是否支持 PCIe 總線的 ID-basedOrdering;第 1 位表示是否支持 Relaxed Ordering;而第 0 位表示該 TLP 在經過 RC 到達存儲器時,是否需要進行 Cache 共享一致性處理。

這裏寫圖片描述

    4.TH 位爲 1 表示當前 TLP 中含有 TPH(TLP Processing Hint)信息,TPH 是 PCIe V2.1 總線規範引入的一個重要功能。TLP 的發送端可以使用 TPH 信息,通知接收端即將訪問數據的特性,以便接收端合理地預讀和管理數據。
    5.TD 位表示 TLP 中的 TLP Digest 是否有效,爲 1 表示有效,爲 0 表示無效。
    6.EP 位表示當前 TLP 中的數據是否有效,爲 1 表示無效,爲 0 表示有效。
    7.AT 與 PCIe 總線的地址轉換相關,可暫時不考慮。
    8.Length字段用來描述 TLP 的有效負載(Data Payload)大小。PCIe 總線規範規定一個 TLP的 Data Payload 的大小。PCIe 總線設置 Length 字段的目的是提高總線的傳送效率。Length 字段以 DW 爲單位,其最小單位爲 1 個 DW。
    9.Request ID字段包含“生成返個 TLP 報文”的 PCIe 設備的總線號(Bus Number)、設備號(Device Number)和功能號(Function Number)

這裏寫圖片描述

    10.Last DW BE和First DW BE,在PCIe 總線以字節爲基本單位迕行數據傳遞,但是 Length 字段以 DW 爲最小單位。爲此TLP 使用 Last DW BE 和 First DW BE 返兩個字段迕行字節使能,使得在一個 TLP 中,有效數據以字節爲單位。

這裏寫圖片描述

這裏寫圖片描述
    一個簡單的TLP

這裏寫圖片描述

    這是一個TLP報文:01a0090f40000001+0403020100000010,由於一次發送64位,因此這個報文的前64位是標頭,後64位是數據和地址。
    按照上圖的數據格式可知
    Request ID:16’b0000_0001_1010_0000
    Tag:8’b0000_1001
    Last DW BE:4’b0000
    First DW BE:4’b1111
    Fmt:2’b10
    Type:5’b0_0000
    Length:10’b00_0000_0001
    Data:32’h04030201
    Address:32’h00000010(低兩位無效)

    完成報文(摘自PCIe入門)
    當發送端發送一個TLP給接收端後,接收端有時需要發送一個完成報文返回給發送端,如發送端發送一個Memory Read(存儲器讀請求)到接收端,那麼接收端就會讀取相應地址下的數據,拼接到完成報文中反饋給發送端,這樣就完成了一次存儲器讀請求。
    不是所有的請求都會反饋一個完成報文,下圖中Non-Posted 表示需要反饋完成報文,而Posted 表示不需要反饋完成報文。

這裏寫圖片描述

    完成報文的格式

這裏寫圖片描述

    其中Byte0~Byte3的字段功能和普通TLP的一致,不一樣的字段分析如下:
    Requester ID 和 Tag 字段完成報文使用 ID 路由方式。完成報文頭的長度爲 3DW,完成報文頭包含 Transaction ID 信息,由 Requester ID 和 Tag 字段組成,返個 ID 必須與源設備發送的數據請求報文的 Transaction ID 對應,完成報文使用 Transaction ID 運行 ID 路由,並將數據發送給源設備。當 PCIe 設備收到存儲器讀、I/O 讀寫或者配置讀寫請求 TLP 時,需要首先保存返些報文的 Transaction ID,然後當該設備準備好完成報文後,將完成報文的 Requester ID 和 Tag 字段賦值爲當前保存的 Transaction ID 字段。
    Completer ID 字段存放發送完成報文的PCIe設備的 ID號,PCIe設備運行數據請求時需要在TLP字段中包含Requester ID字段;而使用完成報文結束數據請求時,需要提供 Completer ID 字段。
    Lower Address 字段,如果當前完成報文爲存儲器讀完成 TLP,該字段存放在存儲器讀完成 TLP 中第一個數據所對應地址的最低位。值得注意的是,在完成報文中,並不存在 First DW BE 和 Last DW BE字段,因此接收端必須使用存儲器讀完成 TLP 的 Low Address 字段,識別一個TLP中包含數據的起始地址。
    Status字段保存當前完成報文的完成狀態,表示當前 TLP 是正確地將數據傳遞給數據請求端;還是在數據傳遞過程中出現錯誤;或者要求數據請求方運行重試。

這裏寫圖片描述

這裏寫圖片描述
    BAR空間
    PCIe板卡訪問PC內存時,板卡向 PC 發送 TLP 包,例如 MWr 包,地址信息就是PC 的物理地址;如果是 MRd 包,那 PC 收到後回覆一個完成包,板卡從完成包分析出數據即得到 MRd 讀取地址的數據。這是PCIe板卡訪問PC。
     PC訪問PCIe板卡,簡單的解釋,PC 啓動是,BIOS 探測所有的外設。對 PCIe (PCI)設備來說,BIOS 檢測到板卡有多少個 BAR 空間,每個空間有多大,然後對應爲這些 BAR 空間分配地址。對 PC 設備來說,它能“看”到 PCIe 板卡的空間只有 BAR 空間,也就只能訪問這些 BAR 空間。也就是說,板卡可以發送合法的 PCIe TLP 包,並得到 PC 端的相應;但是 PC 端訪問板卡被侷限在 BAR 空間。
    以上是PCIe TLP的部分基礎知識,當然不止這些,詳細內容請參考PCIe相關中英文資料。
第二步:工程生成及代碼分析
    1.使用VIVADO新建一個工程,都是很常規的操作,但是注意在選擇芯片或開發板時,FPGA芯片一定帶有PCIe(可選xc7z035ffg676-2),然後點擊Next直到Finish。

這裏寫圖片描述

    2.在IP Catalog中找到PCIe IP核,然後雙擊IP核進行配置。

這裏寫圖片描述

    3.配置x1,5GT/s

這裏寫圖片描述

    4.配置ID

這裏寫圖片描述

    5.配置BARs

這裏寫圖片描述

    6.配置Core Capabilities

這裏寫圖片描述

    7.配置Interrupts

這裏寫圖片描述

    8.點擊Generate

這裏寫圖片描述

    9.生成Example Design

這裏寫圖片描述

    10.選擇文件夾存放Example Design

這裏寫圖片描述

    11.然後就可以在指定的文件夾中找到這個工程了。

這裏寫圖片描述


第三步:關鍵代碼分析
1.整體結構

這裏寫圖片描述

    打開工程後可以看見這個工程的結構如上圖所示,xilinx_pcie_2_1_ep_7x是頂層模塊,其下有pcie_7x_0_support_i 和app 兩個子模塊;其中pcie_7x_0_support_i屬於PCIe IP核,是已經封裝好了的,不需要修改;而app 是屬於應用模塊,我們能修改的就是這個模塊。
    app 中又包含PIO_EP_MEM_ACCESS,PIO_RX_ENGINE 和PIO_TX_ENGINE 三個子模塊,這三個模塊就是我們重點關注的。
    PIO_EP_MEM_ACCESS 用於控制FPGA的存儲器的讀寫;
    PIO_RX_ENGINE 是接收引擎,用於接收、解析TLP;
    PIO_TX_ENGINE 是發送引擎,用於組裝、發送TLP;

PIO 工作流程大概描述:

這裏寫圖片描述

    首先差分接收接口(rxn、rxp)接收到信號,信號經過物理層、數據鏈路層、事務層之後變成TLP進入接收引擎(PIO_RX_ENGINE )進行解析;接着根據標頭判斷這個TLP是讀存儲器還是寫存儲器,若是寫存儲器,就將下一個TLP中的地址和數據解析出來(因爲這裏一次發送64bit,所以第一個TLP中不包含地址和數據,第二個TLP中包含地址和數據),然後通過PIO_EP_MEM_ACCESS 模塊將數據寫入指定的地址中;若是讀存儲器,就將下一個TLP中包含的地址解析出來,再通過PIO_EP_MEM_ACCESS 模塊將數據從指定的地址中讀取出來,然後經過發送引擎(PIO_TX_ENGINE )進行完成包拼接,最後通過事務層、數據鏈路層、物理層封裝之後,通過差分發送接口(txn、txp)將數據發送出去。

2.主要模塊代碼分析
    由上可知,PIO_RX_ENGINE 、PIO_TX_ENGINE 和PIO_EP_MEM_ACCESS 是主要的分析對象。
PIO_RX_ENGINE.v
    接收引擎中包含6種頭標

這裏寫圖片描述

    支持64位和128位數據位寬,這裏分析是64位數據位寬。

這裏寫圖片描述

這裏寫圖片描述
    此外,從代碼中可以得知,接收引擎是通過AXI接口與其他模塊進行信號交換。其中m_axis_rx_tdata 就是傳入接收引擎的TLP。

這裏寫圖片描述

    分析來看,接收引擎的關鍵在於狀態機跳轉。

這裏寫圖片描述

    狀態機分析

這裏寫圖片描述

    首先進入PIO_RX_RST_START 狀態,這是一個復位狀態,在復位狀態中,首先判斷sop 信號是否有效,sop 信號是TLP開始的信號,若這個信號無效,則程序會一直在復位狀態中直到sop 信號有效;若sop 信號有效,根據上文一個簡單的TLP 中分析可知,TLP的24~30位代表着這個TLP的類型,所以接着分析m_axis_rx_tdata[30:24] 判定包的類型。

這裏寫圖片描述

    若分析後是PIO_RX_MEM_RD32_FMT_TYPE 狀態

這裏寫圖片描述

    m_axis_rx_tready 信號標誌着接受設備是否準備好接收TLP,所以當接收到一個TLP後m_axis_rx_tready <= 1’b0 ,表示剛接收到一個TLP,還沒有準備好接收下一個,等到當前TLP處理完之後才能準備好接收下一個TLP。
    m_axis_rx_tdata[9:0] 是代表當前TLP的Length,這個Length表示這個TLP中數據的數量,單位是DW(64bit),m_axis_rx_tdata[9:0]==10’b1這是因爲PIO通常一次傳輸一個DW數據。

這裏寫圖片描述

    所以判斷m_axis_rx_tdata[9:0]==10’b1,確定當前是PIO模式,接着解析出TLP中的其他字段信息,然後跳轉到PIO_RX_MEM_RD32_DW1DW2 狀態。

這裏寫圖片描述

    由上文對完成報文的介紹可知,存儲器如請求是需要反饋完成報文的,而且這個完成報文反饋的是從存儲器指定地址下的數據,req_addr 表示需要讀取的存儲器的地址,req_compl 表示需要發送完成報文,req_compl_wd 表示完成報文中包含數據,然後跳轉到PIO_RX_WAIT_STATE 狀態。

這裏寫圖片描述

    PIO_RX_WAIT_STATE 狀態是一個等待完成狀態,在該狀態下,程序先通過tlp_type 和compl_done 兩個信號爲條件進行判定。
    tlp_type 表示當前TLP的類型,compl_done 表示完成包已經發送完成,由發送引擎反饋給接收引擎。在PIO_RX_MEM_RD32_DW1DW2狀態中有的req_compl 、req_compl_wd 和req_addr 三個信號,其中req_addr 信號輸入到存儲器模塊中讀取該地址下數據,然後又將數據發送到發送引擎中。req_compl和req_compl_wd這兩個信號輸入到發送引擎中用以合成完成報文,當合成完成報文併發送之後,發送引擎產生compl_done 信號,表明完成包已經發送完成,該信號輸入到接收引擎中,這時表明一個存儲器讀請求已經完成,所以在PIO_RX_WAIT_STATE 狀態中將m_axis_rx_tready信號置1,表明接收引擎準備好可以接收下一個TLP了,狀態又跳轉到PIO_RX_RST_STATE狀態。
    以上完成對PIO_RX_MEM_RD32_FMT_TYPE 這個存儲器讀請求的分析,總的來說就是在這個TLP中包含req_compl 、req_compl_wd 和req_addr 三個重要信號,req_compl 和req_compl_wd 告訴發送引擎發送一個帶數據的完成包;req_addr 信號先傳入發送引擎中截取出存儲器地址,在傳入存儲器模塊讀取這個地址下的數據,返回到發送引擎中作爲完成包的數據,這樣就完成了存儲器讀請求TLP的操作。
    接下來將分析PIO_RX_MEM_WR32_FMT_TYPE TLP。這個TLP的目是向存儲器指定的地址中寫入數據,而這些數據包含在這個TLP中。

這裏寫圖片描述

    首先是從TLP中解析出TLP類型和這個TLP中數據的長度(單位是DW);然後同樣將ready信號置0,表明還沒有準備好接收下一個TLP;接着判定這個TLP中的數據長度是否爲1DW(因爲這是PIO模式,所以數據長度爲1DW);最後解析出FIRST DW BE和LAST DW BE,判斷傳輸過來的數據是否有效;狀態跳轉到PIO_RX_MEM_WR32_DW1DW2。

這裏寫圖片描述

    在PIO_RX_MEM_WR32_DW1DW2 狀態中,首先通過m_axis_rx_tdata[63:32]解析出這個TLP中的數據;然後設置wr_en有效,這個信號是使能存儲器寫功能;接着設置m_axis_rx_tready無效,表明還沒準備好接收下一個包;最後解析出需要寫入存儲器的地址wr_addr,狀態跳轉到PIO_RX_WAIT_STATE。

這裏寫圖片描述

    同樣,PIO_RX_WAIT_STATE是一個等待狀態,判定TLP的類型和wr_busy 信號。wr_busy 信號是一個寫繁忙信號,因爲這個TLP是想往存儲器中寫數據,當要寫數據送入存儲器模塊之後,存儲器模塊產生一個寫繁忙有效信號表明此時在寫存儲器,當寫接收之後,寫繁忙信號失效,這時在接收引擎中接收到wr_busy 信號失效,表明存儲器寫請求處理完成,所以設置ready信號有效,表明可以接收下一個TLP了,狀態也跳轉回復位狀態。
    以上完成了在接收引擎(RX_ENGINE)中PIO_RX_MEM_RD32_FMT_TYPE和PIO_RX_MEM_WR32_FMT_TYPE兩種頭標的分析,其餘類型的頭標的分析與此類似。接着分析發送引擎(TX_ENGINE)。

 

PIO_TX_ENGINE.v
    發送引擎中包含兩種頭標

這裏寫圖片描述

    然而這兩種頭標的完成包只有帶不帶數據的區別,包內其他信息幾乎一樣。
    實現發送完成包的關鍵還是狀態機分析。

這裏寫圖片描述

    首先設置compl_done <= 1’b0表示完成包發送未完成,而這個信號就是在接收引擎等待狀態中的compl_done 信號。接着判定req_compl_q 信號(這個信號就是從接收引擎傳來的發送完成包信號),如果需要發送完成包,則設置compl_busy_i 信號有效,表示正在發送完成包,接下來就是狀態機跳轉發送完成包了。

這裏寫圖片描述

    首先進入復位狀態,在compl_busy_i有效的情況下進行初始化,然後判定從機是否準備好接收信號,接着跳轉到PIO_TX_CPLD_QW1_FIRST 狀態。

這裏寫圖片描述

    這個狀態中傳輸第一幀(64bit)數據,這一幀數據是頭標的前2DW數據,其中通過req_compl_wd_q 信號判定這個完成包是否帶有數據,同時因爲這一幀信號都是有效的信號,所以s_axis_tx_tkeep 爲FF。接着狀態機跳轉。

這裏寫圖片描述

    這個狀態比較簡單,只設置了valid信號,說明主機準備好發送第二幀。接着跳轉到PIO_TX_CPLD_QW1 狀態。

這裏寫圖片描述

    在這個狀態中,首先通過s_axis_tx_tready 信號判斷從設備是否準備好接受信號;由於完成包由3DW標頭和1DW的數據構成,總共2幀,所以這一幀是最後一幀,因此此時設置s_axis_tx_tlast爲1表明這是最後一幀;然後設置s_axis_tx_tvalid爲1表明此時主設備準備好發送數據;接着就根據完成包格式拼接發送數據。拼接完成後通過req_compl_wd_q設置s_axis_tx_tkeep信號,由於一次傳輸64bit,所以第二幀剛好1DW標頭+1DW數據,這一幀都有效,所以s_axis_tx_tkeep爲FF,如果這個完成包不帶數據,即req_compl_wd_q無效,則最後一幀數據中只有1DW標頭,那麼s_axis_tx_tkeep就爲0F。
    至此這個帶數據的完成包就發送完成了,所以設置compl_done有效,這個信號返回到接收引擎中,使得接收引擎準備接收下一個TLP,設置compl_busy_i無效,說明又可以發送完成包了,同時狀態機跳轉至PIO_TX_RST_STATE狀態。

PIO_EP_MEM_ACCESS.v
    存儲器的讀寫控制相對了發送和接收引擎來說簡單許多,讀操作就是將指定地址下的數據讀取出來;存儲器寫操作就是不太一樣,是先將該地址的數據讀取出來,然後將數據改變成需要寫入的數據,最後將數據寫入。寫操作的狀態機跳轉如下:

這裏寫圖片描述

    首先進去PIO_MEM_ACCESS_WR_RST狀態

這裏寫圖片描述

    在該狀態下,判斷wr_en信號,這是從接收引擎中引入的信號,表示寫存儲器寫控制狀態機開始,然後進去寫等待狀態PIO_MEM_ACCESS_WR_WAIT。

這裏寫圖片描述

    在這個狀態下,程序先讓write_en無效,表示這時不能對存儲器進行寫入,而準備將這個地址下的數據讀出,進入下一狀態。

這裏寫圖片描述

    在這個狀態下,將w_pre_wr_data的值賦給pre_wr_data,而w_pre_wr_data的值就是從存儲器中讀出的值,可通過如下代碼看出。

這裏寫圖片描述

    由上可知,pre_wr_data 中的值就是存儲器中讀出的值。之後又進入存儲器寫狀態PIO_MEM_ACCESS_WR_WRITE。

這裏寫圖片描述

    由於這個數據是接收引擎中的攜帶的數據,而且是最後一幀的最後一個DW,所以需要通過LAST DW BE信號判定這個DW是否都有效,而wr_be信號就是LAST DW BE信號,由程序可知,如果這個DW中的某一個字節有效,則就將這個字節寫入存儲器,否則這個字節的數據還是原來的數據。這就是存儲器的寫操作原則。
    在分析信號時,建議在VIVADO中將Schematic打開,以輔助程序分析。

這裏寫圖片描述

結束
    以上就是PCIe的部分基礎知識,以及本人對PIO中的重要模塊的簡單分析,理解有誤之處還望斧正;此外上傳了一些基礎文檔以及上述三個重要模塊的代碼(不是工程,就是三個.v文件),代碼有較爲詳細的註釋,有需要的同學可以下載看看(https://download.csdn.net/download/cllovexyh/10330415)。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章