# Лабораторная работа 10 "Подсистема прерывания" Данная лабораторная работа посвящена изучению систем прерывания в компьютерах и их использованию для обработки программных и аппаратных событий. В процессе работы вы познакомитесь с основными понятиями и принципами работы систем прерывания, а также со средствами программной обработки прерываний. ## Материал для подготовки к лабораторной работе - Изучить [теорию по регистрам контроля и статуса](../../Other/CSR.md). ## Цель 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.drawio.svg](../../.pic/Labs/lab_10_irq/fig_01.drawio.svg) _Рисунок 1. Распределение привилегий по уровням абстракций программного обеспечения [[5, стр.448](https://doi.org/10.1007/978-981-16-9113-3_33)]._ Переключение между этими режимами происходит с помощью исключения, называемого **системный вызов**, и который происходит при выполнении специальной инструкции. Для RISC-V такой инструкцией является **ecall**. Это похоже на вызов подпрограммы, но при системном вызове изменяется режим работы и управление передается операционной системе, которая, по коду в инструкции вызова определяет, что от нее хотят. Например, операционная система может предоставить данные с диска, так как запускаемая программа не имеет никакого представления о том, на какой машине ее запустили, или что используется какая-то конкретная файловая система. Системы прерываний имеет ряд характеристик, которые варьируются в зависимости от их реализации. Все системы можно условно разбить на две категории: обзорные (прямые) и векторные. В **обзорных системах прерывания** любой перехват приводит к запуску одного и того же обработчика. Внутри такого обработчика перехвата определяется причина его возникновения (как правило — это число в специальном регистре), и уже в зависимости от причины запускается нужная подпрограмма. Обзорные системы аппаратно проще векторных, но требуют больше рутины и времени на обработку. Именно такая система прерываний будет реализована в данной лабораторной работе. В **векторных системах прерывания** разные события приводят к запуску на исполнение разных программ обработчиков. Адрес начала обработчика перехвата называется **вектором прерывания**. В векторных системах прерывания выделяется фрагмент памяти, в котором хранятся адреса переходов на начало каждого из обработчиков. Такой участок памяти называется **таблицей векторов прерываний** (**Interrupt Vector Table**, **IVT**). В самом простом случае система прерывания позволяет обрабатывать только одно прерывание за раз (именно такую систему мы и будет делать в рамках данной лабораторной работы). Существуют реализации позволяющие во время обработки прерывания «отвлекаться» на другие события. В таких системах используется система приоритетов, чтобы прерывание с более низким приоритетом не прерывало более приоритетное. ### Регистры Контроля и Статуса В процессе создания декодера инструкций в [ЛР№5](../05.%20Main%20decoder/) вы уже реализовывали инструкции для работы с [регистрами контроля и статуса](../../Other/CSR.md). Теперь необходимо спроектировать блок управления этими регистрами. Для реализации простейшей системы прерывания на процессоре с архитектурой RISC-V достаточно реализовать 5 регистров контроля и статуса, работающих в машинном (самом привилегированном) режиме. | Адрес | Уровень привилегий | Название | Описание | |--------|--------------------|----------|----------------------------------------------------| | **Machine Trap Setup** | |0x304 | MRW | mie | Регистр маски перехватов. | |0x305 | MRW | mtvec | Базовый адрес обработчика перехвата. | |0x340 | MRW | mscratch | Адрес верхушки стека обработчика перехвата. | |0x341 | MRW | mepc | Регистр, хранящий адрес перехваченной инструкции. | |0x342 | MRW | mcause | Причина перехвата | _Таблица 1. Список регистров, подлежащих реализации в рамках лабораторной работы._ По адресу `0x304` должен располагаться регистр, позволяющий маскировать перехваты. Например, если на 5-ом входе системы прерывания генерируется прерывание, то процессор отреагирует на него только в том случае, если 5-ый бит регистра `mie` будет равен 1. Младшие 16 бит этого регистра спецификация RISC-V отводит под маскирование специальных системных прерываний, который не будут поддерживаться нашим процессором (подробней об этом будет в описании регистра mcause). Поэтому в нашей процессорной системе мы будем использовать только старшие 16 бит регистра `mie`, которые отведены для нужд конкретной платформы. По адресу `0x305` должен располагаться регистр `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`, расположенного по адресу `0x340` и по сути является указателем на верхушку стека прерываний. Регистр `mepc`, расположенный по адресу `0x341` сохраняет адрес инструкции во время исполнения которой произошел перехват. Это очень важно понимать, при реализации **обработчика исключения** — если в нем не перезаписать этот регистр, по возврату из обработчика **процессор снова окажется на инструкции, которая вызвала исключение**. То как кодируется причина перехвата в регистре `mcause`, расположенного по адресу `0x342` описано в спецификации привилегированной архитектуры[[6, стр. 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) _Таблица 2. Кодирование причины перехвата в регистре `mcause`._ Нас интересуют части, выделенные красным. В первую очередь то, как кодируется старший бит регистра `mcause`. Он зависит от типа причины перехвата (`1` в случае прерывания, `0` в случае исключения). Оставшиеся 31 бит регистра отводятся под коды различных причин. Поскольку мы создаем учебный процессор, который не будет использован в реальной жизни, он не будет поддерживать большую часть прерываний/исключений (таких как невыровненный доступ к памяти, таймеры и т.п.). В рамках данного курса мы должны поддерживать исключение по нелегальной инструкции (код 0x02) и должны уметь поддерживать прерывания периферийных устройств (под которые зарезервированы коды начиная с 16-го, именно поэтому мы будем использовать только старшие 16 бит регистра `mie`). В рамках данной лабораторной работы процессор будет поддерживать только один источник прерывания, поэтому для кодирования причины прерывания нам потребуется только первый код из диапазона _"Designated for platform use"_. В случае, если вы захотите расширить количество источников прерываний, вы можете выполнить вспомогательную [лабораторную работу №12](../12.%20Daisy%20chain). Таким образом: в случае если произошло исключение (в связи с нелегальной инструкцией), значение `mcause` должно быть `0x00000002`. Если произошло прерывание, значение `mcause` должно быть `0x80000010`. При желании, процессор можно будет улучшить, добавив поддержку большего числа периферийных устройств. В этом случае потребуется только расширить контроллер прерываний. Когда процессор включается, программа первым делом должна инициализировать регистры контроля и статуса, в частности: - задать адрес вектора прерывания `mtvec`, - задать адрес вершины стека прерываний `mscratch`, - задать маску прерывания `mie`. После чего уже можно переходить к исполнению основного потока инструкций. Обратите внимание, что маску прерываний следует задавать в последнюю очередь, т.к. в противном случае система может начать реагировать на прерывания, не имея в регистре `mtvec` корректного адреса вектора прерываний. ### Реализация прерываний в архитектуре RISC-V Процессор RISC-V может работать в одном из нескольких режимов выполнения с различными уровнями привилегий. Машинный режим – это самый высокий уровень привилегий; программа, работающая в этом режиме, может получить доступ ко всем регистрам и ячейкам памяти. M-режим является единственным необходимым режимом привилегий и единственным режимом, используемым в процессорах без операционной системы, включая многие встраиваемые системы. Обработчики прерываний/исключений используют для перехвата четыре специальных регистра управления и состояния (CSR): `mtvec`, `mcause`, `mepc` и `mscratch`. Регистр базового адреса вектора прерывания `mtvec`, содержит адрес кода обработчика прерывания. При перехвате процессор: - записывает причину перехвата в `mcause`, - сохраняет адрес перехваченной инструкции, в `mepc`, - переходит к обработчику перехвата, загружая в `PC` адрес, предварительно настроенный в `mtvec`. После перехода по адресу в `mtvec` обработчик считывает регистр `mcause`, чтобы проверить, что вызвало прерывание или исключение, и реагирует соответствующим образом (например, считывая пришедший с клавиатуры символ при аппаратном прерывании). После выполнения программы-обработчика перехвата, возвращение в программу выполняется командой возврата `mret`, которая помещает в `PC` значение регистра `mepc`. Сохранение `PC` инструкции при прерывании в `mepc` аналогично использованию регистра `ra` для хранения обратного адреса во время инструкции `jal`. Поскольку обработчики перехватов могут использовать для своей работы регистровый файл, для хранения и восстановления значений его регистров им нужен отдельный стек, на который указывает `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`. Исключение во время обработки исключения не поддерживается данной микроархитектурой и скорее всего приведет к циклическому вызову обработчика исключения. Поэтому код обработчика исключений должен быть написан с особым вниманием. Логика установки и сброса регистров `irq_h` и `exc_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 # устанавливаем указатель на глобальные данные 0С: la x5, trap_handler # псевдоинструкция la аналогично li загружает число, 10: # только в случае la — это число является адресом # указанного места (адреса обработчика перехвата) # данная псевдоинструкция будет разбита на две # инструкции: lui и addi 14: csrw mtvec, x5 # устанавливаем вектор прерывания 18: li x5, 0x00001FFC # готовим адрес верхушки стека прерывания 1С: # данная псевдоинструкция будет разбита на две # инструкции: lui и addi 20: csrw mscratch, x5 # загружаем указатель на верхушку стека прерывания 24: li x5, 0x00010000 # подготавливаем маску прерывания единственного # входа прерываний 28: csrw mie, 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` проекта с предыдущих лаб, создайте `SystemVerilog`-файл `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. **По завершению симуляции убедитесь, что в логе есть сообщение о завершении теста!** 4. Внимательно ознакомьтесь с описанием функционального поведения сигналов `interrupt_controller`, а также его структурной схемой. В случае возникновения вопросов, проконсультируйтесь с преподавателем. 5. Реализуйте модуль `interrupt_controller`. Для этого: 1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemVerilog`-файл `interrupt_controller.sv`. 2. Опишите в нем модуль `interrupt_controller` с таким же именем и портами, как указано в [задании](#задание). 6. После описания модуля, его необходимо проверить с помощью тестового окружения. 1. Тестовое окружение находится [здесь](tb_irq.sv). 2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md). 3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (`tb_irq`). 4. **По завершению симуляции убедитесь, что в логе есть сообщение о завершении теста!** ## Список использованной литературы 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. [Pillai, V.P., Megalingam, R.K. (2022). System Partitioning with Virtualization for Federated and Distributed Machine Learning on Critical IoT Edge Systems. In: Saraswat, M., Sharma, H., Balachandran, K., Kim, J.H., Bansal, J.C. (eds) Congress on Intelligent Systems. Lecture Notes on Data Engineering and Communications Technologies, vol 111. Springer, Singapore.](https://doi.org/10.1007/978-981-16-9113-3_33) 6. [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)