Mod-N Counter in Verilog — Concept, RTL, and Testbench (Explained)
What is a Mod-N Counter?
A Mod-N counter cycles through N distinct states 0 … N−1 and then wraps to 0. This is the workhorse for timing, clock division, address stepping, baud rate generators, and any place you need a repeatable cycle shorter than a power of two.
Key idea: the counter’s numeric width is sized just big enough to represent N−1, and the next-state logic wraps at N, not at 2^WIDTH.
Design Goals (Problem Statement)
-
Parameterized modulus: N (compile-time parameter)
-
Width: WIDTH = ceil(log2(N))
-
Synchronous enable (en) and synchronous load (ld)
-
Asynchronous reset (arst) (active high) to a known state
-
Outputs: state Q[WIDTH-1:0] and a one-cycle terminal_count flag
-
Behavior:
-
Count 0 … N−1 when en=1
-
Wrap to 0 after N−1
-
ld=1 loads D on the next clock
-
terminal_count asserts for one cycle when the counter hits and wraps from N−1 to 0 (as coded below)
-
RTL: Parameterized Mod-N Up-Counter
Verilog
module modn_upcounter #(
parameter N = 16,
parameter WIDTH = $clog2(N)
)(
input wire clk,
input wire arst, // async reset, active-high
input wire en, // sync enable
input wire ld, // sync load
input wire [WIDTH-1:0] D, // load data
output reg [WIDTH-1:0] Q, // state
output reg terminal_count // 1-cycle pulse on wrap
);
always @(posedge clk or posedge arst) begin
if (arst) begin
Q <= {WIDTH{1'b0}};
terminal_count <= 1'b0;
end else if (ld) begin
Q <= D;
terminal_count <= 1'b0;
end else if (en) begin
if (Q == N-1) begin
Q <= {WIDTH{1'b0}}; // wrap
terminal_count <= 1'b1;
end else begin
Q <= Q + 1'b1;
terminal_count <= 1'b0;
end
end else begin
terminal_count <= 1'b0; // hold state; no terminal pulse
end
end
endmodule
How the RTL Works (line-by-line)
-
Parameters N and WIDTH:
WIDTH = $clog2(N) gives the minimum number of bits to represent N−1. For N=10, WIDTH=4.
-
Asynchronous reset (posedge arst):
Clears Q and the flag immediately (no clock needed). Deassert reset cleanly (meet recovery/removal).
-
Synchronous load (ld):
Has priority over en. On the next clock edge, Q<=D. The flag is cleared to avoid spurious pulses.
-
-
Synchronous enable (en):
When enabled:
-
If Q == N-1, wrap Q to 0 and raise terminal_count for one clock cycle.
-
Else increment Q and keep the flag low.
-
-
Hold (no ld, no en):
State Q holds implicitly (no assignment needed); the code clears the flag explicitly for clarity.
Flag semantics: In this implementation, terminal_count pulses on the cycle that wraps.
If you prefer “early warning” (flag high one cycle before the wrap), compute from Q == N-2 and register the flag accordingly.
Why This Is “Mod-N” (and not Mod-2^WIDTH)
Left to itself, an WIDTH-bit adder will wrap at 2^WIDTH. The if (Q == N-1) branch forces the wrap at N, so you count 0 … N−1 even when N is not a power of two.
Tip (loads): If software might load D >= N, clamp or sanitize on load so behavior is well-defined:
verilog
Q <= (D < N) ? D : {WIDTH{1'b0}};
Testbench: Stimulus & Observability
Verilog
module tb_modn_upcounter;
parameter N = 10;
parameter WIDTH = $clog2(N);
reg clk = 0;
reg arst = 1;
reg en = 0;
reg ld = 0;
reg [WIDTH-1:0] D = 0;
wire [WIDTH-1:0] Q;
wire terminal_count;
modn_upcounter #(.N(N)) tb (
.clk(clk), .arst(arst), .en(en), .ld(ld),
.D(D), .Q(Q), .terminal_count(terminal_count)
);
// 10-unit clock (posedge at 5,15,25,…)
always #5 clk = ~clk;
initial begin
// Reset phase
arst = 1; #12; arst = 0; // Q=0 at release; first posedge is at t=15
// Count for 100 time units (≈10 cycles), enough to wrap at N=10
en = 1; #100;
// Pause, load, and continue
en = 0;
ld = 1; D = 4; #10; // load happens at next posedge
ld = 0; en = 1; #50;
$finish;
end
initial
$monitor("Time=%0t | arst=%b | en=%b | ld=%b | D=%0d | Q=%0d | tc=%b",
$time, arst, en, ld, D, Q, terminal_count);
endmodule
What You’ll See
-
Reset: Q=0, tc=0.
-
Counting: Q runs 0,1,2,…,9,0,…; terminal_count pulses when Q hits 9 and wraps to 0.
-
Load: With ld=1, D=4, the next clock sets Q=4, tc=0, then counting resumes
Common Refinements & Gotchas
-
Tool/Language note: $clog2 is SystemVerilog. If you must stick to Verilog-2001, use a small function or macro to compute width at elaboration.
-
Parameter checks: Guard legal ranges at compile time:
3.Out-of-range loads: Decide policy—clamp, mod, or treat as error. Clamping is safest in hardware.
4.Early-warning terminal flag (optional):
If you need terminal_count one cycle before wrap, derive from Q == N-2:
5.Synthesis mapping:
-
FPGAs: Q+1 maps to carry chains, the compare maps to small LUTs—fast.
-
ASICs: tiny comparator + register file; very timing-friendly.
6.No gated clocks: You already used a clean CE style (en inside the clocked block). That’s exactly what STA and clock- tree tools want.
7.Verification tips:
-
Directed tests: wrap point, load to N-1, load to 0, pause and resume.
-
Randomized tests: random en/ld/D, check against a software reference.
-
Assertions (SystemVerilog):
Verilog
else if (en) begin
if (dir && Q != {N{1'b1}}) Q <= Q + 1'b1; // up until max
else if (!dir && Q != {N{1'b0}}) Q <= Q - 1'b1; // down until 0
end
Verilog
wire tc_next = en && (Q == N-2) && !ld;
always @(posedge clk or posedge arst)
if (arst) terminal_count <= 1'b0;
else terminal_count <= tc_next;
…and compute Q_next with (Q == N-1) ? 0 : Q+1.
Verilog
// Monotonic + wrap behavior when enabled and not loading
assert property (@(posedge clk) disable iff (arst)
ld |-> (Q == $past(D)));
assert property (@(posedge clk) disable iff (arst || ld)
en && ($past(Q) != N-1) |-> Q == $past(Q) + 1);
assert property (@(posedge clk) disable iff (arst || ld)
en && ($past(Q) == N-1) |-> (Q == 0) && terminal_count);
Why Mod-N Counters Matter
-
Right-sized cycles save power and logic vs. padding up to 2^k.
-
Terminal flags make downstream control straightforward.
-
Parameterized RTL is reusable: change N, re-synthesize, done.
TL;DR
This Mod-N counter:
-
Counts 0 … N−1, wraps at N, pulses terminal_count on wrap, and supports sync load and sync enable with async reset.
-
It’s parameterized, synthesis-friendly, and easy to verify.
Interview Questions
Q1) How do you choose the width for a Mod-N counter?
Refined:
You need enough bits to represent 0 … N-1:
WIDTH=⌈log2(N)⌉\text{WIDTH} = \lceil \log_2(N) \rceilWIDTH=⌈log2(N)⌉
In SystemVerilog use $clog2(N), but guard tiny N:
verilog
localparam int WIDTH = (N <= 1) ? 1 : $clog2(N);
Note: $clog2(1)=0, which would produce a zero-width bus—hence the guard.
Q2) How do you implement a non-power-of-two modulus efficiently?
Refined:
Compare against N-1 and wrap; don’t use division/modulo at RTL.
verilog
if (Q == N-1) Q <= '0; else Q <= Q + 1'b1;
This synthesizes to a small comparator + incrementer and is timing-friendly.
Q3) What if N is larger than 2^WIDTH?
Refined:
The counter will wrap early (at 2^WIDTH), producing incorrect sequences. Prevent it with a compile-time check:
verilog
initial begin
if (N < 2) $fatal("N must be >= 2");
if ((1<<WIDTH) < N) $fatal("WIDTH too small for N");
end
Q4) How do you implement synchronous load that respects the modulus?
Refined:
Ensure D < N. Enforce in hardware (clamp) or by spec (software guarantee). Hardware clamp:
verilog
Q <= (D < N) ? D : '0; // keeps state legal
Give ld higher priority than en.
Q5) How do you cascade Mod-N counters for wider counts?
Refined:
Use a synchronous pulse from the lower stage as an enable for the higher stage.
verilog
wire tc_pulse_lo; // one-cycle at wrap of low counter
assign en_hi = en_global & tc_pulse_lo; // cascaded enable
Keep everything clocked by the same clk (no derived clocks), and decide how loads/reset propagate across stages.
Q6) How do you handle invalid or negative modulus values?
Refined:
Reject them at elaboration:
verilog
initial if (N < 2) $fatal("Invalid N (must be >=2)");
Document in the module header that N must be ≥ 2 and D must be < N (unless clamped).
Q7) How do you test boundary conditions?
Refined (TB plan):
-
Count through N-2 → N-1 → 0 and check wrap.
-
Load D=N-1 then step; expect Q=0 next.
-
Load D=0 and step; expect Q=1.
-
Toggle en around the wrap point.
-
Inject illegal D and confirm clamp or policy.
Q8) How do you formally verify Mod-N behavior?
Refined (SVA snippets):
Q9) How do you design a low-power Mod-N counter?
Refined:
-
Gate activity with en inside the flop block (tools infer clock-enable).
-
In ASIC, prefer ICG cells instead of manual clock gating.
-
If outputs cross clock domains, Gray code the observable value to reduce toggles there (the internal counter can stay binary).
Q10) Why avoid division/modulus operators in synthesizable code?
Refined:
Generic / or % infer expensive datapaths. A simple compare-and-wrap is cheap and fast.
Exception: % power_of_two may be optimized, but don’t rely on it for portability.
Q11) How do you implement a countdown Mod-N counter?
Refined:
verilog
if (Q == '0) Q <= N-1;
else Q <= Q - 1'b1;
Same priorities (reset > load > count), and a terminal_count_down can pulse when Q goes 0 → N-1.
Q12) How to support asynchronous set to an arbitrary value?
Refined:
Add an async set only if you truly need it; it complicates timing (recovery/removal) and data bus must be stable when asserted. Safer pattern: async reset to 0 + sync load to arbitrary D. If you must:
verilog
always @(posedge clk or posedge aset) begin
if (aset) Q <= D_async; // D_async must be stable + CDC-safe
else ...
end
Prefer a synchronized handshake that performs a synchronous load.
Q13) Signed vs unsigned counters?
Refined:
Treat the counter as unsigned unless the algorithm requires signed math:
verilog
logic [WIDTH-1:0] Q; // unsigned
If comparing with N-1, size the literal:
verilog
if (Q == WIDTH'(N-1)).
Q14) How do you avoid glitches on terminal_count?
Refined:
Register the flag; don’t expose purely combinational compares.
verilog
wire tc_next = en && (Q == N-1) && !ld;
always @(posedge clk or posedge arst)
if (arst) terminal_count <= 1'b0;
else terminal_count <= tc_next;
This yields a clean, one-cycle pulse.
Q15) Common corner cases interviewers probe.
Refined checklist:
-
N=2: WIDTH=1 → behaves like a T-FF (0↔1).
-
N=1: degenerate; disallow ($fatal) or define as constant zero with terminal_count high each cycle—state your policy.
-
$clog2 edge cases (see Q1).
-
Loading D ≥ N policy (clamp/mod/error).
-
Interaction priority: reset > load > enable > hold (be explicit).
-
CDC on control signals (ensure en/ld synchronous).
Verilog
// Load wins
assert property (@(posedge clk) disable iff (arst) ld |-> Q == $past(D));
// Normal step
assert property (@(posedge clk) disable iff (arst || ld)
en && $past(Q) != N-1 |-> Q == $past(Q) + 1);
// Wrap step + flag
assert property (@(posedge clk) disable iff (arst || ld)
en && $past(Q) == N-1 |-> Q == 0 && terminal_count);
A tidy reference RTL (up-count, with clean tc and clamps)
Verilog
module modn_upcounter #(
parameter int N = 10,
parameter int WIDTH = (N <= 1) ? 1 : $clog2(N)
)(
input logic clk, arst,
input logic en, ld,
input logic [WIDTH-1:0] D,
output logic [WIDTH-1:0] Q,
output logic terminal_count
);
// Compile-time guards
initial begin
if (N < 2) $fatal("N must be >= 2");
if ((1<<WIDTH) < N) $fatal("WIDTH too small for N");
end
// Next terminal pulse
wire tc_next = en && (Q == WIDTH'(N-1)) && !ld;
always_ff @(posedge clk or posedge arst) begin
if (arst) begin
Q <= '0;
terminal_count <= 1'b0;
end else begin
terminal_count <= tc_next;
if (ld) Q <= (D < N) ? D : '0;
else if (en) Q <= (Q == WIDTH'(N-1)) ? '0 : (Q + 1'b1);
// else hold
end
end
endmodule