Menu

Seu primeiro module em Verilog: Um passo a passo

Escreva seu primeiro module Verilog completo do zero - declaração, ports, uma peça de lógica combinacional e um testbench que o aciona. Executável no navegador.

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

Um module é a unidade do Verilog

Tudo em Verilog vive dentro de um module. Um module embrulha uma peça de circuito: declara quais sinais entram, quais sinais saem e quais wires/registers/lógica ficam entre eles. Todo chip que você já viu é uma árvore de modules instanciando outros modules, lá embaixo até as primitivas de gate fornecidas pelo fabricante.

A forma é sempre a mesma:

module name(port_list);
    // declarações: wire, reg, parameter
    // corpo: assigns, instanciações, blocos always
endmodule

Vamos construir um module completo em etapas.

O menor module útil: uma porta AND de duas entradas

Precisamos de algo simples e autocontido. Uma porta AND de duas entradas é perfeita: duas entradas, uma saída, uma linha de lógica.

Pressione Run. Você deve ver as quatro linhas de uma tabela-verdade do AND. Vamos passar por cada parte.

Lendo a declaração do module

module and_gate(
    input  wire a,
    input  wire b,
    output wire y
);
  • module and_gate declara um module chamado and_gate. O nome é como outros modules vão instanciá-lo.
  • A lista entre parênteses é a lista de ports - os sinais visíveis de fora.
  • input wire a - a é uma porta de entrada; é um wire (conduzido de fora).
  • output wire y - y é uma porta de saída conduzida por algo dentro do module.

Se você quisesse ser conciso, poderia escrever input a em vez de input wire a - apenas a direção faz o tipo padrão ser wire. Mas ser explícito é um hábito que vale a pena formar. Module Ports cobre o conjunto completo de formas de porta.

O corpo

assign y = a & b;

Essa é uma continuous assignment. Ela diz "sempre que a ou b mudar, recalcule y como o AND bit a bit dos dois". Não há clock, sem timing - a relação é sempre verdadeira. Essa é lógica combinacional pura.

endmodule fecha o bloco. O module está pronto.

O testbench

Você não consegue executar and_gate sozinho. Precisa de um segundo module que acione suas entradas e observe suas saídas. Esse é o testbench, e por convenção o chamamos de test, tb ou <design>_tb.

module test;
    reg  a, b;
    wire y;

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

Três coisas para notar:

  • Sem lista de ports. Um testbench é o topo da simulação - nada fora dele.
  • reg a, b e wire y. Entradas para o design under test (DUT) são reg no testbench porque estamos acionando elas a partir de um bloco procedural. A saída é um wire porque o DUT a aciona.
  • and_gate dut(.a(a), .b(b), .y(y)). Isso é instanciação. Estamos imprimindo uma cópia de and_gate e chamando-a de dut (um nome comum - "design under test"). A sintaxe .a(a) diz "conecte a porta chamada a na instância ao sinal local chamado a". Module Instantiation entra mais a fundo.

O estímulo

initial begin
    a = 0; b = 0; #1 $display("a=%b b=%b y=%b", a, b, y);
    a = 0; b = 1; #1 $display("a=%b b=%b y=%b", a, b, y);
    a = 1; b = 0; #1 $display("a=%b b=%b y=%b", a, b, y);
    a = 1; b = 1; #1 $display("a=%b b=%b y=%b", a, b, y);
    $finish;
end

O bloco initial executa uma vez no início da simulação. Dentro dele:

  • a = 0; b = 0; aciona as entradas. Essas são blocking assignments - a ordem importa, e cada uma acontece antes da próxima.
  • #1 avança o tempo simulado em 1 unidade. Precisamos disso para que y tenha tempo de se estabilizar depois das mudanças de entrada. Sem o #1, $display imprimiria o valor antigo de y.
  • $display(...) imprime no console da simulação. A string de formato funciona como o printf do C: %b é binário, %d é decimal, %h é hex, %t é tempo de simulação.
  • $finish termina a simulação. Sem ele o simulador continuaria avançando o tempo para sempre esperando um evento que nunca vem.

Tente quebrá-lo

Modifique o module para ser uma porta OR (troque & por |) e execute novamente. A tabela-verdade vira. Agora tente um XOR:

Mesmo esqueleto. Operador diferente. Esse é o jogo todo para lógica combinacional - declare ports, escreva assigns, varra entradas, observe saídas.

Um module com duas saídas

Modules podem ter mais de uma saída. Aqui está um half-adder - duas entradas, soma e carry-out:

As duas declarações assign ficam lado a lado mas acontecem em paralelo - não há "primeiro calcule sum, depois calcule carry". Ambas são sempre verdadeiras. Essa é a concorrência sobre a qual falamos em Hardware vs Software, tornada concreta.

O que você agora sabe

Você viu o esqueleto inteiro de um arquivo Verilog: um module de design com ports declarados e um corpo, mais um module testbench que o instancia, aciona suas entradas em um bloco initial e reporta resultados. Quase todo arquivo fonte Verilog que você vai ler se encaixa neste template. O resto da linguagem é apenas preencher com lógica mais rica, sinais multi-bit, comportamento clocked e testbenches maiores.

A seguir: comentários e estilo de código, para que seus modules permaneçam legíveis conforme crescem.

Perguntas frequentes

Qual é o module mais simples em Verilog?

O menor module legal em Verilog é apenas module name; endmodule - sem ports, sem corpo. O menor útil é um module de uma saída: module and_gate(input wire a, input wire b, output wire y); assign y = a & b; endmodule. Esta é uma peça real de lógica combinacional que você pode plugar em qualquer design maior.

Como executo um module Verilog?

Você não consegue executar um module sozinho - ele é uma descrição de circuito, não um programa. Você escreve um module testbench que instancia seu design e aciona suas entradas, depois compila ambos com iverilog -o sim design.v test.v e roda vvp sim. O editor no navegador desta página faz ambos os passos para você quando você pressiona Run.

O que é um testbench em Verilog?

Um testbench é um segundo module - geralmente sem ports - cujo trabalho é exercitar seu design. Ele instancia o design, mexe nas suas entradas através de um bloco initial, observa as saídas com $display ou $monitor, e chama $finish quando termina. Testbenches não são sintetizáveis; existem apenas para verificar comportamento.

Por que meu código Verilog precisa de $finish?

Porque hardware nunca para. Um simulador finge que o tempo está passando e, sem um $finish explícito, continuaria avançando para sempre esperando novos eventos. $finish diz ao simulador 'terminamos, saia limpo'. Em um testbench é a última linha do bloco initial - rode o teste, depois finalize.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR