Hi,
ich bin VHDL Anfänger (sonst nur embedded C++) und wollte ein
"einfaches" Hello World VHDL erzeugen: Button press -> sendet Hello
World über Uart.
Leider habe ich nichts zufriedenstellendes gefunden und daher etwas
eigenes versucht (voller source und testbench im Anhang):
1
entityuart_transmitteris
2
generic(
3
CLK_FREQUENCY:positive:=100e6;
4
BAUD_RATE:positive:=9600;
5
FIFO_LENGTH:positive:=16;
6
BYTE_WIDTH:positive:=1
7
);
8
Port(
9
tx_enable_i:instd_logic;
10
data_i:instd_logic_vector(BYTE_WIDTH*8-1downto0);
11
latch_input_i:instd_logic;
12
clk_i:instd_logic;
13
busy_o:outstd_logic;
14
fifo_full_o:outstd_logic;
15
tx_o:outstd_logic
16
);
17
enduart_transmitter;
-> Es ist ein UART Transmitter mit eingebauter FIFO und konfigurierbar
breitem Zugriff geworden. Oftmals möchte man Daten fester Länge senden
und dies wird hiermit vereinfacht.
-> Ich habe in einem Hardwaretest latch_input auf ein Buttonsignal
gelegt und es kam immer ein voller "Hello World" String heraus (je nach
Baud dutzende bis Tausende). Soweit so gut.
Ich hoffe Ihr könnt Feedback geben und mir weiterhelfen:
- Braucht man zwingend ein Resetsignal? Im Internet sieht man das fast
überall, aber die Defaultzuweisung für Signale reicht vielleicht aus?
- fifo_full_o wird ein Takt zu spät auf High gesetzt, obwohl ich die
Variable writeAvailable im Prozess nochmal update. Lösung?
- Wie warte ich in der Testbench, sodass latch_input genau für einen
Takt auf High steht? Derzeit mache ich es mit hartem "wait for 10ns".
- Was sagt Ihr allgemein zum Stil? Z.B hatte ich irgendwo gesehen, dass
man Portsignale immer mit _i und _o Suffixes ausstattet.
- Ressourcenverbrauch? Ich hatte eine optimierte UART von Xilinx
gesehen, aber die sah vom Interface her kacke aus. Optimierung ist her
bestimmt noch drin?
- Statt integers sollte man vermutlich unsigned benutzen..?
- Wozu das Zwischensignal baud_output? Das habe ich ohne Verstand aus
einem anderem UART Design kopiert.
- Bekommt man die UART Baudrate noch genauer hin? Rundung müsste
irgendwie gehen und die Division durch 2 müsste man durch DDR
wegbekommen?
Gerne auch weiteres Feedback.
vorerst ein Tipp, um den Code sauberer zu schreiben:
schreibe richtige Sensitivity-Listen. Ein getakteter Prozess hat nur den
clock und höchstens den reset drinstehen.
Diese Variablenbenennung ist wahrlich sadistisch ;)
Zu deinen Fragen (nur ein kurzer Auszug):
- im FPGA muss nicht immer an ein Reset gedacht werden, ein
Initialisieren der Signal geht auch. Wenn du jetzt nachfragst.. lieber
nicht.. dann hat dein Thread noch 50 weitere Kommentare dazu..
- Klar kann man integers nutzen. Besonders in der Testbench nützlich.
- den Rest habe ich mir noch nicht so genau angeschaut. Ich empfehle
aber weniger bis gar keine Variablen am Anfang zu nehmen. Auch sind
Prozeduren in der Regel nicht notwendig/hilfreich. Ich schreibe schon
sehr lange und sehr viel, aber Prozeduren nutze ich nur in der
Testbench.
Wo hast du diesen "Zwei-Takte-pro-Prozess" Stil gelernt? ;-)
So werden denn auch keine Takte in einem FPGA gemacht:
1
-- Generates baud clock from main clk
2
:
3
baud_output<=notbaud_output;
4
:
5
baud_clk<=baud_output;
6
:
7
if(rising_edge(baud_clk))then
Das quittiert der Synthesizer mit Kommentaren wie "no good design
practice". Um Takte zu erzeugen, gibt es Taktmanager und -buffer.
Das, was du hier eigentlich brauchst, sind simple Clock-Enables und 1
Takt im gesamten Design:
Warum der unnötige Umweg über ein weiters Signal. Glaub mir: du wirst
später immer wieder über diese Umbenennung stolpern.
In die selbe Richtung geht die überaus akademische Beschreibung des Fifo
mit den vielen Funktionen, die eigentlich nur trivialste Dinge tun. Wenn
ich die Fifolänge auf eine Zweierpotenz definiere (so wie hier 16), dann
kann ich die Schreib- und Lesepointer einfach als unsigned Vektoren
ausführen. Die erzeugen ihren Überlauf auf 0 "automatisch" und müssen
nur inkrementiert werden. Bei mir würde diese Beschreibung also etwa so
aussehen:
Den extra Abzweig im Zustand STOP_BIT lasse ich auch weg, denn er ist
nicht mehr nötig, weil die FSM ja schon aus dem Idle heraus das Startbit
ausgeben kann.
Und das "when others" kann ich weglassen, wenn sowieso alle Zustände der
FSM auscodiert sind:
http://www.lothar-miller.de/s9y/categories/25-when-others
BTW: es ist nicht verboten, sich den Quellcode von anderen anzuschauen,
ihn zu analysieren und zu verstehen. Und es dann ggfs. besser zu machen.
BTW2: man muss nicht unbedingt englische Kommentare schreiben, wenn man
nicht Gefahr läuft, das der Code von internationalem Interesse ist.
Danke für die Antworten.
Lothar M. schrieb:> Die zweite Abfrage zum rising_edge() kommt arg überraschend:> [...]> Wo hast du diesen "Zwei-Takte-pro-Prozess" Stil gelernt? ;-)
Das muss ich nochmal anschauen, genauso wie die Sensitivity Listen.
>> So werden denn auch keine Takte in einem FPGA gemacht:> [...]> Das quittiert der Synthesizer mit Kommentaren wie "no good design> practice". Um Takte zu erzeugen, gibt es Taktmanager und -buffer.> Das, was du hier eigentlich brauchst, sind simple Clock-Enables und 1> Takt im gesamten Design:
Schade, fand mein Konzept logischer. Das "1 single clock" Konzept muss
noch nachvollziehen. Wie bekommt man solche Warnungen (in Vivado)
angezeigt? Gibt es eine äquivalente -Wall Option?
> In die selbe Richtung geht die überaus akademische Beschreibung des Fifo> mit den vielen Funktionen, die eigentlich nur trivialste Dinge tun.> [...]>
Trivial, bis plötzlich Kommentare Abläufe erklären. ;)
Meiner Programmiererfahrung nach sind für sich selbst sprechende
Subprogramme inline Kommentaren vorzuziehen. Kann man sicherlich drüber
streiten in diesem Beispiel.
> Den extra Abzweig im Zustand STOP_BIT lasse ich auch weg, denn er ist> nicht mehr nötig, weil die FSM ja schon aus dem Idle heraus das Startbit> ausgeben kann.
Stimmt, so ist es kompakter.
> Und das "when others" kann ich weglassen, wenn sowieso alle Zustände der> FSM auscodiert sind:
Hm, das habe ich jetzt sogar als "Information" des Synthesizers
entdeckt. :)
vhdlnub schrieb:> die Sensitivity Listen.
... sind ausschließlich für die Simulation interessant. Der
Synthesizer schert sich nur insofern um diese Listen, als er eine Info
ausgibt, die besagt, dass wegen der falschen Liste die Simulation nicht
zur erzeugten Hardware passt.
vhdlnub schrieb:> Schade, fand mein Konzept logischer.
Eigentlich schießt du dir mit diesem langsamen "Takt" insofern ins Bein,
als du für jede Aktion dieses Moduls warten musst, bis dieser Takt
wieder aktiv ist. Wenn also jetzt gerade die FSM im IDLE steht, gerade
ein baud_clk war und genau jetzt war zu versenden wäre, kann der
Transmitter nicht starten, sondern muss aufgrund des langsamen
"Baud-Taktes" noch eine komplette Bitzeit abwarten, bis es losgeht. In
meinem Sender dort auf
http://www.lothar-miller.de/s9y/categories/42-RS232 kann auf Anforderung
mit jedem Quarztakt eine neue Übertragung gestartet werden, weil der
gesamte UART mit dem Systemtakt läuft.
(Dort sieht man übrigens auch, dass ich das Senderegister mit einem 10
Bit Schieberegister implementiert habe, das geht in einem FPGA
effizienter als die Implementation eines Multiplexers. Ich bin da
übrigens nicht selber drauf gekommen, sondern habe mir die internen
Hardwarekomponenten von gängigen µC mal genauer angeschaut, deren
Verhalten untersucht und das dann beschrieben... ;-)
> Das "1 single clock" Konzept muss noch nachvollziehen.
Unbedingt. Ein "ideales" FPGA Design hat systemweit nur 1 Takt, der Rest
wird mit Clock-Enables (man könnte sie auch "Flags" nennen) erledigt,
die jeweils ebenfalls für 1 Taktzyklus aktiv sind und damit in anderen
Modulen irgendwas auslösen.
Und wenn eben doch ein zweiter Takt nötig ist, dann muss der mit
Clockmanagern erzeugt werden, damit die Toolchain die abgeleiteten
Timings bestimmen und kontrollieren kann.
> Wie bekommt man solche Warnungen (in Vivado) angezeigt?
Umpf, ehrlich gesagt weiß ich das jetzt gar nicht, weil ich solche
Meldungen nicht produziere... ;-)
Aber ich würde da jetzt mal nach Anmerkungen zu "Jitter" und "Skew"
suchen.
> Meiner Programmiererfahrung nach sind für sich selbst sprechende> Subprogramme inline Kommentaren vorzuziehen.
Ja, nur muss man dann eben immer wieder mal schauen, was das Subprogramm
denn überhaupt macht. Und vor allen ist die Denkweise in "Subprogrammen"
in VHDL prinzipiell falsch, weil ja kein "Subprogramm" aufgerufen,
sondern Hardware daraus erzeugt wird. Insofern sollte man sich immmer
vergegenwärtigen, dass man mit VHDL eben nicht prozedural sequenziell
"programmiert", sondern parallele Hardware "beschreibt". Nicht umsonst
ist das D und nicht ein P in VHDL... ;-)
Und wo ich mir das hier mal gerade genauer anschaue:
Weil eine loop in VHDL mehrfache Hardware erzeugt, die gleichzeitig
aktiv ist, ergibt diese Beschreibung bei einer BYTE_WIDTH (irgendwie
verwirred dieser Name, ein Byte hat anerkanntermaßen doch 8 Bits...)
ungleich 1 einen 8 Bit (=1 Byte) breiten Speicher, in dem gleichzeitig
auf mehrere Bytes/Adressen zugegriffen wird. Sowas gibt es in Hardware
auf einfache Weise nicht, es muss recht umständlich und aufwändig
zusammengebastelt werden. Man müsste eigetnlich ein Blockram (das kann
zwei unterschiedliche Wortbreiten) nehmen und die beiden Ports in der
Breite entsprechend konfigurieren, aber so ein Bauteil muss manuell
instanziiert werden (siehe dazu den passenden Abschnitt im Synthesizer
User Guide).
Konkret bedeutet das hier, dass bei größeren Wortbreiten auch für das
Schreiben des Fifos sinnvollerweise eine FSM nötig wäre, die von einem
16 oder 32 Bit Wert ein Byte nach dem anderen in den Fifo einträgt.
Sehr sinnvoll ist es übrigens, sich anfänglich ab&zu mal den
RTL-Schaltplan der erzeugten Hardware anzusehen und zu kontrollieren, ob
der Synthesizer aus der VHDL Beschreibung die Hardware
herausdestillieren konnte, die die Grundlage für die ursprüngliche
Hardwarebeschreibung war.
Just my 2 Cents...
Zum Thema Reset: Vorbelegung der Signale funktioniert im FPGA, aber eben
nur dort und auch nur einmal. Was ist, wenn du deinen UART mal im
laufenden Betrieb resetten musst? Kommt vielleicht nicht oft vor, aber
deinen Embedded C++-Code versuchst du auch, möglichst waaserdicht zu
machen, richtig? Also wenn dein Design definierte Signale erfordert, um
korrekt zu funktionieren, dann ist ein Reset auf jeden Fall die saubere
Lösung, die immer und auf jeder Zielplattform funktioniert. Das braucht
ntürlich ein paar Resourcen, aber wenn das Design so auf Kante genäht
ist, dass keine Resetlogik mehr reinpasst, dann wurde da schon viel
früher ein konzeptioneller Fehler gemacht.
Clockdomains: Wenn du mehrere Takte verwendest, musst du üblicherweise
auch Daten von der einen Domain in die andere transportieren. Das nennt
sich clock domain crossing (CDC) und ist im höchten Maße nichttrivial
und eine schier unerschöpfliche Fehlerquelle. Besonders dann, wenn die
beiden Takte stark unterschiedliche Frequenzen haben. Wenn dein Design
mit 100MHz läuft aber Daten mit 9600bps ausgeben soll, dann stehen die
Takte in einem Verhältnis von ca. 1:104. In diesem Fall reichen keine
einfachen (bzw. dreifachen) Synchronsationsstufen mehr, sondern man
braucht asynchrone Fifos, um Daten sicher in die andere Clockdomain zu
übertragen.
Es ist in diesem Fall viel einfacher und sicherer, nur einen Takt zu
verwenden und das UART-Timing mit Steuersignalen zu erzeugen, die aus
Zählern und FSMs generiert werden. Peripherals, die im x*100kHz-Bereich
arbeiten, haben üblicherweise kein Recht auf eigene Clockdomain :-)
Vancouver schrieb:> Peripherals, die im x*100kHz-Bereich arbeiten, haben üblicherweise kein> Recht auf eigene Clockdomain :-)
mit x = 0..100
Und auch einen 20MHz SPI würde ich nach Möglichkeit eher mit
ClockEnables machen als mit zwei Clockdomains herumzukrampfen... ;-)
Vancouver schrieb:> Also wenn dein Design definierte Signale erfordert, um korrekt zu> funktionieren, dann ist ein Reset auf jeden Fall die saubere Lösung
Wobei man dann aber nicht jedes Flipflop (und schon gar nicht asynchron)
zurücksetzen muss, sondern eigentlich "nur" die FSM (die für den
Ruhezustand auf der "Leitung nach aussen" sorgt) auf IDLE und die
Pointer des Fifo zum selben Wert bringen muss. Denn in diesem Zustand
"räumt" sich die Geschichte hier ja quasi selber auf.
Eine gewichtige zu beantwortende Frage ist allerdings: woher kommt
dieser Reset?
Von einem externen Taster? Wenn ja: Reset unnötig, denn der Taster kann
ja via "Config" auch das FPGA laden.
Von einem externen µC? Warum möchte der das FPGA zurücksetzen? Und warum
zupft er nicht einfach an der "Config"-Leitung?
Vancouver schrieb:> Das nennt> sich clock domain crossing (CDC) und ist im höchten Maße nichttrivial> und eine schier unerschöpfliche Fehlerquelle. Besonders dann, wenn die> beiden Takte stark unterschiedliche Frequenzen haben.
Nunja, wenn die Takte nahe beieinander liegen, dann geht das Design die
meiste Zeit und setzt nur sporadisch aus.
BTDT:
Ein CPU lief mit 90 MHz, der Rest des Designs aber mit 100 MHz. In 9 von
10 Fällen kamen die Daten von der CPU im Design richtig an.
Sporadisch auftretende, nicht sicher reproduzierbare Fehler sind mit die
kniffligsten...
Duke
Lothar M. schrieb:> Von einem externen Taster? Wenn ja: Reset unnötig, denn der Taster kann> ja via "Config" auch das FPGA laden.> Von einem externen µC? Warum möchte der das FPGA zurücksetzen? Und warum> zupft er nicht einfach an der "Config"-Leitung?
Auf diese Weise wird dann alles platt gemacht, das ist nicht immer
erwünscht.
Beispiele:
- Eine Signalverarbeitungspipeline kann man z.B. einfach resetten bevor
die neuen Daten kommen, anstatt den ganzen restlichen Müll
rauszuflushen, wobei der Controller aber weiterlaufen soll. Macht man
manchmal so, wenn IIR-Filter drin sind, die eine Ewigkeit zum Abklingen
brauchen.
- Bei Aerospace-Designs verwendet man manchmal Triple Modular
Redundancy, d.h. man vergleicht die Ergbisse von drei redundanten
Prozessen und trifft eine Mehrheitsentscheidung. Liefert einer der
Prozesse dauerhaft ein abweichendes Ergebnis, wird er resettet, während
die anderen weiterlaufen.
- Oder man möchte bestimmte Teile des Designs im Reset halten, bis
irgendeine Bedingung eingetreten ist.
- Und dann gibts noch das Thema Unabhängigkeit von der Zeiltechnologie.
Wie gesagt, im FPGA geht eine Initialisierung ohne Reset, im ASIC aber
nicht.
Ok, wenn alles das nicht zutrifft, kann man im FPGA einfach am config
ziehen und fertig.
Vancouver schrieb:> Auf diese Weise wird dann alles platt gemacht, das ist nicht immer> erwünscht.
Richtig, aber dann ist das eigentlich kein "Reset" im üblichen Sinn,
sondern ein definiertes Setzen von bestimmten Modulen oder Teilen des
Bausteins auf einen definierten Zustand. Das wird eben nicht über das
übliche systemweite Resetnetzwerk gemacht, sondern lokal an den gerade
nötigen Stellen.
> Eine Signalverarbeitungspipeline kann man z.B. einfach resetten bevor> die neuen Daten kommen, anstatt den ganzen restlichen Müll rauszuflushen
Genau dafür ist aber dann meist wieder wesentlich mehr nötig. Denn wenn
die Daten in einem RAM stehen, dann ist ja schon Essig mit einem simplen
Reset, weil ein RAM so einen Reset-Anschluss ja gar nicht hat.
> Ok, wenn alles das nicht zutrifft, kann man im FPGA einfach am config> ziehen und fertig.
Man kann sihc auf diese Art das Leben signifikant erleichtern. Und bei
ganz arg vielen Diskussionen zu diesem Thema abseits von redundanten
Systemen und ASICs möchten viele einfach nur deshalb irgendeinen Reset,
weil sie den so "gewöhnt" sind.
Aber wir schweifen ab... ;-)
Lothar M. schrieb:> Den extra Abzweig im Zustand STOP_BIT lasse ich auch weg, denn er ist> nicht mehr nötig, weil die FSM ja schon aus dem Idle heraus das Startbit> ausgeben kann.
Ja, das vereinfacht die FSM für den Sender...
Lothar M. schrieb:> Wenn also jetzt gerade die FSM im IDLE steht, gerade> ein baud_clk war und genau jetzt war zu versenden wäre, kann der> Transmitter nicht starten, sondern muss aufgrund des langsamen> "Baud-Taktes" noch eine komplette Bitzeit abwarten, bis es losgeht.
...aber riskiert man dann nicht, dass das Stopbit nur einen Quarztakt
(statt einer Bitzeit) lang ist? Zumindest habe ich das so in Erinnerung,
und daher ist das Stopbit bei mir ein Teil der FSM.
Lothar M. schrieb:> aber dann ist das eigentlich kein "Reset" im üblichen Sinn,> sondern ein definiertes Setzen von bestimmten Modulen oder Teilen des> Bausteins auf einen definierten Zustand.
Ist das nicht die Definition des Reset?
Lothar M. schrieb:> möchten viele einfach nur deshalb irgendeinen Reset,> weil sie den so "gewöhnt" sind.
Wie gesagt, wenn die Rekonfiguration als Reset möglich ist, dann gut.
Lothar M. schrieb:> Aber wir schweifen ab... ;-)
Warum auch nicht. Solange es der Erkenntnis dient :-)
S. R. schrieb:> ...aber riskiert man dann nicht, dass das Stopbit nur einen Quarztakt> (statt einer Bitzeit) lang ist?
Nein, es ist wegen des Zustands STOP_BIT sogar mindestens einen
Quarztakt länger als nötig. Dafür kann aber eben auch nach 1,2 oder 2,7
oder sonstwas Komma irgendwas Baudtakten mit jedem Quarztakt neu
gestartet werden.
Lothar M. schrieb:> Dafür kann aber eben auch nach 1,2 oder 2,7> oder sonstwas Komma irgendwas Baudtakten mit jedem Quarztakt neu> gestartet werden.
Oha. Schau an, das wusste ich noch gar nicht.
Letztendlich ist das Stopbit bei der UART eine Pause, die dem Empfänger
bleibt, um daa empfangene Datum auszuwerten.
Die eignetliche Bitlänge ist i.d.R. unerheblich, sie wird nicht gemessen
oder geprüft, nur zu schnell darf es nicht werden.
Die Übertragung wird beim nächsten Startbit wieder synchronisiert.
Duke