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 PASSEDThe log proves the design counted every applied edge: the testbench generated exactly 7 rising edges inside the window, and both the live
countand the gate-latchedfreqread 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 intofreq_out, raises a one-cyclevalidstrobe, 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 endmodulePASS: got exactly 2 valid strobes (one per window) PASS: window 1 measured 5 PASS: window 2 measured 9 (counter cleared between windows) ALL TESTS PASSEDThe log confirms the latch-then-clear handshake across two windows: exactly two
validstrobes 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 / TIf 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
gateinput so the counting logic can be tested independently of the time-base divider.How the edge detector works
Everything is sampled on
clk. The inputfinis registered intofin_devery 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
finitself, so it maps cleanly to FPGA/ASIC flip-flops and avoids glitch-prone gated clocks. The trade-off is thatfinmust be meaningfully slower thanclk— the sampling clock has to see each high phase and each low phase. In practice you wantclkat least a few times faster than the highestfinyou 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
finis sampled by a single flip-flop (fin_d). In real hardware the signal under test is asynchronous toclk, 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
gateis detected withgate_d && !gate, and in that same clock the live counter is copied tofreq_out,validpulses 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 endBecause all three are non-blocking assignments evaluated from the old values,
freq_outgets the final count andcntis cleared on the same edge with no race. The one-cyclevalidstrobe tells downstream logic exactly whenfreq_outis 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
finon 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,
clkmust capture both the high and low phase offin, so the practical ceiling is well belowclk/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 LabRun it in the LabOpen this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.
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 Hi! Ask me about courses, fees, batches or discounts. ×ChipXpert AssistantAsk about courses, fees, batches and centres