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.)?
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.
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 unsigned
Typ heutzutage nicht auch verwenden :)count-1
, da dies das Carry/Borrow-Bit auf die gleiche Weise wie Ihr erstes Beispiel herleitet.)Ich habe den von zwei anderen Postern vorgeschlagenen Code sowohl in als vector
auch integer
in 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.
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;
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;
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;
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 vector
Version und 64 LUTs für die integer
Version 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.
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;
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;
Sie müssen durch einige Reifen springen, um zu vermeiden, dass Sie nur to_unsigned
Bits 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;
Codetechnisch ist die vector
Version 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!
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 integer
s 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.
variable c : unsigned(32 downto 0);
... ist das nicht c
eine 33-Bit-Variable?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.
Klatsch