UART is a universal asynchronous receiver and transmitter.
It is an asynchronous communication
interface i.e it do not have any common clock going between sender and receiver based on data we
receive sender and receiver will align the clock.
Typical data format:
Whenever we have new data we pull the line low so that the other device knows that we have some
data we wish to communicate and that aligns its clock to a transmitter. Than we serially transmit data
bit by bit. The parity bit is optional, which could be use to detect single bit [Link] have a stop bit
which could be 1 bit,1.5 bit or 2 bit duration.
UART can be full/half duplex.
- In asynchronous communication, baud rate measures the speed of data transmission. Baud
rate accounts for all bits sent, including start, stop, and parity bits.
- The actual data transfer speed, represented as bit rate (bps), indicates the amount of data
transmitted from the sender to the receiver.
- UART speeds are expressed in bits per second (bit/s or bps).
- Standard baud rates are as follows: 110, 300, 1200, 2400, 4800, 9600, 14400,19200, 28800,
38400, 57600, 76800, 115200, 230400, 460800, 921600,1382400, 1843200, and 2764800
bit/s.
UART is commonly used to transmit data at lower data rates and when distance of transmission is
small.
A simple UART can have only 2 pins to communicate data between devices
Simple UART Design
Tx
UART Rx
Our signal will be low for entire duration. Once the receiver recognizes the start bit it will be ready to
receive the data. Than we will be serially transmitting the data from transmitter to [Link] the
data that we are going to send is the LSB of our data.
We have made TX line
high this will notify the
receiver that we have
completed
transmission.
We can generate single pulse at the end of a bit duration. That is also one strategy or we can
generate the pulse at the middle of a bit duration and than transmit the data. The common practice
is to generate 16 pulses within single bit duration and this is what we refer as oversampling.
We send all 8 bits than we need to convey the end of transaction by sending the stop bit. The stop bit
is basically making line high again.
Designing the Transmitter Module
Input clock frequency on which
our module will work
Clk and rst are global signals
- A parameter clkcount is calculated as the ratio of clk_freq
(system clock frequency) and baud_rate.
- An always block monitors the posedge clk (rising edge of the
system clock).
- A counter increments until it reaches clkcount/2. When this
happens:
• The counter resets to 0.
• The UART clock (uclk) toggles, marking the half-period.
Operation Flow:
• Count < clkcount/2: The counter increments.
• Count >= clkcount/2: The UART clock toggles, and the
counter resets to zero. This creates a clock for the UART bit
transmission, synced to the desired baud rate.
Temp Variable to store data
Given Parameters:
• System Clock (Sys_Clk): 1 MHz (frequency of the system clock).
• Baud Rate: 9600 (rate at which bits are transmitted).
Ratio (clkcount): This determines the number of system clock cycles
needed to produce one bit.
Half Clock Period: Since the clock signal is toggled at the middle
of the bit duration, the half-period is:
Uclk ➔ slower clock
If rst is high than we are
in idle state
We wait in idle state until
new data gets high
We sample data to din,din
temporary variable We will move to transfer
state
We send start of transmission
Now we will send all 8 bits of data
We begin the transfer of data
We send the data to tx from temporary variable din
At this state we completed the transmission of data
so we made donetx high
Receiver Design
Similar to Transmitter Design
When rx becomes low this marks start of transaction
Once RX becomes low, this marks the start of a
transaction. Hence,from the next logic onward, that
is, from the next cycle of bit duration, we should start
receiving the data from the RX pin. So we jump to the
start state. Else, we stay in an idle state.
-As long as RX is high, we will stay in an idle state. In
the start state,we will collect all bits of data received
serially on the RX pin
We have implemented a right shift register over here,
where the MSB bit will be the data we have on the RX
line,and the seven MSB values of RX data will be shifted
in and right. So at the end of the cycle,we will have RX
on an LSB bit.
Once we receive all eight bits, we make the
count zero again so that it could be utilized in
the next cycle. Done will again be high, and
the state will return to idle, where we will
wait for the next data reception. The default
value for the receiver will be idle.
UART TOP
We have declared a UART top module that will consist of both transmitter and receiver. The series of
ports present in the top module includes clock and reset, which will be global signals going to both
the transmitter and receiver. To receive data from the other device, we have an RX pin. To transmit
data to another device, we have a TX pin. To mark the completion of transmission and reception
operations, we have done RX and done TX, respectively. To send new data, we have a new data and
D_TX bus where we will be adding the data, whereas D_OUT_RX is the bus where we will send the
data received from another device. So these are the series of ports that we need to add to the top
module. Finally, we need to make the necessary connections. So, we have a module UART top, then
the series of ports that we require. Finally, we add the UART TX module and UARTRX module and
perform the necessary connections
UART TB
Testbench Verification for Top Module
We are adding a testbench to verify the functionality of our top module. In this testbench:
1. Variable Setup:
o We define a register variable for the input in the design.
o Wire variables are assigned to each output port.
o These variables are connected to the Device Under Test (DUT).
o Two temporary variables are introduced to hold data for comparison during the
verification process.
2. Transmitter (TX) Logic:
o When new_data is set high, data is transmitted serially on the N_TX line.
o We collect this serial data and compare it with the data on the input bus (D_in).
o If the collected serial data matches the input data bus, it verifies the transmitter is
functioning correctly.
3. Receiver (RX) Logic:
o Data is received serially on the RX line.
o Once the done_RX signal is high, the received data is transferred to the output bus
(D_out_RX).
o We compare the received serial data with the data on the D_out_RX bus to verify the
receiver.
4. Stimulus Generation:
o A high reset signal is maintained for five clock cycles and then released.
o We send 10 random transactions on D_in using a new_random function and transmit
them serially.
o The new_data signal goes high when new data is available for transmission, and the
TX line becomes active.
5. Data Transmission:
o A right-shift register is used to transmit data serially.
o Data is added to the MSB, and after each transmission, the TX data is shifted right,
ensuring the data moves from MSB to LSB.
o The same process is mirrored for the receiver, where data is serially received and
stored in an RX data register.
6. Transaction Execution:
o 10 random transactions are performed for both the transmitter and receiver.
o We observe the done_TX signal for each transaction, confirming successful
transmission by comparing TX_data with the expected values on D_in.
o Similarly, the done_RX signal is checked to ensure that received data matches the
data on the output bus.
7. Final Observation:
o The transmitted and received data values are compared for both the transmitter and
receiver.
o Upon matching values, we conclude that the system is functioning as expected.
module uart_top
#(
parameter clk_freq = 1000000,
parameter baud_rate = 9600
input clk,rst,
input rx,
input [7:0] dintx,
input newd,
output tx,
output [7:0] doutrx,
output donetx,
output donerx
);
uarttx
#(clk_freq, baud_rate)
utx
(clk, rst, newd, dintx, tx, donetx);
uartrx
#(clk_freq, baud_rate)
rtx
(clk, rst, rx, donerx, doutrx);
endmodule
//////////////////////////////////////////////////////////////////
module uarttx
#(
parameter clk_freq = 1000000,
parameter baud_rate = 9600
input clk,rst,
input newd,
input [7:0] tx_data,
output reg tx,
output reg donetx
);
localparam clkcount = (clk_freq/baud_rate); ///x
integer count = 0;
integer counts = 0;
reg uclk = 0;
enum bit[1:0] {idle = 2'b00, start = 2'b01, transfer = 2'b10, done = 2'b11} state;
///////////uart_clock_gen
always@(posedge clk)
begin
if(count < clkcount/2)
count <= count + 1;
else begin
count <= 0;
uclk <= ~uclk;
end
end
reg [7:0] din;
////////////////////Reset decoder
always@(posedge uclk)
begin
if(rst)
begin
state <= idle;
end
else
begin
case(state)
idle:
begin
counts <= 0;
tx <= 1'b1;
donetx <= 1'b0;
if(newd)
begin
state <= transfer;
din <= tx_data;
tx <= 1'b0;
end
else
state <= idle;
end
transfer: begin
if(counts <= 7) begin
counts <= counts + 1;
tx <= din[counts];
state <= transfer;
end
else
begin
counts <= 0;
tx <= 1'b1;
state <= idle;
donetx <= 1'b1;
end
end
default : state <= idle;
endcase
end
end
endmodule
////////////////////////////////////////////////////////////////////
module uartrx
#(
parameter clk_freq = 1000000, //MHz
parameter baud_rate = 9600
input clk,
input rst,
input rx,
output reg done,
output reg [7:0] rxdata
);
localparam clkcount = (clk_freq/baud_rate);
integer count = 0;
integer counts = 0;
reg uclk = 0;
enum bit[1:0] {idle = 2'b00, start = 2'b01} state;
///////////uart_clock_gen
always@(posedge clk)
begin
if(count < clkcount/2)
count <= count + 1;
else begin
count <= 0;
uclk <= ~uclk;
end
end
always@(posedge uclk)
begin
if(rst)
begin
rxdata <= 8'h00;
counts <= 0;
done <= 1'b0;
end
else
begin
case(state)
idle :
begin
rxdata <= 8'h00;
counts <= 0;
done <= 1'b0;
if(rx == 1'b0)
state <= start;
else
state <= idle;
end
start:
begin
if(counts <= 7)
begin
counts <= counts + 1;
rxdata <= {rx, rxdata[7:1]};
end
else
begin
counts <= 0;
done <= 1'b1;
state <= idle;
end
end
default : state <= idle;
endcase
end
end
endmodule
TESTBENCH
module uart_tb;
reg clk = 0,rst = 0;
reg rx = 1;
reg [7:0] dintx;
reg newd;
wire tx;
wire [7:0] doutrx;
wire donetx;
wire donerx;
uart_top #(1000000, 9600) dut (clk, rst, rx, dintx, newd, tx, doutrx, donetx, donerx);
always #5 clk = ~clk;
reg [7:0] rx_data = 0;
reg [7:0] tx_data = 0;
initial begin
rst = 1;
repeat(5) @(posedge clk);
rst = 0;
for(int i = 0 ; i < 10; i++)
begin
rst = 0;
newd = 1;
dintx = $urandom();
wait(tx == 0);
@(posedge [Link]);
for(int j = 0; j < 8; j++)
begin
@(posedge [Link]);
tx_data = {tx,tx_data[7:1]};
end
@(posedge donetx);
end
for(int i = 0 ; i < 10; i++)
begin
rst = 0;
newd = 0;
rx = 1'b0;
@(posedge [Link]);
for(int j = 0; j < 8; j++)
begin
@(posedge [Link]);
rx = $urandom;
rx_data = {rx, rx_data[7:1]};
end
@(posedge donerx);
end
end
endmodule