0
0
VhdlHow-ToIntermediate · 4 min read

VHDL Code for DMA Controller: Syntax, Example, and Tips

A DMA controller in VHDL manages direct memory access by controlling data transfers between memory and peripherals without CPU intervention. The code typically includes state machines for control signals like start, done, and address counters. You can implement it using processes and signals to handle read/write cycles and handshaking.
📐

Syntax

The basic structure of a DMA controller in VHDL includes:

  • Entity: Defines inputs and outputs like clock, reset, start signal, source/destination addresses, and data buses.
  • Architecture: Contains the logic, usually a state machine, to control the transfer process.
  • Processes: Handle synchronous operations like address increment, data transfer, and status signaling.
vhdl
entity dma_controller is
    Port (
        clk       : in  std_logic;
        reset     : in  std_logic;
        start     : in  std_logic;
        src_addr  : in  std_logic_vector(15 downto 0);
        dest_addr : in  std_logic_vector(15 downto 0);
        length    : in  std_logic_vector(15 downto 0);
        mem_read  : out std_logic;
        mem_write : out std_logic;
        mem_addr  : out std_logic_vector(15 downto 0);
        mem_data_in  : in  std_logic_vector(7 downto 0);
        mem_data_out : out std_logic_vector(7 downto 0);
        done      : out std_logic
    );
end dma_controller;
💻

Example

This example shows a simple DMA controller that copies data from a source address to a destination address for a given length. It uses a state machine to control the read and write cycles and signals when the transfer is done.

vhdl
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity dma_controller is
    Port (
        clk       : in  std_logic;
        reset     : in  std_logic;
        start     : in  std_logic;
        src_addr  : in  std_logic_vector(15 downto 0);
        dest_addr : in  std_logic_vector(15 downto 0);
        length    : in  std_logic_vector(15 downto 0);
        mem_read  : out std_logic;
        mem_write : out std_logic;
        mem_addr  : out std_logic_vector(15 downto 0);
        mem_data_in  : in  std_logic_vector(7 downto 0);
        mem_data_out : out std_logic_vector(7 downto 0);
        done      : out std_logic
    );
end dma_controller;

architecture Behavioral of dma_controller is
    type state_type is (IDLE, READ, WRITE, DONE);
    signal state       : state_type := IDLE;
    signal count       : unsigned(15 downto 0) := (others => '0');
    signal current_src : unsigned(15 downto 0);
    signal current_dest: unsigned(15 downto 0);
    signal data_buffer : std_logic_vector(7 downto 0);
begin
    process(clk, reset)
    begin
        if reset = '1' then
            state <= IDLE;
            mem_read <= '0';
            mem_write <= '0';
            mem_addr <= (others => '0');
            mem_data_out <= (others => '0');
            done <= '0';
            count <= (others => '0');
            current_src <= (others => '0');
            current_dest <= (others => '0');
        elsif rising_edge(clk) then
            case state is
                when IDLE =>
                    done <= '0';
                    mem_read <= '0';
                    mem_write <= '0';
                    if start = '1' then
                        current_src <= unsigned(src_addr);
                        current_dest <= unsigned(dest_addr);
                        count <= unsigned(length);
                        state <= READ;
                    end if;
                when READ =>
                    mem_addr <= std_logic_vector(current_src);
                    mem_read <= '1';
                    mem_write <= '0';
                    data_buffer <= mem_data_in;
                    state <= WRITE;
                when WRITE =>
                    mem_addr <= std_logic_vector(current_dest);
                    mem_read <= '0';
                    mem_write <= '1';
                    mem_data_out <= data_buffer;
                    if count = 0 then
                        state <= DONE;
                    else
                        current_src <= current_src + 1;
                        current_dest <= current_dest + 1;
                        count <= count - 1;
                        state <= READ;
                    end if;
                when DONE =>
                    mem_read <= '0';
                    mem_write <= '0';
                    done <= '1';
                    if start = '0' then
                        state <= IDLE;
                    end if;
                when others =>
                    state <= IDLE;
            end case;
        end if;
    end process;
end Behavioral;
Output
No direct console output; the DMA controller signals 'done' when transfer completes.
⚠️

Common Pitfalls

Common mistakes when writing a DMA controller in VHDL include:

  • Not properly synchronizing signals with the clock, causing glitches.
  • Forgetting to reset internal counters and states on reset.
  • Incorrectly handling the done signal, which can cause the controller to restart unexpectedly.
  • Not managing read/write enable signals properly, leading to bus contention.

Always use a clear state machine and reset all signals to known states.

vhdl
---- Wrong: Missing reset for count and state
process(clk)
begin
  if rising_edge(clk) then
    if start = '1' then
      count <= count - 1; -- count not reset on reset
    end if;
  end if;
end process;

---- Right: Proper reset and state management
process(clk, reset)
begin
  if reset = '1' then
    count <= (others => '0');
    state <= IDLE;
  elsif rising_edge(clk) then
    -- state machine logic
  end if;
end process;
📊

Quick Reference

Tips for writing a DMA controller in VHDL:

  • Use a finite state machine to control the transfer phases.
  • Synchronize all signals with the clock and reset.
  • Use unsigned types for address and length counters.
  • Buffer data between read and write cycles to avoid timing issues.
  • Signal completion clearly with a done output.

Key Takeaways

A DMA controller in VHDL uses a state machine to manage memory read/write cycles without CPU intervention.
Always reset internal counters and states to avoid unpredictable behavior.
Synchronize all control signals with the clock to prevent glitches.
Buffer data between read and write phases to ensure correct data transfer.
Use a clear done signal to indicate transfer completion.