From be7deb349ad881f2378754f5ac61a80810ea63a9 Mon Sep 17 00:00:00 2001 From: Amneesh Singh Date: Fri, 15 Sep 2023 14:07:23 +0530 Subject: [PATCH] tests: [WIP] add unit tests for some of the instructions Signed-off-by: Amneesh Singh --- apps/target/main.cc | 4 + flake.nix | 11 +- include/cpu/cpu.hh | 2 +- include/cpu/instruction.hh | 279 ++++++++++++++++++------------------- meson.build | 26 +++- nix/catch2.nix | 17 +++ src/cpu/cpu.cc | 53 +++---- src/cpu/instruction.cc | 6 +- tests/cpu/instruction.cc | 198 ++++++++++++++++++++++++++ tests/cpu/meson.build | 3 + tests/meson.build | 19 +++ 11 files changed, 445 insertions(+), 173 deletions(-) create mode 100644 nix/catch2.nix create mode 100644 tests/cpu/instruction.cc create mode 100644 tests/cpu/meson.build create mode 100644 tests/meson.build diff --git a/apps/target/main.cc b/apps/target/main.cc index 53bd8f8..f86a81d 100644 --- a/apps/target/main.cc +++ b/apps/target/main.cc @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -80,6 +81,9 @@ main(int argc, const char* argv[]) { return 1; } + std::flush(std::cout); + std::flush(std::cerr); + { Memory memory(std::move(bios), std::move(rom)); Bus bus(memory); diff --git a/flake.nix b/flake.nix index fe03bc4..8f5f015 100644 --- a/flake.nix +++ b/flake.nix @@ -8,17 +8,23 @@ systems = [ "x86_64-linux" "aarch64-linux" - # "i686-linux" ]; + eachSystem = with nixpkgs.lib; f: foldAttrs mergeAttrs { } (map (s: mapAttrs (_: v: { ${s} = v; }) (f s)) systems); in eachSystem (system: let pkgs = import nixpkgs { inherit system; }; + + # aliases llvm = pkgs.llvmPackages_16; stdenv = llvm.libcxxStdenv; + # packages + catch2_v3 = pkgs.callPackage ./nix/catch2.nix { inherit stdenv; }; + + #dependencies nativeBuildInputs = with pkgs; [ meson ninja @@ -26,6 +32,7 @@ # libraries pkg-config fmt.dev + catch2_v3.dev ]; in rec { @@ -51,6 +58,8 @@ matar = pkgs.mkShell.override { inherit stdenv; } { name = "matar"; packages = nativeBuildInputs ++ (with pkgs; [ + llvm.libcxx + # dev tools clang-tools_16 ]); diff --git a/include/cpu/cpu.hh b/include/cpu/cpu.hh index f290fba..b5e3bb9 100644 --- a/include/cpu/cpu.hh +++ b/include/cpu/cpu.hh @@ -35,7 +35,7 @@ class Cpu { bool is_flushed; void chg_mode(const Mode to); - void exec_arm(const ArmInstruction instruction); + void exec_arm(const arm::ArmInstruction instruction); struct { std::array fiq; diff --git a/include/cpu/instruction.hh b/include/cpu/instruction.hh index a0f618b..8889abe 100644 --- a/include/cpu/instruction.hh +++ b/include/cpu/instruction.hh @@ -9,159 +9,154 @@ struct overloaded : Ts... { template overloaded(Ts...) -> overloaded; -class ArmInstruction { - public: - ArmInstruction() = delete; - ArmInstruction(uint32_t insn); +namespace arm { +struct BranchAndExchange { + uint8_t rn; +}; - auto get_condition() const { return condition; } - auto get_data() const { return data; } +struct Branch { + bool link; + uint32_t offset; +}; - std::string disassemble(); +struct Multiply { + uint8_t rm; + uint8_t rs; + uint8_t rn; + uint8_t rd; + bool set; + bool acc; +}; - struct BranchAndExchange { - uint8_t rn; +struct MultiplyLong { + uint8_t rm; + uint8_t rs; + uint8_t rdlo; + uint8_t rdhi; + bool set; + bool acc; + bool uns; +}; + +struct SingleDataSwap { + uint8_t rm; + uint8_t rd; + uint8_t rn; + bool byte; +}; + +struct SingleDataTransfer { + std::variant offset; + uint8_t rd; + uint8_t rn; + bool load; + bool write; + bool byte; + bool up; + bool pre; +}; + +struct HalfwordTransfer { + uint8_t offset; + bool half; + bool sign; + uint8_t rd; + uint8_t rn; + bool load; + bool write; + bool byte; + bool imm; + bool up; + bool pre; +}; + +struct BlockDataTransfer { + uint16_t regs; + uint8_t rn; + bool load; + bool write; + bool s; + bool up; + bool pre; +}; + +struct DataProcessing { + std::variant operand; + uint8_t rd; + uint8_t rn; + bool set; + OpCode opcode; +}; + +struct PsrTransfer { + enum class Type { + Mrs, + Msr, + Msr_flg }; - struct Branch { - bool link; - uint32_t offset; - }; + uint32_t operand; + bool spsr; + Type type; + // ignored outside MSR_flg + bool imm; +}; - struct Multiply { - uint8_t rm; - uint8_t rs; - uint8_t rn; - uint8_t rd; - bool set; - bool acc; - }; +struct CoprocessorDataTransfer { + uint8_t offset; + uint8_t cpn; + uint8_t crd; + uint8_t rn; + bool load; + bool write; + bool len; + bool up; + bool pre; +}; - struct MultiplyLong { - uint8_t rm; - uint8_t rs; - uint8_t rdlo; - uint8_t rdhi; - bool set; - bool acc; - bool uns; - }; +struct CoprocessorDataOperation { + uint8_t crm; + uint8_t cp; + uint8_t cpn; + uint8_t crd; + uint8_t crn; + uint8_t cp_opc; +}; - struct SingleDataSwap { - uint8_t rm; - uint8_t rd; - uint8_t rn; - bool byte; - }; +struct CoprocessorRegisterTransfer { + uint8_t crm; + uint8_t cp; + uint8_t cpn; + uint8_t rd; + uint8_t crn; + bool load; + uint8_t cp_opc; +}; - struct SingleDataTransfer { - std::variant offset; - uint8_t rd; - uint8_t rn; - bool load; - bool write; - bool byte; - bool up; - bool pre; - }; +struct Undefined {}; +struct SoftwareInterrupt {}; - struct HalfwordTransfer { - uint8_t offset; - bool half; - bool sign; - uint8_t rd; - uint8_t rn; - bool load; - bool write; - bool byte; - bool imm; - bool up; - bool pre; - }; +using InstructionData = std::variant; - struct BlockDataTransfer { - uint16_t regs; - uint8_t rn; - bool load; - bool write; - bool s; - bool up; - bool pre; - }; - - struct DataProcessing { - std::variant operand; - uint8_t rd; - uint8_t rn; - bool set; - OpCode opcode; - }; - - struct PsrTransfer { - enum class Type { - Mrs, - Msr, - Msr_flg - }; - - uint32_t operand; - bool spsr; - Type type; - // ignored outside MSR_flg - bool imm; - }; - - struct CoprocessorDataTransfer { - uint8_t offset; - uint8_t cpn; - uint8_t crd; - uint8_t rn; - bool load; - bool write; - bool len; - bool up; - bool pre; - }; - - struct CoprocessorDataOperation { - uint8_t crm; - uint8_t cp; - uint8_t cpn; - uint8_t crd; - uint8_t crn; - uint8_t cp_opc; - }; - - struct CoprocessorRegisterTransfer { - uint8_t crm; - uint8_t cp; - uint8_t cpn; - uint8_t rd; - uint8_t crn; - bool load; - uint8_t cp_opc; - }; - - struct Undefined {}; - struct SoftwareInterrupt {}; - - using InstructionData = std::variant; - - private: +struct ArmInstruction { Condition condition; InstructionData data; + + ArmInstruction(uint32_t insn); + std::string disassemble(); }; +} diff --git a/meson.build b/meson.build index 3f901ec..e544e9c 100644 --- a/meson.build +++ b/meson.build @@ -3,10 +3,32 @@ project('matar', 'cpp', license : 'GPLv3', default_options : ['warning_level=3', 'werror=true', - 'optimization=3', - 'cpp_std=c++20']) + 'optimization=3']) + +compiler = meson.get_compiler('cpp') + +if compiler.has_argument('-std=c++23') + add_global_arguments('-std=c++23', language: 'cpp') +elif compiler.has_argument('-std=c++2b') + add_global_arguments('-std=c++2b', language: 'cpp') +else + error(compiler.get_id() + ' ' + compiler.version() + 'does not meet the compiler requirements') +endif + +''' +TODO: use and instead of libfmt once LLVM 17 is out + +if compiler.has_argument('-fexperimental-library') + add_global_arguments('-fexperimental-library', language: 'cpp') +else + error(compiler.get_id() + ' ' + compiler.version() + 'does not support -fexperimental-library') +endif +''' inc = include_directories('include') + subdir('include') subdir('src') subdir('apps') + +subdir('tests') diff --git a/nix/catch2.nix b/nix/catch2.nix new file mode 100644 index 0000000..e9e0255 --- /dev/null +++ b/nix/catch2.nix @@ -0,0 +1,17 @@ +{ stdenv, fetchFromGitHub, meson, ninja }: + +stdenv.mkDerivation rec { + name = "catch2"; + version = "3.4.0"; + + src = fetchFromGitHub { + owner = "catchorg"; + repo = "Catch2"; + rev = "v${version}"; + sha256 = "sha256-DqGGfNjKPW9HFJrX9arFHyNYjB61uoL6NabZatTWrr0="; + }; + + nativeBuildInputs = [ meson ninja ]; + + outputs = [ "out" "dev" ]; +} diff --git a/src/cpu/cpu.cc b/src/cpu/cpu.cc index edb5870..5366ffb 100644 --- a/src/cpu/cpu.cc +++ b/src/cpu/cpu.cc @@ -116,27 +116,29 @@ Cpu::chg_mode(const Mode to) { } void -Cpu::exec_arm(const ArmInstruction instruction) { - auto cond = instruction.get_condition(); - auto data = instruction.get_data(); +Cpu::exec_arm(const arm::ArmInstruction instruction) { + auto cond = instruction.condition; + auto data = instruction.data; if (!cpsr.condition(cond)) { return; } auto pc_error = [](uint8_t r) { - if (r == 15) + if (r == PC_INDEX) log_error("Using PC (R15) as operand register"); }; auto pc_warn = [](uint8_t r) { - if (r == 15) + if (r == PC_INDEX) log_warn("Using PC (R15) as operand register"); }; + using namespace arm; + std::visit( overloaded{ - [this, pc_warn](ArmInstruction::BranchAndExchange& data) { + [this, pc_warn](BranchAndExchange& data) { State state = static_cast(data.rn & 1); pc_warn(data.rn); @@ -156,7 +158,7 @@ Cpu::exec_arm(const ArmInstruction instruction) { // pc is affected so flush the pipeline is_flushed = true; }, - [this](ArmInstruction::Branch& data) { + [this](Branch& data) { if (data.link) gpr[14] = pc - ARM_INSTRUCTION_SIZE; @@ -168,7 +170,7 @@ Cpu::exec_arm(const ArmInstruction instruction) { // pc is affected so flush the pipeline is_flushed = true; }, - [this, pc_error](ArmInstruction::Multiply& data) { + [this, pc_error](Multiply& data) { if (data.rd == data.rm) log_error("rd and rm are not distinct in {}", typeid(data).name()); @@ -186,7 +188,7 @@ Cpu::exec_arm(const ArmInstruction instruction) { cpsr.set_c(0); } }, - [this, pc_error](ArmInstruction::MultiplyLong& data) { + [this, pc_error](MultiplyLong& data) { if (data.rdhi == data.rdlo || data.rdhi == data.rm || data.rdlo == data.rm) log_error("rdhi, rdlo and rm are not distinct in {}", @@ -226,8 +228,8 @@ Cpu::exec_arm(const ArmInstruction instruction) { cpsr.set_v(0); } }, - [](ArmInstruction::Undefined) { log_warn("Undefined instruction"); }, - [this, pc_error](ArmInstruction::SingleDataSwap& data) { + [](Undefined) { log_warn("Undefined instruction"); }, + [this, pc_error](SingleDataSwap& data) { pc_error(data.rm); pc_error(data.rn); pc_error(data.rd); @@ -240,7 +242,7 @@ Cpu::exec_arm(const ArmInstruction instruction) { bus->write_word(gpr[data.rn], gpr[data.rm]); } }, - [this, pc_warn, pc_error](ArmInstruction::SingleDataTransfer& data) { + [this, pc_warn, pc_error](SingleDataTransfer& data) { uint32_t offset = 0; uint32_t address = gpr[data.rn]; @@ -316,7 +318,7 @@ Cpu::exec_arm(const ArmInstruction instruction) { if (data.rd == PC_INDEX && data.load) is_flushed = true; }, - [this, pc_warn, pc_error](ArmInstruction::HalfwordTransfer& data) { + [this, pc_warn, pc_error](HalfwordTransfer& data) { uint32_t address = gpr[data.rn]; if (!data.pre && data.write) @@ -345,15 +347,16 @@ Cpu::exec_arm(const ArmInstruction instruction) { gpr[data.rd] = bus->read_halfword(address); // sign extend the halfword - if (get_bit(gpr[data.rd], PC_INDEX)) - gpr[data.rd] |= 0xFFFF0000; + gpr[data.rd] = + (static_cast(gpr[data.rd]) << 16) >> 16; + // byte } else { gpr[data.rd] = bus->read_byte(address); // sign extend the byte - if (get_bit(gpr[data.rd], 7)) - gpr[data.rd] |= 0xFFFFFF00; + gpr[data.rd] = + (static_cast(gpr[data.rd]) << 24) >> 24; } // unsigned halfword } else if (data.half) { @@ -379,7 +382,7 @@ Cpu::exec_arm(const ArmInstruction instruction) { if (data.rd == PC_INDEX && data.load) is_flushed = true; }, - [this, pc_error](ArmInstruction::BlockDataTransfer& data) { + [this, pc_error](BlockDataTransfer& data) { uint32_t address = gpr[data.rn]; Mode mode = cpsr.mode(); uint8_t alignment = 4; // word @@ -449,7 +452,7 @@ Cpu::exec_arm(const ArmInstruction instruction) { // load back the original mode registers chg_mode(mode); }, - [this, pc_error](ArmInstruction::PsrTransfer& data) { + [this, pc_error](PsrTransfer& data) { if (data.spsr && cpsr.mode() == Mode::User) { log_error("Accessing SPSR in User mode in {}", typeid(data).name()); @@ -458,18 +461,18 @@ Cpu::exec_arm(const ArmInstruction instruction) { Psr& psr = data.spsr ? spsr : cpsr; switch (data.type) { - case ArmInstruction::PsrTransfer::Type::Mrs: + case PsrTransfer::Type::Mrs: pc_error(data.operand); gpr[data.operand] = psr.raw(); break; - case ArmInstruction::PsrTransfer::Type::Msr: + case PsrTransfer::Type::Msr: pc_error(data.operand); if (cpsr.mode() != Mode::User) { psr.set_all(gpr[data.operand]); break; } - case ArmInstruction::PsrTransfer::Type::Msr_flg: + case PsrTransfer::Type::Msr_flg: psr.set_n(get_bit(data.operand, 31)); psr.set_z(get_bit(data.operand, 30)); psr.set_c(get_bit(data.operand, 29)); @@ -477,7 +480,7 @@ Cpu::exec_arm(const ArmInstruction instruction) { break; } }, - [this, pc_error](ArmInstruction::DataProcessing& data) { + [this, pc_error](DataProcessing& data) { uint32_t op_1 = gpr[data.rn]; uint32_t op_2 = 0; @@ -671,7 +674,7 @@ Cpu::exec_arm(const ArmInstruction instruction) { is_flushed = true; } }, - [this](ArmInstruction::SoftwareInterrupt) { + [this](SoftwareInterrupt) { chg_mode(Mode::Supervisor); pc = 0x08; spsr = cpsr; @@ -690,7 +693,7 @@ Cpu::step() { if (cpsr.state() == State::Arm) { debug(cur_pc); uint32_t x = bus->read_word(cur_pc); - ArmInstruction instruction(x); + arm::ArmInstruction instruction(x); log_info("{:#034b}", x); exec_arm(instruction); diff --git a/src/cpu/instruction.cc b/src/cpu/instruction.cc index ca95739..af7cfff 100644 --- a/src/cpu/instruction.cc +++ b/src/cpu/instruction.cc @@ -336,7 +336,8 @@ ArmInstruction::disassemble() { if (*offset == 0) { expression = ""; } else { - expression = fmt::format(",#{:d}", *offset); + expression = + fmt::format(",{}#{:d}", (data.up ? '+' : '-'), *offset); } } else if (const Shift* shift = std::get_if(&data.offset)) { // Shifts are always immediate in single data transfer @@ -365,7 +366,8 @@ ArmInstruction::disassemble() { if (data.offset == 0) { expression = ""; } else { - expression = fmt::format(",#{:d}", data.offset); + expression = fmt::format( + ",{}#{:d}", (data.up ? '+' : '-'), data.offset); } } else { expression = diff --git a/tests/cpu/instruction.cc b/tests/cpu/instruction.cc new file mode 100644 index 0000000..a5cbe85 --- /dev/null +++ b/tests/cpu/instruction.cc @@ -0,0 +1,198 @@ +#include "cpu/instruction.hh" +#include "cpu/utility.hh" +#include +#include + +static constexpr auto TAG = "disassembler"; + +using namespace arm; + +TEST_CASE("Branch and Exchange", TAG) { + uint32_t raw = 0b11000001001011111111111100011010; + ArmInstruction instruction(raw); + BranchAndExchange* bx = nullptr; + + REQUIRE((bx = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::GT); + + REQUIRE(bx->rn == 10); + + REQUIRE(instruction.disassemble() == "BXGT R10"); +} + +TEST_CASE("Branch", TAG) { + uint32_t raw = 0b11101011100001010111111111000011; + ArmInstruction instruction(raw); + Branch* b = nullptr; + + REQUIRE((b = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::AL); + + // last 24 bits = 8748995 + // (8748995 << 8) >> 6 sign extended = 0xFE15FF0C + // Also +8 since PC is two instructions ahead + REQUIRE(b->offset == 0xFE15FF14); + REQUIRE(b->link == true); + + REQUIRE(instruction.disassemble() == "BL 0xFE15FF14"); + + b->link = false; + REQUIRE(instruction.disassemble() == "B 0xFE15FF14"); +} + +TEST_CASE("Multiply", TAG) { + uint32_t raw = 0b00000000001110101110111110010000; + ArmInstruction instruction(raw); + Multiply* mul = nullptr; + + REQUIRE((mul = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::EQ); + + REQUIRE(mul->rm == 0); + REQUIRE(mul->rs == 15); + REQUIRE(mul->rn == 14); + REQUIRE(mul->rd == 10); + REQUIRE(mul->acc == true); + REQUIRE(mul->set == true); + + REQUIRE(instruction.disassemble() == "MLAEQS R10,R0,R15,R14"); + + mul->acc = false; + mul->set = false; + REQUIRE(instruction.disassemble() == "MULEQ R10,R0,R15"); +} + +TEST_CASE("Multiply Long", TAG) { + uint32_t raw = 0b00010000100111100111011010010010; + ArmInstruction instruction(raw); + MultiplyLong* mull = nullptr; + + REQUIRE((mull = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::NE); + + REQUIRE(mull->rm == 2); + REQUIRE(mull->rs == 6); + REQUIRE(mull->rdlo == 7); + REQUIRE(mull->rdhi == 14); + REQUIRE(mull->acc == false); + REQUIRE(mull->set == true); + REQUIRE(mull->uns == false); + + REQUIRE(instruction.disassemble() == "SMULLNES R7,R14,R2,R6"); + + mull->acc = true; + REQUIRE(instruction.disassemble() == "SMLALNES R7,R14,R2,R6"); + + mull->uns = true; + mull->set = false; + REQUIRE(instruction.disassemble() == "UMLALNE R7,R14,R2,R6"); +} + +TEST_CASE("Single Data Swap", TAG) { + uint32_t raw = 0b10100001000010010101000010010110; + ArmInstruction instruction(raw); + SingleDataSwap* swp = nullptr; + + REQUIRE((swp = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::GE); + + REQUIRE(swp->rm == 6); + REQUIRE(swp->rd == 5); + REQUIRE(swp->rn == 9); + REQUIRE(swp->byte == false); + + REQUIRE(instruction.disassemble() == "SWPGE R5,R6,[R9]"); + + swp->byte = true; + REQUIRE(instruction.disassemble() == "SWPGEB R5,R6,[R9]"); +} + +TEST_CASE("Single Data Transfer", TAG) { + uint32_t raw = 0b11100111101000101010111100000110; + ArmInstruction instruction(raw); + SingleDataTransfer* ldr = nullptr; + Shift* shift = nullptr; + + REQUIRE((ldr = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::AL); + + REQUIRE((shift = std::get_if(&ldr->offset))); + REQUIRE(shift->rm == 6); + REQUIRE(shift->data.immediate == true); + REQUIRE(shift->data.type == ShiftType::LSL); + REQUIRE(shift->data.operand == 30); + REQUIRE(ldr->rd == 10); + REQUIRE(ldr->rn == 2); + REQUIRE(ldr->load == false); + REQUIRE(ldr->write == true); + REQUIRE(ldr->byte == false); + REQUIRE(ldr->up == true); + REQUIRE(ldr->pre == true); + + ldr->load = true; + ldr->byte = true; + ldr->write = false; + shift->data.type = ShiftType::ROR; + REQUIRE(instruction.disassemble() == "LDRB R10,[R2,+R6,ROR #30]"); + + ldr->up = false; + ldr->pre = false; + REQUIRE(instruction.disassemble() == "LDRB R10,[R2],-R6,ROR #30"); + + ldr->offset = static_cast(9023); + REQUIRE(instruction.disassemble() == "LDRB R10,[R2],-#9023"); + + ldr->pre = true; + REQUIRE(instruction.disassemble() == "LDRB R10,[R2,-#9023]"); +} + +TEST_CASE("Halfword Transfer", TAG) { + uint32_t raw = 0b00110001101011110010000010110110; + ArmInstruction instruction(raw); + HalfwordTransfer* ldr = nullptr; + + REQUIRE((ldr = std::get_if(&instruction.data))); + REQUIRE(instruction.condition == Condition::CC); + + // offset is not immediate + REQUIRE(ldr->imm == 0); + // hence this offset is a register number (rm) + REQUIRE(ldr->offset == 6); + REQUIRE(ldr->half == true); + REQUIRE(ldr->sign == false); + REQUIRE(ldr->rd == 2); + REQUIRE(ldr->rn == 15); + REQUIRE(ldr->load == false); + REQUIRE(ldr->write == true); + REQUIRE(ldr->up == true); + REQUIRE(ldr->pre == true); + + REQUIRE(instruction.disassemble() == "STRCCH R2,[R15,+R6]!"); + + ldr->pre = false; + ldr->load = true; + ldr->sign = true; + ldr->up = false; + + REQUIRE(instruction.disassemble() == "LDRCCSH R2,[R15],-R6"); + + ldr->half = false; + REQUIRE(instruction.disassemble() == "LDRCCSB R2,[R15],-R6"); + + // not a register anymore + ldr->load = false; + ldr->imm = 1; + ldr->offset = 90; + REQUIRE(instruction.disassemble() == "STRCCSB R2,[R15],-#90"); +} + +TEST_CASE("Undefined", TAG) { + // notice how this is the same as single data transfer except the shift is + // now a register based shift + uint32_t raw = 0b11100111101000101010111100010110; + + REQUIRE(ArmInstruction(raw).disassemble() == "UNDEFINED"); + + raw = 0b11100110000000000000000000010000; + REQUIRE(ArmInstruction(raw).disassemble() == "UNDEFINED"); +} diff --git a/tests/cpu/meson.build b/tests/cpu/meson.build new file mode 100644 index 0000000..020125c --- /dev/null +++ b/tests/cpu/meson.build @@ -0,0 +1,3 @@ +tests_sources += files( + 'instruction.cc' +) \ No newline at end of file diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..cb27544 --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,19 @@ +tests_deps = [ + lib +] + +tests_sources = files() + +subdir('cpu') + +catch2 = dependency('catch2-with-main', version: '>=3.4.0', static: true) +catch2_tests = executable( + meson.project_name() + '_tests', + tests_sources, + dependencies: catch2, + link_with: tests_deps, + include_directories: inc, + build_by_default: false +) + +test('catch2 tests', catch2_tests)