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