Skip to content

VHDL · Chapter 14.2 · Testbench Development

Stimulus Generation

With the harness in place, the first job is driving the DUT's inputs. A stimulus process does this by assigning the inputs, waiting for a fixed time or, far better, for a clock edge, and then applying the next value, building a controlled sequence of vectors. Driving synchronously on the rising edge keeps stimulus aligned with the DUT's own clock and avoids races at the input. As the tests grow, you package a recurring input pattern such as a full bus write into a reusable drive procedure, so stimulus reads like a sequence of transactions. And you deliberately include corner cases: minimum and maximum values, all-zeros and all-ones, and walking-ones patterns that expose wiring bugs. This lesson covers writing synchronous directed stimulus, sequencing and looping test cases, and packaging transactions into drive procedures.

Foundation14 min readVHDLTestbenchStimulusVerificationDrive ProcedureCorner Cases

1. Engineering intuition — apply, wait, apply

Driving a DUT is a rhythm: set the inputs, let time (or a clock edge) pass so the design can react, then set the next inputs. The wait between assignments is what turns a list of values into a sequence over time — without it, every assignment would happen at once. The best beat to march to is the clock edge: drive new inputs just after (or synchronized to) the rising edge, exactly as real synchronous logic feeding the DUT would. From there, stimulus is just choosing what sequence to apply — directed cases that hit the behavior you care about, the boundaries that break naive designs, and the patterns (walking-ones) that expose wiring bugs.

2. Formal explanation — synchronous directed stimulus

stimulus_process.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- Drive inputs SYNCHRONOUSLY: align each new vector to the clock edge.
stim : process
begin
  -- start from a known state (often after reset, see 14.3)
  a <= (others => '0'); b <= (others => '0'); valid <= '0';
  wait until rising_edge(clk);              -- step on the clock, not arbitrary time
 
  -- directed vectors, one per clock:
  a <= x"03"; b <= x"05"; valid <= '1';
  wait until rising_edge(clk);
  a <= x"FF"; b <= x"01"; valid <= '1';     -- corner: max + carry
  wait until rising_edge(clk);
  valid <= '0';
 
  -- loop over a set of test cases:
  for i in test_vectors'range loop
    a <= test_vectors(i).a; b <= test_vectors(i).b; valid <= '1';
    wait until rising_edge(clk);
  end loop;
 
  wait;                                      -- done driving
end process;

A stimulus process assigns the DUT inputs and uses wait until rising_edge(clk) to advance one clock per vector — synchronous stimulus. Directed values are applied in sequence; a loop over a vector set applies many cases compactly. The closing wait; parks the process when driving is finished.

3. Production usage — packaging a transaction as a drive procedure

drive_procedure.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- A reusable DRIVE PROCEDURE (signal-class params, 11.3/11.4) packages a transaction.
procedure drive_op (signal clk   : in  std_logic;
                    signal a, b   : out std_logic_vector(7 downto 0);
                    signal valid  : out std_logic;
                    constant va, vb : in std_logic_vector(7 downto 0)) is
begin
  wait until rising_edge(clk);
  a <= va; b <= vb; valid <= '1';
  wait until rising_edge(clk);
  valid <= '0';                                  -- one-cycle valid pulse
end procedure;
 
-- Stimulus now reads as a sequence of transactions:
stim : process begin
  drive_op(clk, a, b, valid, x"03", x"05");
  drive_op(clk, a, b, valid, x"FF", x"01");      -- corner case
  drive_op(clk, a, b, valid, x"00", x"00");      -- corner case
  wait;
end process;

What hardware does this become? None — stimulus is simulation control, not hardware. The drive procedure is a non-synthesizable testbench routine (it uses wait), and its value is readability and reuse: each call is one transaction, so a long test becomes a clear list of operations instead of repeated assign/wait boilerplate. Corner cases (0xFF+0x01 to force a carry, all-zeros, all-ones) are dropped in as ordinary calls. This is how real directed tests are structured — transactions, not raw signal wiggling.

4. Structural interpretation — the stimulus process driving inputs

stimulus process applying vectors to DUT inputs synchronized to the clock edgedrive @rising_edgetest vectors / casesdirected + corner casesstimulus processassign inputs; wait foredgeDUT inputsa, b, valid (one per clock)12
Stimulus generation drives the DUT inputs from a stimulus process synchronized to the clock. Each iteration assigns the inputs and waits for the rising edge, producing one vector per clock; a drive procedure packages a recurring pattern (assert inputs, pulse valid, deassert) into one reusable call so the test reads as a sequence of transactions. Directed cases and corner cases (min, max, walking-ones) are applied in order or looped from a vector set. This is simulation control, not hardware. This is a stimulus-flow structure; the waveform below shows inputs changing on clock edges.

5. Simulation interpretation — vectors applied on clock edges

Synchronous stimulus: a new vector each rising edge, valid pulsed

8 cycles
Synchronous stimulus: a new vector each rising edge, valid pulsedfirst vector a=03,b=05 driven on the rising edge, valid=1first vector a=03,b=05…corner case a=FF,b=01 (carry) on the next edgecorner case a=FF,b=01 …valid deasserted after the sequencevalid deasserted after…clka000303FFFF000000b0005050101000000valid01111000t0t1t2t3t4t5t6t7
Each vector is applied synchronized to the rising edge, so inputs change in step with the DUT's clock — no mid-cycle races. valid marks the cycles carrying a real transaction. Driving on the edge (wait until rising_edge(clk)) is what keeps directed stimulus deterministic and aligned to the design's timing.

6. Debugging example — asynchronous stimulus races the clock

Expected: the DUT samples each input vector cleanly. Observed: the DUT occasionally captures the wrong value, or results shift by a cycle depending on tiny timing changes. Root cause: stimulus was driven on arbitrary time delays (wait for 7 ns) instead of the clock edge, so an input sometimes changed right at the sampling edge — a race between stimulus and clock. Fix: drive stimulus synchronously with wait until rising_edge(clk) (optionally with a small skew after the edge), so inputs are stable well before the next sampling edge. Engineering takeaway: align stimulus to the clock, not to absolute time — driving on the edge removes input/clock races and makes directed tests deterministic.

drive_on_the_edge.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- BUG: time-based drive can change inputs right at the sampling edge → race.
-- a <= x"03"; wait for 7 ns; a <= x"FF"; wait for 7 ns;
-- FIX: synchronize to the clock edge.
wait until rising_edge(clk); a <= x"03";
wait until rising_edge(clk); a <= x"FF";

7. Common mistakes & what to watch for

  • Time-based instead of edge-based stimulus. Drive on rising_edge(clk) to avoid input/clock races and stay deterministic.
  • Changing inputs exactly at the sampling edge. Apply a small skew after the edge (or drive just after it) so inputs are stable when sampled.
  • No corner cases. Always include min, max, all-zeros/all-ones, and walking-ones; typical-value-only tests miss boundary bugs.
  • Repeated assign/wait boilerplate. Package recurring patterns into drive procedures so stimulus reads as transactions.
  • Driving before reset completes. Apply functional stimulus only after the reset sequence releases (next lesson).

8. Engineering insight & continuity

Stimulus generation drives the DUT's inputs as a controlled, clock-synchronized sequence: assign, wait for the edge, repeat — looping over vector sets and packaging transactions into reusable drive procedures, with deliberate corner cases. Edge-aligned driving is what keeps directed tests race-free and deterministic. Stimulus needs something to step against, though — a clock to drive on and a reset to start from. The next lesson supplies exactly that: Clock and Reset Generation — the free-running clock and the reset assert/release sequence every testbench is built on.