В предыдущей статье цикла «FPGA. Просто о сложном» было рассмотрено внутреннее устройство FPGA, в этой статье пойдет речь о том, как пишется конфигурация для этих БИС.
Дело в том, что все статьи для новичков, которые мне попадались, были типа «помигать светодиодиком» либо на ALTERA, либо на Xilinx и не раскрывали философии параллельности вычислений. Да, Вы все правильно поняли, в ПЛИС все вычисления выполняются параллельно В этой статье я постараюсь рассказать о том, как пишется конфигурация для ПЛИС без привязки к какому либо производителю. Все примеры будут представлены на VHDL. Бытует мнение, что Verilog более понятен, но если честно, это лишь «дело религии» на каком языке писать, в любом случае, все сказанное будет справедливо и для Verilog. После прочтения этой статьи, Вы будете иметь представление как писать под ПЛИС любой фирмы в любом IDE на любом языке.
Для начала давайте разберемся зачем нужно делать конфигурацию на VHDL или Verilog, ведь в интернете полно примеров с редакторами, в которых нужно просто соединять элементы «И», «И-НЕ», и т.д., чтобы получить требуемую схему. К примеру как на рисунке ниже:
Может показаться, что это удобно, ведь все наглядно и привычно, но лишь как будет выглядеть схема если Вам вдруг понадобится добавить в проект хотя бы одно fifo? А если в проекте нужно реализовать БПФ (быстрое преобразование Фурье)? Такая схема разрастется до эпических масштабов так что учите язык, не издевайтесь над собой. На предприятиях специализирующихся на разработке под ПЛИС подобная реализация конфигураций уже лет 30 как не делается, все пишется с помощью VHDL или Verilog, потому что это быстрее и понятнее. Раз мы выяснили, что единственный путь для создания крупных проектов это языки VHDL или Verilog, то приступим к рассмотрению приемов программирования, но перед этим учтем несколько нюансов:
1) Все проекты состоят из модулей написанных на языке описания, которые обладают портами для связи с другими модулями.
2) Любые операции могут быть синхронными (производятся лишь по сигналу тактирования), либо асинхронными (не зависят от сигнала тактирования и производятся в любой момент времени).
Ниже на картинке представлен пример асинхронных вычислений, давайте его разберем: на вход clk подается тактовый сигнал. Тактовый сигнал нужен для синхронизации других сигналов по времени и его источником обычно выступает кварцевый генератор, конкретно в этом примере он нужен лишь для наглядности и нигде не используется. На входную 8-битную шину data подаются 8-битные слова x»01″, x»02″, x»03″, x»04″, источником данных на этой шине может служить любой другой модуль в этой же ПЛИС или же, если привязать эту шину к реальным портам, любой другой источник из внешнего мира . Сигналы sum1 и sum2 по-сути являются 8-битными регистрами, куда мы помещаем значение суммы между приходящим по шине data словом и некоторым прибавляемым значением. Сигнал sum3 является результатом суммирования sum1 и sum2. Вычисления sum1, sum2, и sum3 выполняются асинхронно, это значит, что вычисления происходят мгновенно с изменением сигнала data и никак не синхронизируются по тактовому сигналу.
(Примечание: b»1000_000″ это бинарная запись шестнадцатиричного числа x»80″, а b»0000_0001 это x»01″)
Теперь давайте посмотрим на осциллограмму ниже. Вы можете убедиться, что операции вычисления sum1 и sum2 и sum3 выполнялись параллельно и асинхронно, т.е. над одним и тем же значением сигнала data одновременно проводились две разные операции sum1 и sum2, и в этот же момент времени производилась операция суммирования между sum1 и sum2. Также на осциллограмме ниже видно, что за один такт пришло два слова (асинхронность обведена фиолетовым), но это никак не повлияло ни на время вычислений, ни на их результат.
Теперь слегка изменим программу: добавим синхронизацию вычисления sum3 по фронту тактового сигнала clk и получим пример синхронных вычислений.
Если раньше значение sum3 вычислялось одновременно с sum1 и sum2, то теперь, как видно из осциллограммы ниже, значение sum3 вычисляется лишь с наступлением фронта тактового сигнала, т.е. на следующий такт. Такие задержки в такт нужно учитывать при написании синхронных процессов. Кроме того, на осциллограмме видно, что в момент, когда за один такт на шину data пришло два слова, вычисления произошли лишь над вторым словом, так-как оно было последним установившимся значением перед фронтом тактового сигнала.
На осциллограмме ниже проиллюстрировано, как происходят те же вычисления если синхронизировать источник, посылающий на шину data данные, с тактовым сигналом (с каждым тактом приходит лишь одно слово): мы провели операции суммирования со всеми входными данными ничего не потеряв.
Логично спросить, мол, зачем вообще нужна синхронизация, если все можно делать асинхронно и выигрывать в скорости? Ответ прост: чтобы иметь возможность проводить определенные вычисления в определенный момент времени. К примеру стоит задача принять пакет из 100 8-битных слов и в 99 слово записать какое-то свое значение, чтобы отправить его дальше. Зная, что за один такт нам может придти лишь одно слово, не трудно завести счетчик и посчитать до 99.
Подводя итоги к этой статье могу сказать, что никто не запрещает Вам писать как синхронные процессы так и асинхронные, комбинировать их, все зависит от ситуации: если требуется привязка ко времени (всякие разные протоколы, интерфейсы, счетчики/таймеры и т.д), то делайте синхронизацию, если нужно словить изменение сигнала в любой неопределенный момент времени то делайте процесс асинхронным.
Автор старался излагать максимально доступно, спасибо что дочитали до конца.