mirror of
https://github.com/MPSU/APS.git
synced 2025-09-15 17:20:10 +00:00
ЛР16. Рефактор методички (#97)
* ЛР16. Рефактор методички * Apply suggestions from code review
This commit is contained in:
committed by
GitHub
parent
59510a522b
commit
73e521687e
@@ -26,7 +26,6 @@ OBJS = $(src) startup.o core_list_join.o core_matrix.o core_portme.o core_state.
|
||||
LINK_SCRIPT = linker_script.ld
|
||||
OUTPUT = coremark
|
||||
OUTPUT_PROD = $(addprefix $(OUTPUT), .mem _instr.mem _data.mem .elf _disasm.S)
|
||||
# OUTPUT_PROD :=$(OUTPUT_PROD) $(addprefix tb_$(OUTPUT), .mem _instr.mem _data.mem .elf _disasm.S)
|
||||
|
||||
INC_DIRS = "./"
|
||||
SRC_DIR = ./src
|
||||
@@ -44,9 +43,6 @@ harvard: $(OUTPUT).elf $(OUTPUT)_disasm.S size
|
||||
# $< означает "первая зависимость"
|
||||
${OBJCOPY} -O verilog --verilog-data-width=4 -j .data -j .sdata -j .bss $< $(OUTPUT)_data.mem
|
||||
${OBJCOPY} -O verilog --verilog-data-width=4 -j .text $< $(OUTPUT)_instr.mem
|
||||
${OBJCOPY} -O verilog -j .data -j .sdata -j .bss $< tb_$(OUTPUT)_data.mem
|
||||
${OBJCOPY} -O verilog -j .text $< tb_$(OUTPUT)_instr.mem
|
||||
sed -i '1d' $(OUTPUT)_data.mem
|
||||
|
||||
princeton: $(OUTPUT).elf $(OUTPUT)_disasm.S size
|
||||
${OBJCOPY} -O verilog --verilog-data-width=4 --remove-section=.comment $< $(OUTPUT).mem
|
||||
|
@@ -22,12 +22,14 @@
|
||||
|
||||
Для подсчета производительности, кормарк опирается на функцию, возвращающую текущее время, поэтому для оценки производительности нам потребуется вспомогательное периферийное устройство: таймер.
|
||||
|
||||
Для вывода результатов тестирования, необходимо описать способ, которым кормарк сможет выводить очередной символ сообщения — для этого мы будем использовать контроллер UART из предыдущих лабораторных работ.
|
||||
Для вывода результатов тестирования, необходимо описать способ, которым кормарк сможет выводить очередной символ сообщения — для этого мы будем использовать контроллер UART из ЛР№13.
|
||||
|
||||
Кроме того, скомпилированная без оптимизаций программа будет занимать чуть более 32KiB, поэтому нам потребуется изменить размер памяти инструкций.
|
||||
|
||||
Таким образом, для того чтобы запустить данную программу, нам необходимо выполнить как аппаратные изменения процессорной системы (добавить таймер и (если отсутствует) контроллер UART), так и программные изменения самого кормарка (для этого в нем предусмотрены специальные платформозависимые файлы, в которых объявлены функции, реализацию которых нам необходимо выполнить).
|
||||
|
||||
Говорят, что лучшей проверкой процессора на наличие ошибок является попытка запустить на нем ядро Linux. Наша процессорная система на это в принципе не рассчитана (поскольку для запуска Linux нужна поддержка нескольких дополнительных расширений), поэтому coremark можно по праву считать "бюджетным" аналогом проверки процессора на прочность.
|
||||
|
||||
## Задание
|
||||
|
||||
1. Реализовать модуль-контроллер "таймер".
|
||||
@@ -41,9 +43,9 @@
|
||||
|
||||
### Таймер
|
||||
|
||||
Разберемся с тем, как будет работать наш таймер. По сути, это просто системный счетчик (не путайте с программным счетчиком), непрерывно считающий такты с момента последнего сброса. Системным он называется потому, что работает на системной тактовой частоте. Значения частот, на которых работают процессорные системы сравнимы с 32-битными значениями, поэтому системный счетчик должен быть 64-битным. Для измерения времени мы будем засекать значение счетчика на момент начала отсчета и значение счетчика в конце отсчета. Зная тактовую частоту и разность между значениями счетчика мы с легкостью сможем вычислить прошедшее время. При этом нужно обеспечить счетчик такой разрядностью, чтобы он точно не смог переполниться.
|
||||
Разберемся с тем, как будет работать наш таймер. По сути, это просто системный счетчик (не путайте с программным счетчиком), непрерывно считающий такты с момента последнего сброса. Системным он называется потому, что работает на системной тактовой частоте. Значения частот, на которых работают процессорные системы сопоставимы с 32-битными значениями, поэтому системный счетчик должен быть 64-битным. Для измерения времени мы будем засекать значение счетчика на момент начала отсчета и значение счетчика в конце отсчета. Зная тактовую частоту и разность между значениями счетчика мы с легкостью сможем вычислить прошедшее время. При этом нужно обеспечить счетчик такой разрядностью, чтобы он точно не смог переполниться.
|
||||
|
||||
Поскольку мы уже назвали данный модуль "таймером", чтобы тот не был слишком простым, давайте добавим ему функциональности: пускай это будет устройство, способное генерировать прерывание через заданное число тактов. Таким образом, процессорная система сможет засекать время без постоянного опроса счетчика.
|
||||
Поскольку мы уже назвали данный модуль "таймером", чтобы тот не был слишком простым, давайте добавим ему функциональности: пускай это будет устройство, способное генерировать прерывание через заданное число тактов. Таким образом, процессорная система сможет засекать время без постоянного опроса счетчика. Для работы кормарка эта функциональность не нужна — если ее реализация окажется слишком сложной для вас, просто создайте системный счетчик, инкрементирующийся каждый такт, с доступом на чтение по адресу `32'h0`.
|
||||
|
||||
Было бы удобно, чтобы мы могли управлять тем, каким образом данный модуль будет генерировать такое прерывание: однократно, заданное число раз, или же бесконечно, пока тот не остановят.
|
||||
|
||||
@@ -82,6 +84,8 @@ module timer_sb_ctrl(
|
||||
);
|
||||
```
|
||||
|
||||
Обратите внимание, что у модуля нет сигнала interrupt_return_i. Модуль будет генерировать прерывания ровно на 1 такт. Если процессор в этот момент не будет готов обработать прерывания (обрабатывая в этот момент какой-либо другой перехват) — запрос будет сразу же пропущен и таймер начнет отсчитывать следующий.
|
||||
|
||||
Для работы данного контроллера потребуются следующие сигналы:
|
||||
|
||||
```SystemVerilog
|
||||
@@ -98,11 +102,13 @@ logic [63:0] system_counter_at_start;
|
||||
- `OFF` — отключен (не генерирует прерывания)
|
||||
- `NTIMES` — включен до тех пор, пока не сгенерирует N прерываний (Значение N хранится в регистре `repeat_counter` и обновляется после каждого сгенерированного прерывания). После генерации N прерываний, переходит в режим `OFF`.
|
||||
- `FOREVER` — бесконечная генерация прерываний. Не отключится, пока режим работы прерываний не будет изменен.
|
||||
- `next_mode` — комбинационный сигнал, который подается на вход записи в регистр `mode` (аналог `next_state` из предыдущей лабораторной работы).
|
||||
- `repeat_counter` — регистр, ассоциированный с адресом `0x14`. Количество повторений для режима `NTIMES`. Уменьшается в момент генерации прерывания в этом режиме.
|
||||
- `next_mode` — комбинационный сигнал, который подается на вход записи в регистр `mode` (аналог `next_state` из ЛР№15). Данный сигнал меняется только запросами на запись по адресу `0x10` или в случае, если `repeat_counter == 0` в режиме `NTIMES`. Поскольку этому сигналу можно присваивать только значения сигналов такого же типа (`timer_mods`), либо константы из перечисления, запросы на запись можно реализовать через блок `case` (где перебираются 3 возможных значения `write_data_i`).
|
||||
- `repeat_counter` — регистр, ассоциированный с адресом `0x14`. Количество повторений для режима `NTIMES`. Уменьшается в момент генерации прерывания в этом режиме в случае, если еще не равен нулю.
|
||||
- `system_counter_at_start` — неархитектурный регистр, хранящий значение системного счетчика на момент начала отсчета таймера. Обновляется при генерации прерывания (если это не последнее прерывание в режиме `NTIMES`) и при запросе на запись в регистр `mode` значения не `OFF`.
|
||||
|
||||
Для подключения данного таймера к системной шине, мы воспользуемся первым свободным базовым адресом, оставшимся после ЛР13: `0x08`. Таким образом, для обращения к системному счетчику, процессор будет использовать адрес `0x08000000` для обращения к регистру `delay` `0x08000004` и т.п.
|
||||
Выходной сигнал interrupt_request_o должен быть равен единице, если текущий режим работы не `OFF`, а сумма `system_counter_at_start` и `delay` равна `system_counter`.
|
||||
|
||||
Для подключения данного таймера к системной шине, мы воспользуемся первым свободным базовым адресом, оставшимся после ЛР13: `0x08`. Таким образом, для обращения к системному счетчику, процессор будет использовать адрес `0x08000000` для обращения к регистру `delay` `0x08000008` и т.п.
|
||||
|
||||
### Настройка Coremark
|
||||
|
||||
@@ -132,7 +138,7 @@ barebones_clock()
|
||||
}
|
||||
```
|
||||
|
||||
После ЛР14 вы уже должны представлять, что здесь происходит. Мы создали указатель с абсолютным адресом `0x08000000` — адресом системного счетчика. Разыменование данного указателя вернет текущее значение системного счетчика, что и должно быть результатом вызова этой функции. Поскольку тест закочнится менее чем за секунду, не обязательно загружать значение старших 32 бит (они будут не равны нулю только спустя 2³²тактов / 10⁶тактов/с ≈ 429c).
|
||||
После ЛР14 вы уже должны представлять, что здесь происходит. Мы создали указатель с абсолютным адресом `0x08000000` — адресом системного счетчика. Разыменование данного указателя вернет текущее значение системного счетчика, что и должно быть результатом вызова этой функции. Поскольку тест закончится менее чем за секунду, не обязательно загружать значение старших 32 бит (они будут не равны нулю только спустя 2³²тактов / 10⁶тактов/с ≈ 429c).
|
||||
|
||||
Для того, чтобы корректно преобразовать тики системного счетчика во время, используется функция [`time_in_secs`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.c#L117), которая уже реализована, но для работы которой нужно определить макрос `CLOCKS_PER_SEC`, характеризующий тактовую частоту, на которой работает процессор. Давайте определим данный макрос сразу над макросом [`EE_TICKS_PER_SEC`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.c#L62):
|
||||
|
||||
@@ -234,7 +240,7 @@ sed -i '1d' coremark_data.mem
|
||||
|
||||
### Изменение размера памяти инструкций
|
||||
|
||||
Как видите, размер секции инструкций превышает 32KiB на 1556 байт (32768—34324). Поэтому на время оценки моделирования, нам придется увеличить размер памяти инструкций до 64KiB, изменив число **слов** памяти инструкций до 16384. При этом необходимо изменить диапазон бит адреса, используемых для чтения инструкции из памяти с `[11:2]` на `[15:2]`.
|
||||
Как видите, размер секции инструкций превышает 32KiB на 1556 байт (32768—34000). Поэтому на время оценки моделирования, нам придется увеличить размер памяти инструкций до 64KiB, изменив значение параметра `INSTR_MEM_SIZE_BYTES` в пакете `memory_pkg` до значения `32'h10000`. Размер памяти данных также необходимо увеличить, изменив значение параметра `DATA_MEM_SIZE_BYTES` до `32'h4000`.
|
||||
|
||||
Обратите внимание, что увеличение размера памяти в 16 раз приведет к значительному увеличению времени синтеза устройства, поэтому данное изменение мы производим исключительно на время поведенческого моделирования.
|
||||
|
||||
@@ -242,13 +248,14 @@ sed -i '1d' coremark_data.mem
|
||||
|
||||
Программирование 32KiB по UART займет ощутимое время, поэтому вам предлагается проинициализировать память инструкций и данных "по старинке" через системные функции `$readmemh`.
|
||||
|
||||
Если все было сделано без ошибок, то примерно на `276ms` времени моделирования вам начнется выводиться сообщение вида:
|
||||
Если все было сделано без ошибок, то примерно через `300ms` после снятия сигнала сброса с ядра процессора выход `tx_o` начнет быстро менять свое значение, сигнализируя о выводе результатов программы, которые отобразятся в `tcl console` примерно еще через `55ms` в виде:
|
||||
|
||||
```text
|
||||
2K performance run parameters for coremark.
|
||||
CoreMark Size : 666
|
||||
Total ticks : 2574834
|
||||
Total time (secs): <скрыто то получения результатов моделирования>
|
||||
Iterations/Sec : <скрыто то получения результатов моделирования>
|
||||
Total time (secs): <скрыто до получения результатов моделирования>
|
||||
Iterations/Sec : <скрыто до получения результатов моделирования>
|
||||
ERROR! Must execute for at least 10 secs for a valid result!
|
||||
Iterations : 1
|
||||
Compiler version : GCC13.2.0
|
||||
@@ -259,71 +266,80 @@ seedcrc : 0x29f4
|
||||
[0]crcmatrix : 0x1fd7
|
||||
[0]crcstate : 0x8e3a
|
||||
[0]crcfinal : 0x7704
|
||||
Correct operation validated. See README.md for run and reporting rules.
|
||||
Errors detected
|
||||
```
|
||||
|
||||
(вывод сообщения будет завершен приблизительно на `335ms` времени моделирования).
|
||||
(вывод сообщения будет завершен приблизительно на `355ms` времени моделирования).
|
||||
|
||||
## Порядок выполнения задания
|
||||
|
||||
1. [Опишите](#таймер) таймер в виде модуля `timer_sb_ctrl`.
|
||||
2. Проверьте описанный модуль с помощью тестового окружения [tb_timer](tb_timer.sv).
|
||||
3. Подключите `timer_sb_ctrl` к системной шине. Сигнал прерывания этого модуля подключать не нужно.
|
||||
2.1 В случае, если до этого в ЛР13 вашим устройством вывода было не UART TX, вам необходимо подключить к системной шине и готовый модуль [uart_tx_sb_ctrl](../Made-up%20modules/lab_13.uart_tx_sb_ctrl.sv).
|
||||
4. Получите исходники программы Coremark. Для этого можно либо склонировать репозиторий, либо скачать его в виде архива со страницы: [https://github.com/eembc/coremark](https://github.com/eembc/coremark).
|
||||
5. Добавьте реализацию платформозависимых функций программы coremark. Для этого в папке `barebones` необходимо:
|
||||
2. Проверьте описанный модуль с помощью тестового окружения [lab_16_tb_timer](lab_16_tb_timer.sv).
|
||||
3. Подключите `timer_sb_ctrl` к системной шине.
|
||||
1. Ко входу `rst_i` модуля подключите сигнал `core_reset_o` программатора. Таким образом, системный счетчик начнет работать только когда память системы будет проинициализирована.
|
||||
2. Сигнал прерывания этого модуля подключать не обязательно, т.к. кормарк будет осуществлять чтение путем опроса системного счетчика, а не по прерыванию.
|
||||
4. В случае, если до этого в ЛР13 вашим устройством вывода было не UART TX, вам необходимо подключить к системной шине готовый модуль [uart_tx_sb_ctrl](../Made-up%20modules/lab_13.uart_tx_sb_ctrl.sv).
|
||||
5. Получите исходники программы Coremark. Для этого можно либо склонировать [репозиторий](https://github.com/eembc/coremark/tree/d5fad6bd094899101a4e5fd53af7298160ced6ab), либо скачать его в виде архива.
|
||||
6. Добавьте реализацию платформозависимых функций программы coremark. Для этого в папке `barebones` необходимо:
|
||||
1. в файле `core_portme.c`:
|
||||
1. [реализовать](#1-реализация-функции-измеряющей-время) функцию `barebones_clock`, возвращающую текущее значение системного счетчика;
|
||||
2. объявить макрос `CLOCKS_PER_SEC`, характеризующий тактовую частоту процессора;
|
||||
3. [реализовать](#3-реализация-функции-первичной-настройки) функцию `portable_init`, выполняющую первичную инициализацию периферийных устройств до начала теста;
|
||||
2. в файле `ee_printf.c` [реализовать](#2-реализация-вывода-очередного-символа-сообщения) функцию `uart_send_char`, отвечающую за отправку очередного символа сообщения о результате.
|
||||
6. Добавьте с заменой в корень программы файлы [Makefile](Makefile), [linker_script.ld](linker_script.ld) и [startup.S](../14.%20Programming/startup.S).
|
||||
7. Скомпилируйте программу вызовом `make`.
|
||||
7. Добавьте с заменой в корень программы файлы [Makefile](Makefile), [linker_script.ld](linker_script.ld) и [startup.S](../14.%20Programming/startup.S).
|
||||
8. Скомпилируйте программу вызовом `make`.
|
||||
1. Если кросскомпилятор расположен не в директории `C:/riscv_cc`, перед вызовом `make` вам необходимо соответствующим образом отредактировать первую строчку в `Makefile`.
|
||||
2. В случае отсутствия на компьютере утилиты `make`, вы можете самостоятельно скомпилировать программу вызовом команд, представленных в разделе ["Компиляция"](#компиляция).
|
||||
8. Временно измените размер памяти инструкций до 64KiB.
|
||||
1. Для этого необходимо изменить размер памяти инструкций с 1024 слов до 16384 слов.
|
||||
2. Кроме того, необходимо изменить используемые индексы адреса в памяти с `[11:2]` на `[15:2]`.
|
||||
9. Проинициализируйте память инструкций и память данных файлами "coremark_instr.mem" и "coremark_data.mem", полученными в ходе компиляции программы.
|
||||
10. Выполните моделирование системы с помощью модуля [tb_coremark](tb_coremark).
|
||||
1. Результаты теста будут выведены приблизительно на `335ms` времени моделирования.
|
||||
9. Временно измените размер памяти инструкций до 64KiB, а памяти данных до 16KiB, изменив значение параметров `INSTR_MEM_SIZE_BYTES` и `DATA_MEM_SIZE_BYTES` в пакете `memory_pkg` на `32'h10_000` и `32'h4_000` соответственно.
|
||||
10. Проинициализируйте память инструкций и память данных файлами "coremark_instr.mem" и "coremark_data.mem", полученными в ходе компиляции программы.
|
||||
1. Память можно проинициализировать двумя путями: с помощью вызова системной функции $readmemh, либо же с помощью программатора.Однако имейте в виду, что инициализация памятей с помощью программатора будет достаточно долго моделироваться в виду большого объема программы.
|
||||
2. В случае, если инициализация будет осуществляться посредством $readmemh, не забудьте удалить первую строчку со стартовым адресом из файла, инициализирующего память данных.
|
||||
3. В случае, если инициализация будет осуществляться с помощью программатора, используйте вспомогательные вызовы `program_region` из пакета `bluster_pkg`, как это было сделано в `lab_15_tb_system`.
|
||||
4. В исходном виде тестбенч описан под инициализацию памяти посредством $readmemh.
|
||||
11. Выполните моделирование системы с помощью модуля [tb_coremark](tb_coremark).
|
||||
1. Результаты теста будут выведены приблизительно на `355ms` времени моделирования.
|
||||
|
||||
## Оценка производительности
|
||||
|
||||
<details>
|
||||
<summary>11. Прочти меня после успешного завершения моделирования </summary>
|
||||
<summary>Прочти меня после успешного завершения моделирования </summary>
|
||||
|
||||
Итак, вы получили сообщение вида:
|
||||
|
||||
```text
|
||||
2K performance run parameters for coremark.
|
||||
CoreMark Size : 666
|
||||
Total ticks : 2574834
|
||||
Total time (secs): 0.257483
|
||||
Iterations/Sec : 3.883746
|
||||
Total ticks : 2901822
|
||||
Total time (secs): 0.290182
|
||||
Iterations/Sec : 3.446111
|
||||
ERROR! Must execute for at least 10 secs for a valid result!
|
||||
Iterations : 1
|
||||
Compiler version : GCC13.2.0
|
||||
Compiler flags : -march=rv32i_zicsr -mabi=ilp32
|
||||
Memory location : STACK
|
||||
seedcrc : 0x29f4
|
||||
[0]crclist : 0x7704
|
||||
seedcrc : 0xe9f5
|
||||
[0]crclist : 0xe714
|
||||
[0]crcmatrix : 0x1fd7
|
||||
[0]crcstate : 0x8e3a
|
||||
[0]crcfinal : 0x7704
|
||||
Correct operation validated. See README.md for run and reporting rules.
|
||||
[0]crcfinal : 0xe714
|
||||
Errors detected
|
||||
```
|
||||
|
||||
Не обращайте внимание на строчку "ERROR! Must execute for at least 10 secs for a valid result!". Программа считает, что для корректных результатов, необходимо крутить ее по кругу в течении минимум 10 секунд, однако по большей части это требование необходимо для более достоверного результата у систем с кэшем/предсказателями переходов и прочими блоками, которые могут изменить количество тактов на прохождение между итерациями. Наш однотактный процессор будет вести себя одинаково на каждом круге, поэтому нет смысла в дополнительном времени моделирования.
|
||||
Не обращайте внимание на строки "ERROR! Must execute for at least 10 secs for a valid result!" и "Errors detected". Программа считает, что для корректных результатов, необходимо крутить ее по кругу в течении минимум 10 секунд, однако по большей части это требование необходимо для более достоверного результата у систем с кэшем/предсказателями переходов и прочими блоками, которые могут изменить количество тактов на прохождение между итерациями. Наш однотактный процессор будет вести себя одинаково на каждом круге, поэтому нет смысла в дополнительном времени моделирования. Тем не менее, если вы захотите получить результаты, не содержащих сообщения об ошибках, измените число итераций в файле `core_portme.h` до 45.
|
||||
|
||||
Нас интересует строка:
|
||||
|
||||
```text
|
||||
Iterations/Sec : 3.883746
|
||||
Iterations/Sec : 3.446111
|
||||
```
|
||||
|
||||
Это и есть так называемый "кормарк" — метрика данной программы. Результат нашего процессора: 3.88 кормарка.
|
||||
Это и есть так называемый "кормарк" — метрика данной программы. Результат нашего процессора: ~3.45 кормарка.
|
||||
|
||||
Обычно, для сравнения между собой нескольких реализаций микроархитектур, более достоверной считается величина "кормарк / МГц", т.е. значение кормарка, поделённое на тактовую частоту процессора. Дело в том, что можно реализовать какую-нибудь очень сложную архитектуру, которая будет выдавать очень хороший кормарк, но при этом будет иметь очень низкую частоту. Более того, при сравнении с другими результатами, необходимо учитывать флаги оптимизации, которые использовались при компиляции программы, поскольку они также влияют на результат.
|
||||
Обычно, для сравнения между собой нескольких реализаций микроархитектур, более достоверной считается величина "кормарк / МГц", т.е. значение кормарка, поделённое на тактовую частоту процессора, поскольку время прохождения теста напрямую зависит от тактовой частоты. Это значит, что чип с менее удачной микроархитектурной реализацией может выиграть по кормарку просто потому что он был выпущен по лучшей технологии, позволяющей запустить его на больших частотах. Кормарк/МГц нормализует результаты, позволяя сравнивать микроархитектурные решения не заботясь о том, на какой частоте был получен результат.
|
||||
|
||||
Мы не будем уходить в дебри темных паттернов маркетинга и вместо этого будет оценивать производительность в лоб: сколько кормарков в секунду смог прогнать наш процессор в сравнении с представленными результатами других систем вне зависимости от их оптимизаций.
|
||||
Более того, при сравнении с другими результатами, необходимо учитывать флаги оптимизации, которые использовались при компиляции программы, поскольку они также влияют на результат. Например, если собрать кормарк с уровнем оптимизаций `-O1`, результат нашей системы скакнет до 11.23 кормарков, что всего лишь является следствием того, что программа стала меньше обращаться к памяти вследствие оптимизаций. Именно поэтому результаты кормарка указываются вместе с опциями, с которыми тот был собран.
|
||||
|
||||
Мы не будем уходить в дебри темных паттернов маркетинга и вместо этого будет оценивать производительность в лоб: сколько кормарков в секунду смог прогнать наш процессор без каких-либо оптимизаций в сравнении с представленными результатами других систем вне зависимости от их оптимизаций.
|
||||
|
||||
Таблица опубликованных результатов находится по адресу: [https://www.eembc.org/coremark/scores.php](https://www.eembc.org/coremark/scores.php). Нам необходимо отсортировать эту таблицу по столбцу `CoreMark`, кликнув по нему.
|
||||
|
||||
|
@@ -8,18 +8,28 @@
|
||||
See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details.
|
||||
* ------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
OUTPUT_FORMAT("elf32-littleriscv") /* Указываем порядок следования байт */
|
||||
|
||||
ENTRY(_start) /* мы сообщаем компоновщику, что первая
|
||||
исполняемая процессором инструкция
|
||||
находится у метки "start"
|
||||
находится у метки "_start"
|
||||
*/
|
||||
|
||||
_text_size = 0x10000; /* Размер памяти инстр.: 16KiB */
|
||||
_data_base_addr = _text_size; /* Стартовый адрес секции данных */
|
||||
_data_size = 0x04000; /* Размер памяти данных: 16KiB */
|
||||
|
||||
_data_end = _data_base_addr + _data_size;
|
||||
/*
|
||||
В данном разделе указывается структура памяти:
|
||||
Сперва идет регион "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; /* Размер стека обработчика перехватов.
|
||||
Данный размер позволяет выполнить
|
||||
@@ -29,25 +39,9 @@ _trap_stack_size = 2560; /* Размер стека обрабо
|
||||
|
||||
_stack_size = 1280; /* Размер программного стека.
|
||||
Данный размер позволяет выполнить
|
||||
до 16 вложенных вызовов.
|
||||
от 16 вложенных вызовов.
|
||||
*/
|
||||
|
||||
/*
|
||||
В данном разделе указывается структура памяти:
|
||||
Сперва идет регион "rom", являющийся read-only памятью с исполняемым кодом
|
||||
(об этом говорят атрибуты 'r' и 'x' соответственно). Этот регион начинается
|
||||
с адреса 0x00000000 и занимает _text_size байт.
|
||||
Далее идет регион "ram", начинающийся с адреса _data_base_addr и занимающий
|
||||
_data_size байт. Этот регион является памятью, противоположной региону "ram"
|
||||
(в том смысле, что это не read-only память с исполняемым кодом).
|
||||
*/
|
||||
MEMORY
|
||||
{
|
||||
rom (x) : ORIGIN = 0x00000000, LENGTH = _text_size
|
||||
ram (!x) : ORIGIN = _data_base_addr, LENGTH = _data_size
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
В данном разделе описывается размещение программы в памяти.
|
||||
Программа разделяется на различные секции:
|
||||
@@ -59,7 +53,7 @@ MEMORY
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
PROVIDE( _start = 0x00000000 ); /* Позиция start в памяти
|
||||
|
||||
/*
|
||||
В скриптах компоновщика есть внутренняя переменная, записываемая как '.'
|
||||
Эта переменная называется "счетчиком адресов". Она хранит текущий адрес в
|
||||
@@ -81,32 +75,31 @@ SECTIONS
|
||||
секций, начинающихся на .text во всех переданных компоновщику двоичных
|
||||
файлах.
|
||||
Дополнительно мы указываем, что данная секция должна быть размещена в
|
||||
регионе "rom".
|
||||
регионе "instr_mem".
|
||||
*/
|
||||
.text : {*(.boot) *(.text*)} >rom
|
||||
.text : {
|
||||
PROVIDE(_start = .);
|
||||
*(.boot)
|
||||
*(.text*)
|
||||
} > instr_mem
|
||||
|
||||
|
||||
/*
|
||||
Поскольку мы не знаем суммарного размера получившейся секции, мы проверяем
|
||||
что не вышли за границы памяти инструкций и переносим счетчик адресов за
|
||||
пределы памяти инструкций в область памяти данных.
|
||||
Дополнительно мы указываем, что данная секция должна быть размещена в
|
||||
регионе "ram".
|
||||
Секция данных размещается аналогично секции инструкций за исключением
|
||||
адреса загрузки в памяти (Load Memory Address, LMA). Поскольку память
|
||||
инструкций и данных физически разделены, у них есть пересекающееся адресное
|
||||
пространство, которое мы бы хотели использовать (поэтому в разделе MEMORY мы
|
||||
указали что стартовые адреса обоих памятей равны нулю). Однако компоновщику
|
||||
это не нравится, ведь как он будет размещать две разные секции в одно и то же
|
||||
место. Поэтому мы ему сообщаем, с помощью оператора "AT", что загружать секцию
|
||||
данных нужно на самом деле не по нулевому адресу, а по какому-то другому,
|
||||
заведомо большему чем размер памяти инструкций, но процессор будет
|
||||
использовать адреса, начинающиеся с нуля. Такой вариант компоновщика
|
||||
устраивает и он собирает исполняемый файл без ошибок. Наша же задача,
|
||||
загрузить итоговую секцию данных по нулевым адресам памяти данных.
|
||||
*/
|
||||
ASSERT(. < _text_size, ".text section exceeds instruction memory size")
|
||||
. = _data_base_addr;
|
||||
|
||||
/*
|
||||
Следующая команда сообщает, что начиная с адреса, которому в данных момент
|
||||
равен счетчик адресов (_data_base_addr) будет находиться секция .data
|
||||
итогового файла, которая состоит из секций всех секций, начинающихся
|
||||
на .data во всех переданных компоновщику двоичных файлах.
|
||||
Дополнительно мы указываем, что данная секция должна быть размещена в
|
||||
регионе "ram".
|
||||
*/
|
||||
.data : {*(.*data*)} >ram
|
||||
|
||||
/*
|
||||
.data : AT (0x00800000) {
|
||||
/*
|
||||
Общепринято присваивать GP значение равное началу секции данных, смещенное
|
||||
на 2048 байт вперед.
|
||||
Благодаря относительной адресации со смещением в 12 бит, можно адресоваться
|
||||
@@ -116,13 +109,16 @@ 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*)
|
||||
*(.sdata*)
|
||||
} > data_mem
|
||||
|
||||
|
||||
/*
|
||||
Поскольку мы не знаем суммарный размер всех используемых секций данных,
|
||||
перед размещением других секций, необходимо выравнять счетчик адресов по
|
||||
перед размещением других секций, необходимо выровнять счетчик адресов по
|
||||
4х-байтной границе.
|
||||
*/
|
||||
. = ALIGN(4);
|
||||
@@ -151,10 +147,13 @@ SECTIONS
|
||||
https://en.wikipedia.org/wiki/.bss
|
||||
|
||||
Дополнительно мы указываем, что данная секция должна быть размещена в
|
||||
регионе "ram".
|
||||
регионе "data_mem".
|
||||
*/
|
||||
_bss_start = .;
|
||||
.bss : {*(.*bss*)} >ram
|
||||
.bss : {
|
||||
*(.bss*)
|
||||
*(.sbss*)
|
||||
} > data_mem
|
||||
_bss_end = .;
|
||||
|
||||
|
||||
@@ -166,20 +165,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;
|
||||
|
||||
/*
|
||||
Размещаем указатель программного стека так близко к границе стека
|
||||
@@ -188,13 +184,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, но на случай, если это не так, мы
|
||||
@@ -202,6 +199,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")
|
||||
}
|
||||
|
@@ -31,8 +31,8 @@ _irq_config:
|
||||
li t1, -1 # -1 (все биты равны 1) означает, что разрешены все прерывания
|
||||
la t2, _trap_stack_ptr
|
||||
csrw mtvec, t0
|
||||
csrw mie, t1
|
||||
csrw mscratch, t2
|
||||
csrw mie, t1
|
||||
|
||||
# Вызов функции main
|
||||
_main_call:
|
||||
|
Reference in New Issue
Block a user