Mots-clés : defparam, parameter, instanciation, ram
Lorsqu'un module est instancié par un autre module, le module de niveau supérieur peut redéfinir les valeurs des paramètres du module de niveau inférieur. Cela permet de transmettre différents paramètres à plusieurs modules portant le même nom au moment de la compilation, sans avoir à créer de nouveaux fichiers pour des modules qui ne diffèrent que par leurs paramètres.
Il existe 2 façons de redéfinir les paramètres : 1) utiliser le mot-clé defparam, 2) l'instanciation de module avec des valeurs de paramètres.
Instruction defparam
Le mot-clé defparam peut être utilisé pour redéfinir les valeurs des paramètres de modules de niveau inférieur au moyen de références hiérarchiques de modules.
Par exemple, redéfinissez le paramètre MASK d’un module RAM à un seul port dont les lignes d’adresse et de données ont toutes deux 4 bits de large :
Exemple
defparam u_ram_4x4.MASK = 7 ;
ram_4x4 u_ram_4x4
(
.CLK (clk),
.A (a[4-1:0]),
.D (d),
.EN (en),
.WR (wr), //1 pour écriture et 0 pour lecture
.Q (q) );
Le modèle ram_4x4 est le suivant :
Exemple
(
input CLK ,
input [4-1:0] A ,
input [4-1:0] D ,
input EN ,
input WR , //1 pour écriture et 0 pour lecture
output reg [4-1:0] Q );
parameter MASK = 3 ;
reg [4-1:0] mem [0:(1<<4)-1] ;
always @(posedge CLK) begin
if (EN && WR) begin
mem[A] <= D & MASK;
end
else if (EN && !WR) begin
Q <= mem[A] & MASK;
end
end
endmodule
Effectuez une simulation simple sur ceci, et le banc de test est écrit comme suit :
Exemple
module test ;
parameter AW = 4 ;
parameter DW = 4 ;
reg clk ;
reg [AW:0] a ;
reg [DW-1:0] d ;
reg en ;
reg wr ;
wire [DW-1:0] q ;
//génération d'horloge
always begin
#15 ; clk = 0 ;
#15 ; clk = 1 ;
end
initial begin
a = 10 ;
d = 2 ;
en = 'b0 ;
wr = 'b0 ;
repeat(10) begin
@(negedge clk) ;
en = 1'b1;
a = a + 1 ;
wr = 1'b1 ; //commande d'écriture
d = d + 1 ;
end
a = 10 ;
repeat(10) begin
@(negedge clk) ;
a = a + 1 ;
wr = 1'b0 ; //commande de lecture
end
end // initial begin
//instanciation
defparam u_ram_4x4.MASK = 7 ;
ram_4x4 u_ram_4x4
(
.CLK (clk),
.A (a[AW-1:0]),
.D (d),
.EN (en),
.WR (wr), //1 pour écriture et 0 pour lecture
.Q (q)
);
//arrêt de la simulation
initial begin
forever begin
#100;
if ($time >= 1000) $finish ;
end
end
endmodule // test
Les résultats de simulation sont les suivants :
Dans la partie jaune de la figure, lorsque l'adresse est c pour la première fois, la donnée 4 est écrite ; lorsque l'adresse est c pour la deuxième fois, la donnée 4 est lue. On peut voir que la RAM se comporte correctement à ce moment, et MASK n'est pas 3, car le bit2 de la sortie Q de la RAM n'est pas masqué.
Lorsque l'adresse est 1 pour la première fois, la donnée 9 est écrite ; lorsque l'adresse est 1 pour la deuxième fois, la donnée lue est 1, car à ce moment MASK vaut 7, et le bit3 du signal de sortie Q de la RAM est masqué. On peut en déduire que le paramètre MASK a été correctement redéfini.
Instanciation de module paramétré
La deuxième méthode consiste à écrire les nouvelles valeurs de paramètres dans l'instruction d'instanciation du module lors de l'instanciation du module, ce qui redéfinit les valeurs de paramètres du module d'origine.
Par exemple, effectuez une instanciation de module paramétrée sur un module RAM avec des largeurs d'adresse et de données variables :
Exemple
u_ram
(
.CLK (clk),
.A (a[AW-1:0]),
.D (d),
.EN (en),
.WR (wr), //1 pour écriture et 0 pour lecture
.Q (q)
);
Le modèle RAM est le suivant :
Exemple
#( parameter AW = 2 ,
parameter DW = 3 )
(
input CLK ,
input [AW-1:0] A ,
input [DW-1:0] D ,
input EN ,
input WR , //1 pour écriture et 0 pour lecture
output reg [DW-1:0] Q
);
reg [DW-1:0] mem [0:(1<<AW)-1] ;
always @(posedge CLK) begin
if (EN && WR) begin
mem[A] <= D ;
end
else if (EN && !WR) begin
Q <= mem[A] ;
end
end
endmodule
En simulation, remplacez simplement le module instancié u_ram_4x4 par ce module instancié u_ram dans le banc de test précédent, ou ajoutez-le à nouveau.
Les résultats de simulation sont les suivants. Comme on peut le voir sur la figure, les paramètres AW et DW du module RAM ont tous deux été redéfinis à 4, et la RAM se comporte correctement.
Différences et suggestions
(1) Tout comme l'instanciation de port de module, lors de l'instanciation paramétrée, vous pouvez également effectuer l'instanciation des paramètres dans l'ordre sans spécifier les noms des paramètres d'origine. Par exemple, l'instanciation de u_ram peut être décrite comme :
ram #(4, 4) u_ram (......) ;
(2) Bien sûr, defparam peut également être utilisé pour redéfinir les paramètres déclarés dans la déclaration de port du module, et l'instanciation paramétrée peut également redéfinir les paramètres déclarés dans le corps du module. Par exemple, les instanciations de u_ram et u_ram_4x4 peuvent être décrites comme :
Exemple
defparam u_ram.DW = 4 ;
ram u_ram(......);
ram_4x4 #(.MASK(7)) u_ram_4x4(......);
(3) Ces deux méthodes de redéfinition des paramètres de module peuvent-elles être mélangées ? Bien sûr ! La condition préalable est que tous les paramètres doivent être déclarés soit dans la déclaration de port du module, soit dans le corps du module. Par exemple, la déclaration de u_ram peut également être exprimée ainsi (les paramètres dans le corps du module peuvent être vérifiés par vos propres expériences) :
Exemple
ram #(.DW(4)) u_ram (......); // Seul quelqu'un d'aussi désœuvré que moi expérimenterait ce style d'écriture
(4) Que se passe-t-il si un module a à la fois des paramètres déclarés dans la déclaration de port du module et des paramètres déclarés dans le corps du module ? Ces deux types de paramètres peuvent-ils être redéfinis en même temps ? Par exemple, ajoutez le paramètre MASK au module RAM, et le modèle est le suivant :
Exemple
#( parameter AW = 2 ,
parameter DW = 3 )
(
input CLK ,
input [AW-1:0] A ,
input [DW-1:0] D ,
input EN ,
input WR , //1 pour écriture et 0 pour lecture
output reg [DW-1:0] Q );
parameter MASK = 3 ;
reg [DW-1:0] mem [0:(1<<AW)-1] ;
always @(posedge CLK) begin
if (EN && WR) begin
mem[A] <= D ;
end
else if (EN && !WR) begin
Q <= mem[A] ;
end
end
endmodule
À ce stade, l'utilisation de defparam pour redéfinir la valeur du paramètre MASK provoque une erreur de compilation :
Exemple
defparam u_ram.AW = 4 ;
defparam u_ram.DW = 4 ;
defparam u_ram.MASK = 7 ;
ram u_ram (......);
// Redéfinir un paramètre dans le corps du module avec defparam signalera également une erreur
defparam u_ram.MASK = 7 ;
ram #(.AW(4), .DW(4)) u_ram (......);
Voici le point clé !!! Si vous utilisez la méthode d'instanciation de module paramétré pour redéfinir la valeur du paramètre MASK, la compilation ne signalera pas d'erreur, et MASK sera redéfini avec succès !
ram #(.AW(4), .DW(4), .MASK(7)) u_ram (......);
Une explication possible est que, du point de vue du compilateur, s'il y a des paramètres dans la déclaration de port du module, les paramètres dans le corps sont traités comme de type localparam, et l'utilisation de defparam ne peut pas redéfinir les paramètres déclarés dans le corps du module.
Cela peut aussi être lié au compilateur ; vous pouvez également faire des expériences sur d'autres compilateurs.
(5) Il est recommandé de ne pas utiliser la méthode defparam lors de l'instanciation d'un module existant et de la redéfinition de ses paramètres associés. En plus des inconvénients ci-dessus, defparam n'est généralement pas synthétisable.
(6) De plus, il est recommandé, lors de l'écriture d'un module, si vous savez à l'avance qu'il sera instancié et qu'il y a des paramètres à redéfinir, d'écrire ces paramètres avant la déclaration de port du module (en utilisant le mot-clé hash#pour indiquer).
Téléchargement du code source
Téléchargement
Résumé de l'étape
En fait, après cette introduction, vous pouvez pleinement utiliser les connaissances du langage Verilog apprises précédemment pour construire une petite chaumière de circuits matériels. Oui, une petite chaumière. En raison de la nature particulière des langages de description matérielle correspondant à des circuits matériels réels, lorsque vous construisez divers modèles avec Verilog, vous devez considérer à quoi ressemble le circuit réellement généré et s'il répond aux exigences pratiques. Parfois, la simulation RTL peut réussir, mais le circuit réel généré au final peut fonctionner anormalement.
Par conséquent, pour ajouter des briques et des tuiles à votre petite hutte de chaume, vous devez encore apprendre la partie avancée. Bien sûr, la partie avancée peut seulement transformer votre petite hutte de chaume en une solide maison en briques capable de résister au vent et à la neige, mais elle risque encore de s'effondrer si un tremblement de terre survient.
Si vous voulez renforcer votre maison en briques et construire une villa, vous devez alors apprendre davantage de sujets avancés sur Verilog, tels que PLI (Programming Language Interface), UDP (User Defined Primitives), les contraintes de temps et l'analyse temporelle, etc. Vous devez également participer à davantage de projets d'ingénierie pour accumuler de l'expérience, et accorder une attention particulière à certaines techniques de conception telles que la conception à faible consommation, la conception asynchrone, etc. Bien sûr, apprendre à utiliser SystemVerilog pour une vérification complète ajoutera une couche de protection supplémentaire à votre bâtiment.
Mais si vous voulez finir d'apprendre toutes les connaissances des circuits numériques et de Verilog pour construire un palais présidentiel à l'épreuve des obus, cela dépasse vraiment notre capacité à vous aider. Parce que la mer de l'apprentissage n'a pas de fin, et il n'y a pas de rivage où faire demi-tour.
En raison de limitations d'espace, seule la partie avancée est présentée ici. Si l'occasion se présente, les sujets avancés et les sujets techniques seront également ajoutés.