Frequency Counter in Verilog with Code

A frequency counter measures how fast an unknown signal switches by counting its rising edges over a precise, known time window called the gate. If the gate is open for exactly one second and the counter records N edges, the input frequency is simply N hertz. This page gives you clean, interview-ready frequency counter Verilog code: a gated edge counter that is driven entirely from a stable sampling clock.

Both designs below are self-contained Verilog-2005 modules with self-checking testbenches. The counter samples the input on the system clock, detects rising edges, accumulates them while the gate is high, and latches the result when the gate closes so downstream logic reads a stable value.

The core design: a synchronous rising-edge detector increments count on every low-to-high transition of fin while gate is high, and a second register latches that count into freq the moment the gate falls. The testbench opens the gate, applies exactly seven rising edges, closes the gate, and checks that both the live count and the latched result equal 7.

// Gated edge counter: counts rising edges of fin while gate is high,
// latches the count when the gate closes.
module freq_counter (
    input  wire        clk,      // system / sampling clock
    input  wire        rst,      // synchronous reset, active high
    input  wire        gate,     // measurement window enable (high = counting)
    input  wire        fin,      // unknown input signal (sampled on clk)
    output reg  [31:0] count,    // live edge count during the window
    output reg  [31:0] freq      // latched count at gate close
);
    reg fin_d;  // previous sampled value of fin, for edge detect

    // synchronous edge detector + counter
    always @(posedge clk) begin
        if (rst) begin
            count <= 32'd0;
            freq  <= 32'd0;
            fin_d <= 1'b0;
        end else begin
            fin_d <= fin;
            if (gate) begin
                // rising edge of fin seen between last clk and now
                if (fin && !fin_d)
                    count <= count + 32'd1;
            end
        end
    end

    // latch the result on the falling edge of gate (window close)
    reg gate_d;
    always @(posedge clk) begin
        if (rst) begin
            gate_d <= 1'b0;
        end else begin
            gate_d <= gate;
            if (gate_d && !gate)   // gate just went low
                freq <= count;
        end
    end
endmodule

module tb;
    reg clk, rst, gate, fin;
    wire [31:0] count, freq;
    integer i;

    freq_counter dut (.clk(clk), .rst(rst), .gate(gate), .fin(fin),
                      .count(count), .freq(freq));

    // 100 MHz-style clock: 10 time-unit period
    initial clk = 1'b0;
    always #5 clk = ~clk;

    // Apply EXACTLY N rising edges of fin while gate is high.
    // fin is a slow square wave: one full cycle every 4 clk periods,
    // so one rising edge every 4 clks. We generate 7 rising edges.
    initial begin
        rst  = 1'b1;
        gate = 1'b0;
        fin  = 1'b0;
        @(posedge clk); #1;
        rst = 1'b0;

        // open the gate
        gate = 1'b1;
        #1;

        // drive 7 rising edges on fin, each held for 2 clk periods high,
        // 2 clk periods low, aligned to the clock
        for (i = 0; i < 7; i = i + 1) begin
            @(posedge clk); #1; fin = 1'b1;   // rising edge
            @(posedge clk); #1;               // hold high
            @(posedge clk); #1; fin = 1'b0;   // falling edge
            @(posedge clk); #1;               // hold low
        end

        // close the gate and let the latch register fire
        @(posedge clk); #1; gate = 1'b0;
        @(posedge clk); #1;
        @(posedge clk); #1;

        // Check live count and latched freq
        if (count !== 32'd7)
            $display("FAIL: live count = %0d, expected 7", count);
        else
            $display("PASS: live count counted all 7 rising edges");

        if (freq !== 32'd7)
            $display("FAIL: latched freq = %0d, expected 7", freq);
        else
            $display("PASS: latched freq = 7 at gate close");

        if (count === 32'd7 && freq === 32'd7)
            $display("ALL TESTS PASSED");

        $finish;
    end
endmodule
PASS: live count counted all 7 rising edges
PASS: latched freq = 7 at gate close
ALL TESTS PASSED

The log proves the design counted every applied edge: the testbench generated exactly 7 rising edges inside the window, and both the live count and the gate-latched freq read 7. Because the expected value (7) is the number of edges the testbench itself drives, a miscount or a broken latch would print FAIL.

A continuous-measurement variant, parameterized by counter WIDTH. At the end of every gate window it captures the live count into freq_out, raises a one-cycle valid strobe, and clears the counter for the next window automatically. The testbench runs two back-to-back windows with different edge counts (5 then 9) and verifies both captured results and that exactly two strobes fired.

// Continuous frequency counter: a gate pulse defines each window.
// At the end of every window the live count is captured into freq_out,
// a one-cycle valid strobe is raised, and the counter clears for the
// next window automatically.
module freq_counter_cont #(
    parameter WIDTH = 24
) (
    input  wire              clk,
    input  wire              rst,
    input  wire              gate,       // high during a measurement window
    input  wire              fin,        // signal under test
    output reg  [WIDTH-1:0]  freq_out,   // measured count of the LAST window
    output reg               valid       // 1 for one clk when freq_out updates
);
    reg [WIDTH-1:0] cnt;
    reg fin_d, gate_d;

    always @(posedge clk) begin
        if (rst) begin
            cnt      <= {WIDTH{1'b0}};
            freq_out <= {WIDTH{1'b0}};
            valid    <= 1'b0;
            fin_d    <= 1'b0;
            gate_d   <= 1'b0;
        end else begin
            fin_d  <= fin;
            gate_d <= gate;
            valid  <= 1'b0;                 // default: strobe low

            if (gate_d && !gate) begin
                // window just closed: capture, strobe, and clear
                freq_out <= cnt;
                valid    <= 1'b1;
                cnt      <= {WIDTH{1'b0}};
            end else if (gate) begin
                // inside the window: count rising edges of fin
                if (fin && !fin_d)
                    cnt <= cnt + {{(WIDTH-1){1'b0}}, 1'b1};
            end
        end
    end
endmodule

module tb;
    localparam W = 24;
    reg clk, rst, gate, fin;
    wire [W-1:0] freq_out;
    wire         valid;
    integer i;
    integer captures;
    reg [W-1:0] first_result, second_result;

    freq_counter_cont #(.WIDTH(W)) dut (
        .clk(clk), .rst(rst), .gate(gate), .fin(fin),
        .freq_out(freq_out), .valid(valid));

    initial clk = 1'b0;
    always #5 clk = ~clk;

    // task: run a window that emits `n` rising edges of fin
    task run_window(input integer n);
        integer k;
        begin
            gate = 1'b1; #1;
            for (k = 0; k < n; k = k + 1) begin
                @(posedge clk); #1; fin = 1'b1;
                @(posedge clk); #1;
                @(posedge clk); #1; fin = 1'b0;
                @(posedge clk); #1;
            end
            @(posedge clk); #1; gate = 1'b0;   // close window
            @(posedge clk); #1;                // capture happens here
            @(posedge clk); #1;
        end
    endtask

    // Count valid strobes and snapshot freq_out each time it pulses
    initial begin
        captures = 0;
        first_result  = {W{1'bx}};
        second_result = {W{1'bx}};
        @(negedge rst);
        forever begin
            @(posedge clk);
            if (valid) begin
                if (captures == 0)       first_result  = freq_out;
                else if (captures == 1)  second_result = freq_out;
                captures = captures + 1;
            end
        end
    end

    initial begin
        rst = 1'b1; gate = 1'b0; fin = 1'b0;
        @(posedge clk); #1;
        @(posedge clk); #1; rst = 1'b0;

        run_window(5);    // window 1: 5 edges
        run_window(9);    // window 2: 9 edges

        if (captures !== 2)
            $display("FAIL: expected 2 valid strobes, got %0d", captures);
        else
            $display("PASS: got exactly 2 valid strobes (one per window)");

        if (first_result !== 5)
            $display("FAIL: window 1 result = %0d, expected 5", first_result);
        else
            $display("PASS: window 1 measured 5");

        if (second_result !== 9)
            $display("FAIL: window 2 result = %0d, expected 9", second_result);
        else
            $display("PASS: window 2 measured 9 (counter cleared between windows)");

        if (captures === 2 && first_result === 5 && second_result === 9)
            $display("ALL TESTS PASSED");

        $finish;
    end
endmodule
PASS: got exactly 2 valid strobes (one per window)
PASS: window 1 measured 5
PASS: window 2 measured 9 (counter cleared between windows)
ALL TESTS PASSED

The log confirms the latch-then-clear handshake across two windows: exactly two valid strobes fire, the first captures 5 and the second captures 9. The second result being 9 (not 14) proves the counter truly cleared between windows, and the strobe count proves one capture per window.

Gate-window frequency measurement

The direct (or "gated counter") method measures frequency by counting input events over a fixed time base. A precise reference clock defines a gate of duration T. The counter is enabled only while the gate is open; it accumulates the number of input rising edges N seen in that interval. The measured frequency is then:

f = N / T

If the gate is exactly 1 second, the reading in hertz equals the raw count, which is why classic bench counters expose a 1 s / 0.1 s / 10 ms gate selector. In an FPGA the gate is generated by dividing the system clock: for a 100 MHz clock, a gate counter that counts to 100,000,000 produces a 1 s window. The examples above abstract the gate as an external gate input so the counting logic can be tested independently of the time-base divider.

How the edge detector works

Everything is sampled on clk. The input fin is registered into fin_d every clock, and a rising edge is declared when the current sample is 1 and the previous sample was 0:

fin_d <= fin;
if (gate && fin && !fin_d)
    count <= count + 1;

This is a fully synchronous design: there are no asynchronous counters and no clocking on fin itself, so it maps cleanly to FPGA/ASIC flip-flops and avoids glitch-prone gated clocks. The trade-off is that fin must be meaningfully slower than clk — the sampling clock has to see each high phase and each low phase. In practice you want clk at least a few times faster than the highest fin you intend to measure.

The ±1 count quantization error

The gated method has an inherent uncertainty of one count. Because the input edges are not synchronized to the gate, whether the gate boundary falls just before or just after an edge is effectively random, so the recorded count can land on either side of the true value by up to one.

The resulting relative error is:

error ≈ ±1 / N = ±1 / (f × T)

This is why the method is excellent for high frequencies and poor for low ones. Measuring 10 MHz with a 1 s gate gives N = 10,000,000 and an error near 0.00001%. Measuring 10 Hz with the same gate gives N = 10 and a ±10% error — unusable. Lengthening the gate reduces the error but slows down each reading.

Reciprocal counting for low frequencies

For low-frequency inputs, flip the problem: instead of counting input edges in a fixed time, measure the time of a whole number of input periods using the fast reference clock. This is called reciprocal (or period) counting.

  • Count how many high-frequency reference ticks M occur during K complete input periods.
  • The input frequency is f = K × f_ref / M.

Now the ±1 uncertainty lands on the fast reference count M (which is large) rather than the small input count, so a 10 Hz signal can be measured to high precision in a short time. Many real instruments pick direct vs. reciprocal counting automatically based on the input range. The design above is a direct counter; a reciprocal counter reuses the same rising-edge detector but swaps the roles of gate and counted signal.

Synchronizing the async input (CDC)

In the examples fin is sampled by a single flip-flop (fin_d). In real hardware the signal under test is asynchronous to clk, so that single flop can go metastable. The standard fix is a two-flop synchronizer before the edge detector:

reg s1, s2, s3;
always @(posedge clk) begin
    s1 <= fin;   // may be metastable
    s2 <= s1;    // resolved
    s3 <= s2;    // delayed copy for edge compare
end
wire rise = s2 & ~s3;

The first two flops resolve metastability; the third gives a clean previous-value copy so the edge is detected on fully synchronized data. This adds a two-cycle latency but does not change the count. Skipping the synchronizer is a classic interview trap and a real source of occasional miscounts in silicon.

Latch-then-clear timing

The capture must happen before the clear, or you lose a reading. In the continuous variant the falling edge of gate is detected with gate_d && !gate, and in that same clock the live counter is copied to freq_out, valid pulses high, and the counter is reset to zero:

if (gate_d && !gate) begin
    freq_out <= cnt;   // capture
    valid    <= 1'b1;  // one-cycle strobe
    cnt      <= 0;     // clear for next window
end

Because all three are non-blocking assignments evaluated from the old values, freq_out gets the final count and cnt is cleared on the same edge with no race. The one-cycle valid strobe tells downstream logic exactly when freq_out is fresh, which the testbench relies on to snapshot each window's result.

Synthesis notes

  • Both modules are fully synchronous with a single clock domain (plus the input synchronizer you should add for a real async fin), so they synthesize to simple counter + flip-flop logic with no latches and no gated clocks.
  • Size the counter for the worst case: a W-bit counter overflows at 2W edges per window. For a 1 s gate at 100 MHz input you need 27 bits; Example 2 is parameterized so you can widen it.
  • The real precision limiter is your gate time base — its accuracy comes directly from the reference clock/crystal, so counter precision is only as good as that oscillator.

FAQ

Why sample the input instead of using it as a clock?

Clocking a counter directly from an unknown, possibly glitchy external signal creates an unconstrained clock domain and is hard to place-and-route or constrain in static timing. Sampling fin on a clean system clock keeps the whole design in one timing domain.

What limits the highest measurable frequency?

The sampling clock. To detect every rising edge, clk must capture both the high and low phase of fin, so the practical ceiling is well below clk/2. Above that, use a prescaler or a dedicated high-speed counter.

How do I turn the count into a frequency in Hz?

Divide the latched count by the gate time in seconds. With a 1 s gate the count is the frequency in Hz; with a 0.1 s gate, multiply by 10.

Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.
▶ Try it in the ChipXpert Lab

Open this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.

Run it in the Lab

Learn Verilog with mentors, labs and placement support

This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.

Explore VLSI Courses
Next batch starts 18th November 2026
Start your VLSI career with ChipXpert

Live online and classroom batches in Hyderabad & Bengaluru. Fill in your details and a counsellor will call you back.

  • Real EDA tools in your browser: industry-standard EDAReal EDA tools in your browser
  • Recorded sessions on the elearn portalRecorded sessions
  • Placement assistance: resume, mock interviewsPlacement assistance
  • Merit scholarship up to 60% · EMI optionsScholarship up to 60%
Hi! Ask me about courses, fees, batches or discounts. ×
BENGALURU
HYDERABAD