Языки высокого уровня ImpulseC, Mitrion-C и Handel-C

В данной статье представлены описания основных языков для разработки программ для ПЛИС-микросхем: ImpulseC, Mitrion-C и Handel-C. Поскольку платформа ПЛИС является специфической и предъявляет достаточно серьезные и необычные требования к сопутствующему программному обеспечению, все эти языки во многом схожи друг с другом. Одно из основных сходств исходит из того факта, что программа для ПЛИС реализуется аппаратно, а значит, вся информация о структуре программы должна быть получена на этапе компиляции, поскольку от неё зависит архитектура ПЛИС. Это приводит к появлению таких ограничений, как отсутствие поддержки рекурсии и использование исключительно статических массивов, характерных для всех данных языков. Также общие идеи построения параллельной программы в разных языках пересекаются - например, как ImpulseC, так и Handel-C используют достаточно известную концепцию взаимодействующих последовательных процессов (CSP).

ImpulseC

ImpulseC - это основанный на языке С набор библиотек и средств для написания программ для ПЛИС. Модель программирования ImpulseC - это модифицированная версия разработанной Хоаром модели взаимодействующих последовательных процессов. Основной задачей создателей этого средства было упрощение процесса написания достаточно эффективной программы для ПЛИС. Для того, чтобы перенести последовательную программу на ПЛИС, нужно внести совсем незначительные изменения; стоит, однако, заметить, что в этом случае эффективность отображения, скорее всего, будет довольно низкой.

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

Программирование процессов в ImpulseC сродни программированию нитей, пусть и с некоторыми допущениями: например, по умолчанию в ImpulseC все данные являются локальными, но их можно явно назначить разделяемыми. Важным отличием также является то, что в ImpulseC существует два типа процессов - программные и аппаратные. Программный процесс выполняется на реальном процессоре (будь то обычный RISC-процессор, DSP-процессор или запрограммированное на ПЛИС процессорное ядро), и поэтому ограничен только возможностями этого процессора. Аппаратные процессы, которые будут ложиться на архитектуру ПЛИС, должны подчиняться более строгим правилам, для того чтобы удовлетворять требованиям компилятора Impulse CoBuilder, который отвечает за создание HDL-файлов. К таким ограничениям относятся, например, запрет на использование рекурсии, сильно ограниченный набор поддерживаемых функций и структур и некоторые другие.

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

  • развертка циклов,
  • перестановка операций,
  • организация конвейера, который реализуется с помощью первых двух оптимизаций.

Эти действия компилятор выполняет автоматически, но при желании программист может самостоятельно их модифицировать. Для того чтобы самому увидеть узкие места в программе, предоставляются средства мониторинга. Также в библиотеке ImpulseC содержаться собственные типы данных - аналоги стандартных типов, для которых реализован набор простых (например, арифметических) операций в виде IP-ядер.

Набор программных средств представляет собой компилятор, оптимизатор и средство мониторинга. Поскольку библиотека ImpulseC совместима с ANSI-C, компилировать и отлаживать программу, написанную с помощью этой библиотеки, можно стандартными средствами.

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

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

Mitrion-C

Общая информация. Mitrion - это среда для разработки приложений, реализованных на кристалле ПЛИС. На данный момент Mitrion является одним из самых распространенных высокоуровневых средств для создания приложений для ПЛИС, оно используется на таких платформах, как Cray XD1, SGI RASC, а также на платформах компании Nallatech. По словам разработчиков Mitrion, их платформа наиболее подходит для реализации приложений, для которых характерно следующее:

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

Ядром этой платформы является Mitrion Virtual Processor (MVP) - программный процессор, который является своего рода прослойкой между программной частью и аппаратурой. Эта прослойка позволяет пользователю полностью абстрагироваться от аппаратной составляющей; за взаимодействие программы с ресурсами кристалла отвечает MVP. Процессор Mitrion подстраивается под выполняемый алгоритм: если в алгоритме используется опредёленное количество АЛУ, то именно такое количество АЛУ будет создано в Mitrion-процессоре.

Разработка программы в рамках среды Mitrion осуществляется на созданном этой же компанией языке Mitrion-С. Код на этом языке транслируется в архитектуру процессора MVP, что обеспечивает его переносимость с одной аппаратной платформы на другую, благодаря тому, что процессор обеспечивает абстрагирование программной составляющей от аппаратной. Однако соответствующая платформа должна поддерживаться процессором MVP.

Описание Mitrion-С. Mitrion-C - это высокоуровневый язык, по синтаксису сильно похожий на С. Однако с семантической точки зрения различия между ними существенные, поскольку Mitrion-C направлен на создание параллельной программы.

В Mitrion-C параллелизм задается на нескольких уровнях. Во-первых, это параллелизм на уровне инструкций. В отличие от С, Mitrion-C работает с потоком данных, а не с потоком команд. В нём отсутствует порядок выполнения инструкций, они исполняются тогда, когда для них готовы данные. Таким образом, параллелизм на этом уровне обеспечивается независимостью данных. Анализ зависимостей данных происходит автоматически на этапе компиляции программы. Этот вид параллелизма неявный - пользователю не надо совершать никаких дополнительных действий для его задания или описания. Главная задача пользователя - учитывать наличие данного вида параллелизма при разработке программы.

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

Во-вторых, в Mitrion-C используется параллелизм на уровне циклов. На данном этапе от пользователя требуется явно задавать, какие циклы он хочет распараллеливать. Для того чтобы цикл выполнялся параллельно, нужно указать специальное ключевое слово foreach (аналог стандартного оператора for). Стоит отметить, что с точки зрения ресурса ПЛИС, параллельное выполнение циклов означает то, что компилятор будет пытаться реализовать отдельно каждую итерацию цикла в аппаратуре до тех пор, пока не кончится либо доступные ресурсы, либо число итераций. Параллельное выполнение циклов является основным средством распараллеливания программы в языке Mitrion-C.

Таким образом, можно сделать вывод, что задание параллелизма с точки зрения пользователя осуществляется просто (единственное явное задание - это оператор foreach), однако появляются довольно серьезные требования к разработке алгоритма (отсутствие порядка выполнения команд, однократное присваивание).

В Mitrion-C реализовано несколько интересных особенностей. Например, точность вычисления для любого скаляра задается вручную. При объявлении переменной программист указывает, какое число бит выделить под неё. Это позволяет повысить гибкость и эффективность программы, однако требует большей аккуратности при программировании. При этом программист может явно не указывать для переменной размер занимаемой памяти, тогда данное значение будет вычисляться автоматически при присваивании этой переменной некоторого значения, исходя из размеров операндов и типа операции.

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

Отдельно рассматривается вопрос реализации условного перехода. Обе ветви оператора if реализуются в архитектуре, после чего на этапе выполнения выбирается одна из этих ветвей, а другая отбрасывается. Следует отметить, что в таком случае ресурс, занимаемый отбрасываемой ветвью, простаивает при исполнении, что несколько снижает эффективность использования ПЛИС-микросхемы, однако такой подход позволяет повысить эффективность выполнения программы.

Помимо ограниченности доступных ресурсов ПЛИС, существует также несколько других факторов, ограничивающих возможность распараллеливания программы, которые следует учитывать разработчику программы. В частности, такими ограничениями являются:

  • Число независимых данных. Данные называются независимыми, если каждое из них может быть вычислено независимо от остальных.
  • Число объектов памяти, к которым может быть одновременный доступ. Это значение определяется свойствами ПЛИС-микросхемы.

Стоит отметить, что на этапе программирования архитектуры MVP каждый оператор языка Mitrion-C обвязывается соответствующими регистрами и вспомогательными структурами для его реализации в аппаратуре. При этом использование конструкций языка Mitrion-C автоматически обеспечивает соответствие структуре процессора MVP.

Реализация задачи на ПЛИС. Процесс реализации искомого алгоритма на ПЛИС довольно прост. Общая его схема представлена на рисунке 1. Первый этап после постановки задачи - написание программы на языке Mitrion-C. После этого программа компилируется в архитектуру процессора MVP, которая представляет собой VHDL файл. Затем полученная архитектура прошивается на ПЛИС микросхеме. Для последнего действия можно использовать стандартные утилиты, например, предоставляемые компаниями-производителями ПЛИС микросхем.


Рисунок 1. Общая схема реализации алгоритма на ПЛИС.

Помимо среды программирования и компилятора, в состав платформы Mitrion входит также графический отладчик, который позволяет просматривать структуру кода, делать пошаговую отладку и просматривать значения на каждом шаге; эмулятор кода для проверки работоспособности программы без запуска её на ПЛИС; а также библиотека для интеграции с хост-программой.

Хост-программа - это приложение, которое выполняется на обычном компьютере и управляет процессом исполнения задачи на ПЛИС. Взаимодействие хост-программы и ПЛИС сводится к 9 шагам:

  1. Связь с реальным ПЛИС устройством и его захват;
  2. Создание Mitrion процессора (MVP);
  3. Загрузка битовой прошивки в ускоритель (конфигурирование кристалла ПЛИС);
  4. Создание буферов обмена на ПЛИС устройстве;
  5. Заполнение буферов обмена входными данными;
  6. Запуск ускорителя на исполнение;
  7. Ожидание прерывания готовности выходных данных;
  8. Выгрузка обработанных данных в память хост-программы;
  9. Закрытие ПЛИС, освобождение созданных буферов обмена.

Хост-программа пишется на языке С/С++ и создаётся отдельно от той программы, которая должна выполняться на ускорителе.

Handel-C

Общие сведения. Язык Handel-C был разработан в 1996 году в Оксфордском университете. По синтаксису он очень напоминает стандартный язык С и, в отличие от Mitrion-C, во многом похож на него семантически. Однако поскольку этот язык предназначен для параллельного выполнения программы, конечно, присутствуют и новые свойства, такие как организация параллелизма и взаимодействие процессов. Изначально язык Handel-C был нацелен на рынок встроенных систем, однако последние несколько лет фокусируется внимание также и на рынке высокопроизводительных вычислений на основе реконфигурируемых систем.

Организация параллелизма. Для организации параллелизма в языке Handel-C используется концепция взаимодействующих последовательных процессов, разработанная Хоаром. Однако в отличие от языка Impulse C, в котором программисту необходимо все действия проводить в рамках этой концепции, здесь для этого задано ключевое слово par. Оно позволяет организовать блок операторов, в котором все операторы будут выполняться параллельно. С помощью ключевого слова seq внутри блока par можно задавать последовательные блоки вычислений, таким образом, организуя набор параллельно работающих процессов, каждый из которых выполняется последовательно (допускается любая степень вложенности блоков par и seq друг в друга).

Приведём пример работы двух этих операторов:

Оператор par:
static unsigned 8 a = 2;
static unsigned 8 b = 1;
par
{
    a++;
    b = a+10;
}
Блок par исполняется за один такт,
результат: a = 3, b = 12.
Оператор seq:
static unsigned 8 a = 2;
static unsigned 8 b = 1;
seq
{
    a++;
    b = a+10;
}
Блок seq исполняется за 2 такта,
результат: a = 3, b = 13. Как в обычном С

Цифра 8 перед именем переменной в объявлении задает размер этой переменной в битах.

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

Таким образом, изначально любая программа, написанная на Handel-C, является последовательной, а параллелизм обеспечивается при задании блоков с помощью ключевого слова par. Такая С-подобность языка двояка. Тот факт, что Handel-C похож на обычный С, с одной стороны, позволяет достаточно легко портировать приложения под ПЛИС; однако с другой стороны, может приводить к написанию программ в стиле С, что отрицательно скажется на способности программы к параллельному выполнению.

В языке Handel-C помимо использования конструкции par существует ещё один способ создания параллельной программы - это задание нескольких функций main. Таким образом, можно обеспечивать параллельное выполнение нескольких независимых ветвей программы. Важной особенностью является то, что для каждого такого процесса (который задаётся функцией main) можно задавать свою частоту, с которой этот процесс будет выполняться на ПЛИС. Между разными процессами возможно взаимодействие посредством тех же самых каналов, которые используются в блоке par.

Особенности Handel-С. Помимо описанных выше отличий языка Handel-C от С, можно выделить ещё несколько его особенностей. Некоторые из них характерны и для других языков написания программ для ПЛИС, например, реализация обеих ветвей условного оператора if в аппаратуре, после чего на этапе выполнения выбирается одна из них, а вторая отбрасывается. Также уже отмечалось, что есть возможность задавать произвольный размер для каждой переменной, от 1 до 128 бит, что позволяет оптимизировать использование ресурсов ПЛИС-микросхемы.

В Handel-C отсутствует поддержка чисел с плавающей запятой, поскольку для их реализации требуется большой объём ресурсов ПЛИС. Разработчики Handel-C рекомендуют по возможности использовать числа с фиксированной запятой или целые числа, однако в случае, если работа с числами с плавающей запятой необходима, можно использовать предоставляемую ими внешнюю библиотеку.

Все переменные в языке Handel-C по умолчанию реализуются на регистрах, при этом массив задаётся как набор переменных, поэтому возможен одновременный доступ к разным элементам массива. Помимо этого, существует возможность реализации на ПЛИС типов памяти RAM и ROM (однако для этого требуется поддержка этой возможности со стороны аппаратуры). В отличие от стандартных массивов в Handel-C, которые представляют собой набор переменных, эти два типа более близки к стандартному пониманию массива: в каждый момент времени возможен доступ только к одному элементу. Это окупается за счёт использования меньшего объёма ресурсов ПЛИС.

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

signal unsigned 8 a;
static unsigned 8 b;
par
{
    a = 7;
    b = a;
}
Результат: a = 0, b = 7.
signal unsigned 8 a;
static unsigned 8 b;
seq
{
    a = 7;
    b = a;
}
Результат: a = 0, b = 0.

Это является отступлением от правила одновременного выполнения операторов, однако помогает несколько упорядочить процесс выполнения команд в блоке par и тем самым упростить процесс написания параллельной программы.

Программный пакет DK. Разработка программ на языке Handel-C осуществляется в рамке созданной компанией Celoxica программного пакета DK. Она включает в себя:

  • среду программирования на данном языке;
  • компилятор, который поддерживает ПЛИС-микросхемы компаний Altera, Xilinx и Actel, при этом учитывая особенности архитектуры конкретных ПЛИС (например, наличие и объём памяти RAM);
  • отладчик, который во время разработки в режиме эмуляции позволяет:
    • расставлять контрольные точки;
    • осуществлять пошаговое выполнение;
    • отслеживать значения конкретной переменной;
    • отслеживать параллельное выполнение процессов;
  • эмулятор.

Все эти средства, а также возможность интегрирования пакета DK с такими программами для моделирования как MathWorks и Simulink, и с различными эмуляторами типа SystemC, создают достаточно развитую и разностороннюю поддержку процесса создания программ для ПЛИС-микросхем.

Заключение

У каждого из рассмотренных языков есть свои плюсы и минусы, для одних целей удобней использовать Mitrion-C, для других - ImpulseC или Handel-C. Однако у них можно выделить одно общее основное достоинство - простота разработки и реализации приложения на ПЛИС-ускорителе по сравнению с самым популярным способом на текущий момент - реализацией с помощью VHDL-описаний. Время разработки снижается с нескольких месяцев до нескольких дней или недель; при этом сами языки довольно легки в изучении. Платить за это, что вполне закономерно, приходится эффективностью, однако использование таких языков позволяет расширить круг людей, использующих ПЛИС для реализации своих задач, поскольку даже при не самой высокой эффективности разработки можно получать прирост по производительности в десятки раз по сравнению с использованием обычного компьютера. А привлечение новых, менее опытных в работе с ПЛИС-микросхемами пользователей является одним из ключевых моментов для развития данной области в целом.


© Лаборатория Параллельных информационных технологий НИВЦ МГУ
Rambler's Top100