Wie werden Verilog-„Always“-Anweisungen in Hardware implementiert?

Die Verilog- alwaysAnweisung, nämlich

always @(/* condition */)
    /* block of code */

führt das aus , block of codewann conditionimmer erfüllt ist. Wie wird ein solcher alwaysBlock in Hardware implementiert?

Ich denke es kommt stark darauf an was das block of codeist..
Und ob die Bedingung ist posedge xoder geradex
@Justin: Nehmen wir an, es gibt keine posedge.

Antworten (3)

Beachten Sie zunächst, dass nicht alle Verilog-Designs synthetisierbar sind. Üblicherweise kann nur eine ganz bestimmte Teilmenge von Konstrukten in einem Design verwendet werden, das in Hardware realisiert werden soll.

Eine wichtige Einschränkung, die auftaucht, ist, dass jede regVariable nur in höchstens einer alwaysAnweisung zugewiesen werden kann. Mit anderen Worten, regs haben eine Affinität zu alwaysBlöcken.

Die folgenden Arten von alwaysBlöcken können allgemein verwendet werden.

always @(*) begin
    // combinational
end

always @(posedge clk) begin
    // sequential
end

Im ersteren Fall *zeigt an, dass der Block immer dann ausgeführt werden sollte, wenn sich irgendein in dem Block verwendetes Signal ändert, oder äquivalent, dass der Block kontinuierlich ausgeführt werden sollte. Daher werden regs, die Affinität zu kombinatorischen alwaysBlöcken haben, als Signale implementiert, die aus anderen Signalen unter Verwendung von kombinatorischer Logik, dh Gattern, berechnet werden.

Register, die eine Affinität zu alwaysBlöcken des letztgenannten Typs haben, sind andererseits Ausgänge von D-Flip-Flops, die auf der ansteigenden Flanke getaktet werden clk(falls verwendet wird auf der abfallenden Flanke negedge). Eingänge zu den Flip-Flops werden wiederum mit kombinatorischer Logik aus anderen Signalen berechnet.

Betrachten Sie das folgende, etwas erfundene Beispiel.

reg out, out_n;
always @(*) begin
    out_n = !out;
end
always @(posedge clk) begin
    out <= !out;
end

Dabei out_nist dem ersten alwaysBlock outder zweite zugeordnet. out_nwird mit einem einzelnen NICHT-Gatter implementiert, das ansteuert out_nund angesteuert wird out(beachten Sie, dass es sich um eine reine kombinatorische Logik handelt). Andererseits outwird von einem Flip-Flop getaktet von angesteuert clk. Der Eingang zum Flip-Flop wird wieder von einem NICHT-Gatter aus berechnet out(das von dem oben erwähnten Flip-Flop angesteuert wird). Optimierende Synthesizer kombinieren die beiden NICHT-Gatter und verwenden ein NICHT-Gatter und ein Flip-Flop.

Abhängig von der Ihnen zur Verfügung stehenden Hardware können auch andere Arten von Konstrukten verwendet werden. Wenn zum Beispiel die Flip-Flops asynchrone Rücksetzungen haben, ist das folgende Konstrukt auch synthetisierbar.

always @(posedge clk or posedge rst) begin
    if (rst)
        // reset
    else
        // sequential
end
Vielen Dank. In Bezug auf die *, dachte ich, dass es darauf hindeutet, dass der Block ausgeführt werden sollte, wenn sich ein Signal im Block ändert (im Gegensatz zum Design ).
@Randomblue, du hast Recht, ich werde die Antwort korrigieren. Beachten Sie jedoch, dass die beiden im Verhalten äquivalent sind.
WAHR; Meinetwegen!

Ein alwaysBlock wird üblicherweise verwendet, um ein Flip-Flop, ein Latch oder einen Multiplexer zu beschreiben. Der Code würde mit einem Flip-Flop, einem Latch oder einem Multiplexer implementiert werden.

In einem FPGA sind ein Flip-Flop und ein Latch im Allgemeinen nur zwei verschiedene Konfigurationen eines allgemeineren Registergeräts. Ein Multiplexer würde aus einem oder mehreren Universallogikelementen (LUTs) aufgebaut werden.

Im Allgemeinen gibt es zwei Möglichkeiten, mit Verilog an das Design heranzugehen:

  1. Visualisieren Sie die gewünschte Logik in Form von Gattern und Registern und finden Sie dann heraus, wie Sie sie in Verilog beschreiben können. Die Synthese-Leitfäden der FPGA-Anbieter oder Anbieter von Synthese-Tools enthalten Musterbeispiele für die gängigsten Strukturen, mit denen Sie möglicherweise arbeiten möchten.

  2. Schreiben Sie einfach Verilog und machen Sie sich keine Gedanken darüber, wie die zugrunde liegende Hardware aussieht. Aber selbst wenn Sie dies tun, müssen Sie immer noch wissen, was synthetisierbar ist und was nicht. Sie werden sich also wieder die von Ihrem Tool-Anbieter bereitgestellte Boilerplate ansehen und diese an Ihre Anwendung anpassen.

BEARBEITEN

Avakars Antwort ist viel besser für Ihre Frage, aber dies hat einige interessante Diskussionen über die Unterschiede zwischen Xilinx und Altera ausgelöst, sodass ich sie nicht löschen werde.

"Flip-Flop und ein Latch sind im Allgemeinen nur zwei verschiedene Konfigurationen" Sind sie das? Ich würde erwarten, dass Latches mit LUTs implementiert werden (vorsichtig, wenn die LUTs nicht störungsfrei sind).
@avakar, ich weiß, dass in allen Xilinx-FPGAs (oder zumindest allen entfernt neueren) die Latches dieselbe Hardware wie ein Flip-Flop verwenden und sich nur um ein einziges Bit im Konfigurationsbitstrom unterscheiden. Bei anderen Marken bin ich mir nicht sicher.
Hmm. Einige ältere Altera-Designs hatten Rückkopplungspfade, die es ermöglichen würden, LUT zur Implementierung von Latches zu verwenden. Es sieht fast so aus, als ob das Hauptrouting benötigt werden könnte, um in den neueren Designs überhaupt Latches zu implementieren. Dies ist jedoch nicht überraschend, da im modernen RTL-Design echte Latches (statt Flip-Flops) selten erwünscht sind.
@avakar, ich bin besser mit Xilinx vertraut, wo das Registergerät als Flip-Flop oder Latch konfiguriert werden kann. Wenn dies bei Altera oder einem anderen Anbieter nicht möglich ist, würde dies den allgemeinen Rat "nicht mit Latches entwerfen" noch stärker machen.
@KevinCathcart und Photon: Ich verstehe, ich bin mit Xilinx nicht vertraut, nur mit der Altera Cyclone-Serie, die keine dedizierte Latch-Schaltung hat.

Wie gesagt wurde, sind nicht alle Always-Blöcke synthetisierbar. Es gibt auch einige Blöcke, die die Synthesewerkzeuge akzeptieren, die aber Ergebnisse erzeugen, die sich von denen eines Simulators unterscheiden.

Zunächst einmal die Empfindlichkeitsliste. Die übliche Regel ist, dass es entweder nur Kantenerkennungskonstrukte enthalten muss (und es gibt normalerweise eine begrenzte Auswahl an möglichen Kombinationen) oder es muss (möglicherweise durch die Verwendung von * oder von systemverilogs always_comb) jedes Signal enthalten, das als Eingang für den Block verwendet wird. Wir nennen ersteren einen sequentiellen Block und letzteren einen kombinatorischen Block. Wenn Sie nur eine Teilmenge von Eingaben in eine kombinatorische Blocksynthese einbeziehen, werden Sie normalerweise von Werkzeugen ignoriert und so getan, als ob die vollständige Liste angegeben worden wäre (Erzeugung von Simulations-/Synthese-Nichtübereinstimmungen).

Zweite Blockierung vs. Noblocking-Zuweisungen. In einem kombinatorischen Block spielt der Unterschied keine große Rolle, aber in einem sequentiellen Block ist er sehr wichtig.

In einem sequentiellen Block modellieren nicht blockierende Zuweisungen ein Register ziemlich direkt, während Blockzuweisungen Variablen modellieren (die je nach Reihenfolge des Setzens und Lesens Register implizieren können oder nicht). In der Regel sollte ein "reg"-Satz, der blockierende Zuweisungen in einem sequentiellen Block verwendet, nur im selben Block gelesen werden, und blockierende und nicht blockierende Zuweisungen sollten nicht auf demselben "reg" gemischt werden.

Das Mischen von blockierenden und nicht blockierenden Zuweisungen für dasselbe Element führt wahrscheinlich zu Synthesefehlern. Das Vornehmen einer blockierenden Zuweisung in einem Block und das Lesen in einem anderen führt wahrscheinlich zu Simulations-/Synthese-Nichtübereinstimmungen (und möglicherweise sogar zu Nichtübereinstimmungen zwischen verschiedenen Simulationsläufen).

Jetzt haben wir die Grundregeln für die Überlegung, wie der Compiler Code in Logik umwandelt.

Der erste Schritt besteht darin, alle Schlaufen aufzurollen. Das bedeutet, dass Schleifen eine maximale Iterationszahl haben müssen, die zur Synthesezeit bestimmt werden kann, oder Sie erhalten einen Synthesefehler.

Dann kann das Tool den Kontrollfluss des Blocks analysieren und in einen Datenfluss umwandeln. Jede Variable wird zu einem oder mehreren Signalen. Jede if-Anweisung oder ein ähnliches Konstrukt wird zu einem oder mehreren Multiplexern, die auswählen, welche Ergebnismenge tatsächlich verwendet wird. Wenn eine Variable ihren Wert von einem Lauf eines kombinatorischen Always-Blocks zum nächsten behält, wird ein Register generiert, um den Wert zu behalten.

Das Tool wird dann wahrscheinlich versuchen, einige Optimierungen anzuwenden.

In quartus können Sie die Ergebnisse dieses Prozesses sehen, nachdem Sie Ihr Projekt erstellt haben, indem Sie zu "Tools->Netzlisten-Viewer->RTL-Viewer" gehen.

Nach der Generierung dieser strukturellen Darstellung in Form von abstrakten Logikelementen geht das Tool dann dazu über, diese abstrakten Elemente auf die Ressourcen abzubilden, über die der Chip tatsächlich verfügt.