top of page

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

  1. Tool/Language note: $clog2 is SystemVerilog. If you must stick to Verilog-2001, use a small function or macro to compute width at elaboration.
     

  2. 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=⌈log⁡2(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

Verilog LAB - Up/Down Counter

Verilog LAB - Ring Counter 

© Copyright 2025 VLSI Mentor. All Rights Reserved.©

Connect with us

  • Instagram
  • Facebook
  • Twitter
  • LinkedIn
  • YouTube
bottom of page