← Back to list

Building a UART Transmitter in Verilog: Step by Step

A Beginner’s Guide to Sending Serial Data from Your FPGA

csjo logicion · 2026-04-20 00:19 · 0 claps · 4.9 min read
#fpga #verilog #uart #hardware #digital-design
Open on Medium ↗

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