HomeFPGA from ScratchDay 13
DAY 13 · SERIAL COMMUNICATION

Building a UART Transmitter

By EcrioniX · Updated Jun 11, 2026

UART is the simplest serial protocol and the most useful debug tool on any FPGA. Once you have a working TX, you can print values from your design to a terminal at any time. This lesson builds uart_tx.v — a complete 8N1 UART transmitter — from the framing standard through the FSM to the testbench that verifies every bit.

1. 8N1 framing — what goes on the wire

UART sends characters as a sequence of bits at a fixed rate (the baud rate). The line idles high. Each character is framed as:

8N1 UART Frame — transmitting 0x41 ('A') IDLE START=0 b0=1 b1=0 b2=0 b3=0 b4=0 b5=0 b6=1 b7=0 STOP=1 0x41 = 0100 0001 → LSB first on wire: 1,0,0,0,0,0,1,0
8N1 frame for 'A' (0x41). Line idles high. Start bit pulls low. 8 data bits LSB first. Stop bit returns high.

2. Baud rate divider

Each bit lasts exactly CLK_HZ / BAUD clock cycles. At 100 MHz / 115200 baud = 868 cycles per bit. A counter counts to this value before advancing to the next bit. This is the only timing mechanism UART needs.

3. Port table

PortDirWidthMeaning
clkinput1system clock
rstinput1synchronous reset
startinput1pulse high for one cycle to begin transmitting
datainput8byte to transmit — sampled when start goes high
txoutput1serial output — connect to FPGA pin
busyoutput11 while transmitting — don't assert start when busy
doneoutput1one-cycle pulse when transmission is complete

4. uart_tx.v — design

uart_tx.v — UART transmitter (8N1)
// UART Transmitter — 8N1 format
// CLK_HZ / BAUD must equal integer cycles-per-bit
module uart_tx #(
    parameter CLK_HZ = 100_000_000,
    parameter BAUD   = 115_200
)(
    input  wire       clk,
    input  wire       rst,
    input  wire       start,    // pulse to begin
    input  wire [7:0] data,     // byte to send
    output reg        tx,       // serial output
    output wire       busy,     // 1 while sending
    output reg        done      // 1-cycle completion pulse
);
    localparam CLKS_PER_BIT = CLK_HZ / BAUD; // 868 at 100MHz/115200
    localparam IDLE=2'd0, START=2'd1, DATA=2'd2, STOP=2'd3;

    reg [1:0]  state;
    reg [15:0] baud_cnt;
    reg [2:0]  bit_idx;
    reg [7:0]  shift;

    assign busy = (state != IDLE);

    always @(posedge clk) begin
        done <= 1'b0;
        if (rst) begin
            state <= IDLE; tx <= 1'b1; baud_cnt <= 0; bit_idx <= 0;
        end else begin
            case (state)
                IDLE: begin
                    tx <= 1'b1;
                    if (start) begin shift <= data; state <= START; baud_cnt <= 0; end
                end
                START: begin
                    tx <= 1'b0;  // start bit
                    if (baud_cnt == CLKS_PER_BIT-1) begin baud_cnt<=0; bit_idx<=0; state<=DATA; end
                    else baud_cnt <= baud_cnt+1;
                end
                DATA: begin
                    tx <= shift[bit_idx];  // LSB first
                    if (baud_cnt == CLKS_PER_BIT-1) begin
                        baud_cnt <= 0;
                        if (bit_idx == 3'd7) state <= STOP;
                        else bit_idx <= bit_idx+1;
                    end else baud_cnt <= baud_cnt+1;
                end
                STOP: begin
                    tx <= 1'b1;  // stop bit
                    if (baud_cnt == CLKS_PER_BIT-1) begin
                        baud_cnt <= 0; state <= IDLE; done <= 1'b1;
                    end else baud_cnt <= baud_cnt+1;
                end
            endcase
        end
    end
endmodule

5. Testbench — verify every bit

tb_uart_tx.v — testbench (small CLKS_PER_BIT for speed)
`timescale 1ns/1ps
module tb_uart_tx;
    reg  clk=0,rst=1,start=0; reg [7:0] data;
    wire tx,busy,done;
    // Use CLK_HZ=10, BAUD=1 -> 10 cycles per bit for fast simulation
    uart_tx #(.CLK_HZ(10),.BAUD(1)) dut(.clk(clk),.rst(rst),.start(start),.data(data),.tx(tx),.busy(busy),.done(done));
    always #5 clk=~clk;
    integer errors=0;
    task sample_bit(input exp);
        repeat(10) @(posedge clk); // wait one bit period
        #1;
        if(tx!==exp)begin $display("FAIL tx=%b exp=%b",tx,exp);errors=errors+1;end
        else $display("ok   tx=%b",tx);
    endtask
    initial begin
        @(posedge clk); rst=0;
        data=8'h41; // 'A' = 0100_0001
        start=1; @(posedge clk); #1; start=0;
        // Check: START(0), b0=1,b1=0,b2=0,b3=0,b4=0,b5=0,b6=1,b7=0, STOP(1)
        sample_bit(0); // start
        sample_bit(1); // bit0
        sample_bit(0); // bit1
        sample_bit(0); // bit2
        sample_bit(0); // bit3
        sample_bit(0); // bit4
        sample_bit(0); // bit5
        sample_bit(1); // bit6
        sample_bit(0); // bit7
        sample_bit(1); // stop
        @(posedge clk); #1;
        if(done!==1)begin $display("FAIL done not asserted");errors=errors+1;end
        else $display("ok   done=1");
        if(errors==0) $display("ALL TESTS PASSED"); else $display("%0d FAILED",errors);
        $finish;
    end
endmodule
expected output
ok   tx=0
ok   tx=1
ok   tx=0
ok   tx=0
ok   tx=0
ok   tx=0
ok   tx=0
ok   tx=1
ok   tx=0
ok   tx=1
ok   done=1
ALL TESTS PASSED

🎯 Day 13 takeaways

FAQ

What is UART 8N1?

8 data bits, No parity, 1 stop bit. The most common serial format. Each character: start(0), 8 data bits LSB first, stop(1).

How do you set baud rate in Verilog?

Compute CLK_HZ / BAUD = cycles per bit. Count to this value before advancing to the next bit.

What order are bits transmitted?

LSB first. For 0x41 (01000001) the wire sequence is: 1,0,0,0,0,0,1,0.

Previous
← Day 12: Seven-segment display

← Full roadmap