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.
every fabric clock: phase <- phase + INC
when phase overflows: emit a tick, and keep the remainderThe 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:
INC
f_tick = f_clk × ───────── [Hz] = [Hz] × [dimensionless]
2^ACC_WSetting f_tick to the target baud and solving:
BAUD_HZ × 2^ACC_W
INC = round( ───────────────────── ) [dimensionless]
CLK_HZThe 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:
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_W | ideal INC | INC used | actual baud | error | ppm |
|---|---|---|---|---|---|
| 16 | 75.4975 | 75 | 114,440.9180 | −0.65893 % | −6,589 |
| 20 | 1,207.9596 | 1,208 | 115,203.8574 | +0.00335 % | +33.5 |
| 24 | 19,327.3528 | 19,327 | 115,197.8970 | −0.00183 % | −18.3 |
| 32 | 4,947,802.3250 | 4,947,802 | 115,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_clk | baud | ACC_W | INC | actual baud | error | ppm |
|---|---|---|---|---|---|---|
| 50 MHz | 115,200 | 16 | 151 | 115,203.8574 | +0.00335 % | +33.5 |
| 50 MHz | 115,200 | 24 | 38,655 | 115,200.8772 | +0.00076 % | +7.6 |
| 100 MHz | 1,000,000 | 16 | 655 | 999,450.6836 | −0.05493 % | −549.3 |
| 100 MHz | 1,000,000 | 24 | 167,772 | 999,999.0463 | −0.00010 % | −1.0 |
At 50 MHz a 16-bit accumulator is already fine — INC = 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:
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_W | INC | ideal interval | 2^W / INC | observed intervals | distribution |
|---|---|---|---|---|---|
| 24 | 19,327 | 868.0556 | 868.0714 | 868, 869 | 92.83 % / 7.17 % |
| 20 | 1,208 | 868.0556 | 868.0265 | 868, 869 | 97.33 % / 2.67 % |
| 16 | 75 | 868.0556 | 873.8133 | 873, 874 | 18.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 divider | Fractional accumulator | |
|---|---|---|
| Every interval | identical, exactly N clocks | one of two values |
| Phase error within a frame | grows linearly | bounded by one clock |
| Long-term frequency error | fixed by N | far smaller, set by ACC_W |
| Which one a UART frame cares about | frequency — Chapter 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
// ---------------------------------------------------------------------------
// 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
endmoduleThe width hazard that silently discards the carry
The single most important line is the declaration of sum:
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:
// 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.
| cycle | phase_q | + INC | sum | carry | phase_q after | baud_tick_o |
|---|---|---|---|---|---|---|
| 1 | 0 | 6 | 6 | 0 | 6 | 0 |
| 2 | 6 | 6 | 12 | 0 | 12 | 0 |
| 3 | 12 | 6 | 18 | 1 | 2 | 0 |
| 4 | 2 | 6 | 8 | 0 | 8 | 1 |
| 5 | 8 | 6 | 14 | 0 | 14 | 0 |
| 6 | 14 | 6 | 20 | 1 | 4 | 0 |
| 7 | 4 | 6 | 10 | 0 | 10 | 1 |
| 8 | 10 | 6 | 16 | 1 | 0 | 0 |
| 9 | 0 | 6 | 6 | 0 | 6 | 1 |
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 cycles7. 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:
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:246From 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:
expected ticks = floor(C × INC / 2^ACC_W) ± 1 for the start-up phaseThe ±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:
| Check | Run length needed | Why |
|---|---|---|
| every interval is in the two-value set | any | a per-interval law — one violation is a defect |
| tick count against the derived expression | thousands of ticks | the ±1 start-up term must become negligible |
| the distribution between the two lengths | one full period | shorter 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.
// 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 divider | Fractional accumulator | |
|---|---|---|
| Hardware | counter, comparator, incrementer | phase register, wide adder, carry |
| Adder width | $clog2(DIV_MAX) — ~10–16 bits | ACC_W + 1 — 17–33 bits |
| Rate accuracy | set by N; 64 ppm at 100 MHz / 115,200 | set by ACC_W; −18 ppm at 24 bits, −6,589 ppm at 16 |
| Every interval | identical | one of two values |
| Phase error in a frame | grows linearly | bounded by one clock |
| Verification | exact spacing, no tolerance | exact interval set, no tolerance |
| Runtime reconfiguration | write a divisor | write an increment — same mechanism |
| Fails badly when | DIV_MAX sized from the fastest rate | ACC_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.
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 / INC — not 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
Related tutorials
- Related topic
Integer Divider RTL
The counter that turns a fabric clock into a bit-interval enable — terminal count derived rather than asserted, width from the parameters, and a runtime-programmable divisor with a commit policy that cannot corrupt an interval in flight.
- Related topic
RX and TX Baud Timing: One Generator or Two?
A receiver needs sixteen ticks per bit interval and a transmitter needs one. Sharing a base is excellent when that base is fractional and 73 times worse when it is not — and it does nothing at all for the receiver's phase uncertainty.
- 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.
Where this fits
Part of the UART curriculum.
