首页 / 资讯中心 / 文章详情

FPGA数据搬运架构实战:AXI/AXIS总线、DMA引擎与DDR缓存设计

FPGA数据搬运架构实战:AXI/AXIS总线、DMA引擎与DDR缓存设计 ★ FEATURED ARTICLE
1. 从“能跑通”到“跑得动”为什么数据搬运才是FPGA工程的成人礼很多人学FPGA卡在时序约束、状态机、仿真波形这些地方觉得把这些啃下来就算入门了。但真正做过完整项目的人都知道前面那些只是“让逻辑能跑通”而数据搬运架构的设计才是决定你的系统“能不能跑得动”的分水岭。我见过太多Demo级别的设计在板子上点个灯、跑个计数器、做个简单的图像滤波都没问题可一旦要把几十兆甚至上百兆的数据从ADC搬到DDR再读出来处理整个系统就开始丢数、卡死、时序崩盘。问题往往不出在算法本身而是出在数据通路的架构上。这一篇要聊的就是FPGA开发里绕不开的三件套AXI/AXIS总线、DMA引擎、DDR缓存架构。这三个东西组合起来构成了绝大多数中高带宽FPGA系统的数据骨架。你做的图像处理、雷达信号采集、高速数据记录、视频流水线底层几乎都是这套结构在支撑。标题里说的“从近似0基础开始”我理解不是真的零基础而是你可能已经会写Verilog、会跑仿真、会点灯但对“数据怎么高效地在PL和PS之间、在片内和片外之间流动”这件事还没有系统认知。这篇就是把这个认知补上。我写这部分内容的出发点很直接网上讲AXI协议的文章很多讲DMA的也不少但把AXIS数据流、DMA搬运、DDR缓存这三者串成一个完整架构来讲的并且讲清楚“为什么这么设计”的其实不多。很多教程上来就给你一个IP核配置界面让你勾选几个选项生成出来能用就行但一旦出问题你根本不知道从哪查。所以我会尽量把每个设计决策背后的逻辑讲透包括参数怎么算、带宽怎么估、FIFO深度怎么定、突发长度怎么选。这些才是真正干活时用得上的东西。适合谁看如果你正在做或者准备做一个需要高速数据采集、缓存、处理的项目比如基于图像传感器的视频流处理、高速ADC数据记录、实时信号分析那这篇的内容你大概率会用到。如果你还停留在纯逻辑设计的阶段也可以先看看建立一下系统级的架构思维对后面做完整项目有好处。2. 先把地图画清楚AXI、AXIS、DMA、DDR各自扮演什么角色2.1 用一个物流仓库的类比把整条链路串起来我先不急着上协议细节先用一个生活化的场景把整个数据通路讲清楚这样后面看具体信号的时候你脑子里有个画面。想象一个大型物流仓库。DDR就是仓库的货架区容量大、能存很多东西但存取速度相对慢而且你不能直接站在货架前面一件件拿得通过叉车和通道。DMA就是那个叉车司机他的工作就是按照指令把货物从货架搬到出货口或者从进货口搬到货架整个过程不需要仓库管理员CPU一件件动手。AXI是仓库内部的主干道和装卸平台规定了叉车怎么走、货物怎么摆放、一次能搬多少。而AXIS更像是传送带数据像流水一样一个接一个地过来没有地址概念就是纯粹的数据流适合视频像素、ADC采样这种连续不断的场景。这个类比里最关键的一点是CPU不应该亲自搬货。如果让CPU一个字节一个字节地从DDR读到寄存器再写到外设那CPU基本就废了什么都干不了。DMA存在的意义就是把CPU从数据搬运中解放出来让CPU只负责“下指令”和“处理结果”中间的大量数据移动交给专门的硬件引擎。这就是为什么几乎所有高带宽系统都离不开DMA。理解了这层你就明白为什么FPGA系统里AXI和AXIS总是成对出现AXIS负责把数据流从产生端比如ADC接口、图像传感器接口传到DMADMA再通过AXI把数据写进DDR反过来DMA通过AXI从DDR读出数据再通过AXIS把数据流送给处理模块或输出接口。整条链路里AXIS管“流”AXI管“搬”DMA管“调度”DDR管“存”。2.2 AXI和AXIS的本质区别一个带地址一个不带地址很多人初学的时候分不清AXI和AXIS觉得都是总线有什么区别。核心区别就一句话AXI是带地址的存储器映射总线AXIS是不带地址的流式总线。AXI全称是Advanced eXtensible Interface它有三个子通道读地址通道、读数据通道、写地址通道、写数据通道、写响应通道。每一次传输都要先给地址再传数据还要等响应。它适合访问有明确地址空间的设备比如DDR控制器、寄存器组、BRAM等。AXI的传输是“事务型”的一次事务包含地址阶段和数据阶段可以支持突发传输一次给一个起始地址然后连续传多个数据。AXIS全称是AXI4-Stream它没有地址只有数据线和一些握手信号。数据就像水流一样从上游流到下游你不需要关心它存在哪里只需要关心什么时候有效、什么时候准备好接收。AXIS特别适合视频、音频、ADC采样这类连续数据流。它的握手协议很简单TVALID表示上游数据有效TREADY表示下游准备好接收两者同时为高时数据才真正传输。还有TLAST表示一包数据的结尾TKEEP表示哪些字节有效TUSER可以携带一些边带信息。我个人的经验是在数据产生端和处理端之间尽量用AXIS连接因为流式接口简单、容易做流水线、不需要地址管理。在需要访问存储器的地方用AXI因为你要指定往哪里写、从哪里读。DMA就是这两者的桥梁一边是AXIS流接口一边是AXI存储器映射接口。2.3 DMA到底在搬什么从寄存器搬运到描述符搬运DMA的发展其实有几个阶段。最早期的DMA很简单就是“把源地址的一串数据搬到目的地址”CPU配置好源地址、目的地址、长度启动然后等中断。这种叫简单DMA或寄存器模式DMA适合单次、大块的数据搬运。但实际系统里数据往往是分散的、连续的、多包的。比如视频流是一帧一帧来的每帧数据量固定但帧与帧之间可能有间隔又比如网络包大小不一需要灵活处理。这时候就需要描述符模式DMAScatter-Gather DMA。描述符本质上是一个数据结构里面记录了“这一包数据从哪来、到哪去、多长、下一包在哪”。DMA引擎自己去读描述符然后按照描述符的指示搬运数据搬完一包自动去取下一个描述符不需要CPU干预。这样就实现了链式搬运效率极高。在FPGA里Xilinx现在叫AMD的AXI DMA IP核就是典型的描述符模式DMA支持MM2SMemory Map to Stream从内存读到流和S2MMStream to Memory Map从流写到内存两个方向。它的寄存器空间里有一组描述符寄存器你可以把描述符链的起始地址写进去然后启动DMA就会自动跑完整条链。每搬完一包可以产生中断CPU只需要处理中断和准备下一批描述符。2.4 DDR缓存架构为什么不能只靠BRAMBRAM是FPGA片内的存储资源速度快、延迟低但容量有限。一颗中端FPGA的BRAM总量大概在几兆字节到十几兆字节之间对于视频帧这种动辄几MB的数据存一两帧就到顶了。而DDR是片外存储容量可以到GB级别带宽也能做到几GB/s甚至更高虽然延迟比BRAM大但胜在容量和带宽。所以典型的架构是用BRAM做小容量、低延迟的缓冲比如FIFO、行缓存用DDR做大容量、高带宽的帧缓存或数据缓存。数据从AXIS流进来先经过一个小FIFO做跨时钟域和速率匹配然后DMA把数据批量写进DDR处理模块需要数据时DMA再从DDR读出来通过AXIS送给处理模块。这样就形成了一个“流进-缓存-读出-处理-再缓存-流出”的闭环。这里有个关键设计点DDR的带宽是共享的。如果你的系统里同时有多个读写通道比如写一帧、读一帧、CPU还要访问那DDR控制器的仲裁压力会很大。所以架构设计时一定要估算总带宽需求留足余量。一般来说DDR的实际可用带宽只能达到理论峰值的60%到70%因为还有刷新、激活、预充电等开销。这个后面会详细讲怎么算。3. 核心细节拆解AXIS握手、DMA描述符、DDR带宽这三座大山3.1 AXIS握手协议看似简单坑最多的地方AXIS的握手协议表面上很简单TVALID和TREADY同时为高时数据传输。但实际写代码的时候这里是最容易出问题的地方。我总结了几条铁律都是踩坑踩出来的。第一条TVALID一旦拉高在握手完成之前绝对不能拉低。这是AXIS协议的死规定。很多新手写状态机的时候发现下游TREADY没准备好就把TVALID撤了想着等会儿再发。这在AXIS里是违规的会导致数据丢失或协议错误。正确的做法是数据准备好之后TVALID拉高并保持直到TREADY也为高、完成一次握手才能决定下一个周期是否继续拉高。第二条TREADY可以依赖TVALID但TVALID不能依赖TREADY。这句话的意思是下游可以等上游TVALID来了再决定是否拉高TREADY但上游不能等下游TREADY来了才决定是否拉高TVALID。为什么因为如果两边互相等就会死锁。上游必须主动把数据准备好、TVALID拉高下游看到之后根据自己的缓冲情况决定是否接收。这是AXIS避免死锁的核心设计。第三条TLAST的时机要精确。TLAST表示一包数据的最后一个数据。它必须和最后一个数据同时有效不能早也不能晚。如果TLAST提前了下游会以为包结束了后面的数据就丢了如果TLAST晚了下游会一直等包结束导致超时或卡死。在写AXIS发送逻辑时我一般会用一个计数器记录已发送的数据个数当计数达到包长度减一时在下一个数据上拉高TLAST。第四条跨时钟域要加FIFO。如果AXIS的上下游在不同的时钟域必须用异步FIFO做隔离。AXIS本身不解决跨时钟域问题它只是协议。Xilinx的AXIS FIFO IP核或者自己写的异步FIFO都可以关键是FIFO的深度要算够否则会丢数。深度怎么算后面会讲。3.2 DMA描述符链式搬运的核心数据结构描述符模式DMA的核心就是描述符。一个描述符通常包含这几个字段源地址、目的地址、数据长度、下一描述符地址、控制状态信息。DMA引擎从内存里读取描述符解析出这些信息然后执行搬运搬完之后根据“下一描述符地址”去取下一个描述符形成一条链。在Xilinx AXI DMA里描述符的格式是固定的你可以用SDK或Petalinux提供的驱动来构造描述符链也可以自己用逻辑生成。对于纯FPGA逻辑控制的场景有时候我们会自己写一个简单的描述符生成器把描述符存在BRAM里DMA从BRAM读描述符。这种方式适合描述符数量不多、模式固定的场景。描述符链的设计有几个关键点。第一是描述符的对齐。AXI DMA要求描述符地址按一定字节对齐通常是64字节不对齐会导致DMA读描述符出错。第二是描述符的缓存一致性。如果描述符是CPU写的DMA是硬件读的那要确保CPU写完描述符后做cache flush否则DMA可能读到旧数据。第三是描述符链的循环与终止。你可以设计成循环链搬完最后一包回到第一包也可以设计成一次性链搬完就停。循环链适合连续采集场景一次性链适合单次任务。我个人的经验是对于视频帧缓存这种固定模式的场景用循环描述符链最省心。比如一帧图像分成N个描述符每个描述符对应一行或一块DMA自动循环搬运CPU只需要在帧中断里处理数据不需要每次重新配置DMA。这样CPU负载极低系统稳定性也好。3.3 DDR带宽估算不算清楚就等着丢数DDR带宽估算是架构设计里最容易被忽略、但后果最严重的一步。很多人拍脑袋觉得DDR带宽够用结果一跑起来就丢数查半天查不出原因最后发现是带宽不够。带宽估算的基本公式是总带宽需求 写带宽 读带宽 刷新开销 仲裁开销。写带宽和读带宽根据你的数据率来算。比如你要写一路1080p60的视频流像素时钟148.5MHz每个像素3字节RGB888那写带宽就是148.5M × 3 445.5MB/s。如果同时还要读一路同样的视频流做处理那读带宽也是445.5MB/s总需求就是891MB/s。但这还没完。DDR的实际可用带宽要打折扣。以DDR3-1600为例理论峰值带宽是1600M × 2双沿× 8字节64位位宽 25.6GB/s。但实际可用带宽通常只有60%到70%也就是15GB/s到18GB/s。为什么因为DDR有刷新周期每64ms刷新一次每次刷新占用一定时间、有激活和预充电开销、有读写切换开销、还有控制器仲裁开销。如果你的访问模式是随机小颗粒的效率会更低可能只有30%到40%。所以架构设计时一定要把总带宽需求算出来然后和DDR实际可用带宽对比留至少30%的余量。如果余量不够要么降低数据率要么优化访问模式比如增大突发长度、减少读写切换要么换更宽的DDR位宽或更快的DDR颗粒。还有一个容易忽略的点DDR的读写切换开销很大。DDR控制器在读写之间切换需要时间如果频繁切换效率会急剧下降。所以架构上要尽量把读和写分开比如用乒乓缓冲写的时候集中写读的时候集中读减少切换次数。或者用更大的突发长度一次搬一大块减少切换频率。3.4 FIFO深度计算不是随便拍一个数就行FIFO深度计算是另一个容易拍脑袋的地方。很多人写FIFO的时候随便填个深度比如256、512觉得差不多就行。但FIFO深度不够会导致丢数深度太大又浪费BRAM资源。正确的做法是根据数据率和处理延迟来算。假设你的数据流入速率是R_in流出速率是R_out处理模块的最大延迟是T_delay那FIFO的最小深度是Depth (R_in - R_out) × T_delay。如果R_in大于R_outFIFO会逐渐填满填满的时间就是T_delay所以深度要能容纳这段时间内多出来的数据。举个例子数据流入速率是100MB/s流出速率是80MB/s处理模块最大延迟是10微秒。那多出来的数据率是20MB/s10微秒内多出来的数据量是20M × 10u 200字节。所以FIFO深度至少要200字节考虑到余量和突发一般取2到4倍也就是512字节到1KB。如果是跨时钟域FIFO还要考虑时钟频率比和相位差。一般异步FIFO的深度要能容纳至少一个完整的数据包再加上一些余量。我个人的习惯是对于视频行缓存FIFO深度至少能存两行对于包数据FIFO深度至少能存两个最大包。这样即使有突发延迟也不会丢数。4. 实操过程从零搭一个AXIS-DMA-DDR的完整数据通路4.1 系统框图与数据流规划假设我们要做一个典型的视频采集缓存系统图像传感器输出AXIS视频流经过DMA写入DDR然后CPU或处理模块从DDR读出数据做处理处理完再写回DDR最后通过DMA读出送到显示接口。整个系统包含以下几个模块图像传感器接口模块把传感器的原始数据转成AXIS流包含TVALID、TREADY、TDATA、TLAST、TUSER。AXIS FIFO做跨时钟域和速率匹配深度根据行长度和时钟差计算。AXI DMA配置成S2MM和MM2S两个通道S2MM负责把AXIS流写入DDRMM2S负责从DDR读出到AXIS流。DDR控制器通过AXI接口连接DMA和CPU。处理模块从AXIS流读数据处理后输出AXIS流。中断控制器DMA搬完一帧产生中断通知CPU。数据流是这样的传感器 → AXIS FIFO → DMA S2MM → DDR → DMA MM2S → 处理模块 → DMA S2MM → DDR → DMA MM2S → 显示接口。注意这里DDR被多次读写所以带宽要算总账。4.2 关键参数计算与配置先算带宽。假设传感器是1080p60像素时钟148.5MHz每像素2字节RAW10或YUV422那单路视频流带宽是148.5M × 2 297MB/s。系统里有两路写采集写、处理写和两路读处理读、显示读总带宽需求是297 × 4 1188MB/s。如果DDR是DDR3-1600 64位实际可用带宽约15GB/s余量充足。但如果DDR是DDR3-800 32位理论峰值3.2GB/s实际可用约2GB/s余量就不够了需要优化。再算FIFO深度。假设AXIS FIFO的写时钟是148.5MHz读时钟是100MHz数据位宽是16位。写速率是148.5M × 2 297MB/s读速率是100M × 2 200MB/s。如果DMA的读延迟最大是1微秒那FIFO深度至少是(297 - 200) × 1u 97字节。考虑到突发和余量取512字节也就是256个16位数据。这个深度用BRAM实现完全没问题。DMA描述符配置对于1080p一帧1920×1080×2字节 4.147MB。如果每个描述符搬4KB那一帧需要约1013个描述符。描述符太多会增加DMA读描述符的开销所以一般每个描述符搬大一点比如64KB那一帧只需要64个描述符。描述符链设计成循环模式DMA自动循环搬运CPU只需要在帧中断里处理。4.3 代码实现AXIS发送逻辑与DMA启动先看AXIS发送逻辑的核心代码。假设我们有一个数据源需要把数据打包成AXIS流发送出去module axis_sender #( parameter DATA_WIDTH 16, parameter PACKET_LEN 1024 )( input wire clk, input wire rst_n, input wire [DATA_WIDTH-1:0] data_in, input wire data_valid, output wire [DATA_WIDTH-1:0] m_axis_tdata, output wire m_axis_tvalid, input wire m_axis_tready, output wire m_axis_tlast ); reg [DATA_WIDTH-1:0] data_reg; reg valid_reg; reg last_reg; reg [15:0] cnt; assign m_axis_tdata data_reg; assign m_axis_tvalid valid_reg; assign m_axis_tlast last_reg; always (posedge clk or negedge rst_n) begin if (!rst_n) begin data_reg 0; valid_reg 0; last_reg 0; cnt 0; end else begin if (data_valid !valid_reg) begin data_reg data_in; valid_reg 1b1; last_reg (cnt PACKET_LEN - 1); cnt (cnt PACKET_LEN - 1) ? 0 : cnt 1; end else if (valid_reg m_axis_tready) begin valid_reg 1b0; last_reg 1b0; end end end endmodule这段代码的关键点valid_reg一旦拉高在m_axis_tready为高之前不会拉低符合AXIS协议。last_reg在最后一个数据时拉高和data_reg同步。计数器cnt记录已发送的数据个数达到包长度时归零。再看DMA启动的软件流程以裸机为例// 初始化DMA XAxiDma_Config *cfg XAxiDma_LookupConfig(DMA_DEV_ID); XAxiDma_CfgInitialize(dma_inst, cfg); // 配置S2MM通道循环模式 XAxiDma_IntrEnable(dma_inst, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DEVICE_TO_DMA); // 构造描述符链 // 假设描述符空间已经分配好每个描述符指向DDR中的一块缓冲区 // 启动S2MM XAxiDma_SimpleTransfer(dma_inst, (UINTPTR)buffer_addr, length, XAXIDMA_DEVICE_TO_DMA); // 等待中断 while (!frame_done) { // 中断处理里设置frame_done }实际项目中描述符链的构造会更复杂需要处理对齐、cache一致性、循环链的维护等。但核心流程就是初始化DMA、配置中断、构造描述符、启动传输、等待中断、处理数据。4.4 仿真验证与上板调试仿真阶段我一般会写一个AXIS的master和slave模型模拟数据流的产生和接收验证DMA的读写是否正确。重点看几个地方TVALID和TREADY的握手是否正常TLAST的时机是否正确数据是否有丢失或重复。上板调试时最常用的工具是ILA集成逻辑分析仪。把AXIS信号、DMA的读写地址、DDR的读写使能都接到ILA上抓一段波形看。重点看DMA是否正常启动描述符是否被正确读取数据是否按预期写入DDR中断是否正常产生。我踩过的一个坑是DMA描述符的地址没有对齐导致DMA读描述符时出错但错误不报只是数据搬不对。查了很久才发现是描述符地址的问题。所以描述符地址一定要按DMA要求对齐通常是64字节。还有一个坑是cache一致性问题CPU写完描述符后没有flush cacheDMA读到的是旧描述符导致搬运错误。这个在带cache的处理器上特别常见一定要记得在启动DMA前做cache flush。5. 常见问题与排查技巧实录5.1 AXIS数据丢失从握手信号查起AXIS数据丢失是最常见的问题。排查思路是先看TVALID和TREADY的握手波形确认是否每次传输都完成了握手。如果TVALID拉高了但TREADY一直没拉高说明下游没准备好可能是下游FIFO满了或者处理模块卡住了。如果TVALID拉高后很快又拉低但TREADY没拉高说明上游违反了协议提前撤了TVALID数据丢了。还有一种情况是TLAST时机不对。如果TLAST提前了下游会提前结束包后面的数据被当成新包处理导致包边界错乱。如果TLAST晚了下游会一直等包结束导致超时。用ILA抓波形时重点看TLAST和最后一个数据的对齐关系。5.2 DMA搬不动描述符和中断排查DMA搬不动的原因很多我整理了一个排查表现象可能原因排查方法DMA不启动寄存器配置错误读DMA状态寄存器看是否处于Halted状态DMA启动后无数据描述符地址错误用ILA抓DMA读描述符的AXI事务看地址是否正确数据搬一半停了描述符链断裂检查描述符的NextDesc字段是否指向有效地址中断不产生中断未使能检查DMA中断使能寄存器和中断控制器配置数据错位地址对齐问题检查描述符地址和缓冲区地址是否按64字节对齐数据旧值Cache一致性问题在启动DMA前对描述符和缓冲区做cache flush这个表基本覆盖了80%的DMA问题。我个人的经验是DMA出问题先查描述符再查中断最后查cache。描述符问题占大多数尤其是地址对齐和链断裂。5.3 DDR带宽不够优化访问模式DDR带宽不够的表现是数据偶尔丢、系统跑一段时间后卡死、或者DMA传输速度远低于预期。排查方法是用性能计数器统计DDR的读写带宽看是否接近实际可用带宽上限。如果接近了就要优化。优化的方向有几个。第一是增大突发长度。AXI的突发长度可以从1增加到256突发越长DDR控制器的效率越高。第二是减少读写切换。把读和写分开用乒乓缓冲写的时候集中写读的时候集中读。第三是优化地址映射。DDR的地址映射会影响bank和row的命中率合理的地址映射可以减少激活和预充电开销。第四是提高DDR位宽或频率。如果以上都优化了还不够那就只能换更宽的DDR或更快的颗粒。5.4 跨时钟域导致的数据错误跨时钟域是另一个高频问题。AXIS上下游时钟不同的时候必须用异步FIFO隔离。如果直接连会出现亚稳态和数据错误。异步FIFO的深度要算够读写指针的同步要用格雷码这些是基本要求。我踩过的一个坑是异步FIFO的深度不够导致写满之后TREADY拉低但上游没有及时停止数据丢了。后来把FIFO深度加大到能存两行数据问题解决。所以跨时钟域FIFO的深度一定要留足余量至少能存一个完整的数据包。5.5 实操心得与避坑清单最后分享几条我个人的实操心得都是踩坑踩出来的AXIS的TVALID一旦拉高就不能撤这是铁律违反了一定出问题。DMA描述符地址必须64字节对齐不对齐会出错而且错误不报。启动DMA前一定要做cache flush尤其是描述符和缓冲区在cacheable区域的时候。DDR带宽要留30%以上余量实际可用带宽只有理论值的60%到70%。FIFO深度要算不要拍脑袋算完之后再乘2到4倍留余量。ILA是调试AXIS和DMA的神器关键信号一定要引出来抓波形。循环描述符链适合连续采集CPU负载低系统稳定。读写分开、乒乓缓冲能显著提高DDR效率。这套架构我用了很多年从视频采集到高速数据记录基本都跑得通。核心就是理解数据流的方向和速率算清楚带宽和缓冲然后把AXIS、DMA、DDR这三者串起来。刚开始可能会觉得复杂但搭通一次之后后面就是套模板的事了。真正难的是第一次把整条链路调通那个过程会逼着你把每个细节都搞清楚。调通之后你会发现这套架构的扩展性很好加一路数据流、换个数据率、改个缓存深度都是小改动。
阅读完成 · 觉得有帮助?
咨询建站