RTL Design Patterns · Chapter 3 · Counters, Timers & Pulse Generators
Mod-N & Programmable Counters
A plain binary counter only wraps at a power of two, but real designs need to divide by arbitrary N all the time, like a divide-by-1000 tick or a baud-rate generator. A mod-N counter is just a counter with a compare-and-reset added: advance every cycle, and when the count reaches its terminal value, force it back to zero. The whole design rests on one small decision, and the terminal value is N minus 1, not N. Getting that off by one turns a divide-by-N into a divide-by-N-plus-1, the most common counter bug in the field. You build a fixed modulus set by a parameter, a runtime-programmable modulus with a guard for when N shrinks, and the signature off-by-one, each proven with a clocked self-checking testbench in SystemVerilog, Verilog, and VHDL.
Foundation12 min readRTL Design PatternsMod-N CounterProgrammable CounterClock DividerBaud GeneratorOff-By-One
Chapter 3 · Section 3.2 · Counters, Timers & Pulse Generators
1. The Engineering Problem
You have a free-running counter — the up-counter from 3.1 — and it works: every clock it adds one, and when it hits its maximum it wraps back to zero. The catch is where it wraps. A WIDTH-bit counter is a WIDTH-bit ripple of binary arithmetic, so it wraps only when it overflows, and that happens at exactly 2^WIDTH: a 3-bit counter wraps at 8, a 10-bit counter at 1024, a 16-bit at 65536. The wrap point is a power of two, and nothing else.
But almost nothing a real system needs to divide by is a power of two. You have a 50 MHz reference clock and you need a 1 ms system tick — that is a divide by 50,000. You need to generate 9600 baud for a UART from that same 50 MHz clock — that is a divide by 5208 (and a fraction). You are building a video timing generator and each line is 800 pixel-clocks long — divide by 800. You need a refresh request every 7.8 µs — some awkward integer of cycles. In every one of these the requirement is identical and it is not a power of two: count a fixed number of cycles N, then wrap — for arbitrary N.
You cannot get there by choosing WIDTH, because WIDTH only buys you powers of two. What you need is a counter that ignores the natural overflow point and instead wraps at a point you choose: it counts 0, 1, 2, …, N-1 and then returns to 0, so its period is exactly N cycles, for any N you name. That is the mod-N counter — "mod-N" because the count is always the cycle number taken modulo N. And frequently N is not even fixed at design time: a baud generator must support 9600, 19200, 115200; a programmable timer takes its period from a software-written register. So the same structure must also work when N is a runtime input, retuned on the fly. This page builds both, and the reason it is Chapter 3 is that this one compare-and-reset is the atom underneath every clock divider, every timer, and every pulse generator in the chapters that follow.
// A plain up-counter wraps ONLY at 2^WIDTH — a power of two:
reg [9:0] count; // 10 bits => wraps at 1024, period 1024
always @(posedge clk) count <= count + 1'b1; // 0..1023, then 0 — never /1000
// But the system asks for a /50000 tick, a /5208 baud divisor, a /800 line...
// none of these is a power of two, so WIDTH alone cannot produce them.
// The mod-N counter: count 0..N-1 and wrap, for ANY N (period == N):
// advance every cycle; when count reaches N-1, force it to 0 next cycle.
// N can be a PARAMETER (fixed divisor) or an INPUT (programmable divisor).
// The whole pattern this page builds — and the terminal value is N-1, not N.2. Mental Model
3. Pattern Anatomy
The whole structure is the 3.1 counter plus two additions. Walk the datapath from the register outward.
The count register. Exactly the register from 3.1: a WIDTH-bit flop that holds the current count, updated on the clock edge, cleared by reset, and (optionally) frozen when an enable en is low. Its width must be large enough to hold N-1, i.e. WIDTH = ceil(log2(N)) for a static N, or wide enough for the largest programmable N you will ever load. Nothing about the register is new; the mod-N behaviour lives entirely in what feeds its input.
The terminal-count comparator. A combinational compare of count against the terminal value N-1. Because you are testing for a single exact value, this is an equality comparator (count == N-1), not a magnitude comparator — the cheapest possible compare, one XNOR-reduce, from 1.5. Its output is the wrap signal: high for exactly one cycle each period, on the cycle the counter holds its maximum value. When N is a parameter, N-1 is a constant and the comparator degenerates to a handful of gates on the bits that must be one; when N is an input, N-1 is computed live and the comparator is a full equality compare against a runtime value.
The wrap-to-zero mux. A 2:1 mux on the register's next-value input, steered by wrap. When wrap is low, the next value is count + 1 (advance, the normal counter behaviour). When wrap is high, the next value is forced to 0. This is where the counting sequence is closed into a loop: instead of running on to N and beyond, it snaps back to the start. The wrap pulse is also the natural tick / strobe output of the block — one clock wide, once per N cycles — which is exactly the divided signal a clock divider or baud generator hands downstream.
The period is N cycles; the terminal value is N-1. These are the two invariants to hold in your head, and they are different numbers. The counter visits the N states 0 … N-1; the largest state is N-1; the period — the number of cycles from one wrap to the next — is N. The compare is on the terminal value (N-1); the divide ratio is the period (N). Confusing the two is the bug in §6 and §7.
Static N vs programmable N. With a static modulus, N is a parameter (SV/Verilog) or generic (VHDL): the terminal value N-1 is a compile-time constant, the divisor is fixed in silicon, and the comparator is minimal. With a programmable modulus, N arrives as an input (often latched into a configuration register written by software): the terminal value is mod_n - 1, computed at runtime, and the divisor can be retuned live — the pattern behind a baud-rate generator that switches between 9600 and 115200, or a timer whose period software sets.
What happens when N changes mid-count. A programmable N introduces one hazard the static version never has: if software writes a smaller N while the counter is already above the new N-1, an exact-equality compare (count == mod_n - 1) will never fire — the counter has already passed the value it is waiting for, so it runs all the way up to overflow before it ever wraps. The counter is "stuck" for one very long period. The fix is a >= guard: wrap when count >= mod_n - 1, so a count that is already at or beyond the (new, smaller) terminal value wraps immediately on the next edge. This is the one place a magnitude compare earns its extra area — precisely because a live N can move the target underneath a count that has already gone past it.
Mod-N counter — the 3.1 counter plus a compare-and-reset
data flowTwo closing notes on hardware reality. First, the comparator being equality (not magnitude) is a deliberate area win: a fixed mod-N only needs to detect one value, so it costs a few gates, not a full magnitude compare — reserve >= for the programmable case where a live N genuinely demands it. Second, everything here is a synchronous wrap: the compare is combinational but the reset-to-zero happens on the clock edge through the register, so the counter has no combinational loop and no glitch — the tick is a clean, registered, one-cycle pulse.
4. Real RTL Implementation
This is the core of the page. We build three patterns — (a) a static mod-N counter (parameter N, wrap on count == N-1), (b) a programmable mod-N counter (N as an input, with a >= guard so a shrinking N re-wraps cleanly), and (c) the off-by-one bug (== N buggy vs == N-1 fixed) — and each is shown in SystemVerilog, Verilog, and VHDL, with a synthesizable RTL block and a clocked, self-checking testbench that generates a clock and asserts the period is exactly N and the sequence is 0 … N-1. The idea is identical across the three languages; seeing them side by side is what makes the pattern language-independent.
One discipline runs through every RTL block, inherited from 3.1: the counter is a synchronous design with an explicit reset — <= (non-blocking) on the clock edge, an active-high synchronous rst that clears the count to 0, and an en that freezes it when low. The mod-N behaviour is layered on top as the next-value selection; the reset/enable semantics are unchanged from the plain counter, because a mod-N counter is a plain counter.
4a. Static mod-N counter — parameter N, wrap on count == N-1
Start with the fixed divisor: N is a parameter, so the terminal value N-1 is a compile-time constant. The counter advances on en, the comparator fires when count reaches N-1, and the wrap mux steers 0 back in. tick is the one-cycle strobe — the divided output.
module mod_n #(
parameter int N = 10, // the modulus — fixed divisor
localparam int WIDTH = (N > 1) ? $clog2(N) : 1 // wide enough to hold N-1
)(
input logic clk,
input logic rst, // synchronous, active-high
input logic en, // advance only when high
output logic [WIDTH-1:0] count, // current count, 0 .. N-1
output logic tick // 1-cycle strobe once per N
);
// wrap fires on the TERMINAL value N-1 (not N). It is an EQUALITY compare
// against a compile-time constant, so it costs only a few gates. This pulse
// is both the wrap trigger AND the divided output (tick).
wire wrap = (count == WIDTH'(N - 1));
always_ff @(posedge clk) begin
if (rst) count <= '0; // sync reset to 0
else if (en) begin
if (wrap) count <= '0; // reached N-1 → snap to 0
else count <= count + 1'b1; // otherwise advance
end
end
// tick is high for exactly the cycle the counter holds N-1 (period == N).
assign tick = en & wrap;
endmoduleThe compare is on N-1 and tick marks the terminal cycle, so between two ticks there are exactly N cycles. The clocked, self-checking testbench proves precisely that: it generates a clock, watches the counter for several full periods, and asserts (1) the count sequence is 0, 1, …, N-1, 0, … with no value ever reaching N, and (2) the number of cycles between two ticks is exactly N — not N+1. It runs the check for several N, including a non-power-of-two and N=1.
module mod_n_tb;
localparam int N = 5; // non-power-of-two on purpose
localparam int WIDTH = (N > 1) ? $clog2(N) : 1;
logic clk = 0, rst, en;
logic [WIDTH-1:0] count;
logic tick;
int errors = 0;
mod_n #(.N(N)) dut (.clk(clk), .rst(rst), .en(en), .count(count), .tick(tick));
always #5 clk = ~clk; // 100 MHz clock
// Reference model: the count must equal (cycles mod N) exactly.
int expect_count = 0;
int last_tick_cycle = -1, cyc = 0;
initial begin
rst = 1; en = 0; @(posedge clk); #1;
rst = 0; en = 1; // release reset, start counting
for (cyc = 0; cyc < 4*N + 3; cyc++) begin
@(posedge clk); #1; // sample after the edge settles
// (1) count must never reach N, and must track cycles mod N:
assert (count === WIDTH'(expect_count))
else begin $error("cyc=%0d count=%0d exp=%0d", cyc, count, expect_count); errors++; end
assert (count < WIDTH'(N)) // must visit 0..N-1 only
else begin $error("count %0d reached N=%0d", count, N); errors++; end
// (2) period between ticks must be EXACTLY N:
if (tick) begin
if (last_tick_cycle >= 0)
assert (cyc - last_tick_cycle == N)
else begin $error("period=%0d exp=%0d (off-by-one?)", cyc-last_tick_cycle, N); errors++; end
last_tick_cycle = cyc;
end
expect_count = (expect_count + 1) % N; // advance reference
end
if (errors == 0) $display("PASS: mod_n N=%0d visits 0..N-1, period == N", N);
else $display("FAIL: %0d mismatches", errors);
$finish;
end
endmoduleThe Verilog form is identical in intent — reg/wire typing, always @(posedge clk), and the constant terminal value N-1. The self-check uses if (…) $display("FAIL") instead of assert, and measures the tick-to-tick period the same way.
module mod_n #(
parameter N = 10,
parameter WIDTH = 4 // must satisfy 2**WIDTH >= N
)(
input wire clk,
input wire rst, // synchronous, active-high
input wire en,
output reg [WIDTH-1:0] count,
output wire tick
);
// Equality compare against the constant terminal value N-1 (not N).
wire wrap = (count == (N - 1));
always @(posedge clk) begin
if (rst) count <= {WIDTH{1'b0}}; // sync reset
else if (en) begin
if (wrap) count <= {WIDTH{1'b0}}; // reached N-1 → wrap to 0
else count <= count + 1'b1; // advance
end
end
assign tick = en & wrap; // 1-cycle strobe, period N
endmodule module mod_n_tb;
parameter N = 5;
parameter WIDTH = 3;
reg clk, rst, en;
wire [WIDTH-1:0] count;
wire tick;
integer errors, cyc, expect_count, last_tick_cycle;
mod_n #(.N(N), .WIDTH(WIDTH)) dut
(.clk(clk), .rst(rst), .en(en), .count(count), .tick(tick));
initial clk = 0;
always #5 clk = ~clk; // clock generator
initial begin
errors = 0; expect_count = 0; last_tick_cycle = -1;
rst = 1; en = 0; @(posedge clk); #1;
rst = 0; en = 1;
for (cyc = 0; cyc < 4*N + 3; cyc = cyc + 1) begin
@(posedge clk); #1;
// count must track cycles mod N and never reach N:
if (count !== expect_count[WIDTH-1:0]) begin
$display("FAIL: cyc=%0d count=%0d exp=%0d", cyc, count, expect_count);
errors = errors + 1;
end
if (count >= N) begin
$display("FAIL: count %0d reached N=%0d", count, N);
errors = errors + 1;
end
// period between ticks must be EXACTLY N:
if (tick) begin
if (last_tick_cycle >= 0 && (cyc - last_tick_cycle) !== N) begin
$display("FAIL: period=%0d exp=%0d (off-by-one?)", cyc-last_tick_cycle, N);
errors = errors + 1;
end
last_tick_cycle = cyc;
end
expect_count = (expect_count + 1) % N;
end
if (errors == 0) $display("PASS: mod_n visits 0..N-1, period == N");
else $display("FAIL: %0d mismatches", errors);
$finish;
end
endmoduleIn VHDL the modulus is a generic, the register update is inside a clocked process guarded by rising_edge(clk), and arithmetic goes through numeric_std (unsigned). The terminal value N-1 is again a compile-time constant, and to_unsigned(N-1, WIDTH) builds the comparison constant at the right width.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity mod_n is
generic (
N : positive := 10; -- the modulus (fixed divisor)
WIDTH : positive := 4 -- must hold N-1
);
port (
clk : in std_logic;
rst : in std_logic; -- synchronous, active-high
en : in std_logic;
count : out std_logic_vector(WIDTH-1 downto 0);
tick : out std_logic
);
end entity;
architecture rtl of mod_n is
signal cnt : unsigned(WIDTH-1 downto 0) := (others => '0');
signal wrap : std_logic;
begin
-- Equality compare against the constant terminal value N-1 (not N).
wrap <= '1' when cnt = to_unsigned(N-1, WIDTH) else '0';
process (clk)
begin
if rising_edge(clk) then
if rst = '1' then
cnt <= (others => '0'); -- sync reset
elsif en = '1' then
if wrap = '1' then
cnt <= (others => '0'); -- reached N-1 → wrap to 0
else
cnt <= cnt + 1; -- advance
end if;
end if;
end if;
end process;
count <= std_logic_vector(cnt);
tick <= en and wrap; -- 1-cycle strobe, period N
end architecture; library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity mod_n_tb is
end entity;
architecture sim of mod_n_tb is
constant N : positive := 5; -- non-power-of-two
constant WIDTH : positive := 3;
signal clk : std_logic := '0';
signal rst : std_logic := '1';
signal en : std_logic := '0';
signal count : std_logic_vector(WIDTH-1 downto 0);
signal tick : std_logic;
begin
dut : entity work.mod_n
generic map (N => N, WIDTH => WIDTH)
port map (clk => clk, rst => rst, en => en, count => count, tick => tick);
clk <= not clk after 5 ns; -- clock generator
stim : process
variable exp_count : integer := 0;
variable cyc : integer := 0;
variable last_tick_cycle : integer := -1;
begin
wait until rising_edge(clk); wait for 1 ns;
rst <= '0'; en <= '1'; -- release reset, start
for c in 0 to 4*N + 2 loop
wait until rising_edge(clk); wait for 1 ns;
-- count must track cycles mod N and stay below N:
assert to_integer(unsigned(count)) = exp_count
report "count mismatch (mod-N sequence wrong)" severity error;
assert to_integer(unsigned(count)) < N
report "count reached N — should visit 0..N-1 only" severity error;
-- period between ticks must be EXACTLY N:
if tick = '1' then
if last_tick_cycle >= 0 then
assert (cyc - last_tick_cycle) = N
report "period /= N (off-by-one in terminal compare?)" severity error;
end if;
last_tick_cycle := cyc;
end if;
exp_count := (exp_count + 1) mod N;
cyc := cyc + 1;
end loop;
report "mod_n self-check complete (period = N, visits 0..N-1)" severity note;
wait;
end process;
end architecture;4b. Programmable mod-N counter — N as an input, with a shrink guard
Now make the modulus a runtime value: mod_n is an input (in a real design, the output of a software-written configuration register). The structure is the same compare-and-reset, but two things change. First, the terminal value is now mod_n - 1, computed live. Second — the load-bearing detail — the compare must be >=, not ==: if software writes a smaller mod_n while the counter is already above the new terminal value, an exact-equality compare would never match and the counter would run all the way to overflow before wrapping. The >= guard makes a count that is already at or beyond the new terminal value wrap on the very next edge.
module mod_n_prog #(
parameter int WIDTH = 16 // holds the largest legal mod_n
)(
input logic clk,
input logic rst, // synchronous, active-high
input logic en,
input logic [WIDTH-1:0] mod_n, // RUNTIME modulus (>= 1)
output logic [WIDTH-1:0] count,
output logic tick
);
// >= (not ==): if mod_n was just SHRUNK below the current count, an exact
// equality would never fire and the counter would run to overflow. The
// magnitude compare guarantees a count at/above the NEW terminal wraps next
// edge. Terminal value is mod_n - 1 (never mod_n).
wire wrap = en & (count >= (mod_n - 1'b1));
always_ff @(posedge clk) begin
if (rst) count <= '0;
else if (en) begin
if (wrap) count <= '0; // at/past terminal → wrap to 0
else count <= count + 1'b1; // advance
end
end
assign tick = wrap; // strobe once per mod_n cycles
endmoduleThe self-checking testbench for the programmable version does everything 4a's did — sequence and period for a couple of static values of mod_n — and then adds the distinguishing test: it changes mod_n mid-count (both larger and, critically, smaller than the current count) and checks the counter re-wraps cleanly at the new modulus within one period, never getting stuck running to overflow.
module mod_n_prog_tb;
localparam int WIDTH = 16;
logic clk = 0, rst, en;
logic [WIDTH-1:0] mod_n, count;
logic tick;
int errors = 0, gap = 0, last = -1, cyc = 0;
mod_n_prog #(.WIDTH(WIDTH)) dut
(.clk(clk), .rst(rst), .en(en), .mod_n(mod_n), .count(count), .tick(tick));
always #5 clk = ~clk;
// Check the period between ticks equals the CURRENT mod_n (after settling).
task check_period(input int m);
last = -1; cyc = 0;
repeat (3*m + 2) begin
@(posedge clk); #1;
assert (count < mod_n)
else begin $error("count %0d >= mod_n %0d", count, mod_n); errors++; end
if (tick) begin
if (last >= 0) begin
gap = cyc - last;
assert (gap == m)
else begin $error("period=%0d exp=%0d", gap, m); errors++; end
end
last = cyc;
end
cyc++;
end
endtask
initial begin
rst = 1; en = 0; mod_n = 8; @(posedge clk); #1;
rst = 0; en = 1;
check_period(8); // /8 divider
mod_n = 3; // SHRINK below current count
check_period(3); // must re-wrap cleanly at 3
mod_n = 5; // grow again
check_period(5);
if (errors == 0) $display("PASS: programmable mod-N re-wraps at each new modulus");
else $display("FAIL: %0d mismatches", errors);
$finish;
end
endmoduleThe Verilog programmable counter is the same >= guard with reg/wire typing; the testbench measures the tick-to-tick period and re-runs it after writing a smaller mod_n.
module mod_n_prog #(
parameter WIDTH = 16
)(
input wire clk,
input wire rst,
input wire en,
input wire [WIDTH-1:0] mod_n, // runtime modulus (>= 1)
output reg [WIDTH-1:0] count,
output wire tick
);
// >= guard: a mod_n shrunk below the current count still wraps next edge.
wire wrap = en & (count >= (mod_n - 1'b1));
always @(posedge clk) begin
if (rst) count <= {WIDTH{1'b0}};
else if (en) begin
if (wrap) count <= {WIDTH{1'b0}}; // at/past terminal → wrap
else count <= count + 1'b1;
end
end
assign tick = wrap;
endmodule module mod_n_prog_tb;
parameter WIDTH = 16;
reg clk, rst, en;
reg [WIDTH-1:0] mod_n;
wire [WIDTH-1:0] count;
wire tick;
integer errors, cyc, last, gap, i;
mod_n_prog #(.WIDTH(WIDTH)) dut
(.clk(clk), .rst(rst), .en(en), .mod_n(mod_n), .count(count), .tick(tick));
initial clk = 0;
always #5 clk = ~clk;
// measure tick-to-tick period over ~3 periods and compare to m
task check_period; input integer m; begin
last = -1; cyc = 0;
for (i = 0; i < 3*m + 2; i = i + 1) begin
@(posedge clk); #1;
if (count >= mod_n) begin
$display("FAIL: count=%0d >= mod_n=%0d", count, mod_n);
errors = errors + 1;
end
if (tick) begin
if (last >= 0) begin
gap = cyc - last;
if (gap !== m) begin
$display("FAIL: period=%0d exp=%0d", gap, m);
errors = errors + 1;
end
end
last = cyc;
end
cyc = cyc + 1;
end
end endtask
initial begin
errors = 0;
rst = 1; en = 0; mod_n = 8; @(posedge clk); #1;
rst = 0; en = 1;
check_period(8); // /8
mod_n = 3; // SHRINK below current count
check_period(3); // must re-wrap cleanly
mod_n = 5;
check_period(5);
if (errors == 0) $display("PASS: programmable mod-N re-wraps at each modulus");
else $display("FAIL: %0d mismatches", errors);
$finish;
end
endmoduleIn VHDL the runtime modulus is a std_logic_vector port converted to unsigned; the guard is the same >= on the terminal value mod_n - 1. The testbench drives a smaller mod_n mid-run and asserts the period follows.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity mod_n_prog is
generic ( WIDTH : positive := 16 );
port (
clk : in std_logic;
rst : in std_logic; -- synchronous, active-high
en : in std_logic;
mod_n : in std_logic_vector(WIDTH-1 downto 0); -- runtime modulus (>= 1)
count : out std_logic_vector(WIDTH-1 downto 0);
tick : out std_logic
);
end entity;
architecture rtl of mod_n_prog is
signal cnt : unsigned(WIDTH-1 downto 0) := (others => '0');
signal wrap : std_logic;
signal m_u : unsigned(WIDTH-1 downto 0);
begin
m_u <= unsigned(mod_n);
-- >= (not =): a mod_n shrunk below the current count still wraps next edge.
wrap <= '1' when (en = '1' and cnt >= (m_u - 1)) else '0';
process (clk)
begin
if rising_edge(clk) then
if rst = '1' then
cnt <= (others => '0');
elsif en = '1' then
if wrap = '1' then
cnt <= (others => '0'); -- at/past terminal → wrap
else
cnt <= cnt + 1; -- advance
end if;
end if;
end if;
end process;
count <= std_logic_vector(cnt);
tick <= wrap;
end architecture; library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity mod_n_prog_tb is
end entity;
architecture sim of mod_n_prog_tb is
constant WIDTH : positive := 16;
signal clk : std_logic := '0';
signal rst : std_logic := '1';
signal en : std_logic := '0';
signal mod_n : std_logic_vector(WIDTH-1 downto 0) := std_logic_vector(to_unsigned(8, WIDTH));
signal count : std_logic_vector(WIDTH-1 downto 0);
signal tick : std_logic;
begin
dut : entity work.mod_n_prog
generic map (WIDTH => WIDTH)
port map (clk => clk, rst => rst, en => en, mod_n => mod_n, count => count, tick => tick);
clk <= not clk after 5 ns;
stim : process
variable last : integer := -1;
variable cyc : integer := 0;
procedure check_period(m : integer) is
variable gap : integer;
begin
last := -1; cyc := 0;
for i in 0 to 3*m + 1 loop
wait until rising_edge(clk); wait for 1 ns;
assert to_integer(unsigned(count)) < m
report "count reached/passed modulus" severity error;
if tick = '1' then
if last >= 0 then
gap := cyc - last;
assert gap = m
report "programmable period /= modulus" severity error;
end if;
last := cyc;
end if;
cyc := cyc + 1;
end loop;
end procedure;
begin
wait until rising_edge(clk); wait for 1 ns;
rst <= '0'; en <= '1';
check_period(8); -- /8
mod_n <= std_logic_vector(to_unsigned(3, WIDTH)); -- SHRINK below current count
check_period(3); -- must re-wrap cleanly
mod_n <= std_logic_vector(to_unsigned(5, WIDTH));
check_period(5);
report "programmable mod_n self-check complete" severity note;
wait;
end process;
end architecture;4c. The off-by-one bug — == N (buggy) vs == N-1 (fixed), in all three HDLs
Pattern (c) is the signature failure, shown here as buggy vs fixed RTL in each language and dramatized in §7. The bug is the same everywhere: the terminal-count compare is written against N instead of N-1. It looks right — "wrap when the count reaches N" reads naturally — but it makes the counter visit 0 … N (that is N+1 states), so the period becomes N+1, and a divider built for /N silently divides by /(N+1). It passes a casual eyeball; it fails on a scope at speed.
// BUGGY: compare against N. The counter reaches N before wrapping, so it visits
// 0..N (N+1 states) and the period is N+1 — a /N divider divides by N+1.
module mod_n_bad #(
parameter int N = 10,
localparam int WIDTH = $clog2(N + 1) // note: needs a bit MORE to hold N
)(
input logic clk, rst, en,
output logic tick
);
logic [WIDTH-1:0] count;
always_ff @(posedge clk) begin
if (rst) count <= '0;
else if (en) begin
if (count == WIDTH'(N)) count <= '0; // BUG: == N → wraps one late
else count <= count + 1'b1;
end
end
assign tick = en & (count == WIDTH'(N)); // fires every N+1 cycles
endmodule
// FIXED: compare against the TERMINAL value N-1. The counter visits 0..N-1
// (N states), the period is exactly N, and WIDTH need only hold N-1.
module mod_n_good #(
parameter int N = 10,
localparam int WIDTH = (N > 1) ? $clog2(N) : 1
)(
input logic clk, rst, en,
output logic tick
);
logic [WIDTH-1:0] count;
always_ff @(posedge clk) begin
if (rst) count <= '0;
else if (en) begin
if (count == WIDTH'(N-1)) count <= '0; // FIX: == N-1 → period N
else count <= count + 1'b1;
end
end
assign tick = en & (count == WIDTH'(N-1)); // fires every N cycles
endmodule module mod_n_bug_tb;
localparam int N = 4; // expect period 4
logic clk = 0, rst, en, tick;
int errors = 0, cyc = 0, last = -1, gap;
// Bind the GOOD DUT to PASS; bind mod_n_bad to watch the period come out N+1.
mod_n_good #(.N(N)) dut (.clk(clk), .rst(rst), .en(en), .tick(tick));
always #5 clk = ~clk;
initial begin
rst = 1; en = 0; @(posedge clk); #1;
rst = 0; en = 1;
for (cyc = 0; cyc < 4*N + 4; cyc++) begin
@(posedge clk); #1;
if (tick) begin
if (last >= 0) begin
gap = cyc - last;
// The whole bug: a correct /N counter has period N, not N+1.
assert (gap == N)
else begin $error("period=%0d exp=%0d — off-by-one!", gap, N); errors++; end
end
last = cyc;
end
end
if (errors == 0) $display("PASS: period == N (terminal compare is N-1)");
else $display("FAIL: %0d — divider is /(N+1), not /N", errors);
$finish;
end
endmoduleThe Verilog buggy/fixed pair is the same story with reg outputs. Measuring the tick-to-tick period is what exposes it: the buggy version reads N+1 where the spec says N.
// BUGGY: wrap on == N → visits 0..N (N+1 states) → period N+1 (a /(N+1) divider).
module mod_n_bad #(
parameter N = 10,
parameter WIDTH = 4
)(
input wire clk, rst, en,
output wire tick
);
reg [WIDTH-1:0] count;
always @(posedge clk) begin
if (rst) count <= {WIDTH{1'b0}};
else if (en) begin
if (count == N) count <= {WIDTH{1'b0}}; // BUG: == N
else count <= count + 1'b1;
end
end
assign tick = en & (count == N); // every N+1 cycles
endmodule
// FIXED: wrap on == N-1 (the terminal value) → visits 0..N-1 → period N.
module mod_n_good #(
parameter N = 10,
parameter WIDTH = 4
)(
input wire clk, rst, en,
output wire tick
);
reg [WIDTH-1:0] count;
always @(posedge clk) begin
if (rst) count <= {WIDTH{1'b0}};
else if (en) begin
if (count == N - 1) count <= {WIDTH{1'b0}}; // FIX: == N-1
else count <= count + 1'b1;
end
end
assign tick = en & (count == N - 1); // every N cycles
endmodule module mod_n_bug_tb;
parameter N = 4;
parameter WIDTH = 4;
reg clk, rst, en;
wire tick;
integer errors, cyc, last, gap;
// Bind to mod_n_good to PASS; to mod_n_bad to see period = N+1.
mod_n_good #(.N(N), .WIDTH(WIDTH)) dut (.clk(clk), .rst(rst), .en(en), .tick(tick));
initial clk = 0;
always #5 clk = ~clk;
initial begin
errors = 0; last = -1;
rst = 1; en = 0; @(posedge clk); #1;
rst = 0; en = 1;
for (cyc = 0; cyc < 4*N + 4; cyc = cyc + 1) begin
@(posedge clk); #1;
if (tick) begin
if (last >= 0) begin
gap = cyc - last;
if (gap !== N) begin
$display("FAIL: period=%0d exp=%0d — off-by-one (== N vs == N-1)", gap, N);
errors = errors + 1;
end
end
last = cyc;
end
end
if (errors == 0) $display("PASS: period == N (terminal compare is N-1)");
else $display("FAIL: %0d — divider is /(N+1), not /N", errors);
$finish;
end
endmoduleIn VHDL the identical off-by-one appears when the compare uses to_unsigned(N, WIDTH) instead of to_unsigned(N-1, WIDTH). The buggy compare must also widen the register to hold N, a telltale that something is off.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
-- BUGGY: compare against N. Counter reaches N → visits 0..N (N+1 states) →
-- period is N+1, so a /N divider actually divides by N+1.
entity mod_n_bad is
generic ( N : positive := 10; WIDTH : positive := 4 );
port ( clk, rst, en : in std_logic; tick : out std_logic );
end entity;
architecture rtl of mod_n_bad is
signal cnt : unsigned(WIDTH-1 downto 0) := (others => '0');
begin
process (clk)
begin
if rising_edge(clk) then
if rst = '1' then
cnt <= (others => '0');
elsif en = '1' then
if cnt = to_unsigned(N, WIDTH) then -- BUG: = N
cnt <= (others => '0');
else
cnt <= cnt + 1;
end if;
end if;
end if;
end process;
tick <= '1' when (en = '1' and cnt = to_unsigned(N, WIDTH)) else '0';
end architecture;
-- FIXED: compare against the terminal value N-1 → visits 0..N-1 → period N.
entity mod_n_good is
generic ( N : positive := 10; WIDTH : positive := 4 );
port ( clk, rst, en : in std_logic; tick : out std_logic );
end entity;
architecture rtl of mod_n_good is
signal cnt : unsigned(WIDTH-1 downto 0) := (others => '0');
begin
process (clk)
begin
if rising_edge(clk) then
if rst = '1' then
cnt <= (others => '0');
elsif en = '1' then
if cnt = to_unsigned(N-1, WIDTH) then -- FIX: = N-1
cnt <= (others => '0');
else
cnt <= cnt + 1;
end if;
end if;
end if;
end process;
tick <= '1' when (en = '1' and cnt = to_unsigned(N-1, WIDTH)) else '0';
end architecture; library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity mod_n_bug_tb is
end entity;
architecture sim of mod_n_bug_tb is
constant N : positive := 4;
constant WIDTH : positive := 4;
signal clk : std_logic := '0';
signal rst : std_logic := '1';
signal en : std_logic := '0';
signal tick : std_logic;
begin
-- Bind to mod_n_good to PASS; to mod_n_bad to observe period = N+1.
dut : entity work.mod_n_good
generic map (N => N, WIDTH => WIDTH)
port map (clk => clk, rst => rst, en => en, tick => tick);
clk <= not clk after 5 ns;
stim : process
variable last : integer := -1;
variable cyc : integer := 0;
variable gap : integer;
begin
wait until rising_edge(clk); wait for 1 ns;
rst <= '0'; en <= '1';
for c in 0 to 4*N + 3 loop
wait until rising_edge(clk); wait for 1 ns;
if tick = '1' then
if last >= 0 then
gap := cyc - last;
assert gap = N
report "period /= N — off-by-one (= N instead of = N-1)" severity error;
end if;
last := cyc;
end if;
cyc := cyc + 1;
end loop;
report "mod_n_bug self-check complete (period should be N)" severity note;
wait;
end process;
end architecture;Across all three languages the lesson is one sentence: the terminal value of a mod-N counter is N-1, so the wrap compare is count == N-1 — comparing against N makes the period N+1 and quietly turns a /N divider into a /(N+1) one.
5. Verification Strategy
A mod-N counter is sequential, so its correctness is a behaviour over time, not a truth table — which means the testbench must be clocked: generate a clock, release reset, and sample the counter after each edge with the self-checking discipline from 3.1. Two invariants capture a correct mod-N counter, and everything below makes them checkable, mapping directly onto the testbenches in §4:
(1) The counter visits exactly the states
0, 1, …, N-1and never reachesN; (2) the period between two wraps (twoticks) is exactly N cycles — not N+1.
- Sequence check against a reference (self-checking). Keep a software reference
expect_countthat advances(expect_count + 1) mod Nevery cycle, and after each clock edge assertcount === expect_countandcount < N. This proves the counter visits0 … N-1and never overshoots to N — the direct check that the terminal value is N-1. The three §4a testbenches do exactly this: SVassert (count === expect_count), Verilogif (count !== …) $display("FAIL"), VHDLassert … severity error. - Period-is-exactly-N check (the off-by-one guard). Record the cycle number of each
tick; when the nexttickfires, assert the gap is== N. This is the single most important check on the page because it catches the signature bug: a== Ncompare produces a gap ofN+1, and only a period measurement — not an eyeball of the waveform — reliably exposes it. This is what turns "looks like it counts" into "provably divides by N." - Sweep several N, including a non-power-of-two and N=1. A mod-N counter that passes at
N=8can still be wrong atN=5(where theN-1terminal is not all-ones and a sloppy wrap shows up) or atN=1(the degenerate case: terminal value is0, so the counter should wrap every cycle andtickevery cycle — a period of 1). Re-run the sequence-and-period checks forN = 2, 3, 5, 8and theN=1corner; the non-power-of-two is where the terminal-value discipline earns its keep. - Programmable-N: change N mid-count and check clean re-wrap. For the programmable variant, drive a new
mod_nwhile the counter is running — both larger and, crucially, smaller than the current count — and check the counter re-wraps at the new modulus within one period. The smaller case is the one that exposes a missing>=guard: with a pure==compare a shrunkmod_nis never matched and the counter runs to overflow (a period of2^WIDTHinstead of the new N). The §4b testbenches do this with acheck_periodtask called after each write. - Assert the divided strobe period. The
tickoutput is the divided signal, so its period is the divide ratio. State it as an invariant: "tickis high for exactly one cycle every N cycles," and verify it with the same tick-to-tick gap measurement. This is the property a downstream clock divider or baud generator depends on — if the strobe period is N+1 instead of N, the derived frequency is wrong even though the RTL "runs." - Expected waveform. A correct run shows
countramping0 → 1 → … → N-1and snapping to0on the next edge, withtickpulsing high for exactly the cyclecount == N-1, once every N cycles. The visual signature of the off-by-one bug is a ramp that goes one step too far (toN) before snapping — and atick-to-tickspacing of N+1 clocks instead of N. Recognizing "the ramp reached N" on a waveform is how you spot the bug by eye once you know to look.
6. Common Mistakes
The terminal-compare off-by-one — count == N instead of count == N-1. The signature mod-N bug and the reason this pattern gets its own page. Because the states are 0 … N-1, the largest value the counter holds is N-1, so that is what the wrap compare must test. Writing count == N lets the counter reach N before wrapping — it visits N+1 states, the period becomes N+1, and a /N clock divider actually divides by N+1. It simulates "fine" (it still counts and wraps), passes a casual eyeball, and produces a subtly wrong frequency that only shows up on a scope or as protocol errors at speed (a UART running at a slightly wrong baud, a tick that is 0.1% slow). Always compare against the terminal value N-1; measure the period in verification to catch it.
A programmable N shrinking below the current count with a pure == compare. When mod_n is a runtime input and software writes a smaller value while the counter is already above the new mod_n - 1, an exact-equality compare will never match — the counter has already passed the value it waits for, so it runs all the way to 2^WIDTH before wrapping (a single enormous period, effectively a stuck counter). The fix is a magnitude guard: wrap when count >= mod_n - 1, so a count at or beyond the new terminal wraps on the next edge. This is the one place a >= (not ==) is required, and it exists only because a live N can move the target underneath the count.
The N=0 and N=1 edge cases. N=0 is meaningless (there is no such thing as modulo-zero) and, worse, mod_n - 1 underflows to all-ones for an unsigned modulus — so guard against mod_n == 0 in a programmable design (treat it as illegal, or clamp to 1) rather than letting the terminal value wrap to the maximum. N=1 is legal but degenerate: the terminal value is 0, so the counter should wrap every cycle (tick high every cycle, period 1) — verify this corner explicitly, because a count == N-1 written assuming N >= 2 can misbehave when N-1 is zero.
Using a magnitude compare where equality suffices — wasted area. For a static mod-N (fixed N), the terminal value is a constant and you only need to detect one exact value, so an equality compare (count == N-1) is correct and cheap — a few gates. Reaching for a magnitude comparator (count >= N-1) there is pure waste: it synthesizes a full carry-chain compare to solve a problem equality already solves. Reserve >= for the programmable case, where a shrinking runtime N genuinely needs it; do not pay for magnitude compare when the modulus is fixed.
7. DebugLab
The baud rate that was almost right — a /N divider that divided by N+1
The engineering lesson: the terminal value of a mod-N counter is N-1, not N — the period is the count of states visited, and an off-by-one there is a frequency error, not a functional one. That is what makes it so dangerous: the RTL still "works" (it counts, it wraps, it ticks), so it sails through a functional eyeball and even a same-vs-same loopback; the damage is a divide ratio that is exactly one too large, which surfaces only against a correct reference, at speed, or on long transfers. Two habits make it impossible, and they are identical across SystemVerilog, Verilog, and VHDL: always trigger the wrap on count == N-1 (the largest of the N states 0 … N-1), and verify by measuring the period — assert the tick-to-tick gap is exactly N, because a divide ratio is a number of cycles, and the only way to prove it is to count them.
8. Interview Q&A
9. Exercises
Design and reasoning exercises — no syntax drills. Each rehearses the "terminal value is N-1, period is N" habit.
Exercise 1 — Design a /10 counter, then predict its width
You need a decade (mod-10) counter that emits one tick per 10 clocks. (a) What value must the wrap compare test for, and what sequence of counts does the register hold? (b) What is the minimum register WIDTH? (c) A colleague builds it comparing count == 10 "because it's a /10 counter" — what is the actual period their divider produces, and how many states does the counter visit? (d) State the one-line self-checking assertion that would have caught their bug, and explain why an eyeball of the waveform would not.
Exercise 2 — Baud divisor from a 50 MHz clock
A UART on a 50 MHz clock must generate 9600 baud using a 16× oversampling tick (the sample strobe runs at 16 × baud). (a) Compute the ideal divisor N = f_clk / (16 × 9600) — is it an integer? (b) Choose the nearest integer N and state the actual oversample-tick frequency and the resulting baud-rate error in percent. (c) Now the same UART must also support 115200 baud — compute that N. (d) Given both rates are needed at runtime, argue why the modulus should be a programmable input rather than a parameter, and name the one extra piece of logic the programmable version needs that the fixed one does not.
Exercise 3 — The shrink hazard on paper
A programmable mod-N counter uses an exact-equality wrap: wrap = (count == mod_n - 1). It is currently running with mod_n = 12 and the count happens to be 9. Software now writes mod_n = 5. (a) Trace what the counter does on the next several edges — does it ever wrap at 5? (b) What is the actual period until the next wrap, in terms of the register width? (c) Rewrite the wrap condition to fix it, and explain in one line why your fix makes the counter recover within one clock. (d) Why does the static (parameter-N) version never suffer this hazard, so it can safely keep the cheaper == compare?
Exercise 4 — The N=1 corner and the N=0 trap
(a) For a mod-1 counter, what is the terminal value N-1, how often should tick fire, and what count sequence does the register hold? (b) Explain why a count == N-1 compare written assuming N ≥ 2 still behaves correctly for N=1 — or, if it does not in your design, what to add. (c) For a programmable counter, explain what mod_n - 1 evaluates to when software writes mod_n = 0 on an unsigned modulus, why that is dangerous, and one way to guard against it (reject, or clamp to 1).
10. Related Tutorials
Continue in RTL Design Patterns (sibling and forward links unlock as they ship):
- Up/Down Counters — Chapter 3.1; the plain count register + enable + reset this pattern extends. A mod-N counter is a counter with a compare-and-reset bolted on — start here if the base counter is not yet familiar.
- Gray-Code Counters — Chapter 3.4; the sibling variant where the sequence changes (one-bit-per-step, for safe CDC) rather than the wrap point.
- Clock Dividers & Timers — Chapter 3.3; the direct forward application — a clock divider is a mod-N counter whose tick is the divided output, extended to duty cycle and timers.
- Pulse & Strobe Generators — Chapter 3.5; the mod-N
tickgeneralized into one-shot, periodic, and delayed strobe generation.
Builds on (in-track):
- Comparators — Chapter 1.5; the equality (and magnitude) compare that fires the wrap —
count == N-1for static N,count >= mod_n - 1for the programmable shrink guard. - Multiplexers — Chapter 1.1; the 2:1 mux on the register's next-value input that steers
0(wrap) versuscount + 1(advance). - The Register Pattern (D-FF) — Chapter 2.1; the clocked count register at the heart of every counter.
- Register with Enable & Load — Chapter 2.2; the
engating that freezes the count, and the load semantics a programmable modulus register uses. - Synchronous Reset · Asynchronous Reset — Chapter 2; the reset-to-zero discipline the counter inherits.
- Shift Registers — Chapter 2.3; a related sequential primitive whose length, like a mod-N period, is a deliberate count.
Method / overview:
- The RTL Design Mindset — Chapter 0.2; the Interface → State → Datapath → Control → Verification lenses this page applies (a mod-N counter is state + a tiny bit of control).
- What Is an RTL Design Pattern? — Chapter 0.1; why compare-and-reset earns the name "pattern" — a recurring need, a reusable form, a synthesis reality, and a signature off-by-one failure.
Verilog v1 prerequisites (the grammar this pattern transcribes into):
- Arithmetic Operators — the
+ 1advance and theN - 1terminal-value computation. - Relational Operators — the
==equality wrap and the>=shrink guard for a programmable modulus. - Case Statements — the selection construct behind the advance-vs-wrap next-value choice.
- Blocking and Non-Blocking Assignments — why the count register uses
<=in its clockedalways, the sequential-logic idiom every counter follows.
11. Summary
- A mod-N counter is a counter plus a compare-and-reset. The register and +1 adder are the plain counter (3.1); mod-N adds only an equality comparator (
count == N-1, a 1.5 comparator) and a 2:1 mux that steers0back into the register when the comparator fires. Advance, advance, advance, snap to zero — with a period of N cycles, for any N. - The terminal value is N-1, not N. The counter visits the N states
0 … N-1; the largest isN-1, so that is what the wrap compares against. The period (divide ratio) is N; the terminal value is N-1 — two different numbers, and confusing them is the signature bug. - Static vs programmable modulus. A
parameter/genericN foldsN-1into a compile-time constant (fixed divisor, cheap equality compare); an input N compares against a livemod_n - 1(programmable divisor). The programmable version needs a>=shrink guard so a modulus rewritten smaller than the current count still wraps on the next edge instead of running to overflow. - The off-by-one is a frequency error, not a functional one. Comparing
count == Ninstead ofcount == N-1makes the periodN+1, so a /N divider divides by /(N+1). The RTL still counts and ticks — it passes a casual eyeball and a same-vs-same loopback — but the derived clock/baud is subtly wrong and fails at speed (the §7 DebugLab). Watch theN=1corner (wrap every cycle) and theN=0trap (mod_n - 1underflows). - Verify by measuring the period. A clocked, self-checking testbench must assert both that the counter visits
0 … N-1(never N) and that the tick-to-tick gap is exactly N — the divide ratio is a number of cycles, so the only proof is to count them. Sweep several N (incl. a non-power-of-two and N=1), and for programmable N change the modulus mid-count and confirm a clean re-wrap — self-checking testbenches shown here in SystemVerilog, Verilog, and VHDL.