Compare commits
6 Commits
492c06cc45
...
35164f8e4f
| Author | SHA1 | Date | |
|---|---|---|---|
| 35164f8e4f | |||
|
ed01ed80cd
|
|||
|
8e26cadc9a
|
|||
|
6e56828dfd
|
|||
|
5fcc75bc9a
|
|||
|
560bd5bfa1
|
@@ -4,7 +4,8 @@ project('matar', 'cpp',
|
|||||||
default_options : ['warning_level=3',
|
default_options : ['warning_level=3',
|
||||||
'werror=true',
|
'werror=true',
|
||||||
'optimization=3',
|
'optimization=3',
|
||||||
'cpp_std=c++20'])
|
'cpp_std=c++20',
|
||||||
|
'default_library=static'])
|
||||||
|
|
||||||
compiler = meson.get_compiler('cpp')
|
compiler = meson.get_compiler('cpp')
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ lib_sources += files(
|
|||||||
'alu.cc'
|
'alu.cc'
|
||||||
)
|
)
|
||||||
|
|
||||||
subdir('arm')
|
subdir('arm')
|
||||||
|
subdir('thumb')
|
||||||
191
src/cpu/thumb/instruction.cc
Normal file
191
src/cpu/thumb/instruction.cc
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
#include "instruction.hh"
|
||||||
|
#include "util/bits.hh"
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
namespace matar {
|
||||||
|
namespace thumb {
|
||||||
|
|
||||||
|
Instruction::Instruction(uint16_t insn) {
|
||||||
|
// Format 1: Move Shifted Register
|
||||||
|
if ((insn & 0xE000) == 0x0000) {
|
||||||
|
uint8_t rd = bit_range(insn, 0, 2);
|
||||||
|
uint8_t rs = bit_range(insn, 3, 5);
|
||||||
|
uint8_t offset = bit_range(insn, 6, 10);
|
||||||
|
ShiftType opcode = static_cast<ShiftType>(bit_range(insn, 11, 12));
|
||||||
|
|
||||||
|
data = MoveShiftedRegister{
|
||||||
|
.rd = rd, .rs = rs, .offset = offset, .opcode = opcode
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format 2: Add/Subtract
|
||||||
|
} else if ((insn & 0xF800) == 0x1800) {
|
||||||
|
uint8_t rd = bit_range(insn, 0, 2);
|
||||||
|
uint8_t rs = bit_range(insn, 3, 5);
|
||||||
|
uint8_t offset = bit_range(insn, 6, 8);
|
||||||
|
AddSubtract::OpCode opcode =
|
||||||
|
static_cast<AddSubtract::OpCode>(get_bit(insn, 9));
|
||||||
|
bool imm = get_bit(insn, 10);
|
||||||
|
|
||||||
|
data = AddSubtract{
|
||||||
|
.rd = rd, .rs = rs, .offset = offset, .opcode = opcode, .imm = imm
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format 3: Move/compare/add/subtract immediate
|
||||||
|
} else if ((insn & 0xE000) == 0x2000) {
|
||||||
|
uint8_t offset = bit_range(insn, 0, 7);
|
||||||
|
uint8_t rd = bit_range(insn, 8, 10);
|
||||||
|
MovCmpAddSubImmediate::OpCode opcode =
|
||||||
|
static_cast<MovCmpAddSubImmediate::OpCode>(bit_range(insn, 11, 12));
|
||||||
|
|
||||||
|
data =
|
||||||
|
MovCmpAddSubImmediate{ .offset = offset, .rd = rd, .opcode = opcode };
|
||||||
|
|
||||||
|
// Format 4: ALU operations
|
||||||
|
} else if ((insn & 0xFC00) == 0x4000) {
|
||||||
|
uint8_t rd = bit_range(insn, 0, 2);
|
||||||
|
uint8_t rs = bit_range(insn, 3, 5);
|
||||||
|
AluOperations::OpCode opcode =
|
||||||
|
static_cast<AluOperations::OpCode>(bit_range(insn, 6, 9));
|
||||||
|
|
||||||
|
data = AluOperations{ .rd = rd, .rs = rs, .opcode = opcode };
|
||||||
|
|
||||||
|
// Format 5: Hi register operations/branch exchange
|
||||||
|
} else if ((insn & 0xFC00) == 0x4400) {
|
||||||
|
uint8_t rd = bit_range(insn, 0, 2);
|
||||||
|
uint8_t rs = bit_range(insn, 3, 5);
|
||||||
|
bool hi_2 = get_bit(insn, 6);
|
||||||
|
bool hi_1 = get_bit(insn, 7);
|
||||||
|
HiRegisterOperations::OpCode opcode =
|
||||||
|
static_cast<HiRegisterOperations::OpCode>(bit_range(insn, 8, 9));
|
||||||
|
|
||||||
|
data = HiRegisterOperations{
|
||||||
|
.rd = rd, .rs = rs, .hi_2 = hi_2, .hi_1 = hi_1, .opcode = opcode
|
||||||
|
};
|
||||||
|
// Format 6: PC-relative load
|
||||||
|
} else if ((insn & 0xF800) == 0x4800) {
|
||||||
|
uint8_t word = bit_range(insn, 0, 7);
|
||||||
|
uint8_t rd = bit_range(insn, 8, 10);
|
||||||
|
|
||||||
|
data = PcRelativeLoad{ .word = word, .rd = rd };
|
||||||
|
|
||||||
|
// Format 7: Load/store with register offset
|
||||||
|
} else if ((insn & 0xF200) == 0x5000) {
|
||||||
|
uint8_t rd = bit_range(insn, 0, 2);
|
||||||
|
uint8_t rb = bit_range(insn, 3, 5);
|
||||||
|
uint8_t ro = bit_range(insn, 6, 8);
|
||||||
|
bool byte = get_bit(insn, 10);
|
||||||
|
bool load = get_bit(insn, 11);
|
||||||
|
|
||||||
|
data = LoadStoreRegisterOffset{
|
||||||
|
.rd = rd, .rb = rb, .ro = ro, .byte = byte, .load = load
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format 8: Load/store sign-extended byte/halfword
|
||||||
|
} else if ((insn & 0xF200) == 0x5200) {
|
||||||
|
uint8_t rd = bit_range(insn, 0, 2);
|
||||||
|
uint8_t rb = bit_range(insn, 3, 5);
|
||||||
|
uint8_t ro = bit_range(insn, 6, 8);
|
||||||
|
bool s = get_bit(insn, 10);
|
||||||
|
bool h = get_bit(insn, 11);
|
||||||
|
|
||||||
|
data = LoadStoreSignExtendedHalfword{
|
||||||
|
.rd = rd, .rb = rb, .ro = ro, .s = s, .h = h
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format 9: Load/store with immediate offset
|
||||||
|
} else if ((insn & 0xF000) == 0x6000) {
|
||||||
|
uint8_t rd = bit_range(insn, 0, 2);
|
||||||
|
uint8_t rb = bit_range(insn, 3, 5);
|
||||||
|
uint8_t offset = bit_range(insn, 6, 10);
|
||||||
|
bool load = get_bit(insn, 11);
|
||||||
|
bool byte = get_bit(insn, 12);
|
||||||
|
|
||||||
|
data = LoadStoreImmediateOffset{
|
||||||
|
.rd = rd, .rb = rb, .offset = offset, .load = load, .byte = byte
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format 10: Load/store halfword
|
||||||
|
} else if ((insn & 0xF000) == 0x8000) {
|
||||||
|
uint8_t rd = bit_range(insn, 0, 2);
|
||||||
|
uint8_t rb = bit_range(insn, 3, 5);
|
||||||
|
uint8_t offset = bit_range(insn, 6, 10);
|
||||||
|
bool load = get_bit(insn, 11);
|
||||||
|
|
||||||
|
data = LoadStoreHalfword{
|
||||||
|
.rd = rd, .rb = rb, .offset = offset, .load = load
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format 11: SP-relative load/store
|
||||||
|
} else if ((insn & 0xF000) == 0x9000) {
|
||||||
|
uint8_t word = bit_range(insn, 0, 7);
|
||||||
|
uint8_t rd = bit_range(insn, 8, 10);
|
||||||
|
bool load = get_bit(insn, 11);
|
||||||
|
|
||||||
|
data = SpRelativeLoad{ .word = word, .rd = rd, .load = load };
|
||||||
|
|
||||||
|
// Format 12: Load address
|
||||||
|
} else if ((insn & 0xF000) == 0xA000) {
|
||||||
|
uint8_t word = bit_range(insn, 0, 7);
|
||||||
|
uint8_t rd = bit_range(insn, 8, 10);
|
||||||
|
bool sp = get_bit(insn, 11);
|
||||||
|
|
||||||
|
data = LoadAddress{ .word = word, .rd = rd, .sp = sp };
|
||||||
|
|
||||||
|
// Format 12: Load address
|
||||||
|
} else if ((insn & 0xF000) == 0xA000) {
|
||||||
|
uint8_t word = bit_range(insn, 0, 7);
|
||||||
|
uint8_t rd = bit_range(insn, 8, 10);
|
||||||
|
bool sp = get_bit(insn, 11);
|
||||||
|
|
||||||
|
data = LoadAddress{ .word = word, .rd = rd, .sp = sp };
|
||||||
|
|
||||||
|
// Format 13: Add offset to stack pointer
|
||||||
|
} else if ((insn & 0xFF00) == 0xB000) {
|
||||||
|
uint8_t word = bit_range(insn, 0, 6);
|
||||||
|
bool sign = get_bit(insn, 7);
|
||||||
|
|
||||||
|
data = AddOffsetStackPointer{ .word = word, .sign = sign };
|
||||||
|
|
||||||
|
// Format 14: Push/pop registers
|
||||||
|
} else if ((insn & 0xF600) == 0xB400) {
|
||||||
|
uint8_t regs = bit_range(insn, 0, 7);
|
||||||
|
bool pclr = get_bit(insn, 8);
|
||||||
|
bool load = get_bit(insn, 11);
|
||||||
|
|
||||||
|
data = PushPopRegister{ .regs = regs, .pclr = pclr, .load = load };
|
||||||
|
|
||||||
|
// Format 15: Multiple load/store
|
||||||
|
} else if ((insn & 0xF000) == 0xC000) {
|
||||||
|
uint8_t regs = bit_range(insn, 0, 7);
|
||||||
|
uint8_t rb = bit_range(insn, 8, 10);
|
||||||
|
bool load = get_bit(insn, 11);
|
||||||
|
|
||||||
|
data = MultipleLoad{ .regs = regs, .rb = rb, .load = load };
|
||||||
|
|
||||||
|
// Format 17: Software interrupt
|
||||||
|
} else if ((insn & 0xFF00) == 0xDF00) {
|
||||||
|
data = SoftwareInterrupt{};
|
||||||
|
|
||||||
|
// Format 16: Conditional branch
|
||||||
|
} else if ((insn & 0xF000) == 0xD000) {
|
||||||
|
uint8_t offset = bit_range(insn, 0, 7);
|
||||||
|
Condition condition = static_cast<Condition>(bit_range(insn, 8, 11));
|
||||||
|
|
||||||
|
data = ConditionalBranch{ .offset = offset, .condition = condition };
|
||||||
|
|
||||||
|
// Format 18: Unconditional branch
|
||||||
|
} else if ((insn & 0xF800) == 0xE000) {
|
||||||
|
uint16_t offset = bit_range(insn, 0, 10);
|
||||||
|
|
||||||
|
data = UnconditionalBranch{ .offset = offset };
|
||||||
|
|
||||||
|
// Format 19: Long branch with link
|
||||||
|
} else if ((insn & 0xF000) == 0xF000) {
|
||||||
|
uint16_t offset = bit_range(insn, 0, 10);
|
||||||
|
bool high = get_bit(insn, 11);
|
||||||
|
|
||||||
|
data = LongBranchWithLink{ .offset = offset, .high = high };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
230
src/cpu/thumb/instruction.hh
Normal file
230
src/cpu/thumb/instruction.hh
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "cpu/alu.hh"
|
||||||
|
#include "cpu/psr.hh"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fmt/ostream.h>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace matar {
|
||||||
|
namespace thumb {
|
||||||
|
|
||||||
|
template<class... Ts>
|
||||||
|
struct overloaded : Ts... {
|
||||||
|
using Ts::operator()...;
|
||||||
|
};
|
||||||
|
template<class... Ts>
|
||||||
|
overloaded(Ts...) -> overloaded<Ts...>;
|
||||||
|
|
||||||
|
static constexpr size_t INSTRUCTION_SIZE = 2;
|
||||||
|
|
||||||
|
struct MoveShiftedRegister {
|
||||||
|
uint8_t rd;
|
||||||
|
uint8_t rs;
|
||||||
|
uint8_t offset;
|
||||||
|
ShiftType opcode;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AddSubtract {
|
||||||
|
enum class OpCode {
|
||||||
|
ADD = 0,
|
||||||
|
SUB = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t rd;
|
||||||
|
uint8_t rs;
|
||||||
|
uint8_t offset;
|
||||||
|
OpCode opcode;
|
||||||
|
bool imm;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MovCmpAddSubImmediate {
|
||||||
|
enum class OpCode {
|
||||||
|
MOV = 0b00,
|
||||||
|
CMP = 0b01,
|
||||||
|
ADD = 0b10,
|
||||||
|
SUB = 0b11
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t offset;
|
||||||
|
uint8_t rd;
|
||||||
|
OpCode opcode;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AluOperations {
|
||||||
|
enum class OpCode {
|
||||||
|
AND = 0b0000,
|
||||||
|
EOR = 0b0001,
|
||||||
|
LSL = 0b0010,
|
||||||
|
LSR = 0b0011,
|
||||||
|
ASR = 0b0100,
|
||||||
|
ADC = 0b0101,
|
||||||
|
SBC = 0b0110,
|
||||||
|
ROR = 0b0111,
|
||||||
|
TST = 0b1000,
|
||||||
|
NEG = 0b1001,
|
||||||
|
CMP = 0b1010,
|
||||||
|
CMN = 0b1011,
|
||||||
|
ORR = 0b1100,
|
||||||
|
MUL = 0b1101,
|
||||||
|
BIC = 0b1110,
|
||||||
|
MVN = 0b1111
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t rd;
|
||||||
|
uint8_t rs;
|
||||||
|
OpCode opcode;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HiRegisterOperations {
|
||||||
|
enum class OpCode {
|
||||||
|
ADD = 0b00,
|
||||||
|
CMP = 0b01,
|
||||||
|
MOV = 0b10,
|
||||||
|
BX = 0b11
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t rd;
|
||||||
|
uint8_t rs;
|
||||||
|
bool hi_2;
|
||||||
|
bool hi_1;
|
||||||
|
OpCode opcode;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PcRelativeLoad {
|
||||||
|
uint8_t word;
|
||||||
|
uint8_t rd;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LoadStoreRegisterOffset {
|
||||||
|
uint8_t rd;
|
||||||
|
uint8_t rb;
|
||||||
|
uint8_t ro;
|
||||||
|
bool byte;
|
||||||
|
bool load;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LoadStoreSignExtendedHalfword {
|
||||||
|
uint8_t rd;
|
||||||
|
uint8_t rb;
|
||||||
|
uint8_t ro;
|
||||||
|
bool s;
|
||||||
|
bool h;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LoadStoreImmediateOffset {
|
||||||
|
uint8_t rd;
|
||||||
|
uint8_t rb;
|
||||||
|
uint8_t offset;
|
||||||
|
bool load;
|
||||||
|
bool byte;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LoadStoreHalfword {
|
||||||
|
uint8_t rd;
|
||||||
|
uint8_t rb;
|
||||||
|
uint8_t offset;
|
||||||
|
bool load;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpRelativeLoad {
|
||||||
|
uint8_t word;
|
||||||
|
uint8_t rd;
|
||||||
|
bool load;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LoadAddress {
|
||||||
|
uint8_t word;
|
||||||
|
uint8_t rd;
|
||||||
|
bool sp;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AddOffsetStackPointer {
|
||||||
|
uint8_t word;
|
||||||
|
bool sign;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PushPopRegister {
|
||||||
|
uint8_t regs;
|
||||||
|
bool pclr;
|
||||||
|
bool load;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MultipleLoad {
|
||||||
|
uint8_t regs;
|
||||||
|
uint8_t rb;
|
||||||
|
bool load;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConditionalBranch {
|
||||||
|
uint8_t offset;
|
||||||
|
Condition condition;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SoftwareInterrupt {};
|
||||||
|
|
||||||
|
struct UnconditionalBranch {
|
||||||
|
uint16_t offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LongBranchWithLink {
|
||||||
|
uint16_t offset;
|
||||||
|
bool high;
|
||||||
|
};
|
||||||
|
|
||||||
|
using InstructionData = std::variant<MoveShiftedRegister,
|
||||||
|
AddSubtract,
|
||||||
|
MovCmpAddSubImmediate,
|
||||||
|
AluOperations,
|
||||||
|
HiRegisterOperations,
|
||||||
|
PcRelativeLoad,
|
||||||
|
LoadStoreRegisterOffset,
|
||||||
|
LoadStoreSignExtendedHalfword,
|
||||||
|
LoadStoreImmediateOffset,
|
||||||
|
LoadStoreHalfword,
|
||||||
|
SpRelativeLoad,
|
||||||
|
LoadAddress,
|
||||||
|
AddOffsetStackPointer,
|
||||||
|
PushPopRegister,
|
||||||
|
MultipleLoad,
|
||||||
|
ConditionalBranch,
|
||||||
|
SoftwareInterrupt,
|
||||||
|
UnconditionalBranch,
|
||||||
|
LongBranchWithLink>;
|
||||||
|
|
||||||
|
struct Instruction {
|
||||||
|
InstructionData data;
|
||||||
|
|
||||||
|
Instruction(uint16_t insn);
|
||||||
|
|
||||||
|
std::string disassemble();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream&
|
||||||
|
operator<<(std::ostream& os, const AddSubtract::OpCode cond);
|
||||||
|
|
||||||
|
std::ostream&
|
||||||
|
operator<<(std::ostream& os, const MovCmpAddSubImmediate::OpCode cond);
|
||||||
|
|
||||||
|
std::ostream&
|
||||||
|
operator<<(std::ostream& os, const AluOperations::OpCode cond);
|
||||||
|
|
||||||
|
std::ostream&
|
||||||
|
operator<<(std::ostream& os, const HiRegisterOperations::OpCode cond);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace fmt {
|
||||||
|
template<>
|
||||||
|
struct formatter<matar::thumb::AddSubtract::OpCode> : ostream_formatter {};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct formatter<matar::thumb::MovCmpAddSubImmediate::OpCode>
|
||||||
|
: ostream_formatter {};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct formatter<matar::thumb::AluOperations::OpCode> : ostream_formatter {};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct formatter<matar::thumb::HiRegisterOperations::OpCode>
|
||||||
|
: ostream_formatter {};
|
||||||
|
}
|
||||||
3
src/cpu/thumb/meson.build
Normal file
3
src/cpu/thumb/meson.build
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
lib_sources += files(
|
||||||
|
'instruction.cc'
|
||||||
|
)
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#include "memory.hh"
|
#include "memory.hh"
|
||||||
#include "header.hh"
|
#include "header.hh"
|
||||||
#include "util/bits.hh"
|
#include "util/bits.hh"
|
||||||
|
#include "util/crypto.hh"
|
||||||
#include "util/log.hh"
|
#include "util/log.hh"
|
||||||
#include "util/utils.hh"
|
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
|||||||
@@ -14,19 +14,19 @@ get_bit(Int num, size_t n) {
|
|||||||
template<std::integral Int>
|
template<std::integral Int>
|
||||||
inline void
|
inline void
|
||||||
set_bit(Int& num, size_t n) {
|
set_bit(Int& num, size_t n) {
|
||||||
num |= (1 << n);
|
num |= (static_cast<Int>(1) << n);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<std::integral Int>
|
template<std::integral Int>
|
||||||
inline void
|
inline void
|
||||||
rst_bit(Int& num, size_t n) {
|
rst_bit(Int& num, size_t n) {
|
||||||
num &= ~(1 << n);
|
num &= ~(static_cast<Int>(1) << n);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<std::integral Int>
|
template<std::integral Int>
|
||||||
inline void
|
inline void
|
||||||
chg_bit(Int& num, size_t n, bool x) {
|
chg_bit(Int& num, size_t n, bool x) {
|
||||||
num = (num & ~(1 << n)) | (x << n);
|
num = (num & ~(static_cast<Int>(1) << n)) | (static_cast<Int>(x) << n);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// read range of bits from start to end inclusive
|
/// read range of bits from start to end inclusive
|
||||||
@@ -36,5 +36,5 @@ bit_range(Int num, size_t start, size_t end) {
|
|||||||
// NOTE: we do not require -1 if it is a signed integral
|
// NOTE: we do not require -1 if it is a signed integral
|
||||||
Int left =
|
Int left =
|
||||||
std::numeric_limits<Int>::digits - (std::is_unsigned<Int>::value) - end;
|
std::numeric_limits<Int>::digits - (std::is_unsigned<Int>::value) - end;
|
||||||
return num << left >> (left + start);
|
return static_cast<Int>(num << left) >> (left + start);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
|
|
||||||
namespace logging {
|
namespace logging {
|
||||||
namespace ansi {
|
namespace ansi {
|
||||||
static constexpr std::string_view RED = "\033[31m";
|
static constexpr auto RED = "\033[31m";
|
||||||
static constexpr std::string_view YELLOW = "\033[33m";
|
static constexpr auto YELLOW = "\033[33m";
|
||||||
static constexpr std::string_view MAGENTA = "\033[35m";
|
static constexpr auto MAGENTA = "\033[35m";
|
||||||
static constexpr std::string_view WHITE = "\033[37m";
|
static constexpr auto WHITE = "\033[37m";
|
||||||
static constexpr std::string_view BOLD = "\033[1m";
|
static constexpr auto BOLD = "\033[1m";
|
||||||
static constexpr std::string_view RESET = "\033[0m";
|
static constexpr auto RESET = "\033[0m";
|
||||||
}
|
}
|
||||||
|
|
||||||
using fmt::print;
|
using fmt::print;
|
||||||
@@ -20,8 +20,9 @@ class Logger {
|
|||||||
using LogLevel = matar::LogLevel;
|
using LogLevel = matar::LogLevel;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Logger(LogLevel level = LogLevel::Debug)
|
Logger(LogLevel level = LogLevel::Debug, FILE* stream = stderr)
|
||||||
: level(0) {
|
: level(0)
|
||||||
|
, stream(stream) {
|
||||||
set_level(level);
|
set_level(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,14 +70,14 @@ class Logger {
|
|||||||
void set_level(LogLevel level) {
|
void set_level(LogLevel level) {
|
||||||
this->level = (static_cast<uint8_t>(level) << 1) - 1;
|
this->level = (static_cast<uint8_t>(level) << 1) - 1;
|
||||||
}
|
}
|
||||||
void set_stream(std::ostream& stream) { this->stream = stream; }
|
void set_stream(FILE* stream) { this->stream = stream; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t level;
|
uint8_t level;
|
||||||
std::reference_wrapper<std::ostream> stream = std::clog;
|
FILE* stream;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
extern logging::Logger glogger;
|
extern logging::Logger glogger;
|
||||||
|
|
||||||
#define debug(x) logger.debug("{} = {}", #x, x);
|
#define debug(x) glogger.debug("{} = {}", #x, x);
|
||||||
|
|||||||
43
tests/bus.cc
Normal file
43
tests/bus.cc
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#include "bus.hh"
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
static constexpr auto TAG = "[bus]";
|
||||||
|
|
||||||
|
using namespace matar;
|
||||||
|
|
||||||
|
class BusFixture {
|
||||||
|
public:
|
||||||
|
BusFixture()
|
||||||
|
: bus(Memory(std::array<uint8_t, Memory::BIOS_SIZE>(),
|
||||||
|
std::vector<uint8_t>(Header::HEADER_SIZE))) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Bus bus;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(BusFixture, "Byte", TAG) {
|
||||||
|
CHECK(bus.read_byte(3349) == 0);
|
||||||
|
|
||||||
|
bus.write_byte(3349, 0xEC);
|
||||||
|
CHECK(bus.read_byte(3349) == 0xEC);
|
||||||
|
CHECK(bus.read_word(3349) == 0xEC);
|
||||||
|
CHECK(bus.read_halfword(3349) == 0xEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(BusFixture, "Halfword", TAG) {
|
||||||
|
CHECK(bus.read_halfword(33750745) == 0);
|
||||||
|
|
||||||
|
bus.write_halfword(33750745, 0x1A4A);
|
||||||
|
CHECK(bus.read_halfword(33750745) == 0x1A4A);
|
||||||
|
CHECK(bus.read_word(33750745) == 0x1A4A);
|
||||||
|
CHECK(bus.read_byte(33750745) == 0x4A);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(BusFixture, "Word", TAG) {
|
||||||
|
CHECK(bus.read_word(100724276) == 0);
|
||||||
|
|
||||||
|
bus.write_word(100724276, 0x3ACC491D);
|
||||||
|
CHECK(bus.read_word(100724276) == 0x3ACC491D);
|
||||||
|
CHECK(bus.read_halfword(100724276) == 0x491D);
|
||||||
|
CHECK(bus.read_byte(100724276) == 0x1D);
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ class CpuFixture {
|
|||||||
std::vector<uint8_t>(Header::HEADER_SIZE)))) {}
|
std::vector<uint8_t>(Header::HEADER_SIZE)))) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// TODO: test with other conditions
|
|
||||||
void exec(arm::InstructionData data, Condition condition = Condition::AL) {
|
void exec(arm::InstructionData data, Condition condition = Condition::AL) {
|
||||||
arm::Instruction instruction(condition, data);
|
arm::Instruction instruction(condition, data);
|
||||||
cpu.exec_arm(instruction);
|
cpu.exec_arm(instruction);
|
||||||
@@ -32,7 +31,7 @@ class CpuFixture {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
#define TAG "arm execution"
|
static constexpr auto TAG = "[arm][execution]";
|
||||||
|
|
||||||
using namespace arm;
|
using namespace arm;
|
||||||
|
|
||||||
@@ -804,29 +803,41 @@ TEST_CASE_METHOD(CpuFixture, "Data Processing", TAG) {
|
|||||||
processing->rn = 7;
|
processing->rn = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto flags = [this](bool n, bool z, bool v, bool c) {
|
auto reset_flags = [this]() {
|
||||||
CHECK(cpu.cpsr.n() == n);
|
|
||||||
CHECK(cpu.cpsr.z() == z);
|
|
||||||
CHECK(cpu.cpsr.v() == v);
|
|
||||||
CHECK(cpu.cpsr.c() == c);
|
|
||||||
|
|
||||||
cpu.cpsr.set_n(false);
|
cpu.cpsr.set_n(false);
|
||||||
cpu.cpsr.set_z(false);
|
cpu.cpsr.set_z(false);
|
||||||
cpu.cpsr.set_v(false);
|
cpu.cpsr.set_v(false);
|
||||||
cpu.cpsr.set_c(false);
|
cpu.cpsr.set_c(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto flags = [this, reset_flags](bool n, bool z, bool v, bool c) {
|
||||||
|
CHECK(cpu.cpsr.n() == n);
|
||||||
|
CHECK(cpu.cpsr.z() == z);
|
||||||
|
CHECK(cpu.cpsr.v() == v);
|
||||||
|
CHECK(cpu.cpsr.c() == c);
|
||||||
|
reset_flags();
|
||||||
|
};
|
||||||
|
|
||||||
// immediate operand
|
// immediate operand
|
||||||
processing->operand = static_cast<uint32_t>(54924809);
|
processing->operand = static_cast<uint32_t>(54924809);
|
||||||
// rs
|
// rs
|
||||||
cpu.gpr[12] = 2;
|
cpu.gpr[12] = 2;
|
||||||
cpu.gpr[5] = 0;
|
cpu.gpr[5] = 0;
|
||||||
|
reset_flags();
|
||||||
|
|
||||||
SECTION("AND") {
|
SECTION("AND (with condition check)") {
|
||||||
processing->opcode = OpCode::AND;
|
processing->opcode = OpCode::AND;
|
||||||
exec(data);
|
cpu.cpsr.set_z(false);
|
||||||
|
exec(data, Condition::EQ);
|
||||||
|
|
||||||
|
// condition is false
|
||||||
|
CHECK(cpu.gpr[5] == 0);
|
||||||
|
|
||||||
|
cpu.cpsr.set_z(true);
|
||||||
|
exec(data, Condition::EQ);
|
||||||
|
|
||||||
// -28717 & 54924809
|
// -28717 & 54924809
|
||||||
|
// condition is true now
|
||||||
CHECK(cpu.gpr[5] == 54920705);
|
CHECK(cpu.gpr[5] == 54920705);
|
||||||
|
|
||||||
// check set flags
|
// check set flags
|
||||||
@@ -846,11 +857,19 @@ TEST_CASE_METHOD(CpuFixture, "Data Processing", TAG) {
|
|||||||
flags(false, false, false, false);
|
flags(false, false, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("EOR") {
|
SECTION("EOR (with condition check)") {
|
||||||
processing->opcode = OpCode::EOR;
|
processing->opcode = OpCode::EOR;
|
||||||
exec(data);
|
cpu.cpsr.set_c(true);
|
||||||
|
exec(data, Condition::CC);
|
||||||
|
|
||||||
|
// condition fails
|
||||||
|
CHECK(cpu.gpr[5] == 0);
|
||||||
|
|
||||||
|
cpu.cpsr.set_c(false);
|
||||||
|
exec(data, Condition::CC);
|
||||||
|
|
||||||
// -28717 ^ 54924809
|
// -28717 ^ 54924809
|
||||||
|
// condition is true now
|
||||||
CHECK(cpu.gpr[5] == 4240021978);
|
CHECK(cpu.gpr[5] == 4240021978);
|
||||||
|
|
||||||
// check set flags
|
// check set flags
|
||||||
@@ -1051,5 +1070,3 @@ TEST_CASE_METHOD(CpuFixture, "Data Processing", TAG) {
|
|||||||
CHECK(cpu.spsr.raw() == cpu.cpsr.raw());
|
CHECK(cpu.spsr.raw() == cpu.cpsr.raw());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef TAG
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "cpu/arm/instruction.hh"
|
#include "cpu/arm/instruction.hh"
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
#define TAG "disassembler"
|
static constexpr auto TAG = "[arm][disassembly]";
|
||||||
|
|
||||||
using namespace matar;
|
using namespace matar;
|
||||||
using namespace arm;
|
using namespace arm;
|
||||||
@@ -467,5 +467,3 @@ TEST_CASE("Software Interrupt", TAG) {
|
|||||||
CHECK(instruction.condition == Condition::EQ);
|
CHECK(instruction.condition == Condition::EQ);
|
||||||
CHECK(instruction.disassemble() == "SWIEQ");
|
CHECK(instruction.disassemble() == "SWIEQ");
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef TAG
|
|
||||||
|
|||||||
121
tests/memory.cc
Normal file
121
tests/memory.cc
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#include "memory.hh"
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
static constexpr auto TAG = "[memory]";
|
||||||
|
|
||||||
|
using namespace matar;
|
||||||
|
|
||||||
|
class MemFixture {
|
||||||
|
public:
|
||||||
|
MemFixture()
|
||||||
|
: memory(std::array<uint8_t, Memory::BIOS_SIZE>(),
|
||||||
|
std::vector<uint8_t>(Header::HEADER_SIZE)) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Memory memory;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(MemFixture, "bios", TAG) {
|
||||||
|
memory.write(0, 0xAC);
|
||||||
|
CHECK(memory.read(0) == 0xAC);
|
||||||
|
|
||||||
|
memory.write(0x3FFF, 0x48);
|
||||||
|
CHECK(memory.read(0x3FFF) == 0x48);
|
||||||
|
|
||||||
|
memory.write(0x2A56, 0x10);
|
||||||
|
CHECK(memory.read(0x2A56) == 0x10);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(MemFixture, "board wram", TAG) {
|
||||||
|
memory.write(0x2000000, 0xAC);
|
||||||
|
CHECK(memory.read(0x2000000) == 0xAC);
|
||||||
|
|
||||||
|
memory.write(0x203FFFF, 0x48);
|
||||||
|
CHECK(memory.read(0x203FFFF) == 0x48);
|
||||||
|
|
||||||
|
memory.write(0x2022A56, 0x10);
|
||||||
|
CHECK(memory.read(0x2022A56) == 0x10);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(MemFixture, "chip wram", TAG) {
|
||||||
|
memory.write(0x3000000, 0xAC);
|
||||||
|
CHECK(memory.read(0x3000000) == 0xAC);
|
||||||
|
|
||||||
|
memory.write(0x3007FFF, 0x48);
|
||||||
|
CHECK(memory.read(0x3007FFF) == 0x48);
|
||||||
|
|
||||||
|
memory.write(0x3002A56, 0x10);
|
||||||
|
CHECK(memory.read(0x3002A56) == 0x10);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(MemFixture, "palette ram", TAG) {
|
||||||
|
memory.write(0x5000000, 0xAC);
|
||||||
|
CHECK(memory.read(0x5000000) == 0xAC);
|
||||||
|
|
||||||
|
memory.write(0x50003FF, 0x48);
|
||||||
|
CHECK(memory.read(0x50003FF) == 0x48);
|
||||||
|
|
||||||
|
memory.write(0x5000156, 0x10);
|
||||||
|
CHECK(memory.read(0x5000156) == 0x10);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(MemFixture, "video ram", TAG) {
|
||||||
|
memory.write(0x6000000, 0xAC);
|
||||||
|
CHECK(memory.read(0x6000000) == 0xAC);
|
||||||
|
|
||||||
|
memory.write(0x6017FFF, 0x48);
|
||||||
|
CHECK(memory.read(0x6017FFF) == 0x48);
|
||||||
|
|
||||||
|
memory.write(0x6012A56, 0x10);
|
||||||
|
CHECK(memory.read(0x6012A56) == 0x10);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(MemFixture, "oam obj ram", TAG) {
|
||||||
|
memory.write(0x7000000, 0xAC);
|
||||||
|
CHECK(memory.read(0x7000000) == 0xAC);
|
||||||
|
|
||||||
|
memory.write(0x70003FF, 0x48);
|
||||||
|
CHECK(memory.read(0x70003FF) == 0x48);
|
||||||
|
|
||||||
|
memory.write(0x7000156, 0x10);
|
||||||
|
CHECK(memory.read(0x7000156) == 0x10);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("rom", TAG) {
|
||||||
|
// 32 megabyte ROM
|
||||||
|
Memory memory(std::array<uint8_t, Memory::BIOS_SIZE>(),
|
||||||
|
std::vector<uint8_t>(32 * 1024 * 1024));
|
||||||
|
|
||||||
|
SECTION("ROM1") {
|
||||||
|
memory.write(0x8000000, 0xAC);
|
||||||
|
CHECK(memory.read(0x8000000) == 0xAC);
|
||||||
|
|
||||||
|
memory.write(0x9FFFFFF, 0x48);
|
||||||
|
CHECK(memory.read(0x9FFFFFF) == 0x48);
|
||||||
|
|
||||||
|
memory.write(0x8ef0256, 0x10);
|
||||||
|
CHECK(memory.read(0x8ef0256) == 0x10);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ROM2") {
|
||||||
|
memory.write(0xA000000, 0xAC);
|
||||||
|
CHECK(memory.read(0xA000000) == 0xAC);
|
||||||
|
|
||||||
|
memory.write(0xBFFFFFF, 0x48);
|
||||||
|
CHECK(memory.read(0xBFFFFFF) == 0x48);
|
||||||
|
|
||||||
|
memory.write(0xAEF0256, 0x10);
|
||||||
|
CHECK(memory.read(0xAEF0256) == 0x10);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ROM3") {
|
||||||
|
memory.write(0xC000000, 0xAC);
|
||||||
|
CHECK(memory.read(0xC000000) == 0xAC);
|
||||||
|
|
||||||
|
memory.write(0xDFFFFFF, 0x48);
|
||||||
|
CHECK(memory.read(0xDFFFFFF) == 0x48);
|
||||||
|
|
||||||
|
memory.write(0xCEF0256, 0x10);
|
||||||
|
CHECK(memory.read(0xCEF0256) == 0x10);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,10 +5,13 @@ tests_deps = [
|
|||||||
src = include_directories('../src')
|
src = include_directories('../src')
|
||||||
|
|
||||||
tests_sources = files(
|
tests_sources = files(
|
||||||
'main.cc'
|
'main.cc',
|
||||||
|
'bus.cc',
|
||||||
|
'memory.cc'
|
||||||
)
|
)
|
||||||
|
|
||||||
subdir('cpu')
|
subdir('cpu')
|
||||||
|
subdir('util')
|
||||||
|
|
||||||
catch2 = dependency('catch2', version: '>=3.4.0', static: true)
|
catch2 = dependency('catch2', version: '>=3.4.0', static: true)
|
||||||
catch2_tests = executable(
|
catch2_tests = executable(
|
||||||
|
|||||||
106
tests/util/bits.cc
Normal file
106
tests/util/bits.cc
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#include "util/bits.hh"
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
static constexpr auto TAG = "[util][bits]";
|
||||||
|
|
||||||
|
TEST_CASE("8 bits", TAG) {
|
||||||
|
uint8_t num = 45;
|
||||||
|
|
||||||
|
CHECK(get_bit(num, 0));
|
||||||
|
CHECK(!get_bit(num, 1));
|
||||||
|
CHECK(get_bit(num, 5));
|
||||||
|
CHECK(!get_bit(num, 6));
|
||||||
|
CHECK(!get_bit(num, 7));
|
||||||
|
|
||||||
|
set_bit(num, 6);
|
||||||
|
CHECK(get_bit(num, 6));
|
||||||
|
|
||||||
|
rst_bit(num, 6);
|
||||||
|
CHECK(!get_bit(num, 6));
|
||||||
|
|
||||||
|
chg_bit(num, 5, false);
|
||||||
|
CHECK(!get_bit(num, 5));
|
||||||
|
|
||||||
|
chg_bit(num, 5, true);
|
||||||
|
CHECK(get_bit(num, 5));
|
||||||
|
|
||||||
|
// 0b0110
|
||||||
|
CHECK(bit_range(num, 1, 4) == 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("16 bits", TAG) {
|
||||||
|
uint16_t num = 34587;
|
||||||
|
|
||||||
|
CHECK(get_bit(num, 0));
|
||||||
|
CHECK(get_bit(num, 1));
|
||||||
|
CHECK(!get_bit(num, 5));
|
||||||
|
CHECK(!get_bit(num, 14));
|
||||||
|
CHECK(get_bit(num, 15));
|
||||||
|
|
||||||
|
set_bit(num, 14);
|
||||||
|
CHECK(get_bit(num, 14));
|
||||||
|
|
||||||
|
rst_bit(num, 14);
|
||||||
|
CHECK(!get_bit(num, 14));
|
||||||
|
|
||||||
|
chg_bit(num, 5, true);
|
||||||
|
CHECK(get_bit(num, 5));
|
||||||
|
|
||||||
|
// num = 45
|
||||||
|
chg_bit(num, 5, false);
|
||||||
|
CHECK(!get_bit(num, 5));
|
||||||
|
|
||||||
|
// 0b1000110
|
||||||
|
CHECK(bit_range(num, 2, 8) == 70);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("32 bits", TAG) {
|
||||||
|
uint32_t num = 3194142523;
|
||||||
|
|
||||||
|
CHECK(get_bit(num, 0));
|
||||||
|
CHECK(get_bit(num, 1));
|
||||||
|
CHECK(get_bit(num, 12));
|
||||||
|
CHECK(get_bit(num, 29));
|
||||||
|
CHECK(!get_bit(num, 30));
|
||||||
|
CHECK(get_bit(num, 31));
|
||||||
|
|
||||||
|
set_bit(num, 30);
|
||||||
|
CHECK(get_bit(num, 30));
|
||||||
|
|
||||||
|
rst_bit(num, 30);
|
||||||
|
CHECK(!get_bit(num, 30));
|
||||||
|
|
||||||
|
chg_bit(num, 12, false);
|
||||||
|
CHECK(!get_bit(num, 12));
|
||||||
|
|
||||||
|
chg_bit(num, 12, true);
|
||||||
|
CHECK(get_bit(num, 12));
|
||||||
|
|
||||||
|
// 0b10011000101011111100111
|
||||||
|
CHECK(bit_range(num, 3, 25) == 5003239);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("64 bits", TAG) {
|
||||||
|
uint64_t num = 58943208889991935;
|
||||||
|
|
||||||
|
CHECK(get_bit(num, 0));
|
||||||
|
CHECK(get_bit(num, 1));
|
||||||
|
CHECK(!get_bit(num, 10));
|
||||||
|
CHECK(get_bit(num, 55));
|
||||||
|
CHECK(!get_bit(num, 60));
|
||||||
|
|
||||||
|
set_bit(num, 63);
|
||||||
|
CHECK(get_bit(num, 63));
|
||||||
|
|
||||||
|
rst_bit(num, 63);
|
||||||
|
CHECK(!get_bit(num, 63));
|
||||||
|
|
||||||
|
chg_bit(num, 10, true);
|
||||||
|
CHECK(get_bit(num, 10));
|
||||||
|
|
||||||
|
chg_bit(num, 10, false);
|
||||||
|
CHECK(!get_bit(num, 10));
|
||||||
|
|
||||||
|
// 0b011010001
|
||||||
|
CHECK(bit_range(num, 39, 47) == 209);
|
||||||
|
}
|
||||||
21
tests/util/crypto.cc
Normal file
21
tests/util/crypto.cc
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#include "util/crypto.hh"
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
static constexpr auto TAG = "[util][crypto]";
|
||||||
|
|
||||||
|
TEST_CASE("sha256 matar", TAG) {
|
||||||
|
std::array<uint8_t, 5> data = { 'm', 'a', 't', 'a', 'r' };
|
||||||
|
|
||||||
|
CHECK(crypto::sha256(data) ==
|
||||||
|
"3b02a908fd5743c0e868675bb6ae77d2a62b3b5f7637413238e2a1e0e94b6a53");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("sha256 forgis", TAG) {
|
||||||
|
std::array<uint8_t, 32> data = { 'i', ' ', 'p', 'u', 't', ' ', 't', 'h',
|
||||||
|
'e', ' ', 'n', 'e', 'w', ' ', 'f', 'o',
|
||||||
|
'r', 'g', 'i', 's', ' ', 'o', 'n', ' ',
|
||||||
|
't', 'h', 'e', ' ', 'j', 'e', 'e', 'p' };
|
||||||
|
|
||||||
|
CHECK(crypto::sha256(data) ==
|
||||||
|
"cfddca2ce2673f355518cbe2df2a8522693c54723a469e8b36a4f68b90d2b759");
|
||||||
|
}
|
||||||
4
tests/util/meson.build
Normal file
4
tests/util/meson.build
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
tests_sources += files(
|
||||||
|
'bits.cc',
|
||||||
|
'crypto.cc'
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user