Why Learn Counters? Counters are among the most common blocks in any chip. Timers, baud rate generators, FIFO pointers, refresh logic in memory controllers and watchdogs all count clock cycles. Nearly every counter is a variation of the same few lines of Verilog. In this tutorial, you'll build a 4-bit counter, see what hardware it becomes, and learn how to extend it with an enable and a configurable width.

A 4-bit counter is a sequential circuit whose 4-bit output increases by one on every active clock edge, from 4'b0000 to 4'b1111 (0 to 15). After 15, it overflows and wraps back to 0. The design in this article has a clock input and an active-low synchronous reset that sets the output to 0.

10 min read | Beginner Level

What You'll Learn

  • Write a 4-bit up counter with synchronous reset in Verilog
  • What synthesis builds from out <= out + 1
  • Extend the counter with an enable, a width parameter and an overflow flag
  • Verify the counter with a testbench and avoid common counter bugs
Block diagram of a 4-bit counter with inputs clk and rstn and a 4-bit output out

A 4-bit output has 24 = 16 possible values, so the counter goes through 16 states before repeating. This is called a modulo-16 counter. In general, an N-bit binary counter counts from 0 to 2N - 1.

Clock edgeout (binary)out (decimal)
Reset00000
100011
200102
.........
15111115
1600000 (wraps around)

Electronic Counter Design

The counter is a register that adds 1 to its own value at each rising clock edge. Reset is checked first, so it overrides counting.


module counter (  input clk,               // Declare input port for clock to allow counter to count up
                  input rstn,              // Declare input port for reset to allow the counter to be reset to 0 when required
                  output reg[3:0] out);    // Declare 4-bit output port to get the counter values

  // This always block will be triggered at the rising edge of clk (0->1)
  // Once inside this block, it checks if the reset is 0, if yes then change out to zero
  // If reset is 1, then design should be allowed to count up, so increment counter
  always @ (posedge clk) begin
    if (! rstn)
      out <= 0;
    else
      out <= out + 1;    // 4-bit result: 15 + 1 = 16 does not fit, so it wraps to 0
  end
endmodule

No extra logic is needed for the wrap-around. out is 4 bits wide, so the sum out + 1 is stored in 4 bits, and the carry out of bit 3 is dropped. When out is 4'b1111, the next value is 4'b0000.

Synthesis result: The tool builds four D flip-flops and a 4-bit incrementer that feeds out + 1 back to their D inputs, plus reset gating that forces D to 0 when rstn is low. An incrementer is simpler than a general adder: bit i flips when all lower bits are 1, which is the same rule a chain of T flip-flops follows. See D Flip-Flop Async Reset for synchronous and asynchronous reset styles.

Where are Counters Used?

Timer peripherals in microcontrollers are counters with a few extra features: a prescaler that sets how often they count, a compare register that raises an interrupt at a chosen value, and a reload value. Firmware uses them for delays, periodic interrupts and measuring the time between external events.

Quick Check

Q: The counter holds 4'hF and reset is not active. What is out after the next rising clock edge, and how could the design report that it happened?

Show Answer

4'h0. The counter wraps around silently. To report it, add an output that is high while out equals 4'hF (the terminal count), or capture the carry out of the addition, as shown in the parameterized version below.

Testbench

The testbench generates a 100 MHz clock, holds reset for the first 20 ns, lets the counter run, then asserts reset again in the middle of counting to check that it returns to 0.

Testbench driving clk and rstn into the 4-bit counter and observing out

module tb_counter;
  reg clk;                     // Declare an internal TB variable called clk to drive clock to the design
  reg rstn;                    // Declare an internal TB variable called rstn to drive active low reset to design
  wire [3:0] out;              // Declare a wire to connect to design output

  // Instantiate counter design and connect with Testbench variables
  counter   c0 ( .clk (clk),
                 .rstn (rstn),
                 .out (out));

  // Generate a clock that should be driven to design
  // This clock will flip its value every 5ns -> time period = 10ns -> freq = 100 MHz
  always #5 clk = ~clk;

  // This initial block forms the stimulus of the testbench
  initial begin
    // Print a line whenever clk, rstn or out changes
    $monitor ("[%0tns] clk=%0b rstn=%0b out=0x%0h", $time, clk, rstn, out);

    // 1. Initialize testbench variables to 0 at start of simulation
    clk <= 0;
    rstn <= 0;

    // 2. Drive rest of the stimulus, reset is asserted in between
    #20   rstn <= 1;
    #80   rstn <= 0;
    #50   rstn <= 1;

    // 3. Finish the stimulus after 170ns
    #20 $finish;
  end
endmodule
Output
ncsim> run
[0ns] clk=0 rstn=0 out=0xx
[5ns] clk=1 rstn=0 out=0x0
[10ns] clk=0 rstn=0 out=0x0
[15ns] clk=1 rstn=0 out=0x0
[20ns] clk=0 rstn=1 out=0x0
[25ns] clk=1 rstn=1 out=0x1
[30ns] clk=0 rstn=1 out=0x1
[35ns] clk=1 rstn=1 out=0x2
[40ns] clk=0 rstn=1 out=0x2
[45ns] clk=1 rstn=1 out=0x3
[50ns] clk=0 rstn=1 out=0x3
[55ns] clk=1 rstn=1 out=0x4
[60ns] clk=0 rstn=1 out=0x4
[65ns] clk=1 rstn=1 out=0x5
[70ns] clk=0 rstn=1 out=0x5
[75ns] clk=1 rstn=1 out=0x6
[80ns] clk=0 rstn=1 out=0x6
[85ns] clk=1 rstn=1 out=0x7
[90ns] clk=0 rstn=1 out=0x7
[95ns] clk=1 rstn=1 out=0x8
[100ns] clk=0 rstn=0 out=0x8
[105ns] clk=1 rstn=0 out=0x0
[110ns] clk=0 rstn=0 out=0x0
[115ns] clk=1 rstn=0 out=0x0
[120ns] clk=0 rstn=0 out=0x0
[125ns] clk=1 rstn=0 out=0x0
[130ns] clk=0 rstn=0 out=0x0
[135ns] clk=1 rstn=0 out=0x0
[140ns] clk=0 rstn=0 out=0x0
[145ns] clk=1 rstn=0 out=0x0
[150ns] clk=0 rstn=1 out=0x0
[155ns] clk=1 rstn=1 out=0x1
[160ns] clk=0 rstn=1 out=0x1
[165ns] clk=1 rstn=1 out=0x2
Simulation complete via $finish(1) at time 170 NS + 0

Reading the log:

  • 0 ns: out is x, because a register has no value until something assigns it.
  • 5 ns: The first rising edge sees rstn = 0, so out becomes 0. The reset is synchronous, so it takes effect only at a clock edge.
  • 25 to 95 ns: Reset is released at 20 ns, and out increments at each rising edge, from 1 up to 8.
  • 100 to 105 ns: Reset is asserted again at 100 ns, but out stays 8 until the rising edge at 105 ns clears it.
  • 150 ns onward: Reset is released, and counting starts again from 0.
4-bit counter wave

Hardware Schematic

Synthesized schematic of the 4-bit counter: four flip-flops with an incrementer and reset logic feeding their D inputs

The schematic shows the structure described above: a 4-bit register whose output passes through an incrementer and reset logic before returning to the register's D inputs. This feedback loop through the incrementer is the timing path that limits how fast the counter can run.

Adding an Enable, a Parameter and an Overflow Flag

Real designs rarely count on every clock edge. Most counters have an enable, a configurable width and an output that signals when the count wraps. This version adds all three:


module counter_n #(parameter N = 4) (   // N sets the counter width
  input              clk,
  input              rstn,              // Active-low synchronous reset
  input              en,                // Count only when en is 1
  output reg [N-1:0] out,
  output             tc                 // Terminal count: high when out is all 1s
);

  always @(posedge clk) begin
    if (!rstn)
      out <= 0;
    else if (en)                        // No final else: out holds when en is 0
      out <= out + 1'b1;
  end

  // High during the last count before wrap-around, while counting is enabled
  assign tc = en & (&out);              // &out is 1 only when every bit of out is 1

endmodule

Synthesis result: The enable becomes a clock-enable on the flip-flops, or a mux that feeds out back unchanged when en is 0. The terminal count tc is an N-input AND gate. Several counters can be chained by connecting one counter's tc to the next counter's en, so the second counter advances once each time the first one wraps.

PPA Impact: The longest path in a binary counter is the carry through the incrementer, which grows with N. A 64-bit counter can limit the clock frequency of a fast design. Splitting it into smaller counters chained through tc, or using a different counting sequence, shortens that path.

FIFO Read and Write Pointers

A FIFO buffer keeps two counters: a write pointer that advances when data is written and a read pointer that advances when data is read. Each is an enabled counter like counter_n, with the write or read request as its enable. The FIFO compares the two pointers to decide whether it is full or empty. See Synchronous FIFO.

Baud Rate and Clock Enable Generators

Serial interfaces such as UART need a pulse at a fixed fraction of the system clock, for example once every 868 cycles for 115200 baud from a 100 MHz clock. A counter counts to that value, outputs a one-cycle pulse and restarts. The pulse is used as an enable for the rest of the interface, which keeps the whole design on a single clock.

Quick Check

Q: Two counter_n #(.N(4)) instances share a clock. The tc of the first drives the en of the second, and the first has en tied to 1. How many clock cycles does the second counter take to wrap around?

Show Answer

256. The first counter's tc is high for one cycle out of every 16, so the second counter advances once every 16 cycles. It wraps after 16 of its own counts, which is 16 x 16 = 256 cycles. Together they form an 8-bit counter.

Common Beginner Mistakes

Mistake #1: Leaving out the reset

Wrong: always @(posedge clk) out <= out + 1;
Right: always @(posedge clk) if (!rstn) out <= 0; else out <= out + 1;

Why? out starts as x in simulation, and x + 1 is still x, so the counter never shows a value. The log above shows this for the first 5 ns, until reset clears it. In hardware, the counter would start from an unpredictable value.

Mistake #2: Off-by-one when comparing for a count

Wrong: if (out == 10) out <= 0; else out <= out + 1; to count 10 states
Right: if (out == 9) out <= 0; else out <= out + 1;

Why? A counter that starts at 0 and restarts after reaching 10 goes through 0 to 10, which is 11 states. To divide by N, compare against N - 1. See Verilog Mod-N counter.

Mistake #3: Counting in a combinational block

Wrong: always @(*) out = out + 1;
Right: always @(posedge clk) out <= out + 1;

Why? Without a clock edge, the block runs again every time out changes, and each run changes out again. The simulator hangs in a zero-delay loop, and synthesis builds an incrementer connected to itself with no register, which is a combinational loop.

Mistake #4: Using a counter bit as a clock

Wrong: always @(posedge out[3]) ... to run logic at 1/16 of the clock rate
Right: Keep clk as the clock and use tc as an enable: always @(posedge clk) if (tc) ...

Why? A counter bit used as a clock creates a new clock domain that is skewed from clk, and timing analysis must handle it separately. Using an enable keeps all flip-flops on one clock. Counters that do clock each stage from the previous one are covered in Verilog Ripple Counter.

Practice Exercise

Challenge: 4-bit Up/Down Counter

Write a module updown_counter with inputs clk, rstn (active-low synchronous reset) and up, and a 4-bit output out. When up is 1, the counter increments; when up is 0, it decrements. In both directions it wraps around: 15 + 1 gives 0, and 0 - 1 gives 15.

Hint

Add an if-else on up inside the reset's else branch. The 4-bit width handles the wrap-around in both directions, so no comparisons are needed.

Solution

module updown_counter (
  input            clk,
  input            rstn,   // Active-low synchronous reset
  input            up,     // 1 = count up, 0 = count down
  output reg [3:0] out
);

  always @(posedge clk) begin
    if (!rstn)
      out <= 4'd0;
    else if (up)
      out <= out + 1'b1;   // 4'hF + 1 wraps to 4'h0
    else
      out <= out - 1'b1;   // 4'h0 - 1 wraps to 4'hF
  end

endmodule

Synthesis builds an incrementer and a decrementer (or one adder that adds +1 or -1), a 2 to 1 mux selected by up, and four flip-flops. As with the up counter, the 4-bit width makes the wrap-around automatic.

Summary

In this tutorial, you learned:

  • A 4-bit counter steps through 16 values, from 0 to 15, and wraps back to 0
  • out <= out + 1 in a clocked block builds flip-flops with an incrementer in their feedback path
  • The wrap-around comes for free, because the 4-bit result drops the carry
  • A reset is required, because x + 1 stays x
  • An enable and a terminal count output let counters be chained into longer counters
  • To count N states, compare against N - 1
  • Use counter outputs as enables, not as clocks, to keep the design on one clock
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5