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.