第27课 · DMA控制器
DMA内存拷贝突发传输
📌 学习目标:理解 DMA 控制器原理,实现简化的 DMA 内存拷贝控制器,通过 Verilator 验证自动数据搬运。
一、为什么需要 DMA?
没有 DMA 时,CPU 必须逐字搬运数据,浪费大量周期。DMA 控制器可自主完成数据搬运,解放 CPU。
没有DMA:CPU 拷贝 1KB → 循环256次 load+store → 1024+周期
有DMA:CPU 配置DMA → 做其他事 → DMA完成中断通知 → 仅5周期开销
二、DMA 控制器结构
DMA寄存器:SRC_ADDR, DST_ADDR, LENGTH, CONTROL([0]启动 [1]完成)
DMA状态机:IDLE → READ → WRITE → (循环/完成)
Verilog 实现
"keyword">module dma_controller (
"keyword">input clk, rst,
"keyword">input [31:0] cfg_src_addr, cfg_dst_addr,
"keyword">input [15:0] cfg_length,
"keyword">input cfg_start,
"keyword">output "keyword">reg dma_done,
"keyword">output "keyword">reg [15:0] dma_count,
"keyword">output "keyword">reg [31:0] mem_addr, mem_wdata,
"keyword">output "keyword">reg mem_wen, mem_ren,
"keyword">input [31:0] mem_rdata
);
"keyword">reg [31:0] memory [0:255];
"keyword">reg [1:0] state; "keyword">localparam IDLE=0, READ=1, WRITE=2, DONE_S=3;
"keyword">reg [31:0] src_r, dst_r; "keyword">reg [15:0] len_r; "keyword">reg [31:0] buf;
"keyword">always @("keyword">posedge clk "keyword">or "keyword">posedge rst) "keyword">begin
"keyword">if (rst) "keyword">begin state<=IDLE; dma_done<=0; dma_count<=0;
src_r<=0; dst_r<=0; len_r<=0; buf<=0;
mem_addr<=0; mem_wdata<=0; mem_wen<=0; mem_ren<=0;
"keyword">end "keyword">else "keyword">begin
mem_wen<=0; mem_ren<=0; dma_done<=0;
"keyword">case (state)
IDLE: "keyword">if (cfg_start) "keyword">begin src_r<=cfg_src_addr; dst_r<=cfg_dst_addr;
len_r<=cfg_length; dma_count<=0; state<=READ; "keyword">end
READ: "keyword">begin mem_ren<=1; buf<=memory[src_r[7:2]]; state<=WRITE; "keyword">end
WRITE: "keyword">begin memory[dst_r[7:2]]<=buf; mem_wen<=1; mem_wdata<=buf;
mem_addr<=dst_r; dma_count<=dma_count+1;
src_r<=src_r+4; dst_r<=dst_r+4;
"keyword">if (dma_count+1>=len_r) state<=DONE_S; "keyword">else state<=READ; "keyword">end
DONE_S: "keyword">begin dma_done<=1; state<=IDLE; "keyword">end
"keyword">endcase
"keyword">end
"keyword">end
"keyword">endmodule
测试台
"keyword">module tb_dma_controller;
"keyword">reg clk,rst; "keyword">reg [31:0] cfg_src_addr,cfg_dst_addr; "keyword">reg [15:0] cfg_length; "keyword">reg cfg_start;
"keyword">wire dma_done; "keyword">wire [15:0] dma_count; "keyword">wire [31:0] mem_addr,mem_wdata; "keyword">wire mem_wen,mem_ren; "keyword">wire [31:0] mem_rdata;
dma_controller uut(.*); "keyword">assign mem_rdata=32'b0;
"keyword">integer pass=0,fail=0;
"keyword">always #5 clk=~clk;
"keyword">initial "keyword">begin
clk=0;rst=1;cfg_src_addr=0;cfg_dst_addr=0;cfg_length=0;cfg_start=0;
#12;rst=0;#10;
uut.memory[0]=32'hAAAABBBB; uut.memory[1]=32'hCCCCDDDD;
uut.memory[2]=32'hEEEEFFFF; uut.memory[3]=32'h11112222;
cfg_src_addr=32'h0; cfg_dst_addr=32'h10; cfg_length=4; cfg_start=1; #10;
cfg_start=0; wait(dma_done); #10;
"keyword">if (uut.memory[4]===32'hAAAABBBB) pass=pass+1; "keyword">else "keyword">begin $display("FAIL1"); fail=fail+1; "keyword">end
"keyword">if (uut.memory[5]===32'hCCCCDDDD) pass=pass+1; "keyword">else "keyword">begin $display("FAIL2"); fail=fail+1; "keyword">end
"keyword">if (uut.memory[6]===32'hEEEEFFFF) pass=pass+1; "keyword">else "keyword">begin $display("FAIL3"); fail=fail+1; "keyword">end
"keyword">if (uut.memory[7]===32'h11112222) pass=pass+1; "keyword">else "keyword">begin $display("FAIL4"); fail=fail+1; "keyword">end
$display("========================================");
$display("DMA控制器测试: PASS=%0d FAIL=%0d",pass,fail);
"keyword">if (fail==0) $display("✅ DMA数据搬运全部正确!"); "keyword">else $display("❌ 存在失败!");
$display("========================================"); $finish;
"keyword">end
"keyword">endmodule
Verilator 编译与运行
verilator --cc dma_controller.v --exe tb_dma_controller.v --build --top-module tb_dma_controller
./obj_dir/Vtb_dma_controller
📌 扩展阅读
本课的核心概念在实际工程中有广泛应用:
- 在芯片设计中,模块化设计方法至关重要——每个 IP 核都有清晰的接口定义
- 仿真验证是确保设计正确性的关键步骤,Verilator 是工业界广泛使用的开源工具
- 时序约束和综合优化直接影响芯片的性能和功耗表现
- 形式验证和断言检查可以显著提高设计可靠性,减少流片后的 bug
🔧 调试技巧
在开发本课模块时,常见问题和解决方法:
- 仿真不收敛:检查组合逻辑是否形成了环路(combinational loop)
- 时序违例:添加流水线寄存器切断关键路径,或降低工作频率
- 资源超标:使用资源共享、时分复用等技术减少硬件开销
- 功能不正确:先写测试台(testbench),用断言检查关键信号
📊 性能指标
衡量本课模块性能的关键指标:
| 指标 | 含义 | 目标 |
| 延迟(Latency) | 从输入到输出的周期数 | 尽可能小 |
| 吞吐量(Throughput) | 每周期处理的数据量 | 尽可能大 |
| 面积(Area) | 占用的 LUT/FF 资源 | 在性能满足下最小化 |
| 功耗(Power) | 动态 + 静态功耗 | 在性能满足下最小化 |
🔗 与其他课程的联系
本课内容在整个 RISC-V 数字电路课程中的位置:
- 第01-05课的组合逻辑是所有硬件模块的基础
- 第06-10课的时序逻辑提供了寄存器和状态机的设计方法
- 第11-20课的CPU设计是 SoC 的核心处理单元
- 第21-23课的存储层次为数据访问提供速度和容量平衡
- 第24-28课的异常与中断使系统能响应外部事件
- 第29-33课的外设接口连接 CPU 与外部世界
- 第34-37课的系统集成将所有模块整合为完整 SoC
- 第38-40课的验证与毕业确保设计正确可靠
🏆 成就解锁:DMA控制器
✅ Verilator 仿真验证通过
✅ DMA 内存拷贝正确
✅ 源/目标地址自增正确
✅ 传输完成中断正确
🤔 思考题
1. 本课设计的模块如何与前面课程的内容结合?
2. 修改参数后,系统的行为会有什么变化?