mirror of
https://github.com/MPSU/APS.git
synced 2025-09-16 01:30:10 +00:00
BREAKING CHANGE! Сдвиг нумерации в лабах
Лабу по дейзи-цепочке необходимо вставить сразу после лабы по интеграции контроллера прерываний, поэтому приходится увеличить нумерацию оставшихся лаб.
This commit is contained in:
73
Labs/16. Coremark/Makefile
Normal file
73
Labs/16. Coremark/Makefile
Normal file
@@ -0,0 +1,73 @@
|
||||
CC_PATH = /c/riscv_cc/bin
|
||||
CC_PREFIX = riscv-none-elf
|
||||
|
||||
CC = $(CC_PATH)/$(CC_PREFIX)-gcc
|
||||
OBJDUMP = $(CC_PATH)/$(CC_PREFIX)-objdump
|
||||
OBJCOPY = $(CC_PATH)/$(CC_PREFIX)-objcopy
|
||||
SIZE = $(CC_PATH)/$(CC_PREFIX)-size
|
||||
|
||||
ifndef src
|
||||
src = core_main.o
|
||||
endif
|
||||
|
||||
OBJS = $(src) startup.o core_list_join.o core_matrix.o core_portme.o core_state.o core_util.o cvt.o ee_printf.o
|
||||
|
||||
|
||||
LINK_SCRIPT = linker_script.ld
|
||||
OUTPUT = coremark
|
||||
OUTPUT_PROD = $(addprefix $(OUTPUT), .mem _instr.mem _data.mem .elf _disasm.S)
|
||||
# OUTPUT_PROD :=$(OUTPUT_PROD) $(addprefix tb_$(OUTPUT), .mem _instr.mem _data.mem .elf _disasm.S)
|
||||
|
||||
INC_DIRS = "./"
|
||||
SRC_DIR = ./src
|
||||
CC_FLAGS = -march=rv32i_zicsr -mabi=ilp32 -I$(INC_DIRS)
|
||||
LD_FLAGS = -Wl,--gc-sections -nostartfiles -T $(LINK_SCRIPT)
|
||||
|
||||
.PHONY: all setup clean clean_all size harvard princeton
|
||||
|
||||
all: clean setup harvard
|
||||
|
||||
setup:
|
||||
cp barebones/*.c barebones/*.h ./
|
||||
|
||||
harvard: $(OUTPUT).elf $(OUTPUT)_disasm.S size
|
||||
# $< означает "первая зависимость"
|
||||
${OBJCOPY} -O verilog --verilog-data-width=4 -j .data -j .sdata -j .bss $< $(OUTPUT)_data.mem
|
||||
${OBJCOPY} -O verilog --verilog-data-width=4 -j .text $< $(OUTPUT)_instr.mem
|
||||
${OBJCOPY} -O verilog -j .data -j .sdata -j .bss $< tb_$(OUTPUT)_data.mem
|
||||
${OBJCOPY} -O verilog -j .text $< tb_$(OUTPUT)_instr.mem
|
||||
sed -i '1d' $(OUTPUT)_data.mem
|
||||
|
||||
princeton: $(OUTPUT).elf $(OUTPUT)_disasm.S size
|
||||
${OBJCOPY} -O verilog --verilog-data-width=4 --remove-section=.comment $< $(OUTPUT).mem
|
||||
|
||||
$(OUTPUT).elf: $(OBJS)
|
||||
# $^ Означает "все зависимости".
|
||||
${CC} $^ $(LD_FLAGS) $(CC_FLAGS) -o $(OUTPUT).elf
|
||||
|
||||
$(OUTPUT)_disasm.S: $(OUTPUT).elf
|
||||
# $< означает "первая зависимость", $@ — "цель рецепта".
|
||||
${OBJDUMP} -D $< > $@
|
||||
|
||||
|
||||
# Шаблонные рецепты (см. https://web.mit.edu/gnu/doc/html/make_10.html#SEC91)
|
||||
# Здесь говорится как создать объектные файлы из одноименных исходников
|
||||
%.o: %.S
|
||||
${CC} -c $(CC_FLAGS) $^ -o $@
|
||||
|
||||
%.o: %.c
|
||||
${CC} -c $(CC_FLAGS) $^ -o $@
|
||||
|
||||
%.o: %.cpp
|
||||
${CC} -c $(CC_FLAGS) $^ -o $@
|
||||
|
||||
size: $(OUTPUT).elf
|
||||
# $< означает "первая зависимость"
|
||||
$(SIZE) $<
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS)
|
||||
rm -f core_portme.* cvt.c ee_printf.c
|
||||
|
||||
clean_all: clean
|
||||
rm -f $(OUTPUT_PROD)
|
346
Labs/16. Coremark/README.md
Normal file
346
Labs/16. Coremark/README.md
Normal file
@@ -0,0 +1,346 @@
|
||||
# Лабораторная работа 16 "Оценка производительности"
|
||||
|
||||
## Допуск к лабораторной работе
|
||||
|
||||
Данная лабораторная работа будет полностью опираться на навыки, полученные в ходе выполнения лабораторных работ:
|
||||
|
||||
13. [Периферийные устройства](../13.%20Peripheral%20units/)
|
||||
14. [Программирование](../14.%20Programming/)
|
||||
|
||||
## Цель
|
||||
|
||||
Дать количественную оценку характеризующую производительность реализованной вычислительной системы. На текущий момент мы создали процессорную систему, которая способна взаимодействовать с внешним миром посредством периферийных устройств ввода-вывода и программатора, по сути являющуюся компьютером. Однако встает вопрос, какое место данная система занимает в ряду уже существующих вычислительных систем.
|
||||
|
||||
<!-- Ближе ли она по производительности к [разностной машине Бэббиджа](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) (далее кормарк) — это набор синтетических тестов (специальных программ) для измерения производительности процессорной системы. В данный набор входят такие тесты, как работа со связными списками, матричные вычисления, обработка конечных автоматов и подсчет контрольной суммы. Результат выражается в одном числе, которое можно использовать для сравнения с результатами других процессорных систем.
|
||||
|
||||
Для подсчета производительности, кормарк опирается на функцию, возвращающую текущее время, поэтому для оценки производительности нам потребуется вспомогательное периферийное устройство: таймер.
|
||||
|
||||
Для вывода результатов тестирования, необходимо описать способ, которым кормарк сможет выводить очередной символ сообщения — для этого мы будем использовать контроллер UART из предыдущих лабораторных работ.
|
||||
|
||||
Кроме того, скомпилированная без оптимизаций программа будет занимать чуть более 32KiB, поэтому нам потребуется изменить размер памяти инструкций.
|
||||
|
||||
Таким образом, для того чтобы запустить данную программу, нам необходимо выполнить как аппаратные изменения процессорной системы (добавить таймер и (если отсутствует) контроллер UART), так и программные изменения самого кормарка (для этого в нем предусмотрены специальные платформозависимые файлы, в которых объявлены функции, реализацию которых нам необходимо выполнить).
|
||||
|
||||
## Задание
|
||||
|
||||
1. Реализовать модуль-контроллер "таймер".
|
||||
2. Подключить этот модуль к системной шине.
|
||||
2.1. В случае, если до этого в ЛР12 вашим устройством вывода было не UART TX, вам необходимо подключить к системной шине готовый модуль [uart_tx_sb_ctrl](../Made-up%20modules/lab_13.uart_tx_sb_ctrl.sv).
|
||||
3. Добавить реализацию платформозависимых функций программы coremark.
|
||||
4. Скомпилировать программу.
|
||||
5. Изменить размер памяти инструкций.
|
||||
6. Запустить моделирование.
|
||||
7. Сравнить результаты измерения производительности с результатами существующих процессорных системам.
|
||||
|
||||
### Таймер
|
||||
|
||||
Разберемся с тем, как будет работать наш таймер. По сути, это просто системный счетчик (не путайте с программным счетчиком), непрерывно считающий такты с момента последнего сброса. Системным он называется потому, что работает на системной тактовой частоте. Для измерения времени мы будем засекать значение счетчика на момент начала отсчета и значение счетчика в конце отсчета. Зная тактовую частоту и разность между значениями счетчика мы с легкостью сможем вычислить прошедшее время. При этом нужно обеспечить счетчик такой разрядностью, чтобы он точно не смог переполниться.
|
||||
|
||||
Поскольку мы уже назвали данный модуль "таймером", чтобы тот не был слишком простым, давайте добавим ему функциональности: пускай это будет устройство, способное генерировать прерывание через заданное число тактов. Таким образом, процессорная система сможет засекать время без постоянного опроса счетчика.
|
||||
|
||||
Было бы удобно, чтобы мы могли управлять тем, каким образом данный модуль будет генерировать такое прерывание: однократно, заданное число раз, или же бесконечно, пока тот не остановят.
|
||||
|
||||
Таким образом, мы сформировали следующее адресное пространство данного контроллера:
|
||||
|
||||
|Адрес|Режим доступа|Допустимые значения| Функциональное назначение |
|
||||
|-----|-------------|-------------------|---------------------------------------------------------------------------------|
|
||||
|0x00 | R | [0:2⁶⁴-1] | Значение системного счетчика, доступное только для чтения |
|
||||
|0x04 | RW | [0:2⁶⁴-1] | Указание задержки, спустя которую таймер будет генерировать прерывание |
|
||||
|0x08 | RW | [0:2] | Указание режима генерации прерываний (выключен, заданное число раз, бесконечно) |
|
||||
|0x0c | RW | [0:2³²-1] | Указание количества повторений генерации прерываний |
|
||||
|0x24 | W | 1 | Программный сброс |
|
||||
|
||||
Прототип модуля следующий:
|
||||
|
||||
```SystemVerilog
|
||||
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
|
||||
);
|
||||
```
|
||||
|
||||
Для работы данного контроллера потребуются следующие сигналы:
|
||||
|
||||
```SystemVerilog
|
||||
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`, системный счетчик. Задача регистра заключается в ежетактном увеличении на единицу.
|
||||
- `delay` — регистр, ассоциированный с адресом `0x04`. Число тактов, спустя которое таймер (когда тот будет включен) сгенерирует прерывание. Данный регистр изменяется только сбросом, либо запросом на запись.
|
||||
- `mode` — регистр, ассоциированный с адресом `0x08`. Режим работы таймера:
|
||||
- `OFF` — отключен (не генерирует прерывания)
|
||||
- `NTIMES` — включен до тех пор, пока не сгенерирует N прерываний (Значение N хранится в регистре `repeat_counter` и обновляется после каждого сгенерированного прерывания). После генерации N прерываний, переходит в режим `OFF`.
|
||||
- `FOREVER` — бесконечная генерация прерываний. Не отключится, пока режим работы прерываний не будет изменен.
|
||||
- `next_mode` — комбинационный сигнал, который подается на вход записи в регистр `mode` (аналог `next_state` из предыдущей лабораторной работы).
|
||||
- `repeat_counter` — регистр, ассоциированный с адресом `0x0c`. Количество повторений для режима `NTIMES`. Уменьшается в момент генерации прерывания в этом режиме.
|
||||
- `system_counter_at_start` — неархитектурный регистр, хранящий значение системного счетчика на момент начала отсчета таймера. Обновляется при генерации прерывания (если это не последнее прерывание в режиме `NTIMES`) и при запросе на запись в регистр `mode` значения не `OFF`.
|
||||
|
||||
Для подключения данного таймера к системной шине, мы воспользуемся первым свободным базовым адресом, оставшимся после ЛР12: `0x08`. Таким образом, для обращения к системному счетчику, процессор будет использовать адрес `0x08000000` для обращения к регистру `delay` `0x08000004` и т.п.
|
||||
|
||||
### Настройка 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). В данный момент, в реализации функции описан вызов ошибки (поскольку реализации как таковой нет). Мы должны **заменить** реализацию функции следующим кодом:
|
||||
|
||||
```C
|
||||
barebones_clock()
|
||||
{
|
||||
volatile ee_u32 *ptr = (ee_u32*)0x08000000;
|
||||
ee_u32 tim = *ptr;
|
||||
return tim;
|
||||
}
|
||||
```
|
||||
|
||||
После ЛР13 вы уже должны представлять, что здесь происходит. Мы создали указатель с абсолютным адресом `0x08000000` — адресом системного счетчика. Разыменование данного указателя вернет текущее значение системного счетчика, что и должно быть результатом вызова этой функции.
|
||||
|
||||
Для того, чтобы корректно преобразовать тики системного счетчика во время, используется функция [`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));
|
||||
}
|
||||
```
|
||||
|
||||
`0x06000000` — базовый адрес контроллера UART TX из ЛР12 (и адрес передаваемых этим контроллером данных).
|
||||
`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. Дополнительные настройки
|
||||
|
||||
Для тонких настроек используется заголовочный файл [`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) из ЛР13. Эти файлы необходимо скопировать с заменой в корень папки с программой.
|
||||
|
||||
`Makefile` написан из расчёта, что кросс-компилятор расположен по пути `C:/riscv_cc/`. В случае, если это не так, измените первую строчку данного файла в соответствии с расположением кросс-компилятора.
|
||||
|
||||
Для запуска компиляции, необходимо выполнить следующую команду, находясь в корне программы coremark:
|
||||
|
||||
```bash
|
||||
make
|
||||
```
|
||||
|
||||
В случае, если на вашем рабочем компьютере не установлена утилита `make`, то вы можете скомпилировать программу вручную, выполнив следующую последовательность команд:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
В случае успешной компиляции, вам будет выведено сообщение об итоговом размере секций инструкций и данных:
|
||||
|
||||
```text
|
||||
text data bss dec hex filename
|
||||
34324 2268 100 36692 8f54 coremark.elf
|
||||
```
|
||||
|
||||
### Изменение размера памяти инструкций
|
||||
|
||||
Как видите, размер секции инструкций превышает 32KiB на 1556 байт (32768—34324). Поэтому на время оценки моделирования, нам придется увеличить размер памяти инструкций до 64KiB, изменив число **слов** памяти инструкций до 16384. При этом необходимо изменить диапазон бит адреса, используемых для чтения инструкции из памяти с `[11:2]` на `[15:2]`.
|
||||
|
||||
Обратите внимание, что увеличение размера памяти в 16 раз приведет к значительному увеличению времени синтеза устройства, поэтому данное изменение мы производим исключительно на время поведенческого моделирования.
|
||||
|
||||
### Запуск моделирования
|
||||
|
||||
Программирование 32KiB по UART займет ощутимое время, поэтому вам предлагается проинициализировать память инструкций и данных "по старинке" через системные функции `$readmemh`.
|
||||
|
||||
Если все было сделано без ошибок, то примерно на `276ms` времени моделирования вам начнется выводиться сообщение вида:
|
||||
|
||||
```text
|
||||
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
|
||||
Correct operation validated. See README.md for run and reporting rules.
|
||||
```
|
||||
|
||||
(вывод сообщения будет завершен приблизительно на `335ms` времени моделирования).
|
||||
|
||||
## Порядок выполнения задания
|
||||
|
||||
1. [Опишите](#таймер) таймер в виде модуля `timer_sb_ctrl`.
|
||||
2. Проверьте описанный модуль с помощью тестового окружения [tb_timer](tb_timer.sv).
|
||||
3. Подключите `timer_sb_ctrl` к системной шине. Сигнал прерывания этого модуля подключать не нужно.
|
||||
2.1 В случае, если до этого в ЛР12 вашим устройством вывода было не 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` необходимо:
|
||||
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`.
|
||||
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` времени моделирования.
|
||||
|
||||
<details>
|
||||
<summary>11. Прочти меня после успешного завершения моделирования </summary>
|
||||
|
||||
Итак, вы получили сообщение вида:
|
||||
|
||||
```text
|
||||
CoreMark Size : 666
|
||||
Total ticks : 2574834
|
||||
Total time (secs): 0.257483
|
||||
Iterations/Sec : 3.883746
|
||||
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
|
||||
Correct operation validated. See README.md for run and reporting rules.
|
||||
```
|
||||
|
||||
Не обращайте внимание на строчку "ERROR! Must execute for at least 10 secs for a valid result!". Программа считает, что для корректных результатов, необходимо крутить ее по кругу в течении минимум 10 секунд, однако по большей части это требование необходимо для более достоверного результата у конвейерных систем или системах с запущенной ОС. Наш однотактный процессор будет вести себя одинаково на каждом круге, поэтому нет смысла в дополнительном времени моделирования.
|
||||
|
||||
Нас интересует строка:
|
||||
|
||||
```text
|
||||
Iterations/Sec : <скрыто до получения результатов моделирования>
|
||||
```
|
||||
|
||||
Это и есть так называемый "кормарк" — метрика данной программы. Результат нашего процессора: 3.88 кормарка.
|
||||
|
||||
Обычно, для сравнения между собой нескольких реализаций микроархитектур, более достоверной считается величина "кормарк / МГц", т.е. значение кормарка, поделённое на тактовую частоту процессора. Дело в том, что можно реализовать какую-нибудь очень сложную архитектуру, которая будет выдавать очень хороший кормарк, но при этом будет иметь очень низкую частоту. Более того, при сравнении с другими результатами, необходимо учитывать флаги оптимизации, которые использовались при компиляции программы, поскольку они также влияют на результат.
|
||||
|
||||
Мы не будем уходить в дебри темных паттернов маркетинга и вместо этого будет оценивать производительность в лоб: сколько кормарков в секунду смог прогнать наш процессор в сравнении с представленными результатами других систем вне зависимости от их оптимизаций.
|
||||
|
||||
Таблица опубликованных результатов находится по адресу: [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), который проводил вычисления и контролировал движение, навигацию, и управлял командным и лунным модулями в ходе полётов по программе Аполлон.
|
||||
|
||||
Иными словами, мы разработали процессор, который приблизительно в 7-14 раз производительнее компьютера, управлявшего полетом космического корабля, который доставил человека на Луну!
|
||||
|
||||
Можно ли как-то улучшить наш результат? Безусловно. Мы можем улучшить его примерно на 5% изменив буквально одну строчку. Дело в том, что для простоты реализации, мы генерировали сигнал `stall` для каждой операции обращения в память. Однако приостанавливать работу процессора было необходимо только для операций чтения из памяти. Если не генерировать сигнал `stall` для операций типа `store`, мы уменьшим время, необходимое на исполнение бенчмарка. Попробуйте сделать это сами.
|
||||
|
||||
Добавление умножителей, конвейеризация и множество других потенциальных улучшений увеличат производительность в разы.
|
||||
|
||||
Но это, как говорится, уже другая история.
|
||||
|
||||
</details>
|
197
Labs/16. Coremark/linker_script.ld
Normal file
197
Labs/16. Coremark/linker_script.ld
Normal file
@@ -0,0 +1,197 @@
|
||||
OUTPUT_FORMAT("elf32-littleriscv") /* Указываем порядок следования байт */
|
||||
|
||||
ENTRY(_start) /* мы сообщаем компоновщику, что первая
|
||||
исполняемая процессором инструкция
|
||||
находится у метки "start"
|
||||
*/
|
||||
|
||||
_text_size = 0x10000; /* Размер памяти инстр.: 16KiB */
|
||||
_data_base_addr = _text_size; /* Стартовый адрес секции данных */
|
||||
_data_size = 0x04000; /* Размер памяти данных: 16KiB */
|
||||
|
||||
_data_end = _data_base_addr + _data_size;
|
||||
|
||||
_trap_stack_size = 2560; /* Размер стека обработчика перехватов.
|
||||
Данный размер позволяет выполнить
|
||||
до 32 вложенных вызовов при обработке
|
||||
перехватов.
|
||||
*/
|
||||
|
||||
_stack_size = 1280; /* Размер программного стека.
|
||||
Данный размер позволяет выполнить
|
||||
до 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
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
В данном разделе описывается размещение программы в памяти.
|
||||
Программа разделяется на различные секции:
|
||||
- секции исполняемого кода программа;
|
||||
- секции статических переменных и массивов, значение которых должно быть
|
||||
"вшито" в программу;
|
||||
и т.п.
|
||||
*/
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
PROVIDE( _start = 0x00000000 ); /* Позиция start в памяти
|
||||
/*
|
||||
В скриптах компоновщика есть внутренняя переменная, записываемая как '.'
|
||||
Эта переменная называется "счетчиком адресов". Она хранит текущий адрес в
|
||||
памяти.
|
||||
В начале файла она инициализируется нулем. Добавляя новые секции, эта
|
||||
переменная будет увеличиваться на размер каждой новой секции.
|
||||
Если при размещении секций не указывается никакой адрес, они будут размещены
|
||||
по текущему значению счетчика адресов.
|
||||
Этой переменной можно присваивать значения, после этого, она будет
|
||||
увеличиваться с этого значения.
|
||||
Подробнее:
|
||||
https://home.cs.colorado.edu/~main/cs1300/doc/gnu/ld_3.html#IDX338
|
||||
*/
|
||||
|
||||
/*
|
||||
Следующая команда сообщает, что начиная с адреса, которому в данных момент
|
||||
равен счетчик адресов (в данный момент, начиная с нуля) будет находиться
|
||||
секция .text итогового файла, которая состоит из секций .boot, а также всех
|
||||
секций, начинающихся на .text во всех переданных компоновщику двоичных
|
||||
файлах.
|
||||
Дополнительно мы указываем, что данная секция должна быть размещена в
|
||||
регионе "rom".
|
||||
*/
|
||||
.text : {*(.boot) *(.text*)} >rom
|
||||
|
||||
|
||||
/*
|
||||
Поскольку мы не знаем суммарного размера получившейся секции, мы проверяем
|
||||
что не вышли за границы памяти инструкций и переносим счетчик адресов за
|
||||
пределы памяти инструкций в область памяти данных.
|
||||
Дополнительно мы указываем, что данная секция должна быть размещена в
|
||||
регионе "ram".
|
||||
*/
|
||||
ASSERT(. < _text_size, ".text section exceeds instruction memory size")
|
||||
. = _data_base_addr;
|
||||
|
||||
/*
|
||||
Следующая команда сообщает, что начиная с адреса, которому в данных момент
|
||||
равен счетчик адресов (_data_base_addr) будет находиться секция .data
|
||||
итогового файла, которая состоит из секций всех секций, начинающихся
|
||||
на .data во всех переданных компоновщику двоичных файлах.
|
||||
Дополнительно мы указываем, что данная секция должна быть размещена в
|
||||
регионе "ram".
|
||||
*/
|
||||
.data : {*(.*data*)} >ram
|
||||
|
||||
/*
|
||||
Общепринято присваивать GP значение равное началу секции данных, смещенное
|
||||
на 2048 байт вперед.
|
||||
Благодаря относительной адресации со смещением в 12 бит, можно адресоваться
|
||||
на начало секции данных, а также по всему адресному пространству вплоть до
|
||||
4096 байт от начала секции данных, что сокращает объем требуемых для
|
||||
адресации инструкций (практически не используются операции LUI, поскольку GP
|
||||
уже хранит базовый адрес и нужно только смещение).
|
||||
Подробнее:
|
||||
https://groups.google.com/a/groups.riscv.org/g/sw-dev/c/60IdaZj27dY/m/s1eJMlrUAQAJ
|
||||
*/
|
||||
_gbl_ptr = _data_base_addr + 0x800;
|
||||
|
||||
|
||||
/*
|
||||
Поскольку мы не знаем суммарный размер всех используемых секций данных,
|
||||
перед размещением других секций, необходимо выравнять счетчик адресов по
|
||||
4х-байтной границе.
|
||||
*/
|
||||
. = ALIGN(4);
|
||||
|
||||
|
||||
/*
|
||||
BSS (block started by symbol, неофициально его расшифровывают как
|
||||
better save space) — это сегмент, в котором размещаются неинициализированные
|
||||
статические переменные. В стандарте Си сказано, что такие переменные
|
||||
инициализируются нулем (или NULL для указателей). Когда вы создаете
|
||||
статический массив — он должен быть размещен в исполняемом файле.
|
||||
Без bss-секции, этот массив должен был бы занимать такой же объем
|
||||
исполняемого файла, какого объема он сам. Массив на 1000 байт занял бы
|
||||
1000 байт в секции .data.
|
||||
Благодаря секции bss, начальные значения массива не задаются, вместо этого
|
||||
здесь только записываются названия переменных и их адреса.
|
||||
Однако на этапе загрузки исполняемого файла теперь необходимо принудительно
|
||||
занулить участок памяти, занимаемый bss-секцией, поскольку статические
|
||||
переменные должны быть проинициализированы нулем.
|
||||
Таким образом, bss-секция значительным образом сокращает объем исполняемого
|
||||
файла (в случае использования неинициализированных статических массивов)
|
||||
ценой увеличения времени загрузки этого файла.
|
||||
Для того, чтобы занулить bss-секцию, в скрипте заводятся две переменные,
|
||||
указывающие на начало и конец bss-секции посредством счетчика адресов.
|
||||
Подробнее:
|
||||
https://en.wikipedia.org/wiki/.bss
|
||||
|
||||
Дополнительно мы указываем, что данная секция должна быть размещена в
|
||||
регионе "ram".
|
||||
*/
|
||||
_bss_start = .;
|
||||
.bss : {*(.bss*)} >ram
|
||||
_bss_end = .;
|
||||
|
||||
|
||||
/*=================================
|
||||
Секция аллоцированных данных завершена, остаток свободной памяти отводится
|
||||
под программный стек, стек прерываний и (возможно) кучу. В соглашении о
|
||||
вызовах архитектуры RISC-V сказано, что стек растет снизу вверх, поэтому
|
||||
наша цель разместить его в самых последних адресах памяти.
|
||||
Поскольку стеков у нас два, в самом низу мы разместим стек прерываний, а
|
||||
над ним программный стек. При этом надо обеспечить защиту программного
|
||||
стека от наложения на него стека прерываний.
|
||||
Однако перед этим, мы должны убедиться, что под программный стек останется
|
||||
хотя бы 1280 байт (ничем не обоснованное число, взятое с потолка).
|
||||
Такое значение обеспечивает до 16 вложенных вызовов (если сохранять только
|
||||
необерегаемые регистры).
|
||||
=================================
|
||||
*/
|
||||
|
||||
/* Мы хотим гарантировать, что под стек останется как минимум 1280 байт */
|
||||
ASSERT(. < (_data_end - _trap_stack_size - _stack_size),
|
||||
"Program size is too big")
|
||||
|
||||
/* Перемещаем счетчик адресов над стеком прерываний (чтобы после мы могли
|
||||
использовать его в вызове ALIGN) */
|
||||
. = _data_end - _trap_stack_size;
|
||||
|
||||
/*
|
||||
Размещаем указатель программного стека так близко к границе стека
|
||||
прерываний, насколько можно с учетом требования о выравнивании адреса
|
||||
стека до 16 байт.
|
||||
Подробнее:
|
||||
https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
|
||||
*/
|
||||
_stack_ptr = ALIGN(16) <= _data_end - _trap_stack_size?
|
||||
ALIGN(16) : ALIGN(16) - 16;
|
||||
ASSERT(_stack_ptr <= _data_end - _trap_stack_size, "SP exceed memory size")
|
||||
|
||||
/* Перемещаем счетчик адресов в конец памяти (чтобы после мы могли
|
||||
использовать его в вызове ALIGN) */
|
||||
. = _data_end;
|
||||
|
||||
/*
|
||||
Обычно память имеет размер, кратный 16, но на случай, если это не так, мы
|
||||
делаем проверку, после которой мы либо остаемся в самом конце памяти (если
|
||||
конец кратен 16), либо поднимаемся на 16 байт вверх от края памяти,
|
||||
округленного до 16 в сторону большего значения
|
||||
*/
|
||||
_trap_stack_ptr = ALIGN(16) <= _data_end ? ALIGN(16) : ALIGN(16) - 16;
|
||||
ASSERT(_trap_stack_ptr <= _data_end, "ISP exceed memory size")
|
||||
}
|
135
Labs/16. Coremark/startup.S
Normal file
135
Labs/16. Coremark/startup.S
Normal file
@@ -0,0 +1,135 @@
|
||||
.section .boot
|
||||
|
||||
.global _start
|
||||
_start:
|
||||
la gp, _gbl_ptr # Инициализация глобального указателя
|
||||
la sp, _stack_ptr # Инициализация указателя на стек
|
||||
|
||||
# Инициализация (зануление) сегмента bss
|
||||
la t0, _bss_start
|
||||
la t1, _bss_end
|
||||
_bss_init_loop:
|
||||
blt t1, t0, _irq_config
|
||||
sw zero, 0(t0)
|
||||
addi t0, t0, 4
|
||||
j _bss_init_loop
|
||||
|
||||
# Настройка вектора (mtvec) и маски (mie) прерываний, а также указателя на стек
|
||||
# прерываний (mscratch).
|
||||
_irq_config:
|
||||
la t0, _int_handler
|
||||
li t1, -1 # -1 (все биты равны 1) означает, что разрешены все прерывания
|
||||
la t2, _trap_stack_ptr
|
||||
csrw mtvec, t0
|
||||
csrw mie, t1
|
||||
csrw mscratch, t2
|
||||
|
||||
# Вызов функции main
|
||||
_main_call:
|
||||
li a0, 0 # Передача аргументов argc и argv в main. Формально, argc должен
|
||||
li a1, 0 # быть больше нуля, а argv должен указывать на массив строк,
|
||||
# нулевой элемент которого является именем исполняемого файла,
|
||||
# Но для простоты реализации оба аргумента всего лишь обнулены.
|
||||
# Это сделано для детерминированного поведения программы в случае,
|
||||
# если будет пытаться использовать эти аргументы.
|
||||
call main
|
||||
# Зацикливание после выхода из функции main
|
||||
_endless_loop:
|
||||
j _endless_loop
|
||||
|
||||
# Низкоуровневый обработчик прерывания отвечает за:
|
||||
# * Сохранение и восстановление контекста;
|
||||
# * Вызов высокоуровневого обработчика с передачей id источника прерывания в
|
||||
# качестве аргумента.
|
||||
# В основе кода лежит обработчик из репозитория urv-core:
|
||||
# https://github.com/twlostow/urv-core/blob/master/sw/common/irq.S
|
||||
# Из реализации убраны сохранения нереализованных CS-регистров. Кроме того,
|
||||
# судя по документу приведенному ниже, обычное ABI подразумевает такое же
|
||||
# сохранение контекста, что и при программном вызове (EABI подразумевает еще
|
||||
# меньшее сохранение контекста), поэтому нет нужды сохранять весь регистровый
|
||||
# файл.
|
||||
# Документ:
|
||||
# https://github.com/riscv-non-isa/riscv-eabi-spec/blob/master/EABI.adoc
|
||||
_int_handler:
|
||||
# Данная операция меняет местами регистры sp и mscratch.
|
||||
# В итоге указатель на стек прерываний оказывается в регистре sp, а вершина
|
||||
# программного стека оказывается в регистре mscratch.
|
||||
csrrw sp,mscratch,sp
|
||||
|
||||
# Далее мы поднимаемся по стеку прерываний и сохраняем все регистры.
|
||||
addi sp,sp,-80 # Указатель на стек должен быть выровнен до 16 байт, поэтому
|
||||
# поднимаемся вверх не на 76, а на 80.
|
||||
sw ra,4(sp)
|
||||
# Мы хотим убедиться, что очередное прерывание не наложит стек прерываний на
|
||||
# программный стек, поэтому записываем в освободившийся регистр низ
|
||||
# программного стека, и проверяем что приподнятый указатель на верхушку
|
||||
# стека прерываний не залез в программный стек.
|
||||
# В случае, если это произошло (произошло переполнение стека прерываний),
|
||||
# мы хотим остановить работу процессора, чтобы не потерять данные, которые
|
||||
# могут помочь нам в отладке этой ситуации.
|
||||
la ra, _stack_ptr
|
||||
blt sp, ra, _endless_loop
|
||||
|
||||
sw t0,12(sp) # Мы перепрыгнули через смещение 8, поскольку там должен
|
||||
# лежать регистр sp, который ранее сохранили в mscratch.
|
||||
# Мы запишем его на стек чуть позже.
|
||||
sw t1,16(sp)
|
||||
sw t2,20(sp)
|
||||
sw a0,24(sp)
|
||||
sw a1,28(sp)
|
||||
sw a2,32(sp)
|
||||
sw a3,36(sp)
|
||||
sw a4,40(sp)
|
||||
sw a5,44(sp)
|
||||
sw a6,48(sp)
|
||||
sw a7,52(sp)
|
||||
sw t3,56(sp)
|
||||
sw t4,60(sp)
|
||||
sw t5,64(sp)
|
||||
sw t6,68(sp)
|
||||
|
||||
# Кроме того, мы сохраняем состояние регистров прерываний на случай, если
|
||||
# произойдет еще одно прерывание.
|
||||
csrr t0,mscratch
|
||||
csrr t1,mepc
|
||||
csrr a0,mcause
|
||||
sw t0,8(sp)
|
||||
sw t1,72(sp)
|
||||
sw a0,76(sp)
|
||||
|
||||
# Вызов высокоуровневого обработчика прерываний
|
||||
# call int_handler
|
||||
|
||||
# Восстановление контекста. В первую очередь мы хотим восстановить CS-регистры,
|
||||
# на случай, если происходило вложенное прерывание. Для этого, мы должны
|
||||
# вернуть исходное значение указателя стека прерываний. Однако его нынешнее
|
||||
# значение нам еще необходимо для восстановления контекста, поэтому мы
|
||||
# сохраним его в регистр a0, и будем восстанавливаться из него.
|
||||
mv a0,sp
|
||||
|
||||
lw t1,72(a0)
|
||||
addi sp,sp,80
|
||||
csrw mscratch,sp
|
||||
csrw mepc,t1
|
||||
lw ra,4(a0)
|
||||
lw sp,8(a0)
|
||||
lw t0,12(a0)
|
||||
lw t1,16(a0)
|
||||
lw t2,20(a0)
|
||||
lw a1,28(a0) # Мы пропустили a0, потому что сейчас он используется в
|
||||
# качестве указателя на верхушку стека и не может быть
|
||||
# восстановлен.
|
||||
lw a2,32(a0)
|
||||
lw a3,36(a0)
|
||||
lw a4,40(a0)
|
||||
lw a5,44(a0)
|
||||
lw a6,48(a0)
|
||||
lw a7,52(a0)
|
||||
lw t3,56(a0)
|
||||
lw t4,60(a0)
|
||||
lw t5,64(a0)
|
||||
lw t6,68(a0)
|
||||
lw a0,40(a0)
|
||||
|
||||
# Выход из обработчика прерывания
|
||||
mret
|
132
Labs/16. Coremark/tb_coremark.sv
Normal file
132
Labs/16. Coremark/tb_coremark.sv
Normal file
@@ -0,0 +1,132 @@
|
||||
module tb_coremark();
|
||||
|
||||
logic clk10mhz_i;
|
||||
logic aresetn_i;
|
||||
logic rx_i;
|
||||
logic tx_o;
|
||||
logic clk_i;
|
||||
logic rst_i;
|
||||
|
||||
assign aresetn_i = !rst_i;
|
||||
assign clk10mhz_i = clk_i;
|
||||
|
||||
logic rx_busy, rx_valid, tx_busy, tx_valid;
|
||||
logic [7:0] rx_data, tx_data;
|
||||
|
||||
always #50ns clk_i = !clk_i;
|
||||
|
||||
byte coremark_msg[103];
|
||||
integer coremark_cntr;
|
||||
|
||||
initial begin
|
||||
$timeformat(-9, 2, " ns", 3);
|
||||
clk_i = 0;
|
||||
rst_i <= 0;
|
||||
@(posedge clk_i);
|
||||
rst_i <= 1;
|
||||
repeat(2) @(posedge clk_i);
|
||||
rst_i <= 0;
|
||||
|
||||
dummy_programming();
|
||||
|
||||
coremark_cntr = 0;
|
||||
coremark_msg = {32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32};
|
||||
forever begin
|
||||
@(posedge clk_i);
|
||||
if(rx_valid) begin
|
||||
if((rx_data == 10) | (rx_data == 13)) begin
|
||||
$display("%s", coremark_msg);
|
||||
coremark_cntr = 0;
|
||||
coremark_msg = {32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32};
|
||||
end
|
||||
else begin
|
||||
coremark_msg[coremark_cntr] = rx_data;
|
||||
coremark_cntr++;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
initial #500ms $finish();
|
||||
riscv_top_asic DUT(.clk10mhz_i, .aresetn_i, .rx_i, .tx_o);
|
||||
|
||||
uart_rx rx(
|
||||
.clk_i (clk_i ),
|
||||
.rst_i (rst_i ),
|
||||
.rx_i (tx_o ),
|
||||
.busy_o (rx_busy ),
|
||||
.baudrate_i (17'd115200 ),
|
||||
.parity_en_i(1'b1 ),
|
||||
.stopbit_i (1'b1 ),
|
||||
.rx_data_o (rx_data ),
|
||||
.rx_valid_o (rx_valid )
|
||||
);
|
||||
|
||||
uart_tx tx(
|
||||
.clk_i (clk_i ),
|
||||
.rst_i (rst_i ),
|
||||
.tx_o (rx_i ),
|
||||
.busy_o (tx_busy ),
|
||||
.baudrate_i (17'd115200 ),
|
||||
.parity_en_i(1'b1 ),
|
||||
.stopbit_i (1'b1 ),
|
||||
.tx_data_i (tx_data ),
|
||||
.tx_valid_i (tx_valid )
|
||||
);
|
||||
|
||||
task send_data(input byte mem[$]);
|
||||
for(int i = mem.size()-1; i >=0; i--) begin
|
||||
tx_data = mem[i];
|
||||
tx_valid = 1'b1;
|
||||
@(posedge clk_i);
|
||||
tx_valid = 1'b0;
|
||||
@(posedge clk_i);
|
||||
while(tx_busy) @(posedge clk_i);
|
||||
end
|
||||
endtask
|
||||
|
||||
task rcv_data(input int size);
|
||||
byte str[57];
|
||||
logic [3:0][7:0] size_val;
|
||||
for(int i = 0; i < size; i++) begin
|
||||
@(posedge clk_i);
|
||||
while(!rx_valid)@(posedge clk_i);
|
||||
str[i] = rx_data;
|
||||
size_val[3-i] = rx_data;
|
||||
end
|
||||
if(size!=4)$display("%s", str);
|
||||
else $display("%d", size_val);
|
||||
wait(tx_o);
|
||||
endtask
|
||||
|
||||
task program_region(input byte mem[$], input logic [3:0][7:0] start_addr);
|
||||
byte str [4];
|
||||
logic [3:0][7:0] size;
|
||||
size = mem.size();
|
||||
if(start_addr) begin
|
||||
str = {start_addr[0],start_addr[1],start_addr[2],start_addr[3]};
|
||||
send_data(str);
|
||||
end
|
||||
rcv_data(40);
|
||||
str = {size[0],size[1],size[2],size[3]};
|
||||
send_data(str);
|
||||
rcv_data(4);
|
||||
send_data(mem);
|
||||
rcv_data(57);
|
||||
|
||||
endtask
|
||||
|
||||
task finish_programming();
|
||||
send_data({8'd0, 8'd0, 8'd0, 8'd0});
|
||||
endtask
|
||||
|
||||
task dummy_programming();
|
||||
byte str [4] = {8'd0, 8'd0, 8'd0, 8'd0};
|
||||
rcv_data(40);
|
||||
send_data(str);
|
||||
rcv_data(4);
|
||||
rcv_data(57);
|
||||
send_data(str);
|
||||
endtask
|
||||
|
||||
endmodule
|
95
Labs/16. Coremark/tb_timer.sv
Normal file
95
Labs/16. Coremark/tb_timer.sv
Normal file
@@ -0,0 +1,95 @@
|
||||
module tb_timer();
|
||||
|
||||
logic clk_i;
|
||||
logic rst_i;
|
||||
logic req_i;
|
||||
logic write_enable_i;
|
||||
logic [31:0] addr_i;
|
||||
logic [31:0] write_data_i;
|
||||
logic [31:0] read_data_o;
|
||||
logic ready_o;
|
||||
logic interrupt_request_o;
|
||||
|
||||
localparam SYS_CNT_ADDR = 32'h0000_0000;
|
||||
localparam DELAY_ADDR = 32'h0000_0004;
|
||||
localparam MODE_ADDR = 32'h0000_0008;
|
||||
localparam REP_CNT_ADDR = 32'h0000_000C;
|
||||
localparam RST_ADDR = 32'h0000_0024;
|
||||
|
||||
localparam OFF = 32'd0;
|
||||
localparam NTIMES = 32'd1;
|
||||
localparam FOREVER = 32'd2;
|
||||
|
||||
always #50ns clk_i = !clk_i;
|
||||
|
||||
timer_sb_ctrl DUT(.*);
|
||||
|
||||
initial begin
|
||||
clk_i = 0;
|
||||
rst_i = 0;
|
||||
req_i = 0;
|
||||
write_enable_i = 0;
|
||||
addr_i = 0;
|
||||
write_data_i = 0;
|
||||
@(posedge clk_i);
|
||||
rst_i = 1;
|
||||
repeat(5) @(posedge clk_i);
|
||||
rst_i = 0;
|
||||
|
||||
test_ntimes(0, 0);
|
||||
test_ntimes(0, 1);
|
||||
test_ntimes(1, 0);
|
||||
test_ntimes(1, 1);
|
||||
test_ntimes(10, 1);
|
||||
test_ntimes(10, 10);
|
||||
test_forever(10);
|
||||
write_req(MODE_ADDR, OFF);
|
||||
test_ntimes(10, 10);
|
||||
test_forever(10);
|
||||
test_ntimes(10, 10);
|
||||
$finish();
|
||||
end
|
||||
|
||||
one_cycle_irq: assert property (
|
||||
@(posedge clk_i) disable iff ( rst_i || (DUT.delay==32'd1))
|
||||
(interrupt_request_o |=> !interrupt_request_o)
|
||||
);
|
||||
|
||||
task test_ntimes(input logic [31:0] delay, ntimes);
|
||||
write_req(DELAY_ADDR, delay);
|
||||
write_req(REP_CNT_ADDR, ntimes);
|
||||
write_req(MODE_ADDR, NTIMES);
|
||||
repeat(ntimes) begin
|
||||
repeat(delay)@(posedge clk_i);
|
||||
if(!interrupt_request_o & delay) begin
|
||||
$error("test_ntimes: delay = %d, ntimes = %d", delay, ntimes);
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
task test_forever(input logic [31:0] delay);
|
||||
write_req(DELAY_ADDR, delay);
|
||||
write_req(MODE_ADDR, FOREVER);
|
||||
repeat(1000) begin
|
||||
repeat(delay) @(posedge clk_i);
|
||||
if(!interrupt_request_o) begin
|
||||
$error("test_forever");
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
task write_req(input [31:0] addr, data);
|
||||
@(posedge clk_i);
|
||||
addr_i <= addr;
|
||||
write_data_i <= data;
|
||||
write_enable_i <= 1'b1;
|
||||
req_i <= 1'b1;
|
||||
@(posedge clk_i);
|
||||
while(!ready_o) begin
|
||||
@(posedge clk_i);
|
||||
end
|
||||
req_i <= 1'b0;
|
||||
write_enable_i <= 1'b0;
|
||||
endtask
|
||||
|
||||
endmodule
|
Reference in New Issue
Block a user