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;