Edge Detector Verilog Code (Rising, Falling, Both)
An edge detector is one of the most reused building blocks in digital design. Whenever a module must react to the moment a signal changes rather than to its steady level — a button press, a start strobe, a handshake request — you need logic that turns a level change into a one-clock-wide pulse. This tutorial shows you working edge detector Verilog code for rising, falling and both-edge detection using the registered-sample technique, plus a self-checking testbench that injects edges so you can watch the pulses appear.
Everything here is plain Verilog-2005. Build and run each file with Icarus Verilog using iverilog -g2005 -o sim file.v then vvp sim; the simulation logs under each listing are the real output from that code.
The idea: sample now, compare with the previous sample
A signal that is already synchronous to your clock changes only at clock edges. The registered-sample technique uses a single flip-flop to remember what the signal looked like last clock cycle. On every rising clock edge you store the current value; combinational logic then compares the live value with the stored one:
- Rising edge: the signal is high now but was low last cycle →
sig_in & ~sig_q. - Falling edge: the signal is low now but was high last cycle →
~sig_in & sig_q. - Both edges: the two samples simply differ →
sig_in ^ sig_q.
Because the comparison is between a stored sample and the current one, each output is high for exactly one clock cycle per transition. That single-cycle pulse is what downstream logic — counters, FSMs, FIFOs — usually wants.
Edge detector Verilog code
The module below exposes all three outputs. The testbench drives a rising edge, a falling edge and a narrow one-cycle pulse, so you can confirm each output fires at the right time.
// edge_detect.v -- rising / falling / both edge detector
// Registered-sample technique: one flip-flop remembers the previous
// synchronous sample of sig_in, then combinational logic compares the
// live sample with the stored one to emit single-cycle pulses.
module edge_detect (
input wire clk, // sampling clock
input wire rst_n, // active-low asynchronous reset
input wire sig_in, // signal already synchronous to clk
output wire rise, // 1 for one clk when sig_in goes 0 -> 1
output wire fall, // 1 for one clk when sig_in goes 1 -> 0
output wire both // 1 for one clk on either transition
);
// sig_q holds the value sig_in had at the previous rising clk edge.
reg sig_q;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
sig_q <= 1'b0;
else
sig_q <= sig_in;
end
// Compare the current sample against the stored previous sample.
assign rise = sig_in & ~sig_q; // low last cycle, high now
assign fall = ~sig_in & sig_q; // high last cycle, low now
assign both = sig_in ^ sig_q; // any difference between the two
endmodule
// ---------------------------------------------------------------------
// Testbench: inject a rising edge, a falling edge and a one-cycle pulse.
module edge_detect_tb;
reg clk, rst_n, sig_in;
wire rise, fall, both;
edge_detect dut (
.clk(clk), .rst_n(rst_n), .sig_in(sig_in),
.rise(rise), .fall(fall), .both(both)
);
// 10 time-unit clock period.
initial clk = 1'b0;
always #5 clk = ~clk;
initial begin
$monitor("t=%0t rst_n=%b sig_in=%b | rise=%b fall=%b both=%b",
$time, rst_n, sig_in, rise, fall, both);
rst_n = 1'b0;
sig_in = 1'b0;
// Release reset just after a clock edge.
@(posedge clk); #1 rst_n = 1'b1;
repeat (2) @(posedge clk);
// Inject a rising edge (0 -> 1).
#1 sig_in = 1'b1;
repeat (2) @(posedge clk);
// Inject a falling edge (1 -> 0).
#1 sig_in = 1'b0;
repeat (2) @(posedge clk);
// Inject a one-cycle-wide pulse: rise then fall back to back.
#1 sig_in = 1'b1;
@(posedge clk);
#1 sig_in = 1'b0;
repeat (2) @(posedge clk);
$display("simulation finished");
$finish;
end
endmodule
t=0 rst_n=0 sig_in=0 | rise=0 fall=0 both=0 t=6 rst_n=1 sig_in=0 | rise=0 fall=0 both=0 t=26 rst_n=1 sig_in=1 | rise=1 fall=0 both=1 t=35 rst_n=1 sig_in=1 | rise=0 fall=0 both=0 t=46 rst_n=1 sig_in=0 | rise=0 fall=1 both=1 t=55 rst_n=1 sig_in=0 | rise=0 fall=0 both=0 t=66 rst_n=1 sig_in=1 | rise=1 fall=0 both=1 t=75 rst_n=1 sig_in=1 | rise=0 fall=0 both=0 t=76 rst_n=1 sig_in=0 | rise=0 fall=1 both=1 t=85 rst_n=1 sig_in=0 | rise=0 fall=0 both=0 simulation finished /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpeybn2x14/edge_detect.v:69: $finish called at 95 (1s)
How the code works
The flip-flop sig_q is the heart of the design. It is clocked on posedge clk with an active-low asynchronous reset, so after reset it starts in a known 0 state instead of x. That matters: if sig_q began as x, the very first comparison would produce an unknown pulse in simulation.
The three assign statements are pure combinational logic driven by the live input and the one-cycle-old sample. At t=26 in the log the input has just gone high while sig_q still holds the old 0, so rise and both are 1 for one cycle. One clock later sig_q catches up and the pulse clears itself. At t=46 the mirror case gives a one-cycle fall pulse, and the final back-to-back transition shows a clean rise immediately followed by a fall.
The input must already be synchronous to clk. A signal from a different clock domain, or straight from a chip pin, must first be retimed, which is what the next example adds.
Detecting edges on an asynchronous input
Comparing a raw asynchronous signal against a clocked sample invites metastability and false pulses. The safe pattern puts a two-flip-flop synchronizer in front of the edge logic, then registers the pulse outputs as well. The detection still works the same way — compare one synchronous sample with the previous one — but now it operates on the cleaned-up, retimed version.
// edge_detect_sync.v -- edge detector with a 2-FF synchronizer and
// fully registered pulse outputs, for a signal from another clock domain.
//
// Two back-to-back flip-flops (sync0, sync1) first retime the asynchronous
// input into the clk domain. A third flip-flop (sync2) keeps the sample
// from one cycle earlier. Edges are detected between sync1 and sync2 and
// the pulses themselves are registered, so the outputs are glitch-free.
module edge_detect_sync (
input wire clk,
input wire rst_n,
input wire async_in, // unsynchronized input
output reg rise,
output reg fall
);
reg sync0, sync1, sync2;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sync0 <= 1'b0;
sync1 <= 1'b0;
sync2 <= 1'b0;
rise <= 1'b0;
fall <= 1'b0;
end else begin
sync0 <= async_in; // metastability catcher
sync1 <= sync0; // clean synchronous sample
sync2 <= sync1; // previous synchronous sample
rise <= sync1 & ~sync2; // registered rising pulse
fall <= ~sync1 & sync2; // registered falling pulse
end
end
endmodule
// ---------------------------------------------------------------------
// Testbench: drive async_in off the clock grid to mimic another domain.
module edge_detect_sync_tb;
reg clk, rst_n, async_in;
wire rise, fall;
edge_detect_sync dut (
.clk(clk), .rst_n(rst_n), .async_in(async_in),
.rise(rise), .fall(fall)
);
initial clk = 1'b0;
always #5 clk = ~clk;
initial begin
$monitor("t=%0t async_in=%b | rise=%b fall=%b",
$time, async_in, rise, fall);
rst_n = 1'b0;
async_in = 1'b0;
#12 rst_n = 1'b1; // deassert reset off the clock edge
#23 async_in = 1'b1; // rising edge at an odd time
#37 async_in = 1'b0; // falling edge at an odd time
#40 async_in = 1'b1; // rising edge again
#40 $display("simulation finished");
$finish;
end
endmodule
t=0 async_in=0 | rise=0 fall=0 t=35 async_in=1 | rise=0 fall=0 t=55 async_in=1 | rise=1 fall=0 t=65 async_in=1 | rise=0 fall=0 t=72 async_in=0 | rise=0 fall=0 t=95 async_in=0 | rise=0 fall=1 t=105 async_in=0 | rise=0 fall=0 t=112 async_in=1 | rise=0 fall=0 t=135 async_in=1 | rise=1 fall=0 t=145 async_in=1 | rise=0 fall=0 simulation finished /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmp8n12pkai/edge_detect_sync.v:60: $finish called at 152 (1s)
In the log, notice the input transitions happen at odd times like t=23 and t=72, well off the clock grid, yet rise and fall only ever assert on a clock edge and only for one cycle. The two-stage synchronizer costs a couple of cycles of latency, which is the price of a safe clock-domain crossing.
Common mistakes
| Mistake | What goes wrong |
|---|---|
| Forgetting to reset the sample flip-flop | The first comparison uses x, producing an unknown or spurious pulse at startup in simulation. |
Comparing the input against itself delayed by a # delay | Delays are not synthesizable. The real hardware edge detector must use a clocked register, not #1. |
| Feeding an asynchronous signal straight into the comparator | Metastability can create double pulses or missed edges. Add a 2-FF synchronizer first. |
| Expecting a multi-cycle level, not a pulse | Each output is intentionally one clock wide. If you need a latched flag, add your own set/clear logic downstream. |
| Detecting edges on a clock with another clock | Sampling a fast signal with a slower clock aliases transitions. The signal must toggle slower than your sampling clock. |
Quick check
Why does each output pulse last exactly one clock cycle?
Because the stored sample sig_q catches up to the live input on the next clock edge, so after one cycle the two samples match again and the comparison goes back to 0.
What is the role of the asynchronous reset on sig_q?
It forces the stored sample to a known 0 at startup. Without it the first comparison would use an unknown x value and could emit a false pulse in simulation.
How do you build a both-edge detector from the two samples?
XOR the current and previous samples: both = sig_in ^ sig_q. Any difference between the two means a transition occurred.
Why add a two-flip-flop synchronizer for an asynchronous input?
It retimes the signal into the clock domain and lets any metastability settle before the edge comparison, preventing double or missed pulses at the cost of a couple of cycles of latency.
Share your question in comments or talk to our mentor team for batch guidance.
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