E6 Простая среда выполнения
Вы уже реализовали NPC miniRV на RTL, но он может выполнять лишь очень простые программы. Чтобы выполнять более сложные программы, нам нужно компилировать программы, способные работать на minirv. Для этого мы предоставляем проект под названием abstract-machine, или сокращённо AM, для сборки программ. Этот проект предоставляет среду выполнения bare-metal, обеспечивающую необходимую поддержку для запуска программ непосредственно на процессоре, то есть в среде bare-metal (без ОС).
Познакомьтесь с AM
Сначала попробуем классическую игру. Перед этим вам нужно установить python:
apt install python python-is-python3
Запустите игру для NES
Прочитайте начало раздела Before embarking on an enjoyable PA journey -> What is NEMU? в PA1, следуйте инструкциям хендаута, чтобы попробовать запустить игру для NES, и выполните проверки графики, ввода с клавиатуры и звука.
Возможно, вам интересно, как на самом деле выполняется игра для NES. На самом деле игра — это тоже последовательность инструкций, но её инструкции принадлежат системе команд под названием 6502. Эта система команд появилась в 1980-х годах и сегодня используется редко; соответствующие процессоры тоже уже не так распространены. Но только что вы запустили игру не на настоящем процессоре 6502. Тогда как именно она выполнялась?
Только что произошло следующее: сначала была запущена программа-эмулятор системы команд 6502 под названием fceux. Эта программа ведёт себя весьма особым образом: с помощью поведения программного обеспечения она может симулировать процесс выполнения инструкций 6502, тем самым симулируя процесс выполнения всей игры! Поэтому по сути принцип работы fceux такой же, как у sEMU и minirvEMU, которые вы разрабатывали ранее!
На самом деле AM спроектирован весьма хитроумно: передавая разные параметры ARCH, мы можем компилировать одну и ту же программу под разные платформы. Например, ARCH=native означает компиляцию программы для локального запуска на Linux. Мы также можем скомпилировать fceux под minirv и таким образом запустить игру для NES на спроектированном вами NPC miniRV!
Однако в проекте AM среда выполнения, связанная с minirv, пока не завершена. Давайте сначала доработаем её. Пока вам нужно лишь следовать приведённым ниже действиям для компиляции программ; вникать в детали устройства AM пока не требуется. Мы подробнее расскажем об AM на этапе D.
Запустите первую скомпилированную программу
Сначала мы скомпилируем и запустим простую программу на C. Эта простая программа на C находится в am-kernels/tests/cpu-tests/tests/dummy.c. Прочитав её код, вы обнаружите, что она ничего не делает и сразу же возвращает управление.
Чтобы скомпилировать эту программу dummy, вам сначала нужно установить инструментарий кросс-компиляции:
apt install g++-riscv64-linux-gnu
Затем попробуйте скомпилировать её в каталоге am-kernels/tests/cpu-tests/:
make ARCH=riscv32-nemu ALL=dummy
Если возникнет ошибка компиляции, вам нужно обратиться к разделу RTFM -> Run the first C program -> Fix riscv32 compilation errors в PA2, попробовать исправить соответствующие проблемы, а затем снова ввести указанную выше команду make для компиляции. Если компиляция прошла успешно, с помощью команды ls build/ вы увидите следующие три файла:
dummy-riscv32-nemu.bin
dummy-riscv32-nemu.elf
dummy-riscv32-nemu.txt
Описанные выше операции нужны только для того, чтобы убедиться, что цепочка инструментов RISC-V успешно работает. Теперь скомпилируем программу dummy под minirv. Сначала очистим только что созданные результаты сборки:
make clean
Затем введите следующую команду:
make ARCH=minirv-npc ALL=dummy
Если компиляция прошла успешно, с помощью команды ls build/ вы увидите следующие три файла:
dummy-minirv-npc.bin
dummy-minirv-npc.elf
dummy-minirv-npc.txt
Среди них:
dummy-minirv-npc.bin— двоичное представление программы.dummy-minirv-npc.txt— результат дизассемблирования программы, по которому можно увидеть представление инструкций в виде ассемблера. Мы только что упоминали, что программаdummyпо сути является пустой функцией. Но если прочитать результат дизассемблирования, вы обнаружите, что скомпилированная программа также содержит множество других инструкций. На самом деле эти инструкции — часть среды выполнения, используемая для поддержки выполнения программы.dummy-minirv-npc.elf— ELF-форма программы. Пока можете не обращать на неё внимания.
Прежде чем запускать программу на NPC, мы обычно вручную вписывали последовательность инструкций программы в массив памяти NPC в качестве инициализации. По мере того как скомпилированные программы становятся всё сложнее, ручная инициализация массива памяти будет становиться всё менее эффективной. Чтобы повысить эффективность загрузки программ в среду симуляции, мы можем позволить среде симуляции считывать двоичный файл программы в массив памяти NPC через файл.
В отличие от программ, которые вы ранее запускали на NPC, среда выполнения minirv-npc в AM предполагает, что программы начинаются с адреса 0x80000000. Хотя это и не обязательное требование, процессоры RISC-V обычно резервируют адресное пространство ниже 0x80000000 для устройств. Мы продолжим обсуждать устройства позже. Короче говоря, чтобы запустить программу dummy на NPC miniRV, вам также нужно:
- Изменить значение сброса PC у NPC на
0x80000000. - Изменить
pmem_read()иpmem_write()так, чтобы они обращались к массиву памяти по смещению между параметромaddrи0x80000000.
Запустите программу dummy на NPC miniRV
Измените NPC и среду симуляции согласно указанным выше требованиям, позвольте среде симуляции загрузить программу dummy в память, а затем позвольте NPC начать выполнять программу dummy. Мы рекомендуем изменить массив памяти в среде симуляции на 128 МБ. Этого размера достаточно, чтобы вместить различные тестовые программы в будущем.
Если ваша реализация верна, программа dummy войдёт в бесконечный цикл рядом с функцией halt().
Постройте процесс пакетного запуска программ
Скоро вам понадобится запускать множество программ, чтобы тестировать NPC более тщательно. Если каждый раз вручную указывать программу для запуска и вручную оценивать, верен ли результат, процесс будет очень неэффективным. Поэтому вам нужно построить процесс, который поддерживает автоматический пакетный запуск программ, чтобы вы могли эффективно запускать различные программы.
Ранее мы условились, что программа завершается после выполнения инструкции ebreak. Однако программы, скомпилированные в AM, не содержат инструкцию ebreak, а вручную вставлять инструкцию ebreak в каждую программу в среде симуляции неэффективно. На самом деле мы можем вставить эту инструкцию ebreak в среду выполнения minirv. После этого все программы, скомпилированные под minirv через AM, будут автоматически содержать инструкцию ebreak.
Но функция halt() в AM — это код на C. Чтобы вставить в неё нужную нам инструкцию, мы можем использовать встроенную ассемблерную вставку:
--- abstract-machine/am/src/riscv/npc/trm.c
+++ abstract-machine/am/src/riscv/npc/trm.c
@@ -17,3 +17,4 @@
void halt(int code) {
+ asm volatile("ebreak");
while (1);
}
Скомпилируйте программу, содержащую инструкцию ebreak
После изменения кода перекомпилируйте и запустите программу dummy. Если ваша реализация верна, программа dummy завершится автоматически, а не войдёт в бесконечный цикл.
Далее рассмотрим, как автоматически определять, корректно ли выполнилась программа. Для этого мы определим следующее соглашение: перед завершением программа сначала записывает в регистр a0 целое число, представляющее статус завершения, а затем выполняет инструкцию ebreak. Если это число равно 0, значит программа завершилась корректно; если оно не равно 0, значит в программе произошла ошибка.
Чтобы реализовать это соглашение, с одной стороны, нам нужно изменить последовательность инструкций при завершении программы так, чтобы к моменту выполнения инструкции ebreak регистр a0 уже содержал статус завершения программы. Для этого достаточно немного изменить приведённую выше встроенную ассемблерную инструкцию: asm volatile("mv a0, %0; ebreak" : :"r"(code));. После этого изменения перед выполнением инструкции ebreak встроенная ассемблерная вставка сначала перемещает значение переменной code, представляющей статус завершения программы, в регистр a0.
С другой стороны, после того как NPC выполнит ebreak, среда симуляции может проверить значение регистра a0, чтобы понять, успешно ли завершилась программа. Например, если a0 равен 0, выводится HIT GOOD TRAP, означающее, что программа завершилась успешно; если a0 не равен 0, выводится HIT BAD TRAP, означающее, что в программе произошла ошибка.
Заставьте среду симуляции выводить информацию о завершении программы
Измените встроенную ассемблерную инструкцию и среду симуляции согласно указанным выше требованиям.
После изменения кода перекомпилируйте и запустите программу dummy. Вы увидите, что среда симуляции выводит HIT GOOD TRAP. Попробуйте скомпилировать и запустить программу wrong в том же каталоге — вы увидите, что среда симуляции выводит HIT BAD TRAP.
Чтобы запустить программу wrong, вам может понадобиться вручную изменить путь к файлу программы, указанный в среде симуляции. Если вам нужно часто переключаться между программами, вручную менять путь к файлу очень неэффективно. По этой причине мы можем передавать путь к файлу программы в качестве аргумента командной строки при запуске симуляции. С этого момента, указывая разные пути к файлам через аргументы командной строки, мы сможем запускать разные программы во время симуляции.
Кроме того, мы можем связать команду запуска симуляции с Makefile, чтобы выполнение такой команды, как make run, автоматически запускало симуляцию. В частности, вам нужно реализовать правило run в abstract-machine/scripts/platform/npc.mk, чтобы после ввода make ARCH=minirv-npc ALL=dummy run программа dummy автоматически компилировалась и запускалась на NPC. Кроме того, достаточно изменить ALL=dummy на ALL=wrong, чтобы скомпилировать и запустить программу wrong. Далее вы будете многократно запускать различные программы, чтобы проверять, остаётся ли ваш NPC корректным после добавления новых функций. Эта возможность пакетного запуска программ может значительно повысить эффективность тестирования.
Заставьте правило run автоматически запускать симуляцию
Добавьте соответствующий код согласно указанным выше требованиям, чтобы Makefile мог запускать симуляцию через правило run.
Подсказка: в Makefile вы можете использовать $(IMAGE).bin для обращения к пути созданного двоичного файла.
Однако при запуске программы wrong командой make ARCH=native ALL=wrong run результат показывает FAIL; но при запуске программы wrong командой make ARCH=minirv-npc ALL=wrong run результат, наоборот, показывает PASS. Согласно упомянутому выше соглашению о статусе завершения программы, программа wrong всегда должна завершаться со статусом ошибки, поэтому PASS после запуска на minirv-npc — не то, что ожидается.
На самом деле это происходит потому, что когда среда симуляции обнаруживает, что программа выполняет инструкцию ebreak при a0, не равном 0, она не возвращает Makefile, запустившему симуляцию, информацию о том, что «программа выполнилась некорректно». В результате Makefile считает результат симуляции верным и поэтому показывает PASS. Чтобы исправить эту проблему, нам нужно сделать так, чтобы среда симуляции возвращала из функции main() разные значения в зависимости от результата выполнения программы: возвращать 0, когда программа выполнилась успешно, и ненулевое значение, когда программа выполнилась некорректно.
Дайте Makefile понять, верен ли результат симуляции
Измените код среды симуляции так, чтобы он возвращал разные значения в зависимости от результата выполнения программы. После изменения снова запустите make ARCH=minirv-npc ALL=wrong run. Вы должны увидеть, что результат показывает FAIL.
Запустите больше программ
После построения процесса пакетного запуска мы можем удобно использовать его для запуска большего числа программ и тестирования вашего NPC.
Сначала — riscv-tests:
cd ysyx-workbench
bash init.sh riscv-tests
cd riscv-tests
make ARCH=minirv-npc run TEST_ISA=i # run all RV32I tests
make ARCH=minirv-npc run ALL=addi # run only the addi test
Хотя приведённые выше тесты связаны с RV32I, а тестируемые цели включают множество инструкций, не входящих в minirv, процесс сборки AM гарантирует, что итоговые скомпилированные двоичные файлы содержат только инструкции minirv. Поэтому riscv-tests также можно использовать для текущего NPC miniRV.
Запустите riscv-tests
Запустите riscv-tests, чтобы проверить корректность вашего NPC.
По сравнению с программами, которые вы запускали раньше, эти тестовые программы больше по масштабу. Когда тест не проходит, вы обнаружите, что отладка становится сложнее. Чтобы справиться с этой проблемой, мы можем добавить в среду симуляции упомянутый ранее механизм DiffTest.
Добавьте механизм DiffTest
Добавьте механизм DiffTest и сравнивайте NPC с minirvEMU, чтобы это помогало вам быстро находить неверную инструкцию. Когда DiffTest обнаружит, что результат выполнения инструкции отличается, вам нужно заставить среду симуляции вернуть ненулевое значение, чтобы Makefile мог перехватить ошибку, о которой сообщил DiffTest.
После реализации попробуйте внести в NPC несколько ошибок и понаблюдайте, сообщают ли DiffTest и Makefile об ошибках, как ожидается.
Далее — cpu-tests. Однако некоторые тесты в нём зависят от реализации функций библиотеки klib. Пока мы не требуем от вас реализовывать эти библиотечные функции. Мы подготовили предварительно скомпилированную библиотеку klib. После скачивания и распаковки поместите klib-minirv-npc.a в каталог abstract-machine/klib/build/. Если этого каталога не существует, вам нужно сначала создать его вручную; если файл уже существует, его нужно перезаписать.
Наконец, вам также нужно обновить время его изменения, чтобы make не счёл его устаревшим и не перезаписал:
cd abstract-machine/klib/build/
touch klib-minirv-npc.a
После подготовки klib вы можете скомпилировать и запустить cpu-tests:
cd am-kernels/tests/cpu-tests
make ARCH=minirv-npc run # run all tests
make ARCH=minirv-npc run ALL=fib # run only the fib test
Запустите cpu-tests
Запустите cpu-tests, чтобы проверить корректность вашего NPC.
Добавьте поддержку UART для вывода символов
На этапе F вы узнали, что пользователи взаимодействуют с процессором через устройства ввода и вывода. На этапе F sISA предоставляет для ввода-вывода отдельную инструкцию io. В RISC-V же ввод и вывод выполняются через «ввод-вывод, отображённый на память» (Memory-mapped I/O). А именно: инструкции load и store в RISC-V могут обращаться как к памяти, так и к внешним устройствам. То, к чему именно происходит обращение, определяется диапазоном адресов. minirv также использует этот подход.
Например, предположим, что адрес 0x10000000 соответствует выходному регистру данных последовательного порта. Тогда следующая последовательность инструкций может вывести символ A через последовательный порт:
lui t0, 0x10000 # t0 = 0x10000000
addi t1, zero, 65 # 65 is the ASCII code of 'A'
sb t1, 0(t0)
Как видите, приведённая выше последовательность инструкций по сути ничем не отличается от «записи символа A в память». В коде на C мы можем реализовать похожую функциональность с помощью указателя:
void putch(char c) {
volatile char *p = (volatile char *)0x10000000ul;
*p = c;
}
Здесь volatile — это ключевое слово в C. Оно указывает компилятору не оптимизировать обращения через соответствующий указатель. Оптимизация обращений к внешним устройствам обычно приводит к неожиданным проблемам. Подробнее о volatile мы поговорим на более поздних этапах. Пока вам достаточно понимать, что при обращении к внешним устройствам в коде на C нужно добавлять ключевое слово volatile.
Чтобы позволить программам использовать последовательный порт для вывода, нам также нужно добавить последовательный порт в NPC. Однако пока нам нужно лишь добавить простую поведенческую модель последовательного порта в среду симуляции:
extern "C" void pmem_write(int waddr, int wdata, char wmask) {
if (waddr == 0x10000000) { // write to UART
fputc(wdata & 0xff, stderr); // defined in stdio.h
return;
}
// write to the memory array
}
Эта поведенческая модель проста. Во время операции записи в память, если целевой адрес равен 0x10000000, она выводит символ, представленный младшими 8 битами wdata, через fputc(), тем самым имитируя поведение вывода UART. Эта простая поведенческая модель помогает понять суть ввода-вывода, отображённого на память. Мы представим настоящий контроллер последовательного порта при подключении к SoC.
Добавьте поведенческую модель UART
Реализуйте функцию putch() в abstract-machine/am/src/riscv/npc/trm.c, затем добавьте поведенческую модель UART в среду симуляции, а затем запустите программу hello:
cd am-kernels/kernels/hello
make ARCH=minirv-npc run
Вы можете сначала запустить её на native, чтобы увидеть ожидаемый вывод.
Для DiffTest вы можете воспользоваться подходом, описанным выше: определите в minirvEMU инструкции store, которые пишут в последовательный порт, и игнорируйте их.
С UART мы можем запускать некоторые программы, которым нужно выводить символы.
Запустите бенчмарк
Попробуйте запустить бенчмарки из каталога am-kernels/benchmarks/. Вы можете сначала запустить их на native, чтобы увидеть ожидаемый вывод. Поскольку minirv не очень быстрый, мы рекомендуем сократить время симуляции следующим образом:
- Для
dhrystoneиcoremarkвы можете изменить код и уменьшить число итераций до2. - Для
microbenchукажите вход небольшого масштаба черезmainargs=test.
make ARCH=minirv-npc run mainargs=test
Поскольку мы пока не можем измерять время в NPC, можете пока не обращать внимания на время или счёт, которые выводят программы.
Мы можем даже запустить LLaMa на NPC! Мы перенесли LLaMa в другой набор бенчмарков. Сначала вам нужно получить соответствующий код:
cd ysyx-workbench
bash init.sh archbench
Затем вы можете сначала запустить её на native:
cd archbench/bench/202.llama2
make ARCH=native run mainargs=test
archbench предоставляет четыре масштаба входных данных, от малого к большому: test, train, ref и huge. Для программы LLaMa масштаб test использует очень маленький файл модели с очень низкой точностью, поэтому ей сложно выдать логически связное предложение — она выводит лишь одно слово, Once. Вы можете попробовать другие масштабы входных данных: чем больше масштаб входных данных, тем больше файл модели, используемый LLaMa, и тем более логичным будет сгенерированное предложение. Но и время выполнения будет больше. Поскольку симуляция процессора на несколько порядков медленнее выполнения на реальном железе, запуска масштабов test и train на NPC обычно достаточно. Масштаб test в основном используется для проверки функциональной корректности, а масштаб train — в основном для некоторой оценки производительности. Мы продолжим обсуждать оценку производительности на этапе B.
Запустите LLaMa
Попробуйте запустить LLaMa на NPC и проверьте, верен ли результат. Если у вас хватит терпения, можете попробовать масштаб train, выполнение которого занимает около 5 минут.
Запустите бенчмарк (2)
Запустите остальные программы в archbench и проверьте, верны ли результаты. archbench предоставляет скрипт для параллельного выполнения. Сначала вам нужно установить несколько инструментов:
apt install time parallel python3-numpy
Затем запустите скрипт:
cd archbench/scripts
bash run.sh ARCH=minirv-npc mainargs=test
При использовании настоящего UART программе нужно проверять, готов ли передатчик, прежде чем отправлять символ. Если символ отправлен до того, как передатчик готов, символ может быть потерян. Готовность UART проверяется чтением регистра статуса UART. Аналогично мы можем добавить соответствующую функциональность в поведенческую модель UART:
extern "C" int pmem_read(int raddr) {
if (raddr == 0x10000004) { // read UART status
return (rand() & 0x7) == 0 ? 1 : 0; // ready probability is 12.5%
}
// read the memory array
}
Поведенческая модель выше определяет, что статус UART можно прочитать по адресу 0x10000004. Поведение регистра статуса симулируется случайными числами: с вероятностью 12,5% читается 1, что означает готовность.
Добавьте регистр статуса для UART
Согласно приведённому выше описанию, добавьте регистр статуса в поведенческую модель UART. Затем измените функцию putch() так, чтобы она проверяла статус UART перед выводом символа. Если статус готов, выведите символ и вернитесь; если статус не готов, проверяйте снова, пока он не станет готовым.
В частности, для DiffTest вам также нужно определить инструкции load, используемые для запроса статуса UART, и скопировать прочитанное значение статуса в minirvEMU, чтобы NPC и minirvEMU читали одни и те же данные после выполнения одной и той же инструкции load.
Добавьте поддержку таймера для измерения времени
Аналогично UART, мы можем добавить в среду симуляции простую поведенческую модель таймера:
unsigned long long get_time() {
// return the time elapsed since simulation started, in microseconds
// can be implemented with gettimeofday()
return 0;
}
extern "C" int pmem_read(int raddr) {
if (raddr == 0x10000004) { /* ... */ } // read UART status
// read the low 32 bits of the timer
else if (raddr == 0x20000000) { return get_time() & 0xffffffff; }
// read the high 32 bits of the timer
else if (raddr == 0x20000004) { return get_time() >> 32; }
// read the memory array
}
Добавьте таймер
Согласно приведённому выше описанию, добавьте поведенческую модель таймера в среду симуляции. Затем реализуйте функцию __am_timer_uptime() в abstract-machine/am/src/riscv/npc/timer.c, возвращая число микросекунд через обращение к поведенческой модели таймера.
После реализации попробуйте запустить тест таймера:
cd am-kernels/tests/am-tests
make ARCH=minirv-npc run mainargs=t
Если ваша реализация верна, вы увидите, что программа выводит строку каждую секунду.
Для DiffTest вы можете обрабатывать инструкции load, обращающиеся к поведенческой модели таймера, аналогично регистру статуса UART.
Запустите бенчмарк (3)
Запустите предыдущие бенчмарки снова. Вы увидите, что программы выводят осмысленное время или счёт.
Запустите игру для NES
Теперь вы также можете попробовать запустить игру для NES на NPC! Однако, поскольку NPC пока не подключён к клавиатуре или дисплею, мы можем наблюдать изменения на экране только в символьном режиме. А именно, измените конфигурацию FCEUX следующим образом:
--- fceux-am/src/config.h
+++ fceux-am/src/config.h
@@ -1,5 +1,5 @@
#ifndef __CONFIG_H__
#define __CONFIG_H__
-#define HAS_GUI
+//#define HAS_GUI
#define SIZE_OPT
После изменения скомпилируйте и запустите её на NPC. Вы можете уменьшить размер шрифта в терминале для лучшего визуального эффекта.
На данный момент эффективность выполнения программ на NPC в среде симуляции невысока. Даже если подключить клавиатуру, впечатления от игры будут не очень хорошими. Поэтому пока мы рассматриваем игру для NES как функциональный тест для NPC. Мы улучшим впечатления от игры для NES на более поздних этапах.
Оценка производительности: начало работы
Оцените производительность NPC
Попробуйте запустить через скрипт программу масштаба train из archbench. Скрипт автоматически вычислит счёт каждого теста, а также их среднее арифметическое и среднее геометрическое. Среднее геометрическое обычно используется как итоговый результат оценки производительности.
В дальнейшем мы будем использовать масштаб train из archbench как тестовые программы по умолчанию для оценки производительности. Вы можете оформить результаты скрипта в виде таблицы, чтобы наблюдать количественное влияние изменений системы на результаты оценки производительности.
Можно ли использовать результат выполнения масштаба test как результат оценки производительности?
Обычно работа компьютера заключается в многократной обработке задач. В этом смысле производительность компьютера — это его производительность при многократной обработке этих задач при определённой нагрузке. Поэтому каждая программа, используемая для оценки производительности, должна обладать некоторой представительностью: она должна представлять задачи, которые компьютеру, возможно, придётся многократно обрабатывать в будущем. Оценка производительности по сути заключается в тестировании производительности целевой системы в этих представительных сценариях.
Однако масштаб test обычно предназначен для быстрой проверки функциональной корректности. Соответствующий масштаб входных данных обычно очень мал, число итераций циклов также невелико, а нагрузка слабая. Поэтому с точки зрения оценки производительности он не представителен.
Течение времени внутри NPC
Если задуматься как следует, вы обнаружите, что хотя программы теперь выводят счёт, этот счёт не является осмысленным ориентиром. Это потому, что мы ожидаем от бенчмарков оценки производительности NPC, а значит, для отсчёта времени должны использоваться тактовые импульсы, протекающие внутри NPC. Однако gettimeofday() возвращает время системы хоста, которое отражает время, прошедшее в реальном мире.
Чтобы исправить эту проблему, вы можете изменить реализацию описанной выше функции get_time(). А именно, вам нужно фиксировать, сколько тактов было просимулировано в среде симуляции, а затем переводить число тактов в микросекунды согласно частоте NPC. Однако частота NPC связана с временны́м поведением схемы, и её можно получить через оценку EDA-инструментами. Для простоты пока можете считать, что NPC работает на частоте 100 МГц.
Оцените производительность NPC (2)
Измените реализацию функции get_time(), а затем снова запустите archbench. Теперь счёт, который вы увидите, будет представлять производительность NPC при работе на частоте 100 МГц.
Оцените производительность NPC (3)
Предположите, что NPC работает на частоте 1000 МГц. Измените реализацию функции get_time(), а затем снова запустите archbench. Какой счёт вы ожидаете увидеть? Попробуйте сравнить свои ожидания с фактическим выводом, чтобы проверить, верна ли ваша идея.
Получите частоту NPC с помощью EDA-инструментов
Частота, использованная для NPC ранее, была лишь предположением. На практике мы можем использовать EDA-инструменты для оценки максимальной рабочей частоты NPC.
Для синтеза мы будем использовать набор инструментов ECC (ECOS Chip Compiler). Вам нужно получить следующие инструменты:
ECC v0.1.0-alpha.8. Перейдя по ссылке, найдите список
Assetsвнизу страницы и скачайтеecc-cli-linux-x86_64.tar.gz. После скачивания распакуйте его:mkdir ecc cd ecc mv path-to-ecc-cli-linux-x86_64.tar.gz . tar xvf ecc-cli-linux-x86_64.tar.gzICsprout55 PDK. Получить его можно следующими командами:
git clone -b unconfirmed git@github.com:openecos-projects/icsprout55-pdk cd icsprout55-pdk/patch bash patch.shOpen-Source CAD Tool Suite. Перейдя по ссылке, найдите список
Assetsвнизу страницы и скачайте подходящий пакет. После распаковки добавьтеpath-to-oss-cad-suite/binв переменную окруженияPATH.Этот набор содержит инструмент синтеза RTL с открытым исходным кодом Yosys, плагины SystemVerilog, используемые Yosys, и некоторые другие инструменты, которые понадобятся на более поздних этапах.
Сначала мы используем ECC, чтобы оценить предыдущий пример бегущих огней:
cd ecc
./ecc init light
Создайте файл ecc/light/rtl/light.v и скопируйте в него код бегущих огней из предыдущего примера. Затем отредактируйте ecc/light/ecc.toml и измените его следующим образом:
[design]
name = "light"
top = "light"
rtl = ["rtl/light.v"]
clock_port = "clk"
frequency_mhz = 100.0
[pdk]
name = "ics55"
root = "path-to-icsprout55-pdk"
[flow]
# preset: rtl2gds | rcx | harden | syn_sta
preset = "syn_sta"
run = "default"
[pdk.overrides]
libs = [
"path-to-icsprout55-pdk/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CH/liberty/ics55_LLSC_H7CH_typ_tt_1p2_25_nldm.lib",
"path-to-icsprout55-pdk/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CR/liberty/ics55_LLSC_H7CR_typ_tt_1p2_25_nldm.lib",
"path-to-icsprout55-pdk/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CL_typ_tt_1p2_25_nldm.lib",
]
lefs = [
"path-to-icsprout55-pdk/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CH/lef/ics55_LLSC_H7CH_ecos.lef",
"path-to-icsprout55-pdk/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CR/lef/ics55_LLSC_H7CR_ecos.lef",
"path-to-icsprout55-pdk/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/lef/ics55_LLSC_H7CL_ecos.lef",
]
dont_use = [
"*AO222*",
"*2BB2*",
"*AOI222*",
"*AOI33*",
"*OA222*",
"*OAI222*",
"*OAI33*",
"*NOR4*",
]
Изменения объясняются ниже:
Замените
path-to-icsprout55-pdkна абсолютный путь к ICsprout55 PDK.Задайте свойству
rootв разделе[pdk]путь к ICsprout55 PDK.Измените свойство
presetв разделе[flow]наsyn_sta, что означает выполнение только синтеза и анализа таймингов.В разделе
[pdk.overrides]:- Свойство
libsперечисляет используемые файлы.lib. Здесь выбран process cornertt. - Свойство
lefsперечисляет используемые файлы.lef. - Свойство
dont_useперечисляет отключённые стандартные ячейки. Эти ячейки отключены, потому что у них относительно большое число выводов, что создаёт дополнительную нагрузку на этап трассировки в бэкенд-потоке.
- Свойство
После завершения настройки вы можете использовать ECC для синтеза и оценки проекта бегущих огней:
cd ecc
./ecc run --project light
ECC вызовет Yosys для синтеза RTL-кода и отобразит результаты синтеза на ICsprout55 PDK.
После завершения синтеза вы можете просмотреть следующие файлы, чтобы получить результаты синтеза:
light/runs/default/Synthesis_yosys/report/post_synthesis/qor_summary.rpt— сводный отчёт. Содержит такую информацию, как частота и площадь. Площадь указана в столбцеCELLA, в единицах .light/runs/default/Synthesis_yosys/report/post_synthesis/power.rpt— отчёт по энергопотреблению.light/runs/default/Synthesis_yosys/log/Synthesis.log— лог синтеза.light/runs/default/Synthesis_yosys/output/light_Synthesis.v.gz— netlist, сгенерированный синтезом.light/runs/default/Synthesis_yosys/output/light_Synthesis_sim.v.gz— netlist, предназначенный для симуляции на уровне вентилей.
Скрипт синтеза, используемый потоком, находится по пути:
ecc/_internal/chipcompiler/tools/yosys/scripts/yosys_synthesis.tcl
Скрипт синтеза содержит несколько стратегий синтеза, разделённых на три оптимизационные цели: оптимизация задержки (DELAY), оптимизация площади (AREA) и сбалансированная оптимизация задержки и площади (BALANCE). Каждая оптимизационная цель содержит несколько конкретных стратегий. Конкретная стратегия выбирается в формате optimization_objective index. Например, поток синтеза по умолчанию использует стратегию BALANCE 3. Вы можете проверить, какая стратегия была использована, посмотрев информацию, выводимую в начале лога синтеза. Число доступных стратегий для каждой оптимизационной цели можно определить, просто просмотрев упомянутый выше скрипт синтеза. Вы также можете использовать переменную окружения YOSYS_SYNTH_STRATEGY, чтобы указать стратегию синтеза. Например:
YOSYS_SYNTH_STRATEGY="DELAY 0" ./ecc run --overwrite --project light
Опция --overwrite заставляет ECC перезаписывать существующие результаты синтеза. Подробнее о ECC см. соответствующую документацию на GitHub. Чтобы синтезировать NPC, вам нужно будет внести некоторые изменения в код.
Во-первых, поскольку концептуально память не является частью NPC, мы не хотим синтезировать память вместе с процессором. Поэтому, если сейчас у вас есть обращения к памяти через DPI-C внутри процессора, например в IFU и LSU, вам нужно вынести эти обращения за пределы процессора и вывести связанные с памятью сигналы на верхний уровень процессора.
Это позволяет использовать сам модуль процессора в качестве модуля верхнего уровня при синтезе.
Кроме того, ECC не может обрабатывать несинтезируемый код, включая объявления и вызовы функций DPI-C, $display() и другие системные задачи.
Такой несинтезируемый код можно обернуть в `ifndef SYNTHESIS и `endif. Скрипт синтеза Yosys автоматически определяет макрос SYNTHESIS при чтении исходного кода, из-за чего эти несинтезируемые участки пропускаются.
В частности, если вы используете Chisel, вы можете использовать конфигурационные переменные, чтобы управлять тем, генерируются ли модули BlackBox, связанные с DPI-C.
Оцените частоту синтеза NPC
После корректировки кода NPC синтезируйте NPC согласно описанному выше рабочему процессу ECC. Затем изучите отчёт синтеза, чтобы определить синтезированную частоту.
Если ваш проект NPC содержит несколько исходных файлов, вы можете изменить свойство rtl в разделе [design] файла ecc.toml на список файлов. Например:
rtl = ["rtl/filelist.f"]
Файл rtl/filelist.f может содержать:
IFU.v
IDU.v
EXU.v
LSU.v
WBU.v
Оцените производительность NPC (4)
На основе частоты, полученной от потока синтеза, измените реализацию функции get_time(), а затем снова запустите archbench. Теперь полученный вами счёт будет представлять производительность NPC на её максимальной рабочей частоте на уровне схемы.
Стоит отметить, что частота, полученная синтезом, не обязательно является итоговой рабочей частотой чипа, поскольку она не учитывает эффекты бэкенд-этапа физического проектирования. После завершения потока бэкенд-проектирования максимальная рабочая частота чипа, как правило, ещё снизится. Тем не менее для процесса итераций проектирования на фронтенде частота, полученная синтезом, всё же остаётся полезным ориентиром.
