Autor Thema: Instruction-Length Decoder und Alignment-Unit einer x86 CPU  (Gelesen 16687 mal)

Tufelix

  • Beiträge: 103
    • Profil anzeigen
Gespeichert
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:
  • 2fach Superskalar mit Out-of-order Ausführung.
  • Soll nacher einen durchschnittlichen IPC von 1.2 haben.
  • Als Zielplattform soll eine Cyclone 4 FPGA mit einem Speedgrade C7 dienen.
  • Die CPU soll nacher etwa 30k 4bit LUT-Tabellen und 70kByte Bram auf der FPGA benötigen.
  • Als Takt-Fequenz peile ich etwa 100 Mhz an (also pro Pipeline-Stufe etwa ein Delay von 9 LUT-Tabellen).
  • Der Ziel-Befehlssatz ist AMD64( zusätzlich soll SEE 1 bis 4.2 und VT-X implementiert werden).

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:

  • Den Kompletten ILD und die Alignment-Unit direkt in der CPU-Pipeline implementieren. Der Entwurf basiert auf diesen beiden Patenten : https://www.google.com.au/patents/US6260134 https://www.google.com/patents/US7818542
    Es werden zunächst 16Bytes aus einem kleinem Buffer -welcher die I-Cache von dem Rest der CPU trennt- herraus geladen. Anschließend schiebt ein Shifter 8Byte der 16 Bytes entweder an die Startposition eines Befehls(z.b nach einem Sprungbefehl) oder an das Ende der letzten 8 Bytes. In diesen 8 Bytes gibt es 8 mögliche Startpositionen von maximal 8 möglichen Befehlen. Daher wird nach dem Shiften mit Hilfe 8 ILD für jedes Bytes die Längen 8 möglicher Befehle parallel Dekodiert. Im Anschluss werden diese 8 Befehlsbytes zusammen mit den ILD-Informationen in einem kleinen 24Byte Buffer zwischen gespeichert. Die 8 ILDs verwenden die letzten 8 Bytes aus diesem Buffer als zusätzliche Informationsquelle (falls ein Befehl die 8 Byte-Grenze überschritten hat). Die Alignment-Unit verwendet den 24Byte Buffer um die entsprechenden Instruction-Bytes zu laden  und anhand den ILD-Informationen in die richtige Position zu schieben.
    Dieser Entwurf hab ich nur grob ausgearbeitet. Insgesamt benötigt die Schaltung 3 Pipeline-Stufen um die geforderte Takt-Fequenz zu erreichen. Zusätzliche Predecode-Bits werden aber nicht benötigt.
  • Predecoder vor der I-Cache. Dieses Konzept basiert auf drei Intel-Patente und sieht dem ILD und Alignment-Unit der Silvermont Architektur nicht unähnlich :-D : https://www.google.com/patents/US6539469 https://www.google.ch/patents/US20130290678 https://www.google.com/patents/US5586276 . Als Predecoder war das oben genannte Patent angedacht: https://www.google.com.au/patents/US6260134
    Ein Predecoder dekodiert Befehle, die in die Instruction-Cache geladen werden, vor und markiert mit einem zusätzlichen Bit pro Befehls-Byte das Ende jedes Befehls. ByteBlöcke (16Byte) die aus der I-Cache geladen werden, werden in einen 12x8Byte umfassenden Prefetch-Buffer geladen oder über einen Bypass an die Alignment-Unit weitergereicht.
    Der Prefetchbuffer ist mit 3 Speicherblöcken a 4x8 Byte organisiert. In jedem Takt werden 3x8Byte (also pro Speicherblock 8Byte) aus dem Prefetchbuffer geladen und anschließend über drei 5:1 Multiplexer in die richtige Reihenfolge gebracht (es wird halt anhand des PC welcher die Alignment-Unit verarbeiten möchte ausgewählt welcher der drei 8Byte-Blöcke der erste, der zweite und der Dritte Block ist). Zusätzlich werden diese drei Multiplexer verwendet um den Bypass von der Cache an die Alignment-Unit zu organisieren.
    Die Alignment-Unit besteht in diesem Konzept aus zwei unabhängige Teile. Der erste Teil schiebt zunächt anhand der vorgegebenen Startposition( dem zu verarbeitenem PC) mit Hilfe eines 128Bit breiten 8:1 Multiplexers ein 16Byte Block an die Startposition des ersten Befehls. Daneben wird über einen zweiten 16Bit Breiten 8:1 Multiplexer die EndBits in die selbe Position gebracht und diese anschließend mit Hilfe eines Prioritizer das Ende des ersten Befehls gescannt. Falls der erste Befehl kürzer wie 9 Byte ist (Ansonsten bräuchte man für den zweiten Befehl einen 16:1 Mux) werden 8 Befehlsbytes des zweite Befehl mithilfe eines zweiten 64Bit 8:1 Multiplexer, welcher die gerichteten 128Bit des ersten Befehls verwendet, in ihre richtige Position gebracht.
    In Anschluss werden sowohl die 16 Befehls-Byte des ersten Befehls und die 8 Befehls-Byte des zweiten Befehls auf Prefix-Byte gescannt und ein weiteres mal geschoben, bevor sie an die Decode-Unit weitergereicht werden.
    Der Zweite Teil der Alignment-Unit scannt alle 24 EndBits parallel und bestimmt den PC, der im nächsten Takt-Zyklus verarbeitet wird.
    Dieses Konzept ist am besten ausgearbeitet. Es existiert schon ein Verilog-Code und ist schon im großen und ganzen fertig validiert. Bin aber irgendwie nicht richtig Zufrieden mit dem Konzept, es benötigt zwar nur ein Predecode-Bit pro Befehlsbyte, verbraucht aber zwei Pipeline-Stufen in der CPU. Auserdem werden die Befehlsbytes Unnötig oft geschoben.
  • Der Dritte Entwurf ist ein kompletter Eigenentwurf.
    Wie beim zweiten Entwurf werden werden die Befehlsbyte bevor sie in die I-Cache geladen werden vordekodiert. Diesmal wird aber der Start jedes Befehls markiert. Auserdem werden die Befehlbytes umsortiert. Prefix-Bytes welche immer am Anfang jedes Befehls stehen, werden nach hinten geschoben und als Suffixe an die vorherigen Befehlsbytes -welche vorgeschoben wurden- drangehängt. Die Suffixe werden mit einem zweiten Predecode-Bit markiert.
    Die I-Cache ist mit einem Way-Prediction Mechanismus ausgestattet und ladet die 16 Befehls-Byte direkt in einem 8x16Byte umfassenden PrefetchBuffer.
    Der Prefetchbuffer besteht aus 4 Teilblöcken mit je 8x4BefehlsByte. In jedem Taktzyklus ladet jeder Teilblock 4Byte und gibt sie an die nachfolgende Alignment-Unit weiter. Der Prefetchbuffer ist also in etwa so aufgebaut wie im Zweiten Konzept. In einer der 4Byte der vier Teilblöcke befindet sich der Startbyte des ersten Befehls. Die vier 4Byte werden jetzt aber nicht mit Multiplexer in die Richtige Position gebracht sondern direkt an einen Scanner weitergereicht welcher die Startbyte des ersten und zweiten Befehls erkennt. Mit zwei 16:1 Rotierer werden die beiden Befehle ausgewählt und zwei 8Byte Blöcke( jeder Befehl kann maximal 8Byte lang sein. Falls ein Befehl länger ist, wird nur ein Befehl pro Zyklus verarbeitet) und die nachfolgende Decoder weitergeschickt.
    Der Scanner sucht auch nach einem dritten Startbit welches den Start des nächsten Befehls markiert. Falls der Scanner kein drittes Startbit findet wird in diesem Zyklus nur ein Befehl verarbeitet.
    Für denn Fall das nur ein Startbit gefunden wurde, ist eine Speziallogik nötig. Die hab ich aber nocht nicht ganz zuende entworfen.
    Das Konzept benötigt nur eine einzige Pipeline-Stufe. Daher besitzt die CPU mit diesem Konzept eine extrem Kurze Pipeline, die Branch mispredict loop beträgt 7 Zyklen. Es werden aber 2 Predecode Bits pro Byte benötigt. Die I-Cache wird dadurch um 25% größer. Auserdem stellen sich Probleme mit der Branch-Prediction Unit ein, da diese jetzt nurnoch 1 Taktzyklus zeit hat um eine Vorhersage zumachen (Bzw. eine Branch-Byte-Mask zu generieren, welche die Scan-Unit benötigt). 

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

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #1 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) 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

Tufelix

  • Beiträge: 103
    • Profil anzeigen
Gespeichert
« Antwort #2 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) 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


Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #3 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

Tufelix

  • Beiträge: 103
    • Profil anzeigen
Gespeichert
« Antwort #4 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

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #5 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.

Tufelix

  • Beiträge: 103
    • Profil anzeigen
Gespeichert
« Antwort #6 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



« Letzte Änderung: 21. February 2017, 23:35 von Tufelix »

 

Einloggen