Когда лучше использовать представления VECTOR против INTEGER?

11

В ветке комментариев об ответе на этот вопрос: Неправильные выводы в сущности VHDL было указано:

«С целыми числами у вас нет контроля или доступа к внутреннему логическому представлению в FPGA, в то время как SLV позволяет вам делать такие трюки, как эффективное использование цепи переноса».

Итак, при каких обстоятельствах вы обнаружили, что лучше кодировать, используя вектор представления битов, чем использовать целые числа s для доступа к внутреннему представлению? И какие преимущества вы измерили (с точки зрения площади чипа, тактовой частоты, задержки или иным образом)?

Мартин Томпсон
источник
Я думаю, что это сложно измерить, поскольку, очевидно, это просто вопрос контроля над реализацией на низком уровне.
Клабаккио

Ответы:

5

Я написал код, предложенный двумя другими авторами, как в форме, так vectorи в integerформе, заботясь о том, чтобы обе версии работали как можно более одинаково.

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


Downcounters

Во-первых, downcounter, как предположил Дэвид Кесснер:

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 с top := 16#7fff_fffe#производительностью 66 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-м) они получатся «в стирке» к тому времени, как вы доберетесь до выхода. Мне сказали, что этот документ содержит доказательство этого, но я выглядел немного глупо, чтобы быстро оценить! Теория компьютерного сложения и переполнения - HL Garner

Использование integers в этой ситуации приведет к ошибкам симуляции при их переносе, даже если мы знаем, что в конце они развернутся.


И, как отметил Филипп, когда вам нужно число больше 2 ** 31, у вас нет выбора, кроме как использовать векторы.

Мартин Томпсон
источник
Во втором кодовом блоке у вас есть variable c : unsigned(32 downto 0);... не c33-битная переменная тогда?
Клабаккио
@clabacchio: да, это позволяет получить доступ к «ручному биту», чтобы увидеть, как будет происходить переход.
Мартин Томпсон
5

При написании VHDL я настоятельно рекомендую использовать std_logic_vector (slv) вместо integer (int) для СИГНАЛОВ . (С другой стороны, использование int для обобщений, некоторых констант и некоторых переменных может быть очень полезным.) Проще говоря, если вы объявляете сигнал типа int или вам нужно указать диапазон для целого числа, то вы, вероятно, делаете что-то не так.

Проблема с int в том, что VHDL-программист понятия не имеет, что такое внутреннее логическое представление int, и поэтому мы не можем этим воспользоваться. Например, если я определяю int в диапазоне от 1 до 10, я понятия не имею, как компилятор кодирует эти значения. Надеюсь, это будет закодировано как 4 бита, но мы не знаем много за этим. Если бы вы могли исследовать сигналы внутри ПЛИС, это могло бы быть закодировано от «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 входами. С LUT с 4 входами, которые использует Xilinx Spartan-3, для этого потребуется 11 LUT и 3 уровня логики. Некоторые компиляторы могут преобразовать это в вычитание, которое будет использовать цепочку переноса и охватывать эквивалент 32 LUT, но может работать быстрее, чем 3 уровня логики.

В примере с slv нет 32-битного сравнения, так что это «ноль LUT, ноль уровней логики». Единственным штрафом является то, что наш счетчик - это один дополнительный бит. Поскольку дополнительная синхронизация для этого дополнительного бита счетчика находится в цепочке переноса, дополнительная задержка синхронизации "почти равна нулю".

Конечно, это крайний пример, так как большинство людей не будут использовать 32-битный счетчик таким образом. Это относится к меньшим счетчикам, но разница будет менее существенной, но все же существенной.

Это только один пример того, как использовать slv через int для ускорения синхронизации. Есть много других способов использовать slv - это только воображение.

Обновление: добавлен материал для учета комментариев Мартина Томпсона об использовании int с "if (count-1) <0"

(Примечание: я предполагаю, что вы имели в виду «if count <0», поскольку это сделало бы его более эквивалентным моей версии slv и избавило бы от необходимости этого дополнительного вычитания.)

При некоторых обстоятельствах это может генерировать предполагаемую реализацию логики, но не гарантируется, что она будет работать постоянно. Это будет зависеть от вашего кода и от того, как ваш компилятор кодирует значение int.

В зависимости от вашего компилятора и от того, как вы указываете диапазон вашего 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) или переход на другую архитектуру ПЛИС может привести к неправильным результатам.

Unsigned / Signed vs. slv - это еще одна дискуссия. Вы можете поблагодарить правительственный комитет США за предоставленную нам возможность выбора в VHDL. :) Я использую slv, потому что это стандарт взаимодействия модулей и ядер. Кроме этого, и некоторых других случаев в симуляциях, я не думаю, что есть огромное преимущество в использовании slv по сравнению с подписанным / неподписанным. Я также не уверен, поддерживают ли подписанные / неподписанные сигналы поддержки три заявленные.

Мартин Томпсон
источник
4
Дэвид, эти фрагменты кода не эквивалентны. Считается от нуля до произвольного числа (с дорогим оператором сравнения); другой отсчитывает до нуля от произвольного числа. Вы можете написать оба алгоритма с целыми числами или векторами, и вы получите плохие результаты при подсчете в произвольное число и хорошие результаты при подсчете до нуля. Обратите внимание, что разработчики программного обеспечения также будут вести обратный отсчет до нуля, если им нужно будет выжать чуть больше производительности из горячей петли.
Филипп
1
Как и Филипп, я не уверен, что это правильное сравнение. Если целочисленный пример будет отсчитываться и использоваться, if (count-1) < 0я думаю, что синтезатор выведет бит выполнения и выдаст ту же схему, что и в вашем примере slv. Кроме того, не должны ли мы использовать unsignedтип в эти дни :)
Мартин Томпсон
2
@DavidKessner вы наверняка дали ТОРГОВЫЙ и аргументированный ответ, вы получили мой +1. Я должен спросить, хотя ... почему вы беспокоитесь об оптимизации всего проекта? Не лучше ли сосредоточить свои усилия на тех областях кода, которые требуют его, или сосредоточиться на SLV для точек интерфейса (портов объектов) для совместимости? Я знаю, что в большинстве моих проектов меня не волнует, что использование LUT сводится к минимуму, если оно соответствует времени и соответствует части. Если бы у меня были особенно жесткие ограничения, я бы, конечно, больше осознавал оптимальный дизайн, но не как общее правило.
akohlsmith
2
Я немного удивлен количеством голосов за этот ответ. @ bit_vector @, безусловно, является правильным уровнем абстракции для моделирования и оптимизации микроархитектур, но общая рекомендация опять-таки относится к «высокоуровневым» типам, таким как @ integer @ для сигналов и портов, я нахожу странным. Я видел достаточно запутанный и нечитаемый код из-за отсутствия абстракции, чтобы знать ценность этих функций, и было бы очень грустно, если бы мне пришлось оставить их позади.
Трондд
2
@David Отличные замечания. Это правда, что мы все еще в средневековье по сравнению с разработкой программного обеспечения во многих отношениях, но из моего опыта работы с интегрированным синтезом Quartus и Synplify я не думаю, что все так плохо. Они вполне способны обрабатывать множество вещей, таких как изменение регистра и другие оптимизации, которые улучшают производительность при сохранении читабельности. Я сомневаюсь, что большинство нацелено на несколько наборов инструментов и устройств, но для вашего случая я понимаю требование наименьшего общего знаменателя :-).
Трондд
2

Мой совет: попробуйте оба варианта, а затем посмотрите на сводные, картографические и маршрутные отчеты. Эти отчеты точно подскажут, сколько LUT потребляет каждый подход, а также максимальную скорость, с которой может работать логика.

Я согласен с Дэвидом Кесснером, что вы находитесь во власти своего набора инструментов, и нет «правильного» ответа. Синтез - это чёрная магия, и лучший способ узнать, что произошло, - это внимательно и тщательно прочитать подготовленные отчеты. Инструменты Xilinx позволяют даже видеть внутри ПЛИС, вплоть до того, как запрограммирована каждая LUT, как соединена цепь переноса, как коммутационная матрица соединяет все LUT и т. Д.

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

ajs410
источник
1

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

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

Кстати, Ян Decaluwe написал хорошее эссе об этом: эти Ints сделаны для Countin '

Philippe
источник
Спасибо Филиппу (хотя это не приложение «лучше через доступ к внутреннему представлению», за которым я действительно следую…)
Martin Thompson
Это эссе хорошо, но полностью игнорирует основную реализацию и результирующую скорость и размер логики. Я согласен с большей частью того, что говорит Декалаве, но он ничего не говорит о результатах синтеза. Иногда результаты синтеза не имеют значения, а иногда они имеют значение. Так что это призыв к суждению.
1
@ Дэвид, я согласен, что Ян не вдавался в подробности того, как инструменты синтеза реагируют на целые числа. Но нет, это не призыв к суду. Вы можете измерить результаты синтеза и определить результаты вашего данного инструмента синтеза. Я думаю, что ОП подразумевал его вопрос как вызов для нас, чтобы произвести фрагменты кода и результаты синтеза, которые демонстрируют разницу (если есть) в производительности.
Филипп
@Philippe Нет, я имел в виду, что это суждение, если вы вообще заботитесь о результатах синтеза. Не то чтобы сами результаты синтеза были суждением.
@DavidKessner ОК. Я неправильно понял.
Филипп