Der Baustein: Ein D-Flip-Flop
Alles im synchronen Design läuft auf ein winziges Stück Hardware zurück: das D-Flip-Flop. Es hat einen Takteingang, einen Daten-Eingang und einen Daten-Ausgang. An jeder steigenden Taktflanke erfasst es den Wert von D und hält ihn bis zur nächsten Flanke. Das war's.
In Verilog:
always @(posedge clk) begin
q <= d;
end
Drei Zeilen, ein Flip-Flop. Das <= ist non-blocking, was genau dem Verhalten echter Flip-Flops entspricht (siehe Blocking vs Non-blocking). Die posedge clk-Sensitivität macht es sequentiell.
Stapele viele davon mit kombinatorischer Logik dazwischen und du hast jede synchrone Schaltung, die du bauen willst.
Ein Register mit Reset
Echte Designs brauchen immer einen Reset - einen Weg, das System beim Einschalten oder auf Anforderung in einen bekannten Zustand zu setzen:
Das ist ein synchroner Reset - die Reset-Bedingung wird wie jeder andere Eingang an einer Taktflanke ausgewertet. Der Synthesizer erzeugt ein Flip-Flop mit einem 2-zu-1-Mux am Daten-Eingang: ist reset high, speist der Mux Null; sonst d.
Für die meisten Designs ist synchroner Reset die richtige Wahl. Einfacheres Timing, gutes Zusammenspiel mit FPGAs, und das Reset-Signal kann von überall kommen - es braucht keine eigene takt-ausgerichtete Quelle.
Ein Register mit Enable
Oft willst du ein Register, das nur aktualisiert, wenn ihm etwas dazu sagt. Verwende ein if innerhalb des getakteten Blocks und vertraue auf das implizite "vorigen Wert halten"-Verhalten des fehlenden else:
Das ist das kanonische "Load-Enable-Register". Es taucht überall auf - Konfigurationsregister, Pipeline-Stufen, die nur vorrücken, wenn die nachgelagerte Stufe bereit ist, Counter, die pausieren und weiterlaufen. Das Weglassen des finalen else ist absichtlich und in einem getakteten Block sicher: Ein Flip-Flop erinnert sich bereits an seinen vorigen Wert, also heißt "nichts tun" "halten".
In einem kombinatorischen Block würde derselbe Code ein Latch ableiten. Andere Regeln, gleiche Syntax.
Ein Counter
Ein Counter ist ein Register, dessen nächster Wert sein aktueller Wert plus eins ist:
Der Counter inkrementiert in jedem Taktzyklus, wenn enable high ist. Nach 16 Zyklen springt er auf 0 zurück (weil wir 4 Bit deklariert haben und 15 + 1 zu 0 überläuft). Dieses Wrapping ist N-Bit-Arithmetik immanent und genau das, was ein echter Hardware-Counter tut.
Ein Schieberegister
Flip-Flops stapeln ergibt ein Schieberegister. Der Trick: alle Verschiebungen in einer non-blocking-Anweisung erledigen:
Der Body ist out <= {out[WIDTH-2:0], in} - die unteren Bits des aktuellen out mit dem neuen in konkatenieren und das Ganze zuweisen. Weil non-blocking, liest die RHS das alte out, bevor die LHS aktualisiert. Der Effekt: ein sauberer N-Bit-Shift in einem einzigen Taktzyklus.
Das ist das Schieberegister-Muster in seiner kleinsten Form. Es verallgemeinert sich auf LFSRs, serielle Sender, Deserializer - jedes Design, in dem Daten pro Takt durch eine Flip-Flop-Kette wandern.
Pipelines
Eine Pipeline ist eine Kette von Registern, die durch kombinatorische Logik getrennt sind. Jede Stufe verarbeitet Daten der vorigen Stufe und speist die nächste:
Drei Stufen, drei Zyklen Latenz, aber ein neues Ergebnis pro Zyklus, sobald die Pipeline gefüllt ist. Die Latenz ist 3 Takte, weil die Daten durch drei Flip-Flops gehen; der Durchsatz ist 1 Operation pro Takt, weil alle drei Stufen gleichzeitig an unterschiedlichen Eingängen arbeiten.
So erreichen Hochleistungs-Designs ihre Durchsatzziele: Stufen kurz halten, tiefer pipelinen und Parallelität die Arbeit machen lassen.
Asynchroner Reset (wenn nötig)
Manchmal kannst du nicht auf eine Taktflanke warten, um Reset zu aktivieren - der Chip wird heruntergefahren, der Takt ist gated, ein externer Watchdog reißt die Leitung. In diesen Fällen:
always @(posedge clk or negedge reset_n) begin
if (~reset_n) q <= 0;
else q <= d;
end
Die Sensitivitätsliste hat jetzt sowohl eine Taktflanke als auch eine Resetflanke. Das Flip-Flop reagiert sofort auf eine von beiden. reset_n ist per Konvention active-low (aktiviert, wenn low), deshalb der Test ~reset_n.
Asynchroner Reset hat Trade-offs: Schwieriger zeitlich zu analysieren, kann beim De-Assert Metastabilität verursachen, wenn man nicht aufpasst, nicht in allen FPGA-Architekturen portabel. Nimm standardmäßig synchronen Reset, async nur, wenn das Design es verlangt.
Wie es weitergeht
Du kannst jetzt jeden synchronen Datenpfad bauen. Das nächste Doc - Endliche Automaten - kombiniert ein getaktetes Register mit einer case-Anweisung zu dem Standard-FSM-Idiom: dem Arbeitspferd jedes Controllers, jeder Protokoll-Engine und jedes Entscheidungs-Blocks im Digital-Design.
Häufig gestellte Fragen
Was ist getaktete Logik in Verilog?
Logik, deren Updates durch ein Taktsignal gegatet werden. Das Standardidiom ist always @(posedge clk) target <= next_value; - bei jeder steigenden Flanke von clk übernimmt target next_value. Diese eine Zeile beschreibt in Hardware ein Flip-Flop. Stapelt man viele davon mit kombinatorischer Logik dazwischen, baut man Counter, Schieberegister, Pipelines - alles Synchrone.
Was ist der Unterschied zwischen synchronem und asynchronem Reset in Verilog?
Synchroner Reset nutzt always @(posedge clk) if (reset) ... - der Reset wird wie jeder andere Eingang an einer Taktflanke abgetastet. Asynchroner Reset nutzt always @(posedge clk or negedge reset_n) if (~reset_n) ... - der Block triggert entweder bei einer Taktflanke oder einer Reset-Aktivierung, sodass der Reset sofort wirkt. Synchron ist die Standardwahl; asynchron nimmt man, wenn der Reset garantiert wirken muss, selbst wenn der Takt tot ist.
Wie baut man in Verilog eine Pipeline?
Stapele mehrere Stufen always @(posedge clk) stageN_reg <= stageN_combinational; - die kombinatorische Logik jeder Stufe speist das Register der nächsten Stufe, und jedes Register übernimmt an derselben Taktflanke. Das Ergebnis ist eine Pipeline, in die jeden Takt neue Daten eintreten und N Takte später wieder austreten, mit Durchsatz von einem Ergebnis pro Takt.
Was ist ein Schieberegister in Verilog?
Eine Kette von Flip-Flops, bei der jeder Ausgang den Eingang des nächsten speist. Jede Taktflanke schiebt jedes Bit um eine Position. Die kanonische Verilog-Version nutzt non-blocking-Zuweisungen: out <= {out[N-2:0], in}; - diese eine Zeile erzeugt ein N-Bit-Schieberegister, das pro Zyklus ein Bit von in aufnimmt.