mirror of
https://github.com/MPSU/APS.git
synced 2025-09-15 17:20:10 +00:00
227 lines
26 KiB
Markdown
227 lines
26 KiB
Markdown
# Этапы реализации проекта в ПЛИС
|
||
|
||
Для того, чтобы описанное на **языке описания аппаратуры** устройство было реализовано в ПЛИС, необходимо выполнить несколько этапов:
|
||
|
||
1. Elaboration
|
||
2. Synthesis
|
||
3. Implementation
|
||
4. Bitstream generation
|
||
|
||
В русскоязычной литературе не сложилось устоявшихся терминов для этапов 1 и 3, но **elaboration** можно назвать как "**предобработку**" или "**развертывание**", а **implementation** как "**реализацию**" или "**построение**".
|
||
Этапы 2 и 4 переводятся дословно: **синтез** и "**генерация двоичного файла конфигурации** (**битстрима**)".
|
||
|
||
Более того, граница между этапами весьма условна и в зависимости от используемой **системы автоматизированного проектирования** (**САПР**), задачи, выполняемые на различных этапах, могут перетекать из одного в другой. Описание этапов будет даваться для маршрута проектирования под ПЛИС, однако, с некоторыми оговорками, эти же этапы используются и при проектировании сверхбольших интегральных схем (СБИС).
|
||
|
||
Остановимся на каждом шаге подробнее.
|
||
|
||
## Elaboration
|
||
|
||
На этапе предобработки, САПР разбирает и анализирует HDL-описание вашего устройства, проверяет его на наличие синтаксических ошибок, производит подстановку значений параметров и блоков `generate`, устанавливает разрядности сигналов и строит иерархию модулей устройства.
|
||
|
||
Затем, ставит в соответствие отдельным участкам кода соответствующие абстрактные элементы: логические вентили, мультиплексоры, элементы памяти и т.п. Кроме того, производится анализ и оптимизация схемы, например, если какая-то часть логики в конечном итоге не связана ни с одним из выходных сигналов, эта часть логики будет удалена[[1]](https://support.xilinx.com/s/question/0D52E00006iHshoSAC/what-exactly-is-elaborating-a-design?language=en_US).
|
||
|
||
Итогом предобработки является так называемая **топология межсоединений** (в быту называемая словом **нетлист**). **Нетлист** — это представление цифровой схемы в виде **графа**, где каждый элемент схемы является вершиной графа, а **цепи**, соединяющие эти элементы являются его ребрами. Нетлист может храниться как в виде каких-то внутренних файлов САПР-а (так хранится нетлист этапа **предобработки**), так и в виде **HDL**-файла, описывающего экземпляры примитивов и связи между ними. Рассмотрим этап предобработки и термин нетлиста на примере.
|
||
|
||
Допустим, мы хотим реализовать следующую цифровую схему:
|
||
|
||

|
||
|
||
_Рисунок 1. Пример простой цифровой схемы._
|
||
|
||
Её можно описать следующим **SystemVerilog**-кодом:
|
||
|
||
```SystemVerilog
|
||
module sample(
|
||
input logic a, b, c, d, sel,
|
||
output logic res
|
||
);
|
||
|
||
logic ab = a & b;
|
||
logic xabc = ab ^ c;
|
||
|
||
assign res = sel? d : xabc;
|
||
|
||
endmodule
|
||
```
|
||
|
||
Выполним этап предобработки. Для этого в **Vivado** на вкладке `RTL Analysis` выберем `Schematic`.
|
||
|
||
Откроются следующие окна:
|
||
|
||

|
||
|
||
_Рисунок 2. Результат этапа предобработки._
|
||
|
||
В левом окне мы видим наш нетлист. В нижней части обозначены узлы графа (элементы **ab_i**, **res_i**, **xabc_i**, которые представляют собой **И**, **мультиплексор** и **Исключающее ИЛИ** соответственно. Имена этих элементов схожи с именами проводов, присваиванием которым мы создавали данные элементы)
|
||
|
||
В верхней части обозначены **ребра графа**, соединяющие элементы схемы. Это входы и выходы нашего модуля, а также созданные нами промежуточные цепи.
|
||
|
||
Справа вы видите **схематик** — **графическую схему**, построенную на основе данного **графа** (**нетлиста**).
|
||
|
||
## Synthesis
|
||
|
||
На шаге синтеза, САПР берет сгенерированную ранее цифровую схему и реализует элементы этой схемы через примитивы конкретной ПЛИС — в основном через логические ячейки, содержащие таблицы подстановки, полный однобитный сумматор и `D-триггер` (см. [как работает ПЛИС](../Introduction/How%20FPGA%20works.md)).
|
||
|
||
Поскольку в примере схема чисто **комбинационная**, результат её работы можно рассчитать и выразить в виде **таблицы истинности**, а значит для её реализации лучше всего подойдут **Таблицы Подстановки** (**LUT**-ы). Более того, в ПЛИС `xc7a100tcsg324-1` есть пятивходовые LUT-ы, а у нашей схемы именно столько входов. Это означает, работу всей этой схемы можно заменить **всего одной таблицей подстановки** внутри ПЛИС.
|
||
|
||
Итак, продолжим рассматривать наш пример и выполним этап синтеза. Для этого нажмем на кнопку `Run Synthesis`.
|
||
|
||
После выполнения синтеза у нас появится возможность открыть новый схематик, сделаем это.
|
||
|
||

|
||
|
||
_Рисунок 3. Результат этапа синтеза._
|
||
|
||
Мы видим, что между входами/выходами схемы и её внутренней логикой появились новые примитивы — **буферы**. Они нужны, чтобы преобразовать уровень напряжения между входами ПЛИС и внутренней логикой (условно говоря, на вход плис могут приходить сигналы с уровнем `3.3 В`, а внутри ПЛИС примитивы работают с сигналами уровня `1.2 В`).
|
||
|
||
Сама же логика, как мы и предполагали, свернулась в одну пятивходовую таблицу подстановки.
|
||
|
||
строка `EQN=32'hAAAA3CCC` означает, что таблица подстановки будет инициализирована следующим 32-битным значением: `0xAAAA3CCC`.
|
||
Поскольку у схемы 5 входов, у нас может быть 2<sup>5</sup>=32 возможных комбинаций входных сигналов и для каждого нужно свое значение результата.
|
||
|
||
Можно ли как-то проверить данное 32-битное значение без просчитывания всех 32 комбинаций сигналов "на бумажке"?
|
||
<details>
|
||
<summary>
|
||
Да, можно.
|
||
</summary>
|
||
Сперва надо понять в каком именно порядке будут идти эти комбинации. Мы видим, что сигналы подключены к таблице подстановки в следующем порядке: `d, c, b, a, sel`. Это означает, что сама таблица примет вид:
|
||
|
||
```ascii
|
||
|sel| a | b | c | d | |res|
|
||
|---|---|---|---|---| |---|
|
||
| 0 | 0 | 0 | 0 | 0 | | 0 |
|
||
| 0 | 0 | 0 | 0 | 1 | | 0 |
|
||
| 0 | 0 | 0 | 1 | 0 | | 1 |
|
||
| 0 | 0 | 0 | 1 | 1 | | 1 |
|
||
| 0 | 0 | 1 | 0 | 0 | | 0 |
|
||
....
|
||
| 1 | 1 | 1 | 1 | 1 | | 1 |
|
||
```
|
||
|
||
Давайте посмотрим на логику исходной схемы и данную таблицу истинности: когда `sel==1`, на выход идет `d`, это означает, что мы знаем все значения для нижней половины таблицы истинности, в нижней половине таблице истинности самый левый входной сигнал (`sel`) равен только единице, значит результат будет совпадать с сигналом `d`, который непрерывно меняется с `0` на `1` и оканчивается значением `1`. Это означает, что если читать значения результатов снизу-вверх (от старших значений к младшим), то сначала у нас будет 16 цифр, представляющих 8 пар `10`:`101010101010`, что эквивалентно записи `AAAA` в шестнадцатеричной записи.
|
||
|
||
Рассчитывать оставшиеся 16 вариантов тоже не обязательно, если посмотреть на схему, то можно заметить, что когда `sel!=1`, ни `sel`, ни `d` больше не участвуют в управлении выходом. Выход будет зависеть от операции xor, которая дает `1` только когда её входы не равны между собой. Верхний вход xor (выход И) , будет равен единице только когда входы `a` и `b` равны единице, причем в данный момент, нас интересуют только ситуации, когда `sel!=1`. Принимая во внимание, что в таблице истинности значения входов записываются чередующимися степенями двойки (единицами, парами, четверками, восьмерками) единиц и нулей, мы понимаем, что интересующая нас часть таблицы истинности будет выглядеть следующим образом:
|
||
|
||
```ascii
|
||
...
|
||
|
||
| a | b | c |
|
||
. |---|---|---| .
|
||
. | 1 | 1 | 0 | .
|
||
. | 1 | 1 | 0 | .
|
||
| 1 | 1 | 1 |
|
||
| 1 | 1 | 1 |
|
||
|
||
...
|
||
```
|
||
|
||
Только в этой части таблицы истинности мы получим `1` на выходе **И**, при этом в старшей части, вход `c` так же равен `1`. Это значит, что входы **Исключающего ИЛИ** будут равны и на выходе будет `0`. Значит результат этой таблицы истинности будет равен `0011` или `3` в шестнадцатеричной записи.
|
||
|
||
Ниже данной части таблицы истинности располагается та часть, где `sel==1`, выше та часть, где выход **И** будет равен `0`. Это означает, что оставшаяся младшая часть будет повторять значения `c`, которое сменяется парами нулей и единиц: `00-11-00-11..`. Эта оставшаяся последовательность будет записана в шестнадцатеричном виде как `0xCCC`.
|
||
|
||
Таким образом, мы и получаем искомое выражение `EQN=32'hAAAA3CCC`. Если с этой частью возникли сложности, попробуйте составить данную таблицу истинности (без вычисления самих результатов, а затем просмотрите логику быстрого вычисления результата).
|
||
</details>
|
||
|
||
## Implementation
|
||
|
||
После получения нетлиста, где в качестве элементов используются ресурсы конкретной ПЛИС, происходит **размещение** этой схемы на элементы заданной ПЛИС: выбираются конкретные логические ячейки. Затем происходит **трассировка** (маршрутизация) связей между ними. Для этих процедур часто используется термин **place & route** (размещение и трассировка). Например, реализация 32-битного сумматора с ускоренным переносом может потребовать 32 LUT-а и 8 примитивов вычисления быстрого переноса (`CARRY4`). Будет неразумно использовать для этого примитивы, разбросанные по всему кристаллу ПЛИС, ведь тогда придётся выполнять сложную трассировку сигнала, да и временные характеристики устройства так же пострадают (сигналу, идущему от предыдущего разряда к следующему, придётся проходить больший путь). Вместо этого, САПР будет пытаться разместить схему таким образом, чтобы использовались близлежащие примитивы ПЛИС, для получения оптимальных характеристик.
|
||
|
||
Что именно считается "оптимальным" зависит от двух вещей: настроек САПР и **ограничений** (**constraints**), учитываемых при построении итоговой схемы в ПЛИС. Ограничения сужают область возможных решений по размещению примитивов внутри ПЛИС под определенные характеристики (временны́е и физические). Например, можно сказать, внутри ПЛИС схема должна быть размещена таким образом, чтобы время прохождения по **критическому пути** не превышало `20ns`. Это временно́е ограничение. Также нужно сообщить САПР, к какой ножке ПЛИС необходимо подключить входы и выходы нашей схемы — это физическое ограничение.
|
||
|
||
Ограничения описываются не на языке описания аппаратуры, вместо этого используются текстовые файлы специального формата, зависящего от конкретной САПР.
|
||
|
||
<details>
|
||
<summary>
|
||
Пример используемых ограничений для лабораторной по АЛУ под отладочную плату `Nexys A7-100T` (для упрощения восприятия, убраны старшие разряды переключателей и светодиодов, т.к. ограничения для каждого из разряда однотипны).
|
||
</summary>
|
||
|
||
Строки, начинающиеся с `#` являются комментариями.
|
||
|
||
Строки, начинающиеся с `set_property` являются физическими ограничениями, связывающими входы и выходы нашей схемы с конкретными входами и выходами ПЛИС.
|
||
|
||
Строка `create_clock...` задает временны́е ограничения, описывая целевую тактовую частоту тактового сигнала и его [скважность](https://ru.wikipedia.org/wiki/%D0%A1%D0%BA%D0%B2%D0%B0%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C).
|
||
|
||
```xdc
|
||
## This file is a general .xdc for the Nexys A7-100T
|
||
|
||
# Clock signal
|
||
|
||
set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { CLK100 }]; #IO_L12P_T1_MRCC_35 Sch=clk100mhz
|
||
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports {CLK100}];
|
||
|
||
# Switches
|
||
set_property -dict { PACKAGE_PIN J15 IOSTANDARD LVCMOS33 } [get_ports { SW[0] }]; #IO_L24N_T3_RS0_15 Sch=sw[0]
|
||
set_property -dict { PACKAGE_PIN L16 IOSTANDARD LVCMOS33 } [get_ports { SW[1] }]; #IO_L3N_T0_DQS_EMCCLK_14 Sch=sw[1]
|
||
# ...
|
||
|
||
### LEDs
|
||
|
||
set_property -dict { PACKAGE_PIN H17 IOSTANDARD LVCMOS33 } [get_ports { LED[0] }]; #IO_L18P_T2_A24_15 Sch=led[0]
|
||
set_property -dict { PACKAGE_PIN K15 IOSTANDARD LVCMOS33 } [get_ports { LED[1] }]; #IO_L24P_T3_RS1_15 Sch=led[1]
|
||
# ...
|
||
|
||
## 7 segment display
|
||
set_property -dict { PACKAGE_PIN T10 IOSTANDARD LVCMOS33 } [get_ports { CA }]; #IO_L24N_T3_A00_D16_14 Sch=ca
|
||
set_property -dict { PACKAGE_PIN R10 IOSTANDARD LVCMOS33 } [get_ports { CB }]; #IO_25_14 Sch=cb
|
||
# ...
|
||
|
||
# set_property -dict { PACKAGE_PIN H15 IOSTANDARD LVCMOS33 } [get_ports { DP }]; #IO_L19N_T3_A21_VREF_15 Sch=dp
|
||
set_property -dict { PACKAGE_PIN J17 IOSTANDARD LVCMOS33 } [get_ports { AN[0] }]; #IO_L23P_T3_FOE_B_15 Sch=an[0]
|
||
set_property -dict { PACKAGE_PIN J18 IOSTANDARD LVCMOS33 } [get_ports { AN[1] }]; #IO_L23N_T3_FWE_B_15 Sch=an[1]
|
||
# ...
|
||
|
||
## Buttons
|
||
set_property -dict { PACKAGE_PIN C12 IOSTANDARD LVCMOS33 } [get_ports { resetn }]; #IO_L3P_T0_DQS_AD1P_15 Sch=cpu_resetn
|
||
```
|
||
|
||
</details>
|
||
|
||
После выполнения построения, нетлист и сама цифровая схема остаются неизменными, однако использованные для реализации схемы примитивы получают свой "адрес" внутри ПЛИС:
|
||
|
||

|
||
|
||
_Рисунок 4. "Адрес" конкретного LUT-а в ПЛИС._
|
||
|
||
Теперь, мы можем посмотреть на "внутренности" нашей ПЛИС `xc7a100tcsg324-1` и то, как через её примитивы будет реализована наша схема. Для этого необходимо открыть построенную схему: `Implementation -> Open implemented design`. Откроется следующее окно:
|
||
|
||

|
||
|
||
_Рисунок 5. Окно просмотра реализованного устройства._
|
||
|
||
Это содержимое ПЛИС. Просто из-за огромного количества содержащихся в ней примитивов, оно показана в таком масштабе, что все сливается в один цветной ковер. Большая часть этого окна неактивна (показана в темно-синих тонах) и это нормально, ведь мы реализовали крошечную цифровую схему, она и не должна занимать значительное количество ресурсов ПЛИС.
|
||
|
||
Нас интересует "[бледно-голубая точка](https://ru.wikipedia.org/wiki/Pale_Blue_Dot)", расположенная в нижнем левом углу прямоугольника `X0Y1` (выделено красным). Если отмасштабировать эту зону, мы найдем используемый нами LUT:
|
||
|
||

|
||
|
||
_Рисунок 6. Расположение выбранного LUT-а внутри ПЛИС._
|
||
|
||
Кроме того, если поиграться со свойствами этого примитива, мы сможем найти нашу таблицу истинности, инициализирующую этот примитив.
|
||
|
||
## Bitstream generation
|
||
|
||
После того, как САПР определил конкретные примитивы, их режим работы, и пути сигнала между ними, необходимо создать двоичный файл (**bitstream**), который позволит сконфигурировать ПЛИС необходимым нам образом.
|
||
Получив этот файл, остается запрограммировать ПЛИС, после чего она воплотит разработанное устройство.
|
||
|
||
## Выводы
|
||
|
||
Таким образом, маршрут перехода от HDL-описания устройства до его реализации в ПЛИС выглядит следующим образом:
|
||
|
||
1. Сперва происходит этап **предобработки** (**elaboration**). В основные задачи этого этапа входит:
|
||
1. развертывание иерархии модулей: преобразование иерархической структуры проекта в плоскую, что облегчает дальнейшие этапы обработки;
|
||
2. проверка синтаксиса и семантики HDL-кода;
|
||
3. разрешение параметров и констант;
|
||
4. генерация промежуточного представления проекта, которое затем используется на следующих этапах.
|
||
Полученное промежуточное представление проекта использует абстрактные элементы (логические вентили, мультиплексоры, регистры), которые не привязаны к конкретной ПЛИС.
|
||
2. Затем выполняется этап **синтеза** (**synthesis**) **нетлиста**, который использует ресурсы конкретной ПЛИС. Все, использовавшиеся на предыдущем этапе структуры (регистры, мультиплексоры и прочие блоки) реализуются через примитивы ПЛИС (LUT-ы, D-триггеры, арифметические блоки и т.п.). Выполняется этап оптимизации логических сетей для минимизации занимаемой площади, уменьшения задержек и энергопотребления.
|
||
3. После выполняется этап **построения** (**implementation**) конечной цифровой схемы, выполняющий несколько подэтапов:
|
||
1. **Размещение** (**Placement**): определение конкретных местоположений для всех логических элементов в ПЛИС. Если на предыдущем этапе часть схемы была реализована через LUT, то на этом этапе решается **какой именно** LUT будет использован (имеется в виду не его тип, а какой из множества однотипных элементов будет выбран).
|
||
2. **Трассировка** (**Routing**): создание соединений между элементами в соответствии с нетлистом.
|
||
3. **Временной анализ** (Timing Analysis): Проверка временны́х характеристик для подтверждения, что все сигналы распространяются по цепи в допустимые временны́е рамки.
|
||
Область допустимых решений для этапов "**place & route**" сужается путем наложения **физических** и **временны́х** **ограничений** (**constraints**).
|
||
4. Последним этапом выполняется **генерация двоичного файла конфигурации** (**bitstream generation**), который во время прошивки сконфигурирует ПЛИС на реализацию построенной схемы.
|
||
|
||
## Список использованной литературы
|
||
|
||
1. [Форум Xilinx: what exactly is 'elaborating' a design?](https://support.xilinx.com/s/question/0D52E00006iHshoSAC/what-exactly-is-elaborating-a-design?language=en_US)
|