DEV Community

Cover image for Começando com Verilog 2 - Instanciação de Módulos
Marlon
Marlon

Posted on • Updated on • Originally published at blog.marlonhenq.dev

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://dev.to/marlonhenq/comecando-com-verilog-13n0

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
Enter fullscreen mode Exit fullscreen mode

Obs: Olha ai uma nova forma de declarar os inputs e outputs, no post passado eu repetia sempre a palavra input e output para cada entrada/saida, mas caso elas sejam iguais (isto é, possuem a mesma quantidade de bits) você pode apenas colocar virgulas (como sempre Verilog possui várias formas de fazer a mesma coisa).

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”:

Image description

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

Image description

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
Enter fullscreen mode Exit fullscreen mode

A representação deste código é:

Image description

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
Enter fullscreen mode Exit fullscreen mode

Representação gráfica do modulo mux4:

Image description

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Image description

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
Enter fullscreen mode Exit fullscreen mode

Obs: Olha ai também uma forma nova de se declarar um wire! O fio “coutIntern” foi declarado de forma implícita, já que ele apenas é citado já sendo usado pelos módulos mesmo não sendo declarado formalmente com “wire coutIntern;” anteriormente.
Contudo, fique atento, apenas wires de um único bit podem ser declarados dessa forma.

E graficamente o somador de 2 bits fica assim:

Image description

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
Enter fullscreen mode Exit fullscreen mode

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.

Obs: A utilização do for pode dar a entender a ideia de linguagens de programação de que cada interação é adicionado um módulo ao longo do tempo, mas isso é apenas uma automação para que não seja necessário escrever 100 linhas quase iguais de código.
No final, o que teremos é 100 módulos rodando ao mesmo tempo, como se fossem 100 CIs de adição binária em uma mesma placa. Blz?

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

A visualização para esse circuito graficamente é:

Image description

É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!

Top comments (3)

Collapse
 
cherryramatis profile image
Cherry Ramatis

revivendo meus pesadelos com verilog, otimo conteudo primo <3

Collapse
 
marlonhenq profile image
Marlon

Que isso!? Aprender Verilog pode ser legal!

Collapse
 
dantas profile image
Gustavo

Post incrível, ajuda muito nas aulas de circuitos digitais!