ЛР14. Рефактор методички

This commit is contained in:
Andrei Solodovnikov
2024-07-25 13:11:49 +03:00
parent 901057a224
commit 9cb4b3721e
3 changed files with 176 additions and 180 deletions

View File

@@ -8,53 +8,39 @@
See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details.
* ------------------------------------------------------------------------------
*/
OUTPUT_FORMAT("elf32-littleriscv") /* Указываем порядок следования байт */
ENTRY(_start) /* мы сообщаем компоновщику, что первая
исполняемая процессором инструкция
находится у метки "start"
*/
_text_size = 0x1000; /* Размер памяти инстр.: 4KiB */
_data_size = 0x4000; /* Размер памяти данных: 16KiB */
ASSERT(!(_text_size & (_text_size-1)), /* Проверка что размеры памяти */
"Instr mem size is not power of 2") /* являются степенью двойки */
ASSERT(!(_data_size & (_data_size-1)),
"Data mem size is not power of 2")
_data_base_addr = _text_size >= _data_size ? /* Стартовый адрес секции данных */
_text_size : _data_size; /* указан как больший из размеров*/
/* секции инструкций/данных */
_data_end = _data_base_addr + _data_size;
_trap_stack_size = 2560; /* Размер стека обработчика перехватов.
Данный размер позволяет выполнить
до 32 вложенных вызовов при обработке
перехватов.
*/
_stack_size = 1280; /* Размер программного стека.
Данный размер позволяет выполнить
до 16 вложенных вызовов.
находится у метки "_start"
*/
/*
В данном разделе указывается структура памяти:
Сперва идет регион "rom", являющийся памятью с исполняемым кодом
(об этом говорит аттрибут 'x'). Этот регион начинается
с адреса 0x00000000 и занимает _text_size байт.
Далее идет регион "ram", начинающийся с адреса _data_base_addr и занимающий
_data_size байт. Этот регион является памятью, противоположной региону "rom"
(в том смысле, что это не память с исполняемым кодом).
Сперва идет регион "instr_mem", являющийся памятью с исполняемым кодом
(об этом говорит аттрибут 'x'). Этот регион начинается
с адреса 0x00000000 и занимает 1024 байта.
Далее идет регион "data_mem", начинающийся с адреса 0x00000000 и занимающий
2048 байт. Этот регион является памятью, противоположной региону "instr_mem"
(в том смысле, что это не память с исполняемым кодом).
*/
MEMORY
{
rom (x) : ORIGIN = 0x00000000, LENGTH = _text_size
ram (!x) : ORIGIN = _data_base_addr, LENGTH = _data_size
instr_mem (x) : ORIGIN = 0x00000000, LENGTH = 1K
data_mem (!x) : ORIGIN = 0x00000000, LENGTH = 2K
}
_trap_stack_size = 640; /* Размер стека обработчика перехватов.
Данный размер позволяет выполнить
до 8 вложенных вызовов при обработке
перехватов.
*/
_stack_size = 640; /* Размер программного стека.
Данный размер позволяет выполнить
до 8 вложенных вызовов.
*/
/*
В данном разделе описывается размещение программы в памяти.
@@ -67,7 +53,7 @@ MEMORY
SECTIONS
{
PROVIDE( _start = 0x00000000 ); /* Позиция start в памяти*/
/*
В скриптах компоновщика есть внутренняя переменная, записываемая как '.'
Эта переменная называется "счетчиком адресов". Она хранит текущий адрес в
@@ -89,32 +75,20 @@ SECTIONS
секций, начинающихся на .text во всех переданных компоновщику двоичных
файлах.
Дополнительно мы указываем, что данная секция должна быть размещена в
регионе "rom".
регионе "instr_mem".
*/
.text : {*(.boot) *(.text*)} >rom
.text : {
PROVIDE(_start = .);
*(.boot)
*(.text*)
} > instr_mem
/*
Поскольку мы не знаем суммарного размера получившейся секции, мы проверяем
что не вышли за границы памяти инструкций и переносим счетчик адресов за
пределы памяти инструкций в область памяти данных.
Дополнительно мы указываем, что данная секция должна быть размещена в
регионе "ram".
*/
ASSERT(. < _text_size, ".text section exceeds instruction memory size")
. = _data_base_addr;
/*
Следующая команда сообщает, что начиная с адреса, которому в данных момент
равен счетчик адресов (_data_base_addr) будет находиться секция .data
итогового файла, которая состоит из секций всех секций, начинающихся
на .data во всех переданных компоновщику двоичных файлах.
Дополнительно мы указываем, что данная секция должна быть размещена в
регионе "ram".
*/
.data : {*(.*data*)} >ram
/*
В скобках после оператора AT указывается Load Memory Address (LMA). Чтобы
адреса памяти инструкций и памяти данных при компоновке не пересекались, мы
будем использовать заведомо несуществующий LMA, который в последствии будем
игнорировать.
.data : AT (0x80000000) {
/*
Общепринято присваивать GP значение равное началу секции данных, смещенное
на 2048 байт вперед.
Благодаря относительной адресации со смещением в 12 бит, можно адресоваться
@@ -124,13 +98,15 @@ SECTIONS
уже хранит базовый адрес и нужно только смещение).
Подробнее:
https://groups.google.com/a/groups.riscv.org/g/sw-dev/c/60IdaZj27dY/m/s1eJMlrUAQAJ
*/
_gbl_ptr = _data_base_addr + 0x800;
*/
_gbl_ptr = . + 2048;
*(.*data*)
} > data_mem
/*
Поскольку мы не знаем суммарный размер всех используемых секций данных,
перед размещением других секций, необходимо выравнять счетчик адресов по
перед размещением других секций, необходимо выровнять счетчик адресов по
4х-байтной границе.
*/
. = ALIGN(4);
@@ -159,11 +135,14 @@ SECTIONS
https://en.wikipedia.org/wiki/.bss
Дополнительно мы указываем, что данная секция должна быть размещена в
регионе "ram".
регионе "data_mem".
*/
_bss_start = .;
.bss : {*(.*bss*)} >ram
_bss_end = .;
.bss : {
_bss_start = .;
*bss*
*(COMMON)
_bss_end = .;
} > data_mem
/*=================================
@@ -174,20 +153,17 @@ SECTIONS
Поскольку стеков у нас два, в самом низу мы разместим стек прерываний, а
над ним программный стек. При этом надо обеспечить защиту программного
стека от наложения на него стека прерываний.
Однако перед этим, мы должны убедиться, что под программный стек останется
хотя бы 1280 байт (ничем не обоснованное число, взятое с потолка).
Такое значение обеспечивает до 16 вложенных вызовов (если сохранять только
необерегаемые регистры).
Однако перед этим, мы должны убедиться, что под оба стека хватит места.
=================================
*/
/* Мы хотим гарантировать, что под стек останется как минимум 1280 байт */
ASSERT(. < (_data_end - _trap_stack_size - _stack_size),
/* Мы хотим гарантировать, что под стек останется место */
ASSERT(. < (LENGTH(data_mem) - _trap_stack_size - _stack_size),
"Program size is too big")
/* Перемещаем счетчик адресов над стеком прерываний (чтобы после мы могли
использовать его в вызове ALIGN) */
. = _data_end - _trap_stack_size;
. = LENGTH(data_mem) - _trap_stack_size;
/*
Размещаем указатель программного стека так близко к границе стека
@@ -196,13 +172,14 @@ SECTIONS
Подробнее:
https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
*/
_stack_ptr = ALIGN(16) <= _data_end - _trap_stack_size?
_stack_ptr = ALIGN(16) <= LENGTH(data_mem) - _trap_stack_size?
ALIGN(16) : ALIGN(16) - 16;
ASSERT(_stack_ptr <= _data_end - _trap_stack_size, "SP exceed memory size")
ASSERT(_stack_ptr <= LENGTH(data_mem) - _trap_stack_size,
"SP exceed memory size")
/* Перемещаем счетчик адресов в конец памяти (чтобы после мы могли
использовать его в вызове ALIGN) */
. = _data_end;
. = LENGTH(data_mem);
/*
Обычно память имеет размер, кратный 16, но на случай, если это не так, мы
@@ -210,6 +187,6 @@ SECTIONS
конец кратен 16), либо поднимаемся на 16 байт вверх от края памяти,
округленного до 16 в сторону большего значения
*/
_trap_stack_ptr = ALIGN(16) <= _data_end ? ALIGN(16) : ALIGN(16) - 16;
ASSERT(_trap_stack_ptr <= _data_end, "ISP exceed memory size")
_trap_stack_ptr = ALIGN(16) <= LENGTH(data_mem) ? ALIGN(16) : ALIGN(16) - 16;
ASSERT(_trap_stack_ptr <= LENGTH(data_mem), "ISP exceed memory size")
}