Custom RTL Design: TX & RX
This is the first hands-on RTL episode. We translate the UART protocol specification directly into synthesizable SystemVerilog — starting from the mathematical relationship between clock frequency and baud rate, then building the TX FSM and RX center-sampling logic. Each design decision is derived from first principles so you can reproduce and modify it for any FPGA or baud rate.
Design Requirements
Target: 50 MHz system clock, 8E1 format
| Requirement | Specification |
|---|---|
| System Clock | 50 MHz |
| Baud Rate | 9,600 / 115,200 / 460,800 bps |
| Data Width | 8 bits |
| Parity | Even parity |
| Stop Bits | 1 |
| Format | 8E1 (11 bits total per frame) |
Baud counter maximum:
$$\mathrm{BAUD}_{\mathrm{CNT,MAX}} = \frac{50{,}000{,}000}{115{,}200} \approx 434$$
The counter counts system clock cycles. When it reaches 434, one baud period has elapsed and it resets. The resulting one-cycle pulse (baud_tick) drives all TX bit transitions.
Integer division accuracy: 50,000,000 ÷ 115,200 = 434.027… → we use 434. The error is 0.006%, accumulating to less than 0.07% across an 11-bit frame — well within the ±2–3% UART tolerance.
Baud Rate Generator
Counts system clock cycles and produces a one-cycle baud_tick pulse.
| |
$clog2(BAUD_CNT_MAX) automatically sizes the counter register — for 434 counts, 9 bits are required. Using $clog2 keeps the code portable: changing CLK_FREQ or BAUD_RATE automatically adjusts the counter width without any manual recalculation.
TX FSM — States
IDLE → START → DATA (8 bits) → PARITY → STOP → IDLE
| |
FSM design note: The
tx_shiftregister serializes the data. On eachbaud_tick, the register shifts right by one bit andtx_outtakes the next bit in sequence. Even parity is^tx_data— the reduction XOR of all 8 data bits, computed combinationally from the original data rather than from the shifted register.
RX FSM — Center Sampling
Sample each bit at its center for maximum noise margin.
$$\text{Center offset} = \frac{\mathrm{BAUD}_{\mathrm{CNT,MAX}}}{2} \approx 217,\text{CLK cycles}$$
| |
False start detection: After the falling edge triggers the START state, the FSM waits half a bit period and re-checks
uart_rx. If the line is already High again, the transition was noise — the FSM returns to IDLE without consuming a byte. This glitch rejection is critical on noisy lines and is the reason the half-period wait exists.
Shift direction in RX: Data is shifted in MSB-first into
rx_shift({uart_rx, rx_shift[7:1]}). After 8 shifts,rx_shift[0]holds D0 (the first bit received = LSB of the byte), meaning the finalrx_shiftcontains the byte in the correct bit order.
Center Sampling — Animated
The receiver must sample at the center of each bit period — not at the edge — to avoid noise contamination near signal transitions.
Testbench Strategy
| |
The loopback testbench connects TX output directly to RX input, verifying end-to-end correctness in one simulation. If all bits, timing, and parity computations are correct, the received byte matches the sent byte and parity_err stays Low.
Additional verification steps:
- Confirm TX waveform: Start(0) → D0..D7 LSB-first → Parity → Stop(1)
- Check
baud_cntvalue at each RX sample point (should be ≈ BAUD_CNT_MAX/2 after the first, then BAUD_CNT_MAX-1 for subsequent) - Parity error injection: force a wrong bit on the wire, confirm
parity_errasserts - Frame error injection: hold RX low during Stop bit, confirm
frame_errasserts
UART Frame Simulation Demo
Vivado simulation waveform demo (xsim)
Episode 5 Summary
- Baud Generator:
cnt == BAUD_CNT_MAX-1→ one-cyclebaud_tick - TX FSM: IDLE → START → DATA(8) → PARITY → STOP
- RX FSM: half-period delay → center sampling; validate Start and Stop bits
- Even Parity:
^tx_data(reduction XOR) - Testbench: loopback TX→RX; assert data and parity correctness
Next Episode
Episode 6: Top Module — Integrating TX and RX Clock wizard, reset logic, parameterization, reusable interface design
Key Takeaways
- The baud generator uses integer division —
CLK_FREQ / BAUD_RATErounded down; the resulting error (~0.006% for 115200 bps at 50 MHz) is negligible within one UART frame baud_tickis a single-cycle pulse — advancing the TX FSM state only onbaud_tickguarantees exactly one baud period per state- RX center sampling delays
BAUD_CNT_MAX/2after the Start Bit edge before the first sample; this provides maximum margin against clock drift and edge noise - False start detection (re-check Start Bit at the half-period point) rejects glitches that would otherwise corrupt a received byte
Code and References
- RTL source repository: https://github.com/Easy-FPGA/easyfpga-uart-core-sv.git
- YouTube: https://www.youtube.com/@easy-fpga