TLDR: Benötigt man bei Xilinx LUTRAM auf der Ultrascale-Architektur
zwingend einen externen Mechanismus zur Vermeidung von
Adresskollisionen?
Ich bin gerade darüber gestolpert, dass Vivado für einen per xpm
instanziierten RAM mit der Einstellung "auto" einen distributed RAM
statt eines erwarteten BRAM erzeugt hat. Der RAM ist dual-port mit
unabhängigen
Takten. Im timing report gab es eine Violation zwischen diesen Ports.
Im UG974 [1] bin ich fündig geworden, wie das zu lösen ist (siehe
unten). Man muss für LUTRAM ein false_path constraint setzen. Allerdings
steht dort explizit dabei, dass das Design sich um Adresskollisionen
kümmern muss.
Ich bin mir nicht sicher, wie das zu interpretieren ist. Im Normalfall
sind mir Adresskollisionen eigentlich egal, weil ich per Design sicher
bin, dass ich das Ergebnis der Leseoperation nie brauche, wenn gerade
auf derselben Adresse geschrieben wird. Im Grunde geht es mir nur darum,
dass die Schreiboperation auch durchgeführt wird, auch wenn die
Leseadresse zufällig gerade auf derselben Adresse steht. Bei BRAM ist
das ja auch kein Problem.
Bei LUTRAM könnte das evtl. anders sein, da dass den Speicher der LUTs
nutzt. Dort könnte je nach interner Implementierung etwas undefined
werden, wenn Schreib- und Leseoperation auf den gleichen Speicher
gehen. Hat jemand Erfahrungen, ob das wirklich so ist (für die
Ultrascale-Architektur)? D.h. muss ich mich wirklich um einen externen
Adresskollisionsmechanismus kümmern? Laut UG574 [2] sieht das eigentlich
unkritisch aus. Auf die Simulation will ich da auch nicht vertrauen,
weil das Modell u.U. nicht alles unterstützt, was in HW passiert (wie
z.B. bei den WRITE_MODEs). Der WRITE_MODE wäre mir aber auch egal, da
ich das Ergebnis einer Leseoperation auf derselben Adresse zum gleichen
Zeitpunkt wie gesagt nicht brauche.
1
set_false_path constraint is needed for the independent clock distributed RAM based memory if the design takes care of avoiding address collision (write address != read address at any given point of time). Set USE_EMBEDDED_CONSTRAINT = 1 if XPM_MEMORY needs to take care of necessary constraints. If USE_EMBEDDED_CONSTRAINT = 0, Vivado may trigger Timing-6 or Timing-7 or both. Alternatively, you can also add the constraint when USE_EMBEDDED_CONSTRAINT = 0. An example of adding this constraint is provided below. If Port-B also has write permissions for an Independent clock configuration, then a similar constraint needs to be added for clkb as well.
Für Ultrascale müsst ich erst recherchieren, bei älteren Architekturen
kann man doch den Synchronisationsmodus explizit angeben: write-first,
read-first, no-change.
https://docs.amd.com/r/en-US/ug901-vivado-synthesis/Block-RAM-Read/Write-Synchronization-Modes
Ist jetzt mit addresskollision gemeint, das der RAM zwei unabhängige
ports hat und an beiden die selbe addresse anliegt ?! Und LUT-RAM ist
das selbe wie distributed-RAM ? (Der Link oben ist für BRAM)
In folgenden Forum-beitrag wird auf die Möglichkeit des asynchronen read
bei distributed hingewiesen, was das selbe wie erite first wäre:
https://www.eevblog.com/forum/fpga/xilinx-slicem-luts-as-distributed-rams-read-first-write-first-or-selectable/
IMHO sollte man zuerst versuchen einen BRAM zu "erzwingen", ggf mit
direkter Instantiierung des BRAM-Makros oder eines generierten
memory-IP-Cores.
UG574 passt schon. Das RAM sind dann die LUTs im SLICEM. Das hat 8 LUTs
mit 6 Eingängen, also 8*64 Bit.
So ein RAM hat aber immer nur einen Takteingang.
Weil das Lesen aber asynchron passiert, kannst du die Leseadresse mit
einem anderen Takt erzeugen. Und mit diesem Takt kannst du dann auch die
gelesenen Daten in einer Registerstufe takten.
Man kann jetzt aus diesen RAMs recht flexibel zusammenbauen was man
will. Singleport, Dualport, ...
Wobei man aufpassen muss mit den Bezeichnungen. Dualport heißt das im UG
schon, wenn es eine getrennte Schreib- und Leseadresse gibt. Damit kann
man also nicht gleichzeitig an zwei verschiedene Adressen schreiben.
Das set_false_path sagt doch eigentlich nur, dass die Timinganalyse das
nicht beachten soll. Und das willst du in dem Fall ja auch. Weil die
Leseadresse aus einer anderen Taktdomäne stammt gibt es kein definiertes
Timing zwischen den Schreibdaten und Lesedaten. Daten werden getaktet
geschrieben und liegen dann nach dem einen Takt im Speicher. Und erst
danach können die auch korrekt gelesen werden.
Danke für eure Antworten.
Bradward B. schrieb:> Für Ultrascale müsst ich erst recherchieren, bei älteren Architekturen> kann man doch den Synchronisationsmodus explizit angeben: write-first,> read-first, no-change.
Ja, bei Ultrascale auch, allerdings kann distributed RAM nativ erstmal
nur read-first (bzw. liest asynchron). Aber der Schreibmodus ist mir wie
gesagt egal.
Bradward B. schrieb:> Ist jetzt mit addresskollision gemeint, das der RAM zwei unabhängige> ports hat und an beiden die selbe addresse anliegt ?! Und LUT-RAM ist> das selbe wie distributed-RAM ?
Ja und ja.
Bradward B. schrieb:> In folgenden Forum-beitrag wird auf die Möglichkeit des asynchronen read> bei distributed hingewiesen, was das selbe wie erite first wäre:
Ich denke, das ist read first.
Bradward B. schrieb:> IMHO sollte man zuerst versuchen einen BRAM zu "erzwingen", ggf mit> direkter Instantiierung des BRAM-Makros oder eines generierten> memory-IP-Cores.
So ein xpm-template ist ja ein Makro. Ich muss etwas mit BRAM sparen und
für solche schmalen RAMs wie 512x4Bit verwendet Vivado bei der
Einstellung "auto" dann LUTRAM.
Gustl B. schrieb:> Das set_false_path sagt doch eigentlich nur, dass die Timinganalyse das> nicht beachten soll. Und das willst du in dem Fall ja auch.
Ja, das will ich. Mich wundert eben nur diese Aussage in UG974 "(write
address != read address at any given point of time)". Das klingt so, als
müsste man sich da eben drum kümmern, auch wenn es einen eigentlich per
Design nicht interessiert. Und dann müsste ich eben Zauber zwischen den
Taktdomänen machen, da Leseadresse und Schreibadresse einen
verschiedenen Takt haben.
Aber gut, ich muss das wahrscheinlich einfach langfristig ausprobieren.
Bis jetzt läuft es.
Interessant ist nebenbei auch, dass Vivado auf der Einstellung "auto"
einen LUTRAM mit write mode "NO_CHANGE" ohne Fehlermeldung nimmt
(warnings habe ich noch nicht geschaut). Stelle ich explizit
"distributed" ein, gibt es eine Fehlermeldung, dass ich zwingend auch
"read-first" einstellen soll.
Markus W. schrieb:> Das klingt so, als> müsste man sich da eben drum kümmern
Nein, musst du nicht. Lesen ist asynchron, es kommen eben immer die
Daten raus die an der Leseadresse liegen. Und genau während eines
Schreibvorgangs kann man nicht genau sagen wie lange noch die alten und
wann die neuen Daten rauskommen.
Aber wenn du damit leben kannst dann ist das völlig OK.
> Gustl B. schrieb:>> Das set_false_path sagt doch eigentlich nur, dass die Timinganalyse das>> nicht beachten soll. Und das willst du in dem Fall ja auch.>> Ja, das will ich. Mich wundert eben nur diese Aussage in UG974 "(write> address != read address at any given point of time)". Das klingt so, als> müsste man sich da eben drum kümmern, auch wenn es einen eigentlich per> Design nicht interessiert. Und dann müsste ich eben Zauber zwischen den> Taktdomänen machen, da Leseadresse und Schreibadresse einen> verschiedenen Takt haben.
Bloß nicht selber zwischen den Tahtdomäinen "zaubern" wenn man statt
dessen eine erprobte Lösung des Toolchainherstellers wie bspw. ein
FIFO-Makro nehmen kann.
Im Gesamt-text lese ich jetzt nicht so, das man diese Kollision
unbedingt vermeiden müßte, da steht eher, das wenn man diese
Adress-Kollision vermeidet (durch spezielle Hardware [Komperator]
zwischen den Addressen der beiden Ports) man auch set_fals_path setzen
muß - was IMHO selbstverständlich ist, weil eben die Timingt Analyse die
mit dieses set_fakse_path gesteuert wird, das Szenario unabhängige
clocks nicht lösen kann.
"set_false_path constraint is needed for the independent clock ... if
the design takes care of avoiding address collision ..."
Timing analyse kommt eben mit Pfaden zwischen verschiedenen Clock
domains nicht zurecht, weil eben die Grundvoraussetzung für die
Anwendung der Timing-analyse (quell und ziel in clockdomains mit fixen
phasen-bezug) nicht erfüllt ist.
> Aber gut, ich muss das wahrscheinlich einfach langfristig ausprobieren.> Bis jetzt läuft es.
Wenn man mit "langfristig ausprobieren" eigentlich "Qualifikation im
Dauerlauf" meint ist das sicher eine gute Entwicklungsstrategie.
Um mehrere Dauerläufe während der Entwicklung untereinander vergleichen
zu können, sollte man möglichst von "auto"-Einstellungen wegkommen.
Gerade beim Placement/mapping gibt es Fälle wo ein Design funktioniert,
wenn der LUT's "Rechts oben" genommen werden, aber "weniger" zuverlässig
sind
wenn verstreute LUT's genutzt werden.
> Interessant ist nebenbei auch, dass Vivado auf der Einstellung "auto"> einen LUTRAM mit write mode "NO_CHANGE" ohne Fehlermeldung nimmt> (warnings habe ich noch nicht geschaut). Stelle ich explizit> "distributed" ein, gibt es eine Fehlermeldung, dass ich zwingend auch> "read-first" einstellen soll.
Vielleicht ist es ja funktional weitgehend das selbe, wenn etwas zuerste
gelesen wird, dann wird es ja auch in diesen Moment nicht geändert, ganz
im Unterschied zu "write_first". den Unterschied zu "no_change" würde
man erst beim zweiten Lesen von der selben Adresse bemerken - allerdings
gibt es bei FIFO als Speicherkonfigurationen mit automatischer
Address-Weiterschaltung dieses Szenario (unmittelbar wiederholtes Lesen
von der selben Adresse nicht). Und wenn die Tools keine Ferhlermeldung
ausgeben, heisst es nicht, das es funktional korrekte Einstellung ist.
Es heisst lediglich, das es so im FPGA einstellbar ist und die Tollss
davon ausgehen, das der user schon weiss warum er diese ("unsinnigen"
aber möglichen) Einstellungen so wählt.