VHDL · Chapter 20.1 · Capstone Projects
Capstone: UART Transceiver
Opening the capstone projects module, this is the first complete, verified block built end to end, integrating the whole track. A UART is ideal, because it exercises baud-rate generation from a clock-enable tick rather than a divided clock, a transmit state machine that frames a byte as a start bit, eight data bits sent least-significant-bit first, and a stop bit, and a receiver that oversamples the line, synchronizes the asynchronous receive input through a two-flip-flop synchronizer, detects the start bit, and samples each bit at its midpoint for noise immunity. Around the core sit shift registers, a handshake, and generics so one source serves any rate. Building it applies state machines, datapath and control separation, clock-domain-crossing synchronization, clean RTL, and verification corner cases such as framing, back-to-back frames, and baud tolerance.
Intermediate16 min readVHDLCapstoneUARTFSMSerialVerification
1. Engineering intuition — a serializer and a deserializer sharing a baud clock
A UART is two small machines around a shared sense of time. The transmitter is a serializer: it takes a parallel byte, wraps it in a start bit and a stop bit, and shifts it out one bit per baud period, LSB-first. The receiver is a deserializer with a twist — it has no shared clock with the sender, so it must recover timing from the data itself. It does that by oversampling the line much faster than the baud rate, watching for the start bit's falling edge, then sampling each bit at its midpoint (the most stable point, far from the edges where the value is settling). Both sides march to a baud tick generated by counting the system clock. Picture those three pieces — baud generator, TX serializer FSM, RX oversampling FSM — and the whole design organizes itself.
2. Formal explanation — the UART architecture
-- GENERICS make it rate-agnostic; one source serves any clock/baud.
entity uart is
generic ( CLK_FREQ : positive := 50_000_000;
BAUD : positive := 115_200;
DATA_BITS : positive := 8 );
port ( clk, rst : in std_logic;
-- TX side (parallel in, serial out)
tx_data : in std_logic_vector(DATA_BITS-1 downto 0);
tx_start : in std_logic; tx_busy : out std_logic; txd : out std_logic;
-- RX side (serial in, parallel out)
rxd : in std_logic; rx_data : out std_logic_vector(DATA_BITS-1 downto 0);
rx_valid : out std_logic );
end entity;
-- BAUD GENERATION: a clock-ENABLE tick (NOT a divided clock, 17.4). For RX, oversample (e.g. 16x).
-- constant DIV : positive := CLK_FREQ / BAUD; -- one baud tick every DIV clocks (TX)
-- constant OVS : positive := 16; -- RX oversampling factor
-- constant ODIV: positive := CLK_FREQ / (BAUD*OVS); -- one oversample tick
-- TX FSM: IDLE -> START(drive '0') -> DATA(shift DATA_BITS, LSB-first) -> STOP(drive '1') -> IDLE.
-- RX FSM: synchronize rxd (2-FF) -> detect START (falling edge) -> sample each bit at MID-BIT -> stop.The UART is generics + a baud tick (clock enable, not a divided clock) + a TX FSM (idle/start/data/stop, LSB-first) + an RX FSM (synchronize, start-detect, mid-bit sample). The receiver oversamples to recover timing without a shared clock — the architecture's defining feature.
3. Production usage — TX serialize, RX synchronize and mid-bit sample
-- TX: on a baud tick, shift the framed word out LSB-first (one bit per baud period).
process (clk) begin
if rising_edge(clk) then
if rst = '1' then tx_state <= IDLE; txd <= '1'; tx_busy <= '0';
elsif baud_tick = '1' then
case tx_state is
when IDLE => txd <= '1';
if tx_start = '1' then frame <= tx_data; tx_busy <= '1'; tx_state <= START; end if;
when START => txd <= '0'; bitc <= 0; tx_state <= DATA; -- start bit
when DATA => txd <= frame(0);
frame <= '0' & frame(DATA_BITS-1 downto 1); -- LSB-first shift
if bitc = DATA_BITS-1 then tx_state <= STOP; else bitc <= bitc + 1; end if;
when STOP => txd <= '1'; tx_busy <= '0'; tx_state <= IDLE; -- stop bit
end case;
end if;
end if;
end process;
-- RX: synchronize the ASYNC line first (CDC, 17.5), then sample each bit at its MIDPOINT.
process (clk) begin
if rising_edge(clk) then
rxd_s1 <= rxd; rxd_s2 <= rxd_s1; -- 2-FF synchronizer on the asynchronous rx line
rx_valid <= '0';
if ovs_tick = '1' then
-- detect start (falling edge of rxd_s2), then count oversamples (8 -> mid of start, +16 per bit)
-- to land sampling on each bit's MIDPOINT; shift sampled bit into rx_shift; on stop, latch:
-- rx_data <= rx_shift; rx_valid <= '1';
null; -- (oversample counter / state omitted for brevity)
end if;
end if;
end process;What hardware does this become? A compact serial transceiver: a baud counter producing the tick, a small
TX FSM with a shift register driving txd, a two-FF synchronizer plus an RX oversampling FSM with its
own shift register reconstructing rx_data. The two halves are independent (a classic datapath/control split,
18.5) and share only the baud generation. The defining details are the ones the whole track prepared you for: the
baud rate is a clock enable, not a divided clock (17.4); the asynchronous rxd is synchronized before any
logic touches it (17.5); the receiver samples at mid-bit for tolerance to baud mismatch; and generics make
the same source serve 9600 or 921600 baud.
4. Structural interpretation — the UART top
5. Simulation interpretation — a UART frame and mid-bit sampling
UART frame: start, 8 data bits LSB-first, stop; RX samples at mid-bit
12 cycles6. Debugging example — sampling at the bit edge instead of the midpoint (and an unsynchronized rxd)
Expected: a UART that receives reliably, even with small baud mismatch. Observed: occasional wrong bytes
or framing errors, worse as the cable lengthens or the two clocks differ slightly, and rare unreproducible
glitches. Root cause: the receiver sampled near the bit edges instead of the midpoint, so small timing
skew between sender and receiver caught bits mid-transition; and/or the asynchronous rxd was fed into the FSM
without a synchronizer, so metastability occasionally corrupted the start detection. Fix: oversample and
sample each bit at its midpoint (e.g. 8 oversamples after the start edge, then every 16) for tolerance to baud
differences, and synchronize rxd through a two-FF synchronizer before any logic uses it (17.5). Engineering
takeaway: a robust UART receiver samples at mid-bit (not the edge) for baud tolerance and synchronizes the
asynchronous input first — edge sampling and raw async inputs are the two classic UART receive bugs.
-- BUG: sample at the bit boundary + use raw async rxd -> edge/metastability errors.
-- FIX: synchronize rxd, then sample at the MIDPOINT (mid-bit), counting oversamples from the start edge.
rxd_s1 <= rxd; rxd_s2 <= rxd_s1; -- synchronize first
-- on start edge: wait OVS/2 oversamples (mid of start), then +OVS per bit -> sample at each bit's centre.7. Common mistakes & what to watch for
- Edge sampling in the receiver. Sample at mid-bit (oversample + count to the centre) for tolerance to baud mismatch; edge sampling is fragile.
- Unsynchronized rxd. The async receive line must pass a 2-FF synchronizer before any logic (17.5), or metastability corrupts start detection.
- Divided clock for baud. Generate the baud rate as a clock enable/tick on one clock, not a fabric-divided clock (17.4).
- Wrong bit order. UART is LSB-first; shifting MSB-first silently corrupts every byte.
- Skipping verification corners. Test framing (start/stop), back-to-back frames, baud tolerance (±%), and reset mid-frame — not just one clean byte.
8. Engineering insight & continuity
The UART capstone integrates the whole track into one verified block: generics for rate-agnostic reuse, a baud tick (clock enable, not a divided clock), a TX FSM that frames start/data(LSB-first)/stop, and an oversampling RX FSM that synchronizes the asynchronous line, detects the start bit, and samples at mid-bit for baud tolerance — verified against framing, back-to-back, and baud-tolerance corners. It is the template for building a complete, robust block, not just a snippet. The next capstone applies the same end-to-end discipline to a master with clock generation and modes — the next lesson, Capstone: SPI Controller.