UART · Module 4
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.
Chapter 4.2 established that f_clk / f_baud is almost never a whole number — at 100 MHz and 115,200 it is exactly 15,625 / 18, or about 868.0556 — and that this is the ordinary condition rather than a defect.
A counter holds an integer. So the design must choose one, and the choice is an engineering decision with a measurable cost, not a line of syntax. This chapter derives the options, quantifies what each produces, and then insists on expressing the result in the unit that actually matters — because a rate error quoted as a percentage is not directly comparable with anything else in the timing budget.
1. Three Policies, Not One Formula
The ideal divisor is a rational number:
N_ideal = f_clk / f_baudThree integers are natural candidates, and they are genuinely different choices:
N_floor = floor(N_ideal) round down
N_ceil = ceil(N_ideal) round up
N_nearest = round(N_ideal) round to nearestEach produces a different actual rate, because the counter's period is the divisor:
f_actual = f_clk / N_div
ε = (f_actual − f_baud) / f_baudWorked at the pairing from Chapter 4.2, where N_ideal ≈ 868.0556:
| Policy | N_div | f_actual | ε | ppm | Actual T_bit |
|---|---|---|---|---|---|
| floor | 868 | 115,207.3733 | +0.0064% | +64.0 | 8.68000 µs |
| ceiling | 869 | 115,074.7986 | −0.1087% | −1,086.8 | 8.69000 µs |
| nearest | 868 | 115,207.3733 | +0.0064% | +64.0 | 8.68000 µs |
(Rates rounded to four decimal places; ε to four significant figures.)
Here floor and nearest coincide because the fractional part is 0.0556 — well below a half — and ceiling is seventeen times worse. That is not a general result. At a different pairing the ordering changes completely:
f_clk = 16 MHz, f_baud = 9,600, N_ideal ≈ 1666.6667 | |||
|---|---|---|---|
| floor | 1,666 | 9,603.8415 | +0.0400% (+400.2 ppm) |
| ceiling / nearest | 1,667 | 9,598.0804 | −0.0200% (−200.0 ppm) |
Now the fractional part is 0.6667, above a half, so nearest rounds up and floor is twice as bad as the alternative.
2. Frequency Error and Period Error Have Opposite Signs
This is a genuine source of confusion and it is worth being exact about.
The design generates a rate f_actual and therefore a bit period T_actual = N_div / f_clk. If the rate is high the period is short:
f_actual > f_baud ⟹ T_actual < T_bitso a positive frequency error corresponds to a negative period error. Worked at N_div = 868:
f_actual = 100,000,000 / 868 = 115,207.3733 baud
ε_f = (115,207.3733 − 115,200) / 115,200 = +0.006400%
T_actual = 868 / 100,000,000 = 8.680000 µs
T_bit = 1 / 115,200 ≈ 8.680556 µs
ε_T = (8.680000 − 8.680556) / 8.680556 = −0.006400%Same magnitude to four figures, opposite sign. They are not exactly equal in magnitude — the exact ratio is |ε_f| / |ε_T| = 1.000064 here — but for errors of this size the difference is irrelevant and treating them as equal in magnitude is safe.
What is not safe is getting the sign wrong. A design running fast produces short bit intervals, so a receiver predicting nominal-length intervals falls progressively behind the transmitter. Reverse the sign and the predicted drift direction reverses with it, which matters the moment Chapter 4.5 combines two endpoints' errors.
3. Express the Error in Bit Periods
A rate error quoted as a percentage is hard to reason about because it is not comparable with anything else in the budget. Chapter 2.4 established the unit that is:
Δt(n) / T_bit = n x ε in UI, unit intervalsSo the useful question is not "what is the rate error" but "how much of a bit period does it consume by the end of a frame" — which uses N_frame from Chapter 3.5 and is evaluated at the last interval, n = N_frame − 1, per Chapter 2.5.
N_div | ε | 8N1 (n=9) | 8E1 (n=10) | 8E2 (n=11) |
|---|---|---|---|---|
| 868 (floor/nearest) | +0.0064% | +0.000576 UI | +0.000640 UI | +0.000704 UI |
| 869 (ceiling) | −0.1087% | −0.009781 UI | −0.010868 UI | −0.011955 UI |
Now the numbers say something actionable. With N_div = 868 the divider consumes about 0.06% of a bit period by the end of an 8N1 frame — against the half-bit of available margin, that is one part in a thousand and effectively free. Even the poor ceiling choice consumes under 1% of a bit.
That is the honest conclusion for this pairing: the integer divider's error is negligible. It is worth stating plainly, because a chapter that quantifies an error often leaves the impression that the error is a problem. Here it is not — and knowing which budget terms are small is as valuable as knowing which are large. Chapter 4.5 shows which term actually dominates.
Where it stops being negligible is when the ratio gets small. At 1 Mbaud from a 100 MHz clock the divisor is 100 exactly, so the error is zero — but at a rate the clock divides badly with a divisor in the tens, the fractional part becomes a large share of the divisor and the error grows accordingly. The general shape is that error scales roughly with 1 / N_div, so low divisors are where this chapter's arithmetic starts to matter.
4. The Counter That Implements It
// Synthesizable SystemVerilog — the integer divider, complete.
// This is the arithmetic of this chapter made concrete. The PRODUCTION
// generator — runtime reconfiguration, separate RX/TX timing, error
// reporting — is Module 8; what this module omits is listed there.
module uart_baud_tick #(
parameter int unsigned CLK_HZ = 100_000_000,
parameter int unsigned BAUD_HZ = 115_200,
// Rounding policy, made explicit rather than inherited from the
// behaviour of the "/" operator. 0 = floor, 1 = nearest (§1).
parameter bit ROUND_NEAREST = 1'b1
) (
input logic clk,
input logic rst_n,
output logic baud_tick_o // one clk cycle high, once per bit interval
);
// ── Elaboration-time divisor selection ────────────────────────────
// Nearest via integer arithmetic: (2a + b) / 2b is round-half-up of a/b.
localparam int unsigned N_DIV = ROUND_NEAREST
? ((2 * CLK_HZ + BAUD_HZ) / (2 * BAUD_HZ))
: (CLK_HZ / BAUD_HZ);
// Counter spans 0 .. N_DIV-1, so its largest value is N_DIV-1 and it
// needs $clog2(N_DIV) bits. The guard covers N_DIV == 1, where
// $clog2(1) is 0 and a zero-width vector is illegal.
localparam int unsigned CNT_W = (N_DIV <= 1) ? 1 : $clog2(N_DIV);
initial begin
if (BAUD_HZ == 0 || CLK_HZ == 0)
$fatal(1, "uart_baud_tick: CLK_HZ and BAUD_HZ must be non-zero");
if (N_DIV < 2)
$fatal(1, "uart_baud_tick: N_DIV = %0d. A divisor below 2 leaves no room to count; f_baud is too close to f_clk.", N_DIV);
end
logic [CNT_W-1:0] count_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
count_q <= '0;
baud_tick_o <= 1'b0;
end else if (count_q == CNT_W'(N_DIV - 1)) begin
count_q <= '0; // wrap: the NEXT interval starts here
baud_tick_o <= 1'b1;
end else begin
count_q <= count_q + 1'b1;
baud_tick_o <= 1'b0;
end
end
endmoduleWhat it implies in hardware
A counter register of CNT_W flip-flops, an incrementer, a comparator against a constant, and one more flip-flop for the pulse. At N_DIV = 868 that is 10 bits plus a pulse register — a handful of LUTs and 11 flip-flops. Synthesis will typically implement the comparison against a constant efficiently; the designer's intent is "detect the terminal count", not a particular gate structure.
The corner cases, each with a reason
N_DIV − 1, not N_DIV. The counter takes the values 0, 1, …, N_DIV−1 and then wraps, which is exactly N_DIV clock cycles per interval. Comparing against N_DIV instead gives N_DIV+1 cycles. §5 shows what that costs.
N_DIV < 2 is fatal, not merely odd. At N_DIV = 1 the terminal value is 0, so the counter matches every cycle and the tick asserts continuously — not a bit rate but a stuck enable. At N_DIV = 0 the comparison is against −1, which in an unsigned vector is all-ones, and the counter free-runs for its whole range before ever matching. Both produce a design that elaborates cleanly and does not work, so the check earns its place.
CNT_W is derived, and the N_DIV <= 1 guard is not decorative. $clog2(1) is 0, and a logic [-1:0] declaration is illegal. The guard makes the degenerate case produce a legal — if useless — module rather than an elaboration error whose message points at a vector declaration instead of at the parameters.
The width cast on the comparison constant. CNT_W'(N_DIV - 1) makes the comparison operand explicitly the counter's width. Without it the comparison is performed at the wider int width, which is harmless here but hides a genuine width mismatch if the parameters and the counter ever disagree.
Reset clears both, and the tick resets low. A tick asserting out of reset would advance whatever consumes it before any frame had begun.
5. The Off-by-One, and What It Costs
This is the most common defect in this piece of hardware, and it has a precise and slightly beautiful consequence.
// WRONG — counts 0 .. N_DIV inclusive, which is N_DIV + 1 cycles.
end else if (count_q == CNT_W'(N_DIV)) beginWith N_DIV = 868 the interval becomes 869 clock cycles. And 869 is exactly the ceiling divisor from §1, so the actual rate becomes 115,074.7986 baud — an error of −0.1087% instead of +0.0064%, seventeen times worse and in the opposite direction.
6. Verifying the Divider
The arithmetic is checkable in simulation without waiting for a frame, and the check should measure rather than trust.
// Testbench SystemVerilog — a self-checking interval monitor.
// Counts clk cycles between ticks and compares against the expected
// divisor. Not synthesizable; belongs in the testbench, not the design.
int unsigned cycles_since_tick;
int unsigned observed;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cycles_since_tick <= 0;
end else if (baud_tick_o) begin
observed <= cycles_since_tick + 1; // include this cycle
cycles_since_tick <= 0;
end else begin
cycles_since_tick <= cycles_since_tick + 1;
end
end
// Checked only after the first tick, so the initial partial interval
// — which is shorter by construction — does not raise a false failure.
bit seen_first_tick;
always_ff @(posedge clk) begin
if (baud_tick_o) begin
if (seen_first_tick && observed != EXPECTED_N_DIV) begin
$error("baud interval = %0d cycles, expected %0d", observed, EXPECTED_N_DIV);
end
seen_first_tick <= 1'b1;
end
endThe +1 is where the off-by-one would hide in the checker too. The cycle on which the tick asserts is part of the interval it terminates, so the observed count must include it. A checker that omits the +1 reports 867 for a correct 868-cycle divider and sends an engineer hunting a defect in working RTL.
Skipping the first interval is necessary, not a fudge. The counter starts at zero out of reset, so the first tick arrives after a full interval only if reset released on an interval boundary — which in general it did not. Checking from the second tick onward removes an artefact without weakening the check.
EXPECTED_N_DIV must be recomputed in the testbench from CLK_HZ and BAUD_HZ, not imported from the design. A checker that reads the design's own N_DIV verifies that the counter matches the constant, which is nearly tautological. Recomputing it independently — including the rounding policy — verifies that the constant itself is right, which is the defect §5 describes.
A concurrent assertion is a reasonable alternative for the simpler invariant that ticks are never adjacent:
// Assertion — no two ticks on consecutive cycles, for any N_DIV >= 2.
property p_no_adjacent_ticks;
@(posedge clk) disable iff (!rst_n)
baud_tick_o |=> !baud_tick_o;
endproperty
assert property (p_no_adjacent_ticks);This one is worth writing because it is simple and always true: with N_DIV >= 2 — guaranteed by the elaboration check — a tick is always followed by at least one quiet cycle. Attempting a single assertion that also pins down the exact spacing requires parameterised repetition that is easy to get subtly wrong, and the counting monitor above does that job more clearly. Correctness over compactness: an assertion nobody can verify by inspection is not an asset.
7. What This Means for Verification and on an FPGA
Every supported rate is a separate divisor with a separate error. A design offering several rates from one clock has a different N_div at each, and the error varies non-monotonically across them — 0.0064% at one rate, 0.04% at another, zero at a third. The rates worth testing are the extremes of that set, and the elaboration report of Chapter 4.2 identifies them at build time.
Boundary divisors deserve their own cases. N_DIV = 2 is the smallest legal value and exercises the comparison at its edge. A divisor at a power-of-two boundary — 1,024 against 1,025 — exercises the $clog2 width derivation, where an off-by-one in the width truncates the counter and produces an interval that is wrong by a factor of two rather than by one cycle.
On an FPGA, the divider is small and its error is usually not the binding term. §3's conclusion holds: at typical fabric clocks and standard rates the divider contributes a fraction of a percent of a bit period per frame. The terms that dominate come from elsewhere, and Chapter 4.5 assembles them. The practical advice is to choose nearest rounding, check the elaboration report, and then stop worrying about this term unless the divisor is small.
Measure across many intervals. §5's technique is the single most useful lab skill in this chapter: f_baud = N / Δt over N intervals, with precision improving as 1/N.
8. Understanding Check
9. Summary
The ideal divisor is a rational number and a counter holds an integer, so rounding is a policy. Floor, ceiling and nearest give different actual rates: at 100 MHz and 115,200 floor and nearest both give 868 for +0.0064%, while ceiling gives 869 for −0.1087%; at 16 MHz and 9,600 the ordering reverses and floor is twice as bad as nearest.
CLK_HZ / BAUD_HZ selects floor silently, because integer division truncates. Nearest is the better default and is one addition at elaboration.
Frequency error and period error have opposite signs — running fast means short intervals — with magnitudes equal to four figures at this scale. The sign matters because it fixes the direction of drift.
The error belongs in units of a bit period, because that is the unit the budget is measured in: n x ε UI, evaluated at the frame's last interval. At 868 that is +0.000576 UI over an 8N1 frame — about one part in a thousand of the available half-bit, and honestly negligible. Knowing which terms are small matters as much as knowing which are large.
The hardware is a counter spanning 0 to N_div − 1, a terminal comparison, and a pulse register — producing an enable in the existing clock domain, not a second clock. N_div < 2 must be rejected at elaboration, and $clog2 needs a guard at N_div = 1.
Comparing against N_div instead of N_div − 1 adds exactly one cycle per interval, which at 868 lands on the ceiling divisor and worsens the error seventeenfold — and survives loopback testing perfectly, because both ends are wrong identically.
10. What Comes Next
The fractional part was discarded, and §3 showed that at ordinary rates the cost is negligible. But it was discarded permanently: every interval is the same integer length, so the small error is systematic and accumulates in one direction forever.
Chapter 4.4 asks what happens if it is not discarded — if the design tracks the remainder and occasionally lengthens an interval to compensate, so that the average converges on the ideal. That buys accuracy at the cost of making individual intervals unequal, which is a trade worth understanding precisely before Module 8 builds the hardware.
Browse the full path on the UART tutorials index. For terminal-count counters and enable generation treated as a general RTL pattern, see Clock Dividers and Timers.
Continue learning
Related tutorials
- Related topic
The UART Timing Budget: How Much Mismatch a Frame Survives
The familiar five percent is the exact output of one idealised model with five stated assumptions, all false in hardware. Putting the terms back shows the usable budget moving by a quarter of its value under ordinary implementation choices — which is why the number belongs to a design, not to UART.
- Related topic
Oversampling Strategies: 8×, 16× and the Alternatives
Oversampling is a grid of sample positions inside each bit interval. A higher factor buys placement resolution and costs tick rate, counter width and generator accuracy — and 16× became conventional for an arithmetic reason the standard crystal frequencies make obvious.
- Related topic
Clock Enable vs Generated Clock
Both architectures produce one event per bit interval and both simulate correctly. One adds a clock domain, a constraint and a CDC review to a design that needed none of them.
- Related topic
What a UART Actually Is
Two digital systems need to exchange a small amount of data over very few wires, and no clock travels with it. A UART is the logic that answers that problem — it converts between locally meaningful parallel data and timed activity on a single line, and the timing agreement it depends on is what the rest of the curriculum builds.
Where this fits
Part of the UART curriculum.
