Lowlevel

OffZone => Offtopic => Thema gestartet von: Tufelix am 17. February 2017, 16:33

Titel: Instruction-Length Decoder und Alignment-Unit einer x86 CPU
Beitrag von: Tufelix am 17. February 2017, 16:33
Huhu, ich arbeite seit einiger Zeit an einem Entwurf einer neuen eigenen CPU( sozusagen Logisim-CPU 2.0  :roll: ). Um Fehler zu vermeiden die ich in früheren ähnlicheren Projekten gemacht habe, habe ich mir von Anfang an ein grobes Ziel und Spezifikation gesetzt:

Die Entwicklung ist bis jetzt recht weit Fortgeschritten, das Back-end (Also Integer-ALUs, Memory-Execution-Unit,  Out of order resource allocation und die FPU) hab ich schon grob fertig entworfen.
Die meisten noch ungelöste Fragen und Probleme meines Projektes befinden sich größtenteils im Frontend welches durch den CISC-Befehlssatzt wesentlich mehr Zeitauswand bei der Entwicklung erfordert wie die restlichen Teile des Entwurfs. Die größten Schwierigkeit davon ist der Instruction-Length Decoder (oder einfach ILD) zusammen mit der Alignment-Unit welche die Befehls-Bytes ausrichtet und an die beiden Decoder weiter gibt.

Ich hatte mir für den ILD und der Alignment-Unit ursprünglich als Ziel gesetzt maximal mit einer Pipeline-Sufe zusammen mit einem Predecode-Bit pro Befehlsbyte auszukommen.
Ich arbeite schon mehr als ein halbes Jahr an diesen beiden Komponenten meiner CPU und hab schon mehrere Entwürfe ausgearbeitet, welche aber allesamt mein selbst gesetztes Ziel verfehlt haben.
Die große Schwierigkeit ist die große Komplexität des x86-Befehlssatz. Zum einem kann ein Befehl 1 bis 15Byte lang sein zum anderen befindet sich das erste Opcodebyte nicht immer am ersten Befehlsbyte sondern bedingt durch die Prefix-Bytes an verschieden Stellen im Befehl.
Meine drei besten Entwürfe für den ILD und der Alignment-Unit sind folgende:


Ich bin derzeit ziemlich Unschlüssig welcher meiner Drei Konzepte (welche alle meine Spezifikation nicht ganz erfüllen) die geeigneteste für mein Projekt ist. Hat jemand Erfahrung mit diesem Komplexen Thema? Was haltet ihr von diesen drei Konzepten? Und habt ihr eventuell Anregungen und Verbesserungs-Vorschläge?

Mfg Tufel

PS: Ich hatte mir Vorgenommen in diesem Beitrag alles Unterzubringen was wichtig ist, Irgendwie ist Beitrag aber dadurch etwas länger geworden.  :-D
Titel: Re: Instruction-Length Decoder und Alignment-Unit einer x86 CPU
Beitrag von: Svenska am 19. February 2017, 02:00
Hi,

die Kombination aus "AMD64 + sämtliches SSE + VT-X", "Out-Of-Order und superskalar", "relativ wenig Logik" und "relativ schnell" halte ich für einen nicht erfüllbaren Widerspruch. RISC-V ist deutlich einfacher zu dekodieren, und die Implementationen (sowohl die von Berkeley als auch andere) unterscheiden sich doch ziemlich massiv in Ressourcenverbrauch und erreichbarer Taktfrequenz. Eine 100 MHz schnelle Out-of-Order-Architektur mit IPC > 1 ist schon da echt sportlich. Zum Vergleich: Der Core, den ich verwende (PicoRV32), läuft zwar mit bis zu 275 MHz, ist aber nur In-Order mit einem IPC von etwa 0,3.

Mit den Patenten und den Ansätzen befasse ich mich jetzt nicht, sondern gebe eine weitere Idee. Die Alignment-Unit kannst du mit einem Dual-Ported BRAM mit einem byteweisen Ausgabeport nachbilden. Bei Sprüngen musst du dann nur den Offset beim Zugriff draufaddieren und bekommst einen Bytestrom. Den kannst du klassisch durch eine FSM dekodieren, was natürlich mehrere Takte (einen Takt pro Byte) dauert. Da du aber einen FPGA hast, kannst du den Dekoder in eine eigene, schnell laufende Taktdomäne legen (vermutlich musst du den Teil dann ziemlich massiv constrainen, damit das performt, aber es spart gut Logik).

Der Dekoder würde pro Takt ein Byte dekodieren, muss also für zweifache Superskalarität und einer durchschnittlichen Instruktionslänge von 3 (vgl. Quelle (https://www.strchr.com/x86_machine_code_statistics)) sechsmal so schnell laufen wie der Core selbst. Bei geschätzten 200 MHz Dekodertaktrate und 40 MHz Kerntaktrate passt das einigermaßen zusammen.

Gruß,
Svenska
Titel: Re: Instruction-Length Decoder und Alignment-Unit einer x86 CPU
Beitrag von: Tufelix am 19. February 2017, 13:34
Huhu Svenska,

danke für deine Antwort. Joar mein Ziel ist ziemlich ambitioniert  :mrgreen: . Der größte Widersprung in diesem Projekt ist wohl die maximal benötigte Logik für die CPU. In einem ähnlichen Projekt in dem "nur" ein Superskalarer Pentium-Prozessor auf ner FPGA realisiert wurde (http://www.eecg.toronto.edu/~yiannac/docs/fpga07.pdf) benötigte man mehr als das doppelte an LUT-Tabellen wie ich für mein Projekt vorgesehen habe. Der RTL-Code war aber nicht für eine FPGA optimiert, auserdem habe ich es geschafft für mein Projekt ein ziemlich kleines Backend zu entwerfen.  Das Backend ist dadurch ziemlich fragil und ich bin mir noch nicht ganz sicher ob es eine gute Performance liefert oder einfach nur ein riesiger Bottleneck in meinem System ist  :-D . Aber damit sollte es zumindest möglich sein die relativ geringe Logikgröße zu erreichen.

Mit den Patenten und den Ansätzen befasse ich mich jetzt nicht, sondern gebe eine weitere Idee. Die Alignment-Unit kannst du mit einem Dual-Ported BRAM mit einem byteweisen Ausgabeport nachbilden. Bei Sprüngen musst du dann nur den Offset beim Zugriff draufaddieren und bekommst einen Bytestrom. Den kannst du klassisch durch eine FSM dekodieren, was natürlich mehrere Takte (einen Takt pro Byte) dauert. Da du aber einen FPGA hast, kannst du den Dekoder in eine eigene, schnell laufende Taktdomäne legen (vermutlich musst du den Teil dann ziemlich massiv constrainen, damit das performt, aber es spart gut Logik).

Der Dekoder würde pro Takt ein Byte dekodieren, muss also für zweifache Superskalarität und einer durchschnittlichen Instruktionslänge von 3 (vgl. Quelle (https://www.strchr.com/x86_machine_code_statistics)) sechsmal so schnell laufen wie der Core selbst. Bei geschätzten 200 MHz Dekodertaktrate und 40 MHz Kerntaktrate passt das einigermaßen zusammen.
Die idee find ich ziemlich Smart, man spart sich damit viel Logik und Entwicklungsaufwand beim Dekoder. Der Entwurf ist aber für mein Projekt weniger gut geeignet. Der gesamte zweite Entwurf aus meinem letzten Beitrag benötigt rund 2000 4bit Lut-Tabellen. Zusammen mit dem Decoder -wessen Logik-Verbrauch ich bis jetzt aber nur abschätzen kann- werden für die gesamte Instruktion-Dekodier Einheit der CPU etwa 12% bis 15% des gesamten Logik-Budget verbraucht (Im oben gennanten Pentium wurde prozentual für die Decodereinheit noch weniger Logik benötigt, daher denke ich das meine Schätzung relativ realistisch ist). Die Vorteile des Resourcen-Verbrauchs deiner Idee ist in meinen Augen zu gering um den möglichen zusätzlichen Bottleneck rechtfertigen können. Für nen einfachen i486 Klon wär die idee aber perfekt geeignet (Aber mit sowas kleinem geb ich mich nicht zufrieden  :-D).

Mfg Tufel

Titel: Re: Instruction-Length Decoder und Alignment-Unit einer x86 CPU
Beitrag von: Svenska am 19. February 2017, 16:48
Hi,

naja, dann kann ich dir nicht weiter helfen. Der Befehlssatz wurde für einen sequentiellen Dekoder entworfen, was man an den Prefixbytes deutlich sieht. Die dürfen nämlich in beliebiger Reihenfolge und beliebig oft vorkommen (wobei moderne CPUs ab 16 Bytes Befehlslänge einen #GP werfen), was einen sequentiellen Dekoder nicht weiter stört.

Ich versuch' mal, meinen Ansatz zu retten, obwohl du ihn schon abgelehnt hast.

Wenn du in einem ersten Schritt die Befehlslänge ermittelst, kannst du auch mehrere solcher Dekoder mit jeweils eigenem Offset gleichzeitig laufen lassen und deren Ergebnisse in einer Tabelle, also einem Decoded-Instruction-Cache (DICache) abspeichern. Die Pipeline stallt dann auf dem DICache, nicht auf dem ICache. Am Ende geht es ja nicht darum, die Befehle möglichst schnell zu dekodieren, sondern sie dann dekodiert vorliegen zu haben, wenn du sie brauchst. Es reicht also, wenn du im Mittel schnell genug bist. Intel-Prozessoren führen schon lange keine x86-Befehle mehr direkt aus, also muss dein Core das auch nicht können.

Stellt sich raus, dass dein Dekoder ein Flaschenhals ist, kannst du immernoch an der Anzahl der Decoder drehen. Wirfst du vorne 32 Byte rein (ein einziger beliebig schlecht ausgerichteter Befehl), kannst du vermutlich im Schnitt acht Dekoder voll auslasten und hättest nach Sprüngen nur wenig zusätzliche Latenz. Gegenüber einem Cache Miss fällt das überhaupt nicht ins Gewicht (und mit dem bisschen integriertem RAM - der größte Cyclone IV hat 486 KB - bekommst du keine großen Caches hin). Sehr kurze Schleifen könntest du aber aus dem DICache versorgen.

Gruß,
Svenska
Titel: Re: Instruction-Length Decoder und Alignment-Unit einer x86 CPU
Beitrag von: Tufelix am 19. February 2017, 21:45
Wenn man deine ursprüngliche Idee mit dem einem Sequentiellen Decoder weiterdenkt , dann ist sie eigentlich garnicht so schlecht. Man könnte den Sequentiellen Decoder stark parallelisieren. Also zunächst alle möglichen Prefix-Bytes decodieren und mit einem Shifter das erste Opcodebyte an erster Stelle schieben. Anschließend generieren alle Opcodemaps(also z.B die Map für den Primären Opcode und die Map für den Sekundären Opcode) einen Befehl. Ein Escape-Byte Finder wählt dann die passende Map aus und bestimmt wo sich die Operand-Bytes befinden (also Modrm, Sib, Disp, Immed). Der ausgewählte Befehl wählt am Schluss über Multiplexer die benötigten Operand-Bytes aus. Im Grunde hätte man dann sowas ähnliches wie im i486 als Decoder verwendet wurde. Diesen Decoder könnte man mit dem doppelten Kerntaktrate versorgen. So hätte man einen einzelnen Dekoder für einen 2-fach superskalaren Prozessor. Probleme mit unbekannten Befehlslängen hätte man damit clever umschifft.

 
Ich versuch' mal, meinen Ansatz zu retten, obwohl du ihn schon abgelehnt hast.

Wenn du in einem ersten Schritt die Befehlslänge ermittelst, kannst du auch mehrere solcher Dekoder mit jeweils eigenem Offset gleichzeitig laufen lassen und deren Ergebnisse in einer Tabelle, also einem Decoded-Instruction-Cache (DICache) abspeichern. Die Pipeline stallt dann auf dem DICache, nicht auf dem ICache. Am Ende geht es ja nicht darum, die Befehle möglichst schnell zu dekodieren, sondern sie dann dekodiert vorliegen zu haben, wenn du sie brauchst. Es reicht also, wenn du im Mittel schnell genug bist. Intel-Prozessoren führen schon lange keine x86-Befehle mehr direkt aus, also muss dein Core das auch nicht können.

Stellt sich raus, dass dein Dekoder ein Flaschenhals ist, kannst du immernoch an der Anzahl der Decoder drehen. Wirfst du vorne 32 Byte rein (ein einziger beliebig schlecht ausgerichteter Befehl), kannst du vermutlich im Schnitt acht Dekoder voll auslasten und hättest nach Sprüngen nur wenig zusätzliche Latenz. Gegenüber einem Cache Miss fällt das überhaupt nicht ins Gewicht (und mit dem bisschen integriertem RAM - der größte Cyclone IV hat 486 KB - bekommst du keine großen Caches hin). Sehr kurze Schleifen könntest du aber aus dem DICache versorgen.

Naja, das Problem ist der erste Schritt in dem die Befehlslängen dekodiert werden müssen. Dazu benötigt man wieder nen Instruktion-Length Decoder. Die DICache die du beschreibst, ist ja sowas ähnliches wie ne Trace- oder Micro-op Cache. So ne Cache find ich speziell in nem x86 Prozessor ziemlich sinnvoll, man spart sich einige Pipeline-Stufen und kann den Nachteil des Energie-verbrauch eines Frontend für nen x86 Prozessor gegenüber das eines Risc-Prozessors stark minimieren (Hab mal eine schöne Arbeit gefunden in der es um Verschiedene Implementierungs-Ansätze für Loop-Buffer geht, da wurde auch die µ-Op Cache untersucht: http://pharm.ece.wisc.edu/papers/hpca14_revolver.pdf).
486kb für Cache sollte man nicht unterschätzen. Mit einem ausgereiften Prefetcher kann man da einiges an Performance heraus holen.

Mfg Tufel
Titel: Re: Instruction-Length Decoder und Alignment-Unit einer x86 CPU
Beitrag von: Svenska am 20. February 2017, 03:15
Man könnte den Sequentiellen Decoder stark parallelisieren.
Automaten (und der Dekoder ist nichts anderes) sind äußerst schlecht parallelisierbar. Theoretisch könntest du den auch vollständig ausrollen, weil ein Befehl nach maximal 15 Bytes dekodiert ist, aber dann kannst du kein Blockram mehr benutzen. Im Ergebnis wird das nur ein extremer Gatterverhau, für den viel dupliziert werden muss und der durch seine Tiefe gleichzeitig relativ langsam ist.

Also zunächst alle möglichen Prefix-Bytes decodieren und mit einem Shifter das erste Opcodebyte an erster Stelle schieben.
Der Hauptvorteil eines byteweise durch den Datenstrom laufenden Dekoders ist, dass ihm das Alignment egal ist. Wenn du damit jetzt nur die Präfixe verarbeiten lässt, verlierst du pro Byte einen Takt und musst den Befehl danach trotzdem neu ausrichten.

Naja, das Problem ist der erste Schritt in dem die Befehlslängen dekodiert werden müssen.
Du kannst auch einfach 16 dieser freilaufenden Decoder einfach parallel schalten (mit jeweils einem Byte Versatz) und immer den passenden auswählen. :-D

Die DICache die du beschreibst, ist ja sowas ähnliches wie ne Trace- oder Micro-op Cache.
Genau das meinte ich: Du dekodierst auf Teufel komm raus, tust die Ergebnisse in einen Cache und entkoppelst damit den Dekoder vom Prozessor. Außerdem kannst du so relativ einfach mehrere Befehlssätze unterstützen (vgl. ARM/Thumb), solange sie einigermaßen kompatibel sind.

486kb für Cache sollte man nicht unterschätzen.
Ich glaube, du unterschätzt den Nutzen von Blockram. Der von mir ursprünglich skizzierte Decoder braucht schonmal mindestens einen Block (bei Xilinx ein RAM36 == 36 KBit, weil dualported). Auch die eine oder andere Pipelinestufe kannst du mit Blockram sparsam und schnell implementieren, statt das in diskrete Logik zu gießen. Gleiches gilt für die Tabellen, die du für trigonometrische Funktionen brauchst, um float/double in hinreichender Genauigkeit zu implementieren. Im Prinzip kannst du auch alles in Logik (Distributed RAM) lösen, aber damit überrennst du das Routingnetz im FPGA und riskierst, dass dein Design nicht mehr synthetisiert (oder nur nach mehreren Tagen).

Wenn ich mir die Liste der Befehle und deine Wunschliste so anschaue, dann wirst du mindestens 2000 Befehle implementieren müssen, vielleicht auch 4000, je nach Zählweise. Ein Großteil davon ist eher unwichtig, muss aber funktionieren (z.B. BCD-Arithmetik, die x87-FPU, der Real Mode), obwohl es kaum benutzt würde. Wenn du solche Befehle in Software (als Mikrocode) implementieren kannst, sparst du dir einerseits extrem viel Fleißarbeit und hast später bessere Stellschrauben, was Größe und Geschwindigkeit angeht. Das ginge dann in die Richtung, was Transmeta vor gut 15 Jahren gemacht hat (allerdings lief das dort mit Unterstützung durch das BIOS).

So ein Mikrocode kostet aber wieder viel Blockram, der nicht für Caches zur Verfügung steht.
Titel: Re: Instruction-Length Decoder und Alignment-Unit einer x86 CPU
Beitrag von: Tufelix am 21. February 2017, 20:48
Man könnte den Sequentiellen Decoder stark parallelisieren.
Automaten (und der Dekoder ist nichts anderes) sind äußerst schlecht parallelisierbar. Theoretisch könntest du den auch vollständig ausrollen, weil ein Befehl nach maximal 15 Bytes dekodiert ist, aber dann kannst du kein Blockram mehr benutzen. Im Ergebnis wird das nur ein extremer Gatterverhau, für den viel dupliziert werden muss und der durch seine Tiefe gleichzeitig relativ langsam ist.
Der Gatteraufwand hält sich meiner Meinung nach in Grenzen. Für nen Sequentiellen Dekoder benötigt man ja schon die Opcode-Maps für die Opcode-Bytes, ein paar Multiplexer für die Immediate und Displacement und ne Emulationsrom für alle Befehle die emuliert werden müssen. Für den Parallel-Decoder kommen da nur einige Shifter und Multiplexer hinzu, um die Befehls-Bytes in ihre Einzelteile zu zerlegen und ein paar zusätzliche Prefix-Dekoder. Der zusätzliche Gatterdelay lässt sich mit ein paar Pipelinestufen mehr gut handhaben. Bis auf die Prefix-Dekoder(wobei jeder davon sechs 4bit Lut-Tabellen benötigt) wird da eigentlich nicht viel Dupliziert.

Also zunächst alle möglichen Prefix-Bytes decodieren und mit einem Shifter das erste Opcodebyte an erster Stelle schieben.
Der Hauptvorteil eines byteweise durch den Datenstrom laufenden Dekoders ist, dass ihm das Alignment egal ist. Wenn du damit jetzt nur die Präfixe verarbeiten lässt, verlierst du pro Byte einen Takt und musst den Befehl danach trotzdem neu ausrichten.
Die Idee war eigentlich mit 16 Prefix-Dekoder alle Prefixe gleichzeitig zu dekodieren. Ein zweiter Sequentieller Dekoder nur für die Prefixe würde eher wenig sinnvoll sein  :-D .

Naja, das Problem ist der erste Schritt in dem die Befehlslängen dekodiert werden müssen.
Du kannst auch einfach 16 dieser freilaufenden Decoder einfach parallel schalten (mit jeweils einem Byte Versatz) und immer den passenden auswählen. :-D
So was wird auch in einer der Patente die ich oben verlinkt habe beschrieben. Solche 16 Decoder benötigen, verglichen mit 16 vollwertigen Instruction-Length Decoder, einen wesentlich geringeren Gatteraufwand. Das ganze wird aber je ineffizienter je länger die Befehle werden, maximal wird dann 15 Takte für die 16Bytes benötigt.

Ich glaube, du unterschätzt den Nutzen von Blockram. Der von mir ursprünglich skizzierte Decoder braucht schonmal mindestens einen Block (bei Xilinx ein RAM36 == 36 KBit, weil dualported). Auch die eine oder andere Pipelinestufe kannst du mit Blockram sparsam und schnell implementieren, statt das in diskrete Logik zu gießen. Gleiches gilt für die Tabellen, die du für trigonometrische Funktionen brauchst, um float/double in hinreichender Genauigkeit zu implementieren. Im Prinzip kannst du auch alles in Logik (Distributed RAM) lösen, aber damit überrennst du das Routingnetz im FPGA und riskierst, dass dein Design nicht mehr synthetisiert (oder nur nach mehreren Tagen).

Wenn ich mir die Liste der Befehle und deine Wunschliste so anschaue, dann wirst du mindestens 2000 Befehle implementieren müssen, vielleicht auch 4000, je nach Zählweise. Ein Großteil davon ist eher unwichtig, muss aber funktionieren (z.B. BCD-Arithmetik, die x87-FPU, der Real Mode), obwohl es kaum benutzt würde. Wenn du solche Befehle in Software (als Mikrocode) implementieren kannst, sparst du dir einerseits extrem viel Fleißarbeit und hast später bessere Stellschrauben, was Größe und Geschwindigkeit angeht. Das ginge dann in die Richtung, was Transmeta vor gut 15 Jahren gemacht hat (allerdings lief das dort mit Unterstützung durch das BIOS).

So ein Mikrocode kostet aber wieder viel Blockram, der nicht für Caches zur Verfügung steht.
Ich hatte nie vor 486kb Bram nur für Cache zu verwenden  :roll: . Für die Instruction-Cache hab ich 32KB vorgesehen und für die Datencache 24KB. Recht hast du auf jeden Fall, mit Bram lässt sich vieles einfach implementieren. Bei der Verwendung von Bram hab mich bei meinem jetztigen Projekt aber eher zurückgehalten, ich hab die Befürchtung das zu viel Bram nacher bei der Synthese, den Takt unter die 100Mhz Grenze drücken könnte.
Mit meiner optimistischen Zählweiße komm ich auf ganze 813 Befehle die ich implementieren muss. Hab aber einige Befehlsgruppen zusammengefasst. Eine Mikrocode-Rom für die Emulation von Befehlen brauch ich auf jedem Fall. Befehle wie CPUID lassen sich nur damit implementieren. Wie viel Bram ich dafür benötige ist noch nicht klar. Derzeit rechne ich mit maximal 64kB.

Mfg Tufel