WIP: APS cumulative update (#98)

* WIP: APS cumulative update

* Update How FPGA works.md

* Перенос раздела "Последовательностная логика" в отдельный док

* Исправление картинки

* Исправление оформления индексов

* Переработка раздела Vivado Basics

* Добавление картинки в руководство по созданию проекта

* Исправление ссылок в анализе rtl

* Обновление изображения в sequential logic

* Исправление ссылок в bug hunting

* Исправление ссылок

* Рефактор руководства по прошивке ПЛИС

* Mass update

* Update fig_10

* Restore fig_02
This commit is contained in:
Andrei Solodovnikov
2024-09-02 10:20:08 +03:00
committed by GitHub
parent 78bb01ef95
commit a28002e681
195 changed files with 3640 additions and 2664 deletions

View File

@@ -1,4 +1,4 @@
# Лабораторная работа 16 "Оценка производительности"
# Лабораторная работа 16 "Оценка производительности"
## Материал для подготовки к лабораторной работе
@@ -18,15 +18,15 @@
## Теория
[Coremark](https://www.eembc.org/coremark/faq.php) (далее кормарк) — это набор синтетических тестов (специальных программ) для измерения производительности процессорной системы. В данный набор входят такие тесты, как работа со связными списками, матричные вычисления, обработка конечных автоматов и подсчет контрольной суммы. Результат выражается в одном числе, которое можно использовать для сравнения с результатами других процессорных систем.
[Coremark](https://www.eembc.org/coremark/faq.php) — это набор синтетических тестов (специальных программ) для измерения производительности процессорной системы. В данный набор входят такие тесты, как работа со связными списками, матричные вычисления, обработка конечных автоматов и подсчет контрольной суммы. Результат выражается в одном числе, которое можно использовать для сравнения с результатами других процессорных систем.
Для подсчета производительности, кормарк опирается на функцию, возвращающую текущее время, поэтому для оценки производительности нам потребуется вспомогательное периферийное устройство: таймер.
Для подсчета производительности, coremark опирается на функцию, возвращающую текущее время, поэтому для оценки производительности нам потребуется вспомогательное периферийное устройство: таймер.
Для вывода результатов тестирования, необходимо описать способ, которым кормарк сможет выводить очередной символ сообщения — для этого мы будем использовать контроллер UART из ЛР№13.
Для вывода результатов тестирования, необходимо описать способ, которым coremark сможет выводить очередной символ сообщения — для этого мы будем использовать контроллер UART из ЛР№13.
Кроме того, скомпилированная без оптимизаций программа будет занимать чуть более 32KiB, поэтому нам потребуется изменить размер памяти инструкций.
Таким образом, для того чтобы запустить данную программу, нам необходимо выполнить как аппаратные изменения процессорной системы (добавить таймер и (если отсутствует) контроллер UART), так и программные изменения самого кормарка (для этого в нем предусмотрены специальные платформозависимые файлы, в которых объявлены функции, реализацию которых нам необходимо выполнить).
Таким образом, для того чтобы запустить данную программу, нам необходимо выполнить как аппаратные изменения процессорной системы (добавить таймер и (если отсутствует) контроллер UART), так и программные изменения самого coremark (для этого в нем предусмотрены специальные платформозависимые файлы, в которых объявлены функции, реализацию которых нам необходимо выполнить).
Говорят, что лучшей проверкой процессора на наличие ошибок является попытка запустить на нем ядро Linux. Наша процессорная система на это в принципе не рассчитана (поскольку для запуска Linux нужна поддержка нескольких дополнительных расширений), поэтому coremark можно по праву считать "бюджетным" аналогом проверки процессора на прочность.
@@ -43,27 +43,29 @@
### Таймер
Разберемся с тем, как будет работать наш таймер. По сути, это просто системный счетчик (не путайте с программным счетчиком), непрерывно считающий такты с момента последнего сброса. Системным он называется потому, что работает на системной тактовой частоте. Значения частот, на которых работают процессорные системы сопоставимы с 32-битными значениями, поэтому системный счетчик должен быть 64-битным. Для измерения времени мы будем засекать значение счетчика на момент начала отсчета и значение счетчика в конце отсчета. Зная тактовую частоту и разность между значениями счетчика мы с легкостью сможем вычислить прошедшее время. При этом нужно обеспечить счетчик такой разрядностью, чтобы он точно не смог переполниться.
Разберемся с тем, как будет работать наш таймер. По сути, это просто системный счётчик (не путайте с программным счётчиком), непрерывно считающий такты с момента последнего сброса. Системным он называется потому, что работает на системной тактовой частоте. Значения частот, на которых работают процессорные системы сопоставимы с 32-битными значениями, поэтому системный счётчик должен быть 64-битным. Для измерения времени мы будем засекать значение счётчика на момент начала отсчета и значение счётчика в конце отсчёта. Зная тактовую частоту и разность между значениями счётчика мы с легкостью сможем вычислить прошедшее время. При этом нужно обеспечить счётчик такой разрядностью, чтобы он точно не смог переполниться.
Поскольку мы уже назвали данный модуль "таймером", чтобы тот не был слишком простым, давайте добавим ему функциональности: пускай это будет устройство, способное генерировать прерывание через заданное число тактов. Таким образом, процессорная система сможет засекать время без постоянного опроса счетчика. Для работы кормарка эта функциональность не нужна — если ее реализация окажется слишком сложной для вас, просто создайте системный счетчик, инкрементирующийся каждый такт, с доступом на чтение по адресу `32'h0`.
Поскольку мы уже назвали данный модуль "таймером", чтобы тот не был слишком простым, давайте добавим ему функциональности: пускай это будет устройство, способное генерировать прерывание через заданное число тактов. Таким образом, процессорная система сможет засекать время без постоянного опроса счётчика. Для работы coremark эта функциональность не нужна — если ее реализация окажется слишком сложной для вас, просто создайте системный счётчик, инкрементирующийся каждый такт, с доступом на чтение по адресу `32'h0`.
Было бы удобно, чтобы мы могли управлять тем, каким образом данный модуль будет генерировать такое прерывание: однократно, заданное число раз, или же бесконечно, пока тот не остановят.
Таким образом, мы сформировали следующее адресное пространство данного контроллера:
Таким образом, мы сформировали адресное пространство контроллера, представленное в аблице 1_.
|Адрес|Режим доступа|Допустимые значения| Функциональное назначение |
|-----|-------------|-------------------|---------------------------------------------------------------------------------------|
|0x00 | R | [0:2³²-1] | Значение младших 32 бит системного счетчика, доступное только для чтения |
|0x04 | R | [0:2³²-1] | Значение старших 32 бит системного счетчика, доступное только для чтения |
|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. Адресное пространство
```SystemVerilog
Прототип модуля представлен в _листинге 1_.
```Verilog
module timer_sb_ctrl(
/*
Часть интерфейса модуля, отвечающая за подключение к системной шине
@@ -84,11 +86,13 @@ module timer_sb_ctrl(
);
```
Обратите внимание, что у модуля нет сигнала interrupt_return_i. Модуль будет генерировать прерывания ровно на 1 такт. Если процессор в этот момент не будет готов обработать прерывания (обрабатывая в этот момент какой-либо другой перехват) — запрос будет сразу же пропущен и таймер начнет отсчитывать следующий.
_Листинг 1. Прототип таймера._
Обратите внимание, что у модуля нет сигнала `interrupt_return_i`. Модуль будет генерировать прерывания ровно на 1 такт. Если процессор в этот момент не будет готов обработать прерывания (обрабатывая в этот момент какой-либо другой перехват) — запрос будет сразу же пропущен и таймер начнет отсчитывать следующий.
Для работы данного контроллера потребуются следующие сигналы:
```SystemVerilog
```Verilog
logic [63:0] system_counter;
logic [63:0] delay;
enum logic [1:0] {OFF, NTIMES, FOREVER} mode, next_mode;
@@ -96,7 +100,7 @@ logic [31:0] repeat_counter;
logic [63:0] system_counter_at_start;
```
- `system_counter` — регистр, ассоциированный с адресами `0x00` (младшие 32 бита) и `0x04` (старшие 32 бита), системный счетчик. Задача регистра заключается в ежетактном увеличении на единицу.
- `system_counter` — регистр, ассоциированный с адресами `0x00` (младшие 32 бита) и `0x04` (старшие 32 бита), системный счётчик. Задача регистра заключается в ежетактном увеличении на единицу.
- `delay` — регистр, ассоциированный с адресами `0x08` (младшие 32 бита) и `0x0c` (старшие 32 бита). Число тактов, спустя которое таймер (когда тот будет включен) сгенерирует прерывание. Данный регистр изменяется только сбросом, либо запросом на запись.
- `mode` — регистр, ассоциированный с адресом `0x10`. Режим работы таймера:
- `OFF` — отключен (не генерирует прерывания)
@@ -104,11 +108,11 @@ logic [63:0] system_counter_at_start;
- `FOREVER` — бесконечная генерация прерываний. Не отключится, пока режим работы прерываний не будет изменен.
- `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`.
- `system_counter_at_start` — неархитектурный регистр, хранящий значение системного счётчика на момент начала отсчета таймера. Обновляется при генерации прерывания (если это не последнее прерывание в режиме `NTIMES`) и при запросе на запись в регистр `mode` значения не `OFF`.
Выходной сигнал interrupt_request_o должен быть равен единице, если текущий режим работы не `OFF`, а сумма `system_counter_at_start` и `delay` равна `system_counter`.
Для подключения данного таймера к системной шине, мы воспользуемся первым свободным базовым адресом, оставшимся после ЛР13: `0x08`. Таким образом, для обращения к системному счетчику, процессор будет использовать адрес `0x08000000` для обращения к регистру `delay` `0x08000008` и т.п.
Для подключения данного таймера к системной шине, мы воспользуемся первым свободным базовым адресом, оставшимся после ЛР13: `0x08`. Таким образом, для обращения к системному счётчику, процессор будет использовать адрес `0x08000000` для обращения к регистру `delay` `0x08000008` и т.п.
### Настройка Coremark
@@ -125,9 +129,9 @@ logic [63:0] system_counter_at_start;
#### 1. Реализация функции, измеряющей время
Не мы первые придумали измерять время путем отсчета системных тактов, поэтому вся логика по измерению времени уже реализована в coremark. От нас требуется только реализовать функцию, которая возвращает текущее значение системного счетчика.
Не мы первые придумали измерять время путем отсчета системных тактов, поэтому вся логика по измерению времени уже реализована в coremark. От нас требуется только реализовать функцию, которая возвращает текущее значение системного счётчика.
Данной функцией является `barebones_clock`, расположенная в файле [`core_portme.c`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.c). В данный момент, в реализации функции описан вызов ошибки (поскольку реализации как таковой нет). Мы должны **заменить** реализацию функции следующим кодом:
Данной функцией является `barebones_clock`, расположенная в файле [`core_portme.c`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.c). В данный момент, в реализации функции описан вызов ошибки (поскольку реализации как таковой нет). Мы должны **заменить** реализацию функции кодом, приведённым в _листинге 2_.
```C
barebones_clock()
@@ -138,9 +142,11 @@ barebones_clock()
}
```
После ЛР14 вы уже должны представлять, что здесь происходит. Мы создали указатель с абсолютным адресом `0x08000000` — адресом системного счетчика. Разыменование данного указателя вернет текущее значение системного счетчика, что и должно быть результатом вызова этой функции. Поскольку тест закончится менее чем за секунду, не обязательно загружать значение старших 32 бит (они будут не равны нулю только спустя 2³²тактов / 10⁶тактов/с ≈ 429c).
_Листинг 2. Код функции `barebones_clock`._
Для того, чтобы корректно преобразовать тики системного счетчика во время, используется функция [`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):
После ЛР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):
```C
#define CLOCKS_PER_SEC 10000000
@@ -170,13 +176,15 @@ uart_send_char(char c)
}
```
_Листинг 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 одним стоповым битом и контролем бита четности. В этом случае, мы должны добавить в начало функции следующий код:
Допустим мы хотим, чтобы данные передавались на скорости `115200`, c одним стоповым битом и контролем бита четности. В этом случае, мы должны добавить в начало функции следующий код:
```C
portable_init(core_portable *p, int *argc, char *argv[])
@@ -190,6 +198,8 @@ portable_init(core_portable *p, int *argc, char *argv[])
}
```
_Листинг 4. Код функции `uart_send_char`._
#### 4. Дополнительные настройки
Для тонких настроек используется заголовочный файл [`core_portme.h`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.h), куда также требуется внести несколько изменений. Нам необходимо:
@@ -200,7 +210,7 @@ portable_init(core_portable *p, int *argc, char *argv[])
### Компиляция
Для компиляции программы, вам потребуются предоставленные файлы [Makefile](Makefile) и [linker_script.ld](linker_script.ld), а также файл [startup.S](../14.%20Programming/startup.S) из ЛР14. Эти файлы необходимо скопировать с заменой в корень папки с программой.
Для компиляции программы, вам потребуются предоставленные файлы [Makefile](Makefile) и [linker_script.ld](linker_script.ld), а также файл [startup.S](../14.%20Programming/startup.S) из ЛР14. Эти файлы необходимо скопировать с заменой в корень папки с программой.
`Makefile` написан из расчёта, что кросс-компилятор расположен по пути `C:/riscv_cc/`. В случае, если это не так, измените первую строчку данного файла в соответствии с расположением кросс-компилятора.
@@ -210,27 +220,39 @@ portable_init(core_portable *p, int *argc, char *argv[])
make
```
В случае, если на вашем рабочем компьютере не установлена утилита `make`, то вы можете скомпилировать программу вручную, выполнив следующую последовательность команд:
В случае, если на вашем рабочем компьютере не установлена утилита `make`, то вы можете скомпилировать программу вручную, выполнив последовательность команд, приведённую в _листинге 5_.
```bash
cp barebones/*.c barebones/*.h ./
/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" core_main.c -o core_main.o
/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" startup.S -o startup.o
/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" core_list_join.c -o core_list_join.o
/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" core_matrix.c -o core_matrix.o
/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" core_portme.c -o core_portme.o
/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" core_state.c -o core_state.o
/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" core_util.c -o core_util.o
/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" cvt.c -o cvt.o
/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" ee_printf.c -o ee_printf.o
/c/riscv_cc/bin/riscv-none-elf-gcc core_main.o startup.o core_list_join.o core_matrix.o core_portme.o core_state.o core_util.o cvt.o ee_printf.o -Wl,--gc-sections -nostartfiles -T linker_script.ld -march=rv32i_zicsr -mabi=ilp32 -I"./" -o coremark.elf
/c/riscv_cc/bin/riscv-none-elf-objdump -D coremark.elf > coremark_disasm.S
/c/riscv_cc/bin/riscv-none-elf-objcopy -O verilog --verilog-data-width=4 -j .data -j .sdata -j .bss coremark.elf coremark_data.mem
/c/riscv_cc/bin/riscv-none-elf-objcopy -O verilog --verilog-data-width=4 -j .text coremark.elf coremark_instr.mem
/c/riscv_cc/bin/riscv-none-elf-size coremark.elf
sed -i '1d' coremark_data.mem
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
@@ -246,9 +268,9 @@ sed -i '1d' coremark_data.mem
### Запуск моделирования
Программирование 32KiB по UART займет ощутимое время, поэтому вам предлагается проинициализировать память инструкций и данных "по старинке" через системные функции `$readmemh`.
Программирование 32KiB по UART займет ощутимое время, поэтому вам предлагается проинициализировать память инструкций и данных "по-старинке" через системные функции `$readmemh`.
Если все было сделано без ошибок, то примерно через `300ms` после снятия сигнала сброса с ядра процессора выход `tx_o` начнет быстро менять свое значение, сигнализируя о выводе результатов программы, которые отобразятся в `tcl console` примерно еще через `55ms` в виде:
Если все было сделано без ошибок, то примерно через `300ms` после снятия сигнала сброса с ядра процессора выход `tx_o` начнет быстро менять свое значение, сигнализируя о выводе результатов программы, которые отобразятся в `tcl console` примерно еще через `55ms` в виде _листинга 6_ (вывод сообщения будет завершен приблизительно на `355ms` времени моделирования).
```text
2K performance run parameters for coremark.
@@ -269,20 +291,20 @@ seedcrc : 0x29f4
Errors detected
```
(вывод сообщения будет завершен приблизительно на `355ms` времени моделирования).
_Листинг 6. Лог вывода результатов coremark. Значения "Total time (secs)" и "Iterations/Sec" скрыты до получения результатов моделирования._
## Порядок выполнения задания
1. [Опишите](#таймер) таймер в виде модуля `timer_sb_ctrl`.
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), либо скачать его в виде архива.
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`, возвращающую текущее значение системного счетчика;
1. [реализовать](#1-реализация-функции-измеряющей-время) функцию `barebones_clock`, возвращающую текущее значение системного счётчика;
2. объявить макрос `CLOCKS_PER_SEC`, характеризующий тактовую частоту процессора;
3. [реализовать](#3-реализация-функции-первичной-настройки) функцию `portable_init`, выполняющую первичную инициализацию периферийных устройств до начала теста;
2. в файле `ee_printf.c` [реализовать](#2-реализация-вывода-очередного-символа-сообщения) функцию `uart_send_char`, отвечающую за отправку очередного символа сообщения о результате.
@@ -291,12 +313,12 @@ Errors detected
1. Если кросскомпилятор расположен не в директории `C:/riscv_cc`, перед вызовом `make` вам необходимо соответствующим образом отредактировать первую строчку в `Makefile`.
2. В случае отсутствия на компьютере утилиты `make`, вы можете самостоятельно скомпилировать программу вызовом команд, представленных в разделе ["Компиляция"](#компиляция).
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, не забудьте удалить первую строчку со стартовым адресом из файла, инициализирующего память данных.
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).
4. В исходном виде тестбенч описан под инициализацию памяти посредством `$readmemh`.
11. Выполните моделирование системы с помощью модуля [lab_16.tb_coremark](lab_16.tb_coremark).
1. Результаты теста будут выведены приблизительно на `355ms` времени моделирования.
## Оценка производительности
@@ -304,7 +326,7 @@ Errors detected
<details>
<summary>Прочти меня после успешного завершения моделирования </summary>
Итак, вы получили сообщение вида:
Итак, вы получили сообщение, представленное в _листинге 7_.
```text
2K performance run parameters for coremark.
@@ -325,6 +347,8 @@ seedcrc : 0xe9f5
Errors detected
```
_Листинг 7. Лог вывода результатов coremark._
Не обращайте внимание на строки "ERROR! Must execute for at least 10 secs for a valid result!" и "Errors detected". Программа считает, что для корректных результатов, необходимо крутить ее по кругу в течении минимум 10 секунд, однако по большей части это требование необходимо для более достоверного результата у систем с кэшем/предсказателями переходов и прочими блоками, которые могут изменить количество тактов на прохождение между итерациями. Наш однотактный процессор будет вести себя одинаково на каждом круге, поэтому нет смысла в дополнительном времени моделирования. Тем не менее, если вы захотите получить результаты, не содержащих сообщения об ошибках, измените число итераций в файле `core_portme.h` до 45.
Нас интересует строка:
@@ -335,9 +359,9 @@ Iterations/Sec : 3.446111
Это и есть так называемый "кормарк" — метрика данной программы. Результат нашего процессора: ~3.45 кормарка.
Обычно, для сравнения между собой нескольких реализаций микроархитектур, более достоверной считается величина "кормарк / МГц", т.е. значение кормарка, поделённое на тактовую частоту процессора, поскольку время прохождения теста напрямую зависит от тактовой частоты. Это значит, что чип с менее удачной микроархитектурной реализацией может выиграть по кормарку просто потому что он был выпущен по лучшей технологии, позволяющей запустить его на больших частотах. Кормарк/МГц нормализует результаты, позволяя сравнивать микроархитектурные решения не заботясь о том, на какой частоте был получен результат.
Обычно, для сравнения между собой нескольких реализаций микроархитектур, более достоверной считается величина "кормарк / МГц", т.е. число кормарков, поделённое на тактовую частоту процессора, поскольку время прохождения теста напрямую зависит от тактовой частоты. Это значит, что чип с менее удачной микроархитектурной реализацией может выиграть по кормарку просто потому, что он был выпущен по лучшей технологии, позволяющей запустить его на больших частотах. Кормарк/МГц нормализует результаты, позволяя сравнивать микроархитектурные решения, не заботясь о том, на какой частоте был получен результат.
Более того, при сравнении с другими результатами, необходимо учитывать флаги оптимизации, которые использовались при компиляции программы, поскольку они также влияют на результат. Например, если собрать кормарк с уровнем оптимизаций `-O1`, результат нашей системы скакнет до 11.23 кормарков, что всего лишь является следствием того, что программа стала меньше обращаться к памяти вследствие оптимизаций. Именно поэтому результаты кормарка указываются вместе с опциями, с которыми тот был собран.
Более того, при сравнении с другими результатами, необходимо учитывать флаги оптимизации, которые использовались при компиляции программы, поскольку они также влияют на результат. Например, если собрать coremark с уровнем оптимизаций `-O1`, результат нашей системы скакнёт до 11.23 кормарков, что всего лишь является следствием того, что программа стала меньше обращаться к памяти вследствие оптимизаций. Именно поэтому результаты coremark указываются вместе с опциями, с которыми тот был собран.
Мы не будем уходить в дебри темных паттернов маркетинга и вместо этого будет оценивать производительность в лоб: сколько кормарков в секунду смог прогнать наш процессор без каких-либо оптимизаций в сравнении с представленными результатами других систем вне зависимости от их оптимизаций.
@@ -353,7 +377,7 @@ Iterations/Sec : 3.446111
А знаете, с чем был сопоставим по производительности компьютер TRS-80? С бортовым компьютером [Apollo Guidance Computer](https://en.wikipedia.org/wiki/Apollo_Guidance_Computer), который проводил вычисления и контролировал движение, навигацию, управлял командным и лунным модулями в ходе полётов по программе Аполлон.
Иными словами, мы разработали процессор, который приблизительно в 7-14 раз производительнее компьютера, управлявшего полетом космического корабля, который доставил человека на Луну!
Иными словами, мы разработали процессор, который приблизительно в 7-14 раз производительнее компьютера, управлявшего полётом космического корабля, который доставил человека на Луну!
Можно ли как-то улучшить наш результат? Безусловно. Мы можем улучшить его примерно на 5% изменив буквально одну строчку. Дело в том, что для простоты реализации, мы генерировали сигнал `stall` для каждой операции обращения в память. Однако приостанавливать работу процессора было необходимо только для операций чтения из памяти. Если не генерировать сигнал `stall` для операций типа `store`, мы уменьшим время, необходимое на исполнение бенчмарка. Попробуйте сделать это сами.