4-Bit Synchronous Counter in Verilog (with Enable & Load)
1. Introduction​
In digital design, counters are essential for keeping track of events, generating sequences, and dividing frequencies.
​​
While asynchronous (ripple) counters are easy to implement, they suffer from propagation delays and glitches.
​
A synchronous counter solves these problems by clocking all flip-flops simultaneously, ensuring faster and more reliable operation.
​
In this post, we’ll design a 4-bit synchronous binary up-counter with:
​​
-
Enable control (count only when enabled)
-
Synchronous load (load custom values on the next clock)
-
Asynchronous reset (clear the counter immediately)
2. What is a Synchronous Counter?
A synchronous counter is a sequential circuit where:
​
-
Every flip-flop is triggered by the same clock edge.
-
Combinational logic determines the next value for each flip-flop.
-
All outputs update simultaneously at each clock pulse.
​
Advantages over asynchronous counters:
​
-
No ripple delays.
-
Predictable timing.
-
Easier static timing analysis in VLSI and FPGA designs.
3. Problem Statement
We want to design a 4-bit synchronous up-counter with:
​
-
​​Range: Counts from 0 to 15 (wraps around).
​
-
Inputs:
-
clk → Clock (positive edge triggered)
-
arst → Asynchronous reset (active high)
-
en → Enable (counts only when high)
-
ld → Synchronous load
-
D[3:0] → Parallel data input (value to load)
-
​
-
Output:
-
​Q[3:0] → Current counter value
-
​
Functional Requirements
​
-
On arst = 1 → Clear Q immediately to 0000.
-
On ld = 1 → Load Q with D at next clock.
-
On en = 1 → Increment Q at next clock.
-
If none of the above → Q holds its value.
4. Verilog Code
Verilog
module sync4 (
input wire clk, // Clock input
input wire arst, // Asynchronous reset
input wire en, // Enable counting
input wire ld, // Synchronous load
input wire [3:0] D, // Parallel load data
output reg [3:0] Q // Counter output
);
always @(posedge clk or posedge arst) begin
if (arst)
Q <= 4'b0; // Clear counter immediately
else if (ld)
Q <= D; // Load new value
else if (en)
Q <= Q + 1'b1; // Increment counter
else
Q <= Q; // Hold value
end
endmodule
Code Explanation
​
1.Sensitivity List:
​
-
posedge clk → All updates happen on rising clock edge.
-
posedge arst → Asynchronous reset triggers immediately.
​
2.Priority Order:
​
-
Reset (arst) has highest priority.
-
Load (ld) is checked next.
-
Enable (en) follows.
-
Else, Q stays unchanged.
​
3.Counting:
​
-
When enabled, the counter increments by 1 modulo 16.
4.1) Module interface: what each port means
​​
Verilog
module sync4 (
input wire clk, // system clock (posedge triggered)
input wire arst, // async reset, active-high
input wire en, // sync enable (count when 1)
input wire ld, // sync load (load D at next clock)
input wire [3:0] D, // parallel load data
output reg [3:0] Q // current count
);
-
clk: All flip-flops sample on the rising edge—that’s what makes it synchronous.
-
arst: Asynchronous reset. When it goes high, Q clears immediately, no clock needed.
-
ld: Synchronous load. If high at a rising edge, Q becomes D.
-
en: Synchronous enable. If high at a rising edge (and ld is 0), Q increments.
-
Q: 4-bit count (wraps automatically 15→0).
​
​
4.2) The sequential logic: priority, behavior, and wrap-around
Verilog
always @(posedge clk or posedge arst) begin
if (arst)
Q <= 4'b0; // highest priority: clear now
else if (ld)
Q <= D; // next priority: load D at this edge
else if (en)
Q <= Q + 1'b1; // then: increment
else
Q <= Q; // otherwise hold (this line is optional)
end
Priority (important in interviews)
​
-
arst wins over everything (asynchronous & highest priority).
-
ld wins over en (if both 1 at a clock edge, you load, you don’t count).
-
en increments when ld=0.
-
Else, hold.
​
Tip: The final Q <= Q; is functionally redundant; tools infer “hold” when nothing assigns. It’s okay to leave it for readability.
​
​
Why Q + 1'b1 works
​
-
Q is 4-bit. Adding 1'b1 (1-bit) yields a 4-bit result; overflow is dropped—so the count wraps: 1111 + 0001 → 0000.
-
Best practice: size the constant to match the bus: Q <= Q + 4'd1; (avoids lint nags).
​
​
Non-blocking assignment (<=)
​
-
Correct for sequential logic so all registers update together at the clock edge and simulation matches silicon behavior.
​
​
4.3) How synthesis maps this to hardware
​
Think of the datapath at each flip-flop:
​
-
The tool infers:
-
A 4-bit adder for Q + 1.
-
A mux to choose D (when ld=1) or Q+1 (when en=1) or Q (hold).
-
Four flip-flops with an async clear tied to arst.
-
​
-
Many FPGAs also infer a clock-enable on the FFs for the en branch.
​
​
4.4) Asynchronous reset: timing cautions
​
-
Since arst is async, asserting it is immediate—great for global POR.
​
-
Deassertion must respect recovery/removal times relative to clk. In real chips/FPGAs you’ll often:
-
Synchronize reset deassertion into the clock domain, or
-
Use a synchronous reset if your flow prefers simpler STA.
-
​
​
4.5) Testbench walkthrough (timeline + what you should see)
Verilog
reg clk = 0;
reg arst = 1;
reg en = 0, ld = 0;
reg [3:0] D = 4'b0000;
wire [3:0] Q;
always #5 clk = ~clk; // 10ns period (posedge at 5,15,25,...)
initial begin
// t=0: arst=1 → Q is 0000 immediately; posedge at 5ns also keeps Q=0
#12 arst = 0; // t=12: release reset; first useful edge at 15ns
#10 D = 4'b1010; ld = 1; // t=22: request load of 10
// next posedge is 25ns → Q becomes 1010
​
#10 ld = 0; en = 1; // t=32: start counting
// posedges 35,45,55,65,75 → Q: 1011, 1100, 1101, 1110, 1111
#50 en = 0; // t=82: stop incrementing; Q holds
#20 D = 4'b0101; ld = 1; // t=102: request load of 5
// next posedge 105ns → Q becomes 0101
#10 ld = 0; en = 1; // t=112: count from 0101 at 115,125,135 → 0110,0111,1000
#30 arst = 1; // t≈142: async clear right away → Q=0000 (not waiting for clk)
#10 arst = 0; en = 1; // t≈152: resume counting at next posedge (155ns → Q=0001)
#20 $finish;
end
initial
$monitor("Time=%0t | arst=%b | ld=%b | en=%b | D=%b | Q=%b",
$time, arst, ld, en, D, Q);
What you’ll observe:
​
-
Reset forces Q=0000 immediately.
-
Load captures D on the next rising edge.
-
Enable makes Q increment on each rising edge; when en=0, Q holds.
-
When ld=1 and en=1, load wins (by priority in the RTL).
​
​
4.6) Common pitfalls (and how your code avoids them)
​
-
Gated clocks: Not used (good). Everything is on clk.
-
Blocking = in sequential: You used non-blocking <= (correct).
-
Ambiguous priority: Explicit if/else if cleanly sets reset > load > enable > hold.
-
Mismatched widths: Safer to write Q + 4'd1 (though 1'b1 works here).
-
Async reset release: Mind recovery/removal in real silicon—consider reset synchronizers for robust designs.
​
​table (at a rising clock edge)
4.7) Quick control truth

4.8) Want a parameterized version?
​
Super handy when you need N-bit counters:
Verilog
module sync_counter #(
parameter N = 4
)(
input wire clk,
input wire arst,
input wire en,
input wire ld,
input wire [N-1:0] D,
output reg [N-1:0] Q
);
always @(posedge clk or posedge arst) begin
if (arst) Q <= {N{1'b0}};
else if (ld) Q <= D;
else if (en) Q <= Q + {{(N-1){1'b0}},1'b1}; // or Q + 'd1
end
endmodule
5. Testbench
Verilog
module tb_sync4;
reg clk = 0;
reg arst = 1;
reg en = 0;
reg ld = 0;
reg [3:0] D = 4'b0000;
wire [3:0] Q;
sync4 tb (
.clk(clk), .arst(arst), .en(en), .ld(ld), .D(D), .Q(Q)
);
always #5 clk = ~clk; // Clock period = 10ns
initial begin
arst = 1; en = 0; ld = 0; D = 4'b0000;
#12 arst = 0; // Release reset
#10 D = 4'b1010; ld = 1; // Load 10 at next clock
#10 ld = 0; en = 1; // Enable counting
#50 en = 0; // Stop counting
#20 D = 4'b0101; ld = 1; // Load 5
#10 ld = 0; en = 1; // Start counting again
#30 arst = 1; // Reset
#10 arst = 0; en = 1; // Resume counting
#20 $finish;
end
initial
$monitor("Time=%0t | arst=%b | ld=%b | en=%b | D=%b | Q=%b",
$time, arst, ld, en, D, Q);
endmodule
6. Simulation Behavior
When running the testbench:
-
Reset phase: Q resets to 0000.
-
Load phase: Q loads 1010 (decimal 10).
-
Count phase: Q increments each clock when enabled.
-
Hold phase: Q stays constant when en = 0.
-
Load again: Loads 0101 (decimal 5).
-
Final reset: Clears back to 0000.
7. Why Synchronous Counters Are Preferred
​
-
No ripple effect → All bits change at the same time.
-
Faster operation → Delay limited only by combinational logic.
-
Better for FPGAs/ASICs → Tools optimize synchronous designs easily.
-
Easier to debug → Predictable timing.
​
​
8. Summary
​
In this design:
​
-
Asynchronous reset clears immediately.
-
Synchronous load allows precise control over the count value.
-
Enable control saves power by stopping unnecessary toggling.
​
This structure is FPGA-friendly, meets synthesis requirements, and scales easily for more bits by changing vector size.
Synchronous Counters – Common Interview Questions & Answers
Q1. Why are synchronous counters preferred in high-speed designs?
​
Answer:
In synchronous counters, all flip-flops share the same clock signal and update their outputs simultaneously on the same clock edge. This eliminates the cumulative ripple delay that plagues asynchronous counters, where each flip-flop’s output clocks the next one.
-
Key Benefits:
-
Higher Maximum Clock Frequency: No sequential toggling delays → higher speed.
-
Predictable Timing: All outputs change together, making timing analysis easier.
-
Better for STA (Static Timing Analysis): Single clock domain simplifies verification.
-
​
Example: A 4-bit ripple counter’s MSB changes only after 3 FF delays. In a synchronous design, all bits change within one FF delay from the clock edge.
​
​
Q2. How do you create an enable signal that selectively controls specific bits in a synchronous counter?
​
Answer:
By generating bit-level enable signals using combinational logic that detects conditions for each bit to toggle.
​
-
Example:
-
LSB toggles every clock when en=1.
-
Bit1 toggles when en=1 and Bit0=1.
-
Bit2 toggles when en=1 and Bit0=1 and Bit1=1, etc.
-
This reduces unnecessary toggling, which:
-
Saves dynamic power.
-
Minimizes switching noise.
Verilog
if (en_bit2) Q[2] <= ~Q[2];
where en_bit2 = en & Q[1] & Q[0];
Q3. What is the purpose of synchronous load in counters?
​
Answer:
Synchronous load lets you preset the counter to a specific value on the next clock edge.
-
Use cases:
-
Start counting from a non-zero value.
-
Implement mod-N counters where the counter jumps to 0 or another value after reaching N-1.
-
Sequence control in FSMs (Finite State Machines).
-
Because it’s clocked, it avoids glitches that might occur with asynchronous preset.
Q4. How is modulo-N operation implemented in synchronous counters?
​
Answer:
Detect terminal count (N-1) using combinational logic:
verilog
Copy
if (Q == N-1) Q <= 0; else Q <= Q + 1;
or use synchronous load:
Verilog
if (Q == N-1) Q <= START_VAL;
This keeps the count sequence length fixed to N.
Q5. How can combinational delay be minimized in large synchronous counters?
​
Answer:
-
Use built-in FPGA counter primitives or DSP blocks.
-
Apply carry lookahead instead of ripple carry inside adders.
-
Break large counters into hierarchical stages.
-
Use Gray coding to minimize logic per stage.
In ASICs, large binary adders can be optimized using parallel prefix adders like Kogge–Stone.
Q6. Can synchronous counters use one-hot encoding, and why?
​
Answer:
Yes. In one-hot encoding, each state has exactly one active flip-flop.
-
Advantages:
-
Next-state logic is simple → faster operation.
-
Reduced logic depth → higher fmax.
-
-
Disadvantage: ​​
​
-
Needs N flip-flops for N states (vs logâ‚‚N in binary).
-
Used in high-speed FSM-based counters.
Q7. How do you test synchronous counters for race conditions?
​
Answer:
-
Apply randomized enable pulses and check that only one increment occurs per clock.
-
Use SystemVerilog assertions:
systemverilog
​
assert property (@(posedge clk) en |-> Q == $past(Q)+1);
​
-
Perform gate-level simulation with timing to catch hazards.
Because all FFs are clocked together, race conditions are rare if RTL is correct—but they can occur from poor coding (e.g., mixing blocking and non-blocking assignments incorrectly).
Q8. What is lookahead carry in counters, and why is it important?
​
Answer:
Lookahead precomputes carry signals for multiple bits in parallel, avoiding sequential carry propagation.
-
Benefit: Much faster increment/decrement in wide counters.
-
Implementation: Similar to ALU carry lookahead logic.
Used in high-frequency designs where standard adder delays are too long.
Q9. How can you synthesize a down-counting synchronous counter from the same code as an up-counter?
​
Answer:
Add a direction control signal:
verilog
​
if (dir) Q <= Q - 1;
else Q <= Q + 1;
Ensure proper wrap-around:
-
When counting down from 0 → go to MAX.
When counting up from MAX → go to 0.
Q10. Why are non-blocking assignments (<=) recommended in sequential always blocks?
​
Answer:
-
Models real hardware where all FFs capture inputs at the same clock edge.
-
Avoids race conditions in simulation.
-
Prevents unintended dependencies between registers in the same block.
Blocking = can cause simulation to differ from synthesized logic in multi-register always blocks.
​
​
Q11. How do you add overflow flags to synchronous counters?
​
Answer:
Detect terminal count before the increment:
verilog code:
​
overflow <= (Q == MAX) & en;
Register it so it aligns with the incremented value:
verilog code:
​
always @(posedge clk) if (arst) ovf <= 0; else ovf <= (Q == MAX) & en;
Flag is high for one cycle when overflow happens.
​
​
Q12. What key timing checks are necessary for synchronous counters?
​
Answer:
-
Setup & Hold Time at FF inputs.
-
Clock-to-Q delay of FFs.
-
Combinational path delay from Q → next D input.
For high fmax, total:
nginx
​
Tclk ≥ Tco + Tcomb + Tsetup
Hold violations usually aren’t a problem if using standard synthesis, but must be checked.
​
​
Q13. How is an up/down synchronous counter implemented?
​
Answer:
Combine direction control with arithmetic:
verilog
Q <= Q + (dir ? -1 : 1);
​
For mod-N up/down:
-
Wrap at both 0 and N-1.
-
Adjust logic to detect boundary conditions.
​
Q14. How can power consumption be reduced in synchronous counters?
​
Answer:
-
Use clock enables instead of running all the time.
-
Gray code to minimize bit toggles.
-
Segmented counting: disable higher bits until lower bits overflow.
-
Apply clock gating cells in ASIC flows.
​
Q15. What are effective testbench strategies for synchronous counters?
​
Answer:
-
Directed tests for:
-
Normal count
-
Load operation
-
Reset behavior
-
Overflow
-
Up/Down switching
-
-
Random tests to find edge cases.
-
Assertions for:
-
Correct next-state behavior
-
No glitches on Q
-
Waveform inspection to verify simultaneous output changes.
