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_gatedeclara um module chamadoand_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; é umwire(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, bewire y. Entradas para o design under test (DUT) sãoregno testbench porque estamos acionando elas a partir de um bloco procedural. A saída é umwireporque o DUT a aciona.and_gate dut(.a(a), .b(b), .y(y)). Isso é instanciação. Estamos imprimindo uma cópia deand_gatee chamando-a dedut(um nome comum - "design under test"). A sintaxe.a(a)diz "conecte a porta chamadaana instância ao sinal local chamadoa". 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.#1avança o tempo simulado em 1 unidade. Precisamos disso para queytenha tempo de se estabilizar depois das mudanças de entrada. Sem o#1,$displayimprimiria o valor antigo dey.$display(...)imprime no console da simulação. A string de formato funciona como oprintfdo C:%bé binário,%dé decimal,%hé hex,%té tempo de simulação.$finishtermina 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.