Forum: FPGA, VHDL & Co. VHDL: Kombinatorische Schaltung oder Multiplexer


von D. C. (4tmeldriver)


Lesenswert?

Hallo und schöne Feiertage,

ich habe zwei Prozesse geschrieben, die genau das selbe tun: Einer 
Ziffer die jeweiligen Segmente einer 7-Segment-Anzeige zuordnen.
Für die erste Variante habe ich für jedes Segment aussagenlogische 
Formeln mittels KV-Diagramm entwickelt und für die zweite Variante habe 
ich stur mittels des case-Konstrukts die entsprechenden Bits zugewiesen.
Hier der Code beider Varianten:
Variante 1:
1
showSegments : process (number)
2
  begin
3
    segments(0) <= not(number(3) or number(1) or (number(2) and number(0)) or (not number(2) and not number(0)));
4
    segments(1) <= not(not number(2) or (number(1) and number(0)) or (not number(1) and not(number(0))));
5
    segments(2) <= not(number(2) or number(0) or (not number(1) and not number(0)));
6
    segments(3) <= not(number(3) or (not number(2) and not number(0)) or (not number(2) and number(1)) or (number(2) and not number(1) and number(0)) or (number(1) and not number(0)));
7
    segments(4) <= not((not number(2) and not number(0)) or (number(1) and not number(0)));
8
    segments(5) <= not(number(3) or (not number(1) and not number(0)) or (number(2) and not number(1)) or (number(2) and not number(0)));
9
    segments(6) <= not(number(3) or (not number(2) and number(1)) or (not number(1) and number(2)) or (number(2) and not number(0)));
10
    segments(7) <= '1';
11
  
12
  end process showSegments;

Variante 2:
1
showSegments : process (number)
2
  begin
3
    case number is
4
      when "0000" => segments <= "11000000"; -- "0"     
5
      when "0001" => segments <= "11111001"; -- "1" 
6
      when "0010" => segments <= "10100100"; -- "2" 
7
      when "0011" => segments <= "10110000"; -- "3" 
8
      when "0100" => segments <= "10011001"; -- "4" 
9
      when "0101" => segments <= "10010010"; -- "5" 
10
      when "0110" => segments <= "10000010"; -- "6" 
11
      when "0111" => segments <= "11111000"; -- "7" 
12
      when "1000" => segments <= "10000000"; -- "8"     
13
      when "1001" => segments <= "10010000"; -- "9"
14
      when others => segments <= "11111111"; -- "Error"
15
    end case;
16
  end process showSegments;

Beide Code-Schnipsel funktionieren genauso gut.
Der Lesbarkeit nach hat Variante 2 auf jeden Fall die Nase vorn. Das 
führt mich zu meiner Frage: Welche Variante würde man praktisch der 
anderen vorziehen? Ist eine sparsamer oder wird das ohnehin alles 
optimiert von der Synthese?

Gruß und frohes Fest.

von qwertzuiopü+ (Gast)


Lesenswert?

Auf einem FPGA würde ich davon ausgehen, dass die erste Variante in die 
zweite umgewandelt wird, da ja ein FPGA aus LUTs besteht. Für 
synthetisierte Logik aus einzelnen Gattern hingegen würde ich vermuten, 
dass die erste Variante näher am Endergebnis liegt.
Lesbarer ist aber auf jeden Fall die zweite - und ein Tool, welches es 
nicht schafft, daraus die jeweils bessere Variante zu erzeugen, ist sein 
Geld wohl nicht wert.

von Chuck Miller (Gast)


Lesenswert?

Deine Variante 1 beruecksichtigt ueberhaupt nicht, was denn in 
"Transistoren" gemessen die beste Implementierung waere. Bsp.: Ein 
4-fach NAND ist schneller als ein 4-fach NOR (in heutiger CMOS 
Implementierung), das hat zu tun mit der Ladungstraegerbeweglichkeit und 
anderen physikalischen Effekten. Auch koennte bei Variante 1 evtl. ein 
XOR oder XNOR Transistoren und damit Flaeche/Laufzeit sparen...

Variante 2 sollte ein halbwegs normaler Synthesizer optimieren koennen, 
ein danach folgender Mapper (auf die Zieltechnologie) macht den Rest. 
Beim FPGA halt typischerweise eine 4/5/6-Bit LUT (SRAM 1Bitx16/32/64). 
Also wirst du eigentlich immer Variante 2 hinschreiben wollen, weil: 
Besser lesbar!

Es ist aber durchaus interessant, wenn man sich mal so manche High-Level 
Statements auf Gatterebene runterbricht. Es hilft auch, die Komplexitaet 
eines Designs abschaetzen zu koennen. Und falls du nicht FPGA, sondern 
ASIC mit Standard-Zell machst, kommst du um solche Ueberlegungen eh' 
nicht rum...
Ein Klassiker ist das n-fach AND-OR, der Multiplexer. Dass der in 
Wirklichkeit im ASIC immer auf NAND runtergebrochen wird sollte man mal 
gesehen & verstanden haben.

von Vancouver (Gast)


Lesenswert?

Die Logiksynthese wird beide Varianten zu einer Netzliste synthetisieren 
und diese anschließend optimieren. Dabei wird jeweils eine Architektur 
herauskommen, die für die Zieltechnologie am besten geeignet ist (außer 
du verbietest der Synthese ausdrücklich, etwas zu optimieren). Von 
deinen mühevoll hergeleiteten ORs und NOTs wird dabei vermutlich nichts 
übrig bleiben.
Insofern ist Variante 2 die weitaus bessere, weil sie einfacher zu 
verstehen und zu warten ist. Es macht überhaupt keinen Sinn, den Tools 
irgendwelche Optimierungsaufgaben anbnehmen zu wollen -  die können das 
besser als du. Also schreib deinen Code auf die einfachste Art und Weise 
und lass die Synthese etwas Brauchbares draus backen. Nur wenn das nicht 
klappt (was manchmal vorkommt), musst du ihr unter die Arme greifen.

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


Angehängte Dateien:

Lesenswert?

Derek C. schrieb:
> ich habe zwei Prozesse geschrieben, die genau das selbe tun
Nein, das tun sie nicht.
Siehe Screenshots der Waveform von Variante1.png und Variante2.png.

Vancouver schrieb:
> Von deinen mühevoll hergeleiteten ORs und NOTs wird dabei vermutlich
> nichts übrig bleiben.
> Insofern ist Variante 2 die weitaus bessere,
Besser wäre "im Prinzip" die Variante 1, bei der ganz offensichtlich die 
"others" Bedingung wegfällt. Somit kann der Synthesizer in den 
undefinierten Zuständen "1010"..."1111" beliebige Bitmuster erzeugen, 
statt dort definierte "1111111" im "Error"-Fall ausgeben zu müssen. 
Deshalb hat der Synthesizer bei Variante 1 "im Prinzip" mehr 
Freiheitsgrade zur Optimierung.

Allerdings ist das hier schnurzegal: weil jedes einzelne Segment nur 4 
Eingänge hat und diese 4 Eingänge exakt auf eine 4er-LUT passt, werden 
in beiden Fällen für 8 Ausgänge in beiden Fällen genau 8 LUTs gebraucht.

> weil sie einfacher zu verstehen und zu warten ist.
Dann würde ich gleich auf die Variante mit einem "segment" Array und 
"number" als Index gehen. Damit kann ich mir das ganze unnötig 
ausschweifende Process-Gehampel sparen:
1
type segment_typ is array (0 to 15) of std_logic_vector(7 downto 0); 
2
constant segment : segment_typ :=(
3
"11000000","11111001","10100100","10110000",
4
"10011001","10010010","10000010","11111000",
5
"10000000","10010000","11111111","11111111",
6
"11111111","11111111","11111111","11111111");
7
:
8
:
9
   segments <= segment(to_integer(unsigned(number)));
Diese Variante braucht wie zu erwarten ebenfalls 8 LUTs, die Waveform 
habe ich als VarianteArray.png angehängt.

Das kann man übrigens einfach selber mal ausprobieren und sich dabei die 
Synthesizer-Logs und den RTL-Schaltplan mal genauer anschauen. Den dann 
auftretenden Effekt nennt man "Lernen"...  ;-)

: Bearbeitet durch Moderator
von svedisk ficklampa (Gast)


Lesenswert?

Damit kann ich mir das ganze unnötig ausschweifende Process-Gehampel
und ausschweifende und eklige Typkonvertierungen sparen:
1
  with number select
2
  segments <=  "11000000"  when "0000",
3
      "11111001"  when "0001",
4
      "10100100"  when "0010",
5
      "10110000"  when "0011",
6
      "10011001"  when "0100",
7
      "10010010"  when "0101",
8
      "10000010"  when "0110",
9
      "11111000"  when "0111",
10
      "10000000"  when "1000",
11
      "10010000"  when "1001",
12
      "11111111"  when "1010",
13
      "11111111"  when "1011",
14
      "11111111"  when "1100",
15
      "11111111"  when "1101",
16
      "11111111"  when "1110",
17
      "11111111"  when "1111";

Ein Wunder das der aus dem Array keinen ROM instanziiert hat!

von daniel__m (Gast)


Lesenswert?

Lothar M. schrieb:
> Diese Variante braucht wie zu erwarten ebenfalls 8 LUTs

Hmm, bei mir sind es immer 7 LUTs (MSB ist immer '1').

Lothar M. schrieb:
> Variante 1, bei der ganz offensichtlich die
> "others" Bedingung wegfällt.

Alternativ könnte man in der Array-Variante die "11111111" Einträge mit 
"--------" modellieren (falls wirklich nur 10 Einträge genutzt werden). 
Xilinx zumindest nutzt es tatsächlich als "Don't Care" und setzt ein, 
was ihm optimal erscheint. Und, die Simulation sieht besser aus. Man 
erkennt sofort, wenn doch ungültige Nummern kommen.

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


Lesenswert?

daniel__m schrieb:
> Hmm, bei mir sind es immer 7 LUTs (MSB ist immer '1').
Ich habs mit Lattice Diamond auf einem MachXO2 ausprobiert.

svedisk ficklampa schrieb:
> Ein Wunder das der aus dem Array keinen ROM instanziiert hat!
Eine LUT ist ein 16x1-ROM. Acht LUTs ergeben ein 16x8 ROM. Dafür einen 
vorbelegten RAM-Block mit x kBit zu nehmen, wäre offensichtlicher 
Overkill. Und zudem bräuchte ein zu einem ROM umfunktionierter RAM-Block 
noch einen Takt.

von svedisk ficklampa (Gast)


Angehängte Dateien:

Lesenswert?

> Lattice Diamond

Ja, ein Grund mehr bei Altera zu bleiben.

von Gustl B. (-gb-)


Lesenswert?

Hast du das gemalt oder wurde das automatisch erzeugt? Diese 3-Input 
LUTs finde ich etwas seltsam. Nach dem Place und Route sieht das 
bestimmt anders aus.

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


Angehängte Dateien:

Lesenswert?

svedisk ficklampa schrieb:
> Ja, ein Grund mehr bei Altera zu bleiben.
Ich sehe da kein ROM...

> Ja, ein Grund mehr bei Altera zu bleiben.
Oder doch ein Grund weniger, bei Altera zu bleiben, denn wie vermutet 
bleiben auch bei Diamond zum Schluss in der Technology Schematic nur 7 
LUTs übrig.

von C.A. Rotwang (Gast)


Lesenswert?

qwertzuiopü+ schrieb:
> Auf einem FPGA würde ich davon ausgehen, dass die erste Variante in die
> zweite umgewandelt wird, da ja ein FPGA aus LUTs besteht.

Nicht nur und nicht jeder FPGA! Bspw. xilinx hat neben den LUT's 
einzelne Gatter und Multiplexor die zur Implementierung von Multiplexern 
heranzgezogen werden können.
Insbesonders bei "verteilten Multiplexern" ist das von Vorteil:

https://www.xilinx.com/support/documentation/white_papers/wp274.pdf

https://www.xilinx.com/support/documentation/application_notes/xapp522-mux-design-techniques.pdf

von svedisk ficklampa (Gast)


Lesenswert?

> automatisch erzeugt?

[x]

> Diese 3-Input LUTs finde ich etwas seltsam.

Jo mei, wenn der Term halt nur von 3 abhaengt.
Er zeigt ja die zugehoerige Kombinatorik im Kasterl.

von Duke Scarring (Gast)


Lesenswert?

Derek C. schrieb:
> Welche Variante würde man praktisch der
> anderen vorziehen?
Ich bevorzuge die (maximal) lesbare Form. Optimieren kann man immer 
noch, wenn es tatsächlich nötig werden sollte.
1
library ieee;
2
use ieee.std_logic_1164.all;
3
use ieee.numeric_std.all;
4
5
entity bin_to_segment is
6
    port
7
    (
8
        number      : in  unsigned( 3 downto 0);
9
        segments    : out unsigned( 7 downto 0)
10
    );
11
end entity bin_to_segment;
12
13
14
architecture rtl of bin_to_segment is
15
16
    constant SEG_A      : unsigned(7 downto 0) := "00000001";
17
    constant SEG_B      : unsigned(7 downto 0) := "00000010";
18
    constant SEG_C      : unsigned(7 downto 0) := "00000100";
19
    constant SEG_D      : unsigned(7 downto 0) := "00001000";
20
    constant SEG_E      : unsigned(7 downto 0) := "00010000";
21
    constant SEG_F      : unsigned(7 downto 0) := "00100000";
22
    constant SEG_G      : unsigned(7 downto 0) := "01000000";
23
    constant SEG_DP     : unsigned(7 downto 0) := "10000000";
24
25
    constant DIGIT_0    : unsigned(7 downto 0) := (SEG_A + SEG_B + SEG_C + SEG_D + SEG_E + SEG_F        );
26
    constant DIGIT_1    : unsigned(7 downto 0) := (        SEG_B + SEG_C                                );
27
    constant DIGIT_2    : unsigned(7 downto 0) := (SEG_A + SEG_B +         SEG_D + SEG_E +         SEG_G);
28
    constant DIGIT_3    : unsigned(7 downto 0) := (SEG_A + SEG_B + SEG_C + SEG_D +                 SEG_G);
29
    constant DIGIT_4    : unsigned(7 downto 0) := (        SEG_B + SEG_C +                 SEG_F + SEG_G);
30
    constant DIGIT_5    : unsigned(7 downto 0) := (SEG_A +         SEG_C + SEG_D +         SEG_F + SEG_G);
31
    constant DIGIT_6    : unsigned(7 downto 0) := (SEG_A +         SEG_C + SEG_D + SEG_E + SEG_F + SEG_G);
32
    constant DIGIT_7    : unsigned(7 downto 0) := (SEG_A + SEG_B + SEG_C                                );
33
    constant DIGIT_8    : unsigned(7 downto 0) := (SEG_A + SEG_B + SEG_C + SEG_D + SEG_E + SEG_F + SEG_G);
34
    constant DIGIT_9    : unsigned(7 downto 0) := (SEG_A + SEG_B + SEG_C + SEG_D +         SEG_F + SEG_G);
35
    constant ALL_OFF    : unsigned(7 downto 0) := (others => '0');
36
37
begin
38
39
  with to_integer( number) select
40
  segments <= 
41
      DIGIT_0 when 0,
42
      DIGIT_1 when 1,
43
      DIGIT_2 when 2,
44
      DIGIT_3 when 3,
45
      DIGIT_4 when 4,
46
      DIGIT_5 when 5,
47
      DIGIT_6 when 6,
48
      DIGIT_7 when 7,
49
      DIGIT_8 when 8,
50
      DIGIT_9 when 9,
51
      ALL_OFF when others;
52
53
end architecture rtl;

Außerdem versuche ich im Design alle Signale als high-Aktiv zu behandeln 
(1 = AN). Erst an der Schnittstelle nach außen wird ggf. invertiert, um 
- wie hier - low-Aktive LED-Segmente anzusteuern.

Duke

von Josef G. (bome) (Gast)


Lesenswert?

Ich hab auch noch eine Version:
1
signal zahl :                  std_logic_vector(3 downto 0);
2
signal wert :                  std_logic_vector(7 downto 0);    --(.GFEDCBA) 
3
4
type feld is array(0 to 15) of std_logic_vector(7 downto 0);
5
6
constant tabelle : feld := (x"3f",x"06",x"5b",x"4f",x"66",x"6d",x"7d",x"07",
7
                            x"7f",x"6f",x"77",x"7c",x"39",x"5e",x"79",x"71");
8
9
wert <= tabelle(to_integer(unsigned(zahl)));

Beitrag "Re: 1 Byte in dual 7 Segment Anzeige übersetzen"

Ich hatte da immer an "distributed ROM" gedacht.
Dass das tatsächlich nur 8 oder 7 LUTs sind,
war mir bisher nicht bewusst.

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.