§ Общая схема

Давно хотел написать эту статью, где вкратце бы разобрал, как работает ПЛИС и почему оно так работает. Первым делом, надо сказать о том, какая задача у ПЛИС. Расшифровка этого сокращения такая - Прогаммируемая Логическая Интегральная Схема. То есть, по сути, это словно эмулятор логических схем, хотя это и неточное описание. С помощью ПЛИС можно с помощью синтезируемого языка создавать любые цифровые схемы. ПЛИС позволяет из небольших кирпичиков, как из Лего, создать все что угодно.
Разновидностей ПЛИС много, но я рассмотрю лишь те из них, которые наиболее типичны. Обычно, кристалл ПЛИС состоит из логических ячеек (LE - Logic Element), в которой как минимум находятся два компонента 1) блок комбинаторной логики 2) регистр. Бывает так, что такие логические элементы группируются в LAB - Logic Array Block, массивы блоков по 16 элементов в каждом (в разных кристаллах может быть и другое количество). Это сделано для экономии связей между ячейками и ускорения работы этих ячеек.

На картинке показаны подобные LAB-ы. В одной LAB находится по 2 x 8 элементов. Там где отмечено красным - это значит, что в этом LE используется регистр, а где светло-коричневым - используется комбинаторная схема.
Помимо логических элементов, в кристалле ПЛИС могут также содержаться различные блоки - блочная память (которая работает со скоростью регистров), блоки DSP, например, быстрые умножители, блоки PLL - фазовой автоподстройки частоты, а также каждая ПЛИС умеет работать с портами.

На картинке показан один блок LE от чипа Cyclone 3.
Здесь мы видим занятый блок с комбинационной логикой, который настроен так, чтобы принимать значение от предыдущего элемента в LAB (cin) и генерировать следующий (cout). Поскольку большинство цифровых схем используют сумматоры (а также вычитатели), то именно по этой причине логические элементы и объединены в массивы для более скоростной передачи сигнала на соседние элементы. Здесь cin - это carry in, а cout - carry out, для сумматора. Также, результат комбинаторной логики не обязательно должен идти сквозь регистр.
Помимо цепи cin-cout также на схеме есть цепь с регистром, для обеспечения скоростных сдвигов. Также у регистра можно заметить очень много различных проводов вроде ACLR, SCLR, SLOAD, SDATA, DATAIN, CLK, ENA, с помощью которых можно синхронно или асинхронно сбрасывать значение регистра, устанавливать новые данные по сигналу фронта, разрешать запись или запрещать. Все эти комбинации позволяют достаточно эффективно и оптимально решать задачи по разработке цифровых схем.
Таким образом, ПЛИС - это своего рода Лего, из которого можно создать любую другую схему.

§ Проектирование логического элемента (LE)

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

На схеме есть 3 информационных входа - DATAA, DATAB и CLOCK, и 1 выход - OUT. Помимо информационных, существуют 2 конфигурационных. CONF_COMB задает текущую комбинационную схему, а CONF_REG говорит, откуда будут появляться данные на выходе - их регистра или из комбинационной схемы.
Что такое комбинационная логика? По идее, это таблица истинности. У нас всего лишь 2 входа и для них можно составить таблицу истинности:
A B | C
0 0 | CONF_COMP[0]
0 1 | CONF_COMP[1]
1 0 | CONF_COMP[2]
1 1 | CONF_COMP[3]
То есть, чтобы из LE создать И-НЕ, надо задать CONF_COMB=0111, и тогда схема будет выглядеть так:
A B | C
0 0 | 1
0 1 | 1
1 0 | 1
1 1 | 0
Всего существуют 16 различных таблиц истинности для 2-х элементов 2^{2^2} = 16 , для 4-х элементов будет уже 65536 разных вариантов 2^{2^4} = 65536 .
Если выбрать CONF_REG=0, то значение из комбинационной схемы сразу будет идти на выход, минуя регистр, если же CONF_REG=1, то в данном случае, будет установлено на вход регистра, который активируется с помощью позитивного фронта. Это именно в нашей ПЛИС, в реальных чипах все гораздо сложнее.

§ Проектирование связей между LE

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

Здесь все кажется довольно сложным и на самом деле, оно таким и является. Проводов хоть и мало (всего лишь 3), но они уже занимают огромное пространство и памяти.
На данной схеме, хоть и есть 3 LE, но реально задействован только 1 - средний. Рассмотрю более подробно схему.
У каждого элемента есть Fan-out, то есть, один выход из LE подключается к 3 возможным проводам (шине) через транзистор, который может быть лишь закрыт, либо открыт. Когда транзистор открыт (на базе у него 1), то выход LE подключается к определенному проводу на шине.
Слева и справа элементы полностью закрыты и они никак не участвуют в формировании ни логики, ни сигналов. Работает только элемент посередине. Рассмотрим его поближе.
У элемента есть 3 входа, каждый из которых может быть присоединен через транзистор к 3-м проводам. Итого, 3*3 = 9 транзисторов на каждый элемент и 9 бит, чтобы хранить информацию на базе этих транзисторов. Это очень расточительно! И это лишь 3 провода! А что если проводов будет намного больше? Например, 4 входа на LE и пучок 32 проводов будут давать 128 транзисторов, или 128 бит! Если логических элементов порядка 10 тысяч, то надо хранить 10000*128 бит = 160 кб памяти. На самом деле, это не так и много, но довольно значительно.
Итак, как работает эта схема?
  • Транзистор подключает шину 1 ко входу 2 логического элемента
  • Подключает шину 3 ко входу 1 логического элемента
  • Шину шину 2 ко входу 3 (там находится сигнал CLOCK)
  • Комбинаторная схема равна 1001 и REG=1 (выбрана запись в регистр)
  • Выход элемента подключен к шине 3
Как мы помним, на шине 3 находится вход 1.
В итоге, что у нас получилось?
X Y | C
0 0 | 1 - когда шина 3 равна 0, и шина 1 равна 0
0 1 | 0 - когда шина 3 равна 0, и шина 1 равна 1
1 0 | 0 - шина 3 равна 1, шина 1 равна 0
1 1 | 1 - шина 3 равна 1, шина 1 равна 1
Итак, если шина 1 (X) равна 0, то шина 3 (выход C) будет инверсией шины C. Если же X=1, то C=C. Поскольку мы используем регистр, то данные будут меняться только после того, как будет получен позитивный фронт на шине 2.
Это все крайне сложно осознать, потому что здесь очень легко запутаться в огромных данных. Но, поскольку синтезирует все программное обеспечение, то необходимо все записать в виде кода, и компилятор сам все уложит как надо и оптимизирует.
Теперь я приведу эквивалентную схему на логисиме.

Здесь А - это шина 1, B - шина 2 и C - шина 3, выход.
То есть, даже с помощью 1 элемента мы синтезировали схему, состоящую из регистра, мультиплексора и инвертора.

§ Добавим еще шин

Для того, чтобы не создавать много регистров и сэкономить места на схеме, я объединю провода в легко читаемые блоки:

Всего получаем 5 шин, которые потом легко расширять. Получается матрица связей. Если выходов из этой матрицы будет 3, а входов 5, то потребуется 5*3=15 бит памяти, и 15 транзисторов для того, чтобы матрица связи работала. Для того, чтобы соединить провод A с проводом B', необходимо проставить 1 на базу транзистора, который находится на строке 2, столбце 1.
Точно такая же схема находится на выходе, за исключением того, что вход 1, а выходов 5, или больше.
Получается, если всего шин n, количество входов у логического элемента, например, 4, то тогда для входов необходимо создать матрицу n x 4, а на выход матрицу 1 x n.