Привет! Меня зовут Кирилл Алексеев, я ведущий инженер в отделе интеграции систем на кристалле радиочастотного центра YADRO. В своей предыдущей статье я рассказал, чем FPGA-разработка отличается от программирования: HDL-код описывает не последовательность инструкций, а цифровую схему. Вместо переменных — проводники, соединяющие выход одного аппаратно-реализуемого блока со входом другого, а все описанные модули (блоки) присутствуют на схеме независимо и работают параллельно.
Дальше — интереснее. К реализации одной и той же цифровой схемы есть множество подходов и огромное количество вариантов последовательности обработки данных. Как это устроено, расскажу в статье. Разберем, что такое архитектура и микроархитектура блока, чем процедурная схема отличается от конвейерной. Сравним две различные микроархитектуры — процедурные реализации схемы цифрового КИХ-фильтра.
Статья будет полезна специалистам, которые только начинают работать с ПЛИС: сможете реализовать цифровую схему на практике. Опытных инженеров приглашаю в комментарии для обмена кейсами.
Архитектура и микроархитектура
Перед тем как приступить к разработке устройства на базе ПЛИС, нужно спроектировать его архитектуру и микроархитектуру.
Архитектура устройства описывает его функциональность с точки зрения системного программиста — человека, который будет работать непосредственно с «голым железом». По сути, это набор команд (или набор реализуемых функций и система управления ими) и способ организации доступа к операндам: описание регистров, кеш памяти, ОЗУ и ПЗУ.
Можно выделить два основных класса архитектур по типу функционирования:
Архитектура потока управления (ControlFlow) — это когда крупная задача разбивается на мелкие составные части, а управление вычислительным процессом происходит через передачу последовательности команд, которые выполняет вычислительное ядро. На этой архитектуре построены все современные процессорные системы: x86, ARM, RISC-V и так далее. Широко известная архитектура фон Неймана, как и противопоставляемая ей гарвардская архитектура, относятся к классу ControlFlow архитектур.
Архитектура потока данных (DataFlow) — это когда для решения конкретной прикладной задачи данные обрабатываются по мере готовности. Эту идею реализуют несколько архитектур. В статье мы рассмотрим одну из них: когда входной поток данных из памяти поступает на обработку в специализированный вычислительный модуль, а результирующий поток последовательно записывается в другую память. Пример — конвейерная архитектура современных DSP-процессоров и некоторых нейроускорителей.

Микроархитектура устройства фактически описывает его структуру, которая базируется на выбранной системе команд и раскрывает конкретный вариант реализации заложенной функциональности. Именно микроархитектура определяет, как в конкретном устройстве соединены вычислительные блоки и элементы памяти и как реализуется подсистема коммутации и управления вычислительным процессом.
Одну и ту же архитектуру можно реализовать на базе различных микроархитектур, которые будут отличаться по способу доступа к памяти, скорости выполнения операций и объему используемых аппаратных ресурсов. Это напрямую влияет на скорость решения прикладных задач. При работе с ПЛИС микроархитектура в том числе предполагает оптимизацию разрабатываемой схемы под архитектуру выбранной микросхемы. Нужно учитывать число встроенных арифметических блоков DSP и блоков памяти BRAM / URAM, число и размер логических ячеек LUT, число встроенных процессорных ядер, контроллеров цифровых интерфейсов и контроллеров ОЗУ — я писал об этом в предыдущей статье.
Обычно, микроархитектуру устройства описывают в виде структурной цифровой схемы.
Если вам тоже интересно работать с архитектурой устройств, вы можете откликнуться на вакансии в нашу команду:
— разработчик FPGA;
— инженер по интеграции на FPGA;
— инженер по тестированию цифровых модемов.Смотрите все вакансии на карьерном сайте.
Процедурная и конвейерная организация вычислений
По способу обработки данных цифровой схемой выделяют процедурную и конвейерную организацию вычислений. Чтобы понять, чем они отличаются, разберем их с точки зрения программирования.
Процедурная парадигма программирования гласит: «Для реализации алгоритма нужно разбить его на независимые друг от друга процедуры, которые выполняются последовательно в соответствии с логикой программы». Обычно для выполнения все процедуры используют один и тот же аппаратный ресурс. То есть процедурное программирование для получения результата предполагает последовательное выполнение операций, используя при этом, например, арифметико-логическое устройство (АЛУ) универсального процессора.
Под «процедурной цифровой схемой» будем понимать вычислительный блок, который принимает на вход порцию данных определенного размера, выполняет требуемые вычисления над ними, выдает результат и только после этого может принять на вход следующую порцию данных. Обычно в такой схеме все промежуточные вычисления выполняются на одном и том же аппаратном ресурсе итерационно: данные с выхода снова и снова поступают на вход блока, пока не будет получен конечный результат. Такая схема основана на процедурной парадигме программирования: все вычисления выполняются последовательно, друг за другом на одном и том же вычислительном ресурсе.
Конвейеризация вычислений — это метод, при котором вычислительный процесс разбивается на последовательность независимых стадий (или этапов). Они выполняются разными вычислительными блоками независимо и параллельно.
Под «конвейерной цифровой схемой» будем понимать вычислительный блок, который может принимать на вход непрерывный поток данных каждый такт (или реже, если это требуется), обрабатывать его в темпе поступления и последовательно выдавать результат на выход.
Получается, основное отличие конвейерной схемы от процедурной — это возможность обрабатывать непрерывный поток данных, поступающих с заданной скоростью. А получена она за счет использования различных вычислительных блоков для организации промежуточных вычислений.
Представьте, что вам нужно постирать, высушить и погладить три кучи белья. Каждая операция: стирка, сушка и глажка одной кучи белья, занимает один час.
Процедурный подход: вы кладете первую кучу в стирку (1 час), затем в сушилку (1 час), затем гладите (1 час). Только после этого вы беретесь за вторую кучу. Очевидно, что на все про все уйдет 9 часов. При этом вся техника (стиралка, сушилка, утюг) большую часть времени не используется, то есть простаивает.
Конвейерный подход: Как только первая куча переместилась в сушилку, вы тут же загружаете вторую кучу в стиральную машину. Когда первая идет на глажку, вторая сушится, а третья стирается. При таком подходе все три кучи будут готовы всего за 5 часов. Причем вся техника (вычислительные ресурсы) работает одновременно.
Ниже будем оценивать скорость поступления данных по параметру скважности:
где – число тактов, за которое на вход схемы поступает
данных.
Обычно скважность представляет собой целое число, но, когда речь идет о пакетной обработке, скважность может быть дробной. При этом часто скважность входного потока конвейерной схемы , то есть данные могут поступать на обработку каждый такт.
Теперь подытожим.
Что нужно сделать на этапе разработки архитектуры специализированного устройства:
Разработать основной подход к решению задачи. Для этого сравнить возможные методы и алгоритмы решения с точки зрения точности и качества результата, подкрепить выводы математическими выкладками и программными моделями.
Определить, какая часть вычислений будет выполняться на ускорителе на базе ПЛИС, а какая — на ПК.
Определить программируемую функциональность.
Что нужно сделать на этапе разработки микроархитектуры:
Продумать структурную цифровую схему, которая будет реализована в ПЛИС.
Определить скорость, с которой будут выполняться вычисления (если заданы критерии времени решения задачи).
Продумать микроархитектуру каждого вычислительного блока, выполняющего конкретную функцию. При этом исходить нужно из требований к скорости и имеющегося аппаратного ресурса — «объема» ПЛИС. Чтобы получить лучшее соотношение скорости к аппаратным затратам, скорость обработки данных каждого реализуемого блока нужно сбалансировать: их работа должна быть согласована друг с другом.
Коротко о КИХ-фильтрации
В качестве примера покажу, как создается микроархитектура фильтра с конечной импульсной характеристикой, или КИХ-фильтра. Математически КИХ-фильтрация описывается операцией свертки:
Свертка цифровых сигналов — математическая операция, применяемая к двум сигналам и
, при которой один сигнал сдвигается относительно другого, их перемноженные значения суммируются и формируют результат
. Физически операция свертки показывает, насколько один цифровой сигнал коррелирует с отраженной копией другого сигнала во всех возможных вариантах их сдвига и наложения друг на друга.
Каждый элемент результирующего массива — это результат корреляции элементов сигнала
с элементами импульсной характеристики фильтра
:
где — элемент массива данных, представляющего собой исходный цифровой сигнал,
— размер массива
;
— элемент массива данных, представляющего собой импульсную характеристику фильтра;
— размер массива
.
Корреляция цифровых сигналов — это взаимосвязь двух дискретных сигналов, определяющая, насколько они похожи между собой. Корреляцию между двумя сигналами находят в виде суммы произведений пар отсчетов (элементов, семплов) исследуемых сигналов.
Так выглядит блок-диаграмма алгоритма КИХ-фильтрации и ее программная реализация на Python:

y = [0] * len(x) # array initialization for i in range(len(x)): for j in range(len(h)): if 0 <= i - j: y[i] += x[i-j] * h[j]
Сначала выполняется инициализация результирующего массива данных нулями. Дальше во внешнем цикле реализуется поэлементный расчет массива , тогда как вложенный цикл реализует операцию корреляции части входного дискретного сигнала
с импульсной характеристикой фильтра
. Чтобы в начале работы не выйти за границы массива, условие реализуется во вложенном цикле алгоритма.
В реальной разработке на Python этот код можно заменить на одну-единственную команду из библиотеки numpy или scipy. Но мне важно показать, как такая задача реализуется в виде алгоритма.
Постановка задачи
Нам нужно рассмотреть два варианта микроархитектуры вычислительного блока КИХ-фильтра, которую потом можно будет реализовать на аппаратном уровне. Схемы будем сравнивать по параметру «удельной производительности»: отношению производительности системы к используемым аппаратным затратам.
Производительность системы — число операций за единицу времени. Время обработки данных аппаратно-реализованной схемой можно рассчитать по формуле:
где: — число тактов работы схемы, а
— период тактовой частоты.
Для простоты расчетов предположим, что значение одинаково для обеих рассматриваемых схем, а все арифметические операции выполняются за одно и то же время, равное одному такту частоты работы схемы. В реальности это может быть не так, особенно когда речь об операциях в больших разрядностях или в формате с плавающей запятой, но этот случай лучше рассмотреть в отдельной статье.
Итак, схемы будем сравнивать по таким критериям:
Число тактов работы схемы
: чем меньше значение, тем лучше.
Коэффициент простоя схемы — время, когда схема не выполняет полезной работы. Будем определять его как отношение среднего арифметического времени работы каждой операции в отдельности к общему времени работы схемы для расчета результата.
Число вычислительных блоков и размер подсистемы коммутации, которые определяют требуемые аппаратные затраты на реализацию схемы.
Использование АЛУ
Самый простой, но и самый медленный способ реализации микроархитектуры блока КИХ-фильтрации — использование АЛУ. Такая микроархитектура построена на базе архитектуры ControlFlow, когда команды выполняются строго друг за другом.

В алгоритме КИХ-фильтрации используется всего два математических оператора: умножение и сложение, поэтому нам достаточно реализовать АЛУ из двух данных операций. На вход схемы из общей памяти подаются операнды в требуемом порядке. Сигнал управления op_sel осуществляет выбор одной из реализованных операций. На выходе АЛУ формируется результат вычислений, который записывается в память и, если это необходимо, снова поступает на вход блока.
При анализе схемы будем условно считать, что результат выполнения операции может быть записан в память и уже на следующем такте работы схемы подан на ее вход. Я не буду подробно расписывать, как это реализовать на практике, нам достаточно знать, что это в принципе возможно.
Поведение этой схемы иллюстрируется временной диаграммой ниже. Для простоты возьмем число коэффициентов импульсной характеристики КИХ-фильтра равное 4:

Вы видите выходы умножителя Mult, сумматора Add и моменты, когда будет сформирован результат фильтрации main_result. Красным выделены входные данные для операции суммирования, а зеленым — элементы, которые и будут конечными результатами вычислений. Так, первый результат rez0 формируется сразу после умножения первого элемента входного сигнала x0 и первого коэффициента импульсной характеристики фильтра h0. Второй результат rez1 — это сумма результатов двух умножений: ; третий результат
rez2 — это сумма результатов трех умножений: и т. д.
Отмечу, что архитектура ControlFlow подразумевает множество различных вариантов последовательности обработки данных. Вот вариант, в котором операция суммирования выполняется каждый раз по готовности операндов:

Число тактов работы процедурной схемы с представленной выше архитектурой можно рассчитать по формуле:
Получается, такая схема выполняет обработку за время, равное суммарному времени выполнения каждой операции.
В формулах не учитывается начало работы схемы, когда мы выполняем меньше операций, чем в основном рабочем цикле.
В качестве примера рассчитаем, сколько будет нужно тактов для вычисления фильтрации сигнала размером с импульсной характеристикой фильтра
:
Важно: в такой схеме во время операции умножения вычислительный блок операции сложения будет простаивать, то есть не будет выполнять никакой полезной работы. На временных диаграммах это видно по штриховке на выходах операций.
Можно ли обрабатывать данные быстрее? Конечно! Но для этого нужно сменить парадигму построения вычислительного ядра: вместо АЛУ с универсальными операциями создать специализированную схему, которая будет работать по архитектуре потока данных DataFlow.
Использование конвейерной схемы MAC
Процедурная реализация с помощью конвейерной схемы операции MAC позволяет увеличить скорость обработки и заодно уменьшить время и объем простоя оборудования.

Почему на конвейерной схеме MAC базируется именно процедурная реализация КИХ-фильтрации? Для начала отмечу, что схема действительно представляет собой конвейер с двумя стадиями: на первой стадии выполняется умножение, а на следующей стадии — операция накопления. На вход такой схемы можно подавать данные каждый такт. Но, если верить определению, процедурой является схема, которая принимает порцию данных на входе и должна сначала их обработать, выдать результат и только после этого принять следующую порцию данных. Это определение в точности соответствует тому, как мы используем один блок MAC: сначала подаем одну порцию данных для получения одного результирующего элемента, потом вторую и так далее.
При построении схемы КИХ-фильтрации на одной операции MAC мы делим используемую память на две части:
— память коэффициентов импульсной характеристики COEF RAM;
— память для хранения входных данных Data Delay, которая реализует «скользящее окно» для вычисления операции свертки. В учебниках по цифровой обработке сигналов (ЦОС) этот блок называется линией задержки КИХ-фильтра.
Так выглядит результирующая схема, которая может выполнять КИХ-фильтрацию сигнала с четырьмя элементами импульсной характеристики фильтра:

Данные на схему поступают потоком со скважностью: одно слово входных данных приходит раз в четыре такта и записывается в буфер Data Delay. В этот момент все данные, которые лежат в буфере, сдвигаются на один элемент, причем последнее слово выталкивается из буфера и теряется.
Основная задача блока Data Delay — организация принципа «скользящего окна»: хранение небольшой части входных данных и последовательная выдача этих данных на выходной порт D. Ниже на временной диаграмме проиллюстрирован принцип работы блока Data Delay, где i_D — шина, по которой данные поступают в блок вместе с сигналом валидации i_v(его еще называют «валидом» от англ. valid).

Такое поведение буфера Data Delay схемотехнически можно реализовать разными способами: как кольцевой буфер на базе сдвигового регистра, fifo или памяти. Конкретный способ реализации буфера опустим, так как он не влияет на время обработки. Но стоит отметить, что на временной диаграмме показана работа блока с нулевой задержкой чтения данных (Latency = 0). В реальной схеме задержка может быть любой, но так как она вносит незначительный вклад в общее время вычислений, в этом случае ее учитывать не будем.
Коэффициенты проходящие через порт i_C записываются в память COEF RAM перед началом работы схемы. Входные данные начинают поступать в схему с выбранной скважностью (или большей, но не меньшей!). После поступления каждого нового слова входных данных все накопленные отсчеты входного сигнала последовательно перемножаются с коэффициентами фильтра. Результаты умножения поступают на аккумулятор — сумматор с одним входом и обратной связью. Результат умножения нового слова входных данных на первый коэффициент фильтра обязательно складывается с нулем. Дальше сложение происходит с содержимым регистра аккумулятора. Результат последнего сложения — слово результирующих данных, которое передается на выход.

В этой схеме может быть другая реализация начала работы схемы, когда требуется выполнить меньше операций. Но на практике усложнение схемы приведет к большим аппаратным затратам и практически не изменит время обработки.
Чем проще будет реализована цифровая схема, тем лучше.
Число тактов работы схемы с одной операцией MAC операцией можно рассчитать по следующей формуле:
где: — минимальная скважность поступления входных данных;
— latency-схемы операции MAC.
Как видите, в формуле появился параметр , который определяет число тактов работы конвейерной схемы. На основании latency и параметра тактовой частоты можно рассчитать задержку в обработке данных конвейерной схемой:
Для такой архитектуры . Это значительно меньше числа выполняемых операций, поэтому при подсчете времени таким слагаемым можно пренебречь:
Теперь в качестве примера рассчитаем, сколько нужно тактов для фильтрации сигнала размером с импульсной характеристикой фильтра
По сравнению с процедурной реализацией число тактов уменьшилось практически в два раза. Это произошло потому, что в этой архитектуре все операции выполняют полезную работу каждый такт времени, а коэффициент загрузки оборудования условно равен 1.
И все же время уменьшилось не ровно в два раза. Исходя из этого, можно сделать вывод, что в схеме есть «незначимые операции» - операции, которые не выполняют никакой полезной работы. Так, в схеме КИХ-фильтрации на одном блоке MAC, «незначимой операцией» будет суммирование данного с нулем, что требуется для очистки накопленных данных аккумулятора.
В сухом остатке
Чтобы реализовать перечисленные микроархитектуры, нужен примерно одинаковый объем аппаратного ресурса.
Микроархитектура с АЛУ позволяет решать все задачи, которые требуют выполнения операций умножения и сложения.
Коэффициент простоя процедурной реализации КИХ-фильтра с использованием АЛУ равен ½, то есть схема половину времени не выполняет полезной работы.
Схема КИХ-фильтрации, реализованной на одной операции MAC, решает одну специализированную задачу, но делает это почти в два раза быстрее.
Условно можно считать, что коэффициент простоя КИХ-фильтрации, реализованной на одной операции MAC, равен 0. То есть схема постоянно выполняет полезную работу.
Для высокой удельной производительности на ПЛИС нужно реализовать вычисления согласно архитектуре DataFlow.
Основные параметры при разработке микроархитектуры — это скорость обработки данных и аппаратные затраты.
На сегодня это вся информация. В следующей статье расскажу о способах распараллеливания цифровых схем на примере КИХ-фильтра. А пока готов отвечать на комментарии.
Чтобы уметь находить оптимальные решения в архитектуре цифровых устройств, нужно владеть теоретическим аппаратом и уметь применять его на практике. Если хотите научиться, приходите на соревнования по разработке СнК или на летнюю стажировку Импульс. А если уже умеете, ждем на собеседованиях.
mozg37
Попробую упростить. Конвейер - это способ разбить длинные(имеющие большую задержку) логические цепи на короткие(имеющие меньшую задержку). Меньше задержка = выше частота. Выше частота=выше производительность. Побочные эффекты - больше требуется регистров, а также значительно более трудная и ненаглядная отладка.
ratragor Автор
В статье я рассматриваю понятие "конвейерная микроархитектура" и это не метод, а именно цифровая схема. На жаргоне мы говорим "построить конвейер" или "конвейер зимовать вычисления", что означает, что нужно использовать соответствующий подход к вычислительному процессу.
Есть ещё одно значение термина "конвейеризация", суть которого вы как раз и раскрывает в комментарии. В иностранной литературе - pipelining. Но тут уже речь о другом: не как реализовать микроархитектура вычислителя в общем, а как реализовать имеющуюся микроархитектуру на низком уровне абстракции, в виде триггеров и дискретной логики.
Например, много разрядный сумматор с цепью переноса можно конвейеризировать, разбив на 2 сумматора поменьше, и выход CO первого сумматора завести через триггер на вход CI второго.
Но в статье речь именно про более высокоуровневую конвейеризацию.