UART · Module 5
Majority Voting, Noise Rejection and the Sampling Window
Three samples around the centre reject a disturbance narrower than the gap between them and fail on one only slightly wider. The window also has width, which costs timing margin — and the same three samples span twice as much of the bit at 8x as at 16x.
Chapter 5.3 gave the receiver a grid of positions and a way to land near the centre of a bit cell. This chapter asks what it should do once it gets there.
The obvious answer is: read the line. One observation, one value, done — and for a clean conductor that is entirely sufficient.
The alternative is to read several positions around the centre and combine them. Many UART IPs do this, usually with three samples and a majority. It is worth understanding precisely, because it is the part of receiver design most surrounded by vague claims — "majority voting gives noise immunity" — and the precise version is much more useful:
Three samples reject a disturbance narrower than the gap between them, and fail on one only slightly wider. The window itself has width, and that width costs timing margin.
Both halves matter. A voter is not free, and at a low oversampling factor the price is not small.
1. One Sample, and What It Is Vulnerable To
A single reading at the interval's centre asks one question: what was the line at this instant?
Against a clean conductor that is a complete answer. The bit cell holds one value for its whole width, and the centre is the point furthest from both boundaries (Chapter 2.5), so the reading is correct with the maximum margin the geometry allows.
It is vulnerable to exactly one thing: a disturbance present at that instant. A coupled transient, a reflection, a brief excursion across the threshold — if it happens to coincide with the sampling instant, the reading is wrong, and the receiver has no way to know. There is no second opinion.
The probability of that coincidence is roughly the disturbance's width as a fraction of the bit interval. A 100 ns transient on a 115,200-baud link, where T_bit ≈ 8680.6 ns, lands on the sampling instant about 1.2% of the times it occurs. Rare, and not zero.
2. Three Samples and a Majority
The standard alternative reads three adjacent grid positions — the centre and one tick either side — and takes the value that appears at least twice.
positions: centre − 1, centre, centre + 1 (oversample ticks)
decision: the value present in at least two of the threeThe logic is a two-level expression with no state:
// Synthesizable SystemVerilog — the vote itself.
// Pure combinational; no clock, no reset. Synthesis produces three
// two-input ANDs and an OR, or whatever the technology's equivalent is.
function automatic logic majority3(input logic a, input logic b, input logic c);
return (a & b) | (a & c) | (b & c);
endfunctionEach product term covers one of the three ways two samples can agree, and the OR accepts any of them. The function is symmetric in its arguments, which is correct — the vote does not care which sample dissented.
a | b | c | vote |
|---|---|---|---|
| 1 | 1 | 1 | 1 |
| 1 | 0 | 1 | 1 — dissenting centre outvoted |
| 1 | 1 | 0 | 1 |
| 0 | 0 | 1 | 0 — two corrupted, vote flips |
| 0 | 1 | 0 | 0 |
| 0 | 0 | 0 | 0 |
The second row is the mechanism working. The fourth row is the mechanism failing, and §4 is about when that happens.
One corrupted sample — vote holds
10 cycles3. What It Rejects
Stated precisely, because the vague version is the problem:
A three-sample majority returns the correct value whenever at most one of the three samples is corrupted.
Since the samples sit one tick apart, a disturbance corrupts two of them only if it spans more than one oversample interval. That gives a concrete threshold:
M | one tick | disturbance must exceed, to corrupt two samples |
|---|---|---|
| 8 | 0.1250 UI | 1085.1 ns at 115,200 baud |
| 16 | 0.0625 UI | 542.5 ns at 115,200 baud |
So the mechanism's guarantee is: disturbances narrower than one oversample interval are rejected; wider ones may not be. That is a useful, checkable property, and it is considerably narrower than "noise immunity".
Note the direction of the dependence, which is counter-intuitive on first meeting. A higher oversampling factor makes the rejected class smaller — at 16× the samples are closer together, so a shorter disturbance suffices to corrupt two of them. Finer placement resolution and wider glitch rejection pull in opposite directions, and a design cannot maximise both with a fixed three-sample window.
4. What It Cannot Reject
Three failure classes, and only the first is obvious.
A disturbance spanning two sample positions. The fourth row of the truth table. At 16× a transient of 600 ns — only marginally wider than the 542.5 ns threshold — covers two adjacent samples and the vote returns the wrong value confidently. There is no indication that the decision was marginal.
Two corrupted samples — vote flips
10 cyclesSamples that are correlated rather than independent. The vote's logic assumes three separate observations of the same underlying value. If the window has drifted so that it straddles a bit boundary, the outer samples are reading a different interval — not a corrupted version of this one. The vote then faithfully reports the majority of two different bits, which is meaningless. Chapter 5.5 shows exactly when drift produces this.
Systematic misplacement. If the window is centred on the wrong position, every sample in it is wrong in the same direction, and the vote is unanimous. This is the most dangerous failure because the mechanism's own confidence is highest precisely when it is most wrong.
5. The Window Has Width, and Width Costs Margin
This is the half of the trade that folklore omits entirely.
A single sample sits at the centre, 0.5 UI from either boundary. A three-sample window places its outer samples one tick either side, so the outer samples are closer to the boundaries than the centre is:
M | outer sample offset | outer samples at | margin of the outer sample | δ_window |
|---|---|---|---|---|
| 8 | 1/8 = 0.1250 UI | 0.3750 and 0.6250 UI | 0.3750 UI | 0.1250 UI |
| 16 | 1/16 = 0.0625 UI | 0.4375 and 0.5625 UI | 0.4375 UI | 0.0625 UI |
| 32 | 1/32 = 0.03125 UI | 0.4688 and 0.5313 UI | 0.4688 UI | 0.03125 UI |
The window's span from first to last sample is 2/M UI — 1/8 UI at 16× and 1/4 UI at 8×. The same three samples occupy twice as much of the bit interval at 8× as at 16×.
That difference is the term Chapter 5.3 named δ_window, and it goes straight into Chapter 4.5's budget:
δ_sample = δ_grid + δ_window + δ_arch
at M = 16, three-sample: 0.0625 + 0.0625 = 0.1250 UI
at M = 8, three-sample: 0.1250 + 0.1250 = 0.2500 UIAt 8× a three-sample voter consumes half the available half-bit before any drift is considered. Put the mismatch example of Chapter 5.5 alongside it — ε_rel = 1% over an 8N1 frame, giving 0.09 UI of drift at the stop sample:
δ_window | drift at n=9 | remaining of 0.5 UI | |
|---|---|---|---|
M = 16, three-sample | 0.0625 | 0.0900 | 0.3475 |
M = 8, three-sample | 0.1250 | 0.0900 | 0.2850 |
Both work comfortably here. But the 8× voter has given up 0.0625 UI of margin relative to the 16× one, purchased narrower glitch rejection (§3) — because its wider window needs a wider disturbance to corrupt two samples, but its outer samples sit closer to the boundaries.
So the three-sample window is not a strictly better choice than a single sample. It trades margin for rejection of one specific disturbance class, and the exchange rate depends on M.
6. Implementing the Window
The samples must be captured at three separate ticks and the decision taken once.
// Synthesizable SystemVerilog — three-sample window and vote.
// The phase counter and os_tick come from Chapter 5.3; the shift register
// that consumes bit_value_o is Chapter 6.3. This produces one decided bit
// per interval and nothing else.
module uart_sample_vote #(
parameter int unsigned OVERSAMPLE = 16
) (
input logic clk,
input logic rst_n,
input logic rx_sync_i,
input logic os_tick_i,
input logic [$clog2(OVERSAMPLE)-1:0] phase_i,
input logic active_i,
output logic bit_value_o,
output logic bit_valid_o // one cycle, when the vote is decided
);
localparam int unsigned CENTER = OVERSAMPLE / 2;
initial begin
// The window needs a tick either side of centre, so the centre
// cannot be at position 0 or at the last position.
if (OVERSAMPLE < 4)
$fatal(1, "uart_sample_vote: OVERSAMPLE = %0d cannot hold a three-sample window around the centre", OVERSAMPLE);
end
logic s_first_q, s_mid_q;
function automatic logic majority3(input logic a, input logic b, input logic c);
return (a & b) | (a & c) | (b & c);
endfunction
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
s_first_q <= 1'b1; // idle level, per Chapter 5.1
s_mid_q <= 1'b1;
bit_value_o <= 1'b1;
bit_valid_o <= 1'b0;
end else begin
bit_valid_o <= 1'b0;
if (active_i && os_tick_i) begin
unique case (phase_i)
($clog2(OVERSAMPLE))'(CENTER - 2): s_first_q <= rx_sync_i;
($clog2(OVERSAMPLE))'(CENTER - 1): s_mid_q <= rx_sync_i;
($clog2(OVERSAMPLE))'(CENTER): begin
// Third sample is read combinationally on this tick
// and voted immediately — no fourth storage element.
bit_value_o <= majority3(s_first_q, s_mid_q, rx_sync_i);
bit_valid_o <= 1'b1;
end
default: ; // every other phase: hold
endcase
end
end
end
endmoduleThe phase constants are derived, never literals, for the reason Chapter 5.3 §6 gave: a hard-coded 7 is correct at one factor and silently wrong at every other.
Why the comparisons are CENTER-2, CENTER-1, CENTER. Chapter 5.3 established that an event fires on the tick carrying the count to a position. Three consecutive captures therefore happen on the ticks reaching CENTER-1, CENTER and CENTER+1 — so the comparisons are one lower, and the window is centred on the position the single-sample receiver would have used. An off-by-one here shifts the whole window and costs margin asymmetrically, which §5 showed is the expensive direction.
OVERSAMPLE < 4 is fatal. At M = 2 the centre is position 1 and there is no position either side; at M = 3 the truncated centre is 1 and CENTER-2 is negative. The window architecture simply does not exist below 4, and elaborating a design that pretends otherwise produces samples at wrapped phases.
Two storage elements, not three. The third sample is voted on the cycle it is read. Registering it would add a cycle of latency and a flip-flop for nothing.
Reset values are the idle level, so a receiver leaving reset does not carry a fabricated space into its first vote.
7. Verification
The rejection threshold is the property to characterise, and it should be found empirically rather than assumed:
| Disturbance width | Expected |
|---|---|
| narrower than one oversample interval | rejected — at any phase |
| exactly one interval | boundary — depends on phase relative to the tick grid |
| wider than one interval | may corrupt two samples and flip the vote |
| wider than the whole window | corrupts all three; vote unanimous and wrong |
The third and fourth rows are specified behaviour, not bugs, and a testbench should assert them as expected. Asserting that a voter rejects everything is asserting a property the architecture does not have, and the usual response — widening the window — costs margin per §5.
Phase is an axis as much as width. A disturbance of fixed width lands on different numbers of samples depending on where it falls relative to the tick grid, so sweeping width at one phase finds one boundary while sweeping both finds the region.
Test the unanimous-and-wrong case explicitly. Deliberately misplace the window — by injecting a start-qualification offset, or by configuring a factor the phase constants were not derived for — and confirm the receiver produces clean, consistent, wrong data. This sounds perverse and is valuable: it is the signature §4 warned about, and a team that has seen it in simulation recognises it in the lab.
Assertions worth having, both stating contracts rather than restating the implementation:
// Exactly one vote decision per bit interval while receiving.
property p_one_vote_per_interval;
@(posedge clk) disable iff (!rst_n)
bit_valid_o |=> !bit_valid_o until_with (os_tick_i && phase_i == CENTER - 2);
endproperty
assert property (p_one_vote_per_interval);
// No decision while the receiver is not in a frame.
property p_no_vote_when_idle;
@(posedge clk) disable iff (!rst_n)
!active_i |-> !bit_valid_o;
endproperty
assert property (p_no_vote_when_idle);And a scoreboard should check the vote against the samples, not just the byte. A byte-level comparison cannot distinguish a correct vote from a lucky one. Recording the three sample values and the decided bit lets the checker verify the majority function independently of what the data happened to be.
8. What This Means on an FPGA
The logic is trivial and the margin is not. The vote is a handful of LUTs and two flip-flops. The δ_window of §5 is a real subtraction from the timing budget, and at 8× it is as large as the grid term itself. Compute the budget before adding a voter, not after.
Consider whether the disturbance class is real for your board. Voting rejects transients narrower than one oversample interval — 542.5 ns at 16× and 115,200 baud. If the board's actual noise environment produces disturbances much wider than that, the voter costs margin and rejects nothing that matters; if it produces much narrower ones, a single sample was already unlikely to coincide with them.
At high rates the numbers move. At 1 Mbaud the 16× oversample interval is about 62.5 ns, so the rejected class shrinks accordingly — while board-level transients do not. The mechanism is most effective at the low rates it grew up with.
Bring the vote out during bring-up if you can. Capturing the three samples alongside the decided bit turns §4's failure classes into observations: one dissent means the mechanism worked, two means it was defeated, and unanimous-but-wrong means the window is misplaced.
9. Understanding Check
10. Summary
A single sample at the centre is a complete answer on a clean conductor, vulnerable only to a disturbance coinciding with that instant — roughly the disturbance's width as a fraction of the bit interval.
A three-sample majority reads the centre and one tick either side and takes the value appearing at least twice. Its guarantee is exact: correct whenever at most one sample is corrupted, which means it rejects disturbances narrower than one oversample interval — 542.5 ns at 16× and 115,200 baud. A disturbance only slightly wider corrupts two samples and flips the vote silently.
A higher oversampling factor shrinks the rejected class, because the samples move closer together. Placement resolution and glitch rejection pull in opposite directions.
Three failures are outside the mechanism: a disturbance spanning two samples; samples that are correlated rather than independent, which happens when drift makes the window straddle a boundary; and systematic misplacement, where all three samples agree and the vote is unanimously wrong. The last is the most dangerous, because voting is most confident exactly when it is most wrong — and it removes the variable-error clue a single misplaced sample would have given.
The window has width and width costs margin. Outer samples sit 1/M UI from the centre, so δ_window is 0.0625 UI at 16× and 0.125 UI at 8× — where, combined with the grid term, a three-sample voter consumes half the available half-bit before drift is counted.
So voting is a trade, not an improvement: margin for rejection of one specific disturbance class, at an exchange rate set by M. If a design's binding constraint is timing rather than noise, a voter does not help.
11. What Comes Next
Every mechanism in this module has been developed against a static picture: the sampling positions sit where the receiver placed them and stay there. They do not.
Chapter 5.5 puts the receiver architecture and Module 4's clock mismatch together and walks the sampling point across a frame. It shows the positions migrating tick by tick, identifies exactly which bit fails first and why it is always the last, and — using this chapter's window arithmetic — shows the outer sample of a voting window crossing a boundary before the centre does. It is where Modules 2, 4 and 5 finally meet.
Browse the full path on the UART tutorials index. For the error-detection mechanism that operates on the decided bits rather than the samples, and its own sharp limits, see Chapter 3.3.
Continue learning
Related tutorials
- Related topic
Sampling Centres and the Timing Margin Budget
Half a bit period separates an interval's centre from its boundary. That half-bit is a budget spent by origin uncertainty, interval construction, accumulated drift and the decision mechanism — and the last interval of a frame is where it runs out first.
- Related topic
Parity Generation, Checking and Error Detection
One interval, one XOR reduction, and a detection guarantee with a sharp edge: parity catches every corruption that flips an odd number of protected bits and provably misses every even-numbered one — demonstrated, not asserted.
- Related topic
Frame Configurations: 8N1 and the Configuration Space
8N1 names three of the four choices a UART link depends on and omits the one most likely to be wrong. Reading the shorthand, computing what each configuration costs in intervals and line time, and why a longer frame spends timing margin as well as throughput.
- Related topic
Integer Dividers and Baud-Rate Error
The ratio is a fraction and a counter holds an integer, so rounding is a design decision with a measurable cost. Three policies, the actual rate each produces, and why the error belongs in units of a bit period rather than as a bare percentage.
Where this fits
Part of the UART curriculum.
