PIO 例程设计概述
PIO example设计在终端模式的IP核生成时已经包括在IP核中了。该设计属于简单的典型应用,与终端模式的PCIe IP核的事务层接口(AXI4-Stream接口)进行通讯,用户可以通过使用已经成熟的设计方便构建系统,进而验证通讯链路和功能的正确性。该设计功能如下:
- 使用Xilinx FPGA的块RAM(block RAM)建立的4个事务专用的2KB的目标区域。(共计8192bytes)。
- 通过完成型TLP包来支持单个DW数据负载的读写事务(32/64bit地址的存储空间和I/O空间)。
- 利用IP核(rx_bar_hit[7:0])m_axis_rx_tuser[9:2]信号来区分TLP的目标BAR(基地址寄存器)。
- 提供对32-bit,64-bit和128-bit AXI4-Stream接口的实现的优化。
下图为PCIe系统架构的组成,包括一个Root Complex,一个PCIe交换机设备和一个终端模式的PCIe。
PIO操作包括:从Root Complex中读取数据流到终端,以及从终端传递数据流到Root Complex。
不管哪种操作,都是首先从主机CPU发起的请求。当CPU请求写PCIe终端板卡的存储寄存器时,Root Complex通常会产生一个写存储的TLP包,其中有MMIO(存储映射输入输出)本地地址和将写入寄存器的数据。该TLP包发送到终端PCIe,终端PCIe接收TLP包,更新寄存器数据。
当CPU请求读PCIe终端板卡的寄存器时,Root Complex通常会产生一个读存储的请求,其中有MMIO的本地地址。终端在接收该TLP包后,读取寄存器数据,并产生一个带数据负载的Cpl响应包,并传递给Root Complex。
2.4.2 PIO 硬件设计的配置
PIO设计通过在一种终端PCIe后的一个8192bytes的FPGA块RAM的目标空间来实现。该32-bit的目标空间支持单DW的I/O读写,64bit或32bit的存储读写TLP来访问。PIO设计产生一个带1个DW的数据负载的响应TLP包,来响应读存储和读I/O的请求。对于写I/O的请求,PIO产生一个不带数据负载的响应TLP包。PIO设计产生一个带1-DW数据负载的响应TLP包,来响应读存储的请求,同时将对应地址的FPGA的块RAM空间对应的数据进行更新为收到的TLP的数据负载中的数据。
PIO设计支持4个离散的目标空间。每个空间由一个单独的基地址寄存器(BAR)表示的2KB的块RAM实现。
默认的配置产生的PIO设计包括:
- 一个64-bit可寻址的存储空间BAR
- 一个32-bit可寻址的存储空间BAR。
当IP核接收到了事务包,IP核对地址进行解码,然后决定4个区域的哪个为请求访问的目标。IP核将TLP包传递给PIO设计,并根据请求访问的BAR置位(rx_bar_hit[7:0])m_axis_rx_tuser[9:2]信号。
双端口块RAM,对应的TLP包的BAR以及(rx_bar_hit[7:0])m_axis_rx_tuser[9:2]信号如下图所示。
2.4.3 创建 PIO 设计
本节将介绍使用VIVADO 2019创建一个PIO的REL Project,例程中使用的器件为xc7k325tff900.首先在创建工程之后,使用IP Catalog 生成PCI Express IP 核。 根据自己的需求按2.3节中的配置说明配置好IP核参数,生成IP核。右击IP核“pcie_7x_0”选择“open IP Example Design”,设置好工程保存路径。
PIO工程生成完之后,会自动使用一个新的UI界面打开,打开工程后可以看见这个工程的结构如上图所示,xilinx_pcie_2_1_ep_7x是顶层模块,其下有pcie_7x_0_support_i 和app 两个子模块;其中pcie_7x_0_support_i属于PCIe IP核,是已经封装好了的,不需要修改;而app 是属于应用模块,这个模块我们可以按需修改。PIO设计的用户数据带宽既可以使用64bit, 也可以使用128bit, 用户可以根据需求去掉一些不相关的文件。
该IP核的顶层模块和接口示意图如下图所示。
2.4.4 PIO设计源代码概述
2.4.4.1 PIO 设计框架
下图为PIO设计的各个组成部分,可以被分为4个主要的组成部分:
PIO_EP_MEM_ACCESS 用于控制FPGA的存储器的读写;
PIO_RX_ENGINE 是接收引擎,用于接收、解析TLP;
PIO_TX_ENGINE 是发送引擎,用于组装、发送TLP;
PIO_TO_CTRL 是终端Turn-Off控制单元,用来响应主机CPU的电源关闭消息的ACK;
PCI Express硬核的工程模块组成图如下:
PIO_EP模块连接到终端IP的AXI4-Stream接口和配置(cfg)接口。
下图为64bit的PIO应用的顶层原理图。
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.4.4.2 接收引擎
下图为RX的引擎模块。该模块同IP核的接收接口连接。接收引擎是通过AXI总线接口(图中左侧信号)与用户的其他模块进行信号交换,其中m_axis_rx_tdata 就是进入接收引擎的TLP包。
该RX引擎可以解析访问6种32bit或64bit地址存储或I/O的读写请求(如下在PIO_RX_ENGINE.v 文件中定义),并将TLP包中的信息解析出来传递给存储控制器。
localparam PIO_RX_MEM_RD32_FMT_TYPE = 7'b00_00000; //存储器读请求,3个DW,不带数据
localparam PIO_RX_MEM_WR32_FMT_TYPE = 7'b10_00000;//存储器写请求,3个DW,带数据
localparam PIO_RX_MEM_RD64_FMT_TYPE = 7'b01_00000;//存储器读请求,4个DW,不带数据
localparam PIO_RX_MEM_WR64_FMT_TYPE = 7'b11_00000;//存储器写请求,4个DW,带数据
localparam PIO_RX_IO_RD32_FMT_TYPE = 7'b00_00010;//IO读请求,3个DW,不带数据
localparam PIO_RX_IO_WR32_FMT_TYPE = 7'b10_00010;//存IO写请求,3个DW,带数据
接收引擎模块种定义的6种头标
在应用正在处理当前的TLP时,读数据流将停止从IP核接收新的事务。该过程通过将m_axis_rx_tready信号置非有效位来实现。
对于正在进行的读内存或I/O的事务,模块在接受下一个TLP之前等待compl_done_i输入信号被置有效位,而正在进行的内存或I/O写事务在wr_busy_i信号被置非有效位后被视为完成。
2.4.4.3 发送引擎
下图为PIO_TX引擎模块。该模块连接到IP核的输出接口。该PIO_TX引擎产生读存储和I/O请求的Cmp包,完成包只有带数据和不带数据的区别,包内其他信息几乎时一样的。在PIO_TX_ENGINE.V文件中可以看到这两种包的定义:
localparam PIO_CPLD_FMT_TYPE = 7'b10_01010; //带数据的完成包
localparam PIO_CPL_FMT_TYPE = 7'b00_01010;//不带数据的完成包
PIO_TX不生成输出的读写请求,用户可以添加更改代码来增加该功能。
该PIO_TX引擎产生64bit读存储和I/O请求的Cmp包所需的输入信号如下图。
当Cmp发送完后,TX引擎将compl_done_i输出信号置有效位,来告诉RX引擎,可以将m_axis_rx_tready信号置有效位,继续接收新的事务TLP包了。
2.4.4.4 储存器控制
IO_EP_MEM_ACCESS模块如下图所示。
该模块包括一个终端的存储空间。
该模块将写存储或I/O请求TLP包传入的数据,写入FPGA的片内BRAM;并响应读存储或I/O请求的TLP包,提供数据给响应的TLP包。
EP_MEM模块,基于RX引擎的信息,处理写存储和I/O的请求。当存储控制器处理写存储空间时,将wr_busy_o输出信号置有效位,表示存储控制器为busy状态。
EP_MEM模块的写输入信号如图所示。
EP_MEM读请求响应的处理基于如下的输入信号。
2.4.5 PIO 的读写事务操作时序
下图为PIO设计中一个Back-to-Back的读存储的请求。
接收引擎在第一个TLP包完成接收后,马上将m_axis_rx_tready信号置非有效位。
在compl_done_o被输出引擎置有效位后(表示第一个TLP包成功完成传输),再接收下一个读事务包。
下图为PIO设计的一个Back-to-Back的写存储的操作。只有在wr_busy_o信号被置非有效位后(表示上一个写事务成功写入存储空间),下一个写事务才被存储访问单元接收。
2.4.6仿真概述
2.4.6.1 仿真运行
Example设计提供了一个快速的仿真方法,以观察终端PCIe IP核和Root Port Example设计的行为。
使用Vivado运行仿真可以参考如下的步骤:
1. 右键点击Example工程文件(.xci),选择Open IP Example Design。创建Example工程。
2. 在左手变的流导航窗口的Simulation栏中,右键点击Run Simulation,选择Run Behavioral Simulation。在运行行为级仿真后,用户可以在Tcl Console中实时观察或者通过Log窗口的Simulation选项卡观察编译和elaboration的过程和阶段。
3. 在Tcl Console中,键入run all命令,然后回车。将为Example测试台的每一个测试案例(case)运行完整的整个仿真。
仿真运行完后,可以在Tcl Console中查看仿真结果。
2.4.6.2基于ROOT PORT模型的TEST BENCH 的概述
PCIe Root Port模型提供了一个可与PIO设计或者用户设计配合使用的test程序接口。Root Port模型的产生PCIe TLP数据流的源机制和一个接收PCIe TLP包的目标处理机制,以用来对用户的设计进行仿真。下图为Root Port模型与PIO设计的连接图。
Root Port模型包括如下的几个模块:
1. dsport (Root Port)
2. usrapp_tx
3. usrapp_rx
4. usrapp_com (Verilog only)
其中usrapp_tx和usrapp_rx连接到dsport模块,用来接收从终端模式的IP核的设计测试单元发送来的TLPs包,并发送TLPs包到终端模式的IP核的设计测试单元(DUT)。
终端模式IP核的DUT包含了PCIe的IP核和PIO设计(或用户自定义的设计)。
usrapp_tx模块发送TLPs包到dsport模块,再通过PCIe链路传递给终端的DUT。
反过来,终端的DUT设备通过PCIe链路发送TLPs包到dsport模块,dsport模块然后将其传递给usrapp_rx模块。dsport和IP核负责数据链路层和物理层的处理。usrapp_tx和usrapp_rx都使用usrapp_com模块作为其共享的功能库。
usrapp_tx模块对事务序列或测试程序进行初始化,以激励终端设备的逻辑接口。usrapp接收从终端设备发过来的响应TLPs包。
usrapp_tx和usrapp_rx块之间的通信允许usrapp_tx块在usrapp_rx块从端点设备接收到TLPs时验证正确的行为和相应的操作。
2.4.6.3 测试用例描述
下图为PIO读事务操作的仿真时序图。