文章目录
一、什么是PCIE?
PCIe(Peripheral Component Interconnect Express)是一种高速串行计算机扩展总线标准,用于连接计算机主板与外部设备。它是传统PCI(Peripheral Component Interconnect)总线的改进版本,提供了更高的速度、更低的延迟和更高的带宽;PCI是早期的一种32位并行总线接口,接口图片如下:
由于并行传输有许多问题,主要的问题还是速度不快。因此高速总线转为串行传输是一种发展趋势。PCIE是从PCI总线上升级演变而来的(E就是扩展的意思,表示PCIE是PCI的扩展升级版),因此也继承了许多PCI的东西,PCIE接口照片如下:
我们常见的网卡、显卡、NVMe固态都是使用PCIE接口;PCIE相比PCI有许多优点,例如:
- 高速串行传输 : PCIe使用点对点的高速串行传输方式,与传统的并行PCI总线不同,可以避免并行信号中的时钟偏移问题。
- 可扩展性:PCIe支持不同的链路宽度,如 x1、x4、x8、x16 等,其中“x”表示通道数量;每个通道有两对差分信号线(发送和接收);宽度越大,带宽越高。例如,x16通道通常用于显卡,提供更大的数据吞吐量。
- 热插拔:PCIe支持部分设备的热插拔,而PCI不支持。
二、PCIE的基础知识
由于PCIE是从PCI总线升级而来,有许多历史遗留的问题导致PCIE的协议规范十分庞大,初学者啃协议手册十分吃力。因此本文只介绍一下常用的基础知识,等大家都了解后,再继续深入学习协议手册。
2.1 链路(Link)
PCIE是端到端的数据传输方式,两个PCIE设备之间的数据传输路径称为链路,传输示意图如下所示:
每个链路由多个收发通道(lane)组成, 每一个收发通道由一个接收器和一个发送器组成,每个接收器或者发送器由一对差分线链接,一个收发通道示意图如下所示:
我们平常说的PCIE x1,x2,x4,x8,x16是指每一个链路中包含了多少lane,比如PCIE x4通道的示意图如下所示:
2.2 传输速率
PCIE不同版本的带宽不一样,以下是各版本的速率表:
PCIE版本 | 传输速率 | x1 | x2 | x4 | x8 | x16 | 编码方式 |
1.0 | 2.5GT/s | 250MB/s | 500MB/s | 1GB/s | 2GB/s | 4GB/s | 8B/10B |
2.0 | 5GT/s | 500MB/s | 1GB/s | 2GB/s | 4GB/s | 8GB/s | 8B/10B |
3.0 | 8GT/s | 980MB/s | 1.96GB/s | 3.92GB/s | 7.84GB/s | 15.68GB/s | 128B/130B |
4.0 | 16GT/s | 1.93GB/s | 3.92GB/s | 7.84GB/s | 15.68GB/s | 31.36GB/s | 128B/130B |
5.0 | 32GT/s | 3.92GB/s | 7.84GB/s | 15.68GB/s | 31.36GB/s | 62.72GB/s | 128B/130B |
其中的GT表示每秒传输多少次数据(编码前),因此我们来计算PCIE 1.0 的x1速率 = 2500M × 8(编码前) ÷10(编码后) ÷ 8(一个字节) = 250MB/s;我们来算一下PCIE 3.0 x1的速率 = 8000M × 128 ÷ 130 ÷8 = 984MB/s ≈ 980MB/s,其它的计算同理。
2.3 PCIE总线拓扑结构
PCIE总线为层次机构,拓扑图如下所示:
- Root Complex(RC):根桥组件,负责解析和生成PCIE报文;RC可以接收CPU的指令来生成对应的PCIE报文传输给下游,也可以接收到下游设备的PCIE报文,解析出数据传输给CPU,也可以接外围的存储器等一些列PCIE终端设备,类似于CPU的北桥。
- PCI Express Endpoint : PCIE终端设备,属于整个树状拓扑结构中的叶子节点;比如显卡、网卡、Nvme固态硬盘都属于EP。
- Switch:PCIE扩展设备,类似于网络交换机;目的是扩展PCIE总线,因为PCIE属于端到端的高数串行传输,每一条链路的两端只能有一个设备,如果需要多个PCIE设备传输,则必须使用Switch。
2.4 PCIE 配置空间
PCIE配置空间是非常重要的概念,它是由PCI协议传承下来的。配置空间就相当于一块RAM,里面存放了各种信息;早期的PCI配置空间只有256字节,现在的PCIE的配置空间有4KB;PCIE的配置空间有两种,一种是用于EP设备的简称Type00,另一种是用于RC或者Switch设备的简称Type01,我们用FPGA开发属于EP设备,因此我们只看第一种配置空间:
其中配置空间的头部信息格式如下所示:
- Vendor ID :用于唯一标识设备的制造商,不同的厂商有不同的 ID 代码,系统可借此找到对应的设备驱动程序。比如Xilinx 的厂商ID是10EE,英特尔的厂商ID是8086。
- Device ID :由厂商分配,用于标识特定的设备型号。结合 Vendor ID,能够准确地确定具体的设备类型,以便系统为其加载合适的驱动程序。
- Command:16位命令寄存器,主要用于控制设备的各种操作和功能特性,比如I/O空间使能、内存空间使能、总线控制使能、中断是否禁用、奇偶校验错误
- Status:16位状态寄存器,用于反映设备的当前状态和一些重要事件的发生情况;比如检测到奇偶校验错误,已发出目标中断信号、接收到主机中断信号、中断状态、PCI支持66M时钟等等。
- Revision ID:表示设备的版本信息,有助于区分同一设备的不同硬件版本(只读)。
- Class Code:类代码用于标志 PCIe 设备的用途,共三字节,分别是类代码、子类代码、编程接口。它不仅用于区分设备类型,还规范了编程接口,使得系统能够根据设备的类型和功能提供通用的驱动程序支持。
- Base Address:用于指定设备所需要的内存地址空间和 I/O 地址空间的基地址。系统可以根据这些寄存器的值为设备分配相应的地址范围,使得设备能够与系统的内存和 I/O 资源进行有效的交互。
其它的太多了,感兴趣的同学可以单独查阅一下功能,这里就不一一展开了。
2.5 BAR空间
BAR空间是非常重要的概念,一定要好好理解。BAR(Base Address Register,基地址寄存器)是在PCI或PCIE总线上用于定义设备可访问的地址范围。每个PCI或者PCIE设备可以有多个BAR;BAR 空间的作用主要是定义设备在系统内存地址空间中的映射范围,以便主机能够准确地访问设备的寄存器或内存资源。
2.5.1 BAR寄存器配置流程
一个BAR寄存器为32位,一个EP设备最多有6个BAR;在EP设备未配置的情况下,BAR寄存器都处于未赋值的状态,如下所示:
- 现在我们来配置BAR,首先第0位。如果第0位是0表示该设备映射到存储器空间,1表示映射到I/O空间,在FPGA中大多数都是映射到存储器空间,因此这里设置为0。
- 然后配置第1位,这一位是保留,一直为0
- 然后配置第2位,0表示申请的是32位地址空间,1表示申请的是64位地址空间
- 然后配置第3位,1表示开启prefetch(预取)功能,0表示不开启。如果 prefetch 位置 1,通常表示该设备支持预取操作,系统可以更积极地提前为设备准备数据,以提高数据传输的效率。
- 然后配置需要申请多大的内存空间,比如我要申请4KB的内存,那么需要12位来申请,那么就给BAR寄存器除去上面的4位,再给8位的0,如下所示:
上面就是整个BAR寄存器配置的步骤,接下来我们看CPU怎么通过这个配置来分配地址。
2.5.1 CPU地址分配
当主机系统启动时,会尝试将BAR所有寄存器写入1 (上面已经赋值的位不能写入),写入后的寄存器如下所示:
然后读出BAR里面的值,现在系统读出来的值是:
系统读到第0位为0,就知道该PCIE设备要映射到存储器空间地址;系统读取到第2位为0,就知道该PCIE要申请32位地址空间;系统读取到第3位的0,就知道没有开起预取功能。在读取完这四位后,将这四位全都置0然后所有位取反再加1得到:
系统会给少于4KB的地址空间划分至少4KB的地址空间,上面取反后得到数值后,系统读取到第12位为1,然后就在系统的所有内存空间中寻找空闲的内存区域,然后划分4KB的空间大小分给这个PCIE设备,例如系统找到了4KB的空间大小,假如起始地址是0x2FD76A20,那么系统就会将0x2FD76A20写入到BAR里面,最终的BAR的数据就是:
PCIE设备读取到BAR后就知道自己被分配到了哪一段地址空间,并且可以使用这段地址空间与系统进行数据传输了。
例如在申请2KB的内存大小,BAR寄存器的值为0XFFFFF800,和上述描述过程一致。
2.6 中断机制
在PCIE设备中,中断是用于通知CPU(主机)有重要的事情发送或者需要主机进行处理的机制。中断是设备和主机之间的异步通信,使主机能够及时的响应设备的特殊情况,而不必等到CPU轮询到此设备时才响应。PCIE中的中断方式主要有以下三种:
- 传统中断(Legacy Interrupt): 也称为 INTx 中断,包括 INTA#、INTB#、INTC# 和 INTD# 这四条中断线。
- 首先在设备的PCIE配置空间中,设置与INTx有关的寄存器,以启用中断功能。
- 设备通过对应的INTx对应的引脚想PCIE总线发送中断请求信号。
- 主机的中断控制器收到中断请求信号后会通知CPU有中断发送。
- CPU就会暂停当前执行的任务,转去执行相应的中断服务程序(ISR)。
- 在中断服务程序中,主机读取设备的状态寄存器以获取详细的中断信息,并执行相应的处理操作,如读取数据、清除状态标志等。
- 处理完成后,主机通过写入设备的特定寄存器来清除中断标志,以便设备可以再次触发中断。
- MSI(Message Signaled Interrupt):也叫消息信号中断,在传统的中断机制(如 INTx 中断)中,中断线的数量有限,可能导致中断共享时的复杂性和不确定性。
- 在设备初始化时,主机为 PCIe 设备分配一段内存区域用于存储中断消息。同时,设备在其配置空间中指定了用于发送中断的内存地址。
- 当 PCIe 设备需要向主机发送中断时,它会准备一个中断消息。这个消息通常包含中断向量、设备标识等关键信息。
- 设备将中断消息写入之前主机分配的特定内存地址。
- 主机的中断控制器会监测到这个内存写入操作,并将其识别为中断请求。
- 主机根据中断消息中的信息,确定对应的中断服务程序(ISR),并切换上下文去执行该 ISR 来处理中断。
中断向量是一个特定的数值或地址,用于标识不同的中断源和指向相应的中断服务程序,当一个中断发生时,系统会根据中断向量来确定应该执行哪个中断服务程序来处理这个中断事件。中断向量通常存储在一个特定的中断向量表中。中断向量表是一个存储中断向量的有序集合,每个中断向量对应一个特定的中断类型或设备。
MSI相对于传统中断的优势有:(1)、减少引脚数量:不再依赖有限的物理中断引脚,降低了硬件复杂性。(2)、支持多个中断源:一个设备可以拥有多个独立的中断向量,从而能够区分不同类型的事件。(3)、由于直接通过内存写操作触发中断,减少了中断信号传播的延迟。
- MSI-X(Extended Message Signaled Interrupts):扩展消息信号中断,是对MSI的扩展。MSI中断机制最多只能支持32个中断请求,而且要求中断向量连续,而MSI-X中断机制可以支持更多的中断请求,而且不要求中断向量连续,其他的基本是和MSI一致。
现在大多数PCIE设备都使用MSI或者MSI-X了,不再使用传统中断。
2.7 PCIE总线地址映射
通常一个CPU会外接很多外设,比如内存条、网卡、声卡、显卡等等。每种外设和CPU数据交互都需要利用CPU的DDR做中间缓存;但是CPU可能给每种外设分配的地址都是杂乱无章的,因此CPU通常会使用MMU(内存地址管理单元)来将各种外设地址映射成连续的地址。被映射后的地址也叫虚拟地址,此时CPU通过访问虚拟地址,MMU就能转换成外设的物理地址。
在PCIE设备侧也存在地址映射,PCIE对外是链接CPU,PCIE设备内部也有存储域,CPU侧地址≠PCIE内部存储域地址,因此PICE内部也有对应的地址映射功能。整体的PCIE总线地址映射关系如下所示:
以上示例现实,通过MMU映射后CPU通过访问虚拟内存地址1,就能访问到外设0,其他的同理。
三、配置XDMA搭建BD工程
首先新建一个BD工程,先添加XDMA。
3.1 配置XDMA
- 选择我们板卡上的链路的收发通道数,我这板卡是x2的。
- 选择PCIE的协议版,我的板卡上是PCIE 2.0 因此最大的链路速率可以选到5.0GT/s。
- XDMA参考时钟频率,这里大部分都使用电脑主板上的PCIE插槽提供100M时钟。
- DMA接口, 这里选择AXI4。
- AXI数据宽度,这里选8字节。
第二页就是设置配置空间里面的参数;10EE是Xilinx的厂商ID;设备ID中的70表示这是Xilinx 中的7系列FPGA;2表示PCIE版本为2.0;最低位的2表示通道数量。下面的Class Code就是根据此PCIE功能定制的:
可以选择网络控制器、存储控制器、各种各样的类型设备,本文就先默认选第一个。
- 首先使能AXI-Lite接口,这样主机一侧可以通过PCIE来访问FPGA侧的寄存器,映射空间大小用户自定义。
- 然后是PCIE到AXI的偏移地址,这个地址和后面挂的BRAM偏移地址一致。
- 选择中断数量。
- 是否开启传统中断,这里选NONE。
- 使能MSI中断,中断向量先选4个,最多可选择32个。
最后一页是配置数据流方向的通道数;H2C表示“Host-to-Card”,即主机到板卡的设备数据流方向。C2H表示“Card-to-Host”,即板卡到主机数据流方向。这个通道数量就代表这种传输方向通道有多少个,如果我们有不同的数据来源,我们就可以设置两个数据通道来传输,这样上位机就能很快识别出这数据的来源。
3.2 搭建BD工程
上面配置好XDMA后,我们就例化剩余的模块,最终BD系统框图如下所示:
XDMA的AXI4接口接的是DDR,适合大量数据的突发传输;AXI-Lite后面接了一个Bram,是上面配置时选择的FPGA侧的寄存器,主机可以通过PCIE来读写FPGA侧的寄存器,地址分配如下:
四、安装Xilinx XDMA驱动
工程搭建后,要想实现PCIE通信,还需要安装Xilinx 提供的XDMA驱动程序,让电脑能识别出这个设备。
- 首先让电脑进入测试模式,因为Xilinx这个驱动没有经过Windows系统的签名认证,所以需要让电脑进入测试模式绕过这个认证。
先以管理员的身份打开命令提示符,然后输入“Bcdedit /set testsigning on” ,回车即可;提示操作成功后,重启一下电脑,我们就能在桌面左下角看到:
表示我们已经成功进入了测试模式。如果需要退出测试模式,只需要同样的操作输入“Bcdedit /set testsigning off”。
- 然后打开下载好的驱动包,如下所示:
右键点击XDMA.cer 点击安装证书,然后县级下面的XDMA.inf安装
以上的驱动安装好后,我们把电脑关机,然后把板卡插上去然后开机。
五、下板验证
5.1 检查板卡链接情况
我们开机后打开设备管理器:
如果设备管理器显示了Xilinx DMA则表示已经连接成功
5.2 BAR空间读写测试
我们先来测试一下FPGA侧的寄存器,如BD系统框图里的BRAM,我们打开Windrve:
这里可以看到我们的PCIE配置空间里面的数值,很多都是我们在XDMA IP配置界面设置的,我们可以看到有两个BAR:BAR0,BAR1。其中的BAR1是XDMA为PCIE设备申请的内存空间,BAR0是我们用户申请的BRAM空间,我们可以操作BAR0:
我们先像BAR0的偏移地址0,写入一个32位数字,打开Vivado program and debug 抓取AXI-Lite 的写通道:
可以看到,我们成功在BRAM的0地址写入了 0X35AC6587,接下来我们读出来:
可以看到我们读出来的数值也是 0X35AC6587,打开debug窗口
数据也是成功读出来了。说明整个PCIE 读写 FPGA侧的寄存器没问题,接下来我们测试DMA通道的读写
5.3 DMA通道读写测试
我们先在windows PowerShell敲命令:
.\simple_dma.exe
这命令的意思是向PCIE写入8MB的数据然后读出来。可以计算出PCIE的读写速率。这里可以看到我们写入8MB花了13224us,读8MB数据花了9738us。
再敲命令:
.\xdma_test.exe
这命令的意思是向每个通道写入4KB的数据,然后再读出来验证数据是否正确,我们再前文XDMA配置的时候选了H2C和C2H都是两个通道,因此这里也测试的只有两个通道,看到了数据显示OK,表示两个DMA通道正常,我们打开debug窗口,抓取波形:
可以看到写入的都是驱动里面的测试数据,是通过M-AXI接口传输的。
敲命令:
.\xdma_rw.exe h2c_0 write 0x0000000 -b -f datafile4K.bin -l 4096
这命令的意思是向地址0写入4K个数据,写入后我们再敲读命令:
.\xdma_rw.exe c2h_0 read 0x0000000 -b -f datafile4K_recv.bin -l 4096
可以看到写入的4K数据和读出的4K数据一致,因此我们的DMA通道读写也没问题。