top of page

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)

​

  1. arst wins over everything (asynchronous & highest priority).
     

  2. ld wins over en (if both 1 at a clock edge, you load, you don’t count).
     

  3. en increments when ld=0.
     

  4. 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

Sync_counter_table.png

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:

 

  1. Reset phase: Q resets to 0000.
     

  2. Load phase: Q loads 1010 (decimal 10).
     

  3. Count phase: Q increments each clock when enabled.
     

  4. Hold phase: Q stays constant when en = 0.
     

  5. Load again: Loads 0101 (decimal 5).
     

  6. 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.

Verilog LAB - Ripple Counter

Verilog LAB - Up Counter

© Copyright 2025 VLSI Mentor. All Rights Reserved.©

Connect with us

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