Ich bin ein Neueinsteiger und habe ein paar Fragen zu meinem vhdl Code.
Ich möchte gerne über eine CommandList Werte aus einem Speicher lesen.
Im JobListSeparator wird das Commando getrennt zwischen Adresse und
Wert. Das Funktioniert auch soweit. Im nächsten Process sollte
eigentlich nach der Adresse der Wert in Value geschrieben werden. Dieser
Werte werden in weiteren Processe verwendet. Durch das DoneCommandIntern
sollte dann die Adresse im einen Wert erhöht werden. Bei der Simulation
bekomme ich immer nur den ersten Wert. Woran kann dies liegen?
Warum die vielen Variablen?
Die unteren beiden Prozesse kannst du bei Benutzung der sowieso
definierten Signale ohne jegliche Variable und ohne jegliche
funktionelle Änderung so schreiben:
Und warum eigentlich die vielen Prozesse?
Weil die zudem allesamt getaktet sind, hast du jede Menge Latency an der
Backe. Ich bin mir nicht so ganz sicher, dass das so gewollt ist.
Was du mit den vielen Takten in den vielen Prozessen geschafft hast,
ist, dass von einem gültigen Eintrag in die CommandList bis zum
Hochzählen der Adresse 3 Takte vergehen müssen.
Denn eigentlich braucht dieser "Job" Prozess keinen Takt, weil
CommandAdress und CommandValue ja bereits im Prozess JobListSeparator
gespeichert wurden. Und vermutlich braucht auch dieser Prozess keinen
Takt, weil die CommandList auch schon in Registern stabil gespeichert
ist.
Dan schrieb:> Bei der Simulation bekomme ich immer nur den ersten Wert.
Zeig doch mal.
Wenn ich den Code wie Sie es vorgegeben haben und Ihre Vorschläge
eingepflegt habe, simuliere, dann passiert nichts. Ich habe folgenden
Code simuliert:
Ich habe ein Bild der Simulation angehängt. Was mache ich falsch :(
Den Process MemAdressP habe ich in einen extra Process gemacht, da ich
später über einen Reset die MemAdress zurücksetzten will. Wahrscheinlich
geht es auch einfacher in einem Process :)
Dan schrieb:> dann passiert nichts
Was macht MemAdress?
Nach diesem Code wird das bestenfalls von 0 auf 1 wechseln und dann dort
bleiben, denn das hier sieht zwar aus wie ein Zähler:
MemAdress <= std_logic_vector(to_unsigned(sMemAdress+1,
MemAdress'length));
Es ist aber keiner, weil sMemAdress immer 0 ist und nie hochgezählt
wird...
Dan schrieb:> Ich poste mal noch mein Simulationsprogramm, denn ich vermute, dass dort> der Fehler liegt ;)
Das kann man aber doch im Simulator leicht feststellen. Genau der ist
doch zum Debuggen da.
> Ich poste mal noch mein Simulationsprogramm
Häng einfach beide komplette VHDL Dateien an. Es ist echt unnötig viel
Arbeit, die zum Ausprobieren zu vervollständigen...
EDIT: nochmal drüber geschaut, das sieht wild aus:
Das ist ein Latch (mit einer Tendenz zur kombinatorischen Schleife...).
Das wolltest du ganz sicher auch nicht. Denn das funktioniert im echten
Leben nicht zuverlässig.
Du könntest hier übrigens das Prozess-Gemapel auch weglassen und sowas
schreiben:
Ok ich habe die 2 vhdl Dateien angehängt. Woran erkennt man, dass es
sich dabei um einen Latch handelt? Das ist vielleicht eine Anfänger
Frage, aber nur so lernt man dazu :)
Dan schrieb:> Woran erkennt man, dass es sich dabei um einen Latch handelt?
Es speichert und hat keinen Takt.
> Ok ich habe die 2 vhdl Dateien angehängt.
Aber die haben nichts mit dem Bild zu tun, das du nicht angehängt hast.
Denn das ist ein Fehler:
Danke für die Verbesserung jetzt funktioniert es auch bei meiner
Simulation :)… Ich verstehe nur eins nicht, wieso jetzt CommandAdress
und CommandValue in keinem Process stehen und nicht getaktet sind. Ich
dachte, dass dann ein Latch auftreten kann oder bin ich falsch. Bzw.
wann werden dann diese dann immer aktualisiert? Denn wenn ich
rising_edge verwende wird es immer dann aktualisiert wenn eine steigende
Flanke kommt. Aber das haben wir hier ja nicht.
Dan schrieb:> Ich verstehe nur eins nicht, wieso jetzt CommandAdress und CommandValue> in keinem Process stehen und nicht getaktet sind. Ich dachte, dass dann> ein Latch auftreten kann oder bin ich falsch.
Da ist kein Latch, weil das nur eine Zuweisung ist. Es wird nirgends was
gespeichert, sondern einfach nur zugewiesen.
Ich könnte das auch so schreiben, dann gäbe es die CommandAdress und
CommandValue gar nicht merh:
Dan schrieb:> Bzw. wann werden dann diese dann immer aktualisiert?
Immer genau dann, wenn sich die CommandList ändert.
Der Witz an der Sache ist aber, dass die Testbench für die CommandList
per "wait for 10 ns;" eine Art "Pseudotakt" erzeugt: obwohl kein Takt
bei der CommandList beteiligt ist, sieht es aus, als ob das irgendwie
taktsynchron sei...
Korrekterweise müsste man das etwa so machen:
1
clk<=notclkafter10ns;-- 50 MHz
2
3
tb:PROCESSBEGIN
4
waitforrising_edge(clk);
5
case
6
MemAdressis
7
when"000000"=>CommandList<=x"0001000A";
8
when"000001"=>CommandList<=x"00020064";
9
when"000010"=>CommandList<=x"000300FF";
10
when"000011"=>CommandList<=x"000D01F4";
11
when"000100"=>CommandList<=x"000C0003";
12
whenothers=>CommandList<=x"00000000";
13
endcase;
Denn dann gilt der Takt für die CommandList und für das gesamte Modul
JobList. Das ist dann schönes synchrones Design.
Dan schrieb:> Ich habe nun eine letzte Frage. Wann wird ein neuer Process gemacht?
Die Frage muss eigentlich lauten: was macht ein Prozess?
Hier eine vereinfachte Kurzversion:
Ein Prozess ist erstmal eine sequentielle Hintereinanderreihung von
Befehlen. Der Prozess wird vom Simulator neu berechnet, wenn sich eines
der Signale in der Sensitivliste ändert (der Synthesizer schert sich
dagegen nicht um die Sensitivliste). Ist die Sensitivliste nicht
vollständig, passt die Simulation nicht zur Realität. Das passiert gern
mal bei der Verwendung von Variablen, wie im
Beitrag "Variable vs Signal" verdeutlicht.
Das Neckische und Schöne am Prozess im Zusammenspiel mit Signalen ist,
dass die "letzte" Wertzuweisung an ein Signal "gewinnt". Man kann also
einem Signal im Verlauf eines Prozesses beliebige Werte zuweisen. Der
jeweils zuletzt zugewiesene Wert wird aber nur "zwischengespeichert",
das Signal änert seinen ursprünglichen Wert nicht.
Prozesse, die einen Takt abfragen, erzeugen Flipflops. Prozesse, die
Zustände abfragen im Idealfall nur Logik, wenn man nicht aufpasst aber
auch Latches oder gar kombinatorische Schleifen.
Die Allgemeinform eines Prozesses ist der Prozess mit einem oder
mehreren "wait" im Code:
1
JobListSeparator:process
2
begin
3
waitonCommandList;-- auf irgendeine Änderung in der CommandList warten
4
CommandAdress<=CommandList(31downto16);
5
CommandValue<=CommandList(15downto0);
6
endprocessJobListSeparator;
Ist nur 1 einziges "wait" im code, kann der Prozess auch mit
Sensitivliste geschrieben werden:
1
JobListSeparator:process(CommandList)-- auf irgendeine Änderung in der CommandList warten
2
begin
3
CommandAdress<=CommandList(31downto16);
4
CommandValue<=CommandList(15downto0);
5
endprocessJobListSeparator;
Und die finale Abkürzung ist, wenn man bei simplen Zuweisungen auf den
Prozess verzichtet und eine nebenläufige Anweisung schreibt:
1
CommandAdress<=CommandList(31downto16);
2
CommandValue<=CommandList(15downto0);
Hier wird das Ergebnis vom Simulator naheliegenderweise neu berechnet,
wenn sich die CommandList ändert.
> Wann wird ein neuer Process gemacht?
Eigentlich recht einfach: dann, wenn man einen braucht... ;-)
Man muss aber nicht jede mickrige Berechnung oder Zuweisung in einen
Prozess packen.
Viel machen z.B. für irgendwelche "Takterzeugungen" eigene Prozesse. Ich
packe das auch mal einfach in den selben Prozess:
http://www.lothar-miller.de/s9y/categories/45-SPI-Masterhttp://www.lothar-miller.de/s9y/categories/42-RS232
Gustl B. schrieb:> Das ist auch nur Kombinatorik oder Latches, aber kann man auch> getaktete> Sachen ohne Process machen? Ich meine damit taktflankengesteuerte Dinge.
1
signald:unsigned(7downto0):=(others=>'0');
2
3
...
4
5
d<=d+1whenrising_edge(clk);
ist valides VHDL. Ob das dein Synthesizer akzeptiert und Sinn macht
steht auf einem anderen Blatt und ist ausserhalb des VHDL Scopes.
Gustl B. schrieb:> Fehlt da nicht ein else?
Bei der nebenläufigen getaketen Anweisung? Nein, das passt.
Denn das fehlende else zeigt ja deutlich, dass da etwas gespeichert
wird:
Lothar M. schrieb:> -- als Schieberegister zum Eintakten von Signalen> sr <= sr(sr'left-1 downto 0) & in;
Da fehlt noch was, Lothar. So ist das erst mal - zugegeben, eine
ziemlich effektive - kombinatorische Schleife ;).
Ich habe nochmal ein bisschen an meinem Code herumgespielt und habe
nochmals eine Frage. Ich möchte jetzt gerne noch über ein StartSignal
ein Job starten z.B. Wenn das Startsignal kommt, soll das sMemAdress um
eins erhöhen und dann z.B. über die CommandList sOn einschalten und das
Signal eine Zeit anlassen (when x"0004" … ). Danach soll über
Commandlist SOff wieder ausgeschaltet werden. Die Simulation
funktioniert, leider sieht es aber bei der Hardware nicht so aus :(.
Woran kann dies liegen ;)
Dan schrieb:> when x"0004" => sTestSignal <= '1'; if (counter <> (to_integer(unsigned(CommandList(15 downto 0))))*50)
Was soll die Synthese daraus machen?
Eine Warteschleife, die unabhängig von ihrer Dauer immer in einem Takt
fertig wird?
Dann sieht man, dass der counter mit jedem Takt hochzählt, solange sein
Wert klein genug ist.
Dan schrieb:> Die Simulation funktioniert, leider sieht es aber bei der Hardware nicht> so aus :(. Woran kann dies liegen ;)
Eigentlich nur an der Hardware? Wie sieht die aus? Woher kommt da der
Takt? Woher die Kommandos?
Lothar M. schrieb:> Bei der nebenläufigen getaketen Anweisung? Nein, das passt.
Danke! Dann braucht man tatsächlich keinen Process, aber es ist schon
bequem weil es Schreibarbeit spart.
Und eine Nebenfrage habe ich noch (ist aber etwas OT):
Wenn ich einen Takt habe, macht es einen unterschied für die Synthese
wenn ich alles in einen getakteten Process packe oder das auf mehrere
verteile?
Ich könnte mir vorstellen, dass die Tool versuchen das was in einem
Process steht irgendwie näher zusammen im FPGA zu platzieren.
Es gibt dann auch manchmal Warnungen wegen einem hohen Fanout eines
Taktes, da könnte ja die Synthese das auch aufteilen und dann
selbständig BUFG und BUFR dazwischensetzen.
Ich habe das Intel® Cyclone 10 LP Evaluation Kit Board. Der Takt kommt
direkt von einem Quarz. Und die Adresse wird an den Nios OnChipMemory
gesendet und dann den Wert CommandList gelesen.
Dan schrieb:> Und die Adresse wird an den Nios OnChipMemory gesendet und dann den> Wert CommandList gelesen.
Da scheint wohl in der realen Hardware noch was anders abzulaufen als in
der Testbench. Wenn die Testbench nicht die Realität abbildet, passiert
sowas.
Gustl B. schrieb:> Ich könnte mir vorstellen, dass die Tool versuchen das was in einem> Process steht irgendwie näher zusammen im FPGA zu platzieren.
Dort wird soweit möglich alles zusammengefasst und Unnötiges
weggelassen.
Dieses Design hier braucht deshalb trotz der 3 Counter mit je 8 Bits im
FPGA nur noch 7 Flipflops:
Ich bin immer noch am überlegen, warum die Hardware anders aussieht als
meine Simulation. Ich habe jetzt ein Bild der Simulation angehängt. Was
mir nicht ganz einleuchtet ist, wieso die MemAdress und die CommandList
um einen Takt verschoben ist? Die Umsetzung sieht genau gleich aus wie
am 24.01 um 11.43 Uhr. Es ist nur so dass das sOff nicht mehr gibt und
alles beides auf sOn ein bzw. ausgeschaltet wird. Was ich erreichen
möchte ist, dass ich durch Änderung der Memory Adresse das sOn ein bzw.
ausschalten kann. Im folgenden noch meine Umsetzung der Testbench:
Zweimal "clk <= not clk after 10 ns;" ? Da müßte der Simulator
eigentlich meckern. Zumindest wenn clk vom Typ std_ulogic ist.
Davon abgesehen: Vor dem Takt ist nach dem Takt. Du änderst die
Eingangssignale Deines DUT genau mit der Flanke. Das DUT sieht aber noch
die 'alten' Eingangssignale. Für das bessere Verständnis sollte man die
Eingangssignale kurz vor der steigenden Flanke ändern.
Duke
Dan schrieb:> Was mir nicht ganz einleuchtet ist, wieso die MemAdress und die> CommandList um einen Takt verschoben ist?
Weil wegen des "wait until rising_edge(clk)" nach einer Änderung der
MemAdress erst beim nächsten Takt die CommandList geändert wird.
Oder: herzlichen Glückwunsch, du hast das entdeckt, was sich Latency
nennt. Ich hatte übrigens den Begriff hier im Thread schon ganz am
Anfang mal einfgeführt...
Sieh mal, was passiert, wenn du das machst:
1
tb:PROCESS(MemAdress)BEGIN
2
case
3
MemAdressis
4
when"000000"=>CommandList<=x"00000002";
5
when"000001"=>CommandList<=x"00000003";
6
whenothers=>CommandList<=x"FFFFFFFF";
7
8
endcase;
Oder wenn du den Prozess gleich weglässt und ein Array nimmst (was ja
einem ROM an ehesten entspricht):
Duke Scarring schrieb:> Für das bessere Verständnis sollte man die Eingangssignale kurz vor der> steigenden Flanke ändern.
Man muss eigentlich nur kapieren, dass sich diese Werte allesamt wegen
eines Taktes ändern. Also ändern sie sich im Grunde deshalb immmer
nach dem Takt. Auch wenn das in der Verhaltenssimulation (die ja kein
Pseudotiming mit Pseudodelays simulieren sollte) so aussieht, als ob das
gleichzeitig wäre...
Meine Simulation sieht jetzt gut aus :). Ich habe es mit dem Array
umgesetzt und funktioniert. Ich habe jetzt nur noch das Problem, dass
wenn ich die auf meine Hardware übertrage, und ich den Speicher mit den
gleichen Werten wie in der Simulation beschreibe, dann ist das SOn
Signal immer high. Ich verwende den Speicher On Memory chip und dieser
wird über eine Serielle Schnittstelle beschrieben. Dies funktioniert
auch, da ich es mit dem Tool "In Memory content" überprüft habe. Da der
Speicher ein Read latency von 1 hat, wird dies mit einer Frequenz von
100 Mhz betrieben. Der Rest der Anwendung wird mit 50 MHz betrieben.
Liegt hierbei der Fehler, dass ich einmal mit 100 MHz und den Rest mit
50 MHz betreibe?