VHDL · Chapter 14.4 · Testbench Development
Self-Checking Testbenches
Driving stimulus is only half a test; the other half is deciding whether the answer is right, automatically. A self-checking testbench computes the expected result, from a reference model or a known-good table, and compares it to the design's actual output, declaring pass or fail with no human reading waveforms. Three patterns carry this: an inline reference model that produces the expected value, a scoreboard that queues expected results and matches them against actuals as they arrive, and an error counter with a final pass-or-fail report. The one timing detail is checking at the right moment, accounting for the design's latency. Self-checking is what turns a one-off glance at a waveform into a repeatable regression that can run unattended. This lesson covers the reference-model, scoreboard, and reporting patterns.
Foundation15 min readVHDLTestbenchSelf-CheckingScoreboardReference ModelVerification
1. Engineering intuition — let the testbench judge
If a person has to look at the waveform to decide whether a test passed, the test does not scale: you cannot eye a thousand cases, and you certainly cannot re-check them every night. A self-checking testbench moves that judgment into the harness. For each stimulus it works out what the answer should be — from a reference model or a known-good table — and compares it to what the DUT actually produced. A mismatch is flagged immediately and counted; at the end the testbench prints PASS or FAIL. Now the test is a machine that says yes or no, so it can run on every commit over thousands of vectors without anyone watching.
2. Formal explanation — expected vs actual, compared automatically
-- The TB computes EXPECTED (reference model) and COMPARES to the DUT's ACTUAL output.
signal errors : natural := 0;
check : process
variable expected : std_logic_vector(8 downto 0);
begin
wait until rising_edge(clk);
if valid_in = '1' then
expected := std_logic_vector(resize(unsigned(a),9) + resize(unsigned(b),9)); -- reference model
wait until rising_edge(clk); -- account for the DUT's 1-cycle LATENCY
assert sum = expected
report "MISMATCH: got " & to_hstring(sum) & " expected " & to_hstring(expected)
severity error;
if sum /= expected then errors <= errors + 1; end if; -- count failures
end if;
end process;
-- FINAL pass/fail verdict at end of test:
done : process begin
wait for 10 us;
if errors = 0 then report "TEST PASSED" severity note;
else report "TEST FAILED: " & integer'image(errors) & " errors" severity error;
end if;
std.env.stop;
end process;A self-checking testbench has three pieces: a reference model that computes the expected value, a compare
(here via assert) of expected against the DUT's actual output at the latency-correct cycle, and an error
count feeding a final pass/fail report. The comparison is automatic — no waveform inspection.
3. Production usage — a scoreboard for pipelined/out-of-order results
-- When results come back later (pipeline) or in streams, use a SCOREBOARD: push expected, check actual.
shared variable sb : scoreboard_t; -- protected type (13.6): a queue + error count
-- Producer side: when a transaction is issued, push its expected result.
push_proc : process begin
wait until rising_edge(clk);
if issue = '1' then
sb.push( reference_model(a, b) ); -- expected, queued in order
end if;
end process;
-- Consumer side: when the DUT produces a result, check it against the queue head.
check_proc : process begin
wait until rising_edge(clk);
if result_valid = '1' then
sb.check( dut_result ); -- compares to head; counts mismatches internally
end if;
end process;
-- At end: assert sb.errors = 0 → PASS/FAIL.What hardware does this become? None — checking infrastructure is simulation-only (it uses wait,
assert, protected types). The scoreboard decouples when an expected value is produced from when the DUT
returns its actual, which is essential for pipelines and streaming interfaces where results lag or overlap the
stimulus. The reference model is the trusted "golden" behavior; the scoreboard keeps the bookkeeping; the error
count yields the verdict. This producer/scoreboard/consumer shape is exactly how real verification environments
self-check.
4. Structural interpretation — DUT vs reference model into a comparator
5. Simulation interpretation — actual vs expected, mismatch flagged
Self-check: DUT 'sum' compared to 'expected' each result cycle
8 cycles6. Debugging example — checking at the wrong cycle (latency)
Expected: a correct DUT passes its self-check. Observed: every comparison fails even though the DUT is
right, or failures appear/disappear when the pipeline depth changes. Root cause: the testbench compared
expected to actual at the wrong cycle — it ignored the DUT's latency, checking the output one (or
several) cycles before the result was actually valid. Fix: align the check to when the DUT's output is
valid — wait the DUT's latency (or, better, key the check off a result_valid/handshake), and for variable
latency use a scoreboard that matches results to expected values as they arrive rather than at a fixed offset.
Engineering takeaway: self-checks must compare at the latency-correct moment — gate the comparison on the
DUT's valid signal or use a scoreboard, never assume the result is ready the same cycle as the stimulus.
-- BUG: comparing immediately, ignoring the DUT's pipeline latency → always mismatches.
-- expected := model(a,b); assert sum = expected; -- sum not valid yet
-- FIX: check when the result is actually valid (latency-aware).
if result_valid = '1' then assert sum = expected report "..." severity error; end if;7. Common mistakes & what to watch for
- Comparing at the wrong cycle. Account for DUT latency — gate the check on a valid/handshake signal or use a scoreboard.
- No reference model / golden data. You need an independent source of the expected value; comparing the DUT to itself proves nothing.
- No final verdict. Accumulate errors and print an explicit PASS/FAIL (and set a non-zero status) so regressions can be scored automatically.
- Eyeballing waveforms. That does not scale; move every check into the harness.
- Fixed-offset checks for variable latency. Pipelines/streams need a scoreboard (queue), not a constant cycle delay.
8. Engineering insight & continuity
A self-checking testbench computes the expected result and compares it to the DUT's actual output automatically — via an inline reference model or a scoreboard that queues expected values — counts mismatches, and ends with a PASS/FAIL verdict, all without reading waveforms. Checking at the latency-correct moment is the one detail to get right. This is what makes verification scale into nightly regressions. The comparison itself leans on VHDL's reporting mechanism, which the next lesson covers directly: assert and report Statements — the language constructs for checks, messages, and severity that self-checking is built from.