Skip to content
VLSI Mentor

UART · Module 8

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.

Chapter 4.3 did the arithmetic. It chose between floor, ceiling and nearest divisors, computed the resulting rate error, and published a working divider — explicitly labelled as the elaboration-time version, with a note naming what this chapter owns:

The production generator — runtime reconfiguration, separate RX/TX timing, error reporting — is Module 8.

So this chapter does not re-derive the divisor. It builds the block: the counter's semantics stated before its code, the terminal count derived, the width taken from the parameters, and the runtime-programmable divisor that a UART whose rate is set by software actually needs — together with the commit policy that keeps a rate change from corrupting the interval in progress.

1. Counter Semantics, Before Any Code

Six questions, following the discipline Chapter 6.3 §1 established for the receiver's counters.

QuestionAnswer
What does it count?Fabric clock cycles, every cycle, unconditionally.
What is its range?0 … N−1, where N is the divisor.
What resets it to zero?Reset, and reaching its terminal value.
Which value is terminal?N−1, not N — §2.
How many cycles per output tick?Exactly N.
When is the tick observable?One cycle after the terminal value, because the tick is registered — §5.

The fourth and fifth rows are the same statement seen twice, and the second form is the one to hold on to: a counter walking 0 … N−1 and wrapping takes N cycles per lap. Everything else follows.

2. The Off-By-One, Derived

The tempting comparison is against the divisor itself. Here is what each produces, walked edge by edge at N = 4:

edgecnt before== N−1 (3)?tickcnt after‖cnt before== N (4)?tickcnt after
10no—1‖0no—1
21no—2‖1no—2
32no—3‖2no—3
43yestick0‖3no—4
50no—1‖4yestick0
61no—2‖0no—1
72no—3‖1no—2
83yestick0‖2no—3

Correct form: ticks at edges 4 and 8 — spacing 4 = N. Wrong form: ticks at edges 5 and 10 — spacing 5 = N + 1.

The counter that compares against N visits N + 1 distinct values, so every bit interval is one fabric cycle too long. At 100 MHz and 115,200 baud:

divisoractual bauderrormagnitude
868 — correct115,207.3733+0.0064 %64 ppm
869 — off-by-one115,074.7986−0.1087 %1,087 ppm

Seventeen times worse, and in the opposite direction. The sign flip matters: a design tuned against a far end that was slightly slow now compounds rather than cancels. And 869 is exactly the ceiling divisor from Chapter 4.3 §1 — so the bug silently substitutes a different rounding policy, which is why the symptom looks like a configuration choice rather than a defect.

Terminal count and tick, N = 4

11 cycles
A trace of eleven fabric clock cycles from an integer baud divider configured with a divisor of four. The counter holds zero, one, two and three in successive cycles then wraps back to zero, repeating every four cycles. The baud tick output is high for exactly one cycle at edges four and eight, which is one cycle after the counter showed its terminal value of three, because the tick output is registered. The spacing between successive ticks is exactly four cycles, matching the divisor.NNterminal valueterminal valuetick — registered, one cycle latertick — registered, onecycle laterspacing = 4 = Nspacing = 4 = Nclkcnt_q01230123012cnt_q == N-1baud_tick_ot0t1t2t3t4t5t6t7t8t9t10
Figure 1 — the divider at N = 4, extracted from simulation. Columns are FABRIC CLOCK CYCLES; the values shown are what each register holds entering that edge. The counter walks 0 to 3 and wraps, giving four cycles per lap. The tick appears one cycle after the counter shows its terminal value because baud_tick_o is registered — a latency paid once at start-up, not per interval: successive ticks are exactly four cycles apart.

3. Counter Width

Derived from the parameters, never written as a convenient [31:0]:

Nmax value (N−1)$clog2(N)register holdsnote
100—$clog2(1) is 0; a zero-width vector is illegal — guard to 1
4320–3exact power of two
43443390–511
868867100–1023
10,41710,416140–16,383
65,53665,535160–65,535exact power of two
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------------
//  8.2 — integer divider, production form: runtime-programmable divisor with
//  a safe update policy. Chapter 4.3 published the elaboration-only version.
// ---------------------------------------------------------------------------
module uart_baud_tick_int #(
    parameter int unsigned CLK_HZ  = 100_000_000,
    parameter int unsigned BAUD_HZ = 115_200,
    // Widest runtime divisor the counter must hold. Derived by the integrator
    // from the slowest rate required, not guessed.
    parameter int unsigned DIV_MAX = 65_536
) (
    input  logic clk,
    input  logic rst_n,
    input  logic                    cfg_we_i,      // load a new divisor
    input  logic [$clog2(DIV_MAX)-1:0] cfg_div_i,  // N, not N-1
    output logic                    cfg_err_o,     // latched: illegal divisor
    output logic                    baud_tick_o
);
    localparam int unsigned CNT_W = (DIV_MAX <= 1) ? 1 : $clog2(DIV_MAX);

    // Elaboration-time default, using the nearest policy of Chapter 4.3 §1.
    localparam int unsigned N_DEF = (2*CLK_HZ + BAUD_HZ) / (2*BAUD_HZ);

    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 < 1)
            $fatal(1, "uart_baud_tick_int: derived divisor %0d is below 1", N_DEF);
        if (N_DEF > DIV_MAX)
            $fatal(1, "uart_baud_tick_int: divisor %0d exceeds DIV_MAX %0d", N_DEF, DIV_MAX);
    end

    logic [CNT_W-1:0] cnt_q;
    logic [CNT_W-1:0] div_q;      // ACTIVE divisor
    logic [CNT_W-1:0] div_shadow_q;
    logic             pend_q;

    // A divisor of 0 is illegal: N-1 would underflow and the counter would
    // never reach its terminal value. Rejected, and the old value retained.
    wire cfg_bad = cfg_we_i && (cfg_div_i == '0);

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt_q        <= '0;
            div_q        <= CNT_W'(N_DEF);
            div_shadow_q <= CNT_W'(N_DEF);
            pend_q       <= 1'b0;
            cfg_err_o    <= 1'b0;
            baud_tick_o  <= 1'b0;
        end else begin
            baud_tick_o <= 1'b0;                   // default: one-cycle pulse

            if (cfg_bad) begin
                cfg_err_o <= 1'b1;                 // sticky until reset
            end else if (cfg_we_i) begin
                // SHADOWED: the new divisor takes effect at the next interval
                // boundary, so the interval in progress keeps its length.
                div_shadow_q <= cfg_div_i;
                pend_q       <= 1'b1;
            end

            if (cnt_q >= div_q - 1) begin
                cnt_q       <= '0;
                baud_tick_o <= 1'b1;
                if (pend_q) begin
                    div_q  <= div_shadow_q;        // commit, on a boundary only
                    pend_q <= 1'b0;
                end
            end else begin
                cnt_q <= cnt_q + 1'b1;
            end
        end
    end
endmodule

4. Safe Reconfiguration and the Terminal Test

Six details are deliberate.

cnt_q >= div_q - 1, not == — and the reason is not the obvious one. The intuition is that a divisor written below the counter's current value would never satisfy an equality test. That intuition is wrong for this design, and it was worth checking rather than asserting: because §4's commit zeroes the counter on the very edge the divisor changes, the counter can never be left above the new terminal value, and == behaves identically. Simulation of both forms against this block confirms it — both produce 100-cycle intervals after a change to divisor 100.

The inequality is defence in depth, and its value is measurable the moment the commit policy is bypassed. With the divisor applied immediately instead of shadowed, writing 100 while the counter held 600:

terminal testfirst interval after the writesteady state
>=602 cycles — recovers at once100
==65,636 cycles — 2^CNT_W + N100

The == form recovers, but only after the counter has run all the way to 2^16 and wrapped. Two independent mechanisms are protecting the same failure, and knowing which one is actually load-bearing matters: remove the shadow and the inequality saves the design; remove the inequality and the shadow saves it. Removing both is the defect.

cfg_err_o is sticky until reset. A rejected write is a programming error, and the natural consumer is software reading a status bit some time later. A one-cycle pulse would be unobservable to it. Chapter 8.5 classifies the error kinds and revisits the lifetime.

A divisor of zero is rejected, not clamped. Clamping to 1 would silently run the link at 100 Mbaud; retaining the previous divisor keeps the link working while the error bit reports the write. Neither is universally right, and the choice is documented rather than implied.

DIV_MAX sizes the counter, and elaboration checks the default fits. A default divisor larger than DIV_MAX is a parameter error caught at elaboration rather than a silently truncated counter.

5. Why the Tick Is Registered

baud_tick_o is registered, giving the one-cycle start-up latency §1 recorded. A combinational tick would save that cycle and put a comparator output directly onto a high-fanout enable; registering it keeps the enable clean and its timing trivial.

The default assignment baud_tick_o <= 1'b0 comes first, so every path that does not explicitly set it produces a one-cycle pulse. This is the same pattern the receiver used for sample_now in Chapter 6.3.

An integer baud divider. A free-running counter increments every fabric clock cycle and is compared against the active divisor minus one. When the comparison is satisfied the counter is cleared and a registered tick output is pulsed for one cycle. Separately, a configuration write path validates the incoming divisor, rejecting zero and latching a sticky error flag, and otherwise stores the value into a shadow register while setting a pending flag. The commit of the shadow register into the active divisor register is gated by the same terminal-count signal that emits the tick, so a new divisor can only take effect at an interval boundary and never in the middle of an interval.cnt_q0 .. N-1, every clkcompare>= div_q - 1terminalthe shared gatebaud_tick_oregistered, 1 cyclecfg_we_isoftware writesvalidatezero -> cfg_err_odiv_shadow_q+ pend_qdiv_qactive divisorcountmatchpulsedivisorif legalcommitdiv_q12
Figure 2 — the divider's structure. The counting path is a counter, a comparator against the active divisor and an incrementer; the configuration path is a shadow register and a pending flag whose commit is gated by the same terminal-count signal that emits the tick. That shared gate is what guarantees a divisor can only change on an interval boundary.

6. The Same Divider in Three Languages

§3's uart_baud_tick_int is the production block: runtime-programmable, shadowed, error-reporting. Underneath it is a smaller thing — a counter, a comparator and a registered pulse — and that core is what has to be identical in every language before any of the configuration machinery is worth adding.

Here it is three times. Each was compiled and simulated; §7 reports what each measured.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
//===========================================================================
//  uart_baud_div_v — Synthesizable Verilog-2001
//
//  Integer baud divider. Counts DIV_N clocks and emits a ONE-CLOCK enable.
//  The terminal count is DIV_N-1, not DIV_N: a counter that runs 0..DIV_N-1
//  visits DIV_N distinct values, so comparing against DIV_N gives a period
//  of DIV_N+1 clocks. Chapter 8.2 quantifies what that costs.
//===========================================================================
module uart_baud_div_v #(
    parameter DIV_N = 868
) (
    input  wire clk,
    input  wire rst_n,
    output reg  tick_o
);
    // Verilog-2001 has no $clog2, so the width comes from a constant function.
    // Guarded at 1 because a zero-width vector is illegal and DIV_N may be 1.
    function integer clog2;
        input integer value;
        integer i;
        begin
            clog2 = 1;
            for (i = value - 1; i > 1; i = i >> 1) clog2 = clog2 + 1;
        end
    endfunction

    localparam CNT_W = (DIV_N <= 1) ? 1 : clog2(DIV_N);

    reg [CNT_W-1:0] cnt_q;

    // The terminal comparison, named once so the off-by-one has one home.
    wire at_terminal = (cnt_q == (DIV_N[CNT_W-1:0] - 1'b1));

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt_q  <= {CNT_W{1'b0}};
            tick_o <= 1'b0;                  // never a tick during reset
        end else begin
            if (at_terminal) begin
                cnt_q  <= {CNT_W{1'b0}};
                tick_o <= 1'b1;              // registered: exactly one clock
            end else begin
                cnt_q  <= cnt_q + 1'b1;
                tick_o <= 1'b0;
            end
        end
    end
endmodule

Verilog-2001 has no $clog2, so the width comes from a constant function. That is the only structural difference from the SystemVerilog, and it is worth noticing that it costs eight lines rather than a design change.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
//===========================================================================
//  uart_baud_div_sv — Synthesizable SystemVerilog
//
//  An integer clock-enable generator. Emits one tick_o pulse, one clock
//  wide, every DIV_N clocks of clk.
//
//  The off-by-one lives in one place: the terminal value is DIV_N-1, not
//  DIV_N, because the counter includes zero. See chapter 8.2 section 5.
//===========================================================================
module uart_baud_div_sv #(
    parameter int DIV_N = 868
) (
    input  logic clk,
    input  logic rst_n,
    output logic tick_o
);
    localparam int CNT_W = (DIV_N <= 1) ? 1 : $clog2(DIV_N);

    logic [CNT_W-1:0] cnt_q;

    // DIV_N-1 is computed once, at elaboration, in a full-width int.
    localparam logic [CNT_W-1:0] TERMINAL = CNT_W'(DIV_N - 1);

    wire at_terminal = (cnt_q == TERMINAL);

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt_q  <= '0;
            tick_o <= 1'b0;
        end else if (at_terminal) begin
            cnt_q  <= '0;
            tick_o <= 1'b1;      // registered: exactly one clock wide
        end else begin
            cnt_q  <= cnt_q + 1'b1;
            tick_o <= 1'b0;
        end
    end

    // DIV_N = 1 must still be legal: TERMINAL is 0, so every clock is a tick.
    initial begin
        if (DIV_N < 1)
            $fatal(1, "uart_baud_div_sv: DIV_N must be >= 1, got %0d", DIV_N);
    end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
--==========================================================================
--  uart_baud_div -- Synthesizable VHDL-2008
--
--  An integer clock-enable generator. Emits one tick_o pulse, one clock
--  wide, every DIV_N clocks of clk.
--
--  The off-by-one lives in one place: the terminal value is DIV_N-1, not
--  DIV_N, because the counter includes zero. See chapter 8.2 section 5.
--==========================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;

entity uart_baud_div is
    generic (
        DIV_N : positive := 868
    );
    port (
        clk    : in  std_logic;
        rst_n  : in  std_logic;
        tick_o : out std_logic
    );
end entity uart_baud_div;

architecture rtl of uart_baud_div is

    -- ceil(log2(DIV_N)), floored at 1 so DIV_N = 1 still yields a legal
    -- vector. math_real is elaboration-time only; nothing real-valued
    -- survives into the netlist.
    function cnt_width (n : positive) return positive is
    begin
        if n <= 2 then
            return 1;
        else
            return positive(ceil(log2(real(n))));
        end if;
    end function;

    constant CNT_W    : positive := cnt_width(DIV_N);
    constant TERMINAL : unsigned(CNT_W-1 downto 0) :=
        to_unsigned(DIV_N - 1, CNT_W);

    signal cnt_q : unsigned(CNT_W-1 downto 0);

begin

    process (clk, rst_n)
    begin
        if rst_n = '0' then
            cnt_q  <= (others => '0');
            tick_o <= '0';
        elsif rising_edge(clk) then
            if cnt_q = TERMINAL then
                cnt_q  <= (others => '0');
                tick_o <= '1';          -- registered: exactly one clock wide
            else
                cnt_q  <= cnt_q + 1;
                tick_o <= '0';
            end if;
        end if;
    end process;

end architecture rtl;

VHDL computes the width from math_real, which is elaboration-only — nothing real-valued reaches the netlist. It also makes the terminal value a typed constant rather than an expression evaluated in the comparator, which is the one place where VHDL's verbosity buys something: TERMINAL is unsigned(CNT_W-1 downto 0) and a width mistake is an analysis error.

PropertyVerilog-2001SystemVerilogVHDL-2008Agree?
counter range0 … DIV_N−1samesameyes
terminal valueDIV_N-1DIV_N-1DIV_N-1yes
tickregistered, 1 clocksamesameyes
first tick after resetDIV_N clockssamesameyes
width sourceconstant function$clog2ceil(log2())yes — same value
DIV_N = 1legal, ticks every clocksamesameyes
parameter guardnone$fatal at elaborationassert at elaborationno — see 8.5 §5

One deliberate difference. Verilog-2001 has no elaboration-time assertion mechanism worth the name, so the guard simply is not there; SystemVerilog and VHDL both have one and both use it. Chapter 8.5 §5 is where that gap is closed, and where two of the three guards turned out to be broken.

7. What the Testbenches Measured

Each implementation has a self-checking testbench in its own language. All three follow the same rule: the checker observes clk, rst_n and tick_o and nothing else. It never reads cnt_q — §8 explains why that restriction is the point rather than a formality.

The divider matrix, run at six divisors simultaneously over 200,000 fabric clocks:

What is checked12358688681
first tick produced by the DIV_N-th edge after reset release✓✓✓✓✓✓
every interval exactly DIV_N (min = max, no band)✓✓✓✓✓✓
pulse exactly one clock widen/a✓✓✓✓✓
exact tick count, by integer arithmetic✓✓✓✓✓✓
no tick while reset is asserted✓✓✓✓✓✓
latency repeats after a mid-run reset————✓—

The DIV_N = 1 column has a deliberate n/a. A divide-by-one is a permanently asserted enable, so "one clock wide" is meaningless for it. The testbench replaces that check with "a tick in every cycle" rather than quietly skipping the case.

The exact-count row is the strongest check in the chapter, and it is worth saying why it beats a tolerance band. The testbench predicts the tick count as integer arithmetic — floor((clk_cnt - 1) / DIV_N) — and compares. A rate that drifts by one clock every thousand intervals still passes an averaging check and fails this one.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ iverilog -g2001 -o sim_div_v uart_baud_div_v.v tick_probe_v.v tb_uart_baud_div_v.v
$ ./sim_div_v
== uart_baud_div_v : self-checking Verilog testbench ==
  PASS reset: no ticks while rst_n low (0)
  PASS DIV_N=1    first tick at edge N (1)
  PASS DIV_N=2    first tick at edge N (2)
  PASS DIV_N=3    first tick at edge N (3)
  PASS DIV_N=5    first tick at edge N (5)
  PASS DIV_N=868  first tick at edge N (868)
  PASS DIV_N=8681 first tick at edge N (8681)
  PASS DIV_N=868  every interval exactly N
  PASS DIV_N=868  pulse one clock wide (0)
  PASS DIV_N=1    a tick every cycle (legal)
  PASS DIV_N=868  exact tick count (230)
  PASS DIV_N=8681 exact tick count (23)
  PASS reset mid-run: ticks return low
  PASS after re-reset, latency repeats (868)
== 27 checks, 0 failures ==
   RESULT: ALL VERILOG DIVIDER TESTS PASSED

All three languages run 27 checks with 0 failures — the same 27, checking the same things. §9 prints all three and Chapter 8.5 §9 collects the module totals.

The trace, from the simulator

DIV_N = 5, printed by the trace generator rather than written by hand. Values are what each register holds after the edge on that clock:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
TRACE-A  uart_baud_div_v, DIV_N=5
  clock | cnt_q | tick_o
      1 |   1   |   0
      2 |   2   |   0
      3 |   3   |   0
      4 |   4   |   0        <- cnt_q reaches DIV_N-1 = 4 here
      5 |   0   |   1        <- tick, and the counter has already wrapped
      6 |   1   |   0
      7 |   2   |   0
      8 |   3   |   0
      9 |   4   |   0
     10 |   0   |   1        <- spacing 5 = DIV_N, exactly
     11 |   1   |   0
     12 |   2   |   0

Read clocks 4 and 5 together — that pair is §2's whole argument. The comparison that fires is against 4, which is DIV_N-1; by the time the tick is observable the counter already shows 0. A reader debugging this on a waveform sees the tick next to a zero and reasonably concludes the terminal value is 0, which it is not — it is the value the counter held on the previous edge. That misreading is how the comparison gets "corrected" to == DIV_N and every interval becomes one cycle too long.

8. The Independent Checker

Every testbench in Module 8 is built on one small component, and it is worth reading before the testbenches that use it because it is the thing that makes the verification trustworthy.

tick_probe has exactly three inputs: clk, rst_n and one tick. It cannot see cnt_q, phase_q or any other internal. That restriction is not tidiness — it is the difference between a checker that can catch a defect and one that cannot. A checker that reads the counter and asserts "tick when cnt_q == DIV_N-1" is re-stating the design's own logic and will agree with it however wrong it is.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
//===========================================================================
//  tick_probe — an INDEPENDENT observer of a tick stream.
//
//  It is the oracle the three divider/NCO/timing testbenches share, and it
//  is deliberately ignorant: its only inputs are clk, rst_n and one tick.
//  It never sees a counter, an accumulator or a state bit, so it cannot be
//  fooled by a design that is internally self-consistent and externally
//  wrong.
//
//  Measurement convention (this is the whole off-by-one, one level up):
//  edges after reset release are numbered 1, 2, 3 ...; a tick sampled at
//  edge k was ASSERTED during the cycle ending at k, so the cycle index is
//  read BEFORE it is advanced. Advancing first reports every latency one
//  clock too late.
//===========================================================================
module tick_probe (
    input wire clk,
    input wire rst_n,
    input wire tick
);
    integer clk_cnt;      // cycle index since reset release
    integer n_ticks;      // ticks observed
    integer last_clk;     // cycle index of the previous tick
    integer first_lat;    // cycle index of the first tick
    integer min_iv;       // shortest interval seen
    integer max_iv;       // longest interval seen
    real    sum_iv;       // for the mean rate
    integer wide_pulse;   // ticks asserted on two consecutive cycles
    reg     prev_tick;
    integer iv;

    task clear;
        begin
            clk_cnt = 0; n_ticks = 0; last_clk = 0; first_lat = 0;
            min_iv = 1000000000; max_iv = 0; sum_iv = 0.0;
            wide_pulse = 0; prev_tick = 1'b0;
        end
    endtask

    initial clear;

    always @(posedge clk) begin
        if (!rst_n) begin
            clear;
        end else begin
            if (tick === 1'b1) begin
                if (prev_tick === 1'b1) wide_pulse = wide_pulse + 1;
                n_ticks = n_ticks + 1;
                if (n_ticks == 1) begin
                    first_lat = clk_cnt;
                end else begin
                    iv = clk_cnt - last_clk;
                    if (iv < min_iv) min_iv = iv;
                    if (iv > max_iv) max_iv = iv;
                    sum_iv = sum_iv + iv;
                end
                last_clk = clk_cnt;
            end
            prev_tick = tick;
            clk_cnt   = clk_cnt + 1;    // AFTER sampling, never before
        end
    end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
//===========================================================================
//  tick_probe_sv — the same independent observer, written as SystemVerilog.
//
//  Its inputs are clk, rst_n and one tick. Nothing else. A checker that can
//  see a counter can be fooled by a design that is internally consistent
//  and externally wrong; this one cannot.
//===========================================================================
module tick_probe_sv (input logic clk, input logic rst_n, input logic tick);

    int unsigned clk_cnt, n_ticks, last_clk, first_lat;
    int unsigned min_iv, max_iv, wide_pulse;
    real         sum_iv;
    logic        prev_tick;
    int unsigned iv;

    function automatic void clear();
        clk_cnt = 0; n_ticks = 0; last_clk = 0; first_lat = 0;
        min_iv = 32'hFFFF_FFFF; max_iv = 0; sum_iv = 0.0;
        wide_pulse = 0; prev_tick = 1'b0;
    endfunction

    initial clear();

    always @(posedge clk) begin
        if (!rst_n) clear();
        else begin
            // Sample the cycle that is ending, THEN advance the index.
            if (tick === 1'b1) begin
                if (prev_tick === 1'b1) wide_pulse++;
                n_ticks++;
                if (n_ticks == 1) first_lat = clk_cnt;
                else begin
                    iv = clk_cnt - last_clk;
                    if (iv < min_iv) min_iv = iv;
                    if (iv > max_iv) max_iv = iv;
                    sum_iv += real'(iv);
                end
                last_clk = clk_cnt;
            end
            prev_tick = tick;
            clk_cnt++;
        end
    end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
--==========================================================================
--  tick_probe -- the same independent observer, written as VHDL-2008.
--
--  It publishes its measurements on PORTS rather than hiding them in the
--  architecture, so the checking process reads them as ordinary signals.
--  VHDL has no hierarchical name lookup into an instance the way Verilog
--  does, and ports are the honest way to expose a measurement rather than
--  reaching for an external name.
--
--  Its inputs are clk, rst_n and one tick. Nothing else.
--==========================================================================
library ieee;
use ieee.std_logic_1164.all;

entity tick_probe is
    port (
        clk         : in  std_logic;
        rst_n       : in  std_logic;
        tick        : in  std_logic;
        clk_cnt_o   : out natural;
        n_ticks_o   : out natural;
        first_lat_o : out natural;
        min_iv_o    : out natural;
        max_iv_o    : out natural;
        wide_o      : out natural;
        sum_iv_o    : out real
    );
end entity tick_probe;

architecture bhv of tick_probe is
begin
    process (clk)
        variable clk_cnt   : natural   := 0;
        variable n_ticks   : natural   := 0;
        variable last_clk  : natural   := 0;
        variable first_lat : natural   := 0;
        variable min_iv    : natural   := natural'high;
        variable max_iv    : natural   := 0;
        variable wide      : natural   := 0;
        variable sum_iv    : real      := 0.0;
        variable prev_tick : std_logic := '0';
        variable iv        : natural;
    begin
        if rising_edge(clk) then
            if rst_n = '0' then
                clk_cnt := 0; n_ticks := 0; last_clk := 0; first_lat := 0;
                min_iv := natural'high; max_iv := 0; wide := 0;
                sum_iv := 0.0; prev_tick := '0';
            else
                -- Sample the cycle that is ending, THEN advance the index.
                if tick = '1' then
                    if prev_tick = '1' then
                        wide := wide + 1;
                    end if;
                    n_ticks := n_ticks + 1;
                    if n_ticks = 1 then
                        first_lat := clk_cnt;
                    else
                        iv := clk_cnt - last_clk;
                        if iv < min_iv then min_iv := iv; end if;
                        if iv > max_iv then max_iv := iv; end if;
                        sum_iv := sum_iv + real(iv);
                    end if;
                    last_clk := clk_cnt;
                end if;
                prev_tick := tick;
                clk_cnt   := clk_cnt + 1;
            end if;

            clk_cnt_o   <= clk_cnt;
            n_ticks_o   <= n_ticks;
            first_lat_o <= first_lat;
            min_iv_o    <= min_iv;
            max_iv_o    <= max_iv;
            wide_o      <= wide;
            sum_iv_o    <= sum_iv;
        end if;
    end process;
end architecture bhv;

The VHDL version publishes its measurements on ports, which is the one structural difference between the three. Verilog and SystemVerilog let a testbench reach into an instance by hierarchical name; VHDL does not have that habit, and external names are a heavier tool than this needs. Ports are the honest answer, and they make the measurement part of the component's contract rather than an implementation detail a testbench happens to know about.

9. The Divider Testbench, in Three Languages

Each is self-contained: it instantiates six dividers, attaches a probe to each, and reports PASS/FAIL with totals. Each carries a mandatory watchdog, which is what turns a generator that has stopped ticking into a reported failure instead of a regression that never returns.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
//===========================================================================
//  tb_uart_baud_div_v — self-checking Verilog-2001 testbench for the
//  integer divider.
//
//  Independence: every check below is computed from clk, rst_n and tick_o
//  alone, through tick_probe. cnt_q is never read.
//
//  Mandatory timeout: the watchdog turns a silent generator into a reported
//  failure instead of a hung regression.
//===========================================================================
`timescale 1ns/1ps

module tb_uart_baud_div_v;

    localparam RUN = 200000;        // fabric clocks of steady-state running

    reg clk = 1'b0;
    reg rst_n = 1'b0;
    always #5 clk = ~clk;           // 100 MHz

    integer checks = 0;
    integer failures = 0;

    task check_int;
        input integer got;
        input integer want;
        input [8*52-1:0] name;
        begin
            checks = checks + 1;
            if (got === want) $display("  PASS %0s (%0d)", name, got);
            else begin
                failures = failures + 1;
                $display("  FAIL %0s: got %0d, want %0d", name, got, want);
            end
        end
    endtask

    task check;
        input cond;
        input [8*52-1:0] name;
        begin
            checks = checks + 1;
            if (cond) $display("  PASS %0s", name);
            else begin failures = failures + 1; $display("  FAIL %0s", name); end
        end
    endtask

    // Six divisors at once: the two degenerate ones, two small, the real
    // 115200-at-100MHz divisor, and one an order of magnitude larger.
    wire t1, t2, t3, t5, t868, t8681;

    uart_baud_div_v #(.DIV_N(1))    d1   (.clk(clk), .rst_n(rst_n), .tick_o(t1));
    uart_baud_div_v #(.DIV_N(2))    d2   (.clk(clk), .rst_n(rst_n), .tick_o(t2));
    uart_baud_div_v #(.DIV_N(3))    d3   (.clk(clk), .rst_n(rst_n), .tick_o(t3));
    uart_baud_div_v #(.DIV_N(5))    d5   (.clk(clk), .rst_n(rst_n), .tick_o(t5));
    uart_baud_div_v #(.DIV_N(868))  d868 (.clk(clk), .rst_n(rst_n), .tick_o(t868));
    uart_baud_div_v #(.DIV_N(8681)) d8681(.clk(clk), .rst_n(rst_n), .tick_o(t8681));

    tick_probe p1   (.clk(clk), .rst_n(rst_n), .tick(t1));
    tick_probe p2   (.clk(clk), .rst_n(rst_n), .tick(t2));
    tick_probe p3   (.clk(clk), .rst_n(rst_n), .tick(t3));
    tick_probe p5   (.clk(clk), .rst_n(rst_n), .tick(t5));
    tick_probe p868 (.clk(clk), .rst_n(rst_n), .tick(t868));
    tick_probe p8681(.clk(clk), .rst_n(rst_n), .tick(t8681));

    // Reset contract: no generator may emit a tick while held in reset.
    integer tick_in_reset = 0;
    always @(posedge clk)
        if (!rst_n && (t1 || t2 || t3 || t5 || t868 || t8681))
            tick_in_reset = tick_in_reset + 1;

    initial begin
        #20_000_000;
        $display("  FAIL watchdog: simulation did not finish");
        $display("== %0d checks, %0d failures ==", checks+1, failures+1);
        $display("   RESULT: VERILOG DIVIDER TESTS FAILED (timeout)");
        $finish;
    end

    initial begin
        $display("== uart_baud_div_v : self-checking Verilog testbench ==");

        rst_n = 1'b0;
        repeat (20) @(posedge clk);
        check_int(tick_in_reset, 0, "reset: no ticks while rst_n low");

        @(negedge clk) rst_n = 1'b1;
        repeat (RUN) @(posedge clk);

        // --- first-tick latency: the DIV_N-th edge produces the tick ------
        check_int(p1.first_lat,    1,    "DIV_N=1    first tick at edge N");
        check_int(p2.first_lat,    2,    "DIV_N=2    first tick at edge N");
        check_int(p3.first_lat,    3,    "DIV_N=3    first tick at edge N");
        check_int(p5.first_lat,    5,    "DIV_N=5    first tick at edge N");
        check_int(p868.first_lat,  868,  "DIV_N=868  first tick at edge N");
        check_int(p8681.first_lat, 8681, "DIV_N=8681 first tick at edge N");

        // --- the defining property: min == max == DIV_N. No tolerance. ----
        check(p1.min_iv   ==1    && p1.max_iv   ==1,    "DIV_N=1    every interval exactly N");
        check(p2.min_iv   ==2    && p2.max_iv   ==2,    "DIV_N=2    every interval exactly N");
        check(p3.min_iv   ==3    && p3.max_iv   ==3,    "DIV_N=3    every interval exactly N");
        check(p5.min_iv   ==5    && p5.max_iv   ==5,    "DIV_N=5    every interval exactly N");
        check(p868.min_iv ==868  && p868.max_iv ==868,  "DIV_N=868  every interval exactly N");
        check(p8681.min_iv==8681 && p8681.max_iv==8681, "DIV_N=8681 every interval exactly N");

        // --- pulse width. DIV_N=1 is the documented exception. ------------
        check_int(p2.wide_pulse,    0, "DIV_N=2    pulse one clock wide");
        check_int(p3.wide_pulse,    0, "DIV_N=3    pulse one clock wide");
        check_int(p5.wide_pulse,    0, "DIV_N=5    pulse one clock wide");
        check_int(p868.wide_pulse,  0, "DIV_N=868  pulse one clock wide");
        check_int(p8681.wide_pulse, 0, "DIV_N=8681 pulse one clock wide");
        check(p1.n_ticks == p1.clk_cnt - 1, "DIV_N=1    a tick every cycle (legal)");

        // --- exact counts, by integer arithmetic. A drift of one clock per
        //     thousand intervals passes an average and fails this.
        check_int(p2.n_ticks,    (p2.clk_cnt-1)/2,    "DIV_N=2    exact tick count");
        check_int(p3.n_ticks,    (p3.clk_cnt-1)/3,    "DIV_N=3    exact tick count");
        check_int(p5.n_ticks,    (p5.clk_cnt-1)/5,    "DIV_N=5    exact tick count");
        check_int(p868.n_ticks,  (p868.clk_cnt-1)/868,  "DIV_N=868  exact tick count");
        check_int(p8681.n_ticks, (p8681.clk_cnt-1)/8681,"DIV_N=8681 exact tick count");
        check(p868.n_ticks > 200, "DIV_N=868  hundreds of intervals observed");

        // --- reset mid-run, then the same latency contract again ----------
        @(negedge clk) rst_n = 1'b0;
        repeat (10) @(posedge clk);
        check(t868 === 1'b0 && t8681 === 1'b0, "reset mid-run: ticks return low");
        @(negedge clk) rst_n = 1'b1;
        repeat (900) @(posedge clk);
        check_int(p868.first_lat, 868, "after re-reset, latency repeats");

        $display("== %0d checks, %0d failures ==", checks, failures);
        if (failures == 0) $display("   RESULT: ALL VERILOG DIVIDER TESTS PASSED");
        else               $display("   RESULT: VERILOG DIVIDER TESTS FAILED");
        $finish;
    end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
//===========================================================================
//  tb_uart_baud_div_sv — self-checking SystemVerilog testbench for the
//  integer divider.
//
//  Same contract and same checks as the Verilog testbench, written the way
//  SystemVerilog lets you write them: the sweep is a generate loop and each
//  instance checks itself, because a generate-block array index must be an
//  elaboration-time constant in any tool.
//===========================================================================
`timescale 1ns/1ps

module tb_uart_baud_div_sv;

    localparam int RUN  = 200_000;
    localparam int NDIV = 6;

    // Icarus Verilog 13.0 supports neither packed nor unpacked array
    // parameters. In a tool that does, this is
    //   localparam int DIVS [NDIV] = '{1, 2, 3, 5, 868, 8681};
    function automatic int divs(input int g);
        case (g)
            0: divs = 1;  1: divs = 2;   2: divs = 3;
            3: divs = 5;  4: divs = 868; default: divs = 8681;
        endcase
    endfunction

    logic clk = 1'b0, rst_n = 1'b0;
    always #5ns clk = ~clk;                     // 100 MHz

    int unsigned checks = 0, failures = 0;

    task automatic check(input bit cond, input string name);
        checks++;
        if (cond) $display("  PASS %0s", name);
        else begin failures++; $display("  FAIL %0s", name); end
    endtask

    task automatic check_eq(input longint got, input longint want, input string name);
        checks++;
        if (got == want) $display("  PASS %0s (%0d)", name, got);
        else begin failures++; $display("  FAIL %0s: got %0d, want %0d", name, got, want); end
    endtask

    logic [NDIV-1:0] dtick;

    generate
        for (genvar g = 0; g < NDIV; g++) begin : g_div
            localparam int D = divs(g);

            uart_baud_div_sv #(.DIV_N(D)) u_dut (
                .clk(clk), .rst_n(rst_n), .tick_o(dtick[g]));

            tick_probe_sv u_probe (.clk(clk), .rst_n(rst_n), .tick(dtick[g]));

            task automatic run_checks();
                check_eq(u_probe.first_lat, D,
                         $sformatf("DIV_N=%0d first tick at edge N", D));
                check(u_probe.min_iv == D && u_probe.max_iv == D,
                      $sformatf("DIV_N=%0d every interval exactly N", D));
                if (D > 1)
                    check_eq(u_probe.wide_pulse, 0,
                             $sformatf("DIV_N=%0d pulse one clock wide", D));
                else
                    check(u_probe.n_ticks == u_probe.clk_cnt - 1,
                          "DIV_N=1 a tick every cycle (legal)");
                // Exact, by integer arithmetic. Ticks land on cycles
                // D, 2D, 3D ... and the last observable cycle is clk_cnt-1.
                check_eq(u_probe.n_ticks, (u_probe.clk_cnt - 1) / D,
                         $sformatf("DIV_N=%0d exact tick count", D));
            endtask
        end
    endgenerate

    int unsigned tick_in_reset = 0;
    always @(posedge clk) if (!rst_n && |dtick) tick_in_reset++;

    initial begin
        #20ms;
        $display("  FAIL watchdog: simulation did not finish");
        $display("== %0d checks, %0d failures ==", checks+1, failures+1);
        $display("   RESULT: SYSTEMVERILOG DIVIDER TESTS FAILED (timeout)");
        $finish;
    end

    initial begin
        $display("== uart_baud_div_sv : self-checking SystemVerilog testbench ==");

        rst_n = 1'b0;
        repeat (20) @(posedge clk);
        check_eq(tick_in_reset, 0, "reset: no ticks while rst_n low");

        @(negedge clk) rst_n = 1'b1;
        repeat (RUN) @(posedge clk);

        g_div[0].run_checks();  g_div[1].run_checks();  g_div[2].run_checks();
        g_div[3].run_checks();  g_div[4].run_checks();  g_div[5].run_checks();

        @(negedge clk) rst_n = 1'b0;
        repeat (10) @(posedge clk);
        check(dtick == '0, "reset mid-run: all ticks return low");
        @(negedge clk) rst_n = 1'b1;
        repeat (900) @(posedge clk);
        check_eq(g_div[4].u_probe.first_lat, 868, "after re-reset, latency repeats");

        $display("== %0d checks, %0d failures ==", checks, failures);
        if (failures == 0) $display("   RESULT: ALL SYSTEMVERILOG DIVIDER TESTS PASSED");
        else               $display("   RESULT: SYSTEMVERILOG DIVIDER TESTS FAILED");
        $finish;
    end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
--==========================================================================
--  tb_uart_baud_div -- self-checking VHDL-2008 testbench for the integer
--  divider.
--
--  Same contract and same checks as the Verilog and SystemVerilog
--  testbenches. The sweep table is a named array type, because VHDL will
--  not carry an anonymous one -- which is the tax; the refund is that a
--  range mistake is an analysis error rather than a silent truncation.
--
--  Mandatory timeout: an independent watchdog process ends the run.
--==========================================================================
library ieee;
use ieee.std_logic_1164.all;
use std.env.finish;

entity tb_uart_baud_div is
end entity tb_uart_baud_div;

architecture sim of tb_uart_baud_div is

    type int_vec is array (natural range <>) of integer;
    type nat_vec is array (natural range <>) of natural;
    type real_vec is array (natural range <>) of real;

    constant DIVS       : int_vec := (1, 2, 3, 5, 868, 8681);
    constant CLK_PERIOD : time    := 10 ns;          -- 100 MHz
    constant RUN        : natural := 200000;

    signal clk   : std_logic := '0';
    signal rst_n : std_logic := '0';
    signal done  : std_logic := '0';

    signal dtick   : std_logic_vector(DIVS'range);
    signal clk_cnt : nat_vec(DIVS'range);
    signal n_ticks : nat_vec(DIVS'range);
    signal first_l : nat_vec(DIVS'range);
    signal min_iv  : nat_vec(DIVS'range);
    signal max_iv  : nat_vec(DIVS'range);
    signal wide    : nat_vec(DIVS'range);
    signal sum_iv  : real_vec(DIVS'range);

    signal tick_in_reset : natural := 0;

begin

    clk <= not clk after CLK_PERIOD / 2 when done = '0' else '0';

    gen_div : for g in DIVS'range generate
        u_dut : entity work.uart_baud_div
            generic map (DIV_N => DIVS(g))
            port map (clk => clk, rst_n => rst_n, tick_o => dtick(g));

        u_probe : entity work.tick_probe
            port map (clk => clk, rst_n => rst_n, tick => dtick(g),
                      clk_cnt_o   => clk_cnt(g), n_ticks_o => n_ticks(g),
                      first_lat_o => first_l(g), min_iv_o  => min_iv(g),
                      max_iv_o    => max_iv(g),  wide_o    => wide(g),
                      sum_iv_o    => sum_iv(g));
    end generate gen_div;

    -- One process, one driver. nat_vec is unresolved, so a second driver
    -- would be an elaboration error rather than a mystery at runtime.
    rst_mon : process (clk)
        variable n : natural := 0;
    begin
        if rising_edge(clk) and rst_n = '0' then
            if dtick /= (dtick'range => '0') then
                n := n + 1;
            end if;
            tick_in_reset <= n;
        end if;
    end process rst_mon;

    watchdog : process
    begin
        wait for 20 ms;
        report "watchdog: simulation did not finish" severity error;
        report "   RESULT: VHDL DIVIDER TESTS FAILED (timeout)" severity failure;
        wait;
    end process watchdog;

    stim : process
        variable checks   : natural := 0;
        variable failures : natural := 0;

        procedure check(cond : boolean; name : string) is
        begin
            checks := checks + 1;
            if cond then
                report "  PASS " & name;
            else
                failures := failures + 1;
                report "  FAIL " & name severity error;
            end if;
        end procedure;

        procedure check_eq(got, want : natural; name : string) is
        begin
            checks := checks + 1;
            if got = want then
                report "  PASS " & name & " (" & integer'image(got) & ")";
            else
                failures := failures + 1;
                report "  FAIL " & name & ": got " & integer'image(got) &
                       ", want " & integer'image(want) severity error;
            end if;
        end procedure;
    begin
        report "== uart_baud_div (VHDL) : self-checking testbench ==";

        rst_n <= '0';
        for i in 0 to 19 loop
            wait until rising_edge(clk);
        end loop;
        wait for 1 ns;
        check_eq(tick_in_reset, 0, "reset: no ticks while rst_n low");

        rst_n <= '1';
        for i in 0 to RUN - 1 loop
            wait until rising_edge(clk);
        end loop;
        wait for 1 ns;

        for g in DIVS'range loop
            check_eq(first_l(g), DIVS(g),
                     "DIV_N=" & integer'image(DIVS(g)) & " first tick at edge N");
            check(min_iv(g) = DIVS(g) and max_iv(g) = DIVS(g),
                  "DIV_N=" & integer'image(DIVS(g)) & " every interval exactly N");
            if DIVS(g) > 1 then
                check_eq(wide(g), 0,
                         "DIV_N=" & integer'image(DIVS(g)) & " pulse one clock wide");
            else
                check(n_ticks(g) = clk_cnt(g) - 1,
                      "DIV_N=1 a tick every cycle (legal)");
            end if;
            check_eq(n_ticks(g), (clk_cnt(g) - 1) / DIVS(g),
                     "DIV_N=" & integer'image(DIVS(g)) & " exact tick count");
        end loop;

        rst_n <= '0';
        for i in 0 to 9 loop
            wait until rising_edge(clk);
        end loop;
        wait for 1 ns;
        check(dtick = (dtick'range => '0'), "reset mid-run: all ticks return low");

        rst_n <= '1';
        for i in 0 to 899 loop
            wait until rising_edge(clk);
        end loop;
        wait for 1 ns;
        check_eq(first_l(4), 868, "after re-reset, latency repeats");

        report "== " & integer'image(checks) & " checks, " &
               integer'image(failures) & " failures ==";
        if failures = 0 then
            report "   RESULT: ALL VHDL DIVIDER TESTS PASSED";
        else
            report "   RESULT: VHDL DIVIDER TESTS FAILED" severity error;
        end if;

        done <= '1';
        finish;
    end process stim;

end architecture sim;

The three differ in how they sweep, not in what they check. Verilog writes six instances out; SystemVerilog uses a generate loop where each instance carries its own checking task, because a generate-block array index must be an elaboration-time constant in any tool — a runtime loop variable is not one, and trying it is an error rather than an Icarus limitation; VHDL uses a generate over a typed constant array and reads the probes' output ports.

Verilog-2001SystemVerilogVHDL-2008
sweepsix explicit instancesgenerate + per-instance taskgenerate over int_vec
reading a measurementhierarchical namehierarchical nameoutput ports
watchdoginitial + #initial + #separate process
checks run272727
failures000

Proving these checks can fail

Three mutations of the divider, each run against the unmodified testbench above, whose clean baseline is 27 checks / 0 failures:

MutationWhat it breaksFailures
terminal count uses DIV_N, not DIV_N-1§2's off-by-one18
tick held two clocks wide8.1 §1's pulse contract15
counter resets to all-ones, not 0§1's reset contract9

The first row is the defect this chapter exists to prevent, and 18 of 27 checks catch it. That is what a contract with no tolerance band buys: the wrong divisor is not a small error to be detected statistically, it is a different number, and every exact check sees it at once.

10. Verification

The integer divider is the one block in this module with no tolerance. Every steady-state interval must be exactly N fabric clocks. Not approximately, not on average — a divider whose intervals vary by a cycle is broken, and the test should say so:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
integer  : 3456 intervals, all exactly 868 clks

That line is from simulation of the block above, over three million fabric clocks. The check is min == max == N, with no band.

Measure from the wire, not the counter. Counting cycles between successive baud_tick_o pulses tests the contract. Inspecting cnt_q tests an implementation detail and will pass on a design whose tick logic is wrong.

Test DIVISOR = 1 explicitly, because it is the corner where the width guard and the terminal test meet. With N = 1 the terminal condition cnt_q >= 0 is true on every cycle, so the tick is asserted continuously — which is the correct meaning of one tick per cycle and is verified in simulation.

Test reconfiguration at a deliberately awkward moment, mid-interval, and assert that the interval in progress completes at its old length and the next uses the new one. A test that reconfigures while idle cannot fail §4's defect.

Test the rejected write, and assert both that cfg_err_o sets and that the previous divisor is still in use. Confirmed in simulation: after a rejected write of zero, intervals stayed at 434.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Assertion — the tick is exactly one cycle wide.
property p_tick_one_cycle;
    @(posedge clk) disable iff (!rst_n)
        baud_tick_o |=> !baud_tick_o;
endproperty
assert property (p_tick_one_cycle);

// Assertion — the counter never exceeds the active divisor's terminal value.
// Stated as a contract because the register is wide enough to violate it.
property p_counter_in_range;
    @(posedge clk) disable iff (!rst_n)
        cnt_q <= div_q - 1;
endproperty
assert property (p_counter_in_range);

// Assertion — a commit happens only on a tick, never mid-interval.
property p_commit_on_boundary_only;
    @(posedge clk) disable iff (!rst_n)
        $changed(div_q) |-> $past(baud_tick_o) || $past(!rst_n);
endproperty
assert property (p_commit_on_boundary_only);

The third is §4's policy stated as a property, and it is the one that fails on the naive immediate-apply implementation.

11. What This Means on an FPGA

The block is a counter, a comparator and an incrementer. At DIV_MAX = 65,536 that is 16 flip-flops for the counter, 16 more for each of the active and shadow divisors, and a 16-bit comparator — under sixty flip-flops and a small amount of carry logic. Nothing here is worth optimising.

A fixed divisor folds away entirely. If the divisor is a parameter rather than an input, synthesis replaces the comparator with a constant compare and often shrinks it substantially. That is the resource argument for the elaboration-time form of Chapter 4.3, and Chapter 8.5 weighs it against runtime programmability properly.

Size DIV_MAX from the slowest rate, once. A design supporting 9,600 baud at 100 MHz needs a divisor of 10,417 and therefore 14 bits. Sizing for 115,200 only and discovering the limit when someone configures 9,600 is a recurring and entirely avoidable bug.

Probe baud_tick_o against a known interval. The measurement technique is Chapter 4.3 §6's: count many intervals rather than one, because the relative precision improves as 1/N and a 0.1% error is invisible on a single interval.

12. Debugging

13. Understanding Check

14. Summary

The counter walks 0 … N−1 and wraps, giving exactly N fabric cycles per bit interval. The terminal value is N−1; comparing against N visits one extra value and makes every interval one cycle too long — at 100 MHz and 115,200 baud that is +0.0064% becoming −0.1087%, seventeen times worse and opposite in sign, landing exactly on the ceiling divisor so the symptom reads as a rounding choice.

The tick is registered, so it is observable one cycle after the terminal value — a start-up latency paid once, with interval spacing still exactly N.

Width comes from DIV_MAX, not the default divisor, and DIV_MAX comes from the slowest supported rate. $clog2(1) is 0, so the degenerate case needs an explicit guard.

The >= terminal test is defence in depth, not the load-bearing safeguard. With the commit policy in place == behaves identically, because the counter is zeroed on the commit edge — verified in simulation. Bypass the commit and the difference appears: 602 cycles to recover with >=, 65,636 with ==.

Reconfiguration is shadowed and committed on a boundary, so the interval in progress always completes at its original length. Applying immediately truncates it and corrupts the frame in flight, with the failure correlating with configuration writes rather than with traffic.

A zero divisor is rejected and flagged, not clamped, and the error bit is sticky because its consumer is software reading later.

Verified in simulation: 3,456 intervals, all exactly 868 cycles, over three million fabric clocks — the one block in this module that gets no tolerance.

15. What Comes Next

An integer divider can only produce a rate of f_clk / N for integer N. Chapter 4.3 showed the residual is small at ordinary rates — 64 ppm for the 868 case — but it is systematic: every interval is the same wrong length, so the error never cancels.

Chapter 8.3 builds the alternative. A phase accumulator keeps the fractional part instead of discarding it, so the average rate converges on the target while individual intervals alternate between two lengths. It derives the increment equation, shows why the carry out of the accumulator is the timing event, and confronts the width question — including a case where a too-narrow accumulator is worse than the integer divider it replaced.

Browse the full path on the UART tutorials index. For the divisor arithmetic this chapter implements, read back to Chapter 4.3.

Continue learning

Where this fits

Part of the UART curriculum.