Menu

Assegnazione continua in Verilog: l'istruzione assign

Come funziona assign: la relazione sempre vera che descrive, cosa può e non può pilotare, e i pattern in cui dà il meglio rispetto al codice procedurale.

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

assign descrive una verità permanente

Una dichiarazione wire crea un segnale. Un assign descrive cosa lo pilota. La relazione è continua: qualunque cosa ci sia sul lato destro, il lato sinistro le è uguale, in ogni istante del tempo simulato:

wire y;
assign y = a & b;

Queste due righe implicano due cose:

  • y esiste come wire nel circuito.
  • y è in ogni momento l'AND bit a bit di a e b. Se cambia uno dei due ingressi, y lo segue.

Nessun clock e nessun evento fanno scattare l'aggiornamento. Il simulatore vede cambiare a o b, segna y come da aggiornare e rivaluta l'espressione. In hardware corrisponde a un paio di porte AND: logica combinatoria, senza stato, istantanea.

La forma implicita

Puoi unire dichiarazione e assegnazione in una sola riga:

wire y = a & b;

Equivale alle due righe precedenti. È comoda per wire locali che non interessano a nessuno fuori da questo contesto. Molte guide di stile preferiscono proprio la forma implicita, perché mette la dichiarazione accanto all'equazione.

Cosa può pilotare assign

La destinazione di assign deve essere di tipo net: nel Verilog semplice significa wire (o uno dei cugini più rari come tri, wand, wor). Non può essere un reg. Se per sbaglio dichiari la destinazione come reg:

reg y;
assign y = a & b;   // ERROR: cannot drive reg with assign

Il compilatore te lo segnalerà. Cambia la destinazione in wire, oppure sposta la logica in un blocco always @(*), dove reg è la destinazione ammessa.

Il lato destro può essere qualsiasi cosa che produca un valore: letterali, segnali, parametri, espressioni con operatori, chiamate a funzione. Può mescolare ingressi di larghezze diverse; valgono le normali regole di allargamento.

Quando usare assign e quando always

Entrambi possono produrre logica combinatoria. La scelta dipende soprattutto da come si legge il codice:

  • assign è ideale quando la relazione è una singola espressione. Sommatori, mux semplici costruiti con ?:, maschere, bit di parità, tutto ciò che si scrive in una riga.
  • always @(*) è ideale quando servono istruzioni procedurali. case a più vie, if/else if annidati, tutto ciò che trae vantaggio da reg intermedi con un nome. Ne parliamo in Always Block.

Ecco lo stesso mux 4 a 1 scritto nei due modi:

Entrambi i moduli si sintetizzano sostanzialmente nello stesso multiplexer. La versione con assign occupa una riga di codice, quella con always sei. Con quattro casi la differenza è piccola; con sedici casi il blocco case è decisamente più leggibile.

Pattern comuni

Logica combinatoria semplice

assign sum   = a + b;
assign carry = a[7] & b[7];
assign equal = (data == 8'hFF);

Un'espressione, un wire. Il pane quotidiano di assign.

Un mux 2 a 1

assign out = sel ? a : b;

Una singola espressione condizionale, che il sintetizzatore trasforma in un singolo mux 2 a 1. Il modo più pulito possibile di scrivere "scegli tra a e b".

Impacchettare bit

assign status = {error, overflow, ready, busy, 4'b0};

La concatenazione a destra di un assign è il modo per raccogliere dei flag in un byte di stato. Il risultato viene calcolato e pilotato in modo continuo.

Uscita tri-state

assign data_pin = output_enable ? data_out : 1'bz;

Quando output_enable è alto, il pin viene pilotato. Quando è basso, viene rilasciato in alta impedenza. È il pattern canonico sui pin del chip, dove più driver possono condividere lo stesso wire.

Concorrenza: più assign non sono sequenziali

Un promemoria che resta sempre valido: più istruzioni assign nello stesso modulo vengono eseguite tutte in parallelo. Non sono una sequenza:

assign y = a & b;     // exists at all times
assign z = a | b;     // also exists at all times, independently

L'ordine nel file è irrilevante. Entrambe le equazioni sono vere contemporaneamente. Il sintetizzatore può disporre la porta AND prima o dopo la porta OR; non importa, entrambe le porte lavorano di continuo.

Se vuoi un comportamento che sembri sequenziale, ti serve un blocco always (e probabilmente un clock). Quello è un altro capitolo.

Più driver: il pattern del bus

Un wire può avere più di un assign che lo pilota, ma non lo vuoi quasi mai, tranne che per i bus tri-state. Due driver che si contendono un wire producono un comportamento indefinito:

assign y = a;
assign y = b;   // BAD - two drivers, simulator picks one or x's it out

Il pattern legittimo: ogni driver rilascia a z quando è inattivo e al massimo uno è attivo in ogni momento.

assign bus = device_a_active ? data_from_a : 1'bz;
assign bus = device_b_active ? data_from_b : 1'bz;

Funziona perché in ogni istante al massimo uno dei due operatori ternari produce un valore diverso da z. Il valore effettivo del wire è quello del driver che non si sta rilasciando.

Nella logica interna, cioè ovunque non ci sia un pin del chip o un bus condiviso sul chip, un solo driver per wire. I bug da driver multipli sono terribili da individuare.

Cosa non può fare assign

Ci sono cose per cui assign è lo strumento sbagliato:

  • Memorizzare. assign descrive relazioni combinatorie e non può introdurre un flip-flop. Se ti serve un valore ricordato tra un ciclo di clock e l'altro, serve un blocco always @(posedge clk).
  • Logica procedurale a più passi. Non puoi scrivere if/else o case dentro un assign. Il massimo che ottieni è una catena di ?:, che diventa illeggibile oltre i tre rami.
  • Pilotare registri dall'interno di un blocco procedurale. Le destinazioni reg richiedono un'assegnazione procedurale, non assign.

Conoscere i limiti è ciò che ti fa capire quando passare ad always.

Cosa viene dopo

Ora hai visto l'intero lato strutturale di Verilog: dichiarare moduli, istanziarli e collegare logica combinatoria con assign. Il prossimo capitolo passa ai blocchi procedurali, i costrutti initial e always, dove tempo e ordine iniziano a contare.

Domande frequenti

Cos'è un'assegnazione continua in Verilog?

assign target = expression; dichiara una relazione permanente e continua: target è sempre uguale a expression. Ogni volta che cambia un segnale dell'espressione, il simulatore rivaluta il lato destro e aggiorna target. Non c'è clock né evento: la relazione è vera in ogni istante.

Cosa può pilotare un assign in Verilog?

assign può pilotare un wire, mai un reg. La destinazione deve essere di tipo net. Se invece devi assegnare qualcosa dentro un blocco always, dichiaralo reg. Il compilatore rifiuta assign x = ... se x è reg, e rifiuta x = ... dentro un always se x è wire.

Quando usare assign e quando un blocco always?

Usa assign per logica combinatoria semplice: un'espressione in ingresso, un segnale in uscita, nessun bisogno di if/else. Usa always @(*) quando la logica richiede istruzioni procedurali (un case, una catena di if/else if, un ciclo for). Entrambi producono hardware combinatorio; la scelta riguarda la leggibilità.

Si possono avere più assign sullo stesso wire in Verilog?

Solo se stai modellando un bus tri-state in cui ogni driver rilascia il wire a z quando è inattivo. Due assign che cercano di pilotare il wire a valori definiti nello stesso momento creano un conflitto: il simulatore può sceglierne uno o mettere il segnale a X, a seconda dello strumento. Per la normale logica combinatoria, un solo driver per wire.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA