Building a UART Transmitter in Verilog: Step by Step
A Beginner’s Guide to Sending Serial Data from Your FPGA
Building a UART Transmitter in Verilog: Step by Step
A Beginner’s Guide to Sending Serial Data from Your FPGA
If you’ve ever used a serial terminal to see messages pop up from a microcontroller or FPGA, you’ve almost certainly used UART — the Universal Asynchronous Receiver-Transmitter. Despite how simple it sounds, UART is one of the most important communication protocols every hardware engineer should master. It’s the “Hello World” of digital communication, and it shows up on virtually every FPGA development board.
In this guide, we’ll build a UART transmitter in Verilog from the ground up. By the end, you’ll understand the protocol’s timing, the role of the baud rate, and how to describe a working transmitter as a finite state machine (FSM). Let’s get started.
What is UART, Really?
UART is a simple asynchronous serial communication protocol that uses just two wires per direction: TX (transmit) and RX (receive). “Asynchronous” means there’s no shared clock between the sender and the receiver — both sides just have to agree on the speed, called the baud rate. Common baud rates are 9600, 115200, and 921600 bits per second.
A single UART “frame” typically looks like this: the line sits idle at logic HIGH (1), then drops LOW for one bit time to mark the start bit. Next come the data bits (usually 8), sent least significant bit first. Finally, the line returns HIGH for one or two stop bits. That’s it — no clock, no handshake, just a well-defined waveform both sides can recognize.
Think of UART like two old telegraph operators who don’t share a ticking metronome. They just agreed beforehand how fast to tap, and they use a known “start” tap to let the listener line up the rest.
The Baud Rate: From Bits per Second to Clock Cycles
Most FPGA boards run on a high-frequency clock — say 100 MHz. But our UART needs to produce bits at a much slower rate. If we want 115200 baud, every bit must last exactly 1/115200 seconds, or about 8.68 microseconds.
To convert that into FPGA clock cycles, just divide the clock frequency by the baud rate:
CLKS_PER_BIT = FPGA_Clock_Freq / Baud_Rate
For a 100 MHz clock and 115200 baud, CLKS_PER_BIT = 100,000,000 / 115,200 ≈ 868. In plain words: every UART bit is worth 868 ticks of our FPGA’s clock. That value is the heartbeat of our transmitter. A small counter that rolls over every 868 cycles will act as our “bit-time” trigger.
Designing the Transmitter as a State Machine
A UART transmitter is a textbook use case for a finite state machine. We only need five states:
IDLE — hold the TX line HIGH and wait for a transmit request.
START — drive TX LOW for one bit time to mark the start bit.
DATA — shift out the 8 data bits one at a time, LSB first.
STOP — drive TX HIGH for one bit time as the stop bit.
CLEANUP — raise a “done” flag for one cycle, then return to IDLE.
The transitions between states are all driven by one thing: the bit-time counter. When the counter hits CLKS_PER_BIT − 1, we know one full bit has elapsed, so we can move on.
The Verilog Code
Here’s a compact, beginner-friendly UART transmitter. It assumes an 8-bit data payload, one stop bit, and no parity — the most common configuration (“8-N-1”).
module uart_tx #(
parameter CLKS_PER_BIT = 868 // 100 MHz clock / 115200 baud
)(
input wire clk,
input wire tx_start,
input wire [7:0] tx_data,
output reg tx_line,
output reg tx_done
);
// FSM state encoding
localparam IDLE = 3'd0;
localparam START = 3'd1;
localparam DATA = 3'd2;
localparam STOP = 3'd3;
localparam CLEANUP = 3'd4;
reg [2:0] state = IDLE;
reg [$clog2(CLKS_PER_BIT):0] clk_cnt = 0;
reg [2:0] bit_idx = 0;
reg [7:0] tx_buf = 0;
always @(posedge clk) begin
case (state)
IDLE: begin
tx_line <= 1'b1; // line idles HIGH
tx_done <= 1'b0;
clk_cnt <= 0;
bit_idx <= 0;
if (tx_start) begin
tx_buf <= tx_data;
state <= START;
end
end
START: begin
tx_line <= 1'b0; // start bit is LOW
if (clk_cnt < CLKS_PER_BIT - 1)
clk_cnt <= clk_cnt + 1;
else begin
clk_cnt <= 0;
state <= DATA;
end
end
DATA: begin
tx_line <= tx_buf[bit_idx];
if (clk_cnt < CLKS_PER_BIT - 1)
clk_cnt <= clk_cnt + 1;
else begin
clk_cnt <= 0;
if (bit_idx < 7)
bit_idx <= bit_idx + 1;
else begin
bit_idx <= 0;
state <= STOP;
end
end
end
STOP: begin
tx_line <= 1'b1; // stop bit is HIGH
if (clk_cnt < CLKS_PER_BIT - 1)
clk_cnt <= clk_cnt + 1;
else begin
clk_cnt <= 0;
tx_done <= 1'b1;
state <= CLEANUP;
end
end
CLEANUP: begin
tx_done <= 1'b0;
state <= IDLE;
end
endcase
end
endmodule
Walking Through the Logic
Let’s unpack what’s happening. When tx_start pulses HIGH, the IDLE state latches tx_data into tx_buf and moves to START. In START, the line is pulled LOW for CLKS_PER_BIT cycles. Then DATA uses bit_idx as an index into tx_buf, shifting out one bit every 868 cycles, least significant bit first — that’s how UART expects the data. After all 8 bits, STOP holds the line HIGH for another bit time, and CLEANUP raises tx_done for exactly one clock cycle so a higher-level controller can know the byte was sent.
Quick Reference: Baud Rate vs. Clocks per Bit
For a 100 MHz clock, here are the CLKS_PER_BIT values you’ll want to plug into the parameter:
9600 baud → 10,417 clocks per bit
19,200 baud → 5,208 clocks per bit
38,400 baud → 2,604 clocks per bit
115,200 baud → 868 clocks per bit
921,600 baud → 109 clocks per bit
In real hardware, UART tolerates small baud-rate mismatches — typically up to ±2% — because the receiver samples each bit in the middle of its bit window. That’s why integer rounding is acceptable here.
Testing Your Transmitter
Before wiring this to real pins, always simulate it. A testbench should drive tx_start HIGH for one cycle, load a known byte such as 8'h55 (01010101), and then watch tx_line. Use a short CLKS_PER_BIT like 8 or 10 in simulation so your waveform doesn’t take forever to produce. You should see a nice clean LOW-start, alternating data bits, and a HIGH stop bit.
On real hardware, the simplest check is to connect the tx_line to a USB-to-serial adapter (such as an FT232 or CP2102) and open a terminal like PuTTY or minicom at 115200 baud, 8-N-1. If you continuously send “A” (8'h41), you should see a steady stream of A’s scrolling in your terminal window. Congratulations — you’ve just bridged the digital silicon inside your FPGA to a classic serial protocol in wide industrial use.
Common Pitfalls to Avoid
Beginners usually stumble in the same handful of places. First, sending the MSB first — UART is strictly least-significant-bit first, and flipping the order will produce garbled characters. Second, forgetting to hold tx_start for only one cycle; if it stays HIGH, the FSM may re-trigger and send extra bytes. Third, reusing a single blocking assignment (=) inside a sequential always block — always use non-blocking (≤) for flip-flop logic. And finally, skipping the CLEANUP state; it may look optional but gives your consumer a reliable one-cycle pulse to detect completion.
What’s Next
A transmitter is only half of a UART. In the next post, we’ll look at the trickier cousin — “What is UVM? A Gentle Introduction for Beginners” — where we step back from RTL and explore how professional verification engineers test complex designs using a standardized methodology built on SystemVerilog.
If you found this helpful, follow for more FPGA content — new beginner-friendly tutorials every day. Happy coding, and may your waveforms stay clean!
메타데이터
- post_id
- 1fbbfbb8aeff
- slug
- building-a-uart-transmitter-in-verilog-step-by-step-1fbbfbb8aeff
- url
- https://medium.com/@ahe24mobile/building-a-uart-transmitter-in-verilog-step-by-step-1fbbfbb8aeff
- canonical_url
- https://medium.com/@ahe24mobile/building-a-uart-transmitter-in-verilog-step-by-step-1fbbfbb8aeff
- author_url
- https://medium.com/@ahe24mobile
- status
- ok
- fetched_at
- 2026-06-21 19:25:17