Hallo!
Ich arbeite an einem SDRAM-Controller für einen CycloneII und hab mich
dabei wohl zu lange mit Modelsim beschäftigt. Ich hatte gestern schon
eine laufende Version, die aber laut Simulation mit Back Annotation
Werten von Quartus maximal 50Mhz schaffte, darum bin ich heute lange
gesessen und habe meine State Machine umgebaut, Bank Interleaving
rausgelassen und generell versucht, alles kürzer zu machen, mein Ziel
wären 100Mhz.
Jetzt kämpfe ich seit einiger Zeit mit einigen Problemen, so bekomme ich
haufenweise Warnings wegen inferred latches. Bei einigen Signalen kommen
mir latches ganz angebracht vor, wie zb das Signal, das die
Notwendigkeit eines Refresh anzeigt. Das kann ja kommen, wenn ich mitten
in einer Operation bin und soll dann bis zum nächsten Idle-Zustand
gesetzt sein, um dann den Refresh durchzuführen. Wenn es ginge würde ich
das natürlich gern sauber machen, aber ein einfaches Kochrezept dafür
habe ich im Netz bisher nicht gefunden.
Bei einigen der Latches meldet Quartus, diese könnten "unsafe behaviour"
zeigen, darunter welche, die ich gar nicht als Signale eingeführt habe,
sondern die wohl in der Synthese entstanden sind, diese tragen Namen wie
"comb_1234" usw. Was ich mit denen tun soll, ist mir überhaupt nicht
klar.
Jetzt bin ich an einem Punkt angelangt, an dem mir Quartus mitteilt,
dass das Design evtl nicht funktionieren wird, denn:
"Warning: Circuit may not operate. Detected 201 non-operational path(s)
clocked by clock "clk" with clock skew larger than data delay. See
Compilation Report for details."
(201, na servus :( )
In der Timing Simulation bewahrheitet sich das dann leider, es kommt de
facto Müll heraus.
In der Theorie sind mir die Probleme klar, wenn zb Latches dasselbe
Signal am D- und am ENA-Pin haben oder das mit dem Clock Skew. Aber in
der Praxis fehlt mir der Durchblick, wie ich in VHDL coden soll, um
diese Probleme zu vermeiden. Ich habe eine State Machine, die in einem
Prozess auf Basis des aktuellen state und der Inputs asynchron den
next_state setzt, in einem zweiten auf Basis des next_state asynchron
die nächsten Outputs und in einem dritten Prozess wird synchron mit
jeder clk-Flanke der next_state an den state zugewiesen. Soweit sollte
das State-of-the-Art sein. Wie muss man die Inputs lesen und die Outputs
setzen, damit man nicht diese Probleme bekommt?
Bin über jede hilfe dankbar, insbesondere auch gute Links, in denen man
sich dazu was durchlesen kann.
ligrü Matthias
Latches solltest du erst einmal als "böse" betrachten und vermeiden.
Bei dem angesprochenen Refresh-Signal tut es auch ein Flipflop mit
Clockenable.
Weist du jedem Signal in jedem möglichen Fall von if oder case einen
Wert zu, so hast du schon mal 95% der Latches vermieden. Die anderen 5%
wirst du los durch Verwendung von Clockenable und durch registrieren
aller Signale bevor sie weiterverwendet werden.
Hallo Matthias,
würde Dir der VHDL-Code für einen getesteten DDR-SDRAM-Controller
was helfen (133 MHz). Ist allerdings für XILINX geschrieben, aber
vielleicht als Beispiel brauchbar?
Eine weitere Möglichkeit ist die Vorbelegung mit default. Ferner sollte
man alle Signale im reset verdrahten. Das anze ist aber nicht trivial,
hast schon recht. Ich selber habe aber auch immer noch Probleme mit
einigen Latches in einigen Designs, die sich mir einfach nicht
erschließen wollen, weil es aehnlich verdrahtete Signale gibt, die
Quartus nicht anmeckert.
@Jan M.: Meine State Machine ist schon ziemlich umfangreich und auch die
Zahl der Signale ist nicht klein, in Summe führt das zu einer Menge von
Zuweisungen, die ich dann nur mehr schwer überblicke. Ich weiß nicht, ob
es besser wird, wenn ich es feiner strukturiere. Mein Ziel wäre
natürlich, diese Fehler komplett loszuwerden, aber da fehlt mir eben der
Durchblick. Wenn Du schreibst:
"Bei dem angesprochenen Refresh-Signal tut es auch ein Flipflop mit
Clockenable."
kann ich damit in der Theorie zwar etwas anfangen, aber wie ich das in
vhdl mache (ein eigener Prozess, der auf rising_edge(clk) reagiert und
das Signal zuweist? kann ich fürs rücksetzen des signals, ebenfalls
wieder flankengesteuert den wert des state-signals verwenden und zb beim
letzten refresh-zustand zurücksetzen?) sodass es nach der Synthese
funktioniert ist mir noch nicht klar.
"registrieren aller Signale" habe ich auch noch nicht ganz verstanden,
ist damit das Zwischenspeichern in einem Register gemeint? Eine der
Dinge, die ich erreichen möchte, ist dass ich eine Anfrage gleich (also
bis zum nächsten Clock Cycle) an den SDRAM durchschalten möchte, wenn
die entsprechende Bank/Row schon geöffnet ist. Wenn ich die Daten
dazwischen in ein flankengesteuertes Register schreibe muss ich doch
einen zusätzlichen Wait State einführen, oder?
@Mark: Danke, würde wohl was helfen, aber das ganze ist eine Sache für
Uni, also leider nicht so, dass ich einfach was funktionierendes nehme
und mich dann anderen Dingen zuwende.
Meine Motivation ist auch eher, das Ganze abseits der Theorie in der
praktischen Umsetzung zu begreifen, darum möchte ich lieber die Probleme
meines Modells lösen, als mir woanders was abzuschauen. Aber wenn Du den
Source online stellen kannst schaue ich ihn mir sicher interessiert an.
@tippgeber: Uups, habe selber so lange an meiner Antwort geschrieben,
dass cih Deine nicht gesehen habe. Wenigstens beruhigend, dass ich nicht
der einzige bin, der sich mit so etwas herumärgert.
Bzgl "Ferner sollte man alle Signale im reset verdrahten.": Kommt mir
auch logisch vor, aber es war jetzt eben so, dass ich im VHDL-Modell
Fehler hatte, wenn die Outputs beim Anliegen des Reset-Signals keinen
fixen Wert haben, darum habe ich dort eine Abfrage wie folgt
installiert:
if reset = '1' then
-- setze alle Outputs auf sinnvolle Werte
else
case next_state is
when ...
end if;
Und das war der Punkt, an dem das Timing vollkommen dahin war und
Quartus die Warnung geliefert hat, dass der Clock Skew größer wird als
die Durchlaufzeit für die Daten.
Darum denke ich, dass ich mir da mehr dazu durchlesen muss, für mich ist
das einfach nicht nachvollziehbar, warum diese Änderung das Design so
komplett zusammenhaut.
>die Zahl der Signale ist nicht klein, in Summe führt das zu einer Menge>von Zuweisungen, die ich dann nur mehr schwer überblicke.
Meine state machines sehen immer folgendermaßen aus:
Zuerst werden allen Signalen default-Werte zugewiesen, also entweder
next_signal <= 0; oder next_signal <= signal; , je nach dem, was gerade
gebraucht wird. Dann erst folgt die Auswertung der Zustände. So kann
mankein Signal vergessen und allen Signalen ist immer ein Wert
zugewiesen.
Dann der synchrone Prozess, der alle Signale registriert. Und nur diese
registrierten Signale erscheinen irgendwo in Abfragen oder in
Zuweisungen auf der rechten Seite.
>"registrieren aller Signale" habe ich auch noch nicht ganz verstanden,>ist damit das Zwischenspeichern in einem Register gemeint?
Ja genau.
>if reset = '1' then> -- setze alle Outputs auf sinnvolle Werte>else> case next_state is
Der reset gehört in den synchronen Prozess, auch wenn er asynchron ist:
1
if reset then
2
else if risingedge(clk)
3
else
4
endif
oder
1
if risingedge(clk) then
2
if reset
3
else
4
endif
5
else
6
endif
>>"Bei dem angesprochenen Refresh-Signal tut es auch ein Flipflop mit>>Clockenable.">kann ich damit in der Theorie zwar etwas anfangen, aber wie ich das in>vhdl mache (ein eigener Prozess, der auf rising_edge(clk) reagiert und>das Signal zuweist? kann ich fürs rücksetzen des signals, ebenfalls>wieder flankengesteuert den wert des state-signals verwenden und zb beim>letzten refresh-zustand zurücksetzen?) sodass es nach der Synthese>funktioniert ist mir noch nicht klar.
Kombinatorisch die Bedingung für den Refresh:
1
refresh_ff_en <= refresh_faellig or refresh_abgearbeitet;
Diese beiden Signale dürfen natürliche beide immer nur einen Takt lang
sein. Dann komt ein toggle-flipflop:
1
if risingedge and refresh_ff_en then
2
mach_refresh <= not mach_refresh;
>Eine der Dinge, die ich erreichen möchte, ist dass ich eine Anfrage>gleich (also bis zum nächsten Clock Cycle) an den SDRAM durchschalten>möchte, wenn>die entsprechende Bank/Row schon geöffnet ist. Wenn ich die Daten>dazwischen in ein flankengesteuertes Register schreibe muss ich doch>einen zusätzlichen Wait State einführen, oder?
Denke in der Logik schon einen Takt weiter, dann brauchst du keine
waitstates. Wenn die Bank offen ist und ich sie jetzt nicht schließe,
dann wird sie im nächsten Takt auch noch offen sein.
Nachdem ich heute bedingt durch meine Freundin ein paar waitstates hatte
;) habe ich mich mit dem "Digital Design"-Buch von Wakerly hingesetzt
und anschließend meine State Machine genau so wie er das vorschlägt
gecoded. Jetzt bin ich die Latches alle los :), es schaut so aus: zu
jedem Signal xxx gibt es ein next_xxx Signal. Das Ganze läuft in drei
Prozessen, einer ist der synchrone STATE_MEMORY und weist jeweils xxx <=
next_xxx zu, ein zweiter ist für die NEXT_STATE_LOGIC und weist die
next_xxx asynchron auf Basis von state und Inputs zu und ein dritter ist
OUTPUT_LOGIC, der die outputs asynchron auf Basis von state und inputs
setzt. Ich denke, bei der Struktur werde ich bleiben weil sie die
Mealy-State-Machine sehr logisch abbildet. Damit kommt genau das heraus,
was Jan M. schreibt, die registrierten Signale werden nur im synchronen
Prozess gesetzt, in den anderen Prozessen werden für jeden state-wert
die next_xxx zugewiesen.
Eine Frage an Jan M.: Du schreibst:
> Der reset gehört in den synchronen Prozess, auch wenn er asynchron ist:
Das gilt für alle xxx Signale, aber die next_xxx und die outputs werden
ja jeweils in einem anderen Prozess gesetzt, darum habe ich jetzt in
jedem der drei Prozesse eine reset-Abfrage, die jeweils die zugehörigen
Werte setzt. Das geht doch nicht anders, da ich ein Signal nur jeweils
in einem Prozess zuweisen kann, oder?
Und die Timing-Probleme sind leider nicht zu Ende, mein Ziel wären ja
die 100Mhz, aber das scheint schwierig zu werden, da ich teilweise
kombinatorische und Propagation Delays weit über 10 ns, teils über 20 ns
habe. Wenn ich zb eine Schreibeanforderung im nächsten Zyklus an den
SDRAM weiterreichen möchte, muss ich einige Dinge abfragen: zuerst das
Schreibesignal selbst, dann ob die Operation evtl schon vor einigen
Zyklen (zb während einem Refresh) beantragt wurde, dann ob Bank und Row
gleich geblieben sind und dann erst kann ich das Kommando an den SDRAM
weitergeben. Ich habe schon ein bisschen mit Timing Constraints im
Quartus herumgespielt, aber bei der Timing Simulation hat es mich bis
jezt leider immer geschmissen.
Entweder es gibt da noch ein geniales Rezept oder ich brauche doch einen
zusätzlichen waitstate, während dem ich zb eine Pipeline mit den nötigen
Komandos füllen könnte.
Die next_xxx Signale brauchst du nicht zu reseten, es reicht der reset
von xxx: Da diese auf Basis der synchronen Signale asynchron zugewiesen
werden, haben diese innerhalb des ersten Taktes des reset definierte
Werte. In Zuweisungen werden sie nicht benutzt, deswegen ist es egal,
wenn sie in diesem ersten Takt undefinierte Werte haben sollten.
Ich kenne mich mit der Ansteuerung von sdram nicht aus, aber du
beschreibst das, als müsstest du die ganzen Überprüfungen nacheinander
machen - geht das nicht parallel?
"einer ist der synchrone STATE_MEMORY und weist jeweils xxx <=
next_xxx zu, ein zweiter ist für die NEXT_STATE_LOGIC und weist die
next_xxx asynchron auf Basis von state und Inputs zu und ein dritter ist
OUTPUT_LOGIC, der die outputs asynchron "
Welch Erkenntnis. Das lernt man eigentlich im ersten Semester. Leutz,
ihr muesst mehr reale Schaltungen zusammenlöten, daß ihr die Signale VOR
und NACH den FLipflops / Kombinatorik auseinanderhalten könnt - auch
wenn die später im FPGA bei schneller (schlampiger Programmierung)
denselben Namen haben ...
Matthias wrote:
> ein dritter ist> OUTPUT_LOGIC, der die outputs asynchron auf Basis von state und inputs> setzt.
Kann das nicht zu Problemen führen wenn am Ausgang kein Register ist
(Glitches)?
@ Jan M.: Wie soll ich das parallel machen? Zb der Vergleich der
abgefragten Bank/Row mit der offenen, der wohl das aufwändigste ist:
Soll ich asynchron immer einen Vergleich der offenen mit der abgefragten
Bank machen und das Ergebnis dann nach Auslesen der Request-Signale
verarbeiten? Muss ich da nicht höllisch auf die Laufzeit der einzelnen
Gatter achten?
@ tippgeber: Mag sein, unsere VHDL-Übung auf der Uni war sehr dürftig,
gerade mal, dass man im Max+Plus die richtigen Befehle findet und
Syntaxfehler in einem vorgegebenen Design ausbessert. Den
SDRAM-Controller from scratch zu schreiben fand mein Betreuer auch schon
ziemlich ambitioniert.
@ Andreas Schwarz: Ja, es passieren mir auch Glitsches, aber solange
das Signal bei der Clk-Flanke stabil ist, habe ich das nicht als Problem
betrachtet.
Hallo Matthias,
"Danke, würde wohl was helfen, aber das ganze ist eine Sache für
Uni, also leider nicht so, dass ich einfach was funktionierendes nehme
und mich dann anderen Dingen zuwende.
Meine Motivation ist auch eher, das Ganze abseits der Theorie in der
praktischen Umsetzung zu begreifen, darum möchte ich lieber die Probleme
meines Modells lösen, als mir woanders was abzuschauen. Aber wenn Du den
Source online stellen kannst schaue ich ihn mir sicher interessiert an."
-> der Code steht online bei opencores.org -> DDR SDRAM Controller Core
habe ich selber geschrieben und auf einem Virtex-Eval-Board getestet.
Sowas ist allerdings kein Anfängerprojekt. Nach den Postings zu urteilen
würde ich sagen, Deine einzige Chance was in dieser Art umzusetzen ist
anhand von solchen Beispielen. Ansonsten werkelst Du noch monatelang
rum,
wenn Du Dir das alles selbst erarbeiten willst.
Wenn so eine RAM-Ansteuerung laufen soll musst Du fit sein in der
Implementierung von schnellen State-Machines, musst komplett verstanden
haben was Dein Synthesetool aus dem VHDL-Code macht und
Timing-Constaraints
im Schlaf setzen können. Sonst wirds schwer - sorry.
Danke, ich quäl mich ja schon länger damit rum, da ist es jedenfalls
besser zu hören, dass es schwer ist, wenn es leicht wäre, würde es mir
wohl an der nötigen Hirnmasse für diesen Bereich fehlen.
Ich hab mir den DDR Controller jetzt ein Weilchen angeschaut, meiner ist
nur SDR aber ich denke, ich versteh das Timing halbwegs. Mir kommt vor,
ich will einfach zu viel von meinem, Dein Controller setzt wohl mehr auf
Pipelining und ist dadurch aber deutlich schneller, weil er die
Ansprüche an tpd und tco erfüllen kann. Ich werde wohl wirklich Cycles
opfern und versuchen, meinen damit schneller zu bekommen.