Forum: FPGA, VHDL & Co. Ein Dschungel von Timing Problemen nach der Synthese


von Matthias (Gast)


Lesenswert?

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

von Jan M. (mueschel)


Lesenswert?

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.

von Mark (Gast)


Lesenswert?

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?

von tippgeber (Gast)


Lesenswert?

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.

von Matthias (Gast)


Lesenswert?

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

von Matthias (Gast)


Lesenswert?

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

von Jan M. (mueschel)


Lesenswert?

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

von Matthias (Gast)


Lesenswert?

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.

von Jan M. (mueschel)


Lesenswert?

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?

von tippgeber (Gast)


Lesenswert?

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

von Andreas S. (andreas) (Admin) Benutzerseite


Lesenswert?

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)?

von Matthias (Gast)


Lesenswert?

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

von Mark (Gast)


Lesenswert?

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.

von Matthias (Gast)


Lesenswert?

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.

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.