# Лабораторная работа 10 "Подсистема прерывания"

Данная лабораторная работа посвящена изучению систем прерывания в компьютерах и их использованию для обработки программных и аппаратных событий. В процессе работы вы познакомитесь с основными понятиями и принципами работы систем прерывания, а также со средствами программной обработки прерываний.

## Цель

1. Разработать модуль контроллера прерываний.
2. Разработать модуль контроллера регистров статуса и контроля (**CSR**-контроллер).

## Ход выполнения

1. Изучение теории по прерываниям и исключениям в архитектуре RISC-V, включая работу с регистрами статуса и контроля (**CSR**) и механизмы реализации прерываний.
2. Реализация схемы обработки прерывания для устройства на основе RISC-V
3. Реализация схемы управления регистрами статуса и контроля.

## Теоретическая часть

### Прерывания/Исключения

С компьютером постоянно происходят события, на которые он должен реагировать, запуская соответствующие подпрограммы. Например, при движении мышки нужно перерисовать ее курсор на новом месте или нужно среагировать на подключение флешки и т.п. Возможность запускать нужные подпрограммы в ответ на различные события, возникающие внутри или снаружи компьютера, существенно расширяют его возможности. События, требующие внимания процессора называются **прерываниями** (**interrupt**). Происходящие события формируют запрос на прерывание процессору.

С.А. Орлов, Б.Я. Цилькер в учебнике "Организация ЭВМ и систем" дают следующее определение системе прерывания:

> **Система прерывания** – это совокупность программно-аппаратных средств, позволяющая процессору (при получении соответствующего запроса) на время прервать выполнение текущей программы, передать управление программе обслуживания поступившего запроса, по завершению которой и продолжить прерванную программу с того места, где она была остановлена[1, стр. 155].

Прерывания делятся на **маски́руемые** — которые при желании можно игнорировать (на которые можно наложить [**битовую маску**](https://ru.wikipedia.org/wiki/Битовая_маска), отсюда ударение на второй слог), и **немаски́руемые** — которые игнорировать нельзя (например сбой генератора тактового синхроимпульса в микроконтроллерах семейства PIC24FJ512GU410[[2, стр. 130]](https://ww1.microchip.com/downloads/aemDocuments/documents/MCU16/ProductDocuments/DataSheets/PIC24FJ512GU410-Family-Data-Sheet-DS30010203D.pdf)). Прерывание похоже на незапланированный вызов функции, вследствие события в аппаратном обеспечении. Программа (функция), запускаемая в ответ на прерывание, называется **обработчиком прерывания**.

События могут быть не только аппаратными, но и программными – синхронными. Такие события называются **исключениями** (**exception**). Программа может столкнуться с состоянием ошибки, вызванным программным обеспечением, таким как неопределенная инструкция, неподдерживаемая данным процессором, в таком случаях говорят, что возникло исключение. К исключениям также относятся сброс, деление на ноль, переполнение и попытки считывания из несуществующей памяти.

Важно понимать, что ни прерывание, ни исключение не являются обязательно чем-то плохим. И то и другое — это всего лишь события. Например, с помощью исключений может осуществляться системные вызовы и передача управления отладчику программы.

Как и любой другой вызов функции, при возникновении прерывания или исключения необходимо сохранить адрес возврата, перейти к программе обработчика, выполнить свою работу, восстановить контекст (не оставить никаких следов работы обработчика прерывания) и вернуться к программе, которую прервали.

Благодаря исключениям можно реализовать имитацию наличия каких-то аппаратных блоков программными средствами. Например, при отсутствии аппаратного умножителя, можно написать программу обработчика исключения неподдерживаемой инструкции умножения, реализующую алгоритм умножения через сложение и сдвиг. Тогда, каждый раз, когда в программе будет попадаться инструкция умножения, будет возникать исключение, приводящее к запуску обработчика, перемножающего числа и размещающего результат в нужные ячейки памяти. После выполнения обработчика управление возвращается программе, которая даже не поймет, что что-то произошло и умножитель «ненастоящий».

---

На протяжении многих лет, концепция понятия "прерывание" постоянно расширялась. Семейство процессоров 80x86 внесло ещё большую путаницу введя инструкцию `int` (программное прерывание). Многие производители используют такие термины как: **исключение** (_exception_), **ошибка** (_fault_), **отказ** (_abort_), **ловушка** (_trap_) и **прерывание** (_interrupt_), чтобы описать явление, которому посвящена данная лабораторная работа. К несчастью, не существует какого-то чёткого соглашения насчёт этих названий. Разные авторы по-разному приспосабливают эти термины для своего повествования[[3, стр. 995](https://flint.cs.yale.edu/cs422/doc/art-of-asm/pdf/CH17.PDF)]. Для того, чтобы постараться избежать путаницы, в данной лабораторной работе мы будем использовать три термина, которые введены в спецификации архитектуры RISC-V[[4, стр. 10](https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf)], однако имейте в виду, что за пределами этой методички и спецификации RISC-V в эти термины могут вкладывать другие смыслы.

Сперва озвучим выдержку из спецификации, а потом дадим этим терминам обывательские определения.

1. Под **исключением** будут подразумеваться нетипичные условия, произошедшие во время исполнения программы, связанные с инструкцией в текущем харте (_hart_, сокращение от **har**dware **t**hread — аппаратном потоке).
2. Под **прерыванием** будут подразумеваться внешние асинхронные события, которые могут стать причиной непредвиденной передачи управления внутри текущего харта.
3. Под **перехватом** (вариант глагольного использования слова _trap_, которое обычно переводят как "ловушка") будет подразумеваться передача управления **обработчику перехватов** (_trap handler_), вызванная либо прерыванием, либо исключением.

Иными словами, **прерываниями** мы будем называть исключительно аппаратные (внешние, асинхронные) события, которые могут привести к **перехвату** (передаче управления обработчику). Под **исключениями** мы будем подразумевать исключительно программные (являющиеся следствием какой-то инструкции, синхронные) события, которые могут привести к **перехвату**.

Соответственно **перехватом** будет называться обобщение этих двух терминов.

Прерывания и исключения — это события (причины). Перехват — это действие (следствие).

---

Современные процессоры, предусматривающие запуск операционной системы, обладают несколькими уровнями привилегий выполнения инструкций. Это значит, что существует специальный регистр, определяющий режим, в котором в данный момент находится вычислительная машина. Наличие определенного значения в этом регистре устанавливает определенные ограничения для выполняемой в данный момент программы. В архитектуре RISC-V выделяется 4 режима работы, в порядке убывания возможностей и увеличения ограничений:

1. **машинный** (machine mode), в котором можно всё;
2. **гипервизора** (hypervisor mode), который поддерживает виртуализацию машин, то есть эмуляцию нескольких машин (потенциально с несколькими операционными системами), работающих на одной физической машине;
3. **привилегированный** (supervisor mode), для операционных систем, с возможностью управления ресурсами;
4. **пользовательский** (user mode), для прикладных программ, использующих только те ресурсы, которые определила операционная система.

![../../.pic/Labs/lab_10_irq/fig_01.png](../../.pic/Labs/lab_10_irq/fig_01.png)

_Рисунок 1. Распределение привилегий по уровням абстракций программного обеспечения._

Переключение между этими режимами происходит с помощью исключения, называемого **системный вызов**, и который происходит при выполнении специальной инструкции. Для RISC-V такой инструкцией является **ecall**. Это похоже на вызов подпрограммы, но при системном вызове изменяется режим работы и управление передается операционной системе, которая, по коду в инструкции вызова определяет, что от нее хотят. Например, операционная система может предоставить данные с диска, так как запускаемая программа не имеет никакого представления о том, на какой машине ее запустили, или что используется какая-то конкретная файловая система.

Системы прерываний имеет ряд характеристик, которые варьируются в зависимости от их реализации. Все системы можно условно разбить на две категории: обзорные (прямые) и векторные.

В **обзорных системах прерывания** любой перехват приводит к запуску одного и того же обработчика. Внутри такого обработчика перехвата определяется причина его возникновения (как правило — это число в специальном регистре), и уже в зависимости от причины запускается нужная подпрограмма. Обзорные системы аппаратно проще векторных, но требуют больше рутины и времени на обработку. Именно такая система прерываний будет реализована в данной лабораторной работе.

В **векторных системах прерывания** разные события приводят к запуску на исполнение разных программ обработчиков. Адрес начала обработчика перехвата называется **вектором прерывания**. В векторных системах прерывания выделяется фрагмент памяти, в котором хранятся адреса переходов на начало каждого из обработчиков. Такой участок памяти называется **таблицей векторов прерываний** (**Interrupt Vector Table**, **IVT**).

В самом простом случае система прерывания позволяет обрабатывать только одно прерывание за раз (именно такую систему мы и будет делать в рамках данной лабораторной работы). Существуют реализации позволяющие во время обработки прерывания «отвлекаться» на другие события. В таких системах используется система приоритетов, чтобы прерывание с более низким приоритетом не прерывало более приоритетное.

### Регистры Статуса и Управления (Control and Status Registers)

Для поддержания работы операционной системы, виртуализации, системы прерывания и тому подобное, в архитектуре RISC-V предусмотрено использование группы регистров, под общим названием **Control and Status Registers** (**CSR**), обеспечивающих управление элементами процессора и доступ к статусной информации о системе. С помощью этих регистров реализуются привилегированные режимы работы процессора, хранение указателей на различные программные стеки, статус различных подсистем, регистры для обеспечения работы прерываний и многое другое.

Все регистры имеют уникальные 12-битные адреса, а их роли определены в спецификации на архитектуру RISC-V. В _Таблице 1_ приводится фрагмент спецификации привилегированной архитектуры[[5, стр. 10]](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf), иллюстрирующая некоторые из регистров. В левом столбце указан 12-битный адрес. Далее указывается в каком режиме, что можно делать с этим регистром. После идет название, а в правом столбике описание.

В этой таблице можно увидеть регистры для сохранения адреса возврата из прерывания, адрес вектора прерывания, регистры причины (cause), регистры настройки безопасности и защиты памяти. И это далеко не полный список регистров, предоставляемых стандартом (который помимо прочего, оставляет место в адресном пространстве для ваших собственных регистров).

![../../.pic/Labs/lab_10_irq/tab_01.png](../../.pic/Labs/lab_10_irq/tab_01.png)

_Таблица 1. Регистры контроля и состояния машинного (наивысшего) уровня привилегий._

Для работы с CS-регистрами используются специальные инструкции **SYSTEM** (1110011) I-типа, хранящие в 12-битном поле **imm** адрес регистра, к которому будет осуществлен доступ и адреса в регистровом файле откуда будет считан или куда будет записан один из CS-регистров. Вы уже добавляли поддержку этих инструкций во время выполнения [лабораторной работы №5](../05.%20Main%20decoder/) "Основной дешифратор".

| opcode | func3 | Тип |     Инструкция      |         Описание          |           Операция          |
|--------|-------|-----|---------------------|---------------------------|-----------------------------|
|1110011 | 000   | I   | mret                | Возврат из прерывания     | PC = mepc                   |
|1110011 | 001   | I   | csrrw  rd, csr, rs1 | Чтение и Запись CSR       | rd = csr, csr = rs1         |
|1110011 | 010   | I   | csrrs  rd, csr, rs1 | Чтение и Установка бит CSR| rd = csr, csr = csr \|  rs1 |
|1110011 | 011   | I   | csrrc  rd, csr, rs1 | Чтение и Очистка бит CSR  | rd = csr, csr = csr & ~rs1  |
|1110011 | 101   | I   | csrrwi rd, csr, rs1 | Чтение и Запись CSR       | rd = csr, csr = imm         |
|1110011 | 110   | I   | csrrsi rd, csr, rs1 | Чтение и Установка бит CSR| rd = csr, csr = csr \|  imm |
|1110011 | 111   | I   | csrrci rd, csr, rs1 | Чтение и Очистка бит CSR  | rd = csr, csr = csr & ~imm  |

_Таблица 2. Список инструкций для работы с регистрами контроля и статуса._

Для удобства программирования на языке ассемблера RISC-V существуют псевдоинструкции для работы с CS-регистрами.

| Псевдоинструкция | Инструкция RISC-V  |   Описание  | Операция  |
|------------------|--------------------|-------------|-----------|
| csrr rd, csr     | csrrs rd, csr, x0  | Чтение CSR  | rd = csr  |
| csrw csr, rs1    | csrrw x0, csr, rs1 | Запись CSR  | csr = rs1 |

_Таблица 3. Псевдоинструкции для работы с регистрами контроля и статуса._

Операция логического ИЛИ нулевого регистра с содержимым CS-регистра не меняет его содержимого, поэтому при использовании инструкции `csrr` происходит только операция чтения. Подобным образом реализована псевдоинструкция `csrw`.

Для реализации простейшей системы прерывания на процессоре с архитектурой RISC-V достаточно реализовать 5 CS-регистров, работающих в машинном, самом привилегированном режиме.

| Адрес  | Уровень привилегий | Название | Описание                                           |
|--------|--------------------|----------|----------------------------------------------------|
| **Machine Trap Setup**                                                                      |
|0x304   | MRW                | mie      | Регистр маски прерываний.                          |
|0x305   | MRW                | mtvec    | Базовый адрес обработчика перехвата.               |
|0x340   | MRW                | mscratch | Адрес верхушки стека обработчика перехвата.        |
|0x341   | MRW                | mepc     | Регистр, хранящий адрес перехваченной инструкции.  |
|0x342   | MRW                | mcause   | Причина перехвата                                  |

_Таблица 4. Список регистров, подлежащих реализации в рамках лабораторной работы._

По адресу `0x304` должен располагаться регистр, позволяющий маскировать прерывания. Например, если на 5-ом входе системы прерывания генерируется прерывание, то процессор отреагирует на него только в том случае, если 5-ый бит регистра `mie` будет равен 1.

Регистр `mtvec` состоит из двух полей: BASE[31:2] и MODE. Поле BASE хранит старшие 30 бит базового адреса обработчика перехвата (поскольку этот адрес должен быть всегда равен четырем, младшие два бита считаются равными нулю). Поле MODE кодирует тип системы прерывания:

- `MODE == 2'd0` — система прерывания обзорная;
- `MODE == 2'd1` — система прерывания векторная.

![../../.pic/Labs/lab_10_irq/fig_02.png](../../.pic/Labs/lab_10_irq/fig_02.png)

Рисунок 2. Разделение регистра `mtvec` на поля `BASE` и `MODE`

В случае обзорной системы прерывания, любой перехват приводит к загрузке в PC значения базового адреса обработчика перехвата (`PC=BASE`). В векторной системе прерывания исключения обрабатываются таким же способом, как и в обзорной системе, а вот прерывания обрабатываются путем загрузки в PC суммы базового адреса и учетверенного значения причины прерывания (`PC=BASE+4*CAUSE`).

В рамках данной лабораторной работы мы будем реализовывать обзорную систему прерываний. Кроме того, поскольку у обзорной системы прерываний `MODE==0`, что совпадет с тем, что два младших бита базового адреса обработчика перехвата должны быть равны нулю, при перехвате мы можем присваивать программному счетчику значение `mtvec` без каких-либо преобразований.

Так как обработчик перехвата будет использовать те же регистры, что и прерванная программа, то перед использованием регистрового файла, данные из него необходимо сохранить, разместив их на стеке. Стек для перехвата находится не там же, где программный стек, а адрес начала этого стека хранится в регистре `mscratch` и по сути является указателем на верхушку стека. Регистр `mepc` сохраняет адрес инструкции во время которой произошел перехват. Это очень важно понимать, при реализации обработчика исключения — если в нем не перезаписать этот регистр, по возврату из обработчика процессор снова окажется на инструкции, которая вызвала исключение.

То как кодируется причина перехвата в регистре `mcause` описано в спецификации привилегированной архитектуры[[5, стр. 38]](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf):

![../../.pic/Labs/lab_10_irq/tab_05.png](../../.pic/Labs/lab_10_irq/tab_05.png)

_Таблица 5. Кодирование причины перехвата в регистре `mcause`._

Нас интересуют части, выделенные красным. В первую очередь то, как кодируется старший бит регистра `mcause`. Он зависит от типа причины перехвата (`1` в случае прерывания, `0` в случае исключения). Оставшиеся 31 бит регистра отводятся под коды различных причин. Поскольку мы создаем учебный процессор, который не будет использован в реальной жизни, он не будет поддерживать большую часть прерываний/исключений (таких как невыровненный доступ к памяти, таймеры и т.п.). В рамках данного курса мы должны поддерживать исключение по нелегальной инструкции (код 0x02) и должны уметь поддерживать прерывания периферийных устройств (под которые зарезервированы коды начиная с 16-го). В рамках данной лабораторной работы процессор будет поддерживать только один источник прерывания, поэтому для кодирования причины прерывания нам потребуется только первый код из диапазона _"Designated for platform use"_. В случае, если вы захотите расширить количество источников прерываний, вы можете выполнить вспомогательную [лабораторную работу №12](../12.%20Daisy%20chain).

Таким образом: в случае если произошло исключение (в связи с нелегальной инструкцией), значение `mcause` должно быть `0x00000002`. Если произошло прерывание, значение `mcause` должно быть `0x80000010`.

При желании, процессор можно будет улучшить, добавив поддержку большего числа периферийных устройств. В этом случае потребуется только расширить контроллер прерываний.

Когда процессор включается, программа первым делом должна инициализировать все требуемые CS-регистры, в частности:

- задать маску прерывания `mie`,
- задать адрес вектора прерывания `mtvec`,
- задать адрес вершины стека прерываний `mscratch`.

После чего уже можно переходить к исполнению основного потока инструкций.

### Реализация прерываний в архитектуре RISC-V

Процессор RISC-V может работать в одном из нескольких режимов выполнения с различными уровнями привилегий. Машинный режим – это самый высокий уровень привилегий; программа, работающая в этом режиме, может получить доступ ко всем регистрам и ячейкам памяти. M-режим является единственным необходимым режимом привилегий и единственным режимом, используемым в процессорах без операционной системы, включая многие встраиваемые системы.

Обработчики прерываний/исключений используют для перехвата четыре специальных регистра управления и состояния (CSR): `mtvec`, `mcause`, `mepc` и `mscratch`. Регистр базового адреса вектора прерывания `mtvec`, содержит адрес кода обработчика прерывания. При перехвате процессор:

- записывает причину перехвата в `mcause`,
- сохраняет адрес перехваченной инструкции, в `mepc`,
- переходит к обработчику перехвата, загружая в `PC` адрес, предварительно настроенный в `mtvec`.

После перехода по адресу в `mtvec` обработчик считывает регистр `mcause`, чтобы проверить, что вызвало прерывание или исключение, и реагирует соответствующим образом (например, считывая клавиатуру при аппаратном прерывании).

После выполнения программы-обработчика перехвата, возвращение в программу выполняется командой возврата `mret`, которая помещает в `PC` значение регистра `mepc`. Сохранение `PC` инструкции при прерывании в `mepc` аналогично использованию регистра `ra` для хранения обратного адреса во время инструкции `jal`. Обработчики прерываний должны использовать программные регистры (`x1−x31`) для своей работы, поэтому они используют память, на которую указывает `mscratch`, для хранения и восстановления этих регистров.

Контроллер прерываний – это блок процессора, обеспечивающий взаимодействие с устройствами, запрашивающими прерывания, формирование кода причины прерывания для процессора, маскирование прерываний, а также, в других реализациях, может реагировать на прерывания в соответствии с приоритетом и тому подобное.

Периферийное устройство, которое может генерировать прерывание, подключается к контроллеру прерывания паре проводов: запрос на прерывание (`irq_req_i`) и прерывание обслужено (`irq_ret_o`). Предположим, к контроллеру прерываний подключили клавиатуру. Когда на ней нажимают клавишу, код этой клавиши попадает в буферный регистр с дополнительным управляющим битом, выставленным в единицу, который подключен к входу запроса на прерывание. Если прерывание не замаскировано (в нашем процессоре это означает, что нулевой бит регистра `mie` выставлен в 1), то контроллер прерывания сгенерирует код причины прерывания (в нашем случае — это константа `0x80000010`). Кроме этого, контроллер прерывания подаст сигнал `irq_o`, чтобы устройство управления процессора узнало, что произошло прерывание и разрешило обновить содержимое регистра причины `mcause`, сохранило адрес прерванной инструкции в `mepc` и загрузило в `PC` вектор прерывания `mtvec`.

Когда будет выполняться инструкция `mret`, устройство управления подаст сигнал контроллеру прерывания, чтобы тот, в свою очередь, направил его в виде сигнала «прерывание обслужено» для соответствующего устройства. После этого периферийное устройство обязано снять сигнал запроса прерывания хотя бы на один такт. В нашем примере сигнал «прерывание обслужено» может быть подключен непосредственно к сбросу буферного регистра клавиатуры.

## Структура разрабатываемых устройств

В рамках лабораторной работы необходимо реализовать поддержку обработки аппаратных прерываний. Для этого необходимо реализовать два модуля: блок управления регистрами контроля и статуса (**CSR-контроллер**) и контроллер прерываний **(Interrupt Controller)**.

Блок управления регистрами контроля и статуса позволяет добавить особые **архитектурные регистры**, которые будут использоваться нами при обработке прерываний и исключений.

Контроллер прерываний позволит обрабатывать входящие запросы на прерывания: маски́ровать их, выбирать один запрос из нескольких, а также игнорировать запросы во время обработки текущего прерывания.

![../../.pic/Labs/lab_10_irq/fig_03.drawio.svg](../../.pic/Labs/lab_10_irq/fig_03.drawio.svg)

_Рисунок 3. Место разрабатываемых блоков в структуре процессора._

Пока что вам нужно реализовать только блоки **irq controller** и **control status registers**, а не саму схему, приведенную выше.

### CSR-контроллер

Рассмотрим один из возможных вариантов организации блока **Control and Status Registers**. Основная работа по описанию схемы блока состоит в описании мультиплексора и демультиплексора. Мультиплексор подает на выход **read_data_o** значение регистра, который соответствует пришедшему адресу. В свою же очередь, демультиплексор маршрутизирует сигнал разрешения на запись **write_enable_i** (en) на тот же регистр.

![../../.pic/Labs/lab_10_irq/fig_04.drawio.svg](../../.pic/Labs/lab_10_irq/fig_04.drawio.svg)

_Рисунок 4. Структурная схема контроллера CS-регистров._

3-битный вход **opcode_i** определяет операцию, которая будет производиться над содержимым CSR по адресу **addr_i**.

Для реализации мультиплексора на языке описания аппаратуры SystemVerilog можно воспользоваться конструкцией `case` внутри блока **always_comb**. Для реализации демультиплексора также можно использовать `case`, только если при описании мультиплексора в зависимости от управляющего сигнала на один и тот же выход идут разные входы, то при описании демультиплексора все будет наоборот: в зависимости от управляющего сигнала, один и тот же вход будет идти на разные выходы (например, на разные биты многоразрядной шины `enable`).

Мультиплексоры, располагаемые на входах регистров `mepc` и `mcause` нужны, чтобы при возникновении сигнала прерывания сразу же разрешить обновить значение этих регистров значением `pc_i`, на котором произошел перехват и кодом причины происходящего сейчас перехвата.

### Контроллер прерываний

Рассмотрим один из возможных способов реализации простейшего контроллера прерываний, представленного на _рис. 5_.

![../../.pic/Labs/lab_10_irq/fig_05.drawio.svg](../../.pic/Labs/lab_10_irq/fig_05.drawio.svg)

_Рисунок 5. Структурная схема контроллера прерываний._

Контроллер состоит из логики:

- обработки вложенных прерываний, частью которой являются регистры отслеживания обработки прерывания и исключения (`irq_h` и `exc_h` соответственно),
- установки и сброса этих регистров (которая вместе с этими регистрами заключена в штрихованные прямоугольники),
- приоритета исключений над прерываниями,
- маскирования запросов на прерывание.

Регистры отслеживания обработки прерывания и исключения нужны для того, чтобы мы могли понимать, что в данный момент процессор уже выполняет обработку прерывания / исключения. В такие моменты (если любой из регистров `exc_h`/`irq_h` содержит значение `1`) все последующие запросы на прерывание игнорируются. За это отвечают вентили И и ИЛИ-НЕ в правом верхнем углу схемы.

>Однако возможна ситуация возникновения исключения во время обработки прерывания — в этом случае, оба регистра будут хранить значение `1`. В момент возврата из обработчика, придет сигнал `mret_i`, который в первую очередь сбросит регистр `exc_h` и только если тот равен нулю, сбросит регистр `irq_h`.
>
>Исключение во время обработки исключения не поддерживается данной микроархитектурой и приведет к неопределенному поведению. Поэтому код обработчика исключений должен быть написан с особым вниманием.

Логика установки и сброса работает следующим образом:

- если сигнал, обозначенный в прямоугольнике как `reset` равен единице, в регистр будет записано значение `0`;
- если сигнал, обозначенный в прямоугольнике как `set` равен единице, в регистр будет записано значение `1`;
- в остальных случах, регистр сохраняет свое значение.

Обратите внимание, что логика установки и сброса регистров дает приоритет сбросу, хотя сигнал сброса никогда не придет одновременно с сигналом установки (поскольку инструкция `mret` не генерирует исключение, сигнал `mret_i` никогда не придет одновременно с сигналом `exception_i`, а логика приоритета исключений над прерываниями не даст сигналу `mret` распространиться до регистра `irq_h` одновременно с формированием сигнала `irq_o`).

Логика приоритета исключений над прерываниями заключается в том, что сигнал `exception_i` является частью логики обработки вложенных прерываний. Пройдя через два логических ИЛИ и последующий инвертор, этот сигнал обнулит запрос на прерывание на логическом И в правом верхнем углу.

Логика маскирования запросов на прерывания заключается в простейшем И между запросом на прерывания (`irq_req_i`) и сигналом разрешения прерывания (`mie_i`).

## Пример обработки перехвата

Ниже представлен пример программы и обработчика перехватов. Программа начинается с инициализации начальных значений регистров управления, указателя на верхушку стека и глобальную область данных, после чего уходит в бесконечный цикл ничего не делая, до тех пор, пока не произойдет перехват.

Алгоритм работы обработчика перехвата (`trap handler`) выглядит следующим образом:

1. сохраняется содержимое регистрового файла на стек;
2. проверяется регистр причины чтобы запустить необходимую подпрограмму;
3. происходит вызов необходимой подпрограммы;
4. после возврата происходит восстановление содержимого регистрового файла;
5. затем происходит возврат управления прерванной программе.

Если бы система прерывания была векторной, то рутина со считыванием кода причины отсутствовала.

```asm
_start:
# Инициализируем начальные значения регистров
00: li x2, 0x00003FFC       # устанавливаем указатель на верхушку стека
04:                         # данная псевдоинструкция будет разбита на две
                            # инструкции: lui и addi

08: li x3, 0x00000000       # устанавливаем указатель на глобальные данные

0C: li x5, 0x00000001       # подготавливаем маску прерывания единственного
                            # (нулевого) входа
10: csrw mie, x5            # загружаем маску в регистр маски

14: la x5, trap_handler     # псевдоинструкция la аналогично li загружает число,
18:                         # только в случае la — это число является адресом
                            # указанного места (адреса обработчика перехвата)
                            # данная псевдоинструкция будет разбита на две
                            # инструкции: lui и addi

1С: csrw mtvec, x5          # устанавливаем вектор прерывания

20: li x5, 0x00001FFC       # готовим адрес верхушки стека прерывания
24:                         # данная псевдоинструкция будет разбита на две
                            # инструкции: lui и addi

28: csrw mscratch, x5       # загружаем в указатель на верхушку стека прерывания

2С: li x5, 1                # начальное значение глобальной переменной
30: sw x5, 0(x3)            # загружаем переменную в память

34: li x6, 0                # начальное значение, чтобы в симуляции не было xxx
38: li x7, 0                # начальное значение, чтобы в симуляции не было xxx

# Вызов ecall исключительно из хулиганских соображений, поскольку в данной
# микроархитектурной реализации это приведет к появлению illegal_instr и
# последующей обработке исключения
3С: ecall

# Вызов функции main
main:
40: beq x0, x0, main        # бесконечный цикл, аналогичный while (1);

# ОБРАБОТЧИК ПЕРЕХВАТА
# Без стороннего вмешательства процессор никогда не перейдет к инструкциям ниже,
# однако в случае прерывания в программный счетчик будет загружен адрес первой
# нижележащей инструкции.

# Сохраняем используемые регистры на стек
trap_handler:
44: csrrw x5, mscratch, x5  # меняем местами mscratch и x5
48: sw x6, 0(x5)            # сохраняем x6 на стек mscratch
4С: sw x7, 4(x5)            # сохраняем x7 на стек mscratch

# Проверяем произошло ли прерывание
50: csrr x6, mcause         # x6 = mcause
54: li x7, 0x80000010       # загружаем в x7 код того, что произошло прерывание
58:                         # данная псевдоинструкция будет разбита на две
                            # инструкции: lui и addi
5C: bne x6, x7, exc_handler # если коды не совпадают, переходим к проверке
                            # на исключение
# Обработчик прерывания
60: lw x7, 0(x3)            # загружаем переменную из памяти
64: addi x7, x7, 3          # прибавляем к значению 3
68: sw x7, 0(x3)            # возвращаем переменную в память
6C: j done                  # идем возвращать регистры и на выход

exc_handler:                # Проверяем произошло ли исключение
70: li x7, 0x0000002        # загружаем в x7 код того, что произошло исключение
74: bne x6, x7, done        # если это не оно, то выходим

# Обработчик исключения
78: csrr x6, mepc           # Узнаем значение PC (адреса инструкции,
                            # вызвавшей исключение)
7C: lw x7, 0x0(x6)          # Загружаем эту инструкцию в регистр x7.
                            # В текущей микроархитектурной реализации это
                            # невозможно, т.к. память инструкций отделена от
                            # памяти данных и не участвует в выполнении
                            # операций load / store.
                            # Другой способ узнать об инструкции, приведшей
                            # к исключению — добавить поддержку статусного
                            # регистра mtval, в который при исключении
                            # может быть записана текущая инструкция.
                            # Теоретически мы могли бы после этого
                            # сделать что-то, в зависимости от этой инструкции.
                            # Например, если это операция умножения — вызвать
                            # подпрограмму умножения.

80: addi x6, x6, 4          # Увеличиваем значение PC на 4, чтобы после
                            # возврата не попасть на инструкцию, вызвавшую
                            # исключение.
84: csrw mepc, x6           # Записываем обновленное значение PC в регистр mepc
88: j done                  # идем восстанавливать регистры со стека и на выход

# Возвращаем регистры на места и выходим
done:
8C: lw x6, 0(x5)            # возвращаем x6 со стека
90: lw x7, 4(x5)            # возвращаем x7 со стека
94: csrrw x5, mscratch, x5  # меняем обратно местами x5 и mscratch
98: mret                    # возвращаем управление программе (pc = mepc)
                            # что означает возврат в бесконечный цикл
```

## Задание

1. Описать на языке SystemVerilog модуль контроллера регистров статуса и контроля (**CSR**-контроллер) со следующим прототипом:

```SystemVerilog
module csr_controller(

  input  logic        clk_i,
  input  logic        rst_i,
  input  logic        trap_i,

  input  logic [ 2:0] opcode_i,

  input  logic [11:0] addr_i,
  input  logic [31:0] pc_i,
  input  logic [31:0] mcause_i,
  input  logic [31:0] rs1_data_i,
  input  logic [31:0] imm_data_i,
  input  logic        write_enable_i,

  output logic [31:0] read_data_o,
  output logic [31:0] mie_o,
  output logic [31:0] mepc_o,
  output logic [31:0] mtvec_o
);

import csr_pkg::*;

endmodule
```

2. Описать на языке SystemVerilog модуль контроллера прерываний со следующим прототипом:

```SystemVerilog
module interrupt_controller(
  input  logic        clk_i,
  input  logic        rst_i,
  input  logic        exception_i,
  input  logic        irq_req_i,
  input  logic        mie_i,
  input  logic        mret_i,

  output logic        irq_ret_o,
  output logic [31:0] irq_cause_o,
  output logic        irq_o
);

endmodule
```

## Порядок выполнения задания

1. Внимательно ознакомьтесь с описанием модуля `csr_controller` и его структурной схемой. В случае возникновения вопросов, проконсультируйтесь с преподавателем.
2. Реализуйте модуль `csr_controller`. Для этого:
   1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemSystemVerilog`-файл `csr_controller.sv`.
   2. Опишите в нем модуль `csr_controller` с таким же именем и портами, как указано в [задании](#задание).
   3. Обратите внимание на наличие импорта пакета `csr_pkg`, данный пакет содержит адреса используемых регистров контроля и статуса, которыми будет удобно пользоваться при реализации модуля.
3. После описания модуля, его необходимо проверить с помощью тестового окружения.
   1. Тестовое окружение находится [здесь](tb_csr.sv).
   2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md).
   3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (`tb_csr`).
   4. Во время симуляции, вы должны прожать "Run All" и убедиться, что в логе есть сообщение о завершении теста!
4. Внимательно ознакомьтесь с описанием функционального поведения сигналов `interrupt_controller`, а также его структурной схемой. В случае возникновения вопросов, проконсультируйтесь с преподавателем.
5. Реализуйте модуль `interrupt_controller`. Для этого:
   1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemSystemVerilog`-файл `interrupt_controller.sv`.
   2. Опишите в нем модуль `interrupt_controller` с таким же именем и портами, как указано в [задании](#задание).
6. После описания модуля, его необходимо проверить с помощью тестового окружения.
   1. Тестовое окружение находится [здесь](tb_irq.sv).
   2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md).
   3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (`tb_irq`).
   4. Во время симуляции, вы должны прожать "Run All" и убедиться, что в логе есть сообщение о завершении теста!

## Список использованной литературы

1. С.А. Орлов, Б.Я. Цилькер / Организация ЭВМ и систем: Учебник для вузов. 2-е изд. / СПб.: Питер, 2011.
2. [PIC24FJ512GU410 Family Data Sheet](https://ww1.microchip.com/downloads/aemDocuments/documents/MCU16/ProductDocuments/DataSheets/PIC24FJ512GU410-Family-Data-Sheet-DS30010203D.pdf)
3. [The Art of Assembly Language](https://flint.cs.yale.edu/cs422/doc/art-of-asm/pdf/)
4. [The RISC-V Instruction Set Manual Volume I: Unprivileged ISA](https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf)
5. [The RISC-V Instruction Set Manual Volume II: Privileged Architecture](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf)
