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 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.
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:
Input
d_in
: An 8-bit parallel input representing the data to be transmitted over thetx
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.
`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:
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.
`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:
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 thed_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: