mirror of
https://github.com/MPSU/APS.git
synced 2025-09-15 17:20:10 +00:00
* Опечатки ЛР № 16
* Удаление дубликата startup.S в ЛР № 16
Все ссылки и так вели на 14 лабу, прямым текстом написано брать оттуда
* Ещё неразрывные пробелы перед единицами измерения
* Не писать неразрывные пробелы в заголовках
Не всё ПО правильно читает заголовки
* Убрал скобки
* Update Labs/16. Coremark/README.md
Co-authored-by: Andrei Solodovnikov <VoultBoy@yandex.ru>
* Приведение номеров лаб к уже установленному виду
* Revert "Удаление дубликата startup.S в ЛР № 16"
This reverts commit 8f48071a74
.
---------
Co-authored-by: Andrei Solodovnikov <VoultBoy@yandex.ru>
389 lines
44 KiB
Markdown
389 lines
44 KiB
Markdown
# Лабораторная работа №16 "Оценка производительности"
|
||
|
||
## Материал для подготовки к лабораторной работе
|
||
|
||
Данная лабораторная работа будет полностью опираться на навыки, полученные в ходе выполнения лабораторных работ:
|
||
|
||
13. [Периферийные устройства](../13.%20Peripheral%20units/)
|
||
14. [Программирование](../14.%20Programming/)
|
||
15. [Программатор](../15.%20Programming%20device/)
|
||
|
||
## Цель
|
||
|
||
Дать количественную оценку, характеризующую производительность реализованной вычислительной системы. На текущий момент мы создали процессорную систему, которая способна взаимодействовать с внешним миром посредством периферийных устройств ввода-вывода и программатора, по сути являющуюся компьютером. Однако встает вопрос, какое место данная система занимает в ряду уже существующих вычислительных систем.
|
||
|
||
<!-- Ближе ли она по производительности к [разностной машине Бэббиджа](https://ru.wikipedia.org/wiki/Разностная_машина_Чарльза_Бэббиджа) или же к [Frontier](https://ru.wikipedia.org/wiki/Frontier_(суперкомпьютер)) (первому в мире [экзафлопсному компьютеру](https://ru.wikipedia.org/wiki/Exascale_computing)). Получится ли на ней запустить Doom или Skyrim? Всего этого мы не узнаем наверняка до тех пор, пока не произведем оценку производительности. -->
|
||
|
||
Для оценки производительности необходимо модифицировать существующую процессорную систему, а после собрать и запустить специализированное ПО, отвечающее за измерение производительности (будет использована программа Coremark).
|
||
|
||
## Теория
|
||
|
||
[Coremark](https://www.eembc.org/coremark/faq.php) — это набор синтетических тестов (специальных программ) для измерения производительности процессорной системы. В данный набор входят такие тесты, как работа со связными списками, матричные вычисления, обработка конечных автоматов и подсчет контрольной суммы. Результат выражается в одном числе, которое можно использовать для сравнения с результатами других процессорных систем.
|
||
|
||
Для подсчета производительности, coremark опирается на функцию, возвращающую текущее время, поэтому для оценки производительности нам потребуется вспомогательное периферийное устройство: таймер.
|
||
|
||
Для вывода результатов тестирования, необходимо описать способ, которым coremark сможет выводить очередной символ сообщения — для этого мы будем использовать контроллер UART из ЛР№13.
|
||
|
||
Кроме того, скомпилированная без оптимизаций программа будет занимать чуть более 32 KiB, поэтому нам потребуется изменить размер памяти инструкций.
|
||
|
||
Таким образом, для того чтобы запустить данную программу, нам необходимо выполнить как аппаратные изменения процессорной системы (добавить таймер и (если отсутствует) контроллер UART), так и программные изменения самого coremark (для этого в нем предусмотрены специальные платформозависимые файлы, в которых объявлены функции, реализацию которых нам необходимо выполнить).
|
||
|
||
Говорят, что лучшей проверкой процессора на наличие ошибок является попытка запустить на нем ядро Linux. Наша процессорная система на это в принципе не рассчитана (поскольку для запуска Linux нужна поддержка нескольких дополнительных расширений), поэтому coremark можно по праву считать "бюджетным" аналогом проверки процессора на прочность.
|
||
|
||
## Задание
|
||
|
||
1. Реализовать модуль-контроллер "таймер".
|
||
2. Подключить этот модуль к системной шине.
|
||
1. В случае, если до этого в ЛР№13 вашим устройством вывода было не UART TX, вам необходимо подключить к системной шине готовый модуль [uart_tx_sb_ctrl](../Made-up%20modules/lab_13.uart_tx_sb_ctrl.sv).
|
||
3. Добавить реализацию платформозависимых функций программы coremark.
|
||
4. Скомпилировать программу.
|
||
5. Изменить размер памяти инструкций.
|
||
6. Запустить моделирование.
|
||
7. Сравнить результаты измерения производительности с результатами существующих процессорных системам.
|
||
|
||
### Таймер
|
||
|
||
Разберемся с тем, как будет работать наш таймер. По сути, это просто системный счётчик (не путайте с программным счётчиком), непрерывно считающий такты с момента последнего сброса. Системным он называется потому, что работает на системной тактовой частоте. Значения частот, на которых работают процессорные системы сопоставимы с 32-битными значениями, поэтому системный счётчик должен быть 64-битным. Для измерения времени мы будем засекать значение счётчика на момент начала отсчета и значение счётчика в конце отсчёта. Зная тактовую частоту и разность между значениями счётчика мы с легкостью сможем вычислить прошедшее время. При этом нужно обеспечить счётчик такой разрядностью, чтобы он точно не смог переполниться.
|
||
|
||
Поскольку мы уже назвали данный модуль "таймером", чтобы тот не был слишком простым, давайте добавим ему функциональности: пускай это будет устройство, способное генерировать прерывание через заданное число тактов. Таким образом, процессорная система сможет засекать время без постоянного опроса счётчика. Для работы coremark эта функциональность не нужна — если ее реализация окажется слишком сложной для вас, просто создайте системный счётчик, инкрементирующийся каждый такт, с доступом на чтение по адресу `32'h0`.
|
||
|
||
Было бы удобно, чтобы мы могли управлять тем, каким образом данный модуль будет генерировать такое прерывание: однократно, заданное число раз, или же бесконечно, пока тот не остановят.
|
||
|
||
Таким образом, мы сформировали адресное пространство контроллера, представленное в _таблице 1_.
|
||
|
||
|Адрес|Режим доступа|Допустимые значения| Функциональное назначение |
|
||
|-----|-------------|-------------------|---------------------------------------------------------------------------------------|
|
||
|0x00 | R | [0:2³²-1] | Значение младших 32 бит системного счётчика, доступное только для чтения |
|
||
|0x04 | R | [0:2³²-1] | Значение старших 32 бит системного счётчика, доступное только для чтения |
|
||
|0x08 | RW | [0:2³²-1] | Указание младших 32 бит задержки, спустя которую таймер будет генерировать прерывание |
|
||
|0x0c | RW | [0:2³²-1] | Указание старших 32 бит задержки, спустя которую таймер будет генерировать прерывание |
|
||
|0x10 | RW | [0:2] | Указание режима генерации прерываний (выключен, заданное число раз, бесконечно) |
|
||
|0x14 | RW | [0:2³²-1] | Указание количества повторений генерации прерываний |
|
||
|0x24 | W | 1 | Программный сброс |
|
||
|
||
_Таблица 1. Адресное пространство контроллера таймера._
|
||
|
||
Прототип модуля представлен в _листинге 1_.
|
||
|
||
```Verilog
|
||
module timer_sb_ctrl(
|
||
/*
|
||
Часть интерфейса модуля, отвечающая за подключение к системной шине
|
||
*/
|
||
input logic clk_i,
|
||
input logic rst_i,
|
||
input logic req_i,
|
||
input logic write_enable_i,
|
||
input logic [31:0] addr_i,
|
||
input logic [31:0] write_data_i,
|
||
output logic [31:0] read_data_o,
|
||
output logic ready_o,
|
||
/*
|
||
Часть интерфейса модуля, отвечающая за отправку запросов на прерывание
|
||
процессорного ядра
|
||
*/
|
||
output logic interrupt_request_o
|
||
);
|
||
```
|
||
|
||
_Листинг 1. Прототип контроллера таймера._
|
||
|
||
Обратите внимание, что у модуля нет сигнала `interrupt_return_i`. Модуль будет генерировать прерывания ровно на 1 такт. Если процессор в этот момент не будет готов обработать прерывания (обрабатывая в этот момент какой-либо другой перехват) — запрос будет сразу же пропущен и таймер начнет отсчитывать следующий.
|
||
|
||
Для работы данного контроллера потребуются следующие сигналы:
|
||
|
||
```Verilog
|
||
logic [63:0] system_counter;
|
||
logic [63:0] delay;
|
||
enum logic [1:0] {OFF, NTIMES, FOREVER} mode, next_mode;
|
||
logic [31:0] repeat_counter;
|
||
logic [63:0] system_counter_at_start;
|
||
```
|
||
|
||
- `system_counter` — регистр, ассоциированный с адресами `0x00` (младшие 32 бита) и `0x04` (старшие 32 бита), системный счётчик. Задача регистра заключается в ежетактном увеличении на единицу.
|
||
- `delay` — регистр, ассоциированный с адресами `0x08` (младшие 32 бита) и `0x0c` (старшие 32 бита). Число тактов, спустя которое таймер (когда тот будет включен) сгенерирует прерывание. Данный регистр изменяется только сбросом, либо запросом на запись.
|
||
- `mode` — регистр, ассоциированный с адресом `0x10`. Режим работы таймера:
|
||
- `OFF` — отключен (не генерирует прерывания)
|
||
- `NTIMES` — включен до тех пор, пока не сгенерирует N прерываний (значение N хранится в регистре `repeat_counter` и обновляется после каждого сгенерированного прерывания). После генерации N прерываний, переходит в режим `OFF`.
|
||
- `FOREVER` — бесконечная генерация прерываний. Не отключится, пока режим работы прерываний не будет изменен.
|
||
- `next_mode` — комбинационный сигнал, который подается на вход записи в регистр `mode` (аналог `next_state` из ЛР№15). Данный сигнал меняется только запросами на запись по адресу `0x10` или в случае, если `repeat_counter == 0` в режиме `NTIMES`. Поскольку этому сигналу можно присваивать только сигналы того же типа (`mode`/`next_mode`), либо константы из перечисления, запросы на запись можно реализовать через блок `case` (где перебираются 3 возможных значения `write_data_i`).
|
||
- `repeat_counter` — регистр, ассоциированный с адресом `0x14`. Количество повторений для режима `NTIMES`. Уменьшается в момент генерации прерывания в этом режиме в случае, если еще не равен нулю.
|
||
- `system_counter_at_start` — неархитектурный регистр, хранящий значение системного счётчика на момент начала отсчета таймера. Обновляется при генерации прерывания (если это не последнее прерывание в режиме `NTIMES`) и при запросе на запись в регистр `mode` значения не `OFF`.
|
||
|
||
Выходной сигнал interrupt_request_o должен быть равен единице, если текущий режим работы не `OFF`, а сумма `system_counter_at_start` и `delay` равна `system_counter`.
|
||
|
||
Для подключения данного таймера к системной шине, мы воспользуемся первым свободным базовым адресом, оставшимся после ЛР№13: `0x08`. Таким образом, для обращения к системному счётчику, процессор будет использовать адрес `0x08000000` для обращения к регистру `delay` `0x08000008` и т.п.
|
||
|
||
### Настройка Coremark
|
||
|
||
В первую очередь, необходимо скачать исходный код данной программы, размещенный по адресу: [https://github.com/eembc/coremark](https://github.com/eembc/coremark). На случай возможных несовместимых изменений в будущем, все дальнейшие ссылки будут даваться на слепок репозитория, который был на момент коммита `d5fad6b`.
|
||
|
||
После этого, чтобы добавить поддержку нашей процессорной системы потребуется:
|
||
|
||
1. Реализовать функцию, измеряющую время
|
||
2. Реализовать функцию, выводящую очередной символ сообщения с результатами
|
||
3. Реализовать функцию, выполняющую первичную настройку периферии перед тестом
|
||
4. Выполнить мелкую подстройку, такую как количество итераций в тесте и указание аргументов, с которыми будет скомпилирована программа.
|
||
|
||
Все файлы, содержимое которых мы будем менять расположены в папке [barebones](https://github.com/eembc/coremark/tree/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones).
|
||
|
||
#### 1. Реализация функции, измеряющей время
|
||
|
||
Не мы первые придумали измерять время путем отсчета системных тактов, поэтому вся логика по измерению времени уже реализована в coremark. От нас требуется только реализовать функцию, которая возвращает текущее значение системного счётчика.
|
||
|
||
Данной функцией является `barebones_clock`, расположенная в файле [`core_portme.c`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.c). В данный момент, в реализации функции описан вызов ошибки (поскольку реализации как таковой нет). Мы должны **заменить** реализацию функции кодом, приведённым в _листинге 2_.
|
||
|
||
```C
|
||
barebones_clock()
|
||
{
|
||
volatile ee_u32 *ptr = (ee_u32*)0x08000000;
|
||
ee_u32 tim = *ptr;
|
||
return tim;
|
||
}
|
||
```
|
||
|
||
_Листинг 2. Код функции `barebones_clock`._
|
||
|
||
После ЛР№14 вы уже должны представлять, что здесь происходит. Мы создали указатель с абсолютным адресом `0x08000000` — адресом системного счётчика. Разыменование данного указателя вернет текущее значение системного счётчика, что и должно быть результатом вызова этой функции. Поскольку тест закончится менее чем за секунду, не обязательно загружать значение старших 32 бит (они будут не равны нулю только спустя 2³² тактов / 10⁶ тактов/с ≈ 429 c).
|
||
|
||
Для того, чтобы корректно преобразовать тики системного счётчика во время, используется функция [`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):
|
||
|
||
```C
|
||
#define CLOCKS_PER_SEC 10000000
|
||
```
|
||
|
||
На этом наша задача по измерению времени завершена. Остальные правки будут не сложнее этих.
|
||
|
||
#### 2. Реализация вывода очередного символа сообщения
|
||
|
||
Для вывода очередного символа во встраиваемых системах используется (какое совпадение!) функция [`uart_send_char`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/ee_printf.c#L663), расположенная в файле [`ee_printf.c`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/ee_printf.c).
|
||
|
||
В реализации данной функции вам уже предлагают алгоритм, по которому та должна работать. Необходимо:
|
||
|
||
1. дождаться готовности UART к отправке;
|
||
2. передать отправляемый символ;
|
||
3. дождаться готовности UART к отправке (завершения передачи).
|
||
|
||
Давайте так и реализуем эту функцию:
|
||
|
||
```C
|
||
uart_send_char(char c)
|
||
{
|
||
volatile ee_u8 *uart_ptr = (ee_u8 *)0x06000000;
|
||
while(*(uart_ptr+0x08));
|
||
*uart_ptr = c;
|
||
while(*(uart_ptr+0x08));
|
||
}
|
||
```
|
||
|
||
_Листинг 3. Код функции `uart_send_char`._
|
||
|
||
`0x06000000` — базовый адрес контроллера UART TX из ЛР№13 (и адрес передаваемых этим контроллером данных).
|
||
`0x08` — смещение до адреса регистра `busy` в адресном пространстве этого контроллера.
|
||
|
||
#### 3. Реализация функции первичной настройки
|
||
|
||
Это функция [`portable_init`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.c#L130), расположена в уже известном ранее файле `core_portme.c`. Данная функция выполняет необходимые нам настройки перед началом теста. Для нас главное — настроить нужным образом контроллер UART.
|
||
Допустим мы хотим, чтобы данные передавались на скорости `115200`, c одним стоповым битом и контролем бита четности. В этом случае, мы должны добавить в начало функции следующий код:
|
||
|
||
```C
|
||
portable_init(core_portable *p, int *argc, char *argv[])
|
||
{
|
||
volatile ee_u32 *uart_tx_ptr = (ee_u32 *)0x06000000;
|
||
*(uart_tx_ptr + 3) = 115200;
|
||
*(uart_tx_ptr + 4) = 1;
|
||
*(uart_tx_ptr + 5) = 1;
|
||
|
||
//...
|
||
}
|
||
```
|
||
|
||
_Листинг 4. Код функции `uart_send_char`._
|
||
|
||
#### 4. Дополнительные настройки
|
||
|
||
Для тонких настроек используется заголовочный файл [`core_portme.h`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.h), куда также требуется внести несколько изменений. Нам необходимо:
|
||
|
||
1. Объявить в начале файла макрос `ITERATIONS`, влияющий на количество прогонов теста. Нам достаточно выставить значение 1.
|
||
2. Обновить значение макроса `COMPILER_FLAGS`, заменив его значение `FLAGS_STR` на`"-march=rv32i_zicsr -mabi=ilp32"`, именно с этими аргументами мы будем собирать программу. Это опциональная настройка, которая позволит вывести флаги компиляции в итоговом сообщении.
|
||
3. Добавить подключение заголовочного файла `#include <stddef.h>`.
|
||
|
||
### Компиляция
|
||
|
||
Для компиляции программы, вам потребуются предоставленные файлы [Makefile](Makefile) и [linker_script.ld](linker_script.ld), а также файл [startup.S](../14.%20Programming/startup.S) из ЛР№14. Эти файлы необходимо скопировать с заменой в корень папки с программой.
|
||
|
||
`Makefile` написан из расчёта, что кросс-компилятор расположен по пути `C:/riscv_cc/`. В случае, если это не так, измените первую строчку данного файла в соответствии с расположением кросс-компилятора.
|
||
|
||
Для запуска компиляции, необходимо выполнить следующую команду, находясь в корне программы coremark:
|
||
|
||
```bash
|
||
make
|
||
```
|
||
|
||
В случае, если на вашем рабочем компьютере не установлена утилита `make`, то вы можете скомпилировать программу вручную, выполнив последовательность команд, приведённую в _листинге 5_.
|
||
|
||
```bash
|
||
export CC_BASE=/c/riscv_cc/bin/riscv-none-elf
|
||
export CC="$CC_BASE"-gcc
|
||
export OBJ_DUMP="$CC_BASE"-objdump
|
||
export OBJ_COPY="$CC_BASE"-objcopy
|
||
export SIZE="$CC_BASE"-size
|
||
export CC_FLAGS="-march=rv32i_zicsr -mabi=ilp32 -I./ -I./barebones"
|
||
export LD_FLAGS="-Wl,--gc-sections -nostartfiles -T linker_script.ld"
|
||
export OC_FLAGS="-O verilog --verilog-data-width=4"
|
||
|
||
$CC -c $CC_FLAGS -o core_main.o core_main.c
|
||
$CC -c $CC_FLAGS -o startup.o startup.S
|
||
$CC -c $CC_FLAGS -o core_list_join.o core_list_join.c
|
||
$CC -c $CC_FLAGS -o core_matrix.o core_matrix.c
|
||
$CC -c $CC_FLAGS -o core_state.o core_state.c
|
||
$CC -c $CC_FLAGS -o core_util.o core_util.c
|
||
$CC -c $CC_FLAGS -o core_portme.o barebones/core_portme.c
|
||
$CC -c $CC_FLAGS -o cvt.o barebones/cvt.c
|
||
$CC -c $CC_FLAGS -o ee_printf.o barebones/ee_printf.c
|
||
$CC $CC_FLAGS $LD_FLAGS *.o -o coremark.elf
|
||
|
||
$OBJ_DUMP -D coremark.elf > coremark_disasm.S
|
||
|
||
$OBJ_COPY $OC_FLAGS -j .data -j .bss coremark.elf coremark_data.mem
|
||
$OBJ_COPY $OC_FLAGS -j .text coremark.elf coremark_instr.mem
|
||
|
||
$SIZE coremark.elf
|
||
```
|
||
|
||
_Листинг 5. Последовательность команд для компиляции coremark._
|
||
|
||
В случае успешной компиляции, вам будет выведено сообщение об итоговом размере секций инструкций и данных:
|
||
|
||
```text
|
||
text data bss dec hex filename
|
||
34324 2268 100 36692 8f54 coremark.elf
|
||
```
|
||
|
||
### Изменение размера памяти инструкций
|
||
|
||
Как видите, размер секции инструкций превышает 32 KiB на 1556 байт (32768—34000). Поэтому на время оценки моделирования, нам придется увеличить размер памяти инструкций до 64 KiB, изменив значение параметра `INSTR_MEM_SIZE_BYTES` в пакете `memory_pkg` до значения `32'h10000`. Размер памяти данных также необходимо увеличить, изменив значение параметра `DATA_MEM_SIZE_BYTES` до `32'h4000`.
|
||
|
||
Обратите внимание, что увеличение размера памяти в 16 раз приведет к значительному увеличению времени синтеза устройства, поэтому данное изменение мы производим исключительно на время поведенческого моделирования.
|
||
|
||
### Запуск моделирования
|
||
|
||
Программирование 34 KiB по UART займет ощутимое время, поэтому вам предлагается проинициализировать память инструкций и данных "по-старинке" через системные функции `$readmemh`.
|
||
|
||
Если все было сделано без ошибок, то примерно через `300ms` после снятия сигнала сброса с ядра процессора выход `tx_o` начнет быстро менять свое значение, сигнализируя о выводе результатов программы, которые отобразятся в `tcl console` примерно еще через `55ms` в виде _листинга 6_ (вывод сообщения будет завершен приблизительно на `355ms` времени моделирования).
|
||
|
||
```text
|
||
2K performance run parameters for coremark.
|
||
CoreMark Size : 666
|
||
Total ticks : 2574834
|
||
Total time (secs): <скрыто до получения результатов моделирования>
|
||
Iterations/Sec : <скрыто до получения результатов моделирования>
|
||
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
|
||
[0]crcmatrix : 0x1fd7
|
||
[0]crcstate : 0x8e3a
|
||
[0]crcfinal : 0x7704
|
||
Errors detected
|
||
```
|
||
|
||
_Листинг 6. Лог вывода результатов coremark. Значения "Total time (secs)" и "Iterations/Sec" скрыты до получения результатов моделирования._
|
||
|
||
## Порядок выполнения задания
|
||
|
||
1. [Опишите](#таймер) таймер в виде модуля `timer_sb_ctrl`.
|
||
2. Проверьте модуль с помощью верификационного окружения, описанного в файле [lab_16.tb_timer.sv](lab_16.tb_timer.sv).
|
||
3. Интегрируйте модуль `timer_sb_ctrl` в процессорную систему.
|
||
1. Ко входу `rst_i` модуля подключите сигнал `core_reset_o` программатора. Таким образом, системный счётчик начнет работать только когда память системы будет проинициализирована.
|
||
2. Сигнал прерывания этого модуля подключать не обязательно, т.к. coremark будет осуществлять чтение путем опроса системного счётчика, а не по прерыванию.
|
||
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`, отвечающую за отправку очередного символа сообщения о результате.
|
||
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`, вы можете самостоятельно скомпилировать программу вызовом команд, представленных в параграфе ["Компиляция"](#компиляция).
|
||
9. Временно измените размер памяти инструкций до 64 KiB, а памяти данных до 16 KiB, изменив значение параметров `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. Выполните моделирование системы с помощью модуля [lab_16.tb_coremark](lab_16.tb_coremark.sv).
|
||
1. Результаты теста будут выведены приблизительно на `355ms` времени моделирования.
|
||
|
||
## Оценка производительности
|
||
|
||
<details>
|
||
<summary>Прочти меня после успешного завершения моделирования </summary>
|
||
|
||
Итак, вы получили сообщение, представленное в _листинге 7_.
|
||
|
||
```text
|
||
2K performance run parameters for coremark.
|
||
CoreMark Size : 666
|
||
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 : 0xe9f5
|
||
[0]crclist : 0xe714
|
||
[0]crcmatrix : 0x1fd7
|
||
[0]crcstate : 0x8e3a
|
||
[0]crcfinal : 0xe714
|
||
Errors detected
|
||
```
|
||
|
||
_Листинг 7. Лог вывода результатов coremark._
|
||
|
||
Не обращайте внимание на строки "ERROR! Must execute for at least 10 secs for a valid result!" и "Errors detected". Программа считает, что для корректных результатов, необходимо крутить ее по кругу в течении минимум 10 секунд, однако по большей части это требование необходимо для более достоверного результата у систем с кэшем/предсказателями переходов и прочими блоками, которые могут изменить количество тактов на прохождение между итерациями. Наш однотактный процессор будет вести себя одинаково на каждом круге, поэтому нет смысла в дополнительном времени моделирования. Тем не менее, если вы захотите получить результаты, не содержащих сообщения об ошибках, измените число итераций в файле `core_portme.h` до 45.
|
||
|
||
Нас интересует строка:
|
||
|
||
```text
|
||
Iterations/Sec : 3.446111
|
||
```
|
||
|
||
Это и есть так называемый "кормарк" — метрика данной программы. Результат нашего процессора: ~3.45 кормарка.
|
||
|
||
Обычно, для сравнения между собой нескольких реализаций микроархитектур, более достоверной считается величина "кормарк / МГц", т.е. число кормарков, поделённое на тактовую частоту процессора, поскольку время прохождения теста напрямую зависит от тактовой частоты. Это значит, что чип с менее удачной микроархитектурной реализацией может выиграть по кормарку просто потому, что он был выпущен по лучшей технологии, позволяющей запустить его на больших частотах. Кормарк/МГц нормализует результаты, позволяя сравнивать микроархитектурные решения, не заботясь о том, на какой частоте был получен результат.
|
||
|
||
Более того, при сравнении с другими результатами, необходимо учитывать флаги оптимизации, которые использовались при компиляции программы, поскольку они также влияют на результат. Например, если собрать coremark с уровнем оптимизаций `-O1`, результат нашей системы скакнёт до 11.23 кормарков, что всего лишь является следствием того, что программа стала меньше обращаться к памяти после оптимизаций. Именно поэтому результаты coremark указываются вместе с опциями, с которыми тот был собран.
|
||
|
||
Мы не будем уходить в дебри темных паттернов маркетинга и вместо этого будет оценивать производительность в лоб: сколько кормарков в секунду смог прогнать наш процессор без каких-либо оптимизаций в сравнении с представленными результатами других систем вне зависимости от их оптимизаций.
|
||
|
||
Таблица опубликованных результатов находится по адресу: [https://www.eembc.org/coremark/scores.php](https://www.eembc.org/coremark/scores.php). Нам необходимо отсортировать эту таблицу по столбцу `CoreMark`, кликнув по нему.
|
||
|
||
Мы получим следующий расклад:
|
||
|
||

|
||
|
||
На что мы можем обратить внимание? Ну, во-первых, мы видим, что соседний с нашим микроконтроллер по кормарку — это `ATmega2560` с результатом `4.25` кормарка. Т.е. наш процессор по производительности схож с микроконтроллерами Arduino.
|
||
|
||
Есть ли здесь еще что-нибудь интересное? Посмотрим в верх таблицы, мы можем увидеть производителя Intel с их микропроцессором [Intel 80286](https://ru.wikipedia.org/wiki/Intel_80286). Как написано на вики, данный микропроцессор был в 3-6 раз производительней [Intel 8086](https://ru.wikipedia.org/wiki/Intel_8086), который соперничал по производительности с процессором [Zilog Z80](https://en.wikipedia.org/wiki/Zilog_Z80), который устанавливался в домашний компьютер [TRS-80](https://en.wikipedia.org/wiki/TRS-80).
|
||
|
||
А знаете, с чем был сопоставим по производительности компьютер TRS-80? С бортовым компьютером [Apollo Guidance Computer](https://en.wikipedia.org/wiki/Apollo_Guidance_Computer), который проводил вычисления и контролировал движение, навигацию, управлял командным и лунным модулями в ходе полётов по программе Аполлон.
|
||
|
||
Иными словами, мы разработали процессор, который приблизительно в 6-12 раз производительнее компьютера, управлявшего полётом космического корабля, который доставил человека на Луну!
|
||
|
||
Можно ли как-то улучшить наш результат? Безусловно. Мы можем улучшить его примерно на 5% изменив буквально одну строчку. Дело в том, что для простоты реализации, мы генерировали сигнал `stall` для каждой операции обращения в память. Однако приостанавливать работу процессора было необходимо только для операций чтения из памяти. Если не генерировать сигнал `stall` для операций типа `store`, мы уменьшим время, необходимое на исполнение бенчмарка. Попробуйте сделать это сами.
|
||
|
||
Добавление умножителей, конвейеризация и множество других потенциальных улучшений увеличат производительность в разы.
|
||
|
||
Но это, как говорится, уже другая история.
|
||
|
||
</details>
|