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 §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 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? 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:

QuestionAnswer 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:

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

The 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.

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

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

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:

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. 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:

BlockChapterVerilogSystemVerilogVHDL
uart_baud_div8.2 §9272727
uart_baud_nco8.3 §10252525
uart_timing_gen8.4 §10191919
baud_assertions§8 below—5—
total717671

218 checks across three languages, 0 failures.

LanguageToolCompileSimulate
Verilog-2001Icarus Verilog 13.0 (-g2001)cleanyes
SystemVerilogIcarus Verilog 13.0 (-g2012)cleanyes
VHDL-2008NVC 1.23.0 (LLVM 23.1.1)cleanyes

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:

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

Proving 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:

MutationBlockBreaksFailures
terminal count uses DIV_N, not DIV_N-1divider8.2 §218 / 27
tick held two clocks widedivider8.1 §115 / 27
counter resets to all-onesdivider§4 reset contract9 / 27
accumulator discards the remainderNCO8.3 §119 / 25
increment off by oneNCO8.3 §210 / 25
accumulator resets half-fullNCO§4 reproducibility9 / 25
carry width INC[ACC_W:0]NCO8.3 §74 / 25
TX divide-down off by onecomposed8.4 §66 / 19
baud tick registered, not decodedcomposed8.4 §83 / 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.

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

Each property exists to catch a specific defect, and a property that cannot name its defect does not belong in the file:

PropertyDefect it catchesWhy it would otherwise be missed
p_tick_one_cycleenable held two clocksadvances every consumer twice per interval, halving the baud rate — and the symptom points at the divisor, not the pulse
p_no_tick_in_resetgenerator emits while held in resetconsumers advance before the design is running; invisible unless reset is held for more than a cycle
p_interval_in_range (low)generator free-runsa frame is emitted hundreds of times too fast; the line looks busy and no receiver decodes it
p_interval_in_range (high)generator stopsthe 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 elementUVM componentWhat it carries
a rate to produceconfig object, not a sequence itemCLK_HZ, BAUD_HZ, OVERSAMPLE, ACC_W
a runtime divisor writesequence itemdivisor, expect_error
driving cfg_we_i / cfg_div_idriverone write per item, on a clock edge
observing baud_tick_o / os_tick_omonitormeasures intervals from the wire — this is tick_probe
predicted interval setreference modelnco_ref, or {DIV_N} for the divider
comparisonscoreboardinterval-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:

AxisBinsWhy
DIV_N1, 2, small, typical, large1 and 2 are the degenerate corners; §4's width guard lives there
ACC_W16, 20, 24, 3216 is worse than an integer divider; 32 is where three width defects lived
OVERSAMPLE1, 8, 16, non-power-of-twoa non-power-of-two exercises the counter's terminal decode
reconfiguration timingidle, mid-interval, on a boundary§6's commit policy is only testable mid-interval
rejected writesdivisor 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:

RequirementRTLDirected testCorner testAssertionWaveform
one tick per DIV_N clocks, exactlyuart_baud_divinterval min = max at 6 divisorsDIV_N = 1, 2p_interval_in_range8.2 Fig 1
terminal value is DIV_N-1uart_baud_divexact tick count——8.2 §9 trace
tick exactly one clock wideall threewide_pulse = 0DIV_N = 1 is the exceptionp_tick_one_cycle8.1 Fig 1
no tick during resetall three20-clock reset windowreset re-applied mid-runp_no_tick_in_reset—
first tick at edge DIV_Nuart_baud_div6 divisorsre-reset repeats it—§4 convention table
mean rate = f_clk × INC / 2^ACC_Wuart_baud_ncoexact count + ppmACC_W 16…32—8.3 §8 trace
exactly two interval lengths, adjacentuart_baud_ncomax − min = 1all four widths—8.3 Fig 1
remainder carried between intervalsuart_baud_ncoreference-model match——8.3 §8 trace
OVERSAMPLE os ticks per baud tickuart_timing_genper-interval count, 2 configsos8 and os16—8.4 Fig 2
every baud tick is an os tickuart_timing_gencoincidence check, 2 configsafter re-reset—8.4 Fig 2
divisor change on a boundary onlyuart_baud_tick_intmid-interval writewrite while idle——
illegal divisor rejected, old keptuart_baud_tick_intwrite of 0cfg_err_o sticky——
parameter guards fire at the limitsall threeelaboration 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

Where this fits

Part of the UART curriculum.