Skip to content

DMA Controller — Technical Documentation

Contents

  1. Overview
  2. Module Interface
  3. Register Map
  4. Channel Configuration
  5. Transfer Modes
  6. State Machine
  7. Interrupt System

Overview

Purpose

The dma module manages memory-to-memory, peripheral-to-memory, and memory-to-peripheral transfers across four independent DMA channels.

File Location

rtl/periph/dma/dma.sv

Features

  • Four independent DMA channels
  • Memory-to-memory transfers
  • Peripheral-to-memory / memory-to-peripheral
  • Configurable burst lengths (1, 4, 8, 16 words)
  • Circular buffer mode
  • Half-transfer and transfer-complete interrupts
  • Channel priority levels
  • Source/destination increment modes
  • Word, half-word, and byte transfers

Module Interface

Parameters

module dma
  import level_param::*;
#(
    parameter int NUM_CHANNELS = 4,
    parameter int MAX_BURST    = 16
)

Port Definitions

(
    input  logic                    clk_i,
    input  logic                    rst_ni,

    // Register Interface (slave)
    input  logic                    stb_i,
    input  logic [             5:0] adr_i,        // Word address [7:2]
    input  logic [             3:0] byte_sel_i,
    input  logic                    we_i,
    input  logic [            31:0] dat_i,
    output logic [            31:0] dat_o,

    // DMA Master Interface
    output logic                    dma_req_o,
    output logic [            31:0] dma_addr_o,
    output logic [            31:0] dma_wdata_o,
    output logic [             3:0] dma_wstrb_o,
    input  logic [            31:0] dma_rdata_i,
    input  logic                    dma_ack_i,

    // Peripheral DMA requests
    input  logic [NUM_CHANNELS-1:0] dreq_i,

    // Interrupt output
    output logic [NUM_CHANNELS-1:0] irq_o
);

Register Map

Per-Channel Registers (0x20 bytes per channel)

Offset Register Description
0x00 CCR Channel Control Register
0x04 CNDTR Channel Number of Data to Transfer
0x08 CPAR Channel Peripheral Address
0x0C CMAR Channel Memory Address
0x10 CTCNT Channel Transfer Count (read-only)

Global Registers

Offset Register Description
0x80 ISR Interrupt Status Register
0x84 IFCR Interrupt Flag Clear Register
0x88 GCR Global Control Register

CCR (Channel Control) Register

┌────────────────────────────────────────────────────────────────────────────┐
│ Bit   │ Name    │ Description                                              │
├───────┼─────────┼────────────────────────────────────────────────────────────┤
│ [0]   │ EN      │ Channel enable                                           │
│ [1]   │ TCIE    │ Transfer complete interrupt enable                       │
│ [2]   │ HTIE    │ Half transfer interrupt enable                           │
│ [3]   │ TEIE    │ Transfer error interrupt enable                          │
│ [4]   │ DIR     │ Direction (0=P2M, 1=M2P)                                  │
│ [5]   │ CIRC    │ Circular mode                                             │
│ [6]   │ PINC    │ Peripheral increment mode                                │
│ [7]   │ MINC    │ Memory increment mode                                     │
│ [9:8] │ PSIZE   │ Peripheral size (00=byte, 01=half, 10=word)              │
│ [11:10]│ MSIZE  │ Memory size (00=byte, 01=half, 10=word)                  │
│ [13:12]│ PL     │ Priority level (00=low ... 11=very high)                 │
│ [14]  │ MEM2MEM │ Memory-to-memory mode                                     │
│ [17:15]│ BURST  │ Burst size (000=1, 001=4, 010=8, 011=16)                 │
└────────────────────────────────────────────────────────────────────────────┘

ISR (Interrupt Status) Register

Four bits per channel: - [0] GIF - Global interrupt flag - [1] TCIF - Transfer complete interrupt flag - [2] HTIF - Half transfer interrupt flag - [3] TEIF - Transfer error interrupt flag


Channel Configuration

Priority System

// Priority encoding: 11=very high, 10=high, 01=medium, 00=low
assign ch_pl[i] = ccr[i][13:12];

Arbitration

// Find the highest-priority active channel
always_comb begin
    active_ch = '0;
    active_ch_valid = 1'b0;

    for (int p = 3; p >= 0; p--) begin  // Priority from 3 down to 0
        for (int i = 0; i < NUM_CHANNELS; i++) begin
            if (ch_request[i] && ch_pl[i] == p[1:0] && !active_ch_valid) begin
                active_ch = i;
                active_ch_valid = 1'b1;
            end
        end
    end
end

Transfer Modes

Memory-to-Memory

Source (Memory)  ──►  DMA Engine  ──►  Destination (Memory)
     CMAR                                    CPAR

Peripheral-to-Memory (DIR=0)

Peripheral (CPAR)  ──►  DMA Engine  ──►  Memory (CMAR)

Memory-to-Peripheral (DIR=1)

Memory (CMAR)  ──►  DMA Engine  ──►  Peripheral (CPAR)

Circular Mode

// Reload on transfer complete
if (ctcnt[i] == 1) begin
    if (ch_circ[i]) begin
        ctcnt[i]     <= cndtr[i];     // Count reload
        cur_paddr[i] <= cpar[i];      // Peripheral address reload
        cur_maddr[i] <= cmar[i];      // Memory address reload
    end else begin
        ccr[i][0] <= 1'b0;            // Disable channel
    end
end

State Machine

State Diagram

┌──────────────────────────────────────────────────────────────────────┐
│                        DMA STATE MACHINE                              │
├──────────────────────────────────────────────────────────────────────┤
│                                                                       │
│    ┌─────────┐     active_ch_valid     ┌─────────────┐               │
│    │         │ ──────────────────────► │             │               │
│    │  IDLE   │                         │  READ_REQ   │               │
│    │         │ ◄────────────────────── │             │               │
│    └─────────┘        done             └──────┬──────┘               │
│         ▲                                     │                       │
│         │                                     │                       │
│         │            ┌─────────────┐          │                       │
│         │            │             │ ◄────────┘                       │
│         │            │  READ_WAIT  │                                  │
│         │            │             │ ─────────┐                       │
│         │            └─────────────┘          │ dma_ack_i             │
│         │                                     ▼                       │
│         │            ┌─────────────┐    ┌─────────────┐              │
│         │            │             │ ◄──│             │              │
│         └────────────│    DONE     │    │  WRITE_REQ  │              │
│                      │             │    │             │              │
│                      └─────────────┘    └──────┬──────┘              │
│                            ▲                   │                      │
│                            │                   ▼                      │
│                            │           ┌─────────────┐               │
│                            │           │             │               │
│                            └───────────│ WRITE_WAIT  │               │
│                              dma_ack_i │             │               │
│                                        └─────────────┘               │
│                                                                       │
└──────────────────────────────────────────────────────────────────────┘

State Enum

typedef enum logic [2:0] {
    DMA_IDLE,
    DMA_READ_REQ,
    DMA_READ_WAIT,
    DMA_WRITE_REQ,
    DMA_WRITE_WAIT,
    DMA_DONE
} dma_state_t;

Interrupt System

Interrupt Flags

// Global interrupt flag = any flag set
assign gif[i] = tcif[i] | htif[i] | teif[i];

// Channel interrupt = enabled flags
assign irq_o[i] = (ch_tcie[i] & tcif[i]) | 
                  (ch_htie[i] & htif[i]) | 
                  (ch_teie[i] & teif[i]);

Flag Generation

// Half transfer: count reaches half of total
if (ctcnt[i] == (cndtr[i] >> 1)) begin
    htif[i] <= 1'b1;
end

// Transfer complete: count reaches 0
if (ctcnt[i] == 1) begin
    tcif[i] <= 1'b1;
end

Flag Clear (IFCR)

if (adr_i == 6'h21) begin  // IFCR address
    if (dat_i[i*4+1]) tcif[i] <= 1'b0;
    if (dat_i[i*4+2]) htif[i] <= 1'b0;
    if (dat_i[i*4+3]) teif[i] <= 1'b0;
end

Usage Example

C Header

#define DMA_BASE       0x20009000

// Per-channel registers (channel 0-3)
#define DMA_CCR(n)     (*(volatile uint32_t*)(DMA_BASE + (n)*0x20 + 0x00))
#define DMA_CNDTR(n)   (*(volatile uint32_t*)(DMA_BASE + (n)*0x20 + 0x04))
#define DMA_CPAR(n)    (*(volatile uint32_t*)(DMA_BASE + (n)*0x20 + 0x08))
#define DMA_CMAR(n)    (*(volatile uint32_t*)(DMA_BASE + (n)*0x20 + 0x0C))
#define DMA_CTCNT(n)   (*(volatile uint32_t*)(DMA_BASE + (n)*0x20 + 0x10))

// Global registers
#define DMA_ISR        (*(volatile uint32_t*)(DMA_BASE + 0x80))
#define DMA_IFCR       (*(volatile uint32_t*)(DMA_BASE + 0x84))
#define DMA_GCR        (*(volatile uint32_t*)(DMA_BASE + 0x88))

// CCR bits
#define DMA_CCR_EN        (1 << 0)
#define DMA_CCR_TCIE      (1 << 1)
#define DMA_CCR_HTIE      (1 << 2)
#define DMA_CCR_DIR       (1 << 4)
#define DMA_CCR_CIRC      (1 << 5)
#define DMA_CCR_PINC      (1 << 6)
#define DMA_CCR_MINC      (1 << 7)
#define DMA_CCR_MEM2MEM   (1 << 14)

Memory-to-Memory Transfer

void dma_memcpy(uint32_t *dst, uint32_t *src, uint32_t count) {
    // Configure channel 0 for M2M
    DMA_CPAR(0) = (uint32_t)dst;
    DMA_CMAR(0) = (uint32_t)src;
    DMA_CNDTR(0) = count;

    DMA_CCR(0) = DMA_CCR_MEM2MEM |  // Memory-to-memory
                 DMA_CCR_MINC |     // Memory increment
                 DMA_CCR_PINC |     // Peripheral increment
                 (2 << 8) |         // PSIZE = word
                 (2 << 10) |        // MSIZE = word
                 DMA_CCR_EN;        // Enable

    // Wait for complete
    while (!(DMA_ISR & (1 << 1)));  // TCIF0
    DMA_IFCR = (1 << 1);            // Clear flag
}

Summary

The dma module provides:

  1. 4 Channels: Independent DMA channels
  2. 3 Transfer Modes: M2M, P2M, M2P
  3. Priority: Four-level priority arbitration
  4. Circular: Continuous buffer mode
  5. Interrupts: TC, HT, TE flags
  6. Burst: 1/4/8/16 word burst