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

|
||
|
||
*Рисунок 1. Структурное обозначение элемента АЛУ*
|
||
|
||
На рис. 1 изображен пример АЛУ, используемый в книге "Цифровая схемотехника и архитектура компьютера" Харрис и Харрис. На входы `A` и `B` поступают операнды с разрядностью *N*. На трехбитный вход `F` подается код операции. Например, если туда подать `000`, то на выходе `Y` появится результат операции *логическое И* между битами операндов `A` и `B`. Если на `F` подать `010`, то на выходе появится результат сложения. Это лишь пример, разрядность и коды могут отличаться в зависимости от количества выполняемых операций и архитектуры.
|
||
|
||
Существует несколько подходов к реализации АЛУ, отличающиеся внутренней организацией. В лабораторных работах применяется повсеместно используемый подход мультиплексирования операций, то есть подключения нескольких операционных устройств (которые выполняют какие-то операции, например сложения, логическое И и т.п.) к мультиплексору, который будет передавать результат нужного операционного устройства на выходы АЛУ.
|
||
|
||
Рассмотрим на примере все того же АЛУ MIPS из книги Харрисов. На рис. 2, в левой его части, изображена внутренняя организация этого АЛУ, справа – таблица соответствия кодов операциям. На выходе схемы (внизу) стоит четырехвходовый мультиплексор, управляемый двумя из трех битов `F`. К его входам подключены *N* логических И (побитовое И *N*-разрядных операндов), *N* логических ИЛИ, *N*-разрядный сумматор и Zero Extend – устройство делающее из однобитного числа *N*-битное число, дополняя нулями слева.
|
||
|
||
К одному из входов этих операционных устройств подключен `A` без изменений, а ко второму подключен выход двухвходового мультиплексора, управляемого оставшимся битом *F*. То есть `F[2]` определяет, что будет вторым операндом: `B` или `~B`. Вдобавок `F[2]` подается на входной перенос сумматора, то есть, когда `F[2] == 1` на выходе сумматора появляется результат операции `A + ~B + 1`, что (с учетом [дополнительного кода](https://ru.wikipedia.org/wiki/Дополнительный_код)) эквивалентно `A – B`.
|
||
|
||

|
||
|
||
*Рисунок 2. Структурная схема АЛУ MIPS*
|
||
|
||
Посмотрим, что произойдет, если на вход `F` такого АЛУ подать `111`. Будет выполняться операция `SLT`(сокращение от `Set Less Then`) – выдать `1`, если `A` меньше `B`, в противном случае — выдать `0`. Биты `F[1:0]` переключат мультиплексор на выход блока Zero Extend. На вход Zero Extend поступает старший бит выхода сумматора, этот бит отвечает за знак результата. Так как `F[2] == 1`, сумматор вычисляет `A + ~B + 1`, то есть `A – B`, значит, если `A < B`, то результат вычитания будет отрицательный, а старший бит `Y[N-1] == 1`. Если `A` не меньше `B`, то разность будет неотрицательна, а `Y[N-1] == 0`, как и требуется от этой операции.
|
||
|
||

|
||
|
||
*Рисунок 3. Пример исполнения операции АЛУ*
|
||
|
||
Преимущество такой организации АЛУ в его простой модификации, настройке под нужные коды операций, читаемости кода и масштабируемости. Можно легко добавить или убрать требуемые операции. Подумайте, как бы вы обновили данную схему, если бы от вас потребовалось расширить её функционал операциями XOR (Исключающее ИЛИ) и (SGE операция "больше либо равно")?
|
||
|
||
## Инструменты
|
||
|
||
Как было сказано выше, АЛУ можно реализовать, [мультиплексируя](../../Basic%20Verilog%20structures/Multiplexors.md) результаты нескольких операционных устройств.
|
||
|
||
При описании очередной комбинации управляющего сигнала, выходу мультиплексора можно сразу присваивать необходимое логическое выражение (например результат побитового ИЛИ можно подать на выход сразу в виде выражения: `a | b`, однако в некоторых случаях выражения будут сложнее из-за различных особенностей реализации, о которых будет рассказано в задании).
|
||
|
||
### Параметры
|
||
|
||
Очень удобным на практике оказывается использование параметров. Параметры добавляют модулю гибкости, позволяя убрать "магические" константы из описания модулей, подставляя вместо них выразительное символьное имя. Параметры отдаленно схожи с макросами `#define` в языке Си, однако стоит понимать, параметры используются на этапе синтеза схемы, в отличие от макросов в языке Си, которые заменяются препроцессором еще до этапа компиляции.
|
||
|
||
Допустим, ваше устройство должно включить тостер, если на вход ему придет сигнал `32'haf3c5bd0`. Человек, не знакомый с устройством, при прочтении этого кода будет недоумевать, что это за число и почему используется именно оно. Однако, скрыв его за параметром `TOASTER_EN`, читающий поймет, что это код включения тостера. Кроме того, если некоторая константа должна использоваться в нескольких местах кода, то определив её через в виде параметра, можно будет менять её в одном месте, и она тут же поменяется везде.
|
||
|
||
Параметры позволяют влиять на структуру модуля. К примеру, описывая сумматор, можно параметризовать его разрядность и использовать этот параметр при описании модуля (например, в блоке `generate for`). В этом случае вы сможете создавать множество сумматоров различных разрядностей, подставляя при создании нужное вам значение параметра.
|
||
|
||
Параметр может быть объявлен в модуле двумя способами:
|
||
|
||
- в прототипе модуля;
|
||
- в теле описания модуля.
|
||
|
||
В первом случае для указания параметра после имени модуля ставится символ `#`, за которым в круглых скобках пишется ключевое слово `parameter` затем указывается тип параметра (по умолчанию знаковое 32-битное число), после чего указывается имя и (опционально) значение по умолчанию.
|
||
|
||
Пример:
|
||
|
||
```SystemVerilog
|
||
module overflow #(parameter WIDTH = 32)(
|
||
input logic [WIDTH-1 : 0] a, b,
|
||
output logic overflow
|
||
);
|
||
|
||
logic [WIDТН : 0] sum;
|
||
|
||
ass𝚒gn sum = a + b;
|
||
ass𝚒gn overflow = sum[WIDTH];
|
||
|
||
endmodule
|
||
```
|
||
|
||
В случае, если параметр не влияет на разрядность портов, его можно объявить в теле модуля:
|
||
|
||
```SystemVerilog
|
||
module toaster(
|
||
input logic [31:0] command,
|
||
output logic power
|
||
)
|
||
|
||
parameter TOASTER_EN = 32'haf3c5bd0;
|
||
|
||
assign power = command == TOASTER_EN;
|
||
|
||
endmodule
|
||
```
|
||
|
||
В случае АЛУ будет удобно использовать параметры для обозначения кодов команд. Во-первых, для того чтобы в `case` не допустить ошибок, а во-вторых – чтобы можно было легко менять управляющие коды для повторного использования АЛУ в других проектах.
|
||
|
||
Сравните сами:
|
||
|
||
```SystemVerilog
|
||
//parameter SLT = 5'b00011;
|
||
//parameter BEQ = 5'b11000;
|
||
|
||
//...
|
||
|
||
always_comb
|
||
case(ALUOp)
|
||
//...
|
||
5'b00011: //... // вообще же ничего не понятно
|
||
5'b11000: //... // никуда не годится
|
||
```
|
||
|
||
и
|
||
|
||
```SystemVerilog
|
||
parameter SLT = 5'b00011;
|
||
parameter BEQ = 5'b11000;
|
||
|
||
//...
|
||
|
||
аlwауs_comb
|
||
case(ALUOp)
|
||
//...
|
||
SLT: //... // очень понятно
|
||
BEQ: //... // так лаконично и красиво
|
||
```
|
||
|
||
С параметрами гораздо взрослее, серьезнее и понятнее смотрится. Кстати, сразу на заметку: в SystemVerilog можно объединять группу параметров в **пакет** (package), а затем импортировать его внутрь модуля, позволяя переиспользовать параметры без повторного их прописывания для других модулей.
|
||
|
||
Делается это следующим образом.
|
||
|
||
Сперва создается SystemVerilog-файл, который будет содержать пакет (к примеру, содержимое файла может быть таким):
|
||
|
||
```SystemVerilog
|
||
package riscv_params_pkg;
|
||
parameter ISA_WIDTH = 32;
|
||
parameter ANOTHER_EX = 15;
|
||
endpackage
|
||
```
|
||
|
||
Далее, внутри модуля, которому нужны параметры из этого пакета, необходимо сделать соответствующий импорт этих параметров. Это можно сделать либо для каждого параметра отдельно, либо импортировать все параметры сразу:
|
||
|
||
```SystemVerilog
|
||
module riscv_processor(
|
||
//...Порты
|
||
);
|
||
|
||
import riscv_params_pkg::ISA_WIDTH; // Если необходимо импортировать
|
||
import riscv_params_pkg::ANOTHER_EX; // все параметры в пакете,эти две строчки
|
||
// могут быть заменены закомментированной
|
||
// ниже строкой:
|
||
//import riscv_params_pkg::*;
|
||
|
||
endmodule
|
||
```
|
||
|
||
---
|
||
|
||
При реализации АЛУ, вам также потребуется использовать [операции сдвига](https://ru.wikipedia.org/wiki/Битовый_сдвиг), к которым относятся:
|
||
|
||
- `<<` — логический сдвиг влево
|
||
- `>>` — логический сдвиг вправо
|
||
- `>>>` — арифметический сдвиг вправо
|
||
|
||
<br><br><br><br><br>
|
||
|
||
---
|
||
|
||
### Особенности реализации сдвига
|
||
|
||
**Для ВСЕХ операций сдвига вы должны брать только 5 младших бит операнда B.**
|
||
|
||
Сами посмотрите: пятью битами можно описать 32 комбинации [0-31], а у операнда А будет использоваться ровно 32 бита. **Это обязательное требование**, поскольку старшие биты в дальнейшем будут использоваться по другому назначению и, если вы упустите это, ваш будущий процессор станет работать неправильно.
|
||
|
||
---
|
||
|
||
<br><br><br><br><br>
|
||
|
||
## Задание
|
||
|
||
Необходимо на языке SystemVerilog реализовать АЛУ в соответствии со следующим прототипом:
|
||
|
||
```SystemVerilog
|
||
|
||
module аlu_r𝚒sсv (
|
||
𝚒nput logic [31:0] a_i,
|
||
𝚒nput logic [31:0] b_i,
|
||
𝚒nput logic [4:0] alu_op_i,
|
||
оutput logic flag_o,
|
||
оutput logic [31:0] result_o
|
||
);
|
||
|
||
import alu_opcodes_pkg::*; // импорт параметров, содержащих
|
||
// коды операций для АЛУ
|
||
|
||
endmodule
|
||
```
|
||
|
||
Для стандартного набора целочисленных операций архитектуры RISC-V требуется выполнять 16 различных операций. Для кодирования 16 операций было бы достаточно 4 бит, но в лабораторной работе предлагается использовать 5-битный код, что связано с особенностями кодирования инструкций (будет рассмотрено в лекциях 6 и 7). Видно, что старший бит кода операции указывает на то, является ли операция вычислительной или это операция сравнения.
|
||
|
||
|ALUOp|={flag, add/sub, aluop}|Result |Flag |Операция |
|
||
|-----|-----------------------|-----------------------------------|-----------------------------------|---------------------------------------------------|
|
||
| ADD | 0 0 000 |Result = А + В | Flаg = 0 | Сложение |
|
||
| SUB | 0 1 000 |Result = А – В | Flаg = 0 | Вычитание |
|
||
| SLL | 0 0 001 |Result = А << В | Flаg = 0 | Сдвиг влево |
|
||
| SLTS| 0 0 010 |Result = А < В (знаковое сравнение)| Flаg = 0 | Знаковое сравнение |
|
||
| SLTU| 0 0 011 |Result = А < В | Flаg = 0 | Беззнаковое сравнение |
|
||
| XOR | 0 0 100 |Result = А ^ В | Flаg = 0 | Побитовое исключающее **ИЛИ** |
|
||
| SRL | 0 0 101 |Result = А >> В | Flаg = 0 | Сдвиг вправо |
|
||
| SRA | 0 1 101 |Result = А >>> В | Flаg = 0 | Арифметический сдвиг вправо (операнд А — знаковый)|
|
||
| OR | 0 0 110 |Result = А \| В | Flаg = 0 | Побитовое логическое **ИЛИ** |
|
||
| AND | 0 0 111 |Result = А & В | Flаg = 0 | Побитовое логическое **И** |
|
||
| EQ | 1 1 000 |Result = 0 | Flаg = (А == В) | Выставить флаг, если **равны** |
|
||
| NE | 1 1 001 |Result = 0 | Flаg = (А != В) | Выставить флаг, если **не равны** |
|
||
| LTS | 1 1 100 |Result = 0 | Flаg = А < В (знаковое сравнение) | Знаковое сравнение **<** |
|
||
| GES | 1 1 101 |Result = 0 | Flаg = А ≥ В (знаковое сравнение) | Знаковое сравнение **≥** |
|
||
| LTU | 1 1 110 |Result = 0 | Flаg = А < В | Беззнаковое сравнение **<** |
|
||
| GEU | 1 1 111 |Result = 0 | Flаg = А ≥ В | Беззнаковое сравнение **≥** |
|
||
|
||
**Выражения в этой таблице приведены для примера. Не все из них можно просто переписать — часть этих выражений надо дополнить. Чтобы вы не копировали выражения, в них вставлены неподдерживаемые символы.**
|
||
|
||
Обратите внимание на инструкции сравнения. У нас есть две похожие пары инструкций:
|
||
|
||
- `LTS`
|
||
- `LTU`
|
||
- `SLTS`
|
||
- `SLTU`
|
||
|
||
Первая пара инструкций вычисляет "ветвительный" результат. Результат операции будет подан на выходной сигнал `Flag` и использован непосредственно при ветвлении.
|
||
|
||
Вторые две инструкции используются для получения "вычислительного" результата. Т.е. результат сравнения будет подан на выходной сигнал `Result` так же, как подается результат операции `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` говорит САПР интерпретировать число, переданное в качестве операнда, как знаковое.
|
||
|
||
```SystemVerilog
|
||
аss𝚒gn Rеsult = $s𝚒gnеd(А) >>> В[4:0];
|
||
```
|
||
|
||
В этом примере некоторому сигналу `Result` присваивают результат сдвига знакового числа `A` на значение количества бит получаемых из младших 5 бит сигнала `B`.
|
||
|
||
Так как используются не все возможные комбинации управляющего сигнала АЛУ, то **при описании через `case` не забывайте использовать `default`**. Если описать АЛУ как задумано, то получится что-то похожее на картинку ниже. Но не обязательно, зависит от вашего описания.
|
||
|
||

|
||
|
||
### Порядок выполнения задания
|
||
|
||
1. Добавьте в проект файл [`alu_opcodes_pkg.sv`](alu_opcodes_pkg.sv). Этот файл содержит объявление пакета `alu_opcodes_pkg`, в котором прописаны все опкоды АЛУ.
|
||
2. В `Design Sources` проекта создайте `SystemVerilog`-файл `аlu_r𝚒sсv.sv`.
|
||
3. Опишите в нем модуль АЛУ с таким же именем и портами, как указано в [задании](#задание).
|
||
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. При реализации операций сдвига, руководствуйтесь [особенностями реализации сдвигов](#особенности-реализации-сдвига).
|
||
4. После реализации модуля АЛУ его нужно будет проверить с помощью тестового окружения.
|
||
1. Добавьте файл [`tb_alu.sv`](tb_alu.sv) в `Simulation sources`.
|
||
2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md).
|
||
3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран модуль `tb_alu`.
|
||
4. Убедитесь, что симуляция завершена (об этом будет соответствующее сообщение в консоли). По завершению симуляции, в случае отсутствия ошибок, будет выведено сообщение "SUCCESS", в противном случае будут выведены сообщения об этих ошибках.
|
||
5. В случае, если были найдены ошибки, вы должны найти и исправить их. Для этого руководствуйтесь [документом](../../Vivado%20Basics/Debug%20manual.md).
|
||
5. Добавьте в проект модуль верхнего уровня ([nexys_alu.sv](board%20files/nexys_alu.sv)), соединяющий АЛУ с периферией в ПЛИС. Описание модуля находится [здесь](board%20files)
|
||
6. Подключите к проекту файл ограничений ([nexys_a7_100t.xdc](board%20files/nexys_a7_100t.xdc), файл ограничений, добавленный в первой лабораторной содержит другие данные, вам необходимо вставить содержимое файла для текущей лабы).
|
||
7. Проверьте работу АЛУ в ПЛИС.
|