Pipeline-Implementierung im Vergleich zur Low-Latency-Implementierung eines Zahlenwürfels in Verilog

Ich habe mich mit FPGA-Design beschäftigt und bin dann auf diese Begriffe Throughputund gestoßen Latency. Der Autor lieferte also ein Beispiel für eine hochgradig Pipeline-Implementierung zum Finden der Kubikwurzel einer Zahl:

Geben Sie hier die Bildbeschreibung ein

was anscheinend das folgende Logikdiagramm hat:

Geben Sie hier die Bildbeschreibung ein

.Dann hat der Autor versucht, die 'Latenz' zu reduzieren, indem er den Code wie folgt geschrieben hat:

Geben Sie hier die Bildbeschreibung ein

die sich so abrollt:

Geben Sie hier die Bildbeschreibung ein

Meine Frage ist, dass mir beide Implementierungen fast identisch erscheinen, also wie unterscheiden sie sich? Ich verstehe die blockierende und nicht blockierende Zuweisung, aber wie verursachen sie in diesem Fall ein anderes Logikdiagramm? Wie verringert es latencyim zweiten Fall die der Schaltung?

Antworten (1)

Logisch gesehen hast du Recht, sie sind identisch. Die erste Implementierung ist jedoch getaktet (beachten Sie die Always- @(posedge clk)Anweisungen im Vergleich zu den @*Anweisungen in der zweiten), sodass sie eine Latenz von mindestens drei Zyklen aufweist, die von der Taktperiode bestimmt wird. Die zweite Implementierung wird vollständig asynchron berechnet, sodass ihre Latenz nur von der Geschwindigkeit Ihrer Technologie abhängt (wie schnell sich die Multiplikationen und Routing-Verzögerungen auflösen).

Was dieses Beispiel veranschaulicht, ist, dass viele digitale Funktionen in einer stark gepipelineten Weise oder in einem langen Logikpfad oder irgendwo dazwischen implementiert werden können. Welche Sie wählen, kann von vielen Faktoren abhängen. Die erste Implementierung ist weniger ressourceneffizient, da sie viele zusätzliche Register verwendet, um die Pipeline-Werte von Zyklus zu Zyklus zu speichern. Der zweite ist ressourceneffizienter , aber wenn Sie ihn in ein synchrones System mit hoher Taktfrequenz einsetzen, wird es schwieriger, das Timing auf zu schließen , weil er so viel Logik in einen Zyklus passt.

Bemerkenswerterweise haben beide Implementierungen einen gleichwertigen Durchsatz . Beide können in jedem Taktzyklus eine Berechnung durchführen, nur dass die erste Implementierung die Ausgabe drei Taktzyklen nach dem Empfang der entsprechenden Eingaben liefert.

Sie sagen also, nur die alwaysAussage macht den Unterschied? Was ist mit den Registern? Warum ist es in der zweiten Implementierung nicht vorhanden?
Ja, es kommt auf die Always-Anweisungen an. Das ist auch der Grund, warum Sie in der ersten nicht blockierende Anweisungen sehen (immer für getaktete Aufgaben verwendet) und in der zweiten blockieren. Sie sehen nur Register im ersten, weil sie ein "Speicher"-Gerät sind, das in der getakteten Implementierung benötigt wird, um Werte von Zyklus zu Zyklus zu speichern, aber in der ungetakteten Implementierung unnötig ist.
Schön ... das wusste ich nicht ... auch wie haben Sie die 3-Takt-Latenzzeit genau berechnet ... bei mir kommen in der ersten Implementierung die Daten an und danach werden bei der nächsten steigenden Flanke alle Werte gleichzeitig zugewiesen ... irre ich mich? ... aber für die zweite Implementierung, da es seriell ist, sollte es wahrscheinlich 1 Taktzyklus + 2 Multiplikationszyklen dauern, denke ich?
@DuttaA Ich kann die Latenz von drei Takten auf einen Blick auf das Logikdiagramm sehen, das drei Registerebenen zeigt. Die zweite Implementierung führt keine Taktung durch, sodass die Latenz hauptsächlich die Zeit für die Multiplikationen wäre, obwohl Sie in einer realen Schaltung auch Verzögerungen aufgrund von Routing, Puffern usw. haben würden.
Was ist, wenn wir kein solches Diagramm haben?
Sie können es durch Analyse bestimmen. Der Schlüssel besteht darin, zu beachten, dass die Zuweisungen auf der Position der Uhr in einer nicht blockierenden Weise erfolgen. Dann haben Sie nur noch die Anzahl der getakteten Zuweisungen vom Eingang zum Ausgang. In diesem Fall X -> X1/XPower1 (1 Zyklus), X1/Xpower1 -> X2/Xpower2 (2 Zyklen) und X2/Xpower2 -> Xpower (3 Zyklen). In diesem Codebeispiel ist es ziemlich klar organisiert, um dies zu zeigen, aber es ist nicht immer so.