Skip to content

Self-checking testbenches

A self-checking testbench converts the specification into code and raises a failure whenever actual behavior differs.

Exhaustive testing

When the input space is small, test every combination.

for a_value in 0 to 15 loop
  for b_value in 0 to 15 loop
    for cin_value in 0 to 1 loop
      a_s <= to_unsigned(a_value, a_s'length);
      b_s <= to_unsigned(b_value, b_s'length);
      if cin_value = 0 then
        cin_s <= '0';
      else
        cin_s <= '1';
      end if;
      wait for 1 ns;

      expected := a_value + b_value + cin_value;
      actual   := to_integer(unsigned'(carry_s & sum_s));

      assert actual = expected
        report "adder mismatch"
        severity failure;
    end loop;
  end loop;
end loop;

A 4-bit adder needs only 16 × 16 × 2 = 512 cases, so exhaustive testing is ideal.

Reference functions

Use a function that expresses the mathematical specification:

function model_saturating_add(
  a_value : natural;
  b_value : natural;
  maximum : natural
) return natural is
begin
  if a_value + b_value > maximum then
    return maximum;
  else
    return a_value + b_value;
  end if;
end function;

Keep the reference model simple, readable, and independent of the RTL structure.

Cycle-accurate scoreboard

For a pipelined design, store expected results until their due cycle.

Concept:

cycle N:   drive input A; compute expected A; enqueue it
cycle N+1: drive input B; compute expected B; enqueue it
cycle N+L: dequeue expected A; compare DUT output A

For fixed latency, an array shift register is enough:

expected_pipe(0) := model(input_s);
for stage in 1 to LATENCY loop
  expected_pipe(stage) := expected_pipe(stage - 1);
end loop;

if output_valid_s = '1' then
  assert output_s = expected_pipe(LATENCY)
    severity failure;
end if;

Order variable updates carefully; a protected queue or record-based transaction is clearer for variable-latency protocols.

Transaction records

Group related values:

type transaction_t is record
  a        : natural;
  b        : natural;
  expected : natural;
end record;

Driver, monitor, and scoreboard can pass a transaction rather than loose values.

Boundary-first strategy

Always include:

  • Zero.
  • One.
  • Minimum and maximum representable values.
  • Maximum−1 and sign boundaries.
  • Equal operands.
  • Carry/borrow/overflow transitions.
  • Reset during activity.
  • Enable changes.
  • Back-to-back transactions.
  • Long idle gaps.

Most arithmetic and counter bugs live at boundaries.

Invariants

An invariant must always be true, not just at one test point:

invariant_check : process(clk_s)
begin
  if rising_edge(clk_s) then
    assert not (read_enable_s = '1' and empty_s = '1')
      report "Read attempted while FIFO empty"
      severity failure;
  end if;
end process;

Some invariants describe the environment rather than the DUT. Label them as assumptions so a failure has the right meaning.

Reproducible pseudo-random tests

Random tests must be repeatable:

  1. Use an explicit seed.
  2. Print the seed at the beginning.
  3. Print the failing transaction index.
  4. Preserve a failing seed as a directed regression test.

Do not use randomness as a substitute for boundary analysis or coverage.

Coverage without a framework

Simple counters already add value:

if saw_overflow then
  overflow_cases := overflow_cases + 1;
end if;

-- At test end
assert overflow_cases > 0
  report "Coverage hole: overflow never exercised"
  severity failure;

For an FSM, count state visits and transitions. For a protocol, count each command, response, error, and backpressure case.

Expected-failure discipline

A test of error handling should check the error response, not intentionally end with a failure assertion. Reserve simulator failure for a broken requirement.

Runnable example in this library

The folder examples/adder contains:

  • A generic unsigned adder.
  • An exhaustive test of all 512 four-bit combinations.
  • A file-driven test.
  • A PowerShell runner for XSim.

Run:

cd C:\Users\yanis\Documents\Bibliotheque-VHDL-Basys3\examples\adder
powershell -ExecutionPolicy Bypass -File .\sim\run-tests.ps1