Beginner · Lesson 8 of 10

Counters, timers and debouncing

Clock enables instead of divided clocks, a millisecond tick generator, a button debouncer with edge detection, and a multiplexed seven-segment display, all with a testbench.

Beginner35 minUpdated 13 Nov 2026

You will learn

  • Explain why a divided clock should be a clock enable rather than a new clock
  • Build a parameterised tick generator and calculate its period from the clock frequency
  • Synchronise, debounce and edge-detect a push button in Verilog and VHDL
  • Drive a four-digit seven-segment display by time multiplexing

Before this: finite state machines

Nearly every beginner board has a fast clock, a few buttons and some LEDs or a seven-segment display, and nearly every first project needs to make something happen at a human pace. This lesson covers the small building blocks that let a 100 MHz design blink, count seconds and respond to a finger without misbehaving: a tick generator, a debouncer with edge detection, and a display multiplexer. Each is a few lines, and each hides a lesson about how FPGAs want to be used. You should be comfortable with registers from lesson 04 and with testbenches from lesson 06.

Clock dividers versus clock enables

The obvious way to slow a design down is to divide the clock. A 100 MHz input through a 26-bit counter gives a 1.5 Hz square wave on the top bit, and it is tempting to use that bit as the clock for the rest of the design:

// Do not do this on an FPGA.
always @(posedge count[25]) led <= ~led;

It works, in the sense that the LED blinks. It is also the most common structural mistake in beginner designs, and here is why. An FPGA has a small number of dedicated clock networks, laid out so that a clock edge arrives at every flip-flop at very nearly the same time. A signal that comes out of a flip-flop and goes into general routing is not on one of those networks. The tool will either route it through a long, uneven path with large skew between the flip-flops it drives, or it will promote it onto a clock network at a cost, and either way the timing tools now have a second clock domain to analyse. Every signal that crosses between the 100 MHz logic and the 1.5 Hz logic is a clock-domain crossing, with all the metastability concerns that come with it. On a blinky that does not matter. On anything larger it is a steady source of bugs that only appear on hardware.

The right pattern is a clock enable. Everything runs on the one fast clock, and the slow activity happens on cycles when a one-clock-wide pulse, the enable, is high.

Divided clock (avoid)dividerslow logicclkclk_slowrouted as a clock through fabric, skew, new domainClock enable (preferred)tick_genslow logicclktickone domain, one clock network
A divided clock drives a second clock domain through general routing. A clock enable keeps everything in one domain on the dedicated clock network.

A millisecond tick generator

This module counts clock cycles and emits a single-cycle pulse each time it wraps. With DIVIDE set to 100,000 and a 100 MHz clock the pulse comes once per millisecond. The $clog2 function sizes the counter from the parameter, so you never have to work out the bit width by hand.

// Produces a one-clock-wide pulse every DIVIDE clock cycles.
// With a 100 MHz clock and DIVIDE = 100_000 that is one pulse per millisecond.
module tick_gen #(
  parameter integer DIVIDE = 100_000
) (
  input  wire clk,
  input  wire rst,
  output reg  tick
);
  localparam integer W = $clog2(DIVIDE);
  reg [W-1:0] count;

  always @(posedge clk) begin
    if (rst) begin
      count <= 0;
      tick  <= 1'b0;
    end else if (count == DIVIDE - 1) begin
      count <= 0;
      tick  <= 1'b1;
    end else begin
      count <= count + 1'b1;
      tick  <= 1'b0;
    end
  end
endmodule

Two things to notice. The counter compares against DIVIDE - 1, not DIVIDE, because a counter that runs from 0 to N-1 has N states. Off-by-one errors in dividers are easy to make and invisible on an LED, which is a good reason to put a tick generator in a testbench and count edges. And tick is a registered output. It comes straight out of a flip-flop, so it is clean and arrives at the same time everywhere.

Tick generators chain. A 1 ms tick enabling a second counter that counts to 1,000 gives a 1 s tick, and the second counter only needs ten bits.

Debouncing a button

A mechanical button does not produce one clean edge when pressed. The contacts bounce for anywhere from a few hundred microseconds to several milliseconds, and a 100 MHz design will happily count every bounce as a separate press. There is a second, less visible problem: the button changes at a time unrelated to the clock, and a flip-flop that samples a changing input can go metastable, holding an in-between voltage for longer than a cycle. Both problems are solved in the same module.

A bouncing input, the synchronised copy, the accepted level after it has been stable for several ticks, and the single-cycle pressed pulse.clkbtn_rawbtn_stick_1msbtn_levelbtn_pressed
A bouncing input, the synchronised copy, the accepted level after it has been stable for several ticks, and the single-cycle pressed pulse.
// Synchronise a button, debounce it with a millisecond tick, and
// produce one-clock pulses on each press and release.
module debounce (
  input  wire clk,
  input  wire rst,
  input  wire tick_1ms,
  input  wire btn_raw,     // asynchronous, straight from the pin
  output reg  btn_level,   // clean, debounced level
  output wire btn_pressed, // one-clock pulse on 0 -> 1
  output wire btn_released // one-clock pulse on 1 -> 0
);
  // Two-flop synchroniser.
  reg [1:0] sync;
  always @(posedge clk) sync <= {sync[0], btn_raw};
  wire btn_s = sync[1];

  // The level is accepted once it has been stable for 16 ticks (16 ms).
  reg [3:0] stable;
  always @(posedge clk) begin
    if (rst) begin
      stable    <= 4'd0;
      btn_level <= 1'b0;
    end else if (btn_s == btn_level) begin
      stable <= 4'd0;
    end else if (tick_1ms) begin
      if (stable == 4'd15) begin
        btn_level <= btn_s;
        stable    <= 4'd0;
      end else
        stable <= stable + 4'd1;
    end
  end

  // Edge detector on the clean level.
  reg btn_level_d;
  always @(posedge clk) btn_level_d <= btn_level;
  assign btn_pressed  =  btn_level & ~btn_level_d;
  assign btn_released = ~btn_level &  btn_level_d;
endmodule

The module has three stages, and each does one job.

The synchroniser is two flip-flops in a row. The first may go metastable, but it has a whole clock period to settle before the second samples it, and at 100 MHz the chance of the second one also being metastable is small enough to ignore for a button. Nothing in the design should look at btn_raw or sync[0] directly.

The debouncer is a counter driven by the millisecond tick. Whenever the synchronised input matches the accepted level, the counter is cleared. When it differs, the counter advances once per tick. Only after 16 consecutive ticks of disagreement, 16 ms, does the accepted level change. Any bounce shorter than that resets the count and is ignored. Using the tick rather than the raw clock keeps the counter to four bits.

The edge detector keeps a one-cycle-delayed copy of the clean level and ANDs the two. btn_pressed is high for exactly one clock on the rising edge, which is what an FSM transition wants. In lesson 07 a level input walked the machine through several states. This is the fix.

The debouncer in VHDL

-- Synchroniser, debouncer and edge detector for one push button.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity debounce is
  port (
    clk          : in  std_logic;
    rst          : in  std_logic;
    tick_1ms     : in  std_logic;
    btn_raw      : in  std_logic;
    btn_level    : out std_logic;
    btn_pressed  : out std_logic;
    btn_released : out std_logic
  );
end entity;

architecture rtl of debounce is
  signal sync    : std_logic_vector(1 downto 0) := "00";
  signal stable  : unsigned(3 downto 0) := (others => '0');
  signal level   : std_logic := '0';
  signal level_d : std_logic := '0';
begin
  process (clk)
  begin
    if rising_edge(clk) then
      -- Two-flop synchroniser.
      sync <= sync(0) & btn_raw;

      if rst = '1' then
        stable <= (others => '0');
        level  <= '0';
      elsif sync(1) = level then
        stable <= (others => '0');
      elsif tick_1ms = '1' then
        if stable = 15 then
          level  <= sync(1);
          stable <= (others => '0');
        else
          stable <= stable + 1;
        end if;
      end if;

      level_d <= level;
    end if;
  end process;

  btn_level    <= level;
  btn_pressed  <= level and not level_d;
  btn_released <= (not level) and level_d;
end architecture;

The VHDL version uses an internal level signal and copies it to the port, because a VHDL-2008 out port can be read but older tools reject it. The synchroniser and the delayed copy sit outside the reset branch on purpose. They should run whatever the reset is doing.

Testing the debouncer

The testbench below instantiates both modules, shrinks the tick to one every ten clocks so that the simulation stays short, and drives a press and a release that each bounce six times. It then counts the pulses and expects exactly one of each.

`timescale 1ns / 1ps
// Testbench: a bouncy press and release, with a fast tick to keep it short.
module tb_debounce;
  reg clk = 0, rst = 1, btn_raw = 0;
  wire tick, level, pressed, released;

  // DIVIDE = 10 makes a "millisecond" tick every 100 ns in simulation.
  tick_gen #(.DIVIDE(10)) u_tick (.clk(clk), .rst(rst), .tick(tick));
  debounce u_db (.clk(clk), .rst(rst), .tick_1ms(tick), .btn_raw(btn_raw),
                 .btn_level(level), .btn_pressed(pressed), .btn_released(released));

  always #5 clk = ~clk;

  integer presses = 0, releases = 0;
  always @(posedge clk) begin
    if (pressed)  begin presses  = presses  + 1; $display("t=%0t pressed",  $time); end
    if (released) begin releases = releases + 1; $display("t=%0t released", $time); end
  end

  // Bounce: toggle quickly a few times, then settle.
  task bounce_to(input final_level);
    integer k;
    begin
      for (k = 0; k < 6; k = k + 1) begin
        btn_raw = ~btn_raw; #37;
      end
      btn_raw = final_level;
    end
  endtask

  initial begin
    $timeformat(-9, 0, " ns", 8);
    $dumpfile("debounce.vcd");
    $dumpvars(0, tb_debounce);
    #25 rst = 0;
    #200 bounce_to(1);     // press, with bounce
    #3000 bounce_to(0);    // release, with bounce
    #3000;
    if (presses == 1 && releases == 1) $display("PASS");
    else $display("FAIL: %0d presses, %0d releases", presses, releases);
    $finish;
  end
endmodule
iverilog -g2012 -o debounce.vvp tick_gen.v debounce.v tb_debounce.v
vvp debounce.vvp

The output is two lines of timestamps and PASS. The press is reported about 1.6 µs after the bouncing stops, which is 16 fast ticks of 100 ns. Open the VCD and you can watch stable climb to 15 and reset each time a bounce arrives. Overriding the parameter in the testbench rather than editing the module is a habit worth copying: the module that gets synthesised is the same one that was tested.

A seven-segment multiplexer

Boards such as the Basys 3 have a four-digit seven-segment display wired with the segment lines shared across all digits and one enable per digit. Only one digit can be lit at a time, so the design cycles through them faster than the eye can follow. Switching digits once per millisecond gives each digit a 250 Hz refresh, well above the flicker threshold. Here again the tick generator does the pacing.

// Four-digit multiplexed seven-segment driver.
// Anodes and segments are active low, as on the Basys 3.
module seven_seg_mux (
  input  wire        clk,
  input  wire        rst,
  input  wire        tick_1ms,
  input  wire [15:0] value,   // four hex digits
  output reg  [3:0]  an,      // digit enables, active low
  output reg  [6:0]  seg      // a..g, active low
);
  reg [1:0] digit_sel;
  reg [3:0] nibble;

  // Advance one digit every millisecond: 250 Hz per digit, no visible flicker.
  always @(posedge clk) begin
    if (rst)
      digit_sel <= 2'd0;
    else if (tick_1ms)
      digit_sel <= digit_sel + 2'd1;
  end

  always @(*) begin
    case (digit_sel)
      2'd0: begin an = 4'b1110; nibble = value[3:0];   end
      2'd1: begin an = 4'b1101; nibble = value[7:4];   end
      2'd2: begin an = 4'b1011; nibble = value[11:8];  end
      default: begin an = 4'b0111; nibble = value[15:12]; end
    endcase
  end

  // Segment pattern, bit 0 = a ... bit 6 = g, 0 lights the segment.
  always @(*) begin
    case (nibble)
      4'h0: seg = 7'b1000000;
      4'h1: seg = 7'b1111001;
      4'h2: seg = 7'b0100100;
      4'h3: seg = 7'b0110000;
      4'h4: seg = 7'b0011001;
      4'h5: seg = 7'b0010010;
      4'h6: seg = 7'b0000010;
      4'h7: seg = 7'b1111000;
      4'h8: seg = 7'b0000000;
      4'h9: seg = 7'b0010000;
      4'hA: seg = 7'b0001000;
      4'hB: seg = 7'b0000011;
      4'hC: seg = 7'b1000110;
      4'hD: seg = 7'b0100001;
      4'hE: seg = 7'b0000110;
      default: seg = 7'b0001110;
    endcase
  end
endmodule

The two combinational blocks are pure lookup tables and will become a handful of LUTs. The an and seg outputs change in the same cycle as digit_sel, so the segment data for digit N is always presented together with digit N’s enable. If you ever see a faint ghost of one digit’s pattern on its neighbour, the usual cause is a design that registers one of the two and not the other, so that they change a cycle apart.

Putting it together

A typical top level for a beginner board instantiates one tick_gen, feeds its output to a debounce per button and to the seven_seg_mux, and uses btn_pressed to advance a counter whose value goes to the display. All of it runs on the one board clock. There are no derived clocks, so the timing tool sees a single domain and the design will meet timing without any effort from you. That is the point of this lesson: keep one clock, use enables for everything slower, and let registered single-cycle pulses carry events between blocks.

Exercise

Write top_counter.v for your board. Instantiate tick_gen, one debounce and seven_seg_mux. Keep a 16-bit register that increments on btn_pressed and show it on the display. Then add a second tick generator fed from the 1 ms tick that counts 1,000 ticks and increments the register once per second instead, and use the button to toggle between the two modes. Write a testbench with DIVIDE reduced so that a simulated second is a few microseconds, and check the register value after three simulated seconds. If your board has no seven-segment display, show the low four bits on LEDs instead.

Summary

  • Do not use a divided signal as a clock. Keep one clock and use a one-cycle enable pulse for anything slower.
  • A tick generator counts to DIVIDE minus 1 and emits a registered pulse. Chain them for longer periods.
  • A button needs three stages: a two-flop synchroniser, a debouncer that waits for the input to be stable, and an edge detector that turns the level into a pulse.
  • Override parameters in the testbench to keep simulations short while testing the same module you synthesise.
  • A seven-segment display is driven one digit at a time, with the enable and the segment pattern changing together.

Further reading