Menu

Il tuo primo modulo Verilog: guida passo passo

Scrivi da zero il tuo primo modulo Verilog completo: dichiarazione, porte, un pezzo di logica combinatoria e un testbench che lo pilota. Eseguibile nel browser.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Il modulo è l'unità di base di Verilog

In Verilog tutto vive dentro un module. Un modulo racchiude un pezzo di circuito: dichiara quali segnali entrano, quali escono e quali wire, registri e logica stanno nel mezzo. Ogni chip che hai mai visto è un albero di moduli che istanziano altri moduli, giù giù fino alle primitive di porta fornite dal produttore.

La forma è sempre la stessa:

module name(port_list);
    // dichiarazioni: wire, reg, parameter
    // corpo: assign, istanziazioni, blocchi always
endmodule

Costruiremo un modulo completo per gradi.

Il modulo utile più piccolo: una porta AND a due ingressi

Ci serve qualcosa di semplice e autonomo. Una porta AND a due ingressi è perfetta: due ingressi, un'uscita, una riga di logica.

Premi Esegui. Dovresti vedere le quattro righe della tabella di verità di un AND. Analizziamo ogni parte.

Leggere la dichiarazione del modulo

module and_gate(
    input  wire a,
    input  wire b,
    output wire y
);
  • module and_gate dichiara un modulo chiamato and_gate. È con questo nome che gli altri moduli lo istanzieranno.
  • L'elenco tra parentesi è la lista delle porte: i segnali visibili dall'esterno.
  • input wire a: a è una porta di ingresso; è un wire (pilotato dall'esterno).
  • output wire y: y è una porta di uscita pilotata da qualcosa dentro il modulo.

Se volessi essere più conciso potresti scrivere input a invece di input wire a: la sola direzione rende wire il tipo predefinito. Ma essere espliciti è un'abitudine che vale la pena prendere. Porte dei moduli copre tutte le forme possibili delle porte.

Il corpo

assign y = a & b;

Questa è un'assegnazione continua. Dice "ogni volta che cambia a o b, ricalcola y come AND bit a bit dei due". Non c'è clock né timing: la relazione è sempre vera. È logica combinatoria pura.

endmodule chiude il blocco. Il modulo è finito.

Il testbench

Non puoi eseguire and_gate da solo. Ti serve un secondo modulo che ne piloti gli ingressi e ne osservi le uscite. Questo è il testbench, e per convenzione lo chiamiamo test, tb o <design>_tb.

module test;
    reg  a, b;
    wire y;

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

Tre cose da notare:

  • Nessuna lista di porte. Il testbench è il livello più alto della simulazione: non c'è niente sopra di lui.
  • reg a, b e wire y. Gli ingressi del design sotto test (DUT) sono reg nel testbench perché li stiamo pilotando da un blocco procedurale. L'uscita è un wire perché la pilota il DUT.
  • and_gate dut(.a(a), .b(b), .y(y)). Questa è l'istanziazione. Creiamo una copia di and_gate e la chiamiamo dut (un nome comune, sta per "design under test"). La sintassi .a(a) dice "collega la porta chiamata a dell'istanza al segnale locale chiamato a". Istanziazione dei moduli approfondisce l'argomento.

Lo stimolo

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

Il blocco initial viene eseguito una sola volta all'avvio della simulazione. Al suo interno:

  • a = 0; b = 0; pilota gli ingressi. Sono assegnazioni blocking: l'ordine conta, e ognuna avviene prima della successiva.
  • #1 fa avanzare il tempo simulato di 1 unità. Serve perché y abbia il tempo di stabilizzarsi dopo il cambio degli ingressi. Senza #1, $display stamperebbe il valore precedente di y.
  • $display(...) stampa sulla console di simulazione. La stringa di formato funziona come il printf del C: %b è binario, %d decimale, %h esadecimale, %t il tempo di simulazione.
  • $finish termina la simulazione. Senza, il simulatore continuerebbe a far avanzare il tempo all'infinito aspettando un evento che non arriva mai.

Prova a romperlo

Modifica il modulo per trasformarlo in una porta OR (cambia & in |) ed eseguilo di nuovo. La tabella di verità cambia. Ora prova uno XOR:

Stesso scheletro, operatore diverso. Per la logica combinatoria il gioco è tutto qui: dichiara le porte, scrivi gli assign, fai variare gli ingressi, osserva le uscite.

Un modulo con due uscite

I moduli possono avere più di un'uscita. Ecco un half adder (semisommatore): due ingressi, somma e riporto in uscita:

Le due istruzioni assign stanno una accanto all'altra ma avvengono in parallelo: non c'è un "prima calcola sum, poi calcola carry". Sono entrambe sempre vere. È la concorrenza di cui abbiamo parlato in Hardware e software, resa concreta.

Cosa sai adesso

Hai visto lo scheletro completo di un file Verilog: un modulo di design con porte dichiarate e un corpo, più un modulo testbench che lo istanzia, ne pilota gli ingressi in un blocco initial e riporta i risultati. Quasi ogni file sorgente Verilog che leggerai segue questo schema. Il resto del linguaggio consiste nel riempirlo con logica più ricca, segnali multi-bit, comportamento sincronizzato dal clock e testbench più grandi.

Prossimo passo: commenti e stile del codice, perché i tuoi moduli restino leggibili man mano che crescono.

Domande frequenti

Qual è il modulo Verilog più semplice?

Il più piccolo modulo Verilog valido è semplicemente module name; endmodule: niente porte, niente corpo. Il più piccolo utile è un modulo con una sola uscita: module and_gate(input wire a, input wire b, output wire y); assign y = a & b; endmodule. È un vero pezzo di logica combinatoria che puoi inserire in qualsiasi progetto più grande.

Come si esegue un modulo Verilog?

Un modulo non si può eseguire da solo: è la descrizione di un circuito, non un programma. Scrivi un modulo testbench che istanzia il tuo design e ne pilota gli ingressi, poi compili entrambi con iverilog -o sim design.v test.v ed esegui vvp sim. L'editor nel browser di questa pagina fa entrambi i passaggi per te quando premi Esegui.

Cos'è un testbench in Verilog?

Un testbench è un secondo modulo, di solito senza porte, il cui compito è mettere alla prova il tuo design. Istanzia il design, fa variare i suoi ingressi tramite un blocco initial, osserva le uscite con $display o $monitor e chiama $finish alla fine. I testbench non sono sintetizzabili: esistono solo per verificare il comportamento.

Perché il mio codice Verilog ha bisogno di $finish?

Perché l'hardware non si ferma mai. Un simulatore finge che il tempo scorra e, senza un $finish esplicito, continuerebbe ad avanzare all'infinito in attesa di nuovi eventi. $finish dice al simulatore 'abbiamo finito, esci in modo pulito'. In un testbench è l'ultima riga del blocco initial: esegui il test, poi termina.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA