Serial data transfer is a core building block in digital systems, but understanding it deeply means building it yourself. This project implements a full-duplex UART (transmitter + receiver) in Verilog, verified entirely through simulation — covering configurable data rates, parity-based error checking, and modular, testbench-driven design.
The system integrates a configurable Baud Rate Generator, a Transmitter (Tx) state machine, and a Receiver (Rx) state machine operating with 16x oversampling to ensure robust mid-bit sampling and glitch rejection. It supports configurable data widths, multiple parity modes, and error flagging (parity and framing errors).
The project files are organized into standard Verilog directories (rtl/ for synthesizable logic and tb/ for verification):
UART/
├── rtl/
│ ├── baud_gen.v # Baud tick generator (1x Tx tick, 16x Rx tick)
│ ├── uart_tx.v # UART Transmitter FSM (start, data, parity, stop)
│ ├── uart_rx.v # UART Receiver FSM (16x oversampling & error check)
│ └── uart_top.v # Top-level wrapper (internal Tx-to-Rx loopback)
├── tb/
│ └── tb_uart_top.v # Self-checking testbench & VCD waveform generator
├── uart_sim.vcd # Waveform output file (generated after simulation)
└── README.md # Architecture & simulation documentation
In a physical application, the Tx line connects to an external receiver (e.g., FTDI chip or microcontroller), and the Rx line connects to an external transmitter. For pure simulation verification without external hardware, uart_top.v ties the transmitter's serial output directly into the receiver's serial input in a loopback topology:
+-----------------------------------------------------------------------------------+
| Top-Level Wrapper (uart_top.v) |
| |
| +--------------------+ tx_tick |
| | +----------------------------+ |
| | Baud Generator | | |
| | (baud_gen.v) +------------+ | |
| +---------+----------+ | rx_tick | |
| ^ v v |
| | +-----+-------+ +---+---------+ |
| clk -----+ | | | | |
| rst_n ---+ | UART Rx | | UART Tx | |
| | (uart_rx.v) | | (uart_tx.v) | |
| tx_start -------------------+-------------+---> | |
| tx_data -------------------+-------------+---> | |
| | | | +---> serial_line |
| | |<--+-------------+ (Tx Output) |
| +------+------+ (Rx Input) |
| | |
| v |
| rx_data, rx_done, |
| parity_error, frame_error |
+-----------------------------------------------------------------------------------+
UART (Universal Asynchronous Receiver-Transmitter) transmits data asynchronously without a shared clock signal. Instead, both ends agree on a fixed baud rate.
Each frame transmitted over the serial line follows this sequence:
Idle (1) ---> [Start Bit: 0] ---> [Data Bits: LSB to MSB] ---> [Parity Bit (Optional)] ---> [Stop Bit: 1] ---> Idle (1)
- Idle State: High line state (
1). - Start Bit: Low logic pulse (
0) marking the start of transmission. - Data Payload: Configurable N data bits (default = 8 bits), sent Least Significant Bit (LSB) first.
- Parity Bit (Optional): Error detection bit calculated based on the data payload.
- Stop Bit: High logic pulse (
1) marking frame termination.
Generates two synchronous timing reference tick signals based on the input clock frequency (CLK_FREQ) and desired baud rate (BAUD_RATE):
tx_tick: Fires once per bit period (1x Baud rate).rx_tick: Fires 16x faster than the baud rate (16x oversampling clock).
For a 50 MHz system clock (CLK_FREQ = 50_000_000) and 9600 Baud (BAUD_RATE = 9600):
-
Tx Divisor (
TX_DIVISOR):$N_{\text{tx}} = \frac{50,000,000}{9600} \approx 5208 \text{ clock cycles}$ -
Rx Divisor (
RX_DIVISOR):$N_{\text{rx}} = \frac{50,000,000}{9600 \times 16} \approx 325 \text{ clock cycles}$
The transmitter converts parallel input data (tx_data) into a serial bit stream (tx) governed by tx_tick.
+--------------+
| IDLE |<---------------+
+------+-------+ |
| tx_start |
v |
+--------------+ |
| START | |
+------+-------+ |
| tx_tick |
v |
+--------------+ |
+--------->| DATA | |
| +------+-------+ |
bit_index < 7 | bit_index == 7 |
(tx_tick) v |
Parity En / \ Parity None |
+--------+ +---------+ |
| | |
v v |
+--------------+ +--------------+ |
| PARITY +-------->| STOP +-----+
+--------------+ tx_tick +--------------+ tx_tick
IDLE: Serial outputtx = 1. Waits fortx_startpulse. Ontx_start, loads data into shift register and computes parity XOR bit (^tx_data).START: Assertstx = 0for 1 baud period (tx_tick).DATA: Shifts out data bits LSB-first on eachtx_tick.PARITY: Transmits calculated parity bit if enabled (2'b01= Even,2'b10= Odd).STOP: Assertstx = 1for 1 baud period, setstx_donehigh, and returns toIDLE.
The receiver samples serial line input (rx) using 16x oversampling (rx_tick) to reconstruct parallel data and verify protocol integrity.
To protect against line noise and clock jitter, each bit period is divided into 16 sample counts (0 to 15):
Bit Cell Timing (16 rx_ticks per Bit):
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
^ ^
Start Bit Verification Bit Sample Point
- Start Bit Detection & Glitch Filtering:
- Detects falling edge (
rx == 0) inIDLE. - Waits 8 ticks to sample the middle of the Start bit (count 7).
- If line is still
0, start bit is verified (valid frame). If line returned to1, it is dismissed as a false noise glitch, and state reverts toIDLE.
- Detects falling edge (
- Data & Parity Sampling:
- Samples input line at count 15 (exact mid-point of subsequent bit cells) for maximum noise margin.
- Parity Error (
parity_error): Compares received parity bit (parity_rx) against calculated parity of received data bits (parity_calc). - Framing Error (
frame_error): Flagged if the Stop bit is not logic high (rx != 1'b1).
Instantiates baud_gen, uart_tx, and uart_rx. Connects tx output line directly to rx input line internally for verified full-duplex loopback simulation.
Automates end-to-end verification across all supported parity configurations:
- No Parity Mode (
2'b00): Sends0x55and0xA3. - Even Parity Mode (
2'b01): Sends0x0F. - Odd Parity Mode (
2'b10): Sends0xFF.
Outputs pass/fail assertions for received data bytes and logs framing or parity flags. Also generates a uart_sim.vcd waveform dump file.
| Parameter Name | Default Value | Description |
|---|---|---|
CLK_FREQ |
50_000_000 (50 MHz) |
Reference system clock frequency in Hz |
BAUD_RATE |
9600 |
Target serial communication baud rate in bits/sec |
DATA_BITS |
8 |
Data payload width (bits per frame) |
| Value | Mode | Description |
|---|---|---|
2'b00 |
None | Standard 8N1 (8 data bits, no parity, 1 stop bit) |
2'b01 |
Even | Parity bit set so total number of 1s is even |
2'b10 |
Odd | Parity bit set so total number of 1s is odd |
- Icarus Verilog (
iverilog): Open-source Verilog simulator. - GTKWave: Waveform viewer for inspecting
.vcdfiles.
- Ubuntu / Debian:
sudo apt-get update sudo apt-get install iverilog gtkwave
- macOS (via Homebrew):
brew install icarus-verilog gtkwave
- Windows: Download installer binaries from Bledyer's Icarus Verilog for Windows.
-
Compile and Run Simulation: Open terminal / PowerShell in the project root directory and execute:
iverilog -g2012 -o sim.out rtl/baud_gen.v rtl/uart_tx.v rtl/uart_rx.v rtl/uart_top.v tb/tb_uart_top.v vvp sim.out
-
Expected Output Log:
VCD info: dumpfile uart_sim.vcd opened for output. ---- No Parity ---- PASS: sent 0x55, received 0x55 PASS: sent 0xa3, received 0xa3 ---- Even Parity ---- PASS: sent 0xf, received 0xf ---- Odd Parity ---- PASS: sent 0xff, received 0xff ALL TESTS PASSED -
Inspect Waveforms with GTKWave:
gtkwave uart_sim.vcd
Add signals such as
tb_uart_top.dut.serial_line,tb_uart_top.dut.u_uart_tx.state, andtb_uart_top.dut.u_uart_rx.stateto inspect serial waveforms and state transitions bit-by-bit.