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
-
Reset behavior
-
With arst=1, the counter stays at 0.
-
On deasserting arst, counting is allowed.
-
-
Enable behavior
-
en=1 → Q decrements each posedge.
-
en=0 → Q holds its value (no toggling).
-
-
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.
-
-
Async reset mid-run
-
Asserting arst clears Q immediately, independent of the clock phase.
-
-
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:
-
Async reset
-
Load
-
Enable with direction control
-
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 perspectives.​
​