# Лабораторная работа №1 "Сумматор"

## Цель

Познакомиться с САПР Vivado и научиться реализовывать в нём простейшие схемотехнические модули с помощью конструкций языка SystemVerilog.

## Материал для подготовки к лабораторной работе

[Описание модулей на языке SystemVerilog](../../Basic%20Verilog%20structures/Modules.md).

## Ход работы

1. Изучение 1-битного сумматора;
2. Воспроизведение примера по реализации и проверке полусумматора.
3. Реализация и проверка полного 1-битного сумматора
4. Изучение 4-битного сумматора;
5. Реализация и проверка полного 4-битного сумматора;
6. Реализация и проверка полного 32-битного сумматора.

## Теория

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

Давайте начнём с примера и сложим в столбик произвольную пару чисел, например 42 и 79:

![../../.pic/Labs/lab_01_adder/column_add_dec.drawio.svg](../../.pic/Labs/lab_01_adder/column_add_dec.drawio.svg)

```text
2 + 9             = 11 ➨ 1 пишем, 1 "в уме"
4 + 7 + "1 в уме" = 12 ➨ 2 пишем, 1 "в уме"
0 + 0 + "1 в уме" = 1
```

Итого, 121.

Назовём то, что мы звали "1 в уме", переносом разряда.

Теперь попробуем сделать то же самое, только в двоичной системе исчисления. К примеру, над числами 3 и 5. Три в двоичной системе записывается как 011. Пять записывается как 101.

![../../.pic/Labs/lab_01_adder/column_add_bin.drawio.svg](../../.pic/Labs/lab_01_adder/column_add_bin.drawio.svg)

Поскольку в двоичной системе всего две цифры: 0 и 1, один разряд не может превысить 1. Складывая числа 1 и 1, вы получаете 2, что не умещается в один разряд, поэтому мы пишем 0 и держим 1 "в уме". Это снова перенос разряда. Поскольку в двоичной арифметике разряд называют битом, перенос разряда можно назвать переносом бита, а сам разряд, который перенесли — битом переноса.

### Полный 1-битный сумматор

Полный 1-битный сумматор — это цифровое устройство, которое выполняет сложение двух 1-битных чисел и учитывает входной бит переноса. Это устройство имеет три входа: два слагаемых и входной бит переноса, а также два выхода: 1-битный результат суммы и выходной бит переноса.

Что такое входной бит переноса? Давайте вспомним второй этап сложения чисел 42 и 79:

```text
4 + 7 + "1 в уме" = 12 ➨ 2 пишем, 1 "в уме"
```

**+ "1 в уме"** — это прибавление разряда, перенесённого с предыдущего этапа сложения.

Входной бит переноса — это бит, перенесённый с предыдущего этапа сложения двоичных чисел. Имея этот сигнал, мы можем складывать многоразрядные двоичные числа путём последовательного соединения нескольких 1-битных сумматоров: выходной бит переноса сумматора младшего разряда передастся на входной бит переноса сумматора старшего разряда.

### Реализация одноразрядного сложения

Можно ли как-то описать сложение двух одноразрядных двоичных чисел с помощью логических операций? Давайте посмотрим на таблицу истинности подобной операции:

![../../.pic/Labs/lab_01_adder/tt1.png](../../.pic/Labs/lab_01_adder/tt1.png)

_Таблица истинности одноразрядного сложения._

`S` — это младший разряд 2-битного результата суммы, записываемый в столбце сложения под слагаемыми `a` и `b`. `C` (_carry_, перенос) — это старший разряд суммы, записываемый левее, если произошёл перенос разряда. Как мы видим, перенос разряда происходит только в случае, когда оба числа одновременно равны единице. При этом значение `S` обращается в `0`, и результат записывается как `10`, что в двоичной системе означает `2`. Кроме того, значение `S` равно `0` и в случае, когда оба операнда одновременно равны нулю. Вы можете заметить, что `S` равно нулю в тех случаях, когда `а` и `b` равны, и не равно нулю в противоположном случае. Подобным свойством обладает логическая операция **Исключающее ИЛИ** (**eXclusive OR**, **XOR**), именно поэтому одно из других названий этой операции — сумма по модулю 2.

![../../.pic/Labs/lab_01_adder/tt2.png](../../.pic/Labs/lab_01_adder/tt2.png)

_Таблица истинности операции Исключающее ИЛИ (XOR)._

Для бита переноса всё ещё проще — он описывается операцией **логическое И**:

![../../.pic/Labs/lab_01_adder/tt3.png](../../.pic/Labs/lab_01_adder/tt3.png)

_Таблица истинности операции И._

На _рис. 1_ представлена цифровая схема, связывающая входные и выходные сигналы с помощью логических элементов, соответствующих ожидаемому поведению.

![../../.pic/Labs/lab_01_adder/fig_01.drawio.svg](../../.pic/Labs/lab_01_adder/fig_01.drawio.svg)

_Рисунок 1. Цифровая схема устройства, складывающего два операнда с сохранением переноса (полусумматора)._

Однако, в описании полного 1-битного сумматора сказано, что у него есть три входа, а в наших таблицах истинности и на схеме выше их только два (схема, представленная на _рис. 1_, реализует так называемый "полусумматор"). На самом деле, на каждом этапе сложения в столбик мы всегда складывали три числа: цифру верхнего числа, цифру нижнего числа, и единицу в случае переноса разряда из предыдущего столбца (если с предыдущего разряда не было переноса, прибавление нуля неявно опускалось).

Таким образом, таблица истинности немного усложняется:

![../../.pic/Labs/lab_01_adder/tt4.png](../../.pic/Labs/lab_01_adder/tt4.png)

_Таблица истинности сигналов полного 1-битного сумматора._

Поскольку теперь у нас есть и входной и выходной биты переноса, для их различия добавлены суффиксы "in" и "out".

Как в таком случае описать S? Например, как сумму по модулю 2 этих трёх слагаемых: `а ⊕ b ⊕ Cіn`. Давайте сравним такую операцию с таблицей истинности. Сумма по модуля 2 — это ассоциативная операция [`(a⊕b)⊕c = a⊕(b⊕с)`], т.е. порядок сложения не влияет на результат. Предположим, что Cin равен нулю. Сумма по модулю 2 с нулём даёт второй операнд (`a⊕0=a`), значит `(a⊕b)⊕0 = a⊕b`. Это соответствует верхней половине таблицы истинности для сигнала S, когда Cin равен нулю.
Предположим, что Cin равен единице. Сумма по модулю 2 с единицей даёт нам отрицание второго операнда (`a⊕1=!a`), значит `(a⊕b) ⊕1=!(a⊕b)`. Это соответствует нижней половине таблицы истинности, когда Cin равен единице.

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

`Cоut = (a&b) | (а&Cіn) | (b&Cіn)`, где `&` — логическое И, `|` — логическое ИЛИ.

Цифровая схема устройства с описанным поведением выглядит следующим образом:

![../../.pic/Labs/lab_01_adder/fig_02.drawio.svg](../../.pic/Labs/lab_01_adder/fig_02.drawio.svg)

_Рисунок 2. Цифровая схема полного 1-битного сумматора._

## Практика

Реализуем схему полусумматора (_рис. 1_) в виде модуля, описанного на языке SystemVerilog.

Модуль `half_adder` имеет два входных сигнала и два выходных. Входы `a_i` и `b_i` идут на два логических элемента: Исключающее ИЛИ и И, выходы которых подключены к выходам модуля `sum_o` и `carry_o` соответственно.

```Verilog
module half_adder(
  input  logic    a_i,     // Входные сигналы
  input  logic    b_i,

  output logic    sum_o,   // Выходные сигналы
  output logic    carry_o
);

assign sum_o = a_i ^ b_i;
assign carry_o = a_i & b_i;

endmodule
```

_Листинг 1. SystemVerilog-код модуля half_adder._

По данному коду, САПР может реализовать схему, представленную на рисунке 3.

![../../.pic/Labs/lab_01_adder/fig_03.png](../../.pic/Labs/lab_01_adder/fig_03.png)

_Рисунок 3. Цифровая схема модуля half_adder, сгенерированная САПР Vivado._

Схема похожа на _рис. 1_, но как проверить, что эта схема не содержит ошибок и делает именно то, что от неё ожидается?

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

Подаваемые на схему входные воздействия формируются верификационным окружением. Верификационное окружение (в дальнейшем будет использован термин "**тестбенч**") — это особый несинтезируемый модуль, который не имеет входных или выходных сигналов. Эти сигналы ему не нужны, потому что он сам является генератором всех своих внутренних сигналов, и данный модуль не передаёт ничего вовне — только проверяет тестируемый модуль внутри себя.

Внутри тестбенча можно использовать конструкции из несинтезируемого подмножества языка SystemVerilog, в частности программный блок `initial`, в котором команды выполняются последовательно, что делает этот блок чем-то отдалённо похожим на проверяющую программу. Поскольку изменение внутренних цепей происходит с некоторой задержкой относительно изменений входных сигналов, при моделировании есть возможность делать паузы между командами. Это делается с помощью специального символа #, за которым указывается количество времени симуляции, которое нужно пропустить перед следующей командой.

Перед тем как писать верификационное окружение, необходимо составить план того, как будет проводиться проверка устройства (составить верификационный план). Ввиду предельной простоты устройства, план будет состоять из одного предложения:

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

```Verilog
module testbench();                // <- Не имеет ни входов, ни выходов!
  logic a, b, carry, sum;

  half_adder DUT(                  // <- Подключаем проверяемый модуль
    .a_i    (a    ),
    .b_i    (b    ),
    .carry_o(carry),
    .sum_o  (sum  )
);

  initial begin
    a = 1'b0; b = 1'b0;            // <- Подаём на входы модуля тестовые
    #10ns;                         //    воздействия
    a = 1'b0; b = 1'b1;
    #10ns;                         // <- Делаем паузу в десять наносекунд
    a = 1'b1; b = 1'b0;            //    перед очередным изменением
    #10ns;                         //    входных сигналов
    a = 1'b1; b = 1'b1;
  end
endmodule
```

_Листинг 2. SystemVerilog-код тестбенча для модуля half_adder._

![../../.pic/Labs/lab_01_adder/fig_04.png](../../.pic/Labs/lab_01_adder/fig_04.png)

_Рисунок 4. Временная диаграмма, моделирующая работу схемы с рис. 3._

В данной лабораторной работе вам предстоит реализовать схему полного 1-битного сумматора (_рис. 2_). Модуль полусумматора, код которого представлен в _листинге 1_ не используется в лабораторной работе (он был дан только в качестве примера).

### Полный 4-битный сумматор

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

Давайте посмотрим, как это будет выглядеть на схеме (для простоты, внутренняя логика 1-битного сумматора скрыта, но вы должны помнить, что каждый прямоугольник — это та же самая схема с рис. 2).

![../../.pic/Labs/lab_01_adder/fig_05.drawio.svg](../../.pic/Labs/lab_01_adder/fig_05.drawio.svg)

_Рисунок 5. Схема 4-битного сумматора._

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

Как же реализовать модуль, состоящий из цепочки других модулей? Половину этой задачи мы уже сделали, когда писали тестбенч к 1-битному полусумматору в _Листинге 2_ — мы создавали модуль внутри другого модуля и подключали к нему провода. Теперь надо сделать то же самое, только с чуть большим числом модулей.

Описание 4-битного сумматора, сводится к описанию межсоединения четырёх экземпляров 1-битного сумматора. Подробнее о том, как описывать создание экземпляров модулей рассказано в главе [Описание модулей на языке SystemVerilog](../../Basic%20Verilog%20structures/Modules.md#Иерархия-модулей), который вы изучали перед лабораторной работой.

![../../.pic/Labs/lab_01_adder/fig_06.png](../../.pic/Labs/lab_01_adder/fig_06.png)

_Рисунок 6. Схема 4-битного сумматора, сгенерированная САПР Vivado._

Несмотря на запутанность схемы, если присмотреться, вы увидите, как от шин A, B и S отходят линии к каждому из сумматоров, а бит переноса передаётся от предыдущего сумматора к следующему. Для передачи битов переноса от одного сумматора к другому, потребуется создать вспомогательные провода, которые можно сгруппировать в один [вектор](../../Basic%20Verilog%20structures/Modules.md#Векторы) (см. сигналы c[0]-c[2] на _рис. 5_).

## Задание

Опишите полный 1-битный сумматор, схема которого представлена на _[Рис. 2](../../.pic/Labs/lab_01_adder/fig_02.drawio.svg)_. Прототип модуля следующий:

```Verilog
module fulladder(
  input  logic a_i,
  input  logic b_i,
  input  logic carry_i,
  output logic sum_o,
  output logic carry_o
);
```

Далее, вам необходимо реализовать полный 32-битный сумматор со следующим прототипом:

```verilog
module fulladder32(
    input  logic [31:0] a_i,
    input  logic [31:0] b_i,
    input  logic        carry_i,
    output logic [31:0] sum_o,
    output logic        carry_o
);
```

Соединять вручную 32 однотипных модуля чревато усталостью и ошибками, поэтому можно сначала создать 4-битный сумматор, а затем из набора 4-битных сумматоров сделать 32-битный.

Если вы решите делать 4-битный сумматор, то модуль должен быть описан в соответствии со следующим прототипом:

```Verilog
module fulladder4(
  input  logic [3:0] a_i,
  input  logic [3:0] b_i,
  input  logic       carry_i,
  output logic [3:0] sum_o,
  output logic       carry_o
);
```

Либо же можно создать массив 1-битных сумматоров.

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

- если разрядность подключаемого сигнала совпадает с разрядностью порта модуля из массива, этот сигнал подключается к каждому из модулей в массиве;
- если разрядность подключаемого сигнала превосходит разрядность порта модуля из массива в `N` раз (где `N` — количество модулей в массиве), к модулю подключается соответствующий диапазон бит подключаемого сигнала (диапазон младших бит будет подключён к модулю с меньшим индексом в массиве).
- если разрядность подключаемого сигнала не подходит ни под один из описанных выше пунктов, происходит ошибка синтеза схемы, поскольку в этом случае САПР не способен понять каким образом подключать данный сигнал к каждому модулю из массива.

В _листинге 3_ представлен пример того, как можно создать массив модулей.

```Verilog
module example1(
  input  logic [3:0] a,
  input  logic       b,
  output logic       c,
  output logic       d
);

  assign c = |a ^ b;
  assign d = &a;

endmodule

module example2(
  input  logic [31:0] A,
  input  logic        B,
  output logic [ 8:0] C
);

example1 instance_array[7:0]( // Создается массив из 8 модулей example1
  .a(A),                      // Поскольку разрядность сигнала A в 8 раз больше
                              // разрядности входа a, к каждому модулю в массиве
                              // будет подключен свой диапазон бит сигнала A
                              // (к instance_array[0] будет подключен диапазон
                              // A[3:0], к instance_array[7] будет подключен
                              // диапазон A[31:28]).

  .b(B),                      // Поскольку разрядность сигнала B совпадает с
                              // разрядностью входа b, сигнал B будет подключен
                              // как есть ко всем модулям в массиве.

  .c(C[7:0]),                 // Поскольку разрядность сигнала C не равна
                              // ни разрядности входа c, ни его увосьмерённой
                              // разрядности, мы должны выбрать такой диапазон
                              // бит, который будет удовлетворять одному из
                              // этих требований.

  .d(C[8])                    // Аналогично предыдущему.
);
endmodule
```

_Листинг 3. Пример создания массива модулей._

Реализация массива сумматоров будет осложнена тем, что вам потребуется каким-то образом организовать передачу выходного бита переноса предыдущего разряда до входного бита переноса следующего разряда. Для этого рекомендуется создать два 32-битных вектора:

- вектор входных битов переноса;
- вектор выходных битов переноса.

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

После того, как векторы бит переноса будут готовы, создание массива модулей уже не будет представлять сложности.

### Порядок выполнения задания

1. Создайте проект, согласно [руководству по созданию проекта в Vivado](../../Vivado%20Basics/01.%20New%20project.md)
2. Опишите модуль `fulladder`, схема которого представлена на _[Рис. 2](../../.pic/Labs/lab_01_adder/fig_02.drawio.svg)_.
   1. Модуль необходимо описать с таким же именем и портами, как указано в задании.
3. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_01.tb_fulladder.sv`](lab_01.tb_fulladder.sv). Убедитесь по сигналам временной диаграммы, что модуль работает корректно. В случае обнаружения некорректного поведения сигналов суммы и выходного бита переноса, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) причину этого поведения, и устранить её.
4. Опишите модуль `fulladder4`, схема которого представлена на _Рис. 5 и 6_, используя [`иерархию модулей`](../../Basic%20Verilog%20structures/Modules.md#Иерархия-модулей), чтобы в нем выполнялось поразрядное сложение двух 4-битных чисел и входного бита переноса. Некоторые входы и выходы модуля будет необходимо описать в виде [`векторов`](../../Basic%20Verilog%20structures/Modules.md#Векторы).
   1. Модуль необходимо описать с таким же именем и портами, как указано в задании.
   2. Обратите внимание, что входной бит переноса должен подаваться на сумматор, выполняющий сложение нулевого разряда, выходной бит переноса соединяется с выходным битом переноса сумматора, выполняющего сложение 4-го разряда. Промежуточные биты переноса передаются с помощью вспомогательных проводов, которые необходимо создать самостоятельно.
5. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_01.tb_fulladder4.sv`](lab_01.tb_fulladder4.sv). Убедитесь по сигналам временной диаграммы, что модуль работает корректно. В случае обнаружения некорректного поведения сигналов суммы и выходного бита переноса, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) причину этого поведения, и устранить её.
   1. Перед запуском моделирования убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`.
6. Опишите модуль `fulladder32` так, чтобы в нем выполнялось поразрядное сложение двух 32-битных чисел и входного бита переноса. Его можно реализовать через последовательное соединение восьми 4-битных сумматоров, либо же можно соединить 32 1-битных сумматора (как вручную, так и с помощью создания массива модулей).
   1. Модуль необходимо описать с таким же именем и портами, как указано в задании.
   2. Обратите внимание, что входной бит переноса должен подаваться на сумматор, выполняющий сложение нулевого разряда, выходной бит переноса соединяется с выходным битом переноса сумматора, выполняющего сложение 31-го разряда.
7. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_01.tb_fulladder32.sv`](lab_01.tb_fulladder32.sv). В случае, если в TCL-консоли появились сообщения об ошибках, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) и исправить их.
   1. Перед запуском моделирования убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`.
8. [Проверьте](./board%20files) работоспособность вашей цифровой схемы в ПЛИС.
