基于Xilinx XDMA 的PCIE通信

基于Xilinx XDMA 的PCIE通信

概述:
  想实现基于FPGA的PCIe通信,查阅互联网各种转载…基本都是对PCIe的描述,所以想写一下基于XDMA的PCIe通信的实现(PCIe结构仅做简单的描述(笔记),了解详细结构移至互联网)。实践实践!!!

说明:
参考文档:
PCI Express Base Specification Revision 3.0
PCI Express Base Specification Revision 5.0
pg195-pcie-dma
PCI Express体系结构导读

环境:Vivado2019.2。
源工程:基于XDMA的PCIE VIVADO工程

下一篇:基于XDMA 的PCIE读写DDR


1.PCIe简介

  PCI-Express(peripheral component interconnect express)是一种高速串行计算机扩展总线标准。PCIE属于高速串行点对点双通道高带宽传输,所连接的设备分配独享通道带宽,不共享总线带宽。PCIE设备通过称为互连或链路的逻辑连接进行通信。链路是两个PCI Express端口之间的点对点通信通道,允许它们发送和接收普通PCI请求和中断。PCIE使用共享并行总线架构,其中PCI主机和所有设备共享一组通用的地址,数据和控制线。

在这里插入图片描述
  结构由互连一组组件的点对点链路组成,结构拓扑示例如上图所示。上图显示了称为层次结构的单个结构实例,由一个根联合体(RC)、多个端点(I/O设备)、一个交换机和一个PCI Express to PCI/PCI-X桥组成,所有这些都通过PCI Express链接互连。

1.1 PCIe和PCI之间的区别

PCI PCIe
速度上 PCI的工作频率分为33MHz和66MHz,最大吞吐率 266MB/s PCIe 1.0 x1 的吞吐率达到了250MB/s
传输方式上 PCI 是并行数据传输,一次传输4字节/8字节,半双工 PCIe是串行数据传输,全双工
硬件上 传输PCI信号的是普通电平传输 PCIe信号的是差分电平
链路上 PCI是总线的连接方式 PCIe是点对点的连接方式

1.2 PCIe速度以及不同Gen的编码方式

一代更比一代快。

X1 X2 X4 X8 X16
PCIe 1.0 250MB/s 500MB/s 1GB/s 2GB/s 4GB/s
PCIe 2.0 500MB/s 1GB/s 2GB/s 4GB/s 8GB/s
PCIe 3.0 1 GB/s 2 GB/s 4GB/s 8GB/s 16GB/s

PCIe编码:
   PCIe 1.0和2.0采用了8b/10b编码方式,PCIe 3.0和4.0采用128b/130b编码。

PCIEe版本 编码 时钟 带宽(X1) 每时钟数据(bit) 说明
PCIe 1.0 8b/10b 2.5GHz 250MB/s 1 2500/10=25MB/s
PCIe 2.0 8b/10b 5GHz 500MB/s 1 5000/10=500MB/s
PCIe 3.0 128b/130b 8GHz 1GB/s 1 ( 8000/130 ) x ( 128/8 ) MB/s= 984 MB/s
PCIe 4.0 128b/130b 16GHz 2GB/s 1 ( 16000/130 ) x ( 128/8 ) MB/s= 1969MB/s

1.3 PCIe端口描述-金手指

   金手指分为Side B和Side A(详细引脚对应下面端口描述),方向如下所示:

Side B
在这里插入图片描述

Side A
在这里插入图片描述

PCIe端口:
在这里插入图片描述

1.4 PCIe传输模型

  PCIe总线接口,采用分层实现的,事务层(Transaction Layer),数据链路层(Data Link Layer)和物理层(Physical Layer)。
在这里插入图片描述

事务层的主要职责是创建(发送)或者解析(接收)TLP (Transaction Layer packet),流量控制,QoS,事务排序等。

数据链路层的主要职责是创建(发送)或者解析(接收)DLLP(Data Link Layer packet),Ack/Nak协议(链路层检错和纠错),流控,电源管理等。

物理层的主要职责是处理所有的Packet数据物理传输,发送端数据分发到各个Lane传输(stripe),接收端把各个Lane上的数据汇总起来(De-stripe),每个Lane上加扰(Scramble,目的是让0和1分布均匀,去除信道的电磁干扰EMI)去扰(De-scramble),以及8/10或者128/130编码解码,等等。

  PCIe传输的数据从上到下,都是以packet的形式传输的,每个packet都是有其固定的格式的。传输模型如下所示(数据经过每一层的数据格式):

TX端(生成数据包过程):
在这里插入图片描述
RX端(解包过程):
在这里插入图片描述

整体传输过程简易描述如下:
在这里插入图片描述

数据包传输详细流程如下:
在这里插入图片描述

  在PCIe数据传输的过程中数据都是以Packet的形式传输,就如以下的传输中,每个Endpoint都需要实现这三层,每个Switch的每个Port也是需要实现这三层。
在这里插入图片描述

1.5 Posted TLP和Non-Posted TLP

  假设某个设备要对另一个设备进行读取数据的操作,首先这个设备需要向另一个设备发送一个Request,然后另一个设备通过Completion Packet返回数据或者错误信息。在PCIe Spec中,规定了四种类型的请求(Request):
MemoryIOConfigurationMessages。其中,前三种都是从PCI/PCI-X总线中继承过来的,第四种Messages是PCIe新增加的类型。

Request Type Non-Posted or Posted
Memory Read Non-Posted
Memory Write Posted
Memory Read Lock Non-Posted
IO Read Non-Posted
IO Write Non-Posted
Configuration Read (Type 0 and Type 1) Non-Posted
Configuration Write (Type 0 and Type 1) Non-Posted
Message Posted

Posted TLP:Requester的请求并不需要Completer通过发送包含Completion的包进行应答,当然也就不需要进行等待了。

Non-Posted TLP:Requester发送了一个包含Request的包之后,必须要得到一个包含Completion的包的应答,这次传输才算结束,否则会进行等待。

很显然,Posted类型的操作对总线的利用率(效率)要远高于Non-Posted型。

PCIe的TLP包共有一下几种类型:
在这里插入图片描述
Non-Posted TLP传输示例:

  如下图Endpoint向System Memory发送读请求(Read request)。Endpoint的读请求通过了两个Switch,然后到达其目标Root节点。Root对读请求的包进行解码后,并识别出操作地址,然后锁存数据,并将数据发送至Endpoint,即包含数据的Completion包,ClpD。

在这里插入图片描述
Posted TLP传输示例:

  PCIe中的Memory写操作都是Posted的,因此Requester并不需要来自Completer的Completion。因此没有返回Completion,所以当发生错误时,Requester也不会知道。但是,此时Completer会将错误记录到日志(Log),然后向Root发送包含错误信息的Message。

在这里插入图片描述

1.6 配置和地址空间

Base Address Register:基址地址寄存器,PCIE设备是有自己独立的一套内部空间,不仅仅是配置空间,包括每个设备提供哪些I/O地址,memory地址,而BAR就是用来表征这些地址空间的。
在这里插入图片描述
  对于Switch(Type1)有2个不同的地址空间。对于Endpoint(Type0)来说,最多可以拥有6个不同的地址空间。但是实际应用中基本上不会用到6个,通常1~3个BAR比较常见。如果某个设备的BAR没有被全部使用,则对应的BAR应被硬件全被设置为0,并且告知软件这些BAR是不可以操作的。对于被使用的BAR来说,其部分低比特位是不可以被软件操作的,只有其高比特位才可以被软件操作。而这些不可操作的低比特决定了当前BAR支持的操作类型和可申请的地址空间的大小。一旦BAR的值确定了(Have been programmed),其指定范围内的当前设备中的内部寄存器(或内部存储空间)就可以被访问了。当该设备确认某一个请求(Request)中的地址在自己的BAR的范围内,便会接受这请求。

2.基于XDMA的PCIe通信

2.1设计描述

首先PC安装Xilinx提供的XDMA驱动,通过CMD命令行进行FPGA中BRAM内存的读写操作,以及寄存器的读写。

设计展示:
在这里插入图片描述

设计总貌:
在这里插入图片描述
AXI4:用来数据传输。
AXI4-Lite:用来实现PCIe BAR地址到AXI-Lite寄存器地址的映射。

Block Design引出一下四个接口供用户灵活数据传输:
DATA_WR:PC→FPGA的数据。
DATA_RD: FPGA→PC的数据。
BAR_WR: BAR→AXI_Lite寄存器的数据。
BAR_RD: AXI_Lite寄存器→访问PCIe BAR空间。

功能描述: (先通过功能了解设计,最后有功能展示)
PC通过CMD执行以下命令,将 pc2fpga_file.bin 文件下的4096Byte写入FPGA:

xdma_rw.exe h2c_0 write 0x0000000 -b -f pc2fpga_file.bin -l 4096

PC通过CMD执行以下命令,从FPGA读取4096Byte写入到PC的 fpga2pc_file.bin 文件里:

xdma_rw.exe c2h_0 read 0x0000000 -b -f fpga2pc_file.bin -l 4096

PC通过CMD执行以下命令,写4Byte(0xaa 0xbb 0xcc 0xdd)的数据到FPGA:

xdma_rw.exe h2c_0 write 0x0000000  -l 4 0xaa 0xbb 0xcc 0xdd

PC通过CMD执行以下命令,读取32Byte的数据到PC:

xdma_rw.exe c2h_0 read 0x0000000  -l 32

PC通过CMD执行以下命令,通过PCIe BAR空间将4Byte数据映射到AXI_Lite寄存器:

xdma_rw.exe control write 0x08 -l 4 0xaa 0xbb 0xcc 0xdd

PC通过CMD执行以下命令,PCIe BAR空间访问AXI_Lite寄存器读取1Byte的数据:

xdma_rw.exe user read 0x08 -l 1

2.2 XDMA概述

  XIlinx提供DMASubsystem for PCIExpressIP是一个高性能,可配置的适用于PCIe2.0、PCIe3.0的SG模式的DMA,提供用户可选择的AXI4接口或者AXI-Stream接口。XDMA是SGDMA,并非Block DMA,SG模式下,主机会把要传输的数据组成链表的形式,然后将链表首地址通过BAR传送给XDMA,XDMA会根据链表结构首地址依次完成链表所指定的传输任务。

   该设计主要利用AXI4接口进行数据传输,AXI4-Lite接口进行BAR地空间的访问,通过BAR访问(读/写寄存器)。
在这里插入图片描述
AXI4:用来数据传输。

AXI4-Lite-Master:主机可以使用该接口向用户逻辑产生32位读和写请求(读写用户逻辑寄存器),用来实现PCIe BAR地址到AXI-Lite寄存器地址的映射。

AXI4-Lite-Slave:用户逻辑只是在该接口上控制对DMA控制器内部寄存器的32位读取或写入,不能通过此接口访问PCIE集成模块寄存器。该接口不向主机生成请求(不会映射到BAR)。

2.3 Block Design 设计

先来一个设计全貌:
在这里插入图片描述
Block Design引出一下四个接口供用户灵活数据传输:
BRAM_PORT_WR:PC→FPGA的数据。
BRAM_PORT_RD: FPGA→PC的数据。
BAR_PORT_WR: BAR→AXI_Lite寄存器的数据。
BAR_PORT_RD: AXI_Lite寄存器→访问PCIe BAR空间。

下面说一下几个重要的配置:
XDMA IP设置:(根据自己的板子设置)
Lane Width:X8。
Max Link Speed:选择8.0GT/s 即PCIE3.0。
Reference Clock :100MHZ,参考时钟 100M。
DMA Interface Option:接口选择 AXI4 接口。
AXI Data Width:256bit,即 AXI4 数据总线宽度为256bit。
AXI Clock :250M,即AXI4 接口时钟为 250MHZ。

在这里插入图片描述
PCIe ID配置
在这里插入图片描述
PCIE BAR 配置
首先使能 PCIE to AXI Lite Master Interface ,这样可以在主机一侧通过PCIE 来访问用户逻辑侧寄存器或者其他 AXI4-Lite 总线设备映射空间选择 1M,当然用户也可以根据实际需要来自定义大小。
PCIE to AXI Translation:通常情况下,主机侧PCIE BAR 地址与用户逻辑侧地址是不一样的, 这个设置就是进行BAR 地址到AXI 地址的转换,比如主机一侧 BAR 地址为0,IP 里面转换设置为 0x80000000, 则主机访问 BAR 地址 0 转换到AXI LIte 总线地址就是0x80000000。
在这里插入图片描述
PCIE 中断设置
User Interrupts:用户中断,XDMA 提供16 条中断线给用户逻辑,这里面可以配置使用几条中断线。
Legacy Interrupt:XDMA 支持 Legacy 中断
选择 MSI 中断
在这里插入图片描述
配置DMA 相关内容
Number of DMA Read Channel(H2C)和Number of DMA Write Channel(C2H)通道数,对于PCIE3.0 来说最大 只能选择 4,也就是 XDMA 可以提供最多两个独立的写通道和两个独立的读通道,独立的通道对于实际应用中 有很大的作用,在带宽允许的前提前,一个PCIE 可以实现多种不同的传输功能,并且互不影响。
Number of Request IDs for Read (Write)channel :这个是每个通道设置允许最大的 outstanding 数量。
在这里插入图片描述
添加两个axi_bram_port_ctrl IP,分别对接XDMA的AXI口和AXI-Lite口。
axi_bram_port_ctrl_1 IP配置 :
在这里插入图片描述
axi_bram_port_ctrl_0 IP配置 :
在这里插入图片描述
axi_bram_port_ctrl_0和axi_bram_port_ctrl_1模块均设置两个BRAM端口,并将其引出(Make External)。
通过以下方法设置四个端口。
在这里插入图片描述
BRAM_PORT_WR:只写(PC→FPGA的数据)
BRAM_PORT_RD:只读(FPGA→PC的数据)

BAR_PORT_WR : 只写(BAR→AXI_Lite寄存器的数据)
BAR_PORT_RD : 只读(AXI_Lite寄存器→访问PCIe BAR空间)

引出axi_clk用于对四个端口的读写操作
对axi_clk是不能直接 Make External外引的,我们新建一个端口,将其与axi_clk连接。为什么不用BRAM的clk?因为用了不好使…,接ila发现没信号…,此处应该有大佬出来解释一下。
在这里插入图片描述
大佬你看他没时钟啊…
在这里插入图片描述
所以我是用了axi_clk。

最终的Block Design 全貌:
在这里插入图片描述

端口介绍:
pcie_ref:pcie参考时钟。
pcie_rst_:pcie复位信号。
axi_clk:axi时钟,引出为了BRAM的读写操作。
pcie_mgt:PCIe接口。
BRAM_PORT_WR:只写(PC→FPGA的数据)
BRAM_PORT_RD:只读(FPGA→PC的数据)

BAR_PORT_WR : 只写(BAR→AXI_Lite寄存器的数据)
BAR_PORT_RD : 只读(AXI_Lite寄存器→访问PCIe BAR空间)

Block Design生成的RTL端口(用户新建TOP对其调用即可实现读写操作):

module pcie_system_wrapper
(
    output [13:0]BAR_PORT_RD_addr,		//用于寄存器读
	output BAR_PORT_RD_clk,
	output [31:0]BAR_PORT_RD_din,
	input [31:0]BAR_PORT_RD_dout,
	output BAR_PORT_RD_en,
	output BAR_PORT_RD_rst,
	output [3:0]BAR_PORT_RD_we,
	
	output [13:0]BAR_PORT_WR_addr,		//用于寄存器写
	output BAR_PORT_WR_clk,
	output [31:0]BAR_PORT_WR_din,
	input [31:0]BAR_PORT_WR_dout,
	output BAR_PORT_WR_en,
	output BAR_PORT_WR_rst,
	output [3:0]BAR_PORT_WR_we,
	
	output [15:0]BRAM_PORT_RD_addr,		//用于数据读
	output BRAM_PORT_RD_clk,
	output [31:0]BRAM_PORT_RD_din,
	input [31:0]BRAM_PORT_RD_dout,
	output BRAM_PORT_RD_en,
	output BRAM_PORT_RD_rst,
	output [3:0]BRAM_PORT_RD_we,
	
	output [15:0]BRAM_PORT_WR_addr,		//用于数据写
	output BRAM_PORT_WR_clk,
	output [31:0]BRAM_PORT_WR_din,
	input [31:0]BRAM_PORT_WR_dout,
	output BRAM_PORT_WR_en,
	output BRAM_PORT_WR_rst,
	output [3:0]BRAM_PORT_WR_we,
	
	input [7:0]pcie_mgt_rxn,
	input [7:0]pcie_mgt_rxp,
	output [7:0]pcie_mgt_txn,
	output [7:0]pcie_mgt_txp,
	input [0:0]pcie_ref_clk_n,
	input [0:0]pcie_ref_clk_p,
	
	output axi_clk,
	input pcie_rst_n
),
 

我们建立用户Top文件,调用该Block Design,如下所示:
在这里插入图片描述
无时序违例
在这里插入图片描述

top设计思路:

传输方向 实现方式
数据 PC→FPGA BRAM_PORT_WR 处用ila提取数据进行对比(验证数据到达FPGA无误)
数据 PC←FPGA BRAM0中提前写入数据,通过PCIe将其读入PC
BAR空间 PC→FPGA BAR_PORT_WR 处用ila提取数据进行对比(验证数据到达FPGA无误)
BAR空间 PC←FPGA BRAM1中提前写入数据,通过PCIe将其读入PC

top代码:

module top
(
	input 	[0:0]	pcie_ref_clk_n,
	input 	[0:0]	pcie_ref_clk_p,
	input 			pcie_rst_n,
	
	input  	[7:0]	pcie_mgt_rxn,
	input  	[7:0]	pcie_mgt_rxp,
	output 	[7:0]	pcie_mgt_txn,
	output 	[7:0]	pcie_mgt_txp
	
);


wire 			axi_clk;
wire 	[7:0]	pcie_mgt_rxn;
wire 	[7:0]	pcie_mgt_rxp;
wire 	[7:0]	pcie_mgt_txn;
wire 	[7:0]	pcie_mgt_txp;
wire 	[0:0]	pcie_ref_clk_n;
wire 	[0:0]	pcie_ref_clk_p;
wire 			pcie_rst_n;
	
wire 	[15:0]	BRAM_PORT_RD_addr;
wire 			BRAM_PORT_RD_clk;
wire 	[31:0]	BRAM_PORT_RD_din;
wire 	[31:0]	BRAM_PORT_RD_dout;
wire 			BRAM_PORT_RD_en;
wire 			BRAM_PORT_RD_rst;
wire 	[3:0]	BRAM_PORT_RD_we;

wire 	[15:0]	BRAM_PORT_WR_addr;
wire 			BRAM_PORT_WR_clk;
wire 	[31:0]	BRAM_PORT_WR_din;
wire 	[31:0]	BRAM_PORT_WR_dout;
wire 			BRAM_PORT_WR_en;
wire 			BRAM_PORT_WR_rst;
wire 	[3:0]	BRAM_PORT_WR_we;

wire 	[13:0]	BAR_PORT_RD_addr;
wire 			BAR_PORT_RD_clk;
wire 	[31:0]	BAR_PORT_RD_din;
wire	[31:0]	BAR_PORT_RD_dout;
wire 			BAR_PORT_RD_en;
wire 			BAR_PORT_RD_rst;
wire 	[3:0]	BAR_PORT_RD_we;

wire 	[13:0]	BAR_PORT_WR_addr;
wire 			BAR_PORT_WR_clk;
wire	[31:0]	BAR_PORT_WR_din;
wire	[31:0]	BAR_PORT_WR_dout;
wire 			BAR_PORT_WR_en;
wire 			BAR_PORT_WR_rst;
wire 	[3:0]	BAR_PORT_WR_we;

pcie_system_wrapper pcie_system
(
	.BRAM_PORT_WR_addr(BRAM_PORT_WR_addr),//AXI 只写
    .BRAM_PORT_WR_clk (BRAM_PORT_WR_clk ),
    .BRAM_PORT_WR_din (BRAM_PORT_WR_din ),
    .BRAM_PORT_WR_dout(BRAM_PORT_WR_dout),
    .BRAM_PORT_WR_en  (BRAM_PORT_WR_en  ),
    .BRAM_PORT_WR_rst (BRAM_PORT_WR_rst ),
    .BRAM_PORT_WR_we  (BRAM_PORT_WR_we  ),

	.BRAM_PORT_RD_addr(BRAM_PORT_RD_addr),//AXI 只读
    .BRAM_PORT_RD_clk (BRAM_PORT_RD_clk ),
    .BRAM_PORT_RD_din (BRAM_PORT_RD_din ),
    .BRAM_PORT_RD_dout(BRAM_PORT_RD_dout),
    .BRAM_PORT_RD_en  (BRAM_PORT_RD_en  ),
    .BRAM_PORT_RD_rst (BRAM_PORT_RD_rst ),
    .BRAM_PORT_RD_we  (BRAM_PORT_RD_we  ),


	.BAR_PORT_WR_addr  (BAR_PORT_WR_addr),//AXI_Lite  只写
	.BAR_PORT_WR_clk   (BAR_PORT_WR_clk ),
	.BAR_PORT_WR_din   (BAR_PORT_WR_din ),
	.BAR_PORT_WR_dout  (BAR_PORT_WR_dout),
	.BAR_PORT_WR_en    (BAR_PORT_WR_en  ),
	.BAR_PORT_WR_rst   (BAR_PORT_WR_rst ),
    .BAR_PORT_WR_we    (BAR_PORT_WR_we  ),
	
	.BAR_PORT_RD_addr  (BAR_PORT_RD_addr),//AXI_Lite  只读
	.BAR_PORT_RD_clk   (BAR_PORT_RD_clk ),
	.BAR_PORT_RD_din   (BAR_PORT_RD_din ),
	.BAR_PORT_RD_dout  (BAR_PORT_RD_dout),
	.BAR_PORT_RD_en    (BAR_PORT_RD_en  ),
	.BAR_PORT_RD_rst   (BAR_PORT_RD_rst ),
	.BAR_PORT_RD_we    (BAR_PORT_RD_we  ),
	
    .axi_clk          (axi_clk          ),
    .pcie_mgt_rxn     (pcie_mgt_rxn     ),
    .pcie_mgt_rxp     (pcie_mgt_rxp     ),
    .pcie_mgt_txn     (pcie_mgt_txn     ),
    .pcie_mgt_txp     (pcie_mgt_txp     ),
    .pcie_ref_clk_n   (pcie_ref_clk_n   ),
    .pcie_ref_clk_p   (pcie_ref_clk_p   ),
    .pcie_rst_n       (pcie_rst_n       )

);

//BRAM中写入数据,以便PC读
//RAM状态机
reg[3:0]	RAM_STATE;
parameter	RAM_IDLE  = 4'd0,
			RAM_WRD   = 4'd1,
			RAM_DONE  = 4'd2;
			
reg		[7:0]	ram_cnt;
reg		[7:0]	del_cnt;

wire	vio_exe;//1:开始往BRAM0和BRAM1中写入数据(方便ila捕捉)


wire			ram_wr_ena;
wire			ram_wr_wea;
wire	[8:0]	ram_wr_addra;
wire	[31:0]	ram_wr_dina;

reg				ram_wr_ena_r;
reg				ram_wr_wea_r;
reg		[8:0]	ram_wr_addra_r;
reg		[31:0]	ram_wr_dina_r;

assign	ram_wr_ena 		= ram_wr_ena_r;
assign	ram_wr_wea		= ram_wr_wea_r;
assign	ram_wr_addra	= ram_wr_addra_r;
assign	ram_wr_dina 	= {
    
    ram_cnt[7:0],ram_cnt[7:0],ram_cnt[7:0],ram_cnt[7:0]};

always@(negedge axi_clk or negedge pcie_rst_n)
begin
	if(!pcie_rst_n)
	begin
		ram_cnt 	 	<= 8'h00;
		del_cnt			<= 8'h00;
		ram_wr_addra_r	<= 9'h00;
		ram_wr_ena_r 	<= 1'b0;			  
		ram_wr_wea_r 	<= 1'b0;

		RAM_STATE	 	<= RAM_IDLE;
	end
	else
	begin
		case(RAM_STATE)
			RAM_IDLE:begin						
					ram_wr_dina_r	<= 32'h0000_0000;
					ram_wr_addra_r	<= 9'h00;
					del_cnt			<= (vio_exe == 1)?del_cnt+8'd1:8'd0;
					ram_wr_ena_r 	<= (del_cnt == 2 || del_cnt == 1)?1'b1:1'b0;			  
					ram_wr_wea_r 	<= (del_cnt == 2 || del_cnt == 1)?1'b1:1'b0;

					RAM_STATE 	 	<= (del_cnt == 2)?RAM_WRD:RAM_IDLE;							
				end
			RAM_WRD:begin		
					ram_cnt			<= ram_cnt + 8'd1;
					ram_wr_addra_r	<= (ram_wr_addra_r==32'd511)?32'd511:ram_wr_addra_r+32'd1;//地址控制					

					ram_wr_wea_r 	<= (ram_wr_addra_r==32'd511)?1'b0:1'b1;			  //写命令
										
					RAM_STATE 	 	<= (ram_wr_addra_r==32'd511)?RAM_DONE:RAM_WRD;				
				end
			RAM_DONE:begin
					ram_wr_ena_r 	<= 1'b0;			 							 
				end
			default:begin 
				end	
		endcase
	end
end

//BAR
//BAR状态机
reg[3:0]	BAR_STATE;
parameter	BAR_IDLE  = 4'd0,
			BAR_WRD   = 4'd1,
			BAR_DONE  = 4'd2;
			
reg		[7:0]	BAR_cnt;
reg		[7:0]	del_cnt1;




wire			BAR_wr_ena;
wire			BAR_wr_wea;
wire	[8:0]	BAR_wr_addra;
wire	[31:0]	BAR_wr_dina;

reg				BAR_wr_ena_r;
reg				BAR_wr_wea_r;
reg		[8:0]	BAR_wr_addra_r;
reg		[31:0]	BAR_wr_dina_r;

assign	BAR_wr_ena 		= BAR_wr_ena_r;
assign	BAR_wr_wea		= BAR_wr_wea_r;
assign	BAR_wr_addra	= BAR_wr_addra_r;
assign	BAR_wr_dina 	= {
    
    BAR_cnt[7:0],BAR_cnt[7:0],BAR_cnt[7:0],BAR_cnt[7:0]};

always@(negedge axi_clk or negedge pcie_rst_n)
begin
	if(!pcie_rst_n)
	begin
		BAR_cnt 	 	<= 8'h00;
		del_cnt1			<= 8'h00;
		BAR_wr_addra_r	<= 9'h00;
		BAR_wr_ena_r 	<= 1'b0;			  
		BAR_wr_wea_r 	<= 1'b0;

		BAR_STATE	 	<= BAR_IDLE;
	end
	else
	begin
		case(BAR_STATE)
			BAR_IDLE:begin						
					BAR_wr_dina_r	<= 32'h0000_0000;
					BAR_wr_addra_r	<= 9'h00;
					del_cnt1			<= (vio_exe == 1)?del_cnt1+8'd1:8'd0;
					BAR_wr_ena_r 	<= (del_cnt1 == 2 || del_cnt1 == 1)?1'b1:1'b0;			  
					BAR_wr_wea_r 	<= (del_cnt1 == 2 || del_cnt1 == 1)?1'b1:1'b0;

					BAR_STATE 	 	<= (del_cnt1 == 2)?BAR_WRD:BAR_IDLE;							
				end
			BAR_WRD:begin		
					BAR_cnt			<= BAR_cnt + 8'd1;
					BAR_wr_addra_r	<= (BAR_wr_addra_r==32'd511)?32'd511:BAR_wr_addra_r+32'd1;//地址控制					

					BAR_wr_wea_r 	<= (BAR_wr_addra_r==32'd511)?1'b0:1'b1;			  //写命令
										
					BAR_STATE 	 	<= (BAR_wr_addra_r==32'd511)?BAR_DONE:BAR_WRD;				
				end
			BAR_DONE:begin
					BAR_wr_ena_r 	<= 1'b0;			 							 
				end
			default:begin 
				end	
		endcase
	end
end


//Bram
blk_mem_gen_0 bram_0 (
  .clka(axi_clk),    // input wire clka
  .ena(ram_wr_ena),      // input wire ena
  .wea(ram_wr_wea),      // input wire [0 : 0] wea
  .addra(ram_wr_addra),  // input wire [8 : 0] addra
  .dina(ram_wr_dina),    // input wire [31 : 0] dina
  
  .clkb(axi_clk),    // input wire clkb
  .enb(BRAM_PORT_RD_en),      // input wire enb
  .addrb(BRAM_PORT_RD_addr),  // input wire [8 : 0] addrb
  .doutb(BRAM_PORT_RD_dout)  // output wire [31 : 0] doutb
);
//BAR_PORT_RD_addr
blk_mem_gen_1 bram_1 (
  .clka(axi_clk),    // input wire clka
  .ena(BAR_wr_ena),      // input wire ena
  .wea(BAR_wr_wea),      // input wire [0 : 0] wea
  .addra(BAR_wr_addra),  // input wire [8 : 0] addra
  .dina(BAR_wr_dina),    // input wire [31 : 0] dina
  
  .clkb(axi_clk),    // input wire clkb
  .enb(BAR_PORT_RD_en),      // input wire enb
  .addrb(BAR_PORT_RD_addr),  // input wire [8 : 0] addrb
  .doutb(BAR_PORT_RD_dout)  // output wire [31 : 0] doutb
);


ila_0 ila (
	.clk(axi_clk), // input wire clk
	
	.probe0(BRAM_PORT_RD_addr), // input wire [15:0]  probe0  
	.probe1(BRAM_PORT_RD_din), // input wire [31:0]  probe1 E
	.probe2(BRAM_PORT_RD_dout), // input wire [31:0]  probe2 
	
	.probe3(BRAM_PORT_WR_addr), // input wire [15:0]  probe3 
	.probe4(BRAM_PORT_WR_din), // input wire [31:0]  probe4 
	.probe5(BRAM_PORT_WR_dout), // input wire [31:0]  probe5 
	
	.probe6(BAR_PORT_RD_en), // input wire [0:0]  probe6
	
	.probe7(ram_wr_ena), // input wire [0:0]  probe7 
	.probe8(ram_wr_wea), // input wire [0:0]  probe8 
	.probe9(ram_wr_addra), // input wire [8:0]  probe9 
	.probe10(ram_wr_dina), // input wire [31:0]  probe10
	
	.probe11(BAR_PORT_RD_addr), // input wire [13:0]  probe11 
	.probe12(BAR_PORT_RD_din), // input wire [31:0]  probe12 
	.probe13(BAR_PORT_RD_dout), // input wire [31:0]  probe13 
	
	.probe14(BAR_PORT_WR_addr), // input wire [13:0]  probe14 
	.probe15(BAR_PORT_WR_din), // input wire [31:0]  probe15 
	.probe16(BAR_PORT_WR_dout), // input wire [31:0]  probe16
	
	.probe17(BAR_wr_ena 	), // input wire [0:0]  probe17 
	.probe18(BAR_wr_wea	), // input wire [0:0]  probe18 
	.probe19(BAR_wr_addra), // input wire [8:0]  probe19 
	.probe20(BAR_wr_dina ), // input wire [31:0]  probe20
	
	.probe21(BRAM_PORT_RD_en) // input wire [0:0]  probe21
	
);

vio_0 vio (
  .clk(axi_clk),                // input wire clk
  .probe_out0(vio_exe)  // output wire [0 : 0] probe_out0
);

endmodule

貌似两个BRAM写入数据的产生可以用一个always,懒得改了☹

记录一下top包含的BRAM的IP设置:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.4 PCIe DMA传输流程以及AXI_Lite寄存器描述

如果选择了AXI内存映射接口,
C2H传输的源地址为AXI地址,目的地址为PCIe地址。
H2C传输的源地址是PCIe,目的地址是AXI地址。

H2C和C2H转移的初始设置:
在这里插入图片描述
汉化一下:
在这里插入图片描述
以下图中:绿色是应用程序;橙色是驱动;蓝色是硬件。

H2C DMA传输流程:在这里插入图片描述
汉化一下:
在这里插入图片描述
在H2C的过程中(PC执行写命令时)通过ila在AXI_Lite口捕捉该过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

C2H DMA传输流程:
在这里插入图片描述
汉化一下:
在这里插入图片描述
在C2H的过程中(PC执行读命令时)通过ila在AXI_Lite口捕捉该过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面的寄存器是什么呢?这就涉及到了AXI_Lite口寄存器。

AXI_Lite口寄存器描述:

  PCIE到DMA空间的事务被路由到内部配置寄存器总线上。该总线支持32位地址空间和32位读写请求。PCIe寄存器的DMA子系统可以从主机或者AXI Slave接口访问,这些寄存器用于对直接存储器存取进行编程和检查状态。

PCIe到DMA的地址格式:
在这里插入图片描述
地址字段描述:
在这里插入图片描述
字段 [15:12]:
DMA的目标子模块

4’h0:
在这里插入图片描述
4’h1:
在这里插入图片描述
4’h2:
在这里插入图片描述
4’h3:
在这里插入图片描述
4’h4:
在这里插入图片描述
4’h5:
在这里插入图片描述
4’h6:
在这里插入图片描述
4’h8:
在这里插入图片描述
字段 [7:0]:
目标内要访问的寄存器的字节地址。位[1:0]必须为0。如下图bit0、bit1均为0。
在这里插入图片描述

如上图
AXI_LITE_AWADDR[31:0] = 0x0000_2014
AXI_LITE_WDATA[31:0] = 0x0000_0002
说明:

AXI_LITE_AWADDR :
bit1:bit0 均为0。
bit7:bit0:访问的寄存器的字节地址:0x14。
bit15:bit12:02h,DMA的目标子模块为IRQ Block。
bit31:bit16:Reserved。

上面H2C、C2H流程里面的寄存器应该明白是怎么回事了吧。

具体参考 文档pg195。

2.5 功能展示

分别展示PC通过PCIe传输数据和配置寄存器的过程。
在这里插入图片描述

2.5.1 数据的读写

对应的是对BRAM_PORT的操作。
在这里插入图片描述
读写文件:

1、PC通过CMD执行以下命令,将 pc2fpga.bin 文件下的4096Byte写入FPGA 起始地址为0x00000000:

xdma_rw.exe h2c_0 write 0x0000000 -b -f pc2fpga.bin -l 4096

pc2fpga.bin文件内容如下所示:
在这里插入图片描述
PC通过CMD执行以下命令(TX):
在这里插入图片描述
FPGA数据接收(RX):
在这里插入图片描述

2、PC通过CMD执行以下命令,从FPGA读取4096Byte写入到PC的 fpga2pc.bin 文件里:

xdma_rw.exe c2h_0 read 0x0000000 -b -f fpga2pc.bin -l 4096

PC通过CMD执行以下命令(RX):
在这里插入图片描述
观察在cmd访问的文件夹下,接收并创建了fpga2pc.bin文件,大小为4KB
在这里插入图片描述
接收到的数据fpga2pc.bin 如下所示:
在这里插入图片描述

FPGA端正在上传的数据(TX):
在这里插入图片描述
FPGA上传的数据是怎么产生的?
PC端接收到的数据是FPGA提前写入BRAM的数据,当PC下发读取指令时即读取的为BRAM的数据,那我们BRAM提前写入的是什么呢?提前通过RTL代码将一下数据写入BRAM0。
在这里插入图片描述
则BRAM中的数据为:

0 1 2 3 4 5 6 7 8 9 a b c d e f
00h 00 00 00 00 04 04 04 04 08 08 08 08 0c 0c 0c
10h 10 10 10 10 14 14 14 14 18 18 18 18 1c 1c 1c
20h 20 20 20 20 24 24 24 24 28 28 28 28 2c 2c 2c

再对比读回来的数据,无误。

读写数据:

3、PC通过CMD执行以下命令,写32Byte

0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f

的数据到FPGA:

xdma_rw.exe h2c_0 write 0x0000008  -l 32 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f

PC端 cmd输入(TX):
在这里插入图片描述
FPGA数据接收(RX):
在这里插入图片描述

4、PC通过CMD执行以下命令,读取32Byte的数据到PC:

xdma_rw.exe c2h_0 read 0x0000000  -l 32

PC端 cmd输入(RX):
在这里插入图片描述
FPGA正在上传的数据(TX):
在这里插入图片描述

2.5.2 BAR空间的访问

对应的是对BAR_PORT的操作。

在这里插入图片描述

1、PC通过CMD执行以下命令,通过PCIe BAR空间将4Byte数据映射到AXI_Lite寄存器:

xdma_rw.exe control write 0x08 -l 32 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f

PC端 cmd输入(TX):
在这里插入图片描述

FPGA接收的数据(RX):
在这里插入图片描述

2、PC通过CMD执行以下命令,PCIe BAR空间访问AXI_Lite寄存器读取4Byte的数据:

xdma_rw.exe user read 0x08 -l 4

PC端 cmd输入(RX):
在这里插入图片描述
观察接收到FPGA返回的数据为0x08080808.
观察FPGA正在上传的数据(TX):
在这里插入图片描述
无误。

★★★如有错误,欢迎指导!!!

猜你喜欢

转载自blog.csdn.net/qq_40147893/article/details/118565988
今日推荐