IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

FPGA - Les niveaux d'abstraction dans les langages de description de matériel (HDL) - Partie 2/2
Un billet blog de f-leb

Le , par f-leb

0PARTAGES

Lors du billet précédent, nous avions vu comment établir la description d'un composant simple appelé multiplexeur :


Si sel=0, a est dirigé vers s, et si sel=1, c'est b qui est dirigé vers s

Au niveau d'abstraction le plus bas, le niveau structurel, ce composant est décrit par le câblage de portes logiques élémentaires (portes AND, OR et NOT dans ce cas précis). Voyons comment pourrait-on établir une description de ce composant à un plus haut niveau d'abstraction.

Description par flot de données

En Verilog/SystemVerilog, on reconnait une description de type « flot de données » par le mot-clé assign.

Par exemple, pour le multiplexeur de base :
Code SystemVerilog : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
module mux21 
   ( 
      output      logic s, 
      input       logic a, b, 
      input       logic sel       
   ); 
    
   assign s = (a & ~sel) | (b & sel); 
    
endmodule

Alors que signifie cette « assignation » ? Le signe = n'est pas celui d'une « affectation » que l'on rencontre dans les langages de programmation. D'ailleurs ce module générera un circuit logique combinatoire, il n'y a pas d'emplacement mémoire réservé pour stocker une valeur. Ici, les « variables » en jeu sont des nœuds, qui transportent des signaux logiques, sans mémoire.

On souhaite que le signal s vérifie une égalité, continuellement. Toute évolution du terme à droite du signe = due à l'évolution des signaux doit être répercutée sur le signal à gauche du signe =. La sémantique de l'assignation est donc plus forte (pensez à l'« assignation à résidence », l'« assignation en justice », etc. ça ne rigole pas toujours...)

Le terme de droite, et vous l'aurez reconnue, est l'équation logique du multiplexeur (voir billet précédent, où on avait abouti à l'équation \[s = a \cdot \overline{sel} + b \cdot sel\]). Une équation logique écrite avec les opérateurs binaires comme en langage C, tout cela est bien rassurant, mais ne perdez jamais de vue l'interprétation de cette syntaxe C-like dans un HDL.

Bien entendu, les concepteurs de HDL ont prévu toutes les situations avec des opérateurs en abondance. Par exemple, si les entrées du multiplexeur sont des bus, disons de largeur 4 bits, au lieu d'écrire bit par bit :
Code SystemVerilog : Sélectionner tout
1
2
3
4
assign s[0] = (a[0] & ~sel) | (b[0] & sel); 
assign s[1] = (a[1] & ~sel) | (b[1] & sel); 
assign s[2] = (a[2] & ~sel) | (b[2] & sel); 
assign s[3] = (a[3] & ~sel) | (b[3] & sel);
On peut écrire grâce à l'opérateur de réplication de bits : {n{...}} :
Code SystemVerilog : Sélectionner tout
assign s = (a & {4{~sel}}) | (b & {4{sel}});

On y perd quand même en lisibilité, mais tout cela peut encore être remplacé de façon équivalente par :
Code SystemVerilog : Sélectionner tout
assign s = sel ? b : a;
Encore une syntaxe C-like avec l'opérateur ternaire condition ? signal_si_vrai : signal_si_faux (voir la FAQ C pour rappel). À ce niveau d'abstraction, on s'éloigne déjà bien de l'électronique numérique et de ses circuits à logique combinatoire.

Mais il y a encore un niveau d'abstraction au-dessus...

Description comportementale

Au niveau comportemental, un bloc de logique combinatoire débute avec l'instruction always_comb. On peut repartir de l'équation logique du multiplexeur pour écrire un bloc séquentiel :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module mux21 
   ( 
      output      logic s, 
      input       logic a, b, 
      input       logic sel       
   ); 
         
   logic q1, q2, sel_n; 
 
   always_comb begin 
      sel_n = ~sel; 
      q1 = a & sel_n; 
      q2 = b & sel; 
      s = q1 | q2;    
   end 
 
endmodule
Mais il semble plus naturel de décrire le comportement du multiplexeur à partir de sa définition (Si sel=1 alors l'entrée b est dirigée vers la sortie s, sinon...). On en oublierait (presque) toute notion d'électronique numérique.
Code SystemVerilog : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module mux21 
   ( 
      output logic s, 
      input  logic a, b, 
      input  logic sel       
   ); 
       
   always_comb begin 
      if (sel)  
         s = b; 
      else 
         s = a; 
   end 
 
endmodule

Avec always_comb, vous introduisez donc un bloc de logique combinatoire. Au niveau sémantique, on signifie que la logique du bloc doit être évaluée continuellement en fonction de l'évolution des signaux en entrée.

Si le multiplexeur a quatre entrées, l'emploi de case est également possible :
Code SystemVerilog : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module mux41  // 4 entrées, 1 sortie 
   ( 
      output logic s, 
      input  logic a, b, c, d, 
      input  logic [1:0] sel  // 2 bits pour la sélection     
   ); 
      
   always_comb begin 
      case(sel) 
         0 : s = a; 
         1 : s = b; 
         2 : s = c; 
         3 : s = d;      
      endcase 
   end 
    
endmodule

Alors vous pouvez toujours établir votre description de façon structurelle ou par flot de données (avec assign et une belle équation qui doit tenir sur une ligne), mais le niveau comportemental permet des descriptions beaucoup plus riches et de façon plus naturelle pour un développeur, avec des calculs, des if...else que l'on peut imbriquer, des case, etc.

Par exemple, si on veut rajouter une entrée d'activation au multiplexeur 4 entrées précédent, on pourra écrire de façon comportementale le code suivant :
Code SystemVerilog : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module mux41  // 4 entrées, 1 sortie 
   ( 
      output logic s, 
      input  logic a, b, c, d,  
      input logic enable_n, // entrée d'activation, active à l'état bas 
      input  logic [1:0] sel       
   ); 
      
   always_comb begin 
      if (enable_n)  // si inactif 
         s = 0; 
      else begin 
         case(sel) 
            0 : s = a; 
            1 : s = b; 
            2 : s = c; 
            3 : s = d;      
         endcase 
      end 
   end 
    
endmodule

Analyse par le synthétiseur du multiplexeur 4 entrées avec entrée d'activation

Il faut juste, une fois de plus, ne pas oublier que malgré cette lecture séquentielle du code dans les blocs always_comb, un circuit à logique combinatoire sera généré par le synthétiseur, où tous les signaux évoluent « en même temps ».

Conclusion

LES HDL permettent de synthétiser des systèmes complexes à différents niveaux d'abstraction. À un niveau élevé d'abstraction, le développeur se soucie moins des détails de l'électronique numérique et de son implémentation physique. L'écriture du code en est également facilitée, sa mise au point et sa réutilisation sont favorisées.
Pour autant, dans un projet un peu consistant, vous allez certainement mixer les niveaux d'abstraction. Les fonctionnalités du projet seront découpées en modules logiques plus petits qui seront reliés entre eux ou avec les entrées-sorties du système (description structurelle). À l'intérieur des modules, vous élevez le niveau d'abstraction en fonction de la complexité.


Exemple de structure d'un projet de chronomètre (au 1/10è de seconde avec afficheur 7-segments). Un module pour le comptage, un pour l'affichage du compteur, un autre pour filtrer les rebonds d'un bouton-poussoir, etc.

Une erreur dans cette actualité ? Signalez-nous-la !