Skip to content

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

self_checking.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- 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

scoreboard.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- 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

stimulus into DUT and reference model, outputs compared, mismatches counted to pass/failactualexpectedstimulusdrives bothDUTactual outputreference modelexpected outputcomparator + countermismatch → errors++ →PASS/FAIL12
A self-checking testbench compares the DUT against a reference model automatically. The same stimulus drives both the DUT and a reference (golden) model; a comparator checks the DUT's actual output against the model's expected output at the latency-correct cycle, incrementing an error counter on any mismatch. For pipelined or streaming results a scoreboard queues expected values and matches them to actuals as they arrive. At the end the error count produces a PASS/FAIL verdict — no waveform inspection. This is an automatic-checking structure; the waveform below shows a flagged mismatch.

5. Simulation interpretation — actual vs expected, mismatch flagged

Self-check: DUT 'sum' compared to 'expected' each result cycle

8 cycles
Self-check: DUT 'sum' compared to 'expected' each result cyclesum=expected=0x008 → match (no error)sum=expected=0x008 → m…sum=expected=0x100 → matchsum=expected=0x100 → m…sum=0x0AA but expected=0x0FF → MISMATCH, error flagged + countedsum=0x0AA but expected…clkexpected--008--100--0FF----sum--008--100--0AA----error00000111t0t1t2t3t4t5t6t7
Each result cycle the testbench compares the DUT's sum to the expected value from the reference model. Two cycles match silently; the third mismatches and is flagged and counted immediately — no human needed. The accumulated error count drives the final PASS/FAIL verdict, which is what lets the same test run unattended over thousands of vectors.

6. 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.

check_when_valid.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- 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.