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 §3 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? This needs a convention before it needs a number, because the two obvious ways to count differ by exactly one — which is the same off-by-one Chapter 8.2 §2 is about, now appearing in the measurement rather than in the design.
Number the rising edges of clk after reset release starting at 1. Then:
| Question | Answer at N = 868 |
|---|---|
| which edge produces the first tick? | edge N = 868 |
| at which edge does an observer sampling at edges first see it? | edge N + 1 = 869 |
Both numbers are correct and they describe the same event. The counter needs
N edges to walk 0 … N−1 and reach its terminal value, so edge N is
where the registered output is loaded; the pulse is then high during the cycle
after that edge, so a testbench sampling at rising edges reads it one edge
later.
Measured on both of this module's dividers — the production block of Chapter 8.2 §3 and the teaching core of §6 — which agree exactly:
uart_baud_tick_int (published, N=868)
first tick PRODUCED BY edge : 868
first tick SEEN AT edge : 869
uart_baud_div_sv (core, N=868)
first tick PRODUCED BY edge : 868
first tick SEEN AT edge : 869The rest of this chapter uses the sampling convention — N + 1 — because
that is what a testbench measures. Module 8's regression uses the producing
convention and reports N. Neither is wrong; a test that mixes them fails a
correct design, which is why the convention is written down rather than
assumed.
5. Parameter Guards in Three Languages
Chapter 8.2 §6 and Chapter 8.3 §7 both ended their cross-language tables with the same unresolved row: the elaboration-time guard is the one place the three languages genuinely differ. This is where that is settled.
Verilog-2001 has no mechanism worth using. There is no $fatal, and an
initial $display produces a message a synthesis run never sees. The honest
answer is that a Verilog-2001 block documents its legal parameter ranges in a
header comment and relies on the integrator, and that is a real limitation
rather than a stylistic preference.
SystemVerilog and VHDL both have one, and in this module both were initially written wrong, in the same way.
// WRONG — 32-bit arithmetic. At ACC_W = 32 the right-hand side is 0, so the
// guard rejects every legal increment and the block will not elaborate at
// the width Chapter 8.3 section 3 recommends as the most accurate.
if (INC <= 0 || INC >= (1 << ACC_W))
$fatal(1, "INC must be 1..2^ACC_W-1, got %0d", INC);
// RIGHT — longint on both sides.
if (INC <= 0 || longint'(INC) >= (longint'(1) << ACC_W))
$fatal(1, "INC must be 1..2^ACC_W-1, got %0d", INC);-- WRONG -- VHDL's integer is only guaranteed to 2**31-1, so evaluating
-- 2**ACC_W at ACC_W = 32 raises "result of 2 ** 32 cannot be represented as
-- INTEGER" and aborts the run the guard exists to protect.
assert INC < 2**ACC_W
report "INC must be < 2**ACC_W" severity failure;
-- ALSO WRONG -- guarding the overflow with `or` does not reliably prevent
-- the right operand from being evaluated. NVC 1.23.0 evaluated it anyway.
assert (ACC_W >= 31) or (INC < 2**ACC_W)
report "INC must be < 2**ACC_W" severity failure;
-- RIGHT -- real holds every value in this range exactly and cannot overflow.
assert real(INC) < 2.0 ** ACC_W
report "uart_baud_nco: INC must be < 2**ACC_W" severity failure;Reset, in three languages
Reset is the other property every generator in this module shares, and unlike the guards it is genuinely identical everywhere:
// Verilog-2001 — asynchronous assert, active low.
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
phase_q <= {ACC_W{1'b0}};
tick_o <= 1'b0; // silent while held in reset
end else begin
...
end
end-- VHDL-2008 -- asynchronous assert, active low.
process (clk, rst_n)
begin
if rst_n = '0' then
phase_q <= (others => '0');
tick_o <= '0'; -- silent while held in reset
elsif rising_edge(clk) then
...
end if;
end process;Checked, not assumed. Every generator in the module is held in reset for twenty clocks at the start of the regression and again mid-run, and a single tick during either window is a failure. The count is zero in all three languages.
6. 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.
7. 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. Module 8 has three central RTL blocks, so it has nine testbenches — one per block per language — plus an assertion harness. Every number printed anywhere in the module comes from these runs:
| Block | Chapter | Verilog | SystemVerilog | VHDL |
|---|---|---|---|---|
uart_baud_div | 8.2 §9 | 27 | 27 | 27 |
uart_baud_nco | 8.3 §10 | 25 | 25 | 25 |
uart_timing_gen | 8.4 §10 | 19 | 19 | 19 |
baud_assertions | §8 below | — | 5 | — |
| total | 71 | 76 | 71 |
218 checks across three languages, 0 failures.
| Language | Tool | Compile | Simulate |
|---|---|---|---|
| Verilog-2001 | Icarus Verilog 13.0 (-g2001) | clean | yes |
| SystemVerilog | Icarus Verilog 13.0 (-g2012) | clean | yes |
| VHDL-2008 | NVC 1.23.0 (LLVM 23.1.1) | clean | yes |
The per-block counts are identical across all three languages — 27, 25 and 19 — and that is not a coincidence, it is the acceptance criterion. A module where SystemVerilog runs two hundred checks and VHDL runs three has not verified the same design three times; it has verified it once and compiled it twice. The check counts may legitimately differ between ecosystems; here they did not need to, so they do not.
The assertion harness is SystemVerilog-only, and that is the one honest gap:
Icarus supports neither named properties nor $past, so §8's properties are
published as SVA and executed procedurally. The procedural form is bound into
the SystemVerilog side; the Verilog and VHDL testbenches carry the same
pulse-width and reset checks inline, which is why their totals still match.
And from the two production blocks, simulated separately over three million fabric clocks each:
integer : 3455 intervals, all exactly 868 clks (min = max, no band)
reconfig : divisor 868 -> 434, intervals now 434 clks
rejected : write of 0 refused, intervals stay 434, cfg_err_o = 1
composed : os_tick = 55296 baud_tick = 3456 ratio exactly 16.0000
all 3456 baud ticks coincide with an os tick, 0 do notProving the checkers can fail
A suite that has never failed has not been shown capable of failing. Nine deliberate mutations, each run against the unmodified testbench for its block:
| Mutation | Block | Breaks | Failures |
|---|---|---|---|
terminal count uses DIV_N, not DIV_N-1 | divider | 8.2 §2 | 18 / 27 |
| tick held two clocks wide | divider | 8.1 §1 | 15 / 27 |
| counter resets to all-ones | divider | §4 reset contract | 9 / 27 |
| accumulator discards the remainder | NCO | 8.3 §1 | 19 / 25 |
| increment off by one | NCO | 8.3 §2 | 10 / 25 |
| accumulator resets half-full | NCO | §4 reproducibility | 9 / 25 |
carry width INC[ACC_W:0] | NCO | 8.3 §7 | 4 / 25 |
| TX divide-down off by one | composed | 8.4 §6 | 6 / 19 |
| baud tick registered, not decoded | composed | 8.4 §8 | 3 / 19 |
Read the table bottom to top. Mutations that break something loudly are caught by most of the suite; the ones that break a stated property with no observable symptom are caught by two or three checks, and those checks exist only because somebody wrote the property down first. The last row is a defect that shipped in this module's own RTL and survived review.
The suite also has no external timeout. There is no timeout command on
the machine these ran on — which is exactly why every testbench in Module 8
carries its own watchdog. A mutation that makes a generator fall silent has to
end its own run and report a failure, or mutation testing is not possible at
all.
Between them these cover reset behaviour and reset-to-first-tick, exact integer spacing at six divisors, the fractional interval sets at four accumulator widths, long-run tick counts against exact integer predictions rather than tolerance bands, the composed ratio and its phase contract at two configurations, runtime reconfiguration and a rejected illegal write.
8. Assertions
Four properties hold for any tick generator in this module, whatever
architecture produces the ticks. They are published as SystemVerilog
Assertions because that is what you would write in a tool that supports them,
and executed procedurally because Icarus Verilog 13.0 supports neither named
properties nor $past.
//===========================================================================
// baud_assertions — the properties that hold for ANY tick generator in
// this module, regardless of how it produces its ticks.
//
// Published as SystemVerilog Assertions first, because that is what you
// would write in a tool that supports them. Icarus Verilog 13.0 supports
// neither named properties nor $past, so what is EXECUTED is the
// procedural form below it — and the procedural form is what produced
// every number on these pages.
//===========================================================================
`ifdef HAS_SVA
// p_tick_one_cycle — catches a generator that holds the enable high for
// two clocks, which advances every consumer twice per bit interval and
// halves the baud rate. The symptom looks like a divisor error, so the
// investigation goes to the wrong place.
property p_tick_one_cycle;
@(posedge clk) disable iff (!rst_n) tick |=> !tick;
endproperty
// p_no_tick_in_reset — catches a generator that emits while held in
// reset, advancing consumers before the design is running.
property p_no_tick_in_reset;
@(posedge clk) !rst_n |-> !tick;
endproperty
// p_interval_bounded — catches a generator that stops (interval never
// ends) or free-runs (interval far too short). MIN_GAP == MAX_GAP for
// an integer divider; they differ by one for an NCO.
property p_interval_in_range;
@(posedge clk) disable iff (!rst_n)
tick |-> ##[MIN_GAP:MAX_GAP] tick;
endproperty
`endif
module baud_assertions #(
parameter int MIN_GAP = 1,
parameter int MAX_GAP = 100000
) (
input logic clk,
input logic rst_n,
input logic tick
);
int unsigned n_wide = 0; // p_tick_one_cycle
int unsigned n_in_reset = 0; // p_no_tick_in_reset
int unsigned n_too_short = 0; // p_interval_in_range, low side
int unsigned n_too_long = 0; // p_interval_in_range, high side
int unsigned gap = 0;
logic seen_first = 1'b0;
logic prev_tick = 1'b0;
always @(posedge clk) begin
if (!rst_n) begin
if (tick === 1'b1) n_in_reset++;
gap = 0; seen_first = 1'b0; prev_tick = 1'b0;
end else begin
gap++;
if (tick === 1'b1) begin
if (prev_tick === 1'b1 && MIN_GAP > 1) n_wide++;
if (seen_first) begin
if (gap < MIN_GAP) n_too_short++;
if (gap > MAX_GAP) n_too_long++;
end
seen_first = 1'b1;
gap = 0;
end else if (seen_first && gap > MAX_GAP) begin
n_too_long++;
gap = 0; // report once per overrun, not per clock
end
prev_tick = tick;
end
end
function automatic int unsigned violations();
return n_wide + n_in_reset + n_too_short + n_too_long;
endfunction
endmoduleEach property exists to catch a specific defect, and a property that cannot name its defect does not belong in the file:
| Property | Defect it catches | Why it would otherwise be missed |
|---|---|---|
p_tick_one_cycle | enable held two clocks | advances every consumer twice per interval, halving the baud rate — and the symptom points at the divisor, not the pulse |
p_no_tick_in_reset | generator emits while held in reset | consumers advance before the design is running; invisible unless reset is held for more than a cycle |
p_interval_in_range (low) | generator free-runs | a frame is emitted hundreds of times too fast; the line looks busy and no receiver decodes it |
p_interval_in_range (high) | generator stops | the link goes quiet, and without a bound the testbench waits forever instead of reporting |
9. UVM Mapping
Module 8 produces timing, not transactions, and that changes what a UVM environment is for here. The full UART environment is Module 16's subject; what is worth fixing now is which part of a baud generator's contract each component would own, because those boundaries follow from this module rather than from UVM.
| Generator contract element | UVM component | What it carries |
|---|---|---|
| a rate to produce | config object, not a sequence item | CLK_HZ, BAUD_HZ, OVERSAMPLE, ACC_W |
| a runtime divisor write | sequence item | divisor, expect_error |
driving cfg_we_i / cfg_div_i | driver | one write per item, on a clock edge |
observing baud_tick_o / os_tick_o | monitor | measures intervals from the wire — this is tick_probe |
| predicted interval set | reference model | nco_ref, or {DIV_N} for the divider |
| comparison | scoreboard | interval-by-interval, plus exact tick count |
Three things in that table are decided by this module, not by UVM.
The transaction is a configuration write, not a tick. A tick is not a transaction — it has no payload, no direction and no completion. Modelling every tick as a sequence item produces an environment that generates 115,200 objects per second per link and learns nothing. The rate is a property of the configuration, and the interesting stimulus is the small number of moments the configuration changes.
The monitor is tick_probe, essentially unchanged.
Chapter 8.2 §8 already built a component
whose only inputs are a clock, a reset and a tick, and whose output is the
measured interval set — which is precisely a UVM monitor's job. That is not a
coincidence: the independence rule that makes a good testbench checker is the
same rule that makes a good monitor.
The scoreboard compares interval sets, not individual ticks. For the NCO there is no single correct interval — Chapter 8.3 §4 showed the lengths alternate between two adjacent integers — so a scoreboard that expects one number is wrong by construction. What it must check is that the observed set equals the predicted set and the exact count matches.
Coverage worth collecting, from the axes this module actually established:
| Axis | Bins | Why |
|---|---|---|
DIV_N | 1, 2, small, typical, large | 1 and 2 are the degenerate corners; §4's width guard lives there |
ACC_W | 16, 20, 24, 32 | 16 is worse than an integer divider; 32 is where three width defects lived |
OVERSAMPLE | 1, 8, 16, non-power-of-two | a non-power-of-two exercises the counter's terminal decode |
| reconfiguration timing | idle, mid-interval, on a boundary | §6's commit policy is only testable mid-interval |
| rejected writes | divisor 0, divisor > DIV_MAX | §2's error classes |
The cross that earns its keep is ACC_W × OVERSAMPLE, because the
composed generator's increment is computed from their product and that product
is where the 64-bit intermediate matters
(Chapter 8.4 §9).
What is deliberately not here. No uvm_component boilerplate, no factory
registration, no config-db plumbing. None of it teaches anything about a baud
generator, and all of it is Module 16's subject where it can be built once and
used properly.
10. The Module 8 Verification Matrix
Every requirement this module states, and how it is actually observed:
| Requirement | RTL | Directed test | Corner test | Assertion | Waveform |
|---|---|---|---|---|---|
one tick per DIV_N clocks, exactly | uart_baud_div | interval min = max at 6 divisors | DIV_N = 1, 2 | p_interval_in_range | 8.2 Fig 1 |
terminal value is DIV_N-1 | uart_baud_div | exact tick count | — | — | 8.2 §9 trace |
| tick exactly one clock wide | all three | wide_pulse = 0 | DIV_N = 1 is the exception | p_tick_one_cycle | 8.1 Fig 1 |
| no tick during reset | all three | 20-clock reset window | reset re-applied mid-run | p_no_tick_in_reset | — |
first tick at edge DIV_N | uart_baud_div | 6 divisors | re-reset repeats it | — | §4 convention table |
| mean rate = f_clk × INC / 2^ACC_W | uart_baud_nco | exact count + ppm | ACC_W 16…32 | — | 8.3 §8 trace |
| exactly two interval lengths, adjacent | uart_baud_nco | max − min = 1 | all four widths | — | 8.3 Fig 1 |
| remainder carried between intervals | uart_baud_nco | reference-model match | — | — | 8.3 §8 trace |
| OVERSAMPLE os ticks per baud tick | uart_timing_gen | per-interval count, 2 configs | os8 and os16 | — | 8.4 Fig 2 |
| every baud tick is an os tick | uart_timing_gen | coincidence check, 2 configs | after re-reset | — | 8.4 Fig 2 |
| divisor change on a boundary only | uart_baud_tick_int | mid-interval write | write while idle | — | — |
| illegal divisor rejected, old kept | uart_baud_tick_int | write of 0 | cfg_err_o sticky | — | — |
| parameter guards fire at the limits | all three | elaboration at ACC_W = 32 | §5's three defects | — | — |
Two rows have no assertion and that is deliberate. The reconfiguration rows are sequence properties over a write and the following interval; an immediate assertion cannot express them and a full SVA sequence would need a tool this module does not have. They are covered by directed tests that reconfigure at a deliberately awkward moment, which is the honest substitute.
One row has no waveform, the parameter guards, because an elaboration-time check produces no waveform to show — it either builds or it does not.
11. 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.
12. Understanding Check
13. 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.
14. 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.
15. 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.
