Instanciação de Módulos Verilog e Conexão de Portas

Fundamentos da Instanciação de Módulos

No Verilog, a hierarquia de um projeto é construída através da instanciação de módulos. Um módulo pode ser instanciado dentro de outro, desde que todos os módulos utilizados pertençam ao mesmo projeto e o compilador possa localizá-los. É crucial entender que o código de um módulo não é escrito dentro do corpo de outro módulo; eles são unidades separadas que são conectadas externamente.

Existem duas abordagens principais para conectar sinais aos pinos de um módulo instanciado: por posição e por nome. Dominar ambas é essencial para um design Verilog eficaz.

Conectando Sinais aos Pinos de Módulo

A seguir, detalhamos as duas metodologias com exemplos práticos:

Conexão por Posição

Este método de conexão é análogo à chamada de funções em linguagens como C, onde a ordem dos argumentos é determinante. Ao instanciar um módulo, os sinais externos são conectados aos pinos do módulo instanciado sequencialmente, da esquerda para a direita, conforme a declaração de pinos do módulo.


modulo_bloco exemplo_instancia_posicional (
    sinal_externo_A, // Conectado ao primeiro pino (ex: in1)
    sinal_externo_B, // Conectado ao segundo pino (ex: in2)
    sinal_externo_C  // Conectado ao terceiro pino (ex: out)
);

Neste exemplo, uma instância do tipo modulo\_bloco é criada com o nome exemplo\_instancia\_posicional. O sinal\_externo\_A é ligado ao primeiro pino do módulo, sinal\_externo\_B ao segundo, e assim por diante. A desvantagem dessa sintaxe é que qualquer alteração na lista de pinos do módulo (modulo\_bloco) exigirá a atualização de todas as suas instanciações para manter a correta correspondência dos pinos.

Conexão por Nome

A conexão por nome oferece maior robustez, pois os sinais são conectados aos pinos do módulo através de seus identificadores. Isso significa que a ordem na lista de conexões é irrelevante, e o design se torna mais resistente a modificações na interface do módulo. Embora mais verbosa, essa abordagem é frequentemente preferida em projetos maiores.


modulo_bloco exemplo_instancia_nomeada (
    .saida_res(sinal_externo_C), // Conecta sinal_externo_C ao pino "saida_res"
    .entrada_p1(sinal_externo_A), // Conecta sinal_externo_A ao pino "entrada_p1"
    .entrada_p2(sinal_externo_B)  // Conecta sinal_externo_B ao pino "entrada_p2"
);

Aqui, a instância exemplo\_instancia\_nomeada do tipo modulo\_bloco tem seus pinos conectados explicitamente. Observe o ponto (.) antes do nome do pino do módulo. A ordem .saida\_res(...), .entrada\_p1(...), etc., pode ser alterada sem afetar a funcionalidade, pois a conexão é feita pelo nome do pino.

Exemplos de Implementação em Verilog

Instanciação Básica

Utilizando a conexão por nome para um módulo simples:


module modulo_principal (input sinal_in_a, input sinal_in_b, output sinal_out_res);
    bloco_logico_a meu_bloco (
        .entrada_port1(sinal_in_a),
        .entrada_port2(sinal_in_b),
        .saida_port(sinal_out_res)
    );
endmodule

Conexão de Pinos por Posição

Se a ordem dos pinos do módulo bloco\_logico\_b for (saida\_um, saida\_dois, in1, in2, in3, in4), a instanciação seria:


module modulo_topo_posicao ( 
    input entrada_w, 
    input entrada_x, 
    input entrada_y,
    input entrada_z,
    output saida_w1,
    output saida_w2
);
    bloco_logico_b instancia_b (saida_w1, saida_w2, entrada_w, entrada_x, entrada_y, entrada_z);
endmodule

Conexão de Pinos por Nome

Utilizando a mesma definição de pinos para bloco\_logico\_b:


module modulo_topo_nome ( 
    input entrada_w, 
    input entrada_x, 
    input entrada_y,
    input entrada_z,
    output saida_w1,
    output saida_w2
);
    bloco_logico_b instancia_b_nomeada (
        .saida_um(saida_w1),
        .saida_dois(saida_w2),
        .in1(entrada_w),
        .in2(entrada_x),
        .in3(entrada_y),
        .in4(entrada_z)
    );
endmodule

Encadeamento de Módulos: Três Flip-Flops D

Para construir um registrador de deslocamento simples usando três flip-flops D em cascata, onde a saída de um alimenta a entrada do próximo:


module registrador_deslocamento (
    input clock_principal, 
    input dado_entrada_d, 
    output saida_final_q
);
    wire sinal_temp_1;
    wire sinal_temp_2;

    // Instanciação por posição (assumindo pinos: clk, d, q)
    flip_flop_d ff_estagio1(clock_principal, dado_entrada_d, sinal_temp_1);
    flip_flop_d ff_estagio2(clock_principal, sinal_temp_1, sinal_temp_2);
    flip_flop_d ff_estagio3(clock_principal, sinal_temp_2, saida_final_q);

    /*
    // Alternativamente, por nome:
    flip_flop_d ff_estagio1_nomeado(.clk(clock_principal), .d(dado_entrada_d), .q(sinal_temp_1));
    flip_flop_d ff_estagio2_nomeado(.clk(clock_principal), .d(sinal_temp_1), .q(sinal_temp_2));
    flip_flop_d ff_estagio3_nomeado(.clk(clock_principal), .d(sinal_temp_2), .q(saida_final_q));
    */
endmodule

Módulos com Vetores (Registrador de 8 bits e Mux)

Este exemplo demonstra a instanciação de módulos que operam com sinais vetoriais e a utilização de um multiplexador para selecionar entre diferentes estágios de um registrador de deslocamento de 8 bits.


module registrador_mux_8bits ( 
    input clk_mestre, 
    input [7:0] dados_entrada, 
    input [1:0] seletor_saida, 
    output [7:0] dados_final 
);
    wire [7:0] valor_reg1, valor_reg2, valor_reg3;

    // Instanciação de flip-flops D de 8 bits em cascata
    meu_dff_8bits reg_inst0 (clk_mestre, dados_entrada, valor_reg1);
    meu_dff_8bits reg_inst1 (clk_mestre, valor_reg1, valor_reg2);
    meu_dff_8bits reg_inst2 (clk_mestre, valor_reg2, valor_reg3);

    // Multiplexador para selecionar a saída com base no seletor
    always @(*)
    begin
        case(seletor_saida)
            2'b00: dados_final = dados_entrada; // Direto
            2'b01: dados_final = valor_reg1;     // Após 1 ciclo
            2'b10: dados_final = valor_reg2;     // Após 2 ciclos
            2'b11: dados_final = valor_reg3;     // Após 3 ciclos
            default: dados_final = 8'hXX;        // Valor indefinido para entradas não mapeadas
        endcase
    end
endmodule

Somador de Ondulação (Ripple-Carry Adder)

Um somador de 32 bits pode ser construído encadeando dois módulos de somadores de 16 bits (somador\_16bits). A saída de carry de um estágio alimenta o carry de entrada do próximo.


module somador_32bits_ripple (
    input [31:0] operando_a,
    input [31:0] operando_b,
    output [31:0] resultado_soma
);
    wire carry_out_parte1; // Carry de saída do primeiro somador de 16 bits
    // 'carry_out_parte2' não é estritamente necessário se não for usado externamente

    // Instancia o primeiro somador de 16 bits para os 16 bits menos significativos
    somador_16bits somador_baixo (
        operando_a[15:0],
        operando_b[15:0],
        1'b0, // carry-in inicial é 0 para o bit menos significativo
        resultado_soma[15:0],
        carry_out_parte1
    );

    // Instancia o segundo somador de 16 bits para os 16 bits mais significativos
    somador_16bits somador_alto (
        operando_a[31:16],
        operando_b[31:16],
        carry_out_parte1, // Carry-in é o carry-out do estágio anterior
        resultado_soma[31:16],
        // carry_out_parte2 // Carry de saída final, opcional para esta implementação
    );
endmodule

Definição de Somador Completo e Uso

Este exemplo mostra a definição de um módulo de somador completo de 1 bit (somador\_completo\_1bit) e como ele pode ser implictiamente usado para criar módulos maiores (como somador\_16bits no contexto do Hdlbits, onde add16 já estaria definido internamente).


module somador_32bits_completo (
    input [31:0] val_a,
    input [31:0] val_b,
    output [31:0] soma_final
);
    wire carry_in_primeiro;
    wire carry_saida_segmento1;
    wire [15:0] soma_segmento1, soma_segmento2;

    assign carry_in_primeiro = 1'b0; // O carry-in inicial é zero

    // Instancia o primeiro somador de 16 bits
    somador_16bits bloco_somador0 (val_a[15:0], val_b[15:0], carry_in_primeiro, soma_segmento1, carry_saida_segmento1);

    // Instancia o segundo somador de 16 bits
    somador_16bits bloco_somador1 (val_a[31:16], val_b[31:16], carry_saida_segmento1, soma_segmento2, /*cout2*/);

    assign soma_final = {soma_segmento2, soma_segmento1};
endmodule

// Definição de um Somador Completo de 1 bit (muitas vezes implicitamente usado em 'somador_16bits')
module somador_completo_1bit (
    input bit_a, 
    input bit_b, 
    input carry_in_bit,
    output bit_soma, 
    output carry_out_bit
);
    assign bit_soma = bit_a ^ bit_b ^ carry_in_bit;
    assign carry_out_bit = (bit_a & bit_b) | (bit_a & carry_in_bit) | (bit_b & carry_in_bit);
endmodule

Somador Carry-Select

Para otimizar a velocidade, um somador carry-select calcula o resultado para ambos os casos de carry-in (0 e 1) em paralelo e seleciona o resultado correto após o carry-out do estágio anterior ser determinado.


module somador_carry_select_32bits (
    input [31:0] entrada_a,
    input [31:0] entrada_b,
    output [31:0] soma_completa
);
    wire cin_estagio1_base, cin_estagio2_0, cin_estagio2_1;
    wire cout_estagio1;
    wire [15:0] resultado_estagio1, resultado_estagio2_cin0, resultado_estagio2_cin1;
    wire cout_estagio2_cin0, cout_estagio2_cin1;

    assign cin_estagio1_base = 1'b0; // Carry-in inicial do primeiro segmento
    assign cin_estagio2_0 = 1'b0;  // Carry-in assumido como 0 para o segundo segmento
    assign cin_estagio2_1 = 1'b1;  // Carry-in assumido como 1 para o segundo segmento

    // Primeiro somador de 16 bits para os LSBs
    somador_16bits somador_lsb (
        entrada_a[15:0],
        entrada_b[15:0],
        cin_estagio1_base,
        resultado_estagio1,
        cout_estagio1
    );

    // Segundo somador de 16 bits para os MSBs, assumindo carry-in = 0
    somador_16bits somador_msb_cin0 (
        entrada_a[31:16],
        entrada_b[31:16],
        cin_estagio2_0,
        resultado_estagio2_cin0,
        cout_estagio2_cin0
    );

    // Segundo somador de 16 bits para os MSBs, assumindo carry-in = 1
    somador_16bits somador_msb_cin1 (
        entrada_a[31:16],
        entrada_b[31:16],
        cin_estagio2_1,
        resultado_estagio2_cin1,
        cout_estagio2_cin1
    );
    
    // Seleciona o resultado correto para os MSBs com base no carry-out do LSB
    always @(*)
    begin
        case(cout_estagio1)
            1'b0: soma_completa = {resultado_estagio2_cin0, resultado_estagio1};
            1'b1: soma_completa = {resultado_estagio2_cin1, resultado_estagio1};
        endcase
    end

endmodule

Somador/Subtrator

Um circuito que pode realizar tanto soma quanto subtração com base em um bit de controle sub.


module somador_subtrator_32bits (
    input [31:0] valor_x,
    input [31:0] valor_y,
    input operacao_subtrair, // 1 para subtração, 0 para soma
    output [31:0] resultado_operacao
);
    wire carry_inicial;
    wire carry_saida_baixo, carry_saida_alto;
    wire [15:0] soma_baixo, soma_alto;
    wire [31:0] valor_y_invertido; // y ou complemento de 1 de y

    // Para subtração (X - Y), calcula X + (~Y + 1)
    // Se operacao_subtrair = 1, valor_y_invertido = ~valor_y
    // Se operacao_subtrair = 0, valor_y_invertido = valor_y
    assign valor_y_invertido = valor_y ^ {32{operacao_subtrair}};
    assign carry_inicial = operacao_subtrair; // Para adicionar o '+1' na subtração (complemento de 2)

    // Primeiro estágio do somador/subtrator para os 16 bits inferiores
    somador_16bits bloco_baixo (
        valor_x[15:0],
        valor_y_invertido[15:0],
        carry_inicial,
        soma_baixo,
        carry_saida_baixo
    );

    // Segundo estágio do somador/subtrator para os 16 bits superiores
    somador_16bits bloco_alto (
        valor_x[31:16],
        valor_y_invertido[31:16],
        carry_saida_baixo, // O carry de saída do estágio inferior se torna o carry-in
        soma_alto,
        carry_saida_alto
    );
    
    assign resultado_operacao = {soma_alto, soma_baixo};

endmodule

Tags: Verilog HDL DesignDigital modulos Instanciacao

Publicado em 7-6 00:06