Modelowanie behawioralne i synchronizacja w Verilog
Modele behawioralne w Verilog zawierają instrukcje proceduralne, które kontrolują symulację i manipulują zmiennymi typów danych. Te wszystkie stwierdzenia są zawarte w procedurach. Z każdą procedurą jest powiązany przepływ czynności.
Podczas symulacji modelu behawioralnego wszystkie przepływy zdefiniowane przez instrukcje „zawsze” i „początkowe” rozpoczynają się razem w czasie symulacji „zero”. Instrukcje początkowe są wykonywane raz, a instrukcje always są wykonywane wielokrotnie. W tym modelu zmienne rejestru a i b są inicjalizowane odpowiednio na binarną 1 i 0 w czasie symulacji „zero”. Instrukcja początkowa jest wtedy zakończona i nie jest wykonywana ponownie podczas przebiegu symulacji. Ta instrukcja początkowa zawiera blok instrukcji początkowo-końcowych (nazywany również blokiem sekwencyjnym) instrukcji. W tym bloku typu początek-koniec najpierw inicjowana jest a, a następnie b.
Przykład modelowania behawioralnego
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
Zadania proceduralne
Przypisania proceduralne służą do aktualizowania zmiennych reg, liczb całkowitych, czasu i pamięci. Istnieje znacząca różnica między przydziałem proceduralnym i ciągłym, jak opisano poniżej -
Przypisania ciągłe sterują zmiennymi netto i są oceniane i aktualizowane za każdym razem, gdy argument wejściowy zmienia wartość.
Przypisania proceduralne aktualizują wartość zmiennych rejestru pod kontrolą konstrukcji przepływu procedur, które je otaczają.
Prawą stroną przypisania proceduralnego może być dowolne wyrażenie, którego wynikiem jest wartość. Jednak selekcje części po prawej stronie muszą mieć stałe indeksy. Lewa strona wskazuje zmienną, która otrzymuje przypisanie z prawej strony. Lewa strona zadania proceduralnego może przyjąć jedną z następujących form -
register, integer, real lub time variable - Przypisanie do odwołania do nazwy jednego z tych typów danych.
wybór bitu rejestru, zmiennej całkowitej, rzeczywistej lub czasowej - Przypisanie do pojedynczego bitu, które pozostawia nietknięte inne bity.
częściowy wybór rejestru, zmiennej całkowitej, rzeczywistej lub czasowej - częściowy wybór dwóch lub więcej sąsiadujących ze sobą bitów, który pozostawia nietkniętą resztę bitów. W przypadku formularza wyboru części dozwolone są tylko wyrażenia stałe.
element pamięci - pojedyncze słowo pamięci. Należy zauważyć, że wybieranie bitów i wybieranie części jest niedozwolone w odniesieniach do elementów pamięci.
konkatenacja dowolnej z powyższych - można określić konkatenację dowolnej z czterech poprzednich form, co efektywnie dzieli wynik wyrażenia po prawej stronie i przypisuje części partycji w kolejności do różnych części konkatenacji.
Opóźnienie w przypisaniu (nie do syntezy)
W przypadku opóźnionego przypisania Δt jednostki czasu mijają, zanim instrukcja zostanie wykonana i wykonane zostanie przypisanie po lewej stronie. W przypadku opóźnienia w przypisaniu prawa strona jest oceniana natychmiast, ale występuje opóźnienie Δt przed umieszczeniem wyniku w przypisaniu z lewej strony. Jeśli inna procedura zmienia sygnał z prawej strony podczas Δt, nie ma to wpływu na wyjście. Opóźnienia nie są obsługiwane przez narzędzia syntezy.
Składnia
Procedural Assignmentzmienna = wyrażenie
Delayed assignment# Δt zmienna = wyrażenie;
Intra-assignment delayzmienna = # Δt wyrażenie;
Przykład
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. */
Blokowanie przydziałów
Blokująca instrukcja przypisania proceduralnego musi zostać wykonana przed wykonaniem instrukcji, które następują po niej w bloku sekwencyjnym. Blokująca instrukcja przypisania proceduralnego nie zapobiega wykonywaniu instrukcji, które następują po niej w bloku równoległym.
Składnia
Składnia blokującego przypisania proceduralnego jest następująca -
<lvalue> = <timing_control> <expression>
Gdzie lwartość jest typem danych, który jest prawidłowy dla instrukcji przypisania proceduralnego, = jest operatorem przypisania, a kontrola czasu to opcjonalne opóźnienie wewnątrz przypisania. Opóźnienie sterowania czasem może być sterowaniem opóźnieniem (na przykład # 6) lub sterowaniem zdarzeniem (na przykład @ (posedge clk)). Wyrażenie jest wartością po prawej stronie, którą symulator przypisuje lewej stronie. Operator = przypisanie używany do blokowania przypisań proceduralnych jest również używany przez ciągłe przypisania proceduralne i przydziały ciągłe.
Przykład
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
Przypisania nieblokujące (RTL)
Nieblokujące przypisanie proceduralne umożliwia planowanie przydziałów bez blokowania przepływu procedur. Możesz użyć nieblokującego oświadczenia proceduralnego, gdy chcesz wykonać kilka przypisań do rejestru w tym samym kroku czasowym, bez względu na kolejność lub zależności od siebie.
Składnia
Składnia nieblokującego przypisania proceduralnego jest następująca -
<lvalue> <= <timing_control> <expression>
Gdzie lwartość jest typem danych, który jest prawidłowy dla instrukcji przypisania proceduralnego, <= jest nieblokującym operatorem przypisania, a kontrola taktowania to opcjonalna kontrola czasu wewnątrz przypisania. Opóźnienie sterowania czasem może być sterowaniem opóźnieniem lub sterowaniem zdarzeniem (na przykład @ (posedge clk)). Wyrażenie jest wartością po prawej stronie, którą symulator przypisuje lewej stronie. Nieblokujący operator przypisania jest tym samym operatorem, którego używa symulator dla mniej niż lub równorzędnego operatora relacyjnego. Symulator interpretuje operator <= jako operator relacyjny, gdy używasz go w wyrażeniu, i interpretuje operator <= jako operator przypisania, gdy używasz go w nieblokującej konstrukcji przypisania proceduralnego.
W jaki sposób symulator ocenia nieblokujące przypisania proceduralne Gdy symulator napotka nieblokujące przypisanie proceduralne, ocenia i wykonuje nieblokujące przypisanie proceduralne w dwóch krokach w następujący sposób -
Symulator ocenia prawą stronę i planuje przypisanie nowej wartości tak, aby miało miejsce w czasie określonym przez proceduralną kontrolę czasu. Symulator ocenia prawą stronę i planuje przypisanie nowej wartości tak, aby miało miejsce w czasie określonym przez proceduralną kontrolę czasu.
Na końcu kroku czasowego, w którym upłynęło dane opóźnienie lub nastąpiło odpowiednie zdarzenie, symulator wykonuje przypisanie wartości po lewej stronie.
Przykład
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
Warunki
Instrukcja warunkowa (lub instrukcja if-else) służy do podjęcia decyzji, czy instrukcja zostanie wykonana, czy nie.
Formalnie składnia jest następująca -
<statement>
::= if ( <expression> ) <statement_or_null>
||= if ( <expression> ) <statement_or_null>
else <statement_or_null>
<statement_or_null>
::= <statement>
||= ;
Oceniane jest <wyrażenie>; jeśli to prawda (to znaczy ma znaną wartość niezerową), wykonywana jest pierwsza instrukcja. Jeśli jest fałszywa (ma wartość zerową lub wartość x lub z), pierwsza instrukcja nie jest wykonywana. Jeśli istnieje instrukcja else i <expression> ma wartość false, wykonywana jest instrukcja else. Ponieważ wartość liczbowa wyrażenia if jest testowana na zero, możliwe są pewne skróty.
Na przykład następujące dwie instrukcje wyrażają tę samą logikę -
if (expression)
if (expression != 0)
Ponieważ część else elementu if-else jest opcjonalna, może dojść do zamieszania, gdy w zagnieżdżonej sekwencji if zostanie pominięty element else. Rozwiązuje się to poprzez zawsze łączenie else z najbliższym poprzednim, jeśli brakuje mu innego.
Przykład
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;
Konstrukcja: if- else- if
Poniższa konstrukcja pojawia się na tyle często, że warto ją pokrótce omówić.
Example
if (<expression>)
<statement>
else if (<expression>)
<statement>
else if (<expression>)
<statement>
else
<statement>
Ta sekwencja if (znana jako konstrukcja if-else-if) jest najbardziej ogólnym sposobem pisania decyzji wielostronnej. Wyrażenia są oceniane w kolejności; jeśli jakieś wyrażenie jest prawdziwe, wykonywana jest instrukcja z nim związana, a to kończy cały łańcuch. Każda instrukcja jest pojedynczą instrukcją lub blokiem instrukcji.
Ostatnia część else konstrukcji if-else-if obsługuje „żaden z powyższych” lub domyślny przypadek, w którym żaden z pozostałych warunków nie został spełniony. Czasami nie ma wyraźnej akcji domyślnej; w takim przypadku końcowe else można pominąć lub użyć go do sprawdzania błędów w celu wykrycia niemożliwego warunku.
Instrukcja Case
Instrukcja case jest specjalną wielostronną instrukcją decyzyjną, która sprawdza, czy wyrażenie pasuje do jednego z wielu innych wyrażeń i odpowiednio rozgałęzia się. Instrukcja case jest przydatna do opisania, na przykład, dekodowania instrukcji mikroprocesora. Instrukcja case ma następującą składnię -
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>
Wyrażenia przypadków są oceniane i porównywane w dokładnej kolejności, w jakiej zostały podane. Jeśli podczas wyszukiwania liniowego jedno z wyrażeń pozycji sprawy pasuje do wyrażenia w nawiasach, wykonywana jest instrukcja powiązana z tą pozycją sprawy. Jeśli wszystkie porównania zakończą się niepowodzeniem i zostanie podana pozycja domyślna, wykonywana jest instrukcja pozycji domyślnej. Jeśli nie podano domyślnej instrukcji, a wszystkie porównania zakończą się niepowodzeniem, to żadna z instrukcji pozycji przypadku nie zostanie wykonana.
Oprócz składni instrukcja case różni się od wieloczynnikowej konstrukcji if-else-if na dwa ważne sposoby -
Wyrażenia warunkowe w konstrukcji if-else-if są bardziej ogólne niż porównywanie jednego wyrażenia z kilkoma innymi, jak w przypadku instrukcji.
Instrukcja case zapewnia ostateczny wynik, gdy w wyrażeniu występują wartości x i z.
Instrukcje pętli
Istnieją cztery typy instrukcji zapętlających. Umożliwiają kontrolowanie wykonania instrukcji zero, jeden lub więcej razy.
forever bez przerwy wykonuje polecenie.
Powtórz wykonuje instrukcję określoną liczbę razy.
while wykonuje instrukcję, dopóki wyrażenie nie stanie się fałszywe. Jeśli wyrażenie zaczyna się jako fałsz, instrukcja w ogóle nie jest wykonywana.
do kontroli wykonywania powiązanych instrukcji w trzystopniowym procesie, jak następuje -
Wykonuje przypisanie zwykle używane do inicjalizacji zmiennej, która kontroluje liczbę wykonywanych pętli
Oblicza wyrażenie - jeśli wynik jest równy zero, pętla for kończy działanie, a jeśli nie jest zerem, pętla for wykonuje powiązane instrukcje, a następnie wykonuje krok 3
Wykonuje przypisanie zwykle używane do modyfikowania wartości zmiennej loopcontrol, a następnie powtarza krok 2
Poniżej przedstawiono reguły składni instrukcji pętli -
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
Kontrola opóźnień
Kontrola opóźnienia
Wykonanie instrukcji proceduralnej może być kontrolowane z opóźnieniem przy użyciu następującej składni -
<statement>
::= <delay_control> <statement_or_null>
<delay_control>
::= # <NUMBER>
||= # <identifier>
||= # ( <mintypmax_expression> )
Poniższy przykład opóźnia wykonanie zadania o 10 jednostek czasu -
# 10 rega = regb;
W następnych trzech przykładach podano wyrażenie następujące po znaku liczby (#). Wykonanie przypisania opóźnia się o czas symulacji określony przez wartość wyrażenia.
Kontrola zdarzeń
Wykonanie instrukcji proceduralnej można zsynchronizować ze zmianą wartości w sieci lub rejestrze albo wystąpieniem zadeklarowanego zdarzenia, używając następującej składni sterowania zdarzeniami -
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> to wyrażenie, którego wynikiem jest wartość jednobitowa.
Zmiany wartości w sieciach i rejestrach mogą służyć jako zdarzenia wyzwalające wykonanie instrukcji. Nazywa się to wykrywaniem niejawnego zdarzenia. Składnia Verilog umożliwia również wykrycie zmiany na podstawie kierunku zmiany - to znaczy w kierunku wartości 1 (posedge) lub w kierunku wartości 0 (negedge). Zachowanie posedge i negedge dla nieznanych wartości wyrażenia jest następujące -
- negedge jest wykrywany przy przejściu od 1 do nieznanego iz nieznanego do 0
- pozedge jest wykrywany przy przejściu od 0 do nieznanego iz nieznanego do 1
Procedury: zawsze i początkowe bloki
Wszystkie procedury w Verilog są określone w jednym z następujących czterech bloków. 1) Bloki początkowe 2) Zawsze bloki 3) Zadanie 4) Funkcja
Instrukcje początkowe i always są włączane na początku symulacji. Początkowe bloki są wykonywane tylko raz, a jego aktywność kończy się po zakończeniu instrukcji. W przeciwieństwie do tego, zawsze bloki są wykonywane wielokrotnie. Jego aktywność zanika dopiero po zakończeniu symulacji. Nie ma ograniczeń co do liczby początkowych i zawsze bloków, które można zdefiniować w module. Zadania i funkcje to procedury, które są włączane z jednego lub kilku miejsc w innych procedurach.
Bloki początkowe
Składnia początkowej instrukcji jest następująca -
<initial_statement>
::= initial <statement>
Poniższy przykład ilustruje użycie instrukcji początkowej do inicjalizacji zmiennych na początku symulacji.
Initial
Begin
Areg = 0; // initialize a register
For (index = 0; index < size; index = index + 1)
Memory [index] = 0; //initialize a memory
Word
End
Innym typowym zastosowaniem początkowych bloków jest specyfikacja opisów przebiegów, które są wykonywane raz w celu dostarczenia bodźca do głównej części symulowanego obwodu.
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
Zawsze blokuje
Instrukcja „always” powtarza się w sposób ciągły przez cały przebieg symulacji. Poniżej podano składnię instrukcji always
<always_statement>
::= always <statement>
Instrukcja „always”, ze względu na swoją zapętloną naturę, jest użyteczna tylko w połączeniu z jakąś formą kontroli czasu. Jeśli instrukcja „always” nie zapewnia czasu na przejście do przodu, instrukcja „always” tworzy warunek zakleszczenia symulacji. Na przykład poniższy kod tworzy nieskończoną pętlę z zerowym opóźnieniem -
Always areg = ~areg;
Zapewnienie kontroli czasu w powyższym kodzie tworzy potencjalnie przydatny opis - jak w poniższym przykładzie -
Always #half_period areg = ~areg;