Wann ist es besser, VECTOR-Darstellungen als INTEGERs zu verwenden?

Im Kommentar-Thread zu einer Antwort auf diese Frage: Falsche Ausgaben in VHDL-Entität hieß es:

„Mit Integern haben Sie keine Kontrolle oder Zugriff auf die interne Logikdarstellung im FPGA, während Sie mit SLV Tricks wie die effiziente Nutzung der Übertragskette ausführen können.“

Also, unter welchen Umständen fanden Sie es besser, mit einem Vektor der Bitdarstellung zu codieren, als mit Integer s, um auf die interne Darstellung zuzugreifen? Und welche Vorteile haben Sie gemessen (in Bezug auf Chipfläche, Taktfrequenz, Verzögerung usw.)?

Ich denke, es ist etwas schwer zu messen, da es anscheinend nur eine Frage der Kontrolle über die Low-Level-Implementierung ist.

Antworten (4)

Beim Schreiben von VHDL empfehle ich dringend, std_logic_vector (slv) anstelle von integer (int) für SIGNALS zu verwenden . (Auf der anderen Seite kann die Verwendung von int für Generika, einige Konstanten und einige Variablen sehr nützlich sein.) Einfach ausgedrückt, wenn Sie ein Signal vom Typ int deklarieren oder einen Bereich für eine Ganzzahl angeben müssen, tun Sie dies wahrscheinlich Stimmt etwas nicht.

Das Problem mit int ist, dass der VHDL-Programmierer keine Ahnung hat, was die interne logische Darstellung von int ist, und wir sie daher nicht nutzen können. Wenn ich beispielsweise ein Int im Bereich von 1 bis 10 definiere, habe ich keine Ahnung, wie der Compiler diese Werte codiert. Hoffentlich wäre es mit 4 Bit codiert, aber darüber hinaus wissen wir nicht viel. Wenn Sie die Signale im FPGA prüfen könnten, könnten sie als „0001“ bis „1010“ oder als „0000“ bis „1001“ codiert sein. Es ist auch möglich, dass es auf eine Weise codiert ist, die für uns Menschen absolut keinen Sinn ergibt.

Stattdessen sollten wir einfach slv statt int verwenden, denn dann haben wir die Kontrolle über die Kodierung und haben auch direkten Zugriff auf die einzelnen Bits. Der direkte Zugriff ist wichtig, wie Sie später sehen werden.

Wir könnten einfach ein int an slv werfen, wenn wir Zugriff auf die einzelnen Bits benötigen, aber das wird sehr schnell sehr chaotisch. Das ist, als würde man das Schlechteste aus beiden Welten bekommen, anstatt das Beste aus beiden Welten. Ihr Code wird für den Compiler schwierig zu optimieren und für Sie fast unmöglich zu lesen sein. Ich empfehle das nicht.

Also, wie gesagt, mit slv hat man die Kontrolle über die Bitkodierungen und direkten Zugriff auf die Bits. Was kann man also damit machen? Ich zeige Ihnen ein paar Beispiele. Nehmen wir an, Sie müssen alle 4.294.000.000 Takte einmal einen Impuls ausgeben. So würden Sie dies mit int tun:

signal count :integer range 0 to 4293999999;  -- a 32 bit integer

process (clk)
begin
  if rising_edge(clk) then
    if count = 4293999999 then  -- The important line!
      count <= 0;
      pulse <= '1';
    else
      count <= count + 1;
      pulse <= '0';
    end if;
  end if;
end process;

Und der gleiche Code mit slv:

use ieee.numeric_std.all;
signal count :std_logic_vector (32 downto 0);  -- a 33 bit integer, one extra bit!

process (clk)
begin
  if rising_edge(clk) then
    if count(count'high)='1' then   -- The important line!
      count <= std_logic_vector(4293999999-1,count'length);
      pulse <= '1';
    else
      count <= count - 1;
      pulse <= '0';
    end if;
  end if;
end process;

Der größte Teil dieses Codes ist zwischen int und slv identisch, zumindest im Sinne der Größe und Geschwindigkeit der resultierenden Logik. Natürlich zählt der eine aufwärts und der andere abwärts, aber das ist für dieses Beispiel nicht wichtig.

Der Unterschied liegt in "der wichtigen Linie".

Mit dem int-Beispiel führt dies zu einem Komparator mit 32 Eingängen. Mit 4-Eingangs-LUTs, die der Xilinx Spartan-3 verwendet, werden 11 LUTs und 3 Logikebenen benötigt. Einige Compiler wandeln dies möglicherweise in eine Subtraktion um, die die Übertragskette verwendet und das Äquivalent von 32 LUTs umfasst, aber möglicherweise schneller als 3 Logikebenen ausgeführt wird.

Beim slv-Beispiel gibt es keinen 32-Bit-Vergleich, also "Null-LUTs, Null-Logikpegel". Die einzige Strafe ist, dass unser Zähler ein zusätzliches Bit ist. Da das zusätzliche Timing für dieses zusätzliche Bit des Zählers vollständig in der Übertragskette liegt, gibt es eine zusätzliche Timing-Verzögerung von "nahezu null".

Dies ist natürlich ein extremes Beispiel, da die meisten Leute einen 32-Bit-Zähler nicht auf diese Weise verwenden würden. Es gilt für kleinere Zähler, aber der Unterschied ist weniger dramatisch, wenn auch immer noch signifikant.

Dies ist nur ein Beispiel dafür, wie man slv über int verwendet, um ein schnelleres Timing zu erreichen. Es gibt viele andere Möglichkeiten, slv zu nutzen – es braucht nur etwas Vorstellungskraft.

Update: Dinge hinzugefügt, um Martin Thompsons Kommentare zur Verwendung von int mit "if (count-1) < 0" zu behandeln

(Hinweis: Ich nehme an, Sie meinten "if count<0", da dies meine slv-Version äquivalenter machen und die Notwendigkeit für diese zusätzliche Subtraktion beseitigen würde.)

Unter bestimmten Umständen kann dies die beabsichtigte Logikimplementierung erzeugen, aber es ist nicht garantiert, dass es immer funktioniert. Dies hängt von Ihrem Code ab und davon, wie Ihr Compiler den int-Wert codiert.

Abhängig von Ihrem Compiler und davon, wie Sie den Bereich Ihres int angeben, ist es durchaus möglich, dass ein int-Wert von Null nicht in einen Bitvektor von "0000...0000" codiert wird, wenn er in die FPGA-Logik gelangt. Damit Ihre Variation funktioniert, muss sie in "0000...0000" kodiert werden.

Nehmen wir zum Beispiel an, Sie definieren ein int so, dass es einen Bereich von -5 bis +5 hat. Sie erwarten, dass ein Wert von 0 in 4 Bits wie "0000" und +5 als "0101" und -5 als "1011" codiert wird. Dies ist das typische Zweierkomplement-Codierungsschema.

Aber gehen Sie nicht davon aus, dass der Compiler das Zweierkomplement verwenden wird. Obwohl es ungewöhnlich ist, könnte die Einerkomplementierung zu einer "besseren" Logik führen. Oder der Compiler könnte eine Art "voreingenommene" Codierung verwenden, bei der -5 als "0000", 0 als "0101" und +5 als "1010" codiert wird.

Wenn die Codierung von int "korrekt" ist, wird der Compiler wahrscheinlich ableiten, was mit dem Carry-Bit zu tun ist. Aber wenn es falsch ist, wird die resultierende Logik schrecklich sein.

Es ist möglich, dass die Verwendung eines int auf diese Weise zu einer angemessenen logischen Größe und Geschwindigkeit führt, dies ist jedoch keine Garantie. Der Wechsel zu einem anderen Compiler (z. B. XST zu Synopsis) oder der Wechsel zu einer anderen FPGA-Architektur könnte dazu führen, dass genau das Falsche passiert.

Unsigned/Signed vs. slv ist eine weitere Debatte. Sie können dem US-Regierungsausschuss dafür danken, dass er uns so viele Optionen in VHDL gegeben hat. :) Ich verwende slv, weil dies der Standard für die Schnittstelle zwischen Modulen und Kernen ist. Abgesehen davon und in einigen anderen Fällen in Simulationen glaube ich nicht, dass die Verwendung von slv gegenüber signiert/unsigniert einen großen Vorteil hat. Ich bin mir auch nicht sicher, ob signierte/unsignierte Signale mit drei Zuständen unterstützt werden.

David, diese Codefragmente sind nicht gleichwertig. Man zählt von Null bis zu einer beliebigen Zahl hoch (mit einem teuren Vergleichsoperator); der andere zählt von einer beliebigen Zahl auf Null herunter. Sie können beide Algorithmen entweder mit Ganzzahlen oder Vektoren schreiben, und Sie erhalten schlechte Ergebnisse, wenn Sie gegen eine beliebige Zahl zählen, und gute Ergebnisse, wenn Sie gegen Null zählen. Beachten Sie, dass Softwareentwickler auch auf Null zählen würden, wenn sie etwas mehr Leistung aus einer heißen Schleife herausholen müssen.
@Philippe Sie sind insofern gleichwertig, als sie beide nach jeder beliebigen Anzahl von Takten einen Impuls ausgeben. Sie könnten die Zählrichtung des int-Beispiels umkehren und die Logik-/Geschwindigkeitsverbesserungen der slv-Version würden weiterhin gelten.
Wie Philippe bin ich nicht davon überzeugt, dass dies ein gültiger Vergleich ist. Wenn das Integer-Beispiel heruntergezählt und verwendet wird if (count-1) < 0, würde der Synthesizer das Carry-Out-Bit ableiten und ungefähr die gleiche Schaltung wie Ihr slv-Beispiel erzeugen. Sollten wir den unsignedTyp heutzutage nicht auch verwenden :)
@MartinThompson Ich habe meiner Antwort etwas hinzugefügt, um auf Ihren Kommentar einzugehen.
Wäre es fair zu sagen, dass bei Verwendung von Vektoren von den Synthesewerkzeugen erwartet wird, dass sie die bestimmte angegebene Darstellung verwenden, unabhängig davon, ob sie mehr oder weniger effizient als eine andere Darstellung ist, während das Werkzeug bei Verwendung von Ganzzahlen frei ist alles zu verwenden Darstellung, die es für richtig hält, die es ihm theoretisch ermöglicht, die optimale auszuwählen, aber in der Praxis eher mögliche Optimierungen nicht ausnutzt?
Auch im Hinblick auf die Frage, ob es besser ist, Zählen-auf-N-Zurücksetzen-auf-Null oder Zählen-auf-Null-Zurücksetzen-auf-N zu verwenden, können unterschiedliche Siliziumtechnologien jeden Ansatz bevorzugen, insbesondere wenn N konstant ist . Selbst wenn N variabel ist, können einige Siliziumtechnologien immer noch den Vergleich begünstigen.
@supercat Ja. SLV bringt den Optimierungsball in den Court der Ingenieure und NIMMT IHN der Eng. Im Moment und in absehbarer Zukunft ist ein Ingenieur besser in der Optimierung, weil der Ingenieur Designentscheidungen treffen kann, die über das hinausgehen, was der Compiler tun kann (wie z. B. den Wechsel vom Aufwärts- zum Abwärtszählen). Sicherlich ist es bei LUT-basierten FPGAs mit dedizierten Carry-Ketten besser, "auf minus eins zu zählen, auf N minus zwei zurückzusetzen", obwohl es mit ziemlicher Sicherheit Siliziumarchitekturen gibt, mit denen ich nicht vertraut bin würde bevorzugen Ein anderer Versuch.
@DavidKessner: Eine Änderung, die in der Programmierbranche stattgefunden hat, und ich wäre nicht überrascht, wenn die Siliziumentwicklung folgen würde, ist, dass viel Code auf einer Vielzahl von Maschinen verwendet wird, so dass eine Optimierung für einen eine Pessimierung wäre für Ein weiterer. Ein Programmierer, der die Kontrolle über die Optimierung übernimmt, könnte die Leistung auf der jeweiligen Zielplattform verbessern, aber Compiler haben sich bis zu dem Punkt verbessert, dass die Gesamtleistung auf einer Vielzahl von Plattformen oft am besten ist, wenn man den Compiler viele optimierungsbezogene Probleme handhaben lässt.
@DavidKessner: Es ist eigentlich ein unangenehmer Punkt, da es immer noch Zeiten gibt, in denen Compiler definitiv "Hilfe brauchen", und da die Bemühungen eines Compilers, Dinge zu optimieren, dazu führen können, dass scheinbar geringfügige Designänderungen das generierte radikal verändern Code; das kann in der Software ärgerlich sein, und ich würde erwarten, dass es auch in der Hardware ärgerlich sein könnte. Dennoch gibt es definitiv Fälle, in denen intelligente Logik-Compiler hilfreich sein könnten, wenn sie „intelligent“ genug wären. Zum Beispiel...
@DavidKessner: Angenommen, man versucht, ein serielles Ein- / Ausgabegerät zu entwerfen, um 64xN-Multiplikationen zu beschleunigen. Ein solches Gerät könnte einen 64-Bit-Akkumulator mit einer 64-Bit-Übertragskette verwenden, oder es könnte 64 Latches für den Akkumulator und 64 für gespeicherte Überträge oder 16 Gruppen von fünf Latches verwenden, die jeweils einen 4-Bit-Wert und einen gespeicherten Übertrag darstellen. usw. Die optimale Unterteilung würde stark von der Chiptopologie abhängen, und was für einen Chip optimal ist, würde auf einem anderen stark aufgebläht.
@supercat Software-Compiler hatten einen großen Trumpf: Die CPU-Geschwindigkeiten sind enorm gestiegen. Sicher, die Compiler optimieren besser, aber oft (aber nicht immer) ist handoptimierter Assembler immer noch besser. Natürlich schreibt heutzutage kaum noch jemand Code in Assembler, weil die CPU-Geschwindigkeit schnell genug ist, dass es wirtschaftlich keinen Sinn macht. FPGAs sind noch nicht ganz da, aber wir können träumen! Eines Tages werden wir dieses Thema als antiquiert betrachten, ähnlich wie das manuelle Aufrollen von Schleifen in Software antiquiert ist.
@DavidKessner: Assemblercode, der für eine bestimmte Maschine von Hand optimiert wird, ist besser als kompilierter Code, aber wenn eine neue Maschine herauskommt und Compiler-Autoren einen Codegenerator dafür erstellen, funktioniert häufig High-Level-Code, der für die frühere Maschine geschrieben wurde besser auf dem neuen (nach einer Neukompilierung) als der handgenerierte Assemblercode. Darüber hinaus bedeutet die Verbesserung der Just-in-Time-Compiler-Technologie, dass die "Neukompilierung" automatisch erfolgt, wenn versucht wird, den Code auf der neuen Maschine auszuführen.
@DavidKessner: Tut mir leid, aber Sie machen viele Behauptungen darüber, wie Synthesewerkzeuge ein schlechtes Ergebnis liefern könnten , aber keine Beweise. Meine Frage bezieht sich auf messbare Vorteile. (Und ich meinte count-1, da dies das Carry/Borrow-Bit auf die gleiche Weise wie Ihr erstes Beispiel herleitet.)
@MartinThompson, hier kann es zu Missverständnissen kommen. Was er dir zu sagen versucht. Es ist nicht so, dass er sich nicht sicher ist, dass sie existieren, es ist so, dass Sie sich auf keine dieser Funktionen verlassen können. Dies kann je nach der von Ihnen gewählten Toolchain geschehen. Das bedeutet, dass er Ihnen sagt, dass Sie sich mit einer Tatsache auseinandersetzen müssen, die Sie wirklich nicht wissen können, es sei denn, Sie verwenden Ihren Compiler. Dies ist in vielen verschiedenen Sprachen sehr verbreitet. Versuchen Sie, auf einem zufälligen Mikrocontroller eine Division durch 64 durchzuführen und die Zeit zu messen. Sie werden feststellen, dass wir Ihnen nicht sagen können, wie funktional es ist, wenn wir Ihren Compiler und Ihre Toolchain nicht kennen.
@Kortuk: Ich verstehe, was er und Sie sagen - ich habe selbst oft "auf das Seil gedrückt" der Werkzeuge. Ich schätze, was ich versuche in den Vordergrund zu rücken, ist, ob es sich lohnt, diese Art von „wirklich Low-Level-Optimierungs“-Tricks noch zu spielen. So wie C-Programmierer konstante Multiplikationen nicht mehr als Summen von Verschiebungen schreiben, jetzt, wo Compiler dies für Sie tun. Ich hoffe, jemand hat einige harte Daten ... aber wenn nicht, werde ich versuchen, einige zu generieren.
@MartinThompson Ich bin am besten mit einem Xilinx Spartan-3/6 und dem XST-Compiler vertraut. Für diese Kombination lohnen sich diese Optimierungstechniken. Mit einem anderen FPGA und/oder Compiler kann Ihre Laufleistung variieren. Es lohnt sich, das Experiment selbst zu machen, schon allein um die Erkenntnisse zu gewinnen. Ich habe dieses Experiment schon eine Weile nicht mehr gemacht, und leider habe ich keine Zeit, es noch einmal zu tun. Ansonsten würde ich meine Daten posten.
@DavidKessner Sie haben sicherlich eine gründliche und gut begründete Antwort gegeben, Sie haben meine +1. Ich muss allerdings fragen ... warum machst du dir Sorgen über die Optimierung des gesamten Designs? Wäre es nicht besser, sich auf die Codebereiche zu konzentrieren, die dies erfordern, oder sich aus Kompatibilitätsgründen auf SLVs für Schnittstellenpunkte (Entity-Ports) zu konzentrieren? Ich weiß, dass es mir bei den meisten meiner Designs nicht besonders wichtig ist, dass die LUT-Nutzung minimiert wird, solange sie das Timing erfüllt und zum Teil passt. Wenn ich besonders enge Einschränkungen habe, würde ich sicherlich mehr auf ein optimales Design achten, aber nicht als allgemeine Regel.
@AndrewKohlsmith Vieles von dem, was ich tue, geht in die Massenproduktion, wo die Kosten eine Rolle spielen. Das Design so zu optimieren, dass es in ein kleineres oder langsameres FPGA passt, ist wirtschaftlich sinnvoll. Außerdem liegt ein Großteil meines Codes in Form von Kernbibliotheken vor, die dann in Dutzenden anderer FPGAs verwendet und wiederverwendet werden. Während die Größe für dieses erste FPGA möglicherweise kein Problem darstellt, könnte dies für die folgenden FPGAs der Fall sein. Denken Sie jedoch daran, dass eine kleine und effiziente Logik im Allgemeinen auch die schnellste ist – sie gehen Hand in Hand.
@DavidKessner: "Einfach ausgedrückt, wenn Sie ein Signal vom Typ int deklarieren oder einen Bereich für eine Ganzzahl angeben müssen, machen Sie wahrscheinlich etwas falsch." - Ich denke, das ist Ansichtssache! Ganzzahlige Signale haben viele nützliche Anwendungen und ich mache auch dichte Low-Cost-Designs. Übrigens. Ich habe eine Antwort mit einigen Synthese-LUT-Anzahl/fmax-Zahlen hinzugefügt.
Ich bin ein bisschen überrascht von der Anzahl der positiven Stimmen zu dieser Antwort. @bit_vector@ ist sicherlich die richtige Abstraktionsebene für die Modellierung und Optimierung von Mikroarchitekturen, aber eine generelle Empfehlung gegen „High-Level“-Typen wie @integer@ für Signale und Port finde ich befremdlich. Ich habe genug verworrenen und unlesbaren Code aufgrund des Mangels an Abstraktion gesehen, um den Wert dieser Funktionen zu kennen, und wäre sehr traurig, wenn ich sie zurücklassen müsste.
@trondd Die positiven Stimmen sind darauf zurückzuführen, dass ich die Frage beantwortet habe: "Wann ist es besser, Vektoren als Ganzzahlen zu verwenden?" Wenn es um die Frage nach dem richtigen Zeitpunkt für die Verwendung von Integer-Typen ginge, erhalten Sie möglicherweise eine Antwort, die für Ihre Voreingenommenheit günstiger ist. Sie können diese Frage gerne mit Ihrer eigenen Antwort ergänzen.
Sie haben sicherlich einen gültigen Punkt in Bezug auf optimierte Mikroarchitekturen, aber der Grund, warum ich ihn in Frage stelle, ist, dass das von Ihnen bereitgestellte Beispiel von Martin Thompson gezeigt wird, dass es äquivalente Implementierungen mit beiden Alternativen gibt. Ich denke auch, dass die allgemeine Empfehlung gegen High-Level-Typen schlichtweg falsch ist. Die richtige Abstraktionsebene wäre ein guter allgemeiner Rat: Modellierung auf Bitebene auf einer Ebene, Typen auf höherer Ebene. Auch die Lesbarkeit spielt eine große Rolle, und das Beispiel „Teile durch 2**n“ von Martin ist ein hervorragendes Beispiel dafür, wo Vektoren bevorzugt werden.
@trondd Es war einmal, dass Leute Software in Assembler geschrieben haben. Es war das richtige Tool zur richtigen Zeit, weil Compiler nicht effizient genug Code generierten und unsere CPUs begrenzt waren. Dies hat sich in vielerlei Hinsicht geändert, und Assemblercode wird heutzutage viel weniger verwendet. Dasselbe gilt für programmierbare Logik, aber wir fangen gerade erst an, aus dem dunklen Zeitalter herauszukommen. Grundsätzlich stimme ich Ihnen zu, aber ich habe auch einen Job zu erledigen und kann mir Synplify Pro für 12.000 US-Dollar pro Jahr nicht leisten. Ich muss auch Code schreiben, der für viele Toolketten effizient ist. Eines Tages wird Ihre Utopie existieren, aber das ist nicht heute.
@david Hervorragende Bemerkungen. Es stimmt, dass wir uns im Vergleich zur Softwareentwicklung in vielerlei Hinsicht noch im Mittelalter befinden, aber aufgrund meiner Erfahrung mit Quartus Integrated Synthesis und Synplify denke ich, dass die Dinge nicht so schlimm sind. Sie sind durchaus in der Lage, viele Dinge wie Register-Retiming und andere Optimierungen zu handhaben, die die Leistung verbessern und gleichzeitig die Lesbarkeit aufrechterhalten. Ich bezweifle, dass die Mehrheit auf mehrere Toolchains und Geräte abzielt, aber für Ihren Fall verstehe ich die Anforderung nach dem kleinsten gemeinsamen Nenner :-).

Ich habe den von zwei anderen Postern vorgeschlagenen Code sowohl in als vectorauch integerin Form geschrieben, wobei ich darauf geachtet habe, dass beide Versionen so ähnlich wie möglich funktionieren.

Ich habe die Ergebnisse in der Simulation verglichen und dann mit Synplify Pro für Xilinx Spartan 6 synthetisiert. Die folgenden Codebeispiele wurden aus Arbeitscode eingefügt, sodass Sie sie mit Ihrem bevorzugten Synthesizer verwenden und sehen können sollten, ob er sich genauso verhält.


Abwärtszähler

Erstens der Abwärtszähler, wie von David Kessner vorgeschlagen:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity downcounter is
    generic (top : integer);
    port (clk, reset, enable : in  std_logic; 
         tick   : out std_logic);
end entity downcounter;

Vektorarchitektur:

architecture vec of downcounter is
begin
    count: process (clk) is
        variable c : unsigned(32 downto 0);  -- don't inadvertently not allocate enough bits here... eg if "integer" becomes 64 bits wide
    begin  -- process count
        if rising_edge(clk) then  
            tick <= '0';
            if reset = '1' then
                c := to_unsigned(top-1, c'length);
            elsif enable = '1' then
                if c(c'high) = '1' then
                    tick <= '1';
                    c := to_unsigned(top-1, c'length);
                else
                    c := c - 1;
                end if;
            end if;
        end if;
    end process count;
end architecture vec;

Ganzzahlige Architektur

architecture int of downcounter is
begin
    count: process (clk) is
        variable c : integer;
    begin  -- process count
        if rising_edge(clk) then  
            tick <= '0';
            if reset = '1' then
                c := top-1;
            elsif enable = '1' then
                if c < 0 then
                    tick <= '1';
                    c := top-1;
                else
                    c := c - 1;
                end if;
            end if;
        end if;
    end process count;
end architecture int;

Ergebnisse

In Bezug auf den Code scheint mir die Ganzzahl vorzuziehen, da sie die to_unsigned()Anrufe vermeidet. Ansonsten nicht viel Auswahl.

Wenn Sie es über Synplify Pro laufen lassen, top := 16#7fff_fffe#werden 66 LUTs für die vectorVersion und 64 LUTs für die integerVersion erzeugt. Beide Versionen machen viel Gebrauch von der Tragekette. Beide melden Taktraten von über 280MHz . Der Synthesizer ist durchaus in der Lage, eine gute Nutzung der Carry-Kette herzustellen - ich habe visuell mit dem RTL-Viewer überprüft, dass bei beiden eine ähnliche Logik erzeugt wird. Offensichtlich wird ein Aufwärtszähler mit Komparator größer sein, aber das wäre wieder dasselbe mit ganzen Zahlen und Vektoren.


Division durch 2**n Zähler

Vorgeschlagen von ajs410:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity clkdiv is
    port (clk, reset : in     std_logic;
        clk_2, clk_4, clk_8, clk_16  : buffer std_logic);
end entity clkdiv;

Vektorarchitektur

architecture vec of clkdiv is

begin  -- architecture a1

    process (clk) is
        variable count : unsigned(4 downto 0);
    begin  -- process
        if rising_edge(clk) then  
            if reset = '1' then
                count  := (others => '0');
            else
                count := count + 1;
            end if;
        end if;
        clk_2 <= count(0);
        clk_4 <= count(1);
        clk_8 <= count(2);
        clk_16 <= count(3);
    end process;

end architecture vec;

Ganzzahlige Architektur

Sie müssen durch einige Reifen springen, um zu vermeiden, dass Sie nur to_unsignedBits verwenden und dann entfernen, was eindeutig den gleichen Effekt wie oben erzeugen würde:

architecture int of clkdiv is
begin
    process (clk) is
        variable count : integer := 0;
    begin  -- process
        if rising_edge(clk) then  
            if reset = '1' then
                count  := 0;
                clk_2  <= '0';
                clk_4  <= '0';
                clk_8  <= '0';
                clk_16 <= '0';
            else
                if count < 15 then
                    count := count + 1;
                else
                    count := 0;
                end if;
                clk_2 <= not clk_2;
                for c4 in 0 to 7 loop
                    if count = 2*c4+1 then
                        clk_4 <= not clk_4;
                    end if;
                end loop; 
                for c8 in 0 to 3 loop
                    if count = 4*c8+1 then
                        clk_8 <= not clk_8;
                    end if;
                end loop; 
                for c16 in 0 to 1 loop
                    if count = 8*c16+1 then
                        clk_16 <= not clk_16;
                    end if;
                end loop; 
            end if;
        end if;
    end process;
end architecture int;

Ergebnisse

Codetechnisch ist die vectorVersion in diesem Fall deutlich besser!

In Bezug auf die Syntheseergebnisse produziert die Integer-Version (wie von ajs410 vorhergesagt) für dieses kleine Beispiel 3 zusätzliche LUTs als Teil der Komparatoren. Ich war zu optimistisch in Bezug auf den Synthesizer, obwohl er mit einem schrecklich verschleierten Stück Code arbeitet!


Andere Verwendungen

Vektoren sind ein klarer Gewinn, wenn Sie möchten, dass Arithmetik umläuft (Zähler können sogar als einzelne Zeile ausgeführt werden):

vec <= vec + 1 when rising_edge(clk);

vs

if int < int'high then 
   int := int + 1;
else
   int := 0;
end if;

obwohl zumindest aus diesem Code klar hervorgeht, dass der Autor einen Wrap-Around beabsichtigt hat.


Etwas, das ich nicht in Real-Code verwendet habe, aber darüber nachgedacht habe:

Das Merkmal "natürliches Umbrechen" kann auch zum "Berechnen durch Überläufe" verwendet werden. Wenn Sie wissen, dass die Ausgabe einer Kette von Additionen/Subtraktionen und Multiplikationen begrenzt ist, müssen Sie die hohen Bits der Zwischenberechnungen nicht speichern, da sie (im 2er-Komplement) "in der Wäsche" herauskommen. bis Sie zum Ausgang kommen. Mir wurde gesagt, dass dieses Papier einen Beweis dafür enthält, aber es sah für mich etwas schwerfällig aus, um eine schnelle Bewertung vorzunehmen! Theorie der Computeraddition und -überläufe - HL Garner

Die Verwendung von integers in dieser Situation würde Simulationsfehler verursachen, wenn sie verpackt werden, obwohl wir wissen, dass sie am Ende entpackt werden.


Und wie Philippe betonte, haben Sie keine andere Wahl, als Vektoren zu verwenden, wenn Sie eine Zahl benötigen, die größer als 2**31 ist.

Im zweiten Codeblock haben Sie variable c : unsigned(32 downto 0);... ist das nicht ceine 33-Bit-Variable?
@clabacchio: Ja, das ermöglicht den Zugriff auf das 'Carry-Bit', um den Wrap-Around zu sehen.

Mein Rat ist, beides auszuprobieren und sich dann die Synthese-, Karten- und Place-and-Route-Berichte anzusehen. Diese Berichte sagen Ihnen genau, wie viele LUTs jeder Ansatz verbraucht, sie sagen Ihnen auch, mit welcher maximalen Geschwindigkeit die Logik arbeiten kann.

Ich stimme David Kessner zu, dass Sie Ihrer Toolchain ausgeliefert sind und es keine "richtige" Antwort gibt. Synthese ist schwarze Magie, und der beste Weg, um zu wissen, was passiert ist, besteht darin, die erstellten Berichte sorgfältig und gründlich zu lesen. Mit Xilinx-Tools können Sie sogar in das FPGA hineinsehen, bis hin zur Programmierung jeder LUT, wie die Carry-Kette verbunden ist, wie die Switch-Fabric alle LUTs verbindet usw.

Stellen Sie sich als weiteres dramatisches Beispiel für Herrn Kessners Ansatz vor, dass Sie mehrere Taktfrequenzen bei 1/2, 1/4, 1/8, 1/16 usw. haben möchten. Sie könnten eine Ganzzahl verwenden, die jeden Zyklus konstant hochzählt. und dann mehrere Komparatoren gegen diesen ganzzahligen Wert haben, wobei jeder Komparatorausgang eine andere Taktteilung bildet. Abhängig von der Anzahl der Komparatoren könnte der Fanout unangemessen groß werden und anfangen, zusätzliche LUTs nur zum Puffern zu verbrauchen. Der SLV-Ansatz würde einfach jedes einzelne Bit des Vektors als Ausgabe nehmen.

Ein offensichtlicher Grund ist, dass signierte und unsignierte Werte größere Werte als die 32-Bit-Ganzzahl zulassen. Das ist ein Fehler im Design der VHDL-Sprache, der nicht wesentlich ist. Eine neue Version von VHDL könnte das beheben, indem ganzzahlige Werte benötigt werden, um beliebige Größen zu unterstützen (ähnlich wie BigInt von Java).

Abgesehen davon bin ich sehr daran interessiert, von Benchmarks zu hören, die für Integer anders abschneiden als für Vektoren.

Übrigens, Jan Decaluwe hat dazu einen netten Aufsatz geschrieben: Diese Ints sind fürs Zählen gemacht.

Danke Philippe (obwohl das keine "bessere durch Zugriff auf die interne Repräsentation"-Anwendung ist, was ich wirklich anstrebe ...)
Dieser Aufsatz ist nett, ignoriert aber vollständig die zugrunde liegende Implementierung und die daraus resultierende Logikgeschwindigkeit und -größe. Ich stimme dem meisten von dem zu, was Decaluwe sagt, aber er sagt nichts über die Ergebnisse der Synthese. Manchmal spielen die Ergebnisse der Synthese keine Rolle, und manchmal schon. Es ist also ein Ermessensspiel.
@David, ich stimme zu, dass Jan nicht ausführlich darauf eingeht, wie Synthesewerkzeuge auf ganze Zahlen reagieren. Aber nein, es ist kein Urteilsspruch. Sie können die Syntheseergebnisse messen und die Ergebnisse Ihres gegebenen Synthesewerkzeugs bestimmen. Ich denke, das OP meinte seine Frage als Herausforderung für uns, Codefragmente und Syntheseergebnisse zu erstellen, die einen Leistungsunterschied (falls vorhanden) zeigen.
@Philippe Nein, ich meinte, dass es ein Urteil ist, wenn Sie sich überhaupt für die Syntheseergebnisse interessieren. Nicht, dass die Syntheseergebnisse selbst eine Beurteilung darstellen.
@DavidKessner OK. Ich habe es falsch verstanden.