Lesson 33

🚌 DMA控制器

🏆 内存搬运不占CPU
✅ Verilator仿真验证通过

📖 实验描述

实现DMA控制器,可配置源地址、目标地址和传输长度,自动完成内存块搬运。

DMA(Direct Memory Access)让外设直接访问内存,不经过CPU,大幅提高数据搬运效率。 DMA工作流程: 1. CPU配置:源地址、目标地址、传输长度 2. CPU启动DMA,然后去做别的事 3. DMA自动:读源地址→写目标地址→地址递增→计数 4. 传输完成:DMA发出中断通知CPU DMA vs CPU搬运: - CPU搬运:每字4+时钟/字 - DMA搬运:每字2-3时钟/字 - CPU在DMA传输期间可执行其他任务

🧠 核心概念

📐 电路结构

电路与状态图
DMA控制器架构:

  ┌─────────────────────────────────────┐
  │           DMA Controller            │
  │  CPU配置: src_addr, dst_addr, len   │
  │       ↓                              │
  │  状态机: IDLE→READ→WAIT→WRITE→WAIT  │
  │                └──→ 循环 ←──┘       │
  │                     │               │
  │               count==len? → DONE    │
  └─────────────────────────────────────┘
       │                    │
   mem_addr(rd)       mem_addr(wr)

📝 设计步骤

  1. 1定义CPU配置接口(src/dst/len/start)
  2. 2设计DMA状态机:IDLE→READ→WRITE→循环→DONE
  3. 3READ阶段:发出读请求,等待mem_ready
  4. 4WRITE阶段:发出写请求,地址递增
  5. 5计数器跟踪传输进度
  6. 6传输完成产生dma_done信号

💻 Verilog实现

dma_controller.svSystemVerilog · Verilator 5.020
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
// DMA Controller — DMA控制器
module dma_controller #(parameter ADDR_W=16, DATA_W=32)(
    input wire clk, rst,
    input wire [ADDR_W-1:0] src_addr, dst_addr,
    input wire [15:0] transfer_len, input wire dma_start,
    output wire dma_done, dma_busy,
    output reg [ADDR_W-1:0] mem_addr,
    output wire [DATA_W-1:0] mem_wdata, output wire mem_wr, mem_rd,
    input wire [DATA_W-1:0] mem_rdata, input wire mem_ready
);
typedef enum logic [2:0] {S_IDLE,S_READ,S_READ_WAIT,S_WRITE,S_WRITE_WAIT,S_DONE} state_t;
state_t state; reg [ADDR_W-1:0] src_reg,dst_reg; reg [15:0] len_reg,count_reg; reg [DATA_W-1:0] hold_data;
assign dma_busy = state != S_IDLE; assign dma_done = state == S_DONE;
always @(posedge clk or posedge rst) begin
    if (rst) begin state<=S_IDLE;src_reg<=0;dst_reg<=0;len_reg<=0;count_reg<=0;hold_data<=0;mem_addr<=0; end
    else begin
        case (state)
            S_IDLE: if (dma_start) begin src_reg<=src_addr;dst_reg<=dst_addr;len_reg<=transfer_len;count_reg<=0;mem_addr<=src_addr;state<=S_READ; end
            S_READ: state<=S_READ_WAIT;
            S_READ_WAIT: if (mem_ready) begin hold_data<=mem_rdata;mem_addr<=dst_reg;state<=S_WRITE; end
            S_WRITE: state<=S_WRITE_WAIT;
            S_WRITE_WAIT: if (mem_ready) begin
                count_reg<=count_reg+1;
                if (count_reg>=len_reg-1) state<=S_DONE;
                else begin src_reg<=src_reg+1;dst_reg<=dst_reg+1;mem_addr<=src_reg+1;state<=S_READ; end
            end
            S_DONE: state<=S_IDLE;
            default: state<=S_IDLE;
        endcase
    end
end
assign mem_wdata=hold_data; assign mem_wr=(state==S_WRITE||state==S_WRITE_WAIT);
assign mem_rd=(state==S_READ||state==S_READ_WAIT);
endmodule

🔬 仿真说明

仿真环境与策略

测试:配置src=0x1000, dst=0x2000, len=16。启动DMA后验证地址递增,16次传输后dma_done=1。CPU在DMA传输期间可执行其他操作。

✅ 验证结果

Verilator 5.020 仿真通过

本实验所有Verilog代码已通过Verilator编译验证,功能行为正确。测试用例覆盖核心功能路径,确保设计满足规格要求。

⚠️ 常见错误

🌍 真实世界

工业应用

STM32的DMA支持8路通道、循环模式和双缓冲。PCIe DMA可达16GB/s。NVMe SSD的DMA直接将数据搬入主机内存。ARM SMMU为DMA提供地址翻译和安全隔离。

💡 扩展挑战

🚀 自己动手

# 编译验证
verilator --lint-only dma_controller.sv --timing
verilator --cc dma_controller.sv --exe tb.cpp --build -j 0 --timing

🎯 试一试

修改参数

尝试修改代码中的关键参数,观察仿真结果变化:

  • 调整深度/位宽,观察行为变化
  • 修改时钟分频,测试不同速度
  • 改变初始值/种子,观察不同起始条件

💡 Verilator会在位宽不匹配时给出Warning,这是学习的好机会

添加功能

在现有基础上增加新功能:

  • 添加新的控制信号和状态
  • 增加状态或修改状态转移条件
  • 扩展输出,增加更多信息

🔧 增量开发:每次只改一个地方,验证通过后再改下一个

📋 Verilog速查

语法说明示例
reg [7:0]8位寄存器reg [7:0] data;
wire组合逻辑连线wire valid = cnt > 5;
always @(posedge clk)时序逻辑上升沿触发
always @(*)组合逻辑敏感列表自动推导
localparam局部常量localparam DIV = 50000000;
case多分支选择注意default分支
$clog2计算位宽$clog2(16)=4

📊 性能指标

资源估算(FPGA参考)

本设计在典型FPGA上的资源占用估算:LUT约20-150个,FF约30-120个,无BRAM/DSP依赖(特殊模块除外)。时钟频率可达50-100MHz+。Verilator仿真速度约5-10M周期/秒。

← 上一课 📚 目录 下一课 →