Começando com Verilog 2 - Instanciação de Módulos

Opa, eae?


Curti muito a repercussão do último post e em especial muito obrigado a He4rt Developers, essa comunidade que participo faz uns 3 anos e que é sensacional, não deixe de entrar.

Porém, acredito que ainda tem mais algumas coisas a serem abordadas para que você comece bem com Verilog e principalmente tenha todo o conhecimento para gabaritar o HDLbits.

Bom, como o título já diz, esta é a parte dois do post “Começando com Verilog” então caso você não tenha lido a primeira parte, cola aqui nesse link primeiro: https://blog.marlonhenq.dev/posts/6/comecando-com-verilog.php



Aumentando a complexidade ou subindo a abstração com a instanciação de módulos


Uma das principais coisas que acabei não dizendo no último post é que módulos podem instanciar outros módulos.

Os que possuem olhares muito atentos podem até ter percebido isso quando falei sobre Verilog Estrutural “O Verilog Estrutural cria sua lógica chamando portas como módulos.”, porém irei explicar mais detalhadamente agora.

Uma forma muito comum de se fazer um multiplexador em Verilog é usando o operador ternário:

module top_module(
    input a, b, sel,
    output out
);

    assign out = sel ? b:a;
endmodule

Caso você não saiba o que é um multiplexador, ele nada mais é que um permutador de entradas. No caso os inputs “a” e “b” são as entradas do nosso multiplexador, a “sel” é a nossa porta seletora, caso “sel” receba 0 o que vem por “a” é direcionado a saída “out”, caso “sel” receba 1 o que vem de “b” será direcionado a “out”.

Ele pode ser descrito pela tabela:

A B Sel Out
0 0 0 0
0 0 1 0
0 1 0 0
0 1 1 1
1 0 0 1
1 0 1 0
1 1 0 1
1 1 1 1

Aí está a representação do último código com “a” recebendo 1 (representado pelo verde), “b” recebendo 0 (vermelho) e “sel” recebendo 0, assim o valor de “a” é direcionado a “out”:


Responsive image

Mudando a entrada de “sel” para 1, temos o que vem por “b” indo para “out”:


Responsive image

Mas e se agora quisermos um multiplexador de 4 entradas?

Obviamente, vamos ter que aumentar nossas entradas para agora “a”, “b”, “c” e “d” nossa seleção “sel” também deve aumentar para 2 bits.

E um código que poderia resolver isso seria:

module top_module( 
    input a, b, c, d,
    input [1:0] sel,
    output out ); 
	
    assign out  = sel[1] ? (sel[0] ? d : c) : (sel[0] ? b : a);
endmodule

A representação deste código é:


Responsive image

De fato funciona, mas a legibilidade de ternários encadeados não é muito boa, e se quisermos então um multiplexador de 8 entradas? Ai sim, temos um grande problema, e uma forma mais elegante de fazer isso é instanciar módulos por outros módulos.

Para instanciar módulos dentro de outros módulos em Verilog a sintaxe é “nome_do_modulo nome_da_instanciação (entrada1, entrada2,… saida1…);”.

Vamos lá, tendo o multiplexador de 2 entradas podemos fazer um multiplexador de 4 entradas instanciando o de 2 entradas três vezes, segue o código:

module mux2( //multiplexador de 2 entradas já apresentado
    input a, b, sel,
    output out ); 
	
    assign out  = sel ? b:a;
endmodule

module mux4(//multiplexador de 4 entradas
    input a, b, c, d, 
    input [1:0] sel,
    output out ); 

    wire fio1, fio2;

    mux2 multiplexador1 (a, b, sel[0], fio1);
    mux2 multiplexador2 (c, d, sel[0], fio2);

    mux2 multiplexadorFinal (fio1, fio2, sel[1], out);

endmodule

Representação gráfica do modulo mux4:


Responsive image

Outra forma de instanciar módulos (mais verbosa, porém que prefiro) é a de repetir o nome das portas do módulo que está sendo instanciado dessa forma:

module mux4(
    input a, b, c, d, 
    input [1:0] sel,
    output out ); 

    wire fio1, fio2;

    mux2 multiplexador1 (.a(a), .b(b), .sel(sel[0]), .out(fio1));
    mux2 multiplexador2 (.a(c), .b(d), .sel(sel[0]), .out(fio2));

    mux2 multiplexadorFinal (.a(fio1), .b(fio2), .sel(sel[1]), .out(out));

endmodule

Assim, além de ficar mais claro como está sendo o roteamento, não é necessário utilizar a ordem das entradas e saídas do módulo original, é inclusive possível suprimir algumas destas (caso não for necessário para algum tipo de instanciação).



Aumentando muito a complexidade com a instanciação de N módulos via Generate For


Outro circuito muito comum é o de somador binário, ele basicamente tem 3 entradas, os dois dígitos que ira receber, e a entrada de “vai um” (cin), de saídas temos a saída da soma, além de uma saída de “vai um” (cout), para um próximo somador.

Um código para um somador binário de um único dígito é:

module adder(
    input a,b,
    input cin,
    output cout,
	output sum );
	
    assign sum  = (a ^ b ^ cin);
    assign cout = ((a & b) | (b & cin) | (cin & a));
endmodule

O somador binário de um bit é graficamente representado assim:


Responsive image

Um somador de 2 bits a partir da instanciação do somador simples pode ser:

module adder2(
    input [1:0] a, b,
    input cin,
    output cout,
    output [1:0] sum );

    adder ad1 (a[0], b[0], cin, coutIntern, sum[0]);
    adder ad2 (a[1], b[1], coutIntern, cout, sum[1]);

endmodule

E graficamente o somador de 2 bits fica assim:


Responsive image

E se agora quisermos um somador de 100 bits? Acho que seria muito trabalho repetir essas linhas 100 vezes, né?
E é mesmo, mas temos uma forma mais elegante de fazer isso, o Generate For.

O Generate nada mais é que um bloco onde se pode executar alguns comandos que após realizarem suas condições uma (ou mais) instância(s) de módulo(s) pode(m) ser(em) incorporada(s) (ou não) ao circuito.

Dentro do Generate pode se utilizar “ifs”, “cases” e (os mais utilizados) os “fors” que realizarão loops os quais cada interação pode instanciar módulos.

Falando pode parecer difícil, então vamos direto para o código que é bastante autoexplicativo:

module adder100( 
    input [99:0] a, b,
    input cin,
    output [99:0] cout,
    output [99:0] sum );
    
    genvar i;
    
    adder ad0(a[0], b[0], cin, cout[0], sum[0]);
	
     generate
     	for (i = 1; i < 100; i = i + 1) begin
        		adder ad (a[i], b[i], cout[i-1], cout[i], sum[i]);
    	end
     endgenerate
    

endmodule

Após a criação do nosso módulo “adder100” e a declaração das nossas entradas e saídas, temos a criação de uma variável de geração “genvar” com o nome de “i”.

Após isso, o primeiro módulo de adder é instanciado (ad0), já que este precisa receber o valor de “cin” ele acaba por ficar fora do nosso loop de geração.
Porém, agora todos os nossos demais 99 módulos irão interagir com vetores, ou seja, podem ser gerados por um loop de geração.

Assim, vem o nosso for com declaração muito próxima de C, onde a cada interação é instanciado um módulo adder de um bit.

E que fique a dica, essa é a resposta para o exercício Adder100i do HDLBits!

A visualização para esse circuito graficamente é:


Responsive image

Éhh… Bom, esse circuito é grande de mais para se visualizar e testar graficamente pelo DigitalJS. Em uma tela cheia o máximo que conseguimos ver é 15 instancias (de 100) e meu notebook sofreu para gerar essa simulação.

O que faremos para testar esse tipo de circuito, então?

Testes! Mais especificamente neste caso chamamos de “TestBench”, porém irei deixar isso para o próximo post!



Isso é tudo!


Como sempre, obrigado por ter lido este post até aqui!

Demais dúvidas, comentários e sugestões podem ser enviados aqui embaixo (para o pessoal do DevTo) ou lá no meu Twitter @marlonhenq.

Flw!