新手学习Vivado XDMA(2) - 仿真分析

本文继续之前的章节(新手学习Vivado XDMA (1) - 配置详细分析-CSDN博客)对仿真的工程以及过程进行分析。


项目框架介绍

Design 部分

根据前文打开的example工程的设计部分如下所示:

 上图中的右侧3个BRAM代表的是用户侧的逻辑设计,与下图工程中的xdma_app中3个 blk_mem_gen对应。上图中的 DMA Subsystem for PCIe 与下图中的 xdma_0 对应,这个也是我们上一篇文章中配置好的IP,可以看出它还包含了 PCIe IP。CQ/CC/RQ/RC定义如下:

This diagram refers to the Requester Request (RQ)/Requester Completion (RC) interfaces, and the Completer Request (CQ)/Completer Completion (CC) interfaces.

 而xilinx_dma_pcie_ep作为design的顶层设计对外的接口也比较简单,

module xilinx_dma_pcie_ep #
  (
   parameter PL_LINK_CAP_MAX_LINK_WIDTH          = 2,            // 1- X1; 2 - X2; 4 - X4; 8 - X8
   parameter PL_SIM_FAST_LINK_TRAINING           = "FALSE",      // Simulation Speedup
   parameter PL_LINK_CAP_MAX_LINK_SPEED          = 2,             // 1- GEN1; 2 - GEN2; 4 - GEN3
   parameter C_DATA_WIDTH                        = 64 ,
   parameter EXT_PIPE_SIM                        = "FALSE",  // This Parameter has effect on selecting Enable External PIPE Interface in GUI.
   parameter C_ROOT_PORT                         = "FALSE",      // PCIe block is in root port mode
   parameter C_DEVICE_NUMBER                     = 0,            // Device number for Root Port configurations only
   parameter AXIS_CCIX_RX_TDATA_WIDTH            = 256, 
   parameter AXIS_CCIX_TX_TDATA_WIDTH            = 256,
   parameter AXIS_CCIX_RX_TUSER_WIDTH            = 46,
   parameter AXIS_CCIX_TX_TUSER_WIDTH            = 46
   )
   (
    output [(PL_LINK_CAP_MAX_LINK_WIDTH - 1) : 0] pci_exp_txp,
    output [(PL_LINK_CAP_MAX_LINK_WIDTH - 1) : 0] pci_exp_txn,
    input  [(PL_LINK_CAP_MAX_LINK_WIDTH - 1) : 0] pci_exp_rxp,
    input  [(PL_LINK_CAP_MAX_LINK_WIDTH - 1) : 0] pci_exp_rxn,

    input 					                      sys_clk_p,
    input 					                      sys_clk_n,
    input 					                      sys_rst_n
 );

作为EP,只有2个lanes,即4对差分数据输入,以及1对参考时钟输入,最后是复位管脚。

Simulation 部分

测试框架如下:

其中RP和EP两个参考时钟虽然是独立的两个模块,但在tb里面的参数配置是一致的,所以可以认为是完全同源的时钟(100MHz)。EP则是我们的测试对象(DUT),其内部设计在design 部分已经介绍过。 Simulation 文件夹结构如下:

笔者分析到这里看到整个simulation文件还是比较多的,并且在board.v即top里面并没有发现测试开始的入口,并且由于对PCIe的通信机制不懂,所以决定先启动仿真,等待仿真log出来以后,根据log反向分析整个工作过程。

仿真分析

仿真的启动

由于是新手学习,我们也不用modelsim, questa 或者VCS之类的来仿真了,直接用Vivado 自带的 xsim 来启动,配置如下:

第7步需要改默认仿真时间到 -all ,否则仿真还没完成就暂定下来了。第8步是把design和tb中的所有信号都能存储波形,这样我们后续想要观察某个信号的时候就可以直接从scope->object中找到对应的信号添加,否则事先不在波形窗口中的信号是不记录波形的,这会导致我们每当要新添加(观察)一些信号的时候,都得从头启动仿真,极其费时费力。以下是Tcl console 输出的log最后的信息,用了21分钟。

launch_simulation: Time (s): cpu = 00:01:47 ; elapsed = 00:21:09 . Memory (MB): peak = 1317.504 ; gain = 35.629

 仿真log分析

首先我们需要找到log中系统复位结束的时间,这个是所有功能即将开启的起始时刻。

[             4995000] : System Reset Is De-asserted...

[           143423604] : Transaction Reset Is De-asserted...

[           143427504] : Writing Cfg Addr [0x00000001]

[           143463504] : Reading Cfg Addr [0x00000032]

[           143491504] : Writing Cfg Addr [0x00000032]

[           176147529] : Transaction Link Is Up...

[           176155529] : TSK_PARSE_FRAME on Transmit

 发现首先是在 143427504 ps的时候往地址 0x00000001 进行了配置写入。我们可以在整个目录下搜索 “ Writing Cfg Addr ” 看下是谁操作了这一步,发现是pci_exp_usrapp_cfg模块中的task: TSK_WRITE_CFG_DW. 然后通过搜索这个task 名字来确定是谁调用了这个task, 以此类推一层一层,最终发现我们应该从pci_exp_usrapp_tx.v的第314行作为仿真的入口进行分析。

board.RP.tx_usrapp.TSK_SIMULATION_TIMEOUT(10050)

这个是设置仿真超时时间,但搜索整个workspace后发现,并不会起作用,而且 10050 远远早于上述的复位结束时间。

board.RP.tx_usrapp.TSK_SYSTEM_INITIALIZATION

顾名思义,系统初始化过程,整个task的工作是等待传输接口的复位和链路的建立。

PG213:

等待事务传输接口复位以及根端口模型与端点 DUT 之间的链路建立完成。此任务必须在端点核初始化之前调用。

    task TSK_SYSTEM_INITIALIZATION;
        begin

        //--------------------------------------------------------------------------
        // Event # 1: Wait for Transaction reset to be de-asserted...
        //--------------------------------------------------------------------------
        wait (reset == 0);
        $display("[%t] : Transaction Reset Is De-asserted...", $realtime);

        //--------------------------------------------------------------------------
        // Event # 2: Wait for Transaction link to be asserted...
        //--------------------------------------------------------------------------
        board.RP.cfg_usrapp.TSK_WRITE_CFG_DW(32'h01, 32'h00000007, 4'h1);
        board.RP.cfg_usrapp.TSK_READ_CFG_DW(DEV_CTRL_REG_ADDR_RP/4);	
        board.RP.cfg_usrapp.TSK_WRITE_CFG_DW(DEV_CTRL_REG_ADDR_RP/4,( board.RP.cfg_usrapp.cfg_rd_data | (DEV_CAP_MAX_PAYLOAD_SUPPORTED * 32)) , 4'h1);	
        if (LINK_CAP_MAX_LINK_SPEED_EP>1) begin
            wait(board.RP.pcie3_uscale_rp_top_i.pcie3_uscale_core_top_inst.cfg_ltssm_state == 6'h0B);
            wait(board.RP.pcie3_uscale_rp_top_i.pcie3_uscale_core_top_inst.cfg_ltssm_state == 6'h10);
        end

        wait (board.RP.pcie3_uscale_rp_top_i.user_lnk_up == 1);
        board.RP.tx_usrapp.TSK_TX_CLK_EAT(100);

        $display("[%t] : Transaction Link Is Up...", $realtime);

        TSK_SYSTEM_CONFIGURATION_CHECK;

        end
    endtask

其中事件1中的复位等待结束打印就和我们的log里面143423604ps时的打印对上了。为什么这里会有这么一个复位等待,因为这个reset并不是系统复位,而是RP用户侧的复位,看下图:

上图中的 user_reset 即 TSK_SYSTEM_INITIALIZATION 等待的复位 reset, 它比系统复位晚了蛮久,其实它一直在等待 PCIe PHY 的链路训练完成,完成之后认为PCIe的链路可以正常工作了,而之前必须确保EP和RP侧的用户一直处于复位状态。这个链路训练的状态结果可以从 PCIe PHY 的配置状态接口中获取,对应的寄存器名称为:cfg_phy_link_status,2bits,含义如下:

  • 00b:未检测到接收器
  • 01b:正在进行链路训练
  • 10b:链路上行,正在进行 DL 初始化
  • 11b:链路上行,DL 初始化已完成

同时释放复位的另一个条件是: cfg_phy_link_down 必须为 0b,PCI Express 链路状态,基于物理层 LTSSM(Link Training and Status State Machine),其含义如下:

  • 1b:链路中断(LinkUp 状态变量为 0b)
  • 0b:链路为上行(LinkUp 状态变量为 1b)

而关于LTSSM的机制由于篇幅问题以及笔者还没有学习这块内容,这里真不能展开了。

其中事件2包含了一系列的子task,没关系,我们逐一分析。

board.RP.cfg_usrapp.TSK_WRITE_CFG_DW(32'h01, 32'h00000007, 4'h1)

查询此task定义后,可以看出是向某个地方的地址1写入了数据7。同时可以发现其对接的接口是来自PCIe PHY的配置管理接口。“Configuration Management”(配置管理)接口用于在配置空间寄存器上执行读取和写入。

对于Double Word 地址来说,向地址1写入7,就是把Command 写1,command的定义如下:

引用其他博主的分析:

Command: 这是PCIE设备的命令寄存器,在初始化时其值为0,此时只能接受配置请求事务,需要对该寄存器配置才能访问该设备的存储器或者I/O请求。如下图是比较形象的展示了每个bit位的作用,其中标*的表示不适用于PCIE,必须置为0。 

也就是说通过此配置,使得后续可以访问 Bus, Memory Space 以及 IO Space。 

board.RP.cfg_usrapp.TSK_READ_CFG_DW(DEV_CTRL_REG_ADDR_RP/4)

这个地址是Double Word(DW)下的0x32,一直查询不到是什么意思,但看申明的上一行为:

localparam  [11:0] DEV_CTRL_REG_ADDR  = 12'h068;
localparam  [11:0] DEV_CTRL_REG_ADDR_RP  = 12'h0C8;

查询到对DEV_CTRL_REG_ADDR有网上的解释,是 device control register, 不过看这个命名似乎就是,而且笔者猜测这里的第2个,即带 '_RP' 的应该是RP的PCIE 配置空间里的device control register。根据波形,发现读出来的数据是 0x00002810,按照下面 PCIe spec 中的定义,

Max_Payload_Size - 该字段设置功能的最大 TLP 有效载荷大小。 作为接收器,函数必须处理与设定值一样大的 TLP。 作为发送器,函数不得生成超过设定值的 TLP。

Defined encodings for this field are:

  • 000b 128 bytes max payload size
  • 001b 256 bytes max payload size
  • 010b 512 bytes max payload size
  • 011b 1024 bytes max payload size
  • 100b 2048 bytes max payload size
  • 101b 4096 bytes max payload size
  • 110b Reserved
  • 111b Reserved

[7:5] = 000,意味着最大paylaod size 是 128 bytes。

board.RP.cfg_usrapp.TSK_WRITE_CFG_DW(DEV_CTRL_REG_ADDR_RP/4,( board.RP.cfg_usrapp.cfg_rd_data | (DEV_CAP_MAX_PAYLOAD_SUPPORTED * 32)) , 4'h1)

读完之后又回写同一地址,但写入的值是 0x00002850 即表示要求RP支持512 bytes的最大payload。 

cfg_ltssm_state

接下去是等待LTSSM状态跳转到0x10以后。

        if (LINK_CAP_MAX_LINK_SPEED_EP>1) begin
            wait(board.RP.pcie3_uscale_rp_top_i.pcie3_uscale_core_top_inst.cfg_ltssm_state == 6'h0B);
            wait(board.RP.pcie3_uscale_rp_top_i.pcie3_uscale_core_top_inst.cfg_ltssm_state == 6'h10);
        end

LTSSM 状态。显示当前 LTSSM 状态:

• 00:Detect.Quiet

• 01:Detect.Active

• 02:Polling.Active

• 03:Polling.Compliance

• 04:Polling.Configuration

• 05:Configuration.Linkwidth.Start

• 06:Configuration.Linkwidth.Accept

• 07:Configuration.Lanenum.Accept

• 08:Configuration.Lanenum.Wait

• 09:Configuration.Complete

• 0A:Configuration.Idle

• 0B:Recovery.RcvrLock

• 0C:Recovery.Speed

• 0D:Recovery.RcvrCfg

• 0E:Recovery.Idle

• 10:L0

• 11-16:保留

• 17:L1.Entry

• 18:L1.Idle

• 19-1A:保留

• 20:禁用

• 21:Loopback_Entry_Master

• 22:Loopback_Active_Master

• 23:Loopback_Exit_Master

• 24:Loopback_Entry_Slave

• 25:Loopback_Active_Slave

• 26:Loopback_Exit_Slave

• 27:Hot_Reset

• 28:Recovery_Equalization_Phase0

• 29:Recovery_Equalization_Phase1

• 2a:Recovery_Equalization_Phase2

• 2b: Recovery_Equalization_Phase3

以下摘自网络的分析:

L0 状态是 PCIe 链路的正常工作状态。该状态下,PCIe 链路可以正常发送和接收 TLP、DLLP 和 Ordered Sets。如果需要切换到高于 2.5 GT/s 的速度传输,则需要进入 Recovery 状态进行链路重训练(Re-Training)。 

以及:

PCIe链路训练相关。正常的PCIe链路训练状态转换流程依次是,Detect->Polling->Configuration->L0. L0是PCIe链路可以正常工作的电源状态。
PCIe链路重新训练相关。这个状态也称为Recovery。Recovery是一个非常重要的链路状态,进入这个状态的因素也很多,比如电源状态的变化,PCIe链路速率的变化等。

结合分析下,我们可以猜想,因为我们当时选择的配置是 5GT/s, 所以需要重新训练,整个LTSSM的状态转移如下:

00 --> 01 --> 00 --> 01 --> 02  --> 04  --> 05 --> 06 --> 08 --> 07 --> 09 --> 0a --> 10 --> 0b --> 0d --> 0c --> 0b --> 0d --> 0e --> 10

0x10就是最后的状态:

 再回头去理解 TB 中的 判断 LINK_CAP_MAX_LINK_SPEED_EP>1 就明了了,即当高于 2.5GT/s的速度传输时,需要重新进入recovery 状态,所以需要等待 0x0B 和 0x10。

wait (board.RP.pcie3_uscale_rp_top_i.user_lnk_up == 1);

笔者的理解这里是因为LTSSM重新训练过了,所以担心这个link up的状态是否正常,但看波形发现其实这个在 LTSSM 从 10 --> 0b 的切换中就一直拉高了。

TSK_SYSTEM_CONFIGURATION_CHECK

这个任务的作用是检查 GUI 界面配置的 IP CORE 参数是否正确,包括链路速率,厂商ID等。首先读取 PCIe core (EP 端)的Link Control Register 寄存器中的Target Link Speed值,判断是否和EP端(我们配置的XDMA)中的参数相同。从打印结果来看,是匹配的。注意在读取的过程中使用了TSK_TX_TYPE0_CONFIGURATION_READ task 来读取,PG213 描述了其作用。

PG213:

将“Type 0 PCI Express Config Read TLP”(类型 0 PCI Express 配置读取TLP)从“Root Port Model”(根端口模型)发送到“Endpoint DUT”(端点
DUT)的 reg_addr(含 tag_ 和first_dw_be_ 输入)。从 Endpoint DUT 返回的 Cpld 使用全局EP_BUS_DEV_FNS 的内容作为完成器ID。

后面页检查了 ID 和 CMPS,和log都是能对应上的,如下:

[                   0] : System Reset Is Asserted...
WARNING: 0ns : none of the conditions were true for unique case from File:/wrk/ci/prod/2020.2/sw/continuous/733/packages/customer/vivado/data/ip/xilinx/xdma_v4_1/hdl/xdma_v4_1_vl_rfs.sv:33974
[             4995000] : System Reset Is De-asserted...
[           143423604] : Transaction Reset Is De-asserted...
[           143427504] : Writing Cfg Addr [0x00000001]
[           143463504] : Reading Cfg Addr [0x00000032]
[           143491504] : Writing Cfg Addr [0x00000032]
[           176147529] : Transaction Link Is Up...
[           176155529] : TSK_PARSE_FRAME on Transmit

 frame_store_tx = 0x4a
[           176927529] : TSK_PARSE_FRAME on Receive

 frame_store_tx = 0x4a
[           178155529] :    Check Max Link Speed = 5.0GT/s - PASSED
[           178155529] :    Check Negotiated Link Width = 2 - PASSED
[           178163529] : TSK_PARSE_FRAME on Transmit

 frame_store_tx = 0x4a
[           178927529] : TSK_PARSE_FRAME on Receive

 frame_store_tx = 0x4a
[           180163529] :    Check Device/Vendor ID - PASSED
[           180171529] : TSK_PARSE_FRAME on Transmit

 frame_store_tx = 0x4a
[           180943529] : TSK_PARSE_FRAME on Receive

 frame_store_tx = 0x4a
[           182171529] :    Check CMPS ID - PASSED
[           182171529] :    SYSTEM CHECK PASSED

至此,board.RP.tx_usrapp.TSK_SYSTEM_INITIALIZATION 完成。笔者在这个过程中发现 PG213 对测试平台做了一些解释,这里引用下。

根端口模型包括以下块:
. dsport(根端口)
. usrapp_tx
. usrapp_rx
. usrapp_com(仅限 Verilog)

usrapp_tx 和 usrapp_rx 块与 dsport 块相连,以通过端点受测设计 (DUT) 发射和接收 TLP。端点 DUT 由 PCIe 端点和 PIO 设计(如图所示)或客户设计组成。


usrapp_tx 块将 TLP 发送至 dsport 块以供通过 PCI Express 链路发送至端点 DUT。而端点 DUT 器件则通过 PCI Express 链路将 TLP 发射至dsport,随后此块将被传递到 usrapp_rx 块。通过 PCI Express 逻辑进行通信时,dsport 与核共同负责数据链路层和物理链路层处理。usrapp_tx 和 usrapp_rx 均使用 usrapp_com 来执行共享功能,例如,TLP 处理和日志文件输出。传输事务顺序或测试程序由 usrapp_tx 块发起,以对端点器件互连结构接口进行仿真。usrapp_rx 块将会接收到来自端点器件的 TLP 响应。usrapp_tx 块与 usrapp_rx 块之间的通信使usrapp_tx 块能够在 usrapp_rx 块接收到来自端点器件的 TLP 时,验证行为是否正确并执行相应的操作。 

board.RP.tx_usrapp.TSK_BAR_INIT

PG213:

使用 PCI Express 互连结构按标准顺序执行以端点器件为目标的基址寄存器初始化任务。根据端点 PCI BAR 范围要求执行扫描、执行必要的存储器和 I/O 空间映射计算,最后对端点进行编程,使其做好准备以供访问。完成后,用户测试程序即可开始执行以器件为目标的存储器和I/O 传输事务。该功能标准输出可显示为存储器和 I/O 表,其中详列端点的初始化方式。此任务还可在根端口模型中初始化可供测试程序使用的全局变量。仅限在TSK_SYSTEM_INITIALIZATION 之后调用此任务。

TSK_BAR_SCAN

这个task详细描述了 新手学习Vivado XDMA (1) - 配置详细分析 一文中EP BAR0的地址空间大小获取过程。这里再贴下:

当系统起来的时候,EP BAR 地址会是未初始化的状态,图中显示的是4KB 用户空间,当我们配置4KB大小的时候,value显示的是0xFFFFF000, 这意味着低12bits 是不可写入的。第2步,Host (譬如BIOS)会向BAR地址写全1并回读,发现高20bits 都为1,但低12bits 都为0,此时 BIOS 就知道该用户空间的地址的最小单位是4KB。并且回读时还会获取到低4bits的信息,因为当前配置参数时Memory,32bits,不预取,所以低4位都是0.

    task TSK_BAR_SCAN;
       begin

        //--------------------------------------------------------------------------
        // Write PCI_MASK to bar's space via PCIe fabric interface to find range
        //--------------------------------------------------------------------------

        P_ADDRESS_MASK          = 32'hffff_ffff;
    DEFAULT_TAG         = 0;
    DEFAULT_TC          = 0;


        $display("[%t] : Inspecting Core Configuration Space...", $realtime);

    // Determine Range for BAR0

    TSK_TX_TYPE0_CONFIGURATION_WRITE(DEFAULT_TAG, 12'h10, P_ADDRESS_MASK, 4'hF);
        DEFAULT_TAG = DEFAULT_TAG + 1;
        TSK_TX_CLK_EAT(100);

    // Read BAR0 Range

    TSK_TX_TYPE0_CONFIGURATION_READ(DEFAULT_TAG, 12'h10, 4'hF);
        DEFAULT_TAG = DEFAULT_TAG + 1;
        TSK_WAIT_FOR_READ_DATA;
        BAR_INIT_P_BAR_RANGE[0] = P_READ_DATA;

 上面的代码显示了首先对于BAR0空间地址 12'h10 (byte 地址,对应上图红色的BAR0地址)的写入全1和读取。注意,这里的读写task最后都是通过TLP来通信的,这里的tag 是自动累加的,是唯一的,方便在TLP包中过滤出读写操作的命令消息。

整个task对BAR0 ~ BAR5都做了同样的检查以确定地址空间范围,同时还对 Expansion ROM Base Address 做了同样的操作。但为什么我们需要知道这个ROM 的基地址?BIOS阶段想要使用一个PCIe设备,但是原始的BIOS又不包含这个设备的驱动程序,这个时候就从这个PCIe设备Expansion ROM里获取设备驱动程序代码,并加载到内存中执行。详见更加详细的解释PCIe Expansion Roms-CSDN博客

TSK_BUILD_PCIE_MAP & TSK_DISPLAY_PCIE_MAP

该task的作用是根据 TSK_BAR_SCAN 中获得的各个BAR的地址空间范围建立相关的 memory / IO map。计算结果如下:

[           199083504] PCI EXPRESS BAR MEMORY/IO MAPPING PROCESS BEGUN...
	BAR 0: VALUE = 00000000 RANGE = fff00000 TYPE =  MEM32 MAPPED
	BAR 1: VALUE = 00100000 RANGE = ffff0000 TYPE =  MEM32 MAPPED
	BAR 2: VALUE = 00200000 RANGE = fff00000 TYPE =  MEM32 MAPPED
	BAR 3: VALUE = 00000000 RANGE = 00000000 TYPE =      DISABLED
	BAR 4: VALUE = 00000000 RANGE = 00000000 TYPE =      DISABLED
	BAR 5: VALUE = 00000000 RANGE = 00000000 TYPE =      DISABLED
	EROM : VALUE = 00000000 RANGE = 00000000 TYPE =      DISABLED

BAR0 的其实地址为 0x00000000, 范围是 0xFFF00000, 即 2^20 = 1MB, 这个和 新手学习Vivado XDMA (1) - 配置详细分析-CSDN博客 中配置的参数 BAR0 (PCIe to AXI Lite Master Interface)中的size 大小是一致的。同理,BAR2也是 1MB,但 BAR1(DMA)我们当时并没有配置,而是默认 64KB 大小,这个结果和 AMD PG195 中的描述不一致,暂时没有理解

Size: The available Size range depends on the 32-bit or 64-bit bar selected. The DMA requires
256 KB of space, which is the fixed default selection
. Other BAR size selections are available,
but must be specified.

BAR3 ~ EROM, 当时都没有配置,所以 DISABLED. 再来看看 BAR 的地址地址计算结果,因为 BAR0 的空间范围是1MB(2^20 ),所以 BAR0 的结束地址为 0x00FFFF0(低4bits是控制位),所以 BAR1 的起始地址为 0x00FFFF0 + 0x0000010 = 0x00100000。但问题来了,为什么 BAR2 的地址是 00200000 呢,笔者的理解应该是 BAR1 的起始地址加上 BAR1 的空间范围就行,而 BAR1 的空间范围为 ffff0000,可用bit最低位是bit16, 即 00010000,所以BAR2 理论上应该是 00110000 才对,这里笔者暂时想不通,暂时没有理解。查看代码中的计算发现其用的 BAR2 计算起始地址时的空间范围用的时 BAR2 自己的,而非 BAR1 的,所以计算结果和想象的不一致,因为 BAR2 自己的空间范围也是 1MB。不过笔者按照自己的想法改了下源代码如下:

                                     $display("\n\n\t ------------------%d----------------", ii);
                                     $display("\tBAR_INIT_P_MEM32_START[%d]=%x", ii,BAR_INIT_P_MEM32_START);
                                       // BAR_INIT_P_MEM32_START[          0]=000000000
                                       // BAR_INIT_P_MEM32_START[          1]=000100000
                                       // BAR_INIT_P_MEM32_START[          2]=000110000
                                     $display("\tBAR_INIT_P_BAR_RANGE[%d]=%x", ii,BAR_INIT_P_BAR_RANGE[ii]);
                                       // BAR_INIT_P_BAR_RANGE[          0]=fff00000
                                       // BAR_INIT_P_BAR_RANGE[          1]=ffff0000
                                       // BAR_INIT_P_BAR_RANGE[          2]=fff00000
                                     $display("\tFNC_CONVERT_RANGE_TO_SIZE_32[%d]=%x", ii,FNC_CONVERT_RANGE_TO_SIZE_32(ii));
                                       // FNC_CONVERT_RANGE_TO_SIZE_32[          0]=00100000
                                       // FNC_CONVERT_RANGE_TO_SIZE_32[          1]=00010000
                                       // FNC_CONVERT_RANGE_TO_SIZE_32[          2]=00100000
                                     // We need to calculate where the next BAR should start based on the BAR's range
                                     if (ii == 0)
                                        BAR_INIT_TEMP = BAR_INIT_P_MEM32_START & {1'b1,(32'h0000_0000 & 32'hffff_fff0)};
                                     else
                                        BAR_INIT_TEMP = BAR_INIT_P_MEM32_START & {1'b1,(BAR_INIT_P_BAR_RANGE[ii-1] & 32'hffff_fff0)};
                                     $display("\tBAR_INIT_TEMP=%x", BAR_INIT_TEMP);
                                      //BAR_INIT_TEMP=000000000
                                      //BAR_INIT_TEMP=000100000
                                      //BAR_INIT_TEMP=000100000  --> 000110000
                                     if (BAR_INIT_TEMP < BAR_INIT_P_MEM32_START) begin
                                         $display("@@@");
                                         // 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
                                         $display("$$$");
                                         // 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
                                     $display("\tBAR_INIT_P_BAR[%d]=%x", ii,BAR_INIT_P_BAR[ii]);
                                      //BAR_INIT_P_BAR[          0]=000000000
                                      //BAR_INIT_P_BAR[          1]=000100000
                                      //BAR_INIT_P_BAR[          2]=000200000  --> 000110000

修改之后,H2C, C2H 的 DMA 搬运还是正确的,没有问题。但后续可以再回来研究这里的疑惑。

TSK_BAR_PROGRAM

接下来配置 PCI core 的配置空间/寄存器。把上面读取和计算好的各个BAR数值写入配置空间对应的各个BAR地址上。从0x10 ~ 0x30, 此外还对0x04地址写入了 32'h00000007。具体含义上面有解释,这里不重复了。最后还对Device Control Register 做了一些配置。

TSK_TX_TYPE0_CONFIGURATION_WRITE(DEFAULT_TAG, 12'h04, 32'h00000007, 4'h1);
TSK_TX_TYPE0_CONFIGURATION_WRITE(DEFAULT_TAG, DEV_CTRL_REG_ADDR, 32'h0000005f, 4'h1);

按照前面的描述, Device Control Register的 [7:5] = 010b 表示 512 bytes max payload size,而之前是128 bytes。

board.RP.tx_usrapp.TSK_XDMA_FIND_BAR

改task的工作是为了找到 XDMA 用的 BAR 是哪一个,具体可以看详细的task代码,但笔者也有疑问,为什么 1FC0 就表示找到了 XDMA 的 BAR?如下代码所示。如果按照代码去理解,从memory里面按照 0 ~5 这6个 BAR 中的地址去读取,如果某个BAR空间的起始地址的数据读出是 1FC0, 那么这个BAR 就是 XDMA 的bar,为什么可以这么理解

      if (board.RP.tx_usrapp.P_READ_DATA[31:16] == 16'h1FC0) begin  //Mask [15:0] which will have revision number.
             xdma_bar = jj;
             xdma_bar_found = 1;
             $display (" XDMA BAR found : BAR %d is XDMA BAR\n", xdma_bar);
             end
      else begin
             $display (" XDMA BAR : BAR %d is NOT XDMA BAR\n", jj);
             end

针对上面的疑问,通过阅读PG195的 Register Space 章节,我们可以知道 Host 访问 XDMA 的寄存器也是会进行地址映射的。如下图示:

访问地址低16位有效,在代码中,访问的是某个BAR的偏移地址为0(16'h0):

board.RP.tx_usrapp.TSK_TX_MEMORY_READ_32(
    board.RP.tx_usrapp.DEFAULT_TAG,
    board.RP.tx_usrapp.DEFAULT_TC,
    11'd1,
    board.RP.tx_usrapp.BAR_INIT_P_BAR[jj][31:0]+16'h0,
    4'h0,
    4'hF
);

 那么:

  • [15:12]=4'h0,意味着读取的子模块是 H2C Channel的
  • [11:8]=4'h0,意味着第0个engine被访问(暂时不清楚我们的设计中会有几个engine,但无论哪个engine 都不会影响读取的下个字段的值)
  • [7:0]=4'h0,意味着读取的在 H2C 里的偏移地址为0的寄存器的值

按照PG195,H2C 0x00 的默认值为: [31:16] = 16'h1fc0, 这和前面的代码设计完全对上了。回头思考这个问题,就是如果能在某个BAR的偏移地址为0的空间上读取到 16’h1FC0 就意味着是H2C 的配置空间,也就意味这个这个 BAR 对应的是 XDMA 的空间,当然 XDMA 的 BAR 就这样被找到了。

 附上log以佐证:

[           208403529] : Data read 00000000 from Address 0x0000
 XDMA BAR : BAR           0 is NOT XDMA BAR

[           208411529] : TSK_PARSE_FRAME on Transmit

 frame_store_tx = 0x4a
[           209327529] : TSK_PARSE_FRAME on Receive

 frame_store_tx = 0x4a
[           210451529] : Data read 1fc00006 from Address 0x0000
 XDMA BAR found : BAR           1 is XDMA BAR

根据配置选择对应的测试用例

根据上文找到对应的 XDMA BAR 后,我们可以知道 XDMA 配置的是什么用户接口,AXI-Stream 还是 AXI-MM?不同的接口用的测试用例并不一致。借用上面读出的 H2C 偏移地址为0x00的数据的第15bit,因为我们配置的是 AXI-MM 接口,所以 [15] = 1'b0。因此,测试用例为:"dma_test0",该测试用例先向1个32bits的地址写入1个32bits的数据,并回读比较。

  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

首先,进行了描述符的初始化,关于描述符的解释可以参考PG195的 Table 5,简单点说就是:描述符可以认为是一种指令,指令里面包含了数据搬运的源地址,目的地址,搬运多少等信息。

  1. board.RP.tx_usrapp.TSK_INIT_DATA_H2C,该task 按照描述符格式要求初始化了描述符内容,共计32个字节。同时初始化了momery中的128个字节的数据,用以后续的搬运到 EP 端。起始地址分别是 0x100 和 0x400,都在同一个数组(memory)里面。并且0x100开始存储的描述符中也写明了指向的源地址是 0x400。这里请记住,后续搬运就是用到这里的信息。
  2. board.RP.tx_usrapp.TSK_XDMA_REG_READ(16'h00),再次读取 DMA Engine ID 信息,笔者理解,这里是重复的。
  3. board.RP.tx_usrapp.TSK_XDMA_REG_WRITE(16'h4080, 32'h00000100, 4'hF)

第3步比较关键,0x4080表示 H2C SGDMA 的描述符地址(初始化时地址为0x100),在初始化描述符格式的时候,描述符的数据结构存储在一个命名为 DATA_STORE 的数组里面,这个数组属于 RP 端的,笔者理解,后续 Host(RP) 会根据这个 DATA_STORE 里面的描述符开始操作,是一种模拟行为?

关于SGDMA的解释,引用网友的描述,如下:

Scatter-gather DMA方式是与block DMA方式相对应的一种DMA方式。在DMA传输数据的过程中,要求源物理地址和目标物理地址必须是连续的。但是在某些计算机体系中,如IA架构,连续的存储器地址在物理上不一定是连续的,所以DMA传输要分成多次完成。如果在传输完一块物理上连续的数据后引起一次中断,然后再由主机进行下一块物理上连续的数据传输,那么这种方式就为block DMA方式。Scatter-gather DMA方式则不同,它使用一个链表描述物理上不连续的存储空间,然后把链表首地址告诉DMA master。DMA master在传输完一块物理连续的数据后,不用发起中断,而是根据链表来传输下一块物理上连续的数据,直到传输完毕后再发起一次中断。很显然,scatter-gather DMA方式比block DMA方式效率高。

第4步:board.RP.tx_usrapp.TSK_XDMA_REG_WRITE(16'h0004, 32'hfffe7f, 4'hF) 是对 H2C 的控制寄存器进行的配置,具体细节不再描述,可以查看每个控制的意思,这里可以简单理解为打开所有的功能。配置完成之后,就使能了 DMA 数据搬运工作,数据的搬运方向是从 Host memory 发送到 EP 端 的APP。

第5步: 搬移数据的过程中,就可以在 EP 端 XDMA IP 的 AXI-MM-LITE 输出口监测发给用户 app 的数据是否和RP端准备的数据一致。

分析以下log:

***** TSK_TX_COMPLETION_DATA ****** addr =  256., byte_count =  32, len =    8, comp_status = 0

...

***** TSK_TX_COMPLETION_DATA ****** addr = 1024., byte_count = 128, len =   32, comp_status = 0

...

--- H2C data at XDMA = 0000000000000000000000000000000000000000000000000706050403020100 ---

...

--- Data Stored in TB for H2C Transfer = 0000000000000000000000000000000000000000000000000706050403020100 ---

len 是按照 128 bits 计算的数据长度,是byte_count/4。addr 分别是 描述符的地址和真正搬运数据的地址。RP 发送的数据和 EP 收到的数据也是匹配的。至此,H2C 的测试完成,分析完成。

而由 C2H 过程和H2C过类似,这里不再记录学习笔记。

  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值