Fonctions, tâches et boucles
Factoriser un calcul, écrire des boucles synthétisables et séparer le RTL des outils de test.
Une fonction calcule une valeur
Une fonction Verilog peut regrouper un calcul combinatoire utilisé plusieurs fois. Elle s'exécute sans délai, ne contient pas de contrôle temporel et renvoie une seule valeur.
module popcount_pair #(
parameter WIDTH = 8
) (
input wire [WIDTH-1:0] i_a,
input wire [WIDTH-1:0] i_b,
output wire [31:0] o_count_a,
output wire [31:0] o_count_b
);
function [31:0] count_ones;
input [WIDTH-1:0] value;
integer index;
begin
count_ones = 0;
for (index = 0; index < WIDTH; index = index + 1)
count_ones = count_ones + value[index];
end
endfunction
assign o_count_a = count_ones(i_a);
assign o_count_b = count_ones(i_b);
endmoduleLa boucle possède une borne connue pendant l'élaboration. Le synthétiseur la déroule et crée les additionneurs nécessaires. Le calcul n'attend pas WIDTH cycles.
La taille du résultat compte toujours
Le nombre maximal de bits à 1 vaut WIDTH. La sortie de 32 bits simplifie ici le paramétrage, mais elle peut être trop large pour un bloc réel. Une interface plus soignée utiliserait une largeur dérivée et vérifierait les valeurs autorisées de WIDTH.
Une fonction ne corrige pas automatiquement les règles de taille et de signe. Il faut toujours dimensionner ses entrées, sa valeur de retour et ses calculs intermédiaires.
Une tâche peut avoir plusieurs sorties
Une tâche peut manipuler plusieurs arguments input, output ou inout. En RTL synthétisable, elle doit rester purement matérielle : pas de #, pas de @, pas de wait et des boucles bornées.
module min_max_pair (
input wire [7:0] i_a,
input wire [7:0] i_b,
output reg [7:0] o_min,
output reg [7:0] o_max
);
task order_pair;
input [7:0] a;
input [7:0] b;
output [7:0] low;
output [7:0] high;
begin
if (a <= b) begin
low = a;
Ce style est synthétisable avec les outils courants, mais une fonction ou un petit module est parfois plus facile à relire et à réutiliser. Il faut suivre les règles du projet et vérifier le résultat de synthèse.
Les tâches sont très utiles dans un testbench
Dans un testbench, une tâche peut attendre des fronts d'horloge, appliquer des entrées et vérifier une sortie. Ces contrôles temporels sont faits pour la simulation, pas pour le circuit.
task check_sum;
input [7:0] a;
input [7:0] b;
input [8:0] expected;
begin
i_a = a;
i_b = b;
#1;
if (o_sum !== expected)
$display("ERROR: %0d + %0d", a, b);
end
endtaskLa présence de #1 et de $display rend cette tâche non synthétisable. C'est normal puisqu'elle appartient au banc de test.
for, repeat, while et forever
En RTL, une boucle for à bornes constantes décrit généralement une répétition finie de matériel. Une boucle dont la fin dépend d'une donnée peut être impossible à dérouler ou produire une structure imprévisible.
repeat, while et forever sont surtout pratiques en simulation. Certains usages bornés sont synthétisables, mais ils sont moins évidents pour le lecteur et pour les outils. Pour une opération qui doit réellement prendre plusieurs cycles, il faut un compteur et une machine à états.
Une boucle ne crée pas automatiquement un traitement séquentiel
Ce point est essentiel :
- boucle dans un bloc combinatoire, borne fixe : plusieurs opérations matérielles en parallèle ;
- boucle pilotée par un compteur dans un bloc cadencé : une opération répétée sur plusieurs cycles ;
- boucle temporelle avec
@ou#: comportement de simulation.
Le code peut se ressembler, mais l'architecture obtenue n'est pas la même.
À retenir
- Une fonction renvoie une valeur et ne doit pas consommer de temps de simulation.
- Une tâche peut avoir plusieurs sorties.
- Les contrôles temporels restent dans le testbench.
- Une boucle à borne constante est généralement déroulée en matériel.
- Une opération sur plusieurs cycles demande un état et un compteur explicites.
📝 Tester mes connaissances - Quiz du chapitre