Initial commit

This commit is contained in:
Andrei Solodovnikov
2023-09-07 17:04:37 +03:00
commit f4c0960704
334 changed files with 36105 additions and 0 deletions

View File

@@ -0,0 +1,336 @@
# Руководство по поиску и исправлению ошибок в проекте
## Цель
При выполнении лабораторных работ вы непременно будете сталкиваться с множеством ошибок. И это нормально: **"Не ошибается тот, кто ничего не делает" — © Джейсон Стейтем**.
Важно воспитать в себе положительное восприятие обнаружения ошибок (ведь это приводит к улучшению вашего творения). Если относиться к обнаружению ошибок отрицательно, то вы подсознательно будете пытаться найти ошибки спустя рукава, но если вы "в домике", и ошибок не видите — это не значит что их нет.
При должном отношении, поиск ошибок может превратиться в увлекательное детективное расследование, где у вас есть "место преступления" (обнаруженное несоответствие в поведении, обычно это не сама ошибка, а ее следствие, круги на воде) и какой-то "набор улик" (фрагменты лога, исходный код). И вы по чуть-чуть будете разматывать "нераспутываемую паутину лжи", получая все новые улики, ведущие к истинной ошибке.
Этот документ посвящен практикуму по поискам подобных ошибок в **Verilog**-коде.
- [Руководство по поиску и исправлению ошибок в проекте](#руководство-по-поиску-и-исправлению-ошибок-в-проекте)
- [Цель](#цель)
- [Алгоритм поиска ошибок](#алгоритм-поиска-ошибок)
- [Работа с логом при появлении ошибок](#работа-с-логом-при-появлении-ошибок)
- [Поиск ошибки на временной диаграмме](#поиск-ошибки-на-временной-диаграмме)
- [Открытие файла исходного кода проблемного сигнала](#открытие-файла-исходного-кода-проблемного-сигнала)
- [Добавление сигналов объектов на временную диаграмму](#добавление-сигналов-объектов-на-временную-диаграмму)
- [Сброс симуляции и ее повтор, установка времени моделирования](#сброс-симуляции-и-ее-повтор-установка-времени-моделирования)
- [Исправление сигналов с Z-состоянием](#исправление-сигналов-с-z-состоянием)
- [Поиск ошибки в сигналах, формирующих проблемный сигнал](#поиск-ошибки-в-сигналах-формирующих-проблемный-сигнал)
- [Исправление логики проблемного сигнала](#исправление-логики-проблемного-сигнала)
- [Проблема необъявленных сигналов](#проблема-необъявленных-сигналов)
- [Самостоятельная работа](#самостоятельная-работа)
## Алгоритм поиска ошибок
1. Обычно всё начинается с сообщения в логе тестов (никто не проверяет глазами временную диаграмму сложных проектов, состоящую из тысяч сигналов, меняющихся миллионы раз за микросекунду), но на наших простых лабах, этот шаг иногда может быть и пропущен.
Сообщение в логе обычно содержит следующую ключевую информацию: имя сигнала, на котором установилось неверное значение, и время когда это произошло. Чем лучше написаны тесты, тем больше ключевой информации будет отражено в сообщении, поэтому написание тестов является своего рода искусством.
1. Получив имя сигнала и время, мы отправляемся на временную диаграмму и проверяем нашу ошибку. Как это сделать? Необходимо определить по коду, какие сигналы и каким образом управляют нашим сигналом. Вариантов может быть несколько:
1. Управляющие сигналы имеют корректное значение, но логика, по которой они управляют сигналом неверна, из-за этого на нем возникает неверное значение.
Это идеальный случай, при возникновении которого мы сразу же находим причину проблемы и исправляем ее.
2. Логика управления верна, а какая-то часть управляющих сигналов имеет неверное значение (пусть для примера, неверное значение будет на управляющем сигнале `X`). Это означает, что обнаруженное несоответствие сигналов является уже следствием какой-то ошибки, и мы должны вернуться к шагу 2, проверяя источники сигналов для сигнала `X`. Так происходит до тех пор, пока мы не попадаем в тип 1.
3. Логика управления и значения управляющих сигналов верны. Это самый сложный тип ошибок, который заключается либо в ошибке в спецификации разрабатываемого устройства, либо в САПРе или компонентах, влияющих на его работу. В рамках данного курса вас не должны заботить данные ошибки, и при их возникновении вам стоит обратиться к преподавателю (предварительно убедившись, что ошибка совершенно точно не подходит под первые два варианта).
4. Любая возможная комбинация всех предыдущих типов.
2. Обнаружив первопричину ошибки, мы исправляем ее (возможно дополняя набор тестов, или внеся правки в спецификацию), и повторно запускаем все тесты, чтобы убедиться в двух вещах:
1. ошибка действительно исправлена
2. исправление ошибки не породило новых ошибок
Давайте отработаем эти шаги на примере отладки ошибок в проекте по [вычислению приблизительной длины вектора](../Other//vector_abs/).
## Работа с логом при появлении ошибок
После запуска симуляции мы видим в логе множество ошибок:
![waveform1](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_1.png)
В любой ситуации с множеством ошибок, сначала надо разбираться с самой первой из них, поскольку она может быть ключом к появлению всех остальных. Поэтому листаем лог до момента первой ошибки:
![waveform2](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_2.png)
В логе сказано, что в момент времени `5ns`, на дизайн подавались координаты вектора, равные `0` и `0`, модель посчитала, что длина вектора равна нулю, в то время, как дизайн вернул значение `x`.
## Поиск ошибки на временной диаграмме
Давайте найдем это место на временной диаграмме. Обычно, сразу после запуска симуляции на временной диаграмме отображено место, где симуляция остановилась (возможно с очень неподходящим масштабом). Для начала подгоним масштаб таким образом, чтобы вся временная диаграмма умещалась в окне. Это делается либо нажатием правой кнопкой мыши по в области отображения сигналов, с выбором "Full View" во всплывающем меню, либо нажатием на кнопку Затем найдем приблизительное место рядом с тем временем, что нас интересует, установим там курсор, и приблизим масштаб, периодически уточняя местоположения курсора, пока не найдем интересующее нас место.
![waveform3](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_3.png)
![waveform4](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_4.png)
![waveform4](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_5.png)
![waveform5](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_6.png)
![waveform6](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_7.png)
Мы видим ровно ту информацию, которую нам предоставил тестбенч. Теперь надо разобраться в причинах возникновения X-состояния. Такое может произойти в двух ситуациях: какой-то из сигналов, формирующих этот находится в `X` или `Z` состоянии, либо же два каких-то сигнала одновременно пытаются выставить разные значения (подобный вариант встречается куда реже и в цикле ваших лабораторных вряд ли встретится).
## Открытие файла исходного кода проблемного сигнала
В любом случае, первым делом необходимо определить, источник формирования значения сигнала `res`. Для этого, откроем файл с исходным кодом, где определен данный сигнал. Для этого, нажмем правой кнопкой мыши по имени сигнала на временной диаграмме, и выберем `Go To Source Code`:
![waveform7](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_8.png)
Открывается следующий код (с курсором на строчке `wire [31:0] res;`):
```Verilog
module tb();
reg [31:0] a;
reg [31:0] b;
wire [31:0] res;
vector_abs dut(
.x(a),
.y(b),
.abs(res)
);
//...
```
Выделив `res` мы видим, что у нас подсветился `res` в строке `abs(res)`, что означает что мы завели наш провод внутрь объекта `dut` модуля `vector_abs`, и у нас проблема второго типа (X-состояние передалось от выхода `abs` модуля `vector_abs` проводу `res` модуля `tb`).
В этом можно убедиться, если вытащить сигналы модуля `vector_abs` на временную диаграмму. Чтобы это сделать, надо переключиться на окно `Scope`, где размещена иерархия объектов нашего тестбенча
## Добавление сигналов объектов на временную диаграмму
> Обратите внимание, что в иерархии окна `Scope` находятся не имена модулей, а имена сущностей модуля. В приведенном выше листинге кода мы создали сущность модуля `vector_abs` с именем `dut`, поэтому в иерархии `Scope` мы видим внутри модуля верхнего уровня объект `dut` (не `vector_abs`), так будет и со всеми вложенными объектами.
Выделим объект `dut`. В окне `Objects` справа отобразятся все внутренние сигналы (входы/выходы, внутренние провода и регистры) объекта `dut`:
![waveform8](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_9.png)
Вообще говоря, мы уже видим, что выход `abs` (к которому подключен наш провод `res`) находится в X-состоянии, но для отработки навыков, разберемся с добавлением на временную диаграмму. Можно поступить двумя способами:
1. Добавить все сигналы (то что видно в окне `Objects` на временную диаграмму) из окна `Scope` для этого, либо перетаскиваем нужный нам объект, зажав левую кнопку мыши на временную диаграмму, либо жмем правой кнопкой мыши по нужному объекту, и выбираем `Add to Wave Window`
2. Добавить отдельные сигналы из окна `Objects`. Для этого выделяем их (возможно множественное выделение через модификаторы `shift` или `ctrl`), и как и в прошлом случае, либо перетаскиваем сигналы левой кнопкой мыши, либо добавляем их через правую кнопку мыши.
![waveform9](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_10.png)
![waveform10](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_11.png)
> Обратите внимание, что часть сигналов отображают какое-то значение (сигнал `abs` отображает X-состояние), а часть не отображают ничего. Так произошло, потому что провод `abs` **непрерывно связан** с проводом `res`, с точки зрения симулятора это одна сущность, и записывая во время моделирования значения для сигнала `res`, симулятор неявно записывал значения для сигнала `abs`, чего не скажешь про остальные сигналы, которых не было во время моделирования на временной диаграмме.
## Сброс симуляции и ее повтор, установка времени моделирования
Для того, чтобы получить отсутствующие значения, необходимо повторить моделирование. Для этого, необходимо сбросить время моделирования в 0 и запустить его снова.
Для этого, необходимо на панели симуляции нажать кнопку `Restart` (`|◀`), а затем кнопку `Run all` (`▶`) или `Run for` (`▶t`)
`Run for` выполняет моделирование указанного количества времени, после чего моделирование приостанавливается. Моделирование может быть остановлено так же и вручную, либо вызовом соответствующей инструкции из кода теста.
`Run all` отличается от `Run for` тем, что в качестве количества моделируемого времени указывается "бесконечность", и моделирование будет остановлено только вручную, либо вызовом соответствующей инструкции.
> Обратите внимание, что для добавления недостающих значений добавленных сигналов лучше всего выполнять описанную выше инструкцию. Аналогичного результата можно добиться и нажатием на кнопку `Relaunch Simulation`, однако эта команда запускает повторную компиляцию и запуск симуляции, что для крупных проектов выльется в потерю времени на излишнюю компиляцию.
![waveform11](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_12.png)
Панель управления симуляции с кнопками:
1. `Restart`
2. `Run all`
3. `Run for`
4. `Relaunch Simulation`
Кроме того, чтобы курсор и лог снова не ушли далеко от места первой ошибки, можно сразу указать, необходимое нам время моделирования перед выполнением команды `Run for`: `5ns`
![waveform12](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_13.png)
В итоге видим следующую картину на временной диаграмме:
![waveform13](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_14.png)
Видим два сигнала в Z-состоянии и один сигнал в X-состоянии. Обычно, сигналы с Z-состоянием проще всего исправить, т.к. зачастую это забытое или некорректное подключение провода. Кроме того, сигнал, зависящий от сигнала с Z-состоянием может оказаться в X-состоянии, так что это может быть решением нашей проблемы, поэтому займемся проводами `min` и `min_half`. Сперва займемся сигналом `min` и перейдем к шагу 2 нашего алгоритма (нажимаем правой кнопкой мыши и выбираем `Go To Source Code`):
```Verilog
module vector_abs(
input [31:0] x,
input [31:0] y,
output[31:0] abs
);
wire [31:0] min;
wire [31:0] min_half;
max_min max_min_unit(
.a(x),
.b(y),
.max(max),
.min(min)
);
```
## Исправление сигналов с Z-состоянием
Мы видим, что сигнал `min` подключен к выходу `min` объекта `max_min_unit` модуля `max_min`. Добавим сигналы этого модуля на временную диаграмму. Для этого, необходимо раскрыть список объектов, содержащихся в объекте `dut` иерархии объектов `Scope` и выбрать там объект `max_min_unit`:
![waveform14](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_15.png)
Добавляем внутренние сигналы на временную диаграмму, и повторяем моделирование:
![waveform15](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_16.png)
Произошло что-то странное: все внутренние сигналы объекта `max_min_unit` "зеленые" (не имеющие X или Z состояния), однако подключенный к выходу этого модуля сигнал `min` находится в Z-состоянии. Как такое могло произойти?
Если присмотреться к сигналу `min`, находящемуся в Z-состоянии, можно заметить, что младшая цифра находится не в Z-состоянии, а в состоянии `0`, такое же значение стоит и на сигнале `min` объекта `max_min_unit`. Это интересно.
Если присмотреться к этим двум сигналам еще пристальней, то можно увидеть, что у сигнала `min` объекта `dut` разрядность 32 бита, в то время как разрядность сигнала `min` объекта `max_min_unit` составляет 4 бита.
Это и является проблемой: мы подключили 4 бита сигнала 4-разрядного сигнала `min` к младшим 4 битам 32-разрядного сигнала `min`, а остальные разряды остались не подключенными.
По всей видимости, при написании модуля `max_min`, была указана неверная разрядность сигнала `min`, вместо `31` было написано `3`. Исправим это и повторим моделирование.
> Обратите внимание, что поскольку мы изменили исходный код, в этот раз необходимо нажать на кнопку `Relaunch Simulation`, поскольку нужна повторная компиляция проекта.
![waveform16](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_17.png)
В логе сообщается о 102 найденных ошибках. Это ровно на одну ошибку меньше, чем было ранее. Это не означает, что в проекте осталось 102 ошибки, только то, что исправив данную ошибку, мы действительно что-то исправили, и теперь один из тестовых сценариев, который ранее завершался ошибкой, теперь завершился без нее.
Помните, что если в проекте много ошибок, то часть ошибок может выправлять поведение других ошибок (хоть и не всегда, но иногда минус на минус может выдать плюс контексте ошибок проекта), поэтому надо осторожно полагаться на число найденных ошибок, если их больше нуля.
Посмотрим на нашу временную диаграмму снова, и выберем дальнейшие действия:
![waveform17](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_18.png)
Мы видим, что на временной диаграмме не осталось сигналов в X или Z-состоянии, а значит мы собрали все "низковисящие" улики нашего с вами расследования. Вернемся к месту преступления и попробуем поискать новые улики:
![waveform18](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_19.png)
## Поиск ошибки в сигналах, формирующих проблемный сигнал
Мы видим, что первой ошибкой в логе стала не та ошибка, что была прежде. Раньше первый неверный результат мы видели в момент времени `5ns`, когда на дизайн подавались значения `0` и `0`, теперь же первой ошибкой стал момент времени `10ns`, когда на дизайн подаются значения `1` и `1`. Наше устройство считает, что результат должен равняться `3`, в то время как модель считает, что результат должен равняться `1`. Проверим, нет ли ошибки в модели и посчитаем результат самостоятельно:
Для определения приблизительной длины вектора в евклидовом пространстве(вычисления квадратного корня из суммы квадратов / длины гипотенузы прямоугольного треугольника) можно воспользоваться формулой:
`sqrt(a^2 + b^2) ≈ max + min/2`, где `max` и `min` — большее и меньшее из пары чисел соответственно [**Ричард Лайонс: Цифровая обработка сигналов, Глава 13.2, стр. 475**].
Подставим наши числа в формулу (поскольку оба числа равны, не важно какое из них будет максимумом, а какое минимумом):
```text
1 + 1/2 = 1.5
```
Ни модель ни дизайн не правы?
На самом деле, наше устройство поддерживает только целочисленную арифметику, поэтому результат будет:
```text
1 + 1/2 = 1 + 0 = 1
```
Модель правильно отразила особенность нашего устройства и дала корректный результат.
Значит надо смотреть как формируется результат в нашем устройстве, посмотрим на выход `abs` в модуле `vector_abs`:
```Verilog
assign abs = max + min_half;
```
Выход `abs` зависит от двух внутренних сигналов: max и `min_half`. В соответствии с нашим алгоритмом, либо проблема в логике, связывающей эти два сигнала (операции сложения), либо в значении какого-то из этих сигналов, либо комбинации этих вариантов.
Изучив модуль, мы понимаем, что в логике этого присваивания проблем нет, т.к. оно повторяет логику формулы `max + min/2`, складывая максимум с половиной минимума. Значит проблема в значении какого-то из этих сигналов (или обоих из них). Посчитаем значения этих сигналов самостоятельно (для сложного проекта эти значения бы посчитала модель):
`1` и `0`.
Смотрим, какие значения установлены на сигналах `max` и `min_half` в момент времени `10ns`:
![waveform19](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_20.png)
Мы видим, что в момент времени `10ns` значения `max` и `min_half` изменились ак `1 -> 4` и `2 -> 8` соответственно. Нас интересуют значения `1` и `2`, т.к. в момент времени `10ns` на выходе дизайна в этот момент был установившийся результат для предыдущих значений (еще не успел посчитаться результат для новых значений).
Значение `max=1` совпадает с ожидаемым, в то время как `min_half=2` явно нет.
Мы нашли причину неправильного вычисления результата: и правда, `1+2=3`, теперь необходимо найти ошибку в вычислении сигнала `min_half`.
Как и с сигналом `abs`, необходимо определить сигналы, влияющие на значение сигнала `min_half`. Данный сигнал подключен к выходу `quotient` модуля `half_divider`, поэтому мы будем смотреть исходный код данного модуля:
```Verilog
module half_divider(
input [31:0] numerator,
output[31:0] quotient
);
assign quotient = numerator << 1'b1;
endmodule
```
Что делает данный модуль? Он принимает на вход значение и делит его на два. На вход данного модуля будет приходить значение минимума из нашей формулы.
Выход данного модуля зависит от входа `numerator` и логики сдвига влево на 1. Это значит, что проблема либо в логике, либо в значении, подаваемом на вход. Выведем сигнал `numerator` на временную диаграмму и посмотрим на его значение в момент времени `10ns`:
![waveform20](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_21.png)
Мы помним, что в момент, когда дизайн начал выдавать неправильный результат, на его входы подавались числа `1` и `1`, это значит, что на вход `numerator` пришло корректное значение: минимум из этих двух чисел и правда равен `1`. Проверим логику данного модуля.
## Исправление логики проблемного сигнала
Операция деления в цифровой схемотехнике является очень "дорогой" в плане ресурсов логических блоков и критического пути, поэтому этой операции часто стараются избегать. В нашем случае, нам не нужно обычное деление — нам нужно деление только напополам. В двоичной арифметике, для того чтобы разделить число на два, достаточно отбросить его младшую цифру. Вы часто пользуетесь подобной операцией в повседневной жизни при выполнении операции деления на 10: отбрасываете младшую цифру в десятичной арифметике.
Именно поэтому, когда мы в первый раз пытались посчитать результат "на бумаге", у нас было расхождение с моделью: когда мы делим 1 на 2, мы получаем 0.5, однако деление путем отбрасывания цифры округляет результат вниз (1/2=0, 15/10=1).
Как "отбросить" цифру средствами цифровой логики? Для этого используется операция сдвига вправо.
Операция сдвига вправо в **Verilog** записывается оператором `>>`. Справа от оператора указывается число "отбрасываемых цифр", в нашем случае одна. Но постойте, в логике присваивания стоит оператор `<<`. Это ошибка, исправим ее!
Повторяем моделирование.
![waveform21](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_22.png)
Снова на одну ошибку меньше. Не унываем, вряд ли в проекте число ошибок больше, чем число непустых строк самого проекта. Возвращаемся к начальной ошибке:
![waveform22](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_23.png)
Мы продвинулись в во времени безошибочного моделирования до `15ns`, начинаем наше расследование с начала:
На вход дизайна подаются значения `3` и `4`, дизайн считает, что результатом вычисления `max + min/2` будет `2`, модель считает, что `5`. Посчитаем сами:
```text
max=4
min=3
max + min/2 = 4 + 3/2 = 4 + 1 = 5
```
И снова модель выдала правильный результат. Разберемся в значениях сигналов, формирующих сигнал `abs`.
## Проблема необъявленных сигналов
Поскольку на временной диаграмме стало уже очень много сигналов, уберем лишние, оставив только внутренние сигналы модуля `vector_abs`:
![waveform23](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_24.png)
В глаза сразу же бросается, что сигнал `max` внешне отличается от всех остальных — он ведет себя как однобитный сигнал. Если все остальные сигналы 32-разрядные, то и сигнал `max` должен быть таким же. Перейдем к объявлению этого сигнала, чтобы это исправить (нажав правой кнопкой мыши, и выбрав `Go To Source Code`):
```Verilog
module vector_abs(
input [31:0] x,
input [31:0] y,
output[31:0] abs
);
wire [31:0] min;
wire [31:0] min_half;
max_min max_min_unit(
.a(x),
.b(y),
.max(max),
.min(min)
);
//...
```
Это странно, курсор был установлен на строку `.max(max)`, хотя раньше в этом случае курсор устанавливался на строку, где объявлялся выбранный сигнал. Но вот в чем дело, если мы просмотрим файл внимательно, то не обнаружим объявления сигнала вовсе. Как так вышло, что мы использовали необъявленный сигнал, а САПР не выдал нам ошибку? Дело в том, что стандарт [IEEE 1364-2005](https://ieeexplore.ieee.org/document/1620780) для языка **Verilog** допускает подобное использование необъявленного сигнала. В этом случае, синтезатор неявно создаст одноименный одноразрядный сигнал, что и произошло.
Для исправления этой ошибки, объявим сигнал `max` с корректной разрядностью и повторим моделирование.
![waveform24](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_25.png)
## Самостоятельная работа
Число ошибок сократилось до 40! Мы явно на верном пути. Повторяем предыдущие шаги, вернувшись к первой ошибке:
![waveform25](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_26.png)
В этот раз первая ошибка осталась прежней, только теперь дизайн считает, что результат должен равняться шести (в прошлый раз дизайн выдавал `2`). Мы уже убедились, что в этом случае модель дает правильный результат, поэтому сразу перейдем к формирующим результат сигналам:
![waveform26](../.pic/Vivado%20Basics/Debug%20manual/bugs_hide_and_seek_tutorial_27.png)
Видим, что значение сигнала `min_half`, формирующего значение выхода `abs` неверно (минимумом из `3` и `4` является `3`, `3/2 = 1`).
Не отходя далеко от кассы, мы замечаем, что значение `min`, формирующее сигнал `min_half` неверно: его значение `4`, а должно быть `3`.
Используя [файлы исходного кода проекта](../Other/vector_abs/), попробуйте разобраться в последней обнаруженной нами ошибке.

View File

@@ -0,0 +1,24 @@
# Инструкция по работе с ошибками элаборации
Итак, вы описали модуль на языке Verilog и хотите открыть логическую схему или запустить симуляцию, чтобы убедиться что описание верно.
Однако, в результате какого-то из этих действий появляется окно с сообщением о какой-то непонятной ошибке:
![../.pic/Vivado%20Basics/Elaboration%20Failed/simFail.png](../.pic/Vivado%20Basics/Elaboration%20Failed/simFail.png)
Ничего страшного — ошибки, это часть учебного и рабочего процесса.
Смело нажимаем `OK` не читая сообщения (и поступаем так же, с еще одним открывшимся окном).
Мы собираемся получить информацию об ошибки из более подробного источника: вкладки `Tcl Console`.
![../.pic/Vivado%20Basics/Elaboration%20Failed/err_log.png](../.pic/Vivado%20Basics/Elaboration%20Failed/err_log.png)
В этом окне, ищем самое первое сообщение об ошибке(#4) с последней операции (запуска симуляции/открытии схематика). Место последнего запуска можно найти по тексту синего цвета (#1). Логи различных запусков можно сворачивать, нажимая на кнопку с `-`(#2). Если вам тяжело найти место последнего запуска, можно нажать на значок корзины (кнопка `Clear`, #3) в `Tcl Console` и повторить попытку запуска.
После обнаружения самой первой ошибки за запуск, внимательно читаем сообщение об ошибке (#5). Обычно, оно уже содержит всю необходимую информацию, включая имя файла номер строки, где произошла эта ошибка(#5).
В случае, если вы все ещё не понимаете в чем проблема, сверьтесь со [списком типовых ошибок](../Other/FAQ.md).
Если не помог и он, обратитесь к преподавателю.

View File

@@ -0,0 +1,67 @@
# Структура папок в проекте Vivado
Вы смотрите на окно `Sources` и ничего не понимаете? Или создали модуль, а он куда–то исчез? Или просто хотите понять, как лучше ориентироваться в созданных модулях? Тогда это для вас.
В левом верхнем углу Vivado расположено окно со вкладкой `Sources`. Здесь располагается иерархия модулей вашего проекта. Если у вас нет этой вкладки, открыть её можно так: `Window > Sources`.
![../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_1.png](../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_1.png)
Во вкладке `Design Sources` хранятся модули, описывающие ваш дизайн. В `Constrain` лежат файлы, необходимые для работы с конкретной ПЛИС. `Simulation Sources` хранит в себе тестбенчи и обычные модули.
Допустим, мы создали модуль полного одноботиного сумматора `fulladder`, а также создали и планируем описать модуль полного четырехбитного сумматора `fulladder4`, подключив к нему четыре однобитных.
Итак, раскрываем вкладку `Design Sources` и видим два модуля `fulladder` и `fulladder4`, которые пока что никак друг с другом не связаны. Двойное нажатие на название модуля приведёт к его открытию.
![../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_2.png](../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_2.png)
Модуль `fulladder4` явялется модулем верхнего уровня (top-level module), то есть, если мы запустим синтез или имплементацию проекта, именно этот модуль Vivado будет рассматривать. Чтобы сменить модуль верхнего уровня, необходимо нажать на выбранный модуль правой кнопкой мыши, затем на `Set a top`.
![../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_3.png](../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_3.png)
Подключим `fulladder` к `fulladder4` для создания четырехбитного сумматора путём соединения четырех однобитных. Тогда после сохранения окно изменится так:
![../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_4.png](../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_4.png)
Раскроем вкладку `fulladder4` и увидим 4 подключенных модуля `fulladder`:
![../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_5.png](../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_5.png)
В `Simulation Sources` мы видим один файл тестбенча, к которому что-то подключено, и модуль `fulladder4` с подключенными к нему другими модулями:
![../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_6.png](../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_6.png)
Модули из `Design Sources` автоматически попадают в `Simulation Sources`, так как эти файлы нужны для симуляции. Они не являются копиями модулей, а просто дублируются для удобства. Каждый раз, когда вы меняете что-то в своём дизайне, это отражается как во вкладке `Design Sources`, так и в `Simulation Sources`. Раскроем вкладку с модулем `tb`:
![../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_7.png](../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_7.png)
Такая картина говорит нам о попытке подключить модуль, которого нет в проекте. Часто это связано с неправильным указанием подключаемого модуля. В данном случае мы хотим подключить модуль `half_adder` и Vivado не может его найти.
```Verilog
module tb();
...
half_adder DUT(
.A (a),
.B (b),
.P (p),
.S (s)
);
...
```
Переименуем название подключаемого модуля на `fulladder4` и сохраним.
```Verilog
module tb();
...
fulladder4 DUT(
.A (a),
.B (b),
.P (p),
.S (s)
);
...
```
После обновления в окне `Sources` модуль `fulladder4` "спрячется" под `tb`. Если раскрыть вкладку, будет видно, что `fulladder4` подключен к `tb`, а четыре модуля `fulladder` к `fulladder4`. Также отметим, что `tb` является модулем верхнего уровня, значит, если мы захотим запустить симуляцию, то Vivado выполнит симуляцию именно для модуля `tb`. Изменить модуль верхнего уровня можно так же, как было описано ранее.
![../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_8.png](../.pic/Vivado%20Basics/Folder%20Structure%20In%20The%20Project/folder_structure_8.png)

View File

@@ -0,0 +1,35 @@
# Как добавить файл с содержимым памяти в проект
Представим ситуацию. Сел, значит, ты делать процессор на ПЛИС, делал-делал, и наконец сделал, прошил его в ПЛИС, но как запустить на нем программу?
Благо, в ПЛИС предусмотрена возможность инициализировать (то есть задавать начальные значения) блочную память начальными значениями. Хранит она их рядом со своей прошивкой. При подаче питания на ПЛИС она конфигурирует себя из энергонезависимой памяти, и инициализирует блочную память.
Программу (то, чем нужно проинициализировать память) нужно подсунуть до этапа сборки прошивки в виде текстового файла, в котором данные представлены ASCII-символами в двоичном или 16-ричном виде. В требуемом verilog-модуле необходимо указать какую память в модуле и из какого файла нужно инициализировать. Вот пример объявления памяти RAM с восьмью 32-битными ячейками:
``` verilog
reg [31:0] RAM [0:7];
initial $readmemh("my_program.txt", RAM);
```
В данном примере в память, именуемую RAM, помещаются значения из файла с названием my_program.txt. Файл не обязательно должен покрывать всю область памяти, количество строк может быть любым между 0 и максимумом. Но длинна строк должна строго соответствовать разрядности ячеек инициализируемой памяти. `$readmemh` для 16-ричнного представления чисел в файле, `$readmemb` для файлов с двоичной записью.
Кроме озвученного нужно сообщить САПРу, что файл памяти относится к проекту, и что он файл памяти, а не какой-то другой. Для этого надо сделать два действия:
1. Добавить файл в проект в качестве `Design-файла`
2. Указать в его свойствах тип файла `Memory Initialization Files`
Файл в проект добавляется точно так же, как при создании Verilog-файла.
![../.pic/Vivado%20Basics/How%20to%20add%20a%20mem-file/how_to_mem_1.png](../.pic/Vivado%20Basics/How%20to%20add%20a%20mem-file/how_to_mem_1.png)
Дальше надо добавить файл с содержимым памяти. Обрати внимание, чтобы его можно было выбрать, нужно поставить в фильтрах `All Files`, как на картинке снизу.
![../.pic/Vivado%20Basics/How%20to%20add%20a%20mem-file/how_to_mem_2.png](../.pic/Vivado%20Basics/How%20to%20add%20a%20mem-file/how_to_mem_2.png)
После добавления файла в проект он отобразится в окне `Sources`, в папке `Text`. Чтобы поменять тип файла на нужный, необходимо выделить этот файл, нажав на него, после чего в окне `Source File Properties`, во вкладке `Properties` найти свойство `FILE_TYPE` и заменить его на `Memory Initialization Files`. Описанное отмечено на картинке далее.
![../.pic/Vivado%20Basics/How%20to%20add%20a%20mem-file/how_to_mem_3.png](../.pic/Vivado%20Basics/How%20to%20add%20a%20mem-file/how_to_mem_3.png)
Если после изменений файл переместился в папку `Memory Initialization Files` в окне `Sources`, значит все сделано правильно!
![../.pic/Vivado%20Basics/How%20to%20add%20a%20mem-file/how_to_mem_4.png](../.pic/Vivado%20Basics/How%20to%20add%20a%20mem-file/how_to_mem_4.png)

View File

@@ -0,0 +1,33 @@
# Как открыть цифровую схему проекта
**Вы написали модуль, но не знаете, как открыть цифровую схему?**
Сохраняем модуль > Слева на панели управления раскрываем вкладку RTL ANALYSIS > Раскрываем вкладку Open Elaborated Design > Нажимаем на Schematic:
![../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/open_schematic_1.png](../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/open_schematic_1.png)
`Обратите внимание, что во вкладках SYNTHESIS и IMPLEMENTATION также есть возможность открыть Schematic. Запуск в них строит схему на основе примитивных компонентов ПЛИС. В данной лабораторной работе нам интересна именно цифровая схема, собранная из логических элементов. Получить её можно только во вкладке RTL ANALYSIS.`
Нажимаем ОК:
![../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/open_schematic_2.png](../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/open_schematic_2.png)
Ждём или нажимаем background, чтобы синтез схемы выполнялся в фоновом режиме:
![../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/open_schematic_3.png](../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/open_schematic_3.png)
После этого во вкладке Schematic вы должны увидеть свою схему:
![../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/open_schematic_6.png](../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/open_schematic_6.png)
<br>
**Вы обновили модуль, но схема осталась прежней?**
Сохраняем модуль > Сверху над схемой появилась желтая полоса, нажимаем Reload:
![../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/open_schematic_4.png](../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/open_schematic_4.png)
Ждём загрузку... и вот она, наша новая схема:
![../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/open_schematic_5.png](../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/open_schematic_5.png)

View File

@@ -0,0 +1,227 @@
# Шаги реализации разработанного устройства в ПЛИС
Для того, чтобы описанное на **языке описания аппаратуры** устройство было реализовано в ПЛИС, необходимо выполнить несколько этапов:
1. Элаборацию (elaboration)
2. Синтез (synthesis)
3. Имплементацию (implementation)
4. Генерацию битстрима (generate bitstream)
Остановимся на каждом шаге подробнее:
## Elaboration
На шаге **элаборации**, САПР строит цифровую схему по её **HDL**-описанию. Построенная схема не привязана к конкретной ПЛИС и использует абстрактные элементы: логические вентили, мультиплексоры, элементы памяти и т.п.
На самом деле САПР генерирует не схему (картинку, схематик), а так называемый **нетлист**. **Нетлист** — это представление цифровой схемы в виде **графа**, где каждый элемент схемы является **узлом**, а **цепи**, соединяющие эти элементы являются ребрами. **Нетлист** может храниться как в виде каких-то внутренних файлов САПР-а (в случае **нетлиста** для этапа **элаборации**), так и в виде **HDL**-файла, описывающего экземпляры примитивов) и связи между ними. Рассмотрим этап **элаборации** и термин **нетлиста** на примере.
Допустим, мы хотим реализовать следующую цифровую схему:
![../.pic/Vivado%20Basics/Implementation%20steps/impl_steps_ref_scheme.drawio.png](../.pic/Vivado%20Basics/Implementation%20steps/impl_steps_ref_scheme.drawio.png)
Её можно описать следующим **Verilog**-кодом:
```Verilog
module sample(
input a, b, c, d, sel,
output res
);
wire ab = a & b;
wire xabc = ab ^ c;
assign res = sel? d : xabc;
endmodule
```
Выполним этап **элаборации**. Для этого в **Vivado** на вкладке `RTL Analysis` выберем `Schematic`.
Откроются следующие окна:
![../.pic/Vivado%20Basics/Implementation%20steps/impl_elaborated_netlist.png](../.pic/Vivado%20Basics/Implementation%20steps/impl_elaborated_netlist.png)
В левом окне мы видим наш нетлист. В нижней части обозначены узлы графа (элементы **ab_i**, **res_i**, **xabc_i**, которые представляют собой **И**, **мультиплексор** и **Исключающее ИЛИ** соответственно. Имена этих элементов схожи с именами проводов, присваиванием которым мы создавали данные элементы)
В верхней части обозначены **ребра графа**, соединяющие элементы схемы. Это входы и выходы нашего модуля, а так же созданные нами промежуточные цепи.
Справа вы видите **схематик****графическую схему**, построенную на основе данного **графа** (**нетлиста**).
## Synthesis
На шаге синтеза, САПР берет сгенерированную ранее цифровую схему и реализует элементы этой схемы через примитивы конкретной ПЛИС — в основном через логические ячейки, содержащие таблицы подстановки, полный однобитный сумматор и `D-триггер` (см. [как работает ПЛИС](../Introduction/How%20FPGA%20works.md)).
В рамках нашего примера, САПР смотрит на построенный на этапе **элаборации** **нетлист** и решает, какими средствами (**примитивами**) ПЛИС можно его реализовать. Поскольку схема чисто **комбинационная**, результат её работы можно рассчитать и выразить в виде **таблицы истинности**, а значит для её реализации лучше всего подойдут **Таблицы Подстановки** (**LUT**-ы). Более того, в ПЛИС `xc7a100tcsg324-1` есть пятивходовые LUT-ы, а у нашей схемы именно столько входов. Это означает, работу всей этой схемы можно заменить **всего одной таблицей подстановки** внутри ПЛИС.
Итак, продолжим рассматривать наш пример и выполним этап синтеза. Для этого нажмем на кнопку `Run Synthesis`.
После выполнения синтеза у нас появится возможность открыть новый схематик, сделаем это.
![../.pic/Vivado%20Basics/Implementation%20steps/impl_synthesised_netlist.png](../.pic/Vivado%20Basics/Implementation%20steps/impl_synthesised_netlist.png)
Мы видим, что между входами/выходами схемы и её внутренней логикой появились новые примитивы — **буферы**. Они нужны, преобразовать уровень напряжения между ножками ПЛИС и внутренней логикой (условно говоря, на вход плис могут приходить сигналы с уровнем `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
После построения новой схемы, где в качестве элементов используются ресурсы конкретной ПЛИС, происходит расчёт размещения этой схемы внутри ПЛИС: выбираются конкретные логические ячейки, происходит маршрутизация сигнала между этими ячейками. Например, реализация 32-битного сумматора с ускоренным переносом может потребовать 32 LUT-а и 8 примитивов вычисления быстрого переноса (`CARRY4`). Будет неразумно использовать для этого примитивы, разбросанные по всему кристаллу ПЛИС, ведь тогда придется выполнять сложную маршрутизацию сигнала, да и временные характеристики устройства так же пострадают (сигналу идущему от предыдущего разряда к следующему придется проходить больший путь). Вместо этого, САПР будет пытаться разместить схему таким образом, чтобы использовались близлежащие примитивы ПЛИС, для получения оптимальных характеристик.
Что именно считается "оптимальным" зависит от двух вещей: настроек САПР и **ограничений** (**constraints**), наложенных на имплементацию. Ограничения сужают область возможных решений по размещению примитивов внутри ПЛИС под определенные характеристики (временны́е и физические). Например, можно сказать, внутри ПЛИС схема должна быть размещена таким образом, чтобы время прохождения по **критическому пути** не превышало `20ns`. Это временно́е ограничение. Так же нужно сообщить САПР, к какой ножке ПЛИС необходимо подключить входы и выходы нашей схемы — это физическое ограничение.
Ограничения описываются не на языке описания аппаратуры, вместо этого используются текстовые файлы специального формата, зависящего от конкретной САПР.
<details>
<summary>
Пример используемых ограничений для лабораторной по АЛУ.
</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]
set_property -dict { PACKAGE_PIN M13 IOSTANDARD LVCMOS33 } [get_ports { SW[2] }]; #IO_L6N_T0_D08_VREF_14 Sch=sw[2]
set_property -dict { PACKAGE_PIN R15 IOSTANDARD LVCMOS33 } [get_ports { SW[3] }]; #IO_L13N_T2_MRCC_14 Sch=sw[3]
set_property -dict { PACKAGE_PIN R17 IOSTANDARD LVCMOS33 } [get_ports { SW[4] }]; #IO_L12N_T1_MRCC_14 Sch=sw[4]
set_property -dict { PACKAGE_PIN T18 IOSTANDARD LVCMOS33 } [get_ports { SW[5] }]; #IO_L7N_T1_D10_14 Sch=sw[5]
set_property -dict { PACKAGE_PIN U18 IOSTANDARD LVCMOS33 } [get_ports { SW[6] }]; #IO_L17N_T2_A13_D29_14 Sch=sw[6]
set_property -dict { PACKAGE_PIN R13 IOSTANDARD LVCMOS33 } [get_ports { SW[7] }]; #IO_L5N_T0_D07_14 Sch=sw[7]
set_property -dict { PACKAGE_PIN T8 IOSTANDARD LVCMOS18 } [get_ports { SW[8] }]; #IO_L24N_T3_34 Sch=sw[8]
set_property -dict { PACKAGE_PIN U8 IOSTANDARD LVCMOS18 } [get_ports { SW[9] }]; #IO_25_34 Sch=sw[9]
set_property -dict { PACKAGE_PIN R16 IOSTANDARD LVCMOS33 } [get_ports { SW[10] }]; #IO_L15P_T2_DQS_RDWR_B_14 Sch=sw[10]
set_property -dict { PACKAGE_PIN T13 IOSTANDARD LVCMOS33 } [get_ports { SW[11] }]; #IO_L23P_T3_A03_D19_14 Sch=sw[11]
set_property -dict { PACKAGE_PIN H6 IOSTANDARD LVCMOS33 } [get_ports { SW[12] }]; #IO_L24P_T3_35 Sch=sw[12]
set_property -dict { PACKAGE_PIN U12 IOSTANDARD LVCMOS33 } [get_ports { SW[13] }]; #IO_L20P_T3_A08_D24_14 Sch=sw[13]
set_property -dict { PACKAGE_PIN U11 IOSTANDARD LVCMOS33 } [get_ports { SW[14] }]; #IO_L19N_T3_A09_D25_VREF_14 Sch=sw[14]
set_property -dict { PACKAGE_PIN V10 IOSTANDARD LVCMOS33 } [get_ports { SW[15] }]; #IO_L21P_T3_DQS_14 Sch=sw[15]
### 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]
set_property -dict { PACKAGE_PIN J13 IOSTANDARD LVCMOS33 } [get_ports { LED[2] }]; #IO_L17N_T2_A25_15 Sch=led[2]
set_property -dict { PACKAGE_PIN N14 IOSTANDARD LVCMOS33 } [get_ports { LED[3] }]; #IO_L8P_T1_D11_14 Sch=led[3]
set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { LED[4] }]; #IO_L7P_T1_D09_14 Sch=led[4]
set_property -dict { PACKAGE_PIN V17 IOSTANDARD LVCMOS33 } [get_ports { LED[5] }]; #IO_L18N_T2_A11_D27_14 Sch=led[5]
set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { LED[6] }]; #IO_L17P_T2_A14_D30_14 Sch=led[6]
set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { LED[7] }]; #IO_L18P_T2_A12_D28_14 Sch=led[7]
set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { LED[8] }]; #IO_L16N_T2_A15_D31_14 Sch=led[8]
set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { LED[9] }]; #IO_L14N_T2_SRCC_14 Sch=led[9]
set_property -dict { PACKAGE_PIN U14 IOSTANDARD LVCMOS33 } [get_ports { LED[10] }]; #IO_L22P_T3_A05_D21_14 Sch=led[10]
set_property -dict { PACKAGE_PIN T16 IOSTANDARD LVCMOS33 } [get_ports { LED[11] }]; #IO_L15N_T2_DQS_DOUT_CSO_B_14 Sch=led[11]
set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS33 } [get_ports { LED[12] }]; #IO_L16P_T2_CSI_B_14 Sch=led[12]
set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports { LED[13] }]; #IO_L22N_T3_A04_D20_14 Sch=led[13]
set_property -dict { PACKAGE_PIN V12 IOSTANDARD LVCMOS33 } [get_ports { LED[14] }]; #IO_L20N_T3_A07_D23_14 Sch=led[14]
set_property -dict { PACKAGE_PIN V11 IOSTANDARD LVCMOS33 } [get_ports { LED[15] }]; #IO_L21N_T3_DQS_A06_D22_14 Sch=led[15]
## 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 K16 IOSTANDARD LVCMOS33 } [get_ports { CC }]; #IO_25_15 Sch=cc
set_property -dict { PACKAGE_PIN K13 IOSTANDARD LVCMOS33 } [get_ports { CD }]; #IO_L17P_T2_A26_15 Sch=cd
set_property -dict { PACKAGE_PIN P15 IOSTANDARD LVCMOS33 } [get_ports { CE }]; #IO_L13P_T2_MRCC_14 Sch=ce
set_property -dict { PACKAGE_PIN T11 IOSTANDARD LVCMOS33 } [get_ports { CF }]; #IO_L19P_T3_A10_D26_14 Sch=cf
set_property -dict { PACKAGE_PIN L18 IOSTANDARD LVCMOS33 } [get_ports { CG }]; #IO_L4P_T0_D04_14 Sch=cg
# 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]
set_property -dict { PACKAGE_PIN T9 IOSTANDARD LVCMOS33 } [get_ports { AN[2] }]; #IO_L24P_T3_A01_D17_14 Sch=an[2]
set_property -dict { PACKAGE_PIN J14 IOSTANDARD LVCMOS33 } [get_ports { AN[3] }]; #IO_L19P_T3_A22_15 Sch=an[3]
set_property -dict { PACKAGE_PIN P14 IOSTANDARD LVCMOS33 } [get_ports { AN[4] }]; #IO_L8N_T1_D12_14 Sch=an[4]
set_property -dict { PACKAGE_PIN T14 IOSTANDARD LVCMOS33 } [get_ports { AN[5] }]; #IO_L14P_T2_SRCC_14 Sch=an[5]
set_property -dict { PACKAGE_PIN K2 IOSTANDARD LVCMOS33 } [get_ports { AN[6] }]; #IO_L23P_T3_35 Sch=an[6]
set_property -dict { PACKAGE_PIN U13 IOSTANDARD LVCMOS33 } [get_ports { AN[7] }]; #IO_L23N_T3_A02_D18_14 Sch=an[7]
## Buttons
set_property -dict { PACKAGE_PIN C12 IOSTANDARD LVCMOS33 } [get_ports { resetn }]; #IO_L3P_T0_DQS_AD1P_15 Sch=cpu_resetn
```
</details>
После выполнения имплементации, нетлист и схема остаются неизменными, однако использованные для реализации схемы примитивы получают свой "адрес" внутри ПЛИС:
![cell_add../.pic/Vivado%20Basics/Implementation%20steps/impl_cell_address.pngress](../.pic/Vivado%20Basics/Implementation%20steps/impl_cell_address.png)
Теперь, мы можем посмотреть на "внутренности" нашей ПЛИС `xc7a100tcsg324-1` и то, как через её примитивы будет реализована наша схема. Для этого, необходимо отрыть имплементированное устройство: `Implementation -> Open implemented design`. Откроется следующее окно:
![../.pic/Vivado%20Basics/Implementation%20steps/impl_fpga_device_full_view.png](../.pic/Vivado%20Basics/Implementation%20steps/impl_fpga_device_full_view.png)
Может показаться очень страшным и непонятным, но это содержимое ПЛИС. Просто из-за огромного количества содержащихся в ней примитивов, она показана в таком масштабе, что все сливается в один цветной ковер. Большая часть этого окна неактивна (показана в темно-синих тонах) и это нормально, ведь мы реализовали крошечную цифровую схему, она и не должна занимать значительное количество ресурсов ПЛИС.
Нас интересует "[бледно-голубая точка](https://ru.wikipedia.org/wiki/Pale_Blue_Dot)", расположенная в нижнем левом углу прямоугольника `X0Y1` (выделено красным). Если отмасштабировать эту зону, мы найдем используемый нами LUT:
![../.pic/Vivado%20Basics/Implementation%20steps/impl_fpga_device_zoomed_view.png](../.pic/Vivado%20Basics/Implementation%20steps/impl_fpga_device_zoomed_view.png)
Кроме того, если поиграться со свойствами этого примитива, мы сможем найти нашу таблицу истинности, инициализирующую этот примитив.
## Generate Bitstream
После того, как САПР определил конкретные примитивы, их режим работы, и пути сигнала между ними, необходимо создать двоичный файл (**bitstream**), который позволит сконфигурировать ПЛИС необходимым нам образом.
Получив этот файл, остается запрограммировать ПЛИС, после чего она воплотит разработанное устройство.
## Выводы
Таким образом, маршрут перехода от HDL-описания устройства до его реализации в ПЛИС выглядит следующим образом:
1. Сперва происходит анализ HDL-описания. В ходе этого анализа выявляются простейшие структуры: регистры, мультиплексоры, вычислительные блоки (сложения/умножения/сдвига и т.п.). Строится граф схемы, построенной с помощью этих структур (**нетлист**). Данный нетлист платформонезависим, т.е. не привязан к конкретной ПЛИС.
2. После происходит этап синтеза нетлиста, полученного на предыдущем этапе в нетлист, использующий имеющиеся ресурсы конкретной ПЛИС. Все, использовавшиеся на предыдущем этапе структуры (регистры, мультиплексоры и прочие блоки) реализуются через примитивы ПЛИС (LUT-ы, D-триггеры, блоки сложения и т.п.).
3. Затем происходит этап размещения схемы внутри ПЛИС: если на предыдущем этапе часть схемы была реализована через LUT, то на этом этапе решается **какой именно** LUT будет использован. Область допустимых решений по этому вопросу сужается путем наложения **ограничений** (**constraints**).
4. После размещения остается только сгенерировать двоичный файл (**bitstream**), который во время прошивки сконфигурирует ПЛИС на реализацию нашей схемы.

View File

@@ -0,0 +1,24 @@
# Руководство по установке Vivado
1) ~~Заводите учётную запись на сайте xilinx.com~~
2) ~~Заходите переходите по адресу: https://www.xilinx.com/support/download.html~~
3) ~~Скачиваете веб-установщик под свою ОС, запускаете его~~
4) ~~Нажимаете "Далее", вводите свои учётные данные, нажимаете "Далее"~~
В связи с блокировкой IP-адресов РФ на серверах Xilinx, пункты 1-4 изменены:
1. Находясь в миэтовской локальной сети (через общажную сеть, или подключившись к [миэтовскому vpn](https://vpn.miet.ru/)), перейдите по [этой ссылке](https://nextcloud.borisblade.ru/s/g4YfWoimpYWH6fS);
2. Скачайте файл `Xilinx_Vivado_2019.2_1106_2127.tar.gz`, распакуйте его. Обратите внимание, что для скачанного архива, его распакованной версии и установленного пакета Vivado потребуется около 76.5Гб свободного места на диске (после установки Vivado скачанный архив и его распакованную версию размером в 53.5Гб можно будет удалить).
3. В открывшемся окне, откажитесь от предложения обновить установочный пакет до актуальной версии, нажав на кнопку "Continue"
![../.pic/Vivado%20Basics/Install%20Vivado/update_suggetion.png](../.pic/Vivado%20Basics/Install%20Vivado/update_suggetion.png)
4. Нажмите кнопку Next, проставьте галочки на всех соглашениях (разумеется желательно прочитав их), снова нажмите Next.
5. В списке вариантов установок, выбираете "Vivado HL WebPACK".
6. В настройках установки, в разделе Devices снимите галочки со всего что можно снять, нажмите Next.
![../.pic/Vivado%20Basics/Install%20Vivado/installation_customization.png](../.pic/Vivado%20Basics/Install%20Vivado/installation_customization.png)
7. Выберите место установки (либо оставьте место по умолчанию), нажмите Next.
8. На последней странице вам будет представлена информация о выбранных вами опциях, использование дискового пространства во время и после установки. Если вас все устраивает, нажмите Install
![../.pic/Vivado%20Basics/Install%20Vivado/final_page.png](../.pic/Vivado%20Basics/Install%20Vivado/final_page.png)

View File

@@ -0,0 +1,13 @@
# Как прошить ПЛИС
После того, как вы создали свой модуль и проверили его на прохождение тестбенча, вы можете использовать предоставленный в папке `board files` модуль окружения, который позволяет связать вашу логику с периферией, расположенной на плате `Nexys-A7`. Для его подключения, скачайте и добавьте файл в проект, либо скопируйте содержимое в новый `.v` файл вашего проекта. В окне `Sources` нажмите на него ПКМ и выберите `Set as Top`, после чего в иерархии он станет главным, подключив ваш собственный модуль. Для того, чтобы дизайн мог физически подключиться к периферии, нужно в проекте выбрать `Add Sources`, `Add or create constraints` и подключить файл `nexys_a7_100t.xdc`. Если у вас уже подключен этот файл, необходимо заменить данные на те, которые предложены в текущей папке `board files`.
Для прошивки ПЛИС подключите устройство через USB, включите питание переключателем, выполните синтез и имплементацию вашего дизайна и сгенерируйте битстрим. Если на этом этапе у вас возникают ошибки, постарайтесь исправить их с помощью [`инструкции по работе с ошибками`](Elaboration%20failed.md).
Все этапы проходят достаточно медленно, подробнее о них можно узнать [`здесь`](Implementation%20steps.md). По завершению у вас всплывет окно, информирующее об окончании генерации битстрима, для следующего шага вы можете выбрать пункт `Open Hardware Manager` и нажать `OK`, либо нажать `Cancel` и выбрать в левом меню в самом низу `Open Hardware Manager`, `Open Target` - `Auto Connect`, затем `Program Device` и ваше устройство прошьется.
Генерация битстрима
![../.pic/Labs/board%20files/Program_Device1.png](../.pic/Labs/board%20files/Program_Device1.png)
Прошивка ПЛИС
![../.pic/Labs/board%20files/Program_Device2.png](../.pic/Labs/board%20files/Program_Device2.png)

18
Vivado Basics/README.md Normal file
View File

@@ -0,0 +1,18 @@
# Основа работы с Vivado
Цикл лабораторных работ создан, чтобы вы могли на практике отработать полученные знания по архитектурам процессорных систем, увидеть "изнутри", как "бегают нолики и единицы", подобно тому, как они бегают и в ваших компьютерах.
Для эффективного погружения в лабораторные работы используется САПР **Vivado**. Это довольно сложный инструмент, на одно только осваивание которого требуется порядочное количество времени.
Дабы сократить порог вхождения в освоение этого инструмента, был написан ряд материалов по описанию базовых сценариев использования, который представлен в данной папке.
Здесь находятся инструкции о том как:
1. [Установить Vivado](Install%20Vivado.md)
2. [Создать демо-проект под отладочный стенд Nexys-7](Vivado%20trainer.md)
3. [Загрузить сделанную лабу в ПЛИС](Program%20nexys%20a7.md)
4. [Понять структуру папок в проекте Vivado](Folder%20Structure%20In%20The%20Project.md)
5. [Открыть логическую схему написанного вами модуля](How%20to%20open%20a%20schematic.md)
6. [Запустить симуляцию](Run%20Simulation.md)
7. [Разобраться с ошибками, при попытке открыть схему / запустить симуляцию](Elaboration%20failed.md)
8. [Находить и исправлять ошибки дизайна, найденные тестовым окружением](Debug_manual.md)
9. [Добавить заголовочный файл в проект Vivado](Verilog%20Header.md)
10. [Добавить файл инициализации памяти в проект Vivado](How%20to%20add%20a%20mem-file.md)
11. [Понять как работают этапы элаборации/синтеза/имплементации](Implementation%20steps.md)

View File

@@ -0,0 +1,25 @@
# Как запустить симуляцию модуля в Vivado
При добавлении `tb` модулей и подключении к ним `Design Sources`, мы видим, как в уровнях иерархии модуль симуляции становится самым верхним, включая в себя все модули вашего дизайна. Так называемый test bench (tb) — виртуальный стенд, куда мы поместили наш `top` модуль. Надо обратить внимание, что `tb` модуль не имеет портов ввода-вывода: подключенные сигналы для нашей схемы описываются непосредственно средствами языка Verilog.
Иерархия модулей проекта.
![../.pic/Vivado%20Basics/Run%20Simulation/Run_sim1.png](../.pic/Vivado%20Basics/Run%20Simulation/Run_sim1.png)
Есть 2 способа запустить симуляцию
1. На панели слева в разделе `SIMULATION` нажать `Run Simulation` - `Run Behavioral Simulation`.
![../.pic/Vivado%20Basics/Run%20Simulation/Run_sim2.png](../.pic/Vivado%20Basics/Run%20Simulation/Run_sim2.png)
2. В иерархии проекта нажать по папке `sim_1` ПКМ, далее выбрать `Run Simulation`.
![../.pic/Vivado%20Basics/Run%20Simulation/Run_sim3.png](../.pic/Vivado%20Basics/Run%20Simulation/Run_sim3.png)
Автоматически симуляция запускается на определенный промежуток времени, после чего останавливается, если этого времени не хватает для прохождения всех этапов симуляции, и в консоли вы не видите результат ее прохождения, тогда необходимо обратиться к появившимся вверху кнопкам управления и нажать на `Run All`(треугольный символ воспроизведения), после чего убедиться, что в консоли выдало информацию о достижении конца симуляции.
Важное замечание: если вы изменили топ-модуль симуляции, то вам необходимо закрыть текущую симуляцию. Без этого новая не сможет запуститься и будет выдавать ошибку. Подробнее об этой ошибке можно узнать в [`списке типичных ошибок`](../Other/FAQ.md#%D0%BD%D0%B5-%D0%B7%D0%B0%D0%BF%D1%83%D1%81%D0%BA%D0%B0%D0%B5%D1%82%D1%81%D1%8F-%D1%81%D0%B8%D0%BC%D1%83%D0%BB%D1%8F%D1%86%D0%B8%D1%8F-boot-filesystem-remove-%D0%BF%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81-%D0%BD%D0%B5-%D0%BC%D0%BE%D0%B6%D0%B5%D1%82-%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF-%D0%BA-%D1%84%D0%B0%D0%B9%D0%BB%D1%83). Это решается закрытием симуляции. Закрыть ее можно нажав ПКМ по шапке, расположенной сразу под кнопками управления симуляцией и выбрать `Close`, так же можно найти справа крестик этой шапки, либо нажать ПКМ по **разделу** `SIMULATION`, находящегося на панели слева, и выбрать `Close Simulation`, что так же закроет текущую симуляцию,
![../.pic/Vivado%20Basics/Run%20Simulation/Run_sim4.png](../.pic/Vivado%20Basics/Run%20Simulation/Run_sim4.png)
Для перезапуска симуляции не обязательно действовать так, как описано в двух способах запуска, можно воспользоваться иконкой закругленной стрелки, она это делает в один клик.

View File

@@ -0,0 +1,19 @@
# Как добавить файл с define
Директива `ˋinclude` позволяет вставлять код из одного файла, в код другого, подобно `#include` на языке `C`. Если вы описываете данную директиву, когда подключаемый файл не добавлен в качестве Verilog Header в проект, Vivado будет интерпретировать её как синтаксическую ошибку, поскольку не сможет найти подключаемый файл, и тогда ваш модуль будет находиться в папке `Syntax Error Files` иерархии вашего проекта.
![../.pic/Vivado%20Basics/Verilog%20Header/Verilog_Header1.png](../.pic/Vivado%20Basics/Verilog%20Header/Verilog_Header1.png)
Файл в проект добавляется точно так же, как при создании Verilog-файла, только вместо `Create File` нужно нажать `Add Files`, затем перейти к его расположению, выбрать его и нажать `OK` и `Finish`.
После обновления иерархии вашего проекта, этот файл будет располагаться в папке `Non-module Files`.
![../.pic/Vivado%20Basics/Verilog%20Header/Verilog_Header2.png](../.pic/Vivado%20Basics/Verilog%20Header/Verilog_Header2.png)
Следующим шагом нужно нажать по этому файлу `ПКМ`, выделив его, убедившись, что в окне ниже выбран именно он, необходимо сменить его тип на `Verilog Header`.
![../.pic/Vivado%20Basics/Verilog%20Header/Verilog_Header3.png](../.pic/Vivado%20Basics/Verilog%20Header/Verilog_Header3.png)
После этого нужно убедиться, что наш файл появился в иерархии проекта в папке `Verilog Header`, а наш файл с модулем больше не лежит в папке `Syntax Error Files`.
![../.pic/Vivado%20Basics/Verilog%20Header/Verilog_Header4.png](../.pic/Vivado%20Basics/Verilog%20Header/Verilog_Header4.png)

View File

@@ -0,0 +1,99 @@
# Создание базового проекта с прошивкой ПЛИС в Vivado
## Создание проекта в Системе Автоматизированного Проектирования (САПР)
1. Запустить Vivado 2019.2
2. Нажать `Create Project`
3. В открывшемся окне нажать Next
4. Ввести название проекта (никаких пробелов и кириллических символов) → Выбрать папку для проектов (создать каталок на D:/) → Поставить галку `Create project subdirectory` → Нажать `Next`
5. Выбрать RTL Project → Поставить галку `Do not specify sources at this time` → Нажать Next
6. Выставить фильтры, для сужения списка ПЛИС:
* Family: `Artix 7`
* Package: `CSG324`,
* Speed: `-1`.
<details>
<summary> Скриншот окна с выставленными фильтрами</summary>
![Скриншот окна с выставленными фильтрами](../.pic/Vivado%20Basics/Vivado%20trainer/fpga_filter.png)
</details>
7. В списке выбрать ПЛИС `xc7a100tcsg324-1` → Нажать Next
8. Нажать Finish
9. Закрыть Vivado
10. Удалить папку
11. Повторить все действия самостоятельно
## Создание модуля на Verilog
1. Создать новый Verilog файл, для этого в окне `Sources` нажать на кнопку `+`
2. В открывшемся окне выбрать `Add or create design source` → Нажать `Next`
3. Нажать `Create File`В открывшемся окне ввести имя модуля `top` → Нажать `OK`В оставшемся окне нажать `Finish`
4. В открывшемся окне НЕ вводить названия портов и сразу нажать OK → После чего подтвердить выбор `Yes`
5. Двойным кликов в окне `Source` открыть файл `top.v`
6. Написать следующий код:
```Verilog
module top (
input clk,
input a,
input b,
output reg q
);
wire c;
assign c = a ^ b;
always @ (posedge clk) begin
q <= c;
end
endmodule
```
7. Сохранить изменения
8. Нажать `Open Elaborated Design`
9. Нажать `Schematic` в открывшемся списке
10. Проанализировать полученный результат (сопоставить с Verilog-описанием)
11. Закрыть проект
## Реализация простого проекта на отладочном стенде
1. Создать новый проект
2. Создать новый Verilog файл с названием basic
3. Написать следующий код:
```Verilog
module basic (
input [15:0] SW,
output [15:0] LED
);
assign LED[0] = SW[0] & SW[1];
assign LED[2] = SW[2] | SW[3];
assign LED[4] = SW[4] ^ SW[5];
assign LED[10:6] = ~SW[10:6];
assign LED[13:11] = {SW[11], SW[12], SW[13]};
assign LED[15:14] = { 2{SW[14]} };
endmodule
```
4. Сохранить изменения
5. В окне Sources нажать на кнопку `+`
6. В открывшемся окне выбрать `Add or create constraints` → Нажать Next
7. Нажать `Create File`В открывшемся окне ввести название → Нажать `OK``Finish`
8. В окне `Source` в открывающемся списке `Constraints` найти только что созданный файл и открыть его дя редактирования двойным щелчком
9. Скопировать содержимое файла констрейнов с [официального сайта](https://github.com/Digilent/digilent-xdc) и вставить в только что созданный → Найти строки посвященные SW и LED и раскомментировать их → Сохранить изменения
10. `Run Synthesis`
11. `Run Implementation`
12. После успешной имплементации нажимаем `Generate Bitstream` для генерации файла прошивки
13. Аккуратно достаем и подключаем стенд к компьютеру → Включаем питание на плате
14. Нажимаем `Open Hardware Manager` (под `Generate Bitstream`)
15. Вместо окна `Source` будет отображаться окно `Hardware`, в нем необходимо нажать кнопку `Auto Connect` (единственная активная кнопка) → В окне появится подключенное устройство
16. Нажать правой кнопкой на устройстве `xc7a100t_0` → Выбрать пункт меню `Program Device`
17. В открывшемся окне нажать `Program`
18. Сопоставить поведение отладочной платы с Verilog-описанием