Forum: FPGA, VHDL & Co. Multiple Schreiboperationen in Blockram


von decimad (Gast)


Lesenswert?

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?

von Bernd S. (Gast)


Lesenswert?

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

von decimad (Gast)


Lesenswert?

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

von Lattice User (Gast)


Lesenswert?

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.

von decimad (Gast)


Lesenswert?

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

von Bernd S. (Gast)


Lesenswert?

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

von Bernd S. (Gast)


Lesenswert?

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

von Bernd S. (Gast)


Lesenswert?

müsste natürlich heißen

   register1<=hilfsregister;

soll ja ein Signal sein...

von Christian R. (supachris)


Lesenswert?

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

von decimad (Gast)


Lesenswert?

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

von decimad (Gast)


Lesenswert?

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

von Michael S. (decimad)


Lesenswert?

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
Noch kein Account? Hier anmelden.