mirror of
https://github.com/MPSU/APS.git
synced 2025-09-16 09:40:10 +00:00
ЛР14. Уточнение методички
This commit is contained in:
@@ -332,6 +332,10 @@ _main_call:
|
|||||||
# Но для простоты реализации оба аргумента всего лишь обнулены.
|
# Но для простоты реализации оба аргумента всего лишь обнулены.
|
||||||
# Это сделано для детерминированного поведения программы в случае,
|
# Это сделано для детерминированного поведения программы в случае,
|
||||||
# если программист будет пытаться использовать эти аргументы.
|
# если программист будет пытаться использовать эти аргументы.
|
||||||
|
|
||||||
|
# Вызов main.
|
||||||
|
# Для того чтобы программа скомпоновалась, где-то должна быть описана
|
||||||
|
# функция именно с таким именем.
|
||||||
call main
|
call main
|
||||||
# Зацикливание после выхода из функции main
|
# Зацикливание после выхода из функции main
|
||||||
_endless_loop:
|
_endless_loop:
|
||||||
@@ -370,34 +374,36 @@ _int_handler:
|
|||||||
la ra, _stack_ptr
|
la ra, _stack_ptr
|
||||||
blt sp, ra, _endless_loop
|
blt sp, ra, _endless_loop
|
||||||
|
|
||||||
sw t0, 12(sp) # Мы перепрыгнули через смещение 8, поскольку там должен
|
sw t0,12(sp) # Мы перепрыгнули через смещение 8, поскольку там должен
|
||||||
# лежать регистр sp, который ранее сохранили в mscratch.
|
# лежать регистр sp, который ранее сохранили в mscratch.
|
||||||
# Мы запишем его на стек чуть позже.
|
# Мы запишем его на стек чуть позже.
|
||||||
sw t1, 16(sp)
|
sw t1,16(sp)
|
||||||
sw t2, 20(sp)
|
sw t2,20(sp)
|
||||||
sw a0, 24(sp)
|
sw a0,24(sp)
|
||||||
sw a1, 28(sp)
|
sw a1,28(sp)
|
||||||
sw a2, 32(sp)
|
sw a2,32(sp)
|
||||||
sw a3, 36(sp)
|
sw a3,36(sp)
|
||||||
sw a4, 40(sp)
|
sw a4,40(sp)
|
||||||
sw a5, 44(sp)
|
sw a5,44(sp)
|
||||||
sw a6, 48(sp)
|
sw a6,48(sp)
|
||||||
sw a7, 52(sp)
|
sw a7,52(sp)
|
||||||
sw t3, 56(sp)
|
sw t3,56(sp)
|
||||||
sw t4, 60(sp)
|
sw t4,60(sp)
|
||||||
sw t5, 64(sp)
|
sw t5,64(sp)
|
||||||
sw t6, 68(sp)
|
sw t6,68(sp)
|
||||||
|
|
||||||
# Кроме того, мы сохраняем состояние регистров прерываний на случай, если
|
# Кроме того, мы сохраняем состояние регистров прерываний на случай, если
|
||||||
# произойдет еще одно прерывание.
|
# произойдет еще одно прерывание.
|
||||||
csrr t0, mscratch
|
csrr t0,mscratch
|
||||||
csrr t1, mepc
|
csrr t1,mepc
|
||||||
csrr a0, mcause
|
csrr a0,mcause
|
||||||
sw t0, 8(sp)
|
sw t0,8(sp)
|
||||||
sw t1, 72(sp)
|
sw t1,72(sp)
|
||||||
sw a0, 76(sp)
|
sw a0,76(sp)
|
||||||
|
|
||||||
# Вызов высокоуровневого обработчика прерываний
|
# Вызов высокоуровневого обработчика прерываний.
|
||||||
|
# Для того чтобы программа скомпоновалась, где-то должна быть описана
|
||||||
|
# функция именно с таким именем.
|
||||||
call int_handler
|
call int_handler
|
||||||
|
|
||||||
# Восстановление контекста. В первую очередь мы хотим восстановить CS-регистры,
|
# Восстановление контекста. В первую очередь мы хотим восстановить CS-регистры,
|
||||||
@@ -405,38 +411,41 @@ _int_handler:
|
|||||||
# вернуть исходное значение указателя стека прерываний. Однако его нынешнее
|
# вернуть исходное значение указателя стека прерываний. Однако его нынешнее
|
||||||
# значение нам еще необходимо для восстановления контекста, поэтому мы
|
# значение нам еще необходимо для восстановления контекста, поэтому мы
|
||||||
# сохраним его в регистр a0, и будем восстанавливаться из него.
|
# сохраним его в регистр a0, и будем восстанавливаться из него.
|
||||||
mv a0, sp
|
mv a0,sp
|
||||||
|
|
||||||
lw t1, 72(a0)
|
lw t1,72(a0)
|
||||||
addi sp, sp, 80
|
addi sp,sp,80
|
||||||
csrw mscratch, sp
|
csrw mscratch,sp
|
||||||
csrw mepc, t1
|
csrw mepc,t1
|
||||||
lw ra, 4(a0)
|
lw ra,4(a0)
|
||||||
lw sp, 8(a0)
|
lw sp,8(a0)
|
||||||
lw t0, 12(a0)
|
lw t0,12(a0)
|
||||||
lw t1, 16(a0)
|
lw t1,16(a0)
|
||||||
lw t2, 20(a0)
|
lw t2,20(a0)
|
||||||
lw a1, 28(a0) # Мы пропустили a0, потому что сейчас он используется в
|
lw a1,28(a0) # Мы пропустили a0, потому что сейчас он используется в
|
||||||
# качестве указателя на верхушку стека и не может быть
|
# качестве указателя на верхушку стека и не может быть
|
||||||
# восстановлен.
|
# восстановлен.
|
||||||
lw a2, 32(a0)
|
lw a2,32(a0)
|
||||||
lw a3, 36(a0)
|
lw a3,36(a0)
|
||||||
lw a4, 40(a0)
|
lw a4,40(a0)
|
||||||
lw a5, 44(a0)
|
lw a5,44(a0)
|
||||||
lw a6, 48(a0)
|
lw a6,48(a0)
|
||||||
lw a7, 52(a0)
|
lw a7,52(a0)
|
||||||
lw t3, 56(a0)
|
lw t3,56(a0)
|
||||||
lw t4, 60(a0)
|
lw t4,60(a0)
|
||||||
lw t5, 64(a0)
|
lw t5,64(a0)
|
||||||
lw t6, 68(a0)
|
lw t6,68(a0)
|
||||||
lw a0, 40(a0)
|
lw a0,40(a0)
|
||||||
|
|
||||||
# Выход из обработчика прерывания
|
# Выход из обработчика прерывания
|
||||||
mret
|
mret
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
_Листинг 2. Пример содержимого файла первичных команд с поясняющими комментариями._
|
_Листинг 2. Пример содержимого файла первичных команд с поясняющими комментариями._
|
||||||
|
|
||||||
|
Обратите внимание на строки `call main` и `call int_handler`. Компоновка объектного файла, полученного после компиляции `startup.S` будет успешной только в том случае, если в других компонуемых файлах будут функции именно с такими именами.
|
||||||
|
|
||||||
## Практика
|
## Практика
|
||||||
|
|
||||||
Для того, чтобы запустить моделирование исполнения программы на вашем процессоре, сперва эту программу необходимо скомпилировать и преобразовать в текстовый файл, которым САПР сможет проинициализировать память процессора. Для компиляции программы, вам потребуется особый компилятор, который называется "кросскомпилятор". Он позволяет компилировать исходный код под архитектуру компьютера, отличную от компьютера, на котором ведется компиляция. В нашем случае, вы будете собирать код под архитектуру `RISC-V` на компьютере с архитектурой `x86_64`.
|
Для того, чтобы запустить моделирование исполнения программы на вашем процессоре, сперва эту программу необходимо скомпилировать и преобразовать в текстовый файл, которым САПР сможет проинициализировать память процессора. Для компиляции программы, вам потребуется особый компилятор, который называется "кросскомпилятор". Он позволяет компилировать исходный код под архитектуру компьютера, отличную от компьютера, на котором ведется компиляция. В нашем случае, вы будете собирать код под архитектуру `RISC-V` на компьютере с архитектурой `x86_64`.
|
||||||
@@ -453,8 +462,8 @@ _Листинг 2. Пример содержимого файла первичн
|
|||||||
|
|
||||||
Вам потребуются следующие флаги компиляции:
|
Вам потребуются следующие флаги компиляции:
|
||||||
|
|
||||||
* `-march=rv32i_zicsr` — указание разрядности и набора расширений в архитектуре, под которую идет компиляция (у нас процессор rv32i с расширением инструкциями для взаимодействия с регистрами контроля и статуса Zicsr)
|
- `-march=rv32i_zicsr` — указание разрядности и набора расширений в архитектуре, под которую идет компиляция (у нас процессор rv32i с расширением инструкциями для взаимодействия с регистрами контроля и статуса Zicsr)
|
||||||
* `-mabi=ilp32` — указание двоичного интерфейса приложений. Здесь сказано, что типы `int`, `long` и `pointer` являются 32-разрядными.
|
- `-mabi=ilp32` — указание двоичного интерфейса приложений. Здесь сказано, что типы `int`, `long` и `pointer` являются 32-разрядными.
|
||||||
|
|
||||||
Есть очень [хорошее видео](https://youtu.be/29iNHEhHmd0?t=141), описывающее состав тулчейнов, именование исполняемых файлов компиляторов, как формируются ключи архитектуры и двоичного интерфейса приложений.
|
Есть очень [хорошее видео](https://youtu.be/29iNHEhHmd0?t=141), описывающее состав тулчейнов, именование исполняемых файлов компиляторов, как формируются ключи архитектуры и двоичного интерфейса приложений.
|
||||||
|
|
||||||
@@ -474,10 +483,10 @@ _Листинг 2. Пример содержимого файла первичн
|
|||||||
|
|
||||||
Исполняемый файл компилятора тот же самый, флаги компоновки будут следующие:
|
Исполняемый файл компилятора тот же самый, флаги компоновки будут следующие:
|
||||||
|
|
||||||
* `-march=rv32i_zicsr -mabi=ilp32` — те же самые флаги, что были при компиляции (нам все еще нужно указывать архитектуру, иначе компоновщик может скомпоновать объектные файлы со стандартными библиотеками от другой архитектуры)
|
- `-march=rv32i_zicsr -mabi=ilp32` — те же самые флаги, что были при компиляции (нам все еще нужно указывать архитектуру, иначе компоновщик может скомпоновать объектные файлы со стандартными библиотеками от другой архитектуры)
|
||||||
* `-Wl,--gc-sections` — указать компоновщику удалять неиспользуемые секции (сокращает объем итогового файла)
|
- `-Wl,--gc-sections` — указать компоновщику удалять неиспользуемые секции (сокращает объем итогового файла)
|
||||||
* `-nostartfiles` — указать компоновщику не использовать стартап-файлы стандартных библиотек (сокращает объем файла и устраняет ошибки компиляции из-за конфликтов с используемым стартап-файлом).
|
- `-nostartfiles` — указать компоновщику не использовать стартап-файлы стандартных библиотек (сокращает объем файла и устраняет ошибки компиляции из-за конфликтов с используемым стартап-файлом).
|
||||||
* `-T linker_script.ld` — передать компоновщику скрипт компоновки
|
- `-T linker_script.ld` — передать компоновщику скрипт компоновки
|
||||||
|
|
||||||
Пример команды компоновки:
|
Пример команды компоновки:
|
||||||
|
|
||||||
@@ -589,6 +598,8 @@ Disassembly of section .data:
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
_Листинг 3. Пример дизасемблированного файла._
|
||||||
|
|
||||||
Числа в самом левом столбце, увеличивающиеся на 4 — это адреса в памяти. Отлаживая программу на временной диаграмме, вы можете ориентироваться на эти числа, как на значения PC.
|
Числа в самом левом столбце, увеличивающиеся на 4 — это адреса в памяти. Отлаживая программу на временной диаграмме, вы можете ориентироваться на эти числа, как на значения PC.
|
||||||
|
|
||||||
Следующая за адресом строка, записанная в шестнадцатеричном виде — это та инструкция (или данные), которая размещена по этому адресу. С помощью этого столбца вы можете проверить, что считанная инструкция на временной диаграмме (сигнал `instr`) корректна.
|
Следующая за адресом строка, записанная в шестнадцатеричном виде — это та инструкция (или данные), которая размещена по этому адресу. С помощью этого столбца вы можете проверить, что считанная инструкция на временной диаграмме (сигнал `instr`) корректна.
|
||||||
@@ -621,7 +632,16 @@ Disassembly of section .data:
|
|||||||
|
|
||||||
Вам необходимо написать программу для вашего [индивидуального задания](../04.%20Primitive%20programmable%20device/Индивидуальное%20задание#индивидуальные-задания) к 4-ой лабораторной работе на языке C или C++ (в зависимости от выбранного языка необходимо использовать соответствующий компилятор: gcc для C, g++ для C++).
|
Вам необходимо написать программу для вашего [индивидуального задания](../04.%20Primitive%20programmable%20device/Индивидуальное%20задание#индивидуальные-задания) к 4-ой лабораторной работе на языке C или C++ (в зависимости от выбранного языка необходимо использовать соответствующий компилятор: gcc для C, g++ для C++).
|
||||||
|
|
||||||
При этом, вам необходимо получить входные данные от вашего устройства ввода и вывести результат на устройство вывода. Продумайте, как именно будет работать ваша программа, (бесконечно пересчитывать значения, получая новые данные от устройства ввода, или считать один раз, ожидая данные в бесконечном цикле — вариантов реализации очень много).
|
Для того чтобы ваша программа собралась, необходимо описать две функции: `main` и `int_handler`. Аргументы и возвращаемые значения могут быть любыми, но использоваться они не смогут. Функция `main` будет вызвана в начале работы программы (после исполнения подготовительной части startup-файла), функция `int_handler` будет вызываться автоматически каждый раз, когда ваш контроллер устройства ввода будет генерировать запрос прерывания (если процессор закончил обрабатывать предыдущий запрос).
|
||||||
|
|
||||||
|
Таким образом, минимальный алгоритм работы заключается в том, чтобы считать по прерыванию данные от устройства ввода (в индивидуальном задании обозначалось как sw_i), выполнить обработку из вашего варианта, и записать результат в устройство вывода. При этом необходимо помнить о следующем:
|
||||||
|
|
||||||
|
- При вводе данных с клавиатуры, отправляется скан-код клавиши, а не значение нажатой цифры (и не ascii-код нажатой буквы). Более того, при отпускании клавиши, генерируется скан-код `FO`, за которым следует повторная отправка скан-кода этой клавиши.
|
||||||
|
- Работая с uart через программу Putty, вы отправляете ascii-код вводимого символа.
|
||||||
|
|
||||||
|
Таким образом, для этих двух устройств ввода, вам необходимо продумать протокол, по которому вы будете вводить числа в вашу программу. В простейшем случае можно обрабатывать данные "как есть". Т.е. в случае клавиатуры, нажатие на клавишу `1` в верхнем горизонтальном ряду на клавиатуры со скан-кодом 0x16 интерпретировать как число `0x16`. А в случае отправки по uart символа `1` с ascii-кодом `0x31` интерпретировать его как `0x31`. Однако вывод в Putty осуществляется в виде символов принятого ascii-кода, поэтому высок риск получить непечатный символ.
|
||||||
|
|
||||||
|
Функция main может быть как пустой, содержать один лишь оператор return или бесконечный цикл — ход работы в любом случае не сломается, т.к. в стартап-файле прописан бесконечный цикл после выполнения main. Тем не менее, вы можете разместить здесь и какую-то логику, получающую данные от обработчика прерываний через глобальные переменные.
|
||||||
|
|
||||||
Доступ к регистрам контроллеров периферии осуществляется через обращение в память. В простейшем случае такой доступ осуществляется через [разыменование указателей](https://ru.wikipedia.org/wiki/Указатель_(тип_данных)#Действия_над_указателями), проинициализированных адресами регистров из [карты памяти](../13.%20Peripheral%20units#задание) 13-ой лабораторной работы.
|
Доступ к регистрам контроллеров периферии осуществляется через обращение в память. В простейшем случае такой доступ осуществляется через [разыменование указателей](https://ru.wikipedia.org/wiki/Указатель_(тип_данных)#Действия_над_указателями), проинициализированных адресами регистров из [карты памяти](../13.%20Peripheral%20units#задание) 13-ой лабораторной работы.
|
||||||
|
|
||||||
@@ -629,9 +649,16 @@ Disassembly of section .data:
|
|||||||
|
|
||||||
Для того, чтобы уменьшить ваше взаимодействие с черной магией указателей, вам представлен файл [platform.h](platform.h), в котором объявлены указатели структуры, отвечающие за отображение полей на физические адреса периферийных устройств. Вам нужно лишь воспользоваться указателем на ваше периферийное устройство.
|
Для того, чтобы уменьшить ваше взаимодействие с черной магией указателей, вам представлен файл [platform.h](platform.h), в котором объявлены указатели структуры, отвечающие за отображение полей на физические адреса периферийных устройств. Вам нужно лишь воспользоваться указателем на ваше периферийное устройство.
|
||||||
|
|
||||||
Пример взаимодействия с периферийным устройством через вымышленную структуру:
|
Пример взаимодействия с периферийным устройством через структуру **ВЫМЫШЛЕННОГО** периферийного устройства. Данная программа является лишь примером, иллюстрирующим взаимодействие с периферией через представленные указатели на структуры. Вам необходимо разобраться в том, как осуществляется работа с вымышленным устройством, а затем написать собственную программу, работающую по логике вашего индивидуального задания, которая взаимодействует с вашим реальным устройством.
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
|
/*
|
||||||
|
Не надо копировать и использовать в качестве основы вашей программы этот файл.
|
||||||
|
Он для этого не подходит. В вашей процессорной системе нет никаких коллайдеров
|
||||||
|
DEADLY_SERIOUS-событий и аварийных выключателей.
|
||||||
|
Просто разберитесь в операторе `->` и использовании указателей в качестве имени
|
||||||
|
массива и напишите собственную программу.
|
||||||
|
*/
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -675,20 +702,24 @@ extern "C" void int_handler()
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
_Листинг 4. Пример кода на C++, взаимодействующего с выдуманным периферийным устройством через указатели на структуру и массив, объявленные в platform.h._
|
||||||
|
|
||||||
Если одним из ваших периферийных устройств был VGA-контроллер, то вы можете использовать не указатель на структуру, а объявленные в том же файле указатели на байты: `char_map`, `color_map`, `tiff_map`. Как вы знаете, указатель может использоваться в качестве имени массива, а значит вы можете обращаться к нужному вам байту в соответствующей области памяти VGA-контроллера как к элементу массива. Например, для того, чтобы записать символ в шестое знакоместо второй строки, вам необходимо будет обратиться к `char_map[2*80+6]` (2*80 — индекс начала второй строки).
|
Если одним из ваших периферийных устройств был VGA-контроллер, то вы можете использовать не указатель на структуру, а объявленные в том же файле указатели на байты: `char_map`, `color_map`, `tiff_map`. Как вы знаете, указатель может использоваться в качестве имени массива, а значит вы можете обращаться к нужному вам байту в соответствующей области памяти VGA-контроллера как к элементу массива. Например, для того, чтобы записать символ в шестое знакоместо второй строки, вам необходимо будет обратиться к `char_map[2*80+6]` (2*80 — индекс начала второй строки).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Порядок выполнения задания
|
### Порядок выполнения задания
|
||||||
|
|
||||||
1. Написать программу для своего индивидуального задания на языке C или C++.
|
1. Внимательно изучить разделы теории и практики.
|
||||||
2. [Скомпилировать](#практика) программу и [стартап-файл](startup.S) в объектные файлы.
|
2. Разобрать принцип взаимодействия с контрольными и статусными регистрами периферийного устройства на примере _Листинга 4_.
|
||||||
3. Скомпоновать объектные файлы исполняемый файл, передав компоновщику соответствующий [скрипт](linker_script.ld).
|
3. Написать программу для своего индивидуального задания и набора периферийных устройств на языке C или C++. В случае написания кода на C++ помните о необходимости добавления `extern "C"` перед определением функции `int_handler`.
|
||||||
4. Экспортировать из объектного файла секции `.text` и `.data` в текстовые файлы `init_instr.mem`, `init_data.mem`. Если вы не создавали инициализированных статических массивов или глобальных переменных, то файл `init_data.mem` может быть оказаться пустым.
|
4. [Скомпилировать](#практика) программу и [стартап-файл](startup.S) в объектные файлы.
|
||||||
|
5. Скомпоновать объектные файлы исполняемый файл, передав компоновщику соответствующий [скрипт](linker_script.ld).
|
||||||
|
6. Экспортировать из объектного файла секции `.text` и `.data` в текстовые файлы `init_instr.mem`, `init_data.mem`. Если вы не создавали инициализированных статических массивов или глобальных переменных, то файл `init_data.mem` может быть оказаться пустым.
|
||||||
1. Если файл `init_data.mem` не пустой, необходимо проинициализировать память в модуле `ext_mem` c помощью системной функции `$readmemh` как это было сделано для памяти инструкций.
|
1. Если файл `init_data.mem` не пустой, необходимо проинициализировать память в модуле `ext_mem` c помощью системной функции `$readmemh` как это было сделано для памяти инструкций.
|
||||||
2. Перед этим из файла `init_data.mem` необходимо удалить первую строку (вида `@00001000`), указывающую начальный адрес инициализации.
|
2. Перед этим из файла `init_data.mem` необходимо удалить первую строку (вида `@00001000`), указывающую начальный адрес инициализации.
|
||||||
5. Добавить получившиеся текстовые файлы в проект Vivado.
|
7. Добавить получившиеся текстовые файлы в проект Vivado.
|
||||||
6. Запустить моделирование исполнения программы вашим процессором. Для отладки во время моделирования будет удобно использовать дизасемблерный файл, ориентируясь на сигналы адреса и данных шины инструкций.
|
8. Запустить моделирование исполнения программы вашим процессором. Для отладки во время моделирования будет удобно использовать дизасемблерный файл, ориентируясь на сигналы адреса и данных шины инструкций.
|
||||||
7. Проверить корректное исполнение программы процессором в ПЛИС.
|
9. Проверить корректное исполнение программы процессором в ПЛИС.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@@ -91,9 +91,13 @@ struct TX_HANDLE
|
|||||||
};
|
};
|
||||||
struct TX_HANDLE *const tx_ptr = CAST(struct TX_HANDLE *const, 0x06000000);
|
struct TX_HANDLE *const tx_ptr = CAST(struct TX_HANDLE *const, 0x06000000);
|
||||||
|
|
||||||
volatile uint8_t * const char_map = CAST(uint8_t * const, 0x07000000);
|
struct VGA_HANDLE
|
||||||
volatile uint8_t * const color_map = CAST(uint8_t * const, 0x07001000);
|
{
|
||||||
volatile uint8_t * const tiff_map = CAST(uint8_t * const, 0x07002000);
|
volatile uint8_t *const char_map;
|
||||||
|
volatile uint8_t *const color_map;
|
||||||
|
volatile uint8_t *const tiff_map;
|
||||||
|
};
|
||||||
|
struct VGA_HANDLE vga = {CAST(uint8_t *const, 0x07000000), CAST(uint8_t *const, 0x07001000), CAST(uint8_t *const, 0x07002000)};
|
||||||
|
|
||||||
struct TIMER_HANDLE
|
struct TIMER_HANDLE
|
||||||
{
|
{
|
||||||
|
@@ -41,7 +41,11 @@ _main_call:
|
|||||||
# нулевой элемент которого является именем исполняемого файла,
|
# нулевой элемент которого является именем исполняемого файла,
|
||||||
# Но для простоты реализации оба аргумента всего лишь обнулены.
|
# Но для простоты реализации оба аргумента всего лишь обнулены.
|
||||||
# Это сделано для детерминированного поведения программы в случае,
|
# Это сделано для детерминированного поведения программы в случае,
|
||||||
# если будет пытаться использовать эти аргументы.
|
# если программист будет пытаться использовать эти аргументы.
|
||||||
|
|
||||||
|
# Вызов main.
|
||||||
|
# Для того чтобы программа скомпоновалась, где-то должна быть описана
|
||||||
|
# функция именно с таким именем.
|
||||||
call main
|
call main
|
||||||
# Зацикливание после выхода из функции main
|
# Зацикливание после выхода из функции main
|
||||||
_endless_loop:
|
_endless_loop:
|
||||||
@@ -64,12 +68,12 @@ _int_handler:
|
|||||||
# Данная операция меняет местами регистры sp и mscratch.
|
# Данная операция меняет местами регистры sp и mscratch.
|
||||||
# В итоге указатель на стек прерываний оказывается в регистре sp, а вершина
|
# В итоге указатель на стек прерываний оказывается в регистре sp, а вершина
|
||||||
# программного стека оказывается в регистре mscratch.
|
# программного стека оказывается в регистре mscratch.
|
||||||
csrrw sp,mscratch,sp
|
csrrw sp, mscratch,sp
|
||||||
|
|
||||||
# Далее мы поднимаемся по стеку прерываний и сохраняем все регистры.
|
# Далее мы поднимаемся по стеку прерываний и сохраняем все регистры.
|
||||||
addi sp,sp,-80 # Указатель на стек должен быть выровнен до 16 байт, поэтому
|
addi sp, sp, -80 # Указатель на стек должен быть выровнен до 16 байт, поэтому
|
||||||
# поднимаемся вверх не на 76, а на 80.
|
# поднимаемся вверх не на 76, а на 80.
|
||||||
sw ra,4(sp)
|
sw ra, 4(sp)
|
||||||
# Мы хотим убедиться, что очередное прерывание не наложит стек прерываний на
|
# Мы хотим убедиться, что очередное прерывание не наложит стек прерываний на
|
||||||
# программный стек, поэтому записываем в освободившийся регистр низ
|
# программный стек, поэтому записываем в освободившийся регистр низ
|
||||||
# программного стека, и проверяем что приподнятый указатель на верхушку
|
# программного стека, и проверяем что приподнятый указатель на верхушку
|
||||||
@@ -80,34 +84,36 @@ _int_handler:
|
|||||||
la ra, _stack_ptr
|
la ra, _stack_ptr
|
||||||
blt sp, ra, _endless_loop
|
blt sp, ra, _endless_loop
|
||||||
|
|
||||||
sw t0,12(sp) # Мы перепрыгнули через смещение 8, поскольку там должен
|
sw t0, 12(sp) # Мы перепрыгнули через смещение 8, поскольку там должен
|
||||||
# лежать регистр sp, который ранее сохранили в mscratch.
|
# лежать регистр sp, который ранее сохранили в mscratch.
|
||||||
# Мы запишем его на стек чуть позже.
|
# Мы запишем его на стек чуть позже.
|
||||||
sw t1,16(sp)
|
sw t1, 16(sp)
|
||||||
sw t2,20(sp)
|
sw t2, 20(sp)
|
||||||
sw a0,24(sp)
|
sw a0, 24(sp)
|
||||||
sw a1,28(sp)
|
sw a1, 28(sp)
|
||||||
sw a2,32(sp)
|
sw a2, 32(sp)
|
||||||
sw a3,36(sp)
|
sw a3, 36(sp)
|
||||||
sw a4,40(sp)
|
sw a4, 40(sp)
|
||||||
sw a5,44(sp)
|
sw a5, 44(sp)
|
||||||
sw a6,48(sp)
|
sw a6, 48(sp)
|
||||||
sw a7,52(sp)
|
sw a7, 52(sp)
|
||||||
sw t3,56(sp)
|
sw t3, 56(sp)
|
||||||
sw t4,60(sp)
|
sw t4, 60(sp)
|
||||||
sw t5,64(sp)
|
sw t5, 64(sp)
|
||||||
sw t6,68(sp)
|
sw t6, 68(sp)
|
||||||
|
|
||||||
# Кроме того, мы сохраняем состояние регистров прерываний на случай, если
|
# Кроме того, мы сохраняем состояние регистров прерываний на случай, если
|
||||||
# произойдет еще одно прерывание.
|
# произойдет еще одно прерывание.
|
||||||
csrr t0,mscratch
|
csrr t0, mscratch
|
||||||
csrr t1,mepc
|
csrr t1, mepc
|
||||||
csrr a0,mcause
|
csrr a0, mcause
|
||||||
sw t0,8(sp)
|
sw t0, 8(sp)
|
||||||
sw t1,72(sp)
|
sw t1, 72(sp)
|
||||||
sw a0,76(sp)
|
sw a0, 76(sp)
|
||||||
|
|
||||||
# Вызов высокоуровневого обработчика прерываний
|
# Вызов высокоуровневого обработчика прерываний.
|
||||||
|
# Для того чтобы программа скомпоновалась, где-то должна быть описана
|
||||||
|
# функция именно с таким именем.
|
||||||
call int_handler
|
call int_handler
|
||||||
|
|
||||||
# Восстановление контекста. В первую очередь мы хотим восстановить CS-регистры,
|
# Восстановление контекста. В первую очередь мы хотим восстановить CS-регистры,
|
||||||
@@ -117,29 +123,29 @@ _int_handler:
|
|||||||
# сохраним его в регистр a0, и будем восстанавливаться из него.
|
# сохраним его в регистр a0, и будем восстанавливаться из него.
|
||||||
mv a0,sp
|
mv a0,sp
|
||||||
|
|
||||||
lw t1,72(a0)
|
lw t1, 72(a0)
|
||||||
addi sp,sp,80
|
addi sp, sp, 80
|
||||||
csrw mscratch,sp
|
csrw mscratch, sp
|
||||||
csrw mepc,t1
|
csrw mepc, t1
|
||||||
lw ra,4(a0)
|
lw ra, 4(a0)
|
||||||
lw sp,8(a0)
|
lw sp, 8(a0)
|
||||||
lw t0,12(a0)
|
lw t0, 12(a0)
|
||||||
lw t1,16(a0)
|
lw t1, 16(a0)
|
||||||
lw t2,20(a0)
|
lw t2, 20(a0)
|
||||||
lw a1,28(a0) # Мы пропустили a0, потому что сейчас он используется в
|
lw a1, 28(a0) # Мы пропустили a0, потому что сейчас он используется в
|
||||||
# качестве указателя на верхушку стека и не может быть
|
# качестве указателя на верхушку стека и не может быть
|
||||||
# восстановлен.
|
# восстановлен.
|
||||||
lw a2,32(a0)
|
lw a2, 32(a0)
|
||||||
lw a3,36(a0)
|
lw a3, 36(a0)
|
||||||
lw a4,40(a0)
|
lw a4, 40(a0)
|
||||||
lw a5,44(a0)
|
lw a5, 44(a0)
|
||||||
lw a6,48(a0)
|
lw a6, 48(a0)
|
||||||
lw a7,52(a0)
|
lw a7, 52(a0)
|
||||||
lw t3,56(a0)
|
lw t3, 56(a0)
|
||||||
lw t4,60(a0)
|
lw t4, 60(a0)
|
||||||
lw t5,64(a0)
|
lw t5, 64(a0)
|
||||||
lw t6,68(a0)
|
lw t6, 68(a0)
|
||||||
lw a0,40(a0)
|
lw a0, 40(a0)
|
||||||
|
|
||||||
# Выход из обработчика прерывания
|
# Выход из обработчика прерывания
|
||||||
mret
|
mret
|
||||||
|
Reference in New Issue
Block a user