Skip to content

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

uart_architecture.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- 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

uart_tx_rx.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- 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

UART: baud generator feeding a transmit FSM and a synchronized oversampling receive FSMbaud tickoversample tickrxd_s2baud generatorbaud tick + oversample tick(enables)TX FSM (serializer)start / data LSB-first /stop -> txd2-FF synchronizerasync rxd -> safeRX FSM (oversample)start-detect + mid-bit ->rx_data12
A UART transceiver is a baud generator feeding two independent FSMs. The baud generator counts the system clock to produce a baud tick (a clock enable, not a divided clock) and an oversample tick for the receiver. The transmit FSM serializes a parallel byte as start, data (LSB-first), and stop bits onto txd, advancing one bit per baud tick, with tx_busy as its handshake. The receive path first synchronizes the asynchronous rxd line through a two-flip-flop synchronizer, then an oversampling FSM detects the start bit and samples each bit at its midpoint, deserializing into rx_data with rx_valid. Generics set the clock frequency, baud, and data width. This is the integrated-block structure; the waveform below shows a UART frame and the receiver's mid-bit sampling.

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 cycles
UART frame: start, 8 data bits LSB-first, stop; RX samples at mid-bitSTART bit: txd driven low marks the frame beginningSTART bit: txd driven …DATA bits d0..d7 shifted LSB-first; RX samples each at its midpoint (rx_smpl)DATA bits d0..d7 shift…STOP bit (txd high); receiver latches the byte and asserts rx_validSTOP bit (txd high); r…txd101011010011fieldidleSd0d1d2d3d4d5d6d7Tidlerx_smpl001011010000rx_valid000000000010t0t1t2t3t4t5t6t7t8t9t10t11
The transmitter frames the byte: a low start bit, eight data bits LSB-first, then a high stop bit, one bit per baud period. The receiver, having synchronized rxd and detected the start edge, samples each bit at its midpoint (rx_smpl) where the value is most stable, reconstructs the byte, and pulses rx_valid at the stop bit. Mid-bit sampling is what gives the link tolerance to small baud-rate differences between transmitter and receiver.

6. 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.

midbit_and_sync.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- 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.