ЛР16. Рефактор методички (#97)

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

* Apply suggestions from code review
This commit is contained in:
Andrei Solodovnikov
2024-07-29 16:15:48 +03:00
committed by GitHub
parent 59510a522b
commit 73e521687e
4 changed files with 115 additions and 106 deletions

View File

@@ -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 LINK_SCRIPT = linker_script.ld
OUTPUT = coremark OUTPUT = coremark
OUTPUT_PROD = $(addprefix $(OUTPUT), .mem _instr.mem _data.mem .elf _disasm.S) 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 = "./" INC_DIRS = "./"
SRC_DIR = ./src 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 .data -j .sdata -j .bss $< $(OUTPUT)_data.mem
${OBJCOPY} -O verilog --verilog-data-width=4 -j .text $< $(OUTPUT)_instr.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 princeton: $(OUTPUT).elf $(OUTPUT)_disasm.S size
${OBJCOPY} -O verilog --verilog-data-width=4 --remove-section=.comment $< $(OUTPUT).mem ${OBJCOPY} -O verilog --verilog-data-width=4 --remove-section=.comment $< $(OUTPUT).mem

View File

@@ -22,12 +22,14 @@
Для подсчета производительности, кормарк опирается на функцию, возвращающую текущее время, поэтому для оценки производительности нам потребуется вспомогательное периферийное устройство: таймер. Для подсчета производительности, кормарк опирается на функцию, возвращающую текущее время, поэтому для оценки производительности нам потребуется вспомогательное периферийное устройство: таймер.
Для вывода результатов тестирования, необходимо описать способ, которым кормарк сможет выводить очередной символ сообщения — для этого мы будем использовать контроллер UART из предыдущих лабораторных работ. Для вывода результатов тестирования, необходимо описать способ, которым кормарк сможет выводить очередной символ сообщения — для этого мы будем использовать контроллер UART из ЛР№13.
Кроме того, скомпилированная без оптимизаций программа будет занимать чуть более 32KiB, поэтому нам потребуется изменить размер памяти инструкций. Кроме того, скомпилированная без оптимизаций программа будет занимать чуть более 32KiB, поэтому нам потребуется изменить размер памяти инструкций.
Таким образом, для того чтобы запустить данную программу, нам необходимо выполнить как аппаратные изменения процессорной системы (добавить таймер и (если отсутствует) контроллер UART), так и программные изменения самого кормарка (для этого в нем предусмотрены специальные платформозависимые файлы, в которых объявлены функции, реализацию которых нам необходимо выполнить). Таким образом, для того чтобы запустить данную программу, нам необходимо выполнить как аппаратные изменения процессорной системы (добавить таймер и (если отсутствует) контроллер UART), так и программные изменения самого кормарка (для этого в нем предусмотрены специальные платформозависимые файлы, в которых объявлены функции, реализацию которых нам необходимо выполнить).
Говорят, что лучшей проверкой процессора на наличие ошибок является попытка запустить на нем ядро Linux. Наша процессорная система на это в принципе не рассчитана (поскольку для запуска Linux нужна поддержка нескольких дополнительных расширений), поэтому coremark можно по праву считать "бюджетным" аналогом проверки процессора на прочность.
## Задание ## Задание
1. Реализовать модуль-контроллер "таймер". 1. Реализовать модуль-контроллер "таймер".
@@ -41,9 +43,9 @@
### Таймер ### Таймер
Разберемся с тем, как будет работать наш таймер. По сути, это просто системный счетчик (не путайте с программным счетчиком), непрерывно считающий такты с момента последнего сброса. Системным он называется потому, что работает на системной тактовой частоте. Значения частот, на которых работают процессорные системы сравнимы с 32-битными значениями, поэтому системный счетчик должен быть 64-битным. Для измерения времени мы будем засекать значение счетчика на момент начала отсчета и значение счетчика в конце отсчета. Зная тактовую частоту и разность между значениями счетчика мы с легкостью сможем вычислить прошедшее время. При этом нужно обеспечить счетчик такой разрядностью, чтобы он точно не смог переполниться. Разберемся с тем, как будет работать наш таймер. По сути, это просто системный счетчик (не путайте с программным счетчиком), непрерывно считающий такты с момента последнего сброса. Системным он называется потому, что работает на системной тактовой частоте. Значения частот, на которых работают процессорные системы сопоставимы с 32-битными значениями, поэтому системный счетчик должен быть 64-битным. Для измерения времени мы будем засекать значение счетчика на момент начала отсчета и значение счетчика в конце отсчета. Зная тактовую частоту и разность между значениями счетчика мы с легкостью сможем вычислить прошедшее время. При этом нужно обеспечить счетчик такой разрядностью, чтобы он точно не смог переполниться.
Поскольку мы уже назвали данный модуль "таймером", чтобы тот не был слишком простым, давайте добавим ему функциональности: пускай это будет устройство, способное генерировать прерывание через заданное число тактов. Таким образом, процессорная система сможет засекать время без постоянного опроса счетчика. Поскольку мы уже назвали данный модуль "таймером", чтобы тот не был слишком простым, давайте добавим ему функциональности: пускай это будет устройство, способное генерировать прерывание через заданное число тактов. Таким образом, процессорная система сможет засекать время без постоянного опроса счетчика. Для работы кормарка эта функциональность не нужна — если ее реализация окажется слишком сложной для вас, просто создайте системный счетчик, инкрементирующийся каждый такт, с доступом на чтение по адресу `32'h0`.
Было бы удобно, чтобы мы могли управлять тем, каким образом данный модуль будет генерировать такое прерывание: однократно, заданное число раз, или же бесконечно, пока тот не остановят. Было бы удобно, чтобы мы могли управлять тем, каким образом данный модуль будет генерировать такое прерывание: однократно, заданное число раз, или же бесконечно, пока тот не остановят.
@@ -82,6 +84,8 @@ module timer_sb_ctrl(
); );
``` ```
Обратите внимание, что у модуля нет сигнала interrupt_return_i. Модуль будет генерировать прерывания ровно на 1 такт. Если процессор в этот момент не будет готов обработать прерывания (обрабатывая в этот момент какой-либо другой перехват) — запрос будет сразу же пропущен и таймер начнет отсчитывать следующий.
Для работы данного контроллера потребуются следующие сигналы: Для работы данного контроллера потребуются следующие сигналы:
```SystemVerilog ```SystemVerilog
@@ -98,11 +102,13 @@ logic [63:0] system_counter_at_start;
- `OFF` — отключен (не генерирует прерывания) - `OFF` — отключен (не генерирует прерывания)
- `NTIMES` — включен до тех пор, пока не сгенерирует N прерываний (Значение N хранится в регистре `repeat_counter` и обновляется после каждого сгенерированного прерывания). После генерации N прерываний, переходит в режим `OFF`. - `NTIMES` — включен до тех пор, пока не сгенерирует N прерываний (Значение N хранится в регистре `repeat_counter` и обновляется после каждого сгенерированного прерывания). После генерации N прерываний, переходит в режим `OFF`.
- `FOREVER` — бесконечная генерация прерываний. Не отключится, пока режим работы прерываний не будет изменен. - `FOREVER` — бесконечная генерация прерываний. Не отключится, пока режим работы прерываний не будет изменен.
- `next_mode` — комбинационный сигнал, который подается на вход записи в регистр `mode` (аналог `next_state` из предыдущей лабораторной работы). - `next_mode` — комбинационный сигнал, который подается на вход записи в регистр `mode` (аналог `next_state` из ЛР№15). Данный сигнал меняется только запросами на запись по адресу `0x10` или в случае, если `repeat_counter == 0` в режиме `NTIMES`. Поскольку этому сигналу можно присваивать только значения сигналов такого же типа (`timer_mods`), либо константы из перечисления, запросы на запись можно реализовать через блок `case` (где перебираются 3 возможных значения `write_data_i`).
- `repeat_counter` — регистр, ассоциированный с адресом `0x14`. Количество повторений для режима `NTIMES`. Уменьшается в момент генерации прерывания в этом режиме. - `repeat_counter` — регистр, ассоциированный с адресом `0x14`. Количество повторений для режима `NTIMES`. Уменьшается в момент генерации прерывания в этом режиме в случае, если еще не равен нулю.
- `system_counter_at_start` — неархитектурный регистр, хранящий значение системного счетчика на момент начала отсчета таймера. Обновляется при генерации прерывания (если это не последнее прерывание в режиме `NTIMES`) и при запросе на запись в регистр `mode` значения не `OFF`. - `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 ### Настройка 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): Для того, чтобы корректно преобразовать тики системного счетчика во время, используется функция [`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 раз приведет к значительному увеличению времени синтеза устройства, поэтому данное изменение мы производим исключительно на время поведенческого моделирования. Обратите внимание, что увеличение размера памяти в 16 раз приведет к значительному увеличению времени синтеза устройства, поэтому данное изменение мы производим исключительно на время поведенческого моделирования.
@@ -242,13 +248,14 @@ sed -i '1d' coremark_data.mem
Программирование 32KiB по UART займет ощутимое время, поэтому вам предлагается проинициализировать память инструкций и данных "по старинке" через системные функции `$readmemh`. Программирование 32KiB по UART займет ощутимое время, поэтому вам предлагается проинициализировать память инструкций и данных "по старинке" через системные функции `$readmemh`.
Если все было сделано без ошибок, то примерно на `276ms` времени моделирования вам начнется выводиться сообщение вида: Если все было сделано без ошибок, то примерно через `300ms` после снятия сигнала сброса с ядра процессора выход `tx_o` начнет быстро менять свое значение, сигнализируя о выводе результатов программы, которые отобразятся в `tcl console` примерно еще через `55ms` в виде:
```text ```text
2K performance run parameters for coremark.
CoreMark Size : 666 CoreMark Size : 666
Total ticks : 2574834 Total ticks : 2574834
Total time (secs): <скрыто то получения результатов моделирования> Total time (secs): <скрыто до получения результатов моделирования>
Iterations/Sec : <скрыто то получения результатов моделирования> Iterations/Sec : <скрыто до получения результатов моделирования>
ERROR! Must execute for at least 10 secs for a valid result! ERROR! Must execute for at least 10 secs for a valid result!
Iterations : 1 Iterations : 1
Compiler version : GCC13.2.0 Compiler version : GCC13.2.0
@@ -259,71 +266,80 @@ seedcrc : 0x29f4
[0]crcmatrix : 0x1fd7 [0]crcmatrix : 0x1fd7
[0]crcstate : 0x8e3a [0]crcstate : 0x8e3a
[0]crcfinal : 0x7704 [0]crcfinal : 0x7704
Correct operation validated. See README.md for run and reporting rules. Errors detected
``` ```
(вывод сообщения будет завершен приблизительно на `335ms` времени моделирования). (вывод сообщения будет завершен приблизительно на `355ms` времени моделирования).
## Порядок выполнения задания ## Порядок выполнения задания
1. [Опишите](#таймер) таймер в виде модуля `timer_sb_ctrl`. 1. [Опишите](#таймер) таймер в виде модуля `timer_sb_ctrl`.
2. Проверьте описанный модуль с помощью тестового окружения [tb_timer](tb_timer.sv). 2. Проверьте описанный модуль с помощью тестового окружения [lab_16_tb_timer](lab_16_tb_timer.sv).
3. Подключите `timer_sb_ctrl` к системной шине. Сигнал прерывания этого модуля подключать не нужно. 3. Подключите `timer_sb_ctrl` к системной шине.
2.1 В случае, если до этого в ЛР13 вашим устройством вывода было не UART TX, вам необходимо подключить к системной шине и готовый модуль [uart_tx_sb_ctrl](../Made-up%20modules/lab_13.uart_tx_sb_ctrl.sv). 1. Ко входу `rst_i` модуля подключите сигнал `core_reset_o` программатора. Таким образом, системный счетчик начнет работать только когда память системы будет проинициализирована.
4. Получите исходники программы Coremark. Для этого можно либо склонировать репозиторий, либо скачать его в виде архива со страницы: [https://github.com/eembc/coremark](https://github.com/eembc/coremark). 2. Сигнал прерывания этого модуля подключать не обязательно, т.к. кормарк будет осуществлять чтение путем опроса системного счетчика, а не по прерыванию.
5. Добавьте реализацию платформозависимых функций программы coremark. Для этого в папке `barebones` необходимо: 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. в файле `core_portme.c`:
1. [реализовать](#1-реализация-функции-измеряющей-время) функцию `barebones_clock`, возвращающую текущее значение системного счетчика; 1. [реализовать](#1-реализация-функции-измеряющей-время) функцию `barebones_clock`, возвращающую текущее значение системного счетчика;
2. объявить макрос `CLOCKS_PER_SEC`, характеризующий тактовую частоту процессора; 2. объявить макрос `CLOCKS_PER_SEC`, характеризующий тактовую частоту процессора;
3. [реализовать](#3-реализация-функции-первичной-настройки) функцию `portable_init`, выполняющую первичную инициализацию периферийных устройств до начала теста; 3. [реализовать](#3-реализация-функции-первичной-настройки) функцию `portable_init`, выполняющую первичную инициализацию периферийных устройств до начала теста;
2. в файле `ee_printf.c` [реализовать](#2-реализация-вывода-очередного-символа-сообщения) функцию `uart_send_char`, отвечающую за отправку очередного символа сообщения о результате. 2. в файле `ee_printf.c` [реализовать](#2-реализация-вывода-очередного-символа-сообщения) функцию `uart_send_char`, отвечающую за отправку очередного символа сообщения о результате.
6. Добавьте с заменой в корень программы файлы [Makefile](Makefile), [linker_script.ld](linker_script.ld) и [startup.S](../14.%20Programming/startup.S). 7. Добавьте с заменой в корень программы файлы [Makefile](Makefile), [linker_script.ld](linker_script.ld) и [startup.S](../14.%20Programming/startup.S).
7. Скомпилируйте программу вызовом `make`. 8. Скомпилируйте программу вызовом `make`.
1. Если кросскомпилятор расположен не в директории `C:/riscv_cc`, перед вызовом `make` вам необходимо соответствующим образом отредактировать первую строчку в `Makefile`. 1. Если кросскомпилятор расположен не в директории `C:/riscv_cc`, перед вызовом `make` вам необходимо соответствующим образом отредактировать первую строчку в `Makefile`.
2. В случае отсутствия на компьютере утилиты `make`, вы можете самостоятельно скомпилировать программу вызовом команд, представленных в разделе ["Компиляция"](#компиляция). 2. В случае отсутствия на компьютере утилиты `make`, вы можете самостоятельно скомпилировать программу вызовом команд, представленных в разделе ["Компиляция"](#компиляция).
8. Временно измените размер памяти инструкций до 64KiB. 9. Временно измените размер памяти инструкций до 64KiB, а памяти данных до 16KiB, изменив значение параметров `INSTR_MEM_SIZE_BYTES` и `DATA_MEM_SIZE_BYTES` в пакете `memory_pkg` на `32'h10_000` и `32'h4_000` соответственно.
1. Для этого необходимо изменить размер памяти инструкций с 1024 слов до 16384 слов. 10. Проинициализируйте память инструкций и память данных файлами "coremark_instr.mem" и "coremark_data.mem", полученными в ходе компиляции программы.
2. Кроме того, необходимо изменить используемые индексы адреса в памяти с `[11:2]` на `[15:2]`. 1. Память можно проинициализировать двумя путями: с помощью вызова системной функции $readmemh, либо же с помощью программатора.Однако имейте в виду, что инициализация памятей с помощью программатора будет достаточно долго моделироваться в виду большого объема программы.
9. Проинициализируйте память инструкций и память данных файлами "coremark_instr.mem" и "coremark_data.mem", полученными в ходе компиляции программы. 2. В случае, если инициализация будет осуществляться посредством $readmemh, не забудьте удалить первую строчку со стартовым адресом из файла, инициализирующего память данных.
10. Выполните моделирование системы с помощью модуля [tb_coremark](tb_coremark). 3. В случае, если инициализация будет осуществляться с помощью программатора, используйте вспомогательные вызовы `program_region` из пакета `bluster_pkg`, как это было сделано в `lab_15_tb_system`.
1. Результаты теста будут выведены приблизительно на `335ms` времени моделирования. 4. В исходном виде тестбенч описан под инициализацию памяти посредством $readmemh.
11. Выполните моделирование системы с помощью модуля [tb_coremark](tb_coremark).
1. Результаты теста будут выведены приблизительно на `355ms` времени моделирования.
## Оценка производительности
<details> <details>
<summary>11. Прочти меня после успешного завершения моделирования </summary> <summary>Прочти меня после успешного завершения моделирования </summary>
Итак, вы получили сообщение вида: Итак, вы получили сообщение вида:
```text ```text
2K performance run parameters for coremark.
CoreMark Size : 666 CoreMark Size : 666
Total ticks : 2574834 Total ticks : 2901822
Total time (secs): 0.257483 Total time (secs): 0.290182
Iterations/Sec : 3.883746 Iterations/Sec : 3.446111
ERROR! Must execute for at least 10 secs for a valid result! ERROR! Must execute for at least 10 secs for a valid result!
Iterations : 1 Iterations : 1
Compiler version : GCC13.2.0 Compiler version : GCC13.2.0
Compiler flags : -march=rv32i_zicsr -mabi=ilp32 Compiler flags : -march=rv32i_zicsr -mabi=ilp32
Memory location : STACK Memory location : STACK
seedcrc : 0x29f4 seedcrc : 0xe9f5
[0]crclist : 0x7704 [0]crclist : 0xe714
[0]crcmatrix : 0x1fd7 [0]crcmatrix : 0x1fd7
[0]crcstate : 0x8e3a [0]crcstate : 0x8e3a
[0]crcfinal : 0x7704 [0]crcfinal : 0xe714
Correct operation validated. See README.md for run and reporting rules. 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 ```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`, кликнув по нему. Таблица опубликованных результатов находится по адресу: [https://www.eembc.org/coremark/scores.php](https://www.eembc.org/coremark/scores.php). Нам необходимо отсортировать эту таблицу по столбцу `CoreMark`, кликнув по нему.

View File

@@ -8,18 +8,28 @@
See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details.
* ------------------------------------------------------------------------------ * ------------------------------------------------------------------------------
*/ */
OUTPUT_FORMAT("elf32-littleriscv") /* Указываем порядок следования байт */ OUTPUT_FORMAT("elf32-littleriscv") /* Указываем порядок следования байт */
ENTRY(_start) /* мы сообщаем компоновщику, что первая ENTRY(_start) /* мы сообщаем компоновщику, что первая
исполняемая процессором инструкция исполняемая процессором инструкция
находится у метки "start" находится у метки "_start"
*/ */
_text_size = 0x10000; /* Размер памяти инстр.: 16KiB */ /*
_data_base_addr = _text_size; /* Стартовый адрес секции данных */ В данном разделе указывается структура памяти:
_data_size = 0x04000; /* Размер памяти данных: 16KiB */ Сперва идет регион "instr_mem", являющийся памятью с исполняемым кодом
(об этом говорит аттрибут 'x'). Этот регион начинается
_data_end = _data_base_addr + _data_size; с адреса 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; /* Размер стека обработчика перехватов. _trap_stack_size = 2560; /* Размер стека обработчика перехватов.
Данный размер позволяет выполнить Данный размер позволяет выполнить
@@ -29,25 +39,9 @@ _trap_stack_size = 2560; /* Размер стека обрабо
_stack_size = 1280; /* Размер программного стека. _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 SECTIONS
{ {
PROVIDE( _start = 0x00000000 ); /* Позиция start в памяти
/* /*
В скриптах компоновщика есть внутренняя переменная, записываемая как '.' В скриптах компоновщика есть внутренняя переменная, записываемая как '.'
Эта переменная называется "счетчиком адресов". Она хранит текущий адрес в Эта переменная называется "счетчиком адресов". Она хранит текущий адрес в
@@ -81,31 +75,30 @@ SECTIONS
секций, начинающихся на .text во всех переданных компоновщику двоичных секций, начинающихся на .text во всех переданных компоновщику двоичных
файлах. файлах.
Дополнительно мы указываем, что данная секция должна быть размещена в Дополнительно мы указываем, что данная секция должна быть размещена в
регионе "rom". регионе "instr_mem".
*/ */
.text : {*(.boot) *(.text*)} >rom .text : {
PROVIDE(_start = .);
*(.boot)
*(.text*)
} > instr_mem
/* /*
Поскольку мы не знаем суммарного размера получившейся секции, мы проверяем Секция данных размещается аналогично секции инструкций за исключением
что не вышли за границы памяти инструкций и переносим счетчик адресов за адреса загрузки в памяти (Load Memory Address, LMA). Поскольку память
пределы памяти инструкций в область памяти данных. инструкций и данных физически разделены, у них есть пересекающееся адресное
Дополнительно мы указываем, что данная секция должна быть размещена в пространство, которое мы бы хотели использовать (поэтому в разделе MEMORY мы
регионе "ram". указали что стартовые адреса обоих памятей равны нулю). Однако компоновщику
это не нравится, ведь как он будет размещать две разные секции в одно и то же
место. Поэтому мы ему сообщаем, с помощью оператора "AT", что загружать секцию
данных нужно на самом деле не по нулевому адресу, а по какому-то другому,
заведомо большему чем размер памяти инструкций, но процессор будет
использовать адреса, начинающиеся с нуля. Такой вариант компоновщика
устраивает и он собирает исполняемый файл без ошибок. Наша же задача,
загрузить итоговую секцию данных по нулевым адресам памяти данных.
*/ */
ASSERT(. < _text_size, ".text section exceeds instruction memory size") .data : AT (0x00800000) {
. = _data_base_addr;
/*
Следующая команда сообщает, что начиная с адреса, которому в данных момент
равен счетчик адресов (_data_base_addr) будет находиться секция .data
итогового файла, которая состоит из секций всех секций, начинающихся
на .data во всех переданных компоновщику двоичных файлах.
Дополнительно мы указываем, что данная секция должна быть размещена в
регионе "ram".
*/
.data : {*(.*data*)} >ram
/* /*
Общепринято присваивать GP значение равное началу секции данных, смещенное Общепринято присваивать GP значение равное началу секции данных, смещенное
на 2048 байт вперед. на 2048 байт вперед.
@@ -117,12 +110,15 @@ SECTIONS
Подробнее: Подробнее:
https://groups.google.com/a/groups.riscv.org/g/sw-dev/c/60IdaZj27dY/m/s1eJMlrUAQAJ 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х-байтной границе. 4х-байтной границе.
*/ */
. = ALIGN(4); . = ALIGN(4);
@@ -151,10 +147,13 @@ SECTIONS
https://en.wikipedia.org/wiki/.bss https://en.wikipedia.org/wiki/.bss
Дополнительно мы указываем, что данная секция должна быть размещена в Дополнительно мы указываем, что данная секция должна быть размещена в
регионе "ram". регионе "data_mem".
*/ */
_bss_start = .; _bss_start = .;
.bss : {*(.*bss*)} >ram .bss : {
*(.bss*)
*(.sbss*)
} > data_mem
_bss_end = .; _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") "Program size is too big")
/* Перемещаем счетчик адресов над стеком прерываний (чтобы после мы могли /* Перемещаем счетчик адресов над стеком прерываний (чтобы после мы могли
использовать его в вызове ALIGN) */ использовать его в вызове 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 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; 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) */ использовать его в вызове ALIGN) */
. = _data_end; . = LENGTH(data_mem);
/* /*
Обычно память имеет размер, кратный 16, но на случай, если это не так, мы Обычно память имеет размер, кратный 16, но на случай, если это не так, мы
@@ -202,6 +199,6 @@ SECTIONS
конец кратен 16), либо поднимаемся на 16 байт вверх от края памяти, конец кратен 16), либо поднимаемся на 16 байт вверх от края памяти,
округленного до 16 в сторону большего значения округленного до 16 в сторону большего значения
*/ */
_trap_stack_ptr = ALIGN(16) <= _data_end ? ALIGN(16) : ALIGN(16) - 16; _trap_stack_ptr = ALIGN(16) <= LENGTH(data_mem) ? ALIGN(16) : ALIGN(16) - 16;
ASSERT(_trap_stack_ptr <= _data_end, "ISP exceed memory size") ASSERT(_trap_stack_ptr <= LENGTH(data_mem), "ISP exceed memory size")
} }

View File

@@ -31,8 +31,8 @@ _irq_config:
li t1, -1 # -1 (все биты равны 1) означает, что разрешены все прерывания li t1, -1 # -1 (все биты равны 1) означает, что разрешены все прерывания
la t2, _trap_stack_ptr la t2, _trap_stack_ptr
csrw mtvec, t0 csrw mtvec, t0
csrw mie, t1
csrw mscratch, t2 csrw mscratch, t2
csrw mie, t1
# Вызов функции main # Вызов функции main
_main_call: _main_call: