Understanding Asynchronous (Ripple) Counters in Verilog
Counters are one of the most important building blocks in digital electronics and VLSI systems. They are used for event counting, frequency division, address generation, and timing control. In this post, we’ll explore Asynchronous Counters, also known as Ripple Counters, and implement a 4-bit binary up-counter in Verilog.
1. What is an Asynchronous (Ripple) Counter?
An asynchronous counter is a type of counter where flip-flops do not share the same clock signal. Instead:
-
The first flip-flop (LSB) receives the external clock directly.
-
Each subsequent flip-flop’s clock is driven by the output of the previous flip-flop.
-
Bit changes propagate like a ripple, which is why it’s called a ripple counter.
This structure makes ripple counters simple and hardware-efficient, but they suffer from propagation delay—the time taken for the change to ripple through all flip-flops.
2. How Does a Ripple Counter Work?
Let’s take a 4-bit ripple up-counter as an example:
​
-
Q0 (LSB) toggles on every rising edge of the external clock.
-
Q1 toggles when Q0 transitions from 1 → 0.
-
Q2 toggles when Q1 transitions from 1 → 0.
-
Q3 (MSB) toggles when Q2 transitions from 1 → 0.
This creates a binary counting sequence from 0000 to 1111 and then back to 0000.
3. Key Specifications of a Ripple Counter
-
Counting Range: 000 to 2n−12^n - 12n−1 (n = number of bits)
-
Modulus (MOD): 2n2^n2n
-
Triggering: Each stage triggered by the output of the previous stage
-
Reset: Asynchronous reset to clear all bits to 0
-
Delay: Bit updates occur sequentially, not simultaneously
4. Problem Statement
We need to design a 4-bit asynchronous binary up-counter in Verilog with the following requirements:
-
Inputs:
-
clk → external clock
-
arst → active-high asynchronous reset
-
-
Output:
-
Q[3:0] → current count value in binary
-
-
Behavior:
-
Increment count on each clock edge
-
Reset to 0000 when arst is high
-
-
Implementation Style:
-
Use edge-triggered flip-flops in ripple configuration
-
Avoid gated clocks to remain synthesis-friendly
-
5. Verilog Code - 4-Bit Ripple Counter
Verilog
module ripple4 (
input wire clk, // External clock
input wire arst, // Active-high async reset
output reg [3:0] Q // Counter output
);
// LSB Flip-Flop
always @(posedge clk or posedge arst) begin
if (arst)
Q[0] <= 1'b0;
else
Q[0] <= ~Q[0];
end
// Second bit
always @(posedge Q[0] or posedge arst) begin
if (arst)
Q[1] <= 1'b0;
else
Q[1] <= ~Q[1];
end
// Third bit
always @(posedge Q[1] or posedge arst) begin
if (arst)
Q[2] <= 1'b0;
else
Q[2] <= ~Q[2];
end
// MSB
always @(posedge Q[2] or posedge arst) begin
if (arst)
Q[3] <= 1'b0;
else
Q[3] <= ~Q[3];
end
endmodule
Understanding the Ripple Counter (ripple4)
​​
module ripple4 (
input wire clk,
input wire arst,
output reg [3:0] Q
);
-
clk: external clock drives the least-significant bit (LSB).
-
arst: active-high asynchronous reset—forces the counter to 0 immediately, independent of clk.
-
Q[3:0]: 4-bit count output (Q[0] = LSB, Q[3] = MSB).
​​
​
Bit-by-bit behavior (ripple effect)
​
Bit 0 (LSB): toggles on every external clock edge
​
always @(posedge clk or posedge arst) begin
if (arst)
Q[0] <= 1'b0;
else
Q[0] <= ~Q[0];
end
-
On posedge clk: invert Q[0] → it toggles every clock → frequency = f_clk/2.
-
On arst: immediately clear to 0.
Bit 1: clocked by Q[0] (derived clock)
​
always @(posedge Q[0] or posedge arst) begin
if (arst)
Q[1] <= 1'b0;
else
Q[1] <= ~Q[1];
end
-
Toggles on rising edges of Q[0], i.e., every second external clock pulse.
-
Effective frequency = f_clk/4.
Bit 2: clocked by Q[1]
​
always @(posedge Q[1] or posedge arst) begin
if (arst)
Q[2] <= 1'b0;
else
Q[2] <= ~Q[2];
end
-
Toggles on Q[1] rising edges → frequency = f_clk/8.
Bit 3 (MSB): clocked by Q[2]
​
always @(posedge Q[2] or posedge arst) begin
if (arst)
Q[3] <= 1'b0;
else
Q[3] <= ~Q[3];
end
-
Toggles on Q[2] rising edges → frequency = f_clk/16.
What “ripple” means here
​
-
Only Q[0] uses the external clock.
-
Each higher bit uses the previous bit as its clock.
-
After a clk rising edge, you’ll see Q[0] change first, then (after propagation delay) Q[1], then Q[2], then Q[3]. That sequential settling is the ripple.
Counting sequence
The 4 bits together count 0 → 15 → 0 ... in binary:
0000, 0001, 0010, 0011, 0100, ..., 1111, 0000
​
Notes on reset (arst)
-
Because reset is asynchronous, any posedge arst immediately clears the bit, regardless of its clock source.
-
This guarantees a known startup state (all zeros).
Practical synthesis note
​
-
This is a textbook ripple counter. It uses derived clocks (Q[0], Q[1], Q[2]) as clock inputs to other flops.
-
That’s fine for learning, simulation, or low-speed discrete logic.
-
In FPGA/ASIC production RTL, derived clocks are discouraged: they complicate clock-tree/timing. Prefer a synchronous counter where all flops use clk and you compute the next state combinationally.
5. Verilog Code - 4-Bit Ripple Counter
Verilog
module tb_ripple4;
reg clk = 0;
reg arst = 1;
wire [3:0] Q;
ripple4 tb_ripple4_VM (
.clk(clk),
.arst(arst),
.Q(Q)
);
// Clock: 10 ns period
always #5 clk = ~clk;
initial begin
#10 arst = 0; // Release reset after 10 ns
#500; // Run for 500 ns
$display("Final Counter Value: %b", Q);
$finish;
end
// Monitor counter value
initial begin
$monitor("Time: %0t | Counter: %b", $time, Q);
end
// Waveform dump
initial begin
$dumpfile("tb_ripple4.vcd");
$dumpvars(1, tb_ripple4);
end
endmodule
Understanding the Testbench (tb_ripple4)
​
reg clk = 0;
reg arst = 1;
wire [3:0] Q;
ripple4 tb_ripple_VM ( .clk(clk), .arst(arst), .Q(Q) );
-
Instantiates the DUT (ripple4) and connects signals.
Clock generator
​
always #5 clk = ~clk;
-
Generates a 10 time-unit period clock (toggle every 5 → period = 10).
-
So f_clk = 1/10 TU.
Reset and run sequence
​
initial begin
#10 arst = 0; // hold reset high for the first 10 TU
#500; // simulate for 500 TU more
$display("Final Counter Value: %b", Q);
$finish;
end
-
Keeps the counter in reset for the first 10 TU so Q starts from 0000.
-
Runs long enough to observe many counts (500/10 = 50 clock cycles).
Live monitor
​
initial begin
$monitor("Time: %0t | Counter: %b", $time, Q);
end
-
Prints the time and current count whenever Q changes.
-
You’ll see the binary count rolling and, if you open a waveform, the ripple (bit-by-bit settling).
6. What you’ll observe in the waveform
​
-
Q[0] flips every clock edge.
-
Q[1] flips on alternate Q[0] rising edges.
-
Q[2] and Q[3] flip correspondingly slower.
On each clock, for a brief sim time, the bus Q may pass through intermediate values (due to ripple delays) before settling to the final count.
7. Common pitfalls (and how your code avoids them)
-
Uninitialized state → avoided by arst.
-
Gated clocks / glitches → here you use clean flops; the “gating” is really clocking from Qn, which is a valid flop output (still, not ideal for high-speed RTL).
-
Sensitivity lists → correctly use posedge events and include arst for async reset.