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.
| Question | Answer |
|---|---|
| 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:
| edge | cnt before | == N−1 (3)? | tick | cnt after | ‖ | cnt before | == N (4)? | tick | cnt after |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 0 | no | — | 1 | ‖ | 0 | no | — | 1 |
| 2 | 1 | no | — | 2 | ‖ | 1 | no | — | 2 |
| 3 | 2 | no | — | 3 | ‖ | 2 | no | — | 3 |
| 4 | 3 | yes | tick | 0 | ‖ | 3 | no | — | 4 |
| 5 | 0 | no | — | 1 | ‖ | 4 | yes | tick | 0 |
| 6 | 1 | no | — | 2 | ‖ | 0 | no | — | 1 |
| 7 | 2 | no | — | 3 | ‖ | 1 | no | — | 2 |
| 8 | 3 | yes | tick | 0 | ‖ | 2 | no | — | 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:
| divisor | actual baud | error | magnitude |
|---|---|---|---|
| 868 — correct | 115,207.3733 | +0.0064 % | 64 ppm |
| 869 — off-by-one | 115,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 cycles3. Counter Width
Derived from the parameters, never written as a convenient [31:0]:
N | max value (N−1) | $clog2(N) | register holds | note |
|---|---|---|---|---|
| 1 | 0 | 0 | — | $clog2(1) is 0; a zero-width vector is illegal — guard to 1 |
| 4 | 3 | 2 | 0–3 | exact power of two |
| 434 | 433 | 9 | 0–511 | |
| 868 | 867 | 10 | 0–1023 | |
| 10,417 | 10,416 | 14 | 0–16,383 | |
| 65,536 | 65,535 | 16 | 0–65,535 | exact power of two |
// ---------------------------------------------------------------------------
// 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
endmodule4. 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 test | first interval after the write | steady state |
|---|---|---|
>= | 602 cycles — recovers at once | 100 |
== | 65,636 cycles — 2^CNT_W + N | 100 |
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.
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.
//===========================================================================
// 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
endmoduleVerilog-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.
//===========================================================================
// 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--==========================================================================
-- 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.
| Property | Verilog-2001 | SystemVerilog | VHDL-2008 | Agree? |
|---|---|---|---|---|
| counter range | 0 … DIV_N−1 | same | same | yes |
| terminal value | DIV_N-1 | DIV_N-1 | DIV_N-1 | yes |
| tick | registered, 1 clock | same | same | yes |
| first tick after reset | DIV_N clocks | same | same | yes |
| width source | constant function | $clog2 | ceil(log2()) | yes — same value |
DIV_N = 1 | legal, ticks every clock | same | same | yes |
| parameter guard | none | $fatal at elaboration | assert at elaboration | no — 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 checked | 1 | 2 | 3 | 5 | 868 | 8681 |
|---|---|---|---|---|---|---|
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 wide | n/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.
$ 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 PASSEDAll 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:
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 | 0Read 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.
//===========================================================================
// 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//===========================================================================
// 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--==========================================================================
-- 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.
//===========================================================================
// 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//===========================================================================
// 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--==========================================================================
-- 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-2001 | SystemVerilog | VHDL-2008 | |
|---|---|---|---|
| sweep | six explicit instances | generate + per-instance task | generate over int_vec |
| reading a measurement | hierarchical name | hierarchical name | output ports |
| watchdog | initial + # | initial + # | separate process |
| checks run | 27 | 27 | 27 |
| failures | 0 | 0 | 0 |
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:
| Mutation | What it breaks | Failures |
|---|---|---|
terminal count uses DIV_N, not DIV_N-1 | §2's off-by-one | 18 |
| tick held two clocks wide | 8.1 §1's pulse contract | 15 |
| counter resets to all-ones, not 0 | §1's reset contract | 9 |
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:
integer : 3456 intervals, all exactly 868 clksThat 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.
// 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
Related tutorials
- 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.
- Related topic
Parity Generation, Checking and Error Detection
One interval, one XOR reduction, and a detection guarantee with a sharp edge: parity catches every corruption that flips an odd number of protected bits and provably misses every even-numbered one — demonstrated, not asserted.
- Related topic
Frame Configurations: 8N1 and the Configuration Space
8N1 names three of the four choices a UART link depends on and omits the one most likely to be wrong. Reading the shorthand, computing what each configuration costs in intervals and line time, and why a longer frame spends timing margin as well as throughput.
Where this fits
Part of the UART curriculum.
