Skip to content

RTL Design Patterns · Chapter 3 · Counters, Timers & Pulse Generators

Pulse & Strobe Generators

Control logic thinks in events: start now, one sample is ready, advance the pointer once. But the signals feeding it usually arrive as levels, like a go bit firmware holds high for hundreds of cycles or a status flag that stays asserted. A held-high level is not an event, so wiring it into logic that means do it once fires that action every single cycle the level stays high. The fix is the pulse or strobe generator, which turns a level or a slow edge into a clean single-cycle pulse, high for exactly one cycle per event. The core idea is that a strobe is this cycle's value and not last cycle's: register the signal and gate it. From that atom come rising, falling, and any-edge detectors, the one-shot that re-arms on deassert, the pulse stretcher, and the async-input hazard. Everything is built and self-checked in SystemVerilog, Verilog, and VHDL.

Intermediate16 min readRTL Design PatternsEdge DetectorSingle-Cycle StrobeOne-ShotPulse StretcherClock Domain Crossing

Chapter 3 · Section 3.6 · Counters, Timers & Pulse Generators

1. The Engineering Problem

You are building a small transaction engine. It exposes a control register with a start bit: firmware writes 1 to start, and the engine is supposed to launch one transaction. Simple enough — until you look at what start actually is on the wire. Firmware sets the bit and moves on; the register keeps driving start high for as long as the bit is set, which is hundreds of clock cycles. From the hardware's point of view, start is not the event "launch now" — it is the level "the start bit is still 1," held high cycle after cycle.

Now wire that level into the launch logic the obvious way: if (start) begin launch_transaction; end. Every cycle start is high, the condition is true, so the engine launches a transaction — and then another, and another, one per clock, a flood of hundreds of back-to-back transactions from a single firmware write. The bug is not in the launch logic; it is in the mismatch between what the signal is (a level, true for many cycles) and what the logic needs (an event, true for exactly one cycle).

This mismatch is everywhere in real design. Control logic is written in the language of events — "sample ready," "advance the pointer," "kick off the DMA," "toggle the LED" — each of which must happen once per occurrence. But the signals that carry those occurrences arrive as levels (a held-high enable, a status flag that stays asserted, a go bit) or as the edges of slow inputs (a debounced button, a slow handshake line). Something has to stand between the level and the event-driven logic and convert one into the other: take a signal that is high for many cycles and emit a pulse that is high for exactly one cycle, exactly once, at the moment the level became true. That converter is the pulse/strobe generator, and it is why a well-designed start fires once instead of every cycle.

the-need.v — a held-high level is not a one-shot event
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // Firmware writes the start bit; hardware sees a LEVEL held high for many cycles:
   //   start = 1'b1;   // stays 1 for hundreds of cycles until firmware clears it
 
   // WRONG — treat the LEVEL as the event: launches EVERY cycle start is high.
   //   always @(posedge clk) if (start) launch <= 1'b1;   // a FLOOD of launches
 
   // What the engine needs is a single-cycle STROBE at the moment start went high:
   //   start_pulse high for EXACTLY ONE cycle, then low even while start stays high.
   //   That pulse is: start_pulse = start & ~start_d;   // this cycle AND-NOT last
   //   always @(posedge clk) if (start_pulse) launch <= 1'b1;   // launches ONCE

2. Mental Model

3. Pattern Anatomy

The whole family is built from one atom — a register plus a gate — arranged three ways, plus one hazard that governs when the atom is even valid.

The edge detector (the atom). One register (sig_d <= sig_in) delays the signal by a cycle; one gate compares the two versions. The three useful gates give the three edge polarities:

  • Rising-edge strobe: rise_pulse = sig_in & ~sig_d — high for one cycle when the signal goes 0→1.
  • Falling-edge strobe: fall_pulse = ~sig_in & sig_d — high for one cycle when the signal goes 1→0.
  • Any-edge strobe: any_pulse = sig_in ^ sig_d — high for one cycle on either transition.

Every one of these is high for exactly one cycle because sig_d catches up to sig_in on the very next edge, collapsing the disagreement. That single-cycle guarantee is the whole point of a strobe.

The one-shot (level → single pulse, re-arm on deassert). A one-shot is the rising-edge detector applied to a level. A go level held high for many cycles produces exactly one go_pulse, and — crucially — it will not produce a second pulse until go returns to 0 and rises again. The falling edge is the re-arm: no re-arm, no second pulse. This is the pattern behind "one action per button press" or "one transaction per firmware write," and it is why a correctly-built start fires once.

The pulse stretcher (1-cycle → N-cycle level). The inverse problem: you have a clean single-cycle strobe, but a slower consumer needs the signal held high for N cycles (a slow-clocked peripheral, an LED you want visible, a downstream block that samples less often). A stretcher catches the 1-cycle pulse and holds its output high while a down-counter (loaded to N on the pulse) counts down — output high for exactly N cycles, then low. It is a register (the counter, 3.1) plus a comparator, the mirror image of the edge detector: one narrows, the other widens.

The pulse family — one register-plus-gate atom, three arrangements, one hazard

data flow
The pulse family — one register-plus-gate atom, three arrangements, one hazarddelay registersig_d <= sig_in (the value LAST cycle)rising strobesig_in & ~sig_d → 1 cycle on 0→1falling strobe~sig_in & sig_d → 1 cycle on 1→0any-edge strobesig_in ^ sig_d → 1 cycle on either edgeone-shotrising strobe on a LEVEL; re-arms on deassertpulse stretcher1-cycle pulse → N-cycle level (down-counter)async-inputhazardunsynchronized sig → FALSE/double pulses
One delay register holds the signal's value from last cycle. AND-NOT it with this cycle for a rising strobe, swap the gate for falling or any-edge. Apply the rising strobe to a held-high level and you get a one-shot that re-arms only when the level drops; feed a single strobe into a down-counter and you get a pulse stretcher that re-widens it to N cycles. The whole family is the register of Chapter 2.1 plus one gate. The one place it breaks: if sig_in was never synchronized to this clock, sig_d vs sig_in can disagree for reasons that are not a real edge — false or double pulses (synchronize first). Same in SystemVerilog, Verilog, and VHDL.

The async-input hazard (why the atom can lie). The edge detector's correctness rests on one assumption: that sig_in is a clean, synchronous signal, stable around each clock edge, so that "its value this cycle vs last cycle" is a meaningful comparison. If sig_in comes from another clock domain (or an asynchronous input like a raw button) and was not synchronized first, it can change right at the sampling edge — the register may go metastable, and worse, the two flops that form sig_in and sig_d can end up disagreeing about a single transition, producing a false pulse or a double pulse from one real edge. The rule is absolute and is previewed in Chapter 11: synchronize an async signal through a two-flop synchronizer first, then edge-detect the clean, synchronous output. Never edge-detect a raw cross-domain signal. The atom on this page assumes a synchronous input; §6 and the synchronizer chapter cover the crossing.

4. Real RTL Implementation

This is the core of the page. We build three patterns — (a) the edge detector / single-cycle strobe (register + pulse = sig_in & ~sig_d, with rising/falling/any variants), (b) the N-generic pulse stretcher (1-cycle → N-cycle via a down-counter), and (c) the level-as-pulse bug (buggy level-driven start vs edge-detected fix) — and each is shown in SystemVerilog, Verilog, and VHDL, with both an RTL block and a self-checking, clocked testbench. The idea is identical in all three; the syntax differs, and seeing them side by side is what makes the pattern language-independent in your head.

One discipline runs through every clocked RTL block: the delay register is written with non-blocking assignment (<=) so sig_d really is last cycle's value, and every design has an explicit synchronous reset that clears the history to a known state — without it, sig_d is X at power-up and the first "edge" is spurious.

4a. The edge detector — single-cycle strobe, three ways

Start with the atom. One flop delays the signal; three gates give the three edges. Because sig_d catches up to sig_in on the next edge, each pulse is high for exactly one cycle. The reset clears sig_d to 0 so the first real rising edge is detected honestly (and no phantom pulse fires at power-up).

edge_detect.sv — rising / falling / any-edge single-cycle strobes
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module edge_detect (
       input  logic clk,
       input  logic rst,               // synchronous, active-high; clears the history
       input  logic sig_in,            // MUST be synchronous to clk (see §6 for async)
       output logic rise_pulse,        // 1 cycle when sig_in goes 0 -> 1
       output logic fall_pulse,        // 1 cycle when sig_in goes 1 -> 0
       output logic any_pulse          // 1 cycle on EITHER edge
   );
       logic sig_d;                    // sig_in delayed by one cycle = "the value LAST cycle"
 
       // The ONLY state: a one-cycle delay of sig_in. Non-blocking so sig_d is
       // genuinely the pre-edge value; reset to 0 so power-up cannot fake an edge.
       always_ff @(posedge clk) begin
           if (rst) sig_d <= 1'b0;
           else     sig_d <= sig_in;
       end
 
       // Pure combinational compare of "now" vs "then" — one gate per edge polarity.
       assign rise_pulse = sig_in & ~sig_d;   // high AND was-low  -> rose this cycle
       assign fall_pulse = ~sig_in & sig_d;   // low  AND was-high -> fell this cycle
       assign any_pulse  = sig_in ^ sig_d;    // disagree          -> either edge
   endmodule

The self-checking testbench does the thing the whole pattern promises: it holds sig_in high for many cycles and asserts rise_pulse is high for exactly one of them, then drops it and checks fall_pulse fires exactly once. A reference model computes the expected pulses from a locally-registered copy of the stimulus, so the check is automatic every cycle.

edge_detect_tb.sv — hold sig_in high for many cycles; assert exactly one rise_pulse
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module edge_detect_tb;
       logic clk = 0, rst = 1, sig_in = 0;
       logic rise_pulse, fall_pulse, any_pulse;
       logic sig_ref;                          // reference: sig_in delayed one cycle
       int   rise_count = 0, fall_count = 0, errors = 0;
 
       edge_detect dut (.clk(clk), .rst(rst), .sig_in(sig_in),
                        .rise_pulse(rise_pulse), .fall_pulse(fall_pulse), .any_pulse(any_pulse));
 
       always #5 clk = ~clk;                    // rising edge every 10 time units
 
       // Independent reference model of the one-cycle delay, updated every edge.
       always_ff @(posedge clk) sig_ref <= rst ? 1'b0 : sig_in;
 
       initial begin
           @(negedge clk); rst = 1; @(negedge clk); rst = 0;   // release reset
           // Drive sig_in HIGH and hold it for 6 cycles: exactly ONE rise_pulse expected.
           sig_in = 1;
           for (int c = 0; c < 6; c++) begin
               @(posedge clk); #1;
               if (rise_pulse) rise_count++;
               // Invariant: the strobe equals the reference AND-NOT every cycle.
               assert (rise_pulse === (sig_in & ~sig_ref))
                   else begin $error("rise_pulse mismatch @cyc %0d", c); errors++; end
           end
           // Now drop it and hold LOW for 6 cycles: exactly ONE fall_pulse expected.
           sig_in = 0;
           for (int c = 0; c < 6; c++) begin
               @(posedge clk); #1;
               if (fall_pulse) fall_count++;
           end
           // A level held high for 6 cycles must yield EXACTLY ONE rising strobe.
           assert (rise_count == 1)
               else begin $error("expected 1 rise_pulse, got %0d", rise_count); errors++; end
           assert (fall_count == 1)
               else begin $error("expected 1 fall_pulse, got %0d", fall_count); errors++; end
           if (errors == 0) $display("PASS: one strobe per edge, exactly one cycle wide");
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule

The Verilog form is identical in intent — reg/wire typing and always @(posedge clk) instead of always_ff. The compare gates are the same three continuous assignments.

edge_detect.v — rising / falling / any-edge strobes in Verilog
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module edge_detect (
       input  wire clk,
       input  wire rst,                // synchronous, active-high
       input  wire sig_in,             // must be synchronous to clk
       output wire rise_pulse,
       output wire fall_pulse,
       output wire any_pulse
   );
       reg sig_d;                       // one-cycle delay of sig_in
 
       always @(posedge clk) begin
           if (rst) sig_d <= 1'b0;      // clear history on reset (no phantom edge)
           else     sig_d <= sig_in;    // non-blocking: sig_d is last cycle's value
       end
 
       assign rise_pulse = sig_in & ~sig_d;   // 0 -> 1
       assign fall_pulse = ~sig_in & sig_d;   // 1 -> 0
       assign any_pulse  = sig_in ^ sig_d;    // either edge
   endmodule
edge_detect_tb.v — hold input high; count strobes; print PASS/FAIL
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module edge_detect_tb;
       reg     clk, rst, sig_in;
       wire    rise_pulse, fall_pulse, any_pulse;
       reg     sig_ref;
       integer c, rise_count, fall_count, errors;
 
       edge_detect dut (.clk(clk), .rst(rst), .sig_in(sig_in),
                        .rise_pulse(rise_pulse), .fall_pulse(fall_pulse), .any_pulse(any_pulse));
 
       initial clk = 1'b0;
       always #5 clk = ~clk;
 
       always @(posedge clk) sig_ref <= rst ? 1'b0 : sig_in;   // reference delay
 
       initial begin
           rise_count = 0; fall_count = 0; errors = 0;
           rst = 1'b1; sig_in = 1'b0;
           @(negedge clk); @(negedge clk); rst = 1'b0;          // release reset
           sig_in = 1'b1;                                       // hold HIGH for 6 cycles
           for (c = 0; c < 6; c = c + 1) begin
               @(posedge clk); #1;
               if (rise_pulse) rise_count = rise_count + 1;
               if (rise_pulse !== (sig_in & ~sig_ref)) begin
                   $display("FAIL: rise_pulse mismatch @cyc %0d", c);
                   errors = errors + 1;
               end
           end
           sig_in = 1'b0;                                       // hold LOW for 6 cycles
           for (c = 0; c < 6; c = c + 1) begin
               @(posedge clk); #1;
               if (fall_pulse) fall_count = fall_count + 1;
           end
           if (rise_count !== 1) begin
               $display("FAIL: expected 1 rise_pulse, got %0d", rise_count);
               errors = errors + 1;
           end
           if (fall_count !== 1) begin
               $display("FAIL: expected 1 fall_pulse, got %0d", fall_count);
               errors = errors + 1;
           end
           if (errors == 0) $display("PASS: one strobe per edge, exactly one cycle wide");
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule

In VHDL the delay register is a process(clk) guarded by rising_edge(clk); the compare gates are concurrent assignments using and, not, and xor. The signal assignment <= is inherently non-blocking, so sig_d is last cycle's value exactly as in SV/Verilog.

edge_detect.vhd — rising / falling / any-edge strobes in VHDL
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
 
   entity edge_detect is
       port (
           clk        : in  std_logic;
           rst        : in  std_logic;            -- synchronous, active-high
           sig_in     : in  std_logic;            -- must be synchronous to clk
           rise_pulse : out std_logic;
           fall_pulse : out std_logic;
           any_pulse  : out std_logic
       );
   end entity;
 
   architecture rtl of edge_detect is
       signal sig_d : std_logic := '0';           -- one-cycle delay of sig_in
   begin
       -- The only state: a one-cycle delay. Cleared on reset so no phantom edge.
       process (clk)
       begin
           if rising_edge(clk) then
               if rst = '1' then sig_d <= '0';
               else              sig_d <= sig_in;
               end if;
           end if;
       end process;
 
       -- Combinational compare of now vs then — one gate per edge polarity.
       rise_pulse <= sig_in and (not sig_d);      -- 0 -> 1
       fall_pulse <= (not sig_in) and sig_d;      -- 1 -> 0
       any_pulse  <= sig_in xor sig_d;            -- either edge
   end architecture;
edge_detect_tb.vhd — hold input high; assert exactly one rise_pulse (assert report severity)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
 
   entity edge_detect_tb is
   end entity;
 
   architecture sim of edge_detect_tb is
       signal clk, rst, sig_in : std_logic := '0';
       signal rise_pulse, fall_pulse, any_pulse : std_logic;
   begin
       dut : entity work.edge_detect
           port map (clk => clk, rst => rst, sig_in => sig_in,
                     rise_pulse => rise_pulse, fall_pulse => fall_pulse, any_pulse => any_pulse);
 
       clk <= not clk after 5 ns;                 -- rising edge every 10 ns
 
       stim : process
           variable rise_count : integer := 0;
           variable fall_count : integer := 0;
       begin
           rst <= '1'; sig_in <= '0';
           wait until falling_edge(clk); wait until falling_edge(clk);
           rst <= '0';                            -- release reset
           sig_in <= '1';                         -- hold HIGH for 6 cycles
           for c in 0 to 5 loop
               wait until rising_edge(clk); wait for 1 ns;
               if rise_pulse = '1' then rise_count := rise_count + 1; end if;
           end loop;
           sig_in <= '0';                         -- hold LOW for 6 cycles
           for c in 0 to 5 loop
               wait until rising_edge(clk); wait for 1 ns;
               if fall_pulse = '1' then fall_count := fall_count + 1; end if;
           end loop;
           -- A level held high many cycles must produce EXACTLY ONE rising strobe.
           assert rise_count = 1
               report "expected exactly one rise_pulse" severity error;
           assert fall_count = 1
               report "expected exactly one fall_pulse" severity error;
           report "edge_detect self-check complete" severity note;
           wait;
       end process;
   end architecture;

4b. The pulse stretcher — 1-cycle strobe → N-cycle level (N-generic)

Now the inverse. A single-cycle strobe arrives, but a slower consumer needs the signal held high for N cycles. Load a down-counter to N-1 on the pulse and hold the output high while it counts to zero — high for exactly N cycles. N is a parameter (generic in VHDL), and one design serves any stretch width. One decision is load-bearing: a strobe that arrives mid-stretch must re-trigger (reload the counter), not be ignored and not shorten the current stretch — so a new event always gets its full N cycles.

pulse_stretch.sv — 1-cycle pulse to N-cycle level (down-counter, N-generic)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module pulse_stretch #(
       parameter int N = 4                      // output stays high for exactly N cycles
   )(
       input  logic clk,
       input  logic rst,                        // synchronous, active-high
       input  logic pulse_in,                   // single-cycle strobe (from 4a)
       output logic stretch_out                 // high for N cycles after each pulse_in
   );
       localparam int CW = (N > 1) ? $clog2(N) : 1;
       logic [CW-1:0] cnt;                      // cycles remaining in the current stretch
 
       always_ff @(posedge clk) begin
           if (rst) begin
               cnt         <= '0;
               stretch_out <= 1'b0;
           end else if (pulse_in) begin
               // (Re)trigger: a new event ALWAYS gets a full N-cycle stretch, even
               // if one is already in progress. Load N-1 and drive the output high.
               cnt         <= CW'(N - 1);
               stretch_out <= 1'b1;
           end else if (cnt != 0) begin
               cnt         <= cnt - 1'b1;        // count down the remaining cycles
               stretch_out <= 1'b1;
           end else begin
               stretch_out <= 1'b0;              // counter drained -> stretch done
           end
       end
   endmodule

The testbench fires a single-cycle pulse_in, then holds pulse_in low and asserts stretch_out stays high for exactly N cycles and then drops — the direct proof that a 1-cycle event became an N-cycle level.

pulse_stretch_tb.sv — one input pulse; assert stretch_out high for exactly N cycles
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module pulse_stretch_tb;
       localparam int N = 4;
       logic clk = 0, rst = 1, pulse_in = 0, stretch_out;
       int   high_count = 0, errors = 0;
 
       pulse_stretch #(.N(N)) dut (.clk(clk), .rst(rst), .pulse_in(pulse_in), .stretch_out(stretch_out));
 
       always #5 clk = ~clk;
 
       initial begin
           @(negedge clk); rst = 1; @(negedge clk); rst = 0;
           // Fire ONE single-cycle pulse:
           pulse_in = 1; @(negedge clk); pulse_in = 0;
           // Count how many consecutive cycles stretch_out stays high afterwards.
           for (int c = 0; c < N + 3; c++) begin
               @(posedge clk); #1;
               if (stretch_out) high_count++;
           end
           // A 1-cycle pulse must become an EXACTLY-N-cycle level.
           assert (high_count == N)
               else begin $error("stretch width %0d, expected %0d", high_count, N); errors++; end
           if (errors == 0) $display("PASS: 1-cycle pulse stretched to exactly N=%0d cycles", N);
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule

The Verilog stretcher is the same counter with reg typing; SELW-style width handling is done with a fixed parameter the instantiator sets.

pulse_stretch.v — 1-cycle pulse to N-cycle level in Verilog
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module pulse_stretch #(
       parameter N  = 4,
       parameter CW = 2                          // = clog2(N); set by the instantiator
   )(
       input  wire clk,
       input  wire rst,
       input  wire pulse_in,
       output reg  stretch_out
   );
       reg [CW-1:0] cnt;
 
       always @(posedge clk) begin
           if (rst) begin
               cnt         <= {CW{1'b0}};
               stretch_out <= 1'b0;
           end else if (pulse_in) begin
               cnt         <= N - 1;             // (re)trigger: full N-cycle stretch
               stretch_out <= 1'b1;
           end else if (cnt != {CW{1'b0}}) begin
               cnt         <= cnt - 1'b1;        // count the remaining cycles down
               stretch_out <= 1'b1;
           end else begin
               stretch_out <= 1'b0;             // stretch complete
           end
       end
   endmodule
pulse_stretch_tb.v — one input pulse; assert stretch_out high for exactly N cycles
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module pulse_stretch_tb;
       parameter N  = 4;
       parameter CW = 2;
       reg     clk, rst, pulse_in;
       wire    stretch_out;
       integer c, high_count, errors;
 
       pulse_stretch #(.N(N), .CW(CW)) dut
           (.clk(clk), .rst(rst), .pulse_in(pulse_in), .stretch_out(stretch_out));
 
       initial clk = 1'b0;
       always #5 clk = ~clk;
 
       initial begin
           high_count = 0; errors = 0;
           rst = 1'b1; pulse_in = 1'b0;
           @(negedge clk); @(negedge clk); rst = 1'b0;
           pulse_in = 1'b1; @(negedge clk); pulse_in = 1'b0;   // ONE single-cycle pulse
           for (c = 0; c < N + 3; c = c + 1) begin
               @(posedge clk); #1;
               if (stretch_out) high_count = high_count + 1;
           end
           if (high_count !== N) begin
               $display("FAIL: stretch width %0d, expected %0d", high_count, N);
               errors = errors + 1;
           end
           if (errors == 0) $display("PASS: 1-cycle pulse stretched to exactly N=%0d cycles", N);
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule

In VHDL the counter is an integer range (a clean, self-documenting width) reloaded on the pulse; the generic sets N. The output is high while the counter is nonzero or the pulse arrives.

pulse_stretch.vhd — 1-cycle pulse to N-cycle level in VHDL (generic N, down-counter)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
 
   entity pulse_stretch is
       generic ( N : positive := 4 );            -- output high for exactly N cycles
       port (
           clk         : in  std_logic;
           rst         : in  std_logic;          -- synchronous, active-high
           pulse_in    : in  std_logic;          -- single-cycle strobe
           stretch_out : out std_logic
       );
   end entity;
 
   architecture rtl of pulse_stretch is
       signal cnt : integer range 0 to N := 0;   -- cycles remaining in the stretch
   begin
       process (clk)
       begin
           if rising_edge(clk) then
               if rst = '1' then
                   cnt         <= 0;
                   stretch_out <= '0';
               elsif pulse_in = '1' then
                   cnt         <= N - 1;          -- (re)trigger: full N-cycle stretch
                   stretch_out <= '1';
               elsif cnt /= 0 then
                   cnt         <= cnt - 1;        -- count down the remaining cycles
                   stretch_out <= '1';
               else
                   stretch_out <= '0';           -- stretch complete
               end if;
           end if;
       end process;
   end architecture;
pulse_stretch_tb.vhd — one input pulse; assert stretch is exactly N cycles (assert report)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
 
   entity pulse_stretch_tb is
   end entity;
 
   architecture sim of pulse_stretch_tb is
       constant N : positive := 4;
       signal clk, rst, pulse_in : std_logic := '0';
       signal stretch_out        : std_logic;
   begin
       dut : entity work.pulse_stretch generic map (N => N)
           port map (clk => clk, rst => rst, pulse_in => pulse_in, stretch_out => stretch_out);
 
       clk <= not clk after 5 ns;
 
       stim : process
           variable high_count : integer := 0;
       begin
           rst <= '1'; pulse_in <= '0';
           wait until falling_edge(clk); wait until falling_edge(clk);
           rst <= '0';
           pulse_in <= '1'; wait until falling_edge(clk); pulse_in <= '0';   -- one pulse
           for c in 0 to N + 2 loop
               wait until rising_edge(clk); wait for 1 ns;
               if stretch_out = '1' then high_count := high_count + 1; end if;
           end loop;
           -- A 1-cycle pulse must become an EXACTLY-N-cycle level.
           assert high_count = N
               report "stretch width /= N" severity error;
           report "pulse_stretch self-check complete" severity note;
           wait;
       end process;
   end architecture;

4c. The level-as-pulse bug — buggy (level drives start) vs fixed (edge-detected strobe)

Pattern (c) is the signature failure, shown here as buggy vs fixed RTL in each language, then dramatized narratively in §7. The bug: firmware sets a start bit and leaves it high, and the hardware wires that level straight into the launch condition — so a transaction launches on every cycle start is high, a flood of hundreds of repeats. The fix is one register and one gate: edge-detect start into a single-cycle strobe and launch on the strobe, which fires once and re-arms only when start drops.

start_bug.sv — BUGGY (level drives launch) vs FIXED (edge-detected strobe)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // BUGGY: `start` is a LEVEL held high for many cycles. Using it directly as the
   //        launch condition fires a NEW transaction every cycle it is high.
   module launcher_bad (
       input  logic clk,
       input  logic rst,
       input  logic start,             // firmware sets this and LEAVES IT HIGH
       output logic launch             // pulses to kick off ONE transaction
   );
       always_ff @(posedge clk) begin
           if (rst) launch <= 1'b0;
           else     launch <= start;   // launch high for EVERY cycle start is high -> FLOOD
       end
   endmodule
 
   // FIXED: edge-detect `start` into a single-cycle strobe. `launch` fires for ONE
   //        cycle when start RISES, and cannot fire again until start drops and rises.
   module launcher_good (
       input  logic clk,
       input  logic rst,
       input  logic start,
       output logic launch
   );
       logic start_d;                  // start delayed one cycle
       always_ff @(posedge clk) begin
           if (rst) start_d <= 1'b0;
           else     start_d <= start;
       end
       assign launch = start & ~start_d;   // ONE-shot: one launch per 0->1 of start
   endmodule
start_bug_tb.sv — hold start high; assert launch pulses EXACTLY once
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module start_bug_tb;
       logic clk = 0, rst = 1, start = 0, launch;
       int   launch_count = 0, errors = 0;
 
       // Point at launcher_good to PASS; at launcher_bad to see the flood.
       launcher_good dut (.clk(clk), .rst(rst), .start(start), .launch(launch));
 
       always #5 clk = ~clk;
 
       initial begin
           @(negedge clk); rst = 1; @(negedge clk); rst = 0;
           // Firmware behaviour: set start and HOLD it high for 8 cycles.
           start = 1;
           for (int c = 0; c < 8; c++) begin
               @(posedge clk); #1;
               if (launch) launch_count++;
           end
           start = 0; @(posedge clk); #1;
           // A single held-high start must launch EXACTLY ONE transaction.
           assert (launch_count == 1)
               else begin $error("expected 1 launch, got %0d (level treated as pulse?)", launch_count); errors++; end
           if (errors == 0) $display("PASS: one held-high start -> exactly one launch");
           else             $display("FAIL: %0d launches from one start (a flood)", launch_count);
           $finish;
       end
   endmodule

The Verilog pair is the same story with reg typing. Holding start high and counting launch pulses is what exposes the flood: the buggy module counts one per cycle, the fixed one counts exactly one.

start_bug.v — BUGGY vs FIXED in Verilog
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // BUGGY: level drives launch -> a new transaction every cycle start is high.
   module launcher_bad (
       input  wire clk,
       input  wire rst,
       input  wire start,
       output reg  launch
   );
       always @(posedge clk) begin
           if (rst) launch <= 1'b0;
           else     launch <= start;   // FLOOD: launch high every cycle start is high
       end
   endmodule
 
   // FIXED: edge-detect start into a single-cycle strobe -> one launch per rise.
   module launcher_good (
       input  wire clk,
       input  wire rst,
       input  wire start,
       output wire launch
   );
       reg start_d;
       always @(posedge clk) begin
           if (rst) start_d <= 1'b0;
           else     start_d <= start;
       end
       assign launch = start & ~start_d;   // one-shot: one launch per 0->1 of start
   endmodule
start_bug_tb.v — hold start high; assert launch pulses exactly once
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module start_bug_tb;
       reg     clk, rst, start;
       wire    launch;
       integer c, launch_count, errors;
 
       // Bind to launcher_good to PASS; to launcher_bad to observe the flood.
       launcher_good dut (.clk(clk), .rst(rst), .start(start), .launch(launch));
 
       initial clk = 1'b0;
       always #5 clk = ~clk;
 
       initial begin
           launch_count = 0; errors = 0;
           rst = 1'b1; start = 1'b0;
           @(negedge clk); @(negedge clk); rst = 1'b0;
           start = 1'b1;                                   // set start and HOLD it high
           for (c = 0; c < 8; c = c + 1) begin
               @(posedge clk); #1;
               if (launch) launch_count = launch_count + 1;
           end
           start = 1'b0; @(posedge clk); #1;
           if (launch_count !== 1) begin
               $display("FAIL: expected 1 launch, got %0d (level treated as pulse?)", launch_count);
               errors = errors + 1;
           end
           if (errors == 0) $display("PASS: one held-high start -> exactly one launch");
           else             $display("FAIL: %0d launches from one start (a flood)", launch_count);
           $finish;
       end
   endmodule

In VHDL the bug is identical: the buggy architecture registers the start level onto launch; the fixed one registers a delayed copy and drives launch from the AND-NOT. A raw async start would additionally need a two-flop synchronizer before this edge detector (§6).

start_bug.vhd — BUGGY (level) vs FIXED (edge-detected strobe)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
 
   -- BUGGY: registering the LEVEL makes launch high every cycle start is high.
   entity launcher_bad is
       port ( clk, rst, start : in std_logic; launch : out std_logic );
   end entity;
   architecture rtl of launcher_bad is
   begin
       process (clk)
       begin
           if rising_edge(clk) then
               if rst = '1' then launch <= '0';
               else              launch <= start;   -- FLOOD: one launch per high cycle
               end if;
           end if;
       end process;
   end architecture;
 
   -- FIXED: delay start one cycle and AND-NOT -> single-cycle strobe (one-shot).
   library ieee;
   use ieee.std_logic_1164.all;
   entity launcher_good is
       port ( clk, rst, start : in std_logic; launch : out std_logic );
   end entity;
   architecture rtl of launcher_good is
       signal start_d : std_logic := '0';
   begin
       process (clk)
       begin
           if rising_edge(clk) then
               if rst = '1' then start_d <= '0';
               else              start_d <= start;
               end if;
           end if;
       end process;
       launch <= start and (not start_d);          -- one launch per 0 -> 1 of start
   end architecture;
start_bug_tb.vhd — hold start high; assert launch pulses exactly once (assert report)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
 
   entity start_bug_tb is
   end entity;
 
   architecture sim of start_bug_tb is
       signal clk, rst, start : std_logic := '0';
       signal launch          : std_logic;
   begin
       -- Bind to launcher_good to PASS; to launcher_bad to observe the flood.
       dut : entity work.launcher_good port map (clk => clk, rst => rst, start => start, launch => launch);
 
       clk <= not clk after 5 ns;
 
       stim : process
           variable launch_count : integer := 0;
       begin
           rst <= '1'; start <= '0';
           wait until falling_edge(clk); wait until falling_edge(clk);
           rst <= '0';
           start <= '1';                                   -- set start and HOLD it high
           for c in 0 to 7 loop
               wait until rising_edge(clk); wait for 1 ns;
               if launch = '1' then launch_count := launch_count + 1; end if;
           end loop;
           start <= '0'; wait until rising_edge(clk); wait for 1 ns;
           -- One held-high start must launch EXACTLY ONE transaction.
           assert launch_count = 1
               report "level treated as pulse: launch fired more than once" severity error;
           report "start_bug self-check complete" severity note;
           wait;
       end process;
   end architecture;

Across all three languages the lesson is one sentence: an event is an edge, not a level — to fire logic once per occurrence, edge-detect the signal into a single-cycle strobe (sig & ~sig_d) and drive the action from the strobe, never from the held-high level.

5. Verification Strategy

A pulse generator's correctness is temporal — it is a claim about how many cycles a signal is high and how many times it fires — so its testbench is clocked and its self-check counts cycles and pulses. The single invariant that captures a correct strobe is:

A signal held high for many cycles produces exactly ONE rising strobe, high for exactly ONE cycle; the generator does not fire again until the signal drops and rises again.

Everything below is that invariant, made checkable, and it maps directly onto the clocked testbenches in §4.

  • Hold the input high, assert exactly one strobe. The core self-check: drive sig_in high and keep it high for several cycles, count rise_pulse assertions, and assert the count is exactly 1 and the pulse was high for exactly one cycle. This is the property the whole page exists for — a level yields one strobe — and it is what the §4a testbenches do (SV assert (rise_count == 1), Verilog if (rise_count !== 1) $display("FAIL"), VHDL assert rise_count = 1 ... severity error). A generator that fires every cycle (the §7 bug) fails this on the second cycle.
  • Test both polarities. Rising and falling are different gates and must be tested separately: hold high (expect one rise_pulse), then drop and hold low (expect one fall_pulse). A design that got the polarity backwards passes a rising-only test and fails here — which is exactly why §4a exercises both.
  • The one-shot re-arms only after deassert. Prove the re-arm: after the first strobe, keep the level high and assert no further strobe; then drop the level, raise it again, and assert a second strobe now appears. This distinguishes a true one-shot from a generator that leaks extra pulses, and it is the property the launcher of §4c relies on.
  • The stretcher holds exactly N. For the stretcher, fire a single-cycle pulse_in and assert stretch_out is high for exactly N cycles and then low — a direct measurement of the stretch width against the parameter. Re-run for several N (a stretcher is parameter-generic and must be verified generic, not assumed generic), and add a re-trigger corner: fire a second pulse_in mid-stretch and assert the output extends to a full N cycles from the new pulse, not the old one.
  • The async corner — false/double pulses. The dangerous case is an input that was not synchronized. Conceptually, if sig_in can change at the sampling edge, one real edge can produce a false pulse or a double pulse. The verification response is architectural, not a single assertion: drive the edge detector only with a synchronous stimulus and, at the system level, require that any async input passes through a two-flop synchronizer first (Chapter 11). The invariant to state is: "the edge detector's input is always synchronous to clk" — a design constraint the testbench honours and a review must confirm.
  • Expected waveform. A correct rising strobe is a single narrow spike one cycle wide, appearing on the cycle sig_in transitions 0→1, then flat-low even while sig_in stays high — never a level that tracks the input. A correct stretcher shows a one-cycle input spike widening into an N-cycle output plateau. The visual signature of the §7 bug is the opposite: a launch that tracks the held-high level, a plateau where there should be a spike.

6. Common Mistakes

Treating a level as a pulse. The signature failure of this pattern. A held-high go/start/enable is still true every cycle, so logic that reads it directly ("if the level is high, do the thing") does the thing every cycle the level is high — a start that launches hundreds of back-to-back transactions from one firmware write, a counter that increments continuously instead of by one. The fix is to edge-detect the level into a single-cycle strobe (sig & ~sig_d) and drive the action from the strobe. The tell is a burst of repeats where you expected one action. (Full post-mortem in §7; buggy-vs-fixed RTL for all three HDLs in §4c.)

Edge-detecting an unsynchronized async input. An edge detector assumes its input is synchronous — stable around each clock edge — so that "value this cycle vs last cycle" is meaningful. Feed it a raw asynchronous signal (another clock domain, a bare button) and two failures appear: the delay flop can go metastable, and the two sampled versions (sig_in and sig_d) can disagree about a single transition, producing a false pulse or a double pulse from one real edge. Never edge-detect a raw cross-domain signal; synchronize it through a two-flop synchronizer first, then edge-detect the clean, synchronous output. This is the single most common way a "working" pulse generator produces intermittent, unreproducible false triggers on hardware.

Wrong edge polarity. The gates look alike and are easy to swap: sig & ~sig_d fires on the rising edge, ~sig & sig_d on the falling edge, sig ^ sig_d on either. Wire the falling-edge gate where you meant rising and the strobe fires one cycle after the signal drops instead of when it rises — the logic still "works" for a steady input but triggers on the wrong transition. Always name the strobe for its edge (rise_pulse, fall_pulse) and verify the polarity with a directed hold-high / hold-low test, not just a single toggle.

A stretcher that re-triggers wrong (or not at all). If a new pulse_in arrives mid-stretch, the intended behaviour is usually to reload the counter so the new event gets a full N cycles. A stretcher that instead ignores the new pulse (or, worse, only extends by the remaining count) silently drops or shortens events — the output looks plausible but a second event within N cycles is lost or truncated. Decide the re-trigger policy deliberately (reload vs ignore), implement it explicitly (the §4b design reloads on every pulse_in), and test it with a mid-stretch second pulse.

Missing reset on the delay flop. If sig_d powers up as X (no reset), the first "comparison" is sig_in & ~X — undefined, and in a gate-level or FPGA run it can produce a spurious first pulse. Always reset the delay register to a known value (0) so the first real edge is the first strobe, and power-up cannot fake an event.

7. DebugLab

The transaction engine that fired hundreds of times — a level treated as a pulse

The engineering lesson: an event is an edge, not a level — control logic that must act once per occurrence has to be driven by a single-cycle strobe, never by a signal that is held high across many cycles. A held-high level is "the condition is still true," and reading it directly makes the action repeat every cycle the condition holds — the flood in the DebugLab. The tell is a burst of repeated actions where you expected one, and a bug that hides behind a one-cycle test poke but floods under real held-high firmware: if your stimulus pulsed the signal but production holds it, you never tested the actual failure. Two habits make it impossible, and they are identical across SystemVerilog, Verilog, and VHDL: edge-detect any held-high control level into a single-cycle strobe (sig & ~sig_d) and drive the action from the strobe, and verify by holding the input high for many cycles and asserting the action fires exactly once — and if the level is asynchronous, synchronize it before you edge-detect it.

8. Interview Q&A

9. Exercises

Design and reasoning exercises — no syntax drills. Each rehearses the "an event is an edge, not a level" habit.

Exercise 1 — Trace the strobe against a held-high level

A rising-edge detector (sig_d <= sig_in; rise_pulse = sig_in & ~sig_d) starts with sig_d = 0. The input sig_in is 0 at cycle 0, then goes 1 at cycle 1 and stays high through cycle 6, then drops to 0 at cycle 7. Tabulate sig_in, sig_d, and rise_pulse for cycles 0–8. State exactly which cycle(s) rise_pulse is high, and explain in one line why it is high for only one cycle even though sig_in is high for six.

Exercise 2 — Pick the gate for the edge

For each requirement, state which gate on sig_in/sig_d you'd use — sig & ~sig_d, ~sig & sig_d, or sig ^ sig_d — and why in one line: (a) start a DMA when a go bit is set; (b) latch a result when a busy flag clears (goes 1→0); (c) toggle an LED on any change of a debounced switch; (d) count the number of times a status line has been asserted. (Hint: name the transition each requirement cares about.)

Exercise 3 — Why did the strobe fire twice?

An engineer edge-detects a signal that comes directly from a slower clock domain (no synchronizer). On the bench it mostly works, but occasionally a single transition of the source produces two rise_pulse cycles, launching the downstream action twice. Explain precisely how one real edge can become two pulses through the two-flop path (sig_in and sig_d), name the property of the input that was violated, and state the one architectural change that fixes it. (Hint: what is the detector assuming about when the input can change?)

Exercise 4 — Size and re-trigger the stretcher

You have a single-cycle sample_ready strobe on a 100 MHz clock, and a downstream block on a 25 MHz clock that must see the signal held high long enough to sample it reliably. (a) Roughly how many fast-clock cycles must the stretcher hold the output high so the slow block is guaranteed at least one of its own edges within the window — and why "one" is not enough? (b) If two sample_ready strobes can arrive 3 fast cycles apart while N = 8, what does your re-trigger policy do to the second event, and what is the consequence of choosing "ignore during stretch" instead of "reload"?

Continue in RTL Design Patterns (this-batch siblings go live together; forward links unlock as they ship):

  • Ring & Johnson Counters — Chapter 3.4; shift-register counters whose one-hot output is a natural, glitch-free source of periodic single-cycle strobes.
  • Clock Dividers & Timers — Chapter 3.5; where periodic strobes come from — a timer's terminal-count is a single-cycle tick, generated exactly like the strobes on this page.
  • valid/ready Handshake — Chapter 9.1; valid and ready are levels, and a transfer is the single-cycle event valid & ready — the level-vs-event distinction of this page, formalized into a protocol.
  • Pulse Handshake Synchronizer — Chapter 11; how to move a single-cycle pulse safely across clock domains when a two-flop synchronizer alone would miss it.
  • Two-Flop Synchronizer — Chapter 11.2; the mandatory front-end for any async signal before you edge-detect it — the fix for this page's async hazard.
  • FSM Fundamentals — Chapter 4.1; the control logic that consumes these strobes as single-cycle "go" events driving state transitions.

Backward / method:

  • The Register Pattern (D-FF) — Chapter 2.1; the one-cycle delay register (sig_d <= sig_in) that this whole pattern is built on — an edge detector is a register plus one gate.
  • Synchronous Reset — Chapter 2.3; why the delay flop needs a defined power-up value so the first real edge is the first strobe.
  • Up/Down Counters — Chapter 3.1; the down-counter inside the pulse stretcher that re-widens a 1-cycle tick into an N-cycle level.
  • Mod-N Counters — Chapter 3.2; the terminal-count strobe a mod-N counter emits is exactly the single-cycle tick this page generates.
  • Gray Counters — Chapter 3.3; single-bit-change encoding, the counterpart discipline for pointers that must cross clock domains cleanly.
  • Multiplexers — Chapter 1.1; the selection primitive a strobe often gates (load-enable = a 2:1 mux driven by a single-cycle strobe).
  • The RTL Design Mindset — Chapter 0.2; the Interface → State → Datapath → Control → Verification lenses this page applies to the level-vs-event boundary.
  • What Is an RTL Design Pattern? — Chapter 0.1; why the strobe generator earns the name "pattern" — a recurring need, a one-gate form, and a signature failure.

Verilog v1 prerequisites (the grammar this pattern relies on):

11. Summary

  • A strobe is this cycle's value AND-NOT last cycle's. Register the signal (sig_d <= sig_in) and compute pulse = sig_in & ~sig_d for a rising-edge strobe — high for exactly one cycle, on the single cycle where "now" and "then" disagree. Swap the gate for the other edges: ~sig_in & sig_d (falling), sig_in ^ sig_d (any). The whole thing is the register of Chapter 2.1 plus one gate.
  • It turns levels into events. Control logic runs on events ("start now," "sample ready"), but signals arrive as levels held high for many cycles. The edge detector converts a held-high level into a single one-shot pulse that fires once and re-arms only when the level drops — which is why a correctly-built start launches one transaction instead of a flood.
  • The stretcher is the inverse. When a slower consumer needs a signal held high, a down-counter loaded to N-1 on the strobe re-widens a 1-cycle pulse into an exactly-N-cycle level. N is a parameter, and a second pulse mid-stretch must re-trigger (reload) so no event is shortened.
  • Never edge-detect a raw async signal. The atom assumes a synchronous input; feed it an unsynchronized cross-domain signal and it produces false or double pulses (metastability plus a disagreement between sig_in and sig_d). Synchronize through a two-flop synchronizer first, then edge-detect — the async hazard fixed in Chapter 11.
  • Verify temporally, in every HDL. A clocked, self-checking testbench holds the input high for many cycles and asserts exactly one strobe, one cycle wide; tests both polarities; proves the one-shot re-arms only on deassert; and measures the stretcher at exactly N cycles. The signature bug — a level treated as a pulse (§7) — hides behind a one-cycle test poke but floods under real held-high firmware; the self-checking testbenches, shown here in SystemVerilog, Verilog, and VHDL, catch it by holding the input high.