Скорее всего кто-то из читателей уже слышал о генерации HDL кода из моделей Simulink, а также есть немало людей, которые задаются вопросом об эффективности этого процесса.

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

Сделаю это на примере разработки LDPC декодера стандарта DVB-T2, который удалось разработать меньше чем за два месяца. Итак, начнем с описания процесса разработки, который мы использовали.

МОП широкими мазками

Применение модельно-ориентированного проектирования (МОП) позволяет сократить время разработки за счет нахождения ошибок на ранних стадиях проекта. Это достигается с помощью построения эталонов и непрерывного тестирования разрабатываемых алгоритмов относительно этих эталонов. Быстро построить эталон помогает обширная библиотека параметризированных блоков и функций, и при разработке LDPC декодера сначала была построена эталонная модель, содержащая источник данных, LDPC кодер, BPSK модулятор, AWGN канал связи, BPSK демодулятор с мягкими решениями, LDPC декодер и блок, считающий количество ошибок относительно исходных данных (рисунок 1).

Рисунок 1 Эталонная модель LDPC декодирования
Рисунок 1 Эталонная модель LDPC декодирования

С эталонной моделью у нас появился источник тестовых воздействий на LDPC декодер и ожидаемый результат его работы.

Далее, чтобы понять, как устроен LDPC декодер внутри, был разработан прототип на MATLAB, содержащий только базовые математические операции. Помимо понимания, прототип дает возможность оценить, насколько упрощения, требуемые для реализации на аппаратуре, влияют на исправляющую способность декодера (например, замена суммы логарифмов на сумму минимальных значений в LDPC декодере).

Потом была разработана модель LDPC декодера со структурой для реализации на аппаратуре. Потенциально мы могли бы сгенерировать HDL код из прототипа на MATLAB, но он получился бы неэффективным. Модель для реализации на аппаратуре отличается от прототипа на MATLAB тем, что как правило данные на вход модели для аппаратуры приходят последовательно, а не целым кадром, циклы заменены счетчиками и для хранения больших объемов данных используется блочная память. Обычно процесс получения модели для реализации на аппаратуре начинается с поиска соответствующей статьи в интернете. В статье есть структурные схемы, которые переносятся на диаграмму Simulink.

После полной отладки структуры для аппаратуры был сгенерирован HDL код, который с точностью до бита и такта соответствует модели. То, что код полностью соответствует модели, позволяет выполнять всю отладку алгоритма на модели, что значительно экономит время на его верификацию (сравнение скорости симуляции модели и HDL кода я привел ниже в статье). После генерации HDL кода мы получаем оценку по ресурсам и производительности. Если она неудовлетворительная, то модифицируем модель, а не код, например если проблемы с производительностью, добавляем регистры конвейеризации в критичный путь и снова генерируем HDL код.

Далее детальнее остановимся на этих этапах.

Структура LDPC декодера для аппаратуры

В этой статье я опишу общую схему декодера, которая включает в себя устройство быстрого сдвига (barrel shifter), вычислитель вероятностей (node processor) и память для хранения вероятностей.

Упрощенно процесс декодирования можно описать следующим алгоритмом:

  1. Вероятности информационного бита считываются из памяти по адресам заданными поверочной матрицей.

  2. К считанным вероятностям применяется операция быстрого сдвига со значением заданной поверочной матрицей.

  3. Результат сдвига попадает на вычислитель вероятностей.

  4. Вычисленные вероятности после обратного сдвига записывается в память.

Далее на примере устройства быстрого сдвига рассмотрим процесс получения HDL кода. Почему из всех компонентов LDPC рассматриваем именно устройство быстрого сдвига? Во-первых, можно сравнить качество сгенерированного кода, поскольку частный случай этого алгоритма реализуется одним оператором VHDL. Во-вторых, при прямой реализации устройство быстрого сдвига с требуемыми параметрами занимает неприемлемо много ресурсов, поэтому мы рассмотрим, как сделать более оптимальную реализацию.

Проработка вариантов реализации устройства быстрого сдвига Barrel Shifter

Устройство быстрого сдвига делает циклический сдвиг входного вектора на заданную величину. Например, если входной вектор равен 1 2 3 4 5 6 7 8, после применения сдвига на 3, результатом будет вектор 4 5 6 7 8 1 2 3.

Реализовав устройство быстрого сдвига на языке MATLAB и встроив этот код в Simulink через блок MATLAB Function, можно выполнить симуляцию и убедиться, что устройство работает корректно.

Рисунок 2 Тестирование устройство быстрого сдвига на модели
Рисунок 2 Тестирование устройство быстрого сдвига на модели

Быстрый сдвиг для битового вектора (например [1 0 1 0 1 1 0 0]) можно реализовать с помощью оператора rol в VHDL (циклический сдвиг влево). VHDL код для вектора длинной 8 будет выглядеть следующим образом:

LIBRARY IEEE;
USE IEEE.std_logic_1164.ALL;
USE IEEE.numeric_std.ALL;
   
ENTITY BarrelShifter IS
    PORT(   u                                 :   IN    std_logic_vector(7 DOWNTO 0);  
            shift                             :   IN    std_logic_vector(2 DOWNTO 0);  
            y                                 :   OUT   std_logic_vector(7 DOWNTO 0)  
            );
END BarrelShifter;
    
    
ARCHITECTURE rtl OF BarrelShifter IS
    
      SIGNAL u_unsigned                       : unsigned(7 DOWNTO 0);  
      SIGNAL shift_unsigned                   : unsigned(2 DOWNTO 0);  
      SIGNAL y_tmp                            : unsigned(7 DOWNTO 0);  
      SIGNAL k                                : unsigned(7 DOWNTO 0);  
    
BEGIN
      u_unsigned <= unsigned(u);
    
      shift_unsigned <= unsigned(shift);
    
      k <= resize(shift_unsigned, 8);
      y_tmp <= u_unsigned rol to_integer(k);
    
      y <= std_logic_vector(y_tmp);
    
END rtl;

Циклический сдвиг влево для восьмибитного вектора после синтеза на ПЛИС Xilinx Zynq7000 занимает 16 LUT.

Далее мы опишем устройство быстрого сдвига алгоритмически на языке MATLAB, сгенерируем VHDL код из него и сравним с ресурсами, полученными при реализации с помощью оператора rol.

Алгоритмически устройство быстрого сдвига можно представить следующим кодом на языке MATLAB, где вычисляются индексы выходного вектора:

function y = fcn(u, shift)
N = 8;
ind = zeros(N,1, 'int16');
for i = 1:N
    ind(i) = i+int16(shift);
    if ind(i) > N
        ind(i) = ind(i) - N;
    end
end
y = u(ind);

Cгенерированный VHDL код выглядит следующим образом:

LIBRARY IEEE;
USE IEEE.std_logic_1164.ALL;
USE IEEE.numeric_std.ALL;
USE work.Subsystem_pkg.ALL;
  
ENTITY MATLAB_Function IS
  PORT( u                                 :   IN    std_logic_vector(0 TO 7);  -- boolean [8]
        shift                             :   IN    std_logic_vector(7 DOWNTO 0);  -- uint8
        y                                 :   OUT   std_logic_vector(0 TO 7)  -- boolean [8]
        );
END MATLAB_Function;
ARCHITECTURE rtl OF MATLAB_Function IS
   
   -- Signals
   SIGNAL shift_unsigned                   : unsigned(7 DOWNTO 0);  -- uint8
   
BEGIN
   shift_unsigned <= unsigned(shift);
    
   MATLAB_Function_1_output : PROCESS (shift_unsigned, u)
    VARIABLE ind : vector_of_signed16(0 TO 7);
    VARIABLE add_cast : vector_of_signed32(0 TO 7);
    VARIABLE add_temp : vector_of_signed32(0 TO 7);
    VARIABLE sub_temp : vector_of_signed17(0 TO 7);
   BEGIN
  
   FOR i IN 0 TO 7 LOOP
    add_cast(i) := signed(resize(shift_unsigned, 32));
    add_temp(i) := (to_signed(i, 32) + add_cast(i)) + 1;
    ind(i) := add_temp(i)(15 DOWNTO 0);
    IF ind(i) > to_signed(16#00000008#, 16) THEN
      sub_temp(i) := resize(ind(i), 17) - to_signed(16#00008#, 17);
      IF (sub_temp(i)(16) = '0') AND (sub_temp(i)(15) /= '0') THEN
        ind(i) := X"7FFF";
      ELSIF (sub_temp(i)(16) = '1') AND (sub_temp(i)(15) /= '1') THEN
        ind(i) := X"8000";
      ELSE
        ind(i) := sub_temp(i)(15 DOWNTO 0);
      END IF;
    END IF;
    y(i) <= u(to_integer(resize(ind(i), 32) - 1));
  END LOOP;
   
  END PROCESS MATLAB_Function_1_output;

  
END rtl;

Видно, что сгенерированный код с алгоритмического описания устройства быстрого сдвига через работу с индексами выглядит совершенно по-другому, чем через оператор rol, хотя реализуется та же самая функция. К тому же код содержит некоторую избыточность: выполняется проверка на выход за границы диапазона int16, после операции разности, производимой с индексами. Однако после синтеза этого кода на ПЛИС Xilinx Zynq7000 мы получаем те же 16 LUT.

Алгоритмическое описание даст нам гибкость с типами данных на входе, для сравнения ресурсов мы на вход подавали битовый вектор, а теперь для использования в LDPC вектор размером 360 из чисел разрядностью 8.

После синтеза на ПЛИС Xilinx Zynq7000 мы получаем 45574 LUT.

На реализацию одного устройства быстрого сдвига с параметрами, нужными для LDPC декодера, у нас уйдет 45574 LUT, а устройств быстрого сдвига в декодере будет два, получается, что это самый ресурсоемкий элемент в декодере, который далее мы оптимизируем.

LDPC декодер стандарта DVB-T2 может параллельно обрабатывать 360 вероятностей, обеспечивая максимальную пропускную способность, но при этом используется большое количество ресурсов. На практике используют меньшую параллельность, один из вариантов – рассчитывать 30 вероятностей в параллели.

Если параллельность будет меньше 360, очевидно, что можно сократить количество вычислителей вероятностей, и один из вариантов – использовать полностью параллельное устройство быстрого сдвига, поставив ключ, выбирающий сдвинутые данные, для которых необходимо рассчитать вероятность. Однако можно также оптимизировать и устройство быстрого сдвига так, чтобы за раз оно работало только с вектором размера выбранной параллельности.

В оптимизированном устройстве быстрого сдвига при обработке с параллельностью 30 входной вектор из 360 запишем в матрицу размером 12x30, как показано на рисунке (полученное число 12, как 360/30, будет дальше использоваться при расчетах адресов и сдвигов).

Рисунок 3 Сохранение входных данных в виде матрицы
Рисунок 3 Сохранение входных данных в виде матрицы

Например, при сдвиге 4 в полностью параллельной версии мы должны получить вектор [5 6 7 8 9 10 11 12 13 14 15 16 17 18 … 3 4]. В версии с параллельностью 30 сначала считается строка по адресу 4 (вычисляется как остаток от деления сдвига 4 на 12), вычитывается вектор [5 17 29 41 53 … 341 353], затем по адресу 5 вычитывается вектор [6 18 30 42 54 … 342 354] и т.д. Так мы можем последовательно за 12 итераций выдать сдвинутые на 4 данные с прореживанием на 12. Поскольку данные при сдвиге на 4 находятся в первом столбце, то не надо делать дополнительный сдвиг вычитанной строки. Однако если необходимо сдвинуть, например на 22, то надо считать строку по адресу 10 (вычисляется как остаток от деления сдвига 22 на 12), получаем вектор [11 23 35 47 59 ... 347 359], который сдвигаем на 1 (вычисляется как целая часть от деления сдвига 22 на 12), в результате получаем вектор [23 35 47 59 ... 347 359 11].

Хотя алгоритмически устройство быстрого сдвига усложнилось, оно теперь работает только с вектором размером 30. Если выполнить синтез этой реализации на ПЛИС Xilinx Zynq7000 мы получаем 1045 LUT.

Таким образом за счет оптимизации на уровне алгоритма удалось сократить количество ресурсов на реализацию одного устройства быстрого сдвига с 45574 LUT до 1045 LUT.

Проработка вариантов реализации устройства быстрого сдвига Barrel Shifter

Отладка HDL кода сложных систем обычно весьма длительный процесс, обусловленный как сложностью разработки тестов, так и скоростью симуляции HDL кода. В этой части статьи мы посмотрим, как повторно использовать тесты, и сравним скорость выполнения LDPC декодера на модели со скоростью симуляции эквивалентного HDL кода.

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

Загрузив полученный тестовый стенд в HDL симлуятор, например Vivado, можно его выполнить и убедиться, что сгенерированный HDL код выполняется так же, как выполняется модель.

Тажке можно сравнить время симуляции декодирования одного блока на модели, которое равно 1.6 минуты с временем симуляции HDL кода в Vivado – примерно 5 минут. Получилось, что скорость моделирования примерно в три раза выше скорости симуляции в HDL симуляторе.

Рисунок 4 Выполнение сгенерированного тестбенча в Vivado
Рисунок 4 Выполнение сгенерированного тестбенча в Vivado

Выводы

Разработка LDPC декодера с помощью модельно-ориентированного проектирования длилась меньше двух месяцев, после чего он был внедрен в проект DVB-T2 демодулятора, где совместное моделирование помогло ускорить процесс интеграции. Во время разработки был построен эталон, данные с которого использовались при отладке детализированной модели для реализации на аппаратуре. Оптимизация за счет изменения алгоритмов позволила значительно сократить использование ресурсов ПЛИС, а отладка алгортмов на модели сократило время разработки, как за счет повторного использования тестов, так и за счет скорости симуляции, что было особенно актуально при построении BER кривых для декодера. Вообще с помощью МОП мы разработали различные IP, среди который IP по ЦОС, системам связи, компьютерному зрению и нейронным сетям, полный список с описанием доступны по ссылке.

Комментарии (0)