板卡插上主机,lspci能列出设备号,可一访问 BAR 空间就总线错误;或者 DMA 搬运一切正常,DDR 里的数据却纹丝不动——这类问题,十有八九出在 XDMA 的 BAR 分配与 AXI 地址转换上。我最早调 PCIe 时,以为 XDMA IP 配好 DMA 通道就够了,直到把 BAR 空间和 AXI 接口的关系彻底理清,才意识到系统物理地址、BAR 地址、AXI 地址这三层是互相独立的,映射关系完全由配置决定。这篇文章就以 Vivado 2023 为环境,从零讲透 XDMA 配置里 PCIe BAR 与 AXI 总线的地址转换原理与实操步骤,适合刚接触 FPGA PCIe 开发、被 XDMA 的 BAR 配置和地址映射绕晕的工程师。内容会从链路原理讲到 IP 配置,再讲上板验证,最后分享几个我实际踩过的坑。
1. 先搞清楚 XDMA 在整条链路里的角色:从 PCIe 枚举到 AXI 访问
很多初学者拿到 XDMA 之后,第一反应是“这是个 DMA 控制器”,然后立刻开始配描述符、调中断。但 XDMA 实际承担的工作比“搬运数据”要多得多。要先理解它在这条数据链路里的完整位置,后面配置 BAR 和地址转换才不会抓瞎。
1.1 宿主机怎么才能访问你的 FPGA
PCIe 设备和普通内存设备不一样,主机 CPU 并不能天然地读到 FPGA 内部的寄存器或 DDR。每一次访问都要经过这样一条链路:CPU 发起一次内存映射 I/O 请求(MMIO)→ 这个请求经过 Root Complex → 经过 PCIe 交换机或直达端口 → 送到端点设备(也就是你的 FPGA → XDMA IP 接收请求 → XDMA 判断这个地址属于哪个 BAR → 再通过内部逻辑把请求转换到 AXI 总线上。
关键点在于,CPU 访问设备时使用的是“系统物理地址”,但物理地址在配置空间里并不等于设备内部的地址。PCIe 设备通过“基地址寄存器”(BAR)向系统声明自己需要多大的地址窗口,系统枚举时读这个寄存器,才知道设备要多少空间、什么类型,然后在物理地址空间里找一块空位分配给设备。分配完成之后,驱动或用户程序访问这块物理地址,本质上就是往这个设备“门牌号”对应的窗口里发请求。
很多人的误区就是觉得“物理地址等于设备地址”。实际上,系统分配给 BAR 的基地址完全由 BIOS 或操作系统决定,可能今天在 0x40000000,明天换个主板就变成 0xA0000000。设备内部不能假设一个固定值,所以 XDMA 在做 BAR 匹配时,只关心“哪个 BAR 命中了”、“命中的偏移量是多少”,再用这个偏移量作为 AXI 地址的来源。这就是地址转换的出发点。
1.2 XDMA 的两种工作模式:DMA 引擎与 AXI Bridge
XDMA 的全称是 DMA/Bridge Subsystem for PCI Express,注意“DMA/Bridge”这两个词,它天生就有两套功能:
第一套是纯 DMA 引擎。主机通过 BAR 里的控制寄存器,填写描述符环形缓冲区,XDMA 按照描述符启动 H2C(Host to Card,主机到 FPGA)或 C2H(Card to Host,FPGA 到主机)搬运。这套机制适合大批量数据传递,因为数据不需要 CPU 逐个地址搬运,CPU 只需准备好描述符,剩下的交给 XDMA 硬件完成。
第二套是 AXI Bridge,也就是桥接功能。当系统分配的物理地址落在某个配置为“AXI Bridge”的 BAR 区域内时,XDMA 不会启动 DMA 引擎,而是把这次 MMIO 读写转换成一次 AXI 总线访问,直接去读写 FPGA 侧挂载的 BRAM、寄存器或 DDR。这套机制适合小批量控制操作,比如读写配置寄存器、查询状态、点几个 LED。
实际项目里,这两套功能通常是同时开的:用一个 BAR 做寄存器访问(AXI-Lite),用另一个 BAR 给用户逻辑提供 AXI 接口或专门给 DMA 控制寄存器用。你只有把“什么时候走 DMA 引擎、什么时候走 AXI Bridge”这个区别搞明白,才能理解为什么 XDMA 的 BAR 配置里,每个 BAR 可以单独选择接口类型。
2. PCIe BAR 空间拆解:为什么 BAR 配错,后面全白搭
BAR 是配置空间里最关键的字段,但它又是最容易被人忽略的部分。很多人配 XDMA 时,看到 BAR0、BAR1、BAR2 几个选项卡,随便填个 64K 就生成工程,结果上板后要么设备无法识别,要么识别了但访问就崩溃。要避免这种情况,得先从 BAR 的机制看起。
2.1 BAR 的本质:配置空间里的一段“门牌声明”
PCIe 配置空间是一段 4KB 的存储区域,系统枚举时通过读配置空间来了解设备能力。BAR 位于配置空间偏移 0x10 到 0x24,每个 BAR 占 4 字节(64 位 BAR 占两个槽位)。BAR 寄存器里包含了三个核心信息:
- 地址窗口大小:硬件设计时固定,软件读 BAR 时低几位是只读的 0,系统通过写全 1 再读回,就能算出窗口大小。比如一个 4KB 的 BAR,低 12 位读回来全是 0,系统就知道大小是 4KB。
- 窗口类型:是可预取的存储器(Prefetchable Memory)还是不可预取的存储器,是 32 位地址还是 64 位地址。XDMA 的 BAR 通常配成 64 位可预取,但这会让驱动处理复杂一些,也有很多人故意配成 32 位来简化。
- 分配结果:系统枚举完成后,会把实际的基地址写进 BAR,这个地址是系统物理地址,不是设备内部地址。
你可以把 BAR 理解成“门牌号”:设备对系统说“我需要一块这么大的门牌区域”,系统说“行,我给你分到这条街这个号”。此后设备收到的所有 MMIO 请求,只要地址落在“门牌区域”里,设备就得自己判断这个请求是给哪个内部功能的。
XDMA 支持 4 个独立的 BAR,但实际使用中常见的只有 BAR0 和 BAR2。因为前两个 BAR 经常被用作控制寄存器,后两个留给用户自定义。如果四个 BAR 都用 64 位寻址,配置空间会被占满,所以推荐按需开启。
2.2 XDMA 的 BAR 分配策略与地址转换的切入点
在 XDMA IP 配置界面里,BAR 类型和大小是可以逐项选择的。常见的分配方案是:
| BAR | 推荐大小 | 接口类型 | 用途 |
|---|---|---|---|
| BAR0 | 64KB | AXI4-Lite | 用户寄存器访问,小数据量控制 |
| BAR1 | 不使用或 1MB | AXI4 | 用户 AXI 数据接口,可挂 DDR |
| BAR2 | 4KB 或 64KB | AXI4-Lite | XDMA 控制/状态寄存器,驱动操作 |
| BAR3 | 可选 | 通常不使用 | 扩展用户空间 |
需要特别注意:BAR0 和 BAR2 在 XDMA 内部有固定分工。BAR0 是用户侧配置寄存器入口,BAR2 是 DMA 引擎控制寄存器的入口。如果把 DMA 控制寄存器放到别的 BAR,驱动默认是找不到的,除非你自己写驱动并修改地址映射。
地址转换的切入点就是“BAR 命中之后,XDMA 如何产生 AXI 地址”。XDMA 内部有一个 Target BAR 解码逻辑,它把收到的 PCIe 地址拆成两部分:高位代表命中的目标接口,低位代表窗口内的偏移。比如 BAR2 的窗口基址是 0x40000000,窗口大小 4KB,那么主机访问 0x40000010 时,命中的接口是 BAR2,偏移是 0x10。这个偏移会被直接用来作为 AXI 总线上的低地址,或者加上一个可配置的偏移量。理解了这一层,再看 IP 里的地址映射选项就不会懵。
3. 在 Vivado 2023 里一步步把 XDMA 拖进 Block Design
Vivado 2023 的 IP Catalog 里,XDMA 的全称是 DMA/Bridge Subsystem for PCI Express(PCIe),搜索 XDMA 也能找到。如果是做 Block Design 流程,直接把 IP 拖到原理图里,然后双击打开配置界面。这里我会按配置页顺序逐项说明,每个选项背后是什么用途、改错了会有什么后果。
3.1 IP 核版本与工程准备
我用的环境是 Vivado 2023.1,XDMA 版本是 4.1。器件选的是 UltraScale+ 系列,PCIe Block 用 Gen3 x4。工程准备阶段有三件事要确认:
- 工程的目标器件必须带 PCIe Hard Block,Zynq UltraScale+、Kintex UltraScale+、Versal 都有,纯逻辑器件如 Artix-7 也有 PCIe Block,但数量和性能不同。
- 时钟输入:XDMA IP 需要一个 100MHz 的参考时钟,通常来自板卡上的 PCIe 参考晶振或连接器提供的 REFCLK。Vivado 只负责把时钟接进 IP,外部晶振必须实际存在。
- 复位信号:PCIe 的 PERST# 信号要接到 FPGA 引脚,或者由专用复位管理芯片产生。如果板子上没有 PERST#,系统枚举时设备可能处于不确定状态。
这三件事往往比 IP 配置本身更容易让人卡住。尤其是 PERST#,我见过有人直接在 Vivado 里把它绑定到普通 GPIO 上,结果每次冷启动设备都不稳定。正确做法是用 PCIe 连接器的 PERST# 走缓冲后进 FPGA。
3.2 主要配置项逐项说明
打开 XDMA 配置界面后,左边是一列配置页签,右边是参数设置。几个关键页签如下:
PCIe ID 配置页:填写 Vendor ID、Device ID、Subsystem ID。默认的 Vendor ID 是 Xilinx(0x10EE),Device ID 建议改成自己的编号,便于用
lspci识别。比如我常用 0x9038,或者根据驱动要求设置。PCIe 链路配置页:选择 Lane Width 和 Maximum Link Speed。调试阶段建议选 x4 Gen3,不仅时序容易收敛,带宽也足够测试。如果板卡只引出 x1,配 x4 不会导致功能错误,只是无法跑满速,但建议与板卡实际走线宽度一致。
Mode 配置页:有 Basic 和 Advanced 两种。Basic 模式只暴露 DMA 引擎,Advanced 模式会多出 AXI Bridge、Unified BAR 等选项。需要访问用户 AXI 接口或做地址转换时,必须选 Advanced。
DMA 配置页:选择接口类型。通常把 C2H 和 H2C 通道数都设为 1,接口选 AXI Memory-Mapped(AXI4)。这里还有一个 AXI Data Width 选项,PCIe Gen3 x4 建议选 128 位或 256 位。位宽越大,内部 FIFO 越大,时序收敛难度也越高。调试期选 128 位最稳妥。
中断配置页:一般选 MSI-X,支持多个中断向量,可以把不同通道的中断区分开。INTx 信号是电平中断,在共享中断场景下容易互相干扰,能不用就不用。
以下是我常用的一组配置:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| Mode | Advanced | 开启 Bridge 和地址转换选项 |
| PCIe Lane Width | x4 | 根据板卡实际决定 |
| Max Link Speed | Gen3 | 8GT/s,稳定 |
| VDID | 0x10EE,Device ID 0x9038 | 自定义 |
| H2C / C2H Channels | 1 / 1 | 默认够用 |
| DMA Interface | AXI4 | 非 Stream 模式 |
| AXI Data Width | 128 | 平衡性能与布线压力 |
| Global Interrupt | MSI-X | 便于多通道区分 |
| BAR0 | 64KB,AXI4-Lite | 用户寄存器 |
| BAR2 | 4KB,AXI4-Lite | DMA 控制寄存器 |
3.3 AXI 接口与 DMA 通道选择
配置完基本参数后,需要连接 AXI 接口。XDMA 生成后通常有以下几个对外接口:
axi_aclk和axi_aresetn:AXI 总线时钟和复位,必须接到你用户逻辑的时钟域。很多人把复位直接接地或不接,结果 AXI 接口完全无法工作。s_axi_ctl:来自 BAR2 的控制接口,驱动通过它访问 DMA 控制寄存器。m_axi(带m_axi_aw、m_axi_ar等信号):用户数据接口,也就是 DMA 引擎访问 FPGA 内存空间的通道。axi_ctl_aclk:控制接口的独立时钟,可以不同步于主 AXI 时钟,但频率范围有约束,建议与axi_aclk同源。m_axil:如果 BAR0 配置成 AXI4-Lite,这是用户寄存器访问接口。
DMA 通道数不需要多,H2C 和 C2H 各自一个通道,配合 MSI-X 中断,已经能覆盖绝大多数场景。通道数越多,描述符缓冲区和中断逻辑越复杂,驱动也要做更多配置,不建议一开始就开 4 通道。
4. 地址转换的实操核心:BAR 掩码、AXI 偏移与示例配置
好了,IP 已经生成,接下来是重点中的重点:地址转换。这一步做不对,前面都是白干。Xilinx 的文档里对这部分写得比较抽象,但结合实际调试,逻辑其实很清晰。
4.1 XDMA 地址转换的寄存器级逻辑
当一个 PCIe MMIO 请求到达 XDMA 后,地址转换分三步:
第一步,BAR 匹配。XDMA 内部有 BAR 掩码寄存器,用来判断请求地址落在哪个 BAR 窗口内。这一步是硬件完成的,通常是“大于等于基址且小于基址加窗口大小”。
第二步,地址截位。由于 BAR 窗口的基址是系统分配的,请求地址减去窗口基址,得到窗口内偏移量。这个偏移量决定了要访问 XDMA 内部哪个功能。
第三步,AXI 重映射。XDMA 把上一步得到的偏移量,作为 AXI 总线上的地址(或加上一个目标 AXI BAR 偏移),生成AWADDR/ARADDR,同时根据访问方向生成ARPROT、AWSIZE等控制信号。
这三步里面最容易被忽略的是第三步。假设你在 Block Design 里把 XDMA 的m_axi接口接到了 AXI Interconnect,Interconnect 的 Address Editor 给这个接口分配了一个基地址,比如0x00000000,那么整个 AXI 访问的最终地址就是这个基地址加上偏移量。如果 XDMA 的地址转换把偏移量直接作为 AXI 地址,而 AXI Interconnect 又把 0x00000000 基址下的窗口映射到 DDR 控制器,那么最终就能命中 DDR。
但如果你在 Address Editor 里给 XDMA 的m_axi接口分配了0x20000000,而 XDMA 内部还保留了某个“目标 AXI BAR 偏移”,就得仔细看这个偏移加在哪个环节。常见错误是两边各加了一遍,导致最终 AXI 地址指向了你根本没接外设的空区域,访问直接超时。
4.2 一个实际映射案例:BAR2 到 AXI 地址空间的换算
我以一个实际工程为例。假设板卡上有一颗 DDR4,挂在 AXI4 总线上,起始地址为 0x00000000,容量 256MB。XDMA 的m_axi接口通过 AXI Interconnect 连接到 DDR 控制器,Address Editor 给m_axi分配了基地址 0x00000000。
此时 BAR2 窗口大小是 4KB,系统枚举后分配基地址为 0x4A000000。当用户程序向物理地址 0x4A000008 写入数据时,XDMA 的流程是:BAR2 命中 → 偏移量 = 0x4A000008 - 0x4A000000 = 0x8 → AXI 地址 = 0x00000000 + 0x8 = 0x00000008 → DDR 控制器收到对 0x00000008 的写请求。
所以,当 BAR 窗口足够小、并且 AXI Interconnect 基地址设为 0 时,BAR 偏移量就等于最终的 DDR 地址。这是最简单的映射方式,也是调试阶段尽量采用的配置。
如果 DDR 的起始地址不是 0,比如 0x10000000,那么主机访问 BAR 偏移 0 时,AXI 地址会变成 0x10000000 + 0 = 0x10000000,刚好命中 DDR 的起始位置。如果 AXI Interconnect 的基地址分配错误,把 XDMA 接口分到了 0x20000000,那么同样的 BAR 偏移 0 会访问 0x20000000,DDR 根本收不到请求。排查这类问题时,可以用 ILA 核抓 XDMAm_axi口的AWADDR和WVALID,看看 AXI 地址到底是多少,再倒推是地址编辑器分配问题,还是 XDMA 地址转换逻辑的问题。
4.3 UBAR 模式与多个 BAR 的取舍
Vivado 2023 的 XDMA 高级模式里有一个 Unified BAR(UBAR)选项。开启后,多个 BAR 的需求会被合并成一个连续的大 BAR。比如原来 BAR0 64KB、BAR1 1MB、BAR2 4KB,合并后可能是一个 1MB 左右的大 BAR,内部通过固定偏移区分功能。
UBAR 的优点是只占一个 BAR 资源,对驱动和系统兼容性更友好;缺点是内部地址布局是固定的,如果后续要调整某个窗口大小,可能需要改动 IP 配置并同步改动软件。对于通用开发板,我建议默认不开启 UBAR,按标准多 BAR 配置开发。理由有两条:第一,Xilinx 官方驱动的默认行为就是标准多 BAR;第二,做调试时,用lspci能清楚看到每个 BAR 的地址,方便快速定位问题。
如果产品对 ACPI、SR-IOV 等特殊场景有要求,UBAR 可以减小配置空间压力,但那是后话,不展开。
5. 上板验证:枚举、BAR 资源核对与寄存器回读
配置完成、生成 Bitstream 并下载到板卡后,第一步不是跑 DMA,而是先验证系统能否正确枚举设备、BAR 是否正确分配。这一节的内容在 Linux 环境下完成,Windows 环境思路类似,只是工具不同。
5.1 Linux 下确认枚举和 BAR
插上板卡后,先执行lspci找到设备:
lspci | grep Xilinx如果什么都没看到,先查物理链路:lspci -tv看看总线拓扑里有没有设备,再确认 PERST# 和 REFCLK 是否正常。如果能看到设备,但出现在?设备类型下,说明配置空间里的 Class Code 有问题,需要回 Vivado 检查。
确认设备号后,用详细模式查看 BAR:
lspci -s 02:00.0 -v输出里会包含 “Region 0”、“Region 2” 等信息,例如:
Region 0: Memory at 4a000000 (64-bit, prefetchable) [size=64K] Region 2: Memory at 4a010000 (64-bit, prefetchable) [size=4K]这一步要核对两件事:
- 窗口大小是否与 Vivado 配置一致。如果 BAR2 配了 4KB,而这里显示 64KB,说明 BAR 掩码或者配置页面里的“Enable BAR Expansion”选项被改过。
- 地址类型是否可预取。如果显示
non-prefetchable,DMA 驱动在申请一致性内存时可能会行为异常。
确认 BAR 后,可以用devmem或者写一个简短的字符设备驱动来读取 BAR 地址。比如 BAR2 的物理地址是0x4a010000,读取 DMA 控制寄存器中的 ID 寄存器(偏移 0x1000):
devmem 0x4a011000 32读到的值应该是一个非零的 32 位数字,通常是 IP 核心的版本号和 ID。如果读回来的数据全是0xFFFFFFFF,说明访问没有命中 XDMA 逻辑,大概率是地址转换配置有问题。
5.2 用数据通路验证地址转换
寄存器能读通,只能说明 BAR 这个“门”打开了,AXI 地址转换是否真的正确,还需要通过数据通路验证。
最简单的验证方式:在 FPGA 侧放一个简单的 AXI4-Lite 寄存器,映射到 BAR0。通过devmem写一个值,再通过 ILA 核观察 AXI 总线上的AWADDR和WSTRB。如果地址和你要写的内容一致,说明 BAR0 的 AXI Bridge 路径没问题。
对于 DMA 路径,推荐先用 Xilinx 官方驱动跑一遍自带测试。加载驱动后,会生成/dev/xdma0_h2c_0、/dev/xdma0_c2h_0等设备节点。向h2c节点写入数据,再通过c2h节点读回,对比数据是否一致。如果数据搬运成功,说明描述符、DMA 引擎、AXI 地址都正确。如果搬运超时或数据全零,重点查两个地方:一是描述符环形缓冲区中的地址是否指向了你能访问的 AXI 空间;二是m_axi接口连接的 DDR 控制器地址范围是否与描述符中的目标地址一致。
我在调试时习惯同时开一个 ILA 核,抓m_axi接口的AWADDR、AWVALID、AWREADY,这样只要 DMA 一启动,就能立刻看到它去访问哪个 AXI 地址。这个信息比单纯读寄存器直观得多。抓不到波形的话,优先看axi_aclk有没有输出,复位是不是一直拉低,再检查逻辑设计里对复位信号的处理。
6. 我踩过的坑和排查思路
XDMA 的 BAR 和地址转换问题,表面现象五花八门,但根因往往就那几种。我把这几年调试中遇到的典型问题整理出来,按排查优先级列一张表:
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| lspci 看不到设备 | PERST# 异常、参考时钟丢失、链路握手失败 | 检查硬件信号,抓 Link Up 状态,查 PCIe 配置空间 Capabilities |
| 枚举正常但访问 BAR 崩溃 | BAR 类型设置错误、64 位 BAR 被系统分配到高位地址,驱动用 32 位指针访问 | 用 lspci 看 BAR 地址,核对地址位宽,调整 BAR 类型为 32 位或改驱动 |
| BAR 大小与配置不符 | BAR Expansion 选项和掩码设置错误 | 回到 Vivado 检查 BAR 页面,重新生成 IP |
| devmem 读寄存器全 FF | AXI 接口没有接对、AXI 时钟悬空、复位拉住 | 抓 ILA,检查 axi_aclk 和 axi_aresetn |
| DMA 搬运超时 | 描述符地址设置在不可访问空间、Ring Base 写错、目标 AXI 地址超出 DDR 范围 | 打印驱动寄存器,核对 Ring 地址和目标地址,用 ILA 抓 m_axi |
| 搬运成功但数据错位 | AXI 数据位宽与主机字节序不匹配、地址对齐错误 | 核对 128 位 AXI 数据的字节通道,确认低两位地址处理逻辑 |
6.1 BAR 窗口大小与 AXI 空间不匹配
这是我最早踩的坑。当时把一个 BAR 配成 64MB,但 AXI 侧实际只挂了 1MB 的 BRAM。系统枚举成功后,驱动向 BAR 高地址区域写数据,XDMA 把它转换成 AXI 访问,结果访问到了根本不存在的地址,Read 请求直接挂死,后续所有寄存器访问都卡住。
后来总结出来的教训是:BAR 窗口大小必须小于等于实际 AXI 地址空间大小,最好只留一点点余量。如果你只挂 1MB BRAM,BAR 就配 1MB;如果挂 256MB DDR,BAR 可以配 256MB 以上,但要注意驱动或系统访问时不要越界。BAR 太大不会导致枚举失败,但会导致访问空洞时挂死,而这类问题往往比枚举失败更难查。
6.2 AXI 地址总线位宽差异导致的“访问不到 DDR”
XDMA 的m_axi接口有 32 位和 64 位地址宽度可选。如果选择 64 位 AXIL,而 AXI Interconnect 只接了 DDR 控制器的一部分地址,比如从 0x00000000 到 0x0FFFFFFF(256MB),那么访问地址高位清零的情况下,一切正常。但一旦系统把 BAR 分配到了 0x40000000,XDMA 的 AXI 地址就会带上高位,Interconnect 接受的地址范围需要覆盖 0x40000000,否则路由不到 DDR。
这个问题在 Block Design 里非常隐蔽,因为 Address Editor 通常只显示偏移量,不显示整个地址总线。排查方法是直接把m_axi接口的地址宽度改成 32 位,并确认地址转换时高位被正确忽略。如果必须用 64 位,就要在 Address Editor 里明确设置 AXI Interconnect 的从端口基地址,确保与 BAR 窗口匹配。
6.3 描述符地址与 AXI 地址混淆
运行 Xilinx 官方驱动时,驱动会分配一块一致内存作为描述符环形缓冲区,并把它的物理地址写入 DMA 控制寄存器。注意,这个物理地址是宿主机的物理地址,不是 AXI 地址!有些人不理解这一点,想当然地把它换成 DDR 地址,结果 XDMA 从错误的地方读取描述符,直接进入错误状态。
在 Linux 下,可以通过/sys/kernel/debug/xdma/xdma0之类的 debugfs 接口查看寄存器值,核对 Ring Base Address 是否正确。如果发现写入的地址不是 Linux 分配的一致内存地址,那就要检查驱动版本和配置参数。官方驱动里通常有一个desc_buf和addr的概念,不要在用户态代码里手动改写这个值。
这段调试经历让我养成一个习惯:所有与 XDMA 有关的内存地址,我都会在纸上画一张三层地址映射图,从上往下分别是系统物理地址、PCIe BAR 地址、AXI 地址。每次改动地址参数之前,先在这张图上标注清楚,再动手改配置。这个习惯救了我很多次,尤其是当系统 BIOS 给 BAR 分配的位置不稳、不同机器跑出来的 BAR 地址不一样时,提前画好图能快速定位到底是硬件驱动问题,还是地址转换问题。
如果你正准备上板调试 XDMA,我的建议是:先跑通最简单的寄存器读写,再做 DMA 搬运;先不挂 DDR,只挂一块小 BRAM 验证地址通路,再逐步扩展到 DDR。每加一层地址转换,就多一重潜在问题,一步一步来,比一次想全所有配置要省时间得多。