mirror of
https://github.com/MPSU/APS.git
synced 2025-09-16 01:30:10 +00:00
ЛР16. Рефактор методички (#97)
* ЛР16. Рефактор методички * Apply suggestions from code review
This commit is contained in:
committed by
GitHub
parent
59510a522b
commit
73e521687e
@@ -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`, кликнув по нему.
|
||||
|
||||
|
Reference in New Issue
Block a user