# Лабораторная работа №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. **Приведенная архитектура не является заданием для текущей лабораторной работы, лишь отражает то, как в дальнейшем будет подключаться и использоваться реализуемый в данной лабораторной работе декодер.** ![../../.pic/Labs/lab_10_irq/fig_03.drawio.svg](../../.pic/Labs/lab_10_irq/fig_03.drawio.svg) _Рисунок 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-инструкций. ![../../.pic/Labs/lab_05_decoder/rv32i_BIS.png](../../.pic/Labs/lab_05_decoder/rv32i_BIS.png) _Таблица 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`, функциональным описанием и примерами использования. ![../../.pic/Labs/lab_05_decoder/rv32i_summary.png](../../.pic/Labs/lab_05_decoder/rv32i_summary.png) _Таблица 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)