Designing a Parameterizable N-bit Up Counter in Verilog
Counters are among the most common building blocks in digital systems — from timers and event trackers to address generators in memory systems. In this article, we’ll implement an N-bit up counter in Verilog, make it parameterizable, and explore its design and testbench in detail.
What is an Up Counter?
An up counter is a sequential circuit that increments its binary value by one on each active clock edge when enabled.
​
-
Once it reaches its maximum value, it wraps around to zero (modulo operation).
-
It’s often used for counting events, generating timing sequences, or indexing through arrays.
3. Problem Statement
We want to design an N-bit up counter with:
-
Parameterizable bit width (default: 8 bits)
-
Synchronous enable control
Asynchronous reset for immediate clearing
Specifications

Expected Result
-
Counter increments when en is asserted.
-
Stops counting when en=0.
-
Resets immediately when arst=1.
-
Rolls over from maximum value → 0 automatically.
Verilog Implementation
Verilog
module upcounter #(
parameter N = 8 // Number of bits, default = 8
)(
input wire clk, // Clock input
input wire arst, // Asynchronous reset (active high)
input wire en, // Enable signal
output reg [N-1:0] Q // Counter output
);
always @(posedge clk or posedge arst) begin
if (arst)
Q <= {N{1'b0}}; // Reset counter to 0
else if (en)
Q <= Q + 1'b1; // Increment counter
end
endmodule
Code Explanation
​
1.Parameterization
parameter N = 8
​​​
-
Allows the designer to change the counter width without modifying the core code.
-
{N{1'b0}} automatically creates an N-bit zero value.
​
2.Asynchronous Reset
always @(posedge clk or posedge arst)
​​​
-
Monitors both the clock and the reset signal.
-
If arst is high, the counter resets immediately, without waiting for a clock edge.
3.Enable Control
else if (en)
Q <= Q + 1'b1;
​​​
-
When en=1, the counter increments on each clock cycle.
-
If en=0, the counter holds its current value.
​
​4.Wrap-around Behavior
​
Since we’re using fixed-width binary arithmetic, overflow automatically wraps the counter to 0.
Verilog Testbench
Verilog
module tb_upcounter;
parameter N = 8;
reg clk = 0;
reg arst = 1;
reg en = 0;
wire [N-1:0] Q;
// Instantiate the counter
upcounter #(.N(N)) tb (
.clk(clk),
.arst(arst),
.en(en),
.Q(Q)
);
// Clock generation: 10 ns period
always #5 clk = ~clk;
initial begin
// Initial reset
arst = 1; en = 0;
#15 arst = 0;
// Start counting
en = 1; #100;
// Disable counting
en = 0; #30;
// Resume counting
en = 1; #50;
// Trigger reset mid-count
arst = 1; #10;
arst = 0; #30;
$finish;
end
initial
$monitor("Time=%0t | arst=%b | en=%b | Q=%0d (%b)",
$time, arst, en, Q, Q);
endmodule
Testbench Walkthrough
1.Clock Generation
always #5 clk = ~clk;
-
Produces a 10 ns clock period (100 MHz).
​
​
2.Reset and Enable Sequence
​
-
arst starts high → counter is held at 0.
-
After 15 ns, reset is released.
-
en=1 → counter starts incrementing.
-
Later, en is toggled to pause and resume counting.
​
3.Monitoring Outputs
​
$monitor("Time=%0t | arst=%b | en=%b | Q=%0d (%b)", ...);
​
-
Displays simulation time, control signals, and counter values in decimal and binary.
​
​
Expected Simulation Output (Partial)
​
Time=0 | arst=1 | en=0 | Q=0 (00000000)
Time=15 | arst=0 | en=1 | Q=0 (00000000)
Time=25 | arst=0 | en=1 | Q=1 (00000001)
Time=35 | arst=0 | en=1 | Q=2 (00000010)
...
Time=115| arst=0 | en=0 | Q=10 (00001010)
...
Time=175| arst=1 | en=1 | Q=0 (00000000)
​
​
Key Takeaways
​
-
Parameterization makes your design reusable for different bit widths.
-
Asynchronous reset ensures immediate clearing, even without a clock.
-
Enable control allows pausing/resuming without losing the count.
-
Wrap-around behavior comes naturally from fixed-width binary arithmetic.
Interview Questions & Answers on Parameterizable Up Counters
​
Q1. Why parameterize counters?
​
Answer:
Parameterization allows you to design one reusable counter module that can work for any bit-width simply by changing a parameter value, without modifying the code.
-
Advantages:
-
Saves development time — no need to rewrite multiple versions.
-
Reduces human errors in manual code changes.
-
Improves maintainability and scalability in large projects.
-
-
Example: You can have an 8-bit, 16-bit, or 32-bit counter by just setting parameter N = 8, 16, or 32.
​
​
Q2. How do you handle arithmetic when the counter width N = 1?
​
Answer:
For a 1-bit counter, the expression Q <= Q + 1'b1; still works, toggling Q between 0 and 1.
-
Considerations:
-
Edge cases arise if your logic assumes multiple bits (e.g., Q[N-1:0] comparisons).
-
Synthesis tools optimize this into a single flip-flop with toggle logic.
-
Always test 1-bit configurations to confirm correct rollover behavior.
-
​
​
Q3. How can you detect overflow in an up counter?
​
Answer:
Overflow occurs when the counter value reaches all 1s ({N{1'b1}}).
Detection Logic:
verilog code:
overflow = (Q == {N{1'b1}});
​​​
-
Before incrementing, if overflow is high, the next value will wrap to zero.
-
Overflow flags are useful in timers, packet counters, or data overflow detection.
​
​
Q4. What are the pros and cons of wide up counters in FPGAs?
​
Answer:
-
Pros:
-
Simple to understand and implement.
-
Can be directly mapped to FPGA hardware.
-
-
Cons:
-
Large counters consume more flip-flops and routing resources.
-
Longer carry chains increase delay and reduce maximum frequency.
-
For very wide counters, block RAM-based counters or DSP adders may be more efficient.
​
Q5. How does synthesis optimize the statement Q <= Q + 1?
Answer:
Synthesis tools map this to fast adder circuits:
-
FPGA: Uses built-in carry chains for speed.
-
ASIC: Maps to optimized adder cells in standard cell libraries.
This ensures increment operations are performed efficiently, minimizing logic delay.
​
Q6. How do you add an enable signal without gating the clock?
​
Answer:
Never physically gate the clock — it causes clock skew and timing hazards.
Instead:
verilog
​
always @(posedge clk or posedge arst) begin
if (arst)
Q <= 0;
else if (en)
Q <= Q + 1;
end
-
This keeps the clock path clean and only changes data when en is active.
​
Q7. What simulation checks should be included for counters?
​
Answer:
You should verify:
-
Reset behavior (async or sync).
-
Increment only when enabled.
-
Hold value when disabled.
-
Correct wrap-around at maximum value.
-
Stability under random resets and enables.
-
Corner cases (e.g., reset during increment).
​
​
Q8. How to implement a saturating up counter?
​
Answer:
Instead of wrapping, hold the maximum value:
verilog
​
if (Q != MAX && en)
Q <= Q + 1;
-
Useful in applications like rate limiters or event counters where you don’t want rollover.
​
​
Q9. Why might you prefer Gray code counters in ADC sampling applications?
​
Answer:
Gray code changes only one bit per increment, reducing switching noise and avoiding glitches in multi-bit transitions.
-
In ADC sampling, this minimizes errors when reading data asynchronously across clock domains.
​
​
Q10. How do you make a counter synthesizable for ASIC flow?
​
Answer:
-
Use non-blocking assignments (<=) in sequential logic.
-
Avoid simulation-only constructs like #delays or $display in the main logic.
-
Define reset behavior explicitly.
-
Ensure parameters are constants at synthesis time.
-
Follow ASIC timing and area constraints for high performance.
​
​
Q11. Can you implement an up counter with an LFSR for pseudo-counting?
​
Answer:
Yes, Linear Feedback Shift Registers (LFSRs) produce pseudo-random sequences but not linear counts.
-
Use Cases: PRBS generation, random number generation, hardware self-test.
-
Not suitable when you need exact sequential counting.
​
​
Q12. How do you create test vectors that stress an up counter?
​
Answer:
-
Apply long enable pulses for continuous counting.
-
Toggle en randomly to test hold/resume states.
-
Apply resets at random points mid-count.
-
Test wrap-around by setting count near max value.
-
In parameterized counters, test N=1, N=4, N=16, etc.
​
​
Q13. How do you efficiently merge compare and increment logic?
​
Answer:
Combine them in one block:
verilog
​
if (Q == MAX)
Q <= 0;
else
Q <= Q + 1;
-
Reduces logic levels and ensures faster timing closure.
​
​
Q14. What are common beginner bugs in counter designs?
​
Answer:
-
Clock gating instead of using enables.
-
Forgetting reset logic.
-
Using blocking assignments = in sequential blocks.
-
Not handling overflow or wrap-around.
-
Incorrect sensitivity lists in combinational logic (pre-SystemVerilog).
​
​
Q15. How do you support both synchronous load and enable in a counter?
​
Answer:
Use priority order inside the sequential block:
verilog
​
if (arst)
Q <= 0;
else if (load)
Q <= D;
else if (en)
Q <= Q + 1;
-
Ensures load overrides enable when both are active.
-
This makes counter behavior predictable and easy to debug.
​​
​
