Mots-clés : instanciation, generate, additionneur complet, accès hiérarchique

Référencer un autre module à l'intérieur d'un module et connecter ses ports s'appelle l'instanciation de module. L'instanciation de module établit la hiérarchie de la description. Les ports de signal peuvent être associés par position ou par nom, et les connexions de port doivent également suivre certaines règles.

Connexion de port par nom

Cette méthode connecte les ports du module à instancier avec des signaux externes par nom. L'ordre des ports est arbitraire et peut différer de l'ordre de déclaration des ports du module référencé, tant que les noms des ports correspondent aux signaux externes.

Voici un exemple d'instanciation d'un additionneur complet 1 bit :

Exemple

full_adder1 u_adder0(
    .Ai     (a[0]),
    .Bi     (b[0]),
    .Ci     (c==1'b1 ? 1'b0 : 1'b1),
    .So     (so_bit0),
    .Co     (co_temp[0]));

Si certains ports de sortie n'ont pas besoin d'être connectés à l'extérieur, ils peuvent être laissés flottants ou même omis lors de l'instanciation. En général, les ports d'entrée ne peuvent pas être omis lors de l'instanciation, sinon une erreur de compilation se produira, tandis que les ports de sortie peuvent être omis lors de l'instanciation. Par exemple :

Exemple

//port de sortie Co flottant
full_adder1 u_adder0(
    .Ai     (a[0]),
    .Bi     (b[0]),
    .Ci     (c==1'b1 ? 1'b0 : 1'b1),
    .So     (so_bit0),
    .Co     ());
 
//port de sortie Co omis
full_adder1 u_adder0(
    .Ai     (a[0]),
    .Bi     (b[0]),
    .Ci     (c==1'b1 ? 1'b0 : 1'b1),
    .So     (so_bit0));

Connexion ordonnée des ports

Cette méthode fait correspondre et connecte les ports du module à instancier avec des signaux externes selon l'ordre des ports déclaré dans le module, et les positions doivent être strictement cohérentes. Par exemple, le code d'instanciation d'un additionneur complet 1 bit peut être modifié comme suit :

full_adder1  u_adder1(
    a[1], b[1], co_temp[0], so_bit1, co_temp[1]);

Bien que ce code puisse occuper relativement moins d'espace à l'écriture, la lisibilité du code est réduite et le débogage devient plus difficile. Parfois, dans les grandes conceptions, il peut y avoir de nombreux ports, et l'ordre des signaux de port peut changer de temps en temps. Dans de tels cas, utiliser une connexion de ports ordonnée pour l'instanciation de module est manifestement peu pratique. Par conséquent, dans la pratique normale, il est recommandé d'utiliser une connexion de ports nommée pour l'instanciation de module.

Règles de connexion des ports

Port d'entrée

Lors de l'instanciation d'un module, depuis l'extérieur du module, les ports d'entrée peuvent être connectés à des variables de type wire ou reg. Cela diffère de la déclaration de module ; depuis l'intérieur du module, les ports d'entrée doivent être des variables de type wire.

Port de sortie

Lors de l'instanciation d'un module, depuis l'extérieur du module, les ports de sortie doivent être connectés à des variables de type wire. Cela diffère de la déclaration de module ; depuis l'intérieur du module, les ports de sortie peuvent être des variables de type wire ou reg.

Port inout

Lors de l'instanciation d'un module, depuis l'extérieur du module, les ports inout doivent être connectés à des variables de type wire. Cela est identique à la déclaration de module.

Port flottant

Lors de l'instanciation d'un module, si certains signaux n'ont pas besoin d'être connectés ou d'interagir avec des signaux externes, nous pouvons les laisser flottants, c'est-à-dire laisser vide la position d'instanciation du port, comme mentionné dans les exemples ci-dessus.

Lorsqu'un port de sortie est laissé flottant, nous pouvons même l'omettre lors de l'instanciation.

Lorsqu'un port d'entrée est laissé flottant, la fonction logique du signal flottant apparaît comme un état haute impédance (valeur logique z). Cependant, un port d'entrée flottant ne peut généralement pas être omis lors de l'instanciation, sinon une erreur de compilation se produira. Par exemple :

Exemple

//Le code suivant générera un avertissement lors de la compilation
full_adder4 u_adder4(
    .a      (a),
    .b      (b),
    .c      (),
    .so     (so),
    .co     (co));

Exemple

//Si le module full_adder4 possède un port d'entrée c, le code suivant générera une erreur lors de la compilation
full_adder4 u_adder4(
    .a      (a),
    .b      (b),
    .so     (so),
    .co     (co));

En général, il est recommandé de ne pas laisser les ports d'entrée flottants ; lorsqu'il n'y a pas d'autre connexion externe, attribuez-leur une valeur constante. Par exemple :

Exemple

full_adder4 u_adder4(
    .a      (a),
    .b      (b),
    .c      (1'b0),
    .so     (so),
    .co     (co));

Correspondance des largeurs de bits

Lorsque la largeur de bits d'un port d'instanciation ne correspond pas à celle du signal continu, le port sera mis en correspondance par alignement à droite ou troncature du nombre non signé.

Supposons que, dans le module full_adder4, les ports a et b aient tous deux une largeur de 4 bits. Le résultat de l'instanciation du code suivant provoquera :u_adder4.a = {2'bzz, a[1:0]}, u_adder4.b = b[3:0] 。

Exemple

full_adder4 u_adder4(
    .a      (a[1:0]),      //entrée a[3:0]
    .b      (b[5:0]),      //entrée b[3:0]
    .c      (1'b0),
    .so     (so),
    .co     (co));

Types de signaux continus des ports

Les types de signaux connectés aux ports peuvent être : 1) des identificateurs, 2) des sélections de bits, 3) des sélections de parties, 4) des concaténations des types ci-dessus, 5) des expressions utilisées pour les ports d'entrée.

Bien sûr, les noms de signaux peuvent être identiques aux noms de ports, mais leurs significations sont différentes ; ils représentent respectivement des signaux dans 2 modules différents.

Utilisation de generate pour l'instanciation de modules

Lors de l'instanciation de plusieurs modules identiques, les instancier manuellement un par un est fastidieux. L'utilisation d'instructions generate pour instancier plusieurs modules de manière répétée peut grandement simplifier le processus de programmation.

Le code pour instancier de manière répétée quatre additionneurs complets 1-bit afin de former un additionneur complet 4-bit est le suivant :

Exemple

module full_adder4(
    input [3:0]   a ,   //additionneur1
    input [3:0]   b ,   //additionneur2
    input         c ,   //bit de retenue d'entrée
 
    output [3:0]  so ,  //résultat de l'addition
    output        co    //bit de retenue de sortie
    );
 
    wire [3:0]    co_temp ;
    //Le premier module instancié a généralement un format différent et doit être instancié séparément
    full_adder1 u_adder0(
        .Ai     (a[0]),
        .Bi     (b[0]),
        .Ci     (c==1'b1 ? 1'b1 : 1'b0),
        .So     (so[0]),
        .Co     (co_temp[0]));
 
    genvar        i ;
    generate
        for(i=1; i<=3; i=i+1) begin: adder_gen
        full_adder1 u_adder(
            .Ai     (a[i]),
            .Bi     (b[i]),
            .Ci     (co_temp[i-1]), //Le débordement de l'additionneur complet précédent est la retenue d'entrée du suivant
            .So     (so[i]),
            .Co     (co_temp[i]));
        end
    endgenerate
 
    assign co    = co_temp[3] ;
 
endmodule

Le banc de test est le suivant :

Exemple

`timescale 1ns/1ns
 
module test ;
    reg  [3:0]   a ;
    reg  [3:0]   b ;
    //reg c ;
    wire [3:0]   so ;
    wire         co ;
 
    //Commande simple
    initial begin
        a = 4'd5 ;
        b = 4'd2 ;
        #10 ;
        a = 4'd10 ;
        b = 4'd8 ;
    end
 
    full_adder4 u_adder4(
               .a      (a),
               .b      (b),
               .c      (1'b0),   //Les ports peuvent être connectés à des constantes
               .so     (so),
               .co     (co));
 
    initial begin
        forever begin
            #100;
            if ($time >= 1000)  $finish ;
        end
    end
 
endmodule // test

Les résultats de simulation sont les suivants. On peut voir que l'additionneur complet 4 bits fonctionne correctement :

Accès hiérarchique

Le nom de chaque module instancié, les variables de signal de chaque module, etc., sont tous définis à l'aide d'identifiants spécifiques. Dans toute la conception hiérarchique, chaque identifiant a une position et un nom uniques.

En Verilog, en utilisant une série de.symboles pour séparer et relier hiérarchiquement les identifiants de chaque module, les identifiants de toute la conception sont accessibles n'importe où en spécifiant le nom hiérarchique complet.

L'accès hiérarchique est le plus souvent observé en simulation.

Par exemple, avec la conception hiérarchique suivante, les signaux entre les cellules feuilles, les sous-modules et le module de niveau supérieur peuvent accéder les uns aux autres.

Exemple

//Accès aux signaux du module u_n3 dans le module u_n1 :
a = top.u_m2.u_n3.c ;

//Accès aux signaux du module top dans le module u_n1
if (top.p == 'b0) a = 1'b1 ;

//Accès aux signaux du module u_n4 dans le module top
assign p = top.u_m2.u_n4.d ;

Dans les simulations des sections précédentes, l'accès hiérarchique a été effectué dans une certaine mesure. Par exemple"Affectation continue procédurale"section, les instructions suivantes ont été utilisées dans le module de test de stimulus de simulation de niveau supérieur :

wait (test.u_counter.cnt_temp == 4'd4) ;

Téléchargement du code source

Téléchargement