mirror of
https://github.com/MPSU/APS.git
synced 2025-09-15 17:20:10 +00:00
335 lines
43 KiB
Markdown
335 lines
43 KiB
Markdown
# Лабораторная работа №2. Арифметико-логическое устройство
|
||
|
||
Так как основной задачей процессора является обработка цифровых данных, одним из его основных блоков является арифметико-логическое устройство (АЛУ). Задача АЛУ производить над входными данным арифметические и поразрядно логические операции.
|
||
|
||
## Цель
|
||
|
||
Используя навыки по описанию мультиплексоров, описать блок арифметико-логического устройства (АЛУ) на языке SystemVerilog.
|
||
|
||
## Материалы для подготовки к лабораторной работе
|
||
|
||
В дополнение к [материалам](../../Basic%20Verilog%20structures/), изученным в ходе предыдущей лабораторной работы, вам рекомендуется ознакомиться с:
|
||
|
||
- способами описания [мультиплексора](../../Basic%20Verilog%20structures/Multiplexors.md) на языке SystemVerilog.
|
||
|
||
## Общий ход выполнения работы
|
||
|
||
1. Изучить устройство и принцип работы АЛУ (раздел [#теория](#теория))
|
||
2. Изучить языковые конструкции SystemVerilog для реализации АЛУ (раздел [#инструменты](#инструменты))
|
||
3. Внимательно ознакомиться с заданием (раздел [#задание](#задание))
|
||
4. Описать модуль АЛУ, проверить его предоставленным верификационным окружением.
|
||
5. Проверить работу АЛУ в ПЛИС.
|
||
|
||
## Теория
|
||
|
||
**Арифметико-логическое устройство** (**АЛУ**, Arithmetic Logic Unit – ALU) – это блок процессора, выполняющий арифметические и поразрядно логические операции. Разница между арифметическими и логическими операциями в отсутствии у последних бита переноса, так как логические операции происходят между 1-битными числами и дают 1-битный результат, а в случае АЛУ (в рамках данной лабораторной работы) одновременно между 32-мя 1-битными парами чисел. В логических операциях результаты значений отдельных битов друг с другом никак не связаны.
|
||
|
||
Также, кроме результата операций, АЛУ может формировать специальные сигналы-флаги, которые показывают выполняется ли заданное условие. Например, выводить `1`, если один операнд меньше другого, или если в результате суммы произошло переполнение.
|
||
|
||
Обычно АЛУ представляет собой комбинационную схему (то есть не имеет элементов памяти), на входы которой поступают информационные (операнды) и управляющие (код операции) сигналы, в ответ на что на выходе появляется результат заданной операции. АЛУ может быть реализовано и в виде последовательностной логики, но это скорее исключение.
|
||
|
||

|
||
|
||
_Рисунок 1. Структурное обозначение элемента АЛУ[[1, стр. 304]](https://reader.lanbook.com/book/241166?lms=1b8d65a957786d4b32b8201bd30e97f3)._
|
||
|
||
На рис. 1 изображено структурное обозначение АЛУ, используемое в книге "Цифровая схемотехника и архитектура компьютера RISC-V" Харрис и Харрис. На входы `A` и `B` поступают операнды с разрядностью _N_. На 2-битный вход `ALUControl` подается код операции. Например, если туда подать `10`, то на выходе `Y` появится результат операции _логическое И_ между битами операндов `A` и `B`. Если же подать `00`, то на выходе появится результат сложения. Это лишь пример, разрядность и коды могут отличаться в зависимости от количества выполняемых операций и архитектуры.
|
||
|
||
Существует несколько подходов к реализации АЛУ, отличающиеся внутренней организацией. В лабораторных работах применяется повсеместно используемый подход мультиплексирования операций, то есть подключения нескольких операционных устройств (которые выполняют какие-то операции, например сложения, логического ИЛИ и т.п.) к мультиплексору, который будет передавать результат нужного операционного устройства на выходы АЛУ.
|
||
|
||
Рассмотрим данный подход на примере всё того же АЛУ из книги Харрисов. На рис. 2, в левой его части, изображена внутренняя организация этого АЛУ, справа – таблица соответствия кодов операциям. На выходе схемы (внизу) стоит 4-входовой мультиплексор, управляемый 2-битным сигналом `ALUControl`. Ко входам мультиплексора подключены выходы `N` логических И (побитовое И N-битных операндов), `N` логических ИЛИ, а выход N-битного сумматора подключён дважды, позволяя выбирать его результат для двух кодов операции.
|
||
|
||

|
||
|
||
_Рисунок 2. Структурная схема АЛУ [[1, стр. 305]](https://reader.lanbook.com/book/241166?lms=1b8d65a957786d4b32b8201bd30e97f3)._
|
||
|
||
Вход `A` подключается напрямую ко входам этих операционных устройств. Вход `B` тоже — за исключением подключения к сумматору. На вход второго операнда сумматора подаётся результат мультиплексирования сигналов `B` и `~B`. Управляющим сигналом этого мультиплексора является младший бит управляющего сигнала `ALUControl`. Кроме того, этот же младший бит подаётся и на сумматор в качестве входного бита переноса. Это означает, что когда `ALUControl[0] = 0`, вычисляется сумма `A + B + 0`, а когда `ALUControl[0] = 1`, вычисляется сумма `A + ~B + 1`, что (с учётом [дополнительного кода](https://ru.wikipedia.org/wiki/Дополнительный_код) ) эквивалентно `A – B`.
|
||
|
||
Преимущество такой организации АЛУ в простоте его модификации, настройке под нужные коды операций, читаемости кода и масштабируемости. Можно легко добавить или убрать требуемые операции. Подумайте, как бы вы обновили данную схему, если бы от вас потребовалось расширить её функционал операциями XOR (Исключающее ИЛИ) и (SGE операция "больше либо равно")?
|
||
|
||
## Инструменты
|
||
|
||
Как было сказано выше, АЛУ можно реализовать, [мультиплексируя](../../Basic%20Verilog%20structures/Multiplexors.md) результаты нескольких операционных устройств.
|
||
|
||
При описании очередной комбинации управляющего сигнала, выходу мультиплексора можно сразу присваивать необходимое логическое выражение (например результат побитового ИЛИ можно подать на выход сразу в виде выражения: `a | b`, однако в некоторых случаях выражения будут сложнее из-за различных особенностей реализации, о которых будет рассказано в задании).
|
||
|
||
### Параметры
|
||
|
||
Очень удобным на практике оказывается использование параметров. Параметры добавляют модулю гибкости, позволяя убрать ["магические"](https://ru.wikipedia.org/wiki/Магическое_число_(программирование)#Плохая_практика_программирования) константы из описания модулей, подставляя вместо них выразительное символьное имя. Параметры отдаленно схожи с макросами `#define` в языке Си, однако стоит понимать, что это не одно и то же. Дефайны представляют собой специальные текстовые макросы, которые автоматически заменяются на этапе препроцессора (как если бы вы прошлись по всем файлам своего кода и вручную заменили бы макросы на их значения). Например, с помощью дефайнов можно писать целые куски кода, а не просто одно какое-то число. При этом у дефайнов глобальная область видимости (объявив их в одном месте, этот макрос будет доступен во всем последующем коде). Параметр в свою очередь может хранить только значение какого-то конкретного типа (т.е. в параметр нельзя поместить фрагмент кода) а область видимости параметра ограничена тем модулем, где он был объявлен.
|
||
|
||
Допустим, ваше устройство должно включить тостер, если на вход ему придет сигнал `32'haf3c5bd0`. Человек, не знакомый с устройством, при прочтении этого кода будет недоумевать, что это за число и почему используется именно оно. Однако, скрыв его за параметром `TOASTER_EN`, читающий поймет, что это код включения тостера. Кроме того, если некоторая константа должна использоваться в нескольких местах кода, то определив её в виде параметра, можно будет менять её в одном месте, и она тут же поменяется везде.
|
||
|
||
Параметры позволяют влиять на структуру модуля. К примеру, описывая сумматор, можно параметризовать его разрядность и использовать этот параметр при описании модуля (например, в качестве диапазона массива модулей). В этом случае вы сможете создавать множество сумматоров различных разрядностей, подставляя при создании нужное вам значение параметра.
|
||
|
||
Параметр может быть объявлен в модуле двумя способами:
|
||
|
||
- в прототипе модуля;
|
||
- в теле описания модуля.
|
||
|
||
В первом случае для указания параметра после имени модуля ставится символ `#`, за которым в круглых скобках пишется ключевое слово `parameter`, затем указывается тип параметра (по умолчанию знаковое 32-битное число), после чего указывается имя и (опционально) значение по умолчанию.
|
||
|
||
Пример:
|
||
|
||
```Verilog
|
||
module overflow #(parameter WIDTH = 32)(
|
||
input logic [WIDTH-1 : 0] a, b,
|
||
output logic overflow
|
||
);
|
||
|
||
logic [WIDTH : 0] sum;
|
||
|
||
assign sum = a + b;
|
||
assign overflow = sum[WIDTH];
|
||
|
||
endmodule
|
||
```
|
||
|
||
_Листинг 1. Пример описания параметра в прототипе модуля._
|
||
|
||
В случае, если параметр не влияет на разрядность портов, его можно объявить в теле модуля:
|
||
|
||
```Verilog
|
||
module toaster(
|
||
input logic [31:0] command,
|
||
output logic power
|
||
)
|
||
|
||
parameter TOASTER_EN = 32'haf3c5bd0;
|
||
assign power = command == TOASTER_EN;
|
||
|
||
endmodule
|
||
```
|
||
|
||
_Листинг 2. Пример описания параметра в теле модуля._
|
||
|
||
В случае АЛУ будет удобно использовать параметры для обозначения кодов команд. Во-первых, для того чтобы в `case` не допустить ошибок, а во-вторых – чтобы можно было легко менять управляющие коды для повторного использования АЛУ в других проектах.
|
||
|
||
Сравните сами _листинги 3 и 4_:
|
||
|
||
```Verilog
|
||
//parameter ADD = 5'b00000;
|
||
//parameter SUB = 5'b01000;
|
||
|
||
//...
|
||
|
||
always_comb
|
||
case(ALUOp)
|
||
//...
|
||
5'b00011: //... // вообще же ничего не понятно
|
||
5'b11000: //... // никуда не годится
|
||
```
|
||
|
||
_Листинг 3. Пример описания модуля, использующего "магические" числа._
|
||
|
||
```Verilog
|
||
parameter ADD = 5'b00000;
|
||
parameter SUB = 5'b01000;
|
||
|
||
//...
|
||
|
||
always_comb
|
||
case(ALUOp)
|
||
//...
|
||
ADD: //... // очень понятно
|
||
SUB: //... // так лаконично и красиво
|
||
```
|
||
|
||
_Листинг 4. Пример описания модуля, использующего параметры._
|
||
|
||
С параметрами смотрится гораздо взрослее, серьёзнее и понятнее. Кстати, сразу на заметку: в SystemVerilog можно объединять группу параметров в **пакет** (package), а затем импортировать его внутрь модуля, позволяя переиспользовать параметры без повторного их прописывания для других модулей.
|
||
|
||
Делается это следующим образом.
|
||
|
||
Сперва создается SystemVerilog-файл, который будет содержать пакет (к примеру, содержимое файла может быть таким):
|
||
|
||
```Verilog
|
||
package riscv_params_pkg;
|
||
parameter ISA_WIDTH = 32;
|
||
parameter ANOTHER_EX = 15;
|
||
endpackage
|
||
```
|
||
|
||
Далее, внутри модуля, которому нужны параметры из этого пакета, необходимо сделать соответствующий импорт этих параметров. Это можно сделать либо для каждого параметра отдельно, либо импортировать все параметры сразу:
|
||
|
||
```Verilog
|
||
module riscv_processor
|
||
//import riscv_params_pkg::*;
|
||
import riscv_params_pkg::ISA_WIDTH; // Если необходимо импортировать
|
||
(
|
||
//...Порты
|
||
);
|
||
|
||
import riscv_params_pkg::ANOTHER_EX; // все параметры в пакете, эти две строчки
|
||
// могут быть заменены закомментированной
|
||
// выше строкой:
|
||
|
||
endmodule
|
||
```
|
||
|
||
---
|
||
|
||
Реализуемое в данной лабораторной работе АЛУ использует операции битового сдвига. **Битовый сдвиг** — это операция, при которой все биты числа смещаются на заданное количество позиций. Сдвиг числа на _N_ бит эквивалентен _N_ сдвигам на 1 бит. В архитектуре RISC-V используются два типа сдвигов: **логический** и **арифметический**.
|
||
|
||
При **логическом сдвиге** биты сдвигаются влево или вправо, а освободившиеся позиции заполняются нулями. При этом разряды, "вытолкнутые" за пределы разрядной сетки числа, пропадают. Например, если выполнить логический сдвиг двоичного числа _1<ins>0011010</ins>_ на один бит влево, получится _<ins>0011010</ins>0_. Обратите внимание, что старшая единица была вытолкнута за границу и исчезла.
|
||
|
||

|
||
|
||
_Рисунок 3. Иллюстрация преобразования двоичного числа при логическом сдвиге._
|
||
|
||
При арифметическом сдвиге заполнение освобождённых битов выполняется так, чтобы сохранился знак числа. В дополнительном коде знак определяется старшим битом, поэтому:
|
||
|
||
- при **арифметическом сдвиге вправо** освободившиеся позиции заполняются значением старшего бита исходного числа. Это позволяет сохранить знак. Например, арифметический сдвиг на два бита вправо числа _<ins>100110</ins>10_ даёт _11<ins>100110</ins>_;
|
||
- **арифметический сдвиг влево** эквивалентен логическому, так как заполнение младших битов нулями не влияет на знак числа.
|
||
|
||

|
||
|
||
_Рисунок 4. Иллюстрация преобразования двоичного числа при арифметическом сдвиге._
|
||
|
||
Битовый сдвиг имеет важный арифметический смысл — он соответствует умножению или делению числа на степень двойки:
|
||
|
||
- сдвиг влево на _N_ бит эквивалентен умножению на _2<sup>N</sup>_,
|
||
- сдвиг вправо на _N_ бит эквивалентен целочисленному делению на _2<sup>N</sup>_.
|
||
|
||
Этот приём знаком должен быть знаком вам при работе с десятичной системой: умножая число на 10, мы просто дописываем справа ноль. То же самое работает и для деления: если отрезать последний разряд у числа <ins>4</ins>2, получится 4, что соответствует целочисленному делению на 10. В двоичной системе добавление (стирание) нуля справа эквивалентно умножению (делению) на 2.
|
||
|
||
Арифметический сдвиг важен тем, что сохраняет это свойство для знаковых чисел, представленных в дополнительном коде. Логический сдвиг вправо изменяет и знак, и модуль числа, поэтому не может использоваться для деления знаковых чисел.
|
||
|
||
Операции умножения и деления — это очень «дорогие» операции как с точки зрения элементов схемы (если эти операции реализуются аппаратно), так и с точки зрения времени их вычисления. Поэтому выполнение сдвигов в качестве замены умножения применяется повсеместно. Например, написав в коде языка C++ выражение `var * 8`, после компиляции вы наверняка получите операцию сдвига влево на 3.
|
||
|
||
Ещё одно применение сдвигов: установка и очищение флагов управляющих регистров. Дело в том, что обычно процессоры не имеют доступа к отдельным битам многоразрядных регистров — их значения читаются записываются целиком. Вот как можно реализовать битовые операции с помощью сдвигов:
|
||
|
||
```C++
|
||
X = X | (1 << N); // Установка N-го бита
|
||
X = X & ~(1 << N); // Очистка N-го бита
|
||
Y = (X & (1 << N)) != 0; // Чтение N-го бита
|
||
```
|
||
|
||
### Особенности реализации сдвига
|
||
|
||
> [!IMPORTANT]
|
||
> Для **ВСЕХ** операций сдвига в данной лабораторной работе вы должны брать только 5 младших бит операнда B.
|
||
|
||
Сами посмотрите: выполнять операцию сдвига более чем на 31 для 32-битных чисел не имеет смысла, число полностью заполнится нулями (единицами). Т.е. сдвигая на любое число, большее 31, вы получите один и тот же результат. Для того чтобы закодировать 31 требуется минимум 5 бит, отсюда и это требование. Оно обязательно, поскольку старшие биты в дальнейшем будут использоваться по другому назначению и, если вы упустите это, ваш будущий процессор станет работать неправильно.
|
||
|
||
## Задание
|
||
|
||
Необходимо на языке SystemVerilog реализовать АЛУ в соответствии со следующим прототипом:
|
||
|
||
```Verilog
|
||
|
||
module alu (
|
||
input logic [31:0] a_i,
|
||
input logic [31:0] b_i,
|
||
input logic [4:0] alu_op_i,
|
||
output logic flag_o,
|
||
output logic [31:0] result_o
|
||
);
|
||
|
||
import alu_opcodes_pkg::*; // импорт параметров, содержащих
|
||
// коды операций для АЛУ
|
||
|
||
endmodule
|
||
```
|
||
|
||
Для стандартного набора целочисленных операций архитектуры RISC-V требуется выполнять 16 различных операций. Для кодирования 16 операций было бы достаточно 4 бит, но в лабораторной работе предлагается использовать 5-битный код, что связано с особенностями кодирования инструкций. Старший бит кода операции указывает на то, является ли операция вычислительной или это операция сравнения.
|
||
|
||
Для удобства чтения, список инструкций разбит на две таблицы.
|
||
|
||
В первой таблице перечислены операции, вычисляющие значение сигнала `result_o`. **При любом коде операции, не входящим в эту таблицу, сигнал `result_o` должен быть равен нулю**.
|
||
|
||
|Операция|={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 | Побитовое логическое **И** |
|
||
|
||
_Таблица 1. Список вычислительных операций._
|
||
|
||
Во второй таблице перечислены операции, вычисляющие значение сигнала `flag_o`. **При любом коде операции, не входящим в эту таблицу, сигнал flag_o должен быть равен нулю**.
|
||
|
||
|Операция|={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 | **Беззнаковое** сравнение **≥** |
|
||
|
||
_Таблица 2. Список операций сравнения._
|
||
|
||
**Выражения в этих двух таблицах приведены для примера. Не все из них можно просто переписать — часть этих выражений надо дополнить. Чтобы вы не копировали выражения, в них вставлены неподдерживаемые символы.**
|
||
|
||
Несмотря на разделение на вычислительные операции, и операции сравнения, в _Таблице 1_ (вычислительных операция) оказалось две операции `SLTS` и `SLTU`, которые выполняют сравнения. В итоге у нас есть две похожие пары инструкций:
|
||
|
||
- `LTS`
|
||
- `LTU`
|
||
- `SLTS`
|
||
- `SLTU`
|
||
|
||
Первая пара инструкций вычисляет "ветвительный" результат. Результат операции будет подан на выходной сигнал `flag_o` и использован непосредственно при ветвлении.
|
||
|
||
Вторые две инструкции используются для получения "вычислительного" результата. Т.е. результат сравнения будет подан на выходной сигнал `result_o` так же, как подается результат операции `ADD`, и будет использован в неких вычислениях, избегая при этом условного перехода.
|
||
|
||
К примеру, нам необходимо пройтись по массиву из миллиона элементов и убедиться, что все они были неотрицательны. Об этом будет сигнализировать переменная `num_of_err`, значение которой должно быть равно числу элементов массива, меньших нуля. Вычислить значение этой переменной можно двумя способами:
|
||
|
||
1. В каждой итерации цикла сделать ветвление: в одном случае инкрементировать переменную, в другом случае — нет (для ветвления использовать "ветвительную" операцию `LTS`).
|
||
2. В каждой итерации цикла складывать текущее значение переменной с результатом "вычислительной" операции `SLTS`.
|
||
|
||
Операции ветвления очень сильно влияют (в худшую сторону) на производительность конвейерного процессора. В первом случае мы получим миллион операций ветвления, во втором — ни одной! Разумеется, потом переменную `num_of_err` скорее всего сравнят с нулем что приведет к ветвлению, но при вычислении значения этой переменной ветвления можно будет избежать.
|
||
|
||
Различие между `SLTS` и `SLTU` (или `LTS` и `LTU`) заключается в том, как мы интерпретируем операнды: как знаковые числа (операции `STLS` и `LTS`) или как беззнаковые (операции `SLTU` и `LTU`).
|
||
|
||
Предположим, мы сравниваем два двоичных числа: `1011` и `0100`. Если интерпретировать эти числа как беззнаковые, то это `11` и `4`, результат: `11 > 4`. Однако если интерпретировать эти числа как знаковые, то теперь это числа `-5` и `4` и в этом случае `-5 < 4`.
|
||
|
||
Как мы видим, результат одной и той же операции над одними и теми же двоичными числами может зависеть от того, каким образом мы интерпретируем эти двоичные числа. Для большинства операций в АЛУ это не важно: например, сложение будет работать одинаково в обоих случаях, благодаря свойствам дополнительного кода, а побитовые операции работают с отдельными битами двоичного числа. А вот для операции арифметического сдвига это важно — **операнд А в арифметическом сдвиге должен интерпретироваться как знаковый**.
|
||
|
||
По умолчанию SystemVerilog интерпретирует все сигналы как беззнаковые, если мы хотим изменить это поведение, необходимо воспользоваться конструкцией `$signed`.
|
||
|
||
Конструкция `$signed` говорит САПР интерпретировать число, переданное в качестве операнда, как знаковое.
|
||
|
||
```Verilog
|
||
assign Result = $signed(A) / 10;
|
||
```
|
||
|
||
В этом примере некоторому сигналу `Result` присваивают результат деления **знакового** числа `A` на `10`.
|
||
|
||
Так как используются не все возможные комбинации управляющего сигнала АЛУ, то **при описании через `case` не забывайте использовать `default`**. Если описать АЛУ как задумано, то получится что-то похожее на _рис. 5_. Но не обязательно, зависит от вашего описания.
|
||
|
||

|
||
|
||
_Рисунок 5. Пример схемы, реализующей АЛУ._
|
||
|
||
Обратите внимание на то, что сумматор на _рис. 5_ отличается от всех остальных блоков. Для того, чтобы спроектированный в ЛР№1 32-разрядный сумматор был создан не зазря, а также для закрепления навыков по созданию экземпляров модулей внутри других модулей, вам предлагается использовать его при реализации АЛУ.
|
||
|
||
Другие блоки распознаны Vivado на основе представленных в описании АЛУ арифметических или логических выражений и в процессе синтеза будут реализованы через те компоненты ПЛИС, которые позволят лучше всего удовлетворить временным и физическим ограничениям проекта (см. главу "Этапы реализации проекта в ПЛИС"). Сумматор же будет реализован так, как это описали мы, поскольку вместо использования абстрактной операции "+" в описании АЛУ было сказано разместить конкретный модуль, реализующий конкретную схему. Такая реализация сумматора не является эффективной ни в плане временных характеристик, ни в плане количества затраченных на реализацию ресурсов ПЛИС. Но как уже упоминалось в ЛР№1, цель этой реализации — воспроизвести простоту логики рассуждений о том, как спроектировать сумматор.
|
||
|
||
### Порядок выполнения задания
|
||
|
||
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. [Д.М. Харрис, С.Л. Харрис / Цифровая схемотехника и архитектура компьютера: RISC-V / пер. с англ. В. С. Яценков, А. Ю. Романов; под. ред. А. Ю. Романова / М.: ДМК Пресс, 2021](https://e.lanbook.com/book/241166).
|