mirror of
https://github.com/MPSU/APS.git
synced 2025-09-15 17:20:10 +00:00
Предложения по лабораторной работе 13 (#58)
* Предложения по лабораторной работе 13 * Apply suggestions from code review Co-authored-by: Andrei Solodovnikov <VoultBoy@yandex.ru> --------- Co-authored-by: Mikhail Popov <mikhail.popov@bsc.es> Co-authored-by: Andrei Solodovnikov <VoultBoy@yandex.ru>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
# Лабораторная работа 13 "Высокоуровневое программирование"
|
||||
|
||||
Благодаря абстрагированию можно создавать действительно сложные системы — из вентелей можно собрать модули, из модулей микроархитектуру и так далее. В этом контексте архитектура выступает как фундамент, на котором строится программный стек абстракций. На основе архитектур строятся ассемблеры, на основе которых "строятся" языки высокого уровня, на основе которых создаются фреймворки и метафреймворки, что обеспечивает более высокий уровень и удобство при разработке новых программ. Давайте немного глубже погрузимся в этот стек.
|
||||
|
||||
## Цель
|
||||
|
||||
В соответствии с индивидуальным заданием, написать программу на языке программирования высокого уровня C, скомпилировать в машинные коды и запустить на ранее разработанном процессоре RISC-V.
|
||||
@@ -16,8 +18,8 @@
|
||||
2. [Компоновка объектных файлов в исполняемый](#компоновка-объектных-файлов-в-исполняемый)
|
||||
3. [Экспорт секций для инициализации памяти](#экспорт-секций-для-инициализации-памяти)
|
||||
4. [Дизассемблирование](#дизассемблирование)
|
||||
4. [Написать и скомпилировать собственную программу](#задание).
|
||||
5. Проверить исполнение программы вашим процессором в ПЛИС.
|
||||
4. [Написать и скомпилировать собственную программу](#задание)
|
||||
5. Проверить исполнение программы вашим процессором в ПЛИС
|
||||
|
||||
## Теория
|
||||
|
||||
@@ -81,7 +83,7 @@ _Таблица 1. Ассемблерные мнемоники для целоч
|
||||
|
||||
В итоге начиная с нулевого адреса будет размещена секция `.text` файла `fourier.o`. Она будет размещена именно там, поскольку счетчик адресов в начале скрипта равен нулю, а очередная секция размещается по адресу, куда указывает счетчик адресов. После этого, счетчик адресов будет увеличен на размер этой секции и секция `.text` файла `main.o` будет размещена сразу же за секцией `.text` файла `fourier.o`. После этого счетчик адресов будет увеличен на размер этой секции. То же самое произойдет и при размещении оставшихся секций.
|
||||
|
||||
Кроме того, вы в любой момент можете изменить значение счетчика адресов. Например, у вас две раздельные памяти: память инструкций объемом 512 байт и память данных объемом 1024 байта. Эти памяти находятся в одном адресном пространстве. Диапазон адресов памяти инструкций: `[0:511]`, диапазон памяти данных: `[512:1535]`. При этом общий объем секций `.text` составляет 416 байт. В этом случае, вы можете сперва разместить секции `.text` так же, как было описано в предыдущем примере, а затем, выставив значение на счетчике адресов равное `512`, описываете размещение секций данных. Тогда, между секциями будет появится разрыв в 96 байт. А данные окажутся в диапазоне адресов, выделенном для памяти данных.
|
||||
Кроме того, вы в любой момент можете изменить значение счетчика адресов. Например, у вас две раздельные памяти: память инструкций объемом 512 байт и память данных объемом 1024 байта. Эти памяти находятся в одном адресном пространстве. Диапазон адресов памяти инструкций: `[0:511]`, диапазон памяти данных: `[512:1535]`. При этом общий объем секций `.text` составляет 416 байт. В этом случае, вы можете сперва разместить секции `.text` так же, как было описано в предыдущем примере, а затем, выставив значение на счетчике адресов равное `512`, описываете размещение секций данных. Тогда, между секциями появится разрыв в 96 байт, а данные окажутся в диапазоне адресов, выделенном для памяти данных.
|
||||
|
||||
Помимо прочего, в скрипте компоновщика необходимо прописать, где будет находиться стек, и какое будет значение у указателя на глобальную область памяти.
|
||||
|
||||
@@ -293,7 +295,7 @@ _Листинг 1. Пример скрипта компоновщика с ко
|
||||
|
||||
В стартап-файле хранятся инструкции, которые обязательно необходимо выполнить перед началом исполнения любой программы. Это инициализация регистров указателей на стек и глобальную область данных, регистра, хранящего адрес вектора прерываний и т.п.
|
||||
|
||||
По завершению инициализации, стартап-файл выполняет процедуру передаче управления точке входа в запускаемую программу.
|
||||
По завершению инициализации, стартап-файл выполняет процедуру передачи управления точке входа в запускаемую программу.
|
||||
|
||||
```asm
|
||||
.section .boot
|
||||
@@ -329,7 +331,7 @@ _main_call:
|
||||
# нулевой элемент которого является именем исполняемого файла,
|
||||
# Но для простоты реализации оба аргумента всего лишь обнулены.
|
||||
# Это сделано для детерминированного поведения программы в случае,
|
||||
# если будет пытаться использовать эти аргументы.
|
||||
# если программист будет пытаться использовать эти аргументы.
|
||||
call main
|
||||
# Зацикливание после выхода из функции main
|
||||
_endless_loop:
|
||||
@@ -352,12 +354,12 @@ _int_handler:
|
||||
# Данная операция меняет местами регистры sp и mscratch.
|
||||
# В итоге указатель на стек прерываний оказывается в регистре sp, а вершина
|
||||
# программного стека оказывается в регистре mscratch.
|
||||
csrrw sp,mscratch,sp
|
||||
csrrw sp, mscratch, sp
|
||||
|
||||
# Далее мы поднимаемся по стеку прерываний и сохраняем все регистры.
|
||||
addi sp,sp,-80 # Указатель на стек должен быть выровнен до 16 байт, поэтому
|
||||
addi sp, sp, -80 # Указатель на стек должен быть выровнен до 16 байт, поэтому
|
||||
# поднимаемся вверх не на 76, а на 80.
|
||||
sw ra,4(sp)
|
||||
sw ra, 4(sp)
|
||||
# Мы хотим убедиться, что очередное прерывание не наложит стек прерываний на
|
||||
# программный стек, поэтому записываем в освободившийся регистр низ
|
||||
# программного стека, и проверяем что приподнятый указатель на верхушку
|
||||
@@ -368,32 +370,32 @@ _int_handler:
|
||||
la ra, _stack_ptr
|
||||
blt sp, ra, _endless_loop
|
||||
|
||||
sw t0,12(sp) # Мы перепрыгнули через смещение 8, поскольку там должен
|
||||
# лежать регистр sp, который ранее сохранили в mscratch.
|
||||
# Мы запишем его на стек чуть позже.
|
||||
sw t1,16(sp)
|
||||
sw t2,20(sp)
|
||||
sw a0,24(sp)
|
||||
sw a1,28(sp)
|
||||
sw a2,32(sp)
|
||||
sw a3,36(sp)
|
||||
sw a4,40(sp)
|
||||
sw a5,44(sp)
|
||||
sw a6,48(sp)
|
||||
sw a7,52(sp)
|
||||
sw t3,56(sp)
|
||||
sw t4,60(sp)
|
||||
sw t5,64(sp)
|
||||
sw t6,68(sp)
|
||||
sw t0, 12(sp) # Мы перепрыгнули через смещение 8, поскольку там должен
|
||||
# лежать регистр sp, который ранее сохранили в mscratch.
|
||||
# Мы запишем его на стек чуть позже.
|
||||
sw t1, 16(sp)
|
||||
sw t2, 20(sp)
|
||||
sw a0, 24(sp)
|
||||
sw a1, 28(sp)
|
||||
sw a2, 32(sp)
|
||||
sw a3, 36(sp)
|
||||
sw a4, 40(sp)
|
||||
sw a5, 44(sp)
|
||||
sw a6, 48(sp)
|
||||
sw a7, 52(sp)
|
||||
sw t3, 56(sp)
|
||||
sw t4, 60(sp)
|
||||
sw t5, 64(sp)
|
||||
sw t6, 68(sp)
|
||||
|
||||
# Кроме того, мы сохраняем состояние регистров прерываний на случай, если
|
||||
# произойдет еще одно прерывание.
|
||||
csrr t0,mscratch
|
||||
csrr t1,mepc
|
||||
csrr a0,mcause
|
||||
sw t0,8(sp)
|
||||
sw t1,72(sp)
|
||||
sw a0,76(sp)
|
||||
csrr t0, mscratch
|
||||
csrr t1, mepc
|
||||
csrr a0, mcause
|
||||
sw t0, 8(sp)
|
||||
sw t1, 72(sp)
|
||||
sw a0, 76(sp)
|
||||
|
||||
# Вызов высокоуровневого обработчика прерываний
|
||||
call int_handler
|
||||
@@ -403,31 +405,31 @@ _int_handler:
|
||||
# вернуть исходное значение указателя стека прерываний. Однако его нынешнее
|
||||
# значение нам еще необходимо для восстановления контекста, поэтому мы
|
||||
# сохраним его в регистр a0, и будем восстанавливаться из него.
|
||||
mv a0,sp
|
||||
mv a0, sp
|
||||
|
||||
lw t1,72(a0)
|
||||
addi sp,sp,80
|
||||
csrw mscratch,sp
|
||||
csrw mepc,t1
|
||||
lw ra,4(a0)
|
||||
lw sp,8(a0)
|
||||
lw t0,12(a0)
|
||||
lw t1,16(a0)
|
||||
lw t2,20(a0)
|
||||
lw a1,28(a0) # Мы пропустили a0, потому что сейчас он используется в
|
||||
lw t1, 72(a0)
|
||||
addi sp, sp, 80
|
||||
csrw mscratch, sp
|
||||
csrw mepc, t1
|
||||
lw ra, 4(a0)
|
||||
lw sp, 8(a0)
|
||||
lw t0, 12(a0)
|
||||
lw t1, 16(a0)
|
||||
lw t2, 20(a0)
|
||||
lw a1, 28(a0) # Мы пропустили a0, потому что сейчас он используется в
|
||||
# качестве указателя на верхушку стека и не может быть
|
||||
# восстановлен.
|
||||
lw a2,32(a0)
|
||||
lw a3,36(a0)
|
||||
lw a4,40(a0)
|
||||
lw a5,44(a0)
|
||||
lw a6,48(a0)
|
||||
lw a7,52(a0)
|
||||
lw t3,56(a0)
|
||||
lw t4,60(a0)
|
||||
lw t5,64(a0)
|
||||
lw t6,68(a0)
|
||||
lw a0,40(a0)
|
||||
lw a2, 32(a0)
|
||||
lw a3, 36(a0)
|
||||
lw a4, 40(a0)
|
||||
lw a5, 44(a0)
|
||||
lw a6, 48(a0)
|
||||
lw a7, 52(a0)
|
||||
lw t3, 56(a0)
|
||||
lw t4, 60(a0)
|
||||
lw t5, 64(a0)
|
||||
lw t6, 68(a0)
|
||||
lw a0, 40(a0)
|
||||
|
||||
# Выход из обработчика прерывания
|
||||
mret
|
||||
@@ -437,9 +439,9 @@ _Листинг 2. Пример содержимого файла первичн
|
||||
|
||||
## Практика
|
||||
|
||||
Для того, чтобы запустить моделирование исполнения программы на вашем процессоре, сперва эту программу необходимо скомпилировать и преобразовать в текстовый файл, которым САПР сможет проинициализировать память процессора. Для компиляции программы, вам потребуется особый компилятор, который называется "кросскомпилятор". Он позволяет компилировать исходный код под архитектуру компьютера, отличную от компьютера, на котором ведется компиляция. В нашем случае, вы будете собирать код под архитектуру RISC-V на компьютере с архитектурой `x86_64`.
|
||||
Для того, чтобы запустить моделирование исполнения программы на вашем процессоре, сперва эту программу необходимо скомпилировать и преобразовать в текстовый файл, которым САПР сможет проинициализировать память процессора. Для компиляции программы, вам потребуется особый компилятор, который называется "кросскомпилятор". Он позволяет компилировать исходный код под архитектуру компьютера, отличную от компьютера, на котором ведется компиляция. В нашем случае, вы будете собирать код под архитектуру `RISC-V` на компьютере с архитектурой `x86_64`.
|
||||
|
||||
Компилятор, который подойдет для данной задачи (для запуска в операционной системе Windows) уже установлен в аудиториях. Но если что, вы можете скачать его [отсюда](https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases/download/v13.2.0-1/xpack-riscv-none-elf-gcc-13.2.0-1-win32-x64.zip).
|
||||
Компилятор, который подойдет для данной задачи (для запуска в операционной системе Windows) уже установлен в аудиториях. Но если что, вы можете скачать его [отсюда](https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases/download/v13.2.0-1/xpack-riscv-none-elf-gcc-13.2.0-1-win32-x64.zip) (обратите внимание, что размер архива составляет ~550 МБ, попытка скачивания этого архива из учебной аудитории может потратить вашу месячную квоту интернет-трафика).
|
||||
|
||||
### Компиляция объектных файлов
|
||||
|
||||
@@ -617,7 +619,7 @@ 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++).
|
||||
|
||||
При этом, вам необходимо получить входные данные от вашего устройства ввода и вывести результат на устройство вывода. Продумайте, как именно будет работать ваша программа, (бесконечно пересчитывать значения, получая новые данные от устройства ввода, или считать один раз, ожидая данные в бесконечном цикле — вариантов реализации очень много).
|
||||
|
||||
@@ -673,7 +675,7 @@ extern "C" void int_handler()
|
||||
}
|
||||
```
|
||||
|
||||
Если одним из ваших периферийных устройств был 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 — индекс начала второй строки).
|
||||
|
||||
---
|
||||
|
||||
@@ -687,6 +689,6 @@ extern "C" void int_handler()
|
||||
2. Перед этим из файла `init_data.mem` необходимо удалить первую строку (вида `@00001000`), указывающую начальный адрес инициализации.
|
||||
5. Добавить получившиеся текстовые файлы в проект Vivado.
|
||||
6. Запустить моделирование исполнения программы вашим процессором. Для отладки во время моделирования будет удобно использовать дизасемблерный файл, ориентируясь на сигналы адреса и данных шины инструкций.
|
||||
7. Проверить исполнение программы процессором в ПЛИС.
|
||||
7. Проверить корректное исполнение программы процессором в ПЛИС.
|
||||
|
||||
---
|
||||
|
Reference in New Issue
Block a user