Modellazione comportamentale e tempistica in Verilog

I modelli comportamentali in Verilog contengono istruzioni procedurali, che controllano la simulazione e manipolano le variabili dei tipi di dati. Tutte queste dichiarazioni sono contenute nelle procedure. A ciascuna procedura è associato un flusso di attività.

Durante la simulazione del modello comportamentale, tutti i flussi definiti dalle istruzioni "sempre" e "iniziale" iniziano insieme al tempo di simulazione "zero". Le istruzioni iniziali vengono eseguite una volta e le istruzioni always vengono eseguite ripetutamente. In questo modello, le variabili di registro a e b vengono inizializzate rispettivamente a 1 e 0 binari al tempo di simulazione "zero". L'istruzione iniziale viene quindi completata e non viene eseguita nuovamente durante l'esecuzione della simulazione. Questa istruzione iniziale contiene un blocco di inizio-fine (chiamato anche blocco sequenziale) di istruzioni. In questo blocco di tipo inizio-fine, a viene inizializzato per primo seguito da b.

Esempio di modellazione comportamentale

module behave; 
reg [1:0]a,b; 

initial 
begin 
   a = ’b1; 
   b = ’b0; 
end 

always 
begin 
   #50 a = ~a; 
end 

always 
begin 
   #100 b = ~b; 
end 
End module

Incarichi procedurali

Le assegnazioni procedurali servono per l'aggiornamento delle variabili reg, integer, time e memory. C'è una differenza significativa tra assegnazione procedurale e assegnazione continua come descritto di seguito -

Le assegnazioni continue guidano le variabili nette e vengono valutate e aggiornate ogni volta che un operando di input cambia valore.

Le assegnazioni procedurali aggiornano il valore delle variabili di registro sotto il controllo dei costrutti di flusso procedurale che le circondano.

Il lato destro di un'assegnazione procedurale può essere qualsiasi espressione che restituisce un valore. Tuttavia, le selezioni parziali sul lato destro devono avere indici costanti. Il lato sinistro indica la variabile che riceve l'assegnazione dal lato destro. Il lato sinistro di un incarico procedurale può assumere una delle seguenti forme:

  • register, integer, real o time variable - Un'assegnazione al riferimento al nome di uno di questi tipi di dati.

  • selezione di bit di una variabile di registro, numero intero, reale o tempo - Un'assegnazione a un singolo bit che lascia inalterati gli altri bit.

  • selezione parziale di una variabile di registro, numero intero, reale o tempo - Una selezione parziale di due o più bit contigui che lascia il resto dei bit intatti. Per il modulo di selezione parziale, solo le espressioni costanti sono legali.

  • elemento di memoria - Una singola parola di un ricordo. Notare che le selezioni di bit e di parte non sono valide sui riferimenti agli elementi di memoria.

  • concatenazione di una qualsiasi delle precedenti - È possibile specificare una concatenazione di una qualsiasi delle quattro forme precedenti, che suddivide efficacemente il risultato dell'espressione a destra e assegna le parti di partizione, in ordine, alle varie parti della concatenazione.

Ritardo nell'assegnazione (non per la sintesi)

In un'assegnazione ritardata Δt le unità di tempo passano prima che l'istruzione venga eseguita e l'assegnazione a sinistra venga eseguita. Con il ritardo intra-assegnazione, il lato destro viene valutato immediatamente ma c'è un ritardo di Δt prima che il risultato venga posizionato nell'assegnazione a sinistra. Se un'altra procedura cambia un segnale del lato destro durante Δt, non ha effetto sull'uscita. I ritardi non sono supportati dagli strumenti di sintesi.

Sintassi

  • Procedural Assignmentvariabile = espressione

  • Delayed assignment# Δt variabile = espressione;

  • Intra-assignment delayvariabile = # Δt espressione;

Esempio

reg [6:0] sum; reg h, ziltch; 
sum[7] = b[7] ^ c[7]; // execute now. 
ziltch = #15 ckz&h; /* ckz&a evaluated now; ziltch changed 
after 15 time units. */ 

#10 hat = b&c; /* 10 units after ziltch changes, b&c is
evaluated and hat changes. */

Blocco delle assegnazioni

Un'istruzione di assegnazione procedurale di blocco deve essere eseguita prima dell'esecuzione delle istruzioni che la seguono in un blocco sequenziale. Un'istruzione di assegnazione procedurale di blocco non impedisce l'esecuzione di istruzioni che la seguono in un blocco parallelo.

Sintassi

La sintassi per un'assegnazione procedurale di blocco è la seguente:

<lvalue> = <timing_control> <expression>

Dove, lvalue è un tipo di dati valido per un'istruzione di assegnazione procedurale, = è l'operatore di assegnazione e il controllo del tempo è il ritardo facoltativo all'interno dell'assegnazione. Il ritardo del controllo di temporizzazione può essere un controllo di ritardo (ad esempio, # 6) o un controllo di evento (ad esempio, @ (posedge clk)). L'espressione è il valore del lato destro che il simulatore assegna al lato sinistro. L'operatore = assegnazione utilizzato per bloccare le assegnazioni procedurali viene utilizzato anche dalle assegnazioni procedurali continue e dalle assegnazioni continue.

Esempio

rega = 0; 
rega[3] = 1;            // a bit-select 
rega[3:5] = 7;          // a part-select 
mema[address] = 8’hff;  // assignment to a memory element 
{carry, acc} = rega + regb;  // a concatenation

Assegnazioni non bloccanti (RTL)

L'assegnazione procedurale non bloccante consente di pianificare le assegnazioni senza bloccare il flusso procedurale. È possibile utilizzare l'istruzione procedurale non bloccante ogni volta che si desidera eseguire più assegnazioni di registro nella stessa fase temporale senza riguardo all'ordine o alla dipendenza l'una dall'altra.

Sintassi

La sintassi per un'assegnazione procedurale non bloccante è la seguente:

<lvalue> <= <timing_control> <expression>

Dove lvalue è un tipo di dati valido per un'istruzione di assegnazione procedurale, <= è l'operatore di assegnazione non bloccante e il controllo del tempo è il controllo del tempo intra-assegnazione opzionale. Il ritardo del controllo di temporizzazione può essere un controllo di ritardo o un controllo di evento (ad esempio, @ (posedge clk)). L'espressione è il valore del lato destro che il simulatore assegna al lato sinistro. L'operatore di assegnazione non bloccante è lo stesso operatore utilizzato dal simulatore per l'operatore relazionale minore di o uguale. Il simulatore interpreta l'operatore <= come un operatore relazionale quando lo si utilizza in un'espressione e interpreta l'operatore <= come un operatore di assegnazione quando lo si utilizza in un costrutto di assegnazione procedurale non bloccante.

Come il simulatore valuta le assegnazioni procedurali non bloccanti Quando il simulatore incontra un'assegnazione procedurale non bloccante, il simulatore valuta ed esegue l'assegnazione procedurale non bloccante in due fasi come segue:

  • Il simulatore valuta il lato destro e pianifica l'assegnazione del nuovo valore in modo che avvenga in un momento specificato da un controllo di temporizzazione procedurale. Il simulatore valuta il lato destro e pianifica l'assegnazione del nuovo valore in modo che avvenga in un momento specificato da un controllo di temporizzazione procedurale.

  • Al termine della fase temporale, in cui è scaduto il ritardo specificato o si è verificato l'evento appropriato, il simulatore esegue l'assegnazione assegnando il valore al lato sinistro.

Esempio

module evaluates2(out); 
output out; 
reg a, b, c; 
initial 

begin 
   a = 0; 
   b = 1; 
   c = 0; 
end 
always c = #5 ~c; 
always @(posedge c) 

begin 
   a <= b; 
   b <= a; 
end 
endmodule

Condizioni

L'istruzione condizionale (o l'istruzione if-else) viene utilizzata per decidere se un'istruzione viene eseguita o meno.

Formalmente, la sintassi è la seguente:

<statement> 
::= if ( <expression> ) <statement_or_null> 
||= if ( <expression> ) <statement_or_null> 
   else <statement_or_null> 
<statement_or_null> 

::= <statement> 
||= ;

L '<espressione> viene valutata; se è vero (cioè ha un valore noto diverso da zero), viene eseguita la prima istruzione. Se è falso (ha un valore zero o il valore è xoz), la prima istruzione non viene eseguita. Se è presente un'istruzione else e <expression> è falsa, viene eseguita l'istruzione else. Poiché il valore numerico dell'espressione if viene verificato per essere zero, sono possibili alcune scorciatoie.

Ad esempio, le due affermazioni seguenti esprimono la stessa logica:

if (expression) 
if (expression != 0)

Poiché la parte else di un if-else è facoltativa, può esserci confusione quando un'altra parte viene omessa da una sequenza if nidificata. Questo si risolve associando sempre l'altro al precedente più vicino se manca un altro.

Esempio

if (index > 0) 
if (rega > regb) 
   result = rega; 
   else // else applies to preceding if 
   result = regb; 

If that association is not what you want, use a begin-end block statement 
to force the proper association 

if (index > 0) 
begin 

if (rega > regb) 
result = rega; 
end 
   else 
   result = regb;

Costruzione di: if-else- if

La seguente costruzione si verifica così spesso che merita una breve discussione separata.

Example

if (<expression>) 
   <statement> 
   else if (<expression>) 
   <statement> 
   else if (<expression>) 
   <statement> 
   else  
   <statement>

Questa sequenza di if (nota come costrutto if-else-if) è il modo più generale di scrivere una decisione a più vie. Le espressioni vengono valutate in ordine; se una qualsiasi espressione è vera, viene eseguita l'istruzione ad essa associata e ciò termina l'intera catena. Ogni affermazione è una singola affermazione o un blocco di istruzioni.

L'ultima parte del costrutto if-else-if gestisce il caso "nessuno dei precedenti" o predefinito in cui nessuna delle altre condizioni è stata soddisfatta. A volte non esiste un'azione esplicita per l'impostazione predefinita; in tal caso, il trailing else può essere omesso o può essere utilizzato per il controllo degli errori per catturare una condizione impossibile.

Dichiarazione del caso

L'istruzione case è una speciale istruzione decisionale a più vie che verifica se un'espressione corrisponde a una delle numerose altre espressioni e si ramifica di conseguenza. L'istruzione case è utile per descrivere, ad esempio, la decodifica di un'istruzione del microprocessore. L'istruzione case ha la seguente sintassi:

Example

<statement> 
::= case ( <expression> ) <case_item>+ endcase 
||= casez ( <expression> ) <case_item>+ endcase 
||= casex ( <expression> ) <case_item>+ endcase 
<case_item> 
::= <expression> <,<expression>>* : <statement_or_null> 
||= default : <statement_or_null> 
||= default <statement_or_null>

Le espressioni del caso vengono valutate e confrontate nell'ordine esatto in cui sono fornite. Durante la ricerca lineare, se una delle espressioni dell'elemento del caso corrisponde all'espressione tra parentesi, viene eseguita l'istruzione associata a quell'elemento del caso. Se tutti i confronti falliscono e viene fornito l'elemento predefinito, viene eseguita l'istruzione dell'elemento predefinito. Se l'istruzione predefinita non viene fornita e tutti i confronti hanno esito negativo, non viene eseguita nessuna delle istruzioni dell'elemento case.

A parte la sintassi, l'istruzione case differisce dal costrutto a più vie if-else-if in due modi importanti:

  • Le espressioni condizionali nel costrutto if-else-if sono più generali del confronto di un'espressione con molte altre, come nell'istruzione case.

  • L'istruzione case fornisce un risultato definitivo quando ci sono valori xez in un'espressione.

Dichiarazioni in loop

Esistono quattro tipi di istruzioni di ciclo. Forniscono un mezzo per controllare l'esecuzione di un'istruzione zero, una o più volte.

  • per sempre esegue continuamente un'istruzione.

  • repeat esegue un'istruzione un numero fisso di volte.

  • while esegue un'istruzione finché un'espressione non diventa falsa. Se l'espressione inizia falsa, l'istruzione non viene eseguita affatto.

  • per controllare l'esecuzione delle sue istruzioni associate mediante un processo in tre fasi, come segue:

    • Esegue un'assegnazione normalmente utilizzata per inizializzare una variabile che controlla il numero di cicli eseguiti

    • Valuta un'espressione: se il risultato è zero, il ciclo for termina e se non è zero, il ciclo for esegue le istruzioni associate e quindi esegue il passaggio 3

    • Esegue un'assegnazione normalmente utilizzata per modificare il valore della variabile loopcontrol, quindi ripete il passaggio 2

Le seguenti sono le regole di sintassi per le istruzioni di ciclo:

Example

<statement> 
::= forever <statement> 
||=forever 
begin 
   <statement>+ 
end  

<Statement> 
::= repeat ( <expression> ) <statement> 
||=repeat ( <expression> ) 
begin
   <statement>+ 
end  

<statement> 
::= while ( <expression> ) <statement> 
||=while ( <expression> ) 
begin 
   <statement>+ 
end  
<statement> 
::= for ( <assignment> ; <expression> ; <assignment> ) 
<statement> 
||=for ( <assignment> ; <expression> ; <assignment> ) 
begin 
   <statement>+ 
end

Controlli del ritardo

Controllo del ritardo

L'esecuzione di un'istruzione procedurale può essere controllata in base al ritardo utilizzando la seguente sintassi:

<statement> 
::= <delay_control> <statement_or_null> 
<delay_control> 
::= # <NUMBER> 
||= # <identifier> 
||= # ( <mintypmax_expression> )

Il seguente esempio ritarda l'esecuzione dell'assegnazione di 10 unità di tempo -

# 10 rega = regb;

I prossimi tre esempi forniscono un'espressione che segue il segno del numero (#). L'esecuzione dei ritardi di assegnazione della quantità di tempo di simulazione specificato dal valore dell'espressione.

Controllo degli eventi

L'esecuzione di un'istruzione procedurale può essere sincronizzata con un cambio di valore su una rete o registro, o il verificarsi di un evento dichiarato, utilizzando la seguente sintassi di controllo degli eventi:

Example

<statement> 
::= <event_control> <statement_or_null> 

<event_control> 
::= @ <identifier> 
||= @ ( <event_expression> ) 

<event_expression> 
::= <expression> 
||= posedge <SCALAR_EVENT_EXPRESSION> 
||= negedge <SCALAR_EVENT_EXPRESSION> 
||= <event_expression> <or <event_expression>>

* <SCALAR_EVENT_EXPRESSION> è un'espressione che si risolve in un valore di un bit.

Le modifiche ai valori su reti e registri possono essere utilizzate come eventi per attivare l'esecuzione di un'istruzione. Questo è noto come rilevamento di un evento implicito. La sintassi di Verilog consente inoltre di rilevare il cambiamento in base alla direzione del cambiamento, cioè verso il valore 1 (posedge) o verso il valore 0 (negedge). Il comportamento di posedge e negedge per i valori di espressione sconosciuta è il seguente:

  • viene rilevato un negedge sulla transizione da 1 a sconosciuto e da sconosciuto a 0
  • viene rilevato un posedge sulla transizione da 0 a sconosciuto e da sconosciuto a 1

Procedure: blocchi sempre e iniziali

Tutte le procedure in Verilog sono specificate all'interno di uno dei seguenti quattro blocchi. 1) Blocchi iniziali 2) Blocchi sempre 3) Task 4) Funzione

Le istruzioni initial e always sono abilitate all'inizio della simulazione. I blocchi iniziali vengono eseguiti una sola volta e la sua attività termina quando l'istruzione è terminata. Al contrario, il blocco sempre viene eseguito ripetutamente. La sua attività muore solo quando la simulazione è terminata. Non c'è limite al numero di blocchi iniziali e sempre che possono essere definiti in un modulo. Le attività e le funzioni sono procedure abilitate da una o più posizioni in altre procedure.

Blocchi iniziali

La sintassi per l'istruzione iniziale è la seguente:

<initial_statement> 
::= initial <statement>

Il seguente esempio illustra l'uso dell'istruzione iniziale per l'inizializzazione delle variabili all'inizio della simulazione.

Initial 
Begin 
   Areg = 0; // initialize a register 
   For (index = 0; index < size; index = index + 1) 
   Memory [index] = 0; //initialize a memory 
   Word 
End

Un altro utilizzo tipico dei blocchi iniziali è la specifica delle descrizioni delle forme d'onda che vengono eseguite una volta per fornire uno stimolo alla parte principale del circuito simulato.

Initial 
Begin 
   Inputs = ’b000000; 
   // initialize at time zero 
   #10 inputs = ’b011001; // first pattern 
   #10 inputs = ’b011011; // second pattern 
   #10 inputs = ’b011000; // third pattern 
   #10 inputs = ’b001000; // last pattern 
End

Blocca sempre

L'istruzione "sempre" si ripete continuamente durante l'intera esecuzione della simulazione. La sintassi per l'istruzione always è fornita di seguito

<always_statement> 
::= always <statement>

L'istruzione "sempre", a causa della sua natura a ciclo continuo, è utile solo se usata insieme a qualche forma di controllo del tempo. Se un'istruzione "always" non fornisce alcun mezzo per il tempo di avanzare, l'istruzione "always" crea una condizione di deadlock di simulazione. Il codice seguente, ad esempio, crea un ciclo infinito a ritardo zero:

Always areg = ~areg;

Fornire un controllo di temporizzazione al codice precedente crea una descrizione potenzialmente utile, come nell'esempio seguente:

Always #half_period areg = ~areg;