> 技术文档 > 手把手教你学会 Xilinx PCIE/XDMA 读写DDR系列(四) ——AXI lite接口BAR地址空间总线使用教程(一步步教你用XDMA实现FPGA与PC的指令与数据交互)_xdma的user和fpga交互

手把手教你学会 Xilinx PCIE/XDMA 读写DDR系列(四) ——AXI lite接口BAR地址空间总线使用教程(一步步教你用XDMA实现FPGA与PC的指令与数据交互)_xdma的user和fpga交互

因最近客户需求通过PCIE把数据从FPGA传到PC,同时通过BAR地址传输指令信息,借此机会和大家讲解一下如何使用XDMA的BAR地址空间总线

制作不易,记得三连哦,给我动力,持续更新!!!

完整工程文件下载:XDMA中BAR地址空间总线使用工程   (点击蓝色字体获取)


        在前几篇文章中,我们已经介绍了如何通过XDMA实现FPGA和PC之间的透明数据传输。然而,单纯的透明传输在实际工程项目中存在明显局限——缺乏规范化的交互机制使得数据难以被正确处理。这正是XDMA中BAR(Base Address Register)地址空间要解决的核心问题。

        而且本次我将采用最简单的使用办法来给大家进行讲解BAR地址空间在项目中的使用方法,因为我看网上说的都太云里雾里了,新手朋友使用起来肯定非常麻烦。

透明传输的局限性

透明传输虽然简单直接,但在实际应用中会面临以下挑战:

  1. 数据识别困难:传输的数据流没有明确的标识,接收端无法区分数据类型

  2. 控制与数据混合:无法明确分离控制指令和实际数据负载

  3. 状态反馈缺失:FPGA端难以向主机报告其状态或处理结果

  4. 同步问题:缺乏协调机制可能导致数据丢失或重复处理

BAR地址空间的引入

PCIe规范中的BAR空间为FPGA与主机交互提供了标准化的解决方案:

BAR空间的基本概念

BAR是PCIe设备中预定义的一段地址空间,具有以下特性:

  • 由6个独立的区域组成(BAR0-BAR5)

  • 每个区域可配置为内存空间或I/O空间

  • 大小和属性在设备设计时确定

  • 主机通过配置空间访问这些区域

好了废话我也不多说了,理论的这些东西大家网上都可以搜到,看的头晕,直接上实操!!

一、硬件环境介绍

首先还是得给大家讲解一下我的硬件及操作环境

1、FPGA主芯片:K7 325T(全称xc7k325tffg900-2L)

2、PCIE主机:Orin NX (ubuntu系统)               

3、vivado版本:2019.2

二、设计总框图

本次设计基于先前XDMA读写DDR的架构进行功能升级,主要引入了AXI Lite寄存器接口以实现更精细化的控制。系统总体架构如下图所示:

本方案在原有架构基础上进行了以下关键性改进:

  1. 控制平面增强

    • 新增AXI Lite从接口实现寄存器映射

  2. 数据平面优化

    • 保留原有高性能DDR数据通路

    • 维持DMA高速传输特性

    • 增加控制信号联动机制

  3. 接口标准化

    • 严格遵循AXI协议规范

    • 明确区分控制流与数据流

    • 提供统一的访问抽象层

本架构在保持原有数据传输性能的同时,显著提升了系统的可控性和可观测性,为复杂应用场景提供了更完善的硬件支持基础。        

三、代码设计

3.1 FPGA端代码

首先在XDMA的IP上勾选上AXI LITE接口

本系统采用Vivado Block Design(BD)作为主要设计实现方式,这种基于IP集成的方法为AXI总线架构提供了最优化的开发效率。设计实现策略如下:

在此块设计中,axi_writeaxi_readaxi_lite_regfile 均为作者独立封装的IP核,并非来源于官方IP库。这三个IP核是PCIE项目的核心支柱,被我成为PCIE三剑客,在几乎所有PCIE相关项目中均得到广泛应用。经过多个项目的验证,其稳定性和兼容性表现极为出色

相对于之前的XDMA读写DDR文章上的工程,此设计可以从axi_lite_regfile模块引出控制信号,并将一些标志信号存放到regfile。

具体的几个信号如下表所示:

地址/偏移量

名称

方向

功能

0x00~ 0x3c

保留

0x40

wr_done

R

写完成标志信号,代表写数据已经完成,可以进行下一次读取或者写入操作

0x44

rd_done

R

读完成标志信号,代表读数据已经完成,可以进行下一次读取或者写入操作

0x48

wr_start

W

写触发信号,此信号拉高一次,向指定地址写入一帧数据

0x4c

rd_start

W

读触发信号,此信号拉高一次,向指定地址读取一帧数据

0x50

WR_DATA_ADDR

W

写数据地址寄存器,表示FPGA写数据的起始地址

0x54

RD_DATA_ADDR

W

读数据地址寄存器,表示FPGA读数据的起始地址

这样可以实现通过主机控制FPGA数据的写入时机、数据传输至DDR以及写入或读取DDR的特定地址,同时能够监控写入或读取操作的完成状态。

PCIE三剑客代码讲解

因之前文章已经讲解过axi_write和axi_read模块,所以本文章就不在做过多的讲解,这里主要给大家展示axi_lite_regfile这个模块的设计思路和实现。

设计思路

axi_lite_regfile模块是一个基于AXI4-Lite协议的寄存器文件模块,旨在通过XDMA(Xilinx DMA)BAR(Base Address Register)地址空间实现FPGA与主机之间的控制指令和状态监控信息的交互。其核心设计思路如下:

  1. AXI4-Lite协议接口
    • 模块通过标准的AXI4-Lite从接口与主机通信,支持读写操作,允许主机通过特定的寄存器地址访问FPGA内部的控制和状态信息。
    • AXI4-Lite协议以其简单性和低带宽需求,适合用于控制寄存器和状态寄存器的访问。
  2. 寄存器文件设计
    • 模块内部定义了多个32位寄存器(reg_ctrl_0到reg_ctrl_3),用于存储主机的控制指令(如读写启动信号、数据地址等)以及FPGA的状态信息(如读写完成状态)。
    • 寄存器地址空间被映射到特定的BAR地址(如32\'h48、32\'h4C等),主机通过这些地址与FPGA交互。
  3. 主机与FPGA交互
    • 写操作:主机通过AXI4-Lite写通道向寄存器写入控制信息(如触发写操作的wr_start、读操作的rd_start、以及数据地址WR_DATA_ADDR和RD_DATA_ADDR)。
    • 读操作:主机通过AXI4-Lite读通道读取FPGA的状态信息(如wr_done和rd_done)或寄存器内容。
    • 模块支持字节使能(s_axi_wstrb),允许主机对寄存器的部分字节进行写操作。
  4. 状态机设计
    • 模块实现了写状态机读状态机,分别处理AXI4-Lite的写通道和读通道的握手信号(如awready、wready、arready等),确保数据传输的正确性和时序一致性。
  5. 模块化与可扩展性
    • 通过参数化设计(ADDR_BITS、DATA_BITS、DATA_BYTES),模块支持不同地址和数据宽度的配置,增强了通用性和可移植性。
    • 模块接口清晰,易于集成到更大的PCIE系统中,适合作为XDMA BAR地址空间的寄存器控制单元。

核心代码

如下所示:

always @(posedge s_axi_aclk, negedge s_axi_aresetn) begin if(!s_axi_aresetn) begin reg_ctrl_0 <= \'b0; reg_ctrl_1 <= \'b0; reg_ctrl_2 <= \'b0; reg_ctrl_3 <= \'b0; end else if(wr_en) begin case(wr_addr) // wr_start 32\'h48: beginif(wr_be[0]) reg_ctrl_0[7:0] <= wr_dout[7:0];  if(wr_be[1]) reg_ctrl_0[15:8] <= wr_dout[15:8];  if(wr_be[2]) reg_ctrl_0[23:16] <= wr_dout[23:16];  if(wr_be[3]) reg_ctrl_0[31:24] <= wr_dout[31:24]; end // rd_start 32\'h4C: beginif(wr_be[0]) reg_ctrl_1[7:0] <= wr_dout[7:0];  if(wr_be[1]) reg_ctrl_1[15:8] <= wr_dout[15:8];  if(wr_be[2]) reg_ctrl_1[23:16] <= wr_dout[23:16];  if(wr_be[3]) reg_ctrl_1[31:24] <= wr_dout[31:24]; end // WR_DATA_ADDR 32\'h50: beginif(wr_be[0]) reg_ctrl_2[7:0] <= wr_dout[7:0];  if(wr_be[1]) reg_ctrl_2[15:8] <= wr_dout[15:8];  if(wr_be[2]) reg_ctrl_2[23:16] <= wr_dout[23:16];  if(wr_be[3]) reg_ctrl_2[31:24] <= wr_dout[31:24]; end // RD_DATA_ADDR 32\'h54: beginif(wr_be[0]) reg_ctrl_3[7:0] <= wr_dout[7:0];  if(wr_be[1]) reg_ctrl_3[15:8] <= wr_dout[15:8];  if(wr_be[2]) reg_ctrl_3[23:16] <= wr_dout[23:16];  if(wr_be[3]) reg_ctrl_3[31:24] <= wr_dout[31:24]; end default :; endcase end else begin reg_ctrl_0[0] <= 0; reg_ctrl_1[1] <= 0; end end// PL to PS read machinealways @(*)begincase(rd_addr)//从PC读的数据 要在幅值一下32\'h48: rd_din = 0; 32\'h4C: rd_din = 0; 32\'h50: rd_din = reg_ctrl_2; 32\'h54: rd_din = reg_ctrl_3; //发送给PC的数据32\'h40: rd_din[0] = wr_done;32\'h40: rd_din[0] = rd_done;default: rd_din = rd_din;endcaseend

完整的部分从文章开头的蓝色字体获取:

3.2 软件端代码

软件端的代码主要包含两个:读写axi_lite寄存器、读写DDR寄存器

读写DDR寄存器

此次测试暂不涉及自行开发的读写寄存器代码,主要通过 Xilinx 官方驱动 实现寄存器的读写操作,以专注于验证 AXI4-Lite 寄存器 的功能。因此,本次未设计相关代码。在后续讲解中,将为各位提供一份完整的读写寄存器代码示例,以进一步阐述实现细节。

读写axi_lite寄存器

设计思路

该程序是一个命令行工具,旨在通过 自己修改过的Xilinx 官方驱动(libudma.h)与 FPGA 的 AXI4-Lite 寄存器 进行交互,完成寄存器的读写操作。其设计思路如下:

  1. 模块化与可扩展性
    • 使用 自己修改过的Xilinx 提供的驱动接口(如 ReadRegister、WriteRegister、DeviceOpen、DeviceClose)与 FPGA 硬件通信,屏蔽了底层硬件细节,提高了代码的通用性和可移植性。
    • 通过命令行参数支持灵活的读写操作,用户可指定寄存器地址和写入数据,简化测试流程。
  2. 命令行交互
    • 采用标准 C 库的 getopt 解析命令行参数,支持 -r(读操作)和 -w(写操作)选项,符合 Unix/Linux 命令行工具的设计规范。
    • 提供详细的帮助信息(usage 函数),便于用户理解和使用。

主要功能

该程序的主要功能是通过命令行调用 自己修改过的Xilinx 官方驱动,实现对 FPGA 中 AXI4-Lite 寄存器 的读写操作,具体包括:

  1. 命令行参数解析
    • 支持 -r :从指定寄存器地址(十六进制)读取数据。
    • 支持 -w :向指定寄存器地址(十六进制)写入数据(十六进制)。
  2. 寄存器读写
    • 读操作:调用 ReadRegister 从指定地址读取 32 位数据,并将结果以十六进制格式打印。
    • 写操作:调用 WriteRegister 向指定地址写入 32 位数据,并打印操作确认信息。

核心代码

int main(int argc, char *argv[]) { int opt; int read_mode = 0; // 0 表示写操作,1 表示读操作 uint32_t address = 0; // 寄存器地址 uint32_t data = 0; // 写入数据 int device_index = 0; // 设备索引,默认使用 0 // 检查命令行参数数量 if (argc = argc) {  fprintf(stderr, \"错误:写操作需要提供数据值\\n\");  usage(argv[0]); } data = (uint32_t)strtoul(argv[optind], NULL, 16); // 转换为十六进制数据 break; default: usage(argv[0]); } } // 打开设备 DeviceHandle hDev = DeviceOpen(device_index); if (!hDev) { perror(\"设备打开失败\"); return -1; } // 执行读或写操作 if (read_mode) { uint32_t value; if (ReadRegister(hDev, address, &value) != 0) { perror(\"寄存器读取失败\"); DeviceClose(hDev); return -1; } printf(\"从地址 0x%08x 读取数据: 0x%08x\\n\", address, value); } else { if (WriteRegister(hDev, address, data) != 0) { perror(\"寄存器写入失败\"); DeviceClose(hDev); return -1; } printf(\"向地址 0x%08x 写入数据: 0x%08x\\n\", data, address); } // 关闭设备 if (DeviceClose(hDev) != 0) { perror(\"设备关闭失败\"); return -1; } return 0;}

代码部分就讲解完毕了,下板直接下板进行测试!!!

四、下板测试

首先将设计好的FPGA工程编译得到.bit文件,然后将bit文件烧录到FPGA开发板中。

然后通过SSH连接Orin NX,然后运行rescan_pcie.sh脚本刷新PCIE设备,这样就省去了重启操作,此脚本我将后期给大家进行讲解。

然后就可以通过axi_lite_rw程序读写AXI_LITE寄存器,首先通过ILA查看这些寄存器的初始值

同时通过axi_lite_rw程序读取的话,初始值也全部是0

4.1 写操作测试

接下来,我们将执行写操作,具体步骤如下:首先,通过 axi_lite_rw 工具向 FPGA 的写地址寄存器(地址 0x50:WR_DATA_ADDR)写入目标 DDR 地址(例如 0x1000);随后,向写使能寄存器(地址 0x48:wr_start)写入写使能信号,以触发 FPGA 发起写操作。整个写入过程可通过 ILA(Integrated Logic Analyzer) 进行捕获和验证,以确保操作的正确性及分析时序细节。

写入寄存器值:

ILA抓取过程:

并通过xilinx官方的读取程序进行读取:

可以得出,写入的数据正确无误,帧头为:1ACFFC1D,数据部分为:自1开始的累加数

4.2 读操作测试

从上述分析可知,写数据部分功能正常。接下来,我们将通过 AXI4-Lite 寄存器 控制 FPGA 读取 DDR 数据,读取地址与前述保持一致。具体步骤如下:首先,通过 axi_lite_rw 工具向 FPGA 的读地址寄存器(地址 0x54:RD_DATA_ADDR)写入目标 DDR 地址(例如 0x1000);随后,向读使能寄存器(地址 0x4C:rd_start)写入读使能信号,以触发 FPGA 发起读操作。读写过程可通过 ILA(Integrated Logic Analyzer) 进行捕获和验证,以监控操作的正确性及时序。

写入寄存器值:

 ILA抓取过程:

如测试结果所示,AXI4-Lite 寄存器 的读写功能均正常运行,成功实现了通过 AXI4-Lite 接口 控制 FPGA 端的数据操作,并能够有效监控 FPGA 的状态信息。这为项目的应用提供了更高的便捷性和灵活性,使数据传输不再局限于简单的透明模式,而是能够按照预定义规则高效传输有效数据,显著提升了系统的功能性和可控性。

现阶段,所有测试均已顺利完成。总结而言,本次测试验证了 AXI4-Lite 寄存器 的稳定性和可靠性,为后续项目开发奠定了坚实基础,并为实现更复杂的数据传输机制提供了技术保障。

后期继续给大家更新,有关PCIE和其他高速接口联合使用的案例

如果感觉文章对您有用,麻烦三连支持一下,方便下次用到的时候,就可以快速找到我,非常感谢您的支持!!!