Um mich für die Arbeit etwas in VHDL fit zu machen, habe ich mir eine
kleine Evaluationsplattform gekauft, welche u.a. eine 4stellige
7-Segment-Anzeige bietet.
Als Lernaufgabe habe ich einen 4-stelligen Dezimalzähler programmiert,
der auch gut läuft.
Trotzdem würde ich gerne wissen, ob sich da Unschönheiten verbergen, die
man gar nicht erst einschleifen lassen sollte.
Zaehler7Segment.vhd stellt die oberste Ebene dar.
Konkrete Fragen:
* Der BCD->7-Segment-Dekodierer wird in eine recht komplexe Struktur
aus Multiplexern umgesetzt. Ich habe eher so etwas wie eine ROM-Tabelle
erwartet, aber dafür hätte ich einen synchronen Prozeß nehmen sollen.
* Ist das mit dem heruntergeteilten Takt so in Ordnung?
Distributed/gated clock soll ja ein typisches Anfängerproblemfeld
sein....
Hab kurz draufgeschaut. Zwei Sachen die mir aufgefallen sind:
"Clk1000Hz": Ich würde das 1000Hz weglassen. Denn es ist in Zukunft sehr
mühsam, wenn das Modul wiederverwendet werden soll und dann das "1000Hz"
umbenannt werden muss, weils z.B. "2000Hz" sind.
Zum Clock Divider: Um Clocks zu erzeugen gibts spezielle Teile im FPGA.
Sieh dir mal die PLL, DLL und DCM an. Außerdem sollten clocks dann
üblicherweise über spezielle clock-netze geleitet werden.
Das mit den clock-Netzen muß ich mir ansehen, danke. Leider hängt bei
diesem Board der Oszillator an Clk3, so daß ich dort keine PLL
zuschalten kann (Cyclone I).
Die Takte werde ich gemäß ihrer Anwendung bezeichnen.
Vielen Dank!
meckerziege schrieb:> Zum Clock Divider: Um Clocks zu erzeugen gibts spezielle Teile im FPGA.
Ein Anfängerdesign hat genau 1 (in Worten: einen einzigen) Takt. Und der
Rest wird mit Clock-Enables gemacht.
Und so werden sowieso keine Takte gemacht, weil dieser "Takt"
anschließend nicht auf ein Taktnetz geht, sondern "von Hand" geroutet
wird:
1
ClkInt<=notClkInt;
Und das hier ist echt aufwändig und umständlich zu lesen:
Man kann kein Muster erkennen. Und die 7Segment-LEDs heißen
normalerweise a...g
Insgesamt ist der Code m.E. ein Musterbeispiel, warum VHDL als
"geschwätzig" und "umständlich" dargestellt wird.
Sieh dir mal das da an. Dort sind beide Geschichten realisiert:
1. der Clock-Teiler über das Clock-Enable und
2. der leserliche BCD-7Segment-Decoder
http://www.lothar-miller.de/s9y/archives/88-VHDL-vs.-Verilog-am-Beispiel-einer-Stoppuhr.html
Lothar Miller schrieb:> weil dieser "Takt" anschließend nicht auf ein Taktnetz geht,
Bringt Quartus eine Meldung wie "automatically promoted clkInt"?
Wenn ja, dann wird er über eine Taktleitung gerouted.
Wenn nein: Bei Xilinx/Spartan3 kann man die Aufschaltung auf eine
Taktleitung erzwingen durch Instanzierung eines Global-Clock-Buffers.
Wie das bei Altera/CycloneI ist, weiss ich allerdings nicht.
Falls eine solche Aufschaltung beim CycloneI gar nicht möglich
ist, dann ist mein Beitrag hier allerdings hinfällig.
Den 7-Segment-Decoder habe ich aus 2 Gründen so gebaut:
a) wollte ich ihn hinsichtlich der Art der Anzeige (common
anode/cathode) variabel gestalten
b) hatte ich die Tabelle anhand dieses Bildes quasi beim Schreiben
entworfen:
http://www.fpga4fun.com/images/Opto%207seg.gif
Ich habe quasi immer nur abgelesen, welche Segmente ein- oder
ausgeschaltet sein müssen.
Hinsichtlich der Handhabung des Clock-Signals schaue ich heute noch mal
in die Ausgaben von Quartus.
Mir ist noch nicht ganz klar, wie ich das mit dem Clock-Enable hätte
machen sollen - selbst wenn ich in ClockGen das Signal nur für eine
Taktperiode auf '1' setze und dann mit
if (rising_edge(clk) and clkEn = '1')
abfrage, besteht doch die Gefahr daß clkEn und die steigende Flanke von
clk evtl. durch Laufzeitverzögerungen disjunkt sind?
Wäre es töricht, ClockGen etwa für die Taktversorgung eines Softcores
wie des T80 einzusetzen?
Rüdiger Knörig schrieb:Rüdiger Knörig schrieb:> Mir ist noch nicht ganz klar, wie ich das mit dem Clock-Enable hätte> machen sollen - selbst wenn ich in ClockGen das Signal nur für eine> Taktperiode auf '1' setze
Genau so und nicht anders wird es gemacht.
> und dann mit> if (rising_edge(clk) and clkEn = '1')> abfrage, besteht doch die Gefahr daß clkEn und die steigende Flanke von> clk evtl. durch Laufzeitverzögerungen disjunkt sind?
Frage es so ab, dass die Ursache klar ist:
1
ifrising_edge(clk)then
2
ifclkEn='1'then...
Denn dann sieht jeder, dass bei einer steigenden Taktflanke der Zustand
von clkEn ausgewertet wird. Und weil das alles sowieso nur eine
Verhaltensbeschreibung ist, gibt es keine Laufzeitverzögerungen. Oder
andersrum: solange du der Toolchain mitteilst, was clk ist, und die
Toolchain nicht meckert, dass der zu schnell sei, so lange kann man auch
die Laufzeiten ignorieren. Und das Design wird sicher laufen. diese
Vorgehensweise nennt man "synchrones Design".
> Wäre es töricht, ClockGen etwa für die Taktversorgung eines Softcores> wie des T80 einzusetzen?
Ja. Wie gesagt: so werden keine Takte erzeugt.
Ja, Quartus war recht eifrig mit dem Befördern:
"Info: Automatically promoted signal "Clk" to use Global clock in PIN 92
Info: Automatically promoted some destinations of signal
"ClockDivider:ClockDivider_1kHz|ClkInt" to use Global clock
Info: Destination "ClockDivider:ClockDivider_1kHz|ClkInt" may be
non-global or may not use global clock
Info: Automatically promoted signal
"BCDCounter:\gen_counters:1:BCDCounter_i|Cout" to use Global clock
Info: Automatically promoted signal "BCDCounter:BCDCounter_0|Cout" to
use Global clock
Info: Automatically promoted some destinations of signal
"ClockDivider:ClockDivider_10Hz|ClkInt" to use Global clock
Info: Destination "ClockDivider:ClockDivider_10Hz|ClkInt" may be
non-global or may not use global clock
Info: Automatically promoted signal
"BCDCounter:\gen_counters:2:BCDCounter_i|Cout" to use Global clock
Info: Automatically promoted some destinations of signal "Rst_n" to use
Global clock
Info: Destination "ActiveCounterOutput[0]" may be non-global or may
not use global clock
Info: Destination "ActiveCounterOutput[1]" may be non-global or may
not use global clock
Info: Destination "ActiveCounterOutput[2]" may be non-global or may
not use global clock
Info: Destination "ActiveCounterOutput[3]" may be non-global or may
not use global clock
Info: Destination "outputMux[0]~reg0" may be non-global or may not use
global clock
Info: Destination "outputMux[1]~reg0" may be non-global or may not use
global clock
Info: Destination "outputMux[2]~reg0" may be non-global or may not use
global clock
Info: Destination "outputMux[3]~reg0" may be non-global or may not use
global clock
Info: Destination "\multiplex:currentCounter[1]" may be non-global or
may not use global clock
Info: Destination "\multiplex:currentCounter[0]" may be non-global or
may not use global clock
Info: Limited to 10 non-global destinations
Info: Pin "Rst_n" drives global clock, but is not placed in a dedicated
clock pin position"
Rüdiger Knörig schrieb:> Info: Pin "Rst_n" drives global clock
Wozu braucht ein schnöder Taktteiler (oder von mir aus auch
Enable-Generator) auch einen Reset? Etwas, das sowieso alle paar us oder
ms in der exakt gleichen Art immer wieder passiert braucht keinen Reset.
Ein Initwert von 0 reicht vollkommen aus...
Und hier wird die Simulation nicht lange laufen, weil der Überlauf nicht
abgefanden wird:
1
variablecurrentCounter:integerrange0to3:=0;-- Counter variable corresponding to the active counter
2
3
ifrising_edge(Clk1000Hz)then
4
:
5
currentCounter:=currentCounter+1;
6
endif;
Einen impliziten Überlauf von 3 nach 0 kennt der Simulator nämlich
nicht. Auch wenn es in der Hardware tatsächlich funktioniert.
Das hier wird allerdings schon nicht mehr wie "erwartet" funktionieren:
1
variableCounter:integerrange0to4:=0;
2
3
ifrising_edge(Clk)then
4
:
5
Counter:=Counter+1;
6
endif;
BTW:
warum braucht eine lokale Variable einen solch langen und
umständlichen Namen: "Stromzähler"?
Ich war der Meinung, daß bei dem vorgegebenen Integer-Typ (integer range
...) auch der Simulator weiß, wo das Ende der Fahnenstange ist?
Ansonsten habe ich im wahrsten Sinne des Wortes lesbaren Quelltext auch
deshalb schätzen gelernt, da ich neben der Entwicklung auch für die
Wartung zuständig bin und deshalb recht viele Unterbrechungen der Art
"da läuft was nicht | wie geht das" habe.
Nicht nur dann ist es schön, wenn man auch bei lokalen Variablen schnell
ihre Aufgabe aus dem Namen ableiten kann.
Und eine kleine schlanke Routine kann schnell mal anwachsen, und je nach
dem Kontext, aus dem man rüberschaltet, können dann schnell mal
Divergenzen zwischen der tatsächlichen und angenommenen Aufgabe der
Variable 'k' entstehen.
Kurz zusammengefaßt: Quelltext muß für mich eine Handlung erzählen. Und
da IDE's heute automatisierte Vervollständigung anbieten, sollte man
nicht bei der Aussagekraft von Bezeichnern sparen.
Rüdiger Knörig schrieb:> Ich war der Meinung, daß bei dem vorgegebenen Integer-Typ (integer range> ...) auch der Simulator weiß, wo das Ende der Fahnenstange ist?
Er weiß es. Und er sagt es dir dann auch. Denn er macht den Überlauf
nicht von sich aus, sondern bleibt einfach stehen mit "error:
currentCounter out of range 0 to 3".
> sollte man nicht bei der Aussagekraft von Bezeichnern sparen.
Mal ganz ehrlich: "currentCounter" sagt mir gar nichts...
Denn natürlich ist das kein Strom (=Current), der da gezählt wird. Und
ebenso natürlich hat jeder Counter einen momentanen (=current) Wert.
Ich würde einen Counter, der mit 1kHz die 1ms-Tics zählt "cnt1ms"
nennen. Denn wenn man Clock zu "clk" (Zitat: if rising_edge(Clk1000Hz))
abkürzen kann, dann kann ich Counter auch zu "cnt" abkürzen. Und wenn es
unbedingt ein Roman sein soll, dann "MillisecCounter" oder sogar
"MillisekundenZaehler". Dann ist wirklich selbsterklärend, WAS der
Zähler zählt...