Hallo Leute! Ich versuche mich gerade daran, eine kleine CPU mit VHDL zu entwickeln. Leider habe ich habe zugegebenermaßen nach meinen Maßstäben überhaupt keine Ahnung von der Materie, ich nutze diese Spielerei gerade dazu, mir ein mentales Modell der Problemstellung zu bilden, um dann beim Lesen von einschlägiger Literatur einen besseren Kontext zu haben. Der Ansatz, den ich gerade verfolge, besitzt sowieso schon einige Probleme, aber auch wenn ich die erstmal so hinnehme (ein schlechtes Ergebnis macht glücklicher als gar keines =)), habe ich gerade ein arges umsetzungstechnisches Problem. Es ist ja nun so, dass gewisse ALU-Blöcke auch mal mehr als einen Takt brauchen können, um ein Ergebnis auszuspucken. Daher bin ich der Meinung, dass auch ein anständiger CPU-Ansatz das Problem hat, dass die Schreibleistung in den Registersatz größer sein muss als 1 Element pro Takzyklus, denn ansonsten würde es doch zu schlimmen "Hiccups" kommen können, wenn mehrere unabhängige ALU-Einheiten gleichzeitig fertig werden und alles darauf wartet, dass die Ergebnisse zurück in die Zielregister geschrieben werden. Eine wohlgeformte Befehlsausführung wird wohl selbstverständlich auch garantieren, dass keinesfalls mehrere dieser Ergebnisse in demselben Register landen sollen. Nun setze ich in meinem Möchtegern-Design (für Nexys3 - Spartan6) Block-RAM für den Registersatz (Die erste Umsetzung mit Logikgattern führte schnell zu der Erkenntnis, dass man das wohl eher sein lassen sollte =)). Nun meine Frage, falls nicht schon die Prämissen wackeln: Gibt es einen Weg, solche konkurrierenden Schreibzugriffe auf Block-RAM ressourcenschonend umzusetzen, wenn man nur garantiert, dass niemals dieselben Block-RAM-Elemente gleichzeitig beschrieben werden sollen? Ich habe mir überlegt, dass man natürlich die zu schreibenden Werte in std_logic_vector'en mit Registersatzbreite aufblähen könnte, aber dann habe ich ja praktisch wieder das Problem mit den Ressourcen, das ich mit dem Block-RAM zu beheben gedachte. Also gedanklich so etwas: process ... ... register_set( write_dest1 ) <= result_mux1_data; register_set( write_dest2 ) <= result_mux2_data; ... end process; Ich möchte also 2 Muxer hinter dem ALU-Einheiten-Satz haben (von denen einer nur im Bedarfsfall aushilft), die die anfallenden Ergebnisse auf 2 Kanäle runtermuxen und diese dann in die Zielregister schreiben. Geht das irgendwie? Viele Grüße, Deci PS: Mir fällt gerade auf, dass ich stillschweigend voraussetze, dass da mehrere unabhängige ALU-Einheiten vorhanden sind. Damit meine ich nicht vollwertige allround ALUs, sondern deren Bestandteile (Also Addierer, Shifter usw.). Durch die Unabhängigkeit möchte ich erreichen, dass ich recht einfach weitere Befehle hinzufügen kann, so wie ich es gerade brauche (in etwa so "übersichtlich" und modular gedacht, wie man das halt in der Software-Entwicklung kennt, auch wenn es vielleicht nicht das sinnigste ist, das auf Hardwaresynthese übertragen zu wollen). Wahrscheinlich wäre ein monolithischer ALU-Ansatz einfacher umzusetzen, oder?
Erst mal: Respekt vor der eigenen CPU, das ist ein Haufen Arbeit. Register sind per se Register und KEIN RAM oder BlockRAM. Ich hab noch nicht so genau verstanden warum du das also nicht in Registern realisieren willst? Ist dir bewusst, dass ein typischer Prozessor schon allein deshalb Register (=FF) benutzt, weil diese das Ergebnis mit 1 Takt übernehmen? RAM speichert in den meisten Fällen mit schnellen Taktraten das Ergebnis nicht innerhalb 1 Taktes sondern braucht evtl. 4 oder 8 Takte, je nach Taktrate natürlich, das ist der Grund warum das so gemacht wird. Solltest du trotz allem BlockRAM verwenden: Dieser besitzt bei Xilinx (Virtex 5, Spartan 6 glaub ich auch) 1 oder auch 2 Ports, das lässt sich mit dem Generator einstellen. Wenn du 1 Port erzeugst, dann kann man mit Muxer natürlich mehrere Eingänge auf den Port schalten und das BlockRAM beschreiben und auslesen. Durch die Einstellung vom Muxer ist dann sichergestellt, dass nur 1 Ergebnis gleichzeitig gespeichert wird. Wenn du das BlockRAM mit 2 Ports generierst musst du dir halt das Datenblatt des Xilinx BlockRAM-Generators ansehen, das erzählt dir dann ob ein Adresskonflikt beim Schreiben zu einem Problem führt oder nicht. Wenn du übrigens tatsächlich eine ALU bauen willst die pro Taktzyklus mehrere Ergebnisse liefern kann, dann nennt man das "superskalar" und du möchtest also sofort in die "modernste" Prozessorarchitektur einsteigen. Nochmals herzlichen Glückwunsch, ich an deiner Stelle würde eher eine skalare Struktur anstreben, und selbst die wirst du wahrscheinlich nicht erreichen. Das Problem sind die höchst unterschiedlich komplizierten Prozessorbefehle, die eben auch unterschiedlich lang benötigen für die Ausführung. Wenn du tatsächlich superskalar werden möchtest brauchst du am Anfang der Pipeline eine Logik, die die anstehenden Befehle sinnvoll auf deine Prozessoreinheiten verteilt, sicherstellt, dass sich nichts überholt usw... Das kann extrem kompliziert werden. LG Bernd
Hallo Bernd! Vielen Dank für Deine Antwort! Also das größte Problem an der Situation, dass man keine Ahnung von dem hat was man tut, ist natürlich immer, dass man auch nicht so gut ausdrücken kann, was man tut :D Dieser Situation falle ich hier zum Opfer. Ich habe mir ein schönes buntes Bild (Signalflussplan) der (ersteinmal unabhängigen) Blöcke aufgemalt, die ich gedenke miteinander zu verschalten, um etwas CPU-artiges zu erhalten. Die arbeite ich jetzt so nach und nach ab (und stoße erwartungsgemäß auf überall auf Schwierigkeiten, aber wie sonst soll man auch etwas lernen :D). Also in meiner ersten Version des Registersatz-Moduls hatte ich so etwas wie:
1 | signal register_set : std_logic_vector( num_registers*16-1 downto 0 ); |
Also num_registers 16-Bit-Werte. Als ich den herauskommenden Block inklusive dahinterligenden Lese-Mux einmal synthetisieren lassen habe und mir das Resultat anschaute, kam die Ernüchterung. Im nächsten Versuch ging es folgendermaßen:
1 | type registers_t is array (0 to 15) of std_logic_vector(15 downto 0); |
2 | signal registers : registers_t := (others => (others => '0')); |
Im Internet fand ich die Information, dass solcherart Signaldeklaration von Xilinx in eine Blockramumsetzung synthetisiert wird. Nun hatte ich vor Deiner Bemerkung eigentlich nicht damit gerechnet, dass das zu Problemen führen würde. Ich ging jetzt davon aus, dass dieser RAM imstande wäre, die ganzen Operationen innerhalb eines Taktzyklusses durchzuführen, der HDL-Synthesizer hat mich bislang auch noch nicht gewarnt, dass es da zu irgendwelchen Taktproblemen kommen würde und ich habe diese Syntax auch schon in einem anderen Miniprojekt erfolgreich eingesetzt, ohne dass mir da Taktverzögerungen aufgefallen wären (ich beginne stark zu erröten, offenbar habe ich also nicht aufmerskam simuliert). Selbstversändlich möchte ich lieber Speicher ohne Taktverzögerung verwenden, danke dass Du mich darauf hinweist. Jetzt muss ich nur noch herausfinden, wie ich dafür sorge, dass Register synthetisiert werden. Zu der Sache mit Superskalar: Ich habe das eben mal auf Wikipedia nachgeschlagen und muss hier ganz klar etwas besser beschreiben. Ich möchte keinesfalls so etwas abgefahrenes machen! :D Was ich mir vorstellte waren die einzelnen ALU-Operationen als diskrete Bausteine hinter einem Quellenmuxer (Der Daten aus den Registern nimmt, oder Konstanten anlegen kann) an einen "Bus" mit 4 Operandenkanälen anzuschließen. Dann wird der dem Befehl entsprechende ALU-Block Block1 "getriggert", der sich die Daten vom Bus nimmt und seine Sache erledigt. Diese Operation kann natürlich länger dauern, als ein Taktzyklus. Solange der nächste Maschinenbefehl aber diese ALU-Einheit nicht benötigt, soll das egal sein und eine andere Einheit Block2 erledigt ihren Job. Soweit so gut. Nun kommt es aber zwangsläufig zu der Situation, dass zu einem Takt t1 sowohl Block1 als auch ein anderer Block2 ein Ergebnis fertiggestellt haben. Jetzt stehe ich vor der Möglichkeit, die gesamte Pipeline anzuhalten, um eine zusätzliche Register-Schreib-Operation reinzuquetschen, oder ich finde irgendwie einen Weg, wie ich zwei Register in einem Takt beschreiben kann. Pipeline ist auch wieder etwas zu hoch gegriffen. Mit der "Pipeline" verfolge ich kein anderes Ziel, als die einzelnen Funktionsbausteine in einer solchen Form voneinander zu trennen, dass ich da überhaupt noch durchsteige, das hat hier überhaupt nichts damit zu tun, etwas abgefahrenes machen zu wollen :D Hoffentlich klingt das ganze jetzt weniger stark super-überambitioniert :D Viele Grüße, Deci
Bernd S. schrieb: > Erst mal: Respekt vor der eigenen CPU, das ist ein Haufen Arbeit. > > Register sind per se Register und KEIN RAM oder BlockRAM. Ich hab noch > nicht so genau verstanden warum du das also nicht in Registern > realisieren willst? Z.b. weil das nicht zu knapp an Resourcen spart? Wobei ich distributed RAM (oder wie immer das bei Xilinx heisst) verwenden würde. > Ist dir bewusst, dass ein typischer Prozessor schon > allein deshalb Register (=FF) benutzt, weil diese das Ergebnis mit 1 > Takt übernehmen? RAM speichert in den meisten Fällen mit schnellen > Taktraten das Ergebnis nicht innerhalb 1 Taktes sondern braucht evtl. 4 > oder 8 Takte, je nach Taktrate natürlich, das ist der Grund warum das so > gemacht wird. > Was so nicht stimmt. BlockRAM kann sehr wohl in einem Takt geschrieben bzw gelesen werden. Auch bei hohen Taktraten.
Hallo nochmal! Danke, dass Du mich auf dual port ram gestoßen hast, ich habe mich eben mal durch die Dokumentation des Vendor-Blocks gekämpft. Zumindest mal der Blockram unterstützt TDP, also 2 komplett unabhängige Schrebeingänge, die im gleichen Takt schreiben. Wenn ich jetzt noch die Anmerkung von Lattice User dazunehme, dass es keine Taktlatenzen gibt, scheint es genau das zu sein, was ich brauche. Wer hätte gedacht, dass Xilinx das so vorausschauend für mich vorbereitet hat! \o/ Jetzt mal schauen, ob das auch irgendwie mit dem Distributed-Ram geht (der jetzt meinem ersten Verständnis nach den Vorteil bietet, dass er näher an die Logik platziert werden kann, wenn ich das richtig verstehe?) Außerdem kann man da im gleichen Takt noch kombinatorisches hinterschalten, weil das Lesen asynchron vonstatten geht, ja? Das klingt natürlich nach größerem Spielraum! Danke euch erstmal, über weitere Anmerkungen und Zurechtweisungen freue ich mich natürlich noch immer ;) Viele Grüße, Deci
> Im nächsten Versuch ging es folgendermaßen: type registers_t is array (0 to 15) of std_logic_vector(15 downto 0); > signal registers : registers_t := (others => (others => '0')); > Im Internet fand ich die Information, dass solcherart Signaldeklaration > von Xilinx in eine Blockramumsetzung synthetisiert wird. Ja, ich kann mir vorstellen, dass man Blockram auch implizit deklarieren kann. Wenn das dann schnell genug ist (schreibt und liest in 1 Takt) und der Syntheseprozess also das Timing implizit sicherstellt, dann ist das auch kein Problem. In meiner Vorstellung war BlockRAM jetzt aber eine Black Box, welche man generiert und dann als COMPONENT einbindet. Und hier ist man dann selber verantwortlich, dass man diese black box mit dem richtigen Timing (Wartezyklen usw.) anspricht. Diese Denke kommt bei mir natürlich auch davon, dass ich bei solchen Konstrukten den RAM einfach durch einen anderen austauschen kann, z.B. wenn man als Zieltechnologie ein ASIC hat. Dann bindet man anstatt das Xilinx BlockRAM dann halt das ASIC-RAM in die Simulation ein. Und wenn man das RAM als black box betrachtet, dann können Prozessor-Register fast zwingend nur als FF ausgeführt werden. Entweder richtige FF im ASIC oder halt als LUT im FPGA. > Jetzt stehe ich vor der Möglichkeit, die gesamte Pipeline anzuhalten, um > eine zusätzliche Register-Schreib-Operation reinzuquetschen, oder ich > finde irgendwie einen Weg, wie ich zwei Register in einem Takt > beschreiben kann. Ja, verstehe, hat natürlich Ansätze von superskalarität. Hier muss also geprüft werden ob die beiden Blöcke in die gleichen Zielregister schreiben, und wenn nicht können sie gleichzeitig schreiben, zumindest dann wenn es für jeden Block eine Datenleitung/Mux zu allen Registern gibt. Die Überprüfung ob sie auf das gleiche Register schreiben ist bei Prozessorbefehlen die das Zielregister direkt als Operand in sich tragen einfach, bei indirekter Adressierung wirds natürlich schon aufwändiger. Das Anhalten der Pipeline oder wenn du keine Pipeline hast, das Anhalten des Prozessors um etwas bestimmtes herauszufinden ist Standard, da wirst du wohl kaum herumkommen.
decimad schrieb: > also 2 komplett unabhängige > Schrebeingänge, die im gleichen Takt schreiben. ja, aber gleichzeitig auf die gleiche Adresse zu schreiben, kann ja nicht gut gehen.... Distributed RAM ist übrigens im Endeffekt nichts anderes als die LUTs als Speicher herzunehmen und nicht die Standard RAM-Zellen die im FPGA zusätzlich zu den LUTS auf dem Die vorhanden sind. Ist natürlich schneller weil hier kein Lese-/Schreibvorgang auf die Standard-RAM-Zelle gemacht werden muss. Verbraucht so weit ich weiß halt eine Menge der LUTs für relativ wenig Speicher. Aus meinem Verständnis würde ich alle Prozessorregister als FF realisieren und nicht versuchen dafür BlockRAM herzunehmen. Aber ich lasse mich auch gern eines Besseren belehren, man lernt ja ständig dazu. Wenn du einen getakteten Prozess machst, dann sind alle Zielsignale schon FFs. z.B. process (clk) begin register1 := hilfsregister; end process; Und schon ist register1 als FF realisiert. Wozu also sich mit Blockram herumschlagen? Wenn der Syntheseprozess aus irgendwelchen Gründen das nicht in den LUTs speichert sondern intern im BlockRAM-Block auf dem Die speichert und sich automatisch um die dazu notwenigen Lese-/Schreibzyklen kümmert, kann es dem geneigten VHDL-Coder völlig egal sein wo das Datum gelandet ist. In der RTL-Beschreibung ist es eben als FF realisiert...
müsste natürlich heißen register1<=hilfsregister; soll ja ein Signal sein...
Der BlockRAM ist in der Regel so schnell wie auch das interne Taktnetzwerk, jedenfalls bei Xilinx. Bringt also gegenüber dem LUT RAM da keinen Nachteil. LUT RAM kann sinnvoll sein, wenn man den BRAM für was anderes braucht, oder die gewünschte RAM Größe weniger als 1k ist, denn dann geht jedesmal auch ein ganzer BRAM Block drauf. Die nötigen Schreibzugriffe beschreibt man ja in VHDL selber, wenn man die Lese-Adresse getaktet anlegt, wird ein BlockRAM generiert, ansonsten ein LUT-RAM. Beim BLock RAM mit 2 Ports muss man natürlich aufpassen, was man tut, aber das ist ja klar. Im XST User Guide oder auch in den Templates in der ISE ist das eigentlich sehr gut erklärt...
Hallo Leute, danke für die tollen Anregungen! Die Register-Bank meiner CPU ist jetzt bei mir sozusagen als Blackbox mit 2 Schreibeingängen und zwei Register -> Ausgangsbus Multiplexer ausgelegt. Für's erste werde ich weiter Block-Ram verwenden, denke ich, einfach weil ich im Moment keine andere Verwendung dafür habe (außer dann vielleicht einen LUT für Sinus/Kosinus oder andere Funktionen, aber es sind ja noch ein paar übrig). Wenn es dann mal eng wird und ich den RAM brauche, aber noch Logik übrig habe, kann ich die Blackbox dann ja einfach anders umsetzen, so ist das doch gedacht, oder? :) Und so kann ich einen ziemlich großen Satz an Registern vorhalten, was mangels Speichercontroller und Compiler erstmal verlockend klingt (Speichercontroller wär' natürlich was tolles, irgendwann einmal, wenn ich weniger wenig weiß, was ich da tue). So wie ich das im Moment umsetze, wird ein einfacher Befehl dann 3 Takte benötigen: Auf Bus lesen -> Berechnen -> Zurückschreiben. Aber das läuft dann wenigstens ohne Probleme beim Referenztakt, was ganz gut ist, weil ich mich im Moment noch nicht damit herumschlagen möchte, eine weitere Taktdomäne mit erforderlicher Synchronisierung einzuführen. Denn dann habe ich noch weniger Ahnung, was ich da eigentlich tue :D Erstmal kleine Brötchen backen, oder wie sagt man? :) Aber eine "33MHz" CPU ist ja auch erstmal nicht schlecht, hehe. So, jetzt erstmal weitermachen hier, so ein Wochenende ist ja nicht endlos lang. Viele Grüße, Deci
Anmerkung... (ich sollte mich einmal registrieren!) Wobei der Durchsatz in meinem, bei entsprechender Aneinanderreihung von Befehlen natürlich 1 Befehl pro Takt sein kann, wenn man eben keinen Register als Quelle referenziert, der als "busy" markiert ist, also auf ein Berechnungsergebnis wartet. Der Steuer-Multiplexer für die ALU-Einheiten könnte eigentlich auf dem Rückschreibe-Bus mitlauschen und die Berechnungsergebnisse dort "abfangen", das würde wieder einen Takt sparen. Hrmmm, aber erstmal einfach machen... :)
So, ich war sogar schon registriert, hehe, die Gedächtnisleistung zählt nicht zu meinen Stärken! o/
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.