UART VHDL Code: Transmitter and Receiver Implementation

This article provides VHDL code for a UART (Universal Asynchronous Receiver/Transmitter), covering both the transmitter and receiver functionalities. UARTs are commonly used for serial data communication, particularly over interfaces like telephone lines with modems.

Figure 1 illustrates a typical UART-based serial data transmission system. The PC and modem communicate through the UART interface.

UART Transmitter Receiver system Figure 1: UART Transmitter Receiver System

The UART serial data format consists of a start bit, 8 data bits (including an optional parity bit), and a stop bit.

UART Block Diagram Figure 2: UART Block Diagram

Figure 2 shows the block diagram of a UART, which includes the transmitter, baud rate generator, and receiver.

UART Transmitter VHDL Code

Figure 3 depicts the flowchart for the UART transmitter.

UART Transmitter flow chart Figure 3: UART Transmitter Flow Chart

The following is the VHDL code for the UART transmitter:

library ieee;
use ieee.std_logic_1164.all;

entity UART_Transmitter is
    port(
        Bclk, sysclk, rst_b, TDRE, loadTDR: in std_logic;
        DBUS: in std_logic_vector(7 downto 0);
        setTDRE, TxD: out std_logic
    );
end UART_Transmitter;

architecture xmit of UART_Transmitter is
    type stateType is (IDLE, SYNCH, TDATA);
    signal state, nextstate : stateType;
    signal TSR : std_logic_vector (8 downto 0);  -- Transmit Shift Register
    signal TDR : std_logic_vector(7 downto 0);  -- Transmit Data Register
    signal Bct: integer range 0 to 9;  -- counts number of bits sent
    signal inc, clr, loadTSR, shftTSR, start: std_logic;
    signal Bclk_rising, Bclk_dlayed: std_logic;
begin

    TxD <= TSR(0);
    setTDRE <= loadTSR;
    Bclk_rising <= Bclk and (not Bclk_dlayed);  -- indicates the rising edge of bit clock

    Xmit_Control: process(state, TDRE, Bct, Bclk_rising)
    begin
        inc <= '0';
        clr <= '0';
        loadTSR <= '0';
        shftTSR <= '0';
        start <= '0';  -- reset control signals

        case state is
            when IDLE =>
                if (TDRE = '0' ) then
                    loadTSR <= '1';
                    nextstate <= SYNCH;
                else
                    nextstate <= IDLE;
                end if;

            when SYNCH =>  -- synchronize with the bit clock
                if (Bclk_rising = '1') then
                    start <= '1';
                    nextstate <= TDATA;
                else
                    nextstate <= SYNCH;
                end if;

            when TDATA =>
                if (Bclk_rising = '0') then
                    nextstate <= TDATA;
                elsif (Bct /= 9) then
                    shftTSR <= '1';
                    inc <= '1';
                    nextstate <= TDATA;
                else
                    clr <= '1';
                    nextstate <= IDLE;
                end if;
        end case;
    end process;

    Xmit_update: process (sysclk, rst_b)
    begin
        if (rst_b = '0') then
            TSR <= "111111111";
            state <= IDLE;
            Bct <= 0;
            Bclk_dlayed <= '0';
        elsif (sysclk'event and sysclk = '1') then
            state <= nextstate;
            if (clr = '1') then
                Bct <= 0;
            elsif (inc = '1') then
                Bct <= Bct + 1;
            end if;

            if (loadTDR = '1') then
                TDR <= DBUS;
            end if;

            if (loadTSR = '1') then
                TSR <= TDR & '1';
            end if;

            if (start = '1') then
                TSR(0) <= '0';
            end if;

            if (shftTSR = '1') then
                TSR <= '1' & TSR(8 downto 1);
            end if;  -- shift out one bit

            Bclk_dlayed <= Bclk;  -- Bclk delayed by 1 sysclk
        end if;
    end process;

end xmit;

UART Receiver VHDL Code

Figure 4 illustrates the flowchart for the UART receiver.

UART Receiver flow chart Figure 4: UART Receiver Flow Chart

The following VHDL code implements the UART receiver:

library ieee;
use ieee.std_logic_1164.all;

entity UART_Receiver is
    port(
        RxD, BclkX8, sysclk, rst_b, RDRF: in std_logic;
        RDR: out std_logic_vector(7 downto 0);
        setRDRF, setOE, setFE: out std_logic
    );
end UART_Receiver;

architecture rcvr of UART_Receiver is
    type stateType is (IDLE, START_DETECTED, RECV_DATA);
    signal state, nextstate: stateType;
    signal RSR: std_logic_vector (7 downto 0);  -- receive shift register
    signal ct1 : integer range 0 to 7;  -- indicates when to read the RxD input
    signal ct2 : integer range 0 to 8;  -- counts number of bits read
    signal inc1, inc2, clr1, clr2, shftRSR, loadRDR : std_logic;
    signal BclkX8_Dlayed, BclkX8_rising : std_logic;
begin

    BclkX8_rising <= BclkX8 and (not BclkX8_Dlayed);  -- indicates the rising edge of bitX8 clock

    Rcvr_Control: process(state, RxD, RDRF, ct1, ct2, BclkX8_rising)
    begin
        -- reset control signals
        inc1 <= '0';
        inc2 <= '0';
        clr1 <= '0';
        clr2 <= '0';
        shftRSR <= '0';
        loadRDR <= '0';
        setRDRF <= '0';
        setOE <= '0';
        setFE <= '0';

        case state is
            when IDLE =>
                if (RxD = '0' ) then
                    nextstate <= START_DETECTED;
                else
                    nextstate <= IDLE;
                end if;

            when START_DETECTED =>
                if (BclkX8_rising = '0') then
                    nextstate <= START_DETECTED;
                elsif (RxD = '1') then
                    clr1 <= '1';
                    nextstate <= IDLE;
                elsif (ct1 = 3) then
                    clr1 <= '1';
                    nextstate <= RECV_DATA;
                else
                    inc1 <= '1';
                    nextstate <= START_DETECTED;
                end if;

            when RECV_DATA =>
                if (BclkX8_rising = '0') then
                    nextstate <= RECV_DATA;
                else
                    inc1 <= '1';
                    if (ct1 /= 7) then
                        nextstate <= RECV_DATA;  -- wait for 8 clock cycles
                    elsif (ct2 /= 8) then
                        shftRSR <= '1';
                        inc2 <= '1';
                        clr1 <= '1';  -- read next data bit
                        nextstate <= RECV_DATA;
                    else
                        nextstate <= IDLE;
                        setRDRF <= '1';
                        clr1 <= '1';
                        clr2 <= '1';
                        if (RDRF = '1') then
                            setOE <= '1';  -- overrun error
                        elsif (RxD = '0') then
                            setFE <= '1';  -- framing error
                        else
                            loadRDR <= '1';
                        end if;  -- load recv data register
                    end if;
                end if;
        end case;
    end process;

    Rcvr_update: process (sysclk, rst_b)
    begin
        if (rst_b = '0') then
            state <= IDLE;
            BclkX8_Dlayed <= '0';
            ct1 <= 0;
            ct2 <= 0;
        elsif (sysclk'event and sysclk = '1') then
            state <= nextstate;
            if (clr1 = '1') then
                ct1 <= 0;
            elsif (inc1 = '1') then
                ct1 <= ct1 + 1;
            end if;
            if (clr2 = '1') then
                ct2 <= 0;
            elsif (inc2 = '1') then
                ct2 <= ct2 + 1;
            end if;
            if (shftRSR = '1') then
                RSR <= RxD & RSR(7 downto 1);
            end if;  -- update shift reg.
            if (loadRDR = '1') then
                RDR <= RSR;
            end if;
            BclkX8_Dlayed <= BclkX8;  -- BclkX8 delayed by 1 sysclk
        end if;
    end process;

end rcvr;

Complete UART VHDL Code

The complete UART VHDL code, incorporating both transmitter and receiver, is provided below:

library ieee;
use ieee.std_logic_1164.all;

entity UART is
    port (
        SCI_sel, R_W, clk, rst_b, RxD : in std_logic;
        ADDR2: in std_logic_vector(1 downto 0);
        DBUS : inout std_logic_vector(7 downto 0);
        SCI_IRQ, TxD : out std_logic
    );
end UART;

architecture uart1 of UART is

    component UART_Receiver
        port (
            RxD, BclkX8, sysclk, rst_b, RDRF: in std_logic;
            RDR: out std_logic_vector(7 downto 0);
            setRDRF, setOE, setFE: out std_logic
        );
    end component;

    component UART_Transmitter
        port (
            Bclk, sysclk, rst_b, TDRE, loadTDR: in std_logic;
            DBUS: in std_logic_vector(7 downto 0);
            setTDRE, TxD: out std_logic
        );
    end component;

    component clk_divider
        port (
            Sysclk, rst_b: in std_logic;
            Sel: in std_logic_vector(2 downto 0);
            BclkX8: buffer std_logic;
            Bclk: out std_logic
        );
    end component;

    signal RDR : std_logic_vector(7 downto 0);  -- Receive Data Register
    signal SCSR : std_logic_vector(7 downto 0);  -- Status Register
    signal SCCR : std_logic_vector(7 downto 0);  -- Control Register
    signal TDRE, RDRF, OE, FE, TIE, RIE : std_logic;
    signal BaudSel : std_logic_vector(2 downto 0);
    signal setTDRE, setRDRF, setOE, setFE, loadTDR, loadSCCR : std_logic;
    signal clrRDRF, Bclk, BclkX8, SCI_Read, SCI_Write : std_logic;

begin

    RCVR: UART_Receiver port map(RxD, BclkX8, clk, rst_b, RDRF, RDR, setRDRF, setOE, setFE);
    XMIT: UART_Transmitter port map(Bclk, clk, rst_b, TDRE, loadTDR, DBUS, setTDRE, TxD);
    CLKDIV: clk_divider port map(clk, rst_b, BaudSel, BclkX8, Bclk);

    -- This process updates the control and status registers
    process (clk, rst_b)
    begin
        if (rst_b = '0') then
            TDRE <= '1';
            RDRF <= '0';
            OE<= '0';
            FE <= '0';
            TIE <= '0';
            RIE <= '0';
        elsif (rising_edge(clk)) then
            TDRE <= (setTDRE and not TDRE) or (not loadTDR and TDRE);
            RDRF <= (setRDRF and not RDRF) or (not clrRDRF and RDRF);
            OE <= (setOE and not OE) or (not clrRDRF and OE);
            FE <= (setFE and not FE) or (not clrRDRF and FE);
            if (loadSCCR = '1') then
                TIE <= DBUS(7);
                RIE <= DBUS(6);
                BaudSel <= DBUS(2 downto 0);
            end if;
        end if;
    end process;

    -- IRQ generation logic
    SCI_IRQ <= '1' when ((RIE = '1' and (RDRF = '1' or OE = '1')) or (TIE = '1' and TDRE = '1')) else '0';

    -- Bus Interface
    SCSR <= TDRE & RDRF & "0000" & OE & FE;
    SCCR <= TIE & RIE & "000" & BaudSel;

    SCI_Read <= '1' when (SCI_sel = '1' and R_W = '0') else '0';
    SCI_Write <= '1' when (SCI_sel = '1' and R_W = '1') else '0';

    clrRDRF <= '1' when (SCI_Read = '1' and ADDR2 = "00") else '0';
    loadTDR <= '1' when (SCI_Write = '1' and ADDR2 = "00") else '0';
    loadSCCR <= '1' when (SCI_Write = '1' and ADDR2 = "10") else '0';

    DBUS <= "ZZZZZZZZ" when (SCI_Read = '0')  -- tristate bus when not reading
            else RDR when (ADDR2 = "00")  -- write appropriate register to the bus
            else SCSR when (ADDR2 = "01")
            else SCCR;  -- dbus = sccr, if ADDR2 is "10" or "11"

end uart1;