Synchronous vs asynchronous, up/down, Mod-N, BCD, ring, and Johnson counters — with truth tables, timing diagrams, and working Verilog for every type.
A counter is a sequential logic circuit that progresses through a defined sequence of binary states on each clock edge. It is built from flip-flops — typically D or T type — connected so that each clock pulse advances the count by one. After reaching its maximum state, the counter wraps back to zero and repeats the sequence indefinitely.
An n-bit counter has 2ⁿ possible states, counted from 0 to 2ⁿ−1. A 4-bit counter counts 0 through 15 (0000 to 1111 in binary), then resets to 0000. Every real digital system uses counters: your CPU's program counter increments on every fetch, your UART uses a counter to time bit periods, and the address bus of every RAM controller is driven by a counter.
Counters are classified by:
In an asynchronous (ripple) counter, only the first flip-flop (Q0, LSB) is driven by the external clock. Every subsequent flip-flop uses the output of the previous flip-flop as its clock. The "clock pulse" ripples through the chain — hence the name.
Each T flip-flop with T=1 toggles on every rising edge of its clock input. So Q0 toggles every clock cycle. Q1 toggles every time Q0 goes from 1→0 (falling edge). Q2 toggles every time Q1 falls. Q3 toggles every time Q2 falls. The result is binary counting: Q3Q2Q1Q0 cycles 0000→0001→0010→...→1111→0000.
Propagation delay problem: Each flip-flop adds one tpd of delay before the next flip-flop can respond. For a 4-bit ripple counter, the total delay is 4 × tpd. At high clock frequencies, Q3 has not yet settled before the next clock edge arrives, causing glitches and incorrect intermediate states (e.g., the counter passes through 0111 on the way from 0111→1000). This is why ripple counters are not used above a few MHz in real designs.
| Clock Pulse | Q3 | Q2 | Q1 | Q0 | Decimal |
|---|---|---|---|---|---|
| 0 (Reset) | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 0 | 0 | 1 | 1 |
| 2 | 0 | 0 | 1 | 0 | 2 |
| 3 | 0 | 0 | 1 | 1 | 3 |
| 4 | 0 | 1 | 0 | 0 | 4 |
| 8 | 1 | 0 | 0 | 0 | 8 |
| 15 | 1 | 1 | 1 | 1 | 15 |
| 16 (wraps) | 0 | 0 | 0 | 0 | 0 |
Simple circuit — each FF needs no extra logic. Uses fewer gates than synchronous. Natural for low-frequency event counting where glitches don't matter (e.g., counting button presses).
Cumulative propagation delay limits maximum clock speed. Produces glitches — intermediate wrong states during counting. Output cannot be used combinationally without extra settling time.
In a synchronous counter, all flip-flops share the same clock. They all update simultaneously at the rising clock edge. Combinational logic on the flip-flop inputs determines which FFs toggle. The result: no propagation delay accumulation — the maximum clock frequency is limited only by the slowest combinational logic, not the chain of FFs.
Using T flip-flops (toggle when T=1):
This is the carry chain: each higher bit only toggles when all lower bits are 1 — which is exactly when a carry would propagate in binary addition. Unlike ripple where the carry physically ripples one FF at a time, here the carry is computed combinationally and all FFs update on the same clock edge.
Key insight: Synchronous counters are the standard in all VLSI and FPGA designs. When you write count <= count + 1 in Verilog inside an always @(posedge clk) block, the synthesizer implements a synchronous counter — an n-bit adder whose output feeds back into a register. The adder is purely combinational; only the register is clocked.
module sync_counter_4bit (
input wire clk,
input wire rst, // synchronous reset
output reg [3:0] count
);
always @(posedge clk) begin
if (rst)
count <= 4'b0000;
else
count <= count + 1'b1;
end
endmodule
module sync_down_counter (
input wire clk,
input wire rst,
output reg [3:0] count
);
always @(posedge clk) begin
if (rst)
count <= 4'b1111; // starts at max
else
count <= count - 1'b1;
end
endmodule
module up_down_counter (
input wire clk,
input wire rst,
input wire up, // 1 = count up, 0 = count down
output reg [3:0] count
);
always @(posedge clk) begin
if (rst)
count <= 4'b0000;
else if (up)
count <= count + 1'b1;
else
count <= count - 1'b1;
end
endmodule
A Mod-N counter counts from 0 to N−1, then resets to 0. The modulus N defines how many states the counter visits before wrapping. A standard 4-bit binary counter is Mod-16. Clock dividers, timers, and display drivers all require specific moduli.
Common examples: Mod-10 (BCD, used in decimal digit displays), Mod-6 (seconds/minutes tens digit — 0–5), Mod-12 (12-hour clock hour display), Mod-60 (built from Mod-6 × Mod-10 cascaded).
Reset when count reaches 6 (binary 0110):
module mod6_counter (
input wire clk,
input wire rst,
output reg [3:0] count
);
always @(posedge clk) begin
if (rst || count == 4'd5)
count <= 4'b0000;
else
count <= count + 1'b1;
end
endmodule
Counts 0–9, resets on 10 (1010). Used in digital clocks, calculators, and any decimal display system:
module bcd_counter (
input wire clk,
input wire rst,
output reg [3:0] count,
output wire carry_out // goes high when count wraps (useful for cascading)
);
assign carry_out = (count == 4'd9);
always @(posedge clk) begin
if (rst || count == 4'd9)
count <= 4'b0000;
else
count <= count + 1'b1;
end
endmodule
// Cascade two BCD counters for 00-99:
// bcd_counter units(.clk(clk), .rst(rst), .count(units_val), .carry_out(tens_clk));
// bcd_counter tens (.clk(tens_clk), .rst(rst), .count(tens_val), .carry_out());
module mod_n_counter #(
parameter N = 10, // modulus
parameter W = $clog2(N) // auto-size bit width
)(
input wire clk,
input wire rst,
output reg [W-1:0] count
);
always @(posedge clk) begin
if (rst || count == N - 1)
count <= '0;
else
count <= count + 1'b1;
end
endmodule
A ring counter is a circular shift register. A single 1 bit rotates through n flip-flops. Only one output is HIGH at any time — a one-hot encoding. An n-bit ring counter has only n valid states (not 2ⁿ). It is initialized with exactly one FF at 1 and all others at 0.
4-bit ring counter sequence (initialized to 1000):
module ring_counter (
input wire clk,
input wire rst,
output reg [3:0] q
);
always @(posedge clk) begin
if (rst)
q <= 4'b1000; // initialize with single 1
else
q <= {q[0], q[3:1]}; // rotate right
end
endmodule
Use case: Sequencing multiple peripherals — asserting exactly one enable signal per cycle without priority encoder logic. Simpler decode than a binary counter.
A Johnson counter feeds the complement of the last FF back to the first. An n-bit Johnson counter produces 2n states — double a ring counter. The sequence for a 4-bit Johnson counter:
module johnson_counter (
input wire clk,
input wire rst,
output reg [3:0] q
);
always @(posedge clk) begin
if (rst)
q <= 4'b0000;
else
q <= {~q[0], q[3:1]}; // invert last bit, shift right
end
endmodule
Use case: Generating overlapping two-phase clock signals. Each adjacent pair of outputs overlaps by one cycle, useful for controlling two-phase CMOS logic and stepper motor drives.
Ring: n states, one-hot encoding, easy decode — one FF is output directly.
Johnson: 2n states, two-hot→one-hot transition, slightly more complex decode — AND of adjacent pairs.
A Gray code counter changes only one bit per transition. Critical in CDC (clock domain crossing) — a multi-bit pointer changing only 1 bit avoids metastability ambiguity. Used in async FIFO read/write pointer design.
A counter's MSB output has half the frequency of the input clock. An n-bit counter divides the clock by 2ⁿ. A single D flip-flop divides by 2. Three flip-flops in a ripple chain divide by 8. This is how PLLs generate sub-clocks from a high-frequency reference, and how UART baud rate generators work — a counter overflows at the desired bit period, generating a baud-rate enable pulse.
The program counter is a register that counts the address of the next instruction to fetch. On every clock cycle (absent branches), it increments by the instruction size (4 bytes for a 32-bit ISA). On a branch, the PC is loaded with the target address. On a function call, the old PC is saved to the stack, then PC is loaded with the function entry. The PC is the most frequently updated register in any CPU — it advances 10⁹ times per second on a 1 GHz processor.
A complete digital clock chains counters: a 32768 Hz crystal oscillator feeds a 15-bit counter (divides by 32768) to produce a 1 Hz tick. This drives a Mod-10 counter (units seconds, 0–9), whose carry drives a Mod-6 counter (tens seconds, 0–5). Their combined output (00–59) drives the minutes chain (Mod-10 + Mod-6). The hours chain uses Mod-12 or Mod-24. Total flip-flop count for a complete clock: under 30.
Burst read/write operations in DDR memory controllers use a counter to auto-increment the column address within a row. The burst length counter (typically 4–16 beats) fires a "burst done" signal when it reaches the programmed burst length. AXI4 burst transactions use a beat counter that counts from 0 to AWLEN, generating ARVALID/RVALID signals for each data beat.
A PWM generator compares a free-running counter against a programmable threshold register. When counter < threshold, output is HIGH; otherwise LOW. Changing the threshold changes the duty cycle without stopping the counter. This is how motor speed controllers, LED dimmers, and servo controllers work at the hardware level — all built from one counter and one comparator.
| Application | Counter Type | Bits | Key Parameter |
|---|---|---|---|
| UART Baud Rate | Synchronous Up | 8–16 | Overflow period = 1/baud rate |
| Digital Clock | BCD (Mod-10 + Mod-6) | 4 each | Cascaded for seconds/min/hr |
| CPU Program Counter | Synchronous Up + Load | 32 or 64 | Increments by instruction size |
| PWM Generator | Free-running Up | 8–16 | Compare with duty cycle register |
| Async FIFO Pointer | Gray code Up | log₂(depth)+1 | One-bit change per increment |
| Frequency Divider | Ripple or Sync Up | N | MSB output = clk / 2ⁿ |
| Feature | Synchronous | Asynchronous (Ripple) |
|---|---|---|
| Clock connection | All FFs share one clock | FFs chain-clock each other |
| Propagation delay | No accumulation — fixed tpd | Adds n × tpd (n = number of FFs) |
| Glitches | None — all FFs update together | Yes — incorrect intermediate states |
| Max clock speed | High — limited by logic, not chain | Low — limited by ripple delay |
| Power | Higher (all FFs toggle on same edge) | Lower (FFs toggle less often) |
| Gate count | More (needs carry logic) | Less (no carry logic needed) |
| Used in VLSI | Always — standard practice | Rarely — only for slow counting |
Rule in VLSI design: Always use synchronous counters. Asynchronous counters create multi-bit transitions that violate timing analysis assumptions and can cause metastability when their outputs cross clock domains. In all RTL designs targeting synthesis, count <= count + 1 with a single clock edge is the correct implementation — the synthesizer handles the carry logic optimally.