Skip to content
VLSI Mentor

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.

ParameterUnitLegal rangeIllegal whenHardware consequence
CLK_HZHz> 00none directly — it sets the derived divisor or increment
BAUD_HZbaud0 < BAUD_HZ ≤ CLK_HZ0, or above CLK_HZsets the divisor / increment
DIV_MAXcycles≥ derived divisorbelow itsets the counter and divisor register widths
ACC_Wbits2 … 32outside, or INC rounding to 0sets the accumulator and adder width
OVERSAMPLE≥ 1, with BAUD × M ≤ CLK_HZproduct exceeds CLK_HZsets 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 errorRuntime programming errorAccuracy warning
ExampleBAUD_HZ = 0divisor register written with 0derived divisor gives 3% rate error
Detectedat elaborationon the writeat elaboration
Mechanism$fatala status bit in hardware$warning, or a review gate
Consumerthe designersoftwarethe designer
Exists in silicon?noyesno
Can it be ignored?no — build stopsyes, and the link misbehavesyes, 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.

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

The runtime check is real hardware and must behave like an interface:

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

ConfigurationResult
BAUD_HZ = 0CLK_HZ and BAUD_HZ must be non-zero
CLK_HZ = 0CLK_HZ and BAUD_HZ must be non-zero
BAUD_HZ > CLK_HZBAUD_HZ (115200) exceeds CLK_HZ (100000)
divisor > DIV_MAXdivisor 868 exceeds DIV_MAX 64
ACC_W = 1ACC_W = 1 outside 2..32
ACC_W = 8 at 115,200increment 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:

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

Elaborated across five configurations and compared against an independent exact calculation:

CLK_HZBAUD_HZNreportedexact
100 MHz115,20086864 ppm64.0
50 MHz115,20043464 ppm64.0
100 MHz9,60010,41731 ppm32.0
100 MHz1,000,0001000 ppm0.0
100 MHz16,000,000641,666 ppm41,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 changesneverany time
Divisor logicconstant-folded — comparator against a literalfull-width comparator plus divisor registers
Extra registersnoneactive + shadow + pending flag
Widthsized from the one ratesized from DIV_MAX, the slowest rate
Invalid valuesimpossible after elaborationpossible — needs §2's status bit
Update hazardnonemust be committed on a boundary
Verificationone configurationevery 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:

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

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

Three 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

Where this fits

Part of the UART curriculum.