0
0
VhdlHow-ToBeginner · 4 min read

VHDL Code for SPI Master: Syntax, Example, and Tips

A basic SPI master in VHDL controls the clock, data out (MOSI), and chip select signals to communicate with SPI slaves. The code includes a clock divider, state machine for sending bits, and output signals SCLK, MOSI, and CS. This design sends one byte per transaction and can be customized for your SPI device.
📐

Syntax

The SPI master in VHDL typically includes these parts:

  • Inputs: system clock, reset, data to send, start signal
  • Outputs: SPI clock (SCLK), Master Out Slave In (MOSI), chip select (CS), and a done flag
  • Clock divider: to generate SPI clock from system clock
  • State machine: controls sending bits and toggling signals

Below is the basic syntax pattern for an SPI master process.

vhdl
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity spi_master is
    Port (
        clk       : in  std_logic;       -- system clock
        rst       : in  std_logic;       -- reset
        start     : in  std_logic;       -- start transmission
        data_in   : in  std_logic_vector(7 downto 0); -- byte to send
        sclk      : out std_logic;       -- SPI clock
        mosi      : out std_logic;       -- Master Out Slave In
        cs        : out std_logic;       -- Chip Select (active low)
        done      : out std_logic        -- transmission done
    );
end spi_master;

architecture Behavioral of spi_master is
    -- internal signals
    signal clk_div_cnt : integer range 0 to 1 := 0;
    signal spi_clk     : std_logic := '0';
    signal bit_cnt     : integer range 0 to 7 := 0;
    signal shift_reg   : std_logic_vector(7 downto 0) := (others => '0');
    type state_type is (idle, transfer, finish);
    signal state       : state_type := idle;
begin
    -- Clock divider process to generate SPI clock (half speed)
    clk_divider : process(clk, rst) begin
        if rst = '1' then
            clk_div_cnt <= 0;
            spi_clk <= '0';
        elsif rising_edge(clk) then
            clk_div_cnt <= clk_div_cnt + 1;
            if clk_div_cnt = 1 then
                spi_clk <= not spi_clk;
                clk_div_cnt <= 0;
            end if;
        end if;
    end process;

    -- SPI master state machine
    spi_process : process(clk, rst) begin
        if rst = '1' then
            state <= idle;
            cs <= '1';
            mosi <= '0';
            done <= '0';
            bit_cnt <= 0;
            shift_reg <= (others => '0');
        elsif rising_edge(clk) then
            case state is
                when idle =>
                    done <= '0';
                    cs <= '1';
                    mosi <= '0';
                    if start = '1' then
                        cs <= '0';
                        shift_reg <= data_in;
                        bit_cnt <= 7;
                        state <= transfer;
                    end if;
                when transfer =>
                    if spi_clk = '0' then  -- data changes on falling edge
                        mosi <= shift_reg(bit_cnt);
                    elsif spi_clk = '1' then  -- data sampled on rising edge
                        if bit_cnt = 0 then
                            state <= finish;
                        else
                            bit_cnt <= bit_cnt - 1;
                        end if;
                    end if;
                when finish =>
                    cs <= '1';
                    done <= '1';
                    state <= idle;
                when others =>
                    state <= idle;
            end case;
        end if;
    end process;

    sclk <= spi_clk;
end Behavioral;
💻

Example

This example shows a simple SPI master that sends one byte when start is high. It generates the SPI clock at half the system clock speed and outputs the data bit by bit on MOSI. The CS signal goes low during transmission and high when done. The done signal indicates the end of sending.

vhdl
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity spi_master is
    Port (
        clk       : in  std_logic;
        rst       : in  std_logic;
        start     : in  std_logic;
        data_in   : in  std_logic_vector(7 downto 0);
        sclk      : out std_logic;
        mosi      : out std_logic;
        cs        : out std_logic;
        done      : out std_logic
    );
end spi_master;

architecture Behavioral of spi_master is
    signal clk_div_cnt : integer range 0 to 1 := 0;
    signal spi_clk     : std_logic := '0';
    signal bit_cnt     : integer range 0 to 7 := 0;
    signal shift_reg   : std_logic_vector(7 downto 0) := (others => '0');
    type state_type is (idle, transfer, finish);
    signal state       : state_type := idle;
begin
    clk_divider : process(clk, rst) begin
        if rst = '1' then
            clk_div_cnt <= 0;
            spi_clk <= '0';
        elsif rising_edge(clk) then
            clk_div_cnt <= clk_div_cnt + 1;
            if clk_div_cnt = 1 then
                spi_clk <= not spi_clk;
                clk_div_cnt <= 0;
            end if;
        end if;
    end process;

    spi_process : process(clk, rst) begin
        if rst = '1' then
            state <= idle;
            cs <= '1';
            mosi <= '0';
            done <= '0';
            bit_cnt <= 0;
            shift_reg <= (others => '0');
        elsif rising_edge(clk) then
            case state is
                when idle =>
                    done <= '0';
                    cs <= '1';
                    mosi <= '0';
                    if start = '1' then
                        cs <= '0';
                        shift_reg <= data_in;
                        bit_cnt <= 7;
                        state <= transfer;
                    end if;
                when transfer =>
                    if spi_clk = '0' then
                        mosi <= shift_reg(bit_cnt);
                    elsif spi_clk = '1' then
                        if bit_cnt = 0 then
                            state <= finish;
                        else
                            bit_cnt <= bit_cnt - 1;
                        end if;
                    end if;
                when finish =>
                    cs <= '1';
                    done <= '1';
                    state <= idle;
                when others =>
                    state <= idle;
            end case;
        end if;
    end process;

    sclk <= spi_clk;
end Behavioral;
Output
No console output; hardware signals change as follows: - CS goes low when start is high - MOSI outputs bits of data_in one by one on SPI clock edges - SCLK toggles at half system clock speed - DONE goes high after all bits sent
⚠️

Common Pitfalls

Common mistakes when writing SPI master in VHDL include:

  • Not properly dividing the system clock to generate the SPI clock, causing timing errors.
  • Changing data on the wrong clock edge, leading to incorrect data sampling by the slave.
  • Forgetting to control the chip select (CS) signal, which must be low during transmission.
  • Not resetting internal counters and state machines correctly on reset.

Always verify clock polarity and phase match your SPI slave device requirements.

vhdl
-- wrong approach:
-- Changing MOSI on rising edge instead of falling edge
if spi_clk = '1' then
    mosi <= shift_reg(bit_cnt);
end if;

-- correct approach:
if spi_clk = '0' then
    mosi <= shift_reg(bit_cnt);
end if;
📊

Quick Reference

SPI Master Signals:

  • SCLK: SPI clock output, usually slower than system clock
  • MOSI: Master Out Slave In data line
  • CS: Chip Select, active low to enable slave
  • start: Input signal to begin transmission
  • done: Output signal indicating transmission complete

Tips:

  • Use a clock divider to generate SPI clock from system clock
  • Use a state machine to control data shifting and CS signal
  • Match clock polarity and phase to your SPI slave device
  • Reset all signals and counters properly

Key Takeaways

Use a clock divider to generate the SPI clock from the system clock.
Control the chip select (CS) signal to enable the slave only during transmission.
Shift data out on the correct SPI clock edge to match slave timing requirements.
Implement a state machine to manage transmission states cleanly.
Always reset internal signals and counters to avoid unexpected behavior.