ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA当Root Complex:AXI Memory Mapped To PCIe IP核配置与调试全流程

FPGA当Root Complex:AXI Memory Mapped To PCIe IP核配置与调试全流程 干了这么多年FPGAPCIe这个方向有点意思。大多数开发者第一次接触PCIe都是拿FPGA当EndpointEP挂到电脑主板上做个采集卡、加速卡什么的。真正把身份反过来让FPGA做Root ComplexRC去主动访问别的PCIe设备很多人可能只在文档里见过。标题里这个“AXI Memory Mapped To PCIe”就是干这个事的IP核我这次想把它在RC模式下的完整流程理一遍从Vivado里搭工程、配IP到写AXI侧逻辑再到上板调试链路训练和读写通路。这篇东西适合已经跑过PCIe EP基础工程、熟悉AXI总线但没怎么碰过RC模式的人看。内容比较多我把配置步骤和调试坑都摊开讲。1. 为什么需要FPGA当Root ComplexRC模式与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模式EndpointRC模式Root ComplexFPGA角色被动外设主动主机事务发起方主机侧CPUFPGA内部逻辑配置空间暴露自己的配置空间负责配置下游设备总线枚举不需要必须实现或借助IP逻辑地址映射BAR由主机分配通过AXI地址访问下游BAR调试重点响应TLP、完成读写链路训练、配置访问、AXI超时典型应用采集卡、加速卡嵌入式平台扩展PCIe设备、数据中转1.2 RC模式解决了什么真实工程问题这里说几个真实场景大家感受一下RC模式到底什么时候用得上。第一个场景是嵌入式和异构平台上外接PCIe设备。比如基于FPGA的主控板想接NVMe SSD、PCIe万兆网卡或者专用的视频采集卡。此时板上没有x86 CPU也没有现成的PCIe Root ComplexFPGA只能自己顶上去。第二个场景是实验室里要模拟一个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 IPXDMA搞混。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及以上版本同时确认器件选型。AmdXilinx在Versal和UltraScale系列里对这个IP的支持最完整7系列也能用但License和IP版本功能上会有些差异。工程创建时语言选Verilog目标器件根据手头的板子来如果是自制板一定要确认PCB上的PCIe参考时钟、复位和供电都引到了FPGA相应引脚。打开Vivado后在IP Catalog里搜索“AXI Memory Mapped To PCIe”能看到Gen2、Gen3等不同速度等级对应的IP版本。选型时主要看链路速率要求Gen2对应5GT/sGen3对应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厂商IDDevice 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位地址最多4GB64位地址就是大地址空间。数据位宽常见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 TLP3.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 3d0, AR_ISSUE 3d1, WAIT_RDATA 3d2, DONE 3d3; 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 2d0; rd_addr 32h0; end else begin case (state) IDLE: begin if (start_read) begin rd_addr start_addr; axi_araddr start_addr; axi_arvalid 1b1; axi_arlen 4d3; // 4 beats state AR_ISSUE; end end AR_ISSUE: begin if (axi_arready axi_arvalid) begin axi_arvalid 1b0; state WAIT_RDATA; beat_cnt 2d0; end end WAIT_RDATA: begin if (axi_rvalid axi_rready) begin data_buf[beat_cnt] axi_rdata; beat_cnt beat_cnt 1b1; if (beat_cnt 2d3 || axi_rlast) begin state DONE; end end end DONE: begin read_done 1b1; 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 1b1; axi_wdata write_data; axi_wvalid 1b1; axi_wlast 1b1; // single beat write state WAIT_WRITE; end WAIT_WRITE: begin if (axi_awready axi_awvalid) begin axi_awvalid 1b0; end if (axi_wready axi_wvalid) begin axi_wvalid 1b0; end if (axi_bvalid axi_bready) begin write_done 1b1; 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 data0x1234”这个手段比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状态编码调试时对照参考状态编码状态名含义0x0Detect.Quiet检测静默等待链路存在0x1Detect.Active检测接收器是否有端接0x8Polling.Active尝试发送TS1训练序列0x9Polling.Compliance兼容性测试0x12Configuration.Linkwidth.Start链路宽度协商0x13Configuration.Linkwidth.Accept确认链路宽度0x16L0正常工作态0x17L0s省电状态0x18L1低功耗状态0x20Recovery.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模式的开发难度至少降低一半。
返回列表