L'interfaccia di un modulo
L'elenco delle porte di un modulo è la sua interfaccia: tutto ciò che il mondo esterno può vedere e toccare. All'interno del modulo, il corpo calcola le uscite a partire dagli ingressi. Le porte sono i fili che attraversano il confine.
La dichiarazione moderna in stile ANSI mette direzione, tipo e larghezza direttamente in linea:
module my_module(
input wire clk,
input wire reset,
input wire [7:0] data_in,
output reg [7:0] data_out,
output wire valid
);
// body
endmodule
Questa è tutta la struttura. Ogni porta ha:
- Una direzione:
input,outputoinout. - Un tipo:
wireoreg(ologicin SystemVerilog). - Una larghezza: un intervallo come
[7:0], oppure un solo bit se omessa. - Un nome: l'identificatore che userai nel corpo.
Le tre direzioni
input: pilotata dall'esterno
Gli ingressi sono sempre wire. Il driver è fuori dal modulo: il segnale di un modulo di livello superiore oppure il reg del testbench. Dentro questo modulo puoi leggere gli ingressi nelle espressioni, ma non assegnarli mai:
module reader(input wire [7:0] data);
initial $display("data = %h", data);
endmodule
Scrivere data = ... dentro il modulo sarebbe un errore.
output: pilotata dall'interno
Le uscite sono pilotate da qualcosa dentro questo modulo. Possono essere wire (pilotate da assign o dall'uscita di un sottomodulo) oppure reg (pilotate da always/initial).
module driver(
input wire a,
input wire b,
input wire clk,
output wire y, // wire: pilotato da assign
output reg q // reg: pilotato da always
);
assign y = a & b; // OK perché y è wire
always @(posedge clk)
q <= a; // OK perché q è reg
endmodule
Cercare di assegnare y dentro il blocco always, o di pilotare q con un assign, darebbe un errore di compilazione. La scelta della parola chiave deve corrispondere al driver.
inout: bidirezionale
Le porte inout sono wire che possono essere pilotati, alternativamente, dal modulo o da ciò che è collegato all'esterno. Compaiono ai confini del chip: linee SDA dell'I²C, pin GPIO bidirezionali, bus dati condivisi. Dentro il modulo controlli la direzione con un pattern tri-state:
module bidir_pin(
input wire data_out,
input wire output_enable,
output wire data_in,
inout wire pin
);
assign pin = output_enable ? data_out : 1'bz;
assign data_in = pin;
endmodule
pin è il wire condiviso. Quando output_enable è alto, il modulo pilota pin al valore di data_out. Quando è basso, rilascia il pin in alta impedenza (z) e lascia che lo usi un driver esterno. data_in osserva sempre ciò che si trova sul pin in quel momento.
inout è raro nella logica puramente interna. Se stai costruendo un controller di memoria, un core di CPU o una pipeline di elaborazione di immagini, potresti non averne mai bisogno. È una funzionalità per l'anello di I/O ai confini del chip.
Porte da un bit e a più bit
L'intervallo di larghezza è facoltativo: omettilo per una porta da un bit:
input wire valid, // 1 bit
input wire [7:0] data, // 8 bits
input wire [31:0] addr, // 32 bits
Per le larghezze puoi usare dei parametri, ed è così che funzionano i moduli parametrici:
module bus #(
parameter WIDTH = 32
)(
input wire [WIDTH-1:0] in,
output wire [WIDTH-1:0] out
);
assign out = in;
endmodule
Stile ANSI e stile Verilog-1995
Ogni tanto vedrai codice più vecchio che separa l'elenco delle porte dalle dichiarazioni:
// Vecchio stile Verilog-1995: NON usarlo nel codice nuovo
module foo(clk, data_in, data_out);
input clk;
input [7:0] data_in;
output reg [7:0] data_out;
// ...
endmodule
L'elenco delle porte contiene solo i nomi; ogni porta viene poi dichiarata a parte nel corpo. È prolisso, soggetto a errori del tipo "nominata nell'elenco ma mai dichiarata" e richiede il doppio della battitura. Lo stile ANSI-2001 (quello usato in tutte queste pagine) sostituisce entrambe le parti:
module foo(
input wire clk,
input wire [7:0] data_in,
output reg [7:0] data_out
);
// ...
endmodule
Usa lo stile ANSI. Gli strumenti lo supportano dal 2001.
Un esempio completo
Questo singolo modulo contiene:
- Un parametro (
WIDTH). - Ingressi per clock, reset, abilitazione al caricamento, dati e abilitazione allo shift.
- Un'uscita
reg(data_out) pilotata dentro un bloccoalways. - Un'uscita
wire(msb_out) pilotata da un'assegnazione continua. - Una dichiarazione completa in stile ANSI che elenca in linea direzione, tipo e larghezza di ogni porta.
Quasi ogni modulo sintetizzabile che scriverai ha questa forma.
Cosa viene dopo
La prossima pagina, Module Instantiation, mostra come prendere questo modulo e usarlo dentro un progetto più grande. Lo shifter che hai appena scritto da solo non serve a molto: diventa utile quando qualcosa lo istanzia e lo collega a un sistema.
Domande frequenti
Quali sono le tre direzioni delle porte in Verilog?
input, output e inout. input è pilotata dall'esterno del modulo. output è pilotata dall'interno del modulo. inout è bidirezionale: serve per i bus tri-state in cui il modulo sia pilota sia legge lo stesso pin. La stragrande maggioranza delle porte interne è input o output; inout compare solo sui pin del chip.
Qual è la differenza tra output wire e output reg?
output wire significa che l'uscita è pilotata da un'assegnazione continua o dall'uscita di un sottomodulo, cioè da qualsiasi cosa fuori da un blocco always. output reg significa che è pilotata da un blocco procedurale (initial o always). La parola chiave stabilisce se puoi assegnare il segnale dall'interno di un always, non se l'hardware sintetizzato contiene un flip-flop.
Cos'è lo stile ANSI per le porte in Verilog?
Lo stile ANSI dichiara direzione e tipo di ogni porta direttamente nell'elenco delle porte: module foo(input wire [7:0] data, output reg [7:0] result);. Il vecchio stile Verilog-1995 elencava solo i nomi e li ridichiarava dentro il modulo. Nel codice nuovo usa sempre lo stile ANSI: è più corto, meno soggetto a errori e standard in tutti gli strumenti moderni.
Un modulo Verilog può avere più ingressi e uscite?
Sì: un modulo può avere tutte le porte che ti servono, in qualsiasi combinazione di direzioni. Separale con virgole nell'elenco delle porte. L'ordine nell'elenco determina l'ordine dei collegamenti per posizione nell'istanziazione, ma quasi sempre si usano i collegamenti per nome (.port(signal)) per non dipendere dall'ordine.