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_nameprecisa bater com o nome de uma declaraçãomoduleem 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_connectionsconectam 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: test → full_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 ideclara uma variável de loop usável emgenerate. Não é um sinal de runtime - só existe em tempo de elaboração.generate ... endgenerateenvolve o loop. Algumas ferramentas aceitam loops generate sem a palavra-chavegenerateexplícita, mas escrevê-la torna a intenção óbvia.begin : invert_looprotula 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.