[Перевод] Генерация и тестирование ядра RISC-V

Как я писал в прошлом посте, название моего проекта на GSoC-2016 — «порт RISC-V на Parallella», и первая вещь, которую я должен был сделать, это познакомиться с экосистемой RISC-V. Один из лучших способов это сделать, — посмотреть видео с презентации первого воркшопа RISC-V на Youtube. Для того, чтобы понять самые важные концепции, я рекомендую следующие презентации:

  • Введение (Krste Asanović) видео слайды
  • Тулчейн RISC-V (Andrew Waterman) видео слайды
  • SoC-генератор RISC-V «Rocket Chip» в Chisel (Yunsup Lee) видео слайды
  • Структура программного стека RISC-V (Sagar Karandikar) видео слайды
  • Отладка на RISC-V (Albert Ou) видео слайды
  • Портирование нового кода на RISC-V с OpenEmbedded (Martin Maas) видео слайды
  • Окружение тестирования RISC-V (Stephen Twigg) видео слайды


Ещё одна ссылка, если вы интересуетесь Chisel, языком, основанным на Scala, который используется для описания текущей аппаратной реализации ядра RISC-V (ядро Rocket имеет in-order конвейер, BOOM — out-of-order), и любых будущих реализаций.

Краткое руководство по Chisel (Jonathan Bachrach) видео слайды

Работа с Rocket Chip, добавление расширений, инфраструктура ASIC и FPGA (Colin Schmidt) видео слайды

Если вы глубоко заинтересованы в RISC-V и развитии сообщества, я предлагаю вам принять участие в воркшопах.

5j1juczpvfrzr4y4roqj4m6zhsu.png

Наш порт RISC-V на Parallella будет использовать совершенно стандартное ядро, сгенерированное с помощью Rocket Chip из исходников на Chisel (в статье даётся очень хорошее введение). Конечным результатом будет Verilog RTL (в нечитаемом виде, только исходники на Chisel читаемы), который мы можем включить в наш проект FPGA для Parallella, который доступен в репозитории Parallella OH (Open Hardware). Однако я не буду вдаваться в подробности сейчас, всё это будет рассмотрено в следующих постах.

Для того, чтобы компилировать ПО для этого ядра, вы можете использовать тулчейн RISC-V GNU, который можно взять здесь. Есть два варианта компиляции: либо с библиотекой Newlib C, которая используется для программ, работающих на прокси-ядре (proxy kernel, pk) либо компилируете с библиотекой GNU C library (glibc), для компиляции Linux и программ под Linux. Я скомпилировал обе версии, (ядро Linux, релиз 4.1), используя прилагаемые инструкции, и всё заработало, как ожидалось, при симуляции в Spike, референсном симуляторе RISC-V ISA.

Главный репозиторий Rocket Chip связан путём использования субмодулей Git со всеми репозиториями, содержащими исходники (главным образом, Chisel), которые нужны для правильной генерации с использованием своих файлов-обёрток, расположенных в src/main/scala/. Каждый субмодуль связан с определённым комитом, заведомо рабочим, в результате сорка получается стабильной. Лично я не встретил никаких ошибок при генерации дефолтного ядра RV64G (64 бита, [I]nteger, [M]ultiply / Division, [A]tomic, [F] Single Floating, [D]ouble Floating), сокращение IMAFD сокращается до G (General), содержащего L1-кэши инструкций и данных, TLB и FPU с интерфейсами MemIO и HostIO для связи с внешним миром.

В типичной FPGA-системе (как показано на диаграмме из презентации Y.Lee по Rocket Chip), эти два интерфейса нужны для доступа к основной памяти и для загрузки ядра RISC-V (бутстрап). Последний используется для управления CSR (control-status registers) и поддержки системных вызовов (для операций ввода-вывода) из целевой машины (на которой запущено прокси-ядро или ядро Linux) в хост-машину (т.е. в двухядерный процессор ARM в Zynq FPGA), на которой запущен инстанс riscv-fesvr (front end server) для диспетчеризации системных вызовов в операционную систему (т.е. ARM Linux).

Для конфигурирования ядра вы можете также изменить значения внутри файла Config.scala. Этот файл содержит набор конфигураций для различных применений (модель C++, FPGA обычного размера, маленькая FPGA, VLSI, и т.д.). Всё, что делает скрипт, принимающий переменную CONFIG=XXX (т.е. DefaultFPGAConfig), это устанавливает требуемую конфигурацию, используемую для сборки симулятора C++ либо Verilog RTL для FPGA или VLSI. Это очень полезная вещь, если вы хотите экспериментировать с разными доступными опциями, быстро генерировать и тестировать их в программном либо аппаратном виде.

Хорошей идеей является скомпилировать тулчейн (с Newlib лучше всего), прежде чем генерировать любые ядра, так как вам нужно компилировать и запускать тесты для верификации вашего ядра с помощью симулятора C++ или Synopsys VCS. К сожалению, я смог использовать только первый, но не последний. Вы не можете использовать любой симулятор Verilog (например, Verilator, Icarus Verilog, Modelsim, QuestSim), так как тестбенч использует функции DirectC (связывающие тестбенчи Verilog и произвольный код C/C++). К счастью, симулятор С++ обеспечивает вывод VCD и я смог использовать GTKWave для просмотра сгенерированных временных диаграмм тестов.

sixdq7bmjtwepsfgp54pb5bztz8.png

Я также добавил сессию с простыми командами, нужными для сборки тулчейна RISC-V и генерации ядра RV64G для FPGA. Эти команды будут добавлены в финальный скрипт компиляции, который я разрабатываю для полной автоматизации всего необходимого для того, чтобы получить рабочее ядро RISC-V внутри чипа Zynq на Parallella.

# Скачиваем и инициализируем репозиторий Rocket Chip
git clone https://github.com/ucb-bar/rocket-chip.git
cd rocket-chip
git submodule update --init
export TOP=$(pwd)

# Устанавливаем необходимые пакеты (Ubuntu)
sudo apt-get install autoconf automake autotools-dev curl \
libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex \
texinfo gperf libtool patchutils bc

# Компилируем тулчейн RISC-V (используется Newlibдля работы без ОС)
export RISCV=$TOP/riscv-tools
# Добавляем всё, что будет скомпилировано, в пути)
export PATH=$PATH:$RISCV/bin
cd $RISCV
git submodule update --init --recursive
# Редактируем файл build.common
# JOBS = Количество ядер / потоков, которые поддерживает ваша машина
./build.sh

# Тестируем тулчейн RISC-V, скомпилировав простую программу
# и запускаем её на Spike (симулятор ISA)
# с использованием имеющегося прокси-ядра (pk)
cd $TOP
echo -e '#include \n int main(void) \
{ printf("Hello world!\\n"); return 0; }' > hello.c
riscv64-unknown-elf-gcc -o hello hello.c
spike pk hello

# Опционально: компилируем эмулятор Rocket Chip
# Заменить -j8 на -jN (количество ядер/потоков на вашей машине)
cd $TOP/emulator
make
# Запускаем тесты (внимание: это может занять некоторое время)
# make -j8 run-asm-tests
# make -j8 run-bmark-tests
# лучше просто запустить конкретный тест:
make output/rv64ui-p-add.out

# Опционально: компилируем эмулятор Rocket Chip с выводом VCD
# Меняем -j8 на -jN 
cd $TOP/emulator
# Если у вас нет утилиты vcd2vpd, открываем src/main/scala/Testing.scala
# файл и меняем все .vpd на .vcd (6 раз)
make debug
# Запускаем тесты (внимание: это может занять время и место на диске)
# make -j8 run-asm-tests-debug
# make -j8 run-bmark-tests-debug
# запускаем определённые тесты и тогда нам не нужен приведённый выше 
# хак при отсутствии утилиты vcd2vpd:
make output/rv64ui-p-add.vcd

# Генерируем Rocket Chip Verilog RTL для FPGAs (в fsim/generated-src/)
# Дефолтная конфигурация "DefaultFPGAConfig", но вы не можете её изменить
# например, на "SmallFPGAConfig" если дефолтная реализация с FPU
# не влезает в ваше устройство. Используем make CONFIG=ConfigName verilog для установки 
# желаемой конфигурации из числа доступных (src/main/scala/Configs.scala)
# Финальный вывод Verilog RTL называется Top.ConfigName.v
cd $TOP/fsim
make verilog


В следующем посте мы рассмотрим, как получить IP-блок из сгенерированного ядра RV64G для того, чтобы его можно было легко подключать к проектам, для тестирования на Zedboard или для использования в большом проекте на Parallella.

© Habrahabr.ru