top of page

Designing a Parameterizable N-bit Down Counter in Verilog

Counters are everywhere in digital systems—timers, protocol engines, address generators, you name it. While up counters increment, a down counter does the opposite: it decrements by one each clock (when enabled). In fixed-width binary, it naturally wraps from 0 to the maximum value (all 1s).

​

This post builds a reusable, parameterizable N-bit down counter (default N=8) with a synchronous enable and an asynchronous reset, and then walks through a concise testbench.

Problem Statement

  • Goal: Implement an N-bit down counter with:
     

    • Async reset (active-high) to clear immediately
       

    • Sync enable to step only when desired
       

    • Wrap-around on underflow (mod 2N2^N2N)
       

  • Interface:
     

    • Inputs: clk, arst, en
       

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

    • Parameter: N (default 8)

​

  • Behavior: On each posedge clk, if en=1 the counter decrements by 1. If it goes below 0, it wraps to 2^N - 1. When arst=1, Q clears to 0 immediately (no clock needed).

Verilog RTL

 Verilog

module downcounter #(

  parameter N = 8

)(

  input  wire           clk,

  input  wire          arst,   // active-high async reset

  input  wire           en,     // synchronous enable

  output reg  [N-1:0]   Q

);

  always @(posedge clk or posedge arst) begin

    if (arst)

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

    else if (en)

      Q <= Q - 1'b1;           // decrement on clock when enabled

    // else: hold (implicit)

  end

endmodule

How the RTL works (line by line)

​

  • Parameter N
    Sets the bit-width once; reuse the same module at 4/8/16+ bits by changing N. {N{1'b0}} is an N-bit zero literal.

     

  • Async reset (posedge arst)
    Including posedge arst in the sensitivity list makes reset asynchronous: asserting arst sets Q=0 right away, without waiting for a clock.

     

  • Enable logic
    Inside the posedge clk branch, we check en. If high, Q becomes Q - 1. If low, Q holds (no assignment needed).

     

  • Wrap-around “for free”
    Subtraction happens in fixed N-bit arithmetic. When Q=0 and you subtract 1, it underflows and hardware naturally yields 2^N - 1 (all 1s). That’s your wrap behavior.

     

Tip: Tools are fine with Q - 1'b1. If you want to be pedantic about widths, you can write Q <= Q - {{(N-1){1'b0}},1'b1}; or Q <= Q - 1;.

​

  • Non-blocking assignments (<=)

     Correct for sequential logic so all registers update together at the clock edge and simulation matches silicon.

 Verilog Testbench

 Verilog

module tb_downcounter;

  parameter N = 8;

 

  reg  clk  = 0;

  reg  arst = 1;

  reg  en   = 0;

  wire [N-1:0] Q;

 

  downcounter #(N) tb (

    .clk(clk), .arst(arst), .en(en), .Q(Q)

  );

 

  // 10 time-unit clock (toggle every 5)

  always #5 clk = ~clk;

 

  initial begin

    // Hold reset initially

    arst = 1; en = 0;

    #15;             // keep Q=0 for a bit

 

    // Release reset, start counting down

    arst = 0;

    en   = 1;        // decrement every posedge

    #100;

 

    // Pause counting (hold current Q)

    en = 0;

    #30;

 

    // Resume counting

    en = 1;

    #50;

 

    // Assert reset mid-run (clears immediately)

    arst = 1;

    #10;

    arst = 0;

 

    // Run a little more

    #30;

    $finish;

  end

​

  initial

    $monitor("Time=%0t | arst=%b | en=%b | Q=%0d (%b)",

             $time, arst, en, Q, Q);

endmodule

What this test is checking

  1. Reset behavior
     

    • With arst=1, the counter stays at 0.
       

    • On deasserting arst, counting is allowed.
       

  2. Enable behavior
     

    • en=1 → Q decrements each posedge.
       

    • en=0 → Q holds its value (no toggling).
       

  3. Wrap-around
     

    • If the simulation runs long enough to cross 0, you’ll see Q jump to 255 (for N=8), i.e., 1111_1111.
       

  4. Async reset mid-run
     

    • Asserting arst clears Q immediately, independent of the clock phase.
       

  5. Monitoring
     

    • $monitor prints time, reset, enable, and Q in decimal + binary so you can correlate behaviors quickly.
       

Synthesis & Hardware Notes

​

  • Speed: Q <= Q - 1 maps to carry chains on FPGAs or optimized adder cells in ASICs. Fast and compact.
     

  • N=1 edge case: A 1-bit down counter simply toggles (1 → 0 → 1…). Your code still works; just remember comparisons like Q == {N{1'b1}} become Q == 1'b1.
     

  • Async reset deassertion: In silicon, deassert reset cleanly (consider reset synchronizers) to meet recovery/removal timing.
     

  • No gated clocks: You did it right—use en inside the clocked block, never AND/OR the clock net.

Common Variations (easy extensions)

​

  • Synchronous load:


Add ld/D with priority over en:

 Verilog

else if (ld) Q <= D;

else if (en) Q <= Q - 1;

  • ​Up/Down counter:

​

Add a dir input:

 Verilog

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

  • ​Saturating down counter:

​

 Hold at zero instead of wrapping:

 Verilog

else if (en && Q != {N{1'b0}}) Q <= Q - 1;

Key Takeaways

​

  • A down counter with parameterizable width is compact, reusable, and synthesis-friendly.
     

  • Async reset gives immediate clearing; sync enable prevents unwanted toggles.
     

  • Fixed-width arithmetic gives you wrap-around “for free.”
     

  • The testbench proves reset, enable/hold, wrap, and mid-run reset behaviors.

Down Counter – Interview Questions & Answers (Detailed)

​

Q1. How to prevent underflow when not desired?

Detailed Answer:
By default, an N-bit binary down counter will wrap around when it decrements from 0—it jumps to 2^N - 1. This is fine for circular counting, but sometimes you want a saturating counter that stops at zero.
To implement it:

​

verilog

​

if (Q != {N{1'b0}})

    Q <= Q - 1'b1;

 

Here, the counter only decrements when Q > 0. This prevents underflow and ensures the value never goes negative (in hardware terms, never wraps to max).
Use Case: Countdown timers where reaching zero should halt rather than restart.

​

​

Q2. How to implement up/down mode with a single module?

Detailed Answer:
You can add a direction control signal (dir) to choose between increment and decrement:

verilog

​

if (en)

    Q <= dir ? Q + 1'b1 : Q - 1'b1;

 

Where:

  • dir = 1 → Count up
     

  • dir = 0 → Count down
    This approach allows the same counter hardware to work in both modes without duplicating code.
    Use Case: Bidirectional timers, position counters in motors, or sequence generators.

     

​

Q3. Why check arithmetic width when using signed/unsigned?

Detailed Answer:
Verilog defaults to unsigned for reg and wire unless declared signed. Mixing signed/unsigned signals can produce:

  • Wrong simulation results
     

  • Incorrect synthesis hardware
    For example:

     

verilog

​

reg signed [7:0] count;

count <= count - 1; // Works for signed countdown

 

If count was unsigned, negative values would wrap to large positives. Always match operand widths and sign interpretations.

​

​

Q4. Can borrow/borrow-out be used to cascade down counters?

Detailed Answer:
Yes. A borrow signal is the down-counter equivalent of an overflow carry in up counters. It asserts when the counter transitions from 0 to max:

verilog

​

assign borrow = (Q == 0) && en;

 

This borrow can be connected to the en of the next higher counter stage to cascade multiple counters into a wider one.

​

​

Q5. How to test underflow behavior?

Detailed Answer:

  • Initialize Q = 0.
     

  • Enable counting (en=1) and observe:
     

    • Wrap Mode: Q jumps to max value.
       

    • Saturating Mode: Q stays at 0.
      Include random enable and reset toggles in your testbench to confirm that edge cases are handled.

       

​

Q6. What about synthesis of subtraction by 1?

Detailed Answer:
Subtracting 1 in hardware is just adding a two’s complement of 1. Synthesis tools map:

​

verilog

​

Q - 1'b1

 

to the same fast carry-chain logic used for adders, so performance impact is minimal. On FPGAs, it often uses dedicated arithmetic slices.

​

Q7. How to design for minimal glitch on output?

Detailed Answer:

  • Use purely synchronous logic (no combinational loops feeding outputs directly).
     

  • Avoid gated clocks—use enables instead.
     

  • Use non-blocking assignments (<=) to update all registers together.
    This ensures outputs only change at clock edges, preventing short pulses or glitches.

     

​

​

Q8. How to support parallel load in down counter?

Detailed Answer:
Add a synchronous load (ld) and data input (D):

​

verilog

​

if (ld)

    Q <= D;

else if (en)

    Q <= Q - 1'b1;

 

The load operation should have priority over decrement to ensure deterministic behavior.

​

​

Q9. How to combine up/down and load flags?

Detailed Answer:
Use priority logic in your always block:

  1. Async reset
     

  2. Load
     

  3. Enable with direction control
     

  4. Hold state
    Example:

     

verilog

​

if (arst)

    Q <= 0;

else if (ld)

    Q <= D;

else if (en)

    Q <= dir ? Q + 1'b1 : Q - 1'b1;

 

This avoids unpredictable interactions between features.

​​

​

Q10. How to add terminal count flags for safety?

Detailed Answer:
Detect terminal values and output a flag:

verilog

​

assign min_flag = (Q == 0);

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

 

These flags help external logic decide when to stop or reverse counting.

​

Q11. Why are non-blocking assignments important here?

Detailed Answer:
Non-blocking (<=) assignments ensure all registers in the clocked block update simultaneously at the clock edge—exactly like flip-flops in real hardware. Using blocking (=) here can cause simulation mismatches and hidden bugs.

​

Q12. How to handle mixed-width arithmetic?

Detailed Answer:
Extend all operands to the same width:

​

verilog

​

Q <= Q - {{(N-1){1'b0}}, 1'b1}; // Zero-extend

 

If using signed arithmetic, sign-extend instead:

​

verilog

​

Q <= Q - {{(N-1){Q[N-1]}}, 1'b1};

 

This prevents unintended truncation or sign errors.

​

​

Q13. How to keep design testable?

Detailed Answer:

  • Add parallel load for setting known states.
     

  • Provide terminal count flags for observability.
     

  • Testbench should cover:
     

    • Reset
       

    • Enable/disable
       

    • Underflow
       

    • Load
       

    • Random toggling
      Good observability speeds up debugging.

       

​

Q14. How do metastability and timing violations affect down counters, and how can you prevent them?

Detailed Answer:
If inputs like en, dir, or ld come from a different clock domain without synchronization, flip-flops may go metastable—producing unpredictable outputs and corrupting the counter state.
Prevention:

  • Synchronize async inputs with two-stage flip-flops.
     

  • Use only one clock domain per counter.
     

  • Ensure setup/hold times are met via static timing analysis.
     

  • Avoid gated clocks.
     

​

Q15. When to use down counters versus up counters?

Detailed Answer:

  • Down Counters: Timers, countdown to events, resource tracking (e.g., remaining bytes).
     

  • Up Counters: Event counting, elapsed time, address generation.
    Some designs use both—an up counter for elapsed time and a down counter for remaining time, giving two perspecti
    ves.
    ​

​

Verilog LAB - Up Counter

Verilog LAB - Up/Down Counter

© Copyright 2025 VLSI Mentor. All Rights Reserved.©

Connect with us

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