Verilog generar bloque
Un generate
block permite multiplicar instancias de módulos o realizar una instanciación condicional de cualquier módulo. Proporciona la capacidad de que el diseño se construya en función de los parámetros de Verilog. Estas declaraciones son particularmente convenientes cuando la misma operación o instancia de módulo debe repetirse varias veces o si cierto código debe incluirse condicionalmente en función de determinados parámetros de Verilog.
Un generate
el bloque no puede contener puerto, parámetro, specparam
declaraciones o specify
bloques Sin embargo, se permiten otros elementos del módulo y otros bloques de generación. Todas las instancias generadas están codificadas dentro de un module
y entre las palabras clave generate
y endgenerate
.
Las instancias generadas pueden tener módulos, asignaciones continuas, always
o initial
bloques y primitivas definidas por el usuario. Hay dos tipos de construcciones de generación:bucles y condicionales.
- Generar bucle for
- Generar si más
- Generar caso
Generar bucle for
Un medio sumador será instanciado N veces en otro módulo de diseño de nivel superior llamado my_design usando un generate
para construcción de bucle. La variable de bucle debe declararse con la palabra clave genvar
que le dice a la herramienta que esta variable se usará específicamente durante la elaboración del bloque de generación.
// 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
Banco de pruebas
El parámetro testbench se usa para controlar el número de instancias de medio sumador en el diseño. Cuando N es 2, my_design tendrá dos instancias de medio sumador.
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] y b[0] dan la salida sum[0] y cout[0] mientras que a[1] y b[1] dan la salida sum[1] y cout[1].
Registro de simulaciónncsim> 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
Vea que RTL elaborado de hecho tiene dos instancias de medio sumador generadas por el generate
bloquear.
Generar si
A continuación se muestra un ejemplo usando un if else
dentro de un generate
construir para seleccionar entre dos implementaciones de multiplexor diferentes. El primer diseño usa un assign
declaración para implementar un mux mientras que el segundo diseño usa un case
declaración. Un parámetro llamado USE_CASE se define en el módulo de diseño de nivel superior para seleccionar entre las dos opciones.
// 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
Banco de pruebas
Testbench instancia el módulo de nivel superior my_design y establece el parámetro USE_CASE en 1 para que instancia el diseño usando case
declaración.
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
Cuando el parámetro USE_CASE es 1, se puede ver en el registro de simulación que el diseño del multiplexor usa case
se instancia la declaración. Y cuando USE_CASE es cero, el diseño del multiplexor usa assign
se instancia la declaración. Esto es visible en la declaración de visualización que se imprime en el registro de simulación.
// 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.
Generar caso
Un caso de generación permite instanciar módulos, bloques iniciales y siempre en otro módulo basado en un case
expresión para seleccionar una de las muchas opciones.
// 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
Banco de pruebas
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
Tenga en cuenta que debido a que se instancia un medio sumador, cin no tiene ningún efecto en las salidas sum y cout.
Registro de simulaciónncsim> 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