Mots-clés : fonctions, conversion endian, décodage d'affichage à sept segments
En Verilog, les tâches (mot-clé : task) ou les fonctions (mot-clé : function) peuvent être utilisées pour extraire des conceptions comportementales répétitives et les appeler à plusieurs endroits, évitant ainsi de réécrire du code dupliqué et rendant le code plus concis et plus facile à comprendre.
Fonction
Les fonctions ne peuvent être définies que dans un module, à n'importe quel emplacement, et peuvent être référencées n'importe où dans le module ; leur portée est également limitée à ce module. Les fonctions ont principalement les caractéristiques suivantes :
- 1) Elles ne contiennent ni délai, ni temporisation, ni logique de contrôle de temporisation.
- 2) Elles ont au moins une variable d'entrée.
- 3) Elles n'ont qu'une seule valeur de retour et aucune sortie.
- 4) Elles ne contiennent pas d'instructions d'affectation non bloquantes.
- 5) Les fonctions peuvent appeler d'autres fonctions, mais ne peuvent pas appeler de tâches.
Le format de déclaration d'une fonction Verilog est le suivant :
function [range-1:0] function_id ; input_declaration ; other_declaration ; procedural_statement ; endfunction
Lorsqu'une fonction est déclarée, une variable de registre avec une plage de largeur et le nom function_id est implicitement déclarée, et la valeur de retour de la fonction est transmise via cette variable. Lorsqu'aucune largeur n'est spécifiée pour cette variable de registre, la largeur par défaut est 1.
Les fonctions sont appelées en spécifiant le nom de la fonction et les variables d'entrée. Lorsque la fonction se termine, la valeur de retour est transmise à l'emplacement appelant.
Le format d'appel de fonction est le suivant :
function_id(input1, input2, …);
Ci-dessous, une fonction est utilisée pour implémenter la conversion d'endianness des données.
Lorsque l'entrée est 4'b0011, la sortie peut être 4'b1100. Par exemple :
Exemple
#(paramètre N = 4)
(
input en, //commande d'activation
input [N-1:0] a ,
output [N-1:0] b
);
reg [N-1:0] b_temp ;
always @(*) begin
if (en) begin
b_temp = data_rvs(a);
end
else begin
b_temp = 0 ;
end
end
assign b = b_temp ;
//entité de fonction
function [N-1:0] data_rvs ;
input [N-1:0] data_in ;
parameter MASK = 32'h3 ;
entier k ;
begin
for(k=0; k<N; k=k+1) begin
data_rvs[N-k-1] = data_in[k] ;
end
end
endfunction
endmodule
Les paramètres dans une fonction peuvent aussi être modifiés, par exemple :
defparam data_rvs.MASK = 32'd7 ;
Cependant, la simulation révèle que ce style d'écriture se compile avec succès, mais dans les résultats de simulation, le paramètre MASK dans la fonction n'est pas réellement modifié avec succès, et il reste 32'h3. Cela peut être lié au compilateur. Les chercheurs intéressés peuvent expérimenter avec d'autres compilateurs Verilog.
Lors de la déclaration d'une fonction, vous pouvez également ajouter des parenthèses après le nom de la fonction pour y placer les déclarations d'entrée.
Par exemple, la fonction de déclaration de conversion endian ci-dessus peut s'écrire comme suit :
function [N-1:0] data_rvs(
input [N-1:0] data_in
......
) ;
Fonction constante
Une fonction constante est une fonction dont le résultat est calculé comme une constante lors de la compilation, avant le début de la simulation. Les fonctions constantes ne sont pas autorisées à accéder aux variables globales ni à appeler des fonctions système, mais elles peuvent appeler d'autres fonctions constantes.
De telles fonctions peuvent être utilisées pour référencer des valeurs complexes, de sorte qu'elles peuvent être utilisées pour remplacer des constantes.
Par exemple, la fonction constante suivante peut être utilisée pour calculer la largeur du bus d'adresses dans un module :
Exemple
reg [logb2(MEM_DEPTH)-1: 0] addr ; //La largeur de addr peut être obtenue comme 8 bits
function integer logb2;
input integer depth ;
//256 correspond à 9 bits, notre donnée finale doit être 8, donc la boucle doit s'arrêter plus tôt lorsque depth=2
for(logb2=0; depth>1; logb2=logb2+1) begin
depth = depth >> 1 ;
end
endfunction
fonction automatique
En Verilog, les variables locales des fonctions ordinaires sont statiques, ce qui signifie que chaque appel à la fonction utilise le même espace de stockage pour ses variables locales. Si une fonction est appelée simultanément à deux endroits différents, les deux comportements d'appel de fonction opèrent sur la même adresse en même temps, ce qui conduit à des résultats de fonction incertains.
Verilog utilise le mot-clé automatic pour décrire les fonctions. De telles fonctions peuvent allouer automatiquement un nouvel espace mémoire lorsqu'elles sont appelées, ce qui peut aussi être compris comme étant récursif. Par conséquent, les variables locales déclarées dans une fonction automatique ne sont pas accessibles via une nomenclature hiérarchique, mais la fonction automatique elle-même peut être appelée via un nom hiérarchique.
Ci-dessous, une fonction automatique est utilisée pour implémenter le calcul de la factorielle :
Exemple
function automatic integer factorial ;
input integer data ;
entier i ;
begin
factorial = (data>=2)? data * factorial(data-1) : 1 ;
end
endfunction // factorial
Voici les résultats de simulation avec et sans le mot-clé automatic.
On peut voir sur la figure que le signal results3 a obtenu le résultat souhaité, c'est-à-dire la factorielle de 4.
Le signal results_noauto a une valeur de 1, ce qui n'est pas un résultat normal prévisible, donc aucune analyse inutile supplémentaire ne sera effectuée ici.

Décodage d'affichage à sept segments
Les connaissances sur les fonctions abordées ci-dessus ne semblent pas démontrer les avantages des fonctions. Ci-dessous, un décodeur d'affichage à sept segments pour nombres décimaux à 4 chiffres est conçu pour illustrer l'avantage que les fonctions peuvent offrir pour simplifier le code.
La figure ci-dessous est un schéma physique d'un affichage à sept segments, qui peut être utilisé pour afficher des nombres décimaux à 4 chiffres. Il a des applications assez larges dans la notation des compétitions, le chronométrage et d'autres aspects.

Chaque segment d'affichage de chiffre possède 8 bornes de commande de lumière (comme indiqué par a-g sur la figure), qui peuvent être utilisées pour contrôler l'affichage des chiffres 0-9.
L'afficheur à sept segments possède 4 sélections de puce (indiquées par 1-4 sur la figure), utilisées pour contrôler quel segment d'affichage de chiffre doit être sélectionné, c'est-à-dire lequel doit s'allumer. Si les 4 segments d'affichage de chiffres sont sélectionnés et allumés en séquence dans un laps de temps très court, tandis que différentes commandes d'éclairage sont fournies sous différentes sélections de puce (chacune correspondant aux 4 chiffres décimaux), alors, dans les limites de ce que l'œil humain peut distinguer, l'effet d'afficher 4 chiffres décimaux simultanément est obtenu.

Ci-dessous, nous utilisons le signal abcdefg pour contrôler les bornes de commande d'éclairage, et le signal csn pour contrôler la sélection de puce. Les chiffres des unités, dizaines, centaines et milliers du nombre décimal à 4 chiffres sont représentés par quatre signaux de 4 bits single_digit, ten_digit, hundred_digit et kilo_digit, respectivement. La conception d'affichage d'un afficheur à sept segments peut être décrite comme suit :
Exemple
(
input clk ,
input rstn ,
input en ,
input [3:0] single_digit ,
input [3:0] ten_digit ,
input [3:0] hundred_digit ,
input [3:0] kilo_digit ,
output reg [3:0] csn , //sélection de puce, active à l'état bas
output reg [6:0] abcdefg //commande d'éclairage
);
reg [1:0] scan_r ; //scan_ctrl
always @ (posedge clk or negedge rstn) begin
if(!rstn)begin
csn <= 4'b1111;
abcdefg <= 'd0;
scan_r <= 3'd0;
end
else if (en) begin
case(scan_r)
2'd0:begin
scan_r <= 3'd1;
csn <= 4'b0111; //sélectionner le chiffre des unités
abcdefg <= dt_translate(single_digit);
end
2'd1:begin
scan_r <= 3'd2;
csn <= 4'b1011; //sélectionner le chiffre des dizaines
abcdefg <= dt_translate(ten_digit);
end
2'd2:begin
scan_r <= 3'd3;
csn <= 4'b1101; //sélectionner le chiffre des centaines
abcdefg <= dt_translate(hundred_digit);
end
2'd3:begin
scan_r <= 3'd0;
csn <= 4'b1110; //sélectionner le chiffre des milliers
abcdefg <= dt_translate(kilo_digit);
end
endcase
end
end
/*------------ fonction de traduction -------*/
function [6:0] dt_translate;
input [3:0] data;
begin
case(data)
4'd0: dt_translate = 7'b1111110; //nombre 0 -> 0x7e
4'd1: dt_translate = 7'b0110000; //nombre 1 -> 0x30
4'd2: dt_translate = 7'b1101101; //nombre 2 -> 0x6d
4'd3: dt_translate = 7'b1111001; //nombre 3 -> 0x79
4'd4: dt_translate = 7'b0110011; //nombre 4 -> 0x33
4'd5: dt_translate = 7'b1011011; //nombre 5 -> 0x5b
4'd6: dt_translate = 7'b1011111; //nombre 6 -> 0x5f
4'd7: dt_translate = 7'b1110000; //nombre 7 -> 0x70
4'd8: dt_translate = 7'b1111111; //nombre 8 -> 0x7f
4'd9: dt_translate = 7'b1111011; //nombre 9 -> 0x7b
endcase
end
endfunction
endmodule
Les résultats de la simulation sont les suivants.
On peut voir sur la figure que des signaux tels que la sélection de puce et le décodage sont tous conformes à la conception. En pratique, les 4 chiffres doivent rester inchangés pendant un certain temps, tandis que le signal de sélection de puce balaie continuellement en boucle, de sorte que l'affichage à sept segments présente un effet d'affichage statique à l'œil humain.

Résumé
Si la conception du décodeur n'utilisait pas la fonction dt_translate, alors lors de l'affectation du signal abcdefg dans chaque option case, il serait également nécessaire d'effectuer des jugements sur single_digit, ten_digit, hundred_digit et kilo_digit. Ces instructions de jugement seraient répétées 4 fois. Bien que le circuit matériel réel synthétisé à la fin puisse être identique, il est évident que le code utilisant des fonctions est plus concis et lisible.
Téléchargement du code source
Téléchargement