Un wire o tanti
Finora ogni segnale che abbiamo visto era largo un bit. I progetti reali non sono quasi mai così: gli indirizzi sono a 16 o 32 bit, i bus dati a 8 o 64, i pixel RGB a 24. Verilog ti offre un unico meccanismo per rendere multi-bit qualsiasi segnale: aggiungere un range tra parentesi quadre.
wire [7:0] data; // 8-bit wire, bit 7 is MSB, bit 0 is LSB
reg [15:0] address; // 16-bit reg
output reg [31:0] result; // 32-bit module output
I numeri tra parentesi quadre sono le posizioni del bit più significativo e di quello meno significativo. [7:0] significa "questo segnale ha bit numerati da 7 a 0", cioè 8 bit in totale. Il primo numero è l'indice alto, il secondo quello basso.
Ogni tanto vedrai [0:7]: stesso numero di bit, endianness opposta per quanto riguarda gli slice. La forma [high:low] è la convenzione nettamente prevalente nel settore: usala, a meno che tu non abbia un buon motivo per fare altrimenti.
Un esempio completo: un sommatore a 8 bit
Ogni + somma i due vettori a 8 bit e produce un risultato a 9 bit. I letterali numerici come 8'd10 significano "un valore decimale 10 largo 8 bit": li vedremo in Letterali numerici.
Slicing: estrarre bit
Una volta che hai un vettore, puoi estrarne singoli bit o range contigui:
Non farti distrarre dalla riga force del testbench: ci serve solo un modo per iniettare un valore e mostrare lo slicing. La parte interessante sono gli slice.
Alcune regole:
- La direzione dello slice deve corrispondere alla dichiarazione. Se hai dichiarato
[7:0], estrai con[high:low]. Invertire la direzione è un errore di sintassi. - Uno slice fuori range produce
x(sconosciuto) in simulazione. Lo strumento di sintesi può dare un avviso o un errore. - La selezione dei bit usa l'indice che hai scritto tu, partendo da zero:
data[0]è il bit chiamato0, che (per una dichiarazione[7:0]) è l'LSB.
Slice a base variabile: +: e -:
Un'esigenza comune: "dammi 8 bit a partire dal bit N". Non puoi scrivere direttamente data[N+7:N], perché Verilog richiede che entrambi gli estremi del range siano costanti. La sintassi che risolve il problema:
data[base +: width] // width bits starting at `base`, going UP
data[base -: width] // width bits starting at `base`, going DOWN
La larghezza è costante (prendiamo 8 bit alla volta), ma la base può essere un'espressione calcolata a runtime. È proprio ciò che serve per memorie indirizzabili a byte, prese di uno shift register e casi simili.
Array: un passo oltre i vettori
Un vettore è un singolo segnale multi-bit. Un array è un insieme di vettori, indicizzati in modo indipendente:
reg [31:0] mem [0:1023];
Quella dichiarazione ha due range, e i due range significano cose diverse:
[31:0]è la dimensione packed: la larghezza di ogni singola parola.[0:1023]è la dimensione unpacked: quante parole ci sono.
Quindi mem sono 1024 registri separati da 32 bit. Accedi a uno di essi con un singolo indice:
mem[5] = 32'hCAFE_BABE; // write word 5
data = mem[address]; // read the word at `address`
È una minuscola memoria che contiene dei quadrati. I progetti reali usano lo stesso pattern per register file, tabelle di lookup, FIFO e qualsiasi altra memoria on-chip più grande di un singolo vettore.
Packed e unpacked: perché conta
La distinzione tra dimensioni packed e unpacked compare ovunque. Sapere quale è quale ti risparmia molto debug:
- Un vettore packed è un solo segnale. Puoi trattarlo tutto come un numero:
data + 1funziona,data == 32'h0funziona,data[7:0]funziona. - Un array unpacked è fatto di tanti segnali. Non puoi trattarlo tutto come un numero:
mem + 1è un errore di sintassi. Devi prima estrarre una parola specifica.
Sono ammesse anche più dimensioni packed:
reg [3:0][7:0] regs; // 4 bytes packed together into a 32-bit signal
regs[0] è un byte (il byte basso). regs nel suo insieme è a 32 bit. SystemVerilog usa molto questa forma.
Più dimensioni unpacked creano una memoria 2D:
reg [31:0] frame [0:479][0:639]; // 480x640 of 32-bit pixels
Accedi a un singolo pixel con frame[y][x]. Ecco come appare un buffer d'immagine in HDL.
Cosa viene dopo
Ora sai dichiarare e manipolare segnali di qualsiasi larghezza. La prossima guida, Parametri, mostra come rendere configurabili queste larghezze, così lo stesso modulo funziona a 8 bit in un'istanza e a 32 in un'altra. Poi passeremo alle regole per scrivere i numeri letterali (8'b1010_1100, 32'hDEAD_BEEF) e ai valori x/z che compaiono ogni volta che qualcosa non è pilotato.
Domande frequenti
Cos'è un vettore in Verilog?
Un vettore è un segnale multi-bit. Lo dichiari aggiungendo un range a un wire o a un reg: wire [7:0] data è un wire a 8 bit. I numeri tra parentesi quadre sono le posizioni dei bit: in questo caso il bit 7 è il più significativo e il bit 0 il meno significativo. Puoi estrarre singoli bit (data[3]) o range contigui (data[7:4]).
Cosa significa [7:0] in Verilog?
[7:0] dichiara un range che va dal bit 7 al bit 0, estremi inclusi: un segnale a 8 bit con il bit 7 come bit più significativo. Il primo numero è l'indice alto, il secondo quello basso. Puoi anche scrivere [0:7] per un'indicizzazione little-endian, ma la forma [high:low] è di gran lunga la convenzione più diffusa nel codice industriale.
Come si estraggono bit in Verilog?
Con l'indicizzazione tra parentesi quadre. data[3] prende un singolo bit. data[7:4] prende i quattro bit alti come vettore a 4 bit. Lo slice deve avere la stessa direzione della dichiarazione: se hai dichiarato [7:0], estrai con [high:low]. SystemVerilog aggiunge anche data[3 +: 4] per slice a base variabile e larghezza costante.
Qual è la differenza tra array packed e unpacked in Verilog?
Un array packed è un unico bus contiguo: reg [31:0] word è un solo segnale a 32 bit. Un array unpacked (o 'memoria') è un insieme di parole indipendenti: reg [31:0] mem [0:1023] sono 1024 registri separati da 32 bit. Puoi leggere o scrivere una parola intera di un array unpacked, ma non puoi operare sull'insieme come se fosse un unico segnale.
Come si dichiara una memoria in Verilog?
reg [31:0] mem [0:1023]; dichiara una memoria di 1024 elementi, ciascuno largo 32 bit. La prima coppia di parentesi è la larghezza della parola (packed), la seconda è il numero di parole (unpacked). Accedi a un elemento con mem[address], e puoi leggere o scrivere una porzione di quell'elemento con mem[address][7:0] quando l'indicizzazione di SystemVerilog-2005 è abilitata.