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:
-
Asynchronous reset (highest)
-
Synchronous load (ld)
-
Enable (en) → count (up/down)
-
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
