mirror of
https://github.com/MPSU/APS.git
synced 2025-09-15 17:20:10 +00:00
* ЛР5. Исправление примера формирования управляющих сигналов для инструкции sw * Update ERRATA.md --------- Co-authored-by: Vlad Nikiforov <bfly@mail.ru> Co-authored-by: Andrei Solodovnikov <VoultBoy@yandex.ru>
443 lines
62 KiB
Markdown
443 lines
62 KiB
Markdown
# Лабораторная работа №5 "Декодер инструкций"
|
||
|
||
Устройство управления (УУ) – один из базовых блоков процессора, функцией которого является декодирование инструкций и выдача управляющих сигналов для всех блоков процессора. Роль УУ в данном курсе (с некоторыми оговорками) будет играть декодер инструкций.
|
||
|
||
## Цель
|
||
|
||
Описать на языке **SystemVerilog** блок декодера инструкций для однотактного процессора с архитектурой **RISC-V**.
|
||
|
||
## Материалы для подготовки к лабораторной работе
|
||
|
||
- [Форматы кодирования инструкций базового набора команд `RV32I`](../../Other/rv32i.md).
|
||
- [Теорию по регистрам контроля и статуса](../../Other/CSR.md).
|
||
- [Различия между блокирующими и неблокирующими присваиваниями](../../Basic%20Verilog%20structures/Assignments.md).
|
||
|
||
## Ход работы
|
||
|
||
1. Изучить микроархитектуру реализуемого процессорного ядра.
|
||
1. Разобраться с логикой формирования управляющих сигналов для всех типов инструкций.
|
||
2. Изучить [описание сигналов декодера инструкций](#описание-сигналов-декодера-инструкций).
|
||
3. Изучить [набор поддерживаемых инструкций **RISC-V** и способы их кодирования](#набор-поддерживаемых-инструкций-risc-v-и-способы-их-кодирования)
|
||
4. Изучить конструкции **SystemVerilog**, с помощью которых будет описан декодер ([#инструменты](#инструменты))
|
||
5. Реализовать на языке **SystemVerilog** декодер инструкций ([#задание](#задание))
|
||
6. Проверить с помощью верификационного окружения корректность его работы.
|
||
|
||
## Предлагаемая микроархитектура процессора RISC-V
|
||
|
||
На _рис. 1_ приводится микроархитектура реализуемого ядра процессора RISC-V.
|
||
|
||
**Приведенная архитектура не является заданием для текущей лабораторной работы, лишь отражает то, как в дальнейшем будет подключаться и использоваться реализуемый в данной лабораторной работе декодер.**
|
||
|
||

|
||
|
||
_Рисунок 1. Микроархитектура будущего процессорного ядра._
|
||
|
||
Предложенная микроархитектура похожа на микроархитектуру процессора **CYBERcobra 3000 Pro 2.0** из ЛР№4, но с некоторыми изменениями.
|
||
|
||
В первую очередь изменились входы и выходы процессора:
|
||
|
||
- память инструкций вынесена наружу, таким образом, у процессора появляются входы и выходы: `instr_addr_o` и `instr_i`;
|
||
- также у процессора появились сигналы интерфейса памяти данных:
|
||
- `mem_addr_o` — адрес внешней памяти;
|
||
- `mem_req_o` — запрос на обращение во внешнюю память;
|
||
- `mem_size_o` — размер данных при обращении в память;
|
||
- `mem_we_o` — сигнал разрешения записи во внешнюю память;
|
||
- `mem_wd_o` — данные для записи во внешнюю память;
|
||
- `mem_rd_i` — считанные из внешней памяти данные.
|
||
Эти сигналы используются при выполнении инструкций загрузки (сохранения) информации из (в) памяти данных.
|
||
- еще у процессора появился вход `stall_i`, приостанавливающий обновление программного счётчика.
|
||
|
||
Кроме того, появилось два новых модуля: **Interrupt Controller** и **Control Status Registers**. Эти модули будут обеспечивать поддержку прерываний в процессорной системе.
|
||
|
||
Так же добавились источники операндов АЛУ: программный счетчик, множество констант из инструкций и микроархитектурных констант — а значит необходимо мультиплексировать эти сигналы.
|
||
|
||
Изменились и источники записи в регистровый файл, теперь это:
|
||
|
||
- результат операции на АЛУ;
|
||
- данные, считанные с внешней памяти;
|
||
- данные из модуля регистров контроля и статуса.
|
||
|
||
Для того, чтобы управлять усложнившимся набором мультиплексоров, интерфейсом памяти данных и появившимися модулями нужно специальное устройство — Устройство управления (УУ). В данной микроархитектуре логика устройства управления не вынесена в отдельный модуль, лишь выделена на схеме синим цветом. По большей части, в предложенной микроархитектуре роль устройства управления выполняет декодер инструкций.
|
||
|
||
## Описание сигналов декодера инструкций
|
||
|
||
Список портов декодера инструкций и их назначение представлен в _таблице 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`— этот сигнал указывает размер порции данных для передачи (возможные значения этого сигнала указаны в _Таблице 2_). Для удобства использования, данные значения определены в виде параметров в пакете `decoder_pkg`.
|
||
|
||
|Параметр|Значение `mem_size_o`| Пояснение |
|
||
|--------|---------------------|------------------------------|
|
||
|LDST_B | 3'd0 |Знаковое 8-битное значение |
|
||
|LDST_H | 3'd1 |Знаковое 16-битное значение |
|
||
|LDST_W | 3'd2 |32-битное значение |
|
||
|LDST_BU | 3'd4 |Беззнаковое 8-битное значение |
|
||
|LDST_HU | 3'd5 |Беззнаковое 16-битное значение|
|
||
|
||
_Таблица 2. Значения сигнала `mem_size_o` при передаче различных порций данных._
|
||
|
||
Перечисленных сигналов достаточно для того, чтобы основная память понимала: обращаются ли к ней в данный момент, нужно ли записывать или считывать данные, и о какой порции данных идет речь.
|
||
|
||
### Сигналы разрешения записи
|
||
|
||
В данную категорию входят два однобитных сигнала:
|
||
|
||
- `gpr_we_o` — сигнал разрешения записи в регистровый файл (General Purpose Registers, GPR);
|
||
- `csr_we_o` — сигнал разрешения записи в модуле регистров контроля и статуса.
|
||
|
||
### Сигналы управления программным счетчиком
|
||
|
||
В данную категорию входят однобитные сигналы, которые оповещают о том, что выполняется инструкция, связанная с изменением значения программного счетчика:
|
||
|
||
- `branch_o` — сигнал об инструкции условного перехода;
|
||
- `jal_o` — сигнал об инструкции безусловного перехода `jal`;
|
||
- `jalr_o` — сигнал об инструкции безусловного перехода `jalr`;
|
||
- `mret_o` — сигнал об инструкции возврата из прерывания/исключения `mret`.
|
||
|
||
### Сигнал нелегальной инструкции
|
||
|
||
Сигнал, который должен принять значение `1`, в случае если пришла инструкция, которая не входит в список поддерживаемых процессором.
|
||
|
||
Это не единственное, что должен сделать декодер в подобной ситуации. Давайте разберем подробней, что должно происходить по приходу нелегальной инструкции.
|
||
|
||
## Обработка нелегальной инструкции
|
||
|
||
Существует множество причин, почему процессору может прийти на исполнение неподдерживаемая инструкция, в том числе:
|
||
|
||
- ошибка компиляции: либо баг в самом компиляторе, либо компиляция с неверными параметрами;
|
||
- ошибка в аппаратуре (например сбой в работе памяти);
|
||
- намеренная вставка неподдерживаемой инструкции (например для эксплуатации какой-нибудь уязвимости);
|
||
- инструкция, которая на самом деле поддерживается процессором, но требует большего уровня привилегий и потому не может быть выполнена.
|
||
|
||
В случае появления инструкции, которая не поддерживается процессором, устройство управления должно обеспечить стабильность системы. В самом простом случае, такую инструкцию необходимо пропустить, сохранив так называемое **архитектурное состояние** процессора — т.е. сохранив значение всех элементов, характеризующих состояние системы в текущий момент. К таким элементам относятся: содержимое регистрового файла, основой памяти, содержимое регистров контроля и статуса и т.п. Значение программного счетчика также входит в архитектурное состояние процессора, однако в контексте пропуска инструкции с сохранением архитектурного состояния, его значение нужно изменить, иначе система оказалась бы в бесконечном цикле (неизменный счетчик бы указывал на ту же самую инструкцию, которая не должна менять архитектурного состояния).
|
||
|
||
Иными словами, в случае появления нелегальной инструкции, устройство управления (роль которого в нашей системе по большей части играет декодер) должно проследить за тем, чтобы в системе не изменилось ничего кроме программного счетчика. К сигналам, влияющим на изменение архитектурного состояния, относятся:
|
||
|
||
- `mem_req_o`,
|
||
- `mem_we_o`,
|
||
- `gpr_we_o`,
|
||
- `csr_we_o`,
|
||
- `branch_o`,
|
||
- `jal_o`,
|
||
- `jalr_o`,
|
||
- `mret_o`,
|
||
|
||
то есть, должны быть запрещены все запросы на запись, обращения в память и любые "прыжки" программного счетчика.
|
||
|
||
Давайте теперь разберемся с тем, какие именно инструкции должен будет поддерживать наш процессор.
|
||
|
||
### Набор поддерживаемых инструкций **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, задающийся прямиком в инструкции) операнд (константа), расположение и порядок битов которого указывается в квадратных скобках. Обратите внимание, что в разных форматах кодирования константы имеют различную разрядность, а их биты расположены по-разному. Непосредственные операнды всех типов интерпретируются как знаковые и требуют знакового расширения [[1](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf), стр. 23]. Исключение составляют 5-битные константы CSR-инструкций.
|
||
|
||

|
||
|
||
_Таблица 3. Базовый набор инструкций из спецификации RISC-V[[1, стр. 554]](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf), Стандартное расширение Zicsr[[1, стр.556]](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf), а также привилегированная инструкция mret[[2, стр.51]](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/priv-isa-asciidoc.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`. Если это не так, то вся инструкция уже запрещенная и не нуждается в дальнейшем декодировании.
|
||
|
||
Для удобства, значения кодов операций определены в виде параметров в пакете `decoder_pkg`.
|
||
|
||
|Параметр|Opcode| Описание группы операций | Краткая запись |
|
||
|--------|------|-------------------------------------------------------------------------------------------------------|------------------------------------|
|
||
|OP |01100 |Записать в `rd` результат вычисления АЛУ над `rs1` и `rs2` |`rd = alu_op(rs1, rs2)` |
|
||
|OP_IMM |00100 |Записать в `rd` результат вычисления АЛУ над `rs1` и `imm` |`rd = alu_op(rs1, imm)` |
|
||
|LUI |01101 |Записать в `rd` значение непосредственного операнда U-типа `imm_u` |`rd = imm << 12` |
|
||
|LOAD |00000 |Записать в `rd` данные из памяти по адресу `rs1+imm` |`rd = Mem[rs1 + imm]` |
|
||
|STORE |01000 |Записать в память по адресу `rs1+imm` данные из `rs2` |`Mem[rs1 + imm] = rs2` |
|
||
|BRANCH |11000 |Увеличить счетчик команд на значение `imm`, если верен результат сравнения `rs1` и `rs2` |`if cmp_op(rs1, rs2) then PC += imm`|
|
||
|JAL |11011 |Записать в `rd` следующий адрес счетчика команд, увеличить счетчик команд на значение `imm` |`rd = PC + 4; PC += imm` |
|
||
|JALR |11001 |Записать в `rd` следующий адрес счетчика команд, в счетчик команд записать `rs1+imm` |`rd = PC + 4; PC = rs1+imm` |
|
||
|AUIPC |00101 |Записать в `rd` результат сложения непосредственного операнда U-типа `imm_u` и счетчика команд |`rd = PC + (imm << 12)` |
|
||
|MISC-MEM|00011 |Не производить операцию | `-` |
|
||
|SYSTEM |11100 |Записать в `rd` значение `csr`. Обновить значение `csr` с помощью `rs1`. (либо инструкция `mret`/`ecall`/`ebreak`)|`csr = csr_op(rs1); rd = csr` |
|
||
|
||
_Таблица 5. Описание кодов операций._
|
||
|
||
#### SYSTEM-инструкции
|
||
|
||
SYSTEM-инструкции используются для доступа к системным функциям и могут требовать привилегированный доступ. Данные инструкции могут быть разделены на два класса:
|
||
|
||
- Обращение к регистрам статуса и контроля (**Control and Status Registers**, **CSR**)
|
||
- Все остальные инструкции (возможно из набора привилегированных инструкций)
|
||
|
||
Для того, чтобы в будущем процессор поддерживал прерывания, нам требуется декодировать инструкции обоих классов.
|
||
|
||
Обращение к регистрам контроля и статуса осуществляется шестью инструкциями стандартного расширения `Zicsr`. Каждая из этих инструкций (если у нее легальные поля) осуществляет запись в CSR и регистровый файл (блоки `Control Status Registers` и `Register File` на _рис. 1_ соответственно).
|
||
|
||
Кроме того, для возврата управления основному потоку инструкций, нужна дополнительная `SYSTEM`-инструкция привилегированного набора команд `MRET`.
|
||
|
||
Перечисленные выше инструкции являются "дополнительными" — их добавили сверх стандартного набора инструкций, чтобы обеспечить требуемый нашей системе функционал. Однако осталось ещё две SYSTEM-инструкции, которые мы должны уметь декодировать, поскольку они есть в стандартном наборе инструкций.
|
||
|
||
Инструкции `ECALL` и `EBREAK` вызывают исключение. Подробнее исключения и прерывания будут разобраны в ЛР№10, пока что надо знать лишь то, что в нашей процессорной системе все исключения будут реализованы через выставление 1 на сигнале `illegal_instr_o`.
|
||
|
||
#### MISC-MEM инструкции
|
||
|
||
В базовом наборе инструкций **RISC-V** к `MISC-MEM`-операциям относятся инструкции `FENCE`, `FENCE.TSO`, `PAUSE` (которые объединены в таблице 5 в одну инструкцию `FENCE`). В реализуемом процессорном ядре эта инструкция не должна приводить к изменениям. Инструкция `FENCE` в **RISC-V** необходима при работе с несколькими аппаратными потоками, или "хартами" (hart – «**har**dware **t**hread»). Она помогает согласовать доступ к данным между ними. В **RISC-V** используется "расслабленная модель" памяти (**relaxed memory model**): которая позволяет потокам видеть операции других потоков, но не обязательно в том порядке, в каком они были записаны в коде программы. Инструкция `FENCE`, использованная между двумя инструкциями чтения и/или записи гарантирует, что остальные потоки увидят первую инструкцию перед второй. Реализация `FENCE` является опциональной в **RISC-V** и в данном случае в ней нет необходимости, так как в системе не предполагается наличия нескольких аппаратных потоков. Данная инструкция должна быть реализована как `NOP` (**n**o **op**eration).
|
||
|
||
В _таблице 6_ представлены инструкции из таблицы 3 с приведением их типов, значениями полей `opcode`, `func3`, `func7`, функциональным описанием и примерами использования.
|
||
|
||

|
||
|
||
_Таблица 6. Расширенное описание инструкций RV32IZicsr._
|
||
|
||
> [!IMPORTANT]
|
||
> Обратите внимание на операции `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`. Именно поэтому в ЛР№2 мы использовали только 5 младших бит операнда `B` — чтобы отбросить ту часть константы, которая используется в качестве `func7` на операциях битового сдвига.
|
||
|
||
> [!IMPORTANT]
|
||
> Также обратите внимание на инструкции `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'd3`,
|
||
- `alu_op_o= ALU_ADD`.
|
||
|
||
(см. _рисунок 1_).
|
||
|
||
Кроме того, для самой операции записи в основную память, декодер должен сформировать управляющие сигналы интерфейса памяти (запрос на обращение в память, размер передаваемых данных и сигнал разрешения записи):
|
||
|
||
- `mem_req_o = 1'b1`,
|
||
- `mem_size_o = LDST_W` (см. _таблицу 2_),
|
||
- `mem_we_o = 1'b1`.
|
||
|
||
Несмотря на то, что для записи во внешнюю память ключевыми сигналами будут описанные выше, это не означает, что остальные выходные сигналы дешифратора команд могут быть абы какими.
|
||
|
||
Поскольку операция `sw` не является операцией перехода, сигналы `jal_o`, `jalr_o` и `branch_o` и `mret_o` должны быть равны нулю (иначе процессор совершит переход, а инструкция `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 |
|
||
|
||
_Таблица 7. Описание портов дешифратора команд._
|
||
|
||
Дешифратор должен выдать единицу на выходе `illegal_instr_o` в случае:
|
||
|
||
- неравенства двух младших битов opcode значению `11`;
|
||
- если значение поля `opcode` не совпадает ни с одним из известных и следовательно операция не определена.
|
||
- некорректного значения полей `func3` или `func7` для данного опкода.
|
||
|
||
Кроме того, поскольку представленная на _рис. 1_ микроархитектура поддерживает только одно исключение (исключение через сигнал `illegal_instr_o`), этот сигнал должен быть равен единице и в случае:
|
||
|
||
- если это инструкция `ECALL` / `EBREAK`.
|
||
|
||
## Инструменты
|
||
|
||
**SystemVerilog** – это язык описания аппаратуры. С помощью этого языка человек объясняет либо синтезатору какое он хочет получить устройство, либо симулятору – как он хочет это устройство проверить. Синтезатор – это программа, которая создает из логических элементов цифровое устройство по описанию, предоставляемому человеком. Синтезатору внутри **Vivado** нужно объяснить, что от него нужно. Например, чтобы спросить дорогу у испанца, придется делать это на испанском языке, иначе он ничем не сможет помочь. А если вы хорошо знаете испанский, то скорее всего сможете это сделать еще и разными способами. В **SystemVerilog** точно также – одно и то же устройство можно описать разным кодом, но результат синтеза будет одним и тем же. Однако, часто два разных кода, одинаковые по смыслу, могут синтезироваться в разную аппаратуру, хотя функционально они будут идентичны, но могут отличаться, например, скоростью работы. Или одни и те же специальные языковые конструкции могут применяться для синтезирования разных цифровых элементов.
|
||
|
||
Декодер – комбинационная схема. Это значит, что каждый раз подавая на вход одни и те же значения, вы будете получать на выходе один и тот же результат.
|
||
|
||
Можно по-разному описывать комбинационные схемы. Например — через оператор непрерывного присваивания `assign`. Для описания декодера отлично подойдет конструкция `case`, которая превратится не в мультиплексор, а в комбинационную схему с оптимальными параметрами критического пути. В доверилоговую эпоху разработчикам пришлось бы строить гигантские таблицы истинности и [карты Карно](https://ru.wikipedia.org/wiki/Карта_Карно), искать оптимальные схемы реализации. Сегодня эту задачу решает синтезатор, по описанию устройства он сам находит наиболее эффективное решение.
|
||
|
||
Разница с реализацией мультиплексора в том, что в этом случае справа от знака равно всегда стоит константа. Получается это такой способ описать таблицу истинности. В такой код легко вносить правки и искать интересующие фрагменты.
|
||
|
||
Рассмотрим _листинг 1_. Внутри конструкции `always_comb`, перед конструкцией `case` указываются значения по умолчанию. Благодаря этому пропадает необходимость указывать все сигналы внутри каждого обработчика `case`, достаточно указать только те, что имеют значение отличное от значения по умолчанию. Представленный пример реализует комбинационную схему, которая при `control_signal== 4'b1100` будет выставлять сигнал `c = 1'b0`, то есть отличное, от значения по умолчанию. Сигнал `a` никак не меняется, поэтому он не указан в соответствующем обработчике. Если `sub_signal == 1'b0`, то `b` будет равен 1, а `d` равен 0. Если `sub_signal == 1'b1`, то наоборот – `b` будет равен 0, а `d` равен 1.
|
||
|
||
```Verilog
|
||
module example (
|
||
input logic [3:0] control_signal,
|
||
input logic sub_signal,
|
||
output logic a, b, c, d
|
||
);
|
||
parameter logic [3:0] SOME_PARAM = 4'b1100;
|
||
always_comb begin
|
||
a = 1'b0; // значения по умолчанию
|
||
b = 1'b0; // обратите внимание, что в блоке
|
||
c = 1'b1; // always_comb используется оператор
|
||
d = 1'b0; // блокирующего присваивания
|
||
case(control_signal)
|
||
// ... какие-то еще комбинации
|
||
SOME_PARAM: begin // если на control_signal значение SOME_PARAM
|
||
c = 1'b0;
|
||
case (sub_signal)
|
||
1'b0: b = 1'b1; // если на sub_signal значение 1'b0
|
||
1'b1: d = 1'b1; // если на sub_signal значение 1'b1
|
||
endcase
|
||
end
|
||
// ... какие-то еще обработчики
|
||
default: begin // так как описаны не все значения
|
||
a = 1'b0; // control_signal, то чтобы результатом
|
||
b = 1'b0; // case не было защелки (latch),
|
||
c = 1'b1; // на выходе нужно обязательно добавлять
|
||
d = 1'b0; // default
|
||
end
|
||
endcase
|
||
end
|
||
|
||
endmodule
|
||
```
|
||
|
||
_Листинг 1. Пример описания декодера._
|
||
|
||
Имейте в виду, что значения по умолчанию, описанные в начале блока `always_comb`, можно использовать таким образом только при помощи **блокирующих присваиваний** (которые [следует](../../Basic%20Verilog%20structures/Assignments.md) использовать только в комбинационных блоках).
|
||
|
||
Кроме того, использование вложенных блоков `case` обосновано только в ситуации создания блока декодера (т.е. в случаях, когда справа от всех присваиваний будут использованы константы, а не другие сигналы). В случае описания мультиплексора, вложенные блоки `case` могут быть синтезированы в каскад мультиплексоров, что негативно скажется на временных характеристиках схемы.
|
||
|
||
## Задание
|
||
|
||
Необходимо реализовать на языке **SystemVerilog** модуль декодера инструкций однотактного процессора RISC-V в соответствии с предложенной микроархитектурой. В _листинге 2_ приводится прототип разрабатываемого модуля.
|
||
|
||
```Verilog
|
||
module decoder (
|
||
input logic [31:0] fetched_instr_i,
|
||
output logic [1:0] a_sel_o,
|
||
output logic [2:0] b_sel_o,
|
||
output logic [4:0] alu_op_o,
|
||
output logic [2:0] csr_op_o,
|
||
output logic csr_we_o,
|
||
output logic mem_req_o,
|
||
output logic mem_we_o,
|
||
output logic [2:0] mem_size_o,
|
||
output logic gpr_we_o,
|
||
output logic [1:0] wb_sel_o,
|
||
output logic illegal_instr_o,
|
||
output logic branch_o,
|
||
output logic jal_o,
|
||
output logic jalr_o,
|
||
output logic mret_o
|
||
);
|
||
import decoder_pkg::*;
|
||
|
||
endmodule
|
||
```
|
||
|
||
_Листинг 2. Прототип декодера инструкций._
|
||
|
||
В зависимости от стиля оформления, модуль может занимать больше сотни строк кода, но это не делает его реализацию сложной. По сути, декодер — это просто большой блок `case` с описанием того, в каком случае, какие сигналы и чему должны быть равны. Работа требует внимательности, немного усидчивости и понимания выполняемых действий. С огромной вероятностью в коде будут ошибки и их нужно будет исправлять. Ошибки — это нормально (не ошибается тот, кто ничего не делает), а исправление ошибок дает бесценный опыт разработки. Возможно, реализация этого модуля в какой-то момент покажется рутинной, но по окончании лабораторной работы №7 удовольствие от результата покажет, что оно того стоило.
|
||
|
||
## Порядок выполнения задания
|
||
|
||
1. Внимательно ознакомьтесь с выходными сигналами декодера инструкций и тем, как они управляют функциональными блоками процессорного ядра, представленного на _рис. 1_, а также типами команд. В случае возникновения вопросов, проконсультируйтесь с преподавателем.
|
||
2. Добавьте в `Design Sources` проекта файл [`alu_opcodes_pkg.sv`](alu_opcodes_pkg.sv) (если тот ещё не был добавлен в ходе выполнения ЛР№2), а также файлы [`csr_pkg.sv`](csr_pkg.sv) и [`decoder_pkg.sv`](decoder_pkg.sv). Эти файлы содержат параметры, которые будет удобно использовать при описании декодера.
|
||
3. Опишите модуль декодера инструкций с таким же именем и портами, как указано в задании.
|
||
1. Для удобства дальнейшего описания модуля рекомендуется сперва создать сигналы `opcode`, `func3`, `func7` и присвоить им соответствующие биты входного сигнала инструкции.
|
||
2. Модуль может быть описан множеством способов: каждый выходной сигнал может быть описан через собственную комбинационную логику в отдельном блоке `case`, однако проще всего будет описать все сигналы через вложенные `case` внутри одного блока `always_comb`.
|
||
3. Внутри блока `always_comb` до начала блока `case` можно указать базовые значения для всех выходных сигналов. Это не то же самое, что вариант `default` в блоке `case`. Здесь вы можете описать состояния, которые будут использованы чаще всего, и в этом случае, присваивание сигналу будет выполняться только в том месте, где появится инструкция, требующая значение этого сигнала, отличное от базового.
|
||
4. Далее вы можете описать базовый блок `case`, где будет определен тип операции по ее коду.
|
||
5. Определив тип операции, вы сможете определить какая конкретно операция по полям `func3` и `func7` (если данный тип имеет такие поля).
|
||
6. Не забывайте, что в случае, если на каком-то из этапов (определения типа, или определения конкретной операции) вам приходит непредусмотренное ISA значение какого-либо поля, необходимо выставить сигнал `illegal_instr_o`.
|
||
7. В случае некорректной инструкции, вы должны гарантировать, что не произойдет условный/безусловный переход, а во внешнюю память, регистровый файл, а также регистры контроля и статуса ничего не запишется. Не важно, что будет выполняться на АЛУ, не важно какие данные будут выбраны на мультиплексоре источника записи. Важно чтобы не произошел сам факт записи в любое из устройств (подумайте какие значения для каких сигналов необходимо для этого выставить).
|
||
4. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_05.tb_decoder.sv`](lab_05.tb_decoder.sv). Вполне возможно, что после первого запуска вы столкнётесь с сообщениями о множестве ошибок. Вам необходимо [исследовать](../../Vivado%20Basics/05.%20Bug%20hunting.md) эти ошибки на временной диаграмме и исправить их в вашем модуле.
|
||
1. Перед запуском моделирования убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`.
|
||
5. Данная лабораторная работа не предполагает проверки в ПЛИС
|
||
|
||
## Список источников
|
||
|
||
1. [The RISC-V Instruction Set Manual Volume I: Unprivileged ISA](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf)
|
||
2. [The RISC-V Instruction Set Manual Volume II: Privileged Architecture](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/priv-isa-asciidoc.pdf)
|