Hallo,
Schau dir mal folgendes Spiel an, das zeigt einiges recht anschaulich:
http://www.zachtronicsindustries.com/play-kohctpyktop/
Nett! Wenn ich nicht was besseres mit meiner knappen Lebenszeit anzufangen wüsste könnte ich da glatt süchtig werden.
ob man nebenläufige Logik theoretisch beliebig lang verschachteln kann (abgesehen von Latenz), oder ob irgendwann der Strom knapp wird.
Ja, Du kannst beliebig viele Logik-Ebenen zwischen 2 getakteten D-FFs packen, nur die Taktfrequenz muss niedrig genug sein damit das dann auch funktioniert. In absoluter Performance scheint es auf den ersten Blick sogar Sinn zu ergeben genau so zu verfahren. Jedes Flip-Flop kostet immer auch etwas Latenz für jeden einzelnen Pfad, es gibt eine Latenz vom Flip-Flop selber (die neuen Daten liegen nicht unmittelbar sofort nach der Taktflanke am Datenausgang an sondern erst minimal später) und dann muss noch die Hold-Zeit eingehalten werden (die neuen Daten müssen nicht erst unmittelbar vor der Taktflanke ankommen sondern sie müssen schon ein klein wenig eher am Dateneingang anliegen damit diese auch zuverlässig übernommen werden). Wenn Du also eine Logik hast die Du über 3 Takte verteilst und mit der Frequenz X laufen lässt dann wird die mögliche Frequenz etwas mehr als ein Drittel betragen wenn Du das zu einem Takt umbaust indem Du die zwei mittleren FF-Ebenen entfernst. Dafür geht Dir das Pipeline-Prinzip verloren und weil Du mit der 3-stufigen Pipeline in minimal mehr als der selben Zeit 3 Ergebnisse bekommst bist Du damit effektiv wieder schneller. Jetzt könnte man wieder versuchen die Schaltung in noch mehr Stufen aufzuteilen aber dem sind natürlich Grenzen gesetzt weil es einfach immer schwieriger wird sinnvolle Teilschritte zu finden. Auch wird es nur selten möglich sein mit einer Verdoppelung der Stufen den Takt zu verdoppeln weil es kaum möglich ist in einem asynchronen Logik-Geflecht genau in der Mitte aller Laufzeiten (für alle möglichen Wege) aufzutrennen, desto feiner man das aufteilen will desto eher wird man da suboptimale Kompromisse eingehen müssen. Es läuft letztendlich auf einen Trade-Off hinaus bei dem man das gesamte System berücksichtigen muss weil es auch nicht praktikabel ist jede Teilschaltung mit einer eigenen Taktfrequenz laufen zu lassen (durch das korrekte Überführen der Daten von einer Takt-Domäne zu einer anderen entstehen auch erhebliche Latenzen, nebst dessen dass das keine triviale Angelegenheit ist).
Kann man Busse mit einer höheren (mehrfachen) Frequenz zur Pipeline takten?
Klar kann man das, es gibt dafür auch Beispiele aber in der Praxis ist das trotzdem ziemlich selten weil ein Bus ja eben über weitere Strecken kommunizieren soll und deswegen mit deutlich längeren Signallaufzeiten zu rechnen ist als innerhalb einer kompakten Logik-Wolke.
Auf eine minimales Beispiel reduziert würde die Frage lauten, ob man beim nächsten Takt den Status aus einem Latch mit 2 Leitungen abgreifen und in der folgenden Logik an 2 Stellen verwenden kann.
Ja, das geht, ansonsten könnte man keine digitale Schaltung bauen. Du kannst auch innerhalb der Logik-Wolke das Ergebnis jedes Einzelelementes (AND/OR/XOR/...) mehrfach weiter benutzen. Du musst nur berücksichtigen da ein höherer Fan-Out auch mehr Last für das treibende Element bedeutet und damit die Laufzeit dieses Signals für alle Empfänger etwas länger wird. Die FPGA-Synthese-Tools berücksichtigen das aber wenn sie die maximal mögliche Taktfrequenz Deiner Schaltung errechnen.
Kann man auf einen internen Cache im selben Takt von mehreren parallelen Prozessen (VHDL) aus lesend zugreifen? Wie sieht es beim schreibenden Zugriff aus?
Wenn Du mit Cache klassischen SRAM (was auch die Basis für den RAM innerhalb von FPGAs ist) meinst dann nicht. Die RAM-Blöcke in FPGAs sind typischerweise als Dual-Port-SRAM ausgelegt (es gibt da zwar eine spezielle Ausnahme aber das lassen wir jetzt mal lieber, wenn es Dich interessiert dann schau Dich einfach auf tabula.com um) und arbeiten immer taktsynchron. Das bedeutet das wenn Du auf eine bestimmte Stelle im RAM zugreifen möchtest musst Du eine Adresse und ein Lese-Kommando an einen der Ports anlegen und das wird dann mit der nächsten Taktflanke übernommen. Dann gibt es üblicherweise 2 Betriebsarten: entweder die Daten kommen noch kurz vor der darauf folgenden Taktflanke an den Ausgangspins raus so das Du diese eventuell noch mit einer kurzen Logik-Wolke verarbeiten und schließlich in (mehreren) Flip-Flops abspeichern kannst oder die Daten kommen erst kurz nach der darauf folgenden Taktflanke raus so das Du eine größere Logik-Wolke dahinter hängen kannst bevor Du diese (verarbeiteten) Daten dann in (mehreren) Flip-Fops abspeicherst. Die zweite Variante ist der Pipeline-Betrieb und ermöglicht es den RAM mit höheren Taktfrequenzen laufen zu lassen aber dafür bekommst Du einen Takt zusätzliche Latenz bei theoretisch höherem Durchsatz. Bei beiden Varianten kann man mit jedem Takt eine andere Adresse angeben und bekommt dann die Daten in der selben Reihenfolge (entsprechend verzögert) ausgeliefert. Schreiben funktioniert ähnlich nur das man da zusammen mit der Adresse und dem Schreib-Kommando auch noch die zu schreibenden Daten angeben muss, auch lässt sich Lesen und Schreiben beliebig mischen. Es muss nur beachtet werden das die gerade geschriebenen Daten nicht sofort wieder gelesen werden können. Dadurch das diese RAMs Dual-Ported sind hat man eben 2 solcher Ports zur Verfügung die man sogar mit unterschiedlichen Taktfrequenzen betreiben kann aber natürlich fordert auch hier die Physik ihren Tribut indem es u.a. nicht möglich ist auf die selbe Speicherstelle von beiden Ports aus gleichzeitig zu schreiben (das wird aber nicht im RAM irgendwie verhindert sondern der speichert dann einfach Quatsch ab sondern Du als Programmierer musst Dir überlegen wie Du das verhinderst damit nur sinnvolle Daten im RAM landen) und auch wenn ein Port schreibt kann der andere Port diese Speicherstelle erst nach einer gewissen Zeit zuverlässig lesen (ansonsten kommt nur Quatsch raus, vor allem wenn man mit unterschiedlichen Taktfrequenzen arbeitet ist es recht kompliziert festzulegen ab wann mit dem anderen Port zuverlässig gelesen werden kann). Ich schlage vor Du schaust Dir einfach mal die Datenblätter bei den verschiedenen FPGA-Herstellern an, empfehlen würde ich da Actel, Altera, Lattice und Xilinx (die Reihenfolge ist keine Wertung), glücklicherweise sind die FPGA-Hersteller keine notorischen Geheimniskrämer (zumindest die 4 großen, bei den kleineren FPGA-Herstellern ist das leider anders aber auch da ist es relativ einfach schaffbar alles lesenswerte zu bekommen).
Um zu entspannen denke ich zwischen dem Programmieren in Java ein wenig über massiv parallele HW-Architekturen nach.
Hm, aha.
- Sollen ausführbare Instruktionen eine kompakte Form von Schlüssel-Wert-Paaren enthalten?
Hä, von was schreibst Du da? Meine Befehle werden in der internen Darstellung (die aus dem Decoder hinten raus kommt und so in der gesamten Pipeline verwendet wird) immer einen OpCode, die Ausführungsbedingung, ein paar Attribute, die Register-Nummern aller benutzten Register (bis 5 Stück) und noch ein Immediate (mit voller Größe) enthalten. Was davon alles benutzt wird hängt natürlich von dem konkreten Befehl ab der im OpCode kodiert ist.
- Könnten Adressen ausschließlich über Hashtables aufgelöst werden, um sich die Relokalisierung zu sparen?
Auch hier verstehe ich nicht was Du möchtest, vielleicht solltest Du solche Fragen anhand einer konkreten Beschreibung Deines eigentlichen Vorhabens stellen.
- Könnten Konstanten ausschließlich im RAM liegen?
Ja, das kann man machen, aber für Konstanten die normal innerhalb des VHDL-Codes benutzt werden ist das eher nicht geeignet. Das würde sich eher für Konstanten lohnen die mit den eigentlichen Nutzdaten in irgendeiner Form verrechnet werden. Bis auf die von Actel erlauben es eigentlich alle FPGAs das man die internen RAM-Blöcke mit bestimmten Daten vorbelegt wenn der FPGA erwacht so das Du diese Konstanten nicht erst zur Laufzeit dort mühsam mit irgendeiner Logik hineinpacken musst.
- Kann der Code teilweise oder vollständig im Cache gehalten werden?
Ja. Du kannst sogar den Code erst hinter dem Decoder cachen, das hat mal der Pentium 4 so gemacht, um Dir die Latenz des Decoders bei Sprüngen zu sparen, in meiner CPU möchte ich das machen.
- Können die Einheiten innerhalb der CPU über einen(mehrere) spezielle Busse mit "Adressleitung" Aufgaben untereinander verteilen?
- Inwiefern kann die Kommunikation hierarchisch organisiert werden, sodass mehrere parallele Tasks ihre Instruktionen auf mehrfach vorhandene Einheiten verteilen und diese die Ergebnisse im Speicher ablegen können?
Das ist beides nur von Deiner Fantasie und Deinen Fähigkeiten als VHDL-Programmierer begrenzt.
Solange es technisch möglich ist sollte es auch umsetzbar sein.
- Kann jede instruktion ihre eigene Pipeline haben? (Die Takte einer jeweiligen Instruktion sind konstant)
Also für jede einzelne Instriktion würde ich das nicht machen wollen, das sind üblicherweise ne ganze Menge. Aber bei den größeren CPUs arbeitet man mit mehreren Ausführungseinheiten parallel. Da kann man z.B. mehrere ALUs haben in die alle einfachen Rechenbefehle (die meisten brauchen ja auch nur einen Takt) rein kommen, dazu dann noch eine/mehrere FPU-Einheit(en) (hier brauchen eigentlich alle Befehle mehrere Takte) und noch Speicherzugriffeinheiten usw.. Nur zu viele solcher Einheiten sollten es nicht werden da natürlich jede Einheit Logik braucht und auch mit Befehlen versorgt werden will (gerade das ist ja eine der Schwächen der aktuellen Bulldozer-CPUs von AMD, der Decoder schafft es nicht für die vielen Ausführungseinheiten immer genügend Befehle nachzuliefern damit die sich nicht langweilen müssen).
- Wie verhalten sich Pipelines bei einem geänderten Ausführungspfad, anders gefragt: Kann der Code statisch (oder dynamisch) nach dem definitiven oder warscheinlichsten Pfad analysiert werden?
Typische Pipelines reagieren auf Verzweigungen im Programmablauf eher schlecht, das liegt an der Länge da ja das Ziel einer Verzweigung oft erst am Ende der Pipeline fest steht kommt es da zu teilweise erheblichen Latenzen bis die neuen Befehle (vom Sprungziel) wieder Ergebnisse liefern können. Gerade bei bedingten Verzweigungen ist das ein ernstes Problem. In der Oberliga nutzt man dazu spekulative Ausführung, die bei bedingten Verzweigungen eben den Pfad der mit höherer Wahrscheinlichkeit ausgeführt werden wird schon mal in die Pipeline lässt. Solange diese Vorhersage stimmt ist alles prima aber wenn diese Vorhersage falsch liegt gibt es noch mehr Straftakte da ja erst mal die Pipeline von diesen
falschen Befehlen gesäubert werden muss.
Grüße
Erik