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.
UART sends characters as a sequence of bits at a fixed rate (the baud rate). The line idles high. Each character is framed as:
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.
| Port | Dir | Width | Meaning |
|---|---|---|---|
| clk | input | 1 | system clock |
| rst | input | 1 | synchronous reset |
| start | input | 1 | pulse high for one cycle to begin transmitting |
| data | input | 8 | byte to transmit — sampled when start goes high |
| tx | output | 1 | serial output — connect to FPGA pin |
| busy | output | 1 | 1 while transmitting — don't assert start when busy |
| done | output | 1 | one-cycle pulse when transmission is complete |
// 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`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
endmoduleok 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
start pulse into a shift register — don't read data mid-frame.8 data bits, No parity, 1 stop bit. The most common serial format. Each character: start(0), 8 data bits LSB first, stop(1).
Compute CLK_HZ / BAUD = cycles per bit. Count to this value before advancing to the next bit.
LSB first. For 0x41 (01000001) the wire sequence is: 1,0,0,0,0,0,1,0.