3. DMA控制器架构:寄存器配置、状态机、通道管理、地址生成逻辑

好,咱们进入第三章。DMA控制器说白了就是个专门干搬运的硬件加速器。你要让它干活,得先告诉它“从哪搬、搬到哪、搬多少、搬完怎么通知你”。这些东西都写在它的配置寄存器里,然后由一个状态机来指挥整个搬运流程。

我个人习惯把DMA控制器拆成四个部分来看:寄存器配置、状态机、通道管理、地址生成逻辑。你把这四块吃透了,基本上任何DMA控制器在你眼里都是个“改改寄存器就能跑”的东西。

3.1 寄存器配置:给DMA下达指令

寄存器配置是DMA的“大脑”。你得把任务参数写进去,它才知道怎么干活。我见过不少新人在配置时把源地址和目的地址搞反,结果数据全写到别的地方去了。嗯,这种事我干过不止一次。

核心寄存器就那么几类:

寄存器类型作用关键字段
控制寄存器启动/停止/复位,设定传输模式ENABLE、START、BURST_SIZE、DIRECTION
状态寄存器查询传输是否完成、有无错误DONE、ERROR、BUSY、CHAIN_END
源/目的地址寄存器指定数据搬运的两端地址SRC_ADDR、DST_ADDR,常有自增模式位
传输计数寄存器告诉它搬多少字节/字COUNT、REMAINING,递减到0时触发完成
中断控制寄存器决定哪些事件可以打断CPUIE_DONE、IE_ERROR、IE_CHAIN

我的一点经验:配置顺序很重要。我建议先写地址和计数,最后写控制寄存器里的ENABLE或START位。你想想看,如果先使能,地址还没写对,DMA就已经开始搬了。我之前在一个项目里就因为顺序搞反,搬了一堆垃圾数据,排查了半天才发现。

另外,多数DMA支持突发传输(Burst Transfer)。BURST_SIZE这个字段直接影响总线效率。设得太小,频繁仲裁浪费带宽;设得太大,可能独占总线太久,别的设备就等死了。我一般建议匹配总线宽度和存储器的访问粒度,设成8、16或32个节拍。

3.2 状态机:DMA干活的内核

DMA控制器内部藏着一个有限状态机,它才是真正干活的主儿。说白了,状态机就像一个“交通指挥系统”,告诉DMA每个时刻该做什么。

基本状态也就那么五六个:

  1. IDLE(空闲)——等着被配置,啥也不干。
  2. CONFIG(配置)——寄存器数据被锁存进内部逻辑,准备干活。
  3. TRANSFER(传输)——正在搬数据,这时状态机会循环执行“读-写-地址自增-计数减一”。
  4. PAUSE(暂停)——有的控制器支持暂停,比如总线被抢占时,状态机挂起,等总线空闲再恢复。
  5. ERROR(错误)——出现总线超时、地址对齐出错等状况,状态机跳进这里报错。
  6. DONE(完成)——计数归零,触发中断或链式传输的下一跳。

我记得之前有个项目,状态机在PAUSE和TRANSFER之间反复跳,就是不停。查了半天发现是总线握手机制的一个信号没拉高,状态机收不到确认信号就一直重试。嗯,这种问题特别隐蔽,建议你在仿真阶段就把状态机的所有跳转条件列个表,一条条检查。

DMA控制器核心状态机 IDLE CONFIG TRANSFER PAUSE ERROR DONE 图例 自动跳转 条件跳转 圆形=空闲/完成

注意:状态机跳转的时序非常敏感。我曾经遇到一个bug,TRANSFER状态跳到DONE时,因为计数器的比较逻辑比地址更新快了一个时钟,导致最后一步数据没写完就报完成了。解决方法是让状态机在计数归零后多等一个周期,等写操作真正完成再切状态。这种“时序流水”问题,你调试的时候一定要看波形。

3.3 通道管理:多路并行的艺术

现代DMA控制器一般都支持多通道,比如8路、16路甚至更多。每个通道独立拥有一整套寄存器,可以同时配置不同的传输任务。说白了,通道就是DMA内部的“多线程”。

通道管理主要解决三个问题:

  • 优先级仲裁——多个通道同时请求传输,谁先走?固定优先级还是轮询?我个人偏向使用轮询加权重,防止低优先级通道被饿死。我之前做的一个多通道DMA设计,搞了个动态优先级轮询,结果在高速接口下出现低优先级通道饿死的情况,不得不加了超时提升机制。
  • 通道分时复用——有的设计里多个通道共享一套读写引擎,靠时间片轮转。这样节省面积,但吞吐量会受限。如果你的接口速率特别高,建议每个通道独立仲裁。
  • 链式传输(Scatter-Gather)——一个通道可以挂一串任务(由链表描述),搬完一个自动加载下一个。这个太实用了,我在做大数据包处理时就用它,把数据分散到不同缓冲区,CPU只需要配置一次表头。

3.4 地址生成逻辑:数据搬家的路线规划

地址生成是DMA的“导航系统”。它负责算下一个读/写地址在哪。别小看这个逻辑,它直接影响能不能正确搬运数据。

常见的地址生成模式有四种:

模式说明应用场景
固定地址地址不变,反复读写同一个位置外设FIFO、状态寄存器轮询
递增地址每次传输后地址+1(或+BurstSize)内存缓冲区、线性数据搬运
递减地址地址递减,倒着搬栈操作、队列尾部插入
循环缓冲地址到边界后跳回基地址音频/视频的环形缓冲区、连续采样

地址生成的实现通常是一个加法器和一组多路选择器。每次传输完成,加法器把当前地址加上步长(或减去),然后根据模式选择是否跳转。

这里有个容易被忽略的点:地址对齐。很多DMA要求源地址和目的地址对齐到总线宽度,比如32位总线上地址必须是4的倍数。我之前在一个项目里,外设地址是0x1001(奇数地址),DMA配了32位突发,结果地址直接报错。后来我加了字节对齐检测,如果不齐就自动降级为单字节模式先搬掉头几个字节,等对齐了再切回突发。

核心思路:对于高速接口,地址生成逻辑最好用流水线实现。不要等这次传输完成再算下次地址,而是提前一个周期算好,挂起仲裁信号。这样地址出现在总线上时,读/写请求可以无缝衔接,没有气泡。我实测过,流水线地址生成能让吞吐量提升15%左右,尤其在Burst之间。

嗯,关于DMA控制器架构的核心部分,基本就这些。寄存器配置是大脑,状态机是指挥官,通道管理是调度员,地址生成是导航员。你把这四块理清了,接下来看DMA和高速接口怎么衔接就会顺很多。