干了这么多年FPGA,PCIe这个方向有点意思。大多数开发者第一次接触PCIe,都是拿FPGA当Endpoint(EP)挂到电脑主板上,做个采集卡、加速卡什么的。真正把身份反过来,让FPGA做Root Complex(RC)去主动访问别的PCIe设备,很多人可能只在文档里见过。标题里这个“AXI Memory Mapped To PCIe”就是干这个事的IP核,我这次想把它在RC模式下的完整流程理一遍:从Vivado里搭工程、配IP,到写AXI侧逻辑,再到上板调试链路训练和读写通路。这篇东西适合已经跑过PCIe EP基础工程、熟悉AXI总线,但没怎么碰过RC模式的人看。内容比较多,我把配置步骤和调试坑都摊开讲。
1. 为什么需要FPGA当Root Complex?RC模式与EP模式的本质区别
1.1 两种角色的底层差异
先打个比方。EP模式下的FPGA像一个租客,CPU是房东。你访问我、读写我的寄存器,所有事务都由CPU那边发起,FPGA只能被动响应。RC模式就反过来了,FPGA自己当房东,主动去敲别的设备(Endpoint)的门,发起配置访问、Memory读写、甚至DMA搬运。这个区别不只是角色标签变了,整个数据流方向、地址空间归属、枚举机制全部翻转。
从PCIe协议角度看,RC最重要的工作有三块:一是链路初始化,也就是LTSSM状态训练,确保物理链路能从Detect一路跑到L0;二是总线枚举,通过Type 0和Type 1配置请求,发现总线上的Endpoint、分配总线号和BAR地址;三是事务层转发,把软件或者FPGA逻辑要做的Memory/IO访问,封装成正确的TLP发出去。EP模式下的FPGA这些都不用管,因为主机侧的RC已经把这些处理完了,你只需要响应配置请求和收发TLP。
很多人在EP模式下写逻辑写习惯了,第一反应是“RC模式也不过是给IP核换个配置选项”。等真正上板才发现,恶魔都在细节里。AXI Memory Mapped To PCIe这个IP核在RC模式下,AXI侧的角色、地址映射的方式、复位和时钟的要求,跟EP模式完全不一样。下面这张表可以快速对比两种模式的区别:
| 对比项 | EP模式(Endpoint) | RC模式(Root Complex) |
|---|---|---|
| FPGA角色 | 被动外设 | 主动主机 |
| 事务发起方 | 主机侧CPU | FPGA内部逻辑 |
| 配置空间 | 暴露自己的配置空间 | 负责配置下游设备 |
| 总线枚举 | 不需要 | 必须实现或借助IP逻辑 |
| 地址映射 | BAR由主机分配 | 通过AXI地址访问下游BAR |
| 调试重点 | 响应TLP、完成读写 | 链路训练、配置访问、AXI超时 |
| 典型应用 | 采集卡、加速卡 | 嵌入式平台扩展PCIe设备、数据中转 |
1.2 RC模式解决了什么真实工程问题
这里说几个真实场景,大家感受一下RC模式到底什么时候用得上。
第一个场景是嵌入式和异构平台上外接PCIe设备。比如基于FPGA的主控板想接NVMe SSD、PCIe万兆网卡或者专用的视频采集卡。此时板上没有x86 CPU,也没有现成的PCIe Root Complex,FPGA只能自己顶上去。第二个场景是实验室里要模拟一个RC主机,用来验证某款PCIe Endpoint IP或者新出的一款PCIe芯片,比如在FPGA开发环境里验证设备的上电枚举行为。第三个场景是高速数据中转,FPGA直接扮演主机去从PCIe接口的AD/DA板上拉数据,省掉中间CPU跳转的延迟。
这些场景的共同点是:你需要主动发起PCIe事务,并且有相对明确的下游设备要访问。用通用CPU虽然也能当RC,但实时性、数据通路灵活性不及FPGA实现来得顺。AXI Memory Mapped To PCIe IP就是专门做这件事的,把PCIe协议栈、链路训练、事务层逻辑都封装好了,你只需要在AXI侧准备好地址和数据。
1.3 AXI Memory Mapped To PCIe 与 XDMA 的区别
很多初学者会把这个IP和另一款常用的Xilinx DMA IP(XDMA)搞混。XDMA的核心特点是把Endpoint接口和DMA控制器集成在一起,重点解决“大量数据怎么在主机内存和FPGA之间搬运”的问题。它在PC上工作时,主机侧要装驱动,本质上还是FPGA作为Endpoint被主机管理。
AXI Memory Mapped To PCIe(有时也叫AXI Bridge for PCI Express)的重点则是一个透明桥接。它不绑定DMA引擎,主要提供AXI事务与PCIe TLP之间的映射。在RC模式下,FPGA侧逻辑只要能发起AXI读写,就能访问下游Endpoint的内存空间和配置空间,整体路径非常直接。如果你的目的是“让FPGA可控地读写一个PCIe外部设备”,选这个更合适;如果你是要“让PC主机和FPGA做大数据量交互”,那XDMA更顺手。这个选型一步错,后面工程方向就全歪了,务必先想清楚。
2. 从官方例程起步:Vivado工程搭建与IP核配置实操
2.1 创建工程、选择器件版本和IP版本
我建议直接使用Vivado 2021.1及以上版本,同时确认器件选型。Amd(Xilinx)在Versal和UltraScale+系列里对这个IP的支持最完整,7系列也能用,但License和IP版本功能上会有些差异。工程创建时语言选Verilog,目标器件根据手头的板子来,如果是自制板,一定要确认PCB上的PCIe参考时钟、复位和供电都引到了FPGA相应引脚。
打开Vivado后,在IP Catalog里搜索“AXI Memory Mapped To PCIe”,能看到Gen2、Gen3等不同速度等级对应的IP版本。选型时主要看链路速率要求:Gen2对应5GT/s,Gen3对应8GT/s。如果下游设备是NVMe SSD这类高速外设,优先考虑Gen3;如果只是低速采集卡或者工控设备,Gen2也够用。IP配置向导支持同时生成Endpoint和Root Complex两种模式,切到RC模式之前,建议先打开官方Example Design走一遍,避免自己从空工程摸索。
2.2 IP配置向导中的关键选项详解
在IP配置窗口里,最上面一栏选择“Root Complex”模式,这一步定了后面所有AXI接口的方向和内部逻辑行为。下面这5个配置项是我反复踩过之后认为最关键的。
第一个是PCIe ID配置,包括Vendor ID(厂商ID)、Device ID(设备ID)、Subsystem ID。RC模式虽然不像Endpoint那样需要对外呈现设备身份,但很多调试工具和软件协议栈会依据这些ID判断匹配关系。建议直接用AMD默认的10EE厂商ID,Device ID按自己的项目编一个。
第二个是链路宽度和速率。链路宽度有x1、x2、x4、x8等选项,必须和PCB实际走线数量一致。我见过有人IP里配置x8,结果硬件上只引出x4的差分对,链路训练一直上不去,卡在Polling状态。速率方面,同样要兼顾对端设备的支持能力,如果对端是老的PCIe 2.0设备,哪怕IP配了Gen3,训练结果也会自动降级到Gen2,但前提是你的IP数据通路要能容忍这种自适应。
第三个是参考时钟配置。PCIe参考时钟通常要求100MHz差分输入,而且有专门的抖动和精度约束。IP向导里会让你选是使用板上独立时钟还是由其它逻辑转发,建议选择独立时钟源,不要从任何PLL派生。RC模式下,这个100MHz时钟不只是给FPGA内部PCIe逻辑用的,有时候还要作为RefClk输出给下游设备,这就涉及到时钟缓冲和具体的引脚分配,务必先查板子的原理图。
第四个是AXI地址宽度和数据位宽。AXI侧地址宽度决定了你能访问的PCIe地址空间范围,比如32位地址最多4GB,64位地址就是大地址空间。数据位宽常见128位或256位,和PCIe链路位宽对应关系紧密。这个配置直接影响AXI总线的突发长度表现,建议和后面的用户逻辑一起考虑后再定。
第五个是BAR空间设置。在RC模式下,FPGA侧主要访问的是下游设备的BAR空间,但IP自身也有一组BAR配置选项需要填。如果后续你希望主机侧通过某种方式访问RC自身的寄存器或者内部存储器,就需要把对应BAR打开;如果只是纯主动访问外部设备,把这组BAR设置保持默认或者关闭即可,别在无关的配置上浪费思考时间。
2.3 打开官方Example Design,了解工程结构
配置完成后,在IP核右键菜单里选“Open IP Example Design”,Vivado会生成一个完整的参考工程。这个工程值得仔细读,因为官方例程已经把RC模式下的基本通路搭好了。例程里通常有AXI Slave和Master两个方向的设计,其中Master方向就是用来发起PCIe读写访问的,里面有完整的PIO逻辑和简单的状态机。
例程结构中需要重点看的模块有几个:首先是AXI Master接口的状态机,它演示了怎么发起aw/ar/w通道的事务,怎么等待响应;其次是中断逻辑,RC模式下FPGA内部可以产生中断,也可以通过INTx/MSI打断主机,例程里能看到中断向量和控制寄存器的管理方式;再者是ILA探针,官方例程默认在AXI接口和一些关键PCIe状态信号上挂好了调试核,上板之后可以直接看波形。
不要一上来就删掉官方例程自己写,哪怕你心里已经有了很清晰的设计思路。我先在官方Example Design基础上跑通一次综合、实现、生成Bitstream、上板观测,再在此基础上加入自己的逻辑,这个节奏出问题的概率低很多。
2.4 综合、实现、生成Bitstream的注意事项
这个IP综合时间比普通逻辑长不少,尤其开了Gen3模式后,物理层逻辑的资源占用和时序收敛难度都会上升。在综合前确认时序约束文件(XDC)里包含了pci_express相关的时钟约束,否则实现时容易报出大量时序违规。Vivado自带模板可以通过向导自动引入,不用手写。
实现过程中如果出现时序不收敛,优先检查参考时钟的约束时钟频率是否正确,其次看PCIe复位信号是否被错误地接到了普通GPIO上。复位信号要求异步复位、同步释放,并且要满足PCIe规范的上电时序。很多时候实现时序不过,不是逻辑效率问题,是复位路径上多了个反相器或者延迟链,导致物理层和事务层的延迟预算超标。生成Bitstream之后,先不要急着上板,回到Example Design里确认ILA探针都接好了,再去加载固件。
3. 核心逻辑拆解:AXI事务如何变成PCIe TLP
3.1 地址映射的底层原理
整个IP核最核心的机制就是地址映射。FPGA内部的AXI总线地址空间,通过IP核转换成PCIe地址空间里的TLP请求。这个转换不是简单的地址加偏移,而是由IP内部的地址译码逻辑和BAR空间共同决定的。
举个例子,如果你在RC模式配置里给AXI Master方向设定了起始地址0x00000000,那么当FPGA侧逻辑发起一个AXI读事务,地址是0x00001000时,IP会把这条读请求转换成PCIe Memory Read TLP,目标地址对应下游设备BAR窗口内的偏移。如果下游设备的BAR空间只有256B,而你访问了1KB以外的地址,TLP会发出去,但对端设备会返回Unsupported Request或者直接不响应,AXI侧就会一直等到超时。
所以在RC模式下写逻辑之前,第一件事是把下游设备的BAR空间大小和基地址搞清楚。可以通过板卡手册查,也可以先用配置读写枚举设备,读出BAR寄存器里的值再推算。地址映射搞错,后面所有调试都白费,数据读回来全是一堆无意义的0xFF或者死等。
3.2 官方例程中的读写通路
官方例程里有一段经典的PIO状态机,它做的事情很简单:收到AXI总线上的写请求,把数据存入寄存器;收到读请求,把寄存器值返回。这段代码把AXI4协议里最关键的握手信号都展示了一遍,包括AW通道的awvalid/awready、W通道的wvalid/wready、B通道的bvalid/bready,以及AR通道和R通道。
这段代码的逻辑很直接,但有一个地方新手容易忽略:AXI突发传输长度。官方PIO通常只处理单拍读写,长度固定为1。如果你的实际应用要从FPGA侧发起较长突发的读写,比如一次读64字节,那就需要把状态机扩展成能处理多个数据的循环。IP核本身支持突发,但AXI侧的地址递增规则和PCIe TLP的payload封装方式必须对齐,不然会出现数据错位。
这里给一段基于官方例程扩展的读状态机伪代码,演示怎么发起一次长度为4的读突发:
// AXI read burst state machine (burst length = 4) localparam IDLE = 3'd0, AR_ISSUE = 3'd1, WAIT_RDATA = 3'd2, DONE = 3'd3; reg [2:0] state; reg [1:0] beat_cnt; reg [31:0] rd_addr; always @(posedge axi_aclk or negedge axi_aresetn) begin if (!axi_aresetn) begin state <= IDLE; beat_cnt <= 2'd0; rd_addr <= 32'h0; end else begin case (state) IDLE: begin if (start_read) begin rd_addr <= start_addr; axi_araddr <= start_addr; axi_arvalid <= 1'b1; axi_arlen <= 4'd3; // 4 beats state <= AR_ISSUE; end end AR_ISSUE: begin if (axi_arready && axi_arvalid) begin axi_arvalid <= 1'b0; state <= WAIT_RDATA; beat_cnt <= 2'd0; end end WAIT_RDATA: begin if (axi_rvalid && axi_rready) begin data_buf[beat_cnt] <= axi_rdata; beat_cnt <= beat_cnt + 1'b1; if (beat_cnt == 2'd3 || axi_rlast) begin state <= DONE; end end end DONE: begin read_done <= 1'b1; state <= IDLE; end endcase end end这个逻辑在实际工程里基本够用了。要注意的是rlast信号,它是AXI读突发最后一个节拍的标致,判断结束条件时最好以它为准,而不是自己数节拍。我自己就犯过数错拍子导致卡死的错误,后来统一改成以rlast为准,逻辑简单且不会出错。
3.3 最小可用逻辑:对下游设备BAR空间发起读写
把上面的读状态机加上写状态机组合起来,再配合一个简单的命令寄存器,就能实现“FPGA主动访问下游PCIe设备BAR空间”的最小系统。写流程和读类似,区别是多了AW和W两个通道。实际操作中我习惯把AXI Master的地址线、数据线、控制信号独立封装成一个模块,对外只提供最简的start/write/read接口,这样上层逻辑可以跟业务功能解耦。
一个最小写的状态机核心部分如下:
case (state) IDLE: if (start_write) begin axi_awaddr <= write_addr; axi_awvalid <= 1'b1; axi_wdata <= write_data; axi_wvalid <= 1'b1; axi_wlast <= 1'b1; // single beat write state <= WAIT_WRITE; end WAIT_WRITE: begin if (axi_awready && axi_awvalid) begin axi_awvalid <= 1'b0; end if (axi_wready && axi_wvalid) begin axi_wvalid <= 1'b0; end if (axi_bvalid && axi_bready) begin write_done <= 1'b1; state <= IDLE; end end endcase这里刻意把写突发长度设成1,先跑通链路再说。很多初学者上来就想做64字节一次的长突发,一旦出错,很难判断是AXI侧问题还是PCIe侧问题。建议先把单拍读写调通,确认地址映射正确、链路稳定后,再去扩展突发逻辑,这个顺序省时间。
3.4 字节序和数据宽度问题
这是一块非常容易被忽略的硬骨头。AXI总线的数据位宽和PCIe链路的数据位宽不一定相等,IP核内部做了转换,但转换之后的字节序关系需要特别注意。PCIe TLP里的数据是按小端字节序传递的,而FPGA侧逻辑和AXI协议里经常是大端思维。最直观的后果就是:你写入的寄存器是0x12345678,再从下游设备读回来发现变成了0x78563412。
解决这个问题没有统一公式,最好在工程一开始就约定好数据字节的映射规则。比如把AXI数据总线的byte lane和PCIe TLP的byte lane画一张对应表,给下游设备驱动开发人员也发一份,两边按同一张表理解数据。我见过很多项目在联调阶段因为这个字节序问题来回扯皮,最后才发现是RC侧的Byte Swap配置不一样。IP核里通常提供相关配置选项,但逻辑侧捕获数据后是否要再反转,完全取决于你的业务场景,没有哪个配置是一劳永逸的。
3.5 复位与时钟树管理
RC模式对复位时序的要求比EP模式更敏感。RC要在链路上电后主动发起配置事务,这要求IP核的user_reset输出已经拉低并且内部状态机完成初始化,至少等到LTSSM进入L0以后,FPGA侧AXI逻辑才能可靠地发起访问。
官方文档里会给一个大概的复位释放时间,但这个时间依赖具体链路训练情况,不是一个固定值。我一般不是在逻辑里硬等一个固定延时,而是去读LTSSM状态寄存器,检测到L0之后再拉高“link_up”标志,业务逻辑看到这个标志才开始发AXI事务。这个做法看起来多花了一点LUT,却省掉了大量时序对齐的麻烦。时钟方面,AXI接口时钟一般由IP核输出,时钟频率等于链路速率除以某个系数。Gen3 x4链路下,AXI时钟通常是250MHz或125MHz。业务逻辑如果用到这个时钟,必须注意跨时钟域问题,不能想当然地把它当成普通时钟直接用。
4. 硬件调试实录:从链路训练到数据回读
4.1 调试环境准备
RC模式调试比EP模式更依赖硬件观测手段。EP模式好歹还有主机侧软件可以配合,RC模式下整个FPGA就是主机,没有现成操作系统和驱动帮你打印日志,所以调试手段要提前准备好。
我的调试清单是这样几项:第一,Vivado的ILA调试核,至少挂两套,一套在AXI接口上,另一套挂在IP核的调试总线上,专门看LTSSM状态和事务层错误;第二,串口调试助手配合FPGA侧UART逻辑,用来在业务层打印关键事件,比如“AXI write done”“read data=0x1234”,这个手段比ILA形象直观,适合看流程性信息;第三,如果板子上有LED,也可以临时加一些状态指示;第四,必要的PCIe协议分析仪,但这个不是人人都有,小项目可以用ILA追踪TLP层信号替代。
ILA触发条件要提前想好。调试链路训练时,触发条件是ltssm_state变化到L0;调试AXI读写时,触发条件是awvalid或者arvalid拉高。如果触发条件设置得太宽,抓回来的波形绝大多数都是无关数据,反而影响分析效率。
4.2 链路训练排查:LTSSM状态怎么看
链路训练是RC模式调试的第一道关,过不了这关,后面所有AXI逻辑都白搭。AXI Memory Mapped To PCIe IP核会暴露一组调试总线,其中ltssm_state信号就是链路训练状态机的当前状态编码。ILA抓取后,对照状态编码表就能知道链路到底卡在哪一步。
这里列一下常见的LTSSM状态编码,调试时对照参考:
| 状态编码 | 状态名 | 含义 |
|---|---|---|
| 0x0 | Detect.Quiet | 检测静默,等待链路存在 |
| 0x1 | Detect.Active | 检测接收器是否有端接 |
| 0x8 | Polling.Active | 尝试发送TS1训练序列 |
| 0x9 | Polling.Compliance | 兼容性测试 |
| 0x12 | Configuration.Linkwidth.Start | 链路宽度协商 |
| 0x13 | Configuration.Linkwidth.Accept | 确认链路宽度 |
| 0x16 | L0 | 正常工作态 |
| 0x17 | L0s | 省电状态 |
| 0x18 | L1 | 低功耗状态 |
| 0x20 | Recovery.RcvrLock | 重新训练恢复锁定 |
如果卡在Detect状态,大概率是参考时钟或者物理连接问题。先查100MHz差分时钟是否到了FPGA引脚,再看复位释放是否正确,最后确认PCIe插槽或者金手指供电正常。如果卡在Polling状态,多半是链路宽度配置和实际物理走线不一致,或者是差分信号质量太差导致对端始终收不到有效的TS1序列。如果卡在Configuration状态,通常是软件配置的链路速度与下游设备能力不匹配,可以尝试强制降速到Gen1再做链路训练。
4.3 访问下游设备配置空间的坑
链路跑到L0之后,下一步就是枚举下游设备。所谓枚举,核心是往总线地址0x00000000发Type 0配置读请求,读取下游设备的Vendor ID和Device ID。如果这个读事务能返回正常数据,说明配置空间通路是通的。
实际操作中有一个很隐蔽的坑:很多初学者在RC模式下,还没有做完整的枚举流程,就试图直接去访问下游设备的Memory BAR。此时下游设备可能还没有完成内部初始化,或者它的BAR大小还没被正确分配,你发的Memory请求根本到不了设备内部。正确做法是先通过配置读写读取设备配置空间中的Class Code、BAR寄存器的初始值,确认设备类型和资源需求,再决定要不要给设备分配新的BAR区间。
配置读的TLP类型和Memory读写不同,IP核的AXI接口并不直接暴露配置读写通道,所以官方例程里通常会在内部用一段特殊的地址区间来映射配置空间。比如把AXI高地址段映射成配置访问,具体映射关系可以在IP配置界面的地址区域看到。调试的时候一定先搞清楚这个映射关系,否则你看似在发配置请求,实际上发的可能是Memory请求,读回来的数据没有意义。
4.4 数据回读校验的思路
当AXI读写事务都能正常完成,且LTSSM稳定在L0,就到了最后一步:数据正确性验证。我的习惯是先做一个简单的回环测试,在FPGA逻辑里生成一个递增序列,写到下游设备的某段BAR空间,然后再读回来比对。如果比对一致,说明整条链路的数据通路是完整的;如果不对,再用ILA定位是写入端还是读取端出了问题。
一次基本的写入和回读校验可以这样组织:先在AXI侧发起一次写事务,写入数据0x00000001,然后发起一次读事务,读取同一个地址。若读回来不是0x00000001,需要优先检查ILA抓到的AXI总线数据与PCIe TLP数据是否一致。如果AXI层数据是对的,但读回来是0xFFFFFFFF,多半是对端设备不支持该地址访问;如果数据是错位的,则要看字节序和data width转换是否合理。
我强烈建议把校验逻辑做成小规模状态机固化到工程里,而不是每次调试都用ILA手动触发一次写读。因为手工触发的方式很难保证多次尝试的一致性,状态机固化后可以自动循环执行写读比较,把错误次数和错误地址通过串口打印出来,排错效率提升好几个量级。
4.5 常见问题速查表
下面这个表是我整理RC模式调试中比较集中的问题,按出现概率从高到低排,每一类都有对应的定位思路。
| 问题现象 | 可能原因 | 定位方法 |
|---|---|---|
| LTSSM卡在Detect | 参考时钟丢失、复位未释放 | 用示波器量100MHz时钟、查复位引脚 |
| LTSSM卡在Polling | 链路宽度配置与PCB不一致 | 对照原理图核对IP配置 |
| LTSSM反复训练进入不了L0 | 信号质量差、速率协商失败 | 降低链路速度到Gen1测试 |
| AXI读事务一直等到超时 | 目标BAR地址错误 | 先读BAR寄存器再发起Memory访问 |
| AXI写事务完成后读回全0xFF | 访问了不存在的设备地址 | 检查地址映射,确认下游设备在位 |
| 数据字节顺序错乱 | Byte Swap配置不对 | 画byte lane映射表,核对IP配置 |
| 偶尔一次读写失败 | 时钟抖动大、链路不稳定 | 跑回环压力测试,观察错误率 |
5. 几个我踩过的坑,以及给初学者的建议
5.1 最容易忽略的五个细节
第一个是参考时钟的抖动指标。PCIe对参考时钟的抖动要求比较高,如果用普通有源晶振替代100MHz差分时钟,链路训练偶尔会成功偶尔会失败,非常折磨人。接下来重点检查复位信号有没有做异步复位同步释放,没有做同步处理的复位在链路训练时会产生亚稳态问题,表象跟时钟抖动很像。第三个是AXI总线上跨时钟域处理,IP核输出的AXI时钟一旦和用户逻辑时钟不同,必须做正确的同步处理,不能简单把两个时钟域的信号直连。第四个是ILA的采样深度,PCIe TLP事务产生的大量数据可能把事件淹没,条件触发和深度设置要提前规划。第五个是版本一致性问题,Vivado版本、IP版本和下游设备的PCIe规范版本要保持兼容,太新的IP核有时会对老设备产生额外的枚举时序要求。
5.2 从官方例程到真正的工程化,还差什么
官方例程能跑通,只代表这条链路能工作,离工程化还有一段路。你需要把中断机制完善起来。RC模式下的中断处理往往比EP模式更复杂,因为你要为下游设备转发中断,还要处理MSI中断的解析。你需要把错误处理机制补齐,PCIe设备在访问出错时会返回错误完成包,但你的AXI逻辑可能只会等到超时,必须把这种状态映射成可读错误码上报,不然产品出问题很难定位。还需要考虑DMA能力。桥接IP本身不带DMA,但在实际应用中,单纯靠CPU或者状态机逐拍读写BAR空间,性能肯定不够。这时可以在AXI侧加上自己的DMA控制器,利用AXI突发能力把数据成块搬运,性能才能满足高速场景需求。
5.3 后续可以从哪几个方向继续深入
如果这块内容对你有用,我个人觉得后续值得去关注的几个方向是:一是完整的PCIe枚举流程实现,包括Config Read/Write、设备扫描、冲突仲裁,这部分做完,你对PCIe协议的理解会上升一个层次;二是在AXI侧引入DMA控制器后的性能调优,尤其要关注AXI突发长度和PCIe Max Payload Size之间的匹配关系;三是尝试对接NVMe设备,那是一个更综合的挑战,会同时涉及PCIe枚举、DMA、中断和NVMe命令集,把这条路走通,很多存储类产品的原型都可以在此基础上快速搭建。
最后再分享一个个人习惯:在开始写逻辑前,先在纸上把AXI地址、PCIe地址、配置空间地址之间的关系图画出来,贴在显示器边上。调试的时候,90%的脑力消耗都在回忆“这个地址是干嘛的”,而不是在分析问题本身。画好这张图,整个RC模式的开发难度至少降低一半。