Verhaltensmodellierung und Timing in Verilog
Verhaltensmodelle in Verilog enthalten prozedurale Anweisungen, die die Simulation steuern und Variablen der Datentypen manipulieren. Diese alle Aussagen sind in den Verfahren enthalten. Jeder Prozedur ist ein Aktivitätsfluss zugeordnet.
Während der Simulation des Verhaltensmodells beginnen alle durch die Anweisungen 'always' und 'initial' definierten Flüsse zum Simulationszeitpunkt 'zero' zusammen. Die Anfangsanweisungen werden einmal ausgeführt, und die Always-Anweisungen werden wiederholt ausgeführt. In diesem Modell werden die Registervariablen a und b zum Simulationszeitpunkt 'Null' auf binär 1 bzw. 0 initialisiert. Die erste Anweisung ist dann abgeschlossen und wird während dieses Simulationslaufs nicht erneut ausgeführt. Diese anfängliche Anweisung enthält einen Anfang-Ende-Block (auch als sequentieller Block bezeichnet) von Anweisungen. In diesem Anfangstyp-Block wird a zuerst initialisiert, gefolgt von b.
Beispiel für Verhaltensmodellierung
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
Verfahrensaufgaben
Prozedurale Zuweisungen dienen zum Aktualisieren von Reg-, Integer-, Zeit- und Speichervariablen. Es gibt einen signifikanten Unterschied zwischen prozeduraler Zuweisung und kontinuierlicher Zuweisung, wie unten beschrieben -
Kontinuierliche Zuweisungen steuern Netzvariablen und werden ausgewertet und aktualisiert, wenn ein Eingabeoperand den Wert ändert.
Prozedurale Zuweisungen aktualisieren den Wert von Registervariablen unter der Kontrolle der sie umgebenden prozeduralen Flusskonstrukte.
Die rechte Seite einer prozeduralen Zuweisung kann ein beliebiger Ausdruck sein, der einen Wert ergibt. Teileauswahlen auf der rechten Seite müssen jedoch konstante Indizes haben. Die linke Seite gibt die Variable an, die die Zuweisung von der rechten Seite erhält. Die linke Seite einer prozeduralen Aufgabe kann eine der folgenden Formen annehmen:
Register-, Ganzzahl-, Real- oder Zeitvariable - Eine Zuordnung zur Namensreferenz eines dieser Datentypen.
Bitauswahl eines Registers, einer Ganzzahl, einer Real- oder Zeitvariablen - Eine Zuordnung zu einem einzelnen Bit, bei der die anderen Bits unberührt bleiben.
Teilauswahl eines Registers, einer Ganzzahl, einer Real- oder Zeitvariablen - Eine Teilauswahl von zwei oder mehr zusammenhängenden Bits, die den Rest der Bits unberührt lässt. Für das Teileauswahlformular sind nur konstante Ausdrücke zulässig.
Speicherelement - Ein einzelnes Wort eines Speichers. Beachten Sie, dass Bitauswahl und Teilauswahl bei Speicherelementreferenzen unzulässig sind.
Verkettung einer der oben genannten Formen - Es kann eine Verkettung einer der vorherigen vier Formen angegeben werden, die das Ergebnis des Ausdrucks auf der rechten Seite effektiv partitioniert und die Partitionsteile der Reihe nach den verschiedenen Teilen der Verkettung zuordnet.
Zuordnungsverzögerung (nicht zur Synthese)
Bei einer verzögerten Zuweisung vergehen Δt Zeiteinheiten, bevor die Anweisung ausgeführt und die Zuweisung für die linke Hand vorgenommen wird. Bei einer Verzögerung innerhalb der Zuweisung wird die rechte Seite sofort ausgewertet, es gibt jedoch eine Verzögerung von Δt, bevor das Ergebnis in die Zuweisung für die linke Hand eingefügt wird. Wenn eine andere Prozedur während Δt ein Signal auf der rechten Seite ändert, wirkt sich dies nicht auf die Ausgabe aus. Verzögerungen werden von Synthesewerkzeugen nicht unterstützt.
Syntax
Procedural AssignmentVariable = Ausdruck
Delayed assignment# Δt Variable = Ausdruck;
Intra-assignment delayVariable = # Δt Ausdruck;
Beispiel
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. */
Zuweisungen blockieren
Eine blockierende prozedurale Zuweisungsanweisung muss vor der Ausführung der darauf folgenden Anweisungen in einem sequentiellen Block ausgeführt werden. Eine blockierende prozedurale Zuweisungsanweisung verhindert nicht die Ausführung von Anweisungen, die ihr in einem parallelen Block folgen.
Syntax
Die Syntax für eine blockierende prozedurale Zuweisung lautet wie folgt:
<lvalue> = <timing_control> <expression>
Wobei lvalue ein Datentyp ist, der für eine prozedurale Zuweisungsanweisung gültig ist, = der Zuweisungsoperator ist und die Zeitsteuerung die optionale Verzögerung innerhalb der Zuweisung ist. Die Zeitsteuerungsverzögerung kann entweder eine Verzögerungssteuerung (zum Beispiel # 6) oder eine Ereignissteuerung (zum Beispiel @ (posedge clk)) sein. Der Ausdruck ist der Wert auf der rechten Seite, den der Simulator der linken Seite zuweist. Der Zuweisungsoperator =, der zum Blockieren von prozeduralen Zuweisungen verwendet wird, wird auch von prozeduralen fortlaufenden Zuweisungen und fortlaufenden Zuweisungen verwendet.
Beispiel
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
Nicht blockierende (RTL) Zuweisungen
Mit der nicht blockierenden prozeduralen Zuweisung können Sie Zuweisungen planen, ohne den prozeduralen Ablauf zu blockieren. Sie können die nicht blockierende Verfahrensanweisung verwenden, wenn Sie innerhalb eines Zeitschritts mehrere Registerzuweisungen vornehmen möchten, ohne Rücksicht auf Reihenfolge oder Abhängigkeit voneinander.
Syntax
Die Syntax für eine nicht blockierende prozedurale Zuweisung lautet wie folgt:
<lvalue> <= <timing_control> <expression>
Wenn lvalue ein Datentyp ist, der für eine prozedurale Zuweisungsanweisung gültig ist, ist <= der nicht blockierende Zuweisungsoperator und die Zeitsteuerung die optionale Zeitsteuerung innerhalb der Zuweisung. Die Zeitsteuerungsverzögerung kann entweder eine Verzögerungssteuerung oder eine Ereignissteuerung sein (zum Beispiel @ (posedge clk)). Der Ausdruck ist der Wert auf der rechten Seite, den der Simulator der linken Seite zuweist. Der nicht blockierende Zuweisungsoperator ist derselbe Operator, den der Simulator für den weniger als ungleichen relationalen Operator verwendet. Der Simulator interpretiert den Operator <= als relationalen Operator, wenn Sie ihn in einem Ausdruck verwenden, und interpretiert den Operator <= als Zuweisungsoperator, wenn Sie ihn in einem nicht blockierenden prozeduralen Zuweisungskonstrukt verwenden.
Wie der Simulator nicht blockierende prozedurale Zuweisungen bewertet Wenn der Simulator auf eine nicht blockierende prozedurale Zuweisung stößt, bewertet der Simulator die nicht blockierende prozedurale Zuweisung und führt sie in zwei Schritten aus:
Der Simulator wertet die rechte Seite aus und plant die Zuweisung des neuen Werts zu einem Zeitpunkt, der durch eine prozedurale Zeitsteuerung festgelegt wird. Der Simulator wertet die rechte Seite aus und plant die Zuweisung des neuen Werts zu einem Zeitpunkt, der durch eine prozedurale Zeitsteuerung festgelegt wird.
Am Ende des Zeitschritts, in dem die angegebene Verzögerung abgelaufen ist oder das entsprechende Ereignis stattgefunden hat, führt der Simulator die Zuweisung aus, indem er den Wert der linken Seite zuweist.
Beispiel
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
Bedingungen
Die bedingte Anweisung (oder if-else-Anweisung) wird verwendet, um eine Entscheidung darüber zu treffen, ob eine Anweisung ausgeführt wird oder nicht.
Formal lautet die Syntax wie folgt:
<statement>
::= if ( <expression> ) <statement_or_null>
||= if ( <expression> ) <statement_or_null>
else <statement_or_null>
<statement_or_null>
::= <statement>
||= ;
Der <Ausdruck> wird ausgewertet. Wenn es wahr ist (dh einen bekannten Wert ungleich Null hat), wird die erste Anweisung ausgeführt. Wenn es falsch ist (einen Nullwert hat oder der Wert x oder z ist), wird die erste Anweisung nicht ausgeführt. Wenn eine else-Anweisung vorhanden ist und <expression> false ist, wird die else-Anweisung ausgeführt. Da der numerische Wert des if-Ausdrucks auf Null getestet wird, sind bestimmte Verknüpfungen möglich.
Zum Beispiel drücken die folgenden zwei Anweisungen dieselbe Logik aus -
if (expression)
if (expression != 0)
Da der else-Teil eines if-else optional ist, kann es zu Verwirrung kommen, wenn ein else in einer verschachtelten if-Sequenz weggelassen wird. Dies wird gelöst, indem das else immer mit dem nächstgelegenen vorherigen verknüpft wird, wenn das else fehlt.
Beispiel
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;
Konstruktion von: if- else- if
Die folgende Konstruktion kommt so oft vor, dass es sich lohnt, eine kurze separate Diskussion zu führen.
Example
if (<expression>)
<statement>
else if (<expression>)
<statement>
else if (<expression>)
<statement>
else
<statement>
Diese Folge von ifs (bekannt als if-else-if-Konstrukt) ist die allgemeinste Art, eine Mehrwegentscheidung zu schreiben. Die Ausdrücke werden der Reihe nach ausgewertet. Wenn ein Ausdruck wahr ist, wird die ihm zugeordnete Anweisung ausgeführt, wodurch die gesamte Kette beendet wird. Jede Anweisung ist entweder eine einzelne Anweisung oder ein Anweisungsblock.
Der letzte else-Teil des if-else-if-Konstrukts behandelt den Fall 'keine der oben genannten' oder den Standardfall, in dem keine der anderen Bedingungen erfüllt war. Manchmal gibt es keine explizite Aktion für die Standardeinstellung. In diesem Fall kann das nachfolgende else weggelassen oder zur Fehlerprüfung verwendet werden, um einen unmöglichen Zustand zu erfassen.
Fallbeschreibung
Die case-Anweisung ist eine spezielle Mehrwegentscheidungsanweisung, die prüft, ob ein Ausdruck mit einer Reihe anderer Ausdrücke übereinstimmt, und entsprechend verzweigt. Die case-Anweisung ist nützlich, um beispielsweise die Decodierung eines Mikroprozessorbefehls zu beschreiben. Die case-Anweisung hat die folgende Syntax:
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>
Die Fallausdrücke werden in der genauen Reihenfolge ausgewertet und verglichen, in der sie angegeben sind. Wenn während der linearen Suche einer der Ausdrücke für Fallelemente mit dem Ausdruck in Klammern übereinstimmt, wird die diesem Fallelement zugeordnete Anweisung ausgeführt. Wenn alle Vergleiche fehlschlagen und das Standardelement angegeben ist, wird die Standardelementanweisung ausgeführt. Wenn die Standardanweisung nicht angegeben wird und alle Vergleiche fehlschlagen, wird keine der case item-Anweisungen ausgeführt.
Abgesehen von der Syntax unterscheidet sich die case-Anweisung in zwei wichtigen Punkten vom Mehrweg-if-else-if-Konstrukt:
Die bedingten Ausdrücke im if-else-if-Konstrukt sind allgemeiner als der Vergleich eines Ausdrucks mit mehreren anderen, wie in der case-Anweisung.
Die case-Anweisung liefert ein endgültiges Ergebnis, wenn ein Ausdruck x- und z-Werte enthält.
Schleifenanweisungen
Es gibt vier Arten von Schleifenanweisungen. Sie bieten eine Möglichkeit, die Ausführung einer Anweisung null, einmal oder mehrmals zu steuern.
führt für immer kontinuierlich eine Anweisung aus.
repeat führt eine Anweisung eine feste Anzahl von Malen aus.
while führt eine Anweisung aus, bis ein Ausdruck falsch wird. Wenn der Ausdruck mit false beginnt, wird die Anweisung überhaupt nicht ausgeführt.
zur Steuerung der Ausführung der zugehörigen Anweisung (en) durch einen dreistufigen Prozess wie folgt:
Führt eine Zuweisung aus, die normalerweise zum Initialisieren einer Variablen verwendet wird, die die Anzahl der ausgeführten Schleifen steuert
Wertet einen Ausdruck aus. Wenn das Ergebnis Null ist, wird die for-Schleife beendet. Wenn es nicht Null ist, führt die for-Schleife die zugehörigen Anweisungen aus und führt dann Schritt 3 aus
Führt eine Zuweisung aus, die normalerweise zum Ändern des Werts der Schleifensteuerungsvariablen verwendet wird, und wiederholt dann Schritt 2
Im Folgenden sind die Syntaxregeln für die Schleifenanweisungen aufgeführt:
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
Verzögerungssteuerung
Verzögerungssteuerung
Die Ausführung einer prozeduralen Anweisung kann mithilfe der folgenden Syntax verzögert gesteuert werden:
<statement>
::= <delay_control> <statement_or_null>
<delay_control>
::= # <NUMBER>
||= # <identifier>
||= # ( <mintypmax_expression> )
Das folgende Beispiel verzögert die Ausführung der Zuordnung um 10 Zeiteinheiten -
# 10 rega = regb;
Die nächsten drei Beispiele enthalten einen Ausdruck nach dem Nummernzeichen (#). Die Ausführung der Zuweisung verzögert sich um die Simulationszeit, die durch den Wert des Ausdrucks angegeben wird.
Ereignissteuerung
Die Ausführung einer prozeduralen Anweisung kann mit einer Wertänderung in einem Netz oder Register oder dem Auftreten eines deklarierten Ereignisses mithilfe der folgenden Ereignissteuerungssyntax synchronisiert werden:
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> ist ein Ausdruck, der in einen Ein-Bit-Wert aufgelöst wird.
Wertänderungen an Netzen und Registern können als Ereignisse verwendet werden, um die Ausführung einer Anweisung auszulösen. Dies wird als Erkennen eines impliziten Ereignisses bezeichnet. Mit der Verilog-Syntax können Sie Änderungen auch anhand der Richtung der Änderung erkennen, dh in Richtung des Werts 1 (Posedge) oder des Werts 0 (Negedge). Das Verhalten von posedge und negedge für unbekannte Ausdruckswerte ist wie folgt:
- Beim Übergang von 1 nach unbekannt und von unbekannt nach 0 wird ein Negedge erkannt
- Beim Übergang von 0 nach unbekannt und von unbekannt nach 1 wird ein Posedge erkannt
Prozeduren: Immer und Anfangsblöcke
Alle Prozeduren in Verilog werden in einem der folgenden vier Blöcke angegeben. 1) Anfangsblöcke 2) Immer Blöcke 3) Aufgabe 4) Funktion
Die anfänglichen und immer-Anweisungen werden zu Beginn der Simulation aktiviert. Die Anfangsblöcke werden nur einmal ausgeführt und ihre Aktivität stirbt, wenn die Anweisung beendet ist. Im Gegensatz dazu werden die Always-Blöcke wiederholt ausgeführt. Seine Aktivität stirbt nur, wenn die Simulation beendet wird. Die Anzahl der anfänglichen und immer blockierten Blöcke, die in einem Modul definiert werden können, ist unbegrenzt. Aufgaben und Funktionen sind Prozeduren, die an einer oder mehreren Stellen in anderen Prozeduren aktiviert werden.
Anfangsblöcke
Die Syntax für die ursprüngliche Anweisung lautet wie folgt:
<initial_statement>
::= initial <statement>
Das folgende Beispiel zeigt die Verwendung der Anfangsanweisung zur Initialisierung von Variablen zu Beginn der Simulation.
Initial
Begin
Areg = 0; // initialize a register
For (index = 0; index < size; index = index + 1)
Memory [index] = 0; //initialize a memory
Word
End
Eine andere typische Verwendung der anfänglichen Blöcke ist die Spezifikation von Wellenformbeschreibungen, die einmal ausgeführt werden, um den Hauptteil der zu simulierenden Schaltung zu stimulieren.
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
Blockiert immer
Die Anweisung 'always' wird während des gesamten Simulationslaufs kontinuierlich wiederholt. Die Syntax für die always-Anweisung ist unten angegeben
<always_statement>
::= always <statement>
Die 'always'-Anweisung ist aufgrund ihrer Schleifenfunktion nur dann nützlich, wenn sie in Verbindung mit einer Form der Zeitsteuerung verwendet wird. Wenn eine 'always'-Anweisung keine Zeit zum Vorrücken bietet, erstellt die' always'-Anweisung eine Simulations-Deadlock-Bedingung. Der folgende Code erstellt beispielsweise eine Endlosschleife mit null Verzögerung -
Always areg = ~areg;
Durch die Bereitstellung einer Zeitsteuerung für den obigen Code wird eine potenziell nützliche Beschreibung erstellt - wie im folgenden Beispiel -
Always #half_period areg = ~areg;