SourceRTL Design Directory

UART: Overview

UART, or Universal Asynchronous Receiver/Transmitter, is a widely used protocol for data communication over long distances due to its simplicity and low cost. It facilitates communication by transmitting data sequentially over a single line, making it ideal for scenarios where minimal wiring is preferred.

  • Transmitter: Converts parallel data into a serial stream for transmission over a single communication line.
  • Receiver: Converts the incoming serial data back into parallel form for further processing.

The asynchronous nature of UART means that there is no shared clock signal between the transmitter and receiver. Instead, both sides must agree on the baud rate (data transmission speed) beforehand to ensure accurate communication. This simplicity makes UART popular in microcontrollers, sensors, and other embedded systems.

Figure 1: UART Frame

Figure 1: UART Frame

Figure 1 illustrates a simple 8-bit data transmission over a UART line. When no data is transmitted, the communication line remains in an idle state (logic high). To initiate communication, the transmitter pulls the line low for one bit period, signaling the start of transmission (start bit). Following the start bit, the data bits are transmitted serially, starting with the Least Significant Bit (LSB) and ending with the Most Significant Bit (MSB). Once all 8 data bits are sent, a stop bit (logic high) indicates the end of the transmission. After the stop bit, the line returns to the idle state (high) until the next transmission occurs.

An optional parity bit can be included at the end of the data transmission to provide error detection. The parity bit helps ensure that the data has been transmitted correctly by checking the number of 1s in the data.

  • Odd Parity: The parity bit is set so that the total number of 1s is odd.
  • Even Parity: The parity bit is set to ensure that the total number of 1s is even.

If the receiver detects a parity mismatch, it can identify that an error occurred during transmission. Using a parity bit adds an extra layer of reliability.

Baud Rate

The baud rate represents the speed of data transfer within a communication channel. It measures the number of signal changes per second. On the other hand, bit rate—often expressed as bits per second (bps)—indicates the number of bits transmitted per second. In a UART setup, one signal change corresponds to one bit, meaning the baud rate and bit rate are equal. For reliable data transmission, both the transmitter and receiver must agree on the same baud rate. If the baud rates are mismatched, the receiver may misinterpret the incoming data, leading to communication errors.

Baud Rate Divider

While implementing the UART on FPGA the baud rate is derived by dividin the system clock frequency to match the desired data transfer rate. The baud rate divider dvsr is essential parameter to generate the desired baud rate. The equation for calculaing the baud rate is:

\begin{equation} \label{eq.1} dvsr = \frac{f_{clk}}{baud rate \times 16} \end{equation}

  • fclk - system clock frequency
  • baud rate - desired baud rate
  • 16 - oversampling factor

A counter is implemented to generate the baud rate. Since the counter starts from 0 to dvsr, a mod(dvsr + 1) counter is designed. Because of the counter implementation the above dvsr is rewritten as:

A counter is used to generate the desired baud rate. The counter starts from 0 and counts up to the dvsr (divider value) to achieve the necessary timing. To account for this behavior, the counter is implemented as a mod(dvsr + 1) counter. This ensures the baud rate is accurately maintained. Due to the counter-based implementation, the dvsr equation is adjusted as follows:

\begin{equation} \label{eq.2} \begin{aligned} dvsr + 1 & = \frac{f_{clk}}{baud rate \times 16} \\ dvsr & = \frac{f_{clk}}{baud rate \times 16} - 1 \end{aligned} \end{equation}

For a system clock of 125 MHz and baud rate of 115200 bps, the required dvsr is:

\begin{equation} \label{eq.3} \begin{aligned} dvsr & = \frac{125 \times 10^6}{115200 \times 16} - 1 \\ & = 67.68 - 1 \\ & = 66.68 \\ dvsr & \approx 66 \end{aligned} \end{equation}

A counter is implemented to run from 0 to 66, therefore a pulse is generated for every \[ 67*(1/125MHz) = 536 ns \]

Baud Rate Generator

A baud rate generator produces a pulse at regular intervals to ensure the correct data transmission rate. In this example, the generator produces a pulse every 536 nanoseconds (ns). To achieve this timing, a counter (cnt) is used. The counter increments with each clock cycle and resets to 0 when it reaches a value of 66 to restart the counting process.

`timescale 1ns/1ns
module baud_rate(/*AUTOARG*/
   // Outputs
   s_tick,
   // Inputs
   clk, rst, dvsr
   );
   input logic clk;
   input logic rst;
   input logic [10:0] dvsr;
   output logic       s_tick;

   logic [10:0]       cnt_reg, cnt_nxt;

   always_ff@(posedge clk, posedge rst)
     if(rst)
       cnt_reg <= 0;
     else
       cnt_reg <= cnt_nxt;

   assign cnt_nxt = (cnt_reg == dvsr) ? 0 : cnt_reg + 1;

   assign s_tick = (cnt_reg == 1);

endmodule // baud_rate

Oversampling

Oversampling is a technique used in UART to improve the accuracy of data detection by sampling each bit multiple times. It helps ensure that even if there are slight timing variations or noise, the receiver can accurately detect the transmitted data.

In UART, oversampling is typically implemented on the receiver side. The serial data is sampled at multiple points within each bit period, with the sample taken at the middle of the bit being used to determine the bit value. A 16x oversampling factor means that instead of sampling the incoming data once per bit, the receiver samples it 16 times during each bit period. Each bit of the incoming serial data is divided into 16 equal sampling intervals. The receiver takes multiple samples, but the 8th sample (middle of the bit) is used to decide the bit value. This ensures that minor timing shifts won’t cause data errors. It ensures that even if the edges of the signal are slightly distorted, the correct bit value is still detected.

Figure 2: Oversampling

Figure 2: Oversampling

Consider a 115200 baud rate. Each bit period lasts:

\begin{equation} \label{eq.4} \begin{aligned} \frac{1}{115200} = 8.68 us \end{aligned} \end{equation}

With 16x oversampling, the receiver samples the bit every:

\begin{equation} \label{eq.5} \begin{aligned} \frac{8.68 us}{16} = 542.53 ns \end{aligned} \end{equation}

From the baud rate generator, the sample tick s_tick is generated for every 536ns.

UART TX

A black box refers to a design module where only the inputs and outputs are visible, without exposing the internal logic. It abstracts the complex functionality, focusing on how the module interacts with other components in the system.

Below is a black box representation of a UART transmission module:

Figure 3: UART TX Black box

Figure 3: UART TX Black box

  • Input

    • d_in: An 8-bit parallel input representing the data to be transmitted over the tx output line.
    • s_tick: A pulse signal generated every 536 ns, used to control the timing of the transmission.
    • tx_start: A control signal that indicates the UART controller has received the 8-bit data and is ready to begin transmission.
    • tx_done: This signal is set high once the entire 8-bit data has been successfully transmitted.
  • Output

    • tx: The serial output signal carrying the transmitted data.

Figure 4: UART TX FSM

Figure 4: UART TX FSM

`timescale 1ns/1ns
module uart_tx #(parameter DBIT = 8, SB_TICK = 16)(/*AUTOARG*/
   // Outputs
   tx_done_tick, tx,
   // Inputs
   clk, rst, din, tx_start, s_tick
   );
   input logic clk;
   input logic rst;
   input logic [7:0] din;
   input logic 	     tx_start;
   input logic 	     s_tick;
   output logic      tx_done_tick;
   output logic      tx;

   // fsm states
   typedef enum      {idle, start, data, stop} state_type;

   state_type st_reg, st_nxt;

   logic [3:0] 	tk_reg, tk_nxt;
   logic [2:0] 	bt_reg, bt_nxt;
   logic [7:0] 	dt_reg, dt_nxt;
   logic 	tx_reg, tx_nxt;

   always_ff@(posedge clk, posedge rst)
     if(rst)begin
        st_reg <= idle;
        tk_reg <= 0;
        bt_reg <= 0;
        dt_reg <= 0;
        tx_reg <= 0;
     end else begin
        st_reg <= st_nxt;
        tk_reg <= tk_nxt;
        bt_reg <= bt_nxt;
        dt_reg <= dt_nxt;
        tx_reg <= tx_nxt;
     end // else: !if(rst)

   always_comb begin
      st_nxt = st_reg;
      tk_nxt = tk_reg;
      bt_nxt = bt_reg;
      dt_nxt = dt_reg;
      tx_nxt = tx_reg;
      tx_done_tick = 1'b0;

      case(st_reg)

        idle: begin
          tx_nxt = 1'b1;
          if(tx_start)begin
             st_nxt = start;
             tk_nxt = 0;
             dt_nxt = din;
          end
        end // case: begin

        start: begin
           tx_nxt = 1'b0;
           if(s_tick)
             if(tk_reg == 15)begin
                st_nxt = data;
                tk_nxt = 0;
                bt_nxt = 0;
             end
             else
               tk_nxt = tk_reg + 1;
        end // case: start

        data: begin
           tx_nxt = dt_reg[0];
           if(s_tick)
             if(tk_reg == 15)begin
                      tk_nxt = 0;
                      dt_nxt = dt_reg >> 1;
            if(bt_nxt == (DBIT-1))
              st_nxt = stop;
            else
              bt_nxt = bt_reg + 1;
             end
             else
               tk_nxt = tk_reg + 1;
        end // case: data

        stop: begin
           tx_nxt =  1'b1;
           if(s_tick)
             if(tk_reg == (SB_TICK-1))begin
                st_nxt = idle;
                tx_done_tick = 1'b1;
             end
             else
               tk_nxt = tk_reg +1;
        end // case: stop

      endcase // case (st_reg)
   end // always_comb

   assign tx = tx_reg;

endmodule // uart_tx

UART RX

Below is a black box representation of a UART receiver module:

Figure 5: UART RX Black box

Figure 5: UART RX Black box

  • Input

    • rx: The serial input signal that carries the data from the transmitter.
    • s_tick: A pulse signal generated every 536 ns, used to control the timing of the transmission.
  • Output

    • d_out: An 8-bit parallel output representing the received data.
    • rx_done: This signal is set high once the entire 8-bit data has been successfully received.

Figure 6: UART RX FSM

Figure 6: UART RX FSM

`timescale 1ns/1ns
module uart_rx #(parameter DBIT = 8, SB_TICK = 16)(/*AUTOARG*/
   // Outputs
   dout, rx_done_tick,
   // Inputs
   clk, rst, rx, s_tick
   );
   input logic clk;
   input logic rst;
   input logic rx;
   input logic s_tick;

   output logic [7:0] dout;
   output logic	rx_done_tick;

   // fsm states
   typedef enum {idle, start, data, stop} state_type;

   state_type st_reg, st_nxt;

   logic [3:0] 	tk_reg, tk_nxt;    // tick counter
   logic [2:0] 	bt_reg, bt_nxt;    // bit position counter
   logic [7:0] 	dt_reg, dt_nxt;    // data registers


   always_ff@(posedge clk, posedge rst)
     if(rst)begin
        st_reg <= idle;
        tk_reg <= 0;
        bt_reg <= 0;
        dt_reg <= 0;
     end else begin
        st_reg <= st_nxt;
        tk_reg <= tk_nxt;
        bt_reg <= bt_nxt;
        dt_reg <= dt_nxt;
     end // else: !if(rst)

   always_comb begin
      st_nxt = st_reg;
      tk_nxt = tk_reg;
      bt_nxt = bt_reg;
      dt_nxt = dt_reg;
      rx_done_tick = 1'b0;
      case(st_reg)

        idle: begin
          if(~rx)begin
            st_nxt = start;
             tk_nxt = 0;
          end
        end // case: idle


        start: begin
          if(s_tick)
            if(tk_reg == 7)begin
               st_nxt = data;
               tk_nxt = 0;
               bt_nxt = 0;
            end
            else
              tk_nxt = tk_reg + 1;
        end // case: start


        data: begin
          if(s_tick)
            if(tk_reg == 15)begin
               tk_nxt = 0;
               dt_nxt = {rx, dt_reg[7:1]};
               if(bt_nxt == (DBIT-1))
                 st_nxt = stop;
               else
                 bt_nxt = bt_reg + 1;
            end
            else
              tk_nxt = tk_reg + 1;
        end // case: data


        stop: begin
          if(s_tick)
            if(tk_reg == (SB_TICK - 1))begin
               st_nxt = idle;
               rx_done_tick = 1'b1;
            end
            else
              tk_nxt = tk_reg + 1;
        end // case: stop


      endcase // case (st_reg)
   end // always_comb

   assign dout = dt_reg;

endmodule // uart_rx

UART TOP

The UART TOP module serves as a wrapper that connects the baud rate generator to the UART transmitter and UART receiver. It integrates these components to simulate the entire UART communication system.

In this setup, the transmitter sends data, and the receiver receives the data, demonstrating how the UART protocol works. This design allows for functional verification and helps in understanding the flow of data between the transmitter and receiver.

Below is a pictorial representation of the wrapper design:

Figure 7: UART Design

Figure 7: UART Design

This simulation-based project provides a clear understanding of UART transmission and reception, making it easier to grasp the working of the protocol in real-world applications.

`timescale 1ns/1ns
module uart #(parameter DBIT = 8, SB_TICK = 16)(/*AUTOARG*/
   // Outputs
   tx_done_tick, rx_done_tick, dout,
   // Inputs
   clk, rst, din, dvsr, tx_start
   );
   input logic clk;
   input logic rst;
   input logic [DBIT-1: 0] din;
   input logic [10:0] 	   dvsr;
   input logic 		   tx_start;
   //input logic 	   rx;
   //output logic 	   tx;
   output logic tx_done_tick;
   output logic rx_done_tick;
   output logic [DBIT-1:0] dout;


  /*AUTOREG*/
  /*AUTOWIRE*/
  // Beginning of automatic wires (for undeclared instantiated-module outputs)

  logic			s_tick;			// From BR of baud_rate.v
  logic			tx;			// From TX of uart_tx.v
  // End of automatics

   logic 		rx;

   assign rx = tx;

   baud_rate BR (/*AUTOINST*/
                 // Outputs
                 .s_tick		(s_tick),
                 // Inputs
                 .clk			(clk),
                 .rst			(rst),
                 .dvsr			(dvsr[10:0]));

   uart_tx #(/*AUTOINSTPARAM*/
             // Parameters
             .DBIT			(DBIT),
             .SB_TICK			(SB_TICK)) TX (/*AUTOINST*/
                                                       // Outputs
                                                       .tx_done_tick	(tx_done_tick),
                                                       .tx		(tx),
                                                       // Inputs
                                                       .clk		(clk),
                                                       .rst		(rst),
                                                       .din		(din[7:0]),
                                                       .tx_start	(tx_start),
                                                       .s_tick		(s_tick));
   uart_rx #(/*AUTOINSTPARAM*/
             // Parameters
             .DBIT			(DBIT),
             .SB_TICK			(SB_TICK)) RX (/*AUTOINST*/
                                                       // Outputs
                                                       .dout		(dout[7:0]),
                                                       .rx_done_tick	(rx_done_tick),
                                                       // Inputs
                                                       .clk		(clk),
                                                       .rst		(rst),
                                                       .rx		(rx),
                                                       .s_tick		(s_tick));

endmodule

UART TESTBENCH

A self-checking testbench automates the process of comparing the transmitted data with the received data, ensuring the correctness of the UART module without requiring manual inspection.

Self Checking logic

For the UART Protocol Validation with tx_start and rx_done signals behave similarly to the valid and ready handshake mechanism commonly used in producer-consumer protocols.

  • The tx_start signal indicates that the data on the d_in line is valid and ready to be transmitted.
  • The rx_done signal confirms that the receiver has successfully captured all 8-bits of data and is prepared to accept the next data packet.

The testbench plays a critical role in validating this communication. It compares the transmitted data on the d_in line with the received data on the d_out line. If both signals match, the protocol is functioning correctly. If there is a mismatch, the testbench will flag it as an error, indicating a potential issue in the transmission or reception process.

`timescale 1ns/1ns
module tb_uart();
   localparam t = 8; //125MHz for ZyboZ7
   logic clk;
   logic rst;

   localparam DBIT = 8;
   localparam SB_TICK = 16;

   /*AUTOREG*/
   /*AUTOWIRE*/
   // Beginning of automatic wires (for undeclared instantiated-module outputs)
   logic [DBIT-1:0]	dout;			// From DUT of uart.v
   logic		rx_done_tick;		// From DUT of uart.v
   // End of automatics

   logic [7:0] din; // = 8'b01010110;
   logic [10:0] dvsr = 11'd66;
   logic 	tx_start;

   uart #(/*AUTOINSTPARAM*/
          // Parameters
          .DBIT				(DBIT),
          .SB_TICK			(SB_TICK)) DUT (/*AUTOINST*/
                                                        // Outputs
                                                        .tx_done_tick   (tx_done_tick),
                                                        .rx_done_tick	(rx_done_tick),
                                                        .dout		(dout[DBIT-1:0]),
                                                        // Inputs
                                                        .clk		(clk),
                                                        .rst		(rst),
                                                        .din		(din[DBIT-1:0]),
                                                        .dvsr		(dvsr[10:0]),
                                                        .tx_start	(tx_start));

   initial clk = 1;
   always #(t/2) clk = ~clk;

   initial begin
      rst = 1;
      #(2*t);
      rst = 0;
      #(t);
      tx_start = 1;
      #(2*t);
      tx_start = 0;
   end

   always@(posedge clk) begin
      for(int i= 0; i<20; i= i+1) begin
          din = $urandom();
          tx_start = 1;
          #(4*t);
          tx_start = 0;
          wait(rx_done_tick == 1);
          if(dout == din)begin
            $display("PASS ;) : tx = %d == rx = %d", din, dout);
          end else begin
            $display("FAIL ;( : tx = %d != rx = %d", din, dout);
            $finish;
          end
          wait(tx_done_tick == 1);
      end
      $finish;
   end



   initial begin
      $dumpfile("tb_uart.vcd");
      $dumpvars(0,tb_uart);
   end

endmodule

Results of are shown below: