0
0
VhdlHow-ToBeginner · 4 min read

VHDL Code for UART Receiver: Syntax and Example

A UART receiver in VHDL reads serial data bits and converts them into parallel data. It typically uses a state machine to detect start bits, read data bits, and check stop bits. The code includes a baud rate clock, input sampling, and output data signals.
📐

Syntax

The UART receiver in VHDL usually includes these parts:

  • Input signals: serial data line, clock, reset
  • Output signals: received byte, data ready flag
  • Baud rate generator: to sample bits at correct timing
  • State machine: to detect start bit, read data bits, and stop bit

The code uses a process triggered by the clock to sample the serial input and shift bits into a register.

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

entity uart_rx is
    Port (
        clk       : in  std_logic;
        reset     : in  std_logic;
        rx_serial : in  std_logic;
        rx_data   : out std_logic_vector(7 downto 0);
        rx_ready  : out std_logic
    );
end uart_rx;

architecture Behavioral of uart_rx is
    -- Baud rate and state definitions here
begin
    -- Process for UART reception
end Behavioral;
💻

Example

This example shows a simple UART receiver that reads 8 data bits, no parity, 1 stop bit at 9600 baud assuming a 50 MHz clock.

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

entity uart_rx is
    Port (
        clk       : in  std_logic;
        reset     : in  std_logic;
        rx_serial : in  std_logic;
        rx_data   : out std_logic_vector(7 downto 0);
        rx_ready  : out std_logic
    );
end uart_rx;

architecture Behavioral of uart_rx is
    constant CLK_FREQ      : integer := 50000000; -- 50 MHz
    constant BAUD_RATE     : integer := 9600;
    constant BAUD_TICK_CNT : integer := CLK_FREQ / BAUD_RATE;

    type state_type is (idle, start_bit, data_bits, stop_bit);
    signal state       : state_type := idle;
    signal baud_count  : integer range 0 to BAUD_TICK_CNT := 0;
    signal bit_index   : integer range 0 to 7 := 0;
    signal rx_shift_reg: std_logic_vector(7 downto 0) := (others => '0');
    signal rx_ready_int: std_logic := '0';

begin
    process(clk, reset)
    begin
        if reset = '1' then
            state <= idle;
            baud_count <= 0;
            bit_index <= 0;
            rx_shift_reg <= (others => '0');
            rx_ready_int <= '0';
        elsif rising_edge(clk) then
            rx_ready_int <= '0';
            case state is
                when idle =>
                    if rx_serial = '0' then -- start bit detected
                        state <= start_bit;
                        baud_count <= BAUD_TICK_CNT / 2; -- sample middle of bit
                    end if;
                when start_bit =>
                    if baud_count = 0 then
                        if rx_serial = '0' then
                            state <= data_bits;
                            bit_index <= 0;
                            baud_count <= BAUD_TICK_CNT - 1;
                        else
                            state <= idle; -- false start bit
                        end if;
                    else
                        baud_count <= baud_count - 1;
                    end if;
                when data_bits =>
                    if baud_count = 0 then
                        rx_shift_reg(bit_index) <= rx_serial;
                        if bit_index = 7 then
                            state <= stop_bit;
                        else
                            bit_index <= bit_index + 1;
                        end if;
                        baud_count <= BAUD_TICK_CNT - 1;
                    else
                        baud_count <= baud_count - 1;
                    end if;
                when stop_bit =>
                    if baud_count = 0 then
                        if rx_serial = '1' then
                            rx_ready_int <= '1'; -- data ready
                        end if;
                        state <= idle;
                    else
                        baud_count <= baud_count - 1;
                    end if;
            end case;
        end if;
    end process;

    rx_data <= rx_shift_reg;
    rx_ready <= rx_ready_int;

end Behavioral;
Output
When connected to a UART transmitter sending bytes, the receiver outputs the received byte on rx_data and sets rx_ready high for one clock cycle when a byte is received.
⚠️

Common Pitfalls

Common mistakes when writing a UART receiver in VHDL include:

  • Not sampling the serial input at the middle of the bit period, causing bit errors.
  • Ignoring the start bit and stop bit validation, which can cause framing errors.
  • Not resetting internal counters and state machine properly on reset.
  • Using asynchronous reset without care, which can cause metastability.
  • Not handling baud rate clock generation correctly, leading to timing mismatches.

Always verify timing and test with real UART data.

vhdl
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

-- Wrong: sampling at bit edges instead of middle
process(clk)
begin
    if rising_edge(clk) then
        if rx_serial = '0' then -- start bit detected
            -- immediately read data bits without delay
        end if;
    end if;
end process;

-- Right: sample at middle of bit period using baud counter
-- See example code for correct approach.
📊

Quick Reference

UART Receiver Tips:

  • Use a baud rate clock to sample bits at the middle of each bit period.
  • Implement a state machine with states: idle, start bit, data bits, stop bit.
  • Shift in bits serially into a register.
  • Validate start bit is low and stop bit is high.
  • Set a data ready flag when a full byte is received.

Key Takeaways

Sample the UART serial input at the middle of each bit period for reliable data.
Use a state machine to detect start bit, read data bits, and check stop bit.
Shift received bits into a register and signal when a full byte is ready.
Reset all counters and states properly to avoid errors.
Test the UART receiver with real serial data to ensure correct timing.