CMake: Подключение riscv-arch-test для тестирования имплементации rv32

от автора

При написании своей VM для RISC-V возникла необходимость в тестировании.

Сначала я пытался писать юнит-тесты самостоятельно, но выходило, что я просто копирую логику из основной.

И по сути тестирую не соответствие спецификации, а соответствие моему пониманию.

Через некоторое время я наткнулся на официальный набор тестов для RISC-V и решил их использовать.

Это помогло найти несколько багов в моём коде.

Что ж.

Смотрим в репозиторий и огорчаемся — поддержки cmake там нет.

Ну она особо и не нужна, а нужны исходники тестов.

Ищем как в cmake скачать репозиторий -> ExternalProject -> ExternalProject_Add

В настройках нужно указать команды конфигурации и сборки(RTFM, please) но сейчас нужны только данные, по этому отключаем.

Тесты не всегда нужны, поэтому ввел настройку:

option(YETI_ENABLE_ARCH_TESTS "Build riscv-arch-tests tests" ON)

Подключаем данные:

if (YETI_ENABLE_ARCH_TESTS)     enable_testing()      ExternalProject_Add(riscv_arch_test             GIT_REPOSITORY https://github.com/riscv-non-isa/riscv-arch-test.git             GIT_TAG fc32e41d49480fd99ba0a192dfff9c3319b44873 # 3.10.0             GIT_PROGRESS ON             SOURCE_DIR "${DOWNLOAD_BASE_DIR}/riscv-arch-test"             CONFIGURE_COMMAND ""             BUILD_COMMAND ""             INSTALL_COMMAND ""             BUILD_IN_SOURCE ON     )      add_subdirectory(tests/arch-tests) endif ()

Теперь нужно создать «запускатор» тестов и скомпилировать сами тесты под RV32IM

Тесты требуют определенной настройки под тестируемую архитектуру — создание model_test.h.
В нем описываются макросы для тестирования а-ля assert
У меня вышло следующее:

// config for https://github.com/riscv-non-isa/riscv-arch-test/ // docs: https://github.com/riscv-non-isa/riscv-arch-test/releases  #ifndef YETI_VM_MODEL_H #define YETI_VM_MODEL_H  // Supports rv32 #define XLEN 32 // float32 #define FLEN 32  #define ALIGNMENT 2  #define TEST_CASE_1  // startup code #define RVMODEL_BOOT \     RVMODEL_IO_INIT  \  // stop code #define RVMODEL_HALT \ .do_exit:            \     li a7, 10;       \     ecall;           \     .global _assert_failed; \     _assert_failed: ebreak; \     j _assert_failed;\  #define RVMODEL_DATA_BEGIN \     .align 4;              \     .global begin_signature; \     begin_signature:  #define RVMODEL_DATA_END \     .align 4;            \     .global end_signature; \     end_signature:  #define RVMODEL_IO_INIT #define RVMODEL_IO_WRITE_STR(_R, _STR) #define RVMODEL_IO_CHECK()  // asserts: testreg, destreg, correctval // store values in "DEV" memory // generic purpose registers: #define LBL_OK(_S, _R, _I, _L) .assert_ok ## _L  #define RVMODEL_IO_ASSERT_GPR_EQ_IMPL(_S, _R, _I, _L) \     LI(_S, _I);                               \     beq _S, _R, LBL_OK(_S, _R, _I, _L);          \     LA(_S, __dev_start);                     \     sw zero, 0(_S);                          \     sw _R, 4(_S);                            \     LI(_R, _I);                              \     sw _R, 8(_S);                            \     LI(_R, _L);                              \     sw _R, 12(_S);                                    \     j  _assert_failed;                                \     LBL_OK(_S, _R, _I, _L):                           \  #define RVMODEL_IO_ASSERT_GPR_EQ(_S, _R, _I) \     RVMODEL_IO_ASSERT_GPR_EQ_IMPL(_S, _R, _I, __LINE__); \  // float32 registers #define RVMODEL_IO_ASSERT_SFPR_EQ(_F, _R, _I) // float64 registers #define RVMODEL_IO_ASSERT_DFPR_EQ(_D, _R, _I)  // machine-mode interrupts // use default behavior - end test // TODO: learn about it //#define RVMODEL_SET_MSW_INT //#define RVMODEL_CLEAR_MSW_INT //#define RVMODEL_CLEAR_MTIMER_INT //#define RVMODEL_CLEAR_MEXT_INT  #endif // YETI_VM_MODEL_H 

В качестве запускатора служит код:

#include <yeti-vm/vm_basic.hxx>  #include <iostream> #include <cstring>  namespace vm::yeti_runner { struct Runner: protected vm::basic_vm {     bool initProgram(int testIdx, char ** argv)     {         bool isa_ok = init_isa();         bool mem_ok = init_memory();         mem_ok = mem_ok && add_memory(std::make_shared<DeviceMemory>(this));          bool init_ok = isa_ok && mem_ok;         init_ok = init_ok && initSysCalls();          auto code = vm::parse_hex(argv[testIdx]);         init_ok = init_ok && code.has_value();         init_ok = init_ok && set_program(code.value());          return init_ok;     }     bool initSysCalls()     {         syscall_should_throw(false);         using call = vm::syscall_functor;         auto& sys = get_syscalls();         bool ok = sys.register_handler(                 call::create(10, "exit"                              , [this](vm::MachineInterface* m)                              { return do_exit(m); }));          return ok;     }     void do_exit(vm::MachineInterface*)     {         basic_vm::halt();     }      bool exec(bool debug = false)     {         enable_debugging(debug);         start();         try         {             run();         }         catch (std::exception& e)         {             std::cerr << std::endl << "Exception: " << e.what() << std::endl;             dump_state(std::cerr);             return false;         }         return !set_dev; // no failures     } protected:     void debug() override     {         if (set_dev)         {             dump_state(std::cerr);             auto fill_c = std::cerr.fill();             std::cerr << std::dec;             std::cerr << "set_dev == true " << std::endl;             std::cerr << "DEV MEM: " << std::endl;             for(auto v: dev_mem)             {                 std::cerr << "\t" << std::hex << std::setfill('0') << std::setw(8) << v << std::endl;             }             std::cerr << "\t:DEV MEM" << std::endl;             std::cerr << std::dec << std::setfill(fill_c);             halt();         }         return basic_vm::debug();     }     void assert_set(uint32_t idx, uint32_t v)     {         dev_mem[idx] = v;         set_dev = true;     }     bool set_dev = false;     std::array<uint32_t, 4> dev_mem{}; protected:     struct DeviceMemory final: public vm::memory_block     {         explicit DeviceMemory(Runner* runner)             : vm::memory_block{def_data_base + def_data_size, def_data_size}             , runner{runner} {}          [[nodiscard]]         bool load(address_type address, void *dest, size_type size) const final         {             std::memset(dest, 0, size);             return true;         }          [[nodiscard]]         bool store(memory_block::address_type address, const void *source, memory_block::size_type size) final         {             runner->set_dev = true;             if (size != 4) return false;             auto offset = (address - get_start_address()) / 4;             if (offset >= runner->dev_mem.size()) return false;             runner->assert_set(offset, *reinterpret_cast<const uint32_t*>(source));             return true;         }      protected:         [[nodiscard]]         const void *get_ro(address_type address, size_type size) const final         {             return nullptr;         }          [[nodiscard]]         void *get_rw(address_type address, size_type size) final         {             return nullptr;         }     private:         Runner* runner = nullptr;     }; }; } // vm::yeti_runner  int main(int argc, char ** argv) {     int numFails = 0;     for (int testIdx = 1; testIdx < argc; ++testIdx)     {         vm::yeti_runner::Runner yetiVM;         if (!yetiVM.initProgram(testIdx, argv))         {             std::cerr << "Unable init: " << std::dec << testIdx << " " << argv[testIdx] << std::endl;             return EXIT_FAILURE;         }         if (!yetiVM.exec(argc == 2)) // single file - enable debug output         {             std::cerr << "Fail: " << std::dec << testIdx << " " << argv[testIdx] << std::endl;             ++numFails;         }     }     return numFails; } 

В нем используется memory-mapped device, которое обслуживает событие «завершение работы»

Для компиляции тестов нужен toolchain riscv64-unknown-elf

Для поиска используется функция find_riscv_toolchain

Собственно для компиляции и запуска тестов используется код

# use POST_BUILD step to compile tests block()     set(_out_dir "${CMAKE_CURRENT_BINARY_DIR}")     foreach (_subset IN LISTS ARCH_TEST_SUBSETS)         set(_subset_dir "${ARCH_TEST_SUITE_RV32}/${_subset}/src/")         file(GLOB _subset_tests RELATIVE "${_subset_dir}" "${_subset_dir}/*.S")         message(DEBUG "Dir: ${_subset_dir} ... tests: ${_subset_tests}")          set(_tests_to_run)          foreach (_test_asm IN LISTS _subset_tests)             get_filename_component(_test_name "${_test_asm}" NAME_WLE)             set(_test_id "${_subset}/${_test_name}")             message(DEBUG "Add test ${_test_id}")             set(_input_file "${_subset_dir}/${_test_asm}")             set(_elf_file "${_out_dir}/${_subset}_${_test_name}.elf")             set(_hex_file "${_out_dir}/${_subset}_${_test_name}.hex")             list(APPEND _build_args                     "${YETI_VM_ARCH_ARGS}"                     "${ARCH_TEST_INCLUDE_DIRS}"                     -T "${CMAKE_CURRENT_LIST_DIR}/config/link.ld"                     "${_input_file}"                     -o "${_elf_file}"             )             add_custom_command(TARGET yeti-runner POST_BUILD                     COMMENT "Build ${_test_id}"                     COMMAND rv_tools::_gcc                     ARGS "${_build_args}"                     DEPENDS "${_input_file}"                         "${CMAKE_CURRENT_LIST_DIR}/config/link.ld"                         "${CMAKE_CURRENT_LIST_DIR}/config/model_test.h"                     BYPRODUCTS "${_elf_file}"                     COMMAND_EXPAND_LISTS             )             add_custom_command(TARGET yeti-runner POST_BUILD                     COMMENT "Make hex file ${_test_id}"                     COMMAND rv_tools::_objcopy                     ARGS -O ihex "${_elf_file}" "${_hex_file}"                     DEPENDS "${_elf_file}"                     BYPRODUCTS "${_hex_file}"             )              list(APPEND _tests_to_run "${_hex_file}")              unset(_input_file)             unset(_elf_file)             unset(_hex_file)             unset(_build_args)         endforeach ()          add_test(NAME "RV32_ISA_${_subset}"                 COMMAND yeti-runner "${_tests_to_run}"                 COMMAND_EXPAND_LISTS                 )         unset(_subset_dir)         unset(_tests_to_run)     endforeach ()     unset(_subset)     unset(_out_dir) endblock()

В котором для каждого теста из выбранного набора происходит компиляция, преобразование в ihex и добавление команды для «запускатора»


ссылка на оригинал статьи https://habr.com/ru/articles/896986/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *