UART · Module 8
Parameterisation, Reset and Error Reporting
Compute divider values at elaboration, surface the resulting error, and define reset and reconfiguration behaviour — keeping apart the three kinds of error that are routinely conflated.
Chapters 8.2, 8.3 and 8.4 produced working generators and an architecture to compose them. None of them stated a contract.
A block is reusable when someone can instantiate it from its interface without reading its body — the test Chapter 6.1 §1 set for the receiver. For a timing generator that means answering four questions the preceding chapters left implicit:
- Which parameter values are legal, and what does each one do to the hardware?
- What happens when an illegal one is supplied — and is it the same kind of event as a bad runtime write?
- What is on the output during reset, and when does the first tick arrive?
- Should the rate be fixed at elaboration or programmable at runtime, and what does each cost?
This chapter answers them, and closes the module.
1. The Parameter Contract
Every parameter, with the one thing that matters most: what it does to the hardware.
| Parameter | Unit | Legal range | Illegal when | Hardware consequence |
|---|---|---|---|---|
CLK_HZ | Hz | > 0 | 0 | none directly — it sets the derived divisor or increment |
BAUD_HZ | baud | 0 < BAUD_HZ ≤ CLK_HZ | 0, or above CLK_HZ | sets the divisor / increment |
DIV_MAX | cycles | ≥ derived divisor | below it | sets the counter and divisor register widths |
ACC_W | bits | 2 … 32 | outside, or INC rounding to 0 | sets the accumulator and adder width |
OVERSAMPLE | — | ≥ 1, with BAUD × M ≤ CLK_HZ | product exceeds CLK_HZ | sets the subsample counter width |
Two of these change register widths and three do not, and the split is the useful one. CLK_HZ and BAUD_HZ are arithmetic inputs — change them and the same hardware gets a different constant. DIV_MAX, ACC_W and OVERSAMPLE are structural — change them and the netlist changes shape.
That distinction drives the parameter-count discipline. Chapter 6.6 §6 argued that every parameter multiplies the verification burden, and it applies here with a sharper edge: a structural parameter creates width corner cases that must actually be elaborated and simulated, while an arithmetic one mostly changes a number. This module's generators expose two structural parameters between them, which is why Chapter 8.3 §9 could argue about ACC_W from measured error rather than from caution.
DIV_MAX is the one most often got wrong, and Chapter 8.2 §3 gave the rule: it is derived from the slowest rate the design must support, because slow rates need large divisors. Sizing it from the nominal rate produces a generator that works until someone configures 9,600 baud.
2. Three Kinds of Error
These are routinely lumped into "error handling" and they have different mechanisms, different consumers and different lifetimes.
| Elaboration error | Runtime programming error | Accuracy warning | |
|---|---|---|---|
| Example | BAUD_HZ = 0 | divisor register written with 0 | derived divisor gives 3% rate error |
| Detected | at elaboration | on the write | at elaboration |
| Mechanism | $fatal | a status bit in hardware | $warning, or a review gate |
| Consumer | the designer | software | the designer |
| Exists in silicon? | no | yes | no |
| Can it be ignored? | no — build stops | yes, and the link misbehaves | yes, and it may be correct to |
The elaboration guards are simulation and synthesis constructs, not hardware. $fatal infers nothing; it stops the build. Presenting it as a runtime check is a category error that appears in real code reviews, usually as an expectation that a bad parameter will "be caught" in the field.
// Elaboration guard — NOT synthesizable hardware. This infers nothing and
// exists only to stop a build whose parameters cannot produce a generator.
initial begin
if (CLK_HZ == 0 || BAUD_HZ == 0)
$fatal(1, "uart_baud_tick_int: CLK_HZ and BAUD_HZ must be non-zero");
if (BAUD_HZ > CLK_HZ)
$fatal(1, "uart_baud_tick_int: BAUD_HZ (%0d) exceeds CLK_HZ (%0d)", BAUD_HZ, CLK_HZ);
if (N_DEF > DIV_MAX)
$fatal(1, "uart_baud_tick_int: divisor %0d exceeds DIV_MAX %0d", N_DEF, DIV_MAX);
endThe runtime check is real hardware and must behave like an interface:
// Synthesizable — a rejected write is a status bit software can read.
// Sticky until reset, because the consumer reads it some time later and a
// one-cycle pulse would be unobservable to it.
wire cfg_bad = cfg_we_i && (cfg_div_i == '0);Writing a guard is not the same as knowing it fires. All six illegal configurations were elaborated deliberately and each produced its intended message:
| Configuration | Result |
|---|---|
BAUD_HZ = 0 | CLK_HZ and BAUD_HZ must be non-zero |
CLK_HZ = 0 | CLK_HZ and BAUD_HZ must be non-zero |
BAUD_HZ > CLK_HZ | BAUD_HZ (115200) exceeds CLK_HZ (100000) |
divisor > DIV_MAX | divisor 868 exceeds DIV_MAX 64 |
ACC_W = 1 | ACC_W = 1 outside 2..32 |
ACC_W = 8 at 115,200 | increment rounds to 0 — ACC_W too narrow |
And six legal configurations elaborate cleanly. A guard that has never been exercised is an untested line of code in the one place where being wrong is silent.
3. The Accuracy Warning Is the Interesting One
The third column of §2 has no equivalent in most blocks, and it is the one that earns its place here.
A generator can be given entirely legal parameters and still produce a rate that will not work. Chapter 8.4 §1 showed 1 Mbaud at 16× on a 100 MHz clock needing a divisor of 6.25, rounded to 6 — +4.17%, which no UART link tolerates. Nothing is illegal: the clock is non-zero, the rate is below the clock, the divisor fits.
The block knows the error at elaboration — it computed the divisor — so it can report it:
// Elaboration accuracy report — NOT hardware. The threshold is a project
// decision, not a protocol constant: Chapter 4.5 showed the tolerable error
// depends on frame length, the far end's specification, the sampling
// architecture and the reserve the designer wants.
//
// error = (CLK_HZ/N - BAUD) / BAUD = (CLK_HZ - N x BAUD) / (N x BAUD)
//
// The right-hand form is used deliberately: it performs NO division before
// the subtraction, and it is evaluated in longint so the x1_000_000 cannot
// overflow. The callout below shows what the obvious form does instead.
localparam longint unsigned DEN = longint'(N_DEF) * longint'(BAUD_HZ);
localparam longint unsigned NUM = (longint'(CLK_HZ) > DEN)
? (longint'(CLK_HZ) - DEN)
: (DEN - longint'(CLK_HZ));
localparam int unsigned ACTUAL_PPM_ABS = int'((NUM * 1_000_000) / DEN);
initial begin
$display("uart_baud_tick_int: CLK_HZ=%0d BAUD_HZ=%0d -> N=%0d, |error| = %0d ppm",
CLK_HZ, BAUD_HZ, N_DEF, ACTUAL_PPM_ABS);
if (ACTUAL_PPM_ABS > ERR_WARN_PPM)
$warning("uart_baud_tick_int: rate error %0d ppm exceeds ERR_WARN_PPM %0d",
ACTUAL_PPM_ABS, ERR_WARN_PPM);
endElaborated across five configurations and compared against an independent exact calculation:
CLK_HZ | BAUD_HZ | N | reported | exact |
|---|---|---|---|---|
| 100 MHz | 115,200 | 868 | 64 ppm | 64.0 |
| 50 MHz | 115,200 | 434 | 64 ppm | 64.0 |
| 100 MHz | 9,600 | 10,417 | 31 ppm | 32.0 |
| 100 MHz | 1,000,000 | 100 | 0 ppm | 0.0 |
| 100 MHz | 16,000,000 | 6 | 41,666 ppm | 41,666.7 |
The last row is the 1 Mbaud-at-16× base rate from Chapter 8.4 §1, and the report makes its 4.17% error impossible to miss in a build log.
4. Reset Behaviour
Three questions, answered rather than left to emerge.
What is on baud_tick_o during reset? Low. A tick is an event, and a generator that is not running has no events to report. Asserting it during reset would advance every consumer the moment reset released — or, worse, while it is still asserted, which some consumers do not guard against.
What is the accumulator or counter reset to? Zero, for both generators. For the integer divider this makes the first interval a full N cycles rather than a partial one. For the accumulator it makes the tick sequence reproducible: two identical generators reset together produce identical interval sequences forever, which is what allows a reference model to predict tick positions exactly rather than only the average rate (Chapter 8.3 §4).
When does the first tick arrive? For the integer divider, N + 1 fabric clocks after reset release — verified in simulation at 869 for N = 868. The counter needs N edges to walk 0 … N−1 and reach its terminal value, and the registered output makes the pulse observable one edge later.
5. Compile-Time or Runtime?
The last architectural decision, and the module has now produced evidence for both sides.
| Elaboration-time (parameter) | Runtime (input register) | |
|---|---|---|
| Rate changes | never | any time |
| Divisor logic | constant-folded — comparator against a literal | full-width comparator plus divisor registers |
| Extra registers | none | active + shadow + pending flag |
| Width | sized from the one rate | sized from DIV_MAX, the slowest rate |
| Invalid values | impossible after elaboration | possible — needs §2's status bit |
| Update hazard | none | must be committed on a boundary |
| Verification | one configuration | every configuration, plus the update path |
The elaboration-time form is meaningfully smaller, and not by a rounding error: the comparator has a constant operand, the divisor registers disappear entirely, and at a fixed rate the counter can be sized to that rate rather than to the slowest supported one. Chapter 4.3's generator is exactly this, which is why it was the right thing to publish there.
The runtime form is what a UART with software-selectable baud actually needs, and that is most of them. Chapter 8.2 built it, including the commit policy that keeps a rate change from truncating the interval in progress.
The wrong answer is to build both into one block behind a PROGRAMMABLE parameter. It doubles the configuration space, and every test must now run twice to cover both paths — for a choice that is made once per integration and never changes afterwards. Two small blocks with one clear contract each is the better structure, and the composition of Chapter 8.4 shows how they are selected at instantiation.
When a rate change should take effect is a policy question above the generator. Chapter 8.2 §4 guarantees the interval in progress is not corrupted; it does not guarantee the frame is, and a change applied between two bits of a frame still corrupts it cleanly. Waiting for the link to be idle is usually right and it is a decision for the layer that knows whether a frame is in flight — the transmitter exposes tx_busy_o for exactly this (Chapter 7.4 §2), and Module 13 owns the register-level sequencing.
6. Verification
Elaborate the illegal configurations deliberately. §2's table is that test, and it is the only way to know a guard fires. Six invalid parameter sets, six expected messages, and six valid sets that must elaborate cleanly.
Elaborate across the structural parameters, not just the default. ACC_W at 16, 24 and 32; DIV_MAX at values that do and do not accommodate the derived divisor; OVERSAMPLE at a non-power-of-two. A block verified only at its default values has verified one point in a space it advertises as a range.
Assert no tick during reset, which is cheap and catches a reset that was assumed rather than written:
// Checker — the generator is silent while held in reset.
always_ff @(posedge clk)
if (!rst_n && baud_tick_o)
$error("baud_tick_o asserted during reset at %0t", $time);Measure reset-to-first-tick and assert the derived value, not a guessed one. §4's N + 1, computed from the parameters rather than written as a constant, so the test follows a rate change.
Check the reported error against an independent calculation. The $display line in §3 is only useful if it is right; comparing it against a script that recomputes the same quantity from CLK_HZ and BAUD_HZ catches an integer-arithmetic mistake in the ppm expression — which is easy to make, since it must avoid overflow and division-before-multiplication in equal measure.
Results from this module's suite:
TOTAL CHECKS: 17 FAILURES: 0
integer : 3456 intervals, all exactly 868 clks
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
composed : os_tick=55312 baud_tick=3457 baud interval 868..869 clks
reconfig : divisor 868 -> 434, intervals now 434 clksThree million fabric clocks, covering reset-to-first-tick, exact integer spacing, the fractional interval sets at three widths, long-run tick counts against derived bounds, the composed ratio, runtime reconfiguration and a rejected illegal write.
7. What This Means on an FPGA
A fixed-rate generator is very small. Constant folding removes the divisor registers and reduces the comparator to a constant compare; what remains is a counter and a few LUTs. If a design does not need software-selectable baud, this is a real saving and not a theoretical one.
A runtime-programmable generator is still small, just not as small: two divisor registers and a full-width comparator at DIV_MAX width. At 16 bits that is around fifty flip-flops for the whole block.
Put the accuracy report where someone will read it. The $display in §3 appears in the simulation log; a synthesis-time equivalent — a $info in an initial block, which most tools print during elaboration — puts the achieved rate and its error in the build log next to the timing summary. That is a cheap way to make a 4% generator visible before the board arrives rather than after.
Reset release is a Module 12 subject and it does apply here. A generator whose reset releases asynchronously relative to clk can produce a first interval of unpredictable length. It does not affect the rate and, per §4, nothing in a UART measures the tick stream's absolute phase — but it is worth knowing that the N + 1 figure assumes a synchronously-released reset, which is what Module 12's methodology provides.
8. Understanding Check
9. Summary
Parameters split into structural and arithmetic. DIV_MAX, ACC_W and OVERSAMPLE change register widths and create corner cases that must actually be elaborated; CLK_HZ and BAUD_HZ change a constant. This module exposes two structural parameters between its generators, deliberately. DIV_MAX is derived from the slowest supported rate.
Three kinds of error, with different mechanisms and consumers. An elaboration error stops a build and is read by the designer — $fatal is not hardware and infers nothing. A runtime programming error is real logic reporting through a sticky status bit read by software. An accuracy warning is neither: legal parameters producing an unusable rate.
Guards were elaborated deliberately: six invalid configurations, six expected messages; six valid configurations elaborating cleanly. A guard that has never fired is untested in the one place where being wrong is silent.
The generator reports its own contribution and does not pass judgement. There is no universal tolerance to enforce — Chapter 4.5 showed the tolerable error depends on frame length, the far end, the sampling architecture and the reserve. So: print the ppm unconditionally, warn above a project-chosen threshold, never $fatal on accuracy.
Reset: the tick is low, counters and accumulators are zeroed so the sequence is reproducible, and the first tick arrives N + 1 clocks after release — the output register's latency, paid once, with every interval thereafter exactly N. A test expecting N fails on a correct design.
Fixed-rate generators constant-fold to something meaningfully smaller; programmable ones are what software-selectable baud requires. Building both behind one parameter is the wrong structure — two blocks with one contract each.
10. Where Module 8 Leaves You
Chapter 8.1 established the tick as an enable and priced the generated-clock alternative in constraints, CDC review and clock resources. Chapter 8.2 built the integer divider with its terminal count derived, its width taken from the slowest supported rate, and a commit policy that cannot corrupt an interval in flight. Chapter 8.3 built the phase accumulator, derived the increment equation, and found that a too-narrow accumulator is a hundred times worse than the divider it replaces. Chapter 8.4 resolved the RX/TX asymmetry and showed that sharing a base is 58× better with a fractional generator and 73× worse with an integer one. This chapter gave all of it a contract.
The thread running through the module is one idea, stated three times in different clothes: the accuracy of a generator is set by the relative rounding error of the integer it quantises. It explains why 868 is good and 54 is not, why a 16-bit accumulator fails where a 24-bit one succeeds, why the same 16-bit accumulator is fine at a 16× higher base rate, and why sharing helps one architecture and ruins the other.
An engineer holding this module can take a clock frequency and a required rate, choose an architecture, derive every width, predict the achieved rate and its error, and say what the block does at reset and when reconfigured — without memorising any of the RTL.
11. What Comes Next
Both halves of the link now exist and both are fed. What has not been addressed is what happens when a frame is not clean.
Module 9 takes the per-frame status the receiver produces — framing error, parity error, overrun — and builds the error architecture on top of it: the detection limits, break generation and detection, the sticky flags a status register exposes, the idle detection that would suppress the fabricated byte Chapter 6.2 §7 demonstrated, and how a receiver gets back in step after a malformed frame.
After that: Module 10 adds the buffering that moves the overrun threshold and the flow control neither half can provide alone; Module 11 assembles an IP; Module 12 returns to the clock-domain and reset questions this module deliberately scoped out.
Browse the full path on the UART tutorials index. For the budget every generator in this module contributes one term to, read back to Chapter 4.5.
Continue learning
Related tutorials
- 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
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
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.
- 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.
Where this fits
Part of the UART curriculum.
