手把手教你学会 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地址空间在项目中的使用方法,因为我看网上说的都太云里雾里了,新手朋友使用起来肯定非常麻烦。
透明传输的局限性
透明传输虽然简单直接,但在实际应用中会面临以下挑战:
-
数据识别困难:传输的数据流没有明确的标识,接收端无法区分数据类型
-
控制与数据混合:无法明确分离控制指令和实际数据负载
-
状态反馈缺失:FPGA端难以向主机报告其状态或处理结果
-
同步问题:缺乏协调机制可能导致数据丢失或重复处理
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寄存器接口以实现更精细化的控制。系统总体架构如下图所示:
本方案在原有架构基础上进行了以下关键性改进:
-
控制平面增强
-
新增AXI Lite从接口实现寄存器映射
-
-
数据平面优化
-
保留原有高性能DDR数据通路
-
维持DMA高速传输特性
-
增加控制信号联动机制
-
-
接口标准化
-
严格遵循AXI协议规范
-
明确区分控制流与数据流
-
提供统一的访问抽象层
-
本架构在保持原有数据传输性能的同时,显著提升了系统的可控性和可观测性,为复杂应用场景提供了更完善的硬件支持基础。
三、代码设计
3.1 FPGA端代码
首先在XDMA的IP上勾选上AXI LITE接口
本系统采用Vivado Block Design(BD)作为主要设计实现方式,这种基于IP集成的方法为AXI总线架构提供了最优化的开发效率。设计实现策略如下:
在此块设计中,axi_write、axi_read 和 axi_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与主机之间的控制指令和状态监控信息的交互。其核心设计思路如下:
- AXI4-Lite协议接口:
- 模块通过标准的AXI4-Lite从接口与主机通信,支持读写操作,允许主机通过特定的寄存器地址访问FPGA内部的控制和状态信息。
- AXI4-Lite协议以其简单性和低带宽需求,适合用于控制寄存器和状态寄存器的访问。
- 寄存器文件设计:
- 模块内部定义了多个32位寄存器(reg_ctrl_0到reg_ctrl_3),用于存储主机的控制指令(如读写启动信号、数据地址等)以及FPGA的状态信息(如读写完成状态)。
- 寄存器地址空间被映射到特定的BAR地址(如32\'h48、32\'h4C等),主机通过这些地址与FPGA交互。
- 主机与FPGA交互:
- 写操作:主机通过AXI4-Lite写通道向寄存器写入控制信息(如触发写操作的wr_start、读操作的rd_start、以及数据地址WR_DATA_ADDR和RD_DATA_ADDR)。
- 读操作:主机通过AXI4-Lite读通道读取FPGA的状态信息(如wr_done和rd_done)或寄存器内容。
- 模块支持字节使能(s_axi_wstrb),允许主机对寄存器的部分字节进行写操作。
- 状态机设计:
- 模块实现了写状态机和读状态机,分别处理AXI4-Lite的写通道和读通道的握手信号(如awready、wready、arready等),确保数据传输的正确性和时序一致性。
- 模块化与可扩展性:
- 通过参数化设计(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 寄存器 进行交互,完成寄存器的读写操作。其设计思路如下:
- 模块化与可扩展性:
- 使用 自己修改过的Xilinx 提供的驱动接口(如 ReadRegister、WriteRegister、DeviceOpen、DeviceClose)与 FPGA 硬件通信,屏蔽了底层硬件细节,提高了代码的通用性和可移植性。
- 通过命令行参数支持灵活的读写操作,用户可指定寄存器地址和写入数据,简化测试流程。
- 命令行交互:
- 采用标准 C 库的 getopt 解析命令行参数,支持 -r(读操作)和 -w(写操作)选项,符合 Unix/Linux 命令行工具的设计规范。
- 提供详细的帮助信息(usage 函数),便于用户理解和使用。
主要功能
该程序的主要功能是通过命令行调用 自己修改过的Xilinx 官方驱动,实现对 FPGA 中 AXI4-Lite 寄存器 的读写操作,具体包括:
- 命令行参数解析:
- 支持 -r :从指定寄存器地址(十六进制)读取数据。
- 支持 -w :向指定寄存器地址(十六进制)写入数据(十六进制)。
- 寄存器读写:
- 读操作:调用 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和其他高速接口联合使用的案例。
如果感觉文章对您有用,麻烦三连支持一下,方便下次用到的时候,就可以快速找到我,非常感谢您的支持!!!