Skip to content
VLSI Mentor

UART · Module 8

Fractional Accumulator Baud Generation

A phase accumulator keeps the remainder an integer divider discards, so the average rate converges while individual intervals alternate between two lengths — and a too-narrow accumulator is worse than the divider it replaced.

Chapter 8.2 built a divider that produces f_clk / N for integer N, and nothing else. At 100 MHz and 115,200 baud the ideal divisor is 868.0556, so the hardware runs at 868 and the rate is 0.0064% fast — 64 ppm. Small, and systematic: every interval is the same wrong length, so the error never cancels.

Chapter 4.4 asked what happens if the remainder is kept rather than discarded, worked the arithmetic, and published a conceptual block explicitly deferring to this chapter:

The production generator is Chapter 8.3: reset and start-up behaviour, runtime reconfiguration, whether RX and TX share an accumulator, and how the residual error is reported all belong there.

So this chapter builds the hardware. It also does something Chapter 4.4 could not, because it had no accumulator width to speak of: it asks how wide the accumulator must be, and finds a case where a fractional generator is worse than the integer divider it replaced.

1. The Model

An integer divider counts to a whole number of cycles. A phase accumulator instead tracks how far through the current interval it is, as a fraction, and lets that fraction be as precise as its register width allows.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
every fabric clock:    phase  <-  phase + INC
when phase overflows:  emit a tick, and keep the remainder

The overflow is not a side effect to be handled — the carry out of the accumulator is the timing event. That is the whole mechanism, and it is why the block has no comparator.

Think of phase as a position on a circle of circumference 2^ACC_W. Each clock advances it by INC. Every time it passes the origin, one bit interval has elapsed. Because the remainder is carried forward rather than thrown away, the position is never rounded — only the moment of crossing is quantised to a clock edge.

2. The Increment Equation

The accumulator wraps once per 2^ACC_W / INC clocks, so the tick rate is:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
                 INC
f_tick  =  f_clk × ─────────           [Hz] = [Hz] × [dimensionless]
                   2^ACC_W

Setting f_tick to the target baud and solving:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
                 BAUD_HZ × 2^ACC_W
INC  =  round( ───────────────────── )      [dimensionless]
                      CLK_HZ

The rounding is the entire quantisation. An integer divider quantises the interval length to a whole number of cycles; the accumulator quantises the increment instead — and since INC is a number in the tens of thousands rather than a number like 868, the relative error of rounding it is far smaller. That is the whole reason the architecture exists.

At 100 MHz, 115,200 baud, ACC_W = 24:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
INC = round(115,200 × 16,777,216 / 100,000,000) = round(19,327.3528) = 19,327

f_tick = 100,000,000 × 19,327 / 16,777,216 = 115,197.8970 baud     → −0.00183 %

against the integer divider's +0.0064%. A 3.5× improvement, and in the opposite direction.

3. The Width Question — and a Result Worth Knowing

ACC_W is the only real design choice, and the temptation is to pick a round number. Machine-computed at 100 MHz and 115,200 baud:

ACC_Wideal INCINC usedactual bauderrorppm
1675.497575114,440.9180−0.65893 %−6,589
201,207.95961,208115,203.8574+0.00335 %+33.5
2419,327.352819,327115,197.8970−0.00183 %−18.3
324,947,802.32504,947,802115,199.9924−0.00001 %−0.1

Compare the integer divider from Chapter 8.2: +0.0064%, or +64 ppm.

The same table at other clock/rate pairs, showing that the crossover is not a constant:

f_clkbaudACC_WINCactual bauderrorppm
50 MHz115,20016151115,203.8574+0.00335 %+33.5
50 MHz115,2002438,655115,200.8772+0.00076 %+7.6
100 MHz1,000,00016655999,450.6836−0.05493 %−549.3
100 MHz1,000,00024167,772999,999.0463−0.00010 %−1.0

At 50 MHz a 16-bit accumulator is already fineINC = 151 for the same rate, because the ratio BAUD/CLK doubled. The width needed is a property of the pair, not of the baud rate.

4. What the Intervals Actually Look Like

The average rate converges. Individual intervals do not — and the precise statement matters.

An accumulator with increment INC wraps every 2^ACC_W / INC clocks on average, but a clock edge is an integer, so each interval must be one of the two integers bracketing that value:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
interval ∈ { floor(2^ACC_W / INC),  ceil(2^ACC_W / INC) }

Note what that expression is not. It is not the floor and ceiling of the ideal interval CLK_HZ / BAUD_HZ. The difference between the two is exactly the residual rate error of §3, and at a narrow width they are different numbers entirely. Simulation over three million fabric clocks:

ACC_WINCideal interval2^W / INCobserved intervalsdistribution
2419,327868.0556868.0714868, 86992.83 % / 7.17 %
201,208868.0556868.0265868, 86997.33 % / 2.67 %
1675868.0556873.8133873, 87418.67 % / 81.33 %

The 16-bit row is the §3 warning made physical: the intervals are not 868 and 869 at all, they are 873 and 874. The generator is not producing a slightly imperfect 115,200 baud — it is producing a clean 114,441 baud.

5. Frequency Error and Phase Error Are Different

Worth separating explicitly, because the fractional generator is the design where they diverge.

Frequency error is rate mismatch: the generator's long-term average tick rate against the target. At ACC_W = 24 it is −18.3 ppm, and it is what accumulates across a frame in Chapter 5.5's model.

Phase error is the displacement of an individual boundary from where a perfect continuous-time generator would have put it. For the accumulator it is bounded by one fabric clock — the interval is either the floor or the ceiling, never further — and it does not accumulate, because the remainder is carried.

An integer divider has the opposite profile: zero phase variation between intervals, and a frequency error that is therefore expressed entirely as a steadily growing displacement.

Integer dividerFractional accumulator
Every intervalidentical, exactly N clocksone of two values
Phase error within a framegrows linearlybounded by one clock
Long-term frequency errorfixed by Nfar smaller, set by ACC_W
Which one a UART frame cares aboutfrequencyChapter 4.4 §5

Chapter 4.4 §5 already settled which matters for a UART: the receiver samples at interval centres and a one-clock displacement of a boundary is a rounding error against a bit period of 868 clocks, while a frequency error accumulates across all ten intervals of a frame. The fractional generator improves the term that matters and slightly worsens the term that does not, which is why it is worth its adder.

6. The RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------------
//  8.3 — fractional (NCO / phase-accumulator) generator.
//    f_tick = f_clk x INC / 2^ACC_W
// ---------------------------------------------------------------------------
module uart_baud_tick_frac #(
    parameter int unsigned CLK_HZ  = 100_000_000,
    parameter int unsigned BAUD_HZ = 115_200,
    parameter int unsigned ACC_W   = 24
) (
    input  logic clk,
    input  logic rst_n,
    output logic baud_tick_o
);
    // INC = round(BAUD_HZ x 2^ACC_W / CLK_HZ), in integer arithmetic.
    // The 64-bit intermediate matters: BAUD_HZ << ACC_W overflows 32 bits for
    // any ordinary rate at ACC_W >= 16.
    localparam longint unsigned SCALED = (longint'(BAUD_HZ) <<< ACC_W);
    localparam int unsigned INC =
        int'((SCALED + (longint'(CLK_HZ) / 2)) / longint'(CLK_HZ));

    initial begin
        if (CLK_HZ == 0 || BAUD_HZ == 0)
            $fatal(1, "uart_baud_tick_frac: CLK_HZ and BAUD_HZ must be non-zero");
        if (ACC_W < 2 || ACC_W > 32)
            $fatal(1, "uart_baud_tick_frac: ACC_W = %0d outside 2..32", ACC_W);
        if (INC == 0)
            $fatal(1, "uart_baud_tick_frac: increment rounds to 0 — ACC_W too narrow");
        if (INC >= (1 <<< ACC_W))
            $fatal(1, "uart_baud_tick_frac: increment %0d >= 2^ACC_W — BAUD_HZ too close to CLK_HZ", INC);
    end

    logic [ACC_W-1:0] phase_q;
    // ONE bit wider than the accumulator, so the carry out of the top bit is
    // captured rather than silently discarded. This is the defect §37 warns of.
    logic [ACC_W:0]   sum;

    assign sum = {1'b0, phase_q} + (ACC_W+1)'(INC);

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            phase_q     <= '0;
            baud_tick_o <= 1'b0;
        end else begin
            phase_q     <= sum[ACC_W-1:0];   // wrap = subtract 2^ACC_W
            baud_tick_o <= sum[ACC_W];       // the carry IS the timing event
        end
    end
endmodule
A fractional baud generator built as a phase accumulator. A phase register holds the current position within the bit interval. Each fabric clock its value is added to a constant increment derived at elaboration from the target baud rate, the clock frequency and the accumulator width. The addition is performed at one bit wider than the accumulator so that the carry out of the top bit is preserved rather than discarded. The bits below that carry are written back into the phase register, which is arithmetically the same as subtracting the accumulator modulus and is what preserves the fractional remainder between intervals. The carry bit itself is registered and becomes the one-cycle baud tick output. No comparator appears anywhere in the structure.phase_qACC_W bitsadderACC_W+1 bitssumthe extra bitbaud_tick_oregistered carryINClocalparamclkevery cyclesum[ACC_W-1:0]remainder keptback to phase= subtract 2^ACC_Wphaseconstantsum[ACC_W]next phase12
Figure 1 — the accumulator's structure. There is no comparator anywhere: the phase register feeds an adder one bit wider than itself, the carry out of that adder is the timing event, and the bits below the carry become the next phase. That extra bit is the whole design — without it the carry has nowhere to go and the generator emits nothing.

The width hazard that silently discards the carry

The single most important line is the declaration of sum:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
logic [ACC_W:0]   sum;                              // ACC_W+1 bits
assign sum = {1'b0, phase_q} + (ACC_W+1)'(INC);

One bit wider than the accumulator, and the operand explicitly extended to that width. Written the obvious way instead:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the carry has nowhere to go.
logic [ACC_W-1:0] sum;
assign sum = phase_q + INC;

SystemVerilog sizes the addition to the width of the widest operand in the expression context, which here is the ACC_W-bit assignment target. The addition therefore happens in ACC_W bits, the overflow is discarded, and sum[ACC_W] does not exist to be read.

The failure mode is total and silent. The accumulator still wraps correctly — losing the carry is arithmetically the same as subtracting 2^ACC_W, which is exactly what the wrap should do — so phase_q behaves perfectly. What vanishes is the tick. The generator produces a correct phase sequence and no output at all, and the RTL looks right.

This is a general SystemVerilog hazard rather than a UART one, and it is worth carrying: when a carry is the thing you want, the sum must be declared wide enough to hold it. Relying on the expression to widen itself works only when some operand is already wide enough, and a same-width accumulator and increment never are.

The longint in the INC computation is the same hazard at elaboration. BAUD_HZ << ACC_W for 115,200 and ACC_W = 24 is about 1.93 × 10¹², which overflows a 32-bit integer. Computing it in longint and narrowing afterwards is what makes ACC_W = 24 and 32 produce correct increments rather than wrapped nonsense.

The trace, from simulation

A deliberately small instance makes the arithmetic readable: ACC_W = 4, INC = 6, so 2⁴/6 = 2.667 and intervals must be 2 or 3.

cyclephase_q+ INCsumcarryphase_q afterbaud_tick_o
1066060
266120120
312618120
4268081
586140140
614620140
746100101
810616100
9066061

Carries at cycles 3, 6 and 8; ticks at 4, 7 and 9 — one cycle later, because the tick is registered, exactly as in Chapter 8.2 §2. The tick spacing is 3, 2 — both members of the predicted {2, 3} set, averaging 2.5 over this window and converging to 2.667 over a longer one.

Note cycle 8: sum is exactly 16, so phase_q returns to 0 and the carry fires. Equality is a carry, not a near miss — which is why the test is a carry bit rather than a comparison.

Phase accumulator, ACC_W = 4, INC = 6

9 cycles
A trace of nine fabric clock cycles from a phase accumulator with a four-bit accumulator and an increment of six. The phase register advances by six each cycle, holding zero, six, twelve, two, eight, fourteen, four, ten and zero. Whenever the extended sum reaches or exceeds sixteen a carry is produced, which happens at cycles three, six and eight. The registered tick output pulses one cycle after each carry, at cycles four, seven and nine, giving interval lengths of three and two clocks which bracket the ideal interval of two point six six seven.interval = 3interval = 3interval = 2interval = 2sum 18 >= 16 — carrysum 18 >= 16 — carrysum exactly 16 — still a carrysum exactly 16 — still acarryclkphase_q061228144100sum612188142010166carrybaud_tick_ot0t1t2t3t4t5t6t7t8
Figure 2 — the accumulator wrapping, from simulation. Columns are FABRIC CLOCK CYCLES. The phase advances by INC every cycle and the carry out of the extended sum is the timing event; the tick appears one cycle later because it is registered. With ACC_W = 4 and INC = 6 the intervals are 3 and 2 clocks — the two integers bracketing 2^4/6 = 2.667 — which is the deterministic modulation of §4 at a scale small enough to read.

7. Verification

Assert the interval set, with no tolerance. §4 established that every interval must be one of exactly two known integers. This is stronger than an average-rate check and it fails immediately on a width error, a lost carry or a wrong increment:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ACC_W=16 : 3433 intervals  873:641  874:2792
ACC_W=20 : 3456 intervals  868:3365 869:91
ACC_W=24 : 3455 intervals  868:3209 869:246

From simulation over three million fabric clocks. Zero intervals outside the predicted set in all three cases — the check is count_outside == 0, not a percentage.

Derive the expected tick count rather than guessing a tolerance. Over C fabric clocks the accumulator advances C × INC, so it wraps:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
expected ticks  =  floor(C × INC / 2^ACC_W)      ± 1 for the start-up phase

The ±1 is not slack — it is the single tick whose position depends on where the accumulator happened to start. Simulation over 3,000,000 clocks at ACC_W = 24: 3,456 observed against 3,455 expected, within the derived bound. A test using a loose percentage band would pass on a design with a materially wrong increment.

Know which checks need a long run and which do not. These are different, and conflating them produces either wasted simulation or an unsupported claim:

CheckRun length neededWhy
every interval is in the two-value setanya per-interval law — one violation is a defect
tick count against the derived expressionthousands of ticksthe ±1 start-up term must become negligible
the distribution between the two lengthsone full periodshorter samples a biased slice

The three-million-clock run above covers 45.8 full periods at ACC_W = 16 and 22.9 at 20, but only 0.179 of one period at 24 — where the period is 16,777,216 clocks. So the 92.83 % / 7.17 % split reported for the 24-bit case is a sample, not the settled distribution, and it is quoted here as an observation rather than as a characterisation. The interval-set and tick-count checks are unaffected, which is why those are the ones the suite asserts.

Compare against a software model, cycle by cycle. The accumulator is trivially modelled — phase = (phase + INC) mod 2^W, tick on wrap — so a reference model can predict every tick position exactly rather than only the aggregate rate. That is what caught the interval-set law being about 2^W / INC rather than the ideal interval while this chapter was being written.

Test the narrow width deliberately. ACC_W = 16 at 100 MHz is not a corner case to avoid; it is a configuration someone will choose, and the test should demonstrate that it produces 873/874 rather than 868/869. A verification suite that only exercises the width the designer intended cannot report that a different width is unusable.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Assertion — the tick is one cycle wide, as Chapter 8.1 requires.
property p_tick_one_cycle;
    @(posedge clk) disable iff (!rst_n)
        baud_tick_o |=> !baud_tick_o;
endproperty
assert property (p_tick_one_cycle);

// Assertion — the tick is exactly the carry, one cycle delayed. This is the
// property that fails when the sum is declared too narrow and the carry is
// silently discarded: baud_tick_o would simply never assert.
property p_tick_follows_carry;
    @(posedge clk) disable iff (!rst_n)
        baud_tick_o == $past(sum[ACC_W]);
endproperty
assert property (p_tick_follows_carry);

// Assertion — the phase register never exceeds the accumulator's modulus.
// Trivially true by width, and stated so a later widening cannot break it.
property p_phase_in_range;
    @(posedge clk) disable iff (!rst_n)
        phase_q < (1 <<< ACC_W);
endproperty
assert property (p_phase_in_range);

8. Integer or Fractional?

Neither wins universally, and the comparison below is what the decision should be made from.

Integer dividerFractional accumulator
Hardwarecounter, comparator, incrementerphase register, wide adder, carry
Adder width$clog2(DIV_MAX) — ~10–16 bitsACC_W + 1 — 17–33 bits
Rate accuracyset by N; 64 ppm at 100 MHz / 115,200set by ACC_W; −18 ppm at 24 bits, −6,589 ppm at 16
Every intervalidenticalone of two values
Phase error in a framegrows linearlybounded by one clock
Verificationexact spacing, no toleranceexact interval set, no tolerance
Runtime reconfigurationwrite a divisorwrite an increment — same mechanism
Fails badly whenDIV_MAX sized from the fastest rateACC_W chosen too narrow

Choose the integer divider when the residual error already fits the budget Chapter 4.5 computes for the link — which at 100 MHz and 115,200 baud it comfortably does, since 64 ppm against a far end specified at ±0.5% is 1.3% of the endpoint's own error. It is the right default, and Chapter 4.3 §3 already made that argument arithmetically.

Choose the accumulator when the clock and rate are genuinely awkward — an ungenerous crystal, a high rate where the divisor is small and its relative rounding error correspondingly large, or a design that must support many rates from one clock and cannot tune each. Note the second case is the one where the integer divider degrades: a divisor of 100 has a far worse relative rounding error than a divisor of 868.

And check INC before committing to the accumulator. If it is not comfortably larger than the integer divisor it replaces, the fractional form is buying nothing — and at ACC_W = 16 on this clock it is actively losing.

9. What This Means on an FPGA

The adder is the cost, and it is modest. A 25-bit adder at ACC_W = 24 is a few carry-chain slices on any modern device. It runs at the fabric clock, which for a UART is the easiest timing path in the design.

Width costs are linear and small — choose from the error, not from caution. Going from 20 to 24 bits costs four flip-flops and four adder bits and improves the residual from +33.5 to −18.3 ppm. Going from 24 to 32 costs eight more and reaches −0.1 ppm, which is far below the far end's own oscillator tolerance and therefore buys nothing a link can use. 32 bits is not the safe choice; it is the unexamined one.

Constant folding does most of the work. INC is a localparam, so the adder has one constant operand and synthesis simplifies accordingly. That is a reason to prefer the elaboration-time form where runtime reconfiguration is not required — a point Chapter 8.5 develops.

Probe the tick and count it against a known window. The accumulator's internal phase is not useful on a logic analyser — it changes every cycle and means nothing in isolation. Counting ticks over a known number of fabric clocks measures the thing that matters, and the expected count is the derived expression from §7.

10. Debugging

11. Understanding Check

12. Summary

A phase accumulator advances by INC each fabric clock on a circle of circumference 2^ACC_W, and the carry out of the accumulator is the timing event. The remainder below the carry is the next interval's fractional part, preserved rather than discarded.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
f_tick = f_clk × INC / 2^ACC_W        INC = round(BAUD_HZ × 2^ACC_W / CLK_HZ)

The quantisation moves from the interval length to the increment, and that is the whole benefit — INC = 19,327 rounds far more precisely than N = 868.

But only if the accumulator is wide enough. At ACC_W = 16 on a 100 MHz clock the increment is 75, the error is −0.659%, and the intervals are 873 and 874 rather than 868 and 869 — a hundred times worse than the integer divider it replaced, in a block that looks more sophisticated. The needed width is a property of the clock/rate pair: at 50 MHz the same rate is fine at 16 bits.

Intervals are always one of exactly two integers, bracketing 2^ACC_W / INCnot the ideal interval; the gap between those two is the residual error. The sequence is deterministic, repeating with a known period, so the right check has no tolerance band at all.

Frequency error and phase error are different things. The accumulator improves the frequency error that accumulates across a frame and slightly worsens the phase error that does not, which is why it is worth its adder.

The sum must be one bit wider than the accumulator. Otherwise the carry is discarded, the phase sequence remains perfectly correct, and the generator emits nothing.

Verified in simulation: three widths, three million fabric clocks, zero intervals outside the predicted sets, tick counts within the derived ±1 bound.

13. What Comes Next

Both generators produce one tick per bit interval. The transmitter of Module 7 wants exactly that. The receiver of Module 6 wants OVERSAMPLE of them — sixteen times as many — because Chapter 5.3 needed a grid of positions inside each interval to find a boundary it was never told about.

Chapter 8.4 resolves that asymmetry. It derives the receiver's required tick rate, compares independent generators against a shared high-rate base with a divider for the transmitter, and settles the question Chapter 7.3 §4 deliberately left open. It also draws a line that is easy to blur: sharing a generator does not reduce the receiver's phase uncertainty at all, because that uncertainty comes from the far end's clock and not from ours.

Browse the full path on the UART tutorials index. For the remainder-keeping arithmetic this chapter implements, read back to Chapter 4.4.

Continue learning

Where this fits

Part of the UART curriculum.