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

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

## Цель

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

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

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

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

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

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

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

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

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

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

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

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

---

На протяжении многих лет, концепция понятия "прерывание" постоянно расширялась. Семейство процессоров 80x86 внесло ещё большую путаницу введя инструкцию `int` (программное прерывание). Многие производители используют такие термины как: **исключение** (_exception_), **ошибка** (_fault_), **отказ** (_abort_), **ловушка** (_trap_) и **прерывание** (_interrupt_), чтобы описать явление, которому посвящена данная лабораторная работа. К несчастью, не существует какого-то чёткого соглашения насчёт этих названий. Разные авторы по-разному приспосабливают эти термины для своего повествования<sup>[[1](https://flint.cs.yale.edu/cs422/doc/art-of-asm/pdf/CH17.PDF)]</sup>. Для того, чтобы постараться избежать путаницы, в данной лабораторной работе мы будем использовать три термина, которые введены в спецификации архитектуры RISC-V<sup>[[2](https://github.com/riscv/riscv-isa-manual/releases/download/draft-20221004-28b46de/riscv-spec.pdf)]</sup>, однако имейте в виду, что за пределами этой методички и спецификации 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_ приводится фрагмент [спецификации](https://github.com/riscv/riscv-isa-manual/releases/download/riscv-isa-release-1239329-2023-05-23/riscv-privileged.pdf) привилегированной архитектуры (стр. 10), иллюстрирующая некоторые из регистров. В левом столбце указан 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 "Основной дешифратор".

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

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

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

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

Регистр `mtvec` является базовым адресом обработчика прерывания. Это значит, что предусмотрена возможность реализации как обзорной (прямой), так и векторной системы прерывания. В первом случае при возникновении прерывания в **program counter** загружается значение `mtvec`. Во втором случае, в **program counter** загружается сумма регистра базового адреса `mtvec` и регистра причины прерывания `mcause`, который обновляется каждый раз, когда происходит прерывание, значение в нем несет информацию о том, что именно произошло в системе.

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

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

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

Остальные операции считывают значение одного из CS-регистров в регистровый файл, при этом, инструкция **csrrw** еще записывает значение из регистрового файла в CSR. Инструкция `csrrs` выполняет логическое ИЛИ между содержимым CS-регистров и регистрового файла, тем самым устанавливая в CS-регистре единицу в тех же битах, что и у считываемого регистра. Операция `csrrc` приводит к очищению битов, значения которых в считываемом из регистрового файла регистре были равны 1.

| 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

Процессор 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`, для хранения и восстановления этих регистров.

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

Каждое периферийное устройство, которое может сгенерировать прерывание, подключается к контроллеру прерывания по одной из 32 пар проводов: запрос на прерывание (`int_req_i`) и прерывание обслужено (`int_ret_o`). Например, подключили клавиатуру к 7-ой паре. Когда на клавиатуру нажимают, код этой клавиши попадает в буферный регистр с дополнительным управляющим битом, выставленным в единицу, который подключен к входу запроса на прерывание. Если прерывание не замаскировано, то есть в данном примере 7-ой бит регистра `mie` выставлен в 1, то контроллер прерывания сгенерирует соответствующий код причины (например, 7 или 7*4, если реализуется векторная система прерывания [по желанию студента]). Кроме этого, контроллер прерывания выдаст сигнал **INT** прямо в устройство управления процессора, чтобы оно узнало, что произошло прерывание и разрешило обновить содержимое регистра причины `mcause`, сохранило адрес прерванной инструкции в `mepc` и загрузило в `PC` вектор прерывания `mtvec`.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Рассмотрим один из возможных способов реализации простейшего контроллера прерываний по схеме `daisy chain` (гирлянда). Пример такой схемы вы можете увидеть на _рис. 4_.

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

_Рисунок 4. Структурная схема daisy-цепочки_

Дейзи-цепочка состоит из двух массивов элементов И. Первый массив (верхний ряд элементов) формирует многоразрядный сигнал (назовем его для определенности `enable`), который перемножается с запросами с помощью массива элементов из нижнего ряда. Обратите внимание на то, что результат очередного элемента нижнего массива влияет на результат следующего за ним элемента верхнего массива. Как только на одном из элементов нижнего массива появится значение `1`, оно сразу же распространится в виде инверсии по всем оставшимся элементам верхнего массива.

Обратите внимание, что результат верхнего ряда массивов (который ранее была назван `enable`) не является маской разрешения прерываний `mie_i`. Сперва исходные запросы на прерывания логически перемножаются с маской `mie_i`, и только после этого, результат логического умножения снова логически перемножается с сигналом `enable` (и это логическое перемножение формирует нижний ряд элементов на схеме). Его результат может содержать только одну единицу, она будет соответствовать одному из запросов на прерывание. Поэтому этот результат можно использовать в качестве сигнала `mcause_o` для идентификации причины прерывания (соответствует сигналам y<sub>1</sub>,y<sub>2</sub>,...,y<sub>n</sub> на схеме). Свертка по ИЛИ этого сигнала даст итоговый запрос на прерывание.

Для описания верхнего ряда на языке SystemVerilog будет удобно воспользоваться конструкцией `generate for`, о которой рассказывалось в [ЛР 1 "Сумматор"](../01.%20Adder#Задание).

Рассмотрим реализацию нашего контроллера прерываний:

Помимо портов `clk_i` и `rst_i`, модуль будет иметь 4 входа и три выхода:

- `irq_req_i` — 32-разрядный вход запроса прерывания (т.е. процессор будет поддерживать 32 источника прерывания).
- `mie_i` — маска прерывания, логически перемножающаяся с запросом на прерывание. С помощью маски можно игнорировать отдельные прерывания (0 — прерывание игнорируется, 1 — прерывание не игнорируется).
- `stall_i` — сигнал о выполнении операции с памятью. Пока он равен единице, программный счетчик не изменится, а нам будет нужно отследить момент, когда будет меняться программный счетчик.
- `mret_i` — сигнал о возврате управления основному потоку инструкций (выход из обработчика прерываний)
- `irq_o` — сигнал о начале обработки прерываний. Когда этот сигнал равен единице, в программный счетчик будет загружаться адрес из CS-регистра `mtvec`. Поэтому в случае обработки прерывания, единица должна подняться на этом выходе ровно на 1 такт (иначе в программный счетчик будет непрерывно записываться значение `mtvec`).
- `mcause_o` — причина прерывания. В нашем случае, на данном выходе только один бит будет равен единице в момент обработки прерывания (бит, соответствующий индексу принятого прерывания)
- `irq_ret_o` — сигнал о завершении обработки запроса на прерывания. Будет соответствовать `mcause_o` в момент появления сигнала `mret_i`.

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

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

Особое внимание стоит уделить регистру `busy`. Инверсия этого регистра подается на вход daisy-цепочки, и если эта инверсия будет равна нулю, то вся цепь будет отключена.

С помощью этого регистра можно управлять работой контроллера прерываний, пока он равен единице, новый сигнал о прерывании формироваться не будет. Таким образом мы будем игнорировать все последующие прерывания, пока не будет обслужено предыдущее. Более того, в случае если прерывание пришло посреди исполнения инструкции работы с памятью (поднят сигнал `stall_i`), необходимо дождаться завершения исполнения этой инструкции.

## Пример обработки прерывания

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

Для данного примера представим, что к контроллеру прерываний подключено две условные кнопки: одна к 5-ому входу прерывания, вторая – к 19-ому. Обработчик прерывания сначала сохраняет значения используемых регистров на стек → проверяет регистр причины, чтобы запустить необходимую подпрограмму для конкретного прерывания → выполняет полезную задачу, связанную с этим прерывание → восстанавливает значения регистрового файла → возвращает управление прерванной программе. Если бы система прерывания была векторная, то рутина со считыванием кода причины отсутствовала.

Для примера пусть прерывание 5 будет прибавлять число 3 к некоторой глобальной переменной, а прерывание 19 будет делить это же число пополам.

```asm
_start:
# Инициализируем начальные значения регистров
li sp, 0xFFFFFFFC          # устанавливаем указатель на верхушку стека
li gp, 0x10000000          # устанавливаем указатель на глобальные данные

li t0, 0x00080020          # подготавливаем маску прерывания для 5 и 19 входов
csrw mie, t0               # загружаем маску в регистр маски
la t0, interrupt           # аналогично li загружает число, в данном случае - адрес
csrw mtvec, t0             # устанавливаем вектор прерывания
li t0, 0xEFFFFFFC          # готовим адрес верхушки стека прерывания
csrw mscratch, t0          # загружаем в указатель на верхушку стека прерывания

li t0, 1                   # начальное значение глобальной переменной
sw t0, 0(gp)               # загружаем переменную в память

li t1, 0                   # начальное значение, чтобы в симуляции не было xxx
li t2, 0                   # начальное значение, чтобы в симуляции не было xxx

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

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

# Сохраняем используемые регистры на стек
interrupt:
    csrrw t0, mscratch, t0 # меняем местами mscratch и t0
    sw t1, 0(t0)           # сохраняем t1 на стек mscratch
    sw t2, 4(t0)           # сохраняем t2 на стек mscratch

    # Проверяем регистр причины и на 5-ое прерывание
    csrr t1, mcause        # t1 = mcause
    li t2, 5               # t2 = 5 (код одного из прерываний)
    bne t1, t2, nineteen   # если это не 5 прерывание, то проверяем 19
        # Обработчик 5-го прерывания
    lw t2, 0(gp)           # загружаем переменную из памяти
    addi t2, t2, 3         # прибавляем к значению 3
    sw t2, 0(gp)           # возвращаем переменную в память
    j done                 # идем возвращать регистры и на выход

nineteen: # Проверяем на 19-ое прерывание
    li t2, 19              # t2 = 19 (код другого прерывания)
    bne t1, t2, done       # если не 19-ое, то выходим
    # Обработчик 19-го прерывания
    lw t2, 0(gp)           # загружаем переменную из памяти
    srli t2, t2, 1         # делим число пополам сдвигом вправо
    sw t2, 0(gp)           # возвращаем переменную в память
    j done                 # идем возвращать регистры и на выход

# Возвращаем регистры на места и выходим
done:
    lw t1, 0(t0)           # возвращаем t1 со стека
    lw t2, 4(t0)           # возвращаем t2 со стека
    csrrw t0, mscratch, t0 # меняем обратно местами t0 и mscratch
    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        stall_i,
  input  logic [31:0] irq_req_i,
  input  logic [31:0] mie_i,
  input  logic        mret_i,

  output logic [31:0] irq_ret_o,
  output logic [31:0] mcause_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. Внимательно ознакомьтесь с описанием функционального поведения сигналов `irq_controller`, а так же его структурной схемой. В случае возникновения вопросов, проконсультируйтесь с преподавателем.
4. Реализуйте модуль `irq_controller`. Для этого:
   1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemSystemVerilog`-файл `irq_controller.sv`.
   2. Опишите в нем модуль `irq_controller` с таким же именем и портами, как указано в [задании](#задание).
      1. Обратите внимание. что верхний ряд 32 элементов логических И проще всего будет реализовать с помощью непрерывного присваивания в блоке `generate for`. Нижний ряд логических И реализовывается в одном выражении побитового И между двумя 32-разрядными сигналами.
      2. Свертка по ИЛИ выполняется посредством **унарного оператора** `|`, ставящегося перед многоразрядным сигналом.
5. Работа по интеграции и проверке модулей в ядро процессора будет происходить в рамках следующей лабораторной работы.
