# Лабораторная работа 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_), чтобы описать явление, которому посвящена данная лабораторная работа. К несчастью, не существует какого-то чёткого соглашения насчёт этих названий. Разные авторы по-разному приспосабливают эти термины для своего повествования[[1](https://flint.cs.yale.edu/cs422/doc/art-of-asm/pdf/CH17.PDF)]. Для того, чтобы постараться избежать путаницы, в данной лабораторной работе мы будем использовать три термина, которые введены в спецификации архитектуры RISC-V[[2](https://github.com/riscv/riscv-isa-manual/releases/download/draft-20221004-28b46de/riscv-spec.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_ приводится фрагмент [спецификации](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](../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` является базовым адресом обработчика прерывания. Это значит, что предусмотрена возможность реализации как обзорной (прямой), так и векторной системы прерывания. В первом случае при возникновении прерывания в **program counter** загружается значение `mtvec`. Во втором случае, в **program counter** загружается сумма регистра базового адреса `mtvec` и регистра причины прерывания `mcause`, который обновляется каждый раз, когда происходит прерывание, значение в нем несет информацию о том, что именно произошло в системе. Так как обработчик прерывания будет использовать те же регистры, что и прерванная программа, то перед использованием регистрового файла, данные из него необходимо сохранить, разместив их на стеке. Стек для прерывания находится не там же, где программный стек, а адрес начала этого стека хранится в регистре `mscratch` и по сути является указателем на верхушку стека. Регистр `mepc` сохраняет адрес инструкции во время которой произошел перехват. Это очень важно понимать, при реализации обработчика исключения — если в нем не перезаписать этот регистр, по возврату из обработчика процессор снова окажется на инструкции, которая вызвала исключение. То как кодируется причина перехвата в регистре `mcause` описано в [спецификации](https://github.com/riscv/riscv-isa-manual/releases/download/riscv-isa-release-1239329-2023-05-23/riscv-privileged.pdf) привилегированной архитектуры (раздел 3.1.15, стр. 38): ![../../.pic/Labs/lab_10_irq/tab_03.png](../../.pic/Labs/lab_10_irq/tab_03.png) _Таблица 5. Кодирование причины прерывания в регистре `mcause`_ Нас интересуют части, выделенные красным. В первую очередь то как кодируется старший бит регистра `mcause`. Он зависит от типа причины перехвата (`1` в случае прерывания, `0` в случае исключения). Оставшиеся 31 бит регистра отводятся под коды различных причин. Поскольку мы создаем учебный процессор, который не будет использован в реальной жизни, он не будет поддерживать большую часть прерываний/исключений (таких как невыровненный доступ к памяти, таймеры и т.п.). В рамках данного курса мы должны поддерживать исключение по нелегальной инструкции (код 0x02) и должны уметь поддерживать прерывания периферийных устройств (под которые зарезервированы коды начиная с 16-го). Процессор будет поддерживать только один источник прерывания, поэтому для кодирования причины прерывания нам потребуется только первый код из диапазона _"Designated for platform use"_. Таким образом: в случае если произошло исключение (в связи с нелегальной инструкцией), значение `mcause` должно быть `0x00000002`. Если произошло прерывание, значение `mcause` должно быть `0x10000010`. При желании, процессор можно будет улучшить, добавив поддержку большего числа периферийных устройств. В этом случае потребуется только расширить контроллер прерываний. Когда процессор включается, программа первым делом должна инициализировать все требуемые 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`, для хранения и восстановления этих регистров. Контроллер прерываний – это блок процессора, обеспечивающий взаимодействие с устройствами, запрашивающими прерывания, формирование кода причины прерывания для процессора, маскирование прерываний, а также, в других реализациях, может реагировать на прерывания в соответствии с приоритетом и тому подобное. Периферийное устройство, которое может генерировать прерывание, подключается к контроллеру прерывания паре проводов: запрос на прерывание (`int_req_i`) и прерывание обслужено (`int_ret_o`). Предположим, к контроллеру прерываний подключили клавиатуру. Когда на ней нажимают клавишу, код этой клавиши попадает в буферный регистр с дополнительным управляющим битом, выставленным в единицу, который подключен к входу запроса на прерывание. Если прерывание не замаскировано (в нашем процессоре это означает, что нулевой бит регистра `mie` выставлен в 1), то контроллер прерывания сгенерирует код причины прерывания (в нашем случае — это константа `0x10000010`). Кроме этого, контроллер прерывания подаст сигнал `irq_o`, чтобы устройство управления процессора узнало, что произошло прерывание и разрешило обновить содержимое регистра причины `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`, на котором произошел перехват и кодом причины происходящего сейчас перехвата. ### Контроллер прерываний Рассмотрим один из возможных способов реализации простейшего контроллера прерываний, представленного на _рис. 4_. ![../../.pic/Labs/lab_10_irq/fig_04.drawio.png](../../.pic/Labs/lab_10_irq/fig_04.drawio.png) _Рисунок 4. Структурная схема контроллера прерываний_ Несмотря на простоту схемы, данный контроллер состоит из: - логики обработки вложенных прерываний, частью которой являются регистры отслеживания обработки прерывания и исключения (`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: # Инициализируем начальные значения регистров li sp, 0xFFFFFFFC # устанавливаем указатель на верхушку стека li gp, 0x10000000 # устанавливаем указатель на глобальные данные li t0, 0x00000001 # подготавливаем маску прерывания единственного # (нулевого) входа csrw mie, t0 # загружаем маску в регистр маски la t0, interrupt # псевдоинструкция la аналогично li загружает число, # только в случае la — это число является адресом # указанного места (адреса обработчика перехвата) 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 # Проверяем произошло ли прерывание csrr t1, mcause # t1 = mcause li t2, 0x10000010 # загружаем в t2 код того, что произошло прерывание bne t1, t2, exc_handler # если коды не совпадают, переходим к проверке # на исключение # Обработчик прерывания lw t2, 0(gp) # загружаем переменную из памяти addi t2, t2, 3 # прибавляем к значению 3 sw t2, 0(gp) # возвращаем переменную в память j done # идем возвращать регистры и на выход exc_handler: # Проверяем произошло ли исключение li t2, 0x0000002 # загружаем в t2 код того, что произошло исключение bne t1, t2, done # если это не оно, то выходим # Обработчик исключения csrr mepc, t1 # Узнаем значение PC (адреса инструкции, # вызвавшей исключение) lw t2 0x0(t1) # Загружаем эту инструкцию в регистр t2 # Теоретически мы могли бы после этого # сделать что-то, в зависимости от этой инструкции. # Например если это операция умножения — вызвать # подпрограмму умножения. addi t1, t1, 4 # Увеличиваем значение PC на 4, чтобы после # возврата не попасть на инструкцию, вызвавшую # исключение. сsrw mepc, t1 # Записываем обновленное значение PC в регистр mepc 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 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. Внимательно ознакомьтесь с описанием функционального поведения сигналов `irq_controller`, а так же его структурной схемой. В случае возникновения вопросов, проконсультируйтесь с преподавателем. 5. Реализуйте модуль `irq_controller`. Для этого: 1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemSystemVerilog`-файл `irq_controller.sv`. 2. Опишите в нем модуль `irq_controller` с таким же именем и портами, как указано в [задании](#задание). 6. После описания модуля, его необходимо проверить с помощью тестового окружения. 1. Тестовое окружение находится [здесь](tb_irq.sv). 2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md). 3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (`tb_irq`). 4. Во время симуляции, вы должны прожать "Run All" и убедиться, что в логе есть сообщение о завершении теста!