Manufaturação industrial
Internet das coisas industrial | Materiais industriais | Manutenção e reparo de equipamentos | Programação industrial |
home  MfgRobots >> Manufaturação industrial >  >> Industrial programming >> Verilog

Dominando Blocos Generate do Verilog: Instanciação Dinâmica de Módulos e Design Condicional

Um bloco generate permite multiplicar instâncias de módulos ou realizar instanciação condicional de qualquer módulo. Ele fornece a capacidade de o design ser construído baseado em parâmetros Verilog. Essas instruções são particularmente convenientes quando a mesma operação ou instância de módulo precisa ser repetida múltiplas vezes ou se certo código tem que ser condicionalmente incluído baseado em parâmetros Verilog dados.

Um bloco generate não pode conter declarações de porta, parâmetro, specparam ou blocos specify. No entanto, outros itens de módulo e outros blocos generate são permitidos. Todas as instanciações generate são codificadas dentro de um module e entre as palavras-chave generate e endgenerate.

Instanciações geradas podem ter módulos, atribuições contínuas, blocos always ou initial e primitivas definidas pelo usuário. Existem dois tipos de construções generate - loops e condicionais.

Generate for loop

Um meio somador será instanciado N vezes em outro módulo de design de nível superior chamado my_design usando uma construção generate for loop. A variável de loop tem que ser declarada usando a palavra-chave genvar que diz à ferramenta que esta variável deve ser especificamente usada durante elaboração do bloco generate.


// Design para um meio somador
module ha (
    input a, b,
    output sum, cout
);
    assign sum = a ^ b;
    assign cout = a & b;
endmodule

// Um design de nível superior que contém N instâncias de meio somador
module my_design #(parameter N=4) (
    input [N-1:0] a, b,
    output [N-1:0] sum, cout
);
    // Declarar uma variável de loop temporária a ser usada durante
    // geração e não estará disponível durante simulação
    genvar i;

    // Generate for loop para instanciar N vezes
    generate
        for (i = 0; i < N; i = i + 1) begin
            ha u0 (a[i], b[i], sum[i], cout[i]);
        end
    endgenerate
endmodule

Testbench

O parâmetro do testbench é usado para controlar o número de instâncias de meio somador no design. Quando N é 2, my_design terá duas instâncias de meio somador.


module tb;
    parameter N = 2;
    reg [N-1:0] a, b;
    wire [N-1:0] sum, cout;

    // Instanciar design de nível superior com N=2 para que tenha 2
    // instâncias separadas de meios somadores e ambas recebam duas
    // entradas separadas
    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] e b[0] dão a saída sum[0] e cout[0] enquanto a[1] e b[1] dão a saída sum[1] e cout[1].

Log de Simulação

ncsim> 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

Veja que o RTL elaborado de fato tem duas instâncias de meio somador geradas pelo bloco generate.

Generate if

Mostrado abaixo está um exemplo usando um if else dentro de uma construção generate para selecionar entre duas diferentes implementações de multiplexador. O primeiro design usa uma instrução assign para implementar um mux enquanto o segundo design usa uma instrução case. Um parâmetro chamado USE_CASE é definido no módulo de design de nível superior para selecionar entre as duas escolhas.


// Design #1: Multiplexador usa "assign" para atribuir
// sinal out
module mux_assign (
    input a, b, sel,
    output out
);
    assign out = sel ? a : b;
    // A instrução initial display é usada para que
    // saibamos qual design foi instanciado dos logs
    // de simulação
    initial $display ("mux_assign é instanciado");
endmodule

// Design #2: Multiplexador usa "case" para conduzir
// sinal out
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
    // A instrução initial display é usada para que
    // saibamos qual design foi instanciado dos logs
    // de simulação
    initial $display ("mux_case é instanciado");
endmodule

// Design de Nível Superior: Use um parâmetro para escolher um
module my_design (
    input a, b, sel,
    output out
);
    parameter USE_CASE = 0;

    // Use um bloco "generate" para instanciar either mux_case
    // ou mux_assign usando um construto if else com 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

Testbench

Testbench instancia o módulo de nível superior my_design e define o parâmetro USE_CASE para 1 para que instancie o design usando instrução case.


module tb;
    // Declarar variáveis do testbench
    reg a, b, sel;
    wire out;
    integer i;

    // Instanciar design de nível superior e definir parâmetro USE_CASE para 1 para que
    // o design usando case statement seja instanciado
    my_design #(.USE_CASE(1)) u0 (
        .a(a), .b(b), .sel(sel), .out(out)
    );

    initial begin
        // Inicializar variáveis do testbench
        a <= 0; b <= 0; sel <= 0;

        // Atribuir valores aleatórios às entradas do DUT com algum atraso
        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

Quando o parâmetro USE_CASE é 1, pode ser visto no log de simulação que o design do multiplexador usando instrução case é instanciado. E quando USE_CASE é zero, o design do multiplexador usando instrução assign é instanciado. Isso é visível da instrução display que é impressa no log de simulação.

Log de Simulação

// Quando USE_CASE = 1
ncsim> run
mux_case é instanciado
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.

// Quando USE_CASE = 0
ncsim> run
mux_assign é instanciado
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.

Generate Case

Um generate case permite que módulos, blocos initial e always sejam instanciados em outro módulo baseado em uma expressão case para selecionar uma das muitas escolhas.


// Design #1: Meio somador
module ha (input a, b, output reg sum, cout);
    always @ (a or b) {cout, sum} = a + b;
    initial $display ("Instanciação de meio somador");
endmodule

// Design #2: Somador completo
module fa (input a, b, cin, output reg sum, cout);
    always @ (a or b or cin) {cout, sum} = a + b + cin;
    initial $display ("Instanciação de somador completo");
endmodule

// Design de nível superior: Escolha entre meio somador e somador completo
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

Testbench


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

Note que como um meio somador é instanciado, cin não tem nenhum efeito nas saídas sum e cout.

Log de Simulação

ncsim> run
Meio somador instanciação
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

  1. Domine o Verilog: Tutorial Completo para Projeto de Circuitos Digitais
  2. Dominando Concatenação Verilog: Guia Prático
  3. Verilog Essencial: Visão Geral Concisa
  4. Entendendo Atribuições em Verilog: Tipos, Sintaxe e Boas Práticas
  5. Verilog Blocking vs Non-Blocking: Diferenças Essenciais Explicadas
  6. Dominando Funções em Verilog para Design RTL Eficiente
  7. Entendendo Tasks em Verilog: Funções vs Tasks, Sintaxe e Uso
  8. Gerador de Clock Verilog: Guia de Design e Implementação
  9. Dominando Funções Matemáticas do Verilog para Design de Hardware Eficiente
  10. Dominando $timeformat do Verilog: Precisão e Controle de Exibição