Compare commits

..

2 Commits

Author SHA1 Message Date
Andrei Solodovnikov
047d4e65a6 Eng. Lab#2 2026-04-13 10:31:30 +03:00
Andrei Solodovnikov
ca0ae579f3 Eng. Lab#1 2026-04-13 10:03:43 +03:00
2 changed files with 269 additions and 274 deletions

View File

@@ -1,121 +1,119 @@
# Лабораторная работа №1 "Сумматор"
# Lab 1 "Adder"
## Цель
## Goal
Познакомиться с САПР Vivado и научиться реализовывать в нём простейшие схемотехнические модули с помощью конструкций языка SystemVerilog.
Get familiar with the Vivado EDA tool and learn how to implement basic circuit modules using SystemVerilog language constructs.
## Материал для подготовки к лабораторной работе
## Preparation Materials
[Описание модулей на языке SystemVerilog](../../Basic%20Verilog%20structures/Modules.md).
[Describing modules in SystemVerilog](../../Basic%20Verilog%20structures/Modules.md).
## Ход работы
## Workflow
1. Изучение 1-битного сумматора;
2. Воспроизведение примера по реализации и проверке полусумматора.
3. Реализация и проверка полного 1-битного сумматора
4. Изучение 4-битного сумматора;
5. Реализация и проверка полного 4-битного сумматора;
6. Реализация и проверка полного 32-битного сумматора.
1. Study the 1-bit adder;
2. Reproduce the example of implementing and verifying a half adder.
3. Implement and verify a full 1-bit adder
4. Study the 4-bit adder;
5. Implement and verify a full 4-bit adder;
6. Implement and verify a full 32-bit adder.
## Теория
## Theory
Итогом лабораторной работы будет создание устройства, способного складывать два числа. Но перед тем, как учиться создавать подобное устройство, необходимо немного освоиться в самом процессе складывания чисел.
The outcome of this lab will be a device capable of adding two numbers. But before learning how to build such a device, it is necessary to get comfortable with the addition process itself.
Давайте начнём с примера и сложим в столбик произвольную пару чисел, например 42 и 79:
Let's start with an example and add a pair of numbers in column format, say 42 and 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
2 + 9 = 11 ➨ write 1, carry 1
4 + 7 + carry 1 = 12 ➨ write 2, carry 1
0 + 0 + carry 1 = 1
```
Итого, 121.
Total: 121.
Назовём то, что мы звали "1 в уме", переносом разряда.
Теперь попробуем сделать то же самое, только в двоичной системе исчисления. К примеру, над числами 3 и 5. Три в двоичной системе записывается как 011. Пять записывается как 101.
Now let's do the same thing in binary. For example, with the numbers 3 and 5. Three in binary is 011. Five is 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 "в уме". Это снова перенос разряда. Поскольку в двоичной арифметике разряд называют битом, перенос разряда можно назвать переносом бита, а сам разряд, который перенесли — битом переноса.
Since binary has only two digits — 0 and 1 — a single bit cannot exceed 1. When adding 1 and 1, you get 2, which does not fit in one bit, so we write 0 and carry 1. This is again a carry. Since binary digits are called bits, the carry is called a carry bit.
### Полный 1-битный сумматор
### Full 1-bit Adder
Полный 1-битный сумматор — это цифровое устройство, которое выполняет сложение двух 1-битных чисел и учитывает входной бит переноса. Это устройство имеет три входа: два слагаемых и входной бит переноса, а также два выхода: 1-битный результат суммы и выходной бит переноса.
A full 1-bit adder is a digital device that adds two 1-bit numbers and accounts for an incoming carry bit. It has three inputs — two operands and a carry-in — and two outputs: a 1-bit sum result and a carry-out.
Что такое входной бит переноса? Давайте вспомним второй этап сложения чисел 42 и 79:
What is the carry-in? Let's recall the second step of adding 42 and 79:
```text
4 + 7 + "1 в уме" = 12 ➨ 2 пишем, 1 "в уме"
4 + 7 + carry 1 = 12 ➨ write 2, carry 1
```
**+ "1 в уме"** — это прибавление разряда, перенесённого с предыдущего этапа сложения.
**+ carry 1** — this is the addition of the carry bit propagated from the previous step.
Входной бит переноса — это бит, перенесённый с предыдущего этапа сложения двоичных чисел. Имея этот сигнал, мы можем складывать многоразрядные двоичные числа путём последовательного соединения нескольких 1-битных сумматоров: выходной бит переноса сумматора младшего разряда передастся на входной бит переноса сумматора старшего разряда.
The carry-in is the bit propagated from the previous stage of binary addition. With this signal, we can add multi-bit binary numbers by chaining multiple 1-bit adders: the carry-out of the lower-order adder is fed into the carry-in of the higher-order adder.
### Реализация одноразрядного сложения
### Single-bit Addition Implementation
Можно ли как-то описать сложение двух одноразрядных двоичных чисел с помощью логических операций? Давайте посмотрим на таблицу истинности подобной операции:
Can single-bit binary addition be described using logical operations? Let's look at the truth table for this operation:
![../../.pic/Labs/lab_01_adder/tt1.png](../../.pic/Labs/lab_01_adder/tt1.png)
_Таблица истинности одноразрядного сложения._
_Truth table for single-bit addition._
`S` — это младший разряд 2-битного результата суммы, записываемый в столбце сложения под слагаемыми `a` и `b`. `C` (_carry_, перенос) — это старший разряд суммы, записываемый левее, если произошёл перенос разряда. Как мы видим, перенос разряда происходит только в случае, когда оба числа одновременно равны единице. При этом значение `S` обращается в `0`, и результат записывается как `10`, что в двоичной системе означает `2`. Кроме того, значение `S` равно `0` и в случае, когда оба операнда одновременно равны нулю. Вы можете заметить, что `S` равно нулю в тех случаях, когда `а` и `b` равны, и не равно нулю в противоположном случае. Подобным свойством обладает логическая операция **Исключающее ИЛИ** (**eXclusive OR**, **XOR**), именно поэтому одно из других названий этой операции — сумма по модулю 2.
`S` is the least significant bit of the 2-bit sum result, written in the sum column below operands `a` and `b`. `C` (_carry_) is the most significant bit of the sum, written to the left when a carry occurs. As we can see, a carry occurs only when both numbers are simultaneously equal to one. In this case, `S` becomes `0` and the result is written as `10`, which equals `2` in binary. Additionally, `S` equals `0` when both operands are simultaneously zero. You may notice that `S` is zero when `a` and `b` are equal, and non-zero otherwise. This property belongs to the **Exclusive OR** (**XOR**) logical operation, which is why another name for this operation is "sum modulo 2".
![../../.pic/Labs/lab_01_adder/tt2.png](../../.pic/Labs/lab_01_adder/tt2.png)
_Таблица истинности операции Исключающее ИЛИ (XOR)._
_Truth table for the Exclusive OR (XOR) operation._
Для бита переноса всё ещё проще — он описывается операцией **логическое И**:
The carry bit is even simpler — it is described by the **logical AND** operation:
![../../.pic/Labs/lab_01_adder/tt3.png](../../.pic/Labs/lab_01_adder/tt3.png)
_Таблица истинности операции И._
_Truth table for the AND operation._
На _рис. 1_ представлена цифровая схема, связывающая входные и выходные сигналы с помощью логических элементов, соответствующих ожидаемому поведению.
_Fig. 1_ shows the digital circuit connecting inputs and outputs through logic gates that match the expected behavior.
![../../.pic/Labs/lab_01_adder/fig_01.drawio.svg](../../.pic/Labs/lab_01_adder/fig_01.drawio.svg)
_Рисунок 1. Цифровая схема устройства, складывающего два операнда с сохранением переноса (полусумматора)._
_Figure 1. Digital circuit of a device that adds two operands with carry preservation (half adder)._
Однако, в описании полного 1-битного сумматора сказано, что у него есть три входа, а в наших таблицах истинности и на схеме выше их только два (схема, представленная на _рис. 1_, реализует так называемый "полусумматор"). На самом деле, на каждом этапе сложения в столбик мы всегда складывали три числа: цифру верхнего числа, цифру нижнего числа, и единицу в случае переноса разряда из предыдущего столбца (если с предыдущего разряда не было переноса, прибавление нуля неявно опускалось).
However, the description of a full 1-bit adder states that it has three inputs, while our truth tables and the circuit above only have two (the circuit in _Fig. 1_ implements a so-called "half adder"). In fact, at every step of column addition we always add three numbers: the digit of the top number, the digit of the bottom number, and one in case of a carry from the previous column (if there was no carry from the previous digit, adding zero was implicitly omitted).
Таким образом, таблица истинности немного усложняется:
Therefore, the truth table becomes slightly more complex:
![../../.pic/Labs/lab_01_adder/tt4.png](../../.pic/Labs/lab_01_adder/tt4.png)
_Таблица истинности сигналов полного 1-битного сумматора._
_Truth table for a full 1-bit adder._
Поскольку теперь у нас есть и входной и выходной биты переноса, для их различия добавлены суффиксы "in" и "out".
Since we now have both a carry-in and a carry-out, suffixes "in" and "out" are added to distinguish them.
Как в таком случае описать 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 равен единице.
How do we express S in this case? For example, as the sum modulo 2 of the three operands: `a ⊕ b ⊕ Cin`. Let's verify this against the truth table. Sum modulo 2 is an associative operation [`(a⊕b)⊕c = a⊕(b⊕c)`], meaning the order of addition does not affect the result. Assume Cin is zero. Sum modulo 2 with zero gives the second operand (`a⊕0=a`), so `(a⊕b)⊕0 = a⊕b`. This corresponds to the upper half of the truth table for signal S when Cin is zero.
Assume Cin is one. Sum modulo 2 with one gives the negation of the second operand (`a⊕1=!a`), so `(a⊕b)⊕1=!(a⊕b)`. This corresponds to the lower half of the truth table when Cin is one.
Для выходного бита переноса всё гораздо проще. Он равен единице, когда хотя бы два из трех операндов равны единице, это значит, что необходимо попарно сравнить все операнды, и если найдется хоть одна такая пара, он равен единице. Это утверждение можно записать следующим образом:
For the carry-out, things are simpler. It equals one when at least two of the three operands equal one, meaning we need to compare all pairs of operands and if any such pair is found, it equals one. This can be written as:
`Cоut = (a&b) | (а&Cіn) | (b&Cіn)`, где `&` — логическое И, `|` — логическое ИЛИ.
`Cout = (a&b) | (a&Cin) | (b&Cin)`, where `&` is logical AND, `|` is logical OR.
Цифровая схема устройства с описанным поведением выглядит следующим образом:
The digital circuit with this described behavior looks as follows:
![../../.pic/Labs/lab_01_adder/fig_02.drawio.svg](../../.pic/Labs/lab_01_adder/fig_02.drawio.svg)
_Рисунок 2. Цифровая схема полного 1-битного сумматора._
_Figure 2. Digital circuit of a full 1-bit adder._
## Практика
## Practice
Реализуем схему полусумматора (_рис. 1_) в виде модуля, описанного на языке SystemVerilog.
Let's implement the half adder circuit (_Fig. 1_) as a module described in SystemVerilog.
Модуль `half_adder` имеет два входных сигнала и два выходных. Входы `a_i` и `b_i` идут на два логических элемента: Исключающее ИЛИ и И, выходы которых подключены к выходам модуля `sum_o` и `carry_o` соответственно.
The `half_adder` module has two input signals and two output signals. Inputs `a_i` and `b_i` feed into two logic elements: Exclusive OR and AND, whose outputs are connected to module outputs `sum_o` and `carry_o` respectively.
```Verilog
module half_adder(
input logic a_i, // Входные сигналы
input logic a_i, // Input signals
input logic b_i,
output logic sum_o, // Выходные сигналы
output logic sum_o, // Output signals
output logic carry_o
);
@@ -125,31 +123,31 @@ assign carry_o = a_i & b_i;
endmodule
```
_Листинг 1. SystemVerilog-код модуля half_adder._
_Listing 1. SystemVerilog code for the half_adder module._
По данному коду, САПР может реализовать схему, представленную на рисунке 3.
From this code, the EDA tool can implement the circuit shown in Figure 3.
![../../.pic/Labs/lab_01_adder/fig_03.png](../../.pic/Labs/lab_01_adder/fig_03.png)
_Рисунок 3. Цифровая схема модуля half_adder, сгенерированная САПР Vivado._
_Figure 3. Digital circuit of the half_adder module generated by the Vivado EDA tool._
Схема похожа на _рис. 1_, но как проверить, что эта схема не содержит ошибок и делает именно то, что от неё ожидается?
The circuit resembles _Fig. 1_, but how do we verify that this circuit is error-free and behaves as expected?
Для этого необходимо провести моделирование этой схемы. Во время моделирования на входы подаются тестовые воздействия. Каждое изменение входных сигналов приводит к каскадному изменению состояний внутренних цепей, что в свою очередь приводит к изменению значений на выходных сигналах схемы.
To do this, we need to simulate the circuit. During simulation, test stimuli are applied to the inputs. Each change in input signals causes a cascading change in the states of internal nets, which in turn causes changes in the output signal values.
Подаваемые на схему входные воздействия формируются верификационным окружением. Верификационное окружение (в дальнейшем будет использован термин "**тестбенч**") — это особый несинтезируемый модуль, который не имеет входных или выходных сигналов. Эти сигналы ему не нужны, потому что он сам является генератором всех своих внутренних сигналов, и данный модуль не передаёт ничего вовне — только проверяет тестируемый модуль внутри себя.
The test stimuli applied to the circuit are generated by the verification environment. The verification environment (hereafter referred to as a "**testbench**") is a special non-synthesizable module that has no input or output signals. It does not need them because it generates all its internal signals itself, and this module does not pass anything to the outside world — it only tests the design under test (DUT) internally.
Внутри тестбенча можно использовать конструкции из несинтезируемого подмножества языка SystemVerilog, в частности программный блок `initial`, в котором команды выполняются последовательно, что делает этот блок чем-то отдалённо похожим на проверяющую программу. Поскольку изменение внутренних цепей происходит с некоторой задержкой относительно изменений входных сигналов, при моделировании есть возможность делать паузы между командами. Это делается с помощью специального символа #, за которым указывается количество времени симуляции, которое нужно пропустить перед следующей командой.
Inside the testbench, constructs from the non-synthesizable subset of SystemVerilog can be used, in particular the `initial` procedural block, in which statements execute sequentially, making this block somewhat similar to a test program. Since changes in internal nets occur with some delay relative to input signal changes, it is possible to insert pauses between statements during simulation. This is done using the special `#` symbol followed by the amount of simulation time to skip before the next statement.
Перед тем как писать верификационное окружение, необходимо составить план того, как будет проводиться проверка устройства (составить верификационный план). Ввиду предельной простоты устройства, план будет состоять из одного предложения:
Before writing the verification environment, it is necessary to draft a plan for how the device will be verified (a verification plan). Given the extreme simplicity of the device, the plan consists of a single statement:
> Поскольку устройство не имеет внутреннего состояния, которое могло бы повлиять на результат, а число всех его возможных входных наборов воздействий равно четырём, мы можем проверить его работу, перебрав все возможные комбинации его входных сигналов.
> Since the device has no internal state that could affect the result, and the total number of all possible input stimulus combinations equals four, we can verify its operation by exhausting all possible combinations of its input signals.
```Verilog
module testbench(); // <- Не имеет ни входов, ни выходов!
module testbench(); // <- Has neither inputs nor outputs!
logic a, b, carry, sum;
half_adder DUT( // <- Подключаем проверяемый модуль
half_adder DUT( // <- Connect the design under test
.a_i (a ),
.b_i (b ),
.carry_o(carry),
@@ -157,50 +155,50 @@ module testbench(); // <- Не имеет ни входов, ни
);
initial begin
a = 1'b0; b = 1'b0; // <- Подаём на входы модуля тестовые
#10ns; // воздействия
a = 1'b0; b = 1'b0; // <- Apply test stimuli to the module
#10ns; // inputs
a = 1'b0; b = 1'b1;
#10ns; // <- Делаем паузу в десять наносекунд
a = 1'b1; b = 1'b0; // перед очередным изменением
#10ns; // входных сигналов
#10ns; // <- Pause for ten nanoseconds before
a = 1'b1; b = 1'b0; // the next input signal change
#10ns;
a = 1'b1; b = 1'b1;
end
endmodule
```
_Листинг 2. SystemVerilog-код тестбенча для модуля half_adder._
_Listing 2. SystemVerilog code for the half_adder testbench._
![../../.pic/Labs/lab_01_adder/fig_04.png](../../.pic/Labs/lab_01_adder/fig_04.png)
_Рисунок 4. Временная диаграмма, моделирующая работу схемы с рис. 3._
_Figure 4. Timing diagram simulating the operation of the circuit from Fig. 3._
В данной лабораторной работе вам предстоит реализовать схему полного 1-битного сумматора (_рис. 2_). Модуль полусумматора, код которого представлен в _листинге 1_ не используется в лабораторной работе (он был дан только в качестве примера).
In this lab, you will implement the full 1-bit adder circuit (_Fig. 2_). The half adder module whose code is shown in _Listing 1_ is not used in this lab (it was provided as an example only).
### Полный 4-битный сумматор
### Full 4-bit Adder
До этого мы реализовали сложение в столбик только для одного разряда, теперь мы хотим реализовать всю операцию сложения в столбик. Как это сделать? Сделать ровно то, что делается при сложении в столбик: сначала сложить младший разряд, получить бит переноса для следующего разряда, сложить следующий и т.д.
So far, we have implemented column addition for only one digit. Now we want to implement the full column addition operation. How? By doing exactly what column addition does: first add the least significant bit, obtain the carry for the next bit, add the next, and so on.
Давайте посмотрим, как это будет выглядеть на схеме (для простоты, внутренняя логика 1-битного сумматора скрыта, но вы должны помнить, что каждый прямоугольник — это та же самая схема с рис. 2).
Let's look at how this appears as a circuit (for simplicity, the internal logic of the 1-bit adder is hidden, but remember that each rectangle is the same circuit from Fig. 2).
![../../.pic/Labs/lab_01_adder/fig_05.drawio.svg](../../.pic/Labs/lab_01_adder/fig_05.drawio.svg)
_Рисунок 5. Схема 4-битного сумматора._
_Figure 5. 4-bit adder circuit._
Фиолетовой линией на схеме показаны провода, соединяющие выходной бит переноса сумматора предыдущего разряда с входным битом переноса сумматора следующего разряда.
The purple lines in the circuit show the wires connecting the carry-out of one adder stage to the carry-in of the next stage.
Как же реализовать модуль, состоящий из цепочки других модулей? Половину этой задачи мы уже сделали, когда писали тестбенч к 1-битному полусумматору в _Листинге 2_ — мы создавали модуль внутри другого модуля и подключали к нему провода. Теперь надо сделать то же самое, только с чуть большим числом модулей.
How do we implement a module composed of a chain of other modules? We already did half of this when we wrote the testbench for the 1-bit half adder in _Listing 2_ — we created a module inside another module and connected wires to it. Now we need to do the same thing, just with a slightly larger number of modules.
Описание 4-битного сумматора, сводится к описанию межсоединения четырёх экземпляров 1-битного сумматора. Подробнее о том, как описывать создание экземпляров модулей рассказано в главе [Описание модулей на языке SystemVerilog](../../Basic%20Verilog%20structures/Modules.md#Иерархия-модулей), который вы изучали перед лабораторной работой.
Describing a 4-bit adder reduces to describing the interconnection of four instances of a 1-bit adder. More details on how to instantiate modules are covered in the chapter [Describing modules in SystemVerilog](../../Basic%20Verilog%20structures/Modules.md#Module-hierarchy), which you studied before this lab.
![../../.pic/Labs/lab_01_adder/fig_06.png](../../.pic/Labs/lab_01_adder/fig_06.png)
_Рисунок 6. Схема 4-битного сумматора, сгенерированная САПР Vivado._
_Figure 6. 4-bit adder circuit generated by the Vivado EDA tool._
Несмотря на запутанность схемы, если присмотреться, вы увидите, как от шин A, B и S отходят линии к каждому из сумматоров, а бит переноса передаётся от предыдущего сумматора к следующему. Для передачи битов переноса от одного сумматора к другому, потребуется создать вспомогательные провода, которые можно сгруппировать в один [вектор](../../Basic%20Verilog%20structures/Modules.md#Векторы) (см. сигналы c[0]-c[2] на _рис. 5_).
Despite how complex the circuit looks, if you look closely, you can see lines running from buses A, B, and S to each of the adders, with the carry bit propagating from one adder to the next. To transfer the carry bits from one adder to the next, auxiliary wires need to be created; these can be grouped into a single [vector](../../Basic%20Verilog%20structures/Modules.md#Vectors) (see signals c[0]c[2] in _Fig. 5_).
## Задание
## Assignment
Опишите полный 1-битный сумматор, схема которого представлена на _[Рис. 2](../../.pic/Labs/lab_01_adder/fig_02.drawio.svg)_. Прототип модуля следующий:
Describe a full 1-bit adder whose circuit is shown in _[Fig. 2](../../.pic/Labs/lab_01_adder/fig_02.drawio.svg)_. The module prototype is as follows:
```Verilog
module fulladder(
@@ -212,7 +210,7 @@ module fulladder(
);
```
Далее, вам необходимо реализовать полный 32-битный сумматор со следующим прототипом:
Next, implement a full 32-bit adder with the following prototype:
```verilog
module fulladder32(
@@ -224,9 +222,9 @@ module fulladder32(
);
```
Соединять вручную 32 однотипных модуля чревато усталостью и ошибками, поэтому можно сначала создать 4-битный сумматор, а затем из набора 4-битных сумматоров сделать 32-битный.
Manually connecting 32 identical modules is tedious and error-prone, so it is recommended to first build a 4-bit adder and then combine four 4-bit adders into a 32-bit one.
Если вы решите делать 4-битный сумматор, то модуль должен быть описан в соответствии со следующим прототипом:
If you choose to build a 4-bit adder, the module must follow this prototype:
```Verilog
module fulladder4(
@@ -238,15 +236,15 @@ module fulladder4(
);
```
Либо же можно создать массив 1-битных сумматоров.
Alternatively, you can create an array of 1-bit adders.
Создание массива модулей схоже с созданием одного модуля за исключением того, что после имени экземпляра модуля указывается диапазон, определяющий количество модулей в массиве. При этом подключение сигналов к массиву модулей осуществляется следующим образом:
Creating a module array is similar to instantiating a single module, except that a range defining the number of modules in the array is specified after the instance name. Signal connections to a module array work as follows:
- если разрядность подключаемого сигнала совпадает с разрядностью порта модуля из массива, этот сигнал подключается к каждому из модулей в массиве;
- если разрядность подключаемого сигнала превосходит разрядность порта модуля из массива в `N` раз (где `N` — количество модулей в массиве), к модулю подключается соответствующий диапазон бит подключаемого сигнала (диапазон младших бит будет подключён к модулю с меньшим индексом в массиве).
- если разрядность подключаемого сигнала не подходит ни под один из описанных выше пунктов, происходит ошибка синтеза схемы, поскольку в этом случае САПР не способен понять каким образом подключать данный сигнал к каждому модулю из массива.
- if the width of the connected signal matches the port width of the module in the array, that signal is connected to every module in the array;
- if the width of the connected signal is `N` times the port width of the array module (where `N` is the number of modules in the array), the corresponding bit range of the signal is connected to each module (the lower bit range is connected to the module with the smaller index in the array);
- if the width of the connected signal does not match either of the above cases, a synthesis error occurs, because the EDA tool cannot determine how to connect the signal to each module in the array.
В _листинге 3_ представлен пример того, как можно создать массив модулей.
_Listing 3_ shows an example of how to create a module array.
```Verilog
module example1(
@@ -267,54 +265,52 @@ module example2(
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]).
example1 instance_array[7:0]( // Creates an array of 8 example1 modules
.a(A), // Since the width of signal A is 8 times greater
// than the width of port a, each module in the array
// is connected to its own bit range of signal A
// (instance_array[0] is connected to A[3:0],
// instance_array[7] is connected to A[31:28]).
.b(B), // Поскольку разрядность сигнала B совпадает с
// разрядностью входа b, сигнал B будет подключен
// как есть ко всем модулям в массиве.
.b(B), // Since the width of signal B matches the width
// of port b, signal B is connected as-is to
// all modules in the array.
.c(C[7:0]), // Поскольку разрядность сигнала C не равна
// ни разрядности входа c, ни его увосьмерённой
// разрядности, мы должны выбрать такой диапазон
// бит, который будет удовлетворять одному из
// этих требований.
.c(C[7:0]), // Since the width of signal C does not equal
// either the port width of c or eight times
// that width, we must select a bit range that
// satisfies one of the requirements.
.d(C[8]) // Аналогично предыдущему.
.d(C[8]) // Same as the previous case.
);
endmodule
```
_Листинг 3. Пример создания массива модулей._
_Listing 3. Example of creating a module array._
Реализация массива сумматоров будет осложнена тем, что вам потребуется каким-то образом организовать передачу выходного бита переноса предыдущего разряда до входного бита переноса следующего разряда. Для этого рекомендуется создать два 32-битных вектора:
Implementing the adder array will be complicated by the need to propagate the carry-out of each stage to the carry-in of the next. To do this, it is recommended to create two 32-bit vectors:
- вектор входных битов переноса;
- вектор выходных битов переноса.
- a vector of carry-in bits;
- a vector of carry-out bits.
Далее, с помощью оператора непрерывного присваивания соединить разряды вектора выходных битов переноса с соответствующими разрядами вектора входных битов переноса. Кроме того, вам потребуется связать входной и выходной биты переноса модуля с младшим и старшим разрядом соответствующих векторов.
Then use continuous assignment to connect the bits of the carry-out vector to the corresponding bits of the carry-in vector. In addition, you will need to connect the module-level carry-in and carry-out to the least significant and most significant bits of the corresponding vectors.
После того, как векторы бит переноса будут готовы, создание массива модулей уже не будет представлять сложности.
Once the carry bit vectors are ready, creating the module array will be straightforward.
### Порядок выполнения задания
### Step-by-Step Instructions
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) работоспособность вашей цифровой схемы в ПЛИС.
1. Create a project following the [Vivado project creation guide](../../Vivado%20Basics/01.%20New%20project.md).
2. Describe the `fulladder` module whose circuit is shown in _[Fig. 2](../../.pic/Labs/lab_01_adder/fig_02.drawio.svg)_.
1. The module must be described with the same name and ports as specified in the assignment.
3. Verify the module using the verification environment provided in the file [`lab_01.tb_fulladder.sv`](lab_01.tb_fulladder.sv). Check the waveform signals to confirm that the module operates correctly. If incorrect behavior is observed on the sum or carry-out signals, you must [find](../../Vivado%20Basics/05.%20Bug%20hunting.md) the cause and fix it.
4. Describe the `fulladder4` module whose circuit is shown in _Figs. 5 and 6_, using [module hierarchy](../../Basic%20Verilog%20structures/Modules.md#Module-hierarchy) to perform bit-by-bit addition of two 4-bit numbers and a carry-in. Some inputs and outputs of the module will need to be described as [vectors](../../Basic%20Verilog%20structures/Modules.md#Vectors).
1. The module must be described with the same name and ports as specified in the assignment.
2. Note that the carry-in must be fed to the adder that processes bit 0, and the carry-out must be connected to the carry-out of the adder processing bit 3. Intermediate carry bits are passed using auxiliary wires that you must create yourself.
5. Verify the module using the verification environment provided in the file [`lab_01.tb_fulladder4.sv`](lab_01.tb_fulladder4.sv). Check the waveform signals to confirm that the module operates correctly. If incorrect behavior is observed on the sum or carry-out signals, you must [find](../../Vivado%20Basics/05.%20Bug%20hunting.md) the cause and fix it.
1. Before launching simulation, make sure the correct top-level module is selected in `Simulation Sources`.
6. Describe the `fulladder32` module to perform bit-by-bit addition of two 32-bit numbers and a carry-in. It can be implemented by chaining eight 4-bit adders, or by connecting 32 1-bit adders (either manually or by creating a module array).
1. The module must be described with the same name and ports as specified in the assignment.
2. Note that the carry-in must be fed to the adder that processes bit 0, and the carry-out must be connected to the carry-out of the adder processing bit 31.
7. Verify the module using the verification environment provided in the file [`lab_01.tb_fulladder32.sv`](lab_01.tb_fulladder32.sv). If error messages appear in the TCL console, you must [find](../../Vivado%20Basics/05.%20Bug%20hunting.md) and fix them.
1. Before launching simulation, make sure the correct top-level module is selected in `Simulation Sources`.
8. [Verify](./board%20files) the operation of your digital circuit on the FPGA.

View File

@@ -1,73 +1,73 @@
# Лабораторная работа №2. Арифметико-логическое устройство
# Lab 2. Arithmetic Logic Unit
Так как основной задачей процессора является обработка цифровых данных, одним из его основных блоков является арифметико-логическое устройство (АЛУ). Задача АЛУ производить над входными данными арифметические и поразрядно логические операции.
Since the primary purpose of a processor is to process digital data, one of its core components is the arithmetic logic unit (ALU). The ALU performs arithmetic and bitwise logical operations on input data.
## Цель
## Goal
Используя навыки по описанию мультиплексоров, описать блок арифметико-логического устройства (АЛУ) на языке SystemVerilog.
Using multiplexer description skills, describe an arithmetic logic unit (ALU) module in SystemVerilog.
## Материалы для подготовки к лабораторной работе
## Preparation Materials
В дополнение к [материалам](../../Basic%20Verilog%20structures/), изученным в ходе предыдущей лабораторной работы, вам рекомендуется ознакомиться с:
In addition to the [materials](../../Basic%20Verilog%20structures/) covered in the previous lab, you are encouraged to review:
- способами описания [мультиплексора](../../Basic%20Verilog%20structures/Multiplexors.md) на языке SystemVerilog.
- how to describe a [multiplexer](../../Basic%20Verilog%20structures/Multiplexors.md) in SystemVerilog.
## Общий ход выполнения работы
## General Workflow
1. Изучить устройство и принцип работы АЛУ (раздел [#теория](#Теория))
2. Изучить языковые конструкции SystemVerilog для реализации АЛУ (раздел [#инструменты](#Инструменты))
3. Внимательно ознакомиться с заданием (раздел [#задание](#Задание))
4. Описать модуль АЛУ, проверить его предоставленным верификационным окружением.
5. Проверить работу АЛУ в ПЛИС.
1. Study the structure and operating principle of an ALU (see [#theory](#theory))
2. Study the SystemVerilog language constructs for implementing an ALU (see [#tools](#Tools))
3. Read the assignment carefully (see [#assignment](#Assignment))
4. Describe the ALU module and verify it using the provided verification environment.
5. Test the ALU on the FPGA.
## Теория
## Theory
**Арифметико-логическое устройство** (**АЛУ**, Arithmetic Logic Unit ALU) это блок процессора, выполняющий арифметические и поразрядно логические операции. Разница между арифметическими и логическими операциями в отсутствии у последних бита переноса, так как логические операции происходят между 1-битными числами и дают 1-битный результат, а в случае АЛУ (в рамках данной лабораторной работы) одновременно между 32-мя 1-битными парами чисел. В логических операциях результаты значений отдельных битов друг с другом никак не связаны.
An **arithmetic logic unit** (**ALU**) is a processor block that performs arithmetic and bitwise logical operations. The difference between arithmetic and logical operations is that logical operations have no carry bit — logical operations are performed between 1-bit numbers and produce a 1-bit result, whereas in the ALU (within the scope of this lab) they operate simultaneously on 32 pairs of 1-bit numbers. In logical operations, the result of individual bits are entirely independent of one another.
Также, кроме результата операций, АЛУ может формировать специальные сигналы-флаги, которые показывают выполняется ли заданное условие. Например, выводить `1`, если один операнд меньше другого, или если в результате суммы произошло переполнение.
In addition to the operation result, the ALU can generate special flag signals that indicate whether a given condition holds. For example, it can output `1` if one operand is less than another, or if an addition resulted in overflow.
Обычно АЛУ представляет собой комбинационную схему (то есть не имеет элементов памяти), на входы которой поступают информационные (операнды) и управляющие (код операции) сигналы, в ответ на что на выходе появляется результат заданной операции. АЛУ может быть реализовано и в виде последовательностной логики, но это скорее исключение.
An ALU is typically a combinational circuit (i.e., it has no memory elements). Its inputs receive data (operands) and control (operation code) signals, and its output produces the result of the specified operation. An ALU can also be implemented as sequential logic, but this is the exception rather than the rule.
![../../.pic/Labs/lab_02_alu/fig_01.drawio.svg](../../.pic/Labs/lab_02_alu/fig_01.drawio.svg)
_Рисунок 1. Структурное обозначение элемента АЛУ[[1, стр.304]](https://reader.lanbook.com/book/241166?lms=1b8d65a957786d4b32b8201bd30e97f3)._
_Figure 1. Block diagram symbol of an ALU [1, p. 247]._
На рис. 1 изображено структурное обозначение АЛУ, используемое в книге "Цифровая схемотехника и архитектура компьютера RISC-V" Харрис и Харрис. На входы `A` и `B` поступают операнды с разрядностью _N_. На 2-битный вход `ALUControl` подается код операции. Например, если туда подать `10`, то на выходе `Y` появится результат операции огическое И_ между битами операндов `A` и `B`. Если же подать `00`, то на выходе появится результат сложения. Это лишь пример, разрядность и коды могут отличаться в зависимости от количества выполняемых операций и архитектуры.
Fig. 1 shows the ALU block diagram symbol used in the book "Digital Design and Computer Architecture: RISC-V Edition" by Harris & Harris. Inputs `A` and `B` receive N-bit operands. The 2-bit `ALUControl` input carries the operation code. For example, supplying `10` causes output `Y` to produce the bitwise AND of operands `A` and `B`. Supplying `00` produces the addition result. This is just an example — the bit widths and codes may vary depending on the number of operations and the architecture.
Существует несколько подходов к реализации АЛУ, отличающиеся внутренней организацией. В лабораторных работах применяется повсеместно используемый подход мультиплексирования операций, то есть подключения нескольких операционных устройств (которые выполняют какие-то операции, например сложения, логического ИЛИ и т.п.) к мультиплексору, который будет передавать результат нужного операционного устройства на выходы АЛУ.
There are several approaches to implementing an ALU, differing in their internal organization. In these labs we use the widely adopted operation-multiplexing approach: multiple functional units (each performing a specific operation such as addition, bitwise OR, etc.) are connected to a multiplexer, which forwards the result of the selected unit to the ALU outputs.
Рассмотрим данный подход на примере всё того же АЛУ из книги Харрисов. На рис. 2, в левой его части, изображена внутренняя организация этого АЛУ, справа таблица соответствия кодов операциям. На выходе схемы (внизу) стоит 4-входовой мультиплексор, управляемый 2-битным сигналом `ALUControl`. Ко входам мультиплексора подключены выходы `N` логических И (побитовое И N-битных операндов), `N` логических ИЛИ, а выход N-битного сумматора подключён дважды, позволяя выбирать его результат для двух кодов операции.
Let us examine this approach using the same ALU from the Harris & Harris book. In Fig. 2, the left side shows the internal organization of this ALU; the right side shows the operation code table. At the output of the circuit (bottom) is a 4-input multiplexer controlled by the 2-bit `ALUControl` signal. The multiplexer inputs are connected to the outputs of `N` AND gates (bitwise AND of N-bit operands), `N` OR gates, and the output of an N-bit adder — connected twice, allowing the adder result to be selected for two different operation codes.
![../../.pic/Labs/lab_02_alu/fig_02.drawio.svg](../../.pic/Labs/lab_02_alu/fig_02.drawio.svg)
_Рисунок 2. Структурная схема АЛУ [[1, стр.305]](https://reader.lanbook.com/book/241166?lms=1b8d65a957786d4b32b8201bd30e97f3)._
_Figure 2. ALU block diagram [1, p. 247]._
Вход `A` подключается напрямую ко входам этих операционных устройств. Вход `B` тоже — за исключением подключения к сумматору. На вход второго операнда сумматора подаётся результат мультиплексирования сигналов `B` и `~B`. Управляющим сигналом этого мультиплексора является младший бит управляющего сигнала `ALUControl`. Кроме того, этот же младший бит подаётся и на сумматор в качестве входного бита переноса. Это означает, что когда `ALUControl[0] = 0`, вычисляется сумма `A + B + 0`, а когда `ALUControl[0] = 1`, вычисляется сумма `A + ~B + 1`, что (с учётом [дополнительного кода](https://ru.wikipedia.org/wiki/Дополнительный_код) ) эквивалентно `A B`.
Input `A` connects directly to the inputs of all functional units. Input `B` does as well — except for the adder connection. The second operand input of the adder receives the result of multiplexing `B` and `~B`. The control signal for this multiplexer is the least significant bit of `ALUControl`. Furthermore, that same least significant bit (LSB) is supplied to the adder as its carry-in. This means that when `ALUControl[0] = 0`, the sum `A + B + 0` is computed, and when `ALUControl[0] = 1`, the sum `A + ~B + 1` is computed — which (due to the properties of [two's complement](https://en.wikipedia.org/wiki/Two%27s_complement)) is equivalent to `A B`.
Преимущество такой организации АЛУ в простоте его модификации, настройке под нужные коды операций, читаемости кода и масштабируемости. Можно легко добавить или убрать требуемые операции. Подумайте, как бы вы обновили данную схему, если бы от вас потребовалось расширить её функционал операциями XOR (Исключающее ИЛИ) и (SGE операция "больше либо равно")?
The advantage of this ALU organization is its ease of modification, flexibility in assigning operation codes, code readability, and scalability. Operations can easily be added or removed. Consider how you would update this circuit if you needed to extend its functionality with XOR (exclusive OR) and SGE (greater-than-or-equal) operations.
## Инструменты
## Tools
Как было сказано выше, АЛУ можно реализовать, [мультиплексируя](../../Basic%20Verilog%20structures/Multiplexors.md) результаты нескольких операционных устройств.
As mentioned above, an ALU can be implemented by [multiplexing](../../Basic%20Verilog%20structures/Multiplexors.md) the results of several functional units.
При описании очередной комбинации управляющего сигнала, выходу мультиплексора можно сразу присваивать необходимое логическое выражение (например результат побитового ИЛИ можно подать на выход сразу в виде выражения: `a | b`, однако в некоторых случаях выражения будут сложнее из-за различных особенностей реализации, о которых будет рассказано в задании).
When describing each combination of the control signal, you can directly assign the required logical expression to the multiplexer output (for example, the result of a bitwise OR can be expressed directly as `a | b`, though some cases require more complex expressions due to implementation specifics discussed in the assignment).
### Параметры
### Parameters
Очень удобным на практике оказывается использование параметров. Параметры добавляют модулю гибкости, позволяя убрать ["магические"](https://ru.wikipedia.org/wiki/Магическое_число_(программирование)#Плохая_практика_программирования) константы из описания модулей, подставляя вместо них выразительное символьное имя. Параметры отдаленно схожи с макросами `#define` в языке Си, однако стоит понимать, что это не одно и то же. Дефайны представляют собой специальные текстовые макросы, которые автоматически заменяются на этапе препроцессора (как если бы вы прошлись по всем файлам своего кода и вручную заменили бы макросы на их значения). Например, с помощью дефайнов можно писать целые куски кода, а не просто одно какое-то число. При этом у дефайнов глобальная область видимости (объявив их в одном месте, этот макрос будет доступен во всем последующем коде). Параметр в свою очередь может хранить только значение какого-то конкретного типа (т.е. в параметр нельзя поместить фрагмент кода) а область видимости параметра ограничена тем модулем, где он был объявлен.
Parameters are very useful in practice. They add flexibility to a module by replacing ["magic"](https://en.wikipedia.org/wiki/Magic_number_(programming)#Numeric_literal) constants in module descriptions with meaningful symbolic names. Parameters are loosely similar to `#define` macros in C, but they are not the same thing. Defines are special text macros that are automatically substituted at the preprocessor stage (as if you had manually replaced every occurrence in your source files). For example, defines can expand into entire code fragments, not just single values. Defines also have global scope (once declared, the macro is available throughout all subsequent code). A parameter, by contrast, can only hold a value of a specific type (you cannot store a code fragment in a parameter), and its scope is limited to the module in which it is declared.
Допустим, ваше устройство должно включить тостер, если на вход ему придет сигнал `32'haf3c5bd0`. Человек, не знакомый с устройством, при прочтении этого кода будет недоумевать, что это за число и почему используется именно оно. Однако, скрыв его за параметром `TOASTER_EN`, читающий поймет, что это код включения тостера. Кроме того, если некоторая константа должна использоваться в нескольких местах кода, то определив её в виде параметра, можно будет менять её в одном месте, и она тут же поменяется везде.
Suppose your device must turn on a toaster when it receives the signal `32'haf3c5bd0`. A reader unfamiliar with the design would be puzzled by that number. However, hiding it behind a parameter named `TOASTER_EN` immediately communicates that it is the toaster enable code. Furthermore, if the same constant is used in multiple places, defining it as a parameter means you only need to change it in one location for the change to propagate everywhere.
Параметры позволяют влиять на структуру модуля. К примеру, описывая сумматор, можно параметризовать его разрядность и использовать этот параметр при описании модуля (например, в качестве диапазона массива модулей). В этом случае вы сможете создавать множество сумматоров различных разрядностей, подставляя при создании нужное вам значение параметра.
Parameters can also influence the structure of a module. For example, when describing an adder, you can parameterize its bit width and use that parameter throughout the module description (e.g., as the range of a module array). This lets you instantiate adders of different widths simply by supplying a different parameter value at instantiation time.
Параметр может быть объявлен в модуле двумя способами:
A parameter can be declared in a module in two ways:
- в прототипе модуля;
- в теле описания модуля.
- in the module prototype;
- in the module body.
В первом случае после имени модуля ставится символ `#`, затем в круглых скобках указывается ключевое слово `parameter`. Далее идет тип параметра (по умолчанию — знаковое 32-битное число), после чего задаются его имя и, при необходимости, значение по умолчанию.
In the first case, a `#` symbol is placed after the module name, followed by the keyword `parameter` in parentheses. Next comes the parameter type (default is a signed 32-bit integer), then the name, and optionally a default value.
Пример:
Example:
```Verilog
module overflow #(parameter WIDTH = 32)(
@@ -83,9 +83,9 @@ module overflow #(parameter WIDTH = 32)(
endmodule
```
_Листинг 1. Пример описания параметра в прототипе модуля._
_Listing 1. Example of declaring a parameter in the module prototype._
В случае, если параметр не влияет на разрядность портов, его можно объявить в теле модуля:
If the parameter does not affect port widths, it can be declared in the module body:
```Verilog
module toaster(
@@ -99,11 +99,11 @@ module toaster(
endmodule
```
_Листинг 2. Пример описания параметра в теле модуля._
_Listing 2. Example of declaring a parameter in the module body._
В случае АЛУ будет удобно использовать параметры для обозначения кодов команд. Во-первых, для того чтобы в `case` не допустить ошибок, а во-вторых чтобы можно было легко менять управляющие коды для повторного использования АЛУ в других проектах.
For the ALU, it is convenient to use parameters to denote operation codes. First, this avoids errors in the `case` statement; second, it makes it easy to change control codes when reusing the ALU in other projects.
Сравните сами _листинги 3 и 4_:
Compare _listings 3 and 4_ yourself:
```Verilog
//parameter ADD = 5'b00000;
@@ -114,11 +114,11 @@ _Листинг 2. Пример описания параметра в теле
always_comb
case(ALUOp)
//...
5'b00011: //... // вообще же ничего не понятно
5'b11000: //... // никуда не годится
5'b00011: //... // completely unclear
5'b11000: //... // unacceptable
```
_Листинг 3. Пример описания модуля, использующего "магические" числа._
_Listing 3. Example of a module description using "magic" numbers._
```Verilog
parameter ADD = 5'b00000;
@@ -129,17 +129,17 @@ parameter SUB = 5'b01000;
always_comb
case(ALUOp)
//...
ADD: //... // очень понятно
SUB: //... // так лаконично и красиво
ADD: //... // very clear
SUB: //... // concise and clean
```
_Листинг 4. Пример описания модуля, использующего параметры._
_Listing 4. Example of a module description using parameters._
С параметрами смотрится гораздо взрослее, серьёзнее и понятнее. Кстати, сразу на заметку: в SystemVerilog можно объединять группу параметров в **пакет** (package), а затем импортировать его внутрь модуля, позволяя переиспользовать параметры без повторного их прописывания для других модулей.
Using parameters looks far more professional, serious, and readable. As a side note: in SystemVerilog you can group parameters into a **package** and then import it into a module, allowing you to reuse parameters without redeclaring them in each module.
Делается это следующим образом.
This is done as follows.
Сперва создается SystemVerilog-файл, который будет содержать пакет (к примеру, содержимое файла может быть таким):
First, create a SystemVerilog file that contains the package (for example, the file might contain):
```Verilog
package riscv_params_pkg;
@@ -148,71 +148,70 @@ package riscv_params_pkg;
endpackage
```
Далее, внутри модуля, которому нужны параметры из этого пакета, необходимо сделать соответствующий импорт этих параметров. Это можно сделать либо для каждого параметра отдельно, либо импортировать все параметры сразу:
Then, inside the module that needs parameters from this package, import them. You can import each parameter individually or import all of them at once:
```Verilog
module riscv_processor
//import riscv_params_pkg::*;
import riscv_params_pkg::ISA_WIDTH; // Если необходимо импортировать
import riscv_params_pkg::ISA_WIDTH; // If you need to import
(
//...Порты
//...Ports
);
import riscv_params_pkg::ANOTHER_EX; // все параметры в пакете, эти две строчки
// могут быть заменены закомментированной
// выше строкой:
import riscv_params_pkg::ANOTHER_EX; // all parameters in the package, these two lines
// can be replaced by the commented-out line above:
endmodule
```
### Битовые сдвиги
### Bit Shifts
Реализуемое в данной лабораторной работе АЛУ использует операции битового сдвига. **Битовый сдвиг** — это операция, при которой все биты числа смещаются на заданное количество позиций. Сдвиг числа на _N_ бит эквивалентен _N_ сдвигам на 1 бит. В архитектуре RISC-V используются два типа сдвигов: **логический** и **арифметический**.
The ALU implemented in this lab uses bit shift operations. A **bit shift** is an operation in which all bits of a number are moved by a specified number of positions. Shifting a number by _N_ bits is equivalent to performing _N_ single-bit shifts. RISC-V uses two types of shifts: **logical** and **arithmetic**.
При **логическом сдвиге** биты сдвигаются влево или вправо, а освободившиеся позиции заполняются нулями. При этом разряды, "вытолкнутые" за пределы разрядной сетки числа, пропадают. Например, если выполнить логический сдвиг двоичного числа _1<ins>0011010</ins>_ на один бит влево, получится _<ins>0011010</ins>0_. Обратите внимание, что старшая единица была вытолкнута за границу и исчезла.
In a **logical shift**, bits are moved left or right and the vacated positions are filled with zeros. Bits that are shifted beyond the bit-width of the number are discarded. For example, a logical left shift of the binary number _1<ins>0011010</ins>_ by one bit yields _<ins>0011010</ins>0_. Note that the leading one was pushed out and lost.
![../../.pic/Labs/lab_02_alu/fig_03.drawio.svg](../../.pic/Labs/lab_02_alu/fig_03.drawio.svg)
_Рисунок 3. Иллюстрация преобразования двоичного числа при логическом сдвиге._
_Figure 3. Illustration of a binary number transformation under a logical shift._
При арифметическом сдвиге заполнение освобождённых битов выполняется так, чтобы сохранился знак числа. В дополнительном коде знак определяется старшим битом, поэтому:
In an **arithmetic shift**, vacated bit positions are filled in a way that preserves the sign of the number. In two's complement, the sign is determined by the most significant bit, therefore:
- при **арифметическом сдвиге вправо** освободившиеся позиции заполняются значением старшего бита исходного числа. Это позволяет сохранить знак. Например, арифметический сдвиг на два бита вправо числа _<ins>100110</ins>10_ даёт _11<ins>100110</ins>_;
- **арифметический сдвиг влево** эквивалентен логическому, так как заполнение младших битов нулями не влияет на знак числа.
- in an **arithmetic right shift**, vacated positions are filled with the value of the original MSB. This preserves the sign. For example, an arithmetic right shift by two bits of _<ins>100110</ins>10_ yields _11<ins>100110</ins>_;
- an **arithmetic left shift** is equivalent to a logical left shift, since filling the lower bits with zeros does not affect the sign of the number.
![../../.pic/Labs/lab_02_alu/fig_04.drawio.svg](../../.pic/Labs/lab_02_alu/fig_04.drawio.svg)
_Рисунок 4. Иллюстрация преобразования двоичного числа при арифметическом сдвиге._
_Figure 4. Illustration of a binary number transformation under an arithmetic shift._
Битовый сдвиг имеет важный арифметический смысл — он соответствует умножению или делению числа на степень двойки:
Bit shifts have an important arithmetic meaning — they correspond to multiplication or division of a number by a power of two:
- сдвиг влево на _N_ бит эквивалентен умножению на _2<sup>N</sup>_,
- сдвиг вправо на _N_ бит эквивалентен целочисленному делению на _2<sup>N</sup>_.
- a left shift by _N_ bits is equivalent to multiplication by _2<sup>N</sup>_,
- a right shift by _N_ bits is equivalent to integer division by _2<sup>N</sup>_.
Этот приём знаком должен быть знаком вам при работе с десятичной системой: умножая число на 10, мы просто дописываем справа ноль. То же самое работает и для деления: если отрезать последний разряд у числа <ins>4</ins>2, получится 4, что соответствует целочисленному делению на 10. В двоичной системе добавление (стирание) нуля справа эквивалентно умножению (делению) на 2.
You may be familiar with this principle from the decimal system: multiplying a number by 10 simply appends a zero on the right. The same applies to division: dropping the last digit of <ins>4</ins>2 gives 4, which is integer division by 10. In binary, appending (or removing) a zero on the right is equivalent to multiplying (or dividing) by 2.
Арифметический сдвиг важен тем, что сохраняет это свойство для знаковых чисел, представленных в дополнительном коде. Логический сдвиг вправо изменяет и знак, и модуль отрицательного числа в дополнительном коде, поэтому не может использоваться для деления знаковых чисел.
Arithmetic shift is important because it preserves this property for signed numbers represented in two's complement. A logical right shift changes both the sign and the magnitude of a negative number in two's complement, so it cannot be used for dividing signed numbers.
Операции умножения и деления — это очень «дорогие» операции как с точки зрения элементов схемы (если эти операции реализуются аппаратно), так и с точки зрения времени их вычисления. Поэтому выполнение сдвигов в качестве замены умножения применяется повсеместно. Например, написав в коде языка C++ выражение `var * 8`, после компиляции вы наверняка получите операцию сдвига влево на 3.
Multiplication and division are very "expensive" operations both in terms of circuit area (when implemented in hardware) and computation time. As a result, using shifts as a substitute for multiplication is widespread. For example, writing `var * 8` in C++ code will almost certainly be compiled to a left shift by 3.
Ещё одно применение сдвигов: установка и очищение флагов управляющих регистров. Дело в том, что обычно процессоры не имеют доступа к отдельным битам многоразрядных регистров — их значения читаются записываются целиком. Вот как можно реализовать битовые операции с помощью сдвигов:
Another application of shifts is setting and clearing bits in control registers. Since processors typically cannot access individual bits of wide registers — their values are read and written as a whole — bit manipulation is achieved using shifts:
```C++
X = X | (1 << N); // Установка N-го бита
X = X & ~(1 << N); // Очистка N-го бита
Y = (X & (1 << N)) != 0; // Чтение N-го бита
X = X | (1 << N); // Set the N-th bit
X = X & ~(1 << N); // Clear the N-th bit
Y = (X & (1 << N)) != 0; // Read the N-th bit
```
#### Особенности реализации сдвига
#### Shift Implementation Notes
> [!IMPORTANT]
> По спецификации RISC-V, для **ВСЕХ** операций сдвига используются только 5 младших бит операнда B[2, стр. 26-27].
> According to the RISC-V specification, **ALL** shift operations use only the 5 least significant bits of operand B [2, pp. 2627].
Сами посмотрите: выполнять операцию сдвига более чем на 31 для 32-битных чисел не имеет смысла, число полностью заполнится нулями (единицами). Т.е. сдвигая на любое число, большее 31, вы получите один и тот же результат. Для того чтобы закодировать 31 требуется минимум 5 бит, отсюда и это требование. Оно обязательно, поскольку старшие биты в дальнейшем будут использоваться по другому назначению и, если вы упустите это, ваш будущий процессор станет работать неправильно.
Consider why: shifting a 32-bit number by more than 31 positions makes no sense — the result is simply all zeros (or all ones). That is, shifting by any value greater than 31 always produces the same result. Encoding 31 requires at least 5 bits, hence this requirement. It is mandatory: the upper bits will be used for other purposes later, and ignoring this will cause your future processor to behave incorrectly.
## Задание
## Assignment
Необходимо на языке SystemVerilog реализовать АЛУ в соответствии со следующим прототипом:
Implement an ALU in SystemVerilog according to the following prototype:
```Verilog
@@ -224,112 +223,112 @@ module alu (
output logic [31:0] result_o
);
import alu_opcodes_pkg::*; // импорт параметров, содержащих
// коды операций для АЛУ
import alu_opcodes_pkg::*; // import of parameters containing
// operation codes for the ALU
endmodule
```
Для стандартного набора целочисленных операций архитектуры RISC-V требуется выполнять 16 различных операций. Для кодирования 16 операций было бы достаточно 4 бит, но в лабораторной работе предлагается использовать 5-битный код, что связано с особенностями кодирования инструкций. Старший бит кода операции указывает на то, является ли операция вычислительной или это операция сравнения.
The standard integer instruction set of RISC-V requires 16 distinct operations. While 4 bits would suffice to encode 16 operations, this lab uses a 5-bit code, which is related to the instruction encoding scheme. The MSB of the operation code indicates whether the operation is a computational operation or a comparison.
Для удобства чтения, список инструкций разбит на две таблицы.
For readability, the instruction list is split into two tables.
В первой таблице перечислены операции, вычисляющие значение сигнала `result_o`. **При получении АЛУ любого кода операции, не входящего в эту таблицу, сигнал `result_o` должен быть равен нулю**.
The first table lists the operations that compute the value of the `result_o` signal. **If the ALU receives any operation code not listed in this table, the `result_o` signal must be zero.**
|Операция|={cmp, mod, opcode}|Выражение | Действие |
|--------|-------------------|-----------------------|-------------------------------------------------------|
| ADD | 0 0 000 |result_o = a_i + b_i | Сложение |
| SUB | 0 1 000 |result_o = a_i b_i | Вычитание |
| SLL | 0 0 001 |result_o = a_i << b_i | Сдвиг влево |
| SLTS | 0 0 010 |result_o = a_i < b_i | **Знаковое** сравнение |
| SLTU | 0 0 011 |result_o = a_i < b_i | **Беззнаковое** сравнение |
| XOR | 0 0 100 |result_o = a_i ^ b_i | Побитовое исключающее **ИЛИ** |
| SRL | 0 0 101 |result_o = a_i >> b_i | Сдвиг вправо |
| SRA | 0 1 101 |result_o = a_i >>> b_i | Арифметический сдвиг вправо (операнд `a_i` — знаковый)|
| OR | 0 0 110 |result_o = a_i \| b_i | Побитовое логическое **ИЛИ** |
| AND | 0 0 111 |result_o = a_i & b_i | Побитовое логическое **И** |
| Operation | ={cmp, mod, opcode} | Expression | Action |
|-----------|---------------------|-------------------------|-------------------------------------------------------|
| ADD | 0 0 000 | result_o = a_i + b_i | Addition |
| SUB | 0 1 000 | result_o = a_i b_i | Subtraction |
| SLL | 0 0 001 | result_o = a_i << b_i | Left shift |
| SLTS | 0 0 010 | result_o = a_i < b_i | **Signed** comparison |
| SLTU | 0 0 011 | result_o = a_i < b_i | **Unsigned** comparison |
| XOR | 0 0 100 | result_o = a_i ^ b_i | Bitwise exclusive **OR** |
| SRL | 0 0 101 | result_o = a_i >> b_i | Right shift |
| SRA | 0 1 101 | result_o = a_i >>> b_i | Arithmetic right shift (`a_i` operand is signed) |
| OR | 0 0 110 | result_o = a_i \| b_i | Bitwise logical **OR** |
| AND | 0 0 111 | result_o = a_i & b_i | Bitwise logical **AND** |
_Таблица 1. Список вычислительных операций._
_Table 1. List of computational operations._
Во второй таблице перечислены операции, вычисляющие значение сигнала `flag_o`. **При получении АЛУ любого кода операции, не входящего в эту таблицу, сигнал `flag_o` должен быть равен нулю**.
The second table lists the operations that compute the value of the `flag_o` signal. **If the ALU receives any operation code not listed in this table, the `flag_o` signal must be zero.**
|Операция|={cmp, mod, opcode}| Выражение | Действие |
|--------|-------------------|----------------------|-----------------------------------|
| EQ | 1 1 000 | flag_o = (a_i == b_i)| Выставить флаг, если **равны** |
| NE | 1 1 001 | flag_o = (a_i != b_i)| Выставить флаг, если **не равны** |
| LTS | 1 1 100 | flag_o = a_i < b_i | **Знаковое** сравнение **<** |
| GES | 1 1 101 | flag_o = a_i ≥ b_i | **Знаковое** сравнение **≥** |
| LTU | 1 1 110 | flag_o = a_i < b_i | **Беззнаковое** сравнение **<** |
| GEU | 1 1 111 | flag_o = a_i ≥ b_i | **Беззнаковое** сравнение **≥** |
| Operation | ={cmp, mod, opcode} | Expression | Action |
|-----------|---------------------|------------------------|--------------------------------------|
| EQ | 1 1 000 | flag_o = (a_i == b_i) | Set flag if operands are **equal** |
| NE | 1 1 001 | flag_o = (a_i != b_i) | Set flag if operands are **unequal** |
| LTS | 1 1 100 | flag_o = a_i < b_i | **Signed** comparison **<** |
| GES | 1 1 101 | flag_o = a_i ≥ b_i | **Signed** comparison **≥** |
| LTU | 1 1 110 | flag_o = a_i < b_i | **Unsigned** comparison **<** |
| GEU | 1 1 111 | flag_o = a_i ≥ b_i | **Unsigned** comparison **≥** |
_Таблица 2. Список операций сравнения._
_Table 2. List of comparison operations._
**Выражения в этих двух таблицах приведены для примера. Не все из них можно просто переписать — часть этих выражений надо дополнить. Чтобы вы не копировали выражения, в них вставлены неподдерживаемые символы.**
**The expressions in these two tables are provided as examples. Not all of them can simply be transcribed — some must be supplemented. To prevent copy-pasting, the expressions contain unsupported characters.**
Несмотря на разделение на вычислительные операции, и операции сравнения, в _Таблице 1_ (вычислительных операций) оказалось две операции: `SLTS` и `SLTU`, которые выполняют сравнения. В итоге у нас есть две похожие пары инструкций:
Despite the separation into computational operations and comparison operations, _Table 1_ (computational operations) includes two operations — `SLTS` and `SLTU` — that perform comparisons. This gives us two similar pairs of instructions:
- `LTS`
- `LTU`
- `SLTS`
- `SLTU`
Первая пара инструкций вычисляет "ветвительный" результат. Результат операции будет подан на выходной сигнал `flag_o` и использован непосредственно при ветвлении.
The first pair computes a "branch" result. The operation result is placed on the `flag_o` output and used directly for branching.
Вторые две инструкции используются для получения "вычислительного" результата. Т.е. результат сравнения будет подан на выходной сигнал `result_o` так же, как подается результат операции `ADD`, и будет использован в неких вычислениях, избегая при этом условного перехода.
The second pair is used to obtain a "computational" result. That is, the comparison result is placed on the `result_o` output in the same way as the result of the `ADD` operation, and is used in further computations — avoiding a conditional branch.
К примеру, нам необходимо пройтись по массиву из миллиона элементов и убедиться, что все они были неотрицательны. Об этом будет сигнализировать переменная `num_of_err`, значение которой должно быть равно числу элементов массива, меньших нуля. Вычислить значение этой переменной можно двумя способами:
For example, suppose we need to iterate over an array of one million elements and check that all of them are non-negative. This is tracked by the variable `num_of_err`, whose value should equal the count of elements less than zero. The variable can be computed in two ways:
1. В каждой итерации цикла сделать ветвление: в одном случае инкрементировать переменную, в другом случае — нет (для ветвления использовать "ветвительную" операцию `LTS`).
2. В каждой итерации цикла складывать текущее значение переменной с результатом "вычислительной" операции `SLTS`.
1. In each loop iteration, perform a branch: in one case increment the variable, in the other do not (use the "branch" operation `LTS` for branching).
2. In each loop iteration, add the result of the "computational" operation `SLTS` to the current value of the variable.
Операции ветвления очень сильно влияют (в худшую сторону) на производительность конвейерного процессора. В первом случае мы получим миллион операций ветвления, во втором — ни одной! Разумеется, потом переменную `num_of_err` скорее всего сравнят с нулем что приведет к ветвлению, но при вычислении значения этой переменной ветвления можно будет избежать.
Branch operations have a strong (negative) impact on the performance of a pipelined processor. In the first case we get one million branch operations; in the second — none! Of course, `num_of_err` will likely be compared to zero at some point, causing a branch, but during the computation of that variable's value the branch can be avoided entirely.
Различие между `SLTS` и `SLTU` (или `LTS` и `LTU`) заключается в том, как мы интерпретируем операнды: как знаковые числа (операции `STLS` и `LTS`) или как беззнаковые (операции `SLTU` и `LTU`).
The difference between `SLTS` and `SLTU` (or `LTS` and `LTU`) lies in how we interpret the operands: as signed numbers (operations `SLTS` and `LTS`) or as unsigned (operations `SLTU` and `LTU`).
Предположим, мы сравниваем два двоичных числа: `1011` и `0100`. Если интерпретировать эти числа как беззнаковые, то это `11` и `4`, результат: `11 > 4`. Однако если интерпретировать эти числа как знаковые, то теперь это числа `-5` и `4` и в этом случае `-5 < 4`.
Suppose we compare two binary numbers: `1011` and `0100`. Interpreted as unsigned, these are `11` and `4`, giving `11 > 4`. Interpreted as signed, they are `-5` and `4`, giving `-5 < 4`.
Как мы видим, результат одной и той же операции над одними и теми же двоичными числами может зависеть от того, каким образом мы интерпретируем эти двоичные числа. Для большинства операций в АЛУ это не важно: например, сложение будет работать одинаково в обоих случаях, благодаря свойствам дополнительного кода, а побитовые операции работают с отдельными битами двоичного числа. А вот для операции арифметического сдвига это важно — **операнд А в арифметическом сдвиге должен интерпретироваться как знаковый**.
As we can see, the result of the same operation on the same binary numbers can depend on how those numbers are interpreted. For most ALU operations this does not matter — for example, addition works identically in both cases due to the properties of two's complement, and bitwise operations work on individual bits. However, for arithmetic right shift it does matter — **the operand A in an arithmetic shift must be interpreted as signed**.
По умолчанию SystemVerilog интерпретирует все сигналы как беззнаковые, если мы хотим изменить это поведение, необходимо воспользоваться конструкцией `$signed`.
By default, SystemVerilog interprets all signals as unsigned. To change this behavior, use the `$signed` construct.
Конструкция `$signed` говорит САПР интерпретировать число, переданное в качестве операнда, как знаковое.
The `$signed` construct tells the synthesis tool to interpret the value passed as an operand as a signed number.
```Verilog
assign Result = $signed(A) / 10;
```
В этом примере некоторому сигналу `Result` присваивают результат деления **знакового** числа `A` на `10`.
In this example, signal `Result` is assigned the result of dividing the **signed** number `A` by `10`.
Так как используются не все возможные комбинации управляющего сигнала АЛУ, то **при описании через `case` не забывайте использовать `default`**. Если описать АЛУ как задумано, то получится что-то похожее на _рис. 5_. Но не обязательно, зависит от вашего описания.
Since not all possible combinations of the ALU control signal are used, **when describing the logic with `case`, do not forget to include a `default` branch**. If the ALU is described as intended, the result will look something like _Fig. 5_ — though it may differ depending on your description.
![../../.pic/Labs/lab_02_alu/fig_05.png](../../.pic/Labs/lab_02_alu/fig_05.png)
_Рисунок 5. Пример схемы, реализующей АЛУ._
_Figure 5. Example circuit implementing the ALU._
Обратите внимание на то, что сумматор на _рис. 5_ отличается от всех остальных блоков. Для того, чтобы спроектированный в ЛР№1 32-разрядный сумматор был создан не зазря, а также для закрепления навыков по созданию экземпляров модулей внутри других модулей, вам предлагается использовать его при реализации АЛУ.
Note that the adder in _Fig. 5_ differs from all other blocks. To make the 32-bit adder designed in Lab 1 worthwhile, and to reinforce module instantiation skills, you are expected to use it when implementing the ALU.
Другие блоки распознаны Vivado на основе представленных в описании АЛУ арифметических или логических выражений и в процессе синтеза будут реализованы через те компоненты ПЛИС, которые позволят лучше всего удовлетворить временным и физическим ограничениям проекта (см. главу "Этапы реализации проекта в ПЛИС"). Сумматор же будет реализован так, как это описали мы, поскольку вместо использования абстрактной операции "+" в описании АЛУ было сказано разместить конкретный модуль, реализующий конкретную схему. Такая реализация сумматора не является эффективной ни в плане временных характеристик, ни в плане количества затраченных на реализацию ресурсов ПЛИС. Но как уже упоминалось в ЛР№1, цель этой реализации — воспроизвести простоту логики рассуждений о том, как спроектировать сумматор.
The other blocks are recognized by Vivado from the arithmetic or logical expressions in the ALU description and will be synthesized using the FPGA primitives that best satisfy the project's timing and physical constraints (see "FPGA Design Flow"). The adder, however, will be implemented exactly as described, because instead of using the abstract `+` operator, the ALU description instantiates a specific module implementing a specific circuit. This adder implementation is not efficient — neither in terms of timing characteristics nor in terms of FPGA resource utilization. But as mentioned in Lab 1, the goal of this implementation is to illustrate the simplicity of the design reasoning behind the adder.
### Порядок выполнения задания
### Assignment Steps
1. Добавьте в `Design Sources` проекта файл [`alu_opcodes_pkg.sv`](alu_opcodes_pkg.sv). Этот файл содержит объявление пакета `alu_opcodes_pkg`, в котором прописаны все опкоды АЛУ.
1. Поскольку данный файл не содержит описания модулей, он не отобразится во вкладке `Hierarchy` окна `Sources` Vivado (исключением может быть ситуация, когда в проекте вообще нет ни одного модуля). Добавленный файл можно будет найти во вкладках `Libraries` и `Compile Order`.
2. Обратите внимание, что имена параметров кодов операций АЛУ, объявленных в добавляемом пакете, имеют префикс `ALU_`, которого не было в _таблицах 1 и 2_.
3. В случае, если вы добавили пакет в проект и импортировали его в модуле АЛУ, однако Vivado выдает ошибку о том, что используемые параметры не объявлены, попробуйте сперва исправить все остальные синтаксические ошибки и сохранить файл. Если и это не помогло, можно перейти на вкладку `Compile Order`, нажать правой кнопкой мыши по файлу `alu_opcodes_pkg.sv` и выбрать `Move to Top`. Таким образом, мы сообщаем Vivado, что при компиляции проекта, этот файл всегда необходимо собирать в первую очередь. Это вариант "последней надежды" и должен использоваться только в самом крайнем случае. Когда в проекте нет никаких проблем, Vivado всегда может самостоятельно определить правильный порядок компиляции файлов. Тот факт, что вам приходится менять этот порядок означает, что в проекте есть какие-то проблемы, не позволяющие Vivado определить правильный порядок самостоятельно.
2. Опишите модуль `alu` с таким же именем и портами, как указано в [задании](#Задание).
1. Поскольку у вас два выходных сигнала, зависящих от сигнала `alu_op_i`, вам потребуется описать два разных [мультиплексора](../../Basic%20Verilog%20structures/Multiplexors.md) (их лучше всего описывать через два отдельных блока `case`). При описании, используйте `default` на оставшиеся комбинации сигнала `alu_op_i`.
2. Следите за разрядностью ваших сигналов.
3. Для реализации АЛУ, руководствуйтесь таблицей с операциями, а не схемой в конце задания, которая приведена в качестве референса. Обратите внимание, в одной половине операций `flag_o` должен быть равен нулю, в другой `result_o` (т.е. всегда либо один, либо другой сигнал должен быть равен нулю). Именно поэтому удобней всего будет описывать АЛУ в двух разных блоках `case`.
4. Вам не нужно переписывать опкоды из таблицы в качестве вариантов для блока `case`. Вместо этого используйте символьные имена с помощью параметров, импортированных из пакета `alu_opcodes_pkg`.
5. Описывая операцию сложения вы **должны** использовать ваш 32-битный сумматор из первой лабораторной. При описании вычитания сумматор использовать не надо, можно использовать оператор `-`.
1. При подключении сумматора, на входной бит переноса необходимо подать значение `1'b0`. Если не подать значение на входной бит переноса, результат суммы будет не определен (т.к. не определено одно из слагаемых).
2. Выходной бит переноса при подключении сумматора можно не указывать, т.к. он использоваться не будет.
6. При реализации операций сдвига, руководствуйтесь [особенностями реализации сдвигов](#Особенности-реализации-сдвига).
3. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_02.tb_alu.sv`](lab_02.tb_alu.sv). В случае, если в TCL-консоли появились сообщения об ошибках, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) и исправить их.
1. Перед запуском моделирования убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`.
4. [Проверьте](./board%20files) работоспособность вашей цифровой схемы в ПЛИС.
1. Add the file [`alu_opcodes_pkg.sv`](alu_opcodes_pkg.sv) to the project's `Design Sources`. This file contains the declaration of the `alu_opcodes_pkg` package, which defines all ALU operation codes.
1. Since this file contains no module declarations, it will not appear in the `Hierarchy` tab of the Vivado `Sources` window (the exception is when the project contains no modules at all). The added file can be found in the `Libraries` and `Compile Order` tabs.
2. Note that the ALU operation code parameter names declared in the added package have the prefix `ALU_`, which was not present in _Tables 1 and 2_.
3. If you have added the package to the project and imported it in the ALU module but Vivado reports an error saying the used parameters are undeclared, first try fixing all other syntax errors and saving the file. If that does not help, go to the `Compile Order` tab, right-click on `alu_opcodes_pkg.sv`, and select `Move to Top`. This tells Vivado to always compile this file first. This is a last-resort option and should only be used as a final measure. When the project has no issues, Vivado is always able to determine the correct compilation order on its own. The fact that you need to change this order indicates there are problems in the project preventing Vivado from determining the correct order automatically.
2. Describe the `alu` module with the same name and ports as specified in the [assignment](#Assignment).
1. Since you have two output signals that depend on `alu_op_i`, you will need to describe two separate [multiplexers](../../Basic%20Verilog%20structures/Multiplexors.md) (best described using two separate `case` blocks). Use `default` to cover the remaining combinations of `alu_op_i`.
2. Pay attention to the bit widths of your signals.
3. When implementing the ALU, follow the operations table rather than the reference schematic at the end of the assignment. Note that in one half of the operations `flag_o` must be zero, and in the other half `result_o` must be zero (i.e., at all times, one or the other signal must be zero). This is why it is most convenient to describe the ALU using two separate `case` blocks.
4. You do not need to copy operation codes from the table as `case` values. Instead, use the symbolic names from the parameters imported from the `alu_opcodes_pkg` package.
5. When describing the addition operation, you **must** use your 32-bit adder from Lab 1. For subtraction, you do not need to use the adder — you may use the `-` operator.
1. When connecting the adder, supply `1'b0` to the carry-in input. If the carry-in is left undriven, the sum result will be undefined (since one of the addends is undefined).
2. The carry-out can be left unconnected, as it will not be used.
6. When implementing shift operations, follow the [shift implementation notes](#Shift-Implementation-Notes).
3. Verify the module using the verification environment provided in the file [`lab_02.tb_alu.sv`](lab_02.tb_alu.sv). If error messages appear in the TCL console, [find](../../Vivado%20Basics/05.%20Bug%20hunting.md) and fix them.
1. Before running the simulation, make sure the correct top-level module is selected in `Simulation Sources`.
4. [Verify](./board%20files) that your digital circuit works correctly on the FPGA.
## Список использованной литературы
## References
1. [Д.М. Харрис, С.Л. Харрис / Цифровая схемотехника и архитектура компьютера: RISC-V / пер. с англ. В. С. Яценков, А. Ю. Романов; под. ред. А. Ю. Романова / М.: ДМК Пресс, 2021](https://e.lanbook.com/book/241166);
1. D.M. Harris, S.L. Harris / Digital Design and Computer Architecture. RISC-V Edition, 2021;
2. [The RISC-V Instruction Set Manual Volume I: Unprivileged ISA, Document Version 20240411, Editors Andrew Waterman and Krste Asanović, RISC-V Foundation, April 2024](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf).