Pulse Stretcher in Verilog: Extend a Short Pulse with Code

A pulse stretcher (also called a pulse extender or one-shot) takes a brief input pulse—sometimes only a single clock cycle wide—and produces an output that stays asserted for a fixed, programmable number of cycles. It is the circuit you reach for when a fast event must be made visible to a slower observer: driving an LED long enough for a human eye, holding a flag until a slow state machine can sample it, or widening a one-cycle strobe before it crosses into a slower clock domain.

This page gives you two pieces of pulse stretcher Verilog code—a retriggerable version and a non-retriggerable one-shot—each with a self-checking testbench. Both are written in portable Verilog-2005 and the simulation logs below are produced by Icarus Verilog, so you can see exactly how the output width tracks the parameter.

This retriggerable stretcher reloads a down-counter every time a pulse arrives. A single one-cycle pulse holds pulse_out high for exactly STRETCH cycles; a new pulse inside that window restarts the count, so a steady stream of pulses keeps the output continuously asserted.

// Retriggerable pulse stretcher: a pulse on pulse_in (re)loads a down-counter
// to STRETCH and holds pulse_out high until the count expires. Any new pulse
// during the window restarts the count (retriggerable).
module pulse_stretch_retrig #(parameter STRETCH = 4) (
    input  wire clk,
    input  wire rst_n,
    input  wire pulse_in,
    output reg  pulse_out
);
    localparam CW = 16;
    reg [CW-1:0] cnt;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt       <= {CW{1'b0}};
            pulse_out <= 1'b0;
        end else if (pulse_in) begin
            // (Re)trigger: reload counter, assert output.
            cnt       <= STRETCH[CW-1:0];
            pulse_out <= 1'b1;
        end else if (cnt != {CW{1'b0}}) begin
            cnt       <= cnt - 1'b1;
            pulse_out <= (cnt != 16'd1); // drop on the last tick
        end else begin
            pulse_out <= 1'b0;
        end
    end
endmodule

module tb;
    localparam STRETCH = 4;
    reg clk, rst_n, pulse_in;
    wire pulse_out;
    integer high_count;
    integer errors;

    pulse_stretch_retrig #(.STRETCH(STRETCH)) dut (
        .clk(clk), .rst_n(rst_n), .pulse_in(pulse_in), .pulse_out(pulse_out)
    );

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

    initial begin
        errors = 0;
        rst_n = 1'b0; pulse_in = 1'b0;
        @(negedge clk);
        @(negedge clk); rst_n = 1'b1;

        // ---- Case 1: single 1-cycle pulse should hold output high exactly STRETCH cycles ----
        @(negedge clk); pulse_in = 1'b1;   // 1-cycle input pulse
        @(negedge clk); pulse_in = 1'b0;   // pulse_out is now asserted
        high_count = 0;
        while (pulse_out === 1'b1) begin
            high_count = high_count + 1;
            @(negedge clk);
        end
        if (high_count == STRETCH)
            $display("PASS: single pulse held output high %0d cycles (== STRETCH)", high_count);
        else begin
            $display("FAIL: single pulse held output high %0d cycles, expected %0d", high_count, STRETCH);
            errors = errors + 1;
        end

        // settle a few idle cycles
        repeat (3) @(negedge clk);

        // ---- Case 2: retrigger during window restarts the count ----
        // Pulse, idle 1, pulse again -> last trigger is 2 cycles after the first,
        // so total high window = 2 + STRETCH = 6 cycles (known-answer, hand-computed).
        @(negedge clk); pulse_in = 1'b1;
        @(negedge clk); pulse_in = 1'b0;
        @(negedge clk); pulse_in = 1'b1;   // retrigger
        @(negedge clk); pulse_in = 1'b0;
        // output has been high since the first trigger; count remaining high cycles + the 2 already elapsed
        high_count = 2;                    // the two cycles already observed high before this point
        while (pulse_out === 1'b1) begin
            high_count = high_count + 1;
            @(negedge clk);
        end
        if (high_count == 6 && high_count > STRETCH)
            $display("PASS: retrigger extended output to %0d cycles (> STRETCH, restarted count)", high_count);
        else begin
            $display("FAIL: retrigger gave %0d high cycles, expected 6", high_count);
            errors = errors + 1;
        end

        // ---- Case 3: asynchronous reset clears the output mid-window ----
        @(negedge clk); pulse_in = 1'b1;
        @(negedge clk); pulse_in = 1'b0;
        if (pulse_out === 1'b1) $display("PASS: output asserted before reset");
        else begin $display("FAIL: output not asserted before reset"); errors = errors + 1; end
        rst_n = 1'b0; #1;
        if (pulse_out === 1'b0) $display("PASS: async reset cleared output");
        else begin $display("FAIL: async reset did not clear output"); errors = errors + 1; end
        rst_n = 1'b1;

        if (errors == 0) $display("ALL TESTS PASSED");
        else $display("TESTS FAILED: %0d error(s)", errors);
        $finish;
    end
endmodule
PASS: single pulse held output high 4 cycles (== STRETCH)
PASS: retrigger extended output to 6 cycles (> STRETCH, restarted count)
PASS: output asserted before reset
PASS: async reset cleared output
ALL TESTS PASSED

The log confirms the three properties that matter: a lone one-cycle pulse yields an output exactly STRETCH (4) cycles wide, a retrigger two cycles into the window extends the total high time to 6 cycles (proving the count truly restarts), and the asynchronous active-low reset drops pulse_out immediately mid-window.

The non-retriggerable one-shot latches a busy flag when it fires and ignores every further trigger until the current pulse finishes. The testbench sweeps two widths (STRETCH = 3 and 6) and then proves that a mid-window retrigger is discarded.

// Non-retriggerable one-shot pulse stretcher: a trigger starts a fixed STRETCH-
// cycle output pulse. While that pulse is active (busy), further triggers are
// ignored until the one-shot completes.
module one_shot_nonretrig #(parameter STRETCH = 4) (
    input  wire clk,
    input  wire rst_n,
    input  wire trig,
    output reg  pulse_out
);
    localparam CW = 16;
    reg [CW-1:0] cnt;
    reg          busy;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt       <= {CW{1'b0}};
            busy      <= 1'b0;
            pulse_out <= 1'b0;
        end else if (!busy) begin
            if (trig) begin
                cnt       <= STRETCH[CW-1:0] - 1'b1; // this cycle is the first high cycle
                busy      <= 1'b1;
                pulse_out <= 1'b1;
            end else begin
                pulse_out <= 1'b0;
            end
        end else begin
            // busy: ignore trig until the pulse finishes
            if (cnt != {CW{1'b0}}) begin
                cnt       <= cnt - 1'b1;
                pulse_out <= 1'b1;
            end else begin
                busy      <= 1'b0;
                pulse_out <= 1'b0;
            end
        end
    end
endmodule

module tb;
    reg clk, rst_n, trig;

    // Two widths exercised to sweep the self-check.
    wire out3, out6;
    one_shot_nonretrig #(.STRETCH(3)) dut3 (.clk(clk), .rst_n(rst_n), .trig(trig), .pulse_out(out3));
    one_shot_nonretrig #(.STRETCH(6)) dut6 (.clk(clk), .rst_n(rst_n), .trig(trig), .pulse_out(out6));

    integer errors;
    integer c3, c6;

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

    // measure consecutive high cycles of a signal starting now (sampled on negedges)
    task count_high;
        input  sig_is_out3;  // 1 -> watch out3, 0 -> watch out6
        output integer n;
        begin
            n = 0;
            if (sig_is_out3) begin
                while (out3 === 1'b1) begin n = n + 1; @(negedge clk); end
            end else begin
                while (out6 === 1'b1) begin n = n + 1; @(negedge clk); end
            end
        end
    endtask

    initial begin
        errors = 0;
        rst_n = 1'b0; trig = 1'b0;
        @(negedge clk);
        @(negedge clk); rst_n = 1'b1;

        // ---- Case 1: single trigger -> output high exactly STRETCH cycles (both widths) ----
        @(negedge clk); trig = 1'b1;
        @(negedge clk); trig = 1'b0;   // outputs now asserted
        // out3 is the shorter one; capture it first, out6 keeps running in parallel.
        count_high(1'b1, c3);
        if (c3 == 3) $display("PASS: STRETCH=3 one-shot held %0d cycles", c3);
        else begin $display("FAIL: STRETCH=3 gave %0d cycles, expected 3", c3); errors = errors + 1; end

        // out6 may already be partway or done; re-run a fresh trigger for a clean count.
        repeat (4) @(negedge clk);     // ensure both DUTs idle
        @(negedge clk); trig = 1'b1;
        @(negedge clk); trig = 1'b0;
        count_high(1'b0, c6);
        if (c6 == 6) $display("PASS: STRETCH=6 one-shot held %0d cycles", c6);
        else begin $display("FAIL: STRETCH=6 gave %0d cycles, expected 6", c6); errors = errors + 1; end

        // ---- Case 2: retrigger during the window is IGNORED (non-retriggerable) ----
        repeat (4) @(negedge clk);
        @(negedge clk); trig = 1'b1;   // first trigger (STRETCH=3)
        @(negedge clk); trig = 1'b0;
        @(negedge clk); trig = 1'b1;   // mid-window retrigger -> must be ignored
        @(negedge clk); trig = 1'b0;
        // out3 has been high since the first trigger; 2 cycles already elapsed high.
        c3 = 2;
        while (out3 === 1'b1) begin c3 = c3 + 1; @(negedge clk); end
        if (c3 == 3) $display("PASS: mid-window retrigger ignored, output stayed exactly %0d cycles", c3);
        else begin $display("FAIL: retrigger not ignored, got %0d high cycles, expected 3", c3); errors = errors + 1; end

        if (errors == 0) $display("ALL TESTS PASSED");
        else $display("TESTS FAILED: %0d error(s)", errors);
        $finish;
    end
endmodule
PASS: STRETCH=3 one-shot held 3 cycles
PASS: STRETCH=6 one-shot held 6 cycles
PASS: mid-window retrigger ignored, output stayed exactly 3 cycles
ALL TESTS PASSED

The log shows both parameterized widths producing output pulses of exactly 3 and 6 cycles, and—critically—a second trigger fired two cycles into an active 3-cycle pulse is ignored, so the output still stops after exactly 3 cycles instead of extending. That is the defining difference from the retriggerable design above.

Retriggerable vs non-retriggerable

The single most important design decision for a pulse stretcher is what happens when a new input event arrives while the output is still asserted.

  • Retriggerable (Example 1): every incoming pulse reloads the counter. The output stays high until STRETCH cycles after the last trigger. Use this when you want the output to represent “activity seen within the last N cycles”—for example a link-activity LED or a watchdog-style timeout that resets on each event.
  • Non-retriggerable (Example 2): once fired, the one-shot ignores triggers until it completes. The output width is always exactly STRETCH cycles regardless of input activity. Use this when you need a bounded, deterministic pulse width—a fixed blanking interval, a debounce dead-time, or a single-shot command strobe.

Edge-detect on the input

The examples above trigger on the input being high for a cycle. In a real system the “pulse” may actually be a level that stays high for many cycles, and you usually want to stretch only the rising edge. Add a one-flop edge detector in front of the stretcher:

reg in_d;
wire rise = in & ~in_d;
always @(posedge clk or negedge rst_n)
    if (!rst_n) in_d <= 1'b0;
    else        in_d <= in;

Feed rise into pulse_in/trig instead of the raw level. This guarantees a single-cycle trigger per edge and keeps the stretch length independent of how long the source holds its level.

Choosing the stretch length

The counter width must hold STRETCH, so size it as $clog2(STRETCH+1) bits in real code (the examples use a generous fixed 16-bit counter for clarity). Pick STRETCH from the ratio of the two things you are bridging:

  • Human-visible LED: stretch to roughly 20–50 ms. At a 50 MHz clock that is about 1–2.5 million cycles.
  • Slow-clock sampling: stretch to at least two periods of the slower clock so the destination is guaranteed to sample the output at least once.

Always round up: a pulse that is one cycle too short in the destination domain is as good as lost.

Crossing to a slow clock domain

A pulse stretcher is often the first half of a pulse synchronizer. If a source-domain strobe is narrower than one destination-clock period, the destination's sampling flop can miss it entirely. Widening the pulse so it spans at least one—preferably two—destination edges makes the capture reliable.

Be careful, though: stretching alone does not make a signal safe to sample. The stretched output must still pass through a two-flop synchronizer in the destination domain to resolve metastability. For bursts of pulses that must each be counted, a proper toggle-based (closed-loop) pulse synchronizer is the correct structure; a simple stretcher only guarantees that at least one pulse is seen, not that every pulse is counted.

Reset behavior

Both designs use an asynchronous active-low reset that forces pulse_out, the counter, and the busy flag to their idle state, and Example 1's testbench checks that reset clears the output mid-window. In an FPGA flow you may prefer a synchronous reset; either way the key requirement is that reset leave the stretcher idle with the output deasserted, so a spurious pulse is never emitted at power-up.

FAQ

Is a pulse stretcher the same as a monostable multivibrator?

Conceptually yes—a digital one-shot is the synchronous equivalent of a 555-style monostable. Instead of an RC time constant it uses a clock and a counter, which makes the output width exact and repeatable.

Why not just use a shift register?

For small, fixed stretch lengths a shift register works and needs no comparator. A counter scales far better: a 20-cycle stretch is a 5-bit counter versus a 20-flop shift register, and the length becomes a single parameter.

Can the output glitch?

No. In both designs pulse_out is a registered flip-flop output, so it is glitch-free and changes only on a clock edge—exactly what you want when the stretched pulse feeds other synchronous logic.

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