Aller au contenu

Fondamentaux des bancs de test

Un banc de test est une entité sans port externe. Il instancie le design sous test (DUT), pilote ses entrées, observe ses sorties et décide automatiquement de la réussite.

Structure minimale

library ieee;
use ieee.std_logic_1164.all;
use std.env.finish;

entity tb_example is
end entity;

architecture sim of tb_example is
  signal input_s  : std_logic := '0';
  signal output_s : std_logic;
begin
  dut : entity work.example(rtl)
    port map (
      input_i  => input_s,
      output_o => output_s
    );

  stimulus : process
  begin
    input_s <= '0';
    wait for 1 ns;
    assert output_s = '0' severity failure;

    input_s <= '1';
    wait for 1 ns;
    assert output_s = '1' severity failure;

    report "PASS";
    finish;
  end process;
end architecture;

Génération d'horloge

constant CLOCK_PERIOD : time := 10 ns;
signal clk_s          : std_logic := '0';

clk_s <= not clk_s after CLOCK_PERIOD / 2;

Ce code, réservé à la simulation, produit les 100 MHz de la Basys 3.

Une variante arrêtable :

clock_process : process
begin
  while simulation_running loop
    clk_s <= '0';
    wait for CLOCK_PERIOD / 2;
    clk_s <= '1';
    wait for CLOCK_PERIOD / 2;
  end loop;
  wait;
end process;

Reset

Pour un reset synchrone actif à un :

reset_s <= '1';
wait until rising_edge(clk_s);
wait until rising_edge(clk_s);
reset_s <= '0';
wait until rising_edge(clk_s);

Modifiez les entrées loin du front actif pour éviter des courses artificielles :

wait until falling_edge(clk_s);
enable_s <= '1';

L'entrée est alors stable au prochain front montant.

Vérifier une sortie enregistrée

Après un front, la mise à jour apparaît dans un cycle delta :

wait until rising_edge(clk_s);
wait for 0 ns;
assert count_s = expected;

On peut aussi vérifier juste avant le front suivant. L'essentiel est d'utiliser partout la même convention entre mise à jour du modèle et échantillonnage du DUT.

Assertions utiles

assert actual = expected
  report "count mismatch: expected=" & integer'image(expected) &
         " actual=" & integer'image(actual)
  severity failure;

La condition décrit le succès. Le message n'est affiché que lorsqu'elle est fausse.

Ajoutez le numéro de transaction, les entrées, les valeurs attendue/observée, l'état et le cycle.

Procédure de comparaison

procedure check_equal(
  constant actual   : in natural;
  constant expected : in natural;
  constant message  : in string
) is
begin
  assert actual = expected
    report message & ": expected=" & integer'image(expected) &
           ", actual=" & integer'image(actual)
    severity failure;
end procedure;

Gardien temporel

Tout test attendant une condition doit posséder un timeout indépendant :

watchdog : process
begin
  wait for 1 ms;
  assert false
    report "TIMEOUT: test did not finish within 1 ms"
    severity failure;
end process;

Sur réussite, finish termine avant le gardien.

Procédure pilotant des signaux

procedure apply_and_check(
  signal a_s          : out std_logic;
  signal b_s          : out std_logic;
  signal y_s          : in  std_logic;
  constant a_value    : in  std_logic;
  constant b_value    : in  std_logic;
  constant expected_y : in  std_logic
) is
begin
  a_s <= a_value;
  b_s <= b_value;
  wait for 1 ns;
  assert y_s = expected_y severity failure;
end procedure;

Un paramètre doit être déclaré signal lorsque la procédure planifie son affectation.

Initialisation

Initialisez explicitement les signaux pilotés par le banc. Dans le DUT, utilisez le reset pour établir l'état opérationnel. Une valeur initiale de déclaration peut être synthétisée sur beaucoup de FPGA, mais ne remplace pas un contrat de démarrage lorsqu'il est nécessaire.

Chronogrammes

Ouvrez-les après un échec pour déterminer :

  • le pilote a-t-il appliqué la bonne transaction ?
  • à quel cycle apparaît la première divergence ?
  • reset et validation ont-ils la bonne polarité ?
  • le résultat a-t-il été lu trop tôt ?
  • une inconnue se propage-t-elle ?

Ne faites pas de l'inspection manuelle le verdict de chaque régression.

Liste de contrôle

  • Aucun port externe.
  • DUT instancié directement.
  • Horloge et reset conformes à la spécification.
  • Entrées modifiées à une phase volontaire.
  • Valeurs attendues vérifiées automatiquement.
  • Messages d'échec suffisamment détaillés.
  • Timeout présent.
  • Succès terminé par finish.