Verilog génère un bloc
Un generate
Le bloc permet de multiplier les instances de module ou d'effectuer une instanciation conditionnelle de n'importe quel module. Il offre la possibilité de construire la conception en fonction des paramètres Verilog. Ces instructions sont particulièrement pratiques lorsque la même opération ou instance de module doit être répétée plusieurs fois ou si certains codes doivent être inclus de manière conditionnelle en fonction de paramètres Verilog donnés.
Un generate
le bloc ne peut pas contenir de port, paramètre, specparam
déclarations ou specify
blocs. Cependant, d'autres éléments de module et d'autres blocs de génération sont autorisés. Toutes les instanciations générées sont codées dans un module
et entre les mots clés generate
et endgenerate
.
Les instanciations générées peuvent avoir des modules, des affectations continues, always
ou initial
blocs et primitives définies par l'utilisateur. Il existe deux types de constructions de génération :les boucles et les conditions.
- Générer une boucle for
- Générer si sinon
- Générer un cas
Générer une boucle for
Un demi-additionneur sera instancié N fois dans un autre module de conception de niveau supérieur appelé my_design en utilisant un generate
pour la construction de la boucle. La variable de boucle doit être déclarée à l'aide du mot-clé genvar
qui indique à l'outil que cette variable doit être spécifiquement utilisée lors de l'élaboration du bloc de génération.
// Design for a half-adder
module ha ( input a, b,
output sum, cout);
assign sum = a ^ b;
assign cout = a & b;
endmodule
// A top level design that contains N instances of half adder
module my_design
#(parameter N=4)
( input [N-1:0] a, b,
output [N-1:0] sum, cout);
// Declare a temporary loop variable to be used during
// generation and won't be available during simulation
genvar i;
// Generate for loop to instantiate N times
generate
for (i = 0; i < N; i = i + 1) begin
ha u0 (a[i], b[i], sum[i], cout[i]);
end
endgenerate
endmodule
Banc de test
Le paramètre testbench est utilisé pour contrôler le nombre d'instances de demi-additionneurs dans la conception. Lorsque N vaut 2, my_design aura deux instances de demi additionneur.
module tb;
parameter N = 2;
reg [N-1:0] a, b;
wire [N-1:0] sum, cout;
// Instantiate top level design with N=2 so that it will have 2
// separate instances of half adders and both are given two separate
// inputs
my_design #(.N(N)) md( .a(a), .b(b), .sum(sum), .cout(cout));
initial begin
a <= 0;
b <= 0;
$monitor ("a=0x%0h b=0x%0h sum=0x%0h cout=0x%0h", a, b, sum, cout);
#10 a <= 'h2;
b <= 'h3;
#20 b <= 'h4;
#10 a <= 'h5;
end
endmodule
a[0] et b[0] donnent la sortie sum[0] et cout[0] tandis que a[1] et b[1] donnent la sortie sum[1] et cout[1].
Journal de simulationncsim> run a=0x0 b=0x0 sum=0x0 cout=0x0 a=0x2 b=0x3 sum=0x1 cout=0x2 a=0x2 b=0x0 sum=0x2 cout=0x0 a=0x1 b=0x0 sum=0x1 cout=0x0 ncsim: *W,RNQUIE: Simulation is complete. ncsim> exit
Voir que RTL élaboré a en effet deux demi-instances d'addition générées par le generate
bloquer.
Générer si
Ci-dessous un exemple utilisant un if else
dans un generate
construire pour choisir entre deux implémentations de multiplexeurs différentes. La première conception utilise un assign
déclaration pour implémenter un multiplexeur tandis que la deuxième conception utilise un case
déclaration. Un paramètre appelé USE_CASE est défini dans le module de conception de niveau supérieur pour choisir entre les deux choix.
// Design #1: Multiplexer design uses an "assign" statement to assign
// out signal
module mux_assign ( input a, b, sel,
output out);
assign out = sel ? a : b;
// The initial display statement is used so that
// we know which design got instantiated from simulation
// logs
initial
$display ("mux_assign is instantiated");
endmodule
// Design #2: Multiplexer design uses a "case" statement to drive
// out signal
module mux_case (input a, b, sel,
output reg out);
always @ (a or b or sel) begin
case (sel)
0 : out = a;
1 : out = b;
endcase
end
// The initial display statement is used so that
// we know which design got instantiated from simulation
// logs
initial
$display ("mux_case is instantiated");
endmodule
// Top Level Design: Use a parameter to choose either one
module my_design ( input a, b, sel,
output out);
parameter USE_CASE = 0;
// Use a "generate" block to instantiate either mux_case
// or mux_assign using an if else construct with generate
generate
if (USE_CASE)
mux_case mc (.a(a), .b(b), .sel(sel), .out(out));
else
mux_assign ma (.a(a), .b(b), .sel(sel), .out(out));
endgenerate
endmodule
Banc de test
Testbench instancie le module de niveau supérieur my_design et définit le paramètre USE_CASE sur 1 afin qu'il instancie la conception en utilisant case
déclaration.
module tb;
// Declare testbench variables
reg a, b, sel;
wire out;
integer i;
// Instantiate top level design and set USE_CASE parameter to 1 so that
// the design using case statement is instantiated
my_design #(.USE_CASE(1)) u0 ( .a(a), .b(b), .sel(sel), .out(out));
initial begin
// Initialize testbench variables
a <= 0;
b <= 0;
sel <= 0;
// Assign random values to DUT inputs with some delay
for (i = 0; i < 5; i = i + 1) begin
#10 a <= $random;
b <= $random;
sel <= $random;
$display ("i=%0d a=0x%0h b=0x%0h sel=0x%0h out=0x%0h", i, a, b, sel, out);
end
end
endmodule
Lorsque le paramètre USE_CASE est 1, il ressort du journal de simulation que la conception du multiplexeur utilisant case
l'instruction est instanciée. Et lorsque USE_CASE vaut zéro, la conception du multiplexeur utilisant assign
l'instruction est instanciée. Ceci est visible à partir de l'instruction d'affichage qui est imprimée dans le journal de simulation.
// When USE_CASE = 1 ncsim> run mux_case is instantiated i=0 a=0x0 b=0x0 sel=0x0 out=0x0 i=1 a=0x0 b=0x1 sel=0x1 out=0x1 i=2 a=0x1 b=0x1 sel=0x1 out=0x1 i=3 a=0x1 b=0x0 sel=0x1 out=0x0 i=4 a=0x1 b=0x0 sel=0x1 out=0x0 ncsim: *W,RNQUIE: Simulation is complete. // When USE_CASE = 0 ncsim> run mux_assign is instantiated i=0 a=0x0 b=0x0 sel=0x0 out=0x0 i=1 a=0x0 b=0x1 sel=0x1 out=0x0 i=2 a=0x1 b=0x1 sel=0x1 out=0x1 i=3 a=0x1 b=0x0 sel=0x1 out=0x1 i=4 a=0x1 b=0x0 sel=0x1 out=0x1 ncsim: *W,RNQUIE: Simulation is complete.
Générer un cas
Un cas de génération permet aux modules, aux blocs initiaux et toujours d'être instanciés dans un autre module basé sur un case
expression pour sélectionner l'un des nombreux choix.
// Design #1: Half adder
module ha (input a, b,
output reg sum, cout);
always @ (a or b)
{cout, sum} = a + b;
initial
$display ("Half adder instantiation");
endmodule
// Design #2: Full adder
module fa (input a, b, cin,
output reg sum, cout);
always @ (a or b or cin)
{cout, sum} = a + b + cin;
initial
$display ("Full adder instantiation");
endmodule
// Top level design: Choose between half adder and full adder
module my_adder (input a, b, cin,
output sum, cout);
parameter ADDER_TYPE = 1;
generate
case(ADDER_TYPE)
0 : ha u0 (.a(a), .b(b), .sum(sum), .cout(cout));
1 : fa u1 (.a(a), .b(b), .cin(cin), .sum(sum), .cout(cout));
endcase
endgenerate
endmodule
Banc de test
module tb;
reg a, b, cin;
wire sum, cout;
my_adder #(.ADDER_TYPE(0)) u0 (.a(a), .b(b), .cin(cin), .sum(sum), .cout(cout));
initial begin
a <= 0;
b <= 0;
cin <= 0;
$monitor("a=0x%0h b=0x%0h cin=0x%0h cout=0%0h sum=0x%0h",
a, b, cin, cout, sum);
for (int i = 0; i < 5; i = i + 1) begin
#10 a <= $random;
b <= $random;
cin <= $random;
end
end
endmodule
Notez qu'étant donné qu'un demi-additionneur est instancié, cin n'a aucun effet sur les sorties sum et cout.
Journal de simulationncsim> run Half adder instantiation a=0x0 b=0x0 cin=0x0 cout=00 sum=0x0 a=0x0 b=0x1 cin=0x1 cout=00 sum=0x1 a=0x1 b=0x1 cin=0x1 cout=01 sum=0x0 a=0x1 b=0x0 cin=0x1 cout=00 sum=0x1 ncsim: *W,RNQUIE: Simulation is complete.
Verilog