FPGA UART Interface Design and Simulation

Universal Asynchronous Receiver/Transmitter (UART) facilitates asynchronous data exchange between devices using a simple serial link. It converts parallel data into a serial stream for transmission and reconstructs it upon receipt. Full-duplex communication is supported, allowing simultaneous sending and receiving.

The asynchronous frame consists of several specific components. Transmission begins with a start bit, held low to signal activity. Following this, data bits are transmitted least-significant-bit first, typically spanning 5 to 8 bits depending on configuration. An optional parity bit may follow to verify data integrity via odd or even checks. Finally, one or more stop bits return the line to a high logic state, marking the end of the frame and allowing clock alignment tolerance. Idle lines remain high.

Baud rate determines the signaling speed. For a system clock of 50MHz (20ns period) targeting 9600bps, the required bit duration is approximately 104.17µs. This translates to dividing the system clock by 5208 ticks per bit.

// Optimized UART Transmitter Module
module uart_tx_inst (
    input wire            clk,
    input wire            rst_n,
    
    input wire            tx_req,
    input wire [7:0]      tx_data,
    
    output reg            rx_done,
    output wire           serial_out
);

    localparam BIT_COUNT   = 5207; // 5208 cycles total
    localparam DATA_WIDTH  = 8;

    reg [12:0] tick_cnt;
    reg [2:0]  state;       // 0:Idle, 1:Send Start, 2:Send Data, 3:Send Stop
    reg [2:0]  bit_idx;
    reg        tx_active;
    reg [7:0]  shift_reg;

    // State Machine Control
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            tick_cnt <= 13'd0;
            state <= 3'd0;
            bit_idx <= 3'd0;
            tx_active <= 1'b0;
            shift_reg <= 8'd0;
            rx_done   <= 1'b0;
        end else begin
            if (tx_req) begin
                tx_active <= 1'b1;
                shift_reg <= tx_data;
            end
            
            // Tick Counter Reset Logic
            if (state == 3'd0 && !tx_active) tick_cnt <= 13'd0;
            
            if (tick_cnt < BIT_COUNT) tick_cnt <= tick_cnt + 1'b1;
            
            // State Transitions based on bit counting
            case (state)
                3'd0: begin // IDLE
                    if (tx_active) begin
                        state <= 3'd1;
                        bit_idx <= 3'd0;
                    end
                end
                3'd1: begin // START BIT
                    if (tick_cnt == BIT_COUNT) begin
                        state <= 3'd2;
                        tick_cnt <= 13'd0;
                    end
                end
                3'd2: begin // DATA BITS
                    if (bit_idx >= 7) begin
                        state <= 3'd3;
                        bit_idx <= 3'd0;
                    end
                    if (tick_cnt == BIT_COUNT) begin
                        tick_cnt <= 13'd0;
                        bit_idx <= bit_idx + 1'b1;
                    end
                end
                3'd3: begin // STOP BIT
                    if (tick_cnt == BIT_COUNT) begin
                        state <= 3'd0;
                        tick_cnt <= 13'd0;
                        tx_active <= 1'b0;
                        rx_done   <= 1'b1; // Pulse completion flag
                    end
                end
                default: state <= 3'd0;
            endcase
        end
    end

    // Output Generation
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            serial_out <= 1'b1;
        else begin
            case (state)
                3'd1: serial_out <= 1'b0; // Start
                3'd2: serial_out <= shift_reg[bit_idx]; // Data
                3'd3: serial_out <= 1'b1; // Stop
                default: serial_out <= 1'b1; // Idle
            endcase
        end
    end
    
    // Clear done flag after pulse
    always @(posedge clk or negedge rst_n) begin
         if (!rst_n) rx_done <= 1'b0;
         else if (state == 3'd0 && tick_cnt > BIT_COUNT/2) rx_done <= 1'b0;
    end

endmodule

The receiver mirrors this logic, requiring edge detection and precise sampling.

// UART Receiver Module
module uart_rx_inst (
    input wire            clk,
    input wire            rst_n,
    
    input wire            serial_in,
    
    output reg            data_ready,
    output reg [7:0]      received_byte
);

    localparam BIT_COUNT = 5207;
    
    reg [12:0] sys_cnt;
    reg [2:0]  recv_state; // 0:Detect, 1:WaitHalf, 2:SampleData, 3:StopBit
    reg [2:0]  bit_count;
    reg [7:0]  buffer;
    reg        rising_edge_flag;
    reg [2:0]  prev_bit;
    
    // Detect Falling Edge (Start Bit)
    always @(posedge clk) begin
        prev_bit <= {prev_bit[1:0], serial_in};
        rising_edge_flag <= (~serial_in & prev_bit[2]); 
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sys_cnt <= 13'd0;
            recv_state <= 3'd0;
            bit_count <= 3'd0;
            buffer <= 8'd0;
            data_ready <= 1'b0;
        end else begin
            case (recv_state)
                3'd0: begin // Detection Phase
                    if (rising_edge_flag) begin
                        sys_cnt <= 13'd0;
                        recv_state <= 3'd1; // Move to Wait Half
                    end
                end
                3'd1: begin // Wait Half Bit Time
                    if (sys_cnt == BIT_COUNT/2 - 1) begin
                        sys_cnt <= 13'd0;
                        recv_state <= 3'd2; // Begin Sampling
                        bit_count <= 3'd1;
                    end
                end
                3'd2: begin // Sampling Data Bits
                    // Sample Middle of Bit
                    if (sys_cnt == BIT_COUNT/2 - 1) begin
                        buffer <= {buffer[6:0], serial_in};
                        bit_count <= bit_count + 1'b1;
                        sys_cnt <= 13'd0;
                        
                        if (bit_count == 8) recv_state <= 3'd3; // Finish Data
                    end
                    else if (sys_cnt == BIT_COUNT) sys_cnt <= 13'd0;
                end
                3'd3: begin // Verify Stop Bit
                     if (sys_cnt == BIT_COUNT/2 - 1) begin
                         if (serial_in) begin
                             data_ready <= 1'b1;
                         end
                         sys_cnt <= 13'd0;
                         recv_state <= 3'd0;
                         bit_count <= 3'd0;
                     end
                     else if (sys_cnt == BIT_COUNT) sys_cnt <= 13'd0;
                end
            endcase
        end
    end

    // Generate pulse width for data_ready
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) data_ready <= 1'b0;
        else if(recv_state == 3'd0) data_ready <= 1'b0;
        else if(data_ready) data_ready <= 1'b0; // Auto clear next cycle
    end

endmodule

Integration allows for loopback testing where the transmitter output connects to the receiver input.

// System-Level Integration (Loopback Test)
module uwr_top (
    input wire        clk,
    input wire        rst_n,
    input wire        loop_enable
);

    wire tx_out;
    wire rx_data_valid;
    wire [7:0] rx_data;

    uart_tx_inst u_tx (
        .clk(clk),
        .rst_n(rst_n),
        .tx_req(loop_enable),
        .tx_data({loop_enable ? 8'hAA : 8'd0}),
        .rx_done(),
        .serial_out(tx_out)
    );

    uart_rx_inst u_rx (
        .clk(clk),
        .rst_n(rst_n),
        .serial_in(tx_out),
        .data_ready(rx_data_valid),
        .received_byte(rx_data)
    );

    // In a real scenario, monitor rx_data vs expected payload here

endmodule

Simulation verifies timing accuracy by observing the serialized waveforms against the defined system clock. The counters ensure that data shifts ocurr exactly once per bit time derived from the 50MHz source.

Tags: FPGA Verilog UART Serial Protocol Hardware Design

Posted on Tue, 25 Aug 2026 16:34:55 +0000 by jdimino