Общий случай VHDL

Я пытаюсь создать мультиплексор с общим количеством операторов case. В настоящее время мой код выглядит так:

В этих примерах data_array и selector являются входными данными, data — выходными данными, а ширина мультиплексора равна 4.

process(all)
begin
    case? selector is
        when "1---" => data <= data_array(3);
        when "01--" => data <= data_array(2);
        when "001-" => data <= data_array(1);
        when "0001" => data <= data_array(0);
        when others => data <= (others => '-');
    end case?;
end process;

Есть ли способ получить общее количество операторов case? Или есть аналогичная функция, которую я мог бы использовать?

Я мог бы решить эту проблему, используя генерацию кода для создания соответствующего количества операторов case, но мне было интересно, есть ли функция VHDL (-2008), которую я мог бы использовать для решения этой проблемы.

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

В этом примере GENERIC_WIDTH — это ширина мультиплексора.

process(all)
begin
    data_v := (others => '0');
    for i in 0 to GENERIC_WIDTH-1 loop
        if selector(i) then
            data <= data_array(i);
        end if;
    end loop;
end process;

Я ориентируюсь на устройство Xilinx, использующее Vivado 2017.3. Результаты реализации показывают, что использование оператора case дает более эффективную логику (с точки зрения WNS и логической глубины), чем использование цикла for.


person Maurice    schedule 30.11.2017    source источник
comment
Использование рекурсии иногда может решить такие проблемы: вместо какой-то длинной тонкой топологии с большой задержкой через нее вы можете сгенерировать древовидную структуру, которая в зависимости от количества узлов может иметь гораздо более короткую задержку через нее (примерно log2) . Однако это не так просто сделать. По сути, вам нужна рекурсивная реализация, когда каждый экземпляр делит проблему на две части, а рекурсия продолжается до тех пор, пока проблема не станет тривиальной.   -  person Matthew Taylor    schedule 30.11.2017
comment
Что произойдет во втором примере кода, если вы добавите строку exit; после data <= data_array(i);? Я ожидаю, что это даст тот же результат, что и ваш первый пример. Кроме того, предполагая, что ваш selector имеет диапазон downto, ваш цикл for также должен использовать диапазон downto, чтобы соответствовать соответствующему оператору case.   -  person scary_jeff    schedule 30.11.2017
comment
Мне не хватает минимально воспроизводимого примера, чтобы проверить вашу проблему. То есть вы говорите, что первый фрагмент кода производит более эффективную логику, чем второй фрагмент кода для GENERIC_WIDTH:=4?   -  person JHBonarius    schedule 01.12.2017
comment
Смотрите мое редактирование: кажется, что весь код синтезирован одинаково. Это похоже на ожидания. Логика, которую вы описываете, представляет собой цепочку мультиплексоров. Если вы установите GENERIC_WIDTH на большое число, использование ресурсов и задержка станут большими. Это неизбежно.   -  person JHBonarius    schedule 01.12.2017
comment
Да, я понимаю, что существует фундаментальное ограничение на количество логических уровней, которые вы можете иметь для определенного пути между регистрами. Что меня больше интересовало в этом случае, так это изменения времени после реализации.   -  person Maurice    schedule 01.12.2017


Ответы (1)


Это уже не важно: современные средства синтеза правильно оптимизируют всю логику. Я провел сравнение между выходами Vivado 2017.3. Базовая сущность

library ieee;
use ieee.std_logic_1164.all;

entity MyMux is
    generic(
        data_width : positive := 32;
        data_depth : positive := 4
    );
    port(
        clk : in std_logic;
        data_in : in std_logic_vector(data_width-1 downto 0);
        selector : in std_logic_vector(data_depth-1 downto 0);
        data_out : out std_logic_vector(data_width-1 downto 0)
    );
end entity;

Архитектура 1:

architecture rtl of MyMux is
    subtype data_type is std_logic_vector(data_width-1 downto 0);
    type data_array_type is array (0 to data_depth-1) of data_type;

    signal data_array : data_array_type := (others => (others => '0'));
begin
    read_data : process(clk) begin
        if rising_edge(clk) then
            for i in data_depth-1 downto 1 loop
                data_array(i) <= data_array(i-1);
            end loop;
            data_array(0) <= data_in;
        end if;
    end process;

    select_output: process(all) begin
        case? selector is
            when "1---" => data_out <= data_array(3);
            when "01--" => data_out <= data_array(2);
            when "001-" => data_out <= data_array(1);
            when "0001" => data_out <= data_array(0);
            when others => data_out <= (others => '-');
        end case?;
    end process;
end architecture;

Архитектура 2:

architecture rtl of MyMux is
    subtype data_type is std_logic_vector(data_width-1 downto 0);
    type data_array_type is array (0 to data_depth-1) of data_type;

    signal data_array : data_array_type := (others => (others => '0'));
begin
    read_data : process(clk) begin
        if rising_edge(clk) then
            for i in data_depth-1 downto 1 loop
                data_array(i) <= data_array(i-1);
            end loop;
            data_array(0) <= data_in;
        end if;
    end process;

    select_output: process(all) begin
        data_out <= (others => '-');
        for i in 0 to data_depth-1 loop
            if selector(i) then
                data_out <= data_array(i);
            end if;
        end loop;
    end process;
end architecture;

Архитектура 3:

architecture rtl of MyMux is
    subtype data_type is std_logic_vector(data_width-1 downto 0);
    type data_array_type is array (0 to data_depth-1) of data_type;

    signal data_array : data_array_type := (others => (others => '0'));

    function my_mux(
        selector : std_logic_vector(data_depth-1 downto 0);
        data_array : data_array_type) return data_type is
        variable data : data_type;
    begin
        data := (others => '-');
        for i in 0 to data_depth-1 loop
            if selector(i)='1' then
                data := data_array(i);
            end if;
        end loop;
        return data;
    end function;
begin
    read_data : process(clk) begin
        if rising_edge(clk) then
            for i in data_depth-1 downto 1 loop
                data_array(i) <= data_array(i-1);
            end loop;
            data_array(0) <= data_in;
        end if;
    end process;

    data_out <= my_mux(selector, data_array);
end architecture;

Вывод:

  • Архитектура 1: 32 LUT3, 32 LUT6, 128 FDRE
  • Архитектура 2: 32 LUT3, 32 LUT5, 128 FDRE
  • Архитектура 3: 32 LUT3, 32 LUT5, 128 FDRE

Так что они все практически одинаковые.

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

Вы должны принять к сведению, что логика, которую вы описываете в своем коде, будет реализована как цепочка мультиплексоров. Когда GENERIC_WIDTH увеличивается, увеличивается и задержка. Это неизбежно.

person JHBonarius    schedule 30.11.2017
comment
Хотя хорошо использовать абстракции, я бы не ожидал, что функция будет иметь здесь какое-либо значение для синтеза: сначала полностью оцениваются только мультиплексоры и вентили. Конечно, определенные инструменты синтезатора могут не дать ожидаемого перевода... - person user_1818839; 01.12.2017
comment
@BrianDrummond Это не должно иметь значения, и, может быть, уже не имеет значения. Но по моему опыту, еще в 2010-2012 годах, когда я много чего делал с помощью Xilinx ISE, я действительно заметил, что инструмент синтеза искажает логику в циклах процессов, которые я мог решить с помощью функциональных циклов. - person JHBonarius; 01.12.2017
comment
Это очень интересно! Я действительно заметил, что после синтеза результаты похожи. Результаты, похоже, меняются после внедрения. Это делает оценку особенно сложной, поскольку стратегии реализации имеют недетерминированный компонент (например, изменение пробелов приводит к совпадению времени/несоответствию времени). Я обновлю исходный пост, добавив больше информации о времени и сравню с вашими предложениями. - person Maurice; 01.12.2017
comment
Если подумать, если результат синтеза тот же, то единственное, что может измениться во время реализации, — это недетерминированный компонент. Я сделаю больше исследований времени. - person Maurice; 01.12.2017
comment
@Maurice действительно, размещение - это такое большое пространство поиска, что поиск (?) оптимального решения невозможен; используется что-то вроде генетического алгоритма или алгоритма отжига, начиная со случайного размещения, сгенерированного из источника и семени. Классический ISE PAR позволял вам запускать многопроходное место/маршрут, также известное как MPPR, с разными начальными значениями для каждого прохода и выбирать лучшее — может быть, у Vivado есть то же самое? К сожалению, с предусмотрительностью, присущей большому количеству программного обеспечения Xilinx, оно было агрессивно однопоточным, сколько бы ЦП у вас не было. Сценарий оболочки... - person user_1818839; 02.12.2017
comment
Сценарий оболочки, порождающий несколько запусков PAR, каждый с другим начальным числом с помощью параметра командной строки, был одним из способов обойти это ограничение. @JHBonarius +1 за действительно хорошее расследование, кстати. - person user_1818839; 02.12.2017