Menu

Module Instantiation em Verilog: Conectando submódulos

Como instanciar um module dentro de outro, a diferença entre conexões de port nomeadas e posicionais e os padrões de múltiplas instâncias que você vai usar para construir designs reais.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Modules dentro de modules

Um design Verilog é uma árvore de modules. O module de nível superior (seu testbench, ou o wrapper de nível superior de um chip) instancia modules de nível mais baixo, que instanciam modules ainda mais baixos, lá embaixo até primitivas de gate fornecidas pelo fabricante. Instanciação é a sintaxe para esse aninhamento.

Você já viu a forma - usamos em Seu primeiro module:

and_gate dut(.a(a), .b(b), .y(y));

Essa linha estampa uma única instância de and_gate, a nomeia como dut, e conecta seus ports a sinais locais. Vamos desempacotar cada pedaço.

A forma de uma instanciação

module_name instance_name (port_connections);
  • module_name precisa bater com o nome de uma declaração module em algum lugar do seu projeto. Verilog é case-sensitive.
  • instance_name é um rótulo que você escolhe - geralmente descrevendo o papel que essa instância tem. Você o usará em caminhos hierárquicos e em visualizações de forma de onda.
  • port_connections conectam os ports da instância a sinais locais. Há duas formas de escrever isso.

Conexões de port nomeadas (use estas)

A forma nomeada parece com:

my_module instance_name(
    .clk    (clk),
    .reset  (reset_n),
    .data_in(in_bus),
    .data_out(out_bus),
    .valid  (out_valid)
);

Cada par .port(signal) diz "conecte o port desta instância chamado port ao sinal local chamado signal". A ordem não importa. Se você adicionar um novo port à declaração do module, instanciações existentes não quebram desde que você dê ao novo port um valor padrão ou atualize cada chamada.

Duas notas práticas:

  • O nome do port (à esquerda dos parênteses) precisa bater exatamente com a declaração do module.
  • O nome do sinal (dentro dos parênteses) é local de onde quer que a instanciação esteja - geralmente o module pai.

Se um port está desconectado, deixe vazio por dentro: .optional_port(). O sinal flutua (z) dentro da instância. Algumas ferramentas de síntese avisam; a maioria aceita.

Conexões de port posicionais (evite estas)

A forma mais concisa lista sinais na ordem da lista de ports:

my_module instance_name(clk, reset_n, in_bus, out_bus, out_valid);

Isso é mais curto mas frágil. Reordene a lista de ports do module (um refactor real que acontece) e cada instanciação posicional silenciosamente fica mal conectada. Não recorra a posicional a menos que a lista de ports tenha exatamente um ou dois membros e seja improvável de mudar.

Onde ainda é aceitável: pequenos modules utilitários onde a ordem dos ports faz parte da API. Um gate de duas entradas está bem posicional. Um controlador de memória de 30 ports está pedindo problema.

Um exemplo hierárquico completo

Essa é uma hierarquia de três níveis real: testfull_adder → duas instâncias half_adder. Cada instância tem sua própria cópia dos gates dentro de half_adder; a ferramenta de síntese vai emitir um circuito por instanciação.

Múltiplas instâncias do mesmo module

Quando você instancia o mesmo module várias vezes, cada instância é hardware independente. Não compartilham estado. Não compartilham gates. Imagine cada instância como uma cópia nova estampada do design.

adder add0(.a(a0), .b(b0), .sum(s0));
adder add1(.a(a1), .b(b1), .sum(s1));
adder add2(.a(a2), .b(b2), .sum(s2));
adder add3(.a(a3), .b(b3), .sum(s3));

São quatro somadores separados rodando em paralelo. Se adder contivesse um register, cada instância teria sua própria cópia desse register, com seu próprio estado.

Loops generate: Estampando hardware repetido

Escrever quatro instâncias à mão está ok. Escrever 64 é tedioso. O bloco generate deixa o elaborador fazer a digitação para você:

Três pedaços de sintaxe nova:

  • genvar i declara uma variável de loop usável em generate. Não é um sinal de runtime - só existe em tempo de elaboração.
  • generate ... endgenerate envolve o loop. Algumas ferramentas aceitam loops generate sem a palavra-chave generate explícita, mas escrevê-la torna a intenção óbvia.
  • begin : invert_loop rotula o escopo do generate. O rótulo se torna parte do nome hierárquico de cada instância gerada (dut.invert_loop[0].u_inv, dut.invert_loop[1].u_inv, etc.).

O sintetizador desenrola o loop e produz WIDTH cópias de bit_inverter. Cada cópia é hardware independente.

Sobrescritas de parameter na instanciação

Se o module tem parameters, você pode sobrescrevê-los com #(.PARAM(value)) entre o nome do module e o nome da instância:

counter #(.WIDTH(16)) c16 (.clk(clk), .count(out16));
counter #(.WIDTH(32)) c32 (.clk(clk), .count(out32));

Ambas as instâncias usam o mesmo fonte counter mas têm larguras diferentes. Cobrimos a sintaxe em Parameters; ela encaixa limpinha na instanciação.

Nomes hierárquicos

Uma vez que você tem uma hierarquia, cada sinal tem um caminho hierárquico:

test.dut.ha0.sum

Isso lê como: no module test, dentro da instância dut, dentro da instância ha0, o sinal chamado sum. Você verá esses caminhos em visualizadores de forma de onda, mensagens de erro e na chamada $display ocasional que cutuca profundamente em um submódulo a partir de um testbench:

$display("internal carry1 = %b", dut.carry1);

Referências hierárquicas como essa são só para testbenches e debug - RTL sintetizável não alcança dentro de outros modules.

Erros comuns

Nome de port incorreto. .clk_in(clk) conecta o clk local a um port chamado clk_in. Se o port do module na verdade é clk, o parser vai te avisar (algumas ferramentas mais claramente que outras).

Largura incompatível em um port. Conectar um sinal de 4 bits a um port de 8 bits zero-extende silenciosamente; o inverso trunca silenciosamente. A maioria das ferramentas avisa; se você não vê avisos, olhe mais de perto.

Esquecer o # do parameter. counter (.WIDTH(8)) c(.clk(clk)) parece uma sobrescrita mas não é - o parser tenta tratar (.WIDTH(8)) como uma conexão de port e falha. Correto: counter #(.WIDTH(8)) c(.clk(clk)).

Reusar um nome de instância. Duas instâncias não podem ter o mesmo nome no mesmo escopo. A mensagem de erro geralmente é clara; a tentação de copy-paste é o que te pega.

O que vem a seguir

Você agora consegue conectar modules em uma hierarquia real. O próximo doc arredonda o lado estrutural do Verilog - Continuous Assignment - e vai mais a fundo na declaração assign que viemos usando livremente desde o primeiro capítulo.

Perguntas frequentes

Como instancio um module em Verilog?

Escreva o nome do module, depois um nome de instância, depois uma lista entre parênteses de conexões de port: my_module instance_name(.port(signal), ...);. O estilo mais comum usa conexões nomeadas (.port(signal)), que combinam por nome de port independentemente da ordem. O estilo posicional mais conciso (my_module instance(signal1, signal2)) depende da ordem da lista de ports e é perigoso de manter.

Qual a diferença entre conexões de port nomeadas e posicionais?

Conexões posicionais listam sinais na mesma ordem da lista de ports do module - o primeiro sinal conecta no primeiro port, segundo no segundo, e assim por diante. Conexões nomeadas usam .port_name(signal_name), casando por nome. Nomeada é verbosa mas imune a reordenação de ports e se autodocumenta no ponto de chamada. Use nomeada para qualquer coisa além de dois ou três ports.

Posso instanciar o mesmo module Verilog várias vezes?

Sim - esse é o ponto principal. Cada instância é hardware independente com seu próprio estado. Se você tem um module adder, pode instanciá-lo 64 vezes em uma unidade SIMD, cada uma com entradas diferentes. O loop generate é a sintaxe canônica quando as instâncias são similares e indexadas.

O que é um bloco generate em Verilog?

generate ... endgenerate é uma construção em tempo de compilação que estampa hardware repetido. Um loop for dentro de generate cria N instâncias do que estiver no corpo. generate roda em tempo de elaboração, antes que a simulação comece - não é um loop em tempo de execução, é um gerador de código para o sintetizador.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR