0% found this document useful (0 votes)
3 views5 pages

UART Core Module Analysis

The document provides a detailed analysis of a UART core module, describing its functionality, interfaces, and compliance with the 8-N-1 protocol. It outlines the transmitter and receiver logic, including state machines for handling data transmission and reception, as well as potential issues and recommendations for improvements. Key aspects include the handling of start and stop bits, the use of a two-flop synchronizer for the receiver, and suggestions for enhancing noise robustness and flexibility in configuration.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views5 pages

UART Core Module Analysis

The document provides a detailed analysis of a UART core module, describing its functionality, interfaces, and compliance with the 8-N-1 protocol. It outlines the transmitter and receiver logic, including state machines for handling data transmission and reception, as well as potential issues and recommendations for improvements. Key aspects include the handling of start and stop bits, the use of a two-flop synchronizer for the receiver, and suggestions for enhancing noise robustness and flexibility in configuration.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

UART Core Module Analysis

A typical UART frame consists of an idle (mark) state held at logic 1, followed by a low start bit for one
bit-time, then the data bits (usually 5–9 bits, commonly 8 bits) sent least-significant-bit first, and finally
one or more high stop bits 1 2 . In other words, when the transmitter is idle the line stays high, and
a falling edge (high→low) for one bit-time signifies the start of a frame 3 . After the start bit, the data
payload is clocked out serially (LSB first), and then the line returns to high (the stop bit) for at least one
bit-time to signal the end of the frame 1 2 . This core’s configuration matches the common 8‑N‑1
UART format (8 data bits, no parity, 1 stop bit) with asynchronous timing set by the baud_div
parameter (system clock divided by baud rate, e.g. 50 MHz/115200 ≈ 434).

Functionality and Interfaces


The uart_core module implements both a transmitter (TX) and receiver (RX) in one. Its interface is
classic UART: a parallel-byte input ( tx_data ) and a single-cycle pulse input ( tx_start ) for TX, with
outputs tx (serial line) and tx_busy (active while sending). For RX it takes a serial input rx , and
produces a parallel output rx_data along with a one-cycle strobe rx_valid when a byte has been
received. There is also a 16-bit baud_div input that sets the bit timing (number of system-clock cycles
per UART bit). The core uses an asynchronous reset ( rst_n ) that initializes TX to idle-high and RX to
idle state, and it handles metastability on the RX input using a two-stage synchroniser ( rx_sync1 ,
rx_sync2 ) 4 .

Internally, a baud-rate generator counts system clocks and asserts baud_tick once every
baud_div cycles, yielding the desired serial bit rate. The TX state machine has four states ( TX_IDLE ,
TX_START , TX_DATA , TX_STOP ) corresponding to idle, start bit, data bits, and stop bit. A rising-
edge detector on tx_start ( tx_start_pulse ) ensures that even a one-clock pulse is captured. In
TX_IDLE , the line is kept high ( tx = 1 ) and tx_busy = 0 until a tx_start_pulse arrives; then
tx_busy is set high, the input byte is latched into a shift register, and the machine moves to
TX_START . On the next baud_tick , the start bit is sent by driving tx low, then the state enters
TX_DATA . In TX_DATA , each baud_tick shifts out one data bit (LSB first) on tx and shifts the
buffer right, counting up to the last bit. When all data bits are sent, the state goes to TX_STOP . On the
following baud_tick , tx is driven high for the stop bit, tx_busy is cleared, and the state returns
to TX_IDLE . The code ensures that tx remains high (idle) between frames and that only complete
start/data/stop bits are output. (Many UART transmitter designs also include a one-cycle “done” pulse
when transmission finishes 5 ; here, tx_busy simply goes low to indicate completion.)

1
Transmitter (TX) Logic
• Idle state: Drives tx = 1 (mark) and tx_busy = 0 . Any rising edge on tx_start latches
tx_data into a shift register and transitions to the start state. Note the code uses edge
detection ( tx_start_pulse ) so a multi-cycle tx_start is only honored once; the host must
de-assert and re-assert tx_start for each frame.
• Start bit: On the next baud tick, tx is driven low for one bit period (the start bit) and the
machine moves to the data state. Only complete bits are output on baud ticks, so if tx_start
arrived part-way through a bit period, the code delays until the next tick. This ensures bit timing
is aligned to the baud counter.
• Data bits: On each subsequent baud_tick , the current LSB of the shift register is placed on
tx , and the register is shifted right. A 4-bit counter ( tx_bit_cnt ) increments each bit. After
DATA_WIDTH bits, the state machine transitions to stop. (The design checks the bit counter only
on ticks, so it always sends exactly DATA_WIDTH bits.)
• Stop bit: On the next baud tick, tx is driven high for one stop-bit duration and the state
returns to IDLE; tx_busy is then cleared. The core uses only one stop bit and no parity. In
principle the stop bit could be extended or multiple stop bits added for compatibility, but this
implementation fixes it at one.

This TX implementation follows the standard UART framing: an idle-high line, one low start bit, LSB-first
data, and a high stop bit 1 3 . For example, [Link]’s UART tutorial describes exactly this 8‑N‑1
scheme 1 . The core correctly latches tx_data on request and shifts it out; it also ensures tx_busy
is high from the moment transmission starts until the stop bit is done. (By contrast, many textbook
designs add a separate “done” or strobe signal when the stop bit finishes 5 ; in this core the falling of
tx_busy signals completion.) One thing to watch is that the host must not change tx_data or
assert a new start until tx_busy returns low, or else the current frame could be corrupted. Overall,
the TX FSM appears logically correct for the 8‑N‑1 protocol.

Receiver (RX) Logic


• Synchronization: The asynchronous rx input is first passed through two flip-flops
( rx_sync1 , then rx_sync2 ) clocked by clk . This two-FF synchroniser is standard practice
to avoid metastability when sampling asynchronous lines 4 . The downstream FSM always sees
the debounced rx_sync2 signal as the input.
• Idle (RX_IDLE): The RX FSM watches for a low level on rx_sync2 (start bit candidate). When
rx_sync2 goes low, it presumes a start bit has begun. It then initializes a sample counter to
half a bit period (exactly baud_div/2 cycles) to wait till the middle of the start bit for
validation.
• Start-bit validation (RX_START): The counter counts down each clock. Once it reaches 0, the
code checks that rx_sync2 is still low. This rejects a brief glitch on the line. If it is still low at
mid-bit, the start bit is confirmed. The FSM then clears the shift buffer, resets the bit counter,
reloads the sample counter to one full bit ( baud_div-1 ), and moves to the data state. If the
line returned high instead, the machine goes back to IDLE.
• Data bits (RX_DATA): Now at each time sample_cnt reaches 0 (every bit period), the receiver
samples rx_sync2 and stores the bit into rx_shift[rx_bit_cnt] . It then reloads
sample_cnt = baud_div-1 and increments rx_bit_cnt . This captures each bit exactly at
the center of its period. After DATA_WIDTH bits, the FSM goes to RX_STOP.
• Stop bit (RX_STOP): The counter waits another full bit period. When sample_cnt hits 0, it
checks the stop bit. If rx_sync2 is high, the frame is valid: the shifted byte is moved to

2
rx_data and rx_valid is pulsed high for one cycle. If rx_sync2 is still low (framing error),
the byte is discarded and rx_valid remains 0. In either case the state goes back to IDLE.

The result is that rx_data and rx_valid are produced one cycle after the stop bit is sampled. Note
that rx_valid is a one-cycle strobe (it is cleared at every clock and only set to 1 in the final stop
condition). Thus the user must latch rx_data when rx_valid is high, otherwise it could be
overwritten by the next frame 6 . This matches the typical design: many UART implementations pulse
a “data ready” signal for exactly one bit-time 6 .

The RX timing implements a simple mid-bit sampling scheme. It samples each bit exactly once at its
centre, without oversampling. In more robust designs, it’s common to oversample the bit period (e.g. 8×
or 16×) and even take multiple samples per bit to reject noise 7 . For example, one reference notes
that sampling at 7/16, 8/16, and 9/16 of the bit (with a majority vote) improves noise immunity 7 . This
core assumes the clock and baud are very stable, since it takes only a single sample per bit. In practice
this means the transmit and receive clocks should be within a few percent of each other; without
multiple samples, any jitter or skew could cause bit errors (oversampling reduces that uncertainty 7 ).

Protocol Compliance and Best Practices


Overall, the design follows the UART 8‑N‑1 protocol: idle-high, LSB-first data, one low start bit, and one
high stop bit 1 3 . The use of a two-flop synchroniser on the RX input is good practice for
asynchronous signals 4 . The TX and RX state machines each wait a full bit period before moving on,
which ensures bits are sent and sampled at the baud rate with minimal timing error. The baud_div
input must be chosen so that the bit time (and half-bit time) are integer numbers of system clocks;
unusual baud_div (e.g. 0 or 1) could cause pathological behavior (e.g. a tick every cycle if
baud_div=1 ).

One notable difference from many UART IPs is that no parity bit or error flag is implemented. Only
framing (stop-bit) errors are silently dropped. Also, there is no parameter for multiple stop bits. If inter-
byte spacing is a concern, one could extend the stop bit or require the host to wait for tx_busy to
clear before sending the next byte. (Some UART implementations use two stop bits or insert an idle gap
to help resynchronise – an optional mode sometimes called “forgiving” mode 8 .)

Potential Issues and Edge Cases


• No oversampling: The receiver samples each bit exactly once at its center. If the clock and baud
are not perfectly aligned, sampling jitter could cause error. Without oversampling, the maximum
timing error is ±½ bit. By contrast, 8× or 16× oversampling (with majority voting) is often used in
UART RX designs for noise immunity 7 . In this core, any small timing offset translates directly
into sample error.
• Glitch/spike rejection: The RX_START state rejects a brief high spike by requiring the line to stay
low until mid-start-bit. However, any noise longer than half a bit could be misinterpreted as a
start or data bit. There is no additional filtering (e.g. requiring a full stable period).
• Baud rate changes: If baud_div were changed dynamically during operation, the timing
would become invalid. The design assumes baud_div is stable or changed only when idle.
• Stop-bit assumption: The RX logic assumes exactly one stop bit. If the transmitter sent two stop
bits, the second would just appear as idle. If the transmitter omitted the stop bit (framing error),
the receiver would drop the data (since rx_valid is only asserted on a correct stop).

3
• Input return to idle: If the RX line stays low after a frame (for example, if the transmitting device
fails), the core might immediately detect it as a new start bit. In normal RS‑232 idle the line is
high, so this is unlikely.
• Reset behavior: An asynchronous reset is used for both TX and RX. If rst_n is deasserted and
asserted mid-frame, the FSM will reset to idle. (In some designs a synchronous reset is used
instead to avoid metastability on rst_n .)
• Signal timing vs. tx_busy : While tx_busy is deasserted on the last stop bit, the tx line is
also high in TX_IDLE . The host should ensure tx_data and tx_start are valid before
asserting tx_start . Since edge detection only triggers on rising edges, holding tx_start
high through multiple idle cycles will not start multiple frames; the host must release and re-
assert for each frame.

Testbench Scenarios and Validation


To verify this core, one should simulate a variety of conditions. For example:

• Basic transmission: Send known bytes (e.g. 0x55, 0xAA, 0xFF, 0x00) with tx_start , and check
that tx output waveform shows correct 8N1 frames (the embedded frame image is a guide).
Confirm that tx_busy is high during the entire frame and low between frames.
• Back-to-back frames: Test that asserting tx_start for a second byte immediately after
tx_busy goes low correctly generates a new frame. In particular, try varying the gap between
frames (minimum gap or one idle bit) and verify the line levels.
• Long tx_start : Hold tx_start high for multiple clocks. Verify that only the first rising edge
caused a frame (extra pulses are ignored until start is released).
• RX reception: Drive the rx input with a simulated TX pattern at the same baud_div . Check
that rx_data matches and rx_valid pulses exactly once per frame, immediately after the
stop bit. For example, loop back tx to rx internally.
• Timing offset: Simulate slight clock skew by adjusting when bits arrive relative to the system
clock edges. Verify the receiver still samples correctly (within tolerance) but also test extreme
misalignment (half-bit off) to see failure modes.
• Glitches: Insert a narrow low pulse on rx that lasts less than half a bit. The RX logic should
ignore it (since the start bit must persist at least half a period). Try a glitch longer than half bit,
which should cause a false start.
• Frame error: Send a frame with an incorrect (low) stop bit. The core should not assert
rx_valid for that byte. Check that rx_data remains unchanged or is not captured.
• Reset scenarios: Trigger a reset in the middle of a frame. Both TX and RX should abort and
return to idle, and outputs should go to idle levels (TX=1, RX idle).
• Boundary conditions: For example, use the smallest baud_div = 2 (half clock per bit) or a
very large one, and confirm counters wrap properly.

Recommendations and Improvements


Based on this review, the core is largely correct but a few enhancements or fixes could be considered:

• Oversampling (RX): To improve noise robustness, consider oversampling the rx line (e.g. 4×,
8×, or 16× the baud) and taking multiple samples per bit 7 . This could allow a simple majority
vote or filter. Many UART IPs generate an oversample clock and then sample 3–5 times per bit.
• Parameterize stop bits: Add a parameter for 1 vs 2 stop bits, or allow configurable frame
length. The protocol permits one or more stop bits 2 ; making this configurable would increase
flexibility.

4
• Parity/error flags: If needed, implement optional parity generation/check and signal parity
errors. Also consider providing a framing-error output to indicate if a received byte had a bad
stop bit.
• Output strobes: Provide an explicit “done” or “valid” strobe for TX (to complement tx_busy )
just as rx_valid is for RX. A single-cycle tx_done pulse (as in many tutorials) lets the host
know exactly when the byte finished 5 .
• Documentation: Clarify in comments/user guide how to use tx_busy / rx_valid . For
example, document that rx_valid is one cycle long and rx_data is valid during that cycle
(so the host should latch it immediately). The TimRudy example notes “output data should be
taken away within one baud interval” 6 – a similar note here would help users.
• Reset strategy: If this core is part of a larger system, consider whether a synchronous reset or
reset synchronization is preferable (to avoid asynchronous resets causing metastability). If
rst_n is external, ensure it meets timing.
• Code clarity: The current FSM coding style is mostly clear, but some suggestions: use named
localparams for state codes (already done), ensure no latches are inferred (all outputs are
registered), and maybe separate the baud_tick generator into its own module for reuse. The
rx_valid logic clears on every cycle – one could also use a simple edge detection on
rx_state==RX_STOP to generate the strobe, but the current method is fine.
• Resource sharing: If size is a concern, note that both TX and RX have their own baud counters
and logic. In a unidirectional use case, one could disable unused side to save cycles. Or combine
logic if full-duplex is not needed.

In summary, the core correctly implements the essential UART protocol for 8‑N‑1 frames with proper bit
timing. Its TX and RX FSMs follow standard patterns, and it uses safe design practices like input
synchronization 4 . Key issues to test are asynchronous timing and noise sensitivity due to single-
sample reception. Adding oversampling or error detection would align the design with more robust
commercial UART implementations 7 5 . Careful testbench verification (as outlined above) should
confirm that corner cases (glitches, framing errors, back-to-back bytes) behave as intended. Good
documentation of signal timing and usage will also improve usability of this module in a system.

Sources: The above observations are based on the UART protocol specification 1 2 , known FPGA
design practices 4 7 , and examples of UART implementations 6 9 . Each citation corresponds to
these references.

1 Building a UART Transmitter in Verilog: Step by Step | by csjo logicion | Apr, 2026 | Medium
[Link]

2 UART: A Hardware Communication Protocol Understanding Universal Asynchronous Receiver/


3

Transmitter | Analog Devices


[Link]

4 Two-FF Synchronizer Explained


[Link]

5 8 9 uart-verilog/Uart8Transmitter.v at main · TimRudy/uart-verilog · GitHub


[Link]

6 uart-verilog/Uart8Receiver.v at main · TimRudy/uart-verilog · GitHub


[Link]

7 fpga - UART Receiver Sampling Rate - Electrical Engineering Stack Exchange


[Link]

You might also like