| |||
在 DW_ahb_dmac 的语境中,一个 “Block” 通常指的是一次 DMA 传输事务(Transaction)中,不可分割的一组连续数据传输。它是由控制器硬件自动完成的一个完整数据块移动操作。
为了更好地理解,我们可以将其分解:
1. Block 的核心概念
一个 Block 是 DMA 控制器配置和执行的一个基本传输单元。当您启动一次 DMA 传输时,您通常是在请求控制器移动一个“Block”的数据。
它不是软件中的“函数”或“代码块”,而是硬件操作的一个数据块。
它通常对应一次 AHB 总线上的 Burst 传输序列。AHB 总线高效的原因之一就是支持 Burst(突发)传输,可以在一次地址握手后连续传输多个数据。一个 Block 通常就由这样的一个 Burst 序列构成。
2. Block 的构成:如何定义一个大小的 Block?
一个 Block 的大小和行为是由 DMA 通道的寄存器配置决定的,主要包括:
传输宽度 (CTLx.SRC_TR_WIDTH & CTLx.DST_TR_WIDTH):决定一次传输操作的基本单位是字节(8bit)、半字(16bit)还是字(32bit)。
Block 传输大小 (CTLx.BLOCK_TS):这是定义 Block 大小的核心寄存器。它指定了在这个 Block 中,要进行多少次“基本传输单位”的传输。
例如:如果传输宽度设置为字(32位/4字节),且 BLOCK_TS = 10,那么这个 Block 将传输 10 * 4字节 = 40字节 的数据。
源和目标地址 (SARx, DARx):定义了 Block 传输的起始地址。
地址控制 (CTLx.SRC_INC, CTLx.DST_INC):决定每传输一个“基本单位”后,地址是递增、递减还是保持不变。
3. 一个具体的例子
假设我们配置 DMA 通道如下:
SRC_TR_WIDTH = 2 (代表 32位,即 1 Word = 4 Bytes)
DST_TR_WIDTH = 2 (代表 32位,即 1 Word = 4 Bytes)
BLOCK_TS = 4 (表示这个 Block 要传输 4 个 “Word”)
SRC_INC = 1 (源地址递增)
DST_INC = 1 (目标地址递增)
那么这次 Block 传输的过程将是:
cpu 启动 DMA 传输。
DMA 控制器成为一个 AHB 总线主设备,并发起一次 Burst 传输。
控制器会连续从源地址 (SARx) 读取 4 次数据,每次读取 32 位(1 Word)。
每读一次,源地址自动增加 4 字节(因为宽度是 32位)。
同时,控制器将读到的数据连续写入目标地址 (DARx) 4 次。
每写一次,目标地址自动增加 4 字节。
传输完 4 个 Word(共 16 字节)后,整个 Block 传输完成。控制器可能会产生一个中断(如果配置了),并等待下一次触发。
4. Block、LLI(链表项)和 Multi-block Transfer
DW_ahb_dmac 更强大的功能在于支持链表模式 (Linked List),这就引入了多 Block 传输的概念:
单一 Block 传输:配置好寄存器后启动一次,传输一个 Block 后停止。
多 Block 传输 (使用 LLI):您可以创建一个链表(LinkedList),其中每个节点(称为 LLI - Linked List Item)都包含了一组完整的寄存器配置(如 SAR, DAR, BLOCK_TS 等),用来描述一个 Block 如何传输。
控制器会自动完成当前 Block(即当前 LLI 所描述的传输),然后无需软件干预,自动从下一个内存中的 LLI 节点加载配置,开始传输下一个 Block。
这样,通过一个链表,DMA 控制器就可以将多个不连续的内存块(每个都是一个 Block)组织成一次连续的传输流,极大地提高了效率,减轻了 CPU 负担。
总结
在 DW_ahb_dmac 中:
一个 Block 是 DMA 传输的核心操作单元,由 CTLx.BLOCK_TS 等寄存器定义其大小。
它通常对应于 AHB 总线上的一次 Burst 传输,是硬件自动完成的一组连续数据的移动。
通过链表 (LLI) 结构,可以实现多个 Block 的自动、连续传输,用于处理分散/聚集(Scatter/Gather)等复杂数据搬移任务。
您提供的文档中“Example 5”和“2.9.1.5.1 Hardware Realignment of SAR/DAR Registers”章节完美地解释了这种情况。我将结合文档内容为您梳理清楚。
1. 流控制器决定块大小:块的大小(总字节数)由流控制器(Flow Controller)决定。在大多数情况下,当 SRC_TR_WIDTH != DST_TR_WIDTH
时,DMAC 是流控制器(CTLx.TT_FC
设置为 000, 001, 010, 011)。
2. BLOCK_TS
始终以源端数据项数量为单位:无论目标端宽度如何,CTLx.BLOCK_TS
寄存器编程的值始终代表需要从源端读取的“数据项”的数量。一个“数据项”的宽度由 CTLx.SRC_TR_WIDTH
定义。
3. 总字节数计算:整个 Block 传输的总字节数(blk_size_bytes
)由源端配置决定。
blk_size_bytes = CTLx.BLOCK_TS * (CTLx.SRC_TR_WIDTH / 8)
这个值必须与目标端的需求匹配,否则需要软件进行干预和计算。
让我们用文档中的 Example 5 作为基础,并扩展另一个例子。
这是文档中的 Example 5,也是最复杂的情况,可能涉及 fifo 刷新(Flush)。
· 配置:
o CTLx.SRC_TR_WIDTH = 3‘b000
(8-bit)
o CTLx.DST_TR_WIDTH = 3’b011
(64-bit)
o CTLx.BLOCK_TS = 44
o DMAC 是流控制器。
· 计算:
1. Block 总字节数:
blk_size_bytes = 44 * (8/8) = 44 bytes
这意味着 DMAC 需要从源端读取 44 个字节。
2. 目标端传输次数:
理想情况下,DMAC 希望每次以 64-bit (8-byte) 为单位写入目标。
44 bytes / 8 bytes = 5.5
这表示无法用整数次 64-bit 传输完成。前 5 次传输将写入 5 * 8 = 40
字节。
· 会发生什么?
0. DMAC 会先进行 5 次 64-bit 的 AHB 突发传输,消耗掉 40 字节的数据。
1. 此时,FIFO 中还剩 44 - 40 = 4
字节数据需要写入,但无法凑成一个 64-bit 传输。
2. 此时,DW_ahb_dmac 会自动进入 “FIFO Flush Mode”。
3. 在 Flush 模式下,控制器会临时改变目标端的传输宽度,将其从配置的 64-bit 切换为与源端相同的 8-bit。
4. 然后,DMAC 发起 4 次 8-bit 的 AHB 单次传输,将剩余的 4 字节数据写完。
5. 特别注意:由于最后一次传输改变了地址对齐方式(从 64-bit 边界变成了 8-bit 边界),如果下一个 Block 是连续地址传输,硬件会自动对 DARx
进行重新对齐(Realignment),使其回到 DST_TR_WIDTH
对应的边界上,以保证下一个 Block 的正确开始。
这种情况相对简单。
· 配置:
o CTLx.SRC_TR_WIDTH = 3‘b010
(32-bit)
o CTLx.DST_TR_WIDTH = 3’b001
(16-bit)
o CTLx.BLOCK_TS = 10
// 要从源端读 10 个 32-bit 数据
o DMAC 是流控制器。
· 计算:
1. Block 总字节数:
blk_size_bytes = 10 * (32/8) = 10 * 4 = 40 bytes
这意味着 DMAC 需要从源端读取 40 个字节。
2. 目标端传输次数:
DMAC 需要以 16-bit (2-byte) 为单位写入目标。
40 bytes / 2 bytes = 20
这意味着需要 20 次 16-bit 的 AHB 传输才能完成整个 Block。
· 会发生什么?
DMAC 的内部 FIFO 会进行数据打包和解包。
0. 它从源端进行一次 32-bit 读取,收到 4 字节数据。
1. 然后,它需要发起 2 次 16-bit 的写入,才能将这 4 字节数据送出。
2. 对于 BLOCK_TS=10
,它总共需要进行 10 次源端读取和 20 次目标端写入。
这个过程对程序员是透明的,你只需要关心从源端读取多少個数据项 (BLOCK_TS
)。
场景 |
| 总字节数计算 | 关键行为 |
| 从源端读取的数据项数,也是向目标端写入的数据项数。 |
| 最直接的情况,一一对应。 |
| 从源端读取的数据项数。 |
| 可能触发 FIFO Flush。目标端最后可能用更小的位宽传输剩余数据。硬件负责地址重对齐。 |
| 从源端读取的数据项数。 |
| DMAC 自动进行多次目标端传输来完成一次源端读取。内部 FIFO 处理数据拆包。 |
给程序员的黄金法则:
1. 永远以源端视角编程 BLOCK_TS
:你只需要告诉 DMAC “从源设备读取多少个数据项”。数据项的宽度就是 SRC_TR_WIDTH
。
2. 确保总字节数有效:计算 BLOCK_TS * (SRC_TR_WIDTH/8)
,确保这个总字节数对于目标设备是合理且可接受的。例如,如果目标设备期望接收 100 字节,而源端是 8-bit,那么 BLOCK_TS
就应该设为 100。
3. 小心宽度不匹配的边界:特别是当源端较窄时,要意识到最后可能会有一段“零头”数据,但 DMAC 硬件会处理它。
因此,在您提供的配置中,BLOCK_TS
的计算与 DST_TR_WIDTH
无关。它的值直接由您想要搬运的数据总量和源端传输宽度决定:BLOCK_TS = desired_total_bytes / (SRC_TR_WIDTH / 8)
。
由谁作为流控制器(Flow Controller) 决定了 DMA 传输的发起和控制方式,直接影响系统架构和软件复杂度。
根据文档DW_ahb_dmac(特别是 Chapter 2.2 和 Table 2-1),选择流控制器的核心原则可以归结为一条:
在 DMA 通道启动之时,由谁(软件/DMAC 或 外设)知道本次传输的“块(Block)”的确切大小?
下面我们详细分解这个原则,并阐述如何做出选择。
流控制器 | 核心问题 | 适用场景 | 配置方法 ( |
DMAC | 软件是否在传输前就知道要传输多少数据? | 传输固定大小的数据块。例如: |
|
源外设 (Source Peripheral) | 源设备是否在数据传输过程中才能确定块何时结束? | 源设备产生数据流,其长度未知。例如: |
|
目标外设 (Destination Peripheral) | 目标设备是否在接收数据过程中才能确定块何时结束? | 目标设备处理数据流,其所需长度未知。例如: |
|
注:110
(Mem-Periph with Peripheral FC) 模式较少见,可能用于目标外设有严格FIFO限制等特殊场景。
为了更直观地理解,下图展示了两种模式下的主要工作流程与关键区别:
· 工作机制:
1. 软件配置:软件在启动 DMA 前,精确计算并写入 CTLx.BLOCK_TS
寄存器。该值表示需要从源端读取的“数据项”数量。
2. 硬件执行:DMAC 内部有一个计数器,从 BLOCK_TS
开始递减。
3. 传输结束:当计数器减到零时,DMAC 知道一个 Block 传输完毕,它会自动停止通道(如果未使能多块传输)并产生相应的中断(如 IntBlock
)。
4. 外设角色:外设通过握手信号(dma_req
, dma_single
)仅用于事务级(Transaction) 的流控,即“我准备好发送/接收一个或一组数据了”,而不知道整个块何时结束。
· 优点:效率高,软件控制力强,逻辑简单。
· 缺点:要求数据长度在传输前已知。
· 工作机制:
1. 软件配置:软件无需也无法设置 BLOCK_TS
(其值无效)。它只需启动 DMA 通道。
2. 硬件执行:DMAC 会持续响应外设的传输请求,不断搬移数据。
3. 传输结束:当外设决定传输结束时(例如,UART 检测到停止位,或 FIFO 达到特定状态),它会通过握手接口的 dma_last
信号通知 DMAC。
4. 结束通知:DMAC 在当前事务传输完成后,接收到 dma_last
信号,便知道整个 Block 传输完毕,随后停止通道并产生中断。
5. 数据预取风险:当目标外设是流控制器时,需要特别注意 CFGx.FCMODE
位。
§ FCMODE=0
(预取):DMAC 会积极地从源端读取数据,可能造成数据丢失(如果目标提前结束,源端多读的数据会被丢弃)。适用于源端是内存等非敏感设备。
§ FCMODE=1
(非预取):DMAC 只在目标端请求数据时才去源端读取,保证数据不丢失。适用于源端是 FIFO 等“读敏感”设备。
· 优点:能处理未知长度的数据流,适应性强。
· 缺点:需要外设支持相应的握手协议(提供 dma_last
),逻辑更复杂。
选择流控制器的决策流程可以归纳为以下几步:
1. 明确数据源和目的地:是内存到外设?外设到内存?还是外设到外设?
2. 询问关键问题:“在点击‘开始’按钮的那一刻,我知道这次要传多少字节数据吗?”
o 答案是“我知道”:例如,“我要传 1024 字节”,“这个 ADC 缓冲区是 256 个采样点”。 -> 选择 DMAC 作为流控制器 (TT_FC
= 000, 001, 010, 011)。
o 答案是“我不知道,取决于外设”:例如,“要等串口收到回车符才算完”,“要等网络包尾”。 -> 选择外设作为流控制器 (TT_FC
= 100, 101, 111)。
3. 特殊情况处理:如果选择了目标外设作为流控制器,并且源端是类似 FIFO 的敏感设备,务必设置 CFGx.FCMODE = 1
来禁用数据预取,防止数据丢失。
遵循这个原则,您就能为 DW_ahb_dmac 的每个通道做出正确的流控制器选择。