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 comparaisonFonctionTâche
EntréeUne fonction doit avoir au moins une entrée, et la déclaration de port ne peut pas contenir le type inoutUne 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
SortieUne fonction n'a pas de sortieUne tâche peut ne pas avoir de sortie ou avoir plusieurs sorties
Valeur de retourUne fonction a au moins une valeur de retourUne tâche n'a pas de valeur de retour
Temps de simulationUne fonction commence toujours son exécution au temps zéroUne tâche peut s'exécuter à des temps non nuls
Logique séquentielleUne fonction ne peut contenir aucune logique de contrôle de temporisationUne 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
AppelUne fonction peut uniquement appeler des fonctions, pas des tâchesUne tâche peut appeler des fonctions et des tâches
Normes de codageUne fonction ne peut pas apparaître comme instruction autonome ; elle peut uniquement être placée du côté droit d'une instruction d'affectationUne 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

task xor_oper_iner;
    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

task xor_oper_iner(
    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

module xor_oper
    #(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

`timescale 1ns/1ns
 
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

//way1 pour décrire la génération de clk, ne fonctionne pas
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

task test_flag ;
        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