Ich weiß, dass ein FPGA Nachschlagetabellen (LUTs) verwendet, um Logikgatter zu synthetisieren. Eine LUT ist ein RAM-Block, der durch eine Reihe von Eingängen indiziert wird. Die Ausgabe ist der an dieser Speicheradresse gespeicherte Wert. Die Magie besteht darin, dass die LUT so programmiert werden kann, dass sie für einen bestimmten Eingang alles anzeigt, was Sie möchten. Eine LUT kann also mit der Wahrheitstabelle eines beliebigen Logikgatters programmiert werden, um sie nachzuahmen! Auf diese Weise synthetisiert ein FPGA die Logikgatter, die Sie in Ihrem HDL-Code angeben.
Ich dachte neulich, wie ahmt ein normaler Computer Logikgatter nach? Soweit ich weiß (was nicht weit ist), wenn ich ein Programm in C++ schreibe, muss es zuerst in Maschinencode kompiliert werden, damit die CPU es lesen kann. Wenn ich dann auf "Ausführen" drücke, geht der Maschinencode in den Speicher, um auf die Verarbeitung durch die CPU zu warten. Mir ist nicht ganz klar, was als nächstes passiert, aber irgendwann muss die CPU die logischen Operationen ausgeführt haben, die mein Programm enthält, richtig? Im Gegensatz zu einem FGPA kann die CPU nicht einfach die Ressourcen synthetisieren, die sie benötigt. Wie führt es das Programm aus?
Meine Vermutungen:
Die CPU verfügt über eine Reihe vorgefertigter Logikgatter. Wenn es im ausgeführten C++-Code auf eine AND-Anweisung stößt, verwendet es eines seiner AND-Gatter. Wenn es eine ODER-Anweisung sieht, verwendet es eines seiner ODER-Gatter; wenn es eine IF-Anweisung sieht, verwendet es eines seiner IF-Gatter; usw.
Oder die Logik wird ähnlich wie eine LUT im Speicher implementiert. Dies ist für mich sinnvoller, da es nicht auf eine begrenzte Anzahl von Gate-Ressourcen angewiesen ist. Wenn mein Programm zum Beispiel tonnenweise OR-Logik benötigt, wird die CPU nicht durch fehlende OR-Gatter ins Stocken geraten.
Also, wie weit bin ich weg?
Bearbeiten: Vielen Dank für die Antworten an alle, ich habe einiges über CPUs und ALUs gelernt. Außerdem ist das "IF-Gatter" in meiner ersten Vermutung ein Tippfehler, das sollte "OR-Gatter" sein (obwohl es nur ein Beispiel ist, würde jedes Logikgatter ausreichen). Sorry für diese Verwirrung.
Tatsächlich ist Ihre erste Vermutung nicht so weit entfernt, wie manche behaupten.
Eine CPU ist um etwas herum aufgebaut, das als "Arithmetic Logic Unit" (ALU) bezeichnet wird, und eine vereinfachte Implementierung davon besteht darin, dass die Logikgatter alle grundlegenden Operationen parallel mit den Eingängen verbinden. Alle möglichen elementaren Berechnungen werden somit parallel durchgeführt, wobei die Ausgabe der tatsächlich gewünschten durch einen Multiplexer ausgewählt wird.
In einer extrem einfachen (Kreidetafel-Modell) CPU werden einige Bits des aktuell ausgeführten Befehls-Opcodes mit diesem Multiplexer verbunden, um ihm mitzuteilen, welches Ergebnis der Logikfunktion verwendet werden soll. (Die anderen, unerwünschten Ergebnisse werden einfach verschwendet)
Die tatsächliche Technologie, die verwendet wird, um die Berechnungen in der ALU zu implementieren, variiert - es könnten "echte" Logikgatter sein, oder es könnten LUTs sein, wenn die CPU in einem LUT-basierten FPGA implementiert ist (eine sehr gute Möglichkeit, die Grundlagen gespeicherter - Program Computing besteht darin, einen einfachen Prozessor zu entwerfen und ihn in einen Logiksimulator und vielleicht dann in ein FPGA einzubauen).
Ziemlich weit weg.
Eine CPU besteht aus echten Gattern (nicht programmierbaren LUTs). Die Tastenoperationen an Daten werden in einem Logikblock durchgeführt, der oft als ALU (arithmetisch-logische Einheit) bekannt ist. Innerhalb dieses Blocks befindet sich eine Reihe von Gattern, die beispielsweise zwei Operanden bitweise UND-verknüpfen können. Es gibt einen weiteren Satz von Toren, die sie zusammenfügen können, und so weiter.
Wenn Sie Anweisungen auf der CPU ausführen, werden die Anweisungen einzeln decodiert, und die mit dieser Anweisung verbundene Logik wird innerhalb der ALU aktiviert.
Der Unterschied ist ein Kompromiss zwischen Zeit und Fläche. Wenn Sie viele UNDs ausführen müssen, können Sie diese in einem FPGA mit vielen LUTs parallel ausführen und in kurzer Zeit erledigen. Wenn Sie sie in einer CPU ausführen, werden sie einzeln (sequenziell) in dem winzigen Logikblock ausgeführt, der für diese Aufgabe entwickelt wurde.
Die CPU hat nicht nur "eine Reihe" vorgefertigter Logikgatter. Ein moderner Prozessor hat etwa 50 Millionen bis mehrere Milliarden Transistoren, was vielen Millionen Gattern entspricht.
Die CPU verfügt bereits über alle Ressourcen, die zum Ausführen Ihres C++-Programms erforderlich sind. Die bereitgestellten Ressourcen erfüllen den von dieser Hardwareplattform definierten Befehlssatz, sei es x86, ARM, MIPS usw. Diese Anweisungen umfassen alle arithmetische Anweisungen, das Verschieben von Speicher, Bedingungen usw. Sehen Sie sich die Befehlssätze Ihrer Plattform an, um ein Verständnis zu erlangen wie die CPU selbst tatsächlich arbeitet.
Wenn die CPU eine "UND"-Operation durchführt, während sie irgendwo ein UND-Gatter verwendet, gibt es Millionen von UND-Gattern in der CPU für alle Arten von Operationen.
Diese Befehle sind alle im Layout der Transistoren im Chip implementiert. Um zu sehen, wie einige davon funktionieren, schlagen Sie Dinge wie Flip-Flops, Addierer oder andere digitale Logik nach.
Andere Antworten haben die spezifischen Fragen auf Detailebene behandelt, aber ich denke, hier besteht die Möglichkeit, sie aus einem anderen Blickwinkel zu betrachten. Heutige Prozessoren haben viele Millionen (Milliarden in Desktop-CPUs der aktuellen Generation) von Transistoren, die eine vergleichsweise große Anzahl von Gattern implementieren. Während nur wenige dieser Gatter tatsächlich zur Implementierung der XOR
Berechnung verwendet werden, ist es schwierig, sie in dem riesigen Wald von unterstützenden Funktionen zu sehen.
Die Oldtimer hier (ich glaube, ich kann zugeben, dass ich mir dieses Label auch verdient habe) haben zugesehen, wie dieser Wald erwachsen wurde, aber es ist leicht zu verstehen, wie schwer ein Neuling auf dem Gebiet mit nur ein wenig digitaler Designerfahrung es finden könnte, das zu sehen Parallelen zwischen einer reinen Hardware-Berechnung und einer modernen Mehrkern-CPU mit vielen Ebenen von Cache, Verzweigungsvorhersage und Pipeline-Ausführung.
Ich würde empfehlen, dass Sie Datenblätter und Referenzmaterial für Programmierer für mehrere (aber jeweils einen) der alten 8-Bit-Mikroprozessoren aus den 70er und 80er Jahren finden. In vielen Fällen findet man davon sogar Open-Source-Implementierungen in Form von reinen Software-Emulatoren sowie Verilog oder VHDL für den Einsatz in einem FPGA.
Ich empfehle, hier anzufangen, weil der 8080 (verwendet im Altair 8800 von 1975, der den größten Computermarkt ins Leben rief), MC6800 (erschien Ende der 70er Jahre in vielen kleinen Computern), 6809 (RadioShack Coco und andere), 6502 (Apple 1, Apple ][und viele mehr) und viele mehr wie sie wurden größtenteils von einzelnen Ingenieuren oder sehr kleinen Teams entworfen und implementiert und mussten daher von einem sehr kleinen Team vollständig verstanden werden. Sie demonstrieren auch die Mindestanzahl an Funktionen, die für eine kommerziell erfolgreiche CPU erforderlich sind, ohne zusätzlichen Speicher, Cache oder Peripheriegeräte hinzuzufügen.
Ein Großteil des 8080-Erbes wird auf der Z80-Familienseite bewahrt . Der Z80 war Zilogs Erweiterung der Intel 8080-Plattform, und Kerne, die ihn implementieren, sind noch heute zu finden. Ein Verilog 8080 ist bei OpenCores.org, zusammen mit mehreren weiteren 8080- und Z80-Implementierungen. Für die MCS80-Architektur und ihre umfangreiche Familie existiert eine Fülle von Dokumentationen, Betriebssystemen, Assemblern und Compilern.
OpenCores verfügt über eine große Anzahl von Open-Source-Kernen. Es gibt fast 100 reine CPUs, zusammen mit weiteren etwa 50 SOCs, die die Grundlage für weitere Erkundungen bilden könnten.
Wie Sie beobachtet haben, bestimmt der Inhalt der Nachschlagetabelle, ob eine bestimmte LUT ein ODER-Gatter (0, 1, 1, 1) und ein UND-Gatter (0, 0, 0, 1), ein XOR (0, 1, 1, 0) usw.
Die Nachschlagetabelle selbst wird mit fest codierten Gattern implementiert, dh das Ergebnis ist
(lut[0] AND NOT a AND NOT b) OR
(lut[1] AND a AND NOT b) OR
(lut[2] AND NOT a AND b) OR
(lut[3] AND a AND b)
Wenn man sich das zeilenweise anschaut, sieht man, dass immer nur eine dieser Zeilen eine logische Eins haben kann, also einen der LUT-Einträge auswählt. Auf die gleiche Weise können Sie auch zwischen mehreren Datenquellen auswählen:
Wenn op1
die Zwei-Bit-Registernummer ist, kann der lhs
Operand ausgewählt werden als
(reg0 AND NOT op1[0] AND NOT op1[1]) OR
(reg1 AND op1[0] AND NOT op1[1]) OR
(reg2 AND NOT op1[0] AND op1[1]) OR
(reg3 AND op1[0] AND op1[1])
Anschließend opcode
kann die auszuführende Operation ausgewählt werden:
((lhs AND rhs) AND NOT opcode[0] AND NOT opcode[1]) OR
((lhs OR rhs) AND opcode[0] AND NOT opcode[1]) OR
((lhs + rhs) AND NOT opcode[0] AND opcode[1]) OR
((lhs - rhs) AND opcode[0] AND opcode[1])
Wo res = (lhs + rhs)
ist definiert als
res[0] = lhs[0] XOR rhs[0];
res[1] = lhs[1] XOR rhs[1] XOR (lhs[0] AND rhs[0]);
...
Am Ende kann ich also alles auf feste Gates reduzieren und nur die Eingänge variabel lassen. Ein FPGA ist eine solche Variante, bei der die Gatter so angeordnet sind, dass sie eine Tabellensuche durchführen.
In einem realen System würde ich zusätzlich optimieren, z. B. äquivalente Signale kombinieren und versuchen, das Umschalten von Gattern zu minimieren, wenn dieses Signal später durch ein UND-Gatter eliminiert wird und keinen Einfluss auf das Ergebnis hat:
is_and_op = NOT opcode[0] AND NOT opcode[1];
Mehrere Schaltungselemente möchten wissen, ob wir derzeit eine "und"-Operation ausführen.
lhs_and = lhs AND is_and_op;
rhs_and = rhs AND is_and_op;
Wenn nicht, übergeben wir Nullen an die Gatter, die die Operation ausführen.
res_and = lhs_and AND rhs_and;
Dies ist wie zuvor die eigentliche Operation.
res = res_and AND is_and_op OR ...;
Auswahl kann auch unsere Kurzschrift verwenden.
Der Unterschied zwischen einer CPU und einem FPGA ist die Parallelität. FPGAs sind sehr gut darin, eine Reihe von (logischerweise) einfacheren Aufgaben gleichzeitig mit minimaler Verzögerung auszuführen. Komplexere Logik und Operationsfolgen werden besser von der ALU (Arithmetic and Logic Unit) der CPU abgedeckt.
Wenn Sie an einer gängigen Software-Emulation des Gate-Designs interessiert sind, die typischerweise (wenn auch naiv) zur Vereinfachung von booleschen Logikfunktionen verwendet wird, dann werfen Sie einen Blick auf den Quine-McCluskey-Algorithmus . Ich habe dies verwendet, um meine eigene Synthesesoftware an der Uni zu entwerfen, als ich die teuren Studio's nicht weitergeben konnte, und zum Spaß.
Josh Strahl
Ky -