Forum: FPGA, VHDL & Co. Ratschlag / Verbesserung zu vhdl-Progrämmchen


von Hartmut K. (Gast)


Lesenswert?

Hallo zusammen!

#ACHTUNG: längerer Post.


Ich experimentiere im Moment mit einem xc9572 cpld. In diesem Post geht 
es um ein kleines Programm zu dem ich gerne Meinungen, 
Verbesserungsvorschläge und allgemeine vhdl Tips möchte.


Auf der Platine ist das cpld, 8 Dioden, 2 7-Segmentanzeigen die paralles 
an I/O Pins hängen und jeweils von einem Transistor 'scharf' geschaltet 
werden. Die Transistoren haben wieder 2 I/Os. Außerdem habe ich 2 
taster, die mit einem 74er entprellt sind um mir da Code zu sparen.

Das Programm soll einfach die 1 je nach Taste nach links oder rechts 
schieben und dan Inhalt des Schieberegisters an den Dioden ausgeben 
sowie an den (beiden zunächst) 7-Segmentanzeigen darstellen, welche 
Diode eigentlich an ist.

So weit so gut. Das Programm tut auch was es soll, aber ich habe das 
Gefühl das das nicht der Weisheit letzter Schluss ist. Deshalb einige 
Fragen an euch:

- Wie würde man die Aufgabe anders (richtiger?) programmieren?

- Ist es bei vhdl analog zu C möglich so umständlichen Code zu 
produzieren, daß weniger performante Hardware herauskommt oder massig 
Ressourcen verschwendet werden? Oder sind die Optimierer tatsächlich in 
der Lage aus jedem Gurkencode die selbe Hardware zu machen?

- wie strukturiert man eigentlich ein vhdl Programm. Also analog zu : in 
C main-Schleife mit zyklischen Aufgaben und ggf. Interrupts die den 
Ablauf beeinflussen.

-würde man sinnvoll eine eigene entity 7-Segment Ausgabe programmieren 
und die in einem Programm als component verwenden? Ich überblicke gerade 
nicht, wie das aussehen würde, aber das component Konzept ist mir im 
Prinzip bekannt. Da müsste man die Ausgabe entity in der architecture 
des "Hauptprogrammes" an gemeinsame Signale Knoten?

-wirklich viel scheint man in ein cpld nicht zu bekommen? Der Bericht 
sagte mir im Grunde, daß mit dem bischen schon 30% voll sind. Oder ist 
das eine proggrammierunsfrage (s.o.)?

-wird man in einem reaken etwas komplexeren Design nicht wahnsinnig vor 
internen Signalen? Ich habe das Gefühl daß ich zu fast jedem 
Ausgangssignal irgendwo ein internes Signal habe und das bei components 
noch schlimmer würde?


Damit auch alle wissen, was ich verbrochen habe und worüber die Fragen 
sind der Code:
1
library IEEE;
2
use IEEE.STD_LOGIC_1164.ALL;
3
use IEEE.STD_LOGIC_ARITH.ALL;
4
use IEEE.STD_LOGIC_UNSIGNED.ALL;
5
6
entity test1_code is
7
    Port ( D     : out STD_LOGIC_VECTOR (7 downto 0);          --LED Zeile
8
        DISP   : out STD_LOGIC_VECTOR (6 downto 0) := "0000000";  --7-Segment Anzeige
9
        DISP_L : out STD_LOGIC;                        --en links
10
        DISP_R : out STD_LOGIC;                        --en rechts 
11
        clk    : in  STD_LOGIC;                        --Global clk
12
           T     : in  STD_LOGIC;                         --Taste links
13
        T2     : in  STD_LOGIC);                        --Taste rechts
14
end test1_code;
15
16
architecture Behavioral of test1_code is
17
  signal D_int : STD_LOGIC_VECTOR (7 downto 0):="00000001";
18
  signal T_old :STD_LOGIC;
19
  signal T2_old:STD_LOGIC;
20
  signal toggleDisp:STD_LOGIC;
21
begin
22
  
23
  main_clock: process(clk)
24
  begin
25
    if rising_edge(clk) then
26
      if (T='1' and T_old='0') then      
27
        D_int <= (D_int(6 downto 0) & D_int(7));            --nach links schieben
28
      elsif (T2='1' and T2_old='0') then
29
        D_int <= ( D_int(0) & D_int(7 downto 1));            --nach rechts schieben
30
      end if;          
31
      T_old <= T;
32
      T2_old<= T2;
33
      toggleDisp <= not toggleDisp;                      --aktives 7-Seg wechseln
34
    end if;
35
  end process main_clock;
36
  
37
  DISP_L <= toggleDisp;                              --entweder links an
38
  DISP_R <= not toggleDisp;                            --oder rechts
39
  D<=D_int;                                      --Schiebestand an Diodenreihe 
40
41
  with D_int select                                  --Schiebestand an die Segmente
42
    DISP <= "1111001" when "00000001",
43
            "0100100" when "00000010",
44
            "0110000" when "00000100",
45
          "0011001" when "00001000",
46
          "0010010" when "00010000",
47
          "0000011" when "00100000",
48
          "1111000" when "01000000",
49
            "0000000" when "10000000",
50
          "1111111" when others;
51
52
end Behavioral;

von Klaus Falser (Gast)


Lesenswert?

> - Wie würde man die Aufgabe anders (richtiger?) programmieren?
Richtiger ist bei einem Programm dieser Größenordnung Geschmackssache, 
aber man könnte es  z.B. strukturieren, eben mit Untermodule, die nur 
eine bestimmte Funktion haben, z.B. Schieberegister, 7-Segment-Anzeige 
usw.

> - Ist es bei vhdl analog zu C möglich so umständlichen Code zu
> produzieren, daß weniger performante Hardware herauskommt oder massig
> Ressourcen verschwendet werden? Oder sind die Optimierer tatsächlich in
> der Lage aus jedem Gurkencode die selbe Hardware zu machen?
Jain.
Die Verwendung von Struktur und Untermodulen bringt jedenfalls keine 
Leistungseinbußen.

> - wie strukturiert man eigentlich ein vhdl Programm.
Man lagert zusammengehörende Signale und Funktionalität in Untermodule 
aus.
> Also analog zu : in C main-Schleife mit zyklischen Aufgaben
> und ggf. Interrupts die den Ablauf beeinflussen.
VHDL funktioniert nun wirklich komplett anders.

> -würde man sinnvoll eine eigene entity 7-Segment Ausgabe programmieren
> und die in einem Programm als component verwenden? Ich überblicke gerade
> nicht, wie das aussehen würde, aber das component Konzept ist mir im
> Prinzip bekannt. Da müsste man die Ausgabe entity in der architecture
> des "Hauptprogrammes" an gemeinsame Signale Knoten?
Ja

> -wirklich viel scheint man in ein cpld nicht zu bekommen? Der Bericht
> sagte mir im Grunde, daß mit dem bischen schon 30% voll sind. Oder ist
> das eine proggrammierunsfrage (s.o.)?
Ein 9572 ist schon wirklich klein, dass füllt sich schnell. Für 
komplexere Designs gibt es FPGAs. Mit Programmierstil hat das (fast) 
nichts zu tun.

> -wird man in einem reaken etwas komplexeren Design nicht wahnsinnig vor
> internen Signalen? Ich habe das Gefühl daß ich zu fast jedem
> Ausgangssignal irgendwo ein internes Signal habe und das bei components
> noch schlimmer würde?
Deshalb wird das ganze ja strukturiert. Ein C/C++ Programm ohne 
Unterprogramme hätte ja auch unübersichtlich viele Variablen.

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


Lesenswert?

> -wirklich viel scheint man in ein cpld nicht zu bekommen?
Ein 9572 hat nur 72 FFs, die sind schnell weg. CPLDs sind daher nur für 
sehr begrenzte Aufgaben geeignet (z.B. als SPI-Slave oder 
Adressdecoder).

Um wirklich größere Hardwareaufgaben zu bewältigen brauchst du ein FPGA. 
Dort gibts FFs wie Sand am Meer ;-)

1
     if (T='1' and T_old='0') then      
2
       :
3
     end if;
4
     T_old <= T;
So nicht!
Du mußt externe Signale mit 2 FF einsynchronisieren.
Nichtbeachten dieser Regel führt zu sproradischen Fehlern, d.h. dein 
Design hat z.b. jede Minute/Stunde/Tag ein Fehlverhalten. Stichworte: 
Metastabilität, Taktdomänenübergang
1
  main_clock: process(clk)
Es gibt keinen Main-Prozess. Der clk ist der Master-Takt, der in allen 
(getakteten) Prozessen verwendet wird. D.h. es gibt nur 1 Takt. Später 
darfst du auch mal (wenns unbedingt sein muß) mehrere Takte verwenden 
;-)

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


Lesenswert?

> Ist es bei vhdl analog zu C möglich so umständlichen Code zu
> produzieren, daß weniger performante Hardware herauskommt oder massig
> Ressourcen verschwendet werden?
Ja.
Allerdings kommt es hier nicht (bzw. nur eingeschränkt) auf die Anzahl 
der Prozesse und Zuweisungen an. Es kommt auf die Denkweise an:

Nehmen wir mal diesen Code 1:
1
architecture Behavioral of test1_code is
2
  signal cnt : integer range 0 to 200 := 0;
3
begin
4
  
5
  process begin
6
    wait until rising_edge(clk);
7
    if (cnt=100) then    
8
       cnt <= 0;  
9
    else
10
       cnt <= cnt+1;
11
    end if;
12
  end process;
13
14
end;

Und diesen Code 2:
1
architecture Behavioral of test1_code is
2
  signal cnt : integer range 0 to 200 := 0;
3
begin
4
  
5
  process begin
6
    wait until rising_edge(clk);
7
    if (cnt>=100) then    
8
       cnt <= 0;  
9
    else
10
       cnt <= cnt+1;
11
    end if;
12
  end process;
13
14
end;

Beide Beschreibungen machen funktionell das selbe: sie zählen von 0 bis 
100 (jeweils einschliesslich). Nur wird einmal auf GLEICH und das andere 
mal auf GRÖSSER-GLEICH verglichen.


Und hier das Ergebnis. Auf einem Spartan 3 FPGA ergibt sich für
Lösung 1: Maximum Frequency: 187.875MHz
und
Lösung 2: Maximum Frequency: 215.943MHz
und damit immerhin 15% Performance-Unterschied   :-o

Klar, sagt jetzt jeder, dann nehmen wir immer die Gößer-Gleich Variante.

Aber, was passiert, wenn ich im Code 2 folgendes mache:
1
:
2
  signal cnt : integer range 0 to 110 := 0;  -- passen in 7 Bit --> nur 7-Bit Vergleich nötig
3
:
Dann wird die schnelle Lösung 2 noch einiges schneller, nämlich
Maximum Frequency: 232.693MHz
und damit 24% schneller als die exakt funktionsgleiche langsamere 
Beschreibung.




BTW:
Nimm statt dieser Synopsys-Libs
> use IEEE.STD_LOGIC_ARITH.ALL;
> use IEEE.STD_LOGIC_UNSIGNED.ALL;
besser die herstellerunabängige
> use IEEE.NUMERIC_STD.ALL;
Dann kanst du Vektoren explizit signed oder unsigned verarbeiten.
(Also nicht nur implizit über IEEE.STD_LOGIC_UNSIGNED.ALL und 
IEEE.STD_LOGIC_SIGNED.ALL)

von Hartmutte K. (Gast)


Lesenswert?

Interessant. Hätte gedacht '=' wär schneller....
Wo wird die maximale Frequenz angezeigt die mit einem Design geht?

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


Lesenswert?

> Interessant. Hätte gedacht '=' wär schneller....
Ja, andere auch   ;-)

> Wo wird die maximale Frequenz angezeigt die mit einem Design geht?
Die Synthese gibt einen ersten Tip ab, das finale Timing kann im Static 
Timing Report untersucht werden.

von Corni (Gast)


Lesenswert?

Und wie kommt man dan auf sowas? simples ausprobieren oder gibts da 
irgendwelche tricks, die man entweder weiß, oder auch nicht?
MfG
Corni

von Jan M. (mueschel)


Lesenswert?

Das ist aber auch ein ganz spezieller Fall, den Lothar da ausgegraben 
hat. Ich habe mal eine kurze Testreihe gemacht, indem ich als 
Vergleichswert 100 und 101, als Operator = und >=, und als Maximalwert 
für cnt 200 und 110 verwendet habe.

Vergl.  Opera.   Max.   Freq.
100     =        200    188.2
101     =        200    188.2
100     >=       200    216.4
101     >=       200    193.6

100     =        110    218.8
101     =        110    218.8
100     >=       110    218.8
101     >=       110    214.9


Der = Operator erzeugt also unabhängig vom Vergleichswert immer die 
selbe Laufzeit (klar, es müssen immer alle Bits verglichen werden), bei 
>= (oder auch <=) sind aber Optimierungen möglich, weil einzelne Bits 
gar nicht betrachtet werden müssen.
Der beste Code ergibt sich in jedem Fall mit einer korrekt angegebenen 
Größe des Counters - bei einem Maximalwert von 200 muss ja noch ein 
zusätzliches Bit betrachtet werden.

Ich würde zusammenfassen: Bei Vergleichen mit Zahlen, die viel kleiner 
als der Maximalwert sind oder ein sehr glatter Wert sind (im Binärformat 
natürlich), lohnt sich ein Vergleich auf Ungleichheit, im allgemeinen 
Fall ist Gleichheit aber einfacher.

von Huch (Gast)


Lesenswert?

http://www.mikrocontroller.net/articles/VHDL_schnipsel_count_slice

> Ein Vergleich ob eine Binärzahl größer oder kleiner als eine andere
> ist, benötigt in der Regel mehr Logikgatter, als der Vergleich zweier
> Binärzahlen auf Gleichheit. Sogar bis zum Dreifachen der Slices, wie
> ein Blick in das Kapitel "Slice Count" in dem Xilinx Library Guide
> beweist.

????

von Jan M. (mueschel)


Lesenswert?

Der Grund für die höhere Komplexität von Ungleichheits-Vergleichen ist 
ganz einfach:
Für = kann man jedes Bit einzeln betrachten und stellenweise mit dem 
Sollwert vergleichen. Bei <,>,<= etc. müssen auch noch Querbeziehungen 
zwischen den Stellen berücksichtigt werden ("Bit2 darf nur dann gesetzt 
sein, wenn gleichzeitig Bit3 gesetzt und Bit4 nicht gesetzt ist").

z.B. für x = 14 (dezimal betrachtet, die Zehnerstelle sei x2, die 
Einerstelle x1) ergibt sich als logischer Ausdruck:
x2 = 1 und x1 = 4

Für x <= 14 aber
(x2 = 1 und x1 <= 4) oder (x2<1)

Bei größeren Stellenanzahlen wird der Unterschied noch deutlicher.

Jetzt gibt es aber noch Spezialfälle, wo der Vergleich auf Ungleichheit 
einfacher ist, z.B.  x <= 19, das wird einfach zu x2<=1, die Einerstelle 
braucht nicht betrachtet zu werden.

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


Lesenswert?

> Ich habe mal eine kurze Testreihe gemacht...
Schön, wenn man andere ins Grübeln bringt ;-)
Ich habe auch Versuche gemacht und bin für größere Vergleicher-Breiten 
(ab 20Bit) zum diametralen Ergebnis gekommen:
Ein Vergleich auf GLEICH war marginal schneller.

> und als Maximalwert für cnt 200 und 110 verwendet habe.
Am wichtigsten ist eine richtig definierte Wortbreite. Wenn ich nur 7 
Bit brauche, macht es keinen Sinn, 8 Bit zu vergleichen.

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


Lesenswert?

> Der Grund für die höhere Komplexität von Ungleichheits-Vergleichen ist
> ganz einfach:
Uberraschend ist aber (nach althergebrachter Denkweise), dass ein 
Abwärtszähler keinen Geschwindigkeitsvorteil bringt:
1
architecture Behavioral of test1_code is
2
  signal cnt : integer range 0 to 100 := 0;
3
begin
4
  
5
  process begin
6
    wait until rising_edge(clk);
7
    if (cnt=0) then  cnt <= 100;  
8
    else             cnt <= cnt-1;
9
    end if;
10
  end process;
11
12
end;
Der könnte doch ganz locker mit einem ordinären und schnellen OR-Gatter 
abgebildet werden (könnte man meinen).

Warum ist ein Vergleich auf 100 gleich schnell, wie einer auf 0?
Ganz einfach. Ein Vergleich von 7 Bit mit einem gespeicherten Wert ist 
eine Logische Funktion mit 7 Eingägnen. Sowas erfordert 2 LUT-Ebenen 
hintereinander:
1
       _____
2
0 ----|     |
3
1 ----|     |
4
2 ----|     |---.
5
3 ----|_____|   |    _____
6
                `---|     |
7
4 ------------------|     |
8
5 ------------------|     |----- A
9
6 ------------------|_____|
Mit was verglichen wird (0 oder sonstwas) ist dabei egal, weil die LUT 
auf diese Art jede logisache Kombination der 7 Eingangswerte abbilden 
können.

von Duke Scarring (Gast)


Lesenswert?

@Hartmut K.:

Dein Quelltext sieht auf den ersten Blick nicht zu lang aus, für die 
Aufgabe, die er erfüllen soll. ieee.std_logic_arith.all vs. 
ieee.numeric_std.all wurde schon erwähnt.

Ich würde statt den Bitstrings etwas lesbares hinschreiben, z.B. 
vordefinierte Konstanten:
1
constant seg_1 : std_logic_vector(6 downto 0) := "1111001";

>-wird man in einem realen etwas komplexeren Design nicht wahnsinnig vor
>internen Signalen? Ich habe das Gefühl daß ich zu fast jedem
>Ausgangssignal irgendwo ein internes Signal habe und das bei components
>noch schlimmer würde?

Ich verwende da gerne einen _in und einen _out record. Siehe auch 
[1]. Damit wird es übersichtlich und es läßt sich auch in einem 
komplexen Design ein Signal schmerzfrei hinzufügen.

Duke


[1] http://www-md.e-technik.uni-rostock.de/lehre/vlsi_i/vhdlf.ppt

von bko (Gast)


Lesenswert?

Hi,

>Der Grund für die höhere Komplexität von Ungleichheits-Vergleichen ist
>ganz einfach:
>Für = kann man jedes Bit einzeln betrachten und stellenweise mit dem
>Sollwert vergleichen. Bei <,>,<= etc. müssen auch noch Querbeziehungen
>zwischen den Stellen berücksichtigt werden ("Bit2 darf nur dann gesetzt
>sein, wenn gleichzeitig Bit3 gesetzt und Bit4 nicht gesetzt ist")

bei CMOS-Logic Standart-Cell Logik Synthese stimmt dies 100%.

Aber der Spartan hat schnelle kaskadierbare Addierer und
Multiplexer (ADD1 und MUXCY). Diese werden von der ISE
Synthese auch für Vergleiche benutzt (habe es eben
mal ausprobiert).

bko

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.