How do you implement standard communication protocols (SPI, I2C, UART) in an FPGA?

ampheoampheo
4 min read

Implementing communication protocols in FPGAs requires understanding both the protocol specifications and FPGA design techniques. Here's how to implement these three common protocols:

1. UART Implementation

Core Components:

  • Baud rate generator: Creates precise timing pulses

  • Transmitter: Parallel-to-serial conversion

  • Receiver: Serial-to-parallel conversion

  • FIFO buffers (optional): For data flow control

VHDL/Verilog Implementation Example (UART TX):

vhdl

-- Simple UART transmitter in VHDL
entity uart_tx is
    generic (
        CLK_FREQ : integer := 100_000_000; -- 100 MHz clock
        BAUD_RATE : integer := 115200
    );
    port (
        clk : in std_logic;
        reset : in std_logic;
        tx_data : in std_logic_vector(7 downto 0);
        tx_start : in std_logic;
        tx : out std_logic;
        tx_busy : out std_logic
    );
end entity;

architecture rtl of uart_tx is
    constant BIT_PERIOD : integer := CLK_FREQ/BAUD_RATE;
    signal baud_counter : integer range 0 to BIT_PERIOD-1;
    signal bit_index : integer range 0 to 9;
    signal shift_reg : std_logic_vector(9 downto 0);
begin
    process(clk, reset)
    begin
        if reset = '1' then
            -- Reset logic
        elsif rising_edge(clk) then
            if tx_start = '1' and bit_index = 0 then
                -- Start new transmission
                shift_reg <= '1' & tx_data & '0'; -- Stop bit, data, start bit
                bit_index <= 1;
                baud_counter <= 0;
                tx_busy <= '1';
            elsif bit_index > 0 then
                if baud_counter = BIT_PERIOD-1 then
                    baud_counter <= 0;
                    tx <= shift_reg(bit_index-1);
                    if bit_index = 10 then
                        bit_index <= 0;
                        tx_busy <= '0';
                    else
                        bit_index <= bit_index + 1;
                    end if;
                else
                    baud_counter <= baud_counter + 1;
                end if;
            end if;
        end if;
    end process;
end architecture;

2. SPI Implementation

Core Components:

  • Clock divider: Generates SCK from system clock

  • Shift registers: For data transfer

  • State machine: Controls protocol flow

  • Chip select logic: Manages slave devices

Implementation Considerations:

  • Support multiple modes (CPOL, CPHA)

  • Configurable data width (typically 8-32 bits)

  • Optional FIFO for buffering

verilog

// SPI Master in Verilog
module spi_master (
    input clk,
    input reset,
    input [7:0] data_in,
    input start,
    output reg [7:0] data_out,
    output reg busy,
    output sck,
    output mosi,
    input miso,
    output reg cs
);
    reg [2:0] bit_count;
    reg [7:0] shift_reg;
    reg sck_int;
    reg [3:0] clk_div;

    // Clock divider for SCK generation
    always @(posedge clk) begin
        if (reset) clk_div <= 0;
        else clk_div <= clk_div + 1;
    end

    assign sck = sck_int;

    always @(posedge clk) begin
        if (reset) begin
            // Reset logic
        end else if (start && !busy) begin
            // Start new transfer
        end else if (busy) begin
            // Handle ongoing transfer
        end
    end
endmodule

3. I2C Implementation

Core Components:

  • Clock stretching handling

  • Start/Stop condition detection

  • ACK/NACK generation/checking

  • Bit-level timing control

Implementation Challenges:

  • Bidirectional SDA line handling

  • Multi-master arbitration

  • Clock synchronization

vhdl

-- I2C Master in VHDL
entity i2c_master is
    generic (
        CLK_FREQ : integer := 100_000_000;
        I2C_FREQ : integer := 100_000
    );
    port (
        clk : in std_logic;
        reset : in std_logic;
        sda : inout std_logic;
        scl : out std_logic;
        -- Control interface
        addr : in std_logic_vector(6 downto 0);
        rw : in std_logic;
        data_wr : in std_logic_vector(7 downto 0);
        data_rd : out std_logic_vector(7 downto 0);
        busy : out std_logic;
        ack_error : out std_logic
    );
end entity;

architecture fsm of i2c_master is
    type state_type is (IDLE, START, ADDR, RW, ACK, DATA, STOP);
    signal state : state_type;
    signal scl_en : std_logic := '0';
    signal sda_int : std_logic := '1';
begin
    -- Finite State Machine implementation
    process(clk, reset)
    begin
        if reset = '1' then
            -- Reset logic
        elsif rising_edge(clk) then
            case state is
                when IDLE =>
                    -- Wait for command
                when START =>
                    -- Generate start condition
                -- Other states...
            end case;
        end if;
    end process;

    -- SCL generation
    scl <= '0' when scl_en = '1' and (state = ADDR or state = RW or state = DATA) else '1';
    -- SDA handling
    sda <= '0' when sda_int = '0' else 'Z';
end architecture;

Verification and Testing

  1. Simulation: Use testbenches to verify protocol timing

    • Check signal transitions

    • Verify data integrity

    • Test error conditions

  2. Hardware Validation:

    • Use logic analyzers (Saleae, Sigrok)

    • Protocol analyzers (I2C/SPI decoders)

    • Cross-check with known-good devices

Optimization Techniques

  1. Clock Domain Crossing:

    • Proper synchronization for multi-clock designs

    • FIFOs for rate matching

  2. Resource Sharing:

    • Reuse logic blocks for multiple protocol instances

    • Time-division multiplexing

  3. Performance Tuning:

    • Pipeline critical paths

    • Optimize state machines for minimum cycles

IP Core Alternatives

For production designs, consider:

  1. Xilinx/Intel IP Cores:

    • AXI-I2C, AXI-SPI, AXI-UART

    • Pre-verified and optimized

  2. Open-Source Cores:

0
Subscribe to my newsletter

Read articles from ampheo directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

ampheo
ampheo