Hallo zusammen,
ich habe nach sehr langem Testen ein für mein Empfinden äußerst
komisches Verhalten bei meinem VHDL Code festgestellt. Ich versuche es
mal kurz zusammenzufassen:
Ich habe eine Schnittstelle umgesetzt, welche Signale nach außen
sendet/schreibt und empfangen/lesen kann. Die Schnittstelle wird in
meinem Top-Level instanziiert, mit einem enable-Signal wird der Lese-
oder Schreibprozess gestartet und mit einem internen Signal readWrite
wird festgelegt, ob geschrieben oder gelesen werden soll. Alles
funktioniert wunderbar.
In einer Test-Anwendung muss nun nur geschrieben werden. Um keinen
Aufwand zu haben, hatte ich in der port map das readWrite Signal einfach
fest auf '1' gesetzt. Die Verhaltens-Simulation zeigte weiterhin das
gewünschte Verhalten. Bei einer Post-Translate-Simulation war das
Verhalten jedoch ziemlich verwirrend und entspricht nicht dem
erwarteten. Nach langem hin und her hatte ich festgestellt, dass es an
der festen Zuweisung von readWrite gleich '1' liegt. Verwende ich für
readWrite ein Signal, welches einfach in einem getakteten Prozess fest
auf '1' gesetzt wird, funktioniert wieder alles. Die Abfrage innerhalb
der Schnittstelle ist allerdings ebenfalls getaktet und fragt bei enable
lediglich ab, ob readWrite gleich '0' oder '1' ist.
Das komische ist nun weiterhin, dass das Verhalten bei fester Zuweisung
auf '1' weder dem Verhalten für '1' noch dem Verhalten für '0'
entspricht, sondern eben sehr seltsam ist...
Die "komischen" Ergebnisse der Timing-Simulation wurden durch Messung
mit dem Oszi bestätigt. Es ist also kein Simulationsfehler, sondern
entspricht der Realität.
Ich weiß wie gesagt was der Fehler ist und wie er behoben werden kann:
Sobald readWrite entweder asynchron oder in der port map fest auf '1'
gesetzt wird, ist das Verhalten nicht wie erwartet. Wird ein getakteter
Prozess verwendet, in welchem readWrite fest auf '1' gesetzt wird, geht
alles.
Jetzt würde gerne verstehen warum das so ist bzw. woran das liegen kann.
Vielen Dank im Voraus für eure Hilfe und Erklärungen!
Dominik schrieb:> Sobald readWrite entweder asynchron oder in der port map fest auf '1'> gesetzt wird, ist das Verhalten nicht wie erwartet.
Was passiert, wen dieses signal ein Portpin ist, der auf einen
definieren Pegel gelegt wird?
> Verwende ich für readWrite ein Signal, welches einfach in einem> getakteten Prozess fest auf '1' gesetzt wird, funktioniert wieder alles.
Dieses Verhalten deutet auf ein Timingproblem durch wegoptimierte und
gekürzte Pfade hin. Ausgangspunkt dieses Verhaltens sind gern
irgendwelche asynchronen Pfade oder Resets im System.
Man müsste für eine brauchbare Analyse also mal die fragliche
Beschreibung sehen...
> Jetzt würde gerne verstehen warum das so ist bzw. woran das liegen kann.
Es könnte auch ein Fehler oder eine Eigenheit der bisher unbekannten
Toolchain beim bisher unbekannten FPGA sein.
Vielen Dank für die schnelle Antwort.
Es handelt sich um einen Xilinx Spartan-6 LX9 (auf dem Avnet MicroBoard
Entwicklungsboard).
Verwendet habe ich ISE Version 14.7. Simuliert wurde mit ISim.
Leider bin ich mir nicht ganz sicher, was genau mit "Portpin" gemeint
ist. Eine "abgespeckte" Version meines Codes (zeigt gleiches Verhalten)
ist hier:
1
useIEEE.STD_LOGIC_1164.ALL;
2
3
entitytmpis
4
Generic(CLKIN_PERIOD:real:=10.0);
5
Port(enable:instd_logic;
6
clk:instd_logic;
7
rst:instd_logic;
8
out1:outstd_logic;
9
out2:outstd_logic;
10
out3:outstd_logic;
11
out4:outstd_logic
12
);
13
endtmp;
14
15
architectureBehavioraloftmpis
16
17
18
signalselectController:integerrange1to3;
19
signalwriteAllRegToDriver:std_logic;
20
signalwriteOneRegToDriver:std_logic;
21
signalregAddress:std_logic_vector(2downto0);
22
signaldataToIntReg:std_logic_vector(11downto0);
23
24
25
componentSPI_writeReg
26
Port(clk:inSTD_LOGIC;
27
enable:inSTD_LOGIC;
28
rst:inSTD_LOGIC;
29
selectController:inintegerrange1to3;
30
writeAllRegToDriver:instd_logic;
31
writeOneRegToDriver:instd_logic;
32
regAddress:instd_logic_vector(2downto0);
33
dataToIntReg:instd_logic_vector(11downto0);
34
SCS:outSTD_LOGIC;
35
SCLK:outSTD_LOGIC;
36
DATAtoDRIVER:outSTD_LOGIC;
37
writeDone:outstd_logic
38
);
39
endcomponent;
40
41
begin
42
43
SPI_writeReg_inst:SPI_writeReg
44
portmap(clk=>clk,
45
enable=>enable,
46
rst=>rst,
47
selectController=>selectController,
48
writeAllRegToDriver=>writeAllRegToDriver,
49
writeOneRegToDriver=>writeOneRegToDriver,
50
regAddress=>regAddress,
51
dataToIntReg=>dataToIntReg,
52
SCS=>out1,
53
SCLK=>out2,
54
DATAtoDRIVER=>out3,
55
writeDone=>out4
56
);
57
58
59
--process
60
--begin
61
--wait until rising_edge(clk);
62
dataToIntReg<="000000000000";
63
regAddress<="000";
64
writeAllRegToDriver<='1';
65
writeOneRegToDriver<='0';
66
selectController<=1;
67
--end process;
68
69
endBehavioral;
Wird process... einkommentiert, funktioniert alles. Ist der Teil
auskommentierten, funktioniert es nicht.
Innerhalb von SPI_writeReg wird wie folgt abgefragt:
1
process
2
begin
3
waituntilrising_edge(clk);
4
5
...
6
ifenable='1'then
7
...
8
ifwriteAllRegToDriver='1'then
9
...
Der Tipp mit den wegoptimierten Pfaden hört sich sehr plausibel an. Kann
ich das zum testen irgendwie ausstellen?
Nochmals vielen Dank für die Hilfe und die Bemühungen!!
Ich kann auch gerne den ganzen Code zur Verfügung stellen. Aber ich
fürchte da werden einige die Hände über dem Kopf zusammenschlagen was da
so wie gemacht wurde...
Dein Eingang "enable" ist nicht synchronisiert, wenn ich das richtig
gesehen habe.
Eventuell liegt das Problem daran.
Das Timing ändert sich, abhängig davon, ob du die anderen Signale
synchronisierst oder nicht.
Und dann tritt das letztendlich vom asynchronen enable verursachte
Problem auf, oder auch nicht.
Hallo Schlumpf,
vielen Dank für die Antwort. Ich habe das etwas unschön hingeschrieben.
Das enable ist in dem gleichen Prozess und nach dem wait until. Es
sollte also alles synchronisiert sein.
Dominik schrieb:> Hallo Schlumpf,> vielen Dank für die Antwort. Ich habe das etwas unschön hingeschrieben.> Das enable ist in dem gleichen Prozess und nach dem wait until. Es> sollte also alles synchronisiert sein.
Ich vermute mal, er wollte dich auf das Problem mit der Metastabilität
hinweisen. Enable wird jetzt nicht aus einer anderen Taktdomäne
gesteuert? Es wäre mir aber neu wenn die Tools deswegen Dinge
wegoptimieren.
Was noch ein Problem sein könnte: gibt es eventuell noch eine andere
Stelle wo du das Signal beschreibst? Oder wird es nie gesetzt? Du
solltest dir mal alle Warnungen anschauen die ISE so ausspuckt.
Ist wahrscheinlich nicht der Grund, aber "wait until" in einem
synthetiserten process ist zumindest etwas ungewöhnlich. Es ist
syntaxmäßig nicht verboten, aber viele Synthesetools wollen hier lieber
ein "if rising_edge(clk) then .. else" sehen. Das steht zumindest in den
Guidelines von Xilinx überall so drin, und nach meiner Erfahrung sind
manche Tools etwas zickig, wenn man es anders macht
Mike schrieb:> Du solltest dir mal alle Warnungen anschauen die ISE so ausspuckt.
Dabei im Besonderen auf "Latches" oder "Combinatorial Loops" achten.
Wieviele Takte hast du in dem Design (alles mit 'event oder
rising_edge() oder falling_edge() ist ein Takt)?
Vancouver schrieb:> Ist wahrscheinlich nicht der Grund, aber "wait until" in einem> synthetiserten process ist zumindest etwas ungewöhnlich.
Jeder Synthesizer kann seit gut 10 Jahren das "wait until" sinnvoll
interpretieren.
> Es ist syntaxmäßig nicht verboten, aber viele Synthesetools wollen hier> lieber ein "if rising_edge(clk) then .. else" sehen.
Ich weiß, es ist Pfennigfuchserei, aber kein Synthesetools möchte nach
einem "if rising_edge() then" noch ein "else" sehen... ;-)
Lothar M. schrieb:> Ich weiß, es ist Pfennigfuchserei, aber kein Synthesetools möchte nach> einem "if rising_edge() then" noch ein "else" sehen... ;-)
hmm, Stichwort 'asynchroner Set/Reset'?
Ich weiss, klassisch schreibt man das andersrum, aber so geht's auch...
Dominik schrieb:> Das enable ist in dem gleichen Prozess und nach dem wait until. Es> sollte also alles synchronisiert sein.
Das verstehe ich nicht ganz.. du verbindest in deinem Design den
enable-Port direkt mit dem enable-Eingang des SPI. Damit ist es nicht
synchronisiert.
Wenn du einfach nur eine Zeile wie z.B.
1
enable_sync<=enable;
vergessen hast, dann fehlt da aber auch noch die Signaldeklaration des
synchroniserten Signals und auf das SPI ist dann auch das falsche Signal
gemapped.
Bist du sicher, dass wir vom Gleichen reden?
Hallo,
nochmals vielen Dank für die Kommentare und Tipps.
Zu den einzelnen Punkten: Es wird nur ein Taktsignal verwendet. Enable
wird auch nur an einer Stelle gesetzt. Um sicher zu gehen, habe ich auch
alle wait until durch if rising_edge (und die Sensitivity list) ersetzt,
leider ohne Erfolg.
Sämtliche ausgegebenen Warnungen beziehen sich auf "Signal hat
konstanten Wert / Signal ist gleich einem anderen Signal. Das FF/Latch
wird im Optimierungsprozess entfernt." oder "Signal ist nicht
verbunden". Da ich nur einen Teil der beschriebenen Hardware verwende,
sind diese Warnungen natürlich logisch und nachvollziehbar.
Wenn ich mir das RTL Schematic anschaue, dann wird bei getaktetem
writeAllRegToDriver <= '1'; das Signal direkt mit VCC verbunden. Ist
writeAllRegToDriver <= '1'; asynchron, dann ist die Leitung einfach
nicht verbunden.
Daher würde ich gerne nochmal versuchen das "Optimieren" zu
unterdrücken. Gibt es da eine Einstellung um ISE zu sagen, dass nichts
entfernt werden soll?
Falls jemand Zeit und Lust hat sich das einmal anzuschauen, ich habe den
Code jetzt doch angehängt. Die Top-Level Datei ist tmp.vhd
Hallo Schlumpf,
du hast recht, das enable-Signal wird hier nicht einsynchronisiert. Das
hatte ich bei der "abgespeckten" Version vergessen. Allerdings ändert
das leider auch nichts. Bei der "realen" Anwendung hatte ich noch eine
entsprechende Stufe davor geschaltet.
Wobei ich dachte, dass bei einer taktsynchronen Abfrage des
enable-Signals und Einhaltung aller Timing-Constraints zumindest in der
Simulation mit einem sauberen Übergang der Signale ohne Spikes,
Rauschen, Jitter usw. das an dieser Stelle egal sein sollte (auch wenn
es dann in der Realität vielleicht nicht zuverlässig laufen würde...).
Bei dem obigen Code hatte ich die Test-Bench vergessen. Diese habe ich
hier noch angehängt.
Dominik schrieb:> Wobei ich dachte, dass bei einer taktsynchronen Abfrage des> enable-Signals und Einhaltung aller Timing-Constraints zumindest in der> Simulation mit einem sauberen Übergang der Signale ohne Spikes,> Rauschen, Jitter usw. das an dieser Stelle egal sein sollte (auch wenn> es dann in der Realität vielleicht nicht zuverlässig laufen würde...).
Das enable-Signal wirkt in deinem Desing auf viele Register.
Diese Register sind synchron zueinander (alle haben den gleichen Takt)
Aber der logische Pfad vom enable-Pin bis zum Dateneinang der Register
hat "irgendeine" Länge.
Kommt nun das enable-Signal zu einem beliebigen Zeitpunkt, so kann es
passieren, dass die Information bei einem Teil der Register noch
rechtzeitig vor der Taktflanke da ist und somit übernommen wird.
Bei einem anderen Teil der Register kann es passieren, dass sie
Metastabil werden, wenn tsu unterschritten wird. Und bei einem weiteren
Teil der Register kann es passieren, dass die Information erst nach der
Taktflanke kommt und somit erst mit der darauffolgenden Flanke
übernommen wird.
Wie du siehst, kann alles mögliche in deinem Design passieren, wenn du
enable nicht synchronisierst.
berndl schrieb:> hmm, Stichwort 'asynchroner Set/Reset'?> Ich weiss, klassisch schreibt man das andersrum
Das ist aber eigentlich prinzipiell falsch und könnte sogar bei einer
Verhaltenssimulation zu Race-Conditions führen (Stichwort Timestep).
Denn diese Beschreibung müsste dann "während" des steigenden Taktes
(also für die theoretische Dauer 0) ein high am Ausgang zeigen:
1
ifrising_edge(clk)then
2
ausgang<='1';
3
else
4
ausgang<='0';
5
endif;
Und diese Schaltung hier muss das bei aktivem Reset(!) tun:
1
ifrising_edge(clk)then
2
ausgang<='1';
3
elsifreset='1'then
4
ausgang<='0';
5
endif;
> aber so geht's auch...
Das hast du aber selber rausgefunden... ;-)
Das mit dem einsynchronisieren sehe ich ein und ist natürlich völlig
richtig. Ich habe es gleich nochmal probiert. Leider hat es mein
aktuelles Problem auch nicht gelöst...
Dominik schrieb:> Leider hat es mein> aktuelles Problem auch nicht gelöst...
writeAllRegToDriver ist bei dir ja nur ein internes signal. Wenn du es
fest auf 1 legst, dann hat der Synthesizer keinen Grund, ein solches
Signal zu erzeugen. Sondern er kann die Abfragen des Signals schon zur
Synthesezeit auswerten. Also z.B. in deiner Abfrage
if writeAllRegToDriver = '1' then
Anweisungsblock1
elsif ...
Anweisungsblock2
wird er gleich nur den ersten Block synthetisieren. Der elsif-Teil wird
ignoriert. Und damit braucht man auch kein Signal writeAllRegToDriver
mehr, da keine if-Abfrage mehr übrig ist, die das Signal auswerten
würde.
Wenn alles richtig läuft, brauchst du in der Simu also kein Signal
writeAllRegToDriver zu sehen (das zu nichts mehr genutzt wird). Du
solltest aber sehen, dass der erste Block deiner If-Abfrage
synthetisiert wurde (und der zweite Block nicht).
Wenn du ein definiertes Signal writeAllRegToDriver sehen willst, dann
kannst du das z.B. auf der Top-Ebene eine Kopie davon als Ausgang
treiben - dann solltest du dort die erwartete 1 sehen können.
Wenn es mit Initialwerten aus einem getakteten Prozess funktioniert und
mit direkt angelegten nicht, passiert möglicherweise in dem allerersten
Takt irgendwas Unerwartetes. Die getakteten Signale (zumindest die
ungleich 0) sind ja erst im zweiten Takt an deiner weiteren Logik, die
ungetakteten sofort. Im allerersten Systemtakt läuft das System da
vielleicht schon in einen anderen Zustand.
Außerdem solltest du in deiner TB deine Signale nicht genau zur
Taktflanke ändern, das hat schon gelegentlich zu Verwirrung mit
Simulatordeltazyklen geführt.
Vielen Dank nochmal für die sehr interessanten und hilfreichen Tipps.
Es stimmt auf jeden Fall, dass sehr viel wegoptimiert wird. Und es ist
natürlich ein guter Punkt, dass die if-Abfrage nach writeAllRegToDriver
nicht umgesetzt wird, wenn diese immer gleich '1' ist. Wobei dieser Teil
ja auch noch korrekt ausgeführt wird (zumindest scheinbar). Trotzdem
natürlich ein logischer Punkt, den ich nicht richtig auf dem Schirm
hatte.
Auch den Tipp mit der TB und dem Zeitpunkt der Änderung der Signale
werde ich in Zukunft auf jeden Fall berücksichtigen. Danke!
Den Reset hatte ich auch synchronisiert. Da hatte ich mich auf der Seite
von Lothar Miller schlau gemacht und viel dazu gelernt.
An den Reset als Übeltäter hatte ich auch schon gedacht. Jetzt hatte ich
mich gewundert, warum die Adresse der Register regAddress_int zum Teil
wegoptimiert wird, obwohl die sich eigentlich aendert. Dieses Signal
hatte ich bei einem Reset nicht berücksichtigt, da ich dachte es wird
sowieso sobald es verwendet wird neu gesetzt. Deshalb habe ich
regAddress_int einfach ebenfalls bei einem Reset nochmals auf "000"
gesetzt - und siehe da, es funktioniert. Ich muss leider zugeben, dass
ich es immer noch nicht ganz verstehe warum. Ich dachte der
Initialisierungswert eines std_logic_vectors sei überall 0 und das
Signal wird ja bei einem enable überschrieben. Aber es liegt
offensichtlich an der "Optimierung" und dem Reset...
Dominik schrieb:> Jetzt hatte ich> mich gewundert, warum die Adresse der Register regAddress_int zum Teil> wegoptimiert wird, obwohl die sich eigentlich aendert.
sie ändert sich imho nicht. Im Block
if writeAllRegToDriver = '1' then
weist du der regAddress_int eine Null zu.
Und die Case-Struktur, in der du die Adresse hochzählst, erreichst du
nicht, weil die Bedingung
SPI_waitBetweenWriteCycles(SPI_waitBetweenWriteCycles'left to
....)="10"
nie erfüllt ist (du weist SPI_waitBetweenWriteCycles nie etwas anderes
als 0 zu).
Ich habe jetzt das "Optimieren" von regAddress_int mit dem Attribut KEEP
unterdrückt und jetzt funktioniert alles!
Es liegt also tatsächlich daran, dass durch die Optimierung ein
eigentlich benötigter Teil aus der Schaltung entfernt wird und dies dann
zu einem Fehler führt.
Ich möchte mich hiermit noch einmal ganz herzlich für die vielen Tipps
und hilfreichen Kommentare bedanken!!!
Dominik schrieb:> dachte der Initialisierungswert eines std_logic_vectors sei überall 0
Wenn kein Initialwert angegeben wird, kann sogar ein Register wie das
hier wegoptimiert werden:
1
signalflag:std_logic;
2
:
3
process(clk)begin
4
ifrising_edge(clk)then-- Ganz logisch: ein Takt
5
flag<='1';-- ergibt ein Flipflop
6
endif;
7
endprocess;
Denn der Synthesizer sieht, dass der Wert von flag eigentlich egal ist
und dann auf '1' gesetzt wird. Also setzt er ihn sofort auf '1' gesetzt
und kann entsprechende Codeteile wegoptimieren. Und jetzt kommt der
überraschende Trick: das passiert sogar, wenn ein enable im Spiel ist:
1
signalflag:std_logic;
2
:
3
process(clk)begin
4
ifrising_edge(clk)then-- Ganz logisch: ein Takt
5
ifenable='1'then-- mit enable
6
flag<='1';-- ergibt ein Flipflop
7
endif;
8
endif;
9
endprocess;
Erst mit Initialisierung wird tatsächlich ein Flipflop daraus...