﻿# DMA 多通道架构：并发能力、仲裁机制与工程价值

> 每个 DMA 通道是否独立、能否并行传输？如果后端总线共享，搞那么多通道意义何在？本文从硬件 FSM 到总线仲裁再到工程价值，把"多通道到底有什么用"讲清楚。

---

## §零 一句话结论

**每个 DMA 通道拥有独立传输状态机（FSM），硬件允许并行启动；最终数据吞吐是否并行，看后端总线是否独立。即使总线共享，多通道的核心价值也不在"叠加带宽"，而在数据流隔离、优先级抢占、多外设独立中断、长短任务互不阻塞。**

---

## §一 先拆两个概念（很多人混淆）

这是最容易被混在一起的两个层次，必须拆开：

| 层次 | 问题 | 谁决定 |
|------|------|--------|
| **① 通道 FSM 是否独立** | 能不能同时启动传输指令 | DMAC 内部设计 |
| **② 总线能否同时吞吐数据** | 会不会抢带宽、分时复用 | 后端 AXI/PCIe 端口数 |

```plantuml
@startuml
skinparam shadowing false
skinparam defaultFontSize 10
rectangle "通道 0\n独立寄存器组\n独立 FSM" as CH0
rectangle "通道 1\n独立寄存器组\n独立 FSM" as CH1
rectangle "通道 2\n独立寄存器组\n独立 FSM" as CH2
rectangle "通道 N\n独立寄存器组\n独立 FSM" as CHN
rectangle "内部仲裁器\n(round-robin / fixed-priority)" as ARB
rectangle "AXI Master 端口\n(对外访问内存)" as AXI
CH0 -down-> ARB
CH1 -down-> ARB
CH2 -down-> ARB
CHN -down-> ARB
ARB -down-> AXI
note right of ARB : 这里是瓶颈：\nN 条通道 FSM 并行启动\n但只有 1 个出口到内存
@enduml
```

- **FSM 层**：通道 0/1/2/N 各有独立寄存器、独立 LLI 链表指针、独立中断——可以"同时就绪"。
- **总线层**：出口只有一组 AXI Master，所有就绪通道的读写请求在这里**时分复用**（time-multiplexed）。

---

## §二 硬件架构内部

### 2.1 通道内部（单通道视角）

以 ARM PL330 单通道为例：

| 资源 | 作用 |
|------|------|
| **源地址寄存器**（SAR） | 数据从哪读 |
| **目标地址寄存器**（DAR） | 数据写到哪 |
| **传输计数寄存器**（TCR） | 还剩多少字节 |
| **LLI 链表寄存器**（LLP） | 下一段描述符在哪（scatter-gather） |
| **控制/配置寄存器**（CCR） | 传输方向、burst 长度、地址递增模式 |
| **通道状态机**（FSM） | idle → load_descriptor → transfer → done |
| **独立中断线** | 该通道完成时触发专属 IRQ |

### 2.2 多通道 → 共享总线：仲裁器怎样分片

当 ch0 和 ch1 同时就绪、同时发出传输请求时：

```plantuml
@startuml
skinparam shadowing false
skinparam defaultFontSize 10
participant "CH0\n(内存→外设)" as C0
participant "CH1\n(外设→内存)" as C1
participant "仲裁器" as ARB
participant "AXI Master\n→ DDR" as AXI
== 同时到达的请求 ==
C0 -> ARB: 请求 AXI 写事务 (burst=4)
C1 -> ARB: 请求 AXI 读事务 (burst=4)
Note over ARB: 固定优先级: CH0 > CH1\n或 round-robin: 上次是CH1, 这次给CH0
== 仲裁: CH0 先走 ==
ARB -> AXI: CH0 的 burst write (4 beats)
AXI --> ARB: 写响应 OK
ARB --> C0: 授予传输, CH0 完成 1 个 burst
== CH1 排队等到了 ==
C1 -> ARB: 仍在等待...
ARB -> AXI: CH1 的 burst read (4 beats)
AXI --> ARB: 读数据返回
ARB --> C1: 授予传输, CH1 完成 1 个 burst
== 下一轮 ==
C0 -> ARB: 请求新 burst (还剩数据)
C1 -> ARB: 请求新 burst (还剩数据)
Note over ARB: round-robin: 上次是 CH1\n这次给 CH0\n交替切片
@enduml
```

**仲裁后的实际效果**（时间线视图）：

```bash
时间 →
CH0:  [burst0======][等待...][burst1======][等待...]
CH1:  [等待..........][burst0======][等待...][burst1======]
AXI:  [CH0-burst0][CH1-burst0][CH0-burst1][CH1-burst1]  ← 时分复用
```

> **FSM 并行**：CH0 和 CH1 的状态机都在"传输中"状态，谁都没有被挂起。

> **总线串行**：物理总线某一时刻只能服务一个 burst，多通道交替获得总线使用权。

### 2.3 两种典型仲裁策略

| 策略 | 规则 | 适用场景 |
|------|------|---------|
| **固定优先级**（Fixed） | CH0 > CH1 > ... > CH7，高优先级先走 | 有明确的紧急/普通业务分级 |
| **轮询**（Round-robin） | 上次服务 CH_N，下次服务 CH_N+1，轮流 | 各通道同等重要，防止低优先级饿死 |

---

## §三 多通道的五大工程价值

### 3.1 数据流隔离——低速不阻塞高速 **（最高价值）**

单通道时，UART 持续 DMA 占用控制器，以太网突发包只能排队：

```plantuml
@startuml
skinparam shadowing false
skinparam defaultFontSize 10
participant "UART\n(低速)" as UART
participant "单通道 DMA" as DMAC
participant "以太网\n(高速)" as ETH
UART -> DMAC: 持续低速传输 (115200bps)
activate DMAC
ETH -> DMAC: 突发数据包 (100Mbps)
Note right of DMAC: 排队等待!\n延迟 = UART 传输完成时间
UART --> DMAC: ...还在传...
DMAC --> ETH: 终于轮到你了 (延迟高达几十ms)
deactivate DMAC
@enduml
```

多通道方案：UART 占 ch0，以太网占 ch1，ch1 随时能插入高优先级传输：

```plantuml
@startuml
skinparam shadowing false
skinparam defaultFontSize 10
participant "UART" as UART
participant "CH0\n(低优先级)" as C0
participant "仲裁器" as ARB
participant "CH1\n(高优先级)" as C1
participant "以太网" as ETH
UART -> C0: DREQ 持续拉高
activate C0
C0 -> ARB: 请求 AXI
activate ARB
ETH -> C1: 突发数据包到达!
activate C1
C1 -> ARB: 高优先级请求
ARB -> C1: 打断 CH0 当前传输
deactivate C0
C1 -> C1: 以太网数据写入内存
C1 --> ETH: 完成
deactivate C1
ARB -> C0: 恢复 CH0 传输
deactivate ARB
activate C0
C0 -> C0: 继续 UART 传输
@enduml
```

> 通道内置**优先级仲裁**，高优先级通道可以抢占低优先级通道的剩余带宽——不需要等低优先级传完。

### 3.2 多种传输方向同时存在

单通道只能串行：读完才能写。

| 单通道（串行） | 多通道（并发启动） |
|-------------|-----------------|
| read 512B → done → write 512B → done | ch0: read 内存→FPGA |
| 总时间 = read + write | ch1: write FPGA→内存 |
| | **两个方向同时进行**（各自 FSM 推进）|

### 3.3 适配多外设独立中断

每个通道有**独立中断信号**。外设的 DMA 请求（DREQ）绑定到专属通道：

```bash
SPI_TX  → ch2  → CH2_IRQ  → 驱动知道"SPI 发送完成"
SPI_RX  → ch3  → CH3_IRQ  → 驱动知道"SPI 接收完成"
ADC     → ch4  → CH4_IRQ  → 驱动知道"ADC 采样 DMA 完成"
```

如果只有 1 条通道：所有 DREQ 挤在一起，驱动必须轮询状态寄存器判断"是谁完成的"——慢且易出错。

### 3.4 LLI 长链表传输穿插实时短任务

一条通道在执行 64KB scatter-gather 链表（几十段描述符），此时硬件事件触发需要紧急搬 64 字节——另一条通道可以**立刻响应**，插入短报文传输。

```bash
CH0: [desc0====][desc1====][desc2====][desc3====][desc4====]...  (LLI 链表, 64KB)
CH1:                [紧急64B]                                        (插入, 不阻塞)
单通道: 必须等 CH0 跑完 64KB 才能响应紧急任务 → 延迟可达 ms 级
多通道: CH1 仲裁获得总线 → 几百 ns 内完成紧急传输 → 延迟 μs 级
```

### 3.5 驱动软件解耦

不同业务静态分配专属通道，传输上下文互不干扰：

| 业务 | 通道 | 说明 |
|------|------|------|
| 视频流 | ch0~ch1 | 高带宽，连续传输 |
| 控制报文 | ch2 | 低延迟，小包 |
| 日志搬运 | ch3 | 低优先级，不抢带宽 |

驱动层无需复杂锁争抢同一个通道资源，每条通道独立管理自己的描述符队列和中断。

---

## §四 两种极端场景对比

### 场景 A：单 AXI 端口 + 多通道（绝大多数嵌入式 DMAC，如 PL330）

```bash
✅ 通道 FSM 独立——可以同时就绪、并行发起请求
✅ 数据流隔离——低速不阻塞高速
✅ 优先级抢占——紧急通道打断普通通道
✅ 独立中断——多外设无需轮询
❌ 带宽不叠加——AXI 出口时分复用，总带宽 = 单端口带宽
```

### 场景 B：多独立端口（高端 SoC / XDMA / FPGA AXI-DMA）

```bash
✅ 通道 FSM 独立 + 总线端口独立
✅ 数据真正并行搬运——带宽线性叠加
✅ FPGA AXI-DMA: MM2S 和 S2MM 各占一条 AXI 通路，全双工互不争抢
✅ XDMA: 每对 H2D/D2H 独立 PCIe 流量通路，多通道带宽隔离
```

---

## §五 FPGA 高速场景

### 5.1 AXI DMA：收发天然全双工

标准 Xilinx AXI DMA IP 内部：

```bash
MM2S 通道（Mem→Stream）: 独立 AXI Read Master  + 独立 FSM
S2MM 通道（Stream→Mem）: 独立 AXI Write Master + 独立 FSM
两套硬件完全独立 → 收发同时进行，互不抢占，真正全双工。
```

### 5.2 XDMA（PCIe DMA）：多通道带宽隔离

每一对 H2D（Host→Device）/ D2H（Device→Host）都是：

- 独立传输引擎
- 独立 PCIe Tag 空间
- 独立中断

多通道可以在 PCIe 链路上**真正并行搬运**，带宽相互隔离——这对 FPGA 加速卡上的多算法流水线至关重要。

---

## §六 总结

| 问题 | 答案 |
|------|------|
| 通道 FSM 能并行吗 | **能**——每通道独立硬件状态机 |
| 数据一定并行吞吐吗 | **不一定**——取决于后端总线端口是否独立 |
| 总线共享时多通道意义何在 | **数据流隔离 + 优先级抢占 + 独立中断 + 长短任务互不阻塞** |
| 何时带宽能叠加 | 每个通道绑定独立 AXI/PCIe 端口 |

> **一句话**：多通道 DMA 真正的价值不在于"N 倍带宽"，而在于"N 条数据流互不阻塞"——低速不拖累高速，紧急能插队，长短各走各的。这在嵌入式实时系统和 FPGA 硬件流水线中是不可替代的。

---

> 配套：DMA 原理见 [dma.md](/concepts/io/dma.md)，8237 四通道经典架构见 [dma-8237.md](/concepts/io/dma-8237.md)，CPU 与 DMA 引擎的完整交互过程见 [dma-cpu-interaction.md](/concepts/io/dma-cpu-interaction.md)，PCIe 多通道流量通路见 [pcie.md](/concepts/io/pcie/pcie.md)。

