ARTICLE DETAIL

资讯详情

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

FPGA中AXI DataMover实现DDR高效读写:配置、调试与实战经验

FPGA中AXI DataMover实现DDR高效读写:配置、调试与实战经验 1. 为什么DDR读写值得单独拎出来讲做过FPGA项目的人都有一个共识逻辑本身不难难的是数据搬得动、搬得快、搬得稳。尤其是涉及DDR的场合很多项目在仿真阶段跑得漂漂亮亮一上板就发现带宽上不去、数据对不上、时序收不紧。问题的根源往往不在算法本身而在于数据搬运这条链路没有设计好。AXI DataMover就是Xilinx给开发者准备的一把“数据搬运专用工具”。它不像AXI DMA那样需要CPU频繁干预也不像自己手写状态机那样容易踩坑。DataMover的定位很明确在AXI4内存映射和AXI4-Stream之间做高速数据搬运支持命令驱动的传输模式可以做到几乎不占用处理器资源的情况下完成大块数据的读写。这篇文章面向的是已经有一定FPGA基础、正在做DDR相关项目的开发者。不管你是做图像处理、高速数据采集还是做通信基带的缓存调度只要涉及到DDR的高效读写DataMover都是一个值得认真掌握的IP。我会从整体设计思路讲起把核心细节拆开再给出Vivado中的完整配置步骤和实际调试中会遇到的问题。内容基于我在多个项目中的实际使用经验不是照搬文档而是把踩过的坑和总结出来的技巧一并分享出来。2. 整体设计思路与方案选型2.1 为什么选DataMover而不是DMA先把这个事情说清楚。Xilinx生态里做DDR读写搬运常见的选择有三种AXI DMA、AXI DataMover、自己写AXI Master状态机。三者各有适用场景。AXI DMA适合什么适合CPU主导的、传输描述符比较复杂的场景。它内部有描述符管理、中断控制、Scatter-Gather等功能用起来方便但资源占用大而且对于纯FPGA逻辑内部的数据流搬运来说很多功能是冗余的。自己写AXI Master状态机呢灵活是灵活但开发周期长容易在突发长度、outstanding事务、握手时序上出问题。尤其是要跑到高带宽的时候手写状态机很难把AXI的流水线效率吃满。DataMover的定位在两者之间。它不需要CPU参与命令下发当然你也可以用CPU下发命令支持AXI4-Stream到AXI4内存映射的双向转换命令接口简单清晰支持几乎所有的AXI突发类型。最关键的是它的吞吐量可以做到接近理论带宽资源占用却比DMA小得多。我实测过的一个场景在Zynq 7020上用DataMover做DDR到Stream的连续读取时钟200MHz数据位宽64bit实际带宽能稳定跑到1.2GB/s以上而逻辑资源只用了不到2000个LUT。同样的场景换成AXI DMALUT要多出将近一倍。2.2 DataMover的核心架构理解DataMover的内部结构可以分成三个部分来理解命令接口、数据通路、状态反馈。命令接口负责接收传输指令。每条命令包含源地址、目的地址、传输字节数、传输类型读还是写等信息。命令通过一个简单的Valid-Ready握手接口写入DataMover内部有命令FIFO做缓冲。数据通路是核心。对于写操作Stream到Memory数据从S_AXIS接口进来经过内部FIFO和位宽转换通过M_AXI接口写入DDR。对于读操作Memory到Stream数据从M_AXI读回来经过FIFO缓冲从M_AXIS接口输出。状态反馈包括命令FIFO的状态、数据FIFO的状态、以及传输完成的中断或标记信号。这些信号对于流控和调试非常关键。有一个容易混淆的点DataMover的M_AXI接口是作为Master存在的它主动发起对DDR的读写。而命令接口和数据接口都是作为Slave存在的接受外部逻辑的控制。理解这个主从关系对后续的地址映射和时序约束很重要。2.3 带宽估算与参数选择在动手配置之前先算一笔账。带宽估算决定了你选什么位宽、什么时钟频率、什么突发长度。基本公式是带宽 时钟频率 × 数据位宽 × 传输效率。传输效率这个因子很关键。理论上AXI的传输效率可以接近100%但实际中因为握手开销、DDR刷新、bank切换等原因能到70%到85%就算不错了。DataMover因为内部有FIFO缓冲和命令预取机制效率通常比手写状态机高。举个例子。如果你的DDR控制器跑在200MHz数据位宽64bit理论带宽是200M × 8Byte 1.6GB/s。考虑80%的效率实际可用带宽大约1.28GB/s。如果你需要连续写入1MB的数据耗时大约是1MB / 1.28GB/s ≈ 780微秒。这个估算决定了你的FIFO深度。如果上游数据是突发的FIFO要能缓冲足够的数据来平滑带宽波动。我通常建议FIFO深度至少能缓冲2到4个最大突发长度的数据。位宽选择上DataMover支持8到1024bit的数据位宽。但不是越宽越好。位宽越宽资源占用越大时序越难收敛。一般64bit或128bit是比较平衡的选择。如果你的DDR控制器是32bit位宽的DataMover的位宽设成32bit或64bit都可以设成64bit的话DataMover内部会做位宽转换。3. 核心细节解析与实操要点3.1 命令格式与地址对齐DataMover的命令格式是一个64bit或72bit的向量取决于是否使能了地址扩展。以64bit命令为例各字段的分布是这样的字段位宽说明TAG4bit命令标签用于区分不同传输SADDR32bit源地址DADDR32bit目的地址BTT23bit传输字节数TYPE4bit传输类型EOF1bit是否在传输结束时置位TLAST这里有一个非常容易踩的坑地址对齐。DataMover要求传输的起始地址必须与数据位宽对齐。如果你的数据位宽是64bit8字节那么起始地址必须是8的倍数。如果地址没有对齐DataMover不会报错但传输的数据会错位读出来的数据整体偏移几个字节非常隐蔽。我在一个图像处理项目里就遇到过这个问题。图像行的起始地址是奇数结果读出来的图像整体偏移了一个像素查了两天才发现是地址对齐的问题。后来在命令下发之前加了一个对齐检查逻辑确保地址是8字节对齐的。BTT字段是传输字节数最大支持8MB2的23次方。如果你的传输量超过8MB需要拆成多条命令。拆命令的时候要注意每条命令的传输量最好是2的幂次或者至少是突发长度的整数倍这样效率最高。3.2 突发长度与Outstanding配置DataMover的M_AXI接口支持的最大突发长度是256。这个参数在IP配置界面里可以设置但实际能跑多少取决于你的DDR控制器和互联矩阵。突发长度设得越大传输效率越高因为地址阶段的 overhead 被摊薄了。但突发长度太大也有问题一是FIFO深度要跟着加大二是如果传输过程中被打断恢复的代价更高。我的经验是如果DDR控制器支持突发长度设成128或256。如果系统里还有其他Master在竞争DDR带宽突发长度设成64或128更稳妥避免长时间占用总线导致其他Master饿死。Outstanding配置是另一个关键参数。它决定了DataMover能同时发出多少个未完成的AXI事务。Outstanding数越大流水线越深带宽越高。但同样资源占用也越大而且如果DDR控制器的接受能力有限设太大也没用。在Vivado的DataMover配置界面里有一个“Max Outstanding”参数。我一般从4开始试如果带宽不够再往上加。实测在Zynq 7020上Outstanding设成8的时候带宽比设成4提升了大约15%。再往上加提升就不明显了。3.3 FIFO深度与流控设计DataMover内部有命令FIFO和数据FIFO。命令FIFO的深度决定了你能预取多少条命令数据FIFO的深度决定了能缓冲多少数据。命令FIFO深度一般设成4到16就够了。如果你的命令是连续下发的深度大一点可以减少命令下发的等待时间。但命令FIFO太深也有风险如果前面的命令出了问题后面预取的命令可能已经执行了导致错误扩散。数据FIFO深度要根据你的数据流特性来定。如果上游数据是均匀的FIFO深度可以小一点。如果上游数据是突发的FIFO深度要能吸收突发。一个简单的估算方法FIFO深度 最大突发长度 × 2。流控设计上DataMover提供了几个关键信号s_axis_s2mm_tready、m_axis_mm2s_tvalid、cmd_wdt_last等。你需要根据这些信号来设计上游和下游的握手逻辑。有一个细节DataMover的m_axis_mm2s_tvalid信号在数据FIFO快空的时候会拉低。如果你下游的逻辑对数据连续性要求很高需要在DataMover和下游之间再加一级FIFO做缓冲。3.4 时钟域与复位处理DataMover支持独立的时钟域。命令接口、S_AXIS、M_AXIS可以跑在一个时钟域M_AXI可以跑在另一个时钟域。这个特性在跨时钟域的场景下非常有用。但跨时钟域也带来了额外的复杂性。DataMover内部有异步FIFO做时钟域转换但异步FIFO的深度是有限的。如果两个时钟域的频率比太大或者数据突发太猛异步FIFO可能会溢出。我的建议是如果两个时钟域的频率比不超过2:1可以直接用DataMover内部的异步FIFO。如果频率比更大最好在外部再加一级同步FIFO。复位处理上DataMover的复位信号需要同步到各自的时钟域。aresetn是低电平有效的异步复位但内部会做同步处理。需要注意的是复位释放的时候要确保命令FIFO和数据FIFO都是空的否则可能会有残留数据导致传输错误。4. Vivado中的完整配置与实操步骤4.1 IP核添加与基本参数设置打开Vivado在Block Design中点击“Add IP”搜索“DataMover”双击添加。第一步是设置数据位宽。在“Basic”标签页里找到“Data Width”参数。这个参数决定了S_AXIS和M_AXIS的数据位宽。根据前面的带宽估算我一般设成64或128。如果你的DDR控制器是32bit的设成32也可以但带宽会受限。第二步是设置命令位宽。在“Command”标签页里有“Command Width”参数。如果地址空间不超过4GB用默认的64bit就够了。如果地址空间超过4GB需要使能地址扩展命令位宽变成72bit。第三步是设置突发长度。在“Basic”标签页里找到“Max Burst Length”参数。这个参数决定了M_AXI接口单次突发的最大长度。根据前面的分析设成128或256。第四步是设置Outstanding。在“Basic”标签页里找到“Max Outstanding”参数。从4开始根据带宽需求调整。4.2 接口连接与地址映射DataMover的接口比较多连接的时候要理清楚。M_AXI接口需要连接到DDR控制器或者互联矩阵。在Block Design中点击“Run Connection Automation”Vivado会自动帮你连接M_AXI到Zynq的HP端口或者AXI Interconnect。S_AXIS接口是写数据入口。你需要把上游的数据源连接到这个接口。注意S_AXIS的tready信号是DataMover输出的tvalid和tdata是上游输入的。M_AXIS接口是读数据出口。你需要把下游的数据消费者连接到这个接口。M_AXIS的tvalid和tdata是DataMover输出的tready是下游输入的。命令接口需要连接到你的命令生成逻辑。命令接口的握手协议是Valid-Ready命令数据在tvalid和tready同时为高的时候被写入。地址映射方面DataMover的M_AXI接口会出现在Address Editor中。你需要给这个接口分配地址段。地址段的大小取决于你的DDR容量。比如DDR是512MB地址段就分配512MB。4.3 命令生成逻辑的实现命令生成逻辑可以用状态机实现也可以用简单的计数器加查找表。下面是一个用Verilog实现的简单命令生成器的核心代码// 命令生成状态机 localparam IDLE 2b00; localparam SEND 2b01; localparam WAIT 2b10; reg [1:0] state; reg [63:0] cmd_data; reg cmd_valid; always (posedge clk) begin if (!rst_n) begin state IDLE; cmd_valid 1b0; cmd_data 64d0; end else begin case (state) IDLE: begin if (start_transfer) begin cmd_data {4d0, src_addr, dst_addr, 23d4096, 4d0, 1b1}; cmd_valid 1b1; state SEND; end end SEND: begin if (cmd_ready) begin cmd_valid 1b0; state WAIT; end end WAIT: begin if (transfer_done) begin state IDLE; end end endcase end end这段代码生成一条读命令从src_addr读取4096字节到dst_addr。命令的各个字段按照前面说的格式拼接。注意EOF位设成了1表示传输结束时置位TLAST。4.4 时序约束与实现策略DataMover的时序约束主要有两块时钟约束和跨时钟域约束。时钟约束方面需要为DataMover的各个时钟创建时钟定义。如果M_AXI和S_AXIS跑在不同时钟域需要分别约束。create_clock -period 5.000 -name clk_200m [get_ports clk_200m] create_clock -period 10.000 -name clk_100m [get_ports clk_100m]跨时钟域约束方面DataMover内部的异步FIFO需要设置false path或者max delay。Vivado通常会自动识别但为了保险可以手动加约束set_false_path -from [get_clocks clk_100m] -to [get_clocks clk_200m] set_false_path -from [get_clocks clk_200m] -to [get_clocks clk_100m]实现策略上DataMover的资源占用不大但M_AXI接口的时序可能比较紧。如果时序不收敛可以尝试以下方法降低M_AXI的时钟频率、减小突发长度、增加Outstanding的流水线级数、或者在M_AXI接口上加寄存器切片。4.5 仿真验证与上板调试仿真验证是必不可少的。我一般会写一个简单的testbench模拟命令下发和数据读写。Testbench的核心是模拟DDR的行为。可以用一个简单的RAM模型来替代DDR控制器响应M_AXI的读写请求。然后检查读出来的数据是否和写入的一致。上板调试的时候ILA是必不可少的工具。我一般会在以下几个信号上加ILA探针命令接口的tvalid和tready、M_AXI的arvalid和rvalid、M_AXIS的tvalid和tready、以及DataMover的状态信号。通过ILA观察波形可以快速定位问题。比如如果命令接口的tready一直为低说明命令FIFO满了可能是命令下发太快或者传输卡住了。如果M_AXI的arvalid为高但arready一直为低说明DDR控制器没有响应可能是地址映射错了或者DDR控制器没初始化好。5. 常见问题与排查技巧实录5.1 带宽上不去的排查思路带宽上不去是最常见的问题。排查的时候按照从上游到下游的顺序逐一检查。先看命令下发速率。如果命令FIFO经常为空说明命令下发太慢DataMover在等命令。这时候需要优化命令生成逻辑或者增加命令FIFO深度。再看数据FIFO状态。如果数据FIFO经常满说明下游消费太慢DataMover在等下游。这时候需要优化下游逻辑或者增加数据FIFO深度。最后看M_AXI接口的握手效率。如果arvalid和arready的握手间隔很大说明DDR控制器响应慢。这时候需要检查DDR控制器的配置或者减少其他Master的竞争。下面是一个带宽排查的速查表现象可能原因解决方法命令FIFO经常空命令下发太慢优化命令生成逻辑增加FIFO深度数据FIFO经常满下游消费太慢优化下游逻辑增加FIFO深度M_AXI握手间隔大DDR控制器响应慢检查DDR配置减少总线竞争突发长度被截断地址不对齐或跨4KB边界检查地址对齐拆分传输带宽只有理论值一半位宽不匹配检查DataMover位宽和DDR位宽5.2 数据错误的定位方法数据错误比带宽问题更难查因为错误往往是偶发的。第一步是确认错误模式。是固定位置错还是随机错固定位置错通常是地址对齐或者边界问题。随机错通常是时序问题或者FIFO溢出。第二步是缩小范围。如果写进去的数据读出来不对先确认是写错了还是读错了。可以先用一个简单的测试模式比如递增数写入然后读出来对比。第三步是检查跨时钟域。如果DataMover的各个接口跑在不同时钟域检查异步FIFO是否溢出。可以在ILA里观察异步FIFO的满信号。第四步是检查DDR控制器的配置。DDR的刷新周期、bank管理策略、读写切换策略都会影响数据正确性。如果DDR控制器配置不当偶发的数据错误很难避免。5.3 复位与初始化的注意事项DataMover的复位时序有讲究。aresetn拉低之后需要保持至少几个时钟周期确保内部状态机完全复位。复位释放的时候要确保命令FIFO和数据FIFO都是空的。我遇到过一个问题复位释放后DataMover偶尔会输出一个无效的TLAST信号导致下游逻辑误判传输结束。后来发现是复位释放的时候命令FIFO里还有残留数据。解决方法是在复位释放之前先清空命令FIFO和数据FIFO。DDR控制器的初始化顺序也很重要。DDR控制器必须先完成初始化才能接受DataMover的读写请求。如果DataMover在DDR初始化完成之前就发命令命令会超时或者被丢弃。我一般会在DDR初始化完成信号有效之后再释放DataMover的复位。5.4 资源占用与性能的平衡DataMover的资源占用主要取决于数据位宽、Outstanding数和FIFO深度。位宽越大资源越多。Outstanding数越大资源越多。FIFO深度越大BRAM占用越多。在资源紧张的项目里可以适当降低这些参数。比如位宽从128降到64资源占用可以减少大约30%。Outstanding从8降到4资源占用可以减少大约15%。FIFO深度从1024降到256BRAM占用可以减少75%。但降低参数会影响性能。位宽减半带宽减半。Outstanding减半带宽可能降低10%到20%。FIFO深度减小抗突发能力下降。我的建议是先按性能需求配置参数如果资源不够再逐步降低每次降低之后重新评估性能。不要一开始就为了省资源把参数设得很低后面发现性能不够再改回来浪费时间。5.5 与其他IP的协同工作在实际项目中DataMover往往不是单独工作的。它可能和AXI DMA、AXI Stream FIFO、AXI Interconnect等IP一起使用。和AXI Interconnect协同的时候要注意地址映射和QoS配置。如果多个Master共享DDR带宽需要给DataMover分配足够的QoS优先级避免被其他Master饿死。和AXI Stream FIFO协同的时候要注意位宽匹配。如果DataMover的位宽是64bitStream FIFO的位宽也是64bit可以直接连接。如果位宽不匹配需要在中间加位宽转换逻辑。和AXI DMA协同的时候要注意命令接口的仲裁。如果DataMover和DMA共享同一个命令接口需要加仲裁逻辑避免命令冲突。6. 一些实战中的经验体会DataMover这个IP我用过很多次每次都有新的体会。最开始的时候觉得它配置复杂不如自己写状态机来得直接。但用熟了之后发现它帮你处理了很多底层的细节比如AXI握手、突发拆分、跨时钟域同步这些如果自己写很容易出问题。有一个项目让我印象很深。那是一个高速数据采集系统ADC采样率200MSPS数据位宽14bit需要连续采集并存储到DDR。最开始用自己写的AXI Master状态机带宽只能跑到600MB/s而且偶尔会丢数据。后来换成DataMover带宽直接上到1.1GB/s而且连续跑几个小时都没有丢数据。问题就出在状态机的outstanding处理上自己写的时候没有把流水线做深导致带宽上不去。另一个体会是关于命令下发的。DataMover的命令接口虽然简单但命令下发的时机很关键。如果命令下发太早数据FIFO还没准备好命令会等待。如果命令下发太晚DataMover会空闲。我一般会在数据FIFO快满的时候提前下发下一条命令这样DataMover可以无缝衔接。还有一个细节是关于TLAST的。DataMover的TLAST信号是在传输结束时置位的但如果你把EOF位设成0TLAST就不会置位。这个特性在某些场景下很有用比如连续传输多帧数据中间不需要TLAST分隔。但如果你下游的逻辑依赖TLAST来判断帧边界就要确保EOF位设成1。最后说一个调试技巧。如果DataMover的行为不符合预期可以先检查命令FIFO和数据FIFO的状态。这两个FIFO的状态信号在ILA里一目了然。如果命令FIFO满了说明命令下发太快。如果数据FIFO满了说明下游消费太慢。如果两个FIFO都是空的说明DataMover在等命令或者等数据。根据这些状态可以快速定位问题所在。这个IP的文档虽然详细但很多细节只有在实际项目中才能体会到。希望这些经验能帮到正在做DDR相关项目的朋友少走一些弯路。
返回列表