Wie ahmt ein Nicht-FPGA (dh ein PC mit CPU, RAM, Festplatte) Logikgatter nach?

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:

  1. 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.

  2. 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.

Suchen Sie bei Google nach etwas wie "DIY 8-Bit-CPU" oder ähnlichem. Es gibt Ihnen einen Blick von unten nach oben und nicht von oben nach unten.
Ich bin nicht besonders erfahren auf diesem Gebiet, aber ich bin mir ziemlich sicher, dass dies der Sinn von Anweisungswörtern in der Maschinensprache ist

Antworten (6)

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).

Ja, darauf wollte ich bei meiner ersten Vermutung hinaus. Wenn die CPU also Logik ausführen muss, verwendet sie die Gatter (oder LUTs oder eine andere Technologie) in ihrer ALU, um die Operation auszuführen. Ist es möglich, dass die CPU aufgrund begrenzter Rechenressourcen durch ihre ALU blockiert wird? Beispielsweise kann die ALU in jedem Taktzyklus nur eine Anzahl von Berechnungen durchführen, die durch die Anzahl relevanter Gatter begrenzt ist, die sie hat. Solange das FPGA Platz hat, kann es einfach mehr Gates bauen, um schneller zu werden. Mir ist klar, dass die Logik parallel auf dem FPGA ausgeführt wird und nicht sequentiell für die CPU, aber da
muss ein Vorteil gegenüber der rohen Anzahl von Toren sein, oder?
Eine klassische CPU führt nur eine sinnvolle Berechnung pro Takt aus (höchstens oft weniger), da von allen möglichen parallel laufenden Berechnungen nur eine tatsächlich erwünscht ist . Moderne Techniken beinhalten jedoch oft mehrere parallele Ausführungseinheiten, die entweder dasselbe mit mehreren Datensätzen tun (insbesondere die GPUs auf einer Grafikkarte) oder zwei Möglichkeiten verfolgen, wobei die falsche verworfen wird (wird die bedingte Verzweigung genommen oder nicht? ) oder als tatsächliche unterschiedliche Kerne, die verschiedene Programm-Threads ausführen. Aber es ist nicht trivial, etwas schneller zu machen, indem man einfach mehr Tore darauf wirft.
Ich verstehe. Wenn also eine CPU beispielsweise viele XORs ausführen müsste, würde eine ALU mit einer großen Anzahl von XOR-Gattern dies nicht unbedingt schneller machen als eine ALU mit einer geringeren Anzahl. Was bestimmt also die optimale Anzahl eines bestimmten Gate-Typs auf einer ALU?
Wollten Sie auch sagen "alle möglichen Berechnungen laufen nacheinander" statt "parallel"?
Nein, sie laufen parallel, aber alle Ergebnisse werden verworfen, bis auf dasjenige, das mit der tatsächlich vom Opcode angeforderten Berechnung übereinstimmt. Die Anzahl von Gattern in jedem unterschiedlichen Datenpfad * Pipeline-Stufe wird grundsätzlich durch die Breite des Wortes und die Komplexität der durchgeführten kombinatorischen Operation bestimmt.
@ user3716057: Es hängt von Ihrer Prozessorarchitektur ab. x86 verfügt über SIMD-Anweisungen, die mehrere parallele Operationen ermöglichen, und es gibt verschiedene Vector-Prozessoren , um große Datenmengen parallel zu verarbeiten. GPUs sind eine solche Inkarnation einer ähnlichen Idee, eines Chips, der für hochgradig parallele Berechnungen entwickelt wurde.
@ user3716057: Die meisten modernen High-End-CPUs verfügen tatsächlich über mehr als eine ALU. Auf diese Weise können sie mehr als eine mathematische/logische Operation gleichzeitig ausführen. Manchmal verfügen CPUs auch über separate komplexe ALUs, die multiplizieren/dividieren/addieren/oder/xor usw. können, und einfache ALUs, die nur addieren/oder/und/xor können, sodass komplexe und einfache Operationen zusammen geplant werden können.

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.

Ist das nicht so ziemlich meine erste Vermutung?
@ user3716057, nein, nicht ganz. Beispielsweise gibt es kein "wenn"-Gatter. Ihr Code wird in eine Reihe einzelner Anweisungen zerlegt (begrenzt auf die Breite der CPU-Busse usw.). Dies können Hunderte von Anweisungen für sehr wenig Code auf höherer Ebene sein. Wie Dave sagte, führt die ALU logische Operationen wie AND, OR und arithmetische Operationen wie ADD, SUB durch. Eine "if"-Anweisung in Ihrem C++-Code könnte aus vielen Maschinenanweisungen bestehen.
Der UND-Gatter-Teil war in Ordnung, aber mit dem IF-Gatter, das nicht existiert, sind Sie aus dem Ruder gelaufen. Eine IF-Anweisung wird in eine Reihe von Anweisungen umgewandelt, die Werte vergleichen, während die Simulation das Ergebnis so darstellt, als ob sie parallel ausgeführt worden wäre.
Hoppla Tippfehler, das sollte OR sein. Oder wirklich irgendein Tor. Abgesehen davon bin ich mir nicht sicher, wie meine Antwort "ziemlich weit weg" von Daves Antwort ist.
@ user3716057: Es ist ziemlich weit weg in dem Sinne, dass es nicht "eines" seiner UND-Gatter verwendet, sondern die UND-Operation der ALU auslöst, die immer auf Wortlänge (8/16/32/68 Bit) gleichzeitig arbeitet zu UND zwei Register zusammen. Wenn Sie mit einem einzelnen Bit arbeiten möchten, müssen Sie die Operationen AND, OR, XOR, Left/Right Shift kreativ einsetzen, um das zu bekommen, was Sie wollen.

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.

Aber es ist eine begrenzte Anzahl von Toren. Worauf ich mit meiner Frage wirklich hinaus will, ist die folgende Idee: Wenn ein FPGA zum Beispiel viele XORs ausführen muss, kann es dies schneller tun, indem es mehr XOR-Gatter synthetisiert und die gesamte Logik parallel ausführt. Solange noch Platz auf dem FPGA ist, können weitere Gatter gebaut werden. Die CPU muss auf jede mögliche Logik vorbereitet sein, sie kann nicht nur die Logik erstellen, die sie benötigt. Wenn mein Programm also Tonnen von XOR-Logik verwendet, aber sonst nichts, kann die CPU nicht alle ihre Ressourcen nutzen.
@ user3716057: Es dreht sich alles um Kompromisse. Es gibt Spezial-CPUs, die viele XORs parallel ausführen können, wenn eine bestimmte Anwendung dies benötigt. Beliebte x86 haben SIMD-Anweisungen für solche Dinge. FPGAs sind gut für viele Anwendungen und Allzweck-CPUs sind gut für viele andere.
Dein erster Satz ist völlig falsch. Natürlich hat die CPU eine Reihe von Gattern. Sie scheinen dagegen einzuwenden, dass es sich um eine große Zahl handelt, was ... ungerade ist.
@OllieFord: Ich habe das aus zwei Gründen gesagt, erstens, dass die Aussage, dass eine CPU nur "eine Anzahl von Gattern" hat, die falsche Vorstellung vermittelt, als würde man sagen, dass Software nur "Einsen und Nullen" ist. Sicher, beides ist wahr, aber sie verwirren die Idee. Zweitens werden CPUs heute nicht wirklich Gatter für Gatter entworfen, sie werden durch Funktionsblöcke entworfen, die zwischen einigen und mehreren Tausend Gattern und Transistoren bestehen und von spezialisierter Software ausgelegt werden. Die Konzentration auf die Tore ist die Konzentration auf die falsche Ebene.
Ich bin anderer Meinung - ich denke, für ein gutes Verständnis ist es eine ausgezeichnete Idee zu wissen, was ein Logikgatter eigentlich ist, wie sie einige dieser Funktionsblöcke bilden und so weiter, bis hin zur Funktionsweise Ihres Compilers.

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 XORBerechnung 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.

Haben Sie aus Neugierde eine Ahnung, inwieweit die architektonischen Merkmale früher Mikrocomputer früheren (Nicht-"Mikro")-Computern ähnelten? Ich denke, die meisten Nicht-Mikrocomputer arbeiteten normalerweise mit größeren Wortgrößen, aber ich kann mir durchaus vorstellen, dass für einige Anwendungen ein 8-Bit-Computer oder sogar ein 4-Bit-Computer nützlich gewesen wäre.
Zum Beispiel würde ich denken, dass sogar ein 4-Bit-Computer wahrscheinlich ein zeitgeteiltes automatisiertes Punktesystem für 16 Bowlingbahnen betreiben könnte, wenn er drei 4-Bit-Register zusammenbauen könnte, um 12-Bit-Adressen zu bilden; Der Bau eines 4-Bit-Computers aus diskreten Teilen scheint billiger zu sein, als zu versuchen, ein automatisiertes Bewertungssystem auf andere Weise zu entwerfen.
@supercat Einige Taschenrechner basierten auf einer 4-Bit-CPU, die sich gut für die BCD-Arithmetik eignete, indem sie jeweils eine Dezimalstelle verarbeitete. Ergebnisse mit 12 Dezimalstellen waren üblich. Es gab frühe Maschinen, die eine 1-Bit-ALU seriell verwendeten, um nützliche Berechnungen mit größeren Zahlen durchzuführen. Viele frühe Maschinen hatten unterschiedliche Größen für das ALU-, Adress- und Befehlswort. Der 8080 hatte zum Beispiel einen 16-Bit-Adressbus mit einer 8-Bit-ALU und einem Akkumulator.
Ging es vor der Erfindung des Mikroprozessors um „Taschenrechner“? Hat einer von denen, die irgendeine Form von "dokumentierter" CPU verwendet haben, die Code aus einem adressierbaren Codespeicher (ROM) abgerufen hat, anstatt einfach sequentielle Logik zu verwenden, um verschiedene Aktionen auszulösen?
@supercat: "Hat irgendein Taschenrechner einen dokumentierten Prozessor, der Anweisungen aus einem ROM abruft, aber ohne eine Single-Chip-CPU?" Klingt so, als wäre es eine ausgezeichnete separate Frage auf oberster Ebene. Der CTC Datapoint 2200 (für den der Intel 8008 entwickelt wurde, der jedoch mit einem schnelleren Prozessor ausgeliefert wurde, auf dem derselbe Befehlssatz ausgeführt wird, der aus ~ 100 TTL-Chips besteht) und der Olivetti Programma 101 (der einen gespeicherten Programmprozessor hat, der ohne gebaut wurde irgendwelche integrierten Schaltkreise!) sind sehr nah dran, aber ich würde sie nicht als Taschenformat bezeichnen).

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 op1die Zwei-Bit-Registernummer ist, kann der lhsOperand 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 opcodekann 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.

Meine Frage war mehr darüber, wie eine CPU logische Operationen ausführt. Ich weiß, wie FPGAs LUTs verwenden, um Logik aufzubauen. Wie ein anderer Poster betonte, verwenden CPUs LUTs nicht auf diese Weise.
Mein Punkt ist, dass eine LUT nur ein Haufen fester Tore ist, die eine Auswahllogik implementieren, die dann verwendet werden kann, um ein "Tor" zu implementieren, indem bestimmte Eingaben für den Auswahlprozess bereitgestellt werden. Das heißt, alles, was Sie jemals tun müssen, ist Eingaben bereitzustellen, während die Hardware feststeht.

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ß.