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