Коли акуратніше використовувати представлення VECTOR проти INTEGER?


11

У нитці коментарів щодо відповіді на це запитання: Неправильні результати в об'єкті VHDL було зазначено:

"З цілими числами у вас немає контролю або доступу до внутрішнього логічного представлення в FPGA, тоді як SLV дозволяє робити такі трюки, як ефективно використовувати ланцюжок переноски"

Отже, за яких обставин ви вважаєте, що більш чітко кодувати, використовуючи вектор представлення бітів, ніж використовувати цілі s для доступу до внутрішнього представлення? І якими перевагами ви оцінювали (щодо площі чіпа, тактової частоти, затримки тощо)?


Я думаю, що це щось важко виміряти, оскільки, мабуть, це лише питання контролю над низьким рівнем реалізації.
clabacchio

Відповіді:


5

Я написав код, запропонований двома іншими плакатами, як у формі, так vectorі integerобережно, щоб обидві версії працювали максимально схожим чином.

Я порівняв результати в моделюванні, а потім синтезував за допомогою націлювання Synplify Pro Xilinx Spartan 6. Зразки коду нижче вставляються з робочого коду, тож ви повинні мати можливість використовувати їх зі своїм улюбленим синтезатором і бачити, чи поводиться він так само.


Зниження

По-перше, зниження рівня, як запропонував Девід Кесснер:

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;

Результати

За кодом, ціле число здається мені кращим, оскільки воно уникає to_unsigned()дзвінків. Інакше не багато чого вибирати.

Запуск його через Synplify Pro з 66top := 16#7fff_fffe# виробляє LUT для vectorверсії та 64 LUT для integerверсії. Обидві версії широко використовують ланцюжок для перенесення. Обидва повідомляють тактові швидкості понад 280 МГц . Синтезатор цілком здатний налагодити хороше використання ланцюга переноски - я візуально перевірив з переглядачем RTL, що аналогічна логіка виробляється з обома. Очевидно, що лічильник порівняння з компаратором буде більшим, але це буде те саме, що і з цілими числами, і з векторами.


Ділення на 2 ** n лічильників

Запропоновано 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;

Цілісна архітектура

Вам доведеться перестрибувати кілька обручів, щоб не використовувати, to_unsignedа потім вибирати шматочки, які, очевидно, дадуть той же ефект, що і вище:

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;

Результати

У кодовому випадку в цьому випадку vectorверсія явно краща!

Що стосується результатів синтезу, для цього невеликого прикладу ціла версія (як передбачило ajs410) створює 3 додаткові LUT у складі компараторів, я надто оптимістично ставився до синтезатора, хоча він працює з жахливо заплутаним фрагментом коду!


Інші види використання

Вектори - це явний виграш, коли ви хочете, щоб арифметика оберталася (лічильники можна робити як єдиний рядок):

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

проти

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

хоча принаймні з цього коду видно, що автор задумав обгортати.


Щось я не використовував у реальному коді, але розмірковував:

Функція "природне обгортання" також може бути використана для "обчислення через переливи". Коли ви знаєте, що вихід ланцюжка додавання / віднімання та множення обмежений, вам не доведеться зберігати високі біти проміжних обчислень, оскільки (у 2-х доповненнях) він вийде «під час миття» до моменту, коли ви потрапите на вихід. Мені кажуть, що цей документ містить докази цього, але для швидкої оцінки я виглядав трохи щільно! Теорія комп’ютерного додавання та переливів - Х. Л. Гарнер

Використання integers у цій ситуації призведе до помилок моделювання, коли вони завернуться, хоча ми знаємо, що в кінці кінця вони розгорнуться.


І як зазначив Філіпп, коли вам потрібно число, більше 2 ** 31, вам не залишається іншого вибору, як використовувати вектори.


У другому кодовому блоці у вас є variable c : unsigned(32 downto 0);... чи не c33-бітна змінна?
clabacchio

@clabacchio: так, це дозволяє отримати доступ до "переносного біта", щоб побачити завершення.
Мартін Томпсон

5

При написанні VHDL, я настійно рекомендую використовувати std_logic_vector (SLV) замість цілого (INT) для сигналів . (З іншого боку, використання int для дженериків, деяких констант і деяких змінних може бути дуже корисним.) Простіше кажучи, якщо ви оголошуєте сигнал типу int або потрібно вказати діапазон для цілого числа, то ви, ймовірно, робите щось не так.

Проблема з int полягає в тому, що програміст VHDL поняття не має, що таке внутрішнє логічне подання int, і тому ми не можемо ним скористатися. Наприклад, якщо я визначаю int діапазону від 1 до 10, я поняття не маю, як компілятор кодує ці значення. Сподіваємось, це було б закодовано як 4 біти, але ми не знаємо багато іншого, ніж це. Якщо ви можете зондувати сигнали всередині FPGA, він може бути закодований як "0001" до "1010", або кодуватися як "0000" до "1001". Можливо також, що він закодований таким чином, що абсолютно не має сенсу для нас, людей.

Натомість нам слід просто використовувати slv замість int, оскільки тоді ми маємо контроль над кодуванням, а також маємо прямий доступ до окремих бітів. Важливий прямий доступ, як ви побачите далі.

Ми можемо просто кинути int на slv, коли нам потрібен доступ до окремих біт, але це стає справді безладним, дуже швидко. Це як отримати найгірше з обох світів замість найкращого з обох світів. Код буде важко оптимізувати компілятору і майже неможливо прочитати. Я не рекомендую цього.

Отже, як я вже сказав, з slv ви маєте контроль над кодуванням бітів і прямий доступ до бітів. То що ви можете зробити з цим? Я покажу вам кілька прикладів. Скажімо, що вам потрібно виводити імпульс раз на 4 294 000 000 годин. Ось як би це зробити за допомогою 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;

І той самий код за допомогою 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;

Більшість цього коду ідентична між int та slv, принаймні в сенсі розміру та швидкості отриманої логіки. Звичайно, один підраховує, а другий відраховує, але це не важливо для цього прикладу.

Різниця полягає у "важливій лінії".

З прикладом int, це призведе до порівняння з 32 входами. Із 4-вхідними LUT, які використовує Xilinx Spartan-3, для цього знадобиться 11 LUT та 3 рівня логіки. Деякі компілятори можуть перетворити це в віднімання, яке використовуватиме ланцюжок переносу і охоплює еквівалент 32 LUT, але може працювати швидше, ніж 3 рівня логіки.

У прикладі slv немає 32-бітного порівняння, тому це "нульові LUT, нульові рівні логіки". Єдине покарання полягає в тому, що наш лічильник - це один зайвий біт. Оскільки додаткові терміни для цього додаткового лічильника лічильників знаходяться у ланцюзі перенесення, існує «майже нульова» додаткова затримка часу.

Звичайно, це надзвичайний приклад, оскільки більшість людей не використовуватиме 32-бітний лічильник таким чином. Це стосується менших лічильників, але різниця буде менш драматичною, хоча все ще значною.

Це лише один приклад того, як використовувати slv over int, щоб отримати швидші терміни. Існує багато інших способів використання slv - для цього потрібна лише фантазія.

Оновлення: Додано матеріали для вирішення коментарів Мартіна Томпсона щодо використання int з "if (count-1) <0"

(Примітка. Я припускаю, що ви мали на увазі "якщо рахувати <0", оскільки це зробить його більш еквівалентним моїй версії slv і усуне необхідність у додатковому відніманні.)

За певних обставин це може генерувати передбачувану логічну реалізацію, але не гарантується, що вона буде працювати весь час. Це залежатиме від вашого коду та того, як ваш компілятор кодує значення int.

Залежно від вашого компілятора і того, як ви визначаєте діапазон вашого int, цілком можливо, що значення нуля нуля не кодує бітовий вектор "0000 ... 0000", коли він перетворює його в логіку FPGA. Щоб ваша варіація працювала, вона повинна кодувати "0000 ... 0000".

Наприклад, скажімо, ви визначаєте int, щоб мати діапазон від -5 до +5. Ви очікуєте, що значення 0 буде закодовано в 4 біти, як "0000", і +5 як "0101", а -5 - "1011". Це типова схема кодування двійок-доповнення.

Але не варто вважати, що компілятор буде використовувати подвійні доповнення. Хоча незвично, доповнення може призвести до "кращої" логіки. Або компілятор міг би використовувати якесь "упереджене" кодування, де -5 кодується як "0000", 0 - "0101", а +5 - "1010".

Якщо кодування int "правильне", компілятор, швидше за все, зробить висновок, що робити з бітом перенесення. Але якщо це неправильно, то отримана логіка буде жахливою.

Можливо, що використання int таким чином може призвести до розумного логічного розміру та швидкості, але це не є гарантією. Перехід на інший компілятор (наприклад, XST на Synopsis) або перехід до іншої архітектури FPGA може призвести до того, що трапиться зовсім неправильна річ.

Непідписаний / підписаний проти slv - це ще одна дискусія. Ви можете подякувати урядовому комітету США за те, що нам дали стільки варіантів ВГДЛ. :) Я використовую slv, оскільки це стандарт для взаємодії модулів і ядер. Окрім цього, та деяких інших випадків моделювання, я не думаю, що використання переваги slv над підписаним / неподписаним не має великої користі. Я також не впевнений, чи підписані / непідписані підтримують триголосні сигнали.


4
Девіде, ці фрагменти коду не рівноцінні. Один підраховує від нуля до довільного числа (з дорогим оператором порівняння); інший відлічується до нуля від довільного числа. Ви можете записати обидва алгоритми або з цілими числами, або з векторами, і ви отримаєте погані результати при підрахунку довільного числа і хороших результатів підрахунку до нуля. Зауважте, що інженери програмного забезпечення також відраховуватимуть до нуля, якщо їм потрібно буде вичавити трохи більше продуктивності з гарячого циклу.
Філіп

1
Як і Філіпп, я не переконаний, що це справедливе порівняння. Якщо цілий приклад відлічити і використати, if (count-1) < 0я б подумав, що синтезатор може зробити висновок про біт виконання та створити майже ту саму схему, що і ваш приклад slv. Також, чи не повинні ми використовувати цей unsignedтип сьогодні :)
Мартін Томпсон

2
@DavidKessner Ви, безумовно, дали ТОРГУЮ та обґрунтовану відповідь, у вас є мій +1. Я маю запитати, хоча ... чому ти переживаєш за оптимізацію впродовж дизайну? Чи не було б краще зосередити свої зусилля на областях коду, які вимагають цього, або зосередитись на SLV-файлах для точок інтерфейсу (портів сутності) для сумісності? Я знаю, що в більшості моїх проектів я не особливо дбаю про те, щоб використання LUT було мінімізоване, якщо воно відповідає термінам і відповідає деталі. Якщо у мене є особливо жорсткі обмеження, я, безумовно, краще усвідомлюю оптимальний дизайн, але не як загальне правило.
akohlsmith

2
Я трохи здивований кількістю голосів за цю відповідь. @ bit_vector @ - це, безумовно, правильний рівень абстрагування для моделювання та оптимізації мікро-архітектур, але загальна рекомендація стосується типів "високого рівня", таких як @ integer @ для сигналів і порту - це щось, що мені здається дивним. Я бачив досить складний і нечитабельний код через відсутність абстракції, щоб знати значення, які ці функції надають, і мені було б дуже сумно, якби мені довелося залишити їх позаду.
trondd

2
@david Відмінні зауваження. Це правда, що ми все ще в середньовічній епосі порівняно з розробкою програмного забезпечення в багатьох відношеннях, але з мого досвіду інтегрованого синтезу і синхронізації Quartus я не думаю, що все так погано. Вони цілком здатні працювати з багатьма речами, такими як переналагодження реєстру та інші оптимізації, що покращує продуктивність, зберігаючи читабельність. Я сумніваюсь, що більшість націлена на кілька ланцюжків інструментів та пристроїв, але для вашого випадку я розумію вимогу до найменшого спільного знаменника :-).
trondd

2

Моя порада спробувати обидва, а потім переглянути звіти про синтез, карту та звіти про місце та маршрут. Ці звіти точно підкажуть, скільки LUT споживає кожен підхід, вони також підкажуть вам максимальну швидкість, з якою може діяти логіка.

Я погоджуюсь з Девідом Кесснером, що ти на милості свого інструментального ланцюга, і "правильної" відповіді немає. Синтез - це чорна магія, і найкращий спосіб дізнатися, що трапилося, - це уважно і ретельно прочитати випущені звіти. Інструменти Xilinx навіть дозволяють бачити всередині FPGA, аж до того, як запрограмований кожен LUT, як з'єднаний ланцюг несучої, як тканина комутатора з'єднує всі LUT та ін.

Для іншого драматичного прикладу підходу містера Кесснера, уявіть, що ви хочете мати кілька тактових частот на 1/2, 1/4, 1/8, 1/16 і т.д. Ви можете використовувати ціле число, яке постійно підраховує кожен цикл, а потім мати кілька компараторів проти цілого цілого значення, при цьому кожен вихід компаратора утворює різний тактовий поділ. Залежно від кількості компараторів, вентилятор може стати нерозумно великим і почати споживати додаткові LUT лише для буферизації. SLV-підхід просто сприймає кожен окремий біт вектора як вихід.


1

Однією з очевидних причин є те, що підписані та неподписані допускають більші значення, ніж ціле число 32 біт. Це вада в мовній конструкції VHDL, яка не є істотною. Нова версія VHDL могла це виправити, вимагаючи цілих значень для підтримки довільного розміру (подібного до BigInt Java).

Крім цього, мені дуже цікаво почути про тести, які виконують різні значення для цілих чисел порівняно з векторами.

До речі, Ян Декалуве написав про це чудовий нарис: Ці Інти зроблені для Countin '


Дякую Філіппе (хоча це не є кращим через доступ до внутрішнього представництва), про що я дійсно після ...)
Мартін Томпсон,

Цей твір є приємним, але повністю ігнорує основні реалізації та результуючу логічну швидкість та розмір. Я згоден з більшістю того, що говорить Декалуве, але він нічого не говорить про результати синтезу. Іноді результати синтезу не мають значення, а іноді і є. Тож це заклик вирок.

1
@David, я погоджуюся, що Ян не розглядає деталі щодо того, як інструменти синтезу реагують на цілі числа. Але, ні, це не заклик суду. Ви можете виміряти результати синтезу та визначити результати даного інструменту синтезу. Я думаю, що ОП означало його питання як виклик для нас, щоб створити фрагменти коду та синтезувати результати, які демонструють різницю (якщо вона є) у виконанні.
Філіп

@Philippe Ні, я мав на увазі, що це судження, якщо ви взагалі переймаєтесь результатами синтезу. Не те, що самі результати синтезу є закликом судження.

@DavidKessner Добре. Я неправильно зрозумів.
Філіп
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.