PCIe学习笔记系列:
- PCIe基础知识及Xilinx相关IP核介绍 概念了解:简单学习PCIe的数据链路与拓扑结构,另外看看有什么相关的IP核。
- 【PG054】7 Series Integrated Block for PCI Express IP核的学习 基础学习:关于Pcie IP核的数据手册,学习PCIe相关的IP核的配置参数及其对应的含义。
- Xilinx PCIe IP核示例工程代码分析与仿真 基础学习:关于PCIe IP核的仿真,学习PCIe的配置流程以及应用过程。
- Xilinx XDMA 例程代码分析与仿真结果 应用学习:关于Xilinx PCIe DMA IP核的仿真,学习 PCIe DMA 的配置过程以及具体的数据传输流程。
- XDMA linux平台调试过程记录 应用学习:关于XDMA的实际调试过程,可在此基础上定制自己的需求。
- 1 IP核的配置
- 1.1 Basic
- 1.2 PCIe ID
- 1.3 PCIe:BARs
- 1.4 PCIe:MISC
- 1.5 PCIe:DMA
- 2 生成IP核的例程
- 2.1 例程结构
- 2.2 例程的仿真
- 2.2.1 测试用例
- 2.2.2 仿真过程
- 2.2.3 Descriptor Bypass 模式
- 2.4 测试任务
- 3 仿真结果
- 3.1 tx_usrapp:pci_exp_usrapp_tx中的初始化
- 3.2 sample_tests.vh中测试用例的仿真流程
- 4 总结
- 5 附件
- 5.1 Modelsim波形文件
- 5.2 Modelsim所有的输出信息
配置好IP核后右击IP核选择Open Ip Exanple Design...
打开例程
例程的结构:
当然,右边的接口根据自己是否使能会有变化。因为用于仿真,所以我们在BAR中同时勾选了AXI4-Lite master interface和DMA Bypass interface,所以仿真工程同时会有这三个接口。
2.2 例程的仿真生成的例程的TestBench环境中有一个PCI Express Root Port 模型,可以与提供的PIO(Programmed Input/Output ,PIO)设计或与个人设计一起使用。模型的目的是为下游提供源机制的TLP流激励用户的设计,以及在仿真环境中从用户设计接收上游PCI Express TLP流的机制。初始化核心配置空间、创建TLP事务、生成TLP日志以及提供创建和验证测试的接口的所有重要工作都已完成。不需要自己搭建testbench。
Root Port 模型包括:
- 测试编程接口(TPI),激励端点设备PCI Express。
- 演示如何使用测试程序TPI的示例测试。
- 所有 Root Port模型组件的Verilog源代码,允许自定义test bench。
模型结构及与PCIe DMA Subsystem 的连接:
大致的结构是usrapp_tx
和 usrapp_rx
通过dsport
与DUT进行TLP包的收发。DUT中包含PCIe DMA Subsystem。
dsport
则模拟数据链路层以及物理层在数据通信过程中的相关处理。
usrapp_com
模块中则是一些共享的逻辑,比如TLP处理以及log文件的输出。
因为先安装的Vivado 2017.1(XDMA IP核版本为3.1),后安装的2018.3(对应版本为4.1),导致我的2018.3和Modelsim联合仿真环境有问题,所以使用2017.1版本进行仿真。
2.2.1 测试用例DMA Subsystem for PCIe 可以配置成AXI4 内存映射(AXI-MM)或者AXI4-Stream(AXI_ST)接口。仿真测试用例通过读取配置寄存器来判断是配置的哪个接口。然后基于AXI的设置,执行对应的仿真。
测试用例名称描述Dma_test0AXI-MM接口的仿真,读取Host的内存进行然后写入道block RAM中(H2C)。然后读取block RAM中的数据然后写入到Host内存(C2H)。这个测试用例最后会比较数据的正确性Dma_stream0AXI4-Stream接口仿真。从Host内存读取数据并发送到AXI4-Stream用户接口(H2C),然后数据被环回到主机内存(C2H)。H2C和C2H的仿真模型有64字节的传输大小限制。
2.2.2 仿真过程官方手册描述的仿真步骤:
AXI4内存映射接口
首先,测试用例启动H2C引擎,H2C引擎从Host内存中读取数据然后写到用户端的Block RAM。然后,测试用例启动C2H引擎,从Block RAM 中读取数据然后写到Host 内存中。仿真的步骤:
- 测试用例为H2C引擎设置一个描述符。
- H2C描述符给出数据长度64字节、源地址(Host)和目的地址(Card)。
- 将数据(增量数据)写入源地址空间。
- 测试用例也为C2H引擎设置了一个描述符。
- C2H描述符提供数据长度64字节、源地址(Card)和目的地址(Host)。
- PIO写入H2C描述符启动寄存器。
- PIO写入H2C控制寄存器以启动H2C传输。
- DMA传输将数据从host源地址传输到block RAM目的地址。
- 然后测试用例启动C2H传输。
- PIO写入C2H描述符启动寄存器。
- PIO写入C2H控制寄存器以启动C2H传输。
- DMA传输将数据从block RAM源地址传输到host目的地址。
- 测试用例比较数据的正确性。
- 测试用例检查 H2C和C2H描述符完成计数(值为1)。
AXI-Stream 接口
对于 AXI-Stream,示例设计是一个环回设计。首先,测试用例启动C2H引擎。C2H引擎等待H2C引擎传输的数据。然后,测试用例启动H2C引擎。H2C引擎从host读取数据并发送到Card, Card被循环回给C2H引擎。然后,C2H引擎获取数据,并将数据写回host内存。下面是仿真步骤:
- 测试用例为H2C引擎设置一个描述符。
- H2C描述符提供数据长度64字节、源地址(Host)和目的地址(Card)。
- 将数据(增量数据)写入源地址空间。
- 测试用例也为C2H引擎设置了一个描述符。
- C2H描述符给出数据长度64字节、源地址(Card)和目的地址(host)。
- PIO写入C2H描述符启动寄存器。
- PIO写入C2H控制寄存器,首先启动C2H传输。
- C2H引擎启动,等待来自H2C端口的数据。
- PIO写入H2C描述符启动寄存器。
- PIO写入H2C控制寄存器以启动C2H传输。
- H2C引擎从主机源地址获取数据到Card目的地址。
- 数据环回到C2H引擎。
- C2H引擎从Card获取数据,然后发送和写入host内存目标地址。
- 测试用例检查H2C和C2H描述符完成计数(值为1)。
当H2C和C2H的Channel 0 选择为描述符旁路选项时,可以进行描述符旁路模式的Vivado仿真。示例设计产生了一个描述符准备泵在描述符旁路模式接口。 当传输开始时,一个H2C和一个C2H描述符在描述符旁路接口中传输,然后按照上面章节的解释执行DMA传输。描述符仅为64字节的传输设置。
2.4 测试任务 名称描述TSK_INIT_DATA_H2C这个任务为H2C引擎生成一个描述符,并在主机内存中初始化源数据。TSK_INIT_DATA_C2H这个任务为C2H引擎生成一个描述符。TSK_XDMA_REG_READ这个任务读取DMA子系统的PCIe寄存器。TSK_XDMA_REG_WRITE这个任务写DMA子系统的PCIe寄存器。COMPARE_DATA_H2C这个任务比较host内存中的源数据和写入到block RAM的目标数据。这个任务在AXI4 Memory Mapped仿真中使用。COMPARE_DATA_C2H该任务将hose内存中的原始数据与C2H引擎写入host的数据进行比较。这个任务在AXI4 Memory Mapped仿真中使用。TSK_XDMA_FIND_BAR该任务在不同启用的bar之间查找XDMA配置空间。(BAR0 BAR6)TSK_WRITE_CFG_DW对RP的配置空间进行写操作值得一提的是,我们在Xilinx PCIe IP核示例工程代码分析与仿真中已经了解过,TSK_TX_TYPE0/1_CONFIGURATION_WRITE是对EP的配置空间进行写操作。而TSK_WRITE_CFG_DW是对RP的配置空间进行写操作,同理,读操作TSK命名格式也是类似的。
3 仿真结果 3.1 tx_usrapp:pci_exp_usrapp_tx中的初始化与Xilinx PCIe IP核示例工程代码分析与仿真关于7 Series FPGAs Integrated Block for PCI Express IP核的仿真分析过程类似。分析验证一下上述的仿真步骤。
首先查看测试用例的名称,文件层次为board/RP/tx_usrapp:
其中有一个initial块:
initial begin
dmaTestDone = 0;
pfIndex = 0;
pfTestIteration = 0;
expect_status = 0;
expect_finish_check = 0;
testError = 1'b0;
// Tx transaction interface signal initialization.
pcie_tlp_data = 0;
pcie_tlp_rem = 0;
EP_BUS_DEV_FNS = EP_BUS_DEV_FNS_INIT;
// Payload data initialization.
TSK_USR_DATA_SETUP_SEQ;
board.RP.tx_usrapp.TSK_SIMULATION_TIMEOUT(10050);
board.RP.tx_usrapp.TSK_SYSTEM_INITIALIZATION;
board.RP.tx_usrapp.TSK_BAR_INIT;
// Find which BAR is XDMA BAR and assign 'xdma_bar' variable
board.RP.tx_usrapp.TSK_XDMA_FIND_BAR;
if ($value$plusargs("TESTNAME=%s", testname))
$display("Running test {%0s}......", testname);
else begin
|
// decide if AXI-MM or AXI-ST
board.RP.tx_usrapp.TSK_XDMA_REG_READ(16'h00);
if (P_READ_DATA[15] == 1'b1) begin
| testname = "dma_stream0";
$display("*** Running XDMA AXI-Stream test {%0s}......", testname);
end
else begin
testname = "dma_test0";
$display("*** Running XDMA AXI-MM test {%0s}......", testname);
end
end
//Test starts here
if (testname == "dummy_test") begin
$display("[%t] %m: Invalid TESTNAME: %0s", $realtime, testname);
$finish(2);
end
`include "tests.vh"
else begin
$display("[%t] %m: Error: Unrecognized TESTNAME: %0s", $realtime, testname);
$finish(2);
end
end
line15
: TSK_USR_DATA_SETUP_SEQ 负载数据初始化
将DATA_STORE中的数据初始化位0~4095。
line17
: TSK_SIMULATION_TIMEOUT
设置board.RP.rx_usrapp.sim_timeout的值为10050
line18
: TSK_SYSTEM_INITIALIZATION 对RP进行配置,等待系统初始化完成以及建立链路
- 等待复位完成
- 等待链路建立
在同一个源文件找到TSK_SYSTEM_INITIALIZATION 的定义进行分析:
- 首先TSK_WRITE_CFG_DW(32’h01, 32’h00000007, 4’h1),对RP配置空间地址32’h01写32’h00000007,4’h1是字节选通,含义是选通低字节。
首先回顾一下Xilinx type0(桥设备为type1,非桥设备为type0)PCIe设备的配置空间:
注意一下,数据位宽是32位,地址与EP写配置空间命令一样,指的是DW地址。
所以指令的作用是:
最主要的作用是配置为主设备。
- TSK_READ_CFG_DW(12’h068/4)
读取字节地址为0x68的配置空间寄存器,字段为Device Control Register
。
其他字节可以看Xilinx PCIe IP核示例工程代码分析与仿真的“2.2.2 PCIe配置空间”,这里就不重复贴图了。
读出的值cfg_rd_data为32’h0。
- cfg_usrapp.TSK_WRITE_CFG_DW(DEV_CTRL_REG_ADDR/4,( board.RP.cfg_usrapp.cfg_rd_data | (DEV_CAP_MAX_PAYLOAD_SUPPORTED * 32)) , 4’h1)
其中board.RP.cfg_usrapp.cfg_rd_data | (DEV_CAP_MAX_PAYLOAD_SUPPORTED * 32)=32’h0 | 32’40 = 32’h40
作用:在原配置的基础上设置Max_Payload_Size的值。
可见,设置Max_Payload_Size = 512Byte
以上3步的波形:
- TSK_SYSTEM_CONFIGURATION_CHECK
这一步使用TSK_TX_TYPE0_CONFIGURATION_READ(DEFAULT_TAG, 12’h070, 4’hF);读取字节起始地址为12’h070的DW,字段为Link Control
和Link Status
。读取到的数据为32’h10410000。
此DW的bit[19:16]代表链路速度(我们设置的为2.5GT/s)。bit[23:20]代表链路宽度(我们设置为4)。
同理完成供应商、CMPS ID的校验:
line19
: 任务TSK_BAR_INIT,其中包含4个子任务
- TSK_BAR_SCAN:PCI协议规定给基地址寄存器写全1就会返回BAR的范围。
寄存器 BAR_INIT_P_BAR_RANGE 存储BAR范围。
可以看到,完成BAR扫描后,BAR0(BAR_INIT_P_BAR_RANGE[0]),BAR1(BAR_INIT_P_BAR_RANGE[1]),BAR2(BAR_INIT_P_BAR_RANGE[2])的值分别为32’hfff00000、32’hffff0000、32’hfff00000,分别对应AXI-Lite master接口、DMA接口和Bypass接口连接的存储空间大小。这与我们设置的值是一致的。
但是我们可以看到,在XIlinx的应用中,设置的32’hfff00000只是一个编号值,在初始化过程中,会通过任务FNC_CONVERT_RANGE_TO_SIZE_32对这个编号进行转换:
可以看到,编号32’hfff00000对应的实际大小33’h0010_0000、33’h0001_0000、33’h0010_0000(1MB,64KB,1MB)
- TSK_BUILD_PCIE_MAP:查询BAR的映射关系
寄存器BAR_INIT_P_BAR_ENABLED 存储映射关系。
寄存器BAR_INIT_P_BAR 存储BAR对应的基地址。
寄存器BAR_INIT_P_BAR_ENABLED的值映射关系0未使用BAR1io mapped2mem32 mapped3mem64 mapped- 关于映射类型
是根据最后一个字节进行判断的,具体没有找到资料,只是找到一张有点类似的图:
程序在判断映射关系时,也是按照上图的协议进行判断的。
根据范围推断出映射的类型,可以看到,完成BAR扫描后,BAR0(BAR_INIT_P_BAR_ENABLED[0]),BAR1(BAR_INIT_P_BAR_ENABLED[1]),BAR2(BAR_INIT_P_BAR_ENABLED[2])的值都为2’h2,说明AXI-Lite master接口、DMA接口和Bypass接口都是32位内存映射。这与我们设置的值是一致的。
- 关于基地址的计算
根据初始地址以及各BAR尺寸计算,所以推测设置size为编号值的目的之一应该也是为了满足这个算法,可能x86架构都是这个规范,计算的算法为:
ii从0~6遍历:
// We need to calculate where the next BAR should start based on the BAR's range
BAR_INIT_TEMP = BAR_INIT_P_MEM32_START & {1'b1,(BAR_INIT_P_BAR_RANGE[ii] & 32'hffff_fff0)};
if (BAR_INIT_TEMP < BAR_INIT_P_MEM32_START) begin
// Current MEM32_START is NOT correct start for new base
BAR_INIT_P_BAR[ii] = BAR_INIT_TEMP + FNC_CONVERT_RANGE_TO_SIZE_32(ii);
BAR_INIT_P_MEM32_START = BAR_INIT_P_BAR[ii] + FNC_CONVERT_RANGE_TO_SIZE_32(ii);
end
else begin
// Initial BAR case and Current MEM32_START is correct start for new base
BAR_INIT_P_BAR[ii] = BAR_INIT_P_MEM32_START;
BAR_INIT_P_MEM32_START = BAR_INIT_P_MEM32_START + FNC_CONVERT_RANGE_TO_SIZE_32(ii);
end
初始值:
变量初始值BAR_INIT_P_MEM32_START32’h0BAR_INIT_P_BAR_RANGE[0]32’hFFF00000BAR_INIT_P_BAR_RANGE[1]32’hFFFF0000BAR_INIT_P_BAR_RANGE[2]32’hFFF00000BAR_INIT_P_BAR_RANGE[3]32’h0BAR_INIT_P_BAR_RANGE[4]32’h0BAR_INIT_P_BAR_RANGE[5]32’h0BAR_INIT_P_BAR_RANGE[6]32’h0计算过程:
step1step2step3step4iiBAR_INIT_P_MEM32_STARTBAR_INIT_TEMPBAR_INIT_TEMP < BAR_INIT_P_MEM32_STARTBAR_INIT_P_BAR[ii]032’h032’h0FALSE32’h0132’h0010_000032’h0010_0000FALSE32’h0010_0000232’h0011_000032’h0010_0000TRUE32’h0020_00003////4////5////6////得到的基地址为:
BAR0对应的基地址:32’h00000000 BAR1对应的基地址:32’h00100000 BAR2对应的基地址:32’h00200000
- TSK_DISPLAY_PCIE_MAP
将上面两个扫描的结果显示出来:
- TSK_BAR_PROGRAM
将计算出来的基地址写进BAR中。
虽然使用的是RQ总线,但是Xilinx还是把TLP包打包了一下方便分析。
以写BAR0为例,TLP为:
这是一个寄存器配置TLP包,作用为往地址为0x10(BAR0)写32’h0
line22
:TSK_XDMA_FIND_BAR
找到哪个BAR是XDMA BAR并赋值’xdma_bar’变量
具体流程,分别读取映射类型为内存映射的BAR的偏移地址为0x0的数据,我们知道XIlinx对其DMA BAR偏移地址为0x0的寄存器的定义为:
判断的代码为:
寻找的结果:
line24
:关于这个系统函数$value$plusargs
,这次也学习了一下verilog系统函数:
v
a
l
u
e
value
valueplusargs、
t
e
s
t
test
testplusargs。简单说就是在仿真器运行仿真命令时可以在后面添加一些参数,比如这里可以指定测试用例。
line29
: 读取PCIe设备的PCIe to DMA (BAR1)空间,地址为0x00,上面有此寄存器的定义。
根据bit15的值判断是1: AXI4-Stream Interface或0: Memory Mapped AXI4 Interface并选择对应的测试用例是dma_stream0或dma_test0。
line45
: 将测试用例的代码包含进来
sample_tests.vh文件中关于名为dma_test0的测试用例的部分为:
else if(testname =="dma_test0")
begin
//------------- This test performs a 32 bit write to a 32 bit Memory space and performs a read back
|//----------------------------------------------------------------------------------------
|// XDMA H2C Test Starts
|//----------------------------------------------------------------------------------------
$display(" *** XDMA H2C *** \n");
$display(" **** read Address at BAR0 = %h\n", board.RP.tx_usrapp.BAR_INIT_P_BAR[0][31:0]);
$display(" **** read Address at BAR1 = %h\n", board.RP.tx_usrapp.BAR_INIT_P_BAR[1][31:0]);
//-------------- Load DATA in Buffer ----------------------------------------------------
board.RP.tx_usrapp.TSK_INIT_DATA_H2C;
|//-------------- DMA Engine ID Read -----------------------------------------------------
board.RP.tx_usrapp.TSK_XDMA_REG_READ(16'h00);
//-------------- Descriptor start address x0100 -----------------------------------------
| board.RP.tx_usrapp.TSK_XDMA_REG_WRITE(16'h4080, 32'h00000100, 4'hF);
//-------------- Start DMA tranfer ------------------------------------------------------
$display(" **** Start DMA H2C transfer ***\n");
fork
//-------------- Writing XDMA CFG Register to start DMA Transfer for H2C ----------------
board.RP.tx_usrapp.TSK_XDMA_REG_WRITE(16'h0004, 32'hfffe7f, 4'hF); // Enable H2C DMA
//-------------- compare H2C data -------------------------------------------------------
$display("------Compare H2C Data--------\n");
board.RP.tx_usrapp.COMPARE_DATA_H2C({16'h0,board.RP.tx_usrapp.DMA_BYTE_CNT}); //input payload bytes
join
loop_timeout = 0;
desc_count = 0;
//For this Example Design there is only one Descriptor used, so Descriptor Count would be 1
while (desc_count == 0 && loop_timeout
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?