Skip to content
VLSI Mentor

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
N_ideal = f_clk / f_baud

Three integers are natural candidates, and they are genuinely different choices:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
N_floor   = floor(N_ideal)     round down
N_ceil    = ceil(N_ideal)      round up
N_nearest = round(N_ideal)     round to nearest

Each produces a different actual rate, because the counter's period is the divisor:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
f_actual = f_clk / N_div

ε = (f_actual − f_baud) / f_baud

Worked at the pairing from Chapter 4.2, where N_ideal ≈ 868.0556:

PolicyN_divf_actualεppmActual T_bit
floor868115,207.3733+0.0064%+64.08.68000 µs
ceiling869115,074.7986−0.1087%−1,086.88.69000 µs
nearest868115,207.3733+0.0064%+64.08.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
floor1,6669,603.8415+0.0400% (+400.2 ppm)
ceiling / nearest1,6679,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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
f_actual > f_baud    ⟹    T_actual < T_bit

so a positive frequency error corresponds to a negative period error. Worked at N_div = 868:

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Δt(n) / T_bit  =  n x ε          in UI, unit intervals

So 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

A baud divider implemented as synchronous logic. A counter is incremented by one on every system clock edge. A comparator tests the counter against the terminal value, which is the divisor minus one. When the comparison matches, the counter is cleared to zero and a single-cycle enable pulse is generated. The time between successive enable pulses is the divisor multiplied by the clock period, which is the generated bit interval. The enable is a pulse in the existing clock domain rather than a second clock.f_clkthe existing domaincounter0 .. N_div − 1== N_div − 1terminal comparebaud_tickone cycle, an enableclear to 0same cycle as the match+1 / cyclevaluematchmatchwrap12
Figure 1 — the divider as hardware. A counter advances every clock, a comparator detects the terminal value, and the match both clears the counter and emits a single-cycle enable. The bit interval is the time between enables, which is exactly N_div clock periods — provided the counter spans 0 to N_div − 1 and not 0 to N_div.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endmodule

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — counts 0 .. N_DIV inclusive, which is N_DIV + 1 cycles.
end else if (count_q == CNT_W'(N_DIV)) begin

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

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

The +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:

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

Where this fits

Part of the UART curriculum.