Как я писал в прошлом посте, название моего проекта на 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 и развитии сообщества, я предлагаю вам принять участие в воркшопах.
Наш порт 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 для просмотра сгенерированных временных диаграмм тестов.
Я также добавил сессию с простыми командами, нужными для сборки тулчейна 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 <stdio.h>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.
Автор: Владимир