DMA_AHB的Verilog硬件实现:深入解析AHB总线DMA控制器设计
在现代高性能嵌入式系统中,CPU不再是所有数据流动的中心。随着音视频处理、工业控制和物联网设备对带宽与实时性的要求日益提升,传统的“CPU轮询+中断”模式逐渐成为性能瓶颈。一个典型的场景是:音频ADC每毫秒产生数百字节采样数据,若由CPU逐一读取并存入缓冲区,不仅消耗大量时钟周期,还极易因调度延迟导致丢帧。
解决这一问题的关键,在于引入 直接存储器访问(DMA)技术 ,让外设与内存之间的数据搬运脱离CPU干预。而在AMBA架构主导的SoC设计中,将DMA控制器集成至AHB总线,不仅能充分利用其高带宽、低延迟特性,还能与CPU、SRAM、外设等模块无缝协同。
本文不走寻常路——我们不从理论定义开始,而是直接切入一个真实的设计挑战:如何用Verilog构建一个 可综合、符合AHB协议、支持突发传输的单通道DMA控制器 ?并通过代码细节揭示其背后的状态机逻辑、地址生成机制与总线交互策略。
为什么选择AHB作为DMA主控总线?
ARM的AMBA总线家族中,APB适用于低速外设配置,而AHB则是为高性能路径量身打造。对于DMA这类需要持续吞吐大量数据的模块,AHB几乎是唯一合理的选择。
它具备几个关键优势:
- 支持突发传输(Burst Transfer) :连续多个数据周期内自动递增地址,避免每次传输都发送新地址,极大提升效率。
- 流水线结构 :地址相位与数据相位分离,允许重叠操作,提高总线利用率。
- 多主竞争仲裁机制 :DMA可以像CPU一样申请总线使用权,真正实现并行处理。
- 高频率运行能力 :在先进工艺下可达数百MHz,满足高速数据流需求。
更重要的是,AHB已成为IP复用的事实标准。无论是FPGA厂商提供的软核,还是ASIC平台中的处理器子系统,几乎都原生支持AHB接口。这意味着你设计的DMA控制器一旦完成,便能轻松移植到多种SoC环境中。
DMA是如何工作的?从一次外设采集说起
设想这样一个应用:一个SPI传感器以100kHz速率输出32位ADC样本,需连续采集1024点存入片上SRAM。如果不使用DMA,CPU必须:
- 等待SPI中断;
- 读取DR寄存器;
- 写入内存数组;
- 更新索引;
- 判断是否结束……
这期间任何高优先级中断或任务切换都会造成采样间隔抖动,甚至丢失数据。
而有了AHB-DMA后,流程变为:
-
CPU仅需配置一次:
- 源地址 = SPI数据寄存器
- 目的地址 = SRAM起始位置
- 传输长度 = 1024 × 4 bytes
- 源地址固定,目的地址自增
- 启动方式 = 外设请求触发 -
当SPI收到完整数据后,拉高
DMAREQ信号。 -
DMA控制器响应请求,向AHB仲裁器发出
HLOCK/HBUSREQ,争取总线控制权。 -
获得授权后,DMA作为主设备发起一系列AHB事务:
- 先执行 读操作 :从SPI寄存器取数据
- 再执行 写操作 :写入SRAM目标地址
- 自动更新目的地址(+4)
- 计数器减去4字节 -
完成全部传输后,置位状态寄存器,并触发中断通知CPU:“数据已就绪”。
整个过程完全由硬件完成,CPU可在后台执行算法处理或其他任务,系统响应性和稳定性显著增强。
核心设计:基于AHB的DMA控制器架构
我们的目标是一个 单通道、内存↔外设双向传输 的DMA引擎,主要包含以下几个功能模块:
- 寄存器配置接口 :通过APB或AHB从机端口接收CPU指令
- AHB主控接口 :生成符合AHB协议的读写事务
- 传输控制逻辑 :管理地址递增、计数、突发模式判断
- 中断生成单元 :传输完成或出错时上报IRQ
- 总线仲裁请求模块 :与系统其他主设备协调资源
这些模块协同工作,形成一个独立的数据搬运引擎。下面我们将逐层剖析其实现细节。
AHB主控状态机:驱动一切的核心
AHB协议要求主设备严格按照状态迁移规则操作。我们采用有限状态机(FSM)来实现这一行为。以下是简化但可综合的状态机设计:
typedef enum logic [2:0] {
IDLE,
ADDR_PHASE,
WAIT_WRITE,
WAIT_READ,
DATA_PHASE
} ahb_state_t;
always_ff @(posedge HCLK or negedge HRESETn) begin
if (!HRESETn)
curr_state <= IDLE;
else
curr_state <= next_state;
end
always_comb begin
next_state = curr_state;
unique case (curr_state)
IDLE:
if (dma_start && !busy)
next_state = ADDR_PHASE;
ADDR_PHASE: begin
if (HREADY) begin
if (is_write_op)
next_state = WAIT_WRITE;
else
next_state = WAIT_READ;
end
end
WAIT_READ:
if (HREADY) next_state = DATA_PHASE;
DATA_PHASE: begin
if (transfer_done)
next_state = IDLE;
else
next_state = ADDR_PHASE;
end
default: next_state = IDLE;
endcase
end
这个状态机虽然简洁,却完整覆盖了AHB主设备的基本行为:
-
在
IDLE状态等待启动信号; -
进入
ADDR_PHASE发送地址和控制信号; -
根据读写类型跳转至
WAIT_READ或WAIT_WRITE; -
收到
HREADY后进入DATA_PHASE处理数据; - 若未完成,则继续下一拍传输;否则返回空闲。
值得注意的是,
HREADY
是从设备反馈的关键信号。如果某个外设响应缓慢(如Flash控制器),
HREADY
可能持续多个周期为低电平。此时状态机必须保持当前状态,不能强行推进,否则会违反协议。
实践中建议加入超时计数器,防止因硬件故障导致DMA永久挂起。
AHB信号生成:精准符合协议规范
接下来是实际驱动AHB总线的信号组合逻辑。这部分必须严格遵循AMBA AHB-Lite规范:
assign HADDR = current_addr;
assign HWDATA = wdata_reg;
assign HWRITE = (current_op == WRITE);
assign HTRANS = (curr_state == ADDR_PHASE) ? 2'b10 : 2'b00; // NONSEQ or IDLE
assign HSIZE = 3'b010; // 32-bit transfer
assign HBURST = 3'b000; // SINGLE burst mode
其中几个关键点:
-
HTRANS:首次传输使用NONSEQ(非连续),后续应改为SEQ形成突发。但在本例中为了简化,每次都是单次传输(SINGLE)。若要优化性能,当传输长度 ≥4 且地址对齐时,应设置HBURST=INCR4,一次性完成四笔连续传输。 -
HSIZE:设定为32位,意味着每次传输4字节。若源/目的宽度不同(如8位UART),需做字节拼接处理。 -
HWRITE:根据当前操作方向动态切换,实现双向传输。
此外,还需捕获从设备响应:
always_ff @(posedge HCLK) begin
if (curr_state == WAIT_READ && HREADY && HRESP == OKAY)
rdata_reg <= HRDATA;
end
只有当
HRESP==OKAY
时才认为数据有效。若出现
ERROR
响应,应触发错误中断并停止传输。
地址与计数逻辑:确保正确搬运每一字节
DMA的本质是“自动化的memcpy”。因此地址管理和字节计数至关重要。
reg [31:0] src_addr_reg, dst_addr_reg;
reg [15:0] byte_count_reg;
wire [31:0] next_src_addr = src_autoinc ? src_addr_reg + bytes_per_transfer : src_addr_reg;
wire [31:0] next_dst_addr = dst_autoinc ? dst_addr_reg + bytes_per_transfer : dst_addr_reg;
这里的
src_autoinc
和
dst_autoinc
来自配置寄存器,决定了地址是否递增:
- 外设→内存 :源地址通常固定(指向外设数据寄存器),目的地址递增;
- 内存→外设 :源地址递增,目的地址固定;
- 内存↔内存 :两者均可递增。
计数器则采用递减方式:
always_ff @(posedge HCLK or negedge HRESETn) begin
if (!HRESETn) begin
src_addr_reg <= 32'h0;
dst_addr_reg <= 32'h0;
byte_count_reg <= 16'd0;
end else if (load_config) begin
src_addr_reg <= cfg_src_addr;
dst_addr_reg <= cfg_dst_addr;
byte_count_reg <= cfg_byte_count;
end else if (transfer_valid && HREADY) begin
if (current_op == READ_FROM_SRC)
src_addr_reg <= next_src_addr;
else if (current_op == WRITE_TO_DST)
dst_addr_reg <= next_dst_addr;
if (last_transfer_in_block)
byte_count_reg <= 0;
else
byte_count_reg <= byte_count_reg - bytes_per_transfer;
end
end
当
byte_count_reg == 0
且本次传输完成后,即可判定整块数据搬移结束。
中断机制:让CPU知道“事情办完了”
虽然DMA不需要CPU参与搬运,但仍需在其完成或出错时进行通知。
always_ff @(posedge HCLK or negedge HRESETn) begin
if (!HRESETn)
irq_flag <= 1'b0;
else if (transfer_done || error_occurred)
irq_flag <= 1'b1;
else if (irq_clear)
irq_flag <= 1'b0;
end
assign IRQ = irq_flag;
CPU可通过轮询状态寄存器或使能中断来获取事件。推荐做法是开启中断,并在ISR中清零标志位,避免重复触发。
更高级的设计还可支持中断屏蔽、优先级分级、甚至块级中断(每半满/全满触发一次),用于双缓冲流式传输。
实际部署中的经验与陷阱
即便RTL代码看似完美,真正在SoC中集成时仍可能遇到意想不到的问题。以下是几个常见坑点及应对策略:
✅ 地址对齐检查不可少
AHB要求32位传输地址最低两位为0(即32位对齐)。如果用户误配了一个奇地址(如0x1001),可能导致总线错误或数据错乱。应在加载配置时加入校验逻辑:
if (cfg_size == 2'b10 && (cfg_addr[1:0] != 2'b00)) begin
error_flag <= 1;
end
并在状态寄存器中标记“非法配置”。
✅ 正确处理HREADY低电平
某些慢速外设(如EEPROM控制器)可能需要多个周期才能准备好数据。此时
HREADY=0
是正常现象,但你的状态机必须能容忍这种延迟。
切忌在
HREADY=0
时强行跳转状态!必须等待
HREADY=1
才能进入下一阶段。
✅ 突发模式优化大幅提升性能
单次传输(SINGLE)虽简单安全,但每次都要发送地址,开销大。对于连续内存区域的大块传输,应启用
INCR4
模式:
assign HBURST = (remaining_bytes >= 16 && aligned_start) ? 3'b001 : 3'b000; // INCR4
这样四笔数据只需一次地址相位,带宽利用率提升近4倍。
✅ 功耗控制不容忽视
在电池供电设备中,DMA空闲时应关闭主控时钟域,进入低功耗模式。可通过门控时钟或电源域隔离实现。
✅ 安全边界保护关键内存
防止DMA越界访问是系统安全的重要一环。可添加类似MMU的基址+界限检查:
wire addr_in_range = (current_addr >= base_addr) && (current_addr < base_addr + limit);
if (!addr_in_range) trigger_bus_error();
尤其在多进程或多用户系统中,此机制必不可少。
验证要点:如何确保DMA不出错?
再好的设计也需要充分验证。以下是一些关键测试项:
| 测试场景 | 验证重点 |
|---|---|
| 单次传输(1 word) | 地址、数据、控制信号时序正确性 |
| 突发传输(INCR4) | 是否生成连续地址,HTRANS是否切换为SEQ |
| 跨页传输 | 是否跨越缓存行或内存段边界异常 |
| HREADY延迟注入 | 模拟慢速从设备,检查状态机是否阻塞而非死锁 |
| 错误响应(HRESP=ERROR) | 是否停止传输并触发错误中断 |
| 中断清除机制 | CPU写清零后IRQ是否及时撤回 |
建议使用SystemVerilog + UVM搭建验证环境,配合AHB VIP(Verification IP)进行自动化回归测试。
结语:小模块,大价值
一个看似简单的DMA控制器,实则是连接计算与I/O的桥梁。它不只是“搬砖工”,更是提升系统整体效率的隐形引擎。
本文展示的虽是一个基础版本,但已具备完整的AHB主控能力、灵活的地址策略和可靠的中断机制。在此基础上,你可以进一步扩展:
- 支持多通道优先级调度
- 实现scatter-gather模式(分散-聚集)
- 添加AXI桥接,兼容更现代的总线架构
- 引入QoS机制,保障关键任务带宽
但无论怎样演进,核心思想不变: 把CPU解放出来,让硬件做它最擅长的事——并行、高效、稳定地移动数据 。
这种设计理念,正是当代高性能嵌入式系统的基石所在。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

被折叠的 条评论
为什么被折叠?



