FPGA實戰操作(2) -- PCIe總線(例程設計分析)

1.框架總覽

平臺:vivado 2016.4

FPGA:A7

在實際應用中,我們幾乎不可能自己去編寫接口協議,所以在IP核的例程上進行修改來適用於項目是個不錯的選擇。

通過vivado 中有關PCIe的IP核,生成相應的例程,綜合之後可以得到如下圖的工程結構。


如果在自己的項目中直接使用IP核的話,生成的只有pcie_7x_0這個模塊,在應用層面還需要編寫相應的解析和組裝模塊。好在該例程已經幫我們把這部分模塊編寫好了。例程簡單的工作流程圖如下圖所示。

關於PCIe入門的簡單協議介紹,可以參考博文 FPGA實戰操作(2) -- PCIe總線(協議簡述)

2.應用層模塊設計分析(pcie_app_7x)

例程在PCIe核的基礎上,已經爲用戶設計好了應用層模塊。用戶在使用的過程中只需要在應用層上稍加修改(例程是個閉環系統,需要將收發模塊的部分接口對接到自己項目中),就可以將整個例程移植到自己的項目中了。

下面主要分析應用層模塊中的PIO_RX_ENGINE、PIO_TX_ENGINE這兩個核心模塊。

2.1 PIO_RX_ENGINE

PIO_RX_ENGINE接口層面主要實現三個功能:將PCIe IP核傳遞過來的TLP包解析,之後將結果一部分用於控制存儲器讀取數據,一部分解析結果(協議有效字段)發送給PIO_TX_ENGINE,在需要反饋報文時用。

有關數據的解析,主要由下圖狀態機控制完成。程序中支持64位寬和128位寬,通過C_DATA_WIDTH來判斷執行不同部分。

狀態機狀態state首先進入PIO_RX_RST_STATE,在PIO_RX_RST_STATE中完成數據關鍵字段的解析,根據解析的結果來執行中間幾個狀態(紅線),這幾個執行狀態主要根據命令來完成相應的操作,執行完畢後進入PIO_RX_WAIT_STATE。

下面對幾個關鍵狀態裏的內容進行簡單分析。

2.1.1 PIO_RX_RST_STATE

程序首先進入PIO_RX_RST_START 狀態,這是一個復位狀態。在復位狀態中,首先判斷sop 信號是否有效,sop 信號是TLP開始的信號,若這個信號無效,則程序會一直在復位狀態中直到sop 信號有效;若sop 信號有效,則執行嵌套的一段case語句,case的條件是m_axis_rx_tdata[30:24],研究TLP協議包會發現,該字段對應的是Fmt+Type字段,代表TLP確定的事務類型,實際上就是命令操作,讓你幹什麼。

事務類型的case中,只介紹PIO_RX_MEM_RD32_FMT_TYPE,實際即32位尋址的讀事務。在PIO_RX_MEM_RD32_FMT_TYPE狀態中,按照TLP協議包格式,將各字段數據鎖存入相應的寄存器中,可以參見後面的註釋。其它的事務類型的操作都大同小異,就是鎖存所需字段,執行相應的操作。

case (m_axis_rx_tdata[30:24])   // TLP中 Fmt Type的判斷 
 // 00000001_10100000_00001001_00001111_01000000_00000000_00000000_00000001
 // 00000001_10100000_00001010_00001111_00000000_00000000_00000000_00000001
 // m_axis_rx_tdata[30:24] = 1000000 ,執行 PIO_RX_MEM_WR32_FMT_TYPE
                            
    PIO_RX_MEM_RD32_FMT_TYPE : begin                    // 存儲器讀請求,3個DW,不帶數據
        tlp_type     <= #TCQ m_axis_rx_tdata[31:24];    // 鎖存TLP類型,即Fmt+Type
        req_len      <= #TCQ m_axis_rx_tdata[9:0];      // 鎖存TLP數據包長度,單位是DW
        m_axis_rx_tready <= #TCQ 1'b0;                  // 未準備好
        if (m_axis_rx_tdata[9:0] == 10'b1) begin        // 若數據長度爲1DW
            req_tc   <= #TCQ m_axis_rx_tdata[22:20];
            req_td   <= #TCQ m_axis_rx_tdata[15];
            req_ep   <= #TCQ m_axis_rx_tdata[14];
            req_attr <= #TCQ m_axis_rx_tdata[13:12];
            req_len  <= #TCQ m_axis_rx_tdata[9:0];
            req_rid  <= #TCQ m_axis_rx_tdata[63:48];  // Reverse ID
            req_tag  <= #TCQ m_axis_rx_tdata[47:40];  // Tag字段的長度決定發送端能夠暫存多少
            req_be   <= #TCQ m_axis_rx_tdata[39:32];  // TLP使用 last DW BE和first DW BE這兩個字段
            state    <= #TCQ PIO_RX_MEM_RD32_DW1DW2;  // 表示從存儲器中讀取DW1和DW2

         end // if (m_axis_rx_tdata[9:0] == 10'b1)
         else  begin
            state   <= #TCQ PIO_RX_RST_STATE;
         end // if !(m_axis_rx_tdata[9:0] == 10'b1)
     end // PIO_RX_MEM_RD32_FMT_TYPE

注意:在介紹協議的時候,都是基於DW(32位)介紹,但在通過AXI4總線進行數據交換時,採用的是64位寬或者128位寬,所以要注意一下,數據大小端拼接。

2.1.2 PIO_RX_MEM_RD32_DW1DW2

在執行完成PIO_RX_MEM_RD32_FMT_TYPE狀態之後,主狀態機跳轉至PIO_RX_MEM_RD32_DW1DW2狀態,表示要執行讀操作的配置工作。

由PCIe通信機制可知,存儲器讀請求是需要反饋完成報文的,而且這個完成報文反饋包括兩方面:第一,TLP協議有些固定字段,要反饋;第二,從存儲器指定地址下的數據,要反饋。

req_addr 表示需要讀取的存儲器的地址,req_compl 表示需要發送完成報文,req_compl_wd 表示完成報文中包含數據,然後跳轉到PIO_RX_WAIT_STATE 狀態。

PIO_RX_WAIT_STATE 狀態沒什麼好說的。

2.2 PIO_TX_ENGINE

PIO_TX_ENGINE結構與PIO_RX_ENGINE類似,也是由狀態機完成,主要執行組裝封包功能。

着重看一下PIO_TX_CPLD_QW1_FIRST和PIO_TX_CPLD_QW1兩個狀態。

2.2.1 PIO_TX_CPLD_QW1_FIRST

PIO_TX_CPLD_QW1_FIRST : begin   // 完成報文頭標的第一個DW
    if (s_axis_tx_tready) begin
        s_axis_tx_tlast  <= #TCQ 1'b0;
        s_axis_tx_tdata  <= #TCQ {    // Bits
            completer_id,             // 16
            {3'b0},                   // 3  完成狀態,000--成功完成
            {1'b0},                   // 1
            byte_count,               // 12
            {1'b0},                   // 1
            (req_compl_wd_q ?         // 看此包帶不帶數據
            PIO_CPLD_FMT_TYPE :
            PIO_CPL_FMT_TYPE),        // 7
            {1'b0},                   // 1
            req_tc,                   // 3
            {4'b0},                   // 4
            req_td,                   // 1
            req_ep,                   // 1
            req_attr,                 // 2
            {2'b0},                   // 2
            req_len                   // 10
         };
         s_axis_tx_tkeep   <= #TCQ 8'hFF;
         state             <= #TCQ PIO_TX_CPLD_QW1_TEMP;
      end
      else
         state             <= #TCQ PIO_TX_RST_STATE;
  end //PIO_TX_CPLD_QW1_FIRST

這個狀態中傳輸第一幀(64bit)數據,由TLP協議可知,第一幀數據主要反饋的Head信息,即頭標的前2DW數據,其中通過req_compl_wd_q 信號判定這個完成包是否帶有數據,同時因爲這一幀信號都是有效的信號,所以s_axis_tx_tkeep 爲FF。

2.2.2 PIO_TX_CPLD_QW1

PIO_TX_CPLD_QW1 : begin
    if (s_axis_tx_tready) begin
        s_axis_tx_tlast  <= #TCQ 1'b1;
        s_axis_tx_tvalid <= #TCQ 1'b1;
        // Swap DWORDS for AXI
        s_axis_tx_tdata  <= #TCQ {        // Bits
            rd_data,    // 32
            req_rid,    // 16
            req_tag,    //  8
            {1'b0},     //  1
            lower_addr  //  7
        };
        // Here we select if the packet has data or
        // not.  The strobe signal will mask data
        // when it is not needed.  No reason to change
        // the data bus.
        if (req_compl_wd_q)
           s_axis_tx_tkeep <= #TCQ 8'hFF;
        else
           s_axis_tx_tkeep <= #TCQ 8'h0F;

        compl_done        <= #TCQ 1'b1;
        compl_busy_i      <= #TCQ 1'b0;
        state             <= #TCQ PIO_TX_RST_STATE;
     end // if (s_axis_tx_tready)
     else
        state             <= #TCQ PIO_TX_CPLD_QW1;
end // 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狀態。

參考文獻

  1. PCIe學習(一):PCIe基礎及生成PIO例程分析——judyzhong
  2. 《LogiCORE™ IP Endpoint for PCI Express® v3.7》(UG185)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章