ЛР5. Рефактор методички (#87)

* ЛР5. Рефактор методички

* Apply suggestions from code review

Co-authored-by: Mikhail Popov <gr33nka@icloud.com>

* Замена "разрядности" на "битность" в рисунке

---------

Co-authored-by: Mikhail Popov <gr33nka@icloud.com>
This commit is contained in:
Andrei Solodovnikov
2024-06-27 09:29:55 +03:00
committed by GitHub
parent cdba67e4a3
commit a6bcc2f718
4 changed files with 323 additions and 148 deletions

View File

@@ -1,99 +1,40 @@
# Лабораторная работа 5 "Основной дешифратор команд"
# Лабораторная работа 5 "Декодер инструкций"
Устройство управления один из базовых блоков процессора, функцией которого является декодирование инструкций и выдача управляющих сигналов для всех блоков процессора.
Устройство управления (УУ) один из базовых блоков процессора, функцией которого является декодирование инструкций и выдача управляющих сигналов для всех блоков процессора. Роль УУ в данном курсе (с некоторыми оговорками) будет играть декодер инструкций.
## Цель
Описать на языке **SystemVerilog** блок основного дешифратора команд (модуль **Main Decoder**) для однотактного процессора с архитектурой **RISC-V**.
Описать на языке **SystemVerilog** блок декодера инструкций (модуль **decoder_riscv**) для однотактного процессора с архитектурой **RISC-V**.
## Допуск к лабораторной работе
## Материал для подготовки к лабораторной работе
- Изучить форматы кодирования инструкций базового набора команд [`RV32I`](../../Other/rv32i.md)
- Изучить форматы кодирования инструкций базового набора команд [`RV32I`](../../Other/rv32i.md).
- Изучить [теорию по регистрам контроля и статуса](../../Other/CSR.md).
- Изучить [различия между блокирующими и неблокирующими присваиваниями](../../Basic%20Verilog%20structures/Assignments.md).
## Ход работы
1. Изучить особенности архитектуры **RISC-V** ([#теория](#архитектура-risc-v-и-предлагаемая-микроархитектура))
2. Изучить конструкции **SystemVerilog**, с помощью которых будет описан дешифратор ([#инструменты](#инструменты))
3. Реализовать на языке **SystemVerilog** модуль основного дешифратора команд **Main Decoder** ([#задание](#задание))
4. Верифицировать разработанное устройство с помощью предлагаемого **testbench** (в том же [#задании](#задание))
1. Изучить микроархитектуру реализуемого процессорного ядра.
1. Разобраться с логикой формирования управляющих сигналов для всех типов инструкций.
2. Изучить [описание сигналов декодера инструкций](#описание-сигналов-декодера-инструкций).
3. Изучить [набор поддерживаемых инструкций **RISC-V** и способы их кодирования](#набор-поддерживаемых-инструкций-risc-v-и-способы-их-кодирования)
4. Изучить конструкции **SystemVerilog**, с помощью которых будет описан декодер ([#инструменты](#инструменты))
5. Реализовать на языке **SystemVerilog** модуль декодера инструкций **decoder_riscv** ([#задание](#задание))
6. Верифицировать разработанное устройство с помощью предлагаемого **testbench** (в том же [#задании](#задание))
## Архитектура RISC-V и предлагаемая микроархитектура
## Предлагаемая микроархитектура процессора RISC-V
### Набор инструкций **RISC-V** и способы их кодирования
На _рис. 1_ приводится микроархитектура реализуемого ядра процессора RISC-V.
Все инструкции архитектуры **RISC-V** можно условно разделить на три категории:
- Вычислительные инструкции (операции выполняются на АЛУ)
- Использующие в качестве операндов два регистра
- Использующие в качестве операндов регистр и непосредственный операнд из инструкции (константу)
- Инструкции для доступа к памяти
- Загрузки из основной памяти в регистровый файл
- Сохранения данных из регистрового файла в основную память
- Инструкции управления программой (управляют тем, как изменится счетчик команд `PC`)
- Условные переходы
- Безусловные переходы
В _Таблице 1_ приводится фрагмент из `спецификации RISC-V`. В верхней её части приводится 6 форматов кодирования инструкций: **R**, **I**, **S**, **B**, **U** и **J**, затем идут конкретные значения полей внутри инструкции. Под `rd` подразумевается 5-битный адрес регистра назначения (**r**egister **d**estination), `rs1` и `rs2` —5-битные адреса регистров источников (**r**egister **s**ource), `imm` — константа (immediate), расположение и порядок битов которой указывается в квадратных скобках. Обратите внимание, что в разных форматах кодирования константы имеют различную разрядность, а их биты упакованы по-разному. Для знаковых операций константу предварительно знаково расширяют до 32 бит. Для беззнаковых расширяют нулями до 32 бит.
![../../.pic/Labs/lab_05_decoder/rv32i_BIS.png](../../.pic/Labs/lab_05_decoder/rv32i_BIS.png)
_Таблица 1. Базовый набор инструкций из спецификации RISC-V[[1, стр.130]](https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf), Стандартное расширение Zicsr[[1, стр.131]](https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf), а также привилегированная инструкция mret[[2, стр.138]](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf)._
| Кодирование | Описание |
|-------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| R-тип | Арифметические и логические операции над двумя регистрами с записью результата в третий (регистр назначения может совпадать с одним из регистров-источников) |
| I-тип | Инструкции с 12-битным непосредственным операндом |
| S-тип | Инструкции записи в память (инструкции store) |
| B-тип | Инструкции ветвления |
| U-тип | Инструкции с 20-битным «длинным» непосредственным операндом, сдвинутым влево на 12 |
| J-тип | Единственная инструкция jal, осуществляющая безусловный переход по адресу относительно текущего счетчика команд |
_Таблица 2. Описание типов форматов кодирования инструкций ISA RISC-V._
### SYSTEM-инструкции
SYSTEM-инструкции используются для доступа к системным функциям и могут требовать привилегированный доступ. Данные инструкции могут быть разделены на два класса:
- Обращение к регистрам статуса и контроля (**CSR**)
- Все остальные инструкции (возможно из набора привилегированных инструкций)
Для будущей поддержки прерываний, нам потребуется декодировать инструкции обоих классов.
Обращение к регистрам статуса и контроля осуществляется шестью инструкциями стандартного расширения `Zicsr`. Каждая из этих инструкций (если у нее легальные поля) осуществляет запись в CS-регистры и регистровый файл (блоки `Control Status Registers` и `Register File` на _рис. 1_ соответственно).
Кроме того, для возврата управления основному потоку инструкций, нужна дополнительная `SYSTEM`-инструкция привилегированного набора команд `MRET`.
Единственное что нужно сделать при появлении этой инструкции — это сформировать единицу на одноименном выходе.
Перечисленные выше инструкции являются "дополнительными" — их намеренно их добавили сверх стандартного набора инструкций, чтобы обеспечить требуемый нашей системе функционал. Однако осталось ещё две SYSTEM-инструкции, которые мы должны уметь декодировать, поскольку они есть в стандартном наборе инструкций.
Инструкции `ECALL` и `EBREAK` должны вызывать исключение. Единственное исключение, которое будет поддерживаться нашей системой, это исключение через сигнал `illegal_instr_o`, поэтому в рамках данного цикла лабораторных работ вам предлагается выставлять `illegal_instr_o == 1`, когда приходят эти инструкции (что довольно легко описать, ведь получается что если SYSTEM-инструкция не является `MRET` либо инструкцией из набора `Zicsr`, то это либо нелегальная инструкция, либо `ECALL` / `EBREAK`, которые ведут себя точно так же как и нелегальная инструкция).
### MISC-MEM инструкция
В базовом наборе инструкций **RISC-V** к `MISC-MEM`-операции относится инструкция `FENCE`. В реализуемом процессорном ядре эта инструкция не должны приводить ни к каким изменениям. Инструкция `FENCE` в **RISC-V** необходима при работе с несколькими аппаратными потоками, или "хартами" (hart «hardware thread»). В **RISC-V** используется расслабленная модель памяти (**relaxed memory model**): потоки «видят» все инструкции чтения и записи, которые исполняются другими потоками, однако видимый порядок этих инструкций может отличаться от реального. Инструкция `FENCE`, использованная между двумя инструкциями чтения и/или записи гарантирует, что остальные потоки увидят первую инструкцию перед второй. Реализация `FENCE` является опциональной в **RISC-V** и в данном случае в ней нет необходимости, так как в системе не предполагается наличия нескольких аппаратных потоков. Данная инструкция должна быть реализована как `NOP` (no operation).
![../../.pic/Labs/lab_05_decoder/rv32i_summary.png](../../.pic/Labs/lab_05_decoder/rv32i_summary.png)
_Таблица 3. Инструкции набора RV32I с приведением их типов, функционального описания и примеров использования._
Обратите внимание на операции `slli`, `srli` и `srai` (операции сдвига на константную величину). У этих инструкций немного измененный формат кодирования **I\***. Формат кодирования **I** предоставляет 12-битную константу. Сдвиг 32-битного числа более, чем на 31 не имеет смысла. Для кодирования числа 31 требуется всего 5 бит. Выходит, что из 12 бит константы используется только 5 бит для операции сдвига (в виде поля `shamt`, сокращение от **sh**ift **am**oun**t** — "сколько раз сдвигать"), а оставшиеся 7 бит – не используются. А, главное (какое совпадение!), эти 7 бит находятся ровно в том же месте, где у других инструкций находится поле `func7`. Поэтому, чтобы у инструкций `slli`, `srli` и `srai` использующих формат **I** не пропадала эта часть поля, к ней относятся как к полю `func7`.
Также обратите внимание на инструкции `ecall`, `ebreak` и `mret`. Все эти инструкции I-типа имеют поле func3, равное нулю. С точки зрения декодирования инструкции I-типа, это одна и та же инструкция с разными значениями поля `imm`. Однако конкретно в данном случае (SYSTEM_OPCODE и `func3 == 0`) эти инструкции должны рассматриваться как совокупность всех 32-бит сразу (см. _Таблицу 1_).
### Предлагаемая микроархитектура процессора RISC-V
На _рис. 1_ приводится микроархитектура ядра процессора RISC-V. Регистр `PC` (Program Counter счетчик команд) подключен к адресному входу памяти инструкций. Считываемая инструкция декодируется основным дешифратором, после чего он выставляет управляющие сигналы для всех блоков процессора (мультиплексоры, АЛУ, интерфейс взаимодействия с памятью).
Приведенная архитектура не является заданием для текущей лабораторной работы, лишь отражает то, как в дальнейшем будет подключаться и использоваться реализуемый в данной лабораторной основной дешифратор.
**Приведенная архитектура не является заданием для текущей лабораторной работы, лишь отражает то, как в дальнейшем будет подключаться и использоваться реализуемый в данной лабораторной работе декодер.**
![../../.pic/Labs/lab_11_irq_integration/fig_01.drawio.svg](../../.pic/Labs/lab_11_irq_integration/fig_01.drawio.svg)
_Рисунок 1. Микроархитектура будущего процессорного ядра._
Предложенная микроархитектура процессора `CYBERcobra 3000 Pro 2.0` из прошлой лабораторной имеет схожую структуру, с некоторыми изменениями.
Предложенная микроархитектура похожа на микроархитектуру процессора `CYBERcobra 3000 Pro 2.0` из прошлой лабораторной, имеет схожую структуру, с некоторыми изменениями.
В первую очередь изменились входы и выходы процессора (помимо появления двух непонятных модулей):
В первую очередь изменились входы и выходы процессора:
- память инструкций вынесена наружу процессора, таким образом, у процессора появляются входы и выходы: `instr_addr_o` и `instr_i`;
- помимо прочего, у модуля появились сигналы интерфейса памяти данных:
@@ -103,17 +44,82 @@ _Рисунок 1. Микроархитектура будущего проце
- `mem_we_o` — сигнал разрешения записи во внешнюю память;
- `mem_wd_o` — данные для записи во внешнюю память;
- `mem_rd_i` — считанные из внешней памяти данные;
- еще у процессора появился вход `stall_i`, отключающий программный счетчик.
Эти сигналы используются при выполнении инструкций загрузки (сохранения) информации из (в) памяти данных.
- еще у процессора появился вход `stall_i`, приостанавливающий обновление программного счётчика.
Так же добавились источники операндов АЛУ: программный счетчик, множество констант из инструкций и микроархитектурных констант. Выбор нужных операндов для АЛУ осуществляется с помощью двух мультиплексоров, управляемых сигналами декодера `a_sel_o` и `b_sel_o`.
Кроме того, появилось два новых модуля: **Interrupt Controller** и **Control Status Registers**. Эти модули будут обеспечивать поддержку прерываний в процессорной системе.
Изменилось и число источников записи в регистровый файл: их стало 3: результат операции на АЛУ, данные, считанные с внешней памяти и данные из модуля регистров статуса и контроля. Выборка осуществляется сигналом декодера `wb_sel_o`.
Так же добавились источники операндов АЛУ: программный счетчик, множество констант из инструкций и микроархитектурных констант.
Изменились и источники записи в регистровый файл, теперь это:
- результат операции на АЛУ;
- данные, считанные с внешней памяти;
- данные из модуля регистров контроля и статуса.
Для того, чтобы управлять усложнившимся набором мультиплексоров, интерфейсом памяти данных и появившимися модулями нужно специальное устройство — Устройство управления (УУ). В данной микроархитектуре логика устройства управления не вынесена в отдельный модуль, лишь выделена на схеме синим цветом. По большей части, в предложенной микроархитектуре роль устройства управления выполняет декодер инструкций.
## Описание сигналов декодера инструкций
Список портов декодера инструкций и их назначение представлен в аблице 1_.
|Название сигнала| Пояснение |
|----------------|------------------------------------------------------------------------------------------------|
|fetched_instr_i |Инструкция, подлежащая декодированию |
|a_sel_o |Управляющий сигнал мультиплексора для выбора первого операнда АЛУ |
|b_sel_o |Управляющий сигнал мультиплексора для выбора второго операнда АЛУ |
|alu_op_o |Операция АЛУ |
|csr_op_o |Операция модуля CSR |
|csr_we_o |Разрешение на запись в CSR |
|mem_req_o |Запрос на доступ к памяти (часть интерфейса памяти) |
|mem_we_o |Сигнал разрешения записи в память, «write enable» (при равенстве нулю происходит чтение) |
|mem_size_o |Управляющий сигнал для выбора размера слова при чтении-записи в память (часть интерфейса памяти)|
|wb_sel_o |Управляющий сигнал мультиплексора для выбора данных, записываемых в регистровый файл |
|gpr_we_o |Сигнал разрешения записи в регистровый файл |
|branch_o |Сигнал об инструкции условного перехода |
|jal_o |Сигнал об инструкции безусловного перехода jal |
|jalr_o |Сигнал об инструкции безусловного перехода jalr |
|mret_o |Сигнал об инструкции возврата из прерывания/исключения mret |
|illegal_instr_o |Сигнал о некорректной инструкции |
_Таблица 1. Описание портов декодера инструкций._
У данного модуля будет лишь один вход: `fetched_instr_i` — декодируемая в данный момент инструкция. Все остальные сигналы — это выходы модуля, которые можно классифицировать следующим образом:
### Сигналы кода операции
В данный класс будут входить сигналы, которые сообщают отдельному функциональному блоку о том, какую из операций он должен выполнить. Таких блока два: **АЛУ** и модуль **регистров контроля и статуса**. АЛУ может выполнять одну из 16 операций, представленных в ЛР№2, для выбора которой и нужен подобный сигнал. Вы еще не знакомы с появившимся в микроархитектуре модулем регистров контроля и статуса, однако на текущий момент нужно лишь понимать, что он тоже может выполнять одну из нескольких операций и что для этого ему нужен специальный сигнал.
Таким образом, в класс сигналов кода операции входят:
- `alu_op_o`,
- `csr_op_o`.
Для удобства использования, возможные значения этих сигналов определены в виде параметров в пакетах `alu_opcodes_pkg` и `csr_pkg` соответственно.
### Управляющие сигналы мультиплексоров стадии выполнения и записи результата
В данный класс входят сигналы, управляющие мультиплексорами, размещенными в правой части схемы:
- `a_sel_o`,
- `b_sel_o`,
- `wb_sel_o`.
Сигналы `a_sel_o` и `b_sel_o` определяют откуда пойдут данные на операнды АЛУ `a_i`, `b_i` соответственно. К примеру, если мы хотим, чтобы оба операнда брались из регистрового файла, нам необходимо подать значение `0` на оба соответствующих мультиплексора.
Сигнал `wb_sel_o` определяет источник данных для записи в регистровый файл: это либо результат операции на АЛУ, считанные данные из памяти данных, либо же данные, полученные из модуля регистров контроля и статуса.
### Интерфейс памяти
Интерфейс памяти использует несколько сигналов для взаимодействия с памятью: `mem_req_o` (этот выход должен быть выставлен в 1 каждый раз, когда необходимо обратиться к памяти считать или записать), `mem_we_o` (выставляется в 1, если необходимо записать данные в память, и 0 если считать из памяти) и `mem_size_o` (указывающий размер порции данных необходимых для передачи; возможные значения указаны в _Таблице 4_). Перечисленных сигналов достаточно для того, чтобы основная память понимала: обращаются ли к ней в данный момент, нужно ли записывать или считывать данные, и о какой порции данных идет речь.
Память данных используется для хранения и доступа к информации, необходимой для выполнения программы. Несмотря на то что такая память, как и регистровый файл используются для хранения данных, назначение этих модулей различно: регистровый файл используется для хранения данных, работа над которыми осуществляется здесь и сейчас (в пределах нескольких инструкций процессора), в то время как память данных хранит всю остальную информацию, которая не может уместиться в регистровый файл в виду ограниченности его размера.
|Название|Значение `mem_size_o`| Пояснение |
Для взаимодействия с подсистемой памяти данных, декодер инструкций будет использовать следующие сигналы:
- `mem_req_o` — этот сигнал должен быть выставлен в 1 каждый раз, когда необходимо обратиться к памяти (считать или записать данные);
- `mem_we_o` — этот сигнал должен быть выставлен в 1, если необходимо записать данные в память, (0 при чтении);
- `mem_size_o`— этот сигнал указывает размер порции данных для передачи (возможные значения этого сигнала указаны в _Таблице 2_). Для удобства использования, данные значения определены в виде параметров в пакете `riscv_pkg`.
|Параметр|Значение `mem_size_o`| Пояснение |
|--------|---------------------|------------------------------|
|LDST_B | 3'd0 |Знаковое 8-битное значение |
|LDST_H | 3'd1 |Знаковое 16-битное значение |
@@ -121,66 +127,100 @@ _Рисунок 1. Микроархитектура будущего проце
|LDST_BU | 3'd4 |Беззнаковое 8-битное значение |
|LDST_HU | 3'd5 |Беззнаковое 16-битное значение|
_Таблица 4. Значения сигнала `mem_size_o` при передаче различных порций данных._
_Таблица 2. Значения сигнала `mem_size_o` при передаче различных порций данных._
### Main Decoder — Основной дешифратор команд RISC-V
Перечисленных сигналов достаточно для того, чтобы основная память понимала: обращаются ли к ней в данный момент, нужно ли записывать или считывать данные, и о какой порции данных идет речь.
Как говорилось ранее, дешифратор инструкций в процессоре служит для преобразования инструкции в набор управляющих сигналов, необходимых для ее исполнения.
### Сигналы разрешения записи
Пример: для выполнения инструкции записи 32-бит данных из регистрового файла во внешнюю память `sw`, дешифратор должен:
В данную категорию входят два однобитных сигнала:
- направить в АЛУ два операнда (базовый адрес и смещение) вместе с кодом операции АЛУ (сложения) для вычисления адреса:
- `a_sel_o = 2'd0`;
- `b_sel_o = 3'd1`;
- `alu_op_o= ALU_ADD`;
- сформировать управляющие сигналы интерфейса памяти:
- `mem_req_o = 1'b1`;
- `mem_size_o= 3'd2`;
- `mem_we_o = 1'b1`.
- gpr_we_o — сигнал разрешения записи в регистровый файл (General Purpose Registers, GPR);
- csr_we_o — сигнал разрешения записи в модуле регистров контроля и статуса.
Несмотря на то, что для записи во внешнюю память ключевыми сигналами будут описанные выше, это не означает, что остальные выходные сигналы декодера могут быть абы какими.
### Сигналы управления программным счетчиком
Поскольку операция `sw` не является операцией перехода, сигналы `jal_o`, `jalr_o` и `branch_o` должны быть равны нулю (иначе процессор совершит переход, а инструкция `lw` этого не подразумевает). Точно так же, поскольку во время записи во внешнюю память, в регистровый файл не должно быть ничего записано, сигналы `gpr_we_o` и `csr_we_o` также должны быть равны нулю.
В данную категорию входят однобитные сигналы, которые оповещают о том, что выполняется инструкция, связанная с изменением значения программного счетчика:
А вот сигнал `wb_sel` может принять любое значение (поскольку сигнал разрешения записи в регистровый файл равен нулю, не важно, каким будет источник данных для записи в регистровый файл, т.к. в него все равно ничего не будет записано).
- branch_o — сигнал об инструкции условного перехода;
- jal_o — сигнал об инструкции безусловного перехода jal;
- jalr_o — сигнал об инструкции безусловного перехода jalr;
- mret_o — сигнал об инструкции возврата из прерывания/исключения mret.
---
### Сигнал нелегальной инструкции
Управляющие сигналы на выходе декодера зависят от трех полей инструкции: `opcode`, `func3` и `func7`. Обратите внимание, что расположение этих полей одинаково для всех типов инструкций. Это сделано для удобства декодирования. При этом для некоторых инструкций поля `func3` и `func7` могут отсутствовать.
Это сигнал, который должен принять значение `1` в случае если пришла инструкция, которая не входит в список поддерживаемых процессором.
|Название сигнала| Пояснение | На каких опкодах может принять ненулевое значение (см. таблицу 6)|
|----------------|------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
|fetched_instr_i |Инструкция для декодирования, считанная из памяти инструкций | — |
|a_sel_o |Управляющий сигнал мультиплексора для выбора первого операнда АЛУ | На всех кроме MISC_MEM и SYSTEM |
|b_sel_o |Управляющий сигнал мультиплексора для выбора второго операнда АЛУ | На всех кроме MISC_MEM и SYSTEM |
|alu_op_o |Операция АЛУ | На всех кроме MISC_MEM` и `SYSTEM |
|csr_op_o |Операция модуля CSR | Только на SYSTEM |
|csr_we_o |Разрешение на запись в CSR | Только на SYSTEM |
|mem_req_o |Запрос на доступ к памяти (часть интерфейса памяти) | На LOAD и STORE |
|mem_we_o |Сигнал разрешения записи в память, «write enable» (при равенстве нулю происходит чтение) | Только на STORE |
|mem_size_o |Управляющий сигнал для выбора размера слова при чтении-записи в память (часть интерфейса памяти)| На LOAD и STORE |
|gpr_we_o |Сигнал разрешения записи в регистровый файл | На всех кроме STORE, BRANCH, MISC_MEM |
|wb_sel_o |Управляющий сигнал мультиплексора для выбора данных, записываемых в регистровый файл | На всех кроме STORE, BRANCH, MISC_MEM |
|illegal_instr_o |Сигнал о некорректной инструкции (на схеме не отмечен) | На всех кроме JAL, LUI, AUIPC |
|branch_o |Сигнал об инструкции условного перехода | Только на BRANCH |
|jal_o |Сигнал об инструкции безусловного перехода jal | Только на JAL |
|jalr_o |Сигнал об инструкции безусловного перехода jalr | Только на JALR |
|mret_o |Сигнал об инструкции возврата из прерывания/исключения mret | Только на SYSTEM |
Это не единственное, что должен сделать декодер в подобной ситуации. Давайте разберем подробней, что должно происходить по приходу нелегальной инструкции.
_Таблица 5. Описание портов основного дешифратора._
## Обработка нелегальной инструкции
Единственным входным сигналом этого модуля является `fetched_instr_i`.
В случае появления инструкции, которая не поддерживается процессором, устройство управления должно обеспечить стабильность системы. В самом простом случае, такую инструкцию необходимо пропустить, сохранив так называемое **архитектурное состояние** процессора — т.е. сохранив значение всех элементов системы, характеризующих состояние системы в текущий момент. К таким элементам относятся: содержимое регистрового файла, основой памяти, содержимое регистров контроля и статуса и т.п. Значение программного счетчика также входит в архитектурное состояние процессора, однако в контексте пропуска инструкции с сохранением архитектурного состояния, его значение нужно изменить, иначе система оказалась бы в бесконечном цикле (неизменный счетчик бы указывал на ту же самую инструкцию, которая не должна менять архитектурного состояния).
В системе команд **RV32I** два младших бита поля opcode всегда равны `11`, таким образом декодер понимает, что будут исполняться именно 32-битные инструкции, а не 16-битные, например. **Main decoder** должен выдать единицу на выходе `illegal_instr_o` в случае:
Иными словами, в случае появления нелегальной инструкции, устройство управления (роль которого в нашей системе по большей части играет декодер) должно проследить за тем, чтобы в системе не изменилось ничего кроме программного счетчика. К сигналам, влияющим на изменение архитектурного состояния относятся:
- неравенства двух младших битов opcode значению `11`;
- некорректного значения `func3` или `func7` для данной операции;
- если значение `opcode` не совпадает ни с одним из известных и следовательно операция не определена.
- если это инструкция `ECALL` / `EBREAK`.
- mem_req_o,
- mem_we_o,
- gpr_we_o,
- csr_we_o,
- branch_o,
- jal_o,
- jalr_o,
- mret_o.
При реализации декодера его удобнее описывать разбив все инструкции на однотипные группы, как это сделано в _Таблице 6_. Представленные в ней коды операций 5-битные потому, что 2 младших бита полноценного 7-битного кода операции должны всегда быть равны `11`. Если это не так, то вся инструкция уже запрещенная и не нуждается в дальнейшем декодировании.
Иными словами, должны быть запрещены все запросы на запись, обращения в память и любые "прыжки" программного счетчика.
|Операция|Opcode| Описание операции | Краткая запись |
Давайте теперь разберемся с тем, какие именно инструкции должен будет поддерживать наш процессор.
### Набор поддерживаемых инструкций **RISC-V** и способы их кодирования
Все инструкции архитектуры **RISC-V** можно условно разделить на три категории.
- Вычислительные инструкции (операции выполняются на АЛУ, с записью результата в регистровый файл). В основном, это инструкции:
- использующие в качестве операндов два регистра;
- использующие в качестве операндов регистр и непосредственный операнд из инструкции (константу).
- Инструкции для доступа к памяти:
- загрузки из основной памяти в регистровый файл;
- сохранения данных из регистрового файла в основную память;
- Инструкции управления:
- Условные переходы
- Безусловные переходы
- Системные инструкции
- обращение к регистрам контроля и статуса;
- системные вызовы и возврат из обработчика прерываний
В _Таблице 3_ приводится фрагмент из `спецификации RISC-V`. В верхней её части приводится 6 форматов кодирования инструкций: **R**, **I**, **S**, **B**, **U** и **J** (описание типов представлено в аблице 4_). Затем список всех инструкций с конкретными значениями полей, соответствующих формату кодирования инструкции данного типа.
Под `rd` подразумевается 5-битный адрес регистра назначения (**r**egister **d**estination), `rs1` и `rs2` — 5-битные адреса регистров источников (**r**egister **s**ource), `imm` — непосредственный (immediate, задающийся прямиком в инструкции) операнд (константа), расположение и порядок битов которого указывается в квадратных скобках. Обратите внимание, что в разных форматах кодирования константы имеют различную разрядность, а их биты расположены по-разному. Для знаковых операций константу предварительно знаково расширяют до 32 бит. Для беззнаковых расширяют нулями до 32 бит.
![../../.pic/Labs/lab_05_decoder/rv32i_BIS.png](../../.pic/Labs/lab_05_decoder/rv32i_BIS.png)
_Таблица 3. Базовый набор инструкций из спецификации RISC-V[[1, стр.130]](https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf), Стандартное расширение Zicsr[[1, стр.131]](https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf), а также привилегированная инструкция mret[[2, стр.138]](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf)._
| Кодирование | Описание |
|-------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| R-тип | Арифметические и логические операции над двумя регистрами с записью результата в третий (регистр назначения может совпадать с одним из регистров-источников) |
| I-тип | Инструкции с 12-битным непосредственным операндом |
| S-тип | Инструкции записи в память (инструкции store) |
| B-тип | Инструкции ветвления |
| U-тип | Инструкции с 20-битным «длинным» непосредственным операндом, сдвинутым влево на 12 |
| J-тип | Единственная инструкция jal, осуществляющая безусловный переход по адресу относительно текущего счетчика команд |
_Таблица 4. Описание типов форматов кодирования инструкций ISA RISC-V._
### Декодирование инструкций RISC-V
Как уже описывалось в дополнительных материалах, декодирование инструкции начинается с поля `opcode` (**op**eration **code**, опкод). По этому полю определяется группа инструкций одного типа. Далее (для большинства типов кодирования) инструкция доопределяется через поля `func3` и `func7` (при наличии). Обратите внимание, что расположение этих полей одинаково для всех типов инструкций (см. верхнюю часть аблицы 3_).
Поля `rs1`/`rs2`/`imm` и `rd` декодеру не нужны и используются напрямую для адресации в регистровом файле / использования непосредственного операнда в АЛУ.
Существуют особые инструкции, не имеющие никаких переменных полей (к примеру инструкция ECALL в аблице 3_). Такие инструкции необходимо проверять целиком (нужно убедиться что инструкция совпадает вплоть бита).
В _Таблице 5_ представлены все опкоды реализуемых нами инструкций. Представленные в ней коды операций 5-битные потому, что 2 младших бита полноценного 7-битного кода операции в реализуемых нами инструкциях должны всегда быть равны `11`. Если это не так, то вся инструкция уже запрещенная и не нуждается в дальнейшем декодировании.
Для удобства, значения кодов операций определены в виде параметров в пакете riscv_pkg.
|Параметр|Opcode| Описание группы операций | Краткая запись |
|--------|------|-------------------------------------------------------------------------------------------------------|------------------------------------|
|OP |01100 |Записать в `rd` результат вычисления АЛУ над `rs1` и `rs2` |`rd = alu_op(rs1, rs2)` |
|OP_IMM |00100 |Записать в `rd` результат вычисления АЛУ над `rs1` и `imm` |`rd = alu_op(rs1, imm)` |
@@ -194,15 +234,104 @@ _Таблица 5. Описание портов основного дешифр
|MISC-MEM|00011 |Не производить операцию | `-` |
|SYSTEM |11100 |Записать в `rd` значение `csr`. Обновить значение `csr` с помощью `rs1`. (либо `mret`/`ecall`/`ebreak`)|`csr = csr_op(rs1); rd = csr` |
_Таблица 6. Описание кодов операций_
_Таблица 5. Описание кодов операций._
#### SYSTEM-инструкции
SYSTEM-инструкции используются для доступа к системным функциям и могут требовать привилегированный доступ. Данные инструкции могут быть разделены на два класса:
- Обращение к регистрам статуса и контроля (**Control and Status Registers**, **CSR**)
- Все остальные инструкции (возможно из набора привилегированных инструкций)
Для того, чтобы в будущем процессор поддерживал прерывания, нам требуется декодировать инструкции обоих классов.
Обращение к регистрам контроля и статуса осуществляется шестью инструкциями стандартного расширения `Zicsr`. Каждая из этих инструкций (если у нее легальные поля) осуществляет запись в CSR и регистровый файл (блоки `Control Status Registers` и `Register File` на _рис. 1_ соответственно).
Кроме того, для возврата управления основному потоку инструкций, нужна дополнительная `SYSTEM`-инструкция привилегированного набора команд `MRET`.
Перечисленные выше инструкции являются "дополнительными" — их намеренно их добавили сверх стандартного набора инструкций, чтобы обеспечить требуемый нашей системе функционал. Однако осталось ещё две SYSTEM-инструкции, которые мы должны уметь декодировать, поскольку они есть в стандартном наборе инструкций.
Инструкции `ECALL` и `EBREAK` вызывают исключение. Подробнее исключения и прерывания будут разобраны в ЛР№10.
#### MISC-MEM инструкция
В базовом наборе инструкций **RISC-V** к `MISC-MEM`-операции относится инструкция `FENCE`. В реализуемом процессорном ядре эта инструкция не должны приводить ни к каким изменениям. Инструкция `FENCE` в **RISC-V** необходима при работе с несколькими аппаратными потоками, или "хартами" (hart «hardware thread»). В **RISC-V** используется расслабленная модель памяти (**relaxed memory model**): потоки «видят» все инструкции чтения и записи, которые исполняются другими потоками, однако видимый порядок этих инструкций может отличаться от реального. Инструкция `FENCE`, использованная между двумя инструкциями чтения и/или записи гарантирует, что остальные потоки увидят первую инструкцию перед второй. Реализация `FENCE` является опциональной в **RISC-V** и в данном случае в ней нет необходимости, так как в системе не предполагается наличия нескольких аппаратных потоков. Данная инструкция должна быть реализована как `NOP` (**n**o **op**eration).
В аблице 6_ представлены инструкции из таблицы 3 с приведением их типов, значениями полей `opcode`, `func3`, `func7`, функциональным описанием и примерами использования.
![../../.pic/Labs/lab_05_decoder/rv32i_summary.png](../../.pic/Labs/lab_05_decoder/rv32i_summary.png)
_Таблица 6. Расширенное описание инструкций RV32IZicsr._
Обратите внимание на операции `slli`, `srli` и `srai` (операции сдвига на константную величину). У этих инструкций немного измененный формат кодирования **I\***. Формат кодирования **I** предоставляет 12-битную константу. Сдвиг 32-битного числа более, чем на 31 не имеет смысла. Для кодирования числа 31 требуется всего 5 бит. Выходит, что из 12 бит константы используется только 5 бит для операции сдвига (в виде поля `shamt`, сокращение от **sh**ift **am**oun**t** — "сколько раз сдвигать"), а оставшиеся 7 бит – не используются. А, главное (какое совпадение!), эти 7 бит находятся ровно в том же месте, где у других инструкций находится поле `func7`. Поэтому, чтобы у инструкций `slli`, `srli` и `srai` использующих формат **I** не пропадала эта часть поля, к ней относятся как к полю `func7`.
Также обратите внимание на инструкции `ecall`, `ebreak` и `mret`. Все эти инструкции I-типа имеют поле func3, равное нулю. С точки зрения декодирования инструкции I-типа, это одна и та же инструкция с разными значениями поля `imm`. Однако конкретно в данном случае (SYSTEM_OPCODE и `func3 == 0`) эти инструкции должны рассматриваться как совокупность всех 32-бит сразу (см. аблицу 3_).
### Выставление управляющих сигналов
Как говорилось ранее, декодер инструкций в процессоре служит для преобразования инструкции в набор управляющих сигналов, необходимых для ее исполнения. Таким образом, для каждой инструкции из аблицы 3_ декодер должен поставить в соответствие конкретное значение для каждого из выходов, перечисленных в аблице 1_.
Пример: для выполнения инструкции записи 32-бит данных из регистрового файла во внешнюю память `sw`, дешифратор должен направить в АЛУ два операнда (базовый адрес и смещение) вместе с кодом операции АЛУ (сложения) для вычисления адреса. Базовый адрес берется из регистрового файла, а смещение является непосредственным операндом инструкции S-типа. Таким образом для вычисления адреса записи декодер должен выставить следующие значения на выходах:
- `a_sel_o = 2'd0`,
- `b_sel_o = 3'd1`,
- `alu_op_o= ALU_ADD`.
Кроме того, для самой операции записи в основную память, декодер должен сформировать управляющие сигналы интерфейса памяти (запрос на обращение в память, размер передаваемых данных и сигнал разрешения записи):
- `mem_req_o = 1'b1`,
- `mem_size_o = LDST_W` (см. аблицу 2_),
- `mem_we_o = 1'b1`.
Несмотря на то, что для записи во внешнюю память ключевыми сигналами будут описанные выше, это не означает, что остальные выходные сигналы дешифратора команд могут быть абы какими.
Поскольку операция `sw` не является операцией перехода, сигналы `jal_o`, `jalr_o` и `branch_o` и `mret` должны быть равны нулю (иначе процессор совершит переход, а инструкция `sw` этого не подразумевает). Точно так же, поскольку во время записи во внешнюю память, в регистровый файл и регистры контроля и статуса ничего не должно быть записано, сигналы `gpr_we_o` и `csr_we_o` также должны быть равны нулю.
Иными словами, крайне важно следить выходными сигналами, влияющими на изменение архитектурного состояния процессора, не затрагиваемые инструкцией в явном виде.
А вот сигнал `wb_sel` может принять любое значение (поскольку сигнал разрешения записи в регистровый файл равен нулю, не важно, каким будет источник данных для записи в регистровый файл, т.к. в него все равно ничего не будет записано).
Разумеется, описывая модуль декодера инструкций, было бы нерационально прописывать для каждой из 47 инструкций значение 14 выходов модуля, особенно учитывая, что многие выходные сигналы будут иметь одно и то же значение для всех инструкций одного опкода, поэтому удобнее всего будет описывать их, сгруппировав по кодам операций.
В аблице 7_ определен список выходных сигналов декодера инструкций и групп инструкций, при которых эти выходы могут принимать ненулевое значение.
|Название сигнала| Пояснение | На каких опкодах может принять ненулевое значение (см. таблицу 6)|
|----------------|------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
|a_sel_o |Управляющий сигнал мультиплексора для выбора первого операнда АЛУ | На всех кроме MISC_MEM и SYSTEM |
|b_sel_o |Управляющий сигнал мультиплексора для выбора второго операнда АЛУ | На всех кроме MISC_MEM и SYSTEM |
|alu_op_o |Операция АЛУ | На всех кроме MISC_MEM и SYSTEM |
|csr_op_o |Операция модуля CSR | Только на SYSTEM |
|csr_we_o |Разрешение на запись в CSR | Только на SYSTEM |
|mem_req_o |Запрос на доступ к памяти (часть интерфейса памяти) | На LOAD и STORE |
|mem_we_o |Сигнал разрешения записи в память, «write enable» (при равенстве нулю происходит чтение) | Только на STORE |
|mem_size_o |Управляющий сигнал для выбора размера слова при чтении-записи в память (часть интерфейса памяти)| На LOAD и STORE |
|gpr_we_o |Сигнал разрешения записи в регистровый файл | На всех кроме STORE, BRANCH, MISC_MEM |
|wb_sel_o |Управляющий сигнал мультиплексора для выбора данных, записываемых в регистровый файл | На всех кроме STORE, BRANCH, MISC_MEM |
|illegal_instr_o |Сигнал о некорректной инструкции (на схеме не отмечен) | На всех кроме JAL, LUI, AUIPC |
|branch_o |Сигнал об инструкции условного перехода | Только на BRANCH |
|jal_o |Сигнал об инструкции безусловного перехода jal | Только на JAL |
|jalr_o |Сигнал об инструкции безусловного перехода jalr | Только на JALR |
|mret_o |Сигнал об инструкции возврата из прерывания/исключения mret | Только на SYSTEM |
_Таблица 5. Описание портов дешифратора команд._
Дешифратор должен выдать единицу на выходе `illegal_instr_o` в случае:
- неравенства двух младших битов opcode значению `11`;
- если значение поля `opcode` не совпадает ни с одним из известных и следовательно операция не определена.
- некорректного значения полей `func3` или `func7` для данного опкода.
Кроме того, поскольку представленная на _рис. 1_ микроархитектура поддерживает только одно исключение (исключение через сигнал `illegal_instr_o`), этот сигнал должен быть равен единице и в случае:
- если это инструкция `ECALL` / `EBREAK`.
## Инструменты
В первую очередь язык описания аппаратуры **SystemVerilog** – это язык. С помощью этого языка человек объясняет либо синтезатору какое он хочет получить устройство, либо симулятору как он хочет это устройство проверить. Синтезатор – это программа, которая создает из логических элементов цифровое устройство по описанию, предоставляемому человеком. Синтезатору внутри **Vivado** нужно объяснить, что ты от него хочешь. Например, чтобы спросить дорогу у испанца, придется делать это на испанском языке, иначе он ничем не сможет помочь. Если ты знаешь испанский, то это можно сделать еще и разными способами. В **SystemVerilog** точно также одно и то же устройство можно описать разным кодом, но результат синтеза будет одним и тем же. Однако, часто два разных кода одинаковые по смыслу могут синтезироваться в разную аппаратуру, хотя функционально они будут идентичны, но могут отличаться, например, скоростью работы. Или одни и те же специальные языковые конструкции могут применяться для синтезирования разных цифровых элементов.
В первую очередь язык описания аппаратуры **SystemVerilog** – это язык. С помощью этого языка человек объясняет либо синтезатору какое он хочет получить устройство, либо симулятору как он хочет это устройство проверить. Синтезатор – это программа, которая создает из логических элементов цифровое устройство по описанию, предоставляемому человеком. Синтезатору внутри **Vivado** нужно объяснить, что от него нужно. Например, чтобы спросить дорогу у испанца, придется делать это на испанском языке, иначе он ничем не сможет помочь. А если вы знаете испанский, то скорее всего сможете это сделать еще и разными способами. В **SystemVerilog** точно также одно и то же устройство можно описать разным кодом, но результат синтеза будет одним и тем же. Однако, часто два разных кода одинаковые по смыслу могут синтезироваться в разную аппаратуру, хотя функционально они будут идентичны, но могут отличаться, например, скоростью работы. Или одни и те же специальные языковые конструкции могут применяться для синтезирования разных цифровых элементов.
Основной дешифратор – это комбинационная схема. Это значит, что каждый раз подавая на вход одни и те же значения, вы будете получать на выходе один и тот же результат, потому что комбинационные схемы не содержат элементов памяти.
Декодер  комбинационная схема. Это значит, что каждый раз подавая на вход одни и те же значения, вы будете получать на выходе один и тот же результат, потому что комбинационные схемы не содержат элементов памяти.
Можно по-разному описывать комбинационные схемы. Например — через конструкцию `assign`. Для основного дешифратора отлично подойдет конструкция `case`, которая превратится не в мультиплексор, а в комбинационную схему с оптимальными параметрами критического пути. В доверилоговую эпоху разработчикам пришлось бы строить гигантские таблицы истинности и какие-нибудь [карты Карно](https://ru.wikipedia.org/wiki/Карта_Карно), искать оптимальные схемы реализации. Сегодня эту задачу решает синтезатор, по описанию устройства сам находит наиболее эффективное решение.
Можно по-разному описывать комбинационные схемы. Например — через конструкцию `assign`. Для описания декодера отлично подойдет конструкция `case`, которая превратится не в мультиплексор, а в комбинационную схему с оптимальными параметрами критического пути. В доверилоговую эпоху разработчикам пришлось бы строить гигантские таблицы истинности и какие-нибудь [карты Карно](https://ru.wikipedia.org/wiki/Карта_Карно), искать оптимальные схемы реализации. Сегодня эту задачу решает синтезатор, по описанию устройства сам находит наиболее эффективное решение.
Разница с реализацией мультиплексора в том, что в этом случае справа от знака равно всегда стоит константа. Получается это такой способ описать таблицу истинности. В такой код легко вносить правки и искать интересующие фрагменты.
@@ -246,9 +375,13 @@ module tequila (
endmodule
```
Имейте в виду, что значения по-умолчанию, описанные в начале блока `always_comb` можно использовать таким образом при помощи **блокирующих присваиваний** (которые следует использовать только в комбинационных блоках).
Кроме того, использование вложенных блоков `case` обосновано только в ситуации создания блока декодера (т.е. в случаях, когда справа от всех присваиваний будут использованы константы, а не другие сигналы). В случае описания мультиплексора, вложенные блоки `case` могут быть синтезированы в каскад мультиплексоров, что негативно скажется на временных характеристиках схемы.
## Задание
Необходимо реализовать на языке **SystemVerilog** модуль основного дешифратора команд однотактного процессора RISC-V в соответствии с предложенной микроархитектурой. Далее приводится прототип разрабатываемого модуля.
Необходимо реализовать на языке **SystemVerilog** модуль декодера инструкций однотактного процессора RISC-V в соответствии с предложенной микроархитектурой. Далее приводится прототип разрабатываемого модуля.
```SystemVerilog
module decoder_riscv (
@@ -274,22 +407,22 @@ module decoder_riscv (
endmodule
```
В зависимости от стиля оформления, модуль может занимать больше сотни строк кода, но это не делает его реализацию сложной. По сути, дешифратор — это просто большой `case` с описанием того, в каком случае, какие сигналы и чему должны быть равны. Работа требует внимательности, немного усидчивости и понимания выполняемых действий. С огромной вероятностью в коде будут ошибки и их нужно будет исправлять. Ошибки — это нормально (не ошибается тот, кто ничего не делает), а исправление ошибок дает бесценный опыт разработки. Возможно, реализация этого модуля в какой-то момент покажется рутинной, но поверь, по окончании следующей лабораторной работы удовольствие от результата покажет, что оно того стоило.
В зависимости от стиля оформления, модуль может занимать больше сотни строк кода, но это не делает его реализацию сложной. По сути, дешифратор — это просто большой `case` с описанием того, в каком случае, какие сигналы и чему должны быть равны. Работа требует внимательности, немного усидчивости и понимания выполняемых действий. С огромной вероятностью в коде будут ошибки и их нужно будет исправлять. Ошибки — это нормально (не ошибается тот, кто ничего не делает), а исправление ошибок дает бесценный опыт разработки. Возможно, реализация этого модуля в какой-то момент покажется рутинной, но по окончании следующей лабораторной работы удовольствие от результата покажет, что оно того стоило.
## Порядок выполнения задания
1. Внимательно ознакомьтесь с выходными сигналами декодера и тем, за что они отвечают, а также типами команд. В случае возникновения вопросов, проконсультируйтесь с преподавателем.
1. Внимательно ознакомьтесь с выходными сигналами декодера инструкций и тем, за что они отвечают, а также типами команд. В случае возникновения вопросов, проконсультируйтесь с преподавателем.
2. Реализуйте модуль `decoder_riscv`. Для этого:
1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemVerilog`-файл `decoder_riscv.sv`.
2. Опишите в нем модуль основного дешифратора с таким же именем и портами, как указано в [задании](#задание).
2. Опишите в нем модуль декодера инструкций с таким же именем и портами, как указано в [задании](#задание).
1. Для удобства дальнейшего описания модуля, рекомендуется сперва создать сигналы `opcode`, `func3`, `func7` и присвоить им соответствующие биты входного сигнала инструкции.
2. При описании модуля вы можете воспользоваться параметрами, объявленными **пакетах** `riscv_pkg`, `csr_pkg` и `alu_opcodes_pkg`, описанных в файлах [riscv_pkg.sv](riscv_pkg.sv), [csr_pkg.sv](csr_pkg.sv) и [alu_opcodes_pkg.sv](alu_opcodes_pkg.sv) соответственно.
3. Модуль может быть описан множеством способов: каждый выходной сигнал может быть описан через собственную комбинационную логику в отдельном блоке `case`, однако проще всего будет описать все сигналы через вложенные `case` внутри одного блока `always_comb`.
4. Внутри блока `always_comb` до начала блока `case` можно указать базовые значения для всех выходных сигналов. Это не то же самое, что вариант `default` в блоке `case`. Здесь вы можете описать состояния, которые будут использованы чаще всего, и в этом случае, присваивание сигналу будет выполняться только в том месте, где появится инструкция, требующая значение этого сигнала, отличное от базового.
5. Далее вы можете описать базовый блок `case`, где будет определен тип операции по ее коду.
6. Определив тип операции, вы сможете определить какая конкретно операция по полям `func3` и `func7` (если данный тип имеет такие поля).
7. Не забывайте, что в случае, если на каком-то из этапов (определения типа, или определения конкретной операции) вам приходит какое-то неправильное поле, необходимо выставить сигнал `illegal_instr_o`.
8. В случае некорректной инструкции, вы должны гарантировать, что не произойдет условный/безусловный переход, а во внешнюю память и регистровый файл ничего не запишется. Не важно, что будет выполняться на АЛУ, не важно какие данные будут выбраны на мультиплексоре источника записи. Важно чтобы не произошел сам факт записи в любое из устройств (подумайте какие значения для каких сигналов необходимо для этого выставить).
7. Не забывайте, что в случае, если на каком-то из этапов (определения типа, или определения конкретной операции) вам приходит непредусмотренное ISA значение какого-либо поля, необходимо выставить сигнал `illegal_instr_o`.
8. В случае некорректной инструкции, вы должны гарантировать, что не произойдет условный/безусловный переход, а во внешнюю память, регистровый файл, а также регистры контроля и статуса ничего не запишется. Не важно, что будет выполняться на АЛУ, не важно какие данные будут выбраны на мультиплексоре источника записи. Важно чтобы не произошел сам факт записи в любое из устройств (подумайте какие значения для каких сигналов необходимо для этого выставить).
3. После описания модуля, его необходимо проверить с помощью тестового окружения.
1. Тестовое окружение находится [`здесь`](tb_decoder_riscv.sv).
2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md).