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狀態。
參考文獻
- PCIe學習(一):PCIe基礎及生成PIO例程分析——judyzhong
- 《LogiCORE™ IP Endpoint for PCI Express® v3.7》(UG185)