Mots-clés : Task
Différences entre les tâches et les fonctions
Comme les fonctions, les tâches peuvent être utilisées pour décrire des segments de code communs et peuvent être appelées à n'importe quelle position dans un module, ce qui rend le code plus intuitif et lisible. Les fonctions sont généralement utilisées pour diverses conversions et calculs dans la logique combinatoire, tandis que les tâches ressemblent davantage à des procédures. Elles peuvent non seulement accomplir la fonctionnalité des fonctions, mais aussi inclure une logique de contrôle temporel. Les différences entre les tâches et les fonctions sont résumées ci-dessous :
| Point de comparaison | Fonction | Tâche |
|---|---|---|
| Entrée | Une fonction doit avoir au moins une entrée, et la déclaration de port ne peut pas contenir le type inout | Une tâche peut ne pas avoir d'entrée ou avoir plusieurs entrées, et la déclaration de port peut être de type inout |
| Sortie | Une fonction n'a pas de sortie | Une tâche peut ne pas avoir de sortie ou avoir plusieurs sorties |
| Valeur de retour | Une fonction a au moins une valeur de retour | Une tâche n'a pas de valeur de retour |
| Temps de simulation | Une fonction commence toujours son exécution au temps zéro | Une tâche peut s'exécuter à des temps non nuls |
| Logique séquentielle | Une fonction ne peut contenir aucune logique de contrôle de temporisation | Une tâche ne peut pas contenir d'instructions always, mais peut inclure d'autres contrôles de temporisation, tels que des instructions de délai |
| Appel | Une fonction peut uniquement appeler des fonctions, pas des tâches | Une tâche peut appeler des fonctions et des tâches |
| Normes de codage | Une fonction ne peut pas apparaître comme instruction autonome ; elle peut uniquement être placée du côté droit d'une instruction d'affectation | Une tâche peut apparaître comme instruction autonome dans un bloc d'instructions |
Tâche
Déclaration de tâche
Une tâche est définie à n'importe quelle position dans un module et référencée à n'importe quelle position au sein du module ; sa portée est également limitée à ce module.
Lorsqu'un sous-programme dans un module remplit l'une des conditions suivantes, une tâche doit être utilisée à la place d'une fonction.
- 1) Le sous-programme contient une logique de contrôle temporel, telle que des délais, un contrôle d'événement, etc.
- 2) Il n'y a aucune variable d'entrée
- 3) Il n'y a aucune sortie ou le nombre de sorties est supérieur à 1
Le format de déclaration de tâche Verilog est le suivant :
task task_id ; port_declaration ; procedural_statement ; endtask
Dans une tâche, les mots-clés input, output et inout sont utilisés pour déclarer des ports. Les ports de type input et inout transmettent des variables de l'extérieur de la tâche vers l'intérieur, tandis que les ports de type output et inout renvoient les résultats vers l'extérieur lorsque l'exécution de la tâche est terminée.
Lors de la conception logique d'une tâche, les variables de port déclarées comme input peuvent être considérées comme de type wire, et les variables de port déclarées comme output peuvent être considérées comme de type reg. Cependant, il n'est pas nécessaire de déclarer à nouveau les ports de sortie comme reg.
Lors de l'affectation de valeurs aux signaux de sortie, n'utilisez pas le mot-clé assign. Pour éviter toute confusion temporelle, il est recommandé d'utiliser une affectation bloquante pour les signaux de sortie.
Par exemple, une tâche avec une fonction XOR avec délai est décrite comme suit :
Exemple
input [N-1:0] numa;
input [N-1:0] numb;
output [N-1:0] numco ;
//output reg [N-1:0] numco ; //Pas besoin de spécifier à nouveau le type reg, bien que le spécifier ne soit pas forcément incorrect non plus
#3 numco = numa ^ numb ;
//assign #3 numco = numa ^ numb ; //Ne pas utiliser assign, car la sortie est reg par défaut
endtask
Lors de la déclaration d'une tâche, vous pouvez également ajouter des parenthèses après le nom de la tâche pour y placer les déclarations de ports.
La conception ci-dessus peut être modifiée comme suit :
Exemple
input [N-1:0] numa,
input [N-1:0] numb,
output [N-1:0] numco ) ;
#3 numco = numa ^ numb ;
endtask
Appel de tâche
Une tâche peut apparaître comme une instruction autonome dans un bloc initial ou always, et le format d'appel est le suivant :
task_id(input1, input2, …,outpu1, output2, …);
Lors de l'appel d'une tâche, les ports doivent correspondre dans l'ordre.
Les signaux du module connectés aux ports d'entrée peuvent être de type wire ou de type reg. Les signaux du module connectés aux ports de sortie doivent être de type reg ; cela demande attention.
Appelez la tâche de fonction XOR ci-dessus pour terminer la mise en mémoire tampon du résultat XOR.
Exemple
#(paramètre N = 4)
(
input clk ,
input rstn ,
input [N-1:0] a ,
input [N-1:0] b ,
output [N-1:0] co );
reg [N-1:0] co_t ;
always @(*) begin //Appel de tâche
xor_oper_iner(a, b, co_t);
end
reg [N-1:0] co_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
co_r <= 'b0 ;
end
else begin
co_r <= co_t ; //Mise en mémoire tampon des données
end
end
assign co = co_r ;
/*------------ tâche -------*/
task xor_oper_iner;
input [N-1:0] numa;
input [N-1:0] numb;
output [N-1:0] numco ;
#3 numco = numa ^ numb ; //Affectation bloquante, facile à contrôler le timing
endtask
endmodule
Effectuez une simulation simple de la conception de la fonction XOR ci-dessus ; le banc de test est décrit comme suit.
Pour la partie stimulus, nous utilisons une tâche simple pour la décrire, ce qui rend le stimulus plus clair et plus concis.
En fait, le scénario d'application le plus courant des tâches se trouve dans les bancs de test pour la simulation. Les tâches ne sont pas non plus prises en charge pour la synthèse dans certains compilateurs.
Exemple
module test ;
reg clk, rstn ;
initial begin
rstn = 0 ;
#8 rstn = 1 ;
forever begin
clk = 0 ; # 5;
clk = 1 ; # 5;
end
end
reg [3:0] a, b;
wire [3:0] co ;
initial begin
a = 0 ;
b = 0 ;
sig_input(4'b1111, 4'b1001, a, b);
sig_input(4'b0110, 4'b1001, a, b);
sig_input(4'b1000, 4'b1001, a, b);
end
task sig_input ;
input [3:0] a ;
input [3:0] b ;
output [3:0] ao ;
output [3:0] bo ;
@(posedge clk) ;
ao = a ;
bo = b ;
endtask ; // sig_input
xor_oper u_xor_oper
(
.clk (clk ),
.rstn (rstn ),
.a (a ),
.b (b ),
.co (co ));
initial begin
forever begin
#100;
if ($time >= 1000) $finish ;
end
end
endmodule // test
Les résultats de la simulation sont les suivants.
On peut voir sur la figure que le résultat logique de sortie XOR est correct, avec un délai de 3ns par rapport à l'entrée.
De plus, les signaux connectés a, b, co_t restent cohérents avec les états des signaux numa, numb, numco définis à l'intérieur de la tâche.

Tâche opérant sur des variables globales
Comme une tâche peut être considérée comme une affectation procédurale, le temps de retour des signaux des ports de sortie se situe après l'exécution de toutes les instructions de la tâche.
Les variables à l'intérieur de la tâche ne sont visibles qu'au sein de la tâche. Si vous souhaitez observer spécifiquement le processus d'opération sur les variables dans la tâche, vous devez déclarer les variables observées à l'intérieur du module mais en dehors de la tâche, ce que l'on peut appeler « variables globales ».
Par exemple, voici les deux descriptions suivantes qui tentent d'utiliser une tâche pour générer une horloge.
Exemple
task clk_rvs_iner ;
output clk_no_rvs ;
# 5 ; clk_no_rvs = 0 ;
# 5 ; clk_no_rvs = 1 ;
endtask
reg clk_test1 ;
always clk_rvs_iner(clk_test1);
//way2: utiliser task pour manipuler des variables globales afin de générer clk
reg clk_test2 ;
task clk_rvs_global ;
# 5 ; clk_test2 = 0 ;
# 5 ; clk_test2 = 1 ;
endtask // clk_rvs_iner
always clk_rvs_global;
Les résultats de la simulation sont les suivants.
Dans la première description, bien que les variables internes de la task aient des opérations procédurales d'affectation de 0 et d'affectation de 1, le processus de changement intermédiaire n'est pas visible. La sortie finale ne peut être que la valeur finale du signal de port de sortie après l'exécution de toutes les instructions de la task. Par conséquent, le signal clk_test1 est toujours 1, et cette méthode ne peut pas générer une horloge.
Dans la seconde description, bien qu'il n'y ait pas de signaux de port, elle effectue directement des opérations procédurales sur la "variable globale". Comme la variable globale est visible pour le module, le processus de basculement du signal dans la task sera reflété dans le signal clk_test2.
Tâche automatique
Comme les fonctions, les variables locales dans un appel de task en Verilog sont statiques. Le mot-clé automatic peut être utilisé pour déclarer une task, afin que chaque espace de stockage puisse être alloué dynamiquement lors de l'appel de la task. Chaque task appelée opère indépendamment sur son propre espace d'adressage unique, sans affecter l'exécution concurrente de plusieurs appels à la même task.
Si un segment de code de task est appelé à 2 emplacements ou plus, il doit être déclaré avec le mot-clé automatic.
Lorsque la tâche n'est pas déclarée avec automatic, et que la tâche est appelée deux fois, des interférences de signal peuvent se produire, par exemple comme décrit par le code suivant :
Exemple
input [3:0] cnti ;
input en ;
output [3:0] cnto ;
if (en) cnto = cnti ;
endtask
reg en_cnt ;
reg [3:0] cnt_temp ;
initial begin
en_cnt = 1 ;
cnt_temp = 0 ;
#25 ; en_cnt = 0 ;
end
always #10 cnt_temp = cnt_temp + 1 ;
reg [3:0] cnt1, cnt2 ;
always @(posedge clk) test_flag(2, en_cnt, cnt1); //task(1)
always @(posedge clk) test_flag(cnt_temp, !en_cnt, cnt2);//task(2)
Les résultats de la simulation sont les suivants.
Lorsque en_cnt est à l'état haut, le signal en dans la tâche (1) est actif, et cnt1 peut délivrer la valeur logique correcte ;
À ce moment, le signal en dans la tâche (2) n'est pas activé, donc la valeur de cnt2 est écrasée par la variable partagée cnt_temp pilotée par la tâche (1).
Lorsque en_cnt est à l'état bas, le signal en dans la tâche (2) est actif, donc le signal cnt2 dans la tâche (2) peut délivrer la valeur logique correcte ; à ce moment, sous la commande de l'horloge, la valeur du signal cnt1 est écrasée de manière répétée par la variable partagée cnt_temp pilotée par la tâche (2).
On peut voir que dans les deux appels concurrents, la tâche partage l'espace de stockage, ce qui fait que les signaux s'influencent mutuellement.
Les autres descriptions restent inchangées ; seul le mot-clé automatic est ajouté à la déclaration de tâche ci-dessus, comme suit.
task automatic test_flag ;
Les résultats de simulation à ce moment sont les suivants.
- Lorsque en_cnt est à l'état haut, le signal cnt1 dans la tâche (1) peut délivrer la valeur logique correcte, et la valeur du signal cnt2 dans la tâche (2) est X ;
- Lorsque en_cnt est à l'état bas, le signal cnt2 dans la tâche (2) peut délivrer la valeur logique correcte, et la valeur du signal cnt1 dans la tâche (1) est X ;
On peut voir que dans les deux appels concurrents, comme les espaces de stockage sont indépendants les uns des autres, les signaux ne s'influencent pas mutuellement.

Téléchargement du code source
Téléchargement