mirror of
https://github.com/MPSU/APS.git
synced 2025-09-15 17:20:10 +00:00
205 lines
13 KiB
Plaintext
205 lines
13 KiB
Plaintext
/* -----------------------------------------------------------------------------
|
||
* Project Name : Architectures of Processor Systems (APS) lab work
|
||
* Organization : National Research University of Electronic Technology (MIET)
|
||
* Department : Institute of Microdevices and Control Systems
|
||
* Author(s) : Andrei Solodovnikov
|
||
* Email(s) : hepoh@org.miet.ru
|
||
|
||
See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details.
|
||
* ------------------------------------------------------------------------------
|
||
*/
|
||
|
||
OUTPUT_FORMAT("elf32-littleriscv") /* Указываем порядок следования байт */
|
||
|
||
ENTRY(_start) /* мы сообщаем компоновщику, что первая
|
||
исполняемая процессором инструкция
|
||
находится у метки "_start"
|
||
*/
|
||
|
||
/*
|
||
В данном разделе указывается структура памяти:
|
||
Сперва идет регион "instr_mem", являющийся памятью с исполняемым кодом
|
||
(об этом говорит аттрибут 'x'). Этот регион начинается
|
||
с адреса 0x00000000 и занимает 64 кибибайта.
|
||
Далее идет регион "data_mem", начинающийся с адреса 0x00000000 и занимающий
|
||
16 кибибайт. Этот регион является памятью, противоположной региону "instr_mem"
|
||
(в том смысле, что это не память с исполняемым кодом).
|
||
*/
|
||
MEMORY
|
||
{
|
||
instr_mem (x) : ORIGIN = 0x00000000, LENGTH = 64K
|
||
data_mem (!x) : ORIGIN = 0x00000000, LENGTH = 16K
|
||
}
|
||
|
||
_trap_stack_size = 2560; /* Размер стека обработчика перехватов.
|
||
Данный размер позволяет выполнить
|
||
до 32 вложенных вызовов при обработке
|
||
перехватов.
|
||
*/
|
||
|
||
_stack_size = 1280; /* Размер программного стека.
|
||
Данный размер позволяет выполнить
|
||
от 16 вложенных вызовов.
|
||
*/
|
||
|
||
/*
|
||
В данном разделе описывается размещение программы в памяти.
|
||
Программа разделяется на различные секции:
|
||
- секции исполняемого кода программа;
|
||
- секции статических переменных и массивов, значение которых должно быть
|
||
"вшито" в программу;
|
||
и т.п.
|
||
*/
|
||
|
||
SECTIONS
|
||
{
|
||
|
||
/*
|
||
В скриптах компоновщика есть внутренняя переменная, записываемая как '.'
|
||
Эта переменная называется "счетчиком адресов". Она хранит текущий адрес в
|
||
памяти.
|
||
В начале файла она инициализируется нулем. Добавляя новые секции, эта
|
||
переменная будет увеличиваться на размер каждой новой секции.
|
||
Если при размещении секций не указывается никакой адрес, они будут размещены
|
||
по текущему значению счетчика адресов.
|
||
Этой переменной можно присваивать значения, после этого, она будет
|
||
увеличиваться с этого значения.
|
||
Подробнее:
|
||
https://home.cs.colorado.edu/~main/cs1300/doc/gnu/ld_3.html#IDX338
|
||
*/
|
||
|
||
/*
|
||
Следующая команда сообщает, что начиная с адреса, которому в данных момент
|
||
равен счетчик адресов (в данный момент, начиная с нуля) будет находиться
|
||
секция .text итогового файла, которая состоит из секций .boot, а также всех
|
||
секций, начинающихся на .text во всех переданных компоновщику двоичных
|
||
файлах.
|
||
Дополнительно мы указываем, что данная секция должна быть размещена в
|
||
регионе "instr_mem".
|
||
*/
|
||
.text : {
|
||
PROVIDE(_start = .);
|
||
*(.boot)
|
||
*(.text*)
|
||
} > instr_mem
|
||
|
||
|
||
/*
|
||
Секция данных размещается аналогично секции инструкций за исключением
|
||
адреса загрузки в памяти (Load Memory Address, LMA). Поскольку память
|
||
инструкций и данных физически разделены, у них есть пересекающееся адресное
|
||
пространство, которое мы бы хотели использовать (поэтому в разделе MEMORY мы
|
||
указали что стартовые адреса обоих памятей равны нулю). Однако компоновщику
|
||
это не нравится, ведь как он будет размещать две разные секции в одно и то же
|
||
место. Поэтому мы ему сообщаем, с помощью оператора "AT", что загружать секцию
|
||
данных нужно на самом деле не по нулевому адресу, а по какому-то другому,
|
||
заведомо большему чем размер памяти инструкций, но процессор будет
|
||
использовать адреса, начинающиеся с нуля. Такой вариант компоновщика
|
||
устраивает и он собирает исполняемый файл без ошибок. Наша же задача,
|
||
загрузить итоговую секцию данных по нулевым адресам памяти данных.
|
||
*/
|
||
.data : AT (0x00800000) {
|
||
/*
|
||
Общепринято присваивать GP значение равное началу секции данных, смещенное
|
||
на 2048 байт вперед.
|
||
Благодаря относительной адресации со смещением в 12 бит, можно адресоваться
|
||
на начало секции данных, а также по всему адресному пространству вплоть до
|
||
4096 байт от начала секции данных, что сокращает объем требуемых для
|
||
адресации инструкций (практически не используются операции LUI, поскольку GP
|
||
уже хранит базовый адрес и нужно только смещение).
|
||
Подробнее:
|
||
https://groups.google.com/a/groups.riscv.org/g/sw-dev/c/60IdaZj27dY/m/s1eJMlrUAQAJ
|
||
*/
|
||
_gbl_ptr = . + 2048;
|
||
*(.*data*)
|
||
*(.sdata*)
|
||
} > data_mem
|
||
|
||
|
||
/*
|
||
Поскольку мы не знаем суммарный размер всех используемых секций данных,
|
||
перед размещением других секций, необходимо выровнять счетчик адресов по
|
||
4х-байтной границе.
|
||
*/
|
||
. = ALIGN(4);
|
||
|
||
|
||
/*
|
||
BSS (block started by symbol, неофициально его расшифровывают как
|
||
better save space) — это сегмент, в котором размещаются неинициализированные
|
||
статические переменные. В стандарте Си сказано, что такие переменные
|
||
инициализируются нулем (или NULL для указателей). Когда вы создаете
|
||
статический массив — он должен быть размещен в исполняемом файле.
|
||
Без bss-секции, этот массив должен был бы занимать такой же объем
|
||
исполняемого файла, какого объема он сам. Массив на 1000 байт занял бы
|
||
1000 байт в секции .data.
|
||
Благодаря секции bss, начальные значения массива не задаются, вместо этого
|
||
здесь только записываются названия переменных и их адреса.
|
||
Однако на этапе загрузки исполняемого файла теперь необходимо принудительно
|
||
занулить участок памяти, занимаемый bss-секцией, поскольку статические
|
||
переменные должны быть проинициализированы нулем.
|
||
Таким образом, bss-секция значительным образом сокращает объем исполняемого
|
||
файла (в случае использования неинициализированных статических массивов)
|
||
ценой увеличения времени загрузки этого файла.
|
||
Для того, чтобы занулить bss-секцию, в скрипте заводятся две переменные,
|
||
указывающие на начало и конец bss-секции посредством счетчика адресов.
|
||
Подробнее:
|
||
https://en.wikipedia.org/wiki/.bss
|
||
|
||
Дополнительно мы указываем, что данная секция должна быть размещена в
|
||
регионе "data_mem".
|
||
*/
|
||
_bss_start = .;
|
||
.bss : {
|
||
*(.bss*)
|
||
*(.sbss*)
|
||
} > data_mem
|
||
_bss_end = .;
|
||
|
||
|
||
/*=================================
|
||
Секция аллоцированных данных завершена, остаток свободной памяти отводится
|
||
под программный стек, стек прерываний и (возможно) кучу. В соглашении о
|
||
вызовах архитектуры RISC-V сказано, что стек растет снизу вверх, поэтому
|
||
наша цель разместить его в самых последних адресах памяти.
|
||
Поскольку стеков у нас два, в самом низу мы разместим стек прерываний, а
|
||
над ним программный стек. При этом надо обеспечить защиту программного
|
||
стека от наложения на него стека прерываний.
|
||
Однако перед этим, мы должны убедиться, что под оба стека хватит места.
|
||
=================================
|
||
*/
|
||
|
||
/* Мы хотим гарантировать, что под стек останется место */
|
||
ASSERT(. < (LENGTH(data_mem) - _trap_stack_size - _stack_size),
|
||
"Program size is too big")
|
||
|
||
/* Перемещаем счетчик адресов над стеком прерываний (чтобы после мы могли
|
||
использовать его в вызове ALIGN) */
|
||
. = LENGTH(data_mem) - _trap_stack_size;
|
||
|
||
/*
|
||
Размещаем указатель программного стека так близко к границе стека
|
||
прерываний, насколько можно с учетом требования о выравнивании адреса
|
||
стека до 16 байт.
|
||
Подробнее:
|
||
https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
|
||
*/
|
||
_stack_ptr = ALIGN(16) <= LENGTH(data_mem) - _trap_stack_size?
|
||
ALIGN(16) : ALIGN(16) - 16;
|
||
ASSERT(_stack_ptr <= LENGTH(data_mem) - _trap_stack_size,
|
||
"SP exceed memory size")
|
||
|
||
/* Перемещаем счетчик адресов в конец памяти (чтобы после мы могли
|
||
использовать его в вызове ALIGN) */
|
||
. = LENGTH(data_mem);
|
||
|
||
/*
|
||
Обычно память имеет размер, кратный 16, но на случай, если это не так, мы
|
||
делаем проверку, после которой мы либо остаемся в самом конце памяти (если
|
||
конец кратен 16), либо поднимаемся на 16 байт вверх от края памяти,
|
||
округленного до 16 в сторону большего значения
|
||
*/
|
||
_trap_stack_ptr = ALIGN(16) <= LENGTH(data_mem) ? ALIGN(16) : ALIGN(16) - 16;
|
||
ASSERT(_trap_stack_ptr <= LENGTH(data_mem), "ISP exceed memory size")
|
||
}
|