Pisząc VHDL, zdecydowanie polecam użycie std_logic_vector (slv) zamiast liczby całkowitej (int) dla SIGNALS . (Z drugiej strony, użycie int dla ogólnych, niektóre stałe i niektóre zmienne mogą być bardzo przydatne.) Mówiąc prosto, jeśli deklarujesz sygnał typu int lub musisz określić zakres dla liczby całkowitej, prawdopodobnie robisz to coś źle.
Problem z int polega na tym, że programista VHDL nie ma pojęcia, czym jest wewnętrzna logiczna reprezentacja int, więc nie możemy z tego skorzystać. Na przykład, jeśli zdefiniuję liczbę całkowitą z zakresu od 1 do 10, nie mam pojęcia, jak kompilator koduje te wartości. Mam nadzieję, że będzie zakodowany jako 4 bity, ale nie wiemy zbyt wiele poza tym. Jeśli możesz sondować sygnały wewnątrz układu FPGA, może być zakodowany jako „0001” na „1010” lub zakodowany jako „0000” na „1001”. Możliwe jest również, że jest zakodowany w sposób, który nie ma dla nas żadnego sensu.
Zamiast tego powinniśmy po prostu użyć slv zamiast int, ponieważ wtedy mamy kontrolę nad kodowaniem, a także bezpośredni dostęp do poszczególnych bitów. Posiadanie bezpośredniego dostępu jest ważne, jak zobaczycie później.
Moglibyśmy po prostu rzucić int na slv, ilekroć potrzebujemy dostępu do poszczególnych bitów, ale robi się to naprawdę bałagan, bardzo szybko. To tak, jakby uzyskać najgorsze z obu światów zamiast najlepszych z obu światów. Twój kod będzie trudny do zoptymalizowania przez kompilator i prawie niemożliwy do odczytania. Nie polecam tego.
Tak więc, jak powiedziałem, przy pomocy slv masz kontrolę nad kodowaniem bitów i bezpośredni dostęp do bitów. Co możesz z tym zrobić? Pokażę ci kilka przykładów. Powiedzmy, że musisz wyprowadzać impuls raz na 4 294 000 000 zegarów. Oto jak zrobiłbyś to z int:
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;
I ten sam kod przy użyciu 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;
Większość tego kodu jest identyczna między int i slv, przynajmniej w sensie wielkości i szybkości wynikowej logiki. Oczywiście jeden się odlicza, a drugi odlicza, ale to nie jest ważne w tym przykładzie.
Różnica polega na „ważnej linii”.
W przykładzie int spowoduje to 32-wejściowy komparator. W przypadku 4-wejściowych LUT, których używa Xilinx Spartan-3, będzie to wymagało 11 LUT i 3 poziomów logiki. Niektóre kompilatory mogą konwertować to na odejmowanie, które wykorzysta łańcuch przenoszenia i obejmie równowartość 32 LUT, ale może działać szybciej niż 3 poziomy logiki.
W przykładzie SLV nie ma porównania 32-bitowego, więc jest to „zero LUT, zero poziomów logiki”. Jedyną karą jest to, że nasz licznik to jeden dodatkowy bit. Ponieważ dodatkowe taktowanie dla tego dodatkowego bitu licznika znajduje się w łańcuchu przenoszenia, występuje dodatkowe „prawie zerowe” opóźnienie taktowania.
Oczywiście jest to skrajny przykład, ponieważ większość ludzi nie używałaby 32-bitowego licznika w ten sposób. Dotyczy to mniejszych liczników, ale różnica będzie mniej dramatyczna, choć nadal znacząca.
Jest to tylko jeden przykład wykorzystania slv w stosunku do int, aby uzyskać szybsze synchronizowanie. Istnieje wiele innych sposobów wykorzystania slv - potrzeba tylko trochę wyobraźni.
Aktualizacja: Dodano elementy, aby odpowiedzieć na komentarze Martina Thompsona dotyczące używania int z „if (count-1) <0”
(Uwaga: Zakładam, że miałeś na myśli „if count <0”, ponieważ dzięki temu byłby bardziej równoważny z moją wersją slv i usunąłby potrzebę dodatkowego odejmowania.)
W niektórych okolicznościach może to generować zamierzoną implementację logiki, ale nie gwarantuje się, że będzie działać przez cały czas. Będzie to zależeć od twojego kodu i tego, jak kompilator koduje wartość int.
W zależności od kompilatora i tego, jak określisz zakres wartości int, jest całkiem możliwe, że wartość int równa zero nie koduje wektora bitowego „0000 ... 0000”, kiedy przekształca się w logikę FPGA. Aby Twoja odmiana zadziałała, musi zakodować ją jako „0000 ... 0000”.
Załóżmy na przykład, że definiujesz wartość int w zakresie od -5 do +5. Oczekujesz, że wartość 0 zostanie zakodowana w 4 bitach, takich jak „0000”, i +5 jako „0101” i -5 jako „1011”. Jest to typowy schemat kodowania z uzupełnieniem dwójkowym.
Ale nie zakładaj, że kompilator będzie używał komplementu dwóch. Chociaż jest to niezwykłe, uzupełnienie jednego może skutkować „lepszą” logiką. Lub kompilator może zastosować rodzaj „stronniczego” kodowania, w którym -5 jest kodowane jako „0000”, 0 jako „0101”, a +5 jako „1010”.
Jeśli kodowanie int jest „poprawne”, kompilator prawdopodobnie wywnioskuje, co zrobić z bitem przeniesienia. Ale jeśli jest niepoprawny, wynikowa logika będzie okropna.
Możliwe, że użycie int w ten sposób może spowodować rozsądną wielkość logiczną i szybkość, ale nie jest to gwarancją. Przejście na inny kompilator (na przykład XST na Synopsis) lub przejście na inną architekturę FPGA może spowodować, że coś złego się stanie.
Unsigned / Signed vs. slv to kolejna debata. Możesz podziękować komitetowi rządu USA za udostępnienie nam tak wielu opcji w VHDL. :) Używam slv, ponieważ jest to standard interfejsu między modułami i rdzeniami. Poza tym i niektórymi innymi przypadkami symulacji, nie sądzę, aby korzystanie z SLV nad podpisanymi / niepodpisanymi było ogromną korzyścią. Nie jestem również pewien, czy podpisane / niepodpisane obsługują potrójne sygnały.