Сегодня речь пойдет о модном - о RISС-V микроконтроллере. Я давно хотел познакомится с этим ядром и ждал когда появится что-то похожее на STM32 и вот дождался, встречайте - китайский GigaDevice - GD32V.
Инфраструктура для этого микроконтроллера не такая обширная как для STM32, но есть вне необходмое для того, чтобы начать с ним работать. Благо отладочные платы платы можно заказать на аликекспресс, например, вот тут: Longan Nano GD32VF103CBT6 RISC-V MCU
Китайцы продвигают для этого микроконтроллера среду разработку Platform IO, которую можно поставить как Plugin под Visual Studio Code. Но это как то не по инженерным понятиям, мы же инжерены и хотим разобраться сами с нуля, поэтому давайте попробуем запустить плату на IAR, написав все сами с нуля.
Кстати, IAR раздает отладочный комплект (отладочная плата + отладчик I-JET + 30 Дней полная лицензия) IAR RISC-V GD32V Evaluation kit. Вот тут можно оставить заявку Request for Development Tools. Не уверен, что посылает всем желающим, но нам прислал. Спасибо им за это.
Ну что же, кто заинтересовал, добро пожаловать под кат
Вообще я хотел портировать очень просто планировщик из статьи Переключение контекста и простой вытесняющий планировщик для CortexM, но из-за найденной мною ошибки в компиляторе IAR - на прямую этого сделать не удалось, пришлось немного переделать стратегию определения задач, но и вообще материала получалось много. Да и вообще когда я только сел разбираться, думал, что разберусь за пару часов, но открыв документацию, которая иногда противоречива, понял, что парой часов тут не обойтись и первое впечатление от RISC-V было вот прямо как у Джерими.
Но чуть позже я привык и даже проникся, а потому попытаюсь донести, что же я понял, и что такое китайский RISC-V.
Нет смысла описывать все детали архитектуры RISC-V, я ограничусь только необходимым минимумом для того, чтобы понять, как работает микроконтроллер GD32VF103 и как правильно поморгать светодиодом. Материала по RISCV на русском не так много (вот есть обзор Создание процессора со свободной архитектурой RISC-V), поэтому, как обычно, начнем с азов. Итак, поехали.
-
Описание ISA RISC-V с официального сайта risc-v организации:
-
Ядро микроконтроллера сделано на ядре Bumblebee, которое в свою очередь использует архитектуру Nuclei processor core.
-
Есть общие библиотеки ядра Nucleo RISC-V на Си, например можно посмотреть https://github .com/IARSystems/iar-risc-v-gd32v-eval/tree/master/Firmware/RISCV[здесь в репозитарии IAR] или тут в оригинале n200 drivers
-
Есть библиотека периферии от самой GigaByte, например можно взять тут же в репозитарии IAR или на сайте производителя Библиотека от производителя
-
Так же есть порты операционных систем FreeRTOS и uCOS II n200 sdk
Но, мы попробуем написать код на С++ без всех этих библиотек - все с нуля, пользуясь только документацией. Задача будет такая:
Моргать двумя светодиодами от раз в 100 и 200мс от прерывания таймера.
RISC-V это уникальная архитектура, поэтому все определения и понятия тут свои, основные понятия, которые будут постоянно встречаться на сложном пути изучения RISC-V, я приведу ниже
-
Аппаратный поток(hart) - архитектура поддерживает многопоточность, поэтому может быть несколько потоков исполнения кода. Под потоком (hart) подразумевается аппаратные потоки. Микроконтроллер как миниму должен иметь один поток (hart) с ID равным 0.
-
Trap - ловушка или обработчик. Ловушки бывают нескольких типов.
-
Ловушка исключения (exception) - собственно как и в CortecM архитектуре - это понятие означает синхронное событие, которое прерывает исполнения кода. Исключение может прерываться другим исключением, или NMI.
-
Ловушка прерывания (interrupt) - внешнее асинхронное событие, которое может привести к тому, что поток неожиданно может передать управление. Прерывание может прерываться другим прерыванием, NMI, или исключением.
-
Ловушка NMI - немаскируемое прерывание. NMI не может прерываться другим NMI, но может перейти из обработчика NMI в режим обработки исключения, если в момент обработки NMI произойдет исключение. В нашем микроконтроллере, например, отказ высокоскоростного кварцевого генератора, заведен на немаскируемое прерывание.
-
-
Машинный режим - самый высокоприоритетный режим, который должны поддерживать всеми RISC-V ядрами. Соответственно, все что связано со словом машинный должно поддерживается на уровне ядра.
Для начала, немного википедии:
RISC-V (риск-пять) — открытая и свободная система команд (ISA — Instruction Set Architecture) и процессорная архитектура на основе концепции RISC для микропроцессоров и микроконтроллеров. Спецификация доступна для свободного и бесплатного использования, включая коммерческие реализации непосредственно в кремнии или конфигурировании ПЛИС. Имеет встроенные возможности для расширения списка команд и подходит для широкого круга применений.
На данный момент, в архитектуре разделяются следующие наборы команд:
| Сокращение | Наименование | Версия | Статус |
|---|---|---|---|
Базовые наборы |
|||
RV32I |
32-битный базовый набор с целочисленными операциями с 32 регистрами общего назначения |
2.1 |
Ratified |
RV32E |
32-битный базовый набор с целочисленными операциями для встраиваемых систем с 16 регистрами общего назначения |
1.9 |
Draft |
RV64I |
64-битный базовый набор с целочисленными операциями с 32 регистрами общего назначения |
2.1 |
Ratified |
RV128I |
128-битный базовый набор с целочисленными операциями |
1.7 |
Draft |
Стандартные расширеные наборы |
|||
M |
Целочисленное умножение и деление (Integer Multiplication and Division) |
2.0 |
Ratified |
A |
Атомарные операции (Atomic Instructions) |
2.1 |
Ratified |
F |
Арифметические операции с плавающей запятой над числами одинарной точности (Single-Precision Floating-Point) |
2.2 |
Ratified |
D |
Арифметические операции с плавающей запятой над числами двойной точности (Double-Precision Floating-Point) |
2.2 |
Ratified |
G |
Сокращеное обозначение для комплекта из базового и стандартного наборов команд |
н/д |
н/д |
Q |
Арифметические операции с плавающей запятой над числами четвертной точности |
2.2 |
Ratified |
L |
Арифметические операции над числами с фиксированной запятой (Decimal Floating-Point) |
0.0 |
Open |
C |
Сокращённые имена для команд (Compressed Instructions) |
2.2 |
Ratified |
B |
Битовые операции (Bit Manipulation) |
0.36 |
Open |
J |
Двоичная трансляция и поддержка динамической компиляции (Dynamically Translated Languages) |
0.0 |
Open |
T |
Транзакционная память (Transactional Memory) |
0.0 |
Open |
P |
Короткие SIMD-операции (Packed-SIMD Instructions) |
0.1 |
Open |
V |
Векторные расширения (Vector Operations) |
0.2 |
Open |
N |
Инструкции прерывания (User-Level Interrupts) |
1.1 |
Open |
Табличка из википедии довольно здоровая. На самом деле для микроконтроллеров общего назначения используется в основном 32 битная архитектура с небольшим количеством расширений, например:
Ядро:
-
РВ32Е: архитектура 32бит с 16 регистрами общего назначения
-
Архитектура RV32I: 32bits с 32 регистрами общего назначения
Расширения:
-
M: целочисленные инструкции по умножению и делению
-
C: сжатые до 16 бит инструкции для уменьшения размера кода
-
А: Атомарные Инструкции
-
F: Инструкции С Плавающей Запятой Одиночной Точности
-
D: Инструкции С Плавающей Запятой Двойной Точности
Существует две спецификации набора инструкций:
-
Непривилегированный набор инструкций
-
Привилегированный набор инструкций
В микроконтроллере используется оба набора.
Спецификация на этот набор описывает архитектуру, т.е. инструкции и функциональность которые обычно используются во всех режимах привилегий, т.е. общие для всех архитектур набор инструкций и функций. Спецификация на этот набор доступна здесь: Непривилегированный ISA
Пара слов и привилегированной архитектуре. Основное её назначение - это разделение уровня приложений и уровня ядра, а также поддержка операционных систем в плоть до нескольких разных операционных систем типа Linux, работающих через виртуальную машину.
Но нас это не особо беспокоит, у нас небольшой микроконтроллер, который из всего этого дела использует самую простую форму привилегированности
Спецификация на привилегированный набор описывает возможную архитектуру привилегированных режимов, в том числе специальные инструкции и дополнительную функциональность для этого режима. Спецификация доступна здесь: Привилегированная ISA
Следует уточнить, что эта спецификация носит рекомендованный характер, и она описывает только одно из возможных решений. Основное её преимущество, в том, что привилегированная архитектура никак не задевает основную непривилегированную функциональность и является расширением.
RISC-V имеет 32 регистра x0-x31. Но обычно к ним обращаются через ABI имена.
- Рабочие регистры
-
Регистры t0-t6(x5-x7, x28-x31) и a0-a7(x10-x11, x12-x17), а также регистр адреса возврата являются рабочими регистрами. Любая функция может изменять содержимое этих регистров и если ей нужно воспользоваться какими-то из этих регистров после вызова другой функции, она должна сохранить их значение на стеке.
- Сохраняемы регистры
-
Регистры s0-s11 (x8, x9, x18-x27 ) должны сохраняться вызываемой функцией на стеке (если функция хочет их использовать) перед входом в функцию и восстанавливаться перед выходом, .
Далее табличка из интернета, описывающая каждый регистр, не стал переводить, и так все понятно:
| Register | ABI Name | Description | Saver |
|---|---|---|---|
x0 |
zero |
Hard-wired zero |
— |
x1 |
ra |
Return address |
Caller |
x2 |
sp |
Stack pointer |
Callee |
x3 |
gp |
Global pointer |
— |
x4 |
tp |
Thread pointer |
— |
x5 |
t0 |
Temporary/alternate link register |
Caller |
x6–7 |
t1–2 |
Temporaries |
Caller |
x8 |
s0/fp |
Saved register/frame pointer |
Callee |
x9 |
s1 |
Saved register |
Callee |
x10–11 |
a0–1 |
Function arguments/return values |
Caller |
x12–17 |
a2–7 |
Function arguments |
Caller |
x18–27 |
s2–11 |
Saved registers |
Callee |
x28–31 |
t3–6 |
Temporaries |
Caller |
pc |
pc |
Program counter |
А вот теперь моя вольная интерпретация регистров.
- x0/zero
-
Регистр хранит всегда 0 и может использоваться в некоторых командах доступа к регистрам CSR, например, в команде CSRRS (Atomic Read and Set Bits in CSR), при использовании регистра x0 как источника маски, команда будет атомарно только читать CSR регистр без его модификации, если вы захотите использовать другой регистр в котором хранится ноль, то команда все равно произведет запись в регистр CSR, поэтому если хотите только прочитать биты, то нужно использовать zero. .
- x1/ra
-
(Link register или Return Address регистр). Регистр содержащий адрес возврата из функции. Этот регистр может использоваться как рабочий регистр в функции, поэтому при входе в функцию он должен быть сохранен, а при выходе, перед вызовом инструкции ret, восстановлен.
- x2/sp
-
Указатель стека.
- x3/gp
-
(The global pointer register). Глобальный регистр указателей (gp/x3) используется для эффективного доступа к памяти в пределах области в 4 Кбайта.
Компоновщик сравнивает значение адресов памяти со значением которым должен быть проинициализирован gp, и если оно находится в пределах диапазона 4 кбайта, заменяет абсолютную/pc-относительную адресацию на gp-относительную адресацию, что делает код более эффективным. Этот процесс также называется короткой памятью.
Область 4K может находиться в любом месте памяти, но для того, чтобы оптимизация была эффективной, она должна предпочтительно охватывать наиболее интенсивно используемую область оперативной памяти. Поэтому обычно в настройках компоновщика для инициализации этого указателя используют адрес на начало сегмента глобальных и статических данных.
- x4/tp
-
(The thread pointer). Указатель потока. Этот регистр используется для реализации механизма Локального хранилища потока (Thread Local Storage (TLS)), например при реализации спецификатора класса thread_local в С++.
В RISCV архитектуре существует 3 уровня привилегий. Уровни привилегий используются для обеспечения защиты между различными компонентами программного обеспечения (например, пользовательским приложением и ядром операционной системы). Любые попытки выполнения операций, не разрешенных текущим режимом привилегий, вызовут исключение. Ниже показаны значения режима привилегий:
| Уровень | Код режима | Имя | Сокращенное название | Описание |
|---|---|---|---|---|
0 |
00 |
User/Application |
U |
Самый низкий уровень привилегий |
1 |
01 |
Supervisor |
S |
2 |
10 |
Reserved |
3 |
11 |
| Поддерживаемые режимы | Предполагаемое использование |
|---|---|
M |
Системы со встроенным ПО |
M, U |
Защищенноые системы со встроенным ПО и операционными системами реального времени |
M,S,U |
Системы с Unix подобными операционными системами |
Как видно, для микроконтроллеров, таких как GD32VF103 рекомендованы режимы M и U. В данном случае, если микроконтроллер работает в режиме пользовательский режим U, то ему не доступны настройки и доступ к регистрам ядра, например, таким как mtvt, mepc… о них немного ниже речь пойдет. И чтобы обратиться к ним, вам необходимо зайти в ловушку. Т.е. любое прерывание машинного уровня переводит ядро в машинный режим M и уже внутри него можно обращаться к машинным регистрам. В общем случае алгоритм доступа к машинным регистрам из пользовательского режима выглядит следующим образом - вам надо вызывать инструкцию ecall запрос среды исполнения, которая собственно переведт микроконтроллер в машинный режим и вызовет обработчик ловушки, в котором вы можете поменять машинные регистры в соотвествии с запросом. Переход же из машинного режима в пользовательский происходит после команды mret. Точнее она переходит в тот режим, который укзана в другом регистре
Забегая вперед скажу, что хотя микроконтроллер и поддерживает два режима, после сбора он находится в машинном режиме, и переводить в пользовательский режим мы его не будет, чтобы не нагружать статью.
Следуя стандарту привилегированной архитектуры RISC-V, процессор ядер основная поддержка следующих режимов привилегий:
-
Режим Машины
-
Режим супервизора
-
Пользовательский режим
- Примечание
-
Согласно стандартной привилегированной архитектуре RISC-V, мы никак не можем проверить текущий привилегированный режим (например, машинный режим или режим пользователя).
Можно более подробно теперь рассмотреть наш микроконтроллер.
Пусть вас не смущает буква F в названии микроконтроллера GD32VF103 - это просто маркетинговое название, чтобы было похоже на уже существующую линейку GD32F103, на ядре CortexM3 и никакой поддержки инструкций с плавающей точкой здесь нет. Наверное ставка была на то, что кто-то спутает GD32F103 с ST32F103 и не заметит подвоха… а затем еще спутает и GD32VF103 c GD32F103 :). В общем это в стиле китайцев.
Этот микроконтроллер построен на архитектуре RV32IMAC - что очевидно идентифицирует микроконтроллер как RISC-V 32 битная архитктера с 32 битными регистрами общего назначения, который имеет целочисленные инструкции умножения и атомарные инструкции, инструкции сжаты до 16 бит для уменьшения размера кода.
Данный микроконтроллер может использоваться в защищенных системах, для которых достаточно только два режима:
-
Машинный Режим (Machine Mode), повторюсь, режим который имеет наивысший уровень привилегий и который является обязательным.
-
Пользовательский режим (User Mode), который можно конфигурировать.
Как я уже говорил выше, привилегированная спецификация это не панацея и производители могут добавлять и даже изменять архитектуру. В данном случае, ребята добавили несколько подрежимов Машинного режима.
Существует 4 подрежима:
-
Нормальный подрежим (Normal Mode - 0x0):
-
Ядро будет находиться в этом подрежиме после сброса и работать в нем до тех пора пока не произойдет прерывание, немаскируемое прерывание (NMI) или исключение.
-
-
Подрежим обработки исключения (Exception Handling Mode - 0x2):
-
Ядро находится в этом режиме когда оно обрабатывает исключение.
-
-
Подрежим обработки немаскируемого прерывания (NMI Handling Mode - 0x3):
-
Ядро находится в этом подрежиме когда оно обрабатывает немаскируемое прерывание NMI.
-
-
Подрежим обработки прерывания (Interrupt Handling Mode - 0x1):
-
Ядро находится в этом подрежиме когда оно обрабатывает прерывание.
-
Эти подрежимы можно узнать из поля TYP регистра msumbm
По умолчанию после сброса ядро находится в машинном режиме в подрежиме 0 (Нормалный подрежим работы) и вообще для большинства применений этого и достаточно, потому как у нас есть полный доступ ко всем регистрам и пользовательским и машинным. Собственно, в моем примере я буду использовать только такой режим, но если мы сильно хотим ограничить пользователя от настроек ядра, например, запретить пользователю изменять машинные регистры из задач операционной системы, то мы всегда можем перейти в режим пользователя. Для этого необходимо просто выполнить инструкцию mret - возврат из машинного режима.
Я тут уже вскользь упомянул регистры msumbm, mtvt …, так что это за регистры?
Эти регистры встроены в ядро микроконтроллера, поэтому доступ к ним можно осуществить только с помощью специальных команд ассемблера, например cssr или csrr.
Это конечно не хорошо, так как моя обертка над регистрами не подходит для доступа к регистрам ядра, так как доступ к регистрам ядра осуществляется особым образом через команду csrr и чтобы не трогать уже написанную обертку и генератор, я сделал отдельный класс для их обработки. На пользователей это никак не повлияло, а я получил возможность удобно обращаться к таким регистрам. Суть класса таже самая - только вместо прямого чтения, все сделано на ассемблере встроенных в Iar функции доступа к CSR регистрам.
Пример доступа к специальному регистру на ассемблере
#define read_csr(reg) ({ unsigned long __tmp; \
asm volatile ("csrr %0, " #reg : "=r"(__tmp)); \
__tmp; })Регистров целая куча, есть регистры, которые обязательны в соответствии со спецификацией, а есть уже добавленные производителем. Под спойлером описание всех регистров статуса и управления нашего микроконтроллера.
| Тип | Адрес | Доступ | Имя | Описание |
|---|---|---|---|---|
Стандартные регистры машинного режима, соответствующие спецификации привилегированной архитектуры RISC-V CSR (Machine Mode) |
0xF11 |
MRO |
mvendorid |
(Machine Vendor ID Register) Регистр содержащий код провайдера ядра, который выдается JEDEC ассоциацией |
0xF12 |
MRO |
marchid |
(Machine Microacrhitecture ID Register) Идентификатор микроархитектуры ядра |
|
0xF13 |
MRO |
mimpid |
(Machine Implementation ID Register) Идентификатор номера версии реализации ядра. |
|
0xF14 |
MRO |
mhartid |
(Hart ID Register) Идентификатор аппаратного потока, который выполняет код. |
|
0x300 |
MRW |
mstatus |
(Machine Status Register) Регистр содержит текущее состояние и управляет текущим состоянием аппаратного потока |
|
0x301 |
MRO |
misa |
(Machine ISA Register) Идентификатор набора команд, собственно в нем закодирован поддерживаемый набор команд |
|
0x304 |
MRW |
mie |
(Machine Interrupt Enable Register) Регистр отвечает за включение прерываний при использовании PLIC (platform-level interrupt controller) |
|
0x305 |
MRW |
mtvec |
(Machine Trap-Vector Base-Address Register) Регистр содержит конфигурацию обработчика(ловушки) исключений, которая состоит из адреса этого обработчика и векторного режима. |
|
0x307 |
MRW |
mtvt |
(ECLIC Interrupt Vector Table Base Address) Регистр содержит базовый адрес вектора прерываний для ECLIC контроллера. На самом деле спецификация на контроллер прерываний еще не утверждена, поэтому это не совсем стандартный регистр. |
|
0x340 |
MRW |
mscratch |
(Machine Scratch Register) Назовем его регистр-записная книжка обеспечивает механизм сохранения и восстановления специфических данных для ограничения доступа к данным более высокого уровня привилегий из низкого уровня привилегий. Например, после входа в режим прерывания или обработки исключений регистр указателя стека приложения (SP) временно сохраняется в регистре mscratch. Перед выходом из обработчика исключений значение в регистре-записная книжка используется для восстановления регистра указателя стека (SP). Программное обеспечение может получить доступ к этому регистру только и машинного режима. |
|
0x341 |
MRW |
mepc |
(Machine Exception Program Counter) Регистр, который содержит в себе адрес инструкции, которая была прервана исключением или прерыванием. Регистр может быть явно изменен программой в машинном режиме. Младший бит этого регистра всегда равен 0. |
|
0x342 |
MRW |
mcause |
(Machine Cause Register) Этот регистр индицирует событие, которе стало причиной исключения. |
|
0x343 |
MRW |
mtval |
(Machine Trap Value Register). Регистр содержащий специфическую информацию, чтобы помочь с обработкой исключения, например, может хранить код инструкции вызвавшей исключение или адрес в котором произошла ошибка. |
|
0x344 |
MRW |
mip |
(Machine Interrupt Pending Register). Содержит информацию об ожидающих прерываниях. Но в нашем контроллере он не используется поэтому его значение всегда 0. |
|
Ox345 |
MRW |
mnxti |
(Next Interrupt Handler Address and Interrupt-Enable CSR) Регистр содержащий адрес следующего обработчика прерываний. Может использоваться программным обеспечением для обработки следующего прерывания, когда оно находится в том же режиме привилегий, без очистки конвейера прерываний и затрат на сохранения/восстановления контекста. Тоже регистр из неутвержденной спецификации на контроллер прерываний |
|
0x346 |
MRO |
mintstatus |
(Current Interrupt Levels). Регистр содержащий уровень активного прерывания в машинном режиме. Регистр из неутвержденной спецификации на контроллер прерываний |
|
0x348 |
MRW |
mscratchcsw |
(Scratch swap register for privileged mode). Этот регистр используется для того, чтобы выполнить обмен значения хранящиеся в одном из регистров ядра с регистром mscratch (например для обмена значений указателя на стек SP и mscratch). Используется при входе в прерывание и смене режима привилегий для разграничения доступа к данным между уровнями привилегий. Регистр из неутвержденной спецификации на контроллер прерываний |
|
0x348 |
MRW |
mscratchcswl |
(Scratch swap register for interrupt levels). Этот регистр также используется для обмена значений между регистром ядра и регистром mscratch, но в случае когда уровень привилегий не меняется. В частности он используется для ускорения обработки прерывания при переключении между несколькими уровнями прерываний. Регистр из неутвержденной спецификации на контроллер прерываний |
|
0xB00 |
MRW |
mcycle |
(Lower 32 bits of Cycle counter). Младшие 32 бита счетчика циклов |
|
0xB80 |
MRW |
mcycleh |
(Upper 32 bits of Cycle counter). Старшие 32 бита счетчика циклов |
|
0xB02 |
MRW |
minstret |
(Lower 32 bits of Instructions-retired counter). Младшие 32 бита счетчика успешно выполненных инструкций. |
|
0xB82 |
MRW |
minstreth |
(Lower 32 bits of Instructions-retired counter). Старшие 32 бита счетчика успешно выполненных инструкций. |
|
Стандартные регистры пользовательского режима. RISC-V Standard CSR (User Mode). |
0xC00 |
URO |
cycle |
Копия регистра mсycle, для чтения из пользовательского режима |
0xC01 |
URO |
time |
Копия регистра mtime, содержащий младшие 32 бита счетчика машинного таймера. |
|
0xC02 |
URO |
instret |
Копия регистра minstret, для чтения из пользовательского режима |
|
0xC80 |
URO |
cycleh |
Копия регистра mcycleh, для чтения из пользовательского режима. |
|
0xC81 |
URO |
timeh |
Копия регистра mtimeh, содержащий старшие 32 бита счетчика машинного таймера.. |
|
0xC82 |
URO |
instreth |
Копия регистра minstreth, для чтения из пользовательского режима |
|
0x810 |
MRW |
wfe |
Customized register used to control the WFE mode. |
|
Специализированные регистры ядра Bumblebee. Bumblebee Customized CSR |
0x320 |
MRW |
mcountinhibit |
(Customized register for counters on & off). Регистр для управления включением отключением подсчета тактов (регистр mcycle) и количества успешных команд (minstret). |
0x7c3 |
MRO |
mnvec |
(NMI Entry Address). Адрес обработчика NMI. |
|
0x7c4 |
MRW |
msubm |
(Customized Register Storing Type of Trap). Регистр хранит тип текущей ловушки и ловушки до входа в текущую ловушку. |
|
0x7d0 |
MRW |
mmisc_ctl |
(Customized Register holding NMI Handler Entry Address). Адрес обработчика прерываний NMI. |
|
0x7d6 |
MRW |
msavestatus |
(Customized Register holding the value of mstatus). Регистр хранит значения регистров mstatus и msubm, что гарантирует, что эти регистры не будут сброшены исключением или NMI. |
|
0x7d7 |
MRW |
msaveepc1 |
(Customized Register holding the value of mepc for the first-level preempted NMI or Exception). Регистр хранит значение регистра mepc. |
|
0x7d8 |
MRW |
msavecause1 |
(Customized Register holding the value of mcause for the first-level preempted NMI or Exception). Регистр хранит значение регистра mcause. |
|
0x7d9 |
MRW |
msaveepc2 |
(Customized Register holding the value of mepc for the second-level preempted NMI or Exception). Регистр хранит значение регистра mepc. |
|
0x7eb |
MRW |
pushmsubm |
(Push msubm to stack). Вспомогательный регистр, обеспечивает метод сохранения регистра msubm в стеке. |
|
0x7ec |
MRW |
mtvt2 |
(ECLIC non-vectored interrupt handler address register). Регистр хранит адрес единого обработчика прерывания в режиме не-векторной обработки. |
|
0x7ed |
MRW |
jalmnxti |
(Jumping to next interrupt handler address and interrupt-enable register). Вспомогательный регистр, используется для того, чтобы уменьшить задержки прерываний и скроить обработку цепочки последовательно происходящих прерываний. |
|
0x7ee |
MRW |
pushmcause |
(Push mcause to stack). Вспомогательный регистр, обеспечивает метод сохранения регистра mcause в стеке. |
|
0x7ef |
MRW |
pushmepc |
(Push mepc to stack). Вспомогательный регистр, обеспечивает метод сохранения регистра mepc в стеке. |
|
0x810 |
MRW |
wfe |
(Wait for Event Control Register) Регистр настройки способа пробуждения микроконтроллера от прерывания, NMI или от события . |
|
0x811 |
MRW |
sleepvalue |
(WFI Sleep Mode Register). Регистр содержащий настройку режима пониженного энергопотребления |
|
0x812 |
MRW |
txevt |
(Send Event Register). Регистр настройки события |
Регистров много, но так все они нам не нужны, мы ограничимся только теми, что нужны для решения нашей задачи. Напомню её на всякий случай - поморгать светодиодом.
Регистр указывающий причину возникновения прерывания.
Полн |
Биты |
Описание |
INTERRUPT |
31 |
Тип ловушки: 0: Исключение или NMI 1: Прерывание |
MINHV |
30 |
Указывает на, что микроконтроллер находится состоянии чтения таблицы векторов прерываний. Это поле доступно только в при работе ECLIC контроллера. |
MPP |
29:28 |
Режим привилегий до входа в прерывание: 00: Привилегии пользователя, 01: Привилегии супервизора, 10: Зарезервировано, 11: Режим машинных привилегий |
MPIE |
27 |
При работе PLIC, ядро нашего процессора устанавливает бит MIE в 0, когда начинает обрабатывает ловушку, поэтому при выходе из обработчика необходимо восстановить его значение в 1. Это делается автоматически используя MPIE бит, который показывает были ли разрешены прерывания до входа в обработчик и восстанавливает это значение после выхода из обработчика |
Reserved |
26:24 |
Reserved 0 |
MPIL |
23:16 |
Уровень прерывания до входа в обработчик прерывания |
Reserved |
15:12 |
Reserved 0 |
EXCCODE |
11:0 |
Номер(ID) прерывания |
Регистр хранящий адрес общего обработчика прерываний в не-векторном режиме при работе ECLIC контроллера.
| Поле | Биты | Описание |
|---|---|---|
CMMON-CODE-ENTRY |
31:2 |
Когда mtvt2.MTVT2EN=1, это поле определяет адрес общего обработчика в не-векторном режиме ECLIC контроллера. |
Зарезервировано |
1 |
Значение 0 |
MTVT2EN |
0 |
Бит задействования mtvt2. Если он равен 0: то адрес общего обработчика прерывания в не-векторном режиме ECLIC контроллера определяется регистром mtvec. Если он равен 1: то адрес общего обработчика прерывания в не-векторном режиме ECLIC контроллера определяется регистром полем mtvt2.CMMON-CODE-ENTRY |
Специализированный регистр ядра Bumblebee, хранящий текущий машинный подрежим и подрежим, в которой было ядро перед входом в текущую ловушку.
| Поле | Бит | Описание |
|---|---|---|
Зарезервировано |
31:10 |
Все биты установлены в 0 |
PTYP |
9:8 |
Машинный подрежим перед входом в ловушку. 0: Нормальный Машинный режим, 1: Подрежим обработки прерываний 2: Подрежим обработки исключения 3: Подрежим обработки NMI |
TYP |
7:6 |
Текущий машинный подрежим. 0: Нормальный Машинный режим, 1: Подрежим обработки прерываний 2: Подрежим обработки исключения 3: Подрежим обработки NMI |
Зарезервировано |
5:0 |
Все биты установлены в 0 |
Регистр mstatus отслеживает и управляет текущим рабочим состоянием аппаратного потока (hart)
| Поле | Бит | Описание |
|---|---|---|
SD |
31:31 |
Бит SD - это бит только для чтения, который служит для того, чтобы определить сигнализирует ли поле FS или поле XS о наличии Dirty состояния, которое потребует сохранения контекста расширений микроконтроллера в памяти. По сути этот бит определяется следующей логической операцией: SD = (FS == 11 or (DS == 11)). SD можно проверить при переключения контекста, чтобы быстро определить, требуется ли сохранение или восстановление состояния в блоке FPU или дополнительных расширений |
XS |
16:15 |
Бит XS кодирует состояние пользовательских расширений, включая дополнительные регистры и CSR регистры и используется для снижения затрат на сохранение и восстановление контекста.. 0: - (Off) расширение отключено, любая вызванная инструкция этого расширения вызовет исключение, 1 - (Initial) когда состояние является начальным и имеет некое постоянное значение, 2 - (Clear) соответствующее состояние потенциально отличается от начального значения, но соответствует последнему сохраненному значению контекста. 3 - (Dirty)соответствующее состояние потенциально было изменено с момента последнего сохранения контекста и требуется его сохранение или восстановление. |
FS |
13:14 |
Бит XS кодирует состояние модуля FPU, включая дополнительные регистры(f0-f31) и CSR регистры и используется для снижения затрат на сохранение и восстановление контекста.. 0: - (Off) FPU отключен, любая вызванная инструкция FPU вызовет исключение, 1 - (Initial) когда состояние является начальным и имеет некое постоянное значение, 2 - (Clear) соответствующее состояние потенциально отличается от начального значения, но соответствует последнему сохраненному значению контекста. 3 - (Dirty)соответствующее состояние потенциально было изменено с момента последнего сохранения контекста и требуется его сохранение или восстановление. |
MPP |
11:12 |
Хранит текущий режим привилегий перед входом в ловушку. 0- Пользовательский режим, 1 - Режим Супервизора, 3 - Машинный режим |
MPIE |
7:7 |
Бит значение MIE перед входом в ловушку. Не используется в режиме ECLIC и всегда равен 0. |
MPIE |
7:7 |
Бит значение MIE перед входом в ловушку. Не используется в режиме ECLIC и всегда равен 0. |
MIE |
7:7 |
Глобальное разрешение машинного прерывания 0- Машинные прерывания запрещены. 1- Машинные прерывания разрешены. Не используется в режиме ECLIC и всегда равен 0. |
Регистр содержащий адресс таблицы векторов прерываний. Используется в векторном режиме для определения адреса обработчика ловушки. И если честно я не совсем понял как с ним работать, и как в него писать. Из пользовательского кода он не пишется, и изменить его значение невозможно, но, очевидно, что его значение как-то меняется в зависимости от настроек ECLIC.
Регистр содержит настройку того, чему равно значение регистра mnvec, в котором лежит адрес обработчика ловушки NMI.
Если NMI_CAUSE_FF(бит 9) равен 0, то значение регистра mnvec будет равен адресу содержащемуся по вектору сброса (в нашем контроллере это адрес 0). Если этот бит равен 1, то значение регистра mnvec будет равно значению, лежащему в регистре mtvec, т.е. NMI исключения и прерывания будут обрабатываться через одну ловушку, а номер обработчика будет равен 0xFFF.
Как я уже говорил, существует 3 различных ловушки событий, которые прерывают поток выполнения программы. Эти события разделяются на
-
исключения (синхронные события),
-
NMI(асинхронное немаскируемое событие),
-
прерывания(асинхронные события).
Каждое из таких событий обрабатывается ядром немного по-разному.
Базовый набор исключений ядра Bumblebee нашего микроконтроллера выглядит так:
| Код исключения | Тип Исключения/Прерывания | Синхронное/Асинхронное | Описание |
|---|---|---|---|
0 |
Адрес инструкции не выровнен |
Синхронное(исключение) |
Адрес в PC не выровнен. Это тип исключения не возникает в ядрах с сокращенными командами (С расширение ядра). |
1 |
Ошибка доступа к инструкции |
Синхронное(исключение) |
|
2 |
Недопустимая инструкция |
Синхронное(исключение) |
|
3 |
Точка останова |
Синхронное(исключение) |
Архитектура RISC-V определяет инструкцию BREAK. При выполнении этой инструкции ядро войдет в обработчика исключений. Эта инструкция обычно используется отладчиком, для установки точек останова. |
4 |
Доступ по невыровненному адресу при операции чтения |
Синхронное(исключение) |
Ядро Bumblebee не поддерживает невыровненный доступ к памяти, поэтому доступ к памяти по невыровненным адресам вызовет исключение. |
5 |
Ошибка доступа к памяти при операции чтения |
Асинхронный |
|
6 |
Доступ по невыровненному адресу при операции записи |
Синхронное(исключение) |
Ядро Bumblebee не поддерживает невыровненный доступ к памяти, поэтому доступ к памяти по невыровненным адресам вызовет исключение. |
7 |
Ошибка доступа к памяти при операции записи |
Асинхронный |
|
8 |
Вызов окружения (команды ecall) из Пользовательского режима |
Синхронное(исключение) |
RISC-V архитектура определяет инструкцию ECALL. При выполнении этой инструкции ядро войдет в обработчика исключений. Эта инструкция обычно используется программным обеспечением для принудительного перехода ядра в режим обработки исключений. |
11 |
Вызов окружения (команды ecall) из машинного режима |
Синхронное(исключение) |
RISC-V архитектура определяет инструкцию ECALL. При выполнении этой инструкции ядро войдет в обработчика исключений. Эта инструкция обычно используется программным обеспечением для принудительного перехода ядра в режим обработки исключений. |
Таблица может расширяться прерываниями от периферии в плоть до кода 4095
Далее рассмотрим как обработать прерывания:
As shown in ECLIC Connection (when ECLIC is enabled), the ECLIC unit can support up to 4096 interrupt sources. The ECLIC unit has defined the following concepts for each interrupt source:
ID
IE
IP
Level or Edge-Triggered
Level and Priority
Vector or Non-Vector Mode
These concepts will be detailed at next sections.
Итак, прерывания - это асинхронные события прерывающие поток исполнения. В RISC-V нашего микроконтроллера существует две реализации контроллеров прерывания RISC-V базовый контроллер PCLIC(Platform-Level Interrupt Controller)умолчанию и режим CLIC (Core-Local Interrupt Controller). PLIC описан с привеледже, а драфт версия для CLIC описана тут: https://github.com/riscv/riscv-fast-interrupt/blob/master/clic.adoc
Работа PLIC опирается на регистры mie and mip, которые являются частью RISC-V standard privileged architecture specification. Как говорит руководство на ядро, использование этого контроллера рекомендуется для симметричных многопроцессорных систем или для Операционной системы типы Linux.
А для встроенного ПО и операционных систем реального времени рекомендуется использовать CLIC. Поэтому далее мы будем говорить только про CLIC.
После сброса работает PLIC, поэтому для перехода в работу с CLIC необходимо переключиться в этот режим. Это делается с помощью CSR регистра mtvec в двух его младших битах. По умолчанию они стоят в режиме (00) PLIC в невекторном режиме.
Для перехода в CLIC нужно установить два этих младших бита в 11. На самом деле микроконтроллер GD32VF103 использует ECLIC (расширенный контроллер прерываний) - немного улучшенная версия CLIC, описанного в https://github.com/riscv/riscv-fast-interrupt/blob/master/clic.adoc
В общем вход в ловушку Исключения/Прерывания/NMI происходит практически одинаково и включает в себя следующие шаги, которые выполняются одновременно за один цикл:
-
При входе в ловушку ядро обновляет CSR контрольные регистры
-
mcause
-
mepc
-
mstatus
-
mintstatus для Прерывания или Исключения
-
-
Одновременно ядро переходит в Машинный Привилегированный режим и в нужный подрежим машинного режима
-
В это же время останавливается выполнение текущей программы и PC загружается адрес обработчика ловушки в зависимости от того, какое событие произошло - Исключением, Прерывание или NMI, адрес берется из разных регистров.
Важно, что мы обработчик ловушки находится всегда в Машинном режиме.
На рисунке я показал синим - шаги которые одинаковы для всех видов ловушек и шаги, разными цветами уникальные шаги для входа в различные ловушек.
Нам понадобится это для того, чтобы правильно сделать обработчики прерываний.
Давайте разберемся как же обрабатывать прерывания. Как видно из картинки существует два режима обработки прерываний - векторный, через таблицу векторов и невекторный - через единый обработчик прерывания.
| Смещение | Доступ | Названия | Длина |
|---|---|---|---|
0x0000 |
RW |
cliccfg |
8-bit |
0x0004 |
R |
clicinfo |
|
32-bit |
0x000b |
RW |
mth |
8-bit |
0x1000+4*i |
RW |
clicintip[i] |
8-bit |
0x1001+4*i |
RW |
clicintie[i] |
8-bit |
0x1002+4*i |
RW |
clicintattr[i] |
8-bit |
0x1003+4*i |
RW |
clicintctl[i] |
Регистр общей конфигурации прерываний
| Поле | Биты | Доступ | Значение по умолчанию | Описание |
|---|---|---|---|---|
Зарезервировано |
7 |
R |
N/A |
Зарезервировано, значение 0 |
nmbits |
6:5 |
R |
N/A |
Режим привлегей прерываний. Для нашего микроконтроллера он всегда 0: машинный. |
nlbits |
4:1 |
RW |
0 |
Используется для указания эффективной разрядности значения уровня в регистре clicintctl[i]. Т.е. если в этом регистре стоит значение 4, то при задании уровня в регистре clicintctl[i] можно использовать только 4 старших бита, остальные биты используются для задания приоритета. Обычно используется значение от 2 до 8. |
nvbits |
0 |
R |
N/A |
Для нашего микроконтроллера всегда 1: Поддерживает векторный режим. |
Регистр общей информации о системе прерываний
| Поле | Биты | Разрешение | значение по умолчанию | Описание |
|---|---|---|---|---|
Зарезервировано |
31:25 |
R |
N/A |
Зарезервировано, все значения в 0 |
CLICINTCTLBITS |
24:21 |
R |
N/A |
Эффективная разрядность регистра clicintctl[i]. |
ВЕРСИЯ |
20:13 |
R |
N/A |
Номер версии аппаратной реализации контроллера прерываний. |
NUM_INTERRUPT |
12:0 |
R |
N/A |
Количество источников прерываний, поддерживаемых микроконтроллером. |
Регистр, который задает уровень срабатывания прерывания. Как видно из картинки, можно управлять не только приоритетом прерывания, но и уровнем прерывания и делать, что-то типа прореживания, мол если уровень прерывания ниже определенной границы, то это и не прерывание вовсе и не нужно его возводить.
Сам уровнеь контректного прерывания, как было сказано задается clicintctl[i], он может выглядеть так.
А вот уровень сравнения - как раз задается регистром mth. Собственно это просто регистр 8 битный регистр хранящий уровень срабатывания прерывания.
Регистр содержащий единственный флаг запроса прерывания. i - обозначает номер прерывания. Наш контроллер содержит 86 прерываний, поэтому будет 86 таких регистров.
| Поле | Биты | Разрешение | значение по умолчанию | Описание |
|---|---|---|---|---|
Зарезервировано |
7:1 |
RO |
N/A |
Зарезервировано, все значения в 0 |
IP |
0 |
RW |
0 |
Флаг ожидания источника прерывания. 1 - означает что прерывание сработало. Если контроллер настроен на работу с прерываниями по уровню, то программно его очистить нельзя. Он будет очищен автоматически когда будет очистен исходный источник прерывания. |
Регистр разрешения прерываний.
| Поле | Биты | Разрешение | значение по умолчанию | Описание |
|---|---|---|---|---|
Зарезервировано |
7:1 |
RO |
N/A |
Зарезервировано, все значения в 0 |
IE |
0 |
RW |
0 |
1 - означает что прерывание разрешено |
Регистр настройки источника прерываний. Как было показано выше, контроллер прерываний может работать в несокльких режимах. Прерывания могут срабатывать по уровню, по фронту переднем или заднему, а также тип прерывания векторный или не векторный.
| Поле | Биты | Разрешение | По умолчанию | Описание |
|---|---|---|---|---|
Зарезервировано |
7:6 |
R |
N/A |
Зарезервировано, значение 11 |
Зарезервировано |
5:3 |
R |
N/A |
Зарезервировано, все значения 0 |
TRIG |
2:1 |
RW |
0 |
00 и 10: Прерывание срабатывает по уровню. 01: Прерывание срабатывает по положительному фронту. 11: Прерывание срабатывает по отрицательному фронту. |
SHV |
0 |
RW |
0 |
0: Прерывание обрабатывается в невекторном режиме. 1: Прерывание обрабатывается в векторном режиме. |
Регистр используется для задания уровня и приоритета прерывания. Как было показано выше, старшие биты, количество которых задается в регистре CLICCFG указывают уровень прерывания, а младшие - приоритет. Количество эффективных битов указано в регистре CLICINFO в поле CLICINTCTLBITS.
Контроллер прерываний ECLIC позволяет выбрать режим обработки прерываний и обеспечивает гибкость для выбора поведения каждого отдельного прерывания - либо с использованием аппаратной векторизации, либо без неё. В результате это позволяет пользователям оптимизировать каждое прерывание и пользоваться преимуществом обоих видов поведения. Аппаратная векторизация имеет более быстрый механизм обработки прерывания, но как имеет больший объем кода (из-за сохранения и восстановления контекста для каждого из прерываний). Напротив невекторный режим имеет преимущество в размере кода, так как используется только один обработчик всех прерываний, но обработка происходит медленее.
Итак, по умолчанию все прерывания настроены в невекторный режим. Т.е. для обработки прерывания существует только один обработчик ловушки.
Тип обработки прерывания указывается в регистре CLICINTATTR[i] в поле SHV. По умолчанию там записан 0 - в этом случае прерывание настроено на невекторный режим, т.е. при возниковении прерывания контроллер всегда вызывает единый обработчик, находящийся по адресу, указанному в регистре mtvec (заметим, что это верно для нашего микроконтроллера, так как все его прерывания выполняются в машинном режиме). В этом обработчике необходимо определить, какое прерывание произошло и вызвать небходимую функцию обработки прерывания. УЗнать, что за прерывание произошло с помощью регистра mcause - который хранит в себе номер прерывания в поле EXCCODE.
Контроллер прерываний ECLIC поддерживает работу прерываний в вектроном и невекторном режиме. В этом режиме при возникновении прерывания контроллер прерываний переходит на адрес прерывания, который указан в таблице векторов прерываний (алгоритм работы очень похож на обработку прерываний CortexM).
Ну хватит теории, переходим к практике. и Первое что нужно сделать, настрокить контроллер прерываний. Как я уже говорил выше, работать мы будем с ECLIC, контроллером, и для начала будет использовать невекторный режим обработки прерываний.
В этом режиме у нас один единственный обработчик прерываний, при входе в него нам нужно сохранить все 31 регистр, и еще нужно сохранить регистры mcause, mepc и msubm. А при выходе восстановить все это дело.
Сохранять регистры сами мы не будем, за нас это может сделать компилятор, для этого есть специальный атрибут функции __interrupt, собтвенно когда компилятор его видет, он подставляет пролог и эпилог функции в которых как раз и производится сохранение регистров и при выходе восстановление.
Но если сильно нужно, можно сделать обычные функции и сохранить регистры самостоятельно… эффект будет тот же самый.
__interrupt void NonVectoredInt::IrqEntry()
{
const auto mcause = CSR::MCAUSE::Get(); //__read_csr(_CSR_MCAUSE);
const auto mepc = CSR::MEPC::Get(); //__read_csr(_CSR_MEPC);
const auto msubm = CSRCUSTOM::MSUBM::Get(); //__read_csr(0x7C4);
const auto exceptionCode = mcause & 0xFFF ;
if (exceptionCode != 0xFFF) // Этого не надо делать, если адрес обработчика NMI будет задан отдельно
{
NonVectoredInt::HandleTrap(exceptionCode);
} else
{
DummyModule::HandleInterrupt() ; // Для NMI пока ничего не будем обрабатывать
}
__disable_interrupt();
CSR::MCAUSE::Write(mcause); // __write_csr(_CSR_MCAUSE, mcause);
CSR::MEPC::Write(mepc); //__write_csr(_CSR_MEPC, mepc);
CSRCUSTOM::MSUBM::Write(msubm) ; // __write_csr(0x7C4, msubm);
}Сам функции для обработки прерываний мы положим в одномерный массив
using tInterruptFunction = void(*)() ;
inline constexpr std::array<tInterruptFunction,87> gd_vector_base
{
nullptr,
nullptr,
nullptr,
DummyModule::HandleInterrupt,//eclic_msip_handler,
nullptr,
nullptr,
nullptr,
SystemTimer::HandleInterrupt, //eclic_mtip_handler,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
DummyModule::HandleInterrupt,//eclic_bwei_handler,
DummyModule::HandleInterrupt, //eclic_pmovi_handler,
DummyModule::HandleInterrupt, //WWDGT_IRQHandler,
DummyModule::HandleInterrupt,//LVD_IRQHandler,
....
} ;А теперь вызовем нужное прерывание из этой таблицы
struct NonVectoredInt
{
static void HandleTrap(std::uint32_t interruptId)
{
assert(interruptId < gd_vector_base.size()); // проверим, что код прерывания не больше размера таблицы прерываний
tInterruptFunction fp = gd_vector_base[interruptId];
if (fp != nullptr)
{
fp(); // вызываем нужное превание
}
}
static __interrupt void IrqEntry();
} ;Теперь нужно указать, что NMI будет обрабатываться тем же обработчиком, что и обработчик, адрес которого будет указан в mtvec, а номер обработчика NMI будет 0xFFF
// Устанавливаем указание адреса обработчика NMI через обработчик, адрес которого указан в mtvec. Номер обработчика NMI будет 0xFFF
CSRCUSTOM::MMISC_CTL::NMI_CAUSE_FFF::MnvecIsMtvecNmiIsFFF::Set();Теперь настроим в каком регистре будет указан адрес общего обработчика прерываний
//Настраиваем адрес единого обработчика прерываний. Указываем, что он будет находится в регистре MTVEC
CSRCUSTOM::MTVT2Pack<CSRCUSTOM::MTVT2::MTVT2EN::MtvecIsTrapAddress>::Set() ;
// А если настроить так, то адрес единого обработчика прерываний будет указан в регистре mtvt2, но для обработки NMI тогда нужно отдельно указать адрес в mtvec
//CSRCUSTOM::MTVT2::Write(CSRCUSTOM::MTVT2::MTVT2EN::Mtvt2IsTrapAddress::Value |
// reinterpret_cast<std::uintptr_t>(&NonVectoredInt::IrqEntry));
// Переключаемся на режим работы с ECLIC и устанавливаем адрес обработки Nmi
//CSR::MTVEC::Write(CSR::MTVEC::MODE::Eclic::Value |
// reinterpret_cast<std::uintptr_t>(&NonVectoredInt::NmiEntry));Ну и собственно теперь переключимся в режим работы контроллера ECLIC и укажем адрес единого обработчика в регистре mtvec
// Переключаемся на режим работы с ECLIC и устанавливаем адрес единого обработчика прерываний
CSR::MTVEC::Write(CSR::MTVEC::MODE::Eclic::Value |
reinterpret_cast<std::uintptr_t>(&NonVectoredInt::IrqEntry));Собственно все… контроллер и адреса обработчиков настроены. Полный код
extern "C"
{
int __low_level_init(void)
{
{
CriticalSection cs;
// Устанавливаем указание адреса обработчика NMI через общий обработчик,
// адрес которого указан в mtvec. Номер обработчика NMI будт 0xFFF
CSRCUSTOM::MMISC_CTL::NMI_CAUSE_FFF::MnvecIsMtvecNmiIsFFF::Set();
// Настраиваем адрес единого обработчика прерываний. Указываем, что он
// будет находится в регистре MTVEC
CSRCUSTOM::MTVT2Pack<CSRCUSTOM::MTVT2::MTVT2EN::MtvecIsTrapAddress>::Set() ;
// Переключаемся на режим работы с ECLIC и устанавливаем адрес единого
// обработчика прерываний
CSR::MTVEC::Write(CSR::MTVEC::MODE::Eclic::Value |
reinterpret_cast<std::uintptr_t>(&NonVectoredInt::IrqEntry));
// Включаем подсчет циклов и счетчика инструкций mycycle_minstret
CSRCUSTOM::MCOUNTINHIBITPack<CSRCUSTOM::MCOUNTINHIBIT::IR::MinstretOn,
CSRCUSTOM::MCOUNTINHIBIT::CY::McyclesOn
>::Set();
}
}Теперь нужно настроить машинный таймер и его прерывание.
HXTAL clock monitor (CKM) High speed crystal oscillator (HXTAL) The HXTAL clock monitor function is enabled by the HXTAL Clock Monitor Enable bit, CKMEN, in the Control Register (RCU_CTL). This function should be enabled after the HXTAL start-up delay and disabled when the HXTAL is stopped. Once the HXTAL failure is detected, the HXTAL will be automatically disabled. The HXTAL Clock Stuck interrupt Flag, CKMIF, in the Clock Interrupt Register, RCU_INT, will be set and the HXTAL failure event will be generated. This failure interrupt is connected to the Non-Maskable Interrupt, NMI, of the RISC-V. If the HXTAL is selected as the clock source of CK_SYS, PLL and CK_RTC, the HXTAL failure will force the CK_SYS source to IRC8M, the PLL will be disabled automatically. If the HXTAL is selected as the clock source of PLL, the HXTAL failure will force the PLL closed automatically. If the HXTAL is selected as the clock source of RTC, the HXTAL failure will reset the RTC clock selection.
https://www.susa.net/wordpress/2019/10/longan-nano-gd32vf103/ https://visuale-ru.turbopages.org/s/visuale.ru/blog/sozdanie-protsessora-so-svobodnaya-arkhitekturoj-risc-v-chast-1 https://github.com/riscv/riscv-fast-interrupt/blob/master/clic.adoc#clic-interrupt-attribute-clicintattr
https://doc.nucleisys.com/nuclei_spec/isa/introduction.html https://github.com/nucleisys/Bumblebee_Core_Doc/blob/master/Bumblebee%20Core%20Architecture%20Manual.pdf http://www.gd32mcu.com/data/documents/shujushouce/GD32VF103_User_Manual_EN_V1.2.pdf






