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
-- 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
-- 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
5. Simulation interpretation — vectors applied on clock edges
Synchronous stimulus: a new vector each rising edge, valid pulsed
8 cycles6. 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.
-- 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.