Отказ от ответственности: donaldsebleung является модератором сообщества Codewars, но не официальным сотрудником. Любые мнения, выраженные в этой статье, принадлежат исключительно donaldsebleung и ни в коем случае не должны рассматриваться как официальная позиция Codewars.
Вы слышали? Сборка RISC-V теперь поддерживается на Codewars! Если вы еще не слышали о Codewars, это что-то вроде HackerRank или LeetCode, но с..:
- Надлежащая поддержка разработки на основе тестирования (TDD) с использованием фреймворков модульного тестирования производственного уровня, таких как JUnit для Java или Chai для Node.js.
- Разнообразный спектр задач по коду (Kata), охватывающий основы языка, структуры данных и алгоритмы (DSA), математику, расширенные возможности языка, доказательство теорем… в общем, все, что угодно.
- Столь же разнообразное сообщество
- Поддержка 50+ различных языков программирования на момент написания статьи.
Если вы разработчик, желающий улучшить свои навыки программирования с помощью практики, вам определенно стоит попробовать Codewars 😉
Покончив с этим, вы, возможно, зададитесь вопросом: «Как настроить среду разработки для решения RISC-V Kata локально?». Для большинства языков программирования это просто и понятно — достаточно установить соответствующие средства разработки на локальную машину, и все готово. Например, чтобы решить Node.js Kata локально, вы можете установить на свой компьютер:
- Node.js и
npm
. - Chai
- (Необязательно) VSCode или другую IDE на выбор, плюс специфические для Node.js плагины/расширения для улучшения опыта разработчика.
Затем скопируйте файл решения и модульные тесты из соответствующей Kata и сразу же приступайте к написанию кода.
Но с ассемблером RISC-V все не так просто. Программы на ассемблере тесно связаны с архитектурой процессора и операционной системой, на которую они нацелены, а в 2022 году, скорее всего, архитектура процессора компьютера (или смартфона), на котором вы читаете эту статью, не RISC-V — это, скорее всего, x86_64
/amd64
(для большинства потребительских ПК) или aarch64
(для смартфонов и Apple Silicon Macs). Итак, если это вообще возможно, как запускать программы на ассемблере RISC-V на локальном устройстве?
Оказывается, есть несколько возможных способов сделать это. Вы можете (без особого порядка предпочтений):
- Запустить контейнер, созданный для RISC-V, с эмуляцией пользовательского режима QEMU. Это самый простой способ, а также то, как это делает Codewars. В Windows и macOS это можно сделать с помощью Docker Desktop, а в Linux — с помощью Docker Engine / Podman после установки
qemu-user-static
на Ubuntu или эквивалентного пакета (пакетов) для других дистрибутивов. - Приобретите систему-на-чипе (SoC) RISC-V, например, плату SiFive HiFive Unmatched, загрузите на нее Linux, подключитесь к последовательной консоли, затем (соберите и) установите GCC и Cgreen на плату плюс (опционально) подходящий текстовый редактор, такой как
EmacsNanoVim для облегчения процесса разработки - Как нечто среднее между (1) и (2), сделайте то же самое, что и (2), но на полностью эмулированной плате
virt
с полной эмуляцией системы QEMU.
Как уже упоминалось, (1) — самый простой, а также в точности напоминает среду выполнения Codewars, но он уже хорошо задокументирован в https://github.com/codewars/riscv, поэтому мы не будем его здесь рассматривать. (2) — самый сложный и захватывающий способ, но он требует покупки отдельной платы, которую большинство решателей, вероятно, сочтут излишеством для решения Codewars Kata. Поэтому в этой статье мы рассмотрим способ (3), который почти такой же захватывающий, как и (2), но немного проще и не требует дополнительных вложений в оборудование.
Подготовка
Подходящая среда Linux. Если вы работаете на Windows или macOS, запустите полноценную виртуальную машину (ВМ) Linux с гипервизором, таким как VirtualBox или VMware, и следуйте остальным пунктам этой статьи в ВМ. На Windows также есть возможность использовать WSL2 (WSL1 не будет работать вообще, я уверен), но YMMV.
Эталонным дистрибутивом является Ubuntu 22.04. Если вы используете альтернативный дистрибутив Linux, могут потребоваться изменения в инструкциях, представленных в этой статье (или просто запустите Ubuntu VM в любом случае). Если вы хотите попробовать запустить QEMU непосредственно на Windows / macOS, вы сами справитесь 😉
В любом случае, вы должны быть знакомы с Linux и уметь выполнять простой поиск и устранение неисправностей. Если вы столкнулись с ошибкой типа gpg2: command not found
на полпути, мы полностью ожидаем, что вы догадаетесь sudo apt install gnupg2
вместо того, чтобы жаловаться 😉
Установка зависимостей, настройка платы virt
для загрузки Ubuntu для RISC-V
Основная статья: RISC-V — Ubuntu Wiki
Обновить метаданные репозитория:
$ sudo apt update
Установите необходимые зависимости:
$ sudo apt install qemu-system-misc opensbi u-boot-qemu qemu-utils
Теперь возьмите сжатый образ предустановленного сервера Ubuntu для SiFive HiFive Unmatched, который также подходит для нашей платы virt
:
$ wget https://cdimage.ubuntu.com/releases/22.04/release/ubuntu-22.04.1-preinstalled-server-riscv64+unmatched.img.xz
Распаковать:
$ unxz ubuntu-22.04.1-preinstalled-server-riscv64+unmatched.img.xz
Теперь мы можем загрузить нашу плату virt
:
$ qemu-system-riscv64
-machine virt
-nographic
-m 1024
-smp 4
-bios /usr/lib/riscv64-linux-gnu/opensbi/generic/fw_jump.elf
-kernel /usr/lib/u-boot/qemu-riscv64_smode/uboot.elf
-device virtio-net-device,netdev=eth0
-netdev user,id=eth0
-drive file=ubuntu-22.04.1-preinstalled-server-riscv64+unmatched.img,format=raw,if=virtio
Поскольку это полностью эмулированная плата RISC-V без аппаратного ускорения, нашему гостю Ubuntu RISC-V требуется некоторое время для загрузки и полной инициализации. Пока мы ждем, давайте рассмотрим некоторые опции в приведенной выше команде:
Как только наша плата полностью инициализируется, войдите в систему с именем пользователя ubuntu
и паролем ubuntu
. При первом входе вам будет предложено изменить пароль.
Теперь, когда мы вошли в систему, все дальнейшие команды в этой статье должны выполняться на нашей плате virt
, если не указано иное.
Установка GCC, сборка и установка Cgreen
Codewars использует GCC, предоставляемый дистрибутивом, для сборки решения RISC-V и компиляции тестов, и Cgreen 1.6.0 для модульного тестирования. Чтобы повторить эту установку, мы установим GCC с помощью apt
вместе с зависимостями сборки Cgreen и соберем Cgreen 1.6.0 из исходных текстов.
Обновите метаданные репозитория:
$ sudo apt update
Установите GCC и сборочные зависимости для Cgreen (g++
, make
, cmake
):
$ sudo apt install -y gcc g++ make cmake
Теперь давайте git clone
Cgreen 1.6.0:
$ git clone --recurse-submodules --branch 1.6.0 https://github.com/cgreen-devs/cgreen
Технически нам не нужен --recurse-submodules
для Cgreen, который рекурсивно извлекает зависимости из Git-репозиториев, но он необходим для других фреймворков для тестирования C, таких как Criterion, и включить эту опцию не помешает.
Введите cgreen/
:
$ pushd cgreen/
Соберите кодовую базу (это может занять некоторое время):
$ make
Очень важно, чтобы вы не пытались параллельно собирать, например, с помощью -j$(nproc)
. Я попробовал — вроде бы сборка завершается нормально, но после установки компиляция всего, что связано с юнит-тестами Cgreen, приводит к странным ошибкам компоновщика, которые исчезают после пересборки Cgreen с одним потоком и повторной установки.
Теперь установите:
$ sudo make install
И обновите динамические привязки компоновщика:
$ sudo ldconfig
Выходим из дерева исходников Cgreen:
$ popd
Наконец, быстрый тест, подтверждающий, что Cgreen 1.6.0 установлен правильно:
$ cat > main.c << EOF
#include <cgreen/cgreen.h>
int main(int argc, char **argv) {
return run_test_suite(create_test_suite(), create_text_reporter());
}
EOF
$ gcc main.c -lcgreen -o main
$ ./main
Ожидаемый результат:
Running "main" (0 tests)...
Completed "main": No assertions.
Add (вдохновленный Multiply): пример настройки Kata
Установив GCC и Cgreen, мы можем начать решать RISC-V Kata локально. Чтобы не испортить существующие ката, мы создадим гипотетическое ката под названием «Add», в котором нужно исправить синтаксическую ошибку, чтобы функция add(a, b)
заработала. Функция add()
складывает два числа вместе и возвращает результат.
Установка Codewars (приблизительно) состоит из следующих файлов:
RISC-V на Codewars не поддерживает предварительно загруженный раздел на момент написания статьи (2022-08-13). Существует также Codewars reporter для печати формата вывода Codewars в модульных тестах вместо удобного для человека текстового вывода, но это не имеет значения для локальной разработки нашего решения.
Команда(ы), используемая(ые) для компиляции и выполнения кода, выглядит примерно так:
$ gcc solution.s solution_tests.c tests.c -lcgreen -o tests
$ ./tests
С помощью tests.c
:
#include <cgreen/cgreen.h>
TestSuite *solution_tests();
int main(int argc, char **argv) {
return run_test_suite(solution_tests(), create_text_reporter());
}
Давайте начнем со сломанного решения. Можете ли вы заметить ошибку? Сохраните это как solution.s
:
.globl add
add:
addw a0, a1
ret
Вот несколько тестов в solution_tests.c
:
#include <cgreen/cgreen.h>
int add(int, int);
Describe(Add);
BeforeEach(Add) {}
AfterEach(Add) {}
Ensure(Add, should_work_for_fixed_tests) {
assert_that(add(3, 5), is_equal_to(8));
assert_that(add(-7, 2), is_equal_to(-5));
assert_that(add(11, -4), is_equal_to(7));
assert_that(add(-2, -2), is_equal_to(-4));
}
TestSuite *solution_tests() {
TestSuite *suite = create_test_suite();
add_test_with_context(suite, Add, should_work_for_fixed_tests);
return suite;
}
Попробуйте скомпилировать Kata:
$ gcc solution.s solution_tests.c tests.c -lcgreen -o tests
Упс — наше решение не удалось скомпилировать. Вот что вы должны увидеть:
solution.s: Assembler messages:
solution.s:3: Error: illegal operands `addw a0,a1'
Я дам вам немного времени, чтобы разобраться в ошибке.
Теперь замените наше сломанное решение на рабочее в solution.s
:
.globl add
add:
addw a0, a0, a1
ret
Теперь скомпилируйте и запустите Kata — тесты должны пройти:
$ gcc solution.s solution_tests.c tests.c -lcgreen -o tests
$ ./tests
Вывод:
Running "solution_tests" (1 test)...
"solution_tests": 4 passes in 22ms.
Completed "solution_tests": 4 passes in 32ms.
Наконец, мы можем делать и случайные тесты, как и для всех остальных поддерживаемых языков в Codewars. Вот обновленный solution_tests.c
:
#include <cgreen/cgreen.h>
#include <time.h>
int add(int, int);
Describe(Add);
BeforeEach(Add) {}
AfterEach(Add) {}
Ensure(Add, should_work_for_fixed_tests) {
assert_that(add(3, 5), is_equal_to(8));
assert_that(add(-7, 2), is_equal_to(-5));
assert_that(add(11, -4), is_equal_to(7));
assert_that(add(-2, -2), is_equal_to(-4));
}
Ensure(Add, should_work_for_random_tests) {
srand(time(NULL));
for (int i = 0; i < 100; ++i) {
int a = rand() % 100, b = rand() % 100;
int expected = a + b;
int actual = add(a, b);
assert_equal_with_message(actual, expected, "Expected add(%d, %d) to equal: %d, instead got: %d", a, b, expected, actual);
}
}
TestSuite *solution_tests() {
TestSuite *suite = create_test_suite();
add_test_with_context(suite, Add, should_work_for_fixed_tests);
add_test_with_context(suite, Add, should_work_for_random_tests);
return suite;
}
Скомпилируйте и запустите Kata снова:
$ gcc solution.s solution_tests.c tests.c -lcgreen -o tests
$ ./tests
Выход:
Running "solution_tests" (2 tests)...
"solution_tests": 104 passes in 49ms.
Completed "solution_tests": 104 passes in 55ms.
Завершение работы, следующие шаги
Выключите питание платы:
$ sudo systemctl poweroff
На этом все — надеюсь, вам понравилась статья, и удачных спаррингов!
Вот некоторые возможные дальнейшие шаги, не обязательно связанные с Codewars:
- Приобрести настоящую, физическую плату RISC-V и следовать этой статье с ней, адаптируя инструкции по мере необходимости.
- В этой статье мы загрузили готовый дистрибутив Linux, что само по себе интересно, но было бы интереснее скомпилировать собственное ядро и (минимальное) пользовательское пространство и загрузить их на виртуальную (или физическую) плату. Например, загрузить последнее стабильное ядро с https://kernel.org и кросс-компилировать его для RISC-V, сделать то же самое для BusyBox, а затем загрузить нашу плату с ними. Документация по RISC-V дает краткое описание процесса, но вам придется заполнить пробелы, которые, судя по моему ограниченному опыту, не являются тривиальными.