VHDL · Chapter 14.8 · Testbench Development
Randomized Stimulus in VHDL
Directed vectors test the cases you thought of, while randomized stimulus finds the ones you did not. VHDL generates randomness with the uniform procedure from the standard real-math library, which returns a real value in the zero to one range that you scale to an integer range or vector width, producing varied inputs across a long run. Two disciplines make it work. Seeds make failures reproducible, since uniform is pseudo-random from seed state and recording the seed lets you replay an exact failing run. Self-checking is required because random inputs have unknown answers, so you need a reference model to compute the expected result. You can also bias the distribution, called constrained random, to hit boundaries and rare cases more often. This lesson covers generating, scaling, seeding, and self-checking random stimulus, and where it complements directed tests.
Foundation14 min readVHDLTestbenchRandommath_realConstrained RandomVerification
1. Engineering intuition — let the machine try things you wouldn't
Hand-written vectors reflect your assumptions, and bugs love the cases your assumptions miss. Random stimulus breaks out of that: throw thousands of varied inputs at the DUT and let volume and variety find the corner you never imagined — an unusual value combination, a rare interleaving. The catch is twofold. First, you cannot eyeball random results, so a reference model must say what each random input should produce — random stimulus only pays off with self-checking. Second, randomness must be reproducible: a failure found at 2 a.m. is useless if you cannot recreate it, so you record the seed and replay. With those in place, random testing is a tireless explorer of the input space.
2. Formal explanation — uniform, scaling, and seeds
use ieee.math_real.all; -- uniform(seed1, seed2, x): x is a real in [0,1)
rand_proc : process
variable s1 : positive := 12345; -- SEED state (record these to reproduce a run)
variable s2 : positive := 67890;
variable r : real;
variable a : integer;
variable v : std_logic_vector(7 downto 0);
begin
-- generate a random real, then SCALE to the range/width you need:
uniform(s1, s2, r); a := integer(floor(r * 256.0)); -- 0..255
v := std_logic_vector(to_unsigned(a, 8));
uniform(s1, s2, r); -- scale to an arbitrary range [lo, hi]:
-- a := lo + integer(floor(r * real(hi - lo + 1)));
dut_in <= v;
wait until rising_edge(clk);
end process;
-- Record (s1, s2) initial values in the log → REPLAY an exact failing sequence.ieee.math_real's uniform(seed1, seed2, x) advances the seed state and returns a real x in [0,1);
scale it (floor(x * N), or lo + floor(x*(hi-lo+1))) to an integer range or vector. The seeds fully
determine the sequence, so logging the initial seeds makes any run reproducible.
3. Production usage — random + self-check, and constrained random
-- Random stimulus MUST be paired with a reference model (14.9) — the inputs are unknown ahead of time.
rand_test : process
variable s1 : positive := SEED1; variable s2 : positive := SEED2; -- logged for replay
variable r : real; variable a, b : integer;
begin
for n in 0 to 9999 loop -- thousands of random cases
uniform(s1,s2,r); a := integer(floor(r*256.0));
uniform(s1,s2,r); b := integer(floor(r*256.0));
dut_a <= std_logic_vector(to_unsigned(a,8));
dut_b <= std_logic_vector(to_unsigned(b,8));
wait until rising_edge(clk);
assert dut_sum = reference_model(a, b) -- expected from the golden model
report "random fail: a=" & integer'image(a) & " b=" & integer'image(b) severity error;
end loop;
wait;
end process;
-- CONSTRAINED RANDOM: bias toward interesting cases (weight boundaries higher).
-- uniform(s1,s2,r);
-- if r < 0.2 then a := 0; -- 20% hit the min
-- elsif r < 0.4 then a := 255; -- 20% hit the max
-- else a := lo + integer(floor(...)); end if; -- 60% spread across the rangeWhat hardware does this become? None — random stimulus is simulation-only (math_real, uniform, and
the loop are not synthesizable). Its value is coverage of the unknown: thousands of self-checked cases per run
hit combinations no directed test enumerates. Constrained random steers that exploration — weighting the
distribution so boundaries and specific patterns occur more often than pure uniform chance would give — which
matters because uniform sampling rarely lands exactly on min/max. VHDL has no built-in constraint solver or
functional-coverage language, so weighting and tracking are coded by hand on top of uniform.
4. Structural interpretation — seeded RNG into DUT and model
5. Why this is structural, not timing
Randomized stimulus is a verification strategy — seeded generation, scaling, and pairing with a model — so the structure above (RNG → DUT + model → compare) is the right picture, not a waveform. Each random case still produces ordinary stimulus/response waveforms, but the lesson's substance is the architecture: how randomness is generated reproducibly and checked automatically. Reproducibility via seeds and correctness via a reference model are properties of the test's construction, not of any single signal trace.
6. Debugging example — the unreproducible random failure
Expected: a random-test failure can be reproduced and debugged. Observed: a nightly run reports a failure, but re-running never hits it again — the bug is "gone," undebuggable; or every random run behaves identically. Root cause: the seed was not controlled/recorded — either re-seeded unpredictably so the failing sequence cannot be recreated, or hard-coded identically so the test never varies. (A second failure mode: random inputs were applied with no reference model, so nothing actually checked them.) Fix: initialize from a recorded seed, log it every run, and provide a way to replay a specific seed to reproduce a failure exactly; and always pair random stimulus with a reference model so results are checked. Engineering takeaway: random testing is only useful if it is reproducible and self-checking — record and replay the seed, and compute expected results with a model.
-- BUG: seed not recorded → a failing random run can't be reproduced.
-- variable s1, s2 : positive; -- uninitialized / unlogged
-- FIX: known, logged seeds (overridable) → replay an exact run.
variable s1 : positive := SEED1; variable s2 : positive := SEED2;
report "seeds: " & integer'image(SEED1) & "," & integer'image(SEED2) severity note;7. Common mistakes & what to watch for
- Unrecorded / uncontrolled seeds. Log the seeds and allow replay; an unreproducible failure cannot be debugged.
- Random without a reference model. Random inputs need a model to compute expected results; otherwise nothing is checked.
- Pure uniform missing boundaries. Uniform sampling rarely hits exact min/max; use constrained random to weight corners, and keep directed tests for known ones.
- Scaling bugs. Map [0,1) to your range carefully (
floor(r*N), inclusive bounds); off-by-one scaling skews or excludes values. - Assuming SV-style constraints/coverage. VHDL has none natively; weighting and coverage are manual on top of
math_real.
8. Engineering insight & continuity
Randomized stimulus explores the input space directed tests miss: math_real's uniform scaled to your
ranges, made reproducible by recorded seeds and meaningful only with a self-checking reference model,
optionally biased (constrained random) toward interesting cases. It complements directed/vector tests — random
for breadth, directed for known corners. Both random and self-checking depend on the same crucial component: a
trusted model of correct behavior. That is the focus of the next lesson, Behavioral and Reference Models — how
to build the golden model that every checked test compares against.