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


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
Simulation: Use testbenches to verify protocol timing
Check signal transitions
Verify data integrity
Test error conditions
Hardware Validation:
Use logic analyzers (Saleae, Sigrok)
Protocol analyzers (I2C/SPI decoders)
Cross-check with known-good devices
Optimization Techniques
Clock Domain Crossing:
Proper synchronization for multi-clock designs
FIFOs for rate matching
Resource Sharing:
Reuse logic blocks for multiple protocol instances
Time-division multiplexing
Performance Tuning:
Pipeline critical paths
Optimize state machines for minimum cycles
IP Core Alternatives
For production designs, consider:
-
AXI-I2C, AXI-SPI, AXI-UART
Pre-verified and optimized
Open-Source Cores:
OpenCores.org implementations
GitHub community projects
Subscribe to my newsletter
Read articles from ampheo directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
