Forum: FPGA, VHDL & Co. Meinung zum Anfänger-Code


von Rüdiger K. (sleipnir)


Angehängte Dateien:

Lesenswert?

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

von meckerziege (Gast)


Lesenswert?

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.

von Rudi (Gast)


Lesenswert?

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!

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

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 <= not ClkInt;

Und das hier ist echt aufwändig und umständlich zu lesen:
1
  with input select
2
    output <=
3
    (6 => ledOff, others => ledOn)           when "0000",
4
    (1 => ledOn, 2 => ledOn, others => ledOff) when "0001",
5
    (2 => ledOff, 5=> ledOff, others => ledOn) when "0010",
6
    (4 => ledOff, 5=> ledOff, others => ledOn) when "0011",
7
    (0 =>ledOff, 3 => ledOff, 4=> ledOff, others => ledOn) when "0100",
8
    (1 => ledOff, 4=> ledOff, others => ledOn) when "0101",
9
    (1 => ledOff, others => ledOn) when "0110",
10
    (0 => ledOn, 1 => ledOn, 2 => ledOn, others => ledOff) when "0111",
11
    (others => ledOn) when "1000",
12
    (4 => ledOff,others => ledOn) when "1001",
13
    (others => ledOff) when others;
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

von Josef G. (bome) Benutzerseite


Lesenswert?

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.

von Rüdiger K. (sleipnir)


Lesenswert?

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?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

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
   if rising_edge(clk) then
2
      if clkEn = '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.

von Rüdiger K. (sleipnir)


Lesenswert?

Gut, dann habe ich wieder was dazu gelernt, danke!

von Rüdiger K. (sleipnir)


Lesenswert?

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"

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

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
    variable currentCounter : integer range 0 to 3 := 0;  -- Counter variable corresponding to the active counter
2
3
    if rising_edge(Clk1000Hz) then
4
        :
5
        currentCounter := currentCounter + 1;
6
    end if;
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
    variable Counter : integer range 0 to 4 := 0; 
2
3
    if rising_edge(Clk) then
4
        :
5
        Counter := Counter + 1;
6
    end if;


BTW:
warum braucht eine lokale Variable einen solch langen und 
umständlichen Namen: "Stromzähler"?

von Rüdiger K. (sleipnir)


Lesenswert?

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.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

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

: Bearbeitet durch Moderator
von Rüdiger K. (sleipnir)


Lesenswert?

Das stimmt natürlich - currentCounterValue wäre besser gewesen, aber die 
cnt-Schreibweise gefällt mir auch.

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.