Verilog blok genereren
Een generate
block maakt het mogelijk om module-instanties te vermenigvuldigen of voorwaardelijke instantie van een module uit te voeren. Het biedt de mogelijkheid om het ontwerp te bouwen op basis van Verilog-parameters. Deze instructies zijn met name handig wanneer dezelfde bewerking of module-instantie meerdere keren moet worden herhaald of als bepaalde code voorwaardelijk moet worden opgenomen op basis van bepaalde Verilog-parameters.
Een generate
blok mag geen poort, parameter, specparam
bevatten aangiften of specify
blokken. Andere module-items en andere genererende blokken zijn echter toegestaan. Alle genereren instantiaties zijn gecodeerd binnen een module
en tussen de trefwoorden generate
en endgenerate
.
Gegenereerde instantiaties kunnen modules, doorlopende toewijzingen, always
. hebben of initial
blokken en door de gebruiker gedefinieerde primitieven. Er zijn twee soorten genereer-constructies:lussen en voorwaardelijke.
- Genereer for loop
- Genereer indien anders
- Case genereren
Genereer for loop
Een halve opteller wordt N keer geïnstantieerd in een andere ontwerpmodule op het hoogste niveau, my_design genaamd, met een generate
voor lusconstructie. De lusvariabele moet worden gedeclareerd met het trefwoord genvar
die de tool vertelt dat deze variabele specifiek moet worden gebruikt tijdens het uitwerken van het genereerblok.
// 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
Testbank
De testbench-parameter wordt gebruikt om het aantal halve optellerinstanties in het ontwerp te regelen. Als N 2 is, heeft my_design twee gevallen van halve opteller.
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] en b[0] geven de output sum[0] en cout[0] terwijl a[1] en b[1] de output sum[1] en cout[1] geven.
Simulatielogboekncsim> 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
Zie dat uitgewerkte RTL inderdaad twee halve adder-instanties heeft die worden gegenereerd door de generate
blok.
Genereren als
Hieronder wordt een voorbeeld weergegeven met een if else
in een generate
constructie om te kiezen tussen twee verschillende multiplexerimplementaties. Het eerste ontwerp gebruikt een assign
statement om een mux te implementeren terwijl het tweede ontwerp een case
. gebruikt uitspraak. Een parameter genaamd USE_CASE is gedefinieerd in de ontwerpmodule op het hoogste niveau om tussen de twee keuzes te kiezen.
// 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
Testbank
Testbench maakt de module op het hoogste niveau my_design en stelt de parameter USE_CASE in op 1 zodat het ontwerp wordt geïnstantieerd met behulp van case
verklaring.
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
Wanneer de parameter USE_CASE 1 is, kan uit het simulatielogboek worden gezien dat de multiplexer ontwerpt met behulp van case
verklaring wordt geconcretiseerd. En wanneer USE_CASE nul is, ontwerpt de multiplexer met assign
verklaring wordt geconcretiseerd. Dit is te zien aan de weergaveverklaring die wordt afgedrukt in het simulatielogboek.
// 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.
Case genereren
Met een genereercase kunnen modules, initiële en altijd blokken worden geïnstantieerd in een andere module op basis van een case
expressie om een van de vele keuzes te selecteren.
// 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
Testbank
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
Merk op dat omdat een halve opteller is geïnstantieerd, cin geen enkel effect heeft op de outputs som en cout.
Simulatielogboekncsim> 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