top of page

Designing a Parameterizable Up/Down Counter in Verilog

1) What is an Up/Down Counter?

An up/down counter is a sequential circuit that can increment or decrement its value each clock, controlled by a direction input:

  • dir = 1 → count up
     

  • dir = 0 → count down
     

It’s handy for timers, rotary encoders, positioning systems, and protocols where you sometimes need to move forward and sometimes backward through a range.

2) Design Goals (Problem Statement)

Build a reusable, parameterizable N-bit up/down counter with:

  • Asynchronous reset (arst) — clears immediately.
     

  • Synchronous enable (en) — count only when needed.
     

  • Synchronous load (ld, D) — preset to a value on the next clock.
     

  • Direction (dir) — choose up or down at runtime.
     

  • Wrap-around behavior by default (mod 2N2^N2N).
     

Ports

  • Inputs: clk, arst, en, dir, ld, D[N-1:0]
     

  • Output: Q[N-1:0] (current count)
     

Parameter: N (default 8)

3) Expected Behavior

​

  • If arst=1 → Q clears to 0 immediately.
     

  • Else on posedge clk:
     

    • If ld=1 → load Q <= D.
       

    • Else if en=1:
       

      • If dir=1 → increment.
         

      • If dir=0 → decrement.
         

    • Else → hold Q.
       

Priority: reset > load > count > hold.

​

4) Verilog RTL (Parameterizable)

 Verilog

module updown #(

  parameter N = 8

)(

  input  wire           clk,

  input  wire           arst,   // async reset, active-high

  input  wire           en,     // sync enable

  input  wire           dir,    // 1: up, 0: down

  input  wire           ld,     // sync load

  input  wire [N-1:0]   D,      // load data

  output reg  [N-1:0]   Q       // counter state

);

  always @(posedge clk or posedge arst) begin

    if (arst)

      Q <= {N{1'b0}};                          // clear immediately

    else if (ld)

      Q <= D;                                   // load on next edge

    else if (en)

      Q <= dir ? (Q + 1'b1) : (Q - 1'b1);       // up/down on next edge

    // else: hold (implicit)

  end

endmodule

RTL Explained (line by line)

​

  • Parameter N: One source for width—use this module at 4/8/16/… bits by changing N.
     

  • Async reset (posedge arst): Guarantees immediate, clock-independent clearing—useful for POR (power-on reset).
     

  • Synchronous load (ld): Higher priority than counting; ensures deterministic presets (no glitches).
     

  • Enable + Direction:
     

    • en=1, dir=1 → Q + 1'b1 (wraps from max→0 automatically due to fixed width)
       

    • en=1, dir=0 → Q - 1'b1 (wraps from 0→max automatically)
       

  • Hold: If none of the control conditions are true, no assignment → flip-flops hold state.
     

  • Non-blocking (<=): Models real flip-flop behavior—everything updates together at the clock edge.
     

💡 Width pedantry: Q + 1'b1 and Q - 1'b1 are fine; to silence picky linters use Q + {{(N-1){1'b0}},1'b1} / Q - {{(N-1){1'b0}},1'b1}.

​

​

5) Testbench (Stimulus & Checks)

 Verilog

module tb_updown;

  parameter N = 8;

 

  reg          clk = 0;

  reg         arst = 1;

  reg          en = 0;

  reg          dir = 0;

  reg          ld = 0;

  reg  [N-1:0] D = 0;

  wire [N-1:0] Q;

 

  updown #(.N(N)) tb(

    .clk(clk), .arst(arst), .en(en), .dir(dir), .ld(ld), .D(D), .Q(Q)

  );

 

  // 10 time-unit clock (posedge at 5,15,25, ...)

  always #5 clk = ~clk;

 

  initial begin

    // hold reset initially

    arst = 1; en = 0; ld = 0; dir = 0; D = 0;

    #15;                  // Q=0 under reset

    arst = 0;             // release reset

 

    // synchronous load 0x0A at next edge

    ld = 1; D = 8'h0A;    // request load

    #10;                  // at next posedge, Q=0x0A

 

    // count up for 50 time units

    ld = 0; en = 1; dir = 1;

    #50;

 

    // then count down for 50 time units

    dir = 0;

    #50;

 

    // pause counting

    en = 0; #20;

 

    // load 0x05, then count up again

    ld = 1; D = 8'h05; #10;   // load at next edge

    ld = 0; en = 1; dir = 1;  // resume counting up

    #30;

 

    // async reset in the middle

    arst = 1; #10; arst = 0;

 

    #20;

    $finish;

  end

 

  initial

    $monitor("Time=%0t | arst=%b | en=%b | dir=%b | ld=%b | D=%h | Q=%h",

              $time, arst, en, dir, ld, D, Q);

endmodule

What the TB is proving

​

  • Reset: With arst=1, Q is 0 immediately; after release, design resumes.
     

  • Load: ld=1 → Q becomes D at the next clock edge.
     

  • Enable + Direction:
     

    • en=1, dir=1 → up-count.
       

    • en=1, dir=0 → down-count.
       

    • en=0 → hold.
       

  • Wrap-around: If runs long enough, you’ll see max→0 and 0→max transitions naturally.
     

  • Monitor: Prints all controls and Q in hex—easy to match against expectations.

6) Practical Notes (FPGA/ASIC)

​

  • Synthesis: Q ± 1 maps to fast carry chains (FPGA) or optimized adders (ASIC).
     

  • Reset strategy: Asynchronous assertion is fine; deassertion should be clean (meet recovery/removal, or synchronize release).
     

  • Clocking: No gated clocks—using en inside the clocked block keeps the clock tree clean and timing predictable.
     

  • Scalability: This pattern scales to large N; for very wide counters, consider segmenting or using device primitives.

7) Common Variations (easy to extend)

​

  • Saturating mode (no wrap)

 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

  • Terminal flags

 Verilog

assign at_min = (Q == {N{1'b0}});

assign at_max = (Q == {N{1'b1}});

  • Modulo-M (non-power-of-two)
    Detect terminal count and load a start value:

 Verilog

if (ld) Q <= D;

else if (en) begin

  if (dir)   Q <= (Q == M-1) ? {N{1'b0}} : (Q + 1'b1);

  else       Q <= (Q == 0)   ? (M-1)     : (Q - 1'b1);

end

8) Quick Control Truth Table (at posedge clk)

9) Takeaways

​

  • Parameterization (N) makes the module reusable everywhere.
     

  • Priority ordering (reset > load > count > hold) keeps behavior deterministic.
     

  • Using non-blocking assignments and clean clocking ensures correct simulation and synthesis.
     

  • Wrap-around comes for free from fixed-width arithmetic; add flags or saturation when required.

1) How do you avoid ambiguity when dir flips during enable?

Refined answer:
Make dir stable at the sampling edge. If dir can change mid-cycle (from another domain or noisy source), synchronize it and only use the registered version inside the counter.

verilog

​

// If dir is async, use a 2-FF synchronizer

reg dir_s1, dir_s2;

always @(posedge clk or posedge arst) begin

  if (arst) {dir_s1,dir_s2} <= 2'b00;

  else      {dir_s1,dir_s2} <= {dir, dir_s1};

end

wire dir_q = dir_s2;   // use dir_q in counter logic

 

Also ensure dir_q meets setup/hold at clk. That eliminates ambiguous up/down decisions.

​

​

2) What priority should load and enable have?

Refined answer:
Typical, deterministic priority:

  1. Asynchronous reset (highest)
     

  2. Synchronous load (ld)
     

  3. Enable (en) → count (up/down)
     

  4. Hold
     

verilog

​

if (arst)       Q <= '0;

else if (ld)    Q <= D;

else if (en)    Q <= dir_q ? Q + 1 : Q - 1;

 

This ensures immediate reset, glitch-free loads, and clean counting.

​

3) How to implement saturation mode optionally?

Refined answer:
Gate the increment/decrement at the terminals when SATURATE is on.

verilog

​

parameter SATURATE = 1'b0;

localparam [N-1:0] MAX = {N{1'b1}};

always @(posedge clk or posedge arst) begin

  if (arst) Q <= '0;

  else if (ld) Q <= D;

  else if (en) begin

    if (dir_q)   Q <= (SATURATE && Q==MAX) ? MAX : (Q + 1'b1);

    else         Q <= (SATURATE && Q==0)   ? '0  : (Q - 1'b1);

  end

end

 

Wrap-around happens when SATURATE==0.

​

​

4) How to handle signed counters?

Refined answer:
Declare the state signed and use signed literals/ops.

verilog

​

reg signed [N-1:0] Q;

...

Q <= Q + (dir_q ? $signed(1) : -$signed(1));

 

Be explicit: mixing signed/unsigned or mismatched widths risks wrong arithmetic and synthesis surprises.

ider one.

​

​

5) How to cascade up/down counters?

Refined answer:
Create synchronous carry/borrow enables from the lower stage and use them to enable the higher stage—never use the lower stage as a clock.

​

verilog

​

wire lo_at_max = (Q_lo == MAX);

wire lo_at_min = (Q_lo == '0);

 

wire en_hi = en & ( dir_q ? lo_at_max : lo_at_min );

 

This scales cleanly and stays fully synchronous.

​

6) How do you test both up and down paths thoroughly?

Refined answer:
Use a software reference model in the TB and compare every cycle; randomize dir, ld, en, D, with constraints.

verilog

​

logic [N-1:0] ref;

always @(posedge clk or posedge arst) begin

  if (arst) ref <= '0;

  else if (ld) ref <= D;

  else if (en) ref <= dir ? ref+1 : ref-1;

end

// Assertions

assert property (@(posedge clk) disable iff (arst || ld)

  en &&  dir |-> Q == $past(Q) + 1);

assert property (@(posedge clk) disable iff (arst || ld)

  en && !dir |-> Q == $past(Q) - 1);

assert property (@(posedge clk) disable iff (arst)

  Q == ref);

​

7) How to handle wide counters efficiently?

Refined answer:

  • FPGA: leverage carry chains or DSP/adder blocks; keep logic local (floorplan if needed).
     

  • ASIC: use fast adders (e.g., parallel prefix), or pipeline ultra-wide counters if fMAX is tight.
     

  • Consider segmenting counters and enabling upper segments only on terminal conditions of lower segments.

​

​

8) How to design for minimal glitch when dir changes?

Refined answer:

  • Register dir (and double-sync if async).
     

  • Use only edge-triggered sequential logic—no combinational feedback to outputs.
     

  • Non-blocking (<=) in sequential blocks so outputs update only at clk.
     

​

9) How to add enable gating for power savings?

Refined answer:
Prefer data gating (en inside clocked block). Tools often infer a clock enable on FFs.

verilog

​

if (en) Q <= dir_q ? Q+1 : Q-1;


For ASICs, tech libraries offer ICG cells; use the vendor-recommended pattern so CTS/timing remain safe.

​​​

​

10) How to handle asynchronous load requests?

Refined answer:
Synchronize the load request and convert it to a one-cycle pulse in the clock domain; then perform a synchronous load.

verilog

​

// 2-FF sync + edge detect

reg ld_s1, ld_s2;

always @(posedge clk or posedge arst)

  if (arst) {ld_s1,ld_s2} <= 2'b00;

  else      {ld_s1,ld_s2} <= {ld_async, ld_s1};

wire ld_pulse = ld_s2 & ~ld_s1;  // rising-edge pulse

 

Now use ld_pulse in the RTL.

​

11) Why prefer D-FF based design for up/down counters?

Refined answer:
D-FFs map directly to library/FPGA primitives, keep next-state logic explicit and synthesizable, and make timing straightforward. JK/T styles add implicit toggling paths that are harder to reason about and constrain.

​

12) What about overflow/underflow flags?

Refined answer:
Generate registered one-cycle flags aligned with the update.

​

verilog

​

reg ovf, unf;

always @(posedge clk or posedge arst) begin

  if (arst) begin ovf<=0; unf<=0; end

  else if (ld) begin ovf<=0; unf<=0; end

  else if (en) begin

    ovf <=  dir_q & (Q==MAX);   // about to wrap max->0

    unf <= ~dir_q & (Q==0);     // about to wrap 0->max

  end else begin

    ovf<=0; unf<=0;

  end

end

​

Downstream logic gets clean, synchronous indicators.

​

13) How to prevent multiple toggles in a single clock cycle?

Refined answer:

  • Use posedge D-FFs only; no level-sensitive latches.
     

  • Keep next-state logic acyclic; avoid combinational loops.
     

  • One always-@ (posedge clk …) per register set; non-blocking assignments only.
     

  • Optional SVA:
     

systemverilog

​

assert property (@(posedge clk) disable iff (arst || ld)

  en && dir_q  |-> Q==$past(Q)+1);

assert property (@(posedge clk) disable iff (arst || ld)

  en && !dir_q |-> Q==$past(Q)-1);

​

14) How to document timing for implementers?

Refined answer:
Provide a concise timing spec:

  • Setup/Hold for en, dir, ld, and D relative to clk.
     

  • Recovery/Removal for arst deassertion.
     

  • Min pulse width for clk and resets.
     

CDC rules if any inputs are asynchronous (and the synchronizer pattern to use).
Include a timing diagram (reset → load → count up → count down) and the exact priority order.

​

15) What coding patterns should be avoided?

Refined answer:

  • Gated clocks (AND/OR the clock) → skew/glitches. Use enables or ICG cells.
     

  • Blocking = in sequential always blocks → sim/synth mismatch. Use <=.
     

  • Combinational loops / inferred latches (incomplete if/case in comb logic).
     

  • Unsized constants / width mismatches (explicit sizes keep arithmetic correct).
     

  • Feeding un-synchronized async controls directly into the counter.​​​

Bonus: A tidy up/down counter skeleton (with best practices)

 Verilog

module updown #(

  parameter N = 8,

  parameter SATURATE = 1'b0

)(

  input  wire         clk,

  input  wire         arst,

  input  wire         en,

  input  wire         dir,     // may be async upstream

  input  wire         ld,

  input  wire [N-1:0] D,

  output reg  [N-1:0] Q,

  output reg          ovf, unf

);

  localparam [N-1:0] MAX = {N{1'b1}};

 

  // Optional: dir synchronizer

  reg dir_s1, dir_s2;

  always @(posedge clk or posedge arst)

    if (arst) {dir_s1,dir_s2} <= 2'b00;

    else      {dir_s1,dir_s2} <= {dir,dir_s1};

  wire dir_q = dir_s2;

 

  always @(posedge clk or posedge arst) begin

    if (arst) begin

      Q <= '0; ovf <= 1'b0; unf <= 1'b0;

    end else if (ld) begin

      Q <= D; ovf <= 1'b0; unf <= 1'b0;

    end else if (en) begin

      ovf <= dir_q  && (Q==MAX);

      unf <= ~dir_q && (Q=='0);

      if (dir_q)      Q <= (SATURATE && Q==MAX) ? MAX : (Q + 1'b1);

      else            Q <= (SATURATE && Q==0)   ? '0  : (Q - 1'b1);

    end else begin

      ovf <= 1'b0; unf <= 1'b0; // hold

    end

  end

endmodule

Interview Questions

Up_Down_Counter.png

Verilog LAB - Down Counter 

Verilog LAB -> Mod-N Counter 

© Copyright 2025 VLSI Mentor. All Rights Reserved.©

Connect with us

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