Aller au contenu

Bancs de test auto-vérifiants

Un banc auto-vérifiant transforme la spécification en code et provoque un échec dès que le comportement réel diffère du comportement attendu.

Test exhaustif

Lorsque l'espace d'entrée est petit, essayez toutes les combinaisons.

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;

Un additionneur 4 bits ne représente que 16 × 16 × 2 = 512 cas : l'exhaustivité est idéale.

Fonction de référence

Exprimez la règle mathématique, indépendamment de la structure du RTL :

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;

Le modèle doit rester simple, lisible et distinct de l'implémentation.

Scoreboard tenant compte des cycles

Pour un pipeline, conservez les résultats attendus jusqu'à leur cycle de sortie :

cycle N   : appliquer A, calculer A attendu, l'empiler
cycle N+1 : appliquer B, calculer B attendu, l'empiler
cycle N+L : dépiler A attendu et comparer la sortie A

À latence fixe, un tableau décalé suffit. Pour une latence variable, une file transactionnelle avec identifiants est préférable.

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;

Transactions

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

Pilote, moniteur et scoreboard peuvent échanger une transaction cohérente plutôt que plusieurs valeurs indépendantes.

Tester d'abord les limites

Incluez toujours :

  • zéro et un ;
  • minimum et maximum représentables ;
  • maximum−1 et limites de signe ;
  • opérandes égaux ;
  • transitions de retenue, emprunt et overflow ;
  • reset en activité ;
  • changements de validation ;
  • transactions successives ;
  • longues périodes d'inactivité.

Les erreurs arithmétiques et de compteur se trouvent souvent aux frontières.

Invariants

Un invariant doit être vrai en permanence :

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;

Certaines propriétés sont des hypothèses sur l'environnement plutôt que des garanties du DUT. Nommez-les clairement.

Tests pseudo-aléatoires reproductibles

  1. Employer une graine explicite.
  2. L'afficher au début.
  3. Afficher l'indice de la transaction défaillante.
  4. Transformer toute graine révélant un bogue en test dirigé permanent.

L'aléatoire ne remplace ni les limites ni la couverture.

Couverture simple

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

assert overflow_cases > 0
  report "Coverage hole: overflow never exercised"
  severity failure;

Pour une FSM, comptez états et transitions. Pour un protocole, comptez commandes, réponses, erreurs et backpressure.

Exemple exécutable

Le dossier examples/adder contient un additionneur générique, un test exhaustif de 512 cas, un test par fichier et un lanceur PowerShell XSim.

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